sardonyx_ring 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +6 -0
- data/.github/workflows/main.yml +17 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +23 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +83 -0
- data/LICENSE.txt +21 -0
- data/README.md +27 -0
- data/Rakefile +16 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/example.rb +166 -0
- data/lib/sardonyx_ring/app.rb +139 -0
- data/lib/sardonyx_ring/dsl.rb +98 -0
- data/lib/sardonyx_ring/events/action_event.rb +26 -0
- data/lib/sardonyx_ring/events/general_event.rb +25 -0
- data/lib/sardonyx_ring/events/message_event.rb +35 -0
- data/lib/sardonyx_ring/events/view_event.rb +25 -0
- data/lib/sardonyx_ring/handlers/action_handler.rb +23 -0
- data/lib/sardonyx_ring/handlers/cron_handler.rb +28 -0
- data/lib/sardonyx_ring/handlers/event_handler.rb +23 -0
- data/lib/sardonyx_ring/handlers/message_handler.rb +28 -0
- data/lib/sardonyx_ring/handlers/view_handler.rb +23 -0
- data/lib/sardonyx_ring/services/slack_app_client.rb +35 -0
- data/lib/sardonyx_ring/services/slack_socket_client.rb +67 -0
- data/lib/sardonyx_ring/version.rb +5 -0
- data/lib/sardonyx_ring.rb +25 -0
- data/sardonyx_ring.gemspec +31 -0
- metadata +129 -0
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,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
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
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
data/bin/setup
ADDED
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,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: []
|