first commit
This commit is contained in:
805
lib/services/title_api_service.dart
Normal file
805
lib/services/title_api_service.dart
Normal file
@@ -0,0 +1,805 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:pointycastle/export.dart';
|
||||
|
||||
import '../config/title_server_config.dart';
|
||||
import '../models/user_preview.dart';
|
||||
|
||||
class TitleApiException implements Exception {
|
||||
final String message;
|
||||
const TitleApiException(this.message);
|
||||
@override
|
||||
String toString() => 'TitleApiException: $message';
|
||||
}
|
||||
|
||||
class UserLoginResult {
|
||||
final String token;
|
||||
final int loginId;
|
||||
final String lastLoginDate;
|
||||
|
||||
const UserLoginResult({
|
||||
required this.token,
|
||||
required this.loginId,
|
||||
required this.lastLoginDate,
|
||||
});
|
||||
}
|
||||
|
||||
class TitleApiService {
|
||||
static const String _obfuscateConstant = 'MaimaiChn';
|
||||
|
||||
static String? lastRawResponse;
|
||||
|
||||
final TitleServerConfig _config;
|
||||
|
||||
TitleApiService(this._config);
|
||||
|
||||
Uint8List _aesEncrypt(Uint8List plaintext) {
|
||||
final cipher = PaddedBlockCipherImpl(
|
||||
PKCS7Padding(),
|
||||
CBCBlockCipher(AESEngine()),
|
||||
)..init(
|
||||
true,
|
||||
PaddedBlockCipherParameters(
|
||||
ParametersWithIV(
|
||||
KeyParameter(Uint8List.fromList(_config.aesKeyBytes)),
|
||||
Uint8List.fromList(_config.aesIvBytes),
|
||||
),
|
||||
null,
|
||||
),
|
||||
);
|
||||
return cipher.process(plaintext);
|
||||
}
|
||||
|
||||
Uint8List _aesDecrypt(Uint8List ciphertext) {
|
||||
final cipher = PaddedBlockCipherImpl(
|
||||
PKCS7Padding(),
|
||||
CBCBlockCipher(AESEngine()),
|
||||
)..init(
|
||||
false,
|
||||
PaddedBlockCipherParameters(
|
||||
ParametersWithIV(
|
||||
KeyParameter(Uint8List.fromList(_config.aesKeyBytes)),
|
||||
Uint8List.fromList(_config.aesIvBytes),
|
||||
),
|
||||
null,
|
||||
),
|
||||
);
|
||||
return cipher.process(ciphertext);
|
||||
}
|
||||
|
||||
Uint8List _compress(List<int> data) {
|
||||
return Uint8List.fromList(ZLibEncoder().encode(data));
|
||||
}
|
||||
|
||||
Uint8List _decompress(List<int> data) {
|
||||
return Uint8List.fromList(ZLibDecoder().decodeBytes(data));
|
||||
}
|
||||
|
||||
Uint8List _buildRequestBody(Map<String, dynamic> packet) {
|
||||
final jsonStr = jsonEncode(packet);
|
||||
final jsonBytes = utf8.encode(jsonStr);
|
||||
final compressed = _compress(jsonBytes);
|
||||
return _aesEncrypt(compressed);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _processResponseBody(Uint8List bodyBytes) {
|
||||
final decrypted = _aesDecrypt(bodyBytes);
|
||||
final decompressed = _decompress(decrypted);
|
||||
final jsonStr = utf8.decode(decompressed);
|
||||
return jsonDecode(jsonStr) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
String _buildHash(String apiName) {
|
||||
final raw = apiName + _obfuscateConstant + _config.obfuscateParam;
|
||||
return md5.convert(utf8.encode(raw)).toString();
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _callApi(
|
||||
String apiName,
|
||||
Map<String, dynamic> packet,
|
||||
int userId,
|
||||
) async {
|
||||
final hash = _buildHash(apiName);
|
||||
final body = _buildRequestBody(packet);
|
||||
final baseUrl = _normalizeUrl(_config.titleServerUrl);
|
||||
final url = Uri.parse('$baseUrl/$hash');
|
||||
|
||||
final response = await http
|
||||
.post(
|
||||
url,
|
||||
headers: {
|
||||
'User-Agent': '$hash#$userId',
|
||||
'Content-Type': 'application/json',
|
||||
'Mai-Encoding': _config.apiVersion,
|
||||
'Accept-Encoding': '',
|
||||
'Charset': 'UTF-8',
|
||||
'Content-Encoding': 'deflate',
|
||||
'Host': 'maimai-gm.wahlap.com:42081',
|
||||
},
|
||||
body: body,
|
||||
)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw TitleApiException('$apiName returned ${response.statusCode}');
|
||||
}
|
||||
|
||||
final json = _processResponseBody(response.bodyBytes);
|
||||
final raw = const JsonEncoder.withIndent(' ').convert(json);
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] === RESPONSE ===');
|
||||
// ignore: avoid_print
|
||||
print(raw);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
static int calcRandom() {
|
||||
final rand = _RandomHelper();
|
||||
final max = 1037933;
|
||||
final num2 = (rand.nextInt(max - 1) + 1) * 2069 + 1024;
|
||||
var num3 = 0;
|
||||
var n = num2;
|
||||
for (var i = 0; i < 32; i++) {
|
||||
num3 <<= 1;
|
||||
num3 += n % 2;
|
||||
n >>= 1;
|
||||
}
|
||||
return num3;
|
||||
}
|
||||
|
||||
String _normalizeUrl(String url) {
|
||||
var normalized = url.trim();
|
||||
if (normalized.endsWith('/')) {
|
||||
normalized = normalized.substring(0, normalized.length - 1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
Future<String> userLogin({
|
||||
required int userId,
|
||||
required String token,
|
||||
}) async {
|
||||
const apiName = 'UserLoginApi';
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
final hash = _buildHash(apiName);
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'accessCode': '',
|
||||
'regionId': _config.regionId,
|
||||
'placeId': _config.placeId,
|
||||
'clientId': _config.clientId,
|
||||
'dateTime': now - 600,
|
||||
'loginDateTime': now,
|
||||
'isContinue': false,
|
||||
'genericFlag': 0,
|
||||
'token': token,
|
||||
};
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[userLogin] packet=${jsonEncode(packet)}');
|
||||
final body = _buildRequestBody(packet);
|
||||
final baseUrl = _normalizeUrl(_config.titleServerUrl);
|
||||
final url = Uri.parse('$baseUrl/$hash');
|
||||
// ignore: avoid_print
|
||||
print('[userLogin] POST $url');
|
||||
|
||||
final response = await http
|
||||
.post(
|
||||
url,
|
||||
headers: {
|
||||
'User-Agent': '$hash#$userId',
|
||||
'Content-Type': 'application/json',
|
||||
'Mai-Encoding': _config.apiVersion,
|
||||
'Accept-Encoding': '',
|
||||
'Charset': 'UTF-8',
|
||||
'Content-Encoding': 'deflate',
|
||||
'Host': 'maimai-gm.wahlap.com:42081',
|
||||
},
|
||||
body: body,
|
||||
)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[userLogin] HTTP status=${response.statusCode}');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw TitleApiException(
|
||||
'UserLoginApi returned ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
|
||||
final json = _processResponseBody(response.bodyBytes);
|
||||
final raw = const JsonEncoder.withIndent(' ').convert(json);
|
||||
// ignore: avoid_print
|
||||
print('[userLogin] === RESPONSE ===');
|
||||
// ignore: avoid_print
|
||||
print(raw);
|
||||
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UserLoginApi errorId=$errorId');
|
||||
}
|
||||
|
||||
final newToken = json['token'] as String?;
|
||||
if (newToken == null || newToken.isEmpty) {
|
||||
throw TitleApiException('UserLoginApi returned no token');
|
||||
}
|
||||
|
||||
return newToken;
|
||||
}
|
||||
|
||||
Future<UserPreviewDataBean> getUserPreview({
|
||||
required int userId,
|
||||
required String token,
|
||||
}) async {
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] ===== START =====');
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] userId=$userId');
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] token=$token');
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] clientId=${_config.clientId}');
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] titleServerUrl=${_config.titleServerUrl}');
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] aesKey.len=${_config.aesKeyBytes.length} aesIv.len=${_config.aesIvBytes.length}');
|
||||
|
||||
const apiName = 'GetUserPreviewApi';
|
||||
final hash = _buildHash(apiName);
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] hash=$hash');
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'segaIdAuthKey': '',
|
||||
'token': token,
|
||||
'clientId': _config.clientId,
|
||||
};
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] packet=${jsonEncode(packet)}');
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] building request body (compress + encrypt)...');
|
||||
final body = _buildRequestBody(packet);
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] body size=${body.length} bytes');
|
||||
|
||||
final baseUrl = _normalizeUrl(_config.titleServerUrl);
|
||||
final url = Uri.parse('$baseUrl/$hash');
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] POST $url');
|
||||
|
||||
try {
|
||||
final response = await http
|
||||
.post(
|
||||
url,
|
||||
headers: {
|
||||
'User-Agent': '$hash#$userId',
|
||||
'Content-Type': 'application/json',
|
||||
'Mai-Encoding': _config.apiVersion,
|
||||
'Accept-Encoding': '',
|
||||
'Charset': 'UTF-8',
|
||||
'Content-Encoding': 'deflate',
|
||||
"Host": "maimai-gm.wahlap.com:42081"
|
||||
},
|
||||
body: body,
|
||||
)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] HTTP status=${response.statusCode}');
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] response body size=${response.bodyBytes.length} bytes');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] non-200 response body: ${utf8.decode(response.bodyBytes.take(500).toList())}');
|
||||
throw TitleApiException(
|
||||
'Server returned ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] decrypting + decompressing...');
|
||||
final json = _processResponseBody(response.bodyBytes);
|
||||
final raw = const JsonEncoder.withIndent(' ').convert(json);
|
||||
lastRawResponse = raw;
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] === RESPONSE ===');
|
||||
// ignore: avoid_print
|
||||
print(raw);
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] ===== END =====');
|
||||
return UserPreviewDataBean.fromJson(json);
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
print('[getUserPreview] ERROR: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Generic data APIs ----
|
||||
|
||||
Future<Map<String, dynamic>> getUserData(int userId) async {
|
||||
return _callApi('GetUserDataApi', {'userId': userId}, userId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getUserExtend(int userId) async {
|
||||
return _callApi('GetUserExtendApi', {'userId': userId}, userId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getUserOption(int userId) async {
|
||||
return _callApi('GetUserOptionApi', {'userId': userId}, userId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getUserRating(int userId) async {
|
||||
return _callApi('GetUserRatingApi', {'userId': userId}, userId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getUserCharge(int userId) async {
|
||||
return _callApi('GetUserChargeApi', {'userId': userId}, userId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getUserActivity(int userId) async {
|
||||
return _callApi('GetUserActivityApi', {'userId': userId}, userId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getUserMissionData(int userId) async {
|
||||
return _callApi('GetUserMissionDataApi', {'userId': userId}, userId);
|
||||
}
|
||||
|
||||
// ---- UserLogin (returns full result) ----
|
||||
|
||||
Future<UserLoginResult> userLoginFull({
|
||||
required int userId,
|
||||
required String token,
|
||||
}) async {
|
||||
const apiName = 'UserLoginApi';
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'accessCode': '',
|
||||
'regionId': _config.regionId,
|
||||
'placeId': _config.placeId,
|
||||
'clientId': _config.clientId,
|
||||
'dateTime': now - 600,
|
||||
'loginDateTime': now,
|
||||
'isContinue': false,
|
||||
'genericFlag': 0,
|
||||
'token': token,
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UserLoginApi errorId=$errorId');
|
||||
}
|
||||
|
||||
final loginId = (json['loginId'] as num?)?.toInt() ?? 0;
|
||||
final lastLoginDate = json['lastLoginDate'] as String? ?? '';
|
||||
final newToken = json['token'] as String? ?? '';
|
||||
|
||||
return UserLoginResult(
|
||||
token: newToken,
|
||||
loginId: loginId,
|
||||
lastLoginDate: lastLoginDate,
|
||||
);
|
||||
}
|
||||
|
||||
// ---- UserLogout ----
|
||||
|
||||
Future<void> userLogout({
|
||||
required int userId,
|
||||
required int loginDateTime,
|
||||
}) async {
|
||||
const apiName = 'UserLogoutApi';
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'accessCode': '',
|
||||
'regionId': _config.regionId,
|
||||
'placeId': _config.placeId,
|
||||
'clientId': _config.clientId,
|
||||
'loginDateTime': loginDateTime,
|
||||
'type': 1,
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UserLogoutApi errorId=$errorId');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- UploadUserPlaylog ----
|
||||
|
||||
Future<void> uploadUserPlaylog({
|
||||
required int userId,
|
||||
required int loginId,
|
||||
required Map<String, dynamic> musicData,
|
||||
required Map<String, dynamic> userData,
|
||||
}) async {
|
||||
const apiName = 'UploadUserPlaylogListApi';
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'userPlaylogList': [
|
||||
_buildPlaylogEntry(userId, loginId, now, musicData, userData),
|
||||
],
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UploadUserPlaylogListApi errorId=$errorId');
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _buildPlaylogEntry(
|
||||
int userId,
|
||||
int loginId,
|
||||
int timestamp,
|
||||
Map<String, dynamic> musicData,
|
||||
Map<String, dynamic> userData,
|
||||
) {
|
||||
final user = userData['userData'] as Map<String, dynamic>? ?? {};
|
||||
final charaSlot = (user['charaSlot'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList() ??
|
||||
[0, 0, 0, 0, 0];
|
||||
|
||||
return {
|
||||
'userId': userId,
|
||||
'orderId': 0,
|
||||
'playlogId': loginId,
|
||||
'version': 1053000,
|
||||
'placeId': _config.placeId,
|
||||
'placeName': '',
|
||||
'loginDate': timestamp,
|
||||
'playDate': _formatDate(DateTime.now()),
|
||||
'userPlayDate': _formatDateTime(DateTime.now()),
|
||||
'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': charaSlot.isNotEmpty ? charaSlot[0] : 0,
|
||||
'characterLevel1': 1,
|
||||
'characterAwakening1': 0,
|
||||
'characterId2': charaSlot.length > 1 ? charaSlot[1] : 0,
|
||||
'characterLevel2': 1,
|
||||
'characterAwakening2': 0,
|
||||
'characterId3': charaSlot.length > 2 ? charaSlot[2] : 0,
|
||||
'characterLevel3': 1,
|
||||
'characterAwakening3': 0,
|
||||
'characterId4': charaSlot.length > 3 ? charaSlot[3] : 0,
|
||||
'characterLevel4': 1,
|
||||
'characterAwakening4': 0,
|
||||
'characterId5': charaSlot.length > 4 ? charaSlot[4] : 0,
|
||||
'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': user['playerRating'] ?? 0,
|
||||
'afterRating': user['playerRating'] ?? 0,
|
||||
'beforeGrade': 0,
|
||||
'afterGrade': 0,
|
||||
'afterGradeRank': 0,
|
||||
'beforeDeluxRating': user['playerRating'] ?? 0,
|
||||
'afterDeluxRating': user['playerRating'] ?? 0,
|
||||
'isPlayTutorial': false,
|
||||
'isEventMode': false,
|
||||
'isFreedomMode': false,
|
||||
'playMode': 0,
|
||||
'isNewFree': false,
|
||||
'trialPlayAchievement': -1,
|
||||
'extNum1': musicData['extNum1'] ?? 0,
|
||||
'extNum2': 0,
|
||||
'extNum4': 101,
|
||||
'extBool1': false,
|
||||
'extBool2': false,
|
||||
};
|
||||
}
|
||||
|
||||
// ---- UpsertUserAll ----
|
||||
|
||||
Future<void> upsertUserAll({
|
||||
required int userId,
|
||||
required int loginId,
|
||||
required String loginDate,
|
||||
required Map<String, dynamic> musicData,
|
||||
required List<Map<String, dynamic>> generalUserInfo,
|
||||
}) async {
|
||||
const apiName = 'UpsertUserAllApi';
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
final userData = generalUserInfo[0];
|
||||
final userExtend = generalUserInfo[1];
|
||||
final userOption = generalUserInfo[2];
|
||||
final userRating = generalUserInfo[3];
|
||||
final userChargeList = generalUserInfo[4];
|
||||
final userActivity = generalUserInfo[5];
|
||||
final userMissionData = generalUserInfo[6];
|
||||
|
||||
final user = userData['userData'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'playlogId': loginId,
|
||||
'isEventMode': false,
|
||||
'isFreePlay': false,
|
||||
'loginDateTime': now,
|
||||
'userPlaylogList': [
|
||||
_buildPlaylogEntry(userId, loginId, now, musicData, userData),
|
||||
],
|
||||
'upsertUserAll': {
|
||||
'userData': [
|
||||
{
|
||||
'accessCode': '',
|
||||
'userName': user['userName'] ?? '',
|
||||
'isNetMember': 1,
|
||||
'point': user['point'] ?? 0,
|
||||
'totalPoint': user['totalPoint'] ?? 0,
|
||||
'iconId': user['iconId'] ?? 0,
|
||||
'plateId': user['plateId'] ?? 0,
|
||||
'titleId': user['titleId'] ?? 0,
|
||||
'partnerId': user['partnerId'] ?? 0,
|
||||
'frameId': user['frameId'] ?? 0,
|
||||
'selectMapId': user['selectMapId'] ?? 0,
|
||||
'totalAwake': user['totalAwake'] ?? 0,
|
||||
'gradeRating': user['gradeRating'] ?? 0,
|
||||
'musicRating': user['musicRating'] ?? 0,
|
||||
'playerRating': user['playerRating'] ?? 0,
|
||||
'highestRating': user['highestRating'] ?? 0,
|
||||
'gradeRank': user['gradeRank'] ?? 0,
|
||||
'classRank': user['classRank'] ?? 0,
|
||||
'courseRank': user['courseRank'] ?? 0,
|
||||
'charaSlot': user['charaSlot'] ?? [0, 0, 0, 0, 0],
|
||||
'charaLockSlot': user['charaLockSlot'] ?? [0, 0, 0, 0, 0],
|
||||
'contentBit': user['contentBit'] ?? 0,
|
||||
'playCount': ((user['playCount'] as num?)?.toInt() ?? 0) + 1,
|
||||
'currentPlayCount': ((user['currentPlayCount'] as num?)?.toInt() ?? 0) + 1,
|
||||
'renameCredit': user['renameCredit'] ?? 0,
|
||||
'mapStock': user['mapStock'] ?? 0,
|
||||
'eventWatchedDate': user['eventWatchedDate'] ?? '',
|
||||
'lastGameId': 'SDGB',
|
||||
'lastRomVersion': user['lastRomVersion'] ?? '',
|
||||
'lastDataVersion': user['lastDataVersion'] ?? '',
|
||||
'lastLoginDate': loginDate,
|
||||
'lastPlayDate': _formatDateTime(DateTime.now()),
|
||||
'lastPlayCredit': 1,
|
||||
'lastPlayMode': 0,
|
||||
'lastPlaceId': _config.placeId,
|
||||
'lastPlaceName': '',
|
||||
'lastAllNetId': 0,
|
||||
'lastRegionId': _config.regionId,
|
||||
'lastRegionName': '',
|
||||
'lastClientId': _config.clientId,
|
||||
'lastCountryCode': 'CHN',
|
||||
'lastSelectEMoney': user['lastSelectEMoney'] ?? 0,
|
||||
'lastSelectTicket': user['lastSelectTicket'] ?? 0,
|
||||
'lastSelectCourse': user['lastSelectCourse'] ?? 0,
|
||||
'lastCountCourse': user['lastCountCourse'] ?? 0,
|
||||
'firstGameId': user['firstGameId'] ?? '',
|
||||
'firstRomVersion': user['firstRomVersion'] ?? '',
|
||||
'firstDataVersion': user['firstDataVersion'] ?? '',
|
||||
'firstPlayDate': user['firstPlayDate'] ?? '',
|
||||
'compatibleCmVersion': user['compatibleCmVersion'] ?? '',
|
||||
'dailyBonusDate': user['dailyBonusDate'] ?? '',
|
||||
'dailyCourseBonusDate': user['dailyCourseBonusDate'] ?? '',
|
||||
'lastPairLoginDate': user['lastPairLoginDate'] ?? '',
|
||||
'lastTrialPlayDate': user['lastTrialPlayDate'] ?? '',
|
||||
'playVsCount': user['playVsCount'] ?? 0,
|
||||
'playSyncCount': user['playSyncCount'] ?? 0,
|
||||
'winCount': user['winCount'] ?? 0,
|
||||
'helpCount': user['helpCount'] ?? 0,
|
||||
'comboCount': user['comboCount'] ?? 0,
|
||||
'totalDeluxscore': user['totalDeluxscore'] ?? 0,
|
||||
'totalBasicDeluxscore': user['totalBasicDeluxscore'] ?? 0,
|
||||
'totalAdvancedDeluxscore': user['totalAdvancedDeluxscore'] ?? 0,
|
||||
'totalExpertDeluxscore': user['totalExpertDeluxscore'] ?? 0,
|
||||
'totalMasterDeluxscore': user['totalMasterDeluxscore'] ?? 0,
|
||||
'totalReMasterDeluxscore': user['totalReMasterDeluxscore'] ?? 0,
|
||||
'totalSync': user['totalSync'] ?? 0,
|
||||
'totalBasicSync': user['totalBasicSync'] ?? 0,
|
||||
'totalAdvancedSync': user['totalAdvancedSync'] ?? 0,
|
||||
'totalExpertSync': user['totalExpertSync'] ?? 0,
|
||||
'totalMasterSync': user['totalMasterSync'] ?? 0,
|
||||
'totalReMasterSync': user['totalReMasterSync'] ?? 0,
|
||||
'totalAchievement': user['totalAchievement'] ?? 0,
|
||||
'totalBasicAchievement': user['totalBasicAchievement'] ?? 0,
|
||||
'totalAdvancedAchievement': user['totalAdvancedAchievement'] ?? 0,
|
||||
'totalExpertAchievement': user['totalExpertAchievement'] ?? 0,
|
||||
'totalMasterAchievement': user['totalMasterAchievement'] ?? 0,
|
||||
'totalReMasterAchievement': user['totalReMasterAchievement'] ?? 0,
|
||||
'playerOldRating': user['playerOldRating'] ?? 0,
|
||||
'playerNewRating': user['playerNewRating'] ?? 0,
|
||||
'banState': userData['banState'] ?? 0,
|
||||
'friendRegistSkip': user['friendRegistSkip'] ?? 0,
|
||||
'dateTime': now,
|
||||
}
|
||||
],
|
||||
'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': _buildMissionDataList(userMissionData),
|
||||
'userWeeklyData': {
|
||||
'lastLoginWeek': userMissionData['userWeeklyData']?['lastLoginWeek'] ?? 0,
|
||||
'beforeLoginWeek': userMissionData['userWeeklyData']?['beforeLoginWeek'] ?? 0,
|
||||
'friendBonusFlag': userMissionData['userWeeklyData']?['friendBonusFlag'] ?? 0,
|
||||
},
|
||||
'userGamePlaylogList': [
|
||||
{
|
||||
'playlogId': loginId,
|
||||
'version': user['lastRomVersion'] ?? 0,
|
||||
'playDate': _formatDateTime(DateTime.now()),
|
||||
'playMode': 0,
|
||||
'useTicketId': -1,
|
||||
'playCredit': 1,
|
||||
'playTrack': 1,
|
||||
'clientId': _config.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': '',
|
||||
},
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UpsertUserAllApi errorId=$errorId');
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _buildMissionDataList(
|
||||
Map<String, dynamic> userMissionData) {
|
||||
final list = userMissionData['userMissionDataList'] as List<dynamic>? ?? [];
|
||||
|
||||
return list.take(6).map((item) {
|
||||
final m = item as Map<String, dynamic>;
|
||||
return {
|
||||
'type': m['type'] ?? 0,
|
||||
'difficulty': m['difficulty'] ?? 0,
|
||||
'targetGenreId': m['targetGenreId'] ?? 0,
|
||||
'targetGenreTableId': m['targetGenreTableId'] ?? 0,
|
||||
'conditionGenreId': m['conditionGenreId'] ?? 0,
|
||||
'conditionGenreTableId': m['conditionGenreTableId'] ?? 0,
|
||||
'clearFlag': m['clearFlag'] ?? 0,
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
String _formatDate(DateTime dt) {
|
||||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
String _formatDateTime(DateTime dt) {
|
||||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} '
|
||||
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}.0';
|
||||
}
|
||||
}
|
||||
|
||||
class _RandomHelper {
|
||||
final _random = Random();
|
||||
|
||||
int nextInt(int max) => _random.nextInt(max);
|
||||
}
|
||||
Reference in New Issue
Block a user