Magic Userscript+ :显示站点所有 UserJS

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址