Skip to content

Commit

Permalink
Merge pull request #181 from Ljzd-PRO/dev
Browse files Browse the repository at this point in the history
更新至 v1.3.1
  • Loading branch information
Ljzd-PRO authored Aug 30, 2023
2 parents 447ffb2 + 2090579 commit 4980931
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 98 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
# mysTool - 米游社辅助工具插件

## 📣 更新内容
### 2023.8.30 - v1.3.1
- 登录功能增加黑名单/白名单功能 #178
> [插件偏好设置 - enable_blacklist](https://github.com/Ljzd-PRO/nonebot-plugin-mystool/wiki/Configuration-Preference#enable_blacklist)
- 登录功能支持使用第三方打码平台进行人机验证(暂不支持GT4)
- 修复游戏签到人机验证无效的问题 #163
- 便笺体力除了达到阈值以外,体力已满也会提醒 #163
- 增加 `geetest_params`,用于填写人机验证打码平台API需要传入的URL参数 by @Joseandluue
> [插件偏好设置 - geetest_params](https://github.com/Ljzd-PRO/nonebot-plugin-mystool/wiki/Configuration-Preference#geetest_params)
### 2023.8.21 - v1.3.0
- 修复米游币任务中**讨论区签到失败**的问题 #173
- **讨论区签到**增加通过打码平台自动完成**人机验证**的支持 #157
Expand Down Expand Up @@ -48,7 +57,8 @@
- 可支持多个 QQ 账号,每个 QQ 账号可绑定多个米哈游账户
- QQ 推送执行结果通知
- 原神、崩坏:星穹铁道状态便笺通知
- 可为每日米游币任务、游戏签到配置人机验证打码平台
- 可为登录、每日米游币任务、游戏签到配置人机验证打码平台
- 可配置用户黑名单/白名单

## 📖 使用说明

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot-plugin-mystool"
version = "v1.3.0"
version = "v1.3.1"
description = "QQ聊天、频道机器人插件 | 米游社工具-每日米游币任务、游戏签到、商品兑换、免抓包登录、原神崩铁便笺提醒"
license = "MIT"
authors = [
Expand Down
7 changes: 5 additions & 2 deletions src/nonebot_plugin_mystool/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,13 @@ class MmtData(BaseModel):
"""
challenge: Optional[str]
gt: Optional[str]
mmt_key: str
"""验证ID,即 极验文档 中的captchaId,极验后台申请得到"""
mmt_key: Optional[str]
"""验证任务"""
new_captcha: Optional[bool]
"""宕机情况下使用"""
risk_type: Optional[str]
"""任务类型,如滑动拼图 slide"""
"""结合风控融合,指定验证形式"""
success: Optional[int]
use_v4: Optional[bool]
"""是否使用极验第四代 GT4"""
Expand Down
72 changes: 29 additions & 43 deletions src/nonebot_plugin_mystool/game_sign_api.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"""
### 米游社的游戏签到相关API
"""
from typing import List, Optional, Tuple, Literal, Set, Type, Callable, Any, Coroutine, Union
from typing import List, Optional, Tuple, Literal, Set, Type
from urllib.parse import urlencode

import httpx
import tenacity

from .data_model import GameRecord, BaseApiStatus, Award, GameSignInfo, GeetestResult
from .data_model import GameRecord, BaseApiStatus, Award, GameSignInfo, GeetestResult, MmtData
from .plugin_data import PluginDataManager
from .simple_api import ApiResultHandler, HEADERS_API_TAKUMI_MOBILE, is_incorrect_return, device_login, device_save
from .user_data import UserAccount
from .utils import logger, generate_ds, \
get_async_retry, get_validate
get_async_retry

_conf = PluginDataManager.plugin_data

Expand Down Expand Up @@ -132,17 +132,19 @@ async def get_info(self, platform: Literal["ios", "android"] = "ios", retry: boo

async def sign(self,
platform: Literal["ios", "android"] = "ios",
on_geetest_callback: Union[Callable[[], Any], Coroutine] = None,
retry: bool = True) -> BaseApiStatus:
mmt_data: MmtData = None,
geetest_result: GeetestResult = None,
retry: bool = True) -> Tuple[BaseApiStatus, Optional[MmtData]]:
"""
签到
:param platform: 设备平台
:param on_geetest_callback: 开始尝试进行人机验证时调用的回调函数
:param mmt_data: 人机验证任务
:param geetest_result: 用于执行签到的人机验证结果
:param retry: 是否允许重试
"""
if not self.record:
return BaseApiStatus(success=True)
return BaseApiStatus(success=True), None
content = {
"act_id": self.ACT_ID,
"region": self.record.region,
Expand All @@ -151,32 +153,30 @@ async def sign(self,
headers = HEADERS_API_TAKUMI_MOBILE.copy()
if platform == "ios":
headers["x-rpc-device_id"] = self.account.device_id_ios
headers["Sec-Fetch-Dest"] = "empty"
headers["Sec-Fetch-Site"] = "same-site"
headers["DS"] = generate_ds()
else:
await device_login(self.account)
await device_save(self.account)
headers["x-rpc-device_id"] = self.account.device_id_android
headers["x-rpc-device_model"] = _conf.device_config.X_RPC_DEVICE_MODEL_ANDROID
headers["User-Agent"] = _conf.device_config.USER_AGENT_ANDROID
headers["x-rpc-device_name"] = _conf.device_config.X_RPC_DEVICE_NAME_ANDROID
headers["x-rpc-channel"] = _conf.device_config.X_RPC_CHANNEL_ANDROID
headers["x-rpc-sys_version"] = _conf.device_config.X_RPC_SYS_VERSION_ANDROID
headers["x-rpc-client_type"] = "2"
headers.pop("x-rpc-platform")
await device_login(self.account)
await device_save(self.account)
headers["DS"] = generate_ds(data=content)

challenge = ""
"""人机验证任务 challenge"""
geetest_result = GeetestResult("", "")
"""人机验证结果"""
headers.pop("x-rpc-platform")

try:
async for attempt in get_async_retry(retry):
with attempt:
if geetest_result.validate:
if geetest_result:
headers["x-rpc-validate"] = geetest_result.validate
headers["x-rpc-challenge"] = challenge
headers["x-rpc-seccode"] = f'{geetest_result.validate}|jordan'
headers["x-rpc-challenge"] = mmt_data.challenge
headers["x-rpc-seccode"] = geetest_result.seccode
logger.info("游戏签到 - 尝试使用人机验证结果进行签到")

async with httpx.AsyncClient() as client:
res = await client.post(
Expand All @@ -192,44 +192,30 @@ async def sign(self,
logger.info(
f"游戏签到 - 用户 {self.account.bbs_uid} 登录失效")
logger.debug(f"网络请求返回: {res.text}")
return BaseApiStatus(login_expired=True)
if api_result.invalid_ds:
return BaseApiStatus(login_expired=True), None
elif api_result.invalid_ds:
logger.info(
f"游戏签到 - 用户 {self.account.bbs_uid} DS 校验失败")
logger.debug(f"网络请求返回: {res.text}")
return BaseApiStatus(invalid_ds=True)
if api_result.data.get("risk_code") != 0:
return BaseApiStatus(invalid_ds=True), None
elif api_result.data.get("risk_code") != 0:
logger.warning(
f"{_conf.preference.log_head}游戏签到 - 用户 {self.account.bbs_uid} 可能被人机验证阻拦")
logger.debug(f"{_conf.preference.log_head}网络请求返回: {res.text}")
gt = api_result.data.get("gt", None)
challenge = api_result.data.get("challenge", None)
if gt and challenge:
geetest_result = await get_validate(gt, challenge)
if _conf.preference.geetest_url:
if on_geetest_callback and attempt.retry_state.attempt_number == 1:
if isinstance(on_geetest_callback, Coroutine):
await on_geetest_callback
else:
on_geetest_callback()
continue
else:
return BaseApiStatus(need_verify=True)
logger.success(f"游戏签到 - 用户 {self.account.bbs_uid} 签到成功")
logger.debug(f"网络请求返回: {res.text}")
return BaseApiStatus(success=True)
return BaseApiStatus(need_verify=True), MmtData.parse_obj(api_result.data)
else:
logger.success(f"游戏签到 - 用户 {self.account.bbs_uid} 签到成功")
logger.debug(f"网络请求返回: {res.text}")
return BaseApiStatus(success=True), None

except tenacity.RetryError as e:
if is_incorrect_return(e):
logger.exception(f"游戏签到 - 服务器没有正确返回")
logger.debug(f"网络请求返回: {res.text}")
return BaseApiStatus(incorrect_return=True)
elif _conf.preference.geetest_url and gt and challenge:
logger.error(f"游戏签到 - 进行人机验证失败")
return BaseApiStatus(need_verify=True)
return BaseApiStatus(incorrect_return=True), None
else:
logger.exception(f"游戏签到 - 请求失败")
return BaseApiStatus(network_error=True)
return BaseApiStatus(network_error=True), None


class GenshinImpactSign(BaseGameSign):
Expand Down
41 changes: 34 additions & 7 deletions src/nonebot_plugin_mystool/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
from nonebot.internal.params import ArgStr
from nonebot.params import ArgPlainText, T_State

from .data_model import CreateMobileCaptchaStatus
from .plugin_data import PluginDataManager, write_plugin_data
from .simple_api import get_login_ticket_by_captcha, get_multi_token_by_login_ticket, get_stoken_v2_by_v1, \
get_ltoken_by_stoken, get_cookie_token_by_stoken, get_device_fp, create_mmt, create_mobile_captcha
from .user_data import UserAccount, UserData
from .utils import logger, COMMAND_BEGIN, GeneralMessageEvent, GeneralPrivateMessageEvent, GeneralGroupMessageEvent, \
generate_qr_img
generate_qr_img, get_validate, read_blacklist, read_whitelist

_conf = PluginDataManager.plugin_data

Expand All @@ -31,6 +32,12 @@ async def handle_first_receive(event: Union[GeneralMessageEvent]):
if isinstance(event, GeneralGroupMessageEvent):
await get_cookie.finish("⚠️为了保护您的隐私,请私聊进行登录。")
user_num = len(set(_conf.users.values())) # 由于加入了用户数据绑定功能,可能存在重复的用户数据对象,需要去重
if _conf.preference.enable_blacklist:
if event.get_user_id() in read_blacklist():
await get_cookie.finish("⚠️您已被加入黑名单,无法使用本功能")
elif _conf.preference.enable_whitelist:
if event.get_user_id() not in read_whitelist():
await get_cookie.finish("⚠️您不在白名单内,无法使用本功能")
if user_num <= _conf.preference.max_user or _conf.preference.max_user in [-1, 0]:
# QQ频道可能无法发送链接,需要发送二维码
login_url = "https://user.mihoyo.com/#/login/captcha"
Expand Down Expand Up @@ -75,15 +82,35 @@ async def _(event: Union[GeneralPrivateMessageEvent], state: T_State, phone: str
device_id = None
mmt_status, mmt_data, device_id, _ = await create_mmt(device_id=device_id)
state['device_id'] = device_id
if mmt_status and not mmt_data.gt:
captcha_status, _ = await create_mobile_captcha(phone_number=phone, mmt_data=mmt_data, device_id=device_id)
if captcha_status:
await get_cookie.send("检测到无需进行人机验证,已发送短信验证码,请查收")
return
elif captcha_status.invalid_phone_number:
if mmt_status:
if not mmt_data.gt:
captcha_status, _ = await create_mobile_captcha(phone_number=phone, mmt_data=mmt_data, device_id=device_id)
if captcha_status:
await get_cookie.send("检测到无需进行人机验证,已发送短信验证码,请查收")
return
elif _conf.preference.geetest_url:
await get_cookie.send("⏳正在尝试完成人机验证,请稍后...")
# TODO: 人机验证待支持 GT4
geetest_result = await get_validate(gt=mmt_data.gt)
captcha_status, _ = await create_mobile_captcha(
phone_number=phone,
mmt_data=mmt_data,
geetest_result=geetest_result,
use_v4=False,
device_id=device_id
)
if captcha_status:
await get_cookie.send("已发送短信验证码,请查收")
return
elif captcha_status.incorrect_geetest:
await get_cookie.send("⚠️尝试进行人机验证失败,请手动获取短信验证码")
else:
captcha_status = CreateMobileCaptchaStatus()
if captcha_status.invalid_phone_number:
await get_cookie.reject("⚠️手机号无效,请重新发送手机号")
elif captcha_status.not_registered:
await get_cookie.reject("⚠️手机号未注册,请注册后重新发送手机号")

await get_cookie.send('2.前往米哈游官方登录页,获取验证码(不要登录!)')


Expand Down
57 changes: 41 additions & 16 deletions src/nonebot_plugin_mystool/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .simple_api import genshin_note, get_game_record, starrail_note
from .user_data import UserData
from .utils import get_file, logger, COMMAND_BEGIN, GeneralMessageEvent, send_private_msg, get_all_bind, \
get_unique_users
get_unique_users, get_validate

_conf = PluginDataManager.plugin_data

Expand Down Expand Up @@ -69,6 +69,9 @@ class GenshinNoteNotice(GenshinNote):
原神便笺通知状态
"""
current_resin: bool = False
"""是否达到阈值"""
current_resin_full: bool = False
"""是否溢出"""
current_home_coin: bool = False
transformer: bool = False

Expand All @@ -78,6 +81,9 @@ class StarRailNoteNotice(StarRailNote):
星穹铁道便笺通知状态
"""
current_stamina: bool = False
"""是否达到阈值"""
current_stamina_full: bool = False
"""是否溢出"""
current_train_score: bool = False
current_rogue_score: bool = False

Expand Down Expand Up @@ -198,13 +204,14 @@ async def perform_game_sign(

# 若没签到,则进行签到功能;若获取今日签到情况失败,仍可继续
if (get_info_status and not info.is_sign) or not get_info_status:
if matcher:
sign_status = await signer.sign(
account.platform,
matcher.send("⏳正在尝试完成人机验证,请稍后...")
)
else:
sign_status = await signer.sign(account.platform)
sign_status, mmt_data = await signer.sign(account.platform)
if sign_status.need_verify:
if _conf.preference.geetest_url:
if matcher:
await matcher.send("⏳正在尝试完成人机验证,请稍后...")
geetest_result = await get_validate(mmt_data.gt, mmt_data.challenge)
sign_status, _ = await signer.sign(account.platform, mmt_data, geetest_result)

if not sign_status and (user.enable_notice or matcher):
if sign_status.login_expired:
message = f"⚠️账户 {account.bbs_uid} 🎮『{signer.NAME}』签到时服务器返回登录失效,请尝试重新登录绑定账户"
Expand Down Expand Up @@ -443,12 +450,20 @@ async def genshin_note_check(user: UserData, user_ids: Iterable[str], matcher: M
# 体力溢出提醒
if note.current_resin >= account.user_resin_threshold:
# 防止重复提醒
if not genshin_notice.current_resin:
genshin_notice.current_resin = True
msg += '❕您的树脂已经满啦\n'
do_notice = True
if not genshin_notice.current_resin_full:
if note.current_resin == 160:
genshin_notice.current_resin_full = True
msg += '❕您的树脂已经满啦\n'
do_notice = True
elif not genshin_notice.current_resin:
genshin_notice.current_resin_full = False
genshin_notice.current_resin = True
msg += '❕您的树脂已达到提醒阈值\n'
do_notice = True
else:
genshin_notice.current_resin = False
genshin_notice.current_resin_full = False

# 洞天财瓮溢出提醒
if note.current_home_coin == note.max_home_coin:
# 防止重复提醒
Expand All @@ -458,6 +473,7 @@ async def genshin_note_check(user: UserData, user_ids: Iterable[str], matcher: M
do_notice = True
else:
genshin_notice.current_home_coin = False

# 参量质变仪就绪提醒
if note.transformer:
if note.transformer_text == '已准备就绪':
Expand Down Expand Up @@ -521,12 +537,20 @@ async def starrail_note_check(user: UserData, user_ids: Iterable[str], matcher:
# 体力溢出提醒
if note.current_stamina >= account.user_stamina_threshold:
# 防止重复提醒
if not starrail_notice.current_stamina:
starrail_notice.current_stamina = True
msg += '❕您的开拓力已经满啦\n'
do_notice = True
if not starrail_notice.current_stamina_full:
if note.current_stamina >= 180:
starrail_notice.current_stamina_full = True
msg += '❕您的开拓力已经溢出\n'
do_notice = True
elif not starrail_notice.current_stamina:
starrail_notice.current_stamina_full = False
starrail_notice.current_stamina = True
msg += '❕您的开拓力已达到提醒阈值\n'
do_notice = True
else:
starrail_notice.current_stamina = False
starrail_notice.current_stamina_full = False

# 每日实训状态提醒
if note.current_train_score == note.max_train_score:
# 防止重复提醒
Expand All @@ -536,6 +560,7 @@ async def starrail_note_check(user: UserData, user_ids: Iterable[str], matcher:
do_notice = True
else:
starrail_notice.current_train_score = False

# 每周模拟宇宙积分提醒
if note.current_rogue_score == note.max_rogue_score:
# 防止重复提醒
Expand Down
Loading

0 comments on commit 4980931

Please sign in to comment.