Class: ChatSDK::Slack::Adapter
- Inherits:
-
Adapter::Base
- Object
- Adapter::Base
- ChatSDK::Slack::Adapter
- Defined in:
- lib/chat_sdk/slack/adapter.rb
Instance Attribute Summary collapse
-
#client ⇒ Object
readonly
Returns the value of attribute client.
Instance Method Summary collapse
- #ack_response(rack_request) ⇒ Object
- #add_reaction(channel_id:, message_id:, emoji:) ⇒ Object
- #delete_message(channel_id:, message_id:) ⇒ Object
- #edit_message(channel_id:, message_id:, message:) ⇒ Object
- #fetch_messages(channel_id:, thread_id: nil, cursor: nil, limit: 50) ⇒ Object
- #get_user(user_id) ⇒ Object
-
#initialize(bot_token: nil, signing_secret: nil) ⇒ Adapter
constructor
A new instance of Adapter.
- #mention(user_id) ⇒ Object
- #name ⇒ Object
- #open_dm(user_id) ⇒ Object
- #open_modal(trigger_id:, modal:) ⇒ Object
- #parse_events(rack_request) ⇒ Object
- #post_ephemeral(channel_id:, user_id:, message:, thread_id: nil) ⇒ Object
-
#post_message(channel_id:, message:, thread_id: nil) ⇒ Object
Outbound.
- #publish_home_view(user_id:, view:) ⇒ Object
- #remove_reaction(channel_id:, message_id:, emoji:) ⇒ Object
- #render(postable_message) ⇒ Object
- #schedule_message(channel_id:, message:, post_at:, thread_id: nil) ⇒ Object
- #set_assistant_status(channel_id:, thread_id:, status:) ⇒ Object
- #set_assistant_title(channel_id:, thread_id:, title:) ⇒ Object
- #set_suggested_prompts(channel_id:, thread_id:, prompts:) ⇒ Object
-
#start_socket_mode(app_token: nil, &block) ⇒ Object
Slack-specific: receive real-time events via Socket Mode WebSocket.
- #start_typing(channel_id:, thread_id: nil) ⇒ Object
- #upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil) ⇒ Object
-
#verify_request!(rack_request) ⇒ Object
Inbound.
Constructor Details
#initialize(bot_token: nil, signing_secret: nil) ⇒ Adapter
Returns a new instance of Adapter.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/chat_sdk/slack/adapter.rb', line 13 def initialize(bot_token: nil, signing_secret: nil) @bot_token = bot_token || ENV["SLACK_BOT_TOKEN"] @signing_secret = signing_secret || ENV["SLACK_SIGNING_SECRET"] raise ChatSDK::ConfigurationError, "Slack bot_token required" unless @bot_token raise ChatSDK::ConfigurationError, "Slack signing_secret required" unless @signing_secret ::Slack.configure do |config| config.token = @bot_token end @client = ::Slack::Web::Client.new(token: @bot_token) @renderer = BlockKitRenderer.new @modal_renderer = ModalRenderer.new end |
Instance Attribute Details
#client ⇒ Object (readonly)
Returns the value of attribute client.
11 12 13 |
# File 'lib/chat_sdk/slack/adapter.rb', line 11 def client @client end |
Instance Method Details
#ack_response(rack_request) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/chat_sdk/slack/adapter.rb', line 58 def ack_response(rack_request) body = rack_request.body.read rack_request.body.rewind parsed = parse_body(body, rack_request.content_type) return nil unless parsed if parsed["type"] == "url_verification" [200, {"content-type" => "text/plain"}, [parsed["challenge"]]] end end |
#add_reaction(channel_id:, message_id:, emoji:) ⇒ Object
131 132 133 |
# File 'lib/chat_sdk/slack/adapter.rb', line 131 def add_reaction(channel_id:, message_id:, emoji:) @client.reactions_add(channel: channel_id, timestamp: , name: emoji) end |
#delete_message(channel_id:, message_id:) ⇒ Object
110 111 112 |
# File 'lib/chat_sdk/slack/adapter.rb', line 110 def (channel_id:, message_id:) @client.chat_delete(channel: channel_id, ts: ) end |
#edit_message(channel_id:, message_id:, message:) ⇒ Object
101 102 103 104 105 106 107 108 |
# File 'lib/chat_sdk/slack/adapter.rb', line 101 def (channel_id:, message_id:, message:) msg = ChatSDK::PostableMessage.from() params = {channel: channel_id, ts: } (params, msg) @client.chat_update(**params) end |
#fetch_messages(channel_id:, thread_id: nil, cursor: nil, limit: 50) ⇒ Object
177 178 179 180 181 182 183 184 185 |
# File 'lib/chat_sdk/slack/adapter.rb', line 177 def (channel_id:, thread_id: nil, cursor: nil, limit: 50) result = if thread_id @client.conversations_replies(channel: channel_id, ts: thread_id, cursor: cursor, limit: limit) else @client.conversations_history(channel: channel_id, cursor: cursor, limit: limit) end = result["messages"].map { |m| (m, channel_id) } [, result["response_metadata"]&.dig("next_cursor")] end |
#get_user(user_id) ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/chat_sdk/slack/adapter.rb', line 139 def get_user(user_id) result = @client.users_info(user: user_id) return nil unless result&.dig("user", "id") ChatSDK::Author.new( id: result.dig("user", "id"), name: result.dig("user", "name"), platform: :slack, bot: result.dig("user", "is_bot") || false, raw: result ) end |
#mention(user_id) ⇒ Object
241 242 243 |
# File 'lib/chat_sdk/slack/adapter.rb', line 241 def mention(user_id) "<@#{user_id}>" end |
#name ⇒ Object
29 30 31 |
# File 'lib/chat_sdk/slack/adapter.rb', line 29 def name :slack end |
#open_dm(user_id) ⇒ Object
152 153 154 155 |
# File 'lib/chat_sdk/slack/adapter.rb', line 152 def open_dm(user_id) result = @client.conversations_open(users: user_id) result["channel"]["id"] end |
#open_modal(trigger_id:, modal:) ⇒ Object
187 188 189 190 |
# File 'lib/chat_sdk/slack/adapter.rb', line 187 def open_modal(trigger_id:, modal:) view = @modal_renderer.render(modal) @client.views_open(trigger_id: trigger_id, view: view) end |
#parse_events(rack_request) ⇒ Object
70 71 72 73 74 75 76 77 78 |
# File 'lib/chat_sdk/slack/adapter.rb', line 70 def parse_events(rack_request) body = rack_request.body.read rack_request.body.rewind parsed = parse_body(body, rack_request.content_type) return [] unless parsed EventParser.parse(parsed) end |
#post_ephemeral(channel_id:, user_id:, message:, thread_id: nil) ⇒ Object
114 115 116 117 118 119 120 121 122 |
# File 'lib/chat_sdk/slack/adapter.rb', line 114 def post_ephemeral(channel_id:, user_id:, message:, thread_id: nil) msg = ChatSDK::PostableMessage.from() params = {channel: channel_id, user: user_id} params[:thread_ts] = thread_id if thread_id (params, msg) @client.chat_postEphemeral(**params) end |
#post_message(channel_id:, message:, thread_id: nil) ⇒ Object
Outbound
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/chat_sdk/slack/adapter.rb', line 81 def (channel_id:, message:, thread_id: nil) msg = ChatSDK::PostableMessage.from() params = {channel: channel_id} params[:thread_ts] = thread_id if thread_id (params, msg) result = @client.chat_postMessage(**params) ChatSDK::Message.new( id: result["ts"], text: msg.text || "", author: ChatSDK::Author.new(id: "bot", name: "bot", platform: :slack, bot: true), thread_id: thread_id || result["ts"], channel_id: channel_id, platform: :slack, raw: result ) end |
#publish_home_view(user_id:, view:) ⇒ Object
192 193 194 |
# File 'lib/chat_sdk/slack/adapter.rb', line 192 def publish_home_view(user_id:, view:) @client.views_publish(user_id: user_id, view: view) end |
#remove_reaction(channel_id:, message_id:, emoji:) ⇒ Object
135 136 137 |
# File 'lib/chat_sdk/slack/adapter.rb', line 135 def remove_reaction(channel_id:, message_id:, emoji:) @client.reactions_remove(channel: channel_id, timestamp: , name: emoji) end |
#render(postable_message) ⇒ Object
245 246 247 248 249 250 251 |
# File 'lib/chat_sdk/slack/adapter.rb', line 245 def render() if .card? @renderer.render(.card) else .text end end |
#schedule_message(channel_id:, message:, post_at:, thread_id: nil) ⇒ Object
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/chat_sdk/slack/adapter.rb', line 157 def (channel_id:, message:, post_at:, thread_id: nil) msg = ChatSDK::PostableMessage.from() text = msg.text || "" params = {channel: channel_id, text: text, post_at: post_at.to_i} params[:thread_ts] = thread_id if thread_id result = @client.chat_scheduleMessage(**params) ChatSDK::Message.new( id: result["scheduled_message_id"], text: text, author: ChatSDK::Author.new(id: "bot", name: "bot", platform: :slack, bot: true), thread_id: thread_id || result["scheduled_message_id"], channel_id: channel_id, platform: :slack, raw: result ) end |
#set_assistant_status(channel_id:, thread_id:, status:) ⇒ Object
204 205 206 207 208 209 210 |
# File 'lib/chat_sdk/slack/adapter.rb', line 204 def set_assistant_status(channel_id:, thread_id:, status:) @client.assistant_threads_setStatus( channel_id: channel_id, thread_ts: thread_id, status: status ) end |
#set_assistant_title(channel_id:, thread_id:, title:) ⇒ Object
212 213 214 215 216 217 218 |
# File 'lib/chat_sdk/slack/adapter.rb', line 212 def set_assistant_title(channel_id:, thread_id:, title:) @client.assistant_threads_setTitle( channel_id: channel_id, thread_ts: thread_id, title: title ) end |
#set_suggested_prompts(channel_id:, thread_id:, prompts:) ⇒ Object
196 197 198 199 200 201 202 |
# File 'lib/chat_sdk/slack/adapter.rb', line 196 def set_suggested_prompts(channel_id:, thread_id:, prompts:) @client.assistant_threads_setSuggestedPrompts( channel_id: channel_id, thread_ts: thread_id, prompts: prompts ) end |
#start_socket_mode(app_token: nil, &block) ⇒ Object
Slack-specific: receive real-time events via Socket Mode WebSocket. Requires the optional 'faye-websocket' gem and an app-level token (xapp-*). Not part of the base adapter contract.
Usage:
adapter.start_socket_mode(app_token: "xapp-...") do |event|
# event is a ChatSDK::Events::* instance
end
233 234 235 236 237 238 239 |
# File 'lib/chat_sdk/slack/adapter.rb', line 233 def start_socket_mode(app_token: nil, &block) app_token ||= ENV["SLACK_APP_TOKEN"] raise ChatSDK::ConfigurationError, "Slack app_token required for socket mode" unless app_token socket = SocketMode.new(app_token: app_token, bot_client: @client) socket.start(&block) end |
#start_typing(channel_id:, thread_id: nil) ⇒ Object
220 221 222 223 |
# File 'lib/chat_sdk/slack/adapter.rb', line 220 def start_typing(channel_id:, thread_id: nil) # Slack doesn't have a native typing indicator API for bots # This is a no-op but the capability is declared for streaming support end |
#upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil) ⇒ Object
124 125 126 127 128 129 |
# File 'lib/chat_sdk/slack/adapter.rb', line 124 def upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil) params = {channels: channel_id, file: Faraday::Multipart::FilePart.new(io, nil, filename)} params[:thread_ts] = thread_id if thread_id params[:initial_comment] = comment if comment @client.files_upload(**params) end |
#verify_request!(rack_request) ⇒ Object
Inbound
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/chat_sdk/slack/adapter.rb', line 34 def verify_request!(rack_request) body = rack_request.body.read rack_request.body.rewind = rack_request.env["HTTP_X_SLACK_REQUEST_TIMESTAMP"] signature = rack_request.env["HTTP_X_SLACK_SIGNATURE"] raise ChatSDK::SignatureVerificationError, "Missing Slack signature headers" unless && signature sig_basestring = "v0:#{}:#{body}" hex_digest = OpenSSL::HMAC.hexdigest("SHA256", @signing_secret, sig_basestring) computed = "v0=#{hex_digest}" unless Rack::Utils.secure_compare(computed, signature) raise ChatSDK::SignatureVerificationError, "Invalid Slack signature" end age = Time.now.to_i - .to_i if age.abs > 300 raise ChatSDK::SignatureVerificationError, "Slack request too old (#{age}s)" end true end |