214 lines
6.7 KiB
Python
214 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Simple Email Tracking Server
|
|
|
|
A minimal Flask application that serves transparent tracking pixels
|
|
and logs email opens with metadata.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import logging
|
|
from datetime import datetime
|
|
from flask import Flask, request, send_file, render_template_string
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler('/root/Tools/tracker/data/tracker.log'),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
|
|
# Create Flask app
|
|
app = Flask(__name__)
|
|
|
|
# Directory to store tracking data
|
|
DATA_DIR = '/root/Tools/tracker/data'
|
|
os.makedirs(DATA_DIR, exist_ok=True)
|
|
|
|
# Path to 1x1 transparent pixel
|
|
PIXEL_PATH = os.path.join(DATA_DIR, 'pixel.png')
|
|
|
|
# Create 1x1 transparent PNG if it doesn't exist
|
|
if not os.path.exists(PIXEL_PATH):
|
|
from PIL import Image
|
|
img = Image.new('RGBA', (1, 1), color=(0, 0, 0, 0))
|
|
img.save(PIXEL_PATH)
|
|
|
|
# Path to tracking data
|
|
TRACKING_DATA_PATH = os.path.join(DATA_DIR, 'tracking_data.json')
|
|
|
|
def load_tracking_data():
|
|
"""Load existing tracking data from JSON file"""
|
|
if os.path.exists(TRACKING_DATA_PATH):
|
|
try:
|
|
with open(TRACKING_DATA_PATH, 'r') as f:
|
|
return json.load(f)
|
|
except json.JSONDecodeError:
|
|
logging.error("Error loading tracking data, starting fresh")
|
|
return {}
|
|
|
|
def save_tracking_data(data):
|
|
"""Save tracking data to JSON file"""
|
|
with open(TRACKING_DATA_PATH, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
@app.route('/pixel/<tracking_id>.png')
|
|
def tracking_pixel(tracking_id):
|
|
"""Serve a tracking pixel and log the request"""
|
|
# Get client information
|
|
user_agent = request.headers.get('User-Agent', 'Unknown')
|
|
ip_address = request.remote_addr
|
|
timestamp = datetime.now().isoformat()
|
|
referer = request.headers.get('Referer', 'Unknown')
|
|
|
|
# Log the tracking event
|
|
logging.info(f"Pixel loaded - ID: {tracking_id}, IP: {ip_address}")
|
|
|
|
# Add tracking event to data
|
|
tracking_data = load_tracking_data()
|
|
|
|
if tracking_id not in tracking_data:
|
|
tracking_data[tracking_id] = []
|
|
|
|
tracking_data[tracking_id].append({
|
|
'timestamp': timestamp,
|
|
'ip_address': ip_address,
|
|
'user_agent': user_agent,
|
|
'referer': referer
|
|
})
|
|
|
|
save_tracking_data(tracking_data)
|
|
|
|
# Return the 1x1 transparent pixel
|
|
return send_file(PIXEL_PATH, mimetype='image/png')
|
|
|
|
@app.route('/')
|
|
def dashboard():
|
|
"""Display tracking statistics dashboard"""
|
|
tracking_data = load_tracking_data()
|
|
|
|
# Prepare data for the dashboard
|
|
stats = []
|
|
for tracking_id, events in tracking_data.items():
|
|
stats.append({
|
|
'id': tracking_id,
|
|
'views': len(events),
|
|
'last_view': events[-1]['timestamp'] if events else 'Never',
|
|
'unique_ips': len(set(e['ip_address'] for e in events))
|
|
})
|
|
|
|
# Sort by most views
|
|
stats.sort(key=lambda x: x['views'], reverse=True)
|
|
|
|
# Simple HTML dashboard template
|
|
template = """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Email Tracking Dashboard</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
|
h1 { color: #333; }
|
|
table { border-collapse: collapse; width: 100%; }
|
|
th, td { text-align: left; padding: 8px; border-bottom: 1px solid #ddd; }
|
|
tr:hover { background-color: #f5f5f5; }
|
|
th { background-color: #4CAF50; color: white; }
|
|
.container { max-width: 800px; margin: 0 auto; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Email Tracking Dashboard</h1>
|
|
<p>To track email opens, add this HTML to your emails:</p>
|
|
<pre><img src="https://YOUR_DOMAIN/px/YOUR_TRACKING_ID.png" height="1" width="1" /></pre>
|
|
|
|
<h2>Tracking Statistics</h2>
|
|
<table>
|
|
<tr>
|
|
<th>Tracking ID</th>
|
|
<th>Views</th>
|
|
<th>Unique IPs</th>
|
|
<th>Last View</th>
|
|
<th>Details</th>
|
|
</tr>
|
|
{% for stat in stats %}
|
|
<tr>
|
|
<td>{{ stat.id }}</td>
|
|
<td>{{ stat.views }}</td>
|
|
<td>{{ stat.unique_ips }}</td>
|
|
<td>{{ stat.last_view }}</td>
|
|
<td><a href="/details/{{ stat.id }}">View Details</a></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
return render_template_string(template, stats=stats)
|
|
|
|
@app.route('/details/<tracking_id>')
|
|
def tracking_details(tracking_id):
|
|
"""Display detailed tracking information for a specific ID"""
|
|
tracking_data = load_tracking_data()
|
|
|
|
if tracking_id not in tracking_data:
|
|
return f"No data found for tracking ID: {tracking_id}", 404
|
|
|
|
events = tracking_data[tracking_id]
|
|
|
|
# Simple HTML template for details
|
|
template = """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Tracking Details: {{ tracking_id }}</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
|
h1, h2 { color: #333; }
|
|
table { border-collapse: collapse; width: 100%; }
|
|
th, td { text-align: left; padding: 8px; border-bottom: 1px solid #ddd; }
|
|
tr:hover { background-color: #f5f5f5; }
|
|
th { background-color: #4CAF50; color: white; }
|
|
.container { max-width: 800px; margin: 0 auto; }
|
|
.back { margin-bottom: 20px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="back"><a href="/">« Back to Dashboard</a></div>
|
|
<h1>Tracking Details: {{ tracking_id }}</h1>
|
|
<p>Total views: {{ events|length }}</p>
|
|
|
|
<h2>Events</h2>
|
|
<table>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>IP Address</th>
|
|
<th>User Agent</th>
|
|
<th>Referer</th>
|
|
</tr>
|
|
{% for event in events %}
|
|
<tr>
|
|
<td>{{ event.timestamp }}</td>
|
|
<td>{{ event.ip_address }}</td>
|
|
<td>{{ event.user_agent }}</td>
|
|
<td>{{ event.referer }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
return render_template_string(template, tracking_id=tracking_id, events=events)
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='127.0.0.1', port=5000, debug=False) |