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/eslint-plugin": "^8.9.0",
|
||||||
"@typescript-eslint/parser": "^8.9.0",
|
"@typescript-eslint/parser": "^8.9.0",
|
||||||
"@vercel/og": "^0.6.3",
|
"@vercel/og": "^0.6.3",
|
||||||
"discord-webhook-node": "^1.1.8",
|
"discordx": "^11.12.1",
|
||||||
"elysia": "latest",
|
"elysia": "latest",
|
||||||
"elysia-autoroutes": "^0.5.0",
|
"elysia-autoroutes": "^0.5.0",
|
||||||
"elysia-decorators": "^1.0.2",
|
"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 { PlayerService } from "./service/player.service";
|
||||||
import { cron } from "@elysiajs/cron";
|
import { cron } from "@elysiajs/cron";
|
||||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
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 { connectScoreSaberWebSocket } from "@ssr/common/websocket/scoresaber-websocket";
|
||||||
import ImageController from "./controller/image.controller";
|
import ImageController from "./controller/image.controller";
|
||||||
import ReplayController from "./controller/replay.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 { PlayerDocument, PlayerModel } from "@ssr/common/model/player";
|
||||||
import ScoresController from "./controller/scores.controller";
|
import ScoresController from "./controller/scores.controller";
|
||||||
import LeaderboardController from "./controller/leaderboard.controller";
|
import LeaderboardController from "./controller/leaderboard.controller";
|
||||||
|
import { DiscordChannels, initDiscordBot, logToChannel } from "./bot/bot";
|
||||||
|
import { EmbedBuilder } from "discord.js";
|
||||||
|
|
||||||
// Load .env file
|
// Load .env file
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
@ -40,6 +42,12 @@ connectScoreSaberWebSocket({
|
|||||||
await PlayerService.trackScore(playerScore);
|
await PlayerService.trackScore(playerScore);
|
||||||
await ScoreService.notifyNumberOne(playerScore);
|
await ScoreService.notifyNumberOne(playerScore);
|
||||||
},
|
},
|
||||||
|
onDisconnect: error => {
|
||||||
|
logToChannel(
|
||||||
|
DiscordChannels.backendLogs,
|
||||||
|
new EmbedBuilder().setDescription(`ScoreSaber websocket disconnected: ${error}`)
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const app = new Elysia();
|
export const app = new Elysia();
|
||||||
@ -179,6 +187,9 @@ app.use(swagger());
|
|||||||
|
|
||||||
app.onStart(() => {
|
app.onStart(() => {
|
||||||
console.log("Listening on port http://localhost:8080");
|
console.log("Listening on port http://localhost:8080");
|
||||||
|
if (isProduction()) {
|
||||||
|
initDiscordBot();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(8080);
|
app.listen(8080);
|
||||||
|
@ -10,7 +10,8 @@ import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score
|
|||||||
import { MessageBuilder, Webhook } from "discord-webhook-node";
|
import { MessageBuilder, Webhook } from "discord-webhook-node";
|
||||||
import { formatPp } from "@ssr/common/utils/number-utils";
|
import { formatPp } from "@ssr/common/utils/number-utils";
|
||||||
import { isProduction } from "@ssr/common/utils/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 {
|
export class PlayerService {
|
||||||
/**
|
/**
|
||||||
@ -43,19 +44,31 @@ export class PlayerService {
|
|||||||
|
|
||||||
// Only notify in production
|
// Only notify in production
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
const hook = new Webhook({
|
logToChannel(
|
||||||
url: Config.trackedPlayerWebhook,
|
DiscordChannels.trackedPlayerLogs,
|
||||||
});
|
new EmbedBuilder()
|
||||||
hook.setUsername("Player Tracker");
|
.setTitle("New Player Tracked")
|
||||||
const embed = new MessageBuilder();
|
.setDescription(`https://ssr.fascinated.cc/player/${playerToken.id}`)
|
||||||
embed.setTitle("New Player Tracked");
|
.addFields([
|
||||||
embed.addField("Username", playerToken.name, true);
|
{
|
||||||
embed.addField("ID", playerToken.id, true);
|
name: "Username",
|
||||||
embed.addField("PP", formatPp(playerToken.pp) + "pp", true);
|
value: playerToken.name,
|
||||||
embed.setDescription(`https://ssr.fascinated.cc/player/${playerToken.id}`);
|
inline: true,
|
||||||
embed.setThumbnail(playerToken.profilePicture);
|
},
|
||||||
embed.setColor("#00ff00");
|
{
|
||||||
await hook.send(embed);
|
name: "ID",
|
||||||
|
value: playerToken.id,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PP",
|
||||||
|
value: formatPp(playerToken.pp) + "pp",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.setThumbnail(playerToken.profilePicture)
|
||||||
|
.setColor("#00ff00")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = `Failed to create player document for "${id}"`;
|
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 { MessageBuilder, Webhook } from "discord-webhook-node";
|
||||||
import { formatPp } from "@ssr/common/utils/number-utils";
|
import { formatPp } from "@ssr/common/utils/number-utils";
|
||||||
import { isProduction } from "@ssr/common/utils/utils";
|
import { isProduction } from "@ssr/common/utils/utils";
|
||||||
import { Config } from "@ssr/common/config";
|
|
||||||
import { Metadata } from "@ssr/common/types/metadata";
|
import { Metadata } from "@ssr/common/types/metadata";
|
||||||
import { NotFoundError } from "elysia";
|
import { NotFoundError } from "elysia";
|
||||||
import BeatSaverService from "./beatsaver.service";
|
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 LeaderboardScoresResponse from "@ssr/common/response/leaderboard-scores-response";
|
||||||
import Score from "@ssr/common/score/score";
|
import Score from "@ssr/common/score/score";
|
||||||
import PlayerScoresResponse from "@ssr/common/response/player-scores-response";
|
import PlayerScoresResponse from "@ssr/common/response/player-scores-response";
|
||||||
|
import { DiscordChannels, logToChannel } from "../bot/bot";
|
||||||
|
import { EmbedBuilder } from "discord.js";
|
||||||
|
|
||||||
export class ScoreService {
|
export class ScoreService {
|
||||||
/**
|
/**
|
||||||
@ -45,20 +46,20 @@ export class ScoreService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hook = new Webhook({
|
logToChannel(
|
||||||
url: Config.numberOneWebhook,
|
DiscordChannels.numberOneFeed,
|
||||||
});
|
new EmbedBuilder()
|
||||||
hook.setUsername("Number One Feed");
|
.setTitle(`${player.name} set a #1 on ${leaderboard.songName} ${leaderboard.songSubName}`)
|
||||||
const embed = new MessageBuilder();
|
.setDescription(
|
||||||
embed.setTitle(`${player.name} set a #${score.rank} on ${leaderboard.songName} ${leaderboard.songSubName}`);
|
`
|
||||||
embed.setDescription(`
|
**Player:** https://ssr.fascinated.cc/player/${player.id}
|
||||||
**Player:** https://ssr.fascinated.cc/player/${player.id}
|
**Leaderboard:** https://ssr.fascinated.cc/leaderboard/${leaderboard.id}
|
||||||
**Leaderboard:** https://ssr.fascinated.cc/leaderboard/${leaderboard.id}
|
**PP:** ${formatPp(score.pp)}
|
||||||
**PP:** ${formatPp(score.pp)}
|
`
|
||||||
`);
|
)
|
||||||
embed.setThumbnail(leaderboard.coverImage);
|
.setThumbnail(leaderboard.coverImage)
|
||||||
embed.setColor("#00ff00");
|
.setColor("#00ff00")
|
||||||
await hook.send(embed);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,4 +11,5 @@ export const Config = {
|
|||||||
trackedPlayerWebhook: process.env.TRACKED_PLAYERS_WEBHOOK,
|
trackedPlayerWebhook: process.env.TRACKED_PLAYERS_WEBHOOK,
|
||||||
numberOneWebhook: process.env.NUMBER_ONE_WEBHOOK,
|
numberOneWebhook: process.env.NUMBER_ONE_WEBHOOK,
|
||||||
mongoUri: process.env.MONGO_URI,
|
mongoUri: process.env.MONGO_URI,
|
||||||
|
discordBotToken: process.env.DISCORD_BOT_TOKEN,
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -5,22 +5,29 @@ type ScoresaberSocket = {
|
|||||||
/**
|
/**
|
||||||
* Invoked when a general message is received.
|
* Invoked when a general message is received.
|
||||||
*
|
*
|
||||||
* @param message The received message.
|
* @param message the received message.
|
||||||
*/
|
*/
|
||||||
onMessage?: (message: unknown) => void;
|
onMessage?: (message: unknown) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a score message is received.
|
* Invoked when a score message is received.
|
||||||
*
|
*
|
||||||
* @param score The received score data.
|
* @param score the received score data.
|
||||||
*/
|
*/
|
||||||
onScore?: (score: ScoreSaberPlayerScoreToken) => void;
|
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.
|
* 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;
|
let websocket: WebSocket | null = null;
|
||||||
|
|
||||||
function connectWs() {
|
function connectWs() {
|
||||||
@ -35,6 +42,8 @@ export function connectScoreSaberWebSocket({ onMessage, onScore }: ScoresaberSoc
|
|||||||
if (websocket) {
|
if (websocket) {
|
||||||
websocket.close(); // Close the connection on error
|
websocket.close(); // Close the connection on error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDisconnect && onDisconnect(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
websocket.onclose = () => {
|
websocket.onclose = () => {
|
||||||
|
Reference in New Issue
Block a user