148 lines
4.9 KiB
Dart
148 lines
4.9 KiB
Dart
import 'dart:convert';
|
|
|
|
import '../../channels/channel_id.dart';
|
|
import '../../extensions/helpers_extension.dart';
|
|
import '../../retry.dart';
|
|
import '../../reverse_engineering/youtube_http_client.dart';
|
|
import '../videos.dart';
|
|
import 'comment.dart';
|
|
|
|
/// Queries related to comments of YouTube videos.
|
|
class CommentsClient {
|
|
final YoutubeHttpClient _httpClient;
|
|
|
|
/// Initializes an instance of [CommentsClient]
|
|
CommentsClient(this._httpClient);
|
|
|
|
/// Returns the json parsed comments map.
|
|
Future<Map<String, dynamic>> _getCommentJson(
|
|
String continuation,
|
|
String clickTrackingParams,
|
|
String xsfrToken,
|
|
String visitorInfoLive,
|
|
String ysc) async {
|
|
final url = Uri(
|
|
scheme: 'https',
|
|
host: 'www.youtube.com',
|
|
path: '/next',
|
|
queryParameters: {
|
|
'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
|
|
});
|
|
|
|
return retry(() async {
|
|
var raw = await _httpClient.postString(url, headers: {
|
|
'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
|
|
});
|
|
return json.decode(raw);
|
|
});
|
|
}
|
|
|
|
/// Returns a stream emitting all the [video]'s comment.
|
|
/// A request is page for every comment page,
|
|
/// a page contains at most 20 comments, use .take if you want to limit
|
|
/// the results.
|
|
///
|
|
/// The streams doesn't emit any data if [Video.hasWatchPage] is false.
|
|
/// Use `videos.get(videoId, forceWatchPage: true)` to assure that the
|
|
/// WatchPage is fetched.
|
|
Stream<Comment> getComments(Video video) async* {
|
|
if (video.watchPage == null) {
|
|
return;
|
|
}
|
|
yield* _getComments(
|
|
video.watchPage!.initialData.continuation,
|
|
video.watchPage!.initialData.clickTrackingParams,
|
|
video.watchPage!.xsfrToken,
|
|
video.watchPage!.visitorInfoLive,
|
|
video.watchPage!.ysc);
|
|
}
|
|
|
|
Stream<Comment> _getComments(String continuation, String clickTrackingParams,
|
|
String xsfrToken, String visitorInfoLive, String ysc) async* {
|
|
// contents.twoColumnWatchNextResults.results.results.contents[2](firstWhere itemSectionRenderer != null).itemSectionRenderer.contents[0].continuationItemRenderer
|
|
var data = await _getCommentJson(
|
|
continuation, clickTrackingParams, xsfrToken, visitorInfoLive, ysc);
|
|
var contentRoot = data
|
|
.get('response')
|
|
?.get('continuationContents')
|
|
?.get('itemSectionContinuation')
|
|
?.getT<List<dynamic>>('contents')
|
|
?.map((e) => e['commentThreadRenderer'])
|
|
.toList()
|
|
.cast<Map<String, dynamic>>();
|
|
if (contentRoot == null) {
|
|
return;
|
|
}
|
|
for (final content in contentRoot) {
|
|
var commentRaw = content.get('comment')!.get('commentRenderer')!;
|
|
String? continuation;
|
|
String? 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');
|
|
}
|
|
yield Comment(
|
|
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);
|
|
}
|
|
var continuationRoot = (data
|
|
.get('response')
|
|
?.get('continuationContents')
|
|
?.get('itemSectionContinuation')
|
|
?.getT<List<dynamic>>('continuations')
|
|
?.first)
|
|
?.get('nextContinuationData');
|
|
if (continuationRoot != null) {
|
|
yield* _getComments(
|
|
continuationRoot['continuation'],
|
|
continuationRoot['clickTrackingParams'],
|
|
xsfrToken,
|
|
visitorInfoLive,
|
|
ysc);
|
|
}
|
|
}
|
|
|
|
Stream<Comment> getReplies(Video video, Comment comment) async* {
|
|
if (video.watchPage == null ||
|
|
comment.continuation == null ||
|
|
comment.clicktrackingParams == null) {
|
|
return;
|
|
}
|
|
}
|
|
}
|