Files
kodjodevf-mangayomi-extensions/dart/manga/multisrc/mmrcms/mmrcms.dart

394 lines
9.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 lURL',
'es': 'Editar URL',
'de': 'URL bearbeiten',
'tr': 'URLyi 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);
}