refactor: update to sdgb 1.55

This commit is contained in:
2026-07-07 13:38:06 +08:00
parent 8da425391a
commit 17c419dca0
18 changed files with 4678 additions and 4360 deletions

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

11
pyproject.toml Normal file
View File

@@ -0,0 +1,11 @@
[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",
]

View File

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

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))

View File

@@ -1,9 +1,10 @@
# This file contains the config. No function inside. # This file contains the config. No function inside.
# DO NOT share your env to others. # DO NOT share your env to others.
userId = userId = ...
qrCode = ...
musicData = ({ musicData = {
"musicId": 417, "musicId": 417,
"level": 3, "level": 3,
"playCount": 1, "playCount": 1,
@@ -12,8 +13,8 @@ musicData = ({
"syncStatus": 4, "syncStatus": 4,
"deluxscoreMax": 2277, "deluxscoreMax": 2277,
"scoreRank": 13, "scoreRank": 13,
"extNum1": 0 "extNum1": 0,
}) }
regionId = 1 regionId = 1
regionName = "北京" regionName = "北京"

View File

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

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()

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.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,15 +58,19 @@ 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)
await asyncio.sleep(60) # 模拟游戏时间 await asyncio.sleep(60) # 模拟游戏时间
# 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 settings import * from sdgb.sdgb import MaimaiClient
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))

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:
@@ -41,19 +41,21 @@ class aes_pkcs7(object):
def pkcs7unpadding(self, text): def pkcs7unpadding(self, text):
length = len(text) length = len(text)
unpadding = ord(text[length - 1]) unpadding = ord(text[length - 1])
return text[0:length - unpadding] return text[0 : length - unpadding]
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

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()

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

@@ -0,0 +1,523 @@
import time
import json
import logging
from datetime import datetime
import pytz
from encrypt import CalcRandom
from sdgb.chime import qr_api
from sdgb.settings import (
clientId,
qrCode,
regionId,
regionName,
placeId,
placeName,
)
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,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 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(
resp.raise_for_status() # 如果状态码不是 2xx 则抛出异常 url, headers=headers, data=AESEncrptedData, timeout=10.0
)
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

93
src/sdgb/ticket.py Normal file
View File

@@ -0,0 +1,93 @@
import json
import asyncio
import httpx
import time
import logging
from sdgb.sdgb import MaimaiClient
from sdgb.settings import userId, musicData
from sdgb.payload import (
requestData_UserPreview,
requestData_UserLogin,
requestData_UserData,
requestData_UserLogout,
UserAll_payload,
)
maimai = MaimaiClient()
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
async def run_workflow(self: MaimaiClient):
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"] == 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))

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 = { virtual = "." }
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" },
]