You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
6.8 KiB
Dart

2 years ago
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:isekai_wiki/api/base_api.dart';
2 years ago
import 'package:isekai_wiki/api/response/csrf_token.dart';
2 years ago
import 'package:isekai_wiki/global.dart';
import 'package:json_annotation/json_annotation.dart';
2 years ago
part 'mw_api.g.dart';
@JsonSerializable()
class MWApiErrorException implements Exception {
String code;
2 years ago
String? info;
@JsonKey(name: "docref")
2 years ago
String? detail;
MWApiErrorException({required this.code, this.info, this.detail});
2 years ago
factory MWApiErrorException.fromJson(Map<String, dynamic> json) =>
_$MWApiErrorExceptionFromJson(json);
2 years ago
Map<String, dynamic> toJson() => _$MWApiErrorExceptionToJson(this);
2 years ago
@override
String toString() {
return "MediaWiki Api Error: ${info ?? code}";
2 years ago
}
}
class MWApiEmptyBodyException implements Exception {
dynamic resData;
2 years ago
MWApiEmptyBodyException(this.resData);
2 years ago
@override
String toString() {
return "MediaWiki Api Response body is empty";
2 years ago
}
}
2 years ago
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";
}
}
}
2 years ago
class MWResponse<T> {
2 years ago
Map<String, String>? warnings;
T data;
2 years ago
Map<String, String>? continueInfo;
2 years ago
MWResponse(this.data, {this.warnings, this.continueInfo});
2 years ago
MWResponse<N> replaceData<N>(N data) {
return MWResponse<N>(
data,
2 years ago
warnings: warnings,
continueInfo: continueInfo,
);
2 years ago
}
}
class MWApi {
static Future<MWResponse<Map<String, dynamic>>> get(String action,
{Map<String, dynamic>? params, bool returnRoot = false}) async {
2 years ago
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 {
var apiBaseUri = Uri.parse(Global.siteConfig.apiUrl);
2 years ago
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, returnRoot);
2 years ago
}
static Future<MWResponse<Map<String, dynamic>>> post<T>(String action,
{Map<String, dynamic>? params, String? withToken, bool returnRoot = false}) async {
params ??= {};
params.addAll({
2 years ago
"action": action,
"format": "json",
"formatversion": 2,
2 years ago
"uselang": Global.wikiLang,
});
if (Global.webOrigin != null) {
params["origin"] = Global.webOrigin!;
2 years ago
}
var resText = "";
try {
2 years ago
if (withToken != null) {
// 获取CSRF Token
params["token"] = await getToken(type: withToken);
2 years ago
}
var apiBaseUri = Uri.parse(Global.siteConfig.apiUrl);
resText = await BaseApi.post(apiBaseUri, data: params);
} 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;
}
}
}
2 years ago
if (resText.isEmpty) {
// 没有捕获到服务器返回的错误,则抛给上一层
rethrow;
}
2 years ago
}
return parseMWResponse(action, resText, returnRoot);
2 years ago
}
static MWResponse<Map<String, dynamic>> parseMWResponse(
String action, String resJson, bool returnRoot) {
2 years ago
var resData = jsonDecode(resJson);
if (resData is! Map<String, dynamic>) {
throw MWApiEmptyBodyException(resData);
}
2 years ago
// 处理请求错误
if (resData.containsKey("error")) {
throw MWApiErrorException.fromJson(resData["error"]!);
}
2 years ago
// 请求结果
MWResponse<Map<String, dynamic>> mwRes;
if (returnRoot) {
var filteredResData = <String, dynamic>{}..addAll(resData);
filteredResData
..remove("error")
..remove("warnings")
..remove("batchcomplete");
mwRes = MWResponse(filteredResData);
} else {
if (!resData.containsKey(action) || resData[action] is! Map<String, dynamic>) {
throw MWApiEmptyBodyException(resData);
}
mwRes = MWResponse(resData[action] as Map<String, dynamic>);
}
2 years ago
// 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"];
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();
});
2 years ago
}
}
return mwRes;
}
2 years ago
/// 获取TokenToken可能为空
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!;
}
2 years ago
}