# app/gs_code.py import httpx import aiohttp from bs4 import BeautifulSoup from typing import Dict, Any, List ROLE_URL = "https://api-os-takumi.hoyoverse.com/binding/api/getUserGameRolesByCookie" REDEEM_URL = "https://public-operation-hk4e.hoyoverse.com/common/apicdkey/api/webExchangeCdkey" GENSHIN_API = "https://genshin-impact.fandom.com/api.php?action=parse&page=Promotional_Code&prop=text&format=json" class GSCodeApp: def __init__(self): self.client: httpx.AsyncClient | None = None self.wiki_session: aiohttp.ClientSession | None = None async def start(self): self.client = httpx.AsyncClient(timeout=15) self.wiki_session = aiohttp.ClientSession( headers={"User-Agent": "Mozilla/5.0 (GenshinCodeBot)"} ) async def stop(self): if self.client: await self.client.aclose() if self.wiki_session: await self.wiki_session.close() def _headers(self, cookie: str): return { "Cookie": cookie, "User-Agent": "Mozilla/5.0", "Accept": "application/json" } async def _get_roles(self, cookie: str): r = await self.client.get( ROLE_URL, headers=self._headers(cookie), params={"game_biz": "hk4e_global"} ) return r.json() async def _get_code_info(self, code: str) -> Dict[str, Any]: async with self.wiki_session.get(GENSHIN_API) as resp: if resp.status != 200: return {} data = await resp.json() html = data.get("parse", {}).get("text", {}).get("*") if not html: return {} soup = BeautifulSoup(html, "html.parser") for row in soup.select("table.wikitable tbody tr"): tds = row.find_all("td") if len(tds) < 3: continue code_els = tds[0].find_all("code") codes = [c.get_text(strip=True) for c in code_els] if code not in codes: continue rewards: List[str] = [] for item in tds[2].select(".item-text"): text = item.get_text(" ", strip=True) if text: text = text.replace("×", "×") rewards.append(text) validity = ( tds[3].get_text(" ", strip=True).replace("×", "×") if len(tds) >= 4 else None ) return { "rewards": rewards, "validity": validity } return {} async def redeem_code( self, cookie: str, server_input: str, code: str ) -> Dict[str, Any]: try: role_data = await self._get_roles(cookie) if role_data.get("retcode") != 0: return {"ok": False, "error": "Cookie không hợp lệ"} roles = role_data.get("data", {}).get("list", []) if not roles: return {"ok": False, "error": "Không có tài khoản Genshin"} region_map = { "asia": "os_asia", "america": "os_usa", "usa": "os_usa", "na": "os_usa", "europe": "os_euro", "eu": "os_euro", "tw": "os_cht", "hk": "os_cht" } target_region = region_map.get(server_input.lower()) if not target_region: return {"ok": False, "error": "Server không hợp lệ"} selected_role = next( (r for r in roles if r.get("region") == target_region), None ) if not selected_role: return {"ok": False, "error": f"Không tìm thấy tài khoản ở server {server_input}"} uid = selected_role.get("game_uid") region = selected_role.get("region") params = { "uid": uid, "region": region, "lang": "en", "cdkey": code, "game_biz": "hk4e_global", "sLangKey": "en-us" } r = await self.client.get( REDEEM_URL, headers=self._headers(cookie), params=params ) data = r.json() if data.get("retcode") != 0: return { "ok": False, "error": data.get("message", "Redeem thất bại"), "retcode": data.get("retcode") } code_info = await self._get_code_info(code) return { "ok": True, "message": data.get("data", {}).get("msg"), "uid": uid, "server": server_input.capitalize(), "code": code, "rewards": code_info.get("rewards", []), "validity": code_info.get("validity") } except Exception as e: return {"ok": False, "error": str(e)}