Big changes ( added support for external sources )

This commit is contained in:
kodjomoustapha 2023-07-07 22:38:49 +01:00
parent 86ebc32a0d
commit b38f737c96
109 changed files with 5486 additions and 8171 deletions

View file

@ -4,11 +4,12 @@
<h1 align="center"> Mangayomi </h1>
Mangayomi is free and open source manga reader cross-plateform app inspired by Tachiyomi made with flutter. It allows users to read manga from a variety of sources. Currently, all content sources are directly integrated into the application.
Mangayomi is free and open source manga reader cross-plateform app inspired by Tachiyomi made with flutter. It allows users to read manga from a variety of sources.
## Features
Features include:
* External sources
* Online reading from a variety of sources
* Local reading of downloaded content
* Read and manage local archives (.cbz, .zip)

View file

@ -0,0 +1,609 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
class $MangaModel implements MangaModel, $Instance {
$MangaModel.wrap(this.$value) : _superclass = $Object($value);
static const $type = BridgeTypeRef(
BridgeTypeSpec('package:bridge_lib/bridge_lib.dart', 'MangaModel'));
static const $declaration = BridgeClassDef(BridgeClassType($type),
constructors: {
'': BridgeConstructorDef(BridgeFunctionDef(
returns: BridgeTypeAnnotation($type),
params: [],
namedParams: [
BridgeParameter(
'source',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'author',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'status',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.intType)),
false),
BridgeParameter(
'genre',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.stringType)]),
),
false),
BridgeParameter(
'imageUrl',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'lang',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'name',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'link',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'description',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'baseUrl',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'dateFormat',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'dateFormatLocale',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'apiUrl',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'page',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.intType)),
false),
BridgeParameter(
'query',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'sourceId',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.intType)),
false),
BridgeParameter(
'names',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'urls',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'chaptersScanlators',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'chaptersDateUploads',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'chaptersVolumes',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'chaptersChaps',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'images',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'statusList',
BridgeTypeAnnotation(
BridgeTypeRef(CoreTypes.list,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
]))
},
// Specify class fields
fields: {
'source': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'author': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'status': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType))),
'genre': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.stringType)]),
),
),
'imageUrl': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'lang': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'name': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'link': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'description': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'baseUrl': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'dateFormat': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'dateFormatLocale': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'apiUrl': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'page': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType))),
'query': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
'sourceId': BridgeFieldDef(
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType))),
'names': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'urls': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'chaptersScanlators': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'chaptersDateUploads': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'chaptersVolumes': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'chaptersChaps': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'images': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'statusList': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef(
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
},
wrap: true);
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $MangaModel.wrap(MangaModel());
}
@override
final MangaModel $value;
@override
MangaModel get $reified => $value;
final $Instance _superclass;
@override
$Value? $getProperty(Runtime runtime, String identifier) {
switch (identifier) {
case 'source':
return $String($value.source!);
case 'author':
return $String($value.author!);
case 'status':
return $int($value.status!);
case 'genre':
return $List.wrap($value.genre!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'imageUrl':
return $String($value.imageUrl!);
case 'lang':
return $String($value.lang!);
case 'name':
return $String($value.name!);
case 'link':
return $String($value.link!);
case 'description':
return $String($value.description!);
case 'baseUrl':
return $String($value.baseUrl!);
case 'dateFormat':
return $String($value.dateFormat!);
case 'dateFormatLocale':
return $String($value.dateFormatLocale!);
case 'apiUrl':
return $String($value.apiUrl!);
case 'page':
return $int($value.page!);
case 'query':
return $String($value.query!);
case 'sourceId':
return $int($value.sourceId!);
case 'names':
return $List.wrap($value.names!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'chaptersDateUploads':
return $List.wrap($value.chaptersDateUploads!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'chaptersScanlators':
return $List.wrap($value.chaptersScanlators!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'urls':
return $List.wrap($value.urls!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'chaptersVolumes':
return $List.wrap($value.chaptersVolumes!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'chaptersChaps':
return $List.wrap($value.chaptersChaps!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'images':
return $List.wrap($value.images!.map((e) {
if (e is String) {
return $String(e);
} else {
return e;
}
}).toList());
case 'statusList':
return $List.wrap($value.statusList!.map((e) {
return $int(e);
}).toList());
default:
return _superclass.$getProperty(runtime, identifier);
}
}
@override
int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!);
@override
void $setProperty(Runtime runtime, String identifier, $Value value) {
switch (identifier) {
case 'source':
$value.source = value.$reified;
case 'author':
$value.author = value.$reified;
case 'status':
$value.status = value.$reified;
case 'genre':
$value.genre = value.$reified as List<dynamic>;
case 'imageUrl':
$value.imageUrl = value.$reified;
case 'lang':
$value.lang = value.$reified;
case 'name':
$value.name = value.$reified;
case 'link':
$value.link = value.$reified;
case 'description':
$value.description = value.$reified;
case 'baseUrl':
$value.baseUrl = value.$reified;
case 'dateFormat':
$value.dateFormat = value.$reified;
case 'dateFormatLocale':
$value.dateFormatLocale = value.$reified;
case 'apiUrl':
$value.apiUrl = value.$reified;
case 'page':
$value.page = value.$reified;
case 'query':
$value.query = value.$reified;
case 'sourceId':
$value.sourceId = value.$reified;
case 'names':
$value.names = value.$reified as List<dynamic>;
case 'chaptersDateUploads':
$value.chaptersDateUploads = value.$reified as List<dynamic>;
case 'chaptersScanlators':
$value.chaptersScanlators = value.$reified as List<dynamic>;
case 'urls':
$value.urls = value.$reified as List<dynamic>;
case 'chaptersVolumes':
$value.chaptersVolumes = value.$reified as List<dynamic>;
case 'chaptersChaps':
$value.chaptersChaps = value.$reified as List<dynamic>;
case 'images':
$value.images = value.$reified as List<dynamic>;
case 'statusList':
$value.statusList = value.$reified as List<dynamic>;
default:
_superclass.$setProperty(runtime, identifier, value);
}
}
@override
String? get author => $value.author;
@override
String? get description => $value.description;
@override
String? get imageUrl => $value.imageUrl;
@override
String? get name => $value.name;
@override
String? get source => $value.source;
@override
String? get link => $value.link;
@override
List<dynamic>? get genre => $value.genre;
@override
String? get lang => $value.lang;
@override
int? get sourceId => $value.sourceId;
@override
int? get status => $value.status;
@override
set author(String? author) {
// implement author
}
@override
set description(String? description) {
// implement description
}
@override
set imageUrl(String? imageUrl) {
// implement imageUrl
}
@override
set link(String? link) {
// implement link
}
@override
set name(String? name) {
// implement name
}
@override
set source(String? source) {
// implement source
}
@override
List<dynamic>? get chaptersDateUploads => $value.chaptersDateUploads;
@override
List<dynamic>? get names => $value.names;
@override
List<dynamic>? get chaptersScanlators => $value.chaptersScanlators;
@override
List<dynamic>? get urls => $value.urls;
@override
List<dynamic>? get chaptersVolumes => $value.chaptersVolumes;
@override
List<dynamic>? get chaptersChaps => $value.chaptersChaps;
@override
List<dynamic>? get images => $value.images;
@override
List<dynamic>? get statusList => $value.statusList;
@override
set chaptersDateUploads(List? chaptersDateUploads) {
// implement chaptersDateUploads
}
@override
set names(List? names) {
// implement names
}
@override
set chaptersScanlators(List? chaptersScanlators) {
// implement chaptersScanlators
}
@override
set urls(List? urls) {
// implement urls
}
@override
set genre(List? genre) {
// implement genre
}
@override
set lang(String? lang) {
// implement lang
}
@override
set status(int? status) {
// implement status
}
@override
String? get apiUrl => $value.apiUrl;
@override
String? get baseUrl => $value.baseUrl;
@override
String? get dateFormat => $value.dateFormat;
@override
String? get dateFormatLocale => $value.dateFormatLocale;
@override
set apiUrl(String? apiUrl) {
// implement apiUrl
}
@override
set baseUrl(String? baseUrl) {
// implement baseUrl
}
@override
set dateFormat(String? dateFormat) {
// implement dateFormat
}
@override
set dateFormatLocale(String? dateFormatLocale) {
// implement dateFormatLocale
}
@override
int? get page => $value.page;
@override
set page(int? page) {
// implement page
}
@override
String? get query => $value.query;
@override
set query(String? query) {
// implement page
}
@override
set chaptersChaps(List? chaptersChaps) {
// implement chaptersChaps
}
@override
set chaptersVolumes(List? chaptersVolumes) {
// implement chaptersVolumes
}
@override
set images(List? images) {
// implement images
}
@override
set statusList(List? images) {
// implement statusList
}
@override
set sourceId(int? sourceId) {
// implement sourceId
}
}

View file

@ -0,0 +1,67 @@
class MangaModel {
String? name;
String? link;
String? imageUrl;
String? description;
String? author;
int? status;
List<dynamic>? genre = [];
String? source;
String? lang;
String? baseUrl;
String? dateFormat;
String? dateFormatLocale;
String? apiUrl;
int? page;
String? query;
int? sourceId;
List<dynamic>? names;
List<dynamic>? urls;
List<dynamic>? chaptersScanlators;
List<dynamic>? chaptersDateUploads;
List<dynamic>? chaptersVolumes;
List<dynamic>? chaptersChaps;
List<dynamic>? images;
List<dynamic>? statusList;
MangaModel(
{this.source = "",
this.author = "",
this.genre,
this.imageUrl = "",
this.lang = "",
this.link = "",
this.name = "",
this.status = 0,
this.description = "",
this.apiUrl = "",
this.baseUrl = "",
this.dateFormat = "",
this.dateFormatLocale = "",
this.page = 1,
this.query = "",
this.sourceId = 0,
this.names,
this.chaptersDateUploads,
this.chaptersScanlators,
this.urls,
this.chaptersVolumes,
this.chaptersChaps,
this.images,
this.statusList});
}

View file

@ -0,0 +1,18 @@
import 'dart:typed_data';
import 'package:dart_eval/dart_eval.dart';
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
import 'package:mangayomi/eval/m_bridge.dart';
Uint8List compilerEval(String sourceCode) {
final compiler = Compiler();
compiler.defineBridgeClasses([
$MBridge.$declaration,
$MangaModel.$declaration,
]);
final program = compiler.compile({
'package:mangayomi': {'main.dart': sourceCode}
});
final bytecode = program.write();
return bytecode;
}

1163
lib/eval/m_bridge.dart Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
import 'dart:typed_data';
import 'package:dart_eval/dart_eval.dart';
import 'package:mangayomi/eval/m_bridge.dart';
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
Runtime runtimeEval(Uint8List bytecode) {
final runtime = Runtime(bytecode.buffer.asByteData());
runtime.registerBridgeFunc(
'package:bridge_lib/bridge_lib.dart', 'MBridge.', $MBridge.$construct,
isBridge: true);
runtime.registerBridgeFunc(
'package:bridge_lib/bridge_lib.dart', 'MangaModel.', $MangaModel.$new);
runtime.registerBridgeFunc(
'package:bridge_lib/bridge_lib.dart', 'MBridge.http', $MBridge.$http);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.listParseDateTime', $MBridge.$listParseDateTime);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.jsonPathToString', $MBridge.$jsonPathToString);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.jsonPathToList', $MBridge.$jsonPathToList);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.jsonDecodeToList', $MBridge.$jsonDecodeToList);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.jsonPathToMap', $MBridge.$jsonPathToMap);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.parseStatus', $MBridge.$parseStatus);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.stringParseValue', $MBridge.$stringParseValue);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.getMapValue', $MBridge.$getMapValue);
runtime.registerBridgeFunc(
'package:bridge_lib/bridge_lib.dart', 'MBridge.regExp', $MBridge.$regExp);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.parseChapterDate', $MBridge.$parseChapterDate);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.stringParse', $MBridge.$stringParse);
runtime.registerBridgeFunc(
'package:bridge_lib/bridge_lib.dart', 'MBridge.evalJs', $MBridge.$evalJs);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.intParse', $MBridge.$intParse);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.listParse', $MBridge.$listParse);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.getHtmlViaWebview', $MBridge.$getHtmlViaWebview);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.listContain', $MBridge.$listContain);
runtime.registerBridgeFunc(
'package:bridge_lib/bridge_lib.dart', 'MBridge.xpath', $MBridge.$xpath);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.querySelector', $MBridge.$querySelector);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.querySelectorAll', $MBridge.$querySelectorAll);
runtime.setup();
return runtime;
}

View file

@ -1,6 +1,5 @@
// ignore_for_file: depend_on_referenced_packages
import 'dart:io';
import 'package:bot_toast/bot_toast.dart';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
@ -16,7 +15,18 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
late Isar isar;
class MyHttpoverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
}
void main(List<String> args) async {
HttpOverrides.global = MyHttpoverrides();
if (Platform.isLinux || Platform.isWindows) {
if (runWebViewTitleBarWidget(args)) {
return;
@ -44,6 +54,7 @@ _iniDateFormatting() {
initializeDateFormatting("zh", null);
initializeDateFormatting("pl", null);
initializeDateFormatting("tr", null);
initializeDateFormatting("bg", null);
}
class MyApp extends ConsumerStatefulWidget {
@ -109,6 +120,7 @@ class _MyAppState extends ConsumerState<MyApp> {
themeMode: isDarkTheme ? ThemeMode.dark : ThemeMode.light,
theme: themeLight,
debugShowCheckedModeBanner: false,
builder: BotToastInit(),
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,

View file

@ -1,7 +1,9 @@
import 'package:mangayomi/models/source.dart';
class MangaType {
String? lang;
bool? isFullData;
String? source;
Source? source;
MangaType(
{required this.isFullData, required this.lang, required this.source});
}

View file

@ -1,4 +1,5 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/utils/constant.dart';
part 'settings.g.dart';
@ -85,6 +86,10 @@ class Settings {
List<FilterScanlator>? filterScanlatorList;
final sources = IsarLinks<Source>();
bool? autoUpdateExtensions;
bool? cropBorders;
Settings(
@ -124,7 +129,8 @@ class Settings {
this.saveAsCBZArchive = false,
this.downloadLocation = "",
this.cropBorders = false,
this.libraryLocalSource});
this.libraryLocalSource,
this.autoUpdateExtensions = false});
}
enum DisplayType {
@ -184,7 +190,7 @@ class ChapterPageIndex {
@embedded
class Cookie {
String? source;
String? idSource;
String? cookie;
}

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ part 'source.g.dart';
class Source {
Id? id;
String? sourceName;
String? name;
String? baseUrl;
@ -20,14 +20,17 @@ class Source {
bool? isNsfw;
@enumerated
TypeSource typeSource;
String? sourceCode;
String? logoUrl;
String? sourceCodeUrl;
String? typeSource;
String? iconUrl;
bool? isFullData;
bool? isCloudflare;
bool? hasCloudflare;
bool? lastUsed;
@ -37,38 +40,136 @@ class Source {
String? apiUrl;
String? version;
String? versionLast;
String? headers;
Source({
this.id = Isar.autoIncrement,
required this.sourceName,
required this.baseUrl,
required this.lang,
required this.typeSource,
required this.logoUrl,
required this.dateFormat,
required this.dateFormatLocale,
this.id = 0,
this.name = '',
this.baseUrl = '',
this.lang = '',
this.typeSource = '',
this.iconUrl = '',
this.dateFormat = '',
this.dateFormatLocale = '',
this.isActive = true,
this.isAdded = false,
this.isNsfw = false,
this.isFullData = false,
this.isCloudflare = false,
this.hasCloudflare = false,
this.isPinned = false,
this.lastUsed = false,
this.apiUrl = "",
this.sourceCodeUrl = "",
this.version = "",
this.versionLast = "",
this.sourceCode = '',
this.headers = '',
});
Source.fromJson(Map<String, dynamic> json) {
name = json['name'];
id = json['id'];
baseUrl = json['baseUrl'];
lang = json['lang'];
typeSource = json['typeSource'];
iconUrl = json['iconUrl'];
dateFormat = json['dateFormat'];
dateFormatLocale = json['dateFormatLocale'];
isNsfw = json['isNsfw'];
hasCloudflare = json['hasCloudflare'];
sourceCodeUrl = json['sourceCodeUrl'];
apiUrl = json['apiUrl'];
version = json['version'];
}
}
enum TypeSource {
single,
// enum TypeSource {
// single,
mangathemesia,
// mangathemesia,
comick,
// comick,
mmrcms,
// mmrcms,
heancms,
// heancms,
madara,
// madara,
mangadex
}
// mangadex
// }
// class Source {
// String? name;
// String? baseUrl;
// String? lang;
// bool? isActive;
// bool? isAdded;
// bool? isPinned;
// bool? lastUsed;
// bool? isFullData;
// bool? isNsfw;
// String? sourceCodeUrl;
// String? typeSource;
// String? iconUrl;
// bool? hasCloudflare;
// String? dateFormat;
// String? dateFormatLocale;
// String? apiUrl;
// String? version;
// Source({
// this.name = "",
// this.baseUrl = "",
// this.lang = "",
// this.typeSource = "",
// this.iconUrl = "",
// this.dateFormat,
// this.dateFormatLocale,
// this.isActive = true,
// this.isAdded = false,
// this.isNsfw = false,
// this.isFullData = false,
// this.hasCloudflare = false,
// this.isPinned = false,
// this.lastUsed = false,
// this.sourceCodeUrl = "",
// this.apiUrl = "",
// this.version = "",
// });
// Source.fromJson(Map<String, dynamic> json) {
// name = json['name'];
// id = json['id'];
// baseUrl = json['baseUrl'];
// lang = json['lang'];
// typeSource = json['typeSource'];
// iconUrl = json['iconUrl'];
// dateFormat = json['dateFormat'];
// dateFormatLocale = json['dateFormatLocale'];
// isNsfw = json['isNsfw'];
// hasCloudflare = json['hasCloudflare'];
// sourceCodeUrl = json['sourceCodeUrl'];
// apiUrl = json['apiUrl'];
// version = json['version'];
// }
// }

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/modules/browse/extension/providers/refresh_source_list_data.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/extension_screen.dart';
import 'package:mangayomi/modules/browse/migrate_screen.dart';
import 'package:mangayomi/modules/browse/sources/sources_screen.dart';
@ -28,7 +26,6 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
_chekPermission();
setState(() {
_textEditingController.clear();
_entriesFilter = [];
_isSearch = false;
});
});
@ -39,13 +36,10 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
await StorageProvider().requestPermission();
}
List<Source> _entries = [];
List<Source> _entriesFilter = [];
final _textEditingController = TextEditingController();
bool _isSearch = false;
@override
Widget build(BuildContext context) {
ref.watch(refreshSourceListDataProvider);
return DefaultTabController(
animationDuration: Duration.zero,
length: 3,
@ -61,13 +55,7 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
_isSearch
? SeachFormTextField(
onChanged: (value) {
setState(() {
_entriesFilter = _entries
.where((element) => element.sourceName!
.toLowerCase()
.contains(value.toLowerCase()))
.toList();
});
setState(() {});
},
onSuffixPressed: () {
_textEditingController.clear();
@ -77,7 +65,6 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
_isSearch = false;
});
_textEditingController.clear();
_entriesFilter = _entries;
},
controller: _textEditingController,
)
@ -105,6 +92,7 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
splashRadius: 20,
onPressed: () {
if (_tabBarController.index == 0) {
context.push('/sourceFilter');
} else if (_tabBarController.index == 1) {
_textEditingController.clear();
context.push('/extensionLang');
@ -131,10 +119,7 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
body: TabBarView(controller: _tabBarController, children: [
const SourcesScreen(),
ExtensionScreen(
entriesData: (val) {
_entries = val as List<Source>;
},
entriesFilter: _entriesFilter,
query: _textEditingController.text,
),
const MigrateScreen()
]),

View file

@ -11,7 +11,8 @@ class ExtensionsLang extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
language.sort((a, b) => a.compareTo(b));
final languages = languagesMap.entries.map((e) => e.value).toList();
languages.sort((a, b) => a.compareTo(b));
return Scaffold(
appBar: AppBar(
title: const Text("Extensions"),
@ -22,22 +23,22 @@ class ExtensionsLang extends ConsumerWidget {
builder: (context, snapshot) {
List<Source>? entries = snapshot.hasData ? snapshot.data : [];
return ListView.builder(
itemCount: language.length,
itemCount: languages.length,
itemBuilder: (context, index) {
final lang = languages[index];
return ExtensionLangListTileWidget(
lang: lang(language[index]),
lang: lang,
onChanged: (val) {
isar.writeTxnSync(() {
for (var source in entries) {
if (source.lang == lang(language[index])) {
if (source.lang == lang) {
isar.sources.putSync(source..isActive = val == true);
}
}
});
},
value: entries!
.where((element) =>
element.lang == "${lang(language[index])}")
.where((element) => element.lang == lang)
.where((element) => element.isActive!)
.isNotEmpty,
);

View file

@ -4,72 +4,74 @@ import 'package:grouped_list/grouped_list.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_sources.dart';
import 'package:mangayomi/utils/lang.dart';
import 'package:mangayomi/modules/browse/extension/widgets/extension_list_tile_widget.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
class ExtensionScreen extends ConsumerWidget {
final Function(dynamic) entriesData;
final List<Source> entriesFilter;
const ExtensionScreen(
{required this.entriesData, required this.entriesFilter, super.key});
final String query;
const ExtensionScreen({required this.query, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: const EdgeInsets.only(top: 10),
child: StreamBuilder(
stream: isar.sources
.filter()
.idIsNotNull()
.and()
.isActiveEqualTo(true)
.watch(fireImmediately: true),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
final entries = snapshot.data!
.where((element) => ref.watch(showNSFWStateProvider)
? true
: element.isNsfw == false)
.toList();
entriesData(entries);
return GroupedListView<Source, String>(
elements: entriesFilter.isNotEmpty ? entriesFilter : entries,
groupBy: (element) => completeLang(element.lang!.toLowerCase()),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
groupByValue,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
),
],
ref.watch(fetchSourcesListProvider(id: null));
return RefreshIndicator(
onRefresh: () => ref.refresh(fetchSourcesListProvider(id: null).future),
child: Padding(
padding: const EdgeInsets.only(top: 10),
child: StreamBuilder(
stream: query.isNotEmpty
? isar.sources
.filter()
.nameContains(query.toLowerCase(), caseSensitive: false)
.idIsNotNull()
.and()
.isActiveEqualTo(true)
.watch(fireImmediately: true)
: isar.sources
.filter()
.idIsNotNull()
.and()
.isActiveEqualTo(true)
.watch(fireImmediately: true),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
final entries = snapshot.data!
.where((element) => ref.watch(showNSFWStateProvider)
? true
: element.isNsfw == false)
.toList();
return GroupedListView<Source, String>(
elements: entries,
groupBy: (element) =>
completeLang(element.lang!.toLowerCase()),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
groupByValue,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
),
],
),
),
),
itemBuilder: (context, Source element) {
return ExtensionListTileWidget(
lang: element.lang!,
onChanged: (val) {
isar.writeTxnSync(() {
isar.sources.putSync(element..isAdded = val);
});
},
sourceName: element.sourceName!,
value: element.isAdded!,
logoUrl: element.logoUrl!,
isNsfw: element.isNsfw!,
);
},
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName!.compareTo(item2.sourceName!),
order: GroupedListOrder.ASC,
);
}
return Container();
}),
itemBuilder: (context, Source element) {
return ExtensionListTileWidget(
source: element,
);
},
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
);
}
return Container();
}),
),
);
}
}

View file

@ -0,0 +1,147 @@
import 'dart:convert';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/compiler/compiler.dart';
import 'package:mangayomi/eval/runtime/runtime.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:http/http.dart' as http;
part 'fetch_sources.g.dart';
@riverpod
Future fetchSourcesList(FetchSourcesListRef ref, {int? id}) async {
final req = await http
.get(Uri.parse("https://kodjodevf.github.io/mangayomi-extensions/index.json"));
final res = jsonDecode(req.body) as List;
final sourceList = res.map((e) => Source.fromJson(e)).toList();
isar.writeTxnSync(() async {
for (var source in sourceList) {
if (id != null) {
if (id == source.id) {
final sourc = isar.sources.getSync(id)!;
final req = await http.get(Uri.parse(source.sourceCodeUrl!));
final headers = await getHeaders(req.body, source.baseUrl!);
isar.writeTxnSync(() {
isar.sources.putSync(sourc
..headers = headers ?? ""
..isAdded = true
..sourceCode = req.body
..sourceCodeUrl = source.sourceCodeUrl
..id = id
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version);
});
// log("successfully installed");
}
} else if (isar.sources.getSync(source.id!) != null) {
// log("exist");
final sourc = isar.sources.getSync(source.id!)!;
if (compareVersions(sourc.version!, source.version!) < 0) {
// log("update aivalable auto update");
if (isar.settings.getSync(227)!.autoUpdateExtensions ?? false) {
final req = await http.get(Uri.parse(source.sourceCodeUrl!));
final headers = await getHeaders(req.body, source.baseUrl!);
isar.writeTxnSync(() {
isar.sources.putSync(sourc
..headers = headers
..sourceCode = req.body
..sourceCodeUrl = source.sourceCodeUrl
..id = source.id
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..isFullData = source.isFullData
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version);
});
} else {
// log("update aivalable");
isar.sources.putSync(sourc..versionLast = source.version);
}
}
} else {
isar.sources.putSync(Source()
..sourceCodeUrl = source.sourceCodeUrl
..id = source.id
..sourceCode = source.sourceCode
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version);
// log("new source");
}
}
});
}
int compareVersions(String version1, String version2) {
List<String> v1Components = version1.split('.');
List<String> v2Components = version2.split('.');
for (int i = 0; i < v1Components.length && i < v2Components.length; i++) {
int v1Value = int.parse(v1Components[i]);
int v2Value = int.parse(v2Components[i]);
if (v1Value < v2Value) {
return -1;
} else if (v1Value > v2Value) {
return 1;
}
}
if (v1Components.length < v2Components.length) {
return -1;
} else if (v1Components.length > v2Components.length) {
return 1;
}
return 0;
}
Future<String?> getHeaders(String codeSource, String baseUrl) async {
try {
final bytecode = compilerEval(codeSource);
final runtime = runtimeEval(bytecode);
runtime.args = [$String(baseUrl)];
var result2 = await runtime.executeLib(
'package:package:mangayomi/main.dart',
'getHeader',
);
Map<String, String> headers = {};
if (result2 is $Map) {
headers = result2.$reified
.map((key, value) => MapEntry(key.toString(), value.toString()));
}
return jsonEncode(headers);
} catch (_) {
return null;
}
}

View file

@ -0,0 +1,112 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fetch_sources.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$fetchSourcesListHash() => r'5cb7bececf304fc891319051c39cbf4566127a14';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef FetchSourcesListRef = AutoDisposeFutureProviderRef<dynamic>;
/// See also [fetchSourcesList].
@ProviderFor(fetchSourcesList)
const fetchSourcesListProvider = FetchSourcesListFamily();
/// See also [fetchSourcesList].
class FetchSourcesListFamily extends Family<AsyncValue<dynamic>> {
/// See also [fetchSourcesList].
const FetchSourcesListFamily();
/// See also [fetchSourcesList].
FetchSourcesListProvider call({
int? id,
}) {
return FetchSourcesListProvider(
id: id,
);
}
@override
FetchSourcesListProvider getProviderOverride(
covariant FetchSourcesListProvider provider,
) {
return call(
id: provider.id,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'fetchSourcesListProvider';
}
/// See also [fetchSourcesList].
class FetchSourcesListProvider extends AutoDisposeFutureProvider<dynamic> {
/// See also [fetchSourcesList].
FetchSourcesListProvider({
this.id,
}) : super.internal(
(ref) => fetchSourcesList(
ref,
id: id,
),
from: fetchSourcesListProvider,
name: r'fetchSourcesListProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$fetchSourcesListHash,
dependencies: FetchSourcesListFamily._dependencies,
allTransitiveDependencies:
FetchSourcesListFamily._allTransitiveDependencies,
);
final int? id;
@override
bool operator ==(Object other) {
return other is FetchSourcesListProvider && other.id == id;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, id.hashCode);
return _SystemHash.finish(hash);
}
}
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View file

@ -1,36 +1,36 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/sources/source_list.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'refresh_source_list_data.g.dart';
// import 'package:isar/isar.dart';
// import 'package:mangayomi/main.dart';
// import 'package:mangayomi/models/source.dart';
// import 'package:mangayomi/sources/source_list.dart';
// import 'package:riverpod_annotation/riverpod_annotation.dart';
// part 'refresh_source_list_data.g.dart';
@riverpod
refreshSourceListData(RefreshSourceListDataRef ref) {
isar.writeTxnSync(() {
for (var source in sourcesList) {
final sourceF = isar.sources
.filter()
.sourceNameEqualTo(source.sourceName)
.and()
.langEqualTo(source.lang)
.findAllSync();
if (sourceF.isEmpty) {
isar.sources.putSync(source);
} else {
isar.sources.putSync(sourceF.first
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..isCloudflare = source.isCloudflare
..logoUrl = source.logoUrl
..typeSource = source.typeSource
..isFullData = source.isFullData
..lang = source.lang
..isNsfw = source.isNsfw
..sourceName = source.sourceName);
}
}
});
}
// @riverpod
// refreshSourceListData(RefreshSourceListDataRef ref) {
// isar.writeTxnSync(() {
// for (var source in sourcesList) {
// final sourceF = isar.sources
// .filter()
// .nameEqualTo(source.name)
// .and()
// .langEqualTo(source.lang)
// .findAllSync();
// if (sourceF.isEmpty) {
// isar.sources.putSync(source);
// } else {
// isar.sources.putSync(sourceF.first
// ..apiUrl = source.apiUrl
// ..baseUrl = source.baseUrl
// ..dateFormat = source.dateFormat
// ..dateFormatLocale = source.dateFormatLocale
// ..hasCloudflare = source.hasCloudflare
// ..logoUrl = source.logoUrl
// ..typeSource = source.typeSource
// ..isFullData = source.isFullData
// ..lang = source.lang
// ..isNsfw = source.isNsfw
// ..name = source.name);
// }
// }
// });
// }

View file

@ -1,25 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'refresh_source_list_data.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$refreshSourceListDataHash() =>
r'c6ab103b0519d79afc8530ab7027a988203de5d3';
/// See also [refreshSourceListData].
@ProviderFor(refreshSourceListData)
final refreshSourceListDataProvider = AutoDisposeProvider<dynamic>.internal(
refreshSourceListData,
name: r'refreshSourceListDataProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$refreshSourceListDataHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef RefreshSourceListDataRef = AutoDisposeProviderRef<dynamic>;
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View file

@ -1,63 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/fetch_sources.dart';
import 'package:mangayomi/utils/lang.dart';
class ExtensionListTileWidget extends StatelessWidget {
final String sourceName;
final String lang;
final bool value;
final String logoUrl;
final bool isNsfw;
final Function(bool) onChanged;
const ExtensionListTileWidget(
{super.key,
required this.sourceName,
required this.lang,
required this.value,
required this.onChanged,
required this.logoUrl,
required this.isNsfw});
class ExtensionListTileWidget extends ConsumerStatefulWidget {
final Source source;
const ExtensionListTileWidget({
super.key,
required this.source,
});
@override
Widget build(BuildContext context) {
ConsumerState<ExtensionListTileWidget> createState() =>
_ExtensionListTileWidgetState();
}
class _ExtensionListTileWidgetState
extends ConsumerState<ExtensionListTileWidget> {
bool _isLoading = false;
@override
Widget build(
BuildContext context,
) {
final updateAivalable =
compareVersions(widget.source.version!, widget.source.versionLast!) < 0;
final sourceNotEmpty = widget.source.sourceCode != null &&
widget.source.sourceCode!.isNotEmpty;
return ListTile(
onTap: () {
onChanged(!value);
// onChanged(!value);
},
leading: Container(
height: 37,
width: 37,
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor.withOpacity(0.5),
borderRadius: BorderRadius.circular(5)),
child:
// logoUrl.isEmpty
// ?
const Icon(Icons.source_outlined)
// : CachedNetworkImage(
// imageUrl: logoUrl,
// fit: BoxFit.contain,
// width: 37,
// height: 37,
// errorWidget: (context, url, error) {
// return const SizedBox(
// width: 37,
// height: 37,
// child: Center(
// child: Icon(Icons.source_outlined),
// ),
// );
// },
// ),
),
title: Text(sourceName),
height: 37,
width: 37,
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor.withOpacity(0.5),
borderRadius: BorderRadius.circular(5)),
child:
// iconUrl.isEmpty
// ?
const Icon(Icons.source_outlined)
// : CachedNetworkImage(
// imageUrl: iconUrl,
// fit: BoxFit.contain,
// width: 37,
// height: 37,
// errorWidget: (context, url, error) {
// return const SizedBox(
// width: 37,
// height: 37,
// child: Center(
// child: Icon(Icons.source_outlined),
// ),
// );
// },
// ),
),
title: Text(widget.source.name!),
subtitle: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
completeLang(lang.toLowerCase()),
completeLang(widget.source.lang!.toLowerCase()),
style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 12),
),
if (isNsfw)
if (widget.source.isNsfw!)
Row(
children: [
const SizedBox(
@ -74,10 +83,33 @@ class ExtensionListTileWidget extends StatelessWidget {
)
],
),
trailing: Switch(
value: value,
onChanged: (value) {
onChanged(value);
}));
trailing: TextButton(
onPressed: !updateAivalable && sourceNotEmpty
? null
: () async {
setState(() {
_isLoading = true;
});
await ref.watch(
fetchSourcesListProvider(id: widget.source.id).future);
if (mounted) {
setState(() {
_isLoading = false;
});
}
},
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2.0,
))
: Text(!sourceNotEmpty
? "Install"
: updateAivalable
? "Update"
: "Latest"),
));
}
}

View file

@ -4,11 +4,10 @@ import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/services/get_manga_detail.dart';
import 'package:mangayomi/services/search_manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/source_list.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/colors.dart';
import 'package:mangayomi/utils/headers.dart';
@ -34,7 +33,12 @@ class _GlobalSearchScreenState extends ConsumerState<GlobalSearchScreen> {
Widget build(BuildContext context) {
final sourceList = ref.watch(onlyIncludePinnedSourceStateProvider)
? isar.sources.filter().isPinnedEqualTo(true).findAllSync()
: sourcesList;
: isar.sources
.filter()
.idIsNotNull()
.and()
.isAddedEqualTo(true)
.findAllSync();
return Scaffold(
appBar: AppBar(
leading: Container(),
@ -90,7 +94,10 @@ class SourceSearchScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final search = ref.watch(searchMangaProvider(
source: source.sourceName!, query: query, lang: source.lang!));
source: source,
page: 1,
query: query,
));
return Scaffold(
body: SizedBox(
height: 260,
@ -101,13 +108,12 @@ class SourceSearchScreen extends ConsumerWidget {
onTap: () {
Map<String, dynamic> data = {
'query': query,
'source': source.sourceName,
'lang': source.lang,
'source': source,
'viewOnly': true,
};
context.push('/searchResult', extra: data);
},
title: Text(source.sourceName!),
title: Text(source.name!),
subtitle: Text(
completeLang(source.lang!),
style: const TextStyle(fontSize: 10),
@ -129,8 +135,7 @@ class SourceSearchScreen extends ConsumerWidget {
itemBuilder: (context, index) {
return MangaGlobalImageCard(
manga: data[index]!,
source: source.sourceName!,
lang: source.lang!,
source: source,
);
},
);
@ -147,14 +152,13 @@ class SourceSearchScreen extends ConsumerWidget {
}
class MangaGlobalImageCard extends ConsumerStatefulWidget {
final GetManga manga;
final String source;
final String lang;
final MangaModel manga;
final Source source;
const MangaGlobalImageCard({
super.key,
required this.manga,
required this.source,
required this.lang,
});
@override
@ -168,19 +172,22 @@ class _MangaGlobalImageCardState extends ConsumerState<MangaGlobalImageCard>
Widget build(BuildContext context) {
super.build(context);
final getMangaDetail = ref.watch(getMangaDetailProvider(
source: widget.source, manga: widget.manga, lang: widget.lang));
manga: widget.manga
..lang = widget.source.lang
..source = widget.source.name,
source: widget.source));
return getMangaDetail.when(
data: (data) {
return GestureDetector(
onTap: () async {
pushToMangaReaderDetail(
context: context, getManga: data, lang: widget.lang);
context: context, getManga: data, lang: widget.source.lang!);
},
child: StreamBuilder(
stream: isar.mangas
.filter()
.langEqualTo(widget.lang)
.langEqualTo(widget.source.lang)
.nameEqualTo(data.name)
.sourceEqualTo(data.source)
.favoriteEqualTo(true)
@ -196,8 +203,8 @@ class _MangaGlobalImageCardState extends ConsumerState<MangaGlobalImageCard>
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: cachedNetworkImage(
headers: ref.watch(
headersProvider(source: data.source!)),
headers: ref.watch(headersProvider(
source: data.source!, lang: data.lang!)),
imageUrl: data.imageUrl!,
width: 100,
height: 140,

View file

@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:grouped_list/grouped_list.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/utils/lang.dart';
class SourcesFilterScreen extends ConsumerWidget {
const SourcesFilterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text("Sources"),
),
body: Padding(
padding: const EdgeInsets.only(top: 10),
child: StreamBuilder(
stream: isar.sources
.filter()
.idIsNotNull()
.and()
.sourceCodeIsNotEmpty()
.watch(fireImmediately: true),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
final entries = snapshot.data!
.where((element) => ref.watch(showNSFWStateProvider)
? true
: element.isNsfw == false)
.toList();
return GroupedListView<Source, String>(
elements: entries,
groupBy: (element) => element.lang!,
groupSeparatorBuilder: (String groupByValue) =>
SwitchListTile(
value: entries
.where((element) =>
element.lang == groupByValue && element.isActive!)
.isNotEmpty,
onChanged: (val) {
isar.writeTxnSync(() {
for (var source in entries) {
if (source.lang == groupByValue) {
isar.sources
.putSync(source..isActive = val == true);
}
}
});
},
title: Text(
completeLang(groupByValue),
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
),
),
itemBuilder: (context, Source element) {
if (entries
.where(
(ele) => ele.lang == element.lang && ele.isActive!)
.isEmpty) {
return Container();
}
return CheckboxListTile(
secondary: Container(
height: 37,
width: 37,
decoration: BoxDecoration(
color: Theme.of(context)
.secondaryHeaderColor
.withOpacity(0.5),
borderRadius: BorderRadius.circular(5)),
child: const Icon(Icons.source_outlined),
),
onChanged: (bool? value) {
isar.writeTxnSync(() {
isar.sources.putSync(element..isAdded = value);
});
},
value: element.isAdded!,
title: Text(element.name!),
);
},
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
);
}
return Container();
}),
),
);
}
}

View file

@ -62,7 +62,7 @@ class SourcesScreen extends ConsumerWidget {
groupComparator: (group1, group2) =>
group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName!.compareTo(item2.sourceName!),
item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
);
}),
@ -110,7 +110,7 @@ class SourcesScreen extends ConsumerWidget {
groupComparator: (group1, group2) =>
group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName!.compareTo(item2.sourceName!),
item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
);
}),
@ -158,7 +158,7 @@ class SourcesScreen extends ConsumerWidget {
groupComparator: (group1, group2) =>
group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName!.compareTo(item2.sourceName!),
item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
);
}),

View file

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga_type.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/utils/colors.dart';
import 'package:mangayomi/utils/lang.dart';
@ -23,11 +22,7 @@ class SourceListTile extends StatelessWidget {
}
});
context.push('/mangaHome',
extra: MangaType(
isFullData: source.isFullData,
lang: source.lang,
source: source.sourceName));
context.push('/mangaHome', extra: source);
},
leading: Container(
height: 37,
@ -41,7 +36,7 @@ class SourceListTile extends StatelessWidget {
const Icon(Icons.source_outlined)
// : CachedNetworkImage(
// httpHeaders: ref.watch(
// headersProvider(source: source.sourceName!)),
// headersProvider(source: source.name!)),
// imageUrl: source.logoUrl!,
// fit: BoxFit.contain,
// width: 37,
@ -80,7 +75,7 @@ class SourceListTile extends StatelessWidget {
)
],
),
title: Text(source.sourceName!),
title: Text(source.name!),
trailing: IconButton(
onPressed: () {
isar.writeTxnSync(() =>

View file

@ -179,7 +179,8 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen> {
manga.customCoverImage as Uint8List)
: cachedNetworkImage(
headers: ref.watch(headersProvider(
source: manga.source!)),
source: manga.source!,
lang: manga.lang!)),
imageUrl: manga.imageUrl!,
width: 60,
height: 90,

View file

@ -24,7 +24,7 @@ final importArchivesFromDirectoryProvider =
typedef ImportArchivesFromDirectoryRef = AutoDisposeFutureProviderRef<dynamic>;
String _$importArchivesFromFileHash() =>
r'bc892e1fb32c5d5ec639ad5f76c27996605181f5';
r'ee38771c056b3f15d856ed0b91cd559ab22dc236';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -64,8 +64,9 @@ class LibraryGridViewWidget extends StatelessWidget {
as ImageProvider
: CachedNetworkImageProvider(
entriesManga[index].imageUrl!,
headers: ref.watch(
headersProvider(source: entriesManga[index].source!)),
headers: ref.watch(headersProvider(
source: entriesManga[index].source!,
lang: entriesManga[index].lang!)),
),
onTap: () {
if (isLongPressed) {

View file

@ -107,8 +107,10 @@ class LibraryListViewWidget extends StatelessWidget {
: CachedNetworkImageProvider(
entriesManga[index].imageUrl!,
headers: ref.watch(headersProvider(
source: entriesManga[index]
.source!)),
source:
entriesManga[index].source!,
lang:
entriesManga[index].lang!)),
),
child: InkWell(
child: Container(

View file

@ -1,10 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/services/get_manga_detail.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/modules/manga/detail/manga_details_view.dart';
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
import 'package:mangayomi/modules/manga/detail/providers/isar_providers.dart';
import 'package:mangayomi/modules/widgets/error_text.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
@ -18,6 +15,23 @@ class MangaReaderDetail extends ConsumerStatefulWidget {
}
class _MangaReaderDetailState extends ConsumerState<MangaReaderDetail> {
@override
void initState() {
_init();
super.initState();
}
_init() async {
await Future.delayed(const Duration(milliseconds: 100));
await ref.read(
updateMangaDetailProvider(mangaId: widget.mangaId, isInit: true)
.future);
setState(() {
_isLoading = false;
});
}
bool _isLoading = true;
@override
Widget build(BuildContext context) {
final manga =
@ -27,61 +41,25 @@ class _MangaReaderDetailState extends ConsumerState<MangaReaderDetail> {
data: (manga) {
return RefreshIndicator(
onRefresh: () async {
final mangaS = GetManga(
genre: manga.genre!,
author: manga.author,
status: manga.status,
chapters: manga.chapters.toList(),
imageUrl: manga.imageUrl,
description: manga.description,
url: manga.link,
name: manga.name,
source: manga.source);
bool isOk = false;
ref
.watch(getMangaDetailProvider(
manga: mangaS,
lang: manga.lang!,
source: manga.source!,
).future)
.then((value) async {
if (value.chapters.isNotEmpty &&
value.chapters.length > manga.chapters.length) {
await isar.writeTxn(() async {
int newChapsIndex =
value.chapters.length - manga.chapters.length;
manga.lastUpdate = DateTime.now().millisecondsSinceEpoch;
for (var i = 0; i < newChapsIndex; i++) {
final chapters = Chapter(
name: value.chapters[i].name,
url: value.chapters[i].url,
dateUpload: value.chapters[i].dateUpload,
isBookmarked: false,
scanlator: value.chapters[i].scanlator,
isRead: false,
lastPageRead: '',
)..manga.value = manga;
await isar.chapters.put(chapters);
await chapters.manga.save();
}
});
}
if (mounted) {
setState(() {
isOk = true;
});
}
});
await Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (isOk == true) {
return false;
}
return true;
});
await ref.read(
updateMangaDetailProvider(mangaId: manga.id, isInit: false)
.future);
},
child: MangaDetailsView(
manga: manga!,
child: Stack(
children: [
MangaDetailsView(
manga: manga!,
),
if (_isLoading)
const Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: Center(
child: CircularProgressIndicator(),
)),
],
),
);
},

View file

@ -202,7 +202,8 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
fit: BoxFit.cover)
: cachedNetworkImage(
headers: ref.watch(headersProvider(
source: widget.manga!.source!)),
source: widget.manga!.source!,
lang: widget.manga!.lang!)),
imageUrl: widget.manga!.imageUrl!,
width: mediaWidth(context, 1),
height: 300,
@ -365,11 +366,12 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
context.push("/categories");
} else if (value == 1) {
} else if (value == 2) {
String url = getMangaAPIUrl(
widget.manga!.source!)
.isEmpty
final source = getSource(widget.manga!.lang!,
widget.manga!.source!);
String url = source.apiUrl!.isEmpty
? widget.manga!.link!
: "${getMangaBaseUrl(widget.manga!.source!)}${widget.manga!.link!}";
: "${source.baseUrl}${widget.manga!.link!}";
Share.share(url);
}
}),
@ -426,14 +428,22 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
.spaceBetween
: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets
.symmetric(horizontal: 8),
child: Text(
'${chapters.length} chapters',
style: const TextStyle(
fontWeight:
FontWeight.bold),
Container(
height: chapters.isEmpty
? mediaHeight(context, 1)
: null,
color: Theme.of(context)
.scaffoldBackgroundColor,
child: Padding(
padding: const EdgeInsets
.symmetric(
horizontal: 8),
child: Text(
'${chapters.length} chapters',
style: const TextStyle(
fontWeight:
FontWeight.bold),
),
),
),
if (isLocalArchive)
@ -1045,7 +1055,6 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
Stack(
children: [
SizedBox(
height: 180,
width: mediaWidth(context, 1),
child: Row(
children: [
@ -1211,6 +1220,11 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
],
),
),
if (chapterLength == 0)
Container(
width: mediaWidth(context, 1),
height: mediaHeight(context, 1),
color: Theme.of(context).scaffoldBackgroundColor)
],
),
],
@ -1223,7 +1237,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
as ImageProvider
: CachedNetworkImageProvider(widget.manga!.imageUrl!);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 13),
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 20),
child: GestureDetector(
onTap: () {
_openImage(imageProvider);
@ -1277,12 +1291,15 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
elevation: 0),
onPressed: () {
final manga = widget.manga!;
String url = getMangaAPIUrl(manga.source!).isEmpty
? manga.link!
: "${getMangaBaseUrl(manga.source!)}${manga.link!}";
final source =
getSource(widget.manga!.lang!, widget.manga!.source!);
String url = source.apiUrl!.isEmpty
? widget.manga!.link!
: "${source.baseUrl}${widget.manga!.link!}";
Map<String, String> data = {
'url': url,
'source': manga.source!,
'sourceId': source.id.toString(),
'title': manga.name!
};
context.push("/mangawebview", extra: data);

View file

@ -0,0 +1,126 @@
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/services/get_manga_detail.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'update_manga_detail_providers.g.dart';
@riverpod
Future<dynamic> updateMangaDetail(UpdateMangaDetailRef ref,
{required int? mangaId, required bool isInit}) async {
final manga = isar.mangas.getSync(mangaId!);
if (manga!.chapters.isNotEmpty && isInit) {
return;
}
final source = getSource(manga.lang!, manga.source!);
final mangaS = MangaModel(
name: manga.name,
link: manga.link,
imageUrl: manga.imageUrl,
baseUrl: source.baseUrl,
apiUrl: source.apiUrl,
lang: manga.lang,
dateFormat: source.dateFormat,
dateFormatLocale: source.dateFormatLocale);
final getManga = await ref
.watch(getMangaDetailProvider(manga: mangaS, source: source).future);
manga
..imageUrl =
getManga.imageUrl!.isEmpty ? manga.imageUrl ?? "" : getManga.imageUrl
..name = getManga.name!.isEmpty
? manga.name ?? ""
: getManga.name!.trim().trimLeft().trimRight()
..genre = getManga.genre!.isEmpty
? manga.genre ?? []
: getManga.genre!
.map((e) => e.toString().trim().trimLeft().trimRight())
.toList()
..author = getManga.author!.isEmpty
? manga.author ?? ""
: getManga.author!.trim().trimLeft().trimRight()
..status = switch (getManga.status) {
0 => Status.ongoing,
1 => Status.completed,
2 => Status.onHiatus,
3 => Status.canceled,
4 => Status.publishingFinished,
_ => Status.unknown,
}
..description = getManga.description!.isEmpty
? manga.description ?? ""
: getManga.description!.trim().trimLeft().trimRight()
..link = getManga.link!.isEmpty ? manga.link ?? "" : getManga.link
..source = getManga.source!.isEmpty ? manga.source ?? "" : getManga.source
..lang = getManga.lang!.isEmpty ? manga.lang ?? "" : getManga.lang
..lastUpdate = DateTime.now().millisecondsSinceEpoch;
isar.writeTxnSync(() {
isar.mangas.putSync(manga);
if (getManga.names!.isNotEmpty &&
getManga.names!.length > manga.chapters.length) {
int newChapsIndex = getManga.names!.length - manga.chapters.length;
manga.lastUpdate = DateTime.now().millisecondsSinceEpoch;
for (var i = 0; i < newChapsIndex; i++) {
String title = "";
String scanlator = "";
if (getManga.chaptersChaps != null &&
getManga.chaptersVolumes != null) {
title = beautifyChapterName(getManga.chaptersVolumes![i],
getManga.chaptersChaps![i], getManga.names![i], getManga.lang!);
} else {
title = getManga.names![i].trim().trimLeft().trimRight();
}
if (getManga.chaptersScanlators != null) {
scanlator = getManga.chaptersScanlators![i]
.toString()
.replaceAll(']', "")
.replaceAll("[", "");
}
final chapters = Chapter(
name: title,
url: getManga.urls![i].trim().trimLeft().trimRight(),
dateUpload: getManga.chaptersDateUploads![i],
scanlator: scanlator,
)..manga.value = manga;
isar.chapters.putSync(chapters);
chapters.manga.saveSync();
}
}
});
}
String beautifyChapterName(String vol, String chap, String title, String lang) {
String result = "";
if (vol.trim().trimLeft().trimRight() != "null" && vol.isNotEmpty) {
if (chap.trim().trimLeft().trimRight() != "null" && chap.isEmpty) {
result += "Volume ${vol.trim().trimLeft().trimRight()} ";
} else {
result += "Vol. ${vol.trim().trimLeft().trimRight()} ";
}
}
if (chap.trim().trimLeft().trimRight() != "null" && chap.isNotEmpty) {
if (vol.trim().trimLeft().trimRight() != "null" && vol.isEmpty) {
if (lang.trim().trimLeft().trimRight() != "null" && lang == "fr") {
result += "Chapitre ${chap.trim().trimLeft().trimRight()}";
} else {
result += "Chapter ${chap.trim().trimLeft().trimRight()}";
}
} else {
result += "Ch. ${chap.trim().trimLeft().trimRight()} ";
}
}
if (title.trim().trimLeft().trimRight() != "null" && title.isNotEmpty) {
if (chap.trim().trimLeft().trimRight() != "null" && chap.isEmpty) {
result += title.trim().trimLeft().trimRight();
} else {
result += " : ${title.trim().trimLeft().trimRight()}";
}
}
return result;
}

View file

@ -0,0 +1,121 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'update_manga_detail_providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$updateMangaDetailHash() => r'80479a109b0beb0e0d57d93a32a587bbf41d5bc0';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef UpdateMangaDetailRef = AutoDisposeFutureProviderRef<dynamic>;
/// See also [updateMangaDetail].
@ProviderFor(updateMangaDetail)
const updateMangaDetailProvider = UpdateMangaDetailFamily();
/// See also [updateMangaDetail].
class UpdateMangaDetailFamily extends Family<AsyncValue<dynamic>> {
/// See also [updateMangaDetail].
const UpdateMangaDetailFamily();
/// See also [updateMangaDetail].
UpdateMangaDetailProvider call({
required int? mangaId,
required bool isInit,
}) {
return UpdateMangaDetailProvider(
mangaId: mangaId,
isInit: isInit,
);
}
@override
UpdateMangaDetailProvider getProviderOverride(
covariant UpdateMangaDetailProvider provider,
) {
return call(
mangaId: provider.mangaId,
isInit: provider.isInit,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'updateMangaDetailProvider';
}
/// See also [updateMangaDetail].
class UpdateMangaDetailProvider extends AutoDisposeFutureProvider<dynamic> {
/// See also [updateMangaDetail].
UpdateMangaDetailProvider({
required this.mangaId,
required this.isInit,
}) : super.internal(
(ref) => updateMangaDetail(
ref,
mangaId: mangaId,
isInit: isInit,
),
from: updateMangaDetailProvider,
name: r'updateMangaDetailProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$updateMangaDetailHash,
dependencies: UpdateMangaDetailFamily._dependencies,
allTransitiveDependencies:
UpdateMangaDetailFamily._allTransitiveDependencies,
);
final int? mangaId;
final bool isInit;
@override
bool operator ==(Object other) {
return other is UpdateMangaDetailProvider &&
other.mangaId == mangaId &&
other.isInit == isInit;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, mangaId.hashCode);
hash = _SystemHash.combine(hash, isInit.hashCode);
return _SystemHash.finish(hash);
}
}
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View file

@ -17,63 +17,65 @@ class ReadMoreWidgetState extends State<ReadMoreWidget>
late bool expanded = widget.text.trim().length < 232;
@override
Widget build(BuildContext context) {
return Column(
children: [
Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: ExpandableText(
animationDuration: const Duration(milliseconds: 500),
onExpandedChanged: (value) {
setState(() => expanded = value);
widget.onChanged(value);
},
expandOnTextTap: true,
widget.text.trim(),
expandText: '',
maxLines: 3,
expanded: expanded,
linkColor: Theme.of(context).scaffoldBackgroundColor,
animation: true,
collapseOnTextTap: true,
prefixText: '',
),
),
if (!expanded)
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
width: mediaWidth(context, 1),
height: 30,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.2),
Theme.of(context).scaffoldBackgroundColor
],
stops: const [0, .9],
return widget.text.isEmpty
? const Text("No description")
: Column(
children: [
Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: ExpandableText(
animationDuration: const Duration(milliseconds: 500),
onExpandedChanged: (value) {
setState(() => expanded = value);
widget.onChanged(value);
},
expandOnTextTap: true,
widget.text.trim(),
expandText: '',
maxLines: 3,
expanded: expanded,
linkColor: Theme.of(context).scaffoldBackgroundColor,
animation: true,
collapseOnTextTap: true,
prefixText: '',
),
),
child: const Icon(Icons.keyboard_arrow_down_sharp),
),
if (!expanded)
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
width: mediaWidth(context, 1),
height: 30,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.2),
Theme.of(context).scaffoldBackgroundColor
],
stops: const [0, .9],
),
),
child: const Icon(Icons.keyboard_arrow_down_sharp),
),
),
],
),
],
),
if (expanded)
SizedBox(
width: mediaWidth(context, 1),
height: 20,
child: const Icon(Icons.keyboard_arrow_up_sharp),
)
],
);
if (expanded)
SizedBox(
width: mediaWidth(context, 1),
height: 20,
child: const Icon(Icons.keyboard_arrow_up_sharp),
)
],
);
}
}

View file

@ -95,8 +95,9 @@ Future<List<String>> downloadChapter(
} else {
tasks.add(DownloadTask(
taskId: pageUrls[index],
headers: ref.watch(headersProvider(source: manga.source!)),
url: pageUrls[index],
headers: ref.watch(
headersProvider(source: manga.source!, lang: manga.lang!)),
url: pageUrls[index].trim().trimLeft().trimRight(),
filename: "${padIndex(index + 1)}.jpg",
baseDirectory:
Platform.isAndroid || desktopCustomDownloadLocation
@ -114,8 +115,9 @@ Future<List<String>> downloadChapter(
} else {
tasks.add(DownloadTask(
taskId: pageUrls[index],
headers: ref.watch(headersProvider(source: manga.source!)),
url: pageUrls[index],
headers: ref.watch(
headersProvider(source: manga.source!, lang: manga.lang!)),
url: pageUrls[index].trim().trimLeft().trimRight(),
filename: "${padIndex(index + 1)}.jpg",
baseDirectory:
Platform.isAndroid || desktopCustomDownloadLocation

View file

@ -6,7 +6,7 @@ part of 'download_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$downloadChapterHash() => r'1ddc8d7c1d9b1f3fdea4ba1b4e8252a61597a616';
String _$downloadChapterHash() => r'2af3ee0a570e8521336179f60dc1f9f2be919b9b';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -2,25 +2,21 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/models/manga_type.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/services/get_latest_updates_manga.dart';
import 'package:mangayomi/services/get_manga_detail.dart';
import 'package:mangayomi/services/get_popular_manga.dart';
import 'package:mangayomi/services/search_manga.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/utils/colors.dart';
import 'package:mangayomi/utils/media_query.dart';
import 'package:mangayomi/modules/library/search_text_form_field.dart';
import 'package:mangayomi/modules/manga/home/widget/mangas_card_selector.dart';
import 'package:mangayomi/modules/widgets/bottom_text_widget.dart';
import 'package:mangayomi/modules/widgets/cover_view_widget.dart';
import 'package:mangayomi/modules/widgets/gridview_widget.dart';
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
class MangaHomeScreen extends ConsumerStatefulWidget {
final MangaType mangaType;
const MangaHomeScreen({required this.mangaType, super.key});
final Source source;
const MangaHomeScreen({required this.source, super.key});
@override
ConsumerState<MangaHomeScreen> createState() => _MangaHomeScreenState();
@ -49,30 +45,26 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
final _textEditingController = TextEditingController();
String _query = "";
bool _isSearch = false;
AsyncValue<List<GetManga?>>? _getManga;
AsyncValue<List<MangaModel?>>? _getManga;
int _length = 0;
@override
Widget build(BuildContext context) {
if (_selectedIndex == 2 && _isSearch && _query.isNotEmpty) {
_getManga = ref.watch(searchMangaProvider(
source: widget.mangaType.source!,
query: _query,
lang: widget.mangaType.lang!));
_getManga = ref.watch(
searchMangaProvider(source: widget.source, query: _query, page: 1));
} else if (_selectedIndex == 1 && !_isSearch && _query.isEmpty) {
_getManga = ref.watch(getLatestUpdatesMangaProvider(
source: widget.mangaType.source!,
page: 1,
lang: widget.mangaType.lang!));
_getManga = ref
.watch(getLatestUpdatesMangaProvider(source: widget.source, page: 1));
} else if (_selectedIndex == 0 && !_isSearch && _query.isEmpty) {
_getManga = ref.watch(getPopularMangaProvider(
source: widget.mangaType.source!,
page: 1,
lang: widget.mangaType.lang!));
source: widget.source,
page: 1,
));
}
return Scaffold(
appBar: AppBar(
title: _isSearch ? null : Text('${widget.mangaType.source}'),
title: _isSearch ? null : Text('${widget.source.name}'),
actions: [
_isSearch
? SeachFormTextField(
@ -80,6 +72,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
if (submit.isNotEmpty) {
setState(() {
_selectedIndex = 2;
_query = submit;
});
} else {
@ -87,6 +80,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
_selectedIndex = 0;
});
}
_page = 1;
},
onChanged: (value) {},
onSuffixPressed: () {
@ -98,6 +92,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
_isSearch = false;
_query = "";
_selectedIndex = 0;
_page = 1;
});
_textEditingController.clear();
},
@ -115,8 +110,8 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
IconButton(
onPressed: () {
Map<String, String> data = {
'url': getMangaBaseUrl(widget.mangaType.source!),
'source': widget.mangaType.source!,
'url': widget.source.baseUrl!,
'sourceId': widget.source.id.toString(),
'title': ''
};
context.push("/mangawebview", extra: data);
@ -147,6 +142,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
onPressed: () {
setState(() {
_selectedIndex = index;
_page = 1;
});
},
);
@ -191,7 +187,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
});
}
if (widget.mangaType.isFullData!) {
if (widget.source.isFullData!) {
Future.delayed(const Duration(seconds: 1))
.then((value) {
_fullDataLength = _fullDataLength + 20;
@ -207,35 +203,75 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
_page = _page + 1;
});
}
ref
.watch(getPopularMangaProvider(
source:
widget.mangaType.source!,
page: _page,
lang: widget.mangaType.lang!)
.future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
if (_selectedIndex == 0 &&
!_isSearch &&
_query.isEmpty) {
ref
.watch(getPopularMangaProvider(
source: widget.source,
page: _page,
).future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
} else if (_selectedIndex == 1 &&
!_isSearch &&
_query.isEmpty) {
ref
.watch(
getLatestUpdatesMangaProvider(
source: widget.source,
page: _page,
).future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
} else if (_selectedIndex == 2 &&
_isSearch &&
_query.isNotEmpty) {
ref
.watch(searchMangaProvider(
source: widget.source,
query: _query,
page: _page,
).future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
}
}
}
}
_length = widget.mangaType.isFullData!
_length = widget.source.isFullData!
? _fullDataLength
: data.length;
},
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Click to load more"),
Text("Load more"),
SizedBox(
height: 10,
),
@ -260,7 +296,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
});
}
if (widget.mangaType.isFullData!) {
if (widget.source.isFullData!) {
Future.delayed(const Duration(seconds: 1)).then((value) {
_fullDataLength = _fullDataLength + 10;
if (mounted) {
@ -275,30 +311,67 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
_page = _page + 1;
});
}
ref
.watch(getPopularMangaProvider(
source: widget.mangaType.source!,
page: _page,
lang: widget.mangaType.lang!)
.future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
if (_selectedIndex == 0 && !_isSearch && _query.isEmpty) {
ref
.watch(getPopularMangaProvider(
source: widget.source,
page: _page,
).future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
} else if (_selectedIndex == 1 &&
!_isSearch &&
_query.isEmpty) {
ref
.watch(getLatestUpdatesMangaProvider(
source: widget.source,
page: _page,
).future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
} else if (_selectedIndex == 2 &&
_isSearch &&
_query.isNotEmpty) {
ref
.watch(searchMangaProvider(
source: widget.source,
query: _query,
page: _page,
).future)
.then(
(value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
},
);
}
}
}
}
});
}
_length =
widget.mangaType.isFullData! ? _fullDataLength : data.length;
_length = widget.source.isFullData! ? _fullDataLength : data.length;
return Padding(
padding: const EdgeInsets.only(top: 10),
child: Column(
@ -313,8 +386,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
}
return MangaHomeImageCard(
manga: data[index]!,
source: widget.mangaType.source!,
lang: widget.mangaType.lang!,
source: widget.source,
);
},
)),
@ -331,14 +403,12 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
}
class MangaHomeImageCard extends ConsumerStatefulWidget {
final GetManga manga;
final String source;
final String lang;
final MangaModel manga;
final Source source;
const MangaHomeImageCard({
super.key,
required this.manga,
required this.source,
required this.lang,
});
@override
@ -350,28 +420,12 @@ class _MangaHomeImageCardState extends ConsumerState<MangaHomeImageCard>
@override
Widget build(BuildContext context) {
super.build(context);
final getMangaDetail = ref.watch(getMangaDetailProvider(
source: widget.source, manga: widget.manga, lang: widget.lang));
return getMangaDetail.when(
data: (data) {
return MangaImageCardWidget(
getMangaDetail: data,
lang: widget.lang,
);
},
loading: () => CoverViewWidget(onTap: () {}, children: [
ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Container(
color: Theme.of(context).cardColor,
),
),
BottomTextWidget(
text: widget.manga.name!,
)
]),
error: (error, stackTrace) => Center(child: Text(error.toString())),
return MangaImageCardWidget(
getMangaDetail: widget.manga
..lang = widget.source.lang
..source = widget.source.name,
lang: widget.source.lang!,
);
}

View file

@ -1,108 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/services/search_manga.dart';
import 'package:mangayomi/modules/manga/home/manga_home_screen.dart';
import 'package:mangayomi/modules/widgets/gridview_widget.dart';
class MangaSearchButton extends StatelessWidget {
final String source;
final String lang;
MangaSearchButton({super.key, required this.source, required this.lang});
late final CustomSearchDelegate _delegate =
CustomSearchDelegate(source, lang);
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.search),
onPressed: () async {
await showSearch<dynamic>(
context: context,
delegate: _delegate,
);
},
);
}
}
class CustomSearchDelegate extends SearchDelegate {
final String source;
final String lang;
CustomSearchDelegate(this.source, this.lang);
@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: const BackButtonIcon(),
onPressed: () {
Navigator.pop(context);
},
);
}
@override
Widget buildSuggestions(BuildContext context) {
return Container();
}
@override
Widget buildResults(BuildContext context) {
if (query.isEmpty) {
return const Center(child: Text("Empty"));
}
return SearchResultScreen(
query: query,
source: source,
lang: lang,
);
}
@override
List<Widget>? buildActions(BuildContext context) {
if (query.isNotEmpty) {
return [
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
return [];
}
@override
ThemeData appBarTheme(BuildContext context) {
final ThemeData theme = Theme.of(context);
return theme.copyWith(
appBarTheme: const AppBarTheme(),
inputDecorationTheme: const InputDecorationTheme(hintStyle: TextStyle()),
textTheme: theme.textTheme
.copyWith(titleLarge: theme.textTheme.titleLarge!.copyWith()),
);
}
}
class SearchResultScreen extends ConsumerWidget {
final String query;
final String source;
final String lang;
final Source source;
final bool viewOnly;
const SearchResultScreen({
super.key,
required this.query,
required this.source,
required this.lang,
this.viewOnly = false,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final search = ref
.watch(searchMangaProvider(source: source, query: query, lang: lang));
final search =
ref.watch(searchMangaProvider(source: source, query: query, page: 1));
return Scaffold(
appBar: viewOnly
? AppBar(
@ -122,7 +39,6 @@ class SearchResultScreen extends ConsumerWidget {
return MangaHomeImageCard(
manga: data[index]!,
source: source,
lang: lang,
);
},
);

View file

@ -23,19 +23,19 @@ class ChapterIntervalPageView extends ConsumerWidget {
final readerController =
ReaderController(chapter: uChapDataPreload.chapter!);
String text = uChapDataPreload.isPrevPrePage && hasPrevChapter
String text = uChapDataPreload.hasPrevPrePage && hasPrevChapter
? "Current:"
: "Finished:";
final noMoreChapter = uChapDataPreload.isNextPrePage && !hasNextChapter ||
uChapDataPreload.isPrevPrePage && !hasPrevChapter;
final noMoreChapter = uChapDataPreload.hasNextPrePage && !hasNextChapter ||
uChapDataPreload.hasPrevPrePage && !hasPrevChapter;
String noMore =
uChapDataPreload.isNextPrePage && !hasNextChapter ? "Next" : "Previous";
uChapDataPreload.hasNextPrePage && !hasNextChapter ? "Next" : "Previous";
return SizedBox(
height: mediaHeight(context, 0.27),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (uChapDataPreload.isPrevPrePage && hasPrevChapter)
if (uChapDataPreload.hasPrevPrePage && hasPrevChapter)
Column(
children: [
const Text(
@ -89,7 +89,7 @@ class ChapterIntervalPageView extends ConsumerWidget {
style: const TextStyle(color: Colors.white, fontSize: 14),
textAlign: TextAlign.center,
),
if (uChapDataPreload.isNextPrePage && hasNextChapter)
if (uChapDataPreload.hasNextPrePage && hasNextChapter)
Column(
children: [
Column(

View file

@ -7,6 +7,7 @@ import 'package:mangayomi/utils/headers.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
class ImageViewCenter extends ConsumerWidget {
final String lang;
final int length;
final String url;
final int index;
@ -33,6 +34,7 @@ class ImageViewCenter extends ConsumerWidget {
required this.onDoubleTap,
required this.initGestureConfigHandler,
required this.isLocale,
required this.lang,
this.archiveImage,
});
@ -59,8 +61,8 @@ class ImageViewCenter extends ConsumerWidget {
loadStateChanged: loadStateChanged,
)
: ExtendedImage.network(
url,
headers: ref.watch(headersProvider(source: source)),
url.trim().trimLeft().trimRight(),
headers: ref.watch(headersProvider(source: source, lang: lang)),
enableMemoryCache: true,
mode: ExtendedImageMode.gesture,
cacheMaxAge: const Duration(days: 7),

View file

@ -17,6 +17,7 @@ class ImageViewVertical extends ConsumerWidget {
final String source;
final String chapter;
final Directory path;
final String lang;
final Uint8List? archiveImage;
const ImageViewVertical({
@ -29,6 +30,7 @@ class ImageViewVertical extends ConsumerWidget {
required this.source,
required this.length,
required this.isLocale,
required this.lang,
this.archiveImage,
});
@ -46,8 +48,7 @@ class ImageViewVertical extends ConsumerWidget {
isLocale
? archiveImage != null
? ExtendedImage.memory(archiveImage!,
fit: BoxFit.contain,
enableMemoryCache: false,
fit: BoxFit.contain, enableMemoryCache: false,
loadStateChanged: (ExtendedImageState state) {
if (state.extendedImageLoadState == LoadState.loading) {
return Container(
@ -70,8 +71,9 @@ class ImageViewVertical extends ConsumerWidget {
}
return null;
})
: ExtendedImage.network(url,
headers: ref.watch(headersProvider(source: source)),
: ExtendedImage.network(url.trim().trimLeft().trimRight(),
headers:
ref.watch(headersProvider(source: source, lang: lang)),
handleLoadingProgress: true,
cacheMaxAge: const Duration(days: 7),
fit: BoxFit.contain,

View file

@ -273,8 +273,8 @@ class _MangaChapterPageGalleryState
}
void _onPageChanged(int index) {
if (!(_uChapDataPreload[index].isNextPrePage ||
_uChapDataPreload[index].isPrevPrePage)) {
if (!(_uChapDataPreload[index].hasNextPrePage ||
_uChapDataPreload[index].hasPrevPrePage)) {
_readerController =
ReaderController(chapter: _uChapDataPreload[index].chapter!);
_chapterUrlModel = _uChapDataPreload[_posIndex ?? 0].chapterUrlModel!;
@ -572,12 +572,13 @@ class _MangaChapterPageGalleryState
IconButton(
onPressed: () {
final manga = chapter.manga.value!;
String url = getMangaAPIUrl(manga.source!).isEmpty
? manga.link!
: "${getMangaBaseUrl(manga.source!)}${manga.link!}";
final source = getSource(manga.lang!, manga.source!);
String url = source.apiUrl!.isEmpty
? chapter.url!
: "${source.baseUrl}/${chapter.url!}";
Map<String, String> data = {
'url': url,
'source': manga.source!,
'sourceId': source.id.toString(),
'title': chapter.name!
};
context.push("/mangawebview", extra: data);
@ -1063,8 +1064,8 @@ class _MangaChapterPageGalleryState
final model = resultMap[_listViewContext];
if (model == null) return;
_posIndex = model.firstChild?.index ?? 0;
if (!(_uChapDataPreload[_posIndex ?? 0].isNextPrePage ||
_uChapDataPreload[_posIndex ?? 0].isPrevPrePage)) {
if (!(_uChapDataPreload[_posIndex ?? 0].hasNextPrePage ||
_uChapDataPreload[_posIndex ?? 0].hasPrevPrePage)) {
_readerController = ReaderController(
chapter:
_uChapDataPreload[_posIndex ?? 0].chapter!);
@ -1100,18 +1101,18 @@ class _MangaChapterPageGalleryState
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
if (_uChapDataPreload[index].isNextPrePage ||
_uChapDataPreload[index].isPrevPrePage) {
if (_uChapDataPreload[index].hasNextPrePage ||
_uChapDataPreload[index].hasPrevPrePage) {
bool hasPrevChapter =
_readerController.getChapterIndex() + 1 !=
_readerController.getChaptersLength();
bool hasNextChapter =
_readerController.getChapterIndex() != 0;
final chapter = _uChapDataPreload[index]
.isNextPrePage &&
.hasNextPrePage &&
hasNextChapter
? _readerController.getNextChapter()
: _uChapDataPreload[index].isPrevPrePage &&
: _uChapDataPreload[index].hasPrevPrePage &&
hasPrevChapter
? _readerController.getPrevChapter()
: null;
@ -1122,7 +1123,7 @@ class _MangaChapterPageGalleryState
).future)
.then((value) {
if (_uChapDataPreload[index]
.isNextPrePage) {
.hasNextPrePage) {
_preloadNextChapter(value, chapter);
} else {
_preloadPrevChapter(value, chapter);
@ -1132,18 +1133,18 @@ class _MangaChapterPageGalleryState
}
}
});
if (_uChapDataPreload[index].isNextPrePage ||
_uChapDataPreload[index].isPrevPrePage) {
if (_uChapDataPreload[index].hasNextPrePage ||
_uChapDataPreload[index].hasPrevPrePage) {
bool hasPrevChapter =
_readerController.getChapterIndex() + 1 !=
_readerController.getChaptersLength();
bool hasNextChapter =
_readerController.getChapterIndex() != 0;
final chapter =
_uChapDataPreload[index].isNextPrePage &&
_uChapDataPreload[index].hasNextPrePage &&
hasNextChapter
? _readerController.getNextChapter()
: _uChapDataPreload[index].isPrevPrePage &&
: _uChapDataPreload[index].hasPrevPrePage &&
hasPrevChapter
? _readerController.getPrevChapter()
: null;
@ -1186,6 +1187,11 @@ class _MangaChapterPageGalleryState
cropBorders == true
? true
: _uChapDataPreload[index].isLocale!,
lang: _uChapDataPreload[index]
.chapter!
.manga
.value!
.lang!,
),
);
},
@ -1212,18 +1218,18 @@ class _MangaChapterPageGalleryState
: true;
},
itemBuilder: (BuildContext context, int index) {
if (_uChapDataPreload[index].isNextPrePage ||
_uChapDataPreload[index].isPrevPrePage) {
if (_uChapDataPreload[index].hasNextPrePage ||
_uChapDataPreload[index].hasPrevPrePage) {
bool hasPrevChapter =
_readerController.getChapterIndex() + 1 !=
_readerController.getChaptersLength();
bool hasNextChapter =
_readerController.getChapterIndex() != 0;
final chapter =
_uChapDataPreload[index].isNextPrePage &&
_uChapDataPreload[index].hasNextPrePage &&
hasNextChapter
? _readerController.getNextChapter()
: _uChapDataPreload[index].isPrevPrePage &&
: _uChapDataPreload[index].hasPrevPrePage &&
hasPrevChapter
? _readerController.getPrevChapter()
: null;
@ -1393,6 +1399,11 @@ class _MangaChapterPageGalleryState
_cropImagesList.isNotEmpty && cropBorders == true
? true
: _uChapDataPreload[index].isLocale!,
lang: _uChapDataPreload[index]
.chapter!
.manga
.value!
.lang!,
);
},
itemCount: _uChapDataPreload.length,
@ -1466,8 +1477,8 @@ class UChapDataPreload {
final bool? isLocale;
final Uint8List? archiveImage;
final int? index;
final bool isNextPrePage;
final bool isPrevPrePage;
final bool hasNextPrePage;
final bool hasPrevPrePage;
final GetChapterUrlModel? chapterUrlModel;
UChapDataPreload(
this.chapter,
@ -1476,8 +1487,8 @@ class UChapDataPreload {
this.isLocale,
this.archiveImage,
this.index,
this.isNextPrePage,
this.isPrevPrePage,
this.hasNextPrePage,
this.hasPrevPrePage,
this.chapterUrlModel,
);
}

View file

@ -6,7 +6,7 @@ part of 'reader_controller_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$currentIndexHash() => r'c2b912af925d9efd3e36e7a810914ef11393c1da';
String _$currentIndexHash() => r'a5103d732efaeec89b719a14e060ac7aab16c8f4';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -2,14 +2,12 @@
import 'dart:convert';
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart';
import 'package:mangayomi/services/http_service/cloudflare/cookie.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:url_launcher/url_launcher.dart';
@ -19,12 +17,12 @@ import 'package:path_provider/path_provider.dart';
class MangaWebView extends ConsumerStatefulWidget {
final String url;
final String source;
final String sourceId;
final String title;
const MangaWebView({
super.key,
required this.url,
required this.source,
required this.sourceId,
required this.title,
});
@ -226,7 +224,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
onUpdateVisitedHistory:
(controller, url, isReload) async {
await ref.watch(
setCookieProvider(widget.source, url.toString())
setCookieProvider(widget.sourceId, url.toString())
.future);
final canGoback = await controller.canGoBack();
final canGoForward = await controller.canGoForward();
@ -239,7 +237,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
});
},
initialSettings: InAppWebViewSettings(
contentBlockers: adsContentBlockers(),
// contentBlockers: adsContentBlockers(),
userAgent: isar.settings.getSync(227)!.userAgent!),
initialUrlRequest:
URLRequest(url: WebUri.uri(Uri.parse(widget.url))),

View file

@ -1,15 +1,13 @@
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/utils/colors.dart';
import 'package:mangayomi/utils/headers.dart';
import 'package:mangayomi/modules/widgets/bottom_text_widget.dart';
@ -18,7 +16,7 @@ import 'package:mangayomi/modules/widgets/cover_view_widget.dart';
class MangaImageCardWidget extends ConsumerWidget {
final String lang;
final GetManga? getMangaDetail;
final MangaModel? getMangaDetail;
const MangaImageCardWidget({
required this.lang,
@ -46,8 +44,9 @@ class MangaImageCardWidget extends ConsumerWidget {
as ImageProvider
: CachedNetworkImageProvider(
getMangaDetail!.imageUrl!,
headers: ref.watch(
headersProvider(source: getMangaDetail!.source!)),
headers: ref.watch(headersProvider(
source: getMangaDetail!.source!,
lang: getMangaDetail!.lang!)),
),
onTap: () {
pushToMangaReaderDetail(
@ -88,7 +87,7 @@ class MangaImageCardWidget extends ConsumerWidget {
}
void pushToMangaReaderDetail(
{GetManga? getManga,
{MangaModel? getManga,
required String lang,
required BuildContext context,
int? archiveId,
@ -98,16 +97,15 @@ void pushToMangaReaderDetail(
final manga = mangaM ??
Manga(
imageUrl: getManga!.imageUrl,
name: getManga.name,
genre: getManga.genre,
author: getManga.author,
status: getManga.status!,
description: getManga.description,
link: getManga.url,
name: getManga.name!.trim().trimLeft().trimRight(),
genre: [],
author: "",
status: Status.unknown,
description: "",
link: getManga.link,
source: getManga.source,
lang: lang,
lastUpdate: DateTime.now().millisecondsSinceEpoch);
lastUpdate: 0);
final empty = isar.mangas
.filter()
.langEqualTo(lang)
@ -117,16 +115,6 @@ void pushToMangaReaderDetail(
if (empty) {
isar.writeTxnSync(() {
isar.mangas.putSync(manga);
for (var i = 0; i < getManga!.chapters.length; i++) {
final chapters = Chapter(
name: getManga.chapters[i].name,
url: getManga.chapters[i].url,
dateUpload: getManga.chapters[i].dateUpload,
scanlator: getManga.chapters[i].scanlator,
)..manga.value = manga;
isar.chapters.putSync(chapters);
chapters.manga.saveSync();
}
});
}
@ -181,3 +169,4 @@ void pushToMangaReaderDetail(
context.push('/manga-reader/detail', extra: mangaId);
}

View file

@ -1,8 +1,10 @@
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga_type.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
import 'package:mangayomi/modules/more/settings/downloads/downloads_screen.dart';
import 'package:mangayomi/modules/updates/updates_screen.dart';
import 'package:mangayomi/modules/webview/webview.dart';
@ -30,6 +32,7 @@ final routerProvider = Provider<GoRouter>((ref) {
final router = AsyncRouterNotifier();
return GoRouter(
observers: [BotToastNavigatorObserver()],
initialLocation: '/library',
debugLogDiagnostics: false,
refreshListenable: router,
@ -92,17 +95,17 @@ class AsyncRouterNotifier extends ChangeNotifier {
path: "/mangaHome",
name: "mangaHome",
builder: (context, state) {
final mangaType = state.extra as MangaType?;
final source = state.extra as Source?;
return MangaHomeScreen(
mangaType: mangaType!,
source: source!,
);
},
pageBuilder: (context, state) {
final mangaType = state.extra as MangaType?;
final source = state.extra as Source?;
return CustomTransition(
key: state.pageKey,
child: MangaHomeScreen(
mangaType: mangaType!,
source: source!,
),
);
}),
@ -202,7 +205,6 @@ class AsyncRouterNotifier extends ChangeNotifier {
final data = state.extra as Map<String, dynamic>;
return SearchResultScreen(
query: data['query']!,
lang: data['lang']!,
source: data['source']!,
viewOnly: data['viewOnly'],
);
@ -213,7 +215,6 @@ class AsyncRouterNotifier extends ChangeNotifier {
key: state.pageKey,
child: SearchResultScreen(
query: data['query']!,
lang: data['lang']!,
source: data['source']!,
viewOnly: data['viewOnly'],
),
@ -233,6 +234,19 @@ class AsyncRouterNotifier extends ChangeNotifier {
);
},
),
GoRoute(
path: "/sourceFilter",
name: "sourceFilter",
builder: (context, state) {
return const SourcesFilterScreen();
},
pageBuilder: (context, state) {
return CustomTransition(
key: state.pageKey,
child: const SourcesFilterScreen(),
);
},
),
GoRoute(
path: "/downloadQueue",
name: "downloadQueue",
@ -253,7 +267,7 @@ class AsyncRouterNotifier extends ChangeNotifier {
final data = state.extra as Map<String, String>;
return MangaWebView(
url: data["url"]!,
source: data["source"]!,
sourceId: data["sourceId"]!,
title: data['title']!);
},
pageBuilder: (context, state) {
@ -262,7 +276,7 @@ class AsyncRouterNotifier extends ChangeNotifier {
key: state.pageKey,
child: MangaWebView(
url: data["url"]!,
source: data["source"]!,
sourceId: data["sourceId"]!,
title: data['title']!,
),
);

View file

@ -1,27 +1,23 @@
// ignore_for_file: depend_o
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/eval/compiler/compiler.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/archive_reader/providers/archive_reader_providers.dart';
import 'package:mangayomi/modules/manga/reader/manga_reader_view.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/sources/multisrc/heancms/heancms.dart';
import 'package:mangayomi/sources/multisrc/madara/src/madara.dart';
import 'package:mangayomi/sources/src/all/comick/src/comick.dart';
import 'package:mangayomi/sources/src/all/mangadex/src/mangadex.dart';
import 'package:mangayomi/sources/src/en/mangahere/src/mangahere.dart';
import 'package:mangayomi/sources/src/fr/japscan/src/japscan.dart';
import 'package:mangayomi/sources/src/fr/mangakawaii/src/mangakawaii.dart';
import 'package:mangayomi/sources/multisrc/mangathemesia/src/mangathemesia.dart';
import 'package:mangayomi/sources/multisrc/mmrcms/src/mmrcms.dart';
import 'package:mangayomi/eval/runtime/runtime.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../eval/bridge_class/manga_model.dart';
part 'get_chapter_url.g.dart';
class GetChapterUrlModel {
@ -46,9 +42,7 @@ Future<GetChapterUrlModel> getChapterUrl(
List<UChapDataPreload> uChapDataPreloadp = [];
Directory? path;
List<String> pageUrls = [];
final manga = chapter.manga.value!;
List<bool> isLocaleList = [];
String source = manga.source!.toLowerCase();
final settings = isar.settings.getSync(227);
List<ChapterPageurls>? chapterPageUrlsList =
settings!.chapterPageUrlsList ?? [];
@ -58,7 +52,8 @@ Future<GetChapterUrlModel> getChapterUrl(
final storageProvider = StorageProvider();
path = await storageProvider.getMangaChapterDirectory(chapter);
final mangaDirectory = await storageProvider.getMangaMainDirectory(chapter);
final source =
getSource(chapter.manga.value!.lang!, chapter.manga.value!.source!);
List<Uint8List?> archiveImages = [];
final isLocalArchive = (chapter.archivePath ?? '').isNotEmpty;
if (!chapter.manga.value!.isLocalArchive!) {
@ -66,79 +61,39 @@ Future<GetChapterUrlModel> getChapterUrl(
isarPageUrls.first.urls != null &&
isarPageUrls.first.urls!.isNotEmpty) {
pageUrls = isarPageUrls.first.urls!;
}
} else {
final bytecode = compilerEval(source.sourceCode!);
/*********/
/*comick*/
/********/
else if (getMangaTypeSource(source) == TypeSource.comick) {
pageUrls = await Comick().getChapterUrl(chapter: chapter, ref: ref);
}
/*************/
/*mangathemesia*/
/**************/
else if (getMangaTypeSource(source) == TypeSource.mangathemesia) {
pageUrls =
await MangaThemeSia().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*mangakawaii*/
/***********/
else if (source == 'mangakawaii') {
pageUrls = await MangaKawaii().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*mmrcms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mmrcms) {
pageUrls = await Mmrcms().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*mangahere*/
/***********/
else if (source == 'mangahere') {
pageUrls = await Mangahere().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*japscan*/
/***********/
else if (source == 'japscan') {
pageUrls = await Japscan().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*heancms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.heancms) {
pageUrls = await HeanCms().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*madara*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.madara) {
pageUrls = await Madara().getChapterUrl(chapter: chapter, ref: ref);
}
/***********/
/*mangadex*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mangadex) {
pageUrls = await MangaDex().getChapterUrl(chapter: chapter, ref: ref);
final runtime = runtimeEval(bytecode);
runtime.args = [
$MangaModel.wrap(MangaModel(
lang: source.lang,
link: chapter.url,
baseUrl: source.baseUrl,
source: source.name,
apiUrl: source.apiUrl,
sourceId: source.id,
))
];
var result2 = await runtime.executeLib(
'package:package:mangayomi/main.dart', 'getChapterUrl');
if (result2 is $List) {
for (var element in result2.$reified) {
if (element is $Value) {
pageUrls.add(element.$reified);
} else {
pageUrls.add(element);
}
}
} else {
for (var element in result2) {
if (element is $Value) {
pageUrls.add(element.$reified);
} else {
pageUrls.add(element);
}
}
}
}
}

View file

@ -6,7 +6,7 @@ part of 'get_chapter_url.dart';
// RiverpodGenerator
// **************************************************************************
String _$getChapterUrlHash() => r'6e9e28c2d3775791f9c8544cf0f41bb07b5eaf81';
String _$getChapterUrlHash() => r'46fc82b7ddeb8f6f1658dbc942db69f651505aad';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,99 +1,59 @@
import 'dart:async';
import 'package:mangayomi/eval/compiler/compiler.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/sources/multisrc/heancms/heancms.dart';
import 'package:mangayomi/sources/multisrc/madara/src/madara.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/src/all/comick/src/comick.dart';
import 'package:mangayomi/sources/src/all/mangadex/src/mangadex.dart';
import 'package:mangayomi/sources/src/en/mangahere/src/mangahere.dart';
import 'package:mangayomi/sources/src/fr/japscan/src/japscan.dart';
import 'package:mangayomi/sources/src/fr/mangakawaii/src/mangakawaii.dart';
import 'package:mangayomi/sources/multisrc/mangathemesia/src/mangathemesia.dart';
import 'package:mangayomi/sources/multisrc/mmrcms/src/mmrcms.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/eval/runtime/runtime.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'get_latest_updates_manga.g.dart';
@riverpod
Future<List<GetManga?>> getLatestUpdatesManga(
Future<List<MangaModel?>> getLatestUpdatesManga(
GetLatestUpdatesMangaRef ref, {
required String source,
required Source source,
required int page,
required String lang,
}) async {
List<GetManga?>? latestUpdatesManga;
source = source.toLowerCase();
List<MangaModel?>? latestUpdatesManga = [];
final bytecode = compilerEval(source.sourceCode!);
/*********/
/*comick*/
/*******/
if (getMangaTypeSource(source) == TypeSource.comick) {
latestUpdatesManga = await Comick().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
final runtime = runtimeEval(bytecode);
runtime.args = [
$MangaModel.wrap(MangaModel(
page: page,
lang: source.lang,
baseUrl: source.baseUrl,
apiUrl: source.apiUrl,
sourceId: source.id,
source: source.name,
dateFormat: source.dateFormat,
dateFormatLocale: source.dateFormatLocale))
];
var result2 = await runtime.executeLib(
'package:package:mangayomi/main.dart',
'getLatestUpdatesManga',
);
try {
if (result2 is $MangaModel) {
final value = result2.$reified;
List<MangaModel> newManga = [];
for (var i = 0; i < value.names!.length; i++) {
MangaModel newMangaa = MangaModel(
name: value.names![i],
link: value.urls![i],
imageUrl: value.images![i],
baseUrl: value.baseUrl,
apiUrl: value.apiUrl,
lang: value.lang,
sourceId: value.sourceId,
dateFormat: value.dateFormat,
dateFormatLocale: value.dateFormatLocale);
newManga.add(newMangaa);
}
latestUpdatesManga = newManga;
}
} catch (_) {
throw Exception("");
}
/***************/
/*mangathemesia*/
/**************/
if (getMangaTypeSource(source) == TypeSource.mangathemesia) {
latestUpdatesManga = await MangaThemeSia().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mangakawaii*/
/***********/
if (source == "mangakawaii") {
latestUpdatesManga = await MangaKawaii().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mmrcms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mmrcms) {
latestUpdatesManga = await Mmrcms().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mangahere*/
/***********/
else if (source == "mangahere") {
latestUpdatesManga = await Mangahere().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*japscan*/
/***********/
else if (source == "japscan") {
latestUpdatesManga = await Japscan().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*heancms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.heancms) {
latestUpdatesManga = await HeanCms().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*madara*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.madara) {
latestUpdatesManga = await Madara().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mangadex*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mangadex) {
latestUpdatesManga = await MangaDex().getLatestUpdatesManga(
source: source, page: page, ref: ref, lang: lang);
}
return latestUpdatesManga!;
return latestUpdatesManga;
}

View file

@ -7,7 +7,7 @@ part of 'get_latest_updates_manga.dart';
// **************************************************************************
String _$getLatestUpdatesMangaHash() =>
r'c5ddc8739ee5b7c085cdeede7c55743bfc7f5120';
r'6ad1f68ce7a320c661817e5d5ff1bbf8d68b3948';
/// Copied from Dart SDK
class _SystemHash {
@ -31,27 +31,26 @@ class _SystemHash {
}
typedef GetLatestUpdatesMangaRef
= AutoDisposeFutureProviderRef<List<GetManga?>>;
= AutoDisposeFutureProviderRef<List<MangaModel?>>;
/// See also [getLatestUpdatesManga].
@ProviderFor(getLatestUpdatesManga)
const getLatestUpdatesMangaProvider = GetLatestUpdatesMangaFamily();
/// See also [getLatestUpdatesManga].
class GetLatestUpdatesMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
class GetLatestUpdatesMangaFamily
extends Family<AsyncValue<List<MangaModel?>>> {
/// See also [getLatestUpdatesManga].
const GetLatestUpdatesMangaFamily();
/// See also [getLatestUpdatesManga].
GetLatestUpdatesMangaProvider call({
required String source,
required Source source,
required int page,
required String lang,
}) {
return GetLatestUpdatesMangaProvider(
source: source,
page: page,
lang: lang,
);
}
@ -62,7 +61,6 @@ class GetLatestUpdatesMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
return call(
source: provider.source,
page: provider.page,
lang: provider.lang,
);
}
@ -83,18 +81,16 @@ class GetLatestUpdatesMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
/// See also [getLatestUpdatesManga].
class GetLatestUpdatesMangaProvider
extends AutoDisposeFutureProvider<List<GetManga?>> {
extends AutoDisposeFutureProvider<List<MangaModel?>> {
/// See also [getLatestUpdatesManga].
GetLatestUpdatesMangaProvider({
required this.source,
required this.page,
required this.lang,
}) : super.internal(
(ref) => getLatestUpdatesManga(
ref,
source: source,
page: page,
lang: lang,
),
from: getLatestUpdatesMangaProvider,
name: r'getLatestUpdatesMangaProvider',
@ -107,16 +103,14 @@ class GetLatestUpdatesMangaProvider
GetLatestUpdatesMangaFamily._allTransitiveDependencies,
);
final String source;
final Source source;
final int page;
final String lang;
@override
bool operator ==(Object other) {
return other is GetLatestUpdatesMangaProvider &&
other.source == source &&
other.page == page &&
other.lang == lang;
other.page == page;
}
@override
@ -124,7 +118,6 @@ class GetLatestUpdatesMangaProvider
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, page.hashCode);
hash = _SystemHash.combine(hash, lang.hashCode);
return _SystemHash.finish(hash);
}

View file

@ -1,101 +1,37 @@
import 'dart:async';
import 'package:mangayomi/eval/compiler/compiler.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/sources/multisrc/heancms/heancms.dart';
import 'package:mangayomi/sources/multisrc/madara/src/madara.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/src/all/comick/src/comick.dart';
import 'package:mangayomi/sources/src/all/mangadex/src/mangadex.dart';
import 'package:mangayomi/sources/src/en/mangahere/src/mangahere.dart';
import 'package:mangayomi/sources/src/fr/japscan/src/japscan.dart';
import 'package:mangayomi/sources/src/fr/mangakawaii/src/mangakawaii.dart';
import 'package:mangayomi/sources/multisrc/mangathemesia/src/mangathemesia.dart';
import 'package:mangayomi/sources/multisrc/mmrcms/src/mmrcms.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/eval/runtime/runtime.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'get_manga_detail.g.dart';
@riverpod
Future<GetManga> getMangaDetail(GetMangaDetailRef ref,
{required GetManga manga,
required String lang,
required String source}) async {
GetManga? mangadetail;
Future<MangaModel> getMangaDetail(
GetMangaDetailRef ref, {
required MangaModel manga,
required Source source,
}) async {
MangaModel? mangadetail;
final bytecode = compilerEval(source.sourceCode!);
/********/
/*comick*/
/********/
final runtime = runtimeEval(bytecode);
runtime.args = [
$MangaModel.wrap(manga
..source = source.name
..lang = source.lang)
];
if (getMangaTypeSource(source.toLowerCase()) == TypeSource.comick) {
mangadetail = await Comick()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/*************/
/*mangathemesia*/
/**************/
if (getMangaTypeSource(source.toLowerCase()) == TypeSource.mangathemesia) {
mangadetail = await MangaThemeSia()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/***********/
/*mangakawaii*/
/***********/
else if (source.toLowerCase() == "mangakawaii") {
mangadetail = await MangaKawaii()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/***********/
/*mmrcms*/
/***********/
else if (getMangaTypeSource(source.toLowerCase()) == TypeSource.mmrcms) {
mangadetail = await Mmrcms()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/***********/
/*mangahere*/
/***********/
else if (source.toLowerCase() == "mangahere") {
mangadetail = await Mangahere()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/***********/
/*japscan*/
/***********/
else if (source.toLowerCase() == "japscan") {
mangadetail = await Japscan()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/***********/
/*heancms*/
/***********/
else if (getMangaTypeSource(source.toLowerCase()) == TypeSource.heancms) {
mangadetail = await HeanCms()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/***********/
/*madara*/
/***********/
else if (getMangaTypeSource(source.toLowerCase()) == TypeSource.madara) {
mangadetail = await Madara()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
}
/***********/
/*mangadex*/
/***********/
else if (getMangaTypeSource(source.toLowerCase()) == TypeSource.mangadex) {
mangadetail = await MangaDex()
.getMangaDetail(manga: manga, lang: lang, source: source, ref: ref);
try {
var result = await runtime.executeLib(
'package:package:mangayomi/main.dart', 'getMangaDetail');
if (result is $MangaModel) {
final value = result.$reified;
mangadetail = value;
}
} catch (_) {
return manga;
}
return mangadetail!;
}

View file

@ -6,7 +6,7 @@ part of 'get_manga_detail.dart';
// RiverpodGenerator
// **************************************************************************
String _$getMangaDetailHash() => r'009422e0d88c595e2a8183e2360708ad2e13a955';
String _$getMangaDetailHash() => r'e5c9845c4481eb584cbd899dacaaceda8167ae44';
/// Copied from Dart SDK
class _SystemHash {
@ -29,26 +29,24 @@ class _SystemHash {
}
}
typedef GetMangaDetailRef = AutoDisposeFutureProviderRef<GetManga>;
typedef GetMangaDetailRef = AutoDisposeFutureProviderRef<MangaModel>;
/// See also [getMangaDetail].
@ProviderFor(getMangaDetail)
const getMangaDetailProvider = GetMangaDetailFamily();
/// See also [getMangaDetail].
class GetMangaDetailFamily extends Family<AsyncValue<GetManga>> {
class GetMangaDetailFamily extends Family<AsyncValue<MangaModel>> {
/// See also [getMangaDetail].
const GetMangaDetailFamily();
/// See also [getMangaDetail].
GetMangaDetailProvider call({
required GetManga manga,
required String lang,
required String source,
required MangaModel manga,
required Source source,
}) {
return GetMangaDetailProvider(
manga: manga,
lang: lang,
source: source,
);
}
@ -59,7 +57,6 @@ class GetMangaDetailFamily extends Family<AsyncValue<GetManga>> {
) {
return call(
manga: provider.manga,
lang: provider.lang,
source: provider.source,
);
}
@ -80,17 +77,15 @@ class GetMangaDetailFamily extends Family<AsyncValue<GetManga>> {
}
/// See also [getMangaDetail].
class GetMangaDetailProvider extends AutoDisposeFutureProvider<GetManga> {
class GetMangaDetailProvider extends AutoDisposeFutureProvider<MangaModel> {
/// See also [getMangaDetail].
GetMangaDetailProvider({
required this.manga,
required this.lang,
required this.source,
}) : super.internal(
(ref) => getMangaDetail(
ref,
manga: manga,
lang: lang,
source: source,
),
from: getMangaDetailProvider,
@ -104,15 +99,13 @@ class GetMangaDetailProvider extends AutoDisposeFutureProvider<GetManga> {
GetMangaDetailFamily._allTransitiveDependencies,
);
final GetManga manga;
final String lang;
final String source;
final MangaModel manga;
final Source source;
@override
bool operator ==(Object other) {
return other is GetMangaDetailProvider &&
other.manga == manga &&
other.lang == lang &&
other.source == source;
}
@ -120,7 +113,6 @@ class GetMangaDetailProvider extends AutoDisposeFutureProvider<GetManga> {
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, manga.hashCode);
hash = _SystemHash.combine(hash, lang.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
return _SystemHash.finish(hash);

View file

@ -1,94 +1,59 @@
import 'dart:async';
import 'package:mangayomi/eval/compiler/compiler.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/sources/multisrc/heancms/heancms.dart';
import 'package:mangayomi/sources/multisrc/madara/src/madara.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/src/all/comick/src/comick.dart';
import 'package:mangayomi/sources/src/all/mangadex/src/mangadex.dart';
import 'package:mangayomi/sources/src/en/mangahere/src/mangahere.dart';
import 'package:mangayomi/sources/src/fr/japscan/src/japscan.dart';
import 'package:mangayomi/sources/src/fr/mangakawaii/src/mangakawaii.dart';
import 'package:mangayomi/sources/multisrc/mangathemesia/src/mangathemesia.dart';
import 'package:mangayomi/sources/multisrc/mmrcms/src/mmrcms.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/eval/runtime/runtime.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'get_popular_manga.g.dart';
@riverpod
Future<List<GetManga?>> getPopularManga(GetPopularMangaRef ref,
{required String source, required int page, required String lang}) async {
List<GetManga?>? popularManga;
source = source.toLowerCase();
Future<List<MangaModel?>> getPopularManga(
GetPopularMangaRef ref, {
required Source source,
required int page,
}) async {
List<MangaModel> popularManga = [];
final bytecode = compilerEval(source.sourceCode!);
/*********/
/*comick*/
/*******/
if (getMangaTypeSource(source) == TypeSource.comick) {
popularManga = await Comick()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
final runtime = runtimeEval(bytecode);
runtime.args = [
$MangaModel.wrap(MangaModel(
page: page,
lang: source.lang,
baseUrl: source.baseUrl,
apiUrl: source.apiUrl,
sourceId: source.id,
source: source.name,
dateFormat: source.dateFormat,
dateFormatLocale: source.dateFormatLocale))
];
var result2 = await runtime.executeLib(
'package:package:mangayomi/main.dart',
'getPopularManga',
);
try {
if (result2 is $MangaModel) {
final value = result2.$reified;
List<MangaModel> newManga = [];
for (var i = 0; i < value.names!.length; i++) {
MangaModel newMangaa = MangaModel(
name: value.names![i],
link: value.urls![i],
imageUrl: value.images![i],
baseUrl: value.baseUrl,
apiUrl: value.apiUrl,
lang: value.lang,
status: value.status,
dateFormat: value.dateFormat,
dateFormatLocale: value.dateFormatLocale);
newManga.add(newMangaa);
}
popularManga = newManga;
}
} catch (_) {
throw Exception("");
}
/***************/
/*mangathemesia*/
/**************/
if (getMangaTypeSource(source) == TypeSource.mangathemesia) {
popularManga = await MangaThemeSia()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mangakawaii*/
/***********/
if (source == "mangakawaii") {
popularManga = await MangaKawaii()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mmrcms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mmrcms) {
popularManga = await Mmrcms()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mangahere*/
/***********/
else if (source == "mangahere") {
popularManga = await Mangahere()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*japscan*/
/***********/
else if (source == "japscan") {
popularManga = await Japscan()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*heancms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.heancms) {
popularManga = await HeanCms()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*madara*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.madara) {
popularManga = await Madara()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
/***********/
/*mangadex*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mangadex) {
popularManga = await MangaDex()
.getPopularManga(source: source, page: page, ref: ref, lang: lang);
}
return popularManga!;
return popularManga;
}

View file

@ -6,7 +6,7 @@ part of 'get_popular_manga.dart';
// RiverpodGenerator
// **************************************************************************
String _$getPopularMangaHash() => r'4fe87d57b93957bbc848b9b814dd3c9aa94b89c5';
String _$getPopularMangaHash() => r'f0999a803936680db27e22750a620566c25ae69e';
/// Copied from Dart SDK
class _SystemHash {
@ -29,27 +29,25 @@ class _SystemHash {
}
}
typedef GetPopularMangaRef = AutoDisposeFutureProviderRef<List<GetManga?>>;
typedef GetPopularMangaRef = AutoDisposeFutureProviderRef<List<MangaModel?>>;
/// See also [getPopularManga].
@ProviderFor(getPopularManga)
const getPopularMangaProvider = GetPopularMangaFamily();
/// See also [getPopularManga].
class GetPopularMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
class GetPopularMangaFamily extends Family<AsyncValue<List<MangaModel?>>> {
/// See also [getPopularManga].
const GetPopularMangaFamily();
/// See also [getPopularManga].
GetPopularMangaProvider call({
required String source,
required Source source,
required int page,
required String lang,
}) {
return GetPopularMangaProvider(
source: source,
page: page,
lang: lang,
);
}
@ -60,7 +58,6 @@ class GetPopularMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
return call(
source: provider.source,
page: provider.page,
lang: provider.lang,
);
}
@ -81,18 +78,16 @@ class GetPopularMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
/// See also [getPopularManga].
class GetPopularMangaProvider
extends AutoDisposeFutureProvider<List<GetManga?>> {
extends AutoDisposeFutureProvider<List<MangaModel?>> {
/// See also [getPopularManga].
GetPopularMangaProvider({
required this.source,
required this.page,
required this.lang,
}) : super.internal(
(ref) => getPopularManga(
ref,
source: source,
page: page,
lang: lang,
),
from: getPopularMangaProvider,
name: r'getPopularMangaProvider',
@ -105,16 +100,14 @@ class GetPopularMangaProvider
GetPopularMangaFamily._allTransitiveDependencies,
);
final String source;
final Source source;
final int page;
final String lang;
@override
bool operator ==(Object other) {
return other is GetPopularMangaProvider &&
other.source == source &&
other.page == page &&
other.lang == lang;
other.page == page;
}
@override
@ -122,7 +115,6 @@ class GetPopularMangaProvider
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, page.hashCode);
hash = _SystemHash.combine(hash, lang.hashCode);
return _SystemHash.finish(hash);
}

View file

@ -1,105 +1,19 @@
import 'dart:convert';
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/dom.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/webview/webview.dart';
import 'package:mangayomi/services/http_service/cloudflare/cookie.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'cloudflare_bypass.g.dart';
import 'package:mangayomi/utils/constant.dart';
@riverpod
Future<dom.Document?> cloudflareBypassDom(CloudflareBypassDomRef ref,
Future<String> cloudflareBypass(
{required String url,
required String source,
required bool useUserAgent}) async {
final ua = isar.settings.getSync(227)!.userAgent!;
bool isOk = false;
dom.Document? htmll;
if (Platform.isWindows || Platform.isLinux) {
String? html;
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
windowHeight: 500,
windowWidth: 500,
userDataFolderWindows: await getWebViewPath(),
),
);
webview
..setBrightness(Brightness.dark)
..setApplicationNameForUserAgent(useUserAgent ? ua : '')
..launch(url);
await Future.delayed(const Duration(seconds: 10));
html = await decodeHtml(webview);
isOk = true;
await Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (isOk == true) {
return false;
}
return true;
});
html = await decodeHtml(webview);
htmll = dom.Document.html(html!);
webview.close();
} else {
HeadlessInAppWebView? headlessWebView;
headlessWebView = HeadlessInAppWebView(
onLoadStop: (controller, u) async {
String? html;
html = await controller.evaluateJavascript(
source:
"window.document.getElementsByTagName('html')[0].outerHTML;");
await Future.doWhile(() async {
if (html == null ||
html!.contains("Just a moment") ||
html!.contains("Un instant…") ||
html!.contains("https://challenges.cloudflare.com")) {
html = await controller.evaluateJavascript(
source:
"window.document.getElementsByTagName('html')[0].outerHTML;");
return true;
}
return false;
});
html = await controller.evaluateJavascript(
source:
"window.document.getElementsByTagName('html')[0].outerHTML;");
htmll = dom.Document.html(html!);
isOk = true;
headlessWebView!.dispose();
},
initialSettings: InAppWebViewSettings(
userAgent: useUserAgent ? ua : "",
),
initialUrlRequest: URLRequest(
url: WebUri.uri(Uri.parse(url)),
),
);
headlessWebView.run();
await Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (isOk == true) {
return false;
}
return true;
});
await ref.watch(setCookieProvider(source, url).future);
}
return htmll;
}
@riverpod
Future<String> cloudflareBypassHtml(CloudflareBypassHtmlRef ref,
{required String url,
required String source,
required bool useUserAgent}) async {
required String sourceId,
required int method}) async {
final ua = isar.settings.getSync(227)!.userAgent!;
bool isOk = false;
String? html;
@ -113,7 +27,7 @@ Future<String> cloudflareBypassHtml(CloudflareBypassHtmlRef ref,
);
webview
..setBrightness(Brightness.dark)
..setApplicationNameForUserAgent(useUserAgent ? ua : '')
..setApplicationNameForUserAgent(ua)
..launch(url);
await Future.doWhile(() async {
@ -137,8 +51,7 @@ Future<String> cloudflareBypassHtml(CloudflareBypassHtmlRef ref,
await Future.doWhile(() async {
if (html == null ||
html!.contains("Just a moment") ||
html!.contains("Un instant…") ||
html!.contains("https://challenges.cloudflare.com")) {
html!.contains("challenges.cloudflare.com")) {
html = await controller.evaluateJavascript(
source:
"window.document.getElementsByTagName('html')[0].outerHTML;");
@ -146,7 +59,6 @@ Future<String> cloudflareBypassHtml(CloudflareBypassHtmlRef ref,
}
return false;
});
await Future.delayed(const Duration(seconds: 10));
html = await controller.evaluateJavascript(
source:
"window.document.getElementsByTagName('html')[0].outerHTML;");
@ -154,9 +66,17 @@ Future<String> cloudflareBypassHtml(CloudflareBypassHtmlRef ref,
headlessWebView!.dispose();
},
initialSettings: InAppWebViewSettings(
userAgent: useUserAgent ? ua : "",
userAgent: defaultUserAgent,
),
initialUrlRequest: URLRequest(
headers: headers(sourceId: sourceId),
method: method == 0
? 'GET'
: method == 1
? 'POST'
: method == 2
? 'PUT'
: 'DELETE',
url: WebUri.uri(Uri.parse(url)),
),
);
@ -169,46 +89,21 @@ Future<String> cloudflareBypassHtml(CloudflareBypassHtmlRef ref,
}
return true;
});
await ref.watch(setCookieProvider(source, url).future);
await setCookieB(sourceId, url);
}
return html!;
}
List<ContentBlocker> adsContentBlockers() {
final List<ContentBlocker> contentBlockers = [];
// list of Ad URL filters to be used to block ads loading.
final adUrlFilters = [
".*.doubleclick.net/.*",
".*.ads.pubmatic.com/.*",
".*.googlesyndication.com/.*",
".*.google-analytics.com/.*",
".*.adservice.google.*/.*",
".*.adbrite.com/.*",
".*.exponential.com/.*",
".*.quantserve.com/.*",
".*.scorecardresearch.com/.*",
".*.zedo.com/.*",
".*.adsafeprotected.com/.*",
".*.teads.tv/.*",
".*.outbrain.com/.*"
];
for (final adUrlFilter in adUrlFilters) {
contentBlockers.add(ContentBlocker(
trigger: ContentBlockerTrigger(
urlFilter: adUrlFilter,
),
action: ContentBlockerAction(
type: ContentBlockerActionType.BLOCK,
)));
Map<String, String> headers({required String sourceId}) {
final source = isar.sources.getSync(int.parse(sourceId))!;
if (source.headers!.isEmpty) {
return {};
}
// apply the "display: none" style to some HTML elements
contentBlockers.add(ContentBlocker(
trigger: ContentBlockerTrigger(
urlFilter: ".*",
),
action: ContentBlockerAction(
type: ContentBlockerActionType.CSS_DISPLAY_NONE,
selector: ".banner, .banners, .ads, .ad, .advert")));
return contentBlockers;
Map<String, String> newHeaders = {};
final headers = jsonDecode(source.headers!) as Map;
newHeaders =
headers.map((key, value) => MapEntry(key.toString(), value.toString()));
return newHeaders;
}

View file

@ -1,230 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'cloudflare_bypass.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$cloudflareBypassDomHash() =>
r'9329b2e079dda8e1ccdbccd41fe7370df1509953';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef CloudflareBypassDomRef = AutoDisposeFutureProviderRef<Document?>;
/// See also [cloudflareBypassDom].
@ProviderFor(cloudflareBypassDom)
const cloudflareBypassDomProvider = CloudflareBypassDomFamily();
/// See also [cloudflareBypassDom].
class CloudflareBypassDomFamily extends Family<AsyncValue<Document?>> {
/// See also [cloudflareBypassDom].
const CloudflareBypassDomFamily();
/// See also [cloudflareBypassDom].
CloudflareBypassDomProvider call({
required String url,
required String source,
required bool useUserAgent,
}) {
return CloudflareBypassDomProvider(
url: url,
source: source,
useUserAgent: useUserAgent,
);
}
@override
CloudflareBypassDomProvider getProviderOverride(
covariant CloudflareBypassDomProvider provider,
) {
return call(
url: provider.url,
source: provider.source,
useUserAgent: provider.useUserAgent,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'cloudflareBypassDomProvider';
}
/// See also [cloudflareBypassDom].
class CloudflareBypassDomProvider extends AutoDisposeFutureProvider<Document?> {
/// See also [cloudflareBypassDom].
CloudflareBypassDomProvider({
required this.url,
required this.source,
required this.useUserAgent,
}) : super.internal(
(ref) => cloudflareBypassDom(
ref,
url: url,
source: source,
useUserAgent: useUserAgent,
),
from: cloudflareBypassDomProvider,
name: r'cloudflareBypassDomProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$cloudflareBypassDomHash,
dependencies: CloudflareBypassDomFamily._dependencies,
allTransitiveDependencies:
CloudflareBypassDomFamily._allTransitiveDependencies,
);
final String url;
final String source;
final bool useUserAgent;
@override
bool operator ==(Object other) {
return other is CloudflareBypassDomProvider &&
other.url == url &&
other.source == source &&
other.useUserAgent == useUserAgent;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, url.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, useUserAgent.hashCode);
return _SystemHash.finish(hash);
}
}
String _$cloudflareBypassHtmlHash() =>
r'fdc35dcbef7c4cb9fcca41d70208c4e82f9eb146';
typedef CloudflareBypassHtmlRef = AutoDisposeFutureProviderRef<String>;
/// See also [cloudflareBypassHtml].
@ProviderFor(cloudflareBypassHtml)
const cloudflareBypassHtmlProvider = CloudflareBypassHtmlFamily();
/// See also [cloudflareBypassHtml].
class CloudflareBypassHtmlFamily extends Family<AsyncValue<String>> {
/// See also [cloudflareBypassHtml].
const CloudflareBypassHtmlFamily();
/// See also [cloudflareBypassHtml].
CloudflareBypassHtmlProvider call({
required String url,
required String source,
required bool useUserAgent,
}) {
return CloudflareBypassHtmlProvider(
url: url,
source: source,
useUserAgent: useUserAgent,
);
}
@override
CloudflareBypassHtmlProvider getProviderOverride(
covariant CloudflareBypassHtmlProvider provider,
) {
return call(
url: provider.url,
source: provider.source,
useUserAgent: provider.useUserAgent,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'cloudflareBypassHtmlProvider';
}
/// See also [cloudflareBypassHtml].
class CloudflareBypassHtmlProvider extends AutoDisposeFutureProvider<String> {
/// See also [cloudflareBypassHtml].
CloudflareBypassHtmlProvider({
required this.url,
required this.source,
required this.useUserAgent,
}) : super.internal(
(ref) => cloudflareBypassHtml(
ref,
url: url,
source: source,
useUserAgent: useUserAgent,
),
from: cloudflareBypassHtmlProvider,
name: r'cloudflareBypassHtmlProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$cloudflareBypassHtmlHash,
dependencies: CloudflareBypassHtmlFamily._dependencies,
allTransitiveDependencies:
CloudflareBypassHtmlFamily._allTransitiveDependencies,
);
final String url;
final String source;
final bool useUserAgent;
@override
bool operator ==(Object other) {
return other is CloudflareBypassHtmlProvider &&
other.url == url &&
other.source == source &&
other.useUserAgent == useUserAgent;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, url.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, useUserAgent.hashCode);
return _SystemHash.finish(hash);
}
}
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View file

@ -4,20 +4,28 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'cookie.g.dart';
@riverpod
Future setCookie(SetCookieRef ref, String source, String url) async {
source = source.toLowerCase();
final cookieS = ref.watch(cookieStateProvider(source));
List<Cookie> cookies = [];
Future setCookie(SetCookieRef ref, String sourceId, String url) async {
CookieManager cookie = CookieManager.instance();
cookies = await cookie.getCookies(url: WebUri.uri(Uri.parse(url.toString())));
final cfClearance =
final cookies =
await cookie.getCookies(url: WebUri.uri(Uri.parse(url.toString())));
final newCookie =
cookies.where((element) => element.name == "cf_clearance").toList();
String newCookie = "";
if (cfClearance.isNotEmpty && cfClearance.first.name != cookieS) {
for (var i = 0; i < cookies.length; i++) {
newCookie = "$newCookie ${cookies[i].name}=${cookies[i].value};";
}
ref.read(cookieStateProvider(source).notifier).setCookie(newCookie);
if (newCookie.isNotEmpty) {
ref
.read(cookieStateProvider(sourceId).notifier)
.setCookie("cf_clearance=${newCookie.first.value};");
}
}
Future setCookieB(String sourceId, String url) async {
CookieManager cookie = CookieManager.instance();
final cookies =
await cookie.getCookies(url: WebUri.uri(Uri.parse(url.toString())));
final newCookie =
cookies.where((element) => element.name == "cf_clearance").toList();
if (newCookie.isNotEmpty) {
setCookieB("cf_clearance=${newCookie.first.value};", sourceId);
}
}

View file

@ -6,7 +6,7 @@ part of 'cookie.dart';
// RiverpodGenerator
// **************************************************************************
String _$setCookieHash() => r'e9519081f85b28e34d09466392e4e253cc51b9af';
String _$setCookieHash() => r'3e91a1c44ab0e597cebb55bf50d3a5a29d1eb29b';
/// Copied from Dart SDK
class _SystemHash {
@ -42,11 +42,11 @@ class SetCookieFamily extends Family<AsyncValue<dynamic>> {
/// See also [setCookie].
SetCookieProvider call(
String source,
String sourceId,
String url,
) {
return SetCookieProvider(
source,
sourceId,
url,
);
}
@ -56,7 +56,7 @@ class SetCookieFamily extends Family<AsyncValue<dynamic>> {
covariant SetCookieProvider provider,
) {
return call(
provider.source,
provider.sourceId,
provider.url,
);
}
@ -80,12 +80,12 @@ class SetCookieFamily extends Family<AsyncValue<dynamic>> {
class SetCookieProvider extends AutoDisposeFutureProvider<dynamic> {
/// See also [setCookie].
SetCookieProvider(
this.source,
this.sourceId,
this.url,
) : super.internal(
(ref) => setCookie(
ref,
source,
sourceId,
url,
),
from: setCookieProvider,
@ -98,20 +98,20 @@ class SetCookieProvider extends AutoDisposeFutureProvider<dynamic> {
allTransitiveDependencies: SetCookieFamily._allTransitiveDependencies,
);
final String source;
final String sourceId;
final String url;
@override
bool operator ==(Object other) {
return other is SetCookieProvider &&
other.source == source &&
other.sourceId == sourceId &&
other.url == url;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, sourceId.hashCode);
hash = _SystemHash.combine(hash, url.hashCode);
return _SystemHash.finish(hash);

View file

@ -6,14 +6,15 @@ part 'cookie_providers.g.dart';
@riverpod
class CookieState extends _$CookieState {
@override
String build(String source) {
String build(String idSource) {
final cookiesList = isar.settings.getSync(227)!.cookiesList ?? [];
final cookieList =
cookiesList.where((element) => element.source == source).toList();
cookiesList.where((element) => element.idSource == idSource).toList();
String cookie = "";
if (cookieList.isNotEmpty) {
cookie = cookieList.first.cookie!.toString();
}
return cookie;
}
@ -21,15 +22,42 @@ class CookieState extends _$CookieState {
final settings = isar.settings.getSync(227);
List<Cookie>? cookieList = [];
for (var cookie in settings!.cookiesList ?? []) {
if (cookie.source != source) {
if (cookie.idSource != idSource) {
cookieList.add(cookie);
}
}
cookieList.add(Cookie()
..source = source
..idSource = idSource
..cookie = newCookie);
isar.writeTxnSync(
() => isar.settings.putSync(settings..cookiesList = cookieList));
}
}
void setCookieB(String newCookie, String idSource) {
final settings = isar.settings.getSync(227);
List<Cookie>? cookieList = [];
for (var cookie in settings!.cookiesList ?? []) {
if (cookie.idSource != idSource) {
cookieList.add(cookie);
}
}
cookieList.add(Cookie()
..idSource = idSource
..cookie = newCookie);
isar.writeTxnSync(
() => isar.settings.putSync(settings..cookiesList = cookieList));
}
String getCookie(String idSource) {
final cookiesList = isar.settings.getSync(227)!.cookiesList ?? [];
final cookieList =
cookiesList.where((element) => element.idSource == idSource).toList();
String cookie = "";
if (cookieList.isNotEmpty) {
cookie = cookieList.first.cookie!.toString();
}
return cookie;
}

View file

@ -6,7 +6,7 @@ part of 'cookie_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$cookieStateHash() => r'10a8c516aead11fc1820ae11549c33bc7b82fc39';
String _$cookieStateHash() => r'37608903bb251f586fea7458dd4762160c456868';
/// Copied from Dart SDK
class _SystemHash {
@ -30,10 +30,10 @@ class _SystemHash {
}
abstract class _$CookieState extends BuildlessAutoDisposeNotifier<String> {
late final String source;
late final String idSource;
String build(
String source,
String idSource,
);
}
@ -48,10 +48,10 @@ class CookieStateFamily extends Family<String> {
/// See also [CookieState].
CookieStateProvider call(
String source,
String idSource,
) {
return CookieStateProvider(
source,
idSource,
);
}
@ -60,7 +60,7 @@ class CookieStateFamily extends Family<String> {
covariant CookieStateProvider provider,
) {
return call(
provider.source,
provider.idSource,
);
}
@ -84,9 +84,9 @@ class CookieStateProvider
extends AutoDisposeNotifierProviderImpl<CookieState, String> {
/// See also [CookieState].
CookieStateProvider(
this.source,
this.idSource,
) : super.internal(
() => CookieState()..source = source,
() => CookieState()..idSource = idSource,
from: cookieStateProvider,
name: r'cookieStateProvider',
debugGetCreateSourceHash:
@ -98,17 +98,17 @@ class CookieStateProvider
CookieStateFamily._allTransitiveDependencies,
);
final String source;
final String idSource;
@override
bool operator ==(Object other) {
return other is CookieStateProvider && other.source == source;
return other is CookieStateProvider && other.idSource == idSource;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, idSource.hashCode);
return _SystemHash.finish(hash);
}
@ -118,7 +118,7 @@ class CookieStateProvider
covariant CookieState notifier,
) {
return notifier.build(
source,
idSource,
);
}
}

View file

@ -0,0 +1,34 @@
// import 'dart:developer';
// import 'dart:io';
// import 'package:cookie_jar/cookie_jar.dart';
// import 'package:dio/dio.dart';
// import 'package:dio_cookie_manager/dio_cookie_manager.dart';
// import 'package:mangayomi/utils/constant.dart';
// import 'package:path_provider/path_provider.dart';
// class SetCookie {
// static onSet(List cookiesList, String url) async {
// final dio = Dio();
// List<Cookie> jarCookies = [];
// if (cookiesList.isNotEmpty) {
// for (var i in cookiesList) {
// if (i.name == 'cf_clearance') {
// Cookie jarCookie = Cookie(i.name, i.value);
// log(i.value);
// jarCookies.add(jarCookie);
// }
// }
// }
// final cookieJar = CookieJar();
// await cookieJar.saveFromResponse(Uri.parse(url), jarCookies);
// dio.interceptors.add(CookieManager(cookieJar));
// final tt = await dio.get(url,
// options: Options(headers: {'User-Agent': defaultUserAgent}));
// print(tt.statusCode);
// return true;
// }
// }

View file

@ -1,52 +1,55 @@
import 'package:html/dom.dart';
import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart';
import 'package:http/http.dart' as http;
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/utils/headers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'http_service.g.dart';
// import 'package:html/dom.dart';
// import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart';
// import 'package:http/http.dart' as http;
// import 'package:mangayomi/sources/utils/utils.dart';
// import 'package:mangayomi/utils/headers.dart';
// import 'package:riverpod_annotation/riverpod_annotation.dart';
// part 'http_service.g.dart';
@riverpod
Future<dynamic> httpGet(HttpGetRef ref,
{required String url,
required String source,
required bool resDom,
Map<String, String>? headers,
bool useUserAgent = false}) async {
bool isCloudflaree = isCloudflare(source);
if (resDom) {
Document? dom;
if (isCloudflaree) {
dom = await ref.read(cloudflareBypassDomProvider(
url: url, source: source, useUserAgent: useUserAgent)
.future);
} else {
dom = await httpResToDom(
url: url,
headers: headers ?? ref.watch(headersProvider(source: source)));
}
return dom;
} else {
String? resHtml;
if (isCloudflaree) {
resHtml = await ref.read(cloudflareBypassHtmlProvider(
url: url, source: source, useUserAgent: useUserAgent)
.future);
} else {
try {
final response = await http.get(Uri.parse(url),
headers: headers ?? ref.watch(headersProvider(source: source)));
resHtml = response.body;
} catch (e) {
rethrow;
}
}
return resHtml;
}
}
// @riverpod
// Future<dynamic> httpGet(HttpGetRef ref,
// {required String url,
// required String source,
// required String lang,
// required bool resDom,
// Map<String, String>? headers,
// bool useUserAgent = false}) async {
// bool hasCloudflaree = hasCloudflare(source);
// if (resDom) {
// Document? dom;
// if (hasCloudflaree) {
// dom = await ref.read(cloudflareBypassDomProvider(
// url: url, source: source, useUserAgent: useUserAgent)
// .future);
// } else {
// dom = await httpResToDom(
// url: url,
// headers: headers ??
// ref.watch(headersProvider(source: source, lang: lang)));
// }
// return dom;
// } else {
// String? resHtml;
// if (hasCloudflaree) {
// resHtml = await ref.read(cloudflareBypassHtmlProvider(
// url: url, source: source, useUserAgent: useUserAgent)
// .future);
// } else {
// try {
// final response = await http.get(Uri.parse(url),
// headers: headers ??
// ref.watch(headersProvider(source: source, lang: lang)));
// resHtml = response.body;
// } catch (e) {
// rethrow;
// }
// }
// return resHtml;
// }
// }
Future<Document> httpResToDom(
{required String url, required Map<String, String>? headers}) async {
final response = await http.get(Uri.parse(url), headers: headers);
return Document.html(response.body);
}
// Future<Document> httpResToDom(
// {required String url, required Map<String, String>? headers}) async {
// final response = await http.get(Uri.parse(url), headers: headers);
// return Document.html(response.body);
// }

View file

@ -1,144 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'http_service.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$httpGetHash() => r'115d7fdde9392d32055ddefe661731c37b2b584e';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef HttpGetRef = AutoDisposeFutureProviderRef<dynamic>;
/// See also [httpGet].
@ProviderFor(httpGet)
const httpGetProvider = HttpGetFamily();
/// See also [httpGet].
class HttpGetFamily extends Family<AsyncValue<dynamic>> {
/// See also [httpGet].
const HttpGetFamily();
/// See also [httpGet].
HttpGetProvider call({
required String url,
required String source,
required bool resDom,
Map<String, String>? headers,
bool useUserAgent = false,
}) {
return HttpGetProvider(
url: url,
source: source,
resDom: resDom,
headers: headers,
useUserAgent: useUserAgent,
);
}
@override
HttpGetProvider getProviderOverride(
covariant HttpGetProvider provider,
) {
return call(
url: provider.url,
source: provider.source,
resDom: provider.resDom,
headers: provider.headers,
useUserAgent: provider.useUserAgent,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'httpGetProvider';
}
/// See also [httpGet].
class HttpGetProvider extends AutoDisposeFutureProvider<dynamic> {
/// See also [httpGet].
HttpGetProvider({
required this.url,
required this.source,
required this.resDom,
this.headers,
this.useUserAgent = false,
}) : super.internal(
(ref) => httpGet(
ref,
url: url,
source: source,
resDom: resDom,
headers: headers,
useUserAgent: useUserAgent,
),
from: httpGetProvider,
name: r'httpGetProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$httpGetHash,
dependencies: HttpGetFamily._dependencies,
allTransitiveDependencies: HttpGetFamily._allTransitiveDependencies,
);
final String url;
final String source;
final bool resDom;
final Map<String, String>? headers;
final bool useUserAgent;
@override
bool operator ==(Object other) {
return other is HttpGetProvider &&
other.url == url &&
other.source == source &&
other.resDom == resDom &&
other.headers == headers &&
other.useUserAgent == useUserAgent;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, url.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, resDom.hashCode);
hash = _SystemHash.combine(hash, headers.hashCode);
hash = _SystemHash.combine(hash, useUserAgent.hashCode);
return _SystemHash.finish(hash);
}
}
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View file

@ -1,107 +1,59 @@
import 'package:mangayomi/eval/compiler/compiler.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/sources/multisrc/heancms/heancms.dart';
import 'package:mangayomi/sources/multisrc/madara/src/madara.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/src/all/comick/src/comick.dart';
import 'package:mangayomi/sources/src/all/mangadex/src/mangadex.dart';
import 'package:mangayomi/sources/src/en/mangahere/src/mangahere.dart';
import 'package:mangayomi/sources/src/fr/japscan/src/japscan.dart';
import 'package:mangayomi/sources/src/fr/mangakawaii/src/mangakawaii.dart';
import 'package:mangayomi/sources/multisrc/mangathemesia/src/mangathemesia.dart';
import 'package:mangayomi/sources/multisrc/mmrcms/src/mmrcms.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
import 'package:mangayomi/eval/bridge_class/model.dart';
import 'package:mangayomi/eval/runtime/runtime.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'search_manga.g.dart';
@riverpod
Future<List<GetManga?>> searchManga(
Future<List<MangaModel?>> searchManga(
SearchMangaRef ref, {
required String source,
required Source source,
required String query,
required String lang,
required int page,
}) async {
List<GetManga?>? manga;
source = source.toLowerCase();
List<MangaModel?>? manga = [];
final bytecode = compilerEval(source.sourceCode!);
/********/
/*comick*/
/********/
if (getMangaTypeSource(source) == TypeSource.comick) {
manga = await Comick()
.searchManga(source: source, query: query, ref: ref, lang: lang);
final runtime = runtimeEval(bytecode);
runtime.args = [
$MangaModel.wrap(MangaModel(
query: query,
lang: source.lang,
page: page,
baseUrl: source.baseUrl,
apiUrl: source.apiUrl,
sourceId: source.id,
source: source.name,
dateFormat: source.dateFormat,
dateFormatLocale: source.dateFormatLocale))
];
var result2 = await runtime.executeLib(
'package:package:mangayomi/main.dart',
'searchManga',
);
try {
if (result2 is $MangaModel) {
final value = result2.$reified;
List<MangaModel> newManga = [];
for (var i = 0; i < value.names!.length; i++) {
MangaModel newMangaa = MangaModel(
name: value.names![i],
link: value.urls![i],
imageUrl: value.images![i],
baseUrl: value.baseUrl,
apiUrl: value.apiUrl,
lang: value.lang,
sourceId: value.sourceId,
dateFormat: value.dateFormat,
dateFormatLocale: value.dateFormatLocale);
newManga.add(newMangaa);
}
manga = newManga;
}
} catch (_) {
throw Exception("");
}
/***************/
/*mangathemesia*/
/***************/
else if (getMangaTypeSource(source) == TypeSource.mangathemesia) {
manga = await MangaThemeSia()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
/***********/
/*mangakawaii*/
/***********/
else if (source == "mangakawaii") {
manga = await MangaKawaii()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
/***********/
/*mmrcms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mmrcms) {
manga = await Mmrcms()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
/***********/
/*mangahere*/
/***********/
else if (source == "mangahere") {
manga = await Mangahere()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
/***********/
/*japscan*/
/***********/
else if (source == "japscan") {
manga = await Japscan()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
/***********/
/*heancms*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.heancms) {
manga = await HeanCms()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
/***********/
/*madara*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.madara) {
manga = await Madara()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
/***********/
/*mangadex*/
/***********/
else if (getMangaTypeSource(source) == TypeSource.mangadex) {
manga = await MangaDex()
.searchManga(source: source, query: query, ref: ref, lang: lang);
}
return manga!;
return manga;
}

View file

@ -6,7 +6,7 @@ part of 'search_manga.dart';
// RiverpodGenerator
// **************************************************************************
String _$searchMangaHash() => r'0448881ce3785ac0c670a87914590008ae19fdd5';
String _$searchMangaHash() => r'6e039e81e4764f69dca9a3ed133a76331d291b68';
/// Copied from Dart SDK
class _SystemHash {
@ -29,27 +29,27 @@ class _SystemHash {
}
}
typedef SearchMangaRef = AutoDisposeFutureProviderRef<List<GetManga?>>;
typedef SearchMangaRef = AutoDisposeFutureProviderRef<List<MangaModel?>>;
/// See also [searchManga].
@ProviderFor(searchManga)
const searchMangaProvider = SearchMangaFamily();
/// See also [searchManga].
class SearchMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
class SearchMangaFamily extends Family<AsyncValue<List<MangaModel?>>> {
/// See also [searchManga].
const SearchMangaFamily();
/// See also [searchManga].
SearchMangaProvider call({
required String source,
required Source source,
required String query,
required String lang,
required int page,
}) {
return SearchMangaProvider(
source: source,
query: query,
lang: lang,
page: page,
);
}
@ -60,7 +60,7 @@ class SearchMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
return call(
source: provider.source,
query: provider.query,
lang: provider.lang,
page: provider.page,
);
}
@ -80,18 +80,18 @@ class SearchMangaFamily extends Family<AsyncValue<List<GetManga?>>> {
}
/// See also [searchManga].
class SearchMangaProvider extends AutoDisposeFutureProvider<List<GetManga?>> {
class SearchMangaProvider extends AutoDisposeFutureProvider<List<MangaModel?>> {
/// See also [searchManga].
SearchMangaProvider({
required this.source,
required this.query,
required this.lang,
required this.page,
}) : super.internal(
(ref) => searchManga(
ref,
source: source,
query: query,
lang: lang,
page: page,
),
from: searchMangaProvider,
name: r'searchMangaProvider',
@ -104,16 +104,16 @@ class SearchMangaProvider extends AutoDisposeFutureProvider<List<GetManga?>> {
SearchMangaFamily._allTransitiveDependencies,
);
final String source;
final Source source;
final String query;
final String lang;
final int page;
@override
bool operator ==(Object other) {
return other is SearchMangaProvider &&
other.source == source &&
other.query == query &&
other.lang == lang;
other.page == page;
}
@override
@ -121,7 +121,7 @@ class SearchMangaProvider extends AutoDisposeFutureProvider<List<GetManga?>> {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, source.hashCode);
hash = _SystemHash.combine(hash, query.hashCode);
hash = _SystemHash.combine(hash, lang.hashCode);
hash = _SystemHash.combine(hash, page.hashCode);
return _SystemHash.finish(hash);
}

View file

@ -1,214 +0,0 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/multisrc/heancms/model/search.dart';
import 'package:mangayomi/sources/multisrc/heancms/utils/utils.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:http/http.dart' as http;
import 'package:mangayomi/sources/utils/utils.dart';
class HeanCms extends MangaYomiServices {
Map<String, String> _headers(String source) => {
'Origin': getMangaBaseUrl(source),
'Referer': getMangaAPIUrl(source),
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
};
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
final chapterId = chapter.url!.split("#").last;
var request = http.Request(
'GET',
Uri.parse(
'${getMangaAPIUrl("${chapter.manga.value!.source}")}series/chapter/$chapterId'));
request.headers.addAll({
'Accept': 'application/json, text/plain, */*',
});
http.StreamedResponse response = await request.send();
final res = await response.stream.bytesToString();
var page = jsonDecode(res) as Map<String, dynamic>;
for (var url in page["content"]["images"]) {
if (url.startsWith("http")) {
pageUrls.add(url);
} else {
pageUrls.add("${getMangaAPIUrl("${chapter.manga.value!.source}")}$url");
}
}
return pageUrls;
}
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
String currentSlug = manga.url!.split('/').last;
var request = http.Request(
'GET',
Uri.parse(
'${getMangaAPIUrl(source)}series/$currentSlug#${manga.status}'));
request.headers.addAll({
'Accept': 'application/json, text/plain, */*',
});
http.StreamedResponse response = await request.send();
final res = await response.stream.bytesToString();
var mangaDetail = jsonDecode(res) as Map<String, dynamic>;
final d = Data.fromJson(mangaDetail);
final dom = await httpResToDom(
url: '${getMangaBaseUrl(source)}/series/$currentSlug', headers: {});
final des = dom
.querySelectorAll("div.description-container")
.map((e) => e.text.trim())
.toList();
final genres = dom
.querySelectorAll("div.tags-container.pt-3 > span")
.map((e) => e.text)
.toList();
final auth = dom
.querySelectorAll("div > p")
.where((element) =>
element.outerHtml.toLowerCase().contains('autor') ||
element.outerHtml.toLowerCase().contains('author'))
.map((e) => e.text)
.toList();
author = auth.first.split(":").last;
status = manga.status;
genre = source == "OmegaScans" ? [] : genres;
description = des.first;
for (var chapter in d.chapters!) {
chapterUrl
.add("/series/$currentSlug/${chapter.chapterSlug}#${chapter.id}");
chapterTitle.add(chapter.chapterName!.trim());
chapterDate.add(parseDate(chapter.createdAt!, source));
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
var request = http.Request(
'POST', Uri.parse('${getMangaAPIUrl(source)}series/querysearch'));
request.body = json.encode({
"page": page,
"order": "desc",
"order_by": "total_views",
"series_status": "Ongoing",
"series_type": "Comic"
});
request.headers.addAll(_headers(source));
http.StreamedResponse response = await request.send();
final res = await response.stream.bytesToString();
List<Data>? data;
if (res.startsWith("{")) {
var popularManga = jsonDecode(res) as Map<String, dynamic>;
var popularMangaList = HeanCmsSearchModel.fromJson(popularManga);
data = popularMangaList.data!;
} else {
var popularManga = jsonDecode(res) as List;
data = popularManga.map((e) => Data.fromJson(e)).toList();
}
for (var a in data) {
final status = parseHeanCmsStatus(a.status!);
statusList.add(status);
image.add(a.thumbnail!.startsWith("https://")
? a.thumbnail
: "${getMangaAPIUrl(source)}cover/${a.thumbnail}");
name.add(a.title);
url.add("/series/${a.seriesSlug!.replaceAll(timeStampRegex, '')}");
}
return mangaRes();
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
var request = http.Request(
'POST', Uri.parse('${getMangaAPIUrl(source)}series/search'));
request.body = json.encode({"term": query.trim()});
request.headers.addAll({
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
});
http.StreamedResponse response = await request.send();
final res = await response.stream.bytesToString();
List<Data>? data;
if (res.startsWith("{")) {
var popularManga = jsonDecode(res) as Map<String, dynamic>;
var popularMangaList = HeanCmsSearchModel.fromJson(popularManga);
data = popularMangaList.data!;
} else {
var popularManga = jsonDecode(res) as List;
data = popularManga.map((e) => Data.fromJson(e)).toList();
}
for (var a in data) {
final status = parseHeanCmsStatus(a.status!);
statusList.add(status);
image.add(a.thumbnail!.startsWith("https://")
? a.thumbnail
: "${getMangaAPIUrl(source)}cover/${a.thumbnail}");
name.add(a.title);
url.add("/series/${a.seriesSlug!.replaceAll(timeStampRegex, '')}");
}
return mangaRes();
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
var request = http.Request(
'POST', Uri.parse('${getMangaAPIUrl(source)}series/querysearch'));
request.body = json.encode({
"page": page,
"order": "desc",
"order_by": "latest",
"series_status": "Ongoing",
"series_type": "Comic"
});
request.headers.addAll(_headers(source));
http.StreamedResponse response = await request.send();
final res = await response.stream.bytesToString();
List<Data>? data;
if (res.startsWith("{")) {
var popularManga = jsonDecode(res) as Map<String, dynamic>;
var popularMangaList = HeanCmsSearchModel.fromJson(popularManga);
data = popularMangaList.data!;
} else {
var popularManga = jsonDecode(res) as List;
data = popularManga.map((e) => Data.fromJson(e)).toList();
}
for (var a in data) {
final status = parseHeanCmsStatus(a.status!);
statusList.add(status);
image.add(a.thumbnail!.startsWith("https://")
? a.thumbnail
: "${getMangaAPIUrl(source)}cover/${a.thumbnail}");
name.add(a.title);
url.add("/series/${a.seriesSlug!.replaceAll(timeStampRegex, '')}");
}
return mangaRes();
}
}

View file

@ -1,36 +0,0 @@
import 'package:mangayomi/models/source.dart';
List<Source> get heanCmsSourcesList => _heanCmsSourcesList;
List<Source> _heanCmsSourcesList = [
Source(
sourceName: "YugenMangas",
baseUrl: "https://yugenmangas.com",
apiUrl: "https://api.yugenmangas.com/",
lang: "es",
typeSource: TypeSource.heancms,
isNsfw: true,
logoUrl: '',
dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ",
dateFormatLocale: "en"),
Source(
sourceName: "OmegaScans",
baseUrl: "https://omegascans.org",
apiUrl: "https://api.omegascans.org/",
lang: "en",
typeSource: TypeSource.heancms,
isNsfw: true,
logoUrl: '',
dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ",
dateFormatLocale: "en"),
// Source(
// sourceName: "ReaperScans",
// baseUrl: "https://reaperscans.net",
// apiUrl: "https://api.reaperscans.net/",
// lang: "pt-br",
// typeSource: TypeSource.heancms,
// isNsfw: true,
// logoUrl: 'https://reaperscans.net/images/webicon.png',
// dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ",
// dateFormatLocale: "pt-BR",
// ),
];

View file

@ -1,136 +0,0 @@
class HeanCmsSearchModel {
Meta? meta;
List<Data>? data;
HeanCmsSearchModel({this.meta, this.data});
HeanCmsSearchModel.fromJson(Map<String, dynamic> json) {
meta = json['meta'] != null ? Meta.fromJson(json['meta']) : null;
if (json['data'] != null) {
data = <Data>[];
json['data'].forEach((v) {
data!.add(Data.fromJson(v));
});
}
}
}
class Meta {
int? total;
int? perPage;
int? currentPage;
int? lastPage;
int? firstPage;
String? firstPageUrl;
String? lastPageUrl;
String? nextPageUrl;
Meta({
this.total,
this.perPage,
this.currentPage,
this.lastPage,
this.firstPage,
this.firstPageUrl,
this.lastPageUrl,
this.nextPageUrl,
});
Meta.fromJson(Map<String, dynamic> json) {
total = json['total'];
perPage = json['per_page'];
currentPage = json['current_page'];
lastPage = json['last_page'];
firstPage = json['first_page'];
firstPageUrl = json['first_page_url'];
lastPageUrl = json['last_page_url'];
nextPageUrl = json['next_page_url'];
}
}
class Data {
String? title;
int? id;
String? seriesType;
String? visibility;
String? thumbnail;
String? seriesSlug;
String? status;
String? latest;
String? badge;
List<Chapters>? chapters;
Meta? meta;
Data(
{this.title,
this.id,
this.seriesType,
this.visibility,
this.thumbnail,
this.seriesSlug,
this.status,
this.latest,
this.badge,
this.chapters,
this.meta});
Data.fromJson(Map<String, dynamic> json) {
title = json['title'];
id = json['id'];
seriesType = json['series_type'];
visibility = json['visibility'];
thumbnail = json['thumbnail'];
seriesSlug = json['series_slug'];
status = json['status'];
latest = json['latest'];
badge = json['badge'];
if (json['chapters'] != null) {
chapters = <Chapters>[];
json['chapters'].forEach((v) {
chapters!.add(Chapters.fromJson(v));
});
}
meta = json['meta'] != null ? Meta.fromJson(json['meta']) : null;
}
}
class Chapters {
int? id;
String? index;
String? chapterName;
String? chapterSlug;
String? createdAt;
String? updatedAt;
int? seriesId;
Chapters(
{this.id,
this.index,
this.chapterName,
this.chapterSlug,
this.createdAt,
this.updatedAt,
this.seriesId});
Chapters.fromJson(Map<String, dynamic> json) {
id = json['id'];
index = json['index'];
chapterName = json['chapter_name'];
chapterSlug = json['chapter_slug'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
seriesId = json['series_id'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['index'] = index;
data['chapter_name'] = chapterName;
data['chapter_slug'] = chapterSlug;
data['created_at'] = createdAt;
data['updated_at'] = updatedAt;
data['series_id'] = seriesId;
return data;
}
}

View file

@ -1,14 +0,0 @@
import 'package:mangayomi/models/manga.dart';
RegExp timeStampRegex = RegExp("-\\d+");
parseHeanCmsStatus(String status) {
return switch (status) {
"Ongoing" => Status.ongoing,
"Hiatus" => Status.onHiatus,
"Dropped" => Status.canceled,
"Completed" => Status.completed,
"Finished" => Status.completed,
_ => Status.unknown,
};
}

View file

@ -1,594 +0,0 @@
import 'package:mangayomi/models/source.dart';
const defaultDateFormat = "MMMM dd, yyyy";
const defaultDateFormatLocale = "en_US";
List<Source> get madaraSourcesList => _madaraSourcesList;
List<Source> _madaraSourcesList = [
Source(
sourceName: "FR-Scan",
baseUrl: "https://fr-scan.com",
lang: "fr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: "fr"),
Source(
sourceName: "AstralManga",
baseUrl: "https://astral-manga.fr",
lang: "fr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/mm/yyyy",
dateFormatLocale: "fr"),
Source(
sourceName: "Akuma no Tenshi",
baseUrl: "https://akumanotenshi.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "Adult Webtoon",
baseUrl: "https://adultwebtoon.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Adult Webtoon",
baseUrl: "https://adultwebtoon.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "ArazNovel",
baseUrl: "https://www.araznovel.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "d MMMM yyyy",
dateFormatLocale: "en",
),
Source(
sourceName: "BestManga",
baseUrl: "https://bestmanga.club",
lang: "ru",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd.MM.yyyy",
dateFormatLocale: "ru",
),
Source(
sourceName: "Chibi Manga",
baseUrl: "https://www.cmreader.info",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMM dd, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
// Source(
// sourceName: "Clover Manga",
// baseUrl: "https://clover-manga.com",
// lang: "tr",
// typeSource: TypeSource.madara,
// logoUrl: '',
// dateFormat: defaultDateFormat,
// dateFormatLocale: "tr",
// ),
Source(
sourceName: "CookieToon",
baseUrl: "https://cookietoon.online",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "Drope Scan",
baseUrl: "https://dropescan.com",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "EvaScans",
baseUrl: "https://evascans.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMM d, yyy",
dateFormatLocale: "tr",
),
Source(
sourceName: "Final Scans",
baseUrl: "https://finalscans.com",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: "MMMM d, yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "Glory Manga",
baseUrl: "https://glorymanga.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyy",
dateFormatLocale: "tr",
),
Source(
sourceName: "Hentai Manga",
baseUrl: "https://hentaimanga.me",
isNsfw: true,
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "HentaiWebtoon",
baseUrl: "https://hentaiwebtoon.com",
isNsfw: true,
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Ikifeng",
baseUrl: "https://ikifeng.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: "dd/MM/yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Inmortal Scan",
baseUrl: "https://manga.mundodrama.site",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Its Your Right Manhua",
baseUrl: "https://itsyourightmanhua.com/",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Kami Sama Explorer",
baseUrl: "https://leitor.kamisama.com.br",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd 'de' MMMM 'de' yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "KlikManga",
baseUrl: "https://klikmanga.id",
lang: "id",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: "id",
),
Source(
sourceName: "KSGroupScans",
baseUrl: "https://ksgroupscans.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "LHTranslation",
baseUrl: "https://lhtranslation.net",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Lolicon",
baseUrl: "https://lolicon.mobi",
lang: "en",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Lord Manga",
baseUrl: "https://lordmanga.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "MangaVisa",
baseUrl: "https://mangavisa.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "MangaClash",
baseUrl: "https://mangaclash.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MM/dd/yy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manga District",
baseUrl: "https://mangadistrict.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manga-fast.com",
baseUrl: "https://manga-fast.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "d MMMM'،' yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manga Fenix",
baseUrl: "https://manga-fenix.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "MangaFreak.online",
baseUrl: "https://mangafreak.online",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "d MMMM، yyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "MangaGreat",
baseUrl: "https://mangagreat.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manga Hentai",
baseUrl: "https://mangahentai.me",
lang: "en",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "MangaMe",
baseUrl: "https://mangame.org",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd.MM.yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manga One Love",
baseUrl: "https://mangaonelove.site",
lang: "ru",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: "dd.MM.yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manga Read",
baseUrl: "https://mangaread.co",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "yyyy-MM-dd",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "MangaRolls",
baseUrl: "https://mangarolls.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manga Şehri",
baseUrl: "https://mangasehri.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyy",
dateFormatLocale: "tr",
),
Source(
sourceName: "Mangasushi",
baseUrl: "https://mangasushi.org",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manhwa68",
baseUrl: "https://manhwa68.com",
lang: "en",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Manhwua.fans",
baseUrl: "https://manhwua.fans",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "yyyy'年'M'月'd",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "ManyToon.me",
baseUrl: "https://manytoon.me",
lang: "en",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Milftoon",
baseUrl: "https://milftoon.xxx",
lang: "en",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "d MMMM, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "MurimScan",
baseUrl: "https://murimscan.run",
lang: "en",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Ninja Scan",
baseUrl: "https://ninjascan.xyz",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd 'de' MMMMM 'de' yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "NovelCrow",
baseUrl: "https://novelcrow.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Pirulito Rosa",
baseUrl: "https://pirulitorosa.site",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
isNsfw: true,
dateFormat: "dd/MM/yyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "RagnarokScan",
baseUrl: "https://ragnarokscan.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMMM dd, yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Ragnarok Scanlation",
baseUrl: "https://ragnarokscanlation.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Rio2 Manga",
baseUrl: "https://rio2manga.com",
lang: "en",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: defaultDateFormat,
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "Romantik Manga",
baseUrl: "https://rio2manga.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: defaultDateFormatLocale,
),
Source(
sourceName: "SamuraiScan",
baseUrl: "https://samuraiscan.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Sdl scans",
baseUrl: "https://sdlscans.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMMM dd, yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Shayami",
baseUrl: "https://shayami.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMM d, yyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Taurus Fansub",
baseUrl: "https://tatakaescan.com",
lang: "es",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyy",
dateFormatLocale: "es",
),
Source(
sourceName: "The Sugar",
baseUrl: "https://thesugarscan.com",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyyy",
dateFormatLocale: "pt-BR",
),
// Source(
// sourceName: "365Manga",
// baseUrl: "https://365manga.com",
// lang: "en",
// typeSource: TypeSource.madara,
// logoUrl: '',
// dateFormat: defaultDateFormat,
// dateFormatLocale: defaultDateFormatLocale,
// ),
Source(
sourceName: "Tortuga Ceviri",
baseUrl: "https://tortuga-ceviri.com",
lang: "tr",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "MMMM d, yyyy",
dateFormatLocale: "tr",
),
Source(
sourceName: "Tumangaonline.site",
baseUrl: "https://tumangaonline.site",
lang: "es",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd MMMMM, yyyy",
dateFormatLocale: "es",
),
Source(
sourceName: "Winter Scan",
baseUrl: "https://winterscan.com",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd 'de' MMMM 'de' yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "Yuri Verso",
baseUrl: "https://yuri.live",
lang: "pt-br",
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyyy",
dateFormatLocale: "pt-BR",
),
Source(
sourceName: "Zero Scan",
baseUrl: "https://zeroscan.com.br",
lang: "pt-br",
isNsfw: true,
typeSource: TypeSource.madara,
logoUrl: '',
dateFormat: "dd/MM/yyyy",
dateFormatLocale: "pt-BR",
),
// Source(
// sourceName: "مانجا ليك",
// baseUrl: "https://mangalek.com",
// lang: "ar",
// typeSource: TypeSource.madara,
// logoUrl: '',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "ar",
// ),
];

View file

@ -1,211 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/multisrc/madara/src/utils.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
import 'package:mangayomi/utils/xpath_selector.dart';
import 'package:http/http.dart' as http;
class Madara extends MangaYomiServices {
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await await ref.watch(httpGetProvider(
useUserAgent: true,
url: chapter.url!,
source: chapter.manga.value!.source!.toLowerCase(),
resDom: true)
.future) as Document?;
final res = dom!.querySelector(
"div.page-break, li.blocks-gallery-item, .reading-content, .text-left img");
final imgs = res!
.querySelectorAll('img')
.map((i) => regSrcMatcher(i.outerHtml).trim().trimLeft().trimRight())
.toList();
if (imgs.isNotEmpty && imgs.length == 1) {
final pagesNumber = dom
.querySelector("#single-pager")!
.querySelectorAll("option")
.map((e) => e.outerHtml)
.toList();
for (var i = 0; i < pagesNumber.length; i++) {
if (i.toString().length == 1) {
pageUrls.add(
imgs.first.replaceAll("01", '0${int.parse(i.toString()) + 1}'));
} else if (i.toString().length == 2) {
pageUrls.add(
imgs.first.replaceAll("01", '${int.parse(i.toString()) + 1}'));
} else if (i.toString().length == 3) {
pageUrls.add(
imgs.first.replaceAll("01", '${int.parse(i.toString()) + 1}'));
}
}
} else {
pageUrls = imgs;
}
return pageUrls;
}
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(
httpGetProvider(url: manga.url!, source: source, resDom: true)
.future) as Document?;
author = dom!
.querySelectorAll("div.author-content > a")
.map((e) => e.text)
.toList()
.join(', ');
description = dom
.querySelectorAll(
"div.description-summary div.summary__content, div.summary_content div.post-content_item > h5 + div, div.summary_content div.manga-excerpt, div.sinopsis div.contenedor, .description-summary > p")
.map((e) => e.text)
.toList()
.first;
status = madaraStatusParser(dom
.querySelectorAll("div.summary-content")
.map((e) => e.text.trim().trimLeft().trimRight())
.toList()
.last);
manga.imageUrl = dom
.querySelectorAll("div.summary_image img")
.map((e) => regSrcMatcher(e.outerHtml))
.toList()
.first;
genre = dom
.querySelectorAll("div.genres-content a")
.map((e) => e.text)
.toList();
final mangaId = dom
.querySelectorAll("div[id^=manga-chapters-holder]")
.map((e) => e.attributes['data-id'])
.first;
Response? mangaResponse;
final headers = {
"Referer": "${getMangaBaseUrl(source)}/",
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest"
};
mangaResponse = await http.post(
Uri.parse(
"${getMangaBaseUrl(source)}/wp-admin/admin-ajax.php?action=manga_get_chapters&manga=$mangaId"),
headers: headers);
if (mangaResponse.statusCode == 400) {
mangaResponse = await http.post(Uri.parse("${manga.url}ajax/chapters"),
headers: headers);
}
final xpath = xpathSelector(mangaResponse.body);
for (var url in xpath.query("//li/a/@href").attrs) {
chapterUrl.add(url!);
}
for (var title in xpath.query("//li/a/text()").attrs) {
chapterTitle.add(title!.trim().trimLeft().trimRight());
}
final dateF = xpath.query("//li/span/i/text()").attrs;
if (dateF.length == chapterUrl.length) {
for (var date in dateF) {
chapterDate.add(parseChapterDate(date!, source).toString());
}
} else if (dateF.length < chapterUrl.length) {
final length = chapterUrl.length - dateF.length;
for (var i = 0; i < length; i++) {
chapterDate.add(DateTime.now().millisecondsSinceEpoch.toString());
}
for (var date in dateF) {
chapterDate.add(parseChapterDate(date!, source).toString());
}
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga?>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
String? html;
html = await ref.watch(httpGetProvider(
url: '${getMangaBaseUrl(source)}/manga/page/$page/?m_orderby=views',
source: source,
resDom: false)
.future) as String?;
if (xpathSelector(html!)
.query('//*[@id^="manga-item"]/a/@title')
.attrs
.isEmpty) {
html = await ref.watch(httpGetProvider(
url:
'${getMangaBaseUrl(source)}/manga/page/$page/?m_orderby=trending',
source: source,
resDom: false)
.future) as String?;
if (xpathSelector(html!)
.query('//*[@id^="manga-item"]/a/@title')
.attrs
.isEmpty) {
html = await ref.watch(httpGetProvider(
url: getMangaBaseUrl(source), source: source, resDom: false)
.future) as String?;
}
}
final xpath = xpathSelector(html!);
name = xpath.query('//*[@id^="manga-item"]/a/@title').attrs;
url = xpath.query('//*[@class^="post-title"]/h3/a/@href').attrs;
image = xpath.query('//*[@id^="manga-item"]/a/img/@data-src=').attrs;
return mangaRes();
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final html = await ref.watch(httpGetProvider(
url: '${getMangaBaseUrl(source)}/?s=$query&post_type=wp-manga',
source: source,
resDom: false)
.future) as String?;
final xpath = xpathSelector(html!);
name = xpath.query('//*[@class^="post-title"]/h3/a/text()').attrs;
url = xpath.query('//*[@class^="post-title"]/h3/a/@href').attrs;
image = name;
return mangaRes();
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
String? html;
html = await ref.watch(httpGetProvider(
url:
'${getMangaBaseUrl(source)}/manga/page/$page/?m_orderby=latest',
source: source,
resDom: false)
.future) as String?;
final xpath = xpathSelector(html!);
name = xpath.query('//*[@id^="manga-item"]/a/@title').attrs;
url = xpath.query('//*[@class^="post-title"]/h3/a/@href').attrs;
image = xpath.query('//*[@id^="manga-item"]/a/img/@data-src=').attrs;
return mangaRes();
}
}

View file

@ -1,174 +0,0 @@
import 'package:intl/intl.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/sources/utils/utils.dart';
class WordSet {
final List<String> words;
WordSet(this.words);
bool anyWordIn(String dateString) {
return words
.any((word) => dateString.toLowerCase().contains(word.toLowerCase()));
}
bool startsWith(String dateString) {
return words
.any((word) => dateString.toLowerCase().startsWith(word.toLowerCase()));
}
bool endsWith(String dateString) {
return words
.any((word) => dateString.toLowerCase().endsWith(word.toLowerCase()));
}
}
int parseChapterDate(String? date, String source) {
date ??= '';
int parseRelativeDate(String date) {
final number = int.tryParse(RegExp(r"(\d+)").firstMatch(date)!.group(0)!);
if (number == null) return 0;
final cal = DateTime.now();
if (WordSet([
"hari",
"gün",
"jour",
"día",
"dia",
"day",
"วัน",
"ngày",
"giorni",
"أيام",
""
]).anyWordIn(date)) {
return cal.subtract(Duration(days: number)).millisecondsSinceEpoch;
} else if (WordSet([
"jam",
"saat",
"heure",
"hora",
"hour",
"ชั่วโมง",
"giờ",
"ore",
"ساعة",
"小时"
]).anyWordIn(date)) {
return cal.subtract(Duration(hours: number)).millisecondsSinceEpoch;
} else if (WordSet(
["menit", "dakika", "min", "minute", "minuto", "นาที", "دقائق"])
.anyWordIn(date)) {
return cal.subtract(Duration(minutes: number)).millisecondsSinceEpoch;
} else if (WordSet(["detik", "segundo", "second", "วินาที"])
.anyWordIn(date)) {
return cal.subtract(Duration(seconds: number)).millisecondsSinceEpoch;
} else if (WordSet(["week", "semana"]).anyWordIn(date)) {
return cal.subtract(Duration(days: number * 7)).millisecondsSinceEpoch;
} else if (WordSet(["month", "mes"]).anyWordIn(date)) {
return cal.subtract(Duration(days: number * 30)).millisecondsSinceEpoch;
} else if (WordSet(["year", "año"]).anyWordIn(date)) {
return cal.subtract(Duration(days: number * 365)).millisecondsSinceEpoch;
} else {
return 0;
}
}
if (WordSet(["yesterday", "يوم واحد"]).startsWith(date)) {
DateTime cal = DateTime.now().subtract(const Duration(days: 1));
cal = DateTime(cal.year, cal.month, cal.day);
return cal.millisecondsSinceEpoch;
} else if (WordSet(["today"]).startsWith(date)) {
DateTime cal = DateTime.now();
cal = DateTime(cal.year, cal.month, cal.day);
return cal.millisecondsSinceEpoch;
} else if (WordSet(["يومين"]).startsWith(date)) {
DateTime cal = DateTime.now().subtract(const Duration(days: 2));
cal = DateTime(cal.year, cal.month, cal.day);
return cal.millisecondsSinceEpoch;
} else if (WordSet(["ago", "atrás", "önce", "قبل"]).endsWith(date)) {
return parseRelativeDate(date);
} else if (WordSet(["hace"]).startsWith(date)) {
return parseRelativeDate(date);
} else if (date.contains(RegExp(r"\d(st|nd|rd|th)"))) {
final cleanedDate = date
.split(" ")
.map((it) => it.contains(RegExp(r"\d\D\D"))
? it.replaceAll(RegExp(r"\D"), "")
: it)
.join(" ");
return DateFormat(getFormatDate(source), getFormatDateLocale(source))
.parse(cleanedDate)
.millisecondsSinceEpoch;
} else {
return DateFormat(getFormatDate(source), getFormatDateLocale(source))
.parse(date)
.millisecondsSinceEpoch;
}
}
List<String> completedStatusList = [
"Completed",
"Completo",
"Completado",
"Concluído",
"Concluido",
"Finalizado",
"Terminé",
"Hoàn Thành",
"مكتملة",
"مكتمل",
"已完结",
];
List<String> ongoingStatusList = [
"OnGoing",
"Продолжается",
"Updating",
"Em Lançamento",
"Em lançamento",
"Em andamento",
"Em Andamento",
"En cours",
"Ativo",
"Lançando",
"Đang Tiến Hành",
"Devam Ediyor",
"Devam ediyor",
"In Corso",
"In Arrivo",
"مستمرة",
"مستمر",
"En Curso",
"En curso",
"Emision",
"En marcha",
"Publicandose",
"En emision",
"连载中",
];
List<String> hiatusStatusList = [
"On Hold",
"Pausado",
"En espera",
];
List<String> canceledStatusList = [
"Canceled",
"Cancelado",
];
Status madaraStatusParser(String status) {
return canceledStatusList.contains(status)
? Status.canceled
: completedStatusList.contains(status)
? Status.completed
: ongoingStatusList.contains(status)
? Status.ongoing
: hiatusStatusList.contains(status)
? Status.onHiatus
: Status.unknown;
}

View file

@ -1,316 +0,0 @@
import 'package:mangayomi/models/source.dart';
List<Source> get mangathemesiaSourcesList => _mangathemesiaSourcesList;
List<Source> _mangathemesiaSourcesList = [
Source(
sourceName: "KomikLab",
baseUrl: "https://komiklab.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
Source(
sourceName: "AnimatedGlitchedScans",
baseUrl: "https://anigliscans.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
Source(
sourceName: "ArenaScans",
baseUrl: "https://arenascans.net",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://arenascans.net/wp-content/uploads/2023/02/arena-logo-1.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
Source(
sourceName: "AzureScans",
baseUrl: "https://azuremanga.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
// Source(
// sourceName: "Boosei",
// baseUrl: "https://boosei.net",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl:
// 'https://cdn.boosei.com/wp-content/uploads/2021/11/Logo-Darkmode.png.webp',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "id"),
Source(
sourceName: "Clayrer",
baseUrl: "https://clayrer.net",
lang: "es",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://clayrer.net/wp-content/uploads/2022/12/cropped-BackgroundEraser_20221205_145151639-1.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "es"),
Source(
sourceName: "CosmicScans",
baseUrl: "https://cosmicscans.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
Source(
sourceName: "DiskusScan",
baseUrl: "https://diskusscan.com",
lang: "pt-br",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://diskusscan.com/wp-content/uploads/2022/12/imagem_2022-12-26_222007506.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "pt"),
Source(
sourceName: "DuniaKomik",
baseUrl: "https://duniakomik.id",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "id"),
// Source(
// sourceName: "FlameScans",
// baseUrl: "https://flamescans.fr",
// lang: "fr",
// typeSource: TypeSource.mangathemesia,
// logoUrl: ''),
// Source(
// sourceName: "GremoryMangas",
// baseUrl: "https://gremorymangas.com",
// lang: "es",
// typeSource: TypeSource.mangathemesia,
// logoUrl: 'https://gremorymangas.com/wp-content/uploads/2022/09/6941.png'),
Source(
sourceName: "InfernalVoidScans",
baseUrl: "https://void-scans.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
isCloudflare: true,
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
// Source(
// sourceName: "Kiryuu",
// baseUrl: "https://kiryuu.id",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl: '',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "id"),
// Source(
// sourceName: "KomikIndo",
// baseUrl: "https://komikindo.co",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl: '',
// dateFormat: "MMM dd, yyyy",
// dateFormatLocale: "id"),
Source(
sourceName: "KomikMama",
baseUrl: "https://komikmama.co",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "id"),
Source(
sourceName: "Komiku",
baseUrl: "https://komiku.com",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl: 'https://komiku.com/wp-content/uploads/2022/03/logooo.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "id"),
Source(
sourceName: "KumaScans (Kuma Translation)",
baseUrl: "https://kumascans.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl:
'http://kumascans.com/wp-content/uploads/2021/01/web-rawkuma-copy.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
// Source(
// sourceName: "MangaKita",
// baseUrl: "https://mangakita.net",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl: '',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "en"),
// Source(
// sourceName: "MangaTale",
// baseUrl: "https://mangatale.co",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl:
// 'http://mangatale.co/wp-content/uploads/2022/07/Group-of-23-Objects.png',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "en"),
Source(
sourceName: "MangaYaro",
baseUrl: "https://mangayaro.net",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://cache.mangayaro.net/wp-content/uploads/2021/07/20210723_193720.png.webp',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
Source(
sourceName: "MangKomik",
baseUrl: "https://mangkomik.net",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://cdn.mangkomik.net/wp-content/uploads/2023/02/image_2023-02-04_175453454-e1675527068905.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "id"),
Source(
sourceName: "MangásChan",
baseUrl: "https://mangaschan.com",
lang: "pt-br",
typeSource: TypeSource.mangathemesia,
logoUrl: 'https://mangaschan.com/wp-content/uploads/Logo-web.webp',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "pt-br"),
// Source(
// sourceName: "ManhwaFreak",
// baseUrl: "https://manhwafreak.com",
// lang: "en",
// typeSource: TypeSource.mangathemesia,
// logoUrl:
// 'https://manhwafreak.com/wp-content/uploads/2022/09/New-Theme-Logo-2.png',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "en"),
Source(
sourceName: "ManhwaList",
baseUrl: "https://manhwalist.in",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
// Source(
// sourceName: "MasterKomik",
// baseUrl: "https://masterkomik.com",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl: 'https://masterkomik.com/wp-content/uploads/2020/12/New-MK.png',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "id"),
// Source(
// sourceName: "Nekomik",
// baseUrl: "https://nekomik.com",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl: '',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "en"),
Source(
sourceName: "PhantomScans",
baseUrl: "https://phantomscans.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl: '',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
// Source(
// sourceName: "PhoenixFansub",
// baseUrl: "https://phoenixfansub.com",
// lang: "es",
// typeSource: TypeSource.mangathemesia,
// logoUrl: '',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "es"),
Source(
sourceName: "PiScans",
baseUrl: "https://piscans.in",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://piscans.in/wp-content/uploads/2022/11/WM-Websitewq1.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
// Source(
// sourceName: "Rawkuma",
// baseUrl: "https://rawkuma.com/",
// lang: "ja",
// typeSource: TypeSource.mangathemesia,
// logoUrl: 'http://rawkuma.com/wp-content/uploads/2021/12/Rawkuma-Logo.png',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "en"),
Source(
sourceName: "Readkomik",
baseUrl: "https://readkomik.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://readkomik.com/wp-content/uploads/2021/01/PicsArt_01-23-03.51.01.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
Source(
sourceName: "SuryaScans",
baseUrl: "https://suryascans.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://suryascans.com/wp-content/uploads/2022/09/new-text-surya-logo1x.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
Source(
sourceName: "SushiScan",
baseUrl: "https://sushiscan.net",
lang: "fr",
typeSource: TypeSource.mangathemesia,
logoUrl: 'https://sushiscan.net/wp-content/uploads/SushiScanNewLogo.png',
isCloudflare: true,
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "fr"),
Source(
sourceName: "TsundokuTraduções",
baseUrl: "https://tsundoku.com.br",
lang: "pt-br",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://tsundoku.com.br/wp-content/uploads/2022/01/TsunBranca.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "pt-br"),
Source(
sourceName: "TukangKomik",
baseUrl: "https://tukangkomik.id",
lang: "id",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://tukangkomik.id/wp-content/uploads/2022/03/tukangkomik-logo-1.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
// Source(
// sourceName: "WestManga",
// baseUrl: "https://westmanga.info",
// lang: "id",
// typeSource: TypeSource.mangathemesia,
// logoUrl: 'https://westmanga.info/wp-content/uploads/2021/01/LOGOxx-1.png',
// dateFormat: "MMMM dd, yyyy",
// dateFormatLocale: "en"),
Source(
sourceName: "xCaliBRScans",
baseUrl: "https://xcalibrscans.com",
lang: "en",
typeSource: TypeSource.mangathemesia,
logoUrl:
'https://xcalibrscans.com/wp-content/uploads/2021/06/xcalibr-dark-v3.png',
dateFormat: "MMMM dd, yyyy",
dateFormatLocale: "en"),
];

View file

@ -1,343 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:html/dom.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/utils/utils.dart';
class MangaThemeSia extends MangaYomiServices {
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: manga.url!, source: source, resDom: true, useUserAgent: true)
.future) as Document?;
if (dom!
.querySelectorAll(
'div.bigcontent, div.animefull, div.main-info, div.postbody')
.isNotEmpty) {
final resHtml = dom.querySelector(
'div.bigcontent, div.animefull, div.main-info, div.postbody');
if (resHtml!.querySelectorAll('.tsinfo .imptdt').isNotEmpty) {
status = mangathemesiaStatusParser(resHtml
.querySelectorAll('.tsinfo .imptdt')
.where((e) =>
e.innerHtml.contains("Status") ||
e.innerHtml.contains("Situação"))
.map((e) => e.innerHtml.contains("Situação")
? e.text.replaceAll('Situação', '').trim()
: e.text.replaceAll('Status', '').trim())
.toList()
.last);
} else if (resHtml.querySelectorAll('.infotable tr').isNotEmpty) {
status = mangathemesiaStatusParser(resHtml
.querySelectorAll('.infotable tr')
.where((e) =>
e.innerHtml.toLowerCase().contains('statut') ||
e.innerHtml.toLowerCase().contains('status'))
.map((e) => e.querySelector('td:last-child')!.text)
.toList()
.first);
} else if (resHtml.querySelectorAll('.fmed').isNotEmpty) {
status = mangathemesiaStatusParser(resHtml
.querySelectorAll('.tsinfo .imptdt')
.map((e) => e.text.replaceAll('Status', '').trim())
.toList()
.first);
} else {
status = Status.unknown;
}
//2
if (resHtml.querySelectorAll('.fmed').isNotEmpty) {
author = resHtml
.querySelectorAll('.fmed')
.where((e) => e.innerHtml.contains("Author"))
.map((e) => e.text.replaceAll('Author', '').trim())
.toList()
.first;
} else if (resHtml.querySelectorAll('.tsinfo .imptdt').isNotEmpty) {
author = resHtml
.querySelectorAll('.tsinfo .imptdt')
.where((e) =>
e.innerHtml.contains("Author") ||
e.innerHtml.contains("Auteur") ||
e.innerHtml.contains("Autor"))
.map((e) => e.innerHtml.contains("Autor")
? e.text.replaceAll('Autor', '').trim()
: e.innerHtml.contains("Autheur")
? e.text.replaceAll('Auteur', '').trim()
: e.text.replaceAll('Author', '').trim())
.toList()
.first;
} else if (resHtml.querySelectorAll('.infotable tr').isNotEmpty) {
author = resHtml
.querySelectorAll('.infotable tr')
.where((e) =>
e.innerHtml.toLowerCase().contains('auteur') ||
e.innerHtml.toLowerCase().contains('author'))
.map((e) => e.querySelector('td:last-child')!.text)
.toList()
.first;
} else {
author = "";
}
description = resHtml
.querySelector(".desc, .entry-content[itemprop=description]")!
.text;
if (resHtml
.querySelectorAll('div.gnr a, .mgen a, .seriestugenre a')
.isNotEmpty) {
final tt = resHtml
.querySelectorAll('div.gnr a, .mgen a, .seriestugenre a')
.map((e) => e.text.trim())
.toList();
for (var ok in tt) {
genre.add(ok);
}
} else {
genre.add('');
}
if (resHtml.querySelectorAll('#chapterlist a').isNotEmpty) {
final udl = resHtml
.querySelectorAll('#chapterlist a ')
.where((e) => e.attributes.containsKey('href'))
.map((e) => e.attributes['href'])
.toList();
for (var ok in udl) {
chapterUrl.add(ok!);
}
}
if (resHtml.querySelectorAll('.lch a, .chapternum').isNotEmpty) {
final tt = resHtml
.querySelectorAll('.lch a, .chapternum')
.map((e) => e.text.trim())
.toList();
tt.removeWhere((element) => element.contains('{{number}}'));
for (var ok in tt) {
chapterTitle.add(ok.trimLeft());
}
}
if (resHtml.querySelectorAll('.chapterdate').isNotEmpty) {
final tt = resHtml
.querySelectorAll('.chapterdate')
.map((e) => e.text.trim())
.toList();
tt.removeWhere((element) => element.contains('{{date}}'));
for (var ok in tt) {
chapterDate.add(parseDate(ok, source));
}
}
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga?>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
source = source.toLowerCase();
final dom = await ref.watch(httpGetProvider(
useUserAgent: true,
url:
'${getMangaBaseUrl(source)}/manga/?title=&page=$page&order=popular',
source: source,
resDom: true)
.future) as Document?;
if (dom!
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx')
.isNotEmpty) {
url = dom
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx')
.map((e) {
RegExp exp = RegExp(r'href="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.innerHtml);
String? firstMatch = matches.first.group(1);
return firstMatch;
}).toList();
image = dom
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx')
.map((e) {
RegExp exp = RegExp(r'src="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.innerHtml);
String? firstMatch = matches.first.group(1);
return firstMatch;
}).toList();
name = dom
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx ')
.map((e) {
RegExp exp = RegExp(r'title="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.innerHtml);
String? firstMatch = matches.first.group(1);
return firstMatch;
}).toList();
}
return mangaRes();
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: '${getMangaBaseUrl(source)}/?s=${query.trim()}',
source: source,
resDom: true)
.future) as Document?;
if (dom!
.querySelectorAll(
'#content > div > div.postbody > div > div.listupd > div > div > a')
.isNotEmpty) {
url = dom
.querySelectorAll(
'#content > div > div.postbody > div > div.listupd > div > div > a ')
.where((e) => e.attributes.containsKey('href'))
.map((e) => e.attributes['href'])
.toList();
image = dom
.querySelectorAll(
' #content > div > div.postbody > div > div.listupd > div > div > a > div > img')
.where((e) => e.attributes.containsKey('src'))
.map((e) => e.attributes['src'])
.toList();
name = dom
.querySelectorAll(
'#content > div > div.postbody > div > div.listupd > div > div > a ')
.where((e) => e.attributes.containsKey('title'))
.map((e) => e.attributes['title'])
.toList();
}
return mangaRes();
}
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
useUserAgent: true,
url: chapter.url!,
source: "mangathemesia",
resDom: true)
.future) as Document?;
if (dom!.querySelectorAll('#readerarea').isNotEmpty) {
final ta =
dom.querySelectorAll('#readerarea').map((e) => e.outerHtml).toList();
final RegExp regex = RegExp(r'<img[^>]+src="([^"]+)"');
final Iterable<Match> matches = regex.allMatches(ta.first);
final List<String?> urls = matches.map((m) => m.group(1)).toList();
Iterable<Match> matchess = [];
if (dom.querySelectorAll(' #select-paged ').isNotEmpty) {
final ee = dom
.querySelectorAll(' #select-paged ')
.map((e) => e.outerHtml)
.toList();
final RegExp regexx = RegExp(r'value="([^"]+)"');
matchess = regexx.allMatches(ee.first);
}
final List<String?> urlss = matchess.map((m) => m.group(1)).toList();
if (urls.isNotEmpty && urls.length == 1) {
for (var i = 0; i < urlss.length; i++) {
if (urlss[i]!.length == 1) {
pageUrls.add(
urls.first!.replaceAll("001", '00${int.parse(urlss[i]!) + 1}'));
} else if (urlss[i]!.length == 2) {
pageUrls.add(
urls.first!.replaceAll("001", '0${int.parse(urlss[i]!) + 1}'));
} else if (urlss[i]!.length == 3) {
pageUrls.add(
urls.first!.replaceAll("001", '${int.parse(urlss[i]!) + 1}'));
}
}
} else if (urls.length > 1 && urls.isNotEmpty) {
for (var tt in urls) {
pageUrls.add(tt!);
}
}
}
return pageUrls;
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
source = source.toLowerCase();
final dom = await ref.watch(httpGetProvider(
useUserAgent: true,
url:
'${getMangaBaseUrl(source)}/manga/?title=&page=$page&order=update',
source: source,
resDom: true)
.future) as Document?;
if (dom!
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx')
.isNotEmpty) {
url = dom
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx')
.map((e) {
RegExp exp = RegExp(r'href="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.innerHtml);
String? firstMatch = matches.first.group(1);
return firstMatch;
}).toList();
image = dom
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx')
.map((e) {
RegExp exp = RegExp(r'src="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.innerHtml);
String? firstMatch = matches.first.group(1);
return firstMatch;
}).toList();
name = dom
.querySelectorAll(
'.utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx ')
.map((e) {
RegExp exp = RegExp(r'title="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.innerHtml);
String? firstMatch = matches.first.group(1);
return firstMatch;
}).toList();
}
return mangaRes();
}
}
mangathemesiaStatusParser(String status) {
return (switch (status) {
"ongoing" => Status.ongoing,
"publishing" => Status.ongoing,
"hiatus" => Status.onHiatus,
"completed" => Status.completed,
_ => Status.unknown,
});
}

View file

@ -1,61 +0,0 @@
import 'package:mangayomi/models/source.dart';
List<Source> get mmrcmsSourcesList => _mmrcmsSourcesList;
List<Source> _mmrcmsSourcesList = [
// Source(
// sourceName: "Fallen Angels",
// baseUrl: "https://manga.fascans.com",
// lang: "en",
// typeSource: TypeSource.mmrcms,
// logoUrl: '',
// dateFormat: "d MMM. yyyy",
// dateFormatLocale: "en_US"),
Source(
sourceName: "Scan FR",
baseUrl: "https://www.scan-fr.org",
lang: "fr",
typeSource: TypeSource.mmrcms,
logoUrl: '',
dateFormat: "d MMM. yyyy",
dateFormatLocale: "en_US"),
Source(
sourceName: "Scan VF",
baseUrl: "https://www.scan-vf.net",
lang: "fr",
typeSource: TypeSource.mmrcms,
logoUrl: '',
dateFormat: "d MMM. yyyy",
dateFormatLocale: "en_US"), //
Source(
sourceName: "Komikid",
baseUrl: "https://www.komikid.com",
lang: "id",
typeSource: TypeSource.mmrcms,
logoUrl: '',
dateFormat: "d MMM. yyyy",
dateFormatLocale: "en_US"),
// Source(
// sourceName: "MangaHanta",
// baseUrl: "http://mangahanta.com",
// lang: "tr",
// typeSource: TypeSource.mmrcms,
// logoUrl: '',
// dateFormat: "d MMM. yyyy",
// dateFormatLocale: "en_US"),
Source(
sourceName: "MangaID",
baseUrl: "https://mangaid.click",
lang: "id",
typeSource: TypeSource.mmrcms,
logoUrl: '',
dateFormat: "d MMM. yyyy",
dateFormatLocale: "en_US"),
Source(
sourceName: "Jpmangas",
baseUrl: "https://jpmangas.cc",
lang: "fr",
typeSource: TypeSource.mmrcms,
logoUrl: '',
dateFormat: "d MMM. yyyy",
dateFormatLocale: "en_US"),
];

View file

@ -1,222 +0,0 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:html/dom.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/multisrc/mmrcms/src/utils.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
class Mmrcms extends MangaYomiServices {
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(
httpGetProvider(url: manga.url!, source: source, resDom: true)
.future) as Document?;
description = dom!
.querySelectorAll('.row .well p')
.map((e) => e.text.trim())
.toList()
.first;
status = mmrcmsStatusParser(dom
.querySelectorAll('.row .dl-horizontal dt')
.where((e) =>
e.innerHtml.toString().toLowerCase().contains("status") ||
e.innerHtml.toString().toLowerCase().contains("statut") ||
e.innerHtml.toString().toLowerCase().contains("estado") ||
e.innerHtml.toString().toLowerCase().contains("durum"))
.map((e) => e.nextElementSibling!.text.trim())
.toList()
.first);
if (dom.querySelectorAll(".row .dl-horizontal dt").isNotEmpty) {
author = dom
.querySelectorAll('.row .dl-horizontal dt')
.where((e) =>
e.innerHtml.toString().toLowerCase().contains("auteur(s)") ||
e.innerHtml.toString().toLowerCase().contains("author(s)") ||
e.innerHtml.toString().toLowerCase().contains("autor(es)") ||
e.innerHtml.toString().toLowerCase().contains("yazar(lar)") ||
e.innerHtml.toString().toLowerCase().contains("mangaka(lar)") ||
e.innerHtml
.toString()
.toLowerCase()
.contains("pengarang/penulis") ||
e.innerHtml.toString().toLowerCase().contains("autor") ||
e.innerHtml.toString().toLowerCase().contains("penulis"))
.map((e) => e.nextElementSibling!.text
.trim()
.replaceAll(RegExp(r"\s+\b|\b\s"), ""))
.toList()
.first;
final genr = dom
.querySelectorAll('.row .dl-horizontal dt')
.where((e) =>
e.innerHtml.toString().toLowerCase().contains("categories") ||
e.innerHtml.toString().toLowerCase().contains("categorías") ||
e.innerHtml.toString().toLowerCase().contains("catégories") ||
e.innerHtml.toString().toLowerCase().contains("kategoriler") ||
e.innerHtml.toString().toLowerCase().contains("categorias") ||
e.innerHtml.toString().toLowerCase().contains("kategorie") ||
e.innerHtml.toString().toLowerCase().contains("kategori") ||
e.innerHtml.toString().toLowerCase().contains("tagi"))
.map((e) => e.nextElementSibling!.text.trim())
.toList();
if (genr.isNotEmpty) {
genre = genr.first.replaceAll(RegExp(r"\s+\b|\b\s"), "").split(',');
}
}
final rrr = dom.querySelectorAll(".row [class^=img-responsive]");
final data = rrr.map((e) => e.outerHtml).toList();
if (source.toLowerCase() == 'jpmangas' ||
source.toLowerCase() == 'fr scan') {
manga.imageUrl = regSrcMatcher(data.first).replaceAll('//', 'https://');
} else {
manga.imageUrl = regSrcMatcher(data.first);
}
final ttt = dom
.querySelectorAll("ul[class^=chapters] > li:not(.btn), table.table tr");
if (ttt.isNotEmpty) {
final data = ttt
.map((e) => e.querySelector("[class^=chapter-title-rtl]")!)
.toList();
var name = data;
for (var iaa in name) {
chapterTitle.add(iaa.getElementsByTagName("a").first.text);
chapterUrl
.add(regHrefMatcher(iaa.getElementsByTagName("a").first.outerHtml));
}
final date = ttt
.map((e) => e
.getElementsByClassName("date-chapter-title-rtl")
.map((e) => e.text.trim())
.first)
.toList();
for (var da in date) {
chapterDate.add(parseDate(da, source));
}
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga?>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url:
'${getMangaBaseUrl(source)}/filterList?page=$page&sortBy=views&asc=false',
source: source,
resDom: true)
.future) as Document?;
final urlElement = dom!.getElementsByClassName('chart-title');
for (var e in urlElement) {
RegExp exp = RegExp(r'href="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.outerHtml);
String? firstMatch = matches.first.group(1);
url.add(firstMatch);
name.add(e.text);
}
final imgElement = dom.getElementsByTagName('img');
for (var e in imgElement) {
RegExp exp = RegExp(r'src="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.outerHtml);
String? firstMatch = matches.first.group(1);
image.add(firstMatch);
}
return mangaRes();
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
url: '${getMangaBaseUrl(source)}/search?query=${query.trim()}',
source: source,
resDom: false)
.future) as String?;
final rep = jsonDecode(response!);
for (var ok in rep['suggestions']) {
if (source == 'Read Comics Online') {
url.add('${getMangaBaseUrl(source)}/comic/${ok['data']}');
} else if (source == 'Scan VF') {
url.add('${getMangaBaseUrl(source)}/${ok['data']}');
} else {
url.add('${getMangaBaseUrl(source)}/manga/${ok['data']}');
}
name.add(ok["value"]);
image.add('');
}
return mangaRes();
}
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await await ref.watch(httpGetProvider(
useUserAgent: true,
url: chapter.url!,
source: chapter.manga.value!.source!.toLowerCase(),
resDom: true)
.future) as Document?;
if (dom!.querySelectorAll('#all > .img-responsive').isNotEmpty) {
pageUrls = dom.querySelectorAll('#all > .img-responsive').map((e) {
final RegExp regexx = RegExp(r'data-src="([^"]+)"');
if (chapter.manga.value!.source!.toLowerCase() == 'fr scan' ||
chapter.manga.value!.source!.toLowerCase() == 'jpmangas') {
return regexx
.allMatches(e.outerHtml)
.first
.group(1)!
.replaceAll('//', 'https://')
.replaceAll(RegExp(r"\s+\b|\b\s"), "")
.replaceAll("https:https://", "https://");
}
return regexx
.allMatches(e.outerHtml)
.first
.group(1)!
.replaceAll(RegExp(r"\s+\b|\b\s"), "")
.replaceAll("https:https://", "https://");
}).toList();
}
return pageUrls;
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: '${getMangaBaseUrl(source)}/latest-release?page=$page',
source: source,
resDom: true)
.future) as Document?;
final urlElement = dom!.querySelectorAll("div.mangalist div.manga-item");
for (var e in urlElement) {
RegExp exp = RegExp(r'href="([^"]+)"');
Iterable<Match> matches = exp.allMatches(e.querySelector("a")!.outerHtml);
String? firstMatch = matches.first.group(1);
url.add(firstMatch);
name.add(e.querySelector("a")!.text);
image.add(
"${getMangaBaseUrl(source)}/uploads/manga/${firstMatch!.split('/').last}/cover/cover_250x350.jpg");
}
return mangaRes();
}
}

View file

@ -1,27 +0,0 @@
import 'package:mangayomi/models/manga.dart';
Status mmrcmsStatusParser(String status) {
return completedStatusList.contains(status)
? Status.completed
: ongoingStatusList.contains(status)
? Status.ongoing
: Status.unknown;
}
List<String> completedStatusList = [
"complete",
"مكتملة",
"complet",
"completo",
"zakończone",
"concluído"
];
List<String> ongoingStatusList = [
"ongoing",
"مستمرة",
"en cours",
"em lançamento",
"prace w toku",
"ativo",
"em andamento"
];

View file

@ -1,111 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
abstract class MangaYomiServices {
List<String?> url = [];
List<String?> name = [];
List<String?> image = [];
List<String> genre = [];
String? author = "";
Status? status;
List<Status> statusList = [];
List<String> chapterTitle = [];
List<String> chapterUrl = [];
List<String> chapterDate = [];
String? description = "";
List<Chapter> chapters = [];
List<String> scanlators = [];
List<String> pageUrls = [];
List<GetManga> mangaList = [];
List<GetManga> mangaRes() {
for (var i = 0; i < name.length; i++) {
mangaList.add(GetManga(
genre: genre,
author: author,
status: statusList.isEmpty ? Status.unknown : statusList[i],
chapters: chapters,
imageUrl: image[i],
description: description,
url: url[i],
name: name[i],
source: ""));
}
return mangaList;
}
GetManga mangadetailRes({required GetManga manga, required String source}) {
if (chapterDate.isNotEmpty &&
chapterTitle.isNotEmpty &&
chapterUrl.isNotEmpty) {
for (var i = 0; i < chapterUrl.length; i++) {
chapters.add(Chapter(
name: chapterTitle[i],
url: chapterUrl[i],
dateUpload: chapterDate[i],
isBookmarked: false,
scanlator: scanlators.isEmpty ? "" : scanlators[i],
isRead: false,
lastPageRead: '',
));
}
}
return GetManga(
status: status,
genre: genre,
author: author,
description: description,
name: manga.name,
url: manga.url,
source: source,
imageUrl: manga.imageUrl,
chapters: chapters,
);
}
Future<List<GetManga?>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref});
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref});
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref});
Future<List<String>?> getChapterUrl(
{required Chapter chapter, required AutoDisposeFutureProviderRef ref});
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref});
}
class GetManga {
List<String> genre = [];
List<Chapter> chapters = [];
String? author;
Status? status;
String? source;
String? url;
String? name;
String? imageUrl;
String? description;
GetManga({
required this.genre,
required this.author,
required this.status,
required this.chapters,
required this.imageUrl,
required this.description,
required this.url,
required this.name,
required this.source,
});
}

View file

@ -1,22 +0,0 @@
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/sources/multisrc/heancms/heancms_source_list.dart';
import 'package:mangayomi/sources/src/all/comick/comick_source_list.dart';
import 'package:mangayomi/sources/src/all/mangadex/mangadex_source_list.dart';
import 'package:mangayomi/sources/src/en/mangahere/mangahere_source.dart';
import 'package:mangayomi/sources/src/fr/japscan/japscan_source.dart';
import 'package:mangayomi/sources/multisrc/mangathemesia/mangathemesia_source_list.dart';
import 'package:mangayomi/sources/multisrc/mmrcms/mmrcms_source_list.dart';
import 'package:mangayomi/sources/multisrc/madara/madara_source_list.dart';
// import 'package:mangayomi/sources/src/fr/mangakawaii/mangakawaii_source.dart';
List<Source> get sourcesList => _sourcesList;
List<Source> _sourcesList = [
mangahereSource,
// mangakawaiiSource,
...mangathemesiaSourcesList,
...comickSourcesList,
...mmrcmsSourcesList,
japscanSource,
...heanCmsSourcesList,
...madaraSourcesList,
...mangaDexSourcesList
];

View file

@ -1,180 +0,0 @@
import 'package:mangayomi/models/source.dart';
const logoUrl =
'https://comick.app/_next/image?url=%2Fstatic%2Ficons%2Funicorn-64.png&w=144&q=75';
const apiUrl = 'https://api.comick.fun';
const baseUrl = 'https://comick.app';
const isNsfw = true;
List<Source> get comickSourcesList => _comickSourcesList;
List<Source> _comickSourcesList = [
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'en',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'ar',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'pt',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'pt-br',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'it',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'ru',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'es',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'es-419',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'id',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'hi',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'de',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'ja',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'tr',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'pl',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'zh',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'zh-hk',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
Source(
sourceName: 'Comick',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'fr',
typeSource: TypeSource.comick,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
isNsfw: isNsfw,
dateFormatLocale: "en"),
];

View file

@ -1,171 +0,0 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/sources/src/all/comick/src/model/chapter_page_comick.dart';
import 'package:mangayomi/sources/src/all/comick/src/model/manga_chapter_detail.dart';
import 'package:mangayomi/sources/src/all/comick/src/model/manga_detail_comick.dart';
import 'package:mangayomi/sources/src/all/comick/src/model/popular_manga_comick.dart';
import 'package:mangayomi/sources/src/all/comick/src/model/search_manga_cimick.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/src/all/comick/src/utils/utils.dart';
import 'package:mangayomi/sources/utils/utils.dart';
class Comick extends MangaYomiServices {
@override
Future<List<GetManga?>> getPopularManga(
{required String source,
required String lang,
required int page,
required AutoDisposeFutureProviderRef ref}) async {
source = source.toLowerCase();
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/v1.0/search?sort=follow&page=$page&tachiyomi=true',
source: source,
resDom: false)
.future) as String?;
var popularManga = jsonDecode(response!) as List;
var popularMangaList =
popularManga.map((e) => PopularMangaModelComick.fromJson(e)).toList();
for (var popular in popularMangaList) {
url.add("/comic/${popular.hid}#");
name.add(popular.title);
image.add(popular.coverUrl);
}
return mangaRes();
}
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}${manga.url!.replaceAll("#", '')}?tachiyomi=true',
source: source,
resDom: false)
.future) as String?;
var mangaDetail = jsonDecode(response!) as Map<String, dynamic>;
var mangaDetailLMap = MangaDetailModelComick.fromJson(mangaDetail);
RegExp regExp = RegExp(r'name:\s*(.*?),');
String authorr =
regExp.firstMatch(mangaDetailLMap.authors![0].toString())?.group(1) ??
'';
status = parseStatut(mangaDetailLMap.comic!.status!);
author = authorr;
RegExp regExp1 = RegExp(r'name:\s*(.*?)}');
for (var ok in mangaDetailLMap.genres!) {
genre.add(regExp1.firstMatch(ok.toString())!.group(1)!);
}
description = mangaDetailLMap.comic!.desc;
final request = await paginatedChapterListRequest(
ref, manga.url!.replaceAll("#", ''), 1, source, lang);
final chapterListResponse =
MangaChapterModelComick.fromJson(json.decode(request));
final mangaUrl = request.substring(request.indexOf(getMangaAPIUrl(source)) +
getMangaAPIUrl(source).length);
var resultSize = chapterListResponse.chapters!.length;
var page = 2;
while (chapterListResponse.total! > resultSize) {
final newResponse = await paginatedChapterListRequest(
ref, manga.url!.replaceAll("#", ''), page, source, lang);
final newChapterListResponse =
MangaChapterModelComick.fromJson(json.decode(newResponse));
chapterListResponse.chapters!.addAll(newChapterListResponse.chapters!);
resultSize += newChapterListResponse.chapters!.length;
page += 1;
}
for (var chapter in chapterListResponse.chapters!) {
scanlators.add(chapter.groupName!.isNotEmpty
? chapter.groupName!.join()
: "Unknown");
chapterUrl.add("$mangaUrl/${chapter.hid}-chapter-${chapter.chap}-$lang");
chapterDate.add(parseDate(chapter.createdAt!, source));
chapterTitle.add(beautifyChapterName(
chapter.vol ?? "", chapter.chap ?? "", chapter.title ?? "", lang));
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/v1.0/search?q=${query.trim()}&tachiyomi=true&limit=50&page=1',
source: source,
resDom: false)
.future) as String?;
var search = jsonDecode(response!) as List;
var searchList =
search.map((e) => MangaSearchModelComick.fromJson(e)).toList();
for (var search in searchList) {
url.add("/comic/${search.hid}#");
name.add(search.title);
image.add(search.coverUrl);
}
return mangaRes();
}
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
String mangaHid = chapter.url!.split('/').last.split('-').first;
String source = chapter.manga.value!.source!;
final response = await ref.watch(httpGetProvider(
url: '${getMangaAPIUrl(source)}/chapter/$mangaHid?tachiyomi=true',
source: source,
resDom: false)
.future) as String?;
var data = jsonDecode(response!) as Map<String, dynamic>;
var page = ChapterPageComick.fromJson(data);
for (var url in page.chapter!.images!) {
pageUrls.add(url.url!);
}
return pageUrls;
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
source = source.toLowerCase();
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/v1.0/search?sort=uploaded&page=$page&tachiyomi=true',
source: source,
resDom: false)
.future) as String?;
var popularManga = jsonDecode(response!) as List;
var popularMangaList =
popularManga.map((e) => PopularMangaModelComick.fromJson(e)).toList();
for (var popular in popularMangaList) {
url.add("/comic/${popular.hid}#");
name.add(popular.title);
image.add(popular.coverUrl);
}
return mangaRes();
}
}

View file

@ -1,141 +0,0 @@
class ChapterPageComick {
ChapterC? chapter;
bool? matureContent;
List<Chapters>? chapters;
String? canonical;
String? seoTitle;
String? seoDescription;
String? chapTitle;
bool? checkVol2Chap1;
ChapterPageComick(
{this.chapter,
this.matureContent,
this.chapters,
this.canonical,
this.seoTitle,
this.seoDescription,
this.chapTitle,
this.checkVol2Chap1});
ChapterPageComick.fromJson(Map<String, dynamic> json) {
chapter =
json['chapter'] != null ? ChapterC.fromJson(json['chapter']) : null;
matureContent = json['matureContent'];
if (json['chapters'] != null) {
chapters = <Chapters>[];
json['chapters'].forEach((v) {
chapters!.add(Chapters.fromJson(v));
});
}
canonical = json['canonical'];
seoTitle = json['seoTitle'];
seoDescription = json['seoDescription'];
chapTitle = json['chapTitle'];
checkVol2Chap1 = json['checkVol2Chap1'];
}
}
class ChapterC {
int? id;
String? chap;
String? title;
String? server;
String? hid;
String? hash;
List<String>? groupName;
String? createdAt;
String? mdid;
int? commentCount;
int? upCount;
int? downCount;
String? status;
String? lang;
List<Images>? images;
ChapterC(
{this.id,
this.chap,
this.title,
this.server,
this.hid,
this.hash,
this.groupName,
this.createdAt,
this.mdid,
this.commentCount,
this.upCount,
this.downCount,
this.status,
this.lang,
this.images});
ChapterC.fromJson(Map<String, dynamic> json) {
id = json['id'];
chap = json['chap'];
title = json['title'];
server = json['server'];
hid = json['hid'];
hash = json['hash'];
createdAt = json['created_at'];
mdid = json['mdid'];
commentCount = json['comment_count'];
upCount = json['up_count'];
downCount = json['down_count'];
status = json['status'];
lang = json['lang'];
if (json['images'] != null) {
images = <Images>[];
json['images'].forEach((v) {
images!.add(Images.fromJson(v));
});
}
}
}
class Images {
String? url;
int? w;
int? h;
Images({this.url, this.w, this.h});
Images.fromJson(Map<String, dynamic> json) {
url = json['url'];
w = json['w'];
h = json['h'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['url'] = url;
data['w'] = w;
data['h'] = h;
return data;
}
}
class Chapters {
String? chap;
String? vol;
String? hid;
String? lang;
int? id;
String? title;
Chapters({this.chap, this.vol, this.hid, this.lang, this.id, this.title});
Chapters.fromJson(Map<String, dynamic> json) {
chap = json['chap'];
vol = json['vol'];
hid = json['hid'];
lang = json['lang'];
id = json['id'];
title = json['title'];
}
}

View file

@ -1,80 +0,0 @@
class MangaChapterModelComick {
List<Chapters>? chapters;
int? total;
MangaChapterModelComick({this.chapters, this.total});
MangaChapterModelComick.fromJson(Map<String, dynamic> json) {
if (json['chapters'] != null) {
chapters = <Chapters>[];
json['chapters'].forEach((v) {
chapters!.add(Chapters.fromJson(v));
});
}
total = json['total'];
}
}
class Chapters {
int? id;
String? chap;
String? title;
String? vol;
String? slug;
String? lang;
String? createdAt;
String? updatedAt;
int? upCount;
int? downCount;
List<dynamic>? groupName;
String? hid;
List<MdGroups>? mdGroups;
Chapters(
{this.id,
this.chap,
this.title,
this.vol,
this.slug,
this.lang,
this.createdAt,
this.updatedAt,
this.upCount,
this.downCount,
this.groupName,
this.hid,
this.mdGroups});
Chapters.fromJson(Map<String, dynamic> json) {
id = json['id'];
chap = json['chap'];
title = json['title'];
vol = json['vol'];
slug = json['slug'];
lang = json['lang'];
createdAt = json['created_at'];
// updatedAt = json['updated_at'];
// upCount = json['up_count'];
// downCount = json['down_count'];
groupName = json['group_name'] ?? [];
hid = json['hid'];
// if (json['md_groups'] != null) {
// mdGroups = <MdGroups>[];
// json['md_groups'].forEach((v) {
// mdGroups!.add(MdGroups.fromJson(v));
// });
// }
}
}
class MdGroups {
String? slug;
String? title;
MdGroups({this.slug, this.title});
MdGroups.fromJson(Map<String, dynamic> json) {
slug = json['slug'];
title = json['title'];
}
}

View file

@ -1,163 +0,0 @@
class MangaDetailModelComick {
FirstChap? firstChap;
Comic? comic;
List? artists;
List? authors;
List<String>? langList;
List? genres;
bool? matureContent;
bool? checkVol2Chap1;
MangaDetailModelComick(
{this.firstChap,
this.comic,
this.artists,
this.authors,
this.langList,
this.genres,
this.matureContent,
this.checkVol2Chap1});
MangaDetailModelComick.fromJson(Map<String, dynamic> json) {
firstChap = json['firstChap'] != null
? FirstChap.fromJson(json['firstChap'])
: null;
comic = json['comic'] != null ? Comic.fromJson(json['comic']) : null;
if (json['artists'] != null) {
artists = json['artists'];
}
if (json['authors'] != null) {
authors = json['authors'];
}
if (json['genres'] != null) {
genres = json['genres'];
}
matureContent = json['matureContent'];
checkVol2Chap1 = json['checkVol2Chap1'];
}
}
class FirstChap {
String? chap;
String? hid;
String? lang;
List<String>? groupName;
FirstChap({
this.chap,
this.hid,
this.lang,
this.groupName,
});
FirstChap.fromJson(Map<String, dynamic> json) {
chap = json['chap'];
hid = json['hid'];
lang = json['lang'];
}
}
class Comic {
int? id;
String? hid;
String? title;
String? country;
int? status;
double? lastChapter;
int? chapterCount;
int? demographic;
bool? hentai;
int? userFollowCount;
int? followRank;
int? commentCount;
int? followCount;
String? desc;
String? parsed;
String? slug;
int? year;
String? bayesianRating;
int? ratingCount;
String? contentRating;
bool? translationCompleted;
bool? chapterNumbersResetOnNewVolumeManual;
String? iso6391;
String? langName;
String? langNative;
String? coverUrl;
Comic(
{this.id,
this.hid,
this.title,
this.country,
this.status,
this.lastChapter,
this.chapterCount,
this.demographic,
this.hentai,
this.userFollowCount,
this.followRank,
this.commentCount,
this.followCount,
this.desc,
this.parsed,
this.slug,
this.year,
this.bayesianRating,
this.ratingCount,
this.contentRating,
this.translationCompleted,
this.chapterNumbersResetOnNewVolumeManual,
this.iso6391,
this.langName,
this.langNative,
this.coverUrl});
Comic.fromJson(Map<String, dynamic> json) {
id = json['id'];
hid = json['hid'];
title = json['title'];
country = json['country'];
status = json['status'];
// lastChapter = double.parse(json['last_chapter'].toString());
chapterCount = json['chapter_count'];
demographic = json['demographic'];
hentai = json['hentai'];
userFollowCount = json['user_follow_count'];
followRank = json['follow_rank'];
commentCount = json['comment_count'];
followCount = json['follow_count'];
desc = json['desc'];
// parsed = json['parsed'];
slug = json['slug'];
// year = json['year'];
// bayesianRating = json['bayesian_rating'];
// ratingCount = json['rating_count'];
// contentRating = json['content_rating'];
// translationCompleted = json['translation_completed'];
// chapterNumbersResetOnNewVolumeManual =
// json['chapter_numbers_reset_on_new_volume_manual'];
// iso6391 = json['iso639_1'];
// langName = json['lang_name'];
// langNative = json['lang_native'];
coverUrl = json['cover_url'];
}
}
class Artists {
String? name;
String? slug;
Artists({this.name, this.slug});
Artists.fromJson(Map<String, dynamic> json) {
name = json['name'];
slug = json['slug'];
}
}

View file

@ -1,129 +0,0 @@
class PopularMangaModelComick {
int? id;
String? hid;
String? slug;
String? title;
String? rating;
String? bayesianRating;
int? ratingCount;
int? followCount;
String? desc;
double? lastChapter;
bool? translationCompleted;
int? viewCount;
String? contentRating;
int? demographic;
List<int>? genres;
int? userFollowCount;
int? year;
List<MdTitles>? mdTitles;
List<MdCovers>? mdCovers;
MuComics? muComics;
String? coverUrl;
PopularMangaModelComick(
{this.id,
this.hid,
this.slug,
this.title,
this.rating,
this.bayesianRating,
this.ratingCount,
this.followCount,
this.desc,
this.lastChapter,
this.translationCompleted,
this.viewCount,
this.contentRating,
this.demographic,
this.genres,
this.userFollowCount,
this.year,
this.mdTitles,
this.mdCovers,
this.muComics,
this.coverUrl});
PopularMangaModelComick.fromJson(Map<String, dynamic> json) {
id = json['id'];
hid = json['hid'];
slug = json['slug'];
title = json['title'];
rating = json['rating'];
bayesianRating = json['bayesian_rating'];
ratingCount = json['rating_count'];
followCount = json['follow_count'];
desc = json['desc'];
lastChapter = double.parse(json['last_chapter'].toString());
translationCompleted = json['translation_completed'];
viewCount = json['view_count'];
contentRating = json['content_rating'];
demographic = json['demographic'];
genres = json['genres'].cast<int>();
userFollowCount = json['user_follow_count'];
year = json['year'];
if (json['md_titles'] != null) {
mdTitles = <MdTitles>[];
json['md_titles'].forEach((v) {
mdTitles!.add(MdTitles.fromJson(v));
});
}
if (json['md_covers'] != null) {
mdCovers = <MdCovers>[];
json['md_covers'].forEach((v) {
mdCovers!.add(MdCovers.fromJson(v));
});
}
muComics =
json['mu_comics'] != null ? MuComics.fromJson(json['mu_comics']) : null;
coverUrl = json['cover_url'];
}
}
class MdTitles {
String? title;
MdTitles({this.title});
MdTitles.fromJson(Map<String, dynamic> json) {
title = json['title'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['title'] = title;
return data;
}
}
class MdCovers {
int? w;
int? h;
String? b2key;
MdCovers({this.w, this.h, this.b2key});
MdCovers.fromJson(Map<String, dynamic> json) {
w = json['w'];
h = json['h'];
b2key = json['b2key'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['w'] = w;
data['h'] = h;
data['b2key'] = b2key;
return data;
}
}
class MuComics {
int? year;
MuComics({this.year});
MuComics.fromJson(Map<String, dynamic> json) {
year = json['year'];
}
}

View file

@ -1,55 +0,0 @@
class MangaSearchModelComick {
String? title;
String? hid;
int? id;
String? slug;
String? rating;
int? ratingCount;
int? followCount;
int? userFollowCount;
String? contentRating;
int? demographic;
List<MdCovers>? mdCovers;
String? highlight;
String? coverUrl;
MangaSearchModelComick(
{this.title,
this.hid,
this.id,
this.slug,
this.rating,
this.ratingCount,
this.followCount,
this.userFollowCount,
this.contentRating,
this.demographic,
this.mdCovers,
this.highlight,
this.coverUrl});
MangaSearchModelComick.fromJson(Map<String, dynamic> json) {
title = json['title'];
slug = json['slug'];
hid = json['hid'];
coverUrl = json['cover_url'];
}
}
class MdCovers {
String? vol;
int? w;
int? h;
String? b2key;
MdCovers({this.vol, this.w, this.h, this.b2key});
MdCovers.fromJson(Map<String, dynamic> json) {
vol = json['vol'];
w = json['w'];
h = json['h'];
b2key = json['b2key'];
}
}

View file

@ -1,29 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/utils/utils.dart';
parseStatut(int i) {
return switch (i) {
1 => Status.ongoing,
2 => Status.completed,
3 => Status.canceled,
4 => Status.onHiatus,
_ => Status.unknown,
};
}
beautifyChapterName(String? vol, String? chap, String? title, String? lang) {
return "${vol!.isNotEmpty ? chap!.isEmpty ? "Volume $vol " : "Vol. $vol " : ""}${chap!.isNotEmpty ? vol.isEmpty ? lang == "fr" ? "Chapitre $chap" : "Chapter $chap" : "Ch. $chap " : ""}${title!.isNotEmpty ? chap.isEmpty ? title : " : $title" : ""}";
}
Future<String> paginatedChapterListRequest(AutoDisposeFutureProviderRef ref,
String mangaUrl, int page, String source, String lang) async {
final response = await ref.watch(httpGetProvider(
url:
"${getMangaAPIUrl(source)}$mangaUrl/chapters?${lang != "all" ? 'lang=$lang' : ''}&tachiyomi=true&page=$page",
source: source,
resDom: false)
.future) as String?;
return response!;
}

View file

@ -1,179 +0,0 @@
import 'package:mangayomi/models/source.dart';
const logoUrl = '';
const apiUrl = 'https://api.mangadex.org';
const baseUrl = 'https://mangadex.org';
const isNsfw = true;
List<Source> get mangaDexSourcesList => _mangaDexSourcesList;
List<Source> _mangaDexSourcesList = [
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'en',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'ar',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'pt',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'pt-br',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'it',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'ru',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'es',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'es-419',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'id',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'hi',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'de',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'ja',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'tr',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'pl',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'zh',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'zh-hk',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
Source(
sourceName: 'MangaDex',
apiUrl: apiUrl,
baseUrl: baseUrl,
lang: 'fr',
typeSource: TypeSource.mangadex,
logoUrl: logoUrl,
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
isNsfw: isNsfw,
dateFormatLocale: 'en_Us'),
];

View file

@ -1,52 +0,0 @@
class Aggregate {
String? result;
Map<String, AggregateVolumes>? volumes;
Aggregate({this.result, this.volumes});
Aggregate.fromJson(Map<String, dynamic> json) {
result = json['result'];
if (json['volumes'] is List<dynamic>) {
volumes = (json['volumes'] as List<dynamic>).asMap().map((key, value) =>
MapEntry(key.toString(),
AggregateVolumes.fromJson(value as Map<String, dynamic>)));
} else {
volumes = (json['volumes'] as Map<String, dynamic>?)?.map((key, value) =>
MapEntry(
key, AggregateVolumes.fromJson(value as Map<String, dynamic>)));
}
}
}
class AggregateVolumes {
String? volume;
String? count;
Map<String, AggregateChapter>? chapters;
AggregateVolumes({this.volume, this.chapters, this.count});
AggregateVolumes.fromJson(Map<String, dynamic> json) {
volume = json['volume'];
count = json['count'].toString();
if (json['chapters'] is List<dynamic>) {
chapters = (json['chapters'] as List<dynamic>).asMap().map((key, value) =>
MapEntry(key.toString(),
AggregateChapter.fromJson(value as Map<String, dynamic>)));
} else {
chapters = (json['chapters'] as Map<String, dynamic>?)?.map(
(key, value) => MapEntry(
key, AggregateChapter.fromJson(value as Map<String, dynamic>)));
}
}
}
class AggregateChapter {
String? chapter;
String? count;
AggregateChapter({this.chapter, this.count});
AggregateChapter.fromJson(Map<String, dynamic> json) {
chapter = json['chapter'];
count = json['count'].toString();
}
}

View file

@ -1,123 +0,0 @@
class ChapterMDX {
String? result;
String? response;
List<Data>? data;
int? limit;
int? offset;
int? total;
ChapterMDX(
{this.result,
this.response,
this.data,
this.limit,
this.offset,
this.total});
ChapterMDX.fromJson(Map<String, dynamic> json) {
result = json['result'];
response = json['response'];
if (json['data'] != null) {
data = <Data>[];
json['data'].forEach((v) {
data!.add(Data.fromJson(v));
});
}
limit = json['limit'];
offset = json['offset'];
total = json['total'];
}
}
class Data {
String? id;
String? type;
Attributes? attributes;
List<Relationships>? relationships;
Data({this.id, this.type, this.attributes, this.relationships});
Data.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
attributes = json['attributes'] != null
? Attributes.fromJson(json['attributes'])
: null;
if (json['relationships'] != null) {
relationships = <Relationships>[];
json['relationships'].forEach((v) {
relationships!.add(Relationships.fromJson(v));
});
}
}
}
class Attributes {
String? volume;
String? chapter;
String? title;
String? translatedLanguage;
String? publishAt;
String? readableAt;
String? createdAt;
String? updatedAt;
int? pages;
int? version;
Attributes(
{this.volume,
this.chapter,
this.title,
this.translatedLanguage,
this.publishAt,
this.readableAt,
this.createdAt,
this.updatedAt,
this.pages,
this.version});
Attributes.fromJson(Map<String, dynamic> json) {
volume = json['volume'];
chapter = json['chapter'];
title = json['title'];
translatedLanguage = json['translatedLanguage'];
publishAt = json['publishAt'];
readableAt = json['readableAt'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
pages = json['pages'];
version = json['version'];
}
}
class Relationships {
String? id;
String? type;
RAttributes? attributes;
Relationships({this.id, this.type, this.attributes});
Relationships.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
// log(json['attributes'].toString());
attributes = json['attributes'] != null
? RAttributes.fromJson(json['attributes'])
: null;
}
}
class RAttributes {
String? name;
String? username;
RAttributes({
this.name,
this.username,
});
RAttributes.fromJson(Map<String, dynamic> json) {
name = json['name'];
username = json['username'];
}
}

View file

@ -1,140 +0,0 @@
class CoverAA {
String? result;
String? response;
List<CoverData>? data;
int? limit;
int? offset;
int? total;
CoverAA(
{this.result,
this.response,
this.data,
this.limit,
this.offset,
this.total});
CoverAA.fromJson(Map<String, dynamic> json) {
result = json['result'];
response = json['response'];
if (json['data'] != null) {
data = <CoverData>[];
json['data'].forEach((v) {
data!.add(CoverData.fromJson(v));
});
}
limit = json['limit'];
offset = json['offset'];
total = json['total'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['result'] = result;
data['response'] = response;
if (this.data != null) {
data['data'] = this.data!.map((v) => v.toJson()).toList();
}
data['limit'] = limit;
data['offset'] = offset;
data['total'] = total;
return data;
}
}
class CoverData {
String? id;
String? type;
Attributes? attributes;
List<Relationships>? relationships;
CoverData({this.id, this.type, this.attributes, this.relationships});
CoverData.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
attributes = json['attributes'] != null
? Attributes.fromJson(json['attributes'])
: null;
if (json['relationships'] != null) {
relationships = <Relationships>[];
json['relationships'].forEach((v) {
relationships!.add(Relationships.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['type'] = type;
if (attributes != null) {
data['attributes'] = attributes!.toJson();
}
if (relationships != null) {
data['relationships'] =
relationships!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Attributes {
String? description;
String? volume;
String? fileName;
String? locale;
String? createdAt;
String? updatedAt;
int? version;
Attributes(
{this.description,
this.volume,
this.fileName,
this.locale,
this.createdAt,
this.updatedAt,
this.version});
Attributes.fromJson(Map<String, dynamic> json) {
description = json['description'];
volume = json['volume'];
fileName = json['fileName'];
locale = json['locale'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
version = json['version'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['description'] = description;
data['volume'] = volume;
data['fileName'] = fileName;
data['locale'] = locale;
data['createdAt'] = createdAt;
data['updatedAt'] = updatedAt;
data['version'] = version;
return data;
}
}
class Relationships {
String? id;
String? type;
Relationships({this.id, this.type});
Relationships.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['type'] = type;
return data;
}
}

View file

@ -1,28 +0,0 @@
class GetChapterUrl {
String? result;
String? baseUrl;
Chapter? chapter;
GetChapterUrl({this.result, this.baseUrl, this.chapter});
GetChapterUrl.fromJson(Map<String, dynamic> json) {
result = json['result'];
baseUrl = json['baseUrl'];
chapter =
json['chapter'] != null ? Chapter.fromJson(json['chapter']) : null;
}
}
class Chapter {
String? hash;
List<String>? data;
List<String>? dataSaver;
Chapter({this.hash, this.data, this.dataSaver});
Chapter.fromJson(Map<String, dynamic> json) {
hash = json['hash'];
data = json['data'].cast<String>();
dataSaver = json['dataSaver'].cast<String>();
}
}

View file

@ -1,330 +0,0 @@
class MangaDexDto {
String? result;
String? response;
List<Data>? data;
int? limit;
int? offset;
int? total;
MangaDexDto(
{this.result,
this.response,
this.data,
this.limit,
this.offset,
this.total});
MangaDexDto.fromJson(Map<String, dynamic> json) {
result = json['result'];
response = json['response'];
if (json['data'] != null) {
data = <Data>[];
json['data'].forEach((v) {
data!.add(Data.fromJson(v));
});
}
limit = json['limit'];
offset = json['offset'];
total = json['total'];
}
}
class Data {
String? id;
String? type;
Attributes? attributes;
List<Relationships>? relationships;
Data({this.id, this.type, this.attributes, this.relationships});
Data.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
attributes = json['attributes'] != null
? Attributes.fromJson(json['attributes'])
: null;
if (json['relationships'] != null) {
relationships = <Relationships>[];
json['relationships'].forEach((v) {
relationships!.add(Relationships.fromJson(v));
});
}
}
}
class Attributes {
Map<String, dynamic>? title;
List<dynamic>? altTitles;
Description? description;
bool? isLocked;
Links? links;
String? originalLanguage;
String? lastVolume;
String? lastChapter;
String? publicationDemographic;
String? status;
int? year;
String? contentRating;
List<Tags>? tags;
String? state;
bool? chapterNumbersResetOnNewVolume;
String? createdAt;
String? updatedAt;
int? version;
List<String>? availableTranslatedLanguages;
String? latestUploadedChapter;
Attributes(
{this.title,
this.altTitles,
this.description,
this.isLocked,
this.links,
this.originalLanguage,
this.lastVolume,
this.lastChapter,
this.publicationDemographic,
this.status,
this.year,
this.contentRating,
this.tags,
this.state,
this.chapterNumbersResetOnNewVolume,
this.createdAt,
this.updatedAt,
this.version,
this.availableTranslatedLanguages,
this.latestUploadedChapter});
Attributes.fromJson(Map<String, dynamic> json) {
title = json['title'] as Map<String, dynamic>;
if (json['altTitles'] != null) {
altTitles = json['altTitles'];
}
description = json['description'] != null
? Description.fromJson(json['description'])
: null;
isLocked = json['isLocked'];
links = json['links'] != null ? Links.fromJson(json['links']) : null;
originalLanguage = json['originalLanguage'];
lastVolume = json['lastVolume'];
lastChapter = json['lastChapter'];
publicationDemographic = json['publicationDemographic'];
status = json['status'];
year = json['year'];
contentRating = json['contentRating'];
if (json['tags'] != null) {
tags = <Tags>[];
json['tags'].forEach((v) {
tags!.add(Tags.fromJson(v));
});
}
state = json['state'];
chapterNumbersResetOnNewVolume = json['chapterNumbersResetOnNewVolume'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
version = json['version'];
availableTranslatedLanguages =
json['availableTranslatedLanguages'].cast<String>();
latestUploadedChapter = json['latestUploadedChapter'];
}
}
class Description {
String? en;
String? fr;
String? pt;
String? ptBr;
String? zhHk;
String? ru;
String? es;
String? ja;
String? tr;
String? uk;
String? esLa;
String? ko;
String? de;
String? id;
String? it;
String? pl;
String? th;
String? zh;
String? cs;
Description(
{this.en,
this.fr,
this.pt,
this.ptBr,
this.zhHk,
this.ru,
this.es,
this.ja,
this.tr,
this.uk,
this.esLa,
this.ko,
this.de,
this.id,
this.it,
this.pl,
this.th,
this.zh,
this.cs});
Description.fromJson(Map<String, dynamic> json) {
en = json['en'];
fr = json['fr'];
pt = json['pt'];
ptBr = json['pt-br'];
zhHk = json['zh-hk'];
ru = json['ru'];
es = json['es'];
ja = json['ja'];
tr = json['tr'];
uk = json['uk'];
esLa = json['es-la'];
ko = json['ko'];
de = json['de'];
id = json['id'];
it = json['it'];
pl = json['pl'];
th = json['th'];
zh = json['zh'];
cs = json['cs'];
}
}
class Links {
String? al;
String? mu;
String? amz;
String? mal;
String? engtl;
String? kt;
String? cdj;
String? ebj;
String? raw;
String? ap;
String? bw;
String? nu;
Links(
{this.al,
this.mu,
this.amz,
this.mal,
this.engtl,
this.kt,
this.cdj,
this.ebj,
this.raw,
this.ap,
this.bw,
this.nu});
Links.fromJson(Map<String, dynamic> json) {
al = json['al'];
mu = json['mu'];
amz = json['amz'];
mal = json['mal'];
engtl = json['engtl'];
kt = json['kt'];
cdj = json['cdj'];
ebj = json['ebj'];
raw = json['raw'];
ap = json['ap'];
bw = json['bw'];
nu = json['nu'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['al'] = al;
data['mu'] = mu;
data['amz'] = amz;
data['mal'] = mal;
data['engtl'] = engtl;
data['kt'] = kt;
data['cdj'] = cdj;
data['ebj'] = ebj;
data['raw'] = raw;
data['ap'] = ap;
data['bw'] = bw;
data['nu'] = nu;
return data;
}
}
class Tags {
TagAttributes? attributes;
Tags({
this.attributes,
});
Tags.fromJson(Map<String, dynamic> json) {
attributes = json['attributes'] != null
? TagAttributes.fromJson(json['attributes'])
: null;
}
}
class TagAttributes {
String? group;
TagAttributes({this.group});
TagAttributes.fromJson(Map<String, dynamic> json) {
group = json['group'];
}
}
class Relationships {
String? id;
String? type;
RAttributes? attributes;
String? related;
Relationships({this.id, this.type, this.attributes, this.related});
Relationships.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
attributes = json['attributes'] != null
? RAttributes.fromJson(json['attributes'])
: null;
related = json['related'];
}
}
class RAttributes {
String? description;
String? volume;
String? fileName;
String? locale;
String? createdAt;
String? updatedAt;
int? version;
RAttributes(
{this.description,
this.volume,
this.fileName,
this.locale,
this.createdAt,
this.updatedAt,
this.version});
RAttributes.fromJson(Map<String, dynamic> json) {
description = json['description'];
volume = json['volume'];
fileName = json['fileName'];
locale = json['locale'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
version = json['version'];
}
}

View file

@ -1,209 +0,0 @@
class MDXDetail {
String? result;
String? response;
Dataa? data;
MDXDetail({this.result, this.response, this.data});
MDXDetail.fromJson(Map<String, dynamic> json) {
result = json['result'];
response = json['response'];
data = json['data'] != null ? Dataa.fromJson(json['data']) : null;
}
}
class Dataa {
String? id;
String? type;
Attributes? attributes;
List<Relationships>? relationships;
Dataa({this.id, this.type, this.attributes, this.relationships});
Dataa.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
attributes = json['attributes'] != null
? Attributes.fromJson(json['attributes'])
: null;
if (json['relationships'] != null) {
relationships = <Relationships>[];
json['relationships'].forEach((v) {
relationships!.add(Relationships.fromJson(v));
});
}
}
}
class Attributes {
Title? title;
Map<String, dynamic>? description;
bool? isLocked;
String? originalLanguage;
String? lastVolume;
String? lastChapter;
String? publicationDemographic;
String? status;
int? year;
String? contentRating;
List<Tags>? tags;
String? state;
bool? chapterNumbersResetOnNewVolume;
String? createdAt;
String? updatedAt;
int? version;
List<String>? availableTranslatedLanguages;
String? latestUploadedChapter;
Attributes(
{this.title,
this.description,
this.isLocked,
this.originalLanguage,
this.lastVolume,
this.lastChapter,
this.publicationDemographic,
this.status,
this.year,
this.contentRating,
this.tags,
this.state,
this.chapterNumbersResetOnNewVolume,
this.createdAt,
this.updatedAt,
this.version,
this.availableTranslatedLanguages,
this.latestUploadedChapter});
Attributes.fromJson(Map<String, dynamic> json) {
title = json['title'] != null ? Title.fromJson(json['title']) : null;
description = json['description'];
isLocked = json['isLocked'];
originalLanguage = json['originalLanguage'];
lastVolume = json['lastVolume'];
lastChapter = json['lastChapter'];
publicationDemographic = json['publicationDemographic'];
status = json['status'];
year = json['year'];
contentRating = json['contentRating'];
if (json['tags'] != null) {
tags = <Tags>[];
json['tags'].forEach((v) {
tags!.add(Tags.fromJson(v));
});
}
state = json['state'];
chapterNumbersResetOnNewVolume = json['chapterNumbersResetOnNewVolume'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
version = json['version'];
availableTranslatedLanguages =
json['availableTranslatedLanguages'].cast<String>();
latestUploadedChapter = json['latestUploadedChapter'];
}
}
class Title {
String? en;
Title({this.en});
Title.fromJson(Map<String, dynamic> json) {
en = json['en'];
}
}
class Tags {
String? id;
String? type;
TAttributes? attributes;
Tags({this.id, this.type, this.attributes});
Tags.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
attributes = json['attributes'] != null
? TAttributes.fromJson(json['attributes'])
: null;
}
}
class TAttributes {
Title? name;
Map<String, dynamic>? description;
String? group;
int? version;
TAttributes({this.name, this.description, this.group, this.version});
TAttributes.fromJson(Map<String, dynamic> json) {
name = json['name'] != null ? Title.fromJson(json['name']) : null;
description = json['description'];
group = json['group'];
version = json['version'];
}
}
class Relationships {
String? id;
String? type;
RAttributes? attributes;
String? related;
Relationships({this.id, this.type, this.attributes, this.related});
Relationships.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
attributes = json['attributes'] != null
? RAttributes.fromJson(json['attributes'])
: null;
related = json['related'];
}
}
class RAttributes {
String? name;
Title? biography;
String? twitter;
String? createdAt;
String? updatedAt;
int? version;
String? description;
String? volume;
String? fileName;
String? locale;
RAttributes(
{this.name,
this.biography,
this.twitter,
this.createdAt,
this.updatedAt,
this.version,
this.description,
this.volume,
this.fileName,
this.locale});
RAttributes.fromJson(Map<String, dynamic> json) {
name = json['name'];
biography =
json['biography'] != null ? Title.fromJson(json['biography']) : null;
twitter = json['twitter'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
version = json['version'];
description = json['description'];
volume = json['volume'];
fileName = json['fileName'];
locale = json['locale'];
}
}

View file

@ -1,323 +0,0 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/src/all/mangadex/model/chapter.dart' as mdx;
import 'package:mangayomi/sources/src/all/mangadex/model/cover.dart';
import 'package:mangayomi/sources/src/all/mangadex/model/aggregate.dart';
import 'package:mangayomi/sources/src/all/mangadex/model/get_chapter_url.dart'
as get_chap_url;
import 'package:mangayomi/sources/src/all/mangadex/model/manga.dart'
as manga_dx;
import 'package:mangayomi/sources/src/all/mangadex/model/mdx_detail.dart'
as mdx_detail;
import 'package:mangayomi/sources/src/all/mangadex/src/utils/utils.dart';
import 'package:mangayomi/sources/utils/utils.dart';
class MangaDex extends MangaYomiServices {
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
String chapterId = chapter.url!.split('/').last;
String source = chapter.manga.value!.source!;
final response = await ref.watch(httpGetProvider(
url: '${getMangaAPIUrl(source)}/at-home/server/$chapterId',
source: source,
resDom: false)
.future) as String?;
final result = jsonDecode(response!) as Map<String, dynamic>;
final atHome = get_chap_url.GetChapterUrl.fromJson(result);
final host = atHome.baseUrl;
final hash = atHome.chapter!.hash!;
List<String> pageSuffix = [];
for (var data in atHome.chapter!.data!) {
pageSuffix.add("/data/$hash/$data");
}
for (var url in pageSuffix) {
pageUrls.add("$host$url");
}
return pageUrls;
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/chapter?limit=20&offset=${(20 * (page - 1))}&translatedLanguage[]=$lang&includeFutureUpdates=0${getMDXContentRating()}&order[publishAt]=desc&includeFuturePublishAt=0&includeEmptyPages=0',
source: source,
resDom: false,
).future) as String?;
final result = jsonDecode(response!) as Map<String, dynamic>;
final chapterList = mdx.ChapterMDX.fromJson(result);
List<String> mangaIds = [];
for (var element in chapterList.data!) {
for (var el in element.relationships!) {
mangaIds.add(el.id!);
}
}
mangaIds = mangaIds.toSet().toList();
String manga = "";
for (var id in mangaIds) {
manga = "$manga&ids[]=$id";
}
final mangaResponse = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/manga?includes[]=cover_art&limit=${mangaIds.length}${getMDXContentRating()}$manga',
source: source,
resDom: false,
).future) as String?;
final res = jsonDecode(mangaResponse!) as Map<String, dynamic>;
final resultt = manga_dx.MangaDexDto.fromJson(res);
final firstVolumeCovers =
await fetchFirstVolumeCovers(resultt.data, source, ref);
for (var da in resultt.data!) {
url.add("/manga/${da.id}");
name.add(findTitle(da.attributes!.altTitles, da.attributes!.title, lang));
image.add(
"https://uploads.mangadex.org/covers/${da.id}/${getFileName(da.id!, resultt.data, firstVolumeCovers)}");
}
return mangaRes();
}
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}${manga.url}?includes[]=cover_art&includes[]=author&includes[]=artist',
source: source,
resDom: false,
).future) as String?;
final res = jsonDecode(response!) as Map<String, dynamic>;
final result = mdx_detail.MDXDetail.fromJson(res);
final aggregate = await fetchSimpleChapterList(result, lang, source, ref);
List<String> authors = [];
for (var element in result.data!.relationships!) {
if (element.attributes != null) {
authors.add(element.attributes!.name ?? "");
}
}
final attr = result.data!.attributes!;
author = authors.join(", ");
Map<String, dynamic>? descrip = attr.description;
description = descrip![lang] ?? descrip["en"] ?? "";
for (var element in attr.tags!) {
if (element.attributes != null) {
genre.add(element.attributes!.name!.en!);
}
}
genre = [
...genre,
attr.publicationDemographic ?? "",
attr.contentRating != null && attr.contentRating!.toLowerCase() != "safe"
? "Content rating: ${attr.contentRating}"
: ""
].where((element) => element.isNotEmpty).toList();
status = getPublicationStatus(aggregate, attr);
String mangaId = manga.url!.split('/').last;
final paginatedChapterList =
await paginatedChapterListRequest(mangaId, 0, ref, source, lang);
final rres = jsonDecode(paginatedChapterList) as Map<String, dynamic>;
final chapterListResponse = mdx.ChapterMDX.fromJson(rres);
final chapterListResults = chapterListResponse.data;
var limit = chapterListResponse.limit!;
var offset = chapterListResponse.offset!;
var hasMoreResults = (limit + offset) < chapterListResponse.total!;
while (hasMoreResults) {
offset += limit;
var newRequest =
await paginatedChapterListRequest(mangaId, offset, ref, source, lang);
final rres = jsonDecode(newRequest) as Map<String, dynamic>;
var newChapterList = mdx.ChapterMDX.fromJson(rres);
chapterListResults!.addAll(newChapterList.data!);
hasMoreResults = (limit + offset) < newChapterList.total!;
}
for (var chapterData in chapterListResults!) {
List<String> scan = [];
var groups = chapterData.relationships!
.where((element) => element.id != legacyNoGroupId)
.toList();
for (var element in groups) {
scan.add(
element.attributes != null ? element.attributes!.name ?? "" : "");
}
final scann = scan.join(" ").trim();
if (scann.isEmpty) {
scan = [];
for (var element in groups) {
scan.add(element.attributes != null
? element.attributes!.username != null
? "Uploaded by ${element.attributes!.username}"
: ""
: "");
}
}
if (scann.isEmpty) {
scan = [];
scan.add("No Group");
}
List<String> chapName = [];
var chapAttr = chapterData.attributes!;
if (chapAttr.volume != null && chapAttr.volume!.isNotEmpty) {
chapName.add("Vol.${chapAttr.volume}");
}
if (chapAttr.chapter != null && chapAttr.chapter!.isNotEmpty) {
chapName.add("Ch.${chapAttr.chapter}");
}
if (chapAttr.title != null && chapAttr.title!.isNotEmpty) {
if (chapName.isNotEmpty) {
chapName.add("-");
}
chapName.add(chapAttr.title!);
}
if (chapName.isEmpty) {
chapName.add("Oneshot");
}
scanlators.add(scan.join(" ").trim());
chapterUrl.add("/chapter/${chapterData.id}");
chapterTitle.add(chapName.join(" "));
chapterDate.add(parseDate(chapAttr.publishAt!, source));
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga?>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/manga?limit=20&offset=${(20 * (page - 1))}&availableTranslatedLanguage[]=$lang&includes[]=cover_art${getMDXContentRating()}&order[followedCount]=desc',
source: source,
resDom: false,
).future) as String?;
final res = jsonDecode(response!) as Map<String, dynamic>;
final result = manga_dx.MangaDexDto.fromJson(res);
final firstVolumeCovers =
await fetchFirstVolumeCovers(result.data, source, ref);
for (var da in result.data!) {
url.add("/manga/${da.id}");
name.add(findTitle(da.attributes!.altTitles, da.attributes!.title, lang));
image.add(
"https://uploads.mangadex.org/covers/${da.id}/${getFileName(da.id!, result.data, firstVolumeCovers)}");
}
return mangaRes();
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/manga?includes[]=cover_art&offset=0&limit=20&title=${query.toLowerCase().trim()}${getMDXContentRating()}&order[followedCount]=desc&availableTranslatedLanguage[]=$lang',
source: source,
resDom: false,
).future) as String?;
final res = jsonDecode(response!) as Map<String, dynamic>;
final resultt = manga_dx.MangaDexDto.fromJson(res);
final firstVolumeCovers =
await fetchFirstVolumeCovers(resultt.data, source, ref);
for (var da in resultt.data!) {
url.add("/manga/${da.id}");
name.add(findTitle(da.attributes!.altTitles, da.attributes!.title, lang));
image.add(
"https://uploads.mangadex.org/covers/${da.id}/${getFileName(da.id!, resultt.data, firstVolumeCovers)}");
}
return mangaRes();
}
Future<List<CoverData>?> fetchFirstVolumeCovers(List<manga_dx.Data>? data,
String source, AutoDisposeFutureProviderRef ref) async {
List<String> mangaIds = [];
List<String> locales = [];
for (var element in data!) {
final attributes = element.attributes;
if (attributes!.originalLanguage != null &&
attributes.originalLanguage!.isNotEmpty) {
mangaIds.add(element.id!);
locales.add(attributes.originalLanguage!);
}
}
int limit = (mangaIds.length * locales.length);
if (limit > 100) {
limit = 100;
}
String manga = "";
for (var id in mangaIds) {
manga = "$manga&manga[]=$id";
}
String locale = "";
for (var loc in locales.toSet()) {
locale = "$locale&locales[]=$loc";
}
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/cover?order[volume]=asc&limit=$limit&offset=0$manga$locale',
source: source,
resDom: false,
).future) as String?;
final res = jsonDecode(response!) as Map<String, dynamic>;
final result = CoverAA.fromJson(res).data;
result!.sort(
(a, b) => a.relationships!.firstOrNull!.id!
.compareTo(b.relationships!.firstOrNull!.id!),
);
return result.where((element) {
return mangaIds.contains(element.relationships!.first.id);
}).toList();
}
Future<Aggregate>? fetchSimpleChapterList(mdx_detail.MDXDetail? data,
String lang, String source, AutoDisposeFutureProviderRef ref) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/manga/${data!.data!.id}/aggregate?translatedLanguage[]=$lang',
source: source,
resDom: false,
).future) as String?;
final res = jsonDecode(response!) as Map<String, dynamic>;
return Aggregate.fromJson(res);
}
Future<String> paginatedChapterListRequest(String mangaId, int offset,
AutoDisposeFutureProviderRef ref, String source, String lang) async {
final response = await ref.watch(httpGetProvider(
url:
'${getMangaAPIUrl(source)}/manga/$mangaId/feed?limit=500&offset=$offset&includes[]=user&includes[]=scanlation_group&order[volume]=desc&order[chapter]=desc&translatedLanguage[]=$lang&includeFuturePublishAt=0&includeEmptyPages=0&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic',
source: source,
resDom: false,
).future) as String?;
return response!;
}
}

View file

@ -1,90 +0,0 @@
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/sources/src/all/mangadex/model/aggregate.dart';
import 'package:mangayomi/sources/src/all/mangadex/model/cover.dart';
import 'package:mangayomi/sources/src/all/mangadex/model/manga.dart';
import 'package:mangayomi/sources/src/all/mangadex/model/mdx_detail.dart'
as mdx_detail;
String getMDXContentRating() {
List<String> contentRating = [
"suggestive",
"safe",
"erotica",
"pornographic"
];
String ctnRating = "";
for (var rating in contentRating) {
ctnRating = "$ctnRating&contentRating[]=$rating";
}
return ctnRating;
}
findTitle(
List<dynamic>? altTitles, Map<String, dynamic>? enTitle, String lang) {
final altTitle = altTitles!
.where((element) =>
element[lang].toString().isNotEmpty ||
element["en"].toString().isNotEmpty)
.toList();
String? altTitless = altTitle.isNotEmpty
? altTitle.firstOrNull[lang] ?? altTitle.firstOrNull["en"]
: null;
return enTitle!.values.firstOrNull ?? altTitless ?? "";
}
getFileName(String id, List<Data>? data, List<CoverData>? firstVolumeCovers) {
final mangaMap =
data!.asMap().map((key, value) => MapEntry(value.id, value.attributes));
final firstVolumeCoversF = firstVolumeCovers!
.where((element) =>
element.attributes!.locale == mangaMap[id]!.originalLanguage!)
.where((element) =>
element.attributes!.fileName != null &&
element.attributes!.fileName!.isNotEmpty)
.toList()
.asMap()
.map((key, value) =>
MapEntry(value.relationships!.first.id, value.attributes!.fileName));
final datas = data
.where((element) => element.id == id)
.toList()
.firstOrNull!
.relationships!
.firstOrNull!
.attributes;
return firstVolumeCoversF.isNotEmpty
? firstVolumeCoversF[id]
: datas != null
? datas.fileName
: "";
}
Status getPublicationStatus(Aggregate? aggregate, mdx_detail.Attributes attr) {
List<String> chaptersList = [];
for (var element in aggregate!.volumes!.values) {
for (var elem in element.chapters!.values) {
chaptersList.add(elem.chapter!);
}
}
var tempStatus = switch (attr.status) {
"ongoing" => Status.ongoing,
"cancelled" => Status.canceled,
"completed" => Status.publishingFinished,
"hiatus" => Status.onHiatus,
_ => Status.unknown,
};
var publishedOrCancelled =
tempStatus == Status.publishingFinished || tempStatus == Status.canceled;
var isOneShot = attr.tags!.any((element) => element.id == tagOneShotUuid) &&
attr.tags!.where((element) => (element.id == tagAnthologyUuid)).isEmpty;
return chaptersList.contains(attr.lastChapter) && publishedOrCancelled
? Status.completed
: isOneShot
? Status.completed
: tempStatus;
}
const tagAnthologyUuid = "51d83883-4103-437c-b4b1-731cb73d786c";
const tagOneShotUuid = "0234a31e-a729-4e28-9d6a-3f87c4966b9e";
const legacyNoGroupId = "00e03853-1b96-4f41-9542-c71b8692033b";

View file

@ -1,13 +0,0 @@
import 'package:mangayomi/models/source.dart';
Source get mangahereSource => _mangahereSource;
Source _mangahereSource = Source(
sourceName: "MangaHere",
baseUrl: "http://www.mangahere.cc",
lang: "en",
typeSource: TypeSource.single,
logoUrl: 'http://static.mangahere.cc/v20210106/mangahere/images/logo.png',
dateFormat: "MMM dd,yyyy",
dateFormatLocale: "en",
);

View file

@ -1,362 +0,0 @@
import 'package:flutter_js/flutter_js.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:html/dom.dart';
import 'package:intl/intl.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:http/http.dart' as http;
import 'package:html/dom.dart' as dom;
import 'package:mangayomi/sources/utils/utils.dart';
class Mangahere extends MangaYomiServices {
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: "${getMangaBaseUrl(source)}/${manga.url}",
source: source,
resDom: true)
.future) as Document?;
if (dom!
.querySelectorAll(
' body > div > div > div.detail-info-right > p.detail-info-right-title > span.detail-info-right-title-tip')
.isNotEmpty) {
final tt = dom
.querySelectorAll(
' body > div > div > div.detail-info-right > p.detail-info-right-title > span.detail-info-right-title-tip')
.map((e) => e.text.trim())
.toList();
status = switch (tt[0].toLowerCase()) {
"ongoing" => Status.ongoing,
"completed" => Status.completed,
_ => Status.unknown,
};
} else {
status = Status.unknown;
}
if (dom
.querySelectorAll(
' body > div > div > div.detail-info-right > p.detail-info-right-say > a')
.isNotEmpty) {
final tt = dom
.querySelectorAll(
' body > div > div > div.detail-info-right > p.detail-info-right-say > a')
.map((e) => e.text.trim())
.toList();
author = tt[0];
} else {
author = "";
}
if (dom
.querySelectorAll(
'body > div > div > div.detail-info-right > p.detail-info-right-content')
.isNotEmpty) {
final tt = dom
.querySelectorAll(
'body > div > div > div.detail-info-right > p.detail-info-right-content')
.map((e) => e.text.trim())
.toList();
description = tt.first;
}
if (dom.querySelectorAll('ul > li > a').isNotEmpty) {
final udl = dom
.querySelectorAll('ul > li > a ')
.where((e) => e.attributes.containsKey('href'))
.map((e) => e.attributes['href'])
.toList();
for (var ok in udl) {
chapterUrl.add(ok!);
}
}
if (dom.querySelectorAll('ul > li > a > div > p.title3').isNotEmpty) {
final tt = dom
.querySelectorAll('ul > li > a > div > p.title3')
.map((e) => e.text.trim())
.toList();
for (var ok in tt) {
chapterTitle.add(ok);
}
}
if (dom.querySelectorAll('ul > li > a > div > p.title2').isNotEmpty) {
final tt = dom
.querySelectorAll('ul > li > a > div > p.title2')
.map((e) => e.text.trim())
.toList();
for (var ok in tt) {
chapterDate.add(parseMangaHereChapterDate(ok, source).toString());
}
}
if (dom
.querySelectorAll(
' body > div > div > div.detail-info-right > p.detail-info-right-tag-list > a')
.isNotEmpty) {
final tt = dom
.querySelectorAll(
' body > div > div > div.detail-info-right > p.detail-info-right-tag-list > a')
.map((e) => e.text.trim())
.toList();
for (var ok in tt) {
genre.add(ok);
}
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga?>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: '${getMangaBaseUrl(source)}/directory/$page.htm',
source: source,
resDom: true)
.future) as Document?;
if (dom!.querySelectorAll('.manga-list-1-list li').isNotEmpty) {
url = dom
.querySelectorAll('.manga-list-1-list li > a ')
.where((e) => e.attributes.containsKey('href'))
.map((e) => e.attributes['href'])
.toList();
image = dom
.querySelectorAll('.manga-list-1-list li > a > img')
.where((e) => e.attributes.containsKey('src'))
.where((e) => e.attributes['src']!.contains("cover"))
.map((e) => e.attributes['src'])
.toList();
name = dom
.querySelectorAll('.manga-list-1-list li > a ')
.where((e) => e.attributes.containsKey('title'))
.map((e) => e.attributes['title'])
.toList();
}
return mangaRes();
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url:
'${getMangaBaseUrl(source)}/search?title=${query.trim()}&genres=&nogenres=&sort=&stype=1&name=&type=0&author_method=cw&author=&artist_method=cw&artist=&rating_method=eq&rating=&released_method=eq&released=&st=0',
source: source,
resDom: true)
.future) as Document?;
if (dom!
.querySelectorAll(
'body > div.container > div > div > ul > li > p.manga-list-4-item-title > a')
.isNotEmpty) {
url = dom
.querySelectorAll(
'body > div.container > div > div > ul > li > p.manga-list-4-item-title > a')
.where((e) => e.attributes.containsKey('href'))
.map((e) => e.attributes['href'])
.toList();
image = dom
.querySelectorAll(
'body > div.container > div > div > ul > li > a > img')
.where((e) => e.attributes.containsKey('src'))
.where((e) => e.attributes['src']!.contains("cover"))
.map((e) => e.attributes['src'])
.toList();
name = dom
.querySelectorAll(
'body > div.container > div > div > ul > li > p.manga-list-4-item-title > a')
.where((e) => e.attributes.containsKey('title'))
.map((e) => e.attributes['title'])
.toList();
}
return mangaRes();
}
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
JavascriptRuntime? flutterJs;
flutterJs = getJavascriptRuntime();
extractSecretKey(String response, JavascriptRuntime? flutterJs) {
var secretKeyScriptLocation =
response.indexOf("eval(function(p,a,c,k,e,d)");
var secretKeyScriptEndLocation =
response.indexOf("</script>", secretKeyScriptLocation);
var secretKeyScript = response
.substring(secretKeyScriptLocation, secretKeyScriptEndLocation)
.replaceAll("eval", "");
var secretKeyDeobfuscatedScript =
flutterJs!.evaluate(secretKeyScript).toString();
var secretKeyStartLoc = secretKeyDeobfuscatedScript.indexOf("'");
var secretKeyEndLoc = secretKeyDeobfuscatedScript.indexOf(";");
var secretKeyResultScript = secretKeyDeobfuscatedScript.substring(
secretKeyStartLoc, secretKeyEndLoc);
return secretKeyResultScript;
}
var link =
"${getMangaBaseUrl(chapter.manga.value!.source!)}${chapter.url!}";
final response = await ref.watch(
httpGetProvider(url: link, source: "mangahere", resDom: false)
.future) as String?;
dom.Document htmll = dom.Document.html(response!);
int? pagesNumber = -1;
if (htmll.querySelectorAll('body > div > div > span > a:').isNotEmpty) {
final ta = htmll
.querySelectorAll('body > div > div > span > a:')
.map((e) => e.text.trim())
.toList();
ta.removeLast();
pagesNumber = int.parse(ta.last);
}
if (pagesNumber == -1) {
final script = htmll
.getElementsByTagName("script")
.firstWhere((e) => e.innerHtml.contains(
"function(p,a,c,k,e,d)",
))
.innerHtml
.replaceAll("eval", "");
String deobfuscatedScript = flutterJs.evaluate(script).toString();
List<String> urlss = deobfuscatedScript
.substring(
deobfuscatedScript.indexOf("newImgs=['") + "newImgs=['".length,
deobfuscatedScript.indexOf("'];"))
.split("','");
for (var tt in urlss) {
pageUrls.add("https:$tt");
}
flutterJs.dispose();
} else {
var secretKey = extractSecretKey(response, flutterJs);
var chapterIdStartLoc = response.indexOf("chapterid");
var chapterId = response
.substring(
chapterIdStartLoc + 11, response.indexOf(";", chapterIdStartLoc))
.trim();
var pageBase = link.substring(0, link.lastIndexOf("/"));
for (int i = 1; i <= pagesNumber; i++) {
var pageLink =
"$pageBase/chapterfun.ashx?cid=$chapterId&page=$i&key=$secretKey";
var responseText = "";
for (int tr = 1; tr <= 3; tr++) {
var response = await http.get(Uri.parse(pageLink), headers: {
"Referer": link,
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "keep-alive",
"Host": "www.mangahere.cc",
"X-Requested-With": "XMLHttpRequest"
});
responseText = response.body;
if (responseText.isNotEmpty) {
break;
} else {
secretKey = "";
}
}
var deobfuscatedScript =
flutterJs.evaluate(responseText.replaceAll("eval", "")).toString();
var baseLinkStartPos = deobfuscatedScript.indexOf("pix=") + 5;
var baseLinkEndPos =
deobfuscatedScript.indexOf(";", baseLinkStartPos) - 1;
var baseLink =
deobfuscatedScript.substring(baseLinkStartPos, baseLinkEndPos);
var imageLinkStartPos = deobfuscatedScript.indexOf("pvalue=") + 9;
var imageLinkEndPos =
deobfuscatedScript.indexOf("\"", imageLinkStartPos);
var imageLink =
deobfuscatedScript.substring(imageLinkStartPos, imageLinkEndPos);
pageUrls.add("https:$baseLink$imageLink");
}
flutterJs.dispose();
}
return pageUrls;
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: '${getMangaBaseUrl(source)}/directory/$page.htm?latest',
source: source,
resDom: true)
.future) as Document?;
if (dom!.querySelectorAll('.manga-list-1-list li').isNotEmpty) {
url = dom
.querySelectorAll('.manga-list-1-list li > a ')
.where((e) => e.attributes.containsKey('href'))
.map((e) => e.attributes['href'])
.toList();
image = dom
.querySelectorAll('.manga-list-1-list li > a > img')
.where((e) => e.attributes.containsKey('src'))
.where((e) => e.attributes['src']!.contains("cover"))
.map((e) => e.attributes['src'])
.toList();
name = dom
.querySelectorAll('.manga-list-1-list li > a ')
.where((e) => e.attributes.containsKey('title'))
.map((e) => e.attributes['title'])
.toList();
}
return mangaRes();
}
}
int parseMangaHereChapterDate(String date, String source) {
if (date.contains('Today') || date.contains(' ago')) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
return today.millisecondsSinceEpoch;
} else if (date.contains('Yesterday')) {
final now = DateTime.now();
final yesterday = DateTime(now.year, now.month, now.day - 1);
return yesterday.millisecondsSinceEpoch;
} else {
try {
final dateFormat =
DateFormat(getFormatDate(source), getFormatDateLocale(source));
final parsedDate = dateFormat.parse(date);
return parsedDate.millisecondsSinceEpoch;
} catch (e) {
return 0;
}
}
}

View file

@ -1,13 +0,0 @@
import 'package:mangayomi/models/source.dart';
Source get japscanSource => _japscanSource;
Source _japscanSource = Source(
sourceName: "Japscan",
baseUrl: "https://japscan.lol",
lang: "fr",
typeSource: TypeSource.single,
logoUrl: '',
isCloudflare: true,
dateFormat: "d MMM yyyy",
dateFormatLocale: "en_US");

View file

@ -1,291 +0,0 @@
// ignore_for_file: depend_on_referenced_packages
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/dom.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart';
import 'package:mangayomi/services/http_service/http_service.dart';
import 'package:mangayomi/sources/service.dart';
import 'package:mangayomi/sources/utils/utils.dart';
class Japscan extends MangaYomiServices {
@override
Future<GetManga?> getMangaDetail(
{required GetManga manga,
required String lang,
required String source,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: manga.url!,
source: source,
resDom: true,
).future) as Document?;
if (dom!.querySelectorAll('.col-7 > p').isNotEmpty) {
final images =
dom.querySelectorAll('.col-5 ').map((e) => e.outerHtml).toList();
RegExp exp = RegExp(r'src="([^"]+)"');
String? srcValue = exp.firstMatch(images[0])?.group(1);
manga.imageUrl = '${getMangaBaseUrl(source)}$srcValue';
if (dom.querySelectorAll('.col-7 > p').isNotEmpty) {
final stat = dom
.querySelectorAll('.col-7 > p')
.where((element) => element.innerHtml.contains('Statut:'))
.map((e) => e.text)
.toList();
if (stat.isNotEmpty) {
String sta = stat[0].replaceAll('Statut:', '').trim();
status = switch (sta) {
"En Cours" => Status.ongoing,
"Terminé" => Status.completed,
_ => Status.unknown,
};
} else {
status = Status.unknown;
}
final auth = dom
.querySelectorAll('.col-7 > p')
.where((element) => element.innerHtml.contains('Auteur(s):'))
.map((e) => e.text)
.toList();
if (auth.isNotEmpty) {
author = auth[0].replaceAll('Auteur(s):', '').trim();
}
} else {
author = "";
status = Status.unknown;
}
final genres = dom
.querySelectorAll('.col-7 > p')
.where((element) => element.innerHtml.contains('Genre(s):'))
.map((e) => e.text.replaceAll('Genre(s):', '').trim())
.toList();
if (genres.isNotEmpty) {
for (var ok in genres[0].split(',')) {
genre.add(ok);
}
}
final synop = dom
.querySelectorAll('p.list-group-item ')
.map((e) => e.text.trim())
.toList();
if (synop.isNotEmpty) {
description = synop[0];
}
}
final urls =
dom.querySelectorAll('.col-8 ').map((e) => e.outerHtml).toList();
for (var ok in urls) {
RegExp exp = RegExp(r'href="([^"]+)"');
String? srcValue = exp.firstMatch(ok)?.group(1);
chapterUrl.add('${getMangaBaseUrl(source)}$srcValue');
}
final chapterTitlee =
dom.querySelectorAll('.col-8').map((e) => e.text.trim()).toList();
if (chapterTitlee.isNotEmpty) {
for (var ok in chapterTitlee) {
chapterTitle.add(ok);
}
}
final chapterDatee =
dom.querySelectorAll('.col-4').map((e) => e.text.trim()).toList();
if (chapterDatee.isNotEmpty) {
for (var ok in chapterDatee) {
chapterDate.add(parseDate(ok, source));
}
}
return mangadetailRes(manga: manga, source: source);
}
@override
Future<List<GetManga?>> getPopularManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: "${getMangaBaseUrl(source)}/", source: source, resDom: true)
.future) as Document?;
if (dom!.querySelectorAll('#top_mangas_week > ul > li ').isNotEmpty) {
final urls = dom
.querySelectorAll('#top_mangas_week > ul > li > a')
.where((e) => e.attributes['href'].toString().contains('manga'))
.map((e) => e.attributes['href'])
.toList();
print(urls);
for (var ok in urls) {
url.add("${getMangaBaseUrl(source)}$ok");
}
name = dom
.querySelectorAll(
'#top_mangas_week > ul > li > a.text-dark.font-weight-bold')
.map((e) => e.innerHtml)
.toList();
for (var i = 0; i < name.length; i++) {
image.add("");
}
}
return mangaRes();
}
@override
Future<List<GetManga?>> searchManga(
{required String source,
required String query,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url:
"https://www.google.com/search?q=${query.toLowerCase()}+japscan",
source: source,
resDom: true)
.future) as Document?;
if (dom!.querySelectorAll("div > div > div > div > div > a").isNotEmpty) {
final urls = dom
.querySelectorAll("div > div > div > div > div > a")
.where((e) => e.attributes.containsKey('href'))
.where((element) =>
element.text.toLowerCase().contains("https://www.japscan."))
.map((e) => e.attributes['href']
.toString()
.replaceAll('lecture-en-ligne', 'manga')
.split("/"))
.toList();
List<String?> tt = [];
List<String?> ta = [];
for (var ok in urls) {
tt.add("${ok[0]}//${ok[2]}/${ok[3]}/${ok[4]}/");
ta.add(ok[4]
.replaceAll('-', " ")
.toString()
.split(' ')
.map((word) =>
word.substring(0, 1).toUpperCase() + word.substring(1))
.join(' '));
}
name = ta.toSet().toList();
url = tt.toSet().toList();
for (var i = 0; i < name.length; i++) {
image.add("");
}
}
return mangaRes();
}
@override
Future<List<String>> getChapterUrl(
{required Chapter chapter,
required AutoDisposeFutureProviderRef ref}) async {
final response = await ref.watch(httpGetProvider(
useUserAgent: true,
url: chapter.url!,
source: "japscan",
resDom: false)
.future) as String?;
RegExp regex = RegExp(r'<script src="/zjs/(.*?)"');
Match? match = regex.firstMatch(response!);
String zjsurl = match!.group(1)!;
baseUrl = response;
zjsUrl = "${getMangaBaseUrl(chapter.manga.value!.source!)}/zjs/$zjsurl";
zjs(ref);
await Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (isOk == true) {
return false;
}
return true;
});
return pageUrls;
}
bool isOk = false;
String? baseUrl;
String? zjsUrl;
zjs(AutoDisposeFutureProviderRef ref) async {
final html = await ref.watch(cloudflareBypassHtmlProvider(
url: zjsUrl!, source: "japscan", useUserAgent: true)
.future);
dom.Document htmll = dom.Document.html(baseUrl!);
List<String?> stringLookupTables =
RegExp(r"'([\dA-Z]{62})'", caseSensitive: false)
.allMatches(html)
.map((match) => match.group(1))
.toList();
if (stringLookupTables.length != 2) {
throw Exception(
"Attendait 2 chaînes de recherche dans ZJS, a trouvé ${stringLookupTables.length}");
}
final scrambledData =
htmll.getElementById("data")!.attributes['data-data']!;
for (var i = 0; i <= 1; i++) {
final otherIndex = i == 0 ? 1 : 0;
final lookupTable = Map.fromIterables(stringLookupTables[i]!.split(''),
stringLookupTables[otherIndex]!.split(''));
try {
final unscrambledData = scrambledData
.split('')
.map((char) => lookupTable[char] ?? char)
.join();
final decoded = utf8.decode(base64.decode(unscrambledData));
final data = jsonDecode(decoded);
for (var url in data["imagesLink"]) {
pageUrls.add(url);
}
} catch (_) {}
}
isOk = true;
}
@override
Future<List<GetManga?>> getLatestUpdatesManga(
{required String source,
required int page,
required String lang,
required AutoDisposeFutureProviderRef ref}) async {
final dom = await ref.watch(httpGetProvider(
url: "${getMangaBaseUrl(source)}/", source: source, resDom: true)
.future) as Document?;
if (dom!
.querySelectorAll(
'#chapters h3.text-truncate, #chapters_list h3.text-truncate')
.isNotEmpty) {
final urls = dom
.querySelectorAll(
'#chapters h3.text-truncate, #chapters_list h3.text-truncate > a')
.where((e) => e.attributes['href'].toString().contains('manga'))
.map((e) => e.attributes['href'])
.toList();
for (var ok in urls) {
url.add("${getMangaBaseUrl(source)}$ok");
}
name = dom
.querySelectorAll(
'#chapters h3.text-truncate, #chapters_list h3.text-truncate > a.text-dark.font-weight-bold')
.map((e) => e.innerHtml)
.toList();
for (var i = 0; i < name.length; i++) {
image.add("");
}
}
return mangaRes();
}
}

View file

@ -1,12 +0,0 @@
import 'package:mangayomi/models/source.dart';
Source get mangakawaiiSource => _mangakawaiiSource;
Source _mangakawaiiSource = Source(
sourceName: "MangaKawaii",
baseUrl: "https://www.mangakawaii.io",
lang: "fr",
typeSource: TypeSource.single,
logoUrl: 'https://www.mangakawaii.io/assets/img/logo.png',
dateFormat: "dd.MM.yyyy",
isCloudflare: true,
dateFormatLocale: "en_US");

Some files were not shown because too many files have changed in this diff Show more