Zero-dependency Discord connector under packages/discord: binding
validation, REST and gateway clients, pi engine adapter, journal with
append-only inbox, outbox, admissions and notices, and a run.lock
ownership record {pid, start, boot} whose identity is checked three ways
and whose cleanup is gated by STOP. CLI check|run|stop|unlock via
scripts/discord.sh; offline suite scripts/test-discord.sh (28 checks,
87 node tests).
Reviewed by rev-code-02 on #1509 over nine rounds; approved exact tree
4e0feb6758c0a7e4a71483912a8e0d3e3ec95aef at comment 26170. Corrections
(1) to (12) recorded in BUILD-LOG. No listener started, no token read,
no Discord write; the live pilot follows this commit per the brief.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
69 lines
3.3 KiB
JavaScript
69 lines
3.3 KiB
JavaScript
// The pure decision: does this MESSAGE_CREATE get a turn? No I/O, no clock.
|
|
// Every drop has one reason string, which the connector journals as a drop
|
|
// line. The order is cheapest and most conservative first.
|
|
//
|
|
// channelInfo: a lookup for channels that are not listed directly, used to
|
|
// find a thread's parent. It is `(id) => {type, parentId} | undefined`. The
|
|
// connector fills it from GUILD_CREATE and THREAD_* events and, on a miss,
|
|
// one REST call. authorize never calls the network itself.
|
|
|
|
export const CHANNEL_TYPE = Object.freeze({
|
|
GUILD_TEXT: 0,
|
|
ANNOUNCEMENT_THREAD: 10,
|
|
PUBLIC_THREAD: 11,
|
|
PRIVATE_THREAD: 12,
|
|
});
|
|
const THREAD_TYPES = new Set([CHANNEL_TYPE.ANNOUNCEMENT_THREAD, CHANNEL_TYPE.PUBLIC_THREAD, CHANNEL_TYPE.PRIVATE_THREAD]);
|
|
|
|
export function isThreadType(type) {
|
|
return THREAD_TYPES.has(type);
|
|
}
|
|
|
|
export const DROP = Object.freeze({
|
|
NOT_OBJECT: "not-an-object",
|
|
NO_ID: "no-message-id",
|
|
GUILD: "wrong-guild",
|
|
BOT: "author-is-bot",
|
|
WEBHOOK: "webhook",
|
|
SELF: "author-is-self",
|
|
USER: "user-unlisted",
|
|
CHANNEL: "channel-unlisted",
|
|
THREAD_PARENT: "thread-parent-unlisted",
|
|
MENTION: "no-mention",
|
|
});
|
|
|
|
function mentionsBot(message, botUserId) {
|
|
if (!Array.isArray(message.mentions)) return false;
|
|
return message.mentions.some((m) => m && typeof m === "object" && m.id === botUserId);
|
|
}
|
|
|
|
// Returns {ok: true, channel, thread, oversize} or {ok: false, reason}.
|
|
export function authorize(binding, message, channelInfo = () => undefined) {
|
|
if (!message || typeof message !== "object") return { ok: false, reason: DROP.NOT_OBJECT };
|
|
if (typeof message.id !== "string" || message.id.length === 0) return { ok: false, reason: DROP.NO_ID };
|
|
if (message.guild_id !== binding.guildId) return { ok: false, reason: DROP.GUILD };
|
|
const author = message.author && typeof message.author === "object" ? message.author : null;
|
|
if (!author || typeof author.id !== "string") return { ok: false, reason: DROP.USER };
|
|
if (author.id === binding.botUserId) return { ok: false, reason: DROP.SELF };
|
|
if (message.webhook_id !== undefined && message.webhook_id !== null) return { ok: false, reason: DROP.WEBHOOK };
|
|
if (author.bot === true || author.system === true) return { ok: false, reason: DROP.BOT };
|
|
if (!binding.users.some((u) => u.id === author.id)) return { ok: false, reason: DROP.USER };
|
|
|
|
let channel = binding.channels.find((c) => c.id === message.channel_id);
|
|
let thread = null;
|
|
if (!channel) {
|
|
const info = channelInfo(message.channel_id);
|
|
if (!info || !isThreadType(info.type)) return { ok: false, reason: DROP.CHANNEL };
|
|
if (info.guildId !== undefined && info.guildId !== binding.guildId) return { ok: false, reason: DROP.GUILD };
|
|
channel = binding.channels.find((c) => c.id === info.parentId);
|
|
if (!channel) return { ok: false, reason: DROP.THREAD_PARENT };
|
|
thread = { id: message.channel_id, name: typeof info.name === "string" ? info.name : null };
|
|
}
|
|
// A thread inherits the parent's mode (Q4). @everyone is not a mention of
|
|
// the bot; only an entry in `mentions` with the bot's id counts.
|
|
if (channel.mode === "mention" && !mentionsBot(message, binding.botUserId)) return { ok: false, reason: DROP.MENTION };
|
|
|
|
const content = typeof message.content === "string" ? message.content : "";
|
|
return { ok: true, channel, thread, oversize: content.length > binding.limits.inboundMaxChars };
|
|
}
|