mirror of
https://github.com/yusufipk/deste-hearthstone-linux-tracker.git
synced 2026-09-11 10:46:12 +00:00
Reads the game's own Power.log: no screen capture, no image recognition, no memory reading. - deck tracking (remaining cards, draw chance, played cards fade in place) - opponent panel (revealed cards, hand and deck counts) - decks read from the game's offline cache, matched to the match - card art in rows, full card on hover - overlay and window modes, adjustable transparency, tray icon - English and Turkish interface - consistency tests over a real log corpus, cross-checked against Zone.log
97 lines
3.1 KiB
Python
97 lines
3.1 KiB
Python
"""Kart verisi: HearthstoneJSON'dan indirir, diske cache'ler.
|
||
|
||
Not: api.hearthstonejson.com varsayılan urllib User-Agent'ına 403 dönüyor,
|
||
bu yüzden kendi UA'mızı göndermek zorunlu.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
import time
|
||
import urllib.error
|
||
import urllib.request
|
||
from pathlib import Path
|
||
|
||
CARDS_URL = "https://api.hearthstonejson.com/v1/latest/enUS/cards.json"
|
||
USER_AGENT = "deste/0.1 (kisisel hearthstone tracker; +local)"
|
||
CACHE_DIR = Path(os.environ.get("XDG_CACHE_HOME", "~/.cache")).expanduser() / "deste"
|
||
CARDS_PATH = CACHE_DIR / "cards.json"
|
||
MAX_AGE_SECONDS = 7 * 24 * 3600
|
||
|
||
|
||
def _download(url: str, timeout: int = 60) -> bytes:
|
||
request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||
return response.read()
|
||
|
||
|
||
def cache_is_stale(path: Path = CARDS_PATH, max_age: int = MAX_AGE_SECONDS) -> bool:
|
||
if not path.exists():
|
||
return True
|
||
return (time.time() - path.stat().st_mtime) > max_age
|
||
|
||
|
||
def update_cache(force: bool = False) -> bool:
|
||
"""Kart verisini indirir. Ağ yoksa mevcut cache korunur."""
|
||
if not force and not cache_is_stale():
|
||
return True
|
||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||
try:
|
||
payload = _download(CARDS_URL)
|
||
except (urllib.error.URLError, TimeoutError, OSError):
|
||
return CARDS_PATH.exists()
|
||
tmp = CARDS_PATH.with_suffix(".tmp")
|
||
tmp.write_bytes(payload)
|
||
tmp.replace(CARDS_PATH)
|
||
return True
|
||
|
||
|
||
class CardDB:
|
||
"""Kart kimliğinden isim, mana, sınıf gibi alanlara erişim."""
|
||
|
||
def __init__(self, cards: list[dict]):
|
||
self.by_id: dict[str, dict] = {c["id"]: c for c in cards if "id" in c}
|
||
self.by_dbf: dict[int, dict] = {c["dbfId"]: c for c in cards if "dbfId" in c}
|
||
|
||
@classmethod
|
||
def load(cls, path: Path = CARDS_PATH, auto_update: bool = True) -> "CardDB":
|
||
if auto_update:
|
||
update_cache()
|
||
if not path.exists():
|
||
raise FileNotFoundError(
|
||
f"Kart verisi yok: {path}. İnternete bağlanıp tekrar deneyin."
|
||
)
|
||
return cls(json.loads(path.read_text(encoding="utf-8")))
|
||
|
||
def get(self, card_id: str) -> dict:
|
||
return self.by_id.get(card_id, {})
|
||
|
||
def name(self, card_id: str) -> str:
|
||
return self.get(card_id).get("name", card_id or "?")
|
||
|
||
def cost(self, card_id: str) -> int:
|
||
return self.get(card_id).get("cost", 0)
|
||
|
||
def card_class(self, card_id: str) -> str:
|
||
card = self.get(card_id)
|
||
classes = card.get("classes")
|
||
if classes:
|
||
return classes[0]
|
||
return card.get("cardClass", "")
|
||
|
||
def rarity(self, card_id: str) -> str:
|
||
return self.get(card_id).get("rarity", "")
|
||
|
||
def card_type(self, card_id: str) -> str:
|
||
return self.get(card_id).get("type", "")
|
||
|
||
def dbf_id(self, card_id: str) -> int | None:
|
||
return self.get(card_id).get("dbfId")
|
||
|
||
def id_from_dbf(self, dbf_id: int) -> str:
|
||
return self.by_dbf.get(dbf_id, {}).get("id", "")
|
||
|
||
def collectible_by_dbf(self, dbf_id: int) -> dict:
|
||
return self.by_dbf.get(dbf_id, {})
|