slk 0.7.0 → 0.9.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.
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slk
4
+ module Services
5
+ # Slack has no "who left" endpoint. What it does have is users.list, which
6
+ # returns deactivated accounts with `deleted: true` and an `updated` epoch
7
+ # for the last change to the account — and for a deactivated account that
8
+ # change is nearly always the deactivation itself.
9
+ #
10
+ # The full roster is one API call per 1000 members, so the derived result
11
+ # (deactivated members plus headcounts, not the whole roster) is cached in
12
+ # the workspace meta cache with a TTL.
13
+ class DeactivationScanner
14
+ CACHE_KEY = 'deactivations_v1'
15
+ DEFAULT_TTL = 21_600 # 6 hours
16
+ REQUIRED_KEYS = %w[fetched_at member_count human_count active_count records].freeze
17
+
18
+ Report = Data.define(:records, :member_count, :human_count, :active_count, :fetched_at) do
19
+ def deactivated_count = records.size
20
+
21
+ def bots = records.count(&:bot)
22
+ end
23
+
24
+ def initialize(users_api:, workspace_name:, cache_store: nil, ttl: DEFAULT_TTL, on_debug: nil)
25
+ @users_api = users_api
26
+ @workspace_name = workspace_name
27
+ @cache = cache_store
28
+ @ttl = ttl
29
+ @on_debug = on_debug
30
+ end
31
+
32
+ # @return [Report] every deactivated account, newest deactivation first
33
+ def scan(refresh: false)
34
+ build_report(cached(refresh: refresh) || store(collect))
35
+ end
36
+
37
+ private
38
+
39
+ def cached(refresh:)
40
+ return nil if refresh
41
+
42
+ data = MetaCache.read(@cache, @workspace_name, CACHE_KEY, ttl: @ttl)
43
+ return data if usable?(data)
44
+
45
+ @on_debug&.call('deactivations cache is unusable; re-fetching the roster') if data
46
+ nil
47
+ end
48
+
49
+ # A truncated or older-shaped entry would otherwise be read field by
50
+ # field into a report of zero active members and zero departures — a
51
+ # confident, wrong answer. Missing anything required means refetch.
52
+ def usable?(data)
53
+ data.is_a?(Hash) && data['records'].is_a?(Array) && REQUIRED_KEYS.all? { |key| data[key] }
54
+ end
55
+
56
+ def store(data)
57
+ MetaCache.write(@cache, @workspace_name, CACHE_KEY, data)
58
+ data
59
+ end
60
+
61
+ def collect
62
+ members = fetch_members
63
+ deleted = members.select { |m| m['deleted'] }
64
+
65
+ counts(members).merge(
66
+ 'fetched_at' => Time.now.to_i,
67
+ 'records' => deleted.map { |m| Models::Deactivation.from_api(m).to_cache }
68
+ )
69
+ end
70
+
71
+ def counts(members)
72
+ humans = members.reject { |m| Models::Deactivation.bot?(m) }
73
+
74
+ {
75
+ 'member_count' => members.size,
76
+ 'human_count' => humans.size,
77
+ 'active_count' => humans.count { |m| !m['deleted'] }
78
+ }
79
+ end
80
+
81
+ def fetch_members
82
+ @users_api.list_all do |total|
83
+ @on_debug&.call("users.list: #{total} members fetched")
84
+ end
85
+ end
86
+
87
+ def build_report(data)
88
+ Report.new(
89
+ records: sorted_records(data['records'] || []),
90
+ member_count: data['member_count'].to_i,
91
+ human_count: data['human_count'].to_i,
92
+ active_count: data['active_count'].to_i,
93
+ fetched_at: data['fetched_at']&.to_i
94
+ )
95
+ end
96
+
97
+ # Newest first; accounts with no usable timestamp sort to the bottom
98
+ # rather than pretending to be from 1970.
99
+ def sorted_records(raw)
100
+ raw.map { |hash| Models::Deactivation.from_cache(hash) }
101
+ .sort_by { |record| -(record.deactivated_at || 0) }
102
+ end
103
+ end
104
+ end
105
+ end
data/lib/slk/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Slk
4
- VERSION = '0.7.0'
4
+ VERSION = '0.9.0'
5
5
  end
data/lib/slk.rb CHANGED
@@ -67,11 +67,14 @@ module Slk
67
67
  autoload :Workspace, 'slk/models/workspace'
68
68
  autoload :Status, 'slk/models/status'
69
69
  autoload :ScheduledStatus, 'slk/models/scheduled_status'
70
+ autoload :DndState, 'slk/models/dnd_state'
71
+ autoload :StatusSnapshot, 'slk/models/status_snapshot'
70
72
  autoload :Message, 'slk/models/message'
71
73
  autoload :Reaction, 'slk/models/reaction'
72
74
  autoload :User, 'slk/models/user'
73
75
  autoload :Channel, 'slk/models/channel'
74
76
  autoload :Preset, 'slk/models/preset'
77
+ autoload :Deactivation, 'slk/models/deactivation'
75
78
  autoload :SearchResult, 'slk/models/search_result'
76
79
  autoload :SavedItem, 'slk/models/saved_item'
77
80
  autoload :Profile, 'slk/models/profile'
@@ -105,6 +108,7 @@ module Slk
105
108
  autoload :ProfileBuilder, 'slk/services/profile_builder'
106
109
  autoload :ProfileResolver, 'slk/services/profile_resolver'
107
110
  autoload :MetaCache, 'slk/services/meta_cache'
111
+ autoload :DeactivationScanner, 'slk/services/deactivation_scanner'
108
112
  end
109
113
 
110
114
  # Output formatters for messages, durations, and emoji
@@ -117,6 +121,7 @@ module Slk
117
121
  autoload :MessageFormatter, 'slk/formatters/message_formatter'
118
122
  autoload :ReactionFormatter, 'slk/formatters/reaction_formatter'
119
123
  autoload :JsonMessageFormatter, 'slk/formatters/json_message_formatter'
124
+ autoload :JsonStatusFormatter, 'slk/formatters/json_status_formatter'
120
125
  autoload :ActivityFormatter, 'slk/formatters/activity_formatter'
121
126
  autoload :AttachmentFormatter, 'slk/formatters/attachment_formatter'
122
127
  autoload :BlockFormatter, 'slk/formatters/block_formatter'
@@ -126,6 +131,7 @@ module Slk
126
131
  autoload :ProfileFormatter, 'slk/formatters/profile_formatter'
127
132
  autoload :ProfileFieldRenderer, 'slk/formatters/profile_field_renderer'
128
133
  autoload :ProfileRows, 'slk/formatters/profile_rows'
134
+ autoload :DeactivationFormatter, 'slk/formatters/deactivation_formatter'
129
135
  end
130
136
 
131
137
  # CLI commands implementing user-facing functionality
@@ -150,6 +156,7 @@ module Slk
150
156
  autoload :Debug, 'slk/commands/debug'
151
157
  autoload :Who, 'slk/commands/who'
152
158
  autoload :Org, 'slk/commands/org'
159
+ autoload :Deactivations, 'slk/commands/deactivations'
153
160
  end
154
161
 
155
162
  # Thin wrappers around Slack API endpoints
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.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Boehs
@@ -45,6 +45,7 @@ files:
45
45
  - lib/slk/commands/cache.rb
46
46
  - lib/slk/commands/catchup.rb
47
47
  - lib/slk/commands/config.rb
48
+ - lib/slk/commands/deactivations.rb
48
49
  - lib/slk/commands/debug.rb
49
50
  - lib/slk/commands/dnd.rb
50
51
  - lib/slk/commands/emoji.rb
@@ -64,9 +65,11 @@ files:
64
65
  - lib/slk/formatters/activity_formatter.rb
65
66
  - lib/slk/formatters/attachment_formatter.rb
66
67
  - lib/slk/formatters/block_formatter.rb
68
+ - lib/slk/formatters/deactivation_formatter.rb
67
69
  - lib/slk/formatters/duration_formatter.rb
68
70
  - lib/slk/formatters/emoji_replacer.rb
69
71
  - lib/slk/formatters/json_message_formatter.rb
72
+ - lib/slk/formatters/json_status_formatter.rb
70
73
  - lib/slk/formatters/markdown_output.rb
71
74
  - lib/slk/formatters/mention_replacer.rb
72
75
  - lib/slk/formatters/message_formatter.rb
@@ -79,6 +82,8 @@ files:
79
82
  - lib/slk/formatters/search_formatter.rb
80
83
  - lib/slk/formatters/text_processor.rb
81
84
  - lib/slk/models/channel.rb
85
+ - lib/slk/models/deactivation.rb
86
+ - lib/slk/models/dnd_state.rb
82
87
  - lib/slk/models/duration.rb
83
88
  - lib/slk/models/message.rb
84
89
  - lib/slk/models/preset.rb
@@ -89,6 +94,7 @@ files:
89
94
  - lib/slk/models/scheduled_status.rb
90
95
  - lib/slk/models/search_result.rb
91
96
  - lib/slk/models/status.rb
97
+ - lib/slk/models/status_snapshot.rb
92
98
  - lib/slk/models/user.rb
93
99
  - lib/slk/models/workspace.rb
94
100
  - lib/slk/runner.rb
@@ -96,6 +102,7 @@ files:
96
102
  - lib/slk/services/api_client.rb
97
103
  - lib/slk/services/cache_store.rb
98
104
  - lib/slk/services/configuration.rb
105
+ - lib/slk/services/deactivation_scanner.rb
99
106
  - lib/slk/services/emoji_downloader.rb
100
107
  - lib/slk/services/emoji_searcher.rb
101
108
  - lib/slk/services/encryption.rb
@@ -152,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
159
  - !ruby/object:Gem::Version
153
160
  version: '0'
154
161
  requirements: []
155
- rubygems_version: 4.0.13
162
+ rubygems_version: 4.0.16
156
163
  specification_version: 4
157
164
  summary: A command-line interface for Slack
158
165
  test_files: []