diff --git a/assets/images/hat/anime hair.png b/assets/images/hat/anime hair.png new file mode 100644 index 00000000..1dae31eb Binary files /dev/null and b/assets/images/hat/anime hair.png differ diff --git a/assets/images/christmas-hat.png b/assets/images/hat/christmas.png similarity index 100% rename from assets/images/christmas-hat.png rename to assets/images/hat/christmas.png diff --git a/assets/images/hat/top hat.png b/assets/images/hat/top hat.png new file mode 100644 index 00000000..cbf50467 Binary files /dev/null and b/assets/images/hat/top hat.png differ diff --git a/commands/avatar-edit/distort.js b/commands/avatar-edit/distort.js new file mode 100644 index 00000000..9e5ab933 --- /dev/null +++ b/commands/avatar-edit/distort.js @@ -0,0 +1,52 @@ +const { Command } = require('discord.js-commando'); +const { createCanvas, loadImage } = require('canvas'); +const snekfetch = require('snekfetch'); +const { distort } = require('../../util/Util'); + +module.exports = class DistortCommand extends Command { + constructor(client) { + super(client, { + name: 'distort', + group: 'avatar-edit', + memberName: 'distort', + description: 'Draws a user\'s avatar but distorted.', + throttling: { + usages: 1, + duration: 15 + }, + clientPermissions: ['ATTACH_FILES'], + args: [ + { + key: 'level', + prompt: 'What level of distortion would you like to use?', + type: 'integer' + }, + { + key: 'user', + prompt: 'Which user would you like to edit the avatar of?', + type: 'user', + default: '' + } + ] + }); + } + + async run(msg, { level, user }) { + if (!user) user = msg.author; + const avatarURL = user.displayAvatarURL({ + format: 'png', + size: 512 + }); + try { + const { body } = await snekfetch.get(avatarURL); + const avatar = await loadImage(body); + const canvas = createCanvas(avatar.width, avatar.height); + const ctx = canvas.getContext('2d'); + ctx.drawImage(avatar, 0, 0); + distort(ctx, 0, 0, avatar.width, avatar.height, level); + return msg.say({ files: [{ attachment: canvas.toBuffer(), name: 'distort.png' }] }); + } catch (err) { + return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); + } + } +}; diff --git a/commands/avatar-edit/christmas-hat.js b/commands/avatar-edit/glitch.js similarity index 71% rename from commands/avatar-edit/christmas-hat.js rename to commands/avatar-edit/glitch.js index a356ae81..f6ba5681 100644 --- a/commands/avatar-edit/christmas-hat.js +++ b/commands/avatar-edit/glitch.js @@ -1,15 +1,15 @@ const { Command } = require('discord.js-commando'); const { createCanvas, loadImage } = require('canvas'); const snekfetch = require('snekfetch'); -const path = require('path'); +const { distort } = require('../../util/Util'); -module.exports = class ChristmasHatCommand extends Command { +module.exports = class GlitchCommand extends Command { constructor(client) { super(client, { - name: 'christmas-hat', + name: 'glitch', group: 'avatar-edit', - memberName: 'christmas-hat', - description: 'Draws a Christmas hat over a user\'s avatar.', + memberName: 'glitch', + description: 'Draws a user\'s avatar but glitched.', throttling: { usages: 1, duration: 15 @@ -33,14 +33,13 @@ module.exports = class ChristmasHatCommand extends Command { size: 512 }); try { - const base = await loadImage(path.join(__dirname, '..', '..', 'assets', 'images', 'christmas-hat.png')); const { body } = await snekfetch.get(avatarURL); const avatar = await loadImage(body); const canvas = createCanvas(avatar.width, avatar.height); const ctx = canvas.getContext('2d'); ctx.drawImage(avatar, 0, 0); - ctx.drawImage(base, 0, 0, avatar.width, avatar.height); - return msg.say({ files: [{ attachment: canvas.toBuffer(), name: 'christmas-hat.png' }] }); + distort(ctx, 0, 0, avatar.width, avatar.height, 20, 5); + return msg.say({ files: [{ attachment: canvas.toBuffer(), name: 'glitch.png' }] }); } catch (err) { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } diff --git a/commands/avatar-edit/hat.js b/commands/avatar-edit/hat.js new file mode 100644 index 00000000..659397ba --- /dev/null +++ b/commands/avatar-edit/hat.js @@ -0,0 +1,60 @@ +const { Command } = require('discord.js-commando'); +const { createCanvas, loadImage } = require('canvas'); +const snekfetch = require('snekfetch'); +const path = require('path'); +const { list } = require('../../util/Util'); +const hats = ['christmas', 'anime hair', 'top hat']; + +module.exports = class HatCommand extends Command { + constructor(client) { + super(client, { + name: 'hat', + group: 'avatar-edit', + memberName: 'hat', + description: 'Draws a hat over a user\'s avatar.', + throttling: { + usages: 1, + duration: 15 + }, + clientPermissions: ['ATTACH_FILES'], + args: [ + { + key: 'type', + prompt: `What type of hat would you like to use? Either ${list(hats, 'or')}.`, + type: 'string', + validate: type => { + if (hats.includes(type.toLowerCase())) return true; + return `Invalid type, please enter either ${list(hats, 'or')}.`; + }, + parse: type => type.toLowerCase() + }, + { + key: 'user', + prompt: 'Which user would you like to edit the avatar of?', + type: 'user', + default: '' + } + ] + }); + } + + async run(msg, { type, user }) { + if (!user) user = msg.author; + const avatarURL = user.displayAvatarURL({ + format: 'png', + size: 512 + }); + try { + const base = await loadImage(path.join(__dirname, '..', '..', 'assets', 'images', 'hat', `${type}.png`)); + const { body } = await snekfetch.get(avatarURL); + const avatar = await loadImage(body); + const canvas = createCanvas(avatar.width, avatar.height); + const ctx = canvas.getContext('2d'); + ctx.drawImage(avatar, 0, 0); + ctx.drawImage(base, 0, 0, avatar.width, avatar.height); + return msg.say({ files: [{ attachment: canvas.toBuffer(), name: `${type}-hat.png` }] }); + } catch (err) { + return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); + } + } +}; diff --git a/commands/games/hunger-games.js b/commands/games/hunger-games.js index 2ea2ea14..2c445a07 100644 --- a/commands/games/hunger-games.js +++ b/commands/games/hunger-games.js @@ -7,7 +7,7 @@ module.exports = class HungerGamesCommand extends Command { constructor(client) { super(client, { name: 'hunger-games', - aliases: ['hunger-games-simulator', 'brantsteele'], + aliases: ['hunger-games-simulator', 'brant-steele'], group: 'games', memberName: 'hunger-games', description: 'Simulate a Hunger Games match.', diff --git a/commands/games/quiz.js b/commands/games/quiz.js index 13eb36f2..33b50b4d 100644 --- a/commands/games/quiz.js +++ b/commands/games/quiz.js @@ -9,7 +9,7 @@ module.exports = class QuizCommand extends Command { constructor(client) { super(client, { name: 'quiz', - aliases: ['jeopardy'], + aliases: ['jeopardy', 'test'], group: 'games', memberName: 'quiz', description: 'Answer a quiz question.', diff --git a/commands/games/typing-game.js b/commands/games/typing-game.js index 6feaa3a4..55ce1707 100644 --- a/commands/games/typing-game.js +++ b/commands/games/typing-game.js @@ -15,7 +15,7 @@ module.exports = class TypingGameCommand extends Command { constructor(client) { super(client, { name: 'typing-game', - aliases: ['typing-test'], + aliases: ['typing-quiz', 'typing-test'], group: 'games', memberName: 'typing-game', description: 'See how fast you can type a sentence in a given time limit.', diff --git a/commands/search/dictionary.js b/commands/search/dictionary.js index 96fdec94..f567500e 100644 --- a/commands/search/dictionary.js +++ b/commands/search/dictionary.js @@ -7,7 +7,7 @@ module.exports = class DictionaryCommand extends Command { constructor(client) { super(client, { name: 'dictionary', - aliases: ['define', 'wordnik', 'define-wordnik'], + aliases: ['define', 'wordnik', 'define-wordnik', 'wordnik-define'], group: 'search', memberName: 'dictionary', description: 'Defines a word.', diff --git a/commands/search/imgur.js b/commands/search/imgur.js index 3f834329..509a4f07 100644 --- a/commands/search/imgur.js +++ b/commands/search/imgur.js @@ -6,7 +6,7 @@ module.exports = class ImgurCommand extends Command { constructor(client) { super(client, { name: 'imgur', - aliases: ['imgur-image'], + aliases: ['imgur-image', 'image'], group: 'search', memberName: 'imgur', description: 'Searches Imgur for your query.', diff --git a/commands/search/anime.js b/commands/search/kitsu-anime.js similarity index 90% rename from commands/search/anime.js rename to commands/search/kitsu-anime.js index 6b99d7e3..565e4f2d 100644 --- a/commands/search/anime.js +++ b/commands/search/kitsu-anime.js @@ -3,13 +3,13 @@ const { MessageEmbed } = require('discord.js'); const snekfetch = require('snekfetch'); const { shorten } = require('../../util/Util'); -module.exports = class AnimeCommand extends Command { +module.exports = class KitsuAnimeCommand extends Command { constructor(client) { super(client, { - name: 'anime', - aliases: ['my-anime-list-anime', 'mal-anime', 'kitsu-anime'], + name: 'kitsu-anime', + aliases: ['my-anime-list-anime', 'mal-anime', 'anime'], group: 'search', - memberName: 'anime', + memberName: 'kitsu-anime', description: 'Searches Kitsu.io for your query, getting anime results.', clientPermissions: ['EMBED_LINKS'], args: [ diff --git a/commands/search/manga.js b/commands/search/kitsu-manga.js similarity index 90% rename from commands/search/manga.js rename to commands/search/kitsu-manga.js index 5ed81861..095d9cd4 100644 --- a/commands/search/manga.js +++ b/commands/search/kitsu-manga.js @@ -3,13 +3,13 @@ const { MessageEmbed } = require('discord.js'); const snekfetch = require('snekfetch'); const { shorten } = require('../../util/Util'); -module.exports = class MangaCommand extends Command { +module.exports = class KitsuMangaCommand extends Command { constructor(client) { super(client, { - name: 'manga', - aliases: ['my-anime-list-manga', 'mal-manga', 'kitsu-manga'], + name: 'kitsu-manga', + aliases: ['my-anime-list-manga', 'mal-manga', 'manga'], group: 'search', - memberName: 'manga', + memberName: 'kitsu-manga', description: 'Searches Kitsu.io for your query, getting manga results.', clientPermissions: ['EMBED_LINKS'], args: [ diff --git a/commands/search/movie.js b/commands/search/tmdb-movie.js similarity index 93% rename from commands/search/movie.js rename to commands/search/tmdb-movie.js index cb4a4b7d..7212aa12 100644 --- a/commands/search/movie.js +++ b/commands/search/tmdb-movie.js @@ -4,13 +4,13 @@ const snekfetch = require('snekfetch'); const { shorten } = require('../../util/Util'); const { TMDB_KEY } = process.env; -module.exports = class MovieCommand extends Command { +module.exports = class TMDBMovieCommand extends Command { constructor(client) { super(client, { - name: 'movie', - aliases: ['tmdb-movie', 'imdb'], + name: 'tmdb-movie', + aliases: ['movie', 'imdb'], group: 'search', - memberName: 'movie', + memberName: 'tmdb-movie', description: 'Searches TMDB for your query, getting movie results.', clientPermissions: ['EMBED_LINKS'], args: [ diff --git a/commands/search/tv-show.js b/commands/search/tmdb-tv-show.js similarity index 92% rename from commands/search/tv-show.js rename to commands/search/tmdb-tv-show.js index a53107ed..ed5aecb6 100644 --- a/commands/search/tv-show.js +++ b/commands/search/tmdb-tv-show.js @@ -4,13 +4,13 @@ const snekfetch = require('snekfetch'); const { shorten } = require('../../util/Util'); const { TMDB_KEY } = process.env; -module.exports = class TVShowCommand extends Command { +module.exports = class TMDBTVShowCommand extends Command { constructor(client) { super(client, { - name: 'tv-show', - aliases: ['tmdb-tv-show', 'tv', 'tmdb-tv'], + name: 'tmdb-tv-show', + aliases: ['tv-show', 'tv', 'tmdb-tv'], group: 'search', - memberName: 'tv-show', + memberName: 'tmdb-tv-show', description: 'Searches TMDB for your query, getting TV show results.', clientPermissions: ['EMBED_LINKS'], args: [ diff --git a/commands/search/urban.js b/commands/search/urban-dictionary.js similarity index 87% rename from commands/search/urban.js rename to commands/search/urban-dictionary.js index 2f2f2dda..51569c11 100644 --- a/commands/search/urban.js +++ b/commands/search/urban-dictionary.js @@ -3,13 +3,13 @@ const { MessageEmbed } = require('discord.js'); const snekfetch = require('snekfetch'); const { shorten } = require('../../util/Util'); -module.exports = class UrbanCommand extends Command { +module.exports = class UrbanDictionaryCommand extends Command { constructor(client) { super(client, { - name: 'urban', - aliases: ['urban-dictionary', 'urban-define', 'define-urban'], + name: 'urban-dictionary', + aliases: ['urban', 'urban-define', 'define-urban'], group: 'search', - memberName: 'urban', + memberName: 'urban-dictionary', description: 'Searches Urban Dictionary for your query.', clientPermissions: ['EMBED_LINKS'], args: [ diff --git a/commands/search/youtube.js b/commands/search/youtube.js index 7d99b5ce..1629106c 100644 --- a/commands/search/youtube.js +++ b/commands/search/youtube.js @@ -41,7 +41,11 @@ module.exports = class YouTubeCommand extends Command { .setDescription(data.snippet.description) .setAuthor(`YouTube - ${data.snippet.channelTitle}`, 'https://i.imgur.com/kKHJg9Q.png') .setURL(`https://www.youtube.com/watch?v=${data.id.videoId}`) - .setThumbnail(data.snippet.thumbnails.default.url); + .setThumbnail(data.snippet.thumbnails.default ? data.snippet.thumbnails.default.url : null) + .addField('❯ ID', + data.id.videoId, true) + .addField('❯ Publish Date', + new Date(data.snippet.publishedAt).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/text-edit/anagram.js b/commands/text-edit/anagram.js deleted file mode 100644 index 47d3751f..00000000 --- a/commands/text-edit/anagram.js +++ /dev/null @@ -1,40 +0,0 @@ -const { Command } = require('discord.js-commando'); -const snekfetch = require('snekfetch'); - -module.exports = class AnagramCommand extends Command { - constructor(client) { - super(client, { - name: 'anagram', - group: 'text-edit', - memberName: 'anagram', - description: 'Gets an anagram for a word.', - args: [ - { - key: 'word', - prompt: 'What word would you like to get an anagram for?', - type: 'string', - parse: word => encodeURIComponent(word.toLowerCase()) - }, - { - key: 'strict', - prompt: 'Would you like to get only words that contain all letters?', - type: 'boolean', - default: true - } - ] - }); - } - - async run(msg, { word, strict }) { - try { - const { body } = await snekfetch.get(`http://www.anagramica.com/best/${word}`); - const all = body.best.filter(anagram => anagram.toLowerCase() !== word); - const words = strict ? all.filter(anagram => anagram.length === word.length) : all; - if (!words.length) return msg.say('Could not find any results.'); - return msg.say(words[Math.floor(Math.random() * words.length)]); - } catch (err) { - if (err.status === 500) return msg.say('Could not find any results.'); - return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); - } - } -}; diff --git a/commands/text-edit/organization-xiii-name.js b/commands/text-edit/organization-xiii-name.js index 4e274ddc..18fe4eea 100644 --- a/commands/text-edit/organization-xiii-name.js +++ b/commands/text-edit/organization-xiii-name.js @@ -5,7 +5,7 @@ module.exports = class OrganizationXIIINameCommand extends Command { constructor(client) { super(client, { name: 'organization-xiii-name', - aliases: ['org-xiii-name', 'xiii-name', 'nobody-name'], + aliases: ['org-xiii-name', 'xiii-name', 'nobody-name', 'organization-xiii', 'org-xiii', 'xiii'], group: 'text-edit', memberName: 'organization-xiii-name', description: 'Converts a name into the Organization XIII style.', diff --git a/package.json b/package.json index 75612acf..bf33743f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiaobot", - "version": "49.3.3", + "version": "49.4.0", "description": "Your personal server companion.", "main": "XiaoBot.js", "scripts": { diff --git a/util/Util.js b/util/Util.js index 1b9bbbbf..2a0eb4ba 100644 --- a/util/Util.js +++ b/util/Util.js @@ -102,6 +102,25 @@ class Util { return ctx; } + static distort(ctx, x, y, width, height, amplitude, strideLevel = 4) { + const data = ctx.getImageData(x, y, width, height); + const temp = ctx.getImageData(x, y, width, height); + const stride = width * strideLevel; + for (let i = 0; i < width; i++) { + for (let j = 0; j < height; j++) { + const xs = Math.round(amplitude * Math.sin(2 * Math.PI * 3 * (y / height))); + const ys = Math.round(amplitude * Math.cos(2 * Math.PI * 3 * (x / width))); + const dest = j * (stride + i) * strideLevel; + const src = (j + ys) * (stride + (i + xs)) * strideLevel; + data.data[dest] = temp.data[src]; + data.data[dest + 1] = temp.data[src + 1]; + data.data[dest + 2] = temp.data[src + 2]; + } + } + ctx.putImageData(data, x, y); + return ctx; + } + static async verify(channel, user, time = 30000) { const filter = res => { const value = res.content.toLowerCase();