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
25 changes: 22 additions & 3 deletions RUNEWAGER_FUNCTIONALITY_MAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# RUNEWAGER_FUNCTIONALITY_MAP.md

_Last audited: 2026-02-28_
_Last audited: 2026-02-28 (v3.1 pass)_
_Source of truth files: `index.js`, `test/*.test.js`, scripts under `scripts/`, deployment/runtime docs in repo root._

---
Expand Down Expand Up @@ -48,6 +48,8 @@ Navigation is driven by inline menus plus command aliases. Persistent user/admin
- Many menu-style responses use `replaceCallbackPanel(...)` to avoid stale stacked cards.
- Single active menu rule: `clearOldMenus(ctx)` + menu send helpers keep only one active transient menu card per user/chat.
- `to_main_menu` clears transient menu cards via `clearOldMenus(...)` before rendering persistent menu headers.
- **Group command guard middleware** intercepts commands sent in group/supergroup chats. Commands that have their own group-specific handling (`link`, `linkrunewager`, `giveaway`, `start_giveaway`, `admin`) pass through. All other commands receive a "💬 This command works in DM" response with a deep-link button; the command handler is suppressed. This prevents onboarding, settings, promo, and other DM flows from executing in group chats.
- **`GROUP_PASSTHROUGH_COMMANDS`** set defines which commands bypass the group guard; it does NOT restrict admin access or callback queries.

## 4. User Menu Tree

Expand Down Expand Up @@ -86,7 +88,8 @@ Navigation is driven by inline menus plus command aliases. Persistent user/admin
- TestAll engine (`/testall`) runs structured diagnostics across environment, data/stores, callbacks/commands, navigation helpers, giveaway/promo/helpful-tooltips, SSHV, pendingAction timeout/label rules; summary line: `TestAll complete — X passed, Y warnings, Z failures.`
- `admin_cat_giveaway`: start/test/status + persistent navigation row (`Admin Dashboard`, `Main Menu`, `Cancel`).
- `admin_cat_promo`: full promo manager actions + guide + persistent navigation row (`Admin Dashboard`, `Main Menu`, `Cancel`).
- `admin_cat_system`: health/version/verify/setup/backup/admin mode/testall/sshv + persistent navigation row (`Admin Dashboard`, `Main Menu`, `Cancel`).
- `admin_cat_system`: health/version/verify/setup/backup/admin mode/testall/sshv + **🔗 Group Linking** (v3.1 — opens group linking tools with return to System Tools) + persistent navigation row (`Admin Dashboard`, `Main Menu`, `Cancel`).
- `admin_sys_group_linking` callback renders the group linking panel (`renderGroupLinkingTools`) with `admin_cat_system` as the back target.
- `admin_cat_tests`: bug tools + test tools + sshv shortcut + return navigation controls.
- `admin_cat_support`: bug report management shortcuts + persistent navigation row (`Admin Dashboard`, `Main Menu`, `Cancel`).

Expand Down Expand Up @@ -150,13 +153,27 @@ The bot uses `user.pendingAction.type` as its input state machine. Key families:
5. Community join prompts (channel/group flags).
6. Walkthrough progression tracking (`user.walkthrough`, onboarding milestones).

**Progress Indicator (v3.1+):**
- `onboardingProgressBar(step)` renders a visual dot bar: `●●○○○ Step 2 of 5 — Link Runewager`.
- `showOnboardingPrompt(ctx, user, step)` sends the progress bar as a Markdown message (auto-deletes after 8s) before each step-specific prompt.
- `user.onboarding.completionCardShown` — boolean, defaults `false`. Set to `true` on first main-menu arrival after completion.
- **Completion card** is shown once, the first time the user reaches the main menu after completing all 5 steps: friendly welcome, feature summary, and a "🎮 Open Menu" button.

Recovery:
- `confirm_no_username` returns to username entry.
- `/stuck` and `/fixaccount` provide guided recovery paths.

## 10. Group Commands

