8 Commits

Author SHA1 Message Date
069e9aeae9 refactor: patcher + fullplay workflow 2026-07-07 17:15:15 +08:00
07e816bc83 feat: unlock music 2026-07-07 17:01:22 +08:00
d2b4e01d72 fix: use HTTPS for PowerOn 2026-07-07 16:14:48 +08:00
1fba026324 chore: delete the non-functional ticket script 2026-07-07 14:05:50 +08:00
b1b9c7fc10 chore: move payload scripts to action/ 2026-07-07 14:05:23 +08:00
9a94fe8b4e chore: do not qr login twice times 2026-07-07 13:51:11 +08:00
a3c8426786 refactor: split up modules 2026-07-07 13:48:39 +08:00
17c419dca0 refactor: update to sdgb 1.55 2026-07-07 13:38:06 +08:00
19 changed files with 4745 additions and 4362 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
**/__pycache__ **/__pycache__
**/settings.py **/settings.py
**/.DS_Store **/.DS_Store
*.egg-info

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

View File

@@ -1,23 +1,29 @@
import asyncio import asyncio
from chime import * import httpx
from sdgb import MaimaiClient from sdgb import MaimaiClient
from settings import * from sdgb.chime import qr_api
from sdgb.settings import clientId, qrCode
maimai = MaimaiClient() maimai = MaimaiClient()
userId = qr_api(qrCode)['userID'] qrResp = qr_api(qrCode)
token = qr_api(qrCode)['token'] userId = qrResp["userID"]
token = qrResp["token"]
async def run_workflow(maimai):
async def run_workflow(maimai: MaimaiClient):
async with httpx.AsyncClient(verify=False) as client: async with httpx.AsyncClient(verify=False) as client:
data = { data = {
"userId": userId, "userId": userId,
"segaIdAuthKey": "", "segaIdAuthKey": "",
"token": token, "token": token,
"clientId": clientId "clientId": clientId,
} }
result = await maimai.call_api(client, "GetUserPreviewApi", data, userId) result = await maimai.call_api(client, "GetUserPreviewApi", data, userId)
print(result)
# 执行入口 # 执行入口
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,46 +1,45 @@
from urllib.parse import unquote
import httpx import httpx
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import pad from Crypto.Util.Padding import pad
from urllib.parse import unquote
def enc(key, iv, data): def enc(key, iv, data):
cipher = AES.new(key, AES.MODE_CBC, iv) cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(data) encrypted = cipher.encrypt(data)
return encrypted return encrypted
def dec(key, iv, data): def dec(key, iv, data):
de_cipher = AES.new(key, AES.MODE_CBC, iv) de_cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = de_cipher.decrypt(data) decrypted = de_cipher.decrypt(data)
return decrypted return decrypted
def hello(): def hello():
key = bytes([47, 63, 106, 111, 43, 34, 76, 38, 92, 67, 114, 57, 40, 61, 107, 71]) key = bytes([47, 63, 106, 111, 43, 34, 76, 38, 92, 67, 114, 57, 40, 61, 107, 71])
# key = bytes([ 45, 97, 53, 55, 85, 88, 52, 121, 57, 47, 104, 40, 73, 109, 65, 81 ]) # key = bytes([ 45, 97, 53, 55, 85, 88, 52, 121, 57, 47, 104, 40, 73, 109, 65, 81 ])
iv = bytes.fromhex('00000000000000000000000000000000') iv = bytes.fromhex("00000000000000000000000000000000")
ua = 'SDGB;Windows/Lite' ua = "SDGB;Windows/Lite"
# ua = 'SDHJ;Windows/Lite' # ua = 'SDHJ;Windows/Lite'
# 构建 payload # 构建 payload
content = bytes([0] * 16) + b'title_id=SDGB&title_ver=1.52&client_id=A63E01E6149' content = bytes([0] * 16) + b"title_id=SDGB&title_ver=1.52&client_id=A63E01E6149"
print(f"Content: {content}") print(f"Content: {content}")
header = bytes.fromhex('00000000000000000000000000000000') header = bytes.fromhex("00000000000000000000000000000000")
bytes_data = pad(header + content, 16) bytes_data = pad(header + content, 16)
encrypted = enc(key, iv, bytes_data) encrypted = enc(key, iv, bytes_data)
# --- HTTPX 修改部分 --- # --- HTTPX 修改部分 ---
headers = { headers = {"User-Agent": ua, "Pragma": "DFI"}
'User-Agent': ua,
'Pragma': 'DFI'
}
try: try:
# 发送 POST 请求 # 发送 POST 请求
# urllib3 的 body 参数在 httpx 中对应 content (用于二进制数据) # urllib3 的 body 参数在 httpx 中对应 content (用于二进制数据)
r = httpx.post( r = httpx.post(
'http://at.sys-allnet.cn/net/initialize', "https://at.sys-allnet.cn/net/initialize", content=encrypted, headers=headers
content=encrypted,
headers=headers
) )
# 检查响应状态码 (可选,但在 httpx 中推荐) # 检查响应状态码 (可选,但在 httpx 中推荐)
@@ -54,7 +53,7 @@ def hello():
if len(resp_data) >= 16: if len(resp_data) >= 16:
decrypted = dec(key, resp_data[:16], resp_data) decrypted = dec(key, resp_data[:16], resp_data)
decrypted_bytes = decrypted[16:] decrypted_bytes = decrypted[16:]
decrypted_str = unquote(decrypted_bytes.decode('UTF-8'), 'utf-8') decrypted_str = unquote(decrypted_bytes.decode("UTF-8"), "utf-8")
print(f"Decrypted: {decrypted_str}") print(f"Decrypted: {decrypted_str}")
else: else:
print("Response data too short.") print("Response data too short.")
@@ -62,5 +61,6 @@ def hello():
except httpx.RequestError as e: except httpx.RequestError as e:
print(f"An error occurred while requesting: {e}") print(f"An error occurred while requesting: {e}")
if __name__ == '__main__':
if __name__ == "__main__":
hello() hello()

154
action/UnlockMusic.py Normal file
View File

