cord-bot 1.1.0 → 1.1.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/package.json +1 -1
- package/skills/cord/SKILL.md +10 -0
- package/src/bot.ts +61 -2
package/package.json
CHANGED
package/skills/cord/SKILL.md
CHANGED
|
@@ -340,6 +340,16 @@ cord buttons 123456789 "Delete all archived items older than 30 days?" \
|
|
|
340
340
|
|
|
341
341
|
---
|
|
342
342
|
|
|
343
|
+
## Auto-Complete Behavior
|
|
344
|
+
|
|
345
|
+
When a user adds a ✅ reaction to the **last message** in a thread, Cord automatically:
|
|
346
|
+
1. Detects the reaction
|
|
347
|
+
2. Updates the thread starter message to "✅ Done"
|
|
348
|
+
|
|
349
|
+
This provides a quick way for users to signal "conversation complete" without explicit commands.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
343
353
|
## HTTP API
|
|
344
354
|
|
|
345
355
|
For advanced use cases (webhooks, external scripts), see [HTTP-API.md](./HTTP-API.md).
|
package/src/bot.ts
CHANGED
|
@@ -29,6 +29,7 @@ const client = new Client({
|
|
|
29
29
|
GatewayIntentBits.Guilds,
|
|
30
30
|
GatewayIntentBits.GuildMessages,
|
|
31
31
|
GatewayIntentBits.MessageContent,
|
|
32
|
+
GatewayIntentBits.GuildMessageReactions,
|
|
32
33
|
],
|
|
33
34
|
});
|
|
34
35
|
|
|
@@ -129,19 +130,33 @@ client.on(Events.MessageCreate, async (message: Message) => {
|
|
|
129
130
|
|
|
130
131
|
log(`New mention from ${message.author.tag}`);
|
|
131
132
|
|
|
132
|
-
//
|
|
133
|
+
// Post status message in channel, then create thread from it
|
|
134
|
+
// This allows us to update the status message later (Processing... → Done)
|
|
135
|
+
let statusMessage;
|
|
133
136
|
let thread;
|
|
134
137
|
try {
|
|
138
|
+
// Post status message in the channel
|
|
139
|
+
statusMessage = await (message.channel as TextChannel).send('🤖 Processing...');
|
|
140
|
+
|
|
135
141
|
// Generate thread name from message content
|
|
136
142
|
const rawText = message.content.replace(/<@!?\d+>/g, '').trim();
|
|
137
143
|
const threadName = rawText.length > 50
|
|
138
144
|
? rawText.slice(0, 47) + '...'
|
|
139
145
|
: rawText || 'New conversation';
|
|
140
146
|
|
|
141
|
-
thread
|
|
147
|
+
// Create thread from the status message
|
|
148
|
+
thread = await statusMessage.startThread({
|
|
142
149
|
name: threadName,
|
|
143
150
|
autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
|
|
144
151
|
});
|
|
152
|
+
|
|
153
|
+
// Copy the original message content into the thread for context
|
|
154
|
+
// (excluding the bot mention and the status message)
|
|
155
|
+
const originalMessages = await message.channel.messages.fetch({ limit: 10 });
|
|
156
|
+
const userMessage = originalMessages.find(m => m.id === message.id);
|
|
157
|
+
if (userMessage) {
|
|
158
|
+
await thread.send(`**${message.author.tag}:** ${rawText}`);
|
|
159
|
+
}
|
|
145
160
|
} catch (error) {
|
|
146
161
|
log(`Failed to create thread: ${error}`);
|
|
147
162
|
await message.reply('Failed to start thread. Try again?');
|
|
@@ -152,6 +167,7 @@ client.on(Events.MessageCreate, async (message: Message) => {
|
|
|
152
167
|
const sessionId = crypto.randomUUID();
|
|
153
168
|
|
|
154
169
|
// Store the thread → session mapping
|
|
170
|
+
// Note: thread.id === statusMessage.id because thread was created from that message
|
|
155
171
|
db.run(
|
|
156
172
|
'INSERT INTO threads (thread_id, session_id) VALUES (?, ?)',
|
|
157
173
|
[thread.id, sessionId]
|
|
@@ -176,6 +192,49 @@ client.on(Events.MessageCreate, async (message: Message) => {
|
|
|
176
192
|
});
|
|
177
193
|
});
|
|
178
194
|
|
|
195
|
+
// =========================================================================
|
|
196
|
+
// REACTION HANDLER: ✅ on last message marks thread as done
|
|
197
|
+
// =========================================================================
|
|
198
|
+
client.on(Events.MessageReactionAdd, async (reaction, user) => {
|
|
199
|
+
// Ignore bot reactions
|
|
200
|
+
if (user.bot) return;
|
|
201
|
+
|
|
202
|
+
// Only handle ✅ reactions
|
|
203
|
+
if (reaction.emoji.name !== '✅') return;
|
|
204
|
+
|
|
205
|
+
// Only handle reactions in threads
|
|
206
|
+
const channel = reaction.message.channel;
|
|
207
|
+
if (!channel.isThread()) return;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const thread = channel;
|
|
211
|
+
const parentChannelId = thread.parentId;
|
|
212
|
+
if (!parentChannelId) return;
|
|
213
|
+
|
|
214
|
+
// Check if this is the last message in the thread
|
|
215
|
+
const messages = await thread.messages.fetch({ limit: 1 });
|
|
216
|
+
const lastMessage = messages.first();
|
|
217
|
+
|
|
218
|
+
if (!lastMessage || lastMessage.id !== reaction.message.id) {
|
|
219
|
+
// Reaction is not on the last message, ignore
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
log(`✅ reaction on last message in thread ${thread.id}`);
|
|
224
|
+
|
|
225
|
+
// Update thread starter message to "Done"
|
|
226
|
+
// The thread ID equals the starter message ID (thread was created from that message)
|
|
227
|
+
const parentChannel = await client.channels.fetch(parentChannelId);
|
|
228
|
+
if (parentChannel?.isTextBased()) {
|
|
229
|
+
const starterMessage = await (parentChannel as TextChannel).messages.fetch(thread.id);
|
|
230
|
+
await starterMessage.edit('✅ Done');
|
|
231
|
+
log(`Thread ${thread.id} marked as Done`);
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
log(`Failed to mark thread done: ${error}`);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
179
238
|
// Start the bot
|
|
180
239
|
const token = process.env.DISCORD_BOT_TOKEN;
|
|
181
240
|
if (!token) {
|