510 lines
20 KiB
Python
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
|