@elizaos/plugin-twitch 2.0.0-alpha.7 → 2.0.0-beta.1
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 +132 -0
- package/auto-enable.ts +21 -0
- package/dist/accounts.d.ts +17 -0
- package/dist/accounts.d.ts.map +1 -0
- package/dist/connector-account-provider.d.ts +18 -0
- package/dist/connector-account-provider.d.ts.map +1 -0
- package/dist/index.d.ts +1 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +544 -679
- package/dist/index.js.map +9 -12
- package/dist/providers/userContext.d.ts.map +1 -1
- package/dist/service.d.ts +18 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/toon.d.ts +2 -0
- package/dist/toon.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow-credential-provider.d.ts +21 -0
- package/dist/workflow-credential-provider.d.ts.map +1 -0
- package/package.json +19 -9
- package/dist/actions/joinChannel.d.ts +0 -6
- package/dist/actions/joinChannel.d.ts.map +0 -1
- package/dist/actions/leaveChannel.d.ts +0 -6
- package/dist/actions/leaveChannel.d.ts.map +0 -1
- package/dist/actions/listChannels.d.ts +0 -6
- package/dist/actions/listChannels.d.ts.map +0 -1
- package/dist/actions/sendMessage.d.ts +0 -6
- package/dist/actions/sendMessage.d.ts.map +0 -1
- package/dist/providers/channelState.d.ts +0 -9
- package/dist/providers/channelState.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,122 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { logger as logger2 } from "@elizaos/core";
|
|
2
|
+
import { getConnectorAccountManager, logger as logger2 } from "@elizaos/core";
|
|
3
3
|
|
|
4
|
+
// src/accounts.ts
|
|
5
|
+
var DEFAULT_TWITCH_ACCOUNT_ID = "default";
|
|
6
|
+
function stringSetting(runtime, key) {
|
|
7
|
+
const value = runtime.getSetting(key);
|
|
8
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
9
|
+
}
|
|
10
|
+
function characterConfig(runtime) {
|
|
11
|
+
const settings = runtime.character?.settings;
|
|
12
|
+
const raw = settings?.twitch;
|
|
13
|
+
return raw && typeof raw === "object" ? raw : {};
|
|
14
|
+
}
|
|
15
|
+
function parseAccountsJson(runtime) {
|
|
16
|
+
const raw = stringSetting(runtime, "TWITCH_ACCOUNTS");
|
|
17
|
+
if (!raw)
|
|
18
|
+
return {};
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(raw);
|
|
21
|
+
if (Array.isArray(parsed)) {
|
|
22
|
+
return Object.fromEntries(parsed.filter((item) => Boolean(item) && typeof item === "object").map((item) => [
|
|
23
|
+
normalizeTwitchAccountId(item.accountId ?? item.id),
|
|
24
|
+
item
|
|
25
|
+
]));
|
|
26
|
+
}
|
|
27
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
28
|
+
} catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function allAccountConfigs(runtime) {
|
|
33
|
+
return {
|
|
34
|
+
...characterConfig(runtime).accounts ?? {},
|
|
35
|
+
...parseAccountsJson(runtime)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function accountConfig(runtime, accountId) {
|
|
39
|
+
const accounts = allAccountConfigs(runtime);
|
|
40
|
+
return accounts[accountId] ?? accounts[normalizeTwitchAccountId(accountId)] ?? {};
|
|
41
|
+
}
|
|
42
|
+
function boolValue(value, fallback = false) {
|
|
43
|
+
if (typeof value === "boolean")
|
|
44
|
+
return value;
|
|
45
|
+
if (typeof value === "string")
|
|
46
|
+
return value.trim().toLowerCase() === "true";
|
|
47
|
+
return fallback;
|
|
48
|
+
}
|
|
49
|
+
function stringList(value) {
|
|
50
|
+
if (Array.isArray(value))
|
|
51
|
+
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
52
|
+
if (typeof value === "string") {
|
|
53
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
function roleList(value) {
|
|
58
|
+
const values = stringList(value).map((role) => role.toLowerCase());
|
|
59
|
+
return values.length ? values : ["all"];
|
|
60
|
+
}
|
|
61
|
+
function normalizeTwitchAccountId(accountId) {
|
|
62
|
+
if (typeof accountId !== "string")
|
|
63
|
+
return DEFAULT_TWITCH_ACCOUNT_ID;
|
|
64
|
+
const trimmed = accountId.trim();
|
|
65
|
+
return trimmed || DEFAULT_TWITCH_ACCOUNT_ID;
|
|
66
|
+
}
|
|
67
|
+
function listTwitchAccountIds(runtime) {
|
|
68
|
+
const ids = new Set;
|
|
69
|
+
const config = characterConfig(runtime);
|
|
70
|
+
if (stringSetting(runtime, "TWITCH_ACCESS_TOKEN") || config.accessToken) {
|
|
71
|
+
ids.add(DEFAULT_TWITCH_ACCOUNT_ID);
|
|
72
|
+
}
|
|
73
|
+
for (const id of Object.keys(allAccountConfigs(runtime))) {
|
|
74
|
+
ids.add(normalizeTwitchAccountId(id));
|
|
75
|
+
}
|
|
76
|
+
return Array.from(ids.size ? ids : new Set([DEFAULT_TWITCH_ACCOUNT_ID])).sort((a, b) => a.localeCompare(b));
|
|
77
|
+
}
|
|
78
|
+
function resolveDefaultTwitchAccountId(runtime) {
|
|
79
|
+
const requested = stringSetting(runtime, "TWITCH_DEFAULT_ACCOUNT_ID") ?? stringSetting(runtime, "TWITCH_ACCOUNT_ID");
|
|
80
|
+
if (requested)
|
|
81
|
+
return normalizeTwitchAccountId(requested);
|
|
82
|
+
const ids = listTwitchAccountIds(runtime);
|
|
83
|
+
return ids.includes(DEFAULT_TWITCH_ACCOUNT_ID) ? DEFAULT_TWITCH_ACCOUNT_ID : ids[0];
|
|
84
|
+
}
|
|
85
|
+
function readTwitchAccountId(...sources) {
|
|
86
|
+
for (const source of sources) {
|
|
87
|
+
if (!source || typeof source !== "object")
|
|
88
|
+
continue;
|
|
89
|
+
const record = source;
|
|
90
|
+
const parameters = record.parameters && typeof record.parameters === "object" ? record.parameters : {};
|
|
91
|
+
const data = record.data && typeof record.data === "object" ? record.data : {};
|
|
92
|
+
const metadata = record.metadata && typeof record.metadata === "object" ? record.metadata : {};
|
|
93
|
+
const twitch = data.twitch && typeof data.twitch === "object" ? data.twitch : {};
|
|
94
|
+
const value = record.accountId ?? parameters.accountId ?? data.accountId ?? twitch.accountId ?? metadata.accountId;
|
|
95
|
+
if (typeof value === "string" && value.trim())
|
|
96
|
+
return normalizeTwitchAccountId(value);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
function resolveTwitchAccountSettings(runtime, requestedAccountId) {
|
|
101
|
+
const accountId = normalizeTwitchAccountId(requestedAccountId ?? resolveDefaultTwitchAccountId(runtime));
|
|
102
|
+
const base = characterConfig(runtime);
|
|
103
|
+
const account = accountConfig(runtime, accountId);
|
|
104
|
+
const allowEnv = accountId === DEFAULT_TWITCH_ACCOUNT_ID;
|
|
105
|
+
return {
|
|
106
|
+
accountId,
|
|
107
|
+
username: account.username ?? base.username ?? (allowEnv ? stringSetting(runtime, "TWITCH_USERNAME") : undefined) ?? "",
|
|
108
|
+
clientId: account.clientId ?? base.clientId ?? (allowEnv ? stringSetting(runtime, "TWITCH_CLIENT_ID") : undefined) ?? "",
|
|
109
|
+
accessToken: account.accessToken ?? base.accessToken ?? (allowEnv ? stringSetting(runtime, "TWITCH_ACCESS_TOKEN") : undefined) ?? "",
|
|
110
|
+
clientSecret: account.clientSecret ?? base.clientSecret ?? (allowEnv ? stringSetting(runtime, "TWITCH_CLIENT_SECRET") : undefined),
|
|
111
|
+
refreshToken: account.refreshToken ?? base.refreshToken ?? (allowEnv ? stringSetting(runtime, "TWITCH_REFRESH_TOKEN") : undefined),
|
|
112
|
+
channel: account.channel ?? base.channel ?? (allowEnv ? stringSetting(runtime, "TWITCH_CHANNEL") : undefined) ?? "",
|
|
113
|
+
additionalChannels: stringList(account.additionalChannels ?? account.channels ?? base.additionalChannels ?? base.channels ?? (allowEnv ? stringSetting(runtime, "TWITCH_CHANNELS") : undefined)),
|
|
114
|
+
requireMention: boolValue(account.requireMention ?? base.requireMention ?? (allowEnv ? stringSetting(runtime, "TWITCH_REQUIRE_MENTION") : undefined)),
|
|
115
|
+
allowedRoles: roleList(account.allowedRoles ?? base.allowedRoles ?? (allowEnv ? stringSetting(runtime, "TWITCH_ALLOWED_ROLES") : undefined)),
|
|
116
|
+
allowedUserIds: stringList(account.allowedUserIds ?? base.allowedUserIds),
|
|
117
|
+
enabled: boolValue(account.enabled ?? base.enabled, true)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
4
120
|
// src/service.ts
|
|
5
121
|
import {
|
|
6
122
|
logger,
|
|
@@ -100,6 +216,59 @@ class TwitchApiError extends TwitchPluginError {
|
|
|
100
216
|
}
|
|
101
217
|
|
|
102
218
|
// src/service.ts
|
|
219
|
+
var TWITCH_CONNECTOR_CONTEXTS = ["social", "connectors"];
|
|
220
|
+
var TWITCH_CONNECTOR_CAPABILITIES = [
|
|
221
|
+
"send_message",
|
|
222
|
+
"resolve_targets",
|
|
223
|
+
"list_rooms",
|
|
224
|
+
"join",
|
|
225
|
+
"leave",
|
|
226
|
+
"chat_context"
|
|
227
|
+
];
|
|
228
|
+
function normalizeTwitchConnectorQuery(value) {
|
|
229
|
+
return normalizeChannel(value.trim().replace(/^@/, "")).toLowerCase();
|
|
230
|
+
}
|
|
231
|
+
function scoreTwitchChannelMatch(query, channel) {
|
|
232
|
+
const normalized = normalizeTwitchConnectorQuery(channel);
|
|
233
|
+
if (!query) {
|
|
234
|
+
return 0.45;
|
|
235
|
+
}
|
|
236
|
+
if (normalized === query) {
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
if (normalized.startsWith(query)) {
|
|
240
|
+
return 0.85;
|
|
241
|
+
}
|
|
242
|
+
if (normalized.includes(query)) {
|
|
243
|
+
return 0.7;
|
|
244
|
+
}
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
async function logTwurpleCall(op, context, fn) {
|
|
248
|
+
const startedAt = Date.now();
|
|
249
|
+
logger.debug({ sdk: "twurple", op, ...context }, `[TwitchService] ${op} started`);
|
|
250
|
+
try {
|
|
251
|
+
const result = await fn();
|
|
252
|
+
logger.info({
|
|
253
|
+
sdk: "twurple",
|
|
254
|
+
op,
|
|
255
|
+
...context,
|
|
256
|
+
durationMs: Date.now() - startedAt
|
|
257
|
+
}, `[TwitchService] ${op} ok`);
|
|
258
|
+
return result;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
261
|
+
logger.warn({
|
|
262
|
+
sdk: "twurple",
|
|
263
|
+
op,
|
|
264
|
+
...context,
|
|
265
|
+
durationMs: Date.now() - startedAt,
|
|
266
|
+
error: message
|
|
267
|
+
}, `[TwitchService] ${op} failed`);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
103
272
|
class TwitchService extends Service {
|
|
104
273
|
static serviceType = TWITCH_SERVICE_NAME;
|
|
105
274
|
capabilityDescription = "Provides Twitch chat integration for sending and receiving messages";
|
|
@@ -107,11 +276,46 @@ class TwitchService extends Service {
|
|
|
107
276
|
client;
|
|
108
277
|
connected = false;
|
|
109
278
|
joinedChannels = new Set;
|
|
279
|
+
accountServices = new Map;
|
|
110
280
|
static async start(runtime) {
|
|
111
281
|
const service = new TwitchService;
|
|
112
282
|
await service.initialize(runtime);
|
|
113
283
|
return service;
|
|
114
284
|
}
|
|
285
|
+
static registerSendHandlers(runtime, serviceInstance) {
|
|
286
|
+
if (!serviceInstance) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
for (const accountService of serviceInstance.getAccountServiceList()) {
|
|
290
|
+
const accountId = accountService.getAccountId(runtime);
|
|
291
|
+
const sendHandler = accountService.handleSendMessage.bind(accountService);
|
|
292
|
+
if (typeof runtime.registerMessageConnector === "function") {
|
|
293
|
+
runtime.registerMessageConnector({
|
|
294
|
+
source: "twitch",
|
|
295
|
+
accountId,
|
|
296
|
+
label: "Twitch",
|
|
297
|
+
description: "Twitch public chat connector for sending messages to joined channels.",
|
|
298
|
+
capabilities: [...TWITCH_CONNECTOR_CAPABILITIES],
|
|
299
|
+
supportedTargetKinds: ["channel"],
|
|
300
|
+
contexts: [...TWITCH_CONNECTOR_CONTEXTS],
|
|
301
|
+
metadata: {
|
|
302
|
+
accountId,
|
|
303
|
+
service: TWITCH_SERVICE_NAME,
|
|
304
|
+
maxMessageLength: MAX_TWITCH_MESSAGE_LENGTH
|
|
305
|
+
},
|
|
306
|
+
resolveTargets: accountService.resolveConnectorTargets.bind(accountService),
|
|
307
|
+
listRecentTargets: accountService.listRecentConnectorTargets.bind(accountService),
|
|
308
|
+
listRooms: accountService.listConnectorRooms.bind(accountService),
|
|
309
|
+
joinHandler: accountService.handleJoinChannel.bind(accountService),
|
|
310
|
+
leaveHandler: accountService.handleLeaveChannel.bind(accountService),
|
|
311
|
+
getChatContext: accountService.getConnectorChatContext.bind(accountService),
|
|
312
|
+
sendHandler
|
|
313
|
+
});
|
|
314
|
+
runtime.logger.info({ src: "plugin:twitch", agentId: runtime.agentId }, "Registered Twitch chat connector");
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
115
319
|
static async stopRuntime(runtime) {
|
|
116
320
|
const service = runtime.getService(TWITCH_SERVICE_NAME);
|
|
117
321
|
if (service) {
|
|
@@ -120,7 +324,26 @@ class TwitchService extends Service {
|
|
|
120
324
|
}
|
|
121
325
|
async initialize(runtime) {
|
|
122
326
|
this.runtime = runtime;
|
|
123
|
-
|
|
327
|
+
const startedAccounts = [];
|
|
328
|
+
for (const accountId of listTwitchAccountIds(runtime)) {
|
|
329
|
+
const settings = resolveTwitchAccountSettings(runtime, accountId);
|
|
330
|
+
if (settings.enabled === false) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const accountService = new TwitchService;
|
|
334
|
+
await accountService.initializeAccount(runtime, accountId);
|
|
335
|
+
this.accountServices.set(accountService.getAccountId(), accountService);
|
|
336
|
+
startedAccounts.push(accountService.getAccountId());
|
|
337
|
+
}
|
|
338
|
+
if (startedAccounts.length === 0) {
|
|
339
|
+
logger.warn("No enabled Twitch accounts configured");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
logger.info(`Twitch service started ${startedAccounts.length} account(s): ${startedAccounts.join(", ")}`);
|
|
343
|
+
}
|
|
344
|
+
async initializeAccount(runtime, accountId) {
|
|
345
|
+
this.runtime = runtime;
|
|
346
|
+
this.settings = this.loadSettings(accountId);
|
|
124
347
|
this.validateSettings();
|
|
125
348
|
const authProvider = await this.createAuthProvider();
|
|
126
349
|
const allChannels = [
|
|
@@ -136,31 +359,8 @@ class TwitchService extends Service {
|
|
|
136
359
|
await this.connect();
|
|
137
360
|
logger.info(`Twitch service initialized for ${this.settings.username}, joined channels: ${allChannels.join(", ")}`);
|
|
138
361
|
}
|
|
139
|
-
loadSettings() {
|
|
140
|
-
|
|
141
|
-
const clientId = this.runtime.getSetting("TWITCH_CLIENT_ID");
|
|
142
|
-
const accessToken = this.runtime.getSetting("TWITCH_ACCESS_TOKEN");
|
|
143
|
-
const clientSecret = this.runtime.getSetting("TWITCH_CLIENT_SECRET");
|
|
144
|
-
const refreshToken = this.runtime.getSetting("TWITCH_REFRESH_TOKEN");
|
|
145
|
-
const channel = this.runtime.getSetting("TWITCH_CHANNEL");
|
|
146
|
-
const additionalChannelsStr = this.runtime.getSetting("TWITCH_CHANNELS");
|
|
147
|
-
const requireMentionStr = this.runtime.getSetting("TWITCH_REQUIRE_MENTION");
|
|
148
|
-
const allowedRolesStr = this.runtime.getSetting("TWITCH_ALLOWED_ROLES");
|
|
149
|
-
const additionalChannels = typeof additionalChannelsStr === "string" && additionalChannelsStr ? additionalChannelsStr.split(",").map((c) => c.trim()).filter(Boolean) : [];
|
|
150
|
-
const allowedRoles = typeof allowedRolesStr === "string" && allowedRolesStr ? allowedRolesStr.split(",").map((r) => r.trim().toLowerCase()) : ["all"];
|
|
151
|
-
return {
|
|
152
|
-
username: typeof username === "string" ? username : "",
|
|
153
|
-
clientId: typeof clientId === "string" ? clientId : "",
|
|
154
|
-
accessToken: typeof accessToken === "string" ? accessToken : "",
|
|
155
|
-
clientSecret: typeof clientSecret === "string" ? clientSecret : undefined,
|
|
156
|
-
refreshToken: typeof refreshToken === "string" ? refreshToken : undefined,
|
|
157
|
-
channel: typeof channel === "string" ? channel : "",
|
|
158
|
-
additionalChannels,
|
|
159
|
-
requireMention: requireMentionStr === "true",
|
|
160
|
-
allowedRoles,
|
|
161
|
-
allowedUserIds: [],
|
|
162
|
-
enabled: true
|
|
163
|
-
};
|
|
362
|
+
loadSettings(accountId) {
|
|
363
|
+
return resolveTwitchAccountSettings(this.runtime, accountId);
|
|
164
364
|
}
|
|
165
365
|
validateSettings() {
|
|
166
366
|
if (!this.settings.username) {
|
|
@@ -209,7 +409,8 @@ class TwitchService extends Service {
|
|
|
209
409
|
this.connected = true;
|
|
210
410
|
logger.info("Twitch chat connected");
|
|
211
411
|
this.runtime.emitEvent("TWITCH_CONNECTION_READY" /* CONNECTION_READY */, {
|
|
212
|
-
runtime: this.runtime
|
|
412
|
+
runtime: this.runtime,
|
|
413
|
+
accountId: this.getAccountId()
|
|
213
414
|
});
|
|
214
415
|
});
|
|
215
416
|
this.client.onDisconnect((_manually, reason) => {
|
|
@@ -217,6 +418,7 @@ class TwitchService extends Service {
|
|
|
217
418
|
logger.warn(`Twitch chat disconnected: ${reason || "unknown reason"}`);
|
|
218
419
|
this.runtime.emitEvent("TWITCH_CONNECTION_LOST" /* CONNECTION_LOST */, {
|
|
219
420
|
runtime: this.runtime,
|
|
421
|
+
accountId: this.getAccountId(),
|
|
220
422
|
reason
|
|
221
423
|
});
|
|
222
424
|
});
|
|
@@ -227,6 +429,7 @@ class TwitchService extends Service {
|
|
|
227
429
|
logger.info(`Joined Twitch channel: ${normalized}`);
|
|
228
430
|
this.runtime.emitEvent("TWITCH_JOIN_CHANNEL" /* JOIN_CHANNEL */, {
|
|
229
431
|
runtime: this.runtime,
|
|
432
|
+
accountId: this.getAccountId(),
|
|
230
433
|
channel: normalized
|
|
231
434
|
});
|
|
232
435
|
}
|
|
@@ -238,6 +441,7 @@ class TwitchService extends Service {
|
|
|
238
441
|
logger.info(`Left Twitch channel: ${normalized}`);
|
|
239
442
|
this.runtime.emitEvent("TWITCH_LEAVE_CHANNEL" /* LEAVE_CHANNEL */, {
|
|
240
443
|
runtime: this.runtime,
|
|
444
|
+
accountId: this.getAccountId(),
|
|
241
445
|
channel: normalized
|
|
242
446
|
});
|
|
243
447
|
}
|
|
@@ -289,6 +493,7 @@ class TwitchService extends Service {
|
|
|
289
493
|
logger.debug(`Twitch message from ${userInfo.displayName} in #${normalizedChannel}: ${text.slice(0, 50)}...`);
|
|
290
494
|
this.runtime.emitEvent("TWITCH_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, {
|
|
291
495
|
runtime: this.runtime,
|
|
496
|
+
accountId: this.getAccountId(),
|
|
292
497
|
message
|
|
293
498
|
});
|
|
294
499
|
}
|
|
@@ -297,6 +502,12 @@ class TwitchService extends Service {
|
|
|
297
502
|
this.connected = true;
|
|
298
503
|
}
|
|
299
504
|
async stop() {
|
|
505
|
+
if (this.accountServices?.size > 0) {
|
|
506
|
+
await Promise.all(Array.from(this.accountServices.values()).map((service) => service.stop()));
|
|
507
|
+
this.accountServices.clear();
|
|
508
|
+
logger.info("Twitch service stopped");
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
300
511
|
if (this.client) {
|
|
301
512
|
this.client.quit();
|
|
302
513
|
}
|
|
@@ -304,18 +515,200 @@ class TwitchService extends Service {
|
|
|
304
515
|
this.joinedChannels.clear();
|
|
305
516
|
logger.info("Twitch service stopped");
|
|
306
517
|
}
|
|
518
|
+
getAccountServiceList() {
|
|
519
|
+
return this.accountServices?.size > 0 ? Array.from(this.accountServices.values()) : [this];
|
|
520
|
+
}
|
|
521
|
+
getDefaultAccountService() {
|
|
522
|
+
if (!this.accountServices || this.accountServices.size === 0) {
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
const defaultAccountId = normalizeTwitchAccountId(resolveDefaultTwitchAccountId(this.runtime));
|
|
526
|
+
return this.accountServices.get(defaultAccountId) ?? Array.from(this.accountServices.values())[0];
|
|
527
|
+
}
|
|
528
|
+
getAccountService(accountId) {
|
|
529
|
+
if (!this.accountServices || this.accountServices.size === 0) {
|
|
530
|
+
const ownAccountId = this.getAccountId();
|
|
531
|
+
if (normalizeTwitchAccountId(accountId) !== ownAccountId) {
|
|
532
|
+
throw new Error(`Twitch account '${accountId}' is not available in this service instance`);
|
|
533
|
+
}
|
|
534
|
+
return this;
|
|
535
|
+
}
|
|
536
|
+
const normalized = normalizeTwitchAccountId(accountId);
|
|
537
|
+
const service = this.accountServices.get(normalized);
|
|
538
|
+
if (!service) {
|
|
539
|
+
throw new Error(`Twitch account '${normalized}' is not available`);
|
|
540
|
+
}
|
|
541
|
+
return service;
|
|
542
|
+
}
|
|
307
543
|
isConnected() {
|
|
544
|
+
if (this.accountServices?.size > 0) {
|
|
545
|
+
return Array.from(this.accountServices.values()).some((service) => service.isConnected());
|
|
546
|
+
}
|
|
308
547
|
return this.connected;
|
|
309
548
|
}
|
|
310
549
|
getBotUsername() {
|
|
550
|
+
if (this.accountServices?.size > 0) {
|
|
551
|
+
return this.getDefaultAccountService().getBotUsername();
|
|
552
|
+
}
|
|
311
553
|
return this.settings.username;
|
|
312
554
|
}
|
|
555
|
+
getAccountId(runtime) {
|
|
556
|
+
if (this.accountServices?.size > 0) {
|
|
557
|
+
return this.getDefaultAccountService().getAccountId(runtime);
|
|
558
|
+
}
|
|
559
|
+
return normalizeTwitchAccountId(this.settings?.accountId ?? (runtime ? resolveDefaultTwitchAccountId(runtime) : undefined));
|
|
560
|
+
}
|
|
313
561
|
getPrimaryChannel() {
|
|
562
|
+
if (this.accountServices?.size > 0) {
|
|
563
|
+
return this.getDefaultAccountService().getPrimaryChannel();
|
|
564
|
+
}
|
|
314
565
|
return this.settings.channel;
|
|
315
566
|
}
|
|
316
567
|
getJoinedChannels() {
|
|
568
|
+
if (this.accountServices?.size > 0) {
|
|
569
|
+
return this.getDefaultAccountService().getJoinedChannels();
|
|
570
|
+
}
|
|
317
571
|
return Array.from(this.joinedChannels);
|
|
318
572
|
}
|
|
573
|
+
async handleSendMessage(runtime, target, content) {
|
|
574
|
+
const requestedAccountId = normalizeTwitchAccountId(target.accountId ?? readTwitchAccountId(content, target) ?? this.getAccountId());
|
|
575
|
+
if (this.accountServices?.size > 0) {
|
|
576
|
+
await this.getAccountService(requestedAccountId).handleSendMessage(runtime, target, content);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (requestedAccountId !== this.getAccountId()) {
|
|
580
|
+
throw new Error(`Twitch account '${requestedAccountId}' is not available in this service instance`);
|
|
581
|
+
}
|
|
582
|
+
const text = typeof content.text === "string" ? content.text.trim() : "";
|
|
583
|
+
if (!text) {
|
|
584
|
+
throw new Error("Twitch connector requires non-empty text content.");
|
|
585
|
+
}
|
|
586
|
+
let channel = target.channelId;
|
|
587
|
+
let replyTo = target.threadId;
|
|
588
|
+
if (target.roomId && !channel) {
|
|
589
|
+
const room = await runtime.getRoom(target.roomId);
|
|
590
|
+
channel = room?.channelId;
|
|
591
|
+
const metadata = room?.metadata;
|
|
592
|
+
replyTo = replyTo ?? (typeof metadata?.twitchReplyTo === "string" ? metadata.twitchReplyTo : undefined);
|
|
593
|
+
}
|
|
594
|
+
await this.sendMessage(text, {
|
|
595
|
+
channel: channel ? normalizeChannel(channel) : this.getPrimaryChannel(),
|
|
596
|
+
replyTo
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
async resolveConnectorTargets(query, _context) {
|
|
600
|
+
const normalizedQuery = normalizeTwitchConnectorQuery(query);
|
|
601
|
+
return this.getConnectorChannels().map((channel) => {
|
|
602
|
+
const score = scoreTwitchChannelMatch(normalizedQuery, channel);
|
|
603
|
+
return score > 0 ? this.buildChannelTarget(channel, score) : null;
|
|
604
|
+
}).filter((target) => Boolean(target)).slice(0, 25);
|
|
605
|
+
}
|
|
606
|
+
async listConnectorRooms(_context) {
|
|
607
|
+
return this.getConnectorChannels().map((channel) => this.buildChannelTarget(channel, 0.5)).slice(0, 50);
|
|
608
|
+
}
|
|
609
|
+
async listRecentConnectorTargets(context) {
|
|
610
|
+
const targets = [];
|
|
611
|
+
const room = context.roomId && typeof context.runtime.getRoom === "function" ? await context.runtime.getRoom(context.roomId) : null;
|
|
612
|
+
const channel = context.target?.channelId ?? room?.channelId;
|
|
613
|
+
if (channel) {
|
|
614
|
+
targets.push(this.buildChannelTarget(channel, 0.95));
|
|
615
|
+
}
|
|
616
|
+
targets.push(...await this.listConnectorRooms(context));
|
|
617
|
+
const seen = new Set;
|
|
618
|
+
return targets.filter((target) => {
|
|
619
|
+
const channelId = target.target.channelId;
|
|
620
|
+
if (!channelId || seen.has(channelId)) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
seen.add(channelId);
|
|
624
|
+
return true;
|
|
625
|
+
}).slice(0, 25);
|
|
626
|
+
}
|
|
627
|
+
async handleJoinChannel(_runtime, params) {
|
|
628
|
+
const channel = this.resolveChannelOpTarget(params);
|
|
629
|
+
if (!channel) {
|
|
630
|
+
throw new Error("Twitch MESSAGE operation=join requires channelId, alias, or target channel.");
|
|
631
|
+
}
|
|
632
|
+
if (this.joinedChannels.has(channel)) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
await this.joinChannel(channel);
|
|
636
|
+
}
|
|
637
|
+
async handleLeaveChannel(_runtime, params) {
|
|
638
|
+
const channel = this.resolveChannelOpTarget(params);
|
|
639
|
+
if (!channel) {
|
|
640
|
+
throw new Error("Twitch MESSAGE operation=leave requires channelId, alias, or target channel.");
|
|
641
|
+
}
|
|
642
|
+
if (channel === normalizeChannel(this.getPrimaryChannel())) {
|
|
643
|
+
throw new Error(`Cannot leave the primary Twitch channel #${channel}.`);
|
|
644
|
+
}
|
|
645
|
+
if (!this.joinedChannels.has(channel)) {
|
|
646
|
+
throw new Error(`Not currently in Twitch channel #${channel}.`);
|
|
647
|
+
}
|
|
648
|
+
await this.leaveChannel(channel);
|
|
649
|
+
}
|
|
650
|
+
async getConnectorChatContext(target, context) {
|
|
651
|
+
let channel = target.channelId;
|
|
652
|
+
if (!channel && target.roomId) {
|
|
653
|
+
const room = await context.runtime.getRoom(target.roomId);
|
|
654
|
+
channel = room?.channelId;
|
|
655
|
+
}
|
|
656
|
+
channel = channel ? normalizeChannel(channel) : this.getPrimaryChannel();
|
|
657
|
+
return {
|
|
658
|
+
target: {
|
|
659
|
+
source: "twitch",
|
|
660
|
+
accountId: this.getAccountId(),
|
|
661
|
+
channelId: channel
|
|
662
|
+
},
|
|
663
|
+
label: formatChannelForDisplay(channel),
|
|
664
|
+
summary: "Twitch chat messages are public and visible to viewers in the channel.",
|
|
665
|
+
metadata: {
|
|
666
|
+
accountId: this.getAccountId(),
|
|
667
|
+
twitchChannel: channel,
|
|
668
|
+
botUsername: this.getBotUsername(),
|
|
669
|
+
joined: this.joinedChannels.has(channel)
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
getConnectorChannels() {
|
|
674
|
+
const channels = new Set;
|
|
675
|
+
if (this.settings?.channel) {
|
|
676
|
+
channels.add(normalizeChannel(this.settings.channel));
|
|
677
|
+
}
|
|
678
|
+
for (const channel of this.settings?.additionalChannels ?? []) {
|
|
679
|
+
channels.add(normalizeChannel(channel));
|
|
680
|
+
}
|
|
681
|
+
for (const channel of this.joinedChannels) {
|
|
682
|
+
channels.add(normalizeChannel(channel));
|
|
683
|
+
}
|
|
684
|
+
return Array.from(channels);
|
|
685
|
+
}
|
|
686
|
+
resolveChannelOpTarget(params) {
|
|
687
|
+
const targetRecord = params.target;
|
|
688
|
+
const raw = params.target?.channelId ?? params.channelId ?? params.alias ?? targetRecord?.alias ?? targetRecord?.name;
|
|
689
|
+
return typeof raw === "string" && raw.trim() ? normalizeChannel(raw.trim()) : null;
|
|
690
|
+
}
|
|
691
|
+
buildChannelTarget(channel, score) {
|
|
692
|
+
const normalized = normalizeChannel(channel);
|
|
693
|
+
return {
|
|
694
|
+
target: {
|
|
695
|
+
source: "twitch",
|
|
696
|
+
accountId: this.getAccountId(),
|
|
697
|
+
channelId: normalized
|
|
698
|
+
},
|
|
699
|
+
label: formatChannelForDisplay(normalized),
|
|
700
|
+
kind: "channel",
|
|
701
|
+
description: "Twitch public chat channel",
|
|
702
|
+
score,
|
|
703
|
+
contexts: [...TWITCH_CONNECTOR_CONTEXTS],
|
|
704
|
+
metadata: {
|
|
705
|
+
accountId: this.getAccountId(),
|
|
706
|
+
twitchChannel: normalized,
|
|
707
|
+
joined: this.joinedChannels.has(normalized),
|
|
708
|
+
primary: normalized === this.getPrimaryChannel()
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
319
712
|
isUserAllowed(user) {
|
|
320
713
|
if (this.settings.allowedUserIds.length > 0 && !this.settings.allowedUserIds.includes(user.userId)) {
|
|
321
714
|
return false;
|
|
@@ -338,6 +731,10 @@ class TwitchService extends Service {
|
|
|
338
731
|
return false;
|
|
339
732
|
}
|
|
340
733
|
async sendMessage(text, options) {
|
|
734
|
+
if (this.accountServices?.size > 0) {
|
|
735
|
+
const accountId = normalizeTwitchAccountId(options?.accountId ?? this.getAccountId());
|
|
736
|
+
return this.getAccountService(accountId).sendMessage(text, options);
|
|
737
|
+
}
|
|
341
738
|
if (!this.connected) {
|
|
342
739
|
throw new TwitchNotConnectedError;
|
|
343
740
|
}
|
|
@@ -349,11 +746,15 @@ class TwitchService extends Service {
|
|
|
349
746
|
const chunks = splitMessageForTwitch(cleanedText);
|
|
350
747
|
let lastMessageId;
|
|
351
748
|
for (const chunk of chunks) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
749
|
+
await logTwurpleCall("say", { channel, chunkLen: chunk.length, replyTo: options?.replyTo }, async () => {
|
|
750
|
+
if (options?.replyTo) {
|
|
751
|
+
await this.client.say(channel, chunk, {
|
|
752
|
+
replyTo: options.replyTo
|
|
753
|
+
});
|
|
754
|
+
} else {
|
|
755
|
+
await this.client.say(channel, chunk);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
357
758
|
lastMessageId = crypto.randomUUID();
|
|
358
759
|
if (chunks.length > 1) {
|
|
359
760
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -361,6 +762,7 @@ class TwitchService extends Service {
|
|
|
361
762
|
}
|
|
362
763
|
this.runtime.emitEvent("TWITCH_MESSAGE_SENT" /* MESSAGE_SENT */, {
|
|
363
764
|
runtime: this.runtime,
|
|
765
|
+
accountId: this.getAccountId(),
|
|
364
766
|
channel,
|
|
365
767
|
text: cleanedText,
|
|
366
768
|
messageId: lastMessageId
|
|
@@ -368,672 +770,135 @@ class TwitchService extends Service {
|
|
|
368
770
|
return { success: true, messageId: lastMessageId };
|
|
369
771
|
}
|
|
370
772
|
async joinChannel(channel) {
|
|
773
|
+
if (this.accountServices?.size > 0) {
|
|
774
|
+
await this.getDefaultAccountService().joinChannel(channel);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
371
777
|
const normalized = normalizeChannel(channel);
|
|
372
|
-
await
|
|
778
|
+
await logTwurpleCall("join", { channel: normalized }, async () => {
|
|
779
|
+
await this.client.join(normalized);
|
|
780
|
+
});
|
|
373
781
|
this.joinedChannels.add(normalized);
|
|
374
782
|
}
|
|
375
783
|
async leaveChannel(channel) {
|
|
784
|
+
if (this.accountServices?.size > 0) {
|
|
785
|
+
await this.getDefaultAccountService().leaveChannel(channel);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
376
788
|
const normalized = normalizeChannel(channel);
|
|
377
|
-
await
|
|
789
|
+
await logTwurpleCall("part", { channel: normalized }, async () => {
|
|
790
|
+
await this.client.part(normalized);
|
|
791
|
+
});
|
|
378
792
|
this.joinedChannels.delete(normalized);
|
|
379
793
|
}
|
|
380
794
|
}
|
|
381
|
-
// src/
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
795
|
+
// src/connector-account-provider.ts
|
|
796
|
+
var TWITCH_PROVIDER_ID = "twitch";
|
|
797
|
+
function toConnectorAccount(settings) {
|
|
798
|
+
const now = Date.now();
|
|
799
|
+
const configured = Boolean(settings.username && settings.clientId && settings.accessToken);
|
|
800
|
+
return {
|
|
801
|
+
id: normalizeTwitchAccountId(settings.accountId),
|
|
802
|
+
provider: TWITCH_PROVIDER_ID,
|
|
803
|
+
label: settings.username || settings.channel || settings.accountId,
|
|
804
|
+
role: "OWNER",
|
|
805
|
+
purpose: ["messaging"],
|
|
806
|
+
accessGate: "open",
|
|
807
|
+
status: settings.enabled !== false && configured ? "connected" : "disabled",
|
|
808
|
+
externalId: settings.username || undefined,
|
|
809
|
+
displayHandle: settings.username || undefined,
|
|
810
|
+
createdAt: now,
|
|
811
|
+
updatedAt: now,
|
|
812
|
+
metadata: {
|
|
813
|
+
channel: settings.channel ?? "",
|
|
814
|
+
additionalChannels: settings.additionalChannels ?? [],
|
|
815
|
+
requireMention: settings.requireMention ?? false,
|
|
816
|
+
hasRefreshToken: Boolean(settings.refreshToken)
|
|
817
|
+
}
|
|
818
|
+
};
|
|
399
819
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw)) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
411
|
-
const __avRegex = new RegExp("\\b(?:twitch|join|channel)\\b", "i");
|
|
412
|
-
const __avRegexOk = __avRegex.test(__avText) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
413
|
-
const __avSource = String(message?.content?.source ?? message?.source ?? "");
|
|
414
|
-
const __avExpectedSource = "twitch";
|
|
415
|
-
const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
416
|
-
const __avOptions = options && typeof options === "object" ? options : {};
|
|
417
|
-
const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object") || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
418
|
-
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
const __avLegacyValidate = async (_runtime, message2, _state) => {
|
|
422
|
-
return message2.content.source === "twitch";
|
|
423
|
-
};
|
|
424
|
-
try {
|
|
425
|
-
return Boolean(await __avLegacyValidate(runtime, message, state, options));
|
|
426
|
-
} catch {
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
},
|
|
430
|
-
handler: async (runtime, message, state, _options, callback) => {
|
|
431
|
-
const twitchService = runtime.getService(TWITCH_SERVICE_NAME);
|
|
432
|
-
if (!twitchService || !twitchService.isConnected()) {
|
|
433
|
-
if (callback) {
|
|
434
|
-
callback({
|
|
435
|
-
text: "Twitch service is not available.",
|
|
436
|
-
source: "twitch"
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
return { success: false, error: "Twitch service not available" };
|
|
440
|
-
}
|
|
441
|
-
const currentState = state ?? await runtime.composeState(message);
|
|
442
|
-
const prompt = await composePromptFromState({
|
|
443
|
-
template: JOIN_CHANNEL_TEMPLATE,
|
|
444
|
-
state: currentState
|
|
445
|
-
});
|
|
446
|
-
let channelName = null;
|
|
447
|
-
for (let attempt = 0;attempt < 3; attempt++) {
|
|
448
|
-
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
449
|
-
prompt
|
|
450
|
-
});
|
|
451
|
-
const parsed = parseJSONObjectFromText(String(response));
|
|
452
|
-
if (parsed?.channel) {
|
|
453
|
-
channelName = normalizeChannel(String(parsed.channel));
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
if (!channelName) {
|
|
458
|
-
if (callback) {
|
|
459
|
-
callback({
|
|
460
|
-
text: "I couldn't understand which channel you want me to join. Please specify the channel name.",
|
|
461
|
-
source: "twitch"
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
return { success: false, error: "Could not extract channel name" };
|
|
465
|
-
}
|
|
466
|
-
if (twitchService.getJoinedChannels().includes(channelName)) {
|
|
467
|
-
if (callback) {
|
|
468
|
-
callback({
|
|
469
|
-
text: `Already in channel #${channelName}.`,
|
|
470
|
-
source: "twitch"
|
|
471
|
-
});
|
|
820
|
+
function createTwitchConnectorAccountProvider(runtime) {
|
|
821
|
+
return {
|
|
822
|
+
provider: TWITCH_PROVIDER_ID,
|
|
823
|
+
label: "Twitch",
|
|
824
|
+
listAccounts: async (_manager) => {
|
|
825
|
+
const ids = listTwitchAccountIds(runtime);
|
|
826
|
+
if (ids.length === 0) {
|
|
827
|
+
return [
|
|
828
|
+
toConnectorAccount(resolveTwitchAccountSettings(runtime, DEFAULT_TWITCH_ACCOUNT_ID))
|
|
829
|
+
];
|
|
472
830
|
}
|
|
831
|
+
return ids.map((id) => toConnectorAccount(resolveTwitchAccountSettings(runtime, id)));
|
|
832
|
+
},
|
|
833
|
+
createAccount: async (input, _manager) => {
|
|
473
834
|
return {
|
|
474
|
-
|
|
475
|
-
|
|
835
|
+
...input,
|
|
836
|
+
provider: TWITCH_PROVIDER_ID,
|
|
837
|
+
role: input.role ?? "OWNER",
|
|
838
|
+
purpose: input.purpose ?? ["messaging"],
|
|
839
|
+
accessGate: input.accessGate ?? "open",
|
|
840
|
+
status: input.status ?? "pending"
|
|
476
841
|
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
success: true,
|
|
487
|
-
data: {
|
|
488
|
-
channel: channelName
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
},
|
|
492
|
-
examples: [
|
|
493
|
-
[
|
|
494
|
-
{
|
|
495
|
-
name: "{{user1}}",
|
|
496
|
-
content: { text: "Join the channel shroud" }
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
name: "{{agent}}",
|
|
500
|
-
content: {
|
|
501
|
-
text: "I'll join that channel.",
|
|
502
|
-
actions: ["TWITCH_JOIN_CHANNEL"]
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
]
|
|
506
|
-
]
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
// src/actions/leaveChannel.ts
|
|
510
|
-
import {
|
|
511
|
-
composePromptFromState as composePromptFromState2,
|
|
512
|
-
ModelType as ModelType2,
|
|
513
|
-
parseJSONObjectFromText as parseJSONObjectFromText2
|
|
514
|
-
} from "@elizaos/core";
|
|
515
|
-
var LEAVE_CHANNEL_TEMPLATE = `You are helping to extract a Twitch channel name.
|
|
516
|
-
|
|
517
|
-
The user wants to leave a Twitch channel.
|
|
518
|
-
|
|
519
|
-
Recent conversation:
|
|
520
|
-
{{recentMessages}}
|
|
521
|
-
|
|
522
|
-
Currently joined channels: {{joinedChannels}}
|
|
523
|
-
|
|
524
|
-
Extract the channel name to leave (without the # prefix).
|
|
525
|
-
|
|
526
|
-
Respond with a JSON object like:
|
|
527
|
-
{
|
|
528
|
-
"channel": "channelname"
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
Only respond with the JSON object, no other text.`;
|
|
532
|
-
var leaveChannel = {
|
|
533
|
-
name: "TWITCH_LEAVE_CHANNEL",
|
|
534
|
-
similes: [
|
|
535
|
-
"LEAVE_TWITCH_CHANNEL",
|
|
536
|
-
"EXIT_CHANNEL",
|
|
537
|
-
"PART_CHANNEL",
|
|
538
|
-
"DISCONNECT_CHANNEL"
|
|
539
|
-
],
|
|
540
|
-
description: "Leave a Twitch channel",
|
|
541
|
-
validate: async (runtime, message, state, options) => {
|
|
542
|
-
const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
|
|
543
|
-
const __avText = __avTextRaw.toLowerCase();
|
|
544
|
-
const __avKeywords = ["twitch", "leave", "channel"];
|
|
545
|
-
const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw)) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
546
|
-
const __avRegex = new RegExp("\\b(?:twitch|leave|channel)\\b", "i");
|
|
547
|
-
const __avRegexOk = __avRegex.test(__avText) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
548
|
-
const __avSource = String(message?.content?.source ?? message?.source ?? "");
|
|
549
|
-
const __avExpectedSource = "twitch";
|
|
550
|
-
const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
551
|
-
const __avOptions = options && typeof options === "object" ? options : {};
|
|
552
|
-
const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object") || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
553
|
-
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
554
|
-
return false;
|
|
555
|
-
}
|
|
556
|
-
const __avLegacyValidate = async (_runtime, message2, _state) => {
|
|
557
|
-
return message2.content.source === "twitch";
|
|
558
|
-
};
|
|
559
|
-
try {
|
|
560
|
-
return Boolean(await __avLegacyValidate(runtime, message, state, options));
|
|
561
|
-
} catch {
|
|
562
|
-
return false;
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
handler: async (runtime, message, state, _options, callback) => {
|
|
566
|
-
const twitchService = runtime.getService(TWITCH_SERVICE_NAME);
|
|
567
|
-
if (!twitchService || !twitchService.isConnected()) {
|
|
568
|
-
if (callback) {
|
|
569
|
-
callback({
|
|
570
|
-
text: "Twitch service is not available.",
|
|
571
|
-
source: "twitch"
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
return { success: false, error: "Twitch service not available" };
|
|
575
|
-
}
|
|
576
|
-
const joinedChannels = twitchService.getJoinedChannels();
|
|
577
|
-
const currentState = state ?? await runtime.composeState(message);
|
|
578
|
-
const enrichedState = {
|
|
579
|
-
...currentState,
|
|
580
|
-
joinedChannels: joinedChannels.join(", ")
|
|
581
|
-
};
|
|
582
|
-
const prompt = await composePromptFromState2({
|
|
583
|
-
template: LEAVE_CHANNEL_TEMPLATE,
|
|
584
|
-
state: enrichedState
|
|
585
|
-
});
|
|
586
|
-
let channelName = null;
|
|
587
|
-
for (let attempt = 0;attempt < 3; attempt++) {
|
|
588
|
-
const response = await runtime.useModel(ModelType2.TEXT_SMALL, {
|
|
589
|
-
prompt
|
|
590
|
-
});
|
|
591
|
-
const parsed = parseJSONObjectFromText2(String(response));
|
|
592
|
-
if (parsed?.channel) {
|
|
593
|
-
channelName = normalizeChannel(String(parsed.channel));
|
|
594
|
-
break;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
if (!channelName) {
|
|
598
|
-
if (callback) {
|
|
599
|
-
callback({
|
|
600
|
-
text: "I couldn't understand which channel you want me to leave. Please specify the channel name.",
|
|
601
|
-
source: "twitch"
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
return { success: false, error: "Could not extract channel name" };
|
|
605
|
-
}
|
|
606
|
-
if (!joinedChannels.includes(channelName)) {
|
|
607
|
-
if (callback) {
|
|
608
|
-
callback({
|
|
609
|
-
text: `Not currently in channel #${channelName}.`,
|
|
610
|
-
source: "twitch"
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
return { success: false, error: "Not in that channel" };
|
|
614
|
-
}
|
|
615
|
-
if (channelName === twitchService.getPrimaryChannel()) {
|
|
616
|
-
if (callback) {
|
|
617
|
-
callback({
|
|
618
|
-
text: `Cannot leave the primary channel #${channelName}.`,
|
|
619
|
-
source: "twitch"
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
return { success: false, error: "Cannot leave primary channel" };
|
|
623
|
-
}
|
|
624
|
-
await twitchService.leaveChannel(channelName);
|
|
625
|
-
if (callback) {
|
|
626
|
-
callback({
|
|
627
|
-
text: `Left channel #${channelName}.`,
|
|
628
|
-
source: message.content.source
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
return {
|
|
632
|
-
success: true,
|
|
633
|
-
data: {
|
|
634
|
-
channel: channelName
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
},
|
|
638
|
-
examples: [
|
|
639
|
-
[
|
|
640
|
-
{
|
|
641
|
-
name: "{{user1}}",
|
|
642
|
-
content: { text: "Leave the channel shroud" }
|
|
643
|
-
},
|
|
644
|
-
{
|
|
645
|
-
name: "{{agent}}",
|
|
646
|
-
content: {
|
|
647
|
-
text: "I'll leave that channel.",
|
|
648
|
-
actions: ["TWITCH_LEAVE_CHANNEL"]
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
]
|
|
652
|
-
]
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
// src/actions/listChannels.ts
|
|
656
|
-
var listChannels = {
|
|
657
|
-
name: "TWITCH_LIST_CHANNELS",
|
|
658
|
-
similes: [
|
|
659
|
-
"LIST_TWITCH_CHANNELS",
|
|
660
|
-
"SHOW_CHANNELS",
|
|
661
|
-
"GET_CHANNELS",
|
|
662
|
-
"CURRENT_CHANNELS"
|
|
663
|
-
],
|
|
664
|
-
description: "List all Twitch channels the bot is currently in",
|
|
665
|
-
validate: async (runtime, message, state, options) => {
|
|
666
|
-
const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
|
|
667
|
-
const __avText = __avTextRaw.toLowerCase();
|
|
668
|
-
const __avKeywords = ["twitch", "list", "channels"];
|
|
669
|
-
const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw)) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
670
|
-
const __avRegex = new RegExp("\\b(?:twitch|list|channels)\\b", "i");
|
|
671
|
-
const __avRegexOk = __avRegex.test(__avText) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
672
|
-
const __avSource = String(message?.content?.source ?? message?.source ?? "");
|
|
673
|
-
const __avExpectedSource = "twitch";
|
|
674
|
-
const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
675
|
-
const __avOptions = options && typeof options === "object" ? options : {};
|
|
676
|
-
const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object") || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
677
|
-
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
const __avLegacyValidate = async (_runtime, message2, _state) => {
|
|
681
|
-
return message2.content.source === "twitch";
|
|
682
|
-
};
|
|
683
|
-
try {
|
|
684
|
-
return Boolean(await __avLegacyValidate(runtime, message, state, options));
|
|
685
|
-
} catch {
|
|
686
|
-
return false;
|
|
687
|
-
}
|
|
688
|
-
},
|
|
689
|
-
handler: async (runtime, message, _state, _options, callback) => {
|
|
690
|
-
const twitchService = runtime.getService(TWITCH_SERVICE_NAME);
|
|
691
|
-
if (!twitchService || !twitchService.isConnected()) {
|
|
692
|
-
if (callback) {
|
|
693
|
-
callback({
|
|
694
|
-
text: "Twitch service is not available.",
|
|
695
|
-
source: "twitch"
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
return { success: false, error: "Twitch service not available" };
|
|
699
|
-
}
|
|
700
|
-
const joinedChannels = twitchService.getJoinedChannels();
|
|
701
|
-
const primaryChannel = twitchService.getPrimaryChannel();
|
|
702
|
-
const channelList = joinedChannels.map((channel) => {
|
|
703
|
-
const displayName = formatChannelForDisplay(channel);
|
|
704
|
-
const isPrimary = channel === primaryChannel;
|
|
705
|
-
return isPrimary ? `${displayName} (primary)` : displayName;
|
|
706
|
-
});
|
|
707
|
-
const responseText = joinedChannels.length > 0 ? `Currently in ${joinedChannels.length} channel(s):
|
|
708
|
-
${channelList.map((c) => `• ${c}`).join(`
|
|
709
|
-
`)}` : "Not currently in any channels.";
|
|
710
|
-
if (callback) {
|
|
711
|
-
callback({
|
|
712
|
-
text: responseText,
|
|
713
|
-
source: message.content.source
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
return {
|
|
717
|
-
success: true,
|
|
718
|
-
data: {
|
|
719
|
-
channelCount: joinedChannels.length,
|
|
720
|
-
channels: joinedChannels,
|
|
721
|
-
primaryChannel
|
|
722
|
-
}
|
|
723
|
-
};
|
|
724
|
-
},
|
|
725
|
-
examples: [
|
|
726
|
-
[
|
|
727
|
-
{
|
|
728
|
-
name: "{{user1}}",
|
|
729
|
-
content: { text: "What channels are you in?" }
|
|
730
|
-
},
|
|
731
|
-
{
|
|
732
|
-
name: "{{agent}}",
|
|
733
|
-
content: {
|
|
734
|
-
text: "I'll list the channels I'm currently in.",
|
|
735
|
-
actions: ["TWITCH_LIST_CHANNELS"]
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
]
|
|
739
|
-
]
|
|
740
|
-
};
|
|
741
|
-
|
|
742
|
-
// src/actions/sendMessage.ts
|
|
743
|
-
import {
|
|
744
|
-
composePromptFromState as composePromptFromState3,
|
|
745
|
-
ModelType as ModelType3,
|
|
746
|
-
parseJSONObjectFromText as parseJSONObjectFromText3
|
|
747
|
-
} from "@elizaos/core";
|
|
748
|
-
var SEND_MESSAGE_TEMPLATE = `You are helping to extract send message parameters for Twitch chat.
|
|
749
|
-
|
|
750
|
-
The user wants to send a message to a Twitch channel.
|
|
751
|
-
|
|
752
|
-
Recent conversation:
|
|
753
|
-
{{recentMessages}}
|
|
754
|
-
|
|
755
|
-
Extract the following:
|
|
756
|
-
1. text: The message text to send
|
|
757
|
-
2. channel: The channel name to send to (without # prefix), or "current" for the current channel
|
|
758
|
-
|
|
759
|
-
Respond with a JSON object like:
|
|
760
|
-
{
|
|
761
|
-
"text": "The message to send",
|
|
762
|
-
"channel": "current"
|
|
842
|
+
},
|
|
843
|
+
patchAccount: async (_accountId, patch, _manager) => {
|
|
844
|
+
return { ...patch, provider: TWITCH_PROVIDER_ID };
|
|
845
|
+
},
|
|
846
|
+
deleteAccount: async (_accountId, _manager) => {}
|
|
847
|
+
};
|
|
763
848
|
}
|
|
764
849
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
"SEND_TWITCH_MESSAGE",
|
|
770
|
-
"TWITCH_CHAT",
|
|
771
|
-
"CHAT_TWITCH",
|
|
772
|
-
"SAY_IN_TWITCH"
|
|
773
|
-
],
|
|
774
|
-
description: "Send a message to a Twitch channel",
|
|
775
|
-
validate: async (runtime, message, state, options) => {
|
|
776
|
-
const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
|
|
777
|
-
const __avText = __avTextRaw.toLowerCase();
|
|
778
|
-
const __avKeywords = ["twitch", "send", "message"];
|
|
779
|
-
const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw)) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
780
|
-
const __avRegex = new RegExp("\\b(?:twitch|send|message)\\b", "i");
|
|
781
|
-
const __avRegexOk = __avRegex.test(__avText) || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
782
|
-
const __avSource = String(message?.content?.source ?? message?.source ?? "");
|
|
783
|
-
const __avExpectedSource = "twitch";
|
|
784
|
-
const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
785
|
-
const __avOptions = options && typeof options === "object" ? options : {};
|
|
786
|
-
const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object") || String(message?.content?.source ?? message?.source ?? "") === "twitch";
|
|
787
|
-
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
const __avLegacyValidate = async (_runtime, message2, _state) => {
|
|
791
|
-
return message2.content.source === "twitch";
|
|
792
|
-
};
|
|
793
|
-
try {
|
|
794
|
-
return Boolean(await __avLegacyValidate(runtime, message, state, options));
|
|
795
|
-
} catch {
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
},
|
|
799
|
-
handler: async (runtime, message, state, _options, callback) => {
|
|
800
|
-
const twitchService = runtime.getService(TWITCH_SERVICE_NAME);
|
|
801
|
-
if (!twitchService || !twitchService.isConnected()) {
|
|
802
|
-
if (callback) {
|
|
803
|
-
callback({
|
|
804
|
-
text: "Twitch service is not available.",
|
|
805
|
-
source: "twitch"
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
return { success: false, error: "Twitch service not available" };
|
|
809
|
-
}
|
|
810
|
-
const currentState = state ?? await runtime.composeState(message);
|
|
811
|
-
const prompt = await composePromptFromState3({
|
|
812
|
-
template: SEND_MESSAGE_TEMPLATE,
|
|
813
|
-
state: currentState
|
|
814
|
-
});
|
|
815
|
-
let messageInfo = null;
|
|
816
|
-
for (let attempt = 0;attempt < 3; attempt++) {
|
|
817
|
-
const response = await runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
818
|
-
prompt
|
|
819
|
-
});
|
|
820
|
-
const parsed = parseJSONObjectFromText3(String(response));
|
|
821
|
-
if (parsed?.text) {
|
|
822
|
-
messageInfo = {
|
|
823
|
-
text: String(parsed.text),
|
|
824
|
-
channel: String(parsed.channel || "current")
|
|
825
|
-
};
|
|
826
|
-
break;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
if (!messageInfo || !messageInfo.text) {
|
|
830
|
-
if (callback) {
|
|
831
|
-
callback({
|
|
832
|
-
text: "I couldn't understand what message you want me to send. Please try again.",
|
|
833
|
-
source: "twitch"
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
return { success: false, error: "Could not extract message parameters" };
|
|
837
|
-
}
|
|
838
|
-
let targetChannel = twitchService.getPrimaryChannel();
|
|
839
|
-
if (messageInfo.channel && messageInfo.channel !== "current") {
|
|
840
|
-
targetChannel = normalizeChannel(messageInfo.channel);
|
|
841
|
-
}
|
|
842
|
-
if (currentState?.data?.room?.channelId) {
|
|
843
|
-
targetChannel = normalizeChannel(currentState.data.room.channelId);
|
|
844
|
-
}
|
|
845
|
-
const result = await twitchService.sendMessage(messageInfo.text, {
|
|
846
|
-
channel: targetChannel
|
|
847
|
-
});
|
|
848
|
-
if (!result.success) {
|
|
849
|
-
if (callback) {
|
|
850
|
-
callback({
|
|
851
|
-
text: `Failed to send message: ${result.error}`,
|
|
852
|
-
source: "twitch"
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
return { success: false, error: result.error };
|
|
856
|
-
}
|
|
857
|
-
if (callback) {
|
|
858
|
-
callback({
|
|
859
|
-
text: "Message sent successfully.",
|
|
860
|
-
source: message.content.source
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
return {
|
|
864
|
-
success: true,
|
|
865
|
-
data: {
|
|
866
|
-
channel: targetChannel,
|
|
867
|
-
messageId: result.messageId
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
},
|
|
871
|
-
examples: [
|
|
872
|
-
[
|
|
873
|
-
{
|
|
874
|
-
name: "{{user1}}",
|
|
875
|
-
content: { text: "Send a message to chat saying 'Hello everyone!'" }
|
|
876
|
-
},
|
|
877
|
-
{
|
|
878
|
-
name: "{{agent}}",
|
|
879
|
-
content: {
|
|
880
|
-
text: "I'll send that message to the chat.",
|
|
881
|
-
actions: ["TWITCH_SEND_MESSAGE"]
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
]
|
|
885
|
-
]
|
|
886
|
-
};
|
|
850
|
+
// src/workflow-credential-provider.ts
|
|
851
|
+
import { Service as Service2 } from "@elizaos/core";
|
|
852
|
+
var WORKFLOW_CREDENTIAL_PROVIDER_TYPE = "workflow_credential_provider";
|
|
853
|
+
var SUPPORTED = ["httpHeaderAuth"];
|
|
887
854
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
const twitchService = runtime.getService(TWITCH_SERVICE_NAME);
|
|
902
|
-
if (!twitchService || !twitchService.isConnected()) {
|
|
903
|
-
return {
|
|
904
|
-
data: {
|
|
905
|
-
connected: false
|
|
906
|
-
},
|
|
907
|
-
values: {
|
|
908
|
-
connected: false
|
|
909
|
-
},
|
|
910
|
-
text: ""
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
const agentName = state?.agentName || "The agent";
|
|
914
|
-
const room = state?.data?.room;
|
|
915
|
-
const channelId = room?.channelId;
|
|
916
|
-
const channel = channelId ? normalizeChannel(channelId) : twitchService.getPrimaryChannel();
|
|
917
|
-
const joinedChannels = twitchService.getJoinedChannels();
|
|
918
|
-
const isPrimaryChannel = channel === twitchService.getPrimaryChannel();
|
|
919
|
-
const botUsername = twitchService.getBotUsername();
|
|
920
|
-
let responseText = `${agentName} is currently in Twitch channel ${formatChannelForDisplay(channel)}.`;
|
|
921
|
-
if (isPrimaryChannel) {
|
|
922
|
-
responseText += " This is the primary channel.";
|
|
923
|
-
}
|
|
924
|
-
responseText += `
|
|
925
|
-
|
|
926
|
-
Twitch is a live streaming platform. Chat messages are public and visible to all viewers.`;
|
|
927
|
-
responseText += ` ${agentName} is logged in as @${botUsername}.`;
|
|
928
|
-
responseText += ` Currently connected to ${joinedChannels.length} channel(s).`;
|
|
855
|
+
class TwitchWorkflowCredentialProvider extends Service2 {
|
|
856
|
+
static serviceType = WORKFLOW_CREDENTIAL_PROVIDER_TYPE;
|
|
857
|
+
capabilityDescription = "Supplies Twitch credentials to the workflow plugin.";
|
|
858
|
+
static async start(runtime) {
|
|
859
|
+
return new TwitchWorkflowCredentialProvider(runtime);
|
|
860
|
+
}
|
|
861
|
+
async stop() {}
|
|
862
|
+
async resolve(_userId, credType) {
|
|
863
|
+
if (credType !== "httpHeaderAuth")
|
|
864
|
+
return null;
|
|
865
|
+
const accessToken = this.runtime.getSetting("TWITCH_ACCESS_TOKEN");
|
|
866
|
+
if (!accessToken?.trim())
|
|
867
|
+
return null;
|
|
929
868
|
return {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
displayChannel: formatChannelForDisplay(channel),
|
|
933
|
-
isPrimaryChannel,
|
|
934
|
-
botUsername,
|
|
935
|
-
joinedChannels,
|
|
936
|
-
channelCount: joinedChannels.length,
|
|
937
|
-
connected: true
|
|
938
|
-
},
|
|
939
|
-
values: {
|
|
940
|
-
channel,
|
|
941
|
-
displayChannel: formatChannelForDisplay(channel),
|
|
942
|
-
isPrimaryChannel,
|
|
943
|
-
botUsername,
|
|
944
|
-
channelCount: joinedChannels.length
|
|
945
|
-
},
|
|
946
|
-
text: responseText
|
|
869
|
+
status: "credential_data",
|
|
870
|
+
data: { name: "Authorization", value: `Bearer ${accessToken.trim()}` }
|
|
947
871
|
};
|
|
948
872
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
// src/providers/userContext.ts
|
|
952
|
-
var userContextProvider = {
|
|
953
|
-
name: "twitchUserContext",
|
|
954
|
-
description: "Provides information about the Twitch user in the current conversation",
|
|
955
|
-
dynamic: true,
|
|
956
|
-
get: async (runtime, message, state) => {
|
|
957
|
-
if (message.content.source !== "twitch") {
|
|
958
|
-
return {
|
|
959
|
-
data: {},
|
|
960
|
-
values: {},
|
|
961
|
-
text: ""
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
const twitchService = runtime.getService(TWITCH_SERVICE_NAME);
|
|
965
|
-
if (!twitchService || !twitchService.isConnected()) {
|
|
966
|
-
return {
|
|
967
|
-
data: {},
|
|
968
|
-
values: {},
|
|
969
|
-
text: ""
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
const agentName = state?.agentName || "The agent";
|
|
973
|
-
const metadata = message.content.metadata;
|
|
974
|
-
const userInfo = metadata?.user;
|
|
975
|
-
if (!userInfo) {
|
|
976
|
-
return {
|
|
977
|
-
data: {},
|
|
978
|
-
values: {},
|
|
979
|
-
text: ""
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
const displayName = getTwitchUserDisplayName(userInfo);
|
|
983
|
-
const roles = [];
|
|
984
|
-
if (userInfo.isBroadcaster) {
|
|
985
|
-
roles.push("broadcaster");
|
|
986
|
-
}
|
|
987
|
-
if (userInfo.isModerator) {
|
|
988
|
-
roles.push("moderator");
|
|
989
|
-
}
|
|
990
|
-
if (userInfo.isVip) {
|
|
991
|
-
roles.push("VIP");
|
|
992
|
-
}
|
|
993
|
-
if (userInfo.isSubscriber) {
|
|
994
|
-
roles.push("subscriber");
|
|
995
|
-
}
|
|
996
|
-
const roleText = roles.length > 0 ? roles.join(", ") : "viewer";
|
|
997
|
-
let responseText = `${agentName} is talking to ${displayName} (${roleText}) in Twitch chat.`;
|
|
998
|
-
if (userInfo.isBroadcaster) {
|
|
999
|
-
responseText += ` ${displayName} is the channel owner/broadcaster.`;
|
|
1000
|
-
} else if (userInfo.isModerator) {
|
|
1001
|
-
responseText += ` ${displayName} is a channel moderator.`;
|
|
1002
|
-
}
|
|
873
|
+
checkCredentialTypes(credTypes) {
|
|
1003
874
|
return {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
username: userInfo.username,
|
|
1007
|
-
displayName,
|
|
1008
|
-
isBroadcaster: userInfo.isBroadcaster,
|
|
1009
|
-
isModerator: userInfo.isModerator,
|
|
1010
|
-
isVip: userInfo.isVip,
|
|
1011
|
-
isSubscriber: userInfo.isSubscriber,
|
|
1012
|
-
roles,
|
|
1013
|
-
color: userInfo.color
|
|
1014
|
-
},
|
|
1015
|
-
values: {
|
|
1016
|
-
userId: userInfo.userId,
|
|
1017
|
-
username: userInfo.username,
|
|
1018
|
-
displayName,
|
|
1019
|
-
roleText,
|
|
1020
|
-
isBroadcaster: userInfo.isBroadcaster,
|
|
1021
|
-
isModerator: userInfo.isModerator
|
|
1022
|
-
},
|
|
1023
|
-
text: responseText
|
|
875
|
+
supported: credTypes.filter((t) => SUPPORTED.includes(t)),
|
|
876
|
+
unsupported: credTypes.filter((t) => !SUPPORTED.includes(t))
|
|
1024
877
|
};
|
|
1025
878
|
}
|
|
1026
|
-
}
|
|
879
|
+
}
|
|
1027
880
|
|
|
1028
881
|
// src/index.ts
|
|
1029
882
|
var twitchPlugin = {
|
|
1030
883
|
name: "twitch",
|
|
1031
884
|
description: "Twitch chat integration plugin for ElizaOS with real-time messaging",
|
|
1032
|
-
services: [TwitchService],
|
|
1033
|
-
actions: [
|
|
1034
|
-
providers: [
|
|
885
|
+
services: [TwitchService, TwitchWorkflowCredentialProvider],
|
|
886
|
+
actions: [],
|
|
887
|
+
providers: [],
|
|
1035
888
|
tests: [],
|
|
889
|
+
autoEnable: {
|
|
890
|
+
connectorKeys: ["twitch"]
|
|
891
|
+
},
|
|
1036
892
|
init: async (_config, runtime) => {
|
|
893
|
+
try {
|
|
894
|
+
const manager = getConnectorAccountManager(runtime);
|
|
895
|
+
manager.registerProvider(createTwitchConnectorAccountProvider(runtime));
|
|
896
|
+
} catch (err) {
|
|
897
|
+
logger2.warn({
|
|
898
|
+
src: "plugin:twitch",
|
|
899
|
+
err: err instanceof Error ? err.message : String(err)
|
|
900
|
+
}, "Failed to register Twitch provider with ConnectorAccountManager");
|
|
901
|
+
}
|
|
1037
902
|
const username = runtime.getSetting("TWITCH_USERNAME");
|
|
1038
903
|
const clientId = runtime.getSetting("TWITCH_CLIENT_ID");
|
|
1039
904
|
const accessToken = runtime.getSetting("TWITCH_ACCESS_TOKEN");
|
|
@@ -1081,18 +946,17 @@ var twitchPlugin = {
|
|
|
1081
946
|
};
|
|
1082
947
|
var src_default = twitchPlugin;
|
|
1083
948
|
export {
|
|
1084
|
-
userContextProvider,
|
|
1085
949
|
stripMarkdownForTwitch,
|
|
1086
950
|
splitMessageForTwitch,
|
|
1087
|
-
|
|
951
|
+
resolveTwitchAccountSettings,
|
|
952
|
+
resolveDefaultTwitchAccountId,
|
|
953
|
+
readTwitchAccountId,
|
|
954
|
+
normalizeTwitchAccountId,
|
|
1088
955
|
normalizeChannel,
|
|
1089
|
-
|
|
1090
|
-
leaveChannel,
|
|
1091
|
-
joinChannel,
|
|
956
|
+
listTwitchAccountIds,
|
|
1092
957
|
getTwitchUserDisplayName,
|
|
1093
958
|
formatChannelForDisplay,
|
|
1094
959
|
src_default as default,
|
|
1095
|
-
channelStateProvider,
|
|
1096
960
|
TwitchServiceNotInitializedError,
|
|
1097
961
|
TwitchService,
|
|
1098
962
|
TwitchPluginError,
|
|
@@ -1101,7 +965,8 @@ export {
|
|
|
1101
965
|
TwitchConfigurationError,
|
|
1102
966
|
TwitchApiError,
|
|
1103
967
|
TWITCH_SERVICE_NAME,
|
|
1104
|
-
MAX_TWITCH_MESSAGE_LENGTH
|
|
968
|
+
MAX_TWITCH_MESSAGE_LENGTH,
|
|
969
|
+
DEFAULT_TWITCH_ACCOUNT_ID
|
|
1105
970
|
};
|
|
1106
971
|
|
|
1107
|
-
//# debugId=
|
|
972
|
+
//# debugId=E272CF35229333E264756E2164756E21
|