conflcit.ts
· 11 KiB · TypeScript
原始檔案
import {
Client,
ButtonBuilder,
ButtonStyle,
ActionRowBuilder,
EmbedBuilder,
ButtonInteraction,
TextChannel,
} from "discord.js";
// Configurable button styles
const RECLAIM_STYLE = ButtonStyle.Secondary; // gray — change to ButtonStyle.Danger for red
const SWITCH_STYLE = ButtonStyle.Secondary;
import { cfg } from "@systems/config";
import { getCharacters, getCharacterByName, setActiveCharacter } from "@systems/characters";
import { clearSessionBorrowForUser, setPersistentPreference, getEffectiveCharacter } from "@systems/borrow";
import { polls, updatePollMessage, createVoteEntry } from "@systems/poll";
import { resolveMessage, nowFormatted } from "@systems/messages";
import { getClassEmoji } from "@systems/emojis";
import { Character } from "@src/types";
import { format } from "@format";
const AUTO_VOTE_ON_SWITCH = process.env.AUTO_VOTE_ON_CONFLICT_SWITCH !== "false";
// Stores pending conflict resolutions: buttonId → { ownerUsermapKey, borrowerUsermapKey, charName, ownerId }
const pendingConflicts = new Map<string, {
ownerUsermapKey: string;
borrowerUsermapKey: string;
charName: string;
ownerId: string;
page: number;
}>();
function formatChar(char: Character): string {
const emoji = getClassEmoji(char.class) || char.class;
return `${emoji} ${char.level} ${char.name}`;
}
// Parse <:name:id> or unicode emoji string for use with ButtonBuilder.setEmoji()
function parseEmoji(emojiStr: string): { name: string; id: string } | string | null {
if (!emojiStr) return null;
const match = emojiStr.match(/^<:(\w+):(\d+)>$/);
if (match) return { name: match[1], id: match[2] };
return emojiStr; // unicode fallback
}
// For button labels — emoji via setEmoji(), text only in label
function applyCharToButton(btn: ButtonBuilder, char: Character): ButtonBuilder {
const emojiStr = getClassEmoji(char.class);
const emoji = format.emoji(emojiStr);
btn.setLabel(`${char.level} ${char.name}`);
if (emoji) btn.setEmoji(emoji as any);
return btn;
}
function buildConflictEmbed(
borrowerKey: string,
char: Character,
ownerKey: string
): EmbedBuilder {
const charDisplay = formatChar(char);
return new EmbedBuilder()
.setTitle("⚠️ Character Conflict")
.setDescription(
`**${charDisplay}** is currently borrowed by **${borrowerKey}** for tonight's TG.\n\nYou can reclaim your character or switch to another one.`
)
.setColor(0xe8a317);
}
function buildConflictButtons(
ownerUsermapKey: string,
borrowerUsermapKey: string,
borrowedCharName: string,
ownerId: string,
allChars: Character[],
page: number
): ActionRowBuilder<ButtonBuilder>[] {
const PAGE_SIZE = 4; // leave 1 slot for reclaim on first row
const otherChars = allChars.filter((c) => c.name !== borrowedCharName);
const pageChars = otherChars.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
const hasMore = otherChars.length > (page + 1) * PAGE_SIZE;
const hasPrev = page > 0;
const rows: ActionRowBuilder<ButtonBuilder>[] = [];
// Row 1: char switch buttons
const charButtons = pageChars.map((char) => {
const id = `conflict_switch:${ownerUsermapKey}:${borrowerUsermapKey}:${char.name}:${ownerId}`;
pendingConflicts.set(id, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page });
return applyCharToButton(
new ButtonBuilder().setCustomId(id).setStyle(ButtonStyle.Secondary),
char
);
});
if (charButtons.length > 0) {
rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(...charButtons));
}
// Row 2: reclaim + pagination
const reclaimId = `conflict_reclaim:${ownerUsermapKey}:${borrowerUsermapKey}:${borrowedCharName}:${ownerId}`;
pendingConflicts.set(reclaimId, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page });
const borrowed = allChars.find((c) => c.name === borrowedCharName);
const reclaimBtn = borrowed
? applyCharToButton(
new ButtonBuilder().setCustomId(reclaimId).setStyle(ButtonStyle.Danger),
borrowed
).setLabel(`${borrowed.level} ${borrowed.name}`)
: new ButtonBuilder().setCustomId(reclaimId).setLabel(`↩️ Reclaim ${borrowedCharName}`).setStyle(RECLAIM_STYLE);
const navButtons: ButtonBuilder[] = [reclaimBtn];
if (hasPrev) {
const prevId = `conflict_page:${ownerUsermapKey}:${borrowerUsermapKey}:${borrowedCharName}:${ownerId}:${page - 1}`;
pendingConflicts.set(prevId, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page: page - 1 });
navButtons.push(new ButtonBuilder().setCustomId(prevId).setLabel("← Prev").setStyle(ButtonStyle.Primary));
}
if (hasMore) {
const nextId = `conflict_page:${ownerUsermapKey}:${borrowerUsermapKey}:${borrowedCharName}:${ownerId}:${page + 1}`;
pendingConflicts.set(nextId, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page: page + 1 });
navButtons.push(new ButtonBuilder().setCustomId(nextId).setLabel("Next →").setStyle(ButtonStyle.Primary));
}
rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(...navButtons));
return rows;
}
// Show conflict embed to owner
export async function showConflictEmbed(
interaction: ButtonInteraction,
ownerUsermapKey: string,
borrowerUsermapKey: string,
borrowedChar: Character,
allOwnerChars: Character[]
): Promise<void> {
const embed = buildConflictEmbed(borrowerUsermapKey, borrowedChar, ownerUsermapKey);
const buttons = buildConflictButtons(
ownerUsermapKey, borrowerUsermapKey, borrowedChar.name,
interaction.user.id, allOwnerChars, 0
);
await interaction.followUp({ embeds: [embed], components: buttons, ephemeral: true });
}
// Handle conflict button interactions
export async function handleConflictButton(interaction: ButtonInteraction): Promise<void> {
const { customId } = interaction;
if (customId.startsWith("conflict_page:")) {
const parts = customId.split(":");
const ownerKey = parts[1];
const borrowerKey = parts[2];
const charName = parts[3];
const ownerId = parts[4];
const page = parseInt(parts[5]);
const allChars = getCharacters(ownerKey);
const borrowed = allChars.find((c) => c.name === charName);
if (!borrowed) return void interaction.reply({ content: "❌ Character not found.", ephemeral: true });
const embed = buildConflictEmbed(borrowerKey, borrowed, ownerKey);
const buttons = buildConflictButtons(ownerKey, borrowerKey, charName, ownerId, allChars, page);
await interaction.update({ embeds: [embed], components: buttons });
return;
}
if (customId.startsWith("conflict_switch:")) {
const parts = customId.split(":");
const ownerKey = parts[1];
const borrowerKey = parts[2];
const newCharName = parts[3];
const ownerId = parts[4];
// Switch owner to the selected char
setActiveCharacter(ownerKey, newCharName);
clearSessionBorrowForUser(ownerKey);
const slot = [...polls.keys()][0];
const state = slot !== undefined ? polls.get(slot) : null;
if (state && AUTO_VOTE_ON_SWITCH) {
// Auto-vote Yes for owner with new char
const guild = interaction.guild!;
const member = await guild.members.fetch(ownerId);
const { char } = getEffectiveCharacter(ownerKey);
const now = nowFormatted();
const publicMsg = resolveMessage("public", "yes", 1, ownerKey, member.nickname ?? null, member.user.globalName ?? null);
state.yes.set(ownerId, {
userKey: ownerKey,
displayName: member.nickname ?? member.user.globalName ?? member.user.username,
characterName: char?.name,
characterClass: char?.class,
characterLevel: char?.level,
characterNation: char?.nation,
votedAt: now,
publicMessage: publicMsg ?? undefined,
});
const channel = await interaction.client.channels.fetch(cfg("pollChannelId")) as TextChannel;
await updatePollMessage(channel, slot!);
}
await interaction.update({
embeds: [new EmbedBuilder()
.setTitle("✅ Switched")
.setDescription(`Switched to **${newCharName}**${AUTO_VOTE_ON_SWITCH ? " and voted Yes." : "."}`)
.setColor(0x57f287)],
components: [],
});
return;
}
if (customId.startsWith("conflict_reclaim:")) {
const parts = customId.split(":");
const ownerKey = parts[1];
const borrowerKey = parts[2];
const charName = parts[3];
const ownerId = parts[4];
const reclaimBehavior = (cfg as any)("conflictReclaimBehavior") ?? "revert"; // "revert" | "remove"
const slot = [...polls.keys()][0];
const state = slot !== undefined ? polls.get(slot) : null;
if (state) {
// Find borrower's vote entry
for (const [id, entry] of [...state.yes.entries(), ...state.no.entries()]) {
if (entry.userKey === borrowerKey) {
if (reclaimBehavior === "remove") {
state.yes.delete(id);
state.no.delete(id);
} else {
// Revert borrower to their own active char
clearSessionBorrowForUser(borrowerKey);
const { char: ownChar } = getEffectiveCharacter(borrowerKey);
if (ownChar) {
entry.characterName = ownChar.name;
entry.characterClass = ownChar.class;
entry.characterLevel = ownChar.level;
entry.characterNation = ownChar.nation;
entry.borrowedFrom = undefined;
} else {
// No own char — remove from poll
state.yes.delete(id);
state.no.delete(id);
}
}
break;
}
}
// Owner joins with their character
const guild = interaction.guild!;
const member = await guild.members.fetch(ownerId);
setActiveCharacter(ownerKey, charName);
clearSessionBorrowForUser(ownerKey);
const { char } = getEffectiveCharacter(ownerKey);
const now = nowFormatted();
const publicMsg = resolveMessage("public", "yes", 1, ownerKey, member.nickname ?? null, member.user.globalName ?? null);
state.yes.set(ownerId, {
userKey: ownerKey,
displayName: member.nickname ?? member.user.globalName ?? member.user.username,
characterName: char?.name,
characterClass: char?.class,
characterLevel: char?.level,
characterNation: char?.nation,
votedAt: now,
publicMessage: publicMsg ?? undefined,
});
const channel = await interaction.client.channels.fetch(cfg("pollChannelId")) as TextChannel;
await updatePollMessage(channel, slot!);
}
await interaction.update({
embeds: [new EmbedBuilder()
.setTitle("↩️ Reclaimed")
.setDescription(`**${charName}** has been reclaimed from **${borrowerKey}** and you've been added to the poll.`)
.setColor(0x57f287)],
components: [],
});
return;
}
}
| 1 | import { |
| 2 | Client, |
| 3 | ButtonBuilder, |
| 4 | ButtonStyle, |
| 5 | ActionRowBuilder, |
| 6 | EmbedBuilder, |
| 7 | ButtonInteraction, |
| 8 | TextChannel, |
| 9 | } from "discord.js"; |
| 10 | |
| 11 | // Configurable button styles |
| 12 | const RECLAIM_STYLE = ButtonStyle.Secondary; // gray — change to ButtonStyle.Danger for red |
| 13 | const SWITCH_STYLE = ButtonStyle.Secondary; |
| 14 | import { cfg } from "@systems/config"; |
| 15 | import { getCharacters, getCharacterByName, setActiveCharacter } from "@systems/characters"; |
| 16 | import { clearSessionBorrowForUser, setPersistentPreference, getEffectiveCharacter } from "@systems/borrow"; |
| 17 | import { polls, updatePollMessage, createVoteEntry } from "@systems/poll"; |
| 18 | import { resolveMessage, nowFormatted } from "@systems/messages"; |
| 19 | import { getClassEmoji } from "@systems/emojis"; |
| 20 | import { Character } from "@src/types"; |
| 21 | import { format } from "@format"; |
| 22 | |
| 23 | const AUTO_VOTE_ON_SWITCH = process.env.AUTO_VOTE_ON_CONFLICT_SWITCH !== "false"; |
| 24 | |
| 25 | // Stores pending conflict resolutions: buttonId → { ownerUsermapKey, borrowerUsermapKey, charName, ownerId } |
| 26 | const pendingConflicts = new Map<string, { |
| 27 | ownerUsermapKey: string; |
| 28 | borrowerUsermapKey: string; |
| 29 | charName: string; |
| 30 | ownerId: string; |
| 31 | page: number; |
| 32 | }>(); |
| 33 | |
| 34 | function formatChar(char: Character): string { |
| 35 | const emoji = getClassEmoji(char.class) || char.class; |
| 36 | return `${emoji} ${char.level} ${char.name}`; |
| 37 | } |
| 38 | |
| 39 | // Parse <:name:id> or unicode emoji string for use with ButtonBuilder.setEmoji() |
| 40 | function parseEmoji(emojiStr: string): { name: string; id: string } | string | null { |
| 41 | if (!emojiStr) return null; |
| 42 | const match = emojiStr.match(/^<:(\w+):(\d+)>$/); |
| 43 | if (match) return { name: match[1], id: match[2] }; |
| 44 | return emojiStr; // unicode fallback |
| 45 | } |
| 46 | |
| 47 | // For button labels — emoji via setEmoji(), text only in label |
| 48 | function applyCharToButton(btn: ButtonBuilder, char: Character): ButtonBuilder { |
| 49 | const emojiStr = getClassEmoji(char.class); |
| 50 | const emoji = format.emoji(emojiStr); |
| 51 | btn.setLabel(`${char.level} ${char.name}`); |
| 52 | if (emoji) btn.setEmoji(emoji as any); |
| 53 | return btn; |
| 54 | } |
| 55 | |
| 56 | function buildConflictEmbed( |
| 57 | borrowerKey: string, |
| 58 | char: Character, |
| 59 | ownerKey: string |
| 60 | ): EmbedBuilder { |
| 61 | const charDisplay = formatChar(char); |
| 62 | return new EmbedBuilder() |
| 63 | .setTitle("⚠️ Character Conflict") |
| 64 | .setDescription( |
| 65 | `**${charDisplay}** is currently borrowed by **${borrowerKey}** for tonight's TG.\n\nYou can reclaim your character or switch to another one.` |
| 66 | ) |
| 67 | .setColor(0xe8a317); |
| 68 | } |
| 69 | |
| 70 | function buildConflictButtons( |
| 71 | ownerUsermapKey: string, |
| 72 | borrowerUsermapKey: string, |
| 73 | borrowedCharName: string, |
| 74 | ownerId: string, |
| 75 | allChars: Character[], |
| 76 | page: number |
| 77 | ): ActionRowBuilder<ButtonBuilder>[] { |
| 78 | const PAGE_SIZE = 4; // leave 1 slot for reclaim on first row |
| 79 | const otherChars = allChars.filter((c) => c.name !== borrowedCharName); |
| 80 | const pageChars = otherChars.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE); |
| 81 | const hasMore = otherChars.length > (page + 1) * PAGE_SIZE; |
| 82 | const hasPrev = page > 0; |
| 83 | |
| 84 | const rows: ActionRowBuilder<ButtonBuilder>[] = []; |
| 85 | |
| 86 | // Row 1: char switch buttons |
| 87 | const charButtons = pageChars.map((char) => { |
| 88 | const id = `conflict_switch:${ownerUsermapKey}:${borrowerUsermapKey}:${char.name}:${ownerId}`; |
| 89 | pendingConflicts.set(id, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page }); |
| 90 | return applyCharToButton( |
| 91 | new ButtonBuilder().setCustomId(id).setStyle(ButtonStyle.Secondary), |
| 92 | char |
| 93 | ); |
| 94 | }); |
| 95 | |
| 96 | if (charButtons.length > 0) { |
| 97 | rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(...charButtons)); |
| 98 | } |
| 99 | |
| 100 | // Row 2: reclaim + pagination |
| 101 | const reclaimId = `conflict_reclaim:${ownerUsermapKey}:${borrowerUsermapKey}:${borrowedCharName}:${ownerId}`; |
| 102 | pendingConflicts.set(reclaimId, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page }); |
| 103 | |
| 104 | const borrowed = allChars.find((c) => c.name === borrowedCharName); |
| 105 | const reclaimBtn = borrowed |
| 106 | ? applyCharToButton( |
| 107 | new ButtonBuilder().setCustomId(reclaimId).setStyle(ButtonStyle.Danger), |
| 108 | borrowed |
| 109 | ).setLabel(`${borrowed.level} ${borrowed.name}`) |
| 110 | : new ButtonBuilder().setCustomId(reclaimId).setLabel(`↩️ Reclaim ${borrowedCharName}`).setStyle(RECLAIM_STYLE); |
| 111 | |
| 112 | const navButtons: ButtonBuilder[] = [reclaimBtn]; |
| 113 | |
| 114 | if (hasPrev) { |
| 115 | const prevId = `conflict_page:${ownerUsermapKey}:${borrowerUsermapKey}:${borrowedCharName}:${ownerId}:${page - 1}`; |
| 116 | pendingConflicts.set(prevId, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page: page - 1 }); |
| 117 | navButtons.push(new ButtonBuilder().setCustomId(prevId).setLabel("← Prev").setStyle(ButtonStyle.Primary)); |
| 118 | } |
| 119 | if (hasMore) { |
| 120 | const nextId = `conflict_page:${ownerUsermapKey}:${borrowerUsermapKey}:${borrowedCharName}:${ownerId}:${page + 1}`; |
| 121 | pendingConflicts.set(nextId, { ownerUsermapKey, borrowerUsermapKey, charName: borrowedCharName, ownerId, page: page + 1 }); |
| 122 | navButtons.push(new ButtonBuilder().setCustomId(nextId).setLabel("Next →").setStyle(ButtonStyle.Primary)); |
| 123 | } |
| 124 | |
| 125 | rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(...navButtons)); |
| 126 | return rows; |
| 127 | } |
| 128 | |
| 129 | // Show conflict embed to owner |
| 130 | export async function showConflictEmbed( |
| 131 | interaction: ButtonInteraction, |
| 132 | ownerUsermapKey: string, |
| 133 | borrowerUsermapKey: string, |
| 134 | borrowedChar: Character, |
| 135 | allOwnerChars: Character[] |
| 136 | ): Promise<void> { |
| 137 | const embed = buildConflictEmbed(borrowerUsermapKey, borrowedChar, ownerUsermapKey); |
| 138 | const buttons = buildConflictButtons( |
| 139 | ownerUsermapKey, borrowerUsermapKey, borrowedChar.name, |
| 140 | interaction.user.id, allOwnerChars, 0 |
| 141 | ); |
| 142 | await interaction.followUp({ embeds: [embed], components: buttons, ephemeral: true }); |
| 143 | } |
| 144 | |
| 145 | // Handle conflict button interactions |
| 146 | export async function handleConflictButton(interaction: ButtonInteraction): Promise<void> { |
| 147 | const { customId } = interaction; |
| 148 | |
| 149 | if (customId.startsWith("conflict_page:")) { |
| 150 | const parts = customId.split(":"); |
| 151 | const ownerKey = parts[1]; |
| 152 | const borrowerKey = parts[2]; |
| 153 | const charName = parts[3]; |
| 154 | const ownerId = parts[4]; |
| 155 | const page = parseInt(parts[5]); |
| 156 | |
| 157 | const allChars = getCharacters(ownerKey); |
| 158 | const borrowed = allChars.find((c) => c.name === charName); |
| 159 | if (!borrowed) return void interaction.reply({ content: "❌ Character not found.", ephemeral: true }); |
| 160 | |
| 161 | const embed = buildConflictEmbed(borrowerKey, borrowed, ownerKey); |
| 162 | const buttons = buildConflictButtons(ownerKey, borrowerKey, charName, ownerId, allChars, page); |
| 163 | await interaction.update({ embeds: [embed], components: buttons }); |
| 164 | return; |
| 165 | } |
| 166 | |
| 167 | if (customId.startsWith("conflict_switch:")) { |
| 168 | const parts = customId.split(":"); |
| 169 | const ownerKey = parts[1]; |
| 170 | const borrowerKey = parts[2]; |
| 171 | const newCharName = parts[3]; |
| 172 | const ownerId = parts[4]; |
| 173 | |
| 174 | // Switch owner to the selected char |
| 175 | setActiveCharacter(ownerKey, newCharName); |
| 176 | clearSessionBorrowForUser(ownerKey); |
| 177 | |
| 178 | const slot = [...polls.keys()][0]; |
| 179 | const state = slot !== undefined ? polls.get(slot) : null; |
| 180 | |
| 181 | if (state && AUTO_VOTE_ON_SWITCH) { |
| 182 | // Auto-vote Yes for owner with new char |
| 183 | const guild = interaction.guild!; |
| 184 | const member = await guild.members.fetch(ownerId); |
| 185 | const { char } = getEffectiveCharacter(ownerKey); |
| 186 | const now = nowFormatted(); |
| 187 | const publicMsg = resolveMessage("public", "yes", 1, ownerKey, member.nickname ?? null, member.user.globalName ?? null); |
| 188 | |
| 189 | state.yes.set(ownerId, { |
| 190 | userKey: ownerKey, |
| 191 | displayName: member.nickname ?? member.user.globalName ?? member.user.username, |
| 192 | characterName: char?.name, |
| 193 | characterClass: char?.class, |
| 194 | characterLevel: char?.level, |
| 195 | characterNation: char?.nation, |
| 196 | votedAt: now, |
| 197 | publicMessage: publicMsg ?? undefined, |
| 198 | }); |
| 199 | |
| 200 | const channel = await interaction.client.channels.fetch(cfg("pollChannelId")) as TextChannel; |
| 201 | await updatePollMessage(channel, slot!); |
| 202 | } |
| 203 | |
| 204 | await interaction.update({ |
| 205 | embeds: [new EmbedBuilder() |
| 206 | .setTitle("✅ Switched") |
| 207 | .setDescription(`Switched to **${newCharName}**${AUTO_VOTE_ON_SWITCH ? " and voted Yes." : "."}`) |
| 208 | .setColor(0x57f287)], |
| 209 | components: [], |
| 210 | }); |
| 211 | return; |
| 212 | } |
| 213 | |
| 214 | if (customId.startsWith("conflict_reclaim:")) { |
| 215 | const parts = customId.split(":"); |
| 216 | const ownerKey = parts[1]; |
| 217 | const borrowerKey = parts[2]; |
| 218 | const charName = parts[3]; |
| 219 | const ownerId = parts[4]; |
| 220 | |
| 221 | const reclaimBehavior = (cfg as any)("conflictReclaimBehavior") ?? "revert"; // "revert" | "remove" |
| 222 | const slot = [...polls.keys()][0]; |
| 223 | const state = slot !== undefined ? polls.get(slot) : null; |
| 224 | |
| 225 | if (state) { |
| 226 | // Find borrower's vote entry |
| 227 | for (const [id, entry] of [...state.yes.entries(), ...state.no.entries()]) { |
| 228 | if (entry.userKey === borrowerKey) { |
| 229 | if (reclaimBehavior === "remove") { |
| 230 | state.yes.delete(id); |
| 231 | state.no.delete(id); |
| 232 | } else { |
| 233 | // Revert borrower to their own active char |
| 234 | clearSessionBorrowForUser(borrowerKey); |
| 235 | const { char: ownChar } = getEffectiveCharacter(borrowerKey); |
| 236 | if (ownChar) { |
| 237 | entry.characterName = ownChar.name; |
| 238 | entry.characterClass = ownChar.class; |
| 239 | entry.characterLevel = ownChar.level; |
| 240 | entry.characterNation = ownChar.nation; |
| 241 | entry.borrowedFrom = undefined; |
| 242 | } else { |
| 243 | // No own char — remove from poll |
| 244 | state.yes.delete(id); |
| 245 | state.no.delete(id); |
| 246 | } |
| 247 | } |
| 248 | break; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | // Owner joins with their character |
| 253 | const guild = interaction.guild!; |
| 254 | const member = await guild.members.fetch(ownerId); |
| 255 | setActiveCharacter(ownerKey, charName); |
| 256 | clearSessionBorrowForUser(ownerKey); |
| 257 | const { char } = getEffectiveCharacter(ownerKey); |
| 258 | const now = nowFormatted(); |
| 259 | const publicMsg = resolveMessage("public", "yes", 1, ownerKey, member.nickname ?? null, member.user.globalName ?? null); |
| 260 | |
| 261 | state.yes.set(ownerId, { |
| 262 | userKey: ownerKey, |
| 263 | displayName: member.nickname ?? member.user.globalName ?? member.user.username, |
| 264 | characterName: char?.name, |
| 265 | characterClass: char?.class, |
| 266 | characterLevel: char?.level, |
| 267 | characterNation: char?.nation, |
| 268 | votedAt: now, |
| 269 | publicMessage: publicMsg ?? undefined, |
| 270 | }); |
| 271 | |
| 272 | const channel = await interaction.client.channels.fetch(cfg("pollChannelId")) as TextChannel; |
| 273 | await updatePollMessage(channel, slot!); |
| 274 | } |
| 275 | |
| 276 | await interaction.update({ |
| 277 | embeds: [new EmbedBuilder() |
| 278 | .setTitle("↩️ Reclaimed") |
| 279 | .setDescription(`**${charName}** has been reclaimed from **${borrowerKey}** and you've been added to the poll.`) |
| 280 | .setColor(0x57f287)], |
| 281 | components: [], |
| 282 | }); |
| 283 | return; |
| 284 | } |
| 285 | } |