mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
429 lines
10 KiB
Dart
429 lines
10 KiB
Dart
part of 'comic_source.dart';
|
|
|
|
class Comment {
|
|
final String userName;
|
|
final String? avatar;
|
|
final String content;
|
|
final String? time;
|
|
final int? replyCount;
|
|
final String? id;
|
|
int? score;
|
|
final bool? isLiked;
|
|
int? voteStatus; // 1: upvote, -1: downvote, 0: none
|
|
|
|
static String? parseTime(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is int) {
|
|
if (value < 10000000000) {
|
|
return DateTime.fromMillisecondsSinceEpoch(value * 1000)
|
|
.toString()
|
|
.substring(0, 19);
|
|
} else {
|
|
return DateTime.fromMillisecondsSinceEpoch(value)
|
|
.toString()
|
|
.substring(0, 19);
|
|
}
|
|
}
|
|
return value.toString();
|
|
}
|
|
|
|
Comment.fromJson(Map<String, dynamic> json)
|
|
: userName = json["userName"],
|
|
avatar = json["avatar"],
|
|
content = json["content"],
|
|
time = parseTime(json["time"]),
|
|
replyCount = json["replyCount"],
|
|
id = json["id"].toString(),
|
|
score = json["score"],
|
|
isLiked = json["isLiked"],
|
|
voteStatus = json["voteStatus"];
|
|
}
|
|
|
|
class Comic {
|
|
final String title;
|
|
|
|
final String cover;
|
|
|
|
final String id;
|
|
|
|
final String? subtitle;
|
|
|
|
final List<String>? tags;
|
|
|
|
final String description;
|
|
|
|
final String sourceKey;
|
|
|
|
final int? maxPage;
|
|
|
|
final String? language;
|
|
|
|
final String? favoriteId;
|
|
|
|
/// 0-5
|
|
final double? stars;
|
|
|
|
const Comic(
|
|
this.title,
|
|
this.cover,
|
|
this.id,
|
|
this.subtitle,
|
|
this.tags,
|
|
this.description,
|
|
this.sourceKey,
|
|
this.maxPage,
|
|
this.language,
|
|
) : favoriteId = null,
|
|
stars = null;
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
"title": title,
|
|
"cover": cover,
|
|
"id": id,
|
|
"subTitle": subtitle,
|
|
"tags": tags,
|
|
"description": description,
|
|
"sourceKey": sourceKey,
|
|
"maxPage": maxPage,
|
|
"language": language,
|
|
"favoriteId": favoriteId,
|
|
};
|
|
}
|
|
|
|
Comic.fromJson(Map<String, dynamic> json, this.sourceKey)
|
|
: title = json["title"],
|
|
subtitle = json["subtitle"] ?? json["subTitle"] ?? "",
|
|
cover = json["cover"],
|
|
id = json["id"],
|
|
tags = List<String>.from(json["tags"] ?? []),
|
|
description = json["description"] ?? "",
|
|
maxPage = json["maxPage"],
|
|
language = json["language"],
|
|
favoriteId = json["favoriteId"],
|
|
stars = (json["stars"] as num?)?.toDouble();
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (other is! Comic) return false;
|
|
return other.id == id && other.sourceKey == sourceKey;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => id.hashCode ^ sourceKey.hashCode;
|
|
}
|
|
|
|
class ComicDetails with HistoryMixin {
|
|
@override
|
|
final String title;
|
|
|
|
@override
|
|
final String? subTitle;
|
|
|
|
@override
|
|
final String cover;
|
|
|
|
final String? description;
|
|
|
|
final Map<String, List<String>> tags;
|
|
|
|
/// id-name
|
|
final ComicChapters? chapters;
|
|
|
|
final List<String>? thumbnails;
|
|
|
|
final List<Comic>? recommend;
|
|
|
|
final String sourceKey;
|
|
|
|
final String comicId;
|
|
|
|
final bool? isFavorite;
|
|
|
|
final String? subId;
|
|
|
|
final bool? isLiked;
|
|
|
|
final int? likesCount;
|
|
|
|
final int? commentCount;
|
|
|
|
final String? uploader;
|
|
|
|
final String? uploadTime;
|
|
|
|
final String? updateTime;
|
|
|
|
final String? url;
|
|
|
|
final double? stars;
|
|
|
|
@override
|
|
final int? maxPage;
|
|
|
|
final List<Comment>? comments;
|
|
|
|
static Map<String, List<String>> _generateMap(Map<dynamic, dynamic> map) {
|
|
var res = <String, List<String>>{};
|
|
map.forEach((key, value) {
|
|
res[key] = List<String>.from(value);
|
|
});
|
|
return res;
|
|
}
|
|
|
|
ComicDetails.fromJson(Map<String, dynamic> json)
|
|
: title = json["title"],
|
|
subTitle = json["subtitle"],
|
|
cover = json["cover"],
|
|
description = json["description"],
|
|
tags = _generateMap(json["tags"]),
|
|
chapters = ComicChapters.fromJsonOrNull(json["chapters"]),
|
|
sourceKey = json["sourceKey"],
|
|
comicId = json["comicId"],
|
|
thumbnails = ListOrNull.from(json["thumbnails"]),
|
|
recommend = (json["recommend"] as List?)
|
|
?.map((e) => Comic.fromJson(e, json["sourceKey"]))
|
|
.toList(),
|
|
isFavorite = json["isFavorite"],
|
|
subId = json["subId"],
|
|
likesCount = json["likesCount"],
|
|
isLiked = json["isLiked"],
|
|
commentCount = json["commentCount"],
|
|
uploader = json["uploader"],
|
|
uploadTime = json["uploadTime"],
|
|
updateTime = json["updateTime"],
|
|
url = json["url"],
|
|
stars = (json["stars"] as num?)?.toDouble(),
|
|
maxPage = json["maxPage"],
|
|
comments = (json["comments"] as List?)
|
|
?.map((e) => Comment.fromJson(e))
|
|
.toList();
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
"title": title,
|
|
"subTitle": subTitle,
|
|
"cover": cover,
|
|
"description": description,
|
|
"tags": tags,
|
|
"chapters": chapters,
|
|
"thumbnails": thumbnails,
|
|
"recommend": null,
|
|
"sourceKey": sourceKey,
|
|
"comicId": comicId,
|
|
"isFavorite": isFavorite,
|
|
"subId": subId,
|
|
"isLiked": isLiked,
|
|
"likesCount": likesCount,
|
|
"commentsCount": commentCount,
|
|
"uploader": uploader,
|
|
"uploadTime": uploadTime,
|
|
"updateTime": updateTime,
|
|
"url": url,
|
|
};
|
|
}
|
|
|
|
@override
|
|
HistoryType get historyType => HistoryType(sourceKey.hashCode);
|
|
|
|
@override
|
|
String get id => comicId;
|
|
|
|
ComicType get comicType => ComicType(sourceKey.hashCode);
|
|
|
|
/// Convert tags map to plain list
|
|
List<String> get plainTags {
|
|
var res = <String>[];
|
|
tags.forEach((key, value) {
|
|
res.addAll(value.map((e) => "$key:$e"));
|
|
});
|
|
return res;
|
|
}
|
|
|
|
/// Find the first author tag
|
|
String? findAuthor() {
|
|
var authorNamespaces = [
|
|
"author",
|
|
"authors",
|
|
"artist",
|
|
"artists",
|
|
"作者",
|
|
"画师"
|
|
];
|
|
for (var entry in tags.entries) {
|
|
if (authorNamespaces.contains(entry.key.toLowerCase()) &&
|
|
entry.value.isNotEmpty) {
|
|
return entry.value.first;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String? _validateUpdateTime(String time) {
|
|
time = time.split(" ").first;
|
|
var segments = time.split("-");
|
|
if (segments.length != 3) return null;
|
|
var year = int.tryParse(segments[0]);
|
|
var month = int.tryParse(segments[1]);
|
|
var day = int.tryParse(segments[2]);
|
|
if (year == null || month == null || day == null) return null;
|
|
if (year < 2000 || year > 3000) return null;
|
|
if (month < 1 || month > 12) return null;
|
|
if (day < 1 || day > 31) return null;
|
|
return "$year-$month-$day";
|
|
}
|
|
|
|
String? findUpdateTime() {
|
|
if (updateTime != null) {
|
|
return _validateUpdateTime(updateTime!);
|
|
}
|
|
const acceptedNamespaces = [
|
|
"更新",
|
|
"最後更新",
|
|
"最后更新",
|
|
"update",
|
|
"last update",
|
|
];
|
|
for (var entry in tags.entries) {
|
|
if (acceptedNamespaces.contains(entry.key.toLowerCase()) &&
|
|
entry.value.isNotEmpty) {
|
|
var value = entry.value.first;
|
|
return _validateUpdateTime(value);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class ArchiveInfo {
|
|
final String title;
|
|
final String description;
|
|
final String id;
|
|
|
|
ArchiveInfo.fromJson(Map<String, dynamic> json)
|
|
: title = json["title"],
|
|
description = json["description"],
|
|
id = json["id"];
|
|
}
|
|
|
|
class ComicChapters {
|
|
final Map<String, String>? _chapters;
|
|
|
|
final Map<String, Map<String, String>>? _groupedChapters;
|
|
|
|
/// Create a ComicChapters object with a flat map
|
|
const ComicChapters(Map<String, String> this._chapters)
|
|
: _groupedChapters = null;
|
|
|
|
/// Create a ComicChapters object with a grouped map
|
|
const ComicChapters.grouped(
|
|
Map<String, Map<String, String>> this._groupedChapters)
|
|
: _chapters = null;
|
|
|
|
factory ComicChapters.fromJson(dynamic json) {
|
|
if (json is! Map) throw ArgumentError("Invalid json type");
|
|
var chapters = <String, String>{};
|
|
var groupedChapters = <String, Map<String, String>>{};
|
|
for (var entry in json.entries) {
|
|
var key = entry.key;
|
|
var value = entry.value;
|
|
if (key is! String) throw ArgumentError("Invalid key type");
|
|
if (value is Map) {
|
|
groupedChapters[key] = Map.from(value);
|
|
} else {
|
|
chapters[key] = value.toString();
|
|
}
|
|
}
|
|
if (chapters.isNotEmpty) {
|
|
return ComicChapters(chapters);
|
|
} else if (groupedChapters.isNotEmpty) {
|
|
return ComicChapters.grouped(groupedChapters);
|
|
} else {
|
|
throw ArgumentError("Empty chapter list");
|
|
}
|
|
}
|
|
|
|
static fromJsonOrNull(dynamic json) {
|
|
if (json == null) return null;
|
|
return ComicChapters.fromJson(json);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
if (_chapters != null) {
|
|
return _chapters;
|
|
} else {
|
|
return _groupedChapters!;
|
|
}
|
|
}
|
|
|
|
/// Whether the chapters are grouped
|
|
bool get isGrouped => _groupedChapters != null;
|
|
|
|
/// All group names
|
|
Iterable<String> get groups => _groupedChapters?.keys ?? [];
|
|
|
|
/// All chapters.
|
|
/// If the chapters are grouped, all groups will be merged.
|
|
Map<String, String> get allChapters {
|
|
if (_chapters != null) return _chapters;
|
|
var res = <String, String>{};
|
|
for (var entry in _groupedChapters!.values) {
|
|
res.addAll(entry);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// Get a group of chapters by name
|
|
Map<String, String> getGroup(String group) {
|
|
return _groupedChapters![group] ?? {};
|
|
}
|
|
|
|
/// Get a group of chapters by index(0-based)
|
|
Map<String, String> getGroupByIndex(int index) {
|
|
return _groupedChapters!.values.elementAt(index);
|
|
}
|
|
|
|
/// Get total number of chapters
|
|
int get length {
|
|
return isGrouped
|
|
? _groupedChapters!.values.map((e) => e.length).reduce((a, b) => a + b)
|
|
: _chapters!.length;
|
|
}
|
|
|
|
/// Get the number of groups
|
|
int get groupCount => _groupedChapters?.length ?? 0;
|
|
|
|
/// Iterate all chapter ids
|
|
Iterable<String> get ids sync* {
|
|
if (isGrouped) {
|
|
for (var entry in _groupedChapters!.values) {
|
|
yield* entry.keys;
|
|
}
|
|
} else {
|
|
yield* _chapters!.keys;
|
|
}
|
|
}
|
|
|
|
/// Iterate all chapter titles
|
|
Iterable<String> get titles sync* {
|
|
if (isGrouped) {
|
|
for (var entry in _groupedChapters!.values) {
|
|
yield* entry.values;
|
|
}
|
|
} else {
|
|
yield* _chapters!.values;
|
|
}
|
|
}
|
|
|
|
String? operator [](String key) {
|
|
if (isGrouped) {
|
|
for (var entry in _groupedChapters!.values) {
|
|
if (entry.containsKey(key)) return entry[key];
|
|
}
|
|
return null;
|
|
} else {
|
|
return _chapters![key];
|
|
}
|
|
}
|
|
}
|