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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ data/admin-events.log
data/*.json
data/backups/**
data/tooltips.json

# Legacy root-level runtime data files (pre-data/ directory structure)
users.json
giveaways.json
promo.json
env.json
analytics*.json
121 changes: 121 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Changelog

All notable changes to Runewager Bot are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

---

## [3.0.0] — 2026-02-27

### Overview
Full production upgrade: menu lifecycle overhaul, auto-vanish TTL system, paginated giveaway
panels, referral sub-menu, admin health dashboard, broadcast preview, and 23 Block 1 security
and correctness fixes.

### Added — Menu Lifecycle & UX
- `mainMenuSentAt` / `adminMenuSentAt` timestamps on user schema for stale menu detection
- `clearStaleMenuIds()` function: clears orphaned menu IDs on bot restart (24h threshold)
- `replyMenu()` TTL support via `ttlMs` extra param — auto-deletes message after timeout
- `menuStaleRecoveries` / `pendingActionsTimedOut` metric counters
- User giveaway list now paginated (5/page, 2-min auto-vanish) via `sendUserGiveawaysPage()`
- Admin giveaway panel now paginated (5/page) via updated `activeGiveawaysKeyboard(page)`
- Dedicated Referral sub-menu (`pmenu_referral`) with share deep-link button
- Admin stats keyboard shows active window indicator; all time windows include Refresh button
- Admin health panel (`admin_cmd_health`) fully inline: memory, error rate, active users (24h), persist age, giveaway count
- Broadcast builder: `👁 Preview` button sends preview to admin DM before mass send
- `admin_gw_page_N` callback for admin giveaway pagination navigation
- `user_giveaways_page_N` callback for user giveaway pagination navigation

### Added — Helpers & Utilities
- `computeParticipantWeight(pUser)` — centralized giveaway weight helper (eliminates duplication)
- `getRealGiveaways()` — centralized test-mode filter (eliminates 6× inline `.filter(!testMode)`)
- `isNewUserPromoEligible(user)` — centralized new-user promo eligibility check
- `SAFE_URL_SCHEMES` Set and `UNSAFE_URL_SCHEMES` regex for URL validation

### Fixed — Security
- `getStartAppLink()`: route interpolated via `encodeURIComponent()` with `/^[\w/-]{1,64}$/` validation
- `referralShareHTML()`: `${code}` wrapped with `escapeHtml()` to prevent HTML injection
- `unwrapTelegramUrl()`: safe fallback returns `''` on parse failure; scheme whitelist enforced; handles `www.t.me` / `telegram.me`
- `getDiscordLink()`: returns `null` (not hardcoded fallback) when DISCORD env vars not configured
- SSHV command execution: rejects null bytes, backticks, `$(`, `${` before exec
- `deploy_status` and `logs` commands use `execFile()` instead of `exec()` (no shell spawn)
- Real admin ID removed from `.env.example` and `deploy.yml` (prior release)

### Fixed — Logic Bugs
- `evaluatePendingActionTimeout()`: boundary changed from `<` to `>=`; `createdAt` never mutated; NaN guard added; increments `pendingActionsTimedOut`
- `getPlayLink()`: restored legacy `user.playMode` fallback for schema migration safety
- Weighted giveaway winner pool: splice after pick guarantees termination (no infinite loop)
- `replaceCallbackPanel()` fallback: untracked `ctx.reply()` now stores message ID in user
- `sendHelpMenu()`: was using bare `ctx.reply()` (stacked panels); now uses `replyMenu()`
- Admin mode toggle double-fire: removed duplicate `refreshAdminMenuHeader()` calls
- `settings_toggle_playmode`: now also refreshes persistent user menu after toggle

### Fixed — Shell Scripts
- `load_tooltips.sh` **fully rewritten**:
- Atomic write (temp file → JSON validate → mv)
- No auto-push (requires explicit `--push` flag)
- No auto-pull (requires explicit `--pull` flag)
- Parameterized `REPO_DIR` (no hardcoded `/var/www/html/Runewager`)
- HTML in tooltips sanitized (only safe tags allowed)
- All git commands guarded with `|| warn` or `|| true`
- `--dry-run` mode for safe inspection
- Permission check before write

### Fixed — Tests
- `smoke.test.js`: `readdirSync` call now wrapped in try/catch
- `smoke.test.js`: `isCatchAllRegexPattern()` expanded to recognize `(.*)`, `(.+)`, `(?:.*)`, `^(?:.*)$`, `(.+)?`
- `smoke.test.js`: `extractCommandHandlerNames()` now supports `let`/`var` declarations and no-semicolon forms

### Infrastructure
- `/metrics` endpoint: added `runewager_menu_stale_recoveries` and `runewager_pending_actions_timed_out` counters; added `runewager_uptime_seconds`
- `pre-deploy-checks.sh`: Gate 3b added — verifies `getUser`, `replyMenu`, `clearOldMenus`, `sendPersistentUserMenu`, `sendPersistentAdminMenu` symbols present in index.js
- `package.json`: Version bumped to `3.0.0`

### Startup Validation (new)
- Warns (non-fatal) on missing `ADMIN_IDS`, `TELEGRAM_CHANNEL_ID`, `TELEGRAM_GROUP_ID` env vars

---

## [2.1.0] — 2026-02-23

### Added
- `bot.catch()` global Telegraf error handler
- `uncaughtException` / `unhandledRejection` process handlers
- `LOG_LEVEL` env-var filtering in `logEvent()` (debug/info/warn/error)
- Error rate alerting: >10 errors in 5 minutes → Telegram admin notification
- Admin events persisted to `data/admin-events.log` (NDJSON, append-only)
- `diskFreeMB` in `/health` endpoint output
- `clearStaleMenuIds()` (v3.0 backport)

### Fixed
- `gw.endsAt` → `gw.endTime` in 3 places; extend action calls `resetGiveawayTimer()`
- `gw.winnersCount` → `gw.maxWinners` in admin panel display
- `escapeMarkdownFull()` added with complete MarkdownV2 escaping
- `broadcastFailedUsers` capped at 500 entries
- `promoStore.logs` capped at 200 entries
- State persist interval reduced 60s → 15s
- Corrupt runtime-state.json: differentiated parse error vs missing file
- `self-diagnose.sh` wrong directory (`current/` subdir)
- `rollback.sh` rewritten as git-based rollback
- `pkill` scoped to `Runewager/index.js` in `deploy.yml`
- SSH `StrictHostKeyChecking=no` → `yes` in `deploy.yml`
- Real admin ID removed from `.env.example` and `deploy.yml`

### Tests
- 33 new unit tests added covering bonus state, lock checks, markdown escaping, username normalization, onboarding logic, promo logic

---

## [2.0.0] — 2026-01-15

### Added
- Durable runtime state persisted to `data/runtime-state.json`
- `/health` and `/metrics` HTTP endpoints
- CI workflow with syntax check, tests, npm audit
- Atomic per-user mutation queue (`runUserMutation`)
- Bonus status state machine with transition guards
- Smart button deduplication tracker with 10-min TTL
- Admin Telegram notifications on deploy events
- Weekly disk-protect cron job
- Git-based rollback script
- Structured JSON logging with `logEvent()`
53 changes: 53 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,56 @@ All AI agent instruction files in this repo must enforce this baseline workflow:

- 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`.

---

### 2026-02-27 — v3.0 Upgrade Session

**Scope:** Full production v3.0 deployment upgrade. `index.js` (14,368 lines after changes), `load_tooltips.sh`, `test/smoke.test.js`, `scripts/pre-deploy-checks.sh`, `package.json`, `CHANGELOG.md`, `todolist.md`.

**Menu Lifecycle Overhaul:**
- `mainMenuSentAt` / `adminMenuSentAt` timestamps added to user schema for stale detection
- `clearStaleMenuIds()` runs at bot startup, nulls out transient and 24h-stale menu IDs
- `replyMenu()` extended with `ttlMs` parameter — auto-deletes messages after timeout via `setTimeout`
- `sendHelpMenu()` now uses `replyMenu()` (was bare `ctx.reply()` — caused panel stacking)
- `replaceCallbackPanel()` fallback tracks sent message ID in user state
- Admin mode toggle double-fire fixed (removed duplicate `refreshAdminMenuHeader()`)
- `settings_toggle_playmode` now refreshes persistent user menu after toggle

**Feature Upgrades:**
- User giveaway list: `sendUserGiveawaysPage()` with 5/page pagination + 2-min auto-vanish TTL
- Admin giveaway panel: `activeGiveawaysKeyboard(page)` with 5/page pagination + `admin_gw_page_N` callbacks
- Referral sub-menu: `pmenu_referral` callback with share deep-link button
- Admin stats: active window indicator + Refresh button in `adminStatsKeyboard(activeWindow)`
- Admin health panel: fully inline with memory, error rate, active users (24h), persist age, giveaway count
- Broadcast builder: `👁 Preview` button sends preview to admin DM before mass send

**Block 1 Security & Logic Fixes (23 items):**
- `getStartAppLink()`: `encodeURIComponent()` + regex whitelist for route
- `referralShareHTML()`: `escapeHtml()` wraps code parameter
- `unwrapTelegramUrl()`: returns `''` on failure; scheme whitelist; handles `www.t.me`/`telegram.me`
- `getDiscordLink()`: returns `null` when DISCORD env vars not configured (no hardcoded fallback)
- `evaluatePendingActionTimeout()`: strict `>=` boundary; NaN guard; never mutates `createdAt`
- `getPlayLink()`: restored legacy `user.playMode` fallback
- Weighted winner pool: splice after pick, guaranteed termination
- `deploy_status` / `logs` commands: `execFile()` instead of `exec()` (no shell spawn)
- SSHV command validation: rejects null bytes, backticks, `$(`, `${`
- Startup warnings: non-fatal alerts for missing `ADMIN_IDS`, `TELEGRAM_CHANNEL_ID`, `TELEGRAM_GROUP_ID`
- Centralized helpers: `computeParticipantWeight()`, `getRealGiveaways()`, `isNewUserPromoEligible()`

**Shell Script (`load_tooltips.sh` — full rewrite):**
- Atomic write (temp→validate→mv); `--push`/`--pull` flags required for git ops
- Parameterized `REPO_DIR`; HTML-safe tooltips; `--dry-run` mode; permission checks

**Tests (`test/smoke.test.js`):**
- `readdirSync` call wrapped in try/catch
- `isCatchAllRegexPattern()` expanded to detect `(.*)`, `(.+)`, `(?:.*)`, `^(?:.*)$`, `(.+)?`
- `extractCommandHandlerNames()` supports `let`/`var` and no-semicolon forms

**Infrastructure:**
- `/metrics`: added `runewager_menu_stale_recoveries`, `runewager_pending_actions_timed_out`, `runewager_uptime_seconds`
- `pre-deploy-checks.sh`: Gate 3b — verifies 5 menu system symbols present in index.js
- `CHANGELOG.md`: created (v3.0.0 + v2.1.0 + v2.0.0 history)
- `package.json`: version bumped `2.1.0` → `3.0.0`

**Final Status:** `node --check index.js` clean, all 60 tests pass.
11 changes: 10 additions & 1 deletion deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,17 @@ NPM_CMD="npm install --omit=dev"
NPM_OUT=""
if NPM_OUT=$(${NPM_CMD} 2>&1); then
say "Dependencies installed."
mkdir -p "$PROJECT_DIR/data" "$PROJECT_DIR/logs"
mkdir -p "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$PROJECT_DIR/logs"
touch "$PROJECT_DIR/data/sshv-sessions.json" "$PROJECT_DIR/data/admin-events.log"

if id -u "$APP_NAME" >/dev/null 2>&1; then
chown -R "$APP_NAME:$APP_NAME" "$PROJECT_DIR/data" "$PROJECT_DIR/logs" || true
[[ -f "$PROJECT_DIR/.env" ]] && chown "$APP_NAME:$APP_NAME" "$PROJECT_DIR/.env" || true
fi

chmod 0750 "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$PROJECT_DIR/logs" || true
chmod 0640 "$PROJECT_DIR/data/sshv-sessions.json" "$PROJECT_DIR/data/admin-events.log" || true
[[ -f "$PROJECT_DIR/.env" ]] && chmod 0600 "$PROJECT_DIR/.env" || true
else
warn "npm install failed — restarting bot on existing node_modules"
warn "npm output: $NPM_OUT"
Expand Down
Loading