Improve CheapShark Metacritic title matching
This commit is contained in:
+71
-13
@@ -48,6 +48,43 @@ _STORE_MAP = {
|
|||||||
}
|
}
|
||||||
_CS_DEDICATED_STORE_IDS = {"1", "7", "30"}
|
_CS_DEDICATED_STORE_IDS = {"1", "7", "30"}
|
||||||
_MC_CACHE = {}
|
_MC_CACHE = {}
|
||||||
|
_TITLE_SUFFIX_RE = re.compile(
|
||||||
|
r"\b("
|
||||||
|
r"complete|deluxe|ultimate|definitive|gold|premium|collector'?s|anniversary|remastered|remaster|"
|
||||||
|
r"goty|game\s+of\s+the\s+year|enhanced|extended|special|standard|starter|pack|bundle|edition"
|
||||||
|
r")\b",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_game_title(title):
|
||||||
|
text = str(title or "").lower()
|
||||||
|
text = re.sub(r"[™®©]", " ", text)
|
||||||
|
text = re.sub(r"[\[\(].*?[\]\)]", " ", text)
|
||||||
|
text = re.sub(r"\b\d{4}\b", " ", text)
|
||||||
|
text = re.sub(r"[:\-–—|_/]+", " ", text)
|
||||||
|
text = _TITLE_SUFFIX_RE.sub(" ", text)
|
||||||
|
text = re.sub(r"\s+", " ", text).strip()
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def _title_candidates(title):
|
||||||
|
base = str(title or "").strip()
|
||||||
|
normalized = _normalize_game_title(base)
|
||||||
|
variants = []
|
||||||
|
for item in (base, normalized):
|
||||||
|
item = str(item or "").strip()
|
||||||
|
if item and item not in variants:
|
||||||
|
variants.append(item)
|
||||||
|
if ":" in base:
|
||||||
|
head = _normalize_game_title(base.split(":", 1)[0])
|
||||||
|
if head and head not in variants:
|
||||||
|
variants.append(head)
|
||||||
|
if "-" in base:
|
||||||
|
head = _normalize_game_title(base.split("-", 1)[0])
|
||||||
|
if head and head not in variants:
|
||||||
|
variants.append(head)
|
||||||
|
return variants
|
||||||
|
|
||||||
|
|
||||||
def _metacritic_from_deal(d):
|
def _metacritic_from_deal(d):
|
||||||
@@ -61,29 +98,50 @@ def _metacritic_from_deal(d):
|
|||||||
return score, link
|
return score, link
|
||||||
|
|
||||||
|
|
||||||
|
def _deal_match_score(deal, target):
|
||||||
|
deal_title = _normalize_game_title(deal.get("title") or "")
|
||||||
|
if not deal_title or not target:
|
||||||
|
return 0
|
||||||
|
if deal_title == target:
|
||||||
|
return 100
|
||||||
|
target_words = set(target.split())
|
||||||
|
deal_words = set(deal_title.split())
|
||||||
|
if len(target_words) <= 1:
|
||||||
|
return 0
|
||||||
|
if deal_title.startswith(target) or target.startswith(deal_title):
|
||||||
|
return 80
|
||||||
|
if not target_words or not deal_words:
|
||||||
|
return 0
|
||||||
|
overlap = len(target_words & deal_words)
|
||||||
|
return int((overlap / max(len(target_words), len(deal_words))) * 70)
|
||||||
|
|
||||||
|
|
||||||
def _cheapshark_metacritic_lookup(title="", steam_appid=None):
|
def _cheapshark_metacritic_lookup(title="", steam_appid=None):
|
||||||
key = f"{steam_appid or ''}:{title or ''}".lower()
|
key = f"{steam_appid or ''}:{title or ''}".lower()
|
||||||
if key in _MC_CACHE:
|
if key in _MC_CACHE:
|
||||||
return _MC_CACHE[key]
|
return _MC_CACHE[key]
|
||||||
params = {"pageSize": 5}
|
if not (steam_appid or title):
|
||||||
if steam_appid:
|
|
||||||
params["steamAppID"] = steam_appid
|
|
||||||
elif title:
|
|
||||||
params["title"] = title
|
|
||||||
else:
|
|
||||||
_MC_CACHE[key] = (0, "")
|
_MC_CACHE[key] = (0, "")
|
||||||
return _MC_CACHE[key]
|
return _MC_CACHE[key]
|
||||||
try:
|
try:
|
||||||
deals = _get(f"{_CS_BASE}/deals", params=params).json()
|
|
||||||
normalized = re.sub(r"\s+", " ", str(title or "").strip()).lower()
|
|
||||||
best = None
|
best = None
|
||||||
for deal in deals or []:
|
if steam_appid:
|
||||||
deal_title = re.sub(r"\s+", " ", str(deal.get("title") or "").strip()).lower()
|
deals = _get(f"{_CS_BASE}/deals", params={"pageSize": 5, "steamAppID": steam_appid}).json()
|
||||||
if steam_appid or deal_title == normalized:
|
best = next((deal for deal in deals or [] if int(float(deal.get("metacriticScore") or 0)) > 0), None)
|
||||||
best = deal
|
|
||||||
break
|
|
||||||
if best is None and deals:
|
if best is None and deals:
|
||||||
best = deals[0]
|
best = deals[0]
|
||||||
|
if best is None:
|
||||||
|
for candidate in _title_candidates(title):
|
||||||
|
deals = _get(f"{_CS_BASE}/deals", params={"pageSize": 12, "title": candidate}).json()
|
||||||
|
target = _normalize_game_title(candidate)
|
||||||
|
ranked = sorted(
|
||||||
|
(deal for deal in deals or [] if int(float(deal.get("metacriticScore") or 0)) > 0),
|
||||||
|
key=lambda deal: _deal_match_score(deal, target),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
if ranked and _deal_match_score(ranked[0], target) >= 45:
|
||||||
|
best = ranked[0]
|
||||||
|
break
|
||||||
_MC_CACHE[key] = _metacritic_from_deal(best or {})
|
_MC_CACHE[key] = _metacritic_from_deal(best or {})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug("CheapShark metacritic lookup failed title=%s appid=%s: %s", title, steam_appid, e)
|
log.debug("CheapShark metacritic lookup failed title=%s appid=%s: %s", title, steam_appid, e)
|
||||||
|
|||||||
Reference in New Issue
Block a user