Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Bot for the [6b6t Discord Server](https://discord.6b6t.org)
| Command | Description | Permissions |
|---------|-------------|-------------|
| `/ip` | Get the 6b6t server IP addresses | Everyone |
| `/playercount` | Check current players online | Everyone |
| `/playercount` | Check current players online and server uptime | Everyone |
| `/version` | Get the current Minecraft version | Everyone |
| `/shop` | Information about the 6b6t shop | Everyone |
| `/boost` | Information about Discord boosting perks | Everyone |
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.3/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/getuser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const GetUserCommand: Command = {
}

await interaction.editReply(
`**${discordUser.tag}** is linked to **${info.name}**.\n` +
`Discord user **${discordUser.tag}** is linked to Minecraft user **${info.name}**.\n` +
`Top Rank: **${info.topRank}**\n` +
`First Join Year: **${info.firstJoinYear}**`,
);
Expand Down
35 changes: 33 additions & 2 deletions src/commands/playercount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getServerData } from "../utils/helpers";
const PlayerCountCommand: Command = {
data: new SlashCommandBuilder()
.setName("playercount")
.setDescription("See 6b6t's current player count"),
.setDescription("See 6b6t's current player count and uptime"),

async execute(interaction: CommandInteraction) {
const data = await getServerData(config.statusHost);
Expand All @@ -15,10 +15,41 @@ const PlayerCountCommand: Command = {
content: "Failed to get server data",
ephemeral: true,
});
return;
}

let uptimeStr = "";
if (data.uptime.serverStartUnix) {
const diff = Math.floor(Date.now() / 1000 - data.uptime.serverStartUnix);
const days = Math.floor(diff / 86400);
const hours = Math.floor((diff % 86400) / 3600);
const minutes = Math.floor((diff % 3600) / 60);
const seconds = diff % 60;
const parts = [];
if (days > 0) parts.push(`${days}d`);
if (hours > 0) parts.push(`${hours}h`);
if (minutes > 0) parts.push(`${minutes}m`);
if (seconds > 0) parts.push(`${seconds}s`);
uptimeStr = parts.join(" ");
} else if (data.uptime.currentUptimeHours) {
const totalSeconds = Math.floor(data.uptime.currentUptimeHours * 3600);
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const parts = [];
if (days > 0) parts.push(`${days}d`);
if (hours > 0) parts.push(`${hours}h`);
if (minutes > 0) parts.push(`${minutes}m`);
if (seconds > 0) parts.push(`${seconds}s`);
uptimeStr = parts.join(" ");
} else {
await interaction.reply(`The server is currently down.`);
return;
}
Comment on lines +21 to 49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there has got to be a better way to do this rather than to duplicate it twice.

maybe use some library
maybe use a shared function
preferably a library


await interaction.reply(
`There are currently ${data.players.now} players online on 6b6t.`,
`There are currently ${data.players.online} players online on 6b6t. The server has been up for ${uptimeStr}.`,
);
},
};
Expand Down
5 changes: 4 additions & 1 deletion src/deploy-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import config from "./config/config";
import { CommandManager } from "./utils/commandManager";
import "dotenv/config";

const token = process.env.DISCORD_TOKEN;
if (!token) throw new Error("Environment variable DISCORD_TOKEN is not set");

const commandManager = new CommandManager();
await commandManager.loadCommands();

const commands = Array.from(commandManager.getCommands().values()).map(
(command) => command.data.toJSON(),
);

const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN!);
const rest = new REST({ version: "10" }).setToken(token);

