mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-11-06 22:23:08 +01:00
99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
"""
|
|
The only module route code touches.
|
|
|
|
@tracked("channel") wrap a stream handler: one event per call,
|
|
def _handle_channel_stream(provider, channel_id): ...
|
|
|
|
annotate(is_catchup=..., proxy=...) optional extra fields for the current request
|
|
|
|
Cost on the request thread when analytics is enabled: a handful of attribute
|
|
reads, one dict literal and one deque.append. When disabled: a single
|
|
`get_emitter() is None` check. Everything is wrapped so that no analytics
|
|
failure can change a response.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import time
|
|
from typing import Any, Callable
|
|
|
|
from bottle import HTTPResponse, request, response
|
|
|
|
from .emitter import get_emitter
|
|
from .events import (
|
|
DELIVERY_ERROR,
|
|
DELIVERY_PROXIED,
|
|
DELIVERY_REDIRECT,
|
|
EVENT_MANIFEST_REQUEST,
|
|
)
|
|
|
|
_ENV_KEY = "analytics.extra"
|
|
_UA_MAX = 256
|
|
|
|
|
|
def annotate(**fields: Any) -> None:
|
|
"""Attach extra fields to the event of the current request. Never raises."""
|
|
try:
|
|
request.environ.setdefault(_ENV_KEY, {}).update(fields)
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
|
|
|
|
def _classify(status: int, redirected: bool) -> str:
|
|
if status >= 400:
|
|
return DELIVERY_ERROR
|
|
return DELIVERY_REDIRECT if redirected else DELIVERY_PROXIED
|
|
|
|
|
|
def _emit(emitter, content_type: str, provider: str, content_id: str,
|
|
status: int, delivery: str) -> None:
|
|
try:
|
|
ua = request.headers.get("User-Agent")
|
|
raw = {
|
|
"event_type": EVENT_MANIFEST_REQUEST,
|
|
"ts": time.time(),
|
|
"client_ip": request.remote_addr,
|
|
"provider": provider,
|
|
"channel_id": content_id,
|
|
"content_type": content_type,
|
|
"http_status": status,
|
|
"delivery": delivery,
|
|
"user_agent": ua[:_UA_MAX] if ua else None,
|
|
"country": request.query.get("country") or None,
|
|
}
|
|
extra = request.environ.get(_ENV_KEY)
|
|
if extra:
|
|
raw.update(extra)
|
|
emitter.emit(raw)
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
|
|
|
|
def tracked(content_type: str) -> Callable:
|
|
"""
|
|
Decorator for `handler(provider, content_id, ...)`. Emits exactly one event
|
|
per call, including for redirects (which Bottle raises as HTTPResponse),
|
|
handler-set error statuses and unexpected exceptions. Never alters the
|
|
handler's return value, raised exception, or response.
|
|
"""
|
|
def decorator(fn: Callable) -> Callable:
|
|
@functools.wraps(fn)
|
|
def wrapper(provider, content_id, *args, **kwargs):
|
|
emitter = get_emitter()
|
|
if emitter is None:
|
|
return fn(provider, content_id, *args, **kwargs)
|
|
|
|
status, delivery = 500, DELIVERY_ERROR
|
|
try:
|
|
result = fn(provider, content_id, *args, **kwargs)
|
|
status = response.status_code
|
|
delivery = _classify(status, redirected=False)
|
|
return result
|
|
except HTTPResponse as exc:
|
|
status = exc.status_code
|
|
delivery = _classify(status, redirected=300 <= status < 400)
|
|
raise
|
|
finally:
|
|
_emit(emitter, content_type, provider, content_id, status, delivery)
|
|
return wrapper
|
|
return decorator |