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 {
|
const { SlashCommandBuilder } = require("discord.js");
|
||||||
SlashCommandBuilder
|
|
||||||
} = require(
|
|
||||||
"discord.js"
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
buildProfileOverview,
|
buildProfileOverview,
|
||||||
buildProfileButtons
|
buildProfileButtons
|
||||||
} = require(
|
} = require("../../utils/profileSystem");
|
||||||
"../../utils/profileSystem"
|
const {
|
||||||
);
|
createInteractionSession
|
||||||
|
} = require("../../utils/interactionSession");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
data:
|
data: new SlashCommandBuilder()
|
||||||
new SlashCommandBuilder()
|
.setName("profile")
|
||||||
.setName(
|
.setDescription("View a player profile")
|
||||||
"profile"
|
.addUserOption(option =>
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
"View a player profile"
|
|
||||||
)
|
|
||||||
.addUserOption(
|
|
||||||
option =>
|
|
||||||
option
|
option
|
||||||
.setName(
|
.setName("user")
|
||||||
"user"
|
.setDescription("Player to view")
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
"Player to view"
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
|
||||||
async execute(
|
async execute(interaction) {
|
||||||
interaction
|
const user = interaction.options.getUser("user") ?? interaction.user;
|
||||||
) {
|
|
||||||
const user =
|
|
||||||
interaction.options.getUser(
|
|
||||||
"user"
|
|
||||||
) ??
|
|
||||||
interaction.user;
|
|
||||||
|
|
||||||
const sessionOwnerId =
|
const embed = await buildProfileOverview(
|
||||||
interaction.user.id;
|
|
||||||
|
|
||||||
const embed =
|
|
||||||
await buildProfileOverview(
|
|
||||||
interaction.guildId,
|
interaction.guildId,
|
||||||
user,
|
user,
|
||||||
sessionOwnerId
|
interaction.user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
return interaction.reply({
|
await interaction.reply({
|
||||||
embeds: [embed],
|
embeds: [embed],
|
||||||
components:
|
components: buildProfileButtons()
|
||||||
buildProfileButtons(
|
|
||||||
"overview",
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const message = await interaction.fetchReply();
|
||||||
|
|
||||||
|
await createInteractionSession(
|
||||||
|
interaction,
|
||||||
|
{
|
||||||
|
messageId: message.id,
|
||||||
|
ownerId: interaction.user.id,
|
||||||
|
kind: "profile",
|
||||||
|
targetId: user.id
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+53
-149
@@ -3,8 +3,11 @@ const {
|
|||||||
MessageFlags
|
MessageFlags
|
||||||
} = require("discord.js");
|
} = require("discord.js");
|
||||||
|
|
||||||
require("dotenv").config();
|
const {
|
||||||
|
guardSessionInteraction
|
||||||
|
} = require("../utils/interactionSession");
|
||||||
|
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: Events.InteractionCreate,
|
name: Events.InteractionCreate,
|
||||||
@@ -36,203 +39,110 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
async function autocompleteHandler(interaction) {
|
async function autocompleteHandler(interaction) {
|
||||||
const command =
|
const command = interaction.client.commands.get(
|
||||||
interaction.client.commands.get(
|
|
||||||
interaction.commandName
|
interaction.commandName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (!command) {
|
if (!command) {
|
||||||
return console.error(
|
return console.error(
|
||||||
`No command matching ${interaction.commandName} was found.`
|
`No command matching ${interaction.commandName} was found.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof command.autocomplete !== "function") {
|
||||||
if (
|
|
||||||
typeof command.autocomplete !==
|
|
||||||
"function"
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await command.autocomplete(
|
await command.autocomplete(interaction);
|
||||||
interaction
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`Autocomplete error for /${interaction.commandName}:`,
|
`Autocomplete error for /${interaction.commandName}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Autocomplete cannot use normal interaction.reply().
|
|
||||||
* If something failed, send an empty set so Discord
|
|
||||||
* stops waiting.
|
|
||||||
*/
|
|
||||||
try {
|
try {
|
||||||
await interaction.respond([]);
|
await interaction.respond([]);
|
||||||
} catch {
|
} catch {}
|
||||||
// Interaction may already be acknowledged or expired.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function commandHandler(interaction) {
|
async function commandHandler(interaction) {
|
||||||
const command =
|
const command = interaction.client.commands.get(
|
||||||
interaction.client.commands.get(
|
|
||||||
interaction.commandName
|
interaction.commandName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (!command) {
|
if (!command) {
|
||||||
return console.error(
|
return console.error(
|
||||||
`No command matching ${interaction.commandName} was found.`
|
`No command matching ${interaction.commandName} was found.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await command.execute(
|
await command.execute(interaction);
|
||||||
interaction
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
await sendInteractionError(
|
||||||
if (
|
interaction,
|
||||||
interaction.replied ||
|
"There was an error while executing this command!"
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function buttonHandler(interaction) {
|
async function buttonHandler(interaction) {
|
||||||
const button =
|
const allowed = await guardSessionInteraction(interaction);
|
||||||
interaction.client.buttons.get(
|
|
||||||
|
if (!allowed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = interaction.client.buttons.get(
|
||||||
interaction.customId
|
interaction.customId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (!button) {
|
if (!button) {
|
||||||
return console.error(
|
return console.error(
|
||||||
`No button registered matching ${interaction.customId}.`
|
`No button registered matching ${interaction.customId}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await button.execute(
|
await button.execute(interaction);
|
||||||
interaction
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
await sendInteractionError(
|
||||||
if (
|
interaction,
|
||||||
interaction.replied ||
|
"There was an error while executing this button!"
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function selectMenuHandler(interaction) {
|
async function selectMenuHandler(interaction) {
|
||||||
const selectMenu =
|
const selectMenu = interaction.client.selectMenus.get(
|
||||||
interaction.client.selectMenus.get(
|
|
||||||
interaction.customId
|
interaction.customId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (!selectMenu) {
|
if (!selectMenu) {
|
||||||
return console.error(
|
return console.error(
|
||||||
`No Select Menu registered matching ${interaction.customId}.`
|
`No Select Menu registered matching ${interaction.customId}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await selectMenu.execute(
|
await selectMenu.execute(interaction);
|
||||||
interaction
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
await sendInteractionError(
|
||||||
if (
|
interaction,
|
||||||
interaction.replied ||
|
"There was an error while executing this select menu!"
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function modalHandler(interaction) {
|
async function modalHandler(interaction) {
|
||||||
const customId =
|
const customId = interaction.customId.split(":")[0];
|
||||||
interaction.customId
|
const modal = interaction.client.modals.get(customId);
|
||||||
.split(":")[0];
|
|
||||||
|
|
||||||
|
|
||||||
const modal =
|
|
||||||
interaction.client
|
|
||||||
.modals
|
|
||||||
.get(
|
|
||||||
customId
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
if (!modal) {
|
if (!modal) {
|
||||||
return console.error(
|
return console.error(
|
||||||
@@ -240,36 +150,30 @@ async function modalHandler(interaction) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await modal.execute(
|
await modal.execute(interaction);
|
||||||
interaction
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
await sendInteractionError(
|
||||||
|
interaction,
|
||||||
|
"There was an error while executing this modal!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendInteractionError(interaction, content) {
|
||||||
|
const payload = {
|
||||||
|
content,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
};
|
||||||
|
|
||||||
if (
|
try {
|
||||||
interaction.replied ||
|
if (interaction.replied || interaction.deferred) {
|
||||||
interaction.deferred
|
await interaction.followUp(payload);
|
||||||
) {
|
|
||||||
await interaction.followUp({
|
|
||||||
content:
|
|
||||||
"There was an error while executing this modal!",
|
|
||||||
|
|
||||||
flags:
|
|
||||||
MessageFlags.Ephemeral
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
await interaction.reply({
|
await interaction.reply(payload);
|
||||||
content:
|
}
|
||||||
"There was an error while executing this modal!",
|
} catch (error) {
|
||||||
|
console.error("Failed to send interaction error response:", error);
|
||||||
flags:
|
|
||||||
MessageFlags.Ephemeral
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,34 +5,18 @@ const {
|
|||||||
Partials
|
Partials
|
||||||
} = require("discord.js");
|
} = require("discord.js");
|
||||||
|
|
||||||
const {
|
const { MongoClient } = require("mongodb");
|
||||||
MongoClient
|
|
||||||
} = require("mongodb");
|
|
||||||
|
|
||||||
const fs = require("node:fs");
|
const fs = require("node:fs");
|
||||||
|
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
|
||||||
|
const mClient = new MongoClient(
|
||||||
/*
|
|
||||||
* MONGODB
|
|
||||||
*/
|
|
||||||
|
|
||||||
const mClient =
|
|
||||||
new MongoClient(
|
|
||||||
process.env.M_URI
|
process.env.M_URI
|
||||||
);
|
);
|
||||||
|
|
||||||
exports.mClient =
|
exports.mClient = mClient;
|
||||||
mClient;
|
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
/*
|
|
||||||
* DISCORD CLIENT
|
|
||||||
*/
|
|
||||||
|
|
||||||
const client =
|
|
||||||
new Client({
|
|
||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
GatewayIntentBits.GuildMembers,
|
GatewayIntentBits.GuildMembers,
|
||||||
@@ -43,7 +27,6 @@ const client =
|
|||||||
GatewayIntentBits.DirectMessages,
|
GatewayIntentBits.DirectMessages,
|
||||||
GatewayIntentBits.MessageContent
|
GatewayIntentBits.MessageContent
|
||||||
],
|
],
|
||||||
|
|
||||||
partials: [
|
partials: [
|
||||||
Partials.Channel,
|
Partials.Channel,
|
||||||
Partials.Message,
|
Partials.Message,
|
||||||
@@ -53,108 +36,51 @@ const client =
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.client =
|
exports.client = client;
|
||||||
client;
|
|
||||||
|
|
||||||
|
client.commands = new Collection();
|
||||||
/*
|
client.legacyCommands = new Collection();
|
||||||
* COLLECTIONS
|
client.aliases = new Collection();
|
||||||
*/
|
client.buttons = new Collection();
|
||||||
|
client.selectMenus = new Collection();
|
||||||
client.commands =
|
client.modals = new Collection();
|
||||||
new Collection();
|
|
||||||
|
|
||||||
client.legacyCommands =
|
|
||||||
new Collection();
|
|
||||||
|
|
||||||
client.aliases =
|
|
||||||
new Collection();
|
|
||||||
|
|
||||||
client.buttons =
|
|
||||||
new Collection();
|
|
||||||
|
|
||||||
client.selectMenus =
|
|
||||||
new Collection();
|
|
||||||
|
|
||||||
client.modals =
|
|
||||||
new Collection();
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* START APPLICATION
|
|
||||||
*/
|
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
try {
|
try {
|
||||||
/*
|
|
||||||
* Connect Mongo first.
|
|
||||||
*/
|
|
||||||
|
|
||||||
await mClient.connect();
|
await mClient.connect();
|
||||||
|
console.log("MongoDB connected");
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ensureCookieIndexes
|
ensureCookieIndexes
|
||||||
} = require(
|
} = require("./utils/cookieEconomy");
|
||||||
"./utils/cookieEconomy"
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ensureProfileSystem
|
ensureProfileSystem
|
||||||
} = require(
|
} = require("./utils/profileSystem");
|
||||||
"./utils/profileSystem"
|
const {
|
||||||
);
|
ensureInteractionSessionIndexes
|
||||||
|
} = require("./utils/interactionSession");
|
||||||
|
const {
|
||||||
|
ensureBankruptcyIndexes
|
||||||
|
} = require("./utils/bankruptcy");
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
ensureCookieIndexes(),
|
ensureCookieIndexes(),
|
||||||
ensureProfileSystem()
|
ensureProfileSystem(),
|
||||||
|
ensureInteractionSessionIndexes(),
|
||||||
|
ensureBankruptcyIndexes()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log(
|
console.log("Database indexes initialized");
|
||||||
"Database indexes initialized"
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const handlerFiles = fs.readdirSync("./handlers");
|
||||||
|
|
||||||
/*
|
for (const handler of handlerFiles) {
|
||||||
* Load handlers.
|
require(`./handlers/${handler}`)(client);
|
||||||
*/
|
|
||||||
|
|
||||||
const handlerFiles =
|
|
||||||
fs.readdirSync(
|
|
||||||
"./handlers"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (
|
|
||||||
const handler
|
|
||||||
of handlerFiles
|
|
||||||
) {
|
|
||||||
require(
|
|
||||||
`./handlers/${handler}`
|
|
||||||
)(client);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Login Discord.
|
|
||||||
*/
|
|
||||||
|
|
||||||
await client.login(
|
await client.login(
|
||||||
process.env.D_TOKEN
|
process.env.D_TOKEN
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"Failed to start ShaiBot:",
|
"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,
|
baseChance: 0.65,
|
||||||
minStealPercent: 0.01,
|
minStealPercent: 0.01,
|
||||||
maxStealPercent: 0.03,
|
maxStealPercent: 0.03,
|
||||||
maxStealAmount: 1500,
|
maxStealAmount: 5000,
|
||||||
minPenaltyPercent: 0.01,
|
minPenaltyPercent: 0.01,
|
||||||
maxPenaltyPercent: 0.04,
|
maxPenaltyPercent: 0.04,
|
||||||
maxPenaltyAmount: 750
|
maxPenaltyAmount: 1500
|
||||||
},
|
},
|
||||||
|
|
||||||
Hack: {
|
Hack: {
|
||||||
baseChance: 0.45,
|
baseChance: 0.45,
|
||||||
minStealPercent: 0.03,
|
minStealPercent: 0.03,
|
||||||
maxStealPercent: 0.06,
|
maxStealPercent: 0.06,
|
||||||
maxStealAmount: 3500,
|
maxStealAmount: 12500,
|
||||||
minPenaltyPercent: 0.03,
|
minPenaltyPercent: 0.03,
|
||||||
maxPenaltyPercent: 0.07,
|
maxPenaltyPercent: 0.07,
|
||||||
maxPenaltyAmount: 2000
|
maxPenaltyAmount: 4000
|
||||||
},
|
},
|
||||||
|
|
||||||
Smash: {
|
Smash: {
|
||||||
baseChance: 0.25,
|
baseChance: 0.25,
|
||||||
minStealPercent: 0.06,
|
minStealPercent: 0.06,
|
||||||
maxStealPercent: 0.12,
|
maxStealPercent: 0.12,
|
||||||
maxStealAmount: 7500,
|
maxStealAmount: 25000,
|
||||||
minPenaltyPercent: 0.06,
|
minPenaltyPercent: 0.06,
|
||||||
maxPenaltyPercent: 0.12,
|
maxPenaltyPercent: 0.12,
|
||||||
maxPenaltyAmount: 5000
|
maxPenaltyAmount: 10000
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const {
|
|||||||
const COLLECT_COOLDOWN =
|
const COLLECT_COOLDOWN =
|
||||||
60 * 60;
|
60 * 60;
|
||||||
|
|
||||||
|
const COLLECT_MAX = 1000;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IMPORTANT:
|
* IMPORTANT:
|
||||||
@@ -223,7 +224,7 @@ async function collectCookiesFromSaucer(
|
|||||||
const amount =
|
const amount =
|
||||||
Math.floor(
|
Math.floor(
|
||||||
Math.random() *
|
Math.random() *
|
||||||
1000
|
COLLECT_MAX
|
||||||
)
|
)
|
||||||
+
|
+
|
||||||
1;
|
1;
|
||||||
|
|||||||
+79
-220
@@ -6,101 +6,56 @@ const {
|
|||||||
} = require("discord.js");
|
} = require("discord.js");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getCookieBalance,
|
getCookieBalance
|
||||||
temporaryEphemeralReply
|
|
||||||
} = require("./cookieEconomy");
|
} = require("./cookieEconomy");
|
||||||
|
|
||||||
|
const {
|
||||||
|
createInteractionSession,
|
||||||
|
requireInteractionSession
|
||||||
|
} = require("./interactionSession");
|
||||||
|
|
||||||
/*
|
async function showCrumbSaucer(interaction) {
|
||||||
* ============================================================
|
if (interaction.isButton()) {
|
||||||
* MAIN MENU
|
const session = await requireInteractionSession(interaction);
|
||||||
* ============================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
async function showCrumbSaucer(
|
if (!session) {
|
||||||
interaction
|
return;
|
||||||
) {
|
}
|
||||||
/*
|
|
||||||
* Button navigation must preserve ownership.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (
|
const balance = await getCookieBalance(interaction.user.id);
|
||||||
interaction.isButton()
|
const embed = buildMainMenuEmbed(interaction.user, balance);
|
||||||
) {
|
|
||||||
const ownerId =
|
|
||||||
getCrumbSaucerSessionOwner(
|
|
||||||
interaction
|
|
||||||
);
|
|
||||||
|
|
||||||
|
return interaction.update({
|
||||||
|
embeds: [embed],
|
||||||
|
components: buildMainMenuButtons()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
const balance = await getCookieBalance(interaction.user.id);
|
||||||
ownerId !==
|
const embed = buildMainMenuEmbed(interaction.user, balance);
|
||||||
interaction.user.id
|
|
||||||
) {
|
await interaction.reply({
|
||||||
return temporaryEphemeralReply(
|
embeds: [embed],
|
||||||
|
components: buildMainMenuButtons()
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = await interaction.fetchReply();
|
||||||
|
|
||||||
|
await createInteractionSession(
|
||||||
interaction,
|
interaction,
|
||||||
{
|
{
|
||||||
content:
|
messageId: message.id,
|
||||||
"This isn't your Crumb Saucer session."
|
ownerId: interaction.user.id,
|
||||||
|
kind: "crumbSaucer"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildMainMenuEmbed(user, balance) {
|
||||||
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()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ============================================================
|
|
||||||
* MAIN MENU EMBED
|
|
||||||
* ============================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
function buildMainMenuEmbed(
|
|
||||||
user,
|
|
||||||
balance
|
|
||||||
) {
|
|
||||||
return new EmbedBuilder()
|
return new EmbedBuilder()
|
||||||
.setTitle(
|
.setTitle("🎰 The Crumb Saucer")
|
||||||
"🎰 The Crumb Saucer"
|
|
||||||
)
|
|
||||||
|
|
||||||
.setDescription(
|
.setDescription(
|
||||||
[
|
[
|
||||||
`Welcome, ${user}!`,
|
`Welcome, ${user}!`,
|
||||||
@@ -121,158 +76,78 @@ function buildMainMenuEmbed(
|
|||||||
"Bet on Red, Black, Odd, Even, or Zero.",
|
"Bet on Red, Black, Odd, Even, or Zero.",
|
||||||
"",
|
"",
|
||||||
"### 🏦 Vault Heist",
|
"### 🏦 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")
|
].join("\n")
|
||||||
)
|
)
|
||||||
|
|
||||||
.addFields({
|
.addFields({
|
||||||
name:
|
name: "Your Vault",
|
||||||
"Your Vault",
|
value: `**${balance.toLocaleString()} 🍪**`
|
||||||
|
|
||||||
value:
|
|
||||||
`**${balance.toLocaleString()} 🍪**`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.setFooter({
|
.setFooter({
|
||||||
text:
|
text: `crumbSaucer:${user.id}`
|
||||||
`crumbSaucer:${user.id}`
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ============================================================
|
|
||||||
* MAIN MENU BUTTONS
|
|
||||||
* ============================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
function buildMainMenuButtons() {
|
function buildMainMenuButtons() {
|
||||||
return [
|
return [
|
||||||
new ActionRowBuilder()
|
new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(
|
.setCustomId("crumbSaucerSlots")
|
||||||
"crumbSaucerSlots"
|
.setLabel("Slots")
|
||||||
)
|
.setEmoji("🎰")
|
||||||
.setLabel(
|
.setStyle(ButtonStyle.Primary),
|
||||||
"Slots"
|
|
||||||
)
|
|
||||||
.setEmoji(
|
|
||||||
"🎰"
|
|
||||||
)
|
|
||||||
.setStyle(
|
|
||||||
ButtonStyle.Primary
|
|
||||||
),
|
|
||||||
|
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(
|
.setCustomId("crumbSaucerBlackjack")
|
||||||
"crumbSaucerBlackjack"
|
.setLabel("Blackjack")
|
||||||
)
|
.setEmoji("🃏")
|
||||||
.setLabel(
|
.setStyle(ButtonStyle.Success),
|
||||||
"Blackjack"
|
|
||||||
)
|
|
||||||
.setEmoji(
|
|
||||||
"🃏"
|
|
||||||
)
|
|
||||||
.setStyle(
|
|
||||||
ButtonStyle.Success
|
|
||||||
),
|
|
||||||
|
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(
|
.setCustomId("crumbSaucerRoulette")
|
||||||
"crumbSaucerRoulette"
|
.setLabel("Roulette")
|
||||||
)
|
.setEmoji("🎡")
|
||||||
.setLabel(
|
.setStyle(ButtonStyle.Primary),
|
||||||
"Roulette"
|
|
||||||
)
|
|
||||||
.setEmoji(
|
|
||||||
"🎡"
|
|
||||||
)
|
|
||||||
.setStyle(
|
|
||||||
ButtonStyle.Primary
|
|
||||||
),
|
|
||||||
|
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(
|
.setCustomId("crumbSaucerHeist")
|
||||||
"crumbSaucerHeist"
|
.setLabel("Vault Heist")
|
||||||
)
|
.setEmoji("🏦")
|
||||||
.setLabel(
|
.setStyle(ButtonStyle.Danger)
|
||||||
"Vault Heist"
|
|
||||||
)
|
|
||||||
.setEmoji(
|
|
||||||
"🏦"
|
|
||||||
)
|
|
||||||
.setStyle(
|
|
||||||
ButtonStyle.Danger
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
|
||||||
new ActionRowBuilder()
|
new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(
|
.setCustomId("crumbSaucerCollectMenu")
|
||||||
"crumbSaucerCollectMenu"
|
.setLabel("Cookie Bakery")
|
||||||
)
|
.setEmoji("🍪")
|
||||||
.setLabel(
|
.setStyle(ButtonStyle.Success),
|
||||||
"Cookie Bakery"
|
|
||||||
)
|
|
||||||
.setEmoji(
|
|
||||||
"🍪"
|
|
||||||
)
|
|
||||||
.setStyle(
|
|
||||||
ButtonStyle.Success
|
|
||||||
),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Do NOT use global "abort" here.
|
|
||||||
*
|
|
||||||
* The dedicated handler validates
|
|
||||||
* Crumb Saucer ownership first.
|
|
||||||
*/
|
|
||||||
|
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(
|
.setCustomId("crumbSaucerBankruptcy")
|
||||||
"crumbSaucerClose"
|
.setLabel("Bankruptcy")
|
||||||
)
|
.setEmoji("🛟")
|
||||||
.setLabel(
|
.setStyle(ButtonStyle.Secondary),
|
||||||
"Close"
|
|
||||||
)
|
new ButtonBuilder()
|
||||||
.setEmoji(
|
.setCustomId("sessionClose")
|
||||||
"✖️"
|
.setLabel("Close")
|
||||||
)
|
.setEmoji("✖️")
|
||||||
.setStyle(
|
.setStyle(ButtonStyle.Secondary)
|
||||||
ButtonStyle.Secondary
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCrumbSaucerSessionOwner(interaction) {
|
||||||
|
const footer = interaction.message?.embeds?.[0]?.footer?.text;
|
||||||
|
const parts = footer?.split(":") ?? [];
|
||||||
|
|
||||||
/*
|
switch (parts[0]) {
|
||||||
* ============================================================
|
|
||||||
* SESSION OWNER
|
|
||||||
* ============================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
function getCrumbSaucerSessionOwner(
|
|
||||||
interaction
|
|
||||||
) {
|
|
||||||
const footer =
|
|
||||||
interaction.message
|
|
||||||
?.embeds?.[0]
|
|
||||||
?.footer
|
|
||||||
?.text;
|
|
||||||
|
|
||||||
|
|
||||||
const parts =
|
|
||||||
footer?.split(":")
|
|
||||||
??
|
|
||||||
[];
|
|
||||||
|
|
||||||
|
|
||||||
switch (
|
|
||||||
parts[0]
|
|
||||||
) {
|
|
||||||
case "crumbSaucer":
|
case "crumbSaucer":
|
||||||
case "crumbSaucerCollect":
|
case "crumbSaucerCollect":
|
||||||
case "crumbSaucerSlots":
|
case "crumbSaucerSlots":
|
||||||
@@ -283,35 +158,19 @@ function getCrumbSaucerSessionOwner(
|
|||||||
case "crumbRouletteBet":
|
case "crumbRouletteBet":
|
||||||
case "crumbRouletteResult":
|
case "crumbRouletteResult":
|
||||||
case "cookieHeist":
|
case "cookieHeist":
|
||||||
|
|
||||||
return parts[1];
|
return parts[1];
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Active Blackjack:
|
|
||||||
*
|
|
||||||
* crumbBlackjack:<gameId>:<userId>
|
|
||||||
*/
|
|
||||||
|
|
||||||
case "crumbBlackjack":
|
case "crumbBlackjack":
|
||||||
|
|
||||||
return parts[2];
|
return parts[2];
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ============================================================
|
|
||||||
* EXPORTS
|
|
||||||
* ============================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
showCrumbSaucer,
|
showCrumbSaucer,
|
||||||
|
buildMainMenuEmbed,
|
||||||
|
buildMainMenuButtons,
|
||||||
getCrumbSaucerSessionOwner
|
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()
|
new ButtonBuilder()
|
||||||
.setCustomId(
|
.setCustomId("sessionClose")
|
||||||
"profileClose"
|
|
||||||
)
|
|
||||||
.setLabel(
|
.setLabel(
|
||||||
"Close"
|
"Close"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user