@elizaos/plugin-twitch 2.0.0-alpha.3

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/src/types.ts ADDED
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Type definitions for the Twitch plugin.
3
+ */
4
+
5
+ import type { IAgentRuntime, Service } from "@elizaos/core";
6
+
7
+ // ============================================================================
8
+ // Constants
9
+ // ============================================================================
10
+
11
+ /** Maximum message length for Twitch chat */
12
+ export const MAX_TWITCH_MESSAGE_LENGTH = 500;
13
+
14
+ /** Service name constant */
15
+ export const TWITCH_SERVICE_NAME = "twitch";
16
+
17
+ // ============================================================================
18
+ // Event Types
19
+ // ============================================================================
20
+
21
+ /** Event types emitted by the Twitch plugin */
22
+ export enum TwitchEventTypes {
23
+ MESSAGE_RECEIVED = "TWITCH_MESSAGE_RECEIVED",
24
+ MESSAGE_SENT = "TWITCH_MESSAGE_SENT",
25
+ JOIN_CHANNEL = "TWITCH_JOIN_CHANNEL",
26
+ LEAVE_CHANNEL = "TWITCH_LEAVE_CHANNEL",
27
+ CONNECTION_READY = "TWITCH_CONNECTION_READY",
28
+ CONNECTION_LOST = "TWITCH_CONNECTION_LOST",
29
+ }
30
+
31
+ // ============================================================================
32
+ // Configuration Types
33
+ // ============================================================================
34
+
35
+ /** Twitch user roles for access control */
36
+ export type TwitchRole = "moderator" | "owner" | "vip" | "subscriber" | "all";
37
+
38
+ /** Configuration settings for the Twitch plugin */
39
+ export interface TwitchSettings {
40
+ /** Twitch username for the bot account */
41
+ username: string;
42
+ /** Twitch application client ID */
43
+ clientId: string;
44
+ /** OAuth access token */
45
+ accessToken: string;
46
+ /** Optional client secret for token refresh */
47
+ clientSecret?: string;
48
+ /** Optional refresh token for automatic token refresh */
49
+ refreshToken?: string;
50
+ /** Primary channel to join */
51
+ channel: string;
52
+ /** Additional channels to join */
53
+ additionalChannels: string[];
54
+ /** Whether to require @mention to respond */
55
+ requireMention: boolean;
56
+ /** Roles allowed to interact with the bot */
57
+ allowedRoles: TwitchRole[];
58
+ /** Optional allowlist of user IDs */
59
+ allowedUserIds: string[];
60
+ /** Whether this configuration is enabled */
61
+ enabled: boolean;
62
+ }
63
+
64
+ // ============================================================================
65
+ // Message Types
66
+ // ============================================================================
67
+
68
+ /** Information about the message sender */
69
+ export interface TwitchUserInfo {
70
+ /** Twitch user ID (numeric string) */
71
+ userId: string;
72
+ /** Twitch username (login name) */
73
+ username: string;
74
+ /** Display name (may include special characters) */
75
+ displayName: string;
76
+ /** Whether the user is a moderator */
77
+ isModerator: boolean;
78
+ /** Whether the user is the channel owner/broadcaster */
79
+ isBroadcaster: boolean;
80
+ /** Whether the user is a VIP */
81
+ isVip: boolean;
82
+ /** Whether the user is a subscriber */
83
+ isSubscriber: boolean;
84
+ /** User's chat color */
85
+ color?: string;
86
+ /** User's badges */
87
+ badges: Map<string, string>;
88
+ }
89
+
90
+ /** Represents a Twitch chat message */
91
+ export interface TwitchMessage {
92
+ /** Unique message ID */
93
+ id: string;
94
+ /** Channel name (without # prefix) */
95
+ channel: string;
96
+ /** Message text content */
97
+ text: string;
98
+ /** Sender information */
99
+ user: TwitchUserInfo;
100
+ /** Message timestamp */
101
+ timestamp: Date;
102
+ /** Whether this is an action message (/me) */
103
+ isAction: boolean;
104
+ /** Whether this is a highlighted message */
105
+ isHighlighted: boolean;
106
+ /** Reply thread info if this is a reply */
107
+ replyTo?: {
108
+ messageId: string;
109
+ userId: string;
110
+ username: string;
111
+ text: string;
112
+ };
113
+ }
114
+
115
+ /** Options for sending a message */
116
+ export interface TwitchMessageSendOptions {
117
+ /** Channel to send to (defaults to primary channel) */
118
+ channel?: string;
119
+ /** Message ID to reply to */
120
+ replyTo?: string;
121
+ }
122
+
123
+ /** Result from sending a message */
124
+ export interface TwitchSendResult {
125
+ /** Whether the send succeeded */
126
+ success: boolean;
127
+ /** Generated message ID (if available) */
128
+ messageId?: string;
129
+ /** Error message if failed */
130
+ error?: string;
131
+ }
132
+
133
+ // ============================================================================
134
+ // Service Interface
135
+ // ============================================================================
136
+
137
+ /** Interface for the Twitch service */
138
+ export interface ITwitchService extends Service {
139
+ /** Check if the service is connected */
140
+ isConnected(): boolean;
141
+
142
+ /** Get the bot username */
143
+ getBotUsername(): string;
144
+
145
+ /** Get the primary channel */
146
+ getPrimaryChannel(): string;
147
+
148
+ /** Get all joined channels */
149
+ getJoinedChannels(): string[];
150
+
151
+ /** Send a message to a channel */
152
+ sendMessage(
153
+ text: string,
154
+ options?: TwitchMessageSendOptions,
155
+ ): Promise<TwitchSendResult>;
156
+
157
+ /** Join a channel */
158
+ joinChannel(channel: string): Promise<void>;
159
+
160
+ /** Leave a channel */
161
+ leaveChannel(channel: string): Promise<void>;
162
+
163
+ /** Check if a user is allowed to interact based on settings */
164
+ isUserAllowed(user: TwitchUserInfo): boolean;
165
+ }
166
+
167
+ // ============================================================================
168
+ // Event Payloads
169
+ // ============================================================================
170
+
171
+ /** Payload for MESSAGE_RECEIVED event */
172
+ export interface TwitchMessageReceivedPayload {
173
+ message: TwitchMessage;
174
+ runtime: IAgentRuntime;
175
+ }
176
+
177
+ /** Payload for MESSAGE_SENT event */
178
+ export interface TwitchMessageSentPayload {
179
+ channel: string;
180
+ text: string;
181
+ messageId?: string;
182
+ }
183
+
184
+ /** Payload for JOIN_CHANNEL event */
185
+ export interface TwitchJoinChannelPayload {
186
+ channel: string;
187
+ }
188
+
189
+ /** Payload for LEAVE_CHANNEL event */
190
+ export interface TwitchLeaveChannelPayload {
191
+ channel: string;
192
+ }
193
+
194
+ // ============================================================================
195
+ // Utility Functions
196
+ // ============================================================================
197
+
198
+ /**
199
+ * Normalize a Twitch channel name (ensure no # prefix).
200
+ */
201
+ export function normalizeChannel(channel: string): string {
202
+ return channel.startsWith("#") ? channel.slice(1) : channel;
203
+ }
204
+
205
+ /**
206
+ * Format a channel name for display (with # prefix).
207
+ */
208
+ export function formatChannelForDisplay(channel: string): string {
209
+ const normalized = normalizeChannel(channel);
210
+ return `#${normalized}`;
211
+ }
212
+
213
+ /**
214
+ * Get the best display name for a Twitch user.
215
+ */
216
+ export function getTwitchUserDisplayName(user: TwitchUserInfo): string {
217
+ return user.displayName || user.username;
218
+ }
219
+
220
+ /**
221
+ * Strip markdown formatting for Twitch chat display.
222
+ * Twitch doesn't render markdown, so we convert it to plain text.
223
+ */
224
+ export function stripMarkdownForTwitch(text: string): string {
225
+ return (
226
+ text
227
+ // Remove bold
228
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
229
+ .replace(/__([^_]+)__/g, "$1")
230
+ // Remove italic
231
+ .replace(/\*([^*]+)\*/g, "$1")
232
+ .replace(/_([^_]+)_/g, "$1")
233
+ // Remove strikethrough
234
+ .replace(/~~([^~]+)~~/g, "$1")
235
+ // Remove inline code
236
+ .replace(/`([^`]+)`/g, "$1")
237
+ // Remove code blocks
238
+ .replace(/```[\s\S]*?```/g, "[code block]")
239
+ // Remove links, keep text
240
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
241
+ // Remove headers
242
+ .replace(/^#{1,6}\s+/gm, "")
243
+ // Remove blockquotes
244
+ .replace(/^>\s+/gm, "")
245
+ // Remove list markers
246
+ .replace(/^[-*+]\s+/gm, "• ")
247
+ .replace(/^\d+\.\s+/gm, "• ")
248
+ // Collapse multiple newlines
249
+ .replace(/\n{3,}/g, "\n\n")
250
+ .trim()
251
+ );
252
+ }
253
+
254
+ /**
255
+ * Split a message into chunks that fit Twitch's message limit.
256
+ */
257
+ export function splitMessageForTwitch(
258
+ text: string,
259
+ maxLength: number = MAX_TWITCH_MESSAGE_LENGTH,
260
+ ): string[] {
261
+ if (text.length <= maxLength) {
262
+ return [text];
263
+ }
264
+
265
+ const chunks: string[] = [];
266
+ let remaining = text;
267
+
268
+ while (remaining.length > 0) {
269
+ if (remaining.length <= maxLength) {
270
+ chunks.push(remaining);
271
+ break;
272
+ }
273
+
274
+ // Try to split at a sentence boundary
275
+ let splitIndex = remaining.lastIndexOf(". ", maxLength);
276
+ if (splitIndex === -1 || splitIndex < maxLength / 2) {
277
+ // Try to split at a word boundary
278
+ splitIndex = remaining.lastIndexOf(" ", maxLength);
279
+ }
280
+ if (splitIndex === -1 || splitIndex < maxLength / 2) {
281
+ // Force split at max length
282
+ splitIndex = maxLength;
283
+ }
284
+
285
+ chunks.push(remaining.slice(0, splitIndex).trim());
286
+ remaining = remaining.slice(splitIndex).trim();
287
+ }
288
+
289
+ return chunks;
290
+ }
291
+
292
+ // ============================================================================
293
+ // Custom Errors
294
+ // ============================================================================
295
+
296
+ /** Base error class for Twitch plugin errors */
297
+ export class TwitchPluginError extends Error {
298
+ constructor(message: string) {
299
+ super(message);
300
+ this.name = "TwitchPluginError";
301
+ }
302
+ }
303
+
304
+ /** Error when the Twitch service is not initialized */
305
+ export class TwitchServiceNotInitializedError extends TwitchPluginError {
306
+ constructor(message: string = "Twitch service is not initialized") {
307
+ super(message);
308
+ this.name = "TwitchServiceNotInitializedError";
309
+ }
310
+ }
311
+
312
+ /** Error when the Twitch client is not connected */
313
+ export class TwitchNotConnectedError extends TwitchPluginError {
314
+ constructor(message: string = "Twitch client is not connected") {
315
+ super(message);
316
+ this.name = "TwitchNotConnectedError";
317
+ }
318
+ }
319
+
320
+ /** Error when there is a configuration problem */
321
+ export class TwitchConfigurationError extends TwitchPluginError {
322
+ settingName?: string;
323
+
324
+ constructor(message: string, settingName?: string) {
325
+ super(message);
326
+ this.name = "TwitchConfigurationError";
327
+ this.settingName = settingName;
328
+ }
329
+ }
330
+
331
+ /** Error when an API call fails */
332
+ export class TwitchApiError extends TwitchPluginError {
333
+ statusCode?: number;
334
+
335
+ constructor(message: string, statusCode?: number) {
336
+ super(message);
337
+ this.name = "TwitchApiError";
338
+ this.statusCode = statusCode;
339
+ }
340
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "lib": ["ES2022"],
17
+ "types": ["bun-types"]
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }