|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
|
import 'package:isekai_wiki/api/base_api.dart';
|
|
|
|
|
import 'package:isekai_wiki/api/response/csrf_token.dart';
|
|
|
|
|
|
|
|
|
|
import 'package:isekai_wiki/global.dart';
|
|
|
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
@ -40,17 +41,31 @@ class MWApiEmptyBodyException implements Exception {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class CannotFetchCSRFTokenException implements Exception {
|
|
|
|
|
String? message;
|
|
|
|
|
CannotFetchCSRFTokenException([this.message]);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String toString() {
|
|
|
|
|
if (message != null) {
|
|
|
|
|
return message!;
|
|
|
|
|
} else {
|
|
|
|
|
return "Cannot fetch CSRF token from MediaWiki API";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MWResponse<T> {
|
|
|
|
|
List<dynamic>? warnList;
|
|
|
|
|
Map<String, String>? warnings;
|
|
|
|
|
T data;
|
|
|
|
|
Map<String, String>? continueInfo;
|
|
|
|
|
|
|
|
|
|
MWResponse(this.data, {this.warnList, this.continueInfo});
|
|
|
|
|
MWResponse(this.data, {this.warnings, this.continueInfo});
|
|
|
|
|
|
|
|
|
|
MWResponse<N> replaceData<N>(N data) {
|
|
|
|
|
return MWResponse<N>(
|
|
|
|
|
data,
|
|
|
|
|
warnList: warnList,
|
|
|
|
|
warnings: warnings,
|
|
|
|
|
continueInfo: continueInfo,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
@ -60,22 +75,66 @@ class MWApi {
|
|
|
|
|
static Uri apiBaseUri = Uri.parse(Global.wikiApiUrl);
|
|
|
|
|
|
|
|
|
|
static Future<MWResponse<Map<String, dynamic>>> get(String action,
|
|
|
|
|
{Map<String, dynamic>? query}) async {
|
|
|
|
|
Map<String, String> queryStr =
|
|
|
|
|
query?.map((key, value) => MapEntry(key, value.toString())) ?? {};
|
|
|
|
|
queryStr.addAll({
|
|
|
|
|
{Map<String, dynamic>? params}) async {
|
|
|
|
|
Map<String, String> paramsStr =
|
|
|
|
|
params?.map((key, value) => MapEntry(key, value.toString())) ?? {};
|
|
|
|
|
paramsStr.addAll({
|
|
|
|
|
"action": action,
|
|
|
|
|
"format": "json",
|
|
|
|
|
"formatversion": "2",
|
|
|
|
|
"uselang": Global.wikiLang,
|
|
|
|
|
});
|
|
|
|
|
if (Global.webOrigin != null) {
|
|
|
|
|
paramsStr["origin"] = Global.webOrigin!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resText = "";
|
|
|
|
|
try {
|
|
|
|
|
resText = await BaseApi.get(apiBaseUri, search: paramsStr);
|
|
|
|
|
} 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 (resText.isEmpty) {
|
|
|
|
|
// 没有捕获到服务器返回的错误,则抛给上一层
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parseMWResponse(action, resText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Future<MWResponse<Map<String, dynamic>>> post(String action,
|
|
|
|
|
{Map<String, dynamic>? params, String? withToken}) async {
|
|
|
|
|
Map<String, String> paramsStr =
|
|
|
|
|
params?.map((key, value) => MapEntry(key, value.toString())) ?? {};
|
|
|
|
|
paramsStr.addAll({
|
|
|
|
|
"action": action,
|
|
|
|
|
"format": "json",
|
|
|
|
|
"formatversion": "2",
|
|
|
|
|
"uselang": Global.wikiLang,
|
|
|
|
|
});
|
|
|
|
|
if (Global.webOrigin != null) {
|
|
|
|
|
queryStr["origin"] = Global.webOrigin!;
|
|
|
|
|
paramsStr["origin"] = Global.webOrigin!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resText = "";
|
|
|
|
|
try {
|
|
|
|
|
resText = await BaseApi.get(apiBaseUri, search: queryStr);
|
|
|
|
|
if (withToken != null) {
|
|
|
|
|
// 获取CSRF Token
|
|
|
|
|
paramsStr["token"] = await getToken(type: withToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resText = await BaseApi.post(apiBaseUri, data: paramsStr);
|
|
|
|
|
} on DioError catch (err) {
|
|
|
|
|
if (err.type == DioErrorType.response) {
|
|
|
|
|
if (err.response != null) {
|
|
|
|
@ -98,8 +157,7 @@ class MWApi {
|
|
|
|
|
return parseMWResponse(action, resText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static MWResponse<Map<String, dynamic>> parseMWResponse(
|
|
|
|
|
String action, String resJson) {
|
|
|
|
|
static MWResponse<Map<String, dynamic>> parseMWResponse(String action, String resJson) {
|
|
|
|
|
var resData = jsonDecode(resJson);
|
|
|
|
|
if (resData is! Map<String, dynamic>) {
|
|
|
|
|
throw MWApiEmptyBodyException(resData);
|
|
|
|
@ -111,13 +169,26 @@ class MWApi {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 请求结果
|
|
|
|
|
if (!resData.containsKey(action) ||
|
|
|
|
|
resData[action] is! Map<String, dynamic>) {
|
|
|
|
|
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>);
|
|
|
|
|
MWResponse<Map<String, dynamic>> mwRes = MWResponse(resData[action] as Map<String, dynamic>);
|
|
|
|
|
|
|
|
|
|
// warnings
|
|
|
|
|
if (resData.containsKey("warnings") && resData["warnings"] is Map<String, dynamic>) {
|
|
|
|
|
var warnings = resData["warnings"] as Map<String, dynamic>;
|
|
|
|
|
Map<String, String> warningMap = {};
|
|
|
|
|
warnings.forEach((key, value) {
|
|
|
|
|
if (value is Map<String, String> && value.containsKey("*")) {
|
|
|
|
|
var valueStr = value["*"] as String;
|
|
|
|
|
warningMap[key] = valueStr;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (warningMap.isNotEmpty) {
|
|
|
|
|
mwRes.warnings = warningMap;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// continue参数
|
|
|
|
|
var batchcomplete = resData["batchcomplete"];
|
|
|
|
@ -134,4 +205,33 @@ class MWApi {
|
|
|
|
|
|
|
|
|
|
return mwRes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 获取Token,Token可能为空
|
|
|
|
|
static Future<MWResponse<String?>> getTokenRaw({String type = "csrf"}) async {
|
|
|
|
|
var query = {
|
|
|
|
|
"meta": "tokens",
|
|
|
|
|
"type": type,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var mwRes = await MWApi.get("query", params: query);
|
|
|
|
|
|
|
|
|
|
var data = CSRFTokenResponse.fromJson(mwRes.data);
|
|
|
|
|
var tokenKey = "${type}token";
|
|
|
|
|
var token = data.tokens[tokenKey];
|
|
|
|
|
|
|
|
|
|
return mwRes.replaceData(token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 获取Token,并在无法获取时抛出异常
|
|
|
|
|
static Future<String> getToken({String type = "csrf"}) async {
|
|
|
|
|
var tokenRes = await getTokenRaw(type: type);
|
|
|
|
|
if (tokenRes.data == null) {
|
|
|
|
|
if (tokenRes.warnings != null && tokenRes.warnings!.containsKey("tokens")) {
|
|
|
|
|
throw CannotFetchCSRFTokenException(tokenRes.warnings!["tokens"]);
|
|
|
|
|
} else {
|
|
|
|
|
throw CannotFetchCSRFTokenException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return tokenRes.data!;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|