refactor: 拆分主页与公共组件,扫码登录改走 WinHTTP
- 抽出 lib/core 的日期/JSON 工具,收敛各页重复解析 - 主页与音乐风险页拆成组件,风险功能共用一套运行框架 - 修复设置页作为主页页签保存时弹掉根路由导致的黑屏 - 登录对齐 Astral aimedb 协议:84 位二维码校验、签名与请求头修正, 补 GlobalSign 中间证书;Windows 改用 WinHTTP 绕过服务端对 BoringSSL 的 TLS 指纹拦截(证书校验保持开启) - 补开发预览数据与测试
This commit is contained in:
1 parent
0cb5320290
commit
e9357f5969
55 files changed
+4387
-3392
No files matched your search
@@ -8,6 +8,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:pointycastle/export.dart';
|
||||
|
||||
import '../config/title_server_config.dart';
|
||||
import '../core/date_time_format.dart';
|
||||
import '../models/user_data.dart';
|
||||
import 'api_log.dart';
|
||||
import '../models/user_character.dart';
|
||||
@@ -22,6 +23,12 @@ class TitleApiException implements Exception {
|
||||
String toString() => 'TitleApiException: $message';
|
||||
}
|
||||
|
||||
/// 面向用户的错误文本:协议层异常只取 message,其余保留 `toString()` 便于排查。
|
||||
String describeApiError(Object error) =>
|
||||
error is TitleApiException ? error.message : error.toString();
|
||||
|
||||
typedef CookiesChangedCallback = void Function(String cookies);
|
||||
|
||||
class UserLoginResult {
|
||||
final String token;
|
||||
final int loginId;
|
||||
@@ -39,21 +46,37 @@ class UserLoginResult {
|
||||
class TitleApiService {
|
||||
static const String _obfuscateConstant = 'MaimaiChn';
|
||||
|
||||
static String? lastRawResponse;
|
||||
/// 最近一次 `captureRaw` 请求的响应原文(主页调试卡用)。
|
||||
String? lastRawResponse;
|
||||
|
||||
final TitleServerConfig _config;
|
||||
final CookiesChangedCallback? onCookiesChanged;
|
||||
String? _cookies;
|
||||
|
||||
TitleApiService(this._config, {this._cookies});
|
||||
// 私有命名参数不合法(不能写 `this._cookies`),只能手写初始化。
|
||||
TitleApiService(
|
||||
this._config, {
|
||||
String? cookies,
|
||||
this.onCookiesChanged,
|
||||
}) :
|
||||
// ignore: prefer_initializing_formals
|
||||
_cookies = cookies;
|
||||
|
||||
/// 从 [TitleServerConfigHolder] 构造;未配置时返回 `null`。
|
||||
///
|
||||
/// 各页原本都写 `if (!holder.isConfigured) ...; TitleApiService(holder.config!, ...)`,
|
||||
/// 收口成一个工厂,避免 `config!` 与 `isConfigured` 散落在十几个地方。
|
||||
static TitleApiService? fromHolder({String? cookies}) {
|
||||
static TitleApiService? fromHolder({
|
||||
String? cookies,
|
||||
CookiesChangedCallback? onCookiesChanged,
|
||||
}) {
|
||||
final holder = TitleServerConfigHolder();
|
||||
if (!holder.isConfigured) return null;
|
||||
return TitleApiService(holder.config!, cookies: cookies);
|
||||
return TitleApiService(
|
||||
holder.config!,
|
||||
cookies: cookies,
|
||||
onCookiesChanged: onCookiesChanged,
|
||||
);
|
||||
}
|
||||
|
||||
String? get cookies => _cookies;
|
||||
@@ -122,6 +145,7 @@ class TitleApiService {
|
||||
if (match != null) {
|
||||
_cookies = 'JSESSIONID=${match.group(1)!.trim()}';
|
||||
ApiLog.log('[$apiName] >>> Captured session cookie: $_cookies');
|
||||
onCookiesChanged?.call(_cookies!);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -243,8 +267,8 @@ class TitleApiService {
|
||||
required int userId,
|
||||
required String token,
|
||||
}) async {
|
||||
// 走通用传输层,保留 `lastRawResponse`(主页调试卡要用 GetUserPreviewApi
|
||||
// 的原文,而不是随后 UserLoginApi / GetUserDataApi 的)。
|
||||
// 走通用传输层,把 GetUserPreviewApi 的原文留在实例上(主页调试卡要用它,
|
||||
// 而不是随后 UserLoginApi / GetUserDataApi 的响应)。
|
||||
final json = await _callApi(
|
||||
'GetUserPreviewApi',
|
||||
{
|
||||
@@ -386,10 +410,8 @@ class TitleApiService {
|
||||
final now = DateTime.now();
|
||||
final validDate = DateTime(now.year, now.month, now.day, 4, 0, 0)
|
||||
.add(const Duration(days: 90));
|
||||
final purchaseDateStr = _formatDateTime(now);
|
||||
final validDateStr =
|
||||
'${validDate.year}-${validDate.month.toString().padLeft(2, '0')}-${validDate.day.toString().padLeft(2, '0')} '
|
||||
'${validDate.hour.toString().padLeft(2, '0')}:${validDate.minute.toString().padLeft(2, '0')}:${validDate.second.toString().padLeft(2, '0')}';
|
||||
final purchaseDateStr = formatPlayDateTime(now);
|
||||
final validDateStr = formatDateTime(validDate);
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
@@ -416,11 +438,6 @@ class TitleApiService {
|
||||
_checkReturnCode(json, apiName);
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
// ---- UpsertUserAll (发包) ----
|
||||
|
||||
/// Upload a single playlog record (fake game log).
|
||||
|
||||
Reference in new issue
Block a user