youtube_explode/lib/src/reverse_engineering/pages/search_page.dart

243 lines
7.8 KiB
Dart
Raw Normal View History

2021-03-11 14:20:10 +01:00
import 'package:collection/collection.dart';
import 'package:html/parser.dart' as parser;
import '../../../youtube_explode_dart.dart';
2020-06-13 22:54:53 +02:00
import '../../extensions/helpers_extension.dart';
import '../../retry.dart';
2020-11-01 15:05:19 +01:00
import '../../search/base_search_content.dart';
2021-07-21 02:06:02 +02:00
import '../models/initial_data.dart';
2021-09-28 16:49:38 +02:00
import '../models/youtube_page.dart';
2020-07-16 20:02:54 +02:00
///
2021-07-21 02:06:02 +02:00
class SearchPage extends YoutubePage<_InitialData> {
2020-07-16 20:02:54 +02:00
///
2020-06-13 22:54:53 +02:00
final String queryString;
2021-08-31 18:06:34 +02:00
late final List<BaseSearchContent> searchContent = initialData.searchContent;
late final List<dynamic> relatedVideos = initialData.relatedVideos;
late final int estimatedResults = initialData.estimatedResults;
2021-07-21 02:06:02 +02:00
/// InitialData
SearchPage.id(this.queryString, _InitialData initialData)
: super(null, null, initialData);
2020-06-13 22:54:53 +02:00
2021-03-11 14:20:10 +01:00
Future<SearchPage?> nextPage(YoutubeHttpClient httpClient) async {
2021-07-21 02:06:02 +02:00
if (initialData.continuationToken?.isEmpty == null ||
initialData.estimatedResults == 0) {
2020-06-13 22:54:53 +02:00
return null;
}
2021-07-21 02:06:02 +02:00
var data =
await httpClient.sendPost('search', initialData.continuationToken!);
return SearchPage.id(queryString, _InitialData(data));
2020-06-13 22:54:53 +02:00
}
2020-07-16 20:02:54 +02:00
///
static Future<SearchPage> get(
YoutubeHttpClient httpClient, String queryString,
2021-07-21 02:06:02 +02:00
{SearchFilter filter = const SearchFilter('')}) {
var url =
'https://www.youtube.com/results?search_query=${Uri.encodeQueryComponent(queryString)}&sp=${filter.value}';
return retry(httpClient, () async {
2020-09-21 17:34:03 +02:00
var raw = await httpClient.getString(url);
2020-06-13 22:54:53 +02:00
return SearchPage.parse(raw, queryString);
});
2020-09-21 17:34:03 +02:00
// ask for next page
}
2020-07-16 20:02:54 +02:00
///
2021-07-21 02:06:02 +02:00
SearchPage.parse(String raw, this.queryString)
: super(parser.parse(raw), (root) => _InitialData(root));
}
2021-07-21 02:06:02 +02:00
class _InitialData extends InitialData {
_InitialData(JsonMap root) : super(root);
2021-07-21 02:06:02 +02:00
List<JsonMap>? getContentContext() {
2021-03-11 14:20:10 +01:00
if (root['contents'] != null) {
return root
.get('contents')
?.get('twoColumnSearchResultsRenderer')
?.get('primaryContents')
?.get('sectionListRenderer')
?.getList('contents')
?.firstOrNull
?.get('itemSectionRenderer')
?.getList('contents');
2020-06-13 22:54:53 +02:00
}
2021-03-11 14:20:10 +01:00
if (root['onResponseReceivedCommands'] != null) {
return root
.getList('onResponseReceivedCommands')
?.firstOrNull
?.get('appendContinuationItemsAction')
?.getList('continuationItems')
?.firstOrNull
?.get('itemSectionRenderer')
?.getList('contents');
2020-06-13 22:54:53 +02:00
}
2020-10-17 22:09:52 +02:00
return null;
2020-06-13 22:54:53 +02:00
}
2021-03-11 14:20:10 +01:00
String? _getContinuationToken() {
if (root['contents'] != null) {
var contents = root
.get('contents')
?.get('twoColumnSearchResultsRenderer')
?.get('primaryContents')
?.get('sectionListRenderer')
?.getList('contents');
2020-09-21 17:34:03 +02:00
2021-03-11 14:20:10 +01:00
if (contents == null || contents.length <= 1) {
2020-09-21 17:34:03 +02:00
return null;
}
2021-03-11 14:20:10 +01:00
return contents
.elementAtSafe(1)
?.get('continuationItemRenderer')
?.get('continuationEndpoint')
?.get('continuationCommand')
?.getT<String>('token');
2020-06-13 22:54:53 +02:00
}
2021-03-11 14:20:10 +01:00
if (root['onResponseReceivedCommands'] != null) {
2020-09-21 17:34:03 +02:00
return root
2021-03-11 14:20:10 +01:00
.getList('onResponseReceivedCommands')
?.firstOrNull
?.get('appendContinuationItemsAction')
?.getList('continuationItems')
?.elementAtSafe(1)
?.get('continuationItemRenderer')
?.get('continuationEndpoint')
?.get('continuationCommand')
?.getT<String>('token');
2020-06-13 22:54:53 +02:00
}
return null;
}
// Contains only [SearchVideo] or [SearchPlaylist]
late final List<BaseSearchContent> searchContent =
getContentContext()?.map(_parseContent).whereNotNull().toList() ??
const [];
2020-06-13 22:54:53 +02:00
List<dynamic> get relatedVideos =>
getContentContext()
2021-03-11 14:20:10 +01:00
?.where((e) => e['shelfRenderer'] != null)
.map((e) => e
.get('shelfRenderer')
?.get('content')
?.get('verticalListRenderer')
?.getList('items'))
2021-03-11 14:20:10 +01:00
.firstOrNull
2020-06-13 22:54:53 +02:00
?.map(_parseContent)
2021-03-11 14:20:10 +01:00
.whereNotNull()
.toList() ??
2020-06-13 22:54:53 +02:00
const [];
2021-03-11 14:20:10 +01:00
late final String? continuationToken = _getContinuationToken();
late final int estimatedResults =
int.parse(root.getT<String>('estimatedResults') ?? '0');
2021-07-21 02:06:02 +02:00
BaseSearchContent? _parseContent(JsonMap? content) {
if (content == null) {
return null;
}
2021-03-11 14:20:10 +01:00
if (content['videoRenderer'] != null) {
var renderer = content.get('videoRenderer')!;
2021-06-24 15:23:35 +02:00
// root.get('ownerText')?.getT<List<dynamic>>('runs')?.parseRuns() ??
2020-06-13 22:54:53 +02:00
return SearchVideo(
2021-03-11 14:20:10 +01:00
VideoId(renderer.getT<String>('videoId')!),
2022-02-03 12:06:09 +01:00
renderer
.get('title')!
.getT<List<dynamic>>('runs')!
.cast<Map<dynamic, dynamic>>()
.parseRuns(),
renderer
.get('ownerText')!
.getT<List<dynamic>>('runs')!
.cast<Map<dynamic, dynamic>>()
.parseRuns(),
2021-06-24 15:23:35 +02:00
renderer
.getList('detailedMetadataSnippets')
?.firstOrNull
?.get('snippetText')
?.getT<List<dynamic>>('runs')
2022-02-03 12:06:09 +01:00
?.cast<Map<dynamic, dynamic>>()
.parseRuns() ??
2021-06-24 15:23:35 +02:00
'',
2021-03-11 14:20:10 +01:00
renderer.get('lengthText')?.getT<String>('simpleText') ?? '',
int.parse(renderer
.get('viewCountText')
?.getT<String>('simpleText')
?.stripNonDigits()
.nullIfWhitespace ??
renderer
.get('viewCountText')
?.getList('runs')
?.firstOrNull
?.getT<String>('text')
?.stripNonDigits()
.nullIfWhitespace ??
'0'),
2021-03-11 14:20:10 +01:00
(renderer.get('thumbnail')?.getList('thumbnails') ?? const [])
.map((e) =>
Thumbnail(Uri.parse(e['url']), e['height'], e['width']))
2021-02-27 18:58:42 +01:00
.toList(),
2021-03-11 14:20:10 +01:00
renderer.get('publishedTimeText')?.getT<String>('simpleText'),
renderer
.get('viewCountText')
?.getList('runs')
?.elementAtSafe(1)
?.getT<String>('text')
?.trim() ==
'watching',
2022-02-03 12:06:09 +01:00
renderer
.get('ownerText')!
.getList('runs')!
.first
.get('navigationEndpoint')!
.get('browseEndpoint')!
.getT<String>('browseId')!);
}
2021-10-16 08:47:24 +02:00
if (content['radioRenderer'] != null ||
content['playlistRenderer'] != null) {
var renderer =
(content.get('radioRenderer') ?? content.get('playlistRenderer'))!;
2020-06-13 22:54:53 +02:00
return SearchPlaylist(
2021-10-16 08:47:24 +02:00
PlaylistId(renderer.getT<String>('playlistId')!),
renderer.get('title')!.getT<String>('simpleText')!,
2022-02-03 12:06:09 +01:00
renderer
.get('videoCountText')
?.getT<List<dynamic>>('runs')
?.cast<Map<dynamic, dynamic>>()
.parseRuns()
.parseInt() ??
0,
2021-10-16 08:47:24 +02:00
(renderer.getList('thumbnails')?[0].getList('thumbnails') ?? const [])
.map((e) => Thumbnail(Uri.parse(e['url']), e['height'], e['width']))
.toList(),
);
2021-03-20 18:31:53 +01:00
}
if (content['channelRenderer'] != null) {
var renderer = content.get('channelRenderer')!;
2021-03-20 18:31:53 +01:00
return SearchChannel(
ChannelId(renderer.getT<String>('channelId')!),
2021-03-18 22:22:55 +01:00
renderer.get('title')!.getT<String>('simpleText')!,
renderer.get('descriptionSnippet')?.getList('runs')?.parseRuns() ??
'',
renderer
.get('videoCountText')
?.getList('runs')
?.first
.getT<String>('text')
?.parseInt() ??
-1);
}
// Here ignore 'horizontalCardListRenderer' & 'shelfRenderer'
return null;
}
}