Save.TV: Archiv-Bewertung Tool + _get_archive POST-Fix

This commit is contained in:
root 2026-03-17 15:05:45 +07:00
parent 51abcb06eb
commit 7ac401c3a5

View file

@ -68,6 +68,17 @@ TOOLS = [
"parameters": {"type": "object", "properties": {}, "required": []}, "parameters": {"type": "object", "properties": {}, "required": []},
}, },
}, },
{
"type": "function",
"function": {
"name": "get_savetv_archive_filme",
"description": "Save.TV Archiv-Filme bewerten: Alle fertigen Aufnahmen holen, "
"nach Qualitaet bewerten, deduplizieren. Zeigt Top-Filme, dringende "
"(bald ablaufend) und weitere. Nutze bei 'gute Filme im Archiv', "
"'welche Filme habe ich', 'was ist sehenswert', 'Archiv bewerten'.",
"parameters": {"type": "object", "properties": {}, "required": []},
},
},
{ {
"type": "function", "type": "function",
"function": { "function": {
@ -87,9 +98,10 @@ TOOLS = [
SYSTEM_PROMPT_EXTRA = """TV / Save.TV Tools: SYSTEM_PROMPT_EXTRA = """TV / Save.TV Tools:
- get_savetv_tipps: Zeigt sehenswerte Spielfilme der naechsten Tage/Wochen - get_savetv_tipps: Zeigt sehenswerte Spielfilme der naechsten Tage/Wochen
- get_savetv_archive_filme: Bewertet alle fertigen Aufnahmen im Archiv nach Qualitaet
- savetv_record: Nimmt einen Film per TelecastId auf - savetv_record: Nimmt einen Film per TelecastId auf
- get_savetv_status: Zeigt Archiv und geplante Aufnahmen - get_savetv_status: Zeigt Archiv und geplante Aufnahmen
Wenn der User einen Film aufnehmen will, nutze savetv_record mit der TelecastId. Wenn der User nach Archiv-Filmen/Bewertung fragt, nutze get_savetv_archive_filme.
""" """
@ -159,17 +171,21 @@ def _save_seen(seen):
def _get_archive(state=0, count=20): def _get_archive(state=0, count=20):
"""Archiv abrufen. state: 0=geplant, 1=fertig.""" """Archiv abrufen (POST, wie Web-UI). state: 0=geplant, 1=fertig."""
s = _get_session() s = _get_session()
if not s: if not s:
return {"error": "Login fehlgeschlagen"} return {"error": "Login fehlgeschlagen"}
try: try:
r = s.get( end = datetime.now().strftime("%Y-%m-%d")
start = (datetime.now() - timedelta(days=60)).strftime("%Y-%m-%d")
r = s.post(
SAVETV_URL + "/STV/M/obj/archive/JSON/VideoArchiveApi.cfm", SAVETV_URL + "/STV/M/obj/archive/JSON/VideoArchiveApi.cfm",
params={ data={
"bAggregateEntries": "false", "bAggregateEntries": "false",
"iEntriesPerPage": str(count), "iEntriesPerPage": str(count),
"iRecordingState": str(state), "iRecordingState": str(state),
"dStartdate": start,
"dEnddate": end,
}, },
headers={"X-Requested-With": "XMLHttpRequest"}, headers={"X-Requested-With": "XMLHttpRequest"},
timeout=15, timeout=15,
@ -179,6 +195,40 @@ def _get_archive(state=0, count=20):
return {"error": str(e)} return {"error": str(e)}
def _get_full_archive():
"""Alle fertigen Aufnahmen paginiert holen."""
s = _get_session()
if not s:
return []
end = datetime.now().strftime("%Y-%m-%d")
start = (datetime.now() - timedelta(days=60)).strftime("%Y-%m-%d")
all_entries = []
for page in range(1, 20):
try:
r = s.post(
SAVETV_URL + "/STV/M/obj/archive/JSON/VideoArchiveApi.cfm",
data={
"bAggregateEntries": "false",
"iEntriesPerPage": "100",
"iCurrentPage": str(page),
"iRecordingState": "1",
"dStartdate": start,
"dEnddate": end,
},
headers={"X-Requested-With": "XMLHttpRequest"},
timeout=15,
)
data = r.json()
entries = data.get("ARRVIDEOARCHIVEENTRIES", [])
if not entries:
break
all_entries.extend(entries)
except Exception as e:
log.error("Archive page %d: %s", page, e)
break
return all_entries
def _scrape_epg(): def _scrape_epg():
"""Holt Filme aus Save.TV Programmseiten (JSON im HTML). """Holt Filme aus Save.TV Programmseiten (JSON im HTML).
@ -342,9 +392,154 @@ def _format_film(f, with_tid=True):
return "\n".join(lines) return "\n".join(lines)
DOKU_KEYWORDS = {
"schlangen", "giftig", "gefährlich", "tiere", "tierwelt",
"wildtiere", "safari", "ozean", "meer", "ozeane",
"doku", "dokumentation", "reportage", "magazin",
"botschafter der meere", "dynastie", "leoparden",
"schildkröten", "wale", "australien", "afrika",
"gehirn unter strom",
}
def _score_archive_film(title, station, highlight, subtitle="", thema=""):
"""Bewertet einen Archiv-Film heuristisch (0-100)."""
t = title.lower()
s = station.lower()
if "programmänderung" in t:
return -1
for kw in DOKU_KEYWORDS:
if kw in t:
return -1
score = 50
premium_stations = {"arte", "zdf", "das erste", "mdr", "swr", "ndr", "wdr", "br"}
action_stations = {"prosieben", "sat.1", "kabel 1", "vox", "rtl", "tele 5", "zdf_neo"}
if s in premium_stations:
score += 5
elif s in action_stations:
score += 3
if highlight:
score += 15
desc = (thema or subtitle or "").lower()
if len(desc) > 30:
score += 5
quality_hints = [
"oscar", "golden globe", "cannes", "berlinale", "venedig",
"preisgekrönt", "meisterwerk", "bestseller", "basiert auf",
]
for hint in quality_hints:
if hint in desc or hint in t:
score += 10
break
if any(c.isascii() and c.isalpha() for c in title) and not all(c.isascii() for c in title if c.isalpha()):
pass
elif re.search(r'[A-Z][a-z]+ [A-Z][a-z]+', title) and not re.search(r'[äöüÄÖÜß]', title):
score += 8
return score
def handle_get_savetv_archive_filme(**kw):
"""Alle fertigen Archiv-Filme holen, bewerten, deduplizieren, sortiert ausgeben."""
entries = _get_full_archive()
if not entries:
return "Keine Archiv-Eintraege gefunden."
films = []
seen_titles = {}
series_count = 0
for e in entries:
tc = e.get("STRTELECASTENTRY", {})
episode = tc.get("SFOLGE", "")
if episode:
series_count += 1
continue
title = tc.get("STITLE", "?")
station = tc.get("STVSTATIONNAME", "?")
highlight = tc.get("BISHIGHLIGHT", False)
subtitle = tc.get("SSUBTITLE", "")
thema = tc.get("STHEMA", "")
date = tc.get("DSTARTDATE", "?")[:10]
days_left = int(tc.get("IDAYSLEFTBEFOREDELETE", 0))
tid = int(tc.get("ITELECASTID", 0))
score = _score_archive_film(title, station, highlight, subtitle, thema)
if score < 0:
continue
key = title.lower().strip()
if key in seen_titles:
if days_left > seen_titles[key]["days_left"]:
seen_titles[key]["days_left"] = days_left
seen_titles[key]["date"] = date
seen_titles[key]["tid"] = tid
continue
seen_titles[key] = {
"title": title, "station": station, "date": date,
"days_left": days_left, "score": score, "tid": tid,
"highlight": highlight,
}
films = sorted(seen_titles.values(), key=lambda x: (-x["score"], x["days_left"]))
total_archive = len(entries)
urgent = sorted(
[f for f in films if f["days_left"] <= 7],
key=lambda x: (x["days_left"], -x["score"]),
)
lines = [
f"Save.TV Archiv-Bewertung: {len(films)} Filme "
f"(von {total_archive} Aufnahmen, {series_count} Serien-Episoden gefiltert)\n"
]
if urgent:
lines.append(f"DRINGEND — {len(urgent)} Filme laufen in <=7 Tagen ab:")
for f in urgent:
hl = " *" if f["highlight"] else ""
lines.append(
f" [{f['days_left']}d] {f['title'][:45]} | {f['station']} | "
f"Score {f['score']}{hl} | TID {f['tid']}"
)
lines.append("")
top = [f for f in films if f["score"] >= 60 and f["days_left"] > 7]
if top:
lines.append(f"TOP-FILME ({len(top)}):")
for f in top[:30]:
hl = " *" if f["highlight"] else ""
lines.append(
f" {f['title'][:45]:45s} | {f['station']:12s} | "
f"{f['date']} | {f['days_left']:2d}d | Score {f['score']}{hl}"
)
lines.append("")
rest = [f for f in films if f["score"] < 60 and f["days_left"] > 7]
if rest:
lines.append(f"WEITERE ({len(rest)}):")
for f in rest[:20]:
lines.append(
f" {f['title'][:45]:45s} | {f['station']:12s} | "
f"{f['date']} | {f['days_left']:2d}d"
)
return "\n".join(lines)
def handle_get_savetv_status(**kw): def handle_get_savetv_status(**kw):
archive = _get_archive(state=1, count=5) archive = _get_archive(state=1, count=20)
planned = _get_archive(state=0, count=10) planned = _get_archive(state=0, count=20)
if "error" in archive: if "error" in archive:
return "Save.TV Fehler: " + archive["error"] return "Save.TV Fehler: " + archive["error"]
@ -352,12 +547,13 @@ def handle_get_savetv_status(**kw):
lines = ["Save.TV Status\n"] lines = ["Save.TV Status\n"]
total = int(archive.get("ITOTALENTRIESINARCHIVE", 0)) total = int(archive.get("ITOTALENTRIESINARCHIVE", 0))
lines.append("Archiv: " + str(total) + " Aufnahmen gesamt") fertig_total = int(archive.get("ITOTALENTRIES", 0))
lines.append(f"Archiv: {total} Aufnahmen gesamt, {fertig_total} fertig")
fertig = archive.get("ARRVIDEOARCHIVEENTRIES", []) fertig = archive.get("ARRVIDEOARCHIVEENTRIES", [])
if fertig: if fertig:
lines.append("\nLetzte fertige Aufnahmen:") lines.append("\nLetzte fertige Aufnahmen:")
for e in fertig[:5]: for e in fertig[:10]:
tc = e.get("STRTELECASTENTRY", {}) tc = e.get("STRTELECASTENTRY", {})
lines.append( lines.append(
" " + tc.get("STITLE", "?")[:40] + " | " " " + tc.get("STITLE", "?")[:40] + " | "
@ -368,7 +564,7 @@ def handle_get_savetv_status(**kw):
geplant = planned.get("ARRVIDEOARCHIVEENTRIES", []) geplant = planned.get("ARRVIDEOARCHIVEENTRIES", [])
plan_total = int(planned.get("ITOTALENTRIES", 0)) plan_total = int(planned.get("ITOTALENTRIES", 0))
if geplant: if geplant:
lines.append("\nGeplante Aufnahmen (" + str(plan_total) + "):") lines.append(f"\nGeplante Aufnahmen ({plan_total}):")
for e in geplant[:10]: for e in geplant[:10]:
tc = e.get("STRTELECASTENTRY", {}) tc = e.get("STRTELECASTENTRY", {})
lines.append( lines.append(
@ -455,5 +651,6 @@ def handle_savetv_record(telecast_id=0, **kw):
HANDLERS = { HANDLERS = {
"get_savetv_status": handle_get_savetv_status, "get_savetv_status": handle_get_savetv_status,
"get_savetv_tipps": handle_get_savetv_tipps, "get_savetv_tipps": handle_get_savetv_tipps,
"get_savetv_archive_filme": handle_get_savetv_archive_filme,
"savetv_record": handle_savetv_record, "savetv_record": handle_savetv_record,
} }