160 lines
4.5 KiB
Dart
160 lines
4.5 KiB
Dart
import 'package:html/dom.dart';
|
|
import 'package:html/parser.dart' as html;
|
|
|
|
import '../models/models.dart';
|
|
import '../youtube_explode_base.dart';
|
|
import 'helpers_extension.dart';
|
|
import 'playlist_extension.dart';
|
|
|
|
/// Channel extension for [YoutubeExplode]
|
|
extension ChannelExtension on YoutubeExplode {
|
|
static final _usernameRegMatchExp =
|
|
RegExp(r'youtube\..+?/user/(.*?)(?:\?|&|/|$)');
|
|
|
|
static final _idRegMatchExp =
|
|
RegExp(r'youtube\..+?/channel/(.*?)(?:\?|&|/|$)');
|
|
|
|
/// Returns the [Channel] associated with the given channelId.
|
|
/// Throws an [ArgumentError] if the channel id is not valid.
|
|
Future<Channel> getChannel(String channelId) async {
|
|
if (!validateChannelId(channelId)) {
|
|
throw ArgumentError.value(
|
|
channelId, 'channelId', 'Invalid YouTube channel id');
|
|
}
|
|
|
|
var channelPage = await getChannelPage(channelId);
|
|
var channelTitle = channelPage
|
|
.querySelector('meta[property="og:title"]')
|
|
.attributes['content'];
|
|
var channelImage = channelPage
|
|
.querySelector('meta[property="og:image"]')
|
|
.attributes['content'];
|
|
|
|
return Channel(channelId, channelTitle, Uri.parse(channelImage));
|
|
}
|
|
|
|
/// Get a channel id from a username.
|
|
/// Returns null if the username is not found.
|
|
Future<String> getChannelId(String username) async {
|
|
if (!validateUsername(username)) {
|
|
throw ArgumentError.value(
|
|
username, 'username', 'Invalid YouTube username');
|
|
}
|
|
|
|
var userPage = await _getUserPage(username);
|
|
if (userPage == null) {
|
|
return null;
|
|
}
|
|
|
|
var channelUrl =
|
|
userPage.querySelector('meta[property="og:url"]').attributes['content'];
|
|
|
|
return channelUrl.replaceFirst('/channel/', '');
|
|
}
|
|
|
|
/// Returns all the videos uploaded by a channel up to [maxPages] count.
|
|
Future<List<Video>> getChannelUploads(String channelId,
|
|
[int maxPages = 5]) async {
|
|
if (!validateChannelId(channelId)) {
|
|
throw ArgumentError.value(
|
|
channelId, 'channelId', 'Invalid YouTube channel id');
|
|
}
|
|
|
|
var playlistId = 'UU${channelId.replaceFirst('UC', '')}';
|
|
var playlist = await getPlaylist(playlistId, maxPages);
|
|
|
|
return playlist.videos;
|
|
}
|
|
|
|
/// Returns the channel id for a given video.
|
|
Future<String> getChannelIdFromVideo(String videoId) async {
|
|
if (!YoutubeExplode.validateVideoId(videoId)) {
|
|
throw ArgumentError.value(videoId, 'videoId', 'Invalid YouTube video id');
|
|
}
|
|
var watchPage = await getVideoWatchPage(videoId);
|
|
var href = watchPage
|
|
.querySelector('.yt-user-info')
|
|
.querySelector('a')
|
|
.attributes['href'];
|
|
return href.replaceFirst('/channel/', '');
|
|
}
|
|
|
|
/// Returns the channel page document.
|
|
Future<Document> getChannelPage(String channelId) async {
|
|
var url = 'https://www.youtube.com/channel/$channelId?hl=en';
|
|
var raw = (await client.get(url)).body;
|
|
|
|
return html.parse(raw);
|
|
}
|
|
|
|
Future<Document> _getUserPage(String username) async {
|
|
var url = 'https://www.youtube.com/user/$username?hl=en';
|
|
var req = await client.get(url);
|
|
if (req.statusCode != 200) {
|
|
return null;
|
|
}
|
|
|
|
return html.parse(req);
|
|
}
|
|
|
|
/// Returns true if [username] is a valid Youtube username.
|
|
static bool validateUsername(String username) {
|
|
if (username.isNullOrWhiteSpace) {
|
|
return false;
|
|
}
|
|
|
|
if (username.length > 20) {
|
|
return false;
|
|
}
|
|
|
|
return !RegExp(r'[^0-9a-zA-Z]').hasMatch(username);
|
|
}
|
|
|
|
/// Parses a username from an url.
|
|
/// Returns null if the username is not found.
|
|
static String parseUsername(String url) {
|
|
if (url.isNullOrWhiteSpace) {
|
|
return null;
|
|
}
|
|
|
|
var regMatch = _usernameRegMatchExp.firstMatch(url)?.group(1);
|
|
if (!regMatch.isNullOrWhiteSpace && validateUsername(regMatch)) {
|
|
return regMatch;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Returns true if [channelId] is a valid Youtube channel id.
|
|
static bool validateChannelId(String channelId) {
|
|
if (channelId.isNullOrWhiteSpace) {
|
|
return false;
|
|
}
|
|
|
|
channelId = channelId.toLowerCase();
|
|
|
|
if (!channelId.startsWith('uc')) {
|
|
return false;
|
|
}
|
|
|
|
if (channelId.length != 24) {
|
|
return false;
|
|
}
|
|
|
|
return !RegExp(r'[^0-9a-zA-Z_\-]').hasMatch(channelId);
|
|
}
|
|
|
|
/// Parses a channel id from an url.
|
|
/// Returns null if the username is not found.
|
|
static String parseChannelId(String url) {
|
|
if (url.isNullOrWhiteSpace) {
|
|
return null;
|
|
}
|
|
|
|
var regMatch = _idRegMatchExp.firstMatch(url)?.group(1);
|
|
if (!regMatch.isNullOrWhiteSpace && validateChannelId(regMatch)) {
|
|
return regMatch;
|
|
}
|
|
return null;
|
|
}
|
|
}
|