facemorph.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. const Clarifai = require("clarifai");
  2. const ClarifaiTOKEN = require("../clarifai_keys.js");
  3. const db = require("../db.js");
  4. const util = require("../util.js");
  5. const Jimp = require("jimp");
  6. const client = require("../client.js");
  7. const schedule = require("node-schedule");
  8. const EMOTE_GUILD = "505333548694241281";
  9. const clarifaiApp = new Clarifai.App({
  10. apiKey: ClarifaiTOKEN
  11. });
  12. const MAX_REQUESTS_PER_MONTH = 5000;
  13. let quotaRecomputeJob = null;
  14. let dailyResetJob = null;
  15. function recomputeQuotas() {
  16. console.log(`Recomputing quotas on ${new Date().toISOString()}!`);
  17. db.set("faceEditInfo.quotaRecomputeDate", "").write();
  18. let channelPredictProbability = db.get("faceEditChannels").value();
  19. let channelStatistics = db.get("faceEditStatistics").value();
  20. let totalRandomPredicts = 0;
  21. for (const channel in channelStatistics) {
  22. if (channelStatistics.hasOwnProperty(channel) && channelPredictProbability.hasOwnProperty(channel)) {
  23. totalRandomPredicts += channelPredictProbability[channel] * channelStatistics[channel];
  24. channelStatistics[channel] = 0;
  25. }
  26. }
  27. let dailyQuota = Math.floor((MAX_REQUESTS_PER_MONTH - totalRandomPredicts) / 30.0);
  28. db.set("faceEditInfo.customRequestsPerDay", dailyQuota).write();
  29. db.set("faceEditStatistics", channelStatistics).write();
  30. quotaRecomputeJob.cancel();
  31. }
  32. function resetDailyQuota() {
  33. console.log("Resetting today image request count!");
  34. db.set("faceEditInfo.todayRequests", 0).write();
  35. }
  36. function setupJobs() {
  37. let recomputeJobDate = db.get("faceEditInfo.quotaRecomputeDate").value();
  38. if (recomputeJobDate)
  39. quotaRecomputeJob = schedule.scheduleJob(new Date(recomputeJobDate), recomputeQuotas);
  40. let rule = new schedule.RecurrenceRule();
  41. rule.hour = 0;
  42. rule.minute = 0;
  43. dailyResetJob = schedule.scheduleJob(rule, resetDailyQuota);
  44. checkQuotaJobs();
  45. }
  46. setupJobs();
  47. function checkQuotaJobs() {
  48. let jobDateText = db.get("faceEditInfo.quotaRecomputeDate").value();
  49. if (jobDateText)
  50. return;
  51. let jobDate = new Date();
  52. jobDate.setMonth(jobDate.getMonth() + 1);
  53. db.set("faceEditInfo.quotaRecomputeDate", jobDate.toISOString()).write();
  54. quotaRecomputeJob.reschedule(jobDate, recomputeQuotas);
  55. }
  56. async function processFaceSwap(message, attachmentUrl, failMessage, successMessage) {
  57. let result = await clarifaiApp.models.predict(Clarifai.FACE_DETECT_MODEL, attachmentUrl);
  58. checkQuotaJobs();
  59. if (result.outputs[0].data.regions === undefined || result.outputs[0].data.regions.length == 0) {
  60. if (failMessage)
  61. message.channel.send(failMessage);
  62. return;
  63. }
  64. let image = await Jimp.read(attachmentUrl);
  65. let w = image.getWidth();
  66. let h = image.getHeight();
  67. let emojiKeys = [...client.guilds.get(EMOTE_GUILD).emojis.filter(e => !e.animated).keys()];
  68. for (let region of result.outputs[0].data.regions) {
  69. let bb = region.region_info.bounding_box;
  70. let bw = (bb.right_col - bb.left_col) * w;
  71. let bh = (bb.bottom_row - bb.top_row) * h;
  72. let dx = (bb.right_col + bb.left_col) * w / 2;
  73. let dy = (bb.bottom_row + bb.top_row) * h / 2;
  74. let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
  75. let emoji = client.emojis.get(emojiKey);
  76. let emojiImage = await Jimp.read(emoji.url);
  77. let ew = emojiImage.getWidth();
  78. let eh = emojiImage.getHeight();
  79. const CONSTANT_SCALE = 1.5;
  80. let scaleFactor = Math.max(bw, bh) / Math.min(ew, eh) * CONSTANT_SCALE;
  81. ew *= scaleFactor;
  82. eh *= scaleFactor;
  83. emojiImage = emojiImage.scale(scaleFactor);
  84. image = image.composite(emojiImage, dx - ew / 2, dy - eh / 2);
  85. }
  86. image.quality(90);
  87. let buffer = await image.getBufferAsync(Jimp.MIME_JPEG);
  88. let messageContents = successMessage || `I noticed a face in the image. I think this looks better ${client.emojis.get("505076258753740810").toString()}`;
  89. message.channel.send(messageContents, {
  90. files: [buffer]
  91. });
  92. }
  93. const onMessage = (msg, contents, actionsDone) => {
  94. if (actionsDone)
  95. return false;
  96. if(msg.mentions.users.size > 0 && msg.mentions.users.first().id == client.user.id)
  97. return false;
  98. let imageAttachment = msg.attachments.find(v => util.isValidImage(v.filename));
  99. if (imageAttachment) {
  100. let probValue = db.get("faceEditChannels").get(msg.channel.id);
  101. if (probValue.isUndefined().value() || probValue.isNull().value())
  102. return false;
  103. if (Math.random() > probValue.value())
  104. return false;
  105. processFaceSwap(msg, imageAttachment.url).catch(err => console.log(`Failed to run faceapp because ${err}`));
  106. return true;
  107. }
  108. return false;
  109. };
  110. const onDirectMention = (msg, content, actionsDone) => {
  111. if (actionsDone)
  112. return false;
  113. let image = msg.attachments.find(v => util.isValidImage(v.filename));
  114. if (!image){
  115. if(msg.attachments.size > 0) {
  116. msg.channel.send(`${msg.author.toString()} Nice, but I can't do anything to it! (Invalid file type)`);
  117. return true;
  118. }
  119. return false;
  120. }
  121. let todayQuota = db.get("faceEditInfo.todayRequests").value();
  122. let maxQuota = db.get("faceEditInfo.customRequestsPerDay").value();
  123. if (todayQuota >= maxQuota) {
  124. msg.channel.send(`${msg.author.toString()} Nice image, but I can't do anything to it! (Daily quota hit)`);
  125. return true;
  126. }
  127. db.set("faceEditInfo.todayRequests", todayQuota + 1).write();
  128. processFaceSwap(
  129. msg,
  130. image.url,
  131. `${msg.author.toString()} Nice image! I don't see anything interesting, though.`,
  132. `${msg.author.toString()} ${client.emojis.get("505076258753740810").toString()}`)
  133. .catch(err => console.log(`Failed to run faceapp because ${err}`));
  134. return true;
  135. };
  136. module.exports = {
  137. onMessage: onMessage,
  138. onDirectMention: onDirectMention
  139. };