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 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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 3362f9fbbe0adac111e2e608d8b66d419f076a48 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 3 Apr 2021 21:35:55 -0500 Subject: [PATCH 07/13] Prototyped stream notifications like CheeseBot --- src/commands/admin.ts | 20 ++++++++ src/commands/utilities/streaminfo.ts | 18 +++++++ src/core/structures.ts | 2 + src/events/channelUpdate.ts | 14 ++++++ src/events/voiceStateUpdate.ts | 74 ++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 src/commands/utilities/streaminfo.ts create mode 100644 src/events/channelUpdate.ts create mode 100644 src/events/voiceStateUpdate.ts diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 2622087..e78068f 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -164,6 +164,26 @@ export default new Command({ }) }) } + }), + stream: new Command({ + description: "Set a channel to send stream notifications.", + async run($) { + if ($.guild) { + const guild = Storage.getGuild($.guild.id); + + if (guild.streamingChannel) { + guild.streamingChannel = null; + $.channel.send("Removed your server's stream notifications channel."); + } else { + guild.streamingChannel = $.channel.id; + $.channel.send(`Set your server's stream notifications channel to ${$.channel}.`); + } + + Storage.save(); + } else { + $.channel.send("You must use this command in a server."); + } + } }) } }), diff --git a/src/commands/utilities/streaminfo.ts b/src/commands/utilities/streaminfo.ts new file mode 100644 index 0000000..e4ea9aa --- /dev/null +++ b/src/commands/utilities/streaminfo.ts @@ -0,0 +1,18 @@ +import Command from "../../core/command"; +import {streamList, getStreamEmbed} from "../../events/voiceStateUpdate"; + +export default new Command({ + description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", + async run($) { + const userID = $.author.id; + + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.description = $.args.join(" ") || undefined; + stream.message.edit(getStreamEmbed(stream.streamer, stream.channel, stream.description)); + } else { + // Alternatively, I could make descriptions last outside of just one stream. + $.channel.send("You can only use this command when streaming."); + } + } +}); diff --git a/src/core/structures.ts b/src/core/structures.ts index 13bdc9f..0e77494 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -58,11 +58,13 @@ class Guild { public welcomeType: "none" | "text" | "graphical"; public welcomeChannel: string | null; public welcomeMessage: string | null; + public streamingChannel: string | null; constructor(data?: GenericJSON) { this.prefix = select(data?.prefix, null, String); this.welcomeChannel = select(data?.welcomeChannel, null, String); this.welcomeMessage = select(data?.welcomeMessage, null, String); + this.streamingChannel = select(data?.streamingChannel, null, String); switch (data?.welcomeType) { case "text": diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts new file mode 100644 index 0000000..0b325c9 --- /dev/null +++ b/src/events/channelUpdate.ts @@ -0,0 +1,14 @@ +import Event from "../core/event"; +import {streamList, getStreamEmbed} from "./voiceStateUpdate"; + +export default new Event<"channelUpdate">({ + async on(before, after) { + if (before.type === "voice" && after.type === "voice") { + for (const {streamer, channel, description, message} of streamList.values()) { + if (after.id === channel.id) { + message.edit(getStreamEmbed(streamer, channel, description)); + } + } + } + } +}); diff --git a/src/events/voiceStateUpdate.ts b/src/events/voiceStateUpdate.ts new file mode 100644 index 0000000..ded1a5a --- /dev/null +++ b/src/events/voiceStateUpdate.ts @@ -0,0 +1,74 @@ +import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Permissions, Message, Collection} from "discord.js"; +import Event from "../core/event"; +import $ from "../core/lib"; +import {Storage} from "../core/structures"; +import {client} from "../index"; + +type Stream = { + streamer: GuildMember; + channel: VoiceChannel; + description?: string; + message: Message; +}; + +// A list of user IDs and message embeds. +export const streamList = new Collection(); + +// Probably find a better, DRY way of doing this. +export function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { + const user = streamer.user; + const embed = new MessageEmbed() + .setTitle(`Stream: \`#${channel.name}\``) + .setAuthor( + streamer.nickname ?? user.username, + user.avatarURL({ + dynamic: true, + format: "png" + }) ?? user.defaultAvatarURL + ) + .setColor(streamer.displayColor); + + if (description) { + embed.setDescription(description); + } + + return embed; +} + +export default new Event<"voiceStateUpdate">({ + async on(before, after) { + const isStartStreamEvent = !before.streaming && after.streaming; + const isStopStreamEvent = before.streaming && (!after.streaming || !after.channel); // If you were streaming before but now are either not streaming or have left the channel. + // Note: isStopStreamEvent can be called twice in a row - If Discord crashes/quits while you're streaming, it'll call once with a null channel and a second time with a channel. + + if (isStartStreamEvent || isStopStreamEvent) { + const {streamingChannel} = Storage.getGuild(after.guild.id); + + if (streamingChannel) { + const member = after.member!; + const voiceChannel = after.channel!; + const textChannel = client.channels.cache.get(streamingChannel); + + if (textChannel && textChannel instanceof TextChannel) { + if (isStartStreamEvent) { + streamList.set(member.id, { + streamer: member, + channel: voiceChannel, + message: await textChannel.send(getStreamEmbed(member, voiceChannel)) + }); + } else if (isStopStreamEvent) { + if (streamList.has(member.id)) { + const {message} = streamList.get(member.id)!; + message.delete(); + streamList.delete(member.id); + } + } + } else { + $.error( + `The streaming notifications channel ${streamingChannel} for guild ${after.guild.id} either doesn't exist or isn't a text channel.` + ); + } + } + } + } +}); From 678485160e3f42052a5d2ba7c6c9daf94b6fc5a4 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 20:55:21 -0500 Subject: [PATCH 08/13] Added optional channel target for setting channel --- src/commands/admin.ts | 25 +++++++++++++++++++++++-- src/commands/utilities/streaminfo.ts | 6 +++--- src/events/channelUpdate.ts | 8 ++++---- src/events/voiceStateUpdate.ts | 10 +++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e78068f..40c2242 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -166,7 +166,8 @@ export default new Command({ } }), stream: new Command({ - description: "Set a channel to send stream notifications.", + description: "Set a channel to send stream notifications. Type `#` to reference the channel.", + usage: "()", async run($) { if ($.guild) { const guild = Storage.getGuild($.guild.id); @@ -183,7 +184,27 @@ export default new Command({ } else { $.channel.send("You must use this command in a server."); } - } + }, + // If/when channel types come out, this will be the perfect candidate to test it. + any: new Command({ + async run($) { + if ($.guild) { + const match = $.args[0].match(/^<#(\d{17,19})>$/); + + if (match) { + Storage.getGuild($.guild.id).streamingChannel = match[1]; + Storage.save(); + $.channel.send(`Successfully set this server's welcome channel to ${match[0]}.`); + } else { + $.channel.send( + "You must provide a reference channel. You can do this by typing `#` then searching for the proper channel." + ); + } + } else { + $.channel.send("You must use this command in a server."); + } + } + }) }) } }), diff --git a/src/commands/utilities/streaminfo.ts b/src/commands/utilities/streaminfo.ts index e4ea9aa..7d90785 100644 --- a/src/commands/utilities/streaminfo.ts +++ b/src/commands/utilities/streaminfo.ts @@ -1,5 +1,5 @@ import Command from "../../core/command"; -import {streamList, getStreamEmbed} from "../../events/voiceStateUpdate"; +import {streamList} from "../../events/voiceStateUpdate"; export default new Command({ description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", @@ -8,8 +8,8 @@ export default new Command({ if (streamList.has(userID)) { const stream = streamList.get(userID)!; - stream.description = $.args.join(" ") || undefined; - stream.message.edit(getStreamEmbed(stream.streamer, stream.channel, stream.description)); + stream.description = $.args.join(" ") || "No description set."; + stream.update(); } else { // Alternatively, I could make descriptions last outside of just one stream. $.channel.send("You can only use this command when streaming."); diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts index 0b325c9..7d49789 100644 --- a/src/events/channelUpdate.ts +++ b/src/events/channelUpdate.ts @@ -1,12 +1,12 @@ import Event from "../core/event"; -import {streamList, getStreamEmbed} from "./voiceStateUpdate"; +import {streamList} from "./voiceStateUpdate"; export default new Event<"channelUpdate">({ async on(before, after) { if (before.type === "voice" && after.type === "voice") { - for (const {streamer, channel, description, message} of streamList.values()) { - if (after.id === channel.id) { - message.edit(getStreamEmbed(streamer, channel, description)); + for (const stream of streamList.values()) { + if (after.id === stream.channel.id) { + stream.update(); } } } diff --git a/src/events/voiceStateUpdate.ts b/src/events/voiceStateUpdate.ts index ded1a5a..54b90d4 100644 --- a/src/events/voiceStateUpdate.ts +++ b/src/events/voiceStateUpdate.ts @@ -9,13 +9,14 @@ type Stream = { channel: VoiceChannel; description?: string; message: Message; + update: () => void; }; // A list of user IDs and message embeds. export const streamList = new Collection(); // Probably find a better, DRY way of doing this. -export function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { +function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { const user = streamer.user; const embed = new MessageEmbed() .setTitle(`Stream: \`#${channel.name}\``) @@ -49,12 +50,15 @@ export default new Event<"voiceStateUpdate">({ const voiceChannel = after.channel!; const textChannel = client.channels.cache.get(streamingChannel); - if (textChannel && textChannel instanceof TextChannel) { + if (textChannel instanceof TextChannel) { if (isStartStreamEvent) { streamList.set(member.id, { streamer: member, channel: voiceChannel, - message: await textChannel.send(getStreamEmbed(member, voiceChannel)) + message: await textChannel.send(getStreamEmbed(member, voiceChannel)), + update(this: Stream) { + this.message.edit(getStreamEmbed(this.streamer, this.channel, this.description)); + } }); } else if (isStopStreamEvent) { if (streamList.has(member.id)) { 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 09/13] 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 10/13] 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 11/13] 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.] From c82083af5d1e604c8b50ded467dceefec8674270 Mon Sep 17 00:00:00 2001 From: Mijyuoon Date: Tue, 6 Apr 2021 18:24:42 +0300 Subject: [PATCH 12/13] Added an entry that should've been added --- src/commands/fun/whois.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index d37e674..a218d97 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -31,6 +31,7 @@ const registry: {[id: string]: string} = { "440399719076855818": "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names.", "243061915281129472": "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~", + "792751612904603668": "Some random nyan. :3 ~~May also secretly be a conlanger, worldbuilder and programmer doofus.~~", "367439475153829892": "A weeb.", "760375501775700038": "˙qǝǝʍ ∀", "389178357302034442": "In his dreams, he is the star. its him. <:itsMe:808174425253871657>", From fae06f66d4ebcb1a0630e0742059a74e436499b1 Mon Sep 17 00:00:00 2001 From: EL2020 Date: Tue, 6 Apr 2021 21:04:27 -0400 Subject: [PATCH 13/13] added functionality for reacting to in-line replies --- src/commands/utilities/react.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index ceef800..49648aa 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -10,8 +10,19 @@ export default new Command({ async run($: CommonLibrary): Promise { let target: Message | undefined; let distance = 1; - - if ($.args.length >= 2) { + + // allows reactions by using an in-line reply + if($.message.reference){ + const messageID = $.message.reference.messageID; + try{ + target = await $.channel.messages.fetch(messageID!) + }catch{ + return $.channel.send("Unknown error occurred!") + } + } + + // handles reacts by message id/distance + else if ($.args.length >= 2) { const last = $.args[$.args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/;