diff --git a/assets/profile/profile.svg b/assets/profile/profile.svg index 0a56ded..0dcb2eb 100644 --- a/assets/profile/profile.svg +++ b/assets/profile/profile.svg @@ -41,7 +41,7 @@ DEV CONTRIB - + {{PROFILE_TEXT}} @@ -72,7 +72,7 @@ - + diff --git a/assets/profile/template.afdesign b/assets/profile/template.afdesign index d6b05bf..5412b94 100644 Binary files a/assets/profile/template.afdesign and b/assets/profile/template.afdesign differ diff --git a/commands/profile.js b/commands/profile.js index 1fe1a08..4f47bce 100644 --- a/commands/profile.js +++ b/commands/profile.js @@ -1,6 +1,8 @@ const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); const { Card, User, Character } = require("../models"); const { UserUtils, Compositing, Rendering } = require("../util"); +const axios = require("axios"); +const sharp = require("sharp"); const fs = require('fs'); const pageSize = 8; @@ -34,6 +36,16 @@ module.exports = { profileTemplate = profileTemplate.replace(/{{CC}}/g, await Card.count({where: {userId: user.id}})); profileTemplate = profileTemplate.replace(/{{LVL}}/g, await user.level().currentLevel); + let userImageBuffer = await axios.get(discordUser.displayAvatarURL({format: 'png', size: 128}), { responseType: 'arraybuffer' }); + userImage = await sharp(userImageBuffer.data); + const rect = new Buffer.from( + '' + ); + userImage = await userImage.composite([{input: rect, blend: 'dest-in' }]).png().toBuffer(); + + let background = await sharp(Buffer.from(profileTemplate, 'utf8')) + .composite([{ input: userImage, left: 360, top: 20 }]).png().toBuffer(); + let slots = ['slotOne', 'slotTwo', 'slotThree', 'slotFour']; let renderedCards = []; for (slot of slots) { @@ -47,7 +59,7 @@ module.exports = { } - let profileImage = await Compositing.renderProfile(profile, profileTemplate, renderedCards); + let profileImage = await Compositing.renderProfile(profile, background, renderedCards); await interaction.editReply({ files: [profileImage] }); } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 81dbfb3..3ab6159 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@discordjs/rest": "^0.3.0", + "axios": "^0.27.2", "discord-api-types": "^0.37.2", "discord.js": "^14.0.0", "dotenv": "^16.0.0", @@ -176,6 +177,15 @@ "node": ">= 4.0.0" } }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -691,6 +701,25 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2220,6 +2249,15 @@ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2629,6 +2667,11 @@ "to-regex-range": "^5.0.1" } }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", diff --git a/package.json b/package.json index ff27935..c1a1386 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "@discordjs/rest": "^0.3.0", + "axios": "^0.27.2", "discord-api-types": "^0.37.2", "discord.js": "^14.0.0", "dotenv": "^16.0.0", diff --git a/util/compositing.js b/util/compositing.js index 43d57b4..eb660db 100644 --- a/util/compositing.js +++ b/util/compositing.js @@ -6,14 +6,14 @@ const { Card } = require('../models'); //TODO: Handle missing images module.exports = { name: "Compositing", - renderProfile: async function(profile, svgTemplate, renderedCards) { - let hash = crypto.createHash('md5').update(JSON.stringify(profile) + svgTemplate).digest('hex'); + renderProfile: async function(profile, background, renderedCards) { + let hash = crypto.createHash('md5').update(JSON.stringify(profile) + background).digest('hex'); let outFile = `/app/assets/image_cache/profiles/${hash}.gif`; console.log('Rendering profile to ' + outFile); //composite {overlay} {background} [{mask}] [-compose {method}] {result} - let args = ['svg:-', 'null:', + let args = ['png:-', 'null:', '\(', `${renderedCards[0]}`, '-coalesce', '\)', '-geometry', '+25+85', '-compose', 'over', '-layers', 'composite', 'null:', '\(', `${renderedCards[1]}`, '-coalesce', '-resize', '170x283', '\)', @@ -27,7 +27,7 @@ module.exports = { console.log('GM Args: ' + args); const composite = spawn('convert', args); - composite.stdin.write(svgTemplate); + composite.stdin.write(background); composite.stdin.end(); composite.stderr.on('data', (data) => {