Compare commits
13 Commits
8582172187
...
89e096bac6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89e096bac6 | ||
|
|
044c23ff71 | ||
|
|
6d234bc2b3 | ||
|
|
47dcc2e045 | ||
|
|
41a2ad5ae1 | ||
|
|
c304c24863 | ||
|
|
cb599c5f35 | ||
|
|
ae6a4d1cf4 | ||
|
|
9a2e8bd1ed | ||
|
|
12093c9b9b | ||
|
|
e99f04c416 | ||
|
|
4fad3aba71 | ||
|
|
52c9e205e5 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -175,3 +175,5 @@ cython_debug/
|
||||
.pypirc
|
||||
Private_Static_Settings.py
|
||||
Private_Static_Settings.py
|
||||
|
||||
/.python-version
|
||||
|
||||
@@ -8,25 +8,36 @@ import requests
|
||||
import json
|
||||
import re
|
||||
from loguru import logger
|
||||
|
||||
# 常量
|
||||
CHIP_ID = "A63E-01E68606624"
|
||||
COMMON_KEY = "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"
|
||||
API_URL = "http://ai.sys-allnet.cn/wc_aime/api/get_data"
|
||||
|
||||
|
||||
# 计算 SHA256
|
||||
def getSHA256(input_str):
|
||||
"""SHA256计算"""
|
||||
return hashlib.sha256(input_str.encode('utf-8')).hexdigest().upper()
|
||||
return hashlib.sha256(input_str.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
|
||||
# 生成时间戳
|
||||
def generateSEGATimestamp():
|
||||
"""SEGA格式的 YYMMDDHHMMSS 时间戳(sb玩意)"""
|
||||
return time.strftime("%y%m%d%H%M%S", time.localtime())
|
||||
|
||||
|
||||
# 计算认证 key
|
||||
def calcSEGAAimeDBAuthKey(varString:str, timestamp:str, commonKey:str="XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW") -> str:
|
||||
def calcSEGAAimeDBAuthKey(
|
||||
varString: str, timestamp: str, commonKey: str = "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"
|
||||
) -> str:
|
||||
"""计算 SEGA AimeDB 的认证 key"""
|
||||
return hashlib.sha256((varString + timestamp + commonKey).encode("utf-8")).hexdigest().upper()
|
||||
return (
|
||||
hashlib.sha256((varString + timestamp + commonKey).encode("utf-8"))
|
||||
.hexdigest()
|
||||
.upper()
|
||||
)
|
||||
|
||||
|
||||
def apiAimeDB(qrCode):
|
||||
"""AimeDB 扫码 API 实现"""
|
||||
@@ -42,11 +53,11 @@ def apiAimeDB(qrCode):
|
||||
"openGameID": "MAID",
|
||||
"key": currentKey,
|
||||
"qrCode": qrCode,
|
||||
"timestamp": timestamp
|
||||
"timestamp": timestamp,
|
||||
}
|
||||
|
||||
# 输出准备好的请求数据
|
||||
print("Payload:", json.dumps(payload, separators=(',', ':')))
|
||||
print("Payload:", json.dumps(payload, separators=(",", ":")))
|
||||
|
||||
# 发送 POST 请求
|
||||
headers = {
|
||||
@@ -55,7 +66,9 @@ def apiAimeDB(qrCode):
|
||||
"User-Agent": "WC_AIME_LIB",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.post(API_URL, data=json.dumps(payload, separators=(',', ':')), headers=headers)
|
||||
response = requests.post(
|
||||
API_URL, data=json.dumps(payload, separators=(",", ":")), headers=headers
|
||||
)
|
||||
|
||||
# 返回服务器的响应
|
||||
return response
|
||||
@@ -64,16 +77,16 @@ def apiAimeDB(qrCode):
|
||||
def isSGWCFormat(input_string: str) -> bool:
|
||||
"""简单检查二维码字符串是否符合格式"""
|
||||
if (
|
||||
len(input_string) != 84 #长度
|
||||
or not input_string.startswith("SGWCMAID") #识别字
|
||||
or re.match("^[0-9A-F]+$", input_string[20:]) is None #有效字符
|
||||
len(input_string) != 84 # 长度
|
||||
or not input_string.startswith("SGWCMAID") # 识别字
|
||||
or re.match("^[0-9A-F]+$", input_string[20:]) is None # 有效字符
|
||||
):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def implAimeDB(qrCode:str, isAlreadyFinal:bool=False) -> str:
|
||||
|
||||
def implAimeDB(qrCode: str, isAlreadyFinal: bool = False) -> str:
|
||||
"""
|
||||
Aime DB 的请求的参考实现。
|
||||
提供完整 QRCode 内容,返回响应的字符串(Json格式)
|
||||
@@ -93,7 +106,7 @@ def implAimeDB(qrCode:str, isAlreadyFinal:bool=False) -> str:
|
||||
return response.text
|
||||
|
||||
|
||||
def implGetUID(qr_content:str) -> dict:
|
||||
def implGetUID(qr_content: str) -> dict:
|
||||
"""
|
||||
包装后的 UID 扫码器实现。
|
||||
此函数会返回 AimeDB 传回的 Json 转成 Python 字典的结果。
|
||||
@@ -101,18 +114,19 @@ def implGetUID(qr_content:str) -> dict:
|
||||
"""
|
||||
# 检查格式
|
||||
if not isSGWCFormat(qr_content):
|
||||
return {'errorID': 60001} # 二维码内容明显无效
|
||||
|
||||
return {"errorID": 60001} # 二维码内容明显无效
|
||||
|
||||
# 发送请求并处理响应
|
||||
try:
|
||||
result = json.loads(implAimeDB(qr_content))
|
||||
logger.info(f"QRScan Got Response {result}")
|
||||
except:
|
||||
return {'errorID': 60002} # 无法解码 Response 的内容
|
||||
|
||||
except Exception:
|
||||
return {"errorID": 60002} # 无法解码 Response 的内容
|
||||
|
||||
# 返回结果
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
userInputQR = input("QRCode: ")
|
||||
print(implAimeDB(userInputQR))
|
||||
|
||||
@@ -7,120 +7,132 @@ from loguru import logger
|
||||
from urllib.parse import parse_qs
|
||||
import configparser as ini
|
||||
|
||||
LITE_AUTH_KEY = bytes([47, 63, 106, 111, 43, 34, 76, 38, 92, 67, 114, 57, 40, 61, 107, 71])
|
||||
LITE_AUTH_IV = bytes.fromhex('00000000000000000000000000000000')
|
||||
LITE_AUTH_KEY = bytes(
|
||||
[47, 63, 106, 111, 43, 34, 76, 38, 92, 67, 114, 57, 40, 61, 107, 71]
|
||||
)
|
||||
LITE_AUTH_IV = bytes.fromhex("00000000000000000000000000000000")
|
||||
|
||||
|
||||
def auth_lite_encrypt(plaintext: str) -> bytes:
|
||||
# 构造数据:16字节头 + 16字节0前缀 + 明文
|
||||
header = bytes(16)
|
||||
content = bytes(16) + plaintext.encode('utf-8')
|
||||
content = bytes(16) + plaintext.encode("utf-8")
|
||||
data = header + content
|
||||
# 填充并加密
|
||||
padded_data = pad(data, AES.block_size)
|
||||
cipher = AES.new(LITE_AUTH_KEY, AES.MODE_CBC, LITE_AUTH_IV)
|
||||
return cipher.encrypt(padded_data)
|
||||
|
||||
|
||||
def auth_lite_decrypt(ciphertext: bytes) -> str:
|
||||
# 解密并去除填充
|
||||
cipher = AES.new(LITE_AUTH_KEY, AES.MODE_CBC, LITE_AUTH_IV)
|
||||
decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size)
|
||||
# 提取内容并解码
|
||||
content = decrypted_data[16:] # 去除头部的16字节
|
||||
return content.decode('utf-8').strip()
|
||||
return content.decode("utf-8").strip()
|
||||
|
||||
def getRawDelivery(title_ver:str="1.51"):
|
||||
encrypted = auth_lite_encrypt(f'title_id=SDGB&title_ver={title_ver}&client_id=A63E01C2805')
|
||||
|
||||
def getRawDelivery(title_ver: str = "1.51"):
|
||||
encrypted = auth_lite_encrypt(
|
||||
f"title_id=SDGB&title_ver={title_ver}&client_id=A63E01C2805"
|
||||
)
|
||||
r = httpx.post(
|
||||
'http://at.sys-allnet.cn/net/delivery/instruction',
|
||||
data = encrypted,
|
||||
headers = {
|
||||
'User-Agent': "SDGB;Windows/Lite",
|
||||
'Pragma': 'DFI'
|
||||
}
|
||||
"http://at.sys-allnet.cn/net/delivery/instruction",
|
||||
data=encrypted,
|
||||
headers={"User-Agent": "SDGB;Windows/Lite", "Pragma": "DFI"},
|
||||
)
|
||||
resp_data = r.content
|
||||
decrypted_str = auth_lite_decrypt(resp_data)
|
||||
# 过滤所有控制字符
|
||||
decrypted_str = ''.join([i for i in decrypted_str if 31 < ord(i) < 127])
|
||||
decrypted_str = "".join([i for i in decrypted_str if 31 < ord(i) < 127])
|
||||
logger.info(f"RAW Response: {decrypted_str}")
|
||||
|
||||
|
||||
return decrypted_str
|
||||
|
||||
|
||||
def parseRawDelivery(deliveryStr):
|
||||
"""解析 RAW 的 Delivery 字符串,返回其中的有效的 instruction URL 的列表"""
|
||||
parsedResponseDict = {key: value[0] for key, value in parse_qs(deliveryStr).items()}
|
||||
urlList = parsedResponseDict['uri'].split('|')
|
||||
parsedResponseDict: dict[str, str] = {
|
||||
key: value[0] for key, value in parse_qs(deliveryStr).items()
|
||||
}
|
||||
urlList = parsedResponseDict["uri"].split("|")
|
||||
# 过滤掉空字符串和内容为 null 的情况
|
||||
urlList = [url for url in urlList if url and url != 'null']
|
||||
urlList = [url for url in urlList if url and url != "null"]
|
||||
logger.info(f"Parsed URL List: {urlList}")
|
||||
validURLs = []
|
||||
for url in urlList:
|
||||
# 检查是否是 HTTPS 的 URL,以及是否是 txt 文件,否则忽略
|
||||
if not url.startswith('https://') or not url.endswith('.txt'):
|
||||
if not url.startswith("https://") or not url.endswith(".txt"):
|
||||
logger.warning(f"Invalid URL will be ignored: {url}")
|
||||
continue
|
||||
validURLs.append(url)
|
||||
logger.info(f"Verified Valid URLs: {validURLs}")
|
||||
return validURLs
|
||||
|
||||
|
||||
def getUpdateIniFromURL(url):
|
||||
# 发送请求
|
||||
response = httpx.get(url, headers={
|
||||
'User-Agent': 'SDGB;Windows/Lite',
|
||||
'Pragma': 'DFI'
|
||||
})
|
||||
response = httpx.get(
|
||||
url, headers={"User-Agent": "SDGB;Windows/Lite", "Pragma": "DFI"}
|
||||
)
|
||||
logger.info(f"成功自 {url} 获取更新信息")
|
||||
return response.text
|
||||
|
||||
|
||||
def parseUpdateIni(iniText):
|
||||
# 解析配置
|
||||
config = ini.ConfigParser(allow_no_value=True)
|
||||
config.read_string(iniText)
|
||||
logger.info(f"成功解析配置文件,包含的节有:{config.sections()}")
|
||||
|
||||
|
||||
# 获取 COMMON 节的配置
|
||||
common = config['COMMON']
|
||||
|
||||
common = config["COMMON"]
|
||||
|
||||
# 初始化消息列表
|
||||
message = []
|
||||
|
||||
|
||||
# 获取游戏描述并去除引号
|
||||
game_desc = common['GAME_DESC'].strip('"')
|
||||
|
||||
game_desc = common["GAME_DESC"].strip('"')
|
||||
|
||||
# 根据前缀选择消息模板和图标
|
||||
prefix_icons = {
|
||||
'PATCH': ('💾 游戏程序更新 (.app) ', 'PATCH_'),
|
||||
'OPTION': ('📚 游戏内容更新 (.opt) ', 'OPTION_')
|
||||
"PATCH": ("💾 游戏程序更新 (.app) ", "PATCH_"),
|
||||
"OPTION": ("📚 游戏内容更新 (.opt) ", "OPTION_"),
|
||||
}
|
||||
icon, prefix = prefix_icons.get(game_desc.split('_')[0], ('📦 游戏更新 ', ''))
|
||||
|
||||
icon, prefix = prefix_icons.get(game_desc.split("_")[0], ("📦 游戏更新 ", ""))
|
||||
|
||||
# 构建消息标题
|
||||
game_title = game_desc.replace(prefix, '', 1)
|
||||
game_title = game_desc.replace(prefix, "", 1)
|
||||
message.append(f"{icon}{game_title}")
|
||||
|
||||
|
||||
# 添加可选文件的下载链接(如果有)
|
||||
if 'OPTIONAL' in config:
|
||||
if "OPTIONAL" in config:
|
||||
message.append("往期更新包:")
|
||||
optional_files = [f"{url.split('/')[-1]} {url}" for _, url in config.items('OPTIONAL')]
|
||||
optional_files = [
|
||||
f"{url.split('/')[-1]} {url}" for _, url in config.items("OPTIONAL")
|
||||
]
|
||||
message.extend(optional_files)
|
||||
|
||||
|
||||
# 添加主文件的下载链接
|
||||
main_file = common['INSTALL1']
|
||||
main_file_name = main_file.split('/')[-1]
|
||||
main_file = common["INSTALL1"]
|
||||
main_file_name = main_file.split("/")[-1]
|
||||
message.append(f"此次更新包: \n{main_file_name} {main_file}")
|
||||
|
||||
# 添加发布时间信息
|
||||
release_time = common['RELEASE_TIME'].replace('T', ' ')
|
||||
release_time = common["RELEASE_TIME"].replace("T", " ")
|
||||
message.append(f"正式发布时间:{release_time}。\n")
|
||||
|
||||
|
||||
# 构建最终的消息字符串
|
||||
final_message = '\n'.join(message)
|
||||
final_message = "\n".join(message)
|
||||
logger.info(f"消息构建完成,最终的消息为:\n{final_message}")
|
||||
|
||||
|
||||
return final_message
|
||||
|
||||
if __name__ == '__main__':
|
||||
urlList = parseRawDelivery(getRawDelivery("1.51"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
raw = getRawDelivery("1.51")
|
||||
urlList = parseRawDelivery(raw)
|
||||
for url in urlList:
|
||||
iniText = getUpdateIniFromURL(url)
|
||||
message = parseUpdateIni(iniText)
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
# 舞萌DX
|
||||
# 标题服务器通讯实现
|
||||
|
||||
import zlib
|
||||
import hashlib
|
||||
import httpx
|
||||
from loguru import logger
|
||||
import random
|
||||
import time
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
from ctypes import c_int32
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from Config import *
|
||||
from typing import Optional
|
||||
import certifi
|
||||
|
||||
use2024Api = False # 是否使用 2024 API
|
||||
from Config import (
|
||||
useProxy,
|
||||
proxyUrl,
|
||||
)
|
||||
|
||||
|
||||
AesKey = "a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R"
|
||||
AesIV = "d6xHIKq]1J]Dt^ue"
|
||||
ObfuscateParam = "B44df8yT"
|
||||
|
||||
|
||||
def use2024Api():
|
||||
global AesKey, AesIV, ObfuscateParam
|
||||
|
||||
if use2024Api:
|
||||
AesKey = "n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@"
|
||||
AesIV = ";;KjR1C3hgB1ovXa"
|
||||
ObfuscateParam = "BEs2D5vW"
|
||||
else:
|
||||
AesKey = "a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R"
|
||||
AesIV = "d6xHIKq]1J]Dt^ue"
|
||||
ObfuscateParam = "B44df8yT"
|
||||
|
||||
|
||||
class SDGBApiError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class SDGBRequestError(SDGBApiError):
|
||||
pass
|
||||
|
||||
|
||||
class SDGBResponseError(SDGBApiError):
|
||||
pass
|
||||
|
||||
|
||||
class aes_pkcs7(object):
|
||||
def __init__(self, key: str, iv: str):
|
||||
self.key = key.encode('utf-8')
|
||||
self.iv = iv.encode('utf-8')
|
||||
self.key = key.encode("utf-8")
|
||||
self.iv = iv.encode("utf-8")
|
||||
self.mode = AES.MODE_CBC
|
||||
|
||||
def encrypt(self, content: bytes) -> bytes:
|
||||
@@ -55,21 +63,23 @@ class aes_pkcs7(object):
|
||||
def pkcs7unpadding(self, text):
|
||||
length = len(text)
|
||||
unpadding = ord(text[length - 1])
|
||||
return text[0:length - unpadding]
|
||||
return text[0 : length - unpadding]
|
||||
|
||||
def pkcs7padding(self, text):
|
||||
bs = 16
|
||||
length = len(text)
|
||||
bytes_length = len(text.encode('utf-8'))
|
||||
bytes_length = len(text.encode("utf-8"))
|
||||
padding_size = length if (bytes_length == length) else bytes_length
|
||||
padding = bs - padding_size % bs
|
||||
padding_text = chr(padding) * padding
|
||||
return text + padding_text
|
||||
|
||||
|
||||
def getSDGBApiHash(api):
|
||||
# API 的 Hash 的生成
|
||||
# 有空做一下 Hash 的彩虹表?
|
||||
return hashlib.md5((api+"MaimaiChn"+ObfuscateParam).encode()).hexdigest()
|
||||
return hashlib.md5((api + "MaimaiChn" + ObfuscateParam).encode()).hexdigest()
|
||||
|
||||
|
||||
def apiSDGB(
|
||||
data: str,
|
||||
@@ -94,7 +104,7 @@ def apiSDGB(
|
||||
endpoint = "https://maimai-gm.wahlap.com:42081/Maimai2Servlet/"
|
||||
|
||||
# 准备好请求数据
|
||||
requestDataFinal = aes.encrypt(zlib.compress(data.encode('utf-8')))
|
||||
requestDataFinal = aes.encrypt(zlib.compress(data.encode("utf-8")))
|
||||
|
||||
if not noLog:
|
||||
logger.debug(f"[Stage 1] 准备开始请求 {targetApi},以 {data}")
|
||||
@@ -108,21 +118,25 @@ def apiSDGB(
|
||||
httpClient = httpx.Client(proxy=proxyUrl, verify=False)
|
||||
else:
|
||||
httpClient = httpx.Client(verify=False)
|
||||
|
||||
|
||||
api_hash = getSDGBApiHash(targetApi)
|
||||
|
||||
logger.info(f"hash: {api_hash}")
|
||||
|
||||
# 发送请求
|
||||
response = httpClient.post(
|
||||
url=endpoint + getSDGBApiHash(targetApi),
|
||||
url=endpoint + api_hash,
|
||||
headers={
|
||||
"User-Agent": f"{getSDGBApiHash(targetApi)}#{agentExtra}",
|
||||
"User-Agent": f"{api_hash}#{agentExtra}",
|
||||
"Content-Type": "application/json",
|
||||
"Mai-Encoding": "1.50",
|
||||
"Accept-Encoding": "",
|
||||
"Charset": "UTF-8",
|
||||
"Content-Encoding": "deflate",
|
||||
"Expect": "100-continue"
|
||||
"Expect": "100-continue",
|
||||
},
|
||||
content=requestDataFinal, #数据
|
||||
timeout=timeout
|
||||
content=requestDataFinal, # 数据
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
if not noLog:
|
||||
@@ -142,30 +156,42 @@ def apiSDGB(
|
||||
if not noLog:
|
||||
logger.debug("[Stage 3] Decryption SUCCESS.")
|
||||
except Exception as e:
|
||||
logger.warning(f"[Stage 3] Decryption FAILED. Raw Content: {responseContentRaw}, Error: {e}")
|
||||
logger.warning(
|
||||
f"[Stage 3] Decryption FAILED. Raw Content: {responseContentRaw}, Error: {e}"
|
||||
)
|
||||
raise SDGBResponseError("Decryption failed")
|
||||
# 然后尝试解压
|
||||
try:
|
||||
# 看看文件头是否是压缩过的
|
||||
if responseContentDecrypted.startswith(b'\x78\x9c'):
|
||||
if responseContentDecrypted.startswith(b"\x78\x9c"):
|
||||
logger.debug("[Stage 4] Zlib detected, decompressing...")
|
||||
responseContentFinal = zlib.decompress(responseContentDecrypted).decode('utf-8')
|
||||
responseContentFinal = zlib.decompress(
|
||||
responseContentDecrypted
|
||||
).decode("utf-8")
|
||||
else:
|
||||
logger.warning(f"[Stage 4] Not Zlib Format!! using raw content: {responseContentDecrypted}")
|
||||
responseContentFinal = responseContentDecrypted.decode('utf-8')
|
||||
logger.warning(
|
||||
f"[Stage 4] Not Zlib Format!! using raw content: {responseContentDecrypted}"
|
||||
)
|
||||
responseContentFinal = responseContentDecrypted.decode("utf-8")
|
||||
# 完成解压
|
||||
if not noLog:
|
||||
logger.debug(f"[Stage 4] Process OK, Content: {responseContentFinal}")
|
||||
logger.debug(
|
||||
f"[Stage 4] Process OK, Content: {responseContentFinal}"
|
||||
)
|
||||
# 最终处理,检查是否是 JSON 格式
|
||||
if responseContentFinal.startswith('{') and responseContentFinal.endswith('}'):
|
||||
if responseContentFinal.startswith(
|
||||
"{"
|
||||
) and responseContentFinal.endswith("}"):
|
||||
# 如果是 JSON 格式,直接返回
|
||||
logger.debug("[Stage 5] Response is JSON, returning.")
|
||||
return responseContentFinal
|
||||
else:
|
||||
# 如果不是 JSON 格式,直接返回但是警告
|
||||
logger.warning("[Stage 5] Response is not JSON, returning as is, take care!")
|
||||
logger.warning(
|
||||
"[Stage 5] Response is not JSON, returning as is, take care!"
|
||||
)
|
||||
return responseContentFinal
|
||||
except:
|
||||
except Exception:
|
||||
logger.warning(f"解压失败,原始响应: {responseContentDecrypted}")
|
||||
raise SDGBResponseError("解压失败")
|
||||
except SDGBRequestError as e:
|
||||
@@ -181,16 +207,17 @@ def apiSDGB(
|
||||
time.sleep(2)
|
||||
|
||||
finally:
|
||||
if 'httpClient' in locals():
|
||||
if "httpClient" in locals():
|
||||
httpClient.close()
|
||||
|
||||
raise SDGBApiError("重试多次仍然无法成功请求服务器")
|
||||
|
||||
|
||||
def calcPlaySpecial():
|
||||
"""使用 c_int32 实现的 SpecialNumber 算法"""
|
||||
rng = random.SystemRandom()
|
||||
num2 = rng.randint(1, 1037933) * 2069
|
||||
num2 += 1024 #GameManager.CalcSpecialNum()
|
||||
num2 += 1024 # GameManager.CalcSpecialNum()
|
||||
num2 = c_int32(num2).value
|
||||
result = c_int32(0)
|
||||
for _ in range(32):
|
||||
@@ -199,21 +226,24 @@ def calcPlaySpecial():
|
||||
num2 >>= 1
|
||||
return c_int32(result.value).value
|
||||
|
||||
|
||||
class AESPKCS7_2024:
|
||||
# 实现了 maimai 通讯所用的 AES 加密的类
|
||||
def __init__(self, key: str, iv: str):
|
||||
self.key = key.encode('utf-8')
|
||||
self.iv = iv.encode('utf-8')
|
||||
self.key = key.encode("utf-8")
|
||||
self.iv = iv.encode("utf-8")
|
||||
self.mode = AES.MODE_CBC
|
||||
|
||||
# 加密
|
||||
def encrypt(self, content) -> bytes:
|
||||
# if content is str, convert to bytes
|
||||
if isinstance(content, str):
|
||||
encodedData = content.encode('utf-8')
|
||||
encodedData = content.encode("utf-8")
|
||||
cipher = AES.new(self.key, self.mode, self.iv)
|
||||
content_padded = pad(encodedData, AES.block_size)
|
||||
encrypted_bytes = cipher.encrypt(content_padded)
|
||||
return encrypted_bytes
|
||||
|
||||
# 解密
|
||||
def decrypt(self, encrypted_content: bytes) -> str:
|
||||
cipher = AES.new(self.key, self.mode, self.iv)
|
||||
@@ -221,7 +251,14 @@ class AESPKCS7_2024:
|
||||
decrypted = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted
|
||||
|
||||
def apiSDGB_2024(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=False, timeout:int=5):
|
||||
|
||||
def apiSDGB_2024(
|
||||
data: str,
|
||||
targetApi: str,
|
||||
userAgentExtraData: str,
|
||||
noLog: bool = False,
|
||||
timeout: int = 5,
|
||||
):
|
||||
"""
|
||||
舞萌DX 2024 API 通讯用函数
|
||||
:param data: 请求数据
|
||||
@@ -258,13 +295,13 @@ def apiSDGB_2024(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=Fal
|
||||
"Accept-Encoding": "",
|
||||
"Charset": "UTF-8",
|
||||
"Content-Encoding": "deflate",
|
||||
"Expect": "100-continue"
|
||||
"Expect": "100-continue",
|
||||
},
|
||||
content=reqData_deflated,
|
||||
# 经测试,加 Verify 之后速度慢好多,因此建议选择性开
|
||||
#verify=certifi.where(),
|
||||
#verify=False,
|
||||
timeout=timeout
|
||||
# verify=certifi.where(),
|
||||
# verify=False,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
if not noLog:
|
||||
@@ -282,22 +319,22 @@ def apiSDGB_2024(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=Fal
|
||||
try:
|
||||
responseDecompressed = zlib.decompress(responseRAWContent)
|
||||
logger.debug("成功解压响应!")
|
||||
except:
|
||||
except Exception:
|
||||
logger.warning(f"无法解压,得到的原始响应: {responseRAWContent}")
|
||||
raise SDGBResponseError("解压失败")
|
||||
try:
|
||||
resultResponse = aes.decrypt(responseDecompressed)
|
||||
logger.debug(f"成功解密响应!")
|
||||
except:
|
||||
logger.debug("成功解密响应!")
|
||||
except Exception:
|
||||
logger.warning(f"解密失败,得到的原始响应: {responseDecompressed}")
|
||||
raise SDGBResponseError("解密失败")
|
||||
|
||||
|
||||
if not noLog:
|
||||
logger.debug(f"响应: {resultResponse}")
|
||||
return resultResponse
|
||||
|
||||
|
||||
# 异常处理
|
||||
except SDGBRequestError as e:
|
||||
except SDGBRequestError:
|
||||
# 请求格式错误,不需要重试
|
||||
raise SDGBRequestError("请求格式错误")
|
||||
except SDGBResponseError as e:
|
||||
|
||||
@@ -1,45 +1,50 @@
|
||||
# 改变版本号,实现伪封号和解封号之类
|
||||
|
||||
from loguru import logger
|
||||
from Config import *
|
||||
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
from HelperFullPlay import implFullPlayAction, generateMusicData
|
||||
from HelperGetUserThing import implGetUser_
|
||||
from MyConfig import testUid8
|
||||
|
||||
def implWipeTickets(userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
|
||||
|
||||
def implWipeTickets(userId: int, currentLoginTimestamp: int, currentLoginResult) -> str:
|
||||
# Get User Charge
|
||||
currentUserCharge = implGetUser_("Charge", userId)
|
||||
|
||||
currentUserChargeList = currentUserCharge['userChargeList']
|
||||
currentUserChargeList = currentUserCharge["userChargeList"]
|
||||
# All stock set to 0
|
||||
for charge in currentUserChargeList:
|
||||
charge['stock'] = 0
|
||||
|
||||
# example format
|
||||
# {"userId":11088995,"length":16,"userChargeList":[{"chargeId":1,"stock":0,"purchaseDate":"2025-02-04 00:51:50","validDate":"2025-02-04 00:51:50","extNum1":0},{"chargeId":2,"stock":0,"purchaseDate":"2025-06-11 17:19:42","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":3,"stock":0,"purchaseDate":"2025-06-11 17:19:40","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":4,"stock":0,"purchaseDate":"2025-06-11 09:34:51","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":5,"stock":0,"purchaseDate":"2025-01-30 12:31:16","validDate":"2025-04-30 04:00:00","extNum1":0},{"chargeId":6,"stock":0,"purchaseDate":"2025-02-17 20:01:42","validDate":"2025-02-17 20:01:42","extNum1":0},{"chargeId":7,"stock":0,"purchaseDate":"2025-02-06 16:17:41","validDate":"2025-02-06 16:17:41","extNum1":0},{"chargeId":8,"stock":0,"purchaseDate":"2025-02-06 16:17:49","validDate":"2025-02-06 16:17:49","extNum1":0},{"chargeId":9,"stock":0,"purchaseDate":"2025-02-06 16:18:00","validDate":"2025-02-06 16:18:00","extNum1":0},{"chargeId":10001,"stock":1,"purchaseDate":"2025-06-11 17:19:51","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":10005,"stock":0,"purchaseDate":"2025-04-25 15:45:55","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":10105,"stock":0,"purchaseDate":"2025-04-25 15:46:00","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":10205,"stock":0,"purchaseDate":"2025-04-25 15:46:03","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":11001,"stock":0,"purchaseDate":"2025-01-08 20:43:05","validDate":"2025-04-08 04:00:00","extNum1":0},{"chargeId":30001,"stock":0,"purchaseDate":"2025-04-25 15:46:17","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":999999,"stock":0,"purchaseDate":"2025-02-06 23:03:14","validDate":"2025-02-06 23:03:14","extNum1":0}]}
|
||||
charge["stock"] = 0
|
||||
|
||||
# example format
|
||||
# {"userId":11088995,"length":16,"userChargeList":[{"chargeId":1,"stock":0,"purchaseDate":"2025-02-04 00:51:50","validDate":"2025-02-04 00:51:50","extNum1":0},{"chargeId":2,"stock":0,"purchaseDate":"2025-06-11 17:19:42","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":3,"stock":0,"purchaseDate":"2025-06-11 17:19:40","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":4,"stock":0,"purchaseDate":"2025-06-11 09:34:51","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":5,"stock":0,"purchaseDate":"2025-01-30 12:31:16","validDate":"2025-04-30 04:00:00","extNum1":0},{"chargeId":6,"stock":0,"purchaseDate":"2025-02-17 20:01:42","validDate":"2025-02-17 20:01:42","extNum1":0},{"chargeId":7,"stock":0,"purchaseDate":"2025-02-06 16:17:41","validDate":"2025-02-06 16:17:41","extNum1":0},{"chargeId":8,"stock":0,"purchaseDate":"2025-02-06 16:17:49","validDate":"2025-02-06 16:17:49","extNum1":0},{"chargeId":9,"stock":0,"purchaseDate":"2025-02-06 16:18:00","validDate":"2025-02-06 16:18:00","extNum1":0},{"chargeId":10001,"stock":1,"purchaseDate":"2025-06-11 17:19:51","validDate":"2025-09-09 04:00:00","extNum1":0},{"chargeId":10005,"stock":0,"purchaseDate":"2025-04-25 15:45:55","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":10105,"stock":0,"purchaseDate":"2025-04-25 15:46:00","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":10205,"stock":0,"purchaseDate":"2025-04-25 15:46:03","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":11001,"stock":0,"purchaseDate":"2025-01-08 20:43:05","validDate":"2025-04-08 04:00:00","extNum1":0},{"chargeId":30001,"stock":0,"purchaseDate":"2025-04-25 15:46:17","validDate":"2025-07-24 04:00:00","extNum1":0},{"chargeId":999999,"stock":0,"purchaseDate":"2025-02-06 23:03:14","validDate":"2025-02-06 23:03:14","extNum1":0}]}
|
||||
|
||||
musicData = generateMusicData()
|
||||
userAllPatches = {
|
||||
"upsertUserAll": {
|
||||
# "userData": [{
|
||||
# "lastRomVersion": romVersion,
|
||||
# "lastDataVersion": dataVersion
|
||||
# }],
|
||||
"userChargeList": currentUserChargeList,
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1" #1避免覆盖
|
||||
}}
|
||||
"upsertUserAll": {
|
||||
# "userData": [{
|
||||
# "lastRomVersion": romVersion,
|
||||
# "lastDataVersion": dataVersion
|
||||
# }],
|
||||
"userChargeList": currentUserChargeList,
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1", # 1避免覆盖
|
||||
}
|
||||
}
|
||||
|
||||
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
|
||||
result = implFullPlayAction(
|
||||
userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
userId = testUid2
|
||||
userId = testUid8
|
||||
currentLoginTimestamp = generateTimestamp()
|
||||
loginResult = apiLogin(currentLoginTimestamp, userId)
|
||||
|
||||
if loginResult['returnCode'] != 1:
|
||||
if loginResult["returnCode"] != 1:
|
||||
logger.info("登录失败")
|
||||
exit()
|
||||
try:
|
||||
@@ -47,4 +52,4 @@ if __name__ == "__main__":
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
finally:
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
#logger.warning("Error")
|
||||
# logger.warning("Error")
|
||||
|
||||
@@ -5,53 +5,59 @@ import rapidjson as json
|
||||
from loguru import logger
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from Config import *
|
||||
from Config import (
|
||||
loginBonusDBPath,
|
||||
loginBonusDBPathFallback,
|
||||
)
|
||||
from MyConfig import testUid
|
||||
from API_TitleServer import apiSDGB
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
from HelperFullPlay import implFullPlayAction, generateMusicData
|
||||
|
||||
|
||||
class NoSelectedBonusError(Exception):
|
||||
pass
|
||||
|
||||
def apiQueryLoginBonus(userId:int) -> str:
|
||||
|
||||
def apiQueryLoginBonus(userId: int) -> str:
|
||||
"""ログインボーナスを取得する API"""
|
||||
data = json.dumps({
|
||||
"userId": int(userId),
|
||||
"nextIndex": 0,
|
||||
"maxCount": 2000
|
||||
})
|
||||
data = json.dumps({"userId": int(userId), "nextIndex": 0, "maxCount": 2000})
|
||||
return apiSDGB(data, "GetUserLoginBonusApi", userId)
|
||||
|
||||
def implLoginBonus(userId: int, currentLoginTimestamp:int, currentLoginResult, bonusGenerateMode=1):
|
||||
|
||||
def implLoginBonus(
|
||||
userId: int, currentLoginTimestamp: int, currentLoginResult, bonusGenerateMode=1
|
||||
):
|
||||
"""
|
||||
ログインボーナスデータをアップロードする
|
||||
bonusGenerateMode は、ログインボーナスを生成する方法を指定します。
|
||||
1: 選択したボーナスのみ MAX にする(選択したボーナスはないの場合は False を返す)
|
||||
2: 全部 MAX にする
|
||||
"""
|
||||
musicData = generateMusicData()
|
||||
musicData = generateMusicData()
|
||||
# サーバーからログインボーナスデータを取得
|
||||
data = json.dumps({
|
||||
"userId": int(userId),
|
||||
"nextIndex": 0,
|
||||
"maxCount": 2000
|
||||
})
|
||||
data = json.dumps({"userId": int(userId), "nextIndex": 0, "maxCount": 2000})
|
||||
UserLoginBonusResponse = json.loads(apiSDGB(data, "GetUserLoginBonusApi", userId))
|
||||
# ログインボーナスリストを生成、それから処理してアップロード
|
||||
UserLoginBonusList = UserLoginBonusResponse['userLoginBonusList']
|
||||
UserLoginBonusList = UserLoginBonusResponse["userLoginBonusList"]
|
||||
finalBonusList = generateLoginBonusList(UserLoginBonusList, bonusGenerateMode)
|
||||
if not finalBonusList:
|
||||
return False #ログインボーナスを選択していないから失敗
|
||||
return False # ログインボーナスを選択していないから失敗
|
||||
# UserAllのパッチ
|
||||
userAllPatches = {
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1", #上書きしない
|
||||
"userLoginBonusList": finalBonusList,
|
||||
"isNewLoginBonusList": "0" * len(finalBonusList)
|
||||
}}
|
||||
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1", # 上書きしない
|
||||
"userLoginBonusList": finalBonusList,
|
||||
"isNewLoginBonusList": "0" * len(finalBonusList),
|
||||
}
|
||||
}
|
||||
result = implFullPlayAction(
|
||||
userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def generateLoginBonusList(UserLoginBonusList, generateMode=1):
|
||||
"""
|
||||
ログインボーナスリストを生成します。
|
||||
@@ -65,60 +71,64 @@ def generateLoginBonusList(UserLoginBonusList, generateMode=1):
|
||||
try:
|
||||
tree = ET.parse(loginBonusDBPath)
|
||||
root = tree.getroot()
|
||||
loginBonusIdList = [int(item.find('id').text) for item in root.findall('.//StringID')]
|
||||
loginBonusIdList = [
|
||||
int(item.find("id").text) for item in root.findall(".//StringID")
|
||||
]
|
||||
logger.debug(f"ログインボーナスIDリスト: {loginBonusIdList}")
|
||||
except FileNotFoundError:
|
||||
try:
|
||||
tree = ET.parse(loginBonusDBPathFallback)
|
||||
root = tree.getroot()
|
||||
loginBonusIdList = [int(item.find('id').text) for item in root.findall('.//StringID')]
|
||||
loginBonusIdList = [
|
||||
int(item.find("id").text) for item in root.findall(".//StringID")
|
||||
]
|
||||
logger.debug(f"ログインボーナスIDリスト: {loginBonusIdList}")
|
||||
except:
|
||||
except Exception:
|
||||
raise FileNotFoundError("ログインボーナスデータベースを読み込めません")
|
||||
|
||||
# ログインボーナスの MAX POINT は5の場合があります
|
||||
# その全部のボーナスIDをこのリストに追加してください
|
||||
# 必ず最新のデータを使用してください
|
||||
Bonus5Id = [12, 29, 30, 38, 43, 604, 611]
|
||||
|
||||
|
||||
# UserBonusList から bonusId を取得
|
||||
UserLoginBonusIdList = [item['bonusId'] for item in UserLoginBonusList]
|
||||
|
||||
UserLoginBonusIdList = [item["bonusId"] for item in UserLoginBonusList]
|
||||
|
||||
# 存在しないボーナス
|
||||
NonExistingBonuses = list(set(loginBonusIdList) - set(UserLoginBonusIdList))
|
||||
logger.debug(f"存在しないボーナス: {NonExistingBonuses}")
|
||||
|
||||
bonusList = []
|
||||
if generateMode == 1: #選択したボーナスのみ MAX にする
|
||||
if generateMode == 1: # 選択したボーナスのみ MAX にする
|
||||
for item in UserLoginBonusList:
|
||||
if item['isCurrent'] and not item['isComplete']:
|
||||
point = 4 if item['bonusId'] in Bonus5Id else 9
|
||||
if item["isCurrent"] and not item["isComplete"]:
|
||||
point = 4 if item["bonusId"] in Bonus5Id else 9
|
||||
data = {
|
||||
"bonusId": item['bonusId'],
|
||||
"bonusId": item["bonusId"],
|
||||
"point": point,
|
||||
"isCurrent": True,
|
||||
"isComplete": False
|
||||
"isComplete": False,
|
||||
}
|
||||
bonusList.append(data)
|
||||
if len(bonusList) == 0:
|
||||
raise NoSelectedBonusError("選択したログインボーナスがありません")
|
||||
elif generateMode == 2: #全部 MAX にする
|
||||
elif generateMode == 2: # 全部 MAX にする
|
||||
# 存在しているボーナスを追加
|
||||
for item in UserLoginBonusList:
|
||||
if not item['isComplete']:
|
||||
if not item["isComplete"]:
|
||||
data = {
|
||||
"bonusId": item['bonusId'],
|
||||
"point": 4 if item['bonusId'] in Bonus5Id else 9,
|
||||
"isCurrent": item['isCurrent'],
|
||||
"isComplete": False
|
||||
"bonusId": item["bonusId"],
|
||||
"point": 4 if item["bonusId"] in Bonus5Id else 9,
|
||||
"isCurrent": item["isCurrent"],
|
||||
"isComplete": False,
|
||||
}
|
||||
bonusList.append(data)
|
||||
elif item['bonusId'] == 999:
|
||||
elif item["bonusId"] == 999:
|
||||
data = {
|
||||
"bonusId": 999,
|
||||
"point": (item['point'] // 10) * 10 + 9,
|
||||
"isCurrent": item['isCurrent'],
|
||||
"isComplete": False
|
||||
"point": (item["point"] // 10) * 10 + 9,
|
||||
"isCurrent": item["isCurrent"],
|
||||
"isComplete": False,
|
||||
}
|
||||
bonusList.append(data)
|
||||
# 存在しないボーナスを追加
|
||||
@@ -127,19 +137,20 @@ def generateLoginBonusList(UserLoginBonusList, generateMode=1):
|
||||
"bonusId": bonusId,
|
||||
"point": 4 if bonusId in Bonus5Id else 9,
|
||||
"isCurrent": False,
|
||||
"isComplete": False
|
||||
"isComplete": False,
|
||||
}
|
||||
bonusList.append(data)
|
||||
else:
|
||||
raise SyntaxError("generateMode は 1 または 2 でなければなりません")
|
||||
|
||||
|
||||
logger.debug(f"ログインボーナスリスト: {bonusList}")
|
||||
return bonusList
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# ログインボーナスデータをアップロードする
|
||||
userId = testUid
|
||||
currentLoginTimestamp = generateTimestamp()
|
||||
currentLoginResult = apiLogin(currentLoginTimestamp, userId)
|
||||
implLoginBonus(userId, currentLoginTimestamp, currentLoginResult, 2)
|
||||
apiLogout(currentLoginTimestamp, userId)
|
||||
apiLogout(currentLoginTimestamp, userId)
|
||||
|
||||
@@ -1,71 +1,99 @@
|
||||
# 删除和上传成绩
|
||||
|
||||
from loguru import logger
|
||||
from Config import *
|
||||
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
from HelperFullPlay import implFullPlayAction
|
||||
from MyConfig import testUid
|
||||
|
||||
def implDeleteMusicRecord(userId: int, currentLoginTimestamp:int, currentLoginResult, musicId:int, levelId:int) -> str:
|
||||
musicData= ({
|
||||
"musicId": musicId,
|
||||
"level": levelId,
|
||||
"playCount": 1,
|
||||
"achievement": 0,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"deluxscoreMax": 0,
|
||||
"scoreRank": 0,
|
||||
"extNum1": 0
|
||||
})
|
||||
|
||||
def implDeleteMusicRecord(
|
||||
userId: int,
|
||||
currentLoginTimestamp: int,
|
||||
currentLoginResult,
|
||||
musicId: int,
|
||||
levelId: int,
|
||||
) -> str:
|
||||
musicData = {
|
||||
"musicId": musicId,
|
||||
"level": levelId,
|
||||
"playCount": 1,
|
||||
"achievement": 0,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"deluxscoreMax": 0,
|
||||
"scoreRank": 0,
|
||||
"extNum1": 0,
|
||||
}
|
||||
userAllPatches = {
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "0" # 0为编辑,即可删除掉成绩
|
||||
}}
|
||||
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "0", # 0为编辑,即可删除掉成绩
|
||||
}
|
||||
}
|
||||
result = implFullPlayAction(
|
||||
userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches
|
||||
)
|
||||
return result
|
||||
|
||||
def implUploadMusicRecord(userId: int, currentLoginTimestamp:int, currentLoginResult, musicId:int, levelId:int, achievement:int, dxScore:int) -> str:
|
||||
|
||||
def implUploadMusicRecord(
|
||||
userId: int,
|
||||
currentLoginTimestamp: int,
|
||||
currentLoginResult,
|
||||
musicId: int,
|
||||
levelId: int,
|
||||
achievement: int,
|
||||
dxScore: int,
|
||||
) -> str:
|
||||
"""
|
||||
VERY EARLY STAGE OF UPLOADING SCORES!!!! DO NOT USE THIS!!!!
|
||||
上传成绩的参考实现。
|
||||
"""
|
||||
|
||||
# 要上传的数据
|
||||
musicData= ({
|
||||
"musicId": musicId,
|
||||
"level": levelId,
|
||||
"playCount": 1,
|
||||
"achievement": achievement,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"deluxscoreMax": dxScore,
|
||||
"scoreRank": 0,
|
||||
"extNum1": 0
|
||||
})
|
||||
musicData = {
|
||||
"musicId": musicId,
|
||||
"level": levelId,
|
||||
"playCount": 1,
|
||||
"achievement": achievement,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"deluxscoreMax": dxScore,
|
||||
"scoreRank": 0,
|
||||
"extNum1": 0,
|
||||
}
|
||||
userAllPatches = {
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1" # 0编辑 1插入
|
||||
}}
|
||||
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1", # 0编辑 1插入
|
||||
}
|
||||
}
|
||||
result = implFullPlayAction(
|
||||
userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
userId = testUid
|
||||
currentLoginTimestamp = generateTimestamp()
|
||||
loginResult = apiLogin(currentLoginTimestamp, userId)
|
||||
|
||||
musicId = 852 #852 is tiamat
|
||||
levelId = 3 #3 is MASTER
|
||||
musicId = 852 # 852 is tiamat
|
||||
levelId = 3 # 3 is MASTER
|
||||
|
||||
if loginResult['returnCode'] != 1:
|
||||
if loginResult["returnCode"] != 1:
|
||||
logger.info("登录失败")
|
||||
exit()
|
||||
try:
|
||||
logger.info(implDeleteMusicRecord(userId, currentLoginTimestamp, loginResult, musicId, levelId))
|
||||
#logger.info(implUploadMusicRecord(userId, currentLoginTimestamp, loginResult, musicId, levelId, 1000000, 100))
|
||||
logger.info(
|
||||
implDeleteMusicRecord(
|
||||
userId, currentLoginTimestamp, loginResult, musicId, levelId
|
||||
)
|
||||
)
|
||||
# logger.info(implUploadMusicRecord(userId, currentLoginTimestamp, loginResult, musicId, levelId, 1000000, 100))
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
finally:
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
#logger.warning("Error")
|
||||
# logger.warning("Error")
|
||||
|
||||
@@ -2,57 +2,59 @@
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from Config import *
|
||||
from MyConfig import testUid8
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
from HelperUnlockThing import implUnlockThing
|
||||
|
||||
def implUnlockSingleItem(itemId: int, itemKind: int, userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
|
||||
|
||||
def implUnlockSingleItem(
|
||||
itemId: int,
|
||||
itemKind: int,
|
||||
userId: int,
|
||||
currentLoginTimestamp: int,
|
||||
currentLoginResult,
|
||||
) -> str:
|
||||
"""
|
||||
发单个东西,比如搭档 10
|
||||
"""
|
||||
userItemList = [
|
||||
{
|
||||
"itemKind": itemKind,
|
||||
"itemId": itemId,
|
||||
"stock": 1,
|
||||
"isValid": True
|
||||
}
|
||||
{"itemKind": itemKind, "itemId": itemId, "stock": 1, "isValid": True}
|
||||
]
|
||||
unlockThingResult = implUnlockThing(userItemList, userId, currentLoginTimestamp, currentLoginResult)
|
||||
unlockThingResult = implUnlockThing(
|
||||
userItemList, userId, currentLoginTimestamp, currentLoginResult
|
||||
)
|
||||
return unlockThingResult
|
||||
|
||||
def implUnlockMusic(musicToBeUnlocked: int, userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
|
||||
|
||||
def implUnlockMusic(
|
||||
musicToBeUnlocked: int, userId: int, currentLoginTimestamp: int, currentLoginResult
|
||||
) -> str:
|
||||
"""
|
||||
解锁乐曲
|
||||
"""
|
||||
userItemList = [
|
||||
{
|
||||
"itemKind": 5,
|
||||
"itemId": musicToBeUnlocked,
|
||||
"stock": 1,
|
||||
"isValid": True
|
||||
},
|
||||
{
|
||||
"itemKind": 6,
|
||||
"itemId": musicToBeUnlocked,
|
||||
"stock": 1,
|
||||
"isValid": True
|
||||
},
|
||||
{"itemKind": 5, "itemId": musicToBeUnlocked, "stock": 1, "isValid": True},
|
||||
{"itemKind": 6, "itemId": musicToBeUnlocked, "stock": 1, "isValid": True},
|
||||
]
|
||||
unlockThingResult = implUnlockThing(userItemList, userId, currentLoginTimestamp, currentLoginResult)
|
||||
unlockThingResult = implUnlockThing(
|
||||
userItemList, userId, currentLoginTimestamp, currentLoginResult
|
||||
)
|
||||
return unlockThingResult
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
userId = testUid2
|
||||
userId = int(input("type user id: ").strip() or "0") or testUid8
|
||||
currentLoginTimestamp = generateTimestamp()
|
||||
loginResult = apiLogin(currentLoginTimestamp, userId)
|
||||
|
||||
if loginResult['returnCode'] != 1:
|
||||
if loginResult["returnCode"] != 1:
|
||||
logger.info("登录失败")
|
||||
exit()
|
||||
try:
|
||||
logger.info(implWipeTickets(userId, currentLoginTimestamp, loginResult))
|
||||
logger.info(
|
||||
implUnlockSingleItem(14, 10, userId, currentLoginTimestamp, loginResult)
|
||||
)
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
finally:
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
#logger.warning("Error")
|
||||
# logger.warning("Error")
|
||||
|
||||
@@ -1,57 +1,56 @@
|
||||
from API_TitleServer import *
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
from Config import *
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
from HelperGetUserMusicDetail import getUserFullMusicDetail
|
||||
from HelperMusicDB import getMusicTitle
|
||||
import requests
|
||||
import rapidjson as json
|
||||
|
||||
|
||||
class divingFishAuthFailError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class divingFishCommError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# 水鱼查分器的 API 地址
|
||||
BASE_URL = 'https://www.diving-fish.com/api/maimaidxprober'
|
||||
BASE_URL = "https://www.diving-fish.com/api/maimaidxprober"
|
||||
|
||||
# 水鱼查分器的成绩状态转换
|
||||
COMBO_ID_TO_NAME = ['', 'fc', 'fcp', 'ap', 'app']
|
||||
SYNC_ID_TO_NAME = ['', 'fs', 'fsp', 'fsd', 'fsdp', 'sync']
|
||||
COMBO_ID_TO_NAME = ["", "fc", "fcp", "ap", "app"]
|
||||
SYNC_ID_TO_NAME = ["", "fs", "fsp", "fsd", "fsdp", "sync"]
|
||||
|
||||
def apiDivingFish(method:str, apiPath:str, importToken:str, data=None):
|
||||
'''水鱼查分器的 API 通讯实现'''
|
||||
headers = {
|
||||
"Import-Token": importToken
|
||||
}
|
||||
if method == 'POST':
|
||||
headers['Content-Type'] = 'application/json'
|
||||
logger.info(f'水鱼查分器 API 请求:{method} {BASE_URL + apiPath}')
|
||||
if method == 'POST':
|
||||
|
||||
def apiDivingFish(method: str, apiPath: str, importToken: str, data=None):
|
||||
"""水鱼查分器的 API 通讯实现"""
|
||||
headers = {"Import-Token": importToken}
|
||||
if method == "POST":
|
||||
headers["Content-Type"] = "application/json"
|
||||
logger.info(f"水鱼查分器 API 请求:{method} {BASE_URL + apiPath}")
|
||||
if method == "POST":
|
||||
response = requests.post(
|
||||
url=BASE_URL + apiPath,
|
||||
json=data,
|
||||
headers=headers,
|
||||
)
|
||||
elif method == 'GET':
|
||||
elif method == "GET":
|
||||
response = requests.get(
|
||||
url=BASE_URL + apiPath,
|
||||
headers=headers,
|
||||
)
|
||||
elif method == 'DELETE':
|
||||
elif method == "DELETE":
|
||||
response = requests.delete(
|
||||
url=BASE_URL + apiPath,
|
||||
headers=headers,
|
||||
)
|
||||
else:
|
||||
logger.error(f'未知的请求方法:{method}')
|
||||
raise ValueError(f'未知的请求方法:{method}')
|
||||
|
||||
logger.info(f'水鱼查分器请求结果:{response.status_code}')
|
||||
logger.debug(f'水鱼查分器回应:{response.text}')
|
||||
finalResponseTextDecode = response.text.encode('utf-8').decode('unicode_escape')
|
||||
logger.debug(f'水鱼查分器回应解码后:{finalResponseTextDecode}')
|
||||
logger.error(f"未知的请求方法:{method}")
|
||||
raise ValueError(f"未知的请求方法:{method}")
|
||||
|
||||
logger.info(f"水鱼查分器请求结果:{response.status_code}")
|
||||
logger.debug(f"水鱼查分器回应:{response.text}")
|
||||
finalResponseTextDecode = response.text.encode("utf-8").decode("unicode_escape")
|
||||
logger.debug(f"水鱼查分器回应解码后:{finalResponseTextDecode}")
|
||||
match response.status_code:
|
||||
case 200:
|
||||
return response.json()
|
||||
@@ -60,89 +59,103 @@ def apiDivingFish(method:str, apiPath:str, importToken:str, data=None):
|
||||
case _:
|
||||
raise divingFishCommError
|
||||
|
||||
|
||||
def getFishRecords(importToken: str) -> dict:
|
||||
'''获取水鱼查分器的成绩'''
|
||||
return apiDivingFish('GET', '/player/records', importToken)
|
||||
"""获取水鱼查分器的成绩"""
|
||||
return apiDivingFish("GET", "/player/records", importToken)
|
||||
|
||||
|
||||
def updateFishRecords(importToken: str, records: list[dict]) -> dict:
|
||||
'''上传成绩到水鱼查分器'''
|
||||
return apiDivingFish('POST', '/player/update_records', importToken, records)
|
||||
"""上传成绩到水鱼查分器"""
|
||||
return apiDivingFish("POST", "/player/update_records", importToken, records)
|
||||
|
||||
def resetFishRecords(fishImportToken:str):
|
||||
'''重置水鱼查分器的用户数据'''
|
||||
return apiDivingFish('DELETE', '/player/delete_records', fishImportToken)
|
||||
|
||||
def getFishUserInfo(userQQ:int):
|
||||
'''按QQ获取水鱼查分器的用户信息'''
|
||||
return apiDivingFish('POST', '/query/player', "", {"qq": userQQ})
|
||||
def resetFishRecords(fishImportToken: str):
|
||||
"""重置水鱼查分器的用户数据"""
|
||||
return apiDivingFish("DELETE", "/player/delete_records", fishImportToken)
|
||||
|
||||
|
||||
def getFishUserInfo(userQQ: int):
|
||||
"""按QQ获取水鱼查分器的用户信息"""
|
||||
return apiDivingFish("POST", "/query/player", "", {"qq": userQQ})
|
||||
|
||||
|
||||
def maimaiUserMusicDetailToDivingFishFormat(userMusicDetailList) -> list:
|
||||
'''舞萌的 UserMusicDetail 成绩格式转换成水鱼的格式'''
|
||||
"""舞萌的 UserMusicDetail 成绩格式转换成水鱼的格式"""
|
||||
divingFishList = []
|
||||
for currentMusicDetail in userMusicDetailList:
|
||||
# musicId 大于 100000 属于宴谱,不计入
|
||||
if currentMusicDetail['musicId'] >= 100000:
|
||||
if currentMusicDetail["musicId"] >= 100000:
|
||||
continue
|
||||
# 获得歌名
|
||||
currentMusicTitle = getMusicTitle(currentMusicDetail['musicId'])
|
||||
currentMusicTitle = getMusicTitle(currentMusicDetail["musicId"])
|
||||
# 如果数据库里未找到此歌曲
|
||||
if currentMusicTitle == "R_ERR_MUSIC_ID_NOT_IN_DATABASE":
|
||||
logger.warning(f"数据库无此歌曲 跳过: {currentMusicDetail['musicId']}")
|
||||
continue
|
||||
# 每一个乐曲都判断下是 DX 还是标准
|
||||
if currentMusicDetail['musicId'] >= 10000:
|
||||
notesType = 'DX'
|
||||
if currentMusicDetail["musicId"] >= 10000:
|
||||
notesType = "DX"
|
||||
else:
|
||||
notesType = 'SD'
|
||||
notesType = "SD"
|
||||
# 追加进列表
|
||||
try:
|
||||
divingFishList.append({
|
||||
'achievements': (currentMusicDetail['achievement'] / 10000), # 水鱼的成绩是 float 而非舞萌的 int
|
||||
'title': currentMusicTitle,
|
||||
'type': notesType,
|
||||
'level_index': currentMusicDetail['level'],
|
||||
'fc': COMBO_ID_TO_NAME[currentMusicDetail['comboStatus']],
|
||||
'fs': SYNC_ID_TO_NAME[currentMusicDetail['syncStatus']],
|
||||
'dxScore': currentMusicDetail['deluxscoreMax'],
|
||||
})
|
||||
except:
|
||||
divingFishList.append(
|
||||
{
|
||||
"achievements": (
|
||||
currentMusicDetail["achievement"] / 10000
|
||||
), # 水鱼的成绩是 float 而非舞萌的 int
|
||||
"title": currentMusicTitle,
|
||||
"type": notesType,
|
||||
"level_index": currentMusicDetail["level"],
|
||||
"fc": COMBO_ID_TO_NAME[currentMusicDetail["comboStatus"]],
|
||||
"fs": SYNC_ID_TO_NAME[currentMusicDetail["syncStatus"]],
|
||||
"dxScore": currentMusicDetail["deluxscoreMax"],
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
logger.error(f"无法将 UserMusic 翻译成水鱼格式: {currentMusicDetail}")
|
||||
|
||||
return divingFishList
|
||||
|
||||
def isVaildFishToken(importToken:str):
|
||||
'''通过尝试获取一次成绩,检查水鱼查分器的 Token 是否有效
|
||||
有效返回 True,无效返回 False'''
|
||||
result = apiDivingFish('GET', '/player/records', importToken)
|
||||
|
||||
def isVaildFishToken(importToken: str):
|
||||
"""通过尝试获取一次成绩,检查水鱼查分器的 Token 是否有效
|
||||
有效返回 True,无效返回 False"""
|
||||
result = apiDivingFish("GET", "/player/records", importToken)
|
||||
logger.debug(f"水鱼查分器 Token 检查结果:{result}")
|
||||
if result:
|
||||
return True
|
||||
return False
|
||||
|
||||
def implGetUserCurrentDXRating(userQQ:int):
|
||||
'''获取用户当前的 DX RATING'''
|
||||
|
||||
def implGetUserCurrentDXRating(userQQ: int):
|
||||
"""获取用户当前的 DX RATING"""
|
||||
try:
|
||||
playerData = getFishUserInfo(userQQ)
|
||||
playerRating = playerData['rating']
|
||||
playerRating = playerData["rating"]
|
||||
logger.info(f"用户 {userQQ} 的 DX RATING 是 {playerRating}")
|
||||
except Exception as e:
|
||||
logger.warning(f"无法获取用户 {userQQ} 的 DX RATING: {e}")
|
||||
return False
|
||||
return playerRating
|
||||
|
||||
def implUserMusicToDivingFish(userId:int, fishImportToken:str):
|
||||
'''上传所有成绩到水鱼的参考实现。
|
||||
|
||||
def implUserMusicToDivingFish(userId: int, fishImportToken: str):
|
||||
"""上传所有成绩到水鱼的参考实现。
|
||||
返回一个 int 的 ErrorCode。
|
||||
0: Success
|
||||
1: Get User Music Fail
|
||||
2: Auth Fail
|
||||
3: Comm Error
|
||||
'''
|
||||
"""
|
||||
logger.info("开始尝试上传舞萌成绩到水鱼查分器!")
|
||||
try:
|
||||
userFullMusicDetailList = getUserFullMusicDetail(userId)
|
||||
logger.info("成功得到成绩!转换成水鱼格式..")
|
||||
divingFishData = maimaiUserMusicDetailToDivingFishFormat(userFullMusicDetailList)
|
||||
divingFishData = maimaiUserMusicDetailToDivingFishFormat(
|
||||
userFullMusicDetailList
|
||||
)
|
||||
logger.info("转换成功!开始上传水鱼..")
|
||||
except Exception as e:
|
||||
logger.error(f"获取成绩失败!{e}")
|
||||
@@ -156,8 +169,9 @@ def implUserMusicToDivingFish(userId:int, fishImportToken:str):
|
||||
logger.error("水鱼查分器通讯失败!")
|
||||
return 3
|
||||
|
||||
|
||||
def generateDebugTestScore():
|
||||
'''生成测试成绩'''
|
||||
"""生成测试成绩"""
|
||||
return [
|
||||
{
|
||||
"achievement": 1010000,
|
||||
@@ -165,7 +179,7 @@ def generateDebugTestScore():
|
||||
"deluxscoreMax": 4026,
|
||||
"level": 4,
|
||||
"musicId": 834,
|
||||
"syncStatus": 4
|
||||
"syncStatus": 4,
|
||||
},
|
||||
{
|
||||
"achievement": 1010000,
|
||||
@@ -173,7 +187,6 @@ def generateDebugTestScore():
|
||||
"deluxscoreMax": 4200,
|
||||
"level": 4,
|
||||
"musicId": 11663,
|
||||
"syncStatus": 4
|
||||
}
|
||||
"syncStatus": 4,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
118
ChargeTicket.py
118
ChargeTicket.py
@@ -1,106 +1,126 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 倍票相关 API 的实现
|
||||
import rapidjson as json
|
||||
import pytz
|
||||
from datetime import datetime, timedelta
|
||||
from loguru import logger
|
||||
|
||||
from Config import *
|
||||
from API_TitleServer import apiSDGB
|
||||
from HelperGetUserThing import implGetUser_
|
||||
|
||||
from loguru import logger
|
||||
from Config import (
|
||||
clientId,
|
||||
placeId,
|
||||
regionId,
|
||||
)
|
||||
from MyConfig import testUid2
|
||||
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
from HelperFullPlay import implFullPlayAction, generateMusicData
|
||||
from HelperGetUserThing import implGetUser_
|
||||
|
||||
def implWipeTickets(userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
|
||||
'''清空用户所有票的 API 请求器,返回 Json String。'''
|
||||
|
||||
def implWipeTickets(userId: int, currentLoginTimestamp: int, currentLoginResult) -> str:
|
||||
"""清空用户所有票的 API 请求器,返回 Json String。"""
|
||||
# 先得到当前用户的 Charge 数据
|
||||
currentUserCharge = implGetUser_("Charge", userId)
|
||||
# 取得 List
|
||||
currentUserChargeList = currentUserCharge['userChargeList']
|
||||
currentUserChargeList = currentUserCharge["userChargeList"]
|
||||
# 所有 stock 都置为 0
|
||||
for charge in currentUserChargeList:
|
||||
charge['stock'] = 0
|
||||
charge["stock"] = 0
|
||||
|
||||
musicData = generateMusicData()
|
||||
userAllPatches = {
|
||||
"upsertUserAll": {
|
||||
"userChargeList": currentUserChargeList,
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1" #1避免覆盖
|
||||
}}
|
||||
"upsertUserAll": {
|
||||
"userChargeList": currentUserChargeList,
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1", # 1避免覆盖
|
||||
}
|
||||
}
|
||||
|
||||
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
|
||||
result = implFullPlayAction(
|
||||
userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches
|
||||
)
|
||||
return result
|
||||
|
||||
def apiQueryTicket(userId:int) -> str:
|
||||
'''查询已有票的 API 请求器,返回 Json String。'''
|
||||
|
||||
def apiQueryTicket(userId: int) -> str:
|
||||
"""查询已有票的 API 请求器,返回 Json String。"""
|
||||
# 构建 Payload
|
||||
data = json.dumps({
|
||||
"userId": userId
|
||||
})
|
||||
data = json.dumps({"userId": userId})
|
||||
# 发送请求
|
||||
userdata_result = apiSDGB(data, "GetUserChargeApi", userId)
|
||||
# 返回响应
|
||||
return userdata_result
|
||||
|
||||
def apiBuyTicket(userId:int, ticketType:int, price:int, playerRating:int, playCount:int) -> str:
|
||||
'''倍票购买 API 的请求器'''
|
||||
|
||||
nowTime = datetime.now(pytz.timezone('Asia/Shanghai'))
|
||||
def apiBuyTicket(
|
||||
userId: int, ticketType: int, price: int, playerRating: int, playCount: int
|
||||
) -> str:
|
||||
"""倍票购买 API 的请求器"""
|
||||
|
||||
nowTime = datetime.now(pytz.timezone("Asia/Shanghai"))
|
||||
|
||||
# 构造请求数据 Payload
|
||||
data = json.dumps({
|
||||
"userId": userId,
|
||||
"userChargelog": {
|
||||
"chargeId": ticketType,
|
||||
"price": price,
|
||||
"purchaseDate": nowTime.strftime("%Y-%m-%d %H:%M:%S.0"),
|
||||
"playCount": playCount,
|
||||
"playerRating": playerRating,
|
||||
"placeId": placeId,
|
||||
"regionId": regionId,
|
||||
"clientId": clientId
|
||||
},
|
||||
"userCharge": {
|
||||
"chargeId": ticketType,
|
||||
"stock": 1,
|
||||
"purchaseDate": nowTime.strftime("%Y-%m-%d %H:%M:%S.0"),
|
||||
"validDate": (nowTime + timedelta(days=90)).replace(hour=4, minute=0, second=0).strftime("%Y-%m-%d %H:%M:%S")
|
||||
data = json.dumps(
|
||||
{
|
||||
"userId": userId,
|
||||
"userChargelog": {
|
||||
"chargeId": ticketType,
|
||||
"price": price,
|
||||
"purchaseDate": nowTime.strftime("%Y-%m-%d %H:%M:%S.0"),
|
||||
"playCount": playCount,
|
||||
"playerRating": playerRating,
|
||||
"placeId": placeId,
|
||||
"regionId": regionId,
|
||||
"clientId": clientId,
|
||||
},
|
||||
"userCharge": {
|
||||
"chargeId": ticketType,
|
||||
"stock": 1,
|
||||
"purchaseDate": nowTime.strftime("%Y-%m-%d %H:%M:%S.0"),
|
||||
"validDate": (nowTime + timedelta(days=90))
|
||||
.replace(hour=4, minute=0, second=0)
|
||||
.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
# 发送请求,返回最终得到的 Json String 回执
|
||||
return apiSDGB(data, "UpsertUserChargelogApi", userId)
|
||||
|
||||
def implBuyTicket(userId:int, ticketType:int):
|
||||
'''
|
||||
|
||||
def implBuyTicket(userId: int, ticketType: int):
|
||||
"""
|
||||
购买倍票 API 的参考实现。
|
||||
需要事先登录.
|
||||
返回服务器响应的 Json string。
|
||||
'''
|
||||
"""
|
||||
# 先使用 GetUserData API 请求器,取得 rating 和 pc 数
|
||||
currentUserData = implGetUser_("Data", userId)
|
||||
if currentUserData:
|
||||
playerRating = currentUserData['userData']['playerRating']
|
||||
playCount = currentUserData['userData'].get('playCount', 0)
|
||||
playerRating = currentUserData["userData"]["playerRating"]
|
||||
playCount = currentUserData["userData"].get("playCount", 0)
|
||||
else:
|
||||
return False
|
||||
# 正式买票
|
||||
getTicketResponseStr = apiBuyTicket(userId, ticketType, ticketType-1, playerRating, playCount)
|
||||
getTicketResponseStr = apiBuyTicket(
|
||||
userId, ticketType, ticketType - 1, playerRating, playCount
|
||||
)
|
||||
# 返回结果
|
||||
return getTicketResponseStr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
userId = testUid2
|
||||
currentLoginTimestamp = generateTimestamp()
|
||||
loginResult = apiLogin(currentLoginTimestamp, userId)
|
||||
|
||||
if loginResult['returnCode'] != 1:
|
||||
if loginResult["returnCode"] != 1:
|
||||
logger.info("登录失败")
|
||||
exit()
|
||||
try:
|
||||
logger.info(implBuyTicket(userId, 2)) # 购买倍票
|
||||
#logger.info(apiQueryTicket(userId))
|
||||
# logger.info(apiQueryTicket(userId))
|
||||
finally:
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
#logger.warning("Error")
|
||||
# logger.warning("Error")
|
||||
|
||||
@@ -14,4 +14,3 @@ loginBonusDBPathFallback = "./maimaiDX-Api/Data/loginBonusDB.xml"
|
||||
musicDBPathFallback = "./maimaiDX-Api/Data/musicDB.json"
|
||||
|
||||
# 日本精工,安全防漏
|
||||
#from MyConfig import *
|
||||
|
||||
@@ -1,36 +1,41 @@
|
||||
# 解小黑屋实现
|
||||
# 仍十分不完善,不建议使用
|
||||
|
||||
from Config import *
|
||||
from API_TitleServer import *
|
||||
from GetPreview import apiGetUserPreview
|
||||
from HelperLogInOut import apiLogout
|
||||
import rapidjson as json
|
||||
from loguru import logger
|
||||
import time
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
|
||||
import rapidjson as json
|
||||
|
||||
from GetPreview import apiGetUserPreview
|
||||
from HelperLogInOut import apiLogout
|
||||
from loguru import logger
|
||||
|
||||
from MyConfig import testUid2
|
||||
|
||||
|
||||
def isUserLoggedIn(userId):
|
||||
isLogin = json.loads(apiGetUserPreview(userId, True))['isLogin']
|
||||
isLogin = json.loads(apiGetUserPreview(userId, True))["isLogin"]
|
||||
logger.debug(f"用户 {userId} 是否登录: {isLogin}")
|
||||
return isLogin
|
||||
|
||||
|
||||
def getHumanReadableTime(unixTime):
|
||||
'''将 Unix 时间戳转换为人类可读的时间'''
|
||||
"""将 Unix 时间戳转换为人类可读的时间"""
|
||||
# 减一个小时,因为舞萌貌似是 UTC+9
|
||||
timestamp = int(unixTime) - 3600
|
||||
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
||||
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def getMaimaiUNIXTime(mmddhhmmss, year=2025):
|
||||
"""
|
||||
解析用户输入的 MMDDHHMMSS 格式的时间,返回 Unix 时间戳
|
||||
时间会被推后一个小时,因为舞萌貌似是 UTC+9?
|
||||
"""
|
||||
#date_time_str = f"{year}{mmddhhmmss}"
|
||||
# date_time_str = f"{year}{mmddhhmmss}"
|
||||
# 加上一个小时
|
||||
date_time_str = f"{year}{mmddhhmmss[:4]}{int(mmddhhmmss[4:6])+1:02}{mmddhhmmss[6:]}"
|
||||
date_time_obj = datetime.strptime(date_time_str, '%Y%m%d%H%M%S')
|
||||
date_time_str = (
|
||||
f"{year}{mmddhhmmss[:4]}{int(mmddhhmmss[4:6]) + 1:02}{mmddhhmmss[6:]}"
|
||||
)
|
||||
date_time_obj = datetime.strptime(date_time_str, "%Y%m%d%H%M%S")
|
||||
# 将 datetime 对象转换为 Unix 时间戳
|
||||
unix_timestamp = int(time.mktime(date_time_obj.timetuple()))
|
||||
logger.info(f"转换出了时间戳: {unix_timestamp}")
|
||||
@@ -41,19 +46,20 @@ def logOut(userId, Timestamp):
|
||||
"""极其简单的登出实现,成功返回 True,失败返回 False
|
||||
注意:不会检查用户是否真的登出了,只会尝试登出"""
|
||||
try:
|
||||
if apiLogout(Timestamp, userId, True)['returnCode'] == 1:
|
||||
if apiLogout(Timestamp, userId, True)["returnCode"] == 1:
|
||||
# 成功送出了登出请求
|
||||
logger.debug(f"已成功尝试登出用户 {userId}")
|
||||
return True
|
||||
except:
|
||||
except Exception:
|
||||
logger.error(f"登出用户 {userId} 的时候发生了错误")
|
||||
return False
|
||||
|
||||
|
||||
def isCorrectTimestamp(timestamp, userId):
|
||||
'''
|
||||
"""
|
||||
动作:给定一个时间戳,用它尝试登出用户,然后检查用户是否成功登出。
|
||||
如果用户成功登出,返回 True;否则返回 False。
|
||||
'''
|
||||
"""
|
||||
if not logOut(userId, timestamp):
|
||||
logger.error(f"用时间戳 {timestamp} 登出用户 {userId} 的时候发生了错误")
|
||||
return False
|
||||
@@ -61,6 +67,7 @@ def isCorrectTimestamp(timestamp, userId):
|
||||
logger.debug(f"时间戳 {timestamp} 是否正确: {isLoggedOut}")
|
||||
return isLoggedOut
|
||||
|
||||
|
||||
def findCorrectTimestamp(timestamp, userId, max_attempts=600):
|
||||
# 初始化偏移量
|
||||
offset = 1
|
||||
@@ -72,19 +79,19 @@ def findCorrectTimestamp(timestamp, userId, max_attempts=600):
|
||||
if isCorrectTimestamp(currentTryTimestamp, userId):
|
||||
logger.info(f"正确的时间戳: {currentTryTimestamp}")
|
||||
return currentTryTimestamp
|
||||
|
||||
|
||||
# 增加偏移量尝试
|
||||
currentTryTimestamp = timestamp + offset
|
||||
if isCorrectTimestamp(currentTryTimestamp, userId):
|
||||
logger.info(f"正确的时间戳(在给定时间以后): {currentTryTimestamp}")
|
||||
return currentTryTimestamp
|
||||
|
||||
|
||||
# 减少偏移量尝试
|
||||
currentTryTimestamp = timestamp - offset
|
||||
if isCorrectTimestamp(currentTryTimestamp, userId):
|
||||
logger.info(f"正确的时间戳(在给定时间以前): {currentTryTimestamp}")
|
||||
return currentTryTimestamp
|
||||
|
||||
|
||||
# 增加尝试次数和偏移量
|
||||
attempts += 2
|
||||
offset += 1
|
||||
@@ -92,6 +99,7 @@ def findCorrectTimestamp(timestamp, userId, max_attempts=600):
|
||||
logger.error(f"无法找到正确的时间戳,尝试次数超过了 {max_attempts}")
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
human_time = "0207155500"
|
||||
beginTimestamp = getMaimaiUNIXTime(human_time)
|
||||
|
||||
@@ -2,56 +2,17 @@
|
||||
|
||||
import rapidjson as json
|
||||
from API_TitleServer import apiSDGB
|
||||
from Config import *
|
||||
import time
|
||||
import random
|
||||
from loguru import logger
|
||||
|
||||
def apiGetUserPreview(userId, noLog:bool=False) -> str:
|
||||
data = json.dumps({
|
||||
"userId": int(userId)
|
||||
})
|
||||
|
||||
def apiGetUserPreview(userId, noLog: bool = False) -> str:
|
||||
data = json.dumps({"userId": int(userId)})
|
||||
preview_result = apiSDGB(data, "GetUserPreviewApi", userId, noLog)
|
||||
return preview_result
|
||||
|
||||
|
||||
# CLI 示例
|
||||
if __name__ == "__main__":
|
||||
#userId = input("请输入用户 ID:")
|
||||
userId = testUid8
|
||||
userId = input("请输入用户 ID:")
|
||||
# userId = testUid8
|
||||
print(apiGetUserPreview(userId))
|
||||
|
||||
###
|
||||
### 以下仅留作归档
|
||||
###
|
||||
|
||||
def crawlAllUserPreview():
|
||||
"""omg it's a evil crawler"""
|
||||
# 这里设置开始和结束的 UserId
|
||||
BeginUserId = 10200000
|
||||
EndUserId = 12599999
|
||||
|
||||
# 打开文件,准备写入
|
||||
with open('Remi_UserID_DB_Output.txt', 'w', encoding="utf-8") as f:
|
||||
# 遍历 UserId
|
||||
for userId in range(BeginUserId, EndUserId + 1):
|
||||
# 调用 API
|
||||
try:
|
||||
userPreview = apiGetUserPreview(userId, True)
|
||||
currentUser = json.loads(userPreview)
|
||||
if currentUser["userId"] is not None:
|
||||
# 每爬到一个就把它存到一个文件里面,每个一行
|
||||
f.write(userPreview + "\n")
|
||||
logger.info(f"{userId}: {currentUser['userName']}, RATING: {currentUser['playerRating']}")
|
||||
else:
|
||||
f.write("\n")
|
||||
except:
|
||||
f.write("ERROR\n")
|
||||
time.sleep(4)
|
||||
f.flush()
|
||||
# 随机等待0.2-0.5秒
|
||||
time.sleep(random.uniform(0.2, 0.5))
|
||||
|
||||
print('Finished!')
|
||||
|
||||
#if __name__ == "__main__":
|
||||
# crawlAllUserPreview()
|
||||
print(apiSDGB("{}", "Ping", userId, False))
|
||||
|
||||
21
GetPreview2024.py
Normal file
21
GetPreview2024.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# 获取用户简略预览数据的 API 实现,此 API 无需任何登录即可调取
|
||||
|
||||
import rapidjson as json
|
||||
|
||||
from API_TitleServer import apiSDGB_2024, use2024Api
|
||||
|
||||
use2024Api()
|
||||
|
||||
|
||||
def apiGetUserPreview(userId, noLog: bool = False) -> str:
|
||||
data = json.dumps({"userId": int(userId)})
|
||||
preview_result = apiSDGB_2024(data, "GetUserPreviewApi", userId, noLog)
|
||||
return preview_result
|
||||
|
||||
|
||||
# CLI 示例
|
||||
if __name__ == "__main__":
|
||||
userId = input("请输入用户 ID:")
|
||||
# userId = testUid8
|
||||
# print(apiGetUserPreview(userId))
|
||||
print(apiSDGB_2024("{}", "Ping", userId, False))
|
||||
@@ -1,16 +1,21 @@
|
||||
import rapidjson as json
|
||||
from loguru import logger
|
||||
|
||||
from Config import *
|
||||
from API_TitleServer import *
|
||||
from API_TitleServer import (
|
||||
SDGBRequestError,
|
||||
SDGBApiError,
|
||||
apiSDGB,
|
||||
calcPlaySpecial,
|
||||
)
|
||||
from HelperGetUserThing import implGetUser_
|
||||
from HelperUploadUserPlayLog import apiUploadUserPlaylog
|
||||
from HelperUserAll import generateFullUserAll
|
||||
|
||||
|
||||
def generateMusicData():
|
||||
"""生成一份占位的音乐数据"""
|
||||
return {
|
||||
"musicId": 834, # PANDORA PARADOXXX
|
||||
"musicId": 834, # PANDORA PARADOXXX
|
||||
"level": 4,
|
||||
"playCount": 1,
|
||||
"achievement": 0,
|
||||
@@ -18,9 +23,10 @@ def generateMusicData():
|
||||
"syncStatus": 0,
|
||||
"deluxscoreMax": 0,
|
||||
"scoreRank": 0,
|
||||
"extNum1": 0
|
||||
"extNum1": 0,
|
||||
}
|
||||
|
||||
|
||||
def applyUserAllPatches(userAll, patches):
|
||||
"""
|
||||
递归地将给定的补丁应用到用户数据的各个层次。
|
||||
@@ -29,13 +35,25 @@ def applyUserAllPatches(userAll, patches):
|
||||
:param patches: 包含所有patch的字典
|
||||
"""
|
||||
for key, value in patches.items():
|
||||
if isinstance(value, dict) and key in userAll and isinstance(userAll[key], dict):
|
||||
if (
|
||||
isinstance(value, dict)
|
||||
and key in userAll
|
||||
and isinstance(userAll[key], dict)
|
||||
):
|
||||
# 如果patch的值是字典,并且userAll中对应的key也是字典,递归处理
|
||||
applyUserAllPatches(userAll[key], value)
|
||||
elif isinstance(value, list) and key in userAll and isinstance(userAll[key], list):
|
||||
elif (
|
||||
isinstance(value, list)
|
||||
and key in userAll
|
||||
and isinstance(userAll[key], list)
|
||||
):
|
||||
# 如果值是列表,进行详细的更新处理
|
||||
for i, patch_item in enumerate(value):
|
||||
if i < len(userAll[key]) and isinstance(patch_item, dict) and isinstance(userAll[key][i], dict):
|
||||
if (
|
||||
i < len(userAll[key])
|
||||
and isinstance(patch_item, dict)
|
||||
and isinstance(userAll[key][i], dict)
|
||||
):
|
||||
# 如果列表项是字典,更新字典中的字段
|
||||
applyUserAllPatches(userAll[key][i], patch_item)
|
||||
elif i >= len(userAll[key]):
|
||||
@@ -44,19 +62,29 @@ def applyUserAllPatches(userAll, patches):
|
||||
else:
|
||||
# 否则直接更新或添加key
|
||||
userAll[key] = value
|
||||
|
||||
def implFullPlayAction(userId: int, currentLoginTimestamp:int, currentLoginResult, musicData, userAllPatches, debugMode=False):
|
||||
|
||||
|
||||
def implFullPlayAction(
|
||||
userId: int,
|
||||
currentLoginTimestamp: int,
|
||||
currentLoginResult,
|
||||
musicData,
|
||||
userAllPatches,
|
||||
debugMode=False,
|
||||
):
|
||||
"""
|
||||
一份完整的上机实现,可以打 patch 来实现各种功能
|
||||
需要在外部先登录并传入登录结果
|
||||
"""
|
||||
|
||||
|
||||
# 取得 UserData
|
||||
currentUserData = implGetUser_("Data", userId)
|
||||
currentUserData2 = currentUserData['userData']
|
||||
currentUserData2 = currentUserData["userData"]
|
||||
|
||||
# 构建并上传一个游玩记录
|
||||
currentUploadUserPlaylogApiResult = apiUploadUserPlaylog(userId, musicData, currentUserData2, currentLoginResult['loginId'])
|
||||
currentUploadUserPlaylogApiResult = apiUploadUserPlaylog(
|
||||
userId, musicData, currentUserData2, currentLoginResult["loginId"]
|
||||
)
|
||||
logger.debug(f"上传 UserPlayLog 结果: {currentUploadUserPlaylogApiResult}")
|
||||
|
||||
# 构建并上传 UserAll
|
||||
@@ -65,13 +93,22 @@ def implFullPlayAction(userId: int, currentLoginTimestamp:int, currentLoginResul
|
||||
# 计算一个特殊数
|
||||
currentPlaySpecial = calcPlaySpecial()
|
||||
# 生成出 UserAll
|
||||
currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial)
|
||||
currentUserAll = generateFullUserAll(
|
||||
userId,
|
||||
currentLoginResult,
|
||||
currentLoginTimestamp,
|
||||
currentUserData2,
|
||||
currentPlaySpecial,
|
||||
)
|
||||
# 应用参数里的补丁
|
||||
applyUserAllPatches(currentUserAll, userAllPatches)
|
||||
|
||||
|
||||
# 调试模式下直接输出数据
|
||||
if debugMode:
|
||||
logger.debug("调试模式:构建出的 UserAll 数据:" + json.dumps(currentUserAll, indent=4))
|
||||
logger.debug(
|
||||
"调试模式:构建出的 UserAll 数据:"
|
||||
+ json.dumps(currentUserAll, indent=4)
|
||||
)
|
||||
logger.info("Bye!")
|
||||
return
|
||||
|
||||
@@ -88,8 +125,8 @@ def implFullPlayAction(userId: int, currentLoginTimestamp:int, currentLoginResul
|
||||
raise SDGBApiError("邪门错误")
|
||||
# 成功上传后退出循环
|
||||
break
|
||||
else: # 重试次数超过3次
|
||||
else: # 重试次数超过3次
|
||||
raise SDGBRequestError
|
||||
|
||||
logger.info("上机:结果:"+ str(currentUserAllResult))
|
||||
|
||||
logger.info("上机:结果:" + str(currentUserAllResult))
|
||||
return currentUserAllResult
|
||||
|
||||
@@ -1,59 +1,62 @@
|
||||
# 获取用户成绩的各种实现
|
||||
from API_TitleServer import *
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
from Config import *
|
||||
import rapidjson as json
|
||||
from HelperMusicDB import getMusicTitle
|
||||
from loguru import logger
|
||||
import sys
|
||||
|
||||
from HelperMusicDB import getMusicTitle
|
||||
from API_TitleServer import apiSDGB
|
||||
from MyConfig import testUid
|
||||
|
||||
# 日志设置
|
||||
#log_level = "DEBUG"
|
||||
#log_format = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS zz}</green> | <level>{level: <8}</level> | <yellow>Line {line: >4} ({file}):</yellow> <b>{message}</b>"
|
||||
#logger.add(sys.stderr, level=log_level, format=log_format, colorize=True, backtrace=True, diagnose=True)
|
||||
#logger.add("file.log", level=log_level, format=log_format, colorize=False, backtrace=True, diagnose=True)
|
||||
# log_level = "DEBUG"
|
||||
# log_format = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS zz}</green> | <level>{level: <8}</level> | <yellow>Line {line: >4} ({file}):</yellow> <b>{message}</b>"
|
||||
# logger.add(sys.stderr, level=log_level, format=log_format, colorize=True, backtrace=True, diagnose=True)
|
||||
# logger.add("file.log", level=log_level, format=log_format, colorize=False, backtrace=True, diagnose=True)
|
||||
|
||||
def getUserMusicDetail(userId:int, nextIndex:int=0, maxCount:int=50) -> dict:
|
||||
|
||||
def getUserMusicDetail(userId: int, nextIndex: int = 0, maxCount: int = 50) -> dict:
|
||||
"""获取用户的成绩的API"""
|
||||
data = json.dumps({
|
||||
"userId": int(userId),
|
||||
"nextIndex": nextIndex,
|
||||
"maxCount": maxCount
|
||||
})
|
||||
data = json.dumps(
|
||||
{"userId": int(userId), "nextIndex": nextIndex, "maxCount": maxCount}
|
||||
)
|
||||
return json.loads(apiSDGB(data, "GetUserMusicApi", userId))
|
||||
|
||||
|
||||
def getUserFullMusicDetail(userId: int):
|
||||
"""获取用户的全部成绩"""
|
||||
currentUserMusicDetailList = []
|
||||
nextIndex:int|None = None # 初始化 nextIndex
|
||||
while nextIndex != 0 or nextIndex is None: #只要还有nextIndex就一直获取获取
|
||||
nextIndex: int | None = None # 初始化 nextIndex
|
||||
while nextIndex != 0 or nextIndex is None: # 只要还有nextIndex就一直获取获取
|
||||
userMusicResponse = getUserMusicDetail(userId, nextIndex or 0)
|
||||
nextIndex = userMusicResponse['nextIndex']
|
||||
nextIndex = userMusicResponse["nextIndex"]
|
||||
logger.info(f"NextIndex: {nextIndex}")
|
||||
# 处理已经没有 userMusicList 的情况
|
||||
if not userMusicResponse['userMusicList']:
|
||||
if not userMusicResponse["userMusicList"]:
|
||||
break
|
||||
# 只要还有 userMusicList 就一直加进去,直到全部获取完毕
|
||||
for currentMusic in userMusicResponse['userMusicList']:
|
||||
for currentMusicDetail in currentMusic['userMusicDetailList']:
|
||||
if not currentMusicDetail['playCount'] > 0:
|
||||
for currentMusic in userMusicResponse["userMusicList"]:
|
||||
for currentMusicDetail in currentMusic["userMusicDetailList"]:
|
||||
if not currentMusicDetail["playCount"] > 0:
|
||||
continue
|
||||
currentUserMusicDetailList.append(currentMusicDetail)
|
||||
return currentUserMusicDetailList
|
||||
|
||||
|
||||
def parseUserFullMusicDetail(userFullMusicDetailList: list):
|
||||
"""解析用户的全部成绩,给出一个迫真人类可读 list 套 dict"""
|
||||
musicDetailList = []
|
||||
for currentMusicDetail in userFullMusicDetailList:
|
||||
musicDetailList.append({
|
||||
'歌名': getMusicTitle(currentMusicDetail['musicId']),
|
||||
'难度': currentMusicDetail['level'],
|
||||
'分数': currentMusicDetail['achievement'] / 10000,
|
||||
'DX分数': currentMusicDetail['deluxscoreMax']
|
||||
})
|
||||
musicDetailList.append(
|
||||
{
|
||||
"歌名": getMusicTitle(currentMusicDetail["musicId"]),
|
||||
"难度": currentMusicDetail["level"],
|
||||
"分数": currentMusicDetail["achievement"] / 10000,
|
||||
"DX分数": currentMusicDetail["deluxscoreMax"],
|
||||
}
|
||||
)
|
||||
return musicDetailList
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
userId = testUid
|
||||
userFullMusicDetailList = getUserFullMusicDetail(userId)
|
||||
parsedUserFullMusicDetail = parseUserFullMusicDetail(userFullMusicDetailList)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# 获取用户数据的 API 实现
|
||||
from loguru import logger
|
||||
import rapidjson as json
|
||||
from API_TitleServer import apiSDGB
|
||||
|
||||
def implGetUser_(thing:str, userId:int, noLog=False) -> dict:
|
||||
|
||||
def implGetUser_(thing: str, userId: int, noLog=False) -> dict:
|
||||
"""获取用户某些数据的 API 实现,返回 Dict"""
|
||||
# 获取 Json String
|
||||
result = apiGetUserThing(userId, thing, noLog)
|
||||
@@ -12,14 +12,12 @@ def implGetUser_(thing:str, userId:int, noLog=False) -> dict:
|
||||
# 返回 Dict
|
||||
return userthingDict
|
||||
|
||||
def apiGetUserThing(userId:int, thing:str, noLog=False) -> str:
|
||||
|
||||
def apiGetUserThing(userId: int, thing: str, noLog=False) -> str:
|
||||
"""获取用户数据的 API 请求器,返回 Json String"""
|
||||
# 构建 Payload
|
||||
data = json.dumps({
|
||||
"userId": userId
|
||||
})
|
||||
data = json.dumps({"userId": userId})
|
||||
# 发送请求
|
||||
userthing_result = apiSDGB(data, "GetUser" + thing + "Api", userId, noLog)
|
||||
# 返回响应
|
||||
return userthing_result
|
||||
|
||||
|
||||
@@ -1,62 +1,83 @@
|
||||
# 登录·登出实现
|
||||
# 一般作为模块使用,但也可以作为 CLI 程序运行以强制登出账号。
|
||||
|
||||
import rapidjson as json
|
||||
import time
|
||||
from loguru import logger
|
||||
import random
|
||||
|
||||
from Config import *
|
||||
from API_TitleServer import apiSDGB
|
||||
import rapidjson as json
|
||||
from loguru import logger
|
||||
|
||||
def apiLogin(timestamp:int, userId:int, noLog:bool=False) -> dict:
|
||||
from API_TitleServer import apiSDGB
|
||||
from Config import (
|
||||
clientId,
|
||||
placeId,
|
||||
regionId,
|
||||
)
|
||||
from MyConfig import testUid
|
||||
|
||||
|
||||
def apiLogin(timestamp: int, userId: int, noLog: bool = False) -> dict:
|
||||
"""登录,返回 dict"""
|
||||
data = json.dumps({
|
||||
"userId": userId,
|
||||
"accessCode": "",
|
||||
"regionId": regionId,
|
||||
"placeId": placeId,
|
||||
"clientId": clientId,
|
||||
"dateTime": timestamp,
|
||||
"isContinue": False,
|
||||
"genericFlag": 0,
|
||||
})
|
||||
data = json.dumps(
|
||||
{
|
||||
"userId": userId,
|
||||
"accessCode": "",
|
||||
"regionId": regionId,
|
||||
"placeId": placeId,
|
||||
"clientId": clientId,
|
||||
"dateTime": timestamp,
|
||||
"isContinue": False,
|
||||
"genericFlag": 0,
|
||||
}
|
||||
)
|
||||
login_result = json.loads(apiSDGB(data, "UserLoginApi", userId, noLog))
|
||||
if not noLog:
|
||||
logger.info("登录:结果:"+ str(login_result))
|
||||
logger.info("登录:结果:" + str(login_result))
|
||||
return login_result
|
||||
|
||||
def apiLogout(timestamp:int, userId:int, noLog:bool=False) -> dict:
|
||||
|
||||
def apiLogout(timestamp: int, userId: int, noLog: bool = False) -> dict:
|
||||
"""登出,返回 dict"""
|
||||
data = json.dumps({
|
||||
"userId": userId,
|
||||
"accessCode": "",
|
||||
"regionId": regionId,
|
||||
"placeId": placeId,
|
||||
"clientId": clientId,
|
||||
"dateTime": timestamp,
|
||||
"type": 1
|
||||
})
|
||||
data = json.dumps(
|
||||
{
|
||||
"userId": userId,
|
||||
"accessCode": "",
|
||||
"regionId": regionId,
|
||||
"placeId": placeId,
|
||||
"clientId": clientId,
|
||||
"dateTime": timestamp,
|
||||
"type": 1,
|
||||
}
|
||||
)
|
||||
logout_result = json.loads(apiSDGB(data, "UserLogoutApi", userId, noLog))
|
||||
if not noLog:
|
||||
logger.info("登出:结果:"+ str(logout_result))
|
||||
logger.info("登出:结果:" + str(logout_result))
|
||||
return logout_result
|
||||
|
||||
|
||||
def generateTimestampLegacy() -> int:
|
||||
"""生成一个凑合用的时间戳"""
|
||||
timestamp = int(time.time()) - 60
|
||||
logger.info(f"生成时间戳: {timestamp}")
|
||||
return timestamp
|
||||
|
||||
|
||||
def generateTimestamp() -> int:
|
||||
"""生成一个今天早上 10:00 随机偏移的时间戳"""
|
||||
timestamp = int(time.mktime(time.strptime(time.strftime("%Y-%m-%d 10:00:00"), "%Y-%m-%d %H:%M:%S"))) + random.randint(-600, 600)
|
||||
timestamp = int(
|
||||
time.mktime(
|
||||
time.strptime(time.strftime("%Y-%m-%d 10:00:00"), "%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
) + random.randint(-600, 600)
|
||||
logger.info(f"生成时间戳: {timestamp}")
|
||||
logger.info(f"此时间戳对应的时间为: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))}")
|
||||
logger.info(
|
||||
f"此时间戳对应的时间为: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))}"
|
||||
)
|
||||
return timestamp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("强制登出 CLI")
|
||||
uid = testUid
|
||||
timestamp = input("Timestamp: ")
|
||||
timestamp = generateTimestamp()
|
||||
apiLogout(int(timestamp), int(uid))
|
||||
|
||||
159
HelperMisc.py
159
HelperMisc.py
@@ -4,9 +4,10 @@ import rapidjson as json
|
||||
from loguru import logger
|
||||
from HelperGetUserThing import implGetUser_
|
||||
import unicodedata
|
||||
from Config import *
|
||||
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
|
||||
|
||||
from MyConfig import testUid
|
||||
|
||||
|
||||
def numberToLetter(number):
|
||||
"""
|
||||
@@ -17,10 +18,11 @@ def numberToLetter(number):
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def maimaiVersionToHumanReadable(romVersion: str, dataVersion: str) -> str:
|
||||
try:
|
||||
romVersionList = romVersion.split('.')
|
||||
dataVersionList = dataVersion.split('.')
|
||||
romVersionList = romVersion.split(".")
|
||||
dataVersionList = dataVersion.split(".")
|
||||
except Exception as e:
|
||||
logger.warning(f"无法解析版本号: {romVersion} {dataVersion},错误:{e}")
|
||||
return "无效版本号:无法解析"
|
||||
@@ -52,24 +54,19 @@ def maimaiVersionToHumanReadable(romVersion: str, dataVersion: str) -> str:
|
||||
|
||||
finalVersionString = f"{versionStringPrefix}{finalVersionList[0]}.{finalVersionList[1]}{finalVersionList[2]}"
|
||||
return finalVersionString
|
||||
|
||||
|
||||
levelIdDict = {
|
||||
"绿": 0,
|
||||
"黄": 1,
|
||||
"红": 2,
|
||||
"紫": 3,
|
||||
"白": 4,
|
||||
"宴": 5
|
||||
}
|
||||
|
||||
levelIdDict = {"绿": 0, "黄": 1, "红": 2, "紫": 3, "白": 4, "宴": 5}
|
||||
|
||||
|
||||
def getHalfWidthString(s):
|
||||
"""全角转半角,舞萌ID用"""
|
||||
return unicodedata.normalize('NFKC', s)
|
||||
return unicodedata.normalize("NFKC", s)
|
||||
|
||||
|
||||
def getHumanReadableLoginErrorCode(loginResult) -> str:
|
||||
'''解析登录结果并且给出中文的报错解释'''
|
||||
match loginResult['returnCode']:
|
||||
"""解析登录结果并且给出中文的报错解释"""
|
||||
match loginResult["returnCode"]:
|
||||
case 1:
|
||||
return False
|
||||
case 100:
|
||||
@@ -79,10 +76,11 @@ def getHumanReadableLoginErrorCode(loginResult) -> str:
|
||||
case 103:
|
||||
return "❌ 试图登录的账号 UID 无效,请检查账号是否正确。"
|
||||
case _:
|
||||
return "❌ 登录失败!这不应该发生,请反馈此问题。错误详情:"+ loginResult
|
||||
return "❌ 登录失败!这不应该发生,请反馈此问题。错误详情:" + loginResult
|
||||
|
||||
|
||||
def checkTechnologyUseCount(userId: int) -> int:
|
||||
'''猜测账号是否用了科技,0没用过,其他为用过'''
|
||||
"""猜测账号是否用了科技,0没用过,其他为用过"""
|
||||
userData1 = implGetUser_("Data", userId)
|
||||
userData = userData1.get("userData", {})
|
||||
userRegion = implGetUser_("Region", userId)
|
||||
@@ -92,13 +90,16 @@ def checkTechnologyUseCount(userId: int) -> int:
|
||||
allRegionPlayCount = 0
|
||||
for region in userRegionList:
|
||||
allRegionPlayCount += region.get("playCount", 0)
|
||||
logger.info(f"用户 {userId} 的总游玩次数: {playCount}, 各地区游玩次数: {allRegionPlayCount}")
|
||||
logger.info(
|
||||
f"用户 {userId} 的总游玩次数: {playCount}, 各地区游玩次数: {allRegionPlayCount}"
|
||||
)
|
||||
# 计算全部的 Region 加起来的游玩次数是否和 playCount 对不上,对不上就是用了科技
|
||||
# 返回差值
|
||||
return playCount - allRegionPlayCount
|
||||
|
||||
def getFriendlyUserData(userId:int) -> str:
|
||||
'''生成一个(相对)友好的UserData的人话'''
|
||||
|
||||
def getFriendlyUserData(userId: int) -> str:
|
||||
"""生成一个(相对)友好的UserData的人话"""
|
||||
userData1 = implGetUser_("Data", userId)
|
||||
userData = userData1.get("userData", {})
|
||||
userRegion = implGetUser_("Region", userId)
|
||||
@@ -114,7 +115,7 @@ def getFriendlyUserData(userId:int) -> str:
|
||||
result += f"最近登录版本: {maimaiVersionToHumanReadable(userData.get('lastRomVersion'), userData.get('lastDataVersion'))} "
|
||||
result += f"最近登录地区: {userData.get('lastRegionName', '未知')}\n"
|
||||
result += f"注册日期: {userData.get('firstPlayDate')} "
|
||||
result += f"注册版本: {maimaiVersionToHumanReadable(userData.get('firstRomVersion'),userData.get('firstDataVersion'))}\n"
|
||||
result += f"注册版本: {maimaiVersionToHumanReadable(userData.get('firstRomVersion'), userData.get('firstDataVersion'))}\n"
|
||||
result += f"封号状态(banState): {banState}\n"
|
||||
try:
|
||||
logger.info(userRegion)
|
||||
@@ -124,28 +125,31 @@ def getFriendlyUserData(userId:int) -> str:
|
||||
|
||||
return result
|
||||
|
||||
def getHumanReadableRegionData(userRegion:str) -> str:
|
||||
'''生成一个人类可读的地区数据'''
|
||||
|
||||
def getHumanReadableRegionData(userRegion: str) -> str:
|
||||
"""生成一个人类可读的地区数据"""
|
||||
userRegionList = userRegion.get("userRegionList")
|
||||
logger.info(userRegionList)
|
||||
result = ""
|
||||
for region in userRegionList:
|
||||
regionName = WAHLAP_REGIONS.get(region['regionId'], '未知')
|
||||
playCount = region['playCount']
|
||||
created = region['created']
|
||||
regionName = WAHLAP_REGIONS.get(region["regionId"], "未知")
|
||||
playCount = region["playCount"]
|
||||
created = region["created"]
|
||||
result += f"\n{regionName} 游玩次数: {playCount} 首次游玩: {created}"
|
||||
return result
|
||||
|
||||
def getHumanReadablePreview(preview_json_content:str) -> str:
|
||||
'''简单,粗略地解释 Preview 的 Json String 为人话。'''
|
||||
|
||||
def getHumanReadablePreview(preview_json_content: str) -> str:
|
||||
"""简单,粗略地解释 Preview 的 Json String 为人话。"""
|
||||
previewData = json.loads(preview_json_content)
|
||||
userName = getHalfWidthString(previewData['userName'])
|
||||
playerRating = previewData['playerRating']
|
||||
userName = getHalfWidthString(previewData["userName"])
|
||||
playerRating = previewData["playerRating"]
|
||||
finalString = f"用户名:{userName}\nDX RATING:{playerRating}\n"
|
||||
return finalString
|
||||
|
||||
|
||||
def getHumanReadableLoginBonusList(jsonString: str):
|
||||
'''生成一个人类可读的 Login Bonus 的列表'''
|
||||
"""生成一个人类可读的 Login Bonus 的列表"""
|
||||
data = json.loads(jsonString)
|
||||
|
||||
result = []
|
||||
@@ -157,32 +161,34 @@ def getHumanReadableLoginBonusList(jsonString: str):
|
||||
result.append(line)
|
||||
|
||||
resultString = ""
|
||||
for line in result: # 转成字符串
|
||||
for line in result: # 转成字符串
|
||||
resultString += line + "\n"
|
||||
|
||||
return resultString
|
||||
|
||||
|
||||
def getHumanReadableTicketList(jsonString: str):
|
||||
'''生成一个人类可读的 UserCharge 的列表'''
|
||||
"""生成一个人类可读的 UserCharge 的列表"""
|
||||
data = json.loads(jsonString)
|
||||
|
||||
userId = data['userId']
|
||||
length = data['length']
|
||||
userChargeList = data['userChargeList']
|
||||
userId = data["userId"]
|
||||
length = data["length"]
|
||||
userChargeList = data["userChargeList"]
|
||||
|
||||
result = f"UID: {userId} 票槽大小: {length} 所有记录:"
|
||||
for currentItem in userChargeList:
|
||||
chargeId = currentItem['chargeId']
|
||||
stock = currentItem['stock']
|
||||
purchaseDate = currentItem['purchaseDate']
|
||||
validDate = currentItem['validDate']
|
||||
chargeId = currentItem["chargeId"]
|
||||
stock = currentItem["stock"]
|
||||
purchaseDate = currentItem["purchaseDate"]
|
||||
validDate = currentItem["validDate"]
|
||||
|
||||
result += f"\nID: {chargeId} 持有: {stock}, 购买日期: {purchaseDate}, 有效期限: {validDate}"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def getHumanReadableUserData(userData) -> str:
|
||||
'''生成一个人类可读的 UserData 的数据(比较详细)'''
|
||||
"""生成一个人类可读的 UserData 的数据(比较详细)"""
|
||||
userId = userData.get("userId")
|
||||
userData = userData.get("userData", {})
|
||||
banState = userData.get("banState")
|
||||
@@ -239,39 +245,40 @@ def getHumanReadableUserData(userData) -> str:
|
||||
result += f"封号状态: {banState}\n"
|
||||
return result
|
||||
|
||||
|
||||
WAHLAP_REGIONS = {
|
||||
1: '北京',
|
||||
2: '重庆',
|
||||
3: '上海',
|
||||
4: '天津',
|
||||
5: '安徽',
|
||||
6: '福建',
|
||||
7: '甘肃',
|
||||
8: '广东',
|
||||
9: '贵州',
|
||||
10: '海南',
|
||||
11: '河北',
|
||||
12: '黑龙江',
|
||||
13: '河南',
|
||||
14: '湖北',
|
||||
15: '湖南',
|
||||
16: '江苏',
|
||||
17: '江西',
|
||||
18: '吉林',
|
||||
19: '辽宁',
|
||||
20: '青海',
|
||||
21: '陕西',
|
||||
22: '山东',
|
||||
23: '山西',
|
||||
24: '四川',
|
||||
25: '(未知25)',
|
||||
26: '云南',
|
||||
27: '浙江',
|
||||
28: '广西',
|
||||
29: '内蒙古',
|
||||
30: '宁夏',
|
||||
31: '新疆',
|
||||
32: '西藏',
|
||||
1: "北京",
|
||||
2: "重庆",
|
||||
3: "上海",
|
||||
4: "天津",
|
||||
5: "安徽",
|
||||
6: "福建",
|
||||
7: "甘肃",
|
||||
8: "广东",
|
||||
9: "贵州",
|
||||
10: "海南",
|
||||
11: "河北",
|
||||
12: "黑龙江",
|
||||
13: "河南",
|
||||
14: "湖北",
|
||||
15: "湖南",
|
||||
16: "江苏",
|
||||
17: "江西",
|
||||
18: "吉林",
|
||||
19: "辽宁",
|
||||
20: "青海",
|
||||
21: "陕西",
|
||||
22: "山东",
|
||||
23: "山西",
|
||||
24: "四川",
|
||||
25: "(未知25)",
|
||||
26: "云南",
|
||||
27: "浙江",
|
||||
28: "广西",
|
||||
29: "内蒙古",
|
||||
30: "宁夏",
|
||||
31: "新疆",
|
||||
32: "西藏",
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -284,12 +291,12 @@ if __name__ == "__main__":
|
||||
currentLoginTimestamp = generateTimestamp()
|
||||
loginResult = apiLogin(currentLoginTimestamp, userId)
|
||||
|
||||
if loginResult['returnCode'] != 1:
|
||||
if loginResult["returnCode"] != 1:
|
||||
logger.info("登录失败")
|
||||
exit()
|
||||
try:
|
||||
logger.info(checkTechnologyUseCount(userId))
|
||||
#logger.info(apiQueryTicket(userId))
|
||||
# logger.info(apiQueryTicket(userId))
|
||||
finally:
|
||||
logger.info(apiLogout(currentLoginTimestamp, userId))
|
||||
#logger.warning("Error")
|
||||
# logger.warning("Error")
|
||||
|
||||
@@ -4,11 +4,11 @@ from loguru import logger
|
||||
|
||||
def getMusicTitle(musicId: int) -> str:
|
||||
"""从数据库获取音乐的标题"""
|
||||
#logger.debug(f"查询歌名: {musicId}")
|
||||
# logger.debug(f"查询歌名: {musicId}")
|
||||
musicInfo = musicDB.get(musicId)
|
||||
if not musicInfo:
|
||||
logger.warning(f"数据库里未找到此歌曲: {musicId}")
|
||||
return "R_ERR_MUSIC_ID_NOT_IN_DATABASE"
|
||||
musicName = musicInfo.get("name")
|
||||
#logger.debug(f"成功查询到歌名: {musicName}")
|
||||
return musicName
|
||||
# logger.debug(f"成功查询到歌名: {musicName}")
|
||||
return musicName
|
||||
|
||||
@@ -1,43 +1,48 @@
|
||||
# 解锁东西的一个通用的助手,不可独立使用
|
||||
from loguru import logger
|
||||
from Config import *
|
||||
from HelperFullPlay import implFullPlayAction
|
||||
|
||||
def implUnlockThing(newUserItemList, userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
|
||||
musicData= ({
|
||||
"musicId": 11538, # Amber Chronicle
|
||||
"level": 0,
|
||||
"playCount": 1,
|
||||
"achievement": 0,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"deluxscoreMax": 0,
|
||||
"scoreRank": 0,
|
||||
"extNum1": 0
|
||||
})
|
||||
|
||||
def implUnlockThing(
|
||||
newUserItemList, userId: int, currentLoginTimestamp: int, currentLoginResult
|
||||
) -> str:
|
||||
musicData = {
|
||||
"musicId": 11538, # Amber Chronicle
|
||||
"level": 0,
|
||||
"playCount": 1,
|
||||
"achievement": 0,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"deluxscoreMax": 0,
|
||||
"scoreRank": 0,
|
||||
"extNum1": 0,
|
||||
}
|
||||
userAllPatches = {
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1",
|
||||
"userItemList": newUserItemList,
|
||||
"isNewItemList": "1" * len(newUserItemList)
|
||||
}}
|
||||
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
|
||||
"upsertUserAll": {
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1",
|
||||
"userItemList": newUserItemList,
|
||||
"isNewItemList": "1" * len(newUserItemList),
|
||||
}
|
||||
}
|
||||
result = implFullPlayAction(
|
||||
userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
itemKindDict = {
|
||||
"PLATE": 1, # 姓名框
|
||||
"TITLE": 2, # 称号
|
||||
"ICON": 3, # 头像
|
||||
"MUSIC": 5, # 歌
|
||||
"MUSIC_MASTER": 6, # 紫谱
|
||||
"MUSIC_RE_MASTER": 7,# 白谱
|
||||
"CHARACTER": 9, # 旅行伙伴
|
||||
"PARTNER": 10, # 搭档
|
||||
"FRAME": 11, # 背景板
|
||||
"TICKET": 12 # 功能票
|
||||
# "PRESENT": 4, # ?
|
||||
# "MUSIC_STRONG": 8, # ?
|
||||
"PLATE": 1, # 姓名框
|
||||
"TITLE": 2, # 称号
|
||||
"ICON": 3, # 头像
|
||||
"MUSIC": 5, # 歌
|
||||
"MUSIC_MASTER": 6, # 紫谱
|
||||
"MUSIC_RE_MASTER": 7, # 白谱
|
||||
"CHARACTER": 9, # 旅行伙伴
|
||||
"PARTNER": 10, # 搭档
|
||||
"FRAME": 11, # 背景板
|
||||
"TICKET": 12, # 功能票
|
||||
# "PRESENT": 4, # ?
|
||||
# "MUSIC_STRONG": 8, # ?
|
||||
}
|
||||
|
||||
itemKindzhCNDict = {
|
||||
@@ -50,9 +55,9 @@ itemKindzhCNDict = {
|
||||
"旅行伙伴": "CHARACTER",
|
||||
"搭档": "PARTNER",
|
||||
"背景板": "FRAME",
|
||||
"功能票": "TICKET"
|
||||
# "礼物": "PRESENT",
|
||||
# "STRONG": "MUSIC_STRONG",
|
||||
"功能票": "TICKET",
|
||||
# "礼物": "PRESENT",
|
||||
# "STRONG": "MUSIC_STRONG",
|
||||
}
|
||||
|
||||
partnerList = {
|
||||
@@ -75,5 +80,5 @@ partnerList = {
|
||||
"26": "黒姫",
|
||||
"27": "俊达萌",
|
||||
"28": "乙姫(2024)",
|
||||
"29": "青柠熊&柠檬熊(2024)"
|
||||
}
|
||||
"29": "青柠熊&柠檬熊(2024)",
|
||||
}
|
||||
|
||||
@@ -8,133 +8,146 @@ from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
from API_TitleServer import apiSDGB
|
||||
from Config import *
|
||||
from Config import (
|
||||
placeId,
|
||||
placeName,
|
||||
)
|
||||
|
||||
def apiUploadUserPlaylog(userId:int, musicDataToBeUploaded, currentUserData2, loginId:int) -> str:
|
||||
|
||||
def apiUploadUserPlaylog(
|
||||
userId: int, musicDataToBeUploaded, currentUserData2, loginId: int
|
||||
) -> str:
|
||||
"""
|
||||
上传一个 UserPlayLog。
|
||||
注意:成绩为随机的空成绩,只用作占位
|
||||
返回 Json String。"""
|
||||
|
||||
# 构建一个 PlayLog
|
||||
data = json.dumps({
|
||||
"userId": int(userId),
|
||||
"userPlaylogList": [
|
||||
data = json.dumps(
|
||||
{
|
||||
"userId": 0,
|
||||
"orderId": 0,
|
||||
"playlogId": loginId,
|
||||
"version": 1051000,
|
||||
"placeId": placeId,
|
||||
"placeName": placeName,
|
||||
"loginDate": int(time.time()), #似乎和登录timestamp不同
|
||||
"playDate": datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d'),
|
||||
"userPlayDate": datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S') + '.0',
|
||||
"type": 0,
|
||||
"musicId": int(musicDataToBeUploaded['musicId']),
|
||||
"level": int(musicDataToBeUploaded['level']),
|
||||
"trackNo": 1,
|
||||
"vsMode": 0,
|
||||
"vsUserName": "",
|
||||
"vsStatus": 0,
|
||||
"vsUserRating": 0,
|
||||
"vsUserAchievement": 0,
|
||||
"vsUserGradeRank": 0,
|
||||
"vsRank": 0,
|
||||
"playerNum": 1,
|
||||
"playedUserId1": 0,
|
||||
"playedUserName1": "",
|
||||
"playedMusicLevel1": 0,
|
||||
"playedUserId2": 0,
|
||||
"playedUserName2": "",
|
||||
"playedMusicLevel2": 0,
|
||||
"playedUserId3": 0,
|
||||
"playedUserName3": "",
|
||||
"playedMusicLevel3": 0,
|
||||
"characterId1": currentUserData2['charaSlot'][0],
|
||||
"characterLevel1": random.randint(1000,6500),
|
||||
"characterAwakening1": 5,
|
||||
"characterId2": currentUserData2['charaSlot'][1],
|
||||
"characterLevel2": random.randint(1000,6500),
|
||||
"characterAwakening2": 5,
|
||||
"characterId3": currentUserData2['charaSlot'][2],
|
||||
"characterLevel3": random.randint(1000,6500),
|
||||
"characterAwakening3": 5,
|
||||
"characterId4": currentUserData2['charaSlot'][3],
|
||||
"characterLevel4": random.randint(1000,6500),
|
||||
"characterAwakening4": 5,
|
||||
"characterId5": currentUserData2['charaSlot'][4],
|
||||
"characterLevel5": random.randint(1000,6500),
|
||||
"characterAwakening5": 5,
|
||||
"achievement": int(musicDataToBeUploaded['achievement']),
|
||||
"deluxscore": int(musicDataToBeUploaded['deluxscoreMax']),
|
||||
"scoreRank": int(musicDataToBeUploaded['scoreRank']),
|
||||
"maxCombo": 0,
|
||||
"totalCombo": random.randint(700,900),
|
||||
"maxSync": 0,
|
||||
"totalSync": 0,
|
||||
"tapCriticalPerfect": 0,
|
||||
"tapPerfect": 0,
|
||||
"tapGreat": 0,
|
||||
"tapGood": 0,
|
||||
"tapMiss": random.randint(1,10),
|
||||
"holdCriticalPerfect": 0,
|
||||
"holdPerfect": 0,
|
||||
"holdGreat": 0,
|
||||
"holdGood": 0,
|
||||
"holdMiss": random.randint(1,15),
|
||||
"slideCriticalPerfect": 0,
|
||||
"slidePerfect": 0,
|
||||
"slideGreat": 0,
|
||||
"slideGood": 0,
|
||||
"slideMiss": random.randint(1,15),
|
||||
"touchCriticalPerfect": 0,
|
||||
"touchPerfect": 0,
|
||||
"touchGreat": 0,
|
||||
"touchGood": 0,
|
||||
"touchMiss": random.randint(1,15),
|
||||
"breakCriticalPerfect": 0,
|
||||
"breakPerfect": 0,
|
||||
"breakGreat": 0,
|
||||
"breakGood": 0,
|
||||
"breakMiss": random.randint(1,15),
|
||||
"isTap": True,
|
||||
"isHold": True,
|
||||
"isSlide": True,
|
||||
"isTouch": True,
|
||||
"isBreak": True,
|
||||
"isCriticalDisp": True,
|
||||
"isFastLateDisp": True,
|
||||
"fastCount": 0,
|
||||
"lateCount": 0,
|
||||
"isAchieveNewRecord": True,
|
||||
"isDeluxscoreNewRecord": True,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"isClear": False,
|
||||
"beforeRating": currentUserData2['playerRating'],
|
||||
"afterRating": currentUserData2['playerRating'],
|
||||
"beforeGrade": 0,
|
||||
"afterGrade": 0,
|
||||
"afterGradeRank": 1,
|
||||
"beforeDeluxRating": currentUserData2['playerRating'],
|
||||
"afterDeluxRating": currentUserData2['playerRating'],
|
||||
"isPlayTutorial": False,
|
||||
"isEventMode": False,
|
||||
"isFreedomMode": False,
|
||||
"playMode": 0,
|
||||
"isNewFree": False,
|
||||
"trialPlayAchievement": -1,
|
||||
"extNum1": 0,
|
||||
"extNum2": 0,
|
||||
"extNum4": 3020,
|
||||
"extBool1": False,
|
||||
"extBool2": False
|
||||
"userId": int(userId),
|
||||
"userPlaylogList": [
|
||||
{
|
||||
"userId": 0,
|
||||
"orderId": 0,
|
||||
"playlogId": loginId,
|
||||
"version": 1051000,
|
||||
"placeId": placeId,
|
||||
"placeName": placeName,
|
||||
"loginDate": int(time.time()), # 似乎和登录timestamp不同
|
||||
"playDate": datetime.now(pytz.timezone("Asia/Shanghai")).strftime(
|
||||
"%Y-%m-%d"
|
||||
),
|
||||
"userPlayDate": datetime.now(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ ".0",
|
||||
"type": 0,
|
||||
"musicId": int(musicDataToBeUploaded["musicId"]),
|
||||
"level": int(musicDataToBeUploaded["level"]),
|
||||
"trackNo": 1,
|
||||
"vsMode": 0,
|
||||
"vsUserName": "",
|
||||
"vsStatus": 0,
|
||||
"vsUserRating": 0,
|
||||
"vsUserAchievement": 0,
|
||||
"vsUserGradeRank": 0,
|
||||
"vsRank": 0,
|
||||
"playerNum": 1,
|
||||
"playedUserId1": 0,
|
||||
"playedUserName1": "",
|
||||
"playedMusicLevel1": 0,
|
||||
"playedUserId2": 0,
|
||||
"playedUserName2": "",
|
||||
"playedMusicLevel2": 0,
|
||||
"playedUserId3": 0,
|
||||
"playedUserName3": "",
|
||||
"playedMusicLevel3": 0,
|
||||
"characterId1": currentUserData2["charaSlot"][0],
|
||||
"characterLevel1": random.randint(1000, 6500),
|
||||
"characterAwakening1": 5,
|
||||
"characterId2": currentUserData2["charaSlot"][1],
|
||||
"characterLevel2": random.randint(1000, 6500),
|
||||
"characterAwakening2": 5,
|
||||
"characterId3": currentUserData2["charaSlot"][2],
|
||||
"characterLevel3": random.randint(1000, 6500),
|
||||
"characterAwakening3": 5,
|
||||
"characterId4": currentUserData2["charaSlot"][3],
|
||||
"characterLevel4": random.randint(1000, 6500),
|
||||
"characterAwakening4": 5,
|
||||
"characterId5": currentUserData2["charaSlot"][4],
|
||||
"characterLevel5": random.randint(1000, 6500),
|
||||
"characterAwakening5": 5,
|
||||
"achievement": int(musicDataToBeUploaded["achievement"]),
|
||||
"deluxscore": int(musicDataToBeUploaded["deluxscoreMax"]),
|
||||
"scoreRank": int(musicDataToBeUploaded["scoreRank"]),
|
||||
"maxCombo": 0,
|
||||
"totalCombo": random.randint(700, 900),
|
||||
"maxSync": 0,
|
||||
"totalSync": 0,
|
||||
"tapCriticalPerfect": 0,
|
||||
"tapPerfect": 0,
|
||||
"tapGreat": 0,
|
||||
"tapGood": 0,
|
||||
"tapMiss": random.randint(1, 10),
|
||||
"holdCriticalPerfect": 0,
|
||||
"holdPerfect": 0,
|
||||
"holdGreat": 0,
|
||||
"holdGood": 0,
|
||||
"holdMiss": random.randint(1, 15),
|
||||
"slideCriticalPerfect": 0,
|
||||
"slidePerfect": 0,
|
||||
"slideGreat": 0,
|
||||
"slideGood": 0,
|
||||
"slideMiss": random.randint(1, 15),
|
||||
"touchCriticalPerfect": 0,
|
||||
"touchPerfect": 0,
|
||||
"touchGreat": 0,
|
||||
"touchGood": 0,
|
||||
"touchMiss": random.randint(1, 15),
|
||||
"breakCriticalPerfect": 0,
|
||||
"breakPerfect": 0,
|
||||
"breakGreat": 0,
|
||||
"breakGood": 0,
|
||||
"breakMiss": random.randint(1, 15),
|
||||
"isTap": True,
|
||||
"isHold": True,
|
||||
"isSlide": True,
|
||||
"isTouch": True,
|
||||
"isBreak": True,
|
||||
"isCriticalDisp": True,
|
||||
"isFastLateDisp": True,
|
||||
"fastCount": 0,
|
||||
"lateCount": 0,
|
||||
"isAchieveNewRecord": True,
|
||||
"isDeluxscoreNewRecord": True,
|
||||
"comboStatus": 0,
|
||||
"syncStatus": 0,
|
||||
"isClear": False,
|
||||
"beforeRating": currentUserData2["playerRating"],
|
||||
"afterRating": currentUserData2["playerRating"],
|
||||
"beforeGrade": 0,
|
||||
"afterGrade": 0,
|
||||
"afterGradeRank": 1,
|
||||
"beforeDeluxRating": currentUserData2["playerRating"],
|
||||
"afterDeluxRating": currentUserData2["playerRating"],
|
||||
"isPlayTutorial": False,
|
||||
"isEventMode": False,
|
||||
"isFreedomMode": False,
|
||||
"playMode": 0,
|
||||
"isNewFree": False,
|
||||
"trialPlayAchievement": -1,
|
||||
"extNum1": 0,
|
||||
"extNum2": 0,
|
||||
"extNum4": 3020,
|
||||
"extBool1": False,
|
||||
"extBool2": False,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
# 发送请求
|
||||
result = apiSDGB(data, "UploadUserPlaylogListApi", userId)
|
||||
logger.info("上传游玩记录:结果:"+ str(result))
|
||||
logger.info("上传游玩记录:结果:" + str(result))
|
||||
# 返回响应
|
||||
return result
|
||||
|
||||
249
HelperUserAll.py
249
HelperUserAll.py
@@ -2,12 +2,20 @@
|
||||
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
from Config import *
|
||||
from HelperGetUserThing import implGetUser_
|
||||
|
||||
from HelperGetUserMusicDetail import getUserMusicDetail
|
||||
from loguru import logger
|
||||
|
||||
from HelperGetUserThing import implGetUser_
|
||||
from HelperGetUserMusicDetail import getUserMusicDetail
|
||||
|
||||
from Config import (
|
||||
clientId,
|
||||
placeName,
|
||||
placeId,
|
||||
regionId,
|
||||
regionName,
|
||||
)
|
||||
|
||||
|
||||
def isNewMusicType(userId, musicId, level) -> str:
|
||||
"""判断这首 musicId 在 isNewMusicDetailList 应该填什么
|
||||
0: Edit
|
||||
@@ -16,21 +24,38 @@ def isNewMusicType(userId, musicId, level) -> str:
|
||||
|
||||
未完工,仅供测试
|
||||
"""
|
||||
userMusicDetailList = getUserMusicDetail(userId, musicId, 1)['userMusicList'][0]['userMusicDetailList']
|
||||
userMusicDetailList = getUserMusicDetail(userId, musicId, 1)["userMusicList"][0][
|
||||
"userMusicDetailList"
|
||||
]
|
||||
logger.info(userMusicDetailList)
|
||||
try:
|
||||
if userMusicDetailList[0]['musicId'] == musicId and userMusicDetailList[0]['level'] == level:
|
||||
if (
|
||||
userMusicDetailList[0]["musicId"] == musicId
|
||||
and userMusicDetailList[0]["level"] == level
|
||||
):
|
||||
logger.info(f"We think {musicId} Level {level} should use EDIT.")
|
||||
return "0"
|
||||
except:
|
||||
except Exception:
|
||||
return "1"
|
||||
|
||||
|
||||
def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial):
|
||||
def generateFullUserAll(
|
||||
userId,
|
||||
currentLoginResult,
|
||||
currentLoginTimestamp,
|
||||
currentUserData2,
|
||||
currentPlaySpecial,
|
||||
):
|
||||
"""从服务器取得必要的数据并构建一个比较完整的 UserAll"""
|
||||
|
||||
# 先构建一个基础 UserAll
|
||||
currentUserAll = generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial)
|
||||
currentUserAll = generateUserAllData(
|
||||
userId,
|
||||
currentLoginResult,
|
||||
currentLoginTimestamp,
|
||||
currentUserData2,
|
||||
currentPlaySpecial,
|
||||
)
|
||||
|
||||
# 然后从服务器取得必要的数据
|
||||
currentUserExtend = implGetUser_("Extend", userId, True)
|
||||
@@ -41,61 +66,78 @@ def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, curre
|
||||
currentUserMissionData = implGetUser_("MissionData", userId, True)
|
||||
|
||||
# 把这些数据都追加进去
|
||||
currentUserAll['upsertUserAll']['userExtend'] = [currentUserExtend['userExtend']]
|
||||
currentUserAll['upsertUserAll']['userOption'] = [currentUserOption['userOption']]
|
||||
currentUserAll['upsertUserAll']['userRatingList'] = [currentUserRating['userRating']]
|
||||
currentUserAll['upsertUserAll']['userActivityList'] = [currentUserActivity['userActivity']]
|
||||
currentUserAll['upsertUserAll']['userChargeList'] = currentUserCharge['userChargeList']
|
||||
currentUserAll['upsertUserAll']['userWeeklyData'] = currentUserMissionData['userWeeklyData']
|
||||
currentUserAll["upsertUserAll"]["userExtend"] = [currentUserExtend["userExtend"]]
|
||||
currentUserAll["upsertUserAll"]["userOption"] = [currentUserOption["userOption"]]
|
||||
currentUserAll["upsertUserAll"]["userRatingList"] = [
|
||||
currentUserRating["userRating"]
|
||||
]
|
||||
currentUserAll["upsertUserAll"]["userActivityList"] = [
|
||||
currentUserActivity["userActivity"]
|
||||
]
|
||||
currentUserAll["upsertUserAll"]["userChargeList"] = currentUserCharge[
|
||||
"userChargeList"
|
||||
]
|
||||
currentUserAll["upsertUserAll"]["userWeeklyData"] = currentUserMissionData[
|
||||
"userWeeklyData"
|
||||
]
|
||||
|
||||
# 完事
|
||||
return currentUserAll
|
||||
|
||||
|
||||
def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial):
|
||||
def generateUserAllData(
|
||||
userId,
|
||||
currentLoginResult,
|
||||
currentLoginTimestamp,
|
||||
currentUserData2,
|
||||
currentPlaySpecial,
|
||||
):
|
||||
"""构建一个非常基础的 UserAll 数据,必须手动填充一些数据"""
|
||||
|
||||
|
||||
data = {
|
||||
"userId": userId,
|
||||
"playlogId": currentLoginResult['loginId'],
|
||||
"playlogId": currentLoginResult["loginId"],
|
||||
"isEventMode": False,
|
||||
"isFreePlay": False,
|
||||
"upsertUserAll": {
|
||||
"userData": [
|
||||
{
|
||||
"accessCode": "",
|
||||
"userName": currentUserData2['userName'],
|
||||
"userName": currentUserData2["userName"],
|
||||
"isNetMember": 1,
|
||||
"point": currentUserData2['point'],
|
||||
"totalPoint": currentUserData2['totalPoint'],
|
||||
"iconId": currentUserData2['iconId'],
|
||||
"plateId": currentUserData2['plateId'],
|
||||
"titleId": currentUserData2['titleId'],
|
||||
"partnerId": currentUserData2['partnerId'],
|
||||
"frameId": currentUserData2['frameId'],
|
||||
"selectMapId": currentUserData2['selectMapId'],
|
||||
"totalAwake": currentUserData2['totalAwake'],
|
||||
"gradeRating": currentUserData2['gradeRating'],
|
||||
"musicRating": currentUserData2['musicRating'],
|
||||
"playerRating": currentUserData2['playerRating'],
|
||||
"highestRating": currentUserData2['highestRating'],
|
||||
"gradeRank": currentUserData2['gradeRank'],
|
||||
"classRank": currentUserData2['classRank'],
|
||||
"courseRank": currentUserData2['courseRank'],
|
||||
"charaSlot": currentUserData2['charaSlot'],
|
||||
"charaLockSlot": currentUserData2['charaLockSlot'],
|
||||
"contentBit": currentUserData2['contentBit'],
|
||||
"playCount": currentUserData2['playCount'],
|
||||
"currentPlayCount": currentUserData2['currentPlayCount'],
|
||||
"point": currentUserData2["point"],
|
||||
"totalPoint": currentUserData2["totalPoint"],
|
||||
"iconId": currentUserData2["iconId"],
|
||||
"plateId": currentUserData2["plateId"],
|
||||
"titleId": currentUserData2["titleId"],
|
||||
"partnerId": currentUserData2["partnerId"],
|
||||
"frameId": currentUserData2["frameId"],
|
||||
"selectMapId": currentUserData2["selectMapId"],
|
||||
"totalAwake": currentUserData2["totalAwake"],
|
||||
"gradeRating": currentUserData2["gradeRating"],
|
||||
"musicRating": currentUserData2["musicRating"],
|
||||
"playerRating": currentUserData2["playerRating"],
|
||||
"highestRating": currentUserData2["highestRating"],
|
||||
"gradeRank": currentUserData2["gradeRank"],
|
||||
"classRank": currentUserData2["classRank"],
|
||||
"courseRank": currentUserData2["courseRank"],
|
||||
"charaSlot": currentUserData2["charaSlot"],
|
||||
"charaLockSlot": currentUserData2["charaLockSlot"],
|
||||
"contentBit": currentUserData2["contentBit"],
|
||||
"playCount": currentUserData2["playCount"],
|
||||
"currentPlayCount": currentUserData2["currentPlayCount"],
|
||||
"renameCredit": 0,
|
||||
"mapStock": currentUserData2['mapStock'],
|
||||
"eventWatchedDate": currentUserData2['eventWatchedDate'],
|
||||
"mapStock": currentUserData2["mapStock"],
|
||||
"eventWatchedDate": currentUserData2["eventWatchedDate"],
|
||||
"lastGameId": "SDGB",
|
||||
"lastRomVersion": currentUserData2['lastRomVersion'],
|
||||
"lastDataVersion": currentUserData2['lastDataVersion'],
|
||||
#"lastLoginDate": currentLoginResult['lastLoginDate'], # sb
|
||||
"lastLoginDate": currentUserData2['lastLoginDate'], # 等待测试
|
||||
"lastPlayDate": datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S') + '.0',
|
||||
"lastRomVersion": currentUserData2["lastRomVersion"],
|
||||
"lastDataVersion": currentUserData2["lastDataVersion"],
|
||||
# "lastLoginDate": currentLoginResult['lastLoginDate'], # sb
|
||||
"lastLoginDate": currentUserData2["lastLoginDate"], # 等待测试
|
||||
"lastPlayDate": datetime.now(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ ".0",
|
||||
"lastPlayCredit": 1,
|
||||
"lastPlayMode": 0,
|
||||
"lastPlaceId": placeId,
|
||||
@@ -107,68 +149,83 @@ def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, curre
|
||||
"lastCountryCode": "CHN",
|
||||
"lastSelectEMoney": 0,
|
||||
"lastSelectTicket": 0,
|
||||
"lastSelectCourse": currentUserData2['lastSelectCourse'],
|
||||
"lastSelectCourse": currentUserData2["lastSelectCourse"],
|
||||
"lastCountCourse": 0,
|
||||
"firstGameId": "SDGB",
|
||||
"firstRomVersion": currentUserData2['firstRomVersion'],
|
||||
"firstDataVersion": currentUserData2['firstDataVersion'],
|
||||
"firstPlayDate": currentUserData2['firstPlayDate'],
|
||||
"compatibleCmVersion": currentUserData2['compatibleCmVersion'],
|
||||
"dailyBonusDate": currentUserData2['dailyBonusDate'],
|
||||
"dailyCourseBonusDate": currentUserData2['dailyCourseBonusDate'],
|
||||
"lastPairLoginDate": currentUserData2['lastPairLoginDate'],
|
||||
"lastTrialPlayDate": currentUserData2['lastTrialPlayDate'],
|
||||
"firstRomVersion": currentUserData2["firstRomVersion"],
|
||||
"firstDataVersion": currentUserData2["firstDataVersion"],
|
||||
"firstPlayDate": currentUserData2["firstPlayDate"],
|
||||
"compatibleCmVersion": currentUserData2["compatibleCmVersion"],
|
||||
"dailyBonusDate": currentUserData2["dailyBonusDate"],
|
||||
"dailyCourseBonusDate": currentUserData2["dailyCourseBonusDate"],
|
||||
"lastPairLoginDate": currentUserData2["lastPairLoginDate"],
|
||||
"lastTrialPlayDate": currentUserData2["lastTrialPlayDate"],
|
||||
"playVsCount": 0,
|
||||
"playSyncCount": 0,
|
||||
"winCount": 0,
|
||||
"helpCount": 0,
|
||||
"comboCount": 0,
|
||||
"totalDeluxscore": currentUserData2['totalDeluxscore'],
|
||||
"totalBasicDeluxscore": currentUserData2['totalBasicDeluxscore'],
|
||||
"totalAdvancedDeluxscore": currentUserData2['totalAdvancedDeluxscore'],
|
||||
"totalExpertDeluxscore": currentUserData2['totalExpertDeluxscore'],
|
||||
"totalMasterDeluxscore": currentUserData2['totalMasterDeluxscore'],
|
||||
"totalReMasterDeluxscore": currentUserData2['totalReMasterDeluxscore'],
|
||||
"totalSync": currentUserData2['totalSync'],
|
||||
"totalBasicSync": currentUserData2['totalBasicSync'],
|
||||
"totalAdvancedSync": currentUserData2['totalAdvancedSync'],
|
||||
"totalExpertSync": currentUserData2['totalExpertSync'],
|
||||
"totalMasterSync": currentUserData2['totalMasterSync'],
|
||||
"totalReMasterSync": currentUserData2['totalReMasterSync'],
|
||||
"totalAchievement": currentUserData2['totalAchievement'],
|
||||
"totalBasicAchievement": currentUserData2['totalBasicAchievement'],
|
||||
"totalAdvancedAchievement": currentUserData2['totalAdvancedAchievement'],
|
||||
"totalExpertAchievement": currentUserData2['totalExpertAchievement'],
|
||||
"totalMasterAchievement": currentUserData2['totalMasterAchievement'],
|
||||
"totalReMasterAchievement": currentUserData2['totalReMasterAchievement'],
|
||||
"playerOldRating": currentUserData2['playerOldRating'],
|
||||
"playerNewRating": currentUserData2['playerNewRating'],
|
||||
"totalDeluxscore": currentUserData2["totalDeluxscore"],
|
||||
"totalBasicDeluxscore": currentUserData2["totalBasicDeluxscore"],
|
||||
"totalAdvancedDeluxscore": currentUserData2[
|
||||
"totalAdvancedDeluxscore"
|
||||
],
|
||||
"totalExpertDeluxscore": currentUserData2["totalExpertDeluxscore"],
|
||||
"totalMasterDeluxscore": currentUserData2["totalMasterDeluxscore"],
|
||||
"totalReMasterDeluxscore": currentUserData2[
|
||||
"totalReMasterDeluxscore"
|
||||
],
|
||||
"totalSync": currentUserData2["totalSync"],
|
||||
"totalBasicSync": currentUserData2["totalBasicSync"],
|
||||
"totalAdvancedSync": currentUserData2["totalAdvancedSync"],
|
||||
"totalExpertSync": currentUserData2["totalExpertSync"],
|
||||
"totalMasterSync": currentUserData2["totalMasterSync"],
|
||||
"totalReMasterSync": currentUserData2["totalReMasterSync"],
|
||||
"totalAchievement": currentUserData2["totalAchievement"],
|
||||
"totalBasicAchievement": currentUserData2["totalBasicAchievement"],
|
||||
"totalAdvancedAchievement": currentUserData2[
|
||||
"totalAdvancedAchievement"
|
||||
],
|
||||
"totalExpertAchievement": currentUserData2[
|
||||
"totalExpertAchievement"
|
||||
],
|
||||
"totalMasterAchievement": currentUserData2[
|
||||
"totalMasterAchievement"
|
||||
],
|
||||
"totalReMasterAchievement": currentUserData2[
|
||||
"totalReMasterAchievement"
|
||||
],
|
||||
"playerOldRating": currentUserData2["playerOldRating"],
|
||||
"playerNewRating": currentUserData2["playerNewRating"],
|
||||
"banState": 0,
|
||||
"friendRegistSkip": currentUserData2['friendRegistSkip'],
|
||||
"dateTime": currentLoginTimestamp
|
||||
"friendRegistSkip": currentUserData2["friendRegistSkip"],
|
||||
"dateTime": currentLoginTimestamp,
|
||||
}
|
||||
],
|
||||
"userExtend": [], #需要填上
|
||||
"userOption": [], #需要填上
|
||||
"userExtend": [], # 需要填上
|
||||
"userOption": [], # 需要填上
|
||||
"userGhost": [],
|
||||
"userCharacterList": [],
|
||||
"userMapList": [],
|
||||
"userLoginBonusList": [],
|
||||
"userRatingList": [], #需要填上
|
||||
"userItemList": [], #可选,但经常要填上
|
||||
"userMusicDetailList": [],#需要填上
|
||||
"userRatingList": [], # 需要填上
|
||||
"userItemList": [], # 可选,但经常要填上
|
||||
"userMusicDetailList": [], # 需要填上
|
||||
"userCourseList": [],
|
||||
"userFriendSeasonRankingList": [],
|
||||
"userChargeList": [], #需要填上
|
||||
"userChargeList": [], # 需要填上
|
||||
"userFavoriteList": [],
|
||||
"userActivityList": [], #需要填上
|
||||
"userActivityList": [], # 需要填上
|
||||
"userMissionDataList": [],
|
||||
"userWeeklyData": [],#应该需要填上
|
||||
"userWeeklyData": [], # 应该需要填上
|
||||
"userGamePlaylogList": [
|
||||
{
|
||||
"playlogId": currentLoginResult['loginId'],
|
||||
"playlogId": currentLoginResult["loginId"],
|
||||
"version": "1.51.00",
|
||||
"playDate": datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S') + '.0',
|
||||
"playDate": datetime.now(pytz.timezone("Asia/Shanghai")).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
+ ".0",
|
||||
"playMode": 0,
|
||||
"useTicketId": -1,
|
||||
"playCredit": 1,
|
||||
@@ -177,9 +234,9 @@ def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, curre
|
||||
"isPlayTutorial": False,
|
||||
"isEventMode": False,
|
||||
"isNewFree": False,
|
||||
"playCount": currentUserData2['playCount'],
|
||||
"playCount": currentUserData2["playCount"],
|
||||
"playSpecial": currentPlaySpecial,
|
||||
"playOtherUserId": 0
|
||||
"playOtherUserId": 0,
|
||||
}
|
||||
],
|
||||
"user2pPlaylog": {
|
||||
@@ -189,9 +246,9 @@ def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, curre
|
||||
"userName2": "",
|
||||
"regionId": 0,
|
||||
"placeId": 0,
|
||||
"user2pPlaylogDetailList": []
|
||||
"user2pPlaylogDetailList": [],
|
||||
},
|
||||
"userIntimateList": [],
|
||||
"userIntimateList": [],
|
||||
"userShopItemStockList": [],
|
||||
"userGetPointList": [],
|
||||
"userTradeItemList": [],
|
||||
@@ -201,13 +258,13 @@ def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, curre
|
||||
"isNewMapList": "",
|
||||
"isNewLoginBonusList": "",
|
||||
"isNewItemList": "",
|
||||
"isNewMusicDetailList": "", #可选但经常要填上
|
||||
"isNewMusicDetailList": "", # 可选但经常要填上
|
||||
"isNewCourseList": "0",
|
||||
"isNewFavoriteList": "",
|
||||
"isNewFriendSeasonRankingList": "",
|
||||
"isNewUserIntimateList": "",
|
||||
"isNewFavoritemusicList": "",
|
||||
"isNewKaleidxScopeList": ""
|
||||
}
|
||||
}
|
||||
"isNewKaleidxScopeList": "",
|
||||
},
|
||||
}
|
||||
return data
|
||||
|
||||
@@ -6,17 +6,17 @@ from typing import Dict, Union
|
||||
MusicDBType = Dict[int, Dict[str, Union[int, str]]]
|
||||
|
||||
# 将 '__all__' 用于模块导出声明
|
||||
__all__ = ['musicDB']
|
||||
__all__ = ["musicDB"]
|
||||
|
||||
# 读取并解析 JSON 文件
|
||||
try:
|
||||
with open(musicDBPath, 'r', encoding='utf-8') as f:
|
||||
with open(musicDBPath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
try:
|
||||
with open(musicDBPathFallback, 'r', encoding='utf-8') as f:
|
||||
with open(musicDBPathFallback, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except:
|
||||
except Exception:
|
||||
raise FileNotFoundError("musicDB.json 文件不存在!")
|
||||
|
||||
# 将 JSON 数据转换为指定格式的字典
|
||||
|
||||
@@ -53,7 +53,7 @@ def main_sdga():
|
||||
|
||||
print(str(decompressedData))
|
||||
|
||||
def main_sdgb():
|
||||
def main_sdgb140():
|
||||
# 填入你的想解密的数据的 base64 编码
|
||||
base64_encoded_data = "eJyrTVvpuGwCR32OdodwtVXZ7/Ofmfhin7k/K61q3XNoad1rAPGwECU="
|
||||
|
||||
@@ -71,3 +71,4 @@ def main_sdgb():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_sdga()
|
||||
main_sdgb140()
|
||||
|
||||
@@ -2,30 +2,40 @@ import sys
|
||||
import rapidjson as json
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QLineEdit, QTextEdit, QPushButton, QLabel, QHBoxLayout
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QLineEdit,
|
||||
QTextEdit,
|
||||
QPushButton,
|
||||
QLabel,
|
||||
QHBoxLayout,
|
||||
)
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
# 将当前目录的父目录加入到 sys.path 中
|
||||
from pathlib import Path
|
||||
|
||||
current_dir = Path(__file__).resolve().parent
|
||||
parent_dir = current_dir.parent
|
||||
sys.path.append(str(parent_dir))
|
||||
|
||||
from API_TitleServer import *
|
||||
|
||||
def sendRequest(requestText:str, apiNameText:str, uid:int) -> str:
|
||||
|
||||
def sendRequest(requestText: str, apiNameText: str, uid: int) -> str:
|
||||
try:
|
||||
data = json.loads(requestText)
|
||||
data = json.dumps(data)
|
||||
except:
|
||||
except Exception:
|
||||
return "给出的输入不是有效的 JSON"
|
||||
try:
|
||||
result = apiSDGB(data, apiNameText, uid)
|
||||
except Exception as e:
|
||||
return "请求失败:" + str(e)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class ApiTester(QMainWindow):
|
||||
def __init__(self):
|
||||
@@ -70,21 +80,18 @@ class ApiTester(QMainWindow):
|
||||
self.ResponseTextBox.setReadOnly(True)
|
||||
MainLayout.addWidget(self.ResponseTextBox)
|
||||
|
||||
|
||||
|
||||
# 布局设定
|
||||
MainLayout.setContentsMargins(5, 5, 5, 5)
|
||||
MainLayout.setSpacing(5)
|
||||
MainLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
|
||||
def prepareRequest(self):
|
||||
# 发送请求用
|
||||
try:
|
||||
RequestDataString = self.RequestInputBox.toPlainText()
|
||||
TargetAPIString = self.TargetAPIInputBox.text()
|
||||
AgentExtraString = int(self.AgentExtraInputBox.text())
|
||||
except:
|
||||
except Exception:
|
||||
self.ResponseTextBox.setPlainText("输入无效")
|
||||
return
|
||||
|
||||
@@ -93,12 +100,13 @@ class ApiTester(QMainWindow):
|
||||
# 显示出输出
|
||||
self.ResponseTextBox.setPlainText(Result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
# Set proper style for each OS
|
||||
#if sys.platform == "win32":
|
||||
# if sys.platform == "win32":
|
||||
# app.setStyle("windowsvista")
|
||||
#else:
|
||||
# else:
|
||||
# app.setStyle("Fusion")
|
||||
window = ApiTester()
|
||||
window.show()
|
||||
|
||||
49
_Special.py
49
_Special.py
@@ -1,36 +1,55 @@
|
||||
# 纯纯测试用
|
||||
|
||||
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:
|
||||
from MyConfig import testUid8
|
||||
|
||||
|
||||
def implChangeVersionNumber(
|
||||
userId: int,
|
||||
currentLoginTimestamp: int,
|
||||
currentLoginResult,
|
||||
dataVersion="1.40.09",
|
||||
romVersion="1.41.00",
|
||||
) -> str:
|
||||
musicData = generateMusicData()
|
||||
userAllPatches = {
|
||||
"upsertUserAll": {
|
||||
"userData": [{
|
||||
"lastRomVersion": romVersion,
|
||||
"lastDataVersion": dataVersion,
|
||||
"playerRating": 114514
|
||||
}],
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1" #1避免覆盖
|
||||
}}
|
||||
result = implFullPlayAction(userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches)
|
||||
"upsertUserAll": {
|
||||
"userData": [
|
||||
{
|
||||
"lastRomVersion": romVersion,
|
||||
"lastDataVersion": dataVersion,
|
||||
"playerRating": 114514,
|
||||
}
|
||||
],
|
||||
"userMusicDetailList": [musicData],
|
||||
"isNewMusicDetailList": "1", # 1避免覆盖
|
||||
}
|
||||
}
|
||||
result = implFullPlayAction(
|
||||
userId, currentLoginTimestamp, currentLoginResult, musicData, userAllPatches
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
userId = testUid8
|
||||
currentLoginTimestamp = generateTimestamp()
|
||||
loginResult = apiLogin(currentLoginTimestamp, userId)
|
||||
|
||||
if loginResult['returnCode'] != 1:
|
||||
if loginResult["returnCode"] != 1:
|
||||
logger.info("登录失败")
|
||||
exit()
|
||||
try:
|
||||
logger.info(implChangeVersionNumber(userId, currentLoginTimestamp, loginResult, "1.00.00", "1.00.00"))
|
||||
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")
|
||||
# logger.warning("Error")
|
||||
|
||||
15
pyproject.toml
Normal file
15
pyproject.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "maimaidx-api"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"datetime>=5.5",
|
||||
"httpx>=0.28.1",
|
||||
"loguru>=0.7.3",
|
||||
"pycryptodome>=3.23.0",
|
||||
"python-rapidjson>=1.21",
|
||||
"pytz>=2025.2",
|
||||
"requests>=2.32.4",
|
||||
]
|
||||
@@ -1,7 +0,0 @@
|
||||
pycryptodome
|
||||
requests
|
||||
httpx
|
||||
python-rapidjson
|
||||
pytz
|
||||
loguru
|
||||
datetime
|
||||
317
uv.lock
generated
Normal file
317
uv.lock
generated
Normal file
@@ -0,0 +1,317 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.7.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "datetime"
|
||||
version = "5.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytz" },
|
||||
{ name = "zope-interface" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/66/e284b9978fede35185e5d18fb3ae855b8f573d8c90a56de5f6d03e8ef99e/DateTime-5.5.tar.gz", hash = "sha256:21ec6331f87a7fcb57bd7c59e8a68bfffe6fcbf5acdbbc7b356d6a9a020191d3", size = 63671, upload-time = "2024-03-21T07:26:50.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/78/8e382b8cb4346119e2e04270b6eb4a01c5ee70b47a8a0244ecdb157204f7/DateTime-5.5-py3-none-any.whl", hash = "sha256:0abf6c51cb4ba7cee775ca46ccc727f3afdde463be28dbbe8803631fefd4a120", size = 52649, upload-time = "2024-03-21T07:26:47.849Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maimaidx-api"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "datetime" },
|
||||
{ name = "httpx" },
|
||||
{ name = "loguru" },
|
||||
{ name = "pycryptodome" },
|
||||
{ name = "python-rapidjson" },
|
||||
{ name = "pytz" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "datetime", specifier = ">=5.5" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "loguru", specifier = ">=0.7.3" },
|
||||
{ name = "pycryptodome", specifier = ">=3.23.0" },
|
||||
{ name = "python-rapidjson", specifier = ">=1.21" },
|
||||
{ name = "pytz", specifier = ">=2025.2" },
|
||||
{ name = "requests", specifier = ">=2.32.4" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.23.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-rapidjson"
|
||||
version = "1.21"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/da/f041c85e7852ddb87dd59e34fdbfebf27defc6e4dfecbc0486ab30553dfd/python_rapidjson-1.21.tar.gz", hash = "sha256:4d0dd9cf1fcb6f4bf79ee606e6e9be4cfa598f273b91338a6974b6a99309a1e6", size = 238903, upload-time = "2025-07-10T06:30:43.413Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/c8/52735f227a9c662df8e4292af2b13960f0351795b0115f9fe95b3bfcfe62/python_rapidjson-1.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:354ac34828a242eec1896901fabe59d14adab7a5054d09a8bdaed4c6ee510b22", size = 220850, upload-time = "2025-07-10T07:19:00.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/83/f02aa1c9ea2594f05dd8c6c51e2ecea398a462118c7f4ddc004a135096f0/python_rapidjson-1.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ddd846b095fad357e4e4c5071e874f9c100c2e5b9765abcc7d71254820251c20", size = 210311, upload-time = "2025-07-10T07:19:02.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/60/b6a4026fd144fe80db1d6f7cb78191ed2f27fb46c69c719b3b63a8542072/python_rapidjson-1.21-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7faf4211354c1db9b21934d723f3c7db1d611ec3013458dc2fe1a732aa100d3e", size = 1679351, upload-time = "2025-07-10T07:19:04.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/7e/73ca21eb527fbe27b267ea04b9047fdfb4a524b2ce2565a082d84cdee7e9/python_rapidjson-1.21-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46ea95e9292260fd8174966eca05fad08b43ca0f5df1ccfab9796edb0868d8eb", size = 1722281, upload-time = "2025-07-10T07:19:06.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/9c/23da2d95b0b4de46695b57a593763ddb8e7f8a786c2793be89fc3dad957e/python_rapidjson-1.21-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1cac86399cfbaf24e72aef6eed1114180a72ba0242c7153f89d874a81fb83c6", size = 1719283, upload-time = "2025-07-10T07:19:07.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/c1/52de9e4c137ccc98116c16835d9a708436d58ba51ba448e238ce08938d5b/python_rapidjson-1.21-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a4afbf54eb60dc56d4c2b6fac5d24709e8a5928baaeff0669c00950961c7a65", size = 2530062, upload-time = "2025-07-10T07:19:09.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/16/08d24136b5895157f1fdc75669796283d5116336c0b1c27fa73de260980f/python_rapidjson-1.21-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e8d4d1285c697325ad1b5300866176d190625bbdd85dce963300fad5bfaf166", size = 2635763, upload-time = "2025-07-10T07:19:10.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/a3/08aecff00e26e5dbb8cc8e5b2372b6f9ada28fcf09623e6de62f08ed2b99/python_rapidjson-1.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1525778bc9b9cee9b8f3058e0435f0e4ff03e2d8c3883893ebcaf58680316964", size = 2635419, upload-time = "2025-07-10T07:19:12.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/21/bd4e0cda3eada4d99562c1b5bcd2cb54588df17d63ab95b8b76af3d93826/python_rapidjson-1.21-cp312-cp312-win32.whl", hash = "sha256:b5717ddb9788ca00e0f97546cbdd3cfd0c25e52ab3bfed0951c7898e7f37cc60", size = 128528, upload-time = "2025-07-10T07:19:14.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/91/70f9083930de93e7ce9ccebd8bd598a5737829452c03c258ea862146bbfa/python_rapidjson-1.21-cp312-cp312-win_amd64.whl", hash = "sha256:409704e52ad25df661265dbcb3a512a2146c98f72f362adc157e9b595704e1be", size = 148421, upload-time = "2025-07-10T07:19:16.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/82/9f1ffd805d6c4b2329046f88b59eec077ea5b2a1894ec23f8fcf22f53e0f/python_rapidjson-1.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:863208f50b848b8e10aefcf0da2a67ce38a1044a2ebf83d6355f16f0c7865d4f", size = 220853, upload-time = "2025-07-10T07:19:17.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/d8/9de439a33cef3d1a7672028abcc1a3c8d0c9982f03a73b9cc71055325ee3/python_rapidjson-1.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:608ade2fff2788821f25e70af4b1bfbf9ab2b2dc8ad47c91616935c5d31e3b72", size = 210314, upload-time = "2025-07-10T07:19:19.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/93/dbee2d13a1619747652b8c8a921cb8a2e7309c0a142867b4954341c76632/python_rapidjson-1.21-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf6377c4e9636e20b403776585903d1ff52613a419c5f8453032146f0422634", size = 1679359, upload-time = "2025-07-10T07:19:21.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/56/06535489e561f6c2a035a5fa8d3d2ecc97c647fb4e1625a8cb7a1db46e6b/python_rapidjson-1.21-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:74cba817cd9b16a3a54e7ddb3b59980c338c9a28900124502215dcba5db5a276", size = 1722183, upload-time = "2025-07-10T07:19:23.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/0b/7c27a2473edfb9d40eb65e5a225dc2e833a5e355f228502aa19c2bdf6679/python_rapidjson-1.21-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3194bde069c3f03f3c425de7037cca37d337b6c7ac30f42cd2f17e15f1c4da3a", size = 1718857, upload-time = "2025-07-10T07:19:24.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/0a/0db35ac49212505c347cdc353ddebd2b4085bfc02090baae552e7b5908b4/python_rapidjson-1.21-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b663932dc7548567028fb735bf4092b50dc373822dc9756a9ade6513541013de", size = 2530213, upload-time = "2025-07-10T07:19:26.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/6c/4972fb83f0419654d3083e0b4881f607cbf0829d64da43b7d2a3a21480a0/python_rapidjson-1.21-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fcb094ecca3d892f1dbd318251579ef66e2b2f06a2a24414ad71c31931d7ffff", size = 2636100, upload-time = "2025-07-10T07:19:27.744Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/08/da452b2bd67f88b8232e48f2341f3320ce9315f0bdfed1ca769b0b389acf/python_rapidjson-1.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f4cdb4f77b08b7ae121b7ca27ed2d57dcce17f8cd9f1d4593c954c982aba18ba", size = 2635274, upload-time = "2025-07-10T07:19:29.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/2b/b81e89c01ec25078a2326051b691443c90797da0d21e1b6c862128de5238/python_rapidjson-1.21-cp313-cp313-win32.whl", hash = "sha256:be63c0ef87bf26059ee77f5de21d84a1be3659cb6baa8484c237ffe17a8beb56", size = 128530, upload-time = "2025-07-10T07:19:30.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/03/e92189d6142860f949b9679c915a6a3752ccc2345a5f80a3995233c45f9f/python_rapidjson-1.21-cp313-cp313-win_amd64.whl", hash = "sha256:38307a2ef4fddbdc18806a796201fe668c30e60de7cd795943a3f7fd39535c8b", size = 148424, upload-time = "2025-07-10T07:19:32.212Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "80.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "win32-setctime"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zope-interface"
|
||||
version = "7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/0b/c7516bc3bad144c2496f355e35bd699443b82e9437aa02d9867653203b4a/zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7", size = 208959, upload-time = "2024-11-28T08:47:47.788Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/e9/1463036df1f78ff8c45a02642a7bf6931ae4a38a4acd6a8e07c128e387a7/zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465", size = 209357, upload-time = "2024-11-28T08:47:50.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/a8/106ca4c2add440728e382f1b16c7d886563602487bdd90004788d45eb310/zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89", size = 264235, upload-time = "2024-11-28T09:18:15.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ca/57286866285f4b8a4634c12ca1957c24bdac06eae28fd4a3a578e30cf906/zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54", size = 259253, upload-time = "2024-11-28T08:48:29.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/08/2103587ebc989b455cf05e858e7fbdfeedfc3373358320e9c513428290b1/zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d", size = 264702, upload-time = "2024-11-28T08:48:37.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/c7/3c67562e03b3752ba4ab6b23355f15a58ac2d023a6ef763caaca430f91f2/zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5", size = 212466, upload-time = "2024-11-28T08:49:14.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/3b/e309d731712c1a1866d61b5356a069dd44e5b01e394b6cb49848fa2efbff/zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98", size = 208961, upload-time = "2024-11-28T08:48:29.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/65/78e7cebca6be07c8fc4032bfbb123e500d60efdf7b86727bb8a071992108/zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d", size = 209356, upload-time = "2024-11-28T08:48:33.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/b1/627384b745310d082d29e3695db5f5a9188186676912c14b61a78bbc6afe/zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c", size = 264196, upload-time = "2024-11-28T09:18:17.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/f6/54548df6dc73e30ac6c8a7ff1da73ac9007ba38f866397091d5a82237bd3/zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398", size = 259237, upload-time = "2024-11-28T08:48:31.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/66/ac05b741c2129fdf668b85631d2268421c5cd1a9ff99be1674371139d665/zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b", size = 264696, upload-time = "2024-11-28T08:48:41.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/2f/1bccc6f4cc882662162a1158cda1a7f616add2ffe322b28c99cb031b4ffc/zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd", size = 212472, upload-time = "2024-11-28T08:49:56.587Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user