From 56c8ae451b51a28a66816d01c0f50774988367ab Mon Sep 17 00:00:00 2001 From: Anoir Ben Tanfous Date: Sun, 23 Feb 2025 23:13:19 -0500 Subject: [PATCH] Update magic-userjs.user.js Fixed sorting by date (requested before but it wasn't fixed). Signed-off-by: Anoir Ben Tanfous --- dist/magic-userjs.user.js | 8905 +++++++++++++++++++------------------ 1 file changed, 4455 insertions(+), 4450 deletions(-) diff --git a/dist/magic-userjs.user.js b/dist/magic-userjs.user.js index e46b3fc..4b6365b 100644 --- a/dist/magic-userjs.user.js +++ b/dist/magic-userjs.user.js @@ -1,5 +1,5 @@ // ==UserScript== -// @version 7.3.0 +// @version 7.3.1 // @name Magic Userscript+ : Show Site All UserJS // @name:ar Magic Userscript+: عرض جميع ملفات UserJS // @name:de Magic Userscript+ : Website anzeigen Alle UserJS @@ -60,648 +60,648 @@ // @run-at document-start // ==/UserScript== (() => { -'use strict'; -/******************************************************************************/ -const inIframe = () => { - try { - return window.self !== window.top; - } catch (e) { - return true; - } -} -if (inIframe()) { - return; -} -let userjs = self.userjs; -/** + 'use strict'; + /******************************************************************************/ + const inIframe = () => { + try { + return window.self !== window.top; + } catch (e) { + return true; + } + } + if (inIframe()) { + return; + } + let userjs = self.userjs; + /** * Skip text/plain documents, based on uBlock Origin `vapi.js` file * * [Source Code](https://github.com/gorhill/uBlock/blob/master/platform/common/vapi.js) */ -if ( - (document instanceof Document || - (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && - /^image\/|^text\/plain/.test(document.contentType || '') === false && - (self.userjs instanceof Object === false || userjs.UserJS !== true) -) { - userjs = self.userjs = { UserJS: true }; -} -if (!(typeof userjs === 'object' && userjs.UserJS)) { - return; -} -/** + if ( + (document instanceof Document || + (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && + /^image\/|^text\/plain/.test(document.contentType || '') === false && + (self.userjs instanceof Object === false || userjs.UserJS !== true) + ) { + userjs = self.userjs = { UserJS: true }; + } + if (!(typeof userjs === 'object' && userjs.UserJS)) { + return; + } + /** * Uncompressed locales + compiler * * [_locales](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales) * * [languageLoader.js](https://github.com/magicoflolis/Userscript-Plus/blob/master/tools/languageLoader.js) */ -const translations = { - "ar": { - "createdby": "انشأ من قبل", - "name": "اسم", - "daily_installs": "التثبيت اليومي", - "close": "يغلق", - "filterA": "منقي", - "max": "تحقيق أقصى قدر", - "min": "تصغير", - "search": "يبحث", - "search_placeholder": "بحث في البرامج النصية", - "install": "تثبيت", - "issue": "إصدار جديد", - "version_number": "الإصدار", - "updated": "آخر تحديث", - "total_installs": "إجمالي التثبيت", - "ratings": "التقييمات", - "good": "جيد", - "ok": "جيد", - "bad": "سيء", - "created_date": "تم إنشاؤه", - "redirect": "شوكة دهنية للكبار", - "filter": "تصفية اللغات الأخرى", - "dtime": "عرض المهلة", - "save": "حفظ", - "reset": "إعادة تعيين", - "preview_code": "كود المعاينة", - "saveFile": "احفظ الملف", - "newTab": "علامة تبويب جديدة", - "applies_to": "ينطبق على", - "license": "الترخيص", - "no_license": "لا يوجد", - "antifeatures": "إعلانات", - "userjs_fullscreen": "ملء الشاشة الكاملة التلقائي", - "listing_none": "(لا يوجد)", - "export_config": "تهيئة التصدير", - "export_theme": "تصدير السمة", - "import_config": "استيراد تهيئة الاستيراد", - "import_theme": "استيراد النسق", - "code_size": "حجم الرمز", - "prmpt_css": "التثبيت كأسلوب المستخدم؟", - "userjs_inject": "حقن Userscript+", - "userjs_close": "إغلاق Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "de": { - "createdby": "Erstellt von", - "name": "Name", - "daily_installs": "Tägliche Installationen", - "close": "Schließen Sie", - "filterA": "Filter", - "max": "Maximieren Sie", - "min": "minimieren", - "search": "Suche", - "search_placeholder": "Suche nach Userscripts", - "install": "Installieren Sie", - "issue": "Neue Ausgabe", - "version_number": "Version", - "updated": "Zuletzt aktualisiert", - "total_installs": "Installationen insgesamt", - "ratings": "Bewertungen", - "good": "Gut", - "ok": "Okay", - "bad": "Schlecht", - "created_date": "Erstellt", - "redirect": "Greasy Fork für Erwachsene", - "filter": "Andere Sprachen herausfiltern", - "dtime": "Zeitüberschreitung anzeigen", - "save": "Speichern Sie", - "reset": "Zurücksetzen", - "preview_code": "Vorschau Code", - "saveFile": "Datei speichern", - "newTab": "Neue Registerkarte", - "applies_to": "Gilt für", - "license": "Lizenz", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Automatischer Vollbildmodus", - "listing_none": "(Keine)", - "export_config": "Konfig exportieren", - "export_theme": "Thema exportieren", - "import_config": "Konfig importieren", - "import_theme": "Thema importieren", - "code_size": "Code Größe", - "prmpt_css": "Als UserStyle installieren?", - "userjs_inject": "Userscript+ einfügen", - "userjs_close": "Userscript+ schließen", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "en": { - "createdby": "Created by", - "name": "Name", - "daily_installs": "Daily Installs", - "close": "Close", - "filterA": "Filter", - "max": "Maximize", - "min": "Minimize", - "search": "Search", - "search_placeholder": "Search for userscripts", - "install": "Install", - "issue": "New Issue", - "version_number": "Version", - "updated": "Last Updated", - "total_installs": "Total Installs", - "ratings": "Ratings", - "good": "Good", - "ok": "Okay", - "bad": "Bad", - "created_date": "Created", - "redirect": "Greasy Fork for adults", - "filter": "Filter out other languages", - "dtime": "Display Timeout", - "save": "Save", - "reset": "Reset", - "preview_code": "Preview Code", - "saveFile": "Save File", - "newTab": "New Tab", - "applies_to": "Applies to", - "license": "License", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Automatic Fullscreen", - "listing_none": "(None)", - "export_config": "Export Config", - "export_theme": "Export Theme", - "import_config": "Import Config", - "import_theme": "Import Theme", - "code_size": "Code Size", - "prmpt_css": "Install as UserStyle?", - "userjs_inject": "Inject Userscript+", - "userjs_close": "Close Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "en_GB": { - "createdby": "Created by", - "name": "Name", - "daily_installs": "Daily Installs", - "close": "Close", - "filterA": "Filter", - "max": "Maximize", - "min": "Minimize", - "search": "Search", - "search_placeholder": "Search scripts", - "install": "Install", - "issue": "New Issue", - "version_number": "Version", - "updated": "Last Updated", - "total_installs": "Total Installs", - "ratings": "Ratings", - "good": "Good", - "ok": "Ok", - "bad": "Bad", - "created_date": "Created", - "redirect": "Greasy Fork for adults", - "filter": "Filter out other languages", - "dtime": "Display Timeout", - "save": "Save", - "reset": "Reset", - "preview_code": "Preview Code", - "saveFile": "Save File", - "newTab": "New Tab", - "applies_to": "Applies to", - "license": "License", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Automatic Fullscreen", - "listing_none": "(None)", - "export_config": "Export Config", - "export_theme": "Export Theme", - "import_config": "Import Config", - "import_theme": "Import Theme", - "code_size": "Code Size", - "prmpt_css": "Install as UserStyle?", - "userjs_inject": "Inject Userscript+", - "userjs_close": "Close Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "es": { - "createdby": "Creado por", - "name": "Nombre", - "daily_installs": "Instalaciones diarias", - "close": "Ya no se muestra", - "filterA": "Filtro", - "max": "Maximizar", - "min": "Minimizar", - "search": "Busque en", - "search_placeholder": "Buscar userscripts", - "install": "Instalar", - "issue": "Nueva edición", - "version_number": "Versión", - "updated": "Última actualización", - "total_installs": "Total de instalaciones", - "ratings": "Clasificaciones", - "good": "Bueno", - "ok": "Ok", - "bad": "Malo", - "created_date": "Creado", - "redirect": "Greasy Fork para adultos", - "filter": "Filtrar otros idiomas", - "dtime": "Mostrar el tiempo de espera", - "save": "Guardar", - "reset": "Reiniciar", - "preview_code": "Vista previa del código", - "saveFile": "Guardar archivo", - "newTab": "Guardar archivo", - "applies_to": "Se aplica a", - "license": "Licencia", - "no_license": "Desconocida", - "antifeatures": "Características indeseables", - "userjs_fullscreen": "Pantalla completa automática", - "listing_none": "(Ninguno)", - "export_config": "Exportar configuración", - "export_theme": "Exportar tema", - "import_config": "Importar configuración", - "import_theme": "Importar tema", - "code_size": "Código Tamaño", - "prmpt_css": "¿Instalar como UserStyle?", - "userjs_inject": "Inyectar Userscript+", - "userjs_close": "Cerrar Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "fr": { - "createdby": "Créé par", - "name": "Nom", - "daily_installs": "Installations quotidiennes", - "close": "Ne plus montrer", - "filterA": "Filtre", - "max": "Maximiser", - "min": "Minimiser", - "search": "Recherche", - "search_placeholder": "Rechercher des userscripts", - "install": "Installer", - "issue": "Nouveau numéro", - "version_number": "Version", - "updated": "Dernière mise à jour", - "total_installs": "Total des installations", - "ratings": "Notations", - "good": "Bon", - "ok": "Ok", - "bad": "Mauvais", - "created_date": "Créé", - "redirect": "Greasy Fork pour les adultes", - "filter": "Filtrer les autres langues", - "dtime": "Délai d'affichage", - "save": "Sauvez", - "reset": "Réinitialiser", - "preview_code": "Prévisualiser le code", - "saveFile": "Enregistrer le fichier", - "newTab": "Nouvel onglet", - "applies_to": "S'applique à", - "license": "Licence", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Plein écran automatique", - "listing_none": "(Aucun)", - "export_config": "Export Config", - "export_theme": "Exporter le thème", - "import_config": "Importer la configuration", - "import_theme": "Importer le thème", - "code_size": "Code Taille", - "prmpt_css": "Installer comme UserStyle ?", - "userjs_inject": "Injecter Userscript+", - "userjs_close": "Fermer Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "ja": { - "createdby": "によって作成された", - "name": "名前", - "daily_installs": "デイリーインストール", - "close": "表示されなくなりました", - "filterA": "フィルター", - "max": "最大化", - "min": "ミニマム", - "search": "検索", - "search_placeholder": "ユーザースクリプトの検索", - "install": "インストール", - "issue": "新刊のご案内", - "version_number": "バージョン", - "updated": "最終更新日", - "total_installs": "総インストール数", - "ratings": "レーティング", - "good": "グッド", - "ok": "良い", - "bad": "悪い", - "created_date": "作成", - "redirect": "大人のGreasyfork", - "filter": "他の言語をフィルタリングする", - "dtime": "表示タイムアウト", - "save": "拯救", - "reset": "リセット", - "preview_code": "コードのプレビュー", - "saveFile": "ファイルを保存", - "newTab": "新しいタブ", - "applies_to": "適用対象", - "license": "ライセンス", - "no_license": "不明", - "antifeatures": "アンチ機能", - "userjs_fullscreen": "自動フルスクリーン", - "listing_none": "(なし)", - "export_config": "エクスポート設定", - "export_theme": "テーマのエクスポート", - "import_config": "設定のインポート", - "import_theme": "テーマのインポート", - "code_size": "コード・サイズ", - "prmpt_css": "UserStyleとしてインストールしますか?", - "userjs_inject": "Userscript+ を挿入", - "userjs_close": "Userscript+ を閉じる", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "nl": { - "createdby": "Gemaakt door", - "name": "Naam", - "daily_installs": "Dagelijkse Installaties", - "close": "Sluit", - "filterA": "Filter", - "max": "Maximaliseer", - "min": "Minimaliseer", - "search": "Zoek", - "search_placeholder": "Zoeken naar gebruikersscripts", - "install": "Installeer", - "issue": "Nieuw Issue", - "version_number": "Versie", - "updated": "Laatste Update", - "total_installs": "Totale Installaties", - "ratings": "Beoordeling", - "good": "Goed", - "ok": "Ok", - "bad": "Slecht", - "created_date": "Aangemaakt", - "redirect": "Greasy Fork voor volwassenen", - "filter": "Filter andere talen", - "dtime": "Weergave timeout", - "save": "Opslaan", - "reset": "Opnieuw instellen", - "preview_code": "Voorbeeldcode", - "saveFile": "Bestand opslaan", - "newTab": "Nieuw tabblad", - "applies_to": "Geldt voor", - "license": "Licentie", - "no_license": "N.v.t.", - "antifeatures": "Functies voor eigen gewin", - "userjs_fullscreen": "Automatisch volledig scherm", - "listing_none": "(Geen)", - "export_config": "Configuratie exporteren", - "export_theme": "Thema exporteren", - "import_config": "Configuratie importeren", - "import_theme": "Thema importeren", - "code_size": "Code Grootte", - "prmpt_css": "Installeren als UserStyle?", - "userjs_inject": "Injecteer Userscript+", - "userjs_close": "Sluit Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "pl": { - "createdby": "Stworzony przez", - "name": "Nazwa", - "daily_installs": "Codzienne instalacje", - "close": "Zamknij", - "filterA": "Filtr", - "max": "Maksymalizuj", - "min": "Minimalizuj", - "search": "Wyszukiwanie", - "search_placeholder": "Wyszukiwanie skryptów użytkownika", - "install": "Instalacja", - "issue": "Nowy numer", - "version_number": "Wersja", - "updated": "Ostatnia aktualizacja", - "total_installs": "Łączna liczba instalacji", - "ratings": "Oceny", - "good": "Dobry", - "ok": "Ok", - "bad": "Zły", - "created_date": "Utworzony", - "redirect": "Greasy Fork dla dorosłych", - "filter": "Odfiltruj inne języki", - "dtime": "Limit czasu wyświetlania", - "save": "Zapisz", - "reset": "Reset", - "preview_code": "Kod podglądu", - "saveFile": "Zapisz plik", - "newTab": "Nowa karta", - "applies_to": "Dotyczy", - "license": "Licencja", - "no_license": "N/A", - "antifeatures": "Antywzorce", - "userjs_fullscreen": "Automatyczny pełny ekran", - "listing_none": "(Brak)", - "export_config": "Konfiguracja eksportu", - "export_theme": "Motyw eksportu", - "import_config": "Importuj konfigurację", - "import_theme": "Importuj motyw", - "code_size": "Kod Rozmiar", - "prmpt_css": "Zainstalować jako UserStyle?", - "userjs_inject": "Wstrzyknij Userscript+", - "userjs_close": "Zamknij Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "ru": { - "createdby": "Сделано", - "name": "Имя", - "daily_installs": "Ежедневные установки", - "close": "Больше не показывать", - "filterA": "Фильтр", - "max": "Максимизировать", - "min": "Минимизировать", - "search": "Поиск", - "search_placeholder": "Поиск юзерскриптов", - "install": "Установите", - "issue": "Новый выпуск", - "version_number": "Версия", - "updated": "Последнее обновление", - "total_installs": "Всего установок", - "ratings": "Рейтинги", - "good": "Хорошо", - "ok": "Хорошо", - "bad": "Плохо", - "created_date": "Создано", - "redirect": "Greasy Fork для взрослых", - "filter": "Отфильтровать другие языки", - "dtime": "Тайм-аут отображения", - "save": "Сохранить", - "reset": "Перезагрузить", - "preview_code": "Предварительный просмотр кода", - "saveFile": "Сохранить файл", - "newTab": "Новая вкладка", - "applies_to": "Применяется к", - "license": "Лицензия", - "no_license": "Недоступно", - "antifeatures": "Нежелательная функциональность", - "userjs_fullscreen": "Автоматический полноэкранный режим", - "listing_none": "(нет)", - "export_config": "Экспорт конфигурации", - "export_theme": "Экспорт темы", - "import_config": "Импорт конфигурации", - "import_theme": "Импортировать тему", - "code_size": "Код Размер", - "prmpt_css": "Установить как UserStyle?", - "userjs_inject": "Вставить Userscript+", - "userjs_close": "Закрыть Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "zh": { - "createdby": "由...制作", - "name": "姓名", - "daily_installs": "日常安装", - "close": "不再显示", - "filterA": "过滤器", - "max": "最大化", - "min": "最小化", - "search": "搜索", - "search_placeholder": "搜索用户脚本", - "install": "安装", - "issue": "新问题", - "version_number": "版本", - "updated": "最后更新", - "total_installs": "总安装量", - "ratings": "评级", - "good": "好的", - "ok": "好的", - "bad": "不好", - "created_date": "创建", - "redirect": "大人的Greasyfork", - "filter": "过滤掉其他语言", - "dtime": "显示超时", - "save": "拯救", - "reset": "重置", - "preview_code": "预览代码", - "saveFile": "保存存档", - "newTab": "新标签", - "applies_to": "适用于", - "license": "许可证", - "no_license": "暂无", - "antifeatures": "可能不受欢迎的功能", - "userjs_fullscreen": "自动全屏", - "listing_none": "(无)", - "export_config": "导出配置", - "export_theme": "导出主题", - "import_config": "导入配置", - "import_theme": "导入主题", - "code_size": "代码 尺寸", - "prmpt_css": "安装为用户风格?", - "userjs_inject": "注入 Userscript+", - "userjs_close": "关闭 Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "zh_CN": { - "createdby": "由...制作", - "name": "姓名", - "daily_installs": "日常安装", - "close": "不再显示", - "filterA": "过滤器", - "max": "最大化", - "min": "最小化", - "search": "搜索", - "search_placeholder": "搜索用户脚本", - "install": "安装", - "issue": "新问题", - "version_number": "版本", - "updated": "最后更新", - "total_installs": "总安装量", - "ratings": "评级", - "good": "好的", - "ok": "好的", - "bad": "不好", - "created_date": "创建", - "redirect": "大人的Greasyfork", - "filter": "过滤掉其他语言", - "dtime": "显示超时", - "save": "拯救", - "reset": "重置", - "preview_code": "预览代码", - "saveFile": "保存存档", - "newTab": "新标签", - "applies_to": "适用于", - "license": "许可证", - "no_license": "暂无", - "antifeatures": "可能不受欢迎的功能", - "userjs_fullscreen": "自动全屏", - "listing_none": "(无)", - "export_config": "导出配置", - "export_theme": "导出主题", - "import_config": "导入配置", - "import_theme": "导入主题", - "code_size": "代码 尺寸", - "prmpt_css": "安装为用户风格?", - "userjs_inject": "注入 Userscript+", - "userjs_close": "关闭 Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - }, - "zh_TW": { - "createdby": "由...制作", - "name": "姓名", - "daily_installs": "日常安装", - "close": "不再显示", - "filterA": "过滤器", - "max": "最大化", - "min": "最小化", - "search": "搜索", - "search_placeholder": "搜索用户脚本", - "install": "安装", - "issue": "新问题", - "version_number": "版本", - "updated": "最后更新", - "total_installs": "总安装量", - "ratings": "评级", - "good": "好的", - "ok": "好的", - "bad": "不好", - "created_date": "创建", - "redirect": "大人的Greasyfork", - "filter": "过滤掉其他语言", - "dtime": "显示超时", - "save": "拯救", - "reset": "重置", - "preview_code": "预览代码", - "saveFile": "保存存档", - "newTab": "新标签", - "applies_to": "适用于", - "license": "许可证", - "no_license": "暂无", - "antifeatures": "可能不受欢迎的功能", - "userjs_fullscreen": "自动全屏", - "listing_none": "(无)", - "export_config": "导出配置", - "export_theme": "导出主题", - "import_config": "导入配置", - "import_theme": "导入主题", - "code_size": "代码 尺寸", - "prmpt_css": "作為使用者樣式安裝?", - "userjs_inject": "注入用戶腳本+", - "userjs_close": "關閉用戶腳本+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" - } -}; -/** + const translations = { + "ar": { + "createdby": "انشأ من قبل", + "name": "اسم", + "daily_installs": "التثبيت اليومي", + "close": "يغلق", + "filterA": "منقي", + "max": "تحقيق أقصى قدر", + "min": "تصغير", + "search": "يبحث", + "search_placeholder": "بحث في البرامج النصية", + "install": "تثبيت", + "issue": "إصدار جديد", + "version_number": "الإصدار", + "updated": "آخر تحديث", + "total_installs": "إجمالي التثبيت", + "ratings": "التقييمات", + "good": "جيد", + "ok": "جيد", + "bad": "سيء", + "created_date": "تم إنشاؤه", + "redirect": "شوكة دهنية للكبار", + "filter": "تصفية اللغات الأخرى", + "dtime": "عرض المهلة", + "save": "حفظ", + "reset": "إعادة تعيين", + "preview_code": "كود المعاينة", + "saveFile": "احفظ الملف", + "newTab": "علامة تبويب جديدة", + "applies_to": "ينطبق على", + "license": "الترخيص", + "no_license": "لا يوجد", + "antifeatures": "إعلانات", + "userjs_fullscreen": "ملء الشاشة الكاملة التلقائي", + "listing_none": "(لا يوجد)", + "export_config": "تهيئة التصدير", + "export_theme": "تصدير السمة", + "import_config": "استيراد تهيئة الاستيراد", + "import_theme": "استيراد النسق", + "code_size": "حجم الرمز", + "prmpt_css": "التثبيت كأسلوب المستخدم؟", + "userjs_inject": "حقن Userscript+", + "userjs_close": "إغلاق Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "de": { + "createdby": "Erstellt von", + "name": "Name", + "daily_installs": "Tägliche Installationen", + "close": "Schließen Sie", + "filterA": "Filter", + "max": "Maximieren Sie", + "min": "minimieren", + "search": "Suche", + "search_placeholder": "Suche nach Userscripts", + "install": "Installieren Sie", + "issue": "Neue Ausgabe", + "version_number": "Version", + "updated": "Zuletzt aktualisiert", + "total_installs": "Installationen insgesamt", + "ratings": "Bewertungen", + "good": "Gut", + "ok": "Okay", + "bad": "Schlecht", + "created_date": "Erstellt", + "redirect": "Greasy Fork für Erwachsene", + "filter": "Andere Sprachen herausfiltern", + "dtime": "Zeitüberschreitung anzeigen", + "save": "Speichern Sie", + "reset": "Zurücksetzen", + "preview_code": "Vorschau Code", + "saveFile": "Datei speichern", + "newTab": "Neue Registerkarte", + "applies_to": "Gilt für", + "license": "Lizenz", + "no_license": "N/A", + "antifeatures": "Antifeatures", + "userjs_fullscreen": "Automatischer Vollbildmodus", + "listing_none": "(Keine)", + "export_config": "Konfig exportieren", + "export_theme": "Thema exportieren", + "import_config": "Konfig importieren", + "import_theme": "Thema importieren", + "code_size": "Code Größe", + "prmpt_css": "Als UserStyle installieren?", + "userjs_inject": "Userscript+ einfügen", + "userjs_close": "Userscript+ schließen", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "en": { + "createdby": "Created by", + "name": "Name", + "daily_installs": "Daily Installs", + "close": "Close", + "filterA": "Filter", + "max": "Maximize", + "min": "Minimize", + "search": "Search", + "search_placeholder": "Search for userscripts", + "install": "Install", + "issue": "New Issue", + "version_number": "Version", + "updated": "Last Updated", + "total_installs": "Total Installs", + "ratings": "Ratings", + "good": "Good", + "ok": "Okay", + "bad": "Bad", + "created_date": "Created", + "redirect": "Greasy Fork for adults", + "filter": "Filter out other languages", + "dtime": "Display Timeout", + "save": "Save", + "reset": "Reset", + "preview_code": "Preview Code", + "saveFile": "Save File", + "newTab": "New Tab", + "applies_to": "Applies to", + "license": "License", + "no_license": "N/A", + "antifeatures": "Antifeatures", + "userjs_fullscreen": "Automatic Fullscreen", + "listing_none": "(None)", + "export_config": "Export Config", + "export_theme": "Export Theme", + "import_config": "Import Config", + "import_theme": "Import Theme", + "code_size": "Code Size", + "prmpt_css": "Install as UserStyle?", + "userjs_inject": "Inject Userscript+", + "userjs_close": "Close Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "en_GB": { + "createdby": "Created by", + "name": "Name", + "daily_installs": "Daily Installs", + "close": "Close", + "filterA": "Filter", + "max": "Maximize", + "min": "Minimize", + "search": "Search", + "search_placeholder": "Search scripts", + "install": "Install", + "issue": "New Issue", + "version_number": "Version", + "updated": "Last Updated", + "total_installs": "Total Installs", + "ratings": "Ratings", + "good": "Good", + "ok": "Ok", + "bad": "Bad", + "created_date": "Created", + "redirect": "Greasy Fork for adults", + "filter": "Filter out other languages", + "dtime": "Display Timeout", + "save": "Save", + "reset": "Reset", + "preview_code": "Preview Code", + "saveFile": "Save File", + "newTab": "New Tab", + "applies_to": "Applies to", + "license": "License", + "no_license": "N/A", + "antifeatures": "Antifeatures", + "userjs_fullscreen": "Automatic Fullscreen", + "listing_none": "(None)", + "export_config": "Export Config", + "export_theme": "Export Theme", + "import_config": "Import Config", + "import_theme": "Import Theme", + "code_size": "Code Size", + "prmpt_css": "Install as UserStyle?", + "userjs_inject": "Inject Userscript+", + "userjs_close": "Close Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "es": { + "createdby": "Creado por", + "name": "Nombre", + "daily_installs": "Instalaciones diarias", + "close": "Ya no se muestra", + "filterA": "Filtro", + "max": "Maximizar", + "min": "Minimizar", + "search": "Busque en", + "search_placeholder": "Buscar userscripts", + "install": "Instalar", + "issue": "Nueva edición", + "version_number": "Versión", + "updated": "Última actualización", + "total_installs": "Total de instalaciones", + "ratings": "Clasificaciones", + "good": "Bueno", + "ok": "Ok", + "bad": "Malo", + "created_date": "Creado", + "redirect": "Greasy Fork para adultos", + "filter": "Filtrar otros idiomas", + "dtime": "Mostrar el tiempo de espera", + "save": "Guardar", + "reset": "Reiniciar", + "preview_code": "Vista previa del código", + "saveFile": "Guardar archivo", + "newTab": "Guardar archivo", + "applies_to": "Se aplica a", + "license": "Licencia", + "no_license": "Desconocida", + "antifeatures": "Características indeseables", + "userjs_fullscreen": "Pantalla completa automática", + "listing_none": "(Ninguno)", + "export_config": "Exportar configuración", + "export_theme": "Exportar tema", + "import_config": "Importar configuración", + "import_theme": "Importar tema", + "code_size": "Código Tamaño", + "prmpt_css": "¿Instalar como UserStyle?", + "userjs_inject": "Inyectar Userscript+", + "userjs_close": "Cerrar Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "fr": { + "createdby": "Créé par", + "name": "Nom", + "daily_installs": "Installations quotidiennes", + "close": "Ne plus montrer", + "filterA": "Filtre", + "max": "Maximiser", + "min": "Minimiser", + "search": "Recherche", + "search_placeholder": "Rechercher des userscripts", + "install": "Installer", + "issue": "Nouveau numéro", + "version_number": "Version", + "updated": "Dernière mise à jour", + "total_installs": "Total des installations", + "ratings": "Notations", + "good": "Bon", + "ok": "Ok", + "bad": "Mauvais", + "created_date": "Créé", + "redirect": "Greasy Fork pour les adultes", + "filter": "Filtrer les autres langues", + "dtime": "Délai d'affichage", + "save": "Sauvez", + "reset": "Réinitialiser", + "preview_code": "Prévisualiser le code", + "saveFile": "Enregistrer le fichier", + "newTab": "Nouvel onglet", + "applies_to": "S'applique à", + "license": "Licence", + "no_license": "N/A", + "antifeatures": "Antifeatures", + "userjs_fullscreen": "Plein écran automatique", + "listing_none": "(Aucun)", + "export_config": "Export Config", + "export_theme": "Exporter le thème", + "import_config": "Importer la configuration", + "import_theme": "Importer le thème", + "code_size": "Code Taille", + "prmpt_css": "Installer comme UserStyle ?", + "userjs_inject": "Injecter Userscript+", + "userjs_close": "Fermer Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "ja": { + "createdby": "によって作成された", + "name": "名前", + "daily_installs": "デイリーインストール", + "close": "表示されなくなりました", + "filterA": "フィルター", + "max": "最大化", + "min": "ミニマム", + "search": "検索", + "search_placeholder": "ユーザースクリプトの検索", + "install": "インストール", + "issue": "新刊のご案内", + "version_number": "バージョン", + "updated": "最終更新日", + "total_installs": "総インストール数", + "ratings": "レーティング", + "good": "グッド", + "ok": "良い", + "bad": "悪い", + "created_date": "作成", + "redirect": "大人のGreasyfork", + "filter": "他の言語をフィルタリングする", + "dtime": "表示タイムアウト", + "save": "拯救", + "reset": "リセット", + "preview_code": "コードのプレビュー", + "saveFile": "ファイルを保存", + "newTab": "新しいタブ", + "applies_to": "適用対象", + "license": "ライセンス", + "no_license": "不明", + "antifeatures": "アンチ機能", + "userjs_fullscreen": "自動フルスクリーン", + "listing_none": "(なし)", + "export_config": "エクスポート設定", + "export_theme": "テーマのエクスポート", + "import_config": "設定のインポート", + "import_theme": "テーマのインポート", + "code_size": "コード・サイズ", + "prmpt_css": "UserStyleとしてインストールしますか?", + "userjs_inject": "Userscript+ を挿入", + "userjs_close": "Userscript+ を閉じる", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "nl": { + "createdby": "Gemaakt door", + "name": "Naam", + "daily_installs": "Dagelijkse Installaties", + "close": "Sluit", + "filterA": "Filter", + "max": "Maximaliseer", + "min": "Minimaliseer", + "search": "Zoek", + "search_placeholder": "Zoeken naar gebruikersscripts", + "install": "Installeer", + "issue": "Nieuw Issue", + "version_number": "Versie", + "updated": "Laatste Update", + "total_installs": "Totale Installaties", + "ratings": "Beoordeling", + "good": "Goed", + "ok": "Ok", + "bad": "Slecht", + "created_date": "Aangemaakt", + "redirect": "Greasy Fork voor volwassenen", + "filter": "Filter andere talen", + "dtime": "Weergave timeout", + "save": "Opslaan", + "reset": "Opnieuw instellen", + "preview_code": "Voorbeeldcode", + "saveFile": "Bestand opslaan", + "newTab": "Nieuw tabblad", + "applies_to": "Geldt voor", + "license": "Licentie", + "no_license": "N.v.t.", + "antifeatures": "Functies voor eigen gewin", + "userjs_fullscreen": "Automatisch volledig scherm", + "listing_none": "(Geen)", + "export_config": "Configuratie exporteren", + "export_theme": "Thema exporteren", + "import_config": "Configuratie importeren", + "import_theme": "Thema importeren", + "code_size": "Code Grootte", + "prmpt_css": "Installeren als UserStyle?", + "userjs_inject": "Injecteer Userscript+", + "userjs_close": "Sluit Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "pl": { + "createdby": "Stworzony przez", + "name": "Nazwa", + "daily_installs": "Codzienne instalacje", + "close": "Zamknij", + "filterA": "Filtr", + "max": "Maksymalizuj", + "min": "Minimalizuj", + "search": "Wyszukiwanie", + "search_placeholder": "Wyszukiwanie skryptów użytkownika", + "install": "Instalacja", + "issue": "Nowy numer", + "version_number": "Wersja", + "updated": "Ostatnia aktualizacja", + "total_installs": "Łączna liczba instalacji", + "ratings": "Oceny", + "good": "Dobry", + "ok": "Ok", + "bad": "Zły", + "created_date": "Utworzony", + "redirect": "Greasy Fork dla dorosłych", + "filter": "Odfiltruj inne języki", + "dtime": "Limit czasu wyświetlania", + "save": "Zapisz", + "reset": "Reset", + "preview_code": "Kod podglądu", + "saveFile": "Zapisz plik", + "newTab": "Nowa karta", + "applies_to": "Dotyczy", + "license": "Licencja", + "no_license": "N/A", + "antifeatures": "Antywzorce", + "userjs_fullscreen": "Automatyczny pełny ekran", + "listing_none": "(Brak)", + "export_config": "Konfiguracja eksportu", + "export_theme": "Motyw eksportu", + "import_config": "Importuj konfigurację", + "import_theme": "Importuj motyw", + "code_size": "Kod Rozmiar", + "prmpt_css": "Zainstalować jako UserStyle?", + "userjs_inject": "Wstrzyknij Userscript+", + "userjs_close": "Zamknij Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "ru": { + "createdby": "Сделано", + "name": "Имя", + "daily_installs": "Ежедневные установки", + "close": "Больше не показывать", + "filterA": "Фильтр", + "max": "Максимизировать", + "min": "Минимизировать", + "search": "Поиск", + "search_placeholder": "Поиск юзерскриптов", + "install": "Установите", + "issue": "Новый выпуск", + "version_number": "Версия", + "updated": "Последнее обновление", + "total_installs": "Всего установок", + "ratings": "Рейтинги", + "good": "Хорошо", + "ok": "Хорошо", + "bad": "Плохо", + "created_date": "Создано", + "redirect": "Greasy Fork для взрослых", + "filter": "Отфильтровать другие языки", + "dtime": "Тайм-аут отображения", + "save": "Сохранить", + "reset": "Перезагрузить", + "preview_code": "Предварительный просмотр кода", + "saveFile": "Сохранить файл", + "newTab": "Новая вкладка", + "applies_to": "Применяется к", + "license": "Лицензия", + "no_license": "Недоступно", + "antifeatures": "Нежелательная функциональность", + "userjs_fullscreen": "Автоматический полноэкранный режим", + "listing_none": "(нет)", + "export_config": "Экспорт конфигурации", + "export_theme": "Экспорт темы", + "import_config": "Импорт конфигурации", + "import_theme": "Импортировать тему", + "code_size": "Код Размер", + "prmpt_css": "Установить как UserStyle?", + "userjs_inject": "Вставить Userscript+", + "userjs_close": "Закрыть Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "zh": { + "createdby": "由...制作", + "name": "姓名", + "daily_installs": "日常安装", + "close": "不再显示", + "filterA": "过滤器", + "max": "最大化", + "min": "最小化", + "search": "搜索", + "search_placeholder": "搜索用户脚本", + "install": "安装", + "issue": "新问题", + "version_number": "版本", + "updated": "最后更新", + "total_installs": "总安装量", + "ratings": "评级", + "good": "好的", + "ok": "好的", + "bad": "不好", + "created_date": "创建", + "redirect": "大人的Greasyfork", + "filter": "过滤掉其他语言", + "dtime": "显示超时", + "save": "拯救", + "reset": "重置", + "preview_code": "预览代码", + "saveFile": "保存存档", + "newTab": "新标签", + "applies_to": "适用于", + "license": "许可证", + "no_license": "暂无", + "antifeatures": "可能不受欢迎的功能", + "userjs_fullscreen": "自动全屏", + "listing_none": "(无)", + "export_config": "导出配置", + "export_theme": "导出主题", + "import_config": "导入配置", + "import_theme": "导入主题", + "code_size": "代码 尺寸", + "prmpt_css": "安装为用户风格?", + "userjs_inject": "注入 Userscript+", + "userjs_close": "关闭 Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "zh_CN": { + "createdby": "由...制作", + "name": "姓名", + "daily_installs": "日常安装", + "close": "不再显示", + "filterA": "过滤器", + "max": "最大化", + "min": "最小化", + "search": "搜索", + "search_placeholder": "搜索用户脚本", + "install": "安装", + "issue": "新问题", + "version_number": "版本", + "updated": "最后更新", + "total_installs": "总安装量", + "ratings": "评级", + "good": "好的", + "ok": "好的", + "bad": "不好", + "created_date": "创建", + "redirect": "大人的Greasyfork", + "filter": "过滤掉其他语言", + "dtime": "显示超时", + "save": "拯救", + "reset": "重置", + "preview_code": "预览代码", + "saveFile": "保存存档", + "newTab": "新标签", + "applies_to": "适用于", + "license": "许可证", + "no_license": "暂无", + "antifeatures": "可能不受欢迎的功能", + "userjs_fullscreen": "自动全屏", + "listing_none": "(无)", + "export_config": "导出配置", + "export_theme": "导出主题", + "import_config": "导入配置", + "import_theme": "导入主题", + "code_size": "代码 尺寸", + "prmpt_css": "安装为用户风格?", + "userjs_inject": "注入 Userscript+", + "userjs_close": "关闭 Userscript+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + }, + "zh_TW": { + "createdby": "由...制作", + "name": "姓名", + "daily_installs": "日常安装", + "close": "不再显示", + "filterA": "过滤器", + "max": "最大化", + "min": "最小化", + "search": "搜索", + "search_placeholder": "搜索用户脚本", + "install": "安装", + "issue": "新问题", + "version_number": "版本", + "updated": "最后更新", + "total_installs": "总安装量", + "ratings": "评级", + "good": "好的", + "ok": "好的", + "bad": "不好", + "created_date": "创建", + "redirect": "大人的Greasyfork", + "filter": "过滤掉其他语言", + "dtime": "显示超时", + "save": "拯救", + "reset": "重置", + "preview_code": "预览代码", + "saveFile": "保存存档", + "newTab": "新标签", + "applies_to": "适用于", + "license": "许可证", + "no_license": "暂无", + "antifeatures": "可能不受欢迎的功能", + "userjs_fullscreen": "自动全屏", + "listing_none": "(无)", + "export_config": "导出配置", + "export_theme": "导出主题", + "import_config": "导入配置", + "import_theme": "导入主题", + "code_size": "代码 尺寸", + "prmpt_css": "作為使用者樣式安裝?", + "userjs_inject": "注入用戶腳本+", + "userjs_close": "關閉用戶腳本+", + "userjs_sync": "Sync with UserScript Manager", + "userjs_autoinject": "Inject on load", + "auto_fetch": "Fetch on load" + } + }; + /** * To compile this CSS `pnpm run build:Sass` * * [Uncompiled Cascading Style Sheet](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/sass) */ -const main_css = `mujs-root { + const main_css = `mujs-root { --mujs-even-row: hsl(222, 14%, 22%); --mujs-odd-row: hsl(222, 14%, 11%); --mujs-even-err: hsl(0, 100%, 22%); @@ -1566,4100 +1566,4105 @@ mujs-btn { cursor: pointer !important; } `; -/******************************************************************************/ -// #region Console -const dbg = (...msg) => { - const dt = new Date(); - console.debug( - '[%cMagic Userscript+%c] %cDBG', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(255, 212, 0);', - `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, - ...msg - ); -}; -const err = (...msg) => { - console.error( - '[%cMagic Userscript+%c] %cERROR', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(249, 24, 128);', - ...msg - ); - const a = typeof alert !== 'undefined' && alert; - for (const ex of msg) { - if (typeof ex === 'object' && 'cause' in ex && a) { - a(`[Magic Userscript+] (${ex.cause}) ${ex.message}`); - } - } -}; -const info = (...msg) => { - console.info( - '[%cMagic Userscript+%c] %cINF', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(0, 186, 124);', - ...msg - ); -}; -const log = (...msg) => { - console.log( - '[%cMagic Userscript+%c] %cLOG', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(219, 160, 73);', - ...msg - ); -}; -// #endregion + /******************************************************************************/ + // #region Console + const dbg = (...msg) => { + const dt = new Date(); + console.debug( + '[%cMagic Userscript+%c] %cDBG', + 'color: rgb(29, 155, 240);', + '', + 'color: rgb(255, 212, 0);', + `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, + ...msg + ); + }; + const err = (...msg) => { + console.error( + '[%cMagic Userscript+%c] %cERROR', + 'color: rgb(29, 155, 240);', + '', + 'color: rgb(249, 24, 128);', + ...msg + ); + const a = typeof alert !== 'undefined' && alert; + for (const ex of msg) { + if (typeof ex === 'object' && 'cause' in ex && a) { + a(`[Magic Userscript+] (${ex.cause}) ${ex.message}`); + } + } + }; + const info = (...msg) => { + console.info( + '[%cMagic Userscript+%c] %cINF', + 'color: rgb(29, 155, 240);', + '', + 'color: rgb(0, 186, 124);', + ...msg + ); + }; + const log = (...msg) => { + console.log( + '[%cMagic Userscript+%c] %cLOG', + 'color: rgb(29, 155, 240);', + '', + 'color: rgb(219, 160, 73);', + ...msg + ); + }; + // #endregion -/** + /** * @type { import("../typings/types.d.ts").config } */ -let cfg = {}; + let cfg = {}; -/** + /** * https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/global-this.js * @returns {typeof globalThis} */ -function globalWin() { - const check = function (it) { - return it && it.Math === Math && it; - }; - return ( - check(typeof globalThis == 'object' && globalThis) || - check(typeof window == 'object' && window) || - check(typeof self == 'object' && self) || - check(typeof this == 'object' && this) || - (function () { - return this; - })() || - Function('return this')() - ); -} + function globalWin() { + const check = function (it) { + return it && it.Math === Math && it; + }; + return ( + check(typeof globalThis == 'object' && globalThis) || + check(typeof window == 'object' && window) || + check(typeof self == 'object' && self) || + check(typeof this == 'object' && this) || + (function () { + return this; + })() || + Function('return this')() + ); + } -/** @type { import("../typings/UserJS.d.ts").safeSelf } */ -function safeSelf() { - if (userjs.safeSelf) { - return userjs.safeSelf; - } - const g = globalWin(); - /** @type { import("../typings/UserJS.d.ts").safeHandles } */ - const safe = { - XMLHttpRequest: g.XMLHttpRequest, - createElement: g.document.createElement.bind(g.document), - createElementNS: g.document.createElementNS.bind(g.document), - createTextNode: g.document.createTextNode.bind(g.document), - setTimeout: g.setTimeout, - clearTimeout: g.clearTimeout, - navigator: g.navigator, - scheduler: { - postTask(callback, options) { - if ('scheduler' in g && 'postTask' in g.scheduler) { - return g.scheduler.postTask(callback, options); + /** @type { import("../typings/UserJS.d.ts").safeSelf } */ + function safeSelf() { + if (userjs.safeSelf) { + return userjs.safeSelf; } + const g = globalWin(); + /** @type { import("../typings/UserJS.d.ts").safeHandles } */ + const safe = { + XMLHttpRequest: g.XMLHttpRequest, + createElement: g.document.createElement.bind(g.document), + createElementNS: g.document.createElementNS.bind(g.document), + createTextNode: g.document.createTextNode.bind(g.document), + setTimeout: g.setTimeout, + clearTimeout: g.clearTimeout, + navigator: g.navigator, + scheduler: { + postTask(callback, options) { + if ('scheduler' in g && 'postTask' in g.scheduler) { + return g.scheduler.postTask(callback, options); + } - options = Object.assign({}, options); + options = Object.assign({}, options); - if (options.delay === undefined) options.delay = 0; - options.delay = Number(options.delay); - if (options.delay < 0) { - return Promise.reject(new TypeError('"delay" must be a positive number.')); - } - return new Promise((resolve) => { - g.setTimeout(() => { - resolve(callback()); - }, options.delay); - }); - }, - yield() { - if ('scheduler' in g && 'yield' in g.scheduler) { - scheduler.yield(); - return g.scheduler.yield(); - } - return new Promise((resolve) => { - g.setTimeout(resolve, 0); - }); - } + if (options.delay === undefined) options.delay = 0; + options.delay = Number(options.delay); + if (options.delay < 0) { + return Promise.reject(new TypeError('"delay" must be a positive number.')); + } + return new Promise((resolve) => { + g.setTimeout(() => { + resolve(callback()); + }, options.delay); + }); + }, + yield() { + if ('scheduler' in g && 'yield' in g.scheduler) { + scheduler.yield(); + return g.scheduler.yield(); + } + return new Promise((resolve) => { + g.setTimeout(resolve, 0); + }); + } + } + }; + userjs.safeSelf = safe; + return userjs.safeSelf; } - }; - userjs.safeSelf = safe; - return userjs.safeSelf; -} -const trustedTypes = { - policy: false, - init() { - if (this.policy) { - return this.policy; - } - this.policy = true; - try { - // Native implementation exists - if (window.trustedTypes && window.trustedTypes.createPolicy) { - window.trustedTypes.createPolicy('default', { - createHTML: (string) => string - }); - } - } catch (ex) { - ex.cause = 'trustedTypes.init()'; - err(ex); - } - return this.policy; - } -}; -trustedTypes.init(); + const trustedTypes = { + policy: false, + init() { + if (this.policy) { + return this.policy; + } + this.policy = true; + try { + // Native implementation exists + if (window.trustedTypes && window.trustedTypes.createPolicy) { + window.trustedTypes.createPolicy('default', { + createHTML: (string) => string + }); + } + } catch (ex) { + ex.cause = 'trustedTypes.init()'; + err(ex); + } + return this.policy; + } + }; + trustedTypes.init(); -const BLANK_PAGE = 'about:blank'; -// Lets highlight me :) -const authorID = 166061; -/** + const BLANK_PAGE = 'about:blank'; + // Lets highlight me :) + const authorID = 166061; + /** * Some UserJS I personally enjoy - `https://greasyfork.org/scripts/{{id}}` */ -const goodUserJS = [ - 394820, - 438684, - 4870, - 394420, - 25068, - 483444, - 1682, - 22587, - 789, - 28497, - 386908, - 24204, - 404443, - 4336, - 368183, - 393396, - 473830, - 12179, - 423001, - 376510, - 23840, - 40525, - 6456, - 'https://openuserjs.org/install/Patabugen/Always_Remember_Me.user.js', - 'https://openuserjs.org/install/nokeya/Direct_links_out.user.js', - 'https://github.com/jijirae/y2monkey/raw/main/y2monkey.user.js', - 'https://github.com/jijirae/r2monkey/raw/main/r2monkey.user.js', - 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/Manga_OnlineViewer.user.js', - 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js', - 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js' -]; -/** + const goodUserJS = [ + 394820, + 438684, + 4870, + 394420, + 25068, + 483444, + 1682, + 22587, + 789, + 28497, + 386908, + 24204, + 404443, + 4336, + 368183, + 393396, + 473830, + 12179, + 423001, + 376510, + 23840, + 40525, + 6456, + 'https://openuserjs.org/install/Patabugen/Always_Remember_Me.user.js', + 'https://openuserjs.org/install/nokeya/Direct_links_out.user.js', + 'https://github.com/jijirae/y2monkey/raw/main/y2monkey.user.js', + 'https://github.com/jijirae/r2monkey/raw/main/r2monkey.user.js', + 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/Manga_OnlineViewer.user.js', + 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js', + 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js' + ]; + /** * Remove UserJS from banned accounts */ -const badUserJS = [478597]; -// Unsupport webpages for each engine -const engineUnsupported = { - greasyfork: ['pornhub.com'], - sleazyfork: ['pornhub.com'], - openuserjs: [], - github: [] -}; -const getUAData = () => { - if (userjs.isMobile !== undefined) { - return userjs.isMobile; - } - try { - const { navigator } = safeSelf(); - if (navigator) { - const { userAgent, userAgentData } = navigator; - const { platform, mobile } = userAgentData ? Object(userAgentData) : {}; - userjs.isMobile = - /Mobile|Tablet/.test(userAgent ? String(userAgent) : '') || - Boolean(mobile) || - /Android|Apple/.test(platform ? String(platform) : ''); - } else { - userjs.isMobile = false; - } - } catch (ex) { - userjs.isMobile = false; - ex.cause = 'getUAData'; - err(ex); - } - return userjs.isMobile; -}; -const isMobile = getUAData(); -const isGM = typeof GM !== 'undefined'; -const builtinList = { - local: /localhost|router|gov|(\d+\.){3}\d+/, - finance: - /school|pay|bank|money|cart|checkout|authorize|bill|wallet|venmo|zalo|skrill|bluesnap|coin|crypto|currancy|insurance|finance/, - social: /login|join|signin|signup|sign-up|password|reset|password_reset/, - unsupported: { - host: 'fakku.net', - pathname: '/hentai/.+/read/page/.+' - } -}; -/** + const badUserJS = [478597]; + // Unsupport webpages for each engine + const engineUnsupported = { + greasyfork: ['pornhub.com'], + sleazyfork: ['pornhub.com'], + openuserjs: [], + github: [] + }; + const getUAData = () => { + if (userjs.isMobile !== undefined) { + return userjs.isMobile; + } + try { + const { navigator } = safeSelf(); + if (navigator) { + const { userAgent, userAgentData } = navigator; + const { platform, mobile } = userAgentData ? Object(userAgentData) : {}; + userjs.isMobile = + /Mobile|Tablet/.test(userAgent ? String(userAgent) : '') || + Boolean(mobile) || + /Android|Apple/.test(platform ? String(platform) : ''); + } else { + userjs.isMobile = false; + } + } catch (ex) { + userjs.isMobile = false; + ex.cause = 'getUAData'; + err(ex); + } + return userjs.isMobile; + }; + const isMobile = getUAData(); + const isGM = typeof GM !== 'undefined'; + const builtinList = { + local: /localhost|router|gov|(\d+\.){3}\d+/, + finance: + /school|pay|bank|money|cart|checkout|authorize|bill|wallet|venmo|zalo|skrill|bluesnap|coin|crypto|currancy|insurance|finance/, + social: /login|join|signin|signup|sign-up|password|reset|password_reset/, + unsupported: { + host: 'fakku.net', + pathname: '/hentai/.+/read/page/.+' + } + }; + /** * @type { import("../typings/types.d.ts").config } */ -const DEFAULT_CONFIG = { - autofetch: true, - autoinject: true, - cache: true, - codePreview: false, - autoexpand: false, - filterlang: false, - sleazyredirect: false, - time: 10000, - blacklist: ['userjs-local', 'userjs-finance', 'userjs-social', 'userjs-unsupported'], - engines: [ - { - enabled: true, - name: 'greasyfork', - query: encodeURIComponent('https://greasyfork.org/scripts/by-site/{host}.json?language=all') - }, - { - enabled: false, - name: 'sleazyfork', - query: encodeURIComponent('https://sleazyfork.org/scripts/by-site/{host}.json?language=all') - }, - { - enabled: false, - name: 'openuserjs', - query: encodeURIComponent('https://openuserjs.org/?q={host}') - }, - { - enabled: false, - name: 'github', - token: '', - query: encodeURIComponent( - 'https://api.github.com/search/code?q="// ==UserScript=="+{host}+ "// ==/UserScript=="+in:file+language:js&per_page=30' - ) - } - ], - theme: { - 'even-row': '', - 'odd-row': '', - 'even-err': '', - 'odd-err': '', - 'background-color': '', - 'gf-color': '', - 'sf-color': '', - 'border-b-color': '', - 'gf-btn-color': '', - 'sf-btn-color': '', - 'sf-txt-color': '', - 'txt-color': '', - 'chck-color': '', - 'chck-gf': '', - 'chck-git': '', - 'chck-open': '', - placeholder: '', - 'position-top': '', - 'position-bottom': '', - 'position-left': '', - 'position-right': '', - 'font-family': '' - }, - recommend: { - author: true, - others: true - }, - filters: { - ASCII: { - enabled: false, - name: 'Non-ASCII', - regExp: '[^\\x00-\\x7F\\s]+' - }, - Latin: { - enabled: false, - name: 'Non-Latin', - regExp: '[^\\u0000-\\u024F\\u2000-\\u214F\\s]+' - }, - Games: { - enabled: false, - name: 'Games', - flag: 'iu', - regExp: - 'Aimbot|AntiGame|Agar|agar\\.io|alis\\.io|angel\\.io|ExtencionRipXChetoMalo|AposBot|DFxLite|ZTx-Lite|AposFeedingBot|AposLoader|Balz|Blah Blah|Orc Clan Script|Astro\\s*Empires|^\\s*Attack|^\\s*Battle|BiteFight|Blood\\s*Wars|Bloble|Bonk|Bots|Bots4|Brawler|\\bBvS\\b|Business\\s*Tycoon|Castle\\s*Age|City\\s*Ville|chopcoin\\.io|Comunio|Conquer\\s*Club|CosmoPulse|cursors\\.io|Dark\\s*Orbit|Dead\\s*Frontier|Diep\\.io|\\bDOA\\b|doblons\\.io|DotD|Dossergame|Dragons\\s*of\\s*Atlantis|driftin\\.io|Dugout|\\bDS[a-z]+\\n|elites\\.io|Empire\\s*Board|eRep(ublik)?|Epicmafia|Epic.*War|ExoPlanet|Falcon Tools|Feuerwache|Farming|FarmVille|Fightinfo|Frontier\\s*Ville|Ghost\\s*Trapper|Gladiatus|Goalline|Gondal|gota\\.io|Grepolis|Hobopolis|\\bhwm(\\b|_)|Ikariam|\\bIT2\\b|Jellyneo|Kapi\\s*Hospital|Kings\\s*Age|Kingdoms?\\s*of|knastv(o|oe)gel|Knight\\s*Fight|\\b(Power)?KoC(Atta?ck)?\\b|\\bKOL\\b|Kongregate|Krunker|Last\\s*Emperor|Legends?\\s*of|Light\\s*Rising|lite\\.ext\\.io|Lockerz|\\bLoU\\b|Mafia\\s*(Wars|Mofo)|Menelgame|Mob\\s*Wars|Mouse\\s*Hunt|Molehill\\s*Empire|MooMoo|MyFreeFarm|narwhale\\.io|Neopets|NeoQuest|Nemexia|\\bOGame\\b|Ogar(io)?|Pardus|Pennergame|Pigskin\\s*Empire|PlayerScripts|pokeradar\\.io|Popmundo|Po?we?r\\s*(Bot|Tools)|PsicoTSI|Ravenwood|Schulterglatze|Skribbl|slither\\.io|slitherplus\\.io|slitheriogameplay|SpaceWars|splix\\.io|Survivio|\\bSW_[a-z]+\\n|\\bSnP\\b|The\\s*Crims|The\\s*West|torto\\.io|Travian|Treasure\\s*Isl(and|e)|Tribal\\s*Wars|TW.?PRO|Vampire\\s*Wars|vertix\\.io|War\\s*of\\s*Ninja|World\\s*of\\s*Tanks|West\\s*Wars|wings\\.io|\\bWoD\\b|World\\s*of\\s*Dungeons|wtf\\s*battles|Wurzelimperium|Yohoho|Zombs' - }, - SocialNetworks: { - enabled: false, - name: 'Social Networks', - flag: 'iu', - regExp: - 'Face\\s*book|Google(\\+| Plus)|\\bHabbo|Kaskus|\\bLepra|Leprosorium|MySpace|meinVZ|odnoklassniki|Одноклассники|Orkut|sch(ue|ü)ler(VZ|\\.cc)?|studiVZ|Unfriend|Valenth|VK|vkontakte|ВКонтакте|Qzone|Twitter|TweetDeck' - }, - Clutter: { - enabled: false, - name: 'Clutter', - flag: 'iu', - regExp: - "^\\s*(.{1,3})\\1+\\n|^\\s*(.+?)\\n+\\2\\n*$|^\\s*.{1,5}\\n|do\\s*n('|o)?t (install|download)|nicht installieren|(just )?(\\ban? |\\b)test(ing|s|\\d|\\b)|^\\s*.{0,4}test.{0,4}\\n|\\ntest(ing)?\\s*|^\\s*(\\{@|Smolka|Hacks)|\\[\\d{4,5}\\]|free\\s*download|theme|(night|dark) ?(mode)?" - } - } -}; -//#region i18n -class i18nHandler { - constructor() { - /** @type { string[] } */ - this.cache = []; + const DEFAULT_CONFIG = { + autofetch: true, + autoinject: true, + cache: true, + codePreview: false, + autoexpand: false, + filterlang: false, + sleazyredirect: false, + time: 10000, + blacklist: ['userjs-local', 'userjs-finance', 'userjs-social', 'userjs-unsupported'], + engines: [ + { + enabled: true, + name: 'greasyfork', + query: encodeURIComponent('https://greasyfork.org/scripts/by-site/{host}.json?language=all') + }, + { + enabled: false, + name: 'sleazyfork', + query: encodeURIComponent('https://sleazyfork.org/scripts/by-site/{host}.json?language=all') + }, + { + enabled: false, + name: 'openuserjs', + query: encodeURIComponent('https://openuserjs.org/?q={host}') + }, + { + enabled: false, + name: 'github', + token: '', + query: encodeURIComponent( + 'https://api.github.com/search/code?q="// ==UserScript=="+{host}+ "// ==/UserScript=="+in:file+language:js&per_page=30' + ) + } + ], + theme: { + 'even-row': '', + 'odd-row': '', + 'even-err': '', + 'odd-err': '', + 'background-color': '', + 'gf-color': '', + 'sf-color': '', + 'border-b-color': '', + 'gf-btn-color': '', + 'sf-btn-color': '', + 'sf-txt-color': '', + 'txt-color': '', + 'chck-color': '', + 'chck-gf': '', + 'chck-git': '', + 'chck-open': '', + placeholder: '', + 'position-top': '', + 'position-bottom': '', + 'position-left': '', + 'position-right': '', + 'font-family': '' + }, + recommend: { + author: true, + others: true + }, + filters: { + ASCII: { + enabled: false, + name: 'Non-ASCII', + regExp: '[^\\x00-\\x7F\\s]+' + }, + Latin: { + enabled: false, + name: 'Non-Latin', + regExp: '[^\\u0000-\\u024F\\u2000-\\u214F\\s]+' + }, + Games: { + enabled: false, + name: 'Games', + flag: 'iu', + regExp: + 'Aimbot|AntiGame|Agar|agar\\.io|alis\\.io|angel\\.io|ExtencionRipXChetoMalo|AposBot|DFxLite|ZTx-Lite|AposFeedingBot|AposLoader|Balz|Blah Blah|Orc Clan Script|Astro\\s*Empires|^\\s*Attack|^\\s*Battle|BiteFight|Blood\\s*Wars|Bloble|Bonk|Bots|Bots4|Brawler|\\bBvS\\b|Business\\s*Tycoon|Castle\\s*Age|City\\s*Ville|chopcoin\\.io|Comunio|Conquer\\s*Club|CosmoPulse|cursors\\.io|Dark\\s*Orbit|Dead\\s*Frontier|Diep\\.io|\\bDOA\\b|doblons\\.io|DotD|Dossergame|Dragons\\s*of\\s*Atlantis|driftin\\.io|Dugout|\\bDS[a-z]+\\n|elites\\.io|Empire\\s*Board|eRep(ublik)?|Epicmafia|Epic.*War|ExoPlanet|Falcon Tools|Feuerwache|Farming|FarmVille|Fightinfo|Frontier\\s*Ville|Ghost\\s*Trapper|Gladiatus|Goalline|Gondal|gota\\.io|Grepolis|Hobopolis|\\bhwm(\\b|_)|Ikariam|\\bIT2\\b|Jellyneo|Kapi\\s*Hospital|Kings\\s*Age|Kingdoms?\\s*of|knastv(o|oe)gel|Knight\\s*Fight|\\b(Power)?KoC(Atta?ck)?\\b|\\bKOL\\b|Kongregate|Krunker|Last\\s*Emperor|Legends?\\s*of|Light\\s*Rising|lite\\.ext\\.io|Lockerz|\\bLoU\\b|Mafia\\s*(Wars|Mofo)|Menelgame|Mob\\s*Wars|Mouse\\s*Hunt|Molehill\\s*Empire|MooMoo|MyFreeFarm|narwhale\\.io|Neopets|NeoQuest|Nemexia|\\bOGame\\b|Ogar(io)?|Pardus|Pennergame|Pigskin\\s*Empire|PlayerScripts|pokeradar\\.io|Popmundo|Po?we?r\\s*(Bot|Tools)|PsicoTSI|Ravenwood|Schulterglatze|Skribbl|slither\\.io|slitherplus\\.io|slitheriogameplay|SpaceWars|splix\\.io|Survivio|\\bSW_[a-z]+\\n|\\bSnP\\b|The\\s*Crims|The\\s*West|torto\\.io|Travian|Treasure\\s*Isl(and|e)|Tribal\\s*Wars|TW.?PRO|Vampire\\s*Wars|vertix\\.io|War\\s*of\\s*Ninja|World\\s*of\\s*Tanks|West\\s*Wars|wings\\.io|\\bWoD\\b|World\\s*of\\s*Dungeons|wtf\\s*battles|Wurzelimperium|Yohoho|Zombs' + }, + SocialNetworks: { + enabled: false, + name: 'Social Networks', + flag: 'iu', + regExp: + 'Face\\s*book|Google(\\+| Plus)|\\bHabbo|Kaskus|\\bLepra|Leprosorium|MySpace|meinVZ|odnoklassniki|Одноклассники|Orkut|sch(ue|ü)ler(VZ|\\.cc)?|studiVZ|Unfriend|Valenth|VK|vkontakte|ВКонтакте|Qzone|Twitter|TweetDeck' + }, + Clutter: { + enabled: false, + name: 'Clutter', + flag: 'iu', + regExp: + "^\\s*(.{1,3})\\1+\\n|^\\s*(.+?)\\n+\\2\\n*$|^\\s*.{1,5}\\n|do\\s*n('|o)?t (install|download)|nicht installieren|(just )?(\\ban? |\\b)test(ing|s|\\d|\\b)|^\\s*.{0,4}test.{0,4}\\n|\\ntest(ing)?\\s*|^\\s*(\\{@|Smolka|Hacks)|\\[\\d{4,5}\\]|free\\s*download|theme|(night|dark) ?(mode)?" + } + } + }; + //#region i18n + class i18nHandler { + constructor() { + /** @type { string[] } */ + this.cache = []; - const { navigator } = safeSelf(); - const { languages } = navigator; - for (const nlang of languages) { - const lg = nlang.split('-')[0]; - if (this.cache.indexOf(lg) === -1) { - this.cache.push(lg); - } - } + const { navigator } = safeSelf(); + const { languages } = navigator; + for (const nlang of languages) { + const lg = nlang.split('-')[0]; + if (this.cache.indexOf(lg) === -1) { + this.cache.push(lg); + } + } - const current = navigator.language.split('-')[0] ?? 'en'; - if (!this.cache.includes(current)) { - this.cache.push(current); - } - } - toDate(str = '') { - const { navigator } = safeSelf(); - return new Intl.DateTimeFormat(navigator.language).format(new Date(str)); - } - toNumber(number) { - const { navigator } = safeSelf(); - return new Intl.NumberFormat(navigator.language).format(number); - } - i18n$(...keys) { - const { navigator } = safeSelf(); - const current = navigator.language.split('-')[0] ?? 'en'; - const list = translations[cfg.language] ?? translations[current]; - const arr = []; - for (const key of keys) { - arr.push(list[key]); + const current = navigator.language.split('-')[0] ?? 'en'; + if (!this.cache.includes(current)) { + this.cache.push(current); + } + } + toDate(str = '') { + const { navigator } = safeSelf(); + return new Intl.DateTimeFormat(navigator.language).format(new Date(str)); + } + toNumber(number) { + const { navigator } = safeSelf(); + return new Intl.NumberFormat(navigator.language).format(number); + } + i18n$(...keys) { + const { navigator } = safeSelf(); + const current = navigator.language.split('-')[0] ?? 'en'; + const list = translations[cfg.language] ?? translations[current]; + const arr = []; + for (const key of keys) { + arr.push(list[key]); + } + return arr.length !== 1 ? arr : arr[0]; + } } - return arr.length !== 1 ? arr : arr[0]; - } -} -const language = new i18nHandler(); -const { i18n$ } = language; -//#endregion -// #region Utilities -const union = (...arr) => [...new Set(arr.flat())]; -/** + const language = new i18nHandler(); + const { i18n$ } = language; + //#endregion + // #region Utilities + const union = (...arr) => [...new Set(arr.flat())]; + /** * @type { import("../typings/types.d.ts").qs } */ -const qs = (selector, root) => { - try { - return (root || document).querySelector(selector); - } catch (ex) { - err(ex); - } - return null; -}; -/** + const qs = (selector, root) => { + try { + return (root || document).querySelector(selector); + } catch (ex) { + err(ex); + } + return null; + }; + /** * @type { import("../typings/types.d.ts").qsA } */ -const qsA = (selectors, root) => { - try { - return (root || document).querySelectorAll(selectors); - } catch (ex) { - err(ex); - } - return []; -}; -/** + const qsA = (selectors, root) => { + try { + return (root || document).querySelectorAll(selectors); + } catch (ex) { + err(ex); + } + return []; + }; + /** * @type { import("../typings/types.d.ts").objToStr } */ -const objToStr = (obj) => Object.prototype.toString.call(obj); -/** + const objToStr = (obj) => Object.prototype.toString.call(obj); + /** * @type { import("../typings/types.d.ts").strToURL } */ -const strToURL = (str) => { - const WIN_LOCATION = window.location ?? BLANK_PAGE; - try { - str = str ?? WIN_LOCATION; - return objToStr(str).includes('URL') ? str : new URL(str); - } catch (ex) { - ex.cause = 'strToURL'; - err(ex); - } - return WIN_LOCATION; -}; -/** + const strToURL = (str) => { + const WIN_LOCATION = window.location ?? BLANK_PAGE; + try { + str = str ?? WIN_LOCATION; + return objToStr(str).includes('URL') ? str : new URL(str); + } catch (ex) { + ex.cause = 'strToURL'; + err(ex); + } + return WIN_LOCATION; + }; + /** * @type { import("../typings/types.d.ts").isRegExp } */ -const isRegExp = (obj) => { - const s = objToStr(obj); - return s.includes('RegExp'); -}; -/** + const isRegExp = (obj) => { + const s = objToStr(obj); + return s.includes('RegExp'); + }; + /** * @type { import("../typings/types.d.ts").isElem } */ -const isElem = (obj) => { - const s = objToStr(obj); - return s.includes('Element'); -}; -/** + const isElem = (obj) => { + const s = objToStr(obj); + return s.includes('Element'); + }; + /** * @type { import("../typings/types.d.ts").isObj } */ -const isObj = (obj) => { - const s = objToStr(obj); - return s.includes('Object'); -}; -/** + const isObj = (obj) => { + const s = objToStr(obj); + return s.includes('Object'); + }; + /** * @type { import("../typings/types.d.ts").isFN } */ -const isFN = (obj) => { - const s = objToStr(obj); - return s.includes('Function'); -}; -/** + const isFN = (obj) => { + const s = objToStr(obj); + return s.includes('Function'); + }; + /** * @type { import("../typings/types.d.ts").isNull } */ -const isNull = (obj) => { - return Object.is(obj, null) || Object.is(obj, undefined); -}; -/** + const isNull = (obj) => { + return Object.is(obj, null) || Object.is(obj, undefined); + }; + /** * @type { import("../typings/types.d.ts").isBlank } */ -const isBlank = (obj) => { - return ( - (typeof obj === 'string' && Object.is(obj.trim(), '')) || - ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || - (Array.isArray(obj) && Object.is(obj.length, 0)) || - (isObj(obj) && Object.is(Object.keys(obj).length, 0)) - ); -}; -/** + const isBlank = (obj) => { + return ( + (typeof obj === 'string' && Object.is(obj.trim(), '')) || + ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || + (Array.isArray(obj) && Object.is(obj.length, 0)) || + (isObj(obj) && Object.is(Object.keys(obj).length, 0)) + ); + }; + /** * @type { import("../typings/types.d.ts").isEmpty } */ -const isEmpty = (obj) => { - return isNull(obj) || isBlank(obj); -}; -/** + const isEmpty = (obj) => { + return isNull(obj) || isBlank(obj); + }; + /** * @type { import("../typings/types.d.ts").normalizeTarget } */ -const normalizeTarget = (target, toQuery = true, root) => { - if (Object.is(target, null) || Object.is(target, undefined)) { - return []; - } - if (Array.isArray(target)) { - return target; - } - if (typeof target === 'string') { - return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target]; - } - if (isElem(target)) { - return [target]; - } - return Array.from(target); -}; -/** + const normalizeTarget = (target, toQuery = true, root) => { + if (Object.is(target, null) || Object.is(target, undefined)) { + return []; + } + if (Array.isArray(target)) { + return target; + } + if (typeof target === 'string') { + return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target]; + } + if (isElem(target)) { + return [target]; + } + return Array.from(target); + }; + /** * @type { import("../typings/types.d.ts").ael } */ -const ael = (el, type, listener, options = {}) => { - try { - for (const elem of normalizeTarget(el)) { - if (!elem) { - continue; - } - if (isMobile && type === 'click') { - elem.addEventListener('touchstart', listener, options); - continue; - } - elem.addEventListener(type, listener, options); - } - } catch (ex) { - err(ex); - } -}; -/** + const ael = (el, type, listener, options = {}) => { + try { + for (const elem of normalizeTarget(el)) { + if (!elem) { + continue; + } + if (isMobile && type === 'click') { + elem.addEventListener('touchstart', listener, options); + continue; + } + elem.addEventListener(type, listener, options); + } + } catch (ex) { + err(ex); + } + }; + /** * @type { import("../typings/types.d.ts").formAttrs } */ -const formAttrs = (elem, attr = {}) => { - if (!elem) { - return elem; - } - for (const key in attr) { - if (typeof attr[key] === 'object') { - formAttrs(elem[key], attr[key]); - } else if (isFN(attr[key])) { - if (/^on/.test(key)) { - elem[key] = attr[key]; - continue; - } - ael(elem, key, attr[key]); - } else if (key === 'class') { - elem.className = attr[key]; - } else { - elem[key] = attr[key]; - } - } - return elem; -}; -/** + const formAttrs = (elem, attr = {}) => { + if (!elem) { + return elem; + } + for (const key in attr) { + if (typeof attr[key] === 'object') { + formAttrs(elem[key], attr[key]); + } else if (isFN(attr[key])) { + if (/^on/.test(key)) { + elem[key] = attr[key]; + continue; + } + ael(elem, key, attr[key]); + } else if (key === 'class') { + elem.className = attr[key]; + } else { + elem[key] = attr[key]; + } + } + return elem; + }; + /** * @type { import("../typings/types.d.ts").make } */ -const make = (tagName, cname, attrs) => { - let el; - try { - const { createElement } = safeSelf(); - el = createElement(tagName); - if (!isEmpty(cname)) { - if (typeof cname === 'string') { - el.className = cname; - } else if (isObj(cname)) { - formAttrs(el, cname); - } - } - if (!isEmpty(attrs)) { - if (typeof attrs === 'string') { - el.textContent = attrs; - } else if (isObj(attrs)) { - formAttrs(el, attrs); - } - } - } catch (ex) { - ex.cause = 'make'; - err(ex); - } - return el; -}; + const make = (tagName, cname, attrs) => { + let el; + try { + const { createElement } = safeSelf(); + el = createElement(tagName); + if (!isEmpty(cname)) { + if (typeof cname === 'string') { + el.className = cname; + } else if (isObj(cname)) { + formAttrs(el, cname); + } + } + if (!isEmpty(attrs)) { + if (typeof attrs === 'string') { + el.textContent = attrs; + } else if (isObj(attrs)) { + formAttrs(el, attrs); + } + } + } catch (ex) { + ex.cause = 'make'; + err(ex); + } + return el; + }; -/** + /** * @type { import("../typings/UserJS.d.ts").getGMInfo } */ -const getGMInfo = () => { - if (isGM) { - if (isObj(GM.info)) { - return GM.info; - } else if (isObj(GM_info)) { - return GM_info; - } - } - return { - script: { - icon: '', - name: 'Magic Userscript+', - namespace: 'https://github.com/magicoflolis/Userscript-Plus', - updateURL: 'https://github.com/magicoflolis/Userscript-Plus/releases', - version: 'Bookmarklet', - bugs: 'https://github.com/magicoflolis/Userscript-Plus/issues' - } - }; -}; -const $info = getGMInfo(); -// #endregion -/** - * @type { import("../typings/types.d.ts").dom } - */ -const dom = { - attr(target, attr, value = undefined) { - for (const elem of normalizeTarget(target)) { - if (value === undefined) { - return elem.getAttribute(attr); - } - if (value === null) { - elem.removeAttribute(attr); - } else { - elem.setAttribute(attr, value); - } - } - }, - prop(target, prop, value = undefined) { - for (const elem of normalizeTarget(target)) { - if (value === undefined) { - return elem[prop]; - } - elem[prop] = value; - } - }, - text(target, text) { - const targets = normalizeTarget(target); - if (text === undefined) { - return targets.length !== 0 ? targets[0].textContent : undefined; - } - for (const elem of targets) { - elem.textContent = text; - } - }, - cl: { - add(target, token) { - if (Array.isArray(token)) { - for (const elem of normalizeTarget(target)) { - elem.classList.add(...token); - } - } else { - for (const elem of normalizeTarget(target)) { - elem.classList.add(token); - } - } - }, - remove(target, token) { - if (Array.isArray(token)) { - for (const elem of normalizeTarget(target)) { - elem.classList.remove(...token); - } - } else { - for (const elem of normalizeTarget(target)) { - elem.classList.remove(token); - } - } - }, - toggle(target, token, force) { - let r; - for (const elem of normalizeTarget(target)) { - r = elem.classList.toggle(token, force); - } - return r; - }, - has(target, token) { - for (const elem of normalizeTarget(target)) { - if (elem.classList.contains(token)) { - return true; + const getGMInfo = () => { + if (isGM) { + if (isObj(GM.info)) { + return GM.info; + } else if (isObj(GM_info)) { + return GM_info; + } } - } - return false; - } - } -}; -class Memorize { - constructor() { - /** - * @type {Map>} - */ - this.store = new Map(); + return { + script: { + icon: '', + name: 'Magic Userscript+', + namespace: 'https://github.com/magicoflolis/Userscript-Plus', + updateURL: 'https://github.com/magicoflolis/Userscript-Plus/releases', + version: 'Bookmarklet', + bugs: 'https://github.com/magicoflolis/Userscript-Plus/issues' + } + }; + }; + const $info = getGMInfo(); + // #endregion /** - * @type { { [key: string]: Map; userjs: Map } } - */ - this.maps = {}; - this.create('cfg', 'container', 'userjs'); - } - /** - * @template { string } S - * @param { ...S } maps - * @returns { S | S[] } - */ - create(...maps) { - const resp = []; - for (const key of maps) { - if (this.store.has(key)) { - return this.store.get(key); - } - const m = new Map(); - this.store.set(key, m); - this.maps[key] = m; - resp.push(this.store.get(key)); - } - return resp.length >= 2 ? resp : resp[0]; - } -} -const memory = new Memorize(); -//#region Icon SVGs -const iconSVG = { - cfg: { - viewBox: '0 0 24 24', - html: '' - }, - close: { - viewBox: '0 0 24 24', - html: '' - }, - filter: { - viewBox: '0 0 24 24', - html: '' - }, - fsClose: { - viewBox: '0 0 24 24', - html: ' ' - }, - fsOpen: { - viewBox: '0 0 24 24', - html: ' ' - }, - fullscreen: { - viewBox: '0 0 96 96', - html: '' - }, - gf: { - viewBox: '0 0 510.4 510.4', - html: '' - }, - gh: { - viewBox: '0 0 16 16', - html: '' - }, - hide: { - viewBox: '0 0 24 24', - html: ' ' - }, - install: { - viewBox: '0 0 16 16', - html: '' - }, - issue: { - viewBox: '0 0 24 24', - html: '' - }, - nav: { - viewBox: '0 0 24 24', - html: ' ' - }, - plus: { - viewBox: '0 0 24 24', - html: '' - }, - search: { - viewBox: '0 0 24 24', - html: '' - }, - verified: { - viewBox: '0 0 56 56', - fill: 'currentColor', - stroke: 'currentColor', - html: '' - }, - refresh: { - viewBox: '0 0 1024 1024', - fill: 'currentColor', - html: '' - }, - load(type, container) { - const { createElementNS } = safeSelf(); - const svgElem = createElementNS('http://www.w3.org/2000/svg', 'svg'); - for (const [k, v] of Object.entries(iconSVG[type])) { - if (k === 'html') { - continue; - } - svgElem.setAttributeNS(null, k, v); - } - try { - if (typeof iconSVG[type].html === 'string') { - svgElem.innerHTML = iconSVG[type].html; - } - // eslint-disable-next-line no-unused-vars - } catch (ex) { - /* empty */ - } - if (container) { - container.appendChild(svgElem); - return svgElem; - } - return svgElem.outerHTML; - } -}; -//#endregion -/** - * @type { import("../typings/UserJS.d.ts").StorageSystem } + * @type { import("../typings/types.d.ts").dom } */ -const StorageSystem = { - prefix: 'MUJS', - getItem(key) { - return window.localStorage.getItem(key); - }, - has(key) { - return !isNull(this.getItem(key)); - }, - setItem(key, value) { - window.localStorage.setItem(key, value); - }, - remove(key) { - window.localStorage.removeItem(key); - }, - async setValue(key, v) { - v = typeof v === 'string' ? v : JSON.stringify(v ?? {}); - if (isGM) { - if (isFN(GM.setValue)) { - await GM.setValue(key, v); - } else if (isFN(GM_setValue)) { - GM_setValue(key, v); - } - } else { - this.setItem(`${this.prefix}-${key}`, v); - } - }, - async getValue(key, def = {}) { - try { - if (isGM) { - let GMType; - if (isFN(GM.getValue)) { - GMType = await GM.getValue(key, JSON.stringify(def)); - } else if (isFN(GM_getValue)) { - GMType = GM_getValue(key, JSON.stringify(def)); + const dom = { + attr(target, attr, value = undefined) { + for (const elem of normalizeTarget(target)) { + if (value === undefined) { + return elem.getAttribute(attr); + } + if (value === null) { + elem.removeAttribute(attr); + } else { + elem.setAttribute(attr, value); + } + } + }, + prop(target, prop, value = undefined) { + for (const elem of normalizeTarget(target)) { + if (value === undefined) { + return elem[prop]; + } + elem[prop] = value; + } + }, + text(target, text) { + const targets = normalizeTarget(target); + if (text === undefined) { + return targets.length !== 0 ? targets[0].textContent : undefined; + } + for (const elem of targets) { + elem.textContent = text; + } + }, + cl: { + add(target, token) { + if (Array.isArray(token)) { + for (const elem of normalizeTarget(target)) { + elem.classList.add(...token); + } + } else { + for (const elem of normalizeTarget(target)) { + elem.classList.add(token); + } + } + }, + remove(target, token) { + if (Array.isArray(token)) { + for (const elem of normalizeTarget(target)) { + elem.classList.remove(...token); + } + } else { + for (const elem of normalizeTarget(target)) { + elem.classList.remove(token); + } + } + }, + toggle(target, token, force) { + let r; + for (const elem of normalizeTarget(target)) { + r = elem.classList.toggle(token, force); + } + return r; + }, + has(target, token) { + for (const elem of normalizeTarget(target)) { + if (elem.classList.contains(token)) { + return true; + } + } + return false; + } } - if (!isNull(GMType)) { - return JSON.parse(GMType); + }; + class Memorize { + constructor() { + /** + * @type {Map>} + */ + this.store = new Map(); + /** + * @type { { [key: string]: Map; userjs: Map } } + */ + this.maps = {}; + this.create('cfg', 'container', 'userjs'); + } + /** + * @template { string } S + * @param { ...S } maps + * @returns { S | S[] } + */ + create(...maps) { + const resp = []; + for (const key of maps) { + if (this.store.has(key)) { + return this.store.get(key); + } + const m = new Map(); + this.store.set(key, m); + this.maps[key] = m; + resp.push(this.store.get(key)); + } + return resp.length >= 2 ? resp : resp[0]; } - } - return this.has(`${this.prefix}-${key}`) - ? JSON.parse(this.getItem(`${this.prefix}-${key}`)) - : def; - } catch (ex) { - err(ex); - return def; - } - } -}; -const Command = { - cmds: new Set(), - register(text, command) { - if (!isGM) { - return; } + const memory = new Memorize(); + //#region Icon SVGs + const iconSVG = { + cfg: { + viewBox: '0 0 24 24', + html: '' + }, + close: { + viewBox: '0 0 24 24', + html: '' + }, + filter: { + viewBox: '0 0 24 24', + html: '' + }, + fsClose: { + viewBox: '0 0 24 24', + html: ' ' + }, + fsOpen: { + viewBox: '0 0 24 24', + html: ' ' + }, + fullscreen: { + viewBox: '0 0 96 96', + html: '' + }, + gf: { + viewBox: '0 0 510.4 510.4', + html: '' + }, + gh: { + viewBox: '0 0 16 16', + html: '' + }, + hide: { + viewBox: '0 0 24 24', + html: ' ' + }, + install: { + viewBox: '0 0 16 16', + html: '' + }, + issue: { + viewBox: '0 0 24 24', + html: '' + }, + nav: { + viewBox: '0 0 24 24', + html: ' ' + }, + plus: { + viewBox: '0 0 24 24', + html: '' + }, + search: { + viewBox: '0 0 24 24', + html: '' + }, + verified: { + viewBox: '0 0 56 56', + fill: 'currentColor', + stroke: 'currentColor', + html: '' + }, + refresh: { + viewBox: '0 0 1024 1024', + fill: 'currentColor', + html: '' + }, + load(type, container) { + const { createElementNS } = safeSelf(); + const svgElem = createElementNS('http://www.w3.org/2000/svg', 'svg'); + for (const [k, v] of Object.entries(iconSVG[type])) { + if (k === 'html') { + continue; + } + svgElem.setAttributeNS(null, k, v); + } + try { + if (typeof iconSVG[type].html === 'string') { + svgElem.innerHTML = iconSVG[type].html; + } + // eslint-disable-next-line no-unused-vars + } catch (ex) { + /* empty */ + } + if (container) { + container.appendChild(svgElem); + return svgElem; + } + return svgElem.outerHTML; + } + }; + //#endregion + /** + * @type { import("../typings/UserJS.d.ts").StorageSystem } + */ + const StorageSystem = { + prefix: 'MUJS', + getItem(key) { + return window.localStorage.getItem(key); + }, + has(key) { + return !isNull(this.getItem(key)); + }, + setItem(key, value) { + window.localStorage.setItem(key, value); + }, + remove(key) { + window.localStorage.removeItem(key); + }, + async setValue(key, v) { + v = typeof v === 'string' ? v : JSON.stringify(v ?? {}); + if (isGM) { + if (isFN(GM.setValue)) { + await GM.setValue(key, v); + } else if (isFN(GM_setValue)) { + GM_setValue(key, v); + } + } else { + this.setItem(`${this.prefix}-${key}`, v); + } + }, + async getValue(key, def = {}) { + try { + if (isGM) { + let GMType; + if (isFN(GM.getValue)) { + GMType = await GM.getValue(key, JSON.stringify(def)); + } else if (isFN(GM_getValue)) { + GMType = GM_getValue(key, JSON.stringify(def)); + } + if (!isNull(GMType)) { + return JSON.parse(GMType); + } + } + return this.has(`${this.prefix}-${key}`) + ? JSON.parse(this.getItem(`${this.prefix}-${key}`)) + : def; + } catch (ex) { + err(ex); + return def; + } + } + }; + const Command = { + cmds: new Set(), + register(text, command) { + if (!isGM) { + return; + } - if (isFN(command)) { - if (this.cmds.has(command)) { - return; - } - this.cmds.add(command); - } + if (isFN(command)) { + if (this.cmds.has(command)) { + return; + } + this.cmds.add(command); + } - if (isFN(GM.registerMenuCommand)) { - GM.registerMenuCommand(text, command); - } else if (isFN(GM_registerMenuCommand)) { - GM_registerMenuCommand(text, command); - } - } -}; -/** + if (isFN(GM.registerMenuCommand)) { + GM.registerMenuCommand(text, command); + } else if (isFN(GM_registerMenuCommand)) { + GM_registerMenuCommand(text, command); + } + } + }; + /** * @type { import("../typings/UserJS.d.ts").Network } */ -const Network = { - async req(url, method = 'GET', responseType = 'json', data, useFetch = false) { - if (isEmpty(url)) { - throw new Error('"url" parameter is empty'); - } - data = Object.assign({}, data); - method = this.bscStr(method, false); - responseType = this.bscStr(responseType); - const params = { - method, - ...data - }; - if (params.hermes) { - delete params.hermes; - } - if (isGM && !useFetch) { - if (params.credentials) { - Object.assign(params, { - anonymous: false - }); - if (Object.is(params.credentials, 'omit')) { - Object.assign(params, { - anonymous: true - }); - } - delete params.credentials; - } - } else if (params.onprogress) { - delete params.onprogress; - } - return new Promise((resolve, reject) => { - if (isGM && !useFetch) { - Network.xmlRequest({ - url, - responseType, - ...params, - onerror: (r_1) => { - reject(new Error(`${r_1.status} ${url}`)); - }, - onload: (r_1) => { - if (r_1.status !== 200) reject(new Error(`${r_1.status} ${url}`)); - if (responseType.match(/basic/)) resolve(r_1); - resolve(r_1.response); - } - }); - } else { - fetch(url, params) - .then((response_1) => { - if (!response_1.ok) reject(response_1); - const check = (str_2 = 'text') => { - return isFN(response_1[str_2]) ? response_1[str_2]() : response_1; - }; - if (responseType.match(/buffer/)) { - resolve(check('arrayBuffer')); - } else if (responseType.match(/json/)) { - resolve(check('json')); - } else if (responseType.match(/text/)) { - resolve(check('text')); - } else if (responseType.match(/blob/)) { - resolve(check('blob')); - } else if (responseType.match(/formdata/)) { - resolve(check('formData')); - } else if (responseType.match(/clone/)) { - resolve(check('clone')); - } else if (responseType.match(/document/)) { - const domParser = new DOMParser(); - const respTxt = check('text'); - if (respTxt instanceof Promise) { - respTxt.then((txt) => { - const doc = domParser.parseFromString(txt, 'text/html'); - resolve(doc); - }); - } else { - const doc = domParser.parseFromString(respTxt, 'text/html'); - resolve(doc); - } - } else { - resolve(response_1); + const Network = { + async req(url, method = 'GET', responseType = 'json', data, useFetch = false) { + if (isEmpty(url)) { + throw new Error('"url" parameter is empty'); } - }) - .catch(reject); - } - }); - }, - format(bytes, decimals = 2) { - if (Number.isNaN(bytes)) return `0 ${this.sizes[0]}`; - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${this.sizes[i]}`; - }, - sizes: ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], - async xmlRequest(details) { - if (isGM) { - if (isFN(GM.xmlHttpRequest)) { - return GM.xmlHttpRequest(details); - } else if (isFN(GM_xmlhttpRequest)) { - return GM_xmlhttpRequest(details); - } - } - return await new Promise((resolve, reject) => { - const { XMLHttpRequest } = safeSelf(); - const req = new XMLHttpRequest(); - let method = 'GET'; - let url = BLANK_PAGE; - let body; - for (const [key, value] of Object.entries(details)) { - if (key === 'onload') { - req.addEventListener('load', () => { - if (isFN(value)) { - value(req); + data = Object.assign({}, data); + method = this.bscStr(method, false); + responseType = this.bscStr(responseType); + const params = { + method, + ...data + }; + if (params.hermes) { + delete params.hermes; } - resolve(req); - }); - } else if (key === 'onerror') { - req.addEventListener('error', (evt) => { - if (isFN(value)) { - value(evt); + if (isGM && !useFetch) { + if (params.credentials) { + Object.assign(params, { + anonymous: false + }); + if (Object.is(params.credentials, 'omit')) { + Object.assign(params, { + anonymous: true + }); + } + delete params.credentials; + } + } else if (params.onprogress) { + delete params.onprogress; } - reject(evt); - }); - } else if (key === 'onabort') { - req.addEventListener('abort', (evt) => { - if (isFN(value)) { - value(evt); + return new Promise((resolve, reject) => { + if (isGM && !useFetch) { + Network.xmlRequest({ + url, + responseType, + ...params, + onerror: (r_1) => { + reject(new Error(`${r_1.status} ${url}`)); + }, + onload: (r_1) => { + if (r_1.status !== 200) reject(new Error(`${r_1.status} ${url}`)); + if (responseType.match(/basic/)) resolve(r_1); + resolve(r_1.response); + } + }); + } else { + fetch(url, params) + .then((response_1) => { + if (!response_1.ok) reject(response_1); + const check = (str_2 = 'text') => { + return isFN(response_1[str_2]) ? response_1[str_2]() : response_1; + }; + if (responseType.match(/buffer/)) { + resolve(check('arrayBuffer')); + } else if (responseType.match(/json/)) { + resolve(check('json')); + } else if (responseType.match(/text/)) { + resolve(check('text')); + } else if (responseType.match(/blob/)) { + resolve(check('blob')); + } else if (responseType.match(/formdata/)) { + resolve(check('formData')); + } else if (responseType.match(/clone/)) { + resolve(check('clone')); + } else if (responseType.match(/document/)) { + const domParser = new DOMParser(); + const respTxt = check('text'); + if (respTxt instanceof Promise) { + respTxt.then((txt) => { + const doc = domParser.parseFromString(txt, 'text/html'); + resolve(doc); + }); + } else { + const doc = domParser.parseFromString(respTxt, 'text/html'); + resolve(doc); + } + } else { + resolve(response_1); + } + }) + .catch(reject); + } + }); + }, + format(bytes, decimals = 2) { + if (Number.isNaN(bytes)) return `0 ${this.sizes[0]}`; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${this.sizes[i]}`; + }, + sizes: ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + async xmlRequest(details) { + if (isGM) { + if (isFN(GM.xmlHttpRequest)) { + return GM.xmlHttpRequest(details); + } else if (isFN(GM_xmlhttpRequest)) { + return GM_xmlhttpRequest(details); + } } - reject(evt); - }); - } else if (key === 'onprogress') { - req.addEventListener('progress', value); - } else if (key === 'responseType') { - if (value === 'buffer') { - req.responseType = 'arraybuffer'; - } else { - req.responseType = value; - } - } else if (key === 'method') { - method = value; - } else if (key === 'url') { - url = value; - } else if (key === 'body') { - body = value; - } - } - req.open(method, url); + return await new Promise((resolve, reject) => { + const { XMLHttpRequest } = safeSelf(); + const req = new XMLHttpRequest(); + let method = 'GET'; + let url = BLANK_PAGE; + let body; + for (const [key, value] of Object.entries(details)) { + if (key === 'onload') { + req.addEventListener('load', () => { + if (isFN(value)) { + value(req); + } + resolve(req); + }); + } else if (key === 'onerror') { + req.addEventListener('error', (evt) => { + if (isFN(value)) { + value(evt); + } + reject(evt); + }); + } else if (key === 'onabort') { + req.addEventListener('abort', (evt) => { + if (isFN(value)) { + value(evt); + } + reject(evt); + }); + } else if (key === 'onprogress') { + req.addEventListener('progress', value); + } else if (key === 'responseType') { + if (value === 'buffer') { + req.responseType = 'arraybuffer'; + } else { + req.responseType = value; + } + } else if (key === 'method') { + method = value; + } else if (key === 'url') { + url = value; + } else if (key === 'body') { + body = value; + } + } + req.open(method, url); - if (isEmpty(req.responseType)) { - req.responseType = 'text'; - } + if (isEmpty(req.responseType)) { + req.responseType = 'text'; + } - if (body) { - req.send(body); - } else { - req.send(); - } - }); - }, - bscStr(str = '', lowerCase = true) { - const txt = str[lowerCase ? 'toLowerCase' : 'toUpperCase'](); - return txt.replaceAll(/\W/g, ''); - } -}; -const sleazyRedirect = () => { - const locObj = window.top.location; - const { hostname } = locObj; - const gfSite = /greasyfork\.org/.test(hostname); - if (!gfSite && cfg.sleazyredirect) { - return; - } - const otherSite = gfSite ? 'sleazyfork' : 'greasyfork'; - if (!qs('span.sign-in-link')) { - return; - } - if (!/scripts\/\d+/.test(locObj.href)) { - return; - } - if ( - !qs('#script-info') && - (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a')) - ) { - const str = locObj.href.replace( - /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/, - '//$1' + otherSite + '.org' - ); - info(`Redirecting to "${str}"`); - if (isFN(locObj.assign)) { - locObj.assign(str); - } else { - locObj.href = str; - } - } -}; -// #region Container -/** + if (body) { + req.send(body); + } else { + req.send(); + } + }); + }, + bscStr(str = '', lowerCase = true) { + const txt = str[lowerCase ? 'toLowerCase' : 'toUpperCase'](); + return txt.replaceAll(/\W/g, ''); + } + }; + const sleazyRedirect = () => { + const locObj = window.top.location; + const { hostname } = locObj; + const gfSite = /greasyfork\.org/.test(hostname); + if (!gfSite && cfg.sleazyredirect) { + return; + } + const otherSite = gfSite ? 'sleazyfork' : 'greasyfork'; + if (!qs('span.sign-in-link')) { + return; + } + if (!/scripts\/\d+/.test(locObj.href)) { + return; + } + if ( + !qs('#script-info') && + (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a')) + ) { + const str = locObj.href.replace( + /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/, + '//$1' + otherSite + '.org' + ); + info(`Redirecting to "${str}"`); + if (isFN(locObj.assign)) { + locObj.assign(str); + } else { + locObj.href = str; + } + } + }; + // #region Container + /** * @type { import("../typings/UserJS.d.ts").Container } */ -class Container { - webpage; - host; - domain; - ready; - injected; - shadowRoot; - supported; - frame; - cache; - userjsCache; - root; - unsaved; - isBlacklisted; - rebuild; - counters; - opacityMin; - opacityMax; - constructor(url) { - this.remove = this.remove.bind(this); - this.refresh = this.refresh.bind(this); - this.updateCounter = this.updateCounter.bind(this); - this.updateCounters = this.updateCounters.bind(this); - this.showError = this.showError.bind(this); + class Container { + webpage; + host; + domain; + ready; + injected; + shadowRoot; + supported; + frame; + cache; + userjsCache; + root; + unsaved; + isBlacklisted; + rebuild; + counters; + opacityMin; + opacityMax; + constructor(url) { + this.remove = this.remove.bind(this); + this.refresh = this.refresh.bind(this); + this.updateCounter = this.updateCounter.bind(this); + this.updateCounters = this.updateCounters.bind(this); + this.showError = this.showError.bind(this); - this.webpage = strToURL(url); - this.host = this.getHost(this.webpage.host); - this.domain = this.getDomain(this.webpage.host); - this.ready = false; - this.injected = false; - this.shadowRoot = undefined; - this.supported = isFN(make('main-userjs').attachShadow); - this.frame = this.supported - ? make('main-userjs', '', { - dataset: { - insertedBy: $info.script.name, - role: 'primary-container' - } - }) - : make('iframe', 'mujs-iframe', { - dataset: { - insertedBy: $info.script.name, - role: 'primary-iframe' - }, - loading: 'lazy', - src: BLANK_PAGE, - style: - 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', - onload: (iFrame) => { - /** + this.webpage = strToURL(url); + this.host = this.getHost(this.webpage.host); + this.domain = this.getDomain(this.webpage.host); + this.ready = false; + this.injected = false; + this.shadowRoot = undefined; + this.supported = isFN(make('main-userjs').attachShadow); + this.frame = this.supported + ? make('main-userjs', '', { + dataset: { + insertedBy: $info.script.name, + role: 'primary-container' + } + }) + : make('iframe', 'mujs-iframe', { + dataset: { + insertedBy: $info.script.name, + role: 'primary-iframe' + }, + loading: 'lazy', + src: BLANK_PAGE, + style: + 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', + onload: (iFrame) => { + /** * @type { HTMLIFrameElement } */ - const target = iFrame.target; - if (!target.contentDocument) { - return; + const target = iFrame.target; + if (!target.contentDocument) { + return; + } + this.shadowRoot = target.contentDocument.documentElement; + this.ready = true; + dom.cl.add([this.shadowRoot, target.contentDocument.body], 'mujs-iframe'); + } + }); + if (this.supported) { + this.shadowRoot = this.frame.attachShadow({ + mode: 'open', + clonable: false, + delegatesfocus: false + }); + this.ready = true; } - this.shadowRoot = target.contentDocument.documentElement; - this.ready = true; - dom.cl.add([this.shadowRoot, target.contentDocument.body], 'mujs-iframe'); - } - }); - if (this.supported) { - this.shadowRoot = this.frame.attachShadow({ - mode: 'open', - clonable: false, - delegatesfocus: false - }); - this.ready = true; - } - this.cache = memory.maps.container; - this.userjsCache = memory.maps.userjs; - this.root = make('mujs-root'); - this.unsaved = false; - this.isBlacklisted = false; - this.rebuild = false; - this.counters = { - total: 0 - }; - this.opacityMin = '0.15'; - this.opacityMax = '1'; - this.elementsReady = this.init(); + this.cache = memory.maps.container; + this.userjsCache = memory.maps.userjs; + this.root = make('mujs-root'); + this.unsaved = false; + this.isBlacklisted = false; + this.rebuild = false; + this.counters = { + total: 0 + }; + this.opacityMin = '0.15'; + this.opacityMax = '1'; + this.elementsReady = this.init(); - const Timeout = class { - constructor() { - this.ids = []; - } + const Timeout = class { + constructor() { + this.ids = []; + } - set(delay, reason) { - return new Promise((resolve, reject) => { - const id = setTimeout(() => { - Object.is(reason, null) || Object.is(reason, undefined) ? resolve() : reject(reason); - this.clear(id); - }, delay); - this.ids.push(id); - }); - } + set(delay, reason) { + return new Promise((resolve, reject) => { + const id = setTimeout(() => { + Object.is(reason, null) || Object.is(reason, undefined) ? resolve() : reject(reason); + this.clear(id); + }, delay); + this.ids.push(id); + }); + } - clear(...ids) { - this.ids = this.ids.filter((id) => { - if (ids.includes(id)) { - clearTimeout(id); - return false; - } - return true; - }); - } - }; - this.timeouts = { - frame: new Timeout(), - mouse: new Timeout() - }; + clear(...ids) { + this.ids = this.ids.filter((id) => { + if (ids.includes(id)) { + clearTimeout(id); + return false; + } + return true; + }); + } + }; + this.timeouts = { + frame: new Timeout(), + mouse: new Timeout() + }; - this.injFN = () => {}; + this.injFN = () => {}; - window.addEventListener('beforeunload', this.remove); - } - /** + window.addEventListener('beforeunload', this.remove); + } + /** * @param { function(): * } callback * @param { Document } doc */ - async inject(callback, doc) { - if (this.checkBlacklist(this.host)) { - err(`Blacklisted "${this.host}"`); - this.remove(); - return; - } - if (!this.shadowRoot) { - return; - } - if (doc === null) { - return; - } + async inject(callback, doc) { + if (this.checkBlacklist(this.host)) { + err(`Blacklisted "${this.host}"`); + this.remove(); + return; + } + if (!this.shadowRoot) { + return; + } + if (doc === null) { + return; + } - while (this.ready === false) { - await new Promise((resolve) => requestAnimationFrame(resolve)); - } - try { - doc.documentElement.appendChild(this.frame); - if (this.injected) { - if (isFN(this.injFN.build)) { - this.injFN.build(); + while (this.ready === false) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + } + try { + doc.documentElement.appendChild(this.frame); + if (this.injected) { + if (isFN(this.injFN.build)) { + this.injFN.build(); + } + return; + } + this.shadowRoot.append(this.root); + if (isNull(this.loadCSS(main_css, 'primary-stylesheet'))) { + throw new Error('Failed to initialize script!', { cause: 'loadCSS' }); + } + this.injected = true; + this.initFn(); + if (this.elementsReady && isFN(callback)) { + this.injFN = callback.call(this, this.shadowRoot); + } + } catch (ex) { + err(ex); + this.remove(); + } } - return; - } - this.shadowRoot.append(this.root); - if (isNull(this.loadCSS(main_css, 'primary-stylesheet'))) { - throw new Error('Failed to initialize script!', { cause: 'loadCSS' }); - } - this.injected = true; - this.initFn(); - if (this.elementsReady && isFN(callback)) { - this.injFN = callback.call(this, this.shadowRoot); - } - } catch (ex) { - err(ex); - this.remove(); - } - } - initFn() { - this.renderTheme(cfg.theme); + initFn() { + this.renderTheme(cfg.theme); - for (const engine of cfg.engines) { - if (engine.enabled) { - const counter = make('count-frame', '', { - dataset: { - counter: engine.name - }, - title: engine.query ? decodeURIComponent(engine.query) : engine.url, - textContent: '0' - }); - this.countframe.append(counter); - } - this.counters[engine.name] = 0; - } + for (const engine of cfg.engines) { + if (engine.enabled) { + const counter = make('count-frame', '', { + dataset: { + counter: engine.name + }, + title: engine.query ? decodeURIComponent(engine.query) : engine.url, + textContent: '0' + }); + this.countframe.append(counter); + } + this.counters[engine.name] = 0; + } - const cfgpage = this.cfgpage; - const table = this.table; - const exBtn = this.exBtn; - const supported = this.supported; - const frame = this.frame; - const refresh = this.refresh; - const cache = this.cache; - const cHost = this.host; - const urlBar = this.urlBar; + const cfgpage = this.cfgpage; + const table = this.table; + const exBtn = this.exBtn; + const supported = this.supported; + const frame = this.frame; + const refresh = this.refresh; + const cache = this.cache; + const cHost = this.host; + const urlBar = this.urlBar; - class Tabs { - /** + class Tabs { + /** * @param { HTMLElement } root */ - constructor(root) { - this.Tab = new Map(); - this.blank = BLANK_PAGE; - this.protocal = 'mujs:'; - this.protoReg = new RegExp(`${this.protocal}(.+)`); - this.el = { - add: make('mujs-addtab', '', { - textContent: '+', - dataset: { - command: 'new-tab' + constructor(root) { + this.Tab = new Map(); + this.blank = BLANK_PAGE; + this.protocal = 'mujs:'; + this.protoReg = new RegExp(`${this.protocal}(.+)`); + this.el = { + add: make('mujs-addtab', '', { + textContent: '+', + dataset: { + command: 'new-tab' + } + }), + head: make('mujs-tabs'), + root + }; + this.el.head.append(this.el.add); + this.el.root.append(this.el.head); + this.custom = () => {}; + } + hasTab(...params) { + for (const p of params) { + if (!this.Tab.has(p)) { + return false; + } + const content = normalizeTarget(this.Tab.get(p)).filter((t) => p === t.dataset.host); + if (isBlank(content)) { + return false; + } + } + return true; + } + storeTab(host) { + const h = host ?? this.blank; + if (!this.Tab.has(h)) { + this.Tab.set(h, new Set()); + } + return this.Tab.get(h); + } + cache(host, ...tabs) { + const h = host ?? this.blank; + const tabCache = this.storeTab(h); + for (const t of normalizeTarget(tabs)) { + if (tabCache.has(t)) { + continue; + } + tabCache.add(t); + } + this.Tab.set(h, tabCache); + return tabCache; + } + intFN(host) { + if (!host.startsWith(this.protocal)) { + return; + } + const type = host.match(this.protoReg)[1]; + if (type === 'settings') { + dom.cl.remove([cfgpage, exBtn], 'hidden'); + dom.cl.add(table, 'hidden'); + if (!supported) { + dom.attr(frame, 'style', 'height: 100%;'); + } + } + } + active(tab, build = true) { + for (const t of normalizeTarget(tab, false)) { + dom.cl.add([cfgpage, exBtn], 'hidden'); + dom.cl.remove(table, 'hidden'); + dom.cl.remove(qsA('mujs-tab', this.el.head), 'active'); + dom.cl.add(t, 'active'); + if (!build) { + continue; + } + const host = t.dataset.host ?? this.blank; + if (host === this.blank) { + refresh(); + } else if (host.startsWith(this.protocal)) { + this.intFN(host); + } else { + this.custom(host); + } + } + } + /** @param { HTMLElement } tab */ + close(tab) { + for (const t of normalizeTarget(tab, false)) { + const host = t.dataset.host; + if (cache.has(host)) { + cache.delete(host); + } + if (dom.cl.has(t, 'active')) { + refresh(); + } + const sibling = t.previousElementSibling ?? t.nextElementSibling; + if (sibling) { + if (sibling.dataset.command !== 'new-tab') { + this.active(sibling); + } + } + if (this.Tab.has(host)) { + this.Tab.delete(host); + } + t.remove(); + } + } + create(host = undefined) { + if (typeof host === 'string') { + if (host.startsWith(this.protocal) && this.hasTab(host)) { + this.active(this.Tab.get(host)); + return; + } + const content = normalizeTarget(this.storeTab(host)).filter( + (t) => host === t.dataset.host + ); + if (!isEmpty(content)) { + return; + } + } + const tab = make('mujs-tab', '', { + dataset: { + command: 'switch-tab' + }, + style: `order: ${this.el.head.childElementCount};` + }); + const tabClose = make('mu-js', '', { + dataset: { + command: 'close-tab' + }, + title: i18n$('close'), + textContent: 'X' + }); + const tabHost = make('mujs-host'); + tab.append(tabHost, tabClose); + this.el.head.append(tab); + this.active(tab, false); + this.cache(host, tab); + if (isNull(host)) { + refresh(); + urlBar.placeholder = i18n$('newTab'); + tab.dataset.host = this.blank; + tabHost.title = i18n$('newTab'); + tabHost.textContent = i18n$('newTab'); + } else if (host.startsWith(this.protocal)) { + const type = host.match(this.protoReg)[1]; + tab.dataset.host = host || cHost; + tabHost.title = type || tab.dataset.host; + tabHost.textContent = tabHost.title; + this.intFN(host); + } else { + tab.dataset.host = host || cHost; + tabHost.title = host || cHost; + tabHost.textContent = tabHost.title; + } + return tab; + } } - }), - head: make('mujs-tabs'), - root - }; - this.el.head.append(this.el.add); - this.el.root.append(this.el.head); - this.custom = () => {}; - } - hasTab(...params) { - for (const p of params) { - if (!this.Tab.has(p)) { - return false; - } - const content = normalizeTarget(this.Tab.get(p)).filter((t) => p === t.dataset.host); - if (isBlank(content)) { - return false; - } - } - return true; - } - storeTab(host) { - const h = host ?? this.blank; - if (!this.Tab.has(h)) { - this.Tab.set(h, new Set()); - } - return this.Tab.get(h); - } - cache(host, ...tabs) { - const h = host ?? this.blank; - const tabCache = this.storeTab(h); - for (const t of normalizeTarget(tabs)) { - if (tabCache.has(t)) { - continue; - } - tabCache.add(t); - } - this.Tab.set(h, tabCache); - return tabCache; - } - intFN(host) { - if (!host.startsWith(this.protocal)) { - return; - } - const type = host.match(this.protoReg)[1]; - if (type === 'settings') { - dom.cl.remove([cfgpage, exBtn], 'hidden'); - dom.cl.add(table, 'hidden'); - if (!supported) { - dom.attr(frame, 'style', 'height: 100%;'); - } - } - } - active(tab, build = true) { - for (const t of normalizeTarget(tab, false)) { - dom.cl.add([cfgpage, exBtn], 'hidden'); - dom.cl.remove(table, 'hidden'); - dom.cl.remove(qsA('mujs-tab', this.el.head), 'active'); - dom.cl.add(t, 'active'); - if (!build) { - continue; - } - const host = t.dataset.host ?? this.blank; - if (host === this.blank) { - refresh(); - } else if (host.startsWith(this.protocal)) { - this.intFN(host); - } else { - this.custom(host); - } - } - } - /** @param { HTMLElement } tab */ - close(tab) { - for (const t of normalizeTarget(tab, false)) { - const host = t.dataset.host; - if (cache.has(host)) { - cache.delete(host); - } - if (dom.cl.has(t, 'active')) { - refresh(); - } - const sibling = t.previousElementSibling ?? t.nextElementSibling; - if (sibling) { - if (sibling.dataset.command !== 'new-tab') { - this.active(sibling); + this.tab = new Tabs(this.toolbar); + this.tab.create(this.host); + + const tabbody = this.tabbody; + const getCellValue = (tr, idx) => {const cell = tr.children[idx];return cell.dataset.sortValue || cell.innerText || cell.textContent;}; + const comparer = (idx, asc) => (a, b) => + ((v1, v2) => + v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) + ? v1 - v2 + : v1.toString().localeCompare(v2))( + getCellValue(asc ? a : b, idx), + getCellValue(asc ? b : a, idx) + ); + for (const th of this.tabhead.rows[0].cells) { + if (dom.text(th) === i18n$('install')) continue; + dom.cl.add(th, 'mujs-pointer'); + ael(th, 'click', () => { + /** [Stack Overflow Reference](https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript/53880407#53880407) */ + Array.from(tabbody.querySelectorAll('tr')) + .sort(comparer(Array.from(th.parentNode.children).indexOf(th), (this.asc = !this.asc))) + .forEach((tr) => tabbody.appendChild(tr)); + }); } - } - if (this.Tab.has(host)) { - this.Tab.delete(host); - } - t.remove(); } - } - create(host = undefined) { - if (typeof host === 'string') { - if (host.startsWith(this.protocal) && this.hasTab(host)) { - this.active(this.Tab.get(host)); - return; - } - const content = normalizeTarget(this.storeTab(host)).filter( - (t) => host === t.dataset.host - ); - if (!isEmpty(content)) { - return; - } - } - const tab = make('mujs-tab', '', { - dataset: { - command: 'switch-tab' - }, - style: `order: ${this.el.head.childElementCount};` + init() { + try { + // #region Elements + this.mainframe = make('mu-js', 'mainframe', { + style: `opacity: ${this.opacityMin};` + }); + this.countframe = make('mujs-column'); + this.mainbtn = make('count-frame', 'mainbtn', { + textContent: '0' }); - const tabClose = make('mu-js', '', { - dataset: { - command: 'close-tab' - }, - title: i18n$('close'), - textContent: 'X' + this.urlBar = make('input', 'mujs-url-bar', { + autocomplete: 'off', + spellcheck: false, + type: 'text', + placeholder: i18n$('search_placeholder') }); - const tabHost = make('mujs-host'); - tab.append(tabHost, tabClose); - this.el.head.append(tab); - this.active(tab, false); - this.cache(host, tab); - if (isNull(host)) { - refresh(); - urlBar.placeholder = i18n$('newTab'); - tab.dataset.host = this.blank; - tabHost.title = i18n$('newTab'); - tabHost.textContent = i18n$('newTab'); - } else if (host.startsWith(this.protocal)) { - const type = host.match(this.protoReg)[1]; - tab.dataset.host = host || cHost; - tabHost.title = type || tab.dataset.host; - tabHost.textContent = tabHost.title; - this.intFN(host); - } else { - tab.dataset.host = host || cHost; - tabHost.title = host || cHost; - tabHost.textContent = tabHost.title; - } - return tab; - } - } - this.tab = new Tabs(this.toolbar); - this.tab.create(this.host); - - const tabbody = this.tabbody; - const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; - const comparer = (idx, asc) => (a, b) => - ((v1, v2) => - v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) - ? v1 - v2 - : v1.toString().localeCompare(v2))( - getCellValue(asc ? a : b, idx), - getCellValue(asc ? b : a, idx) - ); - for (const th of this.tabhead.rows[0].cells) { - if (dom.text(th) === i18n$('install')) continue; - dom.cl.add(th, 'mujs-pointer'); - ael(th, 'click', () => { - /** [Stack Overflow Reference](https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript/53880407#53880407) */ - Array.from(tabbody.querySelectorAll('tr')) - .sort(comparer(Array.from(th.parentNode.children).indexOf(th), (this.asc = !this.asc))) - .forEach((tr) => tabbody.appendChild(tr)); - }); - } - } - init() { - try { - // #region Elements - this.mainframe = make('mu-js', 'mainframe', { - style: `opacity: ${this.opacityMin};` - }); - this.countframe = make('mujs-column'); - this.mainbtn = make('count-frame', 'mainbtn', { - textContent: '0' - }); - this.urlBar = make('input', 'mujs-url-bar', { - autocomplete: 'off', - spellcheck: false, - type: 'text', - placeholder: i18n$('search_placeholder') - }); - this.rateContainer = make('mujs-column', 'rate-container'); - this.footer = make('mujs-row', 'mujs-footer'); - this.tabbody = make('tbody'); - this.promptElem = make('mujs-row', 'mujs-prompt'); - this.toolbar = make('mujs-toolbar'); - this.table = make('table'); - this.tabhead = make('thead'); - this.header = make('mujs-header'); - this.tbody = make('mujs-body'); - this.cfgpage = make('mujs-row', 'mujs-cfg hidden'); - this.btnframe = make('mujs-column', 'btn-frame'); - this.exBtn = make('mujs-column', 'mujs-sty-flex hidden'); - this.fsearch = make('mujs-btn', 'hidden'); - this.exportCFG = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_config'), - dataset: { - command: 'export-cfg' - } - }); - this.importCFG = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_config'), - dataset: { - command: 'import-cfg' - } - }); - this.exportTheme = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_theme'), - dataset: { - command: 'export-theme' - } - }); - this.importTheme = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_theme'), - dataset: { - command: 'import-theme' - } - }); - this.btnHandles = make('mujs-column', 'btn-handles'); - this.btnHide = make('mujs-btn', 'hide-list', { - title: i18n$('min'), - innerHTML: iconSVG.load('hide'), - dataset: { - command: 'hide-list' - } - }); - this.btnfullscreen = make('mujs-btn', 'fullscreen', { - title: i18n$('max'), - innerHTML: iconSVG.load('fullscreen'), - dataset: { - command: 'fullscreen' - } - }); - this.main = make('mujs-main', 'hidden'); - this.urlContainer = make('mujs-url'); - this.closebtn = make('mujs-btn', 'close', { - title: i18n$('close'), - innerHTML: iconSVG.load('close'), - dataset: { - command: 'close' - } - }); - this.btncfg = make('mujs-btn', 'settings hidden', { - title: 'Settings', - innerHTML: iconSVG.load('cfg'), - dataset: { - command: 'settings' - } - }); - this.btnhome = make('mujs-btn', 'github hidden', { - title: `GitHub (v${ + this.rateContainer = make('mujs-column', 'rate-container'); + this.footer = make('mujs-row', 'mujs-footer'); + this.tabbody = make('tbody'); + this.promptElem = make('mujs-row', 'mujs-prompt'); + this.toolbar = make('mujs-toolbar'); + this.table = make('table'); + this.tabhead = make('thead'); + this.header = make('mujs-header'); + this.tbody = make('mujs-body'); + this.cfgpage = make('mujs-row', 'mujs-cfg hidden'); + this.btnframe = make('mujs-column', 'btn-frame'); + this.exBtn = make('mujs-column', 'mujs-sty-flex hidden'); + this.fsearch = make('mujs-btn', 'hidden'); + this.exportCFG = make('mujs-btn', 'mujs-export', { + textContent: i18n$('export_config'), + dataset: { + command: 'export-cfg' + } + }); + this.importCFG = make('mujs-btn', 'mujs-import', { + textContent: i18n$('import_config'), + dataset: { + command: 'import-cfg' + } + }); + this.exportTheme = make('mujs-btn', 'mujs-export', { + textContent: i18n$('export_theme'), + dataset: { + command: 'export-theme' + } + }); + this.importTheme = make('mujs-btn', 'mujs-import', { + textContent: i18n$('import_theme'), + dataset: { + command: 'import-theme' + } + }); + this.btnHandles = make('mujs-column', 'btn-handles'); + this.btnHide = make('mujs-btn', 'hide-list', { + title: i18n$('min'), + innerHTML: iconSVG.load('hide'), + dataset: { + command: 'hide-list' + } + }); + this.btnfullscreen = make('mujs-btn', 'fullscreen', { + title: i18n$('max'), + innerHTML: iconSVG.load('fullscreen'), + dataset: { + command: 'fullscreen' + } + }); + this.main = make('mujs-main', 'hidden'); + this.urlContainer = make('mujs-url'); + this.closebtn = make('mujs-btn', 'close', { + title: i18n$('close'), + innerHTML: iconSVG.load('close'), + dataset: { + command: 'close' + } + }); + this.btncfg = make('mujs-btn', 'settings hidden', { + title: 'Settings', + innerHTML: iconSVG.load('cfg'), + dataset: { + command: 'settings' + } + }); + this.btnhome = make('mujs-btn', 'github hidden', { + title: `GitHub (v${ $info.script.version.includes('.') || $info.script.version.includes('Book') - ? $info.script.version - : $info.script.version.slice(0, 5) - })`, - innerHTML: iconSVG.load('gh'), - dataset: { - command: 'open-tab', - webpage: $info.script.namespace - } - }); - this.btnissue = make('mujs-btn', 'issue hidden', { - innerHTML: iconSVG.load('issue'), - title: i18n$('issue'), - dataset: { - command: 'open-tab', - webpage: $info.script.bugs ?? 'https://github.com/magicoflolis/Userscript-Plus/issues' - } - }); - this.btngreasy = make('mujs-btn', 'greasy hidden', { - title: 'Greasy Fork', - innerHTML: iconSVG.load('gf'), - dataset: { - command: 'open-tab', - webpage: 'https://greasyfork.org/scripts/421603' - } - }); - this.btnnav = make('mujs-btn', 'nav', { - title: 'Navigation', - innerHTML: iconSVG.load('nav'), - dataset: { - command: 'navigation' - } + ? $info.script.version + : $info.script.version.slice(0, 5) + })`, + innerHTML: iconSVG.load('gh'), + dataset: { + command: 'open-tab', + webpage: $info.script.namespace + } }); - const makeTHead = (rows) => { - const tr = make('tr'); - for (const r of normalizeTarget(rows)) { - const tparent = make('th', r.class ?? '', r); - tr.append(tparent); - } - this.tabhead.append(tr); - this.table.append(this.tabhead, this.tabbody); - }; - makeTHead([ - { - class: 'mujs-header-name', - textContent: i18n$('name') - }, - { - textContent: i18n$('createdby') - }, - { - textContent: i18n$('daily_installs') - }, - { - textContent: i18n$('updated') - }, - { - textContent: i18n$('install') - } - ]); - // #endregion + this.btnissue = make('mujs-btn', 'issue hidden', { + innerHTML: iconSVG.load('issue'), + title: i18n$('issue'), + dataset: { + command: 'open-tab', + webpage: $info.script.bugs ?? 'https://github.com/magicoflolis/Userscript-Plus/issues' + } + }); + this.btngreasy = make('mujs-btn', 'greasy hidden', { + title: 'Greasy Fork', + innerHTML: iconSVG.load('gf'), + dataset: { + command: 'open-tab', + webpage: 'https://greasyfork.org/scripts/421603' + } + }); + this.btnnav = make('mujs-btn', 'nav', { + title: 'Navigation', + innerHTML: iconSVG.load('nav'), + dataset: { + command: 'navigation' + } + }); + const makeTHead = (rows) => { + const tr = make('tr'); + for (const r of normalizeTarget(rows)) { + const tparent = make('th', r.class ?? '', r); + tr.append(tparent); + } + this.tabhead.append(tr); + this.table.append(this.tabhead, this.tabbody); + }; + makeTHead([ + { + class: 'mujs-header-name', + textContent: i18n$('name') + }, + { + textContent: i18n$('createdby') + }, + { + textContent: i18n$('daily_installs') + }, + { + textContent: i18n$('updated') + }, + { + textContent: i18n$('install') + } + ]); + // #endregion - this.btnHandles.append(this.btnHide, this.btnfullscreen, this.closebtn); - this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav); - this.toolbar.append(this.btnHandles); - this.urlContainer.append(this.urlBar); - this.header.append(this.urlContainer, this.rateContainer, this.countframe, this.btnframe); - this.tbody.append(this.table, this.cfgpage); - this.main.append(this.toolbar, this.header, this.tbody, this.footer, this.promptElem); - this.mainframe.append(this.mainbtn); - this.exBtn.append(this.importCFG, this.importTheme, this.exportCFG, this.exportTheme); - this.header.append(this.exBtn); - this.root.append(this.mainframe, this.main); + this.btnHandles.append(this.btnHide, this.btnfullscreen, this.closebtn); + this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav); + this.toolbar.append(this.btnHandles); + this.urlContainer.append(this.urlBar); + this.header.append(this.urlContainer, this.rateContainer, this.countframe, this.btnframe); + this.tbody.append(this.table, this.cfgpage); + this.main.append(this.toolbar, this.header, this.tbody, this.footer, this.promptElem); + this.mainframe.append(this.mainbtn); + this.exBtn.append(this.importCFG, this.importTheme, this.exportCFG, this.exportTheme); + this.header.append(this.exBtn); + this.root.append(this.mainframe, this.main); - return true; + return true; } catch (ex) { - err(ex); + err(ex); } - return false; + return false; } - remove() { - memory.store.clear(); - if (this.frame) { - this.frame.remove(); + remove() { + memory.store.clear(); + if (this.frame) { + this.frame.remove(); + } } - } - async save() { - this.unsaved = false; - await StorageSystem.setValue('Config', cfg); - info('Saved config:', cfg); - return cfg; - } - /** + async save() { + this.unsaved = false; + await StorageSystem.setValue('Config', cfg); + info('Saved config:', cfg); + return cfg; + } + /** * @param { string } css - CSS to inject * @param { string } name - Name of stylesheet * @return { HTMLStyleElement } Style element */ - loadCSS(css, name = 'CSS') { - try { - if (typeof name !== 'string') { - throw new Error('"name" must be a typeof "string"', { cause: 'loadCSS' }); - } - if (qs(`style[data-role="${name}"]`, this.root)) { - return qs(`style[data-role="${name}"]`, this.root); - } - if (typeof css !== 'string') { - throw new Error('"css" must be a typeof "string"', { cause: 'loadCSS' }); - } - if (isBlank(css)) { - throw new Error(`"${name}" contains empty CSS string`, { cause: 'loadCSS' }); - } - const parent = isEmpty(this.root.shadowRoot) ? this.root : this.root.shadowRoot; - if (isGM) { - let sty; - if (isFN(GM.addElement)) { - sty = GM.addElement(parent, 'style', { - textContent: css - }); - } else if (isFN(GM_addElement)) { - sty = GM_addElement(parent, 'style', { - textContent: css - }); - } - if (isElem(sty)) { - sty.dataset.insertedBy = $info.script.name; - sty.dataset.role = name; - return sty; - } - } - const sty = make('style', '', { - textContent: css, - dataset: { - insertedBy: $info.script.name, - role: name + loadCSS(css, name = 'CSS') { + try { + if (typeof name !== 'string') { + throw new Error('"name" must be a typeof "string"', { cause: 'loadCSS' }); + } + if (qs(`style[data-role="${name}"]`, this.root)) { + return qs(`style[data-role="${name}"]`, this.root); + } + if (typeof css !== 'string') { + throw new Error('"css" must be a typeof "string"', { cause: 'loadCSS' }); + } + if (isBlank(css)) { + throw new Error(`"${name}" contains empty CSS string`, { cause: 'loadCSS' }); + } + const parent = isEmpty(this.root.shadowRoot) ? this.root : this.root.shadowRoot; + if (isGM) { + let sty; + if (isFN(GM.addElement)) { + sty = GM.addElement(parent, 'style', { + textContent: css + }); + } else if (isFN(GM_addElement)) { + sty = GM_addElement(parent, 'style', { + textContent: css + }); + } + if (isElem(sty)) { + sty.dataset.insertedBy = $info.script.name; + sty.dataset.role = name; + return sty; + } + } + const sty = make('style', '', { + textContent: css, + dataset: { + insertedBy: $info.script.name, + role: name + } + }); + parent.appendChild(sty); + return sty; + } catch (ex) { + err(ex); } - }); - parent.appendChild(sty); - return sty; - } catch (ex) { - err(ex); } - } - checkBlacklist(str) { - str = str || this.host; - let blacklisted = false; - if (/accounts*\.google\./.test(this.webpage.host)) { - blacklisted = true; - } - for (const b of normalizeTarget(cfg.blacklist)) { - if (typeof b === 'string') { - if (b.startsWith('userjs-')) { - const r = /userjs-(\w+)/.exec(b)[1]; - const biList = builtinList[r]; - if (isRegExp(biList)) { - if (!biList.test(str)) continue; - blacklisted = true; - } else if (isObj(biList) && biList.host === this.host) { + checkBlacklist(str) { + str = str || this.host; + let blacklisted = false; + if (/accounts*\.google\./.test(this.webpage.host)) { blacklisted = true; - } } - } else if (isObj(b)) { - if (!b.enabled) { - continue; - } - if (b.regex === true) { - const reg = new RegExp(b.url, b.flags); - if (!reg.test(str)) continue; - blacklisted = true; - } - if (Array.isArray(b.url)) { - for (const c of b.url) { - if (!str.includes(c)) continue; - blacklisted = true; - } + for (const b of normalizeTarget(cfg.blacklist)) { + if (typeof b === 'string') { + if (b.startsWith('userjs-')) { + const r = /userjs-(\w+)/.exec(b)[1]; + const biList = builtinList[r]; + if (isRegExp(biList)) { + if (!biList.test(str)) continue; + blacklisted = true; + } else if (isObj(biList) && biList.host === this.host) { + blacklisted = true; + } + } + } else if (isObj(b)) { + if (!b.enabled) { + continue; + } + if (b.regex === true) { + const reg = new RegExp(b.url, b.flags); + if (!reg.test(str)) continue; + blacklisted = true; + } + if (Array.isArray(b.url)) { + for (const c of b.url) { + if (!str.includes(c)) continue; + blacklisted = true; + } + } + if (!str.includes(b.url)) continue; + blacklisted = true; + } } - if (!str.includes(b.url)) continue; - blacklisted = true; - } + this.isBlacklisted = blacklisted; + return this.isBlacklisted; + } + getInfo(url) { + const webpage = strToURL(url || this.webpage); + const host = this.getHost(webpage.host); + const domain = this.getDomain(webpage.host); + return { + domain, + host, + webpage + }; } - this.isBlacklisted = blacklisted; - return this.isBlacklisted; - } - getInfo(url) { - const webpage = strToURL(url || this.webpage); - const host = this.getHost(webpage.host); - const domain = this.getDomain(webpage.host); - return { - domain, - host, - webpage - }; - } - /** + /** * @template { string } S * @param { S } str */ - getHost(str = '') { - return str.split('.').splice(-2).join('.'); - } - /** + getHost(str = '') { + return str.split('.').splice(-2).join('.'); + } + /** * @template { string } S * @param { S } str */ - getDomain(str = '') { - return str.split('.').at(-2) ?? BLANK_PAGE; - } - renderTheme(theme) { - theme = theme || cfg.theme; - if (theme === DEFAULT_CONFIG.theme) { - return; - } - const sty = this.root.style; - for (const [k, v] of Object.entries(theme)) { - const str = `--mujs-${k}`; - const prop = sty.getPropertyValue(str); - if (isEmpty(v)) { - theme[k] = prop; - } - if (prop === v) { - continue; - } - sty.removeProperty(str); - sty.setProperty(str, v); - } - } - makePrompt(txt, dataset = {}, usePrompt = true) { - if (qs('.prompt', this.promptElem)) { - for (const elem of qsA('.prompt', this.promptElem)) { - if (elem.dataset.prompt) { - elem.remove(); - } - } + getDomain(str = '') { + return str.split('.').at(-2) ?? BLANK_PAGE; } - const el = make('mu-js', 'prompt', { - dataset: { - prompt: txt - } - }); - const elHead = make('mu-js', 'prompt-head', { - innerHTML: `${iconSVG.load('refresh')} ${txt}` - }); - el.append(elHead); - if (usePrompt) { - const elPrompt = make('mu-js', 'prompt-body', { dataset }); - const elYes = make('mujs-btn', 'prompt-confirm', { - innerHTML: 'Confirm', - dataset: { - command: 'prompt-confirm' - } - }); - const elNo = make('mujs-btn', 'prompt-deny', { - innerHTML: 'Deny', - dataset: { - command: 'prompt-deny' + renderTheme(theme) { + theme = theme || cfg.theme; + if (theme === DEFAULT_CONFIG.theme) { + return; } - }); - elPrompt.append(elYes, elNo); - el.append(elPrompt); - } - this.promptElem.append(el); - } - showError(...ex) { - err(...ex); - const error = make('mu-js', 'error'); - let str = ''; - for (const e of ex) { - str += `${typeof e === 'string' ? e : `${e.cause ? `[${e.cause}] ` : ''}${e.message}${e.stack ? ` ${e.stack}` : ''}`}\n`; - if (isObj(e)) { - if (e.notify) { - dom.cl.add(this.mainframe, 'error'); + const sty = this.root.style; + for (const [k, v] of Object.entries(theme)) { + const str = `--mujs-${k}`; + const prop = sty.getPropertyValue(str); + if (isEmpty(v)) { + theme[k] = prop; + } + if (prop === v) { + continue; + } + sty.removeProperty(str); + sty.setProperty(str, v); } - } - } - const { createTextNode } = safeSelf(); - error.appendChild(createTextNode(str)); - this.footer.append(error); - } - updateCounter(count, engine) { - this.counters[engine.name] += count; - this.counters.total += count; - this.updateCounters(); - } - updateCounters() { - for (const [k, v] of Object.entries(this.counters)) { - if (k === 'total') { - continue; - } - const c = qs(`count-frame[data-counter="${k}"]`, this.countframe); - if (c) { - dom.text(c, v); - } - } - dom.text(this.mainbtn, this.counters.total); - } - refresh() { - this.urlBar.placeholder = i18n$('newTab'); - this.counters.total = 0; - for (const engine of cfg.engines) { - this.counters[engine.name] = 0; } - this.updateCounters(); - dom.cl.remove(qsA('tr[data-engine]', this.tabbody), 'hidden'); - dom.cl.remove(qsA('mujs-section[data-name]', this.cfgpage), 'hidden'); - dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', ''); - } -} -const container = new Container(); -// #endregion -// #region Primary Function -function primaryFN() { - const respHandles = { - build: async () => {} - }; - try { - const { scheduler } = safeSelf(); - const { - mainframe, - urlBar, - rateContainer, - footer, - tabbody, - toolbar, - tabhead, - cfgpage, - fsearch, - btnfullscreen, - main, - btncfg, - btnhome, - btnissue, - btngreasy, - tab, - userjsCache, - updateCounter, - showError - } = container; - const frameTimeout = container.timeouts.frame; - const cfgMap = memory.maps.cfg; - const rebuildCfg = () => { - for (const engine of cfg.engines) { - if (cfgMap.has(engine.name)) { - const inp = cfgMap.get(engine.name); - inp.checked = engine.enabled; - if (engine.name === 'github') { - const txt = cfgMap.get('github-token'); - dom.prop(txt, 'value', engine.token); - } - } - } - for (const [k, v] of Object.entries(cfg)) { - if (typeof v === 'boolean') { - if (cfgMap.has(k)) { - const inp = cfgMap.get(k); - if (inp.type === 'checkbox') { - inp.checked = v; - } else { - dom.prop(inp, 'value', v); + makePrompt(txt, dataset = {}, usePrompt = true) { + if (qs('.prompt', this.promptElem)) { + for (const elem of qsA('.prompt', this.promptElem)) { + if (elem.dataset.prompt) { + elem.remove(); + } } - } - } - } - // dom.prop(cfgMap.get('blacklist'), 'value', JSON.stringify(cfg.blacklist, null, ' ')); - for (const [k, v] of Object.entries(cfg.theme)) { - dom.prop(cfgMap.get(k), 'value', v); - } - container.renderTheme(cfg.theme); - }; - const doInstallProcess = async (installLink) => { - const locObj = window.top.location; - if (isFN(locObj.assign)) { - locObj.assign(installLink.href); - } else { - locObj.href = installLink.href; - } - installLink.remove(); - await init(); - }; - // #region Main event handlers - ael(main, 'click', async (evt) => { - try { - /** @type { HTMLElement } */ - const target = evt.target.closest('[data-command]'); - if (!target) { - return; } - const prmpt = /prompt-/.test(target.dataset.command); - let dataset = target.dataset; - let cmd = dataset.command; - let prmptChoice = false; - if (prmpt) { - dataset = target.parentElement.dataset; - cmd = dataset.command; - prmptChoice = /confirm/.test(target.dataset.command); - target.parentElement.parentElement.remove(); - } - if (cmd === 'install-script' && dataset.userjs) { - let installCode = dataset.userjs; - if (!prmpt && dataset.userjs.endsWith('.user.css')) { - container.makePrompt(i18n$('prmpt_css'), dataset); - return; - } else if (prmpt !== prmptChoice) { - installCode = dataset.userjs.replace(/\.user\.css$/, '.user.js'); - } - const dlBtn = make('a', '', { - onclick(evt) { - evt.preventDefault(); - doInstallProcess(evt.target); + const el = make('mu-js', 'prompt', { + dataset: { + prompt: txt } + }); + const elHead = make('mu-js', 'prompt-head', { + innerHTML: `${iconSVG.load('refresh')} ${txt}` + }); + el.append(elHead); + if (usePrompt) { + const elPrompt = make('mu-js', 'prompt-body', { dataset }); + const elYes = make('mujs-btn', 'prompt-confirm', { + innerHTML: 'Confirm', + dataset: { + command: 'prompt-confirm' + } }); - dlBtn.href = installCode; - dlBtn.click(); - } else if (cmd === 'open-tab' && dataset.webpage) { - if (isGM) { - if (isFN(GM.openInTab)) { - return GM.openInTab(dataset.webpage); - } else if (isFN(GM_openInTab)) { - return GM_openInTab(dataset.webpage, { - active: true, - insert: true - }); - } - } - return window.open(dataset.webpage, '_blank'); - } else if (cmd === 'navigation') { - if (dom.cl.has(btngreasy, 'hidden')) { - dom.cl.remove([btncfg, btngreasy, btnhome, btnissue], 'hidden'); - } else { - dom.cl.add([btncfg, btngreasy, btnhome, btnissue], 'hidden'); - } - } else if (cmd === 'list-description') { - const arr = []; - const ignoreTags = new Set(['TD', 'MUJS-A', 'MU-JS']); - for (const node of target.parentElement.childNodes) { - if (ignoreTags.has(node.tagName)) { - continue; - } - if (node.tagName === 'TEXTAREA' && isEmpty(node.value)) { - continue; - } - arr.push(node); - } - if (target.nextElementSibling) { - arr.push(target.nextElementSibling); - if (target.nextElementSibling.nextElementSibling) { - arr.push(target.nextElementSibling.nextElementSibling); - } - } - if (dom.cl.has(arr[0], 'hidden')) { - dom.cl.remove(arr, 'hidden'); - } else { - dom.cl.add(arr, 'hidden'); - } - } else if (cmd === 'close') { - container.remove(); - } else if (cmd === 'show-filter') { - dom.cl.toggle(fsearch, 'hidden'); - } else if (cmd === 'fullscreen') { - if (dom.cl.has(btnfullscreen, 'expanded')) { - dom.cl.remove([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); - } else { - dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); - } - } else if (cmd === 'hide-list') { - dom.cl.add(main, 'hidden'); - dom.cl.remove(mainframe, 'hidden'); - timeoutFrame(); - } else if (cmd === 'save') { - container.rebuild = true; - dom.prop(rateContainer, 'innerHTML', ''); - if (!dom.prop(target, 'disabled')) { - const config = await container.save(); - sleazyRedirect(); - if (container.rebuild) { - container.cache.clear(); - if (config.autofetch) { - respHandles.build(); + const elNo = make('mujs-btn', 'prompt-deny', { + innerHTML: 'Deny', + dataset: { + command: 'prompt-deny' } + }); + elPrompt.append(elYes, elNo); + el.append(elPrompt); + } + this.promptElem.append(el); + } + showError(...ex) { + err(...ex); + const error = make('mu-js', 'error'); + let str = ''; + for (const e of ex) { + str += `${typeof e === 'string' ? e : `${e.cause ? `[${e.cause}] ` : ''}${e.message}${e.stack ? ` ${e.stack}` : ''}`}\n`; + if (isObj(e)) { + if (e.notify) { + dom.cl.add(this.mainframe, 'error'); + } } - container.unsaved = false; - container.rebuild = false; - } - } else if (cmd === 'reset') { - cfg = DEFAULT_CONFIG; - dom.cl.remove(mainframe, 'error'); - if (qs('.error', footer)) { - for (const elem of qsA('.error', footer)) { - elem.remove(); + } + const { createTextNode } = safeSelf(); + error.appendChild(createTextNode(str)); + this.footer.append(error); + } + updateCounter(count, engine) { + this.counters[engine.name] += count; + this.counters.total += count; + this.updateCounters(); + } + updateCounters() { + for (const [k, v] of Object.entries(this.counters)) { + if (k === 'total') { + continue; } - } - container.unsaved = true; - container.rebuild = true; - rebuildCfg(); - } else if (cmd === 'settings') { - if (container.unsaved) { - showError('Unsaved changes'); - } - tab.create('mujs:settings'); - container.rebuild = false; - } else if (cmd === 'new-tab') { - tab.create(); - } else if (cmd === 'switch-tab') { - tab.active(target); - } else if (cmd === 'close-tab' && target.parentElement) { - tab.close(target.parentElement); - } else if (cmd === 'download-userjs') { - if (!container.userjsCache.has(+dataset.userjs)) { - return; - } - const dataUserJS = container.userjsCache.get(+dataset.userjs); - let installCode = dataUserJS.code_url; - if (!prmpt && dataUserJS.code_url.endsWith('.user.css')) { - container.makePrompt('Download as UserStyle?', dataset); - return; - } else if (prmpt !== prmptChoice) { - installCode = dataUserJS.code_url.replace(/\.user\.css$/, '.user.js'); - } - const r = await dataUserJS._mujs.code.request(false, installCode); - const txt = r.data; - if (typeof txt !== 'string') { - return; - } - const userjsName = dataset.userjsName ?? dataset.userjs; - const userjsExt = prmpt !== prmptChoice ? '.user.js' : '.user.css'; - const makeUserJS = new Blob([txt], { type: 'text/plain' }); - const dlBtn = make('a', 'mujs_Downloader'); - dlBtn.href = URL.createObjectURL(makeUserJS); - dlBtn.download = `${userjsName}${userjsExt}`; - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - dlBtn.remove(); - } else if (cmd === 'load-userjs') { - if (!container.userjsCache.has(+dataset.userjs)) { - return; - } - const codeArea = qs('textarea', target.parentElement.parentElement); - if (!isEmpty(codeArea.value)) { - dom.cl.toggle(codeArea, 'hidden'); - return; - } - const dataUserJS = container.userjsCache.get(+dataset.userjs); - const r = await dataUserJS._mujs.code.request(); - const txt = r.data; - if (typeof txt !== 'string') { - return; - } - const code_obj = new ParseUserJS(txt); - codeArea.value = code_obj.get_code_block(); - dom.cl.remove(codeArea, 'hidden'); - const apTo = (name, elem, root) => { - const n = dataUserJS._mujs.code[name]; - if (isEmpty(n)) { - const el = make('mujs-a', '', { - textContent: i18n$('listing_none') - }); - elem.append(el); - return; + const c = qs(`count-frame[data-counter="${k}"]`, this.countframe); + if (c) { + dom.text(c, v); } - dom.prop(elem, 'innerHTML', ''); - dom.cl.remove(root, 'hidden'); - for (const c of n) { - if (typeof c === 'string' && c.startsWith('http')) { - const el = make('mujs-a', '', { - textContent: c, - dataset: { - command: 'open-tab', - webpage: c - } - }); - elem.append(el); - } else if (isObj(c)) { - if (name === 'resource') { - for (const [k, v] of Object.entries(c)) { - const el = make('mujs-a', '', { - textContent: k ?? 'ERROR' - }); - if (v.startsWith('http')) { - el.dataset.command = 'open-tab'; - el.dataset.webpage = v; + } + dom.text(this.mainbtn, this.counters.total); + } + refresh() { + this.urlBar.placeholder = i18n$('newTab'); + this.counters.total = 0; + for (const engine of cfg.engines) { + this.counters[engine.name] = 0; + } + this.updateCounters(); + dom.cl.remove(qsA('tr[data-engine]', this.tabbody), 'hidden'); + dom.cl.remove(qsA('mujs-section[data-name]', this.cfgpage), 'hidden'); + dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', ''); + } +} + const container = new Container(); + // #endregion + // #region Primary Function + function primaryFN() { + const respHandles = { + build: async () => {} + }; + try { + const { scheduler } = safeSelf(); + const { + mainframe, + urlBar, + rateContainer, + footer, + tabbody, + toolbar, + tabhead, + cfgpage, + fsearch, + btnfullscreen, + main, + btncfg, + btnhome, + btnissue, + btngreasy, + tab, + userjsCache, + updateCounter, + showError + } = container; + const frameTimeout = container.timeouts.frame; + const cfgMap = memory.maps.cfg; + const rebuildCfg = () => { + for (const engine of cfg.engines) { + if (cfgMap.has(engine.name)) { + const inp = cfgMap.get(engine.name); + inp.checked = engine.enabled; + if (engine.name === 'github') { + const txt = cfgMap.get('github-token'); + dom.prop(txt, 'value', engine.token); + } } - elem.append(el); - } + } + for (const [k, v] of Object.entries(cfg)) { + if (typeof v === 'boolean') { + if (cfgMap.has(k)) { + const inp = cfgMap.get(k); + if (inp.type === 'checkbox') { + inp.checked = v; + } else { + dom.prop(inp, 'value', v); + } + } + } + } + // dom.prop(cfgMap.get('blacklist'), 'value', JSON.stringify(cfg.blacklist, null, ' ')); + for (const [k, v] of Object.entries(cfg.theme)) { + dom.prop(cfgMap.get(k), 'value', v); + } + container.renderTheme(cfg.theme); + }; + const doInstallProcess = async (installLink) => { + const locObj = window.top.location; + if (isFN(locObj.assign)) { + locObj.assign(installLink.href); } else { - const el = make('mujs-a', '', { - textContent: c.text - }); - if (c.domain) { - el.dataset.command = 'open-tab'; - el.dataset.webpage = `https://${c.text}`; - } - elem.append(el); + locObj.href = installLink.href; } - } else { - const el = make('mujs-a', '', { - textContent: c - }); - elem.append(el); - } - } - }; - const m = qsA('mujs-column[data-el="matches"]', target.parentElement.parentElement); - for (const e of normalizeTarget(m)) { - apTo(e.dataset.type, qs('.mujs-grants', e), e); - } - } else if (/export-/.test(cmd)) { - const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' '); - const bytes = new TextEncoder().encode(str); - const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); - const dlBtn = make('a', 'mujs-exporter', { - href: URL.createObjectURL(blob), - download: `Magic_Userscript_${cmd === 'export-cfg' ? 'config' : 'theme'}.json` - }); - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - } else if (/import-/.test(cmd)) { - if (qs('input', target.parentElement)) { - qs('input', target.parentElement).click(); - return; - } - const inpJSON = make('input', 'hidden', { - type: 'file', - accept: '.json', - onchange(evt) { - try { - [...evt.target.files].forEach((file) => { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = () => { - const result = JSON.parse(reader.result); - if (result.blacklist) { - log(`Imported config: { ${file.name} }`, result); - cfg = result; - container.unsaved = true; - container.rebuild = true; - rebuildCfg(); - container.save().then((config) => { - sleazyRedirect(); - container.cache.clear(); - if (config.autofetch) { - respHandles.build(); + installLink.remove(); + await init(); + }; + // #region Main event handlers + ael(main, 'click', async (evt) => { + try { + /** @type { HTMLElement } */ + const target = evt.target.closest('[data-command]'); + if (!target) { + return; + } + const prmpt = /prompt-/.test(target.dataset.command); + let dataset = target.dataset; + let cmd = dataset.command; + let prmptChoice = false; + if (prmpt) { + dataset = target.parentElement.dataset; + cmd = dataset.command; + prmptChoice = /confirm/.test(target.dataset.command); + target.parentElement.parentElement.remove(); + } + if (cmd === 'install-script' && dataset.userjs) { + let installCode = dataset.userjs; + if (!prmpt && dataset.userjs.endsWith('.user.css')) { + container.makePrompt(i18n$('prmpt_css'), dataset); + return; + } else if (prmpt !== prmptChoice) { + installCode = dataset.userjs.replace(/\.user\.css$/, '.user.js'); + } + const dlBtn = make('a', '', { + onclick(evt) { + evt.preventDefault(); + doInstallProcess(evt.target); + } + }); + dlBtn.href = installCode; + dlBtn.click(); + } else if (cmd === 'open-tab' && dataset.webpage) { + if (isGM) { + if (isFN(GM.openInTab)) { + return GM.openInTab(dataset.webpage); + } else if (isFN(GM_openInTab)) { + return GM_openInTab(dataset.webpage, { + active: true, + insert: true + }); + } + } + return window.open(dataset.webpage, '_blank'); + } else if (cmd === 'navigation') { + if (dom.cl.has(btngreasy, 'hidden')) { + dom.cl.remove([btncfg, btngreasy, btnhome, btnissue], 'hidden'); + } else { + dom.cl.add([btncfg, btngreasy, btnhome, btnissue], 'hidden'); + } + } else if (cmd === 'list-description') { + const arr = []; + const ignoreTags = new Set(['TD', 'MUJS-A', 'MU-JS']); + for (const node of target.parentElement.childNodes) { + if (ignoreTags.has(node.tagName)) { + continue; + } + if (node.tagName === 'TEXTAREA' && isEmpty(node.value)) { + continue; + } + arr.push(node); + } + if (target.nextElementSibling) { + arr.push(target.nextElementSibling); + if (target.nextElementSibling.nextElementSibling) { + arr.push(target.nextElementSibling.nextElementSibling); + } + } + if (dom.cl.has(arr[0], 'hidden')) { + dom.cl.remove(arr, 'hidden'); + } else { + dom.cl.add(arr, 'hidden'); + } + } else if (cmd === 'close') { + container.remove(); + } else if (cmd === 'show-filter') { + dom.cl.toggle(fsearch, 'hidden'); + } else if (cmd === 'fullscreen') { + if (dom.cl.has(btnfullscreen, 'expanded')) { + dom.cl.remove([btnfullscreen, main], 'expanded'); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); + } else { + dom.cl.add([btnfullscreen, main], 'expanded'); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); + } + } else if (cmd === 'hide-list') { + dom.cl.add(main, 'hidden'); + dom.cl.remove(mainframe, 'hidden'); + timeoutFrame(); + } else if (cmd === 'save') { + container.rebuild = true; + dom.prop(rateContainer, 'innerHTML', ''); + if (!dom.prop(target, 'disabled')) { + const config = await container.save(); + sleazyRedirect(); + if (container.rebuild) { + container.cache.clear(); + if (config.autofetch) { + respHandles.build(); + } + } + container.unsaved = false; + container.rebuild = false; + } + } else if (cmd === 'reset') { + cfg = DEFAULT_CONFIG; + dom.cl.remove(mainframe, 'error'); + if (qs('.error', footer)) { + for (const elem of qsA('.error', footer)) { + elem.remove(); + } + } + container.unsaved = true; + container.rebuild = true; + rebuildCfg(); + } else if (cmd === 'settings') { + if (container.unsaved) { + showError('Unsaved changes'); } - container.unsaved = false; + tab.create('mujs:settings'); container.rebuild = false; - }); - } else { - log(`Imported theme: { ${file.name} }`, result); - cfg.theme = result; - container.renderTheme(cfg.theme); + } else if (cmd === 'new-tab') { + tab.create(); + } else if (cmd === 'switch-tab') { + tab.active(target); + } else if (cmd === 'close-tab' && target.parentElement) { + tab.close(target.parentElement); + } else if (cmd === 'download-userjs') { + if (!container.userjsCache.has(+dataset.userjs)) { + return; + } + const dataUserJS = container.userjsCache.get(+dataset.userjs); + let installCode = dataUserJS.code_url; + if (!prmpt && dataUserJS.code_url.endsWith('.user.css')) { + container.makePrompt('Download as UserStyle?', dataset); + return; + } else if (prmpt !== prmptChoice) { + installCode = dataUserJS.code_url.replace(/\.user\.css$/, '.user.js'); + } + const r = await dataUserJS._mujs.code.request(false, installCode); + const txt = r.data; + if (typeof txt !== 'string') { + return; + } + const userjsName = dataset.userjsName ?? dataset.userjs; + const userjsExt = prmpt !== prmptChoice ? '.user.js' : '.user.css'; + const makeUserJS = new Blob([txt], { type: 'text/plain' }); + const dlBtn = make('a', 'mujs_Downloader'); + dlBtn.href = URL.createObjectURL(makeUserJS); + dlBtn.download = `${userjsName}${userjsExt}`; + dlBtn.click(); + URL.revokeObjectURL(dlBtn.href); + dlBtn.remove(); + } else if (cmd === 'load-userjs') { + if (!container.userjsCache.has(+dataset.userjs)) { + return; + } + const codeArea = qs('textarea', target.parentElement.parentElement); + if (!isEmpty(codeArea.value)) { + dom.cl.toggle(codeArea, 'hidden'); + return; + } + const dataUserJS = container.userjsCache.get(+dataset.userjs); + const r = await dataUserJS._mujs.code.request(); + const txt = r.data; + if (typeof txt !== 'string') { + return; + } + const code_obj = new ParseUserJS(txt); + codeArea.value = code_obj.get_code_block(); + dom.cl.remove(codeArea, 'hidden'); + const apTo = (name, elem, root) => { + const n = dataUserJS._mujs.code[name]; + if (isEmpty(n)) { + const el = make('mujs-a', '', { + textContent: i18n$('listing_none') + }); + elem.append(el); + return; + } + dom.prop(elem, 'innerHTML', ''); + dom.cl.remove(root, 'hidden'); + for (const c of n) { + if (typeof c === 'string' && c.startsWith('http')) { + const el = make('mujs-a', '', { + textContent: c, + dataset: { + command: 'open-tab', + webpage: c + } + }); + elem.append(el); + } else if (isObj(c)) { + if (name === 'resource') { + for (const [k, v] of Object.entries(c)) { + const el = make('mujs-a', '', { + textContent: k ?? 'ERROR' + }); + if (v.startsWith('http')) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = v; + } + elem.append(el); + } + } else { + const el = make('mujs-a', '', { + textContent: c.text + }); + if (c.domain) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = `https://${c.text}`; + } + elem.append(el); + } + } else { + const el = make('mujs-a', '', { + textContent: c + }); + elem.append(el); + } + } + }; + const m = qsA('mujs-column[data-el="matches"]', target.parentElement.parentElement); + for (const e of normalizeTarget(m)) { + apTo(e.dataset.type, qs('.mujs-grants', e), e); + } + } else if (/export-/.test(cmd)) { + const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' '); + const bytes = new TextEncoder().encode(str); + const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); + const dlBtn = make('a', 'mujs-exporter', { + href: URL.createObjectURL(blob), + download: `Magic_Userscript_${cmd === 'export-cfg' ? 'config' : 'theme'}.json` + }); + dlBtn.click(); + URL.revokeObjectURL(dlBtn.href); + } else if (/import-/.test(cmd)) { + if (qs('input', target.parentElement)) { + qs('input', target.parentElement).click(); + return; + } + const inpJSON = make('input', 'hidden', { + type: 'file', + accept: '.json', + onchange(evt) { + try { + [...evt.target.files].forEach((file) => { + const reader = new FileReader(); + reader.readAsText(file); + reader.onload = () => { + const result = JSON.parse(reader.result); + if (result.blacklist) { + log(`Imported config: { ${file.name} }`, result); + cfg = result; + container.unsaved = true; + container.rebuild = true; + rebuildCfg(); + container.save().then((config) => { + sleazyRedirect(); + container.cache.clear(); + if (config.autofetch) { + respHandles.build(); + } + container.unsaved = false; + container.rebuild = false; + }); + } else { + log(`Imported theme: { ${file.name} }`, result); + cfg.theme = result; + container.renderTheme(cfg.theme); + } + inpJSON.remove(); + }; + reader.onerror = () => { + showError(reader.error); + inpJSON.remove(); + }; + }); + } catch (ex) { + showError(ex); + inpJSON.remove(); + } + } + }); + target.parentElement.append(inpJSON); + inpJSON.click(); } - inpJSON.remove(); - }; - reader.onerror = () => { - showError(reader.error); - inpJSON.remove(); - }; - }); - } catch (ex) { - showError(ex); - inpJSON.remove(); - } + } catch (ex) { + showError(ex); + } + }); + ael(main, 'auxclick', (evt) => { + if (evt.button !== 1) { + return; + } + /** @type { HTMLElement } */ + const target = evt.target.closest('[data-command]'); + if (!target) { + return; + } + const dataset = target.dataset; + const cmd = dataset.command; + if (cmd === 'switch-tab' || cmd === 'close-tab') { + tab.close(target); + } else if (cmd === 'new-tab') { + tab.create(); + } + }); + if (!isMobile) { + const fade = async (target, type) => { + if (type === 'mouseenter') { + frameTimeout.clear(...frameTimeout.ids); + container.timeouts.mouse.clear(...container.timeouts.mouse.ids); + target.style.opacity = container.opacityMax; + } else if (type === 'mouseleave') { + await container.timeouts.mouse.set(cfg.time); + target.style.opacity = container.opacityMin; + } + }; + for (const e of ['mouseenter', 'mouseleave']) { + ael(main, e, (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + fade(evt.target, evt.type); + }); + } } - }); - target.parentElement.append(inpJSON); - inpJSON.click(); - } - } catch (ex) { - showError(ex); - } - }); - ael(main, 'auxclick', (evt) => { - if (evt.button !== 1) { - return; - } - /** @type { HTMLElement } */ - const target = evt.target.closest('[data-command]'); - if (!target) { - return; - } - const dataset = target.dataset; - const cmd = dataset.command; - if (cmd === 'switch-tab' || cmd === 'close-tab') { - tab.close(target); - } else if (cmd === 'new-tab') { - tab.create(); - } - }); - if (!isMobile) { - const fade = async (target, type) => { - if (type === 'mouseenter') { - frameTimeout.clear(...frameTimeout.ids); - container.timeouts.mouse.clear(...container.timeouts.mouse.ids); - target.style.opacity = container.opacityMax; - } else if (type === 'mouseleave') { - await container.timeouts.mouse.set(cfg.time); - target.style.opacity = container.opacityMin; - } - }; - for (const e of ['mouseenter', 'mouseleave']) { - ael(main, e, (evt) => { - evt.preventDefault(); - evt.stopPropagation(); - fade(evt.target, evt.type); - }); - } - } - // #endregion - const META_START_COMMENT = '// ==UserScript=='; - const META_END_COMMENT = '// ==/UserScript=='; - const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk']; - const APPLIES_TO_ALL_PATTERNS = [ - 'http://*', - 'https://*', - 'http://*/*', - 'https://*/*', - 'http*://*', - 'http*://*/*', - '*', - '*://*', - '*://*/*', - 'http*' - ]; - class ParseUserJS { - /** + // #endregion + const META_START_COMMENT = '// ==UserScript=='; + const META_END_COMMENT = '// ==/UserScript=='; + const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk']; + const APPLIES_TO_ALL_PATTERNS = [ + 'http://*', + 'https://*', + 'http://*/*', + 'https://*/*', + 'http*://*', + 'http*://*/*', + '*', + '*://*', + '*://*/*', + 'http*' + ]; + class ParseUserJS { + /** * @type { string } */ - code; - /** + code; + /** * @type { string } */ - data_meta_block; - /** + data_meta_block; + /** * @type { string } */ - data_code_block; - /** + data_code_block; + /** * @type { { [meta: string]: any } } */ - data_meta; - /** + data_meta; + /** * @type { string[] } */ - data_names; - constructor(code) { - this.code = code; - this.get_all(); - } - get_all() { - this.get_meta_block(); - this.get_code_block(); - this.parse_meta(); - this.calculate_applies_to_names(); - } - get_meta_block() { - if (isEmpty(this.code)) { - return null; - } - if (this.data_meta_block) { - return this.data_meta_block; - } - const start_block = this.code.indexOf(META_START_COMMENT); - if (isNull(start_block)) { - return null; - } - const end_block = this.code.indexOf(META_END_COMMENT, start_block); - if (isNull(end_block)) { - return null; - } - const meta_block = this.code.substring(start_block + META_START_COMMENT.length, end_block); - this.data_meta_block = meta_block; - return meta_block; - } - get_code_block() { - if (isEmpty(this.code)) { - return null; - } - if (this.data_code_block) { - return this.data_code_block; - } - const start_block = this.code.indexOf(META_START_COMMENT); - if (isNull(start_block)) { - return null; - } - const end_block = this.code.indexOf(META_END_COMMENT, start_block); - if (isNull(end_block)) { - return null; - } - const code_block = this.code.substring( - end_block + META_END_COMMENT.length, - this.code.length - ); - const lines = []; - for (const l of code_block.split('\n')) { - if (isEmpty(l)) { - continue; - } - lines.push(l); - } - const clean_code = lines.join('\n'); - this.data_code_block = clean_code; - return clean_code; - } - parse_meta() { - if (isEmpty(this.code)) { - return null; - } - if (this.data_meta) { - return this.data_meta; - } - const meta = {}; - const meta_block = this.get_meta_block(); - const meta_block_map = new Map(); - for (const meta_line of meta_block.split('\n')) { - const meta_match = meta_line.match(/\/\/\s+@([a-zA-Z:-]+)\s+(.*)/); - if (isNull(meta_match)) { - continue; - } - const key = meta_match[1].trim(); - const value = meta_match[2].trim(); - if (!meta_block_map.has(key)) { - meta_block_map.set(key, []); - } - const meta_map = meta_block_map.get(key); - meta_map.push(value); - meta_block_map.set(key, meta_map); - } - for (const [key, value] of meta_block_map) { - if (value.length > 1) { - meta[key] = value; - } else { - meta[key] = value[0]; - } - } - this.data_meta = meta; - return this.data_meta; - } - /** - * @returns { string[] } - */ - calculate_applies_to_names() { - if (isEmpty(this.code)) { - return null; - } - if (this.data_names) { - return this.data_names; - } - const meta = this.parse_meta(); - let patterns = []; - for (const [k, v] of Object.entries(meta)) { - if (/include|match/.test(k)) { - if (Array.isArray(v)) { - patterns = patterns.concat(v); - } else { - patterns = patterns.concat([v]); - } - } - } - if (isEmpty(patterns)) { - return []; - } - if (this.intersect(patterns, APPLIES_TO_ALL_PATTERNS)) { - return []; - } - const name_set = new Set(); - const addObj = (obj) => { - if (name_set.has(obj)) { - return; - } - name_set.add(obj); - }; - for (let p of patterns) { - try { - const original_pattern = p; - let pre_wildcards = []; - if (p.match(/^\/(.*)\/$/)) { - pre_wildcards = [p]; - } else { - let m = p.match(/^\*(https?:.*)/i); - if (!isNull(m)) { - p = m[1]; - } - p = p - .replace(/^\*:/i, 'http:') - .replace(/^\*\/\//i, 'http://') - .replace(/^http\*:/i, 'http:') - .replace(/^(https?):([^/])/i, '$1://$2'); - m = p.match(/^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = m[1] + m[2]; - } - m = p.match(/^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; - } - m = p.match(/^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; - } - m = p.match(/^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/); - if (!isNull(m)) { - if (m[2].match(/A([0-9]+\.){2,}z/)) { - p = `${m[1]}tld${m[3]}`; - pre_wildcards = [p.split('*')[0]]; - } else { - pre_wildcards = [p]; + data_names; + constructor(code) { + this.code = code; + this.get_all(); + } + get_all() { + this.get_meta_block(); + this.get_code_block(); + this.parse_meta(); + this.calculate_applies_to_names(); + } + get_meta_block() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_meta_block) { + return this.data_meta_block; + } + const start_block = this.code.indexOf(META_START_COMMENT); + if (isNull(start_block)) { + return null; + } + const end_block = this.code.indexOf(META_END_COMMENT, start_block); + if (isNull(end_block)) { + return null; + } + const meta_block = this.code.substring(start_block + META_START_COMMENT.length, end_block); + this.data_meta_block = meta_block; + return meta_block; + } + get_code_block() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_code_block) { + return this.data_code_block; + } + const start_block = this.code.indexOf(META_START_COMMENT); + if (isNull(start_block)) { + return null; + } + const end_block = this.code.indexOf(META_END_COMMENT, start_block); + if (isNull(end_block)) { + return null; + } + const code_block = this.code.substring( + end_block + META_END_COMMENT.length, + this.code.length + ); + const lines = []; + for (const l of code_block.split('\n')) { + if (isEmpty(l)) { + continue; + } + lines.push(l); + } + const clean_code = lines.join('\n'); + this.data_code_block = clean_code; + return clean_code; } - } else { - pre_wildcards = [p]; - } - } - for (const pre_wildcard of pre_wildcards) { - try { - const urlObj = new URL(pre_wildcard); - const { host } = urlObj; - if (isNull(host)) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (!host.includes('.') && host.includes('*')) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (host.endsWith('.tld')) { - for (let i = 0; i < TLD_EXPANSION.length; i++) { - const tld = TLD_EXPANSION[i]; - addObj({ - text: host.replace(/tld$/i, tld), - domain: true, - tld_extra: i != 0 - }); - } - } else if (host.endsWith('.')) { - addObj({ - text: host.slice(0, -1), - domain: true, - tld_extra: false - }); - } else { - addObj({ - text: host, - domain: true, - tld_extra: false - }); + parse_meta() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_meta) { + return this.data_meta; + } + const meta = {}; + const meta_block = this.get_meta_block(); + const meta_block_map = new Map(); + for (const meta_line of meta_block.split('\n')) { + const meta_match = meta_line.match(/\/\/\s+@([a-zA-Z:-]+)\s+(.*)/); + if (isNull(meta_match)) { + continue; + } + const key = meta_match[1].trim(); + const value = meta_match[2].trim(); + if (!meta_block_map.has(key)) { + meta_block_map.set(key, []); + } + const meta_map = meta_block_map.get(key); + meta_map.push(value); + meta_block_map.set(key, meta_map); + } + for (const [key, value] of meta_block_map) { + if (value.length > 1) { + meta[key] = value; + } else { + meta[key] = value[0]; + } + } + this.data_meta = meta; + return this.data_meta; } - // eslint-disable-next-line no-unused-vars - } catch (ex) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } + /** + * @returns { string[] } + */ + calculate_applies_to_names() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_names) { + return this.data_names; + } + const meta = this.parse_meta(); + let patterns = []; + for (const [k, v] of Object.entries(meta)) { + if (/include|match/.test(k)) { + if (Array.isArray(v)) { + patterns = patterns.concat(v); + } else { + patterns = patterns.concat([v]); + } + } + } + if (isEmpty(patterns)) { + return []; + } + if (this.intersect(patterns, APPLIES_TO_ALL_PATTERNS)) { + return []; + } + const name_set = new Set(); + const addObj = (obj) => { + if (name_set.has(obj)) { + return; + } + name_set.add(obj); + }; + for (let p of patterns) { + try { + const original_pattern = p; + let pre_wildcards = []; + if (p.match(/^\/(.*)\/$/)) { + pre_wildcards = [p]; + } else { + let m = p.match(/^\*(https?:.*)/i); + if (!isNull(m)) { + p = m[1]; + } + p = p + .replace(/^\*:/i, 'http:') + .replace(/^\*\/\//i, 'http://') + .replace(/^http\*:/i, 'http:') + .replace(/^(https?):([^/])/i, '$1://$2'); + m = p.match(/^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i); + if (!isNull(m)) { + p = m[1] + m[2]; + } + m = p.match(/^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i); + if (!isNull(m)) { + p = `http://${m[1]}`; + } + m = p.match(/^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i); + if (!isNull(m)) { + p = `http://${m[1]}`; + } + m = p.match(/^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/); + if (!isNull(m)) { + if (m[2].match(/A([0-9]+\.){2,}z/)) { + p = `${m[1]}tld${m[3]}`; + pre_wildcards = [p.split('*')[0]]; + } else { + pre_wildcards = [p]; + } + } else { + pre_wildcards = [p]; + } + } + for (const pre_wildcard of pre_wildcards) { + try { + const urlObj = new URL(pre_wildcard); + const { host } = urlObj; + if (isNull(host)) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (!host.includes('.') && host.includes('*')) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (host.endsWith('.tld')) { + for (let i = 0; i < TLD_EXPANSION.length; i++) { + const tld = TLD_EXPANSION[i]; + addObj({ + text: host.replace(/tld$/i, tld), + domain: true, + tld_extra: i != 0 + }); + } + } else if (host.endsWith('.')) { + addObj({ + text: host.slice(0, -1), + domain: true, + tld_extra: false + }); + } else { + addObj({ + text: host, + domain: true, + tld_extra: false + }); + } + // eslint-disable-next-line no-unused-vars + } catch (ex) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } + } + } catch (ex) { + err(ex); + } + } + const arr = [...name_set]; + this.data_names = arr; + return this.data_names; + } + intersect(a = [], b = []) { + for (const v of a) { + if (b.includes(v)) { + return true; + } + } + for (const v of b) { + if (a.includes(v)) { + return true; + } + } + return false; + } + // intersect(a, ...arr) { + // return [...new Set(a)].filter((v) => arr.every((b) => b.includes(v))); + // } } - } catch (ex) { - err(ex); - } - } - const arr = [...name_set]; - this.data_names = arr; - return this.data_names; - } - intersect(a = [], b = []) { - for (const v of a) { - if (b.includes(v)) { - return true; - } - } - for (const v of b) { - if (a.includes(v)) { - return true; - } - } - return false; - } - // intersect(a, ...arr) { - // return [...new Set(a)].filter((v) => arr.every((b) => b.includes(v))); - // } - } - const mergeTemplate = (obj = {}) => { - const template = { - id: 0, - bad_ratings: 0, - good_ratings: 0, - ok_ratings: 0, - daily_installs: 0, - total_installs: 0, - name: 'NOT FOUND', - description: 'NOT FOUND', - version: '0.0.0', - url: BLANK_PAGE, - code_url: BLANK_PAGE, - created_at: Date.now(), - code_updated_at: Date.now(), - locale: 'NOT FOUND', - deleted: false, - users: [ - { - id: 0, - name: 'NOT FOUND', - url: BLANK_PAGE - } - ] - }; - /** @type {ObjectConstructor["hasOwn"]} */ - const hasOwn = - Object.hasOwn || - function hasOwn(it, key) { - return Object.prototype.hasOwnProperty.call(it, key); - }; - for (const key in template) { - if (hasOwn(obj, key)) continue; - obj[key] = template[key]; - } - return obj; - }; - const mkList = (txt = '', obj = {}) => { - if (!obj.root || !obj.type) { - return; - } - const { root, type } = obj; - const appliesTo = make('mu-js', 'mujs-list', { - textContent: `${txt}: ` - }); - const applyList = make('mu-js', 'mujs-grants'); - const ujsURLs = make('mujs-column', 'mujs-list', { - dataset: { - el: 'matches', - type - } + const mergeTemplate = (obj = {}) => { + const template = { + id: 0, + bad_ratings: 0, + good_ratings: 0, + ok_ratings: 0, + daily_installs: 0, + total_installs: 0, + name: 'NOT FOUND', + description: 'NOT FOUND', + version: '0.0.0', + url: BLANK_PAGE, + code_url: BLANK_PAGE, + created_at: Date.now(), + code_updated_at: Date.now(), + locale: 'NOT FOUND', + deleted: false, + users: [ + { + id: 0, + name: 'NOT FOUND', + url: BLANK_PAGE + } + ] + }; + /** @type {ObjectConstructor["hasOwn"]} */ + const hasOwn = + Object.hasOwn || + function hasOwn(it, key) { + return Object.prototype.hasOwnProperty.call(it, key); + }; + for (const key in template) { + if (hasOwn(obj, key)) continue; + obj[key] = template[key]; + } + return obj; + }; + const mkList = (txt = '', obj = {}) => { + if (!obj.root || !obj.type) { + return; + } + const { root, type } = obj; + const appliesTo = make('mu-js', 'mujs-list', { + textContent: `${txt}: ` }); - ujsURLs.append(appliesTo, applyList); - root.append(ujsURLs); + const applyList = make('mu-js', 'mujs-grants'); + const ujsURLs = make('mujs-column', 'mujs-list', { + dataset: { + el: 'matches', + type + } + }); + ujsURLs.append(appliesTo, applyList); + root.append(ujsURLs); - const list = obj.list ?? []; - if (isEmpty(list)) { - const elem = make('mujs-a', '', { - textContent: i18n$('listing_none') - }); - applyList.append(elem); - dom.cl.add(ujsURLs, 'hidden'); - return; - } - for (const c of list) { - if (typeof c === 'string' && c.startsWith('http')) { - const elem = make('mujs-a', '', { - textContent: c, - dataset: { - command: 'open-tab', - webpage: c - } - }); - applyList.append(elem); - } else if (isObj(c)) { - if (type === 'resource') { - for (const [k, v] of Object.entries(c)) { - const elem = make('mujs-a', '', { - textContent: k ?? 'ERROR' - }); - if (v.startsWith('http')) { - elem.dataset.command = 'open-tab'; - elem.dataset.webpage = v; - } - applyList.append(elem); - } - } else { - const elem = make('mujs-a', '', { - textContent: c.text - }); - if (c.domain) { - elem.dataset.command = 'open-tab'; - elem.dataset.webpage = `https://${c.text}`; - } - applyList.append(elem); - } - } else { - const elem = make('mujs-a', '', { - textContent: c - }); - applyList.append(elem); - } - } - }; - /** + const list = obj.list ?? []; + if (isEmpty(list)) { + const elem = make('mujs-a', '', { + textContent: i18n$('listing_none') + }); + applyList.append(elem); + dom.cl.add(ujsURLs, 'hidden'); + return; + } + for (const c of list) { + if (typeof c === 'string' && c.startsWith('http')) { + const elem = make('mujs-a', '', { + textContent: c, + dataset: { + command: 'open-tab', + webpage: c + } + }); + applyList.append(elem); + } else if (isObj(c)) { + if (type === 'resource') { + for (const [k, v] of Object.entries(c)) { + const elem = make('mujs-a', '', { + textContent: k ?? 'ERROR' + }); + if (v.startsWith('http')) { + elem.dataset.command = 'open-tab'; + elem.dataset.webpage = v; + } + applyList.append(elem); + } + } else { + const elem = make('mujs-a', '', { + textContent: c.text + }); + if (c.domain) { + elem.dataset.command = 'open-tab'; + elem.dataset.webpage = `https://${c.text}`; + } + applyList.append(elem); + } + } else { + const elem = make('mujs-a', '', { + textContent: c + }); + applyList.append(elem); + } + } + }; + /** * @param {number} [time] */ - const timeoutFrame = async (time) => { - frameTimeout.clear(...frameTimeout.ids); - if (dom.cl.has(mainframe, 'hidden')) { - return; - } - time = time ?? cfg.time ?? DEFAULT_CONFIG.time; - let n = 10000; - if (typeof time === 'number' && !Number.isNaN(time)) { - n = container.isBlacklisted ? time / 2 : time; - } - await frameTimeout.set(n); - container.remove(); - return frameTimeout.clear(...frameTimeout.ids); - }; - // #region Create UserJS - /** + const timeoutFrame = async (time) => { + frameTimeout.clear(...frameTimeout.ids); + if (dom.cl.has(mainframe, 'hidden')) { + return; + } + time = time ?? cfg.time ?? DEFAULT_CONFIG.time; + let n = 10000; + if (typeof time === 'number' && !Number.isNaN(time)) { + n = container.isBlacklisted ? time / 2 : time; + } + await frameTimeout.set(n); + container.remove(); + return frameTimeout.clear(...frameTimeout.ids); + }; + // #region Create UserJS + /** * @param { import("../typings/types.d.ts").GSForkQuery } ujs * @param { string } engine */ - const createjs = (ujs, engine) => { - ujs = mergeTemplate(ujs); - // Lets not add this UserJS to the list - if (ujs.id === 421603) { - return; - } - if (badUserJS.includes(ujs.id) || badUserJS.includes(ujs.url)) { - return; - } - if (!container.userjsCache.has(ujs.id)) { - container.userjsCache.set(ujs.id, ujs); - } - const eframe = make('td', 'install-btn'); - const uframe = make('td', 'mujs-uframe'); - const fdaily = make('td', 'mujs-list', { - textContent: ujs.daily_installs - }); - const fupdated = make('td', 'mujs-list', { - textContent: language.toDate(ujs.code_updated_at) - }); - const fname = make('td', 'mujs-name'); - const fmore = make('mujs-column', 'mujs-list hidden', { - dataset: { - el: 'more-info' - } - }); - const fBtns = make('mujs-column', 'mujs-list hidden'); - const jsInfo = make('mujs-row', 'mujs-list'); - const jsInfoB = make('mujs-row', 'mujs-list'); - const ratings = make('mujs-column', 'mujs-list'); - const ftitle = make('mujs-a', 'mujs-homepage', { - textContent: ujs.name, - title: ujs.url, - dataset: { - command: 'open-tab', - webpage: ujs.url - } - }); - const fver = make('mu-js', 'mujs-list', { - textContent: `${i18n$('version_number')}: ${ujs.version}` - }); - const fcreated = make('mu-js', 'mujs-list', { - textContent: `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}` - }); - const flicense = make('mu-js', 'mujs-list', { - title: ujs.license ?? i18n$('no_license'), - textContent: `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`, - style: - 'text-overflow: ellipsis; overflow: hidden; white-space: nowrap; width: fit-content; max-width: 20em;' - }); - const ftotal = make('mu-js', 'mujs-list', { - textContent: `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}` - }); - const fratings = make('mu-js', 'mujs-list', { - title: i18n$('ratings'), - textContent: `${i18n$('ratings')}:` - }); - const fgood = make('mu-js', 'mujs-list mujs-ratings', { - title: i18n$('good'), - textContent: ujs.good_ratings, - dataset: { - el: 'good' - } - }); - const fok = make('mu-js', 'mujs-list mujs-ratings', { - title: i18n$('ok'), - textContent: ujs.ok_ratings, - dataset: { - el: 'ok' - } - }); - const fbad = make('mu-js', 'mujs-list mujs-ratings', { - title: i18n$('bad'), - textContent: ujs.bad_ratings, - dataset: { - el: 'bad' - } + const createjs = (ujs, engine) => { + ujs = mergeTemplate(ujs); + // Lets not add this UserJS to the list + if (ujs.id === 421603) { + return; + } + if (badUserJS.includes(ujs.id) || badUserJS.includes(ujs.url)) { + return; + } + if (!container.userjsCache.has(ujs.id)) { + container.userjsCache.set(ujs.id, ujs); + } + const eframe = make('td', 'install-btn'); + const uframe = make('td', 'mujs-uframe'); + const fdaily = make('td', 'mujs-list', { + textContent: ujs.daily_installs + }); + const updatedDate = new Date(ujs.code_updated_at); + const sortValue = isNaN(updatedDate.getTime()) ? 0 : updatedDate.getTime(); + const fupdated = make('td', 'mujs-list', { + textContent: isNaN(updatedDate.getTime()) ? 'Invalid Date' : language.toDate(updatedDate), + dataset: { + sortValue: sortValue + } + }); + const fname = make('td', 'mujs-name'); + const fmore = make('mujs-column', 'mujs-list hidden', { + dataset: { + el: 'more-info' + } + }); + const fBtns = make('mujs-column', 'mujs-list hidden'); + const jsInfo = make('mujs-row', 'mujs-list'); + const jsInfoB = make('mujs-row', 'mujs-list'); + const ratings = make('mujs-column', 'mujs-list'); + const ftitle = make('mujs-a', 'mujs-homepage', { + textContent: ujs.name, + title: ujs.url, + dataset: { + command: 'open-tab', + webpage: ujs.url + } + }); + const fver = make('mu-js', 'mujs-list', { + textContent: `${i18n$('version_number')}: ${ujs.version}` }); - const fdesc = make('mu-js', 'mujs-list mujs-pointer', { - title: ujs.description, - textContent: ujs.description, - dataset: { - command: 'list-description' - } + const fcreated = make('mu-js', 'mujs-list', { + textContent: `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}` }); - const scriptInstall = make('mu-jsbtn', 'install', { - innerHTML: `${iconSVG.load('install')} ${i18n$('install')}`, - title: `${i18n$('install')} "${ujs.name}"`, - dataset: { - command: 'install-script', - userjs: ujs.code_url - } + const flicense = make('mu-js', 'mujs-list', { + title: ujs.license ?? i18n$('no_license'), + textContent: `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`, + style: + 'text-overflow: ellipsis; overflow: hidden; white-space: nowrap; width: fit-content; max-width: 20em;' + }); + const ftotal = make('mu-js', 'mujs-list', { + textContent: `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}` }); - const scriptDownload = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('install')} ${i18n$('saveFile')}`, - dataset: { - command: 'download-userjs', - userjs: ujs.id, - userjsName: ujs.name - } + const fratings = make('mu-js', 'mujs-list', { + title: i18n$('ratings'), + textContent: `${i18n$('ratings')}:` }); - const tr = make('tr', 'frame', { - dataset: { - scriptId: ujs.id + const fgood = make('mu-js', 'mujs-list mujs-ratings', { + title: i18n$('good'), + textContent: ujs.good_ratings, + dataset: { + el: 'good' + } + }); + const fok = make('mu-js', 'mujs-list mujs-ratings', { + title: i18n$('ok'), + textContent: ujs.ok_ratings, + dataset: { + el: 'ok' + } + }); + const fbad = make('mu-js', 'mujs-list mujs-ratings', { + title: i18n$('bad'), + textContent: ujs.bad_ratings, + dataset: { + el: 'bad' + } + }); + const fdesc = make('mu-js', 'mujs-list mujs-pointer', { + title: ujs.description, + textContent: ujs.description, + dataset: { + command: 'list-description' + } + }); + const scriptInstall = make('mu-jsbtn', 'install', { + innerHTML: `${iconSVG.load('install')} ${i18n$('install')}`, + title: `${i18n$('install')} "${ujs.name}"`, + dataset: { + command: 'install-script', + userjs: ujs.code_url + } + }); + const scriptDownload = make('mu-jsbtn', '', { + innerHTML: `${iconSVG.load('install')} ${i18n$('saveFile')}`, + dataset: { + command: 'download-userjs', + userjs: ujs.id, + userjsName: ujs.name + } + }); + const tr = make('tr', 'frame', { + dataset: { + scriptId: ujs.id + } + }); + const codeArea = make('textarea', 'code-area hidden', { + dataset: { + name: 'code' + }, + rows: '10', + autocomplete: false, + spellcheck: false, + wrap: 'soft' + }); + const loadCode = make('mu-jsbtn', '', { + innerHTML: `${iconSVG.load('search')} ${i18n$('preview_code')}`, + dataset: { + command: 'load-userjs', + userjs: ujs.id + } + }); + if (engine) { + tr.dataset.engine = engine; + // if (engine.includes('fork')) { + // fdaily.dataset.command = 'open-tab'; + // fdaily.dataset.webpage = `${ujs.url}/stats`; + // fupdated.dataset.command = 'open-tab'; + // fupdated.dataset.webpage = `${ujs.url}/versions`; + // } + if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { + tr.dataset.good = 'upsell'; + } } - }); - const codeArea = make('textarea', 'code-area hidden', { - dataset: { - name: 'code' - }, - rows: '10', - autocomplete: false, - spellcheck: false, - wrap: 'soft' - }); - const loadCode = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('search')} ${i18n$('preview_code')}`, - dataset: { - command: 'load-userjs', - userjs: ujs.id + for (const u of ujs.users) { + const user = make('mujs-a', '', { + innerHTML: u.name, + title: u.url, + dataset: { + command: 'open-tab', + webpage: u.url + } + }); + if (cfg.recommend.author && u.id === authorID) { + tr.dataset.author = 'upsell'; + dom.prop(user, 'innerHTML', `${u.name} ${iconSVG.load('verified')}`); + } + uframe.append(user); } - }); - if (engine) { - tr.dataset.engine = engine; - // if (engine.includes('fork')) { - // fdaily.dataset.command = 'open-tab'; - // fdaily.dataset.webpage = `${ujs.url}/stats`; - // fupdated.dataset.command = 'open-tab'; - // fupdated.dataset.webpage = `${ujs.url}/versions`; - // } - if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { - tr.dataset.good = 'upsell'; + if (engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.id)) { + tr.dataset.good = 'upsell'; } - } - for (const u of ujs.users) { - const user = make('mujs-a', '', { - innerHTML: u.name, - title: u.url, - dataset: { - command: 'open-tab', - webpage: u.url - } + eframe.append(scriptInstall); + ratings.append(fratings, fgood, fok, fbad); + jsInfo.append(ftotal, ratings, fver, fcreated); + mkList(i18n$('code_size'), { + list: ujs._mujs.code.code_size, + type: 'size', + root: jsInfo }); - if (cfg.recommend.author && u.id === authorID) { - tr.dataset.author = 'upsell'; - dom.prop(user, 'innerHTML', `${u.name} ${iconSVG.load('verified')}`); - } - uframe.append(user); - } - if (engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.id)) { - tr.dataset.good = 'upsell'; - } - eframe.append(scriptInstall); - ratings.append(fratings, fgood, fok, fbad); - jsInfo.append(ftotal, ratings, fver, fcreated); - mkList(i18n$('code_size'), { - list: ujs._mujs.code.code_size, - type: 'size', - root: jsInfo - }); - jsInfoB.append(flicense); - mkList(i18n$('antifeatures'), { - list: ujs._mujs.code.antifeatures, - type: 'antifeatures', - root: jsInfoB - }); - mkList(i18n$('applies_to'), { - list: ujs._mujs.code.match, - type: 'match', - root: jsInfoB - }); - mkList('@grant', { - list: ujs._mujs.code.grant, - type: 'grant', - root: jsInfoB - }); - mkList('@require', { - list: ujs._mujs.code.meta?.require, - type: 'require', - root: jsInfoB - }); - const script_resources = ujs._mujs.code.meta?.resource; - mkList('@resource', { - list: isNull(script_resources) ? [] : [script_resources], - type: 'resource', - root: jsInfoB - }); - fmore.append(jsInfo, jsInfoB); - fBtns.append(scriptDownload, loadCode); - fname.append(ftitle, fdesc, fmore, fBtns, codeArea); + jsInfoB.append(flicense); + mkList(i18n$('antifeatures'), { + list: ujs._mujs.code.antifeatures, + type: 'antifeatures', + root: jsInfoB + }); + mkList(i18n$('applies_to'), { + list: ujs._mujs.code.match, + type: 'match', + root: jsInfoB + }); + mkList('@grant', { + list: ujs._mujs.code.grant, + type: 'grant', + root: jsInfoB + }); + mkList('@require', { + list: ujs._mujs.code.meta?.require, + type: 'require', + root: jsInfoB + }); + const script_resources = ujs._mujs.code.meta?.resource; + mkList('@resource', { + list: isNull(script_resources) ? [] : [script_resources], + type: 'resource', + root: jsInfoB + }); + fmore.append(jsInfo, jsInfoB); + fBtns.append(scriptDownload, loadCode); + fname.append(ftitle, fdesc, fmore, fBtns, codeArea); - const code_data = ujs._mujs.code.data; - if (code_data) { - const code_obj = new ParseUserJS(code_data); - codeArea.value = code_obj.get_code_block(); - } + const code_data = ujs._mujs.code.data; + if (code_data) { + const code_obj = new ParseUserJS(code_data); + codeArea.value = code_obj.get_code_block(); + } - if (ujs._mujs.code?.translated) { - tr.classList.add('translated'); - } + if (ujs._mujs.code?.translated) { + tr.classList.add('translated'); + } - for (const e of [fname, uframe, fdaily, fupdated, eframe]) { - tr.append(e); - } - tabbody.append(tr); - ujs._mujs.root = tr; + for (const e of [fname, uframe, fdaily, fupdated, eframe]) { + tr.append(e); + } + tabbody.append(tr); + ujs._mujs.root = tr; - return tr; + return tr; }; - // #endregion - const loadFilters = () => { - const resp = []; - for (const v of Object.values(cfg.filters)) { - if (!v.enabled) { - continue; - } - resp.push(new RegExp(v.regExp, v.flag)); - } - return { - regExp: resp, - get(name) { + // #endregion + const loadFilters = () => { + const resp = []; for (const v of Object.values(cfg.filters)) { - if (v.name === name) { - return new RegExp(v.regExp, v.flag) - }; - } - // return resp.find(r => r.name === name); - }, - match({ name, users }) { - if (isBlank(resp)) { - return true; - } - const strArr = [{ name }, ...users]; - for (const r of resp) { - const v = strArr.find((o) => o.name.match(r)); - if (v) { - return false; - } + if (!v.enabled) { + continue; + } + resp.push(new RegExp(v.regExp, v.flag)); } - return true; - } + return { + regExp: resp, + get(name) { + for (const v of Object.values(cfg.filters)) { + if (v.name === name) { + return new RegExp(v.regExp, v.flag) + }; + } + // return resp.find(r => r.name === name); + }, + match({ name, users }) { + if (isBlank(resp)) { + return true; + } + const strArr = [{ name }, ...users]; + for (const r of resp) { + const v = strArr.find((o) => o.name.match(r)); + if (v) { + return false; + } + } + return true; + } + }; }; - }; - // #region List - class List { - engines; - cache; - intHost; - constructor(host = undefined) { - this.build = this.build.bind(this); - if (isEmpty(host)) host = container.host; - this.engines = cfg.engines; - this.cache = container.cache.get(host); - this.host = host; - } - - set host(hostname) { - this.intHost = hostname; - - if (!container.cache.has(hostname)) { - const engineTemplate = {}; - for (const engine of cfg.engines) { - engineTemplate[engine.name] = []; - } - container.cache.set(hostname, engineTemplate); - } - if (container.checkBlacklist(hostname)) { - showError(`Blacklisted "${hostname}"`); - timeoutFrame(); - this.blacklisted = true; - } else { - this.blacklisted = false; - } - - const isSupported = (name) => { - for (const [k, v] of Object.entries(engineUnsupported)) { - if (k !== name) { - continue; - } - if (v.includes(hostname)) { - return false; - } + // #region List + class List { + engines; + cache; + intHost; + constructor(host = undefined) { + this.build = this.build.bind(this); + if (isEmpty(host)) host = container.host; + this.engines = cfg.engines; + this.cache = container.cache.get(host); + this.host = host; } - return true; - }; - this.engines = cfg.engines.filter((e) => e.enabled && isSupported(e.name)); - this.cache = container.cache.get(hostname); - } - - get host() { - return this.intHost; - } - // #region Builder - build() { - try { - container.refresh(); - if (this.blacklisted) { - return; - } - if (isEmpty(this.engines)) { - container.opacityMin = '0'; - mainframe.style.opacity = container.opacityMin; - return; - } - info(' Building list', { cache: this.cache, engines: this.engines }); - const customRecords = []; - const host = this.host; - const arr = []; - const bsFilter = loadFilters(); + set host(hostname) { + this.intHost = hostname; - for (const engine of this.engines) { - const cEngine = this.cache[`${engine.name}`]; - if (!isEmpty(cEngine)) { - for (const ujs of cEngine) { - if (isElem(ujs._mujs.root)) { - tabbody.append(ujs._mujs.root); - } + if (!container.cache.has(hostname)) { + const engineTemplate = {}; + for (const engine of cfg.engines) { + engineTemplate[engine.name] = []; + } + container.cache.set(hostname, engineTemplate); } - updateCounter(cEngine.length, engine); - continue; - } - const respError = (error) => { - if (!error.cause) error.cause = engine.name; - if (error.message.startsWith('429')) { - showError(`Engine: "${engine.name}" Too many requests...`); - return; + if (container.checkBlacklist(hostname)) { + showError(`Blacklisted "${hostname}"`); + timeoutFrame(); + this.blacklisted = true; + } else { + this.blacklisted = false; } - showError(`Engine: "${engine.name}"`, error.message); - }; - const _mujs = (d) => { - const obj = { - ...d, - _mujs: { - root: {}, - info: { - engine, - host - }, - code: { - antifeatures: [], - grant: [], - match: [], - meta: {}, - request: async function (translate = false, code_url) { - if (this.data) { - return this; + + const isSupported = (name) => { + for (const [k, v] of Object.entries(engineUnsupported)) { + if (k !== name) { + continue; } - this.data = ''; - /** @type { string } */ - const code = await Network.req(code_url ?? d.code_url, 'GET', 'text').catch( - showError - ); - if (typeof code !== 'string') { - return this; + if (v.includes(hostname)) { + return false; } - Object.assign(this, { - data: code, - code_size: [Network.format(code.length)] - }); - const grantSet = new Set(); - const afSet = new Set(); - const code_obj = new ParseUserJS(code); - const meta = code_obj.parse_meta(); - const applies_to_names = code_obj.calculate_applies_to_names(); + } + return true; + }; + this.engines = cfg.engines.filter((e) => e.enabled && isSupported(e.name)); + this.cache = container.cache.get(hostname); + } - if (translate) { - for (const lng of language.cache) { - if (meta[`name:${lng}`]) { - Object.assign(obj, { - name: meta[`name:${lng}`] - }); - this.translated = true; - } - if (meta[`description:${lng}`]) { - Object.assign(obj, { - description: meta[`description:${lng}`] - }); - this.translated = true; - } - } - } + get host() { + return this.intHost; + } - for (const [key, value] of Object.entries(meta)) { - if (/grant/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (grantSet.has(v)) { - continue; - } - grantSet.add(v); - } - } else if (/antifeature/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (afSet.has(v)) { - continue; - } - afSet.add(v); + // #region Builder + build() { + try { + container.refresh(); + if (this.blacklisted) { + return; + } + if (isEmpty(this.engines)) { + container.opacityMin = '0'; + mainframe.style.opacity = container.opacityMin; + return; + } + info(' Building list', { cache: this.cache, engines: this.engines }); + const customRecords = []; + const host = this.host; + const arr = []; + const bsFilter = loadFilters(); + + for (const engine of this.engines) { + const cEngine = this.cache[`${engine.name}`]; + if (!isEmpty(cEngine)) { + for (const ujs of cEngine) { + if (isElem(ujs._mujs.root)) { + tabbody.append(ujs._mujs.root); + } } - } - } - if (typeof meta.require === 'string') { - const s = meta.require; - meta.require = [s]; + updateCounter(cEngine.length, engine); + continue; } - if (meta.resource) { - const obj = {}; - if (typeof meta.resource === 'string') { - const reg = /(.+)\s+(.+)/.exec(meta.resource); - if (reg) { - obj[reg[1].trim()] = reg[2]; - } - } else { - for (const r of meta.resource) { - const reg = /(.+)\s+(http.+)/.exec(r); - if (reg) { - obj[reg[1].trim()] = reg[2]; - } + const respError = (error) => { + if (!error.cause) error.cause = engine.name; + if (error.message.startsWith('429')) { + showError(`Engine: "${engine.name}" Too many requests...`); + return; } - } - meta.resource = obj; - } - Object.assign(this, { - meta, - match: applies_to_names, - grant: [...grantSet], - antifeatures: [...afSet] - }); + showError(`Engine: "${engine.name}"`, error.message); + }; + const _mujs = (d) => { + const obj = { + ...d, + _mujs: { + root: {}, + info: { + engine, + host + }, + code: { + antifeatures: [], + grant: [], + match: [], + meta: {}, + request: async function (translate = false, code_url) { + if (this.data) { + return this; + } + this.data = ''; + /** @type { string } */ + const code = await Network.req(code_url ?? d.code_url, 'GET', 'text').catch( + showError + ); + if (typeof code !== 'string') { + return this; + } + Object.assign(this, { + data: code, + code_size: [Network.format(code.length)] + }); + const grantSet = new Set(); + const afSet = new Set(); + const code_obj = new ParseUserJS(code); + const meta = code_obj.parse_meta(); + const applies_to_names = code_obj.calculate_applies_to_names(); - return this; - } - } - } - }; - return obj; - }; - const toQuery = (fallback) => { - if (engine.query) { - return decodeURIComponent(engine.query).replace(/\{host\}/g, host); - } - return fallback; - }; - /** + if (translate) { + for (const lng of language.cache) { + if (meta[`name:${lng}`]) { + Object.assign(obj, { + name: meta[`name:${lng}`] + }); + this.translated = true; + } + if (meta[`description:${lng}`]) { + Object.assign(obj, { + description: meta[`description:${lng}`] + }); + this.translated = true; + } + } + } + + for (const [key, value] of Object.entries(meta)) { + if (/grant/.test(key)) { + for (const v of normalizeTarget(value, false)) { + if (grantSet.has(v)) { + continue; + } + grantSet.add(v); + } + } else if (/antifeature/.test(key)) { + for (const v of normalizeTarget(value, false)) { + if (afSet.has(v)) { + continue; + } + afSet.add(v); + } + } + } + if (typeof meta.require === 'string') { + const s = meta.require; + meta.require = [s]; + } + if (meta.resource) { + const obj = {}; + if (typeof meta.resource === 'string') { + const reg = /(.+)\s+(.+)/.exec(meta.resource); + if (reg) { + obj[reg[1].trim()] = reg[2]; + } + } else { + for (const r of meta.resource) { + const reg = /(.+)\s+(http.+)/.exec(r); + if (reg) { + obj[reg[1].trim()] = reg[2]; + } + } + } + meta.resource = obj; + } + Object.assign(this, { + meta, + match: applies_to_names, + grant: [...grantSet], + antifeatures: [...afSet] + }); + + return this; + } + } + } + }; + return obj; + }; + const toQuery = (fallback) => { + if (engine.query) { + return decodeURIComponent(engine.query).replace(/\{host\}/g, host); + } + return fallback; + }; + /** * @param { import("../typings/types.d.ts").GSFork } dataQ */ - const forkFN = async (dataQ) => { - if (!dataQ) { - showError('Invalid data received from the server, check internet connection'); - return; - } - /** + const forkFN = async (dataQ) => { + if (!dataQ) { + showError('Invalid data received from the server, check internet connection'); + return; + } + /** * @type { import("../typings/types.d.ts").GSForkQuery[] } */ - const dq = Array.isArray(dataQ) - ? dataQ - : Array.isArray(dataQ.query) - ? dataQ.query - : []; - const dataA = dq - .filter(Boolean) - .filter((d) => !d.deleted) - .filter(bsFilter.match); - if (isBlank(dataA)) { - return; - } - const data = dataA.map(_mujs); - const otherLng = []; - /** + const dq = Array.isArray(dataQ) + ? dataQ + : Array.isArray(dataQ.query) + ? dataQ.query + : []; + const dataA = dq + .filter(Boolean) + .filter((d) => !d.deleted) + .filter(bsFilter.match); + if (isBlank(dataA)) { + return; + } + const data = dataA.map(_mujs); + const otherLng = []; + /** * @param {import("../typings/types.d.ts").GSForkQuery} d * @returns {boolean} */ - const inUserLanguage = (d) => { - const dlocal = d.locale.split('-')[0] ?? d.locale; - if (language.cache.includes(dlocal)) { - return true; - } - otherLng.push(d); - return false; - }; - const filterLang = data.filter((d) => { - if (cfg.filterlang && !inUserLanguage(d)) { - return false; - } - return true; - }); - let finalList = filterLang; - const hds = []; - for (const ujs of otherLng) { - const c = await ujs._mujs.code.request(true); - if (c.translated) { - hds.push(ujs); - } - } - finalList = union(hds, filterLang); + const inUserLanguage = (d) => { + const dlocal = d.locale.split('-')[0] ?? d.locale; + if (language.cache.includes(dlocal)) { + return true; + } + otherLng.push(d); + return false; + }; + const filterLang = data.filter((d) => { + if (cfg.filterlang && !inUserLanguage(d)) { + return false; + } + return true; + }); + let finalList = filterLang; + const hds = []; + for (const ujs of otherLng) { + const c = await ujs._mujs.code.request(true); + if (c.translated) { + hds.push(ujs); + } + } + finalList = union(hds, filterLang); - for (const ujs of finalList) { - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); - } - createjs(ujs, engine.name); - } - this.cache[engine.name].push(...finalList); - updateCounter(finalList.length, engine); - }; - /** + for (const ujs of finalList) { + if (cfg.codePreview && !ujs._mujs.code.data) { + await ujs._mujs.code.request(); + } + createjs(ujs, engine.name); + } + this.cache[engine.name].push(...finalList); + updateCounter(finalList.length, engine); + }; + /** * @param {Document} htmlDocument */ - const openuserjs = async (htmlDocument) => { - try { - if (!htmlDocument) { - showError('Invalid data received from the server, TODO fix this'); - return; - } - const selected = htmlDocument.documentElement; - if (/openuserjs/gi.test(engine.name)) { - for (const i of qsA('.col-sm-8 .tr-link', selected)) { - while (isNull(qs('.script-version', i))) { - await new Promise((resolve) => requestAnimationFrame(resolve)); - } - const fixurl = dom - .prop(qs('.tr-link-a', i), 'href') - .replace( - new RegExp(document.location.origin, 'gi'), - 'https://openuserjs.org' - ); - const ujs = mergeTemplate( - _mujs({ - name: dom.text(qs('.tr-link-a', i)), - description: dom.text(qs('p', i)), - version: dom.text(qs('.script-version', i)), - url: fixurl, - code_url: `${fixurl.replace(/\/scripts/gi, '/install')}.user.js`, - total_installs: dom.text(qs('td:nth-child(2) p', i)), - created_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'), - code_updated_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'), - users: [ - { - name: dom.text(qs('.inline-block a', i)), - url: dom.prop(qs('.inline-block a', i), 'href') + const openuserjs = async (htmlDocument) => { + try { + if (!htmlDocument) { + showError('Invalid data received from the server, TODO fix this'); + return; + } + const selected = htmlDocument.documentElement; + if (/openuserjs/gi.test(engine.name)) { + for (const i of qsA('.col-sm-8 .tr-link', selected)) { + while (isNull(qs('.script-version', i))) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + } + const fixurl = dom + .prop(qs('.tr-link-a', i), 'href') + .replace( + new RegExp(document.location.origin, 'gi'), + 'https://openuserjs.org' + ); + const ujs = mergeTemplate( + _mujs({ + name: dom.text(qs('.tr-link-a', i)), + description: dom.text(qs('p', i)), + version: dom.text(qs('.script-version', i)), + url: fixurl, + code_url: `${fixurl.replace(/\/scripts/gi, '/install')}.user.js`, + total_installs: dom.text(qs('td:nth-child(2) p', i)), + created_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'), + code_updated_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'), + users: [ + { + name: dom.text(qs('.inline-block a', i)), + url: dom.prop(qs('.inline-block a', i), 'href') + } + ] + }) + ); + if (bsFilter.match(ujs)) { + continue; + } + if (cfg.codePreview && !ujs._mujs.code.data) { + await ujs._mujs.code.request(); + } + createjs(ujs, engine.name); + customRecords.push(ujs); + } + } + this.cache[engine.name].push(...customRecords); + updateCounter(customRecords.length, engine); + } catch (ex) { + showError(ex); } - ] - }) - ); - if (bsFilter.match(ujs)) { - continue; - } - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); - } - createjs(ujs, engine.name); - customRecords.push(ujs); - } - } - this.cache[engine.name].push(...customRecords); - updateCounter(customRecords.length, engine); - } catch (ex) { - showError(ex); - } - }; - const gitFN = async (data) => { - try { - if (isBlank(data.items)) { - showError('Invalid data received from the server, TODO fix this'); - return; - } - for (const r of data.items) { - const ujs = mergeTemplate( - _mujs({ - name: r.name, - description: isEmpty(r.repository.description) - ? i18n$('no_license') - : r.repository.description, - url: r.html_url, - code_url: r.html_url.replace(/\/blob\//g, '/raw/'), - code_updated_at: r.commit || Date.now(), - total_installs: r.score, - users: [ - { - name: r.repository.owner.login, - url: r.repository.owner.html_url - } - ] - }) - ); - if (bsFilter.match(ujs)) { - continue; - } - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); - } - createjs(ujs, engine.name); - customRecords.push(ujs); - } - this.cache[engine.name].push(...customRecords); - updateCounter(data.items.length, engine); - } catch (ex) { - showError(ex); - } - }; - let netFN; - if (/github/gi.test(engine.name)) { - if (isEmpty(engine.token)) { - showError(`"${engine.name}" requires a token to use`); - continue; - } - netFN = Network.req( - toQuery( - `${engine.url}"// ==UserScript=="+${host}+ "// ==/UserScript=="+in:file+language:js&per_page=30` + }; + const gitFN = async (data) => { + try { + if (isBlank(data.items)) { + showError('Invalid data received from the server, TODO fix this'); + return; + } + for (const r of data.items) { + const ujs = mergeTemplate( + _mujs({ + name: r.name, + description: isEmpty(r.repository.description) + ? i18n$('no_license') + : r.repository.description, + url: r.html_url, + code_url: r.html_url.replace(/\/blob\//g, '/raw/'), + code_updated_at: r.commit || Date.now(), + total_installs: r.score, + users: [ + { + name: r.repository.owner.login, + url: r.repository.owner.html_url + } + ] + }) + ); + if (bsFilter.match(ujs)) { + continue; + } + if (cfg.codePreview && !ujs._mujs.code.data) { + await ujs._mujs.code.request(); + } + createjs(ujs, engine.name); + customRecords.push(ujs); + } + this.cache[engine.name].push(...customRecords); + updateCounter(data.items.length, engine); + } catch (ex) { + showError(ex); + } + }; + let netFN; + if (/github/gi.test(engine.name)) { + if (isEmpty(engine.token)) { + showError(`"${engine.name}" requires a token to use`); + continue; + } + netFN = Network.req( + toQuery( + `${engine.url}"// ==UserScript=="+${host}+ "// ==/UserScript=="+in:file+language:js&per_page=30` ), - 'GET', - 'json', - { - headers: { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${engine.token}`, - 'X-GitHub-Api-Version': '2022-11-28' - } - } - ) - .then(gitFN) - .then(() => { - Network.req('https://api.github.com/rate_limit', 'GET', 'json', { - headers: { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${engine.token}`, - 'X-GitHub-Api-Version': '2022-11-28' - } - }) - .then((data) => { - for (const [key, value] of Object.entries(data.resources.code_search)) { - const txt = make('mujs-row', 'rate-info', { - textContent: `${key.toUpperCase()}: ${value}` + 'GET', + 'json', + { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + } + ) + .then(gitFN) + .then(() => { + Network.req('https://api.github.com/rate_limit', 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + .then((data) => { + for (const [key, value] of Object.entries(data.resources.code_search)) { + const txt = make('mujs-row', 'rate-info', { + textContent: `${key.toUpperCase()}: ${value}` }); - rateContainer.append(txt); + rateContainer.append(txt); } - }) - .catch(respError); - }); + }) + .catch(respError); + }); } else if (/openuserjs/gi.test(engine.name)) { - netFN = Network.req(toQuery(`${engine.url}${host}`), 'GET', 'document').then( - openuserjs - ); + netFN = Network.req(toQuery(`${engine.url}${host}`), 'GET', 'document').then( + openuserjs + ); } else { - netFN = Network.req( - toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`) - ).then(forkFN); - } - if (netFN) { - arr.push(netFN.catch(respError)); + netFN = Network.req( + toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`) + ).then(forkFN); } + if (netFN) { + arr.push(netFN.catch(respError)); + } } - urlBar.placeholder = i18n$('search_placeholder'); - urlBar.value = ''; + urlBar.placeholder = i18n$('search_placeholder'); + urlBar.value = ''; - Promise.allSettled(arr).then(() => { - tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); - tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); - }); + Promise.allSettled(arr).then(() => { + tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); + tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); + }); } catch (ex) { - showError(ex); + showError(ex); } } - // #endregion + // #endregion } - const MUList = new List(); - // #endregion - // #region Make Config - const makecfg = () => { - const cbtn = make('mu-js', 'mujs-sty-flex'); - const savebtn = make('mujs-btn', 'save', { - textContent: i18n$('save'), - dataset: { - command: 'save' - }, - disabled: false - }); - const resetbtn = make('mujs-btn', 'reset', { - textContent: i18n$('reset'), - dataset: { - command: 'reset' - } - }); - const makesection = (name, tag) => { - tag = tag ?? i18n$('no_license'); - name = name ?? i18n$('no_license'); - const sec = make('mujs-section', '', { - dataset: { - name: tag - } - }); - const lb = make('label', '', { - dataset: { - command: tag - } - }); - const divDesc = make('mu-js', '', { - textContent: name - }); - ael(sec, 'click', (evt) => { - /** @type { HTMLElement } */ - const target = evt.target.closest('[data-command]'); - if (!target) { - return; - } - const cmd = target.dataset.command; - if (cmd === tag) { - const a = qsA(`label[data-${tag}]`, sec); - if (dom.cl.has(a, 'hidden')) { - dom.cl.remove(a, 'hidden'); - } else { - dom.cl.add(a, 'hidden'); - } - } - }); - - lb.append(divDesc); - sec.append(lb); - cfgpage.append(sec); - return sec; - }; - - const sections = { - general: makesection('General', 'general'), - load: makesection('Automation', 'load'), - list: makesection('List', 'list'), - filters: makesection('List Filters', 'filters'), - blacklist: makesection('Blacklist (WIP)', 'blacklist'), - // engine: makesection('Search Engine', 'engine'), - theme: makesection('Theme Colors', 'theme') - }; - - const makerow = (desc, type = null, nm, attrs = {}) => { - desc = desc ?? i18n$('no_license'); - nm = nm ?? i18n$('no_license'); - const sec = make('mujs-section', '', { - dataset: { - name: nm - } - }); - const lb = make('label'); - const divDesc = make('mu-js', '', { - textContent: desc - }); - lb.append(divDesc); - sec.append(lb); - cfgpage.append(sec); - if (isNull(type)) { - return lb; - } - const inp = make('input', '', { - type, - dataset: { - name: nm - }, - ...attrs - }); - if (!cfgMap.has(nm)) { - cfgMap.set(nm, inp); - } - if (type === 'checkbox') { - const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { - onclick() { - inp.dispatchEvent(new MouseEvent('click')); - } + const MUList = new List(); + // #endregion + // #region Make Config + const makecfg = () => { + const cbtn = make('mu-js', 'mujs-sty-flex'); + const savebtn = make('mujs-btn', 'save', { + textContent: i18n$('save'), + dataset: { + command: 'save' + }, + disabled: false }); - inlab.append(inp, la); - lb.append(inlab); - if (nm.includes('-')) { - return inp; - } - if (/(greasy|sleazy)fork|openuserjs|gi(thub|st)/gi.test(nm)) { - const engine = cfg.engines.find((engine) => engine.name === nm); - if (engine) { - inp.checked = engine.enabled; - inp.dataset.engine = engine.name; - ael(inp, 'change', (evt) => { - container.unsaved = true; - container.rebuild = true; - engine.enabled = evt.target.checked; + const resetbtn = make('mujs-btn', 'reset', { + textContent: i18n$('reset'), + dataset: { + command: 'reset' + } + }); + const makesection = (name, tag) => { + tag = tag ?? i18n$('no_license'); + name = name ?? i18n$('no_license'); + const sec = make('mujs-section', '', { + dataset: { + name: tag + } + }); + const lb = make('label', '', { + dataset: { + command: tag + } + }); + const divDesc = make('mu-js', '', { + textContent: name + }); + ael(sec, 'click', (evt) => { + /** @type { HTMLElement } */ + const target = evt.target.closest('[data-command]'); + if (!target) { + return; + } + const cmd = target.dataset.command; + if (cmd === tag) { + const a = qsA(`label[data-${tag}]`, sec); + if (dom.cl.has(a, 'hidden')) { + dom.cl.remove(a, 'hidden'); + } else { + dom.cl.add(a, 'hidden'); + } + } }); - if (engine.query) { - const d = DEFAULT_CONFIG.engines.find((e) => e.name === engine.name); - const urlInp = make('input', '', { - type: 'text', - defaultValue: '', - value: decodeURIComponent(engine.query) ?? '', - placeholder: decodeURIComponent(d.query) ?? '', + lb.append(divDesc); + sec.append(lb); + cfgpage.append(sec); + return sec; + }; + + const sections = { + general: makesection('General', 'general'), + load: makesection('Automation', 'load'), + list: makesection('List', 'list'), + filters: makesection('List Filters', 'filters'), + blacklist: makesection('Blacklist (WIP)', 'blacklist'), + // engine: makesection('Search Engine', 'engine'), + theme: makesection('Theme Colors', 'theme') + }; + + const makerow = (desc, type = null, nm, attrs = {}) => { + desc = desc ?? i18n$('no_license'); + nm = nm ?? i18n$('no_license'); + const sec = make('mujs-section', '', { dataset: { - name: nm, - engine: engine.name - }, - onchange(evt) { - container.unsaved = true; - container.rebuild = true; - try { - engine.query = encodeURIComponent(new URL(evt.target.value).toString()); - } catch (ex) { - err(ex); - } + name: nm } - }); - sec.append(urlInp); + }); + const lb = make('label'); + const divDesc = make('mu-js', '', { + textContent: desc + }); + lb.append(divDesc); + sec.append(lb); + cfgpage.append(sec); + if (isNull(type)) { + return lb; } - if (engine.name === 'github') { - const ghToken = make('input', '', { - type: 'text', - defaultValue: '', - value: engine.token ?? '', - placeholder: 'Paste Access Token', + const inp = make('input', '', { + type, dataset: { - engine: 'github-token' + name: nm }, - onchange(evt) { - container.unsaved = true; - container.rebuild = true; - engine.token = evt.target.value; - } - }); - sec.append(ghToken); - cfgMap.set('github-token', ghToken); - } - } - } else { - inp.checked = cfg[nm]; - ael(inp, 'change', (evt) => { - container.unsaved = true; - if (/filterlang/i.test(nm)) { - container.rebuild = true; + ...attrs + }); + if (!cfgMap.has(nm)) { + cfgMap.set(nm, inp); } - cfg[nm] = evt.target.checked; - }); - } - } else { - lb.append(inp); - } - return inp; - }; - const mkSection = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => { - cfgMap.set(text, value); - const lb = make('label', 'sub-section hidden', { - textContent: text, - dataset: { - [tag]: text - } - }); - const inp = make('input', '', { - type, - dataset: { - [tag]: text - }, - ...attrs - }); - - if (sections[tag]) { - sections[tag].append(lb); - } - - if (type === 'checkbox') { - const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { - onclick() { - inp.dispatchEvent(new MouseEvent('click')); - } - }); - inlab.append(inp, la); - lb.append(inlab); + if (type === 'checkbox') { + const inlab = make('mu-js', 'mujs-inlab'); + const la = make('label', '', { + onclick() { + inp.dispatchEvent(new MouseEvent('click')); + } + }); + inlab.append(inp, la); + lb.append(inlab); + if (nm.includes('-')) { + return inp; + } + if (/(greasy|sleazy)fork|openuserjs|gi(thub|st)/gi.test(nm)) { + const engine = cfg.engines.find((engine) => engine.name === nm); + if (engine) { + inp.checked = engine.enabled; + inp.dataset.engine = engine.name; + ael(inp, 'change', (evt) => { + container.unsaved = true; + container.rebuild = true; + engine.enabled = evt.target.checked; + }); - const nm = /^(\w+)-(.+)/.exec(value); - if (nm) { - if (nm[1] === 'filters') { - inp.checked = cfg[nm[1]][nm[2]].enabled; - } else { - inp.checked = cfg[nm[1]][nm[2]]; - } - } else { - inp.checked = cfg[value]; - } - ael(inp, 'change', (evt) => { - container.unsaved = true; - if (/filterlang/i.test(value)) { - container.rebuild = true; - } - if (nm) { - if (nm[1] === 'filters') { - cfg[nm[1]][nm[2]].enabled = evt.target.checked; + if (engine.query) { + const d = DEFAULT_CONFIG.engines.find((e) => e.name === engine.name); + const urlInp = make('input', '', { + type: 'text', + defaultValue: '', + value: decodeURIComponent(engine.query) ?? '', + placeholder: decodeURIComponent(d.query) ?? '', + dataset: { + name: nm, + engine: engine.name + }, + onchange(evt) { + container.unsaved = true; + container.rebuild = true; + try { + engine.query = encodeURIComponent(new URL(evt.target.value).toString()); + } catch (ex) { + err(ex); + } + } + }); + sec.append(urlInp); + } + if (engine.name === 'github') { + const ghToken = make('input', '', { + type: 'text', + defaultValue: '', + value: engine.token ?? '', + placeholder: 'Paste Access Token', + dataset: { + engine: 'github-token' + }, + onchange(evt) { + container.unsaved = true; + container.rebuild = true; + engine.token = evt.target.value; + } + }); + sec.append(ghToken); + cfgMap.set('github-token', ghToken); + } + } + } else { + inp.checked = cfg[nm]; + ael(inp, 'change', (evt) => { + container.unsaved = true; + if (/filterlang/i.test(nm)) { + container.rebuild = true; + } + cfg[nm] = evt.target.checked; + }); + } } else { - cfg[nm[1]][nm[2]] = evt.target.checked; + lb.append(inp); } - } else { - cfg[value] = evt.target.checked; - } - }); - } else { - if (type === 'text') { - inp.defaultValue = ''; - inp.value = value ?? ''; - inp.placeholder = value ?? ''; - } - - lb.append(inp); - } - - return lb; - }; - if (isGM) { - mkSection(i18n$('userjs_sync'), 'cache'); - mkSection(i18n$('userjs_autoinject'), 'autoinject', 'checkbox', 'load'); - } - mkSection(i18n$('redirect'), 'sleazyredirect'); - mkSection(`${i18n$('dtime')} (ms)`, 'time', 'number', 'general', { - defaultValue: 10000, - value: cfg.time, - min: 0, - step: 500, - onbeforeinput(evt) { - if (evt.target.validity.badInput) { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } else { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } - }, - oninput(evt) { - container.unsaved = true; - const t = evt.target; - if (t.validity.badInput || (t.validity.rangeUnderflow && t.value !== '-1')) { - dom.cl.add(t, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } else { - dom.cl.remove(t, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - cfg.time = isEmpty(t.value) ? cfg.time : parseFloat(t.value); - } - } - }); + return inp; + }; + const mkSection = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => { + cfgMap.set(text, value); + const lb = make('label', 'sub-section hidden', { + textContent: text, + dataset: { + [tag]: text + } + }); + const inp = make('input', '', { + type, + dataset: { + [tag]: text + }, + ...attrs + }); - mkSection(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load'); - mkSection(i18n$('userjs_fullscreen'), 'autoexpand', 'checkbox', 'load', { - onchange(e) { - if (e.target.checked) { - dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); - } else { - dom.cl.remove([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); - } - } - }); + if (sections[tag]) { + sections[tag].append(lb); + } - mkSection(i18n$('filter'), 'filterlang', 'checkbox', 'list'); - mkSection(i18n$('preview_code'), 'codePreview', 'checkbox', 'list'); - mkSection('Recommend author', 'recommend-author', 'checkbox', 'list'); - mkSection('Recommend scripts', 'recommend-others', 'checkbox', 'list'); + if (type === 'checkbox') { + const inlab = make('mu-js', 'mujs-inlab'); + const la = make('label', '', { + onclick() { + inp.dispatchEvent(new MouseEvent('click')); + } + }); + inlab.append(inp, la); + lb.append(inlab); - for (const [k, v] of Object.entries(cfg.filters)) { - mkSection(v.name, `filters-${k}`, 'checkbox', 'filters'); - } + const nm = /^(\w+)-(.+)/.exec(value); + if (nm) { + if (nm[1] === 'filters') { + inp.checked = cfg[nm[1]][nm[2]].enabled; + } else { + inp.checked = cfg[nm[1]][nm[2]]; + } + } else { + inp.checked = cfg[value]; + } + ael(inp, 'change', (evt) => { + container.unsaved = true; + if (/filterlang/i.test(value)) { + container.rebuild = true; + } + if (nm) { + if (nm[1] === 'filters') { + cfg[nm[1]][nm[2]].enabled = evt.target.checked; + } else { + cfg[nm[1]][nm[2]] = evt.target.checked; + } + } else { + cfg[value] = evt.target.checked; + } + }); + } else { + if (type === 'text') { + inp.defaultValue = ''; + inp.value = value ?? ''; + inp.placeholder = value ?? ''; + } - makerow('Greasy Fork', 'checkbox', 'greasyfork'); - makerow('Sleazy Fork', 'checkbox', 'sleazyfork'); - makerow('Open UserJS', 'checkbox', 'openuserjs'); - makerow('GitHub API', 'checkbox', 'github'); + lb.append(inp); + } - for (const [k, v] of Object.entries(cfg.theme)) { - const lb = make('label', 'hidden', { - textContent: k, - dataset: { - theme: k + return lb; + }; + if (isGM) { + mkSection(i18n$('userjs_sync'), 'cache'); + mkSection(i18n$('userjs_autoinject'), 'autoinject', 'checkbox', 'load'); } - }); - const inp = make('input', '', { - type: 'text', - defaultValue: '', - value: v ?? '', - placeholder: v ?? '', - dataset: { - theme: k - }, - onchange(evt) { - let isvalid = true; - try { - const val = evt.target.value; - const sty = container.root.style; - const str = `--mujs-${k}`; - const prop = sty.getPropertyValue(str); - if (isEmpty(val)) { - cfg.theme[k] = DEFAULT_CONFIG.theme[k]; - sty.removeProperty(str); - return; - } - if (prop === val) { - return; + mkSection(i18n$('redirect'), 'sleazyredirect'); + mkSection(`${i18n$('dtime')} (ms)`, 'time', 'number', 'general', { + defaultValue: 10000, + value: cfg.time, + min: 0, + step: 500, + onbeforeinput(evt) { + if (evt.target.validity.badInput) { + dom.cl.add(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } else { + dom.cl.remove(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + } + }, + oninput(evt) { + container.unsaved = true; + const t = evt.target; + if (t.validity.badInput || (t.validity.rangeUnderflow && t.value !== '-1')) { + dom.cl.add(t, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } else { + dom.cl.remove(t, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + cfg.time = isEmpty(t.value) ? cfg.time : parseFloat(t.value); + } } - sty.removeProperty(str); - sty.setProperty(str, val); - cfg.theme[k] = val; - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); + }); + + mkSection(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load'); + mkSection(i18n$('userjs_fullscreen'), 'autoexpand', 'checkbox', 'load', { + onchange(e) { + if (e.target.checked) { + dom.cl.add([btnfullscreen, main], 'expanded'); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); + } else { + dom.cl.remove([btnfullscreen, main], 'expanded'); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); + } } - } - } - }); - cfgMap.set(k, inp); - lb.append(inp); - sections.theme.append(lb); - } + }); - const createList = (key, v = '', disabled = false, type = 'String') => { - let txt = key; - if (typeof key === 'string') { - if (key.startsWith('userjs-')) { - disabled = true; - const s = key.substring(7); - txt = `Built-in "${s}"`; - v = builtinList[s]; - } - } else { - if (!key.enabled) { - return; + mkSection(i18n$('filter'), 'filterlang', 'checkbox', 'list'); + mkSection(i18n$('preview_code'), 'codePreview', 'checkbox', 'list'); + mkSection('Recommend author', 'recommend-author', 'checkbox', 'list'); + mkSection('Recommend scripts', 'recommend-others', 'checkbox', 'list'); + + for (const [k, v] of Object.entries(cfg.filters)) { + mkSection(v.name, `filters-${k}`, 'checkbox', 'filters'); } - } - if (isRegExp(v)) { - v = v.toString(); - type = 'RegExp'; - } else { - v = JSON.stringify(v); - type = 'Object'; - } + makerow('Greasy Fork', 'checkbox', 'greasyfork'); + makerow('Sleazy Fork', 'checkbox', 'sleazyfork'); + makerow('Open UserJS', 'checkbox', 'openuserjs'); + makerow('GitHub API', 'checkbox', 'github'); - const lb = make('label', 'hidden', { - textContent: txt, - dataset: { - blacklist: key + for (const [k, v] of Object.entries(cfg.theme)) { + const lb = make('label', 'hidden', { + textContent: k, + dataset: { + theme: k + } + }); + const inp = make('input', '', { + type: 'text', + defaultValue: '', + value: v ?? '', + placeholder: v ?? '', + dataset: { + theme: k + }, + onchange(evt) { + let isvalid = true; + try { + const val = evt.target.value; + const sty = container.root.style; + const str = `--mujs-${k}`; + const prop = sty.getPropertyValue(str); + if (isEmpty(val)) { + cfg.theme[k] = DEFAULT_CONFIG.theme[k]; + sty.removeProperty(str); + return; + } + if (prop === val) { + return; + } + sty.removeProperty(str); + sty.setProperty(str, val); + cfg.theme[k] = val; + } catch (ex) { + err(ex); + isvalid = false; + } finally { + if (isvalid) { + dom.cl.remove(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + } else { + dom.cl.add(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } + } + } + }); + cfgMap.set(k, inp); + lb.append(inp); + sections.theme.append(lb); } - }); - const inp = make('input', '', { - type: 'text', - defaultValue: '', - value: v ?? '', - placeholder: v ?? '', - dataset: { - blacklist: key - }, - onchange(evt) { - let isvalid = true; - try { - const val = evt.target.value; - if (isEmpty(val)) { - return; + + const createList = (key, v = '', disabled = false, type = 'String') => { + let txt = key; + if (typeof key === 'string') { + if (key.startsWith('userjs-')) { + disabled = true; + const s = key.substring(7); + txt = `Built-in "${s}"`; + v = builtinList[s]; + } + } else { + if (!key.enabled) { + return; + } } - isvalid = true; - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); + + if (isRegExp(v)) { + v = v.toString(); + type = 'RegExp'; } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); + v = JSON.stringify(v); + type = 'Object'; } - } + + const lb = make('label', 'hidden', { + textContent: txt, + dataset: { + blacklist: key + } + }); + const inp = make('input', '', { + type: 'text', + defaultValue: '', + value: v ?? '', + placeholder: v ?? '', + dataset: { + blacklist: key + }, + onchange(evt) { + let isvalid = true; + try { + const val = evt.target.value; + if (isEmpty(val)) { + return; + } + isvalid = true; + } catch (ex) { + err(ex); + isvalid = false; + } finally { + if (isvalid) { + dom.cl.remove(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + } else { + dom.cl.add(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } + } + } + }); + const selType = make('select', '', { + disabled, + dataset: { + blacklist: key + } + }); + if (disabled) { + inp.readOnly = true; + const o = make('option', '', { + value: type, + textContent: type + }); + selType.append(o); + } else { + for (const selV of ['String', 'RegExp', 'Object']) { + const o = make('option', '', { + value: selV, + textContent: selV + }); + selType.append(o); + } + } + selType.value = type; + lb.append(inp, selType); + sections.blacklist.append(lb); + }; + // const blacklist = make('textarea', '', { + // dataset: { + // name: 'blacklist' + // }, + // rows: '10', + // autocomplete: false, + // spellcheck: false, + // wrap: 'soft', + // value: JSON.stringify(cfg.blacklist, null, ' '), + // oninput(evt) { + // let isvalid = true; + // try { + // cfg.blacklist = JSON.parse(evt.target.value); + // isvalid = true; + // } catch (ex) { + // err(ex); + // isvalid = false; + // } finally { + // if (isvalid) { + // dom.cl.remove(evt.target, 'mujs-invalid'); + // dom.prop(savebtn, 'disabled', false); + // } else { + // dom.cl.add(evt.target, 'mujs-invalid'); + // dom.prop(savebtn, 'disabled', true); + // } + // } + // } + // }); + // cfgMap.set('blacklist', blacklist); + // const addList = make('mujs-add', '', { + // textContent: '+', + // dataset: { + // command: 'new-list' + // } + // }); + // const n = make('input', '', { + // type: 'text', + // defaultValue: '', + // value: '', + // placeholder: 'Name', + // }); + // const inpValue = make('input', '', { + // type: 'text', + // defaultValue: '', + // value: '', + // placeholder: 'Value', + // }); + // const label = make('label', 'new-list hidden', { + // dataset: { + // blacklist: 'new-list' + // } + // }); + // label.append(n, inpValue, addList); + // listSec.append(label); + // ael(addList, 'click', () => { + // if (isEmpty(n.value) || isEmpty(inpValue.value)) { + // return + // }; + // createList(n.value, n.value, inpValue.value); + // }); + + for (const key of cfg.blacklist) { + createList(key); } - }); - const selType = make('select', '', { - disabled, - dataset: { - blacklist: key + + cbtn.append(resetbtn, savebtn); + cfgpage.append(cbtn); + }; + // #endregion + container.tab.custom = (host) => { + MUList.host = host; + respHandles.build(); + }; + ael(mainframe, 'mouseenter', (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + evt.target.style.opacity = container.opacityMax; + frameTimeout.clear(...frameTimeout.ids); + }); + ael(mainframe, 'mouseleave', (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + evt.target.style.opacity = container.opacityMin; + timeoutFrame(); + }); + ael(mainframe, 'click', (evt) => { + evt.preventDefault(); + frameTimeout.clear(...frameTimeout.ids); + dom.cl.remove(main, 'hidden'); + dom.cl.add(mainframe, 'hidden'); + if (cfg.autoexpand) { + dom.cl.add([btnfullscreen, main], 'expanded'); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); } - }); - if (disabled) { - inp.readOnly = true; - const o = make('option', '', { - value: type, - textContent: type - }); - selType.append(o); - } else { - for (const selV of ['String', 'RegExp', 'Object']) { - const o = make('option', '', { - value: selV, - textContent: selV - }); - selType.append(o); + if (dom.cl.has(mainframe, 'error')) { + tab.create('mujs:settings'); } - } - selType.value = type; - lb.append(inp, selType); - sections.blacklist.append(lb); - }; - // const blacklist = make('textarea', '', { - // dataset: { - // name: 'blacklist' - // }, - // rows: '10', - // autocomplete: false, - // spellcheck: false, - // wrap: 'soft', - // value: JSON.stringify(cfg.blacklist, null, ' '), - // oninput(evt) { - // let isvalid = true; - // try { - // cfg.blacklist = JSON.parse(evt.target.value); - // isvalid = true; - // } catch (ex) { - // err(ex); - // isvalid = false; - // } finally { - // if (isvalid) { - // dom.cl.remove(evt.target, 'mujs-invalid'); - // dom.prop(savebtn, 'disabled', false); - // } else { - // dom.cl.add(evt.target, 'mujs-invalid'); - // dom.prop(savebtn, 'disabled', true); - // } - // } - // } - // }); - // cfgMap.set('blacklist', blacklist); - // const addList = make('mujs-add', '', { - // textContent: '+', - // dataset: { - // command: 'new-list' - // } - // }); - // const n = make('input', '', { - // type: 'text', - // defaultValue: '', - // value: '', - // placeholder: 'Name', - // }); - // const inpValue = make('input', '', { - // type: 'text', - // defaultValue: '', - // value: '', - // placeholder: 'Value', - // }); - // const label = make('label', 'new-list hidden', { - // dataset: { - // blacklist: 'new-list' - // } - // }); - // label.append(n, inpValue, addList); - // listSec.append(label); - // ael(addList, 'click', () => { - // if (isEmpty(n.value) || isEmpty(inpValue.value)) { - // return - // }; - // createList(n.value, n.value, inpValue.value); - // }); - - for (const key of cfg.blacklist) { - createList(key); - } - - cbtn.append(resetbtn, savebtn); - cfgpage.append(cbtn); - }; - // #endregion - container.tab.custom = (host) => { - MUList.host = host; - respHandles.build(); - }; - ael(mainframe, 'mouseenter', (evt) => { - evt.preventDefault(); - evt.stopPropagation(); - evt.target.style.opacity = container.opacityMax; - frameTimeout.clear(...frameTimeout.ids); - }); - ael(mainframe, 'mouseleave', (evt) => { - evt.preventDefault(); - evt.stopPropagation(); - evt.target.style.opacity = container.opacityMin; - timeoutFrame(); - }); - ael(mainframe, 'click', (evt) => { - evt.preventDefault(); - frameTimeout.clear(...frameTimeout.ids); - dom.cl.remove(main, 'hidden'); - dom.cl.add(mainframe, 'hidden'); - if (cfg.autoexpand) { - dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); - } - if (dom.cl.has(mainframe, 'error')) { - tab.create('mujs:settings'); - } - }); - ael(urlBar, 'input', (evt) => { - evt.preventDefault(); - if (urlBar.placeholder === i18n$('newTab')) { - return; - } - /** + }); + ael(urlBar, 'input', (evt) => { + evt.preventDefault(); + if (urlBar.placeholder === i18n$('newTab')) { + return; + } + /** * @type { string } */ - const val = evt.target.value; - if (isEmpty(val)) { - dom.cl.remove(qsA('tr[data-engine]', tabbody), 'hidden'); - dom.cl.remove(qsA('mujs-section[data-name]', cfgpage), 'hidden'); - return; - } - const finds = new Set(); - if (!dom.cl.has(cfgpage, 'hidden')) { - const reg = new RegExp(val, 'gi'); - for (const elem of qsA('mujs-section[data-name]', cfgpage)) { - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; + const val = evt.target.value; + if (isEmpty(val)) { + dom.cl.remove(qsA('tr[data-engine]', tabbody), 'hidden'); + dom.cl.remove(qsA('mujs-section[data-name]', cfgpage), 'hidden'); + return; } - if (elem.dataset.name.match(reg)) { - finds.add(elem); + const finds = new Set(); + if (!dom.cl.has(cfgpage, 'hidden')) { + const reg = new RegExp(val, 'gi'); + for (const elem of qsA('mujs-section[data-name]', cfgpage)) { + if (!isElem(elem)) { + continue; + } + if (finds.has(elem)) { + continue; + } + if (elem.dataset.name.match(reg)) { + finds.add(elem); + } + } + dom.cl.add(qsA('mujs-section[data-name]', cfgpage), 'hidden'); + dom.cl.remove([...finds], 'hidden'); + return; } - } - dom.cl.add(qsA('mujs-section[data-name]', cfgpage), 'hidden'); - dom.cl.remove([...finds], 'hidden'); - return; - } - /** + /** * @param {RegExpMatchArray} regExp * @param {keyof import("../typings/types.d.ts").GSForkQuery} key */ - const ezQuery = (regExp, key) => { - const q_value = val.replace(regExp, ''); - const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - let k = v[key]; - if (typeof k === 'number') { - k = `${v[key]}`; - } - if (k && k.match(reg)) { - finds.add(elem); - } - } - }; - if (val.match(/^(code_url|url):/)) { - ezQuery(/^(code_url|url):/, 'code_url'); - } else if (val.match(/^(author|users?):/)) { - const q_value = val.replace(/^(author|users?):/, ''); - const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.users) { - for (const user of v.users) { - for (const value of Object.values(user)) { - if (typeof value === 'string' && value.match(reg)) { - finds.add(elem); - } else if (typeof value === 'number' && `${value}`.match(reg)) { - finds.add(elem); - } + const ezQuery = (regExp, key) => { + const q_value = val.replace(regExp, ''); + const reg = new RegExp(q_value, 'gi'); + for (const v of userjsCache.values()) { + const elem = v._mujs.root; + if (!isElem(elem)) { + continue; + } + if (finds.has(elem)) { + continue; + } + let k = v[key]; + if (typeof k === 'number') { + k = `${v[key]}`; + } + if (k && k.match(reg)) { + finds.add(elem); + } } - } - } - } - } else if (val.match(/^(locale|i18n):/)) { - ezQuery(/^(locale|i18n):/, 'locale'); - } else if (val.match(/^id:/)) { - ezQuery(/^id:/, 'id'); - } else if (val.match(/^license:/)) { - ezQuery(/^license:/, 'license'); - } else if (val.match(/^name:/)) { - ezQuery(/^name:/, 'name'); - } else if (val.match(/^description:/)) { - ezQuery(/^description:/, 'description'); - } else if (val.match(/^(search_engine|engine):/)) { - const q_value = val.replace(/^(search_engine|engine):/, ''); - const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v._mujs.info.engine) { - for (const value of Object.values(v._mujs.info.engine)) { - if (typeof value === 'string' && value.match(reg)) { - finds.add(elem); + }; + if (val.match(/^(code_url|url):/)) { + ezQuery(/^(code_url|url):/, 'code_url'); + } else if (val.match(/^(author|users?):/)) { + const q_value = val.replace(/^(author|users?):/, ''); + const reg = new RegExp(q_value, 'gi'); + for (const v of userjsCache.values()) { + const elem = v._mujs.root; + if (!isElem(elem)) { + continue; + } + if (finds.has(elem)) { + continue; + } + if (v.users) { + for (const user of v.users) { + for (const value of Object.values(user)) { + if (typeof value === 'string' && value.match(reg)) { + finds.add(elem); + } else if (typeof value === 'number' && `${value}`.match(reg)) { + finds.add(elem); + } + } + } + } } - } - } - } - } else if (val.match(/^filter:/)) { - const parts = /^\w+:(.+)/.exec(val); - if (parts) { - const bsFilter = loadFilters(); - const p = parts[1].trim().toLocaleLowerCase(); - for (const [key, value] of Object.entries(cfg.filters)) { - const k = key.trim().toLocaleLowerCase(); - const v = value.name.trim().toLocaleLowerCase(); - if (p.includes(k) || p.includes(v)) { - const reg = bsFilter.get(value.name); - if (reg) { - [...userjsCache.values()].filter(({ name, users, _mujs }) => { - const elem = _mujs.root; + } else if (val.match(/^(locale|i18n):/)) { + ezQuery(/^(locale|i18n):/, 'locale'); + } else if (val.match(/^id:/)) { + ezQuery(/^id:/, 'id'); + } else if (val.match(/^license:/)) { + ezQuery(/^license:/, 'license'); + } else if (val.match(/^name:/)) { + ezQuery(/^name:/, 'name'); + } else if (val.match(/^description:/)) { + ezQuery(/^description:/, 'description'); + } else if (val.match(/^(search_engine|engine):/)) { + const q_value = val.replace(/^(search_engine|engine):/, ''); + const reg = new RegExp(q_value, 'gi'); + for (const v of userjsCache.values()) { + const elem = v._mujs.root; if (!isElem(elem)) { - return false; + continue; } if (finds.has(elem)) { - return false; + continue; } - const strArr = [{ name }, ...users]; - const v = strArr.find((o) => o.name.match(reg)); - if (v) { - return false; + if (v._mujs.info.engine) { + for (const value of Object.values(v._mujs.info.engine)) { + if (typeof value === 'string' && value.match(reg)) { + finds.add(elem); + } + } } - finds.add(elem); - return true; - }); } - } - } - } - } else if (val.match(/^recommend:/)) { - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.users.find(u => u.id === authorID)) { - finds.add(elem); - } else if (goodUserJS.includes(v.url)) { - finds.add(elem); - } else if (goodUserJS.includes(v.id)) { - finds.add(elem); - } - } - } else { - const reg = new RegExp(val, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.name && v.name.match(reg)) { - finds.add(elem); - } - if (v.description && v.description.match(reg)) { - finds.add(elem); - } - const code_data = v._mujs.code.data; - if (code_data) { - const code_obj = new ParseUserJS(code_data); - const meta = code_obj.parse_meta(code_data); - for (const key of Object.keys(meta)) { - if (/name|desc/i.test(key) && key.match(reg)) { - finds.add(elem); + } else if (val.match(/^filter:/)) { + const parts = /^\w+:(.+)/.exec(val); + if (parts) { + const bsFilter = loadFilters(); + const p = parts[1].trim().toLocaleLowerCase(); + for (const [key, value] of Object.entries(cfg.filters)) { + const k = key.trim().toLocaleLowerCase(); + const v = value.name.trim().toLocaleLowerCase(); + if (p.includes(k) || p.includes(v)) { + const reg = bsFilter.get(value.name); + if (reg) { + [...userjsCache.values()].filter(({ name, users, _mujs }) => { + const elem = _mujs.root; + if (!isElem(elem)) { + return false; + } + if (finds.has(elem)) { + return false; + } + const strArr = [{ name }, ...users]; + const v = strArr.find((o) => o.name.match(reg)); + if (v) { + return false; + } + finds.add(elem); + return true; + }); + } + } + } + } + } else if (val.match(/^recommend:/)) { + for (const v of userjsCache.values()) { + const elem = v._mujs.root; + if (!isElem(elem)) { + continue; + } + if (finds.has(elem)) { + continue; + } + if (v.users.find(u => u.id === authorID)) { + finds.add(elem); + } else if (goodUserJS.includes(v.url)) { + finds.add(elem); + } else if (goodUserJS.includes(v.id)) { + finds.add(elem); + } } - } - } - } - } - dom.cl.add(qsA('tr[data-engine]', tabbody), 'hidden'); - dom.cl.remove([...finds], 'hidden'); - }); - ael(urlBar, 'change', (evt) => { - evt.preventDefault(); - const val = evt.target.value; - if (urlBar.placeholder === i18n$('newTab') && qs('mujs-tab.active', toolbar)) { - const tabElem = qs('mujs-tab.active', toolbar); - const tabHost = qs('mujs-host', tabElem); - if (val.startsWith(tab.protocal)) { - tab.close(tabElem); - if (tab.hasTab(val)) { - tab.active(tab.Tab.get(val)); } else { - tab.create(val); + const reg = new RegExp(val, 'gi'); + for (const v of userjsCache.values()) { + const elem = v._mujs.root; + if (!isElem(elem)) { + continue; + } + if (finds.has(elem)) { + continue; + } + if (v.name && v.name.match(reg)) { + finds.add(elem); + } + if (v.description && v.description.match(reg)) { + finds.add(elem); + } + const code_data = v._mujs.code.data; + if (code_data) { + const code_obj = new ParseUserJS(code_data); + const meta = code_obj.parse_meta(code_data); + for (const key of Object.keys(meta)) { + if (/name|desc/i.test(key) && key.match(reg)) { + finds.add(elem); + } + } + } + } } - return; - } else if (val === '*') { - tabElem.dataset.host = val; - tabHost.title = ''; - tabHost.textContent = ''; - MUList.host = val; - respHandles.build(); - return; - } - const value = container.getHost(val); - if (container.checkBlacklist(value)) { - showError(`Blacklisted "${value}"`); - return; - } - tabElem.dataset.host = value; - tabHost.title = value; - tabHost.textContent = value; - MUList.host = value; - respHandles.build(); - return; - } - }); - scheduler.postTask(makecfg, { priority: 'background' }); + dom.cl.add(qsA('tr[data-engine]', tabbody), 'hidden'); + dom.cl.remove([...finds], 'hidden'); + }); + ael(urlBar, 'change', (evt) => { + evt.preventDefault(); + const val = evt.target.value; + if (urlBar.placeholder === i18n$('newTab') && qs('mujs-tab.active', toolbar)) { + const tabElem = qs('mujs-tab.active', toolbar); + const tabHost = qs('mujs-host', tabElem); + if (val.startsWith(tab.protocal)) { + tab.close(tabElem); + if (tab.hasTab(val)) { + tab.active(tab.Tab.get(val)); + } else { + tab.create(val); + } + return; + } else if (val === '*') { + tabElem.dataset.host = val; + tabHost.title = ''; + tabHost.textContent = ''; + MUList.host = val; + respHandles.build(); + return; + } + const value = container.getHost(val); + if (container.checkBlacklist(value)) { + showError(`Blacklisted "${value}"`); + return; + } + tabElem.dataset.host = value; + tabHost.title = value; + tabHost.textContent = value; + MUList.host = value; + respHandles.build(); + return; + } + }); + scheduler.postTask(makecfg, { priority: 'background' }); - respHandles.build = async () => { - const time = await scheduler.postTask(MUList.build, { priority: 'background' }); - return timeoutFrame(time); - }; + respHandles.build = async () => { + const time = await scheduler.postTask(MUList.build, { priority: 'background' }); + return timeoutFrame(time); + }; - if (cfg.autofetch) { - respHandles.build(); - } - dbg('Container', container); + if (cfg.autofetch) { + respHandles.build(); + } + dbg('Container', container); } catch (ex) { - err(ex); - container.remove(); + err(ex); + container.remove(); } - return respHandles; + return respHandles; } -// #endregion -/** + // #endregion + /** * @template { Function } F * @param { (this: F, doc: Document) => * } onDomReady */ -const loadDOM = (onDomReady) => { - if (!isFN(onDomReady)) { - return; - } - if (document.readyState === 'interactive' || document.readyState === 'complete') { - onDomReady(document); - return; - } - document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), { - once: true - }); -}; - -const init = async () => { - const stored = await StorageSystem.getValue('Config', DEFAULT_CONFIG); - cfg = { - ...DEFAULT_CONFIG, - ...stored - }; - info('Config:', cfg); - loadDOM((doc) => { - try { - if (window.location === null) { - throw new Error('"window.location" is null, reload the webpage or use a different one', { - cause: 'loadDOM' - }); - } - if (doc === null) { - throw new Error('"doc" is null, reload the webpage or use a different one', { - cause: 'loadDOM' + const loadDOM = (onDomReady) => { + if (!isFN(onDomReady)) { + return; + } + if (document.readyState === 'interactive' || document.readyState === 'complete') { + onDomReady(document); + return; + } + document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), { + once: true }); - } - trustedTypes.init(); - sleazyRedirect(); + }; + + const init = async () => { + const stored = await StorageSystem.getValue('Config', DEFAULT_CONFIG); + cfg = { + ...DEFAULT_CONFIG, + ...stored + }; + info('Config:', cfg); + loadDOM((doc) => { + try { + if (window.location === null) { + throw new Error('"window.location" is null, reload the webpage or use a different one', { + cause: 'loadDOM' + }); + } + if (doc === null) { + throw new Error('"doc" is null, reload the webpage or use a different one', { + cause: 'loadDOM' + }); + } + trustedTypes.init(); + sleazyRedirect(); - if (cfg.autoinject) container.inject(primaryFN, doc); + if (cfg.autoinject) container.inject(primaryFN, doc); - Command.register(i18n$('userjs_inject'), () => { - container.inject(primaryFN, doc); - }); - Command.register(i18n$('userjs_close'), () => { - container.remove(); - }); - } catch (ex) { - err(ex); - } - }); -}; -init(); + Command.register(i18n$('userjs_inject'), () => { + container.inject(primaryFN, doc); + }); + Command.register(i18n$('userjs_close'), () => { + container.remove(); + }); + } catch (ex) { + err(ex); + } + }); + }; + init(); -})(); \ No newline at end of file +})();