@@ -0,0 +1,154 @@
import json
import asyncio
import logging
from typing import Callable
import httpx
from sdgb import MaimaiClient
from sdgb.settings import userId, musicData
from sdgb.payload import (
requestData_UserLogin,
requestData_UserLogout,
requestData_UserPreview,
requestData_UserData,
UserAll_payload,
)
maimai = MaimaiClient()
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def music_user_all_patcher(
musicId: int,
unlock_music: bool = True,
unlock_master: bool = False,
unlock_remaster: bool = False,
) -> Callable[[dict], None]:
userItemList = []
if unlock_music:
userItemList.append(
0,
{
"itemKind": 5, # MUSIC
"itemId": musicId,
"stock": 1,
"isValid": True,
},
)
if unlock_master:
userItemList.append(
0,
{
"itemKind": 6,
"itemId": musicId, # MASTER
"stock": 1,
"isValid": True,
},
)
if unlock_remaster:
userItemList.append(
{
"itemKind": 7,
"itemId": musicId, # RE: MASTER
"stock": 1,
"isValid": True,
}
)
def merge(d: dict):
d.update(
{
"userMusicDetailList": [musicData],
"isNewMusicDetailList": "1",
"userItemList": userItemList,
"isNewItemList": len(userItemList) * "1",
}
)
return merge
async def run_workflow(
self: MaimaiClient,
user_all_patcher: Callable[[dict], None],
):
async with httpx.AsyncClient(verify=False) as client:
# Preview 探测
PreviewResponse = json.loads(
await self.call_api(
client, "GetUserPreviewApi", requestData_UserPreview, userId
)
)
if PreviewResponse["isLogin"]:
logger.error("已在他处登录。")
return
# UserLogin
LoginResponse = json.loads(
await self.call_api(client, "UserLoginApi", requestData_UserLogin, userId)
)
if LoginResponse["returnCode"] != 1:
logger.error("login failed.")
return
loginId = LoginResponse["loginId"]
loginDate = LoginResponse["lastLoginDate"]
# UserData 等
tasks = [
self.call_api(client, "GetUserDataApi", requestData_UserData, userId),
self.call_api(client, "GetUserExtendApi", requestData_UserData, userId),
self.call_api(client, "GetUserOptionApi", requestData_UserData, userId),
self.call_api(client, "GetUserRatingApi", requestData_UserData, userId),
self.call_api(client, "GetUserChargeApi", requestData_UserData, userId),
self.call_api(client, "GetUserActivityApi", requestData_UserData, userId),
self.call_api(
client, "GetUserMissionDataApi", requestData_UserData, userId
),
]
GeneralUserInfo = await asyncio.gather(*tasks)
await asyncio.sleep(60) # 模拟游戏时间
# UserAll
requestData_UserAll = UserAll_payload(
loginId, loginDate, musicData, GeneralUserInfo
)
user_all_patcher(requestData_UserAll)
await self.call_api(client, "UpsertUserAllApi", requestData_UserAll, userId)
# UserLogout
await self.call_api(client, "UserLogoutApi", requestData_UserLogout, userId)
if __name__ == "__main__":
patcher = music_user_all_patcher(
musicId=834,
unlock_music=False,
# unlock_master=True,
unlock_remaster=True,
)
asyncio.run(
run_workflow(
maimai,
user_all_patcher=patcher,
)
)

View File

@@ -1,43 +1,53 @@
import json import json
import asyncio import asyncio
import httpx
import time
import logging import logging
from sdgb import MaimaiClient
from settings import userId, musicData import httpx
from chime import *
from payload import * from sdgb import MaimaiClient
from sdgb.settings import userId, musicData
from sdgb.payload import (
requestData_UserLogin,
requestData_UserLogout,
requestData_UserPreview,
requestData_UserData,
UserAll_payload,
)
maimai = MaimaiClient() maimai = MaimaiClient()
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
format='%(asctime)s - %(levelname)s - %(message)s'
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def run_workflow(self):
async def run_workflow(self: MaimaiClient):
async with httpx.AsyncClient(verify=False) as client: async with httpx.AsyncClient(verify=False) as client:
# Preview 探测 # Preview 探测
PreviewResponse = json.loads(await self.call_api(client, "GetUserPreviewApi", requestData_UserPreview, userId)) PreviewResponse = json.loads(
if PreviewResponse["isLogin"] == True: await self.call_api(
client, "GetUserPreviewApi", requestData_UserPreview, userId
)
)
if PreviewResponse["isLogin"]:
logger.error("已在他处登录。") logger.error("已在他处登录。")
return return
# UserLogin # UserLogin
LoginResponse = json.loads(await self.call_api(client, "UserLoginApi", requestData_UserLogin, userId)) LoginResponse = json.loads(
await self.call_api(client, "UserLoginApi", requestData_UserLogin, userId)
)
if LoginResponse["returnCode"] != 1: if LoginResponse["returnCode"] != 1:
logger.error("login failed.") logger.error("login failed.")
return return
loginId = LoginResponse['loginId'] loginId = LoginResponse["loginId"]
loginDate = LoginResponse['lastLoginDate'] loginDate = LoginResponse["lastLoginDate"]
# UserData 等 # UserData 等
@@ -48,7 +58,9 @@ async def run_workflow(self):
self.call_api(client, "GetUserRatingApi", requestData_UserData, userId), self.call_api(client, "GetUserRatingApi", requestData_UserData, userId),
self.call_api(client, "GetUserChargeApi", requestData_UserData, userId), self.call_api(client, "GetUserChargeApi", requestData_UserData, userId),
self.call_api(client, "GetUserActivityApi", requestData_UserData, userId), self.call_api(client, "GetUserActivityApi", requestData_UserData, userId),
self.call_api(client, "GetUserMissionDataApi", requestData_UserData, userId), self.call_api(
client, "GetUserMissionDataApi", requestData_UserData, userId
),
] ]
GeneralUserInfo = await asyncio.gather(*tasks) GeneralUserInfo = await asyncio.gather(*tasks)
@@ -56,7 +68,9 @@ async def run_workflow(self):
# UserAll # UserAll
requestData_UserAll = UserAll_payload(loginId, loginDate, musicData, GeneralUserInfo) requestData_UserAll = UserAll_payload(
loginId, loginDate, musicData, GeneralUserInfo
)
await self.call_api(client, "UpsertUserAllApi", requestData_UserAll, userId) await self.call_api(client, "UpsertUserAllApi", requestData_UserAll, userId)
@@ -64,5 +78,6 @@ async def run_workflow(self):
await self.call_api(client, "UserLogoutApi", requestData_UserLogout, userId) await self.call_api(client, "UserLogoutApi", requestData_UserLogout, userId)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(run_workflow(maimai)) asyncio.run(run_workflow(maimai))

View File

@@ -1,12 +1,19 @@
import json
import asyncio import asyncio
import httpx import httpx
from sdgb import MaimaiClient from sdgb import MaimaiClient
from settings import * from sdgb.settings import (
userId,
regionId,
placeId,
clientId,
)
maimai = MaimaiClient() maimai = MaimaiClient()
async def run_workflow(maimai):
async def run_workflow(maimai: MaimaiClient):
async with httpx.AsyncClient(verify=False) as client: async with httpx.AsyncClient(verify=False) as client:
data = { data = {
"userId": userId, "userId": userId,
@@ -15,9 +22,11 @@ async def run_workflow(maimai):
"placeId": placeId, "placeId": placeId,
"clientId": clientId, "clientId": clientId,
"dateTime": 1767000000, "dateTime": 1767000000,
"type": 4 "type": 4,
} }
await maimai.call_api(client, "UserLogoutApi", data, userId) await maimai.call_api(client, "UserLogoutApi", data, userId)
# 执行入口 # 执行入口
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(run_workflow(maimai)) asyncio.run(run_workflow(maimai))

10
pyproject.toml Normal file
View File

@@ -0,0 +1,10 @@
[project]
name = "eaquira"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = ["httpx>=0.28.1", "pycryptodome>=3.23.0", "pytz>=2026.2"]
[tool.uv]
package = true

View File

@@ -1,23 +0,0 @@
# This file contains the config. No function inside.
# DO NOT share your env to others.
userId =
musicData = ({
"musicId": 417,
"level": 3,
"playCount": 1,
"achievement": 1010000,
"comboStatus": 4,
"syncStatus": 4,
"deluxscoreMax": 2277,
"scoreRank": 13,
"extNum1": 0
})
regionId = 1
regionName = "北京"
placeId = 1403
placeName = "插电师北京王府井银泰店"
clientId = "A63E01C2805"
KeychipID = "A63E-01C28055905"

View File

