From 19be26378a4f3afb6af1387bb022b3c498d128a5 Mon Sep 17 00:00:00 2001 From: Sammy Israwi Date: Thu, 21 May 2020 00:29:17 -0700 Subject: [PATCH 01/15] Add roll capabilities to Fred --- package.json | 3 +- src/commands/index.ts | 1 + src/commands/pubSub.ts | 2 +- src/commands/roll.ts | 74 ++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 14 +++++++- 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/commands/roll.ts diff --git a/package.json b/package.json index 507ef6b..8754378 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "start": "node ./output/index.js", "dev": "nodemon ./output/index.js", "format": "prettier --write ./src", - "build": "tsc" + "build": "tsc", + "build:watch": "tsc --watch" }, "dependencies": { "@types/node-fetch": "^2.5.7", diff --git a/src/commands/index.ts b/src/commands/index.ts index ebdf3fd..ade1d3f 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -2,3 +2,4 @@ export * from "./ping"; export * from "./pubSub"; +export * from "./roll"; diff --git a/src/commands/pubSub.ts b/src/commands/pubSub.ts index bfd8017..459baa5 100644 --- a/src/commands/pubSub.ts +++ b/src/commands/pubSub.ts @@ -39,7 +39,7 @@ export function pubSub(message: Discord.Message): Promise { } }) .catch((error) => { - console.log(error); + console.error(error); message.channel.send("Shits broke, Im not fixing it. probably."); }); } diff --git a/src/commands/roll.ts b/src/commands/roll.ts new file mode 100644 index 0000000..c895424 --- /dev/null +++ b/src/commands/roll.ts @@ -0,0 +1,74 @@ +import Discord from "discord.js"; + +const command = "roll"; + +/** + * Rolls a dice given the correct format + * @param message + */ +export function roll(message: Discord.Message) { + // Make is a promise because we are dealing with math and RegEx + return new Promise((resolve, reject) => { + const messageText = message.content.toLowerCase(); + + // Extract the values we want + const commandArguments = messageText + .substr(messageText.indexOf(command) + command.length + 1) + .match(diceRollRegex); + + // If there is no match, we get null + if (commandArguments) { + const [ + input, + _numberOfDice, + _rollTop, + _modifier, + reason, + ] = commandArguments; + + /** Number of dice to be rolled. Default 1 */ + const numberOfDice = _numberOfDice ? Number(_numberOfDice) : 1; + /** The maximum number to roll. Not optional. */ + const rollTop = Number(_rollTop); + /** Modifier to be added at the end of the roll. Default 0. */ + const modifier = _modifier ? Number(_modifier) : 0; + + // Make sure we can do math with the numbers given + if ( + numberOfDice > 10000 || + !Number.isSafeInteger(numberOfDice * rollTop + modifier + numberOfDice) + ) { + message.channel.send( + message.author.toString() + + " Sorry, I can't roll that high! I mean, I can, but I really don't want to." + ); + return reject("Cannot calculate that roll: " + input); + } + + // Calculate the dice rolls + let roll = 0; + for (let dice = 0; dice < Number(numberOfDice); dice++) { + roll += Math.floor(Math.random() * Number(rollTop) + 1); + } + + // Add modifier + roll += Number(modifier); + + // Respond to user + resolve( + message.channel.send( + `${message.author.toString()} **${roll}** ${ + reason ? "for " + reason : "" + }` + ) + ); + } else { + reject( + "Cannot roll for the given value. Arguments did not match expected format." + ); + } + }); +} + +// Will match dice rolls as well as basic rolls and catch a reason +const diceRollRegex = /(?:(\d+)d)?(\d+) ?([\+\-]\d+)?(?: ?for )?(.+)?/; diff --git a/src/index.ts b/src/index.ts index f4fe7dd..cab7fc6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import Discord from "discord.js"; import { prefix, token } from "./config.json"; -import { pubSub, ping } from "./commands"; +import { pubSub, ping, roll } from "./commands"; const client = new Discord.Client(); @@ -23,6 +23,10 @@ client.on("message", (message) => { //Are they on sale? pubSub(message); break; + case "roll": + // Roll dice + roll(message); + break; } } else if (message.channel.type === "dm") { var args = lowerMessage.split(" "); @@ -35,8 +39,16 @@ client.on("message", (message) => { case "pubsub": pubSub(message); break; + case "roll": + // Roll dice + roll(message); + break; } } }); +client.on("error", (err) => { + console.error(err); +}); + client.login(token); From 656e5ca09a4dd10b8bcf310cb9cd903f1355e5e1 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Sun, 24 May 2020 00:37:14 -0400 Subject: [PATCH 02/15] added support for image and animate --- src/commands/imageSearch.ts | 38 +++++++++++++++++++++++++++++++++++++ src/commands/index.ts | 1 + src/index.ts | 14 +++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/commands/imageSearch.ts diff --git a/src/commands/imageSearch.ts b/src/commands/imageSearch.ts new file mode 100644 index 0000000..7323d5e --- /dev/null +++ b/src/commands/imageSearch.ts @@ -0,0 +1,38 @@ +import Discord from "discord.js"; +import { searchId, googleToken } from "../config.json"; +import fetch from "node-fetch"; + +/** + * Initiates a google image search on specified term and responds with a link to the image + * @param {Discord.Messaage} message + */ +export function imageSearch(message: Discord.Message): Promise { + var searchTerm = message.content.split(" "); + searchTerm = searchTerm.splice(2); + var formattedsearch = searchTerm.join("%20"); + var num = Math.floor(Math.random() * 10); + var type = + message.content.split(" ")[1] == "animate" ? "&imgType=animated" : ""; + return fetch( + "https://www.googleapis.com/customsearch/v1?key=" + + googleToken + + "&cx=" + + searchId + + "&searchType=image" + + type + + "&q=" + + formattedsearch + ) + .then((res) => res.json()) + .then((data) => { + message.channel.send( + "search term: " + searchTerm.join(" ") + " \n" + data.items[num].link + ); + }) + .catch((error) => { + console.log(error); + message.channel.send( + "Shits broke, Im not fixing it. probably. <@116415248935682049>" + ); + }); +} diff --git a/src/commands/index.ts b/src/commands/index.ts index ebdf3fd..11fcb79 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -2,3 +2,4 @@ export * from "./ping"; export * from "./pubSub"; +export * from "./imageSearch"; diff --git a/src/index.ts b/src/index.ts index f4fe7dd..73b2d24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import Discord from "discord.js"; import { prefix, token } from "./config.json"; -import { pubSub, ping } from "./commands"; +import { pubSub, ping, imageSearch } from "./commands"; const client = new Discord.Client(); @@ -23,6 +23,12 @@ client.on("message", (message) => { //Are they on sale? pubSub(message); break; + case "image": + imageSearch(message); + break; + case "animate": + imageSearch(message); + break; } } else if (message.channel.type === "dm") { var args = lowerMessage.split(" "); @@ -35,6 +41,12 @@ client.on("message", (message) => { case "pubsub": pubSub(message); break; + case "image": + imageSearch(message); + break; + case "animate": + imageSearch(message); + break; } } }); From eec886611b396d10b9a8016740264e32c87e365c Mon Sep 17 00:00:00 2001 From: Alyssa Date: Sun, 24 May 2020 00:46:36 -0400 Subject: [PATCH 03/15] Added search parameter documentation --- src/commands/imageSearch.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/imageSearch.ts b/src/commands/imageSearch.ts index 7323d5e..3ea8635 100644 --- a/src/commands/imageSearch.ts +++ b/src/commands/imageSearch.ts @@ -6,6 +6,7 @@ import fetch from "node-fetch"; * Initiates a google image search on specified term and responds with a link to the image * @param {Discord.Messaage} message */ +// Search parameters at https://developers.google.com/custom-search/v1/reference/rest/v1/cse/list export function imageSearch(message: Discord.Message): Promise { var searchTerm = message.content.split(" "); searchTerm = searchTerm.splice(2); From 514ad4ece6a69445cbe77d99995722ea3a1d4d57 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Sun, 24 May 2020 00:52:51 -0400 Subject: [PATCH 04/15] TIL you can have case statements next to each other --- src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 73b2d24..ce98766 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,8 +24,6 @@ client.on("message", (message) => { pubSub(message); break; case "image": - imageSearch(message); - break; case "animate": imageSearch(message); break; From d3dd7fb472bad76df21cef3bb4b5e358c94860e8 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Sun, 24 May 2020 00:53:39 -0400 Subject: [PATCH 05/15] Moved the search documentation comment --- src/commands/imageSearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/imageSearch.ts b/src/commands/imageSearch.ts index 3ea8635..ab5fdae 100644 --- a/src/commands/imageSearch.ts +++ b/src/commands/imageSearch.ts @@ -6,7 +6,6 @@ import fetch from "node-fetch"; * Initiates a google image search on specified term and responds with a link to the image * @param {Discord.Messaage} message */ -// Search parameters at https://developers.google.com/custom-search/v1/reference/rest/v1/cse/list export function imageSearch(message: Discord.Message): Promise { var searchTerm = message.content.split(" "); searchTerm = searchTerm.splice(2); @@ -14,6 +13,7 @@ export function imageSearch(message: Discord.Message): Promise { var num = Math.floor(Math.random() * 10); var type = message.content.split(" ")[1] == "animate" ? "&imgType=animated" : ""; + // Search parameters at https://developers.google.com/custom-search/v1/reference/rest/v1/cse/list return fetch( "https://www.googleapis.com/customsearch/v1?key=" + googleToken + From 124ebcf56954b2d2c2e481095d4991f64f174188 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Sun, 24 May 2020 00:54:26 -0400 Subject: [PATCH 06/15] Improved cases in the other spot too --- src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index ce98766..3dd6bec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,8 +40,6 @@ client.on("message", (message) => { pubSub(message); break; case "image": - imageSearch(message); - break; case "animate": imageSearch(message); break; From 86a960988aef1ba41d89e516666d33a6bbd314bc Mon Sep 17 00:00:00 2001 From: Alyssa Date: Sun, 24 May 2020 01:05:32 -0400 Subject: [PATCH 07/15] Update README for image and animate documentation --- README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 845496b..6e8bc39 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,17 @@ The fam has moved to Discord so we are reincarnating Fred to Discord as well Fred's abilities include: -* Ping - * Replies "Pong" -* Pubsub - * Replies with a version of yes or no to whether or not pub subs are on sale - + +- Ping + - Replies "Pong" +- Pubsub + - Replies with a version of yes or no to whether or not pub subs are on sale +- Image (search term) + - Replies with an image from google image search of the specified search term +- Animate (search term) + - Replies with an animated image from google image search of the specified search term + Fred's future abilities: -* Roll X (Where x is a number) - * Replies with a random number between 0-X -* Image X (where x is any search term) - * Replies with a google image result + +- Roll X (Where x is a number) + - Replies with a random number between 0-X From 3e5eb07137f4efb2973ce80bf1e25abd4a2fda89 Mon Sep 17 00:00:00 2001 From: Alyssa Davis Date: Sun, 24 May 2020 01:16:25 -0400 Subject: [PATCH 08/15] Added PR template --- .github/pull_request_template.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b8b1867 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +Changes proposed in this pull request: + +- +- +- + +Description of user interaction with new changes: + +- +- +- + +Fixes # + +- [ ] I have updated the readme to include the new command(s) (If necessary) From 4938e2ca7659121343ac54b64b588e74221fdd2a Mon Sep 17 00:00:00 2001 From: Alyssa Date: Mon, 8 Jun 2020 20:52:34 -0400 Subject: [PATCH 09/15] Added where support --- package.json | 3 ++- src/commands/index.ts | 1 + src/commands/location.ts | 11 +++++++++++ src/index.ts | 6 +++++- 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/commands/location.ts diff --git a/package.json b/package.json index 507ef6b..8754378 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "start": "node ./output/index.js", "dev": "nodemon ./output/index.js", "format": "prettier --write ./src", - "build": "tsc" + "build": "tsc", + "build:watch": "tsc --watch" }, "dependencies": { "@types/node-fetch": "^2.5.7", diff --git a/src/commands/index.ts b/src/commands/index.ts index 11fcb79..58980b2 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -3,3 +3,4 @@ export * from "./ping"; export * from "./pubSub"; export * from "./imageSearch"; +export * from "./location"; diff --git a/src/commands/location.ts b/src/commands/location.ts new file mode 100644 index 0000000..3f5af9c --- /dev/null +++ b/src/commands/location.ts @@ -0,0 +1,11 @@ +import Discord from "discord.js"; + +/** + * Responds to "where" with the host name of the machine Fred is running on + * @param message + */ +export function location(message: Discord.Message) { + var os = require("os"); + var hostname = os.hostname(); + return message.channel.send(hostname); +} diff --git a/src/index.ts b/src/index.ts index 3dd6bec..08d03f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import Discord from "discord.js"; import { prefix, token } from "./config.json"; -import { pubSub, ping, imageSearch } from "./commands"; +import { pubSub, ping, imageSearch, location } from "./commands"; const client = new Discord.Client(); @@ -27,6 +27,8 @@ client.on("message", (message) => { case "animate": imageSearch(message); break; + case "where": + location(message); } } else if (message.channel.type === "dm") { var args = lowerMessage.split(" "); @@ -43,6 +45,8 @@ client.on("message", (message) => { case "animate": imageSearch(message); break; + case "where": + location(message); } } }); From 177a095984e3e69fa0549e0bba6914ed8e681576 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Mon, 8 Jun 2020 20:57:41 -0400 Subject: [PATCH 10/15] Updated README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6e8bc39..4cff50a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Fred's abilities include: - Replies with an image from google image search of the specified search term - Animate (search term) - Replies with an animated image from google image search of the specified search term +- Where + - Replies with the hostname of the machine fred is running from Fred's future abilities: From 38812cd8a4fe92bbc5bf78637789150b63ab6f25 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Mon, 8 Jun 2020 21:07:08 -0400 Subject: [PATCH 11/15] Sammy is still a tyrant --- src/commands/location.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/location.ts b/src/commands/location.ts index 3f5af9c..124ea32 100644 --- a/src/commands/location.ts +++ b/src/commands/location.ts @@ -5,7 +5,7 @@ import Discord from "discord.js"; * @param message */ export function location(message: Discord.Message) { - var os = require("os"); - var hostname = os.hostname(); + const os = require("os"); + const hostname = os.hostname(); return message.channel.send(hostname); } From 94a89bcba54ec913ebc96029926825d6d25689c0 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Mon, 8 Jun 2020 21:08:15 -0400 Subject: [PATCH 12/15] Tyrant breaks --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index 08d03f2..15c6d23 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,6 +29,7 @@ client.on("message", (message) => { break; case "where": location(message); + break; } } else if (message.channel.type === "dm") { var args = lowerMessage.split(" "); @@ -47,6 +48,7 @@ client.on("message", (message) => { break; case "where": location(message); + break; } } }); From fcd53b6477c7c16d62b61970f48813a05c952e40 Mon Sep 17 00:00:00 2001 From: Sammy Israwi Date: Sun, 19 Jul 2020 02:15:50 -0700 Subject: [PATCH 13/15] Implements fred roll better, with proper WoW rolls (Thanks Adam!) --- src/commands/roll.ts | 131 ++++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 45 deletions(-) diff --git a/src/commands/roll.ts b/src/commands/roll.ts index c895424..99eb2b5 100644 --- a/src/commands/roll.ts +++ b/src/commands/roll.ts @@ -2,73 +2,114 @@ import Discord from "discord.js"; const command = "roll"; +/** + * The possible named result groups from the RegEx + */ +type RollRegexGroup = { + wow?: string; + lo?: string; + hi?: string; + dnd?: string; + n?: string; + d?: string; +}; + /** * Rolls a dice given the correct format * @param message */ export function roll(message: Discord.Message) { - // Make is a promise because we are dealing with math and RegEx - return new Promise((resolve, reject) => { + // Make this a promise because we are dealing with math and RegEx which, if abused, could take long. + return new Promise((resolve) => { const messageText = message.content.toLowerCase(); - // Extract the values we want + // Extract the values we want and run the RegEx to parse input. const commandArguments = messageText .substr(messageText.indexOf(command) + command.length + 1) - .match(diceRollRegex); + .match(rollRegex); // If there is no match, we get null if (commandArguments) { - const [ - input, - _numberOfDice, - _rollTop, - _modifier, - reason, - ] = commandArguments; + if (commandArguments.groups?.wow !== undefined) { + // If our match is a WoW one, it behaves as \roll in WoW + const { wow, lo, hi } = commandArguments.groups as RollRegexGroup; - /** Number of dice to be rolled. Default 1 */ - const numberOfDice = _numberOfDice ? Number(_numberOfDice) : 1; - /** The maximum number to roll. Not optional. */ - const rollTop = Number(_rollTop); - /** Modifier to be added at the end of the roll. Default 0. */ - const modifier = _modifier ? Number(_modifier) : 0; + /** Lower boundary. Default 1 */ + const lowerBoundary = Number(lo ?? 1); + /** Upper boundary. Default 100 */ + const upperBoundary = Number(hi ?? 100); - // Make sure we can do math with the numbers given - if ( - numberOfDice > 10000 || - !Number.isSafeInteger(numberOfDice * rollTop + modifier + numberOfDice) - ) { - message.channel.send( - message.author.toString() + - " Sorry, I can't roll that high! I mean, I can, but I really don't want to." - ); - return reject("Cannot calculate that roll: " + input); - } + const roll = + Math.floor(Math.random() * (upperBoundary - lowerBoundary + 1)) + + lowerBoundary; - // Calculate the dice rolls - let roll = 0; - for (let dice = 0; dice < Number(numberOfDice); dice++) { - roll += Math.floor(Math.random() * Number(rollTop) + 1); - } + if (!Number.isSafeInteger(roll)) { + // If our math yielded something that isn't a number + // Maybe because the input includes really large numbers + resolve( + message.channel.send( + `${message.author.toString()} Sorry, I can't roll for that input: ${wow}` + ) + ); + } else { + // Math worked out, notifying user about the result + resolve( + message.channel.send( + `${message.author.toString()} rolls **${roll}** (${lowerBoundary}-${upperBoundary})` + ) + ); + } + } else if (commandArguments.groups?.dnd !== undefined) { + // If our match is a DnD one, treat this as a single dice roll with no modifier. + const { dnd, n, d } = commandArguments.groups as RollRegexGroup; - // Add modifier - roll += Number(modifier); + /** Number of dice to be rolled. Default 1 */ + const numberOfDice = Number(n ?? 1); + /** The maximum number to roll. Not optional, but I am tired and TS understands that it may be undefined. */ + const sidesOfDice = Number(d ?? 20); - // Respond to user + // Make sure we can do math with the numbers given. + // If we can't, tel the user + if ( + numberOfDice > 10000 || + !Number.isSafeInteger(numberOfDice * sidesOfDice) + ) { + resolve( + message.channel.send( + `${message.author.toString()} Sorry, I can't roll that high! I mean, maybe I can, but I really don't want to: ${dnd}` + ) + ); + // I don't think the Promise should reject since the process worked as intended in successfully rejecting faulty input + // But maybe we need a way to notify the parent that that was the case? + // return reject("Cannot calculate that roll: " + dnd); + } else { + // Calculate the dice rolls + let roll = 0; + for (let dice = 0; dice < Number(numberOfDice); dice++) { + roll += Math.floor(Math.random() * Number(sidesOfDice) + 1); + } + // Respond to user to inform the result of the roll + resolve( + message.channel.send( + `${message.author.toString()} rolls **${roll}** (${numberOfDice}d${sidesOfDice})` + ) + ); + } + } + } else { + // Regex did not match, therefore we cannot roll resolve( message.channel.send( - `${message.author.toString()} **${roll}** ${ - reason ? "for " + reason : "" - }` + message.author.toString() + + " Sorry, I cannot roll for that; it did not match the RegEx." ) ); - } else { - reject( - "Cannot roll for the given value. Arguments did not match expected format." - ); + // See comment a few lines above, search for `return reject` + // return reject(...) } }); } -// Will match dice rolls as well as basic rolls and catch a reason -const diceRollRegex = /(?:(\d+)d)?(\d+) ?([\+\-]\d+)?(?: ?for )?(.+)?/; +// Will match dice rolls and wow rolls +// Thank yo Adam for the RegEx! https://regex101.com/r/LG2LyO/1 +const rollRegex = /^(?:(?(?\d+\s+)?(?\d+)?)|(?(?\d+)d(?\d+)))$/; From e5c50726566c85a142205332b9dcf27153839bff Mon Sep 17 00:00:00 2001 From: Sammy Israwi Date: Thu, 21 May 2020 00:29:17 -0700 Subject: [PATCH 14/15] Add roll capabilities to Fred --- src/commands/index.ts | 1 + src/commands/pubSub.ts | 2 +- src/commands/roll.ts | 74 ++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 15 ++++++++- 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/commands/roll.ts diff --git a/src/commands/index.ts b/src/commands/index.ts index 58980b2..d83a9b1 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -4,3 +4,4 @@ export * from "./ping"; export * from "./pubSub"; export * from "./imageSearch"; export * from "./location"; +export * from "./roll"; diff --git a/src/commands/pubSub.ts b/src/commands/pubSub.ts index bfd8017..459baa5 100644 --- a/src/commands/pubSub.ts +++ b/src/commands/pubSub.ts @@ -39,7 +39,7 @@ export function pubSub(message: Discord.Message): Promise { } }) .catch((error) => { - console.log(error); + console.error(error); message.channel.send("Shits broke, Im not fixing it. probably."); }); } diff --git a/src/commands/roll.ts b/src/commands/roll.ts new file mode 100644 index 0000000..c895424 --- /dev/null +++ b/src/commands/roll.ts @@ -0,0 +1,74 @@ +import Discord from "discord.js"; + +const command = "roll"; + +/** + * Rolls a dice given the correct format + * @param message + */ +export function roll(message: Discord.Message) { + // Make is a promise because we are dealing with math and RegEx + return new Promise((resolve, reject) => { + const messageText = message.content.toLowerCase(); + + // Extract the values we want + const commandArguments = messageText + .substr(messageText.indexOf(command) + command.length + 1) + .match(diceRollRegex); + + // If there is no match, we get null + if (commandArguments) { + const [ + input, + _numberOfDice, + _rollTop, + _modifier, + reason, + ] = commandArguments; + + /** Number of dice to be rolled. Default 1 */ + const numberOfDice = _numberOfDice ? Number(_numberOfDice) : 1; + /** The maximum number to roll. Not optional. */ + const rollTop = Number(_rollTop); + /** Modifier to be added at the end of the roll. Default 0. */ + const modifier = _modifier ? Number(_modifier) : 0; + + // Make sure we can do math with the numbers given + if ( + numberOfDice > 10000 || + !Number.isSafeInteger(numberOfDice * rollTop + modifier + numberOfDice) + ) { + message.channel.send( + message.author.toString() + + " Sorry, I can't roll that high! I mean, I can, but I really don't want to." + ); + return reject("Cannot calculate that roll: " + input); + } + + // Calculate the dice rolls + let roll = 0; + for (let dice = 0; dice < Number(numberOfDice); dice++) { + roll += Math.floor(Math.random() * Number(rollTop) + 1); + } + + // Add modifier + roll += Number(modifier); + + // Respond to user + resolve( + message.channel.send( + `${message.author.toString()} **${roll}** ${ + reason ? "for " + reason : "" + }` + ) + ); + } else { + reject( + "Cannot roll for the given value. Arguments did not match expected format." + ); + } + }); +} + +// Will match dice rolls as well as basic rolls and catch a reason +const diceRollRegex = /(?:(\d+)d)?(\d+) ?([\+\-]\d+)?(?: ?for )?(.+)?/; diff --git a/src/index.ts b/src/index.ts index 15c6d23..7c1dee8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import Discord from "discord.js"; import { prefix, token } from "./config.json"; -import { pubSub, ping, imageSearch, location } from "./commands"; +import { pubSub, ping, imageSearch, location, roll } from "./commands"; const client = new Discord.Client(); @@ -30,6 +30,10 @@ client.on("message", (message) => { case "where": location(message); break; + case "roll": + // Roll dice + roll(message); + break; } } else if (message.channel.type === "dm") { var args = lowerMessage.split(" "); @@ -40,6 +44,7 @@ client.on("message", (message) => { ping(message); break; case "pubsub": + //Are they on sale? pubSub(message); break; case "image": @@ -49,8 +54,16 @@ client.on("message", (message) => { case "where": location(message); break; + case "roll": + // Roll dice + roll(message); + break; } } }); +client.on("error", (err) => { + console.error(err); +}); + client.login(token); From 9131d8d0dc30fc8cb8a5d392030e02cbe0e82646 Mon Sep 17 00:00:00 2001 From: Sammy Israwi Date: Sun, 19 Jul 2020 02:15:50 -0700 Subject: [PATCH 15/15] Implements fred roll better, with proper WoW rolls (Thanks Adam!) --- src/commands/roll.ts | 131 ++++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 45 deletions(-) diff --git a/src/commands/roll.ts b/src/commands/roll.ts index c895424..99eb2b5 100644 --- a/src/commands/roll.ts +++ b/src/commands/roll.ts @@ -2,73 +2,114 @@ import Discord from "discord.js"; const command = "roll"; +/** + * The possible named result groups from the RegEx + */ +type RollRegexGroup = { + wow?: string; + lo?: string; + hi?: string; + dnd?: string; + n?: string; + d?: string; +}; + /** * Rolls a dice given the correct format * @param message */ export function roll(message: Discord.Message) { - // Make is a promise because we are dealing with math and RegEx - return new Promise((resolve, reject) => { + // Make this a promise because we are dealing with math and RegEx which, if abused, could take long. + return new Promise((resolve) => { const messageText = message.content.toLowerCase(); - // Extract the values we want + // Extract the values we want and run the RegEx to parse input. const commandArguments = messageText .substr(messageText.indexOf(command) + command.length + 1) - .match(diceRollRegex); + .match(rollRegex); // If there is no match, we get null if (commandArguments) { - const [ - input, - _numberOfDice, - _rollTop, - _modifier, - reason, - ] = commandArguments; + if (commandArguments.groups?.wow !== undefined) { + // If our match is a WoW one, it behaves as \roll in WoW + const { wow, lo, hi } = commandArguments.groups as RollRegexGroup; - /** Number of dice to be rolled. Default 1 */ - const numberOfDice = _numberOfDice ? Number(_numberOfDice) : 1; - /** The maximum number to roll. Not optional. */ - const rollTop = Number(_rollTop); - /** Modifier to be added at the end of the roll. Default 0. */ - const modifier = _modifier ? Number(_modifier) : 0; + /** Lower boundary. Default 1 */ + const lowerBoundary = Number(lo ?? 1); + /** Upper boundary. Default 100 */ + const upperBoundary = Number(hi ?? 100); - // Make sure we can do math with the numbers given - if ( - numberOfDice > 10000 || - !Number.isSafeInteger(numberOfDice * rollTop + modifier + numberOfDice) - ) { - message.channel.send( - message.author.toString() + - " Sorry, I can't roll that high! I mean, I can, but I really don't want to." - ); - return reject("Cannot calculate that roll: " + input); - } + const roll = + Math.floor(Math.random() * (upperBoundary - lowerBoundary + 1)) + + lowerBoundary; - // Calculate the dice rolls - let roll = 0; - for (let dice = 0; dice < Number(numberOfDice); dice++) { - roll += Math.floor(Math.random() * Number(rollTop) + 1); - } + if (!Number.isSafeInteger(roll)) { + // If our math yielded something that isn't a number + // Maybe because the input includes really large numbers + resolve( + message.channel.send( + `${message.author.toString()} Sorry, I can't roll for that input: ${wow}` + ) + ); + } else { + // Math worked out, notifying user about the result + resolve( + message.channel.send( + `${message.author.toString()} rolls **${roll}** (${lowerBoundary}-${upperBoundary})` + ) + ); + } + } else if (commandArguments.groups?.dnd !== undefined) { + // If our match is a DnD one, treat this as a single dice roll with no modifier. + const { dnd, n, d } = commandArguments.groups as RollRegexGroup; - // Add modifier - roll += Number(modifier); + /** Number of dice to be rolled. Default 1 */ + const numberOfDice = Number(n ?? 1); + /** The maximum number to roll. Not optional, but I am tired and TS understands that it may be undefined. */ + const sidesOfDice = Number(d ?? 20); - // Respond to user + // Make sure we can do math with the numbers given. + // If we can't, tel the user + if ( + numberOfDice > 10000 || + !Number.isSafeInteger(numberOfDice * sidesOfDice) + ) { + resolve( + message.channel.send( + `${message.author.toString()} Sorry, I can't roll that high! I mean, maybe I can, but I really don't want to: ${dnd}` + ) + ); + // I don't think the Promise should reject since the process worked as intended in successfully rejecting faulty input + // But maybe we need a way to notify the parent that that was the case? + // return reject("Cannot calculate that roll: " + dnd); + } else { + // Calculate the dice rolls + let roll = 0; + for (let dice = 0; dice < Number(numberOfDice); dice++) { + roll += Math.floor(Math.random() * Number(sidesOfDice) + 1); + } + // Respond to user to inform the result of the roll + resolve( + message.channel.send( + `${message.author.toString()} rolls **${roll}** (${numberOfDice}d${sidesOfDice})` + ) + ); + } + } + } else { + // Regex did not match, therefore we cannot roll resolve( message.channel.send( - `${message.author.toString()} **${roll}** ${ - reason ? "for " + reason : "" - }` + message.author.toString() + + " Sorry, I cannot roll for that; it did not match the RegEx." ) ); - } else { - reject( - "Cannot roll for the given value. Arguments did not match expected format." - ); + // See comment a few lines above, search for `return reject` + // return reject(...) } }); } -// Will match dice rolls as well as basic rolls and catch a reason -const diceRollRegex = /(?:(\d+)d)?(\d+) ?([\+\-]\d+)?(?: ?for )?(.+)?/; +// Will match dice rolls and wow rolls +// Thank yo Adam for the RegEx! https://regex101.com/r/LG2LyO/1 +const rollRegex = /^(?:(?(?\d+\s+)?(?\d+)?)|(?(?\d+)d(?\d+)))$/;