Add update checker #13

This commit is contained in:
Manuel Ruwe 2022-12-19 11:57:04 +01:00
parent cadb67e291
commit 73d36ae1f3
9 changed files with 205 additions and 3 deletions

View File

@ -31,6 +31,7 @@
"@nestjs/core": "^9.0.0", "@nestjs/core": "^9.0.0",
"@nestjs/event-emitter": "^1.3.1", "@nestjs/event-emitter": "^1.3.1",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"@nestjs/schedule": "^2.1.0",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"discord.js": "^14.7.1", "discord.js": "^14.7.1",
"joi": "^17.7.0", "joi": "^17.7.0",
@ -44,6 +45,7 @@
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0", "@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0", "@nestjs/testing": "^9.0.0",
"@types/cron": "^2.0.0",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jest": "28.1.8", "@types/jest": "28.1.8",
"@types/node": "^16.0.0", "@types/node": "^16.0.0",

View File

@ -4,12 +4,14 @@ import * as Joi from 'joi';
import { DiscordModule } from '@discord-nestjs/core'; import { DiscordModule } from '@discord-nestjs/core';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule';
import { DiscordConfigService } from './clients/discord/discord.config.service'; import { DiscordConfigService } from './clients/discord/discord.config.service';
import { DiscordClientModule } from './clients/discord/discord.module'; import { DiscordClientModule } from './clients/discord/discord.module';
import { JellyfinClientModule } from './clients/jellyfin/jellyfin.module'; import { JellyfinClientModule } from './clients/jellyfin/jellyfin.module';
import { CommandModule } from './commands/command.module'; import { CommandModule } from './commands/command.module';
import { PlaybackModule } from './playback/playback.module'; import { PlaybackModule } from './playback/playback.module';
import { UpdatesModule } from './updates/updates.module';
@Module({ @Module({
imports: [ imports: [
@ -19,8 +21,10 @@ import { PlaybackModule } from './playback/playback.module';
JELLYFIN_SERVER_ADDRESS: Joi.string().required(), JELLYFIN_SERVER_ADDRESS: Joi.string().required(),
JELLYFIN_AUTHENTICATION_USERNAME: Joi.string().required(), JELLYFIN_AUTHENTICATION_USERNAME: Joi.string().required(),
JELLYFIN_AUTHENTICATION_PASSWORD: Joi.string().required(), JELLYFIN_AUTHENTICATION_PASSWORD: Joi.string().required(),
UPDATER_DISABLE_NOTIFICATIONS: Joi.boolean(),
}), }),
}), }),
ScheduleModule.forRoot(),
DiscordModule.forRootAsync({ DiscordModule.forRootAsync({
useClass: DiscordConfigService, useClass: DiscordConfigService,
}), }),
@ -30,6 +34,7 @@ import { PlaybackModule } from './playback/playback.module';
DiscordClientModule, DiscordClientModule,
JellyfinClientModule, JellyfinClientModule,
PlaybackModule, PlaybackModule,
UpdatesModule,
], ],
controllers: [], controllers: [],
providers: [], providers: [],

View File

