From 3991a86a4132d5bc128e12998b72c3d32e9eff54 Mon Sep 17 00:00:00 2001 From: Night-stars-1 <99261160+Night-stars-1@users.noreply.github.com> Date: Mon, 18 Dec 2023 19:58:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=AB=E7=A0=81?= =?UTF-8?q?=E7=99=BB=E5=BD=95=EF=BC=8Ccookies=E5=A4=8D=E5=86=99=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=88=90=E9=95=BF=E5=80=BC=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=20(#246)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 添加扫码登录,cookies复写,修正错误提示 * On branch qr_login * chore: 遵守代码规范 * chore: not invalid-name * chore: 添加成长值显示 * imp: 更新版本号 --------- Co-authored-by: github-actions[bot] Co-authored-by: 0-8-4 --- .gitignore | 3 +- miuitask.py | 47 +++++++++------ pdm.lock | 34 +++++++++-- pyproject.toml | 1 + utils/api/login.py | 136 ++++++++++++++++++++++++++++++++++++++++--- utils/api/sign.py | 62 +++++++++++++++----- utils/config.py | 2 +- utils/data_model.py | 20 ++++++- utils/system_info.py | 2 +- utils/utils.py | 23 ++++++++ 10 files changed, 277 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index 99c5238..8dc48cd 100644 --- a/.gitignore +++ b/.gitignore @@ -142,5 +142,4 @@ dmypy.json logs data .pdm-python -test.py -test2.py \ No newline at end of file +test*.py \ No newline at end of file diff --git a/miuitask.py b/miuitask.py index 09fe2b6..758f653 100644 --- a/miuitask.py +++ b/miuitask.py @@ -1,13 +1,15 @@ ''' Date: 2023-11-13 20:29:19 LastEditors: Night-stars-1 nujj1042633805@gmail.com -LastEditTime: 2023-12-03 01:53:48 +LastEditTime: 2023-12-18 19:21:36 ''' # new Env("MIUI-Auto-Task") # pylint: disable=missing-module-docstring # cron 30 8 * * * miuitask.py import asyncio +from tenacity import RetryError, Retrying, stop_after_attempt + from utils.api.login import Login from utils.api.sign import BaseSign, CheckIn from utils.config import ConfigManager @@ -23,24 +25,33 @@ async def main(): """启动签到""" print_info() for account in _conf.accounts: - login_obj = Login(account) - if cookies := await login_obj.login(): - sign_obj = BaseSign(cookies) - daily_tasks = await sign_obj.check_daily_tasks() - sign_task_obj = sign_obj.AVAILABLE_SIGNS # 签到任务对象合集 - for task in daily_tasks: - if not task.showType: - log.info(f"开始执行{task.name}任务") - if task_obj := sign_task_obj.get(task.name): # 签到任务对象 - if getattr(account, task_obj.__name__): + try: + for attempt in Retrying(stop=stop_after_attempt(2)): + with attempt: + login_obj = Login(account) + if cookies := await login_obj.login(): + sign_obj = BaseSign(cookies, account.user_agent) + daily_tasks = await sign_obj.check_daily_tasks() + sign_task_obj = sign_obj.AVAILABLE_SIGNS # 签到任务对象合集 + for task in daily_tasks: + log.info(f"开始执行{task.name}任务") + if task.showType: + log.info(f"{task.name}任务已完成") + continue + if not (task_obj := sign_task_obj.get(task.name)): # 签到任务对象 + log.error(f"未找到{task.name}任务") + continue + if not getattr(account, task_obj.__name__): + log.info(f"任务{task.name}被禁用") + continue token = await get_token(cookies["cUserId"]) if task_obj == CheckIn else None - await task_obj(cookies, token).sign() - else: - log.info(f"任务{task.name}被禁用") - else: - log.error(f"未找到{task.name}任务") - else: - log.info(f"{task.name}任务已完成") + status, reason = await task_obj(cookies, token).sign() + if not status and reason == "cookie": + raise ValueError("Cookie失效") + user_info = await sign_obj.user_info() + log.info(f"{user_info.title} 成长值: {user_info.point}") + except RetryError: + ... notify_me(InterceptHandler.message) diff --git a/pdm.lock b/pdm.lock index bdf81b1..5d5a813 100644 --- a/pdm.lock +++ b/pdm.lock @@ -100,11 +100,8 @@ dependencies = [ [[package]] name = "onepush" -version = "1.2.0" +version = "1.3.0" requires_python = ">=3.6" -git = "https://github.com/y1ndan/onepush.git" -ref = "main" -revision = "8b09e62330ad74ba3221bfc2b080d1732a1dcc55" summary = "A Python library to send notifications to your iPhone, Discord, Telegram, WeChat, QQ and DingTalk." dependencies = [ "pycryptodome", @@ -149,12 +146,28 @@ dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] +[[package]] +name = "pypng" +version = "0.20220715.0" +summary = "Pure Python library for saving and loading PNG images" + [[package]] name = "pyyaml" version = "6.0.1" requires_python = ">=3.6" summary = "YAML parser and emitter for Python" +[[package]] +name = "qrcode" +version = "7.4.2" +requires_python = ">=3.7" +summary = "QR Code image generator" +dependencies = [ + "colorama; platform_system == \"Windows\"", + "pypng", + "typing-extensions", +] + [[package]] name = "requests" version = "2.31.0" @@ -207,7 +220,7 @@ summary = "A small Python utility to set file creation time on Windows" lock_version = "4.2" cross_platform = true groups = ["default"] -content_hash = "sha256:7c922391569ae98b5aba9438d7da9b0ab1b413b23a1e333df2e61bfab038c0ac" +content_hash = "sha256:fb7c34d8c74d9043647bf7d200afa5a64602e32c03b558cc9d2fb1e81ad1c1e5" [metadata.files] "annotated-types 0.6.0" = [ @@ -417,6 +430,9 @@ content_hash = "sha256:7c922391569ae98b5aba9438d7da9b0ab1b413b23a1e333df2e61bfab {url = "https://files.pythonhosted.org/packages/03/0a/4f6fed21aa246c6b49b561ca55facacc2a44b87d65b8b92362a8e99ba202/loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, {url = "https://files.pythonhosted.org/packages/9e/30/d87a423766b24db416a46e9335b9602b054a72b96a88a241f2b09b560fa8/loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, ] +"onepush 1.3.0" = [ + {url = "https://files.pythonhosted.org/packages/e1/a2/1d65907128f1b7980d7da0e68b134298ced50d38df8ddf5133e32c8e9f99/onepush-1.3.0-py3-none-any.whl", hash = "sha256:196af2e147a54381b5ffa32565f5f3955a0647328cc283812ce2f87f4bc09f47"}, +] "orjson 3.9.10" = [ {url = "https://files.pythonhosted.org/packages/03/96/4fd0da4f4a5a450054e69439875b4e856654dcbbfea6907d7753b827c937/orjson-3.9.10-cp312-none-win_amd64.whl", hash = "sha256:3e892621434392199efb54e69edfff9f699f6cc36dd9553c5bf796058b14b20d"}, {url = "https://files.pythonhosted.org/packages/09/33/d090754faab1a63ecf80b1df220d6787605caefd570331c757a3553afbf2/orjson-3.9.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92af0d00091e744587221e79f68d617b432425a7e59328ca4c496f774a356071"}, @@ -618,6 +634,10 @@ content_hash = "sha256:7c922391569ae98b5aba9438d7da9b0ab1b413b23a1e333df2e61bfab {url = "https://files.pythonhosted.org/packages/fd/81/7bcde32ca3434ec80861205d5f949fe28247af9712dd7da940f08445c45d/pydantic_core-2.14.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27828f0227b54804aac6fb077b6bb48e640b5435fdd7fbf0c274093a7b78b69c"}, {url = "https://files.pythonhosted.org/packages/ff/1b/937923cfa9df349c632ddead46e57c996103f9bfe904f5ff6cc87472df6e/pydantic_core-2.14.3-cp39-none-win32.whl", hash = "sha256:caa94726791e316f0f63049ee00dff3b34a629b0d099f3b594770f7d0d8f1f56"}, ] +"pypng 0.20220715.0" = [ + {url = "https://files.pythonhosted.org/packages/3e/b9/3766cc361d93edb2ce81e2e1f87dd98f314d7d513877a342d31b30741680/pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"}, + {url = "https://files.pythonhosted.org/packages/93/cd/112f092ec27cca83e0516de0a3368dbd9128c187fb6b52aaaa7cde39c96d/pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"}, +] "pyyaml 6.0.1" = [ {url = "https://files.pythonhosted.org/packages/02/74/b2320ebe006b6a521cf929c78f12a220b9db319b38165023623ed195654b/PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {url = "https://files.pythonhosted.org/packages/03/5c/c4671451b2f1d76ebe352c0945d4cd13500adb5d05f5a51ee296d80152f7/PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, @@ -670,6 +690,10 @@ content_hash = "sha256:7c922391569ae98b5aba9438d7da9b0ab1b413b23a1e333df2e61bfab {url = "https://files.pythonhosted.org/packages/f1/26/55e4f21db1f72eaef092015d9017c11510e7e6301c62a6cfee91295d13c6/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {url = "https://files.pythonhosted.org/packages/fe/88/def2e57fe740544f2eefb1645f1d6e0094f56c00f4eade708140b6137ead/PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, ] +"qrcode 7.4.2" = [ + {url = "https://files.pythonhosted.org/packages/24/79/aaf0c1c7214f2632badb2771d770b1500d3d7cbdf2590ae62e721ec50584/qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"}, + {url = "https://files.pythonhosted.org/packages/30/35/ad6d4c5a547fe9a5baf85a9edbafff93fc6394b014fab30595877305fa59/qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"}, +] "requests 2.31.0" = [ {url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, diff --git a/pyproject.toml b/pyproject.toml index 60ef542..437b087 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "tenacity>=8.2.3", "tzdata>=2023.3", "onepush>=1.3.0", + "qrcode>=7.4.2", ] requires-python = ">=3.11" license = {text = "MIT"} diff --git a/utils/api/login.py b/utils/api/login.py index 01b0b74..7152775 100644 --- a/utils/api/login.py +++ b/utils/api/login.py @@ -3,8 +3,9 @@ LastEditors: Night-stars-1 nujj1042633805@gmail.com LastEditTime: 2023-11-13 12:32:26 """ +import time from os import getenv -from typing import Dict, Union +from typing import Dict, Optional, Tuple, Union import orjson @@ -13,6 +14,8 @@ from ..logger import log from ..request import get, post from .sign import BaseSign +from ..utils import generate_qrcode + class Login: """登录类""" @@ -24,7 +27,7 @@ def __init__(self, account: Account) -> None: self.password = account.password self.cookies = account.cookies - async def login(self) -> Union[Dict[str, str], bool]: + async def login(self) -> Union[Dict[str, str], bool]: # pylint: disable=too-many-return-statements """登录小米账号""" headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', @@ -68,9 +71,17 @@ async def login(self) -> Union[Dict[str, str], bool]: repo_owner = getenv('GITHUB_REPOSITORY_OWNER') if repo_owner not in [None, "0-8-4"]: return False - if self.cookies != {} and await BaseSign(self.cookies).check_daily_tasks(nolog=True) != []: + if self.cookies != {} and await BaseSign(self.cookies, self.user_agent).check_daily_tasks(nolog=True) != []: log.info("Cookie有效,跳过登录") return self.cookies + elif self.cookies.get("passToken") and \ + (cookies := await self.get_cookies_by_passtk(user_id=self.uid, + pass_token=self.cookies["passToken"])): + log.info("Cookie无效,重新复写") + self.cookies.update(cookies) + self.account.cookies = self.cookies + write_plugin_data() + return cookies response = await post('https://account.xiaomi.com/pass/serviceLoginAuth2', headers=headers, data=data) log.debug(response.text) result = response.text.lstrip('&').lstrip('START').lstrip('&') @@ -83,14 +94,21 @@ async def login(self) -> Union[Dict[str, str], bool]: self.account.cookies = cookies write_plugin_data() return cookies - elif not api_data.pwd_wrong: - log.error(f'小米账号登录失败:{api_data.message}') + elif api_data.pwd_wrong: + log.error('小米账号登录失败:用户名或密码不正确') + check_url = await self.qr_login() + userid, cookies = await self.check_login(check_url) + self.cookies.update(cookies) + self.account.cookies = self.cookies + self.account.uid = userid + write_plugin_data() + return cookies elif api_data.need_captcha: log.error('当前账号需要短信验证码, 请尝试修改UA或设备ID') else: - log.error('小米账号登录失败:用户名或密码不正确') + log.error(f'小米账号登录失败:{api_data.message}') return False - except Exception: # pylint: disable=broad-exception-caught + except Exception: # pylint: disable=broad-exception-caught log.exception("登录小米账号出错") return False @@ -100,7 +118,107 @@ async def get_cookie(self, url: str) -> Union[Dict[str, str], bool]: response = await get(url, follow_redirects=False) log.debug(response.text) return dict(response.cookies) - except Exception: # pylint: disable=broad-exception-caught + except Exception: # pylint: disable=broad-exception-caught log.exception("社区获取 Cookie 失败") return False - \ No newline at end of file + + async def get_cookies_by_passtk(self, user_id: str, pass_token: str) -> Union[Dict[str, str], bool]: + """使用passToken获取签到cookies""" + try: + headers = { + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Pragma': 'no-cache', + 'Referer': 'https://web.vip.miui.com/', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Upgrade-Insecure-Requests': '1', + 'User-Agent': self.user_agent, + 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Microsoft Edge";v="120"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + } + + params = { + 'destUrl': 'https://web.vip.miui.com/page/info/mio/mio/checkIn?app_version=dev.230904', + 'time': round(time.time() * 1000), + } + cookies = { + "userId": user_id, + "passToken": pass_token + } + response = await get('https://api.vip.miui.com/page/login', params=params, headers=headers) + url = response.headers.get("location") + + response = await get(url, cookies=cookies, headers=headers) + url = response.headers.get("location") + response = await get(url, cookies=cookies, headers=headers) + return dict(response.cookies) + except Exception: # pylint: disable=broad-exception-caught + log.exception("从passToken获取 Cookie 失败") + return {} + + async def qr_login(self) -> Tuple[str, bytes]: + """二维码登录""" + headers = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Pragma': 'no-cache', + 'Referer': 'https://account.xiaomi.com/', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0', + 'X-Requested-With': 'XMLHttpRequest', + 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Microsoft Edge";v="120"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + } + + response = await get( + 'https://account.xiaomi.com/longPolling/loginUrl?_group=DEFAULT&_qrsize=240&qs=%253Fcallback%253Dhttps%25253A%25252F%25252Faccount.xiaomi.com%25252Fsts%25253Fsign%25253DZvAtJIzsDsFe60LdaPa76nNNP58%2525253D%252526followup%25253Dhttps%2525253A%2525252F%2525252Faccount.xiaomi.com%2525252Fpass%2525252Fauth%2525252Fsecurity%2525252Fhome%252526sid%25253Dpassport%2526sid%253Dpassport%2526_group%253DDEFAULT&bizDeviceType=&callback=https:%2F%2Faccount.xiaomi.com%2Fsts%3Fsign%3DZvAtJIzsDsFe60LdaPa76nNNP58%253D%26followup%3Dhttps%253A%252F%252Faccount.xiaomi.com%252Fpass%252Fauth%252Fsecurity%252Fhome%26sid%3Dpassport&theme=&sid=passport&needTheme=false&showActiveX=false&serviceParam=%7B%22checkSafePhone%22:false,%22checkSafeAddress%22:false,%22lsrp_score%22:0.0%7D&_locale=zh_CN&_sign=2%26V1_passport%26BUcblfwZ4tX84axhVUaw8t6yi2E%3D&_dc=1702105962382', # pylint: disable=line-too-long + headers=headers, + ) + result = response.text.replace("&&&START&&&", "") + data = orjson.loads(result) # pylint: disable=no-member + login_url = data["loginUrl"] + check_url = data["lp"] + generate_qrcode(login_url) + return check_url + + async def check_login(self, url: str) -> Tuple[Optional[int], Optional[dict]]: + """检查扫码登录状态""" + try: + headers = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Pragma': 'no-cache', + 'Referer': 'https://account.xiaomi.com/', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0', + 'X-Requested-With': 'XMLHttpRequest', + 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Microsoft Edge";v="120"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + } + response = await get(url, headers=headers) + result = response.text.replace("&&&START&&&", "") + data = orjson.loads(result) # pylint: disable=no-member + pass_token = data["passToken"] + user_id = str(data["userId"]) + cookies = await self.get_cookies_by_passtk(user_id=user_id, pass_token=pass_token) + cookies.update({ + "passToken": pass_token + }) + return user_id, cookies + except Exception: # pylint: disable=broad-exception-caught + return None, None diff --git a/utils/api/sign.py b/utils/api/sign.py index 548d99b..e1ecf77 100644 --- a/utils/api/sign.py +++ b/utils/api/sign.py @@ -2,10 +2,10 @@ import time -from typing import Dict, List, Optional, Type, Union, Any +from typing import Dict, List, Optional, Type, Union, Any, Tuple from tenacity import RetryError, Retrying, stop_after_attempt -from ..data_model import ApiResultHandler, DailyTasksResult, SignResultHandler +from ..data_model import ApiResultHandler, DailyTasksResult, SignResultHandler, UserInfoResult from ..request import get, post from ..logger import log from ..utils import is_incorrect_return @@ -30,9 +30,10 @@ class BaseSign: AVAILABLE_SIGNS: Dict[str, Type["BaseSign"]] = {} """可用的子类""" - def __init__(self, cookie: Dict, token: Optional[str] = None): - self.cookie = cookie + def __init__(self, cookies: Dict, user_agent: str, token: Optional[str] = None): + self.cookies = cookies self.token = token + self.user_agent = user_agent self.headers = { } @@ -42,7 +43,7 @@ async def check_daily_tasks(self, nolog: bool = False) -> Union[List[DailyTasksR for attempt in Retrying(stop=stop_after_attempt(3)): with attempt: response = await get('https://api.vip.miui.com/mtop/planet/vip/member/getCheckinPageCakeList', - cookies=self.cookie) + cookies=self.cookies) log.debug(response.text) result = response.json() api_data = ApiResultHandler(result) @@ -68,7 +69,7 @@ async def check_daily_tasks(self, nolog: bool = False) -> Union[List[DailyTasksR log.exception("获取每日任务异常") return [] - async def sign(self) -> bool: + async def sign(self) -> Tuple[bool, str]: """ 每日任务处理器 """ @@ -76,19 +77,19 @@ async def sign(self) -> bool: for attempt in Retrying(stop=stop_after_attempt(3)): with attempt: params = self.PARAMS.copy() - params['miui_vip_ph'] = self.cookie['miui_vip_ph'] if 'miui_vip_ph' in self.cookie else params + params['miui_vip_ph'] = self.cookies['miui_vip_ph'] if 'miui_vip_ph' in self.cookies else params params['token'] = self.token if 'token' in params else params data = self.DATA.copy() - data['miui_vip_ph'] = self.cookie['miui_vip_ph'] if 'miui_vip_ph' in self.cookie else data + data['miui_vip_ph'] = self.cookies['miui_vip_ph'] if 'miui_vip_ph' in self.cookies else data if 'token' in data: if self.token: data['token'] = self.token else: log.info(f"未获取到token, 跳过{self.NAME}") - return False + return False, "None" response = await post(self.URL_SIGN, params=params, data=data, - cookies=self.cookie, headers=self.headers) + cookies=self.cookies, headers=self.headers) log.debug(response.text) result = response.json() api_data = SignResultHandler(result) @@ -97,20 +98,53 @@ async def sign(self) -> bool: log.success(f"{self.NAME}结果: 成长值+{api_data.growth}") else: log.success(f"{self.NAME}结果: {api_data.message}") - return True + return True, "None" elif api_data.ck_invalid: log.error(f"{self.NAME}失败: Cookie无效") - return False + return False, "cookie" else: log.error(f"{self.NAME}失败:{api_data.message}") - return False + return False, "None" except RetryError as error: if is_incorrect_return(error): log.exception(f"{self.NAME} - 服务器没有正确返回 {response.text}") else: log.exception("{self.NAME}出错") - return False + return False, "None" + async def user_info(self) -> UserInfoResult: + """获取用户信息""" + try: + for attempt in Retrying(stop=stop_after_attempt(3)): + with attempt: + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': self.user_agent, + 'Request-Container-Mark': 'android', + 'Host': 'api.vip.miui.com', + 'Connection': 'Keep-Alive', + } + + response = await get( + 'https://api.vip.miui.com/mtop/planet/vip/homepage/mineInfo', + cookies=self.cookies, + headers=headers, + ) + log.debug(response.text) + result = response.json() + api_data = ApiResultHandler(result) + if api_data.success: + return UserInfoResult.model_validate(api_data.data) + else: + log.error(f"获取用户信息失败:{api_data.message}") + return UserInfoResult() + except RetryError as error: + if is_incorrect_return(error): + log.exception(f"用户信息 - 服务器没有正确返回 {response.text}") + else: + log.exception("获取用户信息异常") + return UserInfoResult() +#pylint: disable=trailing-whitespace class CheckIn(BaseSign): """ 每日签到 diff --git a/utils/config.py b/utils/config.py index f2f4d40..e05d213 100644 --- a/utils/config.py +++ b/utils/config.py @@ -8,7 +8,7 @@ import orjson import yaml # pylint: disable=wrong-import-order -from pydantic import BaseModel, ValidationError, field_validator +from pydantic import BaseModel, ValidationError, field_validator # pylint: disable=no-name-in-module from .logger import log diff --git a/utils/data_model.py b/utils/data_model.py index 6045048..8be7097 100644 --- a/utils/data_model.py +++ b/utils/data_model.py @@ -1,6 +1,6 @@ """数据处理模型""" from typing import (Any, Dict, NamedTuple, Optional) -from pydantic import BaseModel +from pydantic import BaseModel # pylint: disable=no-name-in-module class ApiResultHandler(BaseModel): @@ -42,7 +42,7 @@ def success(self): """ 是否成功 """ - return self.status in [0, 200] or self.message in ["成功", "OK", "success"] + return (self.status in [0, 200] or self.message in ["成功", "OK", "success"]) and not self.content.get("notificationUrl") class LoginResultHandler(ApiResultHandler): @@ -65,7 +65,7 @@ def need_captcha(self): """ 是否需要验证码 """ - return self.status == 87001 or "验证码" in self.message + return self.status == 87001 or "验证码" in self.message or self.content.get("notificationUrl") @property def pwd_wrong(self): @@ -146,3 +146,17 @@ class GeetestResult(NamedTuple): """人机验证结果数据""" validate: str challenge: str + +class UserInfoResult(BaseModel): + """用户信息数据""" + title: str = "未知" + """等级名称""" + point: int = 0 + """积分""" + + def __init__(self, **kwargs): + if isinstance(kwargs, dict) and kwargs: + kwargs = kwargs.get("userInfo", {}).get("userGrowLevelInfo") + super().__init__(**kwargs) + else: + super().__init__() diff --git a/utils/system_info.py b/utils/system_info.py index 07994dc..e6e5483 100644 --- a/utils/system_info.py +++ b/utils/system_info.py @@ -10,7 +10,7 @@ def print_info(): """打印系统信息""" - log.info("MIUI-AUTO-TASK v1.7.3") + log.info("MIUI-AUTO-TASK v1.7.4") log.info('---------- 系统信息 -------------') system_info() log.info('---------- 项目信息 -------------') diff --git a/utils/utils.py b/utils/utils.py index 88610e8..f0345fa 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -2,9 +2,11 @@ import base64 import random import time +from io import BytesIO from typing import Type from urllib.parse import parse_qsl, urlparse +import qrcode from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding, serialization from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 @@ -239,3 +241,24 @@ async def get_token(uid: str) -> str | bool: else: log.exception("获取TOKEN异常") return False + +def generate_qrcode(url): + """生成二维码""" + qr = qrcode.QRCode(version=1, # pylint: disable=invalid-name + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4) + qr.add_data(url) + qr.make(fit=True) + img = qr.make_image(fill_color='black', back_color='white') + bio = BytesIO() + img.save(bio) + # 获取二维码的模块 (module) 列表 + qr_modules = qr.get_matrix() + chaes = [" ", "██"] + # 在控制台中打印二维码 + for row in qr_modules: + line = "".join(chaes[pixel] for pixel in row) + print(line) + log.debug(line) + \ No newline at end of file