Initial restarted commit

This commit is contained in:
Remik1r3n 2025-01-23 18:52:09 +08:00
commit 83da4636ac
18 changed files with 1753 additions and 0 deletions

176
.gitignore vendored Normal file
View File

@ -0,0 +1,176 @@
# Anti-leak
Private_Static_Settings.py
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# PyPI configuration file
.pypirc
Private_Static_Settings.py
Private_Static_Settings.py

110
API_AimeDB.py Normal file
View File

@ -0,0 +1,110 @@
import hashlib
import time
import requests
import json
import re
# 计算 SHA256
def compute_sha256(input_str):
"""SHA256计算"""
return hashlib.sha256(input_str.encode('utf-8')).hexdigest().upper()
# 生成时间戳
def get_timestamp():
"""SEGA格式的 YYMMDDHHMMSS 时间戳sb玩意"""
return time.strftime("%y%m%d%H%M%S", time.localtime())
# 计算认证 key
def calculate_auth_key(time_stamp: str, chip_id: str, auth_key_param: str) -> str:
"""计算 Key"""
return hashlib.sha256((chip_id + time_stamp + auth_key_param).encode("utf-8")).hexdigest().upper()
def apiAimeDB(qr_code, chip_id, auth_key_param, game_id, api_url):
"""AimeDB API 实现"""
# 生成一个时间戳
time_stamp = get_timestamp()
# 使用时间戳计算 key
auth_key = calculate_auth_key(time_stamp, chip_id, auth_key_param)
# 构造请求数据
payload = {
"chipID": chip_id,
"openGameID": game_id,
"key": auth_key,
"qrCode": qr_code,
"timestamp": time_stamp
}
# 输出准备好的请求数据
print("Payload:", json.dumps(payload, separators=(',', ':')))
# 发送 POST 请求
headers = {
"Connection": "Keep-Alive",
"Host": api_url.split("//")[-1].split("/")[0],
"User-Agent": "WC_AIME_LIB",
"Content-Type": "application/json",
}
response = requests.post(api_url, data=json.dumps(payload, separators=(',', ':')), headers=headers)
# 返回服务器的响应
return response
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 #有效字符
):
return False
else:
return True
def implAimeDB(qrcode_content_full:str) -> str:
'''
Aime DB 的请求的参考实现
提供完整 QRCode 内容返回响应的字符串Json格式
'''
CHIP_ID = "A63E-01E68606624"
AUTH_KEY_PARAM = "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"
GAME_ID = "MAID"
API_URL = "http://ai.sys-allnet.cn/wc_aime/api/get_data"
qr_code_final = qrcode_content_full[20:]
# 发送请求
response = apiAimeDB(qr_code_final, CHIP_ID, AUTH_KEY_PARAM, GAME_ID, API_URL)
# 获得结果
print("implAimeDB: StatusCode is ", response.status_code)
print("implAimeDB: Response Body is:", response.text)
return(response.text)
def implGetUID(qr_content:str) -> dict:
'''
包装后的 UID 扫码器实现
此函数会返回 AimeDB 传回的 Json 转成 Python 字典的结果
主要特点是添加了几个新的错误码(6000x)用来应对程序的错误
'''
# 检查格式
if not isSGWCFormat(qr_content):
return {'errorID': 60001} # 二维码内容明显无效
# 发送请求并处理响应
try:
result_string = implAimeDB(qr_content)
result_dict = json.loads(result_string)
except:
return {'errorID': 60002} # 无法解码 Response 的内容
# 返回结果
return result_dict
if __name__ == "__main__":
userInputQR = input("QRCode: ")
print(implAimeDB(userInputQR))

170
API_TitleServer.py Normal file
View File

