Class: Legion::Gaia::Channels::SlackAdapter

Inherits:
Legion::Gaia::ChannelAdapter show all
Includes:
Logging::Helper
Defined in:
lib/legion/gaia/channels/slack_adapter.rb

Constant Summary collapse

CAPABILITIES =
%i[rich_text threads reactions mentions file_attachment].freeze

Constants inherited from Legion::Gaia::ChannelAdapter

Legion::Gaia::ChannelAdapter::DIRECT_ADDRESS_PATTERN

Instance Attribute Summary collapse

Attributes inherited from Legion::Gaia::ChannelAdapter

#capabilities, #channel_id

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Legion::Gaia::ChannelAdapter

inherited, register_adapter, #start, #started?, #stop, #supports?

Constructor Details

#initialize(signing_secret: nil, bot_token: nil, default_webhook: nil) ⇒ SlackAdapter

Returns a new instance of SlackAdapter.



29
30
31
32
33
34
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 29

def initialize(signing_secret: nil, bot_token: nil, default_webhook: nil)
  super(channel_id: :slack, capabilities: CAPABILITIES)
  @signing_secret = signing_secret
  @bot_token = bot_token
  @default_webhook = default_webhook
end

Instance Attribute Details

#bot_tokenObject (readonly)

Returns the value of attribute bot_token.



17
18
19
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 17

def bot_token
  @bot_token
end

#signing_secretObject (readonly)

Returns the value of attribute signing_secret.



17
18
19
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 17

def signing_secret
  @signing_secret
end

Class Method Details

.from_settings(settings) ⇒ Object



19
20
21
22
23
24
25
26
27
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 19

def self.from_settings(settings)
  return nil unless settings&.dig(:channels, :slack, :enabled)

  new(
    signing_secret: settings.dig(:channels, :slack, :signing_secret),
    bot_token: settings.dig(:channels, :slack, :bot_token),
    default_webhook: settings.dig(:channels, :slack, :default_webhook)
  )
end

Instance Method Details

#deliver(rendered_content, webhook: nil) ⇒ Object



62
63
64
65
66
67
68
69
70
71
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 62

def deliver(rendered_content, webhook: nil)
  target_webhook = webhook || @default_webhook
  log.info(
    'SlackAdapter delivering ' \
    "mode=#{@bot_token && !target_webhook ? 'api' : 'webhook'} channel=#{channel_id}"
  )
  return deliver_via_api(rendered_content) if @bot_token && !target_webhook

  deliver_via_webhook(rendered_content, target_webhook)
end

#deliver_proactive(output_frame) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 73

def deliver_proactive(output_frame)
  user_id = output_frame.[:target_user]
  return { error: :no_target_user } unless user_id

  dm_result = open_dm(user_id: user_id)
  return dm_result if dm_result.is_a?(Hash) && dm_result[:error]

  channel = dm_result[:channel_id]
  rendered = translate_outbound(output_frame).merge(channel: channel)
  log.info("SlackAdapter proactive delivery user_id=#{user_id} channel=#{channel}")
  deliver_via_api_to_channel(rendered)
end

#open_dm(user_id:) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 86

def open_dm(user_id:)
  return { error: :no_bot_token } unless @bot_token
  return { error: :slack_runner_not_available } unless slack_runner_available?

  result = Legion::Extensions::Slack::Runners::Chat.open_dm(
    user_id: user_id,
    token: @bot_token
  )
  return result if result.is_a?(Hash) && result[:error]

  channel_id = result[:channel_id] || result['channel']&.dig('id') || result['channel']
  log.info("SlackAdapter opened DM user_id=#{user_id} channel=#{channel_id}")
  { channel_id: channel_id }
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'gaia.channels.slack_adapter.open_dm', user_id: user_id)
  { error: :open_dm_failed, message: e.message }
end

#translate_inbound(event) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 36

def translate_inbound(event)
  return nil unless event.is_a?(Hash)

  user = event['user'] || event[:user]
  InputFrame.new(
    content: strip_bot_mention(event['text'] || event[:text] || ''),
    channel_id: :slack,
    content_type: :text,
    channel_capabilities: CAPABILITIES,
    device_context: { platform: :desktop, input_method: :keyboard },
    auth_context: { user_id: user, team_id: event['team'] || event[:team], identity: user },
    metadata: (event)
  )
end

#translate_outbound(output_frame) ⇒ Object



51
52
53
54
55
56
57
58
59
60
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 51

def translate_outbound(output_frame)
  content = output_frame.content.to_s
  thread_ts = output_frame.[:slack_thread_ts]
  channel = output_frame.[:slack_channel]

  result = { text: content }
  result[:thread_ts] = thread_ts if thread_ts
  result[:channel] = channel if channel
  result
end

#verify_request(signing_secret: nil, timestamp: nil, body: nil, signature: nil) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/legion/gaia/channels/slack_adapter.rb', line 104

def verify_request(signing_secret: nil, timestamp: nil, body: nil, signature: nil)
  secret = signing_secret || @signing_secret
  return { valid: false, error: :no_signing_secret } unless secret

  Slack::SigningVerifier.verify(
    signing_secret: secret,
    timestamp: timestamp,
    body: body,
    signature: signature
  )
end