@openclaw/twitch 2026.5.9-beta.1 → 2026.5.10-beta.2
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/dist/api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as twitchPlugin } from "./plugin-
|
|
1
|
+
import { t as twitchPlugin } from "./plugin-BLMSYnzs.js";
|
|
2
2
|
export { twitchPlugin };
|
|
@@ -2,116 +2,127 @@ import { i as getOrCreateClientManager, n as stripMarkdownForTwitch } from "./ma
|
|
|
2
2
|
import { t as getTwitchRuntime } from "./runtime-QZ5I3GlI.js";
|
|
3
3
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
4
4
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
5
|
+
import { createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
5
6
|
//#region extensions/twitch/src/access-control.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
const twitchUserIdentity = defineStableChannelIngressIdentity({
|
|
8
|
+
key: "sender-id",
|
|
9
|
+
entryIdPrefix: "twitch-user-entry"
|
|
10
|
+
});
|
|
11
|
+
const twitchRoleIdentity = defineStableChannelIngressIdentity({
|
|
12
|
+
key: "role-moderator",
|
|
13
|
+
kind: "role",
|
|
14
|
+
normalizeEntry: normalizeTwitchRole,
|
|
15
|
+
normalizeSubject: normalizeTwitchRole,
|
|
16
|
+
aliases: [
|
|
17
|
+
"owner",
|
|
18
|
+
"vip",
|
|
19
|
+
"subscriber"
|
|
20
|
+
].map((role) => ({
|
|
21
|
+
key: `role-${role}`,
|
|
22
|
+
kind: "role",
|
|
23
|
+
normalizeEntry: () => null,
|
|
24
|
+
normalizeSubject: normalizeTwitchRole
|
|
25
|
+
})),
|
|
26
|
+
isWildcardEntry: (entry) => normalizeTwitchRole(entry) === "all",
|
|
27
|
+
resolveEntryId: ({ entryIndex }) => `twitch-role-entry-${entryIndex + 1}`
|
|
28
|
+
});
|
|
29
|
+
async function checkTwitchAccessControl(params) {
|
|
28
30
|
const { message, account, botUsername } = params;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
const policyKind = resolveTwitchPolicyKind(account);
|
|
32
|
+
const decision = (await createChannelIngressResolver({
|
|
33
|
+
channelId: "twitch",
|
|
34
|
+
accountId: "default",
|
|
35
|
+
identity: policyKind === "role" ? twitchRoleIdentity : twitchUserIdentity
|
|
36
|
+
}).message({
|
|
37
|
+
subject: policyKind === "role" ? twitchRoleSubject(message) : { stableId: message.userId },
|
|
38
|
+
conversation: {
|
|
39
|
+
kind: "group",
|
|
40
|
+
id: message.channel
|
|
41
|
+
},
|
|
42
|
+
event: { mayPair: false },
|
|
43
|
+
mentionFacts: {
|
|
44
|
+
canDetectMention: true,
|
|
45
|
+
wasMentioned: mentionsBot(message.message, botUsername)
|
|
46
|
+
},
|
|
47
|
+
dmPolicy: "open",
|
|
48
|
+
groupPolicy: policyKind === "open" ? "open" : "allowlist",
|
|
49
|
+
policy: { activation: {
|
|
50
|
+
requireMention: account.requireMention ?? true,
|
|
51
|
+
allowTextCommands: false,
|
|
52
|
+
order: "before-sender"
|
|
53
|
+
} },
|
|
54
|
+
groupAllowFrom: policyKind === "allowFrom" ? account.allowFrom : policyKind === "role" ? account.allowedRoles : void 0
|
|
55
|
+
})).ingress;
|
|
56
|
+
if (decision.decisiveGateId === "activation" && decision.admission !== "dispatch") return {
|
|
57
|
+
allowed: false,
|
|
58
|
+
reason: "message does not mention the bot (requireMention is enabled)"
|
|
59
|
+
};
|
|
60
|
+
if (decision.admission === "dispatch") {
|
|
61
|
+
if (policyKind === "allowFrom") return {
|
|
47
62
|
allowed: true,
|
|
48
|
-
matchKey:
|
|
63
|
+
matchKey: params.message.userId,
|
|
49
64
|
matchSource: "allowlist"
|
|
50
65
|
};
|
|
51
|
-
return {
|
|
52
|
-
allowed: false,
|
|
53
|
-
reason: "sender is not in allowFrom allowlist"
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
if (account.allowedRoles && account.allowedRoles.length > 0) {
|
|
57
|
-
const allowedRoles = account.allowedRoles;
|
|
58
|
-
if (allowedRoles.includes("all")) return {
|
|
66
|
+
if (policyKind === "role") return {
|
|
59
67
|
allowed: true,
|
|
60
|
-
matchKey: "
|
|
68
|
+
matchKey: params.account.allowedRoles?.join(","),
|
|
61
69
|
matchSource: "role"
|
|
62
70
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
return { allowed: true };
|
|
72
|
+
}
|
|
73
|
+
if (policyKind === "allowFrom") {
|
|
74
|
+
if (!params.message.userId) return {
|
|
67
75
|
allowed: false,
|
|
68
|
-
reason:
|
|
76
|
+
reason: "sender user ID not available for allowlist check"
|
|
69
77
|
};
|
|
70
78
|
return {
|
|
71
|
-
allowed:
|
|
72
|
-
|
|
73
|
-
matchSource: "role"
|
|
79
|
+
allowed: false,
|
|
80
|
+
reason: "sender is not in allowFrom allowlist"
|
|
74
81
|
};
|
|
75
82
|
}
|
|
76
|
-
|
|
83
|
+
if (policyKind === "role") return {
|
|
84
|
+
allowed: false,
|
|
85
|
+
reason: `sender does not have any of the required roles: ${params.account.allowedRoles?.join(", ") ?? ""}`
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
allowed: false,
|
|
89
|
+
reason: reasonForTwitchIngressDecision(decision)
|
|
90
|
+
};
|
|
77
91
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
function resolveTwitchPolicyKind(account) {
|
|
93
|
+
if (account.allowFrom !== void 0) return "allowFrom";
|
|
94
|
+
if (account.allowedRoles && account.allowedRoles.length > 0) return "role";
|
|
95
|
+
return "open";
|
|
96
|
+
}
|
|
97
|
+
function twitchRoleSubject(message) {
|
|
98
|
+
return {
|
|
99
|
+
stableId: message.isMod ? "moderator" : void 0,
|
|
100
|
+
aliases: {
|
|
101
|
+
"role-owner": message.isOwner ? "owner" : void 0,
|
|
102
|
+
"role-vip": message.isVip ? "vip" : void 0,
|
|
103
|
+
"role-subscriber": message.isSub ? "subscriber" : void 0
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function normalizeTwitchRole(value) {
|
|
108
|
+
const role = normalizeLowercaseStringOrEmpty(value);
|
|
109
|
+
if (role === "*") return "all";
|
|
110
|
+
return role === "moderator" || role === "owner" || role === "vip" || role === "subscriber" || role === "all" ? role : null;
|
|
111
|
+
}
|
|
112
|
+
function reasonForTwitchIngressDecision(decision) {
|
|
113
|
+
switch (decision.reasonCode) {
|
|
114
|
+
case "activation_skipped": return "message does not mention the bot (requireMention is enabled)";
|
|
115
|
+
case "group_policy_empty_allowlist":
|
|
116
|
+
case "group_policy_not_allowlisted": return "sender is not in allowFrom allowlist";
|
|
117
|
+
default: return decision.reasonCode;
|
|
97
118
|
}
|
|
98
|
-
return false;
|
|
99
119
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
* Returns a list of lowercase usernames that were mentioned in the message.
|
|
104
|
-
* Twitch mentions are in the format @username.
|
|
105
|
-
*/
|
|
106
|
-
function extractMentions(message) {
|
|
120
|
+
function mentionsBot(message, botUsername) {
|
|
121
|
+
const expected = normalizeLowercaseStringOrEmpty(botUsername);
|
|
107
122
|
const mentionRegex = /@(\w+)/g;
|
|
108
|
-
const mentions = [];
|
|
109
123
|
let match;
|
|
110
|
-
while ((match = mentionRegex.exec(message)) !== null)
|
|
111
|
-
|
|
112
|
-
if (username) mentions.push(normalizeLowercaseStringOrEmpty(username));
|
|
113
|
-
}
|
|
114
|
-
return mentions;
|
|
124
|
+
while ((match = mentionRegex.exec(message)) !== null) if ((match[1] ? normalizeLowercaseStringOrEmpty(match[1]) : "") === expected) return true;
|
|
125
|
+
return false;
|
|
115
126
|
}
|
|
116
127
|
//#endregion
|
|
117
128
|
//#region extensions/twitch/src/monitor.ts
|
|
@@ -292,23 +303,26 @@ async function monitorTwitchProvider(options) {
|
|
|
292
303
|
}
|
|
293
304
|
const unregisterHandler = clientManager.onMessage(account, (message) => {
|
|
294
305
|
if (stopped) return;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
(async () => {
|
|
307
|
+
const botUsername = normalizeLowercaseStringOrEmpty(account.username);
|
|
308
|
+
if (normalizeLowercaseStringOrEmpty(message.username) === botUsername) return;
|
|
309
|
+
const access = await checkTwitchAccessControl({
|
|
310
|
+
message,
|
|
311
|
+
account,
|
|
312
|
+
botUsername
|
|
313
|
+
});
|
|
314
|
+
if (stopped || !access.allowed) return;
|
|
315
|
+
statusSink?.({ lastInboundAt: Date.now() });
|
|
316
|
+
await processTwitchMessage({
|
|
317
|
+
message,
|
|
318
|
+
account,
|
|
319
|
+
accountId,
|
|
320
|
+
config,
|
|
321
|
+
runtime,
|
|
322
|
+
core,
|
|
323
|
+
statusSink
|
|
324
|
+
});
|
|
325
|
+
})().catch((err) => {
|
|
312
326
|
runtime.error?.(`Message processing failed: ${String(err)}`);
|
|
313
327
|
});
|
|
314
328
|
});
|
|
@@ -954,7 +954,7 @@ const twitchPlugin = createChatChannelPlugin({
|
|
|
954
954
|
lastError: null
|
|
955
955
|
});
|
|
956
956
|
ctx.log?.info(`Starting Twitch connection for ${account.username}`);
|
|
957
|
-
const { monitorTwitchProvider } = await import("./monitor-
|
|
957
|
+
const { monitorTwitchProvider } = await import("./monitor-DvbgvvL4.js");
|
|
958
958
|
await monitorTwitchProvider({
|
|
959
959
|
account,
|
|
960
960
|
accountId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/twitch",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.10-beta.2",
|
|
4
4
|
"description": "OpenClaw Twitch channel plugin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"minHostVersion": ">=2026.4.10"
|
|
27
27
|
},
|
|
28
28
|
"compat": {
|
|
29
|
-
"pluginApi": ">=2026.5.
|
|
29
|
+
"pluginApi": ">=2026.5.10-beta.2"
|
|
30
30
|
},
|
|
31
31
|
"build": {
|
|
32
|
-
"openclawVersion": "2026.5.
|
|
32
|
+
"openclawVersion": "2026.5.10-beta.2"
|
|
33
33
|
},
|
|
34
34
|
"channel": {
|
|
35
35
|
"id": "twitch",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"README.md"
|
|
57
57
|
],
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"openclaw": ">=2026.5.
|
|
59
|
+
"openclaw": ">=2026.5.10-beta.2"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"openclaw": {
|