added bankruptcy
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
const {
|
||||
closeInteractionSession
|
||||
} = require("../../utils/interactionSession");
|
||||
|
||||
module.exports = {
|
||||
name: "sessionClose",
|
||||
|
||||
async execute(interaction) {
|
||||
return closeInteractionSession(interaction);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
const {
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
MessageFlags
|
||||
} = require("discord.js");
|
||||
|
||||
const {
|
||||
declareBankruptcy
|
||||
} = require("../../utils/bankruptcy");
|
||||
|
||||
const {
|
||||
requireInteractionSession
|
||||
} = require("../../utils/interactionSession");
|
||||
|
||||
module.exports = {
|
||||
name: "crumbSaucerBankruptcy",
|
||||
|
||||
async execute(interaction) {
|
||||
const session = await requireInteractionSession(interaction);
|
||||
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await declareBankruptcy(interaction);
|
||||
|
||||
if (!result.ok) {
|
||||
if (result.reason === "COOLDOWN") {
|
||||
return interaction.reply({
|
||||
content:
|
||||
`Bankruptcy assistance is unavailable until <t:${result.nextAvailableAt}:R>.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
if (result.reason === "BALANCE_NOT_ZERO") {
|
||||
return interaction.reply({
|
||||
content:
|
||||
`Bankruptcy can only be declared at exactly **0 Cookies**. Your current balance is **${result.balance.toLocaleString()}**.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
return interaction.reply({
|
||||
content: "Bankruptcy could not be declared.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🍪 Bankruptcy Assistance")
|
||||
.setDescription(
|
||||
[
|
||||
`${interaction.user} declared bankruptcy and received a starter kit.`,
|
||||
"",
|
||||
`**+${result.amount.toLocaleString()} Cookies**`,
|
||||
"",
|
||||
"Spend them wisely. Or at least memorably."
|
||||
].join("\n")
|
||||
)
|
||||
.addFields({
|
||||
name: "Next Eligibility",
|
||||
value: `<t:${result.nextAvailableAt}:R>`
|
||||
})
|
||||
.setFooter({
|
||||
text: `crumbSaucer:${interaction.user.id}`
|
||||
});
|
||||
|
||||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId("crumbSaucerBack")
|
||||
.setLabel("Back to Crumb Saucer")
|
||||
.setEmoji("◀️")
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId("sessionClose")
|
||||
.setLabel("Close")
|
||||
.setEmoji("✖️")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
);
|
||||
|
||||
return interaction.update({
|
||||
embeds: [embed],
|
||||
components: [row]
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
const {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
MessageFlags
|
||||
} = require("discord.js");
|
||||
|
||||
const {
|
||||
declareBankruptcy
|
||||
} = require("../../utils/bankruptcy");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("bankruptcy")
|
||||
.setDescription(
|
||||
"Declare bankruptcy at exactly 0 Cookies and receive a starter kit."
|
||||
),
|
||||
|
||||
async execute(interaction) {
|
||||
const result = await declareBankruptcy(interaction);
|
||||
|
||||
if (!result.ok) {
|
||||
if (result.reason === "COOLDOWN") {
|
||||
return interaction.reply({
|
||||
content:
|
||||
`Bankruptcy assistance is unavailable until <t:${result.nextAvailableAt}:R>.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
if (result.reason === "BALANCE_NOT_ZERO") {
|
||||
return interaction.reply({
|
||||
content:
|
||||
`You may only declare bankruptcy at exactly **0 Cookies**. Your current balance is **${result.balance.toLocaleString()}**.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
return interaction.reply({
|
||||
content: "Bankruptcy could not be declared.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🍪 Bankruptcy Declared")
|
||||
.setDescription(
|
||||
[
|
||||
`${interaction.user} has been issued a fresh starter kit.`,
|
||||
"",
|
||||
`**+${result.amount.toLocaleString()} Cookies**`,
|
||||
"",
|
||||
"The Cookie economy believes in second chances."
|
||||
].join("\n")
|
||||
)
|
||||
.addFields({
|
||||
name: "Next Eligibility",
|
||||
value: `<t:${result.nextAvailableAt}:R>`
|
||||
});
|
||||
|
||||
return interaction.reply({
|
||||
embeds: [embed]
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,63 +1,46 @@
|
||||
const {
|
||||
SlashCommandBuilder
|
||||
} = require(
|
||||
"discord.js"
|
||||
);
|
||||
|
||||
const { SlashCommandBuilder } = require("discord.js");
|
||||
const {
|
||||
buildProfileOverview,
|
||||
buildProfileButtons
|
||||
} = require(
|
||||
"../../utils/profileSystem"
|
||||
);
|
||||
} = require("../../utils/profileSystem");
|
||||
const {
|
||||
createInteractionSession
|
||||
} = require("../../utils/interactionSession");
|
||||
|
||||
module.exports = {
|
||||
data:
|
||||
new SlashCommandBuilder()
|
||||
.setName(
|
||||
"profile"
|
||||
)
|
||||
.setDescription(
|
||||
"View a player profile"
|
||||
)
|
||||
.addUserOption(
|
||||
option =>
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("profile")
|
||||
.setDescription("View a player profile")
|
||||
.addUserOption(option =>
|
||||
option
|
||||
.setName(
|
||||
"user"
|
||||
)
|
||||
.setDescription(
|
||||
"Player to view"
|
||||
)
|
||||
.setName("user")
|
||||
.setDescription("Player to view")
|
||||
),
|
||||
|
||||
async execute(
|
||||
interaction
|
||||
) {
|
||||
const user =
|
||||
interaction.options.getUser(
|
||||
"user"
|
||||
) ??
|
||||
interaction.user;
|
||||
async execute(interaction) {
|
||||
const user = interaction.options.getUser("user") ?? interaction.user;
|
||||
|
||||
const sessionOwnerId =
|
||||
interaction.user.id;
|
||||
|
||||
const embed =
|
||||
await buildProfileOverview(
|
||||
const embed = await buildProfileOverview(
|
||||
interaction.guildId,
|
||||
user,
|
||||
sessionOwnerId
|
||||
interaction.user.id
|
||||
);
|
||||
|
||||
return interaction.reply({
|
||||
await interaction.reply({
|
||||
embeds: [embed],
|
||||
components:
|
||||
buildProfileButtons(
|
||||
"overview",
|
||||
0,
|
||||
1
|
||||
)
|
||||
components: buildProfileButtons()
|
||||
});
|
||||
|
||||
const message = await interaction.fetchReply();
|
||||
|
||||
await createInteractionSession(
|
||||
interaction,
|
||||
{
|
||||
messageId: message.id,
|
||||
ownerId: interaction.user.id,
|
||||
kind: "profile",
|
||||
targetId: user.id
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+56
-152
@@ -3,8 +3,11 @@ const {
|
||||
MessageFlags
|
||||
} = require("discord.js");
|
||||
|
||||
require("dotenv").config();
|
||||
const {
|
||||
guardSessionInteraction
|
||||
} = require("../utils/interactionSession");
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
@@ -36,203 +39,110 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
async function autocompleteHandler(interaction) {
|
||||
const command =
|
||||
interaction.client.commands.get(
|
||||
const command = interaction.client.commands.get(
|
||||
interaction.commandName
|
||||
);
|
||||
|
||||
|
||||
if (!command) {
|
||||
return console.error(
|
||||
`No command matching ${interaction.commandName} was found.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
typeof command.autocomplete !==
|
||||
"function"
|
||||
) {
|
||||
if (typeof command.autocomplete !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await command.autocomplete(
|
||||
interaction
|
||||
);
|
||||
|
||||
await command.autocomplete(interaction);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Autocomplete error for /${interaction.commandName}:`,
|
||||
error
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Autocomplete cannot use normal interaction.reply().
|
||||
* If something failed, send an empty set so Discord
|
||||
* stops waiting.
|
||||
*/
|
||||
try {
|
||||
await interaction.respond([]);
|
||||
} catch {
|
||||
// Interaction may already be acknowledged or expired.
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function commandHandler(interaction) {
|
||||
const command =
|
||||
interaction.client.commands.get(
|
||||
const command = interaction.client.commands.get(
|
||||
interaction.commandName
|
||||
);
|
||||
|
||||
|
||||
if (!command) {
|
||||
return console.error(
|
||||
`No command matching ${interaction.commandName} was found.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await command.execute(
|
||||
interaction
|
||||
);
|
||||
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
if (
|
||||
interaction.replied ||
|
||||
interaction.deferred
|
||||
) {
|
||||
await interaction.followUp({
|
||||
content:
|
||||
"There was an error while executing this command!",
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content:
|
||||
"There was an error while executing this command!",
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
await sendInteractionError(
|
||||
interaction,
|
||||
"There was an error while executing this command!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function buttonHandler(interaction) {
|
||||
const button =
|
||||
interaction.client.buttons.get(
|
||||
const allowed = await guardSessionInteraction(interaction);
|
||||
|
||||
if (!allowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const button = interaction.client.buttons.get(
|
||||
interaction.customId
|
||||
);
|
||||
|
||||
|
||||
if (!button) {
|
||||
return console.error(
|
||||
`No button registered matching ${interaction.customId}.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await button.execute(
|
||||
interaction
|
||||
);
|
||||
|
||||
await button.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
if (
|
||||
interaction.replied ||
|
||||
interaction.deferred
|
||||
) {
|
||||
await interaction.followUp({
|
||||
content:
|
||||
"There was an error while executing this button!",
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content:
|
||||
"There was an error while executing this button!",
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
await sendInteractionError(
|
||||
interaction,
|
||||
"There was an error while executing this button!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function selectMenuHandler(interaction) {
|
||||
const selectMenu =
|
||||
interaction.client.selectMenus.get(
|
||||
const selectMenu = interaction.client.selectMenus.get(
|
||||
interaction.customId
|
||||
);
|
||||
|
||||
|
||||
if (!selectMenu) {
|
||||
return console.error(
|
||||
`No Select Menu registered matching ${interaction.customId}.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await selectMenu.execute(
|
||||
interaction
|
||||
);
|
||||
|
||||
await selectMenu.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
if (
|
||||
interaction.replied ||
|
||||
interaction.deferred
|
||||
) {
|
||||
await interaction.followUp({
|
||||
content:
|
||||
"There was an error while executing this select menu!",
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content:
|
||||
"There was an error while executing this select menu!",
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
await sendInteractionError(
|
||||
interaction,
|
||||
"There was an error while executing this select menu!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function modalHandler(interaction) {
|
||||
const customId =
|
||||
interaction.customId
|
||||
.split(":")[0];
|
||||
|
||||
|
||||
const modal =
|
||||
interaction.client
|
||||
.modals
|
||||
.get(
|
||||
customId
|
||||
);
|
||||
|
||||
const customId = interaction.customId.split(":")[0];
|
||||
const modal = interaction.client.modals.get(customId);
|
||||
|
||||
if (!modal) {
|
||||
return console.error(
|
||||
@@ -240,36 +150,30 @@ async function modalHandler(interaction) {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await modal.execute(
|
||||
interaction
|
||||
);
|
||||
|
||||
await modal.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
|
||||
if (
|
||||
interaction.replied ||
|
||||
interaction.deferred
|
||||
) {
|
||||
await interaction.followUp({
|
||||
content:
|
||||
"There was an error while executing this modal!",
|
||||
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content:
|
||||
"There was an error while executing this modal!",
|
||||
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
await sendInteractionError(
|
||||
interaction,
|
||||
"There was an error while executing this modal!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendInteractionError(interaction, content) {
|
||||
const payload = {
|
||||
content,
|
||||
flags: MessageFlags.Ephemeral
|
||||
};
|
||||
|
||||
try {
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp(payload);
|
||||
} else {
|
||||
await interaction.reply(payload);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to send interaction error response:", error);
|
||||
}
|
||||
}
|
||||
@@ -5,34 +5,18 @@ const {
|
||||
Partials
|
||||
} = require("discord.js");
|
||||
|
||||
const {
|
||||
MongoClient
|
||||
} = require("mongodb");
|
||||
|
||||
const { MongoClient } = require("mongodb");
|
||||
const fs = require("node:fs");
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
|
||||
/*
|
||||
* MONGODB
|
||||
*/
|
||||
|
||||
const mClient =
|
||||
new MongoClient(
|
||||
const mClient = new MongoClient(
|
||||
process.env.M_URI
|
||||
);
|
||||
);
|
||||
|
||||
exports.mClient =
|
||||
mClient;
|
||||
exports.mClient = mClient;
|
||||
|
||||
|
||||
/*
|
||||
* DISCORD CLIENT
|
||||
*/
|
||||
|
||||
const client =
|
||||
new Client({
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
@@ -43,7 +27,6 @@ const client =
|
||||
GatewayIntentBits.DirectMessages,
|
||||
GatewayIntentBits.MessageContent
|
||||
],
|
||||
|
||||
partials: [
|
||||
Partials.Channel,
|
||||
Partials.Message,
|
||||
@@ -51,110 +34,53 @@ const client =
|
||||
Partials.GuildMember,
|
||||
Partials.Reaction
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
exports.client =
|
||||
client;
|
||||
exports.client = client;
|
||||
|
||||
|
||||
/*
|
||||
* COLLECTIONS
|
||||
*/
|
||||
|
||||
client.commands =
|
||||
new Collection();
|
||||
|
||||
client.legacyCommands =
|
||||
new Collection();
|
||||
|
||||
client.aliases =
|
||||
new Collection();
|
||||
|
||||
client.buttons =
|
||||
new Collection();
|
||||
|
||||
client.selectMenus =
|
||||
new Collection();
|
||||
|
||||
client.modals =
|
||||
new Collection();
|
||||
|
||||
|
||||
/*
|
||||
* START APPLICATION
|
||||
*/
|
||||
client.commands = new Collection();
|
||||
client.legacyCommands = new Collection();
|
||||
client.aliases = new Collection();
|
||||
client.buttons = new Collection();
|
||||
client.selectMenus = new Collection();
|
||||
client.modals = new Collection();
|
||||
|
||||
async function start() {
|
||||
try {
|
||||
/*
|
||||
* Connect Mongo first.
|
||||
*/
|
||||
|
||||
await mClient.connect();
|
||||
|
||||
console.log(
|
||||
"MongoDB connected"
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Initialize database indexes before handlers are
|
||||
* exposed to Discord interactions.
|
||||
*
|
||||
* Requiring these after mClient is exported avoids
|
||||
* circular-import initialization problems because the
|
||||
* shared utilities import mClient from index.js.
|
||||
*/
|
||||
console.log("MongoDB connected");
|
||||
|
||||
const {
|
||||
ensureCookieIndexes
|
||||
} = require(
|
||||
"./utils/cookieEconomy"
|
||||
);
|
||||
|
||||
} = require("./utils/cookieEconomy");
|
||||
const {
|
||||
ensureProfileSystem
|
||||
} = require(
|
||||
"./utils/profileSystem"
|
||||
);
|
||||
} = require("./utils/profileSystem");
|
||||
const {
|
||||
ensureInteractionSessionIndexes
|
||||
} = require("./utils/interactionSession");
|
||||
const {
|
||||
ensureBankruptcyIndexes
|
||||
} = require("./utils/bankruptcy");
|
||||
|
||||
await Promise.all([
|
||||
ensureCookieIndexes(),
|
||||
ensureProfileSystem()
|
||||
ensureProfileSystem(),
|
||||
ensureInteractionSessionIndexes(),
|
||||
ensureBankruptcyIndexes()
|
||||
]);
|
||||
|
||||
console.log(
|
||||
"Database indexes initialized"
|
||||
);
|
||||
console.log("Database indexes initialized");
|
||||
|
||||
const handlerFiles = fs.readdirSync("./handlers");
|
||||
|
||||
/*
|
||||
* Load handlers.
|
||||
*/
|
||||
|
||||
const handlerFiles =
|
||||
fs.readdirSync(
|
||||
"./handlers"
|
||||
);
|
||||
|
||||
for (
|
||||
const handler
|
||||
of handlerFiles
|
||||
) {
|
||||
require(
|
||||
`./handlers/${handler}`
|
||||
)(client);
|
||||
for (const handler of handlerFiles) {
|
||||
require(`./handlers/${handler}`)(client);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Login Discord.
|
||||
*/
|
||||
|
||||
await client.login(
|
||||
process.env.D_TOKEN
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to start ShaiBot:",
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
const { getCollections, getCookieBalance, getUnixTime } = require("./cookieEconomy");
|
||||
const { trackProfileEvent } = require("./profileSystem");
|
||||
|
||||
const BANKRUPTCY_COLLECTION = "items_bankruptcy";
|
||||
const BANKRUPTCY_GRANT = 1000;
|
||||
const BANKRUPTCY_COOLDOWN = 1 * 60 * 60;
|
||||
|
||||
function getBankruptcyCollection() {
|
||||
return getCollections().db.collection(BANKRUPTCY_COLLECTION);
|
||||
}
|
||||
|
||||
async function ensureBankruptcyIndexes() {
|
||||
const bankruptcy = getBankruptcyCollection();
|
||||
|
||||
await bankruptcy.createIndex(
|
||||
{ userId: 1, guildId: 1 },
|
||||
{ unique: true }
|
||||
);
|
||||
}
|
||||
|
||||
async function getBankruptcyStatus(userId, guildId) {
|
||||
const balance = await getCookieBalance(userId);
|
||||
const document = await getBankruptcyCollection().findOne({
|
||||
userId,
|
||||
guildId
|
||||
});
|
||||
|
||||
const now = getUnixTime();
|
||||
const nextAvailableAt = Number(document?.nextAvailableAt ?? 0);
|
||||
|
||||
return {
|
||||
balance,
|
||||
eligible: balance === 0 && nextAvailableAt <= now,
|
||||
nextAvailableAt,
|
||||
claims: Number(document?.claims ?? 0)
|
||||
};
|
||||
}
|
||||
|
||||
async function declareBankruptcy(interaction, userId = interaction.user.id) {
|
||||
const guildId = interaction.guildId;
|
||||
|
||||
if (!guildId) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "GUILD_ONLY"
|
||||
};
|
||||
}
|
||||
|
||||
const { cookies } = getCollections();
|
||||
const bankruptcy = getBankruptcyCollection();
|
||||
const now = getUnixTime();
|
||||
|
||||
const previous = await bankruptcy.findOne({
|
||||
userId,
|
||||
guildId
|
||||
});
|
||||
|
||||
const nextAvailableAt = Number(previous?.nextAvailableAt ?? 0);
|
||||
|
||||
if (nextAvailableAt > now) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "COOLDOWN",
|
||||
nextAvailableAt
|
||||
};
|
||||
}
|
||||
|
||||
let claimLock;
|
||||
|
||||
try {
|
||||
claimLock = await bankruptcy.findOneAndUpdate(
|
||||
{
|
||||
userId,
|
||||
guildId,
|
||||
$or: [
|
||||
{ nextAvailableAt: { $exists: false } },
|
||||
{ nextAvailableAt: { $lte: now } }
|
||||
]
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
nextAvailableAt: now + BANKRUPTCY_COOLDOWN,
|
||||
lastClaimedAt: now
|
||||
},
|
||||
$inc: {
|
||||
claims: 1
|
||||
},
|
||||
$setOnInsert: {
|
||||
userId,
|
||||
guildId
|
||||
}
|
||||
},
|
||||
{
|
||||
upsert: true,
|
||||
returnDocument: "after"
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (error?.code !== 11000) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!claimLock) {
|
||||
const status = await getBankruptcyStatus(userId, guildId);
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
reason: "COOLDOWN",
|
||||
nextAvailableAt: status.nextAvailableAt
|
||||
};
|
||||
}
|
||||
|
||||
let credit;
|
||||
|
||||
try {
|
||||
credit = await cookies.updateOne(
|
||||
{
|
||||
userId,
|
||||
$or: [
|
||||
{ cookies: 0 },
|
||||
{ cookies: { $exists: false } }
|
||||
]
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
cookies: BANKRUPTCY_GRANT
|
||||
},
|
||||
$setOnInsert: {
|
||||
userId
|
||||
}
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (error?.code !== 11000) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
credit = { modifiedCount: 0, upsertedCount: 0 };
|
||||
}
|
||||
|
||||
if (credit.modifiedCount !== 1 && credit.upsertedCount !== 1) {
|
||||
await bankruptcy.updateOne(
|
||||
{ userId, guildId },
|
||||
{
|
||||
$set: {
|
||||
nextAvailableAt,
|
||||
lastClaimedAt: previous?.lastClaimedAt ?? null,
|
||||
claims: Number(previous?.claims ?? 0)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
reason: "BALANCE_NOT_ZERO",
|
||||
balance: await getCookieBalance(userId)
|
||||
};
|
||||
}
|
||||
|
||||
await trackProfileEvent(
|
||||
interaction,
|
||||
userId,
|
||||
{
|
||||
type: "cookie_gain",
|
||||
amount: BANKRUPTCY_GRANT
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
amount: BANKRUPTCY_GRANT,
|
||||
balance: BANKRUPTCY_GRANT,
|
||||
nextAvailableAt: now + BANKRUPTCY_COOLDOWN,
|
||||
claims: Number(claimLock.claims ?? 1)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BANKRUPTCY_GRANT,
|
||||
BANKRUPTCY_COOLDOWN,
|
||||
ensureBankruptcyIndexes,
|
||||
getBankruptcyStatus,
|
||||
declareBankruptcy
|
||||
};
|
||||
@@ -27,30 +27,30 @@ const HEIST_METHODS = {
|
||||
baseChance: 0.65,
|
||||
minStealPercent: 0.01,
|
||||
maxStealPercent: 0.03,
|
||||
maxStealAmount: 1500,
|
||||
maxStealAmount: 5000,
|
||||
minPenaltyPercent: 0.01,
|
||||
maxPenaltyPercent: 0.04,
|
||||
maxPenaltyAmount: 750
|
||||
maxPenaltyAmount: 1500
|
||||
},
|
||||
|
||||
Hack: {
|
||||
baseChance: 0.45,
|
||||
minStealPercent: 0.03,
|
||||
maxStealPercent: 0.06,
|
||||
maxStealAmount: 3500,
|
||||
maxStealAmount: 12500,
|
||||
minPenaltyPercent: 0.03,
|
||||
maxPenaltyPercent: 0.07,
|
||||
maxPenaltyAmount: 2000
|
||||
maxPenaltyAmount: 4000
|
||||
},
|
||||
|
||||
Smash: {
|
||||
baseChance: 0.25,
|
||||
minStealPercent: 0.06,
|
||||
maxStealPercent: 0.12,
|
||||
maxStealAmount: 7500,
|
||||
maxStealAmount: 25000,
|
||||
minPenaltyPercent: 0.06,
|
||||
maxPenaltyPercent: 0.12,
|
||||
maxPenaltyAmount: 5000
|
||||
maxPenaltyAmount: 10000
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ const {
|
||||
const COLLECT_COOLDOWN =
|
||||
60 * 60;
|
||||
|
||||
const COLLECT_MAX = 1000;
|
||||
|
||||
/*
|
||||
* IMPORTANT:
|
||||
@@ -223,7 +224,7 @@ async function collectCookiesFromSaucer(
|
||||
const amount =
|
||||
Math.floor(
|
||||
Math.random() *
|
||||
1000
|
||||
COLLECT_MAX
|
||||
)
|
||||
+
|
||||
1;
|
||||
|
||||
+78
-219
@@ -6,101 +6,56 @@ const {
|
||||
} = require("discord.js");
|
||||
|
||||
const {
|
||||
getCookieBalance,
|
||||
temporaryEphemeralReply
|
||||
getCookieBalance
|
||||
} = require("./cookieEconomy");
|
||||
|
||||
const {
|
||||
createInteractionSession,
|
||||
requireInteractionSession
|
||||
} = require("./interactionSession");
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* MAIN MENU
|
||||
* ============================================================
|
||||
*/
|
||||
async function showCrumbSaucer(interaction) {
|
||||
if (interaction.isButton()) {
|
||||
const session = await requireInteractionSession(interaction);
|
||||
|
||||
async function showCrumbSaucer(
|
||||
interaction
|
||||
) {
|
||||
/*
|
||||
* Button navigation must preserve ownership.
|
||||
*/
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
interaction.isButton()
|
||||
) {
|
||||
const ownerId =
|
||||
getCrumbSaucerSessionOwner(
|
||||
interaction
|
||||
);
|
||||
const balance = await getCookieBalance(interaction.user.id);
|
||||
const embed = buildMainMenuEmbed(interaction.user, balance);
|
||||
|
||||
return interaction.update({
|
||||
embeds: [embed],
|
||||
components: buildMainMenuButtons()
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
ownerId !==
|
||||
interaction.user.id
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
const balance = await getCookieBalance(interaction.user.id);
|
||||
const embed = buildMainMenuEmbed(interaction.user, balance);
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [embed],
|
||||
components: buildMainMenuButtons()
|
||||
});
|
||||
|
||||
const message = await interaction.fetchReply();
|
||||
|
||||
await createInteractionSession(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"This isn't your Crumb Saucer session."
|
||||
messageId: message.id,
|
||||
ownerId: interaction.user.id,
|
||||
kind: "crumbSaucer"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const balance =
|
||||
await getCookieBalance(
|
||||
interaction.user.id
|
||||
);
|
||||
|
||||
|
||||
const embed =
|
||||
buildMainMenuEmbed(
|
||||
interaction.user,
|
||||
balance
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
interaction.isButton()
|
||||
) {
|
||||
return interaction.update({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildMainMenuButtons()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return interaction.reply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildMainMenuButtons()
|
||||
});
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* MAIN MENU EMBED
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function buildMainMenuEmbed(
|
||||
user,
|
||||
balance
|
||||
) {
|
||||
function buildMainMenuEmbed(user, balance) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🎰 The Crumb Saucer"
|
||||
)
|
||||
|
||||
.setTitle("🎰 The Crumb Saucer")
|
||||
.setDescription(
|
||||
[
|
||||
`Welcome, ${user}!`,
|
||||
@@ -121,158 +76,78 @@ function buildMainMenuEmbed(
|
||||
"Bet on Red, Black, Odd, Even, or Zero.",
|
||||
"",
|
||||
"### 🏦 Vault Heist",
|
||||
"Attempt to breach ShaiBot's Cookie Vault."
|
||||
"Attempt to breach ShaiBot's Cookie Vault.",
|
||||
"",
|
||||
"### 🛟 Bankruptcy",
|
||||
"At exactly 0 Cookies, claim a 1,000 Cookie starter kit."
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.addFields({
|
||||
name:
|
||||
"Your Vault",
|
||||
|
||||
value:
|
||||
`**${balance.toLocaleString()} 🍪**`
|
||||
name: "Your Vault",
|
||||
value: `**${balance.toLocaleString()} 🍪**`
|
||||
})
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`crumbSaucer:${user.id}`
|
||||
text: `crumbSaucer:${user.id}`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* MAIN MENU BUTTONS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function buildMainMenuButtons() {
|
||||
return [
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"crumbSaucerSlots"
|
||||
)
|
||||
.setLabel(
|
||||
"Slots"
|
||||
)
|
||||
.setEmoji(
|
||||
"🎰"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Primary
|
||||
),
|
||||
.setCustomId("crumbSaucerSlots")
|
||||
.setLabel("Slots")
|
||||
.setEmoji("🎰")
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"crumbSaucerBlackjack"
|
||||
)
|
||||
.setLabel(
|
||||
"Blackjack"
|
||||
)
|
||||
.setEmoji(
|
||||
"🃏"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Success
|
||||
),
|
||||
.setCustomId("crumbSaucerBlackjack")
|
||||
.setLabel("Blackjack")
|
||||
.setEmoji("🃏")
|
||||
.setStyle(ButtonStyle.Success),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"crumbSaucerRoulette"
|
||||
)
|
||||
.setLabel(
|
||||
"Roulette"
|
||||
)
|
||||
.setEmoji(
|
||||
"🎡"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Primary
|
||||
),
|
||||
.setCustomId("crumbSaucerRoulette")
|
||||
.setLabel("Roulette")
|
||||
.setEmoji("🎡")
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"crumbSaucerHeist"
|
||||
)
|
||||
.setLabel(
|
||||
"Vault Heist"
|
||||
)
|
||||
.setEmoji(
|
||||
"🏦"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Danger
|
||||
)
|
||||
.setCustomId("crumbSaucerHeist")
|
||||
.setLabel("Vault Heist")
|
||||
.setEmoji("🏦")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
),
|
||||
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"crumbSaucerCollectMenu"
|
||||
)
|
||||
.setLabel(
|
||||
"Cookie Bakery"
|
||||
)
|
||||
.setEmoji(
|
||||
"🍪"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Success
|
||||
),
|
||||
|
||||
/*
|
||||
* Do NOT use global "abort" here.
|
||||
*
|
||||
* The dedicated handler validates
|
||||
* Crumb Saucer ownership first.
|
||||
*/
|
||||
.setCustomId("crumbSaucerCollectMenu")
|
||||
.setLabel("Cookie Bakery")
|
||||
.setEmoji("🍪")
|
||||
.setStyle(ButtonStyle.Success),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"crumbSaucerClose"
|
||||
)
|
||||
.setLabel(
|
||||
"Close"
|
||||
)
|
||||
.setEmoji(
|
||||
"✖️"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Secondary
|
||||
)
|
||||
.setCustomId("crumbSaucerBankruptcy")
|
||||
.setLabel("Bankruptcy")
|
||||
.setEmoji("🛟")
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId("sessionClose")
|
||||
.setLabel("Close")
|
||||
.setEmoji("✖️")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
function getCrumbSaucerSessionOwner(interaction) {
|
||||
const footer = interaction.message?.embeds?.[0]?.footer?.text;
|
||||
const parts = footer?.split(":") ?? [];
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* SESSION OWNER
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function getCrumbSaucerSessionOwner(
|
||||
interaction
|
||||
) {
|
||||
const footer =
|
||||
interaction.message
|
||||
?.embeds?.[0]
|
||||
?.footer
|
||||
?.text;
|
||||
|
||||
|
||||
const parts =
|
||||
footer?.split(":")
|
||||
??
|
||||
[];
|
||||
|
||||
|
||||
switch (
|
||||
parts[0]
|
||||
) {
|
||||
switch (parts[0]) {
|
||||
case "crumbSaucer":
|
||||
case "crumbSaucerCollect":
|
||||
case "crumbSaucerSlots":
|
||||
@@ -283,35 +158,19 @@ function getCrumbSaucerSessionOwner(
|
||||
case "crumbRouletteBet":
|
||||
case "crumbRouletteResult":
|
||||
case "cookieHeist":
|
||||
|
||||
return parts[1];
|
||||
|
||||
|
||||
/*
|
||||
* Active Blackjack:
|
||||
*
|
||||
* crumbBlackjack:<gameId>:<userId>
|
||||
*/
|
||||
|
||||
case "crumbBlackjack":
|
||||
|
||||
return parts[2];
|
||||
|
||||
|
||||
default:
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* EXPORTS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
showCrumbSaucer,
|
||||
buildMainMenuEmbed,
|
||||
buildMainMenuButtons,
|
||||
getCrumbSaucerSessionOwner
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
const { EmbedBuilder, MessageFlags } = require("discord.js");
|
||||
const { mClient } = require("../index");
|
||||
|
||||
const DATABASE_NAME = process.env.M_DB;
|
||||
const SESSION_COLLECTION = "items_sessions";
|
||||
const DEFAULT_SESSION_LIFETIME_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
function getSessionCollection() {
|
||||
if (!DATABASE_NAME) {
|
||||
throw new Error("M_DB is not configured in .env");
|
||||
}
|
||||
|
||||
return mClient.db(DATABASE_NAME).collection(SESSION_COLLECTION);
|
||||
}
|
||||
|
||||
async function ensureInteractionSessionIndexes() {
|
||||
const sessions = getSessionCollection();
|
||||
|
||||
await sessions.createIndex(
|
||||
{ messageId: 1 },
|
||||
{ unique: true }
|
||||
);
|
||||
|
||||
await sessions.createIndex(
|
||||
{ expiresAt: 1 },
|
||||
{ expireAfterSeconds: 0 }
|
||||
);
|
||||
}
|
||||
|
||||
async function createInteractionSession(
|
||||
interaction,
|
||||
{
|
||||
messageId = null,
|
||||
ownerId = interaction.user.id,
|
||||
kind = "menu",
|
||||
targetId = null,
|
||||
metadata = {},
|
||||
lifetimeMs = DEFAULT_SESSION_LIFETIME_MS
|
||||
} = {}
|
||||
) {
|
||||
const resolvedMessageId =
|
||||
messageId ?? interaction.message?.id;
|
||||
|
||||
if (!resolvedMessageId) {
|
||||
throw new Error("SESSION_MESSAGE_ID_REQUIRED");
|
||||
}
|
||||
|
||||
const sessions = getSessionCollection();
|
||||
const now = new Date();
|
||||
const expiresAt = new Date(now.getTime() + lifetimeMs);
|
||||
|
||||
await sessions.updateOne(
|
||||
{ messageId: resolvedMessageId },
|
||||
{
|
||||
$set: {
|
||||
messageId: resolvedMessageId,
|
||||
ownerId,
|
||||
guildId: interaction.guildId ?? null,
|
||||
channelId: interaction.channelId ?? null,
|
||||
kind,
|
||||
targetId,
|
||||
metadata,
|
||||
active: true,
|
||||
updatedAt: now,
|
||||
expiresAt
|
||||
},
|
||||
$setOnInsert: {
|
||||
createdAt: now
|
||||
}
|
||||
},
|
||||
{ upsert: true }
|
||||
);
|
||||
|
||||
return sessions.findOne({ messageId: resolvedMessageId });
|
||||
}
|
||||
|
||||
async function registerReplySession(interaction, options = {}) {
|
||||
const message = await interaction.fetchReply();
|
||||
|
||||
return createInteractionSession(
|
||||
interaction,
|
||||
{
|
||||
...options,
|
||||
messageId: message.id
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getInteractionSession(interaction) {
|
||||
const messageId = interaction.message?.id;
|
||||
|
||||
if (!messageId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getSessionCollection().findOne({
|
||||
messageId,
|
||||
active: true,
|
||||
expiresAt: { $gt: new Date() }
|
||||
});
|
||||
}
|
||||
|
||||
function inferLegacyOwnerId(interaction) {
|
||||
const footer = interaction.message?.embeds?.[0]?.footer?.text;
|
||||
const parts = footer?.split(":") ?? [];
|
||||
|
||||
if (!parts.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (parts[0]) {
|
||||
case "profile":
|
||||
case "crumbSaucer":
|
||||
case "crumbSaucerCollect":
|
||||
case "crumbSaucerSlots":
|
||||
case "crumbSaucerSlotsResult":
|
||||
case "crumbSaucerBlackjack":
|
||||
case "crumbBlackjackResult":
|
||||
case "crumbRouletteMenu":
|
||||
case "crumbRouletteBet":
|
||||
case "crumbRouletteResult":
|
||||
case "cookieHeist":
|
||||
return parts[1] ?? null;
|
||||
|
||||
case "crumbBlackjack":
|
||||
return parts[2] ?? null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function replySessionError(
|
||||
interaction,
|
||||
content = "This isn't your session."
|
||||
) {
|
||||
const payload = {
|
||||
content,
|
||||
flags: MessageFlags.Ephemeral
|
||||
};
|
||||
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
return interaction.followUp(payload);
|
||||
}
|
||||
|
||||
return interaction.reply(payload);
|
||||
}
|
||||
|
||||
async function requireInteractionSession(
|
||||
interaction,
|
||||
{
|
||||
kinds = null,
|
||||
allowLegacy = true
|
||||
} = {}
|
||||
) {
|
||||
const session = await getInteractionSession(interaction);
|
||||
|
||||
if (session) {
|
||||
if (session.ownerId !== interaction.user.id) {
|
||||
await replySessionError(interaction);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(kinds) &&
|
||||
kinds.length > 0 &&
|
||||
!kinds.includes(session.kind)
|
||||
) {
|
||||
await replySessionError(
|
||||
interaction,
|
||||
"This control belongs to a different session."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
if (allowLegacy) {
|
||||
const ownerId = inferLegacyOwnerId(interaction);
|
||||
|
||||
if (ownerId) {
|
||||
if (ownerId !== interaction.user.id) {
|
||||
await replySessionError(interaction);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
messageId: interaction.message?.id ?? null,
|
||||
ownerId,
|
||||
guildId: interaction.guildId ?? null,
|
||||
channelId: interaction.channelId ?? null,
|
||||
kind: "legacy",
|
||||
targetId: null,
|
||||
metadata: {},
|
||||
active: true,
|
||||
legacy: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
messageId: interaction.message?.id ?? null,
|
||||
ownerId: interaction.user.id,
|
||||
kind: "unmanaged",
|
||||
metadata: {},
|
||||
active: true,
|
||||
unmanaged: true
|
||||
};
|
||||
}
|
||||
|
||||
async function guardSessionInteraction(interaction) {
|
||||
if (!interaction.isButton()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const session = await getInteractionSession(interaction);
|
||||
|
||||
if (session) {
|
||||
if (session.ownerId === interaction.user.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await replySessionError(interaction);
|
||||
return false;
|
||||
}
|
||||
|
||||
const ownerId = inferLegacyOwnerId(interaction);
|
||||
|
||||
if (!ownerId || ownerId === interaction.user.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await replySessionError(interaction);
|
||||
return false;
|
||||
}
|
||||
|
||||
async function closeInteractionSession(interaction) {
|
||||
const session = await requireInteractionSession(interaction);
|
||||
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.messageId && !session.unmanaged) {
|
||||
await getSessionCollection().updateOne(
|
||||
{ messageId: session.messageId },
|
||||
{
|
||||
$set: {
|
||||
active: false,
|
||||
closedAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Session Closed")
|
||||
.setDescription("Closing menu. . .");
|
||||
|
||||
await interaction.update({
|
||||
embeds: [embed],
|
||||
components: []
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
interaction.deleteReply().catch(() => {});
|
||||
}, 1200);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ensureInteractionSessionIndexes,
|
||||
createInteractionSession,
|
||||
registerReplySession,
|
||||
getInteractionSession,
|
||||
requireInteractionSession,
|
||||
guardSessionInteraction,
|
||||
closeInteractionSession,
|
||||
replySessionError,
|
||||
inferLegacyOwnerId
|
||||
};
|
||||
@@ -1610,9 +1610,7 @@ function buildProfileButtons() {
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"profileClose"
|
||||
)
|
||||
.setCustomId("sessionClose")
|
||||
.setLabel(
|
||||
"Close"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user