library _youtube_explode.extensions; import 'dart:convert'; import 'package:collection/collection.dart'; import '../reverse_engineering/cipher/cipher_operations.dart'; typedef JsonMap = Map; /// Utility for Strings. extension StringUtility on String { /// Returns null if this string is whitespace. String? get nullIfWhitespace => trim().isEmpty ? null : this; /// Returns null if this string is a whitespace. String substringUntil(String separator) => substring(0, indexOf(separator)); /// String substringAfter(String separator) => substring(indexOf(separator) + separator.length); static final _exp = RegExp(r'\D'); /// Strips out all non digit characters. String stripNonDigits() => replaceAll(_exp, ''); /// Extract and decode json from a string Map? extractJson([String separator = '']) { final index = indexOf(separator) + separator.length; if (index > length) { return null; } final str = substring(index); final startIdx = str.indexOf('{'); var endIdx = str.lastIndexOf('}'); while (true) { try { return json.decode(str.substring(startIdx, endIdx + 1)) as Map; } on FormatException { endIdx = str.lastIndexOf(str.substring(0, endIdx)); if (endIdx == 0) { return null; } } } } /// Format: HH:MM:SS Duration? toDuration() { if (/*string == null ||*/ trim().isEmpty) { return null; } var parts = split(':'); assert(parts.length <= 3); if (parts.length == 1) { return Duration(seconds: int.parse(parts.first)); } if (parts.length == 2) { return Duration( minutes: int.parse(parts[0]), seconds: int.parse(parts[1])); } if (parts.length == 3) { return Duration( hours: int.parse(parts[0]), minutes: int.parse(parts[1]), seconds: int.parse(parts[2])); } // Shouldn't reach here. throw Error(); } DateTime parseDateTime() => DateTime.parse(this); } /// Utility for Strings. extension StringUtility2 on String? { static final RegExp _unitSplit = RegExp(r'^(\d+(?:\.\d+)?)(\w)?'); /// Parses this value as int stripping the non digit characters, /// returns null if this fails. int? parseInt() => int.tryParse(this?.stripNonDigits() ?? ''); int? parseIntWithUnits() { if (this == null) { return null; } final match = _unitSplit.firstMatch(this!.trim()); 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); var multiplier = 1; if (multiplierText == 'K') { multiplier = 1000; } else if (multiplierText == 'M') { multiplier = 1000000; } return (count * multiplier).toInt(); } /// Returns true if the string is null or empty. bool get isNullOrWhiteSpace { if (this == null) { return true; } if (this!.trim().isEmpty) { return true; } return false; } /// Format: ago (5 years ago) DateTime? toDateTime() { if (this == null) { return null; } var parts = this!.split(' '); if (parts.length == 4) { // Streamed x y ago parts = parts.skip(1).toList(); } assert(parts.length == 3); var qty = int.parse(parts.first); // Try to get the unit var unit = parts[1]; Duration time; if (unit.startsWith('second')) { time = Duration(seconds: qty); } else if (unit.startsWith('minute')) { time = Duration(minutes: qty); } else if (unit.startsWith('hour')) { time = Duration(hours: qty); } else if (unit.startsWith('day')) { time = Duration(days: qty); } else if (unit.startsWith('week')) { time = Duration(days: qty * 7); } else if (unit.startsWith('month')) { time = Duration(days: qty * 30); } else if (unit.startsWith('year')) { time = Duration(days: qty * 365); } else { throw StateError('Couldn\'t parse $unit unit of time. ' 'Please report this to the project page!'); } return DateTime.now().subtract(time); } DateTime? tryParseDateTime() { if (this == null) { return null; } return DateTime.parse(this!); } } /// List decipher utility. extension ListDecipher on Iterable { /// Apply every CipherOperation on the [signature] String decipher(String signature) { for (final operation in this) { signature = operation.decipher(signature); } return signature; } } /// List Utility. extension ListUtil on Iterable { /// Same as [elementAt] but if the index is higher than the length returns /// null E? elementAtSafe(int index) { if (index >= length) { return null; } return elementAt(index); } } /// Uri utility extension UriUtility on Uri { /// Returns a new Uri with the new query parameters set. Uri setQueryParam(String key, String value) { var query = Map.from(queryParameters); query[key] = value; return replace(queryParameters: query); } } /// extension GetOrNullMap on Map { /// Get a map inside a map Map? get(String key) { var v = this[key]; if (v == null) { return null; } return v; } /// Get a value inside a map. /// If it is null this returns null, if of another type this throws. T? getT(String key) { var v = this[key]; if (v == null) { return null; } if (v is! T) { throw Exception('Invalid type: ${v.runtimeType} should be $T'); } return v; } /// Get a List>> from a map. List>? getList(String key, [String? orKey]) { var v = this[key]; if (v == null) { if (orKey != null) { v = this[orKey]; if (v == null) { return null; } } else { return null; } } if (v is! List) { throw Exception('Invalid type: ${v.runtimeType} should be of type List'); } return (v.toList()).cast>(); } } /// extension UriUtils on Uri { /// Uri replaceQueryParameters(Map parameters) { var query = Map.from(queryParameters); query.addAll(parameters); return replace(queryParameters: query); } } /// Parse properties with `text` method. extension RunsParser on List { /// String parseRuns() => map((e) => e['text']).join(); } extension GenericExtract on List { /// Used to extract initial data. T extractGenericData(List match, T Function(Map) builder, Exception Function() orThrow) { JsonMap? initialData; for (final m in match) { initialData = firstWhereOrNull((e) => e.contains(m))?.extractJson(m); if (initialData != null) { return builder(initialData); } } throw orThrow(); } } /// Iterable that joins together multiple lists class JoinedIterable extends Iterable { final Iterable> _iterables; JoinedIterable(this._iterables); @override Iterator get iterator { return _JoinedIterator(_iterables.map((e) => e.iterator).toList()); } } class _JoinedIterator extends Iterator { final Iterable> _iterators; var _currentIter = 0; _JoinedIterator(this._iterators); @override bool moveNext([int debug = 0]) { if (!_iterators.elementAt(_currentIter).moveNext()) { if (_currentIter == _iterators.length - 1) { return false; } _currentIter++; return moveNext(debug + 1); } return true; } @override T get current => _iterators.elementAt(_currentIter).current; }