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
50 changes: 38 additions & 12 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-26_
_Last audited: 2026-02-28_
_Source of truth files: `index.js`, `test/*.test.js`, scripts under `scripts/`, deployment/runtime docs in repo root._

---
Expand All @@ -11,7 +11,7 @@ Runewager is a Telegraf-based Telegram bot that provides:
- User onboarding (age gate, account/Discord guidance, username linking).
- Promo flows (DB-backed promo manager + eligibility + claim lifecycle).
- Giveaway flows (creation, join, eligibility checks, auto finalization, admin controls).
- Content Drops (scheduled/random posts to configured target chat).
- Helpful Tooltips (scheduled/random posts to configured target chat; formerly "Content Drops").
- Admin operations (broadcasts, diagnostics, SSHV console, bug triage, backups).
Comment on lines +14 to 15

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Terminology migration is incomplete in the authoritative map.

Line 14 switches to “Helpful Tooltips,” but other sections in this map still use “Content drops” (for example Line 111 and Line 123), which makes the source-of-truth internally inconsistent.

Based on learnings: "Applies to RUNEWAGER_FUNCTIONALITY_MAP.md : Always treat RUNEWAGER_FUNCTIONALITY_MAP.md as the authoritative source of truth before and after code changes."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@RUNEWAGER_FUNCTIONALITY_MAP.md` around lines 14 - 15, The document uses mixed
terminology: replace all remaining occurrences of "Content drops" (e.g.,
instances referenced near the sections currently at lines mentioning that
phrase) with the new canonical term "Helpful Tooltips" so
RUNEWAGER_FUNCTIONALITY_MAP.md is consistent; update any headings, bullet
points, indexes or table-of-contents entries that reference "Content drops" to
"Helpful Tooltips" and verify internal references (links/anchors) still match
the new heading text (adjust anchor names if necessary).

- Runtime health and deploy tooling (`/health`, scripts, systemd template).

Expand All @@ -25,7 +25,7 @@ Navigation is driven by inline menus plus command aliases. Persistent user/admin
- Per-user mutable state stored in memory and persisted to JSON runtime snapshots.

### State/storage layers
- In-memory stores: users, giveaway state, analytics, promo manager store, content drops store, broadcast config, SSHV sessions.
- In-memory stores: users, giveaway state, analytics, promo manager store, helpful tooltips store (`tipsStore`), broadcast config, SSHV sessions.
- File persistence under `data/` (runtime snapshots + promo DB + optional backups).
- Periodic persistence timer + startup restore.

Expand Down Expand Up @@ -80,10 +80,10 @@ Navigation is driven by inline menus plus command aliases. Persistent user/admin
- System & Health
- Tests & Bugs
- VPS Console (/sshv)
- Content Drops manager shortcut
- Helpful Tooltips manager shortcut

### Admin category menus
- TestAll engine (`/testall`) runs structured diagnostics across environment, data/stores, callbacks/commands, navigation helpers, giveaway/promo/content-drop, SSHV, and pendingAction timeout/label rules; summary line: `TestAll complete — X passed, Y warnings, Z failures.`
- 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`).
Expand Down Expand Up @@ -234,7 +234,7 @@ Pending-action timeout handling is enforced in the text input router: each pendi
### Verification controls
- Smoke test enforces `REGISTERED_COMMANDS` parity with command handlers detected from `bot.command(...)` and `registerCommand(...)`, including single/double/backtick literals and simple const-driven names (except `start`, handled by `bot.start`).
- Callback coverage smoke check ignores catch-all callback fallbacks using robust pattern detection (`/.*/`, `/^.*$/`, `/.+/` with optional flags/whitespace), so button coverage must be satisfied by literal handlers or meaningful regex families.
- Smoke checks assert presence of critical 2.0 command families (onboarding/promo/giveaway/content-drops/admin tools) and pending-action escape routes (`/cancel`, `/menu`, `/start`, `/help`).
- Smoke checks assert presence of critical 2.0 command families (onboarding/promo/giveaway/helpful-tooltips/admin tools) and pending-action escape routes (`/cancel`, `/menu`, `/start`, `/help`).
- Smoke checks verify `.env.example` documents core runtime configuration keys used by production flows (Telegram links, HTTPS cert/key paths, mini-app URLs, Discord links, `BOT_PRIVACY_MODE`).


Expand Down Expand Up @@ -308,24 +308,50 @@ admin_cmd_announce_start or /announce
-> summary result
```

### Content drop target registration
### Helpful Tooltip target registration
```text
Admin forwards message from channel/group
-> autoRegisterForwardedChatIfPresent
-> approvedGroupsStore add(chatId)
Admin taps "Link Channel/Group" in Tooltip Settings OR forwards any group/channel message
-> extractForwardedChat -> chatId + title extracted
-> await_tip_link_target or autoRegisterForwardedChatIfPresent
-> approvedGroupsStore.add(chatId)
-> tipsStore.targetGroup = chatId
-> tipsStore.targetGroupTitle = title
-> broadcastConfigStore.targetGroup = chatId
-> confirmation to admin (title + id)
```

### Giveaway start/join
### Helpful Tooltip inline button syntax
```text
Tooltip text:
<tooltip text here>
[Label - https://url] && [Label2 - https://url2] ← same row
[Label3 - https://url3] ← new row
[Open Bot] ← standard "Open Bot" button

parseTooltipButtons() strips button lines from text and builds Telegraf inlineKeyboard.
URL validation + label presence enforced; admin receives error message on malformed syntax.
```

### Giveaway start/join (v3.0+)
```text
Admin starts wizard (gwiz)
-> collect config steps
-> giveawayPreflightCheck: validates group linked, warns if missing pin permission
-> collect config steps (9-step wizard)
-> createGiveaway + announceGiveaway
-> post announcement to group
-> pin announcement (notify admin if permission missing)
-> scheduleGiveawayRefresh: timers at 25%, 50%, 75% of duration
-> each fires: edit/resend message, re-pin
-> scheduleGiveawayReminders: 10m, 5m, 1m, 30s, 10→1 countdown messages
-> users click gw_join_<id>
-> evaluateEligibility
-> join accepted/rejected
-> timer expires -> finalizeGiveaway
-> HTML winners announcement in group (parse_mode: HTML)
-> DM each winner with SC amount, boost status, "tip has been sent" notice
-> admin DM: full report (TG handle, display name, RW username, prize, boost)
+ "View Results in Group" deep-link button
+ Reroll/Mark Paid inline actions
```

## 24. Future Updates Log
Expand Down
63 changes: 63 additions & 0 deletions add_tooltip.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# add_tooltip.sh — Append a new placeholder tooltip entry to tooltips.json.
# Called by the bot admin panel "Add Tooltip (Script)" button, or manually.
#
# Usage:
# ./add_tooltip.sh [--text "Custom tooltip text"]
#
# Outputs: new tooltip ID on stdout.
# On success exits 0; on failure exits non-zero.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="${RUNEWAGER_DIR:-$SCRIPT_DIR}"
DATA_DIR="$APP_DIR/data"
TOOLTIPS_FILE="$DATA_DIR/tooltips.json"
TMP_FILE="$TOOLTIPS_FILE.tmp.$$"

CUSTOM_TEXT=""
while [[ $# -gt 0 ]]; do
case "$1" in
--text) CUSTOM_TEXT="$2"; shift 2 ;;
*) shift ;;
esac
done
Comment on lines +20 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Argument parsing missing bounds check for --text value.

If --text is the last argument without a value, $2 will be empty and shift 2 will fail under set -euo pipefail (or silently consume the next option in edge cases).

🛡️ Add bounds check
 while [[ $# -gt 0 ]]; do
   case "$1" in
-    --text) CUSTOM_TEXT="$2"; shift 2 ;;
+    --text)
+      [[ $# -lt 2 ]] && { echo "ERROR: --text requires a value" >&2; exit 1; }
+      CUSTOM_TEXT="$2"; shift 2 ;;
     *) shift ;;
   esac
 done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while [[ $# -gt 0 ]]; do
case "$1" in
--text) CUSTOM_TEXT="$2"; shift 2 ;;
*) shift ;;
esac
done
while [[ $# -gt 0 ]]; do
case "$1" in
--text)
[[ $# -lt 2 ]] && { echo "ERROR: --text requires a value" >&2; exit 1; }
CUSTOM_TEXT="$2"; shift 2 ;;
*) shift ;;
esac
done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@add_tooltip.sh` around lines 20 - 25, The argument parser loop incorrectly
assumes a value follows the --text flag (it assigns CUSTOM_TEXT="$2" and does
shift 2) without checking bounds; update the case branch that handles --text to
first verify there is at least one remaining argument (e.g. check $# -ge 2 or
test that $2 is non-empty and not another flag) before assigning CUSTOM_TEXT and
shifting, and handle the error path by printing a clear message and exiting;
refer to the while loop and the --text case handling (CUSTOM_TEXT, shift 2) to
locate where to add the guard.


info() { echo "[add_tooltip] INFO: $*"; }
error() { echo "[add_tooltip] ERROR: $*" >&2; exit 1; }

mkdir -p "$DATA_DIR" || error "Cannot create data dir"

# Ensure tooltips.json exists
if [[ ! -f "$TOOLTIPS_FILE" ]]; then
info "tooltips.json not found — initialising empty list"
echo '[]' > "$TOOLTIPS_FILE"
fi

# Validate existing file
node -e "JSON.parse(require('fs').readFileSync('$TOOLTIPS_FILE','utf8'))" 2>/dev/null \
|| error "Existing $TOOLTIPS_FILE is not valid JSON"

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
const fs = require('fs');
const file = process.argv[1];
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 });
Comment on lines +45 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Shell interpolation inside the Node.js heredoc is producing invalid JavaScript when adding the tooltip text.

Inside the heredoc, the shell expands $(node -e ...) before Node runs, so the generated script gets the tooltip text inserted directly (e.g. New tooltip...) instead of as a quoted string literal. That makes the JavaScript invalid and brittle around quotes/special characters.

Instead, JSON-encode the tooltip text once in the shell and pass it as an argument, then parse it inside the Node script:

TOOLTIP_TEXT_JSON=$(node -e 'console.log(JSON.stringify(process.argv[1] || ""))' "$TOOLTIP_TEXT")
NEW_ID=$(node - "$TOOLTIPS_FILE" "$TOOLTIP_TEXT_JSON" <<'EOF'
const fs = require('fs');
const file = process.argv[2];
const text = JSON.parse(process.argv[3]);
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, enabled: true });
fs.writeFileSync('${TMP_FILE}', JSON.stringify(list, null, 2));
console.log(newId);
EOF
)

This keeps quoting inside Node and avoids shell-generated JS fragments.

Comment on lines +45 to +51

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: The Node.js heredoc currently reads from the wrong CLI argument (process.argv[1] instead of the tooltips file argument) and inlines the tooltip text via a nested node -e command substitution, which both causes it to read a non-existent - file and makes any tooltip text containing quotes or special characters break the script or corrupt the JSON; passing the text via an environment variable and using the correct argv index fixes both issues. [logic error]

