slack_bot-events 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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