sardonyx_ring 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 21e10589ebb0785648933d6d233a93102df20e1621e709cae7e92c2d2ecabb16
4
+ data.tar.gz: db44bd9ab5c3456395582bf6aa97c01700043ae8073b8afe9dba1f23467be41c
5
+ SHA512:
6
+ metadata.gz: 3846466f0474c23f1c2c76252b96bf303bfbc6351914acd0d9bdc6938c840826ce193347d0740e4e4079de10ef843ef5d2a2e0f3be0e907c92f53cddae361401
7
+ data.tar.gz: 4db71b79644b7571e5987605410e84a9f611c54d7ccb316c7cfb61c3b55e2cff606c86e13571a304cb2960d8b9b70a6cc42e923eb705a506db4c36eb5a1ea1a3
data/.editorconfig ADDED
@@ -0,0 +1,6 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ indent_size = 2
@@ -0,0 +1,17 @@
1
+ name: Ruby
2
+ on: [push, pull_request]
3
+ jobs:
4
+ spec:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
8
+ ruby: [2.7, 3.0, head]
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - uses: ruby/setup-ruby@v1
13
+ with:
14
+ ruby-version: ${{ matrix.ruby }}
15
+ bundler-cache: true
16
+ - run: bundle exec rubocop
17
+ - run: bundle exec rake test
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,23 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 2.7
4
+ SuggestExtensions: false
5
+
6
+ Layout/LineLength:
7
+ Max: 120
8
+
9
+ Metrics/BlockLength:
10
+ Exclude:
11
+ - 'example.rb'
12
+
13
+ Metrics/ClassLength:
14
+ Enabled: false
15
+
16
+ Metrics/MethodLength:
17
+ Max: 20
18
+
19
+ Style/Documentation:
20
+ Enabled: false
21
+
22
+ Style/OpenStructUse:
23
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in sardonyx_ring.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rubocop', '~> 1.23'
data/Gemfile.lock ADDED
@@ -0,0 +1,83 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sardonyx_ring (0.1.0)
5
+ eventmachine
6
+ faye-websocket
7
+ parse-cron
8
+ slack-ruby-client
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ ast (2.4.2)
14
+ eventmachine (1.2.7)
15
+ faraday (1.8.0)
16
+ faraday-em_http (~> 1.0)
17
+ faraday-em_synchrony (~> 1.0)
18
+ faraday-excon (~> 1.1)
19
+ faraday-httpclient (~> 1.0.1)
20
+ faraday-net_http (~> 1.0)
21
+ faraday-net_http_persistent (~> 1.1)
22
+ faraday-patron (~> 1.0)
23
+ faraday-rack (~> 1.0)
24
+ multipart-post (>= 1.2, < 3)
25
+ ruby2_keywords (>= 0.0.4)
26
+ faraday-em_http (1.0.0)
27
+ faraday-em_synchrony (1.0.0)
28
+ faraday-excon (1.1.0)
29
+ faraday-httpclient (1.0.1)
30
+ faraday-net_http (1.0.1)
31
+ faraday-net_http_persistent (1.2.0)
32
+ faraday-patron (1.0.0)
33
+ faraday-rack (1.0.0)
34
+ faraday_middleware (1.2.0)
35
+ faraday (~> 1.0)
36
+ faye-websocket (0.11.1)
37
+ eventmachine (>= 0.12.0)
38
+ websocket-driver (>= 0.5.1)
39
+ gli (2.20.1)
40
+ hashie (5.0.0)
41
+ multipart-post (2.1.1)
42
+ parallel (1.21.0)
43
+ parse-cron (0.1.4)
44
+ parser (3.0.3.1)
45
+ ast (~> 2.4.1)
46
+ rainbow (3.0.0)
47
+ rake (13.0.6)
48
+ regexp_parser (2.2.0)
49
+ rexml (3.2.5)
50
+ rubocop (1.23.0)
51
+ parallel (~> 1.10)
52
+ parser (>= 3.0.0.0)
53
+ rainbow (>= 2.2.2, < 4.0)
54
+ regexp_parser (>= 1.8, < 3.0)
55
+ rexml
56
+ rubocop-ast (>= 1.12.0, < 2.0)
57
+ ruby-progressbar (~> 1.7)
58
+ unicode-display_width (>= 1.4.0, < 3.0)
59
+ rubocop-ast (1.14.0)
60
+ parser (>= 3.0.1.1)
61
+ ruby-progressbar (1.11.0)
62
+ ruby2_keywords (0.0.5)
63
+ slack-ruby-client (0.17.0)
64
+ faraday (>= 1.0)
65
+ faraday_middleware
66
+ gli
67
+ hashie
68
+ websocket-driver
69
+ unicode-display_width (2.1.0)
70
+ websocket-driver (0.7.5)
71
+ websocket-extensions (>= 0.1.0)
72
+ websocket-extensions (0.1.5)
73
+
74
+ PLATFORMS
75
+ x86_64-linux
76
+
77
+ DEPENDENCIES
78
+ rake (~> 13.0)
79
+ rubocop (~> 1.23)
80
+ sardonyx_ring!
81
+
82
+ BUNDLED WITH
83
+ 2.2.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 rutan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # SardonyxRing
2
+
3
+ > Slack bot framework
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'sardonyx_ring'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install sardonyx_ring
20
+
21
+ ## Usage
22
+
23
+ see [example.rb](https://github.com/rutan/sardonyx_ring/blob/main/example.rb)
24
+
25
+ ## License
26
+
27
+ [MIT License](https://opensource.org/licenses/MIT)
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+ require 'rake/testtask'
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ desc 'test'
10
+ Rake::TestTask.new do |t|
11
+ t.libs << 'test'
12
+ t.test_files = FileList['test/test*.rb']
13
+ t.verbose = true
14
+ end
15
+
16
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'sardonyx_ring'
6
+
7
+ require 'irb'
8
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/example.rb ADDED
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sardonyx_ring'
4
+
5
+ class ExampleBot < SardonyxRing::App
6
+ def greeting
7
+ rand < 0.5 ? 'Hello,' : 'Fooo!'
8
+ end
9
+
10
+ def lottery_result
11
+ rand < 0.5 ? 'GOOD!' : 'BAD!'
12
+ end
13
+
14
+ message 'Hello' do |message|
15
+ message.say(text: "#{greeting} <@#{message.user}>!")
16
+ end
17
+
18
+ message 'modal' do |message|
19
+ message.say(
20
+ text: 'please click button!',
21
+ blocks: [
22
+ {
23
+ type: 'actions',
24
+ elements: [
25
+ {
26
+ type: 'button',
27
+ action_id: 'click_open_button',
28
+ text: {
29
+ type: 'plain_text',
30
+ text: 'Open Modal'
31
+ },
32
+ value: 'OpenModal'
33
+ },
34
+ {
35
+ type: 'button',
36
+ action_id: 'click_close_button',
37
+ text: {
38
+ type: 'plain_text',
39
+ text: 'Close'
40
+ },
41
+ value: 'Close'
42
+ }
43
+ ]
44
+ }
45
+ ]
46
+ )
47
+ end
48
+
49
+ action 'click_open_button' do |event|
50
+ client.views_open(
51
+ trigger_id: event.raw_payload.trigger_id,
52
+ view: {
53
+ type: 'modal',
54
+ callback_id: 'modal',
55
+ title: {
56
+ type: 'plain_text',
57
+ text: 'Sample Modal'
58
+ },
59
+ submit: {
60
+ type: 'plain_text',
61
+ text: 'Submit'
62
+ },
63
+ blocks: [
64
+ {
65
+ type: 'input',
66
+ block_id: 'title_section',
67
+ label: {
68
+ type: 'plain_text',
69
+ text: 'title'
70
+ },
71
+ element: {
72
+ type: 'plain_text_input',
73
+ action_id: 'title_value',
74
+ placeholder: {
75
+ type: 'plain_text',
76
+ text: 'Enter article title'
77
+ }
78
+ }
79
+ },
80
+ {
81
+ type: 'input',
82
+ block_id: 'body_section',
83
+ label: {
84
+ type: 'plain_text',
85
+ text: 'body'
86
+ },
87
+ element: {
88
+ type: 'plain_text_input',
89
+ action_id: 'body_value',
90
+ multiline: true,
91
+ placeholder: {
92
+ type: 'plain_text',
93
+ text: 'Enter article body'
94
+ }
95
+ }
96
+ }
97
+ ]
98
+ }
99
+ )
100
+ end
101
+
102
+ view 'modal' do |event|
103
+ client.chat_postMessage(
104
+ channel: event.raw_payload.user.id,
105
+ text: 'You posted',
106
+ attachments: [
107
+ {
108
+ title: event.state.values.title_section.title_value.value,
109
+ text: event.state.values.body_section.body_value.value
110
+ }
111
+ ]
112
+ )
113
+ end
114
+
115
+ action 'click_close_button' do |event|
116
+ client.chat_update(
117
+ channel: event.raw_payload.channel.id,
118
+ ts: event.raw_payload.message.ts,
119
+ blocks: [],
120
+ attachments: [
121
+ {
122
+ text: 'This button is closed'
123
+ }
124
+ ]
125
+ )
126
+ end
127
+
128
+ message(/\A(\d+)\z/) do |message, match|
129
+ message.say(text: (match[1].to_i * 2).to_s)
130
+ end
131
+
132
+ message(/.+/) do |message|
133
+ message.say(
134
+ text: "This is <@#{message.user}>'s message!",
135
+ attachments: [
136
+ {
137
+ fallback: message.text,
138
+ text: message.text
139
+ }
140
+ ]
141
+ )
142
+ end
143
+
144
+ event 'reaction_added' do |event|
145
+ client.chat_postMessage(
146
+ text: "reaction :#{event.reaction}: added by <@#{event.user}> !",
147
+ channel: event.item.channel
148
+ )
149
+ end
150
+
151
+ every '* * * * *' do
152
+ channel = ENV['CRON_CHANNEL_ID']
153
+ return unless channel
154
+
155
+ client.chat_postMessage(
156
+ text: "current time is #{Time.now}",
157
+ channel: channel
158
+ )
159
+ end
160
+ end
161
+
162
+ ExampleBot.new(
163
+ app_token: ENV['SLACK_APP_TOKEN'],
164
+ bot_token: ENV['SLACK_BOT_TOKEN'],
165
+ logger: Logger.new($stdout, level: :debug)
166
+ ).socket_start!
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ class App
5
+ include DSL
6
+
7
+ def initialize(options = {})
8
+ @app_token = options[:app_token]
9
+ @bot_token = options[:bot_token]
10
+ @bot_auth = nil
11
+ @logger = options[:logger] || Logger.new($stdout, level: :info)
12
+ end
13
+
14
+ def client
15
+ @client ||= Slack::Web::Client.new(token: @bot_token)
16
+ end
17
+
18
+ def socket_start!
19
+ fetch_bot_auth
20
+
21
+ EM.run do
22
+ start_cron
23
+
24
+ socket_client = Services::SlackSocketClient.new(
25
+ token: @app_token,
26
+ logger: @logger
27
+ )
28
+ socket_client.on_message(&method(:handle_event))
29
+ socket_client.connect!
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def start_cron
36
+ cron_handlers.each do |handler|
37
+ register_next_cron(handler)
38
+ end
39
+ end
40
+
41
+ def register_next_cron(cron_handler)
42
+ time = Time.now
43
+ next_time = [(cron_handler.next_time(time) - time).ceil, 1].max
44
+ EventMachine::Timer.new(next_time) do
45
+ cron_handler.run(self)
46
+ register_next_cron(cron_handler)
47
+ end
48
+ end
49
+
50
+ def fetch_bot_auth
51
+ resp = client.auth_test
52
+ raise 'auth error' unless resp['ok']
53
+
54
+ @bot_auth = OpenStruct.new(resp)
55
+ end
56
+
57
+ def handle_event(payload)
58
+ case payload.type
59
+ when 'block_actions'
60
+ on_action(payload)
61
+ when 'view_submission'
62
+ on_view(payload)
63
+ when 'event_callback'
64
+ if payload.event.type == 'message'
65
+ on_message(payload)
66
+ else
67
+ on_event(payload)
68
+ end
69
+ end
70
+ end
71
+
72
+ def on_action(payload)
73
+ @logger.debug("action payload: #{payload}")
74
+
75
+ payload.actions.each do |current_action|
76
+ action_event = Events::ActionEvent.new(payload, current_action)
77
+
78
+ action_handlers.each do |handler|
79
+ next unless handler.match?(action_event)
80
+
81
+ handler.run(self, action_event)
82
+ break
83
+ end
84
+ end
85
+ end
86
+
87
+ def on_view(payload)
88
+ @logger.debug("view payload: #{payload}")
89
+
90
+ view_event = Events::ViewEvent.new(payload)
91
+
92
+ view_handlers.each do |handler|
93
+ next unless handler.match?(view_event)
94
+
95
+ handler.run(self, view_event)
96
+ break
97
+ end
98
+ end
99
+
100
+ def on_message(payload)
101
+ @logger.debug("message payload: #{payload}")
102
+
103
+ return if payload.event.user == @bot_auth.user
104
+ return if payload.event.bot_id
105
+
106
+ message = Events::MessageEvent.new(payload)
107
+ message.register_say_handler(method(:say))
108
+
109
+ message_handlers.each do |handler|
110
+ match = handler.match(payload.event)
111
+ next unless match
112
+
113
+ handler.run(self, message, match)
114
+ break
115
+ end
116
+ end
117
+
118
+ def say(message_event, params = {})
119
+ client.chat_postMessage(
120
+ {
121
+ channel: message_event.raw_payload.event.channel,
122
+ as_user: true
123
+ }.merge(params)
124
+ )
125
+ end
126
+
127
+ def on_event(payload)
128
+ @logger.debug("event payload: #{payload}")
129
+
130
+ event = Events::GeneralEvent.new(payload)
131
+
132
+ event_handlers.each do |handler|
133
+ next unless handler.match?(event)
134
+
135
+ handler.run(self, event)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module DSL
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def action_handlers
10
+ self.class.action_handlers
11
+ end
12
+
13
+ def message_handlers
14
+ self.class.message_handlers
15
+ end
16
+
17
+ def event_handlers
18
+ self.class.event_handlers
19
+ end
20
+
21
+ def view_handlers
22
+ self.class.view_handlers
23
+ end
24
+
25
+ def cron_handlers
26
+ self.class.cron_handlers
27
+ end
28
+
29
+ module ClassMethods
30
+ def action_handlers
31
+ @action_handlers ||= []
32
+ end
33
+
34
+ def message_handlers
35
+ @message_handlers ||= []
36
+ end
37
+
38
+ def event_handlers
39
+ @event_handlers ||= []
40
+ end
41
+
42
+ def view_handlers
43
+ @view_handlers ||= []
44
+ end
45
+
46
+ def cron_handlers
47
+ @cron_handlers ||= []
48
+ end
49
+
50
+ def message(pattern, &block)
51
+ message_handlers.push Handlers::MessageHandler.new(
52
+ pattern,
53
+ create_handler_method(&block)
54
+ )
55
+ end
56
+
57
+ alias on message
58
+
59
+ def action(event_name, &block)
60
+ action_handlers.push Handlers::ActionHandler.new(
61
+ event_name,
62
+ create_handler_method(&block)
63
+ )
64
+ end
65
+
66
+ def view(callback_id, &block)
67
+ view_handlers.push Handlers::ViewHandler.new(
68
+ callback_id,
69
+ create_handler_method(&block)
70
+ )
71
+ end
72
+
73
+ def event(event_name, &block)
74
+ event_handlers.push Handlers::EventHandler.new(
75
+ event_name,
76
+ create_handler_method(&block)
77
+ )
78
+ end
79
+
80
+ def every(cron_format, &block)
81
+ cron_handlers.push Handlers::CronHandler.new(
82
+ cron_format,
83
+ create_handler_method(&block)
84
+ )
85
+ end
86
+
87
+ private
88
+
89
+ def create_handler_method(&block)
90
+ tmp_name = :__tmp_handler_method
91
+ define_method(tmp_name, &block)
92
+ created_method = instance_method(tmp_name)
93
+ remove_method(tmp_name)
94
+ created_method
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Events
5
+ class ActionEvent
6
+ def initialize(raw_payload, current_action)
7
+ @raw_payload = raw_payload
8
+ @current_action = current_action
9
+ end
10
+
11
+ attr_reader :raw_payload, :current_action
12
+
13
+ def respond_to_missing?(symbol, include_private)
14
+ return true if raw_payload.event.respond_to?(symbol)
15
+
16
+ super
17
+ end
18
+
19
+ def method_missing(method, *args)
20
+ return raw_payload.event.send(method, *args) if raw_payload.event.respond_to?(method)
21
+
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Events
5
+ class GeneralEvent
6
+ def initialize(raw_payload)
7
+ @raw_payload = raw_payload
8
+ end
9
+
10
+ attr_reader :raw_payload
11
+
12
+ def respond_to_missing?(symbol, include_private)
13
+ return true if raw_payload.event.respond_to?(symbol)
14
+
15
+ super
16
+ end
17
+
18
+ def method_missing(method, *args)
19
+ return raw_payload.event.send(method, *args) if raw_payload.event.respond_to?(method)
20
+
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Events
5
+ class MessageEvent
6
+ def initialize(raw_payload)
7
+ @raw_payload = raw_payload
8
+ end
9
+
10
+ attr_reader :raw_payload
11
+
12
+ def say(params = {})
13
+ return unless @say_handler
14
+
15
+ @say_handler.call(self, params)
16
+ end
17
+
18
+ def register_say_handler(callback)
19
+ @say_handler = callback
20
+ end
21
+
22
+ def respond_to_missing?(symbol, include_private)
23
+ return true if raw_payload.event.respond_to?(symbol)
24
+
25
+ super
26
+ end
27
+
28
+ def method_missing(method, *args)
29
+ return raw_payload.event.send(method, *args) if raw_payload.event.respond_to?(method)
30
+
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Events
5
+ class ViewEvent
6
+ def initialize(raw_payload)
7
+ @raw_payload = raw_payload
8
+ end
9
+
10
+ attr_reader :raw_payload
11
+
12
+ def respond_to_missing?(symbol, include_private)
13
+ return true if raw_payload.view.respond_to?(symbol)
14
+
15
+ super
16
+ end
17
+
18
+ def method_missing(method, *args)
19
+ return raw_payload.view.send(method, *args) if raw_payload.view.respond_to?(method)
20
+
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Handlers
5
+ class ActionHandler
6
+ def initialize(action_id, callback)
7
+ @action_id = action_id
8
+ @callback = callback
9
+ end
10
+
11
+ attr_reader :action_id
12
+
13
+ def run(app, action_event)
14
+ args = [action_event].slice(0, @callback.arity)
15
+ @callback.bind(app).call(*args)
16
+ end
17
+
18
+ def match?(action_event)
19
+ @action_id == action_event.current_action.action_id
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Handlers
5
+ class CronHandler
6
+ def initialize(cron_format, callback)
7
+ @cron_format = cron_format
8
+ @callback = callback
9
+ end
10
+
11
+ attr_reader :cron_format
12
+
13
+ def run(app)
14
+ @callback.bind(app).call
15
+ end
16
+
17
+ def next_time(current_time = Time.now)
18
+ cron.next(current_time)
19
+ end
20
+
21
+ private
22
+
23
+ def cron
24
+ @cron ||= CronParser.new(cron_format)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Handlers
5
+ class EventHandler
6
+ def initialize(event_name, callback)
7
+ @event_name = event_name
8
+ @callback = callback
9
+ end
10
+
11
+ attr_reader :event_name
12
+
13
+ def run(app, event)
14
+ args = [event].slice(0, @callback.arity)
15
+ @callback.bind(app).call(*args)
16
+ end
17
+
18
+ def match?(general_event)
19
+ @event_name == general_event.type
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Handlers
5
+ class MessageHandler
6
+ def initialize(pattern, callback)
7
+ @pattern = pattern
8
+ @callback = callback
9
+ end
10
+
11
+ attr_reader :pattern
12
+
13
+ def run(app, message_event, match = nil)
14
+ args = [message_event, match].slice(0, @callback.arity)
15
+ @callback.bind(app).call(*args)
16
+ end
17
+
18
+ def match(message)
19
+ case @pattern
20
+ when String
21
+ @pattern if message.text == @pattern
22
+ when Regexp
23
+ message.text&.match(@pattern)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Handlers
5
+ class ViewHandler
6
+ def initialize(callback_id, callback)
7
+ @callback_id = callback_id
8
+ @callback = callback
9
+ end
10
+
11
+ attr_reader :callback_id
12
+
13
+ def run(app, view_event)
14
+ args = [view_event].slice(0, @callback.arity)
15
+ @callback.bind(app).call(*args)
16
+ end
17
+
18
+ def match?(view_event)
19
+ @callback_id == view_event.callback_id
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Services
5
+ class SlackAppClient
6
+ SLACK_API_ORIGIN = 'https://slack.com'
7
+
8
+ def initialize(options = {})
9
+ @app_token = options[:token]
10
+ end
11
+
12
+ def request(path, params = {})
13
+ res = create_http_client.post(
14
+ "/api/#{path}", params.to_json,
15
+ 'Content-Type': 'application/json',
16
+ Authorization: "Bearer #{@app_token}"
17
+ )
18
+ parse_response(res.body)
19
+ end
20
+
21
+ private
22
+
23
+ def create_http_client
24
+ uri = URI.parse(SLACK_API_ORIGIN)
25
+ http = Net::HTTP.new(uri.host, uri.port)
26
+ http.use_ssl = uri.scheme == 'https'
27
+ http
28
+ end
29
+
30
+ def parse_response(response_body)
31
+ JSON.parse(response_body, object_class: OpenStruct)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ module Services
5
+ class SlackSocketClient
6
+ SLACK_API_ORIGIN = 'https://slack.com'
7
+
8
+ def initialize(options = {})
9
+ @app_client = SlackAppClient.new(token: options[:token])
10
+ @logger = options[:logger]
11
+ @handler = nil
12
+ @ws = nil
13
+ end
14
+
15
+ def on_message(&block)
16
+ @handler = block
17
+ end
18
+
19
+ def connect!
20
+ require 'faye/websocket'
21
+
22
+ open_resp = @app_client.request('apps.connections.open')
23
+ raise open_resp unless open_resp.ok
24
+
25
+ url = "#{open_resp.url}#{debug? ? '&debug_reconnects=true' : ''}"
26
+
27
+ @ws = Faye::WebSocket::Client.new(url)
28
+ @ws.on(:open, &method(:on_ws_open))
29
+ @ws.on(:message, &method(:on_ws_message))
30
+ @ws.on(:close, &method(:on_ws_close))
31
+ end
32
+
33
+ private
34
+
35
+ def debug?
36
+ ENV['DEBUG'].to_i != 0
37
+ end
38
+
39
+ def on_ws_open(_event)
40
+ @logger.info 'connect socket mode'
41
+ end
42
+
43
+ def on_ws_message(event)
44
+ return unless @handler
45
+
46
+ data = parse_response(event.data)
47
+ @ws.send({ envelope_id: data.envelope_id }.to_json) if data.envelope_id
48
+
49
+ case data.type
50
+ when 'events_api', 'interactive'
51
+ @handler.call(data.payload)
52
+ when 'disconnect'
53
+ @ws.close
54
+ end
55
+ end
56
+
57
+ def on_ws_close(event)
58
+ @logger.info "close socket mode (#{event.code}#{event.reason ? " / #{event.reason}" : ''})"
59
+ connect!
60
+ end
61
+
62
+ def parse_response(response_body)
63
+ JSON.parse(response_body, object_class: OpenStruct)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SardonyxRing
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'ostruct'
5
+ require 'net/http'
6
+ require 'logger'
7
+
8
+ require 'slack'
9
+ require 'eventmachine'
10
+ require 'parse-cron'
11
+
12
+ require_relative 'sardonyx_ring/version'
13
+ require_relative 'sardonyx_ring/handlers/action_handler'
14
+ require_relative 'sardonyx_ring/handlers/cron_handler'
15
+ require_relative 'sardonyx_ring/handlers/event_handler'
16
+ require_relative 'sardonyx_ring/handlers/message_handler'
17
+ require_relative 'sardonyx_ring/handlers/view_handler'
18
+ require_relative 'sardonyx_ring/events/action_event'
19
+ require_relative 'sardonyx_ring/events/general_event'
20
+ require_relative 'sardonyx_ring/events/message_event'
21
+ require_relative 'sardonyx_ring/events/view_event'
22
+ require_relative 'sardonyx_ring/services/slack_app_client'
23
+ require_relative 'sardonyx_ring/services/slack_socket_client'
24
+ require_relative 'sardonyx_ring/dsl'
25
+ require_relative 'sardonyx_ring/app'
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/sardonyx_ring/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'sardonyx_ring'
7
+ spec.version = SardonyxRing::VERSION
8
+ spec.authors = ['rutan']
9
+ spec.email = ['ru_shalm@hazimu.com']
10
+
11
+ spec.summary = 'Slack bot framework'
12
+ spec.description = 'Slack bot framework'
13
+ spec.homepage = 'https://github.com/rutan/sardonyx_ring'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['rubygems_mfa_required'] = 'true'
19
+
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
22
+ end
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_dependency 'eventmachine'
28
+ spec.add_dependency 'faye-websocket'
29
+ spec.add_dependency 'parse-cron'
30
+ spec.add_dependency 'slack-ruby-client'
31
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sardonyx_ring
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - rutan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-12-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faye-websocket
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: parse-cron
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: slack-ruby-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Slack bot framework
70
+ email:
71
+ - ru_shalm@hazimu.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".editorconfig"
77
+ - ".github/workflows/main.yml"
78
+ - ".gitignore"
79
+ - ".rubocop.yml"
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - bin/console
86
+ - bin/setup
87
+ - example.rb
88
+ - lib/sardonyx_ring.rb
89
+ - lib/sardonyx_ring/app.rb
90
+ - lib/sardonyx_ring/dsl.rb
91
+ - lib/sardonyx_ring/events/action_event.rb
92
+ - lib/sardonyx_ring/events/general_event.rb
93
+ - lib/sardonyx_ring/events/message_event.rb
94
+ - lib/sardonyx_ring/events/view_event.rb
95
+ - lib/sardonyx_ring/handlers/action_handler.rb
96
+ - lib/sardonyx_ring/handlers/cron_handler.rb
97
+ - lib/sardonyx_ring/handlers/event_handler.rb
98
+ - lib/sardonyx_ring/handlers/message_handler.rb
99
+ - lib/sardonyx_ring/handlers/view_handler.rb
100
+ - lib/sardonyx_ring/services/slack_app_client.rb
101
+ - lib/sardonyx_ring/services/slack_socket_client.rb
102
+ - lib/sardonyx_ring/version.rb
103
+ - sardonyx_ring.gemspec
104
+ homepage: https://github.com/rutan/sardonyx_ring
105
+ licenses:
106
+ - MIT
107
+ metadata:
108
+ homepage_uri: https://github.com/rutan/sardonyx_ring
109
+ rubygems_mfa_required: 'true'
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 2.7.0
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubygems_version: 3.2.3
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Slack bot framework
129
+ test_files: []