@sendbird/cli 0.0.0-stage → 0.4.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.
- package/.claude-plugin/plugin.json +11 -0
- package/.codex-plugin/plugin.json +39 -0
- package/LICENSE +13 -0
- package/NOTICE.md +1105 -0
- package/PROVENANCE.md +15 -0
- package/README.md +44 -2
- package/dist/acl.d.ts +2 -0
- package/dist/acl.js +12 -0
- package/dist/acl.js.map +1 -0
- package/dist/billing-plan.d.ts +68 -0
- package/dist/billing-plan.js +235 -0
- package/dist/billing-plan.js.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +20 -0
- package/dist/browser.js.map +1 -0
- package/dist/calls-catalog.d.ts +79 -0
- package/dist/calls-catalog.js +322 -0
- package/dist/calls-catalog.js.map +1 -0
- package/dist/calls-credits.d.ts +31 -0
- package/dist/calls-credits.js +224 -0
- package/dist/calls-credits.js.map +1 -0
- package/dist/calls-trial.d.ts +45 -0
- package/dist/calls-trial.js +77 -0
- package/dist/calls-trial.js.map +1 -0
- package/dist/calls.d.ts +10 -0
- package/dist/calls.js +470 -0
- package/dist/calls.js.map +1 -0
- package/dist/catalog.d.ts +67 -0
- package/dist/catalog.js +784 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +502 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.d.ts +86 -0
- package/dist/core.js +531 -0
- package/dist/core.js.map +1 -0
- package/dist/diagnostics.d.ts +206 -0
- package/dist/diagnostics.js +664 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/discovery.d.ts +703 -0
- package/dist/discovery.js +846 -0
- package/dist/discovery.js.map +1 -0
- package/dist/docs.d.ts +31 -0
- package/dist/docs.js +139 -0
- package/dist/docs.js.map +1 -0
- package/dist/features.d.ts +131 -0
- package/dist/features.js +227 -0
- package/dist/features.js.map +1 -0
- package/dist/generated-api.d.ts +116 -0
- package/dist/generated-api.js +118 -0
- package/dist/generated-api.js.map +1 -0
- package/dist/human-format.d.ts +40 -0
- package/dist/human-format.js +205 -0
- package/dist/human-format.js.map +1 -0
- package/dist/human-list.d.ts +2 -0
- package/dist/human-list.js +368 -0
- package/dist/human-list.js.map +1 -0
- package/dist/human-output.d.ts +10 -0
- package/dist/human-output.js +1351 -0
- package/dist/human-output.js.map +1 -0
- package/dist/human-pro.d.ts +3 -0
- package/dist/human-pro.js +201 -0
- package/dist/human-pro.js.map +1 -0
- package/dist/human-progress.d.ts +5 -0
- package/dist/human-progress.js +49 -0
- package/dist/human-progress.js.map +1 -0
- package/dist/local.d.ts +42 -0
- package/dist/local.js +707 -0
- package/dist/local.js.map +1 -0
- package/dist/oauth-callback.d.ts +3 -0
- package/dist/oauth-callback.js +66 -0
- package/dist/oauth-callback.js.map +1 -0
- package/dist/oauth-lock.d.ts +6 -0
- package/dist/oauth-lock.js +212 -0
- package/dist/oauth-lock.js.map +1 -0
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +493 -0
- package/dist/oauth.js.map +1 -0
- package/dist/presentation.d.ts +208 -0
- package/dist/presentation.js +156 -0
- package/dist/presentation.js.map +1 -0
- package/dist/pro-api-catalog.d.ts +24 -0
- package/dist/pro-api-catalog.js +619 -0
- package/dist/pro-api-catalog.js.map +1 -0
- package/dist/pro-api.d.ts +10 -0
- package/dist/pro-api.js +338 -0
- package/dist/pro-api.js.map +1 -0
- package/dist/project.d.ts +34 -0
- package/dist/project.js +62 -0
- package/dist/project.js.map +1 -0
- package/dist/recovery.d.ts +41 -0
- package/dist/recovery.js +417 -0
- package/dist/recovery.js.map +1 -0
- package/dist/runner.d.ts +47 -0
- package/dist/runner.js +603 -0
- package/dist/runner.js.map +1 -0
- package/dist/sdk-catalog.d.ts +69 -0
- package/dist/sdk-catalog.js +527 -0
- package/dist/sdk-catalog.js.map +1 -0
- package/dist/sdk-config.d.ts +19 -0
- package/dist/sdk-config.js +85 -0
- package/dist/sdk-config.js.map +1 -0
- package/dist/sdk-dto.d.ts +244 -0
- package/dist/sdk-dto.js +98 -0
- package/dist/sdk-dto.js.map +1 -0
- package/dist/sdk-limits.d.ts +2 -0
- package/dist/sdk-limits.js +3 -0
- package/dist/sdk-limits.js.map +1 -0
- package/dist/sdk-node-file-reader.d.ts +6 -0
- package/dist/sdk-node-file-reader.js +38 -0
- package/dist/sdk-node-file-reader.js.map +1 -0
- package/dist/sdk-pro-catalog.d.ts +22 -0
- package/dist/sdk-pro-catalog.js +234 -0
- package/dist/sdk-pro-catalog.js.map +1 -0
- package/dist/sdk-pro-runtime.d.ts +31 -0
- package/dist/sdk-pro-runtime.js +293 -0
- package/dist/sdk-pro-runtime.js.map +1 -0
- package/dist/sdk-query.d.ts +10 -0
- package/dist/sdk-query.js +29 -0
- package/dist/sdk-query.js.map +1 -0
- package/dist/sdk-runtime.d.ts +15 -0
- package/dist/sdk-runtime.js +685 -0
- package/dist/sdk-runtime.js.map +1 -0
- package/dist/sdk-worker.d.ts +1 -0
- package/dist/sdk-worker.js +26 -0
- package/dist/sdk-worker.js.map +1 -0
- package/dist/sdk.d.ts +29 -0
- package/dist/sdk.js +346 -0
- package/dist/sdk.js.map +1 -0
- package/dist/search-engine.d.ts +17 -0
- package/dist/search-engine.js +54 -0
- package/dist/search-engine.js.map +1 -0
- package/dist/search-intents.d.ts +2 -0
- package/dist/search-intents.js +106 -0
- package/dist/search-intents.js.map +1 -0
- package/dist/search.d.ts +7 -0
- package/dist/search.js +58 -0
- package/dist/search.js.map +1 -0
- package/dist/secret-output.d.ts +2 -0
- package/dist/secret-output.js +108 -0
- package/dist/secret-output.js.map +1 -0
- package/dist/shell.d.ts +3 -0
- package/dist/shell.js +14 -0
- package/dist/shell.js.map +1 -0
- package/dist/skills.d.ts +210 -0
- package/dist/skills.js +374 -0
- package/dist/skills.js.map +1 -0
- package/dist/value-semantics.d.ts +70 -0
- package/dist/value-semantics.js +173 -0
- package/dist/value-semantics.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/windows.d.ts +4 -0
- package/dist/windows.js +136 -0
- package/dist/windows.js.map +1 -0
- package/dist/workflows.d.ts +228 -0
- package/dist/workflows.js +1207 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +90 -4
- package/plugin.json +25 -0
- package/references/agent-guide.json +199 -0
- package/references/commands.md +247 -0
- package/references/dashboard-inventory.json +14796 -0
- package/references/docs-index.json +5329 -0
- package/references/sdk-commands.json +15065 -0
- package/references/workflows.json +1246 -0
- package/skills/sendbird/SKILL.md +96 -0
- package/skills/sendbird/agents/openai.yaml +4 -0
- package/skills/sendbird/references/build-chat-app.md +50 -0
- package/skills/sendbird/references/client-guide.md +34 -0
- package/skills/sendbird/references/client-plugin-bridge.md +77 -0
- package/skills/sendbird/references/client-plugin-install.md +39 -0
- package/skills/sendbird/references/operational-checks.md +35 -0
- package/skills/sendbird/references/pro-capabilities.md +52 -0
- package/skills/sendbird/scripts/client-guide.mjs +283 -0
- package/skills/sendbird/scripts/client-plugin-bridge.mjs +703 -0
- package/skills/sendbird-setup/SKILL.md +18 -0
- package/skills/sendbird-setup/agents/openai.yaml +4 -0
- package/skills/sendbird-setup/references/onboarding.md +73 -0
- package/skills/sendbird-setup/scripts/install-cli.mjs +447 -0
- package/skills/sendbird-setup/scripts/npm-run.mjs +34 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import { message, user } from "./sdk-dto.js";
|
|
2
|
+
export { message, user } from "./sdk-dto.js";
|
|
3
|
+
import { readQueryPage } from "./sdk-query.js";
|
|
4
|
+
export { readQueryPage } from "./sdk-query.js";
|
|
5
|
+
import { messageOptions, runProUser, runProChannel, } from "./sdk-pro-runtime.js";
|
|
6
|
+
import { CliError } from "./core.js";
|
|
7
|
+
import { installNodeFileReader } from "./sdk-node-file-reader.js";
|
|
8
|
+
export async function makeSdk(job) {
|
|
9
|
+
await import("@sendbird/chat/node");
|
|
10
|
+
installNodeFileReader();
|
|
11
|
+
const [{ default: SendbirdChat, LogLevel }, { GroupChannelModule }, { OpenChannelModule }, { PollModule },] = await Promise.all([
|
|
12
|
+
import("@sendbird/chat"),
|
|
13
|
+
import("@sendbird/chat/groupChannel"),
|
|
14
|
+
import("@sendbird/chat/openChannel"),
|
|
15
|
+
import("@sendbird/chat/poll"),
|
|
16
|
+
]);
|
|
17
|
+
return SendbirdChat.init({
|
|
18
|
+
appId: job.appId,
|
|
19
|
+
newInstance: true,
|
|
20
|
+
localCacheEnabled: false,
|
|
21
|
+
logLevel: LogLevel.NONE,
|
|
22
|
+
debugMode: false,
|
|
23
|
+
appStateToggleEnabled: false,
|
|
24
|
+
customApiHost: job.apiHost,
|
|
25
|
+
customWebSocketHost: job.wsHost,
|
|
26
|
+
modules: [
|
|
27
|
+
new GroupChannelModule(),
|
|
28
|
+
new OpenChannelModule(),
|
|
29
|
+
new PollModule(),
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function scheduledMessage(m) {
|
|
34
|
+
const info = m.scheduledInfo;
|
|
35
|
+
if (!info)
|
|
36
|
+
throw new CliError("E_SCHEMA", "SDK did not return scheduled message information", 8);
|
|
37
|
+
return {
|
|
38
|
+
scheduled_message_id: info.scheduledMessageId,
|
|
39
|
+
scheduled_at: info.scheduledAt,
|
|
40
|
+
channel_url: m.channelUrl,
|
|
41
|
+
...(m.isUserMessage() ? { message: m.message } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function channel(c) {
|
|
45
|
+
return {
|
|
46
|
+
channel_url: c.url,
|
|
47
|
+
name: c.name,
|
|
48
|
+
type: c.isOpenChannel() ? "open" : c.isSuper ? "supergroup" : "group",
|
|
49
|
+
...(c.isGroupChannel()
|
|
50
|
+
? {
|
|
51
|
+
unread_message_count: c.unreadMessageCount,
|
|
52
|
+
member_count: c.memberCount,
|
|
53
|
+
}
|
|
54
|
+
: { participant_count: c.participantCount }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function myGroupChannel(c) {
|
|
58
|
+
return {
|
|
59
|
+
channel_url: c.url,
|
|
60
|
+
name: c.name,
|
|
61
|
+
type: c.isSuper ? "supergroup" : "group",
|
|
62
|
+
member_count: c.memberCount,
|
|
63
|
+
last_message_id: c.lastMessage?.messageId ?? null,
|
|
64
|
+
sdk_unread_message_count_snapshot: c.lastMessage
|
|
65
|
+
? c.unreadMessageCount
|
|
66
|
+
: null,
|
|
67
|
+
sdk_unread_mention_count_snapshot: c.lastMessage
|
|
68
|
+
? c.unreadMentionCount
|
|
69
|
+
: null,
|
|
70
|
+
my_member_state: c.myMemberState,
|
|
71
|
+
my_count_preference: c.myCountPreference,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
75
|
+
function acknowledged(handler) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
handler.onSucceeded(resolve);
|
|
78
|
+
handler.onFailed(reject);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function runSdk(job, emit = () => { }, signal, factory = makeSdk) {
|
|
82
|
+
const sb = await factory(job);
|
|
83
|
+
let c;
|
|
84
|
+
let entered = false;
|
|
85
|
+
let typing = false;
|
|
86
|
+
let handlerKey;
|
|
87
|
+
const checkAbort = () => {
|
|
88
|
+
if (signal?.aborted)
|
|
89
|
+
throw new CliError("E_INTERRUPTED", "SDK operation interrupted before its next action", 130);
|
|
90
|
+
};
|
|
91
|
+
const group = () => {
|
|
92
|
+
if (!c?.isGroupChannel())
|
|
93
|
+
throw new CliError("E_CHANNEL_TYPE", "This operation requires a Group channel", 2);
|
|
94
|
+
return c;
|
|
95
|
+
};
|
|
96
|
+
const currentMessage = async () => {
|
|
97
|
+
const m = await sb.message.getMessage({
|
|
98
|
+
channelUrl: c.url,
|
|
99
|
+
channelType: c.channelType,
|
|
100
|
+
messageId: Number(job.params.message_id),
|
|
101
|
+
includeMetaArray: true,
|
|
102
|
+
includeReactions: true,
|
|
103
|
+
includeThreadInfo: true,
|
|
104
|
+
includeParentMessageInfo: true,
|
|
105
|
+
});
|
|
106
|
+
checkAbort();
|
|
107
|
+
if (!m)
|
|
108
|
+
throw new CliError("E_NOT_FOUND", "Message not found", 3);
|
|
109
|
+
if (!("messageId" in m) || m.messageId !== Number(job.params.message_id))
|
|
110
|
+
throw new CliError("E_SCHEMA", "Server returned a different message ID", 8);
|
|
111
|
+
if (m.channelUrl !== c.url)
|
|
112
|
+
throw new CliError("E_SCHEMA", "Message belongs to a different channel", 8);
|
|
113
|
+
return m;
|
|
114
|
+
};
|
|
115
|
+
const b = job.body;
|
|
116
|
+
let preparedOptions;
|
|
117
|
+
const common = () => ({
|
|
118
|
+
...preparedOptions,
|
|
119
|
+
...(b.custom_type !== undefined
|
|
120
|
+
? { customType: b.custom_type }
|
|
121
|
+
: {}),
|
|
122
|
+
...(b.data !== undefined ? { data: b.data } : {}),
|
|
123
|
+
...(b.parent_message_id !== undefined
|
|
124
|
+
? { parentMessageId: b.parent_message_id }
|
|
125
|
+
: {}),
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
checkAbort();
|
|
129
|
+
const who = await sb.connect(job.actor, job.token);
|
|
130
|
+
checkAbort();
|
|
131
|
+
if (who.userId !== job.actor)
|
|
132
|
+
throw new CliError("E_AUTH", "Connected user does not match requested identity", 4);
|
|
133
|
+
preparedOptions = await messageOptions(b);
|
|
134
|
+
checkAbort();
|
|
135
|
+
const proUserResult = await runProUser(job, sb, who, checkAbort);
|
|
136
|
+
if (proUserResult !== undefined)
|
|
137
|
+
return proUserResult;
|
|
138
|
+
if (job.action === "status")
|
|
139
|
+
return { connected: true, app_id: job.appId, user: user(who) };
|
|
140
|
+
if (job.action === "emoji-list") {
|
|
141
|
+
const catalog = await sb.getAllEmoji();
|
|
142
|
+
checkAbort();
|
|
143
|
+
return {
|
|
144
|
+
emoji_hash: catalog.emojiHash,
|
|
145
|
+
emojis: catalog.emojiCategories.flatMap((category) => category.emojis.map((emoji) => ({
|
|
146
|
+
key: emoji.key,
|
|
147
|
+
url: emoji.url,
|
|
148
|
+
category_id: category.id,
|
|
149
|
+
category_name: category.name,
|
|
150
|
+
}))),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (job.action === "my-channels-list") {
|
|
154
|
+
const type = String(b.type ?? "all");
|
|
155
|
+
const memberState = String(b.member_state ?? "all");
|
|
156
|
+
const params = {
|
|
157
|
+
limit: Number(b.limit),
|
|
158
|
+
// Staging can report a received message while the channel list still
|
|
159
|
+
// has last_message=null. Excluding "empty" channels then drops the room.
|
|
160
|
+
includeEmpty: true,
|
|
161
|
+
superChannelFilter: (type === "supergroup"
|
|
162
|
+
? "super"
|
|
163
|
+
: type === "group"
|
|
164
|
+
? "nonsuper"
|
|
165
|
+
: "all"),
|
|
166
|
+
myMemberStateFilter: (memberState === "joined"
|
|
167
|
+
? "joined_only"
|
|
168
|
+
: memberState === "invited"
|
|
169
|
+
? "invited_only"
|
|
170
|
+
: "all"),
|
|
171
|
+
};
|
|
172
|
+
const q = sb.groupChannel.createMyGroupChannelListQuery(params);
|
|
173
|
+
const result = await readQueryPage(q, Number(b.page ?? 1), checkAbort);
|
|
174
|
+
return {
|
|
175
|
+
channels: result.items.map(myGroupChannel),
|
|
176
|
+
has_more: result.has_more,
|
|
177
|
+
page: result.page,
|
|
178
|
+
next_page: result.next_page,
|
|
179
|
+
pagination: result.pagination,
|
|
180
|
+
unread_count_note: "Per-channel SDK counts are unknown when last_message_id is null and may disagree with the aggregate; use chat sdk channel my unread for the total.",
|
|
181
|
+
filters: {
|
|
182
|
+
type,
|
|
183
|
+
member_state: memberState,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (job.action === "my-channels-unread") {
|
|
188
|
+
const type = String(b.type ?? "all");
|
|
189
|
+
return {
|
|
190
|
+
type,
|
|
191
|
+
unread_message_count: await sb.groupChannel.getTotalUnreadMessageCount({
|
|
192
|
+
superChannelFilter: (type === "supergroup"
|
|
193
|
+
? "super"
|
|
194
|
+
: type === "group"
|
|
195
|
+
? "nonsuper"
|
|
196
|
+
: "all"),
|
|
197
|
+
}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (job.action === "invitation-get") {
|
|
201
|
+
const preference = await sb.getChannelInvitationPreference();
|
|
202
|
+
return { auto_accept: preference.autoAccept };
|
|
203
|
+
}
|
|
204
|
+
if (job.action === "invitation-set") {
|
|
205
|
+
const preference = await sb.setChannelInvitationPreference(Boolean(b.auto_accept));
|
|
206
|
+
return { auto_accept: preference.autoAccept };
|
|
207
|
+
}
|
|
208
|
+
if (job.action === "push-get")
|
|
209
|
+
return { option: await sb.getPushTriggerOption() };
|
|
210
|
+
if (job.action === "push-set")
|
|
211
|
+
return {
|
|
212
|
+
option: await sb.setPushTriggerOption(String(b.option)),
|
|
213
|
+
};
|
|
214
|
+
if (job.action === "snooze-get")
|
|
215
|
+
return await sb.getSnoozePeriod();
|
|
216
|
+
if (job.action === "snooze-set") {
|
|
217
|
+
// Some backends reject the SDK's default 0/0 interval even when disabling.
|
|
218
|
+
// Preserve the current valid window instead of retrying a rejected write.
|
|
219
|
+
if (b.enabled === false &&
|
|
220
|
+
b.start_at === undefined &&
|
|
221
|
+
b.end_at === undefined) {
|
|
222
|
+
const previous = await sb.getSnoozePeriod();
|
|
223
|
+
checkAbort();
|
|
224
|
+
if (!previous.isSnoozeOn)
|
|
225
|
+
return previous;
|
|
226
|
+
return await sb.setSnoozePeriod(false, previous.startTs, previous.endTs);
|
|
227
|
+
}
|
|
228
|
+
return await sb.setSnoozePeriod(Boolean(b.enabled), b.start_at, b.end_at);
|
|
229
|
+
}
|
|
230
|
+
c =
|
|
231
|
+
job.params.type === "open"
|
|
232
|
+
? await sb.openChannel.getChannel(String(job.params.id))
|
|
233
|
+
: await sb.groupChannel.getChannel(String(job.params.id));
|
|
234
|
+
checkAbort();
|
|
235
|
+
if (c.url !== job.params.id)
|
|
236
|
+
throw new CliError("E_SCHEMA", "SDK returned a different channel URL", 8);
|
|
237
|
+
if (c.isGroupChannel() && c.isSuper !== (job.params.type === "supergroup"))
|
|
238
|
+
throw new CliError("E_CHANNEL_TYPE", "Channel does not match --type; use group or supergroup explicitly", 2);
|
|
239
|
+
if (c.isOpenChannel()) {
|
|
240
|
+
await c.enter();
|
|
241
|
+
entered = true;
|
|
242
|
+
}
|
|
243
|
+
checkAbort();
|
|
244
|
+
const proChannelResult = await runProChannel(job, sb, c, checkAbort, currentMessage);
|
|
245
|
+
if (proChannelResult !== undefined)
|
|
246
|
+
return proChannelResult;
|
|
247
|
+
switch (job.action) {
|
|
248
|
+
case "send":
|
|
249
|
+
return message(await acknowledged(c.sendUserMessage({ ...common(), message: String(b.message) })));
|
|
250
|
+
case "file": {
|
|
251
|
+
let file;
|
|
252
|
+
if (b.file) {
|
|
253
|
+
const { readFile, stat } = await import("node:fs/promises");
|
|
254
|
+
const { basename } = await import("node:path");
|
|
255
|
+
const info = await stat(String(b.file));
|
|
256
|
+
if (info.size > 25 * 1024 * 1024)
|
|
257
|
+
throw new CliError("E_FILE", "Local file exceeds CLI upload limit (25 MiB)", 2);
|
|
258
|
+
file = new File([await readFile(String(b.file))], String(b.file_name ?? basename(String(b.file))), { type: String(b.mime_type ?? "application/octet-stream") });
|
|
259
|
+
}
|
|
260
|
+
checkAbort();
|
|
261
|
+
return message(await acknowledged(c.sendFileMessage({
|
|
262
|
+
...common(),
|
|
263
|
+
...(file ? { file } : { fileUrl: String(b.file_url) }),
|
|
264
|
+
fileName: b.file_name,
|
|
265
|
+
mimeType: b.mime_type,
|
|
266
|
+
thumbnailSizes: b.thumbnail_sizes?.map((s) => ({ maxWidth: s.width, maxHeight: s.height })),
|
|
267
|
+
})));
|
|
268
|
+
}
|
|
269
|
+
case "get":
|
|
270
|
+
return message(await currentMessage());
|
|
271
|
+
case "list": {
|
|
272
|
+
const params = {
|
|
273
|
+
prevResultSize: Number(b.limit),
|
|
274
|
+
nextResultSize: 0,
|
|
275
|
+
isInclusive: false,
|
|
276
|
+
includeMetaArray: true,
|
|
277
|
+
includeReactions: true,
|
|
278
|
+
includeThreadInfo: true,
|
|
279
|
+
includeParentMessageInfo: true,
|
|
280
|
+
};
|
|
281
|
+
const items = b.before_id !== undefined
|
|
282
|
+
? await c.getMessagesByMessageId(Number(b.before_id), params)
|
|
283
|
+
: await c.getMessagesByTimestamp(Number(b.before ?? Date.now()), params);
|
|
284
|
+
const oldest = [...items].sort((a, z) => a.createdAt - z.createdAt || a.messageId - z.messageId)[0];
|
|
285
|
+
return {
|
|
286
|
+
messages: items.map(message),
|
|
287
|
+
next_before_id: oldest?.messageId ?? null,
|
|
288
|
+
pagination: "Use --before-id for the next older page; do not mix with --before.",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
case "update":
|
|
292
|
+
return message(await c.updateUserMessage(Number(job.params.message_id), {
|
|
293
|
+
...common(),
|
|
294
|
+
message: String(b.message),
|
|
295
|
+
}));
|
|
296
|
+
case "delete":
|
|
297
|
+
await c.deleteMessage(await currentMessage());
|
|
298
|
+
return { deleted: true, message_id: job.params.message_id };
|
|
299
|
+
case "reply": {
|
|
300
|
+
const parent = await currentMessage();
|
|
301
|
+
if (parent.parentMessageId)
|
|
302
|
+
throw new CliError("E_VALIDATION", "Reply using the root parent message ID", 2);
|
|
303
|
+
return message(await acknowledged(c.sendUserMessage({
|
|
304
|
+
message: String(b.message),
|
|
305
|
+
...common(),
|
|
306
|
+
parentMessageId: parent.messageId,
|
|
307
|
+
isReplyToChannel: true,
|
|
308
|
+
})));
|
|
309
|
+
}
|
|
310
|
+
case "thread": {
|
|
311
|
+
const parent = await currentMessage();
|
|
312
|
+
if (!parent.isUserMessage() && !parent.isFileMessage())
|
|
313
|
+
throw new CliError("E_VALIDATION", "Threads require a text or file parent", 2);
|
|
314
|
+
const result = await parent.getThreadedMessagesByTimestamp(Number(b.before ?? Date.now()), {
|
|
315
|
+
prevResultSize: Number(b.limit),
|
|
316
|
+
nextResultSize: 0,
|
|
317
|
+
isInclusive: Boolean(b.inclusive),
|
|
318
|
+
includeMetaArray: true,
|
|
319
|
+
includeReactions: true,
|
|
320
|
+
});
|
|
321
|
+
// A structurally valid SDK response is insufficient: a backend may
|
|
322
|
+
// return a reply as the parent. Do not hide this by substituting IDs.
|
|
323
|
+
if (result.parentMessage.messageId !== parent.messageId ||
|
|
324
|
+
result.parentMessage.channelUrl !== c.url ||
|
|
325
|
+
result.threadedMessages.some((m) => m.parentMessageId !== parent.messageId || m.channelUrl !== c.url))
|
|
326
|
+
throw new CliError("E_SCHEMA", "Thread response does not match the requested parent message", 8, {
|
|
327
|
+
requested_message_id: parent.messageId,
|
|
328
|
+
returned_parent_message_id: result.parentMessage.messageId,
|
|
329
|
+
});
|
|
330
|
+
return {
|
|
331
|
+
parent: message(result.parentMessage),
|
|
332
|
+
messages: result.threadedMessages.map(message),
|
|
333
|
+
next_before: result.threadedMessages.length
|
|
334
|
+
? Math.min(...result.threadedMessages.map((m) => m.createdAt))
|
|
335
|
+
: null,
|
|
336
|
+
pagination: "Inclusive timestamp pages can overlap; deduplicate by message_id. If the boundary does not advance, increase --limit (max 100).",
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
case "reaction-add":
|
|
340
|
+
case "reaction-remove": {
|
|
341
|
+
const m = await currentMessage();
|
|
342
|
+
const result = job.action === "reaction-add"
|
|
343
|
+
? await c.addReaction(m, String(b.key))
|
|
344
|
+
: await c.deleteReaction(m, String(b.key));
|
|
345
|
+
return {
|
|
346
|
+
message_id: result.messageId,
|
|
347
|
+
key: result.key,
|
|
348
|
+
user_id: result.userId,
|
|
349
|
+
operation: result.operation,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
case "receipts": {
|
|
353
|
+
const m = await currentMessage();
|
|
354
|
+
const g = group();
|
|
355
|
+
return {
|
|
356
|
+
message_id: m.messageId,
|
|
357
|
+
unread_count: g.getUnreadMemberCount(m),
|
|
358
|
+
undelivered_count: g.getUndeliveredMemberCount(m),
|
|
359
|
+
read_members: g.getReadMembers(m).map(user),
|
|
360
|
+
unread_members: g.getUnreadMembers(m).map(user),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
case "pin":
|
|
364
|
+
await c.pinMessage(Number(job.params.message_id));
|
|
365
|
+
return { pinned: true, message_id: job.params.message_id };
|
|
366
|
+
case "unpin":
|
|
367
|
+
await c.unpinMessage(Number(job.params.message_id));
|
|
368
|
+
return { pinned: false, message_id: job.params.message_id };
|
|
369
|
+
case "join":
|
|
370
|
+
await group().join(b.access_code);
|
|
371
|
+
return channel(c);
|
|
372
|
+
case "leave": {
|
|
373
|
+
const channelUrl = c.url;
|
|
374
|
+
await group().leave();
|
|
375
|
+
return { left: true, channel_url: channelUrl };
|
|
376
|
+
}
|
|
377
|
+
case "accept":
|
|
378
|
+
await group().acceptInvitation(b.access_code);
|
|
379
|
+
return channel(c);
|
|
380
|
+
case "decline": {
|
|
381
|
+
const channelUrl = c.url;
|
|
382
|
+
const target = group();
|
|
383
|
+
// Admin-originated invitations can have no inviter. SDK 4.22.13's
|
|
384
|
+
// decline-event decoder throws on that null user. This one-shot REST
|
|
385
|
+
// operation needs the authenticated session, not an event stream.
|
|
386
|
+
// The public API preserves currentUser/cache; never swallow errors or
|
|
387
|
+
// retry the mutation to work around the decoder failure.
|
|
388
|
+
await sb.disconnectWebSocket();
|
|
389
|
+
checkAbort();
|
|
390
|
+
await target.declineInvitation();
|
|
391
|
+
return { declined: true, channel_url: channelUrl };
|
|
392
|
+
}
|
|
393
|
+
case "participants": {
|
|
394
|
+
if (!c.isOpenChannel())
|
|
395
|
+
throw new CliError("E_CHANNEL_TYPE", "Open channel required", 2);
|
|
396
|
+
const q = c.createParticipantListQuery({ limit: Number(b.limit) });
|
|
397
|
+
const page = await readQueryPage(q, Number(b.page ?? 1), checkAbort);
|
|
398
|
+
return {
|
|
399
|
+
participants: page.items.map(user),
|
|
400
|
+
has_more: page.has_more,
|
|
401
|
+
page: page.page,
|
|
402
|
+
next_page: page.next_page,
|
|
403
|
+
pagination: page.pagination,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
case "members-list": {
|
|
407
|
+
const q = group().createMemberListQuery({ limit: Number(b.limit) });
|
|
408
|
+
const page = await readQueryPage(q, Number(b.page ?? 1), checkAbort);
|
|
409
|
+
return {
|
|
410
|
+
members: page.items.map(user),
|
|
411
|
+
has_more: page.has_more,
|
|
412
|
+
page: page.page,
|
|
413
|
+
next_page: page.next_page,
|
|
414
|
+
pagination: page.pagination,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
case "members-invite":
|
|
418
|
+
return channel(await group().inviteWithUserIds(b.user_ids));
|
|
419
|
+
case "count-preference-get": {
|
|
420
|
+
const g = group();
|
|
421
|
+
return {
|
|
422
|
+
channel_url: g.url,
|
|
423
|
+
type: g.isSuper ? "supergroup" : "group",
|
|
424
|
+
preference: g.myCountPreference,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
case "count-preference-set": {
|
|
428
|
+
const g = group();
|
|
429
|
+
const preference = await g.setMyCountPreference(String(b.preference));
|
|
430
|
+
return {
|
|
431
|
+
channel_url: g.url,
|
|
432
|
+
type: g.isSuper ? "supergroup" : "group",
|
|
433
|
+
preference,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
case "typing-users": {
|
|
437
|
+
const g = group();
|
|
438
|
+
if (g.isSuper)
|
|
439
|
+
throw new CliError("E_CHANNEL_TYPE", "Typing users are available only in regular Group channels", 2);
|
|
440
|
+
return { channel_url: g.url, users: g.getTypingUsers().map(user) };
|
|
441
|
+
}
|
|
442
|
+
case "counters-get":
|
|
443
|
+
return {
|
|
444
|
+
counters: await c.getMetaCounters(b.keys),
|
|
445
|
+
};
|
|
446
|
+
case "counters-create":
|
|
447
|
+
return {
|
|
448
|
+
counters: await c.createMetaCounters(b.counters),
|
|
449
|
+
};
|
|
450
|
+
case "counters-increase":
|
|
451
|
+
return {
|
|
452
|
+
counters: await c.increaseMetaCounters(b.counters),
|
|
453
|
+
};
|
|
454
|
+
case "counters-decrease":
|
|
455
|
+
return {
|
|
456
|
+
counters: await c.decreaseMetaCounters(b.counters),
|
|
457
|
+
};
|
|
458
|
+
case "pins-list": {
|
|
459
|
+
const q = group().createPinnedMessageListQuery({
|
|
460
|
+
limit: Number(b.limit),
|
|
461
|
+
includeMetaArray: true,
|
|
462
|
+
includeReactions: true,
|
|
463
|
+
includeThreadInfo: true,
|
|
464
|
+
includeParentMessageInfo: true,
|
|
465
|
+
includePollDetails: true,
|
|
466
|
+
});
|
|
467
|
+
const page = await readQueryPage(q, Number(b.page ?? 1), checkAbort);
|
|
468
|
+
return {
|
|
469
|
+
messages: page.items.flatMap((pin) => pin.message ? [message(pin.message)] : []),
|
|
470
|
+
has_more: page.has_more,
|
|
471
|
+
page: page.page,
|
|
472
|
+
next_page: page.next_page,
|
|
473
|
+
pagination: page.pagination,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
case "channel-push-get":
|
|
477
|
+
return { option: await group().getMyPushTriggerOption() };
|
|
478
|
+
case "channel-push-set":
|
|
479
|
+
return {
|
|
480
|
+
option: await group().setMyPushTriggerOption(String(b.option)),
|
|
481
|
+
};
|
|
482
|
+
case "hide":
|
|
483
|
+
return channel(await group().hide({
|
|
484
|
+
hidePreviousMessages: Boolean(b.hide_previous_messages),
|
|
485
|
+
}));
|
|
486
|
+
case "unhide":
|
|
487
|
+
return channel(await group().unhide());
|
|
488
|
+
case "reset-history":
|
|
489
|
+
return channel(await group().resetMyHistory());
|
|
490
|
+
case "scheduled-create":
|
|
491
|
+
return scheduledMessage(await acknowledged(group().createScheduledUserMessage({
|
|
492
|
+
message: String(b.message),
|
|
493
|
+
scheduledAt: Number(b.scheduled_at),
|
|
494
|
+
...common(),
|
|
495
|
+
})));
|
|
496
|
+
case "scheduled-list": {
|
|
497
|
+
const q = sb.groupChannel.createScheduledMessageListQuery({
|
|
498
|
+
channelUrl: c.url,
|
|
499
|
+
limit: Number(b.limit),
|
|
500
|
+
scheduledStatus: b.status && b.status !== "all"
|
|
501
|
+
? [b.status]
|
|
502
|
+
: undefined,
|
|
503
|
+
});
|
|
504
|
+
const page = await readQueryPage(q, Number(b.page ?? 1), checkAbort);
|
|
505
|
+
return {
|
|
506
|
+
messages: page.items.map(scheduledMessage),
|
|
507
|
+
has_more: page.has_more,
|
|
508
|
+
page: page.page,
|
|
509
|
+
next_page: page.next_page,
|
|
510
|
+
status_filter: b.status ?? "all",
|
|
511
|
+
pagination: page.pagination,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
case "scheduled-cancel":
|
|
515
|
+
await group().cancelScheduledMessage(Number(job.params.message_id));
|
|
516
|
+
return { canceled: true, message_id: Number(job.params.message_id) };
|
|
517
|
+
case "scheduled-send-now":
|
|
518
|
+
await group().sendScheduledMessageNow(Number(job.params.message_id));
|
|
519
|
+
return { sent: true, message_id: Number(job.params.message_id) };
|
|
520
|
+
case "translate": {
|
|
521
|
+
const m = await currentMessage();
|
|
522
|
+
if (!m.isUserMessage())
|
|
523
|
+
throw new CliError("E_VALIDATION", "Only user messages can be translated", 2);
|
|
524
|
+
const translated = await c.translateUserMessage(m, b.languages);
|
|
525
|
+
return {
|
|
526
|
+
...message(translated),
|
|
527
|
+
translations: translated.translations,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
case "message-changes":
|
|
531
|
+
return await c.getMessageChangeLogsSinceTimestamp(Number(b.since));
|
|
532
|
+
case "poll-list": {
|
|
533
|
+
const q = group().createPollListQuery(Number(b.limit));
|
|
534
|
+
const page = await readQueryPage(q, Number(b.page ?? 1), checkAbort);
|
|
535
|
+
return {
|
|
536
|
+
polls: page.items.map((poll) => ({ poll_id: poll.id, ...poll })),
|
|
537
|
+
has_more: page.has_more,
|
|
538
|
+
page: page.page,
|
|
539
|
+
next_page: page.next_page,
|
|
540
|
+
pagination: page.pagination,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
case "poll-vote":
|
|
544
|
+
return await group().votePoll(Number(b.poll_id), b.option_ids);
|
|
545
|
+
case "poll-option-add":
|
|
546
|
+
return await group().addPollOption(Number(b.poll_id), String(b.option));
|
|
547
|
+
case "operator-add":
|
|
548
|
+
await c.addOperators(b.user_ids);
|
|
549
|
+
return { updated: true };
|
|
550
|
+
case "operator-remove":
|
|
551
|
+
await c.removeOperators(b.user_ids);
|
|
552
|
+
return { updated: true };
|
|
553
|
+
case "mute":
|
|
554
|
+
if (b.user_id)
|
|
555
|
+
await c.muteUserWithUserId(String(b.user_id), b.seconds, b.description);
|
|
556
|
+
return { updated: true };
|
|
557
|
+
case "unmute":
|
|
558
|
+
await c.unmuteUserWithUserId(String(b.user_id));
|
|
559
|
+
return { updated: true };
|
|
560
|
+
case "ban":
|
|
561
|
+
await c.banUserWithUserId(String(b.user_id), b.seconds, b.description);
|
|
562
|
+
return { updated: true };
|
|
563
|
+
case "unban":
|
|
564
|
+
await c.unbanUserWithUserId(String(b.user_id));
|
|
565
|
+
return { updated: true };
|
|
566
|
+
case "freeze":
|
|
567
|
+
if (b.frozen)
|
|
568
|
+
await c.freeze();
|
|
569
|
+
else
|
|
570
|
+
await c.unfreeze();
|
|
571
|
+
return { frozen: Boolean(b.frozen) };
|
|
572
|
+
case "read":
|
|
573
|
+
await group().markAsRead();
|
|
574
|
+
return { read: true, channel_url: c.url };
|
|
575
|
+
case "typing":
|
|
576
|
+
typing = true;
|
|
577
|
+
await group().startTyping();
|
|
578
|
+
await new Promise((r) => {
|
|
579
|
+
const timer = setTimeout(done, Number(b.seconds) * 1000);
|
|
580
|
+
function done() {
|
|
581
|
+
clearTimeout(timer);
|
|
582
|
+
signal?.removeEventListener("abort", done);
|
|
583
|
+
r();
|
|
584
|
+
}
|
|
585
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
586
|
+
if (signal?.aborted)
|
|
587
|
+
done();
|
|
588
|
+
});
|
|
589
|
+
await group().endTyping();
|
|
590
|
+
typing = false;
|
|
591
|
+
return { typing: false, channel_url: c.url };
|
|
592
|
+
case "watch": {
|
|
593
|
+
const [{ GroupChannelHandler }, { OpenChannelHandler }, { ConnectionHandler },] = await Promise.all([
|
|
594
|
+
import("@sendbird/chat/groupChannel"),
|
|
595
|
+
import("@sendbird/chat/openChannel"),
|
|
596
|
+
import("@sendbird/chat"),
|
|
597
|
+
]);
|
|
598
|
+
let count = 0;
|
|
599
|
+
let finish;
|
|
600
|
+
let reason = "duration";
|
|
601
|
+
let ended = false;
|
|
602
|
+
const send = (event, ch, payload) => {
|
|
603
|
+
if (ended || count >= Number(b.max_events) || ch.url !== c.url)
|
|
604
|
+
return;
|
|
605
|
+
emit({ event, channel_url: ch.url, data: payload });
|
|
606
|
+
if (++count >= Number(b.max_events))
|
|
607
|
+
finish?.("event_limit");
|
|
608
|
+
};
|
|
609
|
+
const base = {
|
|
610
|
+
onMessageReceived: (ch, m) => send("message_received", ch, message(m)),
|
|
611
|
+
onMessageUpdated: (ch, m) => send("message_updated", ch, message(m)),
|
|
612
|
+
onMessageDeleted: (ch, id) => send("message_deleted", ch, { message_id: id }),
|
|
613
|
+
};
|
|
614
|
+
handlerKey = "sendbird-cli-watch";
|
|
615
|
+
const connectionLost = () => {
|
|
616
|
+
reason = "connection_lost";
|
|
617
|
+
finish?.(reason);
|
|
618
|
+
};
|
|
619
|
+
sb.addConnectionHandler(handlerKey, new ConnectionHandler({
|
|
620
|
+
onDisconnected: connectionLost,
|
|
621
|
+
onConnectionLost: connectionLost,
|
|
622
|
+
onReconnectFailed: connectionLost,
|
|
623
|
+
}));
|
|
624
|
+
if (c.isOpenChannel())
|
|
625
|
+
sb.openChannel.addOpenChannelHandler(handlerKey, new OpenChannelHandler({
|
|
626
|
+
...base,
|
|
627
|
+
onUserEntered: (ch, u) => send("user_entered", ch, user(u)),
|
|
628
|
+
onUserExited: (ch, u) => send("user_exited", ch, user(u)),
|
|
629
|
+
}));
|
|
630
|
+
else
|
|
631
|
+
sb.groupChannel.addGroupChannelHandler(handlerKey, new GroupChannelHandler({
|
|
632
|
+
...base,
|
|
633
|
+
onTypingStatusUpdated: (ch) => send("typing", ch, { users: ch.getTypingUsers().map(user) }),
|
|
634
|
+
onUnreadMemberStatusUpdated: (ch) => send("read_receipt", ch, { updated: true }),
|
|
635
|
+
onUndeliveredMemberStatusUpdated: (ch) => send("delivery_receipt", ch, { updated: true }),
|
|
636
|
+
onUserJoined: (ch, u) => send("user_joined", ch, user(u)),
|
|
637
|
+
onUserLeft: (ch, u) => send("user_left", ch, user(u)),
|
|
638
|
+
onReactionUpdated: (ch, e) => send("reaction", ch, {
|
|
639
|
+
message_id: e.messageId,
|
|
640
|
+
key: e.key,
|
|
641
|
+
user_id: e.userId,
|
|
642
|
+
operation: e.operation,
|
|
643
|
+
}),
|
|
644
|
+
}));
|
|
645
|
+
emit({ event: "ready", channel: channel(c), user_id: job.actor });
|
|
646
|
+
await new Promise((resolve) => {
|
|
647
|
+
const timer = setTimeout(() => finish?.("duration"), Number(b.seconds) * 1000);
|
|
648
|
+
const abort = () => finish?.("interrupted");
|
|
649
|
+
finish = (why) => {
|
|
650
|
+
ended = true;
|
|
651
|
+
reason = why;
|
|
652
|
+
clearTimeout(timer);
|
|
653
|
+
signal?.removeEventListener("abort", abort);
|
|
654
|
+
resolve();
|
|
655
|
+
};
|
|
656
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
657
|
+
if (signal?.aborted)
|
|
658
|
+
abort();
|
|
659
|
+
if (reason === "connection_lost")
|
|
660
|
+
finish(reason);
|
|
661
|
+
else if (count >= Number(b.max_events))
|
|
662
|
+
finish("event_limit");
|
|
663
|
+
});
|
|
664
|
+
if (reason === "connection_lost")
|
|
665
|
+
throw new CliError("E_NETWORK", "SDK watch lost its connection; events may have been missed", 7);
|
|
666
|
+
return { event: "complete", reason, events: count };
|
|
667
|
+
}
|
|
668
|
+
default:
|
|
669
|
+
throw new CliError("E_COMMAND", "Unsupported SDK operation", 2);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
finally {
|
|
673
|
+
if (handlerKey) {
|
|
674
|
+
sb.removeConnectionHandler(handlerKey);
|
|
675
|
+
sb.groupChannel.removeGroupChannelHandler(handlerKey);
|
|
676
|
+
sb.openChannel.removeOpenChannelHandler(handlerKey);
|
|
677
|
+
}
|
|
678
|
+
if (typing && c?.isGroupChannel())
|
|
679
|
+
await Promise.race([c.endTyping().catch(() => { }), delay(1000)]);
|
|
680
|
+
if (entered && c?.isOpenChannel())
|
|
681
|
+
await Promise.race([c.exit().catch(() => { }), delay(1000)]);
|
|
682
|
+
await Promise.race([sb.disconnect().catch(() => { }), delay(1500)]);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
//# sourceMappingURL=sdk-runtime.js.map
|