Skip to content
Merged
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
27 changes: 8 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ OmegaBot is online

```
.
├── .env-example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug.yml
Expand All @@ -133,24 +132,6 @@ OmegaBot is online
│ │ └── OmegaBot.yml
│ └── pull_request_template.md
├── .husky
│ ├── _
│ │ ├── .gitignore
│ │ ├── applypatch-msg
│ │ ├── commit-msg
│ │ ├── h
│ │ ├── husky.sh
│ │ ├── post-applypatch
│ │ ├── post-checkout
│ │ ├── post-commit
│ │ ├── post-merge
│ │ ├── post-rewrite
│ │ ├── pre-applypatch
│ │ ├── pre-auto-gc
│ │ ├── pre-commit
│ │ ├── pre-merge-commit
│ │ ├── pre-push
│ │ ├── pre-rebase
│ │ └── prepare-commit-msg
│ ├── pre-commit
│ └── pre-push
├── assets
Expand All @@ -173,6 +154,14 @@ OmegaBot is online
│ ├── commands
│ │ ├── changelog
│ │ │ └── changelog.ts
│ │ ├── faq
│ │ │ ├── subcommands
│ │ │ │ ├── _shared.ts
│ │ │ │ ├── add.ts
│ │ │ │ ├── get.ts
│ │ │ │ ├── list.ts
│ │ │ │ └── remove.ts
│ │ │ └── faq.ts
│ │ ├── fun
│ │ │ ├── subcommands
│ │ │ │ ├── chucknorris.ts
Expand Down
140 changes: 140 additions & 0 deletions src/commands/faq/faq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// src/commands/faq/faq.ts

/**
* Base /faq slash command.
*
* Responsibilities of this file:
* - Define the /faq command and its subcommands
* - Handle the Discord interaction lifecycle (defer + reply)
* - Route execution to the correct subcommand handler
*
* Non-goals (by design):
* - No FAQ business logic
* - No file I/O
* - No validation or permissions
*
* All real work is delegated to FAQ services in src/services/faq/.
*/

import {
MessageFlags,
SlashCommandBuilder,
type ChatInputCommandInteraction,
} from "discord.js";
import { logger } from "../../utils/logger.js";

/**
* Slash command definition.
*
* This defines the public Discord API shape only.
* Each subcommand intentionally mirrors a future service operation:
* - add → create FAQ
* - get → retrieve FAQ
* - list → list FAQs
* - remove → delete FAQ
*
* Each subcommand supports an optional ephemeral flag so users
* can avoid spamming public channels.
*/
export const data = new SlashCommandBuilder()
.setName("faq")
.setDescription("FAQ commands")

.addSubcommand((s) =>
s
.setName("add")
.setDescription("Add a FAQ entry")
.addBooleanOption((o) =>
o
.setName("ephemeral")
.setDescription("Only show the result to you")
.setRequired(false),
),
)

.addSubcommand((s) =>
s
.setName("get")
.setDescription("Get a FAQ entry by key")
.addBooleanOption((o) =>
o
.setName("ephemeral")
.setDescription("Only show the result to you")
.setRequired(false),
),
)

.addSubcommand((s) =>
s
.setName("list")
.setDescription("List FAQ entries")
.addBooleanOption((o) =>
o
.setName("ephemeral")
.setDescription("Only show the result to you")
.setRequired(false),
),
)

.addSubcommand((s) =>
s
.setName("remove")
.setDescription("Remove a FAQ entry by key")
.addBooleanOption((o) =>
o
.setName("ephemeral")
.setDescription("Only show the result to you")
.setRequired(false),
),
);

/**
* Command execution entry point.
*
* Pattern used here:
* - Determine subcommand
* - Defer reply immediately (avoid 3s timeout)
* - Route to the correct handler
*
* IMPORTANT:
* This function intentionally contains no business logic.
* Subcommand implementations will live in separate files/services.
*/
export async function execute(interaction: ChatInputCommandInteraction): Promise<void> {
const sub = interaction.options.getSubcommand(true);
const ephemeral = interaction.options.getBoolean("ephemeral") ?? false;

// Parent command owns the interaction lifecycle
await interaction.deferReply(ephemeral ? { flags: MessageFlags.Ephemeral } : undefined);

try {
// Placeholder routing targets.
// Each branch will later call into faqService helpers.
if (sub === "add") {
await interaction.editReply("TODO: /faq add");
return;
}

if (sub === "get") {
await interaction.editReply("TODO: /faq get");
return;
}

if (sub === "list") {
await interaction.editReply("TODO: /faq list");
return;
}

if (sub === "remove") {
await interaction.editReply("TODO: /faq remove");
return;
}

// Safety net in case Discord sends something unexpected
await interaction.editReply("Unknown subcommand.");
} catch (err) {
// Centralized error logging, user sees generic failure
logger.error({ err, sub }, "[faq] subcommand failed");
await interaction.editReply("Something went wrong. Try again in a bit.");
}
}
1 change: 1 addition & 0 deletions src/commands/faq/subcommands/_shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// src/commands/faq/subcommand/_shared.ts
1 change: 1 addition & 0 deletions src/commands/faq/subcommands/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// src/commands/faq/subcommand/add.ts
1 change: 1 addition & 0 deletions src/commands/faq/subcommands/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// src/commands/faq/subcommand/get.ts
1 change: 1 addition & 0 deletions src/commands/faq/subcommands/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// src/commands/faq/subcommand/list.ts
1 change: 1 addition & 0 deletions src/commands/faq/subcommands/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// src/commands/faq/subcommand/remove.ts