taverns.js 0.2.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.
@@ -0,0 +1,408 @@
1
+ /**
2
+ * taverns.js - Type Definitions
3
+ *
4
+ * TypeScript interfaces that mirror the Taverns API responses.
5
+ * All IDs are UUIDs represented as strings.
6
+ */
7
+ import { ChannelType, MessageStatus, MemberStatus, CommandOptionType } from './constants';
8
+ import type { Embed } from './embed';
9
+ export interface User {
10
+ id: string;
11
+ displayName: string;
12
+ avatarUrl: string | null;
13
+ bannerUrl?: string | null;
14
+ isBot?: boolean;
15
+ createdAt?: string;
16
+ }
17
+ /** The bot's own identity returned by GET /bots/@me */
18
+ export interface BotSelf {
19
+ id: string;
20
+ name: string;
21
+ description: string | null;
22
+ user: User;
23
+ taverns: InstalledTavern[];
24
+ }
25
+ /** A tavern the bot is installed in */
26
+ export interface InstalledTavern {
27
+ id: string;
28
+ name: string;
29
+ slug: string;
30
+ iconUrl: string | null;
31
+ memberCount: number;
32
+ grantedPermissions: string;
33
+ installedAt: string;
34
+ }
35
+ export interface Tavern {
36
+ id: string;
37
+ name: string;
38
+ slug: string;
39
+ iconUrl: string | null;
40
+ bannerUrl?: string | null;
41
+ description?: string | null;
42
+ memberCount: number;
43
+ isPublic?: boolean;
44
+ ownerId?: string;
45
+ grantedPermissions?: string;
46
+ installedAt?: string;
47
+ channels?: Channel[];
48
+ roles?: Role[];
49
+ members?: Member[];
50
+ }
51
+ export interface Channel {
52
+ id: string;
53
+ name: string;
54
+ type: ChannelType;
55
+ tavernId: string;
56
+ topic?: string | null;
57
+ position: number;
58
+ categoryId?: string | null;
59
+ slowModeSeconds?: number;
60
+ isNsfw?: boolean;
61
+ isArchived?: boolean;
62
+ isPrivate?: boolean;
63
+ createdAt?: string;
64
+ parentChannelId?: string | null;
65
+ rootMessageId?: string | null;
66
+ creatorId?: string | null;
67
+ isPrivateThread?: boolean;
68
+ autoArchiveDuration?: number;
69
+ archivedAt?: string | null;
70
+ threadMetadata?: ThreadMetadata | null;
71
+ }
72
+ export interface ThreadMetadata {
73
+ messageCount?: number;
74
+ lastMessageAt?: string;
75
+ lastSenderId?: string;
76
+ lastSenderName?: string;
77
+ lastSenderAvatarUrl?: string | null;
78
+ lastMessagePreview?: string;
79
+ }
80
+ export interface Category {
81
+ id: string;
82
+ name: string;
83
+ position: number;
84
+ tavernId: string;
85
+ isPrivate?: boolean;
86
+ isNsfw?: boolean;
87
+ }
88
+ export interface Member {
89
+ id: string;
90
+ userId: string;
91
+ tavernId: string;
92
+ nickname?: string | null;
93
+ status: MemberStatus;
94
+ user: User;
95
+ roles?: MemberRole[];
96
+ joinedAt?: string;
97
+ }
98
+ export interface MemberRole {
99
+ id: string;
100
+ name: string;
101
+ color?: string | null;
102
+ iconUrl?: string | null;
103
+ position: number;
104
+ }
105
+ export interface Role {
106
+ id: string;
107
+ name: string;
108
+ color?: string | null;
109
+ iconUrl?: string | null;
110
+ isMentionable: boolean;
111
+ position: number;
112
+ permissions: string;
113
+ memberCount?: number;
114
+ createdAt?: string;
115
+ }
116
+ export interface Message {
117
+ id: string;
118
+ channelId: string;
119
+ tavernId?: string;
120
+ content: string;
121
+ filteredContent?: string | null;
122
+ status: MessageStatus;
123
+ replyToId?: string | null;
124
+ replyTo?: MessageReply | null;
125
+ metadata?: Record<string, unknown> | null;
126
+ createdAt: string;
127
+ editedAt?: string | null;
128
+ isPinned?: boolean;
129
+ isPublished?: boolean;
130
+ publishedAt?: string | null;
131
+ isSyndicated?: boolean;
132
+ originalAuthorName?: string | null;
133
+ originalAuthorAvatar?: string | null;
134
+ sourceTavernName?: string | null;
135
+ sourceTavernId?: string | null;
136
+ sender: MessageSender;
137
+ }
138
+ export interface MessageReply {
139
+ id: string;
140
+ senderId: string;
141
+ senderName: string;
142
+ preview: string;
143
+ }
144
+ export interface MessageSender {
145
+ id: string;
146
+ displayName: string;
147
+ avatarUrl: string | null;
148
+ aetherName?: string | null;
149
+ nickname?: string | null;
150
+ roles?: MemberRole[];
151
+ isBot?: boolean;
152
+ }
153
+ export interface SendMessageOptions {
154
+ content: string;
155
+ replyToId?: string;
156
+ metadata?: Record<string, unknown>;
157
+ embeds?: Embed[];
158
+ }
159
+ export interface EditMessageOptions {
160
+ content: string;
161
+ }
162
+ export interface GetMessagesOptions {
163
+ limit?: number;
164
+ before?: string;
165
+ after?: string;
166
+ }
167
+ export interface SearchMessagesOptions {
168
+ query: string;
169
+ channelId?: string;
170
+ senderId?: string;
171
+ limit?: number;
172
+ offset?: number;
173
+ }
174
+ export interface CreateChannelOptions {
175
+ name: string;
176
+ type?: ChannelType;
177
+ topic?: string;
178
+ categoryId?: string;
179
+ position?: number;
180
+ slowModeSeconds?: number;
181
+ isNsfw?: boolean;
182
+ isPrivate?: boolean;
183
+ }
184
+ export interface UpdateChannelOptions {
185
+ name?: string;
186
+ topic?: string;
187
+ categoryId?: string | null;
188
+ position?: number;
189
+ slowModeSeconds?: number;
190
+ isNsfw?: boolean;
191
+ isArchived?: boolean;
192
+ }
193
+ export interface CreateRoleOptions {
194
+ name: string;
195
+ color?: string;
196
+ iconUrl?: string;
197
+ permissions?: string;
198
+ isMentionable?: boolean;
199
+ }
200
+ export interface UpdateRoleOptions {
201
+ name?: string;
202
+ color?: string;
203
+ iconUrl?: string;
204
+ permissions?: string;
205
+ isMentionable?: boolean;
206
+ }
207
+ export interface GatewayPayload {
208
+ event: string;
209
+ data: unknown;
210
+ }
211
+ export interface MessageCreateEvent {
212
+ id: string;
213
+ channelId: string;
214
+ tavernId?: string;
215
+ content: string;
216
+ sender: MessageSender;
217
+ replyToId?: string | null;
218
+ replyTo?: MessageReply | null;
219
+ metadata?: Record<string, unknown> | null;
220
+ createdAt: string;
221
+ }
222
+ export interface MessageEditEvent {
223
+ id: string;
224
+ channelId: string;
225
+ tavernId?: string;
226
+ content: string;
227
+ editedAt: string;
228
+ sender: MessageSender;
229
+ }
230
+ export interface MessageDeleteEvent {
231
+ id: string;
232
+ channelId: string;
233
+ tavernId?: string;
234
+ }
235
+ export interface BulkDeleteEvent {
236
+ channelId: string;
237
+ tavernId: string;
238
+ messageIds: string[];
239
+ count: number;
240
+ }
241
+ export interface MemberJoinEvent {
242
+ userId: string;
243
+ tavernId: string;
244
+ displayName: string;
245
+ avatarUrl: string | null;
246
+ isBot?: boolean;
247
+ }
248
+ export interface MemberLeaveEvent {
249
+ userId: string;
250
+ tavernId: string;
251
+ }
252
+ export interface ChannelCreateEvent {
253
+ id: string;
254
+ name: string;
255
+ type: ChannelType;
256
+ tavernId: string;
257
+ topic?: string | null;
258
+ position: number;
259
+ categoryId?: string | null;
260
+ }
261
+ export interface ChannelUpdateEvent {
262
+ id: string;
263
+ tavernId: string;
264
+ name?: string;
265
+ topic?: string | null;
266
+ position?: number;
267
+ }
268
+ export interface ChannelDeleteEvent {
269
+ id: string;
270
+ tavernId: string;
271
+ }
272
+ export interface RoleUpdateEvent {
273
+ id: string;
274
+ tavernId?: string;
275
+ name: string;
276
+ color?: string | null;
277
+ iconUrl?: string | null;
278
+ position: number;
279
+ permissions: string;
280
+ isMentionable: boolean;
281
+ }
282
+ export interface RoleDeleteEvent {
283
+ id: string;
284
+ tavernId: string;
285
+ }
286
+ export interface TavernUpdateEvent {
287
+ id: string;
288
+ name?: string;
289
+ iconUrl?: string | null;
290
+ bannerUrl?: string | null;
291
+ description?: string | null;
292
+ }
293
+ export interface BotInstalledEvent {
294
+ tavernId: string;
295
+ tavernName: string;
296
+ grantedPermissions: string;
297
+ }
298
+ export interface BotRemovedEvent {
299
+ tavernId: string;
300
+ }
301
+ export interface TypingEvent {
302
+ userId: string;
303
+ channelId: string;
304
+ tavernId: string;
305
+ displayName: string;
306
+ }
307
+ export interface MessagePinnedEvent {
308
+ id: string;
309
+ channelId: string;
310
+ tavernId: string;
311
+ }
312
+ export interface MessageUnpinnedEvent {
313
+ id: string;
314
+ channelId: string;
315
+ tavernId: string;
316
+ }
317
+ export interface BotCommand {
318
+ id: string;
319
+ name: string;
320
+ description: string;
321
+ options?: CommandOption[];
322
+ defaultMemberPermissions?: string;
323
+ version: number;
324
+ }
325
+ export interface CommandOption {
326
+ name: string;
327
+ description: string;
328
+ type: CommandOptionType;
329
+ required?: boolean;
330
+ choices?: {
331
+ name: string;
332
+ value: string | number;
333
+ }[];
334
+ }
335
+ export interface Interaction {
336
+ id: string;
337
+ commandName: string;
338
+ tavernId: string;
339
+ channelId: string;
340
+ userId: string;
341
+ options: Record<string, any>;
342
+ }
343
+ export interface InteractionCallbackData {
344
+ content: string;
345
+ ephemeral?: boolean;
346
+ embeds?: Embed[];
347
+ }
348
+ export interface PaginatedMessages {
349
+ messages: Message[];
350
+ hasMore: boolean;
351
+ nextCursor?: string;
352
+ }
353
+ export interface ClientOptions {
354
+ /** Bot token (must start with tavbot_) */
355
+ token?: string;
356
+ /** Override the REST API base URL */
357
+ apiUrl?: string;
358
+ /** Override the WebSocket gateway URL */
359
+ wsUrl?: string;
360
+ /** Whether to automatically reconnect on disconnect (default: true) */
361
+ autoReconnect?: boolean;
362
+ /** Maximum number of reconnect attempts before giving up (default: Infinity) */
363
+ maxReconnectAttempts?: number;
364
+ /** Heartbeat interval in ms (default: 30000) */
365
+ heartbeatInterval?: number;
366
+ /** Gateway intents bitfield (session-level, not persisted to server) */
367
+ intents?: bigint;
368
+ }
369
+ export interface ClientEvents {
370
+ ready: [];
371
+ reconnecting: [attempt: number];
372
+ disconnected: [code: number, reason: string];
373
+ error: [error: Error];
374
+ debug: [message: string];
375
+ messageCreate: [message: Message];
376
+ messageUpdate: [message: MessageEditEvent];
377
+ messageDelete: [event: MessageDeleteEvent];
378
+ messageDeleteBulk: [event: BulkDeleteEvent];
379
+ messagePinned: [event: MessagePinnedEvent];
380
+ messageUnpinned: [event: MessageUnpinnedEvent];
381
+ messagePublished: [message: Message];
382
+ memberJoin: [event: MemberJoinEvent];
383
+ memberLeave: [event: MemberLeaveEvent];
384
+ memberMuted: [event: Record<string, unknown>];
385
+ memberUnmuted: [event: Record<string, unknown>];
386
+ channelCreate: [channel: ChannelCreateEvent];
387
+ channelUpdate: [event: ChannelUpdateEvent];
388
+ channelDelete: [event: ChannelDeleteEvent];
389
+ threadCreate: [channel: Channel];
390
+ threadUpdate: [event: Record<string, unknown>];
391
+ threadDelete: [event: Record<string, unknown>];
392
+ threadArchived: [event: Record<string, unknown>];
393
+ roleUpdate: [role: RoleUpdateEvent];
394
+ roleDelete: [event: RoleDeleteEvent];
395
+ tavernUpdate: [event: TavernUpdateEvent];
396
+ eventCreate: [event: Record<string, unknown>];
397
+ eventUpdate: [event: Record<string, unknown>];
398
+ eventDelete: [event: Record<string, unknown>];
399
+ eventStarting: [event: Record<string, unknown>];
400
+ eventEnded: [event: Record<string, unknown>];
401
+ voiceStateUpdate: [event: Record<string, unknown>];
402
+ typingStart: [event: TypingEvent];
403
+ typingStop: [event: TypingEvent];
404
+ botInstalled: [event: BotInstalledEvent];
405
+ botRemoved: [event: BotRemovedEvent];
406
+ interactionCreate: [interaction: Interaction];
407
+ raw: [event: string, data: unknown];
408
+ }
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * taverns.js - Type Definitions
4
+ *
5
+ * TypeScript interfaces that mirror the Taverns API responses.
6
+ * All IDs are UUIDs represented as strings.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Verify a Tavern webhook request signature.
3
+ *
4
+ * @param rawBody - The raw request body as a string or Buffer
5
+ * @param signature - The X-Tavern-Signature header value (format: "sha256=hex")
6
+ * @param secret - Your webhook signing secret
7
+ * @param timestamp - The X-Tavern-Timestamp header value (optional, for freshness check)
8
+ * @param maxAge - Maximum age in seconds for timestamp freshness (default: 300 = 5 minutes)
9
+ * @returns true if the signature is valid
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { verifyWebhookSignature } from 'taverns.js';
14
+ *
15
+ * app.post('/webhook', (req, res) => {
16
+ * const isValid = verifyWebhookSignature(
17
+ * req.body, // raw body (use express.raw() or body as string)
18
+ * req.headers['x-tavern-signature'],
19
+ * process.env.WEBHOOK_SECRET!,
20
+ * req.headers['x-tavern-timestamp'],
21
+ * );
22
+ *
23
+ * if (!isValid) {
24
+ * return res.status(401).send('Invalid signature');
25
+ * }
26
+ *
27
+ * const event = JSON.parse(req.body);
28
+ * console.log(`Received ${event.event}:`, event.data);
29
+ * res.status(200).send('OK');
30
+ * });
31
+ * ```
32
+ */
33
+ export declare function verifyWebhookSignature(rawBody: string | Buffer, signature: string | undefined, secret: string, timestamp?: string, maxAge?: number): boolean;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyWebhookSignature = verifyWebhookSignature;
4
+ const crypto_1 = require("crypto");
5
+ /**
6
+ * Verify a Tavern webhook request signature.
7
+ *
8
+ * @param rawBody - The raw request body as a string or Buffer
9
+ * @param signature - The X-Tavern-Signature header value (format: "sha256=hex")
10
+ * @param secret - Your webhook signing secret
11
+ * @param timestamp - The X-Tavern-Timestamp header value (optional, for freshness check)
12
+ * @param maxAge - Maximum age in seconds for timestamp freshness (default: 300 = 5 minutes)
13
+ * @returns true if the signature is valid
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { verifyWebhookSignature } from 'taverns.js';
18
+ *
19
+ * app.post('/webhook', (req, res) => {
20
+ * const isValid = verifyWebhookSignature(
21
+ * req.body, // raw body (use express.raw() or body as string)
22
+ * req.headers['x-tavern-signature'],
23
+ * process.env.WEBHOOK_SECRET!,
24
+ * req.headers['x-tavern-timestamp'],
25
+ * );
26
+ *
27
+ * if (!isValid) {
28
+ * return res.status(401).send('Invalid signature');
29
+ * }
30
+ *
31
+ * const event = JSON.parse(req.body);
32
+ * console.log(`Received ${event.event}:`, event.data);
33
+ * res.status(200).send('OK');
34
+ * });
35
+ * ```
36
+ */
37
+ function verifyWebhookSignature(rawBody, signature, secret, timestamp, maxAge = 300) {
38
+ if (!signature)
39
+ return false;
40
+ // Timestamp freshness check (replay protection)
41
+ if (timestamp) {
42
+ const ts = parseInt(timestamp, 10);
43
+ if (isNaN(ts))
44
+ return false;
45
+ const age = Math.abs(Date.now() - ts) / 1000;
46
+ if (age > maxAge)
47
+ return false;
48
+ }
49
+ // Extract the hex digest from "sha256=<hex>"
50
+ const parts = signature.split('=');
51
+ if (parts.length !== 2 || parts[0] !== 'sha256')
52
+ return false;
53
+ const providedHex = parts[1];
54
+ // Compute expected signature
55
+ const body = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');
56
+ const expectedHex = (0, crypto_1.createHmac)('sha256', secret).update(body).digest('hex');
57
+ // Timing-safe comparison
58
+ try {
59
+ const providedBuf = Buffer.from(providedHex, 'hex');
60
+ const expectedBuf = Buffer.from(expectedHex, 'hex');
61
+ if (providedBuf.length !== expectedBuf.length)
62
+ return false;
63
+ return (0, crypto_1.timingSafeEqual)(providedBuf, expectedBuf);
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "taverns.js",
3
+ "version": "0.2.0",
4
+ "description": "SDK for building Tavern bots for the Taverns platform",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "keywords": [
13
+ "taverns",
14
+ "bot",
15
+ "sdk",
16
+ "api",
17
+ "tav"
18
+ ],
19
+ "license": "Apache-2.0",
20
+ "author": "Tav <https://github.com/Tav-gg>",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/Tav-gg/taverns.js.git"
24
+ },
25
+ "homepage": "https://github.com/Tav-gg/taverns.js#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/Tav-gg/taverns.js/issues"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "LICENSE",
35
+ "README.md"
36
+ ],
37
+ "dependencies": {
38
+ "ws": "^8.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/ws": "^8.0.0",
42
+ "typescript": "^5.0.0"
43
+ }
44
+ }