Overview
ScoreSaber Reloaded includes a Discord bot that provides administrative commands, monitoring channels, and automated notifications for various events. The bot enables server admins to manage the system and monitor its health in real-time.
What the Discord Bot Provides
- Administrative Commands: Slash commands for system management
- Monitoring Channels: Real-time logs for various system events
- Score Feeds: Automated notifications for notable scores
- Status Tracking: Live presence showing tracked score count
- Error Logging: Centralized error reporting
Bot Setup
The Discord bot is initialized during backend startup:
export async function initDiscordBot() {
if (!env.DISCORD_BOT_TOKEN) {
Logger.warn("Discord bot token not found, skipping initialization");
return;
}
Logger.info("Initializing discord bot...");
await client.login(env.DISCORD_BOT_TOKEN);
async function updatePresence() {
client.user?.setPresence({
status: "online",
activities: [
{
name: `${formatNumberWithCommas(await ScoreSaberScoreModel.estimatedDocumentCount())} Scores!`,
type: ActivityType.Watching,
url: "https://ssr.fascinated.cc",
},
],
});
}
updatePresence();
setInterval(updatePresence, TimeUnit.toMillis(TimeUnit.Minute, 1));
}
From: backend/src/bot/bot.ts:65-94
Configuration
Required Environment Variables
DISCORD_BOT_TOKEN=your_bot_token_here
Channel Configuration
The bot uses dedicated channels for different purposes:
export const DiscordChannels = {
TRACKED_PLAYER_LOGS: env.DISCORD_CHANNEL_TRACKED_PLAYER_LOGS,
PLAYER_SCORE_REFRESH_LOGS: env.DISCORD_CHANNEL_PLAYER_SCORE_REFRESH_LOGS,
RANKED_BATCH_LOGS: env.DISCORD_CHANNEL_RANKED_BATCH_LOGS,
NUMBER_ONE_FEED: env.DISCORD_CHANNEL_NUMBER_ONE_FEED,
TOP_50_SCORES_FEED: env.DISCORD_CHANNEL_TOP_50_SCORES_FEED,
SCORE_FLOODGATE_FEED: env.DISCORD_CHANNEL_SCORE_FLOODGATE_FEED,
MEDAL_SCORES_FEED: env.DISCORD_CHANNEL_MEDAL_SCORES_FEED,
BACKEND_LOGS: env.DISCORD_CHANNEL_BACKEND_LOGS,
BEATSAVER_LOGS: env.DISCORD_CHANNEL_BEATSAVER_LOGS,
};
From: backend/src/bot/bot.ts:23-33
Administrative Commands
Refresh Ranked Leaderboards
Force a refresh of all ranked leaderboards:
@Slash({
description: "Force refresh ranked leaderboards",
name: "refresh-ranked-leaderboards",
})
async forceRankedLeaderboardsRefresh(interaction: CommandInteraction) {
await interaction.deferReply();
try {
await LeaderboardRankingService.refreshRankedLeaderboards();
await interaction.editReply({
content: "Ranked leaderboards refreshed",
});
} catch (error) {
await interaction.editReply({
content: error instanceof Error ? error.message : "An unknown error occurred",
});
}
}
From: backend/src/bot/command/refresh-ranked-leaderboards.ts:11-27
Usage: /refresh-ranked-leaderboards
Available Commands
The bot includes several admin commands (all require owner permissions):
/refresh-ranked-leaderboards - Refresh ranked map data
/fetch-missing-player-scores - Fetch scores for tracked players
/force-track-player-statistics - Force track player statistics
/refresh-medal-scores - Refresh medal score data
/update-player-medals - Update player medal counts
From: backend/src/bot/bot.ts:17-21
Monitoring Channels
Tracked Player Logs
Logs when players are added or removed from tracking:
TRACKED_PLAYER_LOGS: env.DISCORD_CHANNEL_TRACKED_PLAYER_LOGS
Player Score Refresh Logs
Tracks score refresh operations:
PLAYER_SCORE_REFRESH_LOGS: env.DISCORD_CHANNEL_PLAYER_SCORE_REFRESH_LOGS
Backend Logs
General backend errors and important events:
BACKEND_LOGS: env.DISCORD_CHANNEL_BACKEND_LOGS
BeatSaver Logs
BeatSaver API interactions and errors:
BEATSAVER_LOGS: env.DISCORD_CHANNEL_BEATSAVER_LOGS
From: backend/src/bot/bot.ts:23-33
Score Feeds
Number One Feed
Notifies when players achieve rank 1 on leaderboards:
NUMBER_ONE_FEED: env.DISCORD_CHANNEL_NUMBER_ONE_FEED
Top 50 Scores Feed
Tracks all top 50 global scores:
TOP_50_SCORES_FEED: env.DISCORD_CHANNEL_TOP_50_SCORES_FEED
Score Floodgate Feed
High-volume feed for all tracked scores:
SCORE_FLOODGATE_FEED: env.DISCORD_CHANNEL_SCORE_FLOODGATE_FEED
Medal Scores Feed
Notifies when players earn medal-worthy scores:
MEDAL_SCORES_FEED: env.DISCORD_CHANNEL_MEDAL_SCORES_FEED
From: backend/src/bot/bot.ts:23-33
Sending Messages
Send Embed
Send rich embed messages to channels:
export async function sendEmbedToChannel(
channelId: (typeof DiscordChannels)[keyof typeof DiscordChannels],
embed: EmbedBuilder,
components: ActionRowData<MessageActionRowComponentBuilder>[] = []
) {
if (!channelId) {
return;
}
try {
const channel = await client.channels.fetch(channelId);
if (channel != undefined && channel.isSendable()) {
return await channel.send({ embeds: [embed], components });
}
} catch (error) {
Logger.error(`Failed to send message to channel ${channelId}:`, error);
}
return undefined;
}
From: backend/src/bot/bot.ts:102-119
Send Text Message
Send plain text messages:
export async function sendMessageToChannel(
channelId: (typeof DiscordChannels)[keyof typeof DiscordChannels],
message: string
) {
if (!channelId) {
return;
}
try {
const channel = await client.channels.fetch(channelId);
if (channel != undefined && channel.isSendable()) {
return await channel.send(message);
}
} catch (error) {
Logger.error(`Failed to send message to channel ${channelId}:`, error);
}
return undefined;
}
From: backend/src/bot/bot.ts:127-144
Send File
Send file attachments:
export async function sendFile(
channelId: (typeof DiscordChannels)[keyof typeof DiscordChannels],
filename: string,
content: string,
message?: string
) {
if (!channelId) {
return;
}
try {
const channel = await client.channels.fetch(channelId);
if (channel != undefined && channel.isSendable()) {
return await channel.send({
content: message,
files: [
new AttachmentBuilder(Buffer.from(content), {
name: filename,
}),
],
});
}
} catch (error) {
Logger.error(`Error sending file to channel ${channelId}:`, error);
}
}
From: backend/src/bot/bot.ts:153-178
Bot Presence
The bot’s status displays the total number of tracked scores:
client.user?.setPresence({
status: "online",
activities: [
{
name: `${formatNumberWithCommas(await ScoreSaberScoreModel.estimatedDocumentCount())} Scores!`,
type: ActivityType.Watching,
url: "https://ssr.fascinated.cc",
},
],
});
The presence updates every minute to show the current score count.
From: backend/src/bot/bot.ts:76-85
Error Handling
The bot includes error handling for interaction failures:
client.on("interactionCreate", interaction => {
try {
client.executeInteraction(interaction);
} catch (error) {
Logger.error("Error executing interaction:", error);
if (interaction.isCommand() || interaction.isContextMenuCommand()) {
interaction.reply({
content: "An error occurred while processing your request. Please try again later.",
flags: MessageFlags.Ephemeral,
});
}
}
});
From: backend/src/bot/bot.ts:51-63
Permissions
All administrative commands are protected by permission guards:
@Discord()
@Guard(IsGuildUser(OwnerOnly))
class RefreshRankedLeaderboards {
// Command implementation
}
From: backend/src/bot/command/refresh-ranked-leaderboards.ts:7-10
Only users with the OwnerOnly permission can execute administrative commands. Configure this in your Discord server settings.
Use Cases
System Monitoring
Administrators can monitor system health through dedicated channels:
- Track when players are added/removed
- Monitor score refresh operations
- Receive alerts for errors
- Track BeatSaver API issues
Manual Interventions
When needed, admins can manually trigger:
- Leaderboard refreshes
- Player score fetches
- Medal updates
- Statistics tracking
Score feed channels can be used for:
- Celebrating top performances
- Tracking rank 1 achievements
- Monitoring medal-worthy scores
Bot Client Configuration
The bot is configured with necessary intents:
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers,
],
silent: true,
});
From: backend/src/bot/bot.ts:35-43
Ready Event
When the bot connects, it initializes application commands:
client.once("clientReady", async () => {
await client.initApplicationCommands();
Logger.info("Discord bot ready!");
});
From: backend/src/bot/bot.ts:45-49
Best Practices
Set up separate Discord channels for different log types to keep monitoring organized and prevent channel spam.
The bot token should be kept secure and never committed to version control. Use environment variables for configuration.
High-volume feeds like SCORE_FLOODGATE_FEED can generate many messages. Consider using a dedicated channel with slower mode enabled.