@tinyclaw/plugin-channel-discord 1.1.0-staging.a410a62 → 2.0.0-dev.00575dc
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/index.d.ts +7 -0
- package/dist/index.js +77 -10
- package/dist/pairing.d.ts +1 -1
- package/dist/pairing.js +1 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* Prefixed to prevent collisions with web UI user IDs.
|
|
16
16
|
*/
|
|
17
17
|
import type { ChannelPlugin } from '@tinyclaw/types';
|
|
18
|
+
export interface DiscordRuntimeStatus {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
state: 'idle' | 'disabled' | 'starting' | 'connected' | 'error' | 'stopped';
|
|
21
|
+
readyTag: string | null;
|
|
22
|
+
lastError: string | null;
|
|
23
|
+
}
|
|
24
|
+
export declare function getDiscordRuntimeStatus(): DiscordRuntimeStatus;
|
|
18
25
|
declare const discordPlugin: ChannelPlugin;
|
|
19
26
|
/** Split a string into chunks without cutting words at boundaries. */
|
|
20
27
|
export declare function splitIntoChunks(text: string, maxLength: number): string[];
|
package/dist/index.js
CHANGED
|
@@ -14,30 +14,50 @@
|
|
|
14
14
|
* userId format: "discord:<discord-user-id>"
|
|
15
15
|
* Prefixed to prevent collisions with web UI user IDs.
|
|
16
16
|
*/
|
|
17
|
-
import { Client, GatewayIntentBits, Partials, Events, } from 'discord.js';
|
|
18
17
|
import { logger } from '@tinyclaw/logger';
|
|
19
|
-
import {
|
|
18
|
+
import { Client, Events, GatewayIntentBits, Partials, } from 'discord.js';
|
|
19
|
+
import { createDiscordPairingTools, DISCORD_ENABLED_CONFIG_KEY, DISCORD_TOKEN_SECRET_KEY, } from './pairing.js';
|
|
20
20
|
let client = null;
|
|
21
|
+
const runtimeStatus = {
|
|
22
|
+
enabled: false,
|
|
23
|
+
state: 'idle',
|
|
24
|
+
readyTag: null,
|
|
25
|
+
lastError: null,
|
|
26
|
+
};
|
|
27
|
+
export function getDiscordRuntimeStatus() {
|
|
28
|
+
return { ...runtimeStatus };
|
|
29
|
+
}
|
|
21
30
|
const discordPlugin = {
|
|
22
31
|
id: '@tinyclaw/plugin-channel-discord',
|
|
23
32
|
name: 'Discord',
|
|
24
33
|
description: 'Connect Tiny Claw to a Discord bot',
|
|
25
34
|
type: 'channel',
|
|
26
35
|
version: '0.1.0',
|
|
36
|
+
channelPrefix: 'discord',
|
|
27
37
|
getPairingTools(secrets, configManager) {
|
|
28
38
|
return createDiscordPairingTools(secrets, configManager);
|
|
29
39
|
},
|
|
30
40
|
async start(context) {
|
|
31
41
|
const isEnabled = context.configManager.get(DISCORD_ENABLED_CONFIG_KEY);
|
|
42
|
+
runtimeStatus.enabled = Boolean(isEnabled);
|
|
32
43
|
if (!isEnabled) {
|
|
44
|
+
runtimeStatus.state = 'disabled';
|
|
45
|
+
runtimeStatus.readyTag = null;
|
|
46
|
+
runtimeStatus.lastError = null;
|
|
33
47
|
logger.info('Discord plugin: not enabled — run pairing to enable');
|
|
34
48
|
return;
|
|
35
49
|
}
|
|
36
50
|
const token = await context.secrets.retrieve(DISCORD_TOKEN_SECRET_KEY);
|
|
37
51
|
if (!token) {
|
|
52
|
+
runtimeStatus.state = 'error';
|
|
53
|
+
runtimeStatus.readyTag = null;
|
|
54
|
+
runtimeStatus.lastError = 'Discord bot token not found in secrets';
|
|
38
55
|
logger.warn('Discord plugin: enabled but no token found — re-pair to fix');
|
|
39
56
|
return;
|
|
40
57
|
}
|
|
58
|
+
runtimeStatus.state = 'starting';
|
|
59
|
+
runtimeStatus.readyTag = null;
|
|
60
|
+
runtimeStatus.lastError = null;
|
|
41
61
|
client = new Client({
|
|
42
62
|
intents: [
|
|
43
63
|
GatewayIntentBits.Guilds,
|
|
@@ -48,23 +68,28 @@ const discordPlugin = {
|
|
|
48
68
|
partials: [Partials.Channel],
|
|
49
69
|
});
|
|
50
70
|
client.once(Events.ClientReady, (readyClient) => {
|
|
71
|
+
runtimeStatus.state = 'connected';
|
|
72
|
+
runtimeStatus.readyTag = readyClient.user.tag;
|
|
73
|
+
runtimeStatus.lastError = null;
|
|
51
74
|
logger.info(`Discord bot ready: ${readyClient.user.tag}`);
|
|
52
75
|
});
|
|
76
|
+
client.on(Events.Error, (error) => {
|
|
77
|
+
runtimeStatus.state = 'error';
|
|
78
|
+
runtimeStatus.readyTag = null;
|
|
79
|
+
runtimeStatus.lastError = error.message;
|
|
80
|
+
logger.error('Discord plugin: client error', error);
|
|
81
|
+
});
|
|
53
82
|
client.on(Events.MessageCreate, async (msg) => {
|
|
54
83
|
// Ignore messages from bots (including self)
|
|
55
84
|
if (msg.author.bot)
|
|
56
85
|
return;
|
|
57
86
|
const isDM = msg.channel.isDMBased();
|
|
58
|
-
const isMention = client?.user
|
|
59
|
-
? msg.mentions.users.has(client.user.id)
|
|
60
|
-
: false;
|
|
87
|
+
const isMention = client?.user ? msg.mentions.users.has(client.user.id) : false;
|
|
61
88
|
// Only respond to DMs or @mentions
|
|
62
89
|
if (!isDM && !isMention)
|
|
63
90
|
return;
|
|
64
91
|
// Strip @mention tokens from guild messages
|
|
65
|
-
const rawContent = msg.content
|
|
66
|
-
.replace(/<@!?[\d]+>/g, '')
|
|
67
|
-
.trim();
|
|
92
|
+
const rawContent = msg.content.replace(/<@!?[\d]+>/g, '').trim();
|
|
68
93
|
if (!rawContent)
|
|
69
94
|
return;
|
|
70
95
|
// Prefix userId to isolate Discord sessions from web UI sessions
|
|
@@ -97,8 +122,48 @@ const discordPlugin = {
|
|
|
97
122
|
}
|
|
98
123
|
}
|
|
99
124
|
});
|
|
100
|
-
|
|
101
|
-
|
|
125
|
+
try {
|
|
126
|
+
await client.login(token);
|
|
127
|
+
logger.info('Discord bot connected');
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
runtimeStatus.state = 'error';
|
|
131
|
+
runtimeStatus.readyTag = null;
|
|
132
|
+
runtimeStatus.lastError = error instanceof Error ? error.message : String(error);
|
|
133
|
+
if (client) {
|
|
134
|
+
client.destroy();
|
|
135
|
+
client = null;
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
async sendToUser(userId, message) {
|
|
141
|
+
if (!client) {
|
|
142
|
+
throw new Error('Discord client is not connected');
|
|
143
|
+
}
|
|
144
|
+
// Parse the Discord user ID from the prefixed format "discord:<id>"
|
|
145
|
+
const discordId = userId.replace(/^discord:/, '');
|
|
146
|
+
if (!discordId) {
|
|
147
|
+
throw new Error(`Invalid Discord userId: ${userId}`);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const user = await client.users.fetch(discordId);
|
|
151
|
+
const text = message.content;
|
|
152
|
+
if (text.length <= 2000) {
|
|
153
|
+
await user.send(text);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const chunks = splitIntoChunks(text, 1900);
|
|
157
|
+
for (const chunk of chunks) {
|
|
158
|
+
await user.send(chunk);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
logger.info(`Discord: sent outbound message to ${userId}`);
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
logger.error(`Discord: failed to send to ${userId}`, err);
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
102
167
|
},
|
|
103
168
|
async stop() {
|
|
104
169
|
if (client) {
|
|
@@ -106,6 +171,8 @@ const discordPlugin = {
|
|
|
106
171
|
client = null;
|
|
107
172
|
logger.info('Discord bot disconnected');
|
|
108
173
|
}
|
|
174
|
+
runtimeStatus.state = runtimeStatus.enabled ? 'stopped' : 'disabled';
|
|
175
|
+
runtimeStatus.readyTag = null;
|
|
109
176
|
},
|
|
110
177
|
};
|
|
111
178
|
/** Split a string into chunks without cutting words at boundaries. */
|
package/dist/pairing.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* These tools are injected into the agent's tool list at boot so the agent
|
|
10
10
|
* can invoke them conversationally when a user asks to connect Discord.
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { ConfigManagerInterface, SecretsManagerInterface, Tool } from '@tinyclaw/types';
|
|
13
13
|
/** Secret key for the Discord bot token. */
|
|
14
14
|
export declare const DISCORD_TOKEN_SECRET_KEY: string;
|
|
15
15
|
/** Config key for the enabled flag. */
|
package/dist/pairing.js
CHANGED
|
@@ -21,6 +21,7 @@ export function createDiscordPairingTools(secrets, configManager) {
|
|
|
21
21
|
{
|
|
22
22
|
name: 'discord_pair',
|
|
23
23
|
description: 'Pair Tiny Claw with a Discord bot. ' +
|
|
24
|
+
'If the user has not provided a bot token yet, first walk them through creating one in the Discord Developer Portal and ask them to paste it here. ' +
|
|
24
25
|
'Stores the bot token securely and enables the Discord channel plugin. ' +
|
|
25
26
|
'After pairing, call tinyclaw_restart to connect the bot. ' +
|
|
26
27
|
'To get a token: go to https://discord.com/developers/applications, ' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinyclaw/plugin-channel-discord",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-dev.00575dc",
|
|
4
4
|
"description": "Discord channel plugin for Tiny Claw",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"author": "Waren Gonzaga",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"build": "tsc -p tsconfig.json"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@tinyclaw/logger": "
|
|
36
|
-
"@tinyclaw/types": "
|
|
35
|
+
"@tinyclaw/logger": "2.0.0",
|
|
36
|
+
"@tinyclaw/types": "2.0.0",
|
|
37
37
|
"discord.js": "^14.0.0"
|
|
38
38
|
}
|
|
39
39
|
}
|