add anime serervers extractor , fix home page load more ,add hasNextPage check

This commit is contained in:
kodjomoustapha 2023-08-02 00:07:49 +01:00
parent 47734532b3
commit 0d7f4e276f
18 changed files with 282 additions and 71 deletions

View file

@ -153,6 +153,11 @@ class $MangaModel implements MangaModel, $Instance {
[BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
false),
BridgeParameter(
'hasNextPage',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.boolType)),
false),
]))
},
// Specify class fields
@ -241,6 +246,11 @@ class $MangaModel implements MangaModel, $Instance {
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]),
),
),
'hasNextPage': BridgeFieldDef(
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.boolType),
),
),
},
wrap: true);
@ -357,7 +367,8 @@ class $MangaModel implements MangaModel, $Instance {
return $List.wrap($value.statusList!.map((e) {
return $int(e);
}).toList());
case 'hasNextPage':
return $bool($value.hasNextPage!);
default:
return _superclass.$getProperty(runtime, identifier);
}
@ -417,7 +428,8 @@ class $MangaModel implements MangaModel, $Instance {
$value.images = value.$reified as List<dynamic>;
case 'statusList':
$value.statusList = value.$reified as List<dynamic>;
case 'hasNextPage':
$value.hasNextPage = value.$reified;
default:
_superclass.$setProperty(runtime, identifier, value);
}
@ -480,6 +492,10 @@ class $MangaModel implements MangaModel, $Instance {
set source(String? source) {
// implement source
}
@override
set hasNextPage(bool? hasNextPage) {
// implement hasNextPage
}
@override
List<dynamic>? get chaptersDateUploads => $value.chaptersDateUploads;
@ -549,6 +565,9 @@ class $MangaModel implements MangaModel, $Instance {
@override
String? get dateFormatLocale => $value.dateFormatLocale;
@override
bool? get hasNextPage => $value.hasNextPage;
@override
set apiUrl(String? apiUrl) {
// implement apiUrl
@ -581,7 +600,7 @@ class $MangaModel implements MangaModel, $Instance {
@override
set query(String? query) {
// implement page
// implement query
}
@override
@ -599,7 +618,7 @@ class $MangaModel implements MangaModel, $Instance {
}
@override
set statusList(List? images) {
// implement statusList
// implement images
}
@override

View file

@ -31,6 +31,8 @@ class MangaModel {
int? sourceId;
bool? hasNextPage;
List<dynamic>? names;
List<dynamic>? urls;
List<dynamic>? chaptersScanlators;
@ -63,7 +65,8 @@ class MangaModel {
this.chaptersVolumes,
this.chaptersChaps,
this.images,
this.statusList});
this.statusList,
this.hasNextPage = true});
}
class VideoModel {

View file

@ -26,6 +26,7 @@ import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/modules/webview/webview.dart';
import 'package:mangayomi/services/anime_extractors/streamwish_extractor.dart';
import 'package:mangayomi/services/anime_extractors/vidbom_extractor.dart';
import 'package:mangayomi/services/anime_extractors/voe_extractor.dart';
import 'package:mangayomi/services/anime_extractors/your_upload_extractor.dart';
@ -538,6 +539,11 @@ class MBridge {
);
}
static Future<List<Video>> streamWishExtractor(
String url, String prefix) async {
return await StreamWishExtractor().videosFromUrl(url, prefix);
}
static Future<List<Video>> mp4UploadExtractor(
String url, String? headers, String prefix, String suffix) async {
Map<String, String> newHeaders = {};
@ -1430,6 +1436,24 @@ class $MBridge extends MBridge with $Bridge {
],
namedParams: []),
isStatic: true),
'streamWishExtractor': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
params: [
BridgeParameter(
'url',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'prefix',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
],
namedParams: []),
isStatic: true),
'getHtmlViaWebview': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
@ -1725,6 +1749,12 @@ class $MBridge extends MBridge with $Bridge {
.then((value) =>
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $streamWishExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.streamWishExtractor(args[0]!.$value, args[1]!.$value)
.then((value) =>
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $sendVidExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.sendVidExtractor(

View file

@ -24,6 +24,8 @@ Runtime runtimeEval(Uint8List bytecode) {
'MBridge.doodExtractor', $MBridge.$doodExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.streamTapeExtractor', $MBridge.$streamTapeExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.streamWishExtractor', $MBridge.$streamWishExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.mp4UploadExtractor', $MBridge.$mp4UploadExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',

View file

@ -211,7 +211,7 @@ class _AnimeStreamPageState extends State<AnimeStreamPage> {
await _controller.setDataSource(
DataSource(
type: DataSourceType.network,
source: _video.value!.url,
source: _video.value!.originalUrl,
httpHeaders: _video.value!.headers),
autoplay: true,
seekTo: _currentPosition,

View file

@ -199,50 +199,53 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
body: _getManga!.when(
data: (data) {
Widget buildProgressIndicator() {
return _isLoading
? const Center(
child: SizedBox(
height: 100,
width: 200,
child: Center(
child: CircularProgressIndicator(),
),
),
)
: isTablet(context)
? Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5))),
onPressed: () {
if (mounted) {
setState(() {
_isLoading = true;
});
}
_loadMore().then((value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
});
},
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Load more"),
SizedBox(
height: 10,
),
Icon(Icons.arrow_forward_outlined),
],
)),
return !(data.isNotEmpty && (data.last!.hasNextPage ?? true))
? Container()
: _isLoading
? const Center(
child: SizedBox(
height: 100,
width: 200,
child: Center(
child: CircularProgressIndicator(),
),
),
)
: Container();
: isTablet(context)
? Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5))),
onPressed: () {
if (mounted) {
setState(() {
_isLoading = true;
});
}
_loadMore().then((value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
});
},
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Load more"),
SizedBox(
height: 10,
),
Icon(Icons.arrow_forward_outlined),
],
)),
)
: Container();
}
if (data.isEmpty) {
@ -251,19 +254,21 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
if (mounted) {
setState(() {
_isLoading = true;
});
}
_loadMore().then((value) {
if (data.isNotEmpty && (data.last!.hasNextPage ?? true)) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
_isLoading = true;
});
}
});
_loadMore().then((value) {
if (mounted) {
setState(() {
data.addAll(value);
_isLoading = false;
});
}
});
}
}
});

View file

@ -7,7 +7,7 @@ class DoodExtractor {
Future<List<Video>> videosFromUrl(
String url, {
String? quality,
bool redirect = false,
bool redirect = true,
}) async {
final newQuality = quality ?? ('Doodstream${redirect ? ' mirror' : ''}');

View file

@ -0,0 +1,56 @@
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/utils/extensions.dart';
class MyStreamExtractor {
Future<List<Video>> videosFromUrl(
String url, Map<String, String> headers) async {
final host = url.substringBefore("/watch");
final client = http.Client();
try {
final response = await client.get(Uri.parse(url), headers: headers);
final document = response.body;
final streamCode = document
.substringAfter("${url.substringAfter("?v=")}\", \"")
.substringBefore("\",null,null");
final streamUrl = "$host/m3u8/$streamCode/master.txt?s=1&cache=1";
final cookie = response.headers.entries
.firstWhere(
(entry) =>
entry.key.toLowerCase() == "set-cookie" &&
entry.value.startsWith("PHPSESSID", 0),
orElse: () => const MapEntry("set-cookie", ""),
)
.value
.split(";")
.first;
final newHeaders = {...headers, "cookie": cookie, "accept": "*/*"};
final masterPlaylistResponse =
await client.get(Uri.parse(streamUrl), headers: newHeaders);
final masterPlaylist = masterPlaylistResponse.body;
const separator = "#EXT-X-STREAM-INF";
return masterPlaylist
.substringAfter(separator)
.split(separator)
.map((it) {
final resolution =
"${it.substringAfter("RESOLUTION=").substringBefore("\n").substringAfter("x").substringBefore(",")}p";
final quality = "MyStream: $resolution";
final videoUrl = it.substringAfter("\n").substringBefore("\n");
return Video(videoUrl, quality, videoUrl, headers: newHeaders);
}).toList();
} catch (_) {
return [];
} finally {
client.close();
}
}
}

View file

@ -17,7 +17,7 @@ class OkruExtractor {
'lowest': '240p',
'mobile': '144p',
};
return qualities[quality] ?? quality;
return qualities[quality.toLowerCase()] ?? quality;
}
Future<List<Video>> videosFromUrl(String url,
@ -27,17 +27,19 @@ class OkruExtractor {
final videosString = document
.querySelector('div[data-options]')
?.attributes['data-options']!
.substringAfter(
'''\\"videos\\":[{\\\\"name\\\\":\\\\"''').substringBefore(']') ??
.substringAfter("\\\"videos\\\":[{\\\"name\\\":\\\"")
.substringBefore(']') ??
'';
List<Video> videoList = [];
List<String> values =
videosString.split('''{\\\\"name\\\\":\\\\"''').reversed.toList();
videosString.split("{\\\"name\\\":\\\"").reversed.toList();
for (var value in values) {
final videoUrl = value
.substringAfter('''url\\\\":\\\\"''').substringBefore(
'''\\"''').replaceAll(r'\\u0026', '&');
final quality = value.substringBefore('''\\"''');
.substringAfter("url\\\":\\\"")
.substringBefore("\\\"")
.replaceAll(r'\\\u0026', '&');
final quality = value.substringBefore("\\\"");
final fixedQuality = fixQualities ? fixQuality(quality) : quality;
final videoQuality =
'${prefix.isNotEmpty ? '$prefix ' : ''}Okru:$fixedQuality';

View file

@ -0,0 +1,62 @@
import 'package:js_packer/js_packer.dart';
import 'package:http/http.dart' as http;
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/utils/extensions.dart';
import 'package:mangayomi/utils/xpath_selector.dart';
class StreamWishExtractor {
final http.Client client = http.Client();
final Map<String, String> headers = {};
Future<List<Video>> videosFromUrl(String url, String prefix) async {
final videoList = <Video>[];
final response = await client.get(Uri.parse(url), headers: headers);
final jsEval = xpathSelector(response.body)
.queryXPath('//script[contains(text(), "m3u8")]/text()')
.attrs;
if (jsEval.isEmpty) {
return [];
}
String? masterUrl = _evalJs(jsEval.first!)
?.substringAfter('source')
.substringAfter('file:"')
.substringBefore('"');
if (masterUrl == null) return [];
final playlistHeaders = Map<String, String>.from(headers)
..addAll({
'Accept': '*/*',
'Host': Uri.parse(masterUrl).host,
'Origin': 'https://${Uri.parse(url).host}',
'Referer': 'https://${Uri.parse(url).host}/',
});
final masterBase =
'${'https://${Uri.parse(masterUrl).host}${Uri.parse(masterUrl).path}'.substringBeforeLast('/')}/';
final masterPlaylistResponse =
await client.get(Uri.parse(masterUrl), headers: playlistHeaders);
final masterPlaylist = masterPlaylistResponse.body;
const separator = '#EXT-X-STREAM-INF:';
masterPlaylist.substringAfter(separator).split(separator).forEach((it) {
final quality =
'$prefix${it.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p ';
final videoUrl =
masterBase + it.substringAfter('\n').substringBefore('\n');
videoList
.add(Video(videoUrl, quality, videoUrl, headers: playlistHeaders));
});
return videoList;
}
}
String? _evalJs(String script) {
final jsPacker = JSPacker(script);
return jsPacker.unpack();
}

View file

@ -16,12 +16,12 @@ class VoeExtractor {
if (script.isEmpty) {
return [];
}
final videoUrl =
script.first!.substringAfter("hls': '").substringBefore("'");
final resolution =
script.first!.substringAfter("video_height': ").substringBefore(",");
final qualityStr = quality ?? "VoeCDN(${resolution}p)";
return [Video(videoUrl, qualityStr, videoUrl)];
} catch (_) {
return [];

View file

@ -47,6 +47,7 @@ Future<List<MangaModel?>> getLatestUpdatesManga(
lang: value.lang,
sourceId: value.sourceId,
dateFormat: value.dateFormat,
hasNextPage: value.hasNextPage ?? true,
dateFormatLocale: value.dateFormatLocale);
newManga.add(newMangaa);
}

View file

@ -47,6 +47,7 @@ Future<List<MangaModel?>> getPopularManga(
lang: value.lang,
status: value.status,
dateFormat: value.dateFormat,
hasNextPage: value.hasNextPage ?? true,
dateFormatLocale: value.dateFormatLocale);
newManga.add(newMangaa);
}

View file

@ -47,6 +47,7 @@ Future<List<MangaModel?>> searchManga(
apiUrl: value.apiUrl,
lang: value.lang,
sourceId: value.sourceId,
hasNextPage: value.hasNextPage ?? true,
dateFormat: value.dateFormat,
dateFormatLocale: value.dateFormatLocale);
newManga.add(newMangaa);

View file

@ -13,6 +13,13 @@ extension StringExtensions on String {
return substring(0, endIndex);
}
String substringBeforeLast(String pattern) {
final endIndex = lastIndexOf(pattern);
if (endIndex == -1) return substring(0);
return substring(0, endIndex);
}
}
extension LetExtension<T> on T {

View file

@ -31,10 +31,14 @@ String regCustomMatcher(
String source,
int group,
) {
RegExp exp = RegExp(source);
Iterable<Match> matches = exp.allMatches(input);
String? firstMatch = matches.first.group(group);
return firstMatch!;
try {
RegExp exp = RegExp(source);
Iterable<Match> matches = exp.allMatches(input);
String? firstMatch = matches.first.group(group);
return firstMatch!;
} catch (_) {
return input;
}
}
String padIndex(int index) {

View file

@ -765,6 +765,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
js_packer:
dependency: "direct main"
description:
name: js_packer
sha256: f45ffa90165a810d7134f0b96b54068e4aac9d80a8b181eafa3978ec6dbc66a3
url: "https://pub.dev"
source: hosted
version: "0.0.5"
json_annotation:
dependency: transitive
description:
@ -1157,6 +1165,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.5"
random_string:
dependency: "direct main"
description:
name: random_string
sha256: "03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
rfc_6901:
dependency: transitive
description:

View file

@ -54,6 +54,8 @@ dependencies:
media_kit_libs_android_video: ^1.0.6
media_kit_libs_ios_video: ^1.0.4
loadmore: ^2.1.0
random_string: ^2.3.1
js_packer: ^0.0.5
cupertino_icons: ^1.0.2