add more js api & improve ui

This commit is contained in:
nyne
2024-10-15 20:45:12 +08:00
parent c0a0dc59e1
commit fc86b8bbc6
22 changed files with 609 additions and 140 deletions

View File

@@ -17,8 +17,11 @@ import '../js_engine.dart';
import '../log.dart';
part 'category.dart';
part 'favorites.dart';
part 'parser.dart';
part 'models.dart';
/// build comic list, [Res.subData] should be maxPage or null if there is no limit.
@@ -50,11 +53,16 @@ typedef LikeOrUnlikeComicFunc = Future<Res<bool>> Function(
/// [isLiking] is true if the user is liking the comment, false if unliking.
/// return the new likes count or null.
typedef LikeCommentFunc = Future<Res<int?>> Function(String comicId, String? subId, String commentId, bool isLiking);
typedef LikeCommentFunc = Future<Res<int?>> Function(
String comicId, String? subId, String commentId, bool isLiking);
/// [isUp] is true if the user is upvoting the comment, false if downvoting.
/// return the new vote count or null.
typedef VoteCommentFunc = Future<Res<int?>> Function(String comicId, String? subId, String commentId, bool isUp, bool isCancel);
typedef VoteCommentFunc = Future<Res<int?>> Function(
String comicId, String? subId, String commentId, bool isUp, bool isCancel);
typedef HandleClickTagEvent = Map<String, String> Function(
String namespace, String tag);
class ComicSource {
static final List<ComicSource> _sources = [];
@@ -147,9 +155,6 @@ class ComicSource {
/// Search page.
final SearchPageData? searchPageData;
/// Settings.
final List<SettingItem> settings;
/// Load comic info.
final LoadComicFunc? loadComicInfo;
@@ -186,6 +191,12 @@ class ComicSource {
final LikeCommentFunc? likeCommentFunc;
final Map<String, dynamic>? settings;
final Map<String, Map<String, String>>? translations;
final HandleClickTagEvent? handleClickTagEvent;
Future<void> loadData() async {
var file = File("${App.dataPath}/comic_source/$key.data");
if (await file.exists()) {
@@ -225,29 +236,32 @@ class ComicSource {
}
ComicSource(
this.name,
this.key,
this.account,
this.categoryData,
this.categoryComicsData,
this.favoriteData,
this.explorePages,
this.searchPageData,
this.settings,
this.loadComicInfo,
this.loadComicThumbnail,
this.loadComicPages,
this.getImageLoadingConfig,
this.getThumbnailLoadingConfig,
this.filePath,
this.url,
this.version,
this.commentsLoader,
this.sendCommentFunc,
this.likeOrUnlikeComic,
this.voteCommentFunc,
this.likeCommentFunc,)
: idMatcher = null;
this.name,
this.key,
this.account,
this.categoryData,
this.categoryComicsData,
this.favoriteData,
this.explorePages,
this.searchPageData,
this.settings,
this.loadComicInfo,
this.loadComicThumbnail,
this.loadComicPages,
this.getImageLoadingConfig,
this.getThumbnailLoadingConfig,
this.filePath,
this.url,
this.version,
this.commentsLoader,
this.sendCommentFunc,
this.likeOrUnlikeComic,
this.voteCommentFunc,
this.likeCommentFunc,
this.idMatcher,
this.translations,
this.handleClickTagEvent,
);
}
class AccountConfig {
@@ -368,21 +382,6 @@ class SearchOptions {
String get defaultValue => options.keys.first;
}
class SettingItem {
final String name;
final String iconName;
final SettingType type;
final List<String>? options;
const SettingItem(this.name, this.iconName, this.type, this.options);
}
enum SettingType {
switcher,
selector,
input,
}
typedef CategoryComicsLoader = Future<Res<List<Comic>>> Function(
String category, String? param, List<String> options, int page);
@@ -423,4 +422,3 @@ class CategoryComicsOptions {
const CategoryComicsOptions(this.options, this.notShowWhen, this.showWhen);
}

View File

@@ -107,6 +107,8 @@ class ComicDetails with HistoryMixin {
final String? updateTime;
final String? url;
static Map<String, List<String>> _generateMap(Map<String, dynamic> map) {
var res = <String, List<String>>{};
map.forEach((key, value) {
@@ -137,7 +139,8 @@ class ComicDetails with HistoryMixin {
commentsCount = json["commentsCount"],
uploader = json["uploader"],
uploadTime = json["uploadTime"],
updateTime = json["updateTime"];
updateTime = json["updateTime"],
url = json["url"];
Map<String, dynamic> toJson() {
return {
@@ -159,6 +162,7 @@ class ComicDetails with HistoryMixin {
"uploader": uploader,
"uploadTime": uploadTime,
"updateTime": updateTime,
"url": url,
};
}

View File

@@ -130,7 +130,7 @@ class ComicSourceParser {
_loadFavoriteData(),
_loadExploreData(),
_loadSearchData(),
[],
_parseSettings(),
_parseLoadComicFunc(),
_parseThumbnailLoader(),
_parseLoadComicPagesFunc(),
@@ -144,6 +144,9 @@ class ComicSourceParser {
_parseLikeFunc(),
_parseVoteCommentFunc(),
_parseLikeCommentFunc(),
_parseIdMatch(),
_parseTranslation(),
_parseClickTagEvent(),
);
await source.loadData();
@@ -639,13 +642,13 @@ class ComicSourceParser {
}
LikeOrUnlikeComicFunc? _parseLikeFunc() {
if (!_checkExists("comic.likeOrUnlikeComic")) {
if (!_checkExists("comic.likeComic")) {
return null;
}
return (id, isLiking) async {
try {
await JsEngine().runCode("""
ComicSource.sources.$_key.comic.likeOrUnlikeComic(${jsonEncode(id)}, ${jsonEncode(isLiking)})
ComicSource.sources.$_key.comic.likeComic(${jsonEncode(id)}, ${jsonEncode(isLiking)})
""");
return const Res(true);
} catch (e, s) {
@@ -688,4 +691,41 @@ class ComicSourceParser {
}
};
}
Map<String, dynamic> _parseSettings() {
return _getValue("settings") ?? {};
}
RegExp? _parseIdMatch() {
if (!_checkExists("comic.idMatch")) {
return null;
}
return RegExp(_getValue("comic.idMatch"));
}
Map<String, Map<String, String>>? _parseTranslation() {
if (!_checkExists("translation")) {
return null;
}
var data = _getValue("translation");
var res = <String, Map<String, String>>{};
for (var e in data.entries) {
res[e.key] = Map<String, String>.from(e.value);
}
return res;
}
HandleClickTagEvent? _parseClickTagEvent() {
if (!_checkExists("comic.onClickTag")) {
return null;
}
return (namespace, tag) {
var res = JsEngine().runCode("""
ComicSource.sources.$_key.comic.onClickTag(${jsonEncode(namespace)}, ${jsonEncode(tag)})
""");
var r = Map<String, String?>.from(res);
r.removeWhere((key, value) => value == null);
return Map.from(r);
};
}
}

View File

@@ -19,6 +19,7 @@ import 'package:pointycastle/block/modes/cbc.dart';
import 'package:pointycastle/block/modes/cfb.dart';
import 'package:pointycastle/block/modes/ecb.dart';
import 'package:pointycastle/block/modes/ofb.dart';
import 'package:uuid/uuid.dart';
import 'package:venera/network/app_dio.dart';
import 'package:venera/network/cookie_jar.dart';
@@ -112,6 +113,9 @@ class JsEngine with _JSEngineApi{
{
String key = message["key"];
String dataKey = message["data_key"];
if(dataKey == 'setting'){
throw "setting is not allowed to be saved";
}
var data = message["data"];
var source = ComicSource.find(key)!;
source.data[dataKey] = data;
@@ -145,6 +149,23 @@ class JsEngine with _JSEngineApi{
{
return handleCookieCallback(Map.from(message));
}
case "uuid":
{
return const Uuid().v1();
}
case "load_setting":
{
String key = message["key"];
String settingKey = message["setting_key"];
var source = ComicSource.find(key)!;
return source.data["setting"]?[settingKey]
?? source.settings?[settingKey]['default']
?? (throw "Setting not found: $settingKey");
}
case "isLogged":
{
return ComicSource.find(message["key"])!.isLogged;
}
}
}
}
@@ -303,6 +324,10 @@ mixin class _JSEngineApi{
bool isEncode = data["isEncode"];
try {
switch (type) {
case "utf8":
return isEncode
? utf8.encode(value)
: utf8.decode(value);
case "base64":
if(value is String){
value = utf8.encode(value);
@@ -318,6 +343,21 @@ mixin class _JSEngineApi{
return Uint8List.fromList(sha256.convert(value).bytes);
case "sha512":
return Uint8List.fromList(sha512.convert(value).bytes);
case "hmac":
var key = data["key"];
var hash = data["hash"];
var hmac = Hmac(switch(hash) {
"md5" => md5,
"sha1" => sha1,
"sha256" => sha256,
"sha512" => sha512,
_ => throw "Unsupported hash: $hash"
}, key);
if(data['isString'] == true){
return hmac.convert(value).toString();
} else {
return Uint8List.fromList(hmac.convert(value).bytes);
}
case "aes-ecb":
if(!isEncode){
var key = data["key"];