Greasy Fork is available in English.

Magic Userscript+ : Show Site All UserJS

Finds available userscripts for the current webpage.

Ajankohdalta 5.3.2025. Katso uusin versio.

  1. // ==UserScript==
  2. // @version 7.6.0
  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 greasyfork.org
  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://greasyfork.org/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://greasyfork.org/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;
  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://greasyfork.org/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 greasyfork.org to sleazyfork.org
  3438. *
  3439. * Taken from: https://greasyfork.org/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. })();