From 13249aed18510ae576d5f0b9ea1207ec11fe3e47 Mon Sep 17 00:00:00 2001 From: Mattia Date: Fri, 26 Feb 2021 16:08:48 +0100 Subject: [PATCH] Fix search issues. Fix `NoSuchMethodError` exception. Closes #102 --- lib/src/exceptions/exceptions.dart | 1 + .../exceptions/fatal_failure_exception.dart | 3 +- .../search_item_section_exception.dart | 10 +++ .../exceptions/youtube_explode_exception.dart | 2 +- lib/src/retry.dart | 4 +- .../responses/embed_page.dart | 2 +- .../responses/player_config_base.dart | 3 +- .../responses/search_page.dart | 28 ++++---- .../responses/watch_page.dart | 2 +- .../youtube_http_client.dart | 2 +- lib/src/search/search_client.dart | 72 ++++++++----------- lib/src/search/search_list.dart | 33 +++++++++ lib/src/videos/streams/streams_client.dart | 3 +- lib/src/videos/video.dart | 1 + pubspec.yaml | 1 + test/search_test.dart | 2 +- 16 files changed, 100 insertions(+), 69 deletions(-) create mode 100644 lib/src/exceptions/search_item_section_exception.dart create mode 100644 lib/src/search/search_list.dart diff --git a/lib/src/exceptions/exceptions.dart b/lib/src/exceptions/exceptions.dart index 885350b..74e468c 100644 --- a/lib/src/exceptions/exceptions.dart +++ b/lib/src/exceptions/exceptions.dart @@ -2,6 +2,7 @@ library youtube_explode.exceptions; export 'fatal_failure_exception.dart'; export 'request_limit_exceeded_exception.dart'; +export 'search_item_section_exception.dart'; export 'transient_failure_exception.dart'; export 'video_requires_purchase_exception.dart'; export 'video_unavailable_exception.dart'; diff --git a/lib/src/exceptions/fatal_failure_exception.dart b/lib/src/exceptions/fatal_failure_exception.dart index d4a2fd3..273b1e1 100644 --- a/lib/src/exceptions/fatal_failure_exception.dart +++ b/lib/src/exceptions/fatal_failure_exception.dart @@ -3,8 +3,7 @@ import 'package:http/http.dart'; import 'youtube_explode_exception.dart'; /// Exception thrown when a fatal failure occurs. -class FatalFailureException - implements YoutubeExplodeException { +class FatalFailureException implements YoutubeExplodeException { /// Description message @override final String message; diff --git a/lib/src/exceptions/search_item_section_exception.dart b/lib/src/exceptions/search_item_section_exception.dart new file mode 100644 index 0000000..0ffeb28 --- /dev/null +++ b/lib/src/exceptions/search_item_section_exception.dart @@ -0,0 +1,10 @@ +// + +import '../../youtube_explode_dart.dart'; + +/// Exception thrown when the Item Section is missing from a search request. +class SearchItemSectionException implements YoutubeExplodeException { + @override + // TODO: implement message + String get message => 'Failed to find the item section.'; +} diff --git a/lib/src/exceptions/youtube_explode_exception.dart b/lib/src/exceptions/youtube_explode_exception.dart index 44a75c8..618040f 100644 --- a/lib/src/exceptions/youtube_explode_exception.dart +++ b/lib/src/exceptions/youtube_explode_exception.dart @@ -5,4 +5,4 @@ abstract class YoutubeExplodeException implements Exception { /// YoutubeExplodeException(this.message); -} \ No newline at end of file +} diff --git a/lib/src/retry.dart b/lib/src/retry.dart index 562ea47..4df2cde 100644 --- a/lib/src/retry.dart +++ b/lib/src/retry.dart @@ -26,7 +26,9 @@ Future retry(FutureOr Function() function) async { /// Get "retry" cost of each YoutubeExplode exception. int getExceptionCost(Exception e) { - if (e is TransientFailureException || e is FormatException) { + if (e is TransientFailureException || + e is FormatException || + e is SearchItemSectionException) { return 1; } if (e is RequestLimitExceededException) { diff --git a/lib/src/reverse_engineering/responses/embed_page.dart b/lib/src/reverse_engineering/responses/embed_page.dart index 24a597d..ac608ea 100644 --- a/lib/src/reverse_engineering/responses/embed_page.dart +++ b/lib/src/reverse_engineering/responses/embed_page.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:html/dom.dart'; import 'package:html/parser.dart' as parser; -import 'package:youtube_explode_dart/src/reverse_engineering/responses/player_config_base.dart'; import '../../extensions/helpers_extension.dart'; import '../../retry.dart'; import '../youtube_http_client.dart'; +import 'player_config_base.dart'; /// class EmbedPage { diff --git a/lib/src/reverse_engineering/responses/player_config_base.dart b/lib/src/reverse_engineering/responses/player_config_base.dart index a91ac70..5761b74 100644 --- a/lib/src/reverse_engineering/responses/player_config_base.dart +++ b/lib/src/reverse_engineering/responses/player_config_base.dart @@ -1,6 +1,5 @@ /// Base class for PlayerConfig. abstract class PlayerConfigBase { - /// Root node. final T root; @@ -9,4 +8,4 @@ abstract class PlayerConfigBase { /// Player source url. String get sourceUrl; -} \ No newline at end of file +} diff --git a/lib/src/reverse_engineering/responses/search_page.dart b/lib/src/reverse_engineering/responses/search_page.dart index b019e19..aa31a1a 100644 --- a/lib/src/reverse_engineering/responses/search_page.dart +++ b/lib/src/reverse_engineering/responses/search_page.dart @@ -155,20 +155,22 @@ class _InitialData { _InitialData(this.root); - /* Cache results */ - - List _searchContent; - List _relatedVideos; - List _relatedQueries; - List getContentContext() { if (root.contents != null) { return root.contents.twoColumnSearchResultsRenderer.primaryContents .sectionListRenderer.contents.first.itemSectionRenderer.contents; } if (root.onResponseReceivedCommands != null) { - return root.onResponseReceivedCommands.first.appendContinuationItemsAction - .continuationItems[0].itemSectionRenderer.contents; + final itemSection = root + .onResponseReceivedCommands + .first + .appendContinuationItemsAction + .continuationItems[0] + .itemSectionRenderer; + if (itemSection == null) { + throw SearchItemSectionException(); + } + return itemSection.contents; } return null; } @@ -203,11 +205,11 @@ class _InitialData { } // Contains only [SearchVideo] or [SearchPlaylist] - List get searchContent => _searchContent ??= + List get searchContent => getContentContext().map(_parseContent).where((e) => e != null).toList(); List get relatedQueries => - (_relatedQueries ??= getContentContext() + getContentContext() ?.where((e) => e.horizontalCardListRenderer != null) ?.map((e) => e.horizontalCardListRenderer.cards) ?.firstOrNull @@ -217,16 +219,16 @@ class _InitialData { VideoId( Uri.parse(e.thumbnail.thumbnails.first.url).pathSegments[1]))) ?.toList() - ?.cast()) ?? + ?.cast() ?? const []; List get relatedVideos => - (_relatedVideos ??= getContentContext() + getContentContext() ?.where((e) => e.shelfRenderer != null) ?.map((e) => e.shelfRenderer.content.verticalListRenderer.items) ?.firstOrNull ?.map(_parseContent) - ?.toList()) ?? + ?.toList() ?? const []; String get continuationToken => _getContinuationToken(); diff --git a/lib/src/reverse_engineering/responses/watch_page.dart b/lib/src/reverse_engineering/responses/watch_page.dart index 4f5bdf5..5b180c7 100644 --- a/lib/src/reverse_engineering/responses/watch_page.dart +++ b/lib/src/reverse_engineering/responses/watch_page.dart @@ -1,6 +1,5 @@ import 'package:html/dom.dart'; import 'package:html/parser.dart' as parser; -import 'package:youtube_explode_dart/src/reverse_engineering/responses/player_config_base.dart'; import '../../../youtube_explode_dart.dart'; import '../../extensions/helpers_extension.dart'; @@ -9,6 +8,7 @@ import '../../videos/video_id.dart'; import '../youtube_http_client.dart'; import 'generated/player_response_json.g.dart'; import 'generated/watch_page_id.g.dart'; +import 'player_config_base.dart'; import 'player_response.dart'; /// diff --git a/lib/src/reverse_engineering/youtube_http_client.dart b/lib/src/reverse_engineering/youtube_http_client.dart index aa1b169..4e5d854 100644 --- a/lib/src/reverse_engineering/youtube_http_client.dart +++ b/lib/src/reverse_engineering/youtube_http_client.dart @@ -143,7 +143,7 @@ class YoutubeHttpClient extends http.BaseClient { request.headers[key] = _defaultHeaders[key]; } }); - // print('Request: $request'); + print('Request: $request'); // print('Stack:\n${StackTrace.current}'); return _httpClient.send(request); } diff --git a/lib/src/search/search_client.dart b/lib/src/search/search_client.dart index cb471b1..e44bdf8 100644 --- a/lib/src/search/search_client.dart +++ b/lib/src/search/search_client.dart @@ -1,11 +1,10 @@ -import '../common/common.dart'; -import '../reverse_engineering/responses/playlist_response.dart'; +import '../../youtube_explode_dart.dart'; +import '../retry.dart'; import '../reverse_engineering/responses/search_page.dart'; import '../reverse_engineering/youtube_http_client.dart'; -import '../videos/video.dart'; -import '../videos/video_id.dart'; import 'base_search_content.dart'; import 'search_query.dart'; +import 'search_list.dart'; /// YouTube search queries. class SearchClient { @@ -14,50 +13,29 @@ class SearchClient { /// Initializes an instance of [SearchClient] SearchClient(this._httpClient); - /// Enumerates videos returned by the specified search query. - /// (from the YouTube Embedded API) - Stream