feat: admin dashboard upgrade, giveaway wizard, stats layer, group co… - #57
Conversation
…mmands fix
ADMIN DASHBOARD
- /admin in DM now shows full rich dashboard with status lights, uptime,
deploy time (from DEPLOY_TIME env or .release_info), active user counts
(5m / 10m windows), giveaway stats, and error count
- /admin in group sends a compact inline button to open DM dashboard
- Added Stats submenu button to adminDashboardKeyboard (pages 1 and 2)
- New adminStatsKeyboard() and buildAdminStatusText() helpers
- Removed duplicate admin_dashboard_action handler (was registered twice)
STATS / ANALYTICS LAYER
- New buildStatsWindow(windowMs) for O(n) time-windowed queries:
new users, active users, interactions, promo claims, bonus sent,
giveaways created, giveaway participants
- New admin_stats_menu / admin_stats_24h / admin_stats_7d /
admin_stats_30d / admin_stats_lifetime inline action handlers
- Added lastSeenAt field to user records; updated on every getUser() call
so active-user windows work correctly
- buildDashboard() now includes activeUsers5m, activeUsers10m,
runningGiveaways, totalGiveaways
- trackAnalytics('promo_claimed') now emitted so window queries count it
GIVEAWAY WIZARD — full inline-button flow
- New /giveaway command: non-admins see active giveaway list; admins get
a wizard start menu (group) or full wizard (DM)
- /start_giveaway now launches the inline wizard (not text-only)
- 8-step wizard: title → winners → SC → duration → minParticipants →
join surface → requirements toggles → confirm
- Preset buttons for each numeric step; "Custom" option drops to text input
- Requirements step: tap-to-toggle inline keyboard (editMessageReplyMarkup)
- Confirm step reuses existing gw_create_yes / gw_create_no handlers
GIVEAWAY ENGINE UPGRADES
- New fields: title, minParticipants, joinSurface ('group'|'dm'|'both'),
pinnedMsgId, extendedCount — all serialised / deserialised cleanly
- announceGiveaway() now:
• Pins announcement in group (unpins on finalise); ignores permission errors
• If surface=dm or both: broadcasts DM join message to all opted-in users
• If surface includes both: shows inline group button + t.me deep-link URL
- finalizeGiveaway(gwId, forceEnd) now checks minParticipants:
• If unmet and extended < 2 times: DMs admins with Extend/Force-end buttons
• gw_auto_extend_N: adds 15 min, increments extendedCount
• gw_force_end_N: finalises regardless of participant count
• Unpins announcement message on end
- /start deep link handler: ?start=join_gw_<id> auto-joins the giveaway
from DM (eligibility checked, duplicate prevented)
GROUP COMMANDS FIX (interrupted task)
- Expanded groupCommands from 3 to 17 entries — all relevant commands now
appear when users type / in a group chat (Telegram command picker)
https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN
|
CodeAnt AI is reviewing your PR. |
📝 WalkthroughWalkthroughAdds a comprehensive inline giveaway wizard, expands giveaway and user data models (minParticipants, title, joinSurface, pinnedMsgId, extendedCount, many user fields), implements admin analytics/status panels and time-windowed stats, and extends announce/join/extension lifecycle with new admin controls and DM/group join support. Changes
Sequence DiagramsequenceDiagram
participant Admin as Admin
participant Bot as Bot
participant DB as State/DB
participant User as User
Admin->>Bot: gwizStart (open wizard)
Bot->>DB: initialize wizard session
Bot->>Admin: prompt title (gwiz_title...)
Admin->>Bot: send title
Bot->>Bot: gwizSetWinners -> gwizSetSc -> gwizSetDuration
Bot->>Admin: show winner/SC/duration keyboards
Admin->>Bot: select winners/SC/duration
Bot->>Bot: gwizSetMinParts -> gwizSetSurface -> gwizToggleReqs
Bot->>Admin: show minParts/surface/reqs keyboards
Admin->>Bot: confirm summary (gwiz_reqs_done)
Bot->>Admin: show gwizSummaryText
Admin->>Bot: confirm create (gwiz_start_here)
Bot->>DB: createGiveaway(with new fields)
Bot->>Bot: announceGiveaway (builds messages, pin, DM broadcast)
Bot->>User: send announcement (Group and/or DM)
Bot->>Admin: confirm creation + admin stats update
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
|
CodeAnt AI finished reviewing your PR. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad977b6795
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ]), | ||
| ); | ||
| async function announceGiveaway(giveaway, botUsername) { | ||
| const titleLine = giveaway.title ? `\n📌 *${giveaway.title}*\n` : ''; |
There was a problem hiding this comment.
Escape giveaway title before Markdown rendering
The giveaway title is admin-provided free text but is interpolated directly into a Markdown message (*${giveaway.title}*), so titles containing Markdown metacharacters (for example _, [ or unmatched *) can make Telegram reject sendMessage with a parse error. In gw_create_yes, announceGiveaway is awaited before scheduleGiveawayReminders and resetGiveawayTimer, so this failure can leave a giveaway in running state without an announcement or end timer.
Useful? React with 👍 / 👎.
| const giveaway = giveawayStore.running.get(gwId); | ||
| if (!giveaway) { | ||
| await ctx.reply('⚠️ That giveaway has already ended or does not exist.'); | ||
| } else if (giveaway.participants.has(user.id)) { | ||
| await ctx.reply(`✅ You are already entered in Giveaway #${gwId}!`); |
There was a problem hiding this comment.
Enforce joinSurface for join_gw deep links
The /start join_gw_<id> path accepts DM joins for any running giveaway after eligibility checks, but it never validates giveaway.joinSurface. Because giveaway IDs are sequential, users can manually open join_gw_<id> links and enter giveaways configured as group-only, which bypasses the new surface restriction and can skew participant counts/minimum-participant behavior.
Useful? React with 👍 / 👎.
…leaderboards USER MAIN MENU (persistent): - Auto-deleting greeting (15s) on /start with first name - sendPersistentUserMenu: delete old + send new at bottom - 5 buttons: Play Now, Claim Bonus, My Profile, Giveaways, Help/Commands - Tooltips in button text; reappears after every major action - pmenu_* action handlers for all 5 buttons + pmenu_admin ADMIN MAIN MENU (persistent): - sendPersistentAdminMenu: delete old + send new at bottom - 6 buttons: System Status, Stats Dashboard, Start Giveaway, Active Giveaways, Tools, Back to User Menu - pamenu_* action handlers for all admin menu items - Admin Tools: force refresh menus, clear stuck flows, health check, show logs - Active Giveaways panel with per-giveaway End/Extend/Cancel/Participants controls SYSTEM REQUIREMENTS (new user constraints): - Removed /spin command (cosmetic, no longer supported) - Bot-only leaderboards: /leaderboard = top referrers, /leaderboard_weekly = most active (commands used) - No Runewager API, no Discord API, no external verification - commandsUsed counter on every getUser() call for activity tracking REFERRAL BOOST SYSTEM: - awardReferralProgress() now sets boostExpiresAt = now + 7 days on referrer - referralCount, lastReferralAt, boostExpiresAt fields in user schema - finalizeGiveaway: winners with active boost receive 2× SC (winner.awardedSc = base * 2) - Admin report shows boosted winners (🔥 boosted) - Stats text shows referrals in window + active boosted users count GIVEAWAY WIZARD (9 steps, reordered per spec): - Step 1: Name/Description (skip → default) - Step 2: SC Per Winner (was step 3) - Step 3: Number of Winners (was step 2) - Step 4: Countdown Duration - Step 5: Minimum Participants - Step 6: Join Targets — separate Group/DM toggle buttons (joinGroup, joinDm) - Step 7: Referral + Bonus Info (auto-include, informational proceed) - Step 8: Confirmation summary - Step 9: Launch/Post - gwizSummaryText updated to show joinGroup/joinDm surfaces and referral boost note - Surface stored as joinGroup+joinDm booleans; joinSurface computed at launch WIRING: - to_main_menu, menu command, menu_page_1 → sendPersistentUserMenu - open_admin_dashboard, admin_dashboard_action, admin_dash_page_1 → sendPersistentAdminMenu - /admin command in DM → sendPersistentAdminMenu - promo_confirm_claimed → sendPersistentUserMenu after completion - onboard_gcz_continue, sendGambleCodezVIPStep(sawGambleCodezStep) → sendPersistentUserMenu https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN
1. escapeMarkdownV2 helper + title escaping in announceGiveaway
- Added escapeMarkdownV2(text) to escape _ * ` [ ] in user-supplied text
- Applied to giveaway.title in announceGiveaway() to prevent Markdown injection
2. Enforce joinSurface on join paths
- DM deep-link (?start=join_gw_<id>): rejects with "can only be joined from
the group chat" when joinSurface === 'group'
- gw_join_<id> inline button: rejects DM users when joinSurface === 'group',
rejects group users when joinSurface === 'dm'
3. Remove duplicate admin_dashboard_action handler
- Removed admin_dashboard_action_v2_noop placeholder stub
- Single handler at line ~3708 is the only registration
4. Giveaway wizard edge case fixes
- awaitingAdminDecision flag prevents double-trigger of admin notification
when timer fires before admin has clicked extend/force-end
- gw_auto_extend clears awaitingAdminDecision before resetting timer so next
fire can re-evaluate
- pinnedMsgId set to null after unpinChatMessage in finalizeGiveaway
- DM broadcast already respects optOutBroadcasts and muted flags (verified)
- joinSurface enforcement covers group-only and DM-only cases end-to-end
5. CI/CD fixes
- ci.yml: Added BOT_TOKEN: ${{ secrets.BOT_TOKEN }} to smoke test step env so
require('./index.js') does not throw when BOT_TOKEN is needed at load time
- deploy.yml: Added BOT_TOKEN to global env block; expanded deploy job
environment from shorthand to name: production form per spec
https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN
1. escapeMarkdownV2 helper + title escaping in announceGiveaway
- Added escapeMarkdownV2(text) to escape _ * ` [ ] in user-supplied text
- Applied to giveaway.title in announceGiveaway() to prevent Markdown
injection
2. Enforce joinSurface on join paths
- DM deep-link (?start=join_gw_<id>): rejects with "can only be joined
from the group chat" when joinSurface === 'group'
- gw_join_<id> inline button: rejects DM users when joinSurface ===
'group', rejects group users when joinSurface === 'dm'
3. Remove duplicate admin_dashboard_action handler
- Removed admin_dashboard_action_v2_noop placeholder stub
- Single handler at line ~3708 is the only registration
4. Giveaway wizard edge case fixes
- awaitingAdminDecision flag prevents double-trigger of admin
notification when timer fires before admin has clicked extend/force-end
- gw_auto_extend clears awaitingAdminDecision before resetting timer so
next fire can re-evaluate
- pinnedMsgId set to null after unpinChatMessage in finalizeGiveaway
- DM broadcast already respects optOutBroadcasts and muted flags
(verified)
- joinSurface enforcement covers group-only and DM-only cases end-to-end
5. CI/CD fixes
- ci.yml: Added BOT_TOKEN: ${{ secrets.BOT_TOKEN }} to smoke test step
env so require('./index.js') does not throw when BOT_TOKEN is needed at
load time
- deploy.yml: Added BOT_TOKEN to global env block; expanded deploy job
environment from shorthand to name: production form per spec
https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN
User description
…mmands fix
ADMIN DASHBOARD
STATS / ANALYTICS LAYER
GIVEAWAY WIZARD — full inline-button flow
GIVEAWAY ENGINE UPGRADES
GROUP COMMANDS FIX (interrupted task)
https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN
Summary by CodeRabbit
New Features
Bug Fixes
CodeAnt-AI Description
Add rich admin dashboard, time-window stats, and a full inline giveaway wizard
What Changed
Impact
✅ Clearer admin stats and uptime✅ Shorter giveaway creation for admins✅ Fewer missed giveaway join opportunities (DM links + broadcasts)💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.