From 775264076bf07a11abfb8818f8c74c71ca8919aa Mon Sep 17 00:00:00 2001 From: sandro Date: Sun, 9 Aug 2026 09:39:56 +0200 Subject: [PATCH] Initialer Commit --- __pycache__/app.cpython-312.pyc | Bin 0 -> 3093 bytes __pycache__/dl_csv.cpython-312.pyc | Bin 0 -> 6957 bytes app.py | 67 +++++++ data.json | 1 + dennsen86 | 1 + dl_csv.py | 155 ++++++++++++++++ html/chroma.html | 276 +++++++++++++++++++++++++++++ html/index.html | 276 +++++++++++++++++++++++++++++ 8 files changed, 776 insertions(+) create mode 100644 __pycache__/app.cpython-312.pyc create mode 100644 __pycache__/dl_csv.cpython-312.pyc create mode 100644 app.py create mode 100644 data.json create mode 100644 dennsen86 create mode 100644 dl_csv.py create mode 100644 html/chroma.html create mode 100644 html/index.html diff --git a/__pycache__/app.cpython-312.pyc b/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16a231f1549fa7e29c38c54005efa335fbb1e552 GIT binary patch literal 3093 zcmb_eO>7%Q6n?X_YkT9`o5qdv*N}uZZ5$wh2AZmWA_a;NqEw^@YN=SRcbly1zs|0c zCUv5=R3btMRYHPDAkhPdR;k3#1+L-BC8<>sZCa`PT)3H*11cBZjP1=9QVJ4d?RztC z-W%`C_sx6zT`1H5*yc`5q>FxlpZK8Oa4g!r_X)s9po2~_kR@?Kcwfp$aY<+zr$Wnd z1vSn1vi`Wg<`akq#GD#eg>Hy9NMQIEYD>q1Mlju08+epKba{;Eijdg5?psi*(*!W0 z!{b`(>iz|MOWyRrC2-ddKg3&gy&DYel%xkQ$!<*JLt-|$F(DBX_QY&{NQ~yjRR5>Z z6Vqfw^v$RH09Xs2Ng$2Azc#%|kLX);<$CjiT%98@B~3}O=r4R2BTo2**WVa@&D8ab zF_~n>5j6P^CoOyA=qnECKNgdnP%@LrPbTur95Zv4v$;AnA)4+rX~v2Vgs} z)*0lMUFRE=z&$nT6DcJfN50L#ER5FlB$^f*kC7(H_Mn&kMcVk%`=mZKPq8GJx^g0=)Un#?Mx^4a6 zio@P14|Io-KVCf%GcExZGR=juHqRK2?~p<0n<|AkOv zoJ8xS+JmH`CHnu7D(xl;JcU|YCk}!4_9EdV69f&B&ZA%l>V6&?io(b3f`&5EFjV&{ z$s5p6o|Od+Q9nRK)`DTIlM8-rYOM3GvyQc++3_U{1q1U7=bMLsN;Q&KG$+UmyU21P zU3tjJ?;$5+jg1k`M$E*Vk#*o$5Bl)>VSRvtvoiR1R<_Osz0~ZlQZv1*^{i++m$jW` zEq3+I&)>TCR{5y|W$nPK*1n?k;OvzhH?g8gr01?BYP8!+}Sd2nO0&f@+bB&(;Ih&(q#Y+kcO zE0H?T8`LDsgPxis)jf6UW+rJC$%VBEwTBY&5rJuq2(ri+0w53Uw8{g)8j*4ME4dIy zJrHL-C~rfjVSKFTQE-$ML^<-<{MmA(?`r#U7L1B(g~}mq<+ek+gg9nf5c}P#DO%A0z{*JhK?zokMv+mYCs$i!75dPYy8SOwqt)eAThzuZ1(7 zejR^t!o=X5xJZtiws_&jcX7f=T~Dn4gG2GOL@gF6-E-XhxcFvx*-RuH%8I#ba1V_j z^7{S2{%l?^W{iW3A1W`V)*mRaG!t?gcCA6f8mMc~d>cB}AiM^lH3E& zC3Sn5^j0L549#!53#ho$4q>Q7U8lY>iB+UcWatVyq2f;c!cf`jIz3e;0~N`KPNR46 w4TRI6FjSgA-ZVFTgSOp~wev5(^Xi3HFRNG7FXV5IeJ>xXC?L19-MIfh0s6|S(f|Me literal 0 HcmV?d00001 diff --git a/__pycache__/dl_csv.cpython-312.pyc b/__pycache__/dl_csv.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7840905220b2478d8e89f3dd3e6c9d0cfcb6d05f GIT binary patch literal 6957 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 1 else "" + title_parsed = parse_title(raw_title) + + # Zeile 2 (Index 1): Meta Info aus Spalte B (Index 1) + meta_b2 = rows[1][1] if len(rows[1]) > 1 else "" + deadline = rows[1][8] if len(rows[1]) > 8 else "" # Falls Deadline in Spalte I steht + + # Ab Zeile 4 (Index 3): Spieler-Leaderboard + leaderboard = [] + for row in rows[3:]: + # Nur wenn ein Spielername in Spalte F (Index 5) existiert + if len(row) > 5 and row[5].strip(): + player_name = row[5].strip() + division = row[0].strip() if len(row) > 0 else "" + match division: + case "C": + division = "Champion" + case "M": + division = "Master" + case "D": + division = "Diamond" + case "P": + division = "Platin" + case "G": + division = "Gold" + case "S": + division = "Silver" + case "B": + division = "Bronze" + rank_div = row[2].strip() if len(row) > 2 else "" + + # Rang säubern, z. B. "(1)" -> 1 + rank_overall_raw = row[3].strip() if len(row) > 3 else "" + rank_overall_clean = re.sub(r"[^\d]", "", rank_overall_raw) + + part1 = row[6].strip() if len(row) > 6 else "" + part2 = row[7].strip() if len(row) > 7 else "" + total_time = row[8].strip() if len(row) > 8 else "" + online_str = row[9].strip().lower() #if len(row) > 9 else "" + leaderboard.append({ + "division": division, + "divisionRank": int(rank_div) if rank_div.isdigit() else 0, + "overallRank": int(rank_overall_clean) if rank_overall_clean.isdigit() else None, + "player": player_name, + "part1": part1, + "part2": part2, + "totalTime": total_time, + "isOnline": online_str in ["on", "online", "true", "1", "ja"], + }) + + return { + "title": raw_title.strip(), + "details": title_parsed, + "meta": { + **parse_meta(meta_b2), + "deadline": deadline, + }, + "leaderboardCount": len(leaderboard), + "leaderboard": leaderboard, + } + + +def update_data(): + try: + req = urllib.request.Request(CSV_URL, headers={"User-Agent": "Mozilla/5.0"}) + response = urllib.request.urlopen(req) + lines = [line.decode("utf-8") for line in response.readlines()] + + reader = csv.reader(lines) + parsed_data = parse_csv_data(reader) + with open("data.json", "w") as f: + f.write(json.dumps(parsed_data)) + return parsed_data + + except Exception as e: + return {"success": False, "error": str(e)} + + +def read_data(): + file_path = "data.json" + max_age_seconds = 60 # 1 Minute Cache-Dauer + + # Prüfen, ob die Datei existiert und wie alt sie ist + if os.path.exists(file_path): + # Mtime = Letzte Änderungszeit der Datei in Sekunden seit Epoch + file_age = time.time() - os.path.getmtime(file_path) + + if file_age > max_age_seconds: + update_data() + else: + # Falls die Datei noch gar nicht existiert, direkt initial Daten holen + update_data() + + # Nach dem potenziellen Update die Datei einlesen + with open(file_path, "r", encoding="utf-8") as f: + return json.load(f) + + +if __name__ == "__main__": + # bindet an 0.0.0.0, damit es im gesamten Netzwerk erreichbar ist + read_data() diff --git a/html/chroma.html b/html/chroma.html new file mode 100644 index 0000000..e01632a --- /dev/null +++ b/html/chroma.html @@ -0,0 +1,276 @@ + + + + + + Ninji Leaderboard Ticker + + + + +
+
+
+ + + + diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..168b102 --- /dev/null +++ b/html/index.html @@ -0,0 +1,276 @@ + + + + + + Ninji Leaderboard Ticker + + + + +
+
+
+ + + +