| |
| 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)} |