import hashlib
from user_agents import parse
# Common bot patterns
BOT_PATTERNS = [
"googlebot", "bingbot", "yandexbot", "baidubot", "duckduckbot",
"slurp", "crawler", "spider", "bot", "scraper", "monitor",
"curl", "wget", "python-requests", "httpie", "postman",
"facebookexternalhit", "twitterbot", "linkedinbot", "slackbot",
"discordbot", "telegrambot", "whatsapp", "applebot",
]
def is_bot(user_agent: str) -> bool:
"""Check if a user agent is a known bot."""
ua_lower = user_agent.lower()
return any(pattern in ua_lower for pattern in BOT_PATTERNS)
def parse_user_agent(ua_string: str) -> dict:
"""Parse user agent string into browser, os, device info."""
parsed = parse(ua_string)
return {
"browser": parsed.browser.family if parsed.browser else "Unknown",
"os": parsed.os.family if parsed.os else "Unknown",
"device": parsed.device.family if parsed.device else "Unknown",
"is_mobile": parsed.is_mobile,
"is_tablet": parsed.is_tablet,
"is_bot": parsed.is_bot,
}
def classify_device(parsed_ua: dict) -> str:
"""Classify device into a simple category."""
if parsed_ua["is_mobile"]:
return "mobile"
if parsed_ua["is_tablet"]:
return "tablet"
return "desktop"
def hash_ip(ip: str) -> str:
"""Hash an IP address for privacy."""
return hashlib.sha256(ip.encode()).hexdigest()[:16]
def get_country_from_ip(ip: str) -> str:
"""
Simple geo lookup. For production, use MaxMind GeoIP2 database.
For now, return 'Unknown' — geo can be added later.
"""
return "Unknown"
def process_visitor(client_ip: str, headers: dict, url: str) -> dict:
"""Process a visitor request and extract analytics data."""
ua_string = headers.get("User-Agent", "Unknown")
referrer = headers.get("Referer", headers.get("Referer", ""))
parsed_ua = parse_user_agent(ua_string)
bot = parsed_ua["is_bot"] or is_bot(ua_string)
return {
"page": url,
"referrer": referrer,
"country": get_country_from_ip(client_ip),
"city": "Unknown",
"browser": parsed_ua["browser"],
"os": parsed_ua["os"],
"device": classify_device(parsed_ua),
"is_bot": 1 if bot else 0,
"ip_hash": hash_ip(client_ip),
}