Group-aware commands include giveaway interaction and linking shortcuts:
**Group command guard (v3.1+):** All bot commands sent in a group/supergroup are intercepted by middleware and redirected to DM. Passthrough exceptions (have their own group logic):
- `/link` / `/linkrunewager` — accepts inline username argument, acknowledges in group, continues in DM.
- `/giveaway` — shows active giveaways for that group; admin can start a new one.
- `/start_giveaway` — admin-only wizard in the group context.
- `/admin` — sends brief DM-launch button in group.

All other commands (menu, settings, promo, bonus, profile, help, status, etc.) receive: "💬 This command works in DM. Tap below..." with a direct DM deep-link. The command handler is suppressed (next() not called).

Group-aware commands also include giveaway interaction and linking shortcuts:
- `/giveaway` (admin wizard in group context).
- `/join` and `gw_join_<id>` for participant entry.
- `/eligible [id]` checks eligibility.
Expand Down Expand Up @@ -384,3 +401,5 @@ Mandatory rules for any AI agent touching this repo:
- 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-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.
11 changes: 7 additions & 4 deletions add_tooltip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ node -e "JSON.parse(require('fs').readFileSync('$TOOLTIPS_FILE','utf8'))" 2>/dev
TOOLTIP_TEXT="${CUSTOM_TEXT:-New tooltip — edit in admin panel via /tips.}"

# Append new entry and get new ID using Node.js
NEW_ID=$(node - "$TOOLTIPS_FILE" <<EOF
# Text and tmp path are passed via env vars to avoid shell injection on special characters
NEW_ID=$(TOOLTIP_TEXT_ENV="$TOOLTIP_TEXT" TOOLTIP_TMP_FILE="$TMP_FILE" node - "$TOOLTIPS_FILE" <<'EOF'
const fs = require('fs');
const file = process.argv[1];
const file = process.argv[2];
const text = process.env.TOOLTIP_TEXT_ENV;
const tmpFile = process.env.TOOLTIP_TMP_FILE;
const list = JSON.parse(fs.readFileSync(file, 'utf8'));
const maxId = list.reduce((m, t) => Math.max(m, Number(t.id) || 0), 0);
const newId = maxId + 1;
list.push({ id: newId, text: $(node -e "process.stdout.write(JSON.stringify('$TOOLTIP_TEXT'))"), enabled: true });
fs.writeFileSync('${TMP_FILE}', JSON.stringify(list, null, 2));
list.push({ id: newId, text, enabled: true });
fs.writeFileSync(tmpFile, JSON.stringify(list, null, 2));
console.log(newId);
EOF
)
Expand Down
19 changes: 19 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,25 @@ else
send_admin "⚠️ generate_tooltips.sh missing at $TOOLTIP_SCRIPT — tooltips not refreshed."
fi

# ---------------------------------------------------------
# 3c) Kill anything blocking the bot port before starting
# ---------------------------------------------------------
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'" | tr -d $'\r')"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: When PORT in .env includes an inline comment (e.g., PORT=3000 # dev), the current parsing stores the entire 3000 # dev string in DEPLOY_PORT, so the lsof/fuser call cannot interpret it as a valid port and the script silently fails to kill the blocking process, potentially leaving the deploy unable to start the bot on the intended port. [logic error]

Severity Level: Major ⚠️
- ❌ Deploy script may miss processes blocking configured bot port.
- ⚠️ Bot restart via /deploy can fail to bind port.
- ⚠️ Admin may see deploy complete while bot unreachable.
Suggested change
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'" | tr -d $'\r')"
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null \
| head -1 \
| cut -d= -f2- \
| sed 's/[[:space:]]*#.*$//' \
| tr -d '"' \
| tr -d "'" \
| tr -d $'\r')"
Steps of Reproduction ✅
1. On the VPS, edit the environment file `/workspace/Runewager/.env` (same directory
`PROJECT_DIR` used by `deploy.sh` at `/workspace/Runewager/deploy.sh:26`) and set the port
with an inline comment, e.g. `PORT=3000 # production port`.

