Skip to content

Commit

Permalink
Make claim_daily_reward work for chinese accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
thesadru committed Oct 25, 2021
1 parent 625acfe commit 8553355
Show file tree
Hide file tree
Showing 18 changed files with 111 additions and 102 deletions.
1 change: 1 addition & 0 deletions .github/workflows/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- master

jobs:
deploy:
runs-on: ubuntu-latest
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Linting and Unit Testing.
name: Linting and Unit Testing

on: [push, pull_request]

Expand All @@ -18,7 +18,6 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest mypy black
pip install -r requirements.txt
- name: Test with pytest
env:
Expand All @@ -28,6 +27,19 @@ jobs:
CN_LTOKEN: ${{ secrets.CN_LTOKEN }}
run: |
python -m pytest
lint:
runs-on: ubuntu-latest

steps:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 mypy black
- name: Lint with flake8
run: |
flake8 . --count --statistics --max-line-length=100 --select=E9,F63,F7,F82 --show-source
Expand Down
7 changes: 6 additions & 1 deletion docs/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ Redis caches are powered by [aioredis](https://aioredis.readthedocs.io/en/latest
client = genshin.GenshinClient()
# same arguments you would pass into "aioredis.from_url"
client.set_redis_cache("redis://localhost", username="user", password="pass")
```
```
```py
# set a redis cache but with an EXpiry (in seconds)
client = genshin.GenshinClient()
client.set_redis_cache("redis://localhost", ex=3600)
```
5 changes: 3 additions & 2 deletions docs/credits.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
Thanks to these amazing people genshin.py can be where it is now:

- [Womsxd](https://github.com/Womsxd): The author of [YuanShen User Info](https://github.com/Womsxd/YuanShen_User_Info) - a predecesor to genshinstats
- [GrassSand](https://github.com/grasssand): Provided me with working chinese cookies to test chinese endpoints.
- [Lightczx](https://github.com/Lightczx): The author of [Snap.Genshin](https://github.com/DGP-Studio/Snap.Genshin), helped me figure out the chinese endpoints
- [GrassSand](https://github.com/grasssand) & [molehzy](https://github.com/molehzy): Provided me with working chinese cookies to test chinese endpoints.
- [lulu666lulu](https://github.com/lulu666lulu): Figured out how the chinese dynamic secret is generated
- [Chizy](https://github.com/OhChizy): Keeping me sane throughout the entire development process

And finally me :) - [thesadru](https://github.com/thesadru)
And finally me :) - [thesadru](https://github.com/thesadru)
44 changes: 13 additions & 31 deletions genshin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class GenshinClient:

cache: Optional[MutableMapping[Tuple[Any, ...], Any]] = None
redis_cache: Optional[Redis] = None
redis_cache_ex: Optional[int] = None
paginator_cache: Optional[MutableMapping[Tuple[Any, ...], Any]] = None
static_cache: ClassVar[MutableMapping[str, Any]] = {}
_permanent_cache: ClassVar[MutableMapping[Any, Any]] = {} # TODO: Remove the need for this
Expand Down Expand Up @@ -247,10 +248,11 @@ def set_cache(
self.cache = getattr(cachetools, cls_name)(maxsize, getsizeof=getsizeof)
return self.cache

def set_redis_cache(self, url: str, **kwargs: Any) -> Redis:
def set_redis_cache(self, url: str, *, ex: int = None, **kwargs: Any) -> Redis:
"""Create and set a new redis cache for http requests
:param url: A redis database url
:param ex: An automatically applied key expiry
:param kwargs: Kwargs proxied to aioredis.from_url
:returns: The newly created Redis object
"""
Expand All @@ -260,6 +262,7 @@ def set_redis_cache(self, url: str, **kwargs: Any) -> Redis:
import aioredis

self.redis_cache = aioredis.from_url(url, **kwargs)
self.redis_cache_ex = ex
return self.redis_cache

async def _check_cache(
Expand Down Expand Up @@ -312,7 +315,7 @@ async def _update_cache(
self.cache[key] = data
elif self.redis_cache is not None:
name = ":".join(map(str, key))
await self.redis_cache.set(name, json.dumps(data))
await self.redis_cache.set(name, json.dumps(data), ex=self.redis_cache_ex)

# ASYNCIO HANDLERS:

Expand Down Expand Up @@ -344,21 +347,6 @@ async def request(
headers["user-agent"] = self.USER_AGENT

async with self.session.request(method, url, headers=headers, **kwargs) as r:
req = r.request_info
nl = '\n'
print(f"""
{req.method} {req.url.path} HTTP/{r.version.major}.{r.version.minor}
{nl.join(f'{k}: {v}' for k, v in req.headers.items())}
{json.dumps(kwargs.get('json'))}
------------------------------
HTTP/1.1 200 OK
{nl.join(f'{k}: {v}' for k, v in r.headers.items())}
{await r.text()}
==================================================
""")
r.raise_for_status()
data = await r.json()

Expand Down Expand Up @@ -1229,15 +1217,11 @@ async def request_daily_reward(
method: str = "GET",
params: Dict[str, Any] = None,
headers: Dict[str, Any] = None,
json: Dict[str, Any] = None,
**kwargs: Any,
) -> Dict[str, Any]:
kwargs.pop("lang", None)
headers = headers or {}
# mihoyo accepts both body and params no matter the context
json = json or {}
if params:
json.update(params)
params = params or {}

if not self.cookies:
raise RuntimeError("No cookies provided")
Expand All @@ -1252,24 +1236,22 @@ async def request_daily_reward(
errors.raise_for_retcode({"retcode": -1073})

account = max(accounts, key=lambda a: a.level)
json["uid"] = str(account.uid)
json["region"] = account.server
params["uid"] = str(account.uid)
params["region"] = account.server
else:
json["uid"] = str(uid)
json["region"] = recognize_server(uid)
params["uid"] = str(uid)
params["region"] = recognize_server(uid)

headers["x-rpc-app_version"] = "2.10.1"
headers["x-rpc-client_type"] = "5"
headers["x-rpc-device_id"] = str(uuid.uuid4())
headers["ds"] = generate_dynamic_secret(self.SIGNIN_SALT)

json["act_id"] = self.ACT_ID
params["act_id"] = self.ACT_ID

# self.logger.debug(f"DAILY {method} {url.with_query(json)}")
self.logger.debug(f"DAILY {method} {url}")
self.logger.debug(f"{json=} {headers=}")
self.logger.debug(f"DAILY {method} {url.with_query(params)}")

return await self.request(url, method, json=json, headers=headers, **kwargs)
return await self.request(url, method, params=params, headers=headers, **kwargs)

async def get_reward_info(self, uid: int = None) -> DailyRewardInfo:
"""Get the daily reward info for the current user
Expand Down
42 changes: 22 additions & 20 deletions genshin/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,37 +94,46 @@ class AuthkeyTimeout(AuthkeyException):
msg = "Authkey has timed out."


_errors: Dict[int, Union[Tuple[Type[GenshinException], Optional[str]], Type[GenshinException]]] = {
_TGE = Type[GenshinException]
_errors: Dict[int, Union[_TGE, str, Tuple[_TGE, Optional[str]]]] = {
# misc hoyolab
-100: InvalidCookies,
-108: (GenshinException, "Invalid language."),
-108: "Invalid language.",
# game record
10001: InvalidCookies,
-10001: (GenshinException, "Malformed request."),
-10002: (GenshinException, "No genshin account associated with cookies."),
-10001: "Malformed request.",
-10002: "No genshin account associated with cookies.",
# database game record
10101: TooManyRequests,
10102: DataNotPublic,
10103: (InvalidCookies, "Cookies are valid but do not have a hoyolab account bound to them."),
10104: (GenshinException, "Tried to use a beta feature in an invalid context"),
10104: "Tried to use a beta feature in an invalid context",
# mixin
-1: (GenshinException, "Internal database error."),
-1: "Internal database error.",
1009: AccountNotFound,
# redemption
-1071: InvalidCookies,
-1073: (GenshinException, "Cannot claim code. Account has no game account bound to it."),
-2001: (GenshinException, "Redemption code has expired."),
-2003: (GenshinException, "Invalid redemption code."),
-2017: (GenshinException, "Redeption code has been claimed already."),
-2021: (GenshinException, "Cannot claim codes for account with adventure rank lower than 10."),
-1073: "Cannot claim code. Account has no game account bound to it.",
-2001: "Redemption code has expired.",
-2003: "Invalid redemption code.",
-2017: "Redeption code has been claimed already.",
-2021: "Cannot claim codes for account with adventure rank lower than 10.",
# rewards
-5003: (AlreadyClaimed, "Already claimed the daily reward today."),
-5003: AlreadyClaimed,
# chinese
1008: AccountNotFound,
-1104: "This action must be done in the app",
}

ERRORS: Dict[int, Tuple[Type[GenshinException], Optional[str]]] = {
retcode: ((exc, None) if isinstance(exc, type) else exc) for retcode, exc in _errors.items()
retcode: (
(exc, None)
if isinstance(exc, type)
else (GenshinException, exc)
if isinstance(exc, str)
else exc
)
for retcode, exc in _errors.items()
}


Expand All @@ -145,13 +154,6 @@ def raise_for_retcode(data: Dict[str, Any]) -> NoReturn:
daily reward:
-500x = already claimed the daily reward
unknown:
-1 = malformed request / account not found
-100 = invalid cookies
-108 = invalid language
1009 = account not found
"""
r, m = data.get("retcode", 0), data.get("message", "")

Expand Down
14 changes: 8 additions & 6 deletions genshin/paginator.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,17 @@ async def flatten(self) -> List[ClaimedDailyReward]:
# that means no posible greedy flatten implementation
return [item async for item in self]


class ChineseDailyRewardsPaginator(DailyRewardPaginator):
"""A paginator specifically for claimed daily rewards on chinese bbs"""

client: ChineseClient
uid: Optional[int]
limit: Optional[int]
lang: Optional[str]
current_page: Optional[int]

page_size: int = 10
uid: Optional[int]


def __init__(self, client: ChineseClient, uid: int = None, limit: int = None) -> None:
"""Create a new daily reward pagintor
Expand All @@ -114,13 +115,14 @@ def __init__(self, client: ChineseClient, uid: int = None, limit: int = None) ->
:param limit: The maximum amount of rewards to get
"""
self.client = client
self.limit = limit
self.uid = uid

self.limit = limit

async def _get_page(self, page: int) -> List[ClaimedDailyReward]:
params = dict(current_page=page)
data = await self.client.request_daily_reward("award", self.uid, params=params)
return data
return [ClaimedDailyReward(**i) for i in data["list"]]


class IDPagintor(Generic[MT]):
"""A paginator of genshin end_id pages"""
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ browser-cookie3
typer

pytest
pytest-asyncio-cooperative
pytest-asyncio
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"cookies": ["browser-cookie3"],
"cache": ["cachetools", "aioredis"],
"cli": ["typer", "browser-cookie3"],
"test": ["pytest", "pytest-asyncio-cooperative", "cachetools"],
"test": ["pytest", "pytest-asyncio", "cachetools"],
"doc": [
"mkdocs-material",
"pdoc @ git+https://github.com/devdrian/pdoc@reST_style_docstrings_support",
Expand Down
6 changes: 5 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ def event_loop():

@pytest.fixture(scope="session")
def cookies() -> Dict[str, str]:
return {"ltuid": os.environ["LTUID"], "ltoken": os.environ["LTOKEN"]}
try:
return {"ltuid": os.environ["LTUID"], "ltoken": os.environ["LTOKEN"]}
except KeyError:
pytest.exit("No cookies set", 1)
return {}


@pytest.fixture(scope="session")
Expand Down
6 changes: 3 additions & 3 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from genshin import GenshinClient


@pytest.mark.asyncio_cooperative
@pytest.mark.asyncio
async def test_cache(cookies: Dict[str, str], uid: int):
client = GenshinClient()
client.set_cookies(cookies)
Expand All @@ -21,7 +21,7 @@ async def test_cache(cookies: Dict[str, str], uid: int):
await client.close()


@pytest.mark.asyncio_cooperative
@pytest.mark.asyncio
async def test_static_cache(cookies: Dict[str, str]):
client = GenshinClient()
client.set_cookies(cookies)
Expand All @@ -36,7 +36,7 @@ async def test_static_cache(cookies: Dict[str, str]):
await client.close()


@pytest.mark.asyncio_cooperative
@pytest.mark.asyncio
async def test_cachetools_cache(cookies: Dict[str, str], uid: int):
client = GenshinClient()
client.set_cookies(cookies)
Expand Down
Loading

0 comments on commit 8553355

Please sign in to comment.