feat: add multi-reaction user listing and improve emoji handling in interaction responses
This commit is contained in:
parent
63de53b655
commit
903b33885a
74
src/index.ts
74
src/index.ts
@ -1,5 +1,5 @@
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import nacl from "tweetnacl";
|
import * as nacl from "tweetnacl";
|
||||||
|
|
||||||
/** ====== Env ====== */
|
/** ====== Env ====== */
|
||||||
const BOT_TOKEN = process.env.BOT_TOKEN!;
|
const BOT_TOKEN = process.env.BOT_TOKEN!;
|
||||||
@ -24,8 +24,8 @@ console.log("[BOOT] Discord Audit Bot starting…");
|
|||||||
/** ====== Constants ====== */
|
/** ====== Constants ====== */
|
||||||
const API = "https://discord.com/api/v10";
|
const API = "https://discord.com/api/v10";
|
||||||
const DECIDER_USER_ID = "311380871901085707";
|
const DECIDER_USER_ID = "311380871901085707";
|
||||||
const EMOJI_RENAME = "✏️";
|
const RENAME_EMOJIS = ["✏️", "✏"]; // VS16 + no-VS16
|
||||||
const EMOJI_ROLE = "😭";
|
const ROLE_EMOJIS = ["😭"];
|
||||||
|
|
||||||
/** ====== Helpers ====== */
|
/** ====== Helpers ====== */
|
||||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||||
@ -164,6 +164,21 @@ async function listReactionUsers(
|
|||||||
return Array.isArray(users) ? users : [];
|
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(
|
async function fetchNewMessagesAfter(
|
||||||
channelId: string,
|
channelId: string,
|
||||||
afterMessageId: string
|
afterMessageId: string
|
||||||
@ -198,12 +213,7 @@ async function removeRole(guildId: string, userId: string, roleId: string) {
|
|||||||
|
|
||||||
/** ====== OpenRouter /talk ====== */
|
/** ====== OpenRouter /talk ====== */
|
||||||
const randomSystem = () => {
|
const randomSystem = () => {
|
||||||
const pool = [
|
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.",
|
|
||||||
];
|
|
||||||
return pool[Math.floor(Math.random() * pool.length)];
|
return pool[Math.floor(Math.random() * pool.length)];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -220,7 +230,7 @@ async function callOpenRouterChat(userPrompt: string) {
|
|||||||
{ role: "system", content: randomSystem() },
|
{ role: "system", content: randomSystem() },
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: userPrompt || "Say something useful in 3–5 bullet points.",
|
content: userPrompt || "ผมชื่อว่าเนก ตอบคำถามผมเสมอ. ผมเป็นคนไม่ดี",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
stream: false,
|
stream: false,
|
||||||
@ -332,7 +342,7 @@ app.post("/interactions", async (c) => {
|
|||||||
const kind = hit.kind === "deafen" ? "ปิดหู" : "ปิดไมค์";
|
const kind = hit.kind === "deafen" ? "ปิดหู" : "ปิดไมค์";
|
||||||
|
|
||||||
// Decision message
|
// 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);
|
const sent = await sendMessage(channelId, intro);
|
||||||
if (!sent.ok) {
|
if (!sent.ok) {
|
||||||
await editOriginal(token, "Cannot post decision message.");
|
await editOriginal(token, "Cannot post decision message.");
|
||||||
@ -341,32 +351,50 @@ app.post("/interactions", async (c) => {
|
|||||||
const msgId = sent.data.id as string;
|
const msgId = sent.data.id as string;
|
||||||
|
|
||||||
// Seed reactions so users can tap
|
// Seed reactions so users can tap
|
||||||
await addReaction(channelId, msgId, EMOJI_RENAME);
|
for (const e of RENAME_EMOJIS) await addReaction(channelId, msgId, e);
|
||||||
await addReaction(channelId, msgId, EMOJI_ROLE);
|
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
|
// Poll reactions up to timeout
|
||||||
const deadline = Date.now() + REACTION_TIMEOUT_SEC * 1000;
|
const deadline = Date.now() + REACTION_TIMEOUT_SEC * 1000;
|
||||||
let chosen: "rename" | "role" | null = null;
|
let chosen: "rename" | "role" | null = null;
|
||||||
|
|
||||||
while (Date.now() < deadline && !chosen) {
|
while (Date.now() < deadline && !chosen) {
|
||||||
const [renameUsers, roleUsers] = await Promise.all([
|
const [renameUsersAll, roleUsersAll] = await Promise.all([
|
||||||
listReactionUsers(channelId, msgId, EMOJI_RENAME),
|
listReactionUsersMulti(channelId, msgId, RENAME_EMOJIS),
|
||||||
listReactionUsers(channelId, msgId, EMOJI_ROLE),
|
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
|
(u: any) => u?.id === DECIDER_USER_ID
|
||||||
);
|
);
|
||||||
const deciderRole = roleUsers.find(
|
const deciderRole = roleUsers.some(
|
||||||
(u: any) => u?.id === DECIDER_USER_ID
|
(u: any) => u?.id === DECIDER_USER_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[VOTE] rename=${renameUsers.length} role=${roleUsers.length} deciderRename=${deciderRename} deciderRole=${deciderRole}`
|
||||||
|
);
|
||||||
|
|
||||||
if (deciderRename) chosen = "rename";
|
if (deciderRename) chosen = "rename";
|
||||||
else if (deciderRole) chosen = "role";
|
else if (deciderRole) chosen = "role";
|
||||||
else {
|
else {
|
||||||
// Otherwise first to 2 votes
|
// Otherwise: first path to reach 2 human votes
|
||||||
if ((renameUsers?.length || 0) >= 2) chosen = "rename";
|
if (renameUsers.length >= 2) chosen = "rename";
|
||||||
else if ((roleUsers?.length || 0) >= 2) chosen = "role";
|
else if (roleUsers.length >= 2) chosen = "role";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chosen) await sleep(2000);
|
if (!chosen) await sleep(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +408,7 @@ app.post("/interactions", async (c) => {
|
|||||||
// Ask for new nickname
|
// Ask for new nickname
|
||||||
const ask = await sendMessage(
|
const ask = await sendMessage(
|
||||||
channelId,
|
channelId,
|
||||||
`เลือก: เปลี่ยนชื่อเล่น\nพิมพ์ชื่อใหม่ในข้อความถัดไปภายใน ${RENAME_TIMEOUT_SEC} วินาที`
|
`เลือก: เปลี่ยนชื่อเล่นให้ <@${executorId}>\nพิมพ์ชื่อใหม่ (≤32 ตัวอักษร) ภายใน ${RENAME_TIMEOUT_SEC} วินาที`
|
||||||
);
|
);
|
||||||
if (!ask.ok) {
|
if (!ask.ok) {
|
||||||
await sendMessage(channelId, "ถามชื่อไม่ได้ ทำไมอ่า");
|
await sendMessage(channelId, "ถามชื่อไม่ได้ ทำไมอ่า");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user