238 lines
6.3 KiB
JavaScript
238 lines
6.3 KiB
JavaScript
const { db } = require("./db");
|
|
const { incrementCommands } = require("./stats");
|
|
const {
|
|
buildCommandContext,
|
|
runAdvancedCommand,
|
|
normalizeCommandResult
|
|
} = require("./commands");
|
|
const { getEnabledPlatformIds, normalizePlatformSelection } = require("./platforms");
|
|
|
|
function createCommandRouter({ settings }) {
|
|
const commandMap = new Map();
|
|
const pluginCommands = new Map();
|
|
|
|
function clearCommands(pluginId) {
|
|
const existing = pluginCommands.get(pluginId) || [];
|
|
for (const entry of existing) {
|
|
const handlers = commandMap.get(entry.trigger) || [];
|
|
const nextHandlers = handlers.filter((handler) => handler !== entry.handler);
|
|
if (nextHandlers.length) {
|
|
commandMap.set(entry.trigger, nextHandlers);
|
|
} else {
|
|
commandMap.delete(entry.trigger);
|
|
}
|
|
}
|
|
pluginCommands.delete(pluginId);
|
|
}
|
|
|
|
function registerCommands(pluginId, commands = []) {
|
|
if (!pluginId) {
|
|
throw new Error("Plugin id is required to register commands.");
|
|
}
|
|
clearCommands(pluginId);
|
|
const entries = [];
|
|
for (const command of commands) {
|
|
const triggers = (command.triggers || [])
|
|
.map((trigger) => trigger.toLowerCase())
|
|
.filter(Boolean);
|
|
const handler = buildHandler(command);
|
|
for (const trigger of triggers) {
|
|
const list = commandMap.get(trigger) || [];
|
|
list.push(handler);
|
|
commandMap.set(trigger, list);
|
|
entries.push({ trigger, handler });
|
|
}
|
|
}
|
|
pluginCommands.set(pluginId, entries);
|
|
}
|
|
|
|
function buildHandler(command) {
|
|
const handler = async (ctx) => {
|
|
if (command.platforms && command.platforms.length) {
|
|
if (!command.platforms.includes(ctx.platform)) {
|
|
return false;
|
|
}
|
|
}
|
|
return await command.handler(ctx);
|
|
};
|
|
handler.commandId = command.id || null;
|
|
return handler;
|
|
}
|
|
|
|
async function handleMessage({ platform, raw, user, platformUser, reply, meta }) {
|
|
const prefix = settings.getSetting("command_prefix", "!");
|
|
if (!raw.startsWith(prefix)) {
|
|
return false;
|
|
}
|
|
const rawCommand = raw.slice(prefix.length).trim();
|
|
if (!rawCommand) {
|
|
return false;
|
|
}
|
|
const parts = rawCommand.split(/\s+/);
|
|
const trigger = parts[0].toLowerCase();
|
|
const args = parts.slice(1);
|
|
const argsText = args.join(" ");
|
|
const ctx = {
|
|
platform,
|
|
trigger,
|
|
raw,
|
|
args,
|
|
argsText,
|
|
user: {
|
|
id: user.id,
|
|
username: user.internal_username || user.username,
|
|
platformId: platformUser.id,
|
|
displayName: platformUser.displayName || platformUser.username,
|
|
tag: platformUser.tag
|
|
},
|
|
platformUser,
|
|
meta,
|
|
reply
|
|
};
|
|
|
|
const customHandled = await handleCustomCommand({
|
|
trigger,
|
|
platform,
|
|
ctx,
|
|
raw,
|
|
reply
|
|
});
|
|
if (customHandled) {
|
|
incrementCommands(user.id);
|
|
return true;
|
|
}
|
|
|
|
const handlers = commandMap.get(trigger) || [];
|
|
for (const handler of handlers) {
|
|
try {
|
|
const result = await handler(ctx);
|
|
if (typeof result === "string" && result) {
|
|
await safeReply(reply, result);
|
|
recordCommandUsage(handler.commandId);
|
|
incrementCommands(user.id);
|
|
return true;
|
|
}
|
|
if (result === true) {
|
|
recordCommandUsage(handler.commandId);
|
|
incrementCommands(user.id);
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.error("Command handler failed", error);
|
|
await safeReply(reply, "Command failed to execute.");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return {
|
|
registerCommands,
|
|
clearCommands,
|
|
handleMessage
|
|
};
|
|
}
|
|
|
|
async function handleCustomCommand({ trigger, platform, ctx, raw, reply }) {
|
|
const row = db
|
|
.prepare(
|
|
"SELECT response, mode, language, code, platform FROM custom_commands WHERE trigger = ? AND enabled = 1"
|
|
)
|
|
.get(trigger);
|
|
if (!row) {
|
|
return false;
|
|
}
|
|
const enabledPlatforms = getEnabledPlatformIds();
|
|
const allowedPlatforms = normalizePlatformSelection(row.platform, enabledPlatforms);
|
|
if (!allowedPlatforms.includes(platform)) {
|
|
return false;
|
|
}
|
|
try {
|
|
if (row.mode === "advanced" && row.code) {
|
|
const messageInfo = buildMessageInfo(ctx, raw);
|
|
const commandCtx = buildCommandContext({
|
|
platform,
|
|
user: {
|
|
id: ctx.user.id,
|
|
platformId: ctx.user.platformId,
|
|
username: ctx.user.username,
|
|
displayName: ctx.user.displayName,
|
|
tag: ctx.user.tag
|
|
},
|
|
message: messageInfo,
|
|
args: ctx.args,
|
|
argsText: ctx.argsText
|
|
});
|
|
const result = await runAdvancedCommand(
|
|
{ code: row.code, language: row.language },
|
|
commandCtx
|
|
);
|
|
const output = normalizeCommandResult(result);
|
|
if (output) {
|
|
await safeReply(reply, output);
|
|
} else {
|
|
await safeReply(reply, "Command ran but returned no output.");
|
|
}
|
|
} else {
|
|
await safeReply(reply, row.response);
|
|
}
|
|
recordCommandUsage(`custom:${trigger}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Failed to reply to command", error);
|
|
await safeReply(reply, "Command failed to execute.");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function buildMessageInfo(ctx, raw) {
|
|
if (ctx.platform === "discord" && ctx.meta?.message) {
|
|
const message = ctx.meta.message;
|
|
return {
|
|
id: message.id,
|
|
content: raw,
|
|
channelId: message.channelId,
|
|
guildId: message.guildId
|
|
};
|
|
}
|
|
if (ctx.platform === "twitch") {
|
|
return {
|
|
channel: ctx.meta?.channel,
|
|
content: raw
|
|
};
|
|
}
|
|
if (ctx.platform === "youtube") {
|
|
return {
|
|
liveChatId: ctx.meta?.liveChatId,
|
|
messageId: ctx.meta?.messageId,
|
|
channelId: ctx.meta?.author?.channelId,
|
|
content: raw
|
|
};
|
|
}
|
|
return { content: raw };
|
|
}
|
|
|
|
async function safeReply(reply, content) {
|
|
try {
|
|
await reply(content);
|
|
} catch (error) {
|
|
console.error("Command reply failed", error);
|
|
}
|
|
}
|
|
|
|
function recordCommandUsage(commandId) {
|
|
if (!commandId) {
|
|
return;
|
|
}
|
|
const now = Date.now();
|
|
db.prepare(
|
|
"INSERT INTO command_usage (command_id, count, updated_at) VALUES (?, 1, ?) " +
|
|
"ON CONFLICT(command_id) DO UPDATE SET count = count + 1, updated_at = excluded.updated_at"
|
|
).run(commandId, now);
|
|
}
|
|
|
|
module.exports = {
|
|
createCommandRouter
|
|
};
|