diff --git a/src/index.ts b/src/index.ts index 21a5f64..7cd43ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { Hono } from "hono"; -import nacl from "tweetnacl"; +import * as nacl from "tweetnacl"; /** ====== Env ====== */ const BOT_TOKEN = process.env.BOT_TOKEN!; @@ -24,8 +24,8 @@ console.log("[BOOT] Discord Audit Bot starting…"); /** ====== Constants ====== */ const API = "https://discord.com/api/v10"; const DECIDER_USER_ID = "311380871901085707"; -const EMOJI_RENAME = "✏️"; -const EMOJI_ROLE = "😭"; +const RENAME_EMOJIS = ["✏️", "✏"]; // VS16 + no-VS16 +const ROLE_EMOJIS = ["😭"]; /** ====== Helpers ====== */ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); @@ -164,6 +164,21 @@ async function listReactionUsers( return Array.isArray(users) ? users : []; } +async function listReactionUsersMulti( + channelId: string, + messageId: string, + emojis: string[] +) { + const results = await Promise.all( + emojis.map((e) => listReactionUsers(channelId, messageId, e)) + ); + const byId = new Map(); + for (const arr of results) { + for (const u of arr) byId.set(u.id, u); // de-dupe across variants + } + return Array.from(byId.values()); +} + async function fetchNewMessagesAfter( channelId: string, afterMessageId: string @@ -198,12 +213,7 @@ async function removeRole(guildId: string, userId: string, roleId: string) { /** ====== OpenRouter /talk ====== */ const randomSystem = () => { - const pool = [ - "You are a terse analyst. Answer with precision.", - "You are a sarcastic librarian. Be clear, mildly dry.", - "You are a pragmatic engineer. Prefer bullet points and tradeoffs.", - "You are a security reviewer. Be skeptical and concrete.", - ]; + const pool = ["คุณเป็นคนพูดจาเกรียนๆ"]; return pool[Math.floor(Math.random() * pool.length)]; }; @@ -220,7 +230,7 @@ async function callOpenRouterChat(userPrompt: string) { { role: "system", content: randomSystem() }, { role: "user", - content: userPrompt || "Say something useful in 3–5 bullet points.", + content: userPrompt || "ผมชื่อว่าเนก ตอบคำถามผมเสมอ. ผมเป็นคนไม่ดี", }, ], stream: false, @@ -332,7 +342,7 @@ app.post("/interactions", async (c) => { const kind = hit.kind === "deafen" ? "ปิดหู" : "ปิดไมค์"; // Decision message - const intro = `มีคนโดนทำร้าย😭😭 ${kind}\nคนทำ!: <@${executorId}> → คนโดน: <@${targetId}>\nเลือกการลงโทษโดยการกดส่งอีโมจิ:\n${EMOJI_RENAME} = เปลี่ยนชื่อเล่น, ${EMOJI_ROLE} = ลบ Role`; + const intro = `มีคนโดนทำร้าย😭😭 ${kind}\nคนทำ!: <@${executorId}> → คนโดน: <@${targetId}>\nเลือกการลงโทษโดยการกดส่งอีโมจิ:\n${RENAME_EMOJIS[0]} = เปลี่ยนชื่อเล่น, ${ROLE_EMOJIS[0]} = ลบ Role`; const sent = await sendMessage(channelId, intro); if (!sent.ok) { await editOriginal(token, "Cannot post decision message."); @@ -341,32 +351,50 @@ app.post("/interactions", async (c) => { const msgId = sent.data.id as string; // Seed reactions so users can tap - await addReaction(channelId, msgId, EMOJI_RENAME); - await addReaction(channelId, msgId, EMOJI_ROLE); + for (const e of RENAME_EMOJIS) await addReaction(channelId, msgId, e); + for (const e of ROLE_EMOJIS) await addReaction(channelId, msgId, e); + + // Identify the bot user ID (same as application_id for bot apps) + const botUserId = String(interaction.application_id); // Poll reactions up to timeout const deadline = Date.now() + REACTION_TIMEOUT_SEC * 1000; let chosen: "rename" | "role" | null = null; while (Date.now() < deadline && !chosen) { - const [renameUsers, roleUsers] = await Promise.all([ - listReactionUsers(channelId, msgId, EMOJI_RENAME), - listReactionUsers(channelId, msgId, EMOJI_ROLE), + const [renameUsersAll, roleUsersAll] = await Promise.all([ + listReactionUsersMulti(channelId, msgId, RENAME_EMOJIS), + listReactionUsersMulti(channelId, msgId, ROLE_EMOJIS), ]); - // Priority: decider user - const deciderRename = renameUsers.find( + + // Exclude the bot itself from counts + const renameUsers = renameUsersAll.filter( + (u: any) => u?.id !== botUserId + ); + const roleUsers = roleUsersAll.filter( + (u: any) => u?.id !== botUserId + ); + + // Priority: specific decider + const deciderRename = renameUsers.some( (u: any) => u?.id === DECIDER_USER_ID ); - const deciderRole = roleUsers.find( + const deciderRole = roleUsers.some( (u: any) => u?.id === DECIDER_USER_ID ); + + console.log( + `[VOTE] rename=${renameUsers.length} role=${roleUsers.length} deciderRename=${deciderRename} deciderRole=${deciderRole}` + ); + if (deciderRename) chosen = "rename"; else if (deciderRole) chosen = "role"; else { - // Otherwise first to 2 votes - if ((renameUsers?.length || 0) >= 2) chosen = "rename"; - else if ((roleUsers?.length || 0) >= 2) chosen = "role"; + // Otherwise: first path to reach 2 human votes + if (renameUsers.length >= 2) chosen = "rename"; + else if (roleUsers.length >= 2) chosen = "role"; } + if (!chosen) await sleep(2000); } @@ -380,7 +408,7 @@ app.post("/interactions", async (c) => { // Ask for new nickname const ask = await sendMessage( channelId, - `เลือก: เปลี่ยนชื่อเล่น\nพิมพ์ชื่อใหม่ในข้อความถัดไปภายใน ${RENAME_TIMEOUT_SEC} วินาที` + `เลือก: เปลี่ยนชื่อเล่นให้ <@${executorId}>\nพิมพ์ชื่อใหม่ (≤32 ตัวอักษร) ภายใน ${RENAME_TIMEOUT_SEC} วินาที` ); if (!ask.ok) { await sendMessage(channelId, "ถามชื่อไม่ได้ ทำไมอ่า");