feat: add multi-reaction user listing and improve emoji handling in interaction responses

This commit is contained in:
Sosokker 2025-08-29 01:44:53 +07:00
parent 63de53b655
commit 903b33885a

View File

@ -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<string, any>();
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 35 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, "ถามชื่อไม่ได้ ทำไมอ่า");