snapshot_poker_phase3

This commit is contained in:
DeSqBlocki
2026-09-08 19:37:02 +02:00
parent 995a2317b9
commit 903a2f3081
25 changed files with 7457 additions and 60 deletions
+1
View File
@@ -2,3 +2,4 @@ node_modules
.env .env
backup/ backup/
*.bak *.bak
tests/
+27
View File
@@ -0,0 +1,27 @@
const {
buildPokerHubEmbed,
buildPokerHubComponents,
deferPokerReply
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"crumbSaucerPoker",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
return interaction.editReply({
embeds: [
buildPokerHubEmbed()
],
components:
buildPokerHubComponents()
});
}
};
+17
View File
@@ -0,0 +1,17 @@
const {
executePokerAction
} = require("../../utils/pokerInteractions");
module.exports = {
name: "pokerAllIn",
async execute(interaction) {
return executePokerAction(
interaction,
{
type: "all_in"
},
"✅ All-In."
);
}
};
+47
View File
@@ -0,0 +1,47 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
buildJoinPicker
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerBrowse",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
try {
const tables =
await getPokerService()
.listJoinableTables({
guildId:
interaction.guildId,
channelId:
interaction.channelId
});
return interaction.editReply(
buildJoinPicker(
tables
)
);
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+17
View File
@@ -0,0 +1,17 @@
const {
executePokerAction
} = require("../../utils/pokerInteractions");
module.exports = {
name: "pokerCall",
async execute(interaction) {
return executePokerAction(
interaction,
{
type: "call"
},
"✅ Called."
);
}
};
+77
View File
@@ -0,0 +1,77 @@
const {
cardToString
} = require("../../utils/pokerEngine");
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
POKER_CARD_RESPONSE_LIFETIME,
deferPokerReply,
editPokerError,
getPokerGameIdFromInteraction
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerCards",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
const gameId =
getPokerGameIdFromInteraction(
interaction
);
if (!gameId) {
return interaction.editReply({
content:
"❌ This Poker table is no longer active.",
embeds: [],
components: []
});
}
try {
const cards =
await getPokerService()
.viewCards({
gameId,
userId:
interaction.user.id
});
await interaction.editReply({
content:
`🃏 Your cards: **${cards.map(cardToString).join(" ")}**`,
embeds: [],
components: []
});
setTimeout(
() => {
interaction
.deleteReply()
.catch(() => {});
},
POKER_CARD_RESPONSE_LIFETIME
);
return;
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+67
View File
@@ -0,0 +1,67 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
getPokerGameIdFromInteraction,
refreshPokerTableMessage
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerCashOut",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
const gameId =
getPokerGameIdFromInteraction(
interaction
);
if (!gameId) {
return interaction.editReply({
content:
"❌ This Poker table is no longer active.",
embeds: [],
components: []
});
}
try {
const result =
await getPokerService()
.cashOut({
gameId,
userId:
interaction.user.id
});
await refreshPokerTableMessage(
interaction.client,
result.game
);
return interaction.editReply({
content:
`✅ Cashed out **${result.amount.toLocaleString()} Cookies** back to your vault.`,
embeds: [],
components: []
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+17
View File
@@ -0,0 +1,17 @@
const {
executePokerAction
} = require("../../utils/pokerInteractions");
module.exports = {
name: "pokerCheck",
async execute(interaction) {
return executePokerAction(
interaction,
{
type: "check"
},
"✅ Checked."
);
}
};
+81
View File
@@ -0,0 +1,81 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
publishPokerTable,
buildOpenTableLink
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerCreate",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
const service =
getPokerService();
let game = null;
try {
game =
await service.createTable({
guildId:
interaction.guildId,
channelId:
interaction.channelId,
ownerId:
interaction.user.id
});
const published =
await publishPokerTable(
interaction,
game
);
return interaction.editReply({
content:
`✅ Poker table created. **${published.game.data.buyIn.toLocaleString()} Cookies** were moved into your Poker stack.`,
embeds: [],
components:
buildOpenTableLink(
published.game
)
});
}
catch (error) {
if (game) {
try {
await service.cashOut({
gameId:
game.gameId,
userId:
interaction.user.id
});
}
catch (refundError) {
console.error(
`[Poker] Failed to refund unpublished table ${game.gameId}:`,
refundError
);
}
}
return editPokerError(
interaction,
error
);
}
}
};
+17
View File
@@ -0,0 +1,17 @@
const {
executePokerAction
} = require("../../utils/pokerInteractions");
module.exports = {
name: "pokerFold",
async execute(interaction) {
return executePokerAction(
interaction,
{
type: "fold"
},
"✅ Folded."
);
}
};
+73
View File
@@ -0,0 +1,73 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
getPokerGameIdFromInteraction,
refreshPokerTableMessage,
buildOpenTableLink
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerJoinTable",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
const gameId =
getPokerGameIdFromInteraction(
interaction
);
if (!gameId) {
return interaction.editReply({
content:
"❌ This Poker table is no longer active.",
embeds: [],
components: []
});
}
try {
const game =
await getPokerService()
.joinTable({
gameId,
guildId:
interaction.guildId,
userId:
interaction.user.id
});
await refreshPokerTableMessage(
interaction.client,
game
);
return interaction.editReply({
content:
`✅ Joined the Poker table for **${game.data.buyIn.toLocaleString()} Cookies**.`,
embeds: [],
components:
buildOpenTableLink(
game
)
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+66
View File
@@ -0,0 +1,66 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
refreshPokerTableMessage,
buildOpenTableLink
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerMyTable",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
try {
const game =
await getPokerService()
.getMyTable({
guildId:
interaction.guildId,
userId:
interaction.user.id
});
if (!game) {
return interaction.editReply({
content:
"❌ You are not currently seated at a Poker table.",
embeds: [],
components: []
});
}
await refreshPokerTableMessage(
interaction.client,
game
);
return interaction.editReply({
content:
"♠️ Your active Poker table:",
embeds: [],
components:
buildOpenTableLink(
game
)
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+13
View File
@@ -0,0 +1,13 @@
const {
executeMinimumRaise
} = require("../../utils/pokerInteractions");
module.exports = {
name: "pokerRaise",
async execute(interaction) {
return executeMinimumRaise(
interaction
);
}
};
+65
View File
@@ -0,0 +1,65 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
getPokerGameIdFromInteraction,
refreshPokerTableMessage
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerRefresh",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
const gameId =
getPokerGameIdFromInteraction(
interaction
);
if (!gameId) {
return interaction.editReply({
content:
"❌ This Poker table is no longer active.",
embeds: [],
components: []
});
}
try {
const game =
await getPokerService()
.getTable({
gameId
});
await refreshPokerTableMessage(
interaction.client,
game
);
return interaction.editReply({
content:
"✅ Poker table refreshed.",
embeds: [],
components: []
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+67
View File
@@ -0,0 +1,67 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
getPokerGameIdFromInteraction,
refreshPokerTableMessage
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerStart",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
const gameId =
getPokerGameIdFromInteraction(
interaction
);
if (!gameId) {
return interaction.editReply({
content:
"❌ This Poker table is no longer active.",
embeds: [],
components: []
});
}
try {
const game =
await getPokerService()
.startTableHand({
gameId,
userId:
interaction.user.id
});
await refreshPokerTableMessage(
interaction.client,
game
);
return interaction.editReply({
content:
`✅ Hand **#${game.data.handNumber}** started.`,
embeds: [],
components: []
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+6 -2
View File
@@ -28,7 +28,8 @@ module.exports = {
if ( if (
interaction.isMentionableSelectMenu() || interaction.isMentionableSelectMenu() ||
interaction.isUserSelectMenu() interaction.isUserSelectMenu() ||
interaction.isStringSelectMenu()
) { ) {
return selectMenuHandler(interaction); return selectMenuHandler(interaction);
} }
@@ -174,6 +175,9 @@ async function sendInteractionError(interaction, content) {
await interaction.reply(payload); await interaction.reply(payload);
} }
} catch (error) { } catch (error) {
console.error("Failed to send interaction error response:", error); console.error(
"Failed to send interaction error response:",
error
);
} }
} }
+225
View File
@@ -0,0 +1,225 @@
const {
getPokerService
} = require("../utils/crumbPoker");
const {
refreshPokerTableMessage
} = require("../utils/pokerDiscord");
const TURN_CHECK_INTERVAL =
5 * 1000;
const LOBBY_CHECK_INTERVAL =
30 * 1000;
const STARTUP_RECOVERY_DELAY =
10 * 1000;
module.exports = client => {
setTimeout(
() => {
recoverPoker(
client
);
},
STARTUP_RECOVERY_DELAY
);
setInterval(
() => {
processPokerTurns(
client
);
},
TURN_CHECK_INTERVAL
);
setInterval(
() => {
processPokerLobbies(
client
);
},
LOBBY_CHECK_INTERVAL
);
console.log(
"[Poker] Recovery and timeout watcher initialized"
);
};
async function refreshResultGame(
client,
result
) {
const game =
result?.game;
if (!game) {
return;
}
await refreshPokerTableMessage(
client,
game
);
}
async function processPokerTurns(
client
) {
if (!client.isReady()) {
return;
}
try {
const results =
await getPokerService()
.processExpiredTurns();
for (const result of results) {
if (result.processed) {
console.log(
`[Poker] Auto-folded ${result.userId} after turn timeout.`
);
await refreshResultGame(
client,
result
);
}
}
}
catch (error) {
console.error(
"[Poker] Turn watcher error:",
error
);
}
}
async function processPokerLobbies(
client
) {
if (!client.isReady()) {
return;
}
try {
const results =
await getPokerService()
.processExpiredLobbies();
for (const result of results) {
if (result.expired) {
console.log(
`[Poker] Expired waiting Poker table ${result.game?.gameId ?? "unknown"}.`
);
await refreshResultGame(
client,
result
);
}
}
}
catch (error) {
console.error(
"[Poker] Lobby watcher error:",
error
);
}
}
async function recoverPoker(
client
) {
if (!client.isReady()) {
return;
}
try {
const service =
getPokerService();
const result =
await service.recover();
for (
const turn
of result.turns ?? []
) {
if (turn.processed) {
await refreshResultGame(
client,
turn
);
}
}
for (
const lobby
of result.lobbies ?? []
) {
if (lobby.expired) {
await refreshResultGame(
client,
lobby
);
}
}
for (
const closing
of result.closingTables ?? []
) {
await refreshResultGame(
client,
closing
);
}
for (
const cashout
of result.cashouts ?? []
) {
try {
const game =
await service.getTable({
gameId:
cashout.gameId
});
await refreshPokerTableMessage(
client,
game
);
}
catch {}
}
console.log(
`[Poker] Recovery complete: ` +
`${result.memberships.length} membership(s), ` +
`${result.cashouts.length} cash-out(s), ` +
`${result.closingTables.length} closing table(s), ` +
`${result.turns.length} expired turn(s), ` +
`${result.lobbies.length} expired lobby/lobbies.`
);
}
catch (error) {
console.error(
"[Poker] Startup recovery error:",
error
);
}
}
+2 -1
View File
@@ -4,7 +4,8 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "node --test",
"test:poker": "node --test tests/pokerEngine.test.js tests/crumbPoker.test.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
+70
View File
@@ -0,0 +1,70 @@
const {
getPokerService
} = require("../../utils/crumbPoker");
const {
deferPokerReply,
editPokerError,
refreshPokerTableMessage,
buildOpenTableLink
} = require("../../utils/pokerDiscord");
module.exports = {
name:
"pokerJoinSelect",
async execute(
interaction
) {
await deferPokerReply(
interaction
);
const gameId =
interaction.values?.[0];
if (!gameId) {
return interaction.editReply({
content:
"❌ No Poker table was selected.",
embeds: [],
components: []
});
}
try {
const game =
await getPokerService()
.joinTable({
gameId,
guildId:
interaction.guildId,
userId:
interaction.user.id
});
await refreshPokerTableMessage(
interaction.client,
game
);
return interaction.editReply({
content:
`✅ Joined the Poker table for **${game.data.buyIn.toLocaleString()} Cookies**.`,
embeds: [],
components:
buildOpenTableLink(
game
)
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+247
View File
@@ -268,6 +268,251 @@ async function changeCookies(
} }
/*
* ============================================================
* IDEMPOTENT COOKIE CHANGE
* ============================================================
*
* Applies a Cookie mutation at most once for a user-scoped
* operationId. This is useful for persistent game buy-ins and
* cash-outs that may be retried after a crash or restart.
*
* The Cookie balance mutation and operation marker are written
* atomically to the same user document.
*/
async function changeCookiesOnce(
userId,
amount,
operationId
) {
if (
!Number.isSafeInteger(
amount
)
) {
throw new Error(
"INVALID_COOKIE_AMOUNT"
);
}
if (
typeof operationId !==
"string"
||
!/^[A-Za-z0-9:_-]{1,160}$/.test(
operationId
)
) {
throw new Error(
"INVALID_COOKIE_OPERATION_ID"
);
}
if (
amount ===
0
) {
return {
applied:
false,
balance:
await getCookieBalance(
userId
)
};
}
const {
cookies
} =
getCollections();
const filter = {
userId,
appliedCookieOperations: {
$ne:
operationId
}
};
if (
amount <
0
) {
filter.cookies = {
$gte:
Math.abs(
amount
)
};
}
const update = {
$inc: {
cookies:
amount
},
$addToSet: {
appliedCookieOperations:
operationId
}
};
if (
amount >
0
) {
update.$setOnInsert = {
userId
};
}
try {
const result =
await cookies.updateOne(
filter,
update,
{
upsert:
amount > 0
}
);
if (
result.modifiedCount ===
1
||
result.upsertedCount ===
1
) {
return {
applied:
true,
balance:
await getCookieBalance(
userId
)
};
}
}
catch (error) {
/*
* A concurrent positive retry can race an upsert.
* The unique userId index may reject the losing insert.
* Re-read below and treat an already-recorded operation
* as a successful idempotent retry.
*/
if (
error?.code !==
11000
) {
throw error;
}
}
const document =
await cookies.findOne(
{
userId
},
{
projection: {
cookies:
1,
appliedCookieOperations:
1
}
}
);
const operations =
Array.isArray(
document
?.appliedCookieOperations
)
? document
.appliedCookieOperations
: [];
if (
operations.includes(
operationId
)
) {
return {
applied:
false,
balance:
Math.max(
0,
Number(
document?.cookies ??
0
)
)
};
}
if (
amount <
0
) {
throw new Error(
"INSUFFICIENT_COOKIES"
);
}
throw new Error(
"COOKIE_OPERATION_FAILED"
);
}
async function hasCookieOperation(
userId,
operationId
) {
if (
typeof operationId !==
"string"
||
!/^[A-Za-z0-9:_-]{1,160}$/.test(
operationId
)
) {
throw new Error(
"INVALID_COOKIE_OPERATION_ID"
);
}
const {
cookies
} =
getCollections();
const document =
await cookies.findOne(
{
userId,
appliedCookieOperations:
operationId
},
{
projection: {
_id:
1
}
}
);
return Boolean(
document
);
}
/* /*
* ============================================================ * ============================================================
* COOKIE TRANSFER * COOKIE TRANSFER
@@ -1188,6 +1433,8 @@ module.exports = {
getCookieBalance, getCookieBalance,
changeCookies, changeCookies,
changeCookiesOnce,
hasCookieOperation,
transferCookies, transferCookies,
getCooldown, getCooldown,
+2730
View File
File diff suppressed because it is too large Load Diff
+196 -56
View File
@@ -14,48 +14,88 @@ const {
requireInteractionSession requireInteractionSession
} = require("./interactionSession"); } = require("./interactionSession");
async function showCrumbSaucer(interaction) {
if (interaction.isButton()) { async function showCrumbSaucer(
const session = await requireInteractionSession(interaction); interaction
) {
if (
interaction.isButton()
) {
const session =
await requireInteractionSession(
interaction
);
if (!session) { if (!session) {
return; return;
} }
const balance = await getCookieBalance(interaction.user.id); const balance =
const embed = buildMainMenuEmbed(interaction.user, balance); await getCookieBalance(
interaction.user.id
);
const embed =
buildMainMenuEmbed(
interaction.user,
balance
);
return interaction.update({ return interaction.update({
embeds: [embed], embeds: [
components: buildMainMenuButtons() embed
],
components:
buildMainMenuButtons()
}); });
} }
const balance = await getCookieBalance(interaction.user.id); const balance =
const embed = buildMainMenuEmbed(interaction.user, balance); await getCookieBalance(
interaction.user.id
);
const embed =
buildMainMenuEmbed(
interaction.user,
balance
);
await interaction.reply({ await interaction.reply({
embeds: [embed], embeds: [
components: buildMainMenuButtons() embed
],
components:
buildMainMenuButtons()
}); });
const message = await interaction.fetchReply(); const message =
await interaction.fetchReply();
await createInteractionSession( await createInteractionSession(
interaction, interaction,
{ {
messageId: message.id, messageId:
ownerId: interaction.user.id, message.id,
kind: "crumbSaucer" ownerId:
interaction.user.id,
kind:
"crumbSaucer"
} }
); );
return message; return message;
} }
function buildMainMenuEmbed(user, balance) {
function buildMainMenuEmbed(
user,
balance
) {
return new EmbedBuilder() return new EmbedBuilder()
.setTitle("🎰 The Crumb Saucer") .setTitle(
"🎰 The Crumb Saucer"
)
.setDescription( .setDescription(
[ [
`Welcome, ${user}!`, `Welcome, ${user}!`,
@@ -78,79 +118,178 @@ function buildMainMenuEmbed(user, balance) {
"### 🏦 Vault Heist", "### 🏦 Vault Heist",
"Attempt to breach ShaiBot's Cookie Vault.", "Attempt to breach ShaiBot's Cookie Vault.",
"", "",
"### ♠️ Poker",
"Create or join a 2–6 player Cookie Poker table.",
"",
"### 🛟 Bankruptcy", "### 🛟 Bankruptcy",
"At exactly 0 Cookies, claim a 1,000 Cookie starter kit." "At exactly 0 Cookies, claim a 1,000 Cookie starter kit."
].join("\n") ].join("\n")
) )
.addFields({ .addFields({
name: "Your Vault", name:
value: `**${balance.toLocaleString()} 🍪**` "Your Vault",
value:
`**${balance.toLocaleString()} 🍪**`
}) })
.setFooter({ .setFooter({
text: `crumbSaucer:${user.id}` text:
`crumbSaucer:${user.id}`
}); });
} }
function buildMainMenuButtons() { function buildMainMenuButtons() {
return [ return [
new ActionRowBuilder() new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerSlots") .setCustomId(
.setLabel("Slots") "crumbSaucerSlots"
.setEmoji("🎰") )
.setStyle(ButtonStyle.Primary), .setLabel(
"Slots"
)
.setEmoji(
"🎰"
)
.setStyle(
ButtonStyle.Primary
),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerBlackjack") .setCustomId(
.setLabel("Blackjack") "crumbSaucerBlackjack"
.setEmoji("🃏") )
.setStyle(ButtonStyle.Primary), .setLabel(
"Blackjack"
)
.setEmoji(
"🃏"
)
.setStyle(
ButtonStyle.Primary
),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerRoulette") .setCustomId(
.setLabel("Roulette") "crumbSaucerRoulette"
.setEmoji("🎡") )
.setStyle(ButtonStyle.Primary), .setLabel(
"Roulette"
)
.setEmoji(
"🎡"
)
.setStyle(
ButtonStyle.Primary
),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerHeist") .setCustomId(
.setLabel("Vault Heist") "crumbSaucerHeist"
.setEmoji("🏦") )
.setStyle(ButtonStyle.Danger) .setLabel(
"Vault Heist"
)
.setEmoji(
"🏦"
)
.setStyle(
ButtonStyle.Danger
),
new ButtonBuilder()
.setCustomId(
"crumbSaucerPoker"
)
.setLabel(
"Poker"
)
.setEmoji(
"♠️"
)
.setStyle(
ButtonStyle.Success
)
), ),
new ActionRowBuilder() new ActionRowBuilder()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerCollectMenu") .setCustomId(
.setLabel("Cookie Bakery") "crumbSaucerCollectMenu"
.setEmoji("🍪") )
.setStyle(ButtonStyle.Secondary), .setLabel(
"Cookie Bakery"
)
.setEmoji(
"🍪"
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerBankruptcy") .setCustomId(
.setLabel("Bankruptcy") "crumbSaucerBankruptcy"
.setEmoji("🛟") )
.setStyle(ButtonStyle.Secondary), .setLabel(
"Bankruptcy"
)
.setEmoji(
"🛟"
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerProfile") .setCustomId(
.setLabel("Profile") "crumbSaucerProfile"
.setEmoji("👤") )
.setStyle(ButtonStyle.Secondary), .setLabel(
"Profile"
)
.setEmoji(
"👤"
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("sessionClose") .setCustomId(
.setLabel("Close") "sessionClose"
.setEmoji("✖️") )
.setStyle(ButtonStyle.Danger) .setLabel(
"Close"
)
.setEmoji(
"✖️"
)
.setStyle(
ButtonStyle.Danger
)
) )
]; ];
} }
function getCrumbSaucerSessionOwner(interaction) {
const footer = interaction.message?.embeds?.[0]?.footer?.text;
const parts = footer?.split(":") ?? [];
switch (parts[0]) { 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":
@@ -171,6 +310,7 @@ function getCrumbSaucerSessionOwner(interaction) {
} }
} }
module.exports = { module.exports = {
showCrumbSaucer, showCrumbSaucer,
buildMainMenuEmbed, buildMainMenuEmbed,
File diff suppressed because it is too large Load Diff
+2149
View File
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
const {
getPokerService
} = require("./crumbPoker");
const {
deferPokerReply,
editPokerError,
getPokerGameIdFromInteraction,
refreshPokerTableMessage
} = require("./pokerDiscord");
async function executePokerAction(
interaction,
action,
successText
) {
await deferPokerReply(
interaction
);
const gameId =
getPokerGameIdFromInteraction(
interaction
);
if (!gameId) {
return interaction.editReply({
content:
"❌ This Poker control is no longer attached to an active table.",
embeds: [],
components: []
});
}
try {
const service =
getPokerService();
const game =
await service.act({
gameId,
userId:
interaction.user.id,
action
});
await refreshPokerTableMessage(
interaction.client,
game
);
return interaction.editReply({
content:
typeof successText ===
"function"
? successText(game)
: successText,
embeds: [],
components: []
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
async function executeMinimumRaise(
interaction
) {
await deferPokerReply(
interaction
);
const gameId =
getPokerGameIdFromInteraction(
interaction
);
if (!gameId) {
return interaction.editReply({
content:
"❌ This Poker control is no longer attached to an active table.",
embeds: [],
components: []
});
}
try {
const service =
getPokerService();
const legal =
await service.getLegalPlayerActions({
gameId,
userId:
interaction.user.id
});
const type =
legal.actions?.includes(
"bet"
)
? "bet"
: legal.actions?.includes(
"raise"
)
? "raise"
: null;
if (
!type
||
!Number.isSafeInteger(
legal.minRaiseTo
)
) {
return interaction.editReply({
content:
"❌ A bet or raise is not available right now.",
embeds: [],
components: []
});
}
const game =
await service.act({
gameId,
userId:
interaction.user.id,
action: {
type,
amount:
legal.minRaiseTo
}
});
await refreshPokerTableMessage(
interaction.client,
game
);
return interaction.editReply({
content:
`✅ ${type === "bet" ? "Bet" : "Raised to"} ${legal.minRaiseTo.toLocaleString()} 🍪`,
embeds: [],
components: []
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
module.exports = {
executePokerAction,
executeMinimumRaise
};