Greasy Fork++

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

当前为 2023-08-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Greasy Fork++
  3. // @author CY Fung <https://greasyfork.org/users/371179> & Davide <iFelix18@protonmail.com>
  4. // @namespace https://github.com/iFelix18
  5. // @icon https://www.google.com/s2/favicons?domain=https://greasyfork.org
  6. // @description Adds various features and improves the Greasy Fork experience
  7. // @description:de Fügt verschiedene Funktionen hinzu und verbessert das Greasy Fork-Erlebnis
  8. // @description:es Agrega varias funciones y mejora la experiencia de Greasy Fork
  9. // @description:fr Ajoute diverses fonctionnalités et améliore l'expérience Greasy Fork
  10. // @description:it Aggiunge varie funzionalità e migliora l'esperienza di Greasy Fork
  11. // @description:ru Добавляет различные функции и улучшает работу с Greasy Fork
  12. // @description:zh-CN 添加各种功能并改善 Greasy Fork 体验
  13. // @description:zh-TW 加入多種功能並改善Greasy Fork的體驗
  14. // @description:ja Greasy Forkの体験を向上させる様々な機能を追加
  15. // @description:ko Greasy Fork 경험을 향상시키고 다양한 기능을 추가
  16. // @copyright 2023, CY Fung (https://greasyfork.org/users/371179); 2021, Davide (https://github.com/iFelix18)
  17. // @license MIT
  18. // @version 3.1.3
  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.3.0/dist/index.min.js
  21. // @match *://greasyfork.org/*
  22. // @match *://sleazyfork.org/*
  23. // @connect greasyfork.org
  24. // @compatible chrome
  25. // @compatible edge
  26. // @compatible firefox
  27. // @compatible safari
  28. // @compatible brave
  29. // @grant GM.deleteValue
  30. // @grant GM.getValue
  31. // @grant GM.notification
  32. // @grant GM.registerMenuCommand
  33. // @grant GM.setValue
  34. // @run-at document-start
  35. // @inject-into content
  36. // ==/UserScript==
  37.  
  38. /* global GM_config, VM, GM */
  39.  
  40. // -------- UU Fucntion - original code: https://fastly.jsdelivr.net/npm/@ifelix18/utils@6.5.0/lib/index.min.js --------
  41. // optimized by CY Fung to remove $ dependency and observe creation
  42. const UU = (function () {
  43. const scriptName = GM.info.script.name;
  44. const scriptVersion = GM.info.script.version;
  45. const authorMatch = /^(.*?)\s<\S[^\s@]*@\S[^\s.]*\.\S+>$/.exec(GM.info.script.author);
  46. const author = authorMatch ? authorMatch[1] : GM.info.script.author;
  47. let scriptId = scriptName.toLowerCase().replace(/\s/g, "-");
  48. let loggingEnabled = false;
  49.  
  50. const log = (message) => {
  51. if (loggingEnabled) {
  52. console.log(`${scriptName}:`, message);
  53. }
  54. };
  55.  
  56. const error = (message) => {
  57. console.error(`${scriptName}:`, message);
  58. };
  59.  
  60. const warn = (message) => {
  61. console.warn(`${scriptName}:`, message);
  62. };
  63.  
  64. const alert = (message) => {
  65. window.alert(`${scriptName}: ${message}`);
  66. };
  67.  
  68. /** @param {string} text */
  69. const short = (text, length) => {
  70. const s = text.split(" ");
  71. const l = Number(length);
  72. return s.length > l
  73. ? `${s.slice(0, l).join(" ")} [...]`
  74. : text;
  75. };
  76.  
  77. const addStyle = (css) => {
  78. const head = document.head || document.querySelector("head");
  79. const style = document.createElement("style");
  80. style.textContent = css;
  81. head.appendChild(style);
  82. };
  83.  
  84. const init = async (options = {}) => {
  85. scriptId = options.id || scriptId;
  86. loggingEnabled = typeof options.logging === "boolean" ? options.logging : false;
  87. console.info(
  88. `%c${scriptName}\n%cv${scriptVersion}${author ? ` by ${author}` : ""} is running!`,
  89. "color:red;font-weight:700;font-size:18px;text-transform:uppercase",
  90. ""
  91. );
  92. };
  93.  
  94. return {
  95. init,
  96. log,
  97. error,
  98. warn,
  99. alert,
  100. short,
  101. addStyle
  102. };
  103. })();
  104.  
  105. // -------- UU Fucntion - original code: https://fastly.jsdelivr.net/npm/@ifelix18/utils@6.5.0/lib/index.min.js --------
  106.  
  107.  
  108. const mWindow = (() => {
  109.  
  110.  
  111. const fields = {
  112. hideBlacklistedScripts: {
  113. 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>',
  114. section: ['Features'],
  115. labelPos: 'right',
  116. type: 'checkbox',
  117. default: true
  118. },
  119. hideHiddenScript: {
  120. 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',
  121. labelPos: 'right',
  122. type: 'checkbox',
  123. default: true
  124. },
  125. showInstallButton: {
  126. label: 'Install button:<br><span>Add to the scripts list a button to install the script directly</span>',
  127. labelPos: 'right',
  128. type: 'checkbox',
  129. default: true
  130. },
  131. showTotalInstalls: {
  132. label: 'Installations:<br><span>Shows the number of daily and total installations on the user profile</span>',
  133. labelPos: 'right',
  134. type: 'checkbox',
  135. default: true
  136. },
  137. milestoneNotification: {
  138. 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>',
  139. labelPos: 'left',
  140. type: 'text',
  141. title: 'Separate milestones with a comma!',
  142. size: 150,
  143. default: '10, 100, 500, 1000, 2500, 5000, 10000, 100000, 1000000'
  144. },
  145. nonLatins: {
  146. label: 'Non-Latin:<br><span>This list blocks all scripts with non-Latin characters in the title/description</span>',
  147. section: ['Lists'],
  148. labelPos: 'right',
  149. type: 'checkbox',
  150. default: false // not true
  151. },
  152. blacklist: {
  153. 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>',
  154. labelPos: 'right',
  155. type: 'checkbox',
  156. default: true
  157. },
  158. customBlacklist: {
  159. 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>',
  160. labelPos: 'left',
  161. type: 'text',
  162. title: 'Separate unwanted words with a comma!',
  163. size: 150,
  164. default: ''
  165. },
  166. hiddenList: {
  167. label: 'Hidden Scripts:<br><span>Block individual undesired scripts by their unique IDs<br>Separate IDs with a comma</span>',
  168. labelPos: 'left',
  169. type: 'textarea',
  170. title: 'Separate IDs with a comma!',
  171. default: '',
  172. save: false
  173. },
  174. logging: {
  175. label: 'Logging',
  176. section: ['Developer options'],
  177. labelPos: 'right',
  178. type: 'checkbox',
  179. default: false
  180. },
  181. debugging: {
  182. label: 'Debugging',
  183. labelPos: 'right',
  184. type: 'checkbox',
  185. default: false
  186. }
  187. }
  188.  
  189. const logo = 'data:image/png;base64,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'
  190.  
  191. const locales = { /* cSpell: disable */
  192. de: {
  193. downgrade: 'Auf zurückstufen',
  194. hide: '❌ Dieses skript ausblenden',
  195. install: 'Installieren',
  196. notHide: '✔️ Dieses skript nicht ausblenden',
  197. milestone: 'Herzlichen Glückwunsch, Ihre Skripte haben den Meilenstein von insgesamt $1 Installationen überschritten!',
  198. reinstall: 'Erneut installieren',
  199. update: 'Auf aktualisieren'
  200. },
  201. en: {
  202. downgrade: 'Downgrade to',
  203. hide: '❌ Hide this script',
  204. install: 'Install',
  205. notHide: '✔️ Not hide this script',
  206. milestone: 'Congrats, your scripts got over the milestone of $1 total installs!',
  207. reinstall: 'Reinstall',
  208. update: 'Update to'
  209. },
  210. es: {
  211. downgrade: 'Degradar a',
  212. hide: '❌ Ocultar este script',
  213. install: 'Instalar',
  214. notHide: '✔️ No ocultar este script',
  215. milestone: '¡Felicidades, sus scripts superaron el hito de $1 instalaciones totales!',
  216. reinstall: 'Reinstalar',
  217. update: 'Actualizar a'
  218. },
  219. fr: {
  220. downgrade: 'Revenir à',
  221. hide: '❌ Cacher ce script',
  222. install: 'Installer',
  223. notHide: '✔️ Ne pas cacher ce script',
  224. milestone: 'Félicitations, vos scripts ont franchi le cap des $1 installations au total!',
  225. reinstall: 'Réinstaller',
  226. update: 'Mettre à'
  227. },
  228. it: {
  229. downgrade: 'Riporta a',
  230. hide: '❌ Nascondi questo script',
  231. install: 'Installa',
  232. notHide: '✔️ Non nascondere questo script',
  233. milestone: 'Congratulazioni, i tuoi script hanno superato il traguardo di $1 installazioni totali!',
  234. reinstall: 'Reinstalla',
  235. update: 'Aggiorna a'
  236. },
  237. ru: {
  238. downgrade: 'Откатить до',
  239. hide: '❌ Скрыть этот скрипт',
  240. install: 'Установить',
  241. notHide: '✔️ Не скрывать этот сценарий',
  242. milestone: 'Поздравляем, ваши скрипты преодолели рубеж в $1 установок!',
  243. reinstall: 'Переустановить',
  244. update: 'Обновить до'
  245. },
  246. 'zh-CN': {
  247. downgrade: '降级到',
  248. hide: '❌ 隐藏此脚本',
  249. install: '安装',
  250. notHide: '✔️ 不隐藏此脚本',
  251. milestone: '恭喜,您的脚本超过了 $1 次总安装的里程碑!',
  252. reinstall: '重新安装',
  253. update: '更新到'
  254. },
  255. 'zh-TW': {
  256. downgrade: '降級至',
  257. hide: '❌ 隱藏此腳本',
  258. install: '安裝',
  259. notHide: '✔️ 不隱藏此腳本',
  260. milestone: '恭喜,您的腳本安裝總數已超過 $1!',
  261. reinstall: '重新安裝',
  262. update: '更新至'
  263. },
  264. 'ja': {
  265. downgrade: 'ダウングレードする',
  266. hide: '❌ このスクリプトを隠す',
  267. install: 'インストール',
  268. notHide: '✔️ このスクリプトを隠さない',
  269. milestone: 'おめでとうございます、あなたのスクリプトの合計インストール回数が $1 を超えました!',
  270. reinstall: '再インストール',
  271. update: '更新する'
  272. },
  273. 'ko': {
  274. downgrade: '다운그레이드하기',
  275. hide: '❌ 이 스크립트 숨기기',
  276. install: '설치',
  277. notHide: '✔️ 이 스크립트 숨기지 않기',
  278. milestone: '축하합니다, 스크립트의 총 설치 횟수가 $1을 넘었습니다!',
  279. reinstall: '재설치',
  280. update: '업데이트하기'
  281. }
  282.  
  283. };
  284.  
  285. const blacklist = [ /* cSpell: disable-next-line */
  286. '\\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}'
  287. ];
  288.  
  289.  
  290. const settingsCSS = `
  291.  
  292. /*
  293. #greasyfork-plus label::before {
  294. content:'';
  295. display:block;
  296. position:absolute;
  297. left:0;
  298. right:0;
  299. top:0;
  300. bottom:0;
  301. z-index:1;
  302. }
  303. #greasyfork-plus label {
  304. position:relative;
  305. z-index:0;
  306. }
  307. */
  308. html {
  309. color: #222;
  310. background: #f9f9f9;
  311. }
  312.  
  313. #greasyfork-plus{
  314. --config-var-display: flex;
  315. }
  316. #greasyfork-plus * {
  317. font-family:Open Sans,sans-serif,Segoe UI Emoji !important;
  318. font-size:12px
  319. }
  320. #greasyfork-plus .section_header[class] {
  321. background-color:#670000;
  322. background-image:linear-gradient(#670000,#900);
  323. border:1px solid transparent;
  324. color:#fff
  325. }
  326. #greasyfork-plus .field_label[class]{
  327. margin-bottom:4px
  328. }
  329. #greasyfork-plus .field_label[class] span{
  330. font-size:95%;
  331. font-style:italic;
  332. opacity:.8;
  333. }
  334. #greasyfork-plus .field_label[class] b{
  335. color:#670000
  336. }
  337. #greasyfork-plus_logging_var[class],
  338. #greasyfork-plus_debugging_var[class] {
  339. --config-var-display: inline-flex;
  340. }
  341. #greasyfork-plus #greasyfork-plus_logging_var label.field_label[class],
  342. #greasyfork-plus #greasyfork-plus_debugging_var label.field_label[class] {
  343. margin-bottom:0;
  344. align-self: center;
  345. }
  346. #greasyfork-plus .config_var[class]{
  347. display:var(--config-var-display);
  348. }
  349. #greasyfork-plus_customBlacklist_var[class],
  350. #greasyfork-plus_hiddenList_var[class],
  351. #greasyfork-plus_milestoneNotification_var[class]{
  352. flex-direction:column;
  353. margin-left:21px;
  354. }
  355.  
  356. #greasyfork-plus_customBlacklist_var[class]::before,
  357. #greasyfork-plus_hiddenList_var[class]::before,
  358. #greasyfork-plus_milestoneNotification_var[class]::before{
  359. /* content: "◉"; */
  360. content: "◎";
  361. position: absolute;
  362. left: auto;
  363. top: auto;
  364. margin-left: -16px;
  365. }
  366. #greasyfork-plus_field_customBlacklist[class],
  367. #greasyfork-plus_field_milestoneNotification[class]{
  368. flex:1;
  369. }
  370. #greasyfork-plus_field_hiddenList[class]{
  371. box-sizing:border-box;
  372. overflow:hidden;
  373. resize:none;
  374. width:100%
  375. }
  376.  
  377. body > #greasyfork-plus_wrapper:only-child {
  378. box-sizing: border-box;
  379. overflow: auto;
  380. max-height: calc(100vh - 72px);
  381. padding: 12px;
  382. /* overflow: auto; */
  383. scrollbar-gutter: both-edges;
  384. background: rgba(127,127,127,0.05);
  385. border: 1px solid rgba(127,127,127,0.5);
  386. }
  387.  
  388. #greasyfork-plus_wrapper > #greasyfork-plus_buttons_holder:last-child {
  389. position: fixed;
  390. bottom: 0;
  391. right: 0;
  392. margin: 0 12px 6px 0;
  393. }
  394.  
  395. #greasyfork-plus .saveclose_buttons[class] {
  396. padding: 4px 14px;
  397. margin: 6px;
  398. }
  399. #greasyfork-plus .section_header_holder#greasyfork-plus_section_2[class] {
  400. position: fixed;
  401. left: 0;
  402. bottom: 0;
  403. margin: 8px;
  404. }
  405. #greasyfork-plus .section_header#greasyfork-plus_section_header_2[class] {
  406. background: #000;
  407. color: #eee;
  408. }
  409.  
  410. #greasyfork-plus_header[class]{
  411. font-size: 16pt;
  412. font-weight: bold;
  413. }
  414.  
  415. `;
  416.  
  417. const pageCSS = `
  418.  
  419. .script-list li.blacklisted{
  420. display:none;
  421. background:#321919;
  422. color:#e8e6e3
  423. }
  424. .script-list li.hidden{
  425. display:none;
  426. background:#321932;
  427. color:#e8e6e3
  428. }
  429. .script-list li.blacklisted a:not(.install-link),.script-list li.hidden a:not(.install-link){
  430. color:#ff8484
  431. }
  432. #script-info.hidden,#script-info.hidden .user-content{
  433. background:#321932;
  434. color:#e8e6e3
  435. }
  436. #script-info.hidden a:not(.install-link):not(.install-help-link){
  437. color:#ff8484
  438. }
  439. #script-info.hidden code{
  440. background-color:transparent
  441. }
  442. html {
  443. --block-btn-color:#111;
  444. --block-btn-bgcolor:#eee;
  445. }
  446. #script-info.hidden, #script-info.hidden .user-content {
  447. --block-btn-color:#eee;
  448. --block-btn-bgcolor:#111;
  449. }
  450.  
  451. [style-54998]{
  452. float:right;
  453. font-size: 70%;
  454. text-decoration:none;
  455. }
  456.  
  457. [style-16377]{
  458. cursor:pointer;
  459. font-size:70%;
  460. white-space:nowrap;
  461. border: 1px solid #888;
  462. background: var(--block-btn-bgcolor, #eee);
  463. color: var(--block-btn-color);
  464. border-radius: 4px;
  465. padding: 0px 6px;
  466. margin: 0 8px;
  467. }
  468. [style-77329] {
  469. cursor: pointer;
  470. margin-left: 1ex;
  471. white-space: nowrap;
  472. float: right;
  473. border: 1px solid #888;
  474. background: var(--block-btn-bgcolor, #eee);
  475. color: var(--block-btn-color);
  476. border-radius: 4px;
  477. padding: 0px 6px;
  478. }
  479.  
  480. a#hyperlink-35389,
  481. a#hyperlink-40361,
  482. a#hyperlink-35389:visited,
  483. a#hyperlink-40361:visited,
  484. a#hyperlink-35389:hover,
  485. a#hyperlink-40361:hover,
  486. a#hyperlink-35389:focus,
  487. a#hyperlink-40361:focus,
  488. a#hyperlink-35389:active,
  489. a#hyperlink-40361:active {
  490.  
  491. border: none !important;
  492. outline: none !important;
  493. box-shadow: none !important;
  494. appearance: none !important;
  495. background: none !important;
  496. color:inherit !important;
  497. }
  498.  
  499. a#hyperlink-35389{
  500. opacity: var(--hyperlink-blacklisted-option-opacity);
  501.  
  502. }
  503. a#hyperlink-40361{
  504. opacity: var(--hyperlink-hidden-option-opacity);
  505. }
  506.  
  507.  
  508. html {
  509.  
  510. --hyperlink-blacklisted-option-opacity: 0.5;
  511. --hyperlink-hidden-option-opacity: 0.5;
  512. }
  513.  
  514.  
  515. .list-option.list-current[class] > a[href] {
  516.  
  517. text-decoration:none;
  518. }
  519.  
  520. html {
  521. --blacklisted-display: none;
  522. --hidden-display: none;
  523. }
  524.  
  525. [blacklisted-shown] {
  526. --blacklisted-display: list-item;
  527. --hyperlink-blacklisted-option-opacity: 1;
  528. }
  529. [hidden-shown] {
  530. --hidden-display: list-item;
  531. --hyperlink-hidden-option-opacity: 1;
  532. }
  533.  
  534. .script-list li.blacklisted{
  535. display: var(--blacklisted-display);
  536.  
  537. }
  538.  
  539. .script-list li.hidden{
  540. display: var(--hidden-display);
  541.  
  542. }
  543.  
  544. `
  545.  
  546.  
  547.  
  548.  
  549. return { fields, logo, locales, blacklist, settingsCSS, pageCSS }
  550.  
  551.  
  552.  
  553. })();
  554.  
  555. (async () => {
  556.  
  557. function fixValue(key, def, test) {
  558. return GM.getValue(key, def).then((v) => test(v) || GM.deleteValue(key))
  559. }
  560.  
  561. const isScriptFirstUse = await GM.getValue('firstUse', true);
  562. await Promise.all([
  563. fixValue('hiddenList', [], v => v && typeof v === 'object' && typeof v.length === 'number' && (v.length === 0 || typeof v[0] === 'number')),
  564. fixValue('lastMilestone', 0, v => v && typeof v === 'number' && v >= 0)
  565. ])
  566.  
  567. const id = 'greasyfork-plus';
  568. const title = `${GM.info.script.name} v${GM.info.script.version} Settings`;
  569. const fields = mWindow.fields;
  570. const logo = mWindow.logo;
  571. const nonLatins = /[^\p{Script=Latin}\p{Script=Common}\p{Script=Inherited}]/gu;
  572. const blacklist = new RegExp(mWindow.blacklist.join('|'), 'giu');
  573. const hiddenList = await GM.getValue('hiddenList', []);
  574. const lang = document.documentElement.lang;
  575. const locales = mWindow.locales;
  576.  
  577. const gmc = new GM_config({
  578. id,
  579. title,
  580. fields,
  581. css: mWindow.settingsCSS,
  582. events: {
  583. init: () => {
  584. gmc.initializedResolve && gmc.initializedResolve();
  585. gmc.initializedResolve = null;
  586. if (!Array.isArray(hiddenList)) {
  587. GM.deleteValue('hiddenList');
  588. setTimeout(() => window.location.reload(false), 500);
  589. }
  590.  
  591. },
  592. /** @param {Document} document */
  593. open: async (document) => {
  594. const textarea = document.querySelector(`#${id}_field_hiddenList`);
  595.  
  596. const hiddenList = await GM.getValue('hiddenList', []);
  597. const unsavedHiddenList = gmc.get('hiddenList') !== '' ? gmc.get('hiddenList').split(',').map(Number) : [];
  598.  
  599. if ((hiddenList.filter(item => !unsavedHiddenList.includes(item)).length > 0 || unsavedHiddenList.filter(item => !hiddenList.includes(item)).length > 0) && hiddenList.length !== 0) {
  600. gmc.fields.hiddenList.value = hiddenList.sort((a, b) => a - b).join(', ');
  601.  
  602. gmc.close();
  603. gmc.open();
  604. }
  605.  
  606. const resize = (target) => {
  607. target.style.height = '';
  608. target.style.height = `${target.scrollHeight}px`;
  609. };
  610.  
  611. if (textarea) {
  612. resize(textarea);
  613. textarea.addEventListener('input', (event) => resize(event.target));
  614.  
  615. }
  616.  
  617. document.body.addEventListener('mousedown', (event)=>{
  618. if (event.detail > 1 && !event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && !event.defaultPrevented) {
  619. event.preventDefault();
  620. event.stopPropagation();
  621. event.stopImmediatePropagation();
  622. }
  623. }, true);
  624. },
  625. save: async (forgotten) => {
  626. const unsavedHiddenList = forgotten.hiddenList !== '' ? forgotten.hiddenList.split(',').map(Number).filter((element) => element !== 0) : undefined;
  627.  
  628. if (gmc.isOpen) {
  629. await GM.setValue('hiddenList', Array.from(unsavedHiddenList));
  630.  
  631. UU.alert('settings saved');
  632. gmc.close();
  633. setTimeout(() => window.location.reload(false), 500);
  634. }
  635. }
  636. }
  637. });
  638. gmc.initialized = new Promise(r => (gmc.initializedResolve = r));
  639. await gmc.initialized.then();
  640.  
  641. if (typeof GM.registerMenuCommand === 'function') {
  642. GM.registerMenuCommand('Configure', () => gmc.open());
  643. GM.registerMenuCommand('Reset Everything', () => {
  644. Promise.all([
  645. GM.deleteValue('hiddenList'),
  646. GM.deleteValue('lastMilestone'),
  647. GM.deleteValue('firstUse')
  648. ]).then(() => {
  649. setTimeout(() => window.location.reload(false), 50);
  650. })
  651. });
  652. }
  653.  
  654. UU.init({ id, logging: gmc.get('logging') });
  655. UU.log(nonLatins);
  656. UU.log(blacklist);
  657. UU.log(hiddenList);
  658.  
  659. const _VM = (typeof VM !== 'undefined' ? VM : null) || {
  660. shortcut: {
  661. register: () => { }
  662. }
  663. };
  664. let avoidDuplication = 0;
  665. const avoidDuplicationF = () => {
  666. const p = avoidDuplication;
  667. avoidDuplication = Date.now();
  668. if (avoidDuplication - p < 30) return false;
  669. return true;
  670. }
  671. const shortcuts = [
  672. ['ctrlcmd-alt-s', () => avoidDuplicationF() && gmc.open()],
  673. ['ctrlcmd-alt-ß', () => avoidDuplicationF() && gmc.open()],
  674. ['ctrlcmd-alt-b', () => avoidDuplicationF() && toggleListDisplayingItem('blacklisted')],
  675. ['ctrlcmd-alt-∫', () => avoidDuplicationF() && toggleListDisplayingItem('blacklisted')],
  676. ['ctrlcmd-alt-h', () => avoidDuplicationF() && toggleListDisplayingItem('hidden')],
  677. ['ctrlcmd-alt-˙', () => avoidDuplicationF() && toggleListDisplayingItem('hidden')]
  678. ]
  679. for (const [scKey, scFn] of shortcuts) {
  680. _VM.shortcut.register(scKey, scFn);
  681. }
  682.  
  683. const addSettingsToMenu = () => {
  684. const nav = document.querySelector('#site-nav > nav')
  685. if (!nav) return;
  686.  
  687. const scriptName = GM.info.script.name;
  688. const scriptVersion = GM.info.script.version;
  689. const menu = document.createElement('li');
  690. menu.classList.add(id);
  691. menu.setAttribute('alt', `${scriptName} ${scriptVersion}`);
  692. menu.setAttribute('title', `${scriptName} ${scriptVersion}`);
  693. const link = document.createElement('a');
  694. link.setAttribute('href', '#');
  695. link.textContent = GM.info.script.name;
  696. menu.appendChild(link);
  697. nav.insertBefore(menu, document.querySelector('#site-nav > nav > li:first-of-type'));
  698.  
  699. menu.addEventListener('click', (e) => {
  700. e.preventDefault();
  701. e.stopPropagation();
  702. e.stopImmediatePropagation();
  703. gmc.open();
  704. });
  705. };
  706.  
  707.  
  708. const toggleListDisplayingItem = (t) => {
  709.  
  710. const m = document.documentElement;
  711.  
  712. const p = t + '-shown';
  713. let currentIsShown = m.hasAttribute(p)
  714. if (!currentIsShown) {
  715. m.setAttribute(p, '')
  716. } else {
  717. m.removeAttribute(p)
  718. }
  719.  
  720. }
  721.  
  722. const createListOptionGroup = () => {
  723.  
  724. const html = `<div class="list-option-group" id="${id}-options">${GM.info.script.name} Lists:<ul>
  725. <li class="list-option blacklisted"><a href="#" id="hyperlink-35389"></a></li>
  726. <li class="list-option hidden"><a href="#" id="hyperlink-40361"></a></li>
  727. </ul></div>`;
  728. const firstOptionGroup = document.querySelector('.list-option-groups > div');
  729. firstOptionGroup && firstOptionGroup.insertAdjacentHTML('beforebegin', html);
  730.  
  731. const blacklistedOption = document.querySelector(`#${id}-options li.blacklisted`);
  732. blacklistedOption && blacklistedOption.addEventListener('click', (evt) => {
  733. evt.preventDefault();
  734. toggleListDisplayingItem('blacklisted');
  735. }, false);
  736.  
  737. const hiddenOption = document.querySelector(`#${id}-options li.hidden`);
  738. hiddenOption && hiddenOption.addEventListener('click', (evt) => {
  739. evt.preventDefault();
  740. toggleListDisplayingItem('hidden');
  741. }, false);
  742.  
  743. }
  744.  
  745. const addOptions = () => {
  746.  
  747. const gn = () => {
  748.  
  749. let aBlackList = document.querySelector('#hyperlink-35389');
  750. let aHidden = document.querySelector('#hyperlink-40361');
  751. if (!aBlackList || !aHidden) return;
  752. aBlackList.textContent = `Blacklisted scripts (${document.querySelectorAll('.script-list li.blacklisted').length})`;
  753. aHidden.textContent = `Hidden scripts (${document.querySelectorAll('.script-list li.hidden').length})`
  754.  
  755. }
  756. const callback = (entries) => {
  757. if (entries && entries.length >= 1) requestAnimationFrame(gn);
  758. }
  759.  
  760. const setupScriptList = async () => {
  761. let scriptList;
  762. let i = 8;
  763. while (i-- > 0) {
  764. scriptList = document.querySelector('.script-list li')
  765. if (scriptList) scriptList = scriptList.closest('.script-list')
  766. if (scriptList) break;
  767. await new Promise(r => requestAnimationFrame(r))
  768. }
  769. if (!scriptList) return;
  770. createListOptionGroup();
  771. const mo = new MutationObserver(callback);
  772. mo.observe(scriptList, { childList: true, subtree: true });
  773. gn();
  774. }
  775. setupScriptList();
  776.  
  777. };
  778.  
  779.  
  780. /**
  781. * Get script data from Greasy Fork API
  782. *
  783. * @param {number} id Script ID
  784. * @returns {Promise} Script data
  785. */
  786. let networkMP1 = Promise.resolve();
  787. let networkMP2 = Promise.resolve();
  788. let previousIsCache = false;
  789. // let ss = [];
  790. // var sum = function(nums) {
  791. // var total = 0;
  792. // for (var i = 0, len = nums.length; i < len; i++) total += nums[i];
  793. // return total;
  794. // };
  795. const getScriptData = async (id, noCache) => {
  796. if (!(id >= 0)) return Promise.resolve()
  797. const url = `https://${window.location.hostname}/scripts/${id}.json`;
  798. return new Promise((resolve, reject) => {
  799.  
  800. networkMP1 = networkMP1.then(() => new Promise(unlock => {
  801.  
  802. const maxAgeInSeconds = 900;
  803. const rd = previousIsCache ? 1 : Math.floor(Math.random() * 80 + 80);
  804. let fetchStart = 0;
  805. new Promise(r => setTimeout(r, rd))
  806. .then(() => {
  807. fetchStart = Date.now();
  808. })
  809. .then(() => fetch(url, noCache ? {
  810. method: 'GET',
  811. cache: 'reload',
  812. credentials: 'omit',
  813. headers: new Headers({
  814. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  815. })
  816. } : {
  817. method: 'GET',
  818. cache: 'force-cache',
  819. credentials: 'omit',
  820. headers: new Headers({
  821. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  822. }),
  823. }))
  824. .then((response) => {
  825.  
  826. let fetchStop = Date.now();
  827. // const dd = fetchStop - fetchStart;
  828. // dd (cache) = {min: 1, max: 8, avg: 3.7}
  829. // dd (normal) = {min: 136, max: 316, avg: 162.62}
  830.  
  831. // ss.push(dd)
  832. // ss.maxValue = Math.max(...ss);
  833. // ss.minValue = Math.min(...ss);
  834. // ss.avgValue = sum(ss)/ss.length;
  835. // console.log(dd)
  836. // console.log(ss)
  837. previousIsCache = (fetchStop - fetchStart) < (3.7 + 162.62) / 2;
  838. UU.log(`${response.status}: ${response.url}`)
  839. // UU.log(response)
  840. if (response.ok === true) {
  841. unlock();
  842. return response.json()
  843. }
  844. if (response.status === 503) {
  845. return new Promise(r => setTimeout(r, 270 + rd)).then(() => {
  846. unlock();
  847. return getScriptData(id, true);
  848. });
  849. }
  850. console.warn(response);
  851. new Promise(r => setTimeout(r, 470)).then(unlock); // reload later
  852. })
  853. .then((data) => resolve(data))
  854. .catch((e) => {
  855. unlock();
  856. UU.log(id, url)
  857. console.warn(e)
  858. // reject(e)
  859. })
  860.  
  861. })).catch(() => { })
  862.  
  863. });
  864. }
  865.  
  866. /**
  867. * Get user data from Greasy Fork API
  868. *
  869. * @param {string} userID User ID
  870. * @returns {Promise} User data
  871. */
  872. const getUserData = (userID, noCache) => {
  873.  
  874. if (!(userID >= 0)) return Promise.resolve()
  875.  
  876. const url = `https://${window.location.hostname}/users/${userID}.json`;
  877. return new Promise((resolve, reject) => {
  878.  
  879.  
  880. networkMP2 = networkMP2.then(() => new Promise(unlock => {
  881.  
  882. const maxAgeInSeconds = 900;
  883. const rd = Math.floor(Math.random() * 80 + 80);
  884.  
  885. new Promise(r => setTimeout(r, rd))
  886. .then(() => fetch(url, noCache ? {
  887. method: 'GET',
  888. cache: 'reload',
  889. credentials: 'omit',
  890. headers: new Headers({
  891. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  892. })
  893. } : {
  894. method: 'GET',
  895. cache: 'force-cache',
  896. credentials: 'omit',
  897. headers: new Headers({
  898. 'Cache-Control': `max-age=${maxAgeInSeconds}`,
  899. }),
  900. }))
  901. .then((response) => {
  902. UU.log(`${response.status}: ${response.url}`)
  903. if (response.ok === true) {
  904. unlock();
  905. return response.json()
  906. }
  907. if (response.status === 503) {
  908. return new Promise(r => setTimeout(r, 270 + rd)).then(() => {
  909. unlock();
  910. return getUserData(userID, true); // reload later
  911. });
  912. }
  913. console.warn(response);
  914. new Promise(r => setTimeout(r, 470)).then(unlock);
  915. })
  916. .then((data) => resolve(data))
  917. .catch((e) => {
  918. setTimeout(() => {
  919. unlock()
  920. }, 270)
  921. UU.log(userID, url)
  922. console.warn(e)
  923. // reject(e)
  924. })
  925.  
  926.  
  927.  
  928. })).catch(() => { })
  929.  
  930. });
  931. }
  932. const getTotalInstalls = (data) => {
  933. if (!data || !data.scripts) return;
  934. return new Promise((resolve, reject) => {
  935. const totalInstalls = [];
  936.  
  937. data.scripts.forEach((element) => {
  938. totalInstalls.push(parseInt(element.total_installs, 10));
  939. });
  940.  
  941. resolve(totalInstalls.reduce((a, b) => a + b, 0));
  942. });
  943. };
  944.  
  945.  
  946. const isInstalled = (name, namespace) => {
  947. return new Promise((resolve, reject) => {
  948. if (window.external && window.external.Violentmonkey) {
  949. window.external.Violentmonkey.isInstalled(name, namespace).then((data) => resolve(data));
  950. return;
  951. }
  952.  
  953. if (window.external && window.external.Tampermonkey) {
  954. window.external.Tampermonkey.isInstalled(name, namespace, (data) => {
  955. (data.installed) ? resolve(data.version) : resolve();
  956. });
  957. return;
  958. }
  959.  
  960. resolve();
  961. });
  962. };
  963.  
  964. const compareVersions = (v1, v2) => {
  965. if (!v1 || !v2) return;
  966. if (v1 === null || v2 === null) return;
  967. if (v1 === v2) return 0;
  968.  
  969. const sv1 = v1.split('.').map((index) => +index);
  970. const sv2 = v2.split('.').map((index) => +index);
  971.  
  972. for (let index = 0; index < Math.max(sv1.length, sv2.length); index++) {
  973. if (sv1[index] > sv2[index]) return 1;
  974. if (sv1[index] < sv2[index]) return -1;
  975. }
  976.  
  977. return 0;
  978. };
  979.  
  980.  
  981. /**
  982. * Return label for the hide script button
  983. *
  984. * @param {boolean} hidden Is hidden
  985. * @returns {string} Label
  986. */
  987. const blockLabel = (hidden) => {
  988. return hidden ? (locales[lang] ? locales[lang].notHide : locales.en.notHide) : (locales[lang] ? locales[lang].hide : locales.en.hide)
  989. }
  990.  
  991. /**
  992. * Return label for the install button
  993. *
  994. * @param {number} update Update value
  995. * @returns {string} Label
  996. */
  997. const installLabel = (update) => {
  998. switch (update) {
  999. case undefined: {
  1000. return locales[lang] ? locales[lang].install : locales.en.install
  1001. }
  1002. case 1: {
  1003. return locales[lang] ? locales[lang].update : locales.en.update
  1004. }
  1005. case -1: {
  1006. return locales[lang] ? locales[lang].downgrade : locales.en.downgrade
  1007. }
  1008. default: {
  1009. return locales[lang] ? locales[lang].reinstall : locales.en.reinstall
  1010. }
  1011. }
  1012. }
  1013.  
  1014. const hideBlacklistedScript = (element, list) => {
  1015. if (!element) return;
  1016. const scriptLink = element.querySelector('.script-link')
  1017.  
  1018. const name = scriptLink ? scriptLink.textContent : '';
  1019. const descriptionElem = element.querySelector('.script-description')
  1020. const description = descriptionElem ? descriptionElem.textContent : '';
  1021.  
  1022. if (!name) return;
  1023.  
  1024. switch (list) {
  1025. case 'nonLatins':
  1026. if ((nonLatins.test(name) || nonLatins.test(description)) && !element.classList.contains('blacklisted')) {
  1027. element.classList.add('blacklisted', 'non-latins');
  1028. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1029. let scriptLink = element.querySelector('.script-link');
  1030. if (scriptLink) { scriptLink.textContent += ' (non-latin)'; }
  1031. }
  1032. }
  1033. break;
  1034. case 'blacklist':
  1035. if ((blacklist.test(name) || blacklist.test(description)) && !element.classList.contains('blacklisted')) {
  1036. element.classList.add('blacklisted', 'blacklist');
  1037. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1038. let scriptLink = element.querySelector('.script-link');
  1039. if (scriptLink) { scriptLink.textContent += ' (blacklist)'; }
  1040. }
  1041. }
  1042. break;
  1043. case 'customBlacklist': {
  1044. const customBlacklist = new RegExp(gmc.get('customBlacklist').replace(/\s/g, '').split(',').join('|'), 'giu');
  1045. if ((customBlacklist.test(name) || customBlacklist.test(description)) && !element.classList.contains('blacklisted')) {
  1046. element.classList.add('blacklisted', 'custom-blacklist');
  1047. if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {
  1048. let scriptLink = element.querySelector('.script-link');
  1049. if (scriptLink) { scriptLink.textContent += ' (custom-blacklist)'; }
  1050. }
  1051. }
  1052. break;
  1053. }
  1054. default:
  1055. UU.log('No blacklists');
  1056. break;
  1057. }
  1058. };
  1059.  
  1060. const hideHiddenScript = (element, id, list) => {
  1061. id = +id;
  1062. if (!(id >= 0)) return;
  1063.  
  1064. const isInHiddenList = () => hiddenList.indexOf(id) !== -1;
  1065. const updateScriptLink = (shouldHide) => {
  1066. if (gmc.get('hideHiddenScript') && gmc.get('debugging')) {
  1067. let scriptLink = element.querySelector('.script-link');
  1068. if (scriptLink) {
  1069. if (shouldHide) {
  1070. scriptLink.innerHTML += ' (hidden)';
  1071. } else {
  1072. scriptLink.innerHTML = scriptLink.innerHTML.replace(' (hidden)', '');
  1073. }
  1074. }
  1075. }
  1076. };
  1077.  
  1078. // Check for initial state and set it
  1079. if (isInHiddenList()) {
  1080. element.classList.add('hidden');
  1081. updateScriptLink(true);
  1082. }
  1083.  
  1084. // Add button to hide the script
  1085. const insertButtonHTML = (selector, html) => {
  1086. const target = element.querySelector(selector);
  1087. if (!target) return;
  1088. let p = document.createElement('template');
  1089. p.innerHTML = html;
  1090. target.parentNode.insertBefore(p.content.firstChild, target.nextSibling);
  1091. };
  1092.  
  1093. const isHidden = element.classList.contains('hidden');
  1094. const blockButtonHTML = `<span class=block-button role=button style-16377>${blockLabel(isHidden)}</span>`;
  1095. const blockButtonHeaderHTML = `<span class=block-button role=button style-77329 style="">${blockLabel(isHidden)}</span>`;
  1096.  
  1097. insertButtonHTML('.badge-js, .badge-css', blockButtonHTML);
  1098. insertButtonHTML('header h2', blockButtonHeaderHTML);
  1099.  
  1100. // Add event listener
  1101. const button = element.querySelector('.block-button');
  1102. if (button) {
  1103. button.addEventListener('click', (event) => {
  1104. event.stopPropagation();
  1105. event.stopImmediatePropagation();
  1106.  
  1107. if (!isInHiddenList()) {
  1108. hiddenList.push(id);
  1109. GM.setValue('hiddenList', hiddenList);
  1110.  
  1111. element.classList.add('hidden');
  1112. updateScriptLink(true);
  1113.  
  1114. } else {
  1115. const index = hiddenList.indexOf(id);
  1116. hiddenList.splice(index, 1);
  1117. GM.setValue('hiddenList', hiddenList);
  1118.  
  1119. element.classList.remove('hidden');
  1120. updateScriptLink(false);
  1121. }
  1122.  
  1123. const blockBtn = element.querySelector('.block-button');
  1124. if (blockBtn) blockBtn.textContent = blockLabel(element.classList.contains('hidden'));
  1125. });
  1126. }
  1127. };
  1128.  
  1129. const insertButtonHTML = (element, selector, html) => {
  1130. const target = element.querySelector(selector);
  1131. if (!target) return;
  1132. let p = document.createElement('template');
  1133. p.innerHTML = html;
  1134. target.parentNode.insertBefore(p.content.firstChild, target.nextSibling);
  1135. };
  1136.  
  1137. const addInstallButton = (element, url, label, version) => {
  1138. insertButtonHTML(element, '.badge-js, .badge-css', `<a class="install-link" href="${url}" style-54998>${label} ${version}</a>`);
  1139. };
  1140.  
  1141. const showInstallButton = async (scriptID, element) => {
  1142.  
  1143. const script = await getScriptData(scriptID);
  1144. if (!script) return;
  1145.  
  1146. const installed = await isInstalled(script.name, script.namespace)
  1147.  
  1148. const update = compareVersions(script.version, installed);
  1149. const label = installLabel(update);
  1150. addInstallButton(element, script.code_url, label, script.version);
  1151.  
  1152. }
  1153.  
  1154.  
  1155. const foundScriptList = async (scriptList) => {
  1156.  
  1157. let rid = 0;
  1158. let g = () => {
  1159. if (!scriptList || scriptList.isConnected !== true) return;
  1160.  
  1161. const scriptElements = scriptList.querySelectorAll('li[data-script-id]:not([e8kk])');
  1162.  
  1163. for (const element of scriptElements) {
  1164. element.setAttribute('e8kk', '1');
  1165.  
  1166. const scriptID = +element.getAttribute('data-script-id');
  1167. if (!(scriptID > 0)) continue;
  1168.  
  1169. // blacklisted scripts
  1170. if (gmc.get('nonLatins')) hideBlacklistedScript(element, 'nonLatins');
  1171. if (gmc.get('blacklist')) hideBlacklistedScript(element, 'blacklist');
  1172. if (gmc.get('customBlacklist')) hideBlacklistedScript(element, 'customBlacklist');
  1173.  
  1174. // hidden scripts
  1175. if (gmc.get('hideHiddenScript')) hideHiddenScript(element, scriptID, true);
  1176.  
  1177. // install button
  1178. if (gmc.get('showInstallButton')) {
  1179. showInstallButton(scriptID, element)
  1180. }
  1181. }
  1182.  
  1183. }
  1184. let f = (entries) => {
  1185. const tid = ++rid
  1186. if (entries && entries.length) requestAnimationFrame(() => {
  1187. if (tid === rid) g();
  1188. });
  1189. }
  1190. let mo = new MutationObserver(f);
  1191. mo.observe(scriptList, { subtree: true, childList: true });
  1192.  
  1193. g();
  1194.  
  1195. }
  1196.  
  1197. const onReady = async () => {
  1198. addSettingsToMenu();
  1199.  
  1200.  
  1201. setTimeout(() => {
  1202. let installBtn = document.querySelector('a[data-script-id][data-script-version]')
  1203. let scriptID = installBtn && installBtn.textContent ? +installBtn.getAttribute('data-script-id') : 0;
  1204. if (scriptID > 0) {
  1205. getScriptData(scriptID, true);
  1206. } else {
  1207.  
  1208.  
  1209. const userLink = document.querySelector('#site-nav .user-profile-link a[href]');
  1210. let userID = userLink ? userLink.getAttribute('href') : '';
  1211.  
  1212. userID = userID ? /users\/(\d+)/.exec(userID) : null;
  1213. if (userID) userID = userID[1];
  1214. if (userID) {
  1215. userID = +userID;
  1216. if (userID > 0) {
  1217. getUserData(userID, true);
  1218. }
  1219. }
  1220.  
  1221.  
  1222. }
  1223. }, 740);
  1224.  
  1225. const userLink = document.querySelector('.user-profile-link a[href]');
  1226. const userID = userLink ? userLink.getAttribute('href') : undefined;
  1227.  
  1228. // blacklisted scripts / hidden scripts / install button
  1229. if (window.location.pathname !== userID && !/discussions/.test(window.location.pathname) && (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript') || gmc.get('showInstallButton'))) {
  1230.  
  1231. const scriptList = document.querySelector('.script-list');
  1232. if (scriptList) {
  1233. foundScriptList(scriptList);
  1234. } else {
  1235. const timeout = Date.now() + 3000;
  1236. /** @type {MutationObserver | null} */
  1237. let mo = null;
  1238. const mutationCallbackForScriptList = () => {
  1239. if (!mo) return;
  1240. const scriptList = document.querySelector('.script-list');
  1241. if (scriptList) {
  1242. mo.disconnect();
  1243. mo.takeRecords();
  1244. mo = null;
  1245. foundScriptList(scriptList);
  1246. } else if (Date.now() > timeout) {
  1247. mo.disconnect();
  1248. mo.takeRecords();
  1249. mo = null;
  1250. }
  1251. }
  1252. mo = new MutationObserver(mutationCallbackForScriptList);
  1253. mo.observe(document, { subtree: true, childList: true });
  1254. }
  1255.  
  1256.  
  1257. // hidden scripts on details page
  1258. const installLinkElement = document.querySelector('#script-info .install-link[data-script-id]');
  1259. if (gmc.get('hideHiddenScript') && installLinkElement) {
  1260. const id = +installLinkElement.getAttribute('data-script-id');
  1261. hideHiddenScript(document.querySelector('#script-info'), id, false);
  1262. }
  1263.  
  1264. // add options and style for blacklisted/hidden scripts
  1265. if (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript')) {
  1266. addOptions();
  1267. UU.addStyle(mWindow.pageCSS);
  1268. }
  1269. }
  1270.  
  1271. // total installs
  1272. if (gmc.get('showTotalInstalls') && document.querySelector('#user-script-list')) {
  1273. const dailyInstalls = [];
  1274. const totalInstalls = [];
  1275.  
  1276. const dailyInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-daily-installs');
  1277. for (const element of dailyInstallElements) {
  1278. dailyInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));
  1279. }
  1280.  
  1281. const totalInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-total-installs');
  1282. for (const element of totalInstallElements) {
  1283. totalInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));
  1284. }
  1285.  
  1286. const dailyInstallsSum = dailyInstalls.reduce((a, b) => a + b, 0);
  1287. const totalInstallsSum = totalInstalls.reduce((a, b) => a + b, 0);
  1288.  
  1289. const convertLi = (li) => {
  1290.  
  1291. if (!li) return null;
  1292. const a = li.firstElementChild
  1293. if (a === null) return li;
  1294. if (a === li.lastElementChild && a.nodeName === 'A') return a;
  1295.  
  1296.  
  1297. return null;
  1298. }
  1299.  
  1300. const dailyOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(1)'));
  1301. dailyOption && dailyOption.insertAdjacentHTML('beforeend', `<span> (${dailyInstallsSum.toLocaleString()})</span>`);
  1302.  
  1303. const totalOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(2)'));
  1304. totalOption && totalOption.insertAdjacentHTML('beforeend', `<span> (${totalInstallsSum.toLocaleString()})</span>`);
  1305. }
  1306.  
  1307. // milestone notification
  1308. if (gmc.get('milestoneNotification')) {
  1309. const milestones = gmc.get('milestoneNotification').replace(/\s/g, '').split(',').map(Number);
  1310.  
  1311. if (!userID) return;
  1312.  
  1313. const userData = await getUserData(+userID.match(/\d+(?=\D)/g));
  1314. if (!userData) return;
  1315.  
  1316. const [totalInstalls, lastMilestone] = await Promise.all([
  1317. getTotalInstalls(userData),
  1318. GM.getValue('lastMilestone', 0)]);
  1319.  
  1320. const milestone = milestones.filter(milestone => totalInstalls >= milestone).pop();
  1321.  
  1322. UU.log(`total installs are "${totalInstalls}", milestone reached is "${milestone}", last milestone reached is "${lastMilestone}"`);
  1323.  
  1324. if (milestone <= lastMilestone) return;
  1325.  
  1326. if (milestone && milestone >= 0) {
  1327.  
  1328.  
  1329. GM.setValue('lastMilestone', milestone);
  1330.  
  1331. const lang = document.documentElement.lang;
  1332. const text = (locales[lang] ? locales[lang].milestone : locales.en.milestone).replace('$1', milestone.toLocaleString());
  1333.  
  1334. if (typeof GM.notification === 'function') {
  1335. GM.notification({
  1336. text,
  1337. title: GM.info.script.name,
  1338. image: logo,
  1339. onclick: () => {
  1340. window.location = `https://${window.location.hostname}${userID}#user-script-list-section`;
  1341. }
  1342. });
  1343. } else {
  1344. UU.alert(text);
  1345. }
  1346.  
  1347. }
  1348.  
  1349. }
  1350.  
  1351. if(isScriptFirstUse) GM.setValue('firstUse', false).then(()=>{
  1352. gmc.open();
  1353. });
  1354.  
  1355. }
  1356.  
  1357.  
  1358.  
  1359. Promise.resolve().then(() => {
  1360. if (document.readyState !== 'loading') {
  1361. onReady();
  1362. } else {
  1363. window.addEventListener("DOMContentLoaded", onReady, false);
  1364. }
  1365. });
  1366.  
  1367. })();