from Requests to HTTPX (Higher performance, I guess!)

This commit is contained in:
Remik1r3n 2025-02-06 23:30:49 +08:00
parent e73bdcda66
commit a16525c52e
8 changed files with 117 additions and 81 deletions

View File

@ -3,16 +3,13 @@
import zlib import zlib
import hashlib import hashlib
import requests import httpx
from loguru import logger from loguru import logger
import random import random
import time import time
from ctypes import c_int32 from ctypes import c_int32
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad from Crypto.Util.Padding import unpad
from Config import * from Config import *
# 舞萌DX 2024 # 舞萌DX 2024
@ -20,10 +17,16 @@ AesKey = "n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@"
AesIV = ";;KjR1C3hgB1ovXa" AesIV = ";;KjR1C3hgB1ovXa"
ObfuscateParam = "BEs2D5vW" ObfuscateParam = "BEs2D5vW"
class WahlapServerBoomedError(Exception): class SDGBApiError(Exception):
pass pass
class Request500Error(Exception): class SDGBMaxRetriesError(SDGBApiError):
pass
class SDGBRequestError(SDGBApiError):
pass
class SDGBResponseError(SDGBApiError):
pass pass
class AES_PKCS7(object): class AES_PKCS7(object):
@ -56,93 +59,84 @@ class AES_PKCS7(object):
padding_text = chr(padding) * padding padding_text = chr(padding) * padding
return text + padding_text return text + padding_text
def SDGBApiHash(api): def getSDGBApiHash(api):
return hashlib.md5((api+"MaimaiChn"+ObfuscateParam).encode()).hexdigest() return hashlib.md5((api+"MaimaiChn"+ObfuscateParam).encode()).hexdigest()
def apiSDGB(data:str, useApi, agentExtraData, noLog=False): def apiSDGB(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=False, timeout:int=5):
""" """
舞萌DX 2024 API 通讯用函数 舞萌DX 2024 API 通讯用函数
:param data: 请求数据 :param data: 请求数据
:param useApi: 使用的 API :param targetApi: 使用的 API
:param agentExtraData: UA 附加信息机台相关则为狗号如A63E01E9564用户相关则为 UID :param userAgentExtraData: UA 附加信息机台相关则为狗号如A63E01E9564用户相关则为 UID
:param noLog: 是否不记录日志 :param noLog: 是否不记录日志
""" """
maxRetries = 3 maxRetries = 3
agentExtra = str(userAgentExtraData)
# 历史遗留代码有时候会传入 int故先全部转 str
agentExtra = str(agentExtraData)
# 编码好请求,准备发送
aes = AES_PKCS7(AesKey, AesIV) aes = AES_PKCS7(AesKey, AesIV)
data = data reqData_encrypted = aes.encrypt(data)
data_enc = aes.encrypt(data) reqData_deflated = zlib.compress(reqData_encrypted)
data_def = zlib.compress(data_enc)
requests.packages.urllib3.disable_warnings()
endpoint = "https://maimai-gm.wahlap.com:42081/Maimai2Servlet/" endpoint = "https://maimai-gm.wahlap.com:42081/Maimai2Servlet/"
if not noLog: if not noLog:
logger.debug("TitleServer Request Start: "+ str(useApi)+" , Data: "+str(data)) logger.debug(f"开始请求 {targetApi},以 {data}")
retries = 0 retries = 0
while retries < maxRetries: while retries < maxRetries:
try: try:
# 发送请求 response = httpx.post(
responseRaw = requests.post(endpoint + SDGBApiHash(useApi), headers={ url=endpoint + getSDGBApiHash(targetApi),
"User-Agent": f"{SDGBApiHash(useApi)}#{agentExtra}", headers={
"Content-Type": "application/json", "User-Agent": f"{getSDGBApiHash(targetApi)}#{agentExtra}",
"Mai-Encoding": "1.40", "Content-Type": "application/json",
"Accept-Encoding": "", "Mai-Encoding": "1.40",
"Charset": "UTF-8", "Accept-Encoding": "",
"Content-Encoding": "deflate", "Charset": "UTF-8",
"Expect": "100-continue" "Content-Encoding": "deflate",
}, data=data_def, verify=False) "Expect": "100-continue"
logger.debug("TitleServer Request Sent.") },
content=reqData_deflated,
logger.debug("TitleServer Response Code: " + str(responseRaw.status_code)) verify=False,
# 如果是 404 或 500直接抛出异常不再继续重试 timeout=timeout
match responseRaw.status_code: )
case 200:
logger.debug("Request 200 OK!") logger.info(f"{targetApi} 请求结果: {response.status_code}")
case 404:
logger.error(f"Request 404! ") if response.status_code == 200:
raise NotImplementedError logger.debug("200 OK!")
case 500: else:
logger.error(f"Request Failed! 500!!!! ") errorMessage = f"请求失败: {response.status_code}"
raise Request500Error logger.error(errorMessage)
case _: raise SDGBRequestError(errorMessage)
logger.error(f"Request Failed! {responseRaw.status_code}")
raise NotImplementedError responseRAWContent = response.content
responseContent = responseRaw.content
# 尝试解压请求
try: try:
responseDecompressed = zlib.decompress(responseContent) responseDecompressed = zlib.decompress(responseRAWContent)
logger.debug("Successfully decompressed response.") logger.debug("成功解压响应!")
except zlib.error as e: except zlib.error:
logger.warning(f"RAW Response: {responseContent}") logger.warning(f"无法解压,得到的原始响应: {responseRAWContent}")
logger.warning(f"Wahlap Server Boomed! Will now retry.{e}") raise SDGBResponseError("Decompression failed")
retries += 1
time.sleep(4) # 休眠4秒后重试
continue
# 解压成功,解密请求并返回
resultResponse = unpad(aes.decrypt(responseDecompressed), 16).decode() resultResponse = unpad(aes.decrypt(responseDecompressed), 16).decode()
logger.info("TitleServer:" + useApi + " Response: " + str(responseRaw.status_code))
if not noLog: if not noLog:
logger.debug("TitleServer Response: " + str(resultResponse)) logger.debug(f"响应: {resultResponse}")
return resultResponse return resultResponse
# 除了 404 和 500 之外的错误重试 # 异常处理
except Request500Error: except SDGBRequestError as e:
raise Request500Error("500请求格式错误") # 请求格式错误,不需要重试
except NotImplementedError: raise SDGBRequestError("请求格式错误")
raise NotImplementedError("请求未知错误") except SDGBResponseError as e:
# 响应解析错误,重试但是只一次
logger.warning(f"Will now retry. {e}")
retries += 2
time.sleep(2)
except Exception as e: except Exception as e:
logger.warning(f"Request Failed! Will now retry.. {e}") # 其他错误,重试
logger.warning(f"Will now retry. {e}")
retries += 1 retries += 1
time.sleep(3) time.sleep(3)
else:
# 重试次数用尽WahlapServerBoomedError raise SDGBApiError("Multiple retries failed to make a successful request")
raise WahlapServerBoomedError("重试多次仍然不能成功请求")
def calcSpecialNumber(): def calcSpecialNumber():
"""使用 c_int32 实现的 SpecialNumber 算法""" """使用 c_int32 实现的 SpecialNumber 算法"""
@ -157,7 +151,6 @@ def calcSpecialNumber():
num2 >>= 1 num2 >>= 1
return c_int32(result.value).value return c_int32(result.value).value
def calcSpecialNumber2(): def calcSpecialNumber2():
"""实验性替代 SpecialNumber 算法""" """实验性替代 SpecialNumber 算法"""
max = 1037933 max = 1037933

