diff --git a/README.md b/README.md index 7a2e4d01..e13c82f8 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ in the appropriate channel's topic to use it. ## Commands -Total: 544 +Total: 547 ### Utility: @@ -829,6 +829,12 @@ Total: 544 * **phone-info:** Gives information on the current phone call. * **phone:** Starts a phone call with a random server. +### Coding Tools: + +* **beautify:** Beautifies code with js-beautify. +* **lint-rule:** Responds with information on an ESLint rule. +* **lint:** Lints code using ESLint. + ### Other: * **cleverbot:** Talk to Cleverbot. (Owner-Only) diff --git a/Xiao.js b/Xiao.js index 992c9b47..626aabd3 100644 --- a/Xiao.js +++ b/Xiao.js @@ -36,6 +36,7 @@ client.registry ['edit-number', 'Number Manipulation'], ['voice', 'Voice-Based'], ['phone', 'Phone'], + ['code', 'Coding Tools'], ['other', 'Other'], ['roleplay', 'Roleplay'] ]) diff --git a/assets/json/activity.json b/assets/json/activity.json index 1a74d084..f50fc1b7 100644 --- a/assets/json/activity.json +++ b/assets/json/activity.json @@ -46,5 +46,9 @@ { "text": "with a murderous cow", "type": "PLAYING" + }, + { + "text": "with a linter", + "type": "PLAYING" } ] diff --git a/assets/json/lint.json b/assets/json/lint.json new file mode 100644 index 00000000..8785fb80 --- /dev/null +++ b/assets/json/lint.json @@ -0,0 +1,41 @@ +{ + "goodMessages": [ + "It's all good fam.", + "Nice job.", + "Great code.", + "I like.", + "Nice code, bro.", + "Nice code!", + "Always a pleasure to see good and nice code." + ], + "badMessages": [ + "I think you may want to take a second look.", + "Looks like you have some problems!", + "Did you lint your code?", + "Get a linter bro.", + "Uh oh!", + "I don't like this code.", + "BAD!", + "Did you try to copy that off of GitHub?", + "Do you know JS?", + "smh", + "no", + "You are dumb because:", + "You tried, but failed miserably.", + "What a noob." + ], + "defaultConfig": { + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "env": { + "es6": true, + "node": true + }, + "rules": { + "no-console": 0 + } + } +} diff --git a/commands/code/beautify.js b/commands/code/beautify.js new file mode 100644 index 00000000..718cdf16 --- /dev/null +++ b/commands/code/beautify.js @@ -0,0 +1,31 @@ +const Command = require('../../structures/Command'); +const { js_beautify: beautify } = require('js-beautify'); +const { stripIndents } = require('common-tags'); + +module.exports = class BeautifyCommand extends Command { + constructor(client) { + super(client, { + name: 'beautify', + aliases: ['js-beautify'], + group: 'code', + memberName: 'lint', + description: 'Beautifies code with js-beautify.', + clientPermissions: ['READ_MESSAGE_HISTORY'], + args: [ + { + key: 'code', + prompt: 'What code do you want to beautify?', + type: 'code' + } + ] + }); + } + + async run(msg, { code }) { + return msg.reply(stripIndents` + \`\`\`${code.lang || 'js'} + ${beautify(code.code)} + \`\`\` + `); + } +}; diff --git a/commands/code/lint-rule.js b/commands/code/lint-rule.js new file mode 100644 index 00000000..e6f43be9 --- /dev/null +++ b/commands/code/lint-rule.js @@ -0,0 +1,38 @@ +const Command = require('../../structures/Command'); +const { MessageEmbed } = require('discord.js'); +const { Linter } = require('eslint'); +const linter = new Linter(); +const rules = linter.getRules(); + +module.exports = class LintRuleCommand extends Command { + constructor(client) { + super(client, { + name: 'lint-rule', + aliases: ['eslint-rule'], + group: 'code', + memberName: 'lint-rule', + description: 'Responds with information on an ESLint rule.', + clientPermissions: ['EMBED_LINKS'], + args: [ + { + key: 'rule', + prompt: 'Which rule would you like to get information on?', + type: 'string', + parse: rule => rule.toLowerCase().replace(/ /g, '-') + } + ] + }); + } + + run(msg, { rule }) { + if (!rules.has(rule)) return msg.say('Could not find any results.'); + const data = rules.get(rule).meta; + const embed = new MessageEmbed() + .setAuthor('ESLint', 'https://i.imgur.com/04GhEhU.png', 'https://eslint.org/') + .setColor(0x3A33D1) + .setTitle(`${rule} (${data.docs.category})`) + .setURL(`https://eslint.org/docs/rules/${rule}`) + .setDescription(data.docs.description); + return msg.embed(embed); + } +}; diff --git a/commands/code/lint.js b/commands/code/lint.js new file mode 100644 index 00000000..460dcc0c --- /dev/null +++ b/commands/code/lint.js @@ -0,0 +1,50 @@ +const Command = require('../../structures/Command'); +const { Linter } = require('eslint'); +const linter = new Linter(); +const { stripIndents } = require('common-tags'); +const { trimArray } = require('../../util/Util'); +const { goodMessages, badMessages, defaultConfig } = require('../../assets/json/lint'); + +module.exports = class LintCommand extends Command { + constructor(client) { + super(client, { + name: 'lint', + aliases: ['eslint'], + group: 'code', + memberName: 'lint', + description: 'Lints code using ESLint.', + clientPermissions: ['READ_MESSAGE_HISTORY'], + args: [ + { + key: 'code', + prompt: 'What code do you want to lint?', + type: 'code' + } + ] + }); + } + + async run(msg, { code }) { + if (!code.lang || ['js', 'javascript'].includes(code.lang)) { + const errors = linter.verify(code.code, defaultConfig); + if (!errors.length) return msg.reply(goodMessages[Math.floor(Math.random() * goodMessages.length)]); + const errorMap = trimArray(errors.map(err => `\`[${err.line}:${err.column}] ${err.message}\``)); + return msg.reply(stripIndents` + ${badMessages[Math.floor(Math.random() * badMessages.length)]} + ${errorMap.join('\n')} + `); + } + if (code.lang === 'json') { + try { + JSON.parse(code.code); + return msg.reply(goodMessages[Math.floor(Math.random() * goodMessages.length)]); + } catch (err) { + return msg.reply(stripIndents` + ${badMessages[Math.floor(Math.random() * badMessages.length)]} + \`${err.name}: ${err.message}\` + `); + } + } + return msg.reply('I don\'t know how to lint that language.'); + } +}; diff --git a/package.json b/package.json index d67de556..07ecacf6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "119.27.1", + "version": "119.28.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": { @@ -44,8 +44,10 @@ "discord.js": "^12.3.1", "discord.js-commando": "github:discordjs/Commando", "dotenv": "^8.2.0", + "eslint": "^7.10.0", "gifencoder": "^2.0.1", "gm": "^1.23.1", + "js-beautify": "^1.13.0", "mathjs": "^7.3.0", "moment": "^2.29.0", "moment-duration-format": "^2.3.2", @@ -62,7 +64,6 @@ "utf-8-validate": "^5.0.2" }, "devDependencies": { - "eslint": "^7.10.0", "eslint-config-amber": "^2.0.3", "eslint-plugin-json": "^2.1.2" }, diff --git a/types/code.js b/types/code.js new file mode 100644 index 00000000..c59c00e2 --- /dev/null +++ b/types/code.js @@ -0,0 +1,33 @@ +const { ArgumentType } = require('discord.js-commando'); +const codeblock = /```(?:(\S+)\n)?\s*([^]+?)\s*```/i; + +module.exports = class CodeArgumentType extends ArgumentType { + constructor(client) { + super(client, 'code'); + } + + async validate(value) { + if (!value) return false; + return true; + } + + parse(value) { + if (!value) return null; + if (/^[0-9]+$/.test(value)) { + try { + const message = await msg.channel.messages.fetch(value); + value = message.content; + } catch (err) { + return { code: value, lang: null }; + } + } + if (codeblock.test(value)) { + const parsed = codeblock.exec(value); + return { + code: parsed[2], + lang: parsed[1] ? parsed[1].toLowerCase() : null + }; + } + return { code: value, lang: null }; + } +};