123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- const db = require("../db.js");
- const util = require("../util.js");
- const Jimp = require("jimp");
- const client = require("../client.js");
- const cv = require("opencv4nodejs");
- const path = require("path");
- const request = require("request-promise-native");
- const EMOTE_GUILD = "505333548694241281";
- const animeCascade = new cv.CascadeClassifier(path.resolve(__dirname, "./animu.xml"));
- const faceCascade = new cv.CascadeClassifier(cv.HAAR_FRONTALFACE_ALT2);
- function intersects(r1, r2) {
- return (
- r1.x <= r2.x + r2.width &&
- r1.x + r1.width >= r2.x &&
- (r1.y <= r2.y + r2.height && r1.y + r1.height >= r2.y)
- );
- }
- async function morphFaces(faces, data) {
- let padoru = Math.random() <= getPadoruChance();
- let jimpImage = await Jimp.read(data);
- let emojiKeys = [
- ...client.guilds
- .get(EMOTE_GUILD)
- .emojis.filter(e => !e.animated && e.name.startsWith("PADORU") == padoru)
- .keys()
- ];
- for (const rect of faces) {
- let dx = rect.x + rect.width / 2;
- let dy = rect.y + rect.height / 2;
- let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
- let emoji = client.emojis.get(emojiKey);
- let emojiImage = await Jimp.read(emoji.url);
- let ew = emojiImage.getWidth();
- let eh = emojiImage.getHeight();
- const CONSTANT_SCALE = 1.1;
- let scaleFactor = (Math.max(rect.width, rect.height) / Math.min(ew, eh)) * CONSTANT_SCALE;
- ew *= scaleFactor;
- eh *= scaleFactor;
- emojiImage = emojiImage.scale(scaleFactor);
- jimpImage = jimpImage.composite(emojiImage, dx - ew / 2, dy - eh / 2);
- }
- return jimpImage;
- }
- const CAPTION_OFFSET = 5;
- async function captionFace(faces, data) {
- let padoru = Math.random() <= getPadoruChance();
- let face = faces[Math.floor(Math.random() * faces.length)];
- let squaredFace = await face.toSquareAsync();
- let targetSize = db.get("faceEditConfig.captionedImageSize").value();
- let img = await Jimp.read(data);
- let tempImg = await Jimp.create(squaredFace.width, squaredFace.height);
- tempImg = await tempImg.blit(
- img,
- 0,
- 0,
- squaredFace.x,
- squaredFace.y,
- squaredFace.width,
- squaredFace.height
- );
- tempImg = await tempImg.scale(targetSize / squaredFace.width);
- let font = await Jimp.loadFont(padoru ? Jimp.FONT_SANS_16_WHITE : Jimp.FONT_SANS_16_BLACK);
- let text = padoru ? "PADORU PADORU" : `${db.get("faceCaptions.pre").randomElement().value()} ${db.get("faceCaptions.post").randomElement().value()}`;
- let h = Jimp.measureTextHeight(font, text, targetSize - CAPTION_OFFSET * 2);
- let finalImage = await Jimp.create(targetSize, targetSize + h + CAPTION_OFFSET * 2, padoru ? "#FD2027" : "#FFFFFF");
- finalImage = await finalImage.print(
- font,
- CAPTION_OFFSET,
- CAPTION_OFFSET,
- text,
- finalImage.getWidth() - CAPTION_OFFSET
- );
- finalImage = await finalImage.composite(tempImg, 0, CAPTION_OFFSET * 2 + h);
- return finalImage;
- }
- /**
- * PADORU PADORU
- */
- function getPadoruChance() {
- let now = new Date();
- if(now.getUTCMonth() != 11 || now.getUTCDate() > 26)
- return 0;
- return 1 / (26.0 - now.getUTCDate());
- }
- async function processFaceSwap(message, attachmentUrl, processor, failMessage, successMessage) {
- let data = await request(attachmentUrl, { encoding: null });
- let im = await cv.imdecodeAsync(data, cv.IMREAD_COLOR);
- let gray = await im.cvtColorAsync(cv.COLOR_BGR2GRAY);
- let normGray = await gray.equalizeHistAsync();
- let animeFaces = await animeCascade.detectMultiScaleAsync(
- normGray,
- 1.1,
- 5,
- 0,
- new cv.Size(24, 24)
- );
- let normalFaces = await faceCascade.detectMultiScaleAsync(gray);
- if (animeFaces.objects.length == 0 && normalFaces.objects.length == 0) {
- if (failMessage) message.channel.send(failMessage);
- return;
- }
- let faces = [...normalFaces.objects, ...animeFaces.objects];
- let normalCount = normalFaces.objects.length;
- let animeCount = animeFaces.objects.length;
- for (let i = 0; i < normalCount; i++) {
- const rNormal = faces[i];
- if (animeCount == 0) break;
- for (let j = normalCount; j < faces.length; j++) {
- const rAnime = faces[j];
- if (intersects(rAnime, rNormal)) {
- let animeA = rAnime.width * rAnime.height;
- let faceA = rNormal.width * rNormal.height;
- if (animeA > faceA) {
- faces.splice(i, 1);
- normalCount--;
- i--;
- break;
- } else {
- faces.splice(j, 1);
- animeCount--;
- j--;
- }
- }
- }
- }
- let jimpImage;
- if(processor)
- jimpImage = await processor(faces, data);
- else {
- if(Math.random() <= db.get("faceEditConfig.captionProbability").value())
- jimpImage = await captionFace(faces, data);
- else
- jimpImage = await morphFaces(faces, data);
- }
- jimpImage.quality(90);
- let buffer = await jimpImage.getBufferAsync(Jimp.MIME_JPEG);
-
- let messageContents =
- successMessage ||
- `I noticed a face in the image. I think this looks better ${client.emojis.get("505076258753740810").toString()}`;
- message.channel.send(messageContents, {
- files: [buffer]
- });
- }
- const onMessage = (msg, contents, actionsDone) => {
- if (actionsDone) return false;
- if (msg.mentions.users.size > 0 && msg.mentions.users.first().id == client.user.id)
- return false;
- let imageAttachment = msg.attachments.find(v => util.isValidImage(v.filename));
- if (imageAttachment) {
- let probValue = db.get("faceEditChannels").get(msg.channel.id);
- if (probValue.isUndefined().value() || probValue.isNull().value()) return false;
- if (Math.random() > probValue.value()) return false;
- processFaceSwap(msg, imageAttachment.url).catch(err =>
- console.log(`Failed to run faceapp because ${err}`)
- );
- return true;
- }
- return false;
- };
- const onDirectMention = (msg, content, actionsDone) => {
- if (actionsDone) return false;
- let image = msg.attachments.find(v => util.isValidImage(v.filename));
- if (!image) {
- if (msg.attachments.size > 0) {
- msg.channel.send(
- `${msg.author.toString()} Nice, but I can't do anything to it! (Invalid file type)`
- );
- return true;
- }
- return false;
- }
- let processor;
- if(content.startsWith("caption this"))
- processor = captionFace;
- else
- processor = morphFaces;
- processFaceSwap(
- msg,
- image.url,
- processor,
- `${msg.author.toString()} Nice image! I don't see anything interesting, though.`,
- `${msg.author.toString()} ${client.emojis.get("505076258753740810").toString()}`
- ).catch(err => console.log(`Failed to run faceapp because ${err}`));
- return true;
- };
- module.exports = {
- onMessage: onMessage,
- onDirectMention: onDirectMention
- };
|