slk 0.13.0 → 0.15.0
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/CHANGELOG.md +28 -0
- data/README.md +21 -0
- data/lib/slk/api/users.rb +19 -5
- data/lib/slk/cli.rb +1 -0
- data/lib/slk/commands/activity.rb +6 -0
- data/lib/slk/commands/catchup.rb +29 -11
- data/lib/slk/commands/debug.rb +15 -4
- data/lib/slk/commands/help.rb +1 -0
- data/lib/slk/commands/huddles.rb +94 -0
- data/lib/slk/commands/messages.rb +20 -3
- data/lib/slk/commands/thread.rb +1 -2
- data/lib/slk/commands/unread.rb +34 -19
- data/lib/slk/commands/who.rb +22 -2
- data/lib/slk/formatters/huddle_formatter.rb +54 -0
- data/lib/slk/formatters/json_message_formatter.rb +1 -1
- data/lib/slk/formatters/mention_replacer.rb +9 -3
- data/lib/slk/formatters/profile_formatter.rb +9 -0
- data/lib/slk/formatters/profile_rows.rb +14 -1
- data/lib/slk/formatters/search_formatter.rb +4 -0
- data/lib/slk/models/huddle.rb +17 -0
- data/lib/slk/models/profile.rb +16 -1
- data/lib/slk/models/search_result.rb +3 -0
- data/lib/slk/services/activity_enricher.rb +33 -13
- data/lib/slk/services/api_client.rb +58 -2
- data/lib/slk/services/cache_store.rb +13 -2
- data/lib/slk/services/channel_lookup.rb +78 -0
- data/lib/slk/services/group_dm_label.rb +83 -0
- data/lib/slk/services/huddle_channel_label.rb +62 -0
- data/lib/slk/services/huddle_fetch.rb +90 -0
- data/lib/slk/services/huddle_scanner.rb +101 -0
- data/lib/slk/services/huddle_state.rb +41 -0
- data/lib/slk/services/profile_builder.rb +1 -0
- data/lib/slk/services/profile_resolver.rb +1 -1
- data/lib/slk/services/sent_channel_label.rb +4 -0
- data/lib/slk/services/target_resolver.rb +2 -13
- data/lib/slk/services/unread_marker.rb +8 -5
- data/lib/slk/services/workspace_fanout.rb +28 -0
- data/lib/slk/support/group_dm_name.rb +39 -0
- data/lib/slk/support/user_resolver.rb +21 -5
- data/lib/slk/version.rb +1 -1
- data/lib/slk.rb +13 -0
- metadata +12 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slk
|
|
4
|
+
module Services
|
|
5
|
+
# Loads active huddles for one or more workspaces.
|
|
6
|
+
#
|
|
7
|
+
# A roster scan is a large download. Workspaces do not share a rate-limit
|
|
8
|
+
# bucket, and one HTTP connection can only read one of them at a time, so
|
|
9
|
+
# more than one workspace is fetched concurrently, each on its own client.
|
|
10
|
+
class HuddleFetch
|
|
11
|
+
def initialize(runner:, output:)
|
|
12
|
+
@runner = runner
|
|
13
|
+
@output = output
|
|
14
|
+
@counts = {}
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(workspaces)
|
|
19
|
+
@parallel = workspaces.size > 1 && @runner.api_client.respond_to?(:isolated)
|
|
20
|
+
fetched = WorkspaceFanout.call(workspaces, parallel: @parallel) do |workspace|
|
|
21
|
+
[workspace, fetch(workspace)]
|
|
22
|
+
end
|
|
23
|
+
fetched.map { |workspace, huddles| report(workspace, huddles) }
|
|
24
|
+
ensure
|
|
25
|
+
@output.clear_progress
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def fetch(workspace)
|
|
31
|
+
client = scan_client
|
|
32
|
+
users = Api::Users.new(client, workspace, on_debug: debug_logger)
|
|
33
|
+
HuddleScanner.group(collect(workspace, users))
|
|
34
|
+
ensure
|
|
35
|
+
close_client(client)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def collect(workspace, users)
|
|
39
|
+
kept = []
|
|
40
|
+
users.list_each do |page, total|
|
|
41
|
+
kept.concat(HuddleScanner.keep(page))
|
|
42
|
+
note(workspace.name, total)
|
|
43
|
+
end
|
|
44
|
+
kept
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def report(workspace, huddles)
|
|
48
|
+
labeled = huddles.map { |huddle| huddle.with(channel: labeler.label(workspace, huddle.channel_id)) }
|
|
49
|
+
Models::WorkspaceHuddles.new(workspace: workspace.name, huddles: labeled)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def scan_client
|
|
53
|
+
return @runner.api_client.isolated if @parallel
|
|
54
|
+
|
|
55
|
+
@runner.api_client
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The shared client belongs to the rest of the command. An isolated one
|
|
59
|
+
# exists only for this scan and should not keep its socket open.
|
|
60
|
+
def close_client(client)
|
|
61
|
+
return if client.nil? || client.equal?(@runner.api_client)
|
|
62
|
+
|
|
63
|
+
client.close if client.respond_to?(:close)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def note(name, total)
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
@counts[name] = total
|
|
69
|
+
@output.progress(progress_line)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def progress_line
|
|
74
|
+
return "#{@counts.keys.first}: #{@counts.values.first} members" if @counts.one?
|
|
75
|
+
|
|
76
|
+
@counts.map { |name, total| "#{name}: #{total}" }.join(' · ')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def labeler
|
|
80
|
+
@labeler ||= HuddleChannelLabel.new(
|
|
81
|
+
runner: @runner, cache_store: @runner.cache_store, on_debug: debug_logger
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def debug_logger
|
|
86
|
+
@debug_logger ||= ->(message) { @output.debug(message) }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slk
|
|
4
|
+
module Services
|
|
5
|
+
# Groups a users.list roster into active huddles.
|
|
6
|
+
#
|
|
7
|
+
# Slack has no list-huddles method. A profile's huddle_state is
|
|
8
|
+
# `in_a_huddle` while they are in one, and huddle_state_call_id is the
|
|
9
|
+
# shared key. A missing call id is not evidence two people share a call,
|
|
10
|
+
# so those profiles stay separate. Disagreeing channel ids are dropped
|
|
11
|
+
# rather than labeling the call with one of them.
|
|
12
|
+
class HuddleScanner
|
|
13
|
+
IN_HUDDLE = 'in_a_huddle'
|
|
14
|
+
|
|
15
|
+
def self.group(members)
|
|
16
|
+
new(members).group
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# People who are not in a huddle are useless to the scan and expensive
|
|
20
|
+
# to keep. Drop them as each page arrives.
|
|
21
|
+
def self.keep(members)
|
|
22
|
+
members.select { |member| in_huddle?(member) && member['id'] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.in_huddle?(member)
|
|
26
|
+
member.dig('profile', 'huddle_state') == IN_HUDDLE
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(members)
|
|
30
|
+
@members = members
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def group
|
|
34
|
+
huddles.sort_by { |huddle| sort_key(huddle) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def huddles
|
|
40
|
+
active.group_by { |member| group_key(member) }.map { |key, people| build(key, people) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def active
|
|
44
|
+
self.class.keep(@members)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def group_key(member)
|
|
48
|
+
call_id(member) || [:solo, member['id']]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build(key, people)
|
|
52
|
+
Models::Huddle.new(
|
|
53
|
+
call_id: call_id_for(key),
|
|
54
|
+
channel_id: shared_channel_id(people),
|
|
55
|
+
channel: nil,
|
|
56
|
+
participants: sorted_participants(people)
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def call_id_for(key)
|
|
61
|
+
key.is_a?(Array) ? nil : key
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def shared_channel_id(people)
|
|
65
|
+
ids = people.filter_map { |member| channel_id(member) }.uniq
|
|
66
|
+
ids.first if ids.one?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def sorted_participants(people)
|
|
70
|
+
people.map { |member| participant(member) }.sort_by { |person| [person.name.downcase, person.id] }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def participant(member)
|
|
74
|
+
Models::HuddleParticipant.new(id: member['id'], name: participant_name(member))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def participant_name(member)
|
|
78
|
+
profile = member['profile'] || {}
|
|
79
|
+
names = [profile['display_name'], profile['real_name'], member['real_name'], member['name'], member['id']]
|
|
80
|
+
names.find { |value| !value.to_s.empty? }.to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def call_id(member)
|
|
84
|
+
present(member.dig('profile', 'huddle_state_call_id'))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def channel_id(member)
|
|
88
|
+
present(member.dig('profile', 'huddle_state_channel_id'))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def present(value)
|
|
92
|
+
text = value.to_s
|
|
93
|
+
text.empty? ? nil : text
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def sort_key(huddle)
|
|
97
|
+
[-huddle.participants.size, huddle.participants.first.name.downcase, huddle.call_id.to_s]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slk
|
|
4
|
+
module Services
|
|
5
|
+
# Huddle fields from a profile hash, or from a live users.info.
|
|
6
|
+
#
|
|
7
|
+
# users.info and users.profile.get are cached for an hour. Huddle state
|
|
8
|
+
# changes in minutes, so a card must not trust the cached copy.
|
|
9
|
+
module HuddleState
|
|
10
|
+
EMPTY = { huddle_state: nil, huddle_channel_id: nil, huddle_call_id: nil }.freeze
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def from(profile_data)
|
|
15
|
+
data = profile_data || {}
|
|
16
|
+
{
|
|
17
|
+
huddle_state: blank_to_nil(data['huddle_state']),
|
|
18
|
+
huddle_channel_id: blank_to_nil(data['huddle_state_channel_id']),
|
|
19
|
+
huddle_call_id: blank_to_nil(data['huddle_state_call_id']),
|
|
20
|
+
huddle_channel: nil
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def apply(profile, users_api, user_id, on_debug: nil)
|
|
25
|
+
Models::Profile.new(**profile.to_h, **read(users_api, user_id, on_debug: on_debug))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def read(users_api, user_id, on_debug: nil)
|
|
29
|
+
from(users_api.info(user_id).dig('user', 'profile'))
|
|
30
|
+
rescue ApiError => e
|
|
31
|
+
on_debug&.call("huddle state for #{user_id} failed: #{e.message}")
|
|
32
|
+
EMPTY.merge(huddle_channel: nil)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def blank_to_nil(value)
|
|
36
|
+
text = value.to_s
|
|
37
|
+
text.empty? ? nil : text
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -93,7 +93,7 @@ module Slk
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def attach_extras(profile, user_id)
|
|
96
|
-
profile = attach_home_team_name(profile)
|
|
96
|
+
profile = HuddleState.apply(attach_home_team_name(profile), @users_api, user_id, on_debug: @on_debug)
|
|
97
97
|
presence = fetch_presence(user_id)
|
|
98
98
|
presence ? Models::Profile.new(**profile.to_h, presence: presence) : profile
|
|
99
99
|
end
|
|
@@ -5,6 +5,7 @@ module Slk
|
|
|
5
5
|
# Resolves sent-conversation destinations while retaining raw channel IDs
|
|
6
6
|
# and names in JSON. Group DMs need member lookup: their Slack name is an
|
|
7
7
|
# opaque mpdm-* slug, not a user ID that MentionReplacer can resolve.
|
|
8
|
+
# Named group DMs ("named chats") skip that and show as #name.
|
|
8
9
|
class SentChannelLabel
|
|
9
10
|
USER_ID_PATTERN = /\A[UW][A-Z0-9]+\z/
|
|
10
11
|
|
|
@@ -28,6 +29,9 @@ module Slk
|
|
|
28
29
|
private
|
|
29
30
|
|
|
30
31
|
def group_label(workspace, channel_id, name, self_user_id, self_username)
|
|
32
|
+
named = Support::GroupDmName.label(name)
|
|
33
|
+
return named if named
|
|
34
|
+
|
|
31
35
|
names = group_member_names(workspace, channel_id, self_user_id)
|
|
32
36
|
return "@#{names.join(', ')}" if names.any?
|
|
33
37
|
|
|
@@ -66,19 +66,8 @@ module Slk
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def resolve_channel(workspace, name)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
fetch_and_cache_channel(workspace, name)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def fetch_and_cache_channel(workspace, name)
|
|
76
|
-
channels = @runner.conversations_api(workspace.name).list['channels'] || []
|
|
77
|
-
channel = channels.find { |c| c['name'] == name }
|
|
78
|
-
raise ConfigError, "Channel not found: ##{name}" unless channel
|
|
79
|
-
|
|
80
|
-
@cache.set_channel(workspace.name, name, channel['id'])
|
|
81
|
-
channel['id']
|
|
69
|
+
ChannelLookup.new(runner: @runner, cache_store: @cache).find_id(workspace, name) ||
|
|
70
|
+
raise(ConfigError, "Channel not found: ##{name}")
|
|
82
71
|
end
|
|
83
72
|
|
|
84
73
|
def resolve_dm(workspace, username)
|
|
@@ -14,13 +14,15 @@ module Slk
|
|
|
14
14
|
|
|
15
15
|
# Mark all unreads in a workspace, return counts
|
|
16
16
|
# @param options [Hash] :muted - include muted channels
|
|
17
|
-
# @return [Hash] { dms: count, channels: count, threads: count }
|
|
17
|
+
# @return [Hash] { dms: count, group_dms: count, channels: count, threads: count }
|
|
18
18
|
def mark_all(options: {})
|
|
19
19
|
counts = @client.counts
|
|
20
|
+
muted_ids = options[:muted] ? [] : @users.muted_channels
|
|
20
21
|
|
|
21
22
|
{
|
|
22
23
|
dms: mark_dms(counts['ims'] || []),
|
|
23
|
-
|
|
24
|
+
group_dms: mark_channels(counts['mpims'] || [], muted_ids),
|
|
25
|
+
channels: mark_channels(counts['channels'] || [], muted_ids),
|
|
24
26
|
threads: mark_threads
|
|
25
27
|
}
|
|
26
28
|
end
|
|
@@ -44,13 +46,14 @@ module Slk
|
|
|
44
46
|
count
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
# Channels and group DMs (client.counts `mpims`): a muted group DM is
|
|
50
|
+
# skipped the same way a muted channel is.
|
|
51
|
+
def mark_channels(channels, muted_ids)
|
|
49
52
|
count = 0
|
|
50
53
|
|
|
51
54
|
channels.each do |channel|
|
|
52
55
|
next unless channel['has_unreads']
|
|
53
|
-
next if
|
|
56
|
+
next if muted_ids.include?(channel['id'])
|
|
54
57
|
|
|
55
58
|
count += 1 if mark_conversation(channel['id'])
|
|
56
59
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slk
|
|
4
|
+
module Services
|
|
5
|
+
# Runs one block per item. With parallel, the blocks overlap and the
|
|
6
|
+
# results come back in the original order. A failure waits for the other
|
|
7
|
+
# threads to finish, then raises, so an in-flight request is not abandoned
|
|
8
|
+
# mid-read.
|
|
9
|
+
module WorkspaceFanout
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def call(items, parallel: false, &block)
|
|
13
|
+
return items.map(&block) unless parallel && items.size > 1
|
|
14
|
+
|
|
15
|
+
threads = items.map { |item| start(item, &block) }
|
|
16
|
+
threads.map(&:value)
|
|
17
|
+
ensure
|
|
18
|
+
Array(threads).each(&:join)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The caller raises the thread's exception. Leaving the default on
|
|
22
|
+
# prints a second traceback for a failure the command already reports.
|
|
23
|
+
def start(item, &block)
|
|
24
|
+
Thread.new { block.call(item) }.tap { |thread| thread.report_on_exception = false }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slk
|
|
4
|
+
module Support
|
|
5
|
+
# Group DMs can be given a name in Slack ("named chats"). They are still
|
|
6
|
+
# mpims, but `name` holds what the user typed ("ifams review") rather
|
|
7
|
+
# than the generated mpdm-alice--bob-1 slug. conversations.info flags them
|
|
8
|
+
# with properties.has_custom_mpdm_name; search.messages sends no
|
|
9
|
+
# properties, so a name that isn't an mpdm-* slug counts as custom too.
|
|
10
|
+
module GroupDmName
|
|
11
|
+
SLUG = /\Ampdm-/
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# @param channel [Hash] conversation payload from Slack
|
|
16
|
+
# @return [String, nil] the custom name, or nil for anything else
|
|
17
|
+
def custom(channel)
|
|
18
|
+
return nil unless channel.is_a?(Hash) && channel['is_mpim']
|
|
19
|
+
|
|
20
|
+
name = channel['name'].to_s
|
|
21
|
+
return nil if name.empty?
|
|
22
|
+
return name if channel.dig('properties', 'has_custom_mpdm_name')
|
|
23
|
+
|
|
24
|
+
custom_name?(name) ? name : nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param name [String, nil] a group DM's Slack name
|
|
28
|
+
# @return [Boolean] true when the name was set by a person
|
|
29
|
+
def custom_name?(name)
|
|
30
|
+
!name.to_s.empty? && !name.to_s.match?(SLUG)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [String, nil] "#name" for a named group DM, else nil
|
|
34
|
+
def label(name)
|
|
35
|
+
"##{name}" if custom_name?(name)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -68,6 +68,8 @@ module Slk
|
|
|
68
68
|
public
|
|
69
69
|
|
|
70
70
|
# Resolves a channel ID to a formatted label (@username or #channel)
|
|
71
|
+
# Group DMs get "#name" when named, else "@other, members", never the
|
|
72
|
+
# mpdm-* slug that other lookups may have put in the channel cache.
|
|
71
73
|
# @param workspace [Models::Workspace] The workspace to look up in
|
|
72
74
|
# @param channel_id [String] The channel ID
|
|
73
75
|
# @return [String] Formatted label like "@username" or "#channel"
|
|
@@ -97,8 +99,11 @@ module Slk
|
|
|
97
99
|
end
|
|
98
100
|
|
|
99
101
|
def resolve_channel_label(workspace, channel_id)
|
|
102
|
+
known = group_dm_label.known(workspace, channel_id)
|
|
103
|
+
return known if known
|
|
104
|
+
|
|
100
105
|
cached_name = cache_store.get_channel_name(workspace.name, channel_id)
|
|
101
|
-
return "##{cached_name}" if cached_name
|
|
106
|
+
return "##{cached_name}" if cached_name && !cached_name.match?(Support::GroupDmName::SLUG)
|
|
102
107
|
|
|
103
108
|
fetch_channel_label(workspace, channel_id)
|
|
104
109
|
end
|
|
@@ -108,16 +113,27 @@ module Slk
|
|
|
108
113
|
response = conversations.info(channel: channel_id)
|
|
109
114
|
return "##{channel_id}" unless response['ok'] && response['channel']
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
return
|
|
116
|
+
channel = response['channel']
|
|
117
|
+
return group_dm_label.label_from(workspace, channel_id, channel) if channel['is_mpim']
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
"##{name}"
|
|
119
|
+
named_channel_label(workspace, channel_id, channel['name'])
|
|
116
120
|
rescue ApiError => e
|
|
117
121
|
debug("Channel info lookup failed for #{channel_id}: #{e.message}")
|
|
118
122
|
"##{channel_id}"
|
|
119
123
|
end
|
|
120
124
|
|
|
125
|
+
def named_channel_label(workspace, channel_id, name)
|
|
126
|
+
return "##{channel_id}" unless name
|
|
127
|
+
|
|
128
|
+
cache_store.set_channel(workspace.name, name, channel_id)
|
|
129
|
+
"##{name}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# One per command run, so a group DM is looked up once for all headers.
|
|
133
|
+
def group_dm_label
|
|
134
|
+
@group_dm_label ||= Services::GroupDmLabel.new(runner: runner, on_debug: ->(msg) { debug(msg) })
|
|
135
|
+
end
|
|
136
|
+
|
|
121
137
|
def name_from_user_profile(msg)
|
|
122
138
|
return nil unless msg['user_profile']
|
|
123
139
|
|
data/lib/slk/version.rb
CHANGED
data/lib/slk.rb
CHANGED
|
@@ -79,6 +79,9 @@ module Slk
|
|
|
79
79
|
autoload :Tenure, 'slk/models/tenure'
|
|
80
80
|
autoload :SearchResult, 'slk/models/search_result'
|
|
81
81
|
autoload :SavedItem, 'slk/models/saved_item'
|
|
82
|
+
autoload :Huddle, 'slk/models/huddle'
|
|
83
|
+
autoload :HuddleParticipant, 'slk/models/huddle'
|
|
84
|
+
autoload :WorkspaceHuddles, 'slk/models/huddle'
|
|
82
85
|
autoload :Profile, 'slk/models/profile'
|
|
83
86
|
autoload :ProfileField, 'slk/models/profile_field'
|
|
84
87
|
end
|
|
@@ -117,6 +120,13 @@ module Slk
|
|
|
117
120
|
autoload :SentConversations, 'slk/services/sent_conversations'
|
|
118
121
|
autoload :SentChanges, 'slk/services/sent_changes'
|
|
119
122
|
autoload :SentChannelLabel, 'slk/services/sent_channel_label'
|
|
123
|
+
autoload :HuddleScanner, 'slk/services/huddle_scanner'
|
|
124
|
+
autoload :HuddleFetch, 'slk/services/huddle_fetch'
|
|
125
|
+
autoload :HuddleState, 'slk/services/huddle_state'
|
|
126
|
+
autoload :WorkspaceFanout, 'slk/services/workspace_fanout'
|
|
127
|
+
autoload :HuddleChannelLabel, 'slk/services/huddle_channel_label'
|
|
128
|
+
autoload :GroupDmLabel, 'slk/services/group_dm_label'
|
|
129
|
+
autoload :ChannelLookup, 'slk/services/channel_lookup'
|
|
120
130
|
autoload :ThreadSubscription, 'slk/services/thread_subscription'
|
|
121
131
|
end
|
|
122
132
|
|
|
@@ -141,6 +151,7 @@ module Slk
|
|
|
141
151
|
autoload :ProfileFormatter, 'slk/formatters/profile_formatter'
|
|
142
152
|
autoload :ProfileFieldRenderer, 'slk/formatters/profile_field_renderer'
|
|
143
153
|
autoload :ProfileRows, 'slk/formatters/profile_rows'
|
|
154
|
+
autoload :HuddleFormatter, 'slk/formatters/huddle_formatter'
|
|
144
155
|
autoload :DeactivationFormatter, 'slk/formatters/deactivation_formatter'
|
|
145
156
|
autoload :DeactivationCsv, 'slk/formatters/deactivation_csv'
|
|
146
157
|
autoload :CsvWriter, 'slk/formatters/csv_writer'
|
|
@@ -168,6 +179,7 @@ module Slk
|
|
|
168
179
|
autoload :Later, 'slk/commands/later'
|
|
169
180
|
autoload :Debug, 'slk/commands/debug'
|
|
170
181
|
autoload :Who, 'slk/commands/who'
|
|
182
|
+
autoload :Huddles, 'slk/commands/huddles'
|
|
171
183
|
autoload :Org, 'slk/commands/org'
|
|
172
184
|
autoload :Deactivations, 'slk/commands/deactivations'
|
|
173
185
|
end
|
|
@@ -199,6 +211,7 @@ module Slk
|
|
|
199
211
|
autoload :HelpFormatter, 'slk/support/help_formatter'
|
|
200
212
|
autoload :ErrorLogger, 'slk/support/error_logger'
|
|
201
213
|
autoload :UserResolver, 'slk/support/user_resolver'
|
|
214
|
+
autoload :GroupDmName, 'slk/support/group_dm_name'
|
|
202
215
|
autoload :TextWrapper, 'slk/support/text_wrapper'
|
|
203
216
|
autoload :InteractivePrompt, 'slk/support/interactive_prompt'
|
|
204
217
|
autoload :DateParser, 'slk/support/date_parser'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: slk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Boehs
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- lib/slk/commands/dnd.rb
|
|
51
51
|
- lib/slk/commands/emoji.rb
|
|
52
52
|
- lib/slk/commands/help.rb
|
|
53
|
+
- lib/slk/commands/huddles.rb
|
|
53
54
|
- lib/slk/commands/later.rb
|
|
54
55
|
- lib/slk/commands/messages.rb
|
|
55
56
|
- lib/slk/commands/org.rb
|
|
@@ -71,6 +72,7 @@ files:
|
|
|
71
72
|
- lib/slk/formatters/deactivation_formatter.rb
|
|
72
73
|
- lib/slk/formatters/duration_formatter.rb
|
|
73
74
|
- lib/slk/formatters/emoji_replacer.rb
|
|
75
|
+
- lib/slk/formatters/huddle_formatter.rb
|
|
74
76
|
- lib/slk/formatters/json_message_formatter.rb
|
|
75
77
|
- lib/slk/formatters/json_status_formatter.rb
|
|
76
78
|
- lib/slk/formatters/markdown_output.rb
|
|
@@ -89,6 +91,7 @@ files:
|
|
|
89
91
|
- lib/slk/models/deactivation.rb
|
|
90
92
|
- lib/slk/models/dnd_state.rb
|
|
91
93
|
- lib/slk/models/duration.rb
|
|
94
|
+
- lib/slk/models/huddle.rb
|
|
92
95
|
- lib/slk/models/message.rb
|
|
93
96
|
- lib/slk/models/preset.rb
|
|
94
97
|
- lib/slk/models/profile.rb
|
|
@@ -106,6 +109,7 @@ files:
|
|
|
106
109
|
- lib/slk/services/activity_enricher.rb
|
|
107
110
|
- lib/slk/services/api_client.rb
|
|
108
111
|
- lib/slk/services/cache_store.rb
|
|
112
|
+
- lib/slk/services/channel_lookup.rb
|
|
109
113
|
- lib/slk/services/configuration.rb
|
|
110
114
|
- lib/slk/services/deactivation_scanner.rb
|
|
111
115
|
- lib/slk/services/emoji_downloader.rb
|
|
@@ -113,6 +117,11 @@ files:
|
|
|
113
117
|
- lib/slk/services/encryption.rb
|
|
114
118
|
- lib/slk/services/file_downloader.rb
|
|
115
119
|
- lib/slk/services/gemoji_sync.rb
|
|
120
|
+
- lib/slk/services/group_dm_label.rb
|
|
121
|
+
- lib/slk/services/huddle_channel_label.rb
|
|
122
|
+
- lib/slk/services/huddle_fetch.rb
|
|
123
|
+
- lib/slk/services/huddle_scanner.rb
|
|
124
|
+
- lib/slk/services/huddle_state.rb
|
|
116
125
|
- lib/slk/services/message_resolver.rb
|
|
117
126
|
- lib/slk/services/meta_cache.rb
|
|
118
127
|
- lib/slk/services/preset_store.rb
|
|
@@ -136,11 +145,13 @@ files:
|
|
|
136
145
|
- lib/slk/services/user_matcher.rb
|
|
137
146
|
- lib/slk/services/user_picker.rb
|
|
138
147
|
- lib/slk/services/who_target_resolver.rb
|
|
148
|
+
- lib/slk/services/workspace_fanout.rb
|
|
139
149
|
- lib/slk/support/attachment_fetching.rb
|
|
140
150
|
- lib/slk/support/check_in_time.rb
|
|
141
151
|
- lib/slk/support/date_parser.rb
|
|
142
152
|
- lib/slk/support/decimal_timestamp.rb
|
|
143
153
|
- lib/slk/support/error_logger.rb
|
|
154
|
+
- lib/slk/support/group_dm_name.rb
|
|
144
155
|
- lib/slk/support/help_formatter.rb
|
|
145
156
|
- lib/slk/support/image_preview.rb
|
|
146
157
|
- lib/slk/support/inline_images.rb
|