import json import regex import os import asyncio import signal from telethon import TelegramClient, events import logging import traceback import platform # Configuration files CREDENTIALS_FILE = 'credentials.json' KEYWORDS_FILE = 'keywords.txt' CHANNELS_FILE = 'channels.txt' logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("bot.log"), logging.StreamHandler() ] ) async def get_credentials(): try: if os.path.exists(CREDENTIALS_FILE): with open(CREDENTIALS_FILE, 'r') as file: credentials = json.load(file) if not credentials.get('channel_id'): credentials = await fetch_channel_id(credentials) with open(CREDENTIALS_FILE, 'w') as file: json.dump(credentials, file, indent=4) else: credentials = { 'api_id': input('Enter Telegram API ID: ').strip(), 'api_hash': input('Enter Telegram API Hash: ').strip(), 'phone': input('Enter phone number: ').strip(), 'username': input('Enter username (optional, press enter to skip): ').strip() or None, 'bot_token': input('Enter Telegram bot token: ').strip(), 'channel_id': None } credentials = await fetch_channel_id(credentials) with open(CREDENTIALS_FILE, 'w') as file: json.dump(credentials, file, indent=4) return credentials except Exception as e: logging.error(f"Error in get_credentials: {e}") raise async def fetch_channel_id(credentials): client = TelegramClient(credentials['username'] or 'anon_session', credentials['api_id'], credentials['api_hash']) channel_name = input('Enter channel name: ') async with client: client.start(bot_token=credentials['bot_token']) async for dialog in client.iter_dialogs(): if dialog.is_channel and dialog.name == channel_name: credentials['channel_id'] = dialog.id print(f"Found channel ID: {dialog.id}") break return credentials def load_entries_from_file(file_path): with open(file_path, 'r', encoding='utf-8') as file: content = file.read() entries = [x.strip() for x in content.replace('\n', '').split(',') if x.strip()] return entries def load_patterns(): keywords = load_entries_from_file(KEYWORDS_FILE) word_patterns = {} emoji_pattern = r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF]' for word in keywords: original_word = word if word.endswith('**'): word = word[:-2] pattern = rf'\b(?i)(?:{emoji_pattern})*{regex.escape(word)}\p{{L}}{{0,6}}\b' elif word.endswith('*'): word = word[:-1] pattern = rf'\b(?i)(?:{emoji_pattern})*{regex.escape(word)}\p{{L}}{{0,3}}\b' elif word.startswith('##'): word = word[2:] pattern = rf'(?i)(?:{emoji_pattern})*(?<=\b\d{{0,6}}){regex.escape(word)}\b' elif word.startswith('#'): word = word[1:] pattern = rf'(?i)(?:{emoji_pattern})*(?<=\b\d{{0,3}}){regex.escape(word)}\b' else: pattern = rf'\b(?i)(?:{emoji_pattern})*{regex.escape(word)}\b' try: compiled = regex.compile(pattern) word_patterns[original_word] = compiled except regex.error as e: logging.error(f'Invalid regex pattern for word "{word}": {e}') return word_patterns def escape_markdown(text): return regex.sub(r'(\*\*)', '* *', text) async def shutdown(signal, client, loop): logging.info(f"Received exit signal {signal.name}...") await client.disconnect() loop.stop() async def main(): try: creds = await get_credentials() client = TelegramClient(creds['username'] or 'anon_session', creds['api_id'], creds['api_hash']) await client.start(phone=creds['phone']) word_patterns = load_patterns() channels = load_entries_from_file(CHANNELS_FILE) channel_id = creds['channel_id'] # Use the channel ID from credentials await client.send_message(channel_id, f"Listening to {', '.join(channels)}...") @client.on(events.NewMessage(chats=channels)) async def handler(event): try: message_content = event.message.message if event.message else "" word_counts = {} first_match_contexts = {} for word, pattern in word_patterns.items(): matches = list(pattern.finditer(message_content)) if matches: count = len(matches) word_counts[word] = count if len(first_match_contexts) < 3: if word not in first_match_contexts: first_match = matches[0] start_pos = first_match.start() end_pos = first_match.end() context_start = max(start_pos - 20, 0) context_end = min(end_pos + 20, len(message_content)) context = message_content[context_start:context_end] first_match_contexts[word] = context if word_counts: matched_words_with_counts = [] for word, count in word_counts.items(): if count > 1: matched_word = f"{word} ({count})" else: matched_word = word matched_word = escape_markdown(matched_word) matched_words_with_counts.append(matched_word) matched_words_str = ', '.join(matched_words_with_counts) contexts = [] for word, context in first_match_contexts.items(): word_escaped = escape_markdown(word) contexts.append(f"{word_escaped}: {context}") contexts_str = ";\n".join(contexts) context_label = "First three different match contexts" message_text = f"Keyword Match from {event.chat.title}: {matched_words_str}" if contexts: message_text += f"\n{context_label}:\n{contexts_str}" else: message_text += "\nNo contexts available." await client.send_message(channel_id, message_text) await asyncio.sleep(0.1) await event.message.forward_to(channel_id) await asyncio.sleep(0.5) print(f'Forwarded Message: {message_content}') except Exception as e: logging.error(f"Error in message handler: {e}") logging.info(f"Listening to {', '.join(channels)}...") loop = asyncio.get_event_loop() if platform.system() != 'Windows': for sig in (signal.SIGINT, signal.SIGTERM): loop.add_signal_handler(sig, lambda s=sig: asyncio.create_task(shutdown(s, client, loop))) else: try: await client.run_until_disconnected() except KeyboardInterrupt: await shutdown(signal.SIGINT, client, loop) await client.run_until_disconnected() except Exception as e: logging.error(f"Error in main: {e}") logging.error(traceback.format_exc()) finally: logging.info("Disconnecting client...") await client.disconnect() logging.info("Client disconnected safely.") if __name__ == '__main__': asyncio.run(main())