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