View File

@ -12,7 +12,6 @@ from HelperFullPlay import implFullPlayAction
class NoSelectedBonusError(Exception): class NoSelectedBonusError(Exception):
pass pass
def apiQueryLoginBonus(userId:int) -> str: def apiQueryLoginBonus(userId:int) -> str:
"""ログインボーナスを取得する API""" """ログインボーナスを取得する API"""
data = json.dumps({ data = json.dumps({

View File

@ -102,12 +102,12 @@ def isVaildFishToken(importToken:str):
def implUserMusicToDivingFish(userId:int, fishImportToken:str): def implUserMusicToDivingFish(userId:int, fishImportToken:str):
'''上传所有成绩到水鱼的参考实现''' '''上传所有成绩到水鱼的参考实现'''
logger.info("Start to upload user music detail to DivingFish") logger.info("开始上传舞萌成绩到水鱼查分器!")
userFullMusicDetailList = getUserFullMusicDetail(userId) userFullMusicDetailList = getUserFullMusicDetail(userId)
logger.info("Got UserData, Convert to Fish Format") logger.info("成功得到成绩!转换成水鱼格式..")
divingFishData = maimaiUserMusicDetailToDivingFishFormat(userFullMusicDetailList) divingFishData = maimaiUserMusicDetailToDivingFishFormat(userFullMusicDetailList)
logger.info("Convert OK. Start to Update Fish Records") logger.info("转换成功!开始上传水鱼..")
updateFishRecords(fishImportToken, divingFishData) return updateFishRecords(fishImportToken, divingFishData)
if __name__ == '__main__': if __name__ == '__main__':
if True: if True:

View File

@ -60,3 +60,10 @@ def implBuyTicket(userId:int, ticketType:int):
getTicketResponseStr = apiBuyTicket(userId, ticketType, ticketType-1, playerRating, playCount) getTicketResponseStr = apiBuyTicket(userId, ticketType, ticketType-1, playerRating, playCount)
# 返回结果 # 返回结果
return getTicketResponseStr return getTicketResponseStr
if __name__ == "__main__":
userId = testUid2
ticketType = 3
print(implBuyTicket(userId, ticketType))
print(apiQueryTicket(userId))

View File

@ -2,7 +2,7 @@ import json
from loguru import logger from loguru import logger
from Config import * from Config import *
from API_TitleServer import apiSDGB, calcSpecialNumber, WahlapServerBoomedError, Request500Error from API_TitleServer import *
from HelperGetUserThing import implGetUser_ from HelperGetUserThing import implGetUser_
from HelperUploadUserPlayLog import apiUploadUserPlaylog from HelperUploadUserPlayLog import apiUploadUserPlaylog
from HelperUserAll import generateFullUserAll from HelperUserAll import generateFullUserAll
@ -80,16 +80,16 @@ def implFullPlayAction(userId: int, currentLoginTimestamp:int, currentLoginResul
# 开始上传 UserAll # 开始上传 UserAll
try: try:
currentUserAllResult = json.loads(apiSDGB(data, "UpsertUserAllApi", userId)) currentUserAllResult = json.loads(apiSDGB(data, "UpsertUserAllApi", userId))
except Request500Error: except SDGBRequestError:
logger.warning("上传 UserAll 出现 500. 重建数据.") logger.warning("上传 UserAll 出现 500. 重建数据.")
retries += 1 retries += 1
continue continue
except Exception: except Exception:
raise WahlapServerBoomedError("邪门错误") raise SDGBApiError("邪门错误")
# 成功上传后退出循环 # 成功上传后退出循环
break break
else: # 重试次数超过3次 else: # 重试次数超过3次
raise Request500Error("多次尝试后仍无法成功上传 UserAll") raise SDGBRequestError
logger.info("上机:结果:"+ str(currentUserAllResult)) logger.info("上机:结果:"+ str(currentUserAllResult))
return currentUserAllResult return currentUserAllResult

View File

@ -5,7 +5,7 @@ from API_TitleServer import apiSDGB
def apiGetUserData(userId:int) -> str: def apiGetUserData(userId:int) -> str:
"""已弃用,将逐步淘汰""" """已弃用,将逐步淘汰"""
logger.info("apiGetUserData 已弃用,将逐步淘汰。") #logger.info("apiGetUserData 已弃用,将逐步淘汰。")
# 构建 Payload # 构建 Payload
data = json.dumps({ data = json.dumps({
"userId": userId "userId": userId

View File

@ -2,6 +2,8 @@
# 适用于舞萌DX 2024 # 适用于舞萌DX 2024
# 理论可用于 HDD 登号等(这种情况下自行修改 hosts # 理论可用于 HDD 登号等(这种情况下自行修改 hosts
# SGWCMAID111111111111AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
## 配置 ## 配置
# 0 返回本地生成的假结果 # 0 返回本地生成的假结果
# 1 原样返回官方服务器的结果 # 1 原样返回官方服务器的结果

35
_Special.py Normal file
View File

@ -0,0 +1,35 @@
# 纯纯测试用
from loguru import logger
from Config import *
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
from HelperFullPlay import implFullPlayAction, generateMusicData
def implChangeVersionNumber(userId: int, currentLoginTimestamp:int, currentLoginResult, dataVersion="1.40.09", romVersion="1.41.00") -> str:
musicData = generateMusicData()
userAllPatches = {
"upsertUserAll": {
"userData": [{
"playerRating": 114514,
}],
"userMusicDetailList": [musicData],
"isNewMusicDetailList": "1" #1避免覆盖
}}
logger.info("Changing version number to " + dataVersion + " and " + romVersion)
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
return result
if __name__ == "__main__":
userId = testUid
currentLoginTimestamp = generateTimestamp()
loginResult = apiLogin(currentLoginTimestamp, userId)
if loginResult['returnCode'] != 1:
logger.info("登录失败")
exit()
try:
logger.info(implChangeVersionNumber(userId, currentLoginTimestamp, loginResult, "1.00.00", "1.00.00"))
logger.info(apiLogout(currentLoginTimestamp, userId))
finally:
logger.info(apiLogout(currentLoginTimestamp, userId))
#logger.warning("Error")