登出的请求

main
落雨楓 2 years ago
parent b4f04e5394
commit a8e5d51a3a

@ -61,8 +61,7 @@ class BaseApi {
// Header // Header
dio.interceptors.add( dio.interceptors.add(
InterceptorsWrapper(onRequest: (options, handler) { InterceptorsWrapper(onRequest: (options, handler) {
options.headers["X-IsekaiWikiApp-Version"] = options.headers["X-IsekaiWikiApp-Version"] = Global.packageInfo?.version ?? "unknow";
Global.packageInfo?.version ?? "unknow";
options.headers["User-Agent"] = ""; options.headers["User-Agent"] = "";
return handler.next(options); return handler.next(options);
}), }),
@ -91,8 +90,7 @@ class BaseApi {
); );
if (res.statusCode != null && res.statusCode != 200) { if (res.statusCode != null && res.statusCode != 200) {
throw HttpResponseException(res.statusCode!, throw HttpResponseException(res.statusCode!, statusText: res.statusMessage!);
statusText: res.statusMessage!);
} }
return res.data ?? ""; return res.data ?? "";
@ -108,4 +106,32 @@ class BaseApi {
return {}; 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 { class MWApiList {
/// ///
static Future<MWResponse<List<RecentChangesItem>>> getRecentChanges( static Future<MWResponse<List<RecentChangesItem>>> getRecentChanges(String type,
String type, {int? limit, Map<String, String>? continueInfo}) async {
{int? limit,
Map<String, String>? continueInfo}) async {
var query = { var query = {
"list": "recentchanges", "list": "recentchanges",
"rctype": type, "rctype": type,
@ -18,7 +16,7 @@ class MWApiList {
query["rccontinue"] = continueInfo["rccontinue"]; 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); var rcRes = RecentChangesResponse.fromJson(mwRes.data);
return mwRes.replaceData(rcRes.recentchanges); return mwRes.replaceData(rcRes.recentchanges);
@ -50,12 +48,10 @@ class MWApiList {
var rcResList = await Future.wait([ var rcResList = await Future.wait([
ignoreRcNew ignoreRcNew
? Future.value(MWResponse<List<RecentChangesItem>>([])) ? Future.value(MWResponse<List<RecentChangesItem>>([]))
: getRecentChanges("new", : getRecentChanges("new", limit: limit, continueInfo: continueInfoNew),
limit: limit, continueInfo: continueInfoNew),
ignoreRcEdit ignoreRcEdit
? Future.value(MWResponse<List<RecentChangesItem>>([])) ? Future.value(MWResponse<List<RecentChangesItem>>([]))
: getRecentChanges("edit", : getRecentChanges("edit", limit: limit, continueInfo: continueInfoEdit),
limit: limit, continueInfo: continueInfoEdit),
]); ]);
var rcNewRes = rcResList[0]; var rcNewRes = rcResList[0];
@ -65,35 +61,25 @@ class MWApiList {
mergedList.sort((a, b) { mergedList.sort((a, b) {
// 7 // 7
var timeA = a.type == "new" var timeA = a.type == "new" ? a.timestamp.add(const Duration(days: 7)) : a.timestamp;
? a.timestamp.add(const Duration(days: 7)) var timeB = b.type == "new" ? b.timestamp.add(const Duration(days: 7)) : b.timestamp;
: a.timestamp;
var timeB = b.type == "new"
? b.timestamp.add(const Duration(days: 7))
: b.timestamp;
return timeB.compareTo(timeA); return timeB.compareTo(timeA);
}); });
List<RecentChangesItem> uniqueMergedList = []; List<RecentChangesItem> uniqueMergedList = [];
for (var page in mergedList) { for (var page in mergedList) {
if (uniqueMergedList if (uniqueMergedList.indexWhere((element) => element.pageid == page.pageid) == -1) {
.indexWhere((element) => element.pageid == page.pageid) ==
-1) {
uniqueMergedList.add(page); uniqueMergedList.add(page);
} }
} }
Map<String, String> mergedContinueInfo = {}; Map<String, String> mergedContinueInfo = {};
if (rcNewRes.continueInfo != null && if (rcNewRes.continueInfo != null && rcNewRes.continueInfo!.containsKey("rccontinue")) {
rcNewRes.continueInfo!.containsKey("rccontinue")) { mergedContinueInfo["rcnewcontinue"] = rcNewRes.continueInfo!["rccontinue"]!;
mergedContinueInfo["rcnewcontinue"] =
rcNewRes.continueInfo!["rccontinue"]!;
} }
if (rcEditRes.continueInfo != null && if (rcEditRes.continueInfo != null && rcEditRes.continueInfo!.containsKey("rccontinue")) {
rcEditRes.continueInfo!.containsKey("rccontinue")) { mergedContinueInfo["rceditcontinue"] = rcEditRes.continueInfo!["rccontinue"]!;
mergedContinueInfo["rceditcontinue"] =
rcEditRes.continueInfo!["rccontinue"]!;
} }
return MWResponse( return MWResponse(
@ -139,14 +125,13 @@ class MWApiList {
query.addAll(extraParams); 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 pagesRes = PagesResponse.fromJson(mwRes.data);
var pageList = pagesRes.pages.map((pageInfo) { var pageList = pagesRes.pages.map((pageInfo) {
if (pageInfo.description != null) { if (pageInfo.description != null) {
pageInfo.description = pageInfo.description = pageInfo.description!.replaceAll(RegExp(r"\n\n"), "\n");
pageInfo.description!.replaceAll(RegExp(r"\n\n"), "\n");
} }
if (pageInfo.title.contains("/")) { if (pageInfo.title.contains("/")) {
var splitPos = pageInfo.title.lastIndexOf("/"); var splitPos = pageInfo.title.lastIndexOf("/");
@ -171,8 +156,7 @@ class MWApiList {
if (titles != null) { if (titles != null) {
for (var title in titles) { for (var title in titles) {
var index = var index = pagesRes.pages.indexWhere((element) => element.title == title);
pagesRes.pages.indexWhere((element) => element.title == title);
if (index != -1) { if (index != -1) {
sortedPages.add(pagesRes.pages[index]); sortedPages.add(pagesRes.pages[index]);
} }

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:isekai_wiki/api/base_api.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:isekai_wiki/global.dart';
import 'package:json_annotation/json_annotation.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> { class MWResponse<T> {
List<dynamic>? warnList; Map<String, String>? warnings;
T data; T data;
Map<String, String>? continueInfo; Map<String, String>? continueInfo;
MWResponse(this.data, {this.warnList, this.continueInfo}); MWResponse(this.data, {this.warnings, this.continueInfo});
MWResponse<N> replaceData<N>(N data) { MWResponse<N> replaceData<N>(N data) {
return MWResponse<N>( return MWResponse<N>(
data, data,
warnList: warnList, warnings: warnings,
continueInfo: continueInfo, continueInfo: continueInfo,
); );
} }
@ -60,22 +75,22 @@ class MWApi {
static Uri apiBaseUri = Uri.parse(Global.wikiApiUrl); static Uri apiBaseUri = Uri.parse(Global.wikiApiUrl);
static Future<MWResponse<Map<String, dynamic>>> get(String action, static Future<MWResponse<Map<String, dynamic>>> get(String action,
{Map<String, dynamic>? query}) async { {Map<String, dynamic>? params}) async {
Map<String, String> queryStr = Map<String, String> paramsStr =
query?.map((key, value) => MapEntry(key, value.toString())) ?? {}; params?.map((key, value) => MapEntry(key, value.toString())) ?? {};
queryStr.addAll({ paramsStr.addAll({
"action": action, "action": action,
"format": "json", "format": "json",
"formatversion": "2", "formatversion": "2",
"uselang": Global.wikiLang, "uselang": Global.wikiLang,
}); });
if (Global.webOrigin != null) { if (Global.webOrigin != null) {
queryStr["origin"] = Global.webOrigin!; paramsStr["origin"] = Global.webOrigin!;
} }
var resText = ""; var resText = "";
try { try {
resText = await BaseApi.get(apiBaseUri, search: queryStr); resText = await BaseApi.get(apiBaseUri, search: paramsStr);
} on DioError catch (err) { } on DioError catch (err) {
if (err.type == DioErrorType.response) { if (err.type == DioErrorType.response) {
if (err.response != null) { if (err.response != null) {
@ -98,8 +113,51 @@ class MWApi {
return parseMWResponse(action, resText); return parseMWResponse(action, resText);
} }
static MWResponse<Map<String, dynamic>> parseMWResponse( static Future<MWResponse<Map<String, dynamic>>> post(String action,
String action, String resJson) { {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) {
paramsStr["origin"] = Global.webOrigin!;
}
var resText = "";
try {
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) {
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 MWResponse<Map<String, dynamic>> parseMWResponse(String action, String resJson) {
var resData = jsonDecode(resJson); var resData = jsonDecode(resJson);
if (resData is! Map<String, dynamic>) { if (resData is! Map<String, dynamic>) {
throw MWApiEmptyBodyException(resData); throw MWApiEmptyBodyException(resData);
@ -111,13 +169,26 @@ class MWApi {
} }
// //
if (!resData.containsKey(action) || if (!resData.containsKey(action) || resData[action] is! Map<String, dynamic>) {
resData[action] is! Map<String, dynamic>) {
throw MWApiEmptyBodyException(resData); throw MWApiEmptyBodyException(resData);
} }
MWResponse<Map<String, dynamic>> mwRes = MWResponse<Map<String, dynamic>> mwRes = MWResponse(resData[action] as Map<String, dynamic>);
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 // continue
var batchcomplete = resData["batchcomplete"]; var batchcomplete = resData["batchcomplete"];
@ -134,4 +205,33 @@ class MWApi {
return mwRes; 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", "method": "startauth",
}; };
var mwRes = await MWApi.get("mugenapp", query: query); var mwRes = await MWApi.get("mugenapp", params: query);
var data = MugenAppStartAuthResponse.fromJson(mwRes.data); var data = MugenAppStartAuthResponse.fromJson(mwRes.data);
return mwRes.replaceData(data.startauth); return mwRes.replaceData(data.startauth);
} }
static Future<MWResponse<MugenAppAttemptAuthInfo>> attemptAuth( static Future<MWResponse<MugenAppAttemptAuthInfo>> attemptAuth(String loginRequestKey) async {
String loginRequestKey) async {
var query = { var query = {
"method": "attemptauth", "method": "attemptauth",
"requestkey": loginRequestKey, "requestkey": loginRequestKey,
}; };
var mwRes = await MWApi.get("mugenapp", query: query); var mwRes = await MWApi.get("mugenapp", params: query);
var data = MugenAppAttemptAuthResponse.fromJson(mwRes.data); var data = MugenAppAttemptAuthResponse.fromJson(mwRes.data);
@ -36,10 +35,18 @@ class MWApiUser {
"uiprop": "blockinfo|groups|rights|options|email|realname|latestcontrib", "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); var data = MetaUserInfoResponse.fromJson(mwRes.data);
return mwRes.replaceData(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.freezed.dart';
part 'csrf_token.g.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 @freezed
class CSRFTokenResponse with _$CSRFTokenResponse { class CSRFTokenResponse with _$CSRFTokenResponse {
const factory CSRFTokenResponse({required CSRFTokenInfo tokens}) = const factory CSRFTokenResponse({required Map<String, String> tokens}) = _CSRFTokenInfoResponse;
_CSRFTokenInfoResponse;
factory CSRFTokenResponse.fromJson(Map<String, dynamic> json) => factory CSRFTokenResponse.fromJson(Map<String, dynamic> json) =>
_$CSRFTokenResponseFromJson(json); _$CSRFTokenResponseFromJson(json);

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

@ -235,6 +235,19 @@ class UserController extends GetxController {
} }
Future<void> logout({bool logoutRemote = true}) async { 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 // Cookie
await BaseApi.clearCookie(); await BaseApi.clearCookie();
@ -244,6 +257,8 @@ class UserController extends GetxController {
nickName.value = ""; nickName.value = "";
avatarUrlSet.clear(); avatarUrlSet.clear();
authProcessing.value = true;
saveToStorage(); saveToStorage();
} }
} }

Loading…
Cancel
Save