Greasy Fork镜像++

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

目前为 2023-09-23 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Greasy Fork镜像++
  3. // @namespace https://github.com/iFelix18
  4. // @version 3.2.25
  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.0/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 emoji 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 = [ /* cSpell: disable-next-line */
  297. '\\bagar((\\.)?io)?\\b', '\\bagma((\\.)?io)?\\b', '\\baimbot\\b', '\\barras((\\.)?io)?\\b', '\\bbot(s)?\\b', '\\bbubble((\\.)?am)?\\b', '\\bcheat(s)?\\b', '\\bdiep((\\.)?io)?\\b', '\\bfreebitco((\\.)?in)?\\b', '\\bgota((\\.)?io)?\\b', '\\bhack(s)?\\b', '\\bkrunker((\\.)?io)?\\b', '\\blostworld((\\.)?io)?\\b', '\\bmoomoo((\\.)?io)?\\b', '\\broblox(\\.com)?\\b', '\\bshell\\sshockers\\b', '\\bshellshock((\\.)?io)?\\b', '\\bshellshockers\\b', '\\bskribbl((\\.)?io)?\\b', '\\bslither((\\.)?io)?\\b', '\\bsurviv((\\.)?io)?\\b', '\\btaming((\\.)?io)?\\b', '\\bvenge((\\.)?io)?\\b', '\\bvertix((\\.)?io)?\\b', '\\bzombs((\\.)?io)?\\b', '\\p{Extended_Pictographic}'
  298. ];
  299.  
  300.  
  301. const settingsCSS = `
  302.  
  303. /*
  304. #greasyfork-plus label::before {
  305. content:'';
  306. display:block;
  307. position:absolute;
  308. left:0;
  309. right:0;
  310. top:0;
  311. bottom:0;
  312. z-index:1;
  313. }
  314. #greasyfork-plus label {
  315. position:relative;
  316. z-index:0;
  317. }
  318. */
  319.  
  320. html {
  321. color: #222;
  322. background: #f9f9f9;
  323. }
  324.  
  325. #greasyfork-plus{
  326. --config-var-display: flex;
  327. }
  328. #greasyfork-plus * {
  329. font-family:Open Sans,sans-serif,Segoe UI Emoji !important;
  330. font-size:12px
  331. }
  332. #greasyfork-plus .section_header[class] {
  333. background-color:#670000;
  334. background-image:linear-gradient(#670000,#900);
  335. border:1px solid transparent;
  336. color:#fff
  337. }
  338. #greasyfork-plus .field_label[class]{
  339. margin-bottom:4px
  340. }
  341. #greasyfork-plus .field_label[class] span{
  342. font-size:95%;
  343. font-style:italic;
  344. opacity:.8;
  345. }
  346. #greasyfork-plus .field_label[class] b{
  347. color:#670000
  348. }
  349. #greasyfork-plus_logging_var[class],
  350. #greasyfork-plus_debugging_var[class] {
  351. --config-var-display: inline-flex;
  352. }
  353. #greasyfork-plus #greasyfork-plus_logging_var label.field_label[class],
  354. #greasyfork-plus #greasyfork-plus_debugging_var label.field_label[class] {
  355. margin-bottom:0;
  356. align-self: center;
  357. }
  358. #greasyfork-plus .config_var[class]{
  359. display:var(--config-var-display);
  360. position: relative;
  361. }
  362. #greasyfork-plus_customBlacklist_var[class],
  363. #greasyfork-plus_hiddenList_var[class],
  364. #greasyfork-plus_milestoneNotification_var[class]{
  365. flex-direction:column;
  366. margin-left:21px;
  367. }
  368.  
  369. #greasyfork-plus_customBlacklist_var[class]::before,
  370. #greasyfork-plus_hiddenList_var[class]::before,
  371. #greasyfork-plus_milestoneNotification_var[class]::before{
  372. /* content: "◉"; */
  373. content: "◎";
  374. position: absolute;
  375. left: auto;
  376. top: auto;
  377. margin-left: -16px;
  378. }
  379. #greasyfork-plus_field_customBlacklist[class],
  380. #greasyfork-plus_field_milestoneNotification[class]{
  381. flex:1;
  382. }
  383. #greasyfork-plus_field_hiddenList[class]{
  384. box-sizing:border-box;
  385. overflow:hidden;
  386. resize:none;
  387. width:100%
  388. }
  389.  
  390. body > #greasyfork-plus_wrapper:only-child {
  391. box-sizing: border-box;
  392. overflow: auto;
  393. max-height: calc(100vh - 72px);
  394. padding: 12px;
  395. /* overflow: auto; */
  396. scrollbar-gutter: both-edges;
  397. background: rgba(127,127,127,0.05);
  398. border: 1px solid rgba(127,127,127,0.5);
  399. }
  400.  
  401. #greasyfork-plus_wrapper > #greasyfork-plus_buttons_holder:last-child {
  402. position: fixed;
  403. bottom: 0;
  404. right: 0;
  405. margin: 0 12px 6px 0;
  406. }
  407.  
  408. #greasyfork-plus .saveclose_buttons[class] {
  409. padding: 4px 14px;
  410. margin: 6px;
  411. }
  412. #greasyfork-plus .section_header_holder#greasyfork-plus_section_2[class] {
  413. position: fixed;
  414. left: 0;
  415. bottom: 0;
  416. margin: 8px;
  417. }
  418. #greasyfork-plus .section_header#greasyfork-plus_section_header_2[class] {
  419. background: #000;
  420. color: #eee;
  421. }
  422.  
  423. #greasyfork-plus_header[class]{
  424. font-size: 16pt;
  425. font-weight: bold;
  426. }
  427.  
  428. `;
  429.  
  430. const pageCSS = `
  431.  
  432. .script-list li.blacklisted{
  433. display:none;
  434. background:#321919;
  435. color:#e8e6e3
  436. }
  437. .script-list li.hidden{
  438. display:none;
  439. background:#321932;
  440. color:#e8e6e3
  441. }
  442. .script-list li.blacklisted a:not(.install-link),.script-list li.hidden a:not(.install-link){
  443. color:#ff8484
  444. }
  445. #script-info.hidden,#script-info.hidden .user-content{
  446. background:#321932;
  447. color:#e8e6e3
  448. }
  449. #script-info.hidden a:not(.install-link):not(.install-help-link){
  450. color:#ff8484
  451. }
  452. #script-info.hidden code{
  453. background-color:transparent
  454. }
  455. html {
  456. --block-btn-color:#111;
  457. --block-btn-bgcolor:#eee;
  458. }
  459. #script-info.hidden, #script-info.hidden .user-content {
  460. --block-btn-color:#eee;
  461. --block-btn-bgcolor:#111;
  462. }
  463.  
  464. [style-54998]{
  465. float:right;
  466. font-size: 70%;
  467. text-decoration:none;
  468. }
  469.  
  470. [style-16377]{
  471. cursor:pointer;
  472. font-size:70%;
  473. white-space:nowrap;
  474. border: 1px solid #888;
  475. background: var(--block-btn-bgcolor, #eee);
  476. color: var(--block-btn-color);
  477. border-radius: 4px;
  478. padding: 0px 6px;
  479. margin: 0 8px;
  480. }
  481. [style-77329] {
  482. cursor: pointer;
  483. margin-left: 1ex;
  484. white-space: nowrap;
  485. float: right;
  486. border: 1px solid #888;
  487. background: var(--block-btn-bgcolor, #eee);
  488. color: var(--block-btn-color);
  489. border-radius: 4px;
  490. padding: 0px 6px;
  491. }
  492.  
  493. a#hyperlink-35389,
  494. a#hyperlink-40361,
  495. a#hyperlink-35389:visited,
  496. a#hyperlink-40361:visited,
  497. a#hyperlink-35389:hover,
  498. a#hyperlink-40361:hover,
  499. a#hyperlink-35389:focus,
  500. a#hyperlink-40361:focus,
  501. a#hyperlink-35389:active,
  502. a#hyperlink-40361:active {
  503.  
  504. border: none !important;
  505. outline: none !important;
  506. box-shadow: none !important;
  507. appearance: none !important;
  508. background: none !important;
  509. color:inherit !important;
  510. }
  511.  
  512. a#hyperlink-35389{
  513. opacity: var(--hyperlink-blacklisted-option-opacity);
  514.  
  515. }
  516. a#hyperlink-40361{
  517. opacity: var(--hyperlink-hidden-option-opacity);
  518. }
  519.  
  520.  
  521. html {
  522.  
  523. --hyperlink-blacklisted-option-opacity: 0.5;
  524. --hyperlink-hidden-option-opacity: 0.5;
  525. }
  526.  
  527.  
  528. .list-option.list-current[class] > a[href] {
  529.  
  530. text-decoration:none;
  531. }
  532.  
  533. html {
  534. --blacklisted-display: none;
  535. --hidden-display: none;
  536. }
  537.  
  538. [blacklisted-shown] {
  539. --blacklisted-display: list-item;
  540. --hyperlink-blacklisted-option-opacity: 1;
  541. }
  542. [hidden-shown] {
  543. --hidden-display: list-item;
  544. --hyperlink-hidden-option-opacity: 1;
  545. }
  546.  
  547. .script-list li.blacklisted{
  548. display: var(--blacklisted-display);
  549.  
  550. }
  551.  
  552. .script-list li.hidden{
  553. display: var(--hidden-display);
  554.  
  555. }
  556.  
  557. .install-link.install-status-checking,
  558. .install-link.install-status-checking:visited,
  559. .install-link.install-status-checking:active,
  560. .install-link.install-status-checking:hover,
  561. .install-help-link.install-status-checking {
  562. background-color: #405458;
  563. }
  564.  
  565. div.previewable{
  566. display: flex;
  567. flex-direction: column;
  568. }
  569. .script-version-ainfo-span {
  570. align-self:end;
  571. font-size: 90%;
  572. padding: 4px 8px;
  573. margin: 0;
  574. }
  575. [style*="display:"] + .script-version-ainfo-span{
  576. display: none;
  577. }
  578.  
  579.  
  580. /* Greasy Fork镜像 Enhance - Flat Layout */
  581.  
  582. [greasyfork-enhance-k37*="|flat-layout|"] ol.script-list > li > article > h2 {
  583. width: 0;
  584. flex-grow: 1;
  585. flex-basis: 60%;
  586. }
  587.  
  588. [greasyfork-enhance-k37*="|flat-layout|"] ol.script-list > li > article > div.script-meta-block {
  589. width: auto;
  590. flex-basis: 40%;
  591. flex-shrink: 0;
  592. flex-grow: 0;
  593. }
  594.  
  595. [greasyfork-enhance-k37*="|flat-layout|"] .script-list li:not(.ad-entry) {
  596. padding: 1em;
  597. margin: 0;
  598. }
  599.  
  600. [greasyfork-enhance-k37*="|flat-layout|"] .script-list li:not(.ad-entry) article {
  601. padding: 0;
  602. margin: 0;
  603. }
  604.  
  605. `
  606.  
  607. const window = {};
  608.  
  609. /** @param {typeof WinComm.createInstance} createInstance */
  610. function contentScriptText(shObject, createInstance) {
  611.  
  612. /*
  613. *
  614.  
  615. return new Promise((resolve, reject) => {
  616. const external = unsafeWindow.external;
  617. console.log(334, external)
  618. const scriptHandler = GM.info.scriptHandler;
  619. if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey' ) {
  620. external.Violentmonkey.isInstalled(name, namespace).then((data) => resolve(data));
  621. return;
  622. }
  623.  
  624. if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') {
  625. external.Tampermonkey.isInstalled(name, namespace, (data) => {
  626. (data.installed) ? resolve(data.version) : resolve();
  627. });
  628. return;
  629. }
  630.  
  631. resolve();
  632. });
  633.  
  634. */
  635.  
  636. const setScriptOnDisabled = async (style) => {
  637.  
  638. try {
  639. const pd = Object.getOwnPropertyDescriptor(style.constructor.prototype, 'disabled');
  640. const { get, set } = pd;
  641. Object.defineProperty(style, 'disabled', {
  642. get() {
  643. return get.call(this);
  644. },
  645. set(nv) {
  646. let r = set.call(this, nv);
  647. Promise.resolve().then(chHead);
  648. return r;
  649. }
  650. })
  651. } catch (e) {
  652.  
  653. }
  654. };
  655.  
  656. document.addEventListener('style-s48', function (evt) {
  657. const target = (evt || 0).target || 0;
  658. if (!target) return;
  659. setScriptOnDisabled(target)
  660.  
  661. }, true);
  662.  
  663.  
  664. const isScriptEnabled = (style) => {
  665.  
  666. if (style instanceof HTMLStyleElement) {
  667. if (!style.hasAttribute('s48')) {
  668. style.setAttribute('s48', '');
  669. style.dispatchEvent(new CustomEvent('style-s48'));
  670. // setScriptOnDisabled(style);
  671. }
  672. return style.disabled !== true;
  673. }
  674. return false;
  675. }
  676. const chHead = () => {
  677. let p = [];
  678. if (isScriptEnabled(document.getElementById('greasyfork-enhance-basic')))
  679. p.push('basic');
  680. if (isScriptEnabled(document.getElementById('greasyfork-enhance-flat-layout')))
  681. p.push('flat-layout');
  682. if (isScriptEnabled(document.getElementById('greasyfork-enhance-animation')))
  683. p.push('animation');
  684. if (p.length >= 1)
  685. document.documentElement.setAttribute('greasyfork-enhance-k37', `|${p.join('|')}|`);
  686. else
  687. document.documentElement.removeAttribute('greasyfork-enhance-k37');
  688. }
  689. const moHead = new MutationObserver(chHead);
  690. moHead.observe(document.head, { subtree: false, childList: true });
  691. chHead();
  692.  
  693.  
  694. const { scriptHandler, scriptName, scriptVersion, scriptNamespace, communicationId } = shObject;
  695.  
  696. const wincomm = createInstance(communicationId);
  697.  
  698. const external = window.external;
  699.  
  700. if (external[scriptHandler]) 1;
  701. else if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey') scriptHandler = 'Violentmonkey';
  702. else if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') scriptHandler = 'Tampermonkey';
  703.  
  704. const manager = external[scriptHandler];
  705.  
  706. if (!manager) {
  707.  
  708. wincomm.send('userScriptManagerNotDetected', {
  709. code: 1
  710. });
  711. return;
  712.  
  713. }
  714.  
  715.  
  716. const pnIsInstalled2 = (type, scriptName, scriptNamespace) => new Promise((resolve, reject) => {
  717. const result = manager.isInstalled(scriptName, scriptNamespace);
  718. if (result instanceof Promise) {
  719. result.then((result) => resolve({
  720. type,
  721. result: typeof result === 'string' ? { version: result } : result
  722. })).catch(reject);
  723. } else {
  724. resolve({
  725. type,
  726. result: typeof result === 'string' ? { version: result } : result
  727. })
  728. }
  729.  
  730. }).catch(console.warn);
  731.  
  732.  
  733. const pnIsInstalled3 = (type, scriptName, scriptNamespace) => new Promise((resolve, reject) => {
  734. try {
  735. manager.isInstalled(scriptName, scriptNamespace, (result) => {
  736. resolve({
  737. type,
  738. result: typeof result === 'string' ? { version: result } : result
  739. })
  740.  
  741. });
  742. } catch (e) {
  743. reject(e);
  744. }
  745. }).catch(console.warn);
  746.  
  747.  
  748.  
  749. const enableScriptInstallChecker = (r) => {
  750.  
  751. const { type, result } = r;
  752. let version = result.version;
  753. // console.log(type, result, version)
  754. if (version !== scriptVersion) return;
  755.  
  756. const pnIsInstalled = type < 25 ? pnIsInstalled2 : pnIsInstalled3;
  757.  
  758. wincomm.hook('_$GreasyFork$Msg$OnScriptInstallCheck', {
  759.  
  760. 'installedVersion.req': (d, evt) => {
  761. pnIsInstalled(type, d.data.name, d.data.namespace).then((r) => {
  762. if (r && 'result' in r) {
  763. wincomm.response(evt, 'installedVersion.res', {
  764. version: r.result ? (r.result.version || '') : ''
  765. });
  766. }
  767. })
  768. }
  769.  
  770. });
  771.  
  772. wincomm.send('ready', { type });
  773.  
  774. // console.log('enableScriptInstallChecker', r)
  775.  
  776.  
  777. }
  778.  
  779. const kl = manager.isInstalled.length;
  780.  
  781. if (!(kl === 2 || kl === 3)) return;
  782. const puds = kl === 2 ? [
  783. pnIsInstalled2(21, scriptName, scriptNamespace), // scriptName is GM.info.script.name not GM.info.script.name_i18n
  784. pnIsInstalled2(20, scriptName, '')
  785. ] : [
  786. pnIsInstalled3(31, scriptName, scriptNamespace),
  787. pnIsInstalled3(30, scriptName, '')
  788. ];
  789.  
  790. Promise.all(puds).then((rs) => {
  791. const [r1, r0] = rs;
  792. if (r0 && r0.result && r0.result.version) enableScriptInstallChecker(r0); // '3.1.4'
  793. else if (r1 && r1.result && r1.result.version) enableScriptInstallChecker(r1);
  794. });
  795.  
  796.  
  797.  
  798. // console.log(327, shObject, scriptHandler);
  799.  
  800. }
  801.  
  802.  
  803.  
  804. return { fields, logo, locales, blacklist, settingsCSS, pageCSS, contentScriptText }
  805.  
  806.  
  807.  
  808. })();
  809.  
  810. (async () => {
  811.  
  812. function fixValue(key, def, test) {
  813. return GM.getValue(key, def).then((v) => test(v) || GM.deleteValue(key))
  814. }
  815.  
  816. function numberArr(arrVal) {
  817. if (!arrVal || typeof arrVal.length !== 'number') return [];
  818. return arrVal.filter(e => typeof e === 'number' && !isNaN(e))
  819. }
  820.  
  821. const isScriptFirstUse = await GM.getValue('firstUse', true);
  822. await Promise.all([
  823. fixValue('hiddenList', [], v => v && typeof v === 'object' && typeof v.length === 'number' && (v.length === 0 || typeof v[0] === 'number')),
  824. fixValue('lastMilestone', 0, v => v && typeof v === 'number' && v >= 0)
  825. ])
  826.  
  827. function createRE(t, ...opt) {
  828. try {
  829. return new RegExp(t, ...opt);
  830. } catch (e) { }
  831. return null;
  832. }
  833.  
  834. const useHashedScriptName = true;
  835. const fixLibraryScriptCodeLink = true;
  836. const addAdditionInfoLengthHint = true;
  837.  
  838. const id = 'greasyfork-plus';
  839. const title = `${GM.info.script.name} v${GM.info.script.version} Settings`;
  840. const fields = mWindow.fields;
  841. const logo = mWindow.logo;
  842. const nonLatins = /[^\p{Script=Latin}\p{Script=Common}\p{Script=Inherited}]/gu;
  843. const blacklist = createRE((mWindow.blacklist || []).join('|'), 'giu');
  844. const hiddenList = numberArr(await GM.getValue('hiddenList', []));
  845. const lang = document.documentElement.lang;
  846. const locales = mWindow.locales;
  847.  
  848. function hiddenListStrToArr(str) {
  849. if (!str || typeof str !== 'string') str = '';
  850. return [...new Set(str ? numberArr(str.split(',').map(e => parseInt(e))) : [])];
  851. }
  852.  
  853. const gmc = new GM_config({
  854. id,
  855. title,
  856. fields,
  857. css: mWindow.settingsCSS,
  858. events: {
  859. init: () => {
  860. gmc.initializedResolve && gmc.initializedResolve();
  861. gmc.initializedResolve = null;
  862.  
  863. },
  864. /** @param {Document} document */
  865. open: async (document) => {
  866. const textarea = document.querySelector(`#${id}_field_hiddenList`);
  867.  
  868. const hiddenSet = new Set(numberArr(await GM.getValue('hiddenList', [])));
  869. if (hiddenSet.size !== 0) {
  870. const unsavedHiddenList = hiddenListStrToArr(gmc.get('hiddenList'));
  871. const unsavedHiddenSet = new Set(unsavedHiddenList);
  872.  
  873. const hasDifferentItems = [...hiddenSet].some(item => !unsavedHiddenSet.has(item)) || [...unsavedHiddenSet].some(item => !hiddenSet.has(item));
  874.  
  875. if (hasDifferentItems) {
  876.  
  877. gmc.fields.hiddenList.value = [...hiddenSet].sort((a, b) => a - b).join(', ');
  878.  
  879. gmc.close();
  880. gmc.open();
  881.  
  882. }
  883.  
  884.  
  885. }
  886.  
  887. const resize = (target) => {
  888. target.style.height = '';
  889. target.style.height = `${target.scrollHeight}px`;
  890. };
  891.  
  892. if (textarea) {
  893. resize(textarea);
  894. textarea.addEventListener('input', (event) => resize(event.target));
  895.  
  896. }
  897.  
  898. document.body.addEventListener('mousedown', (event) => {
  899. if (event.detail > 1 && !event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && !event.defaultPrevented) {
  900. event.preventDefault();
  901. event.stopPropagation();
  902. event.stopImmediatePropagation();
  903. }
  904. }, true);
  905. },
  906. save: async (forgotten) => {
  907.  
  908. if (gmc.isOpen) {
  909. await GM.setValue('hiddenList', hiddenListStrToArr(forgotten.hiddenList));
  910.  
  911. UU.alert('settings saved');
  912. gmc.close();
  913. setTimeout(() => window.location.reload(false), 500);
  914. }
  915. }
  916. }
  917. });
  918. gmc.initialized = new Promise(r => (gmc.initializedResolve = r));
  919. await gmc.initialized.then();
  920. const customBlacklistRE = createRE((gmc.get('customBlacklist') || '').replace(/\s/g, '').split(',').join('|'), 'giu');
  921.  
  922. if (typeof GM.registerMenuCommand === 'function') {
  923. GM.registerMenuCommand('Configure', () => gmc.open());
  924. GM.registerMenuCommand('Reset Everything', () => {
  925. Promise.all([
  926. GM.deleteValue('hiddenList'),
  927. GM.deleteValue('lastMilestone'),
  928. GM.deleteValue('firstUse')
  929. ]).then(() => {
  930. setTimeout(() => window.location.reload(false), 50);
  931. })
  932. });
  933. }
  934.  
  935. UU.init({ id, logging: gmc.get('logging') });
  936. UU.log(nonLatins);
  937. UU.log(blacklist);
  938. UU.log(hiddenList);
  939.  
  940. const _VM = (typeof VM !== 'undefined' ? VM : null) || {
  941. shortcut: {
  942. register: () => { }
  943. }
  944. };
  945.  
  946.  
  947. function fixLibraryCodeURL(code_url) {
  948. code_url = code_url.replace(/\/scripts\/(\d+)(\-[^\/]+)\/code\//, '/scripts/$1/code/');
  949. let qm = code_url.indexOf('?');
  950. let s1 = code_url.substring(0, qm);
  951. let s2 = code_url.substring(qm + 1);
  952. code_url = `${decodeURI(s1)}?${s2}`;
  953. return code_url;
  954. }
  955.  
  956. function setClickToSelect(elm) {
  957. elm.addEventListener('click', function () {
  958. if (window.getSelection() + "" === "") {
  959. if (typeof this.select === 'function') {
  960. this.select();
  961. } else {
  962. const range = document.createRange(); // Create a range object
  963. range.selectNode(this); // Select the text within the element
  964. const selection = window.getSelection(); // Get the selection object
  965. selection.removeAllRanges(); // First clear any existing selections
  966. selection.addRange(range); // Add the new range to the selection
  967. }
  968. }
  969. });
  970. elm.addEventListener('drag', function (evt) {
  971. evt.preventDefault();
  972. });
  973. elm.addEventListener('drop', function (evt) {
  974. evt.preventDefault();
  975. });
  976. elm.addEventListener('dragstart', function (evt) {
  977. evt.preventDefault();
  978. });
  979. }
  980.  
  981. const copyText = typeof (((window.navigator || 0).clipboard || 0).writeText) === 'function' ? (text) => {
  982. navigator.clipboard.writeText(text).then(function () {
  983. //
  984. }).catch(function (err) {
  985. alert("Unable to Copy");
  986. });
  987. } : (text) => {
  988. const textToCopy = document.createElement('strong');
  989. textToCopy.style.position = 'fixed';
  990. textToCopy.style.opacity = '0';
  991. textToCopy.style.top = '-900vh';
  992. textToCopy.textContent = text;
  993. document.body.appendChild(textToCopy);
  994.  
  995. const range = document.createRange(); // Create a range object
  996. range.selectNode(textToCopy); // Select the text within the element
  997.  
  998. const selection = window.getSelection(); // Get the selection object
  999. selection.removeAllRanges(); // First clear any existing selections
  1000. selection.addRange(range); // Add the new range to the selection
  1001.  
  1002. try {
  1003. document.execCommand('copy'); // Try to copy the selected text
  1004. } catch (err) {
  1005. alert("Unable to Copy");
  1006. }
  1007.  
  1008. selection.removeAllRanges(); // Remove the selection range after copying
  1009. textToCopy.remove();
  1010. };
  1011.  
  1012.  
  1013. let avoidDuplication = 0;
  1014. const avoidDuplicationF = () => {
  1015. const p = avoidDuplication;
  1016. avoidDuplication = Date.now();
  1017. if (avoidDuplication - p < 30) return false;
  1018. return true;
  1019. }
  1020. // https://violentmonkey.github.io/vm-shortcut/
  1021. const shortcuts = [
  1022. ['ctrlcmd-alt-keys', () => avoidDuplicationF() && gmc.open()],
  1023. ['ctrlcmd-alt-keyb', () => avoidDuplicationF() && toggleListDisplayingItem('blacklisted')],
  1024. ['ctrlcmd-alt-keyh', () => avoidDuplicationF() && toggleListDisplayingItem('hidden')]
  1025. ]
  1026. for (const [scKey, scFn] of shortcuts) {
  1027. _VM.shortcut.register(scKey, scFn);
  1028. }
  1029.  
  1030. const addSettingsToMenu = () => {
  1031. const nav = document.querySelector('#site-nav > nav')
  1032. if (!nav) return;
  1033.  
  1034. const scriptName = GM.info.script.name;
  1035. const scriptVersion = GM.info.script.version;
  1036. const menu = document.createElement('li');
  1037. menu.classList.add(id);
  1038. menu.setAttribute('alt', `${scriptName} ${scriptVersion}`);
  1039. menu.setAttribute('title', `${scriptName} ${scriptVersion}`);
  1040. const link = document.createElement('a');
  1041. link.setAttribute('href', '#');
  1042. link.textContent = GM.info.script.name;
  1043. menu.appendChild(link);
  1044. nav.insertBefore(menu, document.querySelector('#site-nav > nav > li:first-of-type'));
  1045.  
  1046. menu.addEventListener('click', (e) => {
  1047. e.preventDefault();
  1048. e.stopPropagation();
  1049. e.stopImmediatePropagation();
  1050. gmc.open();
  1051. });
  1052. };
  1053.  
  1054.  
  1055. const toggleListDisplayingItem = (t) => {
  1056.  
  1057. const m = document.documentElement;
  1058.  
  1059. const p = t + '-shown';
  1060. let currentIsShown = m.hasAttribute(p)
  1061. if (!currentIsShown) {
  1062. m.setAttribute(p, '')
  1063. } else {
  1064. m.removeAttribute(p)
  1065. }
  1066.  
  1067. }
  1068.  
  1069. const createListOptionGroup = () => {
  1070.  
  1071. const html = `<div class="list-option-group" id="${id}-options">${GM.info.script.name} Lists:<ul>
  1072. <li class="list-option blacklisted"><a href="#" id="hyperlink-35389"></a></li>
  1073. <li class="list-option hidden"><a href="#" id="hyperlink-40361"></a></li>
  1074. </ul></div>`;
  1075. const firstOptionGroup = document.querySelector('.list-option-groups > div');
  1076. firstOptionGroup && firstOptionGroup.insertAdjacentHTML('beforebegin', html);
  1077.  
  1078. const blacklistedOption = document.querySelector(`#${id}-options li.blacklisted`);
  1079. blacklistedOption && blacklistedOption.addEventListener('click', (evt) => {
  1080. evt.preventDefault();
  1081. toggleListDisplayingItem('blacklisted');
  1082. }, false);
  1083.  
  1084. const hiddenOption = document.querySelector(`#${id}-options li.hidden`);
  1085. hiddenOption && hiddenOption.addEventListener('click', (evt) => {
  1086. evt.preventDefault();
  1087. toggleListDisplayingItem('hidden');
  1088. }, false);
  1089.  
  1090. }
  1091.  
  1092. const addOptions = () => {
  1093.  
  1094. const gn = () => {
  1095.  
  1096. let aBlackList = document.querySelector('#hyperlink-35389');
  1097. let aHidden = document.querySelector('#hyperlink-40361');
  1098. if (!aBlackList || !aHidden) return;
  1099. aBlackList.textContent = `Blacklisted scripts (${document.querySelectorAll('.script-list li.blacklisted').length})`;
  1100. aHidden.textContent = `Hidden scripts (${document.querySelectorAll('.script-list li.hidden').length})`
  1101.  
  1102. }
  1103. const callback = (entries) => {
  1104. if (entries && entries.length >= 1) requestAnimationFrame(gn);
  1105. }
  1106.  
  1107. const setupScriptList = async () => {
  1108. let scriptList;
  1109. let i = 8;
  1110. while (i-- > 0) {
  1111. scriptList = document.querySelector('.script-list li')
  1112. if (scriptList) scriptList = scriptList.closest('.script-list')
  1113. if (scriptList) break;
  1114. await new Promise(r => requestAnimationFrame(r))
  1115. }
  1116. if (!scriptList) return;
  1117. createListOptionGroup();
  1118. const mo = new MutationObserver(callback);
  1119. mo.observe(scriptList, { childList: true, subtree: true });
  1120. gn();
  1121. }
  1122. setupScriptList();
  1123.  
  1124. };
  1125.  
  1126.  
  1127. /**
  1128. * Get script data from Greasy Fork镜像 API
  1129. *
  1130. * @param {number} id Script ID
  1131. * @returns {Promise} Script data
  1132. */
  1133. let networkMP1 = Promise.resolve();
  1134. let networkMP2 = Promise.resolve();
  1135. let previousIsCache = false;
  1136. // let ss = [];
  1137. // var sum = function(nums) {
  1138. // var total = 0;
  1139. // for (var i = 0, len = nums.length; i < len; i++) total += nums[i];
  1140. // return total;
  1141. // };
  1142. const getScriptData = async (id, noCache) => {
  1143. if (!(id >= 0)) return Promise.resolve()
  1144. const url = `https://${window.location.hostname}/scripts/${id}.json`;
  1145. return new Promise((resolve, reject) => {
  1146.  
  1147. networkMP1 = networkMP1.then(() => new Promise(unlock => {
  1148.  
  1149. const maxAgeInSeconds = 900;
  1150. const rd = previousIsCache ? 1 : Math.floor(Math.random() * 80 + 80);
  1151. let fetchStart = 0;
  1152. new Promise(r => setTimeout(r, rd))
  1153. .then(() => {
  1154. fetchStart = Date.now();
  1155. })
  1156. .then(() => fetch(url, noCache ? {
  1157. method: 'GET',
  1158. cache: 'reload',
  1159. credentials: 'omit',
  1160. headers: new Headers({
  1161. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1162. })
  1163. } : {
  1164. method: 'GET',
  1165. cache: 'force-cache',
  1166. credentials: 'omit',
  1167. headers: new Headers({
  1168. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1169. }),
  1170. }))
  1171. .then((response) => {
  1172.  
  1173. let fetchStop = Date.now();
  1174. // const dd = fetchStop - fetchStart;
  1175. // dd (cache) = {min: 1, max: 8, avg: 3.7}
  1176. // dd (normal) = {min: 136, max: 316, avg: 162.62}
  1177.  
  1178. // ss.push(dd)
  1179. // ss.maxValue = Math.max(...ss);
  1180. // ss.minValue = Math.min(...ss);
  1181. // ss.avgValue = sum(ss)/ss.length;
  1182. // console.log(dd)
  1183. // console.log(ss)
  1184. previousIsCache = (fetchStop - fetchStart) < (3.7 + 162.62) / 2;
  1185. UU.log(`${response.status}: ${response.url}`)
  1186. // UU.log(response)
  1187. if (response.ok === true) {
  1188. unlock();
  1189. return response.json()
  1190. }
  1191. if (response.status === 503) {
  1192. return new Promise(r => setTimeout(r, 270 + rd)).then(() => {
  1193. unlock();
  1194. return getScriptData(id, true);
  1195. });
  1196. }
  1197. if (response.status === 404) {
  1198. // script XXXX has been reported and is pending review by a moderator.
  1199. unlock();
  1200. return null
  1201. }
  1202. console.warn(response.status, response);
  1203. new Promise(r => setTimeout(r, 470)).then(unlock); // reload later
  1204. })
  1205. .then((data) => resolve(data))
  1206. .catch((e) => {
  1207. unlock();
  1208. UU.log(id, url)
  1209. console.warn(e)
  1210. // reject(e)
  1211. })
  1212.  
  1213. })).catch(() => { })
  1214.  
  1215. });
  1216. }
  1217.  
  1218. /**
  1219. * Get user data from Greasy Fork镜像 API
  1220. *
  1221. * @param {string} userID User ID
  1222. * @returns {Promise} User data
  1223. */
  1224. const getUserData = (userID, noCache) => {
  1225.  
  1226. if (!(userID >= 0)) return Promise.resolve()
  1227.  
  1228. const url = `https://${window.location.hostname}/users/${userID}.json`;
  1229. return new Promise((resolve, reject) => {
  1230.  
  1231.  
  1232. networkMP2 = networkMP2.then(() => new Promise(unlock => {
  1233.  
  1234. const maxAgeInSeconds = 900;
  1235. const rd = Math.floor(Math.random() * 80 + 80);
  1236.  
  1237. new Promise(r => setTimeout(r, rd))
  1238. .then(() => fetch(url, noCache ? {
  1239. method: 'GET',
  1240. cache: 'reload',
  1241. credentials: 'omit',
  1242. headers: new Headers({
  1243. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1244. })
  1245. } : {
  1246. method: 'GET',
  1247. cache: 'force-cache',
  1248. credentials: 'omit',
  1249. headers: new Headers({
  1250. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  1251. }),
  1252. }))
  1253. .then((response) => {
  1254. UU.log(`${response.status}: ${response.url}`)
  1255. if (response.ok === true) {
  1256. unlock();
  1257. return response.json()
  1258. }
  1259. if (response.status === 503) {
  1260. return new Promise(r => setTimeout(r, 270 + rd)).then(() => {
  1261. unlock();
  1262. return getUserData(userID, true); // reload later
  1263. });
  1264. }
  1265. if (response.status === 404) {
  1266. // user XXXX has been reported and is pending review by a moderator. ????
  1267. unlock();
  1268. return null
  1269. }
  1270. console.warn(response.status, response);
  1271. new Promise(r => setTimeout(r, 470)).then(unlock);
  1272. })
  1273. .then((data) => resolve(data))
  1274. .catch((e) => {
  1275. setTimeout(() => {
  1276. unlock()
  1277. }, 270)
  1278. UU.log(userID, url)
  1279. console.warn(e)
  1280. // reject(e)
  1281. })
  1282.  
  1283.  
  1284.  
  1285. })).catch(() => { })
  1286.  
  1287. });
  1288. }
  1289. const getTotalInstalls = (data) => {
  1290. if (!data || !data.scripts) return;
  1291. return new Promise((resolve, reject) => {
  1292. const totalInstalls = [];
  1293.  
  1294. data.scripts.forEach((element) => {
  1295. totalInstalls.push(parseInt(element.total_installs, 10));
  1296. });
  1297.  
  1298. resolve(totalInstalls.reduce((a, b) => a + b, 0));
  1299. });
  1300. };
  1301.  
  1302.  
  1303. const communicationId = WinComm.newCommunicationId();
  1304. const wincomm = WinComm.createInstance(communicationId);
  1305.  
  1306.  
  1307. const isInstalled = (script) => {
  1308. return new Promise((resolve, reject) => {
  1309.  
  1310. promiseScriptCheck.then(d => {
  1311.  
  1312. if (!d) return null;
  1313.  
  1314. const data = d.data;
  1315. const al = data.type % 10;
  1316. if (al === 0) {
  1317. // no namespace
  1318. resolve([null, script.name, '']);
  1319. } else if (al === 1) {
  1320. // namespace
  1321.  
  1322. if (!script.namespace) {
  1323.  
  1324. getScriptData(script.id).then((script) => {
  1325. resolve([null, script.name, script.namespace]);
  1326. });
  1327.  
  1328. } else {
  1329.  
  1330. resolve([null, script.name, script.namespace]);
  1331. }
  1332.  
  1333. }
  1334.  
  1335.  
  1336. })
  1337.  
  1338.  
  1339. }).then((res) => {
  1340.  
  1341.  
  1342. return new Promise((resolve, reject) => {
  1343.  
  1344. if (!res) return '';
  1345.  
  1346.  
  1347. const [_, name, namespace] = res;
  1348. wincomm.request('installedVersion.req', {
  1349. name,
  1350. namespace
  1351. }).then(d => {
  1352. resolve(d.data.version)
  1353. })
  1354.  
  1355. })
  1356.  
  1357. })
  1358.  
  1359. /*
  1360. const external = unsafeWindow.external;
  1361. const scriptHandler = GM.info.scriptHandler;
  1362. if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey') {
  1363. external.Violentmonkey.isInstalled(name, namespace).then((data) => resolve(data));
  1364. return;
  1365. }
  1366.  
  1367. if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') {
  1368. external.Tampermonkey.isInstalled(name, namespace, (data) => {
  1369. (data.installed) ? resolve(data.version) : resolve();
  1370. });
  1371. return;
  1372. }
  1373. */
  1374.  
  1375.  
  1376. };
  1377.  
  1378. const compareVersions = (v1, v2) => {
  1379. if (!v1 || !v2) return NaN;
  1380. if (v1 === null || v2 === null) return NaN;
  1381. if (v1 === v2) return 0;
  1382.  
  1383. const sv1 = v1.split('.').map((index) => parseInt(index));
  1384. const sv2 = v2.split('.').map((index) => parseInt(index));
  1385.  
  1386. for (let index = 0; index < Math.max(sv1.length, sv2.length); index++) {
  1387. if (isNaN(sv1[index]) || isNaN(sv2[index])) return NaN;
  1388. if (sv1[index] > sv2[index]) return 1;
  1389. if (sv1[index] < sv2[index]) return -1;
  1390. }
  1391.  
  1392. return 0;
  1393. };
  1394.  
  1395.  
  1396. /**
  1397. * Return label for the hide script button
  1398. *
  1399. * @param {boolean} hidden Is hidden
  1400. * @returns {string} Label
  1401. */
  1402. const blockLabel = (hidden) => {
  1403. return hidden ? (locales[lang] ? locales[lang].notHide : locales.en.notHide) : (locales[lang] ? locales[lang].hide : locales.en.hide)
  1404. }
  1405.  
  1406. /**
  1407. * Return label for the install button
  1408. *
  1409. * @param {number} update Update value
  1410. * @returns {string} Label
  1411. */
  1412. const installLabel = (update) => {
  1413. switch (update) {
  1414. case 0: {
  1415. return locales[lang] ? locales[lang].reinstall : locales.en.reinstall
  1416. }
  1417. case 1: {
  1418. return locales[lang] ? locales[lang].update : locales.en.update
  1419. }
  1420. case -1: {
  1421. return locales[lang] ? locales[lang].downgrade : locales.en.downgrade
  1422. }
  1423. default: {
  1424. return locales[lang] ? locales[lang].install : locales.en.install
  1425. }
  1426. }
  1427. }
  1428.  
  1429. const hideBlacklistedScript = (element, list) => {
  1430. if (!element) return;
  1431. const scriptLink = element.querySelector('.script-link')
  1432.  
  1433. const name = scriptLink ? scriptLink.textContent : '';
  1434. const descriptionElem = element.querySelector('.script-description')
  1435. const description = descriptionElem ? descriptionElem.textContent : '';
  1436.  
  1437. if (!name) return;
  1438.  
  1439. switch (list) {
  1440. case 'nonLatins':
  1441. if ((nonLatins.test(name) || nonLatins.test(description)) && !element.classList.contains('blacklisted')) {
  1442. element.classList.add('blacklisted', 'non-latins');
  1443. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1444. let scriptLink = element.querySelector('.script-link');
  1445. if (scriptLink) { scriptLink.textContent += ' (non-latin)'; }
  1446. }
  1447. }
  1448. break;
  1449. case 'blacklist':
  1450. if (blacklist && (blacklist.test(name) || blacklist.test(description)) && !element.classList.contains('blacklisted')) {
  1451. element.classList.add('blacklisted', 'blacklist');
  1452. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1453. let scriptLink = element.querySelector('.script-link');
  1454. if (scriptLink) { scriptLink.textContent += ' (blacklist)'; }
  1455. }
  1456. }
  1457. break;
  1458. case 'customBlacklist': {
  1459. const customBlacklist = customBlacklistRE;
  1460. if (customBlacklist && (customBlacklist.test(name) || customBlacklist.test(description)) && !element.classList.contains('blacklisted')) {
  1461. element.classList.add('blacklisted', 'custom-blacklist');
  1462. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1463. let scriptLink = element.querySelector('.script-link');
  1464. if (scriptLink) { scriptLink.textContent += ' (custom-blacklist)'; }
  1465. }
  1466. }
  1467. break;
  1468. }
  1469. default:
  1470. UU.log('No blacklists');
  1471. break;
  1472. }
  1473. };
  1474.  
  1475. const hideHiddenScript = (element, id, list) => {
  1476. id = +id;
  1477. if (!(id >= 0)) return;
  1478.  
  1479. const isInHiddenList = () => hiddenList.indexOf(id) !== -1;
  1480. const updateScriptLink = (shouldHide) => {
  1481. if (gmc.get('hideHiddenScript') && gmc.get('debugging')) {
  1482. let scriptLink = element.querySelector('.script-link');
  1483. if (scriptLink) {
  1484. if (shouldHide) {
  1485. scriptLink.innerHTML += ' (hidden)';
  1486. } else {
  1487. scriptLink.innerHTML = scriptLink.innerHTML.replace(' (hidden)', '');
  1488. }
  1489. }
  1490. }
  1491. };
  1492.  
  1493. // Check for initial state and set it
  1494. if (isInHiddenList()) {
  1495. element.classList.add('hidden');
  1496. updateScriptLink(true);
  1497. }
  1498.  
  1499. // Add button to hide the script
  1500. const insertButtonHTML = (selector, html) => {
  1501. const target = element.querySelector(selector);
  1502. if (!target) return;
  1503. let p = document.createElement('template');
  1504. p.innerHTML = html;
  1505. target.parentNode.insertBefore(p.content.firstChild, target.nextSibling);
  1506. };
  1507.  
  1508. const isHidden = element.classList.contains('hidden');
  1509. const blockButtonHTML = `<span class=block-button role=button style-16377>${blockLabel(isHidden)}</span>`;
  1510. const blockButtonHeaderHTML = `<span class=block-button role=button style-77329 style="">${blockLabel(isHidden)}</span>`;
  1511.  
  1512. insertButtonHTML('.badge-js, .badge-css', blockButtonHTML);
  1513. insertButtonHTML('header h2', blockButtonHeaderHTML);
  1514.  
  1515. // Add event listener
  1516. const button = element.querySelector('.block-button');
  1517. if (button) {
  1518. button.addEventListener('click', (event) => {
  1519. event.stopPropagation();
  1520. event.stopImmediatePropagation();
  1521.  
  1522. if (!isInHiddenList()) {
  1523. hiddenList.push(id);
  1524. GM.setValue('hiddenList', hiddenList);
  1525.  
  1526. element.classList.add('hidden');
  1527. updateScriptLink(true);
  1528.  
  1529. } else {
  1530. const index = hiddenList.indexOf(id);
  1531. hiddenList.splice(index, 1);
  1532. GM.setValue('hiddenList', hiddenList);
  1533.  
  1534. element.classList.remove('hidden');
  1535. updateScriptLink(false);
  1536. }
  1537.  
  1538. const blockBtn = element.querySelector('.block-button');
  1539. if (blockBtn) blockBtn.textContent = blockLabel(element.classList.contains('hidden'));
  1540. });
  1541. }
  1542. };
  1543.  
  1544. const insertButtonHTML = (element, selector, html) => {
  1545. const target = element.querySelector(selector);
  1546. if (!target) return;
  1547. let p = document.createElement('template');
  1548. p.innerHTML = html;
  1549. let button = p.content.firstChild
  1550. target.parentNode.insertBefore(button, target.nextSibling);
  1551. return button;
  1552. };
  1553.  
  1554. const addInstallButton = (element, url) => {
  1555. return insertButtonHTML(element, '.badge-js, .badge-css', `<a class="install-link" href="${url}" style-54998></a>`);
  1556. };
  1557.  
  1558. async function digestMessage(message, algo) {
  1559. const encoder = new TextEncoder();
  1560. const data = encoder.encode(message);
  1561. const hash = await crypto.subtle.digest(algo, data);
  1562. return hash;
  1563. }
  1564.  
  1565. function qexString(buffer) {
  1566. const byteArray = new Uint8Array(buffer);
  1567. const len = byteArray.length;
  1568. const hexCodes = new Array(len * 2);
  1569. const chars = 'a4b3c5d7e6f9h2t';
  1570. for (let i = 0, j = 0; i < len; i++) {
  1571. const byte = byteArray[i];
  1572. hexCodes[j++] = chars[byte >> 4];
  1573. hexCodes[j++] = chars[byte & 0x0F];
  1574. };
  1575. return hexCodes.join('');
  1576. }
  1577.  
  1578. const showInstallButton = async (scriptID, element) => {
  1579.  
  1580. // if(document.querySelector(`li[data-script-id="${scriptID}"]`))
  1581. let _baseScript = null;
  1582. if (element.nodeName === 'LI' && element.hasAttribute('data-script-id') && element.getAttribute('data-script-id') === `${scriptID}` && element.getAttribute('data-script-language') === 'js') {
  1583.  
  1584. const version = element.getAttribute('data-script-version') || ''
  1585. const name = element.getAttribute('data-script-name') || ''
  1586. // if (!/[^\x00-\x7F]/.test(name)) {
  1587.  
  1588. const scriptName = useHashedScriptName ? qexString(await digestMessage(`${+scriptID} ${version}`, 'SHA-1')).substring(0, 8) : encodeURI(name);
  1589. 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);
  1590. // let scriptFilename = `${encodeURI(name)}.user.js`;
  1591. const scriptFilename = `${scriptName}.user.js`;
  1592. _baseScript = {
  1593. id: +scriptID,
  1594. // name: name,
  1595. code_url: `https://${location.hostname}/scripts/${scriptID}-${token}/code/${scriptFilename}`,
  1596. version: version
  1597. }
  1598. // }
  1599.  
  1600. }
  1601.  
  1602. const baseScript = _baseScript || (await getScriptData(scriptID));
  1603.  
  1604.  
  1605. if ((element.nodeName === 'LI' && element.getAttribute('data-script-type') === 'library') || (baseScript.code_url.includes('.js?version='))) {
  1606.  
  1607. const script = baseScript.code_url.includes('.js?version=') ? baseScript : (await getScriptData(scriptID));
  1608. if (script.code_url.includes('.js?version=')) {
  1609.  
  1610.  
  1611. const code_url = fixLibraryCodeURL(script.code_url);
  1612.  
  1613. const button = addInstallButton(element, code_url);
  1614. button.textContent = `Copy URL`;
  1615. button.addEventListener('click', function (evt) {
  1616.  
  1617. const target = (evt || 0).target;
  1618. if (!target) return;
  1619.  
  1620. let a = target.nodeName === 'A' ? target : target.querySelector('a[href]');
  1621.  
  1622. if (!a) return;
  1623. let href = target.getAttribute('href');
  1624. if (!href) return;
  1625.  
  1626. evt.preventDefault();
  1627.  
  1628. copyText(href);
  1629.  
  1630.  
  1631. });
  1632.  
  1633. }
  1634.  
  1635.  
  1636. } else {
  1637.  
  1638.  
  1639. if (!baseScript || !baseScript.code_url || !baseScript.version) return;
  1640. const button = addInstallButton(element, baseScript.code_url);
  1641. button.classList.add('install-status-checking');
  1642. button.textContent = `${installLabel()} ${baseScript.version}`;
  1643. const script = baseScript && baseScript.name && baseScript.namespace ? baseScript : (await getScriptData(scriptID));
  1644. if (!script) return;
  1645.  
  1646. const installed = await isInstalled(script);
  1647. const version = (
  1648. baseScript.version && script.version && compareVersions(baseScript.version, script.version) === 1
  1649. ) ? baseScript.version : script.version;
  1650.  
  1651. const update = compareVersions(version, installed); // NaN 1 -1 0
  1652. const label = installLabel(update);
  1653. button.textContent = `${label} ${version}`;
  1654. button.classList.remove('install-status-checking');
  1655.  
  1656.  
  1657. }
  1658.  
  1659. }
  1660.  
  1661.  
  1662. const foundScriptList = async (scriptList) => {
  1663.  
  1664. let rid = 0;
  1665. let g = () => {
  1666. if (!scriptList || scriptList.isConnected !== true) return;
  1667.  
  1668. const scriptElements = scriptList.querySelectorAll('li[data-script-id]:not([e8kk])');
  1669.  
  1670. for (const element of scriptElements) {
  1671. element.setAttribute('e8kk', '1');
  1672.  
  1673. const scriptID = +element.getAttribute('data-script-id');
  1674. if (!(scriptID > 0)) continue;
  1675.  
  1676. // blacklisted scripts
  1677. if (gmc.get('nonLatins')) hideBlacklistedScript(element, 'nonLatins');
  1678. if (gmc.get('blacklist')) hideBlacklistedScript(element, 'blacklist');
  1679. if (gmc.get('customBlacklist')) hideBlacklistedScript(element, 'customBlacklist');
  1680.  
  1681. // hidden scripts
  1682. if (gmc.get('hideHiddenScript')) hideHiddenScript(element, scriptID, true);
  1683.  
  1684. // install button
  1685. if (gmc.get('showInstallButton')) {
  1686. showInstallButton(scriptID, element)
  1687. }
  1688. }
  1689.  
  1690. }
  1691. let f = (entries) => {
  1692. const tid = ++rid
  1693. if (entries && entries.length) requestAnimationFrame(() => {
  1694. if (tid === rid) g();
  1695. });
  1696. }
  1697. let mo = new MutationObserver(f);
  1698. mo.observe(scriptList, { subtree: true, childList: true });
  1699.  
  1700. g();
  1701.  
  1702. }
  1703.  
  1704. let promiseScriptCheckResolve = null;
  1705. const promiseScriptCheck = new Promise(resolve => {
  1706. promiseScriptCheckResolve = resolve
  1707. });
  1708.  
  1709. const milestoneNotificationFn = async (o) => {
  1710.  
  1711. const { userLink, userID } = o;
  1712.  
  1713.  
  1714. const milestones = gmc.get('milestoneNotification').replace(/\s/g, '').split(',').map(Number);
  1715.  
  1716. if (!userID) return;
  1717.  
  1718. const userData = await getUserData(+userID.match(/\d+(?=\D)/g));
  1719. if (!userData) return;
  1720.  
  1721. const [totalInstalls, lastMilestone] = await Promise.all([
  1722. getTotalInstalls(userData),
  1723. GM.getValue('lastMilestone', 0)]);
  1724.  
  1725. const milestone = milestones.filter(milestone => totalInstalls >= milestone).pop();
  1726.  
  1727. UU.log(`total installs are "${totalInstalls}", milestone reached is "${milestone}", last milestone reached is "${lastMilestone}"`);
  1728.  
  1729. if (milestone <= lastMilestone) return;
  1730.  
  1731. if (milestone && milestone >= 0) {
  1732.  
  1733.  
  1734. GM.setValue('lastMilestone', milestone);
  1735.  
  1736. const lang = document.documentElement.lang;
  1737. const text = (locales[lang] ? locales[lang].milestone : locales.en.milestone).replace('$1', milestone.toLocaleString());
  1738.  
  1739. if (typeof GM.notification === 'function') {
  1740. GM.notification({
  1741. text,
  1742. title: GM.info.script.name,
  1743. image: logo,
  1744. onclick: () => {
  1745. window.location = `https://${window.location.hostname}${userID}#user-script-list-section`;
  1746. }
  1747. });
  1748. } else {
  1749. UU.alert(text);
  1750. }
  1751.  
  1752. }
  1753.  
  1754. }
  1755. const onReady = async () => {
  1756.  
  1757. try {
  1758.  
  1759. const gminfo = GM.info || 0;
  1760. if (gminfo) {
  1761.  
  1762. const gminfoscript = gminfo.script || 0;
  1763.  
  1764.  
  1765. const scriptHandlerObject = {
  1766. scriptHandler: gminfo.scriptHandler || '',
  1767. scriptName: gminfoscript.name || '', // not name_i18n
  1768. scriptVersion: gminfoscript.version || '',
  1769. scriptNamespace: gminfoscript.namespace || '',
  1770. communicationId
  1771. };
  1772.  
  1773.  
  1774. wincomm.hook('_$GreasyFork$Msg$OnScriptInstallFeedback',
  1775. {
  1776.  
  1777. ready: (d, evt) => promiseScriptCheckResolve(d),
  1778. userScriptManagerNotDetected: (d, evt) => promiseScriptCheckResolve(null),
  1779. 'installedVersion.res': wincomm.handleResponse
  1780.  
  1781.  
  1782. })
  1783.  
  1784.  
  1785. document.head.appendChild(document.createElement('script')).textContent = `;(${mWindow.contentScriptText})(${JSON.stringify(scriptHandlerObject)}, ${WinComm.createInstance});`;
  1786.  
  1787.  
  1788. }
  1789.  
  1790.  
  1791. addSettingsToMenu();
  1792.  
  1793.  
  1794. setTimeout(() => {
  1795. let installBtn = document.querySelector('a[data-script-id][data-script-version]')
  1796. let scriptID = installBtn && installBtn.textContent ? +installBtn.getAttribute('data-script-id') : 0;
  1797. if (scriptID > 0) {
  1798. getScriptData(scriptID, true);
  1799. } else {
  1800.  
  1801.  
  1802. const userLink = document.querySelector('#site-nav .user-profile-link a[href]');
  1803. let userID = userLink ? userLink.getAttribute('href') : '';
  1804.  
  1805. userID = userID ? /users\/(\d+)/.exec(userID) : null;
  1806. if (userID) userID = userID[1];
  1807. if (userID) {
  1808. userID = +userID;
  1809. if (userID > 0) {
  1810. getUserData(userID, true);
  1811. }
  1812. }
  1813.  
  1814.  
  1815. }
  1816. }, 740);
  1817.  
  1818. const userLink = document.querySelector('.user-profile-link a[href]');
  1819. const userID = userLink ? userLink.getAttribute('href') : undefined;
  1820.  
  1821. // blacklisted scripts / hidden scripts / install button
  1822. if (window.location.pathname !== userID && !/discussions/.test(window.location.pathname) && (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript') || gmc.get('showInstallButton'))) {
  1823.  
  1824. const scriptList = document.querySelector('.script-list');
  1825. if (scriptList) {
  1826. foundScriptList(scriptList);
  1827. } else {
  1828. const timeout = Date.now() + 3000;
  1829. /** @type {MutationObserver | null} */
  1830. let mo = null;
  1831. const mutationCallbackForScriptList = () => {
  1832. if (!mo) return;
  1833. const scriptList = document.querySelector('.script-list');
  1834. if (scriptList) {
  1835. mo.disconnect();
  1836. mo.takeRecords();
  1837. mo = null;
  1838. foundScriptList(scriptList);
  1839. } else if (Date.now() > timeout) {
  1840. mo.disconnect();
  1841. mo.takeRecords();
  1842. mo = null;
  1843. }
  1844. }
  1845. mo = new MutationObserver(mutationCallbackForScriptList);
  1846. mo.observe(document, { subtree: true, childList: true });
  1847. }
  1848.  
  1849.  
  1850. // hidden scripts on details page
  1851. const installLinkElement = document.querySelector('#script-info .install-link[data-script-id]');
  1852. if (gmc.get('hideHiddenScript') && installLinkElement) {
  1853. const id = +installLinkElement.getAttribute('data-script-id');
  1854. hideHiddenScript(document.querySelector('#script-info'), id, false);
  1855. }
  1856.  
  1857. // add options and style for blacklisted/hidden scripts
  1858. if (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript')) {
  1859. addOptions();
  1860. UU.addStyle(mWindow.pageCSS);
  1861. }
  1862.  
  1863. if (installLinkElement && location.pathname.includes('/scripts/')) {
  1864.  
  1865. installLinkElement.addEventListener('click', async function (e) {
  1866. if (e && e.isTrusted && location.pathname.includes('/scripts/')) {
  1867.  
  1868. await new Promise(r => setTimeout(r, 800));
  1869. await new Promise(r => window.requestAnimationFrame(r));
  1870. await new Promise(r => setTimeout(r, 100));
  1871. document.dispatchEvent(new Event("DOMContentLoaded"));
  1872. }
  1873. })
  1874. }
  1875. }
  1876.  
  1877. // total installs
  1878. if (gmc.get('showTotalInstalls') && document.querySelector('#user-script-list')) {
  1879. const dailyInstalls = [];
  1880. const totalInstalls = [];
  1881.  
  1882. const dailyInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-daily-installs');
  1883. for (const element of dailyInstallElements) {
  1884. dailyInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));
  1885. }
  1886.  
  1887. const totalInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-total-installs');
  1888. for (const element of totalInstallElements) {
  1889. totalInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));
  1890. }
  1891.  
  1892. const dailyInstallsSum = dailyInstalls.reduce((a, b) => a + b, 0);
  1893. const totalInstallsSum = totalInstalls.reduce((a, b) => a + b, 0);
  1894.  
  1895. const convertLi = (li) => {
  1896.  
  1897. if (!li) return null;
  1898. const a = li.firstElementChild
  1899. if (a === null) return li;
  1900. if (a === li.lastElementChild && a.nodeName === 'A') return a;
  1901.  
  1902.  
  1903. return null;
  1904. }
  1905.  
  1906. const plusSign = document.querySelector('#user-script-list-section a[rel="next"][href*="page="], #user-script-list-section a[rel="prev"][href*="page="]') ? '+' : '';
  1907.  
  1908. const dailyOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(1)'));
  1909. dailyOption && dailyOption.insertAdjacentHTML('beforeend', `<span> (${dailyInstallsSum.toLocaleString()}${plusSign})</span>`);
  1910.  
  1911. const totalOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(2)'));
  1912. totalOption && totalOption.insertAdjacentHTML('beforeend', `<span> (${totalInstallsSum.toLocaleString()}${plusSign})</span>`);
  1913. }
  1914.  
  1915. // milestone notification
  1916. if (gmc.get('milestoneNotification')) {
  1917. milestoneNotificationFn({ userLink, userID });
  1918. }
  1919.  
  1920. if (isScriptFirstUse) GM.setValue('firstUse', false).then(() => {
  1921. gmc.open();
  1922. });
  1923.  
  1924. if (fixLibraryScriptCodeLink) {
  1925.  
  1926.  
  1927. let xpath = "//code[contains(text(), '.js?version=')]";
  1928. let snapshot = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  1929.  
  1930. for (let i = 0; i < snapshot.snapshotLength; i++) {
  1931. let element = snapshot.snapshotItem(i);
  1932. if (element.firstElementChild) continue;
  1933. element.textContent = element.textContent.replace(/\bhttps:\/\/(greasyfork|sleazyfork)\.org\/scripts\/\d+\-[^\/]+\/code\/[^\.]+\.js\?version=\d+\b/, (_) => {
  1934. return fixLibraryCodeURL(_);
  1935. });
  1936. element.parentNode.insertBefore(document.createTextNode('\u200B'), element);
  1937. element.style.display = 'inline-flex';
  1938. setClickToSelect(element);
  1939. }
  1940.  
  1941.  
  1942. }
  1943.  
  1944.  
  1945.  
  1946.  
  1947. if (addAdditionInfoLengthHint && location.pathname.includes('/scripts/') && location.pathname.includes('/versions')) {
  1948.  
  1949. function contentLength(text) {
  1950. return text.replace(/\n/g, ' ').length;
  1951. }
  1952. function contentLengthMax() {
  1953. return 50000;
  1954. }
  1955. let _spanContent = null;
  1956. function updateText(ainfo, span) {
  1957. const value = ainfo.value;
  1958. if (typeof value !== 'string') return;
  1959.  
  1960. if (_spanContent !== value) {
  1961. _spanContent = value;
  1962. span.textContent = `Text Length: ${contentLength(value)} / ${contentLengthMax()}`;
  1963.  
  1964.  
  1965. }
  1966. }
  1967. function onChange(evt) {
  1968. let ainfo = (evt || 0).target;
  1969. if (!ainfo) return;
  1970. let span = ainfo.parentNode.querySelector('.script-version-ainfo-span');
  1971. if (!span) return;
  1972.  
  1973. updateText(ainfo, span);
  1974.  
  1975. }
  1976. function kbEvent(evt) {
  1977. Promise.resolve().then(() => {
  1978. onChange(evt);
  1979.  
  1980. })
  1981. }
  1982. for (const ainfo of document.querySelectorAll('textarea[id^="script-version-additional-info"]')) {
  1983. let span = document.createElement('span');
  1984. span.classList.add('script-version-ainfo-span');
  1985. ainfo.addEventListener('change', onChange, false);
  1986. ainfo.addEventListener('keydown', kbEvent, false);
  1987. ainfo.addEventListener('keypress', kbEvent, false);
  1988. ainfo.addEventListener('keyup', kbEvent, false);
  1989. updateText(ainfo, span);
  1990. ainfo.parentNode.insertBefore(span, ainfo.nextSibling);
  1991.  
  1992.  
  1993. }
  1994.  
  1995.  
  1996. }
  1997.  
  1998. } catch (e) {
  1999. console.log(e);
  2000. }
  2001.  
  2002.  
  2003.  
  2004. }
  2005.  
  2006.  
  2007.  
  2008.  
  2009. Promise.resolve().then(() => {
  2010. if (document.readyState !== 'loading') {
  2011. onReady();
  2012. } else {
  2013. window.addEventListener("DOMContentLoaded", onReady, false);
  2014. }
  2015. });
  2016.  
  2017. })();

QingJ © 2025

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