登出的请求

main
落雨楓 2 years ago
parent b4f04e5394
commit a8e5d51a3a

@ -61,8 +61,7 @@ class BaseApi {
// Header
dio.interceptors.add(
InterceptorsWrapper(onRequest: (options, handler) {
options.headers["X-IsekaiWikiApp-Version"] =
Global.packageInfo?.version ?? "unknow";
options.headers["X-IsekaiWikiApp-Version"] = Global.packageInfo?.version ?? "unknow";
options.headers["User-Agent"] = "";
return handler.next(options);
}),
@ -91,8 +90,7 @@ class BaseApi {
);
if (res.statusCode != null && res.statusCode != 200) {
throw HttpResponseException(res.statusCode!,
statusText: res.statusMessage!);
throw HttpResponseException(res.statusCode!, statusText: res.statusMessage!);
}
return res.data ?? "";
@ -108,4 +106,32 @@ class BaseApi {
return {};
}
}
static Future<String> post(Uri uri, {Map<String, dynamic>? search, dynamic data}) async {
var client = await getClient();
var res = await client.post<String>(
uri.toString(),
queryParameters: search,
data: data,
options: Options(responseType: ResponseType.plain),
);
if (res.statusCode != null && res.statusCode != 200) {
throw HttpResponseException(res.statusCode!, statusText: res.statusMessage!);
}
return res.data ?? "";
}
static Future<Map> postJson(Uri uri, {Map<String, dynamic>? search, dynamic data}) async {
var resText = await post(uri, search: search, data: data);
var resData = jsonDecode(resText);
if (resData is Map) {
return resData;
} else {
return {};
}
}
}

