Implement v5 - 1
This commit is contained in:
parent
d36aa68486
commit
447f7f27d7
|
@ -15,5 +15,5 @@ linter:
|
|||
- prefer_contains
|
||||
|
||||
analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
exclude:
|
||||
- example\**
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
class ChannelClient {
|
||||
ChannelClient._();
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'youtube_explode_exception.dart';
|
||||
|
||||
/// Exception thrown when a fatal failure occurs.
|
||||
class FatalFailureException implements YoutubeExplodeException {
|
||||
final String message;
|
||||
|
||||
/// Initializes an instance of [FatalFailureException]
|
||||
FatalFailureException(this.message);
|
||||
|
||||
/// Initializes an instance of [FatalFailureException] with an [HttpRequest]
|
||||
FatalFailureException.httpRequest(Response response)
|
||||
: message = '''
|
||||
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.
|
||||
Request: ${response.request}
|
||||
Response: $response
|
||||
''';
|
||||
|
||||
@override
|
||||
String toString() => 'FatalFailureException: $message';
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:http/http.dart';
|
||||
|
||||
import 'youtube_explode_exception.dart';
|
||||
|
||||
/// Exception thrown when a fatal failure occurs.
|
||||
class RequestLimitExceeded implements YoutubeExplodeException {
|
||||
final String message;
|
||||
|
||||
/// Initializes an instance of [FatalFailureException]
|
||||
RequestLimitExceeded(this.message);
|
||||
|
||||
/// Initializes an instance of [FatalFailureException] with an [HttpRequest]
|
||||
RequestLimitExceeded.httpRequest(Response response)
|
||||
: message = '''
|
||||
Failed to perform an HTTP request to YouTube because of rate limiting.
|
||||
This error indicates that YouTube thinks there were too many requests made from this IP and considers it suspicious.
|
||||
To resolve this error, please wait some time and try again -or- try injecting an HttpClient that has cookies for an authenticated user.
|
||||
Unfortunately, there's nothing the library can do to work around this error.
|
||||
Request: ${response.request}
|
||||
Response: $response
|
||||
''';
|
||||
|
||||
@override
|
||||
String toString() => 'FatalFailureException: $message';
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/// Parent class for domain exceptions thrown by [YoutubeExplode]
|
||||
abstract class YoutubeExplodeException implements Exception {
|
||||
final String message;
|
||||
YoutubeExplodeException(this.message);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import '../cipher/cipher_operations.dart';
|
||||
import '../reverse_engineering/cipher/cipher_operations.dart';
|
||||
|
||||
/// Utility for Strings.
|
||||
extension StringUtility on String {
|
||||
|
|
|
@ -2,8 +2,8 @@ library youtube_explode.cipher;
|
|||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../exceptions/exceptions.dart';
|
||||
import '../extensions/helpers_extension.dart';
|
||||
import '../../exceptions/exceptions.dart';
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import 'cipher_operations.dart';
|
||||
|
||||
final _deciphererFuncNameExp = RegExp(
|
|
@ -0,0 +1,54 @@
|
|||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
|
||||
class ChannelPage {
|
||||
final Document _root;
|
||||
|
||||
|
||||
bool get isOk => _root.querySelector('meta[property="og:url"]') != null;
|
||||
|
||||
String get channelUrl => _root
|
||||
.querySelectorThrow('meta[property="og:url"]')
|
||||
.getAttributeThrow('content');
|
||||
|
||||
String get channelId => channelId.substringAfter('channel/');
|
||||
|
||||
String get channelTitle => _root
|
||||
.querySelectorThrow('meta[property="og:title"]')
|
||||
.getAttributeThrow('content');
|
||||
|
||||
String get channelLogoUrl => _root
|
||||
.querySelectorThrow('meta[property="og:image"]')
|
||||
.getAttributeThrow('content');
|
||||
|
||||
ChannelPage(this._root);
|
||||
|
||||
ChannelPage.parse(String raw) : _root = parser.parse(raw);
|
||||
|
||||
static Future<ChannelPage> hello() {}
|
||||
}
|
||||
|
||||
extension on Document {
|
||||
Element querySelectorThrow(String selectors) {
|
||||
var element = querySelector(selectors);
|
||||
if (element == null) {
|
||||
//TODO: throw
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
extension on Element {
|
||||
String getAttributeThrow(String name) {
|
||||
var attribute = attributes[name];
|
||||
if (attribute == null) {
|
||||
//TODO: throw
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
|
||||
extension on String {
|
||||
String substringAfter(String separator) =>
|
||||
substring(indexOf(separator) + length);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export 'youtube_http_client.dart';
|
|
@ -0,0 +1,69 @@
|
|||
import 'package:http/http.dart';
|
||||
|
||||
class YoutubeHttpClient {
|
||||
final Client _httpClient = Client();
|
||||
|
||||
/// Throws if something is wrong with the response.
|
||||
void _validateResponse(Request request, int statusCode) {
|
||||
if (request.url.host.endsWith('.google.com') &&
|
||||
request.url.path.startsWith('/sorry/')) {
|
||||
//TODO: throw RequestLimitExceededException.FailedHttpRequest(response);
|
||||
}
|
||||
|
||||
if (statusCode >= 500) {
|
||||
//TODO: TransientFailureException.FailedHttpRequest(response);
|
||||
}
|
||||
|
||||
if (statusCode == 429) {
|
||||
//TODO: throw RequestLimitExceededException.FailedHttpRequest(response);
|
||||
}
|
||||
|
||||
if (statusCode >= 400) {
|
||||
//TODO: throw FatalFailureException.FailedHttpRequest(response);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> get(dynamic url, {Map<String, String> headers}) {
|
||||
return _httpClient.get(url, headers: headers);
|
||||
}
|
||||
|
||||
Future<Response> head(dynamic url, {Map<String, String> headers}) {
|
||||
return _httpClient.head(url, headers: headers);
|
||||
}
|
||||
|
||||
Future<String> getString(dynamic url,
|
||||
{Map<String, String> headers, bool validate = true}) async {
|
||||
var response = await _httpClient.get(url, headers: headers);
|
||||
|
||||
if (validate) {
|
||||
_validateResponse(response.request, response.statusCode);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
Stream<List<int>> getStream(dynamic url,
|
||||
{Map<String, String> headers,
|
||||
int from,
|
||||
int to,
|
||||
bool validate = true}) async* {
|
||||
var request = Request('get', url);
|
||||
request.headers['range'] = 'bytes=$from-$to';
|
||||
var response = await request.send();
|
||||
if (validate) {
|
||||
_validateResponse(response.request, response.statusCode);
|
||||
}
|
||||
yield* response.stream;
|
||||
}
|
||||
|
||||
Future<int> getContentLength(dynamic url,
|
||||
{Map<String, String> headers, bool validate = true}) async {
|
||||
var response = await head(url, headers: headers);
|
||||
|
||||
if (validate) {
|
||||
_validateResponse(response.request, response.statusCode);
|
||||
}
|
||||
|
||||
return int.parse(response.headers['content-length']);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,8 @@ import 'extensions/extensions.dart';
|
|||
import 'models/models.dart';
|
||||
import 'parser.dart' as parser;
|
||||
|
||||
import 'channel/channel_client.dart';
|
||||
|
||||
/// YoutubeExplode entry class.
|
||||
class YoutubeExplode {
|
||||
static final _regMatchExp = RegExp(r'youtube\..+?/watch.*?v=(.*?)(?:&|/|$)');
|
||||
|
@ -24,7 +26,7 @@ class YoutubeExplode {
|
|||
|
||||
/// HTTP Client.
|
||||
// Visible only for extensions.
|
||||
http.Client client;
|
||||
final http.Client client;
|
||||
|
||||
/// Initialize [YoutubeExplode] class and http client.
|
||||
YoutubeExplode() : client = http.Client();
|
||||
|
|
Loading…
Reference in New Issue