From a086bebc408118bd4e4f0c50d1f33f29602e0141 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 17 Oct 2024 18:29:30 +0100 Subject: [PATCH] a super shitty discord bot but i needed logging --- bun.lockb | Bin 357632 -> 365656 bytes projects/backend/package.json | 2 +- projects/backend/src/bot/bot.ts | 53 ++++++++++++++++++ projects/backend/src/index.ts | 13 ++++- .../backend/src/service/player.service.ts | 41 +++++++++----- projects/backend/src/service/score.service.ts | 31 +++++----- projects/common/src/config.ts | 1 + .../src/websocket/scoresaber-websocket.ts | 15 ++++- 8 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 projects/backend/src/bot/bot.ts diff --git a/bun.lockb b/bun.lockb index 467e84f7c6368dfca985dd25177a96cae3c953cc..99b23de0c7386d743875bcc4da297d15af41ee52 100755 GIT binary patch delta 15457 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 ne6^l=q?$eWjQL})>m2*mXvQpEHHUi5 { + console.log("Discord bot ready!"); +}); + +export function initDiscordBot() { + console.log("Initializing discord bot..."); + + MetadataStorage.instance.build().then(async () => { + await DiscordBot.login(Config.discordBotToken!).then(); + }); +} + +/** + * Logs the message to a discord channel. + * + * @param channelId the channel id to log to + * @param message the message to log + */ +export function logToChannel(channelId: DiscordChannels, message: EmbedBuilder) { + const channel = DiscordBot.channels.cache.find(c => c.id === channelId); + if (channel == undefined) { + throw new Error(`Channel "${channelId}" not found`); + } + if (!channel.isSendable()) { + throw new Error(`Channel "${channelId}" is not sendable`); + } + + channel.send({ embeds: [message] }); +} diff --git a/projects/backend/src/index.ts b/projects/backend/src/index.ts index f0b563c..462db9b 100644 --- a/projects/backend/src/index.ts +++ b/projects/backend/src/index.ts @@ -15,7 +15,7 @@ import PlayerController from "./controller/player.controller"; import { PlayerService } from "./service/player.service"; import { cron } from "@elysiajs/cron"; import { scoresaberService } from "@ssr/common/service/impl/scoresaber"; -import { delay } from "@ssr/common/utils/utils"; +import { delay, isProduction } from "@ssr/common/utils/utils"; import { connectScoreSaberWebSocket } from "@ssr/common/websocket/scoresaber-websocket"; import ImageController from "./controller/image.controller"; import ReplayController from "./controller/replay.controller"; @@ -24,6 +24,8 @@ import { Config } from "@ssr/common/config"; import { PlayerDocument, PlayerModel } from "@ssr/common/model/player"; import ScoresController from "./controller/scores.controller"; import LeaderboardController from "./controller/leaderboard.controller"; +import { DiscordChannels, initDiscordBot, logToChannel } from "./bot/bot"; +import { EmbedBuilder } from "discord.js"; // Load .env file dotenv.config({ @@ -40,6 +42,12 @@ connectScoreSaberWebSocket({ await PlayerService.trackScore(playerScore); await ScoreService.notifyNumberOne(playerScore); }, + onDisconnect: error => { + logToChannel( + DiscordChannels.backendLogs, + new EmbedBuilder().setDescription(`ScoreSaber websocket disconnected: ${error}`) + ); + }, }); export const app = new Elysia(); @@ -179,6 +187,9 @@ app.use(swagger()); app.onStart(() => { console.log("Listening on port http://localhost:8080"); + if (isProduction()) { + initDiscordBot(); + } }); app.listen(8080); diff --git a/projects/backend/src/service/player.service.ts b/projects/backend/src/service/player.service.ts index a703b96..88fa801 100644 --- a/projects/backend/src/service/player.service.ts +++ b/projects/backend/src/service/player.service.ts @@ -10,7 +10,8 @@ import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score import { MessageBuilder, Webhook } from "discord-webhook-node"; import { formatPp } from "@ssr/common/utils/number-utils"; import { isProduction } from "@ssr/common/utils/utils"; -import { Config } from "@ssr/common/config"; +import { DiscordChannels, logToChannel } from "../bot/bot"; +import { EmbedBuilder } from "discord.js"; export class PlayerService { /** @@ -43,19 +44,31 @@ export class PlayerService { // Only notify in production if (isProduction()) { - const hook = new Webhook({ - url: Config.trackedPlayerWebhook, - }); - hook.setUsername("Player Tracker"); - const embed = new MessageBuilder(); - embed.setTitle("New Player Tracked"); - embed.addField("Username", playerToken.name, true); - embed.addField("ID", playerToken.id, true); - embed.addField("PP", formatPp(playerToken.pp) + "pp", true); - embed.setDescription(`https://ssr.fascinated.cc/player/${playerToken.id}`); - embed.setThumbnail(playerToken.profilePicture); - embed.setColor("#00ff00"); - await hook.send(embed); + logToChannel( + DiscordChannels.trackedPlayerLogs, + new EmbedBuilder() + .setTitle("New Player Tracked") + .setDescription(`https://ssr.fascinated.cc/player/${playerToken.id}`) + .addFields([ + { + name: "Username", + value: playerToken.name, + inline: true, + }, + { + name: "ID", + value: playerToken.id, + inline: true, + }, + { + name: "PP", + value: formatPp(playerToken.pp) + "pp", + inline: true, + }, + ]) + .setThumbnail(playerToken.profilePicture) + .setColor("#00ff00") + ); } } catch (err) { const message = `Failed to create player document for "${id}"`; diff --git a/projects/backend/src/service/score.service.ts b/projects/backend/src/service/score.service.ts index 15571ae..6271939 100644 --- a/projects/backend/src/service/score.service.ts +++ b/projects/backend/src/service/score.service.ts @@ -4,7 +4,6 @@ import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score import { MessageBuilder, Webhook } from "discord-webhook-node"; import { formatPp } from "@ssr/common/utils/number-utils"; import { isProduction } from "@ssr/common/utils/utils"; -import { Config } from "@ssr/common/config"; import { Metadata } from "@ssr/common/types/metadata"; import { NotFoundError } from "elysia"; import BeatSaverService from "./beatsaver.service"; @@ -20,6 +19,8 @@ import { PlayerScore } from "@ssr/common/score/player-score"; import LeaderboardScoresResponse from "@ssr/common/response/leaderboard-scores-response"; import Score from "@ssr/common/score/score"; import PlayerScoresResponse from "@ssr/common/response/player-scores-response"; +import { DiscordChannels, logToChannel } from "../bot/bot"; +import { EmbedBuilder } from "discord.js"; export class ScoreService { /** @@ -45,20 +46,20 @@ export class ScoreService { return; } - const hook = new Webhook({ - url: Config.numberOneWebhook, - }); - hook.setUsername("Number One Feed"); - const embed = new MessageBuilder(); - embed.setTitle(`${player.name} set a #${score.rank} on ${leaderboard.songName} ${leaderboard.songSubName}`); - embed.setDescription(` - **Player:** https://ssr.fascinated.cc/player/${player.id} - **Leaderboard:** https://ssr.fascinated.cc/leaderboard/${leaderboard.id} - **PP:** ${formatPp(score.pp)} - `); - embed.setThumbnail(leaderboard.coverImage); - embed.setColor("#00ff00"); - await hook.send(embed); + logToChannel( + DiscordChannels.numberOneFeed, + new EmbedBuilder() + .setTitle(`${player.name} set a #1 on ${leaderboard.songName} ${leaderboard.songSubName}`) + .setDescription( + ` + **Player:** https://ssr.fascinated.cc/player/${player.id} + **Leaderboard:** https://ssr.fascinated.cc/leaderboard/${leaderboard.id} + **PP:** ${formatPp(score.pp)} + ` + ) + .setThumbnail(leaderboard.coverImage) + .setColor("#00ff00") + ); } /** diff --git a/projects/common/src/config.ts b/projects/common/src/config.ts index 02a1b56..d22437f 100644 --- a/projects/common/src/config.ts +++ b/projects/common/src/config.ts @@ -11,4 +11,5 @@ export const Config = { trackedPlayerWebhook: process.env.TRACKED_PLAYERS_WEBHOOK, numberOneWebhook: process.env.NUMBER_ONE_WEBHOOK, mongoUri: process.env.MONGO_URI, + discordBotToken: process.env.DISCORD_BOT_TOKEN, } as const; diff --git a/projects/common/src/websocket/scoresaber-websocket.ts b/projects/common/src/websocket/scoresaber-websocket.ts index f95b611..f24101e 100644 --- a/projects/common/src/websocket/scoresaber-websocket.ts +++ b/projects/common/src/websocket/scoresaber-websocket.ts @@ -5,22 +5,29 @@ type ScoresaberSocket = { /** * Invoked when a general message is received. * - * @param message The received message. + * @param message the received message. */ onMessage?: (message: unknown) => void; /** * Invoked when a score message is received. * - * @param score The received score data. + * @param score the received score data. */ onScore?: (score: ScoreSaberPlayerScoreToken) => void; + + /** + * Invoked when the connection is closed. + * + * @param error the error that caused the connection to close + */ + onDisconnect?: (error: WebSocket.ErrorEvent) => void; }; /** * Connects to the ScoreSaber websocket and handles incoming messages. */ -export function connectScoreSaberWebSocket({ onMessage, onScore }: ScoresaberSocket) { +export function connectScoreSaberWebSocket({ onMessage, onScore, onDisconnect }: ScoresaberSocket) { let websocket: WebSocket | null = null; function connectWs() { @@ -35,6 +42,8 @@ export function connectScoreSaberWebSocket({ onMessage, onScore }: ScoresaberSoc if (websocket) { websocket.close(); // Close the connection on error } + + onDisconnect && onDisconnect(error); }; websocket.onclose = () => {