From 7105c6284f7583a09fc6d1f9b6ceb5ae6f6e2093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Mon, 25 Jan 2021 23:06:12 +0100 Subject: [PATCH 1/9] Add 'bet' command to the 'eco' module --- src/commands/fun/eco.ts | 4 +- src/commands/fun/subcommands/eco-bet.ts | 137 ++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/commands/fun/subcommands/eco-bet.ts diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 5be316f..557b2aa 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -3,6 +3,7 @@ import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; import {MondayCommand} from "./subcommands/eco-extras"; +import {BetCommand} from "./subcommands/eco-bet"; export default new Command({ description: "Economy command for Monika.", @@ -16,7 +17,8 @@ export default new Command({ leaderboard: LeaderboardCommand, buy: BuyCommand, shop: ShopCommand, - monday: MondayCommand + monday: MondayCommand, + bet: BetCommand }, user: new Command({ description: "See how much money someone else has by using their user ID or pinging them.", diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts new file mode 100644 index 0000000..5046196 --- /dev/null +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -0,0 +1,137 @@ +import Command from "../../../core/command"; +import $ from "../../../core/lib"; +import {Storage} from "../../../core/structures"; +import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; + +export const BetCommand = new Command({ + description: "Bet your Mons with other people [TBD]", + usage: " ", + run: "Who are you betting with?", + user: new Command({ + run: "How much are you betting?", + number: new Command({ + run: "How long until the bet ends?", + any: new Command({ + async run({ client, args, author, channel, guild}): Promise { + if (isAuthorized(guild, channel)) { + const sender = Storage.getUser(author.id); + const target = args[0]; + const receiver = Storage.getUser(target); + const amount = Math.floor(args[1]); + const duration = parseDuration(args[2].trim()); + + if (amount <= 0) return channel.send("You must bet at least one Mon!"); + else if (sender.money < amount) + return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") + return channel.send("You can't bet Mons with a bot!"); + + if (duration <= 0) + return channel.send("Invalid duration"); + // else if (duration <= {threshold}) + // return channel.send("Too short idk"); + // else if (duration >= {threshold}) + // return channel.send("Too long idk"); + + // SEND MESSAGE WITH 2 REACTIONS (OK / NO) + const msg = await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`); + await msg.react("✅"); + await msg.react("⛔"); + + // SET UP ACCEPT TIMEOUT + // ON REACTION CHANGE, CHECK IF NEW REACTION IS FROM TARGET + await msg.awaitReactions( + async (reaction, user) => { + // IF OK + if (user.id === target.id && reaction.emoji.name === "✅") { + // REMOVE AMOUNT FROM AUTHOR + sender.money -= amount; + // REMOVE AMOUNT FROM TARGET + receiver.money -= amount; + // SET BET POOL AS AMOUNT*2 + // => BET POOL ALWAYS EVEN + const pool = amount * 2; + + // SET UP BET TIMEOUT FROM DURATION + client.setTimeout(async () => { + // ON BET TIMEOUT + // GIVE VOTE WITH 2 REACTIONS (OK / NO) + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id} has won the bet?`); + await voteMsg.react("✅"); + await voteMsg.react("⛔"); + + // SET UP VOTE TIMEOUT + voteMsg.awaitReactions( + (reaction, user) => { + return ["✅", "⛔"].includes(reaction.emoji.name); + }, + // waiting for a day for now, might need to make configurable + { time: 8640000 } + ).then(reactions => { + // ON VOTE TIMEOUT + // COUNT OK VOTES + const ok = 0; + // COUNT NO VOTES + const no = 0; + // IF OK > NO + // GIVE TARGET THE BET POOL + if (ok > no) receiver.money += pool; + // ELSE IF OK < NO + // GIVE AUTHOR BET POOL + else if (ok < no) sender.money += pool; + // ELSE + // GIVE TARGET BET POOL / 2 + // GIVE AUTHOR BET POOL / 2 + // => BET EFFECT CANCELLED + else { + sender.money += amount; + receiver.money += amount; + } + }); + }, duration); + } + // IF NO + // DROP + return false; + }, + // waiting for a minute + { time: 60000 } + ); + // ON ACCEPT TIMEOUT + // DROP + } + } + }) + }) + }) +}); + + +/** + * Parses a duration string into milliseconds + * Examples: + * - 3d -> 3 days -> 259200000ms + * - 3s -> 3 seconds -> 3000ms + * - 2h -> 2 hours -> 7200000ms + */ +function parseDuration(duration: string): number { + // extract last char as unit + const unit = duration[duration.length - 1].toLowerCase(); + // get the rest as value + let value: number = +duration.substring(0, duration.length - 1); + + if (!["d","h","m","s"].includes(unit) || isNaN(value)) + return 0; + + if (unit === "d") + value *= 86400000; // 1000ms * 60s * 60m * 24h + else if (unit === "h") + value *= 3600000; // 1000ms * 60s * 60m + else if (unit === "m") + value *= 60000; // 1000ms * 60s + else if (unit === "s") + value *= 1000; // 1000ms + + return value; +} \ No newline at end of file From 38eb0906eeec80c2405cc0736195e149da0bccee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 26 Jan 2021 16:28:04 +0100 Subject: [PATCH 2/9] eco/bet: Clean up comments and add feedback --- src/commands/fun/subcommands/eco-bet.ts | 135 +++++++++++++++--------- 1 file changed, 88 insertions(+), 47 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 5046196..5329b49 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -8,11 +8,46 @@ export const BetCommand = new Command({ usage: " ", run: "Who are you betting with?", user: new Command({ - run: "How much are you betting?", + // handles missing amount argument + async run({args, author, channel, guild}): Promise { + if (isAuthorized(guild, channel)) { + const target = args[0]; + + // handle invalid target + if (target.id == author.id) + return channel.send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") + return channel.send("You can't bet Mons with a bot!"); + + return channel.send("How much are you betting?"); + } + }, number: new Command({ - run: "How long until the bet ends?", + // handles missing duration argument + async run({args, author, channel, guild}): Promise { + if (isAuthorized(guild, channel)) { + const sender = Storage.getUser(author.id); + const target = args[0]; + const receiver = Storage.getUser(target); + const amount = Math.floor(args[1]); + + // handle invalid target + if (target.id == author.id) + return channel.send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") + return channel.send("You can't bet Mons with a bot!"); + + // handle invalid amount + if (amount <= 0) + return channel.send("You must bet at least one Mon!"); + else if (sender.money < amount) + return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + + return channel.send("How long until the bet ends?"); + } + }, any: new Command({ - async run({ client, args, author, channel, guild}): Promise { + async run({client, args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { const sender = Storage.getUser(author.id); const target = args[0]; @@ -20,13 +55,19 @@ export const BetCommand = new Command({ const amount = Math.floor(args[1]); const duration = parseDuration(args[2].trim()); - if (amount <= 0) return channel.send("You must bet at least one Mon!"); - else if (sender.money < amount) - return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); - else if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); + // handle invalid target + if (target.id == author.id) + return channel.send("You can't bet Mons with yourself!"); else if (target.bot && process.argv[2] !== "dev") return channel.send("You can't bet Mons with a bot!"); + // handle invalid amount + if (amount <= 0) + return channel.send("You must bet at least one Mon!"); + else if (sender.money < amount) + return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + + // handle invalid duration if (duration <= 0) return channel.send("Invalid duration"); // else if (duration <= {threshold}) @@ -34,72 +75,71 @@ export const BetCommand = new Command({ // else if (duration >= {threshold}) // return channel.send("Too long idk"); - // SEND MESSAGE WITH 2 REACTIONS (OK / NO) + // [Potential pertinence to use the ask later on?] + // Ask target whether or not they want to take the bet. const msg = await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`); await msg.react("✅"); await msg.react("⛔"); - // SET UP ACCEPT TIMEOUT - // ON REACTION CHANGE, CHECK IF NEW REACTION IS FROM TARGET - await msg.awaitReactions( + // Wait for a reaction. + msg.awaitReactions( async (reaction, user) => { - // IF OK + // If target accepts: set bet up. if (user.id === target.id && reaction.emoji.name === "✅") { - // REMOVE AMOUNT FROM AUTHOR + // [ISSUE: volatile storage] + // Remove amount money from both parts to avoid duplication of money. sender.money -= amount; - // REMOVE AMOUNT FROM TARGET receiver.money -= amount; - // SET BET POOL AS AMOUNT*2 - // => BET POOL ALWAYS EVEN - const pool = amount * 2; - // SET UP BET TIMEOUT FROM DURATION + // Notify both users. + await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); + + // Wait for the duration of the bet. client.setTimeout(async () => { - // ON BET TIMEOUT - // GIVE VOTE WITH 2 REACTIONS (OK / NO) - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id} has won the bet?`); + // [Pertinence to reference the invocation message to let people find the bet more easily] + // When bet is over, give a vote to ask people their thoughts. + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); await voteMsg.react("✅"); await voteMsg.react("⛔"); - // SET UP VOTE TIMEOUT + // Filter reactions to only collect the pertinent ones. voteMsg.awaitReactions( (reaction, user) => { return ["✅", "⛔"].includes(reaction.emoji.name); }, - // waiting for a day for now, might need to make configurable - { time: 8640000 } + // [Pertinence to make configurable on the fly.] + { time: parseDuration("2m") } ).then(reactions => { - // ON VOTE TIMEOUT - // COUNT OK VOTES - const ok = 0; - // COUNT NO VOTES - const no = 0; - // IF OK > NO - // GIVE TARGET THE BET POOL - if (ok > no) receiver.money += pool; - // ELSE IF OK < NO - // GIVE AUTHOR BET POOL - else if (ok < no) sender.money += pool; - // ELSE - // GIVE TARGET BET POOL / 2 - // GIVE AUTHOR BET POOL / 2 - // => BET EFFECT CANCELLED + // Count votes + const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; + const no = reactions.filter(reaction => reaction.emoji.name === "⛔").size; + + if (ok > no) { + receiver.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.`); + } + else if (ok < no) { + sender.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.`); + } else { sender.money += amount; receiver.money += amount; + channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } }); }, duration); } - // IF NO - // DROP + // If target refuses: notify and stop. + else if (user.id === target.id && reaction.emoji.name === "⛔") + await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); + return false; }, - // waiting for a minute + // [Lesser pertinence to make configurable on the fly.] + // Wait for a minute, and delete the asking message after that. { time: 60000 } - ); - // ON ACCEPT TIMEOUT - // DROP + ).then(() => msg.delete()); } } }) @@ -111,9 +151,10 @@ export const BetCommand = new Command({ /** * Parses a duration string into milliseconds * Examples: - * - 3d -> 3 days -> 259200000ms + * - 3d -> 3 days -> 259200000ms + * - 2h -> 2 hours -> 7200000ms + * - 7m -> 7 minutes -> 420000ms * - 3s -> 3 seconds -> 3000ms - * - 2h -> 2 hours -> 7200000ms */ function parseDuration(duration: string): number { // extract last char as unit From 499aea9e66479f6ada569e8c34251daaff0a8ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 26 Jan 2021 18:11:50 +0100 Subject: [PATCH 3/9] eco-bet: save storage modification --- src/commands/fun/subcommands/eco-bet.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 5329b49..12f0191 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -90,6 +90,7 @@ export const BetCommand = new Command({ // Remove amount money from both parts to avoid duplication of money. sender.money -= amount; receiver.money -= amount; + Storage.save(); // Notify both users. await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); @@ -127,6 +128,7 @@ export const BetCommand = new Command({ receiver.money += amount; channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } + Storage.save(); }); }, duration); } From 055a57e928630897afc15ab36f5c83e94f4a653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 26 Jan 2021 19:37:31 +0100 Subject: [PATCH 4/9] eco-bet: use askYesOrNo to clean up --- src/commands/fun/subcommands/eco-bet.ts | 116 +++++++++++------------- 1 file changed, 52 insertions(+), 64 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 12f0191..492da12 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -75,73 +75,61 @@ export const BetCommand = new Command({ // else if (duration >= {threshold}) // return channel.send("Too long idk"); - // [Potential pertinence to use the ask later on?] // Ask target whether or not they want to take the bet. - const msg = await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`); - await msg.react("✅"); - await msg.react("⛔"); + const takeBet = await askYesOrNo( + await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`), + target.id + ); - // Wait for a reaction. - msg.awaitReactions( - async (reaction, user) => { - // If target accepts: set bet up. - if (user.id === target.id && reaction.emoji.name === "✅") { - // [ISSUE: volatile storage] - // Remove amount money from both parts to avoid duplication of money. - sender.money -= amount; - receiver.money -= amount; + if (takeBet) { + // [ISSUE: volatile storage] + // Remove amount money from both parts to avoid duplication of money. + sender.money -= amount; + receiver.money -= amount; + Storage.save(); + + // Notify both users. + await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); + + // Wait for the duration of the bet. + client.setTimeout(async () => { + // [Pertinence to reference the invocation message to let people find the bet more easily] + // When bet is over, give a vote to ask people their thoughts. + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); + await voteMsg.react("✅"); + await voteMsg.react("❌"); + + // Filter reactions to only collect the pertinent ones. + voteMsg.awaitReactions( + (reaction, user) => { + return ["✅", "❌"].includes(reaction.emoji.name); + }, + // [Pertinence to make configurable on the fly.] + { time: parseDuration("2m") } + ).then(reactions => { + // Count votes + const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; + const no = reactions.filter(reaction => reaction.emoji.name === "❌").size; + + if (ok > no) { + receiver.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.`); + } + else if (ok < no) { + sender.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.`); + } + else { + sender.money += amount; + receiver.money += amount; + channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); + } Storage.save(); - - // Notify both users. - await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); - - // Wait for the duration of the bet. - client.setTimeout(async () => { - // [Pertinence to reference the invocation message to let people find the bet more easily] - // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); - await voteMsg.react("✅"); - await voteMsg.react("⛔"); - - // Filter reactions to only collect the pertinent ones. - voteMsg.awaitReactions( - (reaction, user) => { - return ["✅", "⛔"].includes(reaction.emoji.name); - }, - // [Pertinence to make configurable on the fly.] - { time: parseDuration("2m") } - ).then(reactions => { - // Count votes - const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; - const no = reactions.filter(reaction => reaction.emoji.name === "⛔").size; - - if (ok > no) { - receiver.money += amount * 2; - channel.send(`By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.`); - } - else if (ok < no) { - sender.money += amount * 2; - channel.send(`By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.`); - } - else { - sender.money += amount; - receiver.money += amount; - channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); - } - Storage.save(); - }); - }, duration); - } - // If target refuses: notify and stop. - else if (user.id === target.id && reaction.emoji.name === "⛔") - await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); - - return false; - }, - // [Lesser pertinence to make configurable on the fly.] - // Wait for a minute, and delete the asking message after that. - { time: 60000 } - ).then(() => msg.delete()); + }); + }, duration); + } + else + await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); } } }) From d6548c53dbb9c8232422ab960578f6eb59661ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Sun, 21 Feb 2021 14:11:25 +0100 Subject: [PATCH 5/9] eco-bet: improvements - duration bounds - link to calling message --- src/commands/fun/subcommands/eco-bet.ts | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 492da12..556054c 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -47,8 +47,12 @@ export const BetCommand = new Command({ } }, any: new Command({ - async run({client, args, author, channel, guild}): Promise { + async run({client, args, author, message, channel, guild}): Promise { if (isAuthorized(guild, channel)) { + // [Pertinence to make configurable on the fly.] + // Lower and upper bounds for bet + const durationBounds = { min:"1m", max:"1d" }; + const sender = Storage.getUser(author.id); const target = args[0]; const receiver = Storage.getUser(target); @@ -69,11 +73,11 @@ export const BetCommand = new Command({ // handle invalid duration if (duration <= 0) - return channel.send("Invalid duration"); - // else if (duration <= {threshold}) - // return channel.send("Too short idk"); - // else if (duration >= {threshold}) - // return channel.send("Too long idk"); + return channel.send("Invalid bet duration"); + else if (duration <= parseDuration(durationBounds.min)) + return channel.send(`Bet duration is too short, maximum duration is ${durationBounds.min}`); + else if (duration >= parseDuration(durationBounds.max)) + return channel.send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); // Ask target whether or not they want to take the bet. const takeBet = await askYesOrNo( @@ -82,8 +86,8 @@ export const BetCommand = new Command({ ); if (takeBet) { - // [ISSUE: volatile storage] - // Remove amount money from both parts to avoid duplication of money. + // [MEDIUM PRIORITY: bet persistence to prevent losses in case of shutdown.] + // Remove amount money from both parts at the start to avoid duplication of money. sender.money -= amount; receiver.money -= amount; Storage.save(); @@ -93,9 +97,9 @@ export const BetCommand = new Command({ // Wait for the duration of the bet. client.setTimeout(async () => { - // [Pertinence to reference the invocation message to let people find the bet more easily] + // [TODO: when D.JSv13 comes out, inline reply to clean up.] // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?\nhttps://discord.com/channels/${guild.id}/${channel.id}/${message.id}`); await voteMsg.react("✅"); await voteMsg.react("❌"); @@ -165,4 +169,4 @@ function parseDuration(duration: string): number { value *= 1000; // 1000ms return value; -} \ No newline at end of file +} From c71406a8d081965667702d73032f72af98a268a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 23 Feb 2021 23:25:28 +0100 Subject: [PATCH 6/9] eco-bet: added a check for bet target's money --- src/commands/fun/subcommands/eco-bet.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 556054c..edce702 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -42,6 +42,8 @@ export const BetCommand = new Command({ return channel.send("You must bet at least one Mon!"); else if (sender.money < amount) return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (receiver.money < amount) + return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); return channel.send("How long until the bet ends?"); } @@ -70,6 +72,8 @@ export const BetCommand = new Command({ return channel.send("You must bet at least one Mon!"); else if (sender.money < amount) return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (receiver.money < amount) + return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); // handle invalid duration if (duration <= 0) From 1351f3250bf9845532d7a44ca5be4ffbeb15f9d3 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 22:57:03 -0500 Subject: [PATCH 7/9] Added hacky persistence and bug fixes on eco bet --- src/commands/fun/subcommands/eco-bet.ts | 33 ++++++++++++++++--------- src/core/structures.ts | 2 ++ src/events/ready.ts | 14 ++++++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index edce702..68087c2 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -2,6 +2,7 @@ import Command from "../../../core/command"; import $ from "../../../core/lib"; import {Storage} from "../../../core/structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; +import {User} from "discord.js"; export const BetCommand = new Command({ description: "Bet your Mons with other people [TBD]", @@ -27,8 +28,8 @@ export const BetCommand = new Command({ async run({args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { const sender = Storage.getUser(author.id); - const target = args[0]; - const receiver = Storage.getUser(target); + const target = args[0] as User; + const receiver = Storage.getUser(target.id); const amount = Math.floor(args[1]); // handle invalid target @@ -49,15 +50,15 @@ export const BetCommand = new Command({ } }, any: new Command({ - async run({client, args, author, message, channel, guild}): Promise { + async run({client, args, author, message, channel, guild, askYesOrNo}): Promise { if (isAuthorized(guild, channel)) { // [Pertinence to make configurable on the fly.] // Lower and upper bounds for bet const durationBounds = { min:"1m", max:"1d" }; const sender = Storage.getUser(author.id); - const target = args[0]; - const receiver = Storage.getUser(target); + const target = args[0] as User; + const receiver = Storage.getUser(target.id); const amount = Math.floor(args[1]); const duration = parseDuration(args[2].trim()); @@ -94,16 +95,22 @@ export const BetCommand = new Command({ // Remove amount money from both parts at the start to avoid duplication of money. sender.money -= amount; receiver.money -= amount; + // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code. + sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; + receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; Storage.save(); // Notify both users. await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); - // Wait for the duration of the bet. + // Wait for the duration of the bet. client.setTimeout(async () => { + // In debug mode, saving the storage will break the references, so you have to redeclare sender and receiver for it to actually save. + const sender = Storage.getUser(author.id); + const receiver = Storage.getUser(target.id); // [TODO: when D.JSv13 comes out, inline reply to clean up.] // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?\nhttps://discord.com/channels/${guild.id}/${channel.id}/${message.id}`); + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${message.id}`); await voteMsg.react("✅"); await voteMsg.react("❌"); @@ -115,9 +122,11 @@ export const BetCommand = new Command({ // [Pertinence to make configurable on the fly.] { time: parseDuration("2m") } ).then(reactions => { - // Count votes - const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; - const no = reactions.filter(reaction => reaction.emoji.name === "❌").size; + // Count votes + const okReaction = reactions.get("✅"); + const noReaction = reactions.get("❌"); + const ok = okReaction ? (okReaction.count ?? 1) - 1 : 0; + const no = noReaction ? (noReaction.count ?? 1) - 1 : 0; if (ok > no) { receiver.money += amount * 2; @@ -132,11 +141,13 @@ export const BetCommand = new Command({ receiver.money += amount; channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } + sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; + receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; Storage.save(); }); }, duration); } - else + else await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); } } diff --git a/src/core/structures.ts b/src/core/structures.ts index 37b2194..6a4aa94 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -26,6 +26,7 @@ class User { public lastMonday: number; public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone public daylightSavingsRegion: "na" | "eu" | "sh" | null; + public quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled: number; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); @@ -35,6 +36,7 @@ class User { this.daylightSavingsRegion = /^((na)|(eu)|(sh))$/.test(data?.daylightSavingsRegion) ? data?.daylightSavingsRegion : null; + this.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = select(data?.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled, 0, Number); } } diff --git a/src/events/ready.ts b/src/events/ready.ts index e3fe021..561d33a 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,7 +1,7 @@ import Event from "../core/event"; import {client} from "../index"; import $ from "../core/lib"; -import {Config} from "../core/structures"; +import {Config, Storage} from "../core/structures"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"ready">({ @@ -14,5 +14,17 @@ export default new Event<"ready">({ }); } updateGlobalEmoteRegistry(); + + // Run this setup block once to restore eco bet money in case the bot went down. (And I guess search the client for those users to let them know too.) + for (const id in Storage.users) { + const user = Storage.users[id]; + + if(user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled > 0) { + client.users.cache.get(id)?.send(`Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled}\`, has been restored.`); + user.money += user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled; + user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = 0; + } + } + Storage.save(); } }); From dc33dbd180f80bed551d6f0ef0abed8d70cd1a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 6 Apr 2021 08:02:52 +0200 Subject: [PATCH 8/9] Rename insurance field --- src/commands/fun/subcommands/eco-bet.ts | 8 ++++---- src/core/structures.ts | 4 ++-- src/events/ready.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 68087c2..ca8d455 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -96,8 +96,8 @@ export const BetCommand = new Command({ sender.money -= amount; receiver.money -= amount; // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code. - sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; - receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; + sender.ecoBetInsurance += amount; + receiver.ecoBetInsurance += amount; Storage.save(); // Notify both users. @@ -141,8 +141,8 @@ export const BetCommand = new Command({ receiver.money += amount; channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } - sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; - receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; + sender.ecoBetInsurance -= amount; + receiver.ecoBetInsurance -= amount; Storage.save(); }); }, duration); diff --git a/src/core/structures.ts b/src/core/structures.ts index 6a4aa94..e302196 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -26,7 +26,7 @@ class User { public lastMonday: number; public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone public daylightSavingsRegion: "na" | "eu" | "sh" | null; - public quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled: number; + public ecoBetInsurance: number; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); @@ -36,7 +36,7 @@ class User { this.daylightSavingsRegion = /^((na)|(eu)|(sh))$/.test(data?.daylightSavingsRegion) ? data?.daylightSavingsRegion : null; - this.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = select(data?.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled, 0, Number); + this.ecoBetInsurance = select(data?.ecoBetInsurance, 0, Number); } } diff --git a/src/events/ready.ts b/src/events/ready.ts index 561d33a..e74d3fe 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -19,10 +19,10 @@ export default new Event<"ready">({ for (const id in Storage.users) { const user = Storage.users[id]; - if(user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled > 0) { - client.users.cache.get(id)?.send(`Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled}\`, has been restored.`); - user.money += user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled; - user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = 0; + if(user.ecoBetInsurance > 0) { + client.users.cache.get(id)?.send(`Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.ecoBetInsurance}\`, has been restored.`); + user.money += user.ecoBetInsurance; + user.ecoBetInsurance = 0; } } Storage.save(); From fd136dc34f57ad5c483ccbe77d06c9877553de9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 6 Apr 2021 08:39:11 +0200 Subject: [PATCH 9/9] Finally added description fields --- src/commands/fun/subcommands/eco-bet.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index ca8d455..01c6c96 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -5,10 +5,11 @@ import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco- import {User} from "discord.js"; export const BetCommand = new Command({ - description: "Bet your Mons with other people [TBD]", + description: "Bet your Mons with other people.", usage: " ", run: "Who are you betting with?", user: new Command({ + description: "User to bet with.", // handles missing amount argument async run({args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { @@ -24,6 +25,7 @@ export const BetCommand = new Command({ } }, number: new Command({ + description: "Amount of Mons to bet.", // handles missing duration argument async run({args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { @@ -50,6 +52,7 @@ export const BetCommand = new Command({ } }, any: new Command({ + description: "Duration of the bet.", async run({client, args, author, message, channel, guild, askYesOrNo}): Promise { if (isAuthorized(guild, channel)) { // [Pertinence to make configurable on the fly.]