2. Ensure a Node/bot process is already listening on TCP port 3000 (e.g. via a previous
manual `node index.js` run); this is what the guard at
`/workspace/Runewager/deploy.sh:233-249` is meant to detect and kill.

3. Trigger a deploy using the bot's `/deploy` command, which (per
`/workspace/Runewager/index.js:6855-6873` from Grep) spawns `deploy.sh` to handle git,
dependencies, and service restart.

4. During step "3c) Kill anything blocking the bot port before starting" at
`/workspace/Runewager/deploy.sh:233-249`, the current code on lines 235-236 parses
`DEPLOY_PORT` using `cut -d= -f2`, yielding the literal string `3000 # production port`;
`lsof -ti :"$DEPLOY_PORT"` and `fuser -n tcp "$DEPLOY_PORT"` then fail to match the real
port so `_BLOCKING` stays empty, no PIDs are killed, and the subsequent `systemctl start
"${APP_NAME}.service"` at line 257 can fail to bind to port 3000, leaving the new bot
service not running on the intended port.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** deploy.sh
**Line:** 235:235
**Comment:**
	*Logic Error: When `PORT` in `.env` includes an inline comment (e.g., `PORT=3000 # dev`), the current parsing stores the entire `3000 # dev` string in `DEPLOY_PORT`, so the lsof/fuser call cannot interpret it as a valid port and the script silently fails to kill the blocking process, potentially leaving the deploy unable to start the bot on the intended port.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

DEPLOY_PORT="${DEPLOY_PORT:-3000}"
_BLOCKING=""
if command -v lsof >/dev/null 2>&1; then
_BLOCKING="$(lsof -ti :"$DEPLOY_PORT" 2>/dev/null || true)"
elif command -v fuser >/dev/null 2>&1; then
_BLOCKING="$(fuser -n tcp "$DEPLOY_PORT" 2>/dev/null | tr ' ' '\n' | sed '/^$/d' || true)"
fi
if [[ -n "$_BLOCKING" ]]; then
say "Port $DEPLOY_PORT blocked — killing before start…"
for _pid in $_BLOCKING; do
kill -9 "$_pid" 2>/dev/null || true
done
sleep 1
fi

# ---------------------------------------------------------
# 4) Start bot via systemctl
# ---------------------------------------------------------
Expand Down
28 changes: 28 additions & 0 deletions dev-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ if [ "$NODE_MAJOR" -lt 20 ] 2>/dev/null; then
exit 1
fi

# Pull latest code
echo "[dev-run] Pulling latest code from origin main..."
git -C "$ROOT_DIR" fetch origin main 2>&1 \
&& git -C "$ROOT_DIR" reset --hard origin/main 2>&1 \
|| echo "[dev-run] WARN: git pull failed — starting with local copy"

# Refresh tooltips
TOOLTIP_SCRIPT="$ROOT_DIR/generate_tooltips.sh"
if [ -x "$TOOLTIP_SCRIPT" ]; then
echo "[dev-run] Refreshing tooltips..."
RUNEWAGER_DIR="$ROOT_DIR" sh "$TOOLTIP_SCRIPT" >/dev/null 2>&1 \
|| echo "[dev-run] WARN: generate_tooltips.sh failed (non-fatal)"
fi

