From 1b6e28549853966c5f4e048fbfe549d250281c80 Mon Sep 17 00:00:00 2001 From: Cordtus Date: Sun, 25 May 2025 14:39:41 -0600 Subject: [PATCH 1/3] cleanup menu and messages --- bot.js | 258 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 130 insertions(+), 128 deletions(-) diff --git a/bot.js b/bot.js index 6e618fd..a6afc5a 100644 --- a/bot.js +++ b/bot.js @@ -1,4 +1,4 @@ -// bot.js - Fixed imports +// bot.js\ import { Telegraf } from 'telegraf'; import dotenv from 'dotenv'; @@ -219,8 +219,8 @@ async function isBanned(username, firstName, lastName, groupId) { if (username) { const usernameMatch = await matchesPattern(pattern.raw, username.toLowerCase()); if (usernameMatch) { - incrementHitCounter(groupId, pattern.raw); // <--- ADD - console.log(`[BAN_CHECK] ✅ BANNED - Username "${username}" matched pattern "${pattern.raw}"`); + incrementHitCounter(groupId, pattern.raw); + console.log(`[BAN_CHECK] BANNED - Username "${username}" matched pattern "${pattern.raw}"`); return true; } } @@ -239,8 +239,8 @@ async function isBanned(username, firstName, lastName, groupId) { for (const variation of variations) { const nameMatch = await matchesPattern(pattern.raw, variation.toLowerCase()); if (nameMatch) { - incrementHitCounter(groupId, pattern.raw); // <--- ADD - console.log(`[BAN_CHECK] ✅ BANNED - Display name "${variation}" matched pattern "${pattern.raw}"`); + incrementHitCounter(groupId, pattern.raw); + console.log(`[BAN_CHECK] BANNED - Display name "${variation}" matched pattern "${pattern.raw}"`); return true; } } @@ -295,7 +295,7 @@ async function loadGroupPatterns(groupId) { // Use security module to validate and create pattern objects const patternObj = createPatternObject(pt); validatedPatterns.push(patternObj); - console.log(`[LOAD] ✅ Pattern ${i + 1}: "${pt}" - validated`); + console.log(`[LOAD] Pattern ${i + 1}: "${pt}" - validated`); // Safety limit if (validatedPatterns.length >= 100) { @@ -303,7 +303,7 @@ async function loadGroupPatterns(groupId) { break; } } catch (err) { - console.warn(`[LOAD] ❌ Pattern ${i + 1}: "${pt}" - skipped: ${err.message}`); + console.warn(`[LOAD] Pattern ${i + 1}: "${pt}" - skipped: ${err.message}`); } } @@ -328,9 +328,9 @@ async function saveGroupPatterns(groupId, patterns) { try { const filePath = await getGroupPatternFilePath(groupId); await fs.writeFile(filePath, content); - console.log(`[SAVE] ✅ Successfully saved patterns to ${filePath}`); + console.log(`[SAVE] Successfully saved patterns to ${filePath}`); } catch (err) { - console.error(`[SAVE] ❌ Error writing patterns for group ${groupId}:`, err); + console.error(`[SAVE] Error writing patterns for group ${groupId}:`, err); } } @@ -398,10 +398,10 @@ async function saveSettings() { try { await fs.writeFile(SETTINGS_FILE, JSON.stringify(settings, null, 2)); - console.log(`[SETTINGS] ✅ Settings saved successfully`); + console.log(`[SETTINGS] Settings saved successfully`); return true; } catch (err) { - console.error(`[SETTINGS] ❌ Error writing settings:`, err); + console.error(`[SETTINGS] Error writing settings:`, err); return false; } } @@ -470,10 +470,10 @@ async function takePunishmentAction(ctx, userId, username, chatId) { } const message = getRandomMessage(userId, isBan); await ctx.reply(message); - console.log(`[PUNISH] ✅ ${isBan ? 'Banned' : 'Kicked'} user ${userId} successfully`); + console.log(`[PUNISH] ${isBan ? 'Banned' : 'Kicked'} user ${userId} successfully`); return true; } catch (error) { - console.error(`[PUNISH] ❌ Failed to ${isBan ? 'ban' : 'kick'} user ${userId}:`, error); + console.error(`[PUNISH] Failed to ${isBan ? 'ban' : 'kick'} user ${userId}:`, error); return false; } } @@ -575,7 +575,7 @@ function monitorNewUser(chatId, user) { const action = getGroupAction(chatId); const isBan = action === 'ban'; - console.log(`[MONITOR] 🚫 User ${user.id} matched pattern - taking action: ${action.toUpperCase()}`); + console.log(`[MONITOR] User ${user.id} matched pattern - taking action: ${action.toUpperCase()}`); if (isBan) { await bot.telegram.banChatMember(chatId, user.id); @@ -644,17 +644,17 @@ async function showMainMenu(ctx) { const patterns = groupPatterns.get(selectedGroupId) || []; const groupAction = getGroupAction(selectedGroupId); - let text = `🛡️ Admin Menu\n`; + let text = `Admin Menu\n`; if (isGlobalAdmin) { - text += `👑 Global Admin Access\n`; + text += `Global Admin Access\n`; } else { - text += `👮 Group Admin Access\n`; + text += `Group Admin Access\n`; } - text += `📍 Selected Group: ${selectedGroupId}\n`; - text += `📋 Patterns: ${patterns.length}/100\n`; - text += `⚔️ Action: ${groupAction.toUpperCase()}\n\n`; + text += `Selected Group: ${selectedGroupId}\n`; + text += `Patterns: ${patterns.length}/100\n`; + text += `Action: ${groupAction.toUpperCase()}\n\n`; text += `Use the buttons below to manage filters.`; // Create group selection buttons (only for groups user can manage) @@ -662,7 +662,7 @@ async function showMainMenu(ctx) { if (manageableGroups.length > 1) { const groupButtons = manageableGroups.map(groupId => ({ - text: `${groupId === selectedGroupId ? '✅ ' : ''}Group ${groupId} (${getGroupAction(groupId).toUpperCase()})`, + text: `${groupId === selectedGroupId ? 'Selected: ' : ''}Group ${groupId} (${getGroupAction(groupId).toUpperCase()})`, callback_data: `select_group_${groupId}` })); @@ -677,16 +677,16 @@ async function showMainMenu(ctx) { // Add management buttons keyboard.reply_markup.inline_keyboard.push( [ - { text: '➕ Add Filter', callback_data: 'menu_addFilter' }, - { text: '➖ Remove Filter', callback_data: 'menu_removeFilter' } + { text: 'Add Filter', callback_data: 'menu_addFilter' }, + { text: 'Remove Filter', callback_data: 'menu_removeFilter' } ], [ - { text: '📋 List Filters', callback_data: 'menu_listFilters' }, - { text: '📥 Browse & Copy', callback_data: 'menu_browsePatterns' } + { text: 'List Filters', callback_data: 'menu_listFilters' }, + { text: 'Browse & Copy', callback_data: 'menu_browsePatterns' } ], [ - { text: `⚔️ Action: ${groupAction.toUpperCase()}`, callback_data: 'menu_toggleAction' }, - { text: '❓ Pattern Help', callback_data: 'menu_patternHelp' } + { text: `Action: ${groupAction.toUpperCase()}`, callback_data: 'menu_toggleAction' }, + { text: 'Pattern Help', callback_data: 'menu_patternHelp' } ] ); @@ -731,18 +731,18 @@ async function showPatternBrowsingMenu(ctx) { if (allPatterns.size === 0) { await showOrEditMenu(ctx, - `📥 Browse & Copy Patterns\n\nNo patterns found in any groups.`, + `Browse & Copy Patterns\n\nNo patterns found in any groups.`, { parse_mode: 'HTML', reply_markup: { - inline_keyboard: [[{ text: '⬅️ Back to Menu', callback_data: 'menu_back' }]] + inline_keyboard: [[{ text: 'Back to Menu', callback_data: 'menu_back' }]] } } ); return; } - let text = `📥 Browse & Copy Patterns\n`; + let text = `Browse & Copy Patterns\n`; text += `Your Selected Group: ${currentGroupId}\n\n`; text += `Select any group to view and copy patterns:\n\n`; @@ -751,7 +751,7 @@ async function showPatternBrowsingMenu(ctx) { // Add buttons for ALL groups that have patterns (including current group for viewing) for (const [groupId, patterns] of allPatterns) { const buttonText = groupId === currentGroupId - ? `📍 Group ${groupId} (${patterns.length} patterns) - YOUR GROUP` + ? `Group ${groupId} (${patterns.length} patterns) - YOUR GROUP` : `Group ${groupId} (${patterns.length} patterns)`; keyboard.reply_markup.inline_keyboard.push([{ @@ -761,7 +761,7 @@ async function showPatternBrowsingMenu(ctx) { // Add sample patterns to the text if (groupId === currentGroupId) { - text += `📍 Group ${groupId} (Your Group): ${patterns.length} patterns\n`; + text += `Group ${groupId} (Your Group): ${patterns.length} patterns\n`; } else { text += `Group ${groupId}: ${patterns.length} patterns\n`; } @@ -771,11 +771,11 @@ async function showPatternBrowsingMenu(ctx) { // If no other groups have patterns, show a note if (allPatterns.size === 1 && allPatterns.has(currentGroupId)) { - text += `💡 Only your group has patterns. Other groups will appear here once they add patterns.\n\n`; + text += `Only your group has patterns. Other groups will appear here once they add patterns.\n\n`; } keyboard.reply_markup.inline_keyboard.push([ - { text: '⬅️ Back to Menu', callback_data: 'menu_back' } + { text: 'Back to Menu', callback_data: 'menu_back' } ]); await showOrEditMenu(ctx, text, { @@ -795,11 +795,11 @@ async function showGroupPatternsForCopy(ctx, sourceGroupId) { if (sourcePatterns.length === 0) { await showOrEditMenu(ctx, - `📥 Group ${sourceGroupId} Patterns\n\nNo patterns found in this group.`, + `Group ${sourceGroupId} Patterns\n\nNo patterns found in this group.`, { parse_mode: 'HTML', reply_markup: { - inline_keyboard: [[{ text: '⬅️ Back', callback_data: 'menu_browsePatterns' }]] + inline_keyboard: [[{ text: 'Back', callback_data: 'menu_browsePatterns' }]] } } ); @@ -809,10 +809,10 @@ async function showGroupPatternsForCopy(ctx, sourceGroupId) { const isOwnGroup = sourceGroupId === targetGroupId; const canManageTarget = canManageGroup(adminId, targetGroupId); - let text = `📥 Group ${sourceGroupId} Patterns\n`; + let text = `Group ${sourceGroupId} Patterns\n`; if (isOwnGroup) { - text += `📍 This is your selected group\n\n`; + text += `This is your selected group\n\n`; } else { text += `To: Group ${targetGroupId} ${canManageTarget ? '✅' : '❌'}\n\n`; if (!canManageTarget) { @@ -834,17 +834,17 @@ async function showGroupPatternsForCopy(ctx, sourceGroupId) { if (!isOwnGroup && canManageTarget) { text += `\nChoose what to copy:`; keyboard.reply_markup.inline_keyboard.push([ - { text: '📋 Copy All', callback_data: `copy_all_${sourceGroupId}` }, - { text: '🎯 Select Specific', callback_data: `copy_select_${sourceGroupId}` } + { text: 'Copy All', callback_data: `copy_all_${sourceGroupId}` }, + { text: 'Select Specific', callback_data: `copy_select_${sourceGroupId}` } ]); } else if (isOwnGroup) { - text += `\n💡 This is your group. Use the main menu to manage these patterns.`; + text += `\nThis is your group. Use the main menu to manage these patterns.`; } else { - text += `\n💡 You can view these patterns but cannot copy them to Group ${targetGroupId}.`; + text += `\nYou can view these patterns but cannot copy them to Group ${targetGroupId}.`; } keyboard.reply_markup.inline_keyboard.push([ - { text: '⬅️ Back to Browse', callback_data: 'menu_browsePatterns' } + { text: 'Back to Browse', callback_data: 'menu_browsePatterns' } ]); await showOrEditMenu(ctx, text, { @@ -918,30 +918,31 @@ async function promptForPattern(ctx, actionLabel) { const groupId = session.selectedGroupId; const promptText = - `✨ Add Pattern for Group ${groupId} ✨\n\n` + - - `📝 Pattern Types:\n\n` + - - `1. Simple Text - Case-insensitive match\n` + - ` • spam matches "SPAM", "Spam", "spam"\n\n` + - - `2. Wildcards\n` + - ` • * = any characters\n` + - ` • ? = single character\n` + - ` • spam* matches "spam123", "spammer", etc.\n` + - ` • *bot* matches "testbot", "bot_user", etc.\n` + - ` • test? matches "test1", "testa", etc.\n\n` + - - `3. Regular Expressions - Advanced patterns\n` + - ` • Format: /pattern/flags\n` + - ` • /^spam.*$/i starts with "spam"\n` + - ` • /\\d{5,}/ 5+ digits in a row\n` + - ` • /ch[!1i]ld/i "child", "ch!ld", "ch1ld"\n\n` + - - `💡 Examples:\n` + - `• ranger - blocks "ranger"\n` + - `• *porn* - blocks anything with "porn"\n` + - `• /❤.*ch.ld.*p.rn/i - blocks heart+variations\n\n` + + `Add Pattern for Group ${groupId}\n\n` + + + `Pattern Types:\n\n` + + + `1. Simple Text - Case-insensitive substring match\n` + + ` • spam matches "SPAM", "Spam", "spammer"\n\n` + + + `2. Wildcards\n` + + ` • * = any characters\n` + + ` • ? = single character\n` + + ` • spam* matches "spam", "spammer", "spam123"\n` + + ` • *bot matches "mybot", "testbot", "123bot"\n` + + ` • *bad* matches "baduser", "this_is_bad"\n` + + ` • test? matches "test1", "testa", "tests"\n\n` + + + `3. Regular Expressions - Advanced patterns\n` + + ` • Format: /pattern/flags\n` + + ` • /^spam.*$/i starts with "spam"\n` + + ` • /\\d{5,}/ 5+ digits in a row\n` + + ` • /ch[!1i]ld/i "child", "ch!ld", "ch1ld"\n\n` + + + `Examples:\n` + + `• ranger - blocks substring "ranger"\n` + + `• *porn* - blocks anything containing "porn"\n` + + `• /heart.*ch.ld.*p.rn/i - blocks heart+variations\n\n` + `Send your pattern or /cancel to abort.`; @@ -1005,7 +1006,7 @@ bot.on('text', async (ctx, next) => { patterns.push(patternObj); groupPatterns.set(groupId, patterns); await saveGroupPatterns(groupId, patterns); - console.log(`[ADMIN_TEXT] ✅ Added pattern "${patternObj.raw}" to group ${groupId}`); + console.log(`[ADMIN_TEXT] Added pattern "${patternObj.raw}" to group ${groupId}`); await ctx.reply(`Filter "${patternObj.raw}" added to Group ${groupId}.`); } } catch (e) { @@ -1019,7 +1020,7 @@ bot.on('text', async (ctx, next) => { patterns.splice(index, 1); groupPatterns.set(groupId, patterns); await saveGroupPatterns(groupId, patterns); - console.log(`[ADMIN_TEXT] ✅ Removed pattern "${input}" from group ${groupId}`); + console.log(`[ADMIN_TEXT] Removed pattern "${input}" from group ${groupId}`); await ctx.reply(`Filter "${input}" removed from Group ${groupId}.`); } else { console.log(`[ADMIN_TEXT] Pattern not found: "${input}"`); @@ -1052,9 +1053,9 @@ bot.on('text', async (ctx, next) => { const result = await copyPatternsToGroup(sourceGroupId, groupId, patternIndices); if (result.success) { - await ctx.reply(`✅ ${result.message}`); + await ctx.reply(`${result.message}`); } else { - await ctx.reply(`❌ ${result.message}`); + await ctx.reply(`${result.message}`); } } @@ -1071,7 +1072,7 @@ bot.on('text', async (ctx, next) => { } }); -// Enhanced callback handler with browsing functionality (FIXED - no duplicates) +// callback handler bot.on('callback_query', async (ctx) => { if (ctx.chat?.type !== 'private' || !(await isAuthorized(ctx))) { return ctx.answerCbQuery('Not authorized.'); @@ -1133,7 +1134,7 @@ bot.on('callback_query', async (ctx) => { if (result.success) { await ctx.answerCbQuery(`Success! ${result.message}`); // Update the browsing menu to show the result - let resultText = `✅ Copy Complete!\n\n`; + let resultText = `Copy Complete!\n\n`; resultText += `From: Group ${sourceGroupId}\n`; resultText += `To: Group ${targetGroupId}\n\n`; resultText += `${result.message}\n\n`; @@ -1142,7 +1143,7 @@ bot.on('callback_query', async (ctx) => { await showOrEditMenu(ctx, resultText, { parse_mode: 'HTML', reply_markup: { - inline_keyboard: [[{ text: '🏠 Back to Main Menu', callback_data: 'menu_back' }]] + inline_keyboard: [[{ text: 'Back to Main Menu', callback_data: 'menu_back' }]] } }); } else { @@ -1167,7 +1168,7 @@ bot.on('callback_query', async (ctx) => { adminSessions.set(adminId, session); const sourcePatterns = groupPatterns.get(sourceGroupId) || []; - let text = `🎯 Select Patterns to Copy\n\n`; + let text = `Select Patterns to Copy\n\n`; text += `From: Group ${sourceGroupId}\n`; text += `To: Group ${targetGroupId}\n\n`; text += `Send pattern numbers separated by commas (e.g., "1,3,5") or "all" for all patterns:\n\n`; @@ -1179,7 +1180,7 @@ bot.on('callback_query', async (ctx) => { await showOrEditMenu(ctx, text, { parse_mode: 'HTML', reply_markup: { - inline_keyboard: [[{ text: '❌ Cancel', callback_data: 'menu_browsePatterns' }]] + inline_keyboard: [[{ text: 'Cancel', callback_data: 'menu_browsePatterns' }]] } }); return; @@ -1240,44 +1241,53 @@ bot.on('callback_query', async (ctx) => { } else if (data === 'menu_patternHelp') { console.log(`[CALLBACK] Admin ${adminId} requested pattern help`); const helpText = - `✨ Pattern Types Guide ✨\n\n` + + `Pattern Types Guide\n\n` + - `🔤 Simple Text\n` + - `Case-insensitive match\n` + + `Simple Text\n` + + `Case-insensitive substring match\n` + `Example: spam\n` + - `Matches: "SPAM", "Spam", "spam123", etc.\n\n` + + `Matches: "SPAM", "Spam", "spammer", "this spam here", etc.\n\n` + - `⭐ Wildcards\n` + + `Wildcards\n` + `• * = zero or more characters\n` + `• ? = exactly one character\n\n` + `Examples:\n` + - `• spam* → "spam", "spammer", "spam123"\n` + - `• *bot → "mybot", "testbot", "123bot"\n` + - `• *bad* → "baduser", "this_is_bad"\n` + - `• test? → "test1", "testa", "tests"\n\n` + + `• spam* → starts with "spam": "spam", "spammer", "spam123"\n` + + `• *bot → ends with "bot": "mybot", "testbot", "123bot"\n` + + `• *bad* → contains "bad": "baduser", "this_is_bad"\n` + + `• test? → "test" + one char: "test1", "testa", "tests"\n\n` + - `🔧 Regular Expressions\n` + + `Regular Expressions\n` + `Format: /pattern/flags\n\n` + `Useful flags:\n` + `• i = case-insensitive\n` + - `• g = global match\n\n` + + `• g = global match\n` + + `• m = multiline\n` + + `• s = dotall\n` + + `• u = unicode\n\n` + `Examples:\n` + `• /^spam/i → starts with "spam"\n` + `• /user$/i → ends with "user"\n` + - `• /\\d{5,}/ → 5+ digits\n` + + `• /\\d{5,}/ → 5 or more digits\n` + `• /ch[!1i]ld/i → "child", "ch!ld", "ch1ld"\n` + - `• /❤.*p.rn/i → heart + porn variations\n\n` + + `• /heart.*p.rn/i → heart + porn variations\n\n` + + + `What Gets Checked:\n` + + `• Username (if present)\n` + + `• Display name (first + last name)\n` + + `• Display name without quotes/spaces\n` + + `• All converted to lowercase\n\n` + - `💡 Tips:\n` + + `Tips:\n` + `• Test patterns with /testpattern\n` + `• Start simple, then get complex\n` + - `• Patterns are checked against usernames AND display names\n` + + `• Simple text matches as substring\n` + `• Use Browse & Copy to share patterns between groups`; await ctx.editMessageText(helpText, { parse_mode: 'HTML', reply_markup: { - inline_keyboard: [[{ text: '⬅️ Back to Menu', callback_data: 'menu_back' }]] + inline_keyboard: [[{ text: 'Back to Menu', callback_data: 'menu_back' }]] } }); } else if (data === 'menu_back') { @@ -1338,11 +1348,11 @@ bot.command('addFilter', async (ctx) => { groupPatterns.set(groupId, patterns); await saveGroupPatterns(groupId, patterns); - console.log(`[COMMAND] ✅ Added pattern "${patternObj.raw}" to group ${groupId}`); - return ctx.reply(`✅ Added filter: "${patternObj.raw}"`); + console.log(`[COMMAND] Added pattern "${patternObj.raw}" to group ${groupId}`); + return ctx.reply(`Added filter: "${patternObj.raw}"`); } catch (error) { console.error(`[COMMAND] addFilter error:`, error); - return ctx.reply(`❌ Error: ${error.message}`); + return ctx.reply(`Error: ${error.message}`); } }); @@ -1380,7 +1390,7 @@ bot.command('removeFilter', async (ctx) => { patterns.splice(index, 1); groupPatterns.set(groupId, patterns); await saveGroupPatterns(groupId, patterns); - console.log(`[COMMAND] ✅ Removed pattern "${pattern}" from group ${groupId}`); + console.log(`[COMMAND] Removed pattern "${pattern}" from group ${groupId}`); return ctx.reply(`Filter removed: "${pattern}" from Group ${groupId}`); } else { console.log(`[COMMAND] Pattern not found: "${pattern}" in group ${groupId}`); @@ -1480,10 +1490,10 @@ bot.command('setaction', async (ctx) => { settings.groupActions[groupId] = action; const success = await saveSettings(); if (success) { - console.log(`[COMMAND] ✅ Action updated for group ${groupId}: ${action.toUpperCase()}`); + console.log(`[COMMAND] Action updated for group ${groupId}: ${action.toUpperCase()}`); return ctx.reply(`Action updated to: ${action.toUpperCase()} for this group`); } else { - console.log(`[COMMAND] ❌ Failed to save settings for group ${groupId}`); + console.log(`[COMMAND] Failed to save settings for group ${groupId}`); return ctx.reply('Failed to save settings. Check logs for details.'); } } @@ -1515,10 +1525,10 @@ bot.command('setaction', async (ctx) => { settings.groupActions[groupId] = action; const success = await saveSettings(); if (success) { - console.log(`[COMMAND] ✅ Action updated for group ${groupId}: ${action.toUpperCase()}`); + console.log(`[COMMAND] Action updated for group ${groupId}: ${action.toUpperCase()}`); return ctx.reply(`Action updated to: ${action.toUpperCase()} for Group ${groupId}`); } else { - console.log(`[COMMAND] ❌ Failed to save settings for group ${groupId}`); + console.log(`[COMMAND] Failed to save settings for group ${groupId}`); return ctx.reply('Failed to save settings. Check logs for details.'); } } @@ -1577,7 +1587,7 @@ bot.command('hits', async (ctx) => { if (stats.length === 0) { return ctx.reply(`No recorded hits for pattern:\n${patternRaw}`, { parse_mode: 'HTML' }); } - let reply = `📊 Hit counts for pattern ${patternRaw}:\n`; + let reply = `Hit counts for pattern ${patternRaw}:\n`; for (const { groupId, count } of stats) { reply += `• Group ${groupId}: ${count} hit(s)\n`; } @@ -1590,7 +1600,7 @@ bot.command('hits', async (ctx) => { return ctx.reply(`No pattern hits recorded for this group yet.`); } const stats = getHitStatsForGroup(groupId, 10); - let reply = `📈 Top Pattern Hits in Group ${groupId}:\n`; + let reply = `Top Pattern Hits in Group ${groupId}:\n`; for (const { pattern, count } of stats) { reply += `• ${pattern}: ${count}\n`; } @@ -1616,18 +1626,20 @@ bot.command('help', async (ctx) => { `• /setaction - Set action for matches\n` + `• /chatinfo - Show information about current chat\n` + `• /testpattern - Test a pattern\n` + + `• /hits [pattern] - Show hit statistics\n` + `• /cancel - Cancel current operation\n\n` + `Pattern Formats:\n` + - `• Simple text: "spam"\n` + - `• Wildcards: "spam*site", "*bad*user*"\n` + + `• Simple text: "spam" (substring match)\n` + + `• Wildcards: "spam*", "*bad*", "test?"\n` + `• Regex: "/^bad.*user$/i"\n\n` + `Features:\n` + `• Group-specific pattern management\n` + `• Browse and copy patterns between groups\n` + `• Per-group ban/kick settings\n` + - `• Real-time name change monitoring\n\n` + + `• Real-time name change monitoring\n` + + `• Hit tracking and statistics\n\n` + `The bot checks user names when they:\n` + `1. Join a group\n` + @@ -1648,9 +1660,9 @@ bot.command('start', async (ctx) => { console.log(`[COMMAND] /start from admin ${ctx.from.id}`); const welcomeText = - `🛡️ Welcome to the Telegram Ban Bot!\n\n` + + `Welcome to the Telegram Ban Bot!\n\n` + - `This bot helps protect your groups by automatically removing users whose names match specific patterns.\n\n` + + `This bot removes bot spammers by immediately removing new joiners with names or usernames matching your own specified patterns.\n\n` + `Quick Start:\n` + `1. Use /menu to configure patterns\n` + @@ -1658,14 +1670,14 @@ bot.command('start', async (ctx) => { `3. Add patterns (text, wildcards, or regex)\n` + `4. Choose ban or kick action\n\n` + - `New Features:\n` + - `• Browse & copy patterns between groups\n` + - `• Per-group settings management\n` + - `• Enhanced admin controls\n\n` + + `Features:\n` + + `• View & copy blocked patterns between groups\n` + + `• Per-group settings\n` + + `• Hit tracking and statistics\n\n` + `Pattern Examples:\n` + - `• spam - blocks exact text\n` + - `• *bot* - blocks anything with "bot"\n` + + `• spam - blocks substring "spam"\n` + + `• *bot* - blocks anything containing "bot"\n` + `• /^evil/i - blocks names starting with "evil"\n\n` + `Ready to get started?`; @@ -1737,7 +1749,7 @@ bot.on('new_chat_members', async (ctx) => { console.log(`[EVENT] Checking new user: ${user.id} (@${username || 'no_username'}) Name: ${displayName}`); if (await isBanned(username, firstName, lastName, chatId)) { - console.log(`[EVENT] 🚫 New user ${user.id} is banned - taking action`); + console.log(`[EVENT] New user ${user.id} is banned - taking action`); await takePunishmentAction(ctx, user.id, displayName || username || user.id, chatId); } else { console.log(`[EVENT] New user ${user.id} passed initial check - starting monitoring`); @@ -1746,7 +1758,7 @@ bot.on('new_chat_members', async (ctx) => { } }); -// Message handler for banning users +// Action-taken message handler bot.on('message', async (ctx, next) => { if (!isChatAllowed(ctx)) return next(); @@ -1759,7 +1771,7 @@ bot.on('message', async (ctx, next) => { console.log(`[MESSAGE] User ${ctx.from.id} (@${username || 'no_username'}) sending message in chat ${chatId}`); if (await isBanned(username, firstName, lastName, chatId)) { - console.log(`[MESSAGE] 🚫 User ${ctx.from.id} is banned - taking action`); + console.log(`[MESSAGE] User ${ctx.from.id} is banned - taking action`); await takePunishmentAction(ctx, ctx.from.id, displayName || username || ctx.from.id, chatId); } else { console.log(`[MESSAGE] User ${ctx.from.id} passed check - allowing message`); @@ -1791,17 +1803,7 @@ async function startup() { }) .then(() => { console.log('\n=============================='); - console.log('Bot Started'); - console.log('=============================='); - console.log(`Loaded patterns for ${groupPatterns.size} groups`); - console.log(`Group actions:`, settings.groupActions); - console.log(`✅ Security module active`); - console.log(`✅ Pattern validation enabled`); - console.log(`✅ Regex timeout protection enabled`); - console.log(`✅ Enhanced group management enabled`); - console.log(`✅ Pattern browsing & copying enabled`); - console.log(`✅ Comprehensive logging enabled`); - console.log('Bot is running. Press Ctrl+C to stop.'); + console.log('Bot Running'); console.log('==============================\n'); }) .catch(err => { @@ -1837,4 +1839,4 @@ export { incrementHitCounter, getHitStatsForGroup, getHitStatsForPattern -}; +}; \ No newline at end of file From 292153f9a79141b326e4e5ea3b6e6fac7220b260 Mon Sep 17 00:00:00 2001 From: Cordtus Date: Sun, 25 May 2025 15:03:17 -0600 Subject: [PATCH 2/3] add in-line test/simulation function --- bot.js | 197 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 168 insertions(+), 29 deletions(-) diff --git a/bot.js b/bot.js index a6afc5a..0c10e6b 100644 --- a/bot.js +++ b/bot.js @@ -1,4 +1,4 @@ -// bot.js\ +// bot.js - Updated with accurate help text and no emojis import { Telegraf } from 'telegraf'; import dotenv from 'dotenv'; @@ -814,9 +814,9 @@ async function showGroupPatternsForCopy(ctx, sourceGroupId) { if (isOwnGroup) { text += `This is your selected group\n\n`; } else { - text += `To: Group ${targetGroupId} ${canManageTarget ? '✅' : '❌'}\n\n`; + text += `To: Group ${targetGroupId} ${canManageTarget ? 'OK' : 'NO ACCESS'}\n\n`; if (!canManageTarget) { - text += `⚠️ You cannot copy to Group ${targetGroupId}\n`; + text += `You cannot copy to Group ${targetGroupId}\n`; text += `You can only view these patterns.\n\n`; } } @@ -918,31 +918,31 @@ async function promptForPattern(ctx, actionLabel) { const groupId = session.selectedGroupId; const promptText = - `Add Pattern for Group ${groupId}\n\n` + - - `Pattern Types:\n\n` + - - `1. Simple Text - Case-insensitive substring match\n` + - ` • spam matches "SPAM", "Spam", "spammer"\n\n` + - - `2. Wildcards\n` + - ` • * = any characters\n` + - ` • ? = single character\n` + - ` • spam* matches "spam", "spammer", "spam123"\n` + - ` • *bot matches "mybot", "testbot", "123bot"\n` + - ` • *bad* matches "baduser", "this_is_bad"\n` + - ` • test? matches "test1", "testa", "tests"\n\n` + - - `3. Regular Expressions - Advanced patterns\n` + - ` • Format: /pattern/flags\n` + - ` • /^spam.*$/i starts with "spam"\n` + - ` • /\\d{5,}/ 5+ digits in a row\n` + - ` • /ch[!1i]ld/i "child", "ch!ld", "ch1ld"\n\n` + - - `Examples:\n` + - `• ranger - blocks substring "ranger"\n` + - `• *porn* - blocks anything containing "porn"\n` + - `• /heart.*ch.ld.*p.rn/i - blocks heart+variations\n\n` + + `Add Pattern for Group ${groupId}\n\n` + + + `Pattern Types:\n\n` + + + `1. Simple Text - Case-insensitive substring match\n` + + ` • spam matches "SPAM", "Spam", "spammer"\n\n` + + + `2. Wildcards\n` + + ` • * = any characters\n` + + ` • ? = single character\n` + + ` • spam* matches "spam", "spammer", "spam123"\n` + + ` • *bot matches "mybot", "testbot", "123bot"\n` + + ` • *bad* matches "baduser", "this_is_bad"\n` + + ` • test? matches "test1", "testa", "tests"\n\n` + + + `3. Regular Expressions - Advanced patterns\n` + + ` • Format: /pattern/flags\n` + + ` • /^spam.*$/i starts with "spam"\n` + + ` • /\\d{5,}/ 5+ digits in a row\n` + + ` • /ch[!1i]ld/i "child", "ch!ld", "ch1ld"\n\n` + + + `Examples:\n` + + `• ranger - blocks substring "ranger"\n` + + `• *porn* - blocks anything containing "porn"\n` + + `• /heart.*ch.ld.*p.rn/i - blocks heart+variations\n\n` + `Send your pattern or /cancel to abort.`; @@ -1072,7 +1072,7 @@ bot.on('text', async (ctx, next) => { } }); -// callback handler +// Callback handler bot.on('callback_query', async (ctx) => { if (ctx.chat?.type !== 'private' || !(await isAuthorized(ctx))) { return ctx.answerCbQuery('Not authorized.'); @@ -1559,6 +1559,140 @@ bot.command('testpattern', async (ctx) => { } }); +// In-line test - simulate ban checking logic in-app +bot.command('testuser', async (ctx) => { + if (ctx.chat.type !== 'private' || !(await isAuthorized(ctx))) return; + + console.log(`[COMMAND] /testuser from admin ${ctx.from.id}: "${ctx.message.text}"`); + + const parts = ctx.message.text.split(' '); + if (parts.length < 3) { + console.log(`[COMMAND] testuser usage help requested`); + return ctx.reply( + `Usage: /testuser <pattern> <username> [first_name] [last_name]\n\n` + + `Examples:\n` + + `/testuser spam spammer123\n` + + `/testuser *bot* testbot\n` + + `/testuser evil eviluser "Evil User"\n` + + `/testuser /^bad/i badguy "Bad" "Guy"\n\n` + + `This simulates the full ban check process including all name variations.`, + { parse_mode: 'HTML' } + ); + } + + const pattern = parts[1]; + const username = parts[2] || null; + + // Parse display name - handle quoted strings + let remainingParts = parts.slice(3); + let firstName = null; + let lastName = null; + + if (remainingParts.length > 0) { + const fullName = remainingParts.join(' '); + + // Try to parse quoted names + const quotedMatch = fullName.match(/^"([^"]*)"(?:\s+"([^"]*)")?/); + if (quotedMatch) { + firstName = quotedMatch[1] || null; + lastName = quotedMatch[2] || null; + } else { + // Split by spaces + const nameParts = fullName.split(' '); + firstName = nameParts[0] || null; + lastName = nameParts.slice(1).join(' ') || null; + } + } + + try { + // Create a pattern object to validate it + const patternObj = createPatternObject(pattern); + + console.log(`[TESTUSER] Testing pattern "${pattern}" against user: @${username || 'none'}, "${firstName || ''} ${lastName || ''}".trim()`); + + // Simulate the exact logic from isBanned function + let testResults = []; + let overallBanned = false; + + // Test username if provided + if (username) { + const usernameMatch = await matchesPattern(pattern, username.toLowerCase()); + testResults.push({ + type: 'Username', + value: username, + tested: username.toLowerCase(), + result: usernameMatch + }); + if (usernameMatch) overallBanned = true; + } + + // Test display name variations if provided + const displayName = [firstName, lastName].filter(Boolean).join(' '); + if (displayName) { + const variations = [ + { name: 'Display name', value: displayName }, + { name: 'Without quotes', value: displayName.replace(/["'`]/g, '') }, + { name: 'Without spaces', value: displayName.replace(/\s+/g, '') }, + { name: 'Without quotes & spaces', value: displayName.replace(/["'`\s]/g, '') } + ]; + + for (const variation of variations) { + if (variation.value) { + const match = await matchesPattern(pattern, variation.value.toLowerCase()); + testResults.push({ + type: variation.name, + value: variation.value, + tested: variation.value.toLowerCase(), + result: match + }); + if (match) overallBanned = true; + } + } + } + + // Format results + let response = `User Ban Test Results\n\n`; + response += `Pattern: ${pattern}\n`; + response += `Pattern Type: ${patternObj.regex.source ? 'Regex' : pattern.includes('*') || pattern.includes('?') ? 'Wildcard' : 'Simple Text'}\n\n`; + + if (username) { + response += `Username: @${username}\n`; + } + if (displayName) { + response += `Display Name: "${displayName}"\n`; + } + response += `\nTest Results:\n`; + + if (testResults.length === 0) { + response += `No username or display name provided to test.\n\n`; + } else { + for (const test of testResults) { + const status = test.result ? 'MATCH' : 'no match'; + const icon = test.result ? 'BANNED' : 'OK'; + response += `• ${test.type}: "${test.value}" → ${test.tested}${status}\n`; + } + response += `\n`; + } + + // Overall result + const finalResult = overallBanned ? 'NOT OK - USER WOULD BE BANNED' : 'OK - USER ALLOWED'; + const resultIcon = overallBanned ? 'BANNED' : 'ALLOWED'; + response += `Final Result: ${finalResult}\n`; + + if (overallBanned) { + const matchedTests = testResults.filter(t => t.result); + response += `\nBanned because: ${matchedTests.map(t => t.type.toLowerCase()).join(', ')} matched the pattern.`; + } + + console.log(`[TESTUSER] Result: ${resultIcon} - Pattern "${pattern}" vs user data`); + return ctx.reply(response, { parse_mode: 'HTML' }); + + } catch (err) { + console.error(`[TESTUSER] Error:`, err); + return ctx.reply(`Error testing user: ${err.message}`); + } +}); + // Command to show menu directly bot.command('menu', async (ctx) => { if (ctx.chat.type !== 'private' || !(await isAuthorized(ctx))) { @@ -1626,6 +1760,7 @@ bot.command('help', async (ctx) => { `• /setaction - Set action for matches\n` + `• /chatinfo - Show information about current chat\n` + `• /testpattern - Test a pattern\n` + + `• /testuser [first] [last] - Test a pattern against a name\n` + `• /hits [pattern] - Show hit statistics\n` + `• /cancel - Cancel current operation\n\n` + @@ -1634,6 +1769,10 @@ bot.command('help', async (ctx) => { `• Wildcards: "spam*", "*bad*", "test?"\n` + `• Regex: "/^bad.*user$/i"\n\n` + + `Testing Commands:\n` + + `• /testpattern - Test if a pattern matches a string\n` + + `• /testuser - Simulate full check (be sure to test any obvious variations)\n\n` + + `Features:\n` + `• Group-specific pattern management\n` + `• Browse and copy patterns between groups\n` + From 6591507ee3f148870c831866e72e66bff2a9f9e2 Mon Sep 17 00:00:00 2001 From: Cordtus Date: Sun, 25 May 2025 15:10:11 -0600 Subject: [PATCH 3/3] fix ci complaint --- bot.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bot.js b/bot.js index 0c10e6b..d74618d 100644 --- a/bot.js +++ b/bot.js @@ -1668,7 +1668,6 @@ bot.command('testuser', async (ctx) => { } else { for (const test of testResults) { const status = test.result ? 'MATCH' : 'no match'; - const icon = test.result ? 'BANNED' : 'OK'; response += `• ${test.type}: "${test.value}" → ${test.tested}${status}\n`; } response += `\n`; @@ -1676,7 +1675,6 @@ bot.command('testuser', async (ctx) => { // Overall result const finalResult = overallBanned ? 'NOT OK - USER WOULD BE BANNED' : 'OK - USER ALLOWED'; - const resultIcon = overallBanned ? 'BANNED' : 'ALLOWED'; response += `Final Result: ${finalResult}\n`; if (overallBanned) { @@ -1684,7 +1682,7 @@ bot.command('testuser', async (ctx) => { response += `\nBanned because: ${matchedTests.map(t => t.type.toLowerCase()).join(', ')} matched the pattern.`; } - console.log(`[TESTUSER] Result: ${resultIcon} - Pattern "${pattern}" vs user data`); + console.log(`[TESTUSER] Result: ${finalResult} - Pattern "${pattern}" vs user data`); return ctx.reply(response, { parse_mode: 'HTML' }); } catch (err) {