[verified] Publish ReelBox source under GPLv3
This commit is contained in:
commit
db2b505ca3
445 files changed
+105274
No files matched your search
@@ -0,0 +1,305 @@
|
||||
/// A presentation boundary for the independent, film/series-only client.
|
||||
///
|
||||
/// Catalog/media hosts are not blocked merely because the original service owns
|
||||
/// them. The removed vendor destinations, announcements and navigation families
|
||||
/// are rejected separately. This also applies to imported/previously saved data.
|
||||
class ContentPolicy {
|
||||
static const _removedKeys = {
|
||||
'comment',
|
||||
'comments',
|
||||
'replies',
|
||||
'channel',
|
||||
'channels',
|
||||
'livechannel',
|
||||
'livechannels',
|
||||
'live_channels',
|
||||
'publicplaylist',
|
||||
'publicplaylists',
|
||||
'public_playlists',
|
||||
'playlists',
|
||||
'news',
|
||||
'news_url',
|
||||
'announcements',
|
||||
'announcement',
|
||||
'notifications',
|
||||
'notification',
|
||||
'update',
|
||||
'updates',
|
||||
'appupdate',
|
||||
'app_update',
|
||||
'app_version',
|
||||
'latest_version',
|
||||
'new_version',
|
||||
'version_code',
|
||||
'apk',
|
||||
'apk_url',
|
||||
'download_apk',
|
||||
'support',
|
||||
'support_url',
|
||||
'supportlink',
|
||||
'telegram',
|
||||
'telegram_url',
|
||||
'telegram_channel',
|
||||
'telegramaccount',
|
||||
'ads',
|
||||
'advertisements',
|
||||
'advertisement',
|
||||
'admob',
|
||||
'shop',
|
||||
'shop_url',
|
||||
'store_url',
|
||||
'melodybox',
|
||||
'norton',
|
||||
};
|
||||
static const _removedTypes = {
|
||||
'channel',
|
||||
'live',
|
||||
'live_channel',
|
||||
'news',
|
||||
'announcement',
|
||||
'advertisement',
|
||||
'ad',
|
||||
'app',
|
||||
'apk',
|
||||
'update',
|
||||
'support',
|
||||
'public_playlist',
|
||||
};
|
||||
static final _formatting = RegExp(
|
||||
r'[\u064b-\u065f\u0670\u06d6-\u06ed\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufeff]',
|
||||
);
|
||||
static final _urls = RegExp(
|
||||
r'''(?:https?://|tg://|telegram://|www\.)[^\s<>"')]+''',
|
||||
caseSensitive: false,
|
||||
);
|
||||
static final _handles = RegExp(r'(^|[\s>(])@[a-zA-Z][a-zA-Z0-9_]{3,}');
|
||||
|
||||
static String _decode(String value) {
|
||||
var result = value;
|
||||
for (var i = 0; i < 3; i++) {
|
||||
final decoded = result.replaceAllMapped(RegExp(r'(?:%[0-9a-fA-F]{2})+'), (
|
||||
match,
|
||||
) {
|
||||
try {
|
||||
return Uri.decodeComponent(match.group(0)!);
|
||||
} on FormatException {
|
||||
return match.group(0)!;
|
||||
} on ArgumentError {
|
||||
return match.group(0)!;
|
||||
}
|
||||
});
|
||||
if (decoded == result) break;
|
||||
result = decoded;
|
||||
}
|
||||
return result
|
||||
.replaceAllMapped(
|
||||
RegExp(r'&#(x[0-9a-f]+|[0-9]+);', caseSensitive: false),
|
||||
(match) {
|
||||
final entity = match.group(1)!;
|
||||
final code = int.tryParse(
|
||||
entity.startsWith(RegExp('[xX]')) ? entity.substring(1) : entity,
|
||||
radix: entity.startsWith(RegExp('[xX]')) ? 16 : 10,
|
||||
);
|
||||
return code != null && code >= 0 && code <= 0x10ffff
|
||||
? String.fromCharCode(code)
|
||||
: '';
|
||||
},
|
||||
)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll(' ', ' ');
|
||||
}
|
||||
|
||||
static String _normal(String value) =>
|
||||
_decode(value)
|
||||
.toLowerCase()
|
||||
.replaceAll('ي', 'ی')
|
||||
.replaceAll('ى', 'ی')
|
||||
.replaceAll('ك', 'ک')
|
||||
.replaceAll(_formatting, '');
|
||||
static String _compact(String value) =>
|
||||
_normal(value).replaceAll(RegExp(r'[\s_\-\u0640]+'), '');
|
||||
static bool _brand(String value) {
|
||||
final compact = _compact(value);
|
||||
return compact.contains('ملودیباکس') ||
|
||||
compact.contains('ملودیبکس') ||
|
||||
compact.contains('melodybox') ||
|
||||
compact.contains('melodibox') ||
|
||||
compact.contains('نورتون') ||
|
||||
compact.contains('نورتن') ||
|
||||
compact.contains('norton');
|
||||
}
|
||||
|
||||
static bool allowsText(String value) {
|
||||
final normal = _normal(value);
|
||||
final compact = _compact(value);
|
||||
if (_brand(value) ||
|
||||
normal.contains('تلگرام') ||
|
||||
RegExp(r'\btelegram\b').hasMatch(normal) ||
|
||||
_handles.hasMatch(normal)) {
|
||||
return false;
|
||||
}
|
||||
if (RegExp(
|
||||
r'(دانلود|نصب|دریافت).{0,30}نسخه.{0,18}(برنامه|اپلیکیشن|سیمبا|مووی)',
|
||||
dotAll: true,
|
||||
).hasMatch(normal) ||
|
||||
RegExp(
|
||||
r'(آپدیت|بروزرسانی|بهروز رسانی|بهروزرسانی).{0,20}(برنامه|اپلیکیشن|سیمبا)',
|
||||
dotAll: true,
|
||||
).hasMatch(normal) ||
|
||||
RegExp(
|
||||
r'(install|download|update).{0,24}(latest|new).{0,12}(app|version)',
|
||||
dotAll: true,
|
||||
).hasMatch(normal)) {
|
||||
return false;
|
||||
}
|
||||
if ((compact.contains('originalapp') ||
|
||||
compact.contains('moviebox') ||
|
||||
compact.contains('سیمبا')) &&
|
||||
(compact.contains('support') ||
|
||||
compact.contains('news') ||
|
||||
compact.contains('پشتیبانی'))) {
|
||||
return false;
|
||||
}
|
||||
return _urls
|
||||
.allMatches(_decode(value))
|
||||
.every((match) => allowsUrl(match.group(0)!));
|
||||
}
|
||||
|
||||
static String sanitizeText(String value) =>
|
||||
allowsText(value) ? value.replaceAll(_formatting, '').trim() : '';
|
||||
|
||||
static bool allowsUrl(String value) {
|
||||
if (value.isEmpty) return true;
|
||||
if (value != value.trim() || RegExp(r'[\x00-\x20\x7f]').hasMatch(value)) {
|
||||
return false;
|
||||
}
|
||||
final decoded = _decode(value);
|
||||
if (_brand(decoded) || decoded.contains('\\')) return false;
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
decoded.startsWith('www.') ? 'https://$decoded' : decoded,
|
||||
);
|
||||
if (uri.userInfo.isNotEmpty ||
|
||||
(uri.hasScheme &&
|
||||
!{'http', 'https'}.contains(uri.scheme.toLowerCase()))) {
|
||||
return false;
|
||||
}
|
||||
final host = uri.host.toLowerCase();
|
||||
if ([
|
||||
't.me',
|
||||
'telegram.me',
|
||||
'telegram.dog',
|
||||
'telegram.org',
|
||||
'telegra.ph',
|
||||
].any((blocked) => host == blocked || host.endsWith('.$blocked'))) {
|
||||
return false;
|
||||
}
|
||||
final path = _normal(uri.path);
|
||||
if (RegExp(r'\.(apk|apks|xapk|aab|exe|msi|dmg|ipa)(/|$)')
|
||||
.hasMatch(path)) {
|
||||
return false;
|
||||
}
|
||||
if (RegExp(
|
||||
r'(^|/)(original[-_]?app|moviebox|simba)/(support|news|updates?|downloads?)(/|$)',
|
||||
).hasMatch(path)) {
|
||||
return false;
|
||||
}
|
||||
if (RegExp(
|
||||
r'(^|/)(app[-_]?updates?|download[-_]?apk|latest[-_]?version)(/|$)',
|
||||
).hasMatch(path)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} on FormatException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool allowsApiPath(Uri uri) {
|
||||
final path = _normal(uri.path);
|
||||
final actions = (uri.queryParametersAll['action'] ?? const <String>[]).map(
|
||||
_normal,
|
||||
);
|
||||
if (RegExp(
|
||||
r'(^|/)(comments?|replies|reply|channels?|public[-_]?playlists?|news|updates?|support)([-_][a-z0-9_-]+)?(\.php|/|$)',
|
||||
).hasMatch(path)) {
|
||||
return false;
|
||||
}
|
||||
return actions.every(
|
||||
(action) =>
|
||||
!{
|
||||
'comment',
|
||||
'comments',
|
||||
'add-comment',
|
||||
'reply',
|
||||
'replies',
|
||||
'pasokh',
|
||||
'showlist',
|
||||
'show-list_fav',
|
||||
'public-playlists',
|
||||
'playlist-items',
|
||||
'app-update',
|
||||
'app-news',
|
||||
'telegram',
|
||||
'support',
|
||||
}.contains(action) &&
|
||||
!RegExp(r'(^|[-_])(comments?|replies|reply|pasokh)([-_]|$)')
|
||||
.hasMatch(action),
|
||||
);
|
||||
}
|
||||
|
||||
static bool _removedRecord(Map<String, dynamic> value) {
|
||||
final type = _normal('${value['type'] ?? value['kind'] ?? ''}');
|
||||
if (_removedTypes.contains(type)) return true;
|
||||
for (final key in [
|
||||
'title',
|
||||
'name',
|
||||
'description',
|
||||
'note',
|
||||
'message',
|
||||
'text',
|
||||
'content',
|
||||
'body',
|
||||
'summary',
|
||||
'label',
|
||||
]) {
|
||||
final text = value[key];
|
||||
if (text is String && !allowsText(text)) return true;
|
||||
}
|
||||
for (final key in [
|
||||
'link',
|
||||
'url',
|
||||
'website',
|
||||
'button_url',
|
||||
'download_url',
|
||||
'apk_url',
|
||||
]) {
|
||||
final url = value[key];
|
||||
if (url is String && !allowsUrl(url)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool allowsTitleJson(Map<String, dynamic> json) =>
|
||||
!_removedRecord(json);
|
||||
|
||||
/// Returns a fresh JSON tree; removed records/fields never become blank cards.
|
||||
static dynamic sanitizeJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final map = Map<String, dynamic>.from(value);
|
||||
if (_removedRecord(map)) return null;
|
||||
final result = <String, dynamic>{};
|
||||
for (final entry in map.entries) {
|
||||
if (_removedKeys.contains(_normal(entry.key))) continue;
|
||||
final item = sanitizeJson(entry.value);
|
||||
if (item != null) result[entry.key] = item;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (value is List) {
|
||||
return value.map(sanitizeJson).where((v) => v != null).toList();
|
||||
}
|
||||
if (value is String) return allowsText(value) ? value : null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user