From c61e34c241059294f930255048926da9b787682b Mon Sep 17 00:00:00 2001 From: DeSqBlocki Date: Tue, 8 Sep 2026 21:05:52 +0200 Subject: [PATCH] snapshot_poker_uiux_cleanup_2 --- buttons/poker/cancel.js | 85 +++++++++++ buttons/poker/cancelConfirm.js | 50 +++++++ buttons/poker/cancelKeep.js | 17 +++ utils/cookieEconomy.js | 41 +++++ utils/crumbPoker.js | 264 +++++++++++++++++++++++++++++++++ utils/pokerDiscord.js | 18 +++ 6 files changed, 475 insertions(+) create mode 100644 buttons/poker/cancel.js create mode 100644 buttons/poker/cancelConfirm.js create mode 100644 buttons/poker/cancelKeep.js diff --git a/buttons/poker/cancel.js b/buttons/poker/cancel.js new file mode 100644 index 0000000..e1ea4f6 --- /dev/null +++ b/buttons/poker/cancel.js @@ -0,0 +1,85 @@ +const { + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle +} = require("discord.js"); + +const { + getPokerService +} = require("../../utils/crumbPoker"); + +const { + POKER_TABLE_FOOTER_PREFIX, + deferPokerReply, + editPokerError, + getPokerGameIdFromInteraction +} = require("../../utils/pokerDiscord"); + +module.exports = { + name: "pokerCancel", + + 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 }); + + if ( + String(game.data.ownerId) !== + String(interaction.user.id) + ) { + return interaction.editReply({ + content: "❌ Only the table owner can cancel this Poker table.", + embeds: [], + components: [] + }); + } + + return interaction.editReply({ + content: "", + embeds: [ + new EmbedBuilder() + .setTitle("🛑 Cancel Poker Table?") + .setDescription( + [ + "All seated players will be safely cashed out.", + "", + "This cannot be undone." + ].join("\n") + ) + .setFooter({ + text: `${POKER_TABLE_FOOTER_PREFIX}:${gameId}` + }) + ], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("pokerCancelConfirm") + .setLabel("Cancel Table") + .setEmoji("🛑") + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId("pokerCancelKeep") + .setLabel("Keep Table") + .setStyle(ButtonStyle.Secondary) + ) + ] + }); + } catch (error) { + return editPokerError(interaction, error); + } + } +}; diff --git a/buttons/poker/cancelConfirm.js b/buttons/poker/cancelConfirm.js new file mode 100644 index 0000000..bbb55f7 --- /dev/null +++ b/buttons/poker/cancelConfirm.js @@ -0,0 +1,50 @@ +const { + getPokerService +} = require("../../utils/crumbPoker"); + +const { + deferPokerReply, + editPokerError, + getPokerGameIdFromInteraction, + refreshPokerTableMessage +} = require("../../utils/pokerDiscord"); + +module.exports = { + name: "pokerCancelConfirm", + + 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().cancelTable({ + gameId, + userId: interaction.user.id + }); + + await refreshPokerTableMessage( + interaction.client, + game + ); + + return interaction.editReply({ + content: "✅ Poker table cancelled. All seated players were cashed out.", + embeds: [], + components: [] + }); + } catch (error) { + return editPokerError(interaction, error); + } + } +}; diff --git a/buttons/poker/cancelKeep.js b/buttons/poker/cancelKeep.js new file mode 100644 index 0000000..c875a9b --- /dev/null +++ b/buttons/poker/cancelKeep.js @@ -0,0 +1,17 @@ +const { + deferPokerReply +} = require("../../utils/pokerDiscord"); + +module.exports = { + name: "pokerCancelKeep", + + async execute(interaction) { + await deferPokerReply(interaction); + + return interaction.editReply({ + content: "✅ Poker table left open.", + embeds: [], + components: [] + }); + } +}; diff --git a/utils/cookieEconomy.js b/utils/cookieEconomy.js index e6524b1..223e066 100644 --- a/utils/cookieEconomy.js +++ b/utils/cookieEconomy.js @@ -470,6 +470,46 @@ async function changeCookiesOnce( } +async function clearCookieOperation( + 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 result = + await cookies.updateOne( + { + userId + }, + { + $pull: { + appliedCookieOperations: + operationId + } + } + ); + + return result.modifiedCount === + 1; +} + + async function hasCookieOperation( userId, operationId @@ -1435,6 +1475,7 @@ module.exports = { changeCookies, changeCookiesOnce, hasCookieOperation, + clearCookieOperation, transferCookies, getCooldown, diff --git a/utils/crumbPoker.js b/utils/crumbPoker.js index e368fe9..76763ad 100644 --- a/utils/crumbPoker.js +++ b/utils/crumbPoker.js @@ -818,6 +818,22 @@ function createMongoPokerStore( ); }, + async deleteGame( + gameId + ) { + const result = + await collection.deleteOne({ + gameId, + gameType: + POKER_GAME_TYPE, + recordType: + "game" + }); + + return result.deletedCount === + 1; + }, + async listActiveGames() { const documents = await collection @@ -1114,6 +1130,22 @@ function createDefaultPokerEconomy() { userId, operationId ); + }, + + async clearOperation( + userId, + operationId + ) { + const { + clearCookieOperation + } = require( + "./cookieEconomy" + ); + + return clearCookieOperation( + userId, + operationId + ); } }; } @@ -1139,6 +1171,43 @@ function createPokerService({ lobbyTimeout = POKER_LOBBY_TIMEOUT } = {}) { + async function clearOperationQuietly( + userId, + operationId + ) { + try { + await economy.clearOperation( + userId, + operationId + ); + } + + catch (error) { + console.error( + `[Poker] Failed to clear completed Cookie operation ${operationId} for ${userId}:`, + error + ); + } + } + + async function deleteGameQuietly( + gameId + ) { + try { + await store.deleteGame( + gameId + ); + } + + catch (error) { + console.error( + `[Poker] Failed to delete settled table ${gameId}:`, + error + ); + } + } + + async function readGame( gameId ) { @@ -1234,6 +1303,22 @@ function createPokerService({ gameId, sessionId ); + + await clearOperationQuietly( + userId, + buyInOperationId( + gameId, + sessionId + ) + ); + + await clearOperationQuietly( + userId, + buyInRefundOperationId( + gameId, + sessionId + ) + ); } } @@ -1362,6 +1447,14 @@ function createPokerService({ sessionId, now() ); + + await clearOperationQuietly( + ownerId, + buyInOperationId( + gameId, + sessionId + ) + ); } catch (error) { @@ -1519,6 +1612,14 @@ function createPokerService({ sessionId, now() ); + + await clearOperationQuietly( + userId, + buyInOperationId( + gameId, + sessionId + ) + ); } catch (error) { @@ -2317,6 +2418,23 @@ function createPokerService({ player.sessionId ); + await clearOperationQuietly( + userId, + cashOutOperationId( + gameId, + player.sessionId + ) + ); + + if ( + nextGame.state === + "CLOSED" + ) { + await deleteGameQuietly( + gameId + ); + } + return { amount, game: @@ -2403,6 +2521,23 @@ function createPokerService({ closed ) ) { + for ( + const player + of current.data.players + ) { + await clearOperationQuietly( + player.userId, + cashOutOperationId( + gameId, + player.sessionId + ) + ); + } + + await deleteGameQuietly( + gameId + ); + return closed; } } @@ -2413,6 +2548,88 @@ function createPokerService({ ); } + async function cancelTable({ + gameId, + userId + }) { + const game = + await readGame( + gameId + ); + + if ( + String(game.data.ownerId) !== + String(userId) + ) { + throw new PokerTableError( + "NOT_TABLE_OWNER", + "Only the Poker table owner can cancel this table." + ); + } + + if ( + isHandActive( + game + ) + ) { + throw new PokerTableError( + "CANCEL_HAND_ACTIVE", + "Finish the current hand before cancelling the table." + ); + } + + if ( + game.state === + "CLOSING" + ) { + return finishClosingTable( + gameId + ); + } + + if ( + !POKER_OPEN_STATES.includes( + game.state + ) + ) { + throw new PokerTableError( + "TABLE_NOT_CANCELLABLE", + "This Poker table cannot be cancelled in its current state." + ); + } + + const closing = + clone(game); + + closing.state = + "CLOSING"; + + closing.version = + game.version + 1; + + closing.updatedAt = + now(); + + const committed = + await store.updateGameVersion( + gameId, + game.version, + closing + ); + + if (!committed) { + throw new PokerTableError( + "STATE_CONFLICT", + "Poker table changed while cancellation was starting." + ); + } + + return finishClosingTable( + gameId + ); + } + + async function expireLobby( gameId ) { @@ -2569,6 +2786,28 @@ function createPokerService({ player.sessionId ); + await clearOperationQuietly( + player.userId, + cashOutOperationId( + game.gameId, + player.sessionId + ) + ); + + const remainingGame = + await store.getGame( + game.gameId + ); + + if ( + remainingGame?.state === + "CLOSED" + ) { + await deleteGameQuietly( + game.gameId + ); + } + results.push({ gameId: game.gameId, @@ -2651,6 +2890,14 @@ function createPokerService({ now() ); + await clearOperationQuietly( + userId, + buyInOperationId( + tableGameId, + sessionId + ) + ); + results.push({ userId, gameId: @@ -2692,6 +2939,22 @@ function createPokerService({ sessionId ); + await clearOperationQuietly( + userId, + buyInOperationId( + tableGameId, + sessionId + ) + ); + + await clearOperationQuietly( + userId, + buyInRefundOperationId( + tableGameId, + sessionId + ) + ); + results.push({ userId, gameId: @@ -2750,6 +3013,7 @@ function createPokerService({ processTurnTimeout, processExpiredTurns, cashOut, + cancelTable, finishClosingTable, expireLobby, processExpiredLobbies, diff --git a/utils/pokerDiscord.js b/utils/pokerDiscord.js index a93fce7..7d15ac8 100644 --- a/utils/pokerDiscord.js +++ b/utils/pokerDiscord.js @@ -110,6 +110,10 @@ function formatPokerError( "There is no active Poker hand.", HAND_ACTIVE: "You cannot cash out during an active Poker hand.", + CANCEL_HAND_ACTIVE: + "Finish the current hand before cancelling the table.", + TABLE_NOT_CANCELLABLE: + "This Poker table cannot be cancelled right now.", OUT_OF_TURN: "It isn't your turn.", TURN_EXPIRED: @@ -715,6 +719,20 @@ function buildPokerTableComponents( ) .setStyle( ButtonStyle.Secondary + ), + + new ButtonBuilder() + .setCustomId( + "pokerCancel" + ) + .setLabel( + "Cancel Table" + ) + .setEmoji( + "🛑" + ) + .setStyle( + ButtonStyle.Danger ) ) ];