slack_bot-events 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/slack_bot/events/client.rb +32 -42
- data/lib/slack_bot/events/configuration.rb +12 -0
- data/lib/slack_bot/events/middleware/chain.rb +147 -0
- data/lib/slack_bot/events/middleware/event_tracer.rb +40 -0
- data/lib/slack_bot/events/schemas/socket_payload.rb +1 -1
- data/lib/slack_bot/events/schemas/type/message.rb +5 -1
- data/lib/slack_bot/events/schemas/type/reaction_modified.rb +1 -1
- data/lib/slack_bot/events/schematize.rb +27 -0
- data/lib/slack_bot/events/version.rb +1 -1
- data/lib/slack_bot/events.rb +15 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5be1bf5ab14cf6022e8a31b32015e328bd5b1939d928415bb06e610dc3e091e
|
4
|
+
data.tar.gz: e04f3734b913e8514f2ed50eb3f0905906013321f3fa6c26dc3992a7a5136ae4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f59e41eeb158988a8b02ba6b445b1b7ba8009e96c2ec404b313b614e9aa86d5e346518c9ad7e824b49dd29dfc4b04e9f84a827edde5698c07a3d1325b356814
|
7
|
+
data.tar.gz: 4e200610a557c49256745577bca3811e9830609a29009a6f4b09cc1251f972118d013c92a2a9d933e2df3391592c582112abd6911638115987bcdecf7a07fd15
|
data/Gemfile.lock
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "eventmachine"
|
4
4
|
require "faraday"
|
5
5
|
require "faye/websocket"
|
6
|
-
require "
|
6
|
+
require "slack_bot/events/schematize"
|
7
7
|
|
8
8
|
module SlackBot
|
9
9
|
module Events
|
@@ -11,64 +11,54 @@ module SlackBot
|
|
11
11
|
BASE_API = "https://slack.com/api"
|
12
12
|
|
13
13
|
def start!
|
14
|
-
|
15
|
-
websocket.on :open do |
|
16
|
-
|
14
|
+
EventMachine.run do
|
15
|
+
websocket.on :open do |socket_event|
|
16
|
+
process(type: :open, socket_event: socket_event) { |**| }
|
17
17
|
end
|
18
18
|
|
19
|
-
websocket.on :message do |
|
20
|
-
|
21
|
-
parsed_data = JSON.parse(event.data)
|
19
|
+
websocket.on :message do |socket_event|
|
20
|
+
process_message(socket_event: socket_event) do |parsed_data:, schema: nil|
|
22
21
|
case parsed_data["type"]
|
23
22
|
when "events_api"
|
24
|
-
events_api(parsed_data)
|
25
|
-
when "app_rate_limited"
|
26
|
-
# https://api.slack.com/apis/rate-limits#events
|
27
|
-
# Total allowed workspace events are 30,000 per hour
|
28
|
-
# This message type is received once you have gone beyond that
|
29
|
-
params = {
|
30
|
-
minute_rate_limited: parsed_data["minute_rate_limited"],
|
31
|
-
team_id: parsed_data["team_id"],
|
32
|
-
api_app_id: parsed_data["api_app_id"],
|
33
|
-
}
|
34
|
-
event_tracer("message:app_rate_limited", **params)
|
35
|
-
when "hello"
|
36
|
-
params = {
|
37
|
-
num_connections: parsed_data["num_connections"],
|
38
|
-
debug_info_host: parsed_data["debug_info"]["host"],
|
39
|
-
debug_info_connect_time: parsed_data["debug_info"]["approximate_connection_time"],
|
40
|
-
}
|
41
|
-
event_tracer("message:hello", **params)
|
23
|
+
events_api(schema: schema, parsed_data: parsed_data)
|
42
24
|
end
|
43
25
|
end
|
44
26
|
end
|
45
27
|
|
46
|
-
websocket.on :close do |
|
47
|
-
|
28
|
+
websocket.on :close do |socket_event|
|
29
|
+
process(type: :close, socket_event: socket_event) { |**| }
|
48
30
|
@websocket = nil
|
31
|
+
|
32
|
+
# The websocket is closed, explcitly stop the event machine to to end the loop and return to the parent
|
33
|
+
EventMachine.stop
|
49
34
|
end
|
50
35
|
end
|
51
36
|
end
|
52
37
|
|
53
|
-
def
|
54
|
-
|
55
|
-
Events.
|
56
|
-
|
57
|
-
|
58
|
-
|
38
|
+
def process_message(socket_event:)
|
39
|
+
schema_data = Schematize.call(data: socket_event.data)
|
40
|
+
SlackBot::Events.message_middleware.invoke_message(type: :message, socket_event: socket_event, **schema_data) do
|
41
|
+
yield(**schema_data) if block_given?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def process(type:, socket_event:)
|
46
|
+
SlackBot::Events.public_send(:"#{type}_middleware").invoke(type: type, socket_event: socket_event) do
|
47
|
+
yield if block_given?
|
59
48
|
end
|
60
|
-
websocket.send("#{{ envelope_id: schematized.envelope_id }.to_json}")
|
61
49
|
end
|
62
50
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
if block_given?
|
67
|
-
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
68
|
-
yield
|
69
|
-
elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
70
|
-
Events.logger.info "[Event completed] [#{elapsed_time.round(3)}s] #{type} #{stringify}"
|
51
|
+
def events_api(schema:, parsed_data:)
|
52
|
+
if Events.config.print_tldr
|
53
|
+
Events.logger.info { schema.tldr }
|
71
54
|
end
|
55
|
+
|
56
|
+
object = Events.config.listeners[schema.type.to_sym]
|
57
|
+
if object
|
58
|
+
safe_handler(type: schema.type.to_sym, object: object, schema: schema, parsed_data: parsed_data)
|
59
|
+
end
|
60
|
+
|
61
|
+
websocket.send("#{{ envelope_id: schema.envelope_id }.to_json}")
|
72
62
|
end
|
73
63
|
|
74
64
|
private
|
@@ -2,17 +2,29 @@
|
|
2
2
|
|
3
3
|
require "class_composer"
|
4
4
|
require "logger"
|
5
|
+
require "slack_bot/events/middleware/chain"
|
5
6
|
|
6
7
|
module SlackBot
|
7
8
|
module Events
|
8
9
|
class Configuration
|
9
10
|
include ClassComposer::Generator
|
11
|
+
|
12
|
+
ALLOWED_ACKNOWLEDGE = [
|
13
|
+
DEFAULT_ACKNOWLEDGE = :on_complete,
|
14
|
+
:on_success,
|
15
|
+
:on_receive,
|
16
|
+
]
|
17
|
+
|
10
18
|
add_composer :client_id, allowed: String, default: ENV["SLACK_CLIENT_ID"]
|
11
19
|
add_composer :client_secret, allowed: String, default: ENV["SLACK_CLIENT_SECRET"]
|
12
20
|
add_composer :client_signing_secret, allowed: String, default: ENV["SLACK_SIGNING_SECRET"]
|
13
21
|
add_composer :client_socket_token, allowed: String, default: ENV["SLACK_SOCKET_TOKEN"]
|
14
22
|
add_composer :client_verification_token, allowed: String, default: ENV["SLACK_VERIFICATION_TOKEN"]
|
15
23
|
add_composer :print_tldr, allowed: [true.class, false.class], default: true
|
24
|
+
add_composer :message_middleware, allowed: Middleware::Chain, default: Middleware::Chain.new(type: :message)
|
25
|
+
add_composer :open_middleware, allowed: Middleware::Chain, default: Middleware::Chain.new(type: :open)
|
26
|
+
add_composer :close_middleware, allowed: Middleware::Chain, default: Middleware::Chain.new(type: :close)
|
27
|
+
add_composer :envelope_acknowledge, allowed: Symbol, default: DEFAULT_ACKNOWLEDGE, validator: ->(val) { ALLOWED_ACKNOWLEDGE.include?(val) }, invalid_message: ->(_) { "Must by a Symbol in #{ALLOWED_ACKNOWLEDGE}" }
|
16
28
|
|
17
29
|
def register_listener(name:, handler:, on_success: nil, on_failure: nil)
|
18
30
|
if event_handler = listeners[name.to_sym]
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "slack_bot/events/middleware/event_tracer"
|
4
|
+
|
5
|
+
####################
|
6
|
+
#
|
7
|
+
# Adapted from Sidekiq:
|
8
|
+
# https://github.com/sidekiq/sidekiq/blob/main/lib/sidekiq/middleware/chain.rb
|
9
|
+
#
|
10
|
+
####################
|
11
|
+
|
12
|
+
module SlackBot
|
13
|
+
module Events
|
14
|
+
module Middleware
|
15
|
+
class Chain
|
16
|
+
include Enumerable
|
17
|
+
|
18
|
+
attr_reader :type
|
19
|
+
|
20
|
+
DEFAULT_ENTRIES = {
|
21
|
+
message: [Middleware::EventTracer],
|
22
|
+
open: [Middleware::EventTracer],
|
23
|
+
close: [Middleware::EventTracer],
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize(type:)
|
27
|
+
@type = type
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.default_entry(type)
|
31
|
+
DEFAULT_ENTRIES[type].map { Entry.new(_1) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def entries
|
35
|
+
@entries ||= self.class.default_entry(type)
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove(klass)
|
39
|
+
raise ArgumentError, "Unable to remove default Middleware #{klass}" if self.class.default_entry.map(:klass).include?(klass)
|
40
|
+
|
41
|
+
entries.delete_if { |entry| entry.klass == klass }
|
42
|
+
end
|
43
|
+
|
44
|
+
def add(klass, *args)
|
45
|
+
remove(klass)
|
46
|
+
entries << Entry.new(klass, args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def prepend(klass, *args)
|
50
|
+
remove(klass)
|
51
|
+
entries.insert(0, Entry.new(klass, args))
|
52
|
+
end
|
53
|
+
|
54
|
+
def insert_before(oldklass, newklass, *args)
|
55
|
+
i = entries.index { |entry| entry.klass == newklass }
|
56
|
+
new_entry = i.nil? ? Entry.new(klass, args) : entries.delete_at(i)
|
57
|
+
i = entries.index { |entry| entry.klass == oldklass } || 0
|
58
|
+
entries.insert(i, new_entry)
|
59
|
+
end
|
60
|
+
|
61
|
+
def insert_after(oldklass, newklass, *args)
|
62
|
+
i = entries.index { |entry| entry.klass == newklass }
|
63
|
+
new_entry = i.nil? ? Entry.new(klass, args) : entries.delete_at(i)
|
64
|
+
i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
|
65
|
+
entries.insert(i + 1, new_entry)
|
66
|
+
end
|
67
|
+
|
68
|
+
def exists?(klass)
|
69
|
+
any? { |entry| entry.klass == klass }
|
70
|
+
end
|
71
|
+
|
72
|
+
def empty?
|
73
|
+
entries.nil? || entries.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def retrieve
|
77
|
+
map(&:instance!)
|
78
|
+
end
|
79
|
+
|
80
|
+
def each(&block)
|
81
|
+
entries.each(&block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def clear
|
85
|
+
@entries = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def invoke_message(type:, socket_event:, parsed_data:, schema: nil)
|
89
|
+
return yield if empty?
|
90
|
+
|
91
|
+
chain = retrieve
|
92
|
+
traverse_chain = proc do
|
93
|
+
if chain.empty?
|
94
|
+
yield(yield: schema, parsed_data: parsed_data)
|
95
|
+
else
|
96
|
+
params = {
|
97
|
+
parsed_data: parsed_data,
|
98
|
+
schema: schema,
|
99
|
+
socket_event: socket_event,
|
100
|
+
type: type,
|
101
|
+
}
|
102
|
+
chain.shift.call(**params, &traverse_chain)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
traverse_chain.call
|
106
|
+
end
|
107
|
+
|
108
|
+
def invoke(type:, socket_event:)
|
109
|
+
return yield if empty?
|
110
|
+
|
111
|
+
chain = retrieve
|
112
|
+
traverse_chain = proc do
|
113
|
+
if chain.empty?
|
114
|
+
yield
|
115
|
+
else
|
116
|
+
chain.shift.call(type: type, socket_event: socket_event, &traverse_chain)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
traverse_chain.call
|
120
|
+
end
|
121
|
+
|
122
|
+
def inspect_me
|
123
|
+
entries.map do |e|
|
124
|
+
[
|
125
|
+
e.klass,
|
126
|
+
e.args
|
127
|
+
]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Entry
|
133
|
+
attr_reader :klass
|
134
|
+
attr_reader :args
|
135
|
+
|
136
|
+
def initialize(klass, args = [])
|
137
|
+
@klass = klass
|
138
|
+
@args = args
|
139
|
+
end
|
140
|
+
|
141
|
+
def instance!
|
142
|
+
klass.new(*args)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackBot
|
4
|
+
module Events
|
5
|
+
module Middleware
|
6
|
+
class EventTracer
|
7
|
+
def call(type:, socket_event:, schema: nil, parsed_data: nil)
|
8
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
9
|
+
temp_type = type.dup.to_s
|
10
|
+
case type
|
11
|
+
when :close
|
12
|
+
additional_info = "code: #{socket_event.code} reason:#{socket_event.reason}"
|
13
|
+
when :message
|
14
|
+
p_type = parsed_data.dig("type")
|
15
|
+
case p_type
|
16
|
+
when "app_rate_limited"
|
17
|
+
# https://api.slack.com/apis/rate-limits#events
|
18
|
+
# Total allowed workspace events are 30,000 per hour
|
19
|
+
# This message type is received once you have gone beyond that
|
20
|
+
temp_type += ":#{p_type}"
|
21
|
+
additional_info = "minute_rate_limited:#{parsed_data["minute_rate_limited"]} " \
|
22
|
+
"team_id:#{parsed_data["team_id"]} " \
|
23
|
+
"api_app_id:#{parsed_data["api_app_id"]}"
|
24
|
+
else
|
25
|
+
# Expected other types are `events_api` and `hello`
|
26
|
+
temp_type += ":#{p_type}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Events.logger.info { "[Event Received] #{temp_type} #{additional_info}" }
|
31
|
+
|
32
|
+
yield
|
33
|
+
|
34
|
+
elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
35
|
+
Events.logger.info { "[Event Finished] [#{(elapsed_time * 1000).round(2)}ms] #{temp_type}" }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -39,7 +39,7 @@ module SlackBot
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def tldr
|
42
|
-
"type
|
42
|
+
"type:#{type}; user:#{user}; channel:#{channel}; ts_id:#{combined_id}"
|
43
43
|
end
|
44
44
|
|
45
45
|
def thread_ts
|
@@ -52,6 +52,10 @@ module SlackBot
|
|
52
52
|
|
53
53
|
private
|
54
54
|
|
55
|
+
def combined_id
|
56
|
+
thread_ts ? "#{ts}:#{thread_ts}" : ts
|
57
|
+
end
|
58
|
+
|
55
59
|
def return_nil?(val)
|
56
60
|
JsonSchematize::EmptyValue === val || val.nil?
|
57
61
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackBot
|
4
|
+
module Events
|
5
|
+
module Schematize
|
6
|
+
def self.message(parsed_data)
|
7
|
+
case parsed_data["type"]
|
8
|
+
when "events_api"
|
9
|
+
return SlackBot::Events::Schemas::SocketPayload
|
10
|
+
when "app_rate_limited"
|
11
|
+
when "hello"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.call(data:)
|
16
|
+
parsed_data = JSON.parse(data)
|
17
|
+
return { parsed_data: parsed_data } unless schema_klass = message(parsed_data)
|
18
|
+
|
19
|
+
if schema_klass.respond_to?(:call)
|
20
|
+
{ schema: schema_klass.call(parsed_data).new(parsed_data), parsed_data: parsed_data }
|
21
|
+
else
|
22
|
+
{ schema: schema_klass.new(parsed_data), parsed_data: parsed_data }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/slack_bot/events.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "pry"
|
4
|
+
|
3
5
|
require "slack_bot/events/configuration"
|
4
6
|
require "slack_bot/events/client"
|
7
|
+
require "slack_bot/events/middleware/chain"
|
5
8
|
require "slack_bot/events/schemas/socket_payload"
|
6
9
|
|
7
10
|
module SlackBot
|
@@ -40,5 +43,17 @@ module SlackBot
|
|
40
43
|
def self.logger
|
41
44
|
config.logger
|
42
45
|
end
|
46
|
+
|
47
|
+
def self.message_middleware
|
48
|
+
config.message_middleware
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.open_middleware
|
52
|
+
config.open_middleware
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.close_middleware
|
56
|
+
config.close_middleware
|
57
|
+
end
|
43
58
|
end
|
44
59
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slack_bot-events
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Taylor
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-06-
|
11
|
+
date: 2024-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -134,6 +134,8 @@ files:
|
|
134
134
|
- lib/slack_bot/events.rb
|
135
135
|
- lib/slack_bot/events/client.rb
|
136
136
|
- lib/slack_bot/events/configuration.rb
|
137
|
+
- lib/slack_bot/events/middleware/chain.rb
|
138
|
+
- lib/slack_bot/events/middleware/event_tracer.rb
|
137
139
|
- lib/slack_bot/events/schemas/authorization.rb
|
138
140
|
- lib/slack_bot/events/schemas/data_payload.rb
|
139
141
|
- lib/slack_bot/events/schemas/socket_payload.rb
|
@@ -144,6 +146,7 @@ files:
|
|
144
146
|
- lib/slack_bot/events/schemas/type/item.rb
|
145
147
|
- lib/slack_bot/events/schemas/type/message.rb
|
146
148
|
- lib/slack_bot/events/schemas/type/reaction_modified.rb
|
149
|
+
- lib/slack_bot/events/schematize.rb
|
147
150
|
- lib/slack_bot/events/version.rb
|
148
151
|
- slack_bot-events.gemspec
|
149
152
|
homepage: https://github.com/matt-taylor/slack_bot-events
|