@urugus/slack-cli 0.1.6 → 0.1.8
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/commands/channels.d.ts.map +1 -1
- package/dist/commands/channels.js +3 -12
- package/dist/commands/channels.js.map +1 -1
- package/dist/commands/config-subcommands.d.ts +14 -0
- package/dist/commands/config-subcommands.d.ts.map +1 -0
- package/dist/commands/config-subcommands.js +65 -0
- package/dist/commands/config-subcommands.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +7 -55
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/history-display.d.ts +3 -0
- package/dist/commands/history-display.d.ts.map +1 -0
- package/dist/commands/history-display.js +33 -0
- package/dist/commands/history-display.js.map +1 -0
- package/dist/commands/history-validators.d.ts +5 -0
- package/dist/commands/history-validators.d.ts.map +1 -0
- package/dist/commands/history-validators.js +35 -0
- package/dist/commands/history-validators.js.map +1 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +8 -51
- package/dist/commands/history.js.map +1 -1
- package/dist/utils/channel-formatter.d.ts +0 -3
- package/dist/utils/channel-formatter.d.ts.map +1 -1
- package/dist/utils/channel-formatter.js +8 -44
- package/dist/utils/channel-formatter.js.map +1 -1
- package/dist/utils/formatters/channels-list-formatters.d.ts +13 -0
- package/dist/utils/formatters/channels-list-formatters.d.ts.map +1 -0
- package/dist/utils/formatters/channels-list-formatters.js +53 -0
- package/dist/utils/formatters/channels-list-formatters.js.map +1 -0
- package/dist/utils/slack-api-client.d.ts +2 -2
- package/dist/utils/slack-api-client.d.ts.map +1 -1
- package/dist/utils/slack-api-client.js +9 -140
- package/dist/utils/slack-api-client.js.map +1 -1
- package/dist/utils/slack-operations/base-client.d.ts +10 -0
- package/dist/utils/slack-operations/base-client.d.ts.map +1 -0
- package/dist/utils/slack-operations/base-client.js +32 -0
- package/dist/utils/slack-operations/base-client.js.map +1 -0
- package/dist/utils/slack-operations/channel-operations.d.ts +15 -0
- package/dist/utils/slack-operations/channel-operations.d.ts.map +1 -0
- package/dist/utils/slack-operations/channel-operations.js +93 -0
- package/dist/utils/slack-operations/channel-operations.js.map +1 -0
- package/dist/utils/slack-operations/index.d.ts +4 -0
- package/dist/utils/slack-operations/index.d.ts.map +1 -0
- package/dist/utils/slack-operations/index.js +10 -0
- package/dist/utils/slack-operations/index.js.map +1 -0
- package/dist/utils/slack-operations/message-operations.d.ts +12 -0
- package/dist/utils/slack-operations/message-operations.d.ts.map +1 -0
- package/dist/utils/slack-operations/message-operations.js +80 -0
- package/dist/utils/slack-operations/message-operations.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/channels.ts +4 -22
- package/src/commands/config-subcommands.ts +63 -0
- package/src/commands/config.ts +15 -69
- package/src/commands/history-display.ts +36 -0
- package/src/commands/history-validators.ts +46 -0
- package/src/commands/history.ts +13 -60
- package/src/utils/channel-formatter.ts +9 -53
- package/src/utils/formatters/channels-list-formatters.ts +59 -0
- package/src/utils/slack-api-client.ts +12 -172
- package/src/utils/slack-operations/base-client.ts +30 -0
- package/src/utils/slack-operations/channel-operations.ts +112 -0
- package/src/utils/slack-operations/index.ts +3 -0
- package/src/utils/slack-operations/message-operations.ts +94 -0
- package/tests/utils/slack-api-client.test.ts +8 -6
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { RATE_LIMIT, DEFAULTS } from './constants';
|
|
1
|
+
import { ChatPostMessageResponse } from '@slack/web-api';
|
|
2
|
+
import { ChannelOperations } from './slack-operations/channel-operations';
|
|
3
|
+
import { MessageOperations } from './slack-operations/message-operations';
|
|
5
4
|
|
|
6
5
|
export interface Channel {
|
|
7
6
|
id: string;
|
|
@@ -36,13 +35,6 @@ export interface Channel {
|
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
// Extended channel interface with additional properties from API
|
|
40
|
-
interface ChannelWithUnreadInfo extends Channel {
|
|
41
|
-
unread_count: number;
|
|
42
|
-
unread_count_display: number;
|
|
43
|
-
last_read?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
38
|
export interface ListChannelsOptions {
|
|
47
39
|
types: string;
|
|
48
40
|
exclude_archived: boolean;
|
|
@@ -77,184 +69,32 @@ export interface ChannelUnreadResult {
|
|
|
77
69
|
}
|
|
78
70
|
|
|
79
71
|
export class SlackApiClient {
|
|
80
|
-
private
|
|
81
|
-
private
|
|
72
|
+
private channelOps: ChannelOperations;
|
|
73
|
+
private messageOps: MessageOperations;
|
|
82
74
|
|
|
83
75
|
constructor(token: string) {
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
// Limit concurrent API calls to avoid rate limiting
|
|
88
|
-
this.rateLimiter = pLimit(RATE_LIMIT.CONCURRENT_REQUESTS);
|
|
76
|
+
this.channelOps = new ChannelOperations(token);
|
|
77
|
+
this.messageOps = new MessageOperations(token);
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
async sendMessage(channel: string, text: string): Promise<ChatPostMessageResponse> {
|
|
92
|
-
return
|
|
93
|
-
channel,
|
|
94
|
-
text,
|
|
95
|
-
});
|
|
81
|
+
return this.messageOps.sendMessage(channel, text);
|
|
96
82
|
}
|
|
97
83
|
|
|
98
84
|
async listChannels(options: ListChannelsOptions): Promise<Channel[]> {
|
|
99
|
-
|
|
100
|
-
let cursor: string | undefined;
|
|
101
|
-
|
|
102
|
-
// Paginate through all channels
|
|
103
|
-
do {
|
|
104
|
-
const response = await this.client.conversations.list({
|
|
105
|
-
types: options.types,
|
|
106
|
-
exclude_archived: options.exclude_archived,
|
|
107
|
-
limit: options.limit,
|
|
108
|
-
cursor,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (response.channels) {
|
|
112
|
-
channels.push(...(response.channels as Channel[]));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
cursor = response.response_metadata?.next_cursor;
|
|
116
|
-
} while (cursor);
|
|
117
|
-
|
|
118
|
-
return channels;
|
|
85
|
+
return this.channelOps.listChannels(options);
|
|
119
86
|
}
|
|
120
87
|
|
|
121
88
|
async getHistory(channel: string, options: HistoryOptions): Promise<HistoryResult> {
|
|
122
|
-
|
|
123
|
-
const channelId = await channelResolver.resolveChannelId(channel, () =>
|
|
124
|
-
this.listChannels({
|
|
125
|
-
types: 'public_channel,private_channel,im,mpim',
|
|
126
|
-
exclude_archived: true,
|
|
127
|
-
limit: DEFAULTS.CHANNELS_LIMIT,
|
|
128
|
-
})
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const response = await this.client.conversations.history({
|
|
132
|
-
channel: channelId,
|
|
133
|
-
limit: options.limit,
|
|
134
|
-
oldest: options.oldest,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const messages = response.messages as Message[];
|
|
138
|
-
|
|
139
|
-
// Get unique user IDs
|
|
140
|
-
const userIds = [...new Set(messages.filter((m) => m.user).map((m) => m.user!))];
|
|
141
|
-
const users = new Map<string, string>();
|
|
142
|
-
|
|
143
|
-
// Fetch user information
|
|
144
|
-
if (userIds.length > 0) {
|
|
145
|
-
for (const userId of userIds) {
|
|
146
|
-
try {
|
|
147
|
-
const userInfo = await this.client.users.info({ user: userId });
|
|
148
|
-
if (userInfo.user?.name) {
|
|
149
|
-
users.set(userId, userInfo.user.name);
|
|
150
|
-
}
|
|
151
|
-
} catch (error) {
|
|
152
|
-
// If we can't get user info, we'll use the ID
|
|
153
|
-
users.set(userId, userId);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return { messages, users };
|
|
89
|
+
return this.messageOps.getHistory(channel, options);
|
|
159
90
|
}
|
|
160
91
|
|
|
161
92
|
async listUnreadChannels(): Promise<Channel[]> {
|
|
162
|
-
|
|
163
|
-
const response = await this.client.conversations.list({
|
|
164
|
-
types: 'public_channel,private_channel,im,mpim',
|
|
165
|
-
exclude_archived: true,
|
|
166
|
-
limit: 1000,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
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
|
-
const channelsWithUnread: Channel[] = [];
|
|
179
|
-
|
|
180
|
-
// Process batches sequentially with delay
|
|
181
|
-
for (const batch of batches) {
|
|
182
|
-
const batchResults = await Promise.all(
|
|
183
|
-
batch.map((channel) =>
|
|
184
|
-
this.rateLimiter(async () => {
|
|
185
|
-
try {
|
|
186
|
-
const info = await this.client.conversations.info({
|
|
187
|
-
channel: channel.id,
|
|
188
|
-
include_num_members: false,
|
|
189
|
-
});
|
|
190
|
-
const channelInfo = info.channel as ChannelWithUnreadInfo;
|
|
191
|
-
return {
|
|
192
|
-
...channel,
|
|
193
|
-
unread_count: channelInfo.unread_count || 0,
|
|
194
|
-
unread_count_display: channelInfo.unread_count_display || 0,
|
|
195
|
-
last_read: channelInfo.last_read,
|
|
196
|
-
};
|
|
197
|
-
} catch (error) {
|
|
198
|
-
// Log rate limit errors but continue processing
|
|
199
|
-
if (error instanceof Error && error.message?.includes('rate limit')) {
|
|
200
|
-
console.warn(`Rate limit hit for channel ${channel.name}, skipping...`);
|
|
201
|
-
}
|
|
202
|
-
return channel;
|
|
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));
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Filter to only channels with unread messages
|
|
217
|
-
return channelsWithUnread.filter((channel) => (channel.unread_count_display || 0) > 0);
|
|
93
|
+
return this.channelOps.listUnreadChannels();
|
|
218
94
|
}
|
|
219
95
|
|
|
220
96
|
async getChannelUnread(channelNameOrId: string): Promise<ChannelUnreadResult> {
|
|
221
|
-
|
|
222
|
-
const channelId = await channelResolver.resolveChannelId(channelNameOrId, () =>
|
|
223
|
-
this.listChannels({
|
|
224
|
-
types: 'public_channel,private_channel,im,mpim',
|
|
225
|
-
exclude_archived: true,
|
|
226
|
-
limit: DEFAULTS.CHANNELS_LIMIT,
|
|
227
|
-
})
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// Get channel info with unread count
|
|
231
|
-
const info = await this.client.conversations.info({
|
|
232
|
-
channel: channelId,
|
|
233
|
-
});
|
|
234
|
-
const channel = info.channel as ChannelWithUnreadInfo;
|
|
235
|
-
|
|
236
|
-
// Get unread messages
|
|
237
|
-
let messages: Message[] = [];
|
|
238
|
-
let users = new Map<string, string>();
|
|
239
|
-
|
|
240
|
-
if (channel.last_read && channel.unread_count > 0) {
|
|
241
|
-
const historyResult = await this.getHistory(channelId, {
|
|
242
|
-
limit: channel.unread_count,
|
|
243
|
-
oldest: channel.last_read,
|
|
244
|
-
});
|
|
245
|
-
messages = historyResult.messages;
|
|
246
|
-
users = historyResult.users;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
channel: {
|
|
251
|
-
...channel,
|
|
252
|
-
unread_count: channel.unread_count || 0,
|
|
253
|
-
unread_count_display: channel.unread_count_display || 0,
|
|
254
|
-
},
|
|
255
|
-
messages,
|
|
256
|
-
users,
|
|
257
|
-
};
|
|
97
|
+
return this.messageOps.getChannelUnread(channelNameOrId);
|
|
258
98
|
}
|
|
259
99
|
}
|
|
260
100
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { WebClient, LogLevel } from '@slack/web-api';
|
|
2
|
+
import pLimit from 'p-limit';
|
|
3
|
+
import { RATE_LIMIT } from '../constants';
|
|
4
|
+
|
|
5
|
+
export class BaseSlackClient {
|
|
6
|
+
protected client: WebClient;
|
|
7
|
+
protected rateLimiter: ReturnType<typeof pLimit>;
|
|
8
|
+
|
|
9
|
+
constructor(token: string) {
|
|
10
|
+
this.client = new WebClient(token, {
|
|
11
|
+
retryConfig: {
|
|
12
|
+
retries: 0, // Disable automatic retries to handle rate limits manually
|
|
13
|
+
},
|
|
14
|
+
logLevel: LogLevel.ERROR, // Reduce noise from WebClient logs
|
|
15
|
+
});
|
|
16
|
+
// Limit concurrent API calls to avoid rate limiting
|
|
17
|
+
this.rateLimiter = pLimit(RATE_LIMIT.CONCURRENT_REQUESTS);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected async handleRateLimit(error: unknown): Promise<void> {
|
|
21
|
+
if (error instanceof Error && error.message?.includes('rate limit')) {
|
|
22
|
+
// If we hit rate limit, wait longer
|
|
23
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected async delay(ms: number): Promise<void> {
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { BaseSlackClient } from './base-client';
|
|
2
|
+
import { channelResolver } from '../channel-resolver';
|
|
3
|
+
import { DEFAULTS } from '../constants';
|
|
4
|
+
import { Channel, ListChannelsOptions } from '../slack-api-client';
|
|
5
|
+
|
|
6
|
+
interface ChannelWithUnreadInfo extends Channel {
|
|
7
|
+
unread_count: number;
|
|
8
|
+
unread_count_display: number;
|
|
9
|
+
last_read?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ChannelOperations extends BaseSlackClient {
|
|
13
|
+
async listChannels(options: ListChannelsOptions): Promise<Channel[]> {
|
|
14
|
+
const channels: Channel[] = [];
|
|
15
|
+
let cursor: string | undefined;
|
|
16
|
+
|
|
17
|
+
// Paginate through all channels
|
|
18
|
+
do {
|
|
19
|
+
const response = await this.client.conversations.list({
|
|
20
|
+
types: options.types,
|
|
21
|
+
exclude_archived: options.exclude_archived,
|
|
22
|
+
limit: options.limit,
|
|
23
|
+
cursor,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (response.channels) {
|
|
27
|
+
channels.push(...(response.channels as Channel[]));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cursor = response.response_metadata?.next_cursor;
|
|
31
|
+
} while (cursor);
|
|
32
|
+
|
|
33
|
+
return channels;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async listUnreadChannels(): Promise<Channel[]> {
|
|
37
|
+
try {
|
|
38
|
+
// Use users.conversations to get unread counts in a single API call
|
|
39
|
+
const response = await this.client.users.conversations({
|
|
40
|
+
types: 'public_channel,private_channel,im,mpim',
|
|
41
|
+
exclude_archived: true,
|
|
42
|
+
limit: 1000,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const channels = response.channels as Channel[];
|
|
46
|
+
|
|
47
|
+
// Filter to only channels with unread messages
|
|
48
|
+
// The users.conversations endpoint includes unread_count_display
|
|
49
|
+
return channels.filter((channel) => (channel.unread_count_display || 0) > 0);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Fallback to the old method if users.conversations fails
|
|
52
|
+
console.warn('Failed to use users.conversations, falling back to conversations.list');
|
|
53
|
+
return this.listUnreadChannelsFallback();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async listUnreadChannelsFallback(): Promise<Channel[]> {
|
|
58
|
+
// Get all conversations the user is a member of
|
|
59
|
+
const response = await this.client.conversations.list({
|
|
60
|
+
types: 'public_channel,private_channel,im,mpim',
|
|
61
|
+
exclude_archived: true,
|
|
62
|
+
limit: 1000,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const channels = response.channels as Channel[];
|
|
66
|
+
const channelsWithUnread: Channel[] = [];
|
|
67
|
+
|
|
68
|
+
// Process channels one by one with delay to avoid rate limits
|
|
69
|
+
for (const channel of channels) {
|
|
70
|
+
try {
|
|
71
|
+
const info = await this.client.conversations.info({
|
|
72
|
+
channel: channel.id,
|
|
73
|
+
include_num_members: false,
|
|
74
|
+
});
|
|
75
|
+
const channelInfo = info.channel as ChannelWithUnreadInfo;
|
|
76
|
+
|
|
77
|
+
if (channelInfo.unread_count_display && channelInfo.unread_count_display > 0) {
|
|
78
|
+
channelsWithUnread.push({
|
|
79
|
+
...channel,
|
|
80
|
+
unread_count: channelInfo.unread_count || 0,
|
|
81
|
+
unread_count_display: channelInfo.unread_count_display || 0,
|
|
82
|
+
last_read: channelInfo.last_read,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Add delay between API calls to avoid rate limiting
|
|
87
|
+
await this.delay(100);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// Skip channels that fail
|
|
90
|
+
await this.handleRateLimit(error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return channelsWithUnread;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getChannelInfo(channelNameOrId: string): Promise<ChannelWithUnreadInfo> {
|
|
98
|
+
const channelId = await channelResolver.resolveChannelId(channelNameOrId, () =>
|
|
99
|
+
this.listChannels({
|
|
100
|
+
types: 'public_channel,private_channel,im,mpim',
|
|
101
|
+
exclude_archived: true,
|
|
102
|
+
limit: DEFAULTS.CHANNELS_LIMIT,
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const info = await this.client.conversations.info({
|
|
107
|
+
channel: channelId,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return info.channel as ChannelWithUnreadInfo;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ChatPostMessageResponse } from '@slack/web-api';
|
|
2
|
+
import { BaseSlackClient } from './base-client';
|
|
3
|
+
import { channelResolver } from '../channel-resolver';
|
|
4
|
+
import { DEFAULTS } from '../constants';
|
|
5
|
+
import { Message, HistoryOptions, HistoryResult, ChannelUnreadResult } from '../slack-api-client';
|
|
6
|
+
import { ChannelOperations } from './channel-operations';
|
|
7
|
+
|
|
8
|
+
export class MessageOperations extends BaseSlackClient {
|
|
9
|
+
private channelOps: ChannelOperations;
|
|
10
|
+
|
|
11
|
+
constructor(token: string) {
|
|
12
|
+
super(token);
|
|
13
|
+
this.channelOps = new ChannelOperations(token);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async sendMessage(channel: string, text: string): Promise<ChatPostMessageResponse> {
|
|
17
|
+
return await this.client.chat.postMessage({
|
|
18
|
+
channel,
|
|
19
|
+
text,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getHistory(channel: string, options: HistoryOptions): Promise<HistoryResult> {
|
|
24
|
+
// Resolve channel name to ID if needed
|
|
25
|
+
const channelId = await channelResolver.resolveChannelId(channel, () =>
|
|
26
|
+
this.channelOps.listChannels({
|
|
27
|
+
types: 'public_channel,private_channel,im,mpim',
|
|
28
|
+
exclude_archived: true,
|
|
29
|
+
limit: DEFAULTS.CHANNELS_LIMIT,
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const response = await this.client.conversations.history({
|
|
34
|
+
channel: channelId,
|
|
35
|
+
limit: options.limit,
|
|
36
|
+
oldest: options.oldest,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const messages = response.messages as Message[];
|
|
40
|
+
|
|
41
|
+
// Get unique user IDs
|
|
42
|
+
const userIds = [...new Set(messages.filter((m) => m.user).map((m) => m.user!))];
|
|
43
|
+
const users = await this.fetchUserInfo(userIds);
|
|
44
|
+
|
|
45
|
+
return { messages, users };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getChannelUnread(channelNameOrId: string): Promise<ChannelUnreadResult> {
|
|
49
|
+
const channel = await this.channelOps.getChannelInfo(channelNameOrId);
|
|
50
|
+
|
|
51
|
+
// Get unread messages
|
|
52
|
+
let messages: Message[] = [];
|
|
53
|
+
let users = new Map<string, string>();
|
|
54
|
+
|
|
55
|
+
if (channel.last_read && channel.unread_count > 0) {
|
|
56
|
+
const historyResult = await this.getHistory(channel.id, {
|
|
57
|
+
limit: channel.unread_count,
|
|
58
|
+
oldest: channel.last_read,
|
|
59
|
+
});
|
|
60
|
+
messages = historyResult.messages;
|
|
61
|
+
users = historyResult.users;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
channel: {
|
|
66
|
+
...channel,
|
|
67
|
+
unread_count: channel.unread_count || 0,
|
|
68
|
+
unread_count_display: channel.unread_count_display || 0,
|
|
69
|
+
},
|
|
70
|
+
messages,
|
|
71
|
+
users,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async fetchUserInfo(userIds: string[]): Promise<Map<string, string>> {
|
|
76
|
+
const users = new Map<string, string>();
|
|
77
|
+
|
|
78
|
+
if (userIds.length > 0) {
|
|
79
|
+
for (const userId of userIds) {
|
|
80
|
+
try {
|
|
81
|
+
const userInfo = await this.client.users.info({ user: userId });
|
|
82
|
+
if (userInfo.user?.name) {
|
|
83
|
+
users.set(userId, userInfo.user.name);
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// If we can't get user info, we'll use the ID
|
|
87
|
+
users.set(userId, userId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return users;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -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
|
});
|