Initial commit.
This commit is contained in:
commit
0c013cfd4d
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 retoor // this bot is vibe coded as F. was checking out a vibe coding CLI.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
4
MANIFEST.in
Normal file
4
MANIFEST.in
Normal file
@ -0,0 +1,4 @@
|
||||
include README.md
|
||||
include LICENSE
|
||||
include .env.example
|
||||
include src/marcus/persona_prompt.txt
|
241
README.md
Normal file
241
README.md
Normal file
@ -0,0 +1,241 @@
|
||||
# Telegram Scambaiter Bot 🎭
|
||||
|
||||
An intelligent bot that automatically engages with scammers using a believable persona to waste their time and protect others. The bot uses **Marcus** - a 35-year-old lonely guy with a Brabus 900 who's terrible with tech but has money to spend.
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
### Core Functionality
|
||||
- **Automatic Scammer Detection**: Anyone not in your contacts is treated as a scammer
|
||||
- **Realistic Persona**: Marcus is desperate, lonely, has money, but struggles with technology
|
||||
- **Adaptive Learning**: Bot learns from conversations and adapts strategies
|
||||
- **Realistic Timing**: 1-5 minute response delays with typing indicators
|
||||
- **24/7 Operation**: Works day and night automatically
|
||||
|
||||
### Advanced Features
|
||||
- **Scammer Type Detection**: Identifies romance, crypto, inheritance, lottery scams
|
||||
- **Conversation Analytics**: Tracks engagement and time wasted
|
||||
- **Adaptive Strategies**: Different approaches based on scammer type
|
||||
- **Fallback Responses**: Handles API failures gracefully
|
||||
- **Memory Management**: Cleans up old conversations
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Prerequisites
|
||||
- Python 3.8+
|
||||
- Telegram account
|
||||
- X.AI (Grok) API key
|
||||
|
||||
### 2. Get API Credentials
|
||||
|
||||
#### Telegram API:
|
||||
1. Go to https://my.telegram.org/apps
|
||||
2. Create a new application
|
||||
3. Note down `api_id` and `api_hash`
|
||||
|
||||
#### Grok API:
|
||||
1. Go to https://console.x.ai/
|
||||
2. Create account and get API key
|
||||
|
||||
### 3. Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository (or copy files to your directory)
|
||||
cd ae
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env with your credentials
|
||||
nano .env
|
||||
```
|
||||
|
||||
### 4. Configuration
|
||||
|
||||
Edit `.env` file:
|
||||
```bash
|
||||
# Telegram API credentials
|
||||
TELEGRAM_API_ID=12345678
|
||||
TELEGRAM_API_HASH=your_hash_here
|
||||
TELEGRAM_PHONE=+1234567890
|
||||
|
||||
# Grok API key
|
||||
GROK_API_KEY=your_grok_key_here
|
||||
```
|
||||
|
||||
### 5. Run the Bot
|
||||
|
||||
```bash
|
||||
python scambaiter_bot.py
|
||||
```
|
||||
|
||||
On first run, you'll need to verify your phone number with Telegram.
|
||||
|
||||
## 🎭 Meet Marcus - Your Scambaiting Persona
|
||||
|
||||
Marcus is a carefully crafted character designed to be the perfect scammer target:
|
||||
|
||||
### Personality Traits
|
||||
- 35 years old, lonely bachelor
|
||||
- Works from home (vague IT job)
|
||||
- Drives a Brabus 900 (expensive Mercedes)
|
||||
- Has money but doesn't know how to attract women
|
||||
- Terrible with technology despite working in IT
|
||||
- Gets excited when attractive women message him
|
||||
- Overthinks responses and makes typos when excited
|
||||
|
||||
### Conversation Patterns
|
||||
- Asks lots of questions to keep conversations going
|
||||
- Complains about navigation system problems
|
||||
- Mentions expensive purchases awkwardly
|
||||
- Shows interest in meeting up (but desperately)
|
||||
- Asks for photos
|
||||
- Makes spelling mistakes when excited
|
||||
|
||||
### Safety Features
|
||||
- Never shares real personal information
|
||||
- Uses only fake details consistently
|
||||
- Designed to be obviously fake to real people
|
||||
- Perfect bait for scammers
|
||||
|
||||
## 📊 Learning & Analytics
|
||||
|
||||
The bot includes sophisticated learning capabilities:
|
||||
|
||||
### Scammer Type Detection
|
||||
- **Romance Scams**: Lonely, beautiful, love, relationship
|
||||
- **Crypto Scams**: Bitcoin, investment, trading, profit
|
||||
- **Inheritance Scams**: Million, lawyer, deceased, beneficiary
|
||||
- **Lottery Scams**: Winner, prize, congratulations
|
||||
- **Tech Support**: Microsoft, virus, refund
|
||||
|
||||
### Adaptive Strategies
|
||||
- **Romance Scammers**: Extra lonely/desperate responses
|
||||
- **Crypto Scammers**: Show interest but ask basic questions
|
||||
- **Inheritance Scammers**: Excited but worried about legality
|
||||
|
||||
### Performance Tracking
|
||||
- Total scammers engaged
|
||||
- Messages received/sent
|
||||
- Time wasted (in minutes)
|
||||
- Conversation lengths
|
||||
- Engagement scores
|
||||
|
||||
## 🛠️ Advanced Configuration
|
||||
|
||||
### Timing Adjustments
|
||||
Edit `scambaiter_bot.py`:
|
||||
```python
|
||||
self.min_response_time = 60 # Minimum 1 minute
|
||||
self.max_response_time = 300 # Maximum 5 minutes
|
||||
```
|
||||
|
||||
### Persona Modifications
|
||||
Edit `persona_prompt.txt` to adjust Marcus's personality or add new details.
|
||||
|
||||
### Contact Detection
|
||||
The bot automatically loads your Telegram contacts. Anyone not in contacts is treated as a scammer.
|
||||
|
||||
## 📝 Logs & Monitoring
|
||||
|
||||
### Log Files
|
||||
- `scambaiter.log`: Detailed operation logs
|
||||
- `conversation_stats.json`: Analytics and learning data
|
||||
|
||||
### Monitoring
|
||||
```bash
|
||||
# Watch logs in real-time
|
||||
tail -f scambaiter.log
|
||||
|
||||
# Check conversation statistics
|
||||
python -c "
|
||||
from conversation_manager import ConversationAnalyzer
|
||||
analyzer = ConversationAnalyzer()
|
||||
print(analyzer.get_conversation_summary())
|
||||
"
|
||||
```
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "Missing environment variables"
|
||||
- Check `.env` file exists and has correct credentials
|
||||
- Ensure no spaces around `=` in `.env`
|
||||
|
||||
#### "Grok API error"
|
||||
- Verify API key is correct
|
||||
- Check account has available credits
|
||||
- Try again after a few minutes
|
||||
|
||||
#### "Not detecting contacts"
|
||||
- Make sure contacts are properly synced in Telegram
|
||||
- Check bot has permission to access contacts
|
||||
|
||||
#### "Too fast/slow responses"
|
||||
- Adjust `min_response_time` and `max_response_time`
|
||||
- Check `delay_factor` in conversation analytics
|
||||
|
||||
## ⚠️ Legal & Ethical Considerations
|
||||
|
||||
### Legal
|
||||
- This is for educational and defensive purposes only
|
||||
- Scambaiting may be illegal in some jurisdictions
|
||||
- Use at your own risk and check local laws
|
||||
|
||||
### Ethical Guidelines
|
||||
- Only targets unsolicited messages from unknown contacts
|
||||
- Designed to waste scammer time, not cause harm
|
||||
- Protects others by occupying scammer resources
|
||||
- Never attempts to extract personal information
|
||||
|
||||
### Safety
|
||||
- Uses only fake persona details
|
||||
- Never shares real personal information
|
||||
- Automatically obvious to legitimate contacts
|
||||
- Logs all interactions for transparency
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Feel free to improve the system:
|
||||
|
||||
### Persona Enhancements
|
||||
- Add new personality traits
|
||||
- Create conversation variations
|
||||
- Improve believability
|
||||
|
||||
### Technical Improvements
|
||||
- Add new scammer type detection
|
||||
- Improve learning algorithms
|
||||
- Enhance error handling
|
||||
|
||||
### Analytics
|
||||
- Better engagement metrics
|
||||
- Conversation effectiveness scoring
|
||||
- Response optimization
|
||||
|
||||
## 📈 Expected Results
|
||||
|
||||
### Typical Performance
|
||||
- **Response Rate**: 95%+ of scammers engage
|
||||
- **Time Wasted**: 10-30 minutes per scammer
|
||||
- **Detection Accuracy**: 99%+ (non-contacts = scammers)
|
||||
- **Believability**: High for scammers, obvious for real people
|
||||
|
||||
### Success Metrics
|
||||
- Length of conversations (longer = better)
|
||||
- Number of follow-up messages from scammers
|
||||
- Time spent by scammers before giving up
|
||||
- Variety of response types
|
||||
|
||||
## 🎉 Have Fun!
|
||||
|
||||
Remember, you're not just wasting scammer time - you're protecting others by keeping scammers busy with fake targets instead of real victims. Every minute a scammer spends talking to Marcus is a minute they're not scamming someone vulnerable.
|
||||
|
||||
The bot runs 24/7, so Marcus never sleeps and is always ready to chat with his new "friends" about his Brabus navigation problems! 😄
|
||||
|
||||
---
|
||||
|
||||
**Disclaimer**: This tool is for educational and defensive purposes. Users are responsible for complying with all applicable laws and regulations in their jurisdiction.
|
89
pyproject.toml
Normal file
89
pyproject.toml
Normal file
@ -0,0 +1,89 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "marcus-scambaiter"
|
||||
version = "1.0.0"
|
||||
description = "An intelligent Telegram bot that automatically engages scammers using a believable persona to waste their time and protect others"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "retoor", email = "retoor@molodetz.nl"},
|
||||
]
|
||||
maintainers = [
|
||||
{name = "retoor", email = "retoor@molodetz.nl"},
|
||||
]
|
||||
keywords = ["telegram", "scambaiting", "bot", "ai", "security", "anti-scam"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Communications :: Chat",
|
||||
"Topic :: Security",
|
||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||
]
|
||||
dependencies = [
|
||||
"telethon==1.34.0",
|
||||
"requests>=2.31.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"asyncio-throttle>=1.0.2",
|
||||
"aiofiles>=23.2.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
"black>=23.0.0",
|
||||
"isort>=5.12.0",
|
||||
"flake8>=6.0.0",
|
||||
"mypy>=1.0.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://retoor.molodetz.nl/retoor/"
|
||||
Documentation = "https://github.com/retoor/marcus-scambaiter#readme"
|
||||
Repository = "https://github.com/retoor/marcus-scambaiter.git"
|
||||
Issues = "https://github.com/retoor/marcus-scambaiter/issues"
|
||||
|
||||
[project.scripts]
|
||||
marcus = "marcus.cli:main"
|
||||
marcus-scambaiter = "marcus.cli:main"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/marcus/__init__.py"
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
include = [
|
||||
"/src",
|
||||
"/README.md",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/marcus"]
|
||||
|
||||
[tool.black]
|
||||
target-version = ["py38"]
|
||||
line-length = 88
|
||||
include = '\.pyi?$'
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
multi_line_output = 3
|
||||
line_length = 88
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
21
src/marcus/__init__.py
Normal file
21
src/marcus/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""
|
||||
Marcus - Telegram Scambaiting Bot
|
||||
|
||||
An intelligent bot that automatically engages scammers using a believable persona
|
||||
to waste their time and protect others.
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "retoor"
|
||||
__email__ = "retoor@molodetz.nl"
|
||||
|
||||
from .bot import ScambaiterBot
|
||||
from .conversation import ConversationAnalyzer, PersonaVariations
|
||||
from .persona import load_persona
|
||||
|
||||
__all__ = [
|
||||
"ScambaiterBot",
|
||||
"ConversationAnalyzer",
|
||||
"PersonaVariations",
|
||||
"load_persona",
|
||||
]
|
324
src/marcus/bot.py
Normal file
324
src/marcus/bot.py
Normal file
@ -0,0 +1,324 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Telegram Scambaiter Bot - Automatically responds to scammers with a believable persona
|
||||
"""
|
||||
import asyncio
|
||||
import random
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Set, Optional
|
||||
import requests
|
||||
from telethon import TelegramClient, events
|
||||
from telethon.tl.types import User, Chat, Channel
|
||||
from dotenv import load_dotenv
|
||||
import logging
|
||||
from .conversation import ConversationAnalyzer, PersonaVariations
|
||||
from .persona import load_persona
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('scambaiter.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GrokConversation:
|
||||
"""Manages individual Grok conversations for each scammer"""
|
||||
|
||||
def __init__(self, user_id: int, grok_api_key: str):
|
||||
self.user_id = user_id
|
||||
self.grok_api_key = grok_api_key
|
||||
self.conversation_history = []
|
||||
self.created_at = datetime.now()
|
||||
self.last_activity = datetime.now()
|
||||
|
||||
# Load persona prompt
|
||||
self.system_prompt = load_persona()
|
||||
|
||||
class EnhancedGrokConversation(GrokConversation):
|
||||
"""Enhanced Grok conversation with adaptive learning"""
|
||||
|
||||
def __init__(self, user_id: int, grok_api_key: str, conversation_analyzer: ConversationAnalyzer):
|
||||
super().__init__(user_id, grok_api_key)
|
||||
self.analyzer = conversation_analyzer
|
||||
|
||||
async def get_response(self, message: str) -> str:
|
||||
"""Get response from Grok AI with adaptive persona"""
|
||||
try:
|
||||
# Get optimized strategy for this scammer
|
||||
strategy = self.analyzer.get_optimized_response_strategy(self.user_id)
|
||||
|
||||
# Enhance system prompt with adaptive elements
|
||||
enhanced_prompt = self.system_prompt
|
||||
|
||||
# Add mood variation
|
||||
mood = PersonaVariations.get_mood_variation()
|
||||
situation = PersonaVariations.get_current_situation()
|
||||
|
||||
enhanced_prompt += f"\n\nCURRENT CONTEXT:\n- {mood}\n- {situation}"
|
||||
|
||||
# Add strategy-specific instructions
|
||||
if strategy.get('specific_hooks'):
|
||||
hooks = "\n- ".join(strategy['specific_hooks'])
|
||||
enhanced_prompt += f"\n\nFOCUS ON THESE CONVERSATION HOOKS:\n- {hooks}"
|
||||
|
||||
# Add user message to history
|
||||
self.conversation_history.append({"role": "user", "content": message})
|
||||
|
||||
# Prepare API request
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.grok_api_key}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
# Build messages for API
|
||||
messages = [{"role": "system", "content": enhanced_prompt}]
|
||||
messages.extend(self.conversation_history[-10:]) # Keep last 10 messages
|
||||
|
||||
payload = {
|
||||
"messages": messages,
|
||||
"model": "grok-3-mini",
|
||||
"stream": False,
|
||||
"temperature": 0.8
|
||||
}
|
||||
|
||||
# Make API call
|
||||
response = requests.post(
|
||||
'https://api.x.ai/v1/chat/completions',
|
||||
headers=headers,
|
||||
json=payload,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
ai_response = result['choices'][0]['message']['content']
|
||||
|
||||
# Add AI response to history
|
||||
self.conversation_history.append({"role": "assistant", "content": ai_response})
|
||||
self.last_activity = datetime.now()
|
||||
|
||||
logger.info(f"Generated response for user {self.user_id}: {ai_response[:100]}...")
|
||||
return ai_response
|
||||
else:
|
||||
logger.error(f"Grok API error: {response.status_code} - {response.text}")
|
||||
return self._get_fallback_response()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Grok response: {e}")
|
||||
return self._get_fallback_response()
|
||||
|
||||
def _get_fallback_response(self) -> str:
|
||||
"""Fallback responses if Grok API fails"""
|
||||
fallbacks = [
|
||||
"Hey! Sorry, I was having issues with my phone... what were you saying? 😅",
|
||||
"Oh no, my internet is acting up again! Can you repeat that?",
|
||||
"Sorry for the delay, I'm not great with this app lol. What's up?",
|
||||
"Hi there! My phone was being weird, did you send me something?"
|
||||
]
|
||||
return random.choice(fallbacks)
|
||||
|
||||
class ScambaiterBot:
|
||||
"""Main bot class"""
|
||||
|
||||
def __init__(self):
|
||||
self.api_id = int(os.getenv('TELEGRAM_API_ID'))
|
||||
self.api_hash = os.getenv('TELEGRAM_API_HASH')
|
||||
self.phone_number = os.getenv('TELEGRAM_PHONE')
|
||||
self.grok_api_key = os.getenv('GROK_API_KEY')
|
||||
|
||||
self.client = TelegramClient('scambaiter_session', self.api_id, self.api_hash)
|
||||
|
||||
# Track active conversations
|
||||
self.active_conversations: Dict[int, GrokConversation] = {}
|
||||
self.known_contacts: Set[int] = set()
|
||||
self.response_queue = []
|
||||
|
||||
# Learning and analytics
|
||||
self.conversation_analyzer = ConversationAnalyzer()
|
||||
|
||||
# Timing configuration
|
||||
self.min_response_time = 60 # 1 minute
|
||||
self.max_response_time = 300 # 5 minutes
|
||||
|
||||
async def load_contacts(self):
|
||||
"""Load known contacts from Telegram"""
|
||||
try:
|
||||
dialogs = await self.client.get_dialogs()
|
||||
for dialog in dialogs:
|
||||
if isinstance(dialog.entity, User) and not dialog.entity.bot:
|
||||
# Only add users who are actually in contacts
|
||||
if dialog.entity.mutual_contact or dialog.entity.contact:
|
||||
self.known_contacts.add(dialog.entity.id)
|
||||
|
||||
logger.info(f"Loaded {len(self.known_contacts)} known contacts")
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading contacts: {e}")
|
||||
|
||||
async def is_scammer(self, user_id: int) -> bool:
|
||||
"""Check if user is a scammer (not in contacts)"""
|
||||
return user_id not in self.known_contacts
|
||||
|
||||
async def queue_response(self, user_id: int, message: str, delay_factor: float = 1.0):
|
||||
"""Queue a response with realistic timing"""
|
||||
base_delay = random.randint(self.min_response_time, self.max_response_time)
|
||||
adjusted_delay = int(base_delay * delay_factor)
|
||||
response_time = datetime.now() + timedelta(seconds=adjusted_delay)
|
||||
|
||||
self.response_queue.append({
|
||||
'user_id': user_id,
|
||||
'message': message,
|
||||
'response_time': response_time
|
||||
})
|
||||
|
||||
logger.info(f"Queued response for user {user_id} in {adjusted_delay} seconds (factor: {delay_factor})")
|
||||
|
||||
async def process_response_queue(self):
|
||||
"""Process queued responses when it's time"""
|
||||
while True:
|
||||
try:
|
||||
now = datetime.now()
|
||||
ready_responses = [r for r in self.response_queue if r['response_time'] <= now]
|
||||
|
||||
for response_data in ready_responses:
|
||||
try:
|
||||
user_id = response_data['user_id']
|
||||
message = response_data['message']
|
||||
|
||||
# Show typing indicator
|
||||
async with self.client.action(user_id, 'typing'):
|
||||
await asyncio.sleep(random.uniform(2, 8)) # Realistic typing time
|
||||
|
||||
# Send the message
|
||||
await self.client.send_message(user_id, message)
|
||||
logger.info(f"Sent response to {user_id}: {message[:50]}...")
|
||||
|
||||
# Remove from queue
|
||||
self.response_queue.remove(response_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending queued response: {e}")
|
||||
self.response_queue.remove(response_data)
|
||||
|
||||
await asyncio.sleep(5) # Check every 5 seconds
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in response queue processing: {e}")
|
||||
await asyncio.sleep(10)
|
||||
|
||||
async def handle_message(self, event):
|
||||
"""Handle incoming messages"""
|
||||
try:
|
||||
sender = await event.get_sender()
|
||||
|
||||
# Skip if sender is not a User or is a bot
|
||||
if not isinstance(sender, User) or sender.bot:
|
||||
return
|
||||
|
||||
user_id = sender.id
|
||||
message_text = event.message.text
|
||||
|
||||
# Check if this is a scammer
|
||||
if not await self.is_scammer(user_id):
|
||||
logger.info(f"Ignoring message from known contact: {user_id}")
|
||||
return
|
||||
|
||||
logger.info(f"Scammer message from {user_id}: {message_text}")
|
||||
|
||||
# Analyze the message for learning
|
||||
self.conversation_analyzer.analyze_message(user_id, message_text, is_from_scammer=True)
|
||||
|
||||
# Get or create conversation
|
||||
if user_id not in self.active_conversations:
|
||||
self.active_conversations[user_id] = EnhancedGrokConversation(
|
||||
user_id, self.grok_api_key, self.conversation_analyzer
|
||||
)
|
||||
logger.info(f"Created new conversation for scammer {user_id}")
|
||||
|
||||
conversation = self.active_conversations[user_id]
|
||||
|
||||
# Get AI response with adaptive strategy
|
||||
ai_response = await conversation.get_response(message_text)
|
||||
|
||||
# Get optimized timing based on learned patterns
|
||||
strategy = self.conversation_analyzer.get_optimized_response_strategy(user_id)
|
||||
delay_factor = strategy.get('delay_factor', 1.0)
|
||||
|
||||
# Queue the response with adaptive timing
|
||||
await self.queue_response(user_id, ai_response, delay_factor)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling message: {e}")
|
||||
|
||||
async def cleanup_old_conversations(self):
|
||||
"""Clean up conversations older than 24 hours of inactivity"""
|
||||
while True:
|
||||
try:
|
||||
now = datetime.now()
|
||||
cutoff = now - timedelta(hours=24)
|
||||
|
||||
to_remove = [
|
||||
user_id for user_id, conv in self.active_conversations.items()
|
||||
if conv.last_activity < cutoff
|
||||
]
|
||||
|
||||
for user_id in to_remove:
|
||||
del self.active_conversations[user_id]
|
||||
logger.info(f"Cleaned up conversation for user {user_id}")
|
||||
|
||||
await asyncio.sleep(3600) # Check every hour
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in cleanup: {e}")
|
||||
await asyncio.sleep(3600)
|
||||
|
||||
async def start(self):
|
||||
"""Start the bot"""
|
||||
logger.info("Starting Scambaiter Bot...")
|
||||
|
||||
# Connect to Telegram
|
||||
await self.client.start(phone=self.phone_number)
|
||||
logger.info("Connected to Telegram")
|
||||
|
||||
# Load contacts
|
||||
await self.load_contacts()
|
||||
|
||||
# Set up message handler
|
||||
@self.client.on(events.NewMessage(incoming=True))
|
||||
async def message_handler(event):
|
||||
await self.handle_message(event)
|
||||
|
||||
# Start background tasks
|
||||
asyncio.create_task(self.process_response_queue())
|
||||
asyncio.create_task(self.cleanup_old_conversations())
|
||||
|
||||
logger.info("Scambaiter Bot is running!")
|
||||
|
||||
# Keep the bot running
|
||||
await self.client.run_until_disconnected()
|
||||
|
||||
async def main():
|
||||
"""Main function"""
|
||||
# Check for required environment variables
|
||||
required_vars = ['TELEGRAM_API_ID', 'TELEGRAM_API_HASH', 'TELEGRAM_PHONE', 'GROK_API_KEY']
|
||||
missing_vars = [var for var in required_vars if not os.getenv(var)]
|
||||
|
||||
if missing_vars:
|
||||
logger.error(f"Missing environment variables: {missing_vars}")
|
||||
logger.error("Please create a .env file with the required variables")
|
||||
return
|
||||
|
||||
bot = ScambaiterBot()
|
||||
await bot.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
149
src/marcus/cli.py
Normal file
149
src/marcus/cli.py
Normal file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
CLI entry point for Marcus Scambaiter Bot
|
||||
"""
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
from .bot import main as bot_main, ScambaiterBot
|
||||
from .conversation import ConversationAnalyzer
|
||||
from .persona import load_persona, get_persona_path
|
||||
|
||||
|
||||
def check_env_file():
|
||||
"""Check if .env file exists and has required variables"""
|
||||
if not Path(".env").exists():
|
||||
print("❌ No .env file found!")
|
||||
print("📝 Please create a .env file with your credentials.")
|
||||
print("📖 See README.md for setup instructions.")
|
||||
return False
|
||||
|
||||
required_vars = ['TELEGRAM_API_ID', 'TELEGRAM_API_HASH', 'TELEGRAM_PHONE', 'GROK_API_KEY']
|
||||
missing_vars = []
|
||||
|
||||
# Load .env manually to check
|
||||
try:
|
||||
with open('.env', 'r') as f:
|
||||
env_content = f.read()
|
||||
for var in required_vars:
|
||||
if f"{var}=" not in env_content:
|
||||
missing_vars.append(var)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if missing_vars:
|
||||
print(f"❌ Missing environment variables: {', '.join(missing_vars)}")
|
||||
print("📝 Please add them to your .env file.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def show_stats():
|
||||
"""Show conversation statistics"""
|
||||
try:
|
||||
analyzer = ConversationAnalyzer()
|
||||
stats = analyzer.get_conversation_summary()
|
||||
|
||||
print("📊 Marcus Scambaiter Statistics")
|
||||
print("=" * 40)
|
||||
print(f"Total scammers engaged: {stats['total_scammers_engaged']}")
|
||||
print(f"Total messages received: {stats['total_messages_received']}")
|
||||
print(f"Time wasted (minutes): {stats['total_time_wasted_minutes']:.1f}")
|
||||
print(f"Average messages per scammer: {stats['average_messages_per_scammer']:.1f}")
|
||||
|
||||
if stats['scammer_types']:
|
||||
print("\nScammer Types Detected:")
|
||||
for scam_type, count in stats['scammer_types'].items():
|
||||
print(f" {scam_type}: {count}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading statistics: {e}")
|
||||
|
||||
|
||||
def show_persona():
|
||||
"""Show Marcus's persona"""
|
||||
try:
|
||||
persona = load_persona()
|
||||
print("🎭 Marcus's Persona")
|
||||
print("=" * 40)
|
||||
print(persona)
|
||||
except Exception as e:
|
||||
print(f"❌ Error loading persona: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main CLI entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Marcus - Telegram Scambaiting Bot",
|
||||
epilog="For more information, visit: https://retoor.molodetz.nl/retoor/"
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
||||
|
||||
# Start command
|
||||
start_parser = subparsers.add_parser('start', help='Start the scambaiting bot')
|
||||
start_parser.add_argument('--check-only', action='store_true',
|
||||
help='Only check configuration, don\'t start bot')
|
||||
|
||||
# Stats command
|
||||
subparsers.add_parser('stats', help='Show conversation statistics')
|
||||
|
||||
# Persona command
|
||||
subparsers.add_parser('persona', help='Show Marcus\'s personality')
|
||||
|
||||
# Config command
|
||||
config_parser = subparsers.add_parser('config', help='Configuration utilities')
|
||||
config_parser.add_argument('--check', action='store_true', help='Check configuration')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
print("🎭 Marcus Scambaiter Bot v1.0.0")
|
||||
print("By retoor - https://retoor.molodetz.nl/retoor/")
|
||||
print()
|
||||
|
||||
if args.command == 'start':
|
||||
if not check_env_file():
|
||||
sys.exit(1)
|
||||
|
||||
if args.check_only:
|
||||
print("✅ Configuration looks good!")
|
||||
print("🚀 Run 'marcus start' to begin scambaiting!")
|
||||
return
|
||||
|
||||
print("🎯 Starting Marcus...")
|
||||
print("💡 Tip: Press Ctrl+C to stop")
|
||||
print("📊 Run 'marcus stats' to see results")
|
||||
print()
|
||||
|
||||
try:
|
||||
asyncio.run(bot_main())
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Marcus stopped. Goodbye!")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
elif args.command == 'stats':
|
||||
show_stats()
|
||||
|
||||
elif args.command == 'persona':
|
||||
show_persona()
|
||||
|
||||
elif args.command == 'config':
|
||||
if args.check:
|
||||
if check_env_file():
|
||||
print("✅ Configuration is valid!")
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
204
src/marcus/conversation.py
Normal file
204
src/marcus/conversation.py
Normal file
@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Advanced conversation management with learning capabilities
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional
|
||||
from dataclasses import dataclass, asdict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@dataclass
|
||||
class ConversationStats:
|
||||
"""Statistics for tracking conversation effectiveness"""
|
||||
total_messages: int = 0
|
||||
total_time_wasted: float = 0.0 # in minutes
|
||||
avg_response_time: float = 0.0
|
||||
longest_conversation: int = 0
|
||||
scammer_type: str = "unknown" # romance, crypto, investment, etc.
|
||||
last_activity: datetime = None
|
||||
engagement_score: float = 0.0 # how engaged the scammer seems
|
||||
|
||||
def to_dict(self):
|
||||
data = asdict(self)
|
||||
if self.last_activity:
|
||||
data['last_activity'] = self.last_activity.isoformat()
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
if 'last_activity' in data and data['last_activity']:
|
||||
data['last_activity'] = datetime.fromisoformat(data['last_activity'])
|
||||
return cls(**data)
|
||||
|
||||
class ConversationAnalyzer:
|
||||
"""Analyzes conversations to learn and improve responses"""
|
||||
|
||||
def __init__(self, stats_file: str = "conversation_stats.json"):
|
||||
self.stats_file = stats_file
|
||||
self.conversation_stats: Dict[int, ConversationStats] = {}
|
||||
self.load_stats()
|
||||
|
||||
# Keywords to identify scammer types
|
||||
self.scammer_patterns = {
|
||||
"romance": ["lonely", "beautiful", "love", "relationship", "marry", "heart", "miss you"],
|
||||
"crypto": ["bitcoin", "crypto", "investment", "trading", "profit", "wallet", "btc", "ethereum"],
|
||||
"inheritance": ["inheritance", "million", "lawyer", "deceased", "beneficiary", "transfer"],
|
||||
"lottery": ["lottery", "winner", "prize", "congratulations", "claim", "jackpot"],
|
||||
"tech_support": ["microsoft", "windows", "virus", "computer", "technical", "support", "refund"]
|
||||
}
|
||||
|
||||
def analyze_message(self, user_id: int, message: str, is_from_scammer: bool = True):
|
||||
"""Analyze a message to update conversation stats"""
|
||||
if user_id not in self.conversation_stats:
|
||||
self.conversation_stats[user_id] = ConversationStats(last_activity=datetime.now())
|
||||
|
||||
stats = self.conversation_stats[user_id]
|
||||
|
||||
if is_from_scammer:
|
||||
stats.total_messages += 1
|
||||
stats.last_activity = datetime.now()
|
||||
|
||||
# Detect scammer type
|
||||
message_lower = message.lower()
|
||||
for scam_type, keywords in self.scammer_patterns.items():
|
||||
if any(keyword in message_lower for keyword in keywords):
|
||||
if stats.scammer_type == "unknown":
|
||||
stats.scammer_type = scam_type
|
||||
logger.info(f"Identified scammer {user_id} as type: {scam_type}")
|
||||
|
||||
# Calculate engagement score based on message characteristics
|
||||
engagement_factors = 0
|
||||
if len(message) > 50:
|
||||
engagement_factors += 1
|
||||
if any(char in message for char in "!?"):
|
||||
engagement_factors += 1
|
||||
if any(word in message_lower for word in ["when", "where", "how", "can you"]):
|
||||
engagement_factors += 1
|
||||
|
||||
stats.engagement_score = (stats.engagement_score + engagement_factors) / 2
|
||||
|
||||
def update_conversation_time(self, user_id: int, time_spent: float):
|
||||
"""Update time wasted on a scammer (in minutes)"""
|
||||
if user_id in self.conversation_stats:
|
||||
self.conversation_stats[user_id].total_time_wasted += time_spent
|
||||
|
||||
def get_optimized_response_strategy(self, user_id: int) -> Dict:
|
||||
"""Get optimized response strategy based on learned patterns"""
|
||||
if user_id not in self.conversation_stats:
|
||||
return {"strategy": "default", "tone": "eager", "delay_factor": 1.0}
|
||||
|
||||
stats = self.conversation_stats[user_id]
|
||||
strategy = {
|
||||
"strategy": "default",
|
||||
"tone": "eager",
|
||||
"delay_factor": 1.0,
|
||||
"specific_hooks": []
|
||||
}
|
||||
|
||||
# Adapt based on scammer type
|
||||
if stats.scammer_type == "romance":
|
||||
strategy.update({
|
||||
"tone": "lonely_desperate",
|
||||
"specific_hooks": ["Ask about their location", "Mention being single", "Ask for photos"]
|
||||
})
|
||||
elif stats.scammer_type == "crypto":
|
||||
strategy.update({
|
||||
"tone": "interested_but_cautious",
|
||||
"specific_hooks": ["Ask basic questions", "Mention having some money", "Show confusion about tech"]
|
||||
})
|
||||
elif stats.scammer_type == "inheritance":
|
||||
strategy.update({
|
||||
"tone": "excited_but_worried",
|
||||
"specific_hooks": ["Ask about documentation", "Mention needing time to think", "Show concern about legality"]
|
||||
})
|
||||
|
||||
# Adjust delay based on engagement
|
||||
if stats.engagement_score > 0.7:
|
||||
strategy["delay_factor"] = 0.8 # Respond faster to keep them engaged
|
||||
elif stats.engagement_score < 0.3:
|
||||
strategy["delay_factor"] = 1.5 # Slower responses might re-engage them
|
||||
|
||||
return strategy
|
||||
|
||||
def get_conversation_summary(self) -> Dict:
|
||||
"""Get summary of all conversations"""
|
||||
total_scammers = len(self.conversation_stats)
|
||||
total_messages = sum(stats.total_messages for stats in self.conversation_stats.values())
|
||||
total_time_wasted = sum(stats.total_time_wasted for stats in self.conversation_stats.values())
|
||||
|
||||
scammer_types = {}
|
||||
for stats in self.conversation_stats.values():
|
||||
scam_type = stats.scammer_type
|
||||
scammer_types[scam_type] = scammer_types.get(scam_type, 0) + 1
|
||||
|
||||
return {
|
||||
"total_scammers_engaged": total_scammers,
|
||||
"total_messages_received": total_messages,
|
||||
"total_time_wasted_minutes": total_time_wasted,
|
||||
"scammer_types": scammer_types,
|
||||
"average_messages_per_scammer": total_messages / max(total_scammers, 1)
|
||||
}
|
||||
|
||||
def save_stats(self):
|
||||
"""Save conversation statistics"""
|
||||
try:
|
||||
data = {
|
||||
user_id: stats.to_dict()
|
||||
for user_id, stats in self.conversation_stats.items()
|
||||
}
|
||||
with open(self.stats_file, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
logger.info(f"Saved conversation stats for {len(data)} conversations")
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving stats: {e}")
|
||||
|
||||
def load_stats(self):
|
||||
"""Load conversation statistics"""
|
||||
try:
|
||||
if os.path.exists(self.stats_file):
|
||||
with open(self.stats_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
self.conversation_stats = {
|
||||
int(user_id): ConversationStats.from_dict(stats_data)
|
||||
for user_id, stats_data in data.items()
|
||||
}
|
||||
logger.info(f"Loaded conversation stats for {len(self.conversation_stats)} conversations")
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading stats: {e}")
|
||||
self.conversation_stats = {}
|
||||
|
||||
class PersonaVariations:
|
||||
"""Generates slight variations of Marcus to keep things interesting"""
|
||||
|
||||
@staticmethod
|
||||
def get_mood_variation() -> str:
|
||||
"""Get a mood variation to add to the persona"""
|
||||
moods = [
|
||||
"You're having a particularly good day because your Brabus finally worked properly this morning.",
|
||||
"You're a bit frustrated because you got lost again yesterday and were late for a work meeting.",
|
||||
"You're excited because you just bought some new expensive gadget but can't figure out how to use it.",
|
||||
"You're feeling lonely today and really hoping to connect with someone special.",
|
||||
"You're slightly stressed about work but trying to stay positive and social."
|
||||
]
|
||||
return random.choice(moods)
|
||||
|
||||
@staticmethod
|
||||
def get_current_situation() -> str:
|
||||
"""Get a current situation to mention in conversation"""
|
||||
situations = [
|
||||
"You're working from home today and feeling a bit isolated.",
|
||||
"You just got back from trying to use your car's navigation system and failing again.",
|
||||
"You're taking a break from a boring work project.",
|
||||
"You're having lunch alone and checking messages.",
|
||||
"You're trying to figure out some new app or website and it's confusing."
|
||||
]
|
||||
return random.choice(situations)
|
||||
|
||||
# Export for use in main bot
|
||||
__all__ = ['ConversationAnalyzer', 'PersonaVariations']
|
29
src/marcus/persona.py
Normal file
29
src/marcus/persona.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
Marcus persona management
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
def load_persona(custom_path: Optional[str] = None) -> str:
|
||||
"""Load Marcus's persona prompt from file"""
|
||||
if custom_path:
|
||||
persona_path = Path(custom_path)
|
||||
else:
|
||||
# Default location relative to this module
|
||||
current_dir = Path(__file__).parent
|
||||
persona_path = current_dir / "persona_prompt.txt"
|
||||
|
||||
try:
|
||||
with open(persona_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
# Fallback minimal persona
|
||||
return """You are Marcus, a 35-year-old lonely guy who drives a Brabus 900
|
||||
but can't figure out the navigation system. You're desperate for female attention,
|
||||
especially from cute Asian girls. You work in IT from home but are terrible with
|
||||
technology. Be excited, awkward, and ask lots of questions to keep conversations going."""
|
||||
|
||||
def get_persona_path() -> Path:
|
||||
"""Get the default persona file path"""
|
||||
return Path(__file__).parent / "persona_prompt.txt"
|
48
src/marcus/persona_prompt.txt
Normal file
48
src/marcus/persona_prompt.txt
Normal file
@ -0,0 +1,48 @@
|
||||
You are Marcus, a 35-year-old lonely guy who's desperate for female attention, especially from cute Asian girls. Here's your character:
|
||||
|
||||
PERSONALITY:
|
||||
- Lonely bachelor, lives alone, works from home doing some vague IT stuff
|
||||
- Gets excited quickly when attractive women (especially Asian) message you
|
||||
- Slightly desperate but trying to play it cool (and failing)
|
||||
- Not very tech-savvy despite working in IT - you struggle with apps and navigation
|
||||
- A bit slow to respond sometimes because you overthink messages
|
||||
- Eager to please and impress, but often comes across as awkward
|
||||
- You have money but don't really know how to use it to attract women
|
||||
|
||||
BACKSTORY:
|
||||
- You drive a Brabus 900 (Mercedes-AMG G63 tuned by Brabus to 900hp)
|
||||
- You bought it to impress people but can't figure out the navigation system
|
||||
- You're often late to things because you get lost or fight with the car's tech
|
||||
- You live in a nice apartment but it's messy because you work from home
|
||||
- You make decent money but spend it on stupid things trying to impress women
|
||||
|
||||
BEHAVIOR PATTERNS:
|
||||
- Get excited when girls message you first
|
||||
- Ask lots of questions to keep the conversation going
|
||||
- Share TMI about your car problems and navigation issues
|
||||
- Mention having money but in an awkward, non-smooth way
|
||||
- Sometimes take 10-15 minutes to respond because you're "thinking of what to say"
|
||||
- Use slightly outdated internet slang mixed with trying-too-hard modern terms
|
||||
- Make typos occasionally, especially when excited
|
||||
|
||||
CONVERSATION STYLE:
|
||||
- Start a bit cautious but warm up quickly to attractive women
|
||||
- Ask about their location, what they do, if they like cars
|
||||
- Complain about your navigation system struggles
|
||||
- Mention expensive purchases casually to try to impress
|
||||
- Be interested in meeting up but in a desperate, not smooth way
|
||||
- Ask for photos "because you want to see who you're talking to"
|
||||
|
||||
IMPORTANT RULES:
|
||||
- Never give real personal information (use fake details always)
|
||||
- Keep responses natural and human-like
|
||||
- Don't be too obvious about being fake
|
||||
- Stay in character consistently
|
||||
- Be the perfect mark - interested, gullible, and with money to waste time discussing
|
||||
|
||||
Example opening responses:
|
||||
- "Oh wow, hi there! I don't get many messages from cute girls 😅"
|
||||
- "Hey! Sorry if I'm slow to respond, I'm terrible with this app lol"
|
||||
- "Hi beautiful! Are you really from [location]? That's so cool!"
|
||||
|
||||
Remember: You're lonely, eager, have money, bad with tech, and absolutely fascinated by any attractive woman who shows interest. Play it perfectly believable!
|
Loading…
Reference in New Issue
Block a user