immosquare-slack 0.2.0 → 0.2.2

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: 622166a109e7d56741e24a45e19fb139940e467aaa93713fdbf9ee57800f8d00
4
- data.tar.gz: a7603ceee1e8467dd39b48441b201a5fea6938a3a5a3308bf7855066cdce8059
3
+ metadata.gz: 8ee55a9d9a3ffafa9f99139fbe4931f30bff4138ea4e77ff1ba8d5e1b005f55a
4
+ data.tar.gz: 1e2c4c73dc40c6caf741b983317e878204b9f1e42a865fdb7ca8ede4c2921510
5
5
  SHA512:
6
- metadata.gz: cd8138d1bca5e66c75777ccdee7752c8530fa38ae5aa0e8c737679a25994927274fe6eb87c5840ca936c77d5a562c5d74836f86b665e233a6a66b50fd901e495
7
- data.tar.gz: 3db28b76da6504ef0b8ee7f99309342b860071430ec32843fc886063cf59ffaa117fe4fedfffc252c010a62607a40580dd4f4688784fecb120973155cc790120
6
+ metadata.gz: cc6d87db38e715bb3804a6f86755d9a055d55753bac17d5799bd99462d1e4b61e65a177af45c57da4777ddeb61e9689972e51b212debca92723d5bcbba015502
7
+ data.tar.gz: 3bec4651abf9eafeec9aecbc40581ed4f54733c614a3e370bedd7f2b6161f391f1b79acf88c8d2b5d466168a58a65e442f1b81437dfc1019803a9ac41c287773
@@ -8,10 +8,17 @@ module ImmosquareSlack
8
8
 
9
9
  ##============================================================##
10
10
  ## Fetches the list of channels (public + private, including
11
- ## archived). Memoized per process — call sites that need a
12
- ## fresh list should reset @list_channels explicitly.
11
+ ## archived), memoized per process for the lifetime of that
12
+ ## process (a Puma worker or Sidekiq runs for hours/days).
13
+ ##
14
+ ## Piège : le cache ne reflète que l'état du workspace au
15
+ ## premier appel. Un channel créé ou renommé ensuite n'y est
16
+ ## pas. `force: true` vide le cache et refetch — à utiliser sur
17
+ ## un miss de lookup pour distinguer un channel réellement
18
+ ## absent d'un cache périmé avant tout fallback.
13
19
  ##============================================================##
14
- def list_channels
20
+ def list_channels(force: false)
21
+ @list_channels = nil if force
15
22
  @list_channels ||= begin
