Init: v1 release
This commit is contained in:
23
sdgb/.settings.py
Normal file
23
sdgb/.settings.py
Normal file
@@ -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"
|
||||
19
sdgb/GetUserPreviewApi.py
Normal file
19
sdgb/GetUserPreviewApi.py
Normal file
@@ -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))
|
||||
23
sdgb/UserLogoutApi.py
Normal file
23
sdgb/UserLogoutApi.py
Normal file
@@ -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))
|
||||
32
sdgb/chime.py
Normal file
32
sdgb/chime.py
Normal file
@@ -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)
|
||||
63
sdgb/encrypt.py
Normal file
63
sdgb/encrypt.py
Normal file
@@ -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<R"
|
||||
AesIV = "d6xHIKq]1J]Dt^ue"
|
||||
ObfuscateParam = "B44df8yT"
|
||||
|
||||
# AesKey = "n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@"
|
||||
# AesIV = ";;KjR1C3hgB1ovXa"
|
||||
# ObfuscateParam = "BEs2D5vW"
|
||||
|
||||
class aes_pkcs7(object):
|
||||
def __init__(self, key: str, iv: str):
|
||||
self.key = key.encode('utf-8')
|
||||
self.iv = iv.encode('utf-8')
|
||||
self.mode = AES.MODE_CBC
|
||||
|
||||
def encrypt(self, content: bytes) -> bytes:
|
||||
cipher = AES.new(self.key, self.mode, self.iv)
|
||||
content_padded = pad(content, AES.block_size)
|
||||
encrypted_bytes = cipher.encrypt(content_padded)
|
||||
return encrypted_bytes
|
||||
|
||||
def decrypt(self, 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
|
||||
66
sdgb/keychip.py
Normal file
66
sdgb/keychip.py
Normal file
@@ -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()
|
||||
336
sdgb/payload.py
Normal file
336
sdgb/payload.py
Normal file
@@ -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
|
||||
61
sdgb/sdgb.py
Normal file
61
sdgb/sdgb.py
Normal file
@@ -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
|
||||
73
sdgb/ticket.py
Normal file
73
sdgb/ticket.py
Normal file
@@ -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))
|
||||
Reference in New Issue
Block a user