History of the Seen

Script to implement a history of the seen approach for some news sites. Details at https://github.com/theoky/HistoryOfTheSeen

  1. // ==UserScript==
  2. // @name History of the Seen
  3. // @namespace https://github.com/theoky/HistoryOfTheSeen
  4. // @description Script to implement a history of the seen approach for some news sites. Details at https://github.com/theoky/HistoryOfTheSeen
  5. // @author Theoky
  6. // @version 0.4192
  7. // @lastchanges workaround for bug in GreaseMonkey 3.2
  8. // @license GNU GPL version 3
  9. // @released 2014-02-20
  10. // @updated 2014-06-10
  11. // @homepageURL https://github.com/theoky/HistoryOfTheSeen
  12. //
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_deleteValue
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_listValues
  18. // @grant GM_addStyle
  19. //
  20. // for testing purposes (set FireFox greasemonkey.fileIsGreaseable)
  21. // @include file://*testhistory.html
  22. //
  23. // @include http*://*.derstandard.at/*
  24. // @include http*://*.faz.net/*
  25. // @include http*://*.golem.de/*
  26. // @include http*://*.handelsblatt.com/*
  27. // @include http*://*.heise.de/newsticker/*
  28. // @include http*://*.kleinezeitung.at/*
  29. // @include http*://*.nachrichten.at/*
  30. // @include http*://*.oe24.at/*
  31. // @include http*://*.orf.at/*
  32. // @include http*://orf.at/*
  33. // @include http*://*.reddit.com/*
  34. // @include http*://*.spiegel.de/*
  35. // @include http*://*.sueddeutsche.de/*
  36. // @include http*://*.welt.de/*
  37. // @include http*://*.wirtschaftsblatt.at/*
  38. // @include http*://*.zeit.de/*
  39. // @include http*://dastandard.at/*
  40. // @include http*://derstandard.at/*
  41. // @include http*://diepresse.com/*
  42. // @include http*://diestandard.at/*
  43. // @include http*://kurier.at/*
  44. // @include http*://slashdot.org/*
  45. // @include http*://taz.de/*
  46. // @include http*://notalwaysright.com/*
  47. // @include http*://www.nytimes.com/*
  48.  
  49. // @require http://code.jquery.com/jquery-2.1.1.min.js
  50. // @require http://code.jquery.com/ui/1.11.2/jquery-ui.js
  51. // @require https://gf.qytechs.cn/scripts/130-portable-md5-function/code/Portable%20MD5%20Function.js?version=10066
  52. // was require md5.js
  53. // was require http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/md5.js
  54. // ==/UserScript==
  55.  
  56. // Copyright (C) 2015 T. Kopetzky - theoky
  57. //
  58. // This program is free software: you can redistribute it and/or modify
  59. // it under the terms of the GNU General Public License as published by
  60. // the Free Software Foundation, either version 3 of the License, or
  61. // (at your option) any later version.
  62. //
  63. // This program is distributed in the hope that it will be useful,
  64. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  65. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  66. // GNU General Public License for more details.
  67. //
  68. // You should have received a copy of the GNU General Public License
  69. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  70. //
  71. // Tested with FireFox 34 and GreaseMonkey 2.3
  72.  
  73. //-------------------------------------------------
  74. //Functions
  75.  
  76. (function(){
  77.  
  78. var defaultSettings = {
  79. ageOfUrl: 5, // age in days after a url is deleted from the store
  80. // < 0 erases all dates (disables history)
  81. targetOpacity: 0.3,
  82. targetOpacity4Dim: 0.85,
  83. steps: 10,
  84. dimInterval: 30000,
  85. expireAllDomains: true, // On fast machines this can be true and expires
  86. // all domains in the database with each call. If false,
  87. // only the urls of the current domain are expired which
  88. // is slightly faster.
  89. cleanOnlyDaily: true,
  90. considerViewPort: true,
  91. dbOpsPerRun: 5
  92. };
  93.  
  94. var UNDEF = 'undefined';
  95. var DEFAULT_TAG = 'a';
  96. var defaultGetContentFct = function(elem) {
  97. if ((typeof elem != UNDEF) && (typeof elem.href != UNDEF)) {
  98. return elem.href;
  99. }
  100. return UNDEF;
  101. };
  102. var AFTER_SCROLL_DELAY = 750;
  103. var DO_DEBUG = false;
  104. var DEBUG_LVL_ERROR = 1;
  105. var DEBUG_LVL_WARN = 2;
  106. var DEBUG_LVL_INFO = 4;
  107. var perUrlSettings = [
  108. {
  109. url : ['.*\.?slashdot\.org' ],
  110. tag : 'article',
  111. upTrigger: "../article",
  112. getContent: function(elem) {
  113. if ((typeof elem != UNDEF) && (typeof elem.id != UNDEF)) {
  114. return elem.id;
  115. }
  116. return UNDEF;
  117. },
  118. parentHints : [ ]
  119. },
  120.  
  121. {
  122. url : ['.*\.?derstandard\.at', '.*\.?diestandard\.at', '.*\.?dastandard\.at' ],
  123. upTrigger: "../a",
  124. parentHints : [
  125. "ancestor::div[contains(concat(' ', @class, ' '), ' text ')]",
  126. "ancestor::ul[@class='stories']" ]
  127. },
  128.  
  129. {
  130. url : ['notalwaysright\.com'],
  131. upTrigger: "../a[@rel='bookmark']",
  132. parentHints : [ "ancestor::div[contains(concat(' ', @class, ' '), ' post ')]" ]
  133. },
  134.  
  135. {
  136. url : ['.*\.?golem.de'],
  137. upTrigger: "../a",
  138. parentHints : [ "ancestor::li",
  139. "ancestor::section[@id='index-promo']",
  140. "ancestor::section[contains(concat(' ', @class, ' '), ' promo ')]" ]
  141. },
  142.  
  143. {
  144. url : ['.*\.?reddit.com'],
  145. // class="title may-blank srTagged imgScanned"
  146. upTrigger: "../a[contains(@class, 'title') and contains(@class, 'may-blank')]",
  147. parentHints : [ "ancestor::div[contains(concat(' ', @class, ' '), ' thing ')]" ]
  148. },
  149. {
  150. url : ['nytimes\.com'],
  151. upTrigger: "../a",
  152. parentHints : [
  153. // "ancestor::li[contains(concat(' ', @class, ' '), ' portal-post ')]",
  154. "ancestor::div[contains(concat(' ', @class, ' '), ' collection ')]"
  155. ]
  156. }
  157. ];
  158. var dimMap = {};
  159. var countDownTimer = defaultSettings.steps;
  160. var theHRefs = null;
  161. var curSettings = null;
  162. var KEY_LAST_EXPIRE_OP = "lastExpire";
  163. var timeOutAfterLastScroll = UNDEF;
  164. var tag2Process = null;
  165. var getContentFct = null;
  166. var theDomain = null;
  167.  
  168. var progressbar;
  169. var progressLabel;
  170.  
  171. // Styling
  172. var progressBarStyle =
  173. ".ui-widget {" +
  174. " font-family: Verdana,Arial,sans-serif !important;" +
  175. " font-size: 1.1em !important;" +
  176. "}" +
  177. ".ui-widget-content {" +
  178. " border: 1px solid #aaaaaa !important;" +
  179. " color: #222222 !important;" +
  180. "}" +
  181. ".ui-widget-header {" +
  182. " border: 1px solid #aaaaaa !important;" +
  183. " background: #cccccc !important;" +
  184. " color: #222222 !important;" +
  185. " font-weight: bold !important;" +
  186. "}" +
  187. ".ui-progressbar {" +
  188. " height: 2em !important;" +
  189. " text-align: left !important;" +
  190. " overflow: hidden !important;" +
  191. "}" +
  192. ".ui-progressbar .ui-progressbar-value {" +
  193. " margin: -1px !important;" +
  194. " height: 100% !important;" +
  195. "}" +
  196. ".ui-progressbar .ui-progressbar-overlay {" +
  197. " background: url('data:image/gif;base64,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') !important;" +
  198. " height: 100% !important;" +
  199. " filter: alpha(opacity=25) !important; /* support: IE8 */" +
  200. " opacity: 0.25 !important;" +
  201. "}" +
  202. // ".ui-progressbar-indeterminate .ui-progressbar-value {" +
  203. // " background-image: none !important;" +
  204. // "}" +
  205.  
  206. ".ui-progressbar {" +
  207. " height: 2em !important;" +
  208. " text-align: left !important;" +
  209. " overflow: hidden !important;" +
  210. " position: absolute !important;" +
  211. " left: 20% !important;" +
  212. " top: 4px !important;" +
  213. " width: 60% !important;" +
  214. " z-index: 255 !important;" +
  215. "}" +
  216. ".progress-label {" +
  217. " position: absolute !important;" +
  218. " left: 5% !important;" +
  219. " top: 4px !important;" +
  220. " font-weight: bold !important;" +
  221. " text-shadow: 1px 1px 0 #fff !important;" +
  222. " z-index: 256 !important;" +
  223. "}";
  224. // Debugging
  225. function debuglog(msg) {
  226. if (DO_DEBUG) {
  227. console.log(msg);
  228. }
  229. }
  230. function debugLogLvl(lvl, msg) {
  231. if (lvl & DEBUG_LVL_ERROR) {
  232. console.log("error: " + msg);
  233. }
  234. if (DO_DEBUG) {
  235. if (lvl & DEBUG_LVL_WARN) {
  236. console.log("warn:" + msg);
  237. }
  238. if (lvl & DEBUG_LVL_INFO) {
  239. console.log(msg);
  240. }
  241. }
  242. }
  243.  
  244. var g_index;
  245. var g_keys;
  246. var g_lengthOfKeysArray;
  247. var g_workInProgress = false;
  248. var g_par1 = UNDEF;
  249. var g_par2 = UNDEF;
  250. var g_workerFctDefault = function(key, par1, par2) {
  251. GM_deleteValue(key);
  252. };
  253. var g_workerFct = g_workerFctDefault;
  254. var g_finishFct_Default = function() {
  255. document.location.reload(true);
  256. };
  257. var g_finishFct = g_finishFct_Default;
  258. var g_label;
  259.  
  260. function appendProgressBar() {
  261. $("body").append ( '\
  262. <div id="progressbar" class="ui-progressbar ui-progressbar-indeterminate"><div class="progress-label">History of the Seen: Resetting DB for current domain...</div></div>');
  263. }
  264.  
  265. function removeProgressBar(reload) {
  266. $("#progressbar").remove();
  267. if (reload) {
  268. document.location.reload(true);
  269. }
  270. }
  271. /*
  272. * Init function for "threading"
  273. */
  274. function initThreadingLoop()
  275. {
  276. if (g_workInProgress) {
  277. debugLogLvl(DEBUG_LVL_ERROR, "initThreading with already threading in progress.");
  278. return;
  279. }
  280. g_workInProgress = true;
  281. g_index = 0;
  282. g_keys = GM_listValues();
  283.  
  284. if (!g_keys) {
  285. debugLogLvl(DEBUG_LVL_WARN, "g_keys empty?");
  286. return;
  287. }
  288.  
  289. g_lengthOfKeysArray = g_keys.length;
  290. appendProgressBar();
  291. progressbar = $("#progressbar");
  292. progressLabel = $(".progress-label");
  293.  
  294. progressbar.progressbar({
  295. value : false,
  296. change : function() {
  297. progressLabel.text(g_label + progressbar.progressbar("value").toFixed(2) + "% ");
  298. },
  299. complete : function() {
  300. progressLabel.text(" History of the Seen: Operation Complete! ");
  301. }
  302. });
  303.  
  304. progressbar.progressbar("value", 0);
  305. setTimeout(doThreadWork, 1);
  306. }
  307.  
  308. /*
  309. * Worker method
  310. */
  311. function doThreadWork()
  312. {
  313. if (!g_workInProgress) {
  314. return;
  315. }
  316. var i = 0;
  317. var currentKey = null;
  318. currentKey = g_keys[g_index];
  319. while (i < defaultSettings.dbOpsPerRun && currentKey) {
  320. g_workerFct(currentKey, g_par1, g_par2);
  321. g_index ++;
  322. i++;
  323. currentKey = g_keys[g_index];
  324. }
  325. progressbar.progressbar("value", g_index * 100 / g_lengthOfKeysArray);
  326. if (currentKey) {
  327. setTimeout(doThreadWork, 10);
  328. } else
  329. {
  330. removeProgressBar(false);
  331. if (g_finishFct !== UNDEF) {
  332. g_finishFct();
  333. }
  334. g_workInProgress = false;
  335. }
  336. }
  337. // Resetting section
  338. function resetAllUrls() {
  339. if (!g_workInProgress && confirm('Are you sure you want to erase the complete seen history?')) {
  340. g_label = " History of the Seen: Cleaning DB, done ";
  341. g_par1 = UNDEF;
  342. g_par2 = UNDEF;
  343. g_workerFct = g_workerFctDefault;
  344. g_finishFct = g_finishFct_Default;
  345. initThreadingLoop();
  346. }
  347. }
  348.  
  349. function resetUrlsForCurrentHelper(dKey, domainOrUri) {
  350. if (confirm('Are you sure you want to erase the seen history for ' +
  351. domainOrUri + '?')) {
  352. g_label = " History of the Seen: Cleaning DB, done ";
  353. g_par1 = dKey;
  354. g_par2 = domainOrUri;
  355. g_workerFct = function(key, dKey, domainOrUri) {
  356. if (key == KEY_LAST_EXPIRE_OP){
  357. return;
  358. }
  359. try {
  360. var val = GM_getValue(key, "{}");
  361. var dict = JSON.parse(val);
  362. if(dict) {
  363. if (dict[dKey] == domainOrUri) {
  364. GM_deleteValue(key);
  365. }
  366. }
  367. } catch (e) {
  368. console.log(e);
  369. }
  370. };
  371. g_finishFct = g_finishFct_Default;
  372. initThreadingLoop();
  373. }
  374. }
  375. function resetUrlsForCurrentDomain() {
  376. resetUrlsForCurrentHelper("domain", document.domain);
  377. }
  378. function resetUrlsForCurrentSite() {
  379. resetUrlsForCurrentHelper("base", document.baseURI);
  380. }
  381.  
  382. function expireUrls() {
  383. debugLogLvl(DEBUG_LVL_INFO, "expireUrls");
  384. if (defaultSettings.cleanOnlyDaily) {
  385. var lastExpireDate = new Date(GM_getValue(KEY_LAST_EXPIRE_OP, nDaysOlderFromNow(2)));
  386. var diff = Math.abs((new Date()) - lastExpireDate);
  387. if (diff / 1000 / 3600 / 24 < 1) {
  388. // less than one day -> no DB cleaning
  389. return;
  390. }
  391. }
  392.  
  393. /*
  394. var val = GM_getValue(KEY_EXPIRE_OP_INPROGRESS);
  395.  
  396. if (typeof val !== UNDEF) {
  397. // expire in progress
  398. return;
  399. }
  400. GM_setValue(KEY_EXPIRE_OP_INPROGRESS, True);
  401.  
  402. */
  403. // cutOffDate
  404. g_label = " History of the Seen: Expiring old URLs for this site, done ";
  405. g_par1 = nDaysOlderFromNow(defaultSettings.ageOfUrl);
  406. debuglog("cutOffDate" + g_par1);
  407. g_par2 = UNDEF;
  408. g_workerFct = function(key, cutOffDate, par2) {
  409. if (key == KEY_LAST_EXPIRE_OP){
  410. return;
  411. }
  412. var dict = JSON.parse(GM_getValue(key, "{}"));
  413. if(dict) {
  414. try {
  415. debuglog(dict["domain"], cutOffDate.getTime(), dict["date"]);
  416. if (cutOffDate.getTime() > dict["date"]) {
  417. if (defaultSettings.expireAllDomains ||
  418. (dict["domain"] == document.domain))
  419. {
  420. GM_deleteValue(key);
  421. }
  422. }
  423. } catch (e) {
  424. console.log(e);
  425. }
  426. }
  427. else {
  428. console.log('Error! JSON.parse failed - dict is likely to be corrupted. Probably best to completely clean DB.');
  429. }
  430. };
  431. g_finishFct = function() {
  432. GM_setValue(KEY_LAST_EXPIRE_OP, new Date());
  433. }
  434. initThreadingLoop();
  435. }
  436.  
  437. function nDaysOlderFromNow(age, aDate, zeroHour) {
  438. var aDate = typeof aDate !== UNDEF ? aDate : new Date();
  439. var zeroHour = typeof zeroHour !== UNDEF ? zeroHour : true;
  440. var dateStore = new Date(aDate.getTime());
  441. var workDate = aDate;
  442. if (age >= 0) {
  443. workDate.setDate(dateStore.getDate() - age);
  444. if (zeroHour) {
  445. workDate.setHours(0,0,0,0);
  446. }
  447. }
  448. return workDate;
  449. }
  450.  
  451. /*
  452. * Find the settings for a given URL
  453. */
  454. function findPerUrlSettings(theSettings, aDomain) {
  455. debugLogLvl(DEBUG_LVL_INFO, "findPerUrlSettings");
  456. for (var i=0; i < theSettings.length; ++i) {
  457. for (var j = 0; j < theSettings[i].url.length; ++j) {
  458. var myRegExp = new RegExp(theSettings[i].url[j], 'i');
  459. if (aDomain.match(myRegExp)) {
  460. return theSettings[i];
  461. }
  462. }
  463. }
  464. }
  465.  
  466. /*
  467. * Find the parent element as specified in the settings.
  468. */
  469. function locateParentElem(curSettings, aDomain, aRoot) {
  470. if (!curSettings) {
  471. return null;
  472. }
  473. // console.log("locateParentElem 1", curSettings.url);
  474. var res = null;
  475. for (var xpath = 0; xpath < curSettings.parentHints.length; ++xpath) {
  476. // console.log("locateParentElem 2", curSettings.parentHints[xpath], aRoot);
  477. res = document.evaluate(curSettings.parentHints[xpath], aRoot, null, 9, null).singleNodeValue;
  478. if (res) {
  479. // console.log("locateParentElem found something");
  480. return res;
  481. }
  482. }
  483. return res;
  484. }
  485. /*
  486. * Check if the current node qualifies for looking up the hierarchy.
  487. */
  488. function goUp(curSettings, aRoot) {
  489. if (!curSettings) {
  490. return false;
  491. }
  492.  
  493. var res = null;
  494. if (curSettings.upTrigger !== "") {
  495. res = document.evaluate(curSettings.upTrigger, aRoot, null, 9, null).singleNodeValue;
  496. }
  497. return res !== null;
  498. }
  499. /*
  500. * Set the opacity for specified links
  501. */
  502. function dimLinks() {
  503. var interval = (1 - defaultSettings.targetOpacity4Dim)/defaultSettings.steps;
  504. var countDownTimer = countDownTimer - 1;
  505. var curOpacity = defaultSettings.targetOpacity4Dim + interval*countDownTimer;
  506.  
  507. // TODO: Better iterate over dimmap
  508. for(var i = 0; i < theHRefs.length; i++)
  509. {
  510. var hash = 'm' + hex_md5(theHRefs[i].href);
  511. if (hash in dimMap) {
  512. theHRefs[i].style.opacity = curOpacity;
  513. }
  514. }
  515. if (countDownTimer > 0) {
  516. var to = setTimeout(dimLinks, defaultSettings.dimInterval);
  517. }
  518. }
  519. /*
  520. * Check if an element is fully drawn on the viewport
  521. * from http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling?lq=1
  522. */
  523. function isFullyInView(elem)
  524. {
  525. debugLogLvl(DEBUG_LVL_INFO, "isFullyInView");
  526. var docViewTop = $(window).scrollTop();
  527. var docViewBottom = docViewTop + $(window).height();
  528.  
  529. var elemTop = $(elem).offset().top;
  530. var elemBottom = elemTop + $(elem).height();
  531.  
  532. return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
  533.  
  534. // is really fully in view
  535. // return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom)
  536. // && (elemBottom <= docViewBottom) && (elemTop >= docViewTop) );
  537. }
  538.  
  539. /*
  540. * Called after scrolling finished for defined time
  541. */
  542. function evaluateElems() {
  543. debugLogLvl(DEBUG_LVL_INFO, "evaluate all");
  544. processElements(false);
  545. timeOutAfterLastScroll = UNDEF;
  546. }
  547. /*
  548. * Wait for scrolling to end
  549. */
  550. function onScroll()
  551. {
  552. if (timeOutAfterLastScroll !== UNDEF) {
  553. window.clearTimeout(timeOutAfterLastScroll)
  554. }
  555. timeOutAfterLastScroll = setTimeout(evaluateElems, AFTER_SCROLL_DELAY);
  556. }
  557.  
  558. /*
  559. * Process all elements
  560. */
  561. function processElements(firstCall) {
  562. debugLogLvl(DEBUG_LVL_INFO, "processElements");
  563. var allTagElems = document.getElementsByTagName(tag2Process);
  564. var elemMap = {};
  565. var theBase = document.baseURI;
  566.  
  567. // Change the DOM
  568.  
  569. // First loop: gather all new links and make already seen opaque.
  570. for(var i = 0; i < allTagElems.length; i++)
  571. {
  572. var hash = 'm' + hex_md5(getContentFct(allTagElems[i]));
  573. // setValue needs letter in the beginning, thus use of 'm'
  574. debugLogLvl(DEBUG_LVL_INFO, "hash: " + hash);
  575.  
  576. var key = GM_getValue(hash);
  577.  
  578. if (typeof key !== UNDEF && key !== null) {
  579. // workaround for issue https://github.com/greasemonkey/greasemonkey/issues/2156
  580. // key found -> loaded this reference already
  581. debugLogLvl(DEBUG_LVL_INFO, "key found");
  582.  
  583. if (firstCall) {
  584. var done = false;
  585. if(goUp(curSettings, allTagElems[i])) {
  586. var pe = locateParentElem(curSettings, theDomain, allTagElems[i])
  587. // console.log("locate parent done", pe);
  588. if (pe) {
  589. pe.style.opacity = defaultSettings.targetOpacity;
  590. done = true;
  591. }
  592. }
  593. if (!done) {
  594. // change display
  595. allTagElems[i].style.opacity = defaultSettings.targetOpacity;
  596. debugLogLvl(DEBUG_LVL_INFO, "changing opacity");
  597. }
  598. }
  599. } else {
  600. //check if element is fully visible
  601. debugLogLvl(DEBUG_LVL_INFO, "key not found");
  602. if (isFullyInView(allTagElems[i])) {
  603. debuglog(allTagElems[i] + " is in view");
  604.  
  605. // key not found, store it with current date
  606. elemMap[hash] = {"domain":theDomain, "date":(new Date()).getTime(), "base":theBase};
  607. dimMap[hash] = allTagElems[i];
  608. }
  609. }
  610. }
  611.  
  612. // remember all new urls to hide the next time
  613. for (var e2 in elemMap) {
  614. GM_setValue(e2, JSON.stringify(elemMap[e2]));
  615. }
  616. theHRefs = allTagElems;
  617. if (firstCall) {
  618. var to = setTimeout(dimLinks, defaultSettings.dimInterval);
  619. }
  620. }
  621. // Menus
  622. GM_registerMenuCommand("Remove the seen history for this site.", resetUrlsForCurrentSite);
  623. GM_registerMenuCommand("Remove the seen history for this domain.", resetUrlsForCurrentDomain);
  624. GM_registerMenuCommand("Remove all seen history (for all sites)!", resetAllUrls);
  625.  
  626. GM_addStyle(progressBarStyle);
  627.  
  628. // Main part
  629. function run_script() {
  630. debugLogLvl(DEBUG_LVL_INFO, "run");
  631. dimMap = {};
  632. theDomain = document.domain;
  633.  
  634. curSettings = findPerUrlSettings(perUrlSettings, theDomain);
  635. tag2Process = DEFAULT_TAG;
  636. getContentFct = defaultGetContentFct;
  637. if (typeof curSettings != UNDEF) {
  638. if (typeof curSettings.tag != UNDEF) {
  639. tag2Process = curSettings.tag;
  640. }
  641. if (typeof curSettings.getContent != UNDEF) {
  642. getContentFct = curSettings.getContent;
  643. }
  644. }
  645. expireUrls();
  646. processElements(true);
  647. window.addEventListener("scroll", onScroll, false);
  648. }
  649.  
  650. run_script();
  651. })();

QingJ © 2025

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