16
23
  extra_params = {
17
24
  :types => ["public_channel", "private_channel"].join(","),
@@ -33,6 +40,13 @@ module ImmosquareSlack
33
40
  ## If no channel can be resolved after fallback, raises an
34
41
  ## ArgumentError immediately (clearer than the cryptic error
35
42
  ## returned by the Slack API).
43
+ ##
44
+ ## Slack only notifies a mention to members of the channel the
45
+ ## message lands in. When `notify` is an Array of emails, the
46
+ ## mentioned users outside that channel (all of them when the
47
+ ## message falls back to the general channel) also receive it
48
+ ## as a direct message from the bot, unless
49
+ ## `direct_message_unreachable_mentions` is disabled.
36
50
  ##============================================================##
37
51
  def post_message(text, channel_name: nil, notify: nil, notify_text: nil, bot_name: nil, notify_general_if_invalid_channel: true)
38
52
  channel_name ||= ImmosquareSlack.configuration.default_channel
@@ -40,27 +54,27 @@ module ImmosquareSlack
40
54
 
41
55
  raise(ArgumentError, "channel_name is required (or set ImmosquareSlack.configuration.default_channel)") if channel_name.nil?
42
56
 
43
- channel_id = get_channel_id_by_name(channel_name)
57
+ ##============================================================##
58
+ ## A miss can mean the channel really doesn't exist, or that
59
+ ## the memoized list predates its creation. Retry once on a
60
+ ## freshly fetched list before declaring it missing.
61
+ ##============================================================##
62
+ channel_id = get_channel_id_by_name(channel_name)
63
+ channel_id = get_channel_id_by_name(channel_name, :force => true) if channel_id.nil?
64
+ mentioned_members = notify.is_a?(Array) ? members_with_emails(notify) : []
44
65
 
45
66
  if channel_id.nil?
46
- text = "immosquare-slack missing channel *#{channel_name}*\nmessage:\n#{text}"
47
- return post_message(text, :channel_name => GENERAL_CHANNEL, :notify => :channel, :notify_text => "", :bot_name => bot_name, :notify_general_if_invalid_channel => false) if channel_name != GENERAL_CHANNEL && notify_general_if_invalid_channel
67
+ raise("channel '#{channel_name}' not found on slack") if channel_name == GENERAL_CHANNEL || !notify_general_if_invalid_channel
48
68
 
49
- raise("channel '#{channel_name}' not found on slack")
69
+ direct_message_unreachable_members(mentioned_members, nil, "Mentioned in *##{channel_name}* (channel not found):\n#{text}", bot_name)
70
+ text = "immosquare-slack missing channel *#{channel_name}*\nmessage:\n#{text}"
71
+ return post_message(text, :channel_name => GENERAL_CHANNEL, :notify => :channel, :notify_text => "", :bot_name => bot_name, :notify_general_if_invalid_channel => false)
50
72
  end
51
73
 
52
- url = "https://slack.com/api/chat.postMessage"
53
- notification_text = notify ? build_notification_text(channel_id, notify, *notify_text) : nil
54
- text = "#{notification_text}#{text}"
55
-
56
- body = {
57
- :channel => channel_id,
58
- :text => text
59
- }
60
-
61
- body[:username] = bot_name if bot_name
62
-
63
- make_slack_api_call(url, :method => :post, :body => body)
74
+ notification_text = notify ? build_notification_text(channel_id, notify, mentioned_members, *notify_text) : nil
75
+ response = send_message(channel_id, "#{notification_text}#{text}", bot_name)
76
+ direct_message_unreachable_members(mentioned_members, channel_id, "Mentioned in <##{channel_id}>:\n#{text}", bot_name)
77
+ response
64
78
  end
65
79
 
66
80
  private
@@ -70,8 +84,8 @@ module ImmosquareSlack
70
84
  ## via the `is_general` flag because workspaces can rename
71
85
  ## their general channel.
72
86
  ##============================================================##
73
- def get_channel_id_by_name(channel_name)
74
- channels = list_channels
87
+ def get_channel_id_by_name(channel_name, force: false)
88
+ channels = list_channels(:force => force)
75
89
  channel = channels.find do |c|
76
90
  if channel_name == GENERAL_CHANNEL
77
91
  c["is_general"]
@@ -79,7 +93,7 @@ module ImmosquareSlack
79
93
  c["name"] == channel_name && c["is_archived"] == false
80
94
  end
81
95
  end
82
- channel.nil? ? nil : channel["id"]
96
+ channel&.[]("id")
83
97
  end
84
98
 
85
99
  ##============================================================##
@@ -89,6 +103,54 @@ module ImmosquareSlack
89
103
  fetch_paginated_data("https://slack.com/api/conversations.members", "members", {:channel => channel_id})
90
104
  end
91
105
 
106
+ ##============================================================##
107
+ ## Posts `text` to a conversation: a channel id, or a user id
108
+ ## for a direct message from the bot.
109
+ ##============================================================##
110
+ def send_message(conversation_id, text, bot_name)
111
+ body = {
112
+ :channel => conversation_id,
113
+ :text => text
114
+ }
115
+
116
+ body[:username] = bot_name if bot_name
117
+
118
+ make_slack_api_call("https://slack.com/api/chat.postMessage", :method => :post, :body => body)
119
+ end
120
+
121
+ ##============================================================##
122
+ ## Active workspace members whose `profile.email` is in
123
+ ## `emails`. Without the `users:read.email` scope no profile
124
+ ## carries an email, so nobody matches.
125
+ ##
126
+ ## Piège : `users.list` renvoie aussi les comptes désactivés,
127
+ ## qui gardent leur email mais ne peuvent recevoir aucun DM.
128
+ ##============================================================##
129
+ def members_with_emails(emails)
130
+ ImmosquareSlack::User.list_users.select {|m| !m["deleted"] && emails.include?(m.dig("profile", "email")) }
131
+ end
132
+
133
+ ##============================================================##
134
+ ## Sends `text` as a direct message to the mentioned members who
135
+ ## are not in the channel (`channel_id` nil: none of them is
136
+ ## reachable), since Slack would not notify them.
137
+ ##
138
+ ## Best effort : un DM refusé par Slack est ignoré. Le laisser
139
+ ## remonter ferait échouer `post_message` après le post dans le
140
+ ## channel (doublon au retry de l'appelant) ou avant le repli
141
+ ## sur general (message perdu).
142
+ ##============================================================##
143
+ def direct_message_unreachable_members(mentioned_members, channel_id, text, bot_name)
144
+ return if mentioned_members.empty? || !ImmosquareSlack.configuration.direct_message_unreachable_mentions
145
+
146
+ channel_member_ids = channel_id ? get_channel_members(channel_id) : []
147
+ mentioned_members.reject {|m| channel_member_ids.include?(m["id"]) }.each do |member|
148
+ send_message(member["id"], text, bot_name)
149
+ rescue RuntimeError
150
+ next
151
+ end
152
+ end
153
+
92
154
  ##============================================================##
93
155
  ## Builds the notification prefix prepended to the message
94
156
  ## body. `notify` may be:
@@ -99,12 +161,10 @@ module ImmosquareSlack
99
161
  ## Note: avoids `in?` and `present?` so the gem stays usable
100
162
  ## from plain Ruby (no Rails / ActiveSupport required).
101
163
  ##============================================================##
102
- def build_notification_text(channel_id, notify, text = "Hello")
164
+ def build_notification_text(channel_id, notify, mentioned_members, text = "Hello")
103
165
  final =
104
166
  if notify.is_a?(Array)
105
- members = ImmosquareSlack::User.list_users
106
- members = members.select {|m| notify.include?(m["profile"]["email"]) }
107
- members.map {|m| "<@#{m["id"]}>" }.join(", ")
167
+ mentioned_members.map {|m| "<@#{m["id"]}>" }.join(", ")
108
168
  elsif notify.to_sym == :all
109
169
  members = get_channel_members(channel_id)
110
170
  members.map {|m| "<@#{m}>" }.join(", ")
@@ -1,12 +1,13 @@
1
1
  module ImmosquareSlack
2
2
  class Configuration
3
3
 
4
- attr_accessor :slack_api_token_bot, :default_channel, :default_bot_name
4
+ attr_accessor :slack_api_token_bot, :default_channel, :default_bot_name, :direct_message_unreachable_mentions
5
5
 
6
6
  def initialize
7
- @slack_api_token_bot = nil
8
- @default_channel = nil
9
- @default_bot_name = nil
7
+ @slack_api_token_bot = nil
8
+ @default_channel = nil
9
+ @default_bot_name = nil
10
+ @direct_message_unreachable_mentions = true
10
11
  end
11
12
 
12
13
  end
@@ -1,6 +1,7 @@
1
1
  module ImmosquareSlack
2
2
  module User
3
3
  extend SharedMethods
4
+
4
5
  class << self
5
6
 
6
7
 
@@ -1,3 +1,3 @@
1
1
  module ImmosquareSlack
2
- VERSION = "0.2.0".freeze
2
+ VERSION = "0.2.2".freeze
3
3
  end
@@ -4,15 +4,15 @@ require_relative "immosquare-slack/channel"
4
4
  require_relative "immosquare-slack/user"
5
5
 
6
6
 
7
- ##===========================================================================##
8
- ##
9
- ##===========================================================================##
7
+ ##============================================================##
8
+ ## ...
9
+ ##============================================================##
10
10
  module ImmosquareSlack
11
11
  class << self
12
12
 
13
- ##===========================================================================##
13
+ ##============================================================##
14
14
  ## Gem configuration
15
- ##===========================================================================##
15
+ ##============================================================##
16
16
  attr_writer :configuration
17
17
 
18
18
  def configuration
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immosquare-slack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - immosquare
@@ -58,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  - !ruby/object:Gem::Version
59
59
  version: '0'
60
60
  requirements: []
61
- rubygems_version: 4.0.12
61
+ rubygems_version: 4.0.23
62
62
  specification_version: 4
63
63
  summary: A Ruby gem for integrating with Slack API to perform various actions like
64
64
  posting messages.