Compare commits

...

46 Commits

Author SHA1 Message Date
DeSqBlocki 6e3e1620d1 added more documentation 2026-09-13 22:10:34 +02:00
DeSqBlocki dd4d6e4817 added achievements for new steal logic 2026-09-13 21:56:18 +02:00
DeSqBlocki b441de286c added ai rulebook 2026-09-13 21:56:01 +02:00
DeSqBlocki 2ca18aae06 updated steal logic 2026-09-13 21:14:56 +02:00
DeSqBlocki 1ef2295c07 snapshot_poker_phase5 2026-09-08 21:49:43 +02:00
DeSqBlocki 6342c4569b snapshot_poker_ui_ux_fix_again_fuckmylife 2026-09-08 21:12:34 +02:00
DeSqBlocki c61e34c241 snapshot_poker_uiux_cleanup_2 2026-09-08 21:05:52 +02:00
DeSqBlocki 82cef1b34a snapshot_poker_uiux_cleanup 2026-09-08 20:51:58 +02:00
DeSqBlocki 136a5902c0 snapshot_poker_phase4_hotfix 2026-09-08 20:17:19 +02:00
DeSqBlocki 903a2f3081 snapshot_poker_phase3 2026-09-08 19:37:02 +02:00
DeSqBlocki 995a2317b9 small changes to UI/UX 2026-08-27 21:35:06 +02:00
DeSqBlocki e3dbb11e12 snapshot migration pre-phase 6, stable 2026-08-27 21:14:27 +02:00
DeSqBlocki 14a059034c snapshot migration subPhase 5.5 2026-08-27 19:50:12 +02:00
DeSqBlocki 9710fc65bf snapshot migration phase 5 2026-08-27 19:34:16 +02:00
DeSqBlocki 42fc848f8c snapshot migration phase 4 2026-08-27 19:19:58 +02:00
DeSqBlocki c2ac7737c0 snapshot rollout phase 1 2026-08-27 18:52:56 +02:00
DeSqBlocki 6a519f3a2d changed achievment to work without followup 2026-08-23 01:19:22 +02:00
DeSqBlocki 338b32430d made achievments easier to discern 2026-08-23 01:00:03 +02:00
DeSqBlocki 9f1a539501 updated readme 2026-08-23 00:52:37 +02:00
DeSqBlocki 425686ec40 added new logic to the new rewards 2026-08-23 00:43:43 +02:00
DeSqBlocki 4d858554be made collect logic actually connect to the utils 2026-08-23 00:21:52 +02:00
DeSqBlocki 9c867b1adb added bankruptcy 2026-08-23 00:09:13 +02:00
DeSqBlocki 030b4b8883 updated profile and centralized functions 2026-08-22 23:33:20 +02:00
DeSqBlocki 08d1a8c5cd updated badges 2026-08-22 22:46:40 +02:00
DeSqBlocki 9e0870c1ed snapshot 2026-08-22 22:29:16 +02:00
DeSqBlocki ca823726e3 added more achievments 2026-08-22 22:10:41 +02:00
DeSqBlocki ae35f61522 added cookie collect button 2026-08-22 20:59:25 +02:00
DeSqBlocki 985f481fae fixed some copy pasted issues 2026-08-22 20:40:25 +02:00
DeSqBlocki e437c051b9 profile update with badges and achievements 2026-08-22 20:27:42 +02:00
DeSqBlocki c630543f9e fixed runitback for slots 2026-08-22 20:15:25 +02:00
DeSqBlocki 9847e476b8 Merge branch 'main' of https://git.desq-gaming.de/admin_rb/ShaiBot 2026-08-22 20:14:09 +02:00
DeSqBlocki 8aeab3fb5d added base profile update 2026-08-22 20:13:47 +02:00
DeSqBlocki 81ef97370c added runtiback and allin 2026-08-22 19:52:47 +02:00
DeSqBlocki 1fd73538fa added ReadMe 2026-08-18 21:12:32 +02:00
DeSqBlocki 32ce20bd72 added roulette 2026-08-18 21:03:07 +02:00
DeSqBlocki 0d512960a7 added blackjack 2026-08-18 20:44:51 +02:00
DeSqBlocki 0407f4d7f1 crumleStrike Roles snapshot 2026-08-18 20:31:03 +02:00
DeSqBlocki 0f48f09bd9 refactored and moved 2026-08-18 20:07:41 +02:00
DeSqBlocki b9a887259a boos crumble snapshot 2026-08-18 19:45:43 +02:00
DeSqBlocki 24d5b395ca new games snapshot 2026-08-18 18:57:24 +02:00
DeSqBlocki 8bdd6868e8 added more steal logic 2026-08-15 08:44:24 +02:00
DeSqBlocki 5df3430242 fuk you rai <3 2026-08-15 00:38:21 +02:00
DeSqBlocki 832863c1e4 added role reaction dynamic embed changes 2026-08-15 00:32:27 +02:00
DeSqBlocki 7c49e05aec added new assets 2026-08-15 00:31:45 +02:00
DeSqBlocki dafa81e2a6 added proper modal handler and raidplan 2026-08-14 23:49:31 +02:00
DeSqBlocki 9e94e99874 added more static files 2026-08-14 23:49:01 +02:00
147 changed files with 41983 additions and 623 deletions
+3
View File
@@ -1,2 +1,5 @@
node_modules node_modules
.env .env
backup/
*.bak
tests/
+2220
View File
File diff suppressed because it is too large Load Diff
+289
View File
@@ -0,0 +1,289 @@
# ShaiBot
ShaiBot is a modular Discord community bot built around a persistent
**Cookie economy**, interactive games, player progression, and
multiplayer activities.
The bot's main entertainment hub is the **Crumb Saucer**, where players
can collect and wager Cookies, play casino-style games, attempt Vault
Heists, recover through Bankruptcy, and sit down at a multiplayer Texas
Hold'em table.
ShaiBot also includes persistent player profiles, achievements,
guild-unique badges, cooperative encounters, raid planning, and lighter
community features.
> **Status:** Active development and stabilization. The major economy,
> profile, achievement, and Poker overhaul is live; current work is
> focused primarily on testing, balancing, and polish.
## Highlights
- 🍪 Persistent Cookie economy
- 🎰 Crumb Saucer game hub
- 🎰 Slots, Blackjack, Roulette, Vault Heist, and Bankruptcy
- ♠️ Persistent 2--6 player Texas Hold'em
- 🏴 Player-to-player Cookie stealing
- 👤 Lifetime player profiles and statistics
- 🏆 Personal achievements
- 🎖️ Guild-unique global badges
- 🔁 All-In and Run It Back progression
- ⚔️ Cooperative Crumble Strike encounters
- 📅 Raid planning and community utilities
- 💾 Mongo-backed persistence and recovery
## The Crumb Saucer
The **Crumb Saucer** is the main home for ShaiBot's Cookie-powered
games.
### 🍪 Cookie Bakery
Collect Cookies using the same shared collection system as
`/cookies collect`.
### 🎰 Slots
Spin for symbol combinations, jackpots, All-In wagers, and Run It Back
streaks.
### 🃏 Blackjack
Includes Hit, Stand, Double, natural Blackjack, pushes, All-In, and Run
It Back.
### 🎡 Roulette
A compact European Roulette game using numbers `0–36`, with Red, Black,
Odd, Even, and Zero bets.
### 🏦 Vault Heist
Attempt to steal from ShaiBot's house vault using different risk/reward
approaches.
### 🆘 Bankruptcy
A recovery mechanic for players who need a fresh start, with profile
progression for rebuilding afterward.
### ♠️ Shai Poker
A persistent multiplayer Texas Hold'em game for **2--6 players**.
Current defaults:
``` text
Buy-in: 1,000 Cookies
Blinds: 10 / 20
Turn timer: 90 seconds
Pot model: single pot
```
Poker supports private hole cards, public community cards,
Fold/Check/Call/Bet/Raise/All-In, automatic timeout folding, restart
recovery, host cancellation, safe buy-in/cash-out settlement, Poker
statistics, achievements, and guild-first badges.
Side pots, tournaments, bots, rake, and player-facing equity assistance
are intentionally outside the current V1 design.
## Player Profiles
Profiles provide the permanent history behind ShaiBot's progression
systems.
They track things such as:
- Game wins and losses
- Per-game statistics
- Cookie gains and losses
- Giving and stealing
- Daily activity and streaks
- Social progression
- Run It Back streaks
- Poker history and hand accomplishments
- Achievements
- Global badges
Completed multiplayer game documents do not need to become permanent
history when the meaningful result already belongs in the profile
system.
## Achievements & Global Badges
**Achievements** are personal progression. Any eligible player can earn
them.
**Global badges** are guild-unique distinctions, usually awarded to the
first player in a guild to accomplish something notable.
Progression spans the Cookie economy, stealing, Crumb Saucer games,
activity, social interactions, Poker, and special cross-game challenges.
## Cookie Stealing
Player-to-player stealing is separate from Vault Heist.
The current system accounts for wealth differences, recent pressure on
the victim, percentage-based loot, hard caps, and an hourly exposure
budget.
Detailed balancing and mechanics are documented in
[`COOKIE_LOGIC.md`](COOKIE_LOGIC.md).
## ⚔️ Crumble Strike
Crumble Strike is ShaiBot's cooperative boss-battle system.
Players recruit a party, choose combat roles, commit Cookies, and work
together using actions such as Strike, Defend, Heal, Revive, and Limit
Break.
Encounters use persistent state because they can span many players and
interactions.
## 📅 Raid Planner & Community Features
ShaiBot also includes raid-planning tools and smaller social/community
commands alongside its economy and progression systems.
------------------------------------------------------------------------
# Installation
## Requirements
- Node.js
- MongoDB
- A Discord bot application/token
- Dependencies listed in `package.json`
Clone and install:
``` bash
git clone https://git.desq-gaming.de/Shaiwase/ShaiBot.git
cd ShaiBot
npm install
```
Create `.env` in the repository root:
``` env
D_TOKEN=YOUR_DISCORD_BOT_TOKEN
M_URI=YOUR_MONGODB_CONNECTION_STRING
M_DB=YOUR_DATABASE_NAME
# Optional
BOT_AUTHOR_ID=YOUR_DISCORD_USER_ID
PROFILE_TIMEZONE=Europe/Berlin
```
Never commit `.env`.
Start normally:
``` bash
node index.js
```
Or with PM2:
``` bash
pm2 start index.js --name ShaiBot
```
A typical update cycle is:
``` bash
node --check path/to/changed-file.js
pm2 restart ShaiBot
pm2 logs ShaiBot --lines 100
```
## MongoDB
ShaiBot does not require MongoDB multi-document transactions for its
core economy.
The bot instead relies on atomic updates, unique indexes, optimistic
concurrency, idempotent operations, and recovery logic where
appropriate.
If your Mongo deployment does not support retryable writes, your
connection string may require:
``` text
retryWrites=false
```
Never commit database credentials.
------------------------------------------------------------------------
# Documentation
The central README intentionally stays high-level. Detailed
implementation rules live in focused manuals:
------------------------------------------------------------------------------------
Document Purpose
------------------------------------------------ -----------------------------------
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) Runtime structure, handlers,
utilities, Mongo persistence,
concurrency, and recovery
[`docs/ECONOMY.md`](docs/ECONOMY.md) Cookie balances, transfers,
cooldowns, house vault, settlement,
and idempotency
[`docs/PROGRESSION.md`](docs/PROGRESSION.md) Profiles, achievements, badges,
activity, social progression, and
maintenance
[`docs/GAMES.md`](docs/GAMES.md) Crumb Saucer games, Poker, Crumble
Strike, and important game rules
[`COOKIE_LOGIC.md`](COOKIE_LOGIC.md) Detailed PvP Cookie stealing rules
and balancing
[`AGENTS.md`](AGENTS.md) Repository-wide safety and
architecture guide for AI coding
agents
------------------------------------------------------------------------------------
The current repository code remains the final source of truth.
------------------------------------------------------------------------
# Development Principles
ShaiBot generally favors:
- Shared utilities instead of duplicated game/economy logic
- Thin Discord command/button wrappers
- Persistent authoritative state where recovery matters
- Atomic and idempotent economy operations
- Centralized profile progression
- Public game experiences with private sensitive information
- Small explicit solutions over unnecessary abstraction
For stateful changes, always consider duplicate interactions,
simultaneous users, insufficient-balance races, and bot restarts during
settlement.
## Security
Keep at least the following out of version control:
``` gitignore
node_modules/
.env
*.log
```
Rotate Discord or MongoDB credentials immediately if they are exposed.
## License
See the repository's current license information, if present.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

