slack_line 1.1 → 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/Gemfile +2 -0
- data/README.md +135 -1
- 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 +6 -2
- 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
- metadata +6 -2
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
|
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,6 +136,126 @@ 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
261
|
In order to post/update messages, the app behind your `SLACK_LINE_TOKEN` can use
|
|
@@ -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
|
|
|
@@ -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
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
|
|
@@ -173,6 +173,7 @@ 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: []
|
|
@@ -190,13 +191,16 @@ files:
|
|
|
190
191
|
- Gemfile
|
|
191
192
|
- README.md
|
|
192
193
|
- bin/slack_line_message
|
|
194
|
+
- bin/slack_line_stateful_thread
|
|
193
195
|
- bin/slack_line_thread
|
|
194
196
|
- lib/slack_line.rb
|
|
195
197
|
- lib/slack_line/cli.rb
|
|
196
198
|
- lib/slack_line/cli/slack_line_message.rb
|
|
199
|
+
- lib/slack_line/cli/slack_line_stateful_thread.rb
|
|
197
200
|
- lib/slack_line/cli/slack_line_thread.rb
|
|
198
201
|
- lib/slack_line/client.rb
|
|
199
202
|
- lib/slack_line/configuration.rb
|
|
203
|
+
- lib/slack_line/disk_caching.rb
|
|
200
204
|
- lib/slack_line/groups.rb
|
|
201
205
|
- lib/slack_line/memoization.rb
|
|
202
206
|
- lib/slack_line/message.rb
|