slack_line 1.0 → 1.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 +4 -4
- data/.github/workflows/linters.yml +5 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +2 -0
- data/README.md +142 -6
- data/bin/slack_line_stateful_thread +9 -0
- data/lib/slack_line/cli/slack_line_message.rb +6 -2
- data/lib/slack_line/cli/slack_line_stateful_thread.rb +126 -0
- data/lib/slack_line/cli/slack_line_thread.rb +7 -3
- data/lib/slack_line/client.rb +2 -2
- data/lib/slack_line/configuration.rb +23 -2
- data/lib/slack_line/disk_caching.rb +18 -0
- data/lib/slack_line/groups.rb +9 -4
- data/lib/slack_line/sent_thread.rb +6 -1
- data/lib/slack_line/users.rb +9 -4
- data/lib/slack_line/version.rb +1 -1
- data/slack_line.gemspec +5 -5
- metadata +12 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc23e7f575bc68df1c9824aacbde9b74050c14f82f765fc6929dc33ae909eccb
|
|
4
|
+
data.tar.gz: 45e54647313e4c71a27de6c8bda77826deaf7704ac8a5680c1439c5015f9b50e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c9d9e4c86c4ae41adac7a71a1044fe02aef53eaa823bc286862178b9fce6de8716d51a7833ed6ee118a550e29726358dd7b2a54df3bc78526074be65a125af4
|
|
7
|
+
data.tar.gz: fc217ed772fbbcbf1a27aa0911d24f2ef716c4b6ffed127f2c0959329b5358e510654a4d313c9d42e8a0168a7b88fe3c5d253d46545775175bd3671c3e593a65
|
|
@@ -15,10 +15,11 @@ jobs:
|
|
|
15
15
|
|
|
16
16
|
- name: Cache gems
|
|
17
17
|
uses: actions/cache@v3
|
|
18
|
-
with:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
with:
|
|
19
|
+
path: vendor/bundle
|
|
20
|
+
key: ${{ runner.os }}-linters-${{ hashFiles('Gemfile.lock') }}
|
|
21
|
+
restore-keys:
|
|
22
|
+
${{ runner.os }}-linters-
|
|
22
23
|
|
|
23
24
|
- name: Install gems
|
|
24
25
|
run: bundle install --jobs 4 --retry 3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Version 1.0
|
|
4
|
+
|
|
5
|
+
Initially published gem. Can send messages and threads, update messages, extend
|
|
6
|
+
threads, persist those things to disk and reload them. Convenience scripts that
|
|
7
|
+
can do each of those things directly (for simpler messages).
|
|
8
|
+
|
|
9
|
+
## Version 1.1
|
|
10
|
+
|
|
11
|
+
Fixed incorrect description/summary on gemspec.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -80,6 +80,18 @@ be set via ENV or `SlackLine.configure`:
|
|
|
80
80
|
* `per_thread_delay` or `SLACK_LINE_PER_THREAD_DELAY` is a float as
|
|
81
81
|
well - SlackLine will `sleep` for this duration after each _thread_
|
|
82
82
|
is posted, and after each non-thread message is posted.
|
|
83
|
+
* `cache_path` or `SLACK_LINE_CACHE_PATH` - a directory path for
|
|
84
|
+
disk-caching the users and groups lists fetched from the Slack API.
|
|
85
|
+
When set, SlackLine will use the [lightly](https://github.com/DannyBen/lightly)
|
|
86
|
+
gem to cache results to disk, which avoids redundant API calls across
|
|
87
|
+
separate runs. Requires `lightly` to be installed as an optional
|
|
88
|
+
dependency - if it is not available, a `DiskCaching::NoLightly` error
|
|
89
|
+
will be raised at runtime.
|
|
90
|
+
* `cache_duration` or `SLACK_LINE_CACHE_DURATION` - how long cached
|
|
91
|
+
data remains valid (default `"15m"`). Accepts a plain integer number
|
|
92
|
+
of seconds, or a number followed by a unit suffix: `s` (seconds),
|
|
93
|
+
`m` (minutes), `h` (hours), or `d` (days). For example: `"30m"`,
|
|
94
|
+
`"2h"`, `"1d"`, or `"900"`.
|
|
83
95
|
|
|
84
96
|
You can just set those via the environment variables, but you can also
|
|
85
97
|
set them on the singleton configuration object:
|
|
@@ -92,10 +104,12 @@ SlackLine.configure do |config|
|
|
|
92
104
|
config.default_channel = "#ci-flow"
|
|
93
105
|
config.per_message_delay = 0.2
|
|
94
106
|
config.per_thread_delay = 2.0
|
|
107
|
+
config.cache_path = "/tmp/slack_line_cache"
|
|
108
|
+
config.cache_duration = "1h"
|
|
95
109
|
end
|
|
96
110
|
```
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
### Multiple Configurations
|
|
99
113
|
|
|
100
114
|
If you're working in a context where you need to support multiple
|
|
101
115
|
SlackLine configurations, don't worry! The singleton central config is
|
|
@@ -122,13 +136,135 @@ BAR_SLACK.thread("Message 1", "Message 2").post
|
|
|
122
136
|
BAR_SLACK.message("Message 3", to: "#bar-team-3").post
|
|
123
137
|
```
|
|
124
138
|
|
|
139
|
+
## CLI Scripts
|
|
140
|
+
|
|
141
|
+
The gem ships with three executable scripts for sending and managing Slack messages
|
|
142
|
+
from the command line. All three accept `-t`/`--slack-token TOKEN` and
|
|
143
|
+
`-n`/`--bot-name NAME` to override the corresponding environment variables.
|
|
144
|
+
Configuration can also come from `SLACK_LINE_SLACK_TOKEN` and friends as described
|
|
145
|
+
above.
|
|
146
|
+
|
|
147
|
+
### `slack_line_message`
|
|
148
|
+
|
|
149
|
+
Sends, updates, or previews a single Slack message.
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
# Post a simple message
|
|
153
|
+
slack_line_message --post-to "#general" --save /tmp/msg.json "Something happened!"
|
|
154
|
+
|
|
155
|
+
# Preview without posting (prints JSON block kit content)
|
|
156
|
+
slack_line_message "Something happened!"
|
|
157
|
+
|
|
158
|
+
# Post a message using the block-kit DSL on stdin
|
|
159
|
+
echo 'text "Something happened!"' | slack_line_message --post-to "#general" --save /tmp/msg.json
|
|
160
|
+
|
|
161
|
+
# Append a reply to an existing thread (saved from a prior post)
|
|
162
|
+
slack_line_message --append /tmp/msg.json --save /tmp/msg.json "Follow-up!"
|
|
163
|
+
|
|
164
|
+
# Update a previously-sent message in place
|
|
165
|
+
slack_line_message --update /tmp/msg.json "Edited message text"
|
|
166
|
+
|
|
167
|
+
# Update a specific message within a saved thread (0-indexed)
|
|
168
|
+
slack_line_message --update /tmp/msg.json --message-number 2 "Corrected reply"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Options:
|
|
172
|
+
|
|
173
|
+
* `-p`/`--post-to TARGET` - channel or user to post to
|
|
174
|
+
* `-a`/`--append PATH` - append a reply to the thread saved at PATH
|
|
175
|
+
* `-U`/`--update PATH` - update the message (or thread) saved at PATH
|
|
176
|
+
* `-m`/`--message-number N` - which message in a thread to update (0-indexed;
|
|
177
|
+
required with `--update` on a thread)
|
|
178
|
+
* `-s`/`--save PATH` - write the sent/updated result to PATH as JSON
|
|
179
|
+
* `-u`/`--look-up-users` - resolve `@mentions` via the Slack API
|
|
180
|
+
* `--cache-path PATH` / `--cache-duration DURATION` - disk-cache user/group lookups
|
|
181
|
+
* `--no-backoff` - disable per-message sleep delays
|
|
182
|
+
|
|
183
|
+
When no content arguments are given and no DSL is piped, the script reads DSL
|
|
184
|
+
interactively from stdin.
|
|
185
|
+
|
|
186
|
+
### `slack_line_thread`
|
|
187
|
+
|
|
188
|
+
Sends or previews a thread (multiple messages posted together).
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
# Post a thread from positional string arguments
|
|
192
|
+
slack_line_thread --post-to "#general" --save /tmp/thread.json "First" "Second" "Third"
|
|
193
|
+
|
|
194
|
+
# Preview the thread without posting
|
|
195
|
+
slack_line_thread "First" "Second"
|
|
196
|
+
|
|
197
|
+
# Post a thread from block-kit DSL on stdin
|
|
198
|
+
cat thread.dsl | slack_line_thread --post-to "#general" --save /tmp/thread.json
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
A DSL block looks like:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
message "Simple first message"
|
|
205
|
+
message do
|
|
206
|
+
text "Fancier second message"
|
|
207
|
+
context "with some context"
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Options mirror `slack_line_message`, minus the update/append flags:
|
|
212
|
+
|
|
213
|
+
* `-p`/`--post-to TARGET` - channel or user to post to
|
|
214
|
+
* `-s`/`--save PATH` - write the sent result to PATH as JSON
|
|
215
|
+
* `-u`/`--look-up-users`, `--cache-path`, `--cache-duration`, `--no-backoff` -
|
|
216
|
+
same as above
|
|
217
|
+
|
|
218
|
+
### `slack_line_stateful_thread`
|
|
219
|
+
|
|
220
|
+
Designed for long-running processes that need to post a single status message
|
|
221
|
+
and then keep it updated as state changes - for example, a deployment pipeline
|
|
222
|
+
that posts `[running] Deploy started`, updates it to `[done] Deploy finished`,
|
|
223
|
+
and appends replies along the way. The sent message is persisted to a file;
|
|
224
|
+
subsequent invocations load and re-persist that file to know which Slack message
|
|
225
|
+
to update.
|
|
226
|
+
|
|
227
|
+
Messages are formatted as `[STATE] body`. The `--state` and `--message` flags each
|
|
228
|
+
update their respective part independently; omitting one leaves it unchanged on update.
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
# Initial post - creates /tmp/deploy.json and posts "[running] Deploy started"
|
|
232
|
+
slack_line_stateful_thread --path /tmp/deploy.json --post-to "#deploys" \
|
|
233
|
+
--state running --message "Deploy started"
|
|
234
|
+
|
|
235
|
+
# Update the state only - becomes "[done] Deploy started"
|
|
236
|
+
slack_line_stateful_thread --path /tmp/deploy.json --state done
|
|
237
|
+
|
|
238
|
+
# Update the body only - becomes "[done] Deploy finished"
|
|
239
|
+
slack_line_stateful_thread --path /tmp/deploy.json --message "Deploy finished"
|
|
240
|
+
|
|
241
|
+
# Update both state and body - becomes "[failed] Something went wrong"
|
|
242
|
+
slack_line_stateful_thread --path /tmp/deploy.json --state failed --message "Something went wrong"
|
|
243
|
+
|
|
244
|
+
# Append a thread reply without changing the main message
|
|
245
|
+
slack_line_stateful_thread --path /tmp/deploy.json --thread --message "Step 1 complete"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Options:
|
|
249
|
+
|
|
250
|
+
* `--path PATH` - (required) file path used to persist the sent message between invocations
|
|
251
|
+
* `-p`/`--post-to TARGET` - channel or user; required on the first call, forbidden
|
|
252
|
+
thereafter
|
|
253
|
+
* `-s`/`--state STATE` - the state label shown in brackets; required on first call
|
|
254
|
+
* `-m`/`--message MESSAGE` - the message body; required on first call and when using
|
|
255
|
+
`--thread`
|
|
256
|
+
* `--thread` - append a reply instead of updating the main message (mutually exclusive
|
|
257
|
+
with `--state`)
|
|
258
|
+
|
|
125
259
|
## Slack App Permissions
|
|
126
260
|
|
|
127
|
-
In order to post/update messages, the app behind your `SLACK_LINE_TOKEN` can use
|
|
261
|
+
In order to post/update messages, the app behind your `SLACK_LINE_TOKEN` can use
|
|
262
|
+
these permissions:
|
|
128
263
|
|
|
129
264
|
* `chat:write` - send messages at all.
|
|
130
|
-
* `chat:write.public` - send messages to public channels your app _isn't a member
|
|
131
|
-
need to invite them to the relevant channels to make them work).
|
|
265
|
+
* `chat:write.public` - send messages to public channels your app _isn't a member
|
|
266
|
+
of_ (so you don't need to invite them to the relevant channels to make them work).
|
|
132
267
|
* `im:write` - start direct messages with individuals.
|
|
133
|
-
* `users:read` and `usergroups:read` - look up users/groups for (a) messaging them
|
|
134
|
-
(b) supporting the `look_up_users` config option (for those more
|
|
268
|
+
* `users:read` and `usergroups:read` - look up users/groups for (a) messaging them
|
|
269
|
+
directly or (b) supporting the `look_up_users` config option (for those more
|
|
270
|
+
restrictive workspaces)
|
|
@@ -20,11 +20,13 @@ module SlackLine
|
|
|
20
20
|
else
|
|
21
21
|
run_preview
|
|
22
22
|
end
|
|
23
|
+
rescue DiskCaching::NoLightly, Configuration::InvalidValue => e
|
|
24
|
+
raise ExitException, e.message
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def options
|
|
26
28
|
return @options if defined?(@options)
|
|
27
|
-
opts = {post_to: nil, append: nil, update: nil, message_number: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil}
|
|
29
|
+
opts = {post_to: nil, append: nil, update: nil, message_number: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil, cache_path: nil, cache_duration: nil}
|
|
28
30
|
remaining = option_parser(opts).parse(@argv.dup)
|
|
29
31
|
if remaining.empty?
|
|
30
32
|
opts[:dsl] = read_stdin
|
|
@@ -37,7 +39,7 @@ module SlackLine
|
|
|
37
39
|
|
|
38
40
|
def configuration
|
|
39
41
|
return @configuration if defined?(@configuration)
|
|
40
|
-
cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff).compact
|
|
42
|
+
cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff, :cache_path, :cache_duration).compact
|
|
41
43
|
@configuration = Configuration.new(nil, **cfg_opts)
|
|
42
44
|
end
|
|
43
45
|
|
|
@@ -56,6 +58,8 @@ module SlackLine
|
|
|
56
58
|
parser.on("-m", "--message-number N", Integer) { |n| opts[:message_number] = n }
|
|
57
59
|
parser.on("-s", "--save PATH") { |p| opts[:save] = p }
|
|
58
60
|
parser.on("--no-backoff") { opts[:backoff] = false }
|
|
61
|
+
parser.on("--cache-path PATH") { |p| opts[:cache_path] = p }
|
|
62
|
+
parser.on("--cache-duration DURATION") { |d| opts[:cache_duration] = d }
|
|
59
63
|
end
|
|
60
64
|
end
|
|
61
65
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module SlackLine
|
|
4
|
+
module Cli
|
|
5
|
+
class SlackLineStatefulThread
|
|
6
|
+
def initialize(argv:, stdout: $stdout, stderr: $stderr)
|
|
7
|
+
@argv = argv
|
|
8
|
+
@stdout = stdout
|
|
9
|
+
@stderr = stderr
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
validate_options!
|
|
14
|
+
if path_exists?
|
|
15
|
+
run_subsequent
|
|
16
|
+
else
|
|
17
|
+
run_initial
|
|
18
|
+
end
|
|
19
|
+
rescue DiskCaching::NoLightly, Configuration::InvalidValue => e
|
|
20
|
+
raise ExitException, e.message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def options
|
|
24
|
+
return @options if defined?(@options)
|
|
25
|
+
opts = {path: nil, post_to: nil, state: nil, message: nil, thread: nil, slack_token: nil, bot_name: nil}
|
|
26
|
+
option_parser(opts).parse(@argv.dup)
|
|
27
|
+
@options = opts
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def configuration
|
|
31
|
+
return @configuration if defined?(@configuration)
|
|
32
|
+
cfg_opts = options.slice(:slack_token, :bot_name).compact
|
|
33
|
+
@configuration = Configuration.new(nil, **cfg_opts)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :stdout, :stderr
|
|
39
|
+
|
|
40
|
+
def option_parser(opts) # rubocop:disable Metrics/AbcSize
|
|
41
|
+
OptionParser.new do |parser|
|
|
42
|
+
parser.on("-t", "--slack-token TOKEN") { |t| opts[:slack_token] = t }
|
|
43
|
+
parser.on("-n", "--bot-name NAME") { |n| opts[:bot_name] = n }
|
|
44
|
+
parser.on("--path PATH") { |p| opts[:path] = p }
|
|
45
|
+
parser.on("-p", "--post-to TARGET") { |t| opts[:post_to] = t }
|
|
46
|
+
parser.on("-s", "--state STATE") { |s| opts[:state] = s }
|
|
47
|
+
parser.on("-m", "--message MESSAGE") { |m| opts[:message] = m }
|
|
48
|
+
parser.on("--thread") { opts[:thread] = true }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def validate_options!
|
|
53
|
+
raise ExitException, "--path is required" unless options[:path]
|
|
54
|
+
path_exists? ? validate_subsequent_options! : validate_initial_options!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def validate_initial_options!
|
|
58
|
+
raise ExitException, "--thread cannot be used on initial post" if options[:thread]
|
|
59
|
+
raise ExitException, "--post-to is required for initial post" unless options[:post_to]
|
|
60
|
+
raise ExitException, "--state is required for initial post" unless options[:state]
|
|
61
|
+
raise ExitException, "--message is required for initial post" unless options[:message]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def validate_subsequent_options!
|
|
65
|
+
raise ExitException, "--post-to cannot be used after initial post" if options[:post_to]
|
|
66
|
+
options[:thread] ? validate_thread_options! : validate_update_options!
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_thread_options!
|
|
70
|
+
raise ExitException, "--thread cannot be used with --state" if options[:state]
|
|
71
|
+
raise ExitException, "--thread requires --message" unless options[:message]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate_update_options!
|
|
75
|
+
raise ExitException, "One of --state or --message is required" unless options[:state] || options[:message]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def path_exists? = options[:path] && File.exist?(options[:path])
|
|
79
|
+
|
|
80
|
+
def client = @client ||= Client.new(configuration)
|
|
81
|
+
|
|
82
|
+
def load_sent
|
|
83
|
+
@load_sent ||= SlackLine.from_json(JSON.parse(File.read(options[:path])), client:)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def save_sent(result) = File.write(options[:path], JSON.pretty_generate(result.as_json))
|
|
87
|
+
|
|
88
|
+
def run_initial
|
|
89
|
+
text = "[#{options[:state]}] #{options[:message]}"
|
|
90
|
+
sent = Message.new(text, client:).post(to: options[:post_to])
|
|
91
|
+
save_sent(sent)
|
|
92
|
+
stderr.puts "Posted stateful thread to #{options[:post_to]}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def run_subsequent
|
|
96
|
+
options[:thread] ? run_thread_message : run_update_message
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def parse_state_message(text)
|
|
100
|
+
match = text&.match(/\A\[([^\]]*)\] (.+)\z/m)
|
|
101
|
+
raise ExitException, "Cannot parse state and body from stored message content" unless match
|
|
102
|
+
[match[1], match[2]]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def current_state_and_body(sent)
|
|
106
|
+
parse_state_message(sent.content.dig(0, "text", "text"))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def run_update_message
|
|
110
|
+
sent = load_sent
|
|
111
|
+
current_state, current_body = current_state_and_body(sent)
|
|
112
|
+
new_text = "[#{options[:state] || current_state}] #{options[:message] || current_body}"
|
|
113
|
+
updated = sent.update(Message.new(new_text, client:))
|
|
114
|
+
save_sent(updated)
|
|
115
|
+
stderr.puts "Updated message in #{updated.channel}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def run_thread_message
|
|
119
|
+
sent = load_sent
|
|
120
|
+
sent_thread = sent.append(options[:message])
|
|
121
|
+
save_sent(sent_thread)
|
|
122
|
+
stderr.puts "Threaded message in #{sent_thread.channel}"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -16,11 +16,13 @@ module SlackLine
|
|
|
16
16
|
else
|
|
17
17
|
run_preview
|
|
18
18
|
end
|
|
19
|
+
rescue DiskCaching::NoLightly, Configuration::InvalidValue => e
|
|
20
|
+
raise ExitException, e.message
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def options
|
|
22
24
|
return @options if defined?(@options)
|
|
23
|
-
opts = {post_to: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil}
|
|
25
|
+
opts = {post_to: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil, cache_path: nil, cache_duration: nil}
|
|
24
26
|
remaining = option_parser(opts).parse(@argv.dup)
|
|
25
27
|
if remaining.empty?
|
|
26
28
|
opts[:dsl] = read_stdin
|
|
@@ -32,7 +34,7 @@ module SlackLine
|
|
|
32
34
|
|
|
33
35
|
def configuration
|
|
34
36
|
return @configuration if defined?(@configuration)
|
|
35
|
-
cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff).compact
|
|
37
|
+
cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff, :cache_path, :cache_duration).compact
|
|
36
38
|
@configuration = Configuration.new(nil, **cfg_opts)
|
|
37
39
|
end
|
|
38
40
|
|
|
@@ -40,7 +42,7 @@ module SlackLine
|
|
|
40
42
|
|
|
41
43
|
attr_reader :stdout, :stderr
|
|
42
44
|
|
|
43
|
-
def option_parser(opts)
|
|
45
|
+
def option_parser(opts) # rubocop:disable Metrics/AbcSize
|
|
44
46
|
OptionParser.new do |parser|
|
|
45
47
|
parser.on("-t", "--slack-token TOKEN") { |t| opts[:slack_token] = t }
|
|
46
48
|
parser.on("-u", "--look-up-users") { opts[:look_up_users] = true }
|
|
@@ -48,6 +50,8 @@ module SlackLine
|
|
|
48
50
|
parser.on("-p", "--post-to TARGET") { |t| opts[:post_to] = t }
|
|
49
51
|
parser.on("-s", "--save PATH") { |p| opts[:save] = p }
|
|
50
52
|
parser.on("--no-backoff") { opts[:backoff] = false }
|
|
53
|
+
parser.on("--cache-path PATH") { |p| opts[:cache_path] = p }
|
|
54
|
+
parser.on("--cache-duration DURATION") { |d| opts[:cache_duration] = d }
|
|
51
55
|
end
|
|
52
56
|
end
|
|
53
57
|
|
data/lib/slack_line/client.rb
CHANGED
|
@@ -16,8 +16,8 @@ module SlackLine
|
|
|
16
16
|
|
|
17
17
|
def thread(*messages, &dsl_block) = Thread.new(*messages, client: self, &dsl_block)
|
|
18
18
|
|
|
19
|
-
memoize def users = Users.new(
|
|
19
|
+
memoize def users = Users.new(client: self)
|
|
20
20
|
|
|
21
|
-
memoize def groups = Groups.new(
|
|
21
|
+
memoize def groups = Groups.new(client: self)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
module SlackLine
|
|
2
2
|
class Configuration
|
|
3
|
+
include Memoization
|
|
4
|
+
|
|
5
|
+
InvalidValue = Class.new(Error)
|
|
6
|
+
|
|
3
7
|
attr_accessor :slack_token,
|
|
4
8
|
:look_up_users, :bot_name, :default_channel,
|
|
5
9
|
:per_message_delay, :per_thread_delay,
|
|
6
|
-
:backoff
|
|
10
|
+
:backoff, :cache_path
|
|
11
|
+
attr_writer :cache_duration
|
|
7
12
|
|
|
8
13
|
alias_method :look_up_users?, :look_up_users
|
|
9
14
|
|
|
15
|
+
memoize def cache_duration = parse_duration(@cache_duration.to_s)
|
|
16
|
+
|
|
10
17
|
DEFAULTS = {
|
|
11
18
|
slack_token: nil,
|
|
12
19
|
look_up_users: false,
|
|
@@ -14,7 +21,9 @@ module SlackLine
|
|
|
14
21
|
default_channel: nil,
|
|
15
22
|
per_message_delay: 0.0,
|
|
16
23
|
per_thread_delay: 0.0,
|
|
17
|
-
backoff: true
|
|
24
|
+
backoff: true,
|
|
25
|
+
cache_path: nil,
|
|
26
|
+
cache_duration: "15m"
|
|
18
27
|
}.freeze
|
|
19
28
|
|
|
20
29
|
def initialize(base_config = nil, **overrides)
|
|
@@ -28,6 +37,8 @@ module SlackLine
|
|
|
28
37
|
@per_message_delay = cascade(:per_message_delay, "SLACK_LINE_PER_MESSAGE_DELAY", :float)
|
|
29
38
|
@per_thread_delay = cascade(:per_thread_delay, "SLACK_LINE_PER_THREAD_DELAY", :float)
|
|
30
39
|
@backoff = cascade(:backoff, "SLACK_LINE_NO_BACKOFF", :inverse_boolean)
|
|
40
|
+
@cache_path = cascade(:cache_path, "SLACK_LINE_CACHE_PATH", :string)
|
|
41
|
+
@cache_duration = cascade(:cache_duration, "SLACK_LINE_CACHE_DURATION", :string)
|
|
31
42
|
end
|
|
32
43
|
|
|
33
44
|
private
|
|
@@ -57,5 +68,15 @@ module SlackLine
|
|
|
57
68
|
value
|
|
58
69
|
end
|
|
59
70
|
end
|
|
71
|
+
|
|
72
|
+
DURATION_MULTIPLIERS = {s: 1, m: 60, h: 3600, d: 86400}.freeze
|
|
73
|
+
|
|
74
|
+
def parse_duration(value)
|
|
75
|
+
match = value.match(/\A(\d+)([smhd])?\z/)
|
|
76
|
+
raise(InvalidValue, "Invalid duration: #{value.inspect}") unless match
|
|
77
|
+
|
|
78
|
+
digits, unit = match.captures
|
|
79
|
+
digits.to_i * DURATION_MULTIPLIERS.fetch(unit&.to_sym, 1)
|
|
80
|
+
end
|
|
60
81
|
end
|
|
61
82
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require "lightly"
|
|
3
|
+
rescue LoadError
|
|
4
|
+
# optional dependency
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module SlackLine
|
|
8
|
+
module DiskCaching
|
|
9
|
+
NoLightly = Class.new(Error)
|
|
10
|
+
|
|
11
|
+
def cached(config:, key:, &block)
|
|
12
|
+
return yield if config.cache_path.nil?
|
|
13
|
+
raise(NoLightly, "The 'lightly' gem is required for disk caching") unless defined?(Lightly)
|
|
14
|
+
|
|
15
|
+
Lightly.new(dir: config.cache_path, life: config.cache_duration).get(key) { yield }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/slack_line/groups.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
module SlackLine
|
|
2
2
|
class Groups
|
|
3
3
|
include Memoization
|
|
4
|
+
include DiskCaching
|
|
4
5
|
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
6
|
+
def initialize(client:)
|
|
7
|
+
@client = client
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
memoize def all
|
|
10
|
+
memoize def all
|
|
11
|
+
cached(config: client.configuration, key: "groups_all") { fetch_groups }
|
|
12
|
+
end
|
|
10
13
|
|
|
11
14
|
def find(handle:)
|
|
12
15
|
groups_by_handle[handle.downcase]
|
|
@@ -14,7 +17,9 @@ module SlackLine
|
|
|
14
17
|
|
|
15
18
|
private
|
|
16
19
|
|
|
17
|
-
attr_reader :
|
|
20
|
+
attr_reader :client
|
|
21
|
+
|
|
22
|
+
def slack_client = client.slack_client
|
|
18
23
|
|
|
19
24
|
memoize def fetch_groups
|
|
20
25
|
slack_client.usergroups_list.usergroups || []
|
|
@@ -10,9 +10,14 @@ module SlackLine
|
|
|
10
10
|
attr_reader :sent_messages
|
|
11
11
|
alias_method :messages, :sent_messages
|
|
12
12
|
def_delegators :sent_messages, :each, :map, :size, :first, :last, :empty?
|
|
13
|
-
def_delegators :first, :channel, :ts
|
|
13
|
+
def_delegators :first, :channel, :ts, :content
|
|
14
14
|
alias_method :thread_ts, :ts
|
|
15
15
|
|
|
16
|
+
def update(*text_or_blocks, &dsl_block)
|
|
17
|
+
updated_root = first.update(*text_or_blocks, &dsl_block)
|
|
18
|
+
SentThread.new(updated_root, *sent_messages[1..])
|
|
19
|
+
end
|
|
20
|
+
|
|
16
21
|
def append(*text_or_blocks, &dsl_block)
|
|
17
22
|
extended = first.append(*text_or_blocks, &dsl_block)
|
|
18
23
|
SentThread.new(*sent_messages, *extended.sent_messages[1..])
|
data/lib/slack_line/users.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
module SlackLine
|
|
2
2
|
class Users
|
|
3
3
|
include Memoization
|
|
4
|
+
include DiskCaching
|
|
4
5
|
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
6
|
+
def initialize(client:)
|
|
7
|
+
@client = client
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
memoize def all
|
|
10
|
+
memoize def all
|
|
11
|
+
cached(config: client.configuration, key: "users_all") { all_users.reject(&:deleted).reject(&:is_bot) }
|
|
12
|
+
end
|
|
10
13
|
|
|
11
14
|
def find(display_name:)
|
|
12
15
|
users_by_display_name[display_name.downcase]
|
|
@@ -14,7 +17,9 @@ module SlackLine
|
|
|
14
17
|
|
|
15
18
|
private
|
|
16
19
|
|
|
17
|
-
attr_reader :
|
|
20
|
+
attr_reader :client
|
|
21
|
+
|
|
22
|
+
def slack_client = client.slack_client
|
|
18
23
|
|
|
19
24
|
def fetch_page(cursor: nil)
|
|
20
25
|
params = {limit: 200}
|
data/lib/slack_line/version.rb
CHANGED
data/slack_line.gemspec
CHANGED
|
@@ -6,12 +6,12 @@ Gem::Specification.new do |spec|
|
|
|
6
6
|
spec.authors = ["Eric Mueller"]
|
|
7
7
|
spec.email = ["nevinera@gmail.com"]
|
|
8
8
|
|
|
9
|
-
spec.summary = "
|
|
9
|
+
spec.summary = "A gem to send and extend Slack threads and messages"
|
|
10
10
|
spec.description = <<~DESC
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
and
|
|
11
|
+
Sending messages with the Slack API is not that difficult, but I've had
|
|
12
|
+
to solve the same problems a lot of times at different companies. This
|
|
13
|
+
gem attempts to make those solutions irrelevant by providing a simple
|
|
14
|
+
interface and scripts to send and update messages, and build/extend threads.
|
|
15
15
|
DESC
|
|
16
16
|
spec.homepage = "https://github.com/nevinera/slack_line"
|
|
17
17
|
spec.license = "MIT"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: slack_line
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.2'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Mueller
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -165,14 +165,15 @@ dependencies:
|
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
166
|
version: 3.1.0
|
|
167
167
|
description: |
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
and
|
|
168
|
+
Sending messages with the Slack API is not that difficult, but I've had
|
|
169
|
+
to solve the same problems a lot of times at different companies. This
|
|
170
|
+
gem attempts to make those solutions irrelevant by providing a simple
|
|
171
|
+
interface and scripts to send and update messages, and build/extend threads.
|
|
172
172
|
email:
|
|
173
173
|
- nevinera@gmail.com
|
|
174
174
|
executables:
|
|
175
175
|
- slack_line_message
|
|
176
|
+
- slack_line_stateful_thread
|
|
176
177
|
- slack_line_thread
|
|
177
178
|
extensions: []
|
|
178
179
|
extra_rdoc_files: []
|
|
@@ -186,16 +187,20 @@ files:
|
|
|
186
187
|
- ".rspec"
|
|
187
188
|
- ".rubocop.yml"
|
|
188
189
|
- ".standard.yml"
|
|
190
|
+
- CHANGELOG.md
|
|
189
191
|
- Gemfile
|
|
190
192
|
- README.md
|
|
191
193
|
- bin/slack_line_message
|
|
194
|
+
- bin/slack_line_stateful_thread
|
|
192
195
|
- bin/slack_line_thread
|
|
193
196
|
- lib/slack_line.rb
|
|
194
197
|
- lib/slack_line/cli.rb
|
|
195
198
|
- lib/slack_line/cli/slack_line_message.rb
|
|
199
|
+
- lib/slack_line/cli/slack_line_stateful_thread.rb
|
|
196
200
|
- lib/slack_line/cli/slack_line_thread.rb
|
|
197
201
|
- lib/slack_line/client.rb
|
|
198
202
|
- lib/slack_line/configuration.rb
|
|
203
|
+
- lib/slack_line/disk_caching.rb
|
|
199
204
|
- lib/slack_line/groups.rb
|
|
200
205
|
- lib/slack_line/memoization.rb
|
|
201
206
|
- lib/slack_line/message.rb
|
|
@@ -234,5 +239,5 @@ requirements: []
|
|
|
234
239
|
rubygems_version: 3.5.22
|
|
235
240
|
signing_key:
|
|
236
241
|
specification_version: 4
|
|
237
|
-
summary:
|
|
242
|
+
summary: A gem to send and extend Slack threads and messages
|
|
238
243
|
test_files: []
|