youtube_explode/lib/src/reverse_engineering/responses/player_source.dart

147 lines
3.9 KiB
Dart
Raw Normal View History

2020-06-27 21:32:03 +02:00
import 'dart:async';
2020-06-03 23:02:21 +02:00
import '../../exceptions/exceptions.dart';
import '../../retry.dart';
import '../cipher/cipher_operations.dart';
import '../youtube_http_client.dart';
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2020-05-31 23:36:23 +02:00
class PlayerSource {
final RegExp _statIndexExp = RegExp(r'\(\w+,(\d+)\)');
final RegExp _funcBodyExp = RegExp(
r'(\w+)=function\(\w+\){(\w+)=\2\.split\(\x22{2}\);.*?return\s+\2\.join\(\x22{2}\)}');
final RegExp _funcNameExp = RegExp(r'(\w+).\w+\(\w+,\d+\);');
2020-06-05 21:06:54 +02:00
final RegExp _calledFuncNameExp = RegExp(r'\w+(?:.|\[)(\"?\w+(?:\")?)\]?\(');
2020-05-31 23:36:23 +02:00
final String _root;
2020-06-22 17:40:57 +02:00
String _sts;
String _deciphererDefinitionBody;
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2020-05-31 23:36:23 +02:00
String get sts {
2020-06-22 17:40:57 +02:00
if (_sts != null) {
return _sts;
}
2020-06-30 15:00:45 +02:00
var val = RegExp(r'(?<=invalid namespace.*?;[\w+\s]+=)\d+')
2020-05-31 23:36:23 +02:00
.stringMatch(_root)
2020-06-05 16:17:08 +02:00
?.nullIfWhitespace;
2020-05-31 23:36:23 +02:00
if (val == null) {
throw FatalFailureException('Could not find sts in player source.');
}
2020-06-22 17:40:57 +02:00
return _sts ??= val;
2020-05-31 23:36:23 +02:00
}
2020-07-16 20:02:54 +02:00
///
2020-05-31 23:36:23 +02:00
Iterable<CipherOperation> getCiperOperations() sync* {
var funcBody = _getDeciphererFuncBody();
if (funcBody == null) {
throw FatalFailureException(
'Could not find signature decipherer function body.');
}
var definitionBody = _getDeciphererDefinitionBody(funcBody);
if (definitionBody == null) {
throw FatalFailureException(
'Could not find signature decipherer definition body.');
}
for (var statement in funcBody.split(';')) {
2020-06-05 16:17:08 +02:00
var calledFuncName = _calledFuncNameExp.firstMatch(statement)?.group(1);
2020-05-31 23:36:23 +02:00
if (calledFuncName.isNullOrWhiteSpace) {
continue;
}
var escapedFuncName = RegExp.escape(calledFuncName);
// Slice
var exp = RegExp('$escapedFuncName'
2020-06-05 20:08:04 +02:00
r':\bfunction\b\([a],b\).(\breturn\b)?.?\w+\.');
2020-05-31 23:36:23 +02:00
2020-06-05 20:08:04 +02:00
if (exp.hasMatch(definitionBody)) {
2020-05-31 23:36:23 +02:00
var index = int.parse(_statIndexExp.firstMatch(statement).group(1));
yield SliceCipherOperation(index);
}
// Swap
exp = RegExp(
'$escapedFuncName' r':\bfunction\b\(\w+\,\w\).\bvar\b.\bc=a\b');
2020-06-05 20:08:04 +02:00
if (exp.hasMatch(definitionBody)) {
2020-05-31 23:36:23 +02:00
var index = int.parse(_statIndexExp.firstMatch(statement).group(1));
yield SwapCipherOperation(index);
}
// Reverse
exp = RegExp('$escapedFuncName' r':\bfunction\b\(\w+\)');
2020-06-05 20:08:04 +02:00
if (exp.hasMatch(definitionBody)) {
2020-05-31 23:36:23 +02:00
yield const ReverseCipherOperation();
}
}
}
String _getDeciphererFuncBody() {
2020-06-22 17:40:57 +02:00
if (_deciphererDefinitionBody != null) {
return _deciphererDefinitionBody;
}
2020-05-31 23:36:23 +02:00
var funcName = _funcBodyExp.firstMatch(_root).group(1);
var exp = RegExp(
2020-06-05 16:17:08 +02:00
r'(?!h\.)' '${RegExp.escape(funcName)}' r'=function\(\w+\)\{(.*?)\}');
2020-06-22 17:40:57 +02:00
return _deciphererDefinitionBody ??=
exp.firstMatch(_root).group(1).nullIfWhitespace;
2020-05-31 23:36:23 +02:00
}
String _getDeciphererDefinitionBody(String deciphererFuncBody) {
var funcName = _funcNameExp.firstMatch(deciphererFuncBody).group(1);
2020-06-05 16:17:08 +02:00
var exp = RegExp(
r'var\s+'
2020-05-31 23:36:23 +02:00
'${RegExp.escape(funcName)}'
2020-06-05 16:17:08 +02:00
r'=\{(\w+:function\(\w+(,\w+)?\)\{(.*?)\}),?\};',
dotAll: true);
2020-05-31 23:36:23 +02:00
return exp.firstMatch(_root).group(0).nullIfWhitespace;
}
2020-07-16 20:02:54 +02:00
///
2020-06-22 17:40:57 +02:00
PlayerSource(this._root);
2020-07-16 20:02:54 +02:00
///
2020-05-31 23:36:23 +02:00
// Same as default constructor
PlayerSource.parse(this._root);
2020-07-16 20:02:54 +02:00
///
2020-06-27 21:32:03 +02:00
static Future<PlayerSource> get(
YoutubeHttpClient httpClient, String url) async {
if (_cache[url] == null) {
Timer(const Duration(minutes: 10), () {
_cache[url] = null;
});
return _cache[url] = await retry(() async {
var raw = await httpClient.getString(url);
return PlayerSource.parse(raw);
});
}
return _cache[url];
2020-05-31 23:36:23 +02:00
}
2020-06-27 21:32:03 +02:00
static final Map<String, PlayerSource> _cache = {};
2020-05-31 23:36:23 +02:00
}
extension on String {
String get nullIfWhitespace => trim().isEmpty ? null : this;
bool get isNullOrWhiteSpace {
if (this == null) {
return true;
}
if (trim().isEmpty) {
return true;
}
return false;
}
}