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