parent
9d50d205ec
commit
7cb41f8018
|
@ -1,3 +1,6 @@
|
||||||
|
## 1.9.10
|
||||||
|
- Close #139: Implement Channel.subscribersCount.
|
||||||
|
|
||||||
## 1.9.9
|
## 1.9.9
|
||||||
- Fix issue #134 (Error when getting the hls manifest from a livestream)
|
- Fix issue #134 (Error when getting the hls manifest from a livestream)
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@ class Channel with EquatableMixin {
|
||||||
/// URL of the channel's logo image.
|
/// URL of the channel's logo image.
|
||||||
final String logoUrl;
|
final String logoUrl;
|
||||||
|
|
||||||
|
/// The (approximate) channel subscriber's count.
|
||||||
|
final int? subscribersCount;
|
||||||
|
|
||||||
/// Initializes an instance of [Channel]
|
/// Initializes an instance of [Channel]
|
||||||
Channel(this.id, this.title, this.logoUrl);
|
Channel(this.id, this.title, this.logoUrl, this.subscribersCount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'Channel ($title)';
|
String toString() => 'Channel ($title)';
|
||||||
|
|
|
@ -30,7 +30,8 @@ class ChannelClient {
|
||||||
id = ChannelId.fromString(id);
|
id = ChannelId.fromString(id);
|
||||||
var channelPage = await ChannelPage.get(_httpClient, id.value);
|
var channelPage = await ChannelPage.get(_httpClient, id.value);
|
||||||
|
|
||||||
return Channel(id, channelPage.channelTitle, channelPage.channelLogoUrl);
|
return Channel(id, channelPage.channelTitle, channelPage.channelLogoUrl,
|
||||||
|
channelPage.subscribersCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the metadata associated with the channel of the specified user.
|
/// Gets the metadata associated with the channel of the specified user.
|
||||||
|
@ -42,7 +43,7 @@ class ChannelClient {
|
||||||
var channelPage =
|
var channelPage =
|
||||||
await ChannelPage.getByUsername(_httpClient, username.value);
|
await ChannelPage.getByUsername(_httpClient, username.value);
|
||||||
return Channel(ChannelId(channelPage.channelId), channelPage.channelTitle,
|
return Channel(ChannelId(channelPage.channelId), channelPage.channelTitle,
|
||||||
channelPage.channelLogoUrl);
|
channelPage.channelLogoUrl, channelPage.subscribersCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the info found on a YouTube Channel About page.
|
/// Gets the info found on a YouTube Channel About page.
|
||||||
|
|
|
@ -31,6 +31,22 @@ class ChannelPage {
|
||||||
_root.querySelector('meta[property="og:image"]')?.attributes['content'] ??
|
_root.querySelector('meta[property="og:image"]')?.attributes['content'] ??
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
int? get subscribersCount => initialData.subscribersCount;
|
||||||
|
|
||||||
|
///
|
||||||
|
late final _InitialData initialData = _getInitialData();
|
||||||
|
|
||||||
|
_InitialData _getInitialData() {
|
||||||
|
final scriptText = _root
|
||||||
|
.querySelectorAll('script')
|
||||||
|
.map((e) => e.text)
|
||||||
|
.toList(growable: false);
|
||||||
|
return scriptText.extractGenericData(
|
||||||
|
(obj) => _InitialData(obj),
|
||||||
|
() => TransientFailureException(
|
||||||
|
'Failed to retrieve initial data from the channel about page, please report this to the project GitHub page.'));
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
ChannelPage(this._root);
|
ChannelPage(this._root);
|
||||||
|
|
||||||
|
@ -68,3 +84,50 @@ class ChannelPage {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _InitialData {
|
||||||
|
static final RegExp _subCountExp = RegExp(r'(\d+(?:\.\d+)?)(K|M|\s)');
|
||||||
|
|
||||||
|
// Json parsed map
|
||||||
|
final Map<String, dynamic> root;
|
||||||
|
|
||||||
|
_InitialData(this.root);
|
||||||
|
|
||||||
|
int? get subscribersCount {
|
||||||
|
final renderer = root.get('header')?.get('c4TabbedHeaderRenderer');
|
||||||
|
if (renderer?['subscriberCountText'] == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final subText =
|
||||||
|
renderer?.get('subscriberCountText')?.getT<String>('simpleText');
|
||||||
|
if (subText == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final match = _subCountExp.firstMatch(subText);
|
||||||
|
if (match == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (match.groupCount != 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final count = double.tryParse(match.group(1) ?? '');
|
||||||
|
if (count == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final multiplierText = match.group(2);
|
||||||
|
if (multiplierText == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiplier = 1;
|
||||||
|
if (multiplierText == 'K') {
|
||||||
|
multiplier = 1000;
|
||||||
|
} else if (multiplierText == 'M') {
|
||||||
|
multiplier = 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (count * multiplier).toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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.9
|
version: 1.9.10
|
||||||
|
|
||||||
homepage: https://github.com/Hexer10/youtube_explode_dart
|
homepage: https://github.com/Hexer10/youtube_explode_dart
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ void main() {
|
||||||
expect(channel.title, 'Tyrrrz');
|
expect(channel.title, 'Tyrrrz');
|
||||||
expect(channel.logoUrl, isNotEmpty);
|
expect(channel.logoUrl, isNotEmpty);
|
||||||
expect(channel.logoUrl, isNot(equalsIgnoringWhitespace('')));
|
expect(channel.logoUrl, isNot(equalsIgnoringWhitespace('')));
|
||||||
|
expect(channel.subscribersCount, greaterThanOrEqualTo(190));
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Get metadata of any channel', () {
|
group('Get metadata of any channel', () {
|
||||||
|
|
Loading…
Reference in New Issue