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
- 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`.

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 '../playlists/playlists.dart';
import '../reverse_engineering/responses/responses.dart';
@ -50,8 +54,34 @@ class ChannelClient {
}
/// Enumerates videos uploaded by the specified channel.
Stream<Video> getUploads(ChannelId id) {
var playlistId = 'UU${id.value.substringAfter('UC')}';
/// If you want a full list of uploads see [getUploadsFromPage]
Stream<Video> getUploads(dynamic channelId) {
channelId = ChannelId.fromString(channelId);
var playlistId = 'UU${channelId.value.substringAfter('UC')}';
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);
@override
String toString() => '(ChannelVideo) $videoId ($videoTitle)';
String toString() => '[ChannelVideo] $videoTitle ($videoId)';
@override
List<Object> get props => [videoId];

View File

@ -5,3 +5,4 @@ export 'channel_client.dart';
export 'channel_id.dart';
export 'channel_video.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 '../youtube_http_client.dart';
class ChannelWatchPage {
class ChannelUploadPage {
final String channelId;
final Document _root;
@ -47,21 +47,34 @@ class ChannelWatchPage {
return str.substring(0, lastI + 1);
}
ChannelWatchPage(this._root, this.channelId);
ChannelUploadPage(this._root, this.channelId, [_InitialData initialData])
: _initialData = initialData;
Future<ChannelWatchPage> nextPage() {}
static Future<ChannelWatchPage> get(
YoutubeHttpClient httpClient, String channelId) {
Future<ChannelUploadPage> nextPage(YoutubeHttpClient httpClient) {
if (initialData.continuation.isEmpty) {
return Future.value(null);
}
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 {
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);
}
@ -118,10 +131,11 @@ class _InitialData {
return null;
}
List<dynamic> get uploads => _uploads ??= getContentContext(_root)
List<ChannelVideo> get uploads => _uploads ??= getContentContext(_root)
?.map(_parseContent)
?.where((e) => e != null)
?.toList();
?.toList()
?.cast<ChannelVideo>();
String get continuation => _continuation ??=
getContinuationContext(_root)?.getValue('continuation') ?? '';

View File

@ -1,6 +1,6 @@
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.
version: 1.4.0
version: 1.4.1
homepage: https://github.com/Hexer10/youtube_explode_dart
environment: