mirror of
https://github.com/informaticker/discord-jellyfin-bot.git
synced 2024-11-23 18:21:55 +01:00
✨ Add update checker #13
This commit is contained in:
parent
cadb67e291
commit
73d36ae1f3
@ -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",
|
||||||
|
@ -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: [],
|
||||||
|
@ -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',
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
6
src/models/github-release.ts
Normal file
6
src/models/github-release.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface GithubRelease {
|
||||||
|
html_url: string;
|
||||||
|
tag_name: string;
|
||||||
|
name: string;
|
||||||
|
published_at: string;
|
||||||
|
}
|
12
src/updates/updates.module.ts
Normal file
12
src/updates/updates.module.ts
Normal 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 {}
|
112
src/updates/updates.service.ts
Normal file
112
src/updates/updates.service.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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',
|
||||||
|
49
yarn.lock
49
yarn.lock
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user