mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
394 lines
9.6 KiB
Dart
394 lines
9.6 KiB
Dart
import 'package:mangayomi/bridge_lib.dart';
|
||
import 'dart:convert';
|
||
|
||
class MMRCMS extends MProvider {
|
||
MMRCMS({required this.source});
|
||
|
||
MSource source;
|
||
static final Set<String> latestTitles = <String>{};
|
||
final Client client = Client();
|
||
|
||
MManga mangaFromElement(MElement element) {
|
||
final anchor = element.selectFirst(".media-heading a, .manga-heading a");
|
||
final link = anchor?.getHref;
|
||
|
||
return MManga()
|
||
..name = anchor?.text
|
||
..imageUrl = guessCover(link, url: element.selectFirst("img")?.getSrc)
|
||
..link = link;
|
||
}
|
||
|
||
@override
|
||
Future<MPages> getPopular(int page) async {
|
||
final res = (await client.get(
|
||
Uri.parse("${getBaseUrl()}/filterList?page=$page&sortBy=views&asc=false"),
|
||
)).body;
|
||
final document = parseHtml(res);
|
||
final mangaList = <MManga>[];
|
||
for (final el in document.select("div.chapter-container, div.media")) {
|
||
final manga = mangaFromElement(el);
|
||
mangaList.add(manga);
|
||
}
|
||
|
||
return MPages(mangaList, true);
|
||
}
|
||
|
||
@override
|
||
Future<MPages> getLatestUpdates(int page) async {
|
||
if (page == 1) latestTitles.clear();
|
||
|
||
final res = (await client.get(
|
||
Uri.parse("${getBaseUrl()}/latest-release?page=$page"),
|
||
)).body;
|
||
|
||
final document = parseHtml(res);
|
||
final mangaList = <MManga>[];
|
||
|
||
for (var el in document.select("div.mangalist div.manga-item")) {
|
||
final manga = mangaFromElement(el);
|
||
final link = manga.link;
|
||
|
||
if (link != null && latestTitles.add(link)) {
|
||
mangaList.add(manga);
|
||
}
|
||
}
|
||
|
||
return MPages(mangaList, true);
|
||
}
|
||
|
||
@override
|
||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||
String url = getBaseUrl();
|
||
List<MManga> mangaList = [];
|
||
|
||
bool hasNextPage = false;
|
||
if (query.isNotEmpty) {
|
||
url = "$url/search?query=$query";
|
||
final res = (await client.get(Uri.parse(url))).body;
|
||
final jsonList = json.decode(res)["suggestions"];
|
||
|
||
for (var da in jsonList) {
|
||
String value = da["value"];
|
||
String data = da["data"];
|
||
final mangaSubString = getMangaSubString();
|
||
final path = mangaSubString.isEmpty ? data : '$mangaSubString/$data';
|
||
|
||
mangaList.add(
|
||
MManga(
|
||
name: value,
|
||
link: '${getBaseUrl()}/$path',
|
||
imageUrl: guessCover('/$path'),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
return MPages(mangaList, hasNextPage);
|
||
}
|
||
|
||
@override
|
||
Future<MManga> getDetail(String url) async {
|
||
final res = (await client.get(Uri.parse(url))).body;
|
||
final document = parseHtml(res);
|
||
final manga = MManga();
|
||
|
||
// Title
|
||
final mangaTitle = document
|
||
.selectFirst(".panel-heading, .listmanga-header, .widget-title")
|
||
?.text;
|
||
manga.name = mangaTitle;
|
||
|
||
// Cover
|
||
manga.imageUrl = guessCover(
|
||
url,
|
||
url: document.selectFirst(".row img.img-responsive")?.getSrc,
|
||
);
|
||
|
||
// Description
|
||
manga.description = extractDescription(document);
|
||
|
||
document.select('.panel-body h3, .row .dl-horizontal dt').forEach((
|
||
element,
|
||
) {
|
||
final label = _getOwnText(
|
||
element,
|
||
).toLowerCase().replaceFirst(RegExp(r' :$'), '');
|
||
|
||
final valueElement = element.selectFirst('div.text');
|
||
if (valueElement.text == null)
|
||
final valueElement = element.nextElementSibling;
|
||
|
||
_assignMangaInfo(manga, label, valueElement);
|
||
});
|
||
|
||
// Chapters
|
||
List<MChapter>? chaptersList = [];
|
||
for (var ch in document.select("ul.chapters > li:not(.btn)")) {
|
||
chaptersList.add(chapterFromElement(ch, mangaTitle));
|
||
}
|
||
manga.chapters = chaptersList;
|
||
|
||
return manga;
|
||
}
|
||
|
||
MChapter chapterFromElement(MElement element, String mangaTitle) {
|
||
final chapter = MChapter();
|
||
|
||
final titleWrapper = element.selectFirst(".chapter-title-rtl");
|
||
final anchor = titleWrapper?.selectFirst("a");
|
||
|
||
if (anchor != null) {
|
||
chapter.url = anchor.getHref ?? '';
|
||
chapter.name = cleanChapterName(titleWrapper.text, mangaTitle);
|
||
|
||
final dateElement = element.selectFirst(".date-chapter-title-rtl");
|
||
|
||
if (dateElement != null && dateElement.text.isNotEmpty) {
|
||
chapter.dateUpload = parseDates(
|
||
[dateElement.text],
|
||
source.dateFormat,
|
||
source.dateFormatLocale,
|
||
)[0];
|
||
} else {
|
||
chapter.dateUpload = DateTime.now().millisecondsSinceEpoch.toString();
|
||
}
|
||
}
|
||
|
||
return chapter;
|
||
}
|
||
|
||
@override
|
||
Future<List<String>> getPageList(String url) async {
|
||
final response = await client.get(Uri.parse(url));
|
||
final document = parseHtml(response.body);
|
||
|
||
List<String> pagesUrl = [];
|
||
for (var img in document.select('#all img.img-responsive[data-src]')) {
|
||
String? src = img.attr('data-src');
|
||
if (src.startsWith('//')) {
|
||
pagesUrl.add('https:${src}');
|
||
} else {
|
||
pagesUrl.add(src);
|
||
}
|
||
}
|
||
|
||
return pagesUrl;
|
||
}
|
||
|
||
@override
|
||
List<dynamic> getFilterList() {
|
||
return [];
|
||
}
|
||
|
||
@override
|
||
List<dynamic> getSourcePreferences() {
|
||
return [
|
||
EditTextPreference(
|
||
key: "domain_url",
|
||
title: getTitleByLang(source.lang),
|
||
summary: "",
|
||
value: source.baseUrl,
|
||
dialogTitle: "URL",
|
||
dialogMessage: "",
|
||
),
|
||
];
|
||
}
|
||
|
||
String getBaseUrl() {
|
||
final baseUrl = getPreferenceValue(source.id, "domain_url")?.trim();
|
||
|
||
if (baseUrl == null || baseUrl.isEmpty) {
|
||
return source.baseUrl;
|
||
}
|
||
|
||
return baseUrl.endsWith("/")
|
||
? baseUrl.substring(0, baseUrl.length - 1)
|
||
: baseUrl;
|
||
}
|
||
|
||
String getTitleByLang(String? lang) {
|
||
const titles = {
|
||
'ar': 'تحرير الرابط',
|
||
'en': 'Edit URL',
|
||
'fr': 'Modifier l’URL',
|
||
'es': 'Editar URL',
|
||
'de': 'URL bearbeiten',
|
||
'tr': 'URL’yi düzenle',
|
||
'ru': 'Редактировать URL',
|
||
'id': 'Edit URL',
|
||
'pt': 'Editar URL',
|
||
'it': 'Modifica URL',
|
||
'ja': 'URLを編集',
|
||
'zh': '编辑网址',
|
||
'ko': 'URL 편집',
|
||
'fa': 'ویرایش نشانی',
|
||
};
|
||
|
||
return titles[lang?.toLowerCase()] ?? titles['en']!;
|
||
}
|
||
|
||
String getMangaSubString() {
|
||
const sourceTypeMap = {'Scan VF': "", "Read Comics Online": "comic"};
|
||
|
||
return sourceTypeMap[source.name] ?? "manga";
|
||
}
|
||
|
||
String ll(String url) {
|
||
if (url.contains("?")) {
|
||
return "&";
|
||
}
|
||
return "?";
|
||
}
|
||
|
||
String guessCover(String mangaUrl, {String? url}) {
|
||
String baseUrl = getBaseUrl();
|
||
if (url == null || url?.endsWith("no-image.png")) {
|
||
String slug = substringAfterLast(mangaUrl, '/');
|
||
return "${baseUrl}/uploads/manga/${slug}/cover/cover_250x350.jpg";
|
||
} else if (url?.startsWith(baseUrl)) {
|
||
return url;
|
||
} else {
|
||
return Uri.parse(baseUrl).resolve(url).toString();
|
||
}
|
||
}
|
||
|
||
String extractDescription(MDocument document) {
|
||
final container = document.selectFirst(".row .well");
|
||
if (container == null) return "";
|
||
|
||
String text = container.text;
|
||
|
||
container.select("h5").forEach((element) {
|
||
text = text.replaceAll(element.text, "");
|
||
});
|
||
|
||
return text.replaceAll(RegExp(r'\n{3,}'), '\n\n').trim();
|
||
}
|
||
|
||
String _getOwnText(MElement element) {
|
||
final text = element.text;
|
||
final childrenText = element.children.map((e) => e.text).join();
|
||
return text.replaceFirst(childrenText, '').trim();
|
||
}
|
||
|
||
void _assignMangaInfo(MManga manga, String label, MElement valueElement) {
|
||
if (_detailAuthor.contains(label)) {
|
||
manga.author = valueElement.text;
|
||
} else if (_detailArtist.contains(label)) {
|
||
manga.artist = valueElement.text;
|
||
} else if (_detailGenre.contains(label)) {
|
||
manga.genre = valueElement?.select("a").map((e) => e.text).toList;
|
||
} else if (_detailStatus.contains(label)) {
|
||
manga.status = parseStatus(valueElement.text, statusList);
|
||
}
|
||
}
|
||
|
||
String cleanChapterName(String name, String mangaTitle) {
|
||
const chapterString = "Chapter";
|
||
const chapterNamePrefix = "";
|
||
|
||
try {
|
||
final initialName = name.replaceFirst(
|
||
'$chapterNamePrefix$mangaTitle',
|
||
chapterString,
|
||
);
|
||
|
||
final parts = initialName.split(':');
|
||
|
||
if (parts.isEmpty) return name;
|
||
|
||
final firstPart = parts[0].trim();
|
||
if (parts.length == 1) return firstPart;
|
||
|
||
final secondPart = parts.sublist(1).join(':').trim();
|
||
|
||
return firstPart == secondPart ? firstPart : "$firstPart: $secondPart";
|
||
} catch (e) {
|
||
return name;
|
||
}
|
||
}
|
||
|
||
const _detailAuthor = {
|
||
'author(s)',
|
||
'autor(es)',
|
||
'auteur(s)',
|
||
'著作',
|
||
'yazar(lar)',
|
||
'mangaka(lar)',
|
||
'pengarang/penulis',
|
||
'pengarang',
|
||
'penulis',
|
||
'autor',
|
||
'المؤلف',
|
||
'перевод',
|
||
'autor/autorzy',
|
||
};
|
||
|
||
const _detailArtist = {
|
||
'artist(s)',
|
||
'artiste(s)',
|
||
'sanatçi(lar)',
|
||
'artista(s)',
|
||
'artist(s)/ilustrator',
|
||
'الرسام',
|
||
'seniman',
|
||
'rysownik/rysownicy',
|
||
'artista',
|
||
};
|
||
|
||
const _detailGenre = {
|
||
'categories',
|
||
'categorías',
|
||
'catégories',
|
||
'ジャンル',
|
||
'kategoriler',
|
||
'categorias',
|
||
'kategorie',
|
||
'التصنيفات',
|
||
'жанр',
|
||
'kategori',
|
||
'tagi',
|
||
'género',
|
||
};
|
||
|
||
const _detailStatus = {
|
||
'status',
|
||
'statut',
|
||
'estado',
|
||
'状態',
|
||
'durum',
|
||
'الحالة',
|
||
'статус',
|
||
};
|
||
|
||
const statusList = [
|
||
{
|
||
// Ongoing Statuses (0)
|
||
'ongoing': 0,
|
||
'مستمرة': 0,
|
||
'en cours': 0,
|
||
'em lançamento': 0,
|
||
'prace w toku': 0,
|
||
'ativo': 0,
|
||
'em andamento': 0,
|
||
'activo': 0,
|
||
|
||
// Complete Statuses (1)
|
||
'complete': 1,
|
||
'مكتملة': 1,
|
||
'complet': 1,
|
||
'completo': 1,
|
||
'zakończone': 1,
|
||
'concluído': 1,
|
||
'finalizado': 1,
|
||
|
||
// Dropped Statuses (3)
|
||
'dropped': 3,
|
||
},
|
||
];
|
||
}
|
||
|
||
MMRCMS main(MSource source) {
|
||
return MMRCMS(source: source);
|
||
}
|