diff --git a/index.js b/index.js index 10db4d2..f6077ec 100644 --- a/index.js +++ b/index.js @@ -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 — 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.'); +}); + 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,7 +7152,7 @@ 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; } @@ -7148,7 +7160,7 @@ C = Existing User With Wager Requirement`); 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,7 +7654,7 @@ 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); }); @@ -7650,7 +7662,7 @@ bot.action('tips_cmd_toggle', async (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_ @@ -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_ (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' });