Fix #168 + Fix for youtube.com/c/X channels.

This commit is contained in:
Mattia 2022-02-01 00:55:55 +01:00
parent 6dad4d6776
commit e114785ffd
35 changed files with 120 additions and 33 deletions

View File

@ -1,3 +1,8 @@
## 1.10.10
- Fix issue #136: Add `bannerUrl` getter for `Channel`.
- Fix `ChannelClient.getByUsername` for `youtube.com/c/XXXX` channels.
## 1.10.9
- Fix issue #180: YouTube throttling videos. - Thanks to @itssidhere.

View File

@ -18,6 +18,9 @@ class Channel with _$Channel {
/// URL of the channel's logo image.
String logoUrl,
/// URL of the channel's banner image.
String bannerUrl,
/// The (approximate) channel subscriber's count.
int? subscribersCount,
) = _Channel;

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'channel.dart';
@ -17,12 +18,13 @@ final _privateConstructorUsedError = UnsupportedError(
class _$ChannelTearOff {
const _$ChannelTearOff();
_Channel call(
ChannelId id, String title, String logoUrl, int? subscribersCount) {
_Channel call(ChannelId id, String title, String logoUrl, String bannerUrl,
int? subscribersCount) {
return _Channel(
id,
title,
logoUrl,
bannerUrl,
subscribersCount,
);
}
@ -42,6 +44,9 @@ mixin _$Channel {
/// URL of the channel's logo image.
String get logoUrl => throw _privateConstructorUsedError;
/// URL of the channel's banner image.
String get bannerUrl => throw _privateConstructorUsedError;
/// The (approximate) channel subscriber's count.
int? get subscribersCount => throw _privateConstructorUsedError;
@ -54,7 +59,11 @@ abstract class $ChannelCopyWith<$Res> {
factory $ChannelCopyWith(Channel value, $Res Function(Channel) then) =
_$ChannelCopyWithImpl<$Res>;
$Res call(
{ChannelId id, String title, String logoUrl, int? subscribersCount});
{ChannelId id,
String title,
String logoUrl,
String bannerUrl,
int? subscribersCount});
$ChannelIdCopyWith<$Res> get id;
}
@ -72,6 +81,7 @@ class _$ChannelCopyWithImpl<$Res> implements $ChannelCopyWith<$Res> {
Object? id = freezed,
Object? title = freezed,
Object? logoUrl = freezed,
Object? bannerUrl = freezed,
Object? subscribersCount = freezed,
}) {
return _then(_value.copyWith(
@ -87,6 +97,10 @@ class _$ChannelCopyWithImpl<$Res> implements $ChannelCopyWith<$Res> {
? _value.logoUrl
: logoUrl // ignore: cast_nullable_to_non_nullable
as String,
bannerUrl: bannerUrl == freezed
? _value.bannerUrl
: bannerUrl // ignore: cast_nullable_to_non_nullable
as String,
subscribersCount: subscribersCount == freezed
? _value.subscribersCount
: subscribersCount // ignore: cast_nullable_to_non_nullable
@ -108,7 +122,11 @@ abstract class _$ChannelCopyWith<$Res> implements $ChannelCopyWith<$Res> {
__$ChannelCopyWithImpl<$Res>;
@override
$Res call(
{ChannelId id, String title, String logoUrl, int? subscribersCount});
{ChannelId id,
String title,
String logoUrl,
String bannerUrl,
int? subscribersCount});
@override
$ChannelIdCopyWith<$Res> get id;
@ -128,6 +146,7 @@ class __$ChannelCopyWithImpl<$Res> extends _$ChannelCopyWithImpl<$Res>
Object? id = freezed,
Object? title = freezed,
Object? logoUrl = freezed,
Object? bannerUrl = freezed,
Object? subscribersCount = freezed,
}) {
return _then(_Channel(
@ -143,6 +162,10 @@ class __$ChannelCopyWithImpl<$Res> extends _$ChannelCopyWithImpl<$Res>
? _value.logoUrl
: logoUrl // ignore: cast_nullable_to_non_nullable
as String,
bannerUrl == freezed
? _value.bannerUrl
: bannerUrl // ignore: cast_nullable_to_non_nullable
as String,
subscribersCount == freezed
? _value.subscribersCount
: subscribersCount // ignore: cast_nullable_to_non_nullable
@ -154,7 +177,8 @@ class __$ChannelCopyWithImpl<$Res> extends _$ChannelCopyWithImpl<$Res>
/// @nodoc
class _$_Channel extends _Channel {
const _$_Channel(this.id, this.title, this.logoUrl, this.subscribersCount)
const _$_Channel(
this.id, this.title, this.logoUrl, this.bannerUrl, this.subscribersCount)
: super._();
@override
@ -171,12 +195,16 @@ class _$_Channel extends _Channel {
final String logoUrl;
@override
/// URL of the channel's banner image.
final String bannerUrl;
@override
/// The (approximate) channel subscriber's count.
final int? subscribersCount;
@override
String toString() {
return 'Channel(id: $id, title: $title, logoUrl: $logoUrl, subscribersCount: $subscribersCount)';
return 'Channel(id: $id, title: $title, logoUrl: $logoUrl, bannerUrl: $bannerUrl, subscribersCount: $subscribersCount)';
}
@override
@ -187,6 +215,7 @@ class _$_Channel extends _Channel {
const DeepCollectionEquality().equals(other.id, id) &&
const DeepCollectionEquality().equals(other.title, title) &&
const DeepCollectionEquality().equals(other.logoUrl, logoUrl) &&
const DeepCollectionEquality().equals(other.bannerUrl, bannerUrl) &&
const DeepCollectionEquality()
.equals(other.subscribersCount, subscribersCount));
}
@ -197,6 +226,7 @@ class _$_Channel extends _Channel {
const DeepCollectionEquality().hash(id),
const DeepCollectionEquality().hash(title),
const DeepCollectionEquality().hash(logoUrl),
const DeepCollectionEquality().hash(bannerUrl),
const DeepCollectionEquality().hash(subscribersCount));
@JsonKey(ignore: true)
@ -206,9 +236,8 @@ class _$_Channel extends _Channel {
}
abstract class _Channel extends Channel {
const factory _Channel(
ChannelId id, String title, String logoUrl, int? subscribersCount) =
_$_Channel;
const factory _Channel(ChannelId id, String title, String logoUrl,
String bannerUrl, int? subscribersCount) = _$_Channel;
const _Channel._() : super._();
@override
@ -225,6 +254,10 @@ abstract class _Channel extends Channel {
String get logoUrl;
@override
/// URL of the channel's banner image.
String get bannerUrl;
@override
/// The (approximate) channel subscriber's count.
int? get subscribersCount;
@override

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'channel_about.dart';

View File

@ -30,7 +30,7 @@ class ChannelClient {
var channelPage = await ChannelPage.get(_httpClient, id.value);
return Channel(id, channelPage.channelTitle, channelPage.channelLogoUrl,
channelPage.subscribersCount);
channelPage.channelBannerUrl, channelPage.subscribersCount);
}
/// Gets the metadata associated with the channel of the specified user.
@ -41,8 +41,12 @@ class ChannelClient {
var channelPage =
await ChannelPage.getByUsername(_httpClient, username.value);
return Channel(ChannelId(channelPage.channelId), channelPage.channelTitle,
channelPage.channelLogoUrl, channelPage.subscribersCount);
return Channel(
ChannelId(channelPage.channelId),
channelPage.channelTitle,
channelPage.channelLogoUrl,
channelPage.channelBannerUrl,
channelPage.subscribersCount);
}
/// Gets the info found on a YouTube Channel About page.

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'channel_id.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'channel_link.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'channel_video.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'username.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'engagement.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'thumbnail.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'thumbnail_set.dart';

View File

@ -4,11 +4,15 @@ import 'youtube_explode_exception.dart';
/// Exception thrown when a fatal failure occurs.
class FatalFailureException extends YoutubeExplodeException {
final int statusCode;
/// Initializes an instance of [FatalFailureException]
FatalFailureException(String message) : super(message);
FatalFailureException(String message, this.statusCode) : super(message);
/// Initializes an instance of [FatalFailureException] with a [Response]
FatalFailureException.httpRequest(BaseResponse response) : super('''
FatalFailureException.httpRequest(BaseResponse response)
: statusCode = response.statusCode,
super('''
Failed to perform an HTTP request to YouTube due to a fatal failure.
In most cases, this error indicates that YouTube most likely changed something, which broke the library.
If this issue persists, please report it on the project's GitHub page.

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'playlist.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'playlist_id.dart';

View File

@ -1,4 +1,5 @@
import 'package:html/parser.dart' as parser;
import 'package:http/http.dart';
import '../../exceptions/exceptions.dart';
import '../../extensions/helpers_extension.dart';
@ -30,6 +31,8 @@ class ChannelPage extends YoutubePage<_InitialData> {
root!.querySelector('meta[property="og:image"]')?.attributes['content'] ??
'';
String get channelBannerUrl => initialData.bannerUrl ?? '';
int? get subscribersCount => initialData.subscribersCount;
///
@ -57,13 +60,21 @@ class ChannelPage extends YoutubePage<_InitialData> {
var url = 'https://www.youtube.com/user/$username?hl=en';
return retry(httpClient, () async {
var raw = await httpClient.getString(url);
var result = ChannelPage.parse(raw);
try {
var raw = await httpClient.getString(url);
var result = ChannelPage.parse(raw);
if (!result.isOk) {
throw TransientFailureException('Channel page is broken');
if (!result.isOk) {
throw TransientFailureException('Channel page is broken');
}
return result;
} on FatalFailureException catch (e) {
if (e.statusCode != 404) {
rethrow;
}
url = 'https://www.youtube.com/c/$username?hl=en';
}
return result;
throw FatalFailureException('', 0);
});
}
}
@ -110,4 +121,12 @@ class _InitialData extends InitialData {
return (count * multiplier).toInt();
}
String? get bannerUrl => root
.get('header')
?.get('c4TabbedHeaderRenderer')
?.get('banner')
?.getList('thumbnails')
?.first
.getT<String>('url');
}

View File

@ -99,7 +99,7 @@ class _InitialData extends InitialData {
?.cast<JsonMap>();
}
if (context == null) {
throw FatalFailureException('Failed to get initial data context.');
throw FatalFailureException('Failed to get initial data context.', 0);
}
return context;
}

View File

@ -217,11 +217,12 @@ class _InitialData extends InitialData {
renderer.get('descriptionSnippet')?.getList('runs')?.parseRuns() ??
'',
renderer
.get('videoCountText')
?.getList('runs')
?.first
.getT<String>('text')
?.parseInt() ?? -1);
.get('videoCountText')
?.getList('runs')
?.first
.getT<String>('text')
?.parseInt() ??
-1);
}
// Here ignore 'horizontalCardListRenderer' & 'shelfRenderer'
return null;

View File

@ -31,7 +31,7 @@ class PlayerSource {
.stringMatch(root)
?.nullIfWhitespace;
if (val == null) {
throw FatalFailureException('Could not find sts in player source.');
throw FatalFailureException('Could not find sts in player source.', 0);
}
return val;
}
@ -40,13 +40,13 @@ class PlayerSource {
Iterable<CipherOperation> getCipherOperations() sync* {
if (deciphererFuncBody == null) {
throw FatalFailureException(
'Could not find signature decipherer function body.');
'Could not find signature decipherer function body.', 0);
}
var definitionBody = _getDeciphererDefinitionBody(deciphererFuncBody!);
if (definitionBody == null) {
throw FatalFailureException(
'Could not find signature decipherer definition body.');
'Could not find signature decipherer definition body.', 0);
}
for (final statement in deciphererFuncBody!.split(';')) {

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'search_channel.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'search_playlist.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'search_video.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'closed_caption_track_info.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'language.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'comment.dart';

View File

@ -56,5 +56,4 @@ class AudioOnlyStreamInfo with StreamInfo, AudioStreamInfo {
_$AudioOnlyStreamInfoFromJson(json);
Map<String, dynamic> toJson() => _$AudioOnlyStreamInfoToJson(this);
}

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'bitrate.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'filesize.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'framerate.dart';

View File

@ -91,4 +91,4 @@ class MuxedStreamInfo with StreamInfo, AudioStreamInfo, VideoStreamInfo {
_$MuxedStreamInfoFromJson(json);
Map<String, dynamic> toJson() => _$MuxedStreamInfoToJson(this);
}
}

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'stream_container.dart';

View File

@ -101,4 +101,3 @@ class _Column {
String mediaTypeTojson(MediaType value) => value.toString();
MediaType mediaTypeFromJson(String value) => MediaType.parse(value);

View File

@ -78,6 +78,4 @@ class VideoOnlyStreamInfo with StreamInfo, VideoStreamInfo {
_$VideoOnlyStreamInfoFromJson(json);
Map<String, dynamic> toJson() => _$VideoOnlyStreamInfoToJson(this);
}

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'video.dart';

View File

@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'video_id.dart';