@ -0,0 +1,170 @@
# 舞萌DX 2024
# 标题服务器通讯实现
import zlib
import hashlib
import requests
from loguru import logger
import random
import time
from ctypes import c_int32
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Static_Settings import *
# 舞萌DX 2024
AesKey = "n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@"
AesIV = ";;KjR1C3hgB1ovXa"
ObfuscateParam = "BEs2D5vW"
class WahlapServerBoomedError(Exception):
pass
class Request500Error(Exception):
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.mode = AES.MODE_CBC
def encrypt(self, content):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
content_padding = self.pkcs7padding(content)
encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
return encrypt_bytes
def decrypt(self, content):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return cipher.decrypt(content)
def pkcs7unpadding(self, text):
length = len(text)
unpadding = ord(text[length - 1])
return text[0:length - unpadding]
def pkcs7padding(self, text):
bs = 16
length = len(text)
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 SDGBApiHash(api):
return hashlib.md5((api+"MaimaiChn"+ObfuscateParam).encode()).hexdigest()
def apiSDGB(data:str, useApi, agentExtraData, maxRetries=5):
'''
舞萌DX 2024 API 通讯用函数
:param data: 请求数据
:param useApi: 使用的 API
:param agentExtraData: UA 附加信息机台相关则为狗号如A63E01E9564用户相关则为 UID
:param maxRetry: 最大重试次数, 默认为 3
'''
# 历史遗留代码有时候会传入 int故先全部转 str
agentExtra = str(agentExtraData)
# 编码好请求,准备发送
aes = aes_pkcs7(AesKey,AesIV)
data = data
data_enc = aes.encrypt(data)
data_def = zlib.compress(data_enc)
requests.packages.urllib3.disable_warnings()
endpoint = "https://maimai-gm.wahlap.com:42081/Maimai2Servlet/"
logger.debug("TitleServer Request Start: "+ str(useApi)+" , Data: "+str(data))
retries = 0
while retries < maxRetries:
try:
# 发送请求
responseRaw = requests.post(endpoint + SDGBApiHash(useApi), headers={
"User-Agent": f"{SDGBApiHash(useApi)}#{agentExtra}",
"Content-Type": "application/json",
"Mai-Encoding": "1.40",
"Accept-Encoding": "",
"Charset": "UTF-8",
"Content-Encoding": "deflate",
"Expect": "100-continue"
}, data=data_def, verify=False)
logger.debug("TitleServer Request Sent.")
logger.debug("TitleServer Response Code: " + str(responseRaw.status_code))
# 如果是 404 或 500直接抛出异常不再继续重试
match responseRaw.status_code:
case 200:
logger.debug("Request 200 OK!")
case 404:
logger.error(f"Request 404! ")
raise NotImplementedError
case 500:
logger.error(f"Request Failed! 500!!!! ")
raise Request500Error
case _:
logger.error(f"Request Failed! {responseRaw.status_code}")
raise NotImplementedError
responseContent = responseRaw.content
# 尝试解压请求
try:
responseDecompressed = zlib.decompress(responseContent)
logger.debug("Successfully decompressed response.")
except zlib.error as e:
logger.warning(f"RAW Response: {responseContent}")
logger.warning(f"Wahlap Server Boomed! Will now retry.{e}")
retries += 1
time.sleep(4) # 休眠4秒后重试
continue
# 解压成功,解密请求并返回
resultResponse = unpad(aes.decrypt(responseDecompressed), 16).decode()
logger.info("TitleServer Response OK!")
logger.debug("TitleServer Response: " + str(resultResponse))
return resultResponse
# 除了 404 和 500 之外的错误重试
except Request500Error:
raise Request500Error("500请求格式错误")
except NotImplementedError:
raise NotImplementedError("请求未知错误")
except Exception as e:
logger.warning(f"Request Failed! Will now retry.. {e}")
retries += 1
time.sleep(4)
else:
# 重试次数用尽WahlapServerBoomedError
raise WahlapServerBoomedError("重试多次仍然不能成功请求")
def calcSpecialNumber():
'''使用 c_int32 实现的 SpecialNumber 算法'''
rng = random.SystemRandom()
num2 = rng.randint(1, 1037933) * 2069
num2 += 0x400
num2 = c_int32(num2).value
result = c_int32(0)
for _ in range(32):
result.value <<= 1
result.value += num2 & 1
num2 >>= 1
return c_int32(result.value).value
def calcSpecialNumber2():
'''实验性替代 SpecialNumber 算法'''
max = 1037933
num2 = random.randint(1, max) * 2069
num2 += 1024 # specialnum
num3 = 0
for i in range(0, 32):
num3 <<= 1
num3 += num2 % 2
num2 >>= 1
return num3

View File

@ -0,0 +1,80 @@
# 删除成绩
import json
from loguru import logger
from Static_Settings import *
from API_TitleServer import apiSDGB, calcSpecialNumber, WahlapServerBoomedError, Request500Error
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
from HelperGetUserThing import implGetUser_
from HelperUploadUserPlayLog import apiUploadUserPlaylog
from HelperUserAll import generateFullUserAll
def implDeleteMusicRecord(musicToBeDeleted: int, diffLevelId:int, userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
'''
删除成绩的实现
需要在外部先登录并传入登录结果
'''
# 上传上去的歌曲记录
musicDataToBeUploaded = ({
"musicId": musicToBeDeleted,
"level": diffLevelId,
"playCount": 1,
"achievement": 0,
"comboStatus": 0,
"syncStatus": 0,
"deluxscoreMax": 0,
"scoreRank": 0,
"extNum1": 0
})
# 取得 UserData
currentUserData = implGetUser_("Data", userId)
currentUserData2 = currentUserData['userData']
# 构建并上传一个游玩记录
currentUploadUserPlaylogApiResult = apiUploadUserPlaylog(userId, musicDataToBeUploaded, currentUserData2, currentLoginResult['loginId'])
logger.debug(f"上传 UserPlayLog 结果: {currentUploadUserPlaylogApiResult}")
# 构建并上传 UserAll
retries = 0
while retries < 3:
currentSpecialNumber = calcSpecialNumber()
currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber)
currentUserAll['upsertUserAll']["userMusicDetailList"] = [musicDataToBeUploaded]
currentUserAll['upsertUserAll']['isNewMusicDetailList'] = "0" #0为编辑 即可删除掉成绩
data = json.dumps(currentUserAll)
try:
currentUserAllResult = json.loads(apiSDGB(data, "UpsertUserAllApi", userId))
except Request500Error:
logger.warning("500 Error Triggered. Rebuilding data.")
retries += 1
continue
except Exception:
raise WahlapServerBoomedError("邪门错误")
break
else: # 重试次数超过3次
raise Request500Error("多次尝试后仍无法成功上传 UserAll")
logger.info("删除成绩:结果:"+ str(currentUserAllResult))
return currentUserAllResult
if __name__ == "__main__":
userId = testUid
currentLoginTimestamp = generateTimestamp()
loginResult = apiLogin(currentLoginTimestamp, userId)
musicId = 11548 #229 is guruguru wash
levelId = 3 #3 is MASTER
if loginResult['returnCode'] != 1:
logger.info("登录失败")
exit()
try:
logger.info(implDeleteMusicRecord(musicId, levelId, userId, currentLoginTimestamp, loginResult))
logger.info(apiLogout(currentLoginTimestamp, userId))
except:
logger.info(apiLogout(currentLoginTimestamp, userId))
logger.warning("Error")

