@happycastle/openclaw-channel-talk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +50 -1
- package/package.json +1 -1
- package/src/channel.ts +2 -2
- package/src/config-schema.ts +75 -45
- package/src/webhook.ts +42 -1
package/openclaw.plugin.json
CHANGED
|
@@ -6,6 +6,55 @@
|
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
9
|
-
"properties": {
|
|
9
|
+
"properties": {
|
|
10
|
+
"enabled": {
|
|
11
|
+
"type": "boolean",
|
|
12
|
+
"default": true,
|
|
13
|
+
"description": "Enable/disable the Channel Talk plugin"
|
|
14
|
+
},
|
|
15
|
+
"accessKey": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Channel Talk access key for API authentication"
|
|
18
|
+
},
|
|
19
|
+
"accessSecret": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Channel Talk access secret for API authentication"
|
|
22
|
+
},
|
|
23
|
+
"webhook": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"port": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"default": 3979,
|
|
29
|
+
"description": "Port for webhook server"
|
|
30
|
+
},
|
|
31
|
+
"path": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"default": "/api/channel-talk",
|
|
34
|
+
"description": "Path for webhook endpoint"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"botName": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Bot display name for sent messages"
|
|
41
|
+
},
|
|
42
|
+
"groupPolicy": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"enum": ["open", "closed"],
|
|
45
|
+
"default": "open",
|
|
46
|
+
"description": "Group chat policy"
|
|
47
|
+
},
|
|
48
|
+
"allowedGroups": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": { "type": "string" },
|
|
51
|
+
"description": "List of group chatIds to respond to"
|
|
52
|
+
},
|
|
53
|
+
"mentionOnly": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"default": false,
|
|
56
|
+
"description": "Only respond when the bot is mentioned"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
10
59
|
}
|
|
11
60
|
}
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChannelPlugin, OpenClawConfig } from 'openclaw/plugin-sdk';
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk';
|
|
3
3
|
import type { ChannelTalkCredentials } from './types.js';
|
|
4
4
|
import { ChannelTalkConfigSchema } from './config-schema.js';
|
|
5
5
|
import { channelTalkOutbound } from './send.js';
|
|
@@ -56,7 +56,7 @@ export const channelTalkPlugin: ChannelPlugin<ResolvedChannelTalkAccount> = {
|
|
|
56
56
|
|
|
57
57
|
reload: { configPrefixes: ['channels.channel-talk'] },
|
|
58
58
|
|
|
59
|
-
configSchema:
|
|
59
|
+
configSchema: ChannelTalkConfigSchema,
|
|
60
60
|
|
|
61
61
|
config: {
|
|
62
62
|
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
package/src/config-schema.ts
CHANGED
|
@@ -1,60 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Channel Talk Plugin Configuration Schema
|
|
3
|
-
*
|
|
3
|
+
* Plain JSON Schema — avoids TypeBox compatibility issues with OpenClaw's config validator.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Channel Talk configuration schema using TypeBox
|
|
11
|
-
* Defines required and optional configuration fields for the plugin
|
|
12
|
-
*/
|
|
13
|
-
export const ChannelTalkConfigSchema = Type.Object(
|
|
14
|
-
{
|
|
6
|
+
export const ChannelTalkConfigSchema = {
|
|
7
|
+
type: 'object' as const,
|
|
8
|
+
additionalProperties: false,
|
|
9
|
+
properties: {
|
|
15
10
|
/** Enable/disable the Channel Talk plugin */
|
|
16
|
-
enabled:
|
|
17
|
-
|
|
11
|
+
enabled: {
|
|
12
|
+
type: 'boolean' as const,
|
|
13
|
+
default: true,
|
|
14
|
+
description: 'Enable/disable the Channel Talk plugin',
|
|
15
|
+
},
|
|
16
|
+
|
|
18
17
|
/** Channel Talk API access key (required when enabled) */
|
|
19
|
-
accessKey:
|
|
18
|
+
accessKey: {
|
|
19
|
+
type: 'string' as const,
|
|
20
20
|
description: 'Channel Talk access key for API authentication',
|
|
21
|
-
}
|
|
22
|
-
|
|
21
|
+
},
|
|
22
|
+
|
|
23
23
|
/** Channel Talk API access secret (required when enabled) */
|
|
24
|
-
accessSecret:
|
|
24
|
+
accessSecret: {
|
|
25
|
+
type: 'string' as const,
|
|
25
26
|
description: 'Channel Talk access secret for API authentication',
|
|
26
|
-
}
|
|
27
|
-
|
|
27
|
+
},
|
|
28
|
+
|
|
28
29
|
/** Webhook configuration */
|
|
29
|
-
webhook:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
port:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
webhook: {
|
|
31
|
+
type: 'object' as const,
|
|
32
|
+
properties: {
|
|
33
|
+
port: {
|
|
34
|
+
type: 'number' as const,
|
|
35
|
+
default: 3979,
|
|
36
|
+
description: 'Port for webhook server (default: 3979)',
|
|
37
|
+
},
|
|
38
|
+
path: {
|
|
39
|
+
type: 'string' as const,
|
|
40
|
+
default: '/api/channel-talk',
|
|
41
|
+
description: 'Path for webhook endpoint (default: /api/channel-talk)',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
39
46
|
/** Bot display name for sent messages (optional) */
|
|
40
|
-
botName:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
botName: {
|
|
48
|
+
type: 'string' as const,
|
|
49
|
+
description: 'Bot display name for sent messages',
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/** Group chat policy: "open" = all groups allowed, "closed" = none allowed */
|
|
53
|
+
groupPolicy: {
|
|
54
|
+
type: 'string' as const,
|
|
55
|
+
enum: ['open', 'closed'],
|
|
56
|
+
default: 'open',
|
|
57
|
+
description: 'Group chat policy: "open" = all groups allowed, "closed" = none',
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/** Allowlist of group chatIds to respond to */
|
|
61
|
+
allowedGroups: {
|
|
62
|
+
type: 'array' as const,
|
|
63
|
+
items: { type: 'string' as const },
|
|
64
|
+
description:
|
|
65
|
+
'List of group chatIds to respond to. If empty or omitted, all groups are allowed (subject to groupPolicy).',
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/** Only respond when the bot is mentioned */
|
|
69
|
+
mentionOnly: {
|
|
70
|
+
type: 'boolean' as const,
|
|
71
|
+
default: false,
|
|
72
|
+
description:
|
|
73
|
+
'Only respond when the bot is mentioned in the message. Similar to Discord mention-only mode.',
|
|
74
|
+
},
|
|
50
75
|
},
|
|
51
|
-
|
|
52
|
-
additionalProperties: false,
|
|
53
|
-
}
|
|
54
|
-
);
|
|
76
|
+
};
|
|
55
77
|
|
|
56
78
|
/**
|
|
57
|
-
* TypeScript type derived from
|
|
58
|
-
* Use this for type-safe configuration handling
|
|
79
|
+
* TypeScript type for the config (manual, not derived from TypeBox)
|
|
59
80
|
*/
|
|
60
|
-
export
|
|
81
|
+
export interface ChannelTalkConfig {
|
|
82
|
+
enabled?: boolean;
|
|
83
|
+
accessKey: string;
|
|
84
|
+
accessSecret: string;
|
|
85
|
+
webhook?: { port?: number; path?: string };
|
|
86
|
+
botName?: string;
|
|
87
|
+
groupPolicy?: 'open' | 'closed';
|
|
88
|
+
allowedGroups?: string[];
|
|
89
|
+
mentionOnly?: boolean;
|
|
90
|
+
}
|
package/src/webhook.ts
CHANGED
|
@@ -8,6 +8,27 @@ const DEFAULT_ACCOUNT_ID = 'default';
|
|
|
8
8
|
const DEDUP_TTL_MS = 60_000;
|
|
9
9
|
const DEDUP_CLEANUP_INTERVAL_MS = 30_000;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Check if a message mentions the bot.
|
|
13
|
+
* Matches @botName or common patterns like "봇이름아", "봇이름아,", etc.
|
|
14
|
+
* If no botName is configured, always returns false (falls through to respond to all).
|
|
15
|
+
*/
|
|
16
|
+
function checkMention(text: string, botName?: string): boolean {
|
|
17
|
+
if (!botName) return false;
|
|
18
|
+
const lower = text.toLowerCase();
|
|
19
|
+
const nameLower = botName.toLowerCase();
|
|
20
|
+
// Direct @mention
|
|
21
|
+
if (lower.includes(`@${nameLower}`)) return true;
|
|
22
|
+
// Name appears at start or as a word boundary
|
|
23
|
+
const namePattern = new RegExp(`(?:^|\\s)${escapeRegex(nameLower)}(?:[아야,!?\\s]|$)`, 'i');
|
|
24
|
+
if (namePattern.test(text)) return true;
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function escapeRegex(str: string): string {
|
|
29
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
30
|
+
}
|
|
31
|
+
|
|
11
32
|
export type StartChannelTalkWebhookContext = {
|
|
12
33
|
cfg: OpenClawConfig;
|
|
13
34
|
runtime: { log?: (...args: unknown[]) => void; error?: (...args: unknown[]) => void };
|
|
@@ -56,6 +77,8 @@ export async function startChannelTalkWebhook(
|
|
|
56
77
|
const webhookPath = webhookCfg?.path ?? '/api/channel-talk';
|
|
57
78
|
const botName = channelTalkCfg.botName as string | undefined;
|
|
58
79
|
const accountId = ctx.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
80
|
+
const allowedGroups = channelTalkCfg.allowedGroups as string[] | undefined;
|
|
81
|
+
const mentionOnly = channelTalkCfg.mentionOnly as boolean | undefined;
|
|
59
82
|
|
|
60
83
|
const apiClient = createApiClient({ accessKey, accessSecret }, channelTalkCfg.baseUrl as string | undefined);
|
|
61
84
|
|
|
@@ -151,6 +174,24 @@ export async function startChannelTalkWebhook(
|
|
|
151
174
|
return;
|
|
152
175
|
}
|
|
153
176
|
|
|
177
|
+
// --- Group allowlist filtering ---
|
|
178
|
+
if (allowedGroups && allowedGroups.length > 0) {
|
|
179
|
+
if (!allowedGroups.includes(groupId)) {
|
|
180
|
+
log.debug?.('skipping message from non-allowed group', { groupId });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Mention-only filtering ---
|
|
186
|
+
const wasMentioned = mentionOnly
|
|
187
|
+
? checkMention(plainText, botName)
|
|
188
|
+
: false;
|
|
189
|
+
|
|
190
|
+
if (mentionOnly && !wasMentioned) {
|
|
191
|
+
log.debug?.('skipping non-mentioned message (mentionOnly=true)', { groupId });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
154
195
|
const managerId = refers?.manager?.id ?? entity.personId ?? 'unknown';
|
|
155
196
|
const managerName = refers?.manager?.name ?? managerId;
|
|
156
197
|
const timestamp = entity.createdAt ?? Date.now();
|
|
@@ -215,7 +256,7 @@ export async function startChannelTalkWebhook(
|
|
|
215
256
|
Surface: 'channel-talk' as const,
|
|
216
257
|
MessageSid: messageId,
|
|
217
258
|
Timestamp: timestamp,
|
|
218
|
-
WasMentioned:
|
|
259
|
+
WasMentioned: wasMentioned || !mentionOnly,
|
|
219
260
|
CommandAuthorized: false,
|
|
220
261
|
OriginatingChannel: 'channel-talk' as const,
|
|
221
262
|
OriginatingTo: `group:${groupId}`,
|