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
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ OmegaBot is a modular Discord bot designed to support development projects with
- Modular slash-command system (auto-loaded from `dist/commands`)
- Centralized interaction routing with safe error handling
- Structured logging (pino)
- Welcome / onboarding system triggered on member join (`guildMemberAdd`)
- Per-guild configuration system backed by persistent storage and admin slash commands
- Auto-role assignment on member join (optional via DISCORD_AUTO_ROLE_ID)
- Welcome and onboarding flows triggered on member join (`guildMemberAdd`)
- Optional auto-role assignment for new members (`DISCORD_AUTO_ROLE_ID`)
- GitHub integration: issue and PR lookups plus polling-based announcements
- Check if Github is working
- New PR announcements
- Issue and PR assignee change announcements
- Issue and PR closed announcements
- Configuration and feature gating via environment variables (optional features run only when configured)
- Per-guild configuration backed by persistent storage and admin slash commands

### Core commands

Expand Down Expand Up @@ -188,6 +194,7 @@ OmegaBot is online
│ └── omegabot.png
├── data
│ ├── faqs.json
│ ├── github-assignees.json
│ ├── guild-config.json
│ ├── last-seen.json
│ └── timezones.json
Expand Down Expand Up @@ -225,7 +232,8 @@ OmegaBot is online
│ │ │ └── ping.ts
│ │ ├── github
│ │ │ ├── gh.ts
│ │ │ └── pr.ts
│ │ │ ├── pr.ts
│ │ │ └── status.ts
│ │ ├── help
│ │ │ ├── help.ts
│ │ │ └── helpText.ts
Expand Down Expand Up @@ -308,6 +316,7 @@ OmegaBot is online
├── README.md
├── tsconfig.json
└── vitest.config.ts

```

</details>
Expand Down
3 changes: 2 additions & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
- `/gh issue` — Fetch a GitHub issue by number
- `/gh issues` — List open GitHub issues
- `/gh prs` — List open pull requests
- `/pr` — Fetch a single pull request by number
- `/gh status` — Show GitHub integration status (configuration, polling, and channels)
- `/pr` — Fetch a single pull request by number (legacy shortcut)

## Timezone

Expand Down
59 changes: 53 additions & 6 deletions src/commands/github/gh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SlashCommandBuilder,
type ChatInputCommandInteraction,
} from "discord.js";
import { env } from "../../config/env.js";
import {
getIssue,
listIssues,
Expand All @@ -21,6 +22,18 @@ export const data = new SlashCommandBuilder()
.setName("gh")
.setDescription("GitHub helpers")

.addSubcommand((s) =>
s
.setName("status")
.setDescription("Show GitHub integration status for this bot")
.addStringOption((o) =>
o.setName("owner").setDescription("Org/user (optional; defaults to env)"),
)
.addStringOption((o) =>
o.setName("repo").setDescription("Repo (optional; defaults to env)"),
),
)

.addSubcommand((s) =>
s
.setName("issue")
Expand Down Expand Up @@ -73,10 +86,46 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise

await interaction.deferReply({ flags: MessageFlags.Ephemeral });

const owner = interaction.options.getString("owner", true);
const repo = interaction.options.getString("repo", true);

try {
if (sub === "status") {
const owner =
interaction.options.getString("owner") ?? env.githubOwner ?? "(unset)";
const repo = interaction.options.getString("repo") ?? env.githubRepo ?? "(unset)";

const lines: string[] = [];
lines.push("**GitHub Status**");
lines.push("");

// Config (never print secrets)
lines.push(`Token: ${env.githubToken ? "configured" : "missing"}`);
lines.push(`Default repo: ${owner}/${repo}`);
lines.push(`Poll interval: ${env.githubPollIntervalMs}ms`);
lines.push("");

// Polling streams
lines.push("**Polling streams**");
lines.push(
`- PR announcements: ${env.githubPrPollingEnabled ? "enabled" : "disabled"}`,
);
lines.push(` - Channel: ${env.githubPrAnnounceChannelId ?? "(unset)"}`);
lines.push(
`- Assignee activity: ${env.githubAssigneePollingEnabled ? "enabled" : "disabled"}`,
);
lines.push(` - Channel: ${env.githubAssigneeAnnounceChannelId ?? "(unset)"}`);

lines.push("");
lines.push(
"_Tip: If polling is disabled, set GITHUB_TOKEN + GITHUB_OWNER + GITHUB_REPO + the channel env var(s)._",
);

await interaction.editReply(lines.join("\n"));
return;
}

// The remaining subcommands require explicit owner/repo options
const owner = interaction.options.getString("owner", true);
const repo = interaction.options.getString("repo", true);

if (sub === "issue") {
const number = interaction.options.getInteger("number", true);
const issue = await getIssue(owner, repo, number);
Expand Down Expand Up @@ -138,16 +187,14 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise
}

await interaction.editReply("Unknown subcommand.");
return;
} catch (err) {
const msg = getGitHubUserMessage(err);
if (msg) {
await interaction.editReply(msg);
return;
}

logger.warn({ err, owner, repo, sub }, "[gh] GitHub request failed");
logger.warn({ err, sub }, "[gh] GitHub request failed");
await interaction.editReply("GitHub request failed. Please try again in a bit.");
return;
}
}
51 changes: 51 additions & 0 deletions src/commands/github/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// src/commands/github/status.ts

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

/**
* /github status command
*
* Reports current GitHub-related configuration and which GitHub pollers are enabled.
* This command does not call the GitHub API.
*/
export const data = new SlashCommandBuilder()
.setName("status")
.setDescription("Show GitHub integration status for this bot");

export async function execute(interaction: ChatInputCommandInteraction): Promise<void> {
try {
const lines: string[] = [];

// High level config (do not print secrets)
lines.push("**GitHub Status**");
lines.push(`Repo: ${env.githubOwner ?? "(unset)"}/${env.githubRepo ?? "(unset)"}`);
lines.push(`Token: ${env.githubToken ? "present" : "missing"}`);
lines.push(`Poll interval: ${env.githubPollIntervalMs} ms`);
lines.push("");

// PR polling
lines.push("**PR announcements**");
lines.push(`Enabled: ${env.githubPrPollingEnabled ? "yes" : "no"}`);
lines.push(`Channel: ${env.githubPrAnnounceChannelId ?? "(unset)"}`);
lines.push("");

// Assignee/issue activity polling
lines.push("**Assignee + issue activity**");
lines.push(`Enabled: ${env.githubAssigneePollingEnabled ? "yes" : "no"}`);
lines.push(`Channel: ${env.githubAssigneeAnnounceChannelId ?? "(unset)"}`);

await interaction.reply({ content: lines.join("\n"), ephemeral: true });
} catch (err) {
logger.error({ err }, "[/status] failed");

// Best-effort reply (avoid throwing twice)
const msg = "Something went wrong while building GitHub status.";
if (interaction.deferred || interaction.replied) {
await interaction.editReply({ content: msg });
} else {
await interaction.reply({ content: msg, ephemeral: true });
}
}
}