const Command = require('../../structures/Command'); const request = require('node-superfetch'); const { createCanvas, loadImage } = require('canvas'); const { stripIndents } = require('common-tags'); const { base64 } = require('../../util/Util'); module.exports = class WhatAnimeCommand extends Command { constructor(client) { super(client, { name: 'what-anime', aliases: ['anime-source', 'anime-src', 'trace-moe', 'source', 'src'], group: 'analyze', memberName: 'what-anime', description: 'Determines what anime a screenshot is from.', credit: [ { name: 'WAIT: What Anime Is This?', url: 'https://trace.moe/', reason: 'API', reasonURL: 'https://soruly.github.io/trace.moe/#/' } ], args: [ { key: 'screenshot', prompt: 'What screenshot do you want to scan?', type: 'image', 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.`); } let { body } = await request.get(screenshot); if (screenshot.endsWith('.gif')) body = await this.convertGIF(body); const result = await this.search(body, msg.channel.nsfw); if (result === 'size') return msg.reply('Please do not send an image larger than 10MB.'); if (result.nsfw && !msg.channel.nsfw) { return msg.reply('This is from a hentai, and this isn\'t an NSFW channel, pervert.'); } const title = `${result.title}${result.episode ? ` episode ${result.episode}` : ''}`; return msg.reply(stripIndents` I'm ${result.prob}% sure this is from ${title}. ${result.prob < 87 ? '_This probablity is rather low, try using a higher quality image._' : ''} `, result.preview ? { files: [{ attachment: result.preview, name: 'preview.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://trace.moe/api/me'); return { status: body.quota > 0, refresh: body.quota_ttl }; } catch { return { status: false, refresh: Infinity }; } } async search(file) { if (Buffer.byteLength(file) > 1e+7) return 'size'; const { body } = await request .post('https://trace.moe/api/search') .attach('image', base64(file)); const data = body.docs[0]; return { prob: Math.round(data.similarity * 100), episode: data.episode, title: data.title_english, preview: await this.fetchPreview(data), nsfw: data.is_adult }; } async fetchPreview(data) { try { const { body } = await request .get(`https://media.trace.moe/video/${data.anilist_id}/${encodeURIComponent(data.filename)}`) .query({ t: data.at, token: data.tokenthumb }); return body; } catch { return null; } } async convertGIF(image) { const data = await loadImage(image); const canvas = createCanvas(data.width, data.height); const ctx = canvas.getContext('2d'); ctx.drawImage(data, 0, 0); return canvas.toBuffer(); } };