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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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