Severity Level: Critical 🚨
- ❌ Admin "Add Tooltip (Script)" cannot append any tooltips.
- ❌ Manual `./add_tooltip.sh` usage always fails with ENOENT.
- ⚠️ Custom texts containing quotes corrupt or abort JSON generation.
Suggested change
NEW_ID=$(node - "$TOOLTIPS_FILE" <<EOF
const fs = require('fs');
const file = process.argv[1];
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 });
NEW_ID=$(TOOLTIP_TEXT="$TOOLTIP_TEXT" node - "$TOOLTIPS_FILE" <<EOF
const fs = require('fs');
const file = process.argv[2];
const list = JSON.parse(fs.readFileSync(file, 'utf8'));
const tooltipText = process.env.TOOLTIP_TEXT || '';
const maxId = list.reduce((m, t) => Math.max(m, Number(t.id) || 0), 0);
const newId = maxId + 1;
list.push({ id: newId, text: tooltipText, enabled: true });
Steps of Reproduction ✅
1. From the repository root `/workspace/Runewager`, run `./add_tooltip.sh` (script defined
in `add_tooltip.sh:1-63`).

2. Script creates `data/tooltips.json` if missing and validates it via `node -e` at
`add_tooltip.sh:32-40`.

3. The Node heredoc at `add_tooltip.sh:44-54` runs `node - "$TOOLTIPS_FILE"`, so inside
Node `process.argv[1]` is `"-"` and `process.argv[2]` is the actual `tooltips.json` path.

4. Line `47` (`const file = process.argv[1];`) causes `fs.readFileSync('-','utf8')` to
execute; since no file named `-` exists, Node throws `ENOENT`, the `node` command exits
non‑zero, `set -e` aborts the script, and no tooltip is appended.

5. Additionally, invoke `./add_tooltip.sh --text "It's broken"`; at `add_tooltip.sh:51`
the nested `node -e "JSON.stringify('$TOOLTIP_TEXT')"` expansion injects an unescaped
single quote into JavaScript, causing a parse error in the inner Node process and again
aborting the script before updating `tooltips.json`.

6. In both cases, the admin-facing "Add Tooltip (Script)" flow described in
`add_tooltip.sh:3` will fail whenever it calls this script, preventing new tooltips from
being added.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** add_tooltip.sh
**Line:** 45:51
**Comment:**
	*Logic Error: The Node.js heredoc currently reads from the wrong CLI argument (`process.argv[1]` instead of the tooltips file argument) and inlines the tooltip text via a nested `node -e` command substitution, which both causes it to read a non-existent `-` file and makes any tooltip text containing quotes or special characters break the script or corrupt the JSON; passing the text via an environment variable and using the correct argv index fixes both issues.

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.
👍 | 👎

fs.writeFileSync('${TMP_FILE}', JSON.stringify(list, null, 2));
console.log(newId);
EOF
)
Comment on lines +45 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Shell variable interpolation in Node code is vulnerable to injection.

Line 51 embeds $TOOLTIP_TEXT directly in a single-quoted JavaScript string. If the tooltip text contains single quotes or $ characters, this will break or produce unexpected results. Line 52 has the same issue with ${TMP_FILE}.

🔒 Safer approach using environment variables
 # Append new entry and get new ID using Node.js
-NEW_ID=$(node - "$TOOLTIPS_FILE" <<EOF
+NEW_ID=$(TOOLTIP_TEXT="$TOOLTIP_TEXT" TMP_FILE="$TMP_FILE" node - "$TOOLTIPS_FILE" <<'EOF'
 const fs = require('fs');
 const file = process.argv[1];
 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: process.env.TOOLTIP_TEXT, enabled: true });
+fs.writeFileSync(process.env.TMP_FILE, JSON.stringify(list, null, 2));
 console.log(newId);
 EOF
 )

