Greasy Fork镜像++

添加各种功能并改善 Greasy Fork镜像 体验

目前为 2025-02-08 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Greasy Fork镜像++
  3. // @namespace https://github.com/iFelix18
  4. // @version 3.2.57
  5. // @author CY Fung <https://gf.qytechs.cn/users/371179> & Davide <iFelix18@protonmail.com>
  6. // @icon https://www.google.com/s2/favicons?domain=https://gf.qytechs.cn
  7. // @description Adds various features and improves the Greasy Fork镜像 experience
  8. // @description:de Fügt verschiedene Funktionen hinzu und verbessert das Greasy Fork镜像-Erlebnis
  9. // @description:es Agrega varias funciones y mejora la experiencia de Greasy Fork镜像
  10. // @description:fr Ajoute diverses fonctionnalités et améliore l'expérience Greasy Fork镜像
  11. // @description:it Aggiunge varie funzionalità e migliora l'esperienza di Greasy Fork镜像
  12. // @description:ru Добавляет различные функции и улучшает работу с Greasy Fork镜像
  13. // @description:zh-CN 添加各种功能并改善 Greasy Fork镜像 体验
  14. // @description:zh-TW 加入多種功能並改善Greasy Fork镜像的體驗
  15. // @description:ja Greasy Fork镜像の体験を向上させる様々な機能を追加
  16. // @description:ko Greasy Fork镜像 경험을 향상시키고 다양한 기능을 추가
  17. // @copyright 2023, CY Fung (https://gf.qytechs.cn/users/371179); 2021, Davide (https://github.com/iFelix18)
  18. // @license MIT
  19. // @require https://fastly.jsdelivr.net/gh/sizzlemctwizzle/GM_config@06f2015c04db3aaab9717298394ca4f025802873/gm_config.min.js
  20. // @require https://fastly.jsdelivr.net/npm/@violentmonkey/shortcut@1.4.1/dist/index.min.js
  21. // @require https://fastly.jsdelivr.net/gh/cyfung1031/userscript-supports@3fa07109efca28a21094488431363862ccd52d7c/library/WinComm.min.js
  22. // @match *://gf.qytechs.cn/*
  23. // @match *://sleazyfork.org/*
  24. // @connect gf.qytechs.cn
  25. // @compatible chrome
  26. // @compatible edge
  27. // @compatible firefox
  28. // @compatible safari
  29. // @compatible brave
  30. // @grant GM.deleteValue
  31. // @grant GM.getValue
  32. // @grant GM.notification
  33. // @grant GM.registerMenuCommand
  34. // @grant GM.setValue
  35. // @grant unsafeWindow
  36. // @run-at document-start
  37. // @inject-into content
  38. // ==/UserScript==
  39.  
  40. /* global GM_config, VM, GM, WinComm */
  41.  
  42. /**
  43. * @typedef { typeof import("./library/WinComm.js") } WinComm
  44. */
  45.  
  46. // console.log(GM)
  47.  
  48. /** @type {WinComm} */
  49. const WinComm = this.WinComm;
  50.  
  51. // -------- UU Fucntion - original code: https://fastly.jsdelivr.net/npm/@ifelix18/utils@6.5.0/lib/index.min.js --------
  52. // optimized by CY Fung to remove $ dependency and observe creation
  53. const UU = (function () {
  54. const scriptName = GM.info.script.name; // not name_i18n
  55. const scriptVersion = GM.info.script.version;
  56. const authorMatch = /^(.*?)\s<\S[^\s@]*@\S[^\s.]*\.\S+>$/.exec(GM.info.script.author);
  57. const author = authorMatch ? authorMatch[1] : GM.info.script.author;
  58. let scriptId = scriptName.toLowerCase().replace(/\s/g, "-");
  59. let loggingEnabled = false;
  60.  
  61. const log = (message) => {
  62. if (loggingEnabled) {
  63. console.log(`${scriptName}:`, message);
  64. }
  65. };
  66.  
  67. const error = (message) => {
  68. console.error(`${scriptName}:`, message);
  69. };
  70.  
  71. const warn = (message) => {
  72. console.warn(`${scriptName}:`, message);
  73. };
  74.  
  75. const alert = (message) => {
  76. window.alert(`${scriptName}: ${message}`);
  77. };
  78.  
  79. /** @param {string} text */
  80. const short = (text, length) => {
  81. const s = text.split(" ");
  82. const l = Number(length);
  83. return s.length > l
  84. ? `${s.slice(0, l).join(" ")} [...]`
  85. : text;
  86. };
  87.  
  88. const addStyle = (css) => {
  89. const head = document.head || document.querySelector("head");
  90. const style = document.createElement("style");
  91. style.textContent = css;
  92. head.appendChild(style);
  93. };
  94.  
  95. const init = async (options = {}) => {
  96. scriptId = options.id || scriptId;
  97. loggingEnabled = typeof options.logging === "boolean" ? options.logging : false;
  98. console.info(
  99. `%c${scriptName}\n%cv${scriptVersion}${author ? ` by ${author}` : ""} is running!`,
  100. "color:red;font-weight:700;font-size:18px;text-transform:uppercase",
  101. ""
  102. );
  103. };
  104.  
  105. return {
  106. init,
  107. log,
  108. error,
  109. warn,
  110. alert,
  111. short,
  112. addStyle
  113. };
  114. })();
  115.  
  116. // -------- UU Fucntion - original code: https://fastly.jsdelivr.net/npm/@ifelix18/utils@6.5.0/lib/index.min.js --------
  117.  
  118.  
  119. const mWindow = (() => {
  120.  
  121.  
  122. const fields = {
  123. hideBlacklistedScripts: {
  124. label: 'Hide blacklisted scripts:<br><span>Choose which lists to activate in the section below, press <b>Ctrl + Alt + B</b> to show Blacklisted scripts</span>',
  125. section: ['Features'],
  126. labelPos: 'right',
  127. type: 'checkbox',
  128. default: true
  129. },
  130. hideHiddenScript: {
  131. label: 'Hide scripts:<br><span>Add a button to hide the script<br>See and edit the list of hidden scripts below, press <b>Ctrl + Alt + H</b> to show Hidden script',
  132. labelPos: 'right',
  133. type: 'checkbox',
  134. default: true
  135. },
  136. showInstallButton: {
  137. label: 'Install button:<br><span>Add to the scripts list a button to install the script directly</span>',
  138. labelPos: 'right',
  139. type: 'checkbox',
  140. default: true
  141. },
  142. showTotalInstalls: {
  143. label: 'Installations:<br><span>Shows the number of daily and total installations on the user profile</span>',
  144. labelPos: 'right',
  145. type: 'checkbox',
  146. default: true
  147. },
  148. milestoneNotification: {
  149. label: 'Milestone notifications:<br><span>Get notified whenever your total installs got over any of these milestone<br>Separate milestones with a comma, leave blank to turn off notifications</span>',
  150. labelPos: 'left',
  151. type: 'text',
  152. title: 'Separate milestones with a comma!',
  153. size: 150,
  154. default: '10, 100, 500, 1000, 2500, 5000, 10000, 100000, 1000000'
  155. },
  156. nonLatins: {
  157. label: 'Non-Latin:<br><span>This list blocks all scripts with non-Latin characters in the title/description</span>',
  158. section: ['Lists'],
  159. labelPos: 'right',
  160. type: 'checkbox',
  161. default: false // not true
  162. },
  163. blacklist: {
  164. label: 'Blacklist:<br><span>A "non-opinionable" list that blocks all scripts with specific words in the title/description, references to "bots", "cheats" and some online game sites, and other "bullshit"</span>',
  165. labelPos: 'right',
  166. type: 'checkbox',
  167. default: true
  168. },
  169. customBlacklist: {
  170. label: 'Custom Blacklist:<br><span>Personal blacklist defined by a set of unwanted words<br>Separate unwanted words with a comma (example: YouTube, Facebook, pizza), leave blank to disable this list</span>',
  171. labelPos: 'left',
  172. type: 'text',
  173. title: 'Separate unwanted words with a comma!',
  174. size: 150,
  175. default: ''
  176. },
  177. hiddenList: {
  178. label: 'Hidden Scripts:<br><span>Block individual undesired scripts by their unique IDs<br>Separate IDs with a comma</span>',
  179. labelPos: 'left',
  180. type: 'textarea',
  181. title: 'Separate IDs with a comma!',
  182. default: '',
  183. save: false
  184. },
  185. logging: {
  186. label: 'Logging',
  187. section: ['Developer options'],
  188. labelPos: 'right',
  189. type: 'checkbox',
  190. default: false
  191. },
  192. debugging: {
  193. label: 'Debugging',
  194. labelPos: 'right',
  195. type: 'checkbox',
  196. default: false
  197. }
  198. }
  199.  
  200. const logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAASFBMVEVHcEwBAQEDAwMAAAACAgIBAQEAAAAREREDAwMBAQH///8WFhYuLi7U1NSdnZ1bW1vExMTq6uqtra309PRERETf399ycnKGhoaVOQEOAAAACnRSTlMAg87/rjLgE1rzhWrqxgAABexJREFUaN61WouSpCAMVPEJKCqi//+nF4IKKig6e1SduzfupEkT8oIkiRlVVdRpnmdlQ0hTZnme1kVV4Zvk96Fla8nH0ZSI8rP0Ks2uwi1Ilv4EURW5K5xS0slhMb/BkD0hrMk/q1HVeSP6QVILMFIY8wagn6ojTV5Xn8RnbFZaoAPQc9bR3gXQ/yaWvYYA8VfKKeXACZVnAE1V9o4on/izWPsb/q9Ji3j5OcrjhiCXohsAQso6lh6QL9qOEd6GAAbKYAInAFAiiqYC5LMeLIaFKeppR3h/BiAkj6CpLuEPmbbHngUBhFZsdAGiaUL5xLBzRrAAZBlk5wpnVJEohHTbuZoAD0uhMUu+uY/bLZHaryBCH4vQCuugbnSoYf5sk+llKWaEETT/Qu2TecmSHaF1KPT6gmkM4hNLLkIR2l/guAZK1fQrS3kVXmChEX5mKb0xICH/gKXrQtf2pbhlyfqFoL/1LUOVEbFwcsuSs5GfAcjJ8dVkknbafpYUfUXSQYWqRP81THcs8fbVMmTVaQU6ENNOdyxNgGRYmFsp2/mQaFiKzGeC1IcVmAjrDjq4LAF9RgdF13CAo3cTDRcAP2OOCjX6UAwCPpbWyGZsCWTMAM0YTGF2Eg0XAD8bramue9jocGVpi5y7LbUUVRO0dRINF2D9bN/PBSqgAizt8gHByJAUddEyTqa7rYF57oZkkgiYj48lYeVTuuh4Hw1A8pWhxr68snQYioOxHSm6A2gq1wuZz68suUMKELst8oCLfAew+rzMecmOLO251wYwa4CDmd4B8GyPM1YDlyXeUp8Gx412A9Chy6vP9cXO0kW+5e6N104vH68sXeW/jwzptss8OihFf1UAY2dVkgDCdQz8dfiv1m3sZek62rcIsJlr/5uADv1bhNqzxrcIb3VIkzz06m9YykMAM39kidIoAG+5R7icHlm6BViUVDqSZknpfd8NZh2MO1Xz+JKlcYsfZeK3UqjBTDRexn680PVoSxMFBiCST6RJJmXzg2FTegaPzyRWRWu9cERAHW4o6jANmPU0Ewwqe36wa8j1wyQLADHyk1FphM760H1sBY/+PtS5ECQTvucHynoapYPiZJKFDoSNnFxZYl0QYG2gQExtcJFN8LNl1voHOA++5yQelh5yVPhRopma8M3OALMO8p0GhgDT+lgKDatBhhvN5gcuRWaZJeQ8CzVBLmBLd2tgdrLND9xFxh9CW8JABYRSNQVYugJYK8rB2bn5gWOmaM4dzmXQVjvuidMzS3YfpEm9uPnBtp5yNFRJLRUTb9OaiN1x+06uk0q4+cG+U+SqCeoKLmMwrYkp1pYWRbUvgoDjDZng7EScG3/wSxAyK7+/Xvrgl974JZ1gp69r1Bc7LvUlXhEIsSxh4lWU5Ecdwixh6lhlhPwvlkyZlpIvCFEspW4B8h9YWguQYOZynzZEsJTvRWBPxwDABnKuXWJY2ovAKu8H9h7gkSXblqqFIB8AHlhyekbGUk2PYUbXtvgAXGnYjfWwNA+QcDHN3+x2Q2rngENgiSeeAUZfjDMVHkSn1m2GGBVwCh0d8NlfhJ4owiyE+VjiPV0WKQ7tHCxD1h6DeQ7PAMKWvUcERtt2PDakkio9f/1pkdcsxMOSLq7ldD5LAJf3BeCaCfQmDl57s/Xak4sHEJiPjOcdN4f61+n8CDDQaX/iIk8KcrOTDqCC4Km3tdw9AeBM1+dq1IqRE0stI8LbWk6K7AmAjYPeX/jEdF/qJtgpX+pDzfH9eCVunFyt1UEQUt8dUHwE2BE6b2f8A8I1WMxqGLQfyqu7I8zmOwBh08TJrfy36+ANw1XcQdrHEXOeWeTf5edRJ7JV+t/o+UKTc+hRxx8oF+lLaxKCvTmw1vcRshcAbGFZ8eFUv4kF4NnHewn5pM91sauv7z9gumDPPNgoobBq54/XHraLGyAZXPLqaFrnzIMpKoeR/3BxY7t6woWY2hYqZZ0u2DOPeZzZr1dP7OUZbk4MVE+wecrmqcn+5vLMevsneP3ncfwDNtu0vRpuz80AAAAASUVORK5CYII='
  201.  
  202. const locales = { /* cSpell: disable */
  203. de: {
  204. downgrade: 'Auf zurückstufen',
  205. hide: '❌ Dieses skript ausblenden',
  206. install: 'Installieren',
  207. notHide: '✔️ Dieses skript nicht ausblenden',
  208. milestone: 'Herzlichen Glückwunsch, Ihre Skripte haben den Meilenstein von insgesamt $1 Installationen überschritten!',
  209. reinstall: 'Erneut installieren',
  210. update: 'Auf aktualisieren'
  211. },
  212. en: {
  213. downgrade: 'Downgrade to',
  214. hide: '❌ Hide this script',
  215. install: 'Install',
  216. notHide: '✔️ Not hide this script',
  217. milestone: 'Congrats, your scripts got over the milestone of $1 total installs!',
  218. reinstall: 'Reinstall',
  219. update: 'Update to'
  220. },
  221. es: {
  222. downgrade: 'Degradar a',
  223. hide: '❌ Ocultar este script',
  224. install: 'Instalar',
  225. notHide: '✔️ No ocultar este script',
  226. milestone: '¡Felicidades, sus scripts superaron el hito de $1 instalaciones totales!',
  227. reinstall: 'Reinstalar',
  228. update: 'Actualizar a'
  229. },
  230. fr: {
  231. downgrade: 'Revenir à',
  232. hide: '❌ Cacher ce script',
  233. install: 'Installer',
  234. notHide: '✔️ Ne pas cacher ce script',
  235. milestone: 'Félicitations, vos scripts ont franchi le cap des $1 installations au total!',
  236. reinstall: 'Réinstaller',
  237. update: 'Mettre à'
  238. },
  239. it: {
  240. downgrade: 'Riporta a',
  241. hide: '❌ Nascondi questo script',
  242. install: 'Installa',
  243. notHide: '✔️ Non nascondere questo script',
  244. milestone: 'Congratulazioni, i tuoi script hanno superato il traguardo di $1 installazioni totali!',
  245. reinstall: 'Reinstalla',
  246. update: 'Aggiorna a'
  247. },
  248. ru: {
  249. downgrade: 'Откатить до',
  250. hide: '❌ Скрыть этот скрипт',
  251. install: 'Установить',
  252. notHide: '✔️ Не скрывать этот сценарий',
  253. milestone: 'Поздравляем, ваши скрипты преодолели рубеж в $1 установок!',
  254. reinstall: 'Переустановить',
  255. update: 'Обновить до'
  256. },
  257. 'zh-CN': {
  258. downgrade: '降级到',
  259. hide: '❌ 隐藏此脚本',
  260. install: '安装',
  261. notHide: '✔️ 不隐藏此脚本',
  262. milestone: '恭喜,您的脚本超过了 $1 次总安装的里程碑!',
  263. reinstall: '重新安装',
  264. update: '更新到'
  265. },
  266. 'zh-TW': {
  267. downgrade: '降級至',
  268. hide: '❌ 隱藏此腳本',
  269. install: '安裝',
  270. notHide: '✔️ 不隱藏此腳本',
  271. milestone: '恭喜,您的腳本安裝總數已超過 $1!',
  272. reinstall: '重新安裝',
  273. update: '更新至'
  274. },
  275. 'ja': {
  276. downgrade: 'ダウングレードする',
  277. hide: '❌ このスクリプトを隠す',
  278. install: 'インストール',
  279. notHide: '✔️ このスクリプトを隠さない',
  280. milestone: 'おめでとうございます、あなたのスクリプトの合計インストール回数が $1 を超えました!',
  281. reinstall: '再インストール',
  282. update: '更新する'
  283. },
  284. 'ko': {
  285. downgrade: '다운그레이드하기',
  286. hide: '❌ 이 스크립트 숨기기',
  287. install: '설치',
  288. notHide: '✔️ 이 스크립트 숨기지 않기',
  289. milestone: '축하합니다, 스크립트의 총 설치 횟수가 $1을 넘었습니다!',
  290. reinstall: '재설치',
  291. update: '업데이트하기'
  292. }
  293.  
  294. };
  295.  
  296. const blacklist = [
  297. '\\bagar((\\.)?io)?\\b', '\\bagma((\\.)?io)?\\b', '\\baimbot\\b', '\\barras((\\.)?io)?\\b', '\\bbot(s)?\\b',
  298. '\\bbubble((\\.)?am)?\\b', '\\bcheat(s)?\\b', '\\bdiep((\\.)?io)?\\b', '\\bfreebitco((\\.)?in)?\\b', '\\bgota((\\.)?io)?\\b',
  299. '\\bhack(s)?\\b', '\\bkrunker((\\.)?io)?\\b', '\\blostworld((\\.)?io)?\\b', '\\bmoomoo((\\.)?io)?\\b', '\\broblox(\\.com)?\\b',
  300. '\\bshell\\sshockers\\b', '\\bshellshock((\\.)?io)?\\b', '\\bshellshockers\\b', '\\bskribbl((\\.)?io)?\\b', '\\bslither((\\.)?io)?\\b',
  301. '\\bsurviv((\\.)?io)?\\b', '\\btaming((\\.)?io)?\\b', '\\bvenge((\\.)?io)?\\b', '\\bvertix((\\.)?io)?\\b', '\\bzombs((\\.)?io)?\\b',
  302. // '\\p{Extended_Pictographic}'
  303. ];
  304.  
  305.  
  306. const settingsCSS = `
  307.  
  308. /*
  309. #greasyfork-plus label::before {
  310. content:'';
  311. display:block;
  312. position:absolute;
  313. left:0;
  314. right:0;
  315. top:0;
  316. bottom:0;
  317. z-index:1;
  318. }
  319. #greasyfork-plus label {
  320. position:relative;
  321. z-index:0;
  322. }
  323. */
  324.  
  325. html {
  326. color: #222;
  327. background: #f9f9f9;
  328. }
  329.  
  330. #greasyfork-plus{
  331. --config-var-display: flex;
  332. }
  333. #greasyfork-plus * {
  334. font-family:Open Sans,sans-serif,Segoe UI Emoji !important;
  335. font-size:12px
  336. }
  337. #greasyfork-plus .section_header[class] {
  338. background-color:#670000;
  339. background-image:linear-gradient(#670000,#900);
  340. border:1px solid transparent;
  341. color:#fff
  342. }
  343. #greasyfork-plus .field_label[class]{
  344. margin-bottom:4px
  345. }
  346. #greasyfork-plus .field_label[class] span{
  347. font-size:95%;
  348. font-style:italic;
  349. opacity:.8;
  350. }
  351. #greasyfork-plus .field_label[class] b{
  352. color:#670000
  353. }
  354. #greasyfork-plus_logging_var[class],
  355. #greasyfork-plus_debugging_var[class] {
  356. --config-var-display: inline-flex;
  357. }
  358. #greasyfork-plus #greasyfork-plus_logging_var label.field_label[class],
  359. #greasyfork-plus #greasyfork-plus_debugging_var label.field_label[class] {
  360. margin-bottom:0;
  361. align-self: center;
  362. }
  363. #greasyfork-plus .config_var[class]{
  364. display:var(--config-var-display);
  365. position: relative;
  366. }
  367. #greasyfork-plus_customBlacklist_var[class],
  368. #greasyfork-plus_hiddenList_var[class],
  369. #greasyfork-plus_milestoneNotification_var[class]{
  370. flex-direction:column;
  371. margin-left:21px;
  372. }
  373.  
  374. #greasyfork-plus_customBlacklist_var[class]::before,
  375. #greasyfork-plus_hiddenList_var[class]::before,
  376. #greasyfork-plus_milestoneNotification_var[class]::before{
  377. /* content: "◉"; */
  378. content: "◎";
  379. position: absolute;
  380. left: auto;
  381. top: auto;
  382. margin-left: -16px;
  383. }
  384. #greasyfork-plus_field_customBlacklist[class],
  385. #greasyfork-plus_field_milestoneNotification[class]{
  386. flex:1;
  387. }
  388. #greasyfork-plus_field_hiddenList[class]{
  389. box-sizing:border-box;
  390. overflow:hidden;
  391. resize:none;
  392. width:100%
  393. }
  394.  
  395. body > #greasyfork-plus_wrapper:only-child {
  396. box-sizing: border-box;
  397. overflow: auto;
  398. max-height: calc(100vh - 72px);
  399. padding: 12px;
  400. /* overflow: auto; */
  401. scrollbar-gutter: both-edges;
  402. background: rgba(127,127,127,0.05);
  403. border: 1px solid rgba(127,127,127,0.5);
  404. }
  405.  
  406. #greasyfork-plus_wrapper > #greasyfork-plus_buttons_holder:last-child {
  407. position: fixed;
  408. bottom: 0;
  409. right: 0;
  410. margin: 0 12px 6px 0;
  411. }
  412.  
  413. #greasyfork-plus .saveclose_buttons[class] {
  414. padding: 4px 14px;
  415. margin: 6px;
  416. }
  417. #greasyfork-plus .section_header_holder#greasyfork-plus_section_2[class] {
  418. position: fixed;
  419. left: 0;
  420. bottom: 0;
  421. margin: 8px;
  422. }
  423. #greasyfork-plus .section_header#greasyfork-plus_section_header_2[class] {
  424. background: #000;
  425. color: #eee;
  426. }
  427.  
  428. #greasyfork-plus_header[class]{
  429. font-size: 16pt;
  430. font-weight: bold;
  431. }
  432.  
  433. `;
  434.  
  435. const pageCSS = `
  436.  
  437. .script-list li.blacklisted{
  438. display:none;
  439. background:#321919;
  440. color:#e8e6e3
  441. }
  442. .script-list li.hidden{
  443. display:none;
  444. background:#321932;
  445. color:#e8e6e3
  446. }
  447. .script-list li.blacklisted a:not(.install-link),.script-list li.hidden a:not(.install-link){
  448. color:#ff8484
  449. }
  450. #script-info.hidden,#script-info.hidden .user-content{
  451. background:#321932;
  452. color:#e8e6e3
  453. }
  454. #script-info.hidden a:not(.install-link):not(.install-help-link){
  455. color:#ff8484
  456. }
  457. #script-info.hidden code{
  458. background-color:transparent
  459. }
  460. html {
  461. --block-btn-color:#111;
  462. --block-btn-bgcolor:#eee;
  463. }
  464. #script-info.hidden, #script-info.hidden .user-content {
  465. --block-btn-color:#eee;
  466. --block-btn-bgcolor:#111;
  467. }
  468.  
  469. [style-54998]{
  470. float:right;
  471. font-size: 70%;
  472. text-decoration:none;
  473. }
  474.  
  475. [style-16377]{
  476. cursor:pointer;
  477. font-size:70%;
  478. white-space:nowrap;
  479. border: 1px solid #888;
  480. background: var(--block-btn-bgcolor, #eee);
  481. color: var(--block-btn-color);
  482. border-radius: 4px;
  483. padding: 0px 6px;
  484. margin: 0 8px;
  485. }
  486. [style-77329] {
  487. cursor: pointer;
  488. margin-left: 1ex;
  489. white-space: nowrap;
  490. float: right;
  491. border: 1px solid #888;
  492. background: var(--block-btn-bgcolor, #eee);
  493. color: var(--block-btn-color);
  494. border-radius: 4px;
  495. padding: 0px 6px;
  496. }
  497.  
  498. a#hyperlink-35389,
  499. a#hyperlink-40361,
  500. a#hyperlink-35389:visited,
  501. a#hyperlink-40361:visited,
  502. a#hyperlink-35389:hover,
  503. a#hyperlink-40361:hover,
  504. a#hyperlink-35389:focus,
  505. a#hyperlink-40361:focus,
  506. a#hyperlink-35389:active,
  507. a#hyperlink-40361:active {
  508.  
  509. border: none !important;
  510. outline: none !important;
  511. box-shadow: none !important;
  512. appearance: none !important;
  513. background: none !important;
  514. color:inherit !important;
  515. }
  516.  
  517. a#hyperlink-35389{
  518. opacity: var(--hyperlink-blacklisted-option-opacity);
  519.  
  520. }
  521. a#hyperlink-40361{
  522. opacity: var(--hyperlink-hidden-option-opacity);
  523. }
  524.  
  525.  
  526. html {
  527.  
  528. --hyperlink-blacklisted-option-opacity: 0.5;
  529. --hyperlink-hidden-option-opacity: 0.5;
  530. }
  531.  
  532.  
  533. .list-option.list-current[class] > a[href] {
  534.  
  535. text-decoration:none;
  536. }
  537.  
  538. html {
  539. --blacklisted-display: none;
  540. --hidden-display: none;
  541. }
  542.  
  543. [blacklisted-shown] {
  544. --blacklisted-display: list-item;
  545. --hyperlink-blacklisted-option-opacity: 1;
  546. }
  547. [hidden-shown] {
  548. --hidden-display: list-item;
  549. --hyperlink-hidden-option-opacity: 1;
  550. }
  551.  
  552. .script-list li.blacklisted{
  553. display: var(--blacklisted-display);
  554.  
  555. }
  556.  
  557. .script-list li.hidden{
  558. display: var(--hidden-display);
  559.  
  560. }
  561.  
  562. .install-link.install-status-checking,
  563. .install-link.install-status-checking:visited,
  564. .install-link.install-status-checking:active,
  565. .install-link.install-status-checking:hover,
  566. .install-help-link.install-status-checking {
  567. background-color: #405458;
  568. }
  569.  
  570. div.previewable{
  571. display: flex;
  572. flex-direction: column;
  573. }
  574. .script-version-ainfo-span {
  575. align-self:end;
  576. font-size: 90%;
  577. padding: 4px 8px;
  578. margin: 0;
  579. }
  580. [style*="display:"] + .script-version-ainfo-span{
  581. display: none;
  582. }
  583.  
  584.  
  585. /* Greasy Fork镜像 Enhance - Flat Layout */
  586.  
  587. [greasyfork-enhance-k37*="|flat-layout|"] ol.script-list > li > article > h2 {
  588. width: 0;
  589. flex-grow: 1;
  590. flex-basis: 60%;
  591. }
  592.  
  593. [greasyfork-enhance-k37*="|flat-layout|"] ol.script-list > li > article > div.script-meta-block {
  594. width: auto;
  595. flex-basis: 40%;
  596. flex-shrink: 0;
  597. flex-grow: 0;
  598. }
  599.  
  600. [greasyfork-enhance-k37*="|flat-layout|"] .script-list li:not(.ad-entry) {
  601. padding: 1em;
  602. margin: 0;
  603. }
  604.  
  605. [greasyfork-enhance-k37*="|flat-layout|"] .script-list li:not(.ad-entry) article {
  606. padding: 0;
  607. margin: 0;
  608. }
  609.  
  610. [greasyfork-enhance-k37*="|flat-layout|"] #script-info div.script-meta-block + #additional-info {
  611.  
  612. max-width: calc( 100% - 340px );
  613. min-height: 300px;
  614. box-sizing: border-box;
  615. }
  616.  
  617. [greasyfork-enhance-k37*="|basic|"] ul.outline {
  618. margin-bottom: -99vh;
  619.  
  620. }
  621.  
  622. .discussion-list .hidden {
  623. display: none;
  624. }
  625.  
  626. /* Greasy Fork镜像 Empty Ad Block */
  627. .ethical-ads-text[class]:empty {
  628. min-height: unset;
  629. }
  630.  
  631.  
  632. `
  633.  
  634. const window = {};
  635.  
  636. /** @param {typeof WinComm.createInstance} createInstance */
  637. function contentScriptText(shObject, createInstance) {
  638.  
  639. // avoid setupEthicalAdsFallback looping
  640. if (typeof window.ethicalads === "undefined") {
  641. const p = Promise.resolve([]);
  642. window.ethicalads = { wait: p };
  643. }
  644.  
  645. /*
  646. *
  647.  
  648. return new Promise((resolve, reject) => {
  649. const external = unsafeWindow.external;
  650. console.log(334, external)
  651. const scriptHandler = GM.info.scriptHandler;
  652. if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey' ) {
  653. external.Violentmonkey.isInstalled(name, namespace).then((data) => resolve(data));
  654. return;
  655. }
  656.  
  657. if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') {
  658. external.Tampermonkey.isInstalled(name, namespace, (data) => {
  659. (data.installed) ? resolve(data.version) : resolve();
  660. });
  661. return;
  662. }
  663.  
  664. resolve();
  665. });
  666.  
  667. */
  668.  
  669. if (document.querySelector('#greasyfork-enhance-basic')) {
  670.  
  671.  
  672.  
  673. const setScriptOnDisabled = async (style) => {
  674.  
  675. try {
  676. const pd = Object.getOwnPropertyDescriptor(style.constructor.prototype, 'disabled');
  677. const { get, set } = pd;
  678. Object.defineProperty(style, 'disabled', {
  679. get() {
  680. return get.call(this);
  681. },
  682. set(nv) {
  683. let r = set.call(this, nv);
  684. Promise.resolve().then(chHead);
  685. return r;
  686. }
  687. })
  688. } catch (e) {
  689.  
  690. }
  691. };
  692.  
  693. document.addEventListener('style-s48', function (evt) {
  694. const target = (evt || 0).target || 0;
  695. if (!target) return;
  696. setScriptOnDisabled(target)
  697.  
  698. }, true);
  699.  
  700.  
  701. const isScriptEnabled = (style) => {
  702.  
  703. if (style instanceof HTMLStyleElement) {
  704. if (!style.hasAttribute('s48')) {
  705. style.setAttribute('s48', '');
  706. style.dispatchEvent(new CustomEvent('style-s48'));
  707. // setScriptOnDisabled(style);
  708. }
  709. return style.disabled !== true;
  710. }
  711. return false;
  712. }
  713. const chHead = () => {
  714. let p = [];
  715. if (isScriptEnabled(document.getElementById('greasyfork-enhance-basic')))
  716. p.push('basic');
  717. if (isScriptEnabled(document.getElementById('greasyfork-enhance-flat-layout')))
  718. p.push('flat-layout');
  719. if (isScriptEnabled(document.getElementById('greasyfork-enhance-animation')))
  720. p.push('animation');
  721. if (p.length >= 1)
  722. document.documentElement.setAttribute('greasyfork-enhance-k37', `|${p.join('|')}|`);
  723. else
  724. document.documentElement.removeAttribute('greasyfork-enhance-k37');
  725. }
  726. const moHead = new MutationObserver(chHead);
  727. moHead.observe(document.head, { subtree: false, childList: true });
  728. chHead();
  729.  
  730. /*
  731. const outline = document.querySelector('aside.panel > ul.outline');
  732. if(outline) {
  733. const div = document.createElement('div');
  734. //outline.replaceWith(div);
  735. //div.appendChild(outline)
  736. }
  737. */
  738.  
  739. // Promise.resolve().then(()=>{
  740. // let outline = document.querySelector('[greasyfork-enhance-k37*="|basic|"] header + aside.panel ul.outline');
  741. // if(outline){
  742. // let aside = outline.closest('aside.panel');
  743. // let header = aside.parentNode.querySelector('header');
  744. // let p = header.getBoundingClientRect().height;
  745.  
  746. // document.body.parentNode.insertBefore(aside, document.body);
  747. // // outline.style.top='0'
  748. // p+=(parseFloat(getComputedStyle(outline).marginTop.replace('px',''))||0)
  749. // outline.style.marginTop= p.toFixed(2)+'px';
  750. // }
  751.  
  752. // })
  753.  
  754. }
  755.  
  756.  
  757.  
  758. const { scriptHandler, scriptName, scriptVersion, scriptNamespace, communicationId } = shObject;
  759.  
  760. const wincomm = createInstance(communicationId);
  761.  
  762. const external = window.external;
  763.  
  764. if (external[scriptHandler]) 1;
  765. else if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey') scriptHandler = 'Violentmonkey';
  766. else if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') scriptHandler = 'Tampermonkey';
  767.  
  768. const manager = external[scriptHandler];
  769.  
  770. if (!manager) {
  771.  
  772. wincomm.send('userScriptManagerNotDetected', {
  773. code: 1
  774. });
  775. return;
  776.  
  777. }
  778.  
  779. const promiseWrap = (x) => {
  780. // bug in FireFox + Violentmonkey
  781. if (typeof (x || 0) === 'object' && typeof x.then === 'function') return x; else return Promise.resolve(x);
  782. };
  783.  
  784.  
  785. const pnIsInstalled2 = (type, scriptName, scriptNamespace) => new Promise((resolve, reject) => {
  786. const resultPr = promiseWrap(manager.isInstalled(scriptName, scriptNamespace));
  787. resultPr.then((result) => resolve({
  788. type,
  789. result: typeof result === 'string' ? { version: result } : result
  790. })).catch(reject);
  791. }).catch(console.warn);
  792.  
  793.  
  794. const pnIsInstalled3 = (type, scriptName, scriptNamespace) => new Promise((resolve, reject) => {
  795. try {
  796. manager.isInstalled(scriptName, scriptNamespace, (result) => {
  797. resolve({
  798. type,
  799. result: typeof result === 'string' ? { version: result } : result
  800. });
  801. });
  802. } catch (e) {
  803. reject(e);
  804. }
  805. }).catch(console.warn);
  806.  
  807.  
  808.  
  809. const enableScriptInstallChecker = (r) => {
  810.  
  811. const { type, result } = r;
  812. let version = result.version;
  813. // console.log(type, result, version)
  814. if (version !== scriptVersion) return;
  815.  
  816. const pnIsInstalled = type < 25 ? pnIsInstalled2 : pnIsInstalled3;
  817.  
  818. wincomm.hook('_$GreasyFork$Msg$OnScriptInstallCheck', {
  819.  
  820. 'installedVersion.req': (d, evt) => {
  821. pnIsInstalled(type, d.data.name, d.data.namespace).then((r) => {
  822. if (r && 'result' in r) {
  823. wincomm.response(evt, 'installedVersion.res', {
  824. version: r.result ? (r.result.version || '') : ''
  825. });
  826. }
  827. })
  828. }
  829.  
  830. });
  831.  
  832. wincomm.send('ready', { type });
  833.  
  834. // console.log('enableScriptInstallChecker', r)
  835.  
  836.  
  837. }
  838.  
  839. const kl = manager.isInstalled.length;
  840.  
  841. if (!(kl === 2 || kl === 3)) return;
  842. const puds = kl === 2 ? [
  843. pnIsInstalled2(21, scriptName, scriptNamespace), // scriptName is GM.info.script.name not GM.info.script.name_i18n
  844. pnIsInstalled2(20, scriptName, '')
  845. ] : [
  846. pnIsInstalled3(31, scriptName, scriptNamespace),
  847. pnIsInstalled3(30, scriptName, '')
  848. ];
  849.  
  850. Promise.all(puds).then((rs) => {
  851. const [r1, r0] = rs;
  852. if (r0 && r0.result && r0.result.version) enableScriptInstallChecker(r0); // '3.1.4'
  853. else if (r1 && r1.result && r1.result.version) enableScriptInstallChecker(r1);
  854. });
  855.  
  856.  
  857.  
  858. // console.log(327, shObject, scriptHandler);
  859.  
  860. }
  861.  
  862.  
  863.  
  864. return { fields, logo, locales, blacklist, settingsCSS, pageCSS, contentScriptText }
  865.  
  866.  
  867.  
  868. })();
  869.  
  870. (async () => {
  871.  
  872. let rafPromise = null;
  873.  
  874. const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
  875. requestAnimationFrame(hRes => {
  876. rafPromise = null;
  877. resolve(hRes);
  878. });
  879. }));
  880.  
  881. const isVaildURL = (url) => {
  882. if (!url || typeof url !== 'string' || url.length < 23) return;
  883. let obj = null;
  884. try {
  885. obj = new URL(url);
  886. } catch (e) {
  887. return false;
  888. }
  889. if (obj && obj.host === obj.hostname && !obj.port && (obj.protocol || '').startsWith('http') && obj.pathname) {
  890. return true;
  891. }
  892. return false;
  893. };
  894.  
  895. const installLinkPointerDownHandler = function (e) {
  896. if (!e || !e.isTrusted) return;
  897. const button = e.target || this;
  898. if (button.hasAttribute('acnmd')) return;
  899. const href = button.href;
  900. if (!href || !isVaildURL(href)) return;
  901. if (/\.js[^-.\w\d\s:\/\\]*$/.test(href)) {
  902. 0 && fetch(href, {
  903. method: "GET",
  904. cache: 'reload',
  905. redirect: "follow"
  906. }).then(() => {
  907. console.debug('code url reloaded', href);
  908. }).catch((e) => {
  909. console.debug(e);
  910. });
  911. const m = /^(https\:\/\/(greasyfork|sleazyfork)\.org\/[_-\w\/]*scripts\/(\d+)[-\w%]*)(\/|$)/.exec(location.href)
  912. if (m && m[1]) {
  913. const href = `${m[1]}/code`
  914. 0 && fetch(href, {
  915. method: "GET",
  916. cache: 'reload',
  917. redirect: "follow"
  918. }).then(() => {
  919. console.debug('code url reloaded', href);
  920. }).catch((e) => {
  921. console.debug(e);
  922. });
  923. }
  924.  
  925. if (m && m[3] && href.includes('.user.js')) {
  926. const href = `https://${location.hostname}/scripts/${m[3]}-fetching/code/${crypto.randomUUID()}.user.js?version_=${Date.now()}`
  927. 0 && fetch(href, {
  928. method: "GET",
  929. cache: 'reload',
  930. redirect: "follow"
  931. }).then(() => {
  932. console.debug('code url reloaded', href);
  933. }).catch((e) => {
  934. console.debug(e);
  935. });
  936. }
  937.  
  938.  
  939. }
  940.  
  941. button.setAttribute('acnmd', '');
  942. };
  943.  
  944. const setupInstallLink = (button) => {
  945. if (!button || button.className !== 'install-link' || button.nodeName !== "A" || !button.href) return button;
  946. button.addEventListener('pointerdown', installLinkPointerDownHandler);
  947. return button;
  948. };
  949.  
  950. function fixValue(key, def, test) {
  951. return GM.getValue(key, def).then((v) => test(v) || GM.deleteValue(key))
  952. }
  953.  
  954. const isNaNx = Number.isNaN;
  955.  
  956. function numberArr(arrVal) {
  957. if (!arrVal || typeof arrVal.length !== 'number') return [];
  958. return arrVal.filter(e => typeof e === 'number' && !isNaNx(e))
  959. }
  960.  
  961. const isScriptFirstUse = await GM.getValue('firstUse', true);
  962. await Promise.all([
  963. fixValue('hiddenList', [], v => v && typeof v === 'object' && typeof v.length === 'number' && (v.length === 0 || typeof v[0] === 'number')),
  964. fixValue('lastMilestone', 0, v => v && typeof v === 'number' && v >= 0)
  965. ])
  966.  
  967. function createRE(t, ...opt) {
  968. try {
  969. return new RegExp(t, ...opt);
  970. } catch (e) { }
  971. return null;
  972. }
  973.  
  974. const useHashedScriptName = true;
  975. const fixLibraryScriptCodeLink = true;
  976. const addAdditionInfoLengthHint = true;
  977.  
  978. const id = 'greasyfork-plus';
  979. const title = `${GM.info.script.name} v${GM.info.script.version} Settings`;
  980. const fields = mWindow.fields;
  981. const logo = mWindow.logo;
  982. const nonLatins = /[^\p{Script=Latin}\p{Script=Common}\p{Script=Inherited}]/gu;
  983. const blacklist = createRE((mWindow.blacklist || []).filter(e => !!e).join('|'), 'giu');
  984. const hiddenList = numberArr(await GM.getValue('hiddenList', []));
  985. const lang = document.documentElement.lang;
  986. const locales = mWindow.locales;
  987.  
  988. const _isBlackList = (text) => {
  989. if (!text || typeof text !== 'string') return false;
  990. if (text.includes('hack') && (text.includes('EXPERIMENT_FLAGS') || text.includes('yt.'))) return false;
  991. return blacklist.test(text);
  992. }
  993. const isBlackList = (name, description) => {
  994. // To be reviewed
  995. if (!blacklist) return false;
  996. return _isBlackList(name) || _isBlackList(description);
  997. }
  998.  
  999. function hiddenListStrToArr(str) {
  1000. if (!str || typeof str !== 'string') str = '';
  1001. return [...new Set(str ? numberArr(str.split(',').map(e => parseInt(e))) : [])];
  1002. }
  1003.  
  1004. const gmc = new GM_config({
  1005. id,
  1006. title,
  1007. fields,
  1008. css: mWindow.settingsCSS,
  1009. events: {
  1010. init: () => {
  1011. gmc.initializedResolve && gmc.initializedResolve();
  1012. gmc.initializedResolve = null;
  1013.  
  1014. },
  1015. /** @param {Document} document */
  1016. open: async (document) => {
  1017. const textarea = document.querySelector(`#${id}_field_hiddenList`);
  1018.  
  1019. const hiddenSet = new Set(numberArr(await GM.getValue('hiddenList', [])));
  1020. if (hiddenSet.size !== 0) {
  1021. const unsavedHiddenList = hiddenListStrToArr(gmc.get('hiddenList'));
  1022. const unsavedHiddenSet = new Set(unsavedHiddenList);
  1023.  
  1024. const hasDifferentItems = [...hiddenSet].some(item => !unsavedHiddenSet.has(item)) || [...unsavedHiddenSet].some(item => !hiddenSet.has(item));
  1025.  
  1026. if (hasDifferentItems) {
  1027.  
  1028. gmc.fields.hiddenList.value = [...hiddenSet].sort((a, b) => a - b).join(', ');
  1029.  
  1030. gmc.close();
  1031. gmc.open();
  1032.  
  1033. }
  1034.  
  1035.  
  1036. }
  1037.  
  1038. const resize = (target) => {
  1039. target.style.height = '';
  1040. target.style.height = `${target.scrollHeight}px`;
  1041. };
  1042.  
  1043. if (textarea) {
  1044. resize(textarea);
  1045. textarea.addEventListener('input', (event) => resize(event.target));
  1046.  
  1047. }
  1048.  
  1049. document.body.addEventListener('mousedown', (event) => {
  1050. if (event.detail > 1 && !event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && !event.defaultPrevented) {
  1051. event.preventDefault();
  1052. event.stopPropagation();
  1053. event.stopImmediatePropagation();
  1054. }
  1055. }, true);
  1056. },
  1057. save: async (forgotten) => {
  1058.  
  1059. if (gmc.isOpen) {
  1060. await GM.setValue('hiddenList', hiddenListStrToArr(forgotten.hiddenList));
  1061.  
  1062. UU.alert('settings saved');
  1063. gmc.close();
  1064. setTimeout(() => window.location.reload(false), 500);
  1065. }
  1066. }
  1067. }
  1068. });
  1069. gmc.initialized = new Promise(r => (gmc.initializedResolve = r));
  1070. await gmc.initialized.then();
  1071. const customBlacklistRE = createRE((gmc.get('customBlacklist') || '').replace(/\s/g, '').split(',').join('|'), 'giu');
  1072.  
  1073. if (typeof GM.registerMenuCommand === 'function') {
  1074. GM.registerMenuCommand('Configure', () => gmc.open());
  1075. GM.registerMenuCommand('Reset Everything', () => {
  1076. Promise.all([
  1077. GM.deleteValue('hiddenList'),
  1078. GM.deleteValue('lastMilestone'),
  1079. GM.deleteValue('firstUse')
  1080. ]).then(() => {
  1081. setTimeout(() => window.location.reload(false), 50);
  1082. })
  1083. });
  1084. }
  1085.  
  1086. UU.init({ id, logging: gmc.get('logging') });
  1087. UU.log(nonLatins);
  1088. UU.log(blacklist);
  1089. UU.log(hiddenList);
  1090.  
  1091. const _VM = (typeof VM !== 'undefined' ? VM : null) || {
  1092. shortcut: {
  1093. register: () => { }
  1094. }
  1095. };
  1096.  
  1097.  
  1098. function fixLibraryCodeURL(code_url) {
  1099. if (/\/scripts\/(\d+)(\-[^\/]+)\/code\//.test(code_url)) {
  1100. code_url = code_url.replace(/\/scripts\/(\d+)(\-[^\/]+)\/code\//, '/scripts/$1/code/');
  1101. let qm = code_url.indexOf('?');
  1102. let s1 = code_url.substring(0, qm);
  1103. let s2 = code_url.substring(qm + 1);
  1104. if (qm > 0) {
  1105. code_url = `${decodeURI(s1)}?${s2}`;
  1106. }
  1107. }
  1108. return code_url;
  1109. }
  1110.  
  1111. function setClickToSelect(elm) {
  1112. elm.addEventListener('click', function () {
  1113. if (`${window.getSelection()}` === "") {
  1114. if (typeof this.select === 'function') {
  1115. this.select();
  1116. } else {
  1117. const range = document.createRange(); // Create a range object
  1118. range.selectNode(this); // Select the text within the element
  1119. const selection = window.getSelection(); // Get the selection object
  1120. selection.removeAllRanges(); // First clear any existing selections
  1121. selection.addRange(range); // Add the new range to the selection
  1122. }
  1123. }
  1124. });
  1125. elm.addEventListener('drag', function (evt) {
  1126. evt.preventDefault();
  1127. });
  1128. elm.addEventListener('drop', function (evt) {
  1129. evt.preventDefault();
  1130. });
  1131. elm.addEventListener('dragstart', function (evt) {
  1132. evt.preventDefault();
  1133. });
  1134. }
  1135.  
  1136. const copyText = typeof (((window.navigator || 0).clipboard || 0).writeText) === 'function' ? (text) => {
  1137. navigator.clipboard.writeText(text).then(function () {
  1138. //
  1139. }).catch(function (err) {
  1140. alert("Unable to Copy");
  1141. });
  1142. } : (text) => {
  1143. const textToCopy = document.createElement('strong');
  1144. textToCopy.style.position = 'fixed';
  1145. textToCopy.style.opacity = '0';
  1146. textToCopy.style.top = '-900vh';
  1147. textToCopy.textContent = text;
  1148. document.body.appendChild(textToCopy);
  1149.  
  1150. const range = document.createRange(); // Create a range object
  1151. range.selectNode(textToCopy); // Select the text within the element
  1152.  
  1153. const selection = window.getSelection(); // Get the selection object
  1154. selection.removeAllRanges(); // First clear any existing selections
  1155. selection.addRange(range); // Add the new range to the selection
  1156.  
  1157. try {
  1158. document.execCommand('copy'); // Try to copy the selected text
  1159. } catch (err) {
  1160. alert("Unable to Copy");
  1161. }
  1162.  
  1163. selection.removeAllRanges(); // Remove the selection range after copying
  1164. textToCopy.remove();
  1165. };
  1166.  
  1167.  
  1168. let avoidDuplication = 0;
  1169. const avoidDuplicationF = () => {
  1170. const p = avoidDuplication;
  1171. avoidDuplication = Date.now();
  1172. if (avoidDuplication - p < 30) return false;
  1173. return true;
  1174. }
  1175. // https://violentmonkey.github.io/vm-shortcut/
  1176. const shortcuts = [
  1177. ['ctrlcmd-alt-keys', () => avoidDuplicationF() && gmc.open()],
  1178. ['ctrlcmd-alt-keyb', () => avoidDuplicationF() && toggleListDisplayingItem('blacklisted')],
  1179. ['ctrlcmd-alt-keyh', () => avoidDuplicationF() && toggleListDisplayingItem('hidden')]
  1180. ]
  1181. for (const [scKey, scFn] of shortcuts) {
  1182. _VM.shortcut.register(scKey, scFn);
  1183. }
  1184.  
  1185. const addSettingsToMenu = () => {
  1186. const nav = document.querySelector('#site-nav > nav')
  1187. if (!nav) return;
  1188.  
  1189. const scriptName = GM.info.script.name;
  1190. const scriptVersion = GM.info.script.version;
  1191. const menu = document.createElement('li');
  1192. menu.classList.add(id);
  1193. menu.setAttribute('alt', `${scriptName} ${scriptVersion}`);
  1194. menu.setAttribute('title', `${scriptName} ${scriptVersion}`);
  1195. const link = document.createElement('a');
  1196. link.setAttribute('href', '#');
  1197. link.textContent = GM.info.script.name;
  1198. menu.appendChild(link);
  1199. nav.insertBefore(menu, document.querySelector('#site-nav > nav > li:first-of-type'));
  1200.  
  1201. menu.addEventListener('click', (e) => {
  1202. e.preventDefault();
  1203. e.stopPropagation();
  1204. e.stopImmediatePropagation();
  1205. gmc.open();
  1206. });
  1207. };
  1208.  
  1209.  
  1210. const toggleListDisplayingItem = (t) => {
  1211.  
  1212. const m = document.documentElement;
  1213.  
  1214. const p = t + '-shown';
  1215. let currentIsShown = m.hasAttribute(p)
  1216. if (!currentIsShown) {
  1217. m.setAttribute(p, '')
  1218. } else {
  1219. m.removeAttribute(p)
  1220. }
  1221.  
  1222. }
  1223.  
  1224. const createListOptionGroup = () => {
  1225.  
  1226. const html = `<div class="list-option-group" id="${id}-options">${GM.info.script.name} Lists:<ul>
  1227. <li class="list-option blacklisted"><a href="#" id="hyperlink-35389"></a></li>
  1228. <li class="list-option hidden"><a href="#" id="hyperlink-40361"></a></li>
  1229. </ul></div>`;
  1230. const firstOptionGroup = document.querySelector('.list-option-groups > div');
  1231. firstOptionGroup && firstOptionGroup.insertAdjacentHTML('beforebegin', html);
  1232.  
  1233. const blacklistedOption = document.querySelector(`#${id}-options li.blacklisted`);
  1234. blacklistedOption && blacklistedOption.addEventListener('click', (evt) => {
  1235. evt.preventDefault();
  1236. toggleListDisplayingItem('blacklisted');
  1237. }, false);
  1238.  
  1239. const hiddenOption = document.querySelector(`#${id}-options li.hidden`);
  1240. hiddenOption && hiddenOption.addEventListener('click', (evt) => {
  1241. evt.preventDefault();
  1242. toggleListDisplayingItem('hidden');
  1243. }, false);
  1244.  
  1245. }
  1246.  
  1247. const addOptions = () => {
  1248.  
  1249. const gn = () => {
  1250.  
  1251. let aBlackList = document.querySelector('#hyperlink-35389');
  1252. let aHidden = document.querySelector('#hyperlink-40361');
  1253. if (!aBlackList || !aHidden) return;
  1254. aBlackList.textContent = `Blacklisted scripts (${document.querySelectorAll('.script-list li.blacklisted').length})`;
  1255. aHidden.textContent = `Hidden scripts (${document.querySelectorAll('.script-list li.hidden').length})`
  1256.  
  1257. }
  1258. const callback = (entries) => {
  1259. if (entries && entries.length >= 1) requestAnimationFrame(gn);
  1260. }
  1261.  
  1262. const setupScriptList = async () => {
  1263. let scriptList;
  1264. let i = 8;
  1265. while (i-- > 0) {
  1266. scriptList = document.querySelector('.script-list li')
  1267. if (scriptList) scriptList = scriptList.closest('.script-list')
  1268. if (scriptList) break;
  1269. await new Promise(r => requestAnimationFrame(r))
  1270. }
  1271. if (!scriptList) return;
  1272. createListOptionGroup();
  1273. const mo = new MutationObserver(callback);
  1274. mo.observe(scriptList, { childList: true, subtree: true });
  1275. gn();
  1276. }
  1277. setupScriptList();
  1278.  
  1279. };
  1280.  
  1281.  
  1282. const PromiseExternal = ((resolve_, reject_) => {
  1283. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  1284. return class PromiseExternal extends Promise {
  1285. constructor(cb = h) {
  1286. super(cb);
  1287. if (cb === h) {
  1288. /** @type {(value: any) => void} */
  1289. this.resolve = resolve_;
  1290. /** @type {(reason?: any) => void} */
  1291. this.reject = reject_;
  1292. }
  1293. }
  1294. };
  1295. })();
  1296.  
  1297. /**
  1298. * Get script data from Greasy Fork镜像 API
  1299. *
  1300. * @param {number} id Script ID
  1301. * @returns {Promise} Script data
  1302. */
  1303. let networkMP1 = Promise.resolve();
  1304. let networkMP2 = Promise.resolve();
  1305. let previousIsCache = false;
  1306. // let ss = [];
  1307. // var sum = function(nums) {
  1308. // var total = 0;
  1309. // for (var i = 0, len = nums.length; i < len; i++) total += nums[i];
  1310. // return total;
  1311. // };
  1312. let reqStoresA = new Map();
  1313. let reqStoresB = new Map();
  1314.  
  1315. const getOldestEntry = (noCache)=>{
  1316. const reqStores = noCache ? reqStoresB : reqStoresA;
  1317. const oldestEntry = reqStores.entries().next();
  1318. if(!oldestEntry || !oldestEntry.value) return [];
  1319. const id = oldestEntry.value[0]
  1320. const req = oldestEntry.value[1]
  1321. reqStores.delete(id);
  1322. return [id, req];
  1323. }
  1324.  
  1325. let mutexC = Promise.resolve();
  1326. const getScriptDataAN = (noCache)=>{
  1327. mutexC = mutexC.then(async ()=>{
  1328.  
  1329. const [id, req] = getOldestEntry(noCache);
  1330. if (!(id > 0)) return;
  1331.  
  1332. const url = `https://${window.location.hostname}/scripts/${id}.json`;
  1333. const onPageElement = document.querySelector(`[data-script-namespace][data-script-id="${id || 'null'}"][data-script-name][data-script-version][href]`)
  1334. if (onPageElement && /^https\:\/\/update\.\w+\.org\/scripts\/\d+\/[^.?\/]+\.user\.js$/.test(onPageElement.getAttribute('href') || '')) {
  1335. const result = {
  1336. "id": +onPageElement.getAttribute('data-script-id'),
  1337. // "created_at": "2023-08-24T21:16:50.000Z",
  1338. // "daily_installs": 21,
  1339. // "total_installs": 3310,
  1340. // "code_updated_at": "2023-12-20T07:46:54.000Z",
  1341. // "support_url": null,
  1342. // "fan_score": "74.1",
  1343. "namespace": `${onPageElement.getAttribute('data-script-namespace')}`,
  1344. // "contribution_url": null,
  1345. // "contribution_amount": null,
  1346. // "good_ratings": 11,
  1347. // "ok_ratings": 0,
  1348. // "bad_ratings": 0,
  1349. // "users": [
  1350. // {
  1351. // "id": 371179,
  1352. // "name": "𝖢𝖸 𝖥𝗎𝗇𝗀",
  1353. // "url": "https://gf.qytechs.cn/users/371179-%F0%9D%96%A2%F0%9D%96%B8-%F0%9D%96%A5%F0%9D%97%8E%F0%9D%97%87%F0%9D%97%80"
  1354. // }
  1355. // ],
  1356. "name": `${onPageElement.getAttribute('data-script-name')}`,
  1357. // "description": "Adds various features and improves the Greasy Fork镜像 experience",
  1358. // "url": "https://gf.qytechs.cn/scripts/473830-greasy-fork",
  1359. // "code_url": "https://update.gf.qytechs.cn/scripts/473830/Greasy%20Fork%2B%2B.user.js",
  1360. "code_url": `${onPageElement.getAttribute('href')}`,
  1361. // "license": "MIT License",
  1362. "version": `${onPageElement.getAttribute('data-script-version')}`,
  1363. // "locale": "en",
  1364. // "deleted": false
  1365. };
  1366. req.resolve(result);
  1367. return;
  1368. }
  1369. await (networkMP1 = networkMP1.then(() => new Promise(unlock => {
  1370. const maxAgeInSeconds = 900;
  1371. const rd = previousIsCache ? 1 : Math.floor(Math.random() * 80 + 80);
  1372. let fetchStart = 0;
  1373. new Promise(r => setTimeout(r, rd))
  1374. .then(() => {
  1375. fetchStart = Date.now();
  1376. })
  1377. .then(() => fetch(url, noCache ? {
  1378. method: 'GET',
  1379. cache: 'reload',
  1380. credentials: 'omit',
  1381. headers: new Headers({
  1382. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1383. })
  1384. } : {
  1385. method: 'GET',
  1386. cache: 'force-cache',
  1387. credentials: 'omit',
  1388. headers: new Headers({
  1389. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1390. }),
  1391. }))
  1392. .then((response) => {
  1393. let fetchStop = Date.now();
  1394. // const dd = fetchStop - fetchStart;
  1395. // dd (cache) = {min: 1, max: 8, avg: 3.7}
  1396. // dd (normal) = {min: 136, max: 316, avg: 162.62}
  1397. // ss.push(dd)
  1398. // ss.maxValue = Math.max(...ss);
  1399. // ss.minValue = Math.min(...ss);
  1400. // ss.avgValue = sum(ss)/ss.length;
  1401. // console.log(dd)
  1402. // console.log(ss)
  1403. previousIsCache = (fetchStop - fetchStart) < (3.7 + 162.62) / 2;
  1404. UU.log(`${response.status}: ${response.url}`)
  1405. // UU.log(response)
  1406. if (response.ok === true) {
  1407. unlock();
  1408. return response.json()
  1409. }
  1410. if (response.status === 503) {
  1411. return new Promise(r => setTimeout(r, 270 + rd)).then(() => {
  1412. unlock();
  1413. return getScriptData(id, true);
  1414. });
  1415. }
  1416. if (response.status === 404) {
  1417. // script XXXX has been reported and is pending review by a moderator.
  1418. unlock();
  1419. return null
  1420. }
  1421. console.warn(response.status, response);
  1422. new Promise(r => setTimeout(r, 470)).then(unlock); // reload later
  1423. })
  1424. .then((data) => req.resolve(data))
  1425. .catch((e) => {
  1426. unlock();
  1427. UU.log(id, url)
  1428. console.warn(e)
  1429. // reject(e)
  1430. })
  1431. })).catch(() => { }))
  1432.  
  1433. });
  1434.  
  1435. }
  1436. const getScriptData = (id, noCache) => {
  1437. if (!(+id > 0)) return Promise.resolve();
  1438. id = +id;
  1439. const reqStores = noCache ? reqStoresB : reqStoresA;
  1440. const cachedReq = reqStores.get(id);
  1441. if (cachedReq) return cachedReq;
  1442. const req = new PromiseExternal();
  1443. reqStores.set(id, req);
  1444. getScriptDataAN(noCache);
  1445. return req;
  1446. }
  1447.  
  1448. /**
  1449. * Get user data from Greasy Fork镜像 API
  1450. *
  1451. * @param {string} userID User ID
  1452. * @returns {Promise} User data
  1453. */
  1454. const getUserData = (userID, noCache) => {
  1455. const DO_CORS = 'api.gf.qytechs.cn';
  1456.  
  1457. if (!(userID >= 0)) return Promise.resolve()
  1458.  
  1459. const url = `https://${DO_CORS || window.location.hostname}/users/${userID}.json`;
  1460. return new Promise((resolve, reject) => {
  1461.  
  1462.  
  1463. networkMP2 = networkMP2.then(() => new Promise(unlock => {
  1464.  
  1465. const maxAgeInSeconds = 900;
  1466. const rd = Math.floor(Math.random() * 80 + 80);
  1467.  
  1468. const fetchOptions = DO_CORS ? {
  1469. method: 'GET',
  1470. credentials: 'omit'
  1471. } : noCache ? {
  1472. method: 'GET',
  1473. cache: 'reload',
  1474. credentials: 'omit',
  1475. headers: new Headers({
  1476. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1477. })
  1478. } : {
  1479. method: 'GET',
  1480. cache: 'force-cache',
  1481. credentials: 'omit',
  1482. headers: new Headers({
  1483. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1484. })
  1485. };
  1486.  
  1487. new Promise(r => setTimeout(r, rd))
  1488. .then(() => fetch(url, fetchOptions))
  1489. .then((response) => {
  1490. UU.log(`${response.status}: ${response.url}`)
  1491. if (response.ok === true) {
  1492. unlock();
  1493. return response.json()
  1494. }
  1495. if (response.status === 503) {
  1496. return new Promise(r => setTimeout(r, 270 + rd)).then(() => {
  1497. unlock();
  1498. return getUserData(userID, true); // reload later
  1499. });
  1500. }
  1501. if (response.status === 404) {
  1502. // user XXXX has been reported and is pending review by a moderator. ????
  1503. unlock();
  1504. return null
  1505. }
  1506. console.warn(response.status, response);
  1507. new Promise(r => setTimeout(r, 470)).then(unlock);
  1508. })
  1509. .then((data) => resolve(data))
  1510. .catch((e) => {
  1511. setTimeout(() => {
  1512. unlock()
  1513. }, 270)
  1514. UU.log(userID, url)
  1515. console.warn(e)
  1516. // reject(e)
  1517. })
  1518.  
  1519.  
  1520.  
  1521. })).catch(() => { })
  1522.  
  1523. });
  1524. }
  1525. const getTotalInstalls = (data) => {
  1526. if (!data || !data.scripts) return;
  1527. return new Promise((resolve, reject) => {
  1528. const totalInstalls = [];
  1529.  
  1530. data.scripts.forEach((element) => {
  1531. totalInstalls.push(parseInt(element.total_installs, 10));
  1532. });
  1533.  
  1534. resolve(totalInstalls.reduce((a, b) => a + b, 0));
  1535. });
  1536. };
  1537.  
  1538.  
  1539. const communicationId = WinComm.newCommunicationId();
  1540. const wincomm = WinComm.createInstance(communicationId);
  1541.  
  1542.  
  1543. const isInstalled = (script) => {
  1544. return new Promise((resolve, reject) => {
  1545.  
  1546. promiseScriptCheck.then(d => {
  1547.  
  1548. if (!d) return null;
  1549.  
  1550. const data = d.data;
  1551. const al = data.type % 10;
  1552. if (al === 0) {
  1553. // no namespace
  1554. resolve([null, script.name, '']);
  1555. } else if (al === 1) {
  1556. // namespace
  1557.  
  1558. if (!script.namespace) {
  1559.  
  1560. getRafPromise() // foreground
  1561. .then(() => getScriptData(script.id))
  1562. .then((script) => {
  1563. resolve([null, script.name, script.namespace]);
  1564. });
  1565.  
  1566. } else {
  1567.  
  1568. resolve([null, script.name, script.namespace]);
  1569. }
  1570.  
  1571. }
  1572.  
  1573.  
  1574. })
  1575.  
  1576.  
  1577. }).then((res) => {
  1578.  
  1579.  
  1580. return new Promise((resolve, reject) => {
  1581.  
  1582. if (!res) return '';
  1583.  
  1584.  
  1585. const [_, name, namespace] = res;
  1586. wincomm.request('installedVersion.req', {
  1587. name,
  1588. namespace
  1589. }).then(d => {
  1590. resolve(d.data.version)
  1591. })
  1592.  
  1593. })
  1594.  
  1595. })
  1596.  
  1597. /*
  1598. const external = unsafeWindow.external;
  1599. const scriptHandler = GM.info.scriptHandler;
  1600. if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey') {
  1601. external.Violentmonkey.isInstalled(name, namespace).then((data) => resolve(data));
  1602. return;
  1603. }
  1604.  
  1605. if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') {
  1606. external.Tampermonkey.isInstalled(name, namespace, (data) => {
  1607. (data.installed) ? resolve(data.version) : resolve();
  1608. });
  1609. return;
  1610. }
  1611. */
  1612.  
  1613.  
  1614. };
  1615.  
  1616. const compareVersions = (v1, v2) => {
  1617. if (!v1 || !v2) return NaN;
  1618. if (v1 === null || v2 === null) return NaN;
  1619. if (v1 === v2) return 0;
  1620.  
  1621. const sv1 = v1.split('.').map((index) => parseInt(index));
  1622. const sv2 = v2.split('.').map((index) => parseInt(index));
  1623.  
  1624. const count = Math.max(sv1.length, sv2.length);
  1625.  
  1626. for (let index = 0; index < count; index++) {
  1627. if (isNaNx(sv1[index]) || isNaNx(sv2[index])) return NaN;
  1628. if (sv1[index] > sv2[index]) return 1;
  1629. if (sv1[index] < sv2[index]) return -1;
  1630. }
  1631.  
  1632. return 0;
  1633. };
  1634.  
  1635.  
  1636. /**
  1637. * Return label for the hide script button
  1638. *
  1639. * @param {boolean} hidden Is hidden
  1640. * @returns {string} Label
  1641. */
  1642. const blockLabel = (hidden) => {
  1643. return hidden ? (locales[lang] ? locales[lang].notHide : locales.en.notHide) : (locales[lang] ? locales[lang].hide : locales.en.hide)
  1644. }
  1645.  
  1646. /**
  1647. * Return label for the install button
  1648. *
  1649. * @param {number} update Update value
  1650. * @returns {string} Label
  1651. */
  1652. const installLabel = (update) => {
  1653. switch (update) {
  1654. case 0: {
  1655. return locales[lang] ? locales[lang].reinstall : locales.en.reinstall
  1656. }
  1657. case 1: {
  1658. return locales[lang] ? locales[lang].update : locales.en.update
  1659. }
  1660. case -1: {
  1661. return locales[lang] ? locales[lang].downgrade : locales.en.downgrade
  1662. }
  1663. default: {
  1664. return locales[lang] ? locales[lang].install : locales.en.install
  1665. }
  1666. }
  1667. }
  1668.  
  1669. const hideBlacklistedDiscussion = (element, list) => {
  1670.  
  1671. const scriptLink = element.querySelector('a.script-link')
  1672. const m = /\/scripts\/(\d+)/.exec(scriptLink);
  1673. const id = m ? +m[1] : 0;
  1674. if (!(id > 0)) return;
  1675.  
  1676. switch (list) {
  1677. case 'hiddenList': {
  1678. const container = element.closest('.discussion-list-container') || element;
  1679. if (hiddenList.indexOf(id) >= 0) {
  1680. container.classList.add('hidden');
  1681. }
  1682. // if (customBlacklist && (customBlacklist.test(name) || customBlacklist.test(description)) && !element.classList.contains('blacklisted')) {
  1683. // element.classList.add('blacklisted', 'custom-blacklist');
  1684. // if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1685. // let scriptLink = element.querySelector('.script-link');
  1686. // if (scriptLink) { scriptLink.textContent += ' (custom-blacklist)'; }
  1687. // }
  1688. // }
  1689. break;
  1690. }
  1691. default:
  1692. UU.log('No blacklists');
  1693. break;
  1694. }
  1695.  
  1696. }
  1697. const hideBlacklistedScript = (element, list) => {
  1698. if (!element) return;
  1699. const scriptLink = element.querySelector('.script-link')
  1700.  
  1701. const name = scriptLink ? scriptLink.textContent : '';
  1702. const descriptionElem = element.querySelector('.script-description')
  1703. const description = descriptionElem ? descriptionElem.textContent : '';
  1704.  
  1705. if (!name) return;
  1706.  
  1707. switch (list) {
  1708. case 'nonLatins':
  1709. if ((nonLatins.test(name) || nonLatins.test(description)) && !element.classList.contains('blacklisted')) {
  1710. element.classList.add('blacklisted', 'non-latins');
  1711. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1712. let scriptLink = element.querySelector('.script-link');
  1713. if (scriptLink) { scriptLink.textContent += ' (non-latin)'; }
  1714. }
  1715. }
  1716. break;
  1717. case 'blacklist':
  1718. if (isBlackList(name, description) && !element.classList.contains('blacklisted')) {
  1719. element.classList.add('blacklisted', 'blacklist');
  1720. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1721. let scriptLink = element.querySelector('.script-link');
  1722. if (scriptLink) { scriptLink.textContent += ' (blacklist)'; }
  1723. }
  1724. }
  1725. break;
  1726. case 'customBlacklist': {
  1727. const customBlacklist = customBlacklistRE;
  1728. if (customBlacklist && (customBlacklist.test(name) || customBlacklist.test(description)) && !element.classList.contains('blacklisted')) {
  1729. element.classList.add('blacklisted', 'custom-blacklist');
  1730. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1731. let scriptLink = element.querySelector('.script-link');
  1732. if (scriptLink) { scriptLink.textContent += ' (custom-blacklist)'; }
  1733. }
  1734. }
  1735. break;
  1736. }
  1737. default:
  1738. UU.log('No blacklists');
  1739. break;
  1740. }
  1741. };
  1742.  
  1743. const hideHiddenScript = (element, id, list) => {
  1744. id = +id;
  1745. if (!(id >= 0)) return;
  1746.  
  1747. const isInHiddenList = () => hiddenList.indexOf(id) !== -1;
  1748. const updateScriptLink = (shouldHide) => {
  1749. if (gmc.get('hideHiddenScript') && gmc.get('debugging')) {
  1750. let scriptLink = element.querySelector('.script-link');
  1751. if (scriptLink) {
  1752. if (shouldHide) {
  1753. scriptLink.innerHTML += ' (hidden)';
  1754. } else {
  1755. scriptLink.innerHTML = scriptLink.innerHTML.replace(' (hidden)', '');
  1756. }
  1757. }
  1758. }
  1759. };
  1760.  
  1761. // Check for initial state and set it
  1762. if (isInHiddenList()) {
  1763. element.classList.add('hidden');
  1764. updateScriptLink(true);
  1765. }
  1766.  
  1767. // Add button to hide the script
  1768. const insertButtonHTML = (selector, html) => {
  1769. const target = element.querySelector(selector);
  1770. if (!target) return;
  1771. let p = document.createElement('template');
  1772. p.innerHTML = html;
  1773. target.parentNode.insertBefore(p.content.firstChild, target.nextSibling);
  1774. };
  1775.  
  1776. const isHidden = element.classList.contains('hidden');
  1777. const blockButtonHTML = `<span class=block-button role=button style-16377>${blockLabel(isHidden)}</span>`;
  1778. const blockButtonHeaderHTML = `<span class=block-button role=button style-77329 style="">${blockLabel(isHidden)}</span>`;
  1779.  
  1780. insertButtonHTML('.badge-js, .badge-css', blockButtonHTML);
  1781. insertButtonHTML('header h2', blockButtonHeaderHTML);
  1782.  
  1783. // Add event listener
  1784. const button = element.querySelector('.block-button');
  1785. if (button) {
  1786. button.addEventListener('click', (event) => {
  1787. event.stopPropagation();
  1788. event.stopImmediatePropagation();
  1789.  
  1790. if (!isInHiddenList()) {
  1791. hiddenList.push(id);
  1792. GM.setValue('hiddenList', hiddenList);
  1793.  
  1794. element.classList.add('hidden');
  1795. updateScriptLink(true);
  1796.  
  1797. } else {
  1798. const index = hiddenList.indexOf(id);
  1799. hiddenList.splice(index, 1);
  1800. GM.setValue('hiddenList', hiddenList);
  1801.  
  1802. element.classList.remove('hidden');
  1803. updateScriptLink(false);
  1804. }
  1805.  
  1806. const blockBtn = element.querySelector('.block-button');
  1807. if (blockBtn) blockBtn.textContent = blockLabel(element.classList.contains('hidden'));
  1808. });
  1809. }
  1810. };
  1811.  
  1812. const insertButtonHTML = (element, selector, html) => {
  1813. const target = element.querySelector(selector);
  1814. if (!target) return;
  1815. let p = document.createElement('template');
  1816. p.innerHTML = html;
  1817. let button = p.content.firstChild
  1818. target.parentNode.insertBefore(button, target.nextSibling);
  1819. return button;
  1820. };
  1821.  
  1822. const addInstallButton = (element, url) => {
  1823. return setupInstallLink(insertButtonHTML(element, '.badge-js, .badge-css', `<a class="install-link" href="${url}" style-54998></a>`));
  1824. };
  1825.  
  1826. async function digestMessage(message, algo) {
  1827. const encoder = new TextEncoder();
  1828. const data = encoder.encode(message);
  1829. const hash = await crypto.subtle.digest(algo, data);
  1830. return hash;
  1831. }
  1832.  
  1833. function qexString(buffer) {
  1834. const byteArray = new Uint8Array(buffer);
  1835. const len = byteArray.length;
  1836. const hexCodes = new Array(len * 2);
  1837. const chars = 'a4b3c5d7e6f9h2t';
  1838. for (let i = 0, j = 0; i < len; i++) {
  1839. const byte = byteArray[i];
  1840. hexCodes[j++] = chars[byte >> 4];
  1841. hexCodes[j++] = chars[byte & 0x0F];
  1842. };
  1843. return hexCodes.join('');
  1844. }
  1845.  
  1846. const encodeFileName = (s) => {
  1847. if (!s || typeof s !== 'string') return s;
  1848. s = s.replace(/[.!~*'"();\/\\?@&=$,#]/g, '-').replace(/\s+/g, ' ');
  1849. return encodeURI(s);
  1850. }
  1851.  
  1852. const isLibraryURLWithVersion = (url) => {
  1853. if (!url || typeof url !== 'string') return;
  1854.  
  1855. if (url.includes('.js?version=')) return true;
  1856.  
  1857. if (/\/scripts\/\d+\/\d+\/[^.!~*'"();\/\\?@&=$,#]+\.js/.test(url)) return true;
  1858. return false;
  1859.  
  1860. }
  1861.  
  1862. const showInstallButton = async (scriptID, element) => {
  1863.  
  1864. await getRafPromise().then();
  1865. // if(document.querySelector(`li[data-script-id="${scriptID}"]`))
  1866. let _baseScript = null;
  1867. if (element.nodeName === 'LI' && element.hasAttribute('data-script-id') && element.getAttribute('data-script-id') === `${scriptID}` && element.getAttribute('data-script-language') === 'js') {
  1868.  
  1869. const version = element.getAttribute('data-script-version') || ''
  1870.  
  1871. let scriptCodeURL = element.getAttribute('data-code-url');
  1872. if (!scriptCodeURL || !isVaildURL(scriptCodeURL)) {
  1873.  
  1874. const name = element.getAttribute('data-script-name') || ''
  1875. // if (!/[^\x00-\x7F]/.test(name)) {
  1876.  
  1877. // const scriptName = useHashedScriptName ? qexString(await digestMessage(`${+scriptID} ${version}`, 'SHA-1')).substring(0, 8) : encodeURI(name);
  1878. // const token = useHashedScriptName ? `${scriptName.substring(0, 2)}${scriptName.substring(scriptName.length - 2, scriptName.length)}` : String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 19861 + 19861).toString(36);
  1879. const scriptFilename = element.getAttribute('data-script-type') === 'library' ? `${encodeFileName(name)}.js` : `${encodeFileName(name)}.user.js`;
  1880. // const scriptFilename = `${scriptName}.user.js`;
  1881.  
  1882. // code_url: `https://${location.hostname}/scripts/${scriptID}-${token}/code/${scriptFilename}`,
  1883. // code_url: `https://update.${location.hostname}/scripts/${scriptID}.user.js`,
  1884. scriptCodeURL = `https://update.${location.hostname}/scripts/${scriptID}/${scriptFilename}`
  1885. }
  1886. _baseScript = {
  1887. id: +scriptID,
  1888. // name: name,
  1889. code_url: scriptCodeURL,
  1890. version: version
  1891. }
  1892. // }
  1893.  
  1894. }
  1895.  
  1896. const baseScript = _baseScript || (await getScriptData(scriptID));
  1897.  
  1898. if ((element.nodeName === 'LI' && element.getAttribute('data-script-type') === 'library') || (baseScript.code_url.includes('.js?version='))) {
  1899.  
  1900. let scriptCodeURL = element.getAttribute('data-code-url');
  1901.  
  1902. if (!scriptCodeURL || !isVaildURL(scriptCodeURL)) {
  1903. const script = baseScript.code_url.includes('.js?version=') ? baseScript : (await getScriptData(scriptID));
  1904. scriptCodeURL = script.code_url;
  1905. }
  1906.  
  1907. if (scriptCodeURL && isLibraryURLWithVersion(scriptCodeURL)) {
  1908.  
  1909.  
  1910. const code_url = fixLibraryCodeURL(scriptCodeURL);
  1911.  
  1912. const button = addInstallButton(element, code_url);
  1913. button.textContent = `Copy URL`;
  1914. button.addEventListener('click', function (evt) {
  1915.  
  1916. const target = (evt || 0).target;
  1917. if (!target) return;
  1918.  
  1919. let a = target.nodeName === 'A' ? target : target.querySelector('a[href]');
  1920.  
  1921. if (!a) return;
  1922. let href = target.getAttribute('href');
  1923. if (!href) return;
  1924.  
  1925. evt.preventDefault();
  1926.  
  1927. copyText(href);
  1928.  
  1929.  
  1930. });
  1931.  
  1932. }
  1933.  
  1934.  
  1935. } else {
  1936.  
  1937.  
  1938. if (!baseScript || !baseScript.code_url || !baseScript.version) return;
  1939. const button = addInstallButton(element, baseScript.code_url);
  1940. button.classList.add('install-status-checking');
  1941. button.textContent = `${installLabel()} ${baseScript.version}`;
  1942. const script = baseScript && baseScript.name && baseScript.namespace ? baseScript : (await getScriptData(scriptID));
  1943. if (!script) return;
  1944.  
  1945. const installed = await isInstalled(script);
  1946. const version = (
  1947. baseScript.version && script.version && compareVersions(baseScript.version, script.version) === 1
  1948. ) ? baseScript.version : script.version;
  1949.  
  1950. const update = compareVersions(version, installed); // NaN 1 -1 0
  1951. const label = installLabel(update);
  1952. button.textContent = `${label} ${version}`;
  1953. button.classList.remove('install-status-checking');
  1954.  
  1955.  
  1956. }
  1957.  
  1958. }
  1959.  
  1960. const updateReqStoresWithElementsOrder = (x) => {
  1961. try {
  1962. const reqStoresA_ = reqStoresA;
  1963. const reqStoresB_ = reqStoresB;
  1964. const order2 = [...reqStoresA_.keys()];
  1965. const order3 = [...reqStoresB_.keys()];
  1966. const orders1 = x;
  1967. const orders = new Set([...orders1, ...order2, ...order3]);
  1968. const reqStoresA2 = new Map();
  1969. const reqStoresB2 = new Map();
  1970. for (const id of orders) {
  1971. const reqA = reqStoresA_.get(id);
  1972. if (reqA) reqStoresA2.set(id, reqA);
  1973. const reqB = reqStoresB_.get(id);
  1974. if (reqB) reqStoresB2.set(id, reqB);
  1975. }
  1976. reqStoresA = reqStoresA2;
  1977. reqStoresB = reqStoresB2;
  1978. reqStoresA_.clear();
  1979. reqStoresB_.clear();
  1980. } catch (e) {
  1981. console.warn(e)
  1982. }
  1983. };
  1984.  
  1985. let lastIdArrString = '';
  1986.  
  1987. const foundScriptList = async (scriptList) => {
  1988.  
  1989. let rid = 0;
  1990. let g = () => {
  1991. if (!scriptList || scriptList.isConnected !== true) return;
  1992.  
  1993. const scriptElements = scriptList.querySelectorAll('li[data-script-id]:not([e8kk])');
  1994.  
  1995. for (const element of scriptElements) {
  1996. element.setAttribute('e8kk', '1');
  1997.  
  1998. const scriptID = +element.getAttribute('data-script-id');
  1999. if (!(scriptID > 0)) continue;
  2000.  
  2001. // blacklisted scripts
  2002. if (gmc.get('nonLatins')) hideBlacklistedScript(element, 'nonLatins');
  2003. if (gmc.get('blacklist')) hideBlacklistedScript(element, 'blacklist');
  2004. if (gmc.get('customBlacklist')) hideBlacklistedScript(element, 'customBlacklist');
  2005.  
  2006. // hidden scripts
  2007. if (gmc.get('hideHiddenScript')) hideHiddenScript(element, scriptID, true);
  2008.  
  2009. // install button
  2010. if (gmc.get('showInstallButton')) {
  2011. showInstallButton(scriptID, element)
  2012. }
  2013. }
  2014.  
  2015. const idArr = [...scriptList.querySelectorAll('li[data-script-id]')].map(e => +e.getAttribute('data-script-id'));
  2016. const idArrString = idArr.join(',');
  2017. if (lastIdArrString !== idArrString) {
  2018. lastIdArrString = idArrString;
  2019. updateReqStoresWithElementsOrder(idArr);
  2020. }
  2021.  
  2022. }
  2023. let f = (entries) => {
  2024. const tid = ++rid
  2025. if (entries && entries.length) requestAnimationFrame(() => {
  2026. if (tid === rid) g();
  2027. });
  2028. }
  2029. let mo = new MutationObserver(f);
  2030. mo.observe(scriptList, { subtree: true, childList: true });
  2031.  
  2032. g();
  2033.  
  2034. }
  2035.  
  2036. const foundDiscussionList = (discussionsList) => {
  2037.  
  2038. let rid = 0;
  2039. let g = () => {
  2040. if (!discussionsList || discussionsList.isConnected !== true) return;
  2041.  
  2042. const scriptElements = discussionsList.querySelectorAll('.discussion-list-item:not([e8kk])');
  2043.  
  2044. for (const element of scriptElements) {
  2045. element.setAttribute('e8kk', '1');
  2046. // const scriptID = +element.getAttribute('data-script-id');
  2047. // if (!(scriptID > 0)) continue;
  2048.  
  2049.  
  2050. // blacklisted scripts
  2051. // if (gmc.get('nonLatins')) hideBlacklistedDiscussion(element, 'nonLatins');
  2052. // if (gmc.get('blacklist')) hideBlacklistedDiscussion(element, 'blacklist');
  2053. if (gmc.get('hideHiddenScript')) hideBlacklistedDiscussion(element, 'hiddenList');
  2054.  
  2055. // // hidden scripts
  2056. // if (gmc.get('hideHiddenScript')) hideBlacklistedDiscussion(element, scriptID, true);
  2057.  
  2058. // // install button
  2059. // if (gmc.get('showInstallButton')) {
  2060. // showInstallButton(scriptID, element)
  2061. // }
  2062. }
  2063.  
  2064. }
  2065. let f = (entries) => {
  2066. const tid = ++rid
  2067. if (entries && entries.length) requestAnimationFrame(() => {
  2068. if (tid === rid) g();
  2069. });
  2070. }
  2071. let mo = new MutationObserver(f);
  2072. mo.observe(discussionsList, { subtree: true, childList: true });
  2073.  
  2074. g();
  2075.  
  2076. }
  2077.  
  2078. let promiseScriptCheckResolve = null;
  2079. const promiseScriptCheck = new Promise(resolve => {
  2080. promiseScriptCheckResolve = resolve
  2081. });
  2082.  
  2083. const milestoneNotificationFn = async (o) => {
  2084.  
  2085. const { userLink, userID } = o;
  2086.  
  2087.  
  2088. const milestones = gmc.get('milestoneNotification').replace(/\s/g, '').split(',').map(Number);
  2089.  
  2090. if (!userID) return;
  2091.  
  2092. await new Promise(resolve => setTimeout(resolve, 800)); // delay for reducing server burden
  2093. await new Promise(resolve => requestAnimationFrame(resolve)); // foreground
  2094.  
  2095. const userData = await getUserData(+userID.match(/\d+(?=\D)/g));
  2096. if (!userData) return;
  2097.  
  2098. const [totalInstalls, lastMilestone] = await Promise.all([
  2099. getTotalInstalls(userData),
  2100. GM.getValue('lastMilestone', 0)]);
  2101.  
  2102. const milestone = milestones.filter(milestone => totalInstalls >= milestone).pop();
  2103.  
  2104. UU.log(`total installs are "${totalInstalls}", milestone reached is "${milestone}", last milestone reached is "${lastMilestone}"`);
  2105.  
  2106. if (milestone <= lastMilestone) return;
  2107.  
  2108. if (milestone && milestone >= 0) {
  2109.  
  2110.  
  2111. GM.setValue('lastMilestone', milestone);
  2112.  
  2113. const lang = document.documentElement.lang;
  2114. const text = (locales[lang] ? locales[lang].milestone : locales.en.milestone).replace('$1', milestone.toLocaleString());
  2115.  
  2116. if (typeof GM.notification === 'function') {
  2117. GM.notification({
  2118. text,
  2119. title: GM.info.script.name,
  2120. image: logo,
  2121. onclick: () => {
  2122. window.location = `https://${window.location.hostname}${userID}#user-script-list-section`;
  2123. }
  2124. });
  2125. } else {
  2126. UU.alert(text);
  2127. }
  2128.  
  2129. }
  2130.  
  2131. }
  2132. const onReady = async () => {
  2133.  
  2134. try {
  2135.  
  2136. const gminfo = GM.info || 0;
  2137. if (gminfo) {
  2138.  
  2139. const gminfoscript = gminfo.script || 0;
  2140.  
  2141.  
  2142. const scriptHandlerObject = {
  2143. scriptHandler: gminfo.scriptHandler || '',
  2144. scriptName: gminfoscript.name || '', // not name_i18n
  2145. scriptVersion: gminfoscript.version || '',
  2146. scriptNamespace: gminfoscript.namespace || '',
  2147. communicationId
  2148. };
  2149.  
  2150.  
  2151. wincomm.hook('_$GreasyFork$Msg$OnScriptInstallFeedback',
  2152. {
  2153.  
  2154. ready: (d, evt) => promiseScriptCheckResolve(d),
  2155. userScriptManagerNotDetected: (d, evt) => promiseScriptCheckResolve(null),
  2156. 'installedVersion.res': wincomm.handleResponse
  2157.  
  2158.  
  2159. })
  2160.  
  2161.  
  2162. document.head.appendChild(document.createElement('script')).textContent = `;(${mWindow.contentScriptText})(${JSON.stringify(scriptHandlerObject)}, ${WinComm.createInstance});`;
  2163.  
  2164.  
  2165. }
  2166.  
  2167. addSettingsToMenu();
  2168.  
  2169. setTimeout(() => {
  2170. getRafPromise().then(() => {
  2171. let installBtn = document.querySelector('a[data-script-id][data-script-version]')
  2172. let scriptID = installBtn && installBtn.textContent ? +installBtn.getAttribute('data-script-id') : 0;
  2173. if (scriptID > 0) {
  2174. getScriptData(scriptID, true);
  2175. } else {
  2176. const userLink = document.querySelector('#site-nav .user-profile-link a[href]');
  2177. let userID = userLink ? userLink.getAttribute('href') : '';
  2178. userID = userID ? /users\/(\d+)/.exec(userID) : null;
  2179. if (userID) userID = userID[1];
  2180. if (userID) {
  2181. userID = +userID;
  2182. if (userID > 0) {
  2183. getUserData(userID, true);
  2184. }
  2185. }
  2186. }
  2187. });
  2188. }, 740);
  2189.  
  2190. const userLink = document.querySelector('.user-profile-link a[href]');
  2191. const userID = userLink ? userLink.getAttribute('href') : undefined;
  2192.  
  2193. const urlMatch = (url1, url2) => {
  2194. url1 = `${url1}`
  2195. url2 = `${url2}`;
  2196. if (url1.includes(location.hostname)) {
  2197. url1 = url1.replace(`https://${location.hostname}/`, '/')
  2198. url1 = url1.replace(`http://${location.hostname}/`, '/')
  2199. url1 = url1.replace(/^\/+/, '/')
  2200. } else if (!url1.startsWith('/')) {
  2201. url1 = `/${url1}`;
  2202. }
  2203. if (url2.includes(location.hostname)) {
  2204. url2 = url2.replace(`https://${location.hostname}/`, '/')
  2205. url2 = url2.replace(`http://${location.hostname}/`, '/')
  2206. url2 = url2.replace(/^\/+/, '/')
  2207. } else if (!url2.startsWith('/')) {
  2208. url2 = `/${url2}`;
  2209. }
  2210. url1 = url1.replace(/\?\w+=\w+(&\w+=\w+)*$/, '');
  2211. url2 = url2.replace(/\?\w+=\w+(&\w+=\w+)*$/, '');
  2212. return url1.toLowerCase() === url2.toLowerCase();
  2213. }
  2214.  
  2215. UU.addStyle(mWindow.pageCSS);
  2216. // blacklisted scripts / hidden scripts / install button
  2217. if (!urlMatch(window.location.pathname, userID) && !/discussions/.test(window.location.pathname) && (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript') || gmc.get('showInstallButton'))) {
  2218.  
  2219. const scriptList = document.querySelector('.script-list');
  2220. if (scriptList) {
  2221. foundScriptList(scriptList);
  2222. } else {
  2223. const timeout = Date.now() + 3000;
  2224. /** @type {MutationObserver | null} */
  2225. let mo = null;
  2226. const mutationCallbackForScriptList = () => {
  2227. if (!mo) return;
  2228. const scriptList = document.querySelector('.script-list');
  2229. if (scriptList) {
  2230. mo.disconnect();
  2231. mo.takeRecords();
  2232. mo = null;
  2233. foundScriptList(scriptList);
  2234. } else if (Date.now() > timeout) {
  2235. mo.disconnect();
  2236. mo.takeRecords();
  2237. mo = null;
  2238. }
  2239. }
  2240. mo = new MutationObserver(mutationCallbackForScriptList);
  2241. mo.observe(document, { subtree: true, childList: true });
  2242. }
  2243.  
  2244.  
  2245. // hidden scripts on details page
  2246. const installLinkElement = document.querySelector('#script-info .install-link[data-script-id]');
  2247. setupInstallLink(installLinkElement);
  2248. if (gmc.get('hideHiddenScript') && installLinkElement) {
  2249. const id = +installLinkElement.getAttribute('data-script-id');
  2250. hideHiddenScript(document.querySelector('#script-info'), id, false);
  2251. }
  2252.  
  2253. // add options and style for blacklisted/hidden scripts
  2254. if (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript')) {
  2255. addOptions();
  2256. }
  2257.  
  2258. if (installLinkElement && location.pathname.includes('/scripts/')) {
  2259.  
  2260. installLinkElement.addEventListener('click', async function (e) {
  2261. if (e && e.isTrusted && location.pathname.includes('/scripts/')) {
  2262.  
  2263. await new Promise(r => setTimeout(r, 800));
  2264. await new Promise(r => window.requestAnimationFrame(r));
  2265. await new Promise(r => setTimeout(r, 100));
  2266. // let ethicalads497 = 'ethicalads' in window ? window.ethicalads : undefined;
  2267. // window.ethicalads = { wait: new Promise() }
  2268. document.dispatchEvent(new Event("DOMContentLoaded"));
  2269. document.documentElement.dispatchEvent(new Event("turbo:load"));
  2270. // if (ethicalads497 === undefined) delete window.ethicalads; else window.ethicalads = ethicalads497;
  2271. }
  2272. })
  2273. }
  2274. } else if (/\/discussions/.test(window.location.pathname)) {
  2275.  
  2276. const discussionsList = document.querySelector('.discussion-list');
  2277.  
  2278.  
  2279. if (discussionsList) {
  2280. foundDiscussionList(discussionsList);
  2281. } else {
  2282. const timeout = Date.now() + 3000;
  2283. /** @type {MutationObserver | null} */
  2284. let mo = null;
  2285. const mutationCallbackForScriptList = () => {
  2286. if (!mo) return;
  2287. const discussionsList = document.querySelector('.script-list');
  2288. if (discussionsList) {
  2289. mo.disconnect();
  2290. mo.takeRecords();
  2291. mo = null;
  2292. foundDiscussionList(discussionsList);
  2293. } else if (Date.now() > timeout) {
  2294. mo.disconnect();
  2295. mo.takeRecords();
  2296. mo = null;
  2297. }
  2298. }
  2299. mo = new MutationObserver(mutationCallbackForScriptList);
  2300. mo.observe(document, { subtree: true, childList: true });
  2301. }
  2302. }
  2303.  
  2304. // total installs
  2305. if (gmc.get('showTotalInstalls') && document.querySelector('#user-script-list')) {
  2306. const dailyInstalls = [];
  2307. const totalInstalls = [];
  2308.  
  2309. const dailyInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-daily-installs');
  2310. for (const element of dailyInstallElements) {
  2311. dailyInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));
  2312. }
  2313.  
  2314. const totalInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-total-installs');
  2315. for (const element of totalInstallElements) {
  2316. totalInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));
  2317. }
  2318.  
  2319. const dailyInstallsSum = dailyInstalls.reduce((a, b) => a + b, 0);
  2320. const totalInstallsSum = totalInstalls.reduce((a, b) => a + b, 0);
  2321.  
  2322. const convertLi = (li) => {
  2323.  
  2324. if (!li) return null;
  2325. const a = li.firstElementChild
  2326. if (a === null) return li;
  2327. if (a === li.lastElementChild && a.nodeName === 'A') return a;
  2328.  
  2329.  
  2330. return null;
  2331. }
  2332.  
  2333. const plusSign = document.querySelector('#user-script-list-section a[rel="next"][href*="page="], #user-script-list-section a[rel="prev"][href*="page="]') ? '+' : '';
  2334.  
  2335. const dailyOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(1)'));
  2336. dailyOption && dailyOption.insertAdjacentHTML('beforeend', `<span> (${dailyInstallsSum.toLocaleString()}${plusSign})</span>`);
  2337.  
  2338. const totalOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(2)'));
  2339. totalOption && totalOption.insertAdjacentHTML('beforeend', `<span> (${totalInstallsSum.toLocaleString()}${plusSign})</span>`);
  2340. }
  2341.  
  2342. // milestone notification
  2343. if (gmc.get('milestoneNotification')) {
  2344. milestoneNotificationFn({ userLink, userID });
  2345. }
  2346.  
  2347. if (isScriptFirstUse) GM.setValue('firstUse', false).then(() => {
  2348. gmc.open();
  2349. });
  2350.  
  2351. if (fixLibraryScriptCodeLink) {
  2352.  
  2353.  
  2354. let xpath = "//code[contains(text(), '.js?version=') or contains(text(), '// @require https://update.gf.qytechs.cn/scripts/')]";
  2355. let snapshot = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  2356.  
  2357. for (let i = 0; i < snapshot.snapshotLength; i++) {
  2358. let element = snapshot.snapshotItem(i);
  2359. if (element.firstElementChild) continue;
  2360. element.textContent = element.textContent.replace(/\bhttps:\/\/(greasyfork|sleazyfork)\.org\/scripts\/\d+\-[^\/]+\/code\/[^\.]+\.js\?version=\d+\b/, (_) => {
  2361. return fixLibraryCodeURL(_);
  2362. });
  2363. element.parentNode.insertBefore(document.createTextNode('\u200B'), element);
  2364. element.style.display = 'inline-flex';
  2365. setClickToSelect(element);
  2366. }
  2367.  
  2368.  
  2369. }
  2370.  
  2371.  
  2372.  
  2373.  
  2374. if (addAdditionInfoLengthHint && location.pathname.includes('/scripts/') && location.pathname.includes('/versions')) {
  2375.  
  2376. function contentLength(text) {
  2377. return text.replace(/\n/g, ' ').length;
  2378. }
  2379. function contentLengthMax() {
  2380. return 50000;
  2381. }
  2382. let _spanContent = null;
  2383. function updateText(ainfo, span) {
  2384. const value = ainfo.value;
  2385. if (typeof value !== 'string') return;
  2386.  
  2387. if (_spanContent !== value) {
  2388. _spanContent = value;
  2389. span.textContent = `Text Length: ${contentLength(value)} / ${contentLengthMax()}`;
  2390.  
  2391.  
  2392. }
  2393. }
  2394. function onChange(evt) {
  2395. let ainfo = (evt || 0).target;
  2396. if (!ainfo) return;
  2397. let span = ainfo.parentNode.querySelector('.script-version-ainfo-span');
  2398. if (!span) return;
  2399.  
  2400. updateText(ainfo, span);
  2401.  
  2402. }
  2403. function kbEvent(evt) {
  2404. Promise.resolve().then(() => {
  2405. onChange(evt);
  2406.  
  2407. })
  2408. }
  2409. for (const ainfo of document.querySelectorAll('textarea[id^="script-version-additional-info"]')) {
  2410. let span = document.createElement('span');
  2411. span.classList.add('script-version-ainfo-span');
  2412. ainfo.addEventListener('change', onChange, false);
  2413. ainfo.addEventListener('keydown', kbEvent, false);
  2414. ainfo.addEventListener('keypress', kbEvent, false);
  2415. ainfo.addEventListener('keyup', kbEvent, false);
  2416. updateText(ainfo, span);
  2417. ainfo.parentNode.insertBefore(span, ainfo.nextSibling);
  2418.  
  2419.  
  2420. }
  2421.  
  2422.  
  2423. }
  2424.  
  2425. } catch (e) {
  2426. console.log(e);
  2427. }
  2428.  
  2429.  
  2430.  
  2431. }
  2432.  
  2433.  
  2434.  
  2435.  
  2436. Promise.resolve().then(() => {
  2437. if (document.readyState !== 'loading') {
  2438. onReady();
  2439. } else {
  2440. window.addEventListener("DOMContentLoaded", onReady, false);
  2441. }
  2442. });
  2443.  
  2444. })();

QingJ © 2025

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