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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e221d5ba2b7822f18b4079b73bc35005488510570b1c6bf1121b29ae3a38a85f
4
- data.tar.gz: 7c6fd667337ad3eaefc080abac2d53295b6226c41570dcebc118eb321e218a0b
3
+ metadata.gz: c5be1bf5ab14cf6022e8a31b32015e328bd5b1939d928415bb06e610dc3e091e
4
+ data.tar.gz: e04f3734b913e8514f2ed50eb3f0905906013321f3fa6c26dc3992a7a5136ae4
5
5
  SHA512:
6
- metadata.gz: 9545811f097ea137483b27bfd4b93cfb9d64040487051fd9f906de4d67e09503768fa4341faad9eed36348edf644ad33be7f9109f3c5a9d5741d59fc9c8ca17b
7
- data.tar.gz: 2afc096dd076de82f0f3443a7ac437be405f6b8613db994072a94cb05623797cc631b178e163c756ddfbd7f2c29e337bbb42db5fe71e42aed0b5b148c3bfd5ea
6
+ metadata.gz: 7f59e41eeb158988a8b02ba6b445b1b7ba8009e96c2ec404b313b614e9aa86d5e346518c9ad7e824b49dd29dfc4b04e9f84a827edde5698c07a3d1325b356814
7
+ data.tar.gz: 4e200610a557c49256745577bca3811e9830609a29009a6f4b09cc1251f972118d013c92a2a9d933e2df3391592c582112abd6911638115987bcdecf7a07fd15
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slack_bot-events (0.1.0)
4
+ slack_bot-events (0.3.0)
5
5
  class_composer (~> 1.0)
6
6
  faraday
7
7
  faye-websocket
@@ -3,7 +3,7 @@
3
3
  require "eventmachine"
4
4
  require "faraday"
5
5
  require "faye/websocket"
6
- require "pry"
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
- EM.run do
15
- websocket.on :open do |event|
16
- event_tracer(:open)
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 |event|
20
- event_tracer(:message) do
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 |event|
47
- event_tracer(:close, code: event.code, reason: event.reason)
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 events_api(parsed_data)
54
- schematized = SlackBot::Events::Schemas::SocketPayload.new(parsed_data)
55
- Events.logger.info(schematized.tldr) if Events.config.print_tldr
56
- object = Events.config.listeners[schematized.type.to_sym]
57
- if object
58
- safe_handler(type: schematized.type.to_sym, object: object, schema: schematized, parsed_data: parsed_data)
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 event_tracer(type, **params)
64
- stringify = params.map { |k,v| "#{k}:#{v}" }.join("; ")
65
- Events.logger.info "[Event Received] #{type} #{stringify}"
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
@@ -28,7 +28,7 @@ module SlackBot
28
28
  def tldr
29
29
  retry_language = "#{retry_attempt}"
30
30
  retry_language += ":#{retry_reason}" if retry_attempt > 0
31
- "tldr:#{payload.event.tldr}; retry:#{retry_language}"
31
+ "#{payload.event.tldr}; retry:#{retry_language}"
32
32
  end
33
33
  end
34
34
  end
@@ -39,7 +39,7 @@ module SlackBot
39
39
  end
40
40
 
41
41
  def tldr
42
- "type: #{type}; channel:#{channel}; raw_text:#{text}"
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
@@ -28,7 +28,7 @@ module SlackBot
28
28
  end
29
29
 
30
30
  def tldr
31
- "type: #{type}; channel:#{channel}; reaction:#{reaction}"
31
+ "type:#{type}; user:#{item_user}; channel:#{channel}; reaction:#{reaction}"
32
32
  end
33
33
  end
34
34
  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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SlackBot
4
4
  module Events
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -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.2.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-18 00:00:00.000000000 Z
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