diff --git a/README.md b/README.md index 205b8a67..60771f6a 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,22 @@ Xiao is a Discord bot coded in JavaScript with [discord.js](https://discord.js.org/) using the -[Commando](https://github.com/discordjs/Commando) command framework. With +[Commando](https://github.com/discordjs/Commando) command framework. With nearly 300 commands, she is one of the most feature-filled bots out there. ## Invite The bot is no longer available for invite. You can self-host the bot, or use her on the [home server](https://discord.gg/sbMe32W). -## Commands (300) +## Commands (291) ### Utility: * **eval**: Executes JavaScript code. -* **changelog**: Responds with Xiao's latest 10 commits. -* **donate**: Responds with Xiao's donation links. +* **changelog**: Responds with the bot's latest 10 commits. +* **donate**: Responds with the bot's donation links. * **help**: Displays a list of available commands, or detailed information for a specific command. * **info**: Responds with detailed bot information. -* **invite**: Responds with Xiao's invite links. +* **invite**: Responds with the bot's invite links. * **ping**: Checks the bot's ping to the Discord server. ### Discord Information: @@ -63,7 +63,6 @@ on the [home server](https://discord.gg/sbMe32W). * **kiss-marry-kill**: Determines who to kiss, who to marry, and who to kill. * **magic-conch**: Asks your question to the Magic Conch. * **name**: Responds with a random name, with the gender of your choice. -* **new-york-times**: Searches the New York Times for your query. * **number-fact**: Responds with a random fact about a specific number. * **offspring**: Determines if your new child will be a boy or a girl. * **opinion**: Determines the opinion on something. @@ -94,7 +93,6 @@ on the [home server](https://discord.gg/sbMe32W). * **its-joke**: It's joke! * **just-do-it**: Sends a link to the "Just Do It!" motivational speech. * **lenny**: Responds with the lenny face. -* **nitro**: Sends the "This message can only be viewed by users with Discord Nitro." message. * **slow-clap**: _slow clap_ * **spam**: Responds with a picture of Spam. * **tableflip**: Flips a table... With animation! @@ -116,16 +114,13 @@ on the [home server](https://discord.gg/sbMe32W). ### Search: * **bulbapedia**: Searches Bulbapedia for your query. -* **danbooru**: Responds with an image from Danbooru, with optional query. * **derpibooru**: Responds with an image from Derpibooru. * **deviantart**: Responds with an image from a DeviantArt section, with optional query. * **dictionary**: Defines a word. * **discord-js-docs**: Searches the Discord.js docs for your query. * **eshop**: Searches the Nintendo eShop for your query. -* **esrb**: Searches ESRB for your query. * **flickr**: Searches Flickr for your query. * **forecast**: Responds with the seven-day forecast for a specific location. -* **gelbooru**: Responds with an image from Gelbooru, with optional query. * **giphy**: Searches Giphy for your query. * **github**: Responds with information on a GitHub repository. * **google-autofill**: Responds with a list of the Google Autofill results for a particular query. @@ -138,7 +133,6 @@ on the [home server](https://discord.gg/sbMe32W). * **itunes**: Searches iTunes for your query. * **jisho**: Defines a word, but with Japanese. * **kickstarter**: Searches Kickstarter for your query. -* **konachan**: Responds with an image from Konachan, with optional query. * **league-of-legends-champion**: Responds with information on a League of Legends champion. * **map**: Responds with a map of a specific location. * **mdn**: Searches MDN for your query. @@ -154,9 +148,7 @@ on the [home server](https://discord.gg/sbMe32W). * **recipe**: Searches for recipes based on your query. * **rotten-tomatoes**: Searches Rotten Tomatoes for your query. * **rule-of-the-internet**: Responds with a rule of the internet. -* **rule34**: Responds with an image from Rule34, with optional query. * **safebooru**: Responds with an image from Safebooru, with optional query. -* **scrabble-score**: Responds with the scrabble score of a word. * **stack-overflow**: Searches Stack Overflow for your query. * **steam**: Searches Steam for your query. * **stocks**: Responds with the current stocks for a specific symbol. @@ -323,7 +315,6 @@ on the [home server](https://discord.gg/sbMe32W). * **webhook**: Posts a message to the webhook defined in your `process.env`. * **yoda**: Converts text to Yoda speak. * **zalgo**: Converts text to zalgo. -* **🅱**: Replaces b with 🅱. ### Number Manipulation: @@ -331,11 +322,11 @@ on the [home server](https://discord.gg/sbMe32W). * **final-grade-calculator**: Determines the grade you need to make on your final to get your desired course grade. * **math**: Evaluates a math expression. * **roman-numeral**: Converts a number to roman numerals. +* **scrabble-score**: Responds with the scrabble score of a word. * **units**: Converts units to/from other units. ### Other: -* **cleverbot**: Chat with Cleverbot. * **prune**: Deletes up to 99 messages from the current channel. * **strawpoll**: Generates a Strawpoll with the options you provide. diff --git a/commands/analyze/dick.js b/commands/analyze/dick.js index 05aafabc..806a2107 100644 --- a/commands/analyze/dick.js +++ b/commands/analyze/dick.js @@ -9,6 +9,7 @@ module.exports = class DickCommand extends Command { group: 'analyze', memberName: 'dick', description: 'Determines your dick size.', + nsfw: true, args: [ { key: 'user', diff --git a/commands/analyze/gender.js b/commands/analyze/gender.js index 00d242d3..1c6d5768 100644 --- a/commands/analyze/gender.js +++ b/commands/analyze/gender.js @@ -11,31 +11,21 @@ module.exports = class GenderAnalyzeCommand extends Command { description: 'Determines the gender of a name.', args: [ { - key: 'first', - label: 'first name', - prompt: 'What first name do you want to determine the gender of?', - type: 'string', - max: 500, - parse: first => encodeURIComponent(first) - }, - { - key: 'last', - label: 'last name', - prompt: 'What last name do you want to determine the gender of?', - type: 'string', - default: 'null', - max: 500, - parse: last => encodeURIComponent(last) + key: 'name', + prompt: 'What name do you want to determine the gender of?', + type: 'string' } ] }); } - async run(msg, { first, last }) { + async run(msg, { name }) { try { - const { body } = await request.get(`https://api.namsor.com/onomastics/api/json/gender/${first}/${last}`); - if (body.gender === 'unknown') return msg.say(`I have no idea what gender ${body.firstName} is.`); - return msg.say(`I'm ${Math.abs(body.scale * 100)}% sure ${body.firstName} is a ${body.gender} name.`); + const { body } = await request + .get(`https://api.genderize.io/`) + .query({ name }); + if (!body.gender) return msg.say(`I have no idea what gender ${body.name} is.`); + return msg.say(`I'm ${body.probability * 100}% sure ${body.name} is a ${body.gender} name.`); } catch (err) { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } diff --git a/commands/analyze/ship.js b/commands/analyze/ship.js index e02dce5f..28939fcd 100644 --- a/commands/analyze/ship.js +++ b/commands/analyze/ship.js @@ -1,6 +1,5 @@ const Command = require('../../structures/Command'); const Random = require('random-js'); -const { stripIndents } = require('common-tags'); module.exports = class ShipCommand extends Command { constructor(client) { @@ -30,10 +29,6 @@ module.exports = class ShipCommand extends Command { if (first.id === second.id) return msg.reply('Shipping someone with themselves would be pretty weird.'); const random = new Random(Random.engines.mt19937().seed(Math.abs(first.id - second.id))); const level = random.integer(0, 100); - const repeat = Math.floor(level / 5); - return msg.say(stripIndents` - ${first.username} and ${second.username} have a compatability of... **${level}%**! - 💟 \`[${'■'.repeat(repeat)}${' '.repeat(20 - repeat)}]\` 💟 - `); + return msg.say(`${first.username} and ${second.username} have a compatability of... **${level}%**!`); } }; diff --git a/commands/info/emoji-image.js b/commands/info/emoji-image.js index 382792c5..65aedc6f 100644 --- a/commands/info/emoji-image.js +++ b/commands/info/emoji-image.js @@ -21,6 +21,6 @@ module.exports = class EmojiImageCommand extends Command { } run(msg, { emoji }) { - return msg.say({ files: [emoji.url] }); + return msg.say(emoji.url); } }; diff --git a/commands/info/message.js b/commands/info/message.js new file mode 100644 index 00000000..d7ef5174 --- /dev/null +++ b/commands/info/message.js @@ -0,0 +1,34 @@ +const Command = require('../../structures/Command'); +const { MessageEmbed } = require('discord.js'); + +module.exports = class MessageInfoCommand extends Command { + constructor(client) { + super(client, { + name: 'message-info', + aliases: ['message', 'msg', 'msg-info', 'reply'], + group: 'info', + memberName: 'message', + description: 'Responds with detailed information on a message.', + clientPermissions: ['EMBED_LINKS'], + args: [ + { + key: 'message', + prompt: 'Which message would you like to get information on?', + type: 'message' + } + ] + }); + } + + run(msg, { message }) { + const embed = new MessageEmbed() + .setColor(message.member ? message.member.displayHexColor : 0x00AE86) + .setThumbnail(message.author.displayAvatarURL()) + .setAuthor(msg.author.tag, msg.author.displayAvatarURL()) + .setDescription(message.content) + .setTimestamp(message.createdAt) + .setFooter(`ID: ${message.id}`) + .addField('❯ Jump', message.url); + return msg.embed(embed); + } +}; diff --git a/commands/search/scrabble-score.js b/commands/number-edit/scrabble-score.js similarity index 97% rename from commands/search/scrabble-score.js rename to commands/number-edit/scrabble-score.js index 78fbc8a1..5e267167 100644 --- a/commands/search/scrabble-score.js +++ b/commands/number-edit/scrabble-score.js @@ -6,7 +6,7 @@ module.exports = class ScrabbleScoreCommand extends Command { super(client, { name: 'scrabble-score', aliases: ['scrabble'], - group: 'search', + group: 'number-edit', memberName: 'scrabble-score', description: 'Responds with the scrabble score of a word.', args: [ diff --git a/commands/other/cleverbot.js b/commands/other/cleverbot.js deleted file mode 100644 index 3caf1400..00000000 --- a/commands/other/cleverbot.js +++ /dev/null @@ -1,42 +0,0 @@ -const Command = require('../../structures/Command'); -const request = require('node-superfetch'); -const { CLEVERBOT_KEY } = process.env; - -module.exports = class CleverbotCommand extends Command { - constructor(client) { - super(client, { - name: 'cleverbot', - aliases: ['clevs'], - group: 'other', - memberName: 'cleverbot', - description: 'Chat with Cleverbot.', - details: 'Only the bot owner(s) may use this command.', - ownerOnly: true, - args: [ - { - key: 'message', - prompt: 'What do you want to say to Cleverbot?', - type: 'string' - } - ] - }); - - this.convos = new Map(); - } - - async run(msg, { message }) { - try { - const { body } = await request - .get('https://www.cleverbot.com/getreply') - .query({ - key: CLEVERBOT_KEY, - cs: this.convos.get(msg.channel.id), - input: message - }); - this.convos.set(msg.channel.id, body.cs); - return msg.reply(body.output); - } catch (err) { - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } -}; diff --git a/commands/random/advice-slip.js b/commands/random/advice-slip.js index 21fce1a2..25c543fe 100644 --- a/commands/random/advice-slip.js +++ b/commands/random/advice-slip.js @@ -15,7 +15,8 @@ module.exports = class AdviceSlipCommand extends Command { async run(msg) { try { const { text } = await request.get('http://api.adviceslip.com/advice'); - return msg.say(JSON.parse(text).slip.advice); + const body = JSON.parse(text); + return msg.say(`${body.slip.advice} (#${body.slip.slip_id})`); } catch (err) { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } diff --git a/commands/random/new-york-times.js b/commands/random/new-york-times.js deleted file mode 100644 index 0abb8cd5..00000000 --- a/commands/random/new-york-times.js +++ /dev/null @@ -1,51 +0,0 @@ -const Command = require('../../structures/Command'); -const { MessageEmbed } = require('discord.js'); -const request = require('node-superfetch'); -const { shorten } = require('../../util/Util'); -const { NYTIMES_KEY } = process.env; - -module.exports = class NewYorkTimesCommand extends Command { - constructor(client) { - super(client, { - name: 'new-york-times', - aliases: ['ny-times', 'new-york-times-article', 'ny-times-article'], - group: 'random', - memberName: 'new-york-times', - description: 'Searches the New York Times for your query.', - clientPermissions: ['EMBED_LINKS'], - args: [ - { - key: 'query', - prompt: 'What do you want to search for articles about?', - type: 'string', - default: '' - } - ] - }); - } - - async run(msg, { query }) { - try { - const fetch = request - .get('https://api.nytimes.com/svc/search/v2/articlesearch.json') - .query({ - 'api-key': NYTIMES_KEY, - sort: 'newest' - }); - if (query) fetch.query({ q: query }); - const { body } = await fetch; - if (!body.response.docs.length) return msg.say('Could not find any results'); - const data = body.response.docs[Math.floor(Math.random() * body.response.docs.length)]; - const embed = new MessageEmbed() - .setColor(0xF6F6F6) - .setAuthor('New York Times', 'https://i.imgur.com/ZbuTWwO.png', 'https://www.nytimes.com/') - .setURL(data.web_url) - .setTitle(data.headline.main) - .setDescription(shorten(data.snippet)) - .addField('❯ Publish Date', new Date(data.pub_date).toDateString(), true); - return msg.embed(embed); - } catch (err) { - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } -}; diff --git a/commands/search/danbooru.js b/commands/search/danbooru.js deleted file mode 100644 index d5f1f441..00000000 --- a/commands/search/danbooru.js +++ /dev/null @@ -1,42 +0,0 @@ -const Command = require('../../structures/Command'); -const request = require('node-superfetch'); - -module.exports = class DanbooruCommand extends Command { - constructor(client) { - super(client, { - name: 'danbooru', - aliases: ['danbooru-image'], - group: 'search', - memberName: 'danbooru', - description: 'Responds with an image from Danbooru, with optional query.', - nsfw: true, - args: [ - { - key: 'query', - prompt: 'What image would you like to search for?', - type: 'string', - default: '', - validate: query => { - if (!query.includes(' ')) return true; - return 'Invalid query, please only search for one tag at a time.'; - } - } - ] - }); - } - - async run(msg, { query }) { - try { - const { body } = await request - .get('https://danbooru.donmai.us/posts.json') - .query({ - tags: `${query} order:random`, - limit: 1 - }); - if (!body.length || !body[0].file_url) return msg.say('Could not find any results.'); - return msg.say(body[0].file_url); - } catch (err) { - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } -}; diff --git a/commands/search/derpibooru.js b/commands/search/derpibooru.js index 24396abd..e95f551c 100644 --- a/commands/search/derpibooru.js +++ b/commands/search/derpibooru.js @@ -21,18 +21,29 @@ module.exports = class DerpibooruCommand extends Command { async run(msg, { query }) { try { - const search = await request - .get('https://derpibooru.org/search.json') - .query({ - q: query, - random_image: 1 - }); - if (!search.body) return msg.say('Could not find any results.'); - const { body } = await request.get(`https://derpibooru.org/images/${search.body.id}.json`); - return msg.say(`https:${body.representations.full}`); + const id = await this.search(query); + if (!id) return msg.say('Could not find any results.'); + const url = await this.fetchImage(id); + return msg.say(url); } catch (err) { if (err.status === 404) return msg.say('Could not find any results.'); return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } } + + async search(query) { + const { body } = await request + .get('https://derpibooru.org/search.json') + .query({ + q: query, + random_image: 1 + }); + if (!body) return null; + return body.id; + } + + async fetchImage(id) { + const { body } = await request.get(`https://derpibooru.org/images/${id}.json`); + return `${body.representations.full}`; + } }; diff --git a/commands/search/dictionary.js b/commands/search/dictionary.js index 740f083c..14653032 100644 --- a/commands/search/dictionary.js +++ b/commands/search/dictionary.js @@ -36,7 +36,7 @@ module.exports = class DictionaryCommand extends Command { const data = body[0]; return msg.say(stripIndents` **${data.word}** - (${data.partOfSpeech || '???'}) ${data.text} + (${data.partOfSpeech || 'unknown'}) ${data.text} `); } catch (err) { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); diff --git a/commands/search/discord-js-docs.js b/commands/search/discord-js-docs.js index 63a962b3..c2763985 100644 --- a/commands/search/discord-js-docs.js +++ b/commands/search/discord-js-docs.js @@ -39,6 +39,7 @@ module.exports = class DiscordJSDocsCommand extends Command { const { body } = await request .get(`https://djsdocs.sorta.moe/${project}/${branch}/embed`) .query({ q: query }); + if (!body) return msg.say('Could not find any results.'); return msg.embed(body); } catch (err) { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); diff --git a/commands/search/esrb.js b/commands/search/esrb.js deleted file mode 100644 index fd6f15b1..00000000 --- a/commands/search/esrb.js +++ /dev/null @@ -1,66 +0,0 @@ -const Command = require('../../structures/Command'); -const request = require('node-superfetch'); -const { MessageEmbed } = require('discord.js'); -const ratings = { - EC: 'Early Childhood', - E: 'Everyone', - E10plus: 'Everyone 10+', - T: 'Teen', - M: 'Mature', - AO: 'Adults Only' -}; - -module.exports = class ESRBCommand extends Command { - constructor(client) { - super(client, { - name: 'esrb', - aliases: ['esrb-rating'], - group: 'search', - memberName: 'esrb', - description: 'Searches ESRB for your query.', - args: [ - { - key: 'query', - prompt: 'What game would you like to get the rating of?', - type: 'string' - } - ] - }); - } - - async run(msg, { query }) { - try { - const data = await this.fetchRating(query); - const embed = new MessageEmbed() - .setColor(0x231F20) - .setAuthor('ESRB', 'https://i.imgur.com/6KAG7gD.png', 'http://www.esrb.org/') - .setTitle(data.title) - .setURL(data.url) - .setThumbnail(data.ratingImage) - .addField('❯ Rating', ratings[data.rating]); - return msg.embed(embed); - } catch (err) { - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } - - async fetchRating(query) { - const { text, url } = await request - .get('http://www.esrb.org/ratings/search.aspx') - .query({ - from: 'home', - titleOrPublisher: query - }); - const title = text.match(/(.+)<\/strong>/); - if (!title) return null; - const rating = text.match( - /https:\/\/esrbstorage.blob.core.windows.net\/esrbcontent\/images\/(EC|E|E10plus|T|M|AO).png/ - ); - return { - title: title[1].trim(), - rating: rating[1], - ratingImage: rating[0], - url - }; - } -}; diff --git a/commands/search/gelbooru.js b/commands/search/gelbooru.js deleted file mode 100644 index bb260ad5..00000000 --- a/commands/search/gelbooru.js +++ /dev/null @@ -1,42 +0,0 @@ -const Command = require('../../structures/Command'); -const request = require('node-superfetch'); - -module.exports = class GelbooruCommand extends Command { - constructor(client) { - super(client, { - name: 'gelbooru', - aliases: ['gelbooru-image'], - group: 'search', - memberName: 'gelbooru', - description: 'Responds with an image from Gelbooru, with optional query.', - nsfw: true, - args: [ - { - key: 'query', - prompt: 'What image would you like to search for?', - type: 'string', - default: '' - } - ] - }); - } - - async run(msg, { query }) { - try { - const { body } = await request - .get('https://gelbooru.com/index.php') - .query({ - page: 'dapi', - s: 'post', - q: 'index', - json: 1, - tags: query, - limit: 200 - }); - if (!body) return msg.say('Could not find any results.'); - return msg.say(body[Math.floor(Math.random() * body.length)].file_url); - } catch (err) { - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } -}; diff --git a/commands/search/http-cat.js b/commands/search/http-cat.js index 999068bf..25129550 100644 --- a/commands/search/http-cat.js +++ b/commands/search/http-cat.js @@ -22,7 +22,7 @@ module.exports = class HttpCatCommand extends Command { async run(msg, { code }) { try { const { body, headers } = await request.get(`https://http.cat/${code}.jpg`); - if (headers['content-type'] === 'text/html') return msg.say('Could not find any results.'); + if (headers['content-type'].includes('text/html')) return msg.say('Could not find any results.'); return msg.say({ files: [{ attachment: body, name: `${code}.jpg` }] }); } catch (err) { if (err.status === 404) return msg.say('Could not find any results.'); diff --git a/commands/search/konachan.js b/commands/search/konachan.js deleted file mode 100644 index c7186b4c..00000000 --- a/commands/search/konachan.js +++ /dev/null @@ -1,38 +0,0 @@ -const Command = require('../../structures/Command'); -const request = require('node-superfetch'); - -module.exports = class KonachanCommand extends Command { - constructor(client) { - super(client, { - name: 'konachan', - aliases: ['konachan-image'], - group: 'search', - memberName: 'konachan', - description: 'Responds with an image from Konachan, with optional query.', - nsfw: true, - args: [ - { - key: 'query', - prompt: 'What image would you like to search for?', - type: 'string', - default: '' - } - ] - }); - } - - async run(msg, { query }) { - try { - const { body } = await request - .get('https://konachan.net/post.json') - .query({ - tags: `${query} order:random`, - limit: 1 - }); - if (!body.length || !body[0].file_url) return msg.say('Could not find any results.'); - return msg.say(`https:${body[0].file_url}`); - } catch (err) { - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } -}; diff --git a/commands/search/neopets-item.js b/commands/search/neopets-item.js index bf29ceb0..38b95cc1 100644 --- a/commands/search/neopets-item.js +++ b/commands/search/neopets-item.js @@ -50,13 +50,12 @@ module.exports = class NeopetsItemCommand extends Command { if (!id) return null; const price = text.match(/>([0-9,]+) (NP|NC)(.+)<\/h1>/)[1], - details: detailsText.match(/(.+)<\/em>/)[1], + name: details.match(/

(.+)<\/h1>/)[1], + details: details.match(/(.+)<\/em>/)[1], image: `https://items.jellyneo.net/assets/imgs/items/${id[1]}.gif`, price: price ? Number.parseInt(price[1].replace(/,/g, ''), 10) : null, currency: price ? price[2] : null diff --git a/commands/search/rotten-tomatoes.js b/commands/search/rotten-tomatoes.js index 123a6944..0dfdf2bd 100644 --- a/commands/search/rotten-tomatoes.js +++ b/commands/search/rotten-tomatoes.js @@ -24,26 +24,18 @@ module.exports = class RottenTomatoesCommand extends Command { async run(msg, { query }) { try { - const search = await request - .get('https://www.rottentomatoes.com/api/private/v2.0/search/') - .query({ - limit: 10, - q: query - }); - if (!search.body.movies.length) return msg.say('Could not find any results.'); - const find = search.body.movies.find(m => m.name.toLowerCase() === query.toLowerCase()) || search.body.movies[0]; - const urlID = find.url.replace('/m/', ''); - const { text } = await request.get(`https://www.rottentomatoes.com/api/private/v1.0/movies/${urlID}`); - const body = JSON.parse(text); - const criticScore = body.ratingSummary.allCritics; - const audienceScore = body.ratingSummary.audience; + const id = await this.search(query); + if (!id) return msg.say('Could not find any results.'); + const data = await this.fetchMovie(id); + const criticScore = data.ratingSummary.allCritics; + const audienceScore = data.ratingSummary.audience; const embed = new MessageEmbed() .setColor(0xFFEC02) - .setTitle(`${body.title} (${body.year})`) - .setURL(`https://www.rottentomatoes.com${body.url}`) + .setTitle(`${data.title} (${data.year})`) + .setURL(`https://www.rottentomatoes.com${data.url}`) .setAuthor('Rotten Tomatoes', 'https://i.imgur.com/Sru8mZ3.jpg', 'https://www.rottentomatoes.com/') - .setDescription(shorten(body.ratingSummary.consensus)) - .setThumbnail(body.posters.original) + .setDescription(shorten(data.ratingSummary.consensus)) + .setThumbnail(data.posters.original) .addField('❯ Critic Score', criticScore.meterValue ? `${criticScore.meterValue}%` : '???', true) .addField('❯ Audience Score', audienceScore.meterScore ? `${audienceScore.meterScore}%` : '???', true); return msg.embed(embed); @@ -51,4 +43,21 @@ module.exports = class RottenTomatoesCommand extends Command { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } } + + async search(query) { + const { body } = await request + .get('https://www.rottentomatoes.com/api/private/v2.0/search/') + .query({ + limit: 10, + q: query + }); + if (!body.movies.length) return null; + const find = body.movies.find(m => m.name.toLowerCase() === query.toLowerCase()) || body.movies[0]; + return find.url.replace('/m/', ''); + } + + async fetchMovie(id) { + const { text } = await request.get(`https://www.rottentomatoes.com/api/private/v1.0/movies/${id}`); + return JSON.parse(text); + } }; diff --git a/commands/search/rule-of-the-internet.js b/commands/search/rule-of-the-internet.js index ab56d5c5..bdb6fa42 100644 --- a/commands/search/rule-of-the-internet.js +++ b/commands/search/rule-of-the-internet.js @@ -14,7 +14,6 @@ module.exports = class RuleOfTheInternetCommand extends Command { key: 'rule', prompt: 'Which rule would you like to view?', type: 'integer', - default: '', min: 1, max: rules.length } @@ -23,7 +22,6 @@ module.exports = class RuleOfTheInternetCommand extends Command { } run(msg, { rule }) { - if (!rule) return msg.say({ files: ['https://i.imgur.com/vGw29EQ.jpg'] }); return msg.say(`**Rule #${rule}**: ${rules[rule - 1]}`); } }; diff --git a/commands/search/rule34.js b/commands/search/rule34.js deleted file mode 100644 index b058ab69..00000000 --- a/commands/search/rule34.js +++ /dev/null @@ -1,44 +0,0 @@ -const Command = require('../../structures/Command'); -const request = require('node-superfetch'); - -module.exports = class Rule34Command extends Command { - constructor(client) { - super(client, { - name: 'rule34', - aliases: ['rule34-image', 'r34'], - group: 'search', - memberName: 'rule34', - description: 'Responds with an image from Rule34, with optional query.', - nsfw: true, - args: [ - { - key: 'query', - prompt: 'What image would you like to search for?', - type: 'string', - default: '' - } - ] - }); - } - - async run(msg, { query }) { - try { - const { text } = await request - .get('https://rule34.xxx/index.php') - .query({ - page: 'dapi', - s: 'post', - q: 'index', - json: 1, - tags: query, - limit: 200 - }); - if (!text) return msg.say('Could not find any results.'); - const body = JSON.parse(text); - const data = body[Math.floor(Math.random() * body.length)]; - return msg.say(`https://rule34.xxx/images/${data.directory}/${data.image}`); - } catch (err) { - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } -}; diff --git a/commands/search/steam.js b/commands/search/steam.js index 496b552c..6faf3186 100644 --- a/commands/search/steam.js +++ b/commands/search/steam.js @@ -23,19 +23,9 @@ module.exports = class SteamCommand extends Command { async run(msg, { query }) { try { - const search = await request - .get('https://store.steampowered.com/api/storesearch') - .query({ - cc: 'us', - l: 'en', - term: query - }); - if (!search.body.items.length) return msg.say('Could not find any results.'); - const { id, tiny_image } = search.body.items[0]; - const { body } = await request - .get('https://store.steampowered.com/api/appdetails') - .query({ appids: id }); - const { data } = body[id.toString()]; + const id = await this.search(query); + if (!id) return msg.say('Could not find any results.'); + const data = await this.fetchGame(id); const current = data.price_overview ? `$${data.price_overview.final / 100}` : 'Free'; const original = data.price_overview ? `$${data.price_overview.initial / 100}` : 'Free'; const price = current === original ? current : `~~${original}~~ ${current}`; @@ -50,7 +40,7 @@ module.exports = class SteamCommand extends Command { .setAuthor('Steam', 'https://i.imgur.com/xxr2UBZ.png', 'http://store.steampowered.com/') .setTitle(data.name) .setURL(`http://store.steampowered.com/app/${data.steam_appid}`) - .setThumbnail(tiny_image) + .setThumbnail(data.header_image) .addField('❯ Price', price, true) .addField('❯ Metascore', data.metacritic ? data.metacritic.score : '???', true) .addField('❯ Recommendations', data.recommendations ? data.recommendations.total : '???', true) @@ -64,4 +54,23 @@ module.exports = class SteamCommand extends Command { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } } + + async search(query) { + const { body } = await request + .get('https://store.steampowered.com/api/storesearch') + .query({ + cc: 'us', + l: 'en', + term: query + }); + if (!body.items.length) return null; + return body.items[0].id; + } + + async fetchGame(id) { + const { body } = await request + .get('https://store.steampowered.com/api/appdetails') + .query({ appids: id }); + return body[id.toString()].data; + } }; diff --git a/commands/search/wikia.js b/commands/search/wikia.js index 2f9d4b88..a83e59f3 100644 --- a/commands/search/wikia.js +++ b/commands/search/wikia.js @@ -30,27 +30,38 @@ module.exports = class WikiaCommand extends Command { async run(msg, { wiki, query }) { try { - const search = await request - .get(`http://${wiki}.wikia.com/api/v1/Search/List/`) - .query({ - query, - limit: 1, - namespaces: 0 - }); - const { body } = await request - .get(`http://${wiki}.wikia.com/api/v1/Articles/AsSimpleJson/`) - .query({ id: search.body.items[0].id }); - const data = body.sections[0]; + const { id, url } = await this.search(wiki, query); + const data = await this.fetchArticle(wiki, id); const embed = new MessageEmbed() .setColor(0x002D54) .setTitle(data.title) - .setURL(search.body.items[0].url) + .setURL(url) .setAuthor('Wikia', 'https://i.imgur.com/15A34JT.png', 'http://www.wikia.com/fandom') .setDescription(shorten(data.content.map(section => section.text).join('\n\n'))) .setThumbnail(data.images.length ? data.images[0].src : null); return msg.embed(embed); } catch (err) { - return msg.say('Could not find any results.'); + if (err.status === 404) return msg.say('Could not find any results'); + return msg.say(`Oh no, an error occurred: \`${err.message}\`. Perhaps you entered an invalid wiki?`); } } + + async search(wiki, query) { + const { body } = await request + .get(`https://${wiki}.wikia.com/api/v1/Search/List/`) + .query({ + query, + limit: 1, + namespaces: 0 + }); + const data = body.items[0]; + return { id: data.id, url: data.url }; + } + + async fetchArticle(wiki, id) { + const { body } = await request + .get(`https://${wiki}.wikia.com/api/v1/Articles/AsSimpleJson/`) + .query({ id }); + return body.sections[0]; + } }; diff --git a/commands/single/nitro.js b/commands/single/nitro.js deleted file mode 100644 index 6f1b9b80..00000000 --- a/commands/single/nitro.js +++ /dev/null @@ -1,29 +0,0 @@ -const Command = require('../../structures/Command'); -const { MessageEmbed } = require('discord.js'); -const { stripIndents } = require('common-tags'); - -module.exports = class NitroCommand extends Command { - constructor(client) { - super(client, { - name: 'nitro', - aliases: ['discord-nitro', 'nitro-message', 'nitro-msg'], - group: 'single', - memberName: 'nitro', - description: 'Sends the "This message can only be viewed by users with Discord Nitro." message.', - clientPermissions: ['EMBED_LINKS'] - }); - } - - run(msg) { - const embed = new MessageEmbed() - .setAuthor('Discord Nitro', 'https://i.imgur.com/DKaY8fV.jpg', 'https://discordapp.com/nitro') - .setThumbnail('https://i.imgur.com/DKaY8fV.jpg') - .setColor(0x8395D3) - .setTimestamp() - .setDescription(stripIndents` - This message can only be viewed by users with Discord Nitro. - [More Information](https://discordapp.com/nitro) - `); - return msg.embed(embed); - } -}; diff --git a/commands/text-edit/🅱.js b/commands/text-edit/🅱.js deleted file mode 100644 index cc7287a0..00000000 --- a/commands/text-edit/🅱.js +++ /dev/null @@ -1,27 +0,0 @@ -const Command = require('../../structures/Command'); - -module.exports = class BCommand extends Command { - constructor(client) { - super(client, { - name: '🅱', - group: 'text-edit', - memberName: '🅱', - description: 'Replaces b with 🅱.', - args: [ - { - key: 'text', - prompt: 'What text would you like to 🅱?', - type: 'string', - validate: text => { - if (text.replace(/b/gi, '🅱').length < 2000) return true; - return 'Invalid text, your text is too long.'; - } - } - ] - }); - } - - run(msg, { text }) { - return msg.say(text.replace(/b/gi, '🅱')); - } -}; diff --git a/commands/util/changelog.js b/commands/util/changelog.js index 0c3ada26..e9013760 100644 --- a/commands/util/changelog.js +++ b/commands/util/changelog.js @@ -11,7 +11,7 @@ module.exports = class ChangelogCommand extends Command { aliases: ['updates', 'commits'], group: 'util', memberName: 'changelog', - description: 'Responds with Xiao\'s latest 10 commits.', + description: 'Responds with the bot\'s latest 10 commits.', guarded: true }); } diff --git a/commands/util/donate.js b/commands/util/donate.js index 7ad5311e..d7d91ea4 100644 --- a/commands/util/donate.js +++ b/commands/util/donate.js @@ -8,14 +8,14 @@ module.exports = class DonateCommand extends Command { aliases: ['patreon', 'paypal'], group: 'util', memberName: 'donate', - description: 'Responds with Xiao\'s donation links.', + description: 'Responds with the bot\'s donation links.', guarded: true }); } run(msg) { return msg.say(stripIndents` - Contribute to Xiao development! + Contribute to development! `); diff --git a/commands/util/help.js b/commands/util/help.js index f6ae8bd6..dffa709a 100644 --- a/commands/util/help.js +++ b/commands/util/help.js @@ -26,11 +26,13 @@ module.exports = class HelpCommand extends Command { if (!command) { const embed = new MessageEmbed() .setTitle('Command List') - .setDescription(`Use ${msg.usage('')} to view detailed information about a command.`) .setColor(0x00AE86) .setFooter(`${this.client.registry.commands.size} Commands`); for (const group of this.client.registry.groups.values()) { - embed.addField(`❯ ${group.name}`, group.commands.map(cmd => cmd.name).join(', ') || 'None'); + embed.addField( + `❯ ${group.name}`, + group.commands.map(cmd => `\`${cmd.name}\``).join(', ') || 'None' + ); } try { const msgs = []; diff --git a/commands/util/invite.js b/commands/util/invite.js index a050be54..ab7220c1 100644 --- a/commands/util/invite.js +++ b/commands/util/invite.js @@ -1,5 +1,6 @@ const Command = require('../../structures/Command'); const { stripIndents } = require('common-tags'); +const { XIAO_GITHUB_REPO_NAME, XIAO_GITHUB_REPO_USERNAME } = process.env; module.exports = class InviteCommand extends Command { constructor(client) { @@ -8,7 +9,7 @@ module.exports = class InviteCommand extends Command { aliases: ['join'], group: 'util', memberName: 'invite', - description: 'Responds with Xiao\'s invite links.', + description: 'Responds with the bot\'s invite links.', guarded: true }); } @@ -17,6 +18,9 @@ module.exports = class InviteCommand extends Command { return msg.say(stripIndents` You cannot invite me to your server, but you can join my home server to use me: ${this.client.options.invite || 'Coming soon...'} + + You can also self-host me if you prefer: + `); } }; diff --git a/package.json b/package.json index 5937610b..cb4fc613 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "86.0.0", + "version": "87.0.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": { diff --git a/structures/Command.js b/structures/Command.js index b4086684..44934e93 100644 --- a/structures/Command.js +++ b/structures/Command.js @@ -1,12 +1,10 @@ const { Command } = require('discord.js-commando'); -class XiaoCommand extends Command { +module.exports = class XiaoCommand extends Command { constructor(client, info) { super(client, info); this.argsSingleQuotes = info.argsSingleQuotes || false; this.throttling = info.throttling || { usages: 1, duration: 2 }; } -} - -module.exports = XiaoCommand; +}; diff --git a/types/avatar.js b/types/avatar.js index cab24e10..9b297bf5 100644 --- a/types/avatar.js +++ b/types/avatar.js @@ -1,6 +1,6 @@ const { ArgumentType } = require('discord.js-commando'); -class AvatarArgumentType extends ArgumentType { +module.exports = class AvatarArgumentType extends ArgumentType { constructor(client) { super(client, 'avatar'); } @@ -13,6 +13,4 @@ class AvatarArgumentType extends ArgumentType { const user = await this.client.registry.types.get('user').parse(value, msg, arg); return user.displayAvatarURL({ format: 'png', size: 512 }); } -} - -module.exports = AvatarArgumentType; +}; diff --git a/types/emoji.js b/types/emoji.js index 10f8559b..65bc2a4a 100644 --- a/types/emoji.js +++ b/types/emoji.js @@ -1,7 +1,7 @@ const { ArgumentType, util: { disambiguation } } = require('discord.js-commando'); const { escapeMarkdown } = require('discord.js'); -class EmojiArgumentType extends ArgumentType { +module.exports = class EmojiArgumentType extends ArgumentType { constructor(client) { super(client, 'emoji'); } @@ -11,13 +11,13 @@ class EmojiArgumentType extends ArgumentType { if (matches && msg.client.emojis.has(matches[2])) return true; if (!msg.guild) return false; const search = value.toLowerCase(); - let emojis = msg.guild.emojis.filterArray(nameFilterInexact(search)); - if (!emojis.length) return false; - if (emojis.length === 1) return true; + let emojis = msg.guild.emojis.filter(nameFilterInexact(search)); + if (!emojis.size) return false; + if (emojis.size === 1) return true; const exactEmojis = emojis.filter(nameFilterExact(search)); - if (exactEmojis.length === 1) return true; - if (exactEmojis.length > 0) emojis = exactEmojis; - return emojis.length <= 15 + if (exactEmojis.size === 1) return true; + if (exactEmojis.size > 0) emojis = exactEmojis; + return emojis.size <= 15 ? `${disambiguation(emojis.map(emoji => escapeMarkdown(emoji.name)), 'emojis', null)}\n` : 'Multiple emojis found. Please be more specific.'; } @@ -26,11 +26,11 @@ class EmojiArgumentType extends ArgumentType { const matches = value.match(/^(?:?$/); if (matches) return msg.client.emojis.get(matches[2]) || null; const search = value.toLowerCase(); - const emojis = msg.guild.emojis.filterArray(nameFilterInexact(search)); - if (!emojis.length) return null; - if (emojis.length === 1) return emojis[0]; - const exactEmojis = emojis.filterArray(nameFilterExact(search)); - if (exactEmojis.length === 1) return exactEmojis[0]; + const emojis = msg.guild.emojis.filter(nameFilterInexact(search)); + if (!emojis.size) return null; + if (emojis.size === 1) return emojis.first(); + const exactEmojis = emojis.filter(nameFilterExact(search)); + if (exactEmojis.size === 1) return exactEmojis.first(); return null; } } @@ -41,6 +41,4 @@ function nameFilterExact(search) { function nameFilterInexact(search) { return thing => thing.name.toLowerCase().includes(search); -} - -module.exports = EmojiArgumentType; +}; diff --git a/types/image.js b/types/image.js index d2bcd59c..8a1f0114 100644 --- a/types/image.js +++ b/types/image.js @@ -1,6 +1,6 @@ const { ArgumentType } = require('discord.js-commando'); -class ImageArgumentType extends ArgumentType { +module.exports = class ImageArgumentType extends ArgumentType { constructor(client) { super(client, 'image'); } @@ -19,6 +19,4 @@ class ImageArgumentType extends ArgumentType { isEmpty(value, msg) { return msg.attachments.size === 0; } -} - -module.exports = ImageArgumentType; +}; diff --git a/types/month.js b/types/month.js index f72ae1c0..41069077 100644 --- a/types/month.js +++ b/types/month.js @@ -1,7 +1,7 @@ const { ArgumentType } = require('discord.js-commando'); const months = require('../assets/json/month'); -class MonthArgumentType extends ArgumentType { +module.exports = class MonthArgumentType extends ArgumentType { constructor(client) { super(client, 'month'); } @@ -18,6 +18,4 @@ class MonthArgumentType extends ArgumentType { if (!Number.isNaN(num)) return num; return months.indexOf(value.toLowerCase()) + 1; } -} - -module.exports = MonthArgumentType; +}; diff --git a/util/Canvas.js b/util/Canvas.js index cdbd6165..d2d36b2f 100644 --- a/util/Canvas.js +++ b/util/Canvas.js @@ -1,4 +1,4 @@ -class CanvasUtil { +module.exports = class CanvasUtil { static greyscale(ctx, x, y, width, height) { const data = ctx.getImageData(x, y, width, height); for (let i = 0; i < data.data.length; i += 4) { @@ -93,6 +93,4 @@ class CanvasUtil { while (ctx.measureText(text).width > maxWidth) text = text.substr(0, text.length - 1); return shorten ? `${text}...` : text; } -} - -module.exports = CanvasUtil; +}; diff --git a/util/Util.js b/util/Util.js index 27a7769c..68c000ac 100644 --- a/util/Util.js +++ b/util/Util.js @@ -5,7 +5,7 @@ const yes = ['yes', 'y', 'ye', 'yeah', 'yup', 'yea']; const no = ['no', 'n', 'nah', 'nope']; const { SUCCESS_EMOJI_ID } = process.env; -class Util { +module.exports = class Util { static delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -125,6 +125,4 @@ class Util { if (no.includes(choice)) return false; return false; } -} - -module.exports = Util; +};