@ -20,7 +20,7 @@ export class JellyfinService {
this.jellyfin = new Jellyfin({ this.jellyfin = new Jellyfin({
clientInfo: { clientInfo: {
name: Constants.Metadata.ApplicationName, name: Constants.Metadata.ApplicationName,
version: Constants.Metadata.Version, version: Constants.Metadata.Version.All(),
}, },
deviceInfo: { deviceInfo: {
id: 'jellyfin-discord-bot', id: 'jellyfin-discord-bot',

View File

@ -56,7 +56,7 @@ export class StatusCommand implements DiscordCommand {
return embedBuilder.addFields([ return embedBuilder.addFields([
{ {
name: 'Bot Version', name: 'Bot Version',
value: Constants.Metadata.Version, value: Constants.Metadata.Version.All(),
inline: true, inline: true,
}, },
{ {

View File

@ -0,0 +1,6 @@
export interface GithubRelease {
html_url: string;
tag_name: string;
name: string;
published_at: string;
}

View File

@ -0,0 +1,12 @@
import { DiscordModule } from '@discord-nestjs/core';
import { Module } from '@nestjs/common';
import { DiscordClientModule } from '../clients/discord/discord.module';
import { UpdatesService } from './updates.service';
@Module({
imports: [DiscordModule.forFeature(), DiscordClientModule],
providers: [UpdatesService],
controllers: [],
exports: [],
})
export class UpdatesModule {}

View File

@ -0,0 +1,112 @@
import { InjectDiscordClient } from '@discord-nestjs/core';
import { ButtonBuilder } from '@discordjs/builders';
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import axios from 'axios';
import { formatRelative, parseISO } from 'date-fns';
import { ActionRowBuilder, ButtonStyle, Client } from 'discord.js';
import { DiscordMessageService } from '../clients/discord/discord.message.service';
import { GithubRelease } from '../models/github-release';
import { Constants } from '../utils/constants';
@Injectable()
export class UpdatesService {
private readonly logger = new Logger(UpdatesService.name);
constructor(
@InjectDiscordClient() private readonly client: Client,
private readonly discordMessageService: DiscordMessageService,
) {}
@Cron('0 0 */1 * * *')
async handleCron() {
const isDisabled = process.env.UPDATER_DISABLE_NOTIFICATIONS;
if (isDisabled === 'true') {
return;
}
this.logger.debug('Checking for available updates...');
const latestGitHubRelease = await this.fetchLatestGithubRelease();
const currentVersion = Constants.Metadata.Version.All();
if (latestGitHubRelease.tag_name <= currentVersion) {
return;
}
await this.contactOwnerAboutUpdate(currentVersion, latestGitHubRelease);
}
private async contactOwnerAboutUpdate(
currentVersion: string,
latestVersion: GithubRelease,
) {
const guilds = this.client.guilds.cache;
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel('See update')
.setStyle(ButtonStyle.Link)
.setURL(latestVersion.html_url),
new ButtonBuilder()
.setLabel('Report an issue')
.setStyle(ButtonStyle.Link)
.setURL(Constants.Links.ReportIssue),
new ButtonBuilder()
.setLabel('Turn this notification off')
.setStyle(ButtonStyle.Link)
.setURL(Constants.Links.Wiki.DisableNotifications),
);
const isoDate = parseISO(latestVersion.published_at);
const relativeReadable = formatRelative(isoDate, new Date());
guilds.forEach(async (guild, key) => {
const owner = await guild.fetchOwner();
await owner.send({
content: 'Update notification',
embeds: [
this.discordMessageService.buildMessage({
title: 'Update is available',
description: `Hello @${owner.user.tag},\nI'd like to inform you, that there is a new update available.\nTo ensure best security and being able to use the latest features, please update to the newest version.\n\n**${latestVersion.name}** (published ${relativeReadable})\n`,
mixin(embedBuilder) {
return embedBuilder.addFields([
{
name: 'Your version',
value: currentVersion,
inline: true,
},
{
name: 'Newest version',
value: latestVersion.tag_name,
inline: true,
},
]);
},
}),
],
components: [actionRow],
});
});
}
private async fetchLatestGithubRelease(): Promise<null | GithubRelease> {
return axios({
method: 'GET',
url: Constants.Links.Api.GetLatestRelease,
})
.then((response) => {
if (response.status !== 200) {
return null;
}
return response.data as GithubRelease;
})
.catch((err) => {
this.logger.error('Error while checking for updates', err);
return null;
});
}
}

View File

@ -1,6 +1,12 @@
export const Constants = { export const Constants = {
Metadata: { Metadata: {
Version: '0.0.1', Version: {
Major: 0,
Minor: 0,
Patch: 1,
All: () =>
`${Constants.Metadata.Version.Major}.${Constants.Metadata.Version.Minor}.${Constants.Metadata.Version.Patch}`,
},
ApplicationName: 'Discord Jellyfin Music Bot', ApplicationName: 'Discord Jellyfin Music Bot',
}, },
Links: { Links: {
@ -12,6 +18,16 @@ export const Constants = {
new URL( new URL(
`https://github.com/manuel-rw/jellyfin-discord-music-bot/issues/new?assignees=&labels=&template=bug_report.md&title=${title}`, `https://github.com/manuel-rw/jellyfin-discord-music-bot/issues/new?assignees=&labels=&template=bug_report.md&title=${title}`,
), ),
ReleasesPage:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/releases',
Wiki: {
DisableNotifications:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/wiki/%F0%9F%93%A2-Update-Notifications',
},
Api: {
GetLatestRelease:
'https://api.github.com/repos/manuel-rw/jellyfin-discord-music-bot/releases/latest',
},
}, },
Design: { Design: {
InvisibleSpace: '\u1CBC', InvisibleSpace: '\u1CBC',

View File

@ -1155,6 +1155,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@nestjs/schedule@npm:^2.1.0":
version: 2.1.0
resolution: "@nestjs/schedule@npm:2.1.0"
dependencies:
cron: 2.0.0
uuid: 8.3.2
peerDependencies:
"@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0
"@nestjs/core": ^7.0.0 || ^8.0.0 || ^9.0.0
reflect-metadata: ^0.1.12
checksum: 43423eb0491c692c08dcdb6d18d34ec758fe29cda52f4674a945e06933ec5b4e23229193c1b071971842b50db57024d6f1c55fe8f4c6392754b6a597b31eb423
languageName: node
linkType: hard
"@nestjs/schematics@npm:^9.0.0": "@nestjs/schematics@npm:^9.0.0":
version: 9.0.3 version: 9.0.3
resolution: "@nestjs/schematics@npm:9.0.3" resolution: "@nestjs/schematics@npm:9.0.3"
@ -1430,6 +1444,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/cron@npm:^2.0.0":
version: 2.0.0
resolution: "@types/cron@npm:2.0.0"
dependencies:
"@types/luxon": "*"
"@types/node": "*"
checksum: 392d2cfca51504140397533c30be8facd2196251074eb26ee09232a7e983144ff1d8364cd64922ed22d142686a4724934a70672fc8353b441fea729ac0ed0611
languageName: node
linkType: hard
"@types/eslint-scope@npm:^3.7.3": "@types/eslint-scope@npm:^3.7.3":
version: 3.7.4 version: 3.7.4
resolution: "@types/eslint-scope@npm:3.7.4" resolution: "@types/eslint-scope@npm:3.7.4"
@ -1538,6 +1562,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/luxon@npm:*":
version: 3.1.0
resolution: "@types/luxon@npm:3.1.0"
checksum: 04768029342ad76fc2a9339436c143ea64797b35cf9b03ddded787c13eae30f0ca1246e51c2c5365ed912f98068e13a967a3931b137eb4585248a0ad7ec3fa86
languageName: node
linkType: hard
"@types/mime@npm:*": "@types/mime@npm:*":
version: 3.0.1 version: 3.0.1
resolution: "@types/mime@npm:3.0.1" resolution: "@types/mime@npm:3.0.1"
@ -2894,6 +2925,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cron@npm:2.0.0":
version: 2.0.0
resolution: "cron@npm:2.0.0"
dependencies:
luxon: ^1.23.x
checksum: 179ec137ada4ceb44cafe51c55491e84954308d7012d2a44539f0dadbbb1ffbbe3072c2f7aaa88595d60bd56e0d536aae2e4aaa4430c869317d6c2abd966988b
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
version: 7.0.3 version: 7.0.3
resolution: "cross-spawn@npm:7.0.3" resolution: "cross-spawn@npm:7.0.3"
@ -4452,8 +4492,10 @@ __metadata:
"@nestjs/core": ^9.0.0 "@nestjs/core": ^9.0.0
"@nestjs/event-emitter": ^1.3.1 "@nestjs/event-emitter": ^1.3.1
"@nestjs/platform-express": ^9.0.0 "@nestjs/platform-express": ^9.0.0
"@nestjs/schedule": ^2.1.0
"@nestjs/schematics": ^9.0.0 "@nestjs/schematics": ^9.0.0
"@nestjs/testing": ^9.0.0 "@nestjs/testing": ^9.0.0
"@types/cron": ^2.0.0
"@types/express": ^4.17.13 "@types/express": ^4.17.13
"@types/jest": 28.1.8 "@types/jest": 28.1.8
"@types/node": ^16.0.0 "@types/node": ^16.0.0
@ -5189,6 +5231,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"luxon@npm:^1.23.x":
version: 1.28.0
resolution: "luxon@npm:1.28.0"
checksum: 5250cb9f138b6048eeb0b3a9044a4ac994d0058f680c72a0da4b6aeaec8612460385639cba2b1052ef6d5564879e9ed144d686f26d9d97b38ab920d82e18281c
languageName: node
linkType: hard
"macos-release@npm:^2.5.0": "macos-release@npm:^2.5.0":
version: 2.5.0 version: 2.5.0
resolution: "macos-release@npm:2.5.0" resolution: "macos-release@npm:2.5.0"