shardwire 1.2.0 → 1.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/README.md +117 -44
- package/dist/index.d.mts +133 -7
- package/dist/index.d.ts +133 -7
- package/dist/index.js +508 -92
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +509 -92
- package/dist/index.mjs.map +1 -1
- package/package.json +82 -75
package/README.md
CHANGED
|
@@ -30,43 +30,49 @@ Shardwire is built for a common architecture: your Discord bot runs in one proce
|
|
|
30
30
|
npm install shardwire
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## Documentation
|
|
34
|
+
|
|
35
|
+
- [Deployment (TLS, nginx, limits, shutdown)](./docs/deployment.md)
|
|
36
|
+
- [Troubleshooting (auth, capabilities, action errors)](./docs/troubleshooting.md)
|
|
37
|
+
- [Patterns (moderation, interactions, multi-secret)](./docs/patterns.md)
|
|
38
|
+
|
|
33
39
|
## Quick Start
|
|
34
40
|
|
|
35
41
|
### 1) Start the bot bridge process
|
|
36
42
|
|
|
37
43
|
```ts
|
|
38
|
-
import { createBotBridge } from
|
|
44
|
+
import { createBotBridge } from 'shardwire';
|
|
39
45
|
|
|
40
46
|
const bridge = createBotBridge({
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
token: process.env.DISCORD_TOKEN!,
|
|
48
|
+
intents: ['Guilds', 'GuildMessages', 'GuildMessageReactions', 'MessageContent', 'GuildMembers'],
|
|
49
|
+
server: {
|
|
50
|
+
port: 3001,
|
|
51
|
+
secrets: [process.env.SHARDWIRE_SECRET!],
|
|
52
|
+
},
|
|
47
53
|
});
|
|
48
54
|
|
|
49
55
|
await bridge.ready();
|
|
50
|
-
console.log(
|
|
56
|
+
console.log('Bot bridge listening on ws://127.0.0.1:3001/shardwire');
|
|
51
57
|
```
|
|
52
58
|
|
|
53
59
|
### 2) Connect from your app process
|
|
54
60
|
|
|
55
61
|
```ts
|
|
56
|
-
import { connectBotBridge } from
|
|
62
|
+
import { connectBotBridge } from 'shardwire';
|
|
57
63
|
|
|
58
64
|
const app = connectBotBridge({
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
url: 'ws://127.0.0.1:3001/shardwire',
|
|
66
|
+
secret: process.env.SHARDWIRE_SECRET!,
|
|
67
|
+
appName: 'dashboard',
|
|
62
68
|
});
|
|
63
69
|
|
|
64
|
-
app.on(
|
|
65
|
-
|
|
70
|
+
app.on('ready', ({ user }) => {
|
|
71
|
+
console.log('Bot ready as', user.username);
|
|
66
72
|
});
|
|
67
73
|
|
|
68
|
-
app.on(
|
|
69
|
-
|
|
74
|
+
app.on('messageCreate', ({ message }) => {
|
|
75
|
+
console.log(message.channelId, message.content);
|
|
70
76
|
});
|
|
71
77
|
|
|
72
78
|
await app.ready();
|
|
@@ -76,12 +82,12 @@ await app.ready();
|
|
|
76
82
|
|
|
77
83
|
```ts
|
|
78
84
|
const result = await app.actions.sendMessage({
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
channelId: '123456789012345678',
|
|
86
|
+
content: 'Hello from app side',
|
|
81
87
|
});
|
|
82
88
|
|
|
83
89
|
if (!result.ok) {
|
|
84
|
-
|
|
90
|
+
console.error(result.error.code, result.error.message);
|
|
85
91
|
}
|
|
86
92
|
```
|
|
87
93
|
|
|
@@ -89,14 +95,20 @@ if (!result.ok) {
|
|
|
89
95
|
|
|
90
96
|
```ts
|
|
91
97
|
app.on(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
'messageCreate',
|
|
99
|
+
({ message }) => {
|
|
100
|
+
console.log('Only this channel:', message.content);
|
|
101
|
+
},
|
|
102
|
+
{ channelId: '123456789012345678' },
|
|
97
103
|
);
|
|
98
104
|
```
|
|
99
105
|
|
|
106
|
+
## Startup lifecycle
|
|
107
|
+
|
|
108
|
+
- **`createBotBridge(...)`** starts the WebSocket server immediately; `await bridge.ready()` resolves when the Discord client has finished its initial `ready` handshake (same timing you would expect from a normal bot login).
|
|
109
|
+
- **Register `app.on(...)` handlers before `await app.ready()`** so subscriptions are known when the app authenticates. `ready()` connects, completes auth, negotiates capabilities from intents + secret scope, then throws `BridgeCapabilityError` if any registered handler targets an event the app is not allowed to receive.
|
|
110
|
+
- **Sticky `ready`**: if the bot was already ready before the app connected, the bridge replays the latest `ready` payload to matching subscriptions after auth.
|
|
111
|
+
|
|
100
112
|
## Built-In Events
|
|
101
113
|
|
|
102
114
|
Apps subscribe to events with `app.on(...)`. The bridge forwards only what each app subscribes to.
|
|
@@ -106,6 +118,7 @@ Apps subscribe to events with `app.on(...)`. The bridge forwards only what each
|
|
|
106
118
|
- `messageCreate`
|
|
107
119
|
- `messageUpdate`
|
|
108
120
|
- `messageDelete`
|
|
121
|
+
- `messageBulkDelete`
|
|
109
122
|
- `messageReactionAdd`
|
|
110
123
|
- `messageReactionRemove`
|
|
111
124
|
- `guildCreate`
|
|
@@ -116,6 +129,9 @@ Apps subscribe to events with `app.on(...)`. The bridge forwards only what each
|
|
|
116
129
|
- `threadCreate`
|
|
117
130
|
- `threadUpdate`
|
|
118
131
|
- `threadDelete`
|
|
132
|
+
- `channelCreate`
|
|
133
|
+
- `channelUpdate`
|
|
134
|
+
- `channelDelete`
|
|
119
135
|
|
|
120
136
|
Supported filters:
|
|
121
137
|
|
|
@@ -125,13 +141,16 @@ Supported filters:
|
|
|
125
141
|
- `commandName` (for `interactionCreate`)
|
|
126
142
|
- `customId` (for `interactionCreate`)
|
|
127
143
|
- `interactionKind` (for `interactionCreate`)
|
|
144
|
+
- `channelType` (Discord `ChannelType` when present on the payload, for example `messageCreate` / `messageBulkDelete`)
|
|
145
|
+
- `parentChannelId` (category parent, forum/text parent for threads, or thread parent when serialized)
|
|
146
|
+
- `threadId` (guild thread channels only: matches thread channel ids)
|
|
128
147
|
|
|
129
148
|
### Intent Notes
|
|
130
149
|
|
|
131
150
|
- `ready` and `interactionCreate`: no specific event intent requirement
|
|
132
|
-
- `messageCreate`, `messageUpdate`, `messageDelete`: `GuildMessages`
|
|
151
|
+
- `messageCreate`, `messageUpdate`, `messageDelete`, `messageBulkDelete`: `GuildMessages`
|
|
133
152
|
- `messageReactionAdd`, `messageReactionRemove`: `GuildMessageReactions`
|
|
134
|
-
- `guildCreate`, `guildDelete`, `threadCreate`, `threadUpdate`, `threadDelete`: `Guilds`
|
|
153
|
+
- `guildCreate`, `guildDelete`, `threadCreate`, `threadUpdate`, `threadDelete`, `channelCreate`, `channelUpdate`, `channelDelete`: `Guilds`
|
|
135
154
|
- `guildMemberAdd`, `guildMemberRemove`, `guildMemberUpdate`: `GuildMembers`
|
|
136
155
|
|
|
137
156
|
## Built-In Actions
|
|
@@ -157,40 +176,64 @@ Supported filters:
|
|
|
157
176
|
- `removeMemberRole`
|
|
158
177
|
- `addMessageReaction`
|
|
159
178
|
- `removeOwnMessageReaction`
|
|
179
|
+
- `timeoutMember`
|
|
180
|
+
- `removeMemberTimeout`
|
|
181
|
+
- `createChannel`
|
|
182
|
+
- `editChannel`
|
|
183
|
+
- `deleteChannel`
|
|
184
|
+
- `createThread`
|
|
185
|
+
- `archiveThread`
|
|
160
186
|
|
|
161
187
|
All actions return:
|
|
162
188
|
|
|
163
189
|
```ts
|
|
164
190
|
type ActionResult<T> =
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
| { ok: true; requestId: string; ts: number; data: T }
|
|
192
|
+
| { ok: false; requestId: string; ts: number; error: { code: string; message: string; details?: unknown } };
|
|
167
193
|
```
|
|
168
194
|
|
|
169
195
|
### Idempotency for safe retries
|
|
170
196
|
|
|
171
|
-
You can provide an `idempotencyKey` in action options
|
|
197
|
+
You can provide an `idempotencyKey` in action options so repeated calls return the first result while the server-side entry is still valid (default TTL **120s**).
|
|
198
|
+
|
|
199
|
+
- **Default scope (`connection`)**: dedupe is per WebSocket connection (a reconnect is a new connection and does not replay prior keys).
|
|
200
|
+
- **Optional scope (`secret`)**: set `server.idempotencyScope: "secret"` on the bot bridge to dedupe by configured secret id across connections (useful when the app reconnects and retries the same logical operation).
|
|
172
201
|
|
|
173
202
|
```ts
|
|
174
203
|
await app.actions.sendMessage(
|
|
175
|
-
|
|
176
|
-
|
|
204
|
+
{ channelId: '123456789012345678', content: 'Hello once' },
|
|
205
|
+
{ idempotencyKey: 'notify:order:123' },
|
|
177
206
|
);
|
|
178
207
|
```
|
|
179
208
|
|
|
209
|
+
Tune limits on the bot bridge when needed:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
server: {
|
|
213
|
+
port: 3001,
|
|
214
|
+
secrets: [process.env.SHARDWIRE_SECRET!],
|
|
215
|
+
maxPayloadBytes: 65536, // per-frame JSON limit (default 65536)
|
|
216
|
+
idempotencyScope: "secret",
|
|
217
|
+
idempotencyTtlMs: 120_000,
|
|
218
|
+
},
|
|
219
|
+
```
|
|
220
|
+
|
|
180
221
|
### App-side action metrics
|
|
181
222
|
|
|
182
223
|
```ts
|
|
183
224
|
const app = connectBotBridge({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
225
|
+
url: 'ws://127.0.0.1:3001/shardwire',
|
|
226
|
+
secret: process.env.SHARDWIRE_SECRET!,
|
|
227
|
+
metrics: {
|
|
228
|
+
onActionComplete(meta) {
|
|
229
|
+
console.log(meta.name, meta.durationMs, meta.ok, meta.errorCode, meta.discordStatus, meta.retryAfterMs);
|
|
230
|
+
},
|
|
231
|
+
},
|
|
191
232
|
});
|
|
192
233
|
```
|
|
193
234
|
|
|
235
|
+
On Discord **429** responses, failed actions surface `SERVICE_UNAVAILABLE` with `details.retryAfterMs` (when Discord provides `retry_after`) and the metrics hook receives `retryAfterMs` / `discordStatus` for backoff and dashboards.
|
|
236
|
+
|
|
194
237
|
## Secret Scopes
|
|
195
238
|
|
|
196
239
|
Use a plain string secret for full event/action access:
|
|
@@ -229,7 +272,7 @@ console.log(capabilities.events, capabilities.actions);
|
|
|
229
272
|
|
|
230
273
|
## Run the Included Examples
|
|
231
274
|
|
|
232
|
-
|
|
275
|
+
### Minimal (single shared secret)
|
|
233
276
|
|
|
234
277
|
```bash
|
|
235
278
|
# terminal 1
|
|
@@ -241,15 +284,42 @@ DISCORD_TOKEN=your-token SHARDWIRE_SECRET=dev-secret npm run example:bot
|
|
|
241
284
|
SHARDWIRE_SECRET=dev-secret npm run example:app
|
|
242
285
|
```
|
|
243
286
|
|
|
244
|
-
|
|
287
|
+
- Bot bridge: [`examples/bot-basic.ts`](./examples/bot-basic.ts)
|
|
288
|
+
- App client: [`examples/app-basic.ts`](./examples/app-basic.ts)
|
|
245
289
|
|
|
246
|
-
-
|
|
247
|
-
|
|
290
|
+
### Production-style (scoped secrets + moderation + interactions)
|
|
291
|
+
|
|
292
|
+
Use **two different** secret strings (bridge rejects duplicate values across scoped entries).
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# terminal 1 — bot with dashboard + moderation scopes
|
|
296
|
+
DISCORD_TOKEN=your-token \
|
|
297
|
+
SHARDWIRE_SECRET_DASHBOARD=dashboard-secret \
|
|
298
|
+
SHARDWIRE_SECRET_MODERATION=moderation-secret \
|
|
299
|
+
npm run example:bot:prod
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# terminal 2 — delete messages that contain the demo trigger (optional channel filter)
|
|
304
|
+
SHARDWIRE_SECRET_MODERATION=moderation-secret \
|
|
305
|
+
MOD_ALERT_CHANNEL_ID=123456789012345678 \
|
|
306
|
+
npm run example:app:moderation
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# terminal 3 — defer + edit reply for `customId: shardwire.demo.btn` buttons
|
|
311
|
+
SHARDWIRE_SECRET_MODERATION=moderation-secret \
|
|
312
|
+
npm run example:app:interaction
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- [`examples/bot-production.ts`](./examples/bot-production.ts)
|
|
316
|
+
- [`examples/app-moderation.ts`](./examples/app-moderation.ts)
|
|
317
|
+
- [`examples/app-interaction.ts`](./examples/app-interaction.ts)
|
|
248
318
|
|
|
249
319
|
## Public API
|
|
250
320
|
|
|
251
321
|
```ts
|
|
252
|
-
import { createBotBridge, connectBotBridge } from
|
|
322
|
+
import { createBotBridge, connectBotBridge } from 'shardwire';
|
|
253
323
|
```
|
|
254
324
|
|
|
255
325
|
Main exports include:
|
|
@@ -266,7 +336,10 @@ Main exports include:
|
|
|
266
336
|
- Use `wss://` for non-loopback deployments.
|
|
267
337
|
- `ws://` is only accepted for loopback hosts (`127.0.0.1`, `localhost`, `::1`).
|
|
268
338
|
- Event availability depends on enabled intents and secret scope.
|
|
269
|
-
|
|
339
|
+
|
|
340
|
+
**Reporting vulnerabilities:** email [cored.developments@gmail.com](mailto:cored.developments@gmail.com) with enough detail to reproduce the issue (versions, config shape, and steps). Do not open a public issue for undisclosed security problems.
|
|
341
|
+
|
|
342
|
+
**Rotating bridge secrets without downtime:** configure **two** entries in `server.secrets` (old + new values), roll the app env to the new secret, then remove the old entry on the next deploy. Scoped secrets should keep **distinct** `id` values so apps can pin `secretId` during rotation.
|
|
270
343
|
|
|
271
344
|
## Contributing
|
|
272
345
|
|
package/dist/index.d.mts
CHANGED
|
@@ -59,6 +59,14 @@ interface BridgeThread {
|
|
|
59
59
|
archived?: boolean;
|
|
60
60
|
locked?: boolean;
|
|
61
61
|
}
|
|
62
|
+
/** Normalized non-thread channel snapshot for channel lifecycle events. */
|
|
63
|
+
interface BridgeChannel {
|
|
64
|
+
id: Snowflake;
|
|
65
|
+
type: number;
|
|
66
|
+
name?: string | null;
|
|
67
|
+
guildId?: Snowflake;
|
|
68
|
+
parentId?: Snowflake | null;
|
|
69
|
+
}
|
|
62
70
|
interface BridgeMessage {
|
|
63
71
|
id: Snowflake;
|
|
64
72
|
channelId: Snowflake;
|
|
@@ -73,12 +81,18 @@ interface BridgeMessage {
|
|
|
73
81
|
/** Message component rows (JSON-serializable API shape). */
|
|
74
82
|
components?: APIActionRowComponent<APIComponentInMessageActionRow>[];
|
|
75
83
|
reference?: BridgeMessageReference;
|
|
84
|
+
/** Discord `ChannelType` when the runtime could resolve `message.channel`. */
|
|
85
|
+
channelType?: number;
|
|
86
|
+
/** Parent category (guild text channels) or parent text/forum (threads), when known. */
|
|
87
|
+
parentChannelId?: Snowflake;
|
|
76
88
|
}
|
|
77
89
|
interface BridgeDeletedMessage {
|
|
78
90
|
id: Snowflake;
|
|
79
91
|
channelId: Snowflake;
|
|
80
92
|
guildId?: Snowflake;
|
|
81
93
|
deletedAt: string;
|
|
94
|
+
channelType?: number;
|
|
95
|
+
parentChannelId?: Snowflake;
|
|
82
96
|
}
|
|
83
97
|
interface BridgeReactionEmoji {
|
|
84
98
|
id?: Snowflake;
|
|
@@ -92,13 +106,17 @@ interface BridgeMessageReaction {
|
|
|
92
106
|
user?: BridgeUser;
|
|
93
107
|
emoji: BridgeReactionEmoji;
|
|
94
108
|
}
|
|
95
|
-
type BridgeInteractionKind =
|
|
109
|
+
type BridgeInteractionKind = 'chatInput' | 'contextMenu' | 'button' | 'stringSelect' | 'userSelect' | 'roleSelect' | 'mentionableSelect' | 'channelSelect' | 'modalSubmit' | 'unknown';
|
|
96
110
|
interface BridgeInteraction {
|
|
97
111
|
id: Snowflake;
|
|
98
112
|
applicationId: Snowflake;
|
|
99
113
|
kind: BridgeInteractionKind;
|
|
100
114
|
guildId?: Snowflake;
|
|
101
115
|
channelId?: Snowflake;
|
|
116
|
+
/** Discord `ChannelType` for `channelId` when the runtime resolved the channel. */
|
|
117
|
+
channelType?: number;
|
|
118
|
+
/** Parent category or parent text/forum for thread channels, when known. */
|
|
119
|
+
parentChannelId?: Snowflake;
|
|
102
120
|
user: BridgeUser;
|
|
103
121
|
member?: BridgeGuildMember;
|
|
104
122
|
commandName?: string;
|
|
@@ -161,12 +179,32 @@ interface MessageReactionAddEventPayload extends EventEnvelopeBase {
|
|
|
161
179
|
interface MessageReactionRemoveEventPayload extends EventEnvelopeBase {
|
|
162
180
|
reaction: BridgeMessageReaction;
|
|
163
181
|
}
|
|
182
|
+
interface ChannelCreateEventPayload extends EventEnvelopeBase {
|
|
183
|
+
channel: BridgeChannel;
|
|
184
|
+
}
|
|
185
|
+
interface ChannelUpdateEventPayload extends EventEnvelopeBase {
|
|
186
|
+
oldChannel?: BridgeChannel;
|
|
187
|
+
channel: BridgeChannel;
|
|
188
|
+
}
|
|
189
|
+
interface ChannelDeleteEventPayload extends EventEnvelopeBase {
|
|
190
|
+
channel: BridgeChannel;
|
|
191
|
+
}
|
|
192
|
+
interface MessageBulkDeleteEventPayload extends EventEnvelopeBase {
|
|
193
|
+
channelId: Snowflake;
|
|
194
|
+
guildId: Snowflake;
|
|
195
|
+
messageIds: Snowflake[];
|
|
196
|
+
/** Discord `ChannelType` for `channelId` when known. */
|
|
197
|
+
channelType?: number;
|
|
198
|
+
/** Parent category or forum/text parent when the channel reports one. */
|
|
199
|
+
parentChannelId?: Snowflake;
|
|
200
|
+
}
|
|
164
201
|
interface BotEventPayloadMap {
|
|
165
202
|
ready: ReadyEventPayload;
|
|
166
203
|
interactionCreate: InteractionCreateEventPayload;
|
|
167
204
|
messageCreate: MessageCreateEventPayload;
|
|
168
205
|
messageUpdate: MessageUpdateEventPayload;
|
|
169
206
|
messageDelete: MessageDeleteEventPayload;
|
|
207
|
+
messageBulkDelete: MessageBulkDeleteEventPayload;
|
|
170
208
|
messageReactionAdd: MessageReactionAddEventPayload;
|
|
171
209
|
messageReactionRemove: MessageReactionRemoveEventPayload;
|
|
172
210
|
guildCreate: GuildCreateEventPayload;
|
|
@@ -177,6 +215,9 @@ interface BotEventPayloadMap {
|
|
|
177
215
|
threadCreate: ThreadCreateEventPayload;
|
|
178
216
|
threadUpdate: ThreadUpdateEventPayload;
|
|
179
217
|
threadDelete: ThreadDeleteEventPayload;
|
|
218
|
+
channelCreate: ChannelCreateEventPayload;
|
|
219
|
+
channelUpdate: ChannelUpdateEventPayload;
|
|
220
|
+
channelDelete: ChannelDeleteEventPayload;
|
|
180
221
|
}
|
|
181
222
|
type BotEventName = keyof BotEventPayloadMap;
|
|
182
223
|
interface BridgeMessageInput {
|
|
@@ -270,6 +311,53 @@ interface RemoveOwnMessageReactionActionPayload {
|
|
|
270
311
|
messageId: Snowflake;
|
|
271
312
|
emoji: string;
|
|
272
313
|
}
|
|
314
|
+
interface TimeoutMemberActionPayload {
|
|
315
|
+
guildId: Snowflake;
|
|
316
|
+
userId: Snowflake;
|
|
317
|
+
/** Duration in milliseconds (Discord allows up to 28 days). */
|
|
318
|
+
durationMs: number;
|
|
319
|
+
reason?: string;
|
|
320
|
+
}
|
|
321
|
+
interface RemoveMemberTimeoutActionPayload {
|
|
322
|
+
guildId: Snowflake;
|
|
323
|
+
userId: Snowflake;
|
|
324
|
+
reason?: string;
|
|
325
|
+
}
|
|
326
|
+
interface CreateChannelActionPayload {
|
|
327
|
+
guildId: Snowflake;
|
|
328
|
+
name: string;
|
|
329
|
+
/** Discord `ChannelType` (default: `0` guild text). */
|
|
330
|
+
type?: number;
|
|
331
|
+
parentId?: Snowflake;
|
|
332
|
+
topic?: string;
|
|
333
|
+
reason?: string;
|
|
334
|
+
}
|
|
335
|
+
interface EditChannelActionPayload {
|
|
336
|
+
channelId: Snowflake;
|
|
337
|
+
name?: string | null;
|
|
338
|
+
parentId?: Snowflake | null;
|
|
339
|
+
topic?: string | null;
|
|
340
|
+
reason?: string;
|
|
341
|
+
}
|
|
342
|
+
interface DeleteChannelActionPayload {
|
|
343
|
+
channelId: Snowflake;
|
|
344
|
+
reason?: string;
|
|
345
|
+
}
|
|
346
|
+
/** `autoArchiveDuration` is in minutes (Discord-supported values). */
|
|
347
|
+
interface CreateThreadActionPayload {
|
|
348
|
+
parentChannelId: Snowflake;
|
|
349
|
+
name: string;
|
|
350
|
+
/** When set, starts a thread on this message (requires a text-based parent). */
|
|
351
|
+
messageId?: Snowflake;
|
|
352
|
+
type?: 'public' | 'private';
|
|
353
|
+
autoArchiveDuration?: 60 | 1440 | 4320 | 10080;
|
|
354
|
+
reason?: string;
|
|
355
|
+
}
|
|
356
|
+
interface ArchiveThreadActionPayload {
|
|
357
|
+
threadId: Snowflake;
|
|
358
|
+
archived?: boolean;
|
|
359
|
+
reason?: string;
|
|
360
|
+
}
|
|
273
361
|
interface BotActionPayloadMap {
|
|
274
362
|
sendMessage: SendMessageActionPayload;
|
|
275
363
|
editMessage: EditMessageActionPayload;
|
|
@@ -290,6 +378,13 @@ interface BotActionPayloadMap {
|
|
|
290
378
|
removeMemberRole: RemoveMemberRoleActionPayload;
|
|
291
379
|
addMessageReaction: AddMessageReactionActionPayload;
|
|
292
380
|
removeOwnMessageReaction: RemoveOwnMessageReactionActionPayload;
|
|
381
|
+
timeoutMember: TimeoutMemberActionPayload;
|
|
382
|
+
removeMemberTimeout: RemoveMemberTimeoutActionPayload;
|
|
383
|
+
createChannel: CreateChannelActionPayload;
|
|
384
|
+
editChannel: EditChannelActionPayload;
|
|
385
|
+
deleteChannel: DeleteChannelActionPayload;
|
|
386
|
+
createThread: CreateThreadActionPayload;
|
|
387
|
+
archiveThread: ArchiveThreadActionPayload;
|
|
293
388
|
}
|
|
294
389
|
interface DeleteMessageActionResult {
|
|
295
390
|
deleted: true;
|
|
@@ -321,6 +416,10 @@ interface MessageReactionActionResult {
|
|
|
321
416
|
channelId: Snowflake;
|
|
322
417
|
emoji: string;
|
|
323
418
|
}
|
|
419
|
+
interface DeleteChannelActionResult {
|
|
420
|
+
deleted: true;
|
|
421
|
+
channelId: Snowflake;
|
|
422
|
+
}
|
|
324
423
|
interface BotActionResultDataMap {
|
|
325
424
|
sendMessage: BridgeMessage;
|
|
326
425
|
editMessage: BridgeMessage;
|
|
@@ -341,6 +440,13 @@ interface BotActionResultDataMap {
|
|
|
341
440
|
removeMemberRole: BridgeGuildMember;
|
|
342
441
|
addMessageReaction: MessageReactionActionResult;
|
|
343
442
|
removeOwnMessageReaction: MessageReactionActionResult;
|
|
443
|
+
timeoutMember: MemberModerationActionResult;
|
|
444
|
+
removeMemberTimeout: BridgeGuildMember;
|
|
445
|
+
createChannel: BridgeChannel;
|
|
446
|
+
editChannel: BridgeChannel;
|
|
447
|
+
deleteChannel: DeleteChannelActionResult;
|
|
448
|
+
createThread: BridgeThread;
|
|
449
|
+
archiveThread: BridgeThread;
|
|
344
450
|
}
|
|
345
451
|
type BotActionName = keyof BotActionPayloadMap;
|
|
346
452
|
interface BridgeCapabilities {
|
|
@@ -356,14 +462,20 @@ interface EventSubscriptionFilter {
|
|
|
356
462
|
customId?: string | readonly string[];
|
|
357
463
|
/** Matches `BridgeInteraction.kind`. */
|
|
358
464
|
interactionKind?: BridgeInteractionKind | readonly BridgeInteractionKind[];
|
|
465
|
+
/** Matches Discord `ChannelType` when present on the payload (messages, interactions, bulk delete). */
|
|
466
|
+
channelType?: number | readonly number[];
|
|
467
|
+
/** Matches `BridgeMessage.parentChannelId` / thread parent / channel parent when present. */
|
|
468
|
+
parentChannelId?: Snowflake | readonly Snowflake[];
|
|
469
|
+
/** Matches guild thread channels only: same as message `channelId` when `channelType` is a guild thread. */
|
|
470
|
+
threadId?: Snowflake | readonly Snowflake[];
|
|
359
471
|
}
|
|
360
472
|
interface EventSubscription<K extends BotEventName = BotEventName> {
|
|
361
473
|
name: K;
|
|
362
474
|
filter?: EventSubscriptionFilter;
|
|
363
475
|
}
|
|
364
476
|
interface SecretPermissions {
|
|
365
|
-
events?:
|
|
366
|
-
actions?:
|
|
477
|
+
events?: '*' | readonly BotEventName[];
|
|
478
|
+
actions?: '*' | readonly BotActionName[];
|
|
367
479
|
}
|
|
368
480
|
interface ScopedSecretConfig {
|
|
369
481
|
id?: string;
|
|
@@ -377,6 +489,8 @@ interface ActionErrorDetails {
|
|
|
377
489
|
discordCode?: number;
|
|
378
490
|
/** When true, callers may retry with backoff (e.g. rate limits). */
|
|
379
491
|
retryable?: boolean;
|
|
492
|
+
/** Suggested wait derived from Discord `retry_after` (milliseconds), when available. */
|
|
493
|
+
retryAfterMs?: number;
|
|
380
494
|
[key: string]: unknown;
|
|
381
495
|
}
|
|
382
496
|
interface BotBridgeOptions {
|
|
@@ -395,6 +509,14 @@ interface BotBridgeOptions {
|
|
|
395
509
|
maxConcurrentActions?: number;
|
|
396
510
|
/** When the queue is full, fail fast with `SERVICE_UNAVAILABLE` (default: 5000). */
|
|
397
511
|
actionQueueTimeoutMs?: number;
|
|
512
|
+
/**
|
|
513
|
+
* Where `idempotencyKey` deduplication is scoped (default: `connection`).
|
|
514
|
+
* - `connection`: same WebSocket connection only (reconnect uses a new scope).
|
|
515
|
+
* - `secret`: same configured secret id across connections (useful for retries after reconnect).
|
|
516
|
+
*/
|
|
517
|
+
idempotencyScope?: 'connection' | 'secret';
|
|
518
|
+
/** TTL for idempotency cache entries in ms (default: 120000). */
|
|
519
|
+
idempotencyTtlMs?: number;
|
|
398
520
|
};
|
|
399
521
|
logger?: ShardwireLogger;
|
|
400
522
|
}
|
|
@@ -405,6 +527,10 @@ interface AppBridgeMetricsHooks {
|
|
|
405
527
|
durationMs: number;
|
|
406
528
|
ok: boolean;
|
|
407
529
|
errorCode?: string;
|
|
530
|
+
/** Present when Discord returned HTTP 429 or similar retryable signals. */
|
|
531
|
+
retryAfterMs?: number;
|
|
532
|
+
discordStatus?: number;
|
|
533
|
+
discordCode?: number;
|
|
408
534
|
}) => void;
|
|
409
535
|
}
|
|
410
536
|
interface AppBridgeOptions {
|
|
@@ -423,7 +549,7 @@ interface AppBridgeOptions {
|
|
|
423
549
|
metrics?: AppBridgeMetricsHooks;
|
|
424
550
|
}
|
|
425
551
|
interface ActionError {
|
|
426
|
-
code:
|
|
552
|
+
code: 'UNAUTHORIZED' | 'TIMEOUT' | 'DISCONNECTED' | 'FORBIDDEN' | 'NOT_FOUND' | 'INVALID_REQUEST' | 'INTERNAL_ERROR' | 'SERVICE_UNAVAILABLE';
|
|
427
553
|
message: string;
|
|
428
554
|
details?: ActionErrorDetails | unknown;
|
|
429
555
|
}
|
|
@@ -441,9 +567,9 @@ interface ActionFailure {
|
|
|
441
567
|
}
|
|
442
568
|
type ActionResult<T> = ActionSuccess<T> | ActionFailure;
|
|
443
569
|
declare class BridgeCapabilityError extends Error {
|
|
444
|
-
readonly kind:
|
|
570
|
+
readonly kind: 'event' | 'action';
|
|
445
571
|
readonly name: string;
|
|
446
|
-
constructor(kind:
|
|
572
|
+
constructor(kind: 'event' | 'action', name: string, message?: string);
|
|
447
573
|
}
|
|
448
574
|
type EventHandler<K extends BotEventName> = (payload: BotEventPayloadMap[K]) => void;
|
|
449
575
|
type AppBridgeActionInvokeOptions = {
|
|
@@ -478,4 +604,4 @@ declare function createBotBridge(options: BotBridgeOptions): BotBridge;
|
|
|
478
604
|
|
|
479
605
|
declare function connectBotBridge(options: AppBridgeOptions): AppBridge;
|
|
480
606
|
|
|
481
|
-
export { type ActionError, type ActionErrorDetails, type ActionFailure, type ActionResult, type ActionSuccess, type AddMemberRoleActionPayload, type AddMessageReactionActionPayload, type AppBridge, type AppBridgeActionInvokeOptions, type AppBridgeActions, type AppBridgeMetricsHooks, type AppBridgeOptions, type BanMemberActionPayload, type BotActionName, type BotActionPayloadMap, type BotActionResultDataMap, type BotBridge, type BotBridgeOptions, type BotBridgeSecret, type BotEventName, type BotEventPayloadMap, type BotIntentName, type BridgeAttachment, type BridgeCapabilities, BridgeCapabilityError, type BridgeDeletedMessage, type BridgeGuild, type BridgeGuildMember, type BridgeInteraction, type BridgeInteractionKind, type BridgeMessage, type BridgeMessageInput, type BridgeMessageReaction, type BridgeMessageReference, type BridgeReactionEmoji, type BridgeThread, type BridgeUser, type DeferInteractionActionPayload, type DeferInteractionActionResult, type DeferUpdateInteractionActionPayload, type DeferUpdateInteractionActionResult, type DeleteInteractionReplyActionPayload, type DeleteInteractionReplyActionResult, type DeleteMessageActionPayload, type DeleteMessageActionResult, type EditInteractionReplyActionPayload, type EditMessageActionPayload, type EventEnvelopeBase, type EventHandler, type EventSubscription, type EventSubscriptionFilter, type FetchMemberActionPayload, type FetchMessageActionPayload, type FollowUpInteractionActionPayload, type GuildCreateEventPayload, type GuildDeleteEventPayload, type GuildMemberAddEventPayload, type GuildMemberRemoveEventPayload, type GuildMemberUpdateEventPayload, type InteractionCreateEventPayload, type KickMemberActionPayload, type MemberModerationActionResult, type MessageCreateEventPayload, type MessageDeleteEventPayload, type MessageReactionActionResult, type MessageReactionAddEventPayload, type MessageReactionRemoveEventPayload, type MessageUpdateEventPayload, type ReadyEventPayload, type RemoveMemberRoleActionPayload, type RemoveOwnMessageReactionActionPayload, type ReplyToInteractionActionPayload, type ScopedSecretConfig, type SecretPermissions, type SendMessageActionPayload, type ShardwireLogger, type ShowModalActionPayload, type ShowModalActionResult, type ThreadCreateEventPayload, type ThreadDeleteEventPayload, type ThreadUpdateEventPayload, type Unsubscribe, type UpdateInteractionActionPayload, connectBotBridge, createBotBridge };
|
|
607
|
+
export { type ActionError, type ActionErrorDetails, type ActionFailure, type ActionResult, type ActionSuccess, type AddMemberRoleActionPayload, type AddMessageReactionActionPayload, type AppBridge, type AppBridgeActionInvokeOptions, type AppBridgeActions, type AppBridgeMetricsHooks, type AppBridgeOptions, type ArchiveThreadActionPayload, type BanMemberActionPayload, type BotActionName, type BotActionPayloadMap, type BotActionResultDataMap, type BotBridge, type BotBridgeOptions, type BotBridgeSecret, type BotEventName, type BotEventPayloadMap, type BotIntentName, type BridgeAttachment, type BridgeCapabilities, BridgeCapabilityError, type BridgeChannel, type BridgeDeletedMessage, type BridgeGuild, type BridgeGuildMember, type BridgeInteraction, type BridgeInteractionKind, type BridgeMessage, type BridgeMessageInput, type BridgeMessageReaction, type BridgeMessageReference, type BridgeReactionEmoji, type BridgeThread, type BridgeUser, type ChannelCreateEventPayload, type ChannelDeleteEventPayload, type ChannelUpdateEventPayload, type CreateChannelActionPayload, type CreateThreadActionPayload, type DeferInteractionActionPayload, type DeferInteractionActionResult, type DeferUpdateInteractionActionPayload, type DeferUpdateInteractionActionResult, type DeleteChannelActionPayload, type DeleteChannelActionResult, type DeleteInteractionReplyActionPayload, type DeleteInteractionReplyActionResult, type DeleteMessageActionPayload, type DeleteMessageActionResult, type EditChannelActionPayload, type EditInteractionReplyActionPayload, type EditMessageActionPayload, type EventEnvelopeBase, type EventHandler, type EventSubscription, type EventSubscriptionFilter, type FetchMemberActionPayload, type FetchMessageActionPayload, type FollowUpInteractionActionPayload, type GuildCreateEventPayload, type GuildDeleteEventPayload, type GuildMemberAddEventPayload, type GuildMemberRemoveEventPayload, type GuildMemberUpdateEventPayload, type InteractionCreateEventPayload, type KickMemberActionPayload, type MemberModerationActionResult, type MessageBulkDeleteEventPayload, type MessageCreateEventPayload, type MessageDeleteEventPayload, type MessageReactionActionResult, type MessageReactionAddEventPayload, type MessageReactionRemoveEventPayload, type MessageUpdateEventPayload, type ReadyEventPayload, type RemoveMemberRoleActionPayload, type RemoveMemberTimeoutActionPayload, type RemoveOwnMessageReactionActionPayload, type ReplyToInteractionActionPayload, type ScopedSecretConfig, type SecretPermissions, type SendMessageActionPayload, type ShardwireLogger, type ShowModalActionPayload, type ShowModalActionResult, type ThreadCreateEventPayload, type ThreadDeleteEventPayload, type ThreadUpdateEventPayload, type TimeoutMemberActionPayload, type Unsubscribe, type UpdateInteractionActionPayload, connectBotBridge, createBotBridge };
|