mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 23:22:07 +00:00
adding support for Mihon extensions
This commit is contained in:
parent
a15afd4334
commit
5f9efe957a
13 changed files with 1647 additions and 485 deletions
|
|
@ -3,10 +3,12 @@ import 'package:mangayomi/models/source.dart';
|
|||
|
||||
import 'dart/service.dart';
|
||||
import 'javascript/service.dart';
|
||||
import 'mihon/service.dart';
|
||||
|
||||
ExtensionService getExtensionService(Source source) {
|
||||
return switch (source.sourceCodeLanguage) {
|
||||
SourceCodeLanguage.dart => DartExtensionService(source),
|
||||
SourceCodeLanguage.javascript => JsExtensionService(source),
|
||||
SourceCodeLanguage.mihon => MihonExtensionService(source, "http://localhost:8080"),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
85
lib/eval/mihon/models.dart
Normal file
85
lib/eval/mihon/models.dart
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import 'package:mangayomi/models/manga.dart';
|
||||
|
||||
class MangaPages {
|
||||
List<SManga> list;
|
||||
bool hasNextPage;
|
||||
MangaPages({required this.list, this.hasNextPage = false});
|
||||
|
||||
factory MangaPages.fromJson(Map<String, dynamic> json, ItemType itemType) {
|
||||
final name = itemType == ItemType.anime ? "animes" : "mangas";
|
||||
return MangaPages(
|
||||
list: json[name] != null
|
||||
? (json[name] as List).map((e) => SManga.fromJson(e)).toList()
|
||||
: [],
|
||||
hasNextPage: json['hasNextPage'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson(ItemType itemType) => {
|
||||
itemType == ItemType.anime ? "animes" : "mangas": list
|
||||
.map((v) => v.toJson())
|
||||
.toList(),
|
||||
'hasNextPage': hasNextPage,
|
||||
};
|
||||
}
|
||||
|
||||
class SManga {
|
||||
String? url;
|
||||
|
||||
String? title;
|
||||
|
||||
String? artist;
|
||||
|
||||
String? author;
|
||||
|
||||
String? description;
|
||||
|
||||
List<String>? genre;
|
||||
|
||||
Status? status;
|
||||
|
||||
String? thumbnailUrl;
|
||||
|
||||
SManga({
|
||||
this.url,
|
||||
this.title,
|
||||
this.artist,
|
||||
this.author,
|
||||
this.description,
|
||||
this.genre,
|
||||
this.status = Status.unknown,
|
||||
this.thumbnailUrl,
|
||||
});
|
||||
|
||||
factory SManga.fromJson(Map<String, dynamic> json) {
|
||||
return SManga(
|
||||
url: json['url'],
|
||||
title: json['title'],
|
||||
artist: json['artist'],
|
||||
author: json['author'],
|
||||
description: json['description'],
|
||||
genre: (json['genres'] as List?)?.map((e) => e.toString()).toList() ?? [],
|
||||
status: switch (json['status'] as int?) {
|
||||
1 => Status.ongoing,
|
||||
2 => Status.completed,
|
||||
4 => Status.publishingFinished,
|
||||
5 => Status.canceled,
|
||||
6 => Status.onHiatus,
|
||||
_ => Status.unknown,
|
||||
},
|
||||
thumbnailUrl: json['thumbnail_url'],
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'url': url,
|
||||
'title': title,
|
||||
'artist': artist,
|
||||
'author': author,
|
||||
'description': description,
|
||||
'genre': genre?.join(", "),
|
||||
'status': status,
|
||||
'thumbnail_url': thumbnailUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
275
lib/eval/mihon/service.dart
Normal file
275
lib/eval/mihon/service.dart
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import 'dart:convert';
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:mangayomi/eval/model/filter.dart';
|
||||
import 'package:mangayomi/eval/model/m_chapter.dart';
|
||||
import 'package:mangayomi/eval/model/m_manga.dart';
|
||||
import 'package:mangayomi/eval/model/m_pages.dart';
|
||||
import 'package:mangayomi/eval/model/source_preference.dart';
|
||||
import 'package:mangayomi/models/page.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
|
||||
import '../../models/manga.dart';
|
||||
import '../interface.dart';
|
||||
import 'models.dart';
|
||||
|
||||
class MihonExtensionService implements ExtensionService {
|
||||
late String androidProxyServer;
|
||||
@override
|
||||
late Source source;
|
||||
late InterceptedClient client;
|
||||
|
||||
MihonExtensionService(this.source, this.androidProxyServer) {
|
||||
client = MClient.init();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> getHeaders() {
|
||||
return {};
|
||||
}
|
||||
|
||||
@override
|
||||
bool get supportsLatest {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
String get sourceBaseUrl {
|
||||
return source.baseUrl!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(int page) async {
|
||||
final name = source.itemType == ItemType.anime ? "Anime" : "Manga";
|
||||
final res = await client.post(
|
||||
Uri.parse("$androidProxyServer/dalvik"),
|
||||
body: jsonEncode({
|
||||
"method": "getPopular$name",
|
||||
"page": page + 1,
|
||||
"search": "",
|
||||
"data": source.sourceCode,
|
||||
}),
|
||||
);
|
||||
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||
final pages = MangaPages.fromJson(data, source.itemType);
|
||||
return MPages(
|
||||
list: pages.list
|
||||
.map(
|
||||
(e) => MManga(
|
||||
name: e.title,
|
||||
link: e.url,
|
||||
artist: e.artist,
|
||||
author: e.author,
|
||||
description: e.description,
|
||||
genre: e.genre,
|
||||
status: e.status,
|
||||
imageUrl: e.thumbnailUrl,
|
||||
chapters: [],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
hasNextPage: pages.hasNextPage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(int page) async {
|
||||
final name = source.itemType == ItemType.anime ? "Anime" : "Manga";
|
||||
final res = await client.post(
|
||||
Uri.parse("$androidProxyServer/dalvik"),
|
||||
body: jsonEncode({
|
||||
"method": "getLatest$name",
|
||||
"page": page + 1,
|
||||
"search": "",
|
||||
"data": source.sourceCode,
|
||||
}),
|
||||
);
|
||||
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||
final pages = MangaPages.fromJson(data, source.itemType);
|
||||
return MPages(
|
||||
list: pages.list
|
||||
.map(
|
||||
(e) => MManga(
|
||||
name: e.title,
|
||||
link: e.url,
|
||||
artist: e.artist,
|
||||
author: e.author,
|
||||
description: e.description,
|
||||
genre: e.genre,
|
||||
status: e.status,
|
||||
imageUrl: e.thumbnailUrl,
|
||||
chapters: [],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
hasNextPage: pages.hasNextPage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> search(String query, int page, List<dynamic> filters) async {
|
||||
final name = source.itemType == ItemType.anime ? "Anime" : "Manga";
|
||||
final res = await client.post(
|
||||
Uri.parse("$androidProxyServer/dalvik"),
|
||||
body: jsonEncode({
|
||||
"method": "getSearch$name",
|
||||
"page": page + 1,
|
||||
"search": query,
|
||||
"data": source.sourceCode,
|
||||
}),
|
||||
);
|
||||
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||
final pages = MangaPages.fromJson(data, source.itemType);
|
||||
return MPages(
|
||||
list: pages.list
|
||||
.map(
|
||||
(e) => MManga(
|
||||
name: e.title,
|
||||
link: e.url,
|
||||
artist: e.artist,
|
||||
author: e.author,
|
||||
description: e.description,
|
||||
genre: e.genre,
|
||||
status: e.status,
|
||||
imageUrl: e.thumbnailUrl,
|
||||
chapters: [],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
hasNextPage: pages.hasNextPage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(String url) async {
|
||||
final name = source.itemType == ItemType.anime ? "Anime" : "Manga";
|
||||
final res = await client.post(
|
||||
Uri.parse("$androidProxyServer/dalvik"),
|
||||
body: jsonEncode({
|
||||
"method": "getDetails$name",
|
||||
if (source.itemType == ItemType.manga) "mangaData": {"url": url},
|
||||
if (source.itemType == ItemType.anime) "animeData": {"url": url},
|
||||
"data": source.sourceCode,
|
||||
}),
|
||||
);
|
||||
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||
final chapters = await getChapterList(url);
|
||||
return MManga(
|
||||
name: data['title'],
|
||||
link: data['url'],
|
||||
artist: data['artist'],
|
||||
author: data['author'],
|
||||
description: data['description'],
|
||||
genre: (data['genres'] as List?)?.map((e) => e.toString()).toList() ?? [],
|
||||
status: switch (data['status'] as int?) {
|
||||
1 => Status.ongoing,
|
||||
2 => Status.completed,
|
||||
4 => Status.publishingFinished,
|
||||
5 => Status.canceled,
|
||||
6 => Status.onHiatus,
|
||||
_ => Status.unknown,
|
||||
},
|
||||
imageUrl: data['thumbnail_url'],
|
||||
chapters: chapters,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<MChapter>> getChapterList(String url) async {
|
||||
final res = await client.post(
|
||||
Uri.parse("$androidProxyServer/dalvik"),
|
||||
body: jsonEncode({
|
||||
"method": source.itemType == ItemType.anime
|
||||
? "getEpisodeList"
|
||||
: "getChapterList",
|
||||
if (source.itemType == ItemType.manga) "mangaData": {"url": url},
|
||||
if (source.itemType == ItemType.anime) "animeData": {"url": url},
|
||||
"data": source.sourceCode,
|
||||
}),
|
||||
);
|
||||
final data = jsonDecode(res.body) as List;
|
||||
return data
|
||||
.map(
|
||||
(e) => MChapter(
|
||||
name: e['name'],
|
||||
url: e['url'],
|
||||
dateUpload: e['date_upload'] is int
|
||||
? (e['date_upload'] as int).toString()
|
||||
: e['date_upload'],
|
||||
scanlator: e['scanlator'],
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PageUrl>> getPageList(String url) async {
|
||||
final res = await client.post(
|
||||
Uri.parse("$androidProxyServer/dalvik"),
|
||||
body: jsonEncode({
|
||||
"method": "getPageList",
|
||||
"chapterData": {"url": url},
|
||||
"data": source.sourceCode,
|
||||
}),
|
||||
);
|
||||
final data = jsonDecode(res.body) as List;
|
||||
return data.map((e) => PageUrl(e['url'])).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Video>> getVideoList(String url) async {
|
||||
final res = await client.post(
|
||||
Uri.parse("$androidProxyServer/dalvik"),
|
||||
body: jsonEncode({
|
||||
"method": "getVideoList",
|
||||
"episodeData": {"url": url},
|
||||
"data": source.sourceCode,
|
||||
}),
|
||||
);
|
||||
final data = jsonDecode(res.body) as List;
|
||||
return data.map((e) {
|
||||
final tempHeaders =
|
||||
e['headers']['namesAndValues\$okhttp'] as List<dynamic>;
|
||||
final Map<String, String> headers = {};
|
||||
for (var i = 0; i + 1 < tempHeaders.length; i++) {
|
||||
headers[tempHeaders[i]] = tempHeaders[i + 1];
|
||||
}
|
||||
return Video(
|
||||
e['videoUrl'],
|
||||
e['quality'],
|
||||
e['url'],
|
||||
headers: headers,
|
||||
audios:
|
||||
(e['audioTracks'] as List?)
|
||||
?.map((e) => Track(file: e['file'], label: e['label']))
|
||||
.toList() ??
|
||||
[],
|
||||
subtitles:
|
||||
(e['subtitleTracks'] as List?)
|
||||
?.map((e) => Track(file: e['file'], label: e['label']))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getHtmlContent(String name, String url) async {
|
||||
return "";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> cleanHtmlContent(String html) async {
|
||||
return html;
|
||||
}
|
||||
|
||||
@override
|
||||
FilterList getFilterList() {
|
||||
return FilterList([]);
|
||||
}
|
||||
|
||||
@override
|
||||
List<SourcePreference> getSourcePreferences() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -201,6 +201,8 @@ class Settings {
|
|||
|
||||
List<Repo>? novelExtensionsRepo;
|
||||
|
||||
String? androidProxyServer;
|
||||
|
||||
@enumerated
|
||||
late SectionType disableSectionType;
|
||||
|
||||
|
|
@ -371,6 +373,7 @@ class Settings {
|
|||
this.mangaExtensionsRepo,
|
||||
this.animeExtensionsRepo,
|
||||
this.novelExtensionsRepo,
|
||||
this.androidProxyServer,
|
||||
this.lastTrackerLibraryLocation,
|
||||
this.mergeLibraryNavMobile = false,
|
||||
this.enableDiscordRpc = true,
|
||||
|
|
@ -594,6 +597,7 @@ class Settings {
|
|||
.map((e) => Repo.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
androidProxyServer = json['androidProxyServer'];
|
||||
lastTrackerLibraryLocation = json['lastTrackerLibraryLocation'];
|
||||
mergeLibraryNavMobile = json['mergeLibraryNavMobile'];
|
||||
enableDiscordRpc = json['enableDiscordRpc'];
|
||||
|
|
@ -736,6 +740,7 @@ class Settings {
|
|||
'mangaExtensionsRepo': mangaExtensionsRepo?.map((e) => e.toJson()).toList(),
|
||||
'animeExtensionsRepo': animeExtensionsRepo?.map((e) => e.toJson()).toList(),
|
||||
'novelExtensionsRepo': novelExtensionsRepo?.map((e) => e.toJson()).toList(),
|
||||
'androidProxyServer': androidProxyServer,
|
||||
'lastTrackerLibraryLocation': lastTrackerLibraryLocation,
|
||||
'mergeLibraryNavMobile': mergeLibraryNavMobile,
|
||||
'enableDiscordRpc': enableDiscordRpc,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -191,4 +191,4 @@ class Source {
|
|||
}
|
||||
}
|
||||
|
||||
enum SourceCodeLanguage { dart, javascript }
|
||||
enum SourceCodeLanguage { dart, javascript, mihon }
|
||||
|
|
|
|||
|
|
@ -489,10 +489,12 @@ const _SourceitemTypeValueEnumMap = {
|
|||
const _SourcesourceCodeLanguageEnumValueMap = {
|
||||
'dart': 0,
|
||||
'javascript': 1,
|
||||
'mihon': 2,
|
||||
};
|
||||
const _SourcesourceCodeLanguageValueEnumMap = {
|
||||
0: SourceCodeLanguage.dart,
|
||||
1: SourceCodeLanguage.javascript,
|
||||
2: SourceCodeLanguage.mihon,
|
||||
};
|
||||
|
||||
Id _sourceGetId(Source object) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'aniskip.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
|
||||
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
|
||||
|
||||
/// See also [AniSkip].
|
||||
@ProviderFor(AniSkip)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,56 @@ Future<void> fetchSourcesList({
|
|||
final info = await PackageInfo.fromPlatform();
|
||||
|
||||
final sourceList = (jsonDecode(req.body) as List)
|
||||
.map((e) => Source.fromJson(e))
|
||||
.expand((e) sync* {
|
||||
if (e['name'] != null &&
|
||||
e['pkg'] != null &&
|
||||
e['version'] != null &&
|
||||
e['code'] != null &&
|
||||
e['lang'] != null &&
|
||||
e['nsfw'] != null &&
|
||||
e['sources'] != null &&
|
||||
e['apk'] != null) {
|
||||
final repoUrl = url.replaceAll("/index.min.json", "");
|
||||
final sources = e['sources'] as List;
|
||||
for (final source in sources) {
|
||||
final src = Source.fromJson(e)
|
||||
..apiUrl = ''
|
||||
..appMinVerReq = ''
|
||||
..dateFormat = ''
|
||||
..dateFormatLocale = ''
|
||||
..hasCloudflare = false
|
||||
..headers = ''
|
||||
..isActive = true
|
||||
..isAdded = false
|
||||
..isFullData = false
|
||||
..isNsfw = e['nsfw'] == 1
|
||||
..isPinned = false
|
||||
..lastUsed = false
|
||||
..sourceCode = ''
|
||||
..typeSource = ''
|
||||
..versionLast = '0.0.1'
|
||||
..isObsolete = false
|
||||
..isLocal = false
|
||||
..name = source['name']
|
||||
..lang = source['lang']
|
||||
..baseUrl = source['baseUrl']
|
||||
..sourceCodeUrl = "$repoUrl/apk/${e['apk']}"
|
||||
..sourceCodeLanguage = SourceCodeLanguage.mihon
|
||||
..itemType =
|
||||
(e['pkg'] as String).startsWith(
|
||||
"eu.kanade.tachiyomi.animeextension",
|
||||
)
|
||||
? ItemType.anime
|
||||
: ItemType.manga
|
||||
..iconUrl = "$repoUrl/icon/${e['pkg']}.png"
|
||||
..notes = "Requires Android Proxy Server!";
|
||||
src.id = 'mihon-${source['id']}'.hashCode;
|
||||
yield src;
|
||||
}
|
||||
} else {
|
||||
yield Source.fromJson(e);
|
||||
}
|
||||
})
|
||||
.where(
|
||||
(source) =>
|
||||
source.itemType == itemType &&
|
||||
|
|
@ -74,14 +123,17 @@ Future<void> _updateSource(
|
|||
) async {
|
||||
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
final req = await http.get(Uri.parse(source.sourceCodeUrl!));
|
||||
final sourceCode = source.sourceCodeLanguage == SourceCodeLanguage.mihon
|
||||
? base64.encode(req.bodyBytes)
|
||||
: req.body;
|
||||
final headers = getExtensionService(
|
||||
source..sourceCode = req.body,
|
||||
source..sourceCode = sourceCode,
|
||||
).getHeaders();
|
||||
|
||||
final updatedSource = Source()
|
||||
..headers = jsonEncode(headers)
|
||||
..isAdded = true
|
||||
..sourceCode = req.body
|
||||
..sourceCode = sourceCode
|
||||
..sourceCodeUrl = source.sourceCodeUrl
|
||||
..id = source.id
|
||||
..apiUrl = source.apiUrl
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'sync_server.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$syncServerHash() => r'141ba3be28182e05480e06fbf3f1de68f868cb8e';
|
||||
String _$syncServerHash() => r'08225f80e9c249dc62e8e918acecbd593f54541f';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'anilist.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$anilistHash() => r'c786a526fdacc875e4a7e00886b2bda546eafeae';
|
||||
String _$anilistHash() => r'fafb964252b3a5741e981cb8c2f0f2090b3b86ae';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'kitsu.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$kitsuHash() => r'e24e9b57cfea974110d1f7c704c306c3b58e3529';
|
||||
String _$kitsuHash() => r'd46b955c92bc4d7382d32e17827da2e2b3a8434f';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'myanimelist.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$myAnimeListHash() => r'4391ad9446d14b1fb1ffdfbc5323ef04db5140f7';
|
||||
String _$myAnimeListHash() => r'739c836ddbfc7c2c2b7593304f481e8d35074391';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
Loading…
Reference in a new issue