米游社支持直接通过手机号绑定

This commit is contained in:
omg-xtao 2022-09-17 22:58:54 +08:00 committed by GitHub
parent c6baac1659
commit babdb90db7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 225 additions and 7 deletions

3
.gitignore vendored
View File

@ -31,6 +31,7 @@ __pycache__/
**_test.html **_test.html
test_**.html test_**.html
logs/ logs/
report/
/resources/*/*/test/ /resources/*/*/test/
### DotEnv ### ### DotEnv ###
@ -47,4 +48,4 @@ plugins/private
### mtp ### ### mtp ###
paimon.session paimon.session
PaimonBot.session PaimonBot.session
PaimonBot.session-journal PaimonBot.session-journal

View File

@ -249,3 +249,123 @@ class YuanShen:
:return: :return:
""" """
await self.client.aclose() await self.client.aclose()
class SignIn:
LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"
S_TOKEN_URL = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?" \
"login_ticket={0}&token_types=3&uid={1}"
BBS_URL = "https://api-takumi.mihoyo.com/account/auth/api/webLoginByMobile"
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15"
HEADERS = {
"Host": "webapi.account.mihoyo.com",
"Connection": "keep-alive",
"sec-ch-ua": "\".Not/A)Brand\";v=\"99\", \"Microsoft Edge\";v=\"103\", \"Chromium\";v=\"103\"",
"DNT": "1",
"x-rpc-device_model": "OS X 10.15.7",
"sec-ch-ua-mobile": "?0",
"User-Agent": USER_AGENT,
'x-rpc-device_id': get_device_id(USER_AGENT),
"Accept": "application/json, text/plain, */*",
"x-rpc-device_name": "Microsoft Edge 103.0.1264.62",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"x-rpc-client_type": "4",
"sec-ch-ua-platform": "\"macOS\"",
"Origin": "https://user.mihoyo.com",
"Sec-Fetch-Site": "same-site",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://user.mihoyo.com/",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"
}
BBS_HEADERS = {
"Host": "api-takumi.mihoyo.com",
"Content-Type": "application/json;charset=utf-8",
"Origin": "https://bbs.mihoyo.com",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Accept": "application/json, text/plain, */*",
"User-Agent": USER_AGENT,
"Referer": "https://bbs.mihoyo.com/",
"Accept-Language": "zh-CN,zh-Hans;q=0.9"
}
def __init__(self, phone: int):
self.phone = phone
self.client = AsyncClient()
self.uid = 0
self.cookie = {}
def parse_uid(self):
"""
从cookie中获取uid
:param self:
:return:
"""
if "login_ticket" not in self.cookie:
return
for item in ["login_uid", "stuid", "ltuid", "account_id"]:
if item in self.cookie:
self.uid = self.cookie[item]
break
for item in ["login_uid", "stuid", "ltuid", "account_id"]:
self.cookie[item] = self.uid
@staticmethod
def check_error(data: dict) -> bool:
"""
检查是否有错误
:param data:
:return:
"""
res_data = data.get("data", {})
return res_data.get("msg") == "验证码错误" or res_data.get("info") == "Captcha not match Err"
async def login(self, captcha: int) -> bool:
data = await self.client.post(
self.LOGIN_URL,
data={"mobile": str(self.phone), "mobile_captcha": str(captcha), "source": "user.mihoyo.com"},
headers=self.HEADERS
)
res_json = data.json()
if self.check_error(res_json):
return False
for k, v in data.cookies.items():
self.cookie[k] = v
self.parse_uid()
return bool(self.uid)
async def get_s_token(self):
data = await self.client.get(
self.S_TOKEN_URL.format(self.cookie["login_ticket"], self.uid),
headers={"User-Agent": self.USER_AGENT}
)
res_json = data.json()
res_data = res_json.get("data", {}).get("list", [])
for i in res_data:
if i.get("name") and i.get("token"):
self.cookie[i.get("name")] = i.get("token")
async def get_token(self, captcha: int) -> bool:
data = await self.client.post(
self.BBS_URL,
headers=self.BBS_HEADERS,
json={
"is_bh2": False,
"mobile": str(self.phone),
"captcha": str(captcha),
"action_type": "login",
"token_type": 6
}
)
res_json = data.json()
if self.check_error(res_json):
return False
for k, v in data.cookies.items():
self.cookie[k] = v
return "cookie_token" in self.cookie

View File

@ -15,6 +15,7 @@ from core.plugin import Plugin, handler, conversation
from core.user.error import UserNotFoundError from core.user.error import UserNotFoundError
from core.user.models import User from core.user.models import User
from core.user.services import UserService from core.user.services import UserService
from modules.apihelper.hyperion import YuanShen
from utils.decorators.error import error_callable from utils.decorators.error import error_callable
from utils.decorators.restricts import restricts from utils.decorators.restricts import restricts
from utils.log import logger from utils.log import logger
@ -27,9 +28,11 @@ class AddUserCommandData(TelegramObject):
region: RegionEnum = RegionEnum.NULL region: RegionEnum = RegionEnum.NULL
cookies: dict = {} cookies: dict = {}
game_uid: int = 0 game_uid: int = 0
phone: int = 0
sign_in_client: Optional[YuanShen.SignIn] = None
CHECK_SERVER, CHECK_COOKIES, COMMAND_RESULT = range(10100, 10103) CHECK_SERVER, CHECK_PHONE, CHECK_CAPTCHA, INPUT_COOKIES, COMMAND_RESULT = range(10100, 10106)
class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation): class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
@ -57,6 +60,22 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)) await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
return CHECK_SERVER return CHECK_SERVER
@conversation.entry_point
@handler.command(command='mlogin', filters=filters.ChatType.PRIVATE, block=True)
@error_callable
async def choose_method(self, update: Update, context: CallbackContext) -> int:
user = update.effective_user
message = update.effective_message
logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号命令请求")
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
if add_user_command_data is None:
cookies_command_data = AddUserCommandData()
cookies_command_data.region = RegionEnum.HYPERION
context.chat_data["add_user_command_data"] = cookies_command_data
text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!该绑定方法仅支持国服,请发送 11 位手机号码!或回复退出取消操作")}'
await message.reply_markdown_v2(text)
return CHECK_PHONE
@conversation.state(state=CHECK_SERVER) @conversation.state(state=CHECK_SERVER)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True) @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@error_callable @error_callable
@ -109,13 +128,83 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
f"2、复制下方的代码并将其粘贴在地址栏中点击右侧箭头\n" \ f"2、复制下方的代码并将其粘贴在地址栏中点击右侧箭头\n" \
f"`{escape_markdown(javascript_android, version=2, entity_type='code')}`" f"`{escape_markdown(javascript_android, version=2, entity_type='code')}`"
await message.reply_markdown_v2(help_message, disable_web_page_preview=True) await message.reply_markdown_v2(help_message, disable_web_page_preview=True)
return CHECK_COOKIES return INPUT_COOKIES
@conversation.state(state=CHECK_COOKIES) @conversation.state(state=CHECK_PHONE)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True) @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@error_callable @error_callable
async def check_cookies(self, update: Update, context: CallbackContext) -> int: async def check_phone(self, update: Update, context: CallbackContext) -> int:
user = update.effective_user message = update.effective_message
if message.text == "退出":
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
try:
if not message.text.startswith("1"):
raise ValueError
phone = int(message.text)
if len(str(phone)) != 11:
raise ValueError
except ValueError:
await message.reply_text("手机号码输入错误,请重新输入!或回复退出取消操作")
return CHECK_PHONE
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
add_user_command_data.phone = phone
await message.reply_text("请打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码,"
"然后将收到的验证码发送给我(请不要在网页上进行登录)")
return CHECK_CAPTCHA
@conversation.state(state=CHECK_CAPTCHA)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@error_callable
async def check_captcha(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message
if message.text == "退出":
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
try:
captcha = int(message.text)
if len(str(captcha)) != 6:
raise ValueError
except ValueError:
await message.reply_text("验证码输入错误,请重新输入!或回复退出取消操作")
return CHECK_CAPTCHA
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
if not add_user_command_data.sign_in_client:
phone = add_user_command_data.phone
client = YuanShen.SignIn(phone)
try:
success = await client.login(captcha)
if not success:
await message.reply_text(
"登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
return ConversationHandler.END
await client.get_s_token()
except Exception:
await message.reply_text("登录失败:米游社返回了错误的数据,请稍后再试!")
return ConversationHandler.END
add_user_command_data.sign_in_client = client
await message.reply_text(
"请再次打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码(需要等待一分钟),"
"然后将收到的验证码发送给我(请不要在网页上进行登录)")
return CHECK_CAPTCHA
else:
client = add_user_command_data.sign_in_client
try:
success = await client.get_token(captcha)
if not success:
await message.reply_text(
"登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
return ConversationHandler.END
except Exception:
await message.reply_text("登录失败:米游社返回了错误的数据,请稍后再试!")
return ConversationHandler.END
add_user_command_data.cookies = client.cookie
return await self.check_cookies(update, context)
@conversation.state(state=INPUT_COOKIES)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@error_callable
async def input_cookies(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message message = update.effective_message
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data") add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
if message.text == "退出": if message.text == "退出":
@ -135,6 +224,15 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
if not cookies: if not cookies:
await message.reply_text("Cookies格式有误请检查", reply_markup=ReplyKeyboardRemove()) await message.reply_text("Cookies格式有误请检查", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
add_user_command_data.cookies = cookies
return await self.check_cookies(update, context)
@staticmethod
async def check_cookies(update: Update, context: CallbackContext) -> int:
user = update.effective_user
message = update.effective_message
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
cookies = add_user_command_data.cookies
if add_user_command_data.region == RegionEnum.HYPERION: if add_user_command_data.region == RegionEnum.HYPERION:
client = genshin.ChineseClient(cookies=cookies) client = genshin.ChineseClient(cookies=cookies)
elif add_user_command_data.region == RegionEnum.HOYOLAB: elif add_user_command_data.region == RegionEnum.HOYOLAB:
@ -157,7 +255,6 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
except (AttributeError, ValueError): except (AttributeError, ValueError):
await message.reply_text("Cookies错误请检查是否正确", reply_markup=ReplyKeyboardRemove()) await message.reply_text("Cookies错误请检查是否正确", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
add_user_command_data.cookies = cookies
add_user_command_data.game_uid = user_info.uid add_user_command_data.game_uid = user_info.uid
reply_keyboard = [['确认', '退出']] reply_keyboard = [['确认', '退出']]
await message.reply_text("获取角色基础信息成功,请检查是否正确!") await message.reply_text("获取角色基础信息成功,请检查是否正确!")