-
Notifications
You must be signed in to change notification settings - Fork 0
Promo Manager, Content Drops, Announcements & admin UX refactor #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,8 +34,8 @@ const LINKS = { | |
| rwDiscordJoin: process.env.RW_DISCORD_JOIN || 'https://discord.gg/runewagers', | ||
| rwDiscordLink: process.env.RW_DISCORD_LINK || 'https://discord.com/channels/1100486422395355197/1249181934811349052', | ||
| // Telegram channels/groups — external links | ||
| gczChannel: 'https://t.me/GambleCodezDrops', | ||
| gczGroup: 'https://t.me/GambleCodezPrizeHub', | ||
| gczChannel: process.env.TELEGRAM_CHANNEL_LINK || 'https://t.me/GambleCodezDrops', | ||
| gczGroup: process.env.TELEGRAM_GROUP_LINK || 'https://t.me/GambleCodezPrizeHub', | ||
| // Mini App links — always open INSIDE Telegram | ||
| miniAppPlay: process.env.MINI_APP_PLAY_URL || 'https://t.me/RuneWager_bot/Play', | ||
| miniAppProfile: process.env.MINI_APP_PROFILE_URL || 'https://t.me/RuneWager_bot/profile', | ||
|
|
@@ -51,10 +51,12 @@ const PROMO_REQUIREMENT = { | |
| }; | ||
|
|
||
| // Channel username (or chat_id) for admin announcements via /announce | ||
| const ANNOUNCE_CHANNEL = process.env.ANNOUNCE_CHANNEL || '@GambleCodezDrops'; | ||
| const TELEGRAM_CHANNEL_ID = process.env.TELEGRAM_CHANNEL_ID || process.env.ANNOUNCE_CHANNEL || ''; | ||
| const TELEGRAM_GROUP_ID = process.env.TELEGRAM_GROUP_ID || ''; | ||
| const ANNOUNCE_CHANNEL = TELEGRAM_CHANNEL_ID; | ||
|
|
||
| // Group username (or chat_id) for automatic silent tips | ||
| const TIPS_GROUP = process.env.TIPS_GROUP || '@GambleCodezPrizeHub'; | ||
| // Group chat_id for automatic Content Drops | ||
| const TIPS_GROUP = process.env.TIPS_GROUP || TELEGRAM_GROUP_ID || ''; | ||
|
|
||
| // ── Images ───────────────────────────────────────────────────────────────── | ||
| // Images live in /images/ next to index.js. | ||
|
|
@@ -881,6 +883,7 @@ function getUser(ctx) { | |
| if (user.supportTicket === undefined) user.supportTicket = null; | ||
| if (!user.claimedPromoCodes) user.claimedPromoCodes = []; | ||
| if (!Array.isArray(user.claimedPromoCodes)) user.claimedPromoCodes = []; | ||
| if (user.hasClaimedNewUserPromo === undefined) user.hasClaimedNewUserPromo = false; | ||
| if (!user.claimedPromoAt) user.claimedPromoAt = 0; | ||
| if (!user.lastAnyPromoClaimAt) user.lastAnyPromoClaimAt = 0; | ||
| if (user.claimedPromo && !user.claimedPromoAt) user.claimedPromoAt = Date.now(); | ||
|
|
@@ -1710,7 +1713,7 @@ async function evaluatePromoEligibility(user, promo, { logFailure = false } = {} | |
| const reasons = []; | ||
| const userId = Number(user.id); | ||
| const anyRedeemed = hasRedeemedAnyPromo(userId); | ||
| if (promo.new_user_only && anyRedeemed) reasons.push('New-user promo only. You already redeemed a promo.'); | ||
| if (promo.new_user_only && (anyRedeemed || user.hasClaimedNewUserPromo)) reasons.push('New-user promo only. You already redeemed a promo.'); | ||
| if (promo.existing_user_only && !promo.new_user_only && !anyRedeemed) reasons.push('This promo is only for existing promo users.'); | ||
| if (promo.requirement_type === PROMO_REQUIREMENT.EXISTING_USER_WITH_WAGER) { | ||
| const wager = await getRollingWagerForUser(user, promo.required_wager_days || 7); | ||
|
|
@@ -1893,7 +1896,7 @@ function adminPromoToolsKeyboard() { | |
| [Markup.button.callback('👀 Preview Promo', 'admin_pm_preview')], | ||
| [Markup.button.callback('🧾 Approval Queue', 'admin_pm_queue')], | ||
| [Markup.button.callback('📣 Announcements', 'admin_cmd_announce_start')], | ||
| [Markup.button.callback('💡 Tips Manager', 'admin_cmd_tips_dashboard')], | ||
| [Markup.button.callback('💡 Content Drops', 'admin_cmd_tips_dashboard')], | ||
| [Markup.button.callback('⬅️ Admin Dashboard', 'open_admin_dashboard')], | ||
| ]); | ||
| } | ||
|
|
@@ -1917,7 +1920,7 @@ function adminSystemToolsKeyboard(user = null) { | |
| [Markup.button.callback('🩺 Health Check', 'admin_cmd_health')], | ||
| [Markup.button.callback('📦 Bot Version', 'admin_cmd_version')], | ||
| [Markup.button.callback('🤖 Verify Bot Setup', 'admin_cmd_verify_setup')], | ||
| [Markup.button.callback('🧪 Tip Test (4h broadcast)', 'admin_cmd_tiptest')], | ||
| [Markup.button.callback('🧪 Drop Test (4h broadcast)', 'admin_cmd_tiptest')], | ||
| [Markup.button.callback('💾 Backup State', 'admin_backup_action')], | ||
| [Markup.button.callback(toggleLabel, 'admin_cmd_mode_toggle')], | ||
| [Markup.button.callback('⬅️ Admin Dashboard', 'open_admin_dashboard')], | ||
|
|
@@ -2152,7 +2155,7 @@ function adminMainMenuKeyboard(user) { | |
| ], | ||
| [ | ||
| Markup.button.callback('🎛 Promo Manager', 'admin_cat_promo'), | ||
| Markup.button.callback('📣 Announce & Tips', 'admin_cmd_announce_start'), | ||
| Markup.button.callback('📣 Broadcasts & Drops', 'admin_cmd_announce_start'), | ||
| ], | ||
| [ | ||
| Markup.button.callback('⚙️ System & Health', 'admin_cat_system'), | ||
|
|
@@ -3308,14 +3311,14 @@ function buildHelpPages(user) { | |
| '/unapprove_group <chat_id> — Remove group from whitelist', | ||
| '/list_groups — List all approved groups', | ||
| '', | ||
| '💡 TIPS MANAGEMENT', | ||
| '/tips (or /t, /tp) — Post a tip to the group', | ||
| '💡 CONTENT DROPS MANAGEMENT', | ||
| '/tips (or /t, /tp) — Post a content drop to the group', | ||
| '/tiplist — List all tips', | ||
| '/tipadd — Add a new tip', | ||
| '/tipremove — Remove a tip', | ||
| '/tipedit — Edit an existing tip', | ||
| '/tiptoggle — Enable/disable tips feature', | ||
| '/tipsettings — Configure tips settings', | ||
| '/tipsettings — Configure content drop settings', | ||
| '', | ||
| '🐞 BUG REPORTS', | ||
| '/bugreports — View all open bug reports', | ||
|
|
@@ -5195,10 +5198,19 @@ bot.action('menu_claim_bonus', async (ctx) => { | |
| } | ||
|
|
||
| const rows = eligiblePromos.map((promo) => [Markup.button.callback(`🎁 ${promo.name}`, `promo_open_${promo.promo_id}`)]); | ||
| rows.push([Markup.button.callback('✅ I claimed successfully', 'promo_user_claimed_successfully')]); | ||
| rows.push([Markup.button.callback('⬅️ Main Menu', 'to_main_menu')]); | ||
| await ctx.reply(`🎁 *Promo Menu*\n\nOnly promos you are eligible for are shown below.`, { parse_mode: 'Markdown', ...Markup.inlineKeyboard(rows) }); | ||
| }); | ||
|
|
||
| bot.action('promo_user_claimed_successfully', async (ctx) => { | ||
| const user = getUser(ctx); | ||
| user.hasClaimedNewUserPromo = true; | ||
| user.lastAnyPromoClaimAt = Date.now(); | ||
| await ctx.answerCbQuery('Saved'); | ||
| await ctx.reply('✅ Got it — you are marked as already claimed. New-user-only promos are now hidden for your account.'); | ||
|
Comment on lines
+5206
to
+5211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Marking This handler mutates |
||
| }); | ||
|
|
||
| bot.action(/promo_open_(\d+)/, async (ctx) => { | ||
| const user = getUser(ctx); | ||
| const promoId = Number(ctx.match[1]); | ||
|
|
@@ -7131,7 +7143,7 @@ C = Existing User With Wager Requirement`); | |
| if (action.type === 'await_tip_add_text') { | ||
| if (!requireAdmin(ctx)) return; | ||
| if (!text) { | ||
| await ctx.reply('Tip text cannot be empty. Please send the tip you want to add.'); | ||
| await ctx.reply('Drop text cannot be empty. Please send the tip you want to add.'); | ||
| return; | ||
| } | ||
| const newTip = { id: tipsStore.nextTipId, text, enabled: true }; | ||
|
|
@@ -7140,15 +7152,15 @@ C = Existing User With Wager Requirement`); | |
| user.pendingAction = null; | ||
| persistRuntimeState(); | ||
| saveHelpfulMessages(); | ||
| await ctx.reply(`Added as Tip #${newTip.id}.`); | ||
| await ctx.reply(`Added as Drop #${newTip.id}.`); | ||
| return; | ||
| } | ||
|
|
||
| // Tips system: await edited tip text | ||
| if (action.type === 'await_tip_edit_text') { | ||
| if (!requireAdmin(ctx)) return; | ||
| if (!text) { | ||
| await ctx.reply('Tip text cannot be empty. Please send the updated tip text.'); | ||
| await ctx.reply('Drop text cannot be empty. Please send the updated tip text.'); | ||
| return; | ||
| } | ||
| const tipId = action.data && action.data.tipId; | ||
|
|
@@ -7162,7 +7174,7 @@ C = Existing User With Wager Requirement`); | |
| user.pendingAction = null; | ||
| persistRuntimeState(); | ||
| saveHelpfulMessages(); | ||
| await ctx.reply(`Tip #${tipId} updated.`); | ||
| await ctx.reply(`Drop #${tipId} updated.`); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -7395,7 +7407,7 @@ bot.action('announce_send_channel', async (ctx) => { | |
| }); | ||
|
|
||
| // ========================= | ||
| // Tips System — scheduler + admin commands + callbacks | ||
| // Content Drops System — scheduler + admin commands + callbacks | ||
| // ========================= | ||
|
|
||
| async function resolveTipTargetChatId(rawTarget) { | ||
|
|
@@ -7454,7 +7466,7 @@ function startTipsScheduler() { | |
| }, ms); | ||
| } | ||
|
|
||
| /** Build the Tips Manager dashboard keyboard */ | ||
| /** Build the Content Drops Manager dashboard keyboard */ | ||
| function tipsDashboardKeyboard() { | ||
| return Markup.inlineKeyboard([ | ||
| [Markup.button.callback('➕ Add Tip', 'tips_cmd_add'), Markup.button.callback('✏️ Edit Tip', 'tips_cmd_edit')], | ||
|
|
@@ -7479,12 +7491,12 @@ function tipSelectKeyboard(action) { | |
| return Markup.inlineKeyboard(rows); | ||
| } | ||
|
|
||
| /** Send (or re-send) the Tips Manager dashboard */ | ||
| /** Send (or re-send) the Content Drops Manager dashboard */ | ||
| async function sendTipsDashboard(ctx) { | ||
| const total = tipsStore.tips.length; | ||
| const enabled = tipsStore.tips.filter((t) => t.enabled).length; | ||
| const status = tipsStore.systemEnabled ? '🟢 Enabled' : '🔴 Disabled'; | ||
| const text = `📝 *Tips Manager*\n\nStatus: ${status}\nTotal Tips: ${total} (${enabled} active)\nInterval: every ${tipsStore.intervalHours}h\nTarget: ${tipsStore.targetGroup}`; | ||
| const text = `📝 *Content Drops Manager*\n\nStatus: ${status}\nTotal Drops: ${total} (${enabled} active)\nInterval: every ${tipsStore.intervalHours}h\nTarget: ${tipsStore.targetGroup}`; | ||
| await replaceCallbackPanel(ctx, text, { parse_mode: 'Markdown', ...tipsDashboardKeyboard() }); | ||
| } | ||
|
|
||
|
|
@@ -7501,7 +7513,7 @@ bot.command('tp', handleTipsCommand); | |
|
|
||
| bot.command('tiplist', async (ctx) => { | ||
| if (!requireAdmin(ctx)) return; | ||
| const lines = ['📋 *Current Tips:*\n']; | ||
| const lines = ['📋 *Current Content Drops:*\n']; | ||
| for (const [idx, tip] of tipsStore.tips.entries()) { | ||
| const preview = tip.text.replace(/\*/g, '').slice(0, 80); | ||
| lines.push(`${idx + 1}. #${tip.id} ${tip.enabled ? '✅' : '🔇'} ${preview}${tip.text.length > 80 ? '…' : ''}`); | ||
|
|
@@ -7578,7 +7590,7 @@ bot.command('tiptoggle', async (ctx) => { | |
| tipsStore.systemEnabled = !tipsStore.systemEnabled; | ||
| persistRuntimeState(); | ||
| saveHelpfulMessages(); | ||
| const status = tipsStore.systemEnabled ? '🟢 Tips System Enabled' : '🔴 Tips System Disabled'; | ||
| const status = tipsStore.systemEnabled ? '🟢 Content Drops System Enabled' : '🔴 Content Drops System Disabled'; | ||
| await ctx.reply(status); | ||
| }); | ||
|
|
||
|
|
@@ -7604,7 +7616,7 @@ Tip: run /register_chat and forward a message from the target group/channel.`); | |
|
|
||
| bot.command('tipsettings', async (ctx) => { | ||
| if (!requireAdmin(ctx)) return; | ||
| const text = `⚙️ *Tips Settings*\n\nInterval: every ${tipsStore.intervalHours} hours\nSilent mode: Always ON\nTarget group: ${tipsStore.targetGroup}\n\nTo change the interval, reply with the number of hours (e.g. \`4\`).`; | ||
| const text = `⚙️ *Content Drops Settings*\n\nInterval: every ${tipsStore.intervalHours} hours\nSilent mode: Always ON\nTarget group: ${tipsStore.targetGroup}\n\nTo change the interval, reply with the number of hours (e.g. \`4\`).`; | ||
| const user = getUser(ctx); | ||
| clearPendingAction(user); | ||
| user.pendingAction = { type: 'await_tip_settings_interval' }; | ||
|
|
@@ -7642,15 +7654,15 @@ bot.action('tips_cmd_toggle', async (ctx) => { | |
| tipsStore.systemEnabled = !tipsStore.systemEnabled; | ||
| persistRuntimeState(); | ||
| saveHelpfulMessages(); | ||
| const status = tipsStore.systemEnabled ? '🟢 Tips System Enabled' : '🔴 Tips System Disabled'; | ||
| const status = tipsStore.systemEnabled ? '🟢 Content Drops System Enabled' : '🔴 Content Drops System Disabled'; | ||
| await ctx.reply(status); | ||
| await sendTipsDashboard(ctx); | ||
| }); | ||
|
|
||
| bot.action('tips_cmd_list', async (ctx) => { | ||
| if (!requireAdmin(ctx)) return; | ||
| await ctx.answerCbQuery(); | ||
| const lines = ['📋 *Current Tips:*\n']; | ||
| const lines = ['📋 *Current Content Drops:*\n']; | ||
| for (const [idx, tip] of tipsStore.tips.entries()) { | ||
| const preview = tip.text.replace(/\*/g, '').slice(0, 80); | ||
| lines.push(`${idx + 1}. #${tip.id} ${tip.enabled ? '✅' : '🔇'} ${preview}${tip.text.length > 80 ? '…' : ''}`); | ||
|
|
@@ -7695,7 +7707,7 @@ bot.action('tips_cmd_settings', async (ctx) => { | |
| const user = getUser(ctx); | ||
| clearPendingAction(user); | ||
| user.pendingAction = { type: 'await_tip_settings_interval' }; | ||
| const text = `⚙️ *Tips Settings*\n\nInterval: every ${tipsStore.intervalHours} hours\nSilent mode: Always ON\nTarget group: ${tipsStore.targetGroup}\n\nSend the new interval in hours (e.g. \`4\`).`; | ||
| const text = `⚙️ *Content Drops Settings*\n\nInterval: every ${tipsStore.intervalHours} hours\nSilent mode: Always ON\nTarget group: ${tipsStore.targetGroup}\n\nSend the new interval in hours (e.g. \`4\`).`; | ||
| await ctx.reply(text, { parse_mode: 'Markdown' }); | ||
| }); | ||
|
|
||
|
|
@@ -7717,7 +7729,7 @@ bot.action(/^tip_remove_(\d+)$/, async (ctx) => { | |
| tipsStore.tips.splice(idx, 1); | ||
| persistRuntimeState(); | ||
| saveHelpfulMessages(); | ||
| await ctx.reply(`Tip #${tipId} removed.`); | ||
| await ctx.reply(`Drop #${tipId} removed.`); | ||
| }); | ||
|
|
||
| // tip_edit_select_<id> | ||
|
|
@@ -7730,7 +7742,7 @@ bot.action(/^tip_edit_select_(\d+)$/, async (ctx) => { | |
| const user = getUser(ctx); | ||
| clearPendingAction(user); | ||
| user.pendingAction = { type: 'await_tip_edit_text', data: { tipId } }; | ||
| await ctx.reply(`Send the updated text for Tip #${tipId}.\n\nCurrent text:\n${tip.text}`); | ||
| await ctx.reply(`Send the updated text for Drop #${tipId}.\n\nCurrent text:\n${tip.text}`); | ||
| }); | ||
|
|
||
| // tip_toggle_<id> (per-tip enable/disable, accessible from tiplist) | ||
|
|
@@ -7743,7 +7755,7 @@ bot.action(/^tip_toggle_(\d+)$/, async (ctx) => { | |
| tip.enabled = !tip.enabled; | ||
| persistRuntimeState(); | ||
| saveHelpfulMessages(); | ||
| await ctx.reply(`Tip #${tipId} is now ${tip.enabled ? '✅ enabled' : '🔇 disabled'}.`); | ||
| await ctx.reply(`Drop #${tipId} is now ${tip.enabled ? '✅ enabled' : '🔇 disabled'}.`); | ||
| }); | ||
|
|
||
| // ========================= | ||
|
|
@@ -9373,7 +9385,7 @@ bot.command('verify_bot_setup', safeAdminHandler('verify_bot_setup', { usage: '/ | |
| `Can read all group messages: ${me.can_read_all_group_messages ? 'YES' : 'NO'} (BotFather privacy mode affects this)`, | ||
| `Supports inline queries: ${me.supports_inline_queries ? 'YES' : 'NO'}`, | ||
| `Announce channel target: ${broadcastConfigStore.targetChannel}`, | ||
| `Tips/broadcast group target: ${broadcastConfigStore.targetGroup}`, | ||
| `Content Drops/broadcast group target: ${broadcastConfigStore.targetGroup}`, | ||
| `Approved broadcast/group chats tracked: ${approvedGroupsStore.size}`, | ||
| ]; | ||
| await ctx.reply(`🤖 *Bot Setup Verification*\n\n${checks.join('\n')}\n\nIf group visibility is limited, disable privacy mode in @BotFather and ensure admin rights in each target chat.`, { parse_mode: 'Markdown' }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Empty defaults for TELEGRAM_CHANNEL_ID / TIPS_GROUP may silently disable broadcasts/drops.
These env vars used to have non-empty defaults (
@GambleCodezDrops,@GambleCodezPrizeHub), but now can be''if unset. If callers don’t explicitly check for non-empty IDs, broadcasts/drops may silently no-op instead of failing visibly. Consider either failing fast when these are missing (throw / prominent log) or retaining a non-empty default to avoid silent misconfiguration.