(async () => {
try {
Expand Down
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ const client = new Client({
const commandManager = new CommandManager();

async function initializeBot() {
const token = process.env.DISCORD_TOKEN;
if (!token) throw new Error("Environment variable DISCORD_TOKEN is not set");
try {
console.log("Loading commands...");
await commandManager.loadCommands();

const rest = new REST({ version: "10" }).setToken(
process.env.DISCORD_TOKEN!,
);
const rest = new REST({ version: "10" }).setToken(token);
const commands = commandManager.getCommandsJSON();
console.log(
`Registering ${commands.length} commands to guild ${config.guildId}...`,
Expand All @@ -57,7 +57,7 @@ async function initializeBot() {
);

console.log("Initializing bot...");
await client.login(process.env.DISCORD_TOKEN!);
await client.login(token);

console.log(
`Bot initialized and ready to serve in guild: ${config.guildId}`,
Expand Down
8 changes: 7 additions & 1 deletion src/types/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import type {
ButtonInteraction,
ChatInputCommandInteraction,
ModalSubmitInteraction,
SlashCommandBuilder,
SlashCommandOptionsOnlyBuilder,
SlashCommandSubcommandsOnlyBuilder,
} from "discord.js";

export interface Command {
data: any;
data:
| SlashCommandBuilder
| SlashCommandSubcommandsOnlyBuilder
| SlashCommandOptionsOnlyBuilder;
cooldown?: number;
admin?: boolean;
execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
Expand Down
7 changes: 4 additions & 3 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
ChatInputCommandInteraction,
ClientEvents,
SlashCommandBuilder,
} from "discord.js";

Expand All @@ -8,7 +9,7 @@ export interface Command {
execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
}

export interface Event {
name: string;
execute: (...args: any[]) => void;
export interface Event<K extends keyof ClientEvents = keyof ClientEvents> {
name: K;
execute: (...args: ClientEvents[K]) => void;
}
16 changes: 8 additions & 8 deletions src/utils/commandManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,18 @@ export class CommandManager {
userId: string,
cooldownSeconds: number,
): number {
if (!this.cooldowns.has(commandName)) {
this.cooldowns.set(commandName, new Collection());
let timestamps = this.cooldowns.get(commandName);

if (!timestamps) {
timestamps = new Collection<string, number>();
this.cooldowns.set(commandName, timestamps);
}

const now = Date.now();
const timestamps = this.cooldowns.get(commandName)!;
const expirationTime = timestamps.get(userId);

if (timestamps.has(userId)) {
const expirationTime = timestamps.get(userId)!;
if (now < expirationTime) {
return Math.ceil((expirationTime - now) / 1000);
}
if (expirationTime && now < expirationTime) {
return Math.ceil((expirationTime - now) / 1000);
}

timestamps.set(userId, now + cooldownSeconds * 1000);
Expand Down
17 changes: 11 additions & 6 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { RowDataPacket } from "mysql2";
import { getAllUuidDiscordMappings } from "./link/storage";
import { getStatsPool } from "./mysql-client";

const SERVER_API = `https://www.6b6t.org/api`;

export type UserInfo = {
topRank: string;
firstJoinYear: number;
Expand Down Expand Up @@ -151,23 +153,26 @@ export async function botHasRecentMessages(
}

export async function getServerData(host: string): Promise<any> {
const mcUrl = `https://mcapi.us/server/status?ip=${host}&port=25565`;
const versionUrl = `https://www.6b6t.org/api/version`;
const mcUrl = `https://api.mcstatus.io/v2/status/java/${host}`;
const versionUrl = `${SERVER_API}/version`;
const uptimeUrl = `${SERVER_API}/uptime`;

try {
const [mcRes, versionRes] = await Promise.all([
const [mcRes, versionRes, uptimeRes] = await Promise.all([
fetch(mcUrl),
fetch(versionUrl),
fetch(uptimeUrl),
]);

if (!mcRes.ok || !versionRes.ok) return null;
if (!mcRes.ok || !versionRes.ok || !uptimeRes.ok) return null;

const [mcData, versionData] = await Promise.all([
const [mcData, versionData, uptimeData] = await Promise.all([
mcRes.json(),
versionRes.json(),
uptimeRes.json(),
]);

return { ...mcData, ...versionData };
return { ...mcData, ...versionData, uptime: uptimeData.statistics };
} catch (error) {
console.error("Error fetching server data:", error);
return null;
Expand Down