@tensakulabs/discord-mcp 0.1.2 → 0.1.3
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/config.js +12 -0
- package/dist/daemon.js +34 -4
- package/dist/db.js +18 -12
- package/dist/hooks.js +34 -0
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -10,6 +10,12 @@ const DEFAULTS = {
|
|
|
10
10
|
mentioned: 90,
|
|
11
11
|
fallback_to_api: true,
|
|
12
12
|
},
|
|
13
|
+
hooks: {
|
|
14
|
+
on_mention: [],
|
|
15
|
+
on_everyone: [],
|
|
16
|
+
on_here: [],
|
|
17
|
+
on_message: [],
|
|
18
|
+
},
|
|
13
19
|
};
|
|
14
20
|
export function loadConfig() {
|
|
15
21
|
if (!existsSync(CONFIG_FILE))
|
|
@@ -18,6 +24,12 @@ export function loadConfig() {
|
|
|
18
24
|
const raw = JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
19
25
|
return {
|
|
20
26
|
retention: { ...DEFAULTS.retention, ...(raw.retention ?? {}) },
|
|
27
|
+
hooks: {
|
|
28
|
+
on_mention: raw.hooks?.on_mention ?? [],
|
|
29
|
+
on_everyone: raw.hooks?.on_everyone ?? [],
|
|
30
|
+
on_here: raw.hooks?.on_here ?? [],
|
|
31
|
+
on_message: raw.hooks?.on_message ?? [],
|
|
32
|
+
},
|
|
21
33
|
};
|
|
22
34
|
}
|
|
23
35
|
catch {
|
package/dist/daemon.js
CHANGED
|
@@ -8,6 +8,8 @@ import { WebSocket } from "ws";
|
|
|
8
8
|
import { getToken } from "./auth.js";
|
|
9
9
|
import { initDb, insertMessage } from "./db.js";
|
|
10
10
|
import { schedulePurge } from "./purge.js";
|
|
11
|
+
import { loadConfig } from "./config.js";
|
|
12
|
+
import { fireHooks } from "./hooks.js";
|
|
11
13
|
const GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
|
|
12
14
|
const IDENTIFY_INTENTS = (1 << 0) | (1 << 9) | (1 << 12); // GUILDS | GUILD_MESSAGES | MESSAGE_CONTENT
|
|
13
15
|
let ws = null;
|
|
@@ -16,6 +18,7 @@ let sequence = null;
|
|
|
16
18
|
let sessionId = null;
|
|
17
19
|
let resumeGatewayUrl = null;
|
|
18
20
|
let reconnectDelay = 1000;
|
|
21
|
+
let selfId = null; // set from READY event
|
|
19
22
|
const db = initDb();
|
|
20
23
|
schedulePurge();
|
|
21
24
|
async function connect(resume = false) {
|
|
@@ -82,14 +85,24 @@ function handleEvent(type, data) {
|
|
|
82
85
|
case "READY":
|
|
83
86
|
sessionId = data.session_id;
|
|
84
87
|
resumeGatewayUrl = data.resume_gateway_url;
|
|
85
|
-
|
|
88
|
+
selfId = data.user?.id ?? null;
|
|
89
|
+
console.error(`[discord-mcp daemon] Ready. (selfId: ${selfId})`);
|
|
86
90
|
break;
|
|
87
91
|
case "MESSAGE_CREATE": {
|
|
88
92
|
const msg = data;
|
|
89
93
|
if (msg.author?.bot)
|
|
90
94
|
break; // ignore bots
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
// Detect mention type
|
|
96
|
+
const isDirect = selfId !== null && Array.isArray(msg.mentions) &&
|
|
97
|
+
msg.mentions.some((u) => u.id === selfId);
|
|
98
|
+
const isEveryone = msg.mention_everyone === true &&
|
|
99
|
+
msg.content.includes("@everyone");
|
|
100
|
+
const isHere = msg.mention_everyone === true &&
|
|
101
|
+
msg.content.includes("@here");
|
|
102
|
+
const mentionType = isDirect ? "direct"
|
|
103
|
+
: isEveryone ? "everyone"
|
|
104
|
+
: isHere ? "here"
|
|
105
|
+
: null;
|
|
93
106
|
insertMessage(db, {
|
|
94
107
|
id: msg.id,
|
|
95
108
|
channel_id: msg.channel_id,
|
|
@@ -99,8 +112,25 @@ function handleEvent(type, data) {
|
|
|
99
112
|
content: msg.content,
|
|
100
113
|
timestamp: new Date(msg.timestamp).getTime(),
|
|
101
114
|
is_dm: msg.guild_id ? 0 : 1,
|
|
102
|
-
is_mention:
|
|
115
|
+
is_mention: mentionType !== null ? 1 : 0,
|
|
116
|
+
mention_type: mentionType,
|
|
103
117
|
});
|
|
118
|
+
// Fire hooks
|
|
119
|
+
const cfg = loadConfig();
|
|
120
|
+
const ctx = {
|
|
121
|
+
author: msg.author.username,
|
|
122
|
+
content: msg.content,
|
|
123
|
+
channel: msg.channel_id,
|
|
124
|
+
guild: msg.guild_id ?? "dm",
|
|
125
|
+
is_dm: !msg.guild_id,
|
|
126
|
+
};
|
|
127
|
+
if (isDirect)
|
|
128
|
+
fireHooks(cfg.hooks.on_mention, ctx);
|
|
129
|
+
if (isEveryone)
|
|
130
|
+
fireHooks(cfg.hooks.on_everyone, ctx);
|
|
131
|
+
if (isHere)
|
|
132
|
+
fireHooks(cfg.hooks.on_here, ctx);
|
|
133
|
+
fireHooks(cfg.hooks.on_message, ctx);
|
|
104
134
|
break;
|
|
105
135
|
}
|
|
106
136
|
}
|
package/dist/db.js
CHANGED
|
@@ -28,16 +28,17 @@ export function initDb() {
|
|
|
28
28
|
db.pragma("synchronous = NORMAL");
|
|
29
29
|
db.exec(`
|
|
30
30
|
CREATE TABLE IF NOT EXISTS messages (
|
|
31
|
-
id
|
|
32
|
-
channel_id
|
|
33
|
-
guild_id
|
|
34
|
-
author_id
|
|
35
|
-
author_name
|
|
36
|
-
content
|
|
37
|
-
timestamp
|
|
38
|
-
is_dm
|
|
39
|
-
is_mention
|
|
40
|
-
|
|
31
|
+
id TEXT PRIMARY KEY,
|
|
32
|
+
channel_id TEXT NOT NULL,
|
|
33
|
+
guild_id TEXT,
|
|
34
|
+
author_id TEXT NOT NULL,
|
|
35
|
+
author_name TEXT NOT NULL,
|
|
36
|
+
content TEXT NOT NULL,
|
|
37
|
+
timestamp INTEGER NOT NULL, -- unix ms
|
|
38
|
+
is_dm INTEGER NOT NULL DEFAULT 0,
|
|
39
|
+
is_mention INTEGER NOT NULL DEFAULT 0,
|
|
40
|
+
mention_type TEXT DEFAULT NULL, -- "direct" | "everyone" | "here" | null
|
|
41
|
+
seen INTEGER NOT NULL DEFAULT 0
|
|
41
42
|
);
|
|
42
43
|
|
|
43
44
|
CREATE INDEX IF NOT EXISTS idx_channel_ts ON messages(channel_id, timestamp DESC);
|
|
@@ -55,15 +56,20 @@ export function initDb() {
|
|
|
55
56
|
VALUES (new.rowid, new.content, new.author_name);
|
|
56
57
|
END;
|
|
57
58
|
`);
|
|
59
|
+
// Migration: add mention_type column if not present (existing installs)
|
|
60
|
+
const cols = db.pragma("table_info(messages)").map(c => c.name);
|
|
61
|
+
if (!cols.includes("mention_type")) {
|
|
62
|
+
db.exec("ALTER TABLE messages ADD COLUMN mention_type TEXT DEFAULT NULL");
|
|
63
|
+
}
|
|
58
64
|
_db = db;
|
|
59
65
|
return db;
|
|
60
66
|
}
|
|
61
67
|
export function insertMessage(db, msg) {
|
|
62
68
|
const stmt = db.prepare(`
|
|
63
69
|
INSERT OR IGNORE INTO messages
|
|
64
|
-
(id, channel_id, guild_id, author_id, author_name, content, timestamp, is_dm, is_mention, seen)
|
|
70
|
+
(id, channel_id, guild_id, author_id, author_name, content, timestamp, is_dm, is_mention, mention_type, seen)
|
|
65
71
|
VALUES
|
|
66
|
-
(@id, @channel_id, @guild_id, @author_id, @author_name, @content, @timestamp, @is_dm, @is_mention, 0)
|
|
72
|
+
(@id, @channel_id, @guild_id, @author_id, @author_name, @content, @timestamp, @is_dm, @is_mention, @mention_type, 0)
|
|
67
73
|
`);
|
|
68
74
|
stmt.run(msg);
|
|
69
75
|
}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
function substitute(template, ctx) {
|
|
3
|
+
return template
|
|
4
|
+
.replace(/\{author\}/g, ctx.author)
|
|
5
|
+
.replace(/\{content\}/g, ctx.content.replace(/"/g, '\\"'))
|
|
6
|
+
.replace(/\{channel\}/g, ctx.channel)
|
|
7
|
+
.replace(/\{guild\}/g, ctx.guild)
|
|
8
|
+
.replace(/\{is_dm\}/g, String(ctx.is_dm));
|
|
9
|
+
}
|
|
10
|
+
async function runHook(hook, ctx) {
|
|
11
|
+
if (!hook.enabled)
|
|
12
|
+
return;
|
|
13
|
+
if (hook.type === "command" && hook.command) {
|
|
14
|
+
const cmd = substitute(hook.command, ctx);
|
|
15
|
+
exec(cmd, (err) => {
|
|
16
|
+
if (err)
|
|
17
|
+
console.error(`[discord-mcp hooks] command failed: ${err.message}`);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (hook.type === "http" && hook.url) {
|
|
21
|
+
fetch(hook.url, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
|
+
body: JSON.stringify(ctx),
|
|
25
|
+
}).catch((err) => {
|
|
26
|
+
console.error(`[discord-mcp hooks] http failed: ${err.message}`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function fireHooks(hooks, ctx) {
|
|
31
|
+
for (const hook of hooks) {
|
|
32
|
+
runHook(hook, ctx).catch(() => { }); // never block the daemon
|
|
33
|
+
}
|
|
34
|
+
}
|