bhyg-decomp/login.py
2024-07-01 00:27:51 +08:00

510 lines
20 KiB
Python

# Copyright (c) 2023-2024 ZianTT, FriendshipEnder
import base64
import json
import time
import qrcode
import requests
from loguru import logger
from utils import prompt
import inquirer
from i18n import *
from globals import *
def cookie(cookies):
lst = []
for item in cookies.items():
lst.append(f"{item[0]}={item[1]}")
cookie_str = ";".join(lst)
return cookie_str
def appsign(params):
import hashlib
import urllib.parse
appkey = '1d8b6e7d45233436'
appsec = '560c52ccd288fed045859ed18bffd973'
params.update({'appkey': appkey})
params = dict(sorted(params.items())) # 按照 key 重排参数
query = urllib.parse.urlencode(params) # 序列化参数
sign = hashlib.md5((query + appsec).encode()).hexdigest() # 计算 api 签名
params.update({'sign': sign})
return params
def _verify(gt, challenge, token):
global sdk
from geetest import run
time_start = time.time()
data = run(gt, challenge, token, "local_gt")
delta = time.time() - time_start
sdk.metrics.distribution(
key="gt_solve_time",
value=delta * 1000,
unit="millisecond"
)
return data
def qr_login(session, headers):
#from globals import i18n_lang
generate = session.get(
"https://passport.bilibili.com/x/passport-login/web/qrcode/generate",
headers=headers,
)
generate = generate.json()
if generate["code"] == 0:
url = generate["data"]["url"]
else:
logger.error(generate)
return
qr = qrcode.QRCode()
qr.add_data(url)
qr.print_ascii(invert=True)
img = qr.make_image()
img.show()
logger.info(i18n_gt()["qr_login"])
while True:
time.sleep(1)
url = (
"https://passport.bilibili.com/x/passport-login/web/qrcode/poll?source=main-fe-header&qrcode_key="
+ generate["data"]["qrcode_key"]
)
req = session.get(url, headers=headers)
# read as utf-8
check = req.json()["data"]
if check["code"] == 0:
logger.success(i18n_gt()["login_success"])
cookies = requests.utils.dict_from_cookiejar(session.cookies)
break
elif check["code"] == 86101:
pass
elif check["code"] == 86090:
logger.info(check["message"])
elif check["code"] == 86083:
logger.error(check["message"])
return qr_login(session, headers)
elif check["code"] == 86038:
logger.error(check["message"])
return qr_login(session, headers)
else:
logger.error(check)
return qr_login(session, headers)
return cookie(cookies)
def verify_code_login(session, headers):
#from globals import i18n_lang
# https://passport.bilibili.com/x/passport-login/captcha
captcha = session.get(
"https://passport.bilibili.com/x/passport-login/captcha", headers=headers
).json()
gt = captcha["data"]["geetest"]["gt"]
challenge = captcha["data"]["geetest"]["challenge"]
token = captcha["data"]["token"]
tel = prompt([inquirer.Text("tel", message=i18n_gt()["input_phone_num"], validate=lambda _, x: len(x) == 11)])["tel"]
logger.info(i18n_gt()["input_auto_verify"])
cap_data = _verify(gt, challenge, token)
while cap_data == False:
logger.error(i18n_gt()["input_verify_fail"])
captcha = session.post(
"https://passport.bilibili.com/x/passport-login/captcha",
headers=headers,
).json()
gt = captcha["data"]["geetest"]["gt"]
challenge = captcha["data"]["geetest"]["challenge"]
token = captcha["data"]["token"]
cap_data = _verify(gt, challenge, token)
logger.success(i18n_gt()["input_verify_success"])
data = {
"cid": "86",
"tel": tel,
"token": token,
"challenge": cap_data["challenge"],
"validate": cap_data["validate"],
"seccode": cap_data["seccode"] + "|jordan",
}
# https://passport.bilibili.com/x/passport-login/web/sms/send
send = session.post(
"https://passport.bilibili.com/x/passport-login/web/sms/send",
headers=headers,
data=data,
).json()
if send["code"] != 0:
logger.error(f"{send['code']}: {send['message']}")
return verify_code_login(session, headers)
else:
logger.success(i18n_gt()["sms_code_send_ok"])
send_token = send["data"]["captcha_key"]
while True:
code = prompt([inquirer.Text("code", message=i18n_gt()["input_sms_code"], validate=lambda _, x: len(x) == 6)])["code"]
# https://passport.bilibili.com/x/passport-login/web/login/sms
data = {"cid": "86", "tel": tel, "captcha_key": send_token, "code": code}
login = session.post(
"https://passport.bilibili.com/x/passport-login/web/login/sms",
headers=headers,
data=data,
).json()
if login["code"] != 0:
logger.error(f"{login['code']}: {login['message']}")
else:
logger.success(i18n_gt()["login_success"])
cookies = requests.utils.dict_from_cookiejar(session.cookies)
return cookie(cookies)
def verify_code_login_app(session, headers):
#from globals import i18n_lang
logger.warning(i18n_gt()["beta_test_func"])
import uuid
def buvid():
import hashlib
import random
mac = []
for i in range(6):
num = random.randint(0, 0xff)
mac.append(hex(num)[2:])
md5 = hashlib.md5(":".join(mac).encode()).hexdigest()
md5Arr = list(md5)
return f"XY{md5Arr[2]}{md5Arr[12]}{md5Arr[22]}{md5}"
# https://passport.bilibili.com/x/passport-login/captcha
# captcha = session.get(
# "https://passport.bilibili.com/x/passport-login/captcha", headers=headers
# ).json()
# gt = captcha["data"]["geetest"]["gt"]
# challenge = captcha["data"]["geetest"]["challenge"]
# token = captcha["data"]["token"]
tel = prompt([inquirer.Text("tel", message=i18n_gt()["input_phone_num"], validate=lambda _, x: len(x) == 11)])["tel"]
# logger.info(i18n_gt()["input_auto_verify"])
# cap_data = _verify(gt, challenge, token)
# while cap_data == False:
# logger.error(i18n_gt()["input_verify_fail"])
# captcha = session.post(
# "https://passport.bilibili.com/x/passport-login/captcha",
# headers=headers,
# ).json()
# gt = captcha["data"]["geetest"]["gt"]
# challenge = captcha["data"]["geetest"]["challenge"]
# token = captcha["data"]["token"]
# cap_data = _verify(gt, challenge, token)
logger.success(i18n_gt()["input_verify_success"])
session_id = uuid.uuid4().hex.upper()
buvid = buvid()
data = {
"cid": "86",
"tel": tel,
"login_session_id": session_id,
# "recaptcha_token": token,
# "gee_challenge": cap_data["challenge"],
# "gee_validate": cap_data["validate"],
# "gee_seccode": cap_data["seccode"] + "|jordan",
"channel": "bili",
"buvid": buvid,
"local_id": buvid,
"statistics": '{"appId":1,"platform":3,"version":"8.0.0","abtest":""}',
"ts": round(time.time())
}
logger.debug(data)
# https://passport.bilibili.com/x/passport-login/sms/send
send = session.post(
"https://passport.bilibili.com/x/passport-login/sms/send",
headers=headers,
data=appsign(data),
).json()
if send["code"] != 0:
logger.error(f"{send['code']}: {send['message']}")
return verify_code_login_app(session, headers)
else:
logger.success(i18n_gt()["sms_code_send_ok"])
send_token = send["data"]["captcha_key"]
while True:
code = prompt([inquirer.Text("code", message=i18n_gt()["input_sms_code"], validate=lambda _, x: len(x) == 6)])["code"]
# https://passport.bilibili.com/x/passport-login/login/sms
data = {"cid": 86, "tel": int(tel), "captcha_key": send_token, "code": int(code),
"login_session_id": session_id}
login = session.post(
"https://passport.bilibili.com/x/passport-login/login/sms",
headers=headers,
data=appsign(data),
).json()
if login["code"] != 0:
logger.error(f"{login['code']}: {login['message']}")
else:
logger.success(i18n_gt()["login_success"])
cookies = requests.utils.dict_from_cookiejar(session.cookies)
return cookie(cookies)
def password_login(session, headers):
#from globals import i18n_lang
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
username = prompt([inquirer.Text("username", message=i18n_gt()["input_user_name"])])["username"]
password = prompt([inquirer.Password("password", message=i18n_gt()["input_user_password"])])["password"]
captcha = session.get(
"https://passport.bilibili.com/x/passport-login/captcha", headers=headers
).json()
gt = captcha["data"]["geetest"]["gt"]
challenge = captcha["data"]["geetest"]["challenge"]
token = captcha["data"]["token"]
logger.info(i18n_gt()["input_auto_verify"])
cap_data = _verify(gt, challenge, token)
while cap_data == False:
captcha = session.get(
"https://passport.bilibili.com/x/passport-login/captcha",
headers=headers,
).json()
gt = captcha["data"]["geetest"]["gt"]
challenge = captcha["data"]["geetest"]["challenge"]
token = captcha["data"]["token"]
logger.error(i18n_gt()["input_verify_fail"])
cap_data = _verify(gt, challenge, token)
logger.success(i18n_gt()["input_verify_success"])
key = session.get(
"https://passport.bilibili.com/x/passport-login/web/key", headers=headers
).json()["data"]
rsa_pub = RSA.importKey(key["key"])
cipher = PKCS1_v1_5.new(rsa_pub)
enc = base64.b64encode(cipher.encrypt((key["hash"] + password).encode())).decode(
"utf8"
)
data = {
"username": username,
"password": enc,
"token": token,
"challenge": cap_data["challenge"],
"validate": cap_data["validate"],
"seccode": cap_data["seccode"] + "|jordan",
}
login = session.post(
"https://passport.bilibili.com/x/passport-login/web/login",
headers=headers,
data=data,
).json()
if login["code"] != 0:
logger.error(f"{login['code']}: {login['message']}")
if login["code"] == -662:
logger.error(i18n_gt()["request_too_slow"])
return password_login(session, headers)
else:
if login["data"]["status"] == 2 or login["data"]["status"] == 1:
logger.warning(i18n_gt()["need_2nd_verify"])
# extract tmp_code request_id from login["data"]["url"]
tmp_token = login["data"]["url"].split("tmp_token=")[1][:32]
try:
scene = (
login["data"]["url"]
.split("tmp_token=")[0]
.split("scene=")[1]
.split("&")[0]
)
except IndexError:
scene = "loginTelCheck"
info = session.get(
"https://passport.bilibili.com/x/safecenter/user/info?tmp_code="
+ tmp_token,
headers=headers,
).json()
if info["data"]["account_info"]["bind_tel"]:
logger.info(i18n_gt()["phone_banded"])
tel = info["data"]["account_info"]["hide_tel"]
logger.info(i18n_gt()["will_send_sms"] + tel)
captcha = session.post(
"https://passport.bilibili.com/x/safecenter/captcha/pre",
headers=headers,
).json()
gt = captcha["data"]["gee_gt"]
challenge = captcha["data"]["gee_challenge"]
token = captcha["data"]["recaptcha_token"]
logger.info(i18n_gt()["input_auto_verify"])
cap_data = _verify(gt, challenge, token)
while cap_data == False:
logger.error(i18n_gt()["input_verify_fail"])
captcha = session.post(
"https://passport.bilibili.com/x/safecenter/captcha/pre",
headers=headers,
).json()
gt = captcha["data"]["gee_gt"]
challenge = captcha["data"]["gee_challenge"]
token = captcha["data"]["recaptcha_token"]
cap_data = _verify(gt, challenge, token)
logger.success(i18n_gt()["input_verify_success"])
data = {
"recaptcha_token": token,
"gee_challenge": cap_data["challenge"],
"gee_validate": cap_data["validate"],
"gee_seccode": cap_data["seccode"] + "|jordan",
"sms_type": scene,
"tmp_code": tmp_token,
}
# https://passport.bilibili.com/x/safecenter/common/sms/send
send = session.post(
"https://passport.bilibili.com/x/safecenter/common/sms/send",
headers=headers,
data=data,
).json()
if send["code"] != 0:
logger.error(f"{send['code']}: {send['message']}")
return password_login(session, headers)
else:
logger.success(i18n_gt()["sms_code_send_ok"])
send_token = send["data"]["captcha_key"]
while True:
code = prompt([inquirer.Text("code", message=i18n_gt()["input_sms_code"], validate=lambda _, x: len(x) == 6)])[
"code"]
data = {
"type": "loginTelCheck",
"tmp_code": tmp_token,
"captcha_key": send_token,
"code": code,
}
url = "https://passport.bilibili.com/x/safecenter/login/tel/verify"
if login["data"]["status"] == 1:
del data["type"]
data["verify_type"] = "sms"
url = "https://passport.bilibili.com/x/safecenter/sec/verify"
send = session.post(url, headers=headers, data=data).json()
if send["code"] != 0:
logger.error(f"{send['code']}: {send['message']}")
else:
logger.success(i18n_gt()["login_success"])
code = send["data"]["code"]
data = {"source": "risk", "code": code}
session.post(
"https://passport.bilibili.com/x/passport-login/web/exchange_cookie",
headers=headers,
data=data,
).json()
cookies = requests.utils.dict_from_cookiejar(session.cookies)
return cookie(cookies)
logger.success(i18n_gt()["login_success"])
cookies = requests.utils.dict_from_cookiejar(session.cookies)
return cookie(cookies)
def sns_login(session, headers):
#from globals import i18n_lang
method = \
prompt([inquirer.List("method", message=i18n_gt()["choose_sns_login"],\
choices=[i18n_gt()["sns_micromessage"],\
i18n_gt()["sns_qq"],\
i18n_gt()["sns_microblog"]],\
default=i18n_gt()["sns_micromessage"])])["method"]
if method == i18n_gt()["sns_micromessage"]:
sns = "wechat"
elif method == i18n_gt()["sns_qq"]:
sns = "qq"
elif method == i18n_gt()["sns_microblog"]:
sns = "weibo"
else:
logger.error(i18n_gt()["login_not_supported"])
return sns_login(session, headers)
# https://passport.bilibili.com/x/passport-login/web/sns/state/generate
state = session.get(
"https://passport.bilibili.com/x/passport-login/web/sns/state/generate",
headers=headers,
).json()["data"]["csrf_state"]
# https://passport.bilibili.com/x/passport-login/web/sns/authorize/url
data = {
"sns_platform": sns,
"csrf_state": state,
"gourl": "http://127.0.0.1/",
"source": "main-fe-header",
}
url = session.post(
"https://passport.bilibili.com/x/passport-login/web/sns/authorize/url",
headers=headers,
data=data,
).json()["data"]["url"]
logger.info(url)
logger.info(i18n_gt()["open_in_browser"])
# https://passport.bilibili.com/x/passport-login/web/sns/login
redirect = prompt([inquirer.Text("redirect", message=i18n_gt()["input_redirect"])])["redirect"]
# get params from redirect
try:
redirect = redirect.split("?")[1]
params = {}
for item in redirect.split("&"):
key, value = item.split("=")
params[key] = value
data = {
"csrf_state": state,
"gourl": params["go_url"],
"source": "main-fe-header",
"sns_platform": params["sns_platform"],
"code": params["code"],
}
except Exception:
logger.error(i18n_gt()["connect_link_error"])
return sns_login(session, headers)
login = session.post(
"https://passport.bilibili.com/x/passport-login/web/sns/login",
headers=headers,
data=data,
).json()
if login["code"] != 0:
logger.error(f"{login['code']}: {login['message']}")
else:
if not login["data"]["has_bind"]:
logger.error(i18n_gt()["connect_no_account"])
return sns_login(session, headers)
logger.success(i18n_gt()["login_success"])
cookies = requests.utils.dict_from_cookiejar(session.cookies)
return cookie(cookies)
def interactive_login(sentry_sdk=None):
#from globals import i18n_lang
global sdk
sdk = sentry_sdk
import random
headers = {
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/618.1.15.10.15 (KHTML, like Gecko) Mobile/21F90 BiliApp/77900100 os/ios model/iPhone 15 mobi_app/iphone build/77900100 osVer/17.5.1 network/2 channel/AppStore c_locale/zh-Hans_CN s_locale/zh-Hans_CH disable_rcmd/0 "+str(random.randint(0, 9999)),
}
session = requests.session()
session.get("https://www.bilibili.com/", headers=headers)
try: # 登录方式 cookie 扫码 用户名密码 web短信 app短信 sns
method = prompt([inquirer.List("method", message=i18n_gt()["bi_login_method"],
choices=[i18n_gt()["bi_login_cookie"], i18n_gt()["bi_login_qrcode"], \
i18n_gt()["bi_login_user_pass"], i18n_gt()["bi_login_web_sms"], \
i18n_gt()["bi_login_app_sms"], i18n_gt()["bi_login_sns"]],
default= i18n_gt()["bi_login_qrcode"])]) #默认扫码
if method["method"] == i18n_gt()["bi_login_cookie"]:
cookie_str = input(i18n_gt()["bi_input_cookie"])
# verify cookie
try:
session.get("https://www.bilibili.com/",
headers={"User-Agent": "Mozilla/5.0 BiliApp/80000100", "Cookie": cookie_str})
except Exception:
logger.error(i18n_gt()["bi_illegal_cookie"])
return interactive_login()
elif method["method"] == i18n_gt()["bi_login_qrcode"]:
cookie_str = qr_login(session, headers)
elif method["method"] == i18n_gt()["bi_login_user_pass"]:
cookie_str = password_login(session, headers)
elif method["method"] == i18n_gt()["bi_login_web_sms"]:
cookie_str = verify_code_login(session, headers)
elif method["method"] == i18n_gt()["bi_login_sns"]:
cookie_str = sns_login(session, headers)
elif method["method"] == i18n_gt()["bi_login_app_sms"]:
cookie_str = verify_code_login_app(session, headers)
else:
logger.error(i18n_gt()["login_not_supported"])
return interactive_login()
except Exception as e:
logger.error(i18n_gt()["login_failed"])
return interactive_login()
logger.debug("=" * 20)
logger.debug(cookie_str)
logger.debug("=" * 20)
return cookie_str