@openclaw/twitch 2026.5.1-beta.1 → 2026.5.2-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/openclaw.plugin.json +219 -2
- package/package.json +8 -1
- package/src/config-schema.test.ts +46 -0
- package/src/config-schema.ts +23 -19
package/openclaw.plugin.json
CHANGED
|
@@ -3,13 +3,230 @@
|
|
|
3
3
|
"activation": {
|
|
4
4
|
"onStartup": false
|
|
5
5
|
},
|
|
6
|
-
"channels": [
|
|
6
|
+
"channels": [
|
|
7
|
+
"twitch"
|
|
8
|
+
],
|
|
7
9
|
"channelEnvVars": {
|
|
8
|
-
"twitch": [
|
|
10
|
+
"twitch": [
|
|
11
|
+
"OPENCLAW_TWITCH_ACCESS_TOKEN"
|
|
12
|
+
]
|
|
9
13
|
},
|
|
10
14
|
"configSchema": {
|
|
11
15
|
"type": "object",
|
|
12
16
|
"additionalProperties": false,
|
|
13
17
|
"properties": {}
|
|
18
|
+
},
|
|
19
|
+
"channelConfigs": {
|
|
20
|
+
"twitch": {
|
|
21
|
+
"schema": {
|
|
22
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
23
|
+
"anyOf": [
|
|
24
|
+
{
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"name": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
"enabled": {
|
|
31
|
+
"type": "boolean"
|
|
32
|
+
},
|
|
33
|
+
"markdown": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"tables": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": [
|
|
39
|
+
"off",
|
|
40
|
+
"bullets",
|
|
41
|
+
"code",
|
|
42
|
+
"block"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"additionalProperties": false
|
|
47
|
+
},
|
|
48
|
+
"defaultAccount": {
|
|
49
|
+
"type": "string"
|
|
50
|
+
},
|
|
51
|
+
"username": {
|
|
52
|
+
"type": "string"
|
|
53
|
+
},
|
|
54
|
+
"accessToken": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
"clientId": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"channel": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"minLength": 1
|
|
63
|
+
},
|
|
64
|
+
"allowFrom": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"allowedRoles": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enum": [
|
|
75
|
+
"moderator",
|
|
76
|
+
"owner",
|
|
77
|
+
"vip",
|
|
78
|
+
"subscriber",
|
|
79
|
+
"all"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"requireMention": {
|
|
84
|
+
"type": "boolean"
|
|
85
|
+
},
|
|
86
|
+
"responsePrefix": {
|
|
87
|
+
"type": "string"
|
|
88
|
+
},
|
|
89
|
+
"clientSecret": {
|
|
90
|
+
"type": "string"
|
|
91
|
+
},
|
|
92
|
+
"refreshToken": {
|
|
93
|
+
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"expiresIn": {
|
|
96
|
+
"anyOf": [
|
|
97
|
+
{
|
|
98
|
+
"type": "number"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"type": "null"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"obtainmentTimestamp": {
|
|
106
|
+
"type": "number"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"required": [
|
|
110
|
+
"username",
|
|
111
|
+
"accessToken",
|
|
112
|
+
"channel"
|
|
113
|
+
],
|
|
114
|
+
"additionalProperties": false
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"name": {
|
|
120
|
+
"type": "string"
|
|
121
|
+
},
|
|
122
|
+
"enabled": {
|
|
123
|
+
"type": "boolean"
|
|
124
|
+
},
|
|
125
|
+
"markdown": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {
|
|
128
|
+
"tables": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"enum": [
|
|
131
|
+
"off",
|
|
132
|
+
"bullets",
|
|
133
|
+
"code",
|
|
134
|
+
"block"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"additionalProperties": false
|
|
139
|
+
},
|
|
140
|
+
"defaultAccount": {
|
|
141
|
+
"type": "string"
|
|
142
|
+
},
|
|
143
|
+
"accounts": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"propertyNames": {
|
|
146
|
+
"type": "string"
|
|
147
|
+
},
|
|
148
|
+
"additionalProperties": {
|
|
149
|
+
"type": "object",
|
|
150
|
+
"properties": {
|
|
151
|
+
"username": {
|
|
152
|
+
"type": "string"
|
|
153
|
+
},
|
|
154
|
+
"accessToken": {
|
|
155
|
+
"type": "string"
|
|
156
|
+
},
|
|
157
|
+
"clientId": {
|
|
158
|
+
"type": "string"
|
|
159
|
+
},
|
|
160
|
+
"channel": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"minLength": 1
|
|
163
|
+
},
|
|
164
|
+
"enabled": {
|
|
165
|
+
"type": "boolean"
|
|
166
|
+
},
|
|
167
|
+
"allowFrom": {
|
|
168
|
+
"type": "array",
|
|
169
|
+
"items": {
|
|
170
|
+
"type": "string"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"allowedRoles": {
|
|
174
|
+
"type": "array",
|
|
175
|
+
"items": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"enum": [
|
|
178
|
+
"moderator",
|
|
179
|
+
"owner",
|
|
180
|
+
"vip",
|
|
181
|
+
"subscriber",
|
|
182
|
+
"all"
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"requireMention": {
|
|
187
|
+
"type": "boolean"
|
|
188
|
+
},
|
|
189
|
+
"responsePrefix": {
|
|
190
|
+
"type": "string"
|
|
191
|
+
},
|
|
192
|
+
"clientSecret": {
|
|
193
|
+
"type": "string"
|
|
194
|
+
},
|
|
195
|
+
"refreshToken": {
|
|
196
|
+
"type": "string"
|
|
197
|
+
},
|
|
198
|
+
"expiresIn": {
|
|
199
|
+
"anyOf": [
|
|
200
|
+
{
|
|
201
|
+
"type": "number"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"type": "null"
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
"obtainmentTimestamp": {
|
|
209
|
+
"type": "number"
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"required": [
|
|
213
|
+
"username",
|
|
214
|
+
"accessToken",
|
|
215
|
+
"channel"
|
|
216
|
+
],
|
|
217
|
+
"additionalProperties": false
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"required": [
|
|
222
|
+
"accounts"
|
|
223
|
+
],
|
|
224
|
+
"additionalProperties": false
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
"label": "Twitch",
|
|
229
|
+
"description": "Twitch chat integration"
|
|
230
|
+
}
|
|
14
231
|
}
|
|
15
232
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/twitch",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.2-beta.1",
|
|
4
4
|
"description": "OpenClaw Twitch channel plugin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
"defaultChoice": "npm",
|
|
26
26
|
"minHostVersion": ">=2026.4.10"
|
|
27
27
|
},
|
|
28
|
+
"compat": {
|
|
29
|
+
"pluginApi": ">=2026.5.2-beta.1"
|
|
30
|
+
},
|
|
31
|
+
"build": {
|
|
32
|
+
"openclawVersion": "2026.5.2-beta.1"
|
|
33
|
+
},
|
|
28
34
|
"channel": {
|
|
29
35
|
"id": "twitch",
|
|
30
36
|
"label": "Twitch",
|
|
@@ -36,6 +42,7 @@
|
|
|
36
42
|
]
|
|
37
43
|
},
|
|
38
44
|
"release": {
|
|
45
|
+
"publishToClawHub": true,
|
|
39
46
|
"publishToNpm": true
|
|
40
47
|
}
|
|
41
48
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import AjvPkg from "ajv";
|
|
2
|
+
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { TwitchConfigSchema } from "./config-schema.js";
|
|
5
|
+
|
|
6
|
+
function validateTwitchConfig(value: unknown): boolean {
|
|
7
|
+
const Ajv = AjvPkg as unknown as new (opts?: object) => import("ajv").default;
|
|
8
|
+
const schema = buildChannelConfigSchema(TwitchConfigSchema).schema;
|
|
9
|
+
const validate = new Ajv({ allErrors: true, strict: false }).compile(schema);
|
|
10
|
+
const ok = validate(value);
|
|
11
|
+
if (!ok) {
|
|
12
|
+
throw new Error(`expected valid Twitch config: ${JSON.stringify(validate.errors)}`);
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("TwitchConfigSchema JSON schema", () => {
|
|
18
|
+
it("accepts single-account channel config with base fields", () => {
|
|
19
|
+
expect(
|
|
20
|
+
validateTwitchConfig({
|
|
21
|
+
enabled: false,
|
|
22
|
+
username: "openclaw",
|
|
23
|
+
accessToken: "oauth:test",
|
|
24
|
+
clientId: "test-client-id",
|
|
25
|
+
channel: "openclaw-test",
|
|
26
|
+
}),
|
|
27
|
+
).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("accepts multi-account channel config with defaultAccount", () => {
|
|
31
|
+
expect(
|
|
32
|
+
validateTwitchConfig({
|
|
33
|
+
enabled: true,
|
|
34
|
+
defaultAccount: "stream",
|
|
35
|
+
accounts: {
|
|
36
|
+
stream: {
|
|
37
|
+
username: "openclaw",
|
|
38
|
+
accessToken: "oauth:test",
|
|
39
|
+
clientId: "test-client-id",
|
|
40
|
+
channel: "openclaw-test",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
package/src/config-schema.ts
CHANGED
|
@@ -6,10 +6,7 @@ import { z } from "openclaw/plugin-sdk/zod";
|
|
|
6
6
|
*/
|
|
7
7
|
const TwitchRoleSchema = z.enum(["moderator", "owner", "vip", "subscriber", "all"]);
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
* Twitch account configuration schema
|
|
11
|
-
*/
|
|
12
|
-
const TwitchAccountSchema = z.object({
|
|
9
|
+
const TwitchAccountShape = {
|
|
13
10
|
/** Twitch username */
|
|
14
11
|
username: z.string(),
|
|
15
12
|
/** Twitch OAuth access token (requires chat:read and chat:write scopes) */
|
|
@@ -36,16 +33,22 @@ const TwitchAccountSchema = z.object({
|
|
|
36
33
|
expiresIn: z.number().nullable().optional(),
|
|
37
34
|
/** Timestamp when token was obtained (optional, for token refresh tracking) */
|
|
38
35
|
obtainmentTimestamp: z.number().optional(),
|
|
39
|
-
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Twitch account configuration schema
|
|
40
|
+
*/
|
|
41
|
+
const TwitchAccountSchema = z.object(TwitchAccountShape);
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Base configuration properties shared by both single and multi-account modes
|
|
43
45
|
*/
|
|
44
|
-
const
|
|
46
|
+
const TwitchConfigBaseShape = {
|
|
45
47
|
name: z.string().optional(),
|
|
46
48
|
enabled: z.boolean().optional(),
|
|
47
49
|
markdown: MarkdownConfigSchema.optional(),
|
|
48
|
-
|
|
50
|
+
defaultAccount: z.string().optional(),
|
|
51
|
+
};
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
54
|
* Simplified single-account configuration schema
|
|
@@ -53,24 +56,25 @@ const TwitchConfigBaseSchema = z.object({
|
|
|
53
56
|
* Use this for single-account setups. Properties are at the top level,
|
|
54
57
|
* creating an implicit "default" account.
|
|
55
58
|
*/
|
|
56
|
-
const SimplifiedSchema = z.
|
|
59
|
+
const SimplifiedSchema = z.object({
|
|
60
|
+
...TwitchConfigBaseShape,
|
|
61
|
+
...TwitchAccountShape,
|
|
62
|
+
});
|
|
57
63
|
|
|
58
64
|
/**
|
|
59
65
|
* Multi-account configuration schema
|
|
60
66
|
*
|
|
61
67
|
* Use this for multi-account setups. Each key is an account ID (e.g., "default", "secondary").
|
|
62
68
|
*/
|
|
63
|
-
const MultiAccountSchema = z
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}),
|
|
73
|
-
);
|
|
69
|
+
const MultiAccountSchema = z
|
|
70
|
+
.object({
|
|
71
|
+
...TwitchConfigBaseShape,
|
|
72
|
+
/** Per-account configuration (for multi-account setups) */
|
|
73
|
+
accounts: z.record(z.string(), TwitchAccountSchema),
|
|
74
|
+
})
|
|
75
|
+
.refine((val) => Object.keys(val.accounts || {}).length > 0, {
|
|
76
|
+
message: "accounts must contain at least one entry",
|
|
77
|
+
});
|
|
74
78
|
|
|
75
79
|
/**
|
|
76
80
|
* Twitch plugin configuration schema
|