diff --git a/README.md b/README.md index b50867ac..92c48dca 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Xiao is a Discord bot coded in JavaScript with 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 (290) +## Commands (291) ### Utility: * **eval**: Executes JavaScript code. @@ -180,6 +180,7 @@ on the [home server](https://discord.gg/sbMe32W). * **ship**: Ships two users together. * **spoopy-link**: Determines if a link is spoopy or not. * **toxicity**: Determines the toxicity of text. +* **what-anime**: Determines what anime a screenshot is from. * **zodiac-sign**: Responds with the Zodiac Sign for the given month/day. ### Games: diff --git a/commands/analyze/what-anime.js b/commands/analyze/what-anime.js new file mode 100644 index 00000000..04f35d7d --- /dev/null +++ b/commands/analyze/what-anime.js @@ -0,0 +1,89 @@ +const Command = require('../../structures/Command'); +const request = require('node-superfetch'); +const { base64 } = require('../../util/Util'); +const { WHATANIME_KEY } = process.env; + +module.exports = class WhatAnimeCommand extends Command { + constructor(client) { + super(client, { + name: 'what-anime', + aliases: ['whatanime.ga', 'anime-source', 'anime-sauce', 'weeb-sauce'], + group: 'analyze', + memberName: 'what-anime', + description: 'Determines what anime a screenshot is from.', + args: [ + { + key: 'screenshot', + prompt: 'What screenshot do you want to scan?', + type: 'image|avatar', + default: msg => msg.author.displayAvatarURL({ format: 'png', size: 512 }) + } + ] + }); + } + + async run(msg, { screenshot }) { + try { + const status = await this.fetchRateLimit(); + if (!status.status) { + return msg.reply(`Oh no, I'm out of requests! Please wait ${status.refresh} seconds and try again.`); + } + const { body } = await request.get(screenshot); + const result = await this.search(body, msg.channel.nsfw); + if (result === 'size') return msg.reply('Please do not send an image larger than 1MB.'); + if (result === 'nsfw') return msg.reply('This is from a hentai, and this isn\'t an NSFW channel, pervert.'); + return msg.reply( + `I'm ${result.probability}% sure this is from ${result.title} (${result.english}) episode #${result.episode}.`, + result.preview ? { files: [{ attachment: result.preview, name: 'anime.mp4' }] } : {} + ); + } catch (err) { + return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); + } + } + + async fetchRateLimit() { + try { + const { body } = await request + .get('https://whatanime.ga/api/me') + .query({ token: WHATANIME_KEY }); + return { status: body.quota > 0, refresh: body.quota_ttl }; + } catch (err) { + return { status: false, refresh: null }; + } + } + + async search(file, nsfw) { + if (Buffer.byteLength(file) > 1e+6) return 'size'; + const { body } = await request + .post('https://whatanime.ga/api/search') + .query({ token: WHATANIME_KEY }) + .attach('image', base64(file)); + const data = body.docs[0]; + if (data.is_adult && !nsfw) return 'nsfw'; + return { + time: data.at * 1000, + probability: Math.round(data.similarity * 100), + episode: data.episode, + title: data.title_native, + english: data.title_english, + preview: await this.fetchPreview(data), + nsfw: data.is_adult + }; + } + + async fetchPreview(data) { + try { + const { body } = await request + .get('https://whatanime.ga/preview.php') + .query({ + anilist_id: data.anilist_id, + file: data.filename, + t: data.at, + token: data.tokenthumb + }); + return body; + } catch (err) { + return null; + } + } +}; diff --git a/package.json b/package.json index 40bf28a5..8722c34c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "85.1.4", + "version": "85.2.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": {