135 lines
3.7 KiB
Python
135 lines
3.7 KiB
Python
"""Filmvorschläge fürs Rad.
|
|
|
|
Jede Sitzung darf einen Film vorschlagen. Die Sitzung erkennt ein Cookie —
|
|
ohne Anmeldung geht es nicht genauer, und für einen Filmabend im Freundeskreis
|
|
reicht es.
|
|
|
|
Titel, Jahr und Poster werden mitgespeichert. So bleibt ein Vorschlag
|
|
lesbar, auch wenn der Film nie im Katalog war oder Netflix ihn abgibt.
|
|
"""
|
|
|
|
import json
|
|
import random
|
|
import sqlite3
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from .db import connect
|
|
|
|
SCHEMA = """
|
|
CREATE TABLE IF NOT EXISTS suggestions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
session_id TEXT NOT NULL UNIQUE,
|
|
movie_id INTEGER NOT NULL,
|
|
title TEXT NOT NULL,
|
|
year INTEGER,
|
|
poster_path TEXT,
|
|
name TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
"""
|
|
|
|
COOKIE = "filmabend_sid"
|
|
|
|
# So lange läuft die Animation im Browser. Der Server merkt sich das
|
|
# Ergebnis mit, damit eine Seite, die mitten im Dreh geladen wird, es
|
|
# trotzdem bekommt.
|
|
DREH_DAUER = 4.2
|
|
|
|
|
|
def init():
|
|
with connect() as conn:
|
|
conn.executescript(SCHEMA)
|
|
|
|
|
|
def neue_session():
|
|
return uuid.uuid4().hex
|
|
|
|
|
|
def alle():
|
|
with connect() as conn:
|
|
rows = conn.execute(
|
|
"SELECT * FROM suggestions ORDER BY created_at ASC"
|
|
).fetchall()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
def von_session(session_id):
|
|
with connect() as conn:
|
|
row = conn.execute(
|
|
"SELECT * FROM suggestions WHERE session_id = ?", (session_id,)
|
|
).fetchone()
|
|
return dict(row) if row else None
|
|
|
|
|
|
def anlegen(session_id, movie, name=None):
|
|
"""Gibt den Vorschlag zurück, oder None wenn die Sitzung schon einen hat."""
|
|
eintrag = {
|
|
"session_id": session_id,
|
|
"movie_id": movie["id"],
|
|
"title": movie["title"],
|
|
"year": movie.get("year"),
|
|
"poster_path": movie.get("poster_path"),
|
|
"name": (name or "").strip()[:40] or None,
|
|
"created_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
}
|
|
try:
|
|
with connect() as conn:
|
|
conn.execute(
|
|
"INSERT INTO suggestions "
|
|
"(session_id, movie_id, title, year, poster_path, name, created_at) "
|
|
"VALUES (:session_id, :movie_id, :title, :year, :poster_path, "
|
|
":name, :created_at)",
|
|
eintrag,
|
|
)
|
|
except sqlite3.IntegrityError:
|
|
return None
|
|
return von_session(session_id)
|
|
|
|
|
|
def ersetzen(session_id, movie, name=None):
|
|
"""Wer schon vorgeschlagen hat, darf seinen Vorschlag ändern."""
|
|
with connect() as conn:
|
|
conn.execute("DELETE FROM suggestions WHERE session_id = ?", (session_id,))
|
|
return anlegen(session_id, movie, name)
|
|
|
|
|
|
def ziehen():
|
|
"""Server lost aus, damit alle Seiten dasselbe Ergebnis sehen.
|
|
|
|
Der Zielwinkel gehört dazu: sonst dreht jede Seite woanders hin und
|
|
der Zeiger stünde am Ende auf einem anderen Segment.
|
|
"""
|
|
eintraege = alle()
|
|
if not eintraege:
|
|
return None
|
|
|
|
treffer = random.randrange(len(eintraege))
|
|
schritt = 360 / len(eintraege)
|
|
runden = 5 + random.randrange(3)
|
|
# Der Zeiger steht oben, also muss die Mitte des Zielsegments dorthin
|
|
winkel = runden * 360 + (360 - (treffer * schritt + schritt / 2))
|
|
|
|
return {
|
|
"index": treffer,
|
|
"winkel": winkel,
|
|
"dauer": DREH_DAUER,
|
|
"gewinner": {
|
|
k: v for k, v in eintraege[treffer].items() if k != "session_id"
|
|
},
|
|
"anzahl": len(eintraege),
|
|
}
|
|
|
|
|
|
def loeschen(session_id):
|
|
with connect() as conn:
|
|
return conn.execute(
|
|
"DELETE FROM suggestions WHERE session_id = ?", (session_id,)
|
|
).rowcount
|
|
|
|
|
|
def alles_loeschen():
|
|
"""Host setzt die Runde zurück."""
|
|
with connect() as conn:
|
|
return conn.execute("DELETE FROM suggestions").rowcount
|