+11
View File
@@ -0,0 +1,11 @@
const {
closeInteractionSession
} = require("../../utils/interactionSession");
module.exports = {
name: "sessionClose",
async execute(interaction) {
return closeInteractionSession(interaction);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
showCrumbSaucer
} = require(
"../../utils/crumbSaucer"
);
module.exports = {
name:
"crumbSaucerBack",
async execute(interaction) {
return showCrumbSaucer(
interaction
);
}
};
+91
View File
@@ -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]
});
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
showBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbSaucerBlackjack",
async execute(
interaction
) {
return showBlackjack(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
startBlackjackAllIn
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackAllIn",
async execute(
interaction
) {
return startBlackjackAllIn(
interaction
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
startBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackBet10",
async execute(interaction) {
return startBlackjack(
interaction,
10
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
startBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackBet100",
async execute(interaction) {
return startBlackjack(
interaction,
100
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
startBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackBet1000",
async execute(interaction) {
return startBlackjack(
interaction,
1000
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
startBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackBet50",
async execute(interaction) {
return startBlackjack(
interaction,
50
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
startBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackBet500",
async execute(interaction) {
return startBlackjack(
interaction,
500
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
doubleBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackDouble",
async execute(
interaction
) {
return doubleBlackjack(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
hitBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackHit",
async execute(
interaction
) {
return hitBlackjack(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
runBlackjackBack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackRunItBack",
async execute(
interaction
) {
return runBlackjackBack(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
standBlackjack
} = require(
"../../utils/crumbBlackjack"
);
module.exports = {
name:
"crumbBlackjackStand",
async execute(
interaction
) {
return standBlackjack(
interaction
);
}
};
+91
View File
@@ -0,0 +1,91 @@
const {
EmbedBuilder
} = require(
"discord.js"
);
const {
temporaryEphemeralReply
} = require(
"../../utils/cookieEconomy"
);
const {
getCrumbSaucerSessionOwner
} = require(
"../../utils/crumbSaucer"
);
function delay(
ms
) {
return new Promise(
resolve =>
setTimeout(
resolve,
ms
)
);
}
module.exports = {
name:
"crumbSaucerClose",
async execute(
interaction
) {
const ownerId =
getCrumbSaucerSessionOwner(
interaction
);
if (
ownerId !==
interaction.user.id
) {
return temporaryEphemeralReply(
interaction,
{
content:
"This isn't your Crumb Saucer session."
}
);
}
const embed =
new EmbedBuilder()
.setTitle(
"Closed"
)
.setDescription(
"Closing Crumb Saucer..."
);
await interaction.update({
embeds: [
embed
],
components: []
});
await delay(
1500
);
try {
await interaction.deleteReply();
}
catch {}
}
};
+20
View File
@@ -0,0 +1,20 @@
const {
collectCookiesFromSaucer
} = require(
"../../utils/crumbCollect"
);
module.exports = {
name:
"crumbSaucerCollect",
async execute(
interaction
) {
return collectCookiesFromSaucer(
interaction
);
}
};
+20
View File
@@ -0,0 +1,20 @@
const {
showCollectPage
} = require(
"../../utils/crumbCollect"
);
module.exports = {
name:
"crumbSaucerCollectMenu",
async execute(
interaction
) {
return showCollectPage(
interaction
);
}
};
+301
View File
@@ -0,0 +1,301 @@
const {
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle
} = require("discord.js");
const {
getCookieBalance,
getCooldown,
getUnixTime,
temporaryEphemeralReply
} = require(
"../../utils/cookieEconomy"
);
const {
HEIST_COOLDOWN
} = require(
"../../utils/cookieHeist"
);
module.exports = {
name:
"crumbSaucerHeist",
async execute(interaction) {
/*
* Verify ownership of the Crumb Saucer menu.
*/
const footer =
interaction.message
.embeds[0]
?.footer
?.text;
const [
type,
ownerId
] =
footer?.split(":")
?? [];
if (
type !== "crumbSaucer" ||
ownerId !== interaction.user.id
) {
return temporaryEphemeralReply(
interaction,
{
content:
"This isn't your Crumb Saucer session."
}
);
}
/*
* ====================================================
* SHARED HEIST COOLDOWN PRE-CHECK
* ====================================================
*/
const now =
getUnixTime();
const cooldown =
await getCooldown(
interaction.user.id,
interaction.guildId,
"heist"
);
if (
cooldown >
now
) {
return temporaryEphemeralReply(
interaction,
{
content:
`🏦 The ShaiBot Vault is still on alert.\nYou can attempt another heist <t:${cooldown}:R>.`
},
6000
);
}
/*
* Balances
*/
const vaultId =
interaction.client.user.id;
const [
vault,
player
] =
await Promise.all([
getCookieBalance(
vaultId
),
getCookieBalance(
interaction.user.id
)
]);
if (
player <
1
) {
return temporaryEphemeralReply(
interaction,
{
content:
"You need at least **1 Cookie** before attempting a vault heist."
}
);
}
if (
vault <
1
) {
return temporaryEphemeralReply(
interaction,
{
content:
"The ShaiBot Vault is currently empty."
}
);
}
/*
* Heist menu
*/
const embed =
new EmbedBuilder()
.setTitle(
"🏦 ShaiBot Cookie Vault"
)
.setDescription(
[
`**Vault Balance:** ${vault.toLocaleString()} 🍪`,
`**Security:** ${getSecurityLabel(vault)}`,
"",
"Choose your approach.",
"",
"🥷 **Sneak**",
"High chance • Small haul • Small fine",
"",
"💻 **Hack**",
"Medium chance • Medium haul • Medium fine",
"",
"💥 **Smash**",
"Low chance • Large haul • Large fine"
].join("\n")
)
.setFooter({
text:
`cookieHeist:${interaction.user.id}`
});
const heistButtons =
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
"cookieHeistSneak"
)
.setLabel(
"Sneak"
)
.setEmoji(
"🥷"
)
.setStyle(
ButtonStyle.Primary
),
new ButtonBuilder()
.setCustomId(
"cookieHeistHack"
)
.setLabel(
"Hack"
)
.setEmoji(
"💻"
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder()
.setCustomId(
"cookieHeistSmash"
)
.setLabel(
"Smash"
)
.setEmoji(
"💥"
)
.setStyle(
ButtonStyle.Danger
)
);
const backButton =
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
"crumbSaucerBack"
)
.setLabel(
"Back to Crumb Saucer"
)
.setEmoji(
"◀️"
)
.setStyle(
ButtonStyle.Secondary
)
);
return interaction.update({
embeds: [
embed
],
components: [
heistButtons,
backButton
]
});
}
};
/*
* SECURITY LABEL
*/
function getSecurityLabel(
vault
) {
if (
vault <
1000
) {
return "🟢 Low";
}
if (
vault <
10000
) {
return "🟡 Moderate";
}
if (
vault <
50000
) {
return "🟠 High";
}
if (
vault <
250000
) {
return "🔴 Very High";
}
return "🟣 Maximum";
}
+18
View File
@@ -0,0 +1,18 @@
const {
resolveCookieHeist
} = require(
"../../utils/cookieHeist"
);
module.exports = {
name:
"cookieHeistHack",
async execute(interaction) {
return resolveCookieHeist(
interaction,
"Hack"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
resolveCookieHeist
} = require(
"../../utils/cookieHeist"
);
module.exports = {
name:
"cookieHeistSmash",
async execute(interaction) {
return resolveCookieHeist(
interaction,
"Smash"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
resolveCookieHeist
} = require(
"../../utils/cookieHeist"
);
module.exports = {
name:
"cookieHeistSneak",
async execute(interaction) {
return resolveCookieHeist(
interaction,
"Sneak"
);
}
};
+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()
});
}
};
+58
View File
@@ -0,0 +1,58 @@
const {
buildProfileOverview,
buildProfileButtons
} = require("../../utils/profileSystem");
const {
temporaryEphemeralReply
} = require("../../utils/cookieEconomy");
module.exports = {
name: "crumbSaucerProfile",
async execute(interaction) {
const footer =
interaction.message
?.embeds?.[0]
?.footer
?.text;
const [
type,
ownerId
] = footer?.split(":") ?? [];
if (
type !== "crumbSaucer" ||
ownerId !== interaction.user.id
) {
return temporaryEphemeralReply(
interaction,
{
content:
"This isn't your Crumb Saucer session."
}
);
}
const embed =
await buildProfileOverview(
interaction.guildId,
interaction.user,
interaction.user.id
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"overview",
0,
1
)
});
}
};
+17
View File
@@ -0,0 +1,17 @@
const {
showRoulette
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbSaucerRoulette",
async execute(interaction) {
return showRoulette(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
startRouletteAllIn
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteAllIn",
async execute(
interaction
) {
return startRouletteAllIn(
interaction
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseRouletteBet
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteBet10",
async execute(interaction) {
return chooseRouletteBet(
interaction,
10
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseRouletteBet
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteBet100",
async execute(interaction) {
return chooseRouletteBet(
interaction,
100
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseRouletteBet
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteBet1000",
async execute(interaction) {
return chooseRouletteBet(
interaction,
1000
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseRouletteBet
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteBet50",
async execute(interaction) {
return chooseRouletteBet(
interaction,
50
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseRouletteBet
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteBet500",
async execute(interaction) {
return chooseRouletteBet(
interaction,
500
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
playRoulette
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteBlack",
async execute(interaction) {
return playRoulette(
interaction,
"Black"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
playRoulette
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteEven",
async execute(interaction) {
return playRoulette(
interaction,
"Even"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
playRoulette
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteOdd",
async execute(interaction) {
return playRoulette(
interaction,
"Odd"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
playRoulette
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteRed",
async execute(interaction) {
return playRoulette(
interaction,
"Red"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
runRouletteBack
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteRunItBack",
async execute(
interaction
) {
return runRouletteBack(
interaction
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
playRoulette
} = require(
"../../utils/crumbRoulette"
);
module.exports = {
name:
"crumbRouletteZero",
async execute(interaction) {
return playRoulette(
interaction,
"Zero"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
showSlots
} = require(
"../../utils/crumbSlots.js"
);
module.exports = {
name:
"crumbSaucerSlots",
async execute(interaction) {
return showSlots(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playSlots
} = require(
"../../utils/crumbSlots.js"
);
module.exports = {
name:
"crumbSaucerSlots10",
async execute(interaction) {
return playSlots(
interaction,
10
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playSlots
} = require(
"../../utils/crumbSlots.js"
);
module.exports = {
name:
"crumbSaucerSlots100",
async execute(interaction) {
return playSlots(
interaction,
100
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playSlots
} = require(
"../../utils/crumbSlots.js"
);
module.exports = {
name:
"crumbSaucerSlots1000",
async execute(interaction) {
return playSlots(
interaction,
1000
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playSlots
} = require(
"../../utils/crumbSlots.js"
);
module.exports = {
name:
"crumbSaucerSlots50",
async execute(interaction) {
return playSlots(
interaction,
50
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playSlots
} = require(
"../../utils/crumbSlots.js"
);
module.exports = {
name:
"crumbSaucerSlots500",
async execute(interaction) {
return playSlots(
interaction,
500
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
runSlotsBack
} = require(
"../../utils/crumbSlots.js"
);
module.exports = {
name:
"crumbSaucerSlotsRunItBack",
async execute(
interaction
) {
return runSlotsBack(
interaction
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
performCrumbleStrikeAction
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeDefend",
async execute(interaction) {
return performCrumbleStrikeAction(
interaction,
"Defend"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
performCrumbleStrikeAction
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeHeal",
async execute(interaction) {
return performCrumbleStrikeAction(
interaction,
"Heal"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobAstrologian",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"Healer",
"Astrologian"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobBard",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"DPS",
"Bard"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobBlackMage",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"DPS",
"BlackMage"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobDarkKnight",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"Tank",
"DarkKnight"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobDragoon",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"DPS",
"Dragoon"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobPaladin",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"Tank",
"Paladin"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobScholar",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"Healer",
"Scholar"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobWarrior",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"Tank",
"Warrior"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
chooseCrumbleStrikeJob
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeJobWhiteMage",
async execute(interaction) {
return chooseCrumbleStrikeJob(
interaction,
"Healer",
"WhiteMage"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
performCrumbleStrikeAction
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeLimitBreak",
async execute(interaction) {
return performCrumbleStrikeAction(
interaction,
"LimitBreak"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
performCrumbleStrikeAction
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeRevive",
async execute(interaction) {
return performCrumbleStrikeAction(
interaction,
"Revive"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseCrumbleStrikeRole
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeRoleDPS",
async execute(interaction) {
return chooseCrumbleStrikeRole(
interaction,
"DPS"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseCrumbleStrikeRole
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeRoleHealer",
async execute(interaction) {
return chooseCrumbleStrikeRole(
interaction,
"Healer"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
chooseCrumbleStrikeRole
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeRoleTank",
async execute(interaction) {
return chooseCrumbleStrikeRole(
interaction,
"Tank"
);
}
};
+17
View File
@@ -0,0 +1,17 @@
const {
startCrumbleStrikeBattle
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeStart",
async execute(interaction) {
return startCrumbleStrikeBattle(
interaction
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
performCrumbleStrikeAction
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeStrike",
async execute(interaction) {
return performCrumbleStrikeAction(
interaction,
"Strike"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
confirmCrumbleStrikeJoin
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeTierAdventurer",
async execute(interaction) {
return confirmCrumbleStrikeJoin(
interaction,
"Adventurer"
);
}
};
+18
View File
@@ -0,0 +1,18 @@
const {
confirmCrumbleStrikeJoin
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeTierSprout",
async execute(interaction) {
return confirmCrumbleStrikeJoin(
interaction,
"Sprout"
);
}
};
@@ -0,0 +1,18 @@
const {
confirmCrumbleStrikeJoin
} = require(
"../../utils/crumbleStrike"
);
module.exports = {
name:
"crumbleStrikeTierWarriorOfLight",
async execute(interaction) {
return confirmCrumbleStrikeJoin(
interaction,
"WarriorOfLight"
);
}
};
+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."
);
}
};
+85
View File
@@ -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);
}
}
};
+50
View File
@@ -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
);
try {
await interaction.deleteReply();
} catch {}
return;
} catch (error) {
return editPokerError(interaction, error);
}
}
};
+17
View File
@@ -0,0 +1,17 @@
const {
deferPokerReply
} = require("../../utils/pokerDiscord");
module.exports = {
name: "pokerCancelKeep",
async execute(interaction) {
await deferPokerReply(interaction);
try {
await interaction.deleteReply();
} catch {}
return;
}
};
+151
View File
@@ -0,0 +1,151 @@
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 service =
getPokerService();
const [
cards,
game
] =
await Promise.all([
service.viewCards({
gameId,
userId:
interaction.user.id
}),
service.getTable({
gameId
})
]);
const hand =
game.data.hand;
const player =
game.data.players.find(
candidate =>
String(candidate.userId) ===
String(interaction.user.id)
);
const currentPlayer =
hand?.players?.find(
candidate =>
candidate.seat ===
hand.currentSeat
);
const isYourTurn =
Boolean(
player
&&
hand
&&
hand.currentSeat ===
player.seat
);
const communityCards =
hand?.communityCards ?? [];
const lines = [
"### 🃏 Your Cards",
`**${cards.map(cardToString).join(" ")}**`,
"",
"**Community:** " +
(communityCards.length
? communityCards
.map(cardToString)
.join(" ")
: "No community cards yet."),
""
];
if (isYourTurn) {
lines.push(
"👉 **It's YOUR TURN.**",
game.data.turnExpiresAt
? `⏱️ Auto-folds <t:${game.data.turnExpiresAt}:R>.`
: "⏱️ Your turn timer is active."
);
}
else if (currentPlayer) {
lines.push(
`Waiting for <@${currentPlayer.userId}> to act.`
);
}
else {
lines.push(
"The hand is not currently waiting on a player action."
);
}
await interaction.editReply({
content:
lines.join("\n"),
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. Cards have been dealt — use **🃏 Cards** to view your hand.`,
embeds: [],
components: []
});
}
catch (error) {
return editPokerError(
interaction,
error
);
}
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildAchievementsEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileAchievements",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildAchievementsEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
0
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"achievements",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildAchievementsEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileAchievementsLeft",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildAchievementsEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page - 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"achievements",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildAchievementsEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileAchievementsRight",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildAchievementsEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page + 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"achievements",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildBadgesEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileBadges",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildBadgesEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
0
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"badges",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildBadgesEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileBadgesLeft",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildBadgesEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page - 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"badges",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const {
buildBadgesEmbed,
buildProfileButtons,
getProfileContextFromFooter,
requireProfileOwner
} = require(
"../../utils/profileSystem"
);
module.exports = {
name:
"profileBadgesRight",
async execute(
interaction
) {
const context =
await requireProfileOwner(
interaction
);
if (!context) {
return;
}
const user =
await interaction.client.users.fetch(
context.profileUserId
);
const embed =
await buildBadgesEmbed(
interaction.guildId,
user,
context.sessionOwnerId,
context.page + 1
);
const pageContext =
getProfileContextFromFooter(
embed.data.footer?.text
);
return interaction.update({
embeds: [embed],
components:
buildProfileButtons(
"badges",
pageContext?.page ?? 0,
pageContext?.pageCount ?? 1
)
});
}
};

Some files were not shown because too many files have changed in this diff Show More