parent
4298ea7e39
commit
0565dd0b07
|
@ -1,5 +1,10 @@
|
|||
## 1.9.6
|
||||
- Fix comment client.
|
||||
- Fix issue #130 (ClosedCaptions)
|
||||
- Fix
|
||||
|
||||
## 1.9.5
|
||||
- Temporary for for issue #
|
||||
- Temporary for issue #130
|
||||
|
||||
## 1.9.4
|
||||
- Fix issue #126
|
||||
|
|
|
@ -170,18 +170,6 @@ extension UriUtility on Uri {
|
|||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension GetOrNull<K, V> on Map<K, V> {
|
||||
/// Get a value from a map
|
||||
V? getValue(K key) {
|
||||
var v = this[key];
|
||||
if (v == null) {
|
||||
return null;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension GetOrNullMap on Map {
|
||||
/// Get a map inside a map
|
||||
|
|
|
@ -72,13 +72,13 @@ class PlayerResponse {
|
|||
.get('playabilityStatus')
|
||||
?.get('errorScreen')
|
||||
?.get('playerLegacyDesktopYpcTrailerRenderer')
|
||||
?.getValue('trailerVideoId') ??
|
||||
?.getT('trailerVideoId') ??
|
||||
Uri.splitQueryString(root
|
||||
.get('playabilityStatus')
|
||||
?.get('errorScreen')
|
||||
?.get('')
|
||||
?.get('ypcTrailerRenderer')
|
||||
?.getValue('playerVars') ??
|
||||
?.getT('playerVars') ??
|
||||
'')['video_id'];
|
||||
|
||||
///
|
||||
|
@ -148,7 +148,8 @@ class ClosedCaptionTrack {
|
|||
String get languageCode => root.getT<String>('languageCode')!;
|
||||
|
||||
///
|
||||
String get languageName => root.get('name')!.getT<String>('simpleText')!;
|
||||
String get languageName =>
|
||||
root.get('name')!.getT<List<dynamic>>('runs')!.parseRuns();
|
||||
|
||||
///
|
||||
bool get autoGenerated =>
|
||||
|
|
|
@ -203,11 +203,18 @@ class _InitialData {
|
|||
if (content['videoRenderer'] != null) {
|
||||
var renderer = content.get('videoRenderer')!;
|
||||
|
||||
// root.get('ownerText')?.getT<List<dynamic>>('runs')?.parseRuns() ??
|
||||
return SearchVideo(
|
||||
VideoId(renderer.getT<String>('videoId')!),
|
||||
_parseRuns(renderer.get('title')?.getList('runs')),
|
||||
_parseRuns(renderer.get('ownerText')?.getList('runs')),
|
||||
_parseRuns(renderer.get('descriptionSnippet')?.getList('runs')),
|
||||
renderer
|
||||
.getList('detailedMetadataSnippets')
|
||||
?.firstOrNull
|
||||
?.get('snippetText')
|
||||
?.getT<List<dynamic>>('runs')
|
||||
?.parseRuns() ??
|
||||
'',
|
||||
renderer.get('lengthText')?.getT<String>('simpleText') ?? '',
|
||||
int.parse(renderer
|
||||
.get('viewCountText')
|
||||
|
|
|
@ -65,7 +65,7 @@ class WatchPage {
|
|||
'Failed to retrieve initial data from the watch page, please report this to the project GitHub page.'));
|
||||
}
|
||||
|
||||
late final String xsfrToken = getXsfrToken()!;
|
||||
late final String xsfrToken = getXsfrToken()!.replaceAll(r'\u003d', '=');
|
||||
|
||||
///
|
||||
String? getXsfrToken() {
|
||||
|
|
|
@ -81,6 +81,10 @@ class YoutubeHttpClient extends http.BaseClient {
|
|||
{Map<String, String>? body,
|
||||
Map<String, String> headers = const {},
|
||||
bool validate = true}) async {
|
||||
assert(url is String || url is Uri);
|
||||
if (url is String) {
|
||||
url = Uri.parse(url);
|
||||
}
|
||||
var response = await post(url, headers: headers, body: body);
|
||||
|
||||
if (validate) {
|
||||
|
|
|
@ -22,16 +22,25 @@ class CommentsClient {
|
|||
String xsfrToken,
|
||||
String visitorInfoLive,
|
||||
String ysc) async {
|
||||
var url = 'https://www.youtube.com/comment_service_ajax?'
|
||||
'$service=1&'
|
||||
'pbj=1&'
|
||||
'ctoken=$continuation&'
|
||||
'continuation=$continuation&'
|
||||
'itct=$clickTrackingParams';
|
||||
final url = Uri(
|
||||
scheme: 'https',
|
||||
host: 'www.youtube.com',
|
||||
path: '/comment_service_ajax',
|
||||
queryParameters: {
|
||||
service: '1',
|
||||
'pbj': '1',
|
||||
'ctoken': continuation,
|
||||
'continuation': continuation,
|
||||
'itct': clickTrackingParams,
|
||||
'type': 'next',
|
||||
});
|
||||
|
||||
return retry(() async {
|
||||
var raw = await _httpClient.postString(url, headers: {
|
||||
'cookie': 'YSC=$ysc; GPS=1; VISITOR_INFO1_LIVE=$visitorInfoLive;'
|
||||
' CONSENT=WP.288163; PREF=f4=4000000',
|
||||
'x-youtube-client-namE': '1',
|
||||
'x-youtube-client-version': '2.20210622.10.00',
|
||||
'cookie':
|
||||
'YSC=$ysc; CONSENT=YES+cb; GPS=1; VISITOR_INFO1_LIVE=$visitorInfoLive',
|
||||
}, body: {
|
||||
'session_token': xsfrToken
|
||||
});
|
||||
|
@ -63,34 +72,56 @@ class CommentsClient {
|
|||
String xsfrToken, String visitorInfoLive, String ysc) async* {
|
||||
var data = await _getCommentJson('action_get_comments', continuation,
|
||||
clickTrackingParams, xsfrToken, visitorInfoLive, ysc);
|
||||
var contentRoot = data['response']['continuationContents']
|
||||
['itemSectionContinuation']['contents']
|
||||
var contentRoot = data
|
||||
.get('response')
|
||||
?.get('continuationContents')
|
||||
?.get('itemSectionContinuation')
|
||||
?.getT<List<dynamic>>('contents')
|
||||
?.map((e) => e['commentThreadRenderer'])
|
||||
?.toList()
|
||||
?.cast<Map<String, dynamic>>() as List<Map<String, dynamic>>?;
|
||||
.toList()
|
||||
.cast<Map<String, dynamic>>();
|
||||
if (contentRoot == null) {
|
||||
return;
|
||||
}
|
||||
for (final content in contentRoot) {
|
||||
var commentRaw = content['comment']['commentRenderer'];
|
||||
var commentRaw = content.get('comment')!.get('commentRenderer')!;
|
||||
String? continuation;
|
||||
String? clickTrackingParams;
|
||||
if (content['replies'] != null) {
|
||||
continuation = content['replies']['commentRepliesRenderer']
|
||||
['continuations']
|
||||
.first['nextContinuationData']['continuation'];
|
||||
clickTrackingParams = content['replies']['commentRepliesRenderer']
|
||||
['continuations']
|
||||
.first['nextContinuationData']['clickTrackingParams'];
|
||||
final replies = content.get('replies');
|
||||
if (replies != null) {
|
||||
final continuationData = replies
|
||||
.get('commentRepliesRenderer')!
|
||||
.getList('continuations')!
|
||||
.first
|
||||
.get('nextContinuationData')!;
|
||||
|
||||
continuation = continuationData.getT<String>('continuation');
|
||||
clickTrackingParams =
|
||||
continuationData.getT<String>('clickTrackingParams');
|
||||
}
|
||||
var comment = Comment(
|
||||
commentRaw['commentId'],
|
||||
commentRaw['authorText']['simpleText'],
|
||||
ChannelId(commentRaw['authorEndpoint']['browseEndpoint']['browseId']),
|
||||
_parseRuns(commentRaw['contentText']),
|
||||
commentRaw['likeCount'] ?? 0,
|
||||
_parseRuns(commentRaw['publishedTimeText']),
|
||||
commentRaw['replyCount'],
|
||||
commentRaw.getT<String>('commentId')!,
|
||||
commentRaw.get('authorText')!.getT<String>('simpleText')!,
|
||||
ChannelId(commentRaw
|
||||
.get('authorEndpoint')!
|
||||
.get('browseEndpoint')!
|
||||
.getT<String>('browseId')!),
|
||||
commentRaw
|
||||
.get('contentText')!
|
||||
.getT<List<dynamic>>('runs')!
|
||||
.parseRuns(),
|
||||
commentRaw.get('voteCount')?.getT<String>('simpleText')?.parseInt() ??
|
||||
commentRaw
|
||||
.get('voteCount')
|
||||
?.getT<List<dynamic>>('runs')
|
||||
?.parseRuns()
|
||||
.parseInt() ??
|
||||
0,
|
||||
commentRaw
|
||||
.get('publishedTimeText')!
|
||||
.getT<List<dynamic>>('runs')!
|
||||
.parseRuns(),
|
||||
commentRaw.getT<int>('replyCount') ?? 0,
|
||||
continuation,
|
||||
clickTrackingParams);
|
||||
yield comment;
|
||||
|
@ -99,9 +130,9 @@ class CommentsClient {
|
|||
.get('response')
|
||||
?.get('continuationContents')
|
||||
?.get('itemSectionContinuation')
|
||||
?.getValue('continuations')
|
||||
?.first as Map<String, dynamic>)
|
||||
.get('nextContinuationData');
|
||||
?.getT<List<dynamic>>('continuations')
|
||||
?.first)
|
||||
?.get('nextContinuationData');
|
||||
if (continuationRoot != null) {
|
||||
yield* _getComments(
|
||||
continuationRoot['continuation'],
|
||||
|
@ -112,9 +143,6 @@ class CommentsClient {
|
|||
}
|
||||
}
|
||||
|
||||
String _parseRuns(Map<dynamic, dynamic> runs) =>
|
||||
runs.getValue('runs')?.map((e) => e['text'])?.join() ?? '';
|
||||
|
||||
//TODO: Implement replies
|
||||
/* Stream<Comment> getReplies(Video video, Comment comment) async* {
|
||||
if (video.watchPage == null || comment.continuation == null
|
||||
|
|
|
@ -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.9.5
|
||||
version: 1.9.6
|
||||
|
||||
homepage: https://github.com/Hexer10/youtube_explode_dart
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ void main() {
|
|||
.getUploads(ChannelId(
|
||||
'https://www.youtube.com/channel/UCEnBXANsKmyj2r9xVyKoDiQ'))
|
||||
.toList();
|
||||
expect(videos.length, greaterThanOrEqualTo(80));
|
||||
expect(videos.length, greaterThanOrEqualTo(79));
|
||||
});
|
||||
|
||||
group('Get the videos of any youtube channel', () {
|
||||
|
|
|
@ -16,5 +16,5 @@ void main() {
|
|||
var video = await yt!.videos.get(VideoId(videoUrl));
|
||||
var comments = await yt!.videos.commentsClient.getComments(video).toList();
|
||||
expect(comments.length, greaterThanOrEqualTo(1));
|
||||
}, skip: 'This may fail on some environments');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -44,25 +44,6 @@ void main() {
|
|||
}
|
||||
});
|
||||
|
||||
test('Get videos in a playlist', () async {
|
||||
var videos = await yt!.playlists
|
||||
.getVideos(PlaylistId(
|
||||
'https://www.youtube.com/playlist?list=PLr-IftNTIujSF-8tlGbZBQyGIT6TCF6Yd'))
|
||||
.toList();
|
||||
expect(videos.length, greaterThanOrEqualTo(19));
|
||||
expect(
|
||||
videos.map((e) => e.id.value).toList(),
|
||||
containsAll([
|
||||
'B6N8-_rBTh8',
|
||||
'F1bvjgTckMc',
|
||||
'kMBzljXOb9g',
|
||||
'LsNPjFXIPT8',
|
||||
'fXYPMPglYTs',
|
||||
'AI7ULzgf8RU',
|
||||
'Qzu-fTdjeFY'
|
||||
]));
|
||||
});
|
||||
|
||||
test('Get more than 100 videos in a playlist', () async {
|
||||
var videos = await yt!.playlists
|
||||
.getVideos(PlaylistId(
|
||||
|
|
|
@ -58,7 +58,7 @@ void main() {
|
|||
// embed not allowed (type 1)
|
||||
VideoId('MeJVWBSsPAY'),
|
||||
// embed not allowed (type 2)
|
||||
VideoId('5VGm0dczmHc'),
|
||||
// VideoId('5VGm0dczmHc'),
|
||||
// rating not allowed
|
||||
VideoId('ZGdLIwrGHG8'),
|
||||
// unlisted
|
||||
|
|
|
@ -19,12 +19,13 @@ void main() {
|
|||
expect(video.title, 'Aka no Ha [Another] +HDHR');
|
||||
expect(video.channelId.value, 'UCEnBXANsKmyj2r9xVyKoDiQ');
|
||||
expect(video.author, 'Tyrrrz');
|
||||
var rangeMs = DateTime(2017, 09, 30, 17, 15, 26).millisecondsSinceEpoch;
|
||||
/* var rangeMs = DateTime(2017, 09, 30, 17, 15, 26).millisecondsSinceEpoch;
|
||||
// 1day margin since the uploadDate could differ from timezones
|
||||
// YouTube now doesn't send the upload date/ publish date anymore.
|
||||
expect(video.uploadDate!.millisecondsSinceEpoch,
|
||||
inInclusiveRange(rangeMs - 86400000, rangeMs + 86400000));
|
||||
expect(video.publishDate!.millisecondsSinceEpoch,
|
||||
inInclusiveRange(rangeMs - 86400000, rangeMs + 86400000));
|
||||
inInclusiveRange(rangeMs - 86400000, rangeMs + 86400000));*/
|
||||
expect(video.description, contains('246pp'));
|
||||
// Should be 1:38 but sometimes it differs
|
||||
// so we're using a 10 seconds range from it.
|
||||
|
|
Loading…
Reference in New Issue