From 502c25728ae12c1ae08846db2aa3e8fbc8b14813 Mon Sep 17 00:00:00 2001 From: Cody Swartz Date: Sun, 26 Apr 2020 06:59:02 -0700 Subject: [PATCH 01/18] Fix: console log start message Just needed to actually call the function ;) fixes #121 --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 09530ec..8034849 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { Client } from 'discord.js'; const client = new Client(); const core = new Core(client); -client.on('ready', () => core.ready); +client.on('ready', () => core.ready()); client.on('message', core.message); From 4a792a402fc7d726fd3a9400ad67ede48014d2b2 Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Mon, 11 May 2020 10:12:17 -0700 Subject: [PATCH 02/18] Create LanguageRunner system and a command to execute given code using the correct language runner --- src/commands/run.ts | 60 ++++++++++++++++++++++++++++++ src/library/core.ts | 10 ++++- src/library/interfaces/iCommand.ts | 4 +- src/library/interfaces/iRunner.ts | 10 +++++ src/library/languageLoader.ts | 30 +++++++++++++++ src/library/languages.ts | 25 +++++++++++++ src/runners/_template.ts | 8 ++++ src/runners/rust.ts | 38 +++++++++++++++++++ 8 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 src/commands/run.ts create mode 100644 src/library/interfaces/iRunner.ts create mode 100644 src/library/languageLoader.ts create mode 100644 src/library/languages.ts create mode 100644 src/runners/_template.ts create mode 100644 src/runners/rust.ts diff --git a/src/commands/run.ts b/src/commands/run.ts new file mode 100644 index 0000000..4688e17 --- /dev/null +++ b/src/commands/run.ts @@ -0,0 +1,60 @@ +import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; +import axios, { AxiosResponse } from 'axios'; +import { parse } from 'querystring'; +import Languages from '@/library/languages'; + + +// Hack for implementing with static properties/methods +let CodeRunner: ICommand; +export default CodeRunner = class { + + /* istanbul ignore next */ + static get description(): string { + return 'Executes provided code using a LanguageRunner'; + } + + public static execute(args: string[], msg: Message, extra: { + languages: Languages + }) { + + let parseResponse; + + try { + parseResponse = this.parseCode(msg.content); + if(!extra.languages.get(parseResponse.language)) { + msg.channel.send(`Unknown language: ${parseResponse.language}`); + return; + } + } catch { + msg.channel.send("Sorry, I wasn't able to understand your formatting. Please try again."); + return; + } + + + let codeRunnerResponse = extra.languages.get(parseResponse.language).execute(parseResponse.code); + + codeRunnerResponse.then(response => { + if (response.success) { + msg.channel.send("```" + response.output + "```"); + } else { + msg.channel.send("Unfortunately I was unable to run your code. Here is the error I received.\n```" + response.output + "```"); + } + }) + + } + + //Tries to pull language and source code out of message + private static parseCode(messageText: string): { language: string, code: string } { + let codeRegex = /(```(.[^\n]*))(\n(.*))(```)/s; + let match = codeRegex.exec(messageText); + + //Group 2 = language, group 4 = code + if (match && match[2] && match[4]) { + return { language: match[2], code: match[4] }; + } else { + throw new Error(`Unable to extract code from ${messageText}`) + } + } + +}; diff --git a/src/library/core.ts b/src/library/core.ts index b3f9e26..5862a7b 100644 --- a/src/library/core.ts +++ b/src/library/core.ts @@ -4,6 +4,8 @@ import glob from 'glob'; import CommandLoader from './commandLoader'; import CommandParser from './commandParser'; import Commands from './commands'; +import LanguageLoader from './languageLoader'; +import Languages from './languages'; const cmdParser = new CommandParser(config.messagePrefix); const commandsPathGlob = './src/commands/*.ts'; @@ -11,6 +13,11 @@ const commandFiles = glob.sync(commandsPathGlob); const commandClasses = CommandLoader.getCommandClasses(commandFiles); const commands = new Commands(commandClasses); +const languagesPathGlob = './src/runners/*.ts'; +const languageRunnerFiles = glob.sync(languagesPathGlob); +const languageRunners = LanguageLoader.getLanguageClasses(languageRunnerFiles); +const languages = new Languages(languageRunners); + export default class Core { constructor(public client: Client) { } @@ -38,7 +45,8 @@ export default class Core { if (command) { return command.execute(args, msg, { client: this.client, - commands + commands, + languages }); } else { diff --git a/src/library/interfaces/iCommand.ts b/src/library/interfaces/iCommand.ts index ba06045..73f632b 100644 --- a/src/library/interfaces/iCommand.ts +++ b/src/library/interfaces/iCommand.ts @@ -1,5 +1,6 @@ import { Client, Message } from "discord.js"; import Commands from "@/commands"; +import Langauges from "@/languages"; /** * An interface for all commands to extend, representing the API that all @@ -12,6 +13,7 @@ export default interface ICommand { readonly description: string; execute(args: string[], msg: Message, extra?: { client?: Client, - commands?: Commands + commands?: Commands, + languages?: Languages }): void; } diff --git a/src/library/interfaces/iRunner.ts b/src/library/interfaces/iRunner.ts new file mode 100644 index 0000000..00d143d --- /dev/null +++ b/src/library/interfaces/iRunner.ts @@ -0,0 +1,10 @@ +/** + * An interface for all code runners to extend, representing the API that all + * subclasses should implement. + * + * @class Runner + */ + + export default interface IRunner { + execute(code: string): Promise<{success: boolean, output: string}>; + } \ No newline at end of file diff --git a/src/library/languageLoader.ts b/src/library/languageLoader.ts new file mode 100644 index 0000000..9488a19 --- /dev/null +++ b/src/library/languageLoader.ts @@ -0,0 +1,30 @@ +import camelCase from 'lodash.camelcase'; +import path from 'path'; +import Runner from './interfaces/iRunner'; + +export interface ILanguageRunners { [key: string]: Runner }; + +export default class LanguageLoader { + public static getLanguageClasses(commandClassFiles: string[]): ILanguageRunners { + /** + * https://stackoverflow.com/questions/5364928/node-js-require-all-files-in-a-folder + * Load all commands in the commands folder besides _template.js + */ + const files = LanguageLoader.removeTemplateFile(commandClassFiles); + return files.reduce((prev: ILanguageRunners, file) => { + let key = path.basename(file, path.extname(file)); + // Convert the kebab file names to camel case + key = camelCase(key); + + const required = require(path.resolve(file)); + prev[key] = required.default; + + return prev; + }, {}); + } + + private static removeTemplateFile(files: string[]) { + const commandTemplateFile = './src/runners/_template.ts'; + return files.filter(file => file !== commandTemplateFile); + } +} \ No newline at end of file diff --git a/src/library/languages.ts b/src/library/languages.ts new file mode 100644 index 0000000..a85440f --- /dev/null +++ b/src/library/languages.ts @@ -0,0 +1,25 @@ +import { ILanguageRunners } from './languageLoader'; +import LanguageRunner from './interfaces/iRunner'; + +export default class Commands { + public readonly all: ILanguageRunners; + + constructor(languageRunners: ILanguageRunners) { + this.all = languageRunners; + } + + get names() { + return Object.keys(this.all); + } + + public get(languageName: string): LanguageRunner { + return this.all[languageName]; + } + + public longestNameLength() { + // Find the longest synopsis + const longest = this.names.sort((a, b) => b.length - a.length)[0]; + return longest.length; + } + +} diff --git a/src/runners/_template.ts b/src/runners/_template.ts new file mode 100644 index 0000000..dfe81d0 --- /dev/null +++ b/src/runners/_template.ts @@ -0,0 +1,8 @@ +import IRunner from '@/library/interfaces/iRunner'; + +let LanguageName: IRunner; +export default LanguageName = class { + public static execute(code: string): Promise<{success: boolean, output: string}> { + throw new Error(`LanguageRunner not yet implemented for ${this.name}`); + } +} \ No newline at end of file diff --git a/src/runners/rust.ts b/src/runners/rust.ts new file mode 100644 index 0000000..843391d --- /dev/null +++ b/src/runners/rust.ts @@ -0,0 +1,38 @@ +import IRunner from '@/library/interfaces/iRunner'; +import axios, { AxiosResponse } from 'axios'; + +let Rust: IRunner; +export default Rust = class { + public static execute(code: string): Promise<{ success: boolean, output: string }> { + return this.runCode(code) + .then((response) => { + if (response.success) { + return { success: true, output: response.stdout }; + } else { + return { success: false, output: response.stderr }; + } + }) + .catch(error => { + return { success: false, output: "Rust LanguageRunner encountered an internal error" }; + }); + } + + //Sends code to the rust playground for execution + private static runCode(code: string): Promise<{ success: boolean, stdout: string, stderr: string }> { + const url = "https://play.rust-lang.org/execute"; + return axios.post(url, { + channel: "stable", + code: code, + crateType: "bin", + edition: "2018", + mode: "debug", + tests: false + }).then((response: AxiosResponse) => { + return { + success: response.data.success, + stdout: response.data.stdout, + stderr: response.data.stderr + } + }) + } +} \ No newline at end of file From 40b9851b7e3812a81d329b1b46261aa0f0abe431 Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Mon, 11 May 2020 10:28:29 -0700 Subject: [PATCH 03/18] Run auto linter and manually fix line length warning on run.ts:39 --- __tests__/commands/source.test.ts | 2 +- __tests__/commands/version.test.ts | 2 +- __tests__/library/commandLoader.test.ts | 2 +- src/commands/add.ts | 2 +- src/commands/format.ts | 2 +- src/commands/gitProfile.ts | 2 +- src/commands/help.ts | 2 +- src/commands/lmgtfy.ts | 2 +- src/commands/magic8ball.ts | 2 +- src/commands/rules.ts | 2 +- src/commands/run.ts | 28 +++++++++++++------------ src/commands/say.ts | 2 +- src/commands/source.ts | 2 +- src/commands/version.ts | 2 +- src/commands/weather.ts | 4 ++-- src/commands/xmas.ts | 2 +- src/library/languageLoader.ts | 4 ++-- src/library/languages.ts | 2 +- src/runners/_template.ts | 2 +- src/runners/rust.ts | 10 ++++----- 20 files changed, 40 insertions(+), 38 deletions(-) diff --git a/__tests__/commands/source.test.ts b/__tests__/commands/source.test.ts index d42fe5c..5b86ba5 100644 --- a/__tests__/commands/source.test.ts +++ b/__tests__/commands/source.test.ts @@ -1,5 +1,5 @@ -import { message as mockMessage, MockedMessage } from '../mocks/discord'; import Source from '@/commands/source'; +import { message as mockMessage, MockedMessage } from '../mocks/discord'; let sendMock: MockedMessage; beforeEach(() => { diff --git a/__tests__/commands/version.test.ts b/__tests__/commands/version.test.ts index d6be445..1516926 100644 --- a/__tests__/commands/version.test.ts +++ b/__tests__/commands/version.test.ts @@ -1,6 +1,6 @@ +import Version from '@/commands/version'; import { Message } from 'discord.js'; import { message as mockMessage, MockedMessage } from '../mocks/discord'; -import Version from '@/commands/version'; let sendMock: MockedMessage; beforeEach(() => { diff --git a/__tests__/library/commandLoader.test.ts b/__tests__/library/commandLoader.test.ts index eb82da8..8d2b4c6 100644 --- a/__tests__/library/commandLoader.test.ts +++ b/__tests__/library/commandLoader.test.ts @@ -1,5 +1,5 @@ -import glob from 'glob'; import CommandLoader, { ICommandClasses } from '@/library/commandLoader'; +import glob from 'glob'; describe('CommandLoader', () => { let commandClasses: ICommandClasses; diff --git a/src/commands/add.ts b/src/commands/add.ts index fdf810f..dfa3747 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -1,5 +1,5 @@ -import { Message } from "discord.js"; import ICommand from "@/library/interfaces/iCommand"; +import { Message } from "discord.js"; let Add: ICommand; diff --git a/src/commands/format.ts b/src/commands/format.ts index 3a4aaae..c43e9cd 100644 --- a/src/commands/format.ts +++ b/src/commands/format.ts @@ -1,5 +1,5 @@ -import { Message } from 'discord.js'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; // Hack for implementing with static properties/methods let Format: ICommand; diff --git a/src/commands/gitProfile.ts b/src/commands/gitProfile.ts index 2f5c40d..a336f07 100644 --- a/src/commands/gitProfile.ts +++ b/src/commands/gitProfile.ts @@ -1,7 +1,7 @@ +import ICommand from '@/library/interfaces/iCommand'; import axios from 'axios'; import { Message } from 'discord.js'; import moment from 'moment'; -import ICommand from '@/library/interfaces/iCommand'; import IGithubProfile from './interfaces/iGithubProfile'; let GitProfile: ICommand; diff --git a/src/commands/help.ts b/src/commands/help.ts index cfe5c66..906146b 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,7 +1,7 @@ -import { Message } from 'discord.js'; import config from '@/config'; import Commands from '@/library/commands'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; let Help: ICommand; diff --git a/src/commands/lmgtfy.ts b/src/commands/lmgtfy.ts index d1887cd..8e30b1c 100644 --- a/src/commands/lmgtfy.ts +++ b/src/commands/lmgtfy.ts @@ -1,5 +1,5 @@ -import { Message } from 'discord.js'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; let Lmgtfy: ICommand; diff --git a/src/commands/magic8ball.ts b/src/commands/magic8ball.ts index 1dab84d..b7624d7 100644 --- a/src/commands/magic8ball.ts +++ b/src/commands/magic8ball.ts @@ -1,5 +1,5 @@ -import { Message } from 'discord.js'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; let Magic8Ball: ICommand; diff --git a/src/commands/rules.ts b/src/commands/rules.ts index 59e22b4..b61e8de 100644 --- a/src/commands/rules.ts +++ b/src/commands/rules.ts @@ -1,5 +1,5 @@ -import { Message } from 'discord.js'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; let Rules: ICommand; diff --git a/src/commands/run.ts b/src/commands/run.ts index 4688e17..602facc 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -1,9 +1,8 @@ import ICommand from '@/library/interfaces/iCommand'; -import { Message } from 'discord.js'; +import Languages from '@/library/languages'; import axios, { AxiosResponse } from 'axios'; +import { Message } from 'discord.js'; import { parse } from 'querystring'; -import Languages from '@/library/languages'; - // Hack for implementing with static properties/methods let CodeRunner: ICommand; @@ -22,7 +21,7 @@ export default CodeRunner = class { try { parseResponse = this.parseCode(msg.content); - if(!extra.languages.get(parseResponse.language)) { + if (!extra.languages.get(parseResponse.language)) { msg.channel.send(`Unknown language: ${parseResponse.language}`); return; } @@ -30,30 +29,33 @@ export default CodeRunner = class { msg.channel.send("Sorry, I wasn't able to understand your formatting. Please try again."); return; } - - let codeRunnerResponse = extra.languages.get(parseResponse.language).execute(parseResponse.code); + const codeRunnerResponse = extra.languages.get(parseResponse.language).execute(parseResponse.code); codeRunnerResponse.then(response => { if (response.success) { msg.channel.send("```" + response.output + "```"); } else { - msg.channel.send("Unfortunately I was unable to run your code. Here is the error I received.\n```" + response.output + "```"); + msg.channel.send( + "Unfortunately I was unable to run your code. Here is the error I received.\n```" + + response.output + + "```" + ); } - }) + }); } - //Tries to pull language and source code out of message + // Tries to pull language and source code out of message private static parseCode(messageText: string): { language: string, code: string } { - let codeRegex = /(```(.[^\n]*))(\n(.*))(```)/s; - let match = codeRegex.exec(messageText); + const codeRegex = /(```(.[^\n]*))(\n(.*))(```)/s; + const match = codeRegex.exec(messageText); - //Group 2 = language, group 4 = code + // Group 2 = language, group 4 = code if (match && match[2] && match[4]) { return { language: match[2], code: match[4] }; } else { - throw new Error(`Unable to extract code from ${messageText}`) + throw new Error(`Unable to extract code from ${messageText}`); } } diff --git a/src/commands/say.ts b/src/commands/say.ts index c0af7c4..f3562ca 100644 --- a/src/commands/say.ts +++ b/src/commands/say.ts @@ -1,5 +1,5 @@ -import { Message } from 'discord.js'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; let Say: ICommand; diff --git a/src/commands/source.ts b/src/commands/source.ts index 518bb51..5b245e4 100644 --- a/src/commands/source.ts +++ b/src/commands/source.ts @@ -1,5 +1,5 @@ -import { Message } from 'discord.js'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; let Source: ICommand; diff --git a/src/commands/version.ts b/src/commands/version.ts index 98a253d..4298f58 100644 --- a/src/commands/version.ts +++ b/src/commands/version.ts @@ -1,7 +1,7 @@ import config from '@/config'; +import ICommand from '@/library/interfaces/iCommand'; import { version } from '@root/package.json'; import { Message } from 'discord.js'; -import ICommand from '@/library/interfaces/iCommand'; let Version: ICommand; diff --git a/src/commands/weather.ts b/src/commands/weather.ts index 3aa2406..2a819cc 100644 --- a/src/commands/weather.ts +++ b/src/commands/weather.ts @@ -1,7 +1,7 @@ -import axios, { AxiosResponse } from 'axios'; -import { Message } from 'discord.js'; import config from '@/config'; import ICommand from '@/library/interfaces/iCommand'; +import axios, { AxiosResponse } from 'axios'; +import { Message } from 'discord.js'; let Weather: ICommand; diff --git a/src/commands/xmas.ts b/src/commands/xmas.ts index a44e43d..af58bb8 100644 --- a/src/commands/xmas.ts +++ b/src/commands/xmas.ts @@ -1,5 +1,5 @@ -import { Message } from 'discord.js'; import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; let Xmas: ICommand; diff --git a/src/library/languageLoader.ts b/src/library/languageLoader.ts index 9488a19..66ac7d9 100644 --- a/src/library/languageLoader.ts +++ b/src/library/languageLoader.ts @@ -2,7 +2,7 @@ import camelCase from 'lodash.camelcase'; import path from 'path'; import Runner from './interfaces/iRunner'; -export interface ILanguageRunners { [key: string]: Runner }; +export interface ILanguageRunners { [key: string]: Runner; } export default class LanguageLoader { public static getLanguageClasses(commandClassFiles: string[]): ILanguageRunners { @@ -27,4 +27,4 @@ export default class LanguageLoader { const commandTemplateFile = './src/runners/_template.ts'; return files.filter(file => file !== commandTemplateFile); } -} \ No newline at end of file +} diff --git a/src/library/languages.ts b/src/library/languages.ts index a85440f..f72ffe1 100644 --- a/src/library/languages.ts +++ b/src/library/languages.ts @@ -1,5 +1,5 @@ -import { ILanguageRunners } from './languageLoader'; import LanguageRunner from './interfaces/iRunner'; +import { ILanguageRunners } from './languageLoader'; export default class Commands { public readonly all: ILanguageRunners; diff --git a/src/runners/_template.ts b/src/runners/_template.ts index dfe81d0..711a0fe 100644 --- a/src/runners/_template.ts +++ b/src/runners/_template.ts @@ -5,4 +5,4 @@ export default LanguageName = class { public static execute(code: string): Promise<{success: boolean, output: string}> { throw new Error(`LanguageRunner not yet implemented for ${this.name}`); } -} \ No newline at end of file +}; diff --git a/src/runners/rust.ts b/src/runners/rust.ts index 843391d..6c68a1e 100644 --- a/src/runners/rust.ts +++ b/src/runners/rust.ts @@ -17,12 +17,12 @@ export default Rust = class { }); } - //Sends code to the rust playground for execution + // Sends code to the rust playground for execution private static runCode(code: string): Promise<{ success: boolean, stdout: string, stderr: string }> { const url = "https://play.rust-lang.org/execute"; return axios.post(url, { channel: "stable", - code: code, + code, crateType: "bin", edition: "2018", mode: "debug", @@ -32,7 +32,7 @@ export default Rust = class { success: response.data.success, stdout: response.data.stdout, stderr: response.data.stderr - } - }) + }; + }); } -} \ No newline at end of file +}; From 4e26264e24c10a364d601690c2afdd772ab68e83 Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Mon, 11 May 2020 10:32:08 -0700 Subject: [PATCH 04/18] Change run command class name. Turns out the class name has to match the command name --- src/commands/run.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/run.ts b/src/commands/run.ts index 602facc..6b944e7 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -5,8 +5,8 @@ import { Message } from 'discord.js'; import { parse } from 'querystring'; // Hack for implementing with static properties/methods -let CodeRunner: ICommand; -export default CodeRunner = class { +let Run: ICommand; +export default Run = class { /* istanbul ignore next */ static get description(): string { From 924014dd1bbdd60a72f0fbb80c24927ca31657a1 Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Mon, 11 May 2020 11:03:16 -0700 Subject: [PATCH 05/18] Fix some typos and import problems --- src/library/interfaces/iCommand.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library/interfaces/iCommand.ts b/src/library/interfaces/iCommand.ts index 73f632b..57ac92d 100644 --- a/src/library/interfaces/iCommand.ts +++ b/src/library/interfaces/iCommand.ts @@ -1,6 +1,6 @@ import { Client, Message } from "discord.js"; -import Commands from "@/commands"; -import Langauges from "@/languages"; +import Commands from "@/library/commands"; +import Languages from "@/library/languages"; /** * An interface for all commands to extend, representing the API that all From 9b32f1643bb88850ba95d2ff45ea5f25b3ac7929 Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Tue, 12 May 2020 09:28:51 -0700 Subject: [PATCH 06/18] Move languageRunner initialization to languages.ts --- src/commands/run.ts | 12 ++++++------ src/library/core.ts | 10 +--------- src/library/interfaces/iCommand.ts | 5 ++--- src/library/languages.ts | 12 ++++++++++-- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/commands/run.ts b/src/commands/run.ts index 6b944e7..a230ed5 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -4,6 +4,8 @@ import axios, { AxiosResponse } from 'axios'; import { Message } from 'discord.js'; import { parse } from 'querystring'; +const languages = new Languages(); + // Hack for implementing with static properties/methods let Run: ICommand; export default Run = class { @@ -13,15 +15,13 @@ export default Run = class { return 'Executes provided code using a LanguageRunner'; } - public static execute(args: string[], msg: Message, extra: { - languages: Languages - }) { + public static execute(args: string[], msg: Message) { let parseResponse; try { parseResponse = this.parseCode(msg.content); - if (!extra.languages.get(parseResponse.language)) { + if (!languages.get(parseResponse.language)) { msg.channel.send(`Unknown language: ${parseResponse.language}`); return; } @@ -30,9 +30,9 @@ export default Run = class { return; } - const codeRunnerResponse = extra.languages.get(parseResponse.language).execute(parseResponse.code); + const codeRunnerResponse = languages.get(parseResponse.language).execute(parseResponse.code); - codeRunnerResponse.then(response => { + codeRunnerResponse.then((response: { success: any; output: string; }) => { if (response.success) { msg.channel.send("```" + response.output + "```"); } else { diff --git a/src/library/core.ts b/src/library/core.ts index 5862a7b..b3f9e26 100644 --- a/src/library/core.ts +++ b/src/library/core.ts @@ -4,8 +4,6 @@ import glob from 'glob'; import CommandLoader from './commandLoader'; import CommandParser from './commandParser'; import Commands from './commands'; -import LanguageLoader from './languageLoader'; -import Languages from './languages'; const cmdParser = new CommandParser(config.messagePrefix); const commandsPathGlob = './src/commands/*.ts'; @@ -13,11 +11,6 @@ const commandFiles = glob.sync(commandsPathGlob); const commandClasses = CommandLoader.getCommandClasses(commandFiles); const commands = new Commands(commandClasses); -const languagesPathGlob = './src/runners/*.ts'; -const languageRunnerFiles = glob.sync(languagesPathGlob); -const languageRunners = LanguageLoader.getLanguageClasses(languageRunnerFiles); -const languages = new Languages(languageRunners); - export default class Core { constructor(public client: Client) { } @@ -45,8 +38,7 @@ export default class Core { if (command) { return command.execute(args, msg, { client: this.client, - commands, - languages + commands }); } else { diff --git a/src/library/interfaces/iCommand.ts b/src/library/interfaces/iCommand.ts index 57ac92d..6445762 100644 --- a/src/library/interfaces/iCommand.ts +++ b/src/library/interfaces/iCommand.ts @@ -1,6 +1,6 @@ import { Client, Message } from "discord.js"; import Commands from "@/library/commands"; -import Languages from "@/library/languages"; + /** * An interface for all commands to extend, representing the API that all @@ -13,7 +13,6 @@ export default interface ICommand { readonly description: string; execute(args: string[], msg: Message, extra?: { client?: Client, - commands?: Commands, - languages?: Languages + commands?: Commands }): void; } diff --git a/src/library/languages.ts b/src/library/languages.ts index f72ffe1..af0acd3 100644 --- a/src/library/languages.ts +++ b/src/library/languages.ts @@ -1,10 +1,16 @@ import LanguageRunner from './interfaces/iRunner'; import { ILanguageRunners } from './languageLoader'; +import glob from 'glob'; +import LanguageLoader from './languageLoader'; -export default class Commands { + +export default class Languages { public readonly all: ILanguageRunners; - constructor(languageRunners: ILanguageRunners) { + constructor() { + const languagesPathGlob = './src/runners/*.ts'; + const languageRunnerFiles = glob.sync(languagesPathGlob); + const languageRunners = LanguageLoader.getLanguageClasses(languageRunnerFiles); this.all = languageRunners; } @@ -23,3 +29,5 @@ export default class Commands { } } + + From d16e23e1dca4916c1cf3cd47e4b59b6bc9e623a5 Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Thu, 1 Oct 2020 21:43:17 -0700 Subject: [PATCH 07/18] Add hacktoberfest command with test --- __tests__/commands/hacktoberfest.test.ts | 17 +++++++++++++++++ src/commands/hacktoberfest.ts | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 __tests__/commands/hacktoberfest.test.ts create mode 100644 src/commands/hacktoberfest.ts diff --git a/__tests__/commands/hacktoberfest.test.ts b/__tests__/commands/hacktoberfest.test.ts new file mode 100644 index 0000000..b70f39b --- /dev/null +++ b/__tests__/commands/hacktoberfest.test.ts @@ -0,0 +1,17 @@ +import Hacktoberfest from '@/commands/hacktoberfest'; + +import { message as mockMessage, MockedMessage } from '../mocks/discord'; + +let sendMock: MockedMessage; +beforeEach(() => { + sendMock = jest.fn(); + mockMessage.channel.send = sendMock; +}); + +describe('Hacktoberfest command', () => { + test('hacktoberfest', () => { + Hacktoberfest.execute([], mockMessage); + const sentMessage = sendMock.mock.calls[0][0]; + expect(sentMessage.includes('Hacktoberfest')).toEqual(true); + }); +}); diff --git a/src/commands/hacktoberfest.ts b/src/commands/hacktoberfest.ts new file mode 100644 index 0000000..3e1214f --- /dev/null +++ b/src/commands/hacktoberfest.ts @@ -0,0 +1,17 @@ +import ICommand from '@/library/interfaces/iCommand'; +import { Message } from 'discord.js'; + +// Hack for implementing with static properties/methods +let Hacktoberfest: ICommand; +export default Hacktoberfest = class { + + static get description(): string { + return 'Lists information on how to participate in Hacktoberfest 2020'; + } + + public static execute(args: string[], msg: Message) { + const content = "Hacktoberfest has officially begun! Find out more information at\nhttps://hacktoberfest.digitalocean.com\nand stay tuned for opportunities and workshops from tech club members."; + return msg.channel.send(content); + } + +}; From 0e46f94c496fc9bc7e36106e993291692b586c81 Mon Sep 17 00:00:00 2001 From: Cody Swartz Date: Thu, 1 Oct 2020 23:46:03 -0700 Subject: [PATCH 08/18] Edit: quick version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d471af3..b2d867a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackbot", - "version": "2.2.0", + "version": "2.2.1", "description": "Discord bot for the Cascades Tech Club Discord server.", "repository": { "type": "git", From b2c3c6d5041c1ffcb7b6d6cbbb6038276c7fc301 Mon Sep 17 00:00:00 2001 From: Cody Swartz Date: Thu, 1 Oct 2020 23:55:33 -0700 Subject: [PATCH 09/18] Edit: version bump --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4001f52..64e97ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hackbot", - "version": "2.2.0", + "version": "2.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b2d867a..5ab6554 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackbot", - "version": "2.2.1", + "version": "2.2.2", "description": "Discord bot for the Cascades Tech Club Discord server.", "repository": { "type": "git", From 4250b638a156b19102928276f1c14564bd99324c Mon Sep 17 00:00:00 2001 From: Cody Swartz Date: Thu, 1 Oct 2020 23:56:33 -0700 Subject: [PATCH 10/18] Edit: quick npm audit fix --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64e97ca..36c1b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3047,9 +3047,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash.camelcase": { From 49452507d015b27e4a9a6730eee0fba9e47f3a1c Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Tue, 6 Oct 2020 19:17:45 -0700 Subject: [PATCH 11/18] Remove unnecessary imports in 'run.ts' --- src/commands/run.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/commands/run.ts b/src/commands/run.ts index a230ed5..020b5f7 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -1,8 +1,6 @@ import ICommand from '@/library/interfaces/iCommand'; import Languages from '@/library/languages'; -import axios, { AxiosResponse } from 'axios'; import { Message } from 'discord.js'; -import { parse } from 'querystring'; const languages = new Languages(); From d0d5d40a2dcdab33eb96d503741795e98bc715e4 Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Tue, 6 Oct 2020 19:45:16 -0700 Subject: [PATCH 12/18] Refactor 'Languages' constructor and add 'Languages' tests --- src/library/languages.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/library/languages.ts b/src/library/languages.ts index af0acd3..14ea8c8 100644 --- a/src/library/languages.ts +++ b/src/library/languages.ts @@ -7,11 +7,8 @@ import LanguageLoader from './languageLoader'; export default class Languages { public readonly all: ILanguageRunners; - constructor() { - const languagesPathGlob = './src/runners/*.ts'; - const languageRunnerFiles = glob.sync(languagesPathGlob); - const languageRunners = LanguageLoader.getLanguageClasses(languageRunnerFiles); - this.all = languageRunners; + constructor(languages?: ILanguageRunners) { + this.all = languages || this.fetchLanguages(); } get names() { @@ -28,6 +25,12 @@ export default class Languages { return longest.length; } + private fetchLanguages(): ILanguageRunners { + const languagesPathGlob = './src/runners/*.ts'; + const languageRunnerFiles = glob.sync(languagesPathGlob); + return LanguageLoader.getLanguageClasses(languageRunnerFiles); + } + } From 26fd4e918fc14d9f1a6a1b2f09eb397ddf203d5e Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Tue, 6 Oct 2020 20:48:47 -0700 Subject: [PATCH 13/18] Add tests for 'run' command and 'rust' language --- __tests__/__mocks__/axios.ts | 5 ++++- src/commands/run.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/__tests__/__mocks__/axios.ts b/__tests__/__mocks__/axios.ts index ac5c2e9..c4bd9af 100644 --- a/__tests__/__mocks__/axios.ts +++ b/__tests__/__mocks__/axios.ts @@ -4,5 +4,8 @@ export default { }), request: jest.fn().mockResolvedValue({ data: {} - }) + }), + post: jest.fn().mockResolvedValue({ + data: {} + }), }; diff --git a/src/commands/run.ts b/src/commands/run.ts index 020b5f7..3197b38 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -7,6 +7,7 @@ const languages = new Languages(); // Hack for implementing with static properties/methods let Run: ICommand; export default Run = class { + /* istanbul ignore next */ static get description(): string { From 53caf6a44c82b95a873efa97edc50c79120c594b Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Tue, 6 Oct 2020 21:07:17 -0700 Subject: [PATCH 14/18] Display caught error message to user --- __tests__/commands/run.test.ts | 21 +++++++++++++++++++++ src/commands/run.ts | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 __tests__/commands/run.test.ts diff --git a/__tests__/commands/run.test.ts b/__tests__/commands/run.test.ts new file mode 100644 index 0000000..65b073e --- /dev/null +++ b/__tests__/commands/run.test.ts @@ -0,0 +1,21 @@ +import Run from '@/commands/run'; +import { message as mockMessage, MockedMessage } from '../mocks/discord'; + +let sendMock: MockedMessage; +beforeEach(() => { + sendMock = jest.fn(); + mockMessage.channel.send = sendMock; + mockMessage.reply = sendMock; +}); + +test('Malformed message', () => { + mockMessage.content = "Wow this is nowhere near the correct content"; + Run.execute([], mockMessage); + expect(sendMock).lastCalledWith("Sorry, I ran into some problems understanding your message. Here is the error stopping me.\nError: Unable to extract code from Wow this is nowhere near the correct content"); +}); + +test('Unknown language', () => { + mockMessage.content = "```invalidLanguage\nblahblahblah```"; + Run.execute([], mockMessage); + expect(sendMock).lastCalledWith("Unknown language: invalidLanguage") +}); \ No newline at end of file diff --git a/src/commands/run.ts b/src/commands/run.ts index 3197b38..0be6a09 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -7,7 +7,7 @@ const languages = new Languages(); // Hack for implementing with static properties/methods let Run: ICommand; export default Run = class { - + /* istanbul ignore next */ static get description(): string { @@ -24,8 +24,8 @@ export default Run = class { msg.channel.send(`Unknown language: ${parseResponse.language}`); return; } - } catch { - msg.channel.send("Sorry, I wasn't able to understand your formatting. Please try again."); + } catch (e) { + msg.channel.send("Sorry, I ran into some problems understanding your message. Here is the error stopping me.\n" + e); return; } From ab856eb8bb11b93b2a3b4c42dad1e969e7fdcacc Mon Sep 17 00:00:00 2001 From: Colin Suckow Date: Tue, 6 Oct 2020 21:15:20 -0700 Subject: [PATCH 15/18] Actually adds tests for 'rust' language and 'languages' --- __tests__/languages/rust.test.ts | 21 +++++++++++++++++++ __tests__/library/languages.test.ts | 31 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 __tests__/languages/rust.test.ts create mode 100644 __tests__/library/languages.test.ts diff --git a/__tests__/languages/rust.test.ts b/__tests__/languages/rust.test.ts new file mode 100644 index 0000000..aa5e0f0 --- /dev/null +++ b/__tests__/languages/rust.test.ts @@ -0,0 +1,21 @@ +import Rust from '@/runners/rust'; +import axiosMock from '../__mocks__/axios'; + +jest.mock('axios'); + +test('valid code', async () => { + const code = "testCode"; + let mockResponse = Promise.resolve({ data: { success: true, stdout: "test", stderr: " Compiling playground v0.0.1 (/playground)\n Finished dev [unoptimized + debuginfo] target(s) in 0.43s\n Running `target/debug/playground`\n" } }); + axiosMock.post.mockResolvedValueOnce(mockResponse); + let result = await Rust.execute(code); + expect(result).toEqual({ success: true, output: "test" }); +}) + +test('invalid code', async () => { + const code = "testCode"; + const errorResult = "bad code"; + let mockResponse = Promise.resolve({ data: { success: false, stdout: "", stderr: errorResult } }); + axiosMock.post.mockResolvedValueOnce(mockResponse); + let result = await Rust.execute(code); + expect(result).toEqual({ success: false, output: errorResult }); +}) \ No newline at end of file diff --git a/__tests__/library/languages.test.ts b/__tests__/library/languages.test.ts new file mode 100644 index 0000000..9a5da00 --- /dev/null +++ b/__tests__/library/languages.test.ts @@ -0,0 +1,31 @@ +import Languages from '@/library/languages'; +import IRunner from '@/library/interfaces/iRunner'; +import { ILanguageRunners } from '@/library/languageLoader'; + +describe('Languages', () => { + let mockLanguageRunner: IRunner; + let languageRunners: ILanguageRunners; + let languages: Languages; + + beforeEach(() => { + mockLanguageRunner = { + execute: jest.fn() + }; + + languageRunners = { + testLang: mockLanguageRunner, + }; + + languages = new Languages(languageRunners); + }); + + test('.names returns language names', () => { + const languageNames = ['testLang']; + expect(languages.names).toEqual(languageNames); + }); + + test('Can fetch a command', () => { + const testLang = languages.get('testLang'); + expect(testLang).toBe(mockLanguageRunner); + }); +}) \ No newline at end of file From aee765f77521c26ac665bdd1526adf0bb56ebaec Mon Sep 17 00:00:00 2001 From: Cody Swartz Date: Sat, 10 Oct 2020 01:26:25 -0700 Subject: [PATCH 16/18] Edit: README - fix greenkeeper with Snyk Fixes #128 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f05891..d00387d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ |Staging Build|[![Build Status](https://travis-ci.org/osu-cascades/hackbot.svg?branch=dev)](https://travis-ci.org/osu-cascades/hackbot)| |Maintainability|[![Maintainability](https://api.codeclimate.com/v1/badges/96320fe592c30381915f/maintainability)](https://codeclimate.com/github/osu-cascades/hackbot)| |Test Coverage|[![Test Coverage](https://api.codeclimate.com/v1/badges/96320fe592c30381915f/test_coverage)](https://codeclimate.com/github/osu-cascades/hackbot)| -|GreenKeeper|[![Greenkeeper badge](https://badges.greenkeeper.io/osu-cascades/hackbot.svg)](https://greenkeeper.io/)| +|Snyk|[![Known Vulnerabilities](https://snyk.io/test/github/osu-cascades/hackbot/badge.svg?targetFile=package.json)](https://snyk.io/test/github/osu-cascades/hackbot?targetFile=package.json)| + + A Discord bot for the Cascades Tech Club [Discord](http://discordapp.com) server. To add a command, see the [Commands](#commands) section below. From 74a99d2985c380d4e62b6527bf6664bb1e1ca911 Mon Sep 17 00:00:00 2001 From: Cody Swartz Date: Sat, 10 Oct 2020 02:23:37 -0700 Subject: [PATCH 17/18] Edit: version bump Minor bump since there's been a bit in this release including a snazzy command --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 36c1b34..e124be1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hackbot", - "version": "2.2.2", + "version": "2.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5ab6554..8aab51f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackbot", - "version": "2.2.2", + "version": "2.3.0", "description": "Discord bot for the Cascades Tech Club Discord server.", "repository": { "type": "git", From 801f04a7e1caff59242185f738f4c4677a4fab0a Mon Sep 17 00:00:00 2001 From: Cody Swartz Date: Sat, 10 Oct 2020 02:29:09 -0700 Subject: [PATCH 18/18] Edit: bump node version up to 14.13.1 Hope this works! --- .nvmrc | 2 +- .tool-versions | 1 + package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .tool-versions diff --git a/.nvmrc b/.nvmrc index 035f717..f326028 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v13.7 \ No newline at end of file +v14.13.1 \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..4da5c30 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 14.13.1 \ No newline at end of file diff --git a/package.json b/package.json index 8aab51f..00baa20 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "author": "osu-cascades", "license": "MIT", "engines": { - "node": "13.7.x" + "node": "14.13.x" }, "dependencies": { "@types/axios": "^0.14.0",