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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ GITHUB_REPO=
# This must be a numeric channel ID, not a channel name.
# Enable Developer Mode in Discord → right-click channel → Copy ID
GITHUB_ANNOUNCE_CHANNEL_ID=
GITHUB_ASSIGNEE_ANNOUNCE_CHANNEL_ID=

# ============================================================
# GitHub PR Polling Interval
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ OmegaBot is online
│ │ │ ├── githubApi.ts
│ │ │ ├── githubClient.ts
│ │ │ ├── githubErrorMessage.ts
│ │ │ ├── issueAssigneePoller.ts
│ │ │ ├── lastSeenStore.ts
│ │ │ ├── prFormatter.ts
│ │ │ ├── prPoller.ts
Expand Down
8 changes: 8 additions & 0 deletions docs/setup-env.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ OPENAI_API_KEY=your-key
GITHUB_TOKEN=your-github-pat
GITHUB_OWNER=org-or-user
GITHUB_REPO=repo-name

# Channel for PR creation / PR updates
GITHUB_ANNOUNCE_CHANNEL_ID=channel-id

# Channel for issue + PR assignee changes and closures
# (can be the same as above if you want)
GITHUB_ASSIGNEE_CHANNEL_ID=channel-id

# Polling interval (milliseconds)
GITHUB_POLL_INTERVAL_MS=60000
```

Expand Down
65 changes: 47 additions & 18 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Client, GatewayIntentBits } from "discord.js";
import { loadCommands, type CommandClient } from "./services/discord/commandLoader.js";
import { handleInteraction } from "./services/discord/interactionHandler.js";
import { pollPullRequestsOnce } from "./services/github/prPoller.js";
import { pollIssueAssigneesOnce } from "./services/github/issueAssigneePoller.js";
import { onGuildMemberAdd } from "./services/welcome/welcomeHandler.js";
import { env } from "./config/env.js";
import { logger } from "./utils/logger.js";
Expand Down Expand Up @@ -57,34 +58,49 @@ client.on("guildMemberAdd", async (member) => {
});

/**
* Optional GitHub PR polling.
* Enabled only when all required env vars are present.
* Optional GitHub polling.
* Each stream is enabled only when all required env vars are present.
*/
const githubPollingEnabled =
!!env.githubToken &&
!!env.githubOwner &&
!!env.githubRepo &&
!!env.githubAnnounceChannelId;
const githubPrPollingEnabled = env.githubPrPollingEnabled;
const githubAssigneePollingEnabled = env.githubAssigneePollingEnabled;

/**
* Log once when the bot is ready.
*/
client.once("clientReady", () => {
logger.info("OmegaBot is online");

if (githubPollingEnabled) {
if (githubPrPollingEnabled) {
logger.info(
{
owner: env.githubOwner,
repo: env.githubRepo,
channelId: env.githubAnnounceChannelId,
channelId: env.githubPrAnnounceChannelId,
intervalMs: env.githubPollIntervalMs,
},
"GitHub PR polling enabled",
"GitHub PR polling enabled (new PRs)",
);
} else {
logger.info("GitHub PR polling disabled");
}

if (githubAssigneePollingEnabled) {
logger.info(
{
owner: env.githubOwner,
repo: env.githubRepo,
channelId: env.githubAssigneeAnnounceChannelId,
intervalMs: env.githubPollIntervalMs,
},
"GitHub assignee polling enabled (assignee changes)",
);
} else {
logger.info("GitHub assignee polling disabled");
}

if (!githubPrPollingEnabled && !githubAssigneePollingEnabled) {
logger.info("GitHub polling disabled");
}
});

/**
Expand All @@ -93,15 +109,28 @@ client.once("clientReady", () => {
void client.login(env.token);

/**
* Schedule GitHub PR polling (if enabled).
* Schedule GitHub polling (if enabled).
*/
if (githubPollingEnabled) {
if (githubPrPollingEnabled || githubAssigneePollingEnabled) {
setInterval(() => {
void pollPullRequestsOnce({
client,
owner: env.githubOwner!,
repo: env.githubRepo!,
announceChannelId: env.githubAnnounceChannelId!,
});
// 1) PR creation polling (new PR detection)
if (githubPrPollingEnabled) {
void pollPullRequestsOnce({
client,
owner: env.githubOwner!,
repo: env.githubRepo!,
announceChannelId: env.githubPrAnnounceChannelId!,
});
}

// 2) Assignee change polling (issues and PRs)
if (githubAssigneePollingEnabled) {
void pollIssueAssigneesOnce({
client,
owner: env.githubOwner!,
repo: env.githubRepo!,
announceChannelId: env.githubAssigneeAnnounceChannelId!,
});
}
}, env.githubPollIntervalMs);
}
57 changes: 49 additions & 8 deletions src/config/env.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// src/config/env.ts

import dotenv from "dotenv";
dotenv.config({ path: ".env" });

Expand Down Expand Up @@ -74,16 +76,24 @@ type SummaryMode = "local" | "llm";
* Required only when GitHub-backed features are enabled.
*
* - GITHUB_OWNER
* Default repository owner for polling PRs (optional).
* Default repository owner for polling (optional).
*
* - GITHUB_REPO
* Default repository name for polling PRs (optional).
* Default repository name for polling (optional).
*
* - GITHUB_ANNOUNCE_CHANNEL_ID
* Discord channel ID where PR announcements are posted (optional).
* Legacy: single channel where GitHub announcements are posted (optional).
*
* - GITHUB_PR_ANNOUNCE_CHANNEL_ID
* NEW: channel where PR creation announcements are posted (optional).
* Falls back to GITHUB_ANNOUNCE_CHANNEL_ID if unset.
*
* - GITHUB_ASSIGNEE_ANNOUNCE_CHANNEL_ID
* NEW: channel where assignee change announcements are posted (optional).
* Falls back to GITHUB_ANNOUNCE_CHANNEL_ID if unset.
*
* - GITHUB_POLL_INTERVAL_MS
* Polling interval for PR announcements.
* Polling interval for GitHub polling.
* Defaults to 60 seconds if not provided.
*
* All GitHub-related fields are OPTIONAL so the bot can run
Expand All @@ -96,6 +106,15 @@ if (summaryMode === "llm" && !process.env.OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY is required when SUMMARY_MODE=llm");
}

const legacyGithubAnnounceChannelId = process.env.GITHUB_ANNOUNCE_CHANNEL_ID ?? null;

// New split channels (fall back to legacy)
const githubPrAnnounceChannelId =
process.env.GITHUB_PR_ANNOUNCE_CHANNEL_ID ?? legacyGithubAnnounceChannelId;

const githubAssigneeAnnounceChannelId =
process.env.GITHUB_ASSIGNEE_ANNOUNCE_CHANNEL_ID ?? legacyGithubAnnounceChannelId;

export const env = {
/* ---------------------------------------------------------------- */
/* Discord (required) */
Expand Down Expand Up @@ -128,18 +147,22 @@ export const env = {
// Auth token for GitHub REST API (optional)
githubToken: process.env.GITHUB_TOKEN ?? null,

// Default repo configuration for PR polling (optional)
// Default repo configuration for polling (optional)
githubOwner: process.env.GITHUB_OWNER ?? null,
githubRepo: process.env.GITHUB_REPO ?? null,

// Where PR announcements should be posted (optional)
githubAnnounceChannelId: process.env.GITHUB_ANNOUNCE_CHANNEL_ID ?? null,
// Legacy: Where GitHub announcements should be posted (optional)
githubAnnounceChannelId: legacyGithubAnnounceChannelId,

// NEW: split announcement channels (optional; fall back to legacy)
githubPrAnnounceChannelId,
githubAssigneeAnnounceChannelId,

// Polling interval (ms). Defaults to 60s.
githubPollIntervalMs: envInt("GITHUB_POLL_INTERVAL_MS", 60_000),

/**
* Feature gate:
* Legacy feature gate:
*
* GitHub announcement polling is enabled ONLY when all required
* configuration is present. This prevents the bot from attempting
Expand All @@ -151,6 +174,24 @@ export const env = {
Boolean(process.env.GITHUB_REPO) &&
Boolean(process.env.GITHUB_ANNOUNCE_CHANNEL_ID),

/**
* NEW feature gates:
*
* Separate enablement for each GitHub polling stream.
* These fall back to the legacy channel var automatically.
*/
githubPrPollingEnabled:
Boolean(process.env.GITHUB_TOKEN) &&
Boolean(process.env.GITHUB_OWNER) &&
Boolean(process.env.GITHUB_REPO) &&
Boolean(githubPrAnnounceChannelId),

githubAssigneePollingEnabled:
Boolean(process.env.GITHUB_TOKEN) &&
Boolean(process.env.GITHUB_OWNER) &&
Boolean(process.env.GITHUB_REPO) &&
Boolean(githubAssigneeAnnounceChannelId),

/**
* Helper for GitHub-only code paths.
*
Expand Down
Loading