diff --git a/package-lock.json b/package-lock.json index 626c9ee..3bd4a04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "dd-trace": "^1.4.1", "express": "^4.17.1", "morgan": "^1.10.0", - "node-fetch": "^2.6.0", + "node-fetch": "^3.2.10", "sharp": "^0.29.1", "svgdom": "^0.1.8", "text-to-svg": "^3.1.5", @@ -2707,6 +2707,15 @@ "node-fetch": "2.6.0" } }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2773,6 +2782,14 @@ "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -4142,6 +4159,28 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -4285,6 +4324,17 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -6681,12 +6731,39 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz", "integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, "engines": { - "node": "4.x || >=6.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-gyp-build": { @@ -9321,6 +9398,14 @@ "makeerror": "1.0.x" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -11790,6 +11875,14 @@ "dev": true, "requires": { "node-fetch": "2.6.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + } } }, "cross-spawn": { @@ -11851,6 +11944,11 @@ "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" + }, "data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -12920,6 +13018,15 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -13043,6 +13150,14 @@ "mime-types": "^2.1.12" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -14852,10 +14967,20 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz", "integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } }, "node-gyp-build": { "version": "3.9.0", @@ -16908,6 +17033,11 @@ "makeerror": "1.0.x" } }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + }, "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", diff --git a/package.json b/package.json index 9b8f492..2b24404 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dd-trace": "^1.4.1", "express": "^4.17.1", "morgan": "^1.10.0", - "node-fetch": "^2.6.0", + "node-fetch": "^3.2.10", "sharp": "^0.29.1", "svgdom": "^0.1.8", "text-to-svg": "^3.1.5", diff --git a/src/Discord.js b/src/Discord.js index 524a279..8400f41 100644 --- a/src/Discord.js +++ b/src/Discord.js @@ -1,4 +1,5 @@ const fetch = require('node-fetch') +const axios = require('axios') const API_BASE_URL = 'https://discord.com/api/v10' const CDN_BASE_URL = 'https://cdn.discordapp.com' @@ -12,11 +13,15 @@ module.exports = class Discord { return fetch(`${API_BASE_URL}/invites/${inviteCode}?with_counts=true`).then(res => res.json()) } - static fetchIcon (iconUrl) { + static fetchBase64Image (iconUrl) { return fetch(iconUrl).then(res => res.buffer()).then(buffer => buffer.toString('base64')) } static getIconUrl (guildId, iconId) { return `${CDN_BASE_URL}/icons/${guildId}/${iconId}${iconId.startsWith('a_') ? '.gif' : '.jpg'}` } + + static getSplashUrl (guildId, splashId) { + return `${CDN_BASE_URL}/splashes/${guildId}/${splashId}.jpg?size=480` + } } diff --git a/src/InviteRenderer.js b/src/InviteRenderer.js index 8411b10..f066257 100644 --- a/src/InviteRenderer.js +++ b/src/InviteRenderer.js @@ -14,7 +14,6 @@ SVG.extend([SVG.Path, SVG.Circle], { const whitneyBold = TextToSVG.loadSync('./src/fonts/WhitneyBoldRegular.ttf') const whitneySemibold = TextToSVG.loadSync('./src/fonts/WhitneySemiboldRegular.ttf') -const whitneyMedium = TextToSVG.loadSync('./src/fonts/WhitneyMediumRegular.ttf') const strings = require('./strings.json') @@ -23,7 +22,7 @@ const ICON_SIZE = 50 const HEADER_FONT_SIZE = 12 const HEADER_LINE_HEIGHT = 16 -const HEADER_MARGIN_BOTTOM = 12 +const HEADER_MARGIN_BOTTOM = 75 const SERVER_NAME_SIZE = 16 const SERVER_NAME_LINE_HEIGHT = 20 @@ -37,14 +36,17 @@ const PRESENCE_DOT_SIZE = 8 const PRESENCE_DOT_MARGIN_RIGHT = 4 const INVITE_WIDTH = 430 -const INVITE_HEIGHT = 110 +const INVITE_HEIGHT = 174 -const BUTTON_WIDTH = 94.75 +const BUTTON_WIDTH = 63 const BUTTON_HEIGHT = 40 const BUTTON_MARGIN_LEFT = 10 const BADGE_MARGIN_RIGHT = 8 +const SPLASH_WIDTH = 0 +const SPLASH_HEIGHT = 100 + const Constants = require('./constants.js') const BADGES = { VERIFIED: Constants.VERIFIED_ICON, @@ -55,13 +57,13 @@ const BADGES = { // Old green color: #43b581 const COMMON_COLORS = { - joinButtonBackground: '#3ba55c', + joinButtonBackground: '#2d7d46', joinButtonText: '#ffffff', online: '#3ba55c', members: '#747f8d', badges: { PARTNERED: { - flowerStar: '#7289da', + flowerStar: '#4f545c', icon: '#ffffff' }, VERIFIED: { @@ -115,7 +117,7 @@ module.exports = class InviteRenderer { const mainContainer = canvas.nested() .width(INVITE_WIDTH - 2 * PADDING) .height(INVITE_HEIGHT - 2 * PADDING) - .move(PADDING, PADDING) + .move(PADDING, 80) // Header const headerContainer = mainContainer.nested().width(mainContainer.width()).height(HEADER_LINE_HEIGHT) @@ -125,12 +127,12 @@ module.exports = class InviteRenderer { const contentContainer = mainContainer.nested() .width(mainContainer.width()) .height(mainContainer.height() - headerContainer.height() - HEADER_MARGIN_BOTTOM) - .move(0, headerContainer.height() + HEADER_MARGIN_BOTTOM) + .move(0, headerContainer.height() + HEADER_MARGIN_BOTTOM - 64) // Server Icon const squircle = contentContainer.rect(ICON_SIZE, ICON_SIZE).radius(16).fill(themeColors.serverIcon) if (invite.guild.icon) { - const iconBase64 = await Discord.fetchIcon(Discord.getIconUrl(invite.guild.id, invite.guild.icon)) + const iconBase64 = await Discord.fetchBase64Image(Discord.getIconUrl(invite.guild.id, invite.guild.icon)) const iconImage = contentContainer.image(`data:image/${invite.guild.icon.startsWith('a_') ? 'gif' : 'jpg'};base64,${iconBase64}`).size(ICON_SIZE, ICON_SIZE) iconImage.clipWith(squircle) } @@ -146,7 +148,7 @@ module.exports = class InviteRenderer { buttonContainer.rect(BUTTON_WIDTH, BUTTON_HEIGHT) .radius(3) .fill(themeColors.joinButtonBackground) - const joinButtonText = buttonContainer.path(whitneyMedium.getD(locale.button, { fontSize: 14 })) + const joinButtonText = buttonContainer.path(whitneySemibold.getD(locale.button, { fontSize: 14 })) .fill(themeColors.joinButtonText) joinButtonText.move((BUTTON_WIDTH - joinButtonText.width()) / 2, (BUTTON_HEIGHT - joinButtonText.height()) / 2) @@ -185,6 +187,13 @@ module.exports = class InviteRenderer { EXTRA_SERVER_NAME_PADDING = flowerStar.width() + BADGE_MARGIN_RIGHT } + const splashcircle = contentContainer.rect(SPLASH_HEIGHT, SPLASH_WIDTH).radius(16).fill(themeColors.serverIcon) + if (invite.guild.splash) { + const iconBase64 = await Discord.fetchBase64Image(Discord.getSplashUrl(invite.guild.id, invite.guild.splash)) + const iconImage = contentContainer.image(`data:image/jpg;base64,${iconBase64}`).size(SPLASH_WIDTH, SPLASH_HEIGHT) + iconImage.clipWith(splashcircle) + } + // Server Name const serverNameText = innerContainer.path(whitneySemibold.getD(invite.guild.name, { anchor: 'top left', fontSize: SERVER_NAME_SIZE })) .fill(themeColors.serverName)