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

155 lines
4.2 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';
2021-03-11 14:20:10 +01:00
import '../../extensions/helpers_extension.dart';
2020-06-03 23:02:21 +02:00
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 {
2021-03-11 14:20:10 +01:00
static final RegExp _statIndexExp = RegExp(r'\(\w+,(\d+)\)');
2020-05-31 23:36:23 +02:00
2021-03-11 14:20:10 +01:00
static final RegExp _funcBodyExp = RegExp(
2020-05-31 23:36:23 +02:00
r'(\w+)=function\(\w+\){(\w+)=\2\.split\(\x22{2}\);.*?return\s+\2\.join\(\x22{2}\)}');
2021-03-11 14:20:10 +01:00
static final RegExp _objNameExp = RegExp(r'([\$_\w]+).\w+\(\w+,\d+\);');
2020-05-31 23:36:23 +02:00
2021-03-11 14:20:10 +01:00
static final RegExp _calledFuncNameExp =
RegExp(r'\w+(?:.|\[)(\"?\w+(?:\")?)\]?\(');
2020-05-31 23:36:23 +02:00
2021-03-11 14:20:10 +01:00
final String root;
2020-05-31 23:36:23 +02:00
2021-03-11 14:20:10 +01:00
late final String sts = getSts();
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String getSts() {
2020-08-15 15:08:04 +02:00
var val = RegExp(r'(?<=invalid namespace.*?;[\w\s]+=)\d+')
2021-03-11 14:20:10 +01:00
.stringMatch(root)
2020-08-15 15:08:04 +02:00
?.nullIfWhitespace ??
2020-10-27 14:44:11 +01:00
RegExp(r'(?<=signatureTimestamp[=\:])\d+')
2021-03-11 14:20:10 +01:00
.stringMatch(root)
2020-08-15 15:08:04 +02:00
?.nullIfWhitespace;
2020-05-31 23:36:23 +02:00
if (val == null) {
throw FatalFailureException('Could not find sts in player source.', 0);
2020-05-31 23:36:23 +02:00
}
2021-03-11 14:20:10 +01:00
return val;
2020-05-31 23:36:23 +02:00
}
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
Iterable<CipherOperation> getCipherOperations() sync* {
if (deciphererFuncBody == null) {
2020-05-31 23:36:23 +02:00
throw FatalFailureException(
'Could not find signature decipherer function body.', 0);
2020-05-31 23:36:23 +02:00
}
2021-03-11 14:20:10 +01:00
var definitionBody = _getDeciphererDefinitionBody(deciphererFuncBody!);
2020-05-31 23:36:23 +02:00
if (definitionBody == null) {
throw FatalFailureException(
'Could not find signature decipherer definition body.', 0);
2020-05-31 23:36:23 +02:00
}
2021-03-11 14:20:10 +01:00
for (final statement in deciphererFuncBody!.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;
}
2021-03-11 14:20:10 +01:00
var escapedFuncName = RegExp.escape(calledFuncName!);
2020-05-31 23:36:23 +02:00
// 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)) {
2021-03-11 14:20:10 +01:00
var index = int.parse(_statIndexExp.firstMatch(statement)!.group(1)!);
2020-05-31 23:36:23 +02:00
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)) {
2021-03-11 14:20:10 +01:00
var index = int.parse(_statIndexExp.firstMatch(statement)!.group(1)!);
2020-05-31 23:36:23 +02:00
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();
}
}
}
2021-03-11 14:20:10 +01:00
late final String? deciphererFuncBody =
_funcBodyExp.firstMatch(root)?.group(0);
2020-05-31 23:36:23 +02:00
2021-03-11 14:20:10 +01:00
String? _getDeciphererDefinitionBody(String deciphererFuncBody) {
final objName = _objNameExp.firstMatch(deciphererFuncBody)?.group(1);
if (objName == null) {
return null;
}
2020-05-31 23:36:23 +02:00
2021-03-11 14:20:10 +01:00
final exp = RegExp(
2020-06-05 16:17:08 +02:00
r'var\s+'
'${RegExp.escape(objName)}'
2020-06-05 16:17:08 +02:00
r'=\{(\w+:function\(\w+(,\w+)?\)\{(.*?)\}),?\};',
dotAll: true);
2021-03-11 14:20:10 +01:00
return exp.firstMatch(root)?.group(0)?.nullIfWhitespace;
2020-05-31 23:36:23 +02:00
}
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
PlayerSource(this.root);
2020-06-22 17:40:57 +02:00
2021-03-11 14:20:10 +01:00
/// Same as default constructor
PlayerSource.parse(this.root);
2020-05-31 23:36:23 +02:00
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]?.expired ?? true) {
var val = await retry(httpClient, () async {
2020-06-27 21:32:03 +02:00
var raw = await httpClient.getString(url);
return PlayerSource.parse(raw);
});
if (_cache[url] == null) {
_cache[url] = _CachedValue(val);
} else {
2021-03-11 14:20:10 +01:00
_cache[url]!.update(val);
}
2020-06-27 21:32:03 +02:00
}
2021-03-11 14:20:10 +01:00
return _cache[url]!.value;
2020-05-31 23:36:23 +02:00
}
2020-06-27 21:32:03 +02:00
static final Map<String, _CachedValue<PlayerSource>> _cache = {};
}
class _CachedValue<T> {
T _value;
int expireTime;
final int cacheTime;
T get value {
if (expired) {
throw StateError('Value $value is expired!');
}
return _value;
}
bool get expired {
final now = DateTime.now().millisecondsSinceEpoch;
return now > expireTime;
}
set value(T other) => _value = other;
2021-03-11 14:20:10 +01:00
_CachedValue(this._value, [this.cacheTime = 600000])
: expireTime = DateTime.now().millisecondsSinceEpoch + cacheTime;
void update(T newValue) {
var now = DateTime.now().millisecondsSinceEpoch;
expireTime = now + cacheTime;
value = newValue;
}
2020-05-31 23:36:23 +02:00
}