@@ -1,403 +0,0 @@
import time
import pytz
import json
from datetime import datetime, timedelta
from encrypt import CalcRandom
from settings import *
from chime import *
import logging
userId = qr_api(qrCode)['userID']
token = qr_api(qrCode)['token']
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
TimeStamp = int(time.time())
requestData_UserPreview = {
"userId": userId,
"segaIdAuthKey":"",
"token": token,
"clientId": clientId
}
requestData_UserLogin = {
"userId": userId,
"accessCode": "",
"regionId": regionId,
"placeId": placeId,
"clientId": clientId,
"dateTime": TimeStamp - 600,
"loginDateTime": TimeStamp,
"isContinue": False,
"genericFlag": 0,
"token": token
}
requestData_UserData = {
"userId": userId
}
requestData_UserLogout = {
"userId": userId,
"accessCode": "",
"regionId": regionId,
"placeId": placeId,
"clientId": clientId,
"loginDateTime": TimeStamp,
"type": 1
}
def UserAll_payload(loginId: int, loginDate: str, musicData: dict, GeneralUserInfo: list):
userData = json.loads(GeneralUserInfo[0])
userExtend = json.loads(GeneralUserInfo[1])
userOption = json.loads(GeneralUserInfo[2])
userRating = json.loads(GeneralUserInfo[3])
userChargeList = json.loads(GeneralUserInfo[4])
userActivity = json.loads(GeneralUserInfo[5])
userMissionDataList = json.loads(GeneralUserInfo[6])
requestData_UserAll = {
"userId": userId,
"playlogId": loginId,
"isEventMode": False,
"isFreePlay": False,
"loginDateTime": TimeStamp,
"userPlaylogList": [
{
"userId": 0,
"orderId": 0,
"playlogId": loginId,
"version": 1053000,
"placeId": placeId,
"placeName": placeName,
"loginDate": 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": musicData['musicId'],
"level": musicData['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": userData['userData']['charaSlot'][0],
"characterLevel1": 1,
"characterAwakening1": 0,
"characterId2": userData['userData']['charaSlot'][1],
"characterLevel2": 1,
"characterAwakening2": 0,
"characterId3": userData['userData']['charaSlot'][2],
"characterLevel3": 1,
"characterAwakening3": 0,
"characterId4": userData['userData']['charaSlot'][3],
"characterLevel4": 1,
"characterAwakening4": 0,
"characterId5": userData['userData']['charaSlot'][4],
"characterLevel5": 1,
"characterAwakening5": 0,
"achievement": musicData['achievement'],
"deluxscore": musicData['deluxscoreMax'],
"scoreRank": musicData['scoreRank'],
"maxCombo": 0,
"totalCombo": 128,
"maxSync": 0,
"totalSync": 0,
"tapCriticalPerfect": 101,
"tapPerfect": 0,
"tapGreat": 0,
"tapGood": 0,
"tapMiss": 0,
"holdCriticalPerfect": 9,
"holdPerfect": 0,
"holdGreat": 0,
"holdGood": 0,
"holdMiss": 0,
"slideCriticalPerfect": 4,
"slidePerfect": 0,
"slideGreat": 0,
"slideGood": 0,
"slideMiss": 0,
"touchCriticalPerfect": 0,
"touchPerfect": 0,
"touchGreat": 0,
"touchGood": 0,
"touchMiss": 0,
"breakCriticalPerfect": 1,
"breakPerfect": 0,
"breakGreat": 0,
"breakGood": 0,
"breakMiss": 0,
"isTap": True,
"isHold": True,
"isSlide": True,
"isTouch": False,
"isBreak": True,
"isCriticalDisp": True,
"isFastLateDisp": True,
"fastCount": 0,
"lateCount": 0,
"isAchieveNewRecord": False,
"isDeluxscoreNewRecord": False,
"comboStatus": musicData['comboStatus'],
"syncStatus": musicData['syncStatus'],
"isClear": True,
"beforeRating": userData['userData']['playerRating'],
"afterRating": userData['userData']['playerRating'],
"beforeGrade": 0,
"afterGrade": 0,
"afterGradeRank": 0,
"beforeDeluxRating": userData['userData']['playerRating'],
"afterDeluxRating": userData['userData']['playerRating'],
"isPlayTutorial": False,
"isEventMode": False,
"isFreedomMode": False,
"playMode": 0,
"isNewFree": False,
"trialPlayAchievement": -1,
"extNum1": 0,
"extNum2": 0,
"extNum4": 101,
"extBool1": False,
"extBool2": False
}
],
"upsertUserAll": {
"userData": [
{
"accessCode": "",
"userName": userData['userData']['userName'],
"isNetMember": 1,
"point": userData['userData']['point'],
"totalPoint": userData['userData']['totalPoint'],
"iconId": userData['userData']['iconId'],
"plateId": userData['userData']['plateId'],
"titleId": userData['userData']['titleId'],
"partnerId": userData['userData']['partnerId'],
"frameId": userData['userData']['frameId'],
"selectMapId": userData['userData']['selectMapId'],
"totalAwake": userData['userData']['totalAwake'],
"gradeRating": userData['userData']['gradeRating'],
"musicRating": userData['userData']['musicRating'],
"playerRating": userData['userData']['playerRating'],
"highestRating": userData['userData']['highestRating'],
"gradeRank": userData['userData']['gradeRank'],
"classRank": userData['userData']['classRank'],
"courseRank": userData['userData']['courseRank'],
"charaSlot": userData['userData']['charaSlot'],
"charaLockSlot": userData['userData']['charaLockSlot'],
"contentBit": userData['userData']['contentBit'],
"playCount": userData['userData']['playCount'] + 1,
"currentPlayCount": userData['userData']['currentPlayCount'] + 1,
"renameCredit": userData['userData']['renameCredit'],
"mapStock": userData['userData']['mapStock'],
"eventWatchedDate": userData['userData']['eventWatchedDate'],
"lastGameId": "SDGB",
"lastRomVersion": userData['userData']['lastRomVersion'],
"lastDataVersion": userData['userData']['lastDataVersion'],
"lastLoginDate": loginDate,
"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": userData['userData']['lastSelectEMoney'],
"lastSelectTicket": userData['userData']['lastSelectTicket'],
"lastSelectCourse": userData['userData']['lastSelectCourse'],
"lastCountCourse": userData['userData']['lastCountCourse'],
"firstGameId": userData['userData']['firstGameId'],
"firstRomVersion": userData['userData']['firstRomVersion'],
"firstDataVersion": userData['userData']['firstDataVersion'],
"firstPlayDate": userData['userData']['firstPlayDate'],
"compatibleCmVersion": userData['userData']['compatibleCmVersion'],
"dailyBonusDate": userData['userData']['dailyBonusDate'],
"dailyCourseBonusDate": userData['userData']['dailyCourseBonusDate'],
"lastPairLoginDate": userData['userData']['lastPairLoginDate'],
"lastTrialPlayDate": userData['userData']['lastTrialPlayDate'],
"playVsCount": userData['userData']['playVsCount'],
"playSyncCount": userData['userData']['playSyncCount'],
"winCount": userData['userData']['winCount'],
"helpCount": userData['userData']['helpCount'],
"comboCount": userData['userData']['comboCount'],
"totalDeluxscore": userData['userData']['totalDeluxscore'],
"totalBasicDeluxscore": userData['userData']['totalBasicDeluxscore'],
"totalAdvancedDeluxscore": userData['userData']['totalAdvancedDeluxscore'],
"totalExpertDeluxscore": userData['userData']['totalExpertDeluxscore'],
"totalMasterDeluxscore": userData['userData']['totalMasterDeluxscore'],
"totalReMasterDeluxscore": userData['userData']['totalReMasterDeluxscore'],
"totalSync": userData['userData']['totalSync'],
"totalBasicSync": userData['userData']['totalBasicSync'],
"totalAdvancedSync": userData['userData']['totalAdvancedSync'],
"totalExpertSync": userData['userData']['totalExpertSync'],
"totalMasterSync": userData['userData']['totalMasterSync'],
"totalReMasterSync": userData['userData']['totalReMasterSync'],
"totalAchievement": userData['userData']['totalAchievement'],
"totalBasicAchievement": userData['userData']['totalBasicAchievement'],
"totalAdvancedAchievement": userData['userData']['totalAdvancedAchievement'],
"totalExpertAchievement": userData['userData']['totalExpertAchievement'],
"totalMasterAchievement": userData['userData']['totalMasterAchievement'],
"totalReMasterAchievement": userData['userData']['totalReMasterAchievement'],
"playerOldRating": userData['userData']['playerOldRating'],
"playerNewRating": userData['userData']['playerNewRating'],
"banState": userData['banState'],
"friendRegistSkip": userData['userData']['friendRegistSkip'],
"dateTime": TimeStamp
}
],
"userExtend": [userExtend['userExtend']],
"userOption": [userOption['userOption']],
"userCharacterList": [],
"userGhost": [],
"userMapList": [],
"userLoginBonusList": [],
"userRatingList": [userRating['userRating']],
"userItemList": [],
"userMusicDetailList": [musicData],
"userCourseList": [],
"userFriendSeasonRankingList": [],
"userChargeList": userChargeList['userChargeList'],
"userFavoriteList": [
{"itemKind": 3,"itemIdList": []},
{"itemKind": 1,"itemIdList": []},
{"itemKind": 2,"itemIdList": []},
{"itemKind": 10,"itemIdList": []},
{"itemKind": 11,"itemIdList": []}
],
"userActivityList": [userActivity['userActivity']],
"userMissionDataList": [
{
"type": userMissionDataList['userMissionDataList'][0]['type'],
"difficulty": userMissionDataList['userMissionDataList'][0]['difficulty'],
"targetGenreId": userMissionDataList['userMissionDataList'][0]['targetGenreId'],
"targetGenreTableId": userMissionDataList['userMissionDataList'][0]['targetGenreTableId'],
"conditionGenreId": userMissionDataList['userMissionDataList'][0]['conditionGenreId'],
"conditionGenreTableId": userMissionDataList['userMissionDataList'][0]['conditionGenreTableId'],
"clearFlag": userMissionDataList['userMissionDataList'][0]['clearFlag']
},
{
"type": userMissionDataList['userMissionDataList'][1]['type'],
"difficulty": userMissionDataList['userMissionDataList'][1]['difficulty'],
"targetGenreId": userMissionDataList['userMissionDataList'][1]['targetGenreId'],
"targetGenreTableId": userMissionDataList['userMissionDataList'][1]['targetGenreTableId'],
"conditionGenreId": userMissionDataList['userMissionDataList'][1]['conditionGenreId'],
"conditionGenreTableId": userMissionDataList['userMissionDataList'][1]['conditionGenreTableId'],
"clearFlag": userMissionDataList['userMissionDataList'][1]['clearFlag']
},
{
"type": userMissionDataList['userMissionDataList'][2]['type'],
"difficulty": userMissionDataList['userMissionDataList'][2]['difficulty'],
"targetGenreId": userMissionDataList['userMissionDataList'][2]['targetGenreId'],
"targetGenreTableId": userMissionDataList['userMissionDataList'][2]['targetGenreTableId'],
"conditionGenreId": userMissionDataList['userMissionDataList'][2]['conditionGenreId'],
"conditionGenreTableId": userMissionDataList['userMissionDataList'][2]['conditionGenreTableId'],
"clearFlag": userMissionDataList['userMissionDataList'][2]['clearFlag']
},
{
"type": userMissionDataList['userMissionDataList'][3]['type'],
"difficulty": userMissionDataList['userMissionDataList'][3]['difficulty'],
"targetGenreId": userMissionDataList['userMissionDataList'][3]['targetGenreId'],
"targetGenreTableId": userMissionDataList['userMissionDataList'][3]['targetGenreTableId'],
"conditionGenreId": userMissionDataList['userMissionDataList'][3]['conditionGenreId'],
"conditionGenreTableId": userMissionDataList['userMissionDataList'][3]['conditionGenreTableId'],
"clearFlag": userMissionDataList['userMissionDataList'][3]['clearFlag']
},
{
"type": userMissionDataList['userMissionDataList'][4]['type'],
"difficulty": userMissionDataList['userMissionDataList'][4]['difficulty'],
"targetGenreId": userMissionDataList['userMissionDataList'][4]['targetGenreId'],
"targetGenreTableId": userMissionDataList['userMissionDataList'][4]['targetGenreTableId'],
"conditionGenreId": userMissionDataList['userMissionDataList'][4]['conditionGenreId'],
"conditionGenreTableId": userMissionDataList['userMissionDataList'][4]['conditionGenreTableId'],
"clearFlag": userMissionDataList['userMissionDataList'][4]['clearFlag']
},
{
"type": userMissionDataList['userMissionDataList'][5]['type'],
"difficulty": userMissionDataList['userMissionDataList'][5]['difficulty'],
"targetGenreId": userMissionDataList['userMissionDataList'][5]['targetGenreId'],
"targetGenreTableId": userMissionDataList['userMissionDataList'][5]['targetGenreTableId'],
"conditionGenreId": userMissionDataList['userMissionDataList'][5]['conditionGenreId'],
"conditionGenreTableId": userMissionDataList['userMissionDataList'][5]['conditionGenreTableId'],
"clearFlag": userMissionDataList['userMissionDataList'][5]['clearFlag']
}
],
"userWeeklyData": {
"lastLoginWeek": userMissionDataList['userWeeklyData']['lastLoginWeek'],
"beforeLoginWeek": userMissionDataList['userWeeklyData']['beforeLoginWeek'],
"friendBonusFlag": userMissionDataList['userWeeklyData']['friendBonusFlag']
},
"userGamePlaylogList": [
{
"playlogId": loginId,
"version": userData['userData']['lastRomVersion'],
"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": 0,
"playSpecial": CalcRandom(),
"playOtherUserId": 0
}
],
"user2pPlaylog": {
"userId1": 0,
"userId2": 0,
"userName1": "",
"userName2": "",
"regionId": 0,
"placeId": 0,
"user2pPlaylogDetailList": []
},
"userIntimateList": [],
"userShopItemStockList": [],
"userGetPointList": [],
"userTradeItemList": [],
"userFavoritemusicList": [],
"userKaleidxScopeList": [],
"isNewCharacterList": "",
"isNewMapList": "",
"isNewLoginBonusList": "",
"isNewItemList": "",
"isNewMusicDetailList": "0",
"isNewCourseList": "",
"isNewFavoriteList": "11111",
"isNewFriendSeasonRankingList": "",
"isNewUserIntimateList": "",
"isNewFavoritemusicList": "",
"isNewKaleidxScopeList": ""
}
}
logger.info(f"🫥 [INFO] userId: '{userId}', loginId: '{loginId}', loginDate: '{loginDate}', timestamp: '{TimeStamp}'")
return requestData_UserAll

View File

@@ -1,73 +0,0 @@
import json
import asyncio
import httpx
import time
import logging
from sdgb import MaimaiClient
from settings import userId, musicData
from payload import *
maimai = MaimaiClient()
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
async def run_workflow(self):
async with httpx.AsyncClient(verify=False) as client:
# Preview 探测
PreviewResponse = json.loads(await self.call_api(client, "GetUserPreviewApi", requestData_UserPreview, userId))
if PreviewResponse["isLogin"] == True:
logger.error("已在他处登录。")
return
# UserLogin
LoginResponse = json.loads(await self.call_api(client, "UserLoginApi", requestData_UserLogin, userId))
if LoginResponse["returnCode"] == 106:
logger.error("chime verfication failed.")
return
loginId = LoginResponse['loginId']
loginDate = LoginResponse['lastLoginDate']
# UserData 等
tasks = [
self.call_api(client, "GetUserDataApi", requestData_UserData, userId),
self.call_api(client, "GetUserExtendApi", requestData_UserData, userId),
self.call_api(client, "GetUserOptionApi", requestData_UserData, userId),
self.call_api(client, "GetUserRatingApi", requestData_UserData, userId),
self.call_api(client, "GetUserChargeApi", requestData_UserData, userId),
self.call_api(client, "GetUserActivityApi", requestData_UserData, userId),
self.call_api(client, "GetUserMissionDataApi", requestData_UserData, userId),
]
GeneralUserInfo = await asyncio.gather(*tasks)
time.sleep(60) # 模拟游戏时间
# UserPlaylog
requestData_UserPlaylog = UserPlaylog_payload(loginId, musicData, GeneralUserInfo[0])
await self.call_api(client, "UploadUserPlaylogListApi", requestData_UserPlaylog, userId)
# Userall
requestData_Userall = UserAll_payload(loginId, loginDate, musicData, GeneralUserInfo)
await self.call_api(client, "UpsertUserAllApi", requestData_Userall, userId)
# UserLogout
await self.call_api(client, "UserLogoutApi", requestData_UserLogout, userId)
if __name__ == "__main__":
asyncio.run(run_workflow(maimai))

25
src/sdgb/.settings.py Normal file
View File

@@ -0,0 +1,25 @@
# This file contains the config. No function inside.
# DO NOT share your env to others.
userId = ...
qrCode = ...
musicData = {
"musicId": 11538, # Amber Chronicle
"level": 0,
"playCount": 1,
"achievement": 0,
"comboStatus": 0,
"syncStatus": 0,
"deluxscoreMax": 0,
"scoreRank": 0,
"extNum1": 0,
}
regionId = 1
regionName = "北京"
placeId = 1403
placeName = "插电师北京王府井银泰店"
clientId = "A63E01C2805"
KeychipID = "A63E-01C28055905"

View File

@@ -1,23 +1,31 @@
import asyncio
import httpx
import json import json
import logging import logging
import time import zlib
from encrypt import *
import httpx
from sdgb.encrypt import (
AesKey,
AesIV,
aes_pkcs7,
get_hash_api,
)
# 配置日志,方便调试看请求顺序 # 配置日志,方便调试看请求顺序
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
format='%(asctime)s - %(levelname)s - %(message)s'
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class MaimaiClient: class MaimaiClient:
def __init__(self): def __init__(self):
self.base_url = f"https://maimai-gm.wahlap.com:42081/Maimai2Servlet/" self.base_url = "https://maimai-gm.wahlap.com:42081/Maimai2Servlet/"
self.aes = aes_pkcs7(AesKey, AesIV) self.aes = aes_pkcs7(AesKey, AesIV)
async def call_api(self, client: httpx.AsyncClient, ApiType: str, data: dict, userId: int): async def call_api(
self, client: httpx.AsyncClient, ApiType: str, data: dict, userId: int
):
""" """
先压缩再加密请求数据发送请求后解密再解压响应数据 先压缩再加密请求数据发送请求后解密再解压响应数据
这里的 client 需要传入外部创建的 httpx.AsyncClient 实例 这里的 client 需要传入外部创建的 httpx.AsyncClient 实例
@@ -29,11 +37,11 @@ class MaimaiClient:
headers = { headers = {
"User-Agent": f"{ApiTypeHash}#{userId}", "User-Agent": f"{ApiTypeHash}#{userId}",
"Content-Type": "application/json", "Content-Type": "application/json",
"Mai-Encoding": "1.53", "Mai-Encoding": "1.55",
"Accept-Encoding": "", "Accept-Encoding": "",
"Charset": "UTF-8", "Charset": "UTF-8",
"Content-Encoding": "deflate", "Content-Encoding": "deflate",
"Host": "maimai-gm.wahlap.com:42081" "Host": "maimai-gm.wahlap.com:42081",
} }
data = bytes(json.dumps(data), encoding="utf-8") data = bytes(json.dumps(data), encoding="utf-8")
@@ -42,18 +50,19 @@ class MaimaiClient:
try: try:
# 这里的 timeout 设置稍微长一点,防止服务端处理慢 # 这里的 timeout 设置稍微长一点,防止服务端处理慢
resp = await client.post(url, headers=headers, data=AESEncrptedData, timeout=10.0) resp = await client.post(
url, headers=headers, data=AESEncrptedData, timeout=10.0
)
resp.raise_for_status() # 如果状态码不是 2xx 则抛出异常 resp.raise_for_status() # 如果状态码不是 2xx 则抛出异常
AESEncrptedResponse = resp.content AESEncrptedResponse = resp.content
DecryptedData = self.aes.decrypt(AESEncrptedResponse) DecryptedData = self.aes.decrypt(AESEncrptedResponse)
UncompressedData = zlib.decompress(DecryptedData).decode('utf-8') UncompressedData = zlib.decompress(DecryptedData).decode("utf-8")
logger.info(f"✅ [SUCCESS] {ApiType} - {UncompressedData}") logger.info(f"✅ [SUCCESS] {ApiType} - {UncompressedData}")
return UncompressedData return UncompressedData
except httpx.HTTPStatusError as e: except httpx.HTTPStatusError as e:
logger.error(f"❌ [HTTP ERROR] {ApiType}: {e.response.status_code}") logger.error(f"❌ [HTTP ERROR] {ApiType}: {e.response.status_code}")
return None return None

View File

@@ -1,32 +1,42 @@
import hashlib import hashlib
import httpx
import pytz
import json import json
from datetime import datetime from datetime import datetime
from settings import KeychipID
import httpx
import pytz
from sdgb.settings import KeychipID
def qr_api(qr_code: str): def qr_api(qr_code: str):
if len(qr_code) > 64: if len(qr_code) > 64:
qr_code = qr_code[-64:] qr_code = qr_code[-64:]
time_stamp = datetime.now(pytz.timezone('Asia/Tokyo')).strftime("%y%m%d%H%M%S") time_stamp = datetime.now(pytz.timezone("Asia/Tokyo")).strftime("%y%m%d%H%M%S")
auth_key = hashlib.sha256( auth_key = (
(KeychipID + time_stamp + "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW").encode("UTF-8")).hexdigest().upper() hashlib.sha256(
(KeychipID + time_stamp + "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW").encode(
"UTF-8"
)
)
.hexdigest()
.upper()
)
param = { param = {
"chipID": KeychipID, "chipID": KeychipID,
"openGameID": "MAID", "openGameID": "MAID",
"key": auth_key, "key": auth_key,
"qrCode": qr_code, "qrCode": qr_code,
"timestamp": time_stamp "timestamp": time_stamp,
} }
headers = { headers = {
"Contention": "Keep-Alive", "Contention": "Keep-Alive",
"Host": "ai.sys-all.cn", "Host": "ai.sys-all.cn",
"User-Agent": "WC_AIME_LIB" "User-Agent": "WC_AIME_LIB",
} }
res = httpx.post( res = httpx.post(
"http://ai.sys-allnet.cn/wc_aime/api/get_data", "http://ai.sys-allnet.cn/wc_aime/api/get_data",
data = json.dumps(param, separators=(',', ':')), data=json.dumps(param, separators=(",", ":")),
headers = headers headers=headers,
) )
assert res.status_code == 200, "网络错误" assert res.status_code == 200, "网络错误"
return json.loads(res.content) return json.loads(res.content)

View File

@@ -1,16 +1,15 @@
import zlib
import base64
import hashlib import hashlib
import random import random
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad from Crypto.Util.Padding import pad, unpad
AesKey = "o2U8F6<adcYl25f_qwx_n]5_qxRcbLN>" # 1.55 -> 1.56 -- 舞萌 DX 2026
AesIV = "AL<G:k:X6Vu7@_U]" AesKey = "FKM2JX:VjZNK6hc:A0<JU:i5oR7LA]9W"
ObfuscateParam = "LatuAa81" AesIV = "F>;24DjU9W6ZsRH["
ObfuscateParam = "8bF76dE9"
# 1.50 -> 1.52 -- 舞萌 DX 2025 # 1.50 -> 1.53 -- 舞萌 DX 2025
# AesKey = "a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R" # AesKey = "a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R"
# AesIV = "d6xHIKq]1J]Dt^ue" # AesIV = "d6xHIKq]1J]Dt^ue"
# ObfuscateParam = "B44df8yT" # ObfuscateParam = "B44df8yT"
@@ -20,10 +19,11 @@ ObfuscateParam = "LatuAa81"
# AesIV = ";;KjR1C3hgB1ovXa" # AesIV = ";;KjR1C3hgB1ovXa"
# ObfuscateParam = "BEs2D5vW" # ObfuscateParam = "BEs2D5vW"
class aes_pkcs7(object): class aes_pkcs7(object):
def __init__(self, key: str, iv: str): def __init__(self, key: str, iv: str):
self.key = key.encode('utf-8') self.key = key.encode("utf-8")
self.iv = iv.encode('utf-8') self.iv = iv.encode("utf-8")
self.mode = AES.MODE_CBC self.mode = AES.MODE_CBC
def encrypt(self, content: bytes) -> bytes: def encrypt(self, content: bytes) -> bytes:
@@ -46,15 +46,17 @@ class aes_pkcs7(object):
def pkcs7padding(self, text): def pkcs7padding(self, text):
bs = 16 bs = 16
length = len(text) 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_size = length if (bytes_length == length) else bytes_length
padding = bs - padding_size % bs padding = bs - padding_size % bs
padding_text = chr(padding) * padding padding_text = chr(padding) * padding
return text + padding_text return text + padding_text
def get_hash_api(api): def get_hash_api(api):
return hashlib.md5((api + "MaimaiChn" + ObfuscateParam).encode()).hexdigest() return hashlib.md5((api + "MaimaiChn" + ObfuscateParam).encode()).hexdigest()
def CalcRandom(): def CalcRandom():
max = 1037933 max = 1037933
num2 = random.randint(1, max) * 2069 num2 = random.randint(1, max) * 2069

View File

@@ -1,46 +1,45 @@
from urllib.parse import unquote
import httpx import httpx
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import pad from Crypto.Util.Padding import pad
from urllib.parse import unquote
def enc(key, iv, data): def enc(key, iv, data):
cipher = AES.new(key, AES.MODE_CBC, iv) cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(data) encrypted = cipher.encrypt(data)
return encrypted return encrypted
def dec(key, iv, data): def dec(key, iv, data):
de_cipher = AES.new(key, AES.MODE_CBC, iv) de_cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = de_cipher.decrypt(data) decrypted = de_cipher.decrypt(data)
return decrypted return decrypted
def hello(): def hello():
key = bytes([47, 63, 106, 111, 43, 34, 76, 38, 92, 67, 114, 57, 40, 61, 107, 71]) key = bytes([47, 63, 106, 111, 43, 34, 76, 38, 92, 67, 114, 57, 40, 61, 107, 71])
# key = bytes([ 45, 97, 53, 55, 85, 88, 52, 121, 57, 47, 104, 40, 73, 109, 65, 81 ]) # key = bytes([ 45, 97, 53, 55, 85, 88, 52, 121, 57, 47, 104, 40, 73, 109, 65, 81 ])
iv = bytes.fromhex('00000000000000000000000000000000') iv = bytes.fromhex("00000000000000000000000000000000")
ua = 'SDGB;Windows/Lite' ua = "SDGB;Windows/Lite"
# ua = 'SDHJ;Windows/Lite' # ua = 'SDHJ;Windows/Lite'
# 构建 payload # 构建 payload
content = bytes([0] * 16) + b'title_id=SDGB&title_ver=1.52&client_id=A63E01E6149' content = bytes([0] * 16) + b"title_id=SDGB&title_ver=1.52&client_id=A63E01E6149"
print(f"Content: {content}") print(f"Content: {content}")
header = bytes.fromhex('00000000000000000000000000000000') header = bytes.fromhex("00000000000000000000000000000000")
bytes_data = pad(header + content, 16) bytes_data = pad(header + content, 16)
encrypted = enc(key, iv, bytes_data) encrypted = enc(key, iv, bytes_data)
# --- HTTPX 修改部分 --- # --- HTTPX 修改部分 ---
headers = { headers = {"User-Agent": ua, "Pragma": "DFI"}
'User-Agent': ua,
'Pragma': 'DFI'
}
try: try:
# 发送 POST 请求 # 发送 POST 请求
# urllib3 的 body 参数在 httpx 中对应 content (用于二进制数据) # urllib3 的 body 参数在 httpx 中对应 content (用于二进制数据)
r = httpx.post( r = httpx.post(
'http://at.sys-allnet.cn/net/initialize', "http://at.sys-allnet.cn/net/initialize", content=encrypted, headers=headers
content=encrypted,
headers=headers
) )
# 检查响应状态码 (可选,但在 httpx 中推荐) # 检查响应状态码 (可选,但在 httpx 中推荐)
@@ -54,7 +53,7 @@ def hello():
if len(resp_data) >= 16: if len(resp_data) >= 16:
decrypted = dec(key, resp_data[:16], resp_data) decrypted = dec(key, resp_data[:16], resp_data)
decrypted_bytes = decrypted[16:] decrypted_bytes = decrypted[16:]
decrypted_str = unquote(decrypted_bytes.decode('UTF-8'), 'utf-8') decrypted_str = unquote(decrypted_bytes.decode("UTF-8"), "utf-8")
print(f"Decrypted: {decrypted_str}") print(f"Decrypted: {decrypted_str}")
else: else:
print("Response data too short.") print("Response data too short.")
@@ -62,5 +61,6 @@ def hello():
except httpx.RequestError as e: except httpx.RequestError as e:
print(f"An error occurred while requesting: {e}") print(f"An error occurred while requesting: {e}")
if __name__ == '__main__':
if __name__ == "__main__":
hello() hello()

524
src/sdgb/payload.py Normal file
View File

@@ -0,0 +1,524 @@
import time
import json
import logging
from datetime import datetime
import pytz
from sdgb.encrypt import CalcRandom
from sdgb.chime import qr_api
from sdgb.settings import (
clientId,
qrCode,
regionId,
regionName,
placeId,
placeName,
)
qrResp = qr_api(qrCode)
userId = qrResp["userID"]
token = qrResp["token"]
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
TimeStamp = int(time.time())
requestData_UserPreview = {
"userId": userId,
"segaIdAuthKey": "",
"token": token,
"clientId": clientId,
}
requestData_UserLogin = {
"userId": userId,
"accessCode": "",
"regionId": regionId,
"placeId": placeId,
"clientId": clientId,
"dateTime": TimeStamp - 600,
"loginDateTime": TimeStamp,
"isContinue": False,
"genericFlag": 0,
"token": token,
}
requestData_UserData = {"userId": userId}
requestData_UserLogout = {
"userId": userId,
"accessCode": "",
"regionId": regionId,
"placeId": placeId,
"clientId": clientId,
"loginDateTime": TimeStamp,
"type": 1,
}
def UserAll_payload(
loginId: int, loginDate: str, musicData: dict, GeneralUserInfo: list
):
userData = json.loads(GeneralUserInfo[0])
userExtend = json.loads(GeneralUserInfo[1])
userOption = json.loads(GeneralUserInfo[2])
userRating = json.loads(GeneralUserInfo[3])
userChargeList = json.loads(GeneralUserInfo[4])
userActivity = json.loads(GeneralUserInfo[5])
userMissionDataList = json.loads(GeneralUserInfo[6])
requestData_UserAll = {
"userId": userId,
"playlogId": loginId,
"isEventMode": False,
"isFreePlay": False,
"loginDateTime": TimeStamp,
"userPlaylogList": [
{
"userId": 0,
"orderId": 0,
"playlogId": loginId,
"version": 1053000,
"placeId": placeId,
"placeName": placeName,
"loginDate": 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": musicData["musicId"],
"level": musicData["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": userData["userData"]["charaSlot"][0],
"characterLevel1": 1,
"characterAwakening1": 0,
"characterId2": userData["userData"]["charaSlot"][1],
"characterLevel2": 1,
"characterAwakening2": 0,
"characterId3": userData["userData"]["charaSlot"][2],
"characterLevel3": 1,
"characterAwakening3": 0,
"characterId4": userData["userData"]["charaSlot"][3],
"characterLevel4": 1,
"characterAwakening4": 0,
"characterId5": userData["userData"]["charaSlot"][4],
"characterLevel5": 1,
"characterAwakening5": 0,
"achievement": musicData["achievement"],
"deluxscore": musicData["deluxscoreMax"],
"scoreRank": musicData["scoreRank"],
"maxCombo": 0,
"totalCombo": 128,
"maxSync": 0,
"totalSync": 0,
"tapCriticalPerfect": 101,
"tapPerfect": 0,
"tapGreat": 0,
"tapGood": 0,
"tapMiss": 0,
"holdCriticalPerfect": 9,
"holdPerfect": 0,
"holdGreat": 0,
"holdGood": 0,
"holdMiss": 0,
"slideCriticalPerfect": 4,
"slidePerfect": 0,
"slideGreat": 0,
"slideGood": 0,
"slideMiss": 0,
"touchCriticalPerfect": 0,
"touchPerfect": 0,
"touchGreat": 0,
"touchGood": 0,
"touchMiss": 0,
"breakCriticalPerfect": 1,
"breakPerfect": 0,
"breakGreat": 0,
"breakGood": 0,
"breakMiss": 0,
"isTap": True,
"isHold": True,
"isSlide": True,
"isTouch": False,
"isBreak": True,
"isCriticalDisp": True,
"isFastLateDisp": True,
"fastCount": 0,
"lateCount": 0,
"isAchieveNewRecord": False,
"isDeluxscoreNewRecord": False,
"comboStatus": musicData["comboStatus"],
"syncStatus": musicData["syncStatus"],
"isClear": True,
"beforeRating": userData["userData"]["playerRating"],
"afterRating": userData["userData"]["playerRating"],
"beforeGrade": 0,
"afterGrade": 0,
"afterGradeRank": 0,
"beforeDeluxRating": userData["userData"]["playerRating"],
"afterDeluxRating": userData["userData"]["playerRating"],
"isPlayTutorial": False,
"isEventMode": False,
"isFreedomMode": False,
"playMode": 0,
"isNewFree": False,
"trialPlayAchievement": -1,
"extNum1": 0,
"extNum2": 0,
"extNum4": 101,
"extBool1": False,
"extBool2": False,
}
],
"upsertUserAll": {
"userData": [
{
"accessCode": "",
"userName": userData["userData"]["userName"],
"isNetMember": 1,
"point": userData["userData"]["point"],
"totalPoint": userData["userData"]["totalPoint"],
"iconId": userData["userData"]["iconId"],
"plateId": userData["userData"]["plateId"],
"titleId": userData["userData"]["titleId"],
"partnerId": userData["userData"]["partnerId"],
"frameId": userData["userData"]["frameId"],
"selectMapId": userData["userData"]["selectMapId"],
"totalAwake": userData["userData"]["totalAwake"],
"gradeRating": userData["userData"]["gradeRating"],
"musicRating": userData["userData"]["musicRating"],
"playerRating": userData["userData"]["playerRating"],
"highestRating": userData["userData"]["highestRating"],
"gradeRank": userData["userData"]["gradeRank"],
"classRank": userData["userData"]["classRank"],
"courseRank": userData["userData"]["courseRank"],
"charaSlot": userData["userData"]["charaSlot"],
"charaLockSlot": userData["userData"]["charaLockSlot"],
"contentBit": userData["userData"]["contentBit"],
"playCount": userData["userData"]["playCount"] + 1,
"currentPlayCount": userData["userData"]["currentPlayCount"] + 1,
"renameCredit": userData["userData"]["renameCredit"],
"mapStock": userData["userData"]["mapStock"],
"eventWatchedDate": userData["userData"]["eventWatchedDate"],
"lastGameId": "SDGB",
"lastRomVersion": userData["userData"]["lastRomVersion"],
"lastDataVersion": userData["userData"]["lastDataVersion"],
"lastLoginDate": loginDate,
"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": userData["userData"]["lastSelectEMoney"],
"lastSelectTicket": userData["userData"]["lastSelectTicket"],
"lastSelectCourse": userData["userData"]["lastSelectCourse"],
"lastCountCourse": userData["userData"]["lastCountCourse"],
"firstGameId": userData["userData"]["firstGameId"],
"firstRomVersion": userData["userData"]["firstRomVersion"],
"firstDataVersion": userData["userData"]["firstDataVersion"],
"firstPlayDate": userData["userData"]["firstPlayDate"],
"compatibleCmVersion": userData["userData"]["compatibleCmVersion"],
"dailyBonusDate": userData["userData"]["dailyBonusDate"],
"dailyCourseBonusDate": userData["userData"][
"dailyCourseBonusDate"
],
"lastPairLoginDate": userData["userData"]["lastPairLoginDate"],
"lastTrialPlayDate": userData["userData"]["lastTrialPlayDate"],
"playVsCount": userData["userData"]["playVsCount"],
"playSyncCount": userData["userData"]["playSyncCount"],
"winCount": userData["userData"]["winCount"],
"helpCount": userData["userData"]["helpCount"],
"comboCount": userData["userData"]["comboCount"],
"totalDeluxscore": userData["userData"]["totalDeluxscore"],
"totalBasicDeluxscore": userData["userData"][
"totalBasicDeluxscore"
],
"totalAdvancedDeluxscore": userData["userData"][
"totalAdvancedDeluxscore"
],
"totalExpertDeluxscore": userData["userData"][
"totalExpertDeluxscore"
],
"totalMasterDeluxscore": userData["userData"][
"totalMasterDeluxscore"
],
"totalReMasterDeluxscore": userData["userData"][
"totalReMasterDeluxscore"
],
"totalSync": userData["userData"]["totalSync"],
"totalBasicSync": userData["userData"]["totalBasicSync"],
"totalAdvancedSync": userData["userData"]["totalAdvancedSync"],
"totalExpertSync": userData["userData"]["totalExpertSync"],
"totalMasterSync": userData["userData"]["totalMasterSync"],
"totalReMasterSync": userData["userData"]["totalReMasterSync"],
"totalAchievement": userData["userData"]["totalAchievement"],
"totalBasicAchievement": userData["userData"][
"totalBasicAchievement"
],
"totalAdvancedAchievement": userData["userData"][
"totalAdvancedAchievement"
],
"totalExpertAchievement": userData["userData"][
"totalExpertAchievement"
],
"totalMasterAchievement": userData["userData"][
"totalMasterAchievement"
],
"totalReMasterAchievement": userData["userData"][
"totalReMasterAchievement"
],
"playerOldRating": userData["userData"]["playerOldRating"],
"playerNewRating": userData["userData"]["playerNewRating"],
"banState": userData["banState"],
"friendRegistSkip": userData["userData"]["friendRegistSkip"],
"dateTime": TimeStamp,
}
],
"userExtend": [userExtend["userExtend"]],
"userOption": [userOption["userOption"]],
"userCharacterList": [],
"userGhost": [],
"userMapList": [],
"userLoginBonusList": [],
"userRatingList": [userRating["userRating"]],
"userItemList": [],
"userMusicDetailList": [musicData],
"userCourseList": [],
"userFriendSeasonRankingList": [],
"userChargeList": userChargeList["userChargeList"],
"userFavoriteList": [
{"itemKind": 3, "itemIdList": []},
{"itemKind": 1, "itemIdList": []},
{"itemKind": 2, "itemIdList": []},
{"itemKind": 10, "itemIdList": []},
{"itemKind": 11, "itemIdList": []},
],
"userActivityList": [userActivity["userActivity"]],
"userMissionDataList": [
{
"type": userMissionDataList["userMissionDataList"][0]["type"],
"difficulty": userMissionDataList["userMissionDataList"][0][
"difficulty"
],
"targetGenreId": userMissionDataList["userMissionDataList"][0][
"targetGenreId"
],
"targetGenreTableId": userMissionDataList["userMissionDataList"][0][
"targetGenreTableId"
],
"conditionGenreId": userMissionDataList["userMissionDataList"][0][
"conditionGenreId"
],
"conditionGenreTableId": userMissionDataList["userMissionDataList"][
0
]["conditionGenreTableId"],
"clearFlag": userMissionDataList["userMissionDataList"][0][
"clearFlag"
],
},
{
"type": userMissionDataList["userMissionDataList"][1]["type"],
"difficulty": userMissionDataList["userMissionDataList"][1][
"difficulty"
],
"targetGenreId": userMissionDataList["userMissionDataList"][1][
"targetGenreId"
],
"targetGenreTableId": userMissionDataList["userMissionDataList"][1][
"targetGenreTableId"
],
"conditionGenreId": userMissionDataList["userMissionDataList"][1][
"conditionGenreId"
],
"conditionGenreTableId": userMissionDataList["userMissionDataList"][
1
]["conditionGenreTableId"],
"clearFlag": userMissionDataList["userMissionDataList"][1][
"clearFlag"
],
},
{
"type": userMissionDataList["userMissionDataList"][2]["type"],
"difficulty": userMissionDataList["userMissionDataList"][2][
"difficulty"
],
"targetGenreId": userMissionDataList["userMissionDataList"][2][
"targetGenreId"
],
"targetGenreTableId": userMissionDataList["userMissionDataList"][2][
"targetGenreTableId"
],
"conditionGenreId": userMissionDataList["userMissionDataList"][2][
"conditionGenreId"
],
"conditionGenreTableId": userMissionDataList["userMissionDataList"][
2
]["conditionGenreTableId"],
"clearFlag": userMissionDataList["userMissionDataList"][2][
"clearFlag"
],
},
{
"type": userMissionDataList["userMissionDataList"][3]["type"],
"difficulty": userMissionDataList["userMissionDataList"][3][
"difficulty"
],
"targetGenreId": userMissionDataList["userMissionDataList"][3][
"targetGenreId"
],
"targetGenreTableId": userMissionDataList["userMissionDataList"][3][
"targetGenreTableId"
],
"conditionGenreId": userMissionDataList["userMissionDataList"][3][
"conditionGenreId"
],
"conditionGenreTableId": userMissionDataList["userMissionDataList"][
3
]["conditionGenreTableId"],
"clearFlag": userMissionDataList["userMissionDataList"][3][
"clearFlag"
],
},
{
"type": userMissionDataList["userMissionDataList"][4]["type"],
"difficulty": userMissionDataList["userMissionDataList"][4][
"difficulty"
],
"targetGenreId": userMissionDataList["userMissionDataList"][4][
"targetGenreId"
],
"targetGenreTableId": userMissionDataList["userMissionDataList"][4][
"targetGenreTableId"
],
"conditionGenreId": userMissionDataList["userMissionDataList"][4][
"conditionGenreId"
],
"conditionGenreTableId": userMissionDataList["userMissionDataList"][
4
]["conditionGenreTableId"],
"clearFlag": userMissionDataList["userMissionDataList"][4][
"clearFlag"
],
},
{
"type": userMissionDataList["userMissionDataList"][5]["type"],
"difficulty": userMissionDataList["userMissionDataList"][5][
"difficulty"
],
"targetGenreId": userMissionDataList["userMissionDataList"][5][
"targetGenreId"
],
"targetGenreTableId": userMissionDataList["userMissionDataList"][5][
"targetGenreTableId"
],
"conditionGenreId": userMissionDataList["userMissionDataList"][5][
"conditionGenreId"
],
"conditionGenreTableId": userMissionDataList["userMissionDataList"][
5
]["conditionGenreTableId"],
"clearFlag": userMissionDataList["userMissionDataList"][5][
"clearFlag"
],
},
],
"userWeeklyData": {
"lastLoginWeek": userMissionDataList["userWeeklyData"]["lastLoginWeek"],
"beforeLoginWeek": userMissionDataList["userWeeklyData"][
"beforeLoginWeek"
],
"friendBonusFlag": userMissionDataList["userWeeklyData"][
"friendBonusFlag"
],
},
"userGamePlaylogList": [
{
"playlogId": loginId,
"version": userData["userData"]["lastRomVersion"],
"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": 0,
"playSpecial": CalcRandom(),
"playOtherUserId": 0,
}
],
"user2pPlaylog": {
"userId1": 0,
"userId2": 0,
"userName1": "",
"userName2": "",
"regionId": 0,
"placeId": 0,
"user2pPlaylogDetailList": [],
},
"userIntimateList": [],
"userShopItemStockList": [],
"userGetPointList": [],
"userTradeItemList": [],
"userFavoritemusicList": [],
"userKaleidxScopeList": [],
"isNewCharacterList": "",
"isNewMapList": "",
"isNewLoginBonusList": "",
"isNewItemList": "",
"isNewMusicDetailList": "0",
"isNewCourseList": "",
"isNewFavoriteList": "11111",
"isNewFriendSeasonRankingList": "",
"isNewUserIntimateList": "",
"isNewFavoritemusicList": "",
"isNewKaleidxScopeList": "",
},
}
logger.info(
f"🫥 [INFO] userId: '{userId}', loginId: '{loginId}', loginDate: '{loginDate}', timestamp: '{TimeStamp}'"
)
return requestData_UserAll

115
uv.lock generated Normal file
View File

@@ -0,0 +1,115 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "anyio"
version = "4.14.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" },
]
[[package]]
name = "certifi"
version = "2026.6.17"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
]
[[package]]
name = "eaquira"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
{ name = "pycryptodome" },
{ name = "pytz" },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "pycryptodome", specifier = ">=3.23.0" },
{ name = "pytz", specifier = ">=2026.2" },
]
[[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.18"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
]
[[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/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 = "pytz"
version = "2026.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" },
]