facemorph.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. const db = require("../db.js");
  2. const util = require("../util.js");
  3. const Jimp = require("jimp");
  4. const client = require("../client.js");
  5. const cv = require("opencv4nodejs");
  6. const path = require("path");
  7. const request = require("request-promise-native");
  8. const EMOTE_GUILD = "505333548694241281";
  9. const animeCascade = new cv.CascadeClassifier(path.resolve(__dirname, "./animu.xml"));
  10. const faceCascade = new cv.CascadeClassifier(cv.HAAR_FRONTALFACE_ALT2);
  11. function intersects(r1, r2) {
  12. return (r1.x <= r2.x + r2.width && r1.x + r1.width >= r2.x)
  13. && (r1.y <= r2.y + r2.height && r1.y + r1.height >= r2.y);
  14. }
  15. async function processFaceSwap(message, attachmentUrl, failMessage, successMessage) {
  16. let data = await request(attachmentUrl, {encoding: null});
  17. let im = await cv.imdecodeAsync(data, cv.IMREAD_COLOR);
  18. let gray = await im.cvtColorAsync(cv.COLOR_BGR2GRAY);
  19. let normGray = await gray.equalizeHistAsync();
  20. let animeFaces = await animeCascade.detectMultiScaleAsync(normGray, 1.1, 5, 0, new cv.Size(24, 24));
  21. let normalFaces = await faceCascade.detectMultiScaleAsync(gray);
  22. if(animeFaces.objects.length == 0 && normalFaces.objects.length == 0) {
  23. if (failMessage)
  24. message.channel.send(failMessage);
  25. return;
  26. }
  27. let faces = [...normalFaces.objects, ...animeFaces.objects];
  28. let normalCount = normalFaces.objects.length;
  29. let animeCount = animeFaces.objects.length;
  30. for (let i = 0; i < normalCount; i++) {
  31. const rNormal = faces[i];
  32. if(animeCount == 0)
  33. break;
  34. for (let j = normalCount; j < faces.length; j++) {
  35. const rAnime = faces[j];
  36. if(intersects(rAnime, rNormal)) {
  37. let animeA = rAnime.width * rAnime.height;
  38. let faceA = rNormal.width * rNormal.height;
  39. if(animeA > faceA){
  40. faces.splice(i, 1);
  41. normalCount--;
  42. i--;
  43. break;
  44. } else {
  45. faces.splice(j, 1);
  46. animeCount--;
  47. j--;
  48. }
  49. }
  50. }
  51. }
  52. let jimpImage = await Jimp.read(data);
  53. let emojiKeys = [...client.guilds.get(EMOTE_GUILD).emojis.filter(e => !e.animated).keys()];
  54. for (const rect of faces) {
  55. let dx = rect.x + rect.width / 2;
  56. let dy = rect.y + rect.height / 2;
  57. let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
  58. let emoji = client.emojis.get(emojiKey);
  59. let emojiImage = await Jimp.read(emoji.url);
  60. let ew = emojiImage.getWidth();
  61. let eh = emojiImage.getHeight();
  62. const CONSTANT_SCALE = 1.1;
  63. let scaleFactor = Math.max(rect.width, rect.height) / Math.min(ew, eh) * CONSTANT_SCALE;
  64. ew *= scaleFactor;
  65. eh *= scaleFactor;
  66. emojiImage = emojiImage.scale(scaleFactor);
  67. jimpImage = jimpImage.composite(emojiImage, dx - ew / 2, dy - eh / 2);
  68. }
  69. jimpImage.quality(90);
  70. let buffer = await jimpImage.getBufferAsync(Jimp.MIME_JPEG);
  71. let messageContents = successMessage || `I noticed a face in the image. I think this looks better ${client.emojis.get("505076258753740810").toString()}`;
  72. message.channel.send(messageContents, {
  73. files: [buffer]
  74. });
  75. }
  76. const onMessage = (msg, contents, actionsDone) => {
  77. if (actionsDone)
  78. return false;
  79. if(msg.mentions.users.size > 0 && msg.mentions.users.first().id == client.user.id)
  80. return false;
  81. let imageAttachment = msg.attachments.find(v => util.isValidImage(v.filename));
  82. if (imageAttachment) {
  83. let probValue = db.get("faceEditChannels").get(msg.channel.id);
  84. if (probValue.isUndefined().value() || probValue.isNull().value())
  85. return false;
  86. if (Math.random() > probValue.value())
  87. return false;
  88. processFaceSwap(msg, imageAttachment.url).catch(err => console.log(`Failed to run faceapp because ${err}`));
  89. return true;
  90. }
  91. return false;
  92. };
  93. const onDirectMention = (msg, content, actionsDone) => {
  94. if (actionsDone)
  95. return false;
  96. let image = msg.attachments.find(v => util.isValidImage(v.filename));
  97. if (!image){
  98. if(msg.attachments.size > 0) {
  99. msg.channel.send(`${msg.author.toString()} Nice, but I can't do anything to it! (Invalid file type)`);
  100. return true;
  101. }
  102. return false;
  103. }
  104. let todayQuota = db.get("faceEditInfo.todayRequests").value();
  105. let maxQuota = db.get("faceEditInfo.customRequestsPerDay").value();
  106. if (todayQuota >= maxQuota) {
  107. msg.channel.send(`${msg.author.toString()} Nice image, but I can't do anything to it! (Daily quota hit)`);
  108. return true;
  109. }
  110. db.set("faceEditInfo.todayRequests", todayQuota + 1).write();
  111. processFaceSwap(
  112. msg,
  113. image.url,
  114. `${msg.author.toString()} Nice image! I don't see anything interesting, though.`,
  115. `${msg.author.toString()} ${client.emojis.get("505076258753740810").toString()}`)
  116. .catch(err => console.log(`Failed to run faceapp because ${err}`));
  117. return true;
  118. };
  119. module.exports = {
  120. onMessage: onMessage,
  121. onDirectMention: onDirectMention
  122. };