@ -4,10 +4,8 @@ import 'package:isekai_wiki/api/response/recent_changes.dart';
class MWApiList {
///
static Future<MWResponse<List<RecentChangesItem>>> getRecentChanges(
String type,
{int? limit,
Map<String, String>? continueInfo}) async {
static Future<MWResponse<List<RecentChangesItem>>> getRecentChanges(String type,
{int? limit, Map<String, String>? continueInfo}) async {
var query = {
"list": "recentchanges",
"rctype": type,
@ -18,7 +16,7 @@ class MWApiList {
query["rccontinue"] = continueInfo["rccontinue"];
}
var mwRes = await MWApi.get("query", query: query);
var mwRes = await MWApi.get("query", params: query);
var rcRes = RecentChangesResponse.fromJson(mwRes.data);
return mwRes.replaceData(rcRes.recentchanges);
@ -50,12 +48,10 @@ class MWApiList {
var rcResList = await Future.wait([
ignoreRcNew
? Future.value(MWResponse<List<RecentChangesItem>>([]))
: getRecentChanges("new",
limit: limit, continueInfo: continueInfoNew),
: getRecentChanges("new", limit: limit, continueInfo: continueInfoNew),
ignoreRcEdit
? Future.value(MWResponse<List<RecentChangesItem>>([]))
: getRecentChanges("edit",
limit: limit, continueInfo: continueInfoEdit),
: getRecentChanges("edit", limit: limit, continueInfo: continueInfoEdit),
]);
var rcNewRes = rcResList[0];
@ -65,35 +61,25 @@ class MWApiList {
mergedList.sort((a, b) {
// 7
var timeA = a.type == "new"
? a.timestamp.add(const Duration(days: 7))
: a.timestamp;
var timeB = b.type == "new"
? b.timestamp.add(const Duration(days: 7))
: b.timestamp;
var timeA = a.type == "new" ? a.timestamp.add(const Duration(days: 7)) : a.timestamp;
var timeB = b.type == "new" ? b.timestamp.add(const Duration(days: 7)) : b.timestamp;
return timeB.compareTo(timeA);
});
List<RecentChangesItem> uniqueMergedList = [];
for (var page in mergedList) {
if (uniqueMergedList
.indexWhere((element) => element.pageid == page.pageid) ==
-1) {
if (uniqueMergedList.indexWhere((element) => element.pageid == page.pageid) == -1) {
uniqueMergedList.add(page);
}
}
Map<String, String> mergedContinueInfo = {};
if (rcNewRes.continueInfo != null &&
rcNewRes.continueInfo!.containsKey("rccontinue")) {
mergedContinueInfo["rcnewcontinue"] =
rcNewRes.continueInfo!["rccontinue"]!;
if (rcNewRes.continueInfo != null && rcNewRes.continueInfo!.containsKey("rccontinue")) {
mergedContinueInfo["rcnewcontinue"] = rcNewRes.continueInfo!["rccontinue"]!;
}
if (rcEditRes.continueInfo != null &&
rcEditRes.continueInfo!.containsKey("rccontinue")) {
mergedContinueInfo["rceditcontinue"] =
rcEditRes.continueInfo!["rccontinue"]!;
if (rcEditRes.continueInfo != null && rcEditRes.continueInfo!.containsKey("rccontinue")) {
mergedContinueInfo["rceditcontinue"] = rcEditRes.continueInfo!["rccontinue"]!;
}
return MWResponse(
@ -139,14 +125,13 @@ class MWApiList {
query.addAll(extraParams);
}
var mwRes = await MWApi.get("query", query: query);
var mwRes = await MWApi.get("query", params: query);
var pagesRes = PagesResponse.fromJson(mwRes.data);
var pageList = pagesRes.pages.map((pageInfo) {
if (pageInfo.description != null) {
pageInfo.description =
pageInfo.description!.replaceAll(RegExp(r"\n\n"), "\n");
pageInfo.description = pageInfo.description!.replaceAll(RegExp(r"\n\n"), "\n");
}
if (pageInfo.title.contains("/")) {
var splitPos = pageInfo.title.lastIndexOf("/");
@ -171,8 +156,7 @@ class MWApiList {
if (titles != null) {
for (var title in titles) {
var index =
pagesRes.pages.indexWhere((element) => element.title == title);
var index = pagesRes.pages.indexWhere((element) => element.title == title);
if (index != -1) {
sortedPages.add(pagesRes.pages[index]);
}

@ -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;
}
/// 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!;
}
}

@ -1 +0,0 @@
class MWApiToken {}

@ -9,21 +9,20 @@ class MWApiUser {
"method": "startauth",
};
var mwRes = await MWApi.get("mugenapp", query: query);
var mwRes = await MWApi.get("mugenapp", params: query);
var data = MugenAppStartAuthResponse.fromJson(mwRes.data);
return mwRes.replaceData(data.startauth);
}
static Future<MWResponse<MugenAppAttemptAuthInfo>> attemptAuth(
String loginRequestKey) async {
static Future<MWResponse<MugenAppAttemptAuthInfo>> attemptAuth(String loginRequestKey) async {
var query = {
"method": "attemptauth",
"requestkey": loginRequestKey,
};
var mwRes = await MWApi.get("mugenapp", query: query);
var mwRes = await MWApi.get("mugenapp", params: query);
var data = MugenAppAttemptAuthResponse.fromJson(mwRes.data);
@ -36,10 +35,18 @@ class MWApiUser {
"uiprop": "blockinfo|groups|rights|options|email|realname|latestcontrib",
};
var mwRes = await MWApi.get("query", query: query);
var mwRes = await MWApi.get("query", params: query);
var data = MetaUserInfoResponse.fromJson(mwRes.data);
return mwRes.replaceData(data);
}
static Future<void> logout() async {
try {
await MWApi.post("logout", withToken: "csrf");
} on MWApiEmptyBodyException catch (_) {
//
}
}
}

@ -3,26 +3,9 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'csrf_token.freezed.dart';
part 'csrf_token.g.dart';
@freezed
class CSRFTokenInfo with _$CSRFTokenInfo {
const factory CSRFTokenInfo({
String? csrftoken,
String? logintoken,
String? createaccounttoken,
String? patroltoken,
String? rollbacktoken,
String? userrightstoken,
String? watchtoken,
}) = _CSRFTokenInfo;
factory CSRFTokenInfo.fromJson(Map<String, dynamic> json) =>
_$CSRFTokenInfoFromJson(json);
}
@freezed
class CSRFTokenResponse with _$CSRFTokenResponse {
const factory CSRFTokenResponse({required CSRFTokenInfo tokens}) =
_CSRFTokenInfoResponse;
const factory CSRFTokenResponse({required Map<String, String> tokens}) = _CSRFTokenInfoResponse;
factory CSRFTokenResponse.fromJson(Map<String, dynamic> json) =>
_$CSRFTokenResponseFromJson(json);

@ -5,29 +5,51 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'page_info.freezed.dart';
part 'page_info.g.dart';
@freezed
class PageInfo with _$PageInfo {
const PageInfo._();
factory PageInfo({
required int pageid,
required int ns,
required String title,
String? subtitle,
String? displayTitle,
@JsonKey(name: "extract") String? description,
String? contentmodel,
String? pagelanguage,
String? pagelanguagehtmlcode,
String? pagelanguagedir,
bool? inwatchlist,
@JsonKey(name: "touched") DateTime? updatedTime,
int? lastrevid,
int? length,
String? fullurl,
String? editurl,
String? canonicalurl,
}) = _PageInfo;
@JsonSerializable()
class PageInfo {
int pageid;
int ns;
String title;
String? subtitle;
String? displayTitle;
@JsonKey(name: "extract")
String? description;
String? contentmodel;
String? pagelanguage;
String? pagelanguagehtmlcode;
String? pagelanguagedir;
bool? inwatchlist;
@JsonKey(name: "touched")
DateTime? updatedTime;
int? lastrevid;
int? length;
String? fullurl;
String? editurl;
String? canonicalurl;
PageInfo({
required this.pageid,
required this.ns,
required this.title,
this.subtitle,
this.displayTitle,
this.description,
this.contentmodel,
this.pagelanguage,
this.pagelanguagehtmlcode,
this.pagelanguagedir,
this.inwatchlist,
this.updatedTime,
this.lastrevid,
this.length,
this.fullurl,
this.editurl,
this.canonicalurl,
});
String get mainTitle {
return displayTitle ?? title;
@ -38,6 +60,8 @@ class PageInfo with _$PageInfo {
}
factory PageInfo.fromJson(Map<String, dynamic> json) => _$PageInfoFromJson(json);
Map<String, dynamic> toJson() => _$PageInfoToJson(this);
}
@freezed

@ -235,6 +235,19 @@ class UserController extends GetxController {
}
Future<void> logout({bool logoutRemote = true}) async {
authProcessing.value = true;
try {
await MWApiUser.logout();
} catch (err, stack) {
alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err), title: "错误");
if (kDebugMode) {
print("Exception in logout: $err");
stack.printError();
}
return;
}
// Cookie
await BaseApi.clearCookie();
@ -244,6 +257,8 @@ class UserController extends GetxController {
nickName.value = "";
avatarUrlSet.clear();
authProcessing.value = true;
saveToStorage();
}
}

Loading…
Cancel
Save