|
|
|
@ -1,88 +1,64 @@
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:io';
|
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
|
import 'package:isekai_wiki/api/base_api.dart';
|
|
|
|
|
|
|
|
|
|
import 'package:isekai_wiki/global.dart';
|
|
|
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
|
|
|
|
|
|
import '../../utils/api_utils.dart';
|
|
|
|
|
part 'mw_api.g.dart';
|
|
|
|
|
|
|
|
|
|
@JsonSerializable()
|
|
|
|
|
class MWApiErrorException implements Exception {
|
|
|
|
|
String code;
|
|
|
|
|
|
|
|
|
|
class MWError {
|
|
|
|
|
String? code;
|
|
|
|
|
String? info;
|
|
|
|
|
|
|
|
|
|
@JsonKey(name: "*")
|
|
|
|
|
String? detail;
|
|
|
|
|
|
|
|
|
|
MWError({this.code, this.info, this.detail});
|
|
|
|
|
MWApiErrorException({required this.code, this.info, this.detail});
|
|
|
|
|
|
|
|
|
|
static MWError fromMap(Map errorMap) {
|
|
|
|
|
var mwError = MWError();
|
|
|
|
|
if (errorMap.containsKey("code")) {
|
|
|
|
|
mwError.code = errorMap["code"].toString();
|
|
|
|
|
}
|
|
|
|
|
factory MWApiErrorException.fromJson(Map<String, dynamic> json) =>
|
|
|
|
|
_$MWApiExceptionFromJson(json);
|
|
|
|
|
|
|
|
|
|
if (errorMap.containsKey("info")) {
|
|
|
|
|
mwError.info = errorMap["info"].toString();
|
|
|
|
|
}
|
|
|
|
|
Map<String, dynamic> toJson() => _$MWApiExceptionToJson(this);
|
|
|
|
|
|
|
|
|
|
if (errorMap.containsKey("*")) {
|
|
|
|
|
mwError.detail = errorMap["*"].toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mwError;
|
|
|
|
|
@override
|
|
|
|
|
String toString() {
|
|
|
|
|
return "MediaWiki Api Error: ${info ?? code}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MWMultiError extends Error {
|
|
|
|
|
List<MWError> errorList;
|
|
|
|
|
class MWApiEmptyBodyException implements Exception {
|
|
|
|
|
dynamic resData;
|
|
|
|
|
|
|
|
|
|
MWMultiError(this.errorList);
|
|
|
|
|
MWApiEmptyBodyException(this.resData);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String toString() {
|
|
|
|
|
return errorList.map((e) => e.info).join("\n");
|
|
|
|
|
return "MediaWiki Api Response body is empty";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MWResponse<T> {
|
|
|
|
|
bool ok = false;
|
|
|
|
|
List<MWError>? errorList;
|
|
|
|
|
T? data;
|
|
|
|
|
List<dynamic>? warnList;
|
|
|
|
|
T data;
|
|
|
|
|
Map<String, String>? continueInfo;
|
|
|
|
|
|
|
|
|
|
MWResponse({this.ok = false, this.errorList, this.data, this.continueInfo});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MWApiClient extends http.BaseClient {
|
|
|
|
|
final http.Client _inner;
|
|
|
|
|
|
|
|
|
|
MWApiClient(this._inner);
|
|
|
|
|
MWResponse(this.data, {this.warnList, this.continueInfo});
|
|
|
|
|
|
|
|
|
|
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
|
|
|
|
request.headers['user-agent'] = await ApiUtils.getUserAgent();
|
|
|
|
|
return await _inner.send(request);
|
|
|
|
|
MWResponse<N> replaceData<N>(N data) {
|
|
|
|
|
return MWResponse<N>(
|
|
|
|
|
data,
|
|
|
|
|
warnList: warnList,
|
|
|
|
|
continueInfo: continueInfo,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MWApi {
|
|
|
|
|
static Uri apiBaseUri = Uri.parse(Global.wikiApiUrl);
|
|
|
|
|
|
|
|
|
|
static HttpClient getHttpClient() {
|
|
|
|
|
return HttpClient();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Future<Map<String, String>> _getHeaders() async {
|
|
|
|
|
var headers = {
|
|
|
|
|
"X-IsekaiWikiApp-Version": Global.packageInfo?.version ?? "unknow",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!kIsWeb) {
|
|
|
|
|
headers["User-Agent"] = await ApiUtils.getUserAgent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return headers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Future<MWResponse<Map<String, dynamic>>> get(String action,
|
|
|
|
|
{Map<String, dynamic>? query}) async {
|
|
|
|
|
Map<String, String> queryStr =
|
|
|
|
@ -97,58 +73,62 @@ class MWApi {
|
|
|
|
|
queryStr["origin"] = Global.webOrigin!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Uri requestUri = apiBaseUri.replace(queryParameters: queryStr);
|
|
|
|
|
|
|
|
|
|
var res = await http.get(requestUri, headers: await _getHeaders());
|
|
|
|
|
var resText = "";
|
|
|
|
|
try {
|
|
|
|
|
resText = await BaseApi.get(apiBaseUri, search: queryStr);
|
|
|
|
|
} on DioError catch (err) {
|
|
|
|
|
if (err.type == DioErrorType.response) {
|
|
|
|
|
if (err.response != null) {
|
|
|
|
|
var response = err.response!;
|
|
|
|
|
var contentType = response.headers[Headers.contentTypeHeader];
|
|
|
|
|
if (contentType != null &&
|
|
|
|
|
contentType.contains("application/json") &&
|
|
|
|
|
response.data is String) {
|
|
|
|
|
resText = response.data as String;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res.statusCode != 200) {
|
|
|
|
|
throw HttpResponseCodeError(res.statusCode);
|
|
|
|
|
if (resText.isEmpty) {
|
|
|
|
|
// 没有捕获到服务器返回的错误,则抛给上一层
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var responseBody = res.body;
|
|
|
|
|
|
|
|
|
|
return parseMWResponse(action, responseBody);
|
|
|
|
|
return parseMWResponse(action, resText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static MWResponse<Map<String, dynamic>> parseMWResponse(String action, String resJson) {
|
|
|
|
|
var mwRes = MWResponse<Map<String, dynamic>>();
|
|
|
|
|
List<MWError> errorList = [];
|
|
|
|
|
|
|
|
|
|
static MWResponse<Map<String, dynamic>> parseMWResponse(
|
|
|
|
|
String action, String resJson) {
|
|
|
|
|
var resData = jsonDecode(resJson);
|
|
|
|
|
if (resData is Map) {
|
|
|
|
|
// 处理请求错误
|
|
|
|
|
var resError = resData["error"];
|
|
|
|
|
if (resError is Map) {
|
|
|
|
|
errorList.add(MWError.fromMap(resError));
|
|
|
|
|
} else if (resError is List) {
|
|
|
|
|
for (var errItem in resError) {
|
|
|
|
|
if (errItem is Map) {
|
|
|
|
|
errorList.add(MWError.fromMap(errItem));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (errorList.isNotEmpty) {
|
|
|
|
|
mwRes.errorList = errorList;
|
|
|
|
|
return mwRes;
|
|
|
|
|
}
|
|
|
|
|
if (resData is! Map<String, dynamic>) {
|
|
|
|
|
throw MWApiEmptyBodyException(resData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 请求结果
|
|
|
|
|
if (resData.containsKey(action) && resData[action] is Map) {
|
|
|
|
|
mwRes.data = resData[action] as Map<String, dynamic>;
|
|
|
|
|
mwRes.ok = true;
|
|
|
|
|
}
|
|
|
|
|
// 处理请求错误
|
|
|
|
|
if (resData.containsKey("error")) {
|
|
|
|
|
throw MWApiErrorException.fromJson(resData["error"]!);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 继续查询参数
|
|
|
|
|
var batchcomplete = resData["batchcomplete"];
|
|
|
|
|
if (batchcomplete is bool && batchcomplete) {
|
|
|
|
|
var continueInfo = resData["continue"];
|
|
|
|
|
if (continueInfo is Map) {
|
|
|
|
|
mwRes.continueInfo = {};
|
|
|
|
|
continueInfo.forEach((key, value) {
|
|
|
|
|
var keyStr = key.toString();
|
|
|
|
|
mwRes.continueInfo![keyStr] = value.toString();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// 请求结果
|
|
|
|
|
if (!resData.containsKey(action) ||
|
|
|
|
|
resData[action] is! Map<String, dynamic>) {
|
|
|
|
|
throw MWApiEmptyBodyException(resData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MWResponse<Map<String, dynamic>> mwRes =
|
|
|
|
|
MWResponse(resData[action] as Map<String, dynamic>);
|
|
|
|
|
|
|
|
|
|
// continue参数
|
|
|
|
|
var batchcomplete = resData["batchcomplete"];
|
|
|
|
|
if (batchcomplete is bool && batchcomplete) {
|
|
|
|
|
var continueInfo = resData["continue"];
|
|
|
|
|
if (continueInfo is Map<String, dynamic>) {
|
|
|
|
|
mwRes.continueInfo = {};
|
|
|
|
|
continueInfo.forEach((key, value) {
|
|
|
|
|
var keyStr = key;
|
|
|
|
|
mwRes.continueInfo![keyStr] = value.toString();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|