Skip to content

feat: bot status/custom activity#210

Merged
BillChirico merged 2 commits intomainfrom
feat/issue-40
Mar 2, 2026
Merged

feat: bot status/custom activity#210
BillChirico merged 2 commits intomainfrom
feat/issue-40

Conversation

@BillChirico
Copy link
Collaborator

Summary

Implements configurable bot status and activity messages.

Features

  • Configurable status — online, idle, dnd, invisible
  • Custom activity text with variable interpolation:
    • {memberCount} — total members across all guilds
    • {guildCount} — number of guilds
    • {botName} — bot username
  • Rotating activities — cycles through a list on a configurable interval (default 30s, stops if only one activity)
  • Hot-reload — config changes apply immediately without restart
  • API accessiblebotStatus added to config allowlist for dashboard control

Config Shape

{
  "botStatus": {
    "enabled": true,
    "status": "online",
    "activityType": "Playing",
    "activities": [
      "with {memberCount} members",
      "in {guildCount} servers",
      "your assistant | Volvox"
    ],
    "rotateIntervalMs": 30000
  }
}

Tests

41 unit tests covering:

  • Variable interpolation (all 3 vars, null guards, missing client)
  • Status/activityType resolution with valid/invalid/missing values
  • Activities list with filtering/fallback
  • Presence application (setPresence calls, error handling)
  • Rotation start/stop/wrap-around
  • Reload with cached client

Closes #40

- Add botStatus module with configurable status (online/idle/dnd/invisible)
- Support custom activity text with variable interpolation ({memberCount}, {guildCount}, {botName})
- Rotating activities with configurable interval (default 30s)
- Hot-reload on config change via config-listeners
- Wire into startup/shutdown in index.js
- Add botStatus to config allowlist for API access
- Add default config in config.json
- 41 unit tests covering all exported functions

Closes #40
Copilot AI review requested due to automatic review settings March 2, 2026 04:28
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Warning

Rate limit exceeded

@BillChirico has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between dd343b4 and fa596b2.

📒 Files selected for processing (3)
  • config.json
  • src/api/utils/configAllowlist.js
  • src/index.js
📝 Walkthrough

Walkthrough

This PR introduces a bot status management system with configurable Discord presence and activity rotation, integrated into the bot's lifecycle. It also restructures the configuration file with modularized top-level sections for various features.

Changes

Cohort / File(s) Summary
Configuration Restructuring
config.json, src/api/utils/configAllowlist.js
Consolidated configuration into modular top-level objects (ai, triage, welcome, botStatus, etc.); expanded SAFE_CONFIG_KEYS allowlist to include 'botStatus' and 'quietMode'.
Bot Status Module
src/modules/botStatus.js
New module implementing configurable Discord bot presence with activity rotation, variable interpolation (memberCount, guildCount, botName), and lifecycle controls (start/stop/reload).
Lifecycle Integration
src/index.js, src/config-listeners.js
Wired bot status lifecycle into startup/shutdown flows; added hot-reload listeners for botStatus.* configuration changes to trigger reloadBotStatus.
Test Suite
tests/modules/botStatus.test.js
Comprehensive test coverage for interpolation, presence config resolution, activity rotation, and reload semantics.

Possibly related PRs

  • PR #67: Directly builds on the config-change event system by registering botStatus.* listeners for hot-reload integration.
  • PR #18: Both modify src/index.js startup and shutdown lifecycle to integrate new runtime modules.
  • PR #15: Both introduce or modify welcome configuration settings in config.json.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Config restructuring with 14 new top-level sections (memory, starboard, permissions, help, announce, snippet, poll, etc.) is out of scope for bot status feature. Revert config.json to only include botStatus and botStatus-related changes. Move the comprehensive config restructuring to a separate refactoring PR.
Linked Issues check ❓ Inconclusive PR closes #40 (umbrella issue for community engagement features), but the bot status implementation is only tangentially related to most listed sub-features. Clarify whether bot status is a primary objective of #40 or if it should be tracked under a separate issue. If unrelated, consider removing the #40 closure or creating a dedicated issue for bot status.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title 'feat: bot status/custom activity' accurately and concisely describes the main change—implementing configurable bot presence with custom activities.
Description check ✅ Passed Description clearly explains the feature with summary, features, configuration shape, and test coverage. It is directly related to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/issue-40

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new botStatus module to manage the Discord bot’s presence (status + activity text), including variable interpolation and optional rotation, with hot-reload via config listeners and API/dashboard writability.

Changes:

  • Introduces src/modules/botStatus.js with interpolation, config resolution, presence application, rotation, and reload support.
  • Wires the module into startup/shutdown and config hot-reload (src/index.js, src/config-listeners.js).
  • Adds unit tests and updates config allowlist + default config.json (tests/modules/botStatus.test.js, src/api/utils/configAllowlist.js, config.json).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/modules/botStatus.test.js New Vitest suite covering interpolation/config resolution/presence/rotation/reload behaviors.
src/modules/botStatus.js New bot presence module implementing configurable status/activity + rotation + reload.
src/index.js Starts bot status after login and stops it during graceful shutdown.
src/config-listeners.js Adds config change listeners to trigger reloadBotStatus() on botStatus updates.
src/api/utils/configAllowlist.js Allows API/dashboard writes to botStatus (and also adds quietMode).
config.json Adds default botStatus section (and also adds quietMode) and reformats file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@greptile-apps
Copy link

greptile-apps bot commented Mar 2, 2026

Greptile Summary

This PR adds configurable bot presence with rotating activities and variable interpolation ({memberCount}, {guildCount}, {botName}). The implementation is clean and well-tested with 41 unit tests.

Key changes:

  • New src/modules/botStatus.js module with hot-reload support
  • API-accessible via dashboard (botStatus added to allowlist)
  • Proper integration with startup/shutdown lifecycle
  • Comprehensive test coverage

Critical issue:

  • src/index.js accidentally removed stopScheduler() and stopGithubFeed() calls from graceful shutdown — this will prevent the announcement scheduler and GitHub feed from stopping properly on shutdown

Confidence Score: 2/5

  • Cannot merge safely due to critical shutdown logic bug
  • The botStatus feature itself is well-implemented with excellent test coverage, but src/index.js has a critical bug where stopScheduler() and stopGithubFeed() were accidentally removed during merge, which will cause improper shutdown behavior and potential resource leaks
  • Pay close attention to src/index.js — restore missing shutdown calls before merging

Important Files Changed

Filename Overview
src/modules/botStatus.js New module for bot presence management with rotation, variable interpolation, and hot-reload support. Clean implementation with comprehensive error handling.
src/index.js Added bot status startup/shutdown integration but accidentally removed stopScheduler() and stopGithubFeed() calls during merge — critical bug.
tests/modules/botStatus.test.js Comprehensive test suite with 41 tests covering all functions, edge cases, error handling, and rotation logic.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Start[Bot Login] --> Init[startBotStatus called]
    Init --> CheckEnabled{botStatus.enabled?}
    CheckEnabled -->|No| Skip[Skip - module disabled]
    CheckEnabled -->|Yes| LoadConfig[Load config: status, activityType, activities]
    LoadConfig --> SetFirst[Apply first activity immediately]
    SetFirst --> CountActivities{activities.length > 1?}
    CountActivities -->|No| SingleActivity[Single activity - no rotation]
    CountActivities -->|Yes| StartInterval[Start rotation interval]
    StartInterval --> RotateLoop[Every rotateIntervalMs]
    RotateLoop --> Increment[Increment activity index]
    Increment --> WrapAround[Wrap around using modulo]
    WrapAround --> Interpolate[Interpolate variables]
    Interpolate --> SetPresence[client.user.setPresence]
    SetPresence --> RotateLoop
    
    ConfigChange[Config Change Detected] --> Reload[reloadBotStatus]
    Reload --> StopInterval[Stop existing interval]
    StopInterval --> Init
    
    Shutdown[Bot Shutdown] --> StopBot[stopBotStatus]
    StopBot --> ClearInterval[Clear rotation interval]
    ClearInterval --> End[Bot stopped]
Loading

Last reviewed commit: fa596b2

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config.json`:
- Around line 2-303: The README's Configuration section is missing docs for
several config keys; add configuration tables (option name, type, default,
description) for the following sections: memory, logging, help, announce,
snippet, poll, showcase, github, tldr, afk, engagement, challenges, review,
reminders, botStatus, and quietMode; follow the exact format used for the
existing documented sections (ai, triage, welcome, moderation, auditLog,
starboard, reputation, permissions), include each section's nested options
(e.g., memory.maxContextMemories, logging.fileOutput, logging.database.*,
botStatus.activities,
quietMode.allowedRoles/defaultDurationMinutes/maxDurationMinutes, github.feed.*,
engagement.activityBadges, etc.), and ensure examples/defaults match the values
present in the config.json diff.

In `@src/index.js`:
- Around line 478-479: The status rotation is started too early—call
startBotStatus(client) only after the bot is fully ready so applyPresence()
reads populated caches; move the startBotStatus invocation into the ClientReady
event handler (i.e., after client.once('ready' / 'clientReady', ...) or
equivalent) so it runs after guilds are cached, ensuring applyPresence() uses
correct memberCount/guildCount from client.guilds.cache rather than stale zeros.

In `@src/modules/botStatus.js`:
- Around line 97-102: getActivities currently returns an empty array when
cfg.activities exists but all entries are blank, causing applyPresence() to skip
setting presence; update getActivities so that after filtering blank/whitespace
entries it checks the filtered list and returns it only if length > 0, otherwise
return the fallback ['with Discord']; reference the getActivities function and
ensure the "filter" result is used to decide between returning the filtered
array or the default.

In `@tests/modules/botStatus.test.js`:
- Around line 175-193: Add a regression test that ensures getActivities treats
whitespace-only strings as empty: create a test named like "returns default
activity when activities are whitespace-only" that calls getActivities({
activities: ['   ', '\t'] }) (or similar whitespace-only values) and assert the
result equals ['with Discord']; locate the existing tests around getActivities
and mirror their structure for consistency.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcf04e2 and dd343b4.

📒 Files selected for processing (6)
  • config.json
  • src/api/utils/configAllowlist.js
  • src/config-listeners.js
  • src/index.js
  • src/modules/botStatus.js
  • tests/modules/botStatus.test.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
  • GitHub Check: Docker Build Validation
🧰 Additional context used
📓 Path-based instructions (5)
**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

**/*.js: Use ESM modules — use import/export, never require()
Always use node: protocol for Node.js builtins (e.g. import { readFileSync } from 'node:fs')
Always use semicolons in JavaScript code
Use single quotes for strings (enforced by Biome)
Use 2-space indentation (enforced by Biome)

Files:

  • src/config-listeners.js
  • tests/modules/botStatus.test.js
  • src/api/utils/configAllowlist.js
  • src/modules/botStatus.js
  • src/index.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: Always use Winston logger — import { info, warn, error } from '../logger.js'. NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — replace any existing console calls with Winston equivalents
Pass structured metadata to Winston logs: info('Message processed', { userId, channelId })

Files:

  • src/config-listeners.js
  • src/api/utils/configAllowlist.js
  • src/modules/botStatus.js
  • src/index.js
tests/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

All new code must include tests. Test coverage must maintain an 80% threshold on statements, branches, functions, and lines. Run pnpm test before every commit

Files:

  • tests/modules/botStatus.test.js
src/modules/*.js

📄 CodeRabbit inference engine (AGENTS.md)

Per-request modules (AI, spam, moderation) should call getConfig(guildId) on every invocation for automatic config changes. Stateful resources should use onConfigChange listeners for reactive updates

Files:

  • src/modules/botStatus.js
config.json

📄 CodeRabbit inference engine (AGENTS.md)

When adding a new config section or key, document it in README.md's config reference section

Files:

  • config.json
🧠 Learnings (2)
📚 Learning: 2026-03-01T06:03:34.399Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to src/modules/*.js : Per-request modules (AI, spam, moderation) should call `getConfig(guildId)` on every invocation for automatic config changes. Stateful resources should use `onConfigChange` listeners for reactive updates

Applied to files:

  • src/config-listeners.js
  • src/modules/botStatus.js
📚 Learning: 2026-03-01T06:03:34.399Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Tempban scheduler runs on a 60s interval. Started during startup in `index.js`, stopped during graceful shutdown. Catches up on missed unbans after restart

Applied to files:

  • src/index.js
🧬 Code graph analysis (4)
src/config-listeners.js (2)
src/modules/config.js (1)
  • onConfigChange (353-355)
src/modules/botStatus.js (1)
  • reloadBotStatus (213-220)
tests/modules/botStatus.test.js (1)
src/modules/botStatus.js (14)
  • guildCount (65-65)
  • interpolateActivity (61-72)
  • resolvePresenceConfig (80-88)
  • resolvePresenceConfig (116-116)
  • status (81-81)
  • activityType (84-85)
  • getActivities (97-103)
  • stopBotStatus (198-205)
  • cfg (112-112)
  • cfg (150-150)
  • cfg (165-165)
  • applyPresence (110-142)
  • startBotStatus (162-193)
  • reloadBotStatus (213-220)
src/modules/botStatus.js (1)
src/logger.js (2)
  • info (231-233)
  • warn (238-240)
src/index.js (1)
src/modules/botStatus.js (2)
  • stopBotStatus (198-205)
  • startBotStatus (162-193)
🪛 GitHub Check: Test (Vitest Coverage)
src/modules/botStatus.js

[failure] 34-34: tests/index.test.js > index.js > should handle autocomplete errors gracefully
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1


[failure] 34-34: tests/index.test.js > index.js > should handle autocomplete interactions
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1


[failure] 34-34: tests/index.test.js > index.js > should load state from disk when state file exists
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1


[failure] 34-34: tests/index.test.js > index.js > should not call markUnavailable when checkMem0Health succeeds
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1


[failure] 34-34: tests/index.test.js > index.js > should call markUnavailable when checkMem0Health rejects
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1


[failure] 34-34: tests/index.test.js > index.js > should warn and skip db init when DATABASE_URL is not set
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1


[failure] 34-34: tests/index.test.js > index.js > should initialize startup with database when DATABASE_URL is set
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1


[failure] 34-34: tests/index.test.js > index.js > should configure allowedMentions to only parse users (Issue #61)
Error: [vitest] No "ActivityType" export is defined on the "discord.js" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("discord.js"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/modules/botStatus.js:34:12
❯ src/config-listeners.js:12:1

🔇 Additional comments (3)
src/api/utils/configAllowlist.js (1)

30-31: Allowlist expansion is consistent with the new config surface.

Adding botStatus and quietMode to SAFE_CONFIG_KEYS matches the new top-level config sections.

src/config-listeners.js (1)

107-121: Global bot-status hot-reload wiring looks solid.

The global-scope guard plus reloadBotStatus() callback is the right reactive pattern for this stateful module.

Based on learnings: "Stateful resources should use onConfigChange listeners for reactive updates".

src/modules/botStatus.js (1)

28-40: No action required. The review comment's premise is incorrect.

tests/modules/botStatus.test.js does not mock discord.js, so it imports the real module from node_modules, which exports ActivityType in v14.25.1. With globals: false in vitest.config.js, mocks are file-scoped; the incomplete mock in tests/index.test.js does not affect botStatus.test.js. The tests pass successfully in CI.

Likely an incorrect or invalid review comment.

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 2, 2026
@BillChirico BillChirico merged commit 18ab747 into main Mar 2, 2026
6 of 9 checks passed
@BillChirico BillChirico deleted the feat/issue-40 branch March 2, 2026 11:41
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

6 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

stopTempbanScheduler();
stopScheduler();
stopGithubFeed();
stopBotStatus();
Copy link

Choose a reason for hiding this comment

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

stopScheduler() and stopGithubFeed() calls are missing. These were accidentally removed during merge. Add them back:

Suggested change
stopBotStatus();
stopBotStatus();
stopScheduler();
stopGithubFeed();
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/index.js
Line: 280

Comment:
`stopScheduler()` and `stopGithubFeed()` calls are missing. These were accidentally removed during merge. Add them back:

```suggestion
  stopBotStatus();
  stopScheduler();
  stopGithubFeed();
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🎯 Community Engagement Features

2 participants