From 8f6fa715192a72d3e298d4e9d1580d26365cd8de Mon Sep 17 00:00:00 2001 From: Daniel Dietzler Date: Tue, 12 Nov 2024 21:13:12 +0100 Subject: [PATCH] chore: rework release notifications --- src/interfaces/discord.interface.ts | 13 +++- src/repositories/discord.repository.ts | 18 +++++- src/services/discord.service.ts | 10 ++- src/services/report.service.ts | 57 ++++++++++------- src/services/webhook.service.ts | 88 ++++++++++++++++++-------- src/util.ts | 2 +- 6 files changed, 130 insertions(+), 58 deletions(-) diff --git a/src/interfaces/discord.interface.ts b/src/interfaces/discord.interface.ts index 5df3aa0..8f32fae 100644 --- a/src/interfaces/discord.interface.ts +++ b/src/interfaces/discord.interface.ts @@ -1,8 +1,9 @@ -import { Message, MessageCreateOptions } from 'discord.js'; +import { MessageCreateOptions } from 'discord.js'; export const IDiscordInterface = 'IDiscordInterface'; export enum DiscordChannel { + Announcements = '991930592843272342', HelpDesk = '1049703391762321418', General = '994044917355663450', BotSpam = '1159083520027787307', @@ -24,5 +25,13 @@ export enum DiscordEvents { export interface IDiscordInterface { login(token: string): Promise; - sendMessage(channel: DiscordChannel, message: string | MessageCreateOptions): Promise; + sendMessage({ + channel, + message, + crosspost, + }: { + channel: DiscordChannel; + message: string | MessageCreateOptions; + crosspost?: boolean; + }): Promise; } diff --git a/src/repositories/discord.repository.ts b/src/repositories/discord.repository.ts index 27439d2..8294acd 100644 --- a/src/repositories/discord.repository.ts +++ b/src/repositories/discord.repository.ts @@ -1,5 +1,5 @@ import { Logger } from '@nestjs/common'; -import { IntentsBitField, Message, MessageCreateOptions, Partials } from 'discord.js'; +import { IntentsBitField, MessageCreateOptions, Partials } from 'discord.js'; import { Client } from 'discordx'; import { DiscordChannel, IDiscordInterface } from 'src/interfaces/discord.interface'; @@ -65,10 +65,22 @@ export class DiscordRepository implements IDiscordInterface { await bot.login(token); } - async sendMessage(channel: DiscordChannel, message: MessageCreateOptions): Promise { + async sendMessage({ + channel, + message, + crosspost = false, + }: { + channel: DiscordChannel; + message: MessageCreateOptions; + crosspost?: boolean; + }): Promise { const textChannel = await bot.channels.fetch(channel); if (textChannel?.isSendable()) { - return textChannel.send(message); + const sentMessage = await textChannel.send(message); + + if (crosspost) { + await sentMessage.crosspost(); + } } } } diff --git a/src/services/discord.service.ts b/src/services/discord.service.ts index 78d6a02..ff06e5b 100644 --- a/src/services/discord.service.ts +++ b/src/services/discord.service.ts @@ -43,7 +43,10 @@ export class DiscordService { @Cron(Constants.Cron.ImmichBirthday) async onBirthday() { - await this.discord.sendMessage(DiscordChannel.General, `"Happy birthday my other child" - Alex`); + await this.discord.sendMessage({ + channel: DiscordChannel.General, + message: `"Happy birthday my other child" - Alex`, + }); } private async getVersionMessage() { @@ -66,7 +69,10 @@ export class DiscordService { this.logger.log(`Bot ${versionMessage} started`); if (versionMessage) { - await this.discord.sendMessage(DiscordChannel.BotSpam, `I'm alive, running ${versionMessage}!`); + await this.discord.sendMessage({ + channel: DiscordChannel.BotSpam, + message: `I'm alive, running ${versionMessage}!`, + }); } } diff --git a/src/services/report.service.ts b/src/services/report.service.ts index b47168b..1cc1ba6 100644 --- a/src/services/report.service.ts +++ b/src/services/report.service.ts @@ -19,14 +19,17 @@ export class ReportService { const endOfYesterday = DateTime.now().minus({ days: 1 }).endOf('day'); const { server, client } = await this.database.getTotalLicenseCount({ day: endOfYesterday }); - await this.discord.sendMessage(DiscordChannel.Stripe, { - embeds: [ - new EmbedBuilder() - .setTitle(`Daily report for ${endOfYesterday.toLocaleString(DateTime.DATE_FULL)}`) - .setDescription(`Total: ${getTotal({ server, client })}`) - .setColor(Colors.Purple) - .setFields(makeLicenseFields({ server, client })), - ], + await this.discord.sendMessage({ + channel: DiscordChannel.Stripe, + message: { + embeds: [ + new EmbedBuilder() + .setTitle(`Daily report for ${endOfYesterday.toLocaleString(DateTime.DATE_FULL)}`) + .setDescription(`Total: ${getTotal({ server, client })}`) + .setColor(Colors.Purple) + .setFields(makeLicenseFields({ server, client })), + ], + }, }); } @@ -36,14 +39,17 @@ export class ReportService { const lastWeek = endOfYesterday.minus({ weeks: 1 }); const { server, client } = await this.database.getTotalLicenseCount({ week: endOfYesterday }); - await this.discord.sendMessage(DiscordChannel.Stripe, { - embeds: [ - new EmbedBuilder() - .setTitle(`Weekly report for ${lastWeek.toFormat('MMMM dd')} - ${endOfYesterday.toFormat('MMMM dd')}`) - .setDescription(`Total: ${getTotal({ server, client })}`) - .setColor(Colors.Purple) - .setFields(makeLicenseFields({ server, client })), - ], + await this.discord.sendMessage({ + channel: DiscordChannel.Stripe, + message: { + embeds: [ + new EmbedBuilder() + .setTitle(`Weekly report for ${lastWeek.toFormat('MMMM dd')} - ${endOfYesterday.toFormat('MMMM dd')}`) + .setDescription(`Total: ${getTotal({ server, client })}`) + .setColor(Colors.Purple) + .setFields(makeLicenseFields({ server, client })), + ], + }, }); } @@ -53,14 +59,17 @@ export class ReportService { const lastMonth = endOfYesterday.minus({ months: 1 }); const { server, client } = await this.database.getTotalLicenseCount({ month: endOfYesterday }); - await this.discord.sendMessage(DiscordChannel.Stripe, { - embeds: [ - new EmbedBuilder() - .setTitle(`Monthly report for ${lastMonth.toFormat('MMMM dd')} - ${endOfYesterday.toFormat('MMMM dd')}`) - .setDescription(`Total: ${getTotal({ server, client })}`) - .setColor(Colors.Purple) - .setFields(makeLicenseFields({ server, client })), - ], + await this.discord.sendMessage({ + channel: DiscordChannel.Stripe, + message: { + embeds: [ + new EmbedBuilder() + .setTitle(`Monthly report for ${lastMonth.toFormat('MMMM dd')} - ${endOfYesterday.toFormat('MMMM dd')}`) + .setDescription(`Total: ${getTotal({ server, client })}`) + .setColor(Colors.Purple) + .setFields(makeLicenseFields({ server, client })), + ], + }, }); } } diff --git a/src/services/webhook.service.ts b/src/services/webhook.service.ts index 7ac75f6..528d389 100644 --- a/src/services/webhook.service.ts +++ b/src/services/webhook.service.ts @@ -68,7 +68,7 @@ export class WebhookService { }); embed.setColor(color); - await this.discord.sendMessage(DiscordChannel.PullRequests, { embeds: [embed] }); + await this.discord.sendMessage({ channel: DiscordChannel.PullRequests, message: { embeds: [embed] } }); return; } @@ -82,7 +82,7 @@ export class WebhookService { }); embed.setColor(this.getIssueEmbedColor({ action })); - await this.discord.sendMessage(DiscordChannel.IssuesAndDiscussions, { embeds: [embed] }); + await this.discord.sendMessage({ channel: DiscordChannel.IssuesAndDiscussions, message: { embeds: [embed] } }); return; } @@ -96,32 +96,44 @@ export class WebhookService { }); embed.setColor(this.getDiscussionEmbedColor({ action })); - await this.discord.sendMessage(DiscordChannel.IssuesAndDiscussions, { embeds: [embed] }); + await this.discord.sendMessage({ channel: DiscordChannel.IssuesAndDiscussions, message: { embeds: [embed] } }); return; } if ('release' in dto && action === 'released') { - const content = `${_.sample(ReleaseMessages)} ${dto.release.html_url}`; + const embedProps = { + repositoryName: dto.repository.full_name, + name: dto.release.name, + url: dto.release.html_url, + user: dto.sender, + description: dto.repository.full_name === 'immich-app/immich' ? _.sample(ReleaseMessages) : undefined, + }; const messages = [ - (async () => { - const message = await this.discord.sendMessage(DiscordChannel.Releases, { - content: `[${dto.repository.full_name}] ${content}`, - flags: [MessageFlags.SuppressEmbeds], - }); - await message?.crosspost(); - })(), + this.discord.sendMessage({ + channel: DiscordChannel.Releases, + message: { + embeds: [this.getReleaseEmbed(embedProps)], + }, + crosspost: true, + }), ]; - if (dto.repository.full_name === 'immich-app/immich') { + if (embedProps.description) { messages.push( + this.discord.sendMessage({ + channel: DiscordChannel.Announcements, + message: { + embeds: [this.getReleaseEmbed(embedProps)], + }, + crosspost: true, + }), this.zulip.sendMessage({ stream: Constants.Zulip.Streams.Immich, topic: Constants.Zulip.Topics.ImmichRelease, - content, + content: embedProps.description, }), ); } - await Promise.all(messages); } } @@ -157,7 +169,7 @@ export class WebhookService { } } - await this.discord.sendMessage(DiscordChannel.GithubStatus, { embeds: [embed] }); + await this.discord.sendMessage({ channel: DiscordChannel.GithubStatus, message: { embeds: [embed] } }); } } @@ -207,17 +219,41 @@ export class WebhookService { }); const licenseType = description.split('-')[1]; - await this.discord.sendMessage(DiscordChannel.Stripe, { - embeds: [ - new EmbedBuilder() - .setTitle(`${livemode ? '' : 'TEST PAYMENT - '}Immich ${licenseType} license purchased`) - .setURL(`https://dashboard.stripe.com/${livemode ? '' : 'test/'}payments/${id}`) - .setAuthor({ name: 'Stripe Payments', url: 'https://stripe.com' }) - .setDescription(`Price: ${(amount / 100).toLocaleString()} ${currency.toUpperCase()}`) - .setColor(livemode ? Colors.Green : Colors.Yellow) - .setFields(makeLicenseFields({ server, client })), - ], - flags: [MessageFlags.SuppressNotifications], + await this.discord.sendMessage({ + channel: DiscordChannel.Stripe, + message: { + embeds: [ + new EmbedBuilder() + .setTitle(`${livemode ? '' : 'TEST PAYMENT - '}Immich ${licenseType} license purchased`) + .setURL(`https://dashboard.stripe.com/${livemode ? '' : 'test/'}payments/${id}`) + .setAuthor({ name: 'Stripe Payments', url: 'https://stripe.com' }) + .setDescription(`Price: ${(amount / 100).toLocaleString()} ${currency.toUpperCase()}`) + .setColor(livemode ? Colors.Green : Colors.Yellow) + .setFields(makeLicenseFields({ server, client })), + ], + flags: [MessageFlags.SuppressNotifications], + }, + }); + } + + private getReleaseEmbed({ + repositoryName, + name, + user, + url, + description, + }: { + repositoryName: string; + name: string; + user: User; + url: string; + description?: string; + }) { + return new EmbedBuilder({ + title: `[${repositoryName}] New release: ${name}`, + author: { name: user.login, url: user.html_url, iconURL: user.avatar_url }, + url, + description, }); } diff --git a/src/util.ts b/src/util.ts index 5eb704a..2374f7c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,7 +5,7 @@ type Repos = { discord: IDiscordInterface; logger: Logger }; export const logError = async (message: string, error: unknown, { discord, logger }: Repos) => { logger.error(message, error); try { - await discord.sendMessage(DiscordChannel.BotSpam, `${message}: ${error}`); + await discord.sendMessage({ channel: DiscordChannel.BotSpam, message: `${message}: ${error}` }); } catch (error) { logger.error('Failed to send error message to bot spam channel', error); }