Page Flood

Press Shift+Alt+Q to batch open links in the main list in a page.

  1. // ==UserScript==
  2. // @name Page Flood
  3. // @namespace snomiao@gmail.com
  4. // @match http://*/*
  5. // @match https://*/*
  6. // @grant none
  7. // @version 1.0
  8. // @author snomiao@gmail.com
  9. // @description Press Shift+Alt+Q to batch open links in the main list in a page.
  10. // ==/UserScript==
  11.  
  12. main();
  13.  
  14. function main() {
  15. globalThis.PageFloodController?.abort();
  16. const ac = (globalThis.PageFloodController = new AbortController());
  17. window.addEventListener(
  18. "keydown",
  19. async (e) => {
  20. // e.altKey && getMainListLinks()
  21. if (e.shiftKey && e.altKey && e.code == "KeyQ") await openLinksInList();
  22. },
  23. { signal: ac.signal }
  24. );
  25. }
  26.  
  27. function openDeduplicatedUrl(url) {
  28. const opened = (globalThis.openDeduplicatedUrl_opened ??= new Set());
  29. return opened.has(url) || (window.open(url, "_blank") && opened.add(url));
  30. }
  31.  
  32. async function openLinksInList() {
  33. return await openLinks(getMainListLinks());
  34. }
  35. function $$(...args) {
  36. return [...document.querySelectorAll(...args)];
  37. }
  38. function maxRect(rects) {
  39. return {
  40. left: Math.min(...rects.map((e) => e.left)),
  41. top: Math.min(...rects.map((e) => e.top)),
  42. right: Math.max(...rects.map((e) => e.right)),
  43. bottom: Math.max(...rects.map((e) => e.bottom)),
  44. };
  45. }
  46. function area({ left, right, top, bottom }) {
  47. return (right - left) * (bottom - top);
  48. }
  49.  
  50. function elpath(e, path = "") {
  51. return !e
  52. ? path.trim()
  53. : e.tagName.match(/^h\d$/i)
  54. ? e.tagName
  55. : elpath(
  56. e.parentElement,
  57. e.tagName +
  58. [...e.classList]
  59. .filter((e) => e.match(/^[a-z-]+$/))
  60. .map((e) => "." + e)
  61. .join("") +
  62. " " +
  63. path
  64. );
  65. }
  66. // const elpath = function elpath(e){return !e?'': (elpath(e.parentElement) + ' ' + e.tagName).trim('')}
  67. function getDuplicates(list) {
  68. return new Set(
  69. Object.entries(Object.groupBy(list, (e) => e)).flatMap(([text, list]) =>
  70. text && list.length > 1 ? [text] : []
  71. )
  72. );
  73. }
  74. function getExcludeFilter(set, fn) {
  75. return (elem) => !set.has(fn(elem));
  76. }
  77. function removeDuplicateLinks(links) {
  78. return links.filter(
  79. getExcludeFilter(
  80. getDuplicates(links.map((e) => e.textContent)),
  81. (e) => e.href
  82. )
  83. );
  84. }
  85. function getLinkGroups() {
  86. return Object.groupBy(
  87. removeDuplicateLinks(
  88. $$("a").map((e) => (false && (e.style.background = "green"), e))
  89. ),
  90. (e) => elpath(e)
  91. );
  92. }
  93. function peekLog(e) {
  94. return console.log(e), e;
  95. }
  96. function groupEncolor([path, links]) {
  97. return (
  98. ((color) => links.map((a) => false && (a.style.background = color)))(
  99. "#" +
  100. Math.random().toString(16).slice(2, 8).padStart(6, "0") +
  101. Math.floor(256 * 0.995 ** path.length)
  102. .toString(16)
  103. .padStart(2, "0")
  104. ),
  105. peekLog([path, links])
  106. );
  107. }
  108.  
  109. function getLinksLists() {
  110. const compareFn = (fn) => (a, b) => fn(a) - fn(b);
  111. return [getLinkGroups()]
  112. .flatMap(Object.entries)
  113. .map(groupEncolor)
  114. .map(([path, list]) => ({
  115. path,
  116. list,
  117. area: area(maxRect(list.map((a) => a.getBoundingClientRect()))),
  118. }))
  119. .toSorted(compareFn((e) => -e.area));
  120. }
  121.  
  122. function getMainListLinks() {
  123. return getLinksLists()[0].list.map(
  124. (e) => (false && (e.style.background = "yellow"), e)
  125. );
  126. }
  127.  
  128. async function openLinks(links) {
  129. // max 8 page on 1 origin once batch
  130. // max 16 page on all origin once batch
  131. const urlss = Object.values(
  132. Object.groupBy(links, (url, i) => String(Math.floor(i / 8)))
  133. );
  134. for await (const urls of urlss) {
  135. urls.toReversed().map(openDeduplicatedUrl);
  136. await new Promise((r) => setTimeout(r, 1e3)); // 1s cd
  137. await new Promise((r) =>
  138. document.addEventListener("visibilitychange", r, { once: true })
  139. ); // wait for page visible
  140. }
  141. // await Promise.all(Object.entries(Object.groupBy(links, e => e.origin)).map(async ([origin, links]) => {
  142. // const urls = links.map(e => e.href)
  143. // const urlss = Object.values(Object.groupBy(urls, (url, i) => String(Math.floor(i / 8))))
  144. // for await (const urls of urlss) {
  145. // urls.toReversed().map(openUrl)
  146. // await new Promise(r => setTimeout(r, 1e3)) // 1s cd
  147. // await new Promise(r => document.addEventListener("visibilitychange", r, { once: true })) // wait for page visible
  148. // }
  149. // }))
  150. }
  151.  
  152. // getMainListLinks()