Skip to content

Commit a57860d

Browse files
committed
feat(commands): add getPlayerConnectionHistory command for ownerOnly access
1 parent 1abf638 commit a57860d

7 files changed

Lines changed: 98 additions & 12 deletions

File tree

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ DISCORD_TOKEN=
33
DISCORD_CLIENT_ID=
44
SRCDS_LOG_ADDRESS=
55

6+
# Optional, but required for Owner-only commands
7+
DISCORD_OWNER_GUILD_ID=
8+
69
# Required for interacting with Oracle Cloud
710
OCI_CONFIG_FILE=
811

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { SlashCommandBuilder } from "discord.js";
2+
3+
export const getPlayerConnectionHistoryDefinition = new SlashCommandBuilder()
4+
.setName('get-player-connection-history')
5+
.setDescription('Gets the connection history for a specific player (Owner only)')
6+
.setDefaultMemberPermissions(0) // Hides from everyone by default
7+
.addStringOption(option =>
8+
option.setName('player_steam_id3')
9+
.setDescription('Player Steam ID3 (e.g., U:1:12345678)')
10+
.setRequired(true)
11+
);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ChatInputCommandInteraction, MessageFlags } from "discord.js";
2+
3+
export function getPlayerConnectionHistoryHandlerFactory() {
4+
return async (interaction: ChatInputCommandInteraction) => {
5+
const playerSteamId3 = interaction.options.getString('player_steam_id3', true);
6+
7+
// Dummy implementation - just echo back for now
8+
await interaction.reply({
9+
content: `🔍 **Player Connection History**\n\nSearching for player: \`${playerSteamId3}\`\n\n_This is a dummy implementation. Full functionality coming soon!_`,
10+
flags: MessageFlags.Ephemeral
11+
});
12+
};
13+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { getPlayerConnectionHistoryDefinition } from "./definition";
2+
export { getPlayerConnectionHistoryHandlerFactory } from "./handler";

packages/entrypoints/src/commands/index.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import { ConfigManager } from "@tf2qs/core";
1414
import { setUserDataDefinition, setUserDataHandlerFactory } from "./SetUserData";
1515
import { SetUserData } from "@tf2qs/core";
1616
import { statusCommandDefinition, createStatusCommandHandlerFactory } from "./Status";
17+
import { getPlayerConnectionHistoryDefinition, getPlayerConnectionHistoryHandlerFactory } from "./GetPlayerConnectionHistory";
18+
19+
export type Command = {
20+
name: string;
21+
definition: SlashCommandOptionsOnlyBuilder;
22+
handler: (interaction: ChatInputCommandInteraction) => Promise<void>;
23+
ownerOnly?: boolean;
24+
}
1725

1826
export type CommandDependencies = {
1927
createServerForUser: CreateServerForUser;
@@ -26,7 +34,7 @@ export type CommandDependencies = {
2634
getUserServers: GetUserServers;
2735
}
2836

29-
export function createCommands(dependencies: CommandDependencies) {
37+
export function createCommands(dependencies: CommandDependencies): Record<string, Command> {
3038
return {
3139
createServer: {
3240
name: "create-server",
@@ -48,7 +56,13 @@ export function createCommands(dependencies: CommandDependencies) {
4856
definition: setUserDataDefinition,
4957
handler: setUserDataHandlerFactory({
5058
setUserData: dependencies.setUserData,
51-
})
59+
}),
60+
},
61+
getPlayerConnectionHistory: {
62+
name: "get-player-connection-history",
63+
definition: getPlayerConnectionHistoryDefinition,
64+
handler: getPlayerConnectionHistoryHandlerFactory(),
65+
ownerOnly: true,
5266
},
5367
status: {
5468
name: "status",
@@ -82,9 +96,5 @@ export function createCommands(dependencies: CommandDependencies) {
8296
// createCreditsPurchaseOrder: dependencies.createCreditsPurchaseOrder,
8397
// })
8498
// }
85-
} satisfies Record<string, {
86-
name: string;
87-
definition: SlashCommandOptionsOnlyBuilder,
88-
handler: (interaction: ChatInputCommandInteraction) => Promise<void>;
89-
}>
99+
};
90100
}

packages/entrypoints/src/discordBot.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,35 @@ describe("startDiscordBot", () => {
7676
beforeAll(async () => {
7777
process.env.DISCORD_TOKEN = 'valid_token';
7878
process.env.DISCORD_CLIENT_ID = 'valid_client_id';
79+
process.env.DISCORD_OWNER_GUILD_ID = 'test_guild_123';
7980
await startDiscordBot();
8081
})
8182

8283
it("should initialize KnexConnectionManager", () => {
8384
expect(KnexConnectionManager.initialize).toHaveBeenCalled();
8485
})
8586
it("should register all commands globally with Discord API", async () => {
86-
const expectedCommands = Object.values(discordCommands)
87+
const regularCommands = Object.values(discordCommands)
88+
.filter(cmd => !cmd.ownerOnly)
8789
.map(command => command.definition.toJSON());
8890

8991
expect(rest.put).toHaveBeenCalledWith(
9092
Routes.applicationCommands('valid_client_id'),
9193
expect.objectContaining({
92-
body: expectedCommands
94+
body: regularCommands
95+
})
96+
);
97+
})
98+
99+
it("should register owner-only commands to the specific guild", async () => {
100+
const ownerOnlyCommands = Object.values(discordCommands)
101+
.filter(cmd => cmd.ownerOnly)
102+
.map(command => command.definition.toJSON());
103+
104+
expect(rest.put).toHaveBeenCalledWith(
105+
Routes.applicationGuildCommands('valid_client_id', 'test_guild_123'),
106+
expect.objectContaining({
107+
body: ownerOnlyCommands
93108
})
94109
);
95110
})

packages/entrypoints/src/discordBot.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export async function startDiscordBot() {
6060
// Define the bot token
6161
const token = process.env.DISCORD_TOKEN!;
6262
const clientId = process.env.DISCORD_CLIENT_ID!;
63+
const ownerGuildId = process.env.DISCORD_OWNER_GUILD_ID;
6364

6465
// Check if token and clientId are available
6566
if (!token) {
@@ -279,7 +280,16 @@ export async function startDiscordBot() {
279280
})
280281

281282
// Slash commands
282-
const commands = Object.values(discordCommands).map(command => command.definition)
283+
const allCommands = Object.values(discordCommands);
284+
285+
// Separate regular commands from owner-only commands
286+
const regularCommands = allCommands
287+
.filter(cmd => !cmd.ownerOnly)
288+
.map(cmd => cmd.definition);
289+
290+
const ownerOnlyCommands = allCommands
291+
.filter(cmd => cmd.ownerOnly)
292+
.map(cmd => cmd.definition);
283293

284294
// Register commands with Discord API
285295
const rest = new REST({ version: '10' }).setToken(token);
@@ -290,11 +300,33 @@ export async function startDiscordBot() {
290300
body: 'Started refreshing application (/) commands.'
291301
});
292302

293-
// Register commands globally (this applies to all guilds)
303+
// Register regular commands globally (this applies to all guilds)
294304
await rest.put(Routes.applicationCommands(clientId), {
295-
body: commands.map(command => command.toJSON()), // Convert CommandBuilder to JSON
305+
body: regularCommands.map(command => command.toJSON()), // Convert CommandBuilder to JSON
296306
});
297307

308+
logger.emit({
309+
severityText: 'INFO',
310+
body: `Successfully registered ${regularCommands.length} global command(s).`
311+
});
312+
313+
// Register owner-only commands to the specific guild if configured
314+
if (ownerGuildId && ownerOnlyCommands.length > 0) {
315+
await rest.put(Routes.applicationGuildCommands(clientId, ownerGuildId), {
316+
body: ownerOnlyCommands.map(command => command.toJSON()),
317+
});
318+
319+
logger.emit({
320+
severityText: 'INFO',
321+
body: `Successfully registered ${ownerOnlyCommands.length} owner-only command(s) to guild ${ownerGuildId}.`
322+
});
323+
} else if (ownerOnlyCommands.length > 0) {
324+
logger.emit({
325+
severityText: 'WARN',
326+
body: 'DISCORD_OWNER_GUILD_ID not set. Owner-only commands will not be registered.'
327+
});
328+
}
329+
298330
logger.emit({
299331
severityText: 'INFO',
300332
body: 'Successfully reloaded application (/) commands.'

0 commit comments

Comments
 (0)