Note: Changed <<EOF to <<'EOF' (quoted) to prevent shell expansion inside the heredoc.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@add_tooltip.sh` around lines 45 - 55, The heredoc that injects shell
variables into the embedded Node script (the NEW_ID block) is vulnerable to
injection; change it to a quoted heredoc (<<'EOF') and stop interpolating
$TOOLTIP_TEXT and ${TMP_FILE} directly into the JS source — instead export
TOOLTIP_TEXT and TMP_FILE into the environment and read them inside Node via
process.env.TOOLTIP_TEXT and process.env.TMP_FILE (use JSON.parse/JSON.stringify
as needed) so the Node code uses safe environment values rather than
shell-expanded strings.


# Validate and move
node -e "JSON.parse(require('fs').readFileSync('$TMP_FILE','utf8'))" 2>/dev/null \
|| { rm -f "$TMP_FILE"; error "Generated JSON failed validation"; }

mv "$TMP_FILE" "$TOOLTIPS_FILE"
info "Added tooltip #${NEW_ID}: ${TOOLTIP_TEXT:0:60}"
echo "$NEW_ID"
18 changes: 18 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,24 @@ else
exit 1
fi

# ---------------------------------------------------------
# 3b) Auto-run tooltip generation script (must run before bot restart)
# ---------------------------------------------------------
say "Step 3b — Refreshing Helpful Tooltips…"
TOOLTIP_SCRIPT="$PROJECT_DIR/generate_tooltips.sh"
if [[ -x "$TOOLTIP_SCRIPT" ]]; then
if TOOLTIP_OUT=$(RUNEWAGER_DIR="$PROJECT_DIR" bash "$TOOLTIP_SCRIPT" 2>&1); then
say "Helpful tooltips refreshed."
else
warn "generate_tooltips.sh failed (non-fatal): $TOOLTIP_OUT"
# DM admin but continue deploy
send_admin "⚠️ generate_tooltips.sh failed during deploy: ${TOOLTIP_OUT:0:200} — continuing deploy."
fi
else
warn "generate_tooltips.sh not found or not executable at $TOOLTIP_SCRIPT — skipping tooltip refresh."
send_admin "⚠️ generate_tooltips.sh missing at $TOOLTIP_SCRIPT — tooltips not refreshed."
fi

# ---------------------------------------------------------
# 4) Start bot via systemctl
# ---------------------------------------------------------
Expand Down
75 changes: 75 additions & 0 deletions generate_tooltips.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# generate_tooltips.sh — Refresh the Helpful Tooltips data file.
# Idempotent: safe to run on every deploy. Creates/overwrites tooltips.json
# from the source-of-truth DEFAULT_TIPS_LIST embedded in index.js.
# Called automatically by deploy.sh and prod-run.sh before bot restart.
#
# Usage:
# ./generate_tooltips.sh [--dry-run]
# --dry-run Print what would be written without making changes.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="${RUNEWAGER_DIR:-$SCRIPT_DIR}"
DATA_DIR="$APP_DIR/data"
TOOLTIPS_FILE="$DATA_DIR/tooltips.json"
TMP_FILE="$TOOLTIPS_FILE.tmp.$$"

DRY_RUN=false
for arg in "$@"; do
[[ "$arg" == "--dry-run" ]] && DRY_RUN=true
done

info() { echo "[generate_tooltips] INFO: $*"; }
warn() { echo "[generate_tooltips] WARN: $*" >&2; }
error() { echo "[generate_tooltips] ERROR: $*" >&2; exit 1; }

# Ensure data directory exists
mkdir -p "$DATA_DIR" || error "Cannot create data dir: $DATA_DIR"

# Extract DEFAULT_TIPS_LIST from index.js using Node.js
if [[ ! -f "$APP_DIR/index.js" ]]; then
error "index.js not found at $APP_DIR/index.js"
fi

info "Extracting DEFAULT_TIPS_LIST from index.js..."
TOOLTIP_JSON=$(node - <<'EOF'
const fs = require('fs');
const src = fs.readFileSync(process.argv[1] || '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); }
try {
// Use Function constructor for safe eval of the array literal
const list = (new Function('return ' + m[1]))();
console.log(JSON.stringify(list, null, 2));
Comment on lines +37 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The heredoc / command substitution block is syntactically and semantically broken, likely causing generate_tooltips.sh to fail or misbehave.

This combines the heredoc and a separate node call inside one $(...), so after EOF the node "$APP_DIR/index.js" --version line is still parsed as part of the command substitution. That’s invalid and will either break the script or be treated as extra args to node -.

Inside the Node snippet, process.argv[1] is never set to index.js because node - only receives - as the script path; it will always fall back to 'index.js' in the CWD and ignore APP_DIR.

You likely want something along the lines of:

TOOLTIP_JSON=$(node "$APP_DIR/index.js" <<'EOF'
const fs = require('fs');
const appIndex = process.argv[2] || 'index.js';
const src = fs.readFileSync(appIndex, 'utf8');
...
EOF
"$APP_DIR/index.js") || { ... }

or otherwise restructure so the heredoc and any additional node calls are separate, and the path to index.js is passed explicitly into the script.

} catch (e) { process.stderr.write('Parse error: ' + e.message + '\n'); process.exit(1); }
EOF
node "$APP_DIR/index.js" --version 2>/dev/null || true
Comment on lines +37 to +49

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: The tooltip generation script never actually reads the intended index.js file and may also append unrelated node index.js --version output into the JSON, because the Node snippet is invoked as node - <<'EOF' and then uses process.argv[1] (which is '-' in this mode) as the path, and also runs a second node "$APP_DIR/index.js" --version inside the same command substitution; this causes the extraction to fail and always fall back to the placeholder data instead of the real DEFAULT_TIPS_LIST. [logic error]

Severity Level: Major ⚠️
- ⚠️ Helpful tooltips file always ends up with placeholder content.
- ⚠️ Bot loads placeholder tooltip instead of DEFAULT_TIPS_LIST array.
- ⚠️ Admins cannot deploy updated tooltip definitions via index.js.
Suggested change
TOOLTIP_JSON=$(node - <<'EOF'
const fs = require('fs');
const src = fs.readFileSync(process.argv[1] || '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); }
try {
// Use Function constructor for safe eval of the array literal
const list = (new Function('return ' + m[1]))();
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
TOOLTIP_JSON=$(node - "$APP_DIR/index.js" <<'EOF'
const fs = require('fs');
const srcPath = process.argv[2] || 'index.js';
const src = fs.readFileSync(srcPath, '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); }
try {
// Use Function constructor for safe eval of the array literal
const list = (new Function('return ' + m[1]))();
console.log(JSON.stringify(list, null, 2));
} catch (e) { process.stderr.write('Parse error: ' + e.message + '\n'); process.exit(1); }
EOF
Steps of Reproduction ✅
1. Trigger a deployment that runs the tooltip generator, e.g. execute the VPS deployment
script `deploy.sh` at `/workspace/Runewager/deploy.sh`, which at lines 217–221 sets
`TOOLTIP_SCRIPT="$PROJECT_DIR/generate_tooltips.sh"` and invokes
`RUNEWAGER_DIR="$PROJECT_DIR" bash "$TOOLTIP_SCRIPT"` before restarting the bot.

2. Inside `generate_tooltips.sh` at lines 36–47, the script runs `TOOLTIP_JSON=$(node -
<<'EOF' ...)` and the embedded Node snippet calls `fs.readFileSync(process.argv[1] ||
'index.js', 'utf8')`; because the `node` command is invoked as `node -`, `process.argv[1]`
is `'-'`, so Node attempts to read a non-existent file named `-` and exits with an error
without emitting JSON.

3. Still inside the same command substitution (lines 36–50), the next command `node
"$APP_DIR/index.js" --version 2>/dev/null || true` runs successfully and prints a version
string (for example `v3.0.0`) to stdout; because this is the last command in the subshell
its zero exit status causes the overall `$(...)` to succeed, and `TOOLTIP_JSON` is set to
the non‑JSON version string instead of a JSON array.

4. Later in `generate_tooltips.sh` at lines 62–71, the script writes `"$TOOLTIP_JSON"`
(the version string) to the temp file and validates it with `node -e "JSON.parse(...)"`;
JSON parsing fails, so the `|| { ... }` block at lines 66–71 replaces the temp file
contents with a hard-coded placeholder JSON array, and finally `mv "$TMP_FILE"
"$TOOLTIPS_FILE"` at line 73 writes this placeholder to
`/var/www/html/Runewager/data/tooltips.json`, which is exactly the path used by
`loadHelpfulMessages()` in `index.js` (lines 14–23 and the `systemTooltipsFile` constant
at line 337) to populate the in-bot Helpful Tooltips list.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** generate_tooltips.sh
**Line:** 37:49
**Comment:**
	*Logic Error: The tooltip generation script never actually reads the intended `index.js` file and may also append unrelated `node index.js --version` output into the JSON, because the Node snippet is invoked as `node - <<'EOF'` and then uses `process.argv[1]` (which is `'-'` in this mode) as the path, and also runs a second `node "$APP_DIR/index.js" --version` inside the same command substitution; this causes the extraction to fail and always fall back to the placeholder data instead of the real `DEFAULT_TIPS_LIST`.

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.
👍 | 👎

) || {
# Fallback: emit a minimal valid tooltips.json with a placeholder
warn "Could not extract tooltips from index.js — writing placeholder."
TOOLTIP_JSON='[{"id":1,"text":"Helpful tooltip placeholder — configure via /tips in the bot admin.","enabled":true}]'
}
Comment on lines +37 to +54

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Orphaned command inside command substitution will cause issues.

Line 49 (node "$APP_DIR/index.js" --version ...) appears after the heredoc ends but is still within the $(...) command substitution. This line:

  1. Executes the entire index.js file (not just parsing), which may start the bot or have side effects
  2. Its output gets appended to TOOLTIP_JSON, corrupting the JSON
🐛 Proposed fix — remove orphaned line
 TOOLTIP_JSON=$(node - <<'EOF'
 const fs = require('fs');
 const src = fs.readFileSync(process.argv[1] || '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); }
 try {
   // Use Function constructor for safe eval of the array literal
   const list = (new Function('return ' + m[1]))();
   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
 ) || {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TOOLTIP_JSON=$(node - <<'EOF'
const fs = require('fs');
const src = fs.readFileSync(process.argv[1] || '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); }
try {
// Use Function constructor for safe eval of the array literal
const list = (new Function('return ' + m[1]))();
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."
TOOLTIP_JSON='[{"id":1,"text":"Helpful tooltip placeholder — configure via /tips in the bot admin.","enabled":true}]'
}
TOOLTIP_JSON=$(node - <<'EOF'
const fs = require('fs');
const src = fs.readFileSync(process.argv[1] || '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); }
try {
// Use Function constructor for safe eval of the array literal
const list = (new Function('return ' + m[1]))();
console.log(JSON.stringify(list, null, 2));
} catch (e) { process.stderr.write('Parse error: ' + e.message + '\n'); process.exit(1); }
EOF
) || {
# Fallback: emit a minimal valid tooltips.json with a placeholder
warn "Could not extract tooltips from index.js — writing placeholder."
TOOLTIP_JSON='[{"id":1,"text":"Helpful tooltip placeholder — configure via /tips in the bot admin.","enabled":true}]'
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate_tooltips.sh` around lines 37 - 54, The command substitution that
populates TOOLTIP_JSON accidentally contains an orphaned line that runs node
"$APP_DIR/index.js" --version inside the $(...) block, causing the full app to
execute and corrupt the JSON; remove that orphaned line from the heredoc/block
(or move it outside the command substitution and run it separately if you need
the version check), ensuring the only commands inside the $(...) are the inline
node script that extracts DEFAULT_TIPS_LIST and the subsequent JSON-producing
logic so TOOLTIP_JSON contains only valid JSON.


if [[ "$DRY_RUN" == "true" ]]; then
info "[dry-run] Would write to $TOOLTIPS_FILE:"
echo "$TOOLTIP_JSON"
exit 0
fi

# Atomic write: write to temp file, validate JSON, then move
echo "$TOOLTIP_JSON" > "$TMP_FILE"

# Validate JSON before replacing
node -e "JSON.parse(require('fs').readFileSync('$TMP_FILE','utf8'))" 2>/dev/null || {
rm -f "$TMP_FILE"
# Write placeholder instead of failing
warn "Generated JSON failed validation — writing placeholder."
echo '[{"id":1,"text":"Helpful tooltip placeholder — configure via /tips in the bot admin.","enabled":true}]' > "$TMP_FILE"
}

mv "$TMP_FILE" "$TOOLTIPS_FILE"
info "Helpful tooltips refreshed → $TOOLTIPS_FILE"
info "Total entries: $(node -e "console.log(JSON.parse(require('fs').readFileSync('$TOOLTIPS_FILE','utf8')).length)")"
Loading