a super shitty discord bot but i needed logging
This commit is contained in:
parent
373a6355a6
commit
a086bebc40
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
@ -18,7 +18,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.9.0",
|
||||
"@typescript-eslint/parser": "^8.9.0",
|
||||
"@vercel/og": "^0.6.3",
|
||||
"discord-webhook-node": "^1.1.8",
|
||||
"discordx": "^11.12.1",
|
||||
"elysia": "latest",
|
||||
"elysia-autoroutes": "^0.5.0",
|
||||
"elysia-decorators": "^1.0.2",
|
||||
|
53
projects/backend/src/bot/bot.ts
Normal file
53
projects/backend/src/bot/bot.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Client, MetadataStorage } from "discordx";
|
||||
import { Config } from "@ssr/common/config";
|
||||
import { ActivityType, EmbedBuilder } from "discord.js";
|
||||
|
||||
export enum DiscordChannels {
|
||||
trackedPlayerLogs = "1295985197262569512",
|
||||
numberOneFeed = "1295988063817830430",
|
||||
backendLogs = "1296524935237468250",
|
||||
}
|
||||
|
||||
export const DiscordBot = new Client({
|
||||
intents: [],
|
||||
presence: {
|
||||
status: "online",
|
||||
activities: [
|
||||
{
|
||||
name: "scores...",
|
||||
type: ActivityType.Watching,
|
||||
url: "https://ssr.fascinated.cc",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
DiscordBot.once("ready", () => {
|
||||
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] });
|
||||
}
|
@ -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);
|
||||
|
@ -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}"`;
|
||||
|
@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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 = () => {
|
||||
|
Reference in New Issue
Block a user