Add poll chat command with reaction voting
This commit is contained in:
@@ -24,6 +24,9 @@ const SENDABLE_CHANNEL_TYPES = new Set([0, 1, 3, 5, 10, 11, 12]);
|
||||
/** Discord's message fetch action, located lazily at runtime. */
|
||||
const MessageActions = findByPropsLazy("fetchMessages");
|
||||
|
||||
/** Discord's reaction actions, located lazily at runtime. */
|
||||
const ReactionActions = findByPropsLazy("addReaction", "removeReaction");
|
||||
|
||||
function compareIds(a: string, b: string): number {
|
||||
try {
|
||||
const left = BigInt(a);
|
||||
@@ -122,14 +125,30 @@ function buildState() {
|
||||
};
|
||||
}
|
||||
|
||||
async function send(channelId: string, content: string) {
|
||||
async function send(channelId: string, content: string): Promise<unknown> {
|
||||
try {
|
||||
await sendMessage(channelId, { content }, false);
|
||||
return await sendMessage(channelId, { content }, false);
|
||||
} catch (error) {
|
||||
throw new ActionError(PuppetErrorCode.SendFailed, `Discord rejected the message: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Find the ID of a just-sent message. Use the send result first, then the store. */
|
||||
async function resolveMessageId(raw: unknown, channelId: string, content: string): Promise<string | undefined> {
|
||||
if (raw && typeof raw === "object" && typeof (raw as any).id === "string") return (raw as any).id;
|
||||
|
||||
const selfId = UserStore.getCurrentUser()?.id;
|
||||
|
||||
for (let attempt = 0; attempt < 10; attempt++) {
|
||||
const messages: any[] = MessageStore.getMessages(channelId)?._array ?? [];
|
||||
const match = [...messages].reverse().find(message => message.author?.id === selfId && message.content === content);
|
||||
if (match) return match.id;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function resolveDmChannel(userId: string): Promise<string> {
|
||||
let channelId = ChannelStore.getDMFromUserId(userId);
|
||||
if (!channelId) {
|
||||
@@ -204,8 +223,38 @@ export async function handlePuppetAction(
|
||||
getSendableChannel(channelId);
|
||||
enforceOutboundRateLimit();
|
||||
|
||||
await send(channelId, content);
|
||||
return ok({ channelId, sent: true });
|
||||
const messageId = await resolveMessageId(await send(channelId, content), channelId, content);
|
||||
return ok({ channelId, messageId, sent: true });
|
||||
}
|
||||
|
||||
case PuppetAction.ReactionAdd: {
|
||||
const { channelId, messageId, emoji } = params;
|
||||
assert(isSnowflake(channelId), PuppetErrorCode.InvalidSnowflake, "channelId must be a snowflake");
|
||||
assert(isSnowflake(messageId), PuppetErrorCode.InvalidSnowflake, "messageId must be a snowflake");
|
||||
assert(
|
||||
typeof emoji === "string" && emoji.length > 0 && emoji.length <= 32 && !/[\r\n]/.test(emoji),
|
||||
PuppetErrorCode.InvalidParam,
|
||||
"emoji must be a short string"
|
||||
);
|
||||
enforceOutboundRateLimit();
|
||||
|
||||
const addReaction = (ReactionActions as any)?.addReaction;
|
||||
assert(
|
||||
typeof addReaction === "function",
|
||||
PuppetErrorCode.NotSupported,
|
||||
"reaction action is not available in this client"
|
||||
);
|
||||
|
||||
try {
|
||||
await addReaction(channelId, messageId, emoji);
|
||||
} catch (error) {
|
||||
throw new ActionError(
|
||||
PuppetErrorCode.ReactionFailed,
|
||||
`Discord rejected the reaction: ${String(error)}`
|
||||
);
|
||||
}
|
||||
|
||||
return ok({ channelId, messageId, emoji, added: true });
|
||||
}
|
||||
|
||||
case PuppetAction.MessageRead: {
|
||||
|
||||
Reference in New Issue
Block a user