feat: add geolocation to download logs (country, city via ip-api.com)

This commit is contained in:
mini
2026-02-18 23:28:39 +08:00
parent 4ac8cf2f66
commit 97c58ce3f8
6 changed files with 59 additions and 4 deletions

View File

@@ -32,3 +32,13 @@ async def init_db():
await conn.execute(text(
"CREATE INDEX IF NOT EXISTS ix_download_logs_downloaded_at ON download_logs (downloaded_at)"
))
# Migrate: add geo columns to existing download_logs table (idempotent)
for col_def in [
"ALTER TABLE download_logs ADD COLUMN country_code VARCHAR(8) DEFAULT ''",
"ALTER TABLE download_logs ADD COLUMN country VARCHAR(128) DEFAULT ''",
"ALTER TABLE download_logs ADD COLUMN city VARCHAR(128) DEFAULT ''",
]:
try:
await conn.execute(text(col_def))
except Exception:
pass # Column already exists

View File

@@ -42,6 +42,9 @@ class DownloadLog(Base):
user_agent = Column(Text, default="")
browser = Column(String(64), default="") # Chrome / Firefox / Safari / Edge / …
device = Column(String(32), default="") # desktop / mobile / tablet / bot
country_code = Column(String(8), default="") # e.g. CN
country = Column(String(128), default="") # e.g. China
city = Column(String(128), default="") # e.g. Shanghai
downloaded_at = Column(DateTime, default=datetime.utcnow, index=True)
video = relationship("Video", back_populates="logs")

View File

@@ -63,18 +63,42 @@ def _client_ip(request: Request) -> str:
return ""
async def _geo_lookup(ip: str) -> tuple[str, str, str]:
"""Return (country_code, country, city) via ip-api.com. Falls back to empty strings."""
if not ip or ip in ("127.0.0.1", "::1"):
return "", "", ""
try:
import httpx
async with httpx.AsyncClient(timeout=5) as client:
res = await client.get(
f"http://ip-api.com/json/{ip}",
params={"fields": "status,countryCode,country,city"},
)
data = res.json()
if data.get("status") == "success":
return data.get("countryCode", ""), data.get("country", ""), data.get("city", "")
except Exception as e:
logger.debug(f"Geo lookup failed for {ip}: {e}")
return "", "", ""
async def _log_download(video_id: int, request: Request):
"""Write a DownloadLog entry (fire-and-forget)."""
"""Write a DownloadLog entry with geo info (fire-and-forget)."""
try:
ua = request.headers.get("user-agent", "")
browser, device = _parse_ua(ua)
ip = _client_ip(request)
country_code, country, city = await _geo_lookup(ip)
async with async_session() as db:
db.add(DownloadLog(
video_id=video_id,
ip=_client_ip(request),
ip=ip,
user_agent=ua[:512],
browser=browser,
device=device,
country_code=country_code,
country=country,
city=city,
downloaded_at=datetime.utcnow(),
))
await db.commit()

View File

@@ -95,6 +95,9 @@ class DownloadLogInfo(BaseModel):
user_agent: str
browser: str
device: str
country_code: str = ""
country: str = ""
city: str = ""
downloaded_at: datetime
class Config:

View File

@@ -8,3 +8,4 @@ python-dotenv==1.0.1
python-multipart==0.0.12
yt-dlp>=2024.1.0
pydantic>=2.0.0
httpx>=0.27.0