Magic Userscript+ :显示站点所有 UserJS

为当前网页查找可用的用户脚本。

当前为 2025-03-22 提交的版本,查看 最新版本

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