# Kill anything blocking port 3000 (or PORT from .env)
DEV_PORT=$(grep -E '^PORT=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'" || true)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Similar to the deploy script, if PORT in .env includes an inline comment (e.g., PORT=3000 # dev), the current parsing leaves the comment in DEV_PORT, so the lsof/fuser invocation cannot parse it as a valid port and the script may fail to kill the process blocking the dev server port, causing confusing startup failures. [logic error]

Severity Level: Major ⚠️
- ❌ dev-run.sh fails to kill process blocking dev port.
- ⚠️ Local dev `node index.js` may fail with EADDRINUSE.
- ⚠️ Developers must manually diagnose and free blocked ports.
Suggested change
DEV_PORT=$(grep -E '^PORT=' "$ROOT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'" || true)
DEV_PORT=$(grep -E '^PORT=' "$ROOT_DIR/.env" 2>/dev/null \
| head -1 \
| cut -d= -f2- \
| sed 's/[[:space:]]*#.*$//' \
| tr -d '"' \
| tr -d "'" || true)
Steps of Reproduction ✅
1. In the project root `/workspace/Runewager`, create or edit `.env` so the port line
includes an inline comment, e.g. `PORT=3000 # dev`, matching the pattern read by
`dev-run.sh:37` (`grep -E '^PORT=' "$ROOT_DIR/.env"`).

2. Start a process that listens on TCP port 3000, e.g. by running `node index.js` once and
leaving it running, so that port 3000 is already in use before running the dev script.

3. Run the dev helper script `./dev-run.sh`; after the Node version check at
`dev-run.sh:15-20`, the script parses the port at `dev-run.sh:37-38`, assigning
`DEV_PORT="3000 # dev"` because `cut -d= -f2` returns the entire `3000 # dev` value
without stripping the comment.

4. The script then attempts to kill processes on that port via `lsof`/`fuser` at
`dev-run.sh:39-42`, but because `DEV_PORT` contains `3000 # dev`, the arguments to
`lsof`/`fuser` are malformed and no process on TCP port 3000 is actually killed; the
subsequent `exec node index.js` at `dev-run.sh:52` may then fail to bind with an
`EADDRINUSE` error even though `dev-run.sh` claimed to free the port.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** dev-run.sh
**Line:** 37:37
**Comment:**
	*Logic Error: Similar to the deploy script, if `PORT` in `.env` includes an inline comment (e.g., `PORT=3000 # dev`), the current parsing leaves the comment in `DEV_PORT`, so the lsof/fuser invocation cannot parse it as a valid port and the script may fail to kill the process blocking the dev server port, causing confusing startup failures.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

DEV_PORT="${DEV_PORT:-3000}"
if command -v lsof >/dev/null 2>&1; then
_DEV_PIDS=$(lsof -ti :"$DEV_PORT" 2>/dev/null || true)
elif command -v fuser >/dev/null 2>&1; then
_DEV_PIDS=$(fuser -n tcp "$DEV_PORT" 2>/dev/null | tr ' ' '\n' | sed '/^$/d' || true)
fi
if [ -n "${_DEV_PIDS:-}" ]; then
echo "[dev-run] WARN: Port $DEV_PORT blocked — killing..."
for _p in $_DEV_PIDS; do kill -9 "$_p" 2>/dev/null || true; done
sleep 1
fi

# Foreground local run (Termux-safe). Runtime env is loaded by index.js via dotenv.
echo "[dev-run] Starting Runewager in foreground (Node $(node -v))..."
exec node index.js
5 changes: 2 additions & 3 deletions generate_tooltips.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ if [[ ! -f "$APP_DIR/index.js" ]]; then
fi

info "Extracting DEFAULT_TIPS_LIST from index.js..."
TOOLTIP_JSON=$(node - <<'EOF'
TOOLTIP_JSON=$(RUNEWAGER_APP="$APP_DIR/index.js" node - <<'EOF'
const fs = require('fs');
const src = fs.readFileSync(process.argv[1] || 'index.js', 'utf8');
const src = fs.readFileSync(process.env.RUNEWAGER_APP || 'index.js', 'utf8');
// Execute just the DEFAULT_TIPS_LIST block and print it as JSON
const m = src.match(/const DEFAULT_TIPS_LIST\s*=\s*(\[[\s\S]+?\]);/);
if (!m) { process.stderr.write('DEFAULT_TIPS_LIST not found\n'); process.exit(1); }
Expand All @@ -46,7 +46,6 @@ try {
console.log(JSON.stringify(list, null, 2));
} catch (e) { process.stderr.write('Parse error: ' + e.message + '\n'); process.exit(1); }
EOF
node "$APP_DIR/index.js" --version 2>/dev/null || true
) || {
# Fallback: emit a minimal valid tooltips.json with a placeholder
warn "Could not extract tooltips from index.js — writing placeholder."
Expand Down
Loading