From a9c4236bfb8c64818d3e50c2929a86a9f9230e62 Mon Sep 17 00:00:00 2001 From: 91c0e59d-6161-45ab-8aa4-2371574db28f <91c0e59d-6161-45ab-8aa4-2371574db28f@bank-of-china.com> Date: Wed, 31 Dec 2025 13:11:37 +0800 Subject: [PATCH] Init: v1 release --- .gitignore | 3 + README.md | 41 +++++ sdgb/.settings.py | 23 +++ sdgb/GetUserPreviewApi.py | 19 +++ sdgb/UserLogoutApi.py | 23 +++ sdgb/chime.py | 32 ++++ sdgb/encrypt.py | 63 +++++++ sdgb/keychip.py | 66 ++++++++ sdgb/payload.py | 336 ++++++++++++++++++++++++++++++++++++++ sdgb/sdgb.py | 61 +++++++ sdgb/ticket.py | 73 +++++++++ 11 files changed, 740 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 sdgb/.settings.py create mode 100644 sdgb/GetUserPreviewApi.py create mode 100644 sdgb/UserLogoutApi.py create mode 100644 sdgb/chime.py create mode 100644 sdgb/encrypt.py create mode 100644 sdgb/keychip.py create mode 100644 sdgb/payload.py create mode 100644 sdgb/sdgb.py create mode 100644 sdgb/ticket.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f2685a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/__pycache__ +**/settings.py +**/.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..57c73b1 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Eaquira + +A python 3 project which is used to post title server of 「舞萌 DX」&「maimai DX International」 + +--- + +## Info + +__Eaquira__ is a fork of `sdgb-some-api` main branch. Thanks to leakers. + +## SDGA Usage + +特别说明:国际服(下称 SDGA)部分代码仅提供参考,并不具备实用价值。 +- 世嘉对 SDGA 的维护尚可,对于发送请求的 IP 有较为严格的要求。对于滥发请求的 IP 等会快速封禁。 +- 由于 AiMeDB 已禁用对 FeliCa 卡类型老旧接口的支持(主要是 FeliCa Lookup 的部分,将 IDm 查表转换为 accessCode 相关),大部分使用蓝白卡(判断方式:卡号 5 开头)的玩家无法通过命令行模拟的方式实现上号,必须要通过购买第三方读卡器才能实现刷卡上号。 + +still in progress... + +## SDGB Usage + +- _settings.py_ 储存 UserId、机厅信息等重要的信息,**请不要向他人泄露自己的 UserId**。将 ```.settings.py``` 命名为 ```settings.py``` 并按照注释修改设置。 + +## Running + +```bash +pip install -r requirements.txt +``` + +## Warning and Statements + +WE ARE NOT RESIPONSIBLE FOR YOUR ACCOUNT. + +>怂别用,用别怂。 +> +>我也没说过这玩意一直能用,至少现在能用。 + +## Copyright + +GNU License. + +__Eaquira__ is a part of [__Project Fragrance__](https://fragrance.moe). diff --git a/sdgb/.settings.py b/sdgb/.settings.py new file mode 100644 index 0000000..902505d --- /dev/null +++ b/sdgb/.settings.py @@ -0,0 +1,23 @@ +# 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" \ No newline at end of file diff --git a/sdgb/GetUserPreviewApi.py b/sdgb/GetUserPreviewApi.py new file mode 100644 index 0000000..0874c45 --- /dev/null +++ b/sdgb/GetUserPreviewApi.py @@ -0,0 +1,19 @@ +import json +import asyncio +import httpx +from sdgb import MaimaiClient +from settings import * + +maimai = MaimaiClient() + +async def run_workflow(maimai): + async with httpx.AsyncClient(verify=False) as client: + data = { + "userId": userId, + "segaIdAuthKey":"" + } + result = await maimai.call_api(client, "GetUserPreviewApi", data, userId) + +# 执行入口 +if __name__ == "__main__": + asyncio.run(run_workflow(maimai)) \ No newline at end of file diff --git a/sdgb/UserLogoutApi.py b/sdgb/UserLogoutApi.py new file mode 100644 index 0000000..b2af82f --- /dev/null +++ b/sdgb/UserLogoutApi.py @@ -0,0 +1,23 @@ +import json +import asyncio +import httpx +from sdgb import MaimaiClient +from settings import * + +maimai = MaimaiClient() + +async def run_workflow(maimai): + async with httpx.AsyncClient(verify=False) as client: + data = { + "userId": userId, + "accessCode": "", + "regionId": regionId, + "placeId": placeId, + "clientId": clientId, + "dateTime": 1767000000, + "type": 4 + } + await maimai.call_api(client, "UserLogoutApi", data, userId) +# 执行入口 +if __name__ == "__main__": + asyncio.run(run_workflow(maimai)) \ No newline at end of file diff --git a/sdgb/chime.py b/sdgb/chime.py new file mode 100644 index 0000000..e8e1e72 --- /dev/null +++ b/sdgb/chime.py @@ -0,0 +1,32 @@ +import hashlib +import httpx +import pytz +import json +from datetime import datetime +from settings import KeychipID + +def qr_api(qr_code: str): + if len(qr_code) > 64: + qr_code = qr_code[-64:] + time_stamp = datetime.now(pytz.timezone('Asia/Tokyo')).strftime("%y%m%d%H%M%S") + auth_key = hashlib.sha256( + (KeychipID + time_stamp + "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW").encode("UTF-8")).hexdigest().upper() + param = { + "chipID": KeychipID, + "openGameID": "MAID", + "key": auth_key, + "qrCode": qr_code, + "timestamp": time_stamp + } + headers = { + "Contention": "Keep-Alive", + "Host": "ai.sys-all.cn", + "User-Agent": "WC_AIME_LIB" + } + res = httpx.post( + "http://ai.sys-allnet.cn/wc_aime/api/get_data", + data = json.dumps(param, separators=(',', ':')), + headers = headers + ) + assert res.status_code == 200, "网络错误" + return json.loads(res.content) diff --git a/sdgb/encrypt.py b/sdgb/encrypt.py new file mode 100644 index 0000000..6a9ced6 --- /dev/null +++ b/sdgb/encrypt.py @@ -0,0 +1,63 @@ +import zlib +import base64 +import hashlib +import random + +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad, unpad + +AesKey = "a>32bVP7v<63BVLkY[xM>daZ1s9MBP bytes: + cipher = AES.new(self.key, self.mode, self.iv) + content_padded = pad(content, AES.block_size) + encrypted_bytes = cipher.encrypt(content_padded) + return encrypted_bytes + + def decrypt(self, content): + cipher = AES.new(self.key, self.mode, self.iv) + decrypted_padded = cipher.decrypt(content) + decrypted = unpad(decrypted_padded, AES.block_size) + return decrypted + + def pkcs7unpadding(self, text): + length = len(text) + unpadding = ord(text[length - 1]) + return text[0:length - unpadding] + + def pkcs7padding(self, text): + bs = 16 + length = len(text) + bytes_length = len(text.encode('utf-8')) + padding_size = length if (bytes_length == length) else bytes_length + padding = bs - padding_size % bs + padding_text = chr(padding) * padding + return text + padding_text + +def get_hash_api(api): + return hashlib.md5((api+"MaimaiChn"+ObfuscateParam).encode()).hexdigest() + +def CalcRandom(): + max = 1037933 + num2 = random.randint(1, max) * 2069 + + num2 += 1024 # specialnum + num3 = 0 + for i in range(0, 32): + num3 <<= 1 + num3 += num2 % 2 + num2 >>= 1 + + return num3 \ No newline at end of file diff --git a/sdgb/keychip.py b/sdgb/keychip.py new file mode 100644 index 0000000..a5d673e --- /dev/null +++ b/sdgb/keychip.py @@ -0,0 +1,66 @@ +import httpx +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad +from urllib.parse import unquote + +def enc(key, iv, data): + cipher = AES.new(key, AES.MODE_CBC, iv) + encrypted = cipher.encrypt(data) + return encrypted + +def dec(key, iv, data): + de_cipher = AES.new(key, AES.MODE_CBC, iv) + decrypted = de_cipher.decrypt(data) + return decrypted + +def hello(): + 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 ]) + iv = bytes.fromhex('00000000000000000000000000000000') + ua = 'SDGB;Windows/Lite' + #ua = 'SDHJ;Windows/Lite' + + # 构建 payload + content = bytes([0] * 16) + b'title_id=SDGB&title_ver=1.52&client_id=A63E01E6149' + print(f"Content: {content}") + + header = bytes.fromhex('00000000000000000000000000000000') + bytes_data = pad(header + content, 16) + encrypted = enc(key, iv, bytes_data) + + # --- HTTPX 修改部分 --- + headers = { + 'User-Agent': ua, + 'Pragma': 'DFI' + } + + try: + # 发送 POST 请求 + # urllib3 的 body 参数在 httpx 中对应 content (用于二进制数据) + r = httpx.post( + 'http://at.sys-allnet.cn/net/initialize', + content=encrypted, + headers=headers + ) + + # 检查响应状态码 (可选,但在 httpx 中推荐) + # r.raise_for_status() + + # urllib3 的 r.data 在 httpx 中对应 r.content + resp_data = r.content + + # 解密逻辑保持不变 + # 注意:这里逻辑是用响应的前16字节作为IV,同时解密整个数据,然后丢弃前16字节 + if len(resp_data) >= 16: + decrypted = dec(key, resp_data[:16], resp_data) + decrypted_bytes = decrypted[16:] + decrypted_str = unquote(decrypted_bytes.decode('UTF-8'), 'utf-8') + print(f"Decrypted: {decrypted_str}") + else: + print("Response data too short.") + + except httpx.RequestError as e: + print(f"An error occurred while requesting: {e}") + +if __name__ == '__main__': + hello() \ No newline at end of file diff --git a/sdgb/payload.py b/sdgb/payload.py new file mode 100644 index 0000000..f0f6dab --- /dev/null +++ b/sdgb/payload.py @@ -0,0 +1,336 @@ +import time +import pytz +import json +from datetime import datetime, timedelta +from encrypt import CalcRandom +from settings import * +import logging + +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":"" +} + +requestData_UserLogin = { + "userId": userId, + "accessCode": "", + "regionId": regionId, + "placeId": placeId, + "clientId": clientId, + "dateTime": 1767000000, + "isContinue": False, + "genericFlag":0 +} + +requestData_UserData = { + "userId": userId +} + +requestData_UserLogout = { + "userId": userId, + "accessCode": "", + "regionId": regionId, + "placeId": placeId, + "clientId": clientId, + "dateTime": 1767000000, + "type": 1 +} + +def UserPlaylog_payload(loginId: int, musicData: dict, userData: str): + + userData = json.loads(userData) + + requestData_UserPlaylog = { + "userId": userId, + "userPlaylogList": [ + { + "userId": 0, + "orderId": 0, + "playlogId": loginId, + "version": 1052000, + "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": 987, + "maxSync": 0, + "totalSync": 0, + "tapCriticalPerfect": 0, + "tapPerfect": 0, + "tapGreat": 0, + "tapGood": 0, + "tapMiss": 590, + "holdCriticalPerfect": 0, + "holdPerfect": 0, + "holdGreat": 0, + "holdGood": 0, + "holdMiss": 21, + "slideCriticalPerfect": 0, + "slidePerfect": 0, + "slideGreat": 0, + "slideGood": 0, + "slideMiss": 176, + "touchCriticalPerfect": 0, + "touchPerfect": 0, + "touchGreat": 0, + "touchGood": 0, + "touchMiss": 0, + "breakCriticalPerfect": 0, + "breakPerfect": 0, + "breakGreat": 0, + "breakGood": 0, + "breakMiss": 200, + "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": 3020, + "extBool1": False, + "extBool2": False + } + ] + } + return requestData_UserPlaylog + +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, + "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'], + "currentPlayCount": userData['userData']['currentPlayCount'], + "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": [], + "userActivityList": [userActivity['userActivity']], + "userMissionDataList": userMissionDataList['userMissionDataList'], + "userWeeklyData": userMissionDataList['userWeeklyData'], + "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": "", + "isNewFriendSeasonRankingList": "", + "isNewUserIntimateList": "", + "isNewFavoritemusicList": "", + "isNewKaleidxScopeList": "" + } + } + + logger.info(f"🫥 [INFO] userId: '{userId}', loginId: '{loginId}', loginDate: '{loginDate}', timestamp: '{TimeStamp}'") + return requestData_UserAll \ No newline at end of file diff --git a/sdgb/sdgb.py b/sdgb/sdgb.py new file mode 100644 index 0000000..8d8c67c --- /dev/null +++ b/sdgb/sdgb.py @@ -0,0 +1,61 @@ +import asyncio +import httpx +import json +import logging +import time +from encrypt import * + +# 配置日志,方便调试看请求顺序 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +class MaimaiClient: + def __init__(self): + self.base_url = f"https://maimai-gm.wahlap.com:42081/Maimai2Servlet/" + self.aes = aes_pkcs7(AesKey, AesIV) + + async def call_api(self, client: httpx.AsyncClient, ApiType: str, data: dict, userId: int): + """ + 先压缩再加密请求数据,发送请求后解密再解压响应数据 + 这里的 client 需要传入外部创建的 httpx.AsyncClient 实例 + """ + + ApiTypeHash = get_hash_api(ApiType) + url = f"{self.base_url}{ApiTypeHash}" + + headers = { + "User-Agent": f"{ApiTypeHash}#{userId}", + "Content-Type": "application/json", + "Mai-Encoding": "1.50", + "Accept-Encoding": "", + "Charset": "UTF-8", + "Content-Encoding": "deflate" + } + + data = bytes(json.dumps(data), encoding="utf-8") + CompressedData = zlib.compress(data) + AESEncrptedData = self.aes.encrypt(CompressedData) + + try: + # 这里的 timeout 设置稍微长一点,防止服务端处理慢 + resp = await client.post(url, headers=headers, data=AESEncrptedData, timeout=10.0) + resp.raise_for_status() # 如果状态码不是 2xx 则抛出异常 + + AESEncrptedResponse = resp.content + DecryptedData = self.aes.decrypt(AESEncrptedResponse) + UncompressedData = zlib.decompress(DecryptedData).decode('utf-8') + + logger.info(f"✅ [SUCCESS] {ApiType} - {UncompressedData}") + + return UncompressedData + + + except httpx.HTTPStatusError as e: + logger.error(f"❌ [HTTP ERROR] {ApiType}: {e.response.status_code}") + return None + except Exception as e: + logger.error(f"❌ [ERROR] {ApiType}: {str(e)}") + return None diff --git a/sdgb/ticket.py b/sdgb/ticket.py new file mode 100644 index 0000000..b7e2e74 --- /dev/null +++ b/sdgb/ticket.py @@ -0,0 +1,73 @@ +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)) \ No newline at end of file