Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ helpers/__pycache__/*
*.session
results.txt
downloads/*
web_viewer_static/*.log
*.json
.env
.DS_Store
.bot-history
.venv
.venv
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This repository contains Python scripts, `TeleTexter.py`, `TeleGatherer.py` and
- **MONITOR:** Read new messages.
- **DISRUPT:** Delete messages from malicious channels.
- **DISRUPT:** Spam channels at a high rate with the message of your choice.
- **VIEW:** Launch web viewer to browse downloaded messages in a Telegram-style interface.

> [!NOTE]
> This script is intended for threat intelligence analysts or researchers who want to monitor, collect, and track adversaries using Telegram for C2 communication.
Expand Down Expand Up @@ -82,11 +83,35 @@ To gather intelligence from a Telegram channel:

`python TeleGatherer.py -t YOUR_BOT_TOKEN -c YOUR_CHAT_ID`

### Web Viewer

The web viewer provides a modern, Telegram-style interface for browsing downloaded messages. Features include:

- 📱 **Telegram-style UI** - Beautiful, modern interface that mimics Telegram's design
- 🔄 **Dynamic Loading** - Automatically detects and displays all chats from the Downloads folder
- 📝 **Message Display** - Shows message text, metadata, entities, and forwarded messages
- 🔍 **Search Functionality** - Search through messages by text, sender, or forwarded content
- ⚡ **Infinite Scroll** - Automatically loads more messages as you scroll
- 🎨 **Responsive Design** - Works on desktop and mobile devices

To launch the web viewer:

1. Run `TeleGatherer.py` and select option **8** from the menu, or
2. Manually start the server:
```bash
python web_viewer.py
```
3. Open your browser to `http://localhost:5000`

The viewer automatically detects all chats in your `Downloads/` folder and displays them in the sidebar. Click any chat to view its messages. Messages are grouped by their actual chat ID, so messages from different chats stored in the same folder are displayed separately.

## Recent Updates

- **Web Viewer** - New Telegram-style web interface for browsing downloaded messages with search functionality
- Ability to view and download all types of media from channels.
- Option to select the number of messages for downloading.
- Dual format text saving: pretty text and full JSON dumps.
- UTF-8 encoding support for proper handling of emojis and special characters on Windows.
- Numerous improvements and bug fixes to enhance existing features.

## Disclaimer
Expand Down
53 changes: 49 additions & 4 deletions TeleGatherer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,23 @@
import argparse
import multiprocessing
import mimetypes
import sys
import subprocess
import webbrowser
from helpers.TeleTexter import send_telegram_message
from helpers.TeleViewer import process_messages


try:
# Ensure Windows console can handle UTF-8 output (emojis, non-ASCII)
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
# Best-effort; continue if not supported
pass

def parse_dict(title, dictionary):
result = f"{title}:\n"
for key, value in dictionary.items():
Expand Down Expand Up @@ -165,8 +178,9 @@ def main(bot_token, chat_id):
print("4. Delete all messages from the malicious telegram channel that are sent within 24 hours\n")
print("5. Get approximate number of messages on the malicious telegram channel")
print("6. Download ALL messages from the malicious telegram channel")
print("7. Send a file to the malicious telegram channel\n")
print("8. EXIT")
print("7. Send a file to the malicious telegram channel")
print("8. Launch web viewer to browse downloaded messages\n")
print("9. EXIT")
choice = input("\nEnter your choice: ")
if choice == '1':
offset = None # Variable to keep track of the last update ID
Expand Down Expand Up @@ -262,21 +276,52 @@ def main(bot_token, chat_id):
else:
print("Error: Unable to send file: ", response.get("description"))
elif choice == '8':
try:
print("\n[*] Starting web viewer server...")
print("[*] The web viewer will open in your default browser.")
print("[*] Server will run on http://localhost:5000")
print("[*] Press Ctrl+C in the web viewer terminal to stop the server.\n")

# Launch web viewer in a separate process
if sys.platform == 'win32':
# On Windows, create a new console window to show Flask output
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] On Windows, subprocess.CREATE_NEW_CONSOLE is used, but this flag requires importing it from subprocess. The code imports subprocess but doesn't explicitly import the constant. While it's accessible as subprocess.CREATE_NEW_CONSOLE, be aware this is only available on Windows. The code should work as-is since it's inside a platform check, but consider adding a comment about Windows-specific behavior.

Suggested change
# On Windows, create a new console window to show Flask output
# On Windows, create a new console window to show Flask output
# subprocess.CREATE_NEW_CONSOLE is a Windows-only flag to launch the process in a new console window.

Copilot uses AI. Check for mistakes.
viewer_process = subprocess.Popen(
[sys.executable, 'web_viewer.py'],
creationflags=subprocess.CREATE_NEW_CONSOLE
)
else:
# On Unix-like systems, run in background
viewer_process = subprocess.Popen(
Comment on lines +288 to +294
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'viewer_process' is unnecessary as it is redefined before this value is used.

Suggested change
viewer_process = subprocess.Popen(
[sys.executable, 'web_viewer.py'],
creationflags=subprocess.CREATE_NEW_CONSOLE
)
else:
# On Unix-like systems, run in background
viewer_process = subprocess.Popen(
subprocess.Popen(
[sys.executable, 'web_viewer.py'],
creationflags=subprocess.CREATE_NEW_CONSOLE
)
else:
# On Unix-like systems, run in background
subprocess.Popen(

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'viewer_process' is unnecessary as it is redefined before this value is used.

Copilot uses AI. Check for mistakes.
[sys.executable, 'web_viewer.py'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
Comment on lines +285 to +298
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subprocess is started but never properly managed or cleaned up. The viewer_process handle is created but not stored or tracked, so there's no way to stop the server gracefully when the main program exits. Consider storing the process handle and implementing cleanup in a finally block or exit handler.

Copilot uses AI. Check for mistakes.

# Wait a moment for server to start, then open browser
time.sleep(2)
webbrowser.open('http://localhost:5000')

print("[*] Web viewer launched! Check the new window for the server.")
Comment on lines +300 to +304
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses time.sleep(2) to wait for the Flask server to start before opening the browser. This is a race condition - on slow systems or under heavy load, 2 seconds might not be enough for Flask to start listening. Consider implementing a more robust check (e.g., polling the server with HTTP requests) or increasing the delay, or handling the case where the browser opens before the server is ready.

Suggested change
# Wait a moment for server to start, then open browser
time.sleep(2)
webbrowser.open('http://localhost:5000')
print("[*] Web viewer launched! Check the new window for the server.")
# Wait for server to start by polling the endpoint
server_url = 'http://localhost:5000'
max_wait = 10 # seconds
poll_interval = 0.5 # seconds
waited = 0
while waited < max_wait:
try:
response = requests.get(server_url)
if response.status_code == 200:
webbrowser.open(server_url)
print("[*] Web viewer launched! Check the new window for the server.")
break
except requests.exceptions.ConnectionError:
pass
time.sleep(poll_interval)
waited += poll_interval
else:
print("[!] Web viewer server did not start within expected time. Please check for errors.")

Copilot uses AI. Check for mistakes.
print("[*] Returning to main menu...\n")
except Exception as e:
print(f"Error launching web viewer: {e}")
print("You can manually start it by running: python web_viewer.py")
elif choice == '9':
print("Exiting...")
break
else:
print("Invalid choice. Please try again.")

# Check a file for the bot token and chat id pair it is stored in the file in the form of bot_token:chat_id, then print a message asking if you are sure you want to continue. Otherwise continue and add the bot token and chat id to the file in the form of bot_token:chat_id and continue.
def check_file_for_token_and_chat_id(file_path, bot_token, chat_id):
with open(file_path, 'r') as file:
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
if line.strip() == f"{bot_token}:{chat_id}":
return True
return False

def add_token_and_chat_id_to_file(file_path, bot_token, chat_id):
with open(file_path, 'a') as file:
with open(file_path, 'a', encoding='utf-8') as file:
file.write(f"{bot_token}:{chat_id}\n")

if __name__ == "__main__":
Expand Down
10 changes: 6 additions & 4 deletions helpers/TeleViewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ async def progress(current, total):
directory = f'Downloads/{username}/logs'
if not os.path.exists(directory):
os.makedirs(directory)
with open(f'{directory}/{username}_bot.txt', 'a') as file:
with open(f'{directory}/{username}_bot.txt', 'a', encoding='utf-8') as file:
file.write(f"Message ID: {messages.id}\n")
file.write(
f"From User ID: {messages.from_user.id} - Username: {messages.from_user.username}\n"
Expand All @@ -109,17 +109,19 @@ async def progress(current, total):
file.write(f"Reply_markup: {messages.reply_markup}\n\n")
# Save the whole message to a file
with open(f'{directory}/{username}_bot.json',
'a') as file:
'a', encoding='utf-8') as file:
file.write(str(messages))
else:
directory = f'Downloads/{chat_id}/logs'
with open(f'{directory}/{chat_id}_bot.txt', 'a') as file:
if not os.path.exists(directory):
os.makedirs(directory)
with open(f'{directory}/{chat_id}_bot.txt', 'a', encoding='utf-8') as file:
file.write(f"Message ID: {messages.id}\n")
file.write(f"Date: {messages.date}\n")
file.write(f"Text: {messages.text}\n")
file.write(f"Reply_markup: {messages.reply_markup}\n\n")
# Save the whole message to a file
with open(f'{directory}/{chat_id}_bot.json', 'a') as file:
with open(f'{directory}/{chat_id}_bot.json', 'a', encoding='utf-8') as file:
file.write(str(messages))
except AttributeError as e:
print(f"Error: {e}")
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ Pyrogram==2.0.106
python-dotenv==1.0.0
requests==2.31.0
tgcrypto
Flask==3.0.0
flask-cors==4.0.0
Loading
Loading