diff --git a/CLAUDE.md b/CLAUDE.md index d7da086..482e415 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -172,6 +172,8 @@ Every coding session must begin by reading RUNEWAGER_FUNCTIONALITY_MAP.md and mu All AI agent instruction files in this repo must enforce this baseline workflow: (1) read and understand `RUNEWAGER_FUNCTIONALITY_MAP.md` before changing code, (2) update the map after any code change, (3) run a full verification pass after the map update, and (4) do not consider work complete until docstrings, the map, and agent instruction files are synchronized. +**BOT_KNOWN_COMMANDS (mandatory):** Whenever a `bot.command()` registration is added or removed from `index.js`, the `BOT_KNOWN_COMMANDS` set (defined near the Group Command Guard middleware, ~line 279) **must be updated in the same change set**. This set is the authoritative list of commands Runewager owns; it gates the group command guard so the bot never intercepts commands belonging to other bots in shared groups. Forgetting to update it will either cause Runewager to silently ignore its own new command in groups, or continue intercepting a removed command. No command addition or removal is complete until `BOT_KNOWN_COMMANDS` is synchronized. + - Added operational script `load_tooltips.sh` (root) to populate `/var/www/html/Runewager/data/tooltips.json` with 15 approved HTML tooltips; bot now loads this system file on restart when present. - Added 30 SC manual-review menu hardening with explicit user/admin submenus and admin audit logging to `/var/www/html/Runewager/logs/bonus_admin.log`. diff --git a/RUNEWAGER_FUNCTIONALITY_MAP.md b/RUNEWAGER_FUNCTIONALITY_MAP.md index 493674a..c891295 100644 --- a/RUNEWAGER_FUNCTIONALITY_MAP.md +++ b/RUNEWAGER_FUNCTIONALITY_MAP.md @@ -1,6 +1,6 @@ # RUNEWAGER_FUNCTIONALITY_MAP.md -_Last audited: 2026-02-28 (v3.1 pass)_ +_Last audited: 2026-03-04 (/runewager-audit pass — 0 critical, 0 warnings after fix)_ _Source of truth files: `index.js`, `test/*.test.js`, scripts under `scripts/`, deployment/runtime docs in repo root._ --- @@ -288,10 +288,14 @@ Pending-action timeout handling is enforced in the text input router: each pendi ## 22. Internal Utilities & Shared Modules Repo modules: -- `index.js` main app. +- `index.js` main app (15,148 lines after v3.1 merge). +- `telegramSafe.js` — rate-limited Telegram API wrapper; patches `bot.telegram` on init. +- `rateLimiter.js` — global (~28 req/sec) + per-chat (1 msg/sec) queue enforcer. +- `backend.js` — companion HTTP service on port 3001: autofix webhook, admin bridge, `/health/full`. - `promo-message.js` promo copy helper. -- `scripts/*.sh` deployment/runtime ops (backup, restore, smoke, rollback, diagnostics). +- `scripts/*.sh` deployment/runtime ops (backup, restore, smoke, rollback, diagnostics, redeploy, cpu-guard). - `test/*.test.js` smoke/unit/runtime tests. +- `runewager-endpoint.service` — systemd unit for `backend.js` (separate from `runewager.service`). Key shared helper families in `index.js`: - Markdown escape helpers. @@ -397,11 +401,15 @@ Mandatory rules for any AI agent touching this repo: 2. **After generating or modifying any functionality, the AI must run a follow-up audit to ensure the `RUNEWAGER_FUNCTIONALITY_MAP.md` file is fully updated and accurate. No coding session is complete until the map is updated and verified.** +3. **BOT_KNOWN_COMMANDS (mandatory):** Whenever a `bot.command()` registration is added or removed from `index.js`, the `BOT_KNOWN_COMMANDS` set (defined near the Group Command Guard middleware, ~line 279) **must be updated in the same change set**. This set gates the group command guard — it is the authoritative list of commands this bot owns. Forgetting to update it will cause either silent group-command failure (new command ignored in groups) or continued interception of a removed command. No `bot.command()` add/remove is complete until `BOT_KNOWN_COMMANDS` is synchronized. + - 2026-02-26: Added `load_tooltips.sh` to seed `/var/www/html/Runewager/data/tooltips.json` (15 UTF-8 HTML tooltips) and wired runtime tooltip loading to prefer that system JSON on restart. - 2026-02-26: Added deterministic 30 SC user submenu (`How It Works`, `Check My Eligibility`, `Request My Bonus`, `Check Bonus Status`) and Admin submenu (`View Pending Requests`, `Approve Bonus`, `Deny Bonus`, `View User History`, `Reset Attempts`) with manual-review copy and admin action logging to `/var/www/html/Runewager/logs/bonus_admin.log`. - 2026-02-27: Added QA tester scaffolding (`qa/context/bot_capabilities.json`, `qa/context/repo_info.json`, `qa/state/provider_status.json`, `qa/README_QA.md`), with runtime refresh via `/qa_*` commands and 10-minute provider cooldown reset. - 2026-02-27: Hardened SSHV Run prompt flow so admin text in private DM executes against active SSHV sessions if pending state desynchronizes. - 2026-02-28: v3.1 — added group command guard middleware (`GROUP_PASSTHROUGH_COMMANDS` + `bot.use` interceptor); added `onboardingProgressBar()` and progress header on each onboarding step prompt (auto-deletes after 8s); added one-time onboarding completion card (tracked via `user.onboarding.completionCardShown`); added `🔗 Group Linking` to Admin System Tools keyboard (`admin_sys_group_linking` callback with back-to-system-tools navigation). +- 2026-03-04: Merged main branch additions — `telegramSafe.js` (global rate-limit patch via `telegramSafe.init(bot)`), `rateLimiter.js` (per-chat + global Telegram API rate limiter), `backend.js` (companion HTTP service on port 3001, `runewager-endpoint.service` systemd unit), updated `prod-run.sh`, `scripts/runewager_redeploy.sh`, `scripts/rw_cpu_guard.sh`. +- 2026-03-04: /runewager-audit — 17-phase full audit. Findings: 0 critical, 1 warning resolved (dead `adminKeyboard()` function removed — legacy promo keyboard with no callers). Auto-fix applied. 60/60 tests pass post-fix. - 2026-03-01: Created `docs/` feature documentation system — 15 per-feature `.md` files, central `docs/INDEX.md` with full callback + pending-action cross-reference, and `docs/TODO_FUNCTIONALITY_UPGRADE.md` tracking 14 open upgrade/stale-menu items. Future Claude sessions must consult `docs/INDEX.md` first, then the relevant feature `.md`, before reading `index.js`. - 2026-03-01: Phase implementation — resolved T-01/T-02/T-03/T-15 from TODO list. (1) Walkthrough: `sendWalkthroughStep()` upgraded with `clearOldMenus()`, Back disabled on step 1, Finish on last step, `walk_done` on last step returns to main menu. New doc: `16-walkthrough.md`. (2) Menu stacking: `clearOldMenus()` added to `sendOnboardingReferralPrompt`, `renderSshvConsole`, `renderGroupLinkingTools`, `tips_cmd_edit`, `tips_cmd_remove`. (3) Tooltip view: `tips_cmd_view` selector + `tip_view_{id}` handler with Prev/Next/Edit/Toggle/Delete/Back/AdminMenu — "👁 View Tooltip" button added to dashboard. (4) Broadcast failures: 500-item cap removed; all failures logged via `adminLog()`; `/broadcast_failed` shows chunks of 30 with overflow note; >20% failure rate triggers admin DM warnings. PR comments fixed: `add_tooltip.sh` array validation hardened; `docs/12-group-linking.md` entry-point callback corrected to `admin_sys_group_linking`; merge conflicts (PRs #112-114) resolved keeping SIGTERM→SIGKILL safety improvements. 60/60 tests pass. - 2026-02-28: PR #112 review + audit pass — fixed 10 issues: (R1) `await_tip_import_batch` dedicated pending type with JSON-array router; (R2) `generate_tooltips.sh` command-substitution pollution fixed via `RUNEWAGER_APP` env var; (R3) `add_tooltip.sh` shell-injection fixed via `TOOLTIP_TEXT_ENV`/`TOOLTIP_TMP_FILE` env vars and `<<'EOF'`; (R4) `catchAllCases` test extended with multiline patterns + `CATCH_ALL_CORES` updated; (R5) `extractCommandHandlerNames` test extended with `let`/`var`/no-semicolon fixtures; (R6) typo "auto-deletes 8s" → "auto-deletes after 8s"; (A1) dead `buildGiveawayAnnouncementText(giveaway,remainingStr)` removed; (A2) simplified `buildGiveawayAnnouncementKeyboard` with wrong callback removed; (A3+A4) duplicate `bot.action('admin_cat_system')` and `bot.action('admin_cat_support')` first registrations removed. All 60 tests pass. diff --git a/index.js b/index.js index 265215f..d96c82c 100644 --- a/index.js +++ b/index.js @@ -273,8 +273,30 @@ bot.catch((err, ctx) => { // ========================= /** - * Commands that have explicit group-aware logic and should NOT be intercepted. - * All other commands sent in a group get a "DM redirect" response. + * Complete set of commands this bot handles. + * Any command NOT in this list is silently ignored in groups (could belong to another bot). + */ +const BOT_KNOWN_COMMANDS = new Set([ + 'start', 'menu', 'help', 'commands', 'settings', 'language', + 'link', 'linkrunewager', 'walkthrough', + 'admin', 'sshv', 'qa_on', 'qa_off', 'qa_mode', 'qa_status', + 'a', 'announce', 'giveaway', 'start_giveaway', 'cancel', + 'wager30_admin', 'admin_backup', 'deploy', 'whois', 'bonusstatus', 'refreshuser', + 'health', 'admin_notify', 'deploy_status', 'logs', 'version', 'resolvebug', 'exportbugs', + 'bonus', 'startapp', 'claim_history', 'profile', 'leaderboard', 'leaderboard_weekly', + 'boost_referrals', 'linkaccount', 'status', 'referral', 'on', 'off', + 'bugreport', 'bugreports', 'play', 'signup', 'affiliate', 'discord', + 'promo', 'setpromo', 'join', 'pmapprove', 'pmdeny', + 'tips', 't', 'tp', 'tiplist', 'tipadd', 'tipremove', 'tipedit', 'tiptoggle', 'tiptest', 'tipsettings', + 'testall', 'testgiveaway', + 'gw_pause', 'gw_resume', 'scan_eligibility', 'funnel', + 'broadcast_retry', 'broadcast_failed', 'pick_winner', + 'register_chat', 'verify_bot_setup', 'approve_group', 'unapprove_group', 'list_groups', +]); + +/** + * Commands that have explicit group-aware logic and should NOT be redirected to DM. + * Must be a subset of BOT_KNOWN_COMMANDS. */ const GROUP_PASSTHROUGH_COMMANDS = new Set([ 'link', 'linkrunewager', // handled: group username inline confirm @@ -291,8 +313,19 @@ bot.use(async (ctx, next) => { const text = ctx.message.text; if (!text.startsWith('/')) return next(); - // Extract command name, stripping bot @mention and arguments - const rawCmd = text.slice(1).split(/[\s@]/)[0].toLowerCase(); + // Extract command name and optional @mention (e.g. "/warn@otherbot" → "warn", "otherbot") + const cmdToken = text.slice(1).split(/\s/)[0]; // "warn@otherbot" or "warn" + const atIdx = cmdToken.indexOf('@'); + const rawCmd = (atIdx === -1 ? cmdToken : cmdToken.slice(0, atIdx)).toLowerCase(); + const mentionedBot = atIdx === -1 ? '' : cmdToken.slice(atIdx + 1).toLowerCase(); + + // If the command targets a specific bot that is not us, ignore it entirely + const ourUsername = (ctx.botInfo?.username || '').toLowerCase(); + if (mentionedBot && mentionedBot !== ourUsername) return next(); + + // If we don't own this command, ignore it (prevents responding to other bots' commands) + if (!BOT_KNOWN_COMMANDS.has(rawCmd)) return next(); + if (GROUP_PASSTHROUGH_COMMANDS.has(rawCmd)) return next(); // Redirect all other commands to DM @@ -2944,38 +2977,6 @@ function linkPrefKeyboard() { ]); } -/** - - * adminKeyboard executes its scoped Runewager logic and participates in menu/command or utility flow composition. - - * Parameters: See the function signature for exact argument names and accepted values. - - * Returns: Returns the computed value or a Promise resolving to the operation result; may return void for side-effect handlers. - - * Side effects: May mutate runtime stores, pendingAction state, menu state, persistence files, logs, and callback progression. - - * Validation/safety: Uses existing guard utilities (admin checks, input checks, path checks, cooldown checks) where applicable. - - * Timeouts/fallbacks: Timeout and fallback behavior are controlled by the calling flow and global handler/state machine conventions. - - * Errors: Surfaces user-facing error replies and/or logs when inputs, permissions, or dependencies are invalid. - - * System fit: This function is part of the Runewager command/callback/state orchestration pipeline. - - */ - -function adminKeyboard() { - return Markup.inlineKeyboard([ - [Markup.button.callback('📄 View Promo', 'admin_view')], - [Markup.button.callback('✏️ Edit Promo Code', 'admin_edit_code')], - [Markup.button.callback('💰 Edit SC Amount', 'admin_edit_amount')], - [Markup.button.callback('🔢 Edit Claim Limit', 'admin_edit_limit')], - [Markup.button.callback('⏸ Pause Promo', 'admin_pause')], - [Markup.button.callback('▶️ Unpause Promo', 'admin_unpause')], - [Markup.button.callback('🗑 Remove Promo', 'admin_remove')], - [Markup.button.callback('📢 Push Update to Users', 'admin_broadcast')], - ]); -} /** diff --git a/prod-run.sh b/prod-run.sh index 2c5c6fc..f1d367b 100755 --- a/prod-run.sh +++ b/prod-run.sh @@ -499,11 +499,18 @@ if [[ -n "$PID" ]]; then say "Safe restart of running bot (PID: $PID)" fi if command -v systemctl >/dev/null 2>&1 && [[ -f "$SERVICE_FILE" ]]; then - systemctl restart "${APP_NAME}.service" 2>/dev/null || true + if ! systemctl restart "${APP_NAME}.service" 2>/dev/null; then + warn "systemctl restart failed — falling back to manual kill+nohup" + [[ -n "$PID" ]] && kill "$PID" 2>/dev/null || true + sleep 1 + nohup node "$PROJECT_DIR/index.js" >> "$MAIN_LOG" 2>> "$ERROR_LOG" < /dev/null & + disown || true + say "Bot started via nohup fallback" + fi else [[ -n "$PID" ]] && kill "$PID" 2>/dev/null || true nohup node "$PROJECT_DIR/index.js" >> "$MAIN_LOG" 2>> "$ERROR_LOG" < /dev/null & - disown + disown || true fi # Always refresh PID after restart so step 11 knows the live process sleep 3 @@ -546,17 +553,18 @@ fi is_port_listening "$HEALTH_PORT" && PORT_STATUS="listening" -# Telegram admin notification +# Telegram admin notification (plain text — no parse_mode to avoid Markdown rendering issues +# with unescaped log content or special chars in env values) _ADMIN_IDS="$(read_env_value ADMIN_IDS || true)" _BOT_TOKEN="$(read_env_value TELEGRAM_BOT_TOKEN || true)" if [[ -n "$_BOT_TOKEN" && -n "$_ADMIN_IDS" ]]; then - _REPORT="$(( tail -n 30 "$MAIN_LOG"; tail -n 20 "$ERROR_LOG" ) 2>/dev/null \ - | sed 's/"/\\"/g' | head -c 3500)" + _HEALTH_LABEL="$( [[ "$HEALTH_STATUS" == healthy ]] && echo OK || echo FAILED )" + _MSG="Deploy complete: Health ${_HEALTH_LABEL} | Port: ${HEALTH_PORT} | PID: ${PID:-none} | Systemd: ${SYSTEMD_ACTIVE}" for _AID in ${_ADMIN_IDS//,/ }; do curl -s -X POST "https://api.telegram.org/bot${_BOT_TOKEN}/sendMessage" \ - -d chat_id="$_AID" \ - -d parse_mode="Markdown" \ - -d text="Deploy complete: Health $( [[ "$HEALTH_STATUS" == healthy ]] && echo OK || echo FAILED) Port: $HEALTH_PORT PID: ${PID:-none}" >/dev/null 2>&1 || true + --data-urlencode "chat_id=${_AID}" \ + --data-urlencode "text=${_MSG}" \ + >/dev/null 2>&1 || true done fi diff --git a/todolist.md b/todolist.md index 7120619..f04c36c 100644 --- a/todolist.md +++ b/todolist.md @@ -1,6 +1,6 @@ # Runewager Bot — Improvement Task Board -_Last updated: 2026-02-28 — PR #112 review fixes + codebase audit pass_ +_Last updated: 2026-03-04 — /runewager-audit pass + merge main (backend.js, rateLimiter.js, telegramSafe.js, prod-run.sh, scripts)_ --- @@ -11,6 +11,9 @@ _Last updated: 2026-02-28 — PR #112 review fixes + codebase audit pass_ ## BUGS (confirmed code defects) +- [x] **Dead code: `adminKeyboard()` function** `index.js` + - Fixed: 2026-03-04. Legacy promo keyboard function with no callers (belonged to removed `/admin_menu` command). Deleted entire block (JSDoc + function body). All 60 tests pass. + - [x] **`gw.endsAt` / `endTime` field inconsistency** `index.js` - Fixed: all three occurrences replaced with `gw.endTime`; extend action now calls `resetGiveawayTimer(gw)` to re-arm the timer. - Also fixed `gw.winnersCount` → `gw.maxWinners` in the admin panel display.