177
ActionLoginBonus.py Normal file
View File

@ -0,0 +1,177 @@
# ログインボーナス!やったね!
# セガ秘 内部使用のみ(トレードマーク)
import json
from loguru import logger
from Static_Settings import *
from API_TitleServer import apiSDGB, calcSpecialNumber, WahlapServerBoomedError, Request500Error
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
from HelperGetUserThing import implGetUser_
from HelperUploadUserPlayLog import apiUploadUserPlaylog
from HelperUserAll import generateFullUserAll
def implLoginBonus(userId: int, currentLoginTimestamp:int, currentLoginResult, bonusGenerateMode=2):
'''
ログインボーナスデータをアップロードする
'''
musicDataToBeUploaded = {
"musicId": 229, #ぐるぐるWASH
"level": 0,
"playCount": 2,
"achievement": 0,
"comboStatus": 0,
"syncStatus": 0,
"deluxscoreMax": 0,
"scoreRank": 0,
"extNum1": 0
}
# UserData を取得
currentUserData = implGetUser_("Data", userId)
currentUserData2 = currentUserData['userData']
# UserPlayLog を構築してアップロード
currentUploadUserPlaylogApiResult = apiUploadUserPlaylog(userId, musicDataToBeUploaded, currentUserData2, currentLoginResult['loginId'])
logger.debug(f"上传 UserPlayLog 结果: {currentUploadUserPlaylogApiResult}")
# ログインボーナスリストを生成
finalBonusList = generateLoginBonusList(userId,bonusGenerateMode)
if not finalBonusList:
return False
# UserAllを構築してアップロード
retries = 0
while retries < 3:
currentSpecialNumber = calcSpecialNumber()
currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber)
currentUserAll['upsertUserAll']["userMusicDetailList"] = [musicDataToBeUploaded]
currentUserAll['upsertUserAll']['userLoginBonusList'] = finalBonusList
currentUserAll['upsertUserAll']['isNewMusicDetailList'] = "1"
currentUserAll['upsertUserAll']['isNewLoginBonusList'] = "0" * len(finalBonusList)
data = json.dumps(currentUserAll)
try:
currentUserAllResult = json.loads(apiSDGB(data, "UpsertUserAllApi", userId))
except Request500Error:
logger.warning("500 Error Triggered. Rebuilding data.")
retries += 1
continue
except Exception:
raise WahlapServerBoomedError("邪门错误")
break
else: # 重试次数超过3次
raise Request500Error("多次尝试后仍无法成功上传 UserAll")
###### Debug 機能:ログインボーナスをもう一度取得(確認用)
apiLogout(currentLoginTimestamp, userId)
apiLogin(currentLoginTimestamp, userId)
data = json.dumps({
"userId": int(userId),
"nextIndex": 0,
"maxCount": 2000
})
logger.debug(json.loads(apiSDGB(data, "GetUserLoginBonusApi", userId)))
###### PRODUCTION 用には、上記のコードを削除してください
return currentUserAllResult
def generateLoginBonusList(userId, generateMode=1):
'''
ログインボーナスリストを生成します
generateMode ログインボーナスを生成する方法を指定します
1: 全部 MAX にする
2: いま選択したボーナスのみ MAX にする選択したボーナスはないの場合は False を返す
'''
# ログインボーナスの MAX POINT は5の場合があります
# その全部のボーナスIDをこのリストに追加してください
Bonus5Id = [12, 29, 30, 38, 43, 604]
# HDDから、ログインボーナスデータを読み込む
# アップデートがある場合、このファイルを更新する必要があります
# 必ず最新のデータを使用してください!
with open('loginBonus.json') as file:
cache = json.load(file)
loginBonusIdList = [item['id'] for item in cache]
logger.debug(f"ログインボーナスIDリスト: {loginBonusIdList}")
# サーバーからUserのログインボーナスデータを取得
data = json.dumps({
"userId": int(userId),
"nextIndex": 0,
"maxCount": 2000
})
UserLoginBonusResponse = json.loads(apiSDGB(data, "GetUserLoginBonusApi", userId))
UserLoginBonusList = UserLoginBonusResponse['userLoginBonusList']
# UserBonusList から bonusId を取得
UserLoginBonusIdList = [item['bonusId'] for item in UserLoginBonusList]
# 存在しないボーナス
NonExistingBonuses = list(set(loginBonusIdList) - set(UserLoginBonusIdList))
logger.debug(f"存在しないボーナス: {NonExistingBonuses}")
bonusList = []
if generateMode == 1: # AllMax Mode
# 存在しているボーナスを追加
for item in UserLoginBonusList:
if not item['isComplete']:
data = {
"bonusId": item['bonusId'],
"point": 4 if item['bonusId'] in Bonus5Id else 9,
"isCurrent": item['isCurrent'],
"isComplete": False
}
bonusList.append(data)
elif item['bonusId'] == 999:
data = {
"bonusId": 999,
"point": (item['point'] // 10) * 10 + 9,
"isCurrent": item['isCurrent'],
"isComplete": False
}
bonusList.append(data)
# 存在しないボーナスを追加
for bonusId in NonExistingBonuses:
data = {
"bonusId": bonusId,
"point": 4 if bonusId in Bonus5Id else 9,
"isCurrent": False,
"isComplete": False
}
bonusList.append(data)
elif generateMode == 2: # OnlyCurrent Mode
for item in UserLoginBonusList:
if item['isCurrent'] and not item['isComplete']:
point = 4 if item['bonusId'] in Bonus5Id else 9
data = {
"bonusId": item['bonusId'],
"point": point,
"isCurrent": True,
"isComplete": False
}
bonusList.append(data)
if len(bonusList) == 0:
logger.warning("このユーザーはログインボーナスを選択していませんから失敗")
return False
logger.debug(f"ログインボーナスリスト: {bonusList}")
return bonusList
if __name__ == "__main__":
userId = testUid
currentLoginTimestamp = generateTimestamp()
loginResult = apiLogin(currentLoginTimestamp, userId)
if loginResult['returnCode'] != 1:
logger.info("登录失败")
exit()
try:
logger.info(implLoginBonus(userId, currentLoginTimestamp, loginResult))
logger.info(apiLogout(currentLoginTimestamp, userId))
except:
logger.info(apiLogout(currentLoginTimestamp, userId))
logger.warning("Error")

61
ActionUnlockVarious.py Normal file
View File

@ -0,0 +1,61 @@
# 解锁一些东西的外部代码
from loguru import logger
from Static_Settings import *
from HelperLogInOut import apiLogin, apiLogout, generateTimestamp
from HelperUnlockThing import implUnlockThing
def implUnlockPartner(partnerToBeUnlocked: int, userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
'''
解锁搭档
'''
userItemList = [
{
"itemKind": 10,
"itemId": partnerToBeUnlocked,
"stock": 1,
"isValid": True
}
],
unlockThingResult = implUnlockThing(userItemList, userId, currentLoginTimestamp, currentLoginResult)
return unlockThingResult
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
},
],
unlockThingResult = implUnlockThing(userItemList, userId, currentLoginTimestamp, currentLoginResult)
return unlockThingResult
if __name__ == "__main__":
userId = testUid
currentLoginTimestamp = generateTimestamp()
loginResult = apiLogin(currentLoginTimestamp, userId)
wantToUnlockItemId = 1
if loginResult['returnCode'] != 1:
logger.info("登录失败")
exit()
try:
# change it
logger.info(implUnlockMusic(wantToUnlockItemId, userId, currentLoginTimestamp, loginResult))
logger.info(apiLogout(currentLoginTimestamp, userId))
except:
logger.info(apiLogout(currentLoginTimestamp, userId))
logger.warning("Error")

