import 'package:collection/collection.dart'; import 'package:html/parser.dart' as parser; import '../../../youtube_explode_dart.dart'; import '../../extensions/helpers_extension.dart'; import '../../retry.dart'; import '../../search/base_search_content.dart'; import '../../search/search_channel.dart'; import '../models/initial_data.dart'; import '../models/youtube_page.dart'; /// class SearchPage extends YoutubePage<_InitialData> { /// final String queryString; late final List searchContent = initialData.searchContent; late final List relatedVideos = initialData.relatedVideos; late final int estimatedResults = initialData.estimatedResults; /// InitialData SearchPage.id(this.queryString, _InitialData initialData) : super(null, null, initialData); Future nextPage(YoutubeHttpClient httpClient) async { if (initialData.continuationToken?.isEmpty == null || initialData.estimatedResults == 0) { return null; } var data = await httpClient.sendPost('search', initialData.continuationToken!); return SearchPage.id(queryString, _InitialData(data)); } /// static Future get( YoutubeHttpClient httpClient, String queryString, {SearchFilter filter = const SearchFilter('')}) { var url = 'https://www.youtube.com/results?search_query=${Uri.encodeQueryComponent(queryString)}&sp=${filter.value}'; return retry(httpClient, () async { var raw = await httpClient.getString(url); return SearchPage.parse(raw, queryString); }); // ask for next page } /// SearchPage.parse(String raw, this.queryString) : super(parser.parse(raw), (root) => _InitialData(root)); } class _InitialData extends InitialData { _InitialData(JsonMap root) : super(root); List? getContentContext() { if (root['contents'] != null) { return root .get('contents') ?.get('twoColumnSearchResultsRenderer') ?.get('primaryContents') ?.get('sectionListRenderer') ?.getList('contents') ?.firstOrNull ?.get('itemSectionRenderer') ?.getList('contents'); } if (root['onResponseReceivedCommands'] != null) { return root .getList('onResponseReceivedCommands') ?.firstOrNull ?.get('appendContinuationItemsAction') ?.getList('continuationItems') ?.firstOrNull ?.get('itemSectionRenderer') ?.getList('contents'); } return null; } String? _getContinuationToken() { if (root['contents'] != null) { var contents = root .get('contents') ?.get('twoColumnSearchResultsRenderer') ?.get('primaryContents') ?.get('sectionListRenderer') ?.getList('contents'); if (contents == null || contents.length <= 1) { return null; } return contents .elementAtSafe(1) ?.get('continuationItemRenderer') ?.get('continuationEndpoint') ?.get('continuationCommand') ?.getT('token'); } if (root['onResponseReceivedCommands'] != null) { return root .getList('onResponseReceivedCommands') ?.firstOrNull ?.get('appendContinuationItemsAction') ?.getList('continuationItems') ?.elementAtSafe(1) ?.get('continuationItemRenderer') ?.get('continuationEndpoint') ?.get('continuationCommand') ?.getT('token'); } return null; } // Contains only [SearchVideo] or [SearchPlaylist] late final List searchContent = getContentContext()?.map(_parseContent).whereNotNull().toList() ?? const []; List get relatedVideos => getContentContext() ?.where((e) => e['shelfRenderer'] != null) .map((e) => e .get('shelfRenderer') ?.get('content') ?.get('verticalListRenderer') ?.getList('items')) .firstOrNull ?.map(_parseContent) .whereNotNull() .toList() ?? const []; late final String? continuationToken = _getContinuationToken(); late final int estimatedResults = int.parse(root.getT('estimatedResults') ?? '0'); BaseSearchContent? _parseContent(JsonMap? content) { if (content == null) { return null; } if (content['videoRenderer'] != null) { var renderer = content.get('videoRenderer')!; // root.get('ownerText')?.getT>('runs')?.parseRuns() ?? return SearchVideo( VideoId(renderer.getT('videoId')!), renderer .get('title')! .getT>('runs')! .cast>() .parseRuns(), renderer .get('ownerText')! .getT>('runs')! .cast>() .parseRuns(), renderer .getList('detailedMetadataSnippets') ?.firstOrNull ?.get('snippetText') ?.getT>('runs') ?.cast>() .parseRuns() ?? '', renderer.get('lengthText')?.getT('simpleText') ?? '', int.parse(renderer .get('viewCountText') ?.getT('simpleText') ?.stripNonDigits() .nullIfWhitespace ?? renderer .get('viewCountText') ?.getList('runs') ?.firstOrNull ?.getT('text') ?.stripNonDigits() .nullIfWhitespace ?? '0'), (renderer.get('thumbnail')?.getList('thumbnails') ?? const []) .map((e) => Thumbnail(Uri.parse(e['url']), e['height'], e['width'])) .toList(), renderer.get('publishedTimeText')?.getT('simpleText'), renderer .get('viewCountText') ?.getList('runs') ?.elementAtSafe(1) ?.getT('text') ?.trim() == 'watching', renderer .get('ownerText')! .getList('runs')! .first .get('navigationEndpoint')! .get('browseEndpoint')! .getT('browseId')!); } if (content['radioRenderer'] != null || content['playlistRenderer'] != null) { var renderer = (content.get('radioRenderer') ?? content.get('playlistRenderer'))!; return SearchPlaylist( PlaylistId(renderer.getT('playlistId')!), renderer.get('title')!.getT('simpleText')!, renderer .get('videoCountText') ?.getT>('runs') ?.cast>() .parseRuns() .parseInt() ?? 0, (renderer.getList('thumbnails')?[0].getList('thumbnails') ?? const []) .map((e) => Thumbnail(Uri.parse(e['url']), e['height'], e['width'])) .toList(), ); } if (content['channelRenderer'] != null) { var renderer = content.get('channelRenderer')!; return SearchChannel( ChannelId(renderer.getT('channelId')!), renderer.get('title')!.getT('simpleText')!, renderer.get('descriptionSnippet')?.getList('runs')?.parseRuns() ?? '', renderer .get('videoCountText') ?.getList('runs') ?.first .getT('text') ?.parseInt() ?? -1); } // Here ignore 'horizontalCardListRenderer' & 'shelfRenderer' return null; } }