From 6133a3d59f47dfe1f2ce3ed3181017930ec5ebd0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 6 Mar 2023 22:09:05 +0000 Subject: [PATCH] feat: update checker --- package.json | 3 +- pnpm-lock.yaml | 69 ++++++++++++++++++++++----- src/i18n/en-GB.yml | 7 ++- src/index.js | 2 +- src/lib/logger.js | 9 +++- src/lib/updates.js | 89 +++++++++++++++++++++++++++++++++++ src/listeners/client/ready.js | 6 +++ user/example.config.yml | 1 + 8 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 src/lib/updates.js diff --git a/package.json b/package.json index 3fd9856..aeb15dd 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "homepage": "https://discordtickets.app", "engines": { - "node": ">=18.0" + "node": ">=18" }, "dependencies": { "@discord-tickets/settings": "^1.4.2", @@ -50,6 +50,7 @@ "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.10.1", + "boxen": "^7.0.2", "cryptr": "^6.1.0", "discord.js": "^14.7.1", "dotenv": "^16.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e536402..bb6f6bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,7 @@ specifiers: '@fastify/oauth2': ^5.1.0 '@prisma/client': ^4.10.1 all-contributors-cli: ^6.24.0 + boxen: ^7.0.2 bufferutil: ^4.0.7 conventional-changelog-cli: ^2.2.2 cryptr: ^6.1.0 @@ -55,6 +56,7 @@ dependencies: '@fastify/jwt': 5.0.1 '@fastify/oauth2': 5.1.0 '@prisma/client': 4.10.1_prisma@4.10.1 + boxen: 7.0.2 cryptr: 6.1.0 discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 @@ -800,6 +802,12 @@ packages: - encoding dev: true + /ansi-align/3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + dependencies: + string-width: 4.2.3 + dev: false + /ansi-escapes/4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -809,12 +817,10 @@ packages: /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex/6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles/3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -833,7 +839,6 @@ packages: /ansi-styles/6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /anymatch/3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -944,6 +949,20 @@ packages: - supports-color dev: false + /boxen/7.0.2: + resolution: {integrity: sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==} + engines: {node: '>=14.16'} + dependencies: + ansi-align: 3.0.1 + camelcase: 7.0.1 + chalk: 5.2.0 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 + dev: false + /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -1010,6 +1029,11 @@ packages: engines: {node: '>=6'} dev: true + /camelcase/7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + dev: false + /chalk/2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1027,6 +1051,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk/5.2.0: + resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + /chardet/0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true @@ -1051,6 +1080,11 @@ packages: engines: {node: '>=6'} dev: true + /cli-boxes/3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + dev: false + /cli-cursor/3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1518,7 +1552,6 @@ packages: /eastasianwidth/0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /ecdsa-sig-formatter/1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -1540,11 +1573,9 @@ packages: /emoji-regex/8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex/9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true /emojilib/2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} @@ -2349,7 +2380,6 @@ packages: /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point/4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -3740,7 +3770,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width/5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -3749,7 +3778,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.0.1 - dev: true /string_decoder/1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -3767,14 +3795,12 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi/7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom/3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} @@ -4024,6 +4050,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest/2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: false + /type-is/1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -4132,6 +4163,13 @@ packages: isexe: 2.0.0 dev: true + /widest-line/4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + dev: false + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} @@ -4159,6 +4197,15 @@ packages: strip-ansi: 6.0.1 dev: true + /wrap-ansi/8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.0.1 + dev: false + /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index cf7df34..22322be 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -183,7 +183,6 @@ commands: name: tag tickets: description: List your own or someone else's tickets - fields: name: tickets not_staff: description: Only staff members can view others' tickets. @@ -346,6 +345,12 @@ misc: unknown_category: description: Please try a different category. title: ❌ That ticket category doesn't exist + update: + description: | + > [View `{version}` on GitHub]({github}) + > [Changelog]({changelog}) + > [Update guide]({guide}) + title: An update is available modals: feedback: comment: diff --git a/src/index.js b/src/index.js index 25d507f..65f5ba5 100644 --- a/src/index.js +++ b/src/index.js @@ -32,7 +32,7 @@ const { colours } = require('leeks.js'); // check node version if (!semver.satisfies(process.versions.node, pkg.engines.node)) { - console.log('\x07' + colours.redBright(`Error: Your current Node.js version, ${process.versions.node}, does not meet the requirement "${pkg.engines.node}".`)); + console.log('\x07' + colours.redBright(`Error: Your current Node.js version, ${process.versions.node}, does not meet the requirement "${pkg.engines.node}". Please update to version ${semver.minVersion(pkg.engines.node).version} or higher.`)); process.exit(1); } diff --git a/src/lib/logger.js b/src/lib/logger.js index 701aa41..f0a0ab4 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -5,6 +5,7 @@ const { } = require('leekslazylogger'); const DTF = require('@eartharoid/dtf'); const { short } = require('leeks.js'); +const { format } = require('util'); const dtf = new DTF('en-GB'); const colours = { @@ -24,7 +25,13 @@ module.exports = config => { format: log => { const timestamp = dtf.fill('DD/MM/YY HH:mm:ss', log.timestamp); const colour = colours[log.level.name]; - return short(`&f&!7 ${timestamp} &r ${colour[0]}[${log.level.name.toUpperCase()}]&r ${log.namespace ? `&d(${log.namespace.toUpperCase()})&r ` : ''}${colour[1]}${log.content}`); + return format( + short(`&f&!7 %s &r ${colour[0]}[%s]&r %s${colour[1]}%s&r`), + timestamp, + log.level.name.toUpperCase(), + log.namespace ? short(`&d(${log.namespace.toUpperCase()})&r `) : '', + log.content, + ); }, level: config.logs.level, }), diff --git a/src/lib/updates.js b/src/lib/updates.js new file mode 100644 index 0000000..4daec76 --- /dev/null +++ b/src/lib/updates.js @@ -0,0 +1,89 @@ +const semver = require('semver'); +const { short } = require('leeks.js'); +const ExtendedEmbedBuilder = require('./embed'); +const { version: currentVersion } = require('../../package.json'); + +/** @param {import("client")} client */ +module.exports = client => { + client.log.info('Checking for updates...'); + fetch('https://api.github.com/repos/discord-tickets/bot/releases') + .then(res => res.json()) + .then(async json => { + // releases are ordered by date, so a patch for an old version could be before the latest version + const releases = json + .filter(release => !release.prerelease) + .sort((a, b) => semver.compare(semver.coerce(b.tag_name)?.version, semver.coerce(a.tag_name)?.version)); + const latestRelease = releases[0]; + const latestVersion = semver.coerce(latestRelease.tag_name)?.version; + const compared = semver.compare(latestVersion, currentVersion); + + switch (compared) { + case -1: { + client.log.notice('You are running a pre-release version of Discord Tickets'); + break; + } + case 0: { + client.log.info('No updates available'); + break; + } + case 1: { + let currentRelease = releases.findIndex(release => semver.coerce(release.tag_name)?.version === currentVersion); + if (currentRelease === -1) return client.log.warn('Failed to find current release'); + const behind = currentRelease; + currentRelease = releases[currentRelease]; + const changelog = `https://discordtickets.app/changelogs/v${latestVersion.replaceAll('.', '') }/`; + const guide = 'https://discordtickets.app/self-hosting/updating/'; + const { default: boxen } = await import('boxen'); + + client.log.notice( + short('&r&6A new version of Discord Tickets is available (&c%s&6 -> &a%s&6)&r\n'), + currentVersion, + latestVersion, + boxen( + short([ // uses template literals to ensure boxen adds the correct padding + `&6You are &f${behind}&6 version${behind === 1 ? '' : 's'} behind the latest version, &a${latestVersion}&6.&r`, + `&6Changelog: &e${changelog}&r`, + `&6Update guide: &e${guide}&r`, + ].join('\n')), + { + align: 'center', + borderColor: 'yellow', + borderStyle: 'round', + margin: 1, + padding: 1, + title: 'Update available', + }), + ); + + if (process.env.PUBLIC_BOT !== 'true') { + const guilds = await client.prisma.guild.findMany({ where: { logChannel: { not: null } } }); + for (const guild of guilds) { + const getMessage = client.i18n.getLocale(guild.locale); + await client.channels.cache.get(guild.logChannel).send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor('Blurple') + .setAuthor({ + iconURL: latestRelease.author.avatar_url, + name: latestRelease.author.login, + }) + .setTitle(getMessage('misc.update.title')) + .setDescription(getMessage('misc.update.fields.links.value', { + changelog, + github: latestRelease.html_url, + guide, + version: latestRelease.tag_name, + })), + ], + }); + } + } + break; + } + } + }) + .catch(error => { + client.log.warn('Failed to check for updates'); + client.log.error(error); + }); +}; \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 880ee6a..e892210 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -4,6 +4,7 @@ const ms = require('ms'); const { version } = require('../../../package.json'); const { msToMins } = require('../../lib/misc'); const sync = require('../../lib/sync'); +const checkForUpdates = require('../../lib/updates'); module.exports = class extends Listener { constructor(client, options) { @@ -118,6 +119,11 @@ module.exports = class extends Listener { setInterval(() => send(), ms('12h')); } + if (client.config.updates) { + checkForUpdates(client); + setInterval(() => checkForUpdates(client), ms('1w')); + } + setInterval(() => { // TODO: check lastMessageAt and set stale // this.$stale.set(ticket.id, { diff --git a/user/example.config.yml b/user/example.config.yml index c8a2297..d60910f 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -33,3 +33,4 @@ presence: stats: true templates: transcript: transcript.md +updates: true