62
ChargeTicket.py Normal file
View File

@ -0,0 +1,62 @@
# 倍票相关 API 的实现
import json
import pytz
from datetime import datetime, timedelta
from Static_Settings import *
from API_TitleServer import apiSDGB
from HelperGetUserThing import apiGetUserData
def apiQueryTicket(userId:int) -> str:
'''查询已有票的 API 请求器,返回 Json String。'''
# 构建 Payload
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 的请求器'''
# 构造请求数据 Payload
data = json.dumps({
"userId": userId,
"userCharge": {
"chargeId": ticketType,
"stock": 1,
"purchaseDate": (datetime.now(pytz.timezone('Asia/Shanghai')) - timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S.0"),
"validDate": (datetime.now(pytz.timezone('Asia/Shanghai')) - timedelta(hours=1) + timedelta(days=90)).replace(hour=4, minute=0, second=0).strftime("%Y-%m-%d %H:%M:%S")
},
"userChargelog": {
"chargeId": ticketType,
"price": price,
"purchaseDate": (datetime.now(pytz.timezone('Asia/Shanghai')) - timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S.0"),
"playcount": playCount,
"playerRating": playerRating,
"placeId": placeId,
"regionId": regionId,
"clientId": clientId
}
})
# 发送请求,返回最终得到的 Json String 回执
return apiSDGB(data, "UpsertUserChargelogApi", userId)
def implBuyTicket(userId:int, ticketType:int) -> str:
'''
购买倍票 API 的参考实现
需要事先登录.
返回服务器响应的 Json string
'''
# 先使用 GetUserData API 请求器,取得 rating 和 pc 数
currentUserData = json.loads(apiGetUserData(userId))
if currentUserData:
playerRating = currentUserData['userData']['playerRating']
playCount = currentUserData['userData'].get('playCount', 0)
else:
return False
# 正式买票
getTicketResponseStr = apiBuyTicket(userId, ticketType, ticketType-1, playerRating, playCount)
# 返回结果
return getTicketResponseStr

69
DecryptHDD.py Normal file
View File

@ -0,0 +1,69 @@
# 解密从 HDD 抓包得到的数据
# 兼容 PRiSM 和 CN 2024
# 仅用于分析
import base64
import zlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad
# 密钥和 IV
# CN 2024
aesKey2024 = "n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@"
aesIV2024 = ";;KjR1C3hgB1ovXa"
# 国际服 PRiSM
aesKeyPrism = "A;mv5YUpHBK3YxTy5KB^[;5]C2AL50Bq"
aesIVPrism = "9FM:sd9xA91X14v]"
class AESPKCS7:
def __init__(self, key: str, iv: str):
self.key = key.encode('utf-8')
self.iv = iv.encode('utf-8')
self.mode = AES.MODE_CBC
def encrypt(self, content: bytes) -> bytes:
cipher = AES.new(self.key, self.mode, self.iv)
content_padded = pad(content, 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)
decrypted_padded = cipher.decrypt(encrypted_content)
decrypted = unpad(decrypted_padded, AES.block_size)
return decrypted
def main_sdga():
# 填入你的想解密的数据的 base64 编码
base64_encoded_data = "KSGm2qo7qVHz1wrK15PckYC5/kLjKcTtEXOgHeHt1Xn6DPdo3pltoPLADHpe8+Wq"
aes = AESPKCS7(aesKeyPrism, aesIVPrism)
# 首先解码 base64
decodedData = base64.b64decode(base64_encoded_data)
# 然后解密数据PRiSM 是先压缩再加密which is 正确做法)
decryptedData = aes.decrypt(decodedData)
# 解压数据
decompressedData = zlib.decompress(decryptedData)
print(str(decompressedData))
def main_sdgb():
# 填入你的想解密的数据的 base64 编码
base64_encoded_data = "eJyrTVvpuGwCR32OdodwtVXZ7/Ofmfhin7k/K61q3XNoad1rAPGwECU="
aes = AESPKCS7(aesKey2024, aesIV2024)
# 首先解码 base64
decodedData = base64.b64decode(base64_encoded_data)
# 然后解压数据CN 2024 是加密后再压缩(纯傻逼
decompressedData = zlib.decompress(decodedData)
# 最后解密数据
decryptedData = aes.decrypt(decompressedData)
print(str(decryptedData))
if __name__ == "__main__":
main_sdga()

16
GetPreview.py Normal file
View File

@ -0,0 +1,16 @@
# 获取用户简略预览数据的 API 实现,此 API 无需任何登录即可调取
import json
from API_TitleServer import apiSDGB
def apiGetUserPreview(userId) -> str:
data = json.dumps({
"userId": int(userId)
})
preview_result = apiSDGB(data, "GetUserPreviewApi", userId)
return preview_result
# CLI 示例
if __name__ == "__main__":
userId = input("请输入用户 ID")
print(apiGetUserPreview(userId))

36
HelperGetUserThing.py Normal file
View File

@ -0,0 +1,36 @@
# 获取用户数据的 API 实现
from loguru import logger
import json
from API_TitleServer import apiSDGB
def apiGetUserData(userId:int) -> str:
'''已弃用,将逐步淘汰'''
logger.warning("apiGetUserData 已弃用,将逐步淘汰。")
# 构建 Payload
data = json.dumps({
"userId": userId
})
# 发送请求
userdata_result = apiSDGB(data, "GetUserDataApi", userId)
# 返回响应
return userdata_result
def apiGetUserThing(userId:int, thing:str) -> str:
'''获取用户数据的 API 请求器,返回 Json String'''
# 构建 Payload
data = json.dumps({
"userId": userId
})
# 发送请求
userthing_result = apiSDGB(data, "GetUser" + thing + "Api", userId)
# 返回响应
return userthing_result
def implGetUser_(thing:str, userId:int) -> dict:
'''获取用户数据的 API 实现,返回 Dict'''
# 获取 Json String
userthing_result = apiGetUserThing(userId, thing)
# 转换为 Dict
userthing_dict = json.loads(userthing_result)
# 返回 Dict
return userthing_dict

53
HelperLogInOut.py Normal file
View File

@ -0,0 +1,53 @@
# 登录·登出实现
# 一般作为模块使用,但也可以作为 CLI 程序运行以强制登出账号。
import json
import time
from loguru import logger
from Static_Settings import *
from API_TitleServer import apiSDGB
def apiLogin(timestamp:int, userId:int) -> dict:
'''登录,返回服务器给的 Json 的 dict'''
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))
logger.info("登录:结果:"+ str(login_result))
return login_result
def apiLogout(timestamp:int, userId:int) -> dict:
'''登出,返回 Json dict'''
data = json.dumps({
"userId": userId,
"accessCode": "",
"regionId": regionId,
"placeId": placeId,
"clientId": clientId,
"dateTime": timestamp,
"type": 1
})
logout_result = json.loads(apiSDGB(data, "UserLogoutApi", userId))
logger.info("登出:结果:"+ str(logout_result))
return logout_result
def generateTimestamp() -> int:
'''生成时间戳'''
timestamp = int(time.time()) - 60
logger.info(f"生成时间戳: {timestamp}")
return timestamp
if __name__ == "__main__":
print("强制登出 CLI")
uid = testUid
timestamp = input("Timestamp: ")
apiLogout(int(timestamp), int(uid))

73
HelperUnlockThing.py Normal file
View File

@ -0,0 +1,73 @@
# 解锁东西的一个通用的助手,不可独立使用
import json
from loguru import logger
from Static_Settings import *
from API_TitleServer import apiSDGB, calcSpecialNumber, WahlapServerBoomedError, Request500Error
from HelperGetUserThing import implGetUser_
from HelperUploadUserPlayLog import apiUploadUserPlaylog
from HelperUserAll import generateFullUserAll
def implUnlockThing(newUserItemList:list, userId: int, currentLoginTimestamp:int, currentLoginResult) -> str:
'''
解锁东西的实现
Note: itemKind 如下
PLATE = 1 # 姓名框
TITLE = 2 # 称号
ICON = 3 # 头像
PRESENT = 4
MUSIC = 5 # 乐曲
MUSIC_MASTER = 6
MUSIC_RE_MASTER = 7
MUSIC_STRONG = 8
CHARACTER = 9 # 旅行伙伴
PARTNER = 10 # 搭档
FRAME = 11 # 背景板
TICKET = 12 # 功能票
'''
# 上传上去的歌曲记录
musicDataToBeUploaded = ({
"musicId": 229, #洗衣机
"level": 0,
"playCount": 2,
"achievement": 0,
"comboStatus": 0,
"syncStatus": 0,
"deluxscoreMax": 0,
"scoreRank": 0,
"extNum1": 0
})
# UserData を取得
currentUserData = implGetUser_("Data", userId)
currentUserData2 = currentUserData['userData']
# UserPlayLog を構築してアップロード
currentUploadUserPlaylogApiResult = apiUploadUserPlaylog(userId, musicDataToBeUploaded, currentUserData2, currentLoginResult['loginId'])
logger.debug(f"上传 UserPlayLog 结果: {currentUploadUserPlaylogApiResult}")
# UserAllを構築してアップロード
retries = 0
while retries < 3:
currentSpecialNumber = calcSpecialNumber()
currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber)
currentUserAll['upsertUserAll']["userMusicDetailList"] = [musicDataToBeUploaded]
currentUserAll['upsertUserAll']['isNewMusicDetailList'] = "1" # Insert mode(Not overriding)
currentUserAll['upsertUserAll']['userItemList'] = newUserItemList
data = json.dumps(currentUserAll)
try:
currentUserAllResult = json.loads(apiSDGB(data, "UpsertUserAllApi", userId))
except Request500Error:
logger.warning("500 Error Triggered. Rebuilding data.")
retries += 1
continue
except Exception:
raise WahlapServerBoomedError("邪门错误")
break
else: # 重试次数超过3次
raise Request500Error("多次尝试后仍无法成功上传 UserAll")
logger.info("解锁东西:结果:"+ str(currentUserAllResult))
return currentUserAllResult

146
HelperUploadUserPlayLog.py Normal file
View File

@ -0,0 +1,146 @@
# 上传一个占位用的游玩记录的 API 实现
import json
import pytz
import time
import random
from datetime import datetime
from loguru import logger
from API_TitleServer import apiSDGB
from Static_Settings import *
def apiUploadUserPlaylog(userId:int, musicDataToBeUploaded, currentUserData2, loginId:int) -> str:
'''返回 Json String。'''
# 暂存,优化可读性(迫真)
musicId = musicDataToBeUploaded['musicId']
level = musicDataToBeUploaded['level']
#playCount = musicDataToBeUploaded['playCount']
achievement = musicDataToBeUploaded['achievement']
#comboStatus = musicDataToBeUploaded['comboStatus']
#syncStatus = musicDataToBeUploaded['syncStatus']
deluxscoreMax = musicDataToBeUploaded['deluxscoreMax']
scoreRank = musicDataToBeUploaded['scoreRank']
#extNum1 = musicDataToBeUploaded['extNum1']
# 构建一个 PlayLog
data = json.dumps({
"userId": int(userId),
"userPlaylog": {
"userId": 0,
"orderId": 0,
"playlogId": loginId,
"version": 1041000,
"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(musicId),
"level": int(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(achievement),
"deluxscore": int(deluxscoreMax),
"scoreRank": int(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
}
})
# 发送请求
result = apiSDGB(data, "UploadUserPlaylogApi", userId)
logger.info("上传游玩记录:结果:"+ str(result))
# 返回响应
return result

175
HelperUserAll.py Normal file
View File

@ -0,0 +1,175 @@
# UserAll 有关的一些辅助函数
import pytz
from datetime import datetime
from Static_Settings import *
from HelperGetUserThing import implGetUser_
def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber):
'''从服务器取得必要的数据并构建一个比较完整的 UserAll'''
# 先构建一个基础 UserAll
currentUserAll = generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber)
# 然后从服务器取得必要的数据
currentUserExtend = implGetUser_("Extend", userId)
currentUserOption = implGetUser_("Option", userId)
currentUserRating = implGetUser_("Rating", userId)
currentUserActivity = implGetUser_("Activity", userId)
currentUserCharge = implGetUser_("Charge", userId)
# 把这些数据都追加进去
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']
# 完事
return currentUserAll
def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber):
'''构建一个非常基础的 UserAll 数据,必须手动填充一些数据'''
data = {
"userId": userId,
"playlogId": currentLoginResult['loginId'],
"isEventMode": False,
"isFreePlay": False,
"upsertUserAll": {
"userData": [
{
"accessCode": "",
"userName": currentUserData2['userName'],
"isNetMember": 1,
"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'],
"lastGameId": "SDGB",
"lastRomVersion": currentUserData2['lastRomVersion'],
"lastDataVersion": currentUserData2['lastDataVersion'],
"lastLoginDate": currentLoginResult['lastLoginDate'], # sb
"lastPlayDate": datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S') + '.0',
"lastPlayCredit": 1,
"lastPlayMode": 0,
"lastPlaceId": placeId,
"lastPlaceName": placeName,
"lastAllNetId": 0,
"lastRegionId": regionId,
"lastRegionName": regionName,
"lastClientId": clientId,
"lastCountryCode": "CHN",
"lastSelectEMoney": 0,
"lastSelectTicket": 0,
"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'],
"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'],
"banState": 0,
"dateTime": currentLoginTimestamp
}
],
"userExtend": [], #需要填上
"userOption": [], #需要填上
"userGhost": [],
"userCharacterList": [],
"userMapList": [],
"userLoginBonusList": [],
"userRatingList": [], #需要填上
"userItemList": [], #可选,但经常要填上
"userMusicDetailList": [],#需要填上
"userCourseList": [],
"userFriendSeasonRankingList": [],
"userChargeList": [], #需要填上
"userFavoriteList": [],
"userActivityList": [], #需要填上
"userGamePlaylogList": [
{
"playlogId": currentLoginResult['loginId'],
"version": "1.41.00",
"playDate": datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S') + '.0',
"playMode": 0,
"useTicketId": -1,
"playCredit": 1,
"playTrack": 1,
"clientId": clientId,
"isPlayTutorial": False,
"isEventMode": False,
"isNewFree": False,
"playCount": currentUserData2['playCount'],
"playSpecial": currentSpecialNumber,
"playOtherUserId": 0
}
],
"user2pPlaylog": {
"userId1": 0,
"userId2": 0,
"userName1": "",
"userName2": "",
"regionId": 0,
"placeId": 0,
"user2pPlaylogDetailList": []
},
"isNewCharacterList": "",
"isNewMapList": "",
"isNewLoginBonusList": "",
"isNewItemList": "",
"isNewMusicDetailList": "", #可选但经常要填上
"isNewCourseList": "0",
"isNewFavoriteList": "",
"isNewFriendSeasonRankingList": ""
}
}
return data

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# genshin-impact
Genshin Impact Helpers and Tools

8
Static_Settings.py Normal file
View File

@ -0,0 +1,8 @@
regionId = 22
regionName = "山东"
placeId = 3490
placeName = "赛博时空枣庄市中店"
clientId = "A63E01E9564"
# 日本精工,安全防漏
from Private_Static_Settings import *

97
UI.py Normal file
View File

@ -0,0 +1,97 @@
import sys
import json
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QLineEdit, QTextEdit, QPushButton, QLabel, QHBoxLayout
)
from PyQt6.QtCore import Qt
from API_TitleServer import *
def sendRequest(requestText:str, apiNameText:str, uid:int) -> str:
try:
data = json.loads(requestText)
data = json.dumps(data)
except:
return "给出的输入不是有效的 JSON"
result = apiSDGB(data, apiNameText, uid)
return result
class ApiTester(QMainWindow):
def __init__(self):
super().__init__()
# 主窗口设定
self.setWindowTitle("舞萌DX 2024 API 测试器")
self.resize(640, 400)
# 布局
mainWidget = QWidget()
self.setCentralWidget(mainWidget)
MainLayout = QVBoxLayout(mainWidget)
# 目标 API 输入框布局
TargetAPILayout = QHBoxLayout()
# API 输入框
self.TargetAPIInputBox = QLineEdit()
self.TargetAPIInputBox.setPlaceholderText("指定 API")
TargetAPILayout.addWidget(self.TargetAPIInputBox)
# API 后缀标签
TargetAPILabel = QLabel("MaimaiChn")
TargetAPILayout.addWidget(TargetAPILabel)
# 添加到主布局
MainLayout.addLayout(TargetAPILayout)
# UA额外信息输入框
self.AgentExtraInputBox = QLineEdit()
self.AgentExtraInputBox.setPlaceholderText("指定附加信息(UID或狗号)")
MainLayout.addWidget(self.AgentExtraInputBox)
# 请求输入框
self.RequestInputBox = QTextEdit()
self.RequestInputBox.setPlaceholderText("此处填入请求")
MainLayout.addWidget(self.RequestInputBox)
# 发送按钮
SendRequestButton = QPushButton("发送!")
SendRequestButton.clicked.connect(self.prepareRequest)
MainLayout.addWidget(SendRequestButton)
# 响应输出框
self.ResponseTextBox = QTextEdit()
self.ResponseTextBox.setPlaceholderText("此处显示输出")
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:
self.ResponseTextBox.setPlainText("输入无效")
return
Result = sendRequest(RequestDataString, TargetAPIString, AgentExtraString)
# 显示出输出
self.ResponseTextBox.setPlainText(Result)
if __name__ == "__main__":
app = QApplication(sys.argv)
# Set proper style for each OS
#if sys.platform == "win32":
# app.setStyle("windowsvista")
#else:
# app.setStyle("Fusion")
window = ApiTester()
window.show()
sys.exit(app.exec())

242
loginBonus.json Normal file
View File

@ -0,0 +1,242 @@
[
{
"id": 38,
"name": "パートナー:ずんだもん"
},
{
"id": 39,
"name": "パートナー:乙姫(ばでぃーず)"
},
{
"id": 40,
"name": "パートナー:らいむっくま&れもんっくま(ばでぃーず)"
},
{
"id": 34,
"name": "パートナー:黒姫"
},
{
"id": 24,
"name": "パートナー:ラズ(ふぇすてぃばる)"
},
{
"id": 25,
"name": "パートナー:シフォン(ふぇすてぃばる)"
},
{
"id": 26,
"name": "パートナー:ソルト(ふぇすてぃばる)"
},
{
"id": 19,
"name": "パートナー:ちびみるく"
},
{
"id": 20,
"name": "パートナー:百合咲ミカ"
},
{
"id": 8,
"name": "パートナー:しゃま(ゆにばーす)"
},
{
"id": 9,
"name": "パートナー:みるく(ゆにばーす)"
},
{
"id": 7,
"name": "パートナー:乙姫(すぷらっしゅ)"
},
{
"id": 1,
"name": "パートナー:乙姫"
},
{
"id": 2,
"name": "パートナー:ラズ"
},
{
"id": 3,
"name": "パートナー:シフォン"
},
{
"id": 4,
"name": "パートナー:ソルト"
},
{
"id": 5,
"name": "パートナー:しゃま"
},
{
"id": 6,
"name": "パートナー:みるく"
},
{
"id": 605,
"name": "でらっくす譜面oboro"
},
{
"id": 606,
"name": "でらっくす譜面:ナミダと流星"
},
{
"id": 607,
"name": "スタンダード譜面:渦状銀河のシンフォニエッタ"
},
{
"id": 508,
"name": "でらっくす譜面LatentKingdom"
},
{
"id": 41,
"name": "でらっくす譜面:初音ミクの消失"
},
{
"id": 42,
"name": "でらっくす譜面:色は匂へど散りぬるを"
},
{
"id": 601,
"name": "でらっくす譜面BULKUP(GAMEEXCLUSIVEEDIT)"
},
{
"id": 602,
"name": "でらっくす譜面MonochromeRainbow"
},
{
"id": 603,
"name": "でらっくす譜面Selector"
},
{
"id": 35,
"name": "でらっくす譜面:深海少女"
},
{
"id": 36,
"name": "でらっくす譜面:ナイト・オブ・ナイツ"
},
{
"id": 27,
"name": "でらっくす譜面M.S.S.Planet"
},
{
"id": 28,
"name": "でらっくす譜面:響縁"
},
{
"id": 501,
"name": "スタンダード譜面Halcyon"
},
{
"id": 502,
"name": "スタンダード譜面:サンバランド"
},
{
"id": 503,
"name": "でらっくす譜面StarlightDisco"
},
{
"id": 504,
"name": "でらっくす譜面:火炎地獄"
},
{
"id": 505,
"name": "スタンダード譜面VIIIbitExplorer"
},
{
"id": 506,
"name": "でらっくす譜面Maxi"
},
{
"id": 507,
"name": "でらっくす譜面ケロ⑨destiny"
},
{
"id": 21,
"name": "でらっくす譜面:セツナトリップ"
},
{
"id": 22,
"name": "でらっくす譜面Grip&amp;Breakdown!!"
},
{
"id": 17,
"name": "でらっくす譜面:ゴーストルール"
},
{
"id": 18,
"name": "でらっくす譜面tabootearsyouup"
},
{
"id": 43,
"name": "アイコンBUDDiES"
},
{
"id": 604,
"name": "アイコンFESTiVALラズシフォンソルト"
},
{
"id": 29,
"name": "アイコンFESTiVAL"
},
{
"id": 30,
"name": "アイコンLia=Fail"
},
{
"id": 12,
"name": "アイコンUNiVERSE"
},
{
"id": 14,
"name": "ネームプレート:はっぴー(ゆにばーす)"
},
{
"id": 44,
"name": "フレームmystiqueasiris"
},
{
"id": 45,
"name": "フレームVeRForTeαRtE:VEiN"
},
{
"id": 37,
"name": "フレームTricolor⁂circuS"
},
{
"id": 31,
"name": "フレームHeavenlyBlast"
},
{
"id": 32,
"name": "フレームsølips"
},
{
"id": 33,
"name": "フレームRainbowRushStory"
},
{
"id": 23,
"name": "フレーム:ふたりでばかんすにゃ♪"
},
{
"id": 15,
"name": "フレーム:ここからはじまるプロローグ。"
},
{
"id": 16,
"name": "フレーム:モ゜ルモ゜ル"
},
{
"id": 10,
"name": "フレーム:黒姫"
},
{
"id": 11,
"name": "フレーム:百合咲ミカ"
},
{
"id": 999,
"name": "ちほー進行1.5倍チケット"
}
]