Version 1.9.3
This commit is contained in:
parent
0f3d5e7a16
commit
d405feff2f
|
@ -1,3 +1,6 @@
|
||||||
|
## 1.9.3
|
||||||
|
- `getUploadsFromPage` now returns an instance of `ChannelUploadsList`.
|
||||||
|
|
||||||
## 1.9.2+2
|
## 1.9.2+2
|
||||||
- Fix `videoThumbnail` in `ChannelVideo`.
|
- Fix `videoThumbnail` in `ChannelVideo`.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:youtube_explode_dart/src/channels/channel_uploads_list.dart';
|
||||||
|
|
||||||
import '../common/common.dart';
|
import '../common/common.dart';
|
||||||
import '../extensions/helpers_extension.dart';
|
import '../extensions/helpers_extension.dart';
|
||||||
import '../playlists/playlists.dart';
|
import '../playlists/playlists.dart';
|
||||||
|
@ -9,7 +11,7 @@ import '../videos/video.dart';
|
||||||
import '../videos/video_id.dart';
|
import '../videos/video_id.dart';
|
||||||
import 'channel.dart';
|
import 'channel.dart';
|
||||||
import 'channel_id.dart';
|
import 'channel_id.dart';
|
||||||
import 'channel_video.dart';
|
import 'channel_uploads_list.dart';
|
||||||
import 'channels.dart';
|
import 'channels.dart';
|
||||||
import 'username.dart';
|
import 'username.dart';
|
||||||
import 'video_sorting.dart';
|
import 'video_sorting.dart';
|
||||||
|
@ -113,20 +115,33 @@ class ChannelClient {
|
||||||
///
|
///
|
||||||
/// Note that this endpoint provides less info about each video
|
/// Note that this endpoint provides less info about each video
|
||||||
/// (only the Title and VideoId).
|
/// (only the Title and VideoId).
|
||||||
Stream<ChannelVideo> getUploadsFromPage(dynamic channelId,
|
Future<ChannelUploadsList> getUploadsFromPage(dynamic channelId,
|
||||||
[VideoSorting videoSorting = VideoSorting.newest]) async* {
|
[VideoSorting videoSorting = VideoSorting.newest]) async {
|
||||||
channelId = ChannelId.fromString(channelId);
|
channelId = ChannelId.fromString(channelId);
|
||||||
ChannelUploadPage? page = await ChannelUploadPage.get(
|
final page = await ChannelUploadPage.get(
|
||||||
_httpClient, (channelId as ChannelId).value, videoSorting.code);
|
_httpClient, (channelId as ChannelId).value, videoSorting.code);
|
||||||
yield* Stream.fromIterable(page.initialData.uploads);
|
|
||||||
|
|
||||||
// ignore: literal_only_boolean_expressions
|
final channel = await get(channelId);
|
||||||
while (true) {
|
|
||||||
page = await page!.nextPage(_httpClient);
|
return ChannelUploadsList(
|
||||||
if (page == null) {
|
page.initialData.uploads
|
||||||
return;
|
.map((e) => Video(
|
||||||
}
|
e.videoId,
|
||||||
yield* Stream.fromIterable(page.initialData.uploads);
|
e.videoTitle,
|
||||||
}
|
channel.title,
|
||||||
|
channelId,
|
||||||
|
e.videoUploadDate.toDateTime(),
|
||||||
|
null,
|
||||||
|
'',
|
||||||
|
e.videoDuration,
|
||||||
|
ThumbnailSet(e.videoId.value),
|
||||||
|
null,
|
||||||
|
Engagement(e.videoViews, null, null),
|
||||||
|
false))
|
||||||
|
.toList(),
|
||||||
|
channel.title,
|
||||||
|
channelId,
|
||||||
|
page,
|
||||||
|
_httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:youtube_explode_dart/src/reverse_engineering/responses/channel_upload_page.dart';
|
||||||
|
|
||||||
|
import '../../youtube_explode_dart.dart';
|
||||||
|
import '../extensions/helpers_extension.dart';
|
||||||
|
|
||||||
|
/// This list contains a channel uploads.
|
||||||
|
/// This behaves like a [List] but has the [SearchList.nextPage] to get the next batch of videos.
|
||||||
|
class ChannelUploadsList extends DelegatingList<Video> {
|
||||||
|
final ChannelUploadPage _page;
|
||||||
|
final YoutubeHttpClient _httpClient;
|
||||||
|
|
||||||
|
final String author;
|
||||||
|
final ChannelId channel;
|
||||||
|
|
||||||
|
/// Construct an instance of [SearchList]
|
||||||
|
/// See [SearchList]
|
||||||
|
ChannelUploadsList(
|
||||||
|
List<Video> base, this.author, this.channel, this._page, this._httpClient)
|
||||||
|
: super(base);
|
||||||
|
|
||||||
|
/// Fetches the next batch of videos or returns null if there are no more
|
||||||
|
/// results.
|
||||||
|
Future<ChannelUploadsList?> nextPage() async {
|
||||||
|
final page = await _page.nextPage(_httpClient);
|
||||||
|
if (page == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ChannelUploadsList(
|
||||||
|
page.initialData.uploads
|
||||||
|
.map((e) => Video(
|
||||||
|
e.videoId,
|
||||||
|
e.videoTitle,
|
||||||
|
author,
|
||||||
|
channel,
|
||||||
|
e.videoUploadDate.toDateTime(),
|
||||||
|
null,
|
||||||
|
'',
|
||||||
|
e.videoDuration,
|
||||||
|
ThumbnailSet(e.videoId.value),
|
||||||
|
null,
|
||||||
|
Engagement(e.videoViews, null, null),
|
||||||
|
false))
|
||||||
|
.toList(),
|
||||||
|
author,
|
||||||
|
channel,
|
||||||
|
page,
|
||||||
|
_httpClient);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
import '../videos/video_id.dart';
|
import '../videos/video_id.dart';
|
||||||
|
|
||||||
/// Metadata related to a search query result (playlist)
|
/// Metadata related to a search query result (playlist)
|
||||||
|
@ -15,8 +16,16 @@ class ChannelVideo with EquatableMixin {
|
||||||
/// Video thumbnail
|
/// Video thumbnail
|
||||||
final String videoThumbnail;
|
final String videoThumbnail;
|
||||||
|
|
||||||
|
/// Video upload date.
|
||||||
|
/// Formatted like 10 hours ago
|
||||||
|
final String videoUploadDate;
|
||||||
|
|
||||||
|
/// Video view count.
|
||||||
|
final int videoViews;
|
||||||
|
|
||||||
/// Initialize an instance of [ChannelVideo]
|
/// Initialize an instance of [ChannelVideo]
|
||||||
ChannelVideo(this.videoId, this.videoTitle, this.videoDuration, this.videoThumbnail);
|
ChannelVideo(this.videoId, this.videoTitle, this.videoDuration,
|
||||||
|
this.videoThumbnail, this.videoUploadDate, this.videoViews);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '[ChannelVideo] $videoTitle ($videoId)';
|
String toString() => '[ChannelVideo] $videoTitle ($videoId)';
|
||||||
|
|
|
@ -163,24 +163,23 @@ class _InitialData {
|
||||||
|
|
||||||
var video = content.get('gridVideoRenderer')!;
|
var video = content.get('gridVideoRenderer')!;
|
||||||
return ChannelVideo(
|
return ChannelVideo(
|
||||||
VideoId(video.getT<String>('videoId')!),
|
VideoId(video.getT<String>('videoId')!),
|
||||||
video.get('title')?.getT<String>('simpleText') ??
|
video.get('title')?.getT<String>('simpleText') ??
|
||||||
video.get('title')?.getList('runs')?.map((e) => e['text']).join() ??
|
video.get('title')?.getList('runs')?.map((e) => e['text']).join() ??
|
||||||
'',
|
'',
|
||||||
video
|
video
|
||||||
.getList('thumbnailOverlays')
|
.getList('thumbnailOverlays')
|
||||||
?.firstOrNull
|
?.firstOrNull
|
||||||
?.get('thumbnailOverlayTimeStatusRenderer')
|
?.get('thumbnailOverlayTimeStatusRenderer')
|
||||||
?.get('text')
|
?.get('text')
|
||||||
?.getT<String>('simpleText')
|
?.getT<String>('simpleText')
|
||||||
?.toDuration() ??
|
?.toDuration() ??
|
||||||
Duration.zero,
|
Duration.zero,
|
||||||
video
|
video.get('thumbnail')?.getList('thumbnails')?.last.getT<String>('url') ??
|
||||||
.get('thumbnail')
|
'',
|
||||||
?.getList('thumbnails')
|
video.get('publishedTimeText')?.getT<String>('simpleText') ?? '',
|
||||||
?.last
|
video.get('viewCountText')?.getT<String>('simpleText')?.parseInt() ?? 0,
|
||||||
.getT<String>('url') ??
|
);
|
||||||
'');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,17 @@ import '../../youtube_explode_dart.dart';
|
||||||
import '../extensions/helpers_extension.dart';
|
import '../extensions/helpers_extension.dart';
|
||||||
|
|
||||||
/// This list contains search videos.
|
/// This list contains search videos.
|
||||||
|
/// /// This behaves like a [List] but has the [SearchList.nextPage] to get the next batch of videos.
|
||||||
class SearchList extends DelegatingList<Video> {
|
class SearchList extends DelegatingList<Video> {
|
||||||
final SearchPage _page;
|
final SearchPage _page;
|
||||||
final YoutubeHttpClient _httpClient;
|
final YoutubeHttpClient _httpClient;
|
||||||
|
|
||||||
///
|
/// Construct an instance of [SearchList]
|
||||||
|
/// See [SearchList]
|
||||||
SearchList(List<Video> base, this._page, this._httpClient) : super(base);
|
SearchList(List<Video> base, this._page, this._httpClient) : super(base);
|
||||||
|
|
||||||
///
|
/// Fetches the next batch of videos or returns null if there are no more
|
||||||
|
/// results.
|
||||||
Future<SearchList?> nextPage() async {
|
Future<SearchList?> nextPage() async {
|
||||||
final page = await _page.nextPage(_httpClient);
|
final page = await _page.nextPage(_httpClient);
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
|
|
|
@ -52,6 +52,7 @@ class Video with EquatableMixin {
|
||||||
|
|
||||||
/// Used internally.
|
/// Used internally.
|
||||||
/// Shouldn't be used in the code.
|
/// Shouldn't be used in the code.
|
||||||
|
/// TODO: Deprecate this method
|
||||||
final WatchPage? watchPage;
|
final WatchPage? watchPage;
|
||||||
|
|
||||||
/// Returns true if the watch page is available for this video.
|
/// Returns true if the watch page is available for this video.
|
||||||
|
|
|
@ -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.9.2+2
|
version: 1.9.3
|
||||||
|
|
||||||
homepage: https://github.com/Hexer10/youtube_explode_dart
|
homepage: https://github.com/Hexer10/youtube_explode_dart
|
||||||
|
|
||||||
|
|
|
@ -65,12 +65,9 @@ void main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Get videos of a youtube channel from the uploads page', () async {
|
test('Get videos of a youtube channel from the uploads page', () async {
|
||||||
var videos = await yt!.channels
|
var videos =
|
||||||
.getUploadsFromPage('UCEnBXANsKmyj2r9xVyKoDiQ')
|
await yt!.channels.getUploadsFromPage('UCEnBXANsKmyj2r9xVyKoDiQ');
|
||||||
.take(30)
|
|
||||||
.toList();
|
|
||||||
expect(videos, hasLength(30));
|
expect(videos, hasLength(30));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -79,7 +76,10 @@ void main() {
|
||||||
var aboutPage = await yt!.channels.getAboutPageByUsername(
|
var aboutPage = await yt!.channels.getAboutPageByUsername(
|
||||||
'PewDiePie'); // or yt.channels.getAboutPage(channelId)
|
'PewDiePie'); // or yt.channels.getAboutPage(channelId)
|
||||||
expect(aboutPage.title, 'PewDiePie');
|
expect(aboutPage.title, 'PewDiePie');
|
||||||
expect(aboutPage.viewCount, greaterThanOrEqualTo(20000000000)); //Seems youtube likes to change and lower this number
|
expect(
|
||||||
|
aboutPage.viewCount,
|
||||||
|
greaterThanOrEqualTo(
|
||||||
|
20000000000)); //Seems youtube likes to change and lower this number
|
||||||
expect(aboutPage.description, isNotEmpty);
|
expect(aboutPage.description, isNotEmpty);
|
||||||
expect(aboutPage.thumbnails, isNotEmpty); // Avatar list
|
expect(aboutPage.thumbnails, isNotEmpty); // Avatar list
|
||||||
expect(aboutPage.channelLinks, isNotEmpty);
|
expect(aboutPage.channelLinks, isNotEmpty);
|
||||||
|
|
Loading…
Reference in New Issue