Skip to content
Open
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
22 changes: 22 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,28 @@
"flushIntervalMs": 5000
}
},
"botStatus": {
"enabled": true,
"status": "online",
"rotation": {
"enabled": true,
"intervalMinutes": 5,
"messages": [
{
"type": "Watching",
"text": "{guildCount} servers"
},
{
"type": "Listening",
"text": "to {memberCount} members"
},
{
"type": "Playing",
"text": "with /help"
}
]
}
},
"permissions": {
"enabled": true,
"adminRoleIds": [],
Expand Down
27 changes: 27 additions & 0 deletions src/api/utils/configValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,33 @@ export const CONFIG_SCHEMA = {
retentionDays: { type: 'number' },
},
},
botStatus: {
type: 'object',
properties: {
enabled: { type: 'boolean' },
status: { type: 'string', enum: ['online', 'idle', 'dnd', 'invisible'] },
activityType: {
type: 'string',
enum: ['Playing', 'Watching', 'Listening', 'Competing', 'Streaming', 'Custom'],
},
activities: { type: 'array', items: { type: 'string' } },
rotateIntervalMs: { type: 'number' },
rotation: {
type: 'object',
properties: {
enabled: { type: 'boolean' },
intervalMinutes: { type: 'number' },
messages: {
type: 'array',
items: {
type: 'object',
required: ['text'],
Comment on lines +199 to +200
},
Comment on lines +196 to +201
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden rotation.messages[] schema validation.

Line 196–201 only enforces key presence (text), not field shape. Invalid payloads (e.g., non-string text, unsupported type) can pass API validation and silently degrade later.

Proposed schema + validator tightening
       rotation: {
         type: 'object',
         properties: {
           enabled: { type: 'boolean' },
           intervalMinutes: { type: 'number' },
           messages: {
             type: 'array',
             items: {
               type: 'object',
+              properties: {
+                type: {
+                  type: 'string',
+                  enum: ['Playing', 'Watching', 'Listening', 'Competing', 'Streaming', 'Custom'],
+                },
+                text: { type: 'string' },
+              },
               required: ['text'],
             },
           },
         },
       },
         } else if (schema.items.type === 'object') {
           if (typeof item !== 'object' || item === null || Array.isArray(item)) {
             errors.push(
               `${path}[${i}]: expected object, got ${Array.isArray(item) ? 'array' : item === null ? 'null' : typeof item}`,
             );
-          } else if (schema.items.required) {
-            for (const key of schema.items.required) {
-              if (!(key in item)) {
-                errors.push(`${path}[${i}]: missing required key "${key}"`);
-              }
-            }
+          } else {
+            if (schema.items.required) {
+              for (const key of schema.items.required) {
+                if (!(key in item)) {
+                  errors.push(`${path}[${i}]: missing required key "${key}"`);
+                }
+              }
+            }
+            if (schema.items.properties) {
+              errors.push(...validateValue(item, schema.items, `${path}[${i}]`));
+            }
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/utils/configValidation.js` around lines 196 - 201, The
rotation.messages[] JSON schema currently only requires the presence of a text
key; tighten it by making the message item schema validate shapes: in the schema
for rotation.messages.items (the object validated today), change required/items
to explicitly define properties: set properties.text: { type: 'string',
minLength: 1 }, optionally add properties.type: { type: 'string', enum:
['text','markdown','html'] } (or your allowed types), set additionalProperties:
false, and keep required: ['text']; this ensures non-string text or unexpected
keys are rejected by the validator.

},
},
},
},
},
reminders: {
type: 'object',
properties: {
Expand Down
4 changes: 4 additions & 0 deletions src/config-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export function registerConfigListeners({ dbPool, config }) {
'botStatus.activityType',
'botStatus.activities',
'botStatus.rotateIntervalMs',
'botStatus.rotation',
'botStatus.rotation.enabled',
'botStatus.rotation.intervalMinutes',
'botStatus.rotation.messages',
]) {
onConfigChange(key, (_newValue, _oldValue, _path, guildId) => {
// Bot presence is global — ignore per-guild overrides here
Expand Down
6 changes: 5 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ import {
startConversationCleanup,
stopConversationCleanup,
} from './modules/ai.js';
import { startBotStatus, stopBotStatus } from './modules/botStatus.js';
import { getConfig, loadConfig } from './modules/config.js';

import { registerEventHandlers } from './modules/events.js';
import { startGithubFeed, stopGithubFeed } from './modules/githubFeed.js';
import { checkMem0Health, markUnavailable } from './modules/memory.js';
Expand Down Expand Up @@ -292,6 +292,7 @@ async function gracefulShutdown(signal) {
stopWarningExpiryScheduler();
stopScheduler();
stopGithubFeed();
stopBotStatus();

// 1.5. Stop API server (drain in-flight HTTP requests before closing DB)
try {
Expand Down Expand Up @@ -482,6 +483,9 @@ async function startup() {
// Start triage module (per-channel message classification + response)
await startTriage(client, config, healthMonitor);

// Start configurable bot presence rotation
startBotStatus(client);
Comment on lines +487 to +488
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "startBotStatus\\(|client\\.login\\(" src/index.js -C2
rg -n "if \\(!cfg\\?\\.enabled \\|\\| !client\\?\\.user\\) return" src/modules/botStatus.js -C2

Repository: VolvoxLLC/volvox-bot

Length of output: 556


🏁 Script executed:

# Get the full startBotStatus function implementation
rg -n "function startBotStatus|export.*startBotStatus|const startBotStatus" src/ -A 20

# Get the applyPresence function
rg -n "function applyPresence|const applyPresence|async.*applyPresence" src/ -A 15

# Check for ClientReady event handlers
rg -n "ClientReady|client\.on.*ready" src/index.js -C 3

Repository: VolvoxLLC/volvox-bot

Length of output: 2562


🏁 Script executed:

# Get more context on the ClientReady handler - what happens there
rg -n "ClientReady" src/index.js -A 30

# Check the rotation interval setup
rg -n "rotateInterval|setInterval" src/modules/botStatus.js -B 2 -A 8

Repository: VolvoxLLC/volvox-bot

Length of output: 3222


🏁 Script executed:

# Check if there are any event handlers on ready that might depend on startBotStatus
rg -n "startBotStatus|on.*ready|ClientReady" src/index.js -B 2 -A 2

# Verify the rotate function and see if it has the same client.user check
rg -n "function rotate|const rotate" src/modules/botStatus.js -A 10

Repository: VolvoxLLC/volvox-bot

Length of output: 1032


Move startBotStatus(client) to after client.login(token) to ensure initial presence applies immediately.

Currently at line 487, startBotStatus() calls applyPresence(client) before the client is logged in, but applyPresence() returns early when !client?.user. This delays the first visible status update until the next rotation interval tick (5+ minutes by default), instead of appearing immediately on startup.

Proposed fix
-  // Start configurable bot presence rotation
-  startBotStatus(client);
-
   // Start tempban scheduler for automatic unbans (DB required)
   if (dbPool) {
     startTempbanScheduler(client);
     startWarningExpiryScheduler();
     startScheduler(client);
     startGithubFeed(client);
   }

   // Load commands and login
   await loadCommands();
   await client.login(token);
+
+  // Start configurable bot presence rotation after client is authenticated
+  startBotStatus(client);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.js` around lines 487 - 488, The call to startBotStatus(client) runs
before the bot is logged in so applyPresence(client) returns early and the
initial presence is delayed; move the startBotStatus(client) invocation to after
the client.login(token) (or explicitly call applyPresence(client) immediately
after client.login) so the initial presence is applied right away; update any
code in src/index.js where startBotStatus or applyPresence is invoked to run
post-login (referencing startBotStatus, applyPresence, and client.login(token)).


// Start tempban scheduler for automatic unbans (DB required)
if (dbPool) {
startTempbanScheduler(client);
Expand Down
Loading
Loading