This commit is contained in:
Mattia 2020-07-12 18:24:22 +02:00
parent de6caf2949
commit 995738680c
7 changed files with 80 additions and 15 deletions

View File

@ -1,3 +1,6 @@
## 1.4.1
- Implement `getUploadsFromPage` to a channel uploaded videos directly from the YouTube page.
## 1.4.0 ## 1.4.0
- Add ChannelId property to Video class. - Add ChannelId property to Video class.
- Implement `thumbnails` for playlists. The playlist's thumbnail is the same as the thumbnail of its first video. If the playlist is empty, then this property is `null`. - Implement `thumbnails` for playlists. The playlist's thumbnail is the same as the thumbnail of its first video. If the playlist is empty, then this property is `null`.

View File

@ -1,3 +1,7 @@
import 'package:youtube_explode_dart/src/channels/channel_video.dart';
import 'package:youtube_explode_dart/src/channels/video_sorting.dart';
import 'package:youtube_explode_dart/src/reverse_engineering/responses/channel_upload_page.dart';
import '../extensions/helpers_extension.dart'; import '../extensions/helpers_extension.dart';
import '../playlists/playlists.dart'; import '../playlists/playlists.dart';
import '../reverse_engineering/responses/responses.dart'; import '../reverse_engineering/responses/responses.dart';
@ -50,8 +54,34 @@ class ChannelClient {
} }
/// Enumerates videos uploaded by the specified channel. /// Enumerates videos uploaded by the specified channel.
Stream<Video> getUploads(ChannelId id) { /// If you want a full list of uploads see [getUploadsFromPage]
var playlistId = 'UU${id.value.substringAfter('UC')}'; Stream<Video> getUploads(dynamic channelId) {
channelId = ChannelId.fromString(channelId);
var playlistId = 'UU${channelId.value.substringAfter('UC')}';
return PlaylistClient(_httpClient).getVideos(PlaylistId(playlistId)); return PlaylistClient(_httpClient).getVideos(PlaylistId(playlistId));
} }
/// Enumerates videos uploaded by the specified channel.
/// This fetches thru all the uploads pages of the channel so it is
/// recommended to use _.take_ (or any other method) to limit the
/// search result. Every page has 30 results.
///
/// Note that this endpoint provides less info about each video
/// (only the Title and VideoId).
Stream<ChannelVideo> getUploadsFromPage(dynamic channelId,
[VideoSorting videoSorting = VideoSorting.newest]) async* {
channelId = ChannelId.fromString(channelId);
var page = await ChannelUploadPage.get(
_httpClient, channelId.value, videoSorting.code);
yield* Stream.fromIterable(page.initialData.uploads);
// ignore: literal_only_boolean_expressions
while (true) {
page = await page.nextPage(_httpClient);
if (page == null) {
return;
}
yield* Stream.fromIterable(page.initialData.uploads);
}
}
} }

View File

@ -13,7 +13,7 @@ class ChannelVideo with EquatableMixin {
ChannelVideo(this.videoId, this.videoTitle); ChannelVideo(this.videoId, this.videoTitle);
@override @override
String toString() => '(ChannelVideo) $videoId ($videoTitle)'; String toString() => '[ChannelVideo] $videoTitle ($videoId)';
@override @override
List<Object> get props => [videoId]; List<Object> get props => [videoId];

View File

@ -5,3 +5,4 @@ export 'channel_client.dart';
export 'channel_id.dart'; export 'channel_id.dart';
export 'channel_video.dart'; export 'channel_video.dart';
export 'username.dart'; export 'username.dart';
export 'video_sorting.dart';

View File

@ -0,0 +1,17 @@
/// Metadata about video are sorted with [ChannelClient.getUploadsFromPage]
class VideoSorting {
/// Code used to fetch the video.
/// Used internally.
final String code;
/// Sort from the newest video
static const VideoSorting newest = VideoSorting._('dd');
/// Sort from the oldest video.
static const oldest = VideoSorting._('da');
/// Sort from the most popular video.
static const popularity = VideoSorting._('p');
const VideoSorting._(this.code);
}

View File

@ -9,7 +9,7 @@ import '../../retry.dart';
import '../../videos/videos.dart'; import '../../videos/videos.dart';
import '../youtube_http_client.dart'; import '../youtube_http_client.dart';
class ChannelWatchPage { class ChannelUploadPage {
final String channelId; final String channelId;
final Document _root; final Document _root;
@ -47,21 +47,34 @@ class ChannelWatchPage {
return str.substring(0, lastI + 1); return str.substring(0, lastI + 1);
} }
ChannelWatchPage(this._root, this.channelId); ChannelUploadPage(this._root, this.channelId, [_InitialData initialData])
: _initialData = initialData;
Future<ChannelWatchPage> nextPage() {} Future<ChannelUploadPage> nextPage(YoutubeHttpClient httpClient) {
if (initialData.continuation.isEmpty) {
static Future<ChannelWatchPage> get( return Future.value(null);
YoutubeHttpClient httpClient, String channelId) { }
var url = var url =
'https://www.youtube.com/channel/$channelId/videos?view=0&sort=dd&flow=grid'; 'https://www.youtube.com/browse_ajax?ctoken=${initialData.continuation}&continuation=${initialData.continuation}&itct=${initialData.clickTrackingParams}';
return retry(() async { return retry(() async {
var raw = await httpClient.getString(url); var raw = await httpClient.getString(url);
return ChannelWatchPage.parse(raw, channelId); return ChannelUploadPage(
null, channelId, _InitialData(json.decode(raw)[1]));
}); });
} }
ChannelWatchPage.parse(String raw, this.channelId) static Future<ChannelUploadPage> get(
YoutubeHttpClient httpClient, String channelId, String sorting) {
assert(sorting != null);
var url =
'https://www.youtube.com/channel/$channelId/videos?view=0&sort=$sorting&flow=grid';
return retry(() async {
var raw = await httpClient.getString(url);
return ChannelUploadPage.parse(raw, channelId);
});
}
ChannelUploadPage.parse(String raw, this.channelId)
: _root = parser.parse(raw); : _root = parser.parse(raw);
} }
@ -118,10 +131,11 @@ class _InitialData {
return null; return null;
} }
List<dynamic> get uploads => _uploads ??= getContentContext(_root) List<ChannelVideo> get uploads => _uploads ??= getContentContext(_root)
?.map(_parseContent) ?.map(_parseContent)
?.where((e) => e != null) ?.where((e) => e != null)
?.toList(); ?.toList()
?.cast<ChannelVideo>();
String get continuation => _continuation ??= String get continuation => _continuation ??=
getContinuationContext(_root)?.getValue('continuation') ?? ''; getContinuationContext(_root)?.getValue('continuation') ?? '';

View File

@ -1,6 +1,6 @@
name: youtube_explode_dart name: youtube_explode_dart
description: A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key. description: A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
version: 1.4.0 version: 1.4.1
homepage: https://github.com/Hexer10/youtube_explode_dart homepage: https://github.com/Hexer10/youtube_explode_dart
environment: environment: