slk 0.1.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 +7 -0
- data/CHANGELOG.md +46 -0
- data/LICENSE +21 -0
- data/README.md +190 -0
- data/bin/slk +7 -0
- data/lib/slack_cli/api/activity.rb +28 -0
- data/lib/slack_cli/api/bots.rb +32 -0
- data/lib/slack_cli/api/client.rb +49 -0
- data/lib/slack_cli/api/conversations.rb +52 -0
- data/lib/slack_cli/api/dnd.rb +40 -0
- data/lib/slack_cli/api/emoji.rb +21 -0
- data/lib/slack_cli/api/threads.rb +44 -0
- data/lib/slack_cli/api/usergroups.rb +25 -0
- data/lib/slack_cli/api/users.rb +101 -0
- data/lib/slack_cli/cli.rb +118 -0
- data/lib/slack_cli/commands/activity.rb +292 -0
- data/lib/slack_cli/commands/base.rb +175 -0
- data/lib/slack_cli/commands/cache.rb +116 -0
- data/lib/slack_cli/commands/catchup.rb +484 -0
- data/lib/slack_cli/commands/config.rb +159 -0
- data/lib/slack_cli/commands/dnd.rb +143 -0
- data/lib/slack_cli/commands/emoji.rb +412 -0
- data/lib/slack_cli/commands/help.rb +76 -0
- data/lib/slack_cli/commands/messages.rb +317 -0
- data/lib/slack_cli/commands/presence.rb +107 -0
- data/lib/slack_cli/commands/preset.rb +239 -0
- data/lib/slack_cli/commands/status.rb +194 -0
- data/lib/slack_cli/commands/thread.rb +62 -0
- data/lib/slack_cli/commands/unread.rb +312 -0
- data/lib/slack_cli/commands/workspaces.rb +151 -0
- data/lib/slack_cli/formatters/duration_formatter.rb +28 -0
- data/lib/slack_cli/formatters/emoji_replacer.rb +143 -0
- data/lib/slack_cli/formatters/mention_replacer.rb +154 -0
- data/lib/slack_cli/formatters/message_formatter.rb +429 -0
- data/lib/slack_cli/formatters/output.rb +89 -0
- data/lib/slack_cli/models/channel.rb +52 -0
- data/lib/slack_cli/models/duration.rb +85 -0
- data/lib/slack_cli/models/message.rb +217 -0
- data/lib/slack_cli/models/preset.rb +73 -0
- data/lib/slack_cli/models/reaction.rb +54 -0
- data/lib/slack_cli/models/status.rb +57 -0
- data/lib/slack_cli/models/user.rb +56 -0
- data/lib/slack_cli/models/workspace.rb +52 -0
- data/lib/slack_cli/runner.rb +123 -0
- data/lib/slack_cli/services/api_client.rb +149 -0
- data/lib/slack_cli/services/cache_store.rb +198 -0
- data/lib/slack_cli/services/configuration.rb +74 -0
- data/lib/slack_cli/services/encryption.rb +51 -0
- data/lib/slack_cli/services/preset_store.rb +112 -0
- data/lib/slack_cli/services/reaction_enricher.rb +87 -0
- data/lib/slack_cli/services/token_store.rb +117 -0
- data/lib/slack_cli/support/error_logger.rb +28 -0
- data/lib/slack_cli/support/help_formatter.rb +139 -0
- data/lib/slack_cli/support/inline_images.rb +62 -0
- data/lib/slack_cli/support/slack_url_parser.rb +78 -0
- data/lib/slack_cli/support/user_resolver.rb +114 -0
- data/lib/slack_cli/support/xdg_paths.rb +37 -0
- data/lib/slack_cli/version.rb +5 -0
- data/lib/slack_cli.rb +91 -0
- metadata +103 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: '09f740d5f0c6a9290031f4e94edff1bb118c85c80df51ccc4ef98b1dea6800a2'
|
|
4
|
+
data.tar.gz: 298d8188fd6bf43a07c8672523d9df253eff44dfe9481a0801d01062b161c8fd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dca5ad190d8155a4312bbafc39def037d1dfc9401170a58cc9adc2f22d6bbb4645efc7bcfd383e2be5d29b31c4f794da518f182df62182c47445fe3deec5d625
|
|
7
|
+
data.tar.gz: cba53cbbf73ed98c220bc984150792bd44e832992be70e62e62d425e2f0a6885f1414a1fe027d1d07ad449c1bef374f12aa2cc12814fda4cfe5bde9844df9f46
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-01-14
|
|
9
|
+
|
|
10
|
+
Initial release of the Ruby rewrite. Pure Ruby, no external dependencies.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Commands**
|
|
15
|
+
- `status` - Get or set your Slack status with emoji and duration
|
|
16
|
+
- `presence` - Toggle between active/away presence
|
|
17
|
+
- `dnd` - Manage Do Not Disturb (enable, disable, with duration)
|
|
18
|
+
- `messages` - Read channel or DM messages with reactions and threads
|
|
19
|
+
- `thread` - View message threads directly from URL
|
|
20
|
+
- `unread` - View and clear unread messages across workspaces
|
|
21
|
+
- `catchup` - Quick summary of mentions and DMs
|
|
22
|
+
- `activity` - View recent workspace activity (mentions, reactions, threads)
|
|
23
|
+
- `preset` - Define and apply status presets (status + presence + DND)
|
|
24
|
+
- `workspaces` - Manage multiple Slack workspaces
|
|
25
|
+
- `cache` - Manage user/channel name cache
|
|
26
|
+
- `emoji` - Download and search workspace custom emoji
|
|
27
|
+
- `config` - Interactive setup and configuration
|
|
28
|
+
|
|
29
|
+
- **Features**
|
|
30
|
+
- Multi-workspace support with easy switching (`-w` flag or `--all`)
|
|
31
|
+
- Encrypted token storage using `age` with SSH keys
|
|
32
|
+
- XDG-compliant configuration directories
|
|
33
|
+
- HTTP connection reuse for better performance
|
|
34
|
+
- Inline emoji images in supported terminals (iTerm2, tmux)
|
|
35
|
+
- Reaction timestamps showing when users reacted
|
|
36
|
+
- Block Kit message rendering
|
|
37
|
+
- User and channel mention resolution
|
|
38
|
+
- Verbose mode (`-v`) for API call debugging
|
|
39
|
+
- JSON output mode (`--json`) for scripting
|
|
40
|
+
|
|
41
|
+
- **Developer Experience**
|
|
42
|
+
- 542 tests with 1082 assertions
|
|
43
|
+
- Pure Ruby stdlib - no gem dependencies
|
|
44
|
+
- Ruby 3.2+ with modern features (Data.define, pattern matching)
|
|
45
|
+
|
|
46
|
+
[0.1.0]: https://github.com/ericboehs/slack-cli/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Eric Boehs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# slk - Slack CLI
|
|
2
|
+
|
|
3
|
+
A command-line interface for Slack. Manage your status, presence, DND, read messages, and more from the terminal.
|
|
4
|
+
|
|
5
|
+
**Pure Ruby. No dependencies.**
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
gem install slk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Ruby 3.2+.
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
Run the setup wizard:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
slk config setup
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
You'll need a Slack token. Get one from:
|
|
24
|
+
- **User token (xoxp-)**: https://api.slack.com/apps → OAuth & Permissions
|
|
25
|
+
- **Bot token (xoxb-)**: Create a Slack App with bot scopes
|
|
26
|
+
- **Session token (xoxc-)**: Extract from browser (requires cookie too)
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Status
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
slk status # Show current status
|
|
34
|
+
slk status "Working from home" :house: # Set status with emoji
|
|
35
|
+
slk status "In a meeting" :calendar: 1h # Set status for 1 hour
|
|
36
|
+
slk status clear # Clear status
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Presence
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
slk presence # Show current presence
|
|
43
|
+
slk presence away # Set to away
|
|
44
|
+
slk presence active # Set to active
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Do Not Disturb
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
slk dnd # Show DND status
|
|
51
|
+
slk dnd 1h # Enable DND for 1 hour
|
|
52
|
+
slk dnd on 30m # Enable DND for 30 minutes
|
|
53
|
+
slk dnd off # Disable DND
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Messages
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
slk messages #general # Read channel messages
|
|
60
|
+
slk messages @username # Read DM with user
|
|
61
|
+
slk messages #general -n 50 # Show 50 messages
|
|
62
|
+
slk messages #general --json # Output as JSON
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Activity
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
slk activity # Show recent activity feed
|
|
69
|
+
slk activity -n 50 # Show 50 items
|
|
70
|
+
slk activity -m # Show message previews
|
|
71
|
+
slk activity --reactions # Filter: reactions only
|
|
72
|
+
slk activity --mentions # Filter: mentions only
|
|
73
|
+
slk activity --threads # Filter: thread replies only
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Displays your recent activity feed including:
|
|
77
|
+
- Reactions to your messages
|
|
78
|
+
- Mentions (@user, @channel, @here, etc.)
|
|
79
|
+
- Thread replies
|
|
80
|
+
- Bot messages (reminders, notifications)
|
|
81
|
+
|
|
82
|
+
Use `--show-messages` (or `-m`) to preview the actual message content for each activity.
|
|
83
|
+
|
|
84
|
+
### Unread
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
slk unread # Show unread counts
|
|
88
|
+
slk unread clear # Mark all as read
|
|
89
|
+
slk unread clear #general # Mark channel as read
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Catchup (Interactive Triage)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
slk catchup # Interactively review unread channels
|
|
96
|
+
slk catchup --batch # Non-interactive, mark all as read
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Presets
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
slk preset list # List all presets
|
|
103
|
+
slk preset meeting # Apply preset
|
|
104
|
+
slk preset add # Add new preset (interactive)
|
|
105
|
+
slk meeting # Shortcut: use preset name as command
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Built-in presets: `meeting`, `lunch`, `focus`, `brb`, `clear`
|
|
109
|
+
|
|
110
|
+
### Workspaces
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
slk workspaces list # List configured workspaces
|
|
114
|
+
slk workspaces add # Add a workspace
|
|
115
|
+
slk workspaces primary # Show/set primary workspace
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Cache Management
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
slk cache status # Show cache status
|
|
122
|
+
slk cache populate # Pre-populate user cache
|
|
123
|
+
slk cache clear # Clear all caches
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Global Options
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
-w, --workspace NAME # Use specific workspace
|
|
130
|
+
--all # Apply to all workspaces
|
|
131
|
+
-v, --verbose # Show debug output
|
|
132
|
+
-q, --quiet # Suppress output
|
|
133
|
+
--json # Output as JSON (where supported)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Multi-Workspace
|
|
137
|
+
|
|
138
|
+
Configure multiple workspaces and switch between them:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
slk workspaces add # Add another workspace
|
|
142
|
+
slk status -w work # Check status on 'work' workspace
|
|
143
|
+
slk status "OOO" --all # Set status on all workspaces
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Token Encryption
|
|
147
|
+
|
|
148
|
+
Optionally encrypt your tokens with [age](https://github.com/FiloSottile/age) using an SSH key:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
slk config set ssh_key ~/.ssh/id_ed25519
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Tokens will be stored encrypted in `~/.config/slack-cli/tokens.age`.
|
|
155
|
+
|
|
156
|
+
## Configuration
|
|
157
|
+
|
|
158
|
+
Files are stored in XDG-compliant locations:
|
|
159
|
+
|
|
160
|
+
- **Config**: `~/.config/slack-cli/`
|
|
161
|
+
- `config.json` - Settings
|
|
162
|
+
- `tokens.json` or `tokens.age` - Workspace tokens
|
|
163
|
+
- `presets.json` - Status presets
|
|
164
|
+
- **Cache**: `~/.cache/slack-cli/`
|
|
165
|
+
- `users-{workspace}.json` - User cache
|
|
166
|
+
- `channels-{workspace}.json` - Channel cache
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Clone the repo
|
|
172
|
+
git clone https://github.com/ericboehs/slack-cli.git
|
|
173
|
+
cd slack-cli
|
|
174
|
+
|
|
175
|
+
# Run from source
|
|
176
|
+
ruby -Ilib bin/slk --version
|
|
177
|
+
|
|
178
|
+
# Run tests
|
|
179
|
+
rake test
|
|
180
|
+
|
|
181
|
+
# Build gem
|
|
182
|
+
gem build slk.gemspec
|
|
183
|
+
|
|
184
|
+
# Install locally
|
|
185
|
+
gem install ./slk-0.1.0.gem
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
data/bin/slk
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Activity
|
|
6
|
+
def initialize(api_client, workspace)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def feed(limit: 50, types: nil, cursor: nil, mode: 'priority_reads_and_unreads_v1')
|
|
12
|
+
params = {
|
|
13
|
+
mode: mode,
|
|
14
|
+
limit: limit.to_s,
|
|
15
|
+
archive_only: 'false',
|
|
16
|
+
snooze_only: 'false',
|
|
17
|
+
unread_only: 'false',
|
|
18
|
+
priority_only: 'false',
|
|
19
|
+
is_activity_inbox: 'false'
|
|
20
|
+
}
|
|
21
|
+
params[:types] = types if types
|
|
22
|
+
params[:cursor] = cursor if cursor
|
|
23
|
+
|
|
24
|
+
@api.post_form(@workspace, 'activity.feed', params)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Bots
|
|
6
|
+
def initialize(api_client, workspace, on_debug: nil)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
@on_debug = on_debug
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Look up bot information by ID
|
|
13
|
+
# @param bot_id [String] Bot ID starting with "B"
|
|
14
|
+
# @return [Hash, nil] Bot info hash or nil if not found
|
|
15
|
+
def info(bot_id)
|
|
16
|
+
response = @api.post_form(@workspace, "bots.info", { bot: bot_id })
|
|
17
|
+
response["bot"] if response["ok"]
|
|
18
|
+
rescue ApiError => e
|
|
19
|
+
@on_debug&.call("Bot lookup failed for #{bot_id}: #{e.message}")
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get bot name by ID
|
|
24
|
+
# @param bot_id [String] Bot ID starting with "B"
|
|
25
|
+
# @return [String, nil] Bot name or nil if not found
|
|
26
|
+
def get_name(bot_id)
|
|
27
|
+
bot = info(bot_id)
|
|
28
|
+
bot&.dig("name")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Client
|
|
6
|
+
def initialize(api_client, workspace)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def counts
|
|
12
|
+
@api.post(@workspace, "client.counts")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def auth_test
|
|
16
|
+
@api.post(@workspace, "auth.test")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def team_id
|
|
20
|
+
@team_id ||= auth_test["team_id"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def unread_channels
|
|
24
|
+
response = counts
|
|
25
|
+
channels = response.dig("channels") || []
|
|
26
|
+
|
|
27
|
+
channels.select { |ch| (ch["mention_count"] || 0) > 0 || ch["has_unreads"] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def unread_dms
|
|
31
|
+
response = counts
|
|
32
|
+
dms = response.dig("ims") || []
|
|
33
|
+
mpims = response.dig("mpims") || []
|
|
34
|
+
|
|
35
|
+
(dms + mpims).select { |dm| (dm["mention_count"] || 0) > 0 || dm["has_unreads"] }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def total_unread_count
|
|
39
|
+
response = counts
|
|
40
|
+
|
|
41
|
+
channel_count = (response.dig("channels") || []).sum { |c| c["mention_count"] || 0 }
|
|
42
|
+
dm_count = (response.dig("ims") || []).sum { |d| d["mention_count"] || 0 }
|
|
43
|
+
mpim_count = (response.dig("mpims") || []).sum { |m| m["mention_count"] || 0 }
|
|
44
|
+
|
|
45
|
+
channel_count + dm_count + mpim_count
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Conversations
|
|
6
|
+
def initialize(api_client, workspace)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def list(cursor: nil, limit: 1000, types: "public_channel,private_channel")
|
|
12
|
+
params = { limit: limit, types: types }
|
|
13
|
+
params[:cursor] = cursor if cursor
|
|
14
|
+
@api.post(@workspace, "conversations.list", params)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def history(channel:, limit: 20, cursor: nil, oldest: nil, latest: nil)
|
|
18
|
+
params = { channel: channel, limit: limit }
|
|
19
|
+
params[:cursor] = cursor if cursor
|
|
20
|
+
params[:oldest] = oldest if oldest
|
|
21
|
+
params[:latest] = latest if latest
|
|
22
|
+
@api.post(@workspace, "conversations.history", params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def replies(channel:, ts:, limit: 100, cursor: nil)
|
|
26
|
+
params = { channel: channel, ts: ts, limit: limit }
|
|
27
|
+
params[:cursor] = cursor if cursor
|
|
28
|
+
# Use form encoding - some workspaces (Enterprise Grid) require it
|
|
29
|
+
@api.post_form(@workspace, "conversations.replies", params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def open(users:)
|
|
33
|
+
user_list = Array(users).join(",")
|
|
34
|
+
@api.post(@workspace, "conversations.open", { users: user_list })
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def mark(channel:, ts:)
|
|
38
|
+
@api.post(@workspace, "conversations.mark", { channel: channel, ts: ts })
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def info(channel:)
|
|
42
|
+
@api.post_form(@workspace, "conversations.info", { channel: channel })
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def members(channel:, cursor: nil, limit: 100)
|
|
46
|
+
params = { channel: channel, limit: limit }
|
|
47
|
+
params[:cursor] = cursor if cursor
|
|
48
|
+
@api.post(@workspace, "conversations.members", params)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Dnd
|
|
6
|
+
def initialize(api_client, workspace)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def info
|
|
12
|
+
@api.post(@workspace, "dnd.info")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def set_snooze(duration)
|
|
16
|
+
minutes = duration.to_minutes
|
|
17
|
+
@api.post(@workspace, "dnd.setSnooze", { num_minutes: minutes })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def end_snooze
|
|
21
|
+
@api.post(@workspace, "dnd.endSnooze")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def snoozing?
|
|
25
|
+
info["snooze_enabled"] == true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def snooze_remaining
|
|
29
|
+
data = info
|
|
30
|
+
return nil unless data["snooze_enabled"]
|
|
31
|
+
|
|
32
|
+
endtime = data["snooze_endtime"]
|
|
33
|
+
return nil unless endtime
|
|
34
|
+
|
|
35
|
+
remaining = endtime - Time.now.to_i
|
|
36
|
+
remaining > 0 ? Models::Duration.new(seconds: remaining) : nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Emoji
|
|
6
|
+
def initialize(api_client, workspace)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def list
|
|
12
|
+
@api.post(@workspace, "emoji.list")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def custom_emoji
|
|
16
|
+
response = list
|
|
17
|
+
response["emoji"] || {}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Threads
|
|
6
|
+
def initialize(api_client, workspace)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Get unread threads
|
|
12
|
+
# @param limit [Integer] Max threads to return
|
|
13
|
+
# @return [Hash] Response with threads and total_unread_replies
|
|
14
|
+
def get_view(limit: 20)
|
|
15
|
+
@api.post(@workspace, "subscriptions.thread.getView", { limit: limit })
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Mark a thread as read
|
|
19
|
+
# @param channel [String] Channel ID
|
|
20
|
+
# @param thread_ts [String] Thread timestamp
|
|
21
|
+
# @param ts [String] Latest reply timestamp to mark as read
|
|
22
|
+
def mark(channel:, thread_ts:, ts:)
|
|
23
|
+
@api.post_form(@workspace, "subscriptions.thread.mark", {
|
|
24
|
+
channel: channel,
|
|
25
|
+
thread_ts: thread_ts,
|
|
26
|
+
ts: ts
|
|
27
|
+
})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get unread thread count
|
|
31
|
+
# @return [Integer] Number of unread thread replies
|
|
32
|
+
def unread_count
|
|
33
|
+
response = get_view(limit: 1)
|
|
34
|
+
response["total_unread_replies"] || 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check if there are unread threads
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
def has_unreads?
|
|
40
|
+
unread_count > 0
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Usergroups
|
|
6
|
+
def initialize(api_client, workspace)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def list
|
|
12
|
+
@api.post(@workspace, 'usergroups.list')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_handle(subteam_id)
|
|
16
|
+
response = list
|
|
17
|
+
return nil unless response['ok']
|
|
18
|
+
|
|
19
|
+
usergroups = response['usergroups'] || []
|
|
20
|
+
group = usergroups.find { |g| g['id'] == subteam_id }
|
|
21
|
+
group&.dig('handle')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SlackCli
|
|
4
|
+
module Api
|
|
5
|
+
class Users
|
|
6
|
+
def initialize(api_client, workspace, on_debug: nil)
|
|
7
|
+
@api = api_client
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
@on_debug = on_debug
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get_profile
|
|
13
|
+
response = @api.post(@workspace, "users.profile.get")
|
|
14
|
+
response["profile"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_status
|
|
18
|
+
profile = get_profile
|
|
19
|
+
Models::Status.new(
|
|
20
|
+
text: profile["status_text"] || "",
|
|
21
|
+
emoji: profile["status_emoji"] || "",
|
|
22
|
+
expiration: profile["status_expiration"] || 0
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set_status(text:, emoji: nil, duration: nil)
|
|
27
|
+
expiration = duration&.to_expiration || 0
|
|
28
|
+
|
|
29
|
+
@api.post(@workspace, "users.profile.set", {
|
|
30
|
+
profile: {
|
|
31
|
+
status_text: text,
|
|
32
|
+
status_emoji: emoji || "",
|
|
33
|
+
status_expiration: expiration
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear_status
|
|
39
|
+
set_status(text: "", emoji: "", duration: nil)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_presence
|
|
43
|
+
response = @api.post(@workspace, "users.getPresence")
|
|
44
|
+
{
|
|
45
|
+
presence: response["presence"],
|
|
46
|
+
manual_away: response["manual_away"],
|
|
47
|
+
online: response["online"]
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def set_presence(presence)
|
|
52
|
+
@api.post(@workspace, "users.setPresence", { presence: presence })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def list(cursor: nil, limit: 1000)
|
|
56
|
+
params = { limit: limit }
|
|
57
|
+
params[:cursor] = cursor if cursor
|
|
58
|
+
@api.post(@workspace, "users.list", params)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def info(user_id)
|
|
62
|
+
@api.post_form(@workspace, "users.info", { user: user_id })
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_prefs
|
|
66
|
+
@api.post(@workspace, "users.prefs.get")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def muted_channels
|
|
70
|
+
prefs = get_prefs
|
|
71
|
+
|
|
72
|
+
# First try the legacy muted_channels format (comma-separated string)
|
|
73
|
+
muted = prefs.dig("prefs", "muted_channels")
|
|
74
|
+
if muted.is_a?(String) && !muted.empty?
|
|
75
|
+
return muted.split(",").reject(&:empty?)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# New format: muted channels are in all_notifications_prefs JSON string
|
|
79
|
+
# Structure: {"channels": {"C123": {"muted": true}, ...}}
|
|
80
|
+
notifications_prefs = prefs.dig("prefs", "all_notifications_prefs")
|
|
81
|
+
if notifications_prefs.is_a?(String) && !notifications_prefs.empty?
|
|
82
|
+
begin
|
|
83
|
+
parsed = JSON.parse(notifications_prefs)
|
|
84
|
+
channels = parsed["channels"] || {}
|
|
85
|
+
return channels.select { |_id, opts| opts["muted"] == true }.keys
|
|
86
|
+
rescue JSON::ParserError => e
|
|
87
|
+
@on_debug&.call("Failed to parse notification prefs: #{e.message}")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
[]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def conversations(cursor: nil, limit: 1000)
|
|
95
|
+
params = { limit: limit, types: "public_channel,private_channel,mpim,im" }
|
|
96
|
+
params[:cursor] = cursor if cursor
|
|
97
|
+
@api.post_form(@workspace, "users.conversations", params)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|