@urugus/slack-cli 0.1.6 → 0.1.7
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.
- package/CHANGELOG.md +8 -0
- package/dist/utils/slack-api-client.d.ts +1 -0
- package/dist/utils/slack-api-client.d.ts.map +1 -1
- package/dist/utils/slack-api-client.js +44 -31
- package/dist/utils/slack-api-client.js.map +1 -1
- package/package.json +1 -1
- package/src/utils/slack-api-client.ts +54 -45
- package/tests/utils/slack-api-client.test.ts +8 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.7] - 2025-06-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Resolved rate limiting issues with `slack unread` command
|
|
9
|
+
- Disabled WebClient automatic retries to handle rate limits manually
|
|
10
|
+
- Changed to use users.conversations API for more efficient unread retrieval
|
|
11
|
+
- Added fallback mechanism for better compatibility
|
|
12
|
+
|
|
5
13
|
## [0.1.6] - 2025-06-22
|
|
6
14
|
|
|
7
15
|
### Added
|
|
@@ -67,6 +67,7 @@ export declare class SlackApiClient {
|
|
|
67
67
|
listChannels(options: ListChannelsOptions): Promise<Channel[]>;
|
|
68
68
|
getHistory(channel: string, options: HistoryOptions): Promise<HistoryResult>;
|
|
69
69
|
listUnreadChannels(): Promise<Channel[]>;
|
|
70
|
+
private listUnreadChannelsFallback;
|
|
70
71
|
getChannelUnread(channelNameOrId: string): Promise<ChannelUnreadResult>;
|
|
71
72
|
}
|
|
72
73
|
export declare const slackApiClient: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-api-client.d.ts","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,uBAAuB,
|
|
1
|
+
{"version":3,"file":"slack-api-client.d.ts","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,10 @@ const constants_1 = require("./constants");
|
|
|
11
11
|
class SlackApiClient {
|
|
12
12
|
constructor(token) {
|
|
13
13
|
this.client = new web_api_1.WebClient(token, {
|
|
14
|
-
retryConfig:
|
|
14
|
+
retryConfig: {
|
|
15
|
+
retries: 0, // Disable automatic retries to handle rate limits manually
|
|
16
|
+
},
|
|
17
|
+
logLevel: web_api_1.LogLevel.ERROR, // Reduce noise from WebClient logs
|
|
15
18
|
});
|
|
16
19
|
// Limit concurrent API calls to avoid rate limiting
|
|
17
20
|
this.rateLimiter = (0, p_limit_1.default)(constants_1.RATE_LIMIT.CONCURRENT_REQUESTS);
|
|
@@ -74,6 +77,26 @@ class SlackApiClient {
|
|
|
74
77
|
return { messages, users };
|
|
75
78
|
}
|
|
76
79
|
async listUnreadChannels() {
|
|
80
|
+
try {
|
|
81
|
+
// Use users.conversations to get unread counts in a single API call
|
|
82
|
+
const response = await this.client.users.conversations({
|
|
83
|
+
types: 'public_channel,private_channel,im,mpim',
|
|
84
|
+
exclude_archived: true,
|
|
85
|
+
limit: 1000,
|
|
86
|
+
user: undefined, // Current authenticated user
|
|
87
|
+
});
|
|
88
|
+
const channels = response.channels;
|
|
89
|
+
// Filter to only channels with unread messages
|
|
90
|
+
// The users.conversations endpoint includes unread_count_display
|
|
91
|
+
return channels.filter((channel) => (channel.unread_count_display || 0) > 0);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Fallback to the old method if users.conversations fails
|
|
95
|
+
console.warn('Failed to use users.conversations, falling back to conversations.list');
|
|
96
|
+
return this.listUnreadChannelsFallback();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async listUnreadChannelsFallback() {
|
|
77
100
|
// Get all conversations the user is a member of
|
|
78
101
|
const response = await this.client.conversations.list({
|
|
79
102
|
types: 'public_channel,private_channel,im,mpim',
|
|
@@ -81,45 +104,35 @@ class SlackApiClient {
|
|
|
81
104
|
limit: 1000,
|
|
82
105
|
});
|
|
83
106
|
const channels = response.channels;
|
|
84
|
-
// Batch process channels to reduce rate limit issues
|
|
85
|
-
const batchSize = constants_1.RATE_LIMIT.BATCH_SIZE;
|
|
86
|
-
const batches = [];
|
|
87
|
-
for (let i = 0; i < channels.length; i += batchSize) {
|
|
88
|
-
batches.push(channels.slice(i, i + batchSize));
|
|
89
|
-
}
|
|
90
107
|
const channelsWithUnread = [];
|
|
91
|
-
// Process
|
|
92
|
-
for (const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
// Process channels one by one with delay to avoid rate limits
|
|
109
|
+
for (const channel of channels) {
|
|
110
|
+
try {
|
|
111
|
+
const info = await this.client.conversations.info({
|
|
112
|
+
channel: channel.id,
|
|
113
|
+
include_num_members: false,
|
|
114
|
+
});
|
|
115
|
+
const channelInfo = info.channel;
|
|
116
|
+
if (channelInfo.unread_count_display && channelInfo.unread_count_display > 0) {
|
|
117
|
+
channelsWithUnread.push({
|
|
101
118
|
...channel,
|
|
102
119
|
unread_count: channelInfo.unread_count || 0,
|
|
103
120
|
unread_count_display: channelInfo.unread_count_display || 0,
|
|
104
121
|
last_read: channelInfo.last_read,
|
|
105
|
-
};
|
|
122
|
+
});
|
|
106
123
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
// Add delay between API calls to avoid rate limiting
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
// Skip channels that fail
|
|
129
|
+
if (error instanceof Error && error.message?.includes('rate limit')) {
|
|
130
|
+
// If we hit rate limit, wait longer
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
113
132
|
}
|
|
114
|
-
})));
|
|
115
|
-
channelsWithUnread.push(...batchResults);
|
|
116
|
-
// Add delay between batches to avoid rate limiting
|
|
117
|
-
if (batches.indexOf(batch) < batches.length - 1) {
|
|
118
|
-
await new Promise((resolve) => setTimeout(resolve, constants_1.RATE_LIMIT.BATCH_DELAY_MS));
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
|
-
|
|
122
|
-
return channelsWithUnread.filter((channel) => (channel.unread_count_display || 0) > 0);
|
|
135
|
+
return channelsWithUnread;
|
|
123
136
|
}
|
|
124
137
|
async getChannelUnread(channelNameOrId) {
|
|
125
138
|
// Resolve channel name to ID if needed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-api-client.js","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":";;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"slack-api-client.js","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebClient, ChatPostMessageResponse } from '@slack/web-api';
|
|
1
|
+
import { WebClient, ChatPostMessageResponse, LogLevel } from '@slack/web-api';
|
|
2
2
|
import pLimit from 'p-limit';
|
|
3
3
|
import { channelResolver } from './channel-resolver';
|
|
4
4
|
import { RATE_LIMIT, DEFAULTS } from './constants';
|
|
@@ -82,7 +82,10 @@ export class SlackApiClient {
|
|
|
82
82
|
|
|
83
83
|
constructor(token: string) {
|
|
84
84
|
this.client = new WebClient(token, {
|
|
85
|
-
retryConfig:
|
|
85
|
+
retryConfig: {
|
|
86
|
+
retries: 0, // Disable automatic retries to handle rate limits manually
|
|
87
|
+
},
|
|
88
|
+
logLevel: LogLevel.ERROR, // Reduce noise from WebClient logs
|
|
86
89
|
});
|
|
87
90
|
// Limit concurrent API calls to avoid rate limiting
|
|
88
91
|
this.rateLimiter = pLimit(RATE_LIMIT.CONCURRENT_REQUESTS);
|
|
@@ -159,6 +162,28 @@ export class SlackApiClient {
|
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
async listUnreadChannels(): Promise<Channel[]> {
|
|
165
|
+
try {
|
|
166
|
+
// Use users.conversations to get unread counts in a single API call
|
|
167
|
+
const response = await this.client.users.conversations({
|
|
168
|
+
types: 'public_channel,private_channel,im,mpim',
|
|
169
|
+
exclude_archived: true,
|
|
170
|
+
limit: 1000,
|
|
171
|
+
user: undefined, // Current authenticated user
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const channels = response.channels as Channel[];
|
|
175
|
+
|
|
176
|
+
// Filter to only channels with unread messages
|
|
177
|
+
// The users.conversations endpoint includes unread_count_display
|
|
178
|
+
return channels.filter((channel) => (channel.unread_count_display || 0) > 0);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
// Fallback to the old method if users.conversations fails
|
|
181
|
+
console.warn('Failed to use users.conversations, falling back to conversations.list');
|
|
182
|
+
return this.listUnreadChannelsFallback();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async listUnreadChannelsFallback(): Promise<Channel[]> {
|
|
162
187
|
// Get all conversations the user is a member of
|
|
163
188
|
const response = await this.client.conversations.list({
|
|
164
189
|
types: 'public_channel,private_channel,im,mpim',
|
|
@@ -167,54 +192,38 @@ export class SlackApiClient {
|
|
|
167
192
|
});
|
|
168
193
|
|
|
169
194
|
const channels = response.channels as Channel[];
|
|
170
|
-
|
|
171
|
-
// Batch process channels to reduce rate limit issues
|
|
172
|
-
const batchSize = RATE_LIMIT.BATCH_SIZE;
|
|
173
|
-
const batches: Channel[][] = [];
|
|
174
|
-
for (let i = 0; i < channels.length; i += batchSize) {
|
|
175
|
-
batches.push(channels.slice(i, i + batchSize));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
195
|
const channelsWithUnread: Channel[] = [];
|
|
179
196
|
|
|
180
|
-
// Process
|
|
181
|
-
for (const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
channelsWithUnread.push(...batchResults);
|
|
209
|
-
|
|
210
|
-
// Add delay between batches to avoid rate limiting
|
|
211
|
-
if (batches.indexOf(batch) < batches.length - 1) {
|
|
212
|
-
await new Promise((resolve) => setTimeout(resolve, RATE_LIMIT.BATCH_DELAY_MS));
|
|
197
|
+
// Process channels one by one with delay to avoid rate limits
|
|
198
|
+
for (const channel of channels) {
|
|
199
|
+
try {
|
|
200
|
+
const info = await this.client.conversations.info({
|
|
201
|
+
channel: channel.id,
|
|
202
|
+
include_num_members: false,
|
|
203
|
+
});
|
|
204
|
+
const channelInfo = info.channel as ChannelWithUnreadInfo;
|
|
205
|
+
|
|
206
|
+
if (channelInfo.unread_count_display && channelInfo.unread_count_display > 0) {
|
|
207
|
+
channelsWithUnread.push({
|
|
208
|
+
...channel,
|
|
209
|
+
unread_count: channelInfo.unread_count || 0,
|
|
210
|
+
unread_count_display: channelInfo.unread_count_display || 0,
|
|
211
|
+
last_read: channelInfo.last_read,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Add delay between API calls to avoid rate limiting
|
|
216
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
217
|
+
} catch (error) {
|
|
218
|
+
// Skip channels that fail
|
|
219
|
+
if (error instanceof Error && error.message?.includes('rate limit')) {
|
|
220
|
+
// If we hit rate limit, wait longer
|
|
221
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
222
|
+
}
|
|
213
223
|
}
|
|
214
224
|
}
|
|
215
225
|
|
|
216
|
-
|
|
217
|
-
return channelsWithUnread.filter((channel) => (channel.unread_count_display || 0) > 0);
|
|
226
|
+
return channelsWithUnread;
|
|
218
227
|
}
|
|
219
228
|
|
|
220
229
|
async getChannelUnread(channelNameOrId: string): Promise<ChannelUnreadResult> {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { SlackApiClient } from '../../src/utils/slack-api-client';
|
|
3
|
-
import { WebClient } from '@slack/web-api';
|
|
3
|
+
import { WebClient, LogLevel } from '@slack/web-api';
|
|
4
4
|
|
|
5
5
|
vi.mock('@slack/web-api');
|
|
6
6
|
|
|
@@ -15,7 +15,11 @@ describe('SlackApiClient', () => {
|
|
|
15
15
|
postMessage: vi.fn()
|
|
16
16
|
},
|
|
17
17
|
conversations: {
|
|
18
|
-
list: vi.fn()
|
|
18
|
+
list: vi.fn(),
|
|
19
|
+
info: vi.fn()
|
|
20
|
+
},
|
|
21
|
+
users: {
|
|
22
|
+
conversations: vi.fn()
|
|
19
23
|
}
|
|
20
24
|
};
|
|
21
25
|
vi.mocked(WebClient).mockReturnValue(mockWebClient);
|
|
@@ -26,11 +30,9 @@ describe('SlackApiClient', () => {
|
|
|
26
30
|
it('should create WebClient with provided token', () => {
|
|
27
31
|
expect(WebClient).toHaveBeenCalledWith('test-token', {
|
|
28
32
|
retryConfig: {
|
|
29
|
-
retries:
|
|
30
|
-
factor: 2,
|
|
31
|
-
minTimeout: 1000,
|
|
32
|
-
maxTimeout: 30000,
|
|
33
|
+
retries: 0, // Disabled to handle rate limits manually
|
|
33
34
|
},
|
|
35
|
+
logLevel: LogLevel.ERROR,
|
|
34
36
|
});
|
|
35
37
|
});
|
|
36
38
|
});
|