Magic Userscript+ : Show Site All UserJS

Finds available userscripts for the current webpage.

As of 10.03.2025. See ბოლო ვერსია.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

  1. // ==UserScript==
  2. // @version 7.6.1
  3. // @name Magic Userscript+ : Show Site All UserJS
  4. // @name:ar Magic Userscript+: عرض جميع ملفات UserJS
  5. // @name:de Magic Userscript+ : Website anzeigen Alle UserJS
  6. // @name:es Magic Userscript+: Mostrar sitio todos los UserJS
  7. // @name:fr Magic Userscript+ : Afficher le site Tous les UserJS
  8. // @name:ja Magic Userscript+ : サイトをすべて表示 UserJS
  9. // @name:nl Magic Userscript+: Site alle UserJS tonen
  10. // @name:pl Magic Userscript+ : Pokaż witrynę Wszystkie UserJS
  11. // @name:ru Magic Userscript+: показать сайт всем UserJS
  12. // @name:zh Magic Userscript+ :显示站点所有 UserJS
  13. // @name:zh-CN Magic Userscript+ :显示站点所有 UserJS
  14. // @name:zh-TW Magic Userscript+ :显示站点所有 UserJS
  15. // @description Finds available userscripts for the current webpage.
  16. // @description:ar يبحث عن نصوص المستخدمين المتاحة لصفحة الويب الحالية.
  17. // @description:de Findet verfügbare Benutzerskripte für die aktuelle Webseite.
  18. // @description:es Busca los usercripts disponibles para la página web actual.
  19. // @description:fr Recherche les userscripts disponibles pour la page web en cours.
  20. // @description:ja 現在のウェブページで利用可能なユーザスクリプトを検索します。
  21. // @description:nl Zoekt beschikbare gebruikerscripts voor de huidige webpagina.
  22. // @description:pl Wyszukuje dostępne skrypty użytkownika dla bieżącej strony internetowej.
  23. // @description:ru Находит доступные юзерскрипты для текущей веб-страницы.
  24. // @description:zh 为当前网页查找可用的用户脚本。
  25. // @description:zh-CN 为当前网页查找可用的用户脚本。
  26. // @description:zh-TW 为当前网页查找可用的用户脚本。
  27. // @author Magic <magicoflolis@tuta.io>
  28. // @supportURL https://github.com/magicoflolis/Userscript-Plus/issues
  29. // @namespace https://github.com/magicoflolis/Userscript-Plus
  30. // @homepageURL https://github.com/magicoflolis/Userscript-Plus
  31. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV8TRZFKBzuIOGSoTnZREcGlVLEIFkpboVUHk0u/oElDkuLiKLgWHPxYrDq4OOvq4CoIgh8gzg5Oii5S4v+SQosYD4778e7e4+4dIDSrTLN6YoCm22Y6EZdy+VWp7xUigghhDqLMLCOZWczCd3zdI8DXuyjP8j/35xhUCxYDAhJxjBmmTbxBPLNpG5z3icOsLKvE58QTJl2Q+JHrisdvnEsuCzwzbGbT88RhYqnUxUoXs7KpEU8TR1RNp3wh57HKeYuzVq2z9j35C4MFfSXDdZqjSGAJSaQgQUEdFVRhI0qrToqFNO3Hffwjrj9FLoVcFTByLKAGDbLrB/+D391axalJLykYB3pfHOdjDOjbBVoNx/k+dpzWCSA+A1d6x19rArOfpDc6WuQICG0DF9cdTdkDLneA4SdDNmVXEmkKxSLwfkbflAeGboGBNa+39j5OH4AsdbV8AxwcAuMlyl73eXd/d2//nmn39wOjunK6jS33SAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+gDDBAAJWyXgRAAABRPSURBVHjazVt9VNRl9v9854VhhjdBiHcUaFQEDTVCdk1IstTV0jb1LLZkJZrl4knLTnZaKjdqdXdLXcvfyoHUErPUBBEMLREJ6egJFBUUBF9WMd6R4WW+M/P5/eHMdxkYEJS0e8498MzzzPN87537PM+9n3u/An49kgFwA+AJIACAP4AxAEIA+AFwBzDEPLYJQB2AqwDOATgN4AqAywBubNu2rTk+Pl4EAJIyABAEwTQYDyn8CoIrAEQD+BOAMLPgLgAcAGDs2LGQyWSora1FYGAg6urqUFZWBgCIiYnBqVOn0NDQAAA6T0/Pm/X19ZflcvkZo9G4w2AwHAFgwG+Q5ACCg4KClixZsuT82rVrTTt27CCAHpybm8vq6moWFRXRZDKxpqaGkyZN4q0fl3zyySelsaIoMiYmhg899BD/9re/mQAUAogHEGxe8/5TaGiob0RERMrSpUuvZ2Zmmo4cOcIjR44wLy/PpgIOHjzI2tpaTp06lVqtlhUVFUxLS5MU8MQTT0hjLQqZPHkySRIAFy1aZAwNDa0C8C/z9rpvNBbAPwBctyVob3zw4EEWFBRQJpNJFvH999/3WwE5OTncs2ePZUwVgDjzWXPHB9Wd0GsACuRy+UoXFxev5cuXo7OzEyStWBBsHzGiKMJkunWGmUwmyGQy6X+lUgkAcHBw6HXxoUOHWv4dDiAFwNcAJtzpgTUQUgNYIgjCP2bNmiX7+9//jo0bN2Lz5s3IyspCUFCQJIz5xB7Q5FVVVZg9ezby8vKwYsWKgTzTFACZAFYC+BZA+6ArQKlUOouiuAZAgiAIMq1WixEjRuCjjz6Cq6srPvzwQ1RUVNze5GQyKyV1bW/atAnJycmIj49Henr6/66qLpbUdbzRaMS0adOQm5sLAN4A/g/ARADvAGgZvCNeLvdUq9VbzVcQAXDo0KHcs2cPDQYD29ramJiYSKVSedszICIigpGRkTbb9vb2nDRpEmNiYujk5MQZM2bQ3d2dbm5unDFjhtX4qKgonj59mt7e3t3XEAH82+xnDAp5AThjSxgPDw9++eWXNJlMbGpq4htvvCEdbr82r1ixgr/73e966zeaz4W7VoIzgAyZTMaoqChOnTqVarW6hxIyMzNpMBio0+m4ZMmSflnCPWCj2RKc71R4NYD1AIwODg4sKipiU1MTV69e3UNAT09Pfv311yTJxsZGLl++/J5Zwm1YNMugvhMFxAFoA0CFQsH33nuPHR0dbGtr49KlS6lSqawW8/LyYnZ2Ng0GA2/evMkXXniBCoXit6CEFrNbPiB6SalUtncVwNnZmcnJySTJlpYWvv7665TL5VaL+fj4cN++fSTJ+vp6vvzyy31agrOzc1/7eMAcFRXFIUOG2Oq7NhA/YaRcLr85bNgwuri4WE2kUqn48ccfs7OzkzqdjgkJCT0swdfXl7m5uTQajWxubuaCBQskRYWGhjIrK4uCIBAAY2JiSFJq3y2T5PTp03vrP9wfj1EA8PkjjzzC8+fPMysriz4+PlYTubi4cN26dSTJ5uZmm/vdz8+PBw4cIEnW1tbyxRdfpCAIDA4OpsFgsBo7WML3Y64287buk7x9fX07oqOj2dLSQpPJxMzMzB5KUKvV3LRpE/V6PW/evMmFCxfSzs7Oaoy/vz9/+OEHGo1GNjY2cu7cudRoNExNTeWePXuYkZHBsLAw7t+/n4IgUBAEPv/889y+fTu/+OILvvDCC5JAGRkZjIiI4OrVq/nll19aRYxdOSMjg+PHjycAJiUl8cMPP+TMmTO5Y8cOzpo1yxI7ePYV0v5rzpw51Gg0/Mtf/kKdTkeDwcCcnBx6enpaLebq6sr169dLJ7+t/a7Vann+/HmS5I0bN7hgwQK6urpy1apVLCwslLaASqViSkoKW1tbWVJSwuLiYt68eZNpaWlUKpUkyR9//JE///wzL126xOrqagYFBdncAhblZGdns66ujoWFhTx16hQ7Ozvp7+9PcxRpM5QOBlBu2cdOTk5877332NraSpPJxN27d9PLy8tqQQcHB27ZsoWiKLKlpYULFiyQrkhHR0e+++671Ov1tFB9fT1nz54tKSo6OpokOWbMGDY0NPC1116jWq2mvb09ly9fzsbGRoaHh5MkP/nkE9rb23PMmDESTnA7BVy4cIHBwcF0cXHhTz/9xH/+8580yxhsSwHx5nuT0dHRTEpKokql4sqVK9ne3k5RFLl//356eHhYLerm5sbPPvtMEvDFF1+kvb09165dy/b2dnan69ev86mnnqIgCJJwlnC366Hr7OxMk8nE2NhYkpRcYVvASW8KyM/Pl/qys7OZk5Nj8Q3ibQVFhZbBjz32GHU6HQFQqVQyOTmZOp2ORqOR6enpPZTg5OTErVu30mAwsKmpiSkpKezs7GRvdO7cOXp7e9PHx4cGg4GRkZHs7Ozkww8/LM05fvx4dnR0cNKkST0E7q8Cjh49aqUAC0gjCEJh90Aw1uw6SgqwABAWc37rrbfY0dFBURS5d+9eurm5WS3u7u7OlJQU9of0ej2fffZZOjk5UafT0dPTkydOnGBpaSmfeeYZzpkzh6dOnWJxcTG9vLwGTQGWtqOjo9EsM2Rm/lNXcKQ7kOHs7IwNGzZg48aNEEURTz31FD7++OOuwATa2tpw/vx5GAy3xyyVSiV8fX1hMplgMplw48YNvPTSS7h48SJ27NiB9PR0XL58GQsXLkRNTY3t+7oXsMXyeV9h9/z5861kdgdwvPt92tULtFxTarWaSUlJ1Ov11Ov13LVrF4cMGUKlUsl33nmHOp2uXxYgiiLnz59Pi5ttWUcmk1GhUFChUFjdKAqFwuqO79629blcLrfyVLu2zX+PW6LFUAD/7a+zoVKp+Mknn7C9vZ0Gg4Fbtmzh66+/zo6ODvaXzp07R19f3/sdI/zXLDumA2gdyJcdHByYnJxMURSp1+v7PPC6k8lk4ubNm38LIXMrgOlyM572zEAiJVEUUVBQAHd3d4wbNw4qlar/mRhBwKhRo3DlyhWcPXtWAkfvA9kBOCoHsBDAIwP9tsFgwNGjR6HRaBAZGWl14Nx2ZTs7PProo6irq0NxcfGAwdNBpEsAcOhOzSgoKIhqtZr/+c9/BnQGWKilpYVxcXE2cYO3336b77777q+9DQ4BQNmdfDk0NJS5ubm0s7Ojs7Mz169fT6PRaHPP90W//PIL4+Pje8QRmzZtYkpKyqAIGh0dzRMnTrC5uZm7du2in5+fpa8M5qzsgCaUyWRMT0+3+NbS7fDBBx+wvLyc9fX1rK2t5cmTJ/nSSy/xq6++oiiKvSqhqamJc+fO/VUQpMDAQNbW1jI9PZ0JCQksLy/nd999R3t7ewKow7fffmuqq6tjWlqapBlRFBkfH8+srCzJJe4e6lZUVEggaVlZGbVaLRUKBYOCgjhp0iRGRkbS29ubgiDQ3d2d27dv79MSampqOH/+fOke379/Pw8fPmxz24WHh9vkBx98sMf41atXs7KykgEBAQTAWbNmsba2lsHBwQRgwLx584wrVqxgdXU18/PzOXToUJJkVVUVk5KSGBcX12PShx9+WHJhAXDt2rVWgYeDgwMfe+wxenh48NVXX6W/vz/d3Ny4Z8+ePi2hsbGRs2fPplwuZ0JCAg8dOtRj7cOHD/f6/cLCwh7j09PTWVhYKDlBTk5OFEWR4eHhtKTa6yzwVFtbGydOnEiSfOutt3pFWCzRm+UunzBhAltaWjh69GgC4LJly5iXl0dBEOjm5sbg4GA6OTnxgQce4M6dO/u0hGvXrtGCSSQkJPRYWy6XS95id+6OUQJgZmam1Y8jCAJJWhRQpzArYOjJkyehVquhVt9CkH/++ederyeDwQCDwQCVSgVRFFFWVobS0lIkJCRg5cqVWLFiBdasWQMPDw9s2bIFTzzxBGpraxEXF4dly5ZBo9FgxowZkMt74hLe3t5ISUnB888/j23btvXof+ONNzB27Fibz1VWVob333/f6rOGhgZ4enrCzs4Oer0e7u7u0Ov1aGtrA4A6mUKh+K9SqcTTTz+NlpYWNDc3S0L2Ro2NjWhoaEBQUBAAQKfTYdeuXZgyZQoWL14MOzs7HDt2DFqtFjNnzsTcuXMRFhYGPz8/hIaGYvHixdi3b1+v87u5uWHz5s2IjY3t0Tds2DCEhoba5MDAwB7ji4qK4OPjg5CQEMhkMsyZMwcNDQ1obGwEgKtITEzcn5mZyaamJn7zzTfUaDQkyccff7zXk9XR0ZGFhYVctmyZ9NmQIUN45coV3rhxg/v27aNcLmd4eDibm5uZm5vLvXv3sqCggBcvXpSSKdnZ2TavTgvV1tbyySefvKski6urK8vLy1lSUsLdu3ezvr6eqampljk3YsGCBe/n5+fzzTfflCDu0tJSTpw4sc+Jly5dypycHKt9l5SUxNLSUgmalsvlXLp0KY8dO8ZPP/2U0dHRPHPmDAEwODiYPj4+zMjI6PNMuHTpUp8/Rn94+PDh/OKLL1hUVMQ1a9ZIcsrl8iV3FAxZMrm7d+9mWFjYXT2ct7e3lEfo64qMjY0d7HRbq6+v79MDDod7i7/vhv39/Zmdnd2nJVRVVXHy5MmDGg4LghBqExC5H+zv788jR4706Tpfu3aN0dHRg2UJEiAiM9fZ3PdE5vDhw5mbm9unJVRUVDAqKmow1kvpCgNagaL3WwnHjh3r0xKuXLnC3//+93ez/SRQ1AoWDwwM5PDhwwc84eTJkymKIgEwICCAw4YNs+rXaDSSlwiAo0ePpoODQ6/toKAgHjlypE9LKC8vt4LRB8g9YHFoNJqF5eXlpr/+9a8DntCS4rL46l1dz64wu+UX6w/MrdVqefz48T4tobq6mpGRkQO1BKvEiKwLrJ2/bt26ms8++wwAEBAQgGHDhsHNzQ0jRoyAp+etnKK7uztGjBgBb29vm16c0Wjs4UVaYC+S8Pf3BwD4+fkhJCQEo0aNAgD4+/sjJCQEDg4OCAwMRGVlJf785z/jxx9/7NVjDAgIQFpamuQay2Qy+Pj4YOTIkdBqtXB1dZVgOA8PD/j6+kIQhIsACmwmR11cXDZYNPX999+zsrJSMsWysjJOmTKFRUVF0rVkMeuuFvDdd99JlZ8WtmR3ANzWtKdNm0aSfPPNNxkUFMSQkBCeOHHitgfjhAkTqFarmZKSQpPJRKPRyIKCAoaEhDA0NJR5eXkcNWpUn8lRmFPHVZYS1oaGBs6ZM4djx45ldXU1q6urrdobN260qQCj0cj29naJLaixIAi0s7MjSc6cOVNKhHZty2QykuSZM2c4cuRIqlQqjho1isePH+8TaT59+jRHjx5NjUZDZ2dnhoSEMD8/n3v37uVXX33FxMREm+nx7oWSNwC8bb4i1GfPnkVGRgaMRiMqKythb29v1X7wwQdtavHChQv46KOPJKB05MiRWLVqFQBAr9dLf9vb/1fQ2b29bt06lJeXAwCuXr2KxYsXY9u2bXjooYdsIs1hYWHYu3cv5s2bh6qqKjz99NPo6OjA1KlTcfHiRSQkJLSbZbtxu1rhHACFDQ0N0Ov1MBqN0j7u2jYajbC3t7epgOvXr+Pzzz9HamoqUlNTceDAgQHDtR0dHdL/EyZMQGtrK5577jmcPHmy1+9otVps2bIFSqUSxcXF8PLywoULF/D++++jpaWl0CzbbYulGwCsysrKaureMVjwdUdHBwICAnptA8DLL78MX19feHt7IykpCYmJiaioqMCSJUtQWlraa84hIiICr732GnJychAbG4tFixbhm2++uQ5glVm2flWLn9y5c+erhw8f7uwtIdlbctJWX/f2gQMHsGHDBnR0dCA2NrZHGwBu3ryJkpISVFZWIiwsDPv378fIkSNRX1+P5557DiUlJb2uHx0dDZlMhk2bNuHSpUut5iLqkwMulHR0dPy3pWhixowZ/MMf/iCd7NOnT5fa3t7eXLx4cY/Pu0Z8ln4AfOCBBzhv3jwmJCTQz8/Pqu3r6yv5BY8//jgXLlzIRx55hDKZjF5eXlKhxMSJE3nu3Dmbh+LWrVspCAI1Gs1dFUpaSmX/fa/d5O4vTvTG48ePZ3FxsZXwlZWVluvOBODTuymVtZC7ufDYeC8VMG3atH6NHTFiBNPS0piXl8ddu3Z19QwPDGbFuLvZEsR7oQCNRmMT4e2NlUolhwwZYkl2mABsHUzhu26H9ebaW/5GWXe3FeL9qSD/k7n29p4K5+Pjc7uiimvmZ1PjHtAEc+1t271SwKFDh1hQUGAVLU6dOpX+/v7t5me5Jy9NSX4CgLkApgH4ALfe3vpVyWAwQBRFAMCUKVNgNBrxww8/VBsMhrfNHl7DHb0OdBfP1I5b7/juBCAPDg72GjdunLOXl5csMTERoaGhOH36NERRhEajQVxcHOLj4zF58mSIoojLly9Dq9UiOTkZJ06cwMqVK/Hoo4/i7NmzaGtrg4ODA1555RU8++yz0Ov1GDduHI4ePYqSkhJDfX191blz57aaTKYXzNheO+4zyZVK5YMajWahq6vrT4sWLTLl5eUxNTWVLi4uzMnJYW1tLauqqnj58mXW1dVx1qxZUqlsVlYWq6qq2Nrays2bN1OlUnH79u2sqqriwYMH+corr9DLy8uoUCiOC4Lw23p1tpdtFatSqVLHjx9fEhUV9curr77aYYHaVCoVk5KSWFRUxJiYGOr1es6bN49yuZxxcXFcv349PTw8+Mc//rHd09PzuvkXTjFjeIrBflihl/2mkMlkTgAUwi1HXiDZaPbpXVtbW39xcnKiOUByNcf6wi23X2gAgPj4eHl4ePiI4uJij5qaGv+Wlhbftra2EL1erzWZTN4k3a5everk4+ODixcvNpGs02g01wIDA6uvXr16IiIioik/P/9UZ2dnjZ+fX8OVK1dczOuZBEGQ63S6RkdHR5vRGUlPk8nUJJfLOy1tAI2CIOi7j/1/l0eTL0xHMHkAAAAASUVORK5CYII=
  32. // @license MIT
  33. // @compatible chrome
  34. // @compatible firefox
  35. // @compatible edge
  36. // @compatible opera
  37. // @compatible safari
  38. // @connect greatest.deepsurf.us
  39. // @connect sleazyfork.org
  40. // @connect github.com
  41. // @connect githubusercontent.com
  42. // @connect openuserjs.org
  43. // @grant GM_addValueChangeListener
  44. // @grant GM_addElement
  45. // @grant GM_info
  46. // @grant GM_getValue
  47. // @grant GM_openInTab
  48. // @grant GM_setValue
  49. // @grant GM_registerMenuCommand
  50. // @grant GM_removeValueChangeListener
  51. // @grant GM_xmlhttpRequest
  52. // @grant GM.addValueChangeListener
  53. // @grant GM.addElement
  54. // @grant GM.info
  55. // @grant GM.getValue
  56. // @grant GM.openInTab
  57. // @grant GM.setValue
  58. // @grant GM.registerMenuCommand
  59. // @grant GM.removeValueChangeListener
  60. // @grant GM.xmlHttpRequest
  61. // @match https://*/*
  62. // @noframes
  63. // @run-at document-start
  64. // ==/UserScript==
  65. (() => {
  66. 'use strict';
  67. /******************************************************************************/
  68. const inIframe = () => {
  69. try {
  70. return window.self !== window.top;
  71. } catch (e) {
  72. return true;
  73. }
  74. }
  75. if (inIframe()) {
  76. return;
  77. }
  78. let userjs = self.userjs;
  79. /**
  80. * Skip text/plain documents, based on uBlock Origin `vapi.js` file
  81. *
  82. * [Source Code](https://github.com/gorhill/uBlock/blob/master/platform/common/vapi.js)
  83. */
  84. if (
  85. (document instanceof Document ||
  86. (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) &&
  87. /^image\/|^text\/plain/.test(document.contentType || '') === false &&
  88. (self.userjs instanceof Object === false || userjs.UserJS !== true)
  89. ) {
  90. userjs = self.userjs = { UserJS: true };
  91. }
  92. if (!(typeof userjs === 'object' && userjs.UserJS)) {
  93. return;
  94. }
  95. const createPolicy = () => {
  96. // Native implementation exists
  97. if (window.trustedTypes && window.trustedTypes.createPolicy) {
  98. window.trustedTypes.createPolicy('default', {
  99. createHTML: (string) => string
  100. });
  101. }
  102. };
  103. createPolicy();
  104. /**
  105. * [_locales](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales)
  106. */
  107. const translations = {
  108. "ar": {
  109. "createdby": "انشأ من قبل",
  110. "name": "اسم",
  111. "daily_installs": "التثبيت اليومي",
  112. "close": "يغلق",
  113. "filterA": "منقي",
  114. "max": "تحقيق أقصى قدر",
  115. "min": "تصغير",
  116. "search": "يبحث",
  117. "search_placeholder": "بحث في البرامج النصية",
  118. "install": "تثبيت",
  119. "issue": "إصدار جديد",
  120. "version_number": "الإصدار",
  121. "updated": "آخر تحديث",
  122. "total_installs": "إجمالي التثبيت",
  123. "ratings": "التقييمات",
  124. "good": "جيد",
  125. "ok": "جيد",
  126. "bad": "سيء",
  127. "created_date": "تم إنشاؤه",
  128. "redirect": "شوكة دهنية للكبار",
  129. "filter": "تصفية اللغات الأخرى",
  130. "dtime": "عرض المهلة",
  131. "save": "حفظ",
  132. "reset": "إعادة تعيين",
  133. "preview_code": "كود المعاينة",
  134. "saveFile": "احفظ الملف",
  135. "newTab": "علامة تبويب جديدة",
  136. "applies_to": "ينطبق على",
  137. "license": "الترخيص",
  138. "no_license": "لا يوجد",
  139. "antifeatures": "إعلانات",
  140. "userjs_fullscreen": "ملء الشاشة الكاملة التلقائي",
  141. "listing_none": "(لا يوجد)",
  142. "export_config": "تهيئة التصدير",
  143. "export_theme": "تصدير السمة",
  144. "import_config": "استيراد تهيئة الاستيراد",
  145. "import_theme": "استيراد النسق",
  146. "code_size": "حجم الرمز",
  147. "prmpt_css": "التثبيت كأسلوب المستخدم؟",
  148. "userjs_inject": "حقن Userscript+",
  149. "userjs_close": "إغلاق Userscript+",
  150. "userjs_sync": "Sync with UserScript Manager",
  151. "userjs_autoinject": "Inject on load",
  152. "auto_fetch": "Fetch on load"
  153. },
  154. "de": {
  155. "createdby": "Erstellt von",
  156. "name": "Name",
  157. "daily_installs": "Tägliche Installationen",
  158. "close": "Schließen Sie",
  159. "filterA": "Filter",
  160. "max": "Maximieren Sie",
  161. "min": "minimieren",
  162. "search": "Suche",
  163. "search_placeholder": "Suche nach Userscripts",
  164. "install": "Installieren Sie",
  165. "issue": "Neue Ausgabe",
  166. "version_number": "Version",
  167. "updated": "Zuletzt aktualisiert",
  168. "total_installs": "Installationen insgesamt",
  169. "ratings": "Bewertungen",
  170. "good": "Gut",
  171. "ok": "Okay",
  172. "bad": "Schlecht",
  173. "created_date": "Erstellt",
  174. "redirect": "Greasy Fork für Erwachsene",
  175. "filter": "Andere Sprachen herausfiltern",
  176. "dtime": "Zeitüberschreitung anzeigen",
  177. "save": "Speichern Sie",
  178. "reset": "Zurücksetzen",
  179. "preview_code": "Vorschau Code",
  180. "saveFile": "Datei speichern",
  181. "newTab": "Neue Registerkarte",
  182. "applies_to": "Gilt für",
  183. "license": "Lizenz",
  184. "no_license": "N/A",
  185. "antifeatures": "Antifeatures",
  186. "userjs_fullscreen": "Automatischer Vollbildmodus",
  187. "listing_none": "(Keine)",
  188. "export_config": "Konfig exportieren",
  189. "export_theme": "Thema exportieren",
  190. "import_config": "Konfig importieren",
  191. "import_theme": "Thema importieren",
  192. "code_size": "Code Größe",
  193. "prmpt_css": "Als UserStyle installieren?",
  194. "userjs_inject": "Userscript+ einfügen",
  195. "userjs_close": "Userscript+ schließen",
  196. "userjs_sync": "Sync with UserScript Manager",
  197. "userjs_autoinject": "Inject on load",
  198. "auto_fetch": "Fetch on load"
  199. },
  200. "en": {
  201. "createdby": "Created by",
  202. "name": "Name",
  203. "daily_installs": "Daily Installs",
  204. "close": "Close",
  205. "filterA": "Filter",
  206. "max": "Maximize",
  207. "min": "Minimize",
  208. "search": "Search",
  209. "search_placeholder": "Search for userscripts",
  210. "install": "Install",
  211. "issue": "New Issue",
  212. "version_number": "Version",
  213. "updated": "Last Updated",
  214. "total_installs": "Total Installs",
  215. "ratings": "Ratings",
  216. "good": "Good",
  217. "ok": "Okay",
  218. "bad": "Bad",
  219. "created_date": "Created",
  220. "redirect": "Greasy Fork for adults",
  221. "filter": "Filter out other languages",
  222. "dtime": "Display Timeout",
  223. "save": "Save",
  224. "reset": "Reset",
  225. "preview_code": "Preview Code",
  226. "saveFile": "Download",
  227. "newTab": "New Tab",
  228. "applies_to": "Applies to",
  229. "license": "License",
  230. "no_license": "N/A",
  231. "antifeatures": "Antifeatures",
  232. "userjs_fullscreen": "Automatic Fullscreen",
  233. "listing_none": "(None)",
  234. "export_config": "Export Config",
  235. "export_theme": "Export Theme",
  236. "import_config": "Import Config",
  237. "import_theme": "Import Theme",
  238. "code_size": "Code Size",
  239. "prmpt_css": "Install as UserStyle?",
  240. "userjs_inject": "Inject Userscript+",
  241. "userjs_close": "Close Userscript+",
  242. "userjs_sync": "Sync",
  243. "userjs_autoinject": "Inject on load",
  244. "auto_fetch": "Fetch on load"
  245. },
  246. "en_GB": {
  247. "createdby": "Created by",
  248. "name": "Name",
  249. "daily_installs": "Daily Installs",
  250. "close": "Close",
  251. "filterA": "Filter",
  252. "max": "Maximize",
  253. "min": "Minimize",
  254. "search": "Search",
  255. "search_placeholder": "Search scripts",
  256. "install": "Install",
  257. "issue": "New Issue",
  258. "version_number": "Version",
  259. "updated": "Last Updated",
  260. "total_installs": "Total Installs",
  261. "ratings": "Ratings",
  262. "good": "Good",
  263. "ok": "Ok",
  264. "bad": "Bad",
  265. "created_date": "Created",
  266. "redirect": "Greasy Fork for adults",
  267. "filter": "Filter out other languages",
  268. "dtime": "Display Timeout",
  269. "save": "Save",
  270. "reset": "Reset",
  271. "preview_code": "Preview Code",
  272. "saveFile": "Save File",
  273. "newTab": "New Tab",
  274. "applies_to": "Applies to",
  275. "license": "License",
  276. "no_license": "N/A",
  277. "antifeatures": "Antifeatures",
  278. "userjs_fullscreen": "Automatic Fullscreen",
  279. "listing_none": "(None)",
  280. "export_config": "Export Config",
  281. "export_theme": "Export Theme",
  282. "import_config": "Import Config",
  283. "import_theme": "Import Theme",
  284. "code_size": "Code Size",
  285. "prmpt_css": "Install as UserStyle?",
  286. "userjs_inject": "Inject Userscript+",
  287. "userjs_close": "Close Userscript+",
  288. "userjs_sync": "Sync with UserScript Manager",
  289. "userjs_autoinject": "Inject on load",
  290. "auto_fetch": "Fetch on load"
  291. },
  292. "es": {
  293. "createdby": "Creado por",
  294. "name": "Nombre",
  295. "daily_installs": "Instalaciones diarias",
  296. "close": "Ya no se muestra",
  297. "filterA": "Filtro",
  298. "max": "Maximizar",
  299. "min": "Minimizar",
  300. "search": "Busque en",
  301. "search_placeholder": "Buscar userscripts",
  302. "install": "Instalar",
  303. "issue": "Nueva edición",
  304. "version_number": "Versión",
  305. "updated": "Última actualización",
  306. "total_installs": "Total de instalaciones",
  307. "ratings": "Clasificaciones",
  308. "good": "Bueno",
  309. "ok": "Ok",
  310. "bad": "Malo",
  311. "created_date": "Creado",
  312. "redirect": "Greasy Fork para adultos",
  313. "filter": "Filtrar otros idiomas",
  314. "dtime": "Mostrar el tiempo de espera",
  315. "save": "Guardar",
  316. "reset": "Reiniciar",
  317. "preview_code": "Vista previa del código",
  318. "saveFile": "Guardar archivo",
  319. "newTab": "Guardar archivo",
  320. "applies_to": "Se aplica a",
  321. "license": "Licencia",
  322. "no_license": "Desconocida",
  323. "antifeatures": "Características indeseables",
  324. "userjs_fullscreen": "Pantalla completa automática",
  325. "listing_none": "(Ninguno)",
  326. "export_config": "Exportar configuración",
  327. "export_theme": "Exportar tema",
  328. "import_config": "Importar configuración",
  329. "import_theme": "Importar tema",
  330. "code_size": "Código Tamaño",
  331. "prmpt_css": "¿Instalar como UserStyle?",
  332. "userjs_inject": "Inyectar Userscript+",
  333. "userjs_close": "Cerrar Userscript+",
  334. "userjs_sync": "Sync with UserScript Manager",
  335. "userjs_autoinject": "Inject on load",
  336. "auto_fetch": "Fetch on load"
  337. },
  338. "fr": {
  339. "createdby": "Créé par",
  340. "name": "Nom",
  341. "daily_installs": "Installations quotidiennes",
  342. "close": "Ne plus montrer",
  343. "filterA": "Filtre",
  344. "max": "Maximiser",
  345. "min": "Minimiser",
  346. "search": "Recherche",
  347. "search_placeholder": "Rechercher des userscripts",
  348. "install": "Installer",
  349. "issue": "Nouveau numéro",
  350. "version_number": "Version",
  351. "updated": "Dernière mise à jour",
  352. "total_installs": "Total des installations",
  353. "ratings": "Notations",
  354. "good": "Bon",
  355. "ok": "Ok",
  356. "bad": "Mauvais",
  357. "created_date": "Créé",
  358. "redirect": "Greasy Fork pour les adultes",
  359. "filter": "Filtrer les autres langues",
  360. "dtime": "Délai d'affichage",
  361. "save": "Sauvez",
  362. "reset": "Réinitialiser",
  363. "preview_code": "Prévisualiser le code",
  364. "saveFile": "Enregistrer le fichier",
  365. "newTab": "Nouvel onglet",
  366. "applies_to": "S'applique à",
  367. "license": "Licence",
  368. "no_license": "N/A",
  369. "antifeatures": "Antifeatures",
  370. "userjs_fullscreen": "Plein écran automatique",
  371. "listing_none": "(Aucun)",
  372. "export_config": "Export Config",
  373. "export_theme": "Exporter le thème",
  374. "import_config": "Importer la configuration",
  375. "import_theme": "Importer le thème",
  376. "code_size": "Code Taille",
  377. "prmpt_css": "Installer comme UserStyle ?",
  378. "userjs_inject": "Injecter Userscript+",
  379. "userjs_close": "Fermer Userscript+",
  380. "userjs_sync": "Sync with UserScript Manager",
  381. "userjs_autoinject": "Inject on load",
  382. "auto_fetch": "Fetch on load"
  383. },
  384. "ja": {
  385. "createdby": "によって作成された",
  386. "name": "名前",
  387. "daily_installs": "デイリーインストール",
  388. "close": "表示されなくなりました",
  389. "filterA": "フィルター",
  390. "max": "最大化",
  391. "min": "ミニマム",
  392. "search": "検索",
  393. "search_placeholder": "ユーザースクリプトの検索",
  394. "install": "インストール",
  395. "issue": "新刊のご案内",
  396. "version_number": "バージョン",
  397. "updated": "最終更新日",
  398. "total_installs": "総インストール数",
  399. "ratings": "レーティング",
  400. "good": "グッド",
  401. "ok": "良い",
  402. "bad": "悪い",
  403. "created_date": "作成",
  404. "redirect": "大人のGreasyfork",
  405. "filter": "他の言語をフィルタリングする",
  406. "dtime": "表示タイムアウト",
  407. "save": "拯救",
  408. "reset": "リセット",
  409. "preview_code": "コードのプレビュー",
  410. "saveFile": "ファイルを保存",
  411. "newTab": "新しいタブ",
  412. "applies_to": "適用対象",
  413. "license": "ライセンス",
  414. "no_license": "不明",
  415. "antifeatures": "アンチ機能",
  416. "userjs_fullscreen": "自動フルスクリーン",
  417. "listing_none": "(なし)",
  418. "export_config": "エクスポート設定",
  419. "export_theme": "テーマのエクスポート",
  420. "import_config": "設定のインポート",
  421. "import_theme": "テーマのインポート",
  422. "code_size": "コード・サイズ",
  423. "prmpt_css": "UserStyleとしてインストールしますか?",
  424. "userjs_inject": "Userscript+ を挿入",
  425. "userjs_close": "Userscript+ を閉じる",
  426. "userjs_sync": "Sync with UserScript Manager",
  427. "userjs_autoinject": "Inject on load",
  428. "auto_fetch": "Fetch on load"
  429. },
  430. "nl": {
  431. "createdby": "Gemaakt door",
  432. "name": "Naam",
  433. "daily_installs": "Dagelijkse Installaties",
  434. "close": "Sluit",
  435. "filterA": "Filter",
  436. "max": "Maximaliseer",
  437. "min": "Minimaliseer",
  438. "search": "Zoek",
  439. "search_placeholder": "Zoeken naar gebruikersscripts",
  440. "install": "Installeer",
  441. "issue": "Nieuw Issue",
  442. "version_number": "Versie",
  443. "updated": "Laatste Update",
  444. "total_installs": "Totale Installaties",
  445. "ratings": "Beoordeling",
  446. "good": "Goed",
  447. "ok": "Ok",
  448. "bad": "Slecht",
  449. "created_date": "Aangemaakt",
  450. "redirect": "Greasy Fork voor volwassenen",
  451. "filter": "Filter andere talen",
  452. "dtime": "Weergave timeout",
  453. "save": "Opslaan",
  454. "reset": "Opnieuw instellen",
  455. "preview_code": "Voorbeeldcode",
  456. "saveFile": "Bestand opslaan",
  457. "newTab": "Nieuw tabblad",
  458. "applies_to": "Geldt voor",
  459. "license": "Licentie",
  460. "no_license": "N.v.t.",
  461. "antifeatures": "Functies voor eigen gewin",
  462. "userjs_fullscreen": "Automatisch volledig scherm",
  463. "listing_none": "(Geen)",
  464. "export_config": "Configuratie exporteren",
  465. "export_theme": "Thema exporteren",
  466. "import_config": "Configuratie importeren",
  467. "import_theme": "Thema importeren",
  468. "code_size": "Code Grootte",
  469. "prmpt_css": "Installeren als UserStyle?",
  470. "userjs_inject": "Injecteer Userscript+",
  471. "userjs_close": "Sluit Userscript+",
  472. "userjs_sync": "Sync with UserScript Manager",
  473. "userjs_autoinject": "Inject on load",
  474. "auto_fetch": "Fetch on load"
  475. },
  476. "pl": {
  477. "createdby": "Stworzony przez",
  478. "name": "Nazwa",
  479. "daily_installs": "Codzienne instalacje",
  480. "close": "Zamknij",
  481. "filterA": "Filtr",
  482. "max": "Maksymalizuj",
  483. "min": "Minimalizuj",
  484. "search": "Wyszukiwanie",
  485. "search_placeholder": "Wyszukiwanie skryptów użytkownika",
  486. "install": "Instalacja",
  487. "issue": "Nowy numer",
  488. "version_number": "Wersja",
  489. "updated": "Ostatnia aktualizacja",
  490. "total_installs": "Łączna liczba instalacji",
  491. "ratings": "Oceny",
  492. "good": "Dobry",
  493. "ok": "Ok",
  494. "bad": "Zły",
  495. "created_date": "Utworzony",
  496. "redirect": "Greasy Fork dla dorosłych",
  497. "filter": "Odfiltruj inne języki",
  498. "dtime": "Limit czasu wyświetlania",
  499. "save": "Zapisz",
  500. "reset": "Reset",
  501. "preview_code": "Kod podglądu",
  502. "saveFile": "Zapisz plik",
  503. "newTab": "Nowa karta",
  504. "applies_to": "Dotyczy",
  505. "license": "Licencja",
  506. "no_license": "N/A",
  507. "antifeatures": "Antywzorce",
  508. "userjs_fullscreen": "Automatyczny pełny ekran",
  509. "listing_none": "(Brak)",
  510. "export_config": "Konfiguracja eksportu",
  511. "export_theme": "Motyw eksportu",
  512. "import_config": "Importuj konfigurację",
  513. "import_theme": "Importuj motyw",
  514. "code_size": "Kod Rozmiar",
  515. "prmpt_css": "Zainstalować jako UserStyle?",
  516. "userjs_inject": "Wstrzyknij Userscript+",
  517. "userjs_close": "Zamknij Userscript+",
  518. "userjs_sync": "Sync with UserScript Manager",
  519. "userjs_autoinject": "Inject on load",
  520. "auto_fetch": "Fetch on load"
  521. },
  522. "ru": {
  523. "createdby": "Сделано",
  524. "name": "Имя",
  525. "daily_installs": "Ежедневные установки",
  526. "close": "Больше не показывать",
  527. "filterA": "Фильтр",
  528. "max": "Максимизировать",
  529. "min": "Минимизировать",
  530. "search": "Поиск",
  531. "search_placeholder": "Поиск юзерскриптов",
  532. "install": "Установите",
  533. "issue": "Новый выпуск",
  534. "version_number": "Версия",
  535. "updated": "Последнее обновление",
  536. "total_installs": "Всего установок",
  537. "ratings": "Рейтинги",
  538. "good": "Хорошо",
  539. "ok": "Хорошо",
  540. "bad": "Плохо",
  541. "created_date": "Создано",
  542. "redirect": "Greasy Fork для взрослых",
  543. "filter": "Отфильтровать другие языки",
  544. "dtime": "Тайм-аут отображения",
  545. "save": "Сохранить",
  546. "reset": "Перезагрузить",
  547. "preview_code": "Предварительный просмотр кода",
  548. "saveFile": "Сохранить файл",
  549. "newTab": "Новая вкладка",
  550. "applies_to": "Применяется к",
  551. "license": "Лицензия",
  552. "no_license": "Недоступно",
  553. "antifeatures": "Нежелательная функциональность",
  554. "userjs_fullscreen": "Автоматический полноэкранный режим",
  555. "listing_none": "(нет)",
  556. "export_config": "Экспорт конфигурации",
  557. "export_theme": "Экспорт темы",
  558. "import_config": "Импорт конфигурации",
  559. "import_theme": "Импортировать тему",
  560. "code_size": "Код Размер",
  561. "prmpt_css": "Установить как UserStyle?",
  562. "userjs_inject": "Вставить Userscript+",
  563. "userjs_close": "Закрыть Userscript+",
  564. "userjs_sync": "Sync with UserScript Manager",
  565. "userjs_autoinject": "Inject on load",
  566. "auto_fetch": "Fetch on load"
  567. },
  568. "zh": {
  569. "createdby": "由...制作",
  570. "name": "姓名",
  571. "daily_installs": "日常安装",
  572. "close": "不再显示",
  573. "filterA": "过滤器",
  574. "max": "最大化",
  575. "min": "最小化",
  576. "search": "搜索",
  577. "search_placeholder": "搜索用户脚本",
  578. "install": "安装",
  579. "issue": "新问题",
  580. "version_number": "版本",
  581. "updated": "最后更新",
  582. "total_installs": "总安装量",
  583. "ratings": "评级",
  584. "good": "好的",
  585. "ok": "好的",
  586. "bad": "不好",
  587. "created_date": "创建",
  588. "redirect": "大人的Greasyfork",
  589. "filter": "过滤掉其他语言",
  590. "dtime": "显示超时",
  591. "save": "拯救",
  592. "reset": "重置",
  593. "preview_code": "预览代码",
  594. "saveFile": "保存存档",
  595. "newTab": "新标签",
  596. "applies_to": "适用于",
  597. "license": "许可证",
  598. "no_license": "暂无",
  599. "antifeatures": "可能不受欢迎的功能",
  600. "userjs_fullscreen": "自动全屏",
  601. "listing_none": "(无)",
  602. "export_config": "导出配置",
  603. "export_theme": "导出主题",
  604. "import_config": "导入配置",
  605. "import_theme": "导入主题",
  606. "code_size": "代码 尺寸",
  607. "prmpt_css": "安装为用户风格?",
  608. "userjs_inject": "注入 Userscript+",
  609. "userjs_close": "关闭 Userscript+",
  610. "userjs_sync": "Sync with UserScript Manager",
  611. "userjs_autoinject": "Inject on load",
  612. "auto_fetch": "Fetch on load"
  613. },
  614. "zh_CN": {
  615. "createdby": "由...制作",
  616. "name": "姓名",
  617. "daily_installs": "日常安装",
  618. "close": "不再显示",
  619. "filterA": "过滤器",
  620. "max": "最大化",
  621. "min": "最小化",
  622. "search": "搜索",
  623. "search_placeholder": "搜索用户脚本",
  624. "install": "安装",
  625. "issue": "新问题",
  626. "version_number": "版本",
  627. "updated": "最后更新",
  628. "total_installs": "总安装量",
  629. "ratings": "评级",
  630. "good": "好的",
  631. "ok": "好的",
  632. "bad": "不好",
  633. "created_date": "创建",
  634. "redirect": "大人的Greasyfork",
  635. "filter": "过滤掉其他语言",
  636. "dtime": "显示超时",
  637. "save": "拯救",
  638. "reset": "重置",
  639. "preview_code": "预览代码",
  640. "saveFile": "保存存档",
  641. "newTab": "新标签",
  642. "applies_to": "适用于",
  643. "license": "许可证",
  644. "no_license": "暂无",
  645. "antifeatures": "可能不受欢迎的功能",
  646. "userjs_fullscreen": "自动全屏",
  647. "listing_none": "(无)",
  648. "export_config": "导出配置",
  649. "export_theme": "导出主题",
  650. "import_config": "导入配置",
  651. "import_theme": "导入主题",
  652. "code_size": "代码 尺寸",
  653. "prmpt_css": "安装为用户风格?",
  654. "userjs_inject": "注入 Userscript+",
  655. "userjs_close": "关闭 Userscript+",
  656. "userjs_sync": "Sync with UserScript Manager",
  657. "userjs_autoinject": "Inject on load",
  658. "auto_fetch": "Fetch on load"
  659. },
  660. "zh_TW": {
  661. "createdby": "由...制作",
  662. "name": "姓名",
  663. "daily_installs": "日常安装",
  664. "close": "不再显示",
  665. "filterA": "过滤器",
  666. "max": "最大化",
  667. "min": "最小化",
  668. "search": "搜索",
  669. "search_placeholder": "搜索用户脚本",
  670. "install": "安装",
  671. "issue": "新问题",
  672. "version_number": "版本",
  673. "updated": "最后更新",
  674. "total_installs": "总安装量",
  675. "ratings": "评级",
  676. "good": "好的",
  677. "ok": "好的",
  678. "bad": "不好",
  679. "created_date": "创建",
  680. "redirect": "大人的Greasyfork",
  681. "filter": "过滤掉其他语言",
  682. "dtime": "显示超时",
  683. "save": "拯救",
  684. "reset": "重置",
  685. "preview_code": "预览代码",
  686. "saveFile": "保存存档",
  687. "newTab": "新标签",
  688. "applies_to": "适用于",
  689. "license": "许可证",
  690. "no_license": "暂无",
  691. "antifeatures": "可能不受欢迎的功能",
  692. "userjs_fullscreen": "自动全屏",
  693. "listing_none": "(无)",
  694. "export_config": "导出配置",
  695. "export_theme": "导出主题",
  696. "import_config": "导入配置",
  697. "import_theme": "导入主题",
  698. "code_size": "代码 尺寸",
  699. "prmpt_css": "作為使用者樣式安裝?",
  700. "userjs_inject": "注入用戶腳本+",
  701. "userjs_close": "關閉用戶腳本+",
  702. "userjs_sync": "Sync with UserScript Manager",
  703. "userjs_autoinject": "Inject on load",
  704. "auto_fetch": "Fetch on load"
  705. }
  706. };
  707. const main_css = `mujs-root {
  708. --mujs-even-row: hsl(222, 14%, 22%);
  709. --mujs-odd-row: hsl(222, 14%, 11%);
  710. --mujs-even-err: hsl(0, 100%, 22%);
  711. --mujs-odd-err: hsl(0, 100%, 11%);
  712. --mujs-background-color: hsl(222, 14%, 33%);
  713. --mujs-gf-color: hsl(204, 100%, 40%);
  714. --mujs-sf-color: hsl(12, 86%, 50%);
  715. --mujs-border-b-color: hsla(0, 0%, 0%, 0);
  716. --mujs-gf-btn-color: hsl(211, 87%, 56%);
  717. --mujs-sf-btn-color: hsl(12, 86%, 50%);
  718. --mujs-sf-txt-color: hsl(12, 79%, 55%);
  719. --mujs-txt-color: hsl(0, 0%, 100%);
  720. --mujs-chck-color: hsla(0, 0%, 100%, 0.568);
  721. --mujs-chck-gf: hsla(197, 100%, 50%, 0.568);
  722. --mujs-chck-git: hsla(213, 13%, 16%, 0.568);
  723. --mujs-chck-open: hsla(12, 86%, 50%, 0.568);
  724. --mujs-placeholder: hsl(81, 56%, 54%);
  725. --mujs-position-top: unset;
  726. --mujs-position-bottom: 1em;
  727. --mujs-position-left: unset;
  728. --mujs-position-right: 1em;
  729. --mujs-font-family: Arial, Helvetica, sans-serif;
  730. font-family: var(--mujs-font-family, Arial, Helvetica, sans-serif);
  731. text-rendering: optimizeLegibility;
  732. word-break: normal;
  733. font-size: 14px;
  734. color: var(--mujs-txt-color, hsl(0, 0%, 100%));
  735. }
  736.  
  737. mujs-root * {
  738. -webkit-appearance: none;
  739. -moz-appearance: none;
  740. appearance: none;
  741. scrollbar-color: var(--mujs-txt-color, hsl(0, 0%, 100%)) hsl(224, 14%, 21%);
  742. scrollbar-width: thin;
  743. }
  744. @supports not (scrollbar-width: thin) {
  745. mujs-root * ::-webkit-scrollbar {
  746. width: 1.4vw;
  747. height: 3.3vh;
  748. }
  749. mujs-root * ::-webkit-scrollbar-track {
  750. background-color: hsl(224, 14%, 21%);
  751. border-radius: 16px;
  752. margin-top: 3px;
  753. margin-bottom: 3px;
  754. box-shadow: inset 0 0 6px hsla(0, 0%, 0%, 0.3);
  755. }
  756. mujs-root * ::-webkit-scrollbar-thumb {
  757. border-radius: 16px;
  758. background-color: var(--mujs-txt-color, hsl(0, 0%, 100%));
  759. background-image: -webkit-linear-gradient(45deg, hsla(0, 0%, 100%, 0.2) 25%, transparent 25%, transparent 50%, hsla(0, 0%, 100%, 0.2) 50%, hsla(0, 0%, 100%, 0.2) 75%, transparent 75%, transparent);
  760. }
  761. mujs-root * ::-webkit-scrollbar-thumb:hover {
  762. background: var(--mujs-txt-color, hsl(0, 0%, 100%));
  763. }
  764. }
  765.  
  766. mu-js {
  767. line-height: normal;
  768. }
  769.  
  770. mujs-section > label,
  771. .mujs-homepag e,
  772. td.mujs-list,
  773. .install {
  774. font-size: 16px;
  775. }
  776.  
  777. .install,
  778. .mujs-homepage {
  779. font-weight: 700;
  780. }
  781.  
  782. mujs-section > label,
  783. td.mujs-list {
  784. font-weight: 500;
  785. }
  786.  
  787. .mujs-invalid {
  788. border-radius: 8px !important;
  789. border-width: 2px !important;
  790. border-style: solid !important;
  791. border-color: hsl(0, 100%, 50%) !important;
  792. }
  793.  
  794. mujs-tabs,
  795. mujs-column,
  796. mujs-row,
  797. .mujs-sty-flex {
  798. display: flex;
  799. }
  800.  
  801. mujs-column,
  802. mujs-row {
  803. gap: 0.5em;
  804. }
  805.  
  806. mujs-column count-frame[data-counter=greasyfork] {
  807. background: var(--mujs-gf-color, hsl(204, 100%, 40%));
  808. }
  809. mujs-column count-frame[data-counter=sleazyfork] {
  810. background: var(--mujs-sf-color, hsl(12, 86%, 50%));
  811. }
  812. mujs-column count-frame[data-counter=github] {
  813. background: hsl(213, 13%, 16%);
  814. }
  815. mujs-column count-frame[data-counter=openuserjs] {
  816. background: hsla(12, 86%, 50%, 0.568);
  817. }
  818. @media screen and (max-width: 800px) {
  819. mujs-column {
  820. flex-flow: row wrap;
  821. }
  822. }
  823.  
  824. mujs-row {
  825. flex-flow: column wrap;
  826. }
  827.  
  828. mu-js {
  829. cursor: default;
  830. }
  831.  
  832. .hidden {
  833. display: none !important;
  834. z-index: -1 !important;
  835. }
  836.  
  837. mujs-main {
  838. width: 100%;
  839. width: -moz-available;
  840. width: -webkit-fill-available;
  841. background: var(--mujs-background-color, hsl(222, 14%, 33%)) !important;
  842. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  843. border-radius: 16px;
  844. }
  845. @media screen and (max-height: 720px) {
  846. mujs-main:not(.webext-page) {
  847. height: 100% !important;
  848. bottom: 0rem !important;
  849. right: 0rem !important;
  850. margin: 0rem !important;
  851. }
  852. }
  853. mujs-main.expanded {
  854. height: 100% !important;
  855. bottom: 0rem !important;
  856. }
  857. mujs-main:not(.webext-page) {
  858. position: fixed;
  859. height: 492px;
  860. }
  861. mujs-main:not(.webext-page):not(.expanded) {
  862. margin-left: 1rem;
  863. margin-right: 1rem;
  864. right: 1rem;
  865. bottom: 1rem;
  866. }
  867. mujs-main:not(.hidden) {
  868. z-index: 100000000000000000 !important;
  869. display: flex !important;
  870. flex-direction: column !important;
  871. }
  872. mujs-main > * {
  873. width: 100%;
  874. width: -moz-available;
  875. width: -webkit-fill-available;
  876. }
  877. mujs-main mujs-toolbar {
  878. order: 0;
  879. padding: 0.5em;
  880. display: flex;
  881. place-content: space-between;
  882. }
  883. mujs-main mujs-toolbar mujs-tabs {
  884. overflow: hidden;
  885. order: 0;
  886. }
  887. mujs-main mujs-toolbar mujs-column {
  888. flex-flow: row nowrap;
  889. order: 999999999999;
  890. }
  891. mujs-main mujs-toolbar > * {
  892. width: -webkit-fit-content;
  893. width: -moz-fit-content;
  894. width: fit-content;
  895. }
  896. mujs-main mujs-tabs {
  897. gap: 0.5em;
  898. text-align: center;
  899. -webkit-user-select: none;
  900. -moz-user-select: none;
  901. -ms-user-select: none;
  902. user-select: none;
  903. flex-flow: row wrap;
  904. }
  905. mujs-main mujs-tabs mujs-tab {
  906. padding: 0.25em;
  907. min-width: 150px;
  908. width: -webkit-fit-content;
  909. width: -moz-fit-content;
  910. width: fit-content;
  911. height: -webkit-fit-content;
  912. height: -moz-fit-content;
  913. height: fit-content;
  914. display: flex;
  915. place-content: space-between;
  916. border: 1px solid transparent;
  917. border-radius: 4px;
  918. background: transparent;
  919. }
  920. @media screen and (max-width: 800px) {
  921. mujs-main mujs-tabs mujs-tab {
  922. min-width: 6em !important;
  923. }
  924. }
  925. mujs-main mujs-tabs mujs-tab.active {
  926. background: var(--mujs-even-row, hsl(222, 14%, 18%));
  927. }
  928. mujs-main mujs-tabs mujs-tab:not(.active):hover {
  929. background: var(--mujs-even-row, hsl(222, 14%, 18%));
  930. }
  931. mujs-main mujs-tabs mujs-tab mujs-host {
  932. float: left;
  933. overflow: auto;
  934. overflow-wrap: break-word;
  935. text-overflow: ellipsis;
  936. white-space: nowrap;
  937. }
  938. mujs-main mujs-tabs mujs-tab mu-js {
  939. float: right;
  940. }
  941. mujs-main mujs-tabs mujs-addtab {
  942. order: 999999999999;
  943. font-size: 20px;
  944. padding: 0px 0.25em;
  945. }
  946. mujs-main mujs-tabs mujs-addtab:hover {
  947. background: var(--mujs-even-row, hsl(222, 14%, 18%));
  948. }
  949. mujs-main mujs-tab,
  950. mujs-main mujs-btn,
  951. mujs-main input {
  952. width: -webkit-fit-content;
  953. width: -moz-fit-content;
  954. width: fit-content;
  955. height: -webkit-fit-content;
  956. height: -moz-fit-content;
  957. height: fit-content;
  958. }
  959. mujs-main input {
  960. background: hsla(0, 0%, 0%, 0);
  961. color: var(--mujs-txt-color, hsl(0, 0%, 100%));
  962. }
  963. mujs-main input:not([type=checkbox]) {
  964. border: transparent;
  965. outline: none !important;
  966. }
  967. mujs-main mujs-page,
  968. mujs-main textarea {
  969. background: inherit;
  970. overflow-y: auto;
  971. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  972. border-radius: 5px;
  973. outline: none;
  974. font-family: monospace;
  975. font-size: 14px;
  976. }
  977. mujs-main mujs-page {
  978. padding: 0.5em;
  979. margin: 0.5em;
  980. }
  981. mujs-main textarea {
  982. overflow-y: auto;
  983. color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  984. resize: vertical;
  985. }
  986. mujs-main textarea:focus {
  987. outline: none;
  988. }
  989. mujs-main th,
  990. mujs-main .mujs-cfg *:not(input[type=password], input[type=text], input[type=number]) {
  991. -webkit-user-select: none !important;
  992. -moz-user-select: none !important;
  993. -ms-user-select: none !important;
  994. user-select: none !important;
  995. }
  996. mujs-main .mujs-footer {
  997. order: 3;
  998. overflow-x: hidden;
  999. text-align: center;
  1000. border-radius: 16px;
  1001. }
  1002. mujs-main .mujs-footer > * {
  1003. min-height: 50px;
  1004. }
  1005. mujs-main .mujs-footer .error:nth-child(even) {
  1006. background: var(--mujs-even-err, hsl(0, 100%, 22%)) !important;
  1007. }
  1008. mujs-main .mujs-footer .error:nth-child(odd) {
  1009. background: var(--mujs-odd-err, hsl(0, 100%, 11%)) !important;
  1010. }
  1011. mujs-main .mujs-prompt {
  1012. align-items: center;
  1013. justify-content: center;
  1014. }
  1015. mujs-main .mujs-prompt svg {
  1016. width: 14px;
  1017. height: 14px;
  1018. background: transparent;
  1019. }
  1020. mujs-main .mujs-prompt > .prompt {
  1021. position: absolute;
  1022. background: var(--mujs-background-color, hsl(222, 14%, 33%)) !important;
  1023. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1024. border-radius: 16px;
  1025. text-align: center;
  1026. padding: 0.5em;
  1027. z-index: 1;
  1028. }
  1029. mujs-main .mujs-prompt > .prompt .prompt-head {
  1030. font-size: 18px;
  1031. }
  1032. mujs-main .mujs-prompt > .prompt .prompt-body {
  1033. display: grid;
  1034. grid-auto-flow: column;
  1035. grid-gap: 0.5em;
  1036. padding-top: 0.5em;
  1037. }
  1038. mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-deny] {
  1039. background: var(--mujs-sf-btn-color, hsl(12, 86%, 50%));
  1040. border-color: var(--mujs-sf-btn-color, hsl(12, 86%, 50%));
  1041. }
  1042. mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-deny]:hover {
  1043. background: var(--mujs-sf-txt-color, hsl(12, 79%, 55%));
  1044. border-color: var(--mujs-sf-txt-color, hsl(12, 79%, 55%));
  1045. }
  1046. mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-confirm] {
  1047. background: var(--mujs-gf-color, hsl(204, 100%, 40%));
  1048. border-color: var(--mujs-gf-color, hsl(204, 100%, 40%));
  1049. }
  1050. mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-confirm]:hover {
  1051. background: var(--mujs-gf-btn-color, hsl(211, 87%, 56%));
  1052. border-color: var(--mujs-gf-btn-color, hsl(211, 87%, 56%));
  1053. }
  1054.  
  1055. .mainframe {
  1056. background: transparent;
  1057. position: fixed;
  1058. bottom: var(--mujs-position-bottom, 1rem);
  1059. right: var(--mujs-position-right, 1rem);
  1060. top: var(--mujs-position-top, unset);
  1061. left: var(--mujs-position-left, unset);
  1062. }
  1063. .mainframe count-frame {
  1064. width: fit-content;
  1065. width: -moz-fit-content;
  1066. width: -webkit-fit-content;
  1067. height: auto;
  1068. padding: 14px 16px;
  1069. }
  1070. .mainframe.error {
  1071. opacity: 1 !important;
  1072. }
  1073. .mainframe.error count-frame {
  1074. background: var(--mujs-even-err, hsl(0, 100%, 22%)) !important;
  1075. }
  1076. .mainframe:not(.hidden) {
  1077. z-index: 100000000000000000 !important;
  1078. display: block;
  1079. }
  1080.  
  1081. count-frame {
  1082. border-radius: 1000px;
  1083. margin: 0px 3px;
  1084. padding: 4px 6px;
  1085. border: 2px solid var(--mujs-border-b-color, hsla(0, 0%, 0%, 0));
  1086. font-size: 16px;
  1087. font-weight: 400;
  1088. display: inline-block;
  1089. text-align: center;
  1090. min-width: 1em;
  1091. background: var(--mujs-background-color, hsl(222, 14%, 33%));
  1092. -webkit-user-select: none;
  1093. -moz-user-select: none;
  1094. -ms-user-select: none;
  1095. user-select: none;
  1096. }
  1097.  
  1098. mujs-header {
  1099. order: 1;
  1100. display: flex;
  1101. border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1102. padding-left: 0.5em;
  1103. padding-right: 0.5em;
  1104. padding-bottom: 0.5em;
  1105. font-size: 1em;
  1106. place-content: space-between;
  1107. height: fit-content;
  1108. height: -moz-fit-content;
  1109. height: -webkit-fit-content;
  1110. gap: 1em;
  1111. }
  1112. mujs-header > *:not(mujs-url) {
  1113. height: fit-content;
  1114. height: -moz-fit-content;
  1115. height: -webkit-fit-content;
  1116. }
  1117. mujs-header mujs-url {
  1118. order: 0;
  1119. flex-grow: 1;
  1120. }
  1121. mujs-header mujs-url > input {
  1122. width: 100%;
  1123. height: 100%;
  1124. background: var(--mujs-even-row, hsl(222, 14%, 18%));
  1125. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1126. border-radius: 4px;
  1127. }
  1128. mujs-header .rate-container {
  1129. order: 1;
  1130. }
  1131. mujs-header .btn-frame {
  1132. order: 999999999999;
  1133. }
  1134.  
  1135. mujs-body {
  1136. order: 2;
  1137. overflow-x: hidden;
  1138. padding: 0px;
  1139. height: 100%;
  1140. border: 1px solid var(--mujs-border-b-color, hsla(0, 0%, 0%, 0));
  1141. border-bottom-left-radius: 16px;
  1142. border-bottom-right-radius: 16px;
  1143. }
  1144. mujs-body .mujs-ratings {
  1145. padding: 0 0.25em;
  1146. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1147. border-radius: 1000px;
  1148. width: -webkit-fit-content;
  1149. width: -moz-fit-content;
  1150. width: fit-content;
  1151. }
  1152. mujs-body mu-jsbtn {
  1153. -webkit-user-select: none;
  1154. -moz-user-select: none;
  1155. -ms-user-select: none;
  1156. user-select: none;
  1157. }
  1158. mujs-body table,
  1159. mujs-body th,
  1160. mujs-body td {
  1161. border-collapse: collapse;
  1162. }
  1163. mujs-body table {
  1164. width: 100%;
  1165. width: -moz-available;
  1166. width: -webkit-fill-available;
  1167. }
  1168. @media screen and (max-width: 1180px) {
  1169. mujs-body table thead > tr {
  1170. display: table-column;
  1171. }
  1172. mujs-body table .frame:not(.webext-page) {
  1173. width: 100%;
  1174. display: flex;
  1175. flex-flow: row wrap;
  1176. align-items: center;
  1177. padding-top: 0.5em;
  1178. padding-bottom: 0.5em;
  1179. }
  1180. mujs-body table .frame:not(.webext-page) td {
  1181. margin: auto;
  1182. }
  1183. mujs-body table .frame:not(.webext-page) td > mujs-a,
  1184. mujs-body table .frame:not(.webext-page) td > mu-js,
  1185. mujs-body table .frame:not(.webext-page) td > mujs-column {
  1186. text-align: center;
  1187. justify-content: center;
  1188. }
  1189. mujs-body table .frame:not(.webext-page) td > mujs-a {
  1190. width: 100%;
  1191. }
  1192. }
  1193. @media screen and (max-width: 1180px) and (max-width: 800px) {
  1194. mujs-body table .frame:not(.webext-page) td > mujs-column {
  1195. flex-flow: column wrap;
  1196. }
  1197. mujs-body table .frame:not(.webext-page) td > mujs-column > mujs-row {
  1198. align-content: center;
  1199. }
  1200. mujs-body table .frame:not(.webext-page) td > mujs-column mujs-column {
  1201. justify-content: center;
  1202. }
  1203. }
  1204. @media screen and (max-width: 1180px) {
  1205. mujs-body table .frame:not(.webext-page) td:not(.mujs-name, .install-btn) {
  1206. width: 25%;
  1207. }
  1208. }
  1209. @media screen and (max-width: 1180px) and (max-width: 800px) {
  1210. mujs-body table .frame:not(.webext-page) td.install-btn {
  1211. width: 100%;
  1212. }
  1213. }
  1214. @media screen and (max-width: 1180px) {
  1215. mujs-body table .frame:not(.webext-page) .mujs-name {
  1216. width: 100%;
  1217. }
  1218. }
  1219. @media screen and (max-width: 550px) {
  1220. mujs-body table .frame:not(.webext-page) td {
  1221. margin: 1rem !important;
  1222. }
  1223. mujs-body table .frame:not(.webext-page) td:not(.mujs-name, .install-btn) {
  1224. width: auto !important;
  1225. }
  1226. }
  1227. mujs-body table th {
  1228. position: -webkit-sticky;
  1229. position: sticky;
  1230. top: 0;
  1231. background: hsla(222, 14%, 33%, 0.75);
  1232. border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1233. }
  1234. mujs-body table th.mujs-header-name {
  1235. width: 50%;
  1236. }
  1237. @media screen and (max-width: 800px) {
  1238. mujs-body table th.mujs-header-name {
  1239. width: auto !important;
  1240. }
  1241. }
  1242. mujs-body table .frame:nth-child(even) {
  1243. background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important;
  1244. }
  1245. mujs-body table .frame:nth-child(even) textarea {
  1246. background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important;
  1247. }
  1248. mujs-body table .frame:nth-child(odd) {
  1249. background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important;
  1250. }
  1251. mujs-body table .frame:nth-child(odd) textarea {
  1252. background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important;
  1253. }
  1254. mujs-body table .frame:not([data-engine=sleazyfork], [data-engine=greasyfork]) mujs-a {
  1255. color: var(--mujs-sf-txt-color, hsl(12, 79%, 55%));
  1256. }
  1257. mujs-body table .frame:not([data-engine=sleazyfork], [data-engine=greasyfork]) mu-jsbtn {
  1258. background: var(--mujs-sf-btn-color, hsl(12, 86%, 50%));
  1259. border-color: var(--mujs-sf-btn-color, hsl(12, 86%, 50%));
  1260. }
  1261. mujs-body table .frame:not([data-engine=sleazyfork], [data-engine=greasyfork]) mu-jsbtn:hover {
  1262. background: var(--mujs-sf-txt-color, hsl(12, 79%, 55%));
  1263. border-color: var(--mujs-sf-txt-color, hsl(12, 79%, 55%));
  1264. }
  1265. mujs-body table .frame[data-engine=sleazyfork] mujs-a, mujs-body table .frame[data-engine=greasyfork] mujs-a {
  1266. color: var(--mujs-gf-color, hsl(197, 100%, 50%));
  1267. }
  1268. mujs-body table .frame[data-engine=sleazyfork] mujs-a:hover, mujs-body table .frame[data-engine=greasyfork] mujs-a:hover {
  1269. color: var(--mujs-gf-btn-color, hsl(211, 87%, 56%));
  1270. }
  1271. mujs-body table .frame[data-engine=sleazyfork] mu-jsbtn, mujs-body table .frame[data-engine=greasyfork] mu-jsbtn {
  1272. background: var(--mujs-gf-color, hsl(204, 100%, 40%));
  1273. border-color: var(--mujs-gf-color, hsl(204, 100%, 40%));
  1274. }
  1275. mujs-body table .frame[data-engine=sleazyfork] mu-jsbtn:hover, mujs-body table .frame[data-engine=greasyfork] mu-jsbtn:hover {
  1276. background: var(--mujs-gf-btn-color, hsl(211, 87%, 56%));
  1277. border-color: var(--mujs-gf-btn-color, hsl(211, 87%, 56%));
  1278. }
  1279. mujs-body table .frame[data-good] mujs-a, mujs-body table .frame[data-author] mujs-a {
  1280. color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1281. }
  1282. mujs-body table .frame[data-good] mujs-a:hover, mujs-body table .frame[data-author] mujs-a:hover {
  1283. color: hsl(81, 56%, 43%);
  1284. }
  1285. mujs-body table .frame[data-good] .mujs-list, mujs-body table .frame[data-author] .mujs-list {
  1286. color: hsl(0, 0%, 100%);
  1287. }
  1288. mujs-body table .frame[data-good] mu-jsbtn, mujs-body table .frame[data-author] mu-jsbtn {
  1289. color: hsl(215, 47%, 24%);
  1290. background: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1291. border-color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1292. }
  1293. mujs-body table .frame[data-good] mu-jsbtn:hover, mujs-body table .frame[data-author] mu-jsbtn:hover {
  1294. background: hsl(81, 56%, 65%);
  1295. border-color: hsl(81, 56%, 65%);
  1296. }
  1297. mujs-body table .frame.translated:not([data-good], [data-author]) mujs-a {
  1298. color: hsl(249, 56%, 65%);
  1299. }
  1300. mujs-body table .frame.translated:not([data-good], [data-author]) mujs-a:hover {
  1301. color: hsl(249, 56%, 85%);
  1302. }
  1303. mujs-body table .frame.translated:not([data-good], [data-author]) mu-jsbtn {
  1304. color: hsl(215, 47%, 85%);
  1305. background: hsl(249, 56%, 65%);
  1306. border-color: hsl(249, 56%, 65%);
  1307. }
  1308. mujs-body table .frame.translated:not([data-good], [data-author]) mu-jsbtn:hover {
  1309. background: hsl(249, 56%, 65%);
  1310. border-color: hsl(249, 56%, 65%);
  1311. }
  1312. mujs-body table .frame .mujs-ratings[data-el=good] {
  1313. border-color: hsl(120, 50%, 40%);
  1314. background-color: hsla(120, 50%, 40%, 0.102);
  1315. color: hsl(120, 100%, 60%);
  1316. }
  1317. mujs-body table .frame .mujs-ratings[data-el=ok] {
  1318. border-color: hsl(60, 100%, 30%);
  1319. background-color: hsla(60, 100%, 30%, 0.102);
  1320. color: hsl(60, 100%, 50%);
  1321. }
  1322. mujs-body table .frame .mujs-ratings[data-el=bad] {
  1323. border-color: hsl(0, 100%, 30%);
  1324. background-color: hsla(0, 50%, 40%, 0.102);
  1325. color: hsl(0, 100%, 50%);
  1326. }
  1327. mujs-body table .frame svg {
  1328. width: 12px;
  1329. height: 12px;
  1330. fill: currentColor;
  1331. background: transparent;
  1332. }
  1333. mujs-body table .frame > td:not(.mujs-name) {
  1334. text-align: center;
  1335. }
  1336. mujs-body table .frame > .mujs-name > mujs-a {
  1337. width: -webkit-fit-content;
  1338. width: -moz-fit-content;
  1339. width: fit-content;
  1340. }
  1341. mujs-body table .frame > .mujs-name mu-jsbtn,
  1342. mujs-body table .frame > .mujs-name mu-js {
  1343. height: -webkit-fit-content;
  1344. height: -moz-fit-content;
  1345. height: fit-content;
  1346. }
  1347. mujs-body table .frame > .mujs-name > mu-jsbtn {
  1348. margin: auto;
  1349. }
  1350. mujs-body table .frame > .mujs-name > mujs-column > mu-jsbtn {
  1351. padding: 0px 7px;
  1352. }
  1353. @media screen and (max-width: 800px) {
  1354. mujs-body table .frame > .mujs-name > mujs-column > mu-jsbtn {
  1355. width: 100%;
  1356. }
  1357. }
  1358. mujs-body table .frame > .mujs-uframe > mujs-a {
  1359. font-size: 16px;
  1360. font-weight: 500;
  1361. padding-left: 0.5rem;
  1362. padding-right: 0.5rem;
  1363. }
  1364. mujs-body table .frame [data-el=more-info] > mujs-row {
  1365. gap: 0.25em;
  1366. }
  1367. mujs-body table .frame [data-el=matches] {
  1368. gap: 0.25em;
  1369. max-width: 40em;
  1370. }
  1371. mujs-body table .frame [data-el=matches] .mujs-grants {
  1372. display: inline-flex;
  1373. flex-flow: row wrap;
  1374. overflow: auto;
  1375. overflow-wrap: break-word;
  1376. text-overflow: ellipsis;
  1377. white-space: nowrap;
  1378. width: -webkit-fit-content;
  1379. width: -moz-fit-content;
  1380. width: fit-content;
  1381. max-height: 5em;
  1382. gap: 0.2em;
  1383. }
  1384. mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a {
  1385. display: inline;
  1386. }
  1387. mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:not([data-command]) {
  1388. cursor: default !important;
  1389. color: var(--mujs-txt-color, hsl(0, 0%, 100%));
  1390. }
  1391. mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a::after {
  1392. content: ", ";
  1393. color: var(--mujs-txt-color, hsl(0, 0%, 100%));
  1394. }
  1395. mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after {
  1396. content: "";
  1397. }
  1398. @media screen and (max-width: 800px) {
  1399. mujs-body table .frame [data-el=matches] {
  1400. width: 30em !important;
  1401. }
  1402. }
  1403. mujs-body table .frame [data-name=license] {
  1404. text-overflow: ellipsis;
  1405. overflow: hidden;
  1406. white-space: nowrap;
  1407. width: -webkit-fit-content;
  1408. width: -moz-fit-content;
  1409. width: fit-content;
  1410. }
  1411. @media screen and (max-width: 800px) {
  1412. mujs-body table .frame [data-name=license] {
  1413. width: 100% !important;
  1414. width: -moz-available !important;
  1415. width: -webkit-fill-available !important;
  1416. }
  1417. }
  1418.  
  1419. @media screen and (max-width: 1150px) {
  1420. .mujs-cfg {
  1421. margin: 0px auto 1rem auto !important;
  1422. }
  1423. }
  1424. .mujs-cfg {
  1425. height: fit-content;
  1426. height: -moz-fit-content;
  1427. height: -webkit-fit-content;
  1428. }
  1429. .mujs-cfg mujs-section {
  1430. border-radius: 16px;
  1431. padding: 0.5em;
  1432. }
  1433. .mujs-cfg mujs-section:nth-child(even) {
  1434. background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important;
  1435. }
  1436. .mujs-cfg mujs-section:nth-child(even) input,
  1437. .mujs-cfg mujs-section:nth-child(even) select {
  1438. background: var(--mujs-odd-row, hsl(222, 14%, 33%));
  1439. }
  1440. .mujs-cfg mujs-section:nth-child(even) select option {
  1441. background: var(--mujs-odd-row, hsl(222, 14%, 33%));
  1442. }
  1443. .mujs-cfg mujs-section:nth-child(even) select option:hover {
  1444. background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important;
  1445. }
  1446. .mujs-cfg mujs-section:nth-child(odd) {
  1447. background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important;
  1448. }
  1449. .mujs-cfg mujs-section:nth-child(odd) input,
  1450. .mujs-cfg mujs-section:nth-child(odd) select {
  1451. background: var(--mujs-even-row, hsl(222, 14%, 18%));
  1452. }
  1453. .mujs-cfg mujs-section:nth-child(odd) select option {
  1454. background: var(--mujs-even-row, hsl(222, 14%, 18%));
  1455. }
  1456. .mujs-cfg mujs-section:nth-child(odd) select option:hover {
  1457. background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important;
  1458. }
  1459. .mujs-cfg mujs-section[data-name=theme], .mujs-cfg mujs-section[data-name=exp], .mujs-cfg mujs-section[data-name=blacklist] {
  1460. display: flex;
  1461. justify-content: space-between;
  1462. flex-direction: column;
  1463. gap: 0.25em;
  1464. }
  1465. .mujs-cfg mujs-section[data-name=theme] > mujs-btn, .mujs-cfg mujs-section[data-name=exp] > mujs-btn, .mujs-cfg mujs-section[data-name=blacklist] > mujs-btn {
  1466. width: 100%;
  1467. width: -moz-available;
  1468. width: -webkit-fill-available;
  1469. }
  1470. .mujs-cfg mujs-section[data-name=theme] > mujs-btn:hover, .mujs-cfg mujs-section[data-name=exp] > mujs-btn:hover, .mujs-cfg mujs-section[data-name=blacklist] > mujs-btn:hover {
  1471. background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important;
  1472. }
  1473. .mujs-cfg mujs-section input[type=text]::-webkit-input-placeholder {
  1474. color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1475. }
  1476. .mujs-cfg mujs-section input[type=text]::-moz-placeholder {
  1477. color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1478. }
  1479. .mujs-cfg mujs-section input[type=text]:-ms-input-placeholder {
  1480. color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1481. }
  1482. .mujs-cfg mujs-section input[type=text]::-ms-input-placeholder {
  1483. color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1484. }
  1485. .mujs-cfg mujs-section input[type=text]::placeholder {
  1486. color: var(--mujs-placeholder, hsl(81, 56%, 54%));
  1487. }
  1488. .mujs-cfg mujs-section > label:not([data-blacklist]) {
  1489. display: flex;
  1490. justify-content: space-between;
  1491. }
  1492. .mujs-cfg mujs-section > label[data-blacklist] {
  1493. display: grid;
  1494. grid-auto-flow: column;
  1495. }
  1496. .mujs-cfg mujs-section > label[data-blacklist]:not(.new-list) {
  1497. grid-template-columns: repeat(2, 1fr);
  1498. }
  1499. .mujs-cfg mujs-section > label.new-list {
  1500. order: 999999999999;
  1501. }
  1502. .mujs-cfg mujs-section > label.new-list mujs-add {
  1503. font-size: 20px;
  1504. }
  1505. .mujs-cfg mujs-section > label input:not([type=checkbox]) {
  1506. font-size: 14px;
  1507. position: relative;
  1508. border-radius: 4px;
  1509. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1510. }
  1511. .mujs-cfg mujs-section select,
  1512. .mujs-cfg mujs-section select option {
  1513. color: var(--mujs-txt-color, hsl(0, 0%, 100%));
  1514. border: 1px solid transparent;
  1515. list-style: none;
  1516. outline-style: none;
  1517. pointer-events: auto;
  1518. }
  1519. .mujs-cfg mujs-section select {
  1520. text-align: center;
  1521. border-radius: 4px;
  1522. }
  1523. .mujs-cfg mujs-section > *.sub-section {
  1524. padding: 0.2em;
  1525. }
  1526. .mujs-cfg mujs-section > *.sub-section[data-engine] {
  1527. flex-wrap: wrap;
  1528. }
  1529. .mujs-cfg mujs-section > *.sub-section[data-engine] input {
  1530. width: 100%;
  1531. width: -moz-available;
  1532. width: -webkit-fill-available;
  1533. }
  1534. .mujs-cfg mujs-section > *.sub-section input[type=text] {
  1535. margin: 0.2em 0px;
  1536. }
  1537. .mujs-cfg .mujs-inlab {
  1538. position: relative;
  1539. width: 38px;
  1540. }
  1541. .mujs-cfg .mujs-inlab input[type=checkbox] {
  1542. display: none;
  1543. }
  1544. .mujs-cfg .mujs-inlab input[type=checkbox]:checked + label {
  1545. margin-left: 0;
  1546. background: var(--mujs-chck-color, hsla(0, 0%, 100%, 0.568));
  1547. }
  1548. .mujs-cfg .mujs-inlab input[type=checkbox]:checked + label:before {
  1549. right: 0px;
  1550. }
  1551. .mujs-cfg .mujs-inlab input[type=checkbox][data-name=greasyfork]:checked + label {
  1552. background: var(--mujs-gf-color, hsl(204, 100%, 40%));
  1553. }
  1554. .mujs-cfg .mujs-inlab input[type=checkbox][data-name=sleazyfork]:checked + label {
  1555. background: var(--mujs-sf-color, hsl(12, 86%, 50%));
  1556. }
  1557. .mujs-cfg .mujs-inlab input[type=checkbox][data-name=openuserjs]:checked + label {
  1558. background: var(--mujs-chck-open, hsla(12, 86%, 50%, 0.568));
  1559. }
  1560. .mujs-cfg .mujs-inlab input[type=checkbox][data-name=github]:checked + label {
  1561. background: var(--mujs-chck-git, hsla(213, 13%, 16%, 0.568));
  1562. }
  1563. .mujs-cfg .mujs-inlab label {
  1564. padding: 0;
  1565. display: block;
  1566. overflow: hidden;
  1567. height: 16px;
  1568. border-radius: 20px;
  1569. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1570. }
  1571. .mujs-cfg .mujs-inlab label:before {
  1572. content: "";
  1573. display: block;
  1574. width: 20px;
  1575. height: 20px;
  1576. margin: -2px;
  1577. background: var(--mujs-txt-color, hsl(0, 0%, 100%));
  1578. position: absolute;
  1579. top: 0;
  1580. right: 20px;
  1581. border-radius: 20px;
  1582. }
  1583. .mujs-cfg .mujs-sty-flex mujs-btn {
  1584. margin: auto;
  1585. }
  1586. .mujs-cfg .mujs-sty-flex mujs-btn[data-command=reset] {
  1587. background: var(--mujs-sf-btn-color, hsl(12, 86%, 50%));
  1588. border-color: var(--mujs-sf-btn-color, hsl(12, 86%, 50%));
  1589. }
  1590. .mujs-cfg .mujs-sty-flex mujs-btn[data-command=reset]:hover {
  1591. background: var(--mujs-sf-txt-color, hsl(12, 79%, 55%));
  1592. border-color: var(--mujs-sf-txt-color, hsl(12, 79%, 55%));
  1593. }
  1594. .mujs-cfg .mujs-sty-flex mujs-btn[data-command=save] {
  1595. background: var(--mujs-gf-color, hsl(204, 100%, 40%));
  1596. border-color: var(--mujs-gf-color, hsl(204, 100%, 40%));
  1597. }
  1598. .mujs-cfg .mujs-sty-flex mujs-btn[data-command=save]:hover {
  1599. background: var(--mujs-gf-btn-color, hsl(211, 87%, 56%));
  1600. border-color: var(--mujs-gf-btn-color, hsl(211, 87%, 56%));
  1601. }
  1602. .mujs-cfg:not(.webext-page) {
  1603. margin: 1rem 25rem;
  1604. }
  1605. @media screen and (max-height: 720px) {
  1606. .mujs-cfg:not(.webext-page) {
  1607. height: 100%;
  1608. height: -moz-available;
  1609. height: -webkit-fill-available;
  1610. width: 100%;
  1611. width: -moz-available;
  1612. width: -webkit-fill-available;
  1613. overflow-x: auto;
  1614. padding: 0.5em;
  1615. }
  1616. }
  1617.  
  1618. mujs-a {
  1619. display: inline-block;
  1620. }
  1621.  
  1622. .mujs-name {
  1623. display: flex;
  1624. flex-flow: column wrap;
  1625. gap: 0.5em;
  1626. }
  1627. .mujs-name span {
  1628. font-size: 0.8em !important;
  1629. }
  1630.  
  1631. mujs-btn {
  1632. font-style: normal;
  1633. font-weight: 500;
  1634. font-variant: normal;
  1635. text-transform: none;
  1636. text-rendering: auto;
  1637. text-align: center;
  1638. border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));
  1639. font-size: 16px;
  1640. border-radius: 4px;
  1641. line-height: 1;
  1642. padding: 6px 15px;
  1643. }
  1644. mujs-btn svg {
  1645. width: 14px;
  1646. height: 14px;
  1647. fill: var(--mujs-txt-color, hsl(0, 0%, 100%));
  1648. }
  1649.  
  1650. mu-jsbtn {
  1651. font-size: 14px;
  1652. border-radius: 4px;
  1653. font-style: normal;
  1654. padding: 7px 15%;
  1655. font-weight: 400;
  1656. font-variant: normal;
  1657. line-height: normal;
  1658. display: block;
  1659. text-align: center;
  1660. }
  1661.  
  1662. mujs-a,
  1663. mu-jsbtn,
  1664. .mujs-pointer,
  1665. .mujs-cfg mujs-section *:not(input[type=text], input[type=number], [data-theme], [data-blacklist]),
  1666. .mainbtn,
  1667. .mainframe,
  1668. mujs-btn {
  1669. cursor: pointer !important;
  1670. }
  1671. `;
  1672. /******************************************************************************/
  1673. // #region Console
  1674. const dbg = (...msg) => {
  1675. const dt = new Date();
  1676. console.debug(
  1677. '[%cMagic Userscript+%c] %cDBG',
  1678. 'color: rgb(29, 155, 240);',
  1679. '',
  1680. 'color: rgb(255, 212, 0);',
  1681. `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`,
  1682. ...msg
  1683. );
  1684. };
  1685. const err = (...msg) => {
  1686. console.error(
  1687. '[%cMagic Userscript+%c] %cERROR',
  1688. 'color: rgb(29, 155, 240);',
  1689. '',
  1690. 'color: rgb(249, 24, 128);',
  1691. ...msg
  1692. );
  1693. const a = typeof alert !== 'undefined' && alert;
  1694. for (const ex of msg) {
  1695. if (typeof ex === 'object' && 'cause' in ex && a) {
  1696. a(`[Magic Userscript+] (${ex.cause}) ${ex.message}`);
  1697. }
  1698. }
  1699. };
  1700. const info = (...msg) => {
  1701. console.info(
  1702. '[%cMagic Userscript+%c] %cINF',
  1703. 'color: rgb(29, 155, 240);',
  1704. '',
  1705. 'color: rgb(0, 186, 124);',
  1706. ...msg
  1707. );
  1708. };
  1709. const log = (...msg) => {
  1710. console.log(
  1711. '[%cMagic Userscript+%c] %cLOG',
  1712. 'color: rgb(29, 155, 240);',
  1713. '',
  1714. 'color: rgb(219, 160, 73);',
  1715. ...msg
  1716. );
  1717. };
  1718. // #endregion
  1719.  
  1720. /**
  1721. * @type { import("../typings/types.d.ts").config }
  1722. */
  1723. let cfg = {};
  1724.  
  1725. // #region Validators
  1726. /**
  1727. * @type { import("../typings/types.d.ts").objToStr }
  1728. */
  1729. const objToStr = (obj) => Object.prototype.toString.call(obj);
  1730. /**
  1731. * @type { import("../typings/types.d.ts").isRegExp }
  1732. */
  1733. const isRegExp = (obj) => {
  1734. const s = objToStr(obj);
  1735. return s.includes('RegExp');
  1736. };
  1737. /**
  1738. * @type { import("../typings/types.d.ts").isElem }
  1739. */
  1740. const isElem = (obj) => {
  1741. const s = objToStr(obj);
  1742. return s.includes('Element');
  1743. };
  1744. /**
  1745. * @type { import("../typings/types.d.ts").isObj }
  1746. */
  1747. const isObj = (obj) => {
  1748. const s = objToStr(obj);
  1749. return s.includes('Object');
  1750. };
  1751. /**
  1752. * @type { import("../typings/types.d.ts").isFN }
  1753. */
  1754. const isFN = (obj) => {
  1755. const s = objToStr(obj);
  1756. return s.includes('Function');
  1757. };
  1758. /**
  1759. * @type { import("../typings/types.d.ts").isNull }
  1760. */
  1761. const isNull = (obj) => {
  1762. return Object.is(obj, null) || Object.is(obj, undefined);
  1763. };
  1764. /**
  1765. * @type { import("../typings/types.d.ts").isBlank }
  1766. */
  1767. const isBlank = (obj) => {
  1768. return (
  1769. (typeof obj === 'string' && Object.is(obj.trim(), '')) ||
  1770. ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) ||
  1771. (Array.isArray(obj) && Object.is(obj.length, 0)) ||
  1772. (isObj(obj) && Object.is(Object.keys(obj).length, 0))
  1773. );
  1774. };
  1775. /**
  1776. * @type { import("../typings/types.d.ts").isEmpty }
  1777. */
  1778. const isEmpty = (obj) => {
  1779. return isNull(obj) || isBlank(obj);
  1780. };
  1781. // #endregion
  1782.  
  1783. // #region Globals
  1784. /**
  1785. * https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/global-this.js
  1786. * @returns {typeof globalThis}
  1787. */
  1788. function globalWin() {
  1789. const check = function (it) {
  1790. return it && it.Math === Math && it;
  1791. };
  1792. return (
  1793. check(typeof globalThis == 'object' && globalThis) ||
  1794. check(typeof window == 'object' && window) ||
  1795. check(typeof self == 'object' && self) ||
  1796. check(typeof this == 'object' && this) ||
  1797. (function () {
  1798. return this;
  1799. })() ||
  1800. Function('return this')()
  1801. );
  1802. }
  1803. /** @type { import("../typings/UserJS.d.ts").safeSelf } */
  1804. function safeSelf() {
  1805. if (userjs.safeSelf) {
  1806. return userjs.safeSelf;
  1807. }
  1808. const g = globalWin();
  1809. /** @type { import("../typings/UserJS.d.ts").safeHandles } */
  1810. const safe = {
  1811. XMLHttpRequest: g.XMLHttpRequest,
  1812. CustomEvent: g.CustomEvent,
  1813. createElement: g.document.createElement.bind(g.document),
  1814. createElementNS: g.document.createElementNS.bind(g.document),
  1815. createTextNode: g.document.createTextNode.bind(g.document),
  1816. setTimeout: g.setTimeout,
  1817. clearTimeout: g.clearTimeout,
  1818. navigator: g.navigator,
  1819. scheduler: {
  1820. postTask(callback, options) {
  1821. if ('scheduler' in g && 'postTask' in g.scheduler) {
  1822. return g.scheduler.postTask(callback, options);
  1823. }
  1824.  
  1825. options = Object.assign({}, options);
  1826.  
  1827. if (options.delay === undefined) options.delay = 0;
  1828. options.delay = Number(options.delay);
  1829. if (options.delay < 0) {
  1830. return Promise.reject(new TypeError('"delay" must be a positive number.'));
  1831. }
  1832. return new Promise((resolve) => {
  1833. g.setTimeout(() => {
  1834. resolve(callback());
  1835. }, options.delay);
  1836. });
  1837. },
  1838. yield() {
  1839. if ('scheduler' in g && 'yield' in g.scheduler) {
  1840. scheduler.yield();
  1841. return g.scheduler.yield();
  1842. }
  1843. return new Promise((resolve) => {
  1844. g.setTimeout(resolve, 0);
  1845. });
  1846. }
  1847. }
  1848. };
  1849. for (const [k, v] of Object.entries(safe)) {
  1850. if (k === 'scheduler') {
  1851. continue;
  1852. } else if (k === 'navigator') {
  1853. continue;
  1854. } else if (isFN(v)) {
  1855. continue;
  1856. }
  1857. err({ message: `Safe handles "${k}" returned "${v}"`, cause: 'safeSelf' });
  1858. }
  1859. userjs.safeSelf = safe;
  1860. return userjs.safeSelf;
  1861. }
  1862. // #endregion
  1863.  
  1864. const BLANK_PAGE = 'about:blank';
  1865. // Lets highlight me :)
  1866. const authorID = 166061;
  1867. /**
  1868. * Some UserJS I personally enjoy - `https://greatest.deepsurf.us/scripts/{{id}}`
  1869. */
  1870. const goodUserJS = [
  1871. 33005,
  1872. 394820,
  1873. 438684,
  1874. 4870,
  1875. 394420,
  1876. 25068,
  1877. 483444,
  1878. 1682,
  1879. 22587,
  1880. 789,
  1881. 28497,
  1882. 386908,
  1883. 24204,
  1884. 404443,
  1885. 4336,
  1886. 368183,
  1887. 393396,
  1888. 473830,
  1889. 12179,
  1890. 423001,
  1891. 376510,
  1892. 23840,
  1893. 40525,
  1894. 6456,
  1895. 'https://openuserjs.org/install/Patabugen/Always_Remember_Me.user.js',
  1896. 'https://openuserjs.org/install/nokeya/Direct_links_out.user.js',
  1897. 'https://github.com/jijirae/y2monkey/raw/main/y2monkey.user.js',
  1898. 'https://github.com/jijirae/r2monkey/raw/main/r2monkey.user.js',
  1899. 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/Manga_OnlineViewer.user.js',
  1900. 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js',
  1901. 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js'
  1902. ];
  1903. /** Remove UserJS from banned accounts */
  1904. const badUserJS = [478597];
  1905. /** Unsupport host for search engines */
  1906. const engineUnsupported = {
  1907. greasyfork: ['pornhub.com'],
  1908. sleazyfork: ['pornhub.com'],
  1909. openuserjs: [],
  1910. github: []
  1911. };
  1912. const getUAData = () => {
  1913. if (userjs.isMobile !== undefined) {
  1914. return userjs.isMobile;
  1915. }
  1916. try {
  1917. const { navigator } = safeSelf();
  1918. if (navigator) {
  1919. const { userAgent, userAgentData } = navigator;
  1920. const { platform, mobile } = userAgentData ? Object(userAgentData) : {};
  1921. userjs.isMobile =
  1922. /Mobile|Tablet/.test(userAgent ? String(userAgent) : '') ||
  1923. Boolean(mobile) ||
  1924. /Android|Apple/.test(platform ? String(platform) : '');
  1925. } else {
  1926. userjs.isMobile = false;
  1927. }
  1928. } catch (ex) {
  1929. userjs.isMobile = false;
  1930. ex.cause = 'getUAData';
  1931. err(ex);
  1932. }
  1933. return userjs.isMobile;
  1934. };
  1935. const isMobile = getUAData();
  1936. const isGM = typeof GM !== 'undefined';
  1937. const builtinList = {
  1938. local: /localhost|router|gov|(\d+\.){3}\d+/,
  1939. finance:
  1940. /school|pay|bank|money|cart|checkout|authorize|bill|wallet|venmo|zalo|skrill|bluesnap|coin|crypto|currancy|insurance|finance/,
  1941. social: /login|join|signin|signup|sign-up|password|reset|password_reset/,
  1942. unsupported: {
  1943. host: 'fakku.net',
  1944. pathname: '/hentai/.+/read/page/.+'
  1945. }
  1946. };
  1947. // #region DEFAULT_CONFIG
  1948. /**
  1949. * @type { import("../typings/types.d.ts").config }
  1950. */
  1951. const DEFAULT_CONFIG = {
  1952. autofetch: true,
  1953. autoinject: true,
  1954. autoSort: 'daily_installs',
  1955. clearTabCache: true,
  1956. cache: true,
  1957. autoexpand: false,
  1958. filterlang: false,
  1959. sleazyredirect: false,
  1960. time: 10000,
  1961. blacklist: ['userjs-local', 'userjs-finance', 'userjs-social', 'userjs-unsupported'],
  1962. preview: {
  1963. code: false,
  1964. metadata: false
  1965. },
  1966. engines: [
  1967. {
  1968. enabled: true,
  1969. name: 'greasyfork',
  1970. query: encodeURIComponent('https://greatest.deepsurf.us/scripts/by-site/{host}.json?language=all')
  1971. },
  1972. {
  1973. enabled: false,
  1974. name: 'sleazyfork',
  1975. query: encodeURIComponent('https://sleazyfork.org/scripts/by-site/{host}.json?language=all')
  1976. },
  1977. {
  1978. enabled: false,
  1979. name: 'openuserjs',
  1980. query: encodeURIComponent('https://openuserjs.org/?q={host}')
  1981. },
  1982. {
  1983. enabled: false,
  1984. name: 'github',
  1985. token: '',
  1986. query: encodeURIComponent(
  1987. 'https://api.github.com/search/code?q="// ==UserScript=="+{host}+ "// ==/UserScript=="+in:file+language:js&per_page=30'
  1988. )
  1989. }
  1990. ],
  1991. theme: {
  1992. 'even-row': '',
  1993. 'odd-row': '',
  1994. 'even-err': '',
  1995. 'odd-err': '',
  1996. 'background-color': '',
  1997. 'gf-color': '',
  1998. 'sf-color': '',
  1999. 'border-b-color': '',
  2000. 'gf-btn-color': '',
  2001. 'sf-btn-color': '',
  2002. 'sf-txt-color': '',
  2003. 'txt-color': '',
  2004. 'chck-color': '',
  2005. 'chck-gf': '',
  2006. 'chck-git': '',
  2007. 'chck-open': '',
  2008. placeholder: '',
  2009. 'position-top': '',
  2010. 'position-bottom': '',
  2011. 'position-left': '',
  2012. 'position-right': '',
  2013. 'font-family': ''
  2014. },
  2015. recommend: {
  2016. author: true,
  2017. others: true
  2018. },
  2019. filters: {
  2020. ASCII: {
  2021. enabled: false,
  2022. name: 'Non-ASCII',
  2023. regExp: '[^\\x00-\\x7F\\s]+'
  2024. },
  2025. Latin: {
  2026. enabled: false,
  2027. name: 'Non-Latin',
  2028. regExp: '[^\\u0000-\\u024F\\u2000-\\u214F\\s]+'
  2029. },
  2030. Games: {
  2031. enabled: false,
  2032. name: 'Games',
  2033. flag: 'iu',
  2034. regExp:
  2035. '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'
  2036. },
  2037. SocialNetworks: {
  2038. enabled: false,
  2039. name: 'Social Networks',
  2040. flag: 'iu',
  2041. regExp:
  2042. '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'
  2043. },
  2044. Clutter: {
  2045. enabled: false,
  2046. name: 'Clutter',
  2047. flag: 'iu',
  2048. regExp:
  2049. "^\\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)?"
  2050. }
  2051. }
  2052. };
  2053. // #endregion
  2054. // #region i18n
  2055. class i18nHandler {
  2056. constructor() {
  2057. if (userjs.pool !== undefined) {
  2058. return this;
  2059. }
  2060. userjs.pool = new Map();
  2061. for (const [k, v] of Object.entries(translations)) {
  2062. if (!userjs.pool.has(k)) userjs.pool.set(k, v);
  2063. }
  2064. }
  2065. /**
  2066. * @param {string | Date | number} str
  2067. */
  2068. toDate(str = '') {
  2069. const { navigator } = safeSelf();
  2070. return new Intl.DateTimeFormat(navigator.language).format(
  2071. typeof str === 'string' ? new Date(str) : str
  2072. );
  2073. }
  2074. /**
  2075. * @param {number | bigint} number
  2076. */
  2077. toNumber(number) {
  2078. const { navigator } = safeSelf();
  2079. return new Intl.NumberFormat(navigator.language).format(number);
  2080. }
  2081. /**
  2082. * @type { import("../typings/UserJS.d.ts").i18n$ }
  2083. */
  2084. i18n$(key) {
  2085. const { navigator } = safeSelf();
  2086. const current = navigator.language.split('-')[0] ?? 'en';
  2087. return userjs.pool.get(current)?.[key] ?? 'Invalid Key';
  2088. }
  2089. }
  2090. const language = new i18nHandler();
  2091. const { i18n$ } = language;
  2092. // #endregion
  2093. // #region Utilities
  2094. const union = (...arr) => [...new Set(arr.flat())];
  2095. /**
  2096. * @type { import("../typings/types.d.ts").qs }
  2097. */
  2098. const qs = (selector, root) => {
  2099. try {
  2100. return (root || document).querySelector(selector);
  2101. } catch (ex) {
  2102. err(ex);
  2103. }
  2104. return null;
  2105. };
  2106. /**
  2107. * @type { import("../typings/types.d.ts").qsA }
  2108. */
  2109. const qsA = (selectors, root) => {
  2110. try {
  2111. return (root || document).querySelectorAll(selectors);
  2112. } catch (ex) {
  2113. err(ex);
  2114. }
  2115. return [];
  2116. };
  2117. /**
  2118. * @type { import("../typings/types.d.ts").normalizeTarget }
  2119. */
  2120. const normalizeTarget = (target, toQuery = true, root) => {
  2121. if (Object.is(target, null) || Object.is(target, undefined)) {
  2122. return [];
  2123. }
  2124. if (Array.isArray(target)) {
  2125. return target;
  2126. }
  2127. if (typeof target === 'string') {
  2128. return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target];
  2129. }
  2130. if (isElem(target)) {
  2131. return [target];
  2132. }
  2133. return Array.from(target);
  2134. };
  2135. /**
  2136. * @type { import("../typings/types.d.ts").ael }
  2137. */
  2138. const ael = (el, type, listener, options = {}) => {
  2139. try {
  2140. for (const elem of normalizeTarget(el)) {
  2141. if (!elem) {
  2142. continue;
  2143. }
  2144. if (isMobile && type === 'click') {
  2145. elem.addEventListener('touchstart', listener, options);
  2146. continue;
  2147. }
  2148. elem.addEventListener(type, listener, options);
  2149. }
  2150. } catch (ex) {
  2151. ex.cause = 'ael';
  2152. err(ex);
  2153. }
  2154. };
  2155. /**
  2156. * @type { import("../typings/types.d.ts").formAttrs }
  2157. */
  2158. const formAttrs = (elem, attr = {}) => {
  2159. if (!elem) {
  2160. return elem;
  2161. }
  2162. for (const key in attr) {
  2163. if (typeof attr[key] === 'object') {
  2164. formAttrs(elem[key], attr[key]);
  2165. } else if (isFN(attr[key])) {
  2166. if (/^on/.test(key)) {
  2167. elem[key] = attr[key];
  2168. continue;
  2169. }
  2170. ael(elem, key, attr[key]);
  2171. } else if (key === 'class') {
  2172. elem.className = attr[key];
  2173. } else {
  2174. elem[key] = attr[key];
  2175. }
  2176. }
  2177. return elem;
  2178. };
  2179. /**
  2180. * @type { import("../typings/types.d.ts").make }
  2181. */
  2182. const make = (tagName, cname, attrs) => {
  2183. let el;
  2184. try {
  2185. const { createElement } = safeSelf();
  2186. el = createElement(tagName);
  2187. if (!isEmpty(cname)) {
  2188. if (typeof cname === 'string') {
  2189. el.className = cname;
  2190. } else if (isObj(cname)) {
  2191. formAttrs(el, cname);
  2192. }
  2193. }
  2194. if (!isEmpty(attrs)) {
  2195. if (typeof attrs === 'string') {
  2196. el.textContent = attrs;
  2197. } else if (isObj(attrs)) {
  2198. formAttrs(el, attrs);
  2199. }
  2200. }
  2201. } catch (ex) {
  2202. ex.cause = 'make';
  2203. err(ex);
  2204. }
  2205. return el;
  2206. };
  2207.  
  2208. /**
  2209. * @type { import("../typings/UserJS.d.ts").getGMInfo }
  2210. */
  2211. const getGMInfo = () => {
  2212. if (isGM) {
  2213. if (isObj(GM.info)) {
  2214. return GM.info;
  2215. } else if (isObj(GM_info)) {
  2216. return GM_info;
  2217. }
  2218. }
  2219. return {
  2220. script: {
  2221. icon: '',
  2222. name: 'Magic Userscript+',
  2223. namespace: 'https://github.com/magicoflolis/Userscript-Plus',
  2224. updateURL: 'https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.js',
  2225. version: 'Bookmarklet',
  2226. bugs: 'https://github.com/magicoflolis/Userscript-Plus/issues'
  2227. }
  2228. };
  2229. };
  2230. const $info = getGMInfo();
  2231. // #endregion
  2232. /**
  2233. * @type { import("../typings/types.d.ts").dom }
  2234. */
  2235. const dom = {
  2236. attr(target, attr, value = undefined) {
  2237. for (const elem of normalizeTarget(target)) {
  2238. if (value === undefined) {
  2239. return elem.getAttribute(attr);
  2240. }
  2241. if (value === null) {
  2242. elem.removeAttribute(attr);
  2243. } else {
  2244. elem.setAttribute(attr, value);
  2245. }
  2246. }
  2247. },
  2248. prop(target, prop, value = undefined) {
  2249. for (const elem of normalizeTarget(target)) {
  2250. if (value === undefined) {
  2251. return elem[prop];
  2252. }
  2253. elem[prop] = value;
  2254. }
  2255. },
  2256. text(target, text) {
  2257. const targets = normalizeTarget(target);
  2258. if (text === undefined) {
  2259. return targets.length !== 0 ? targets[0].textContent : undefined;
  2260. }
  2261. for (const elem of targets) {
  2262. elem.textContent = text;
  2263. }
  2264. },
  2265. cl: {
  2266. add(target, token) {
  2267. token = Array.isArray(token) ? token : [token];
  2268. return normalizeTarget(target).some((elem) => elem.classList.add(...token));
  2269. },
  2270. remove(target, token) {
  2271. token = Array.isArray(token) ? token : [token];
  2272. return normalizeTarget(target).some((elem) => elem.classList.remove(...token));
  2273. },
  2274. toggle(target, token, force) {
  2275. let r;
  2276. for (const elem of normalizeTarget(target)) {
  2277. r = elem.classList.toggle(token, force);
  2278. }
  2279. return r;
  2280. },
  2281. has(target, token) {
  2282. return normalizeTarget(target).some((elem) => elem.classList.contains(token));
  2283. }
  2284. }
  2285. };
  2286. class Memorize {
  2287. constructor() {
  2288. /**
  2289. * @type {Map<string, Map<string, any>>}
  2290. */
  2291. this.store = new Map();
  2292. /**
  2293. * @type { { [key: string]: Map<string, any>; userjs: Map<number, import("../typings/types.d.ts").GSForkQuery> } }
  2294. */
  2295. this.maps = {};
  2296. this.create('cfg', 'container', 'userjs');
  2297. }
  2298. /**
  2299. * @template { string } S
  2300. * @param { ...S } maps
  2301. * @returns { S | S[] }
  2302. */
  2303. create(...maps) {
  2304. const resp = [];
  2305. for (const key of maps) {
  2306. if (this.store.has(key)) {
  2307. return this.store.get(key);
  2308. }
  2309. const m = new Map();
  2310. this.store.set(key, m);
  2311. this.maps[key] = m;
  2312. resp.push(this.store.get(key));
  2313. }
  2314. return resp.length >= 2 ? resp : resp[0];
  2315. }
  2316. }
  2317. const memory = new Memorize();
  2318. //#region Icon SVGs
  2319. const iconSVG = {
  2320. close: {
  2321. viewBox: '0 0 384 512',
  2322. html: '<path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/>'
  2323. },
  2324. code: {
  2325. viewBox: '0 0 640 512',
  2326. html: '<path d="M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"/>'
  2327. },
  2328. collapse: {
  2329. viewBox: '0 0 448 512',
  2330. html: '<path d="M160 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96zM32 320c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM352 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 320c-17.7 0-32 14.3-32 32l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-96 0z"/>'
  2331. },
  2332. download: {
  2333. viewBox: '0 0 384 512',
  2334. html: '<path d="M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-288-128 0c-17.7 0-32-14.3-32-32L224 0 64 0zM256 0l0 128 128 0L256 0zM216 232l0 102.1 31-31c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-72 72c-9.4 9.4-24.6 9.4-33.9 0l-72-72c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l31 31L168 232c0-13.3 10.7-24 24-24s24 10.7 24 24z"/>'
  2335. },
  2336. expand: {
  2337. viewBox: '0 0 448 512',
  2338. html: '<path d="M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z"/>'
  2339. },
  2340. gear: {
  2341. viewBox: '0 0 512 512',
  2342. html: '<path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/>'
  2343. },
  2344. github: {
  2345. viewBox: '0 0 496 512',
  2346. html: '<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>'
  2347. },
  2348. globe: {
  2349. viewBox: '0 0 512 512',
  2350. html: '<path d="M352 256c0 22.2-1.2 43.6-3.3 64l-185.3 0c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64l185.3 0c2.2 20.4 3.3 41.8 3.3 64zm28.8-64l123.1 0c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64l-123.1 0c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32l-116.7 0c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0l-176.6 0c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0L18.6 160C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192l123.1 0c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64L8.1 320C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6l176.6 0c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352l116.7 0zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6l116.7 0z"/>'
  2351. },
  2352. install: {
  2353. viewBox: '0 0 512 512',
  2354. html: '<path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 242.7-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7 288 32zM64 352c-35.3 0-64 28.7-64 64l0 32c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-32c0-35.3-28.7-64-64-64l-101.5 0-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352 64 352zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/>'
  2355. },
  2356. issue: {
  2357. viewBox: '0 0 512 512',
  2358. html: '<path d="M256 0c53 0 96 43 96 96l0 3.6c0 15.7-12.7 28.4-28.4 28.4l-135.1 0c-15.7 0-28.4-12.7-28.4-28.4l0-3.6c0-53 43-96 96-96zM41.4 105.4c12.5-12.5 32.8-12.5 45.3 0l64 64c.7 .7 1.3 1.4 1.9 2.1c14.2-7.3 30.4-11.4 47.5-11.4l112 0c17.1 0 33.2 4.1 47.5 11.4c.6-.7 1.2-1.4 1.9-2.1l64-64c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-64 64c-.7 .7-1.4 1.3-2.1 1.9c6.2 12 10.1 25.3 11.1 39.5l64.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c0 24.6-5.5 47.8-15.4 68.6c2.2 1.3 4.2 2.9 6 4.8l64 64c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-63.1-63.1c-24.5 21.8-55.8 36.2-90.3 39.6L272 240c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 239.2c-34.5-3.4-65.8-17.8-90.3-39.6L86.6 502.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l64-64c1.9-1.9 3.9-3.4 6-4.8C101.5 367.8 96 344.6 96 320l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64.3 0c1.1-14.1 5-27.5 11.1-39.5c-.7-.6-1.4-1.2-2.1-1.9l-64-64c-12.5-12.5-12.5-32.8 0-45.3z"/>'
  2359. },
  2360. minus: {
  2361. viewBox: '0 0 448 512',
  2362. html: '<path d="M432 256c0 17.7-14.3 32-32 32L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z"/>'
  2363. },
  2364. nav: {
  2365. viewBox: '0 0 448 512',
  2366. html: '<path d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"/>'
  2367. },
  2368. pager: {
  2369. viewBox: '0 0 512 512',
  2370. html: '<path d="M0 128C0 92.7 28.7 64 64 64l384 0c35.3 0 64 28.7 64 64l0 256c0 35.3-28.7 64-64 64L64 448c-35.3 0-64-28.7-64-64L0 128zm64 32l0 64c0 17.7 14.3 32 32 32l320 0c17.7 0 32-14.3 32-32l0-64c0-17.7-14.3-32-32-32L96 128c-17.7 0-32 14.3-32 32zM80 320c-13.3 0-24 10.7-24 24s10.7 24 24 24l56 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-56 0zm136 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l48 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-48 0z"/>'
  2371. },
  2372. verified: {
  2373. viewBox: '0 0 56 56',
  2374. fill: 'currentColor',
  2375. stroke: 'currentColor',
  2376. html: '<g stroke-width="0"/><g stroke-linecap="round" stroke-linejoin="round"/><g><path d="M 23.6641 52.3985 C 26.6407 55.375 29.3594 55.3516 32.3126 52.3985 L 35.9219 48.8125 C 36.2969 48.4610 36.6250 48.3203 37.1172 48.3203 L 42.1797 48.3203 C 46.3749 48.3203 48.3204 46.3985 48.3204 42.1797 L 48.3204 37.1172 C 48.3204 36.625 48.4610 36.2969 48.8124 35.9219 L 52.3749 32.3125 C 55.3749 29.3594 55.3514 26.6407 52.3749 23.6641 L 48.8124 20.0547 C 48.4610 19.7031 48.3204 19.3516 48.3204 18.8829 L 48.3204 13.7969 C 48.3204 9.625 46.3985 7.6563 42.1797 7.6563 L 37.1172 7.6563 C 36.6250 7.6563 36.2969 7.5391 35.9219 7.1875 L 32.3126 3.6016 C 29.3594 .6250 26.6407 .6485 23.6641 3.6016 L 20.0547 7.1875 C 19.7032 7.5391 19.3516 7.6563 18.8828 7.6563 L 13.7969 7.6563 C 9.6016 7.6563 7.6563 9.5782 7.6563 13.7969 L 7.6563 18.8829 C 7.6563 19.3516 7.5391 19.7031 7.1876 20.0547 L 3.6016 23.6641 C .6251 26.6407 .6485 29.3594 3.6016 32.3125 L 7.1876 35.9219 C 7.5391 36.2969 7.6563 36.625 7.6563 37.1172 L 7.6563 42.1797 C 7.6563 46.3750 9.6016 48.3203 13.7969 48.3203 L 18.8828 48.3203 C 19.3516 48.3203 19.7032 48.4610 20.0547 48.8125 Z M 26.2891 49.7734 L 21.8828 45.3438 C 21.3672 44.8047 20.8282 44.5938 20.1016 44.5938 L 13.7969 44.5938 C 11.7110 44.5938 11.3828 44.2656 11.3828 42.1797 L 11.3828 35.875 C 11.3828 35.1719 11.1719 34.6329 10.6563 34.1172 L 6.2266 29.7109 C 4.7501 28.2109 4.7501 27.7891 6.2266 26.2891 L 10.6563 21.8829 C 11.1719 21.3672 11.3828 20.8282 11.3828 20.1016 L 11.3828 13.7969 C 11.3828 11.6875 11.6876 11.3829 13.7969 11.3829 L 20.1016 11.3829 C 20.8282 11.3829 21.3672 11.1953 21.8828 10.6563 L 26.2891 6.2266 C 27.7891 4.7500 28.2110 4.7500 29.7110 6.2266 L 34.1172 10.6563 C 34.6328 11.1953 35.1719 11.3829 35.8750 11.3829 L 42.1797 11.3829 C 44.2657 11.3829 44.5938 11.7109 44.5938 13.7969 L 44.5938 20.1016 C 44.5938 20.8282 44.8282 21.3672 45.3439 21.8829 L 49.7733 26.2891 C 51.2498 27.7891 51.2498 28.2109 49.7733 29.7109 L 45.3439 34.1172 C 44.8282 34.6329 44.5938 35.1719 44.5938 35.875 L 44.5938 42.1797 C 44.5938 44.2656 44.2657 44.5938 42.1797 44.5938 L 35.8750 44.5938 C 35.1719 44.5938 34.6328 44.8047 34.1172 45.3438 L 29.7110 49.7734 C 28.2110 51.2500 27.7891 51.2500 26.2891 49.7734 Z M 24.3438 39.2266 C 25.0235 39.2266 25.5391 38.9453 25.8907 38.5234 L 38.8985 20.3360 C 39.1563 19.9609 39.2969 19.5391 39.2969 19.1407 C 39.2969 18.1094 38.5001 17.2891 37.4219 17.2891 C 36.6485 17.2891 36.2266 17.5469 35.7579 18.2266 L 24.2735 34.3985 L 18.3438 27.8594 C 17.9454 27.4141 17.5001 27.2266 16.9141 27.2266 C 15.7657 27.2266 14.9454 28.0000 14.9454 29.0782 C 14.9454 29.5469 15.1094 29.9922 15.4376 30.3203 L 22.8907 38.6172 C 23.2423 38.9922 23.6876 39.2266 24.3438 39.2266 Z"/></g>'
  2377. },
  2378. refresh: {
  2379. viewBox: '0 0 512 512',
  2380. fill: 'currentColor',
  2381. html: '<path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/>'
  2382. },
  2383. load(type, container) {
  2384. const { createElementNS } = safeSelf();
  2385. const svgElem = createElementNS('http://www.w3.org/2000/svg', 'svg');
  2386. for (const [k, v] of Object.entries(iconSVG[type])) {
  2387. if (k === 'html') {
  2388. continue;
  2389. }
  2390. svgElem.setAttributeNS(null, k, v);
  2391. }
  2392. try {
  2393. if (typeof iconSVG[type].html === 'string') {
  2394. svgElem.innerHTML = iconSVG[type].html;
  2395. dom.attr(svgElem, 'id', `mujs_${type ?? 'Unknown'}`);
  2396. }
  2397. // eslint-disable-next-line no-unused-vars
  2398. } catch (ex) {
  2399. /* empty */
  2400. }
  2401. if (container) {
  2402. container.appendChild(svgElem);
  2403. return svgElem;
  2404. }
  2405. return svgElem.outerHTML;
  2406. }
  2407. };
  2408. //#endregion
  2409. /**
  2410. * @type { import("../typings/UserJS.d.ts").StorageSystem }
  2411. */
  2412. const StorageSystem = {
  2413. prefix: 'MUJS',
  2414. events: new Set(),
  2415. getItem(key) {
  2416. return window.localStorage.getItem(key);
  2417. },
  2418. has(key) {
  2419. return !isNull(this.getItem(key));
  2420. },
  2421. setItem(key, value) {
  2422. window.localStorage.setItem(key, value);
  2423. },
  2424. remove(key) {
  2425. window.localStorage.removeItem(key);
  2426. },
  2427. addListener(name, callback) {
  2428. if (isGM) {
  2429. let GMType;
  2430. if (isFN(GM.addValueChangeListener)) {
  2431. GMType = GM.addValueChangeListener(name, callback);
  2432. } else if (isFN(GM_addValueChangeListener)) {
  2433. GMType = GM_addValueChangeListener(name, callback);
  2434. }
  2435. if (GMType) {
  2436. return this.events.add(GMType) && GMType;
  2437. }
  2438. }
  2439. return (
  2440. this.events.add(callback) &&
  2441. window.addEventListener('storage', (evt) => {
  2442. const { key, oldValue, newValue } = evt;
  2443. if (key === name) callback(key, oldValue, newValue, false);
  2444. })
  2445. );
  2446. },
  2447. attach() {
  2448. window.addEventListener('beforeunload', () => {
  2449. for (const e of this.events) {
  2450. if (isGM && typeof e === 'number' && !Number.isNaN(e)) {
  2451. if (isFN(GM.removeValueChangeListener)) {
  2452. GM.removeValueChangeListener(e);
  2453. } else if (isFN(GM_addValueChangeListener)) {
  2454. GM_removeValueChangeListener(e);
  2455. }
  2456. } else {
  2457. window.removeEventListener('storage', e);
  2458. }
  2459. this.events.delete(e);
  2460. }
  2461. });
  2462. },
  2463. async setValue(key, v) {
  2464. if (!v) {
  2465. return;
  2466. }
  2467. v = typeof v === 'string' ? v : JSON.stringify(v);
  2468. if (isGM) {
  2469. if (isFN(GM.setValue)) {
  2470. await GM.setValue(key, v);
  2471. } else if (isFN(GM_setValue)) {
  2472. GM_setValue(key, v);
  2473. }
  2474. } else {
  2475. this.setItem(`${this.prefix}-${key}`, v);
  2476. }
  2477. },
  2478. async getValue(key, def = {}) {
  2479. try {
  2480. if (isGM) {
  2481. let GMType;
  2482. if (isFN(GM.getValue)) {
  2483. GMType = await GM.getValue(key, JSON.stringify(def));
  2484. } else if (isFN(GM_getValue)) {
  2485. GMType = GM_getValue(key, JSON.stringify(def));
  2486. }
  2487. if (!isNull(GMType)) {
  2488. return JSON.parse(GMType);
  2489. }
  2490. }
  2491. return this.has(`${this.prefix}-${key}`)
  2492. ? JSON.parse(this.getItem(`${this.prefix}-${key}`))
  2493. : def;
  2494. } catch (ex) {
  2495. err(ex);
  2496. return def;
  2497. }
  2498. }
  2499. };
  2500. const Command = {
  2501. cmds: new Set(),
  2502. register(text, command) {
  2503. if (!isGM) {
  2504. return;
  2505. }
  2506.  
  2507. if (isFN(command)) {
  2508. if (this.cmds.has(command)) {
  2509. return;
  2510. }
  2511. this.cmds.add(command);
  2512. }
  2513.  
  2514. if (isFN(GM.registerMenuCommand)) {
  2515. GM.registerMenuCommand(text, command);
  2516. } else if (isFN(GM_registerMenuCommand)) {
  2517. GM_registerMenuCommand(text, command);
  2518. }
  2519. }
  2520. };
  2521. /**
  2522. * @type { import("../typings/UserJS.d.ts").Network }
  2523. */
  2524. const Network = {
  2525. async req(url, method = 'GET', responseType = 'json', data, useFetch = false) {
  2526. if (isEmpty(url)) {
  2527. throw new Error('"url" parameter is empty');
  2528. }
  2529. data = Object.assign({}, data);
  2530. method = this.bscStr(method, false);
  2531. responseType = this.bscStr(responseType);
  2532. const params = {
  2533. method,
  2534. ...data
  2535. };
  2536. if (isGM && !useFetch) {
  2537. if (params.credentials) {
  2538. Object.assign(params, {
  2539. anonymous: false
  2540. });
  2541. if (Object.is(params.credentials, 'omit')) {
  2542. Object.assign(params, {
  2543. anonymous: true
  2544. });
  2545. }
  2546. delete params.credentials;
  2547. }
  2548. } else if (params.onprogress) {
  2549. delete params.onprogress;
  2550. }
  2551. return new Promise((resolve, reject) => {
  2552. if (isGM && !useFetch) {
  2553. Network.xmlRequest({
  2554. url,
  2555. responseType,
  2556. ...params,
  2557. onerror: (r_1) => {
  2558. reject(new Error(`${r_1.status} ${url}`));
  2559. },
  2560. onload: (r_1) => {
  2561. if (r_1.status !== 200) reject(new Error(`${r_1.status} ${url}`));
  2562. if (responseType.match(/basic/)) resolve(r_1);
  2563. resolve(r_1.response);
  2564. }
  2565. });
  2566. } else {
  2567. fetch(url, params)
  2568. .then((response_1) => {
  2569. if (!response_1.ok) reject(response_1);
  2570. const check = (str_2 = 'text') => {
  2571. return isFN(response_1[str_2]) ? response_1[str_2]() : response_1;
  2572. };
  2573. if (responseType.match(/buffer/)) {
  2574. resolve(check('arrayBuffer'));
  2575. } else if (responseType.match(/json/)) {
  2576. resolve(check('json'));
  2577. } else if (responseType.match(/text/)) {
  2578. resolve(check('text'));
  2579. } else if (responseType.match(/blob/)) {
  2580. resolve(check('blob'));
  2581. } else if (responseType.match(/formdata/)) {
  2582. resolve(check('formData'));
  2583. } else if (responseType.match(/clone/)) {
  2584. resolve(check('clone'));
  2585. } else if (responseType.match(/document/)) {
  2586. const respTxt = check('text');
  2587. const domParser = new DOMParser();
  2588. if (respTxt instanceof Promise) {
  2589. respTxt.then((txt) => {
  2590. const doc = domParser.parseFromString(txt, 'text/html');
  2591. resolve(doc);
  2592. });
  2593. } else {
  2594. const doc = domParser.parseFromString(respTxt, 'text/html');
  2595. resolve(doc);
  2596. }
  2597. } else {
  2598. resolve(response_1);
  2599. }
  2600. })
  2601. .catch(reject);
  2602. }
  2603. });
  2604. },
  2605. format(bytes, decimals = 2) {
  2606. if (Number.isNaN(bytes)) return `0 ${this.sizes[0]}`;
  2607. const k = 1024;
  2608. const dm = decimals < 0 ? 0 : decimals;
  2609. const i = Math.floor(Math.log(bytes) / Math.log(k));
  2610. return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${this.sizes[i]}`;
  2611. },
  2612. sizes: ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  2613. async xmlRequest(details) {
  2614. if (isGM) {
  2615. if (isFN(GM.xmlHttpRequest)) {
  2616. return GM.xmlHttpRequest(details);
  2617. } else if (isFN(GM_xmlhttpRequest)) {
  2618. return GM_xmlhttpRequest(details);
  2619. }
  2620. }
  2621. return await new Promise((resolve, reject) => {
  2622. const { XMLHttpRequest } = safeSelf();
  2623. const req = new XMLHttpRequest();
  2624. let method = 'GET';
  2625. let url = BLANK_PAGE;
  2626. let body;
  2627. for (const [key, value] of Object.entries(details)) {
  2628. if (key === 'onload') {
  2629. req.addEventListener('load', () => {
  2630. if (isFN(value)) {
  2631. value(req);
  2632. }
  2633. resolve(req);
  2634. });
  2635. } else if (key === 'onerror') {
  2636. req.addEventListener('error', (evt) => {
  2637. if (isFN(value)) {
  2638. value(evt);
  2639. }
  2640. reject(evt);
  2641. });
  2642. } else if (key === 'onabort') {
  2643. req.addEventListener('abort', (evt) => {
  2644. if (isFN(value)) {
  2645. value(evt);
  2646. }
  2647. reject(evt);
  2648. });
  2649. } else if (key === 'onprogress') {
  2650. req.addEventListener('progress', value);
  2651. } else if (key === 'responseType') {
  2652. if (value === 'buffer') {
  2653. req.responseType = 'arraybuffer';
  2654. } else {
  2655. req.responseType = value;
  2656. }
  2657. } else if (key === 'method') {
  2658. method = value;
  2659. } else if (key === 'url') {
  2660. url = value;
  2661. } else if (key === 'body') {
  2662. body = value;
  2663. }
  2664. }
  2665. req.open(method, url);
  2666.  
  2667. if (isEmpty(req.responseType)) {
  2668. req.responseType = 'text';
  2669. }
  2670.  
  2671. if (body) {
  2672. req.send(body);
  2673. } else {
  2674. req.send();
  2675. }
  2676. });
  2677. },
  2678. bscStr(str = '', lowerCase = true) {
  2679. const txt = str[lowerCase ? 'toLowerCase' : 'toUpperCase']();
  2680. return txt.replaceAll(/\W/g, '');
  2681. }
  2682. };
  2683. const Counter = {
  2684. cnt: {
  2685. total: {
  2686. count: 0
  2687. }
  2688. },
  2689. set(engine) {
  2690. if (!this.cnt[engine.name]) {
  2691. const counter = make('count-frame', engine.enabled ? '' : 'hidden', {
  2692. dataset: {
  2693. counter: engine.name
  2694. },
  2695. title: engine.query ? decodeURIComponent(engine.query) : engine.url,
  2696. textContent: '0'
  2697. });
  2698. this.cnt[engine.name] = {
  2699. root: counter,
  2700. count: 0
  2701. };
  2702. return counter;
  2703. }
  2704. return this.cnt[engine.name].root;
  2705. },
  2706. update(count, engine) {
  2707. this.cnt[engine.name].count += count;
  2708. this.cnt.total.count += count;
  2709. this.updateAll();
  2710. },
  2711. updateAll() {
  2712. for (const v of Object.values(this.cnt)) dom.text(v.root, v.count);
  2713. },
  2714. reset() {
  2715. for (const [k, v] of Object.entries(this.cnt)) {
  2716. dom.text(v.root, 0);
  2717. v.count = 0;
  2718. const engine = cfg.engines.find((engine) => k === engine.name);
  2719. if (engine) {
  2720. dom.cl[engine.enabled ? 'remove' : 'add'](v.root, 'hidden');
  2721. }
  2722. }
  2723. }
  2724. };
  2725.  
  2726. // #region Container
  2727. /**
  2728. * @type { import("../typings/UserJS.d.ts").Container }
  2729. */
  2730. class Container {
  2731. webpage;
  2732. host;
  2733. domain;
  2734. ready;
  2735. injected;
  2736. shadowRoot;
  2737. supported;
  2738. frame;
  2739. cache;
  2740. userjsCache;
  2741. root;
  2742. unsaved;
  2743. isBlacklisted;
  2744. rebuild;
  2745. opacityMin;
  2746. opacityMax;
  2747. constructor(url) {
  2748. this.remove = this.remove.bind(this);
  2749. this.refresh = this.refresh.bind(this);
  2750. this.showError = this.showError.bind(this);
  2751. this.toArr = this.toArr.bind(this);
  2752. this.toElem = this.toElem.bind(this);
  2753.  
  2754. this.webpage = this.strToURL(url);
  2755. this.host = this.getHost(this.webpage.host);
  2756. this.domain = this.getDomain(this.webpage.host);
  2757. this.ready = false;
  2758. this.injected = false;
  2759. this.shadowRoot = undefined;
  2760. this.supported = isFN(make('main-userjs').attachShadow);
  2761. this.frame = this.supported
  2762. ? make('main-userjs', {
  2763. dataset: {
  2764. insertedBy: $info.script.name,
  2765. role: 'primary-container'
  2766. }
  2767. })
  2768. : make('iframe', 'mujs-iframe', {
  2769. dataset: {
  2770. insertedBy: $info.script.name,
  2771. role: 'primary-iframe'
  2772. },
  2773. loading: 'lazy',
  2774. src: BLANK_PAGE,
  2775. style:
  2776. 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;',
  2777. onload: (iFrame) => {
  2778. /**
  2779. * @type { HTMLIFrameElement }
  2780. */
  2781. const target = iFrame.target;
  2782. if (!target.contentDocument) {
  2783. return;
  2784. }
  2785. this.shadowRoot = target.contentDocument.documentElement;
  2786. this.ready = true;
  2787. dom.cl.add([this.shadowRoot, target.contentDocument.body], 'mujs-iframe');
  2788. }
  2789. });
  2790. if (this.supported) {
  2791. this.shadowRoot = this.frame.attachShadow({ mode: 'closed' });
  2792. this.ready = true;
  2793. }
  2794. this.cache = memory.maps.container;
  2795. this.userjsCache = memory.maps.userjs;
  2796. this.root = make('mujs-root');
  2797. this.unsaved = false;
  2798. this.isBlacklisted = false;
  2799. this.rebuild = false;
  2800. this.opacityMin = '0.15';
  2801. this.opacityMax = '1';
  2802. this.elementsReady = this.init();
  2803.  
  2804. const Timeout = class {
  2805. constructor() {
  2806. this.ids = [];
  2807. }
  2808.  
  2809. set(delay, reason) {
  2810. const { setTimeout } = safeSelf();
  2811. return new Promise((resolve, reject) => {
  2812. const id = setTimeout(() => {
  2813. Object.is(reason, null) || Object.is(reason, undefined) ? resolve() : reject(reason);
  2814. this.clear(id);
  2815. }, delay);
  2816. this.ids.push(id);
  2817. });
  2818. }
  2819.  
  2820. clear(...ids) {
  2821. const { clearTimeout } = safeSelf();
  2822. this.ids = this.ids.filter((id) => {
  2823. if (ids.includes(id)) {
  2824. clearTimeout(id);
  2825. return false;
  2826. }
  2827. return true;
  2828. });
  2829. }
  2830. };
  2831. this.timeouts = {
  2832. frame: new Timeout(),
  2833. mouse: new Timeout()
  2834. };
  2835.  
  2836. this.injFN = () => {};
  2837.  
  2838. window.addEventListener('beforeunload', this.remove);
  2839. }
  2840. /**
  2841. * @param { function(): * } callback
  2842. * @param { Document } doc
  2843. */
  2844. async inject(callback, doc) {
  2845. if (this.checkBlacklist(this.host)) {
  2846. err(`Blacklisted "${this.host}"`);
  2847. this.remove();
  2848. return;
  2849. }
  2850. if (!this.shadowRoot) {
  2851. return;
  2852. }
  2853. if (doc === null) {
  2854. return;
  2855. }
  2856.  
  2857. while (this.ready === false) {
  2858. await new Promise((resolve) => requestAnimationFrame(resolve));
  2859. }
  2860. try {
  2861. doc.documentElement.appendChild(this.frame);
  2862. if (this.injected) {
  2863. if (isFN(this.injFN.build)) {
  2864. this.injFN.build();
  2865. }
  2866. return;
  2867. }
  2868. this.shadowRoot.append(this.root);
  2869. if (isNull(this.loadCSS(main_css, 'primary-stylesheet'))) {
  2870. throw new Error('Failed to initialize script!', { cause: 'loadCSS' });
  2871. }
  2872. this.injected = true;
  2873. this.initFn();
  2874. if (this.elementsReady && isFN(callback)) {
  2875. this.injFN = callback.call(this, this.shadowRoot);
  2876. }
  2877. } catch (ex) {
  2878. err(ex);
  2879. this.remove();
  2880. }
  2881. }
  2882. initFn() {
  2883. this.renderTheme(cfg.theme);
  2884.  
  2885. Counter.cnt.total.root = this.mainbtn;
  2886. for (const engine of cfg.engines) this.countframe.append(Counter.set(engine));
  2887. const { cfgpage, table, supported, frame, refresh, cache, urlBar, host } = this;
  2888.  
  2889. class Tabs {
  2890. /**
  2891. * @param { HTMLElement } root
  2892. */
  2893. constructor(root) {
  2894. /**
  2895. * @type { Set<HTMLElement> }
  2896. */
  2897. this.pool = new Set();
  2898. this.blank = BLANK_PAGE;
  2899. this.protocal = 'mujs:';
  2900. this.protoReg = new RegExp(`${this.protocal}(.+)`, 'i');
  2901. this.el = {
  2902. add: make('mujs-addtab', {
  2903. textContent: '+',
  2904. dataset: {
  2905. command: 'new-tab'
  2906. }
  2907. }),
  2908. head: make('mujs-tabs'),
  2909. root
  2910. };
  2911. this.el.head.append(this.el.add);
  2912. this.el.root.append(this.el.head);
  2913. this.custom = () => {};
  2914. }
  2915. /**
  2916. * @param {string} hostname
  2917. */
  2918. getTab(hostname) {
  2919. return [...this.pool].find(({ dataset }) => hostname === dataset.host);
  2920. }
  2921. getActive() {
  2922. return [...this.pool].find((tab) => tab.classList.contains('active'));
  2923. }
  2924. /**
  2925. * @param {string} hostname
  2926. */
  2927. intFN(hostname) {
  2928. if (!hostname.startsWith(this.protocal)) {
  2929. return;
  2930. }
  2931. if (hostname.match(this.protoReg)[1] === 'settings') {
  2932. dom.cl.remove(cfgpage, 'hidden');
  2933. dom.cl.add(table, 'hidden');
  2934. if (!supported) {
  2935. dom.attr(frame, 'style', 'height: 100%;');
  2936. }
  2937. }
  2938. }
  2939. /**
  2940. * @param {HTMLElement} tab
  2941. * @param {boolean} [build]
  2942. */
  2943. active(tab, build = true) {
  2944. if (!this.pool.has(tab)) this.pool.add(tab);
  2945. dom.cl.add(cfgpage, 'hidden');
  2946. dom.cl.remove(table, 'hidden');
  2947. dom.cl.remove([...this.pool], 'active');
  2948. dom.cl.add(tab, 'active');
  2949. if (!build) {
  2950. return;
  2951. }
  2952. const host = tab.dataset.host ?? this.blank;
  2953. if (host === this.blank) {
  2954. refresh();
  2955. } else if (host.startsWith(this.protocal)) {
  2956. this.intFN(host);
  2957. } else {
  2958. this.custom(host);
  2959. }
  2960. }
  2961. /** @param { HTMLElement } tab */
  2962. close(tab) {
  2963. if (this.pool.has(tab)) this.pool.delete(tab);
  2964. const host = tab.dataset.host;
  2965. if (cfg.clearTabCache && cache.has(host)) cache.delete(host);
  2966. if (tab.classList.contains('active')) refresh();
  2967. const sibling = tab.nextElementSibling ?? tab.previousElementSibling;
  2968. if (sibling) {
  2969. if (sibling.dataset.command !== 'new-tab') {
  2970. this.active(sibling);
  2971. }
  2972. }
  2973. tab.remove();
  2974. }
  2975. /**
  2976. * @param {string} [hostname]
  2977. */
  2978. create(hostname = undefined) {
  2979. if (typeof hostname === 'string') {
  2980. const createdTab = this.getTab(hostname);
  2981. if (this.protoReg.test(hostname) && createdTab) {
  2982. this.active(createdTab);
  2983. return;
  2984. }
  2985. }
  2986. const tab = make('mujs-tab', {
  2987. dataset: {
  2988. command: 'switch-tab'
  2989. },
  2990. style: `order: ${this.el.head.childElementCount};`
  2991. });
  2992. const tabClose = make('mu-js', {
  2993. dataset: {
  2994. command: 'close-tab'
  2995. },
  2996. title: i18n$('close'),
  2997. textContent: 'X'
  2998. });
  2999. const tabHost = make('mujs-host');
  3000. tab.append(tabHost, tabClose);
  3001. this.el.head.append(tab);
  3002. this.active(tab, false);
  3003. if (isNull(hostname)) {
  3004. refresh();
  3005. urlBar.placeholder = i18n$('newTab');
  3006. tab.dataset.host = this.blank;
  3007. tabHost.title = i18n$('newTab');
  3008. tabHost.textContent = i18n$('newTab');
  3009. } else if (hostname.startsWith(this.protocal)) {
  3010. const type = hostname.match(this.protoReg)[1];
  3011. tab.dataset.host = hostname || host;
  3012. tabHost.title = type || tab.dataset.host;
  3013. tabHost.textContent = tabHost.title;
  3014. this.intFN(hostname);
  3015. } else {
  3016. tab.dataset.host = hostname || host;
  3017. tabHost.title = hostname || host;
  3018. tabHost.textContent = tabHost.title;
  3019. }
  3020. return tab;
  3021. }
  3022. }
  3023. this.tab = new Tabs(this.toolbar);
  3024. this.tab.create(host);
  3025.  
  3026. const tabbody = this.tabbody;
  3027. const getCellValue = (tr, idx) => tr.children[idx].dataset.value || tr.children[idx].textContent;
  3028. const comparer = (idx, asc) => (a, b) =>
  3029. ((v1, v2) =>
  3030. v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2)
  3031. ? v1 - v2
  3032. : v1.toString().localeCompare(v2))(
  3033. getCellValue(asc ? a : b, idx),
  3034. getCellValue(asc ? b : a, idx)
  3035. );
  3036. for (const th of this.tabhead.rows[0].cells) {
  3037. if (dom.text(th) === i18n$('install')) continue;
  3038. dom.cl.add(th, 'mujs-pointer');
  3039. ael(th, 'click', () => {
  3040. /** [Stack Overflow Reference](https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript/53880407#53880407) */
  3041. Array.from(tabbody.querySelectorAll('tr'))
  3042. .sort(comparer(Array.from(th.parentNode.children).indexOf(th), (this.asc = !this.asc)))
  3043. .forEach((tr) => tabbody.appendChild(tr));
  3044. });
  3045. }
  3046. }
  3047. init() {
  3048. try {
  3049. // #region Elements
  3050. this.mainframe = make('mu-js', 'mainframe', {
  3051. style: `opacity: ${this.opacityMin};`
  3052. });
  3053. this.countframe = make('mujs-column');
  3054. this.mainbtn = make('count-frame', 'mainbtn', {
  3055. textContent: '0'
  3056. });
  3057. this.urlBar = make('input', 'mujs-url-bar', {
  3058. autocomplete: 'off',
  3059. spellcheck: false,
  3060. type: 'text',
  3061. placeholder: i18n$('search_placeholder')
  3062. });
  3063. this.rateContainer = make('mujs-column', 'rate-container');
  3064. this.footer = make('mujs-row', 'mujs-footer');
  3065. this.tabbody = make('tbody');
  3066. this.promptElem = make('mujs-row', 'mujs-prompt');
  3067. this.toolbar = make('mujs-toolbar');
  3068. this.table = make('table');
  3069. this.tabhead = make('thead');
  3070. this.header = make('mujs-header');
  3071. this.tbody = make('mujs-body');
  3072. this.cfgpage = make('mujs-row', 'mujs-cfg hidden');
  3073. this.btnframe = make('mujs-column', 'btn-frame');
  3074. this.fsearch = make('mujs-btn', 'hidden');
  3075. this.btnHandles = make('mujs-column', 'btn-handles');
  3076. this.btnHide = make('mujs-btn', 'hide-list', {
  3077. title: i18n$('min'),
  3078. innerHTML: iconSVG.load('minus'),
  3079. dataset: {
  3080. command: 'hide-list'
  3081. }
  3082. });
  3083. this.btnfullscreen = make('mujs-btn', 'fullscreen', {
  3084. title: i18n$('max'),
  3085. innerHTML: iconSVG.load('expand'),
  3086. dataset: {
  3087. command: 'fullscreen'
  3088. }
  3089. });
  3090. this.main = make('mujs-main', 'hidden');
  3091. this.urlContainer = make('mujs-url');
  3092. this.closebtn = make('mujs-btn', 'close', {
  3093. title: i18n$('close'),
  3094. innerHTML: iconSVG.load('close'),
  3095. dataset: {
  3096. command: 'close'
  3097. }
  3098. });
  3099. this.btncfg = make('mujs-btn', 'settings hidden', {
  3100. title: 'Settings',
  3101. innerHTML: iconSVG.load('gear'),
  3102. dataset: {
  3103. command: 'settings'
  3104. }
  3105. });
  3106. this.btnhome = make('mujs-btn', 'github hidden', {
  3107. title: `GitHub (v${
  3108. $info.script.version.includes('.') || $info.script.version.includes('Book')
  3109. ? $info.script.version
  3110. : $info.script.version.slice(0, 5)
  3111. })`,
  3112. innerHTML: iconSVG.load('github'),
  3113. dataset: {
  3114. command: 'open-tab',
  3115. webpage: $info.script.namespace
  3116. }
  3117. });
  3118. this.btnissue = make('mujs-btn', 'issue hidden', {
  3119. innerHTML: iconSVG.load('issue'),
  3120. title: i18n$('issue'),
  3121. dataset: {
  3122. command: 'open-tab',
  3123. webpage: $info.script.bugs ?? 'https://github.com/magicoflolis/Userscript-Plus/issues'
  3124. }
  3125. });
  3126. this.btngreasy = make('mujs-btn', 'greasy hidden', {
  3127. title: 'Greasy Fork',
  3128. innerHTML: iconSVG.load('globe'),
  3129. dataset: {
  3130. command: 'open-tab',
  3131. webpage: 'https://greatest.deepsurf.us/scripts/421603'
  3132. }
  3133. });
  3134. this.btnnav = make('mujs-btn', 'nav', {
  3135. title: 'Navigation',
  3136. innerHTML: iconSVG.load('nav'),
  3137. dataset: {
  3138. command: 'navigation'
  3139. }
  3140. });
  3141. const makeTHead = (rows = []) => {
  3142. const tr = make('tr');
  3143. for (const r of rows) {
  3144. const tparent = make('th', r.class ?? '', r);
  3145. tr.append(tparent);
  3146. }
  3147. this.tabhead.append(tr);
  3148. this.table.append(this.tabhead, this.tabbody);
  3149. };
  3150. makeTHead([
  3151. {
  3152. class: 'mujs-header-name',
  3153. textContent: i18n$('name')
  3154. },
  3155. {
  3156. textContent: i18n$('createdby')
  3157. },
  3158. {
  3159. textContent: i18n$('daily_installs')
  3160. },
  3161. {
  3162. textContent: i18n$('updated')
  3163. },
  3164. {
  3165. textContent: i18n$('install')
  3166. }
  3167. ]);
  3168. // #endregion
  3169. if (isMobile) {
  3170. dom.cl.add([this.btnHide, this.btnfullscreen, this.closebtn], 'hidden');
  3171. this.btnframe.append(
  3172. this.btnHide,
  3173. this.btnfullscreen,
  3174. this.closebtn,
  3175. this.btnhome,
  3176. this.btngreasy,
  3177. this.btnissue,
  3178. this.btncfg,
  3179. this.btnnav
  3180. );
  3181. } else {
  3182. this.btnHandles.append(this.btnHide, this.btnfullscreen, this.closebtn);
  3183. this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav);
  3184. }
  3185. this.toolbar.append(this.btnHandles);
  3186. this.urlContainer.append(this.urlBar);
  3187. this.header.append(this.urlContainer, this.rateContainer, this.countframe, this.btnframe);
  3188. this.tbody.append(this.table, this.cfgpage);
  3189. this.main.append(this.toolbar, this.header, this.tbody, this.footer, this.promptElem);
  3190. this.mainframe.append(this.mainbtn);
  3191. // this.exBtn.append(this.importCFG, this.importTheme, this.exportCFG, this.exportTheme);
  3192. // this.header.append(this.exBtn);
  3193. this.root.append(this.mainframe, this.main);
  3194.  
  3195. return true;
  3196. } catch (ex) {
  3197. err(ex);
  3198. }
  3199. return false;
  3200. }
  3201. remove() {
  3202. memory.store.clear();
  3203. if (this.frame) {
  3204. this.frame.remove();
  3205. }
  3206. }
  3207. async save() {
  3208. this.unsaved = false;
  3209. await StorageSystem.setValue('Config', cfg);
  3210. info('Saved config:', cfg);
  3211. this.redirect();
  3212. return cfg;
  3213. }
  3214. /**
  3215. * @param { string } css - CSS to inject
  3216. * @param { string } name - Name of stylesheet
  3217. * @return { HTMLStyleElement } Style element
  3218. */
  3219. loadCSS(css, name = 'CSS') {
  3220. try {
  3221. if (typeof name !== 'string') {
  3222. throw new Error('"name" must be a typeof "string"', { cause: 'loadCSS' });
  3223. }
  3224. if (qs(`style[data-role="${name}"]`, this.root)) {
  3225. return qs(`style[data-role="${name}"]`, this.root);
  3226. }
  3227. if (typeof css !== 'string') {
  3228. throw new Error('"css" must be a typeof "string"', { cause: 'loadCSS' });
  3229. }
  3230. if (isBlank(css)) {
  3231. throw new Error(`"${name}" contains empty CSS string`, { cause: 'loadCSS' });
  3232. }
  3233. const parent = isEmpty(this.root.shadowRoot) ? this.root : this.root.shadowRoot;
  3234. if (isGM) {
  3235. let sty;
  3236. if (isFN(GM.addElement)) {
  3237. sty = GM.addElement(parent, 'style', {
  3238. textContent: css
  3239. });
  3240. } else if (isFN(GM_addElement)) {
  3241. sty = GM_addElement(parent, 'style', {
  3242. textContent: css
  3243. });
  3244. }
  3245. if (isElem(sty)) {
  3246. sty.dataset.insertedBy = $info.script.name;
  3247. sty.dataset.role = name;
  3248. return sty;
  3249. }
  3250. }
  3251. const sty = make('style', {
  3252. textContent: css,
  3253. dataset: {
  3254. insertedBy: $info.script.name,
  3255. role: name
  3256. }
  3257. });
  3258. parent.appendChild(sty);
  3259. return sty;
  3260. } catch (ex) {
  3261. err(ex);
  3262. }
  3263. }
  3264. checkBlacklist(str) {
  3265. str = str || this.host;
  3266. let blacklisted = false;
  3267. if (/accounts*\.google\./.test(this.webpage.host)) {
  3268. blacklisted = true;
  3269. }
  3270. for (const b of normalizeTarget(cfg.blacklist)) {
  3271. if (typeof b === 'string') {
  3272. if (b.startsWith('userjs-')) {
  3273. const r = /userjs-(\w+)/.exec(b)[1];
  3274. const biList = builtinList[r];
  3275. if (isRegExp(biList)) {
  3276. if (!biList.test(str)) continue;
  3277. blacklisted = true;
  3278. } else if (isObj(biList) && biList.host === this.host) {
  3279. blacklisted = true;
  3280. }
  3281. }
  3282. } else if (isObj(b)) {
  3283. if (!b.enabled) {
  3284. continue;
  3285. }
  3286. if (b.regex === true) {
  3287. const reg = new RegExp(b.url, b.flags);
  3288. if (!reg.test(str)) continue;
  3289. blacklisted = true;
  3290. }
  3291. if (Array.isArray(b.url)) {
  3292. for (const c of b.url) {
  3293. if (!str.includes(c)) continue;
  3294. blacklisted = true;
  3295. }
  3296. }
  3297. if (!str.includes(b.url)) continue;
  3298. blacklisted = true;
  3299. }
  3300. }
  3301. this.isBlacklisted = blacklisted;
  3302. return this.isBlacklisted;
  3303. }
  3304. getInfo(url) {
  3305. const webpage = this.strToURL(url || this.webpage);
  3306. const host = this.getHost(webpage.host);
  3307. const domain = this.getDomain(webpage.host);
  3308. return {
  3309. domain,
  3310. host,
  3311. webpage
  3312. };
  3313. }
  3314. /**
  3315. * @template { string } S
  3316. * @param { S } str
  3317. */
  3318. getHost(str = '') {
  3319. return str.split('.').splice(-2).join('.');
  3320. }
  3321. /**
  3322. * @template { string } S
  3323. * @param { S } str
  3324. */
  3325. getDomain(str = '') {
  3326. return str.split('.').at(-2) ?? BLANK_PAGE;
  3327. }
  3328. renderTheme(theme) {
  3329. theme = theme || cfg.theme;
  3330. if (theme === DEFAULT_CONFIG.theme) {
  3331. return;
  3332. }
  3333. const sty = this.root.style;
  3334. for (const [k, v] of Object.entries(theme)) {
  3335. const str = `--mujs-${k}`;
  3336. const prop = sty.getPropertyValue(str);
  3337. if (isEmpty(v)) {
  3338. theme[k] = prop;
  3339. }
  3340. if (prop === v) {
  3341. continue;
  3342. }
  3343. sty.removeProperty(str);
  3344. sty.setProperty(str, v);
  3345. }
  3346. }
  3347. makePrompt(txt, dataset = {}, usePrompt = true) {
  3348. if (qs('.prompt', this.promptElem)) {
  3349. for (const elem of qsA('.prompt', this.promptElem)) {
  3350. if (elem.dataset.prompt) {
  3351. elem.remove();
  3352. }
  3353. }
  3354. }
  3355. const el = make('mu-js', 'prompt', {
  3356. dataset: {
  3357. prompt: txt
  3358. }
  3359. });
  3360. const elHead = make('mu-js', 'prompt-head', {
  3361. innerHTML: `${iconSVG.load('refresh')} ${txt}`
  3362. });
  3363. el.append(elHead);
  3364. if (usePrompt) {
  3365. const elPrompt = make('mu-js', 'prompt-body', { dataset });
  3366. const elYes = make('mujs-btn', 'prompt-confirm', {
  3367. innerHTML: 'Confirm',
  3368. dataset: {
  3369. command: 'prompt-confirm'
  3370. }
  3371. });
  3372. const elNo = make('mujs-btn', 'prompt-deny', {
  3373. innerHTML: 'Deny',
  3374. dataset: {
  3375. command: 'prompt-deny'
  3376. }
  3377. });
  3378. elPrompt.append(elYes, elNo);
  3379. el.append(elPrompt);
  3380. }
  3381. this.promptElem.append(el);
  3382. }
  3383. /**
  3384. * @template {string | Error} E
  3385. * @param {...E} ex
  3386. */
  3387. showError(...ex) {
  3388. err(...ex);
  3389. const error = make('mu-js', 'error');
  3390. let str = '';
  3391. for (const e of ex) {
  3392. str += `${typeof e === 'string' ? e : `${e.cause ? `[${e.cause}] ` : ''}${e.message}${e.stack ? ` ${e.stack}` : ''}`}\n`;
  3393. if (isObj(e)) {
  3394. if (e.notify) {
  3395. dom.cl.add(this.mainframe, 'error');
  3396. }
  3397. }
  3398. }
  3399. const { createTextNode } = safeSelf();
  3400. error.appendChild(createTextNode(str));
  3401. this.footer.append(error);
  3402. }
  3403. toArr() {
  3404. return Array.from(this.userjsCache.values()).filter(({ _mujs }) => {
  3405. return isElem(_mujs.root) && _mujs.info.engine.enabled;
  3406. });
  3407. }
  3408. toElem() {
  3409. return this.toArr().map(({ _mujs }) => {
  3410. return _mujs.root;
  3411. });
  3412. }
  3413. refresh() {
  3414. this.urlBar.placeholder = i18n$('newTab');
  3415. Counter.reset();
  3416. dom.cl.remove(this.toElem(), 'hidden');
  3417. dom.cl.remove(qsA('mujs-section[data-name]', this.cfgpage), 'hidden');
  3418. dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', '');
  3419. }
  3420. /**
  3421. * @template {string | URL} S
  3422. * @param {S} str
  3423. * @returns {URL}
  3424. */
  3425. strToURL(str) {
  3426. const WIN_LOCATION = window.location ?? BLANK_PAGE;
  3427. try {
  3428. str = str ?? WIN_LOCATION;
  3429. return objToStr(str).includes('URL') ? str : new URL(str);
  3430. } catch (ex) {
  3431. ex.cause = 'strToURL';
  3432. this.showError(ex);
  3433. }
  3434. return WIN_LOCATION;
  3435. }
  3436. /**
  3437. * Redirects sleazyfork userscripts from greatest.deepsurf.us to sleazyfork.org
  3438. *
  3439. * Taken from: https://greatest.deepsurf.us/scripts/23840
  3440. */
  3441. redirect() {
  3442. const locObj = window.top.location;
  3443. const { hostname } = locObj;
  3444. const gfSite = /greasyfork\.org/.test(hostname);
  3445. if (!gfSite && cfg.sleazyredirect) {
  3446. return;
  3447. }
  3448. const otherSite = gfSite ? 'sleazyfork' : 'greasyfork';
  3449. if (!qs('span.sign-in-link')) {
  3450. return;
  3451. }
  3452. if (!/scripts\/\d+/.test(locObj.href)) {
  3453. return;
  3454. }
  3455. if (
  3456. !qs('#script-info') &&
  3457. (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a'))
  3458. ) {
  3459. const str = locObj.href.replace(
  3460. /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/,
  3461. '//$1' + otherSite + '.org'
  3462. );
  3463. info(`Redirecting to "${str}"`);
  3464. if (isFN(locObj.assign)) {
  3465. locObj.assign(str);
  3466. } else {
  3467. locObj.href = str;
  3468. }
  3469. }
  3470. }
  3471. }
  3472. const container = new Container();
  3473. // #endregion
  3474. // #region Primary Function
  3475. function primaryFN() {
  3476. const respHandles = {
  3477. build: async () => {}
  3478. };
  3479. try {
  3480. const { scheduler } = safeSelf();
  3481. const {
  3482. mainframe,
  3483. urlBar,
  3484. rateContainer,
  3485. footer,
  3486. tabbody,
  3487. cfgpage,
  3488. fsearch,
  3489. btnfullscreen,
  3490. main,
  3491. tab,
  3492. showError
  3493. } = container;
  3494. const frameTimeout = container.timeouts.frame;
  3495. const cfgMap = memory.maps.cfg;
  3496. const rebuildCfg = () => {
  3497. for (const engine of cfg.engines) {
  3498. if (cfgMap.has(engine.name)) {
  3499. const inp = cfgMap.get(engine.name);
  3500. inp.checked = engine.enabled;
  3501. if (engine.name === 'github') {
  3502. const txt = cfgMap.get('github-token');
  3503. dom.prop(txt, 'value', engine.token);
  3504. }
  3505. }
  3506. }
  3507. for (const [k, v] of Object.entries(cfg)) {
  3508. if (typeof v === 'boolean') {
  3509. if (cfgMap.has(k)) {
  3510. const inp = cfgMap.get(k);
  3511. if (inp.type === 'checkbox') {
  3512. inp.checked = v;
  3513. } else {
  3514. dom.prop(inp, 'value', v);
  3515. }
  3516. }
  3517. }
  3518. }
  3519. // dom.prop(cfgMap.get('blacklist'), 'value', JSON.stringify(cfg.blacklist, null, ' '));
  3520. for (const [k, v] of Object.entries(cfg.theme)) {
  3521. dom.prop(cfgMap.get(k), 'value', v);
  3522. }
  3523. container.renderTheme(cfg.theme);
  3524. };
  3525. const doInstallProcess = async (installLink) => {
  3526. const locObj = window.top.location;
  3527. if (isFN(locObj.assign)) {
  3528. locObj.assign(installLink.href);
  3529. } else {
  3530. locObj.href = installLink.href;
  3531. }
  3532. installLink.remove();
  3533. await init();
  3534. };
  3535. const applyTo = (ujs, name, elem, root) => {
  3536. const n = ujs._mujs.code[name] ?? ujs._mujs.code.data_meta[name];
  3537. if (isEmpty(n)) {
  3538. const el = make('mujs-a', {
  3539. textContent: i18n$('listing_none')
  3540. });
  3541. elem.append(el);
  3542. return;
  3543. }
  3544. dom.prop(elem, 'innerHTML', '');
  3545. dom.cl.remove(root, 'hidden');
  3546. if (isObj(n)) {
  3547. if (name === 'resource') {
  3548. for (const [k, v] of Object.entries(n)) {
  3549. const el = make('mujs-a', {
  3550. textContent: k ?? 'ERROR'
  3551. });
  3552. if (v.startsWith('http')) {
  3553. el.dataset.command = 'open-tab';
  3554. el.dataset.webpage = v;
  3555. }
  3556. elem.append(el);
  3557. }
  3558. } else {
  3559. const el = make('mujs-a', {
  3560. textContent: n.text
  3561. });
  3562. if (n.domain) {
  3563. el.dataset.command = 'open-tab';
  3564. el.dataset.webpage = `https://${n.text}`;
  3565. }
  3566. elem.append(el);
  3567. }
  3568. } else if (typeof n === 'string') {
  3569. const el = make('mujs-a', {
  3570. textContent: n
  3571. });
  3572. elem.append(el);
  3573. } else {
  3574. for (const c of n) {
  3575. if (typeof c === 'string' && c.startsWith('http')) {
  3576. const el = make('mujs-a', {
  3577. textContent: c,
  3578. dataset: {
  3579. command: 'open-tab',
  3580. webpage: c
  3581. }
  3582. });
  3583. elem.append(el);
  3584. } else if (isObj(c)) {
  3585. const el = make('mujs-a', {
  3586. textContent: c.text
  3587. });
  3588. if (c.domain) {
  3589. el.dataset.command = 'open-tab';
  3590. el.dataset.webpage = `https://${c.text}`;
  3591. }
  3592. elem.append(el);
  3593. } else {
  3594. const el = make('mujs-a', {
  3595. textContent: c
  3596. });
  3597. elem.append(el);
  3598. }
  3599. }
  3600. }
  3601. };
  3602. // #region Main event handlers
  3603. ael(main, isMobile ? 'touchend' : 'click', async (evt) => {
  3604. try {
  3605. /** @type { HTMLElement } */
  3606. const target = evt.target.closest('[data-command]');
  3607. if (!target) {
  3608. return;
  3609. }
  3610. const prmpt = /prompt-/.test(target.dataset.command);
  3611. let dataset = target.dataset;
  3612. let cmd = dataset.command;
  3613. let prmptChoice = false;
  3614. if (prmpt) {
  3615. dataset = target.parentElement.dataset;
  3616. cmd = dataset.command;
  3617. prmptChoice = /confirm/.test(target.dataset.command);
  3618. target.parentElement.parentElement.remove();
  3619. }
  3620. if (cmd === 'install-script' && dataset.userjs) {
  3621. let installCode = dataset.userjs;
  3622. if (!prmpt && dataset.userjs.endsWith('.user.css')) {
  3623. container.makePrompt(i18n$('prmpt_css'), dataset);
  3624. return;
  3625. } else if (prmpt !== prmptChoice) {
  3626. installCode = dataset.userjs.replace(/\.user\.css$/, '.user.js');
  3627. }
  3628. const dlBtn = make('a', {
  3629. onclick(evt) {
  3630. evt.preventDefault();
  3631. doInstallProcess(evt.target);
  3632. }
  3633. });
  3634. dlBtn.href = installCode;
  3635. dlBtn.click();
  3636. } else if (cmd === 'open-tab' && dataset.webpage) {
  3637. if (isGM) {
  3638. if (isFN(GM.openInTab)) {
  3639. return GM.openInTab(dataset.webpage);
  3640. } else if (isFN(GM_openInTab)) {
  3641. return GM_openInTab(dataset.webpage, {
  3642. active: true,
  3643. insert: true
  3644. });
  3645. }
  3646. }
  3647. return window.open(dataset.webpage, '_blank');
  3648. } else if (cmd === 'navigation') {
  3649. for (const e of qsA('mujs-btn', target.parentElement)) {
  3650. if (dom.cl.has(e, 'nav')) continue;
  3651. if (dom.cl.has(e, 'hidden')) {
  3652. dom.cl.remove(e, 'hidden');
  3653. } else {
  3654. dom.cl.add(e, 'hidden');
  3655. }
  3656. }
  3657. } else if (cmd === 'list-description') {
  3658. const arr = [];
  3659. const ignoreTags = new Set(['TD', 'MUJS-A', 'MU-JS']);
  3660. for (const node of target.parentElement.childNodes) {
  3661. if (ignoreTags.has(node.tagName)) {
  3662. continue;
  3663. }
  3664. if (node.tagName === 'TEXTAREA' && isEmpty(node.value)) {
  3665. continue;
  3666. }
  3667. arr.push(node);
  3668. }
  3669. if (target.nextElementSibling) {
  3670. arr.push(target.nextElementSibling);
  3671. if (target.nextElementSibling.nextElementSibling) {
  3672. arr.push(target.nextElementSibling.nextElementSibling);
  3673. }
  3674. }
  3675. if (dom.cl.has(arr[0], 'hidden')) {
  3676. dom.cl.remove(arr, 'hidden');
  3677. } else {
  3678. dom.cl.add(arr, 'hidden');
  3679. }
  3680. } else if (cmd === 'close') {
  3681. container.remove();
  3682. } else if (cmd === 'show-filter') {
  3683. dom.cl.toggle(fsearch, 'hidden');
  3684. } else if (cmd === 'fullscreen') {
  3685. if (dom.cl.has(btnfullscreen, 'expanded')) {
  3686. dom.cl.remove([btnfullscreen, main], 'expanded');
  3687. dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('expand'));
  3688. } else {
  3689. dom.cl.add([btnfullscreen, main], 'expanded');
  3690. dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse'));
  3691. }
  3692. } else if (cmd === 'hide-list') {
  3693. dom.cl.add(main, 'hidden');
  3694. dom.cl.remove(mainframe, 'hidden');
  3695. timeoutFrame();
  3696. } else if (cmd === 'save') {
  3697. container.rebuild = true;
  3698. dom.prop(rateContainer, 'innerHTML', '');
  3699. if (!dom.prop(target, 'disabled')) {
  3700. const config = await container.save();
  3701. if (container.rebuild) {
  3702. container.cache.clear();
  3703. if (config.autofetch) {
  3704. respHandles.build();
  3705. }
  3706. }
  3707. container.unsaved = false;
  3708. container.rebuild = false;
  3709. }
  3710. } else if (cmd === 'reset') {
  3711. cfg = DEFAULT_CONFIG;
  3712. dom.cl.remove(mainframe, 'error');
  3713. if (qs('.error', footer)) {
  3714. for (const elem of qsA('.error', footer)) {
  3715. elem.remove();
  3716. }
  3717. }
  3718. container.unsaved = true;
  3719. container.rebuild = true;
  3720. rebuildCfg();
  3721. } else if (cmd === 'settings') {
  3722. if (container.unsaved) {
  3723. showError('Unsaved changes');
  3724. }
  3725. tab.create('mujs:settings');
  3726. container.rebuild = false;
  3727. } else if (cmd === 'new-tab') {
  3728. tab.create();
  3729. } else if (cmd === 'switch-tab') {
  3730. tab.active(target);
  3731. } else if (cmd === 'close-tab' && target.parentElement) {
  3732. tab.close(target.parentElement);
  3733. } else if (cmd === 'download-userjs') {
  3734. if (!container.userjsCache.has(+dataset.userjs)) {
  3735. return;
  3736. }
  3737. const dataUserJS = container.userjsCache.get(+dataset.userjs);
  3738. let installCode = dataUserJS.code_url;
  3739. if (!prmpt && dataUserJS.code_url.endsWith('.user.css')) {
  3740. container.makePrompt('Download as UserStyle?', dataset);
  3741. return;
  3742. } else if (prmpt !== prmptChoice) {
  3743. installCode = dataUserJS.code_url.replace(/\.user\.css$/, '.user.js');
  3744. }
  3745. const r = await dataUserJS._mujs.code.request(false, installCode);
  3746. const txt = r.data;
  3747. if (typeof txt !== 'string') {
  3748. return;
  3749. }
  3750. const userjsName = dataset.userjsName ?? dataset.userjs;
  3751. const userjsExt = prmpt !== prmptChoice ? '.user.js' : '.user.css';
  3752. const makeUserJS = new Blob([txt], { type: 'text/plain' });
  3753. const dlBtn = make('a', 'mujs_Downloader');
  3754. dlBtn.href = URL.createObjectURL(makeUserJS);
  3755. dlBtn.download = `${userjsName}${userjsExt}`;
  3756. dlBtn.click();
  3757. URL.revokeObjectURL(dlBtn.href);
  3758. dlBtn.remove();
  3759. } else if (cmd === 'load-userjs' || cmd === 'load-header') {
  3760. if (!container.userjsCache.has(+dataset.userjs)) {
  3761. return;
  3762. }
  3763. const codeArea = qs('textarea', target.parentElement.parentElement);
  3764. if (!isEmpty(codeArea.value) && cmd === codeArea.dataset.load) {
  3765. dom.cl.toggle(codeArea, 'hidden');
  3766. return;
  3767. }
  3768. codeArea.dataset.load = cmd;
  3769. const dataUserJS = container.userjsCache.get(+dataset.userjs);
  3770. const code_obj = await dataUserJS._mujs.code.request();
  3771. if (typeof code_obj.data_code_block !== 'string') {
  3772. codeArea.value = 'An error occured';
  3773. return;
  3774. }
  3775. codeArea.value =
  3776. cmd === 'load-userjs' ? code_obj.data_code_block : code_obj.data_meta_block;
  3777. dom.cl.remove(codeArea, 'hidden');
  3778. for (const e of qsA(
  3779. 'mujs-column[data-el="matches"]',
  3780. target.parentElement.parentElement
  3781. )) {
  3782. applyTo(dataUserJS, e.dataset.type, qs('.mujs-grants', e), e);
  3783. }
  3784. } else if (cmd === 'load-page') {
  3785. if (!container.userjsCache.has(+dataset.userjs)) {
  3786. return;
  3787. }
  3788. let pageArea = qs('mujs-page', target.parentElement.parentElement);
  3789. if (!pageArea) {
  3790. pageArea = make('mujs-page');
  3791. target.parentElement.parentElement.append(pageArea);
  3792. const dataUserJS = container.userjsCache.get(+dataset.userjs);
  3793. const engine = dataUserJS._mujs.info.engine;
  3794. let pageURL;
  3795. if (engine.name.includes('fork')) {
  3796. const { navigator } = safeSelf();
  3797. const current = navigator.language.split('-')[0] ?? 'en';
  3798. pageURL = dataUserJS.url.replace(
  3799. /\/scripts/,
  3800. `/${/^(zh|fr|es)/.test(current) ? navigator.language : current}/scripts`
  3801. );
  3802. } else if (engine.name.includes('github')) {
  3803. const page_url = await Network.req(dataUserJS.page_url, 'GET', 'json', {
  3804. headers: {
  3805. Accept: 'application/vnd.github+json',
  3806. Authorization: `Bearer ${engine.token}`,
  3807. 'X-GitHub-Api-Version': '2022-11-28'
  3808. }
  3809. }).catch(() => {
  3810. return {};
  3811. });
  3812. if (!page_url.download_url) {
  3813. return;
  3814. }
  3815. const page = await Network.req(page_url.download_url, 'GET', 'text');
  3816. if (container.supported) {
  3817. const shadow = pageArea.attachShadow({ mode: 'closed' });
  3818. const div = make('div', {
  3819. innerHTML: page
  3820. });
  3821. shadow.append(div);
  3822. }
  3823. return;
  3824. } else {
  3825. pageURL = dataUserJS.url;
  3826. }
  3827. if (!pageURL) {
  3828. return;
  3829. }
  3830. const page = await Network.req(pageURL, 'GET', 'document');
  3831. const getContent = () => {
  3832. let content = 'An error occured';
  3833. const h = new URL(dataUserJS.url);
  3834. const root = qs('.user-content', page.documentElement);
  3835. for (const e of qsA('[href]', root)) {
  3836. e.target = '_blank';
  3837. e.style = 'pointer-events: auto;';
  3838. if (e.href.startsWith('/')) {
  3839. e.href = `${h.origin}${e.href}`;
  3840. }
  3841. }
  3842. for (const e of qsA('img[src]', root)) {
  3843. e.style =
  3844. 'max-width: 25em; max-height: 25em; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;';
  3845. }
  3846. if (root) {
  3847. content = root.innerHTML;
  3848. } else {
  3849. content = 'No additional info available';
  3850. }
  3851. return content;
  3852. };
  3853. if (container.supported) {
  3854. const shadow = pageArea.attachShadow({ mode: 'closed' });
  3855. const div = make('div', {
  3856. style: 'pointer-events: none;',
  3857. innerHTML: getContent()
  3858. });
  3859. shadow.append(div);
  3860. }
  3861. return;
  3862. }
  3863. if (!dom.cl.has(pageArea, 'hidden')) {
  3864. dom.cl.add(pageArea, 'hidden');
  3865. return;
  3866. }
  3867. dom.cl.remove(pageArea, 'hidden');
  3868. } else if (/export-/.test(cmd)) {
  3869. const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' ');
  3870. const bytes = new TextEncoder().encode(str);
  3871. const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' });
  3872. const dlBtn = make('a', 'mujs-exporter', {
  3873. href: URL.createObjectURL(blob),
  3874. download: `Magic_Userscript_${cmd === 'export-cfg' ? 'config' : 'theme'}.json`
  3875. });
  3876. dlBtn.click();
  3877. URL.revokeObjectURL(dlBtn.href);
  3878. } else if (/import-/.test(cmd)) {
  3879. if (qs('input', target.parentElement)) {
  3880. qs('input', target.parentElement).click();
  3881. return;
  3882. }
  3883. const inpJSON = make('input', 'hidden', {
  3884. type: 'file',
  3885. accept: '.json',
  3886. onchange(evt) {
  3887. try {
  3888. [...evt.target.files].forEach((file) => {
  3889. const reader = new FileReader();
  3890. reader.readAsText(file);
  3891. reader.onload = () => {
  3892. const result = JSON.parse(reader.result);
  3893. if (result.blacklist) {
  3894. log(`Imported config: { ${file.name} }`, result);
  3895. cfg = result;
  3896. container.unsaved = true;
  3897. container.rebuild = true;
  3898. rebuildCfg();
  3899. container.save().then((config) => {
  3900. container.cache.clear();
  3901. if (config.autofetch) {
  3902. respHandles.build();
  3903. }
  3904. container.unsaved = false;
  3905. container.rebuild = false;
  3906. });
  3907. } else {
  3908. log(`Imported theme: { ${file.name} }`, result);
  3909. cfg.theme = result;
  3910. container.renderTheme(cfg.theme);
  3911. }
  3912. inpJSON.remove();
  3913. };
  3914. reader.onerror = () => {
  3915. showError(reader.error);
  3916. inpJSON.remove();
  3917. };
  3918. });
  3919. } catch (ex) {
  3920. showError(ex);
  3921. inpJSON.remove();
  3922. }
  3923. }
  3924. });
  3925. target.parentElement.append(inpJSON);
  3926. inpJSON.click();
  3927. }
  3928. } catch (ex) {
  3929. showError(ex);
  3930. }
  3931. });
  3932. ael(main, 'auxclick', (evt) => {
  3933. if (evt.button !== 1) {
  3934. return;
  3935. }
  3936. /** @type { HTMLElement } */
  3937. const target = evt.target.closest('[data-command]');
  3938. if (!target) {
  3939. return;
  3940. }
  3941. const dataset = target.dataset;
  3942. const cmd = dataset.command;
  3943. if (cmd === 'switch-tab' || cmd === 'close-tab') {
  3944. tab.close(target);
  3945. } else if (cmd === 'new-tab') {
  3946. tab.create();
  3947. }
  3948. });
  3949. if (!isMobile) {
  3950. const fade = async (target, type) => {
  3951. if (type === 'mouseenter') {
  3952. frameTimeout.clear(...frameTimeout.ids);
  3953. container.timeouts.mouse.clear(...container.timeouts.mouse.ids);
  3954. target.style.opacity = container.opacityMax;
  3955. } else if (type === 'mouseleave') {
  3956. await container.timeouts.mouse.set(cfg.time);
  3957. target.style.opacity = container.opacityMin;
  3958. }
  3959. };
  3960. for (const e of ['mouseenter', 'mouseleave']) {
  3961. ael(main, e, (evt) => {
  3962. evt.preventDefault();
  3963. evt.stopPropagation();
  3964. fade(evt.target, evt.type);
  3965. });
  3966. }
  3967. }
  3968. ael(main, 'updateditem', (evt) => {
  3969. /**
  3970. * @type {import("../typings/types.d.ts").GSForkQuery}
  3971. */
  3972. const ujs = evt.detail;
  3973. if (!ujs._mujs) return;
  3974. for (const elem of qsA('[data-name]', ujs._mujs.root)) {
  3975. const name = elem.dataset.name;
  3976. if (name === 'code') {
  3977. if (ujs._mujs.code.data_code_block) {
  3978. if (cfg.preview.code && !cfg.preview.metadata) {
  3979. elem.value = ujs._mujs.code.data_code_block;
  3980. } else if (cfg.preview.metadata && !cfg.preview.code) {
  3981. elem.value = ujs._mujs.code.data_meta_block;
  3982. } else {
  3983. elem.value = `${ujs._mujs.code.META_START_COMMENT}${ujs._mujs.code.data_meta_block}${ujs._mujs.code.META_END_COMMENT}${ujs._mujs.code.data_code_block}`;
  3984. }
  3985. }
  3986. continue;
  3987. }
  3988. if (!ujs[name]) continue;
  3989. if (name === 'license') {
  3990. dom.attr(elem, 'title', ujs.license ?? i18n$('no_license'));
  3991. dom.text(elem, `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`);
  3992. } else if (name === 'code_updated_at') {
  3993. dom.text(elem, language.toDate(ujs.code_updated_at));
  3994. elem.dataset.value = new Date(ujs.code_updated_at).toISOString();
  3995. } else if (name === 'created_date') {
  3996. dom.text(elem, `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}`);
  3997. elem.dataset.value = new Date(ujs.created_at).toISOString();
  3998. } else if (name === 'total_installs') {
  3999. dom.text(elem, `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}`);
  4000. } else {
  4001. dom.text(elem, ujs[name]);
  4002. }
  4003. }
  4004. if (ujs._mujs.code.data_code_block) {
  4005. for (const e of qsA('mujs-column[data-el="matches"]', ujs._mujs.root)) {
  4006. applyTo(ujs, e.dataset.type, qs('.mujs-grants', e), e);
  4007. }
  4008. }
  4009. if (container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs);
  4010. });
  4011. // #endregion
  4012. const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk'];
  4013. const APPLIES_TO_ALL_PATTERNS = [
  4014. 'http://*',
  4015. 'https://*',
  4016. 'http://*/*',
  4017. 'https://*/*',
  4018. 'http*://*',
  4019. 'http*://*/*',
  4020. '*',
  4021. '*://*',
  4022. '*://*/*',
  4023. 'http*'
  4024. ];
  4025. class ParseUserJS {
  4026. /**
  4027. * @type { string }
  4028. */
  4029. code;
  4030. /**
  4031. * @type { string }
  4032. */
  4033. data_meta_block;
  4034. /**
  4035. * @type { string }
  4036. */
  4037. data_code_block;
  4038. /**
  4039. * @type { { [meta: string]: string | string[] | { [resource: string]: string } } }
  4040. */
  4041. data_meta;
  4042. /**
  4043. * @type { {text: string;domain: boolean;tld_extra: boolean}[] }
  4044. */
  4045. data_names;
  4046. constructor(code, isUserCSS) {
  4047. this.code = code;
  4048. this.META_START_COMMENT = isUserCSS ? '/* ==UserStyle==' : '// ==UserScript==';
  4049. this.META_END_COMMENT = isUserCSS ? '==/UserStyle== */' : '// ==/UserScript==';
  4050. this.get_meta_block();
  4051. this.get_code_block();
  4052. this.parse_meta();
  4053. this.calculate_applies_to_names();
  4054. }
  4055. get_meta_block() {
  4056. if (isEmpty(this.code)) {
  4057. return null;
  4058. }
  4059. if (this.data_meta_block) {
  4060. return this.data_meta_block;
  4061. }
  4062. const start_block = this.code.indexOf(this.META_START_COMMENT);
  4063. if (isNull(start_block)) {
  4064. return null;
  4065. }
  4066. const end_block = this.code.indexOf(this.META_END_COMMENT, start_block);
  4067. if (isNull(end_block)) {
  4068. return null;
  4069. }
  4070. const meta_block = this.code.substring(
  4071. start_block + this.META_START_COMMENT.length,
  4072. end_block
  4073. );
  4074. this.data_meta_block = meta_block;
  4075. return this.data_meta_block;
  4076. }
  4077. get_code_block() {
  4078. if (isEmpty(this.code)) {
  4079. return null;
  4080. }
  4081. if (this.data_code_block) {
  4082. return this.data_code_block;
  4083. }
  4084. const start_block = this.code.indexOf(this.META_START_COMMENT);
  4085. if (isNull(start_block)) {
  4086. return null;
  4087. }
  4088. const end_block = this.code.indexOf(this.META_END_COMMENT, start_block);
  4089. if (isNull(end_block)) {
  4090. return null;
  4091. }
  4092. const code_block = this.code.substring(
  4093. end_block + this.META_END_COMMENT.length,
  4094. this.code.length
  4095. );
  4096. this.data_code_block = code_block
  4097. .split('\n')
  4098. .filter((l) => !isEmpty(l))
  4099. .join('\n');
  4100. return this.data_code_block;
  4101. }
  4102. parse_meta() {
  4103. if (isEmpty(this.code)) {
  4104. return null;
  4105. }
  4106. if (this.data_meta) {
  4107. return this.data_meta;
  4108. }
  4109. const meta = {};
  4110. const meta_block_map = new Map();
  4111. for (const meta_line of this.get_meta_block().split('\n')) {
  4112. const meta_match = /\/\/\s+@([a-zA-Z:-]+)\s+(.*)/.exec(meta_line);
  4113. if (!meta_match) {
  4114. continue;
  4115. }
  4116. const key = meta_match[1].trim();
  4117. const value = meta_match[2].trim();
  4118. if (!meta_block_map.has(key)) {
  4119. meta_block_map.set(key, []);
  4120. }
  4121. const meta_map = meta_block_map.get(key);
  4122. meta_map.push(value);
  4123. meta_block_map.set(key, meta_map);
  4124. }
  4125. for (const [key, value] of meta_block_map) {
  4126. if (value.length > 1) {
  4127. meta[key] = value;
  4128. } else {
  4129. meta[key] = value[0];
  4130. }
  4131. }
  4132. this.data_meta = meta;
  4133. return this.data_meta;
  4134. }
  4135. calculate_applies_to_names() {
  4136. if (isEmpty(this.code)) {
  4137. return null;
  4138. }
  4139. if (this.data_names) {
  4140. return this.data_names;
  4141. }
  4142. let patterns = [];
  4143. for (const [k, v] of Object.entries(this.parse_meta())) {
  4144. if (/include|match/i.test(k)) {
  4145. if (Array.isArray(v)) {
  4146. patterns = patterns.concat(v);
  4147. } else {
  4148. patterns = patterns.concat([v]);
  4149. }
  4150. }
  4151. }
  4152. if (isEmpty(patterns)) {
  4153. return [];
  4154. }
  4155. if (this.intersect(patterns, APPLIES_TO_ALL_PATTERNS)) {
  4156. this.data_names = [
  4157. {
  4158. domain: false,
  4159. text: 'All sites',
  4160. tld_extra: false
  4161. }
  4162. ];
  4163. return this.data_names;
  4164. }
  4165. const name_map = new Map();
  4166. const addObj = (obj) => {
  4167. if (name_map.has(obj.text)) {
  4168. return;
  4169. }
  4170. name_map.set(obj.text, obj);
  4171. };
  4172. for (let p of patterns) {
  4173. try {
  4174. const original_pattern = p;
  4175. let pre_wildcards = [];
  4176. if (p.match(/^\/(.*)\/$/)) {
  4177. pre_wildcards = [p];
  4178. } else {
  4179. let m = /^\*(https?:.*)/i.exec(p);
  4180. if (m) {
  4181. p = m[1];
  4182. }
  4183. p = p
  4184. .replace(/^\*:/i, 'http:')
  4185. .replace(/^\*\/\//i, 'http://')
  4186. .replace(/^http\*:/i, 'http:')
  4187. .replace(/^(https?):([^/])/i, '$1://$2');
  4188. m = /^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i.exec(p);
  4189. if (m) {
  4190. p = m[1] + m[2];
  4191. }
  4192. m = /^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i.exec(p);
  4193. if (m) {
  4194. p = `http://${m[1]}`;
  4195. }
  4196. m = /^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i.exec(p);
  4197. if (m) {
  4198. p = `http://${m[1]}`;
  4199. }
  4200. m = /^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/.exec(p);
  4201. if (m) {
  4202. if (m[2].match(/A([0-9]+\.){2,}z/)) {
  4203. p = `${m[1]}tld${m[3]}`;
  4204. pre_wildcards = [p.split('*')[0]];
  4205. } else {
  4206. pre_wildcards = [p];
  4207. }
  4208. } else {
  4209. pre_wildcards = [p];
  4210. }
  4211. }
  4212. for (const pre_wildcard of pre_wildcards) {
  4213. try {
  4214. const urlObj = new URL(pre_wildcard);
  4215. const { host } = urlObj;
  4216. if (isNull(host)) {
  4217. addObj({ text: original_pattern, domain: false, tld_extra: false });
  4218. } else if (!host.includes('.') && host.includes('*')) {
  4219. addObj({ text: original_pattern, domain: false, tld_extra: false });
  4220. } else if (host.endsWith('.tld')) {
  4221. for (let i = 0; i < TLD_EXPANSION.length; i++) {
  4222. const tld = TLD_EXPANSION[i];
  4223. addObj({
  4224. text: host.replace(/tld$/i, tld),
  4225. domain: true,
  4226. tld_extra: i != 0
  4227. });
  4228. }
  4229. } else if (host.endsWith('.')) {
  4230. addObj({
  4231. text: host.slice(0, -1),
  4232. domain: true,
  4233. tld_extra: false
  4234. });
  4235. } else {
  4236. addObj({
  4237. text: host,
  4238. domain: true,
  4239. tld_extra: false
  4240. });
  4241. }
  4242. // eslint-disable-next-line no-unused-vars
  4243. } catch (ex) {
  4244. addObj({ text: original_pattern, domain: false, tld_extra: false });
  4245. }
  4246. }
  4247. } catch (ex) {
  4248. err(ex);
  4249. }
  4250. }
  4251. this.data_names = [...name_map.values()];
  4252. return this.data_names;
  4253. }
  4254. intersect(a, ...arr) {
  4255. return !isBlank([...new Set(a)].filter((v) => arr.every((b) => b.includes(v))));
  4256. }
  4257. }
  4258. const template = {
  4259. id: 0,
  4260. bad_ratings: 0,
  4261. good_ratings: 0,
  4262. ok_ratings: 0,
  4263. daily_installs: 0,
  4264. total_installs: 0,
  4265. name: 'NOT FOUND',
  4266. description: 'NOT FOUND',
  4267. version: '0.0.0',
  4268. url: BLANK_PAGE,
  4269. code_url: BLANK_PAGE,
  4270. created_at: Date.now(),
  4271. code_updated_at: Date.now(),
  4272. locale: 'NOT FOUND',
  4273. deleted: false,
  4274. users: []
  4275. };
  4276. const mkList = (txt = '', obj = {}) => {
  4277. if (!obj.root || !obj.type) {
  4278. return;
  4279. }
  4280. const { root, type } = obj;
  4281. const appliesTo = make('mu-js', 'mujs-list', {
  4282. textContent: `${txt}: `
  4283. });
  4284. const applyList = make('mu-js', 'mujs-grants');
  4285. const ujsURLs = make('mujs-column', 'mujs-list', {
  4286. dataset: {
  4287. el: 'matches',
  4288. type
  4289. }
  4290. });
  4291. ujsURLs.append(appliesTo, applyList);
  4292. root.append(ujsURLs);
  4293.  
  4294. const list = obj.list ?? [];
  4295. if (isEmpty(list)) {
  4296. const elem = make('mujs-a', {
  4297. textContent: i18n$('listing_none')
  4298. });
  4299. applyList.append(elem);
  4300. dom.cl.add(ujsURLs, 'hidden');
  4301. return;
  4302. }
  4303. for (const c of list) {
  4304. if (typeof c === 'string' && c.startsWith('http')) {
  4305. const elem = make('mujs-a', {
  4306. textContent: c,
  4307. dataset: {
  4308. command: 'open-tab',
  4309. webpage: c
  4310. }
  4311. });
  4312. applyList.append(elem);
  4313. } else if (isObj(c)) {
  4314. if (type === 'resource') {
  4315. for (const [k, v] of Object.entries(c)) {
  4316. const elem = make('mujs-a', {
  4317. textContent: k ?? 'ERROR'
  4318. });
  4319. if (v.startsWith('http')) {
  4320. elem.dataset.command = 'open-tab';
  4321. elem.dataset.webpage = v;
  4322. }
  4323. applyList.append(elem);
  4324. }
  4325. } else {
  4326. const elem = make('mujs-a', {
  4327. textContent: c.text
  4328. });
  4329. if (c.domain) {
  4330. elem.dataset.command = 'open-tab';
  4331. elem.dataset.webpage = `https://${c.text}`;
  4332. }
  4333. applyList.append(elem);
  4334. }
  4335. } else {
  4336. const elem = make('mujs-a', {
  4337. textContent: c
  4338. });
  4339. applyList.append(elem);
  4340. }
  4341. }
  4342. };
  4343. /**
  4344. * @param {number} [time]
  4345. */
  4346. const timeoutFrame = async (time) => {
  4347. frameTimeout.clear(...frameTimeout.ids);
  4348. if (dom.cl.has(mainframe, 'hidden')) {
  4349. return;
  4350. }
  4351. time = time ?? cfg.time ?? DEFAULT_CONFIG.time;
  4352. let n = 10000;
  4353. if (typeof time === 'number' && !Number.isNaN(time)) {
  4354. n = container.isBlacklisted ? time / 2 : time;
  4355. }
  4356. await frameTimeout.set(n);
  4357. container.remove();
  4358. return frameTimeout.clear(...frameTimeout.ids);
  4359. };
  4360. // #region Create UserJS
  4361. /**
  4362. * @param { import("../typings/types.d.ts").GSForkQuery } ujs
  4363. * @param { string } engine
  4364. */
  4365. const createjs = (ujs, engine) => {
  4366. // Lets not add this UserJS to the list
  4367. if (ujs.id === 421603) {
  4368. return;
  4369. }
  4370. if (badUserJS.includes(ujs.id) || badUserJS.includes(ujs.url)) {
  4371. return;
  4372. }
  4373. if (!container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs);
  4374. const eframe = make('td', 'install-btn');
  4375. const uframe = make('td', 'mujs-uframe');
  4376. const fdaily = make('td', 'mujs-list', {
  4377. textContent: ujs.daily_installs,
  4378. dataset: {
  4379. name: 'daily_installs'
  4380. }
  4381. });
  4382. const fupdated = make('td', 'mujs-list', {
  4383. textContent: language.toDate(ujs.code_updated_at),
  4384. dataset: {
  4385. name: 'code_updated_at',
  4386. value: new Date(ujs.code_updated_at).toISOString()
  4387. }
  4388. });
  4389. const fname = make('td', 'mujs-name');
  4390. const fmore = make('mujs-column', 'mujs-list hidden', {
  4391. dataset: {
  4392. el: 'more-info'
  4393. }
  4394. });
  4395. const fBtns = make('mujs-column', 'mujs-list hidden');
  4396. const jsInfo = make('mujs-row', 'mujs-list');
  4397. const jsInfoB = make('mujs-row', 'mujs-list');
  4398. const ratings = make('mujs-column', 'mujs-list');
  4399. const ftitle = make('mujs-a', 'mujs-homepage', {
  4400. textContent: ujs.name,
  4401. title: ujs.url,
  4402. dataset: {
  4403. command: 'open-tab',
  4404. webpage: ujs.url
  4405. }
  4406. });
  4407. const fver = make('mu-js', 'mujs-list', {
  4408. textContent: `${i18n$('version_number')}: ${ujs.version}`
  4409. });
  4410. const fcreated = make('mu-js', 'mujs-list', {
  4411. textContent: `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}`,
  4412. dataset: {
  4413. name: 'created_at',
  4414. value: new Date(ujs.created_at).toISOString()
  4415. }
  4416. });
  4417. const flicense = make('mu-js', 'mujs-list', {
  4418. title: ujs.license ?? i18n$('no_license'),
  4419. textContent: `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`,
  4420. dataset: {
  4421. name: 'license'
  4422. }
  4423. });
  4424. const ftotal = make('mu-js', 'mujs-list', {
  4425. textContent: `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}`,
  4426. dataset: {
  4427. name: 'total_installs'
  4428. }
  4429. });
  4430. const fratings = make('mu-js', 'mujs-list', {
  4431. title: i18n$('ratings'),
  4432. textContent: `${i18n$('ratings')}:`
  4433. });
  4434. const fgood = make('mu-js', 'mujs-list mujs-ratings', {
  4435. title: i18n$('good'),
  4436. textContent: ujs.good_ratings,
  4437. dataset: {
  4438. name: 'good_ratings',
  4439. el: 'good'
  4440. }
  4441. });
  4442. const fok = make('mu-js', 'mujs-list mujs-ratings', {
  4443. title: i18n$('ok'),
  4444. textContent: ujs.ok_ratings,
  4445. dataset: {
  4446. name: 'ok_ratings',
  4447. el: 'ok'
  4448. }
  4449. });
  4450. const fbad = make('mu-js', 'mujs-list mujs-ratings', {
  4451. title: i18n$('bad'),
  4452. textContent: ujs.bad_ratings,
  4453. dataset: {
  4454. name: 'bad_ratings',
  4455. el: 'bad'
  4456. }
  4457. });
  4458. const fdesc = make('mu-js', 'mujs-list mujs-pointer', {
  4459. title: ujs.description,
  4460. textContent: ujs.description,
  4461. dataset: {
  4462. command: 'list-description'
  4463. }
  4464. });
  4465. const scriptInstall = make('mu-jsbtn', 'install', {
  4466. innerHTML: `${iconSVG.load('install')} ${i18n$('install')}`,
  4467. title: `${i18n$('install')} "${ujs.name}"`,
  4468. dataset: {
  4469. command: 'install-script',
  4470. userjs: ujs.code_url
  4471. }
  4472. });
  4473. const scriptDownload = make('mu-jsbtn', {
  4474. innerHTML: `${iconSVG.load('download')} ${i18n$('saveFile')}`,
  4475. dataset: {
  4476. command: 'download-userjs',
  4477. userjs: ujs.id,
  4478. userjsName: ujs.name
  4479. }
  4480. });
  4481. const tr = make('tr', 'frame', {
  4482. dataset: {
  4483. scriptId: ujs.id
  4484. }
  4485. });
  4486. const codeArea = make('textarea', 'code-area hidden', {
  4487. dataset: {
  4488. name: 'code'
  4489. },
  4490. rows: '10',
  4491. autocomplete: false,
  4492. spellcheck: false,
  4493. wrap: 'soft'
  4494. });
  4495. const loadCode = make('mu-jsbtn', {
  4496. innerHTML: `${iconSVG.load('code')} ${i18n$('preview_code')}`,
  4497. dataset: {
  4498. command: 'load-userjs',
  4499. userjs: ujs.id
  4500. }
  4501. });
  4502. const loadMetadata = make('mu-jsbtn', {
  4503. innerHTML: `${iconSVG.load('code')} Metadata`,
  4504. dataset: {
  4505. command: 'load-header',
  4506. userjs: ujs.id
  4507. }
  4508. });
  4509. tr.dataset.engine = engine;
  4510. if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) {
  4511. tr.dataset.good = 'upsell';
  4512. }
  4513. for (const u of ujs.users) {
  4514. const user = make('mujs-a', {
  4515. innerHTML: u.name,
  4516. title: u.url,
  4517. dataset: {
  4518. command: 'open-tab',
  4519. webpage: u.url
  4520. }
  4521. });
  4522. if (cfg.recommend.author && u.id === authorID) {
  4523. tr.dataset.author = 'upsell';
  4524. dom.prop(user, 'innerHTML', `${u.name} ${iconSVG.load('verified')}`);
  4525. }
  4526. uframe.append(user);
  4527. }
  4528. if (cfg.recommend.others && goodUserJS.includes(ujs.id)) {
  4529. tr.dataset.good = 'upsell';
  4530. }
  4531. eframe.append(scriptInstall);
  4532. ratings.append(fratings, fgood, fok, fbad);
  4533. jsInfo.append(ftotal, ratings, fver, fcreated);
  4534. mkList(i18n$('code_size'), {
  4535. list: ujs._mujs.code.code_size,
  4536. type: 'code_size',
  4537. root: jsInfo
  4538. });
  4539.  
  4540. jsInfoB.append(flicense);
  4541. const data_meta = ujs._mujs.code?.data_meta ?? {};
  4542. mkList(i18n$('antifeatures'), {
  4543. list: data_meta.antifeatures ?? [],
  4544. type: 'antifeatures',
  4545. root: jsInfoB
  4546. });
  4547. mkList(i18n$('applies_to'), {
  4548. list: ujs._mujs.code?.data_names ?? [],
  4549. type: 'data_names',
  4550. root: jsInfoB
  4551. });
  4552. mkList('@grant', {
  4553. list: data_meta.grant ?? [],
  4554. type: 'grant',
  4555. root: jsInfoB
  4556. });
  4557. mkList('@require', {
  4558. list: data_meta.require,
  4559. type: 'require',
  4560. root: jsInfoB
  4561. });
  4562. mkList('@resource', {
  4563. list: isNull(data_meta.resource) ? [] : [data_meta.resource],
  4564. type: 'resource',
  4565. root: jsInfoB
  4566. });
  4567. fmore.append(jsInfo, jsInfoB);
  4568. fBtns.append(scriptDownload, loadCode, loadMetadata);
  4569. fname.append(ftitle, fdesc, fmore, fBtns, codeArea);
  4570.  
  4571. const loadPage = make('mu-jsbtn', {
  4572. innerHTML: `${iconSVG.load('pager')} Page`,
  4573. dataset: {
  4574. command: 'load-page',
  4575. userjs: ujs.id
  4576. }
  4577. });
  4578. fBtns.append(loadPage);
  4579.  
  4580. if (ujs._mujs.code?.translated) tr.classList.add('translated');
  4581.  
  4582. for (const e of [fname, uframe, fdaily, fupdated, eframe]) tr.append(e);
  4583. ujs._mujs.root = tr;
  4584. return ujs._mujs.root;
  4585. };
  4586. // #endregion
  4587. const loadFilters = () => {
  4588. /** @type {Map<string, import("../typings/types.d.ts").Filters >} */
  4589. const pool = new Map();
  4590. const handles = {
  4591. pool,
  4592. enabled() {
  4593. return [...pool.values()].filter((o) => o.enabled);
  4594. },
  4595. refresh() {
  4596. if (!Object.is(pool.size, 0)) pool.clear();
  4597. for (const [key, value] of Object.entries(cfg.filters)) {
  4598. if (!pool.has(key))
  4599. pool.set(key, {
  4600. ...value,
  4601. reg: new RegExp(value.regExp, value.flag),
  4602. keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'),
  4603. valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi')
  4604. });
  4605. }
  4606. return this;
  4607. },
  4608. get(str) {
  4609. return [...pool.values()].find((v) => v.keyReg.test(str) || v.valueReg.test(str));
  4610. },
  4611. /**
  4612. * @param { import("../typings/types.d.ts").GSForkQuery } param0
  4613. */
  4614. match({ name, users }) {
  4615. const p = handles.enabled();
  4616. if (Object.is(p.length, 0)) return true;
  4617. for (const v of p) {
  4618. if ([{ name }, ...users].find((o) => o.name.match(v.reg))) return false;
  4619. }
  4620. return true;
  4621. }
  4622. };
  4623. for (const [key, value] of Object.entries(cfg.filters)) {
  4624. if (!pool.has(key))
  4625. pool.set(key, {
  4626. ...value,
  4627. reg: new RegExp(value.regExp, value.flag),
  4628. keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'),
  4629. valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi')
  4630. });
  4631. }
  4632. return handles.refresh();
  4633. };
  4634. // #region List
  4635. class List {
  4636. engines;
  4637. intHost;
  4638. constructor(hostname = undefined) {
  4639. this.build = this.build.bind(this);
  4640. this.toArr = this.toArr.bind(this);
  4641. this.groupBy = this.groupBy.bind(this);
  4642. this.dispatch = this.dispatch.bind(this);
  4643. this.sortRecords = this.sortRecords.bind(this);
  4644. if (isEmpty(hostname)) hostname = container.host;
  4645. this.engines = cfg.engines;
  4646. this.host = hostname;
  4647. }
  4648.  
  4649. dispatch(ujs) {
  4650. const { CustomEvent } = safeSelf();
  4651. const customEvent = new CustomEvent('updateditem', { detail: ujs });
  4652. main.dispatchEvent(customEvent);
  4653. }
  4654.  
  4655. set host(hostname) {
  4656. this.intHost = hostname;
  4657.  
  4658. if (!container.cache.has(hostname)) {
  4659. const engineTemplate = {};
  4660. for (const engine of cfg.engines) {
  4661. engineTemplate[engine.name] = [];
  4662. }
  4663. container.cache.set(hostname, engineTemplate);
  4664. }
  4665. this.blacklisted = container.checkBlacklist(hostname);
  4666. if (this.blacklisted) {
  4667. showError(`Blacklisted "${hostname}"`);
  4668. timeoutFrame();
  4669. }
  4670.  
  4671. this.engines = cfg.engines.filter((e) => {
  4672. if (!e.enabled) {
  4673. return false;
  4674. }
  4675. const v = engineUnsupported[e.name] ?? [];
  4676. if (v.includes(hostname)) {
  4677. showError(`Engine: "${e.name}" unsupported on "${hostname}"`);
  4678. timeoutFrame();
  4679. return false;
  4680. }
  4681. return true;
  4682. });
  4683. }
  4684.  
  4685. get host() {
  4686. return this.intHost;
  4687. }
  4688.  
  4689. // #region Builder
  4690. build() {
  4691. try {
  4692. container.refresh();
  4693. const { blacklisted, engines, host, toArr, dispatch } = this;
  4694. if (blacklisted || isEmpty(engines)) {
  4695. container.opacityMin = '0';
  4696. mainframe.style.opacity = container.opacityMin;
  4697. return;
  4698. }
  4699. const fetchRecords = [];
  4700. const bsFilter = loadFilters();
  4701. const hostCache = toArr();
  4702.  
  4703. info('Building list', { hostCache, engines });
  4704.  
  4705. if (isBlank(hostCache)) {
  4706. for (const engine of engines) {
  4707. info(`Fetching from "${engine.name}" for "${host}"`);
  4708. const respError = (error) => {
  4709. if (!error.cause) error.cause = engine.name;
  4710. if (error.message.startsWith('429')) {
  4711. showError(`Engine: "${engine.name}" Too many requests...`);
  4712. return;
  4713. }
  4714. showError(`Engine: "${engine.name}"`, error.message);
  4715. };
  4716. const _mujs = (d) => {
  4717. const obj = {
  4718. ...template,
  4719. ...d,
  4720. _mujs: {
  4721. root: {},
  4722. info: {
  4723. engine,
  4724. host
  4725. },
  4726. code: {
  4727. meta: {},
  4728. request: async function (translate = false, code_url) {
  4729. if (this.data_code_block) {
  4730. return this;
  4731. }
  4732. code_url = code_url ?? d.code_url;
  4733. /** @type { string } */
  4734. const code = await Network.req(code_url, 'GET', 'text').catch(showError);
  4735. if (typeof code !== 'string') {
  4736. return this;
  4737. }
  4738. const code_obj = new ParseUserJS(code, /\.user\.css/.test(code_url));
  4739. const { data_meta } = code_obj;
  4740. if (translate) {
  4741. for (const k of userjs.pool.keys()) {
  4742. if (data_meta[`name:${k}`]) {
  4743. Object.assign(obj, {
  4744. name: data_meta[`name:${k}`]
  4745. });
  4746. this.translated = true;
  4747. }
  4748. if (data_meta[`description:${k}`]) {
  4749. Object.assign(obj, {
  4750. description: data_meta[`description:${k}`]
  4751. });
  4752. this.translated = true;
  4753. }
  4754. }
  4755. }
  4756. if (Array.isArray(data_meta.grant)) {
  4757. data_meta.grant = union(data_meta.grant);
  4758. }
  4759. if (data_meta.resource) {
  4760. const obj = {};
  4761. if (typeof data_meta.resource === 'string') {
  4762. const reg = /(.+)\s+(.+)/.exec(data_meta.resource);
  4763. if (reg) {
  4764. obj[reg[1].trim()] = reg[2];
  4765. }
  4766. } else {
  4767. for (const r of data_meta.resource) {
  4768. const reg = /(.+)\s+(http.+)/.exec(r);
  4769. if (reg) {
  4770. obj[reg[1].trim()] = reg[2];
  4771. }
  4772. }
  4773. }
  4774. data_meta.resource = obj;
  4775. }
  4776. Object.assign(this, {
  4777. code_size: [Network.format(code.length)],
  4778. meta: data_meta,
  4779. ...code_obj
  4780. });
  4781.  
  4782. return this;
  4783. }
  4784. }
  4785. }
  4786. };
  4787. return obj;
  4788. };
  4789. /**
  4790. * Prior to UserScript v7.0.0
  4791. * @template {string} F
  4792. * @param {F} fallback
  4793. * @returns {F}
  4794. */
  4795. const toQuery = (fallback) => {
  4796. if (engine.query) {
  4797. return decodeURIComponent(engine.query).replace(/\{host\}/g, host);
  4798. }
  4799. return fallback;
  4800. };
  4801. /**
  4802. * @param { import("../typings/types.d.ts").GSFork } dataQ
  4803. */
  4804. const forkFN = async (dataQ) => {
  4805. if (!dataQ) {
  4806. showError('Invalid data received from the server, check internet connection');
  4807. return;
  4808. }
  4809. /**
  4810. * @type { import("../typings/types.d.ts").GSForkQuery[] }
  4811. */
  4812. const dq = Array.isArray(dataQ)
  4813. ? dataQ
  4814. : Array.isArray(dataQ.query)
  4815. ? dataQ.query
  4816. : [];
  4817. const dataA = dq
  4818. .filter(Boolean)
  4819. .filter((d) => !d.deleted)
  4820. .filter(bsFilter.match);
  4821. if (isBlank(dataA)) {
  4822. return;
  4823. }
  4824. const data = dataA.map(_mujs);
  4825. const otherLng = [];
  4826. /**
  4827. * @param {import("../typings/types.d.ts").GSForkQuery} d
  4828. * @returns {boolean}
  4829. */
  4830. const inUserLanguage = (d) => {
  4831. if (userjs.pool.has(d.locale.split('-')[0] ?? d.locale)) {
  4832. return true;
  4833. }
  4834. otherLng.push(d);
  4835. return false;
  4836. };
  4837. const filterLang = data.filter((d) => {
  4838. if (cfg.filterlang && !inUserLanguage(d)) {
  4839. return false;
  4840. }
  4841. return true;
  4842. });
  4843. let finalList = filterLang;
  4844. const hds = [];
  4845. for (const ujs of otherLng) {
  4846. const c = await ujs._mujs.code.request(true);
  4847. if (c.translated) {
  4848. hds.push(ujs);
  4849. }
  4850. }
  4851. finalList = union(hds, filterLang);
  4852.  
  4853. for (const ujs of finalList) {
  4854. if (
  4855. !ujs._mujs.code.data_code_block &&
  4856. (cfg.preview.code || cfg.preview.metadata)
  4857. ) {
  4858. ujs._mujs.code.request().then(() => {
  4859. dispatch(ujs);
  4860. });
  4861. }
  4862. createjs(ujs, engine.name);
  4863. }
  4864. };
  4865. /**
  4866. * @param {Document} htmlDocument
  4867. */
  4868. const openuserjs = async (htmlDocument) => {
  4869. try {
  4870. if (!htmlDocument) {
  4871. showError('Invalid data received from the server, TODO fix this');
  4872. return;
  4873. }
  4874. const selected = htmlDocument.documentElement;
  4875. if (/openuserjs/gi.test(engine.name)) {
  4876. const col = qsA('.col-sm-8 .tr-link', selected) ?? [];
  4877. for (const i of col) {
  4878. while (isNull(qs('.script-version', i))) {
  4879. await new Promise((resolve) => requestAnimationFrame(resolve));
  4880. }
  4881. const fixurl = dom
  4882. .prop(qs('.tr-link-a', i), 'href')
  4883. .replace(
  4884. new RegExp(document.location.origin, 'gi'),
  4885. 'https://openuserjs.org'
  4886. );
  4887. const ujs = _mujs({
  4888. name: dom.text(qs('.tr-link-a', i)),
  4889. description: dom.text(qs('p', i)),
  4890. version: dom.text(qs('.script-version', i)),
  4891. url: fixurl,
  4892. code_url: `${fixurl.replace(/\/scripts/gi, '/install')}.user.js`,
  4893. total_installs: dom.text(qs('td:nth-child(2) p', i)),
  4894. created_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'),
  4895. code_updated_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'),
  4896. users: [
  4897. {
  4898. name: dom.text(qs('.inline-block a', i)),
  4899. url: dom.prop(qs('.inline-block a', i), 'href')
  4900. }
  4901. ]
  4902. });
  4903. if (bsFilter.match(ujs)) {
  4904. continue;
  4905. }
  4906. if (
  4907. !ujs._mujs.code.data_code_block &&
  4908. (cfg.preview.code || cfg.preview.metadata)
  4909. ) {
  4910. ujs._mujs.code.request().then(() => {
  4911. dispatch(ujs);
  4912. });
  4913. }
  4914. createjs(ujs, engine.name);
  4915. }
  4916. }
  4917. } catch (ex) {
  4918. showError(ex);
  4919. }
  4920. };
  4921. const gitFN = (data) => {
  4922. try {
  4923. if (isBlank(data.items)) {
  4924. showError('Invalid data received from the server, TODO fix this');
  4925. return;
  4926. }
  4927. for (const r of data.items) {
  4928. const ujs = _mujs({
  4929. id: r.repository.id ?? r.id ?? 0,
  4930. name: r.repository.name ?? r.name,
  4931. description: isEmpty(r.repository.description)
  4932. ? i18n$('no_license')
  4933. : r.repository.description,
  4934. url: r.repository.html_url,
  4935. code_url: r.html_url.replace(/\/blob\//g, '/raw/'),
  4936. page_url: `${r.repository.url}/contents/README.md`,
  4937. users: [
  4938. {
  4939. name: r.repository.owner.login,
  4940. url: r.repository.owner.html_url
  4941. }
  4942. ]
  4943. });
  4944. // if (bsFilter.match(ujs)) {
  4945. // continue;
  4946. // }
  4947. Network.req(r.repository.url, 'GET', 'json', {
  4948. headers: {
  4949. Accept: 'application/vnd.github+json',
  4950. Authorization: `Bearer ${engine.token}`,
  4951. 'X-GitHub-Api-Version': '2022-11-28'
  4952. }
  4953. }).then((repository) => {
  4954. ujs.code_updated_at = r.commit || repository.updated_at || Date.now();
  4955. ujs.created_at = repository.created_at;
  4956. ujs.daily_installs = repository.watchers_count ?? 0;
  4957. ujs.good_ratings = repository.stargazers_count ?? 0;
  4958. if (repository.license?.name) ujs.license = repository.license.name;
  4959. dispatch(ujs);
  4960. });
  4961. if (
  4962. !ujs._mujs.code.data_code_block &&
  4963. (cfg.preview.code || cfg.preview.metadata)
  4964. ) {
  4965. ujs._mujs.code.request().then(() => {
  4966. dispatch(ujs);
  4967. });
  4968. }
  4969. createjs(ujs, engine.name);
  4970. }
  4971. } catch (ex) {
  4972. showError(ex);
  4973. }
  4974. };
  4975. let netFN;
  4976. if (/github/gi.test(engine.name)) {
  4977. if (isEmpty(engine.token)) {
  4978. showError(`"${engine.name}" requires a token to use`);
  4979. continue;
  4980. }
  4981. netFN = Network.req(
  4982. toQuery(
  4983. `${engine.url}"// ==UserScript=="+${host}+ "// ==/UserScript=="+in:file+language:js&per_page=30`
  4984. ),
  4985. 'GET',
  4986. 'json',
  4987. {
  4988. headers: {
  4989. Accept: 'application/vnd.github+json',
  4990. Authorization: `Bearer ${engine.token}`,
  4991. 'X-GitHub-Api-Version': '2022-11-28'
  4992. }
  4993. }
  4994. )
  4995. .then(gitFN)
  4996. .then(() => {
  4997. Network.req('https://api.github.com/rate_limit', 'GET', 'json', {
  4998. headers: {
  4999. Accept: 'application/vnd.github+json',
  5000. Authorization: `Bearer ${engine.token}`,
  5001. 'X-GitHub-Api-Version': '2022-11-28'
  5002. }
  5003. })
  5004. .then((data) => {
  5005. for (const [key, value] of Object.entries(data.resources.code_search)) {
  5006. const txt = make('mujs-row', 'rate-info', {
  5007. textContent: `${key.toUpperCase()}: ${value}`
  5008. });
  5009. rateContainer.append(txt);
  5010. }
  5011. })
  5012. .catch(respError);
  5013. });
  5014. } else if (/openuserjs/gi.test(engine.name)) {
  5015. netFN = Network.req(toQuery(`${engine.url}${host}`), 'GET', 'document').then(
  5016. openuserjs
  5017. );
  5018. } else {
  5019. netFN = Network.req(
  5020. toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`)
  5021. ).then(forkFN);
  5022. }
  5023. if (netFN) {
  5024. fetchRecords.push(netFN.catch(respError));
  5025. }
  5026. }
  5027. } else {
  5028. for (const ujs of hostCache) tabbody.append(ujs._mujs.root);
  5029. }
  5030.  
  5031. urlBar.placeholder = i18n$('search_placeholder');
  5032. urlBar.value = '';
  5033.  
  5034. if (isBlank(fetchRecords)) {
  5035. this.sortRecords();
  5036. return;
  5037. }
  5038. Promise.allSettled(fetchRecords).then(this.sortRecords).catch(showError);
  5039. } catch (ex) {
  5040. showError(ex);
  5041. }
  5042. }
  5043.  
  5044. sortRecords() {
  5045. const arr = this.toArr();
  5046. for (const ujs of arr.flat().sort((a, b) => {
  5047. const sortType = cfg.autoSort ?? 'daily_installs';
  5048. return b[sortType] - a[sortType];
  5049. })) {
  5050. if (isElem(ujs._mujs.root)) tabbody.append(ujs._mujs.root);
  5051. }
  5052. for (const [name, value] of Object.entries(this.groupBy(arr)))
  5053. Counter.update(value.length, { name });
  5054. }
  5055.  
  5056. toArr() {
  5057. const h = this.intHost;
  5058. return container.toArr().filter(({ _mujs }) => _mujs.info.host === h);
  5059. }
  5060.  
  5061. groupBy(arr) {
  5062. const callback = ({ _mujs }) => _mujs.info.engine.name;
  5063. if (isFN(Object.groupBy)) {
  5064. return Object.groupBy(arr, callback);
  5065. }
  5066. /** [Object.groupBy polyfill](https://gist.github.com/gtrabanco/7c97bd41aa74af974fa935bfb5044b6e) */
  5067. return arr.reduce((acc = {}, ...args) => {
  5068. const key = callback(...args);
  5069. acc[key] ??= [];
  5070. acc[key].push(args[0]);
  5071. return acc;
  5072. }, {});
  5073. }
  5074. // #endregion
  5075. }
  5076. const MUList = new List();
  5077. // #endregion
  5078. // #region Make Config
  5079. const makecfg = () => {
  5080. const cbtn = make('mu-js', 'mujs-sty-flex');
  5081. const savebtn = make('mujs-btn', 'save', {
  5082. textContent: i18n$('save'),
  5083. dataset: {
  5084. command: 'save'
  5085. },
  5086. disabled: false
  5087. });
  5088. const resetbtn = make('mujs-btn', 'reset', {
  5089. textContent: i18n$('reset'),
  5090. dataset: {
  5091. command: 'reset'
  5092. }
  5093. });
  5094. cbtn.append(resetbtn, savebtn);
  5095.  
  5096. const makesection = (name, tag) => {
  5097. tag = tag ?? i18n$('no_license');
  5098. name = name ?? i18n$('no_license');
  5099. const sec = make('mujs-section', {
  5100. dataset: {
  5101. name: tag
  5102. }
  5103. });
  5104. const lb = make('label', {
  5105. dataset: {
  5106. command: tag
  5107. }
  5108. });
  5109. const divDesc = make('mu-js', {
  5110. textContent: name
  5111. });
  5112. ael(sec, 'click', (evt) => {
  5113. /** @type { HTMLElement } */
  5114. const target = evt.target.closest('[data-command]');
  5115. if (!target) {
  5116. return;
  5117. }
  5118. const cmd = target.dataset.command;
  5119. if (cmd === tag) {
  5120. const a = qsA(`[data-${tag}]`, sec);
  5121. if (dom.cl.has(a, 'hidden')) {
  5122. dom.cl.remove(a, 'hidden');
  5123. } else {
  5124. dom.cl.add(a, 'hidden');
  5125. }
  5126. }
  5127. });
  5128.  
  5129. lb.append(divDesc);
  5130. sec.append(lb);
  5131. cfgpage.append(sec);
  5132. return sec;
  5133. };
  5134. const sections = {
  5135. general: makesection('General', 'general'),
  5136. load: makesection('Automation', 'load'),
  5137. list: makesection('List', 'list'),
  5138. filters: makesection('List Filters', 'filters'),
  5139. blacklist: makesection('Blacklist (WIP)', 'blacklist'),
  5140. engine: makesection('Search Engines', 'engine'),
  5141. theme: makesection('Theme Colors', 'theme'),
  5142. exp: makesection('Import / Export', 'exp')
  5143. };
  5144. const makeRow = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => {
  5145. const lb = make('label', 'sub-section hidden', {
  5146. textContent: text,
  5147. dataset: {
  5148. [tag]: text
  5149. }
  5150. });
  5151. cfgMap.set(text, value);
  5152. if (type === 'select') {
  5153. const inp = make('select', {
  5154. dataset: {
  5155. [tag]: text
  5156. },
  5157. ...attrs
  5158. });
  5159. for (const selV of Object.keys(template)) {
  5160. if (selV === 'deleted' || selV === 'users') continue;
  5161. const o = make('option', {
  5162. value: selV,
  5163. textContent: selV
  5164. });
  5165. inp.append(o);
  5166. }
  5167. inp.value = cfg[value];
  5168. lb.append(inp);
  5169. if (sections[tag]) {
  5170. sections[tag].append(lb);
  5171. }
  5172. return lb;
  5173. }
  5174. const inp = make('input', {
  5175. type,
  5176. dataset: {
  5177. [tag]: text
  5178. },
  5179. ...attrs
  5180. });
  5181.  
  5182. if (tag === 'engine') {
  5183. inp.dataset.name = value;
  5184. }
  5185.  
  5186. if (sections[tag]) {
  5187. sections[tag].append(lb);
  5188. }
  5189.  
  5190. if (type === 'checkbox') {
  5191. const inlab = make('mu-js', 'mujs-inlab');
  5192. const la = make('label', {
  5193. onclick() {
  5194. inp.dispatchEvent(new MouseEvent('click'));
  5195. }
  5196. });
  5197. inlab.append(inp, la);
  5198. lb.append(inlab);
  5199.  
  5200. const nm = /^(\w+)-(.+)/.exec(value);
  5201. if (nm) {
  5202. if (nm[1] === 'filters') {
  5203. inp.checked = cfg[nm[1]][nm[2]].enabled;
  5204. } else {
  5205. inp.checked = cfg[nm[1]][nm[2]];
  5206. }
  5207. } else {
  5208. inp.checked = cfg[value];
  5209. }
  5210. ael(inp, 'change', (evt) => {
  5211. container.unsaved = true;
  5212. if (/filterlang/i.test(value)) {
  5213. container.rebuild = true;
  5214. }
  5215. if (nm) {
  5216. if (nm[1] === 'filters') {
  5217. cfg[nm[1]][nm[2]].enabled = evt.target.checked;
  5218. } else {
  5219. cfg[nm[1]][nm[2]] = evt.target.checked;
  5220. }
  5221. } else {
  5222. cfg[value] = evt.target.checked;
  5223. }
  5224. });
  5225.  
  5226. if (tag === 'engine') {
  5227. const engine = cfg.engines.find((engine) => engine.name === value);
  5228. if (engine) {
  5229. inp.checked = engine.enabled;
  5230. inp.dataset.engine = engine.name;
  5231. ael(inp, 'change', (evt) => {
  5232. container.unsaved = true;
  5233. container.rebuild = true;
  5234. engine.enabled = evt.target.checked;
  5235. });
  5236.  
  5237. if (engine.query) {
  5238. const d = DEFAULT_CONFIG.engines.find((e) => e.name === engine.name);
  5239. const urlInp = make('input', {
  5240. type: 'text',
  5241. defaultValue: '',
  5242. value: decodeURIComponent(engine.query) ?? '',
  5243. placeholder: decodeURIComponent(d.query) ?? '',
  5244. dataset: {
  5245. name: nm,
  5246. engine: engine.name
  5247. },
  5248. onchange(evt) {
  5249. container.unsaved = true;
  5250. container.rebuild = true;
  5251. try {
  5252. engine.query = encodeURIComponent(new URL(evt.target.value).toString());
  5253. } catch (ex) {
  5254. err(ex);
  5255. }
  5256. }
  5257. });
  5258. lb.append(urlInp);
  5259. }
  5260. if (engine.name === 'github') {
  5261. const ghToken = make('input', {
  5262. type: 'text',
  5263. defaultValue: '',
  5264. value: engine.token ?? '',
  5265. placeholder: 'Paste Access Token',
  5266. dataset: {
  5267. engine: 'github-token'
  5268. },
  5269. onchange(evt) {
  5270. container.unsaved = true;
  5271. container.rebuild = true;
  5272. engine.token = evt.target.value;
  5273. }
  5274. });
  5275. lb.append(ghToken);
  5276. cfgMap.set('github-token', ghToken);
  5277. }
  5278. }
  5279. }
  5280. } else {
  5281. if (type === 'text') {
  5282. inp.defaultValue = '';
  5283. inp.value = value ?? '';
  5284. inp.placeholder = value ?? '';
  5285. }
  5286.  
  5287. lb.append(inp);
  5288. }
  5289.  
  5290. return lb;
  5291. };
  5292. if (isGM) {
  5293. makeRow(i18n$('userjs_sync'), 'cache');
  5294. makeRow(i18n$('userjs_autoinject'), 'autoinject', 'checkbox', 'load');
  5295. }
  5296. makeRow(i18n$('redirect'), 'sleazyredirect');
  5297. makeRow(`${i18n$('dtime')} (ms)`, 'time', 'number', 'general', {
  5298. defaultValue: 10000,
  5299. value: cfg.time,
  5300. min: 0,
  5301. step: 500,
  5302. onbeforeinput(evt) {
  5303. if (evt.target.validity.badInput) {
  5304. dom.cl.add(evt.target, 'mujs-invalid');
  5305. dom.prop(savebtn, 'disabled', true);
  5306. } else {
  5307. dom.cl.remove(evt.target, 'mujs-invalid');
  5308. dom.prop(savebtn, 'disabled', false);
  5309. }
  5310. },
  5311. oninput(evt) {
  5312. container.unsaved = true;
  5313. const t = evt.target;
  5314. if (t.validity.badInput || (t.validity.rangeUnderflow && t.value !== '-1')) {
  5315. dom.cl.add(t, 'mujs-invalid');
  5316. dom.prop(savebtn, 'disabled', true);
  5317. } else {
  5318. dom.cl.remove(t, 'mujs-invalid');
  5319. dom.prop(savebtn, 'disabled', false);
  5320. cfg.time = isEmpty(t.value) ? cfg.time : parseFloat(t.value);
  5321. }
  5322. }
  5323. });
  5324.  
  5325. makeRow(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load');
  5326. makeRow(i18n$('userjs_fullscreen'), 'autoexpand', 'checkbox', 'load', {
  5327. onchange(e) {
  5328. if (e.target.checked) {
  5329. dom.cl.add([btnfullscreen, main], 'expanded');
  5330. dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse'));
  5331. } else {
  5332. dom.cl.remove([btnfullscreen, main], 'expanded');
  5333. dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('expand'));
  5334. }
  5335. }
  5336. });
  5337. makeRow('Clear on Tab close', 'clearTabCache', 'checkbox', 'load');
  5338.  
  5339. makeRow('Default Sort', 'autoSort', 'select', 'list');
  5340. makeRow(i18n$('filter'), 'filterlang', 'checkbox', 'list');
  5341. makeRow(i18n$('preview_code'), 'preview-code', 'checkbox', 'list');
  5342. makeRow('Preview Metadata', 'preview-metadata', 'checkbox', 'list');
  5343. makeRow('Recommend author', 'recommend-author', 'checkbox', 'list');
  5344. makeRow('Recommend scripts', 'recommend-others', 'checkbox', 'list');
  5345.  
  5346. for (const [k, v] of Object.entries(cfg.filters)) {
  5347. makeRow(v.name, `filters-${k}`, 'checkbox', 'filters');
  5348. }
  5349.  
  5350. makeRow('Greasy Fork', 'greasyfork', 'checkbox', 'engine');
  5351. makeRow('Sleazy Fork', 'sleazyfork', 'checkbox', 'engine');
  5352. makeRow('Open UserJS', 'openuserjs', 'checkbox', 'engine');
  5353. makeRow('GitHub API', 'github', 'checkbox', 'engine');
  5354.  
  5355. for (const [k, v] of Object.entries(cfg.theme)) {
  5356. const lb = make('label', 'hidden', {
  5357. textContent: k,
  5358. dataset: {
  5359. theme: k
  5360. }
  5361. });
  5362. const inp = make('input', {
  5363. type: 'text',
  5364. defaultValue: '',
  5365. value: v ?? '',
  5366. placeholder: v ?? '',
  5367. dataset: {
  5368. theme: k
  5369. },
  5370. onchange(evt) {
  5371. let isvalid = true;
  5372. try {
  5373. const val = evt.target.value;
  5374. const sty = container.root.style;
  5375. const str = `--mujs-${k}`;
  5376. const prop = sty.getPropertyValue(str);
  5377. if (isEmpty(val)) {
  5378. cfg.theme[k] = DEFAULT_CONFIG.theme[k];
  5379. sty.removeProperty(str);
  5380. return;
  5381. }
  5382. if (prop === val) {
  5383. return;
  5384. }
  5385. sty.removeProperty(str);
  5386. sty.setProperty(str, val);
  5387. cfg.theme[k] = val;
  5388. } catch (ex) {
  5389. err(ex);
  5390. isvalid = false;
  5391. } finally {
  5392. if (isvalid) {
  5393. dom.cl.remove(evt.target, 'mujs-invalid');
  5394. dom.prop(savebtn, 'disabled', false);
  5395. } else {
  5396. dom.cl.add(evt.target, 'mujs-invalid');
  5397. dom.prop(savebtn, 'disabled', true);
  5398. }
  5399. }
  5400. }
  5401. });
  5402. cfgMap.set(k, inp);
  5403. lb.append(inp);
  5404. sections.theme.append(lb);
  5405. }
  5406.  
  5407. // const blacklist = make('textarea', {
  5408. // dataset: {
  5409. // name: 'blacklist'
  5410. // },
  5411. // rows: '10',
  5412. // autocomplete: false,
  5413. // spellcheck: false,
  5414. // wrap: 'soft',
  5415. // value: JSON.stringify(cfg.blacklist, null, ' '),
  5416. // oninput(evt) {
  5417. // let isvalid = true;
  5418. // try {
  5419. // cfg.blacklist = JSON.parse(evt.target.value);
  5420. // isvalid = true;
  5421. // } catch (ex) {
  5422. // err(ex);
  5423. // isvalid = false;
  5424. // } finally {
  5425. // if (isvalid) {
  5426. // dom.cl.remove(evt.target, 'mujs-invalid');
  5427. // dom.prop(savebtn, 'disabled', false);
  5428. // } else {
  5429. // dom.cl.add(evt.target, 'mujs-invalid');
  5430. // dom.prop(savebtn, 'disabled', true);
  5431. // }
  5432. // }
  5433. // }
  5434. // });
  5435. // cfgMap.set('blacklist', blacklist);
  5436. // const addList = make('mujs-add', {
  5437. // textContent: '+',
  5438. // dataset: {
  5439. // command: 'new-list'
  5440. // }
  5441. // });
  5442. // const n = make('input', {
  5443. // type: 'text',
  5444. // defaultValue: '',
  5445. // value: '',
  5446. // placeholder: 'Name',
  5447. // });
  5448. // const inpValue = make('input', {
  5449. // type: 'text',
  5450. // defaultValue: '',
  5451. // value: '',
  5452. // placeholder: 'Value',
  5453. // });
  5454. // const label = make('label', 'new-list hidden', {
  5455. // dataset: {
  5456. // blacklist: 'new-list'
  5457. // }
  5458. // });
  5459. // label.append(n, inpValue, addList);
  5460. // listSec.append(label);
  5461. // ael(addList, 'click', () => {
  5462. // if (isEmpty(n.value) || isEmpty(inpValue.value)) {
  5463. // return
  5464. // };
  5465. // createList(n.value, n.value, inpValue.value);
  5466. // });
  5467. const createList = (key, v = '', disabled = false, type = 'String') => {
  5468. let txt = key;
  5469. if (typeof key === 'string') {
  5470. if (key.startsWith('userjs-')) {
  5471. disabled = true;
  5472. const s = key.substring(7);
  5473. txt = `Built-in "${s}"`;
  5474. v = builtinList[s];
  5475. }
  5476. } else {
  5477. if (!key.enabled) {
  5478. return;
  5479. }
  5480. }
  5481.  
  5482. if (isRegExp(v)) {
  5483. v = v.toString();
  5484. type = 'RegExp';
  5485. } else {
  5486. v = JSON.stringify(v);
  5487. type = 'Object';
  5488. }
  5489.  
  5490. const lb = make('label', 'hidden', {
  5491. textContent: txt,
  5492. dataset: {
  5493. blacklist: key
  5494. }
  5495. });
  5496. const inp = make('input', {
  5497. type: 'text',
  5498. defaultValue: '',
  5499. value: v ?? '',
  5500. placeholder: v ?? '',
  5501. dataset: {
  5502. blacklist: key
  5503. },
  5504. onchange(evt) {
  5505. let isvalid = true;
  5506. try {
  5507. const val = evt.target.value;
  5508. if (isEmpty(val)) {
  5509. return;
  5510. }
  5511. isvalid = true;
  5512. } catch (ex) {
  5513. err(ex);
  5514. isvalid = false;
  5515. } finally {
  5516. if (isvalid) {
  5517. dom.cl.remove(evt.target, 'mujs-invalid');
  5518. dom.prop(savebtn, 'disabled', false);
  5519. } else {
  5520. dom.cl.add(evt.target, 'mujs-invalid');
  5521. dom.prop(savebtn, 'disabled', true);
  5522. }
  5523. }
  5524. }
  5525. });
  5526. const selType = make('select', {
  5527. disabled,
  5528. dataset: {
  5529. blacklist: key
  5530. }
  5531. });
  5532. if (disabled) {
  5533. inp.readOnly = true;
  5534. const o = make('option', {
  5535. value: type,
  5536. textContent: type
  5537. });
  5538. selType.append(o);
  5539. } else {
  5540. for (const selV of ['String', 'RegExp', 'Object']) {
  5541. const o = make('option', {
  5542. value: selV,
  5543. textContent: selV
  5544. });
  5545. selType.append(o);
  5546. }
  5547. }
  5548. selType.value = type;
  5549. lb.append(inp, selType);
  5550. sections.blacklist.append(lb);
  5551. };
  5552. for (const key of cfg.blacklist) {
  5553. createList(key);
  5554. }
  5555.  
  5556. const transfers = {
  5557. export: {
  5558. cfg: make('mujs-btn', 'mujs-export sub-section hidden', {
  5559. textContent: i18n$('export_config'),
  5560. dataset: {
  5561. command: 'export-cfg',
  5562. exp: 'export-cfg'
  5563. }
  5564. }),
  5565. theme: make('mujs-btn', 'mujs-export sub-section hidden', {
  5566. textContent: i18n$('export_theme'),
  5567. dataset: {
  5568. command: 'export-theme',
  5569. exp: 'export-theme'
  5570. }
  5571. })
  5572. },
  5573. import: {
  5574. cfg: make('mujs-btn', 'mujs-import sub-section hidden', {
  5575. textContent: i18n$('import_config'),
  5576. dataset: {
  5577. command: 'import-cfg',
  5578. exp: 'import-cfg'
  5579. }
  5580. }),
  5581. theme: make('mujs-btn', 'mujs-import sub-section hidden', {
  5582. textContent: i18n$('import_theme'),
  5583. dataset: {
  5584. command: 'import-theme',
  5585. exp: 'import-theme'
  5586. }
  5587. })
  5588. }
  5589. };
  5590. for (const value of Object.values(transfers)) {
  5591. for (const v of Object.values(value)) {
  5592. sections.exp.append(v);
  5593. }
  5594. }
  5595.  
  5596. cfgpage.append(cbtn);
  5597. };
  5598. // #endregion
  5599. container.tab.custom = (host) => {
  5600. MUList.host = host;
  5601. respHandles.build();
  5602. };
  5603. ael(mainframe, 'mouseenter', (evt) => {
  5604. evt.preventDefault();
  5605. evt.stopPropagation();
  5606. evt.target.style.opacity = container.opacityMax;
  5607. frameTimeout.clear(...frameTimeout.ids);
  5608. });
  5609. ael(mainframe, 'mouseleave', (evt) => {
  5610. evt.preventDefault();
  5611. evt.stopPropagation();
  5612. evt.target.style.opacity = container.opacityMin;
  5613. timeoutFrame();
  5614. });
  5615. ael(mainframe, 'click', (evt) => {
  5616. evt.preventDefault();
  5617. frameTimeout.clear(...frameTimeout.ids);
  5618. dom.cl.remove(main, 'hidden');
  5619. dom.cl.add(mainframe, 'hidden');
  5620. if (cfg.autoexpand) {
  5621. dom.cl.add([btnfullscreen, main], 'expanded');
  5622. dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse'));
  5623. }
  5624. if (dom.cl.has(mainframe, 'error')) {
  5625. tab.create('mujs:settings');
  5626. }
  5627. });
  5628. ael(urlBar, 'input', (evt) => {
  5629. evt.preventDefault();
  5630. if (urlBar.placeholder === i18n$('newTab')) {
  5631. return;
  5632. }
  5633. /**
  5634. * @type { string }
  5635. */
  5636. const val = evt.target.value;
  5637. const section = qsA('mujs-section[data-name]', cfgpage);
  5638. if (isEmpty(val)) {
  5639. dom.cl.remove(container.toElem(), 'hidden');
  5640. dom.cl.remove(section, 'hidden');
  5641. return;
  5642. }
  5643. const finds = new Set();
  5644. if (!dom.cl.has(cfgpage, 'hidden')) {
  5645. const reg = new RegExp(val, 'gi');
  5646. for (const elem of section) {
  5647. if (!isElem(elem)) {
  5648. continue;
  5649. }
  5650. if (finds.has(elem)) {
  5651. continue;
  5652. }
  5653. if (elem.dataset.name.match(reg)) {
  5654. finds.add(elem);
  5655. }
  5656. }
  5657. dom.cl.add(section, 'hidden');
  5658. dom.cl.remove([...finds], 'hidden');
  5659. return;
  5660. }
  5661. const cacheValues = container.toArr().filter(({ _mujs }) => {
  5662. return !finds.has(_mujs.root);
  5663. });
  5664. /**
  5665. * @param {RegExpMatchArray} regExp
  5666. * @param {keyof import("../typings/types.d.ts").GSForkQuery} key
  5667. */
  5668. const ezQuery = (regExp, key) => {
  5669. const q_value = val.replace(regExp, '');
  5670. const reg = new RegExp(q_value, 'gi');
  5671. for (const v of cacheValues) {
  5672. let k = v[key];
  5673. if (typeof k === 'number') {
  5674. k = `${v[key]}`;
  5675. }
  5676. if (k && k.match(reg)) {
  5677. finds.add(v._mujs.root);
  5678. }
  5679. }
  5680. };
  5681. if (val.match(/^(code_url|url):/)) {
  5682. ezQuery(/^(code_url|url):/, 'code_url');
  5683. } else if (val.match(/^(author|users?):/)) {
  5684. const parts = /^[\w_]+:(.+)/.exec(val);
  5685. if (parts) {
  5686. const reg = new RegExp(parts[1], 'gi');
  5687. for (const v of cacheValues.filter((v) => !isEmpty(v.users))) {
  5688. for (const user of v.users) {
  5689. for (const value of Object.values(user)) {
  5690. if (typeof value === 'string' && value.match(reg)) {
  5691. finds.add(v._mujs.root);
  5692. } else if (typeof value === 'number' && `${value}`.match(reg)) {
  5693. finds.add(v._mujs.root);
  5694. }
  5695. }
  5696. }
  5697. }
  5698. }
  5699. } else if (val.match(/^(locale|i18n):/)) {
  5700. ezQuery(/^(locale|i18n):/, 'locale');
  5701. } else if (val.match(/^id:/)) {
  5702. ezQuery(/^id:/, 'id');
  5703. } else if (val.match(/^license:/)) {
  5704. ezQuery(/^license:/, 'license');
  5705. } else if (val.match(/^name:/)) {
  5706. ezQuery(/^name:/, 'name');
  5707. } else if (val.match(/^description:/)) {
  5708. ezQuery(/^description:/, 'description');
  5709. } else if (val.match(/^(search_engine|engine):/)) {
  5710. const parts = /^[\w_]+:(\w+)/.exec(val);
  5711. if (parts) {
  5712. const reg = new RegExp(parts[1], 'gi');
  5713. for (const { _mujs } of cacheValues) {
  5714. if (!_mujs.info.engine.name.match(reg)) {
  5715. continue;
  5716. }
  5717. finds.add(_mujs.root);
  5718. }
  5719. }
  5720. } else if (val.match(/^filter:/)) {
  5721. const parts = /^\w+:(.+)/.exec(val);
  5722. if (parts) {
  5723. const bsFilter = loadFilters();
  5724. const filterType = bsFilter.get(parts[1].trim().toLocaleLowerCase());
  5725. if (filterType) {
  5726. const { reg } = filterType;
  5727. for (const { name, users, _mujs } of cacheValues) {
  5728. if ([{ name }, ...users].find((o) => o.name.match(reg))) {
  5729. continue;
  5730. }
  5731. finds.add(_mujs.root);
  5732. }
  5733. }
  5734. }
  5735. } else if (val.match(/^recommend:/)) {
  5736. for (const { url, id, users, _mujs } of cacheValues) {
  5737. if (
  5738. users.find((u) => u.id === authorID) ||
  5739. goodUserJS.includes(url) ||
  5740. goodUserJS.includes(id)
  5741. ) {
  5742. finds.add(_mujs.root);
  5743. }
  5744. }
  5745. } else {
  5746. const reg = new RegExp(val, 'gi');
  5747. for (const v of cacheValues) {
  5748. if (v.name && v.name.match(reg)) finds.add(v._mujs.root);
  5749. if (v.description && v.description.match(reg)) finds.add(v._mujs.root);
  5750. if (v._mujs.code.data_meta) {
  5751. for (const key of Object.keys(v._mujs.code.data_meta)) {
  5752. if (/name|desc/i.test(key) && key.match(reg)) finds.add(v._mujs.root);
  5753. }
  5754. }
  5755. }
  5756. }
  5757. dom.cl.add(qsA('tr[data-engine]', tabbody), 'hidden');
  5758. dom.cl.remove([...finds], 'hidden');
  5759. });
  5760. ael(urlBar, 'change', (evt) => {
  5761. evt.preventDefault();
  5762. const val = evt.target.value;
  5763. const tabElem = tab.getActive();
  5764. if (urlBar.placeholder === i18n$('newTab') && tabElem) {
  5765. const tabHost = tabElem.firstElementChild;
  5766. if (tab.protoReg.test(val)) {
  5767. const createdTab = tab.getTab(val);
  5768. tab.close(tabElem);
  5769. if (createdTab) {
  5770. tab.active(createdTab);
  5771. } else {
  5772. tab.create(val);
  5773. }
  5774. evt.target.placeholder = i18n$('search_placeholder');
  5775. evt.target.value = '';
  5776. return;
  5777. } else if (val === '*') {
  5778. tabElem.dataset.host = val;
  5779. tabHost.title = '<All Sites>';
  5780. tabHost.textContent = '<All Sites>';
  5781. MUList.host = val;
  5782. respHandles.build();
  5783. return;
  5784. }
  5785. const value = container.getHost(val);
  5786. if (container.checkBlacklist(value)) {
  5787. showError(`Blacklisted "${value}"`);
  5788. return;
  5789. }
  5790. tabElem.dataset.host = value;
  5791. tabHost.title = value;
  5792. tabHost.textContent = value;
  5793. MUList.host = value;
  5794. respHandles.build();
  5795. }
  5796. });
  5797. scheduler.postTask(makecfg, { priority: 'background' });
  5798.  
  5799. respHandles.build = async () => {
  5800. const time = await scheduler.postTask(MUList.build, { priority: 'background' });
  5801. return timeoutFrame(time);
  5802. };
  5803.  
  5804. if (cfg.autofetch) {
  5805. respHandles.build();
  5806. }
  5807. dbg('Container', container);
  5808. } catch (ex) {
  5809. err(ex);
  5810. container.remove();
  5811. }
  5812. return respHandles;
  5813. }
  5814. // #endregion
  5815. /**
  5816. * @template { Function } F
  5817. * @param { (this: F, doc: Document) => * } onDomReady
  5818. */
  5819. const loadDOM = (onDomReady) => {
  5820. if (isFN(onDomReady)) {
  5821. if (document.readyState === 'interactive' || document.readyState === 'complete') {
  5822. onDomReady(document);
  5823. } else {
  5824. document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), {
  5825. once: true
  5826. });
  5827. }
  5828. }
  5829. };
  5830.  
  5831. const init = async (prefix = 'Config') => {
  5832. const stored = await StorageSystem.getValue(prefix, DEFAULT_CONFIG);
  5833. cfg = {
  5834. ...DEFAULT_CONFIG,
  5835. ...stored
  5836. };
  5837. info('Config:', cfg);
  5838. loadDOM((doc) => {
  5839. try {
  5840. if (window.location === null) {
  5841. throw new Error('"window.location" is null, reload the webpage or use a different one', {
  5842. cause: 'loadDOM'
  5843. });
  5844. }
  5845. if (doc === null) {
  5846. throw new Error('"doc" is null, reload the webpage or use a different one', {
  5847. cause: 'loadDOM'
  5848. });
  5849. }
  5850. container.redirect();
  5851.  
  5852. if (cfg.autoinject) container.inject(primaryFN, doc);
  5853.  
  5854. Command.register(i18n$('userjs_inject'), () => {
  5855. container.inject(primaryFN, doc);
  5856. });
  5857. Command.register(i18n$('userjs_close'), () => {
  5858. container.remove();
  5859. });
  5860. } catch (ex) {
  5861. err(ex);
  5862. }
  5863. });
  5864. };
  5865. init();
  5866.  
  5867. })();