From 7cb41f8018b92ff403c63c14d3e354d6755b658f Mon Sep 17 00:00:00 2001 From: Mattia Date: Wed, 14 Jul 2021 22:36:25 +0200 Subject: [PATCH] Version 1.9.10 Closes #139 --- CHANGELOG.md | 3 + lib/src/channels/channel.dart | 5 +- lib/src/channels/channel_client.dart | 5 +- .../responses/channel_page.dart | 63 +++++++++++++++++++ pubspec.yaml | 2 +- test/channel_test.dart | 1 + 6 files changed, 75 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32d6b3d..ea74ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.9.10 +- Close #139: Implement Channel.subscribersCount. + ## 1.9.9 - Fix issue #134 (Error when getting the hls manifest from a livestream) diff --git a/lib/src/channels/channel.dart b/lib/src/channels/channel.dart index 8c47ba1..ef654f6 100644 --- a/lib/src/channels/channel.dart +++ b/lib/src/channels/channel.dart @@ -16,8 +16,11 @@ class Channel with EquatableMixin { /// URL of the channel's logo image. final String logoUrl; + /// The (approximate) channel subscriber's count. + final int? subscribersCount; + /// Initializes an instance of [Channel] - Channel(this.id, this.title, this.logoUrl); + Channel(this.id, this.title, this.logoUrl, this.subscribersCount); @override String toString() => 'Channel ($title)'; diff --git a/lib/src/channels/channel_client.dart b/lib/src/channels/channel_client.dart index 4539279..7fc8e66 100644 --- a/lib/src/channels/channel_client.dart +++ b/lib/src/channels/channel_client.dart @@ -30,7 +30,8 @@ class ChannelClient { id = ChannelId.fromString(id); 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. @@ -42,7 +43,7 @@ class ChannelClient { var channelPage = await ChannelPage.getByUsername(_httpClient, username.value); return Channel(ChannelId(channelPage.channelId), channelPage.channelTitle, - channelPage.channelLogoUrl); + channelPage.channelLogoUrl, channelPage.subscribersCount); } /// Gets the info found on a YouTube Channel About page. diff --git a/lib/src/reverse_engineering/responses/channel_page.dart b/lib/src/reverse_engineering/responses/channel_page.dart index e1b6e9e..16f0184 100644 --- a/lib/src/reverse_engineering/responses/channel_page.dart +++ b/lib/src/reverse_engineering/responses/channel_page.dart @@ -31,6 +31,22 @@ class ChannelPage { _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); @@ -68,3 +84,50 @@ class ChannelPage { }); } } + +class _InitialData { + static final RegExp _subCountExp = RegExp(r'(\d+(?:\.\d+)?)(K|M|\s)'); + + // Json parsed map + final Map 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('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(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 45b681e..e4b270e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.9 +version: 1.9.10 homepage: https://github.com/Hexer10/youtube_explode_dart diff --git a/test/channel_test.dart b/test/channel_test.dart index 9571997..eb1d5ae 100644 --- a/test/channel_test.dart +++ b/test/channel_test.dart @@ -18,6 +18,7 @@ void main() { expect(channel.title, 'Tyrrrz'); expect(channel.logoUrl, isNotEmpty); expect(channel.logoUrl, isNot(equalsIgnoringWhitespace(''))); + expect(channel.subscribersCount, greaterThanOrEqualTo(190)); }); group('Get metadata of any channel', () {