Highlight OCH links

Link Checker. Hit escape to check whether one-click hoster links are online or offline. Press Esc + Shift to remove offline links

当前为 2022-12-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Highlight OCH links
  3. // @namespace cuzi
  4. // @license MIT
  5. // @description Link Checker. Hit escape to check whether one-click hoster links are online or offline. Press Esc + Shift to remove offline links
  6. // @icon https://raw.githubusercontent.com/cvzi/Userscripts/master/Multi-OCH/icons/och.png
  7. // @contributionURL https://buymeacoff.ee/cuzi
  8. // @contributionURL https://ko-fi.com/cuzicvzi
  9. // @compatible firefox Greasemonkey
  10. // @compatible chrome Tampermonkey. Allow all domains on first run.
  11. // @homepageURL https://openuserjs.org/scripts/cuzi/Highlight_OCH_links
  12. // @require https://greasyfork.org/scripts/370011-requestqueue/code/RequestQueue.js
  13. // @require https://greasyfork.org/scripts/25445-och-list/code/OCH%20List.js
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM.xmlHttpRequest
  16. // @grant GM.registerMenuCommand
  17. // @connect *
  18. // @version 23.5
  19. // @match *://*/*
  20. // @exclude *.yahoo.*
  21. // @exclude *.google.*
  22. // @exclude *.youtube.*
  23. // @exclude *.bing.com*
  24. // @exclude *yandex.com*
  25. // @exclude *duckduckgo.com*
  26. // ==/UserScript==
  27.  
  28. /* globals RequestQueue, getOCH, NodeFilter, GM */
  29.  
  30. (function () {
  31. 'use strict'
  32.  
  33. // Maximal number of links that are checked (per website)
  34. const MAXREQUESTS = 1000
  35.  
  36. // Maximal number of links that are checked in parallel (per website)
  37. const MAXPARALLELCONNECTIONS = 16
  38.  
  39. // Maximal number of DOM nodes that are examined. Decrease this number on slow machines.
  40. const MAXTEXTNODES = 10000
  41.  
  42. // Maximum download size per link (in KB). If you have activated direct downloads, the script will try to download the whole file instead of checking the link. This limit prevents this.
  43. const MAXDOWNLOADSIZE = 2000 // kilobytes
  44.  
  45. /*
  46. // Export
  47. var mypatterns = [];
  48. var mynames = [];
  49. var myurls = [];
  50. for(var key in OCH) {
  51. var o = OCH[key];
  52. if((""+o.check).length > 30) { // If check function implemented
  53. mypatterns.push(o.pattern.toString());
  54. mynames.push("'"+key+"'");
  55. myurls.push(" - ["+o.title+"]("+o.homepage+")");
  56. }
  57. }
  58.  
  59. alert(mypatterns.join(",\n"));
  60. alert(mynames.join(",\n"));
  61. alert(myurls.join("\n"));
  62. */
  63.  
  64. let links = [] // [ { hoster: "", url: "", element: DOMNode} , ... ]
  65. let deleteOfflineLinks = false
  66. const rq = new RequestQueue(MAXPARALLELCONNECTIONS, MAXREQUESTS)
  67.  
  68. // Get OCH list
  69. const OCH = getOCH(rq, MAXDOWNLOADSIZE)
  70.  
  71. function linkOffline (link) {
  72. link.element.style.backgroundColor = 'rgba(255, 0, 20, 0.5)'
  73. link.element.dataset.linkValidatedAs = 'offline'
  74. if (deleteOfflineLinks) {
  75. link.element.remove()
  76. }
  77. }
  78. function linkOnline (link) {
  79. link.element.style.backgroundColor = 'rgba(70, 255 ,0, 0.5)'
  80. link.element.dataset.linkValidatedAs = 'online'
  81. }
  82. function linkWaiting (link) {
  83. link.element.classList.add('ochlink713')
  84. link.element.style.backgroundColor = 'rgba(255, 150, 80, 0.4)'
  85. }
  86.  
  87. function handleResult (link, result, errorstring) {
  88. if (result === 1) {
  89. linkOnline(link)
  90. } else if (result === 0) {
  91. linkOffline(link)
  92. } else if (result === -1) {
  93. link.element.style.backgroundColor = 'blue'
  94. link.element.title = errorstring
  95. console.log(errorstring)
  96. } else {
  97. console.log('handleResult(link,result,errorstring) wrong resultcode: ' + result)
  98. }
  99. }
  100.  
  101. function matchHoster (str) {
  102. // Return name of first hoster that matches, otherwise return false
  103. for (const name in OCH) {
  104. for (let i = 0; i < OCH[name].pattern.length; i++) {
  105. if (OCH[name].pattern[i].test(str)) {
  106. return name
  107. }
  108. }
  109. }
  110. return false
  111. }
  112.  
  113. let stylesheetAdded = false
  114. function addStylesheet () {
  115. if (stylesheetAdded) {
  116. return
  117. }
  118. document.head.appendChild(document.createElement('style')).innerHTML = `
  119. .ochlink713 {
  120. transition: background-color 700ms;
  121. }
  122. `
  123. stylesheetAdded = true
  124. }
  125.  
  126. function findLinks () {
  127. addStylesheet()
  128.  
  129. links = []
  130.  
  131. // Normalize hoster object: Replace single patterns with arrays [RegExp]
  132. for (const name in OCH) {
  133. if (!Array.isArray(OCH[name].pattern)) {
  134. OCH[name].pattern = [OCH[name].pattern]
  135. }
  136. }
  137.  
  138. // Find all text nodes that contain "http://"
  139. const nodes = []
  140. const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
  141. acceptNode: function (node) {
  142. if (node.parentNode.href || node.parentNode.parentNode.href || node.parentNode.parentNode.parentNode.href) { return NodeFilter.FILTER_REJECT }
  143. if (node.parentNode.tagName === 'TEXTAREA' || node.parentNode.parentNode.tagName === 'TEXTAREA') { return NodeFilter.FILTER_REJECT }
  144. if (node.data.match(/(\s|^)https?:\/\/\w+/)) { return NodeFilter.FILTER_ACCEPT }
  145. }
  146. }, false)
  147. let node = walk.nextNode()
  148. while (node) {
  149. nodes.push(node)
  150. node = walk.nextNode()
  151. }
  152.  
  153. // For each found text nodes check whether the URL is a valid OCH URL
  154. for (let i = 0; i < nodes.length && i < MAXTEXTNODES; i++) {
  155. if (nodes[i].data === '') {
  156. continue
  157. }
  158. const httpPosition = nodes[i].data.indexOf('http')
  159. if (httpPosition === -1) {
  160. continue
  161. }
  162.  
  163. let urlnode
  164. if (httpPosition > 0) {
  165. urlnode = nodes[i].splitText(httpPosition) // Split leading text
  166. } else {
  167. urlnode = nodes[i]
  168. }
  169. const stop = urlnode.data.match(/$|\s/)[0] // Find end of URL
  170. if (stop !== '') { // If empty string, we found $ end of string
  171. const nextnode = urlnode.splitText(urlnode.data.indexOf(stop)) // Split trailing text
  172. if (nextnode.data !== '' && nextnode.data.indexOf('http') !== -1) { // The trailing text might contain another URL
  173. nodes.push(nextnode)
  174. }
  175. }
  176.  
  177. // Check whether the URL is a OCH. If so, create an <a> element
  178. const url = urlnode.data
  179. const mh = matchHoster(url)
  180. if (mh !== false) {
  181. const a = document.createElement('a')
  182. a.href = url
  183. a.appendChild(urlnode.parentNode.replaceChild(a, urlnode))
  184. links.push({
  185. hoster: mh,
  186. url: url,
  187. element: a
  188. })
  189. }
  190. }
  191.  
  192. // Find actual <a> links
  193. const deleteNodes = []
  194. const al = document.getElementsByTagName('a')
  195. for (let i = 0; i < al.length; i++) {
  196. if (al[i].dataset.linkValidatedAs) {
  197. if (deleteOfflineLinks && al[i].dataset.linkValidatedAs === 'offline') {
  198. deleteNodes.push(al[i])
  199. }
  200. continue // Link was already checked
  201. }
  202. const mH = matchHoster(al[i].href)
  203. if (mH !== false) {
  204. links.push({
  205. hoster: mH,
  206. url: al[i].href,
  207. element: al[i]
  208. })
  209. }
  210. }
  211.  
  212. deleteNodes.forEach(e => e.remove())
  213.  
  214. return links.length
  215. }
  216.  
  217. function checkLinks () {
  218. // Check all links by calling the hoster's check function
  219. for (let i = 0; i < links.length; i++) {
  220. if (links[i] && OCH[links[i].hoster].check && typeof (OCH[links[i].hoster].check) === 'function') {
  221. linkWaiting(links[i])
  222. OCH[links[i].hoster].check(links[i], handleResult)
  223. }
  224. }
  225. }
  226.  
  227. function toggleCheck () {
  228. if (!rq.hasRunning()) {
  229. // Highlight links and check them
  230. rq.resetTotal()
  231. const n = findLinks()
  232. if (n > 0) {
  233. checkLinks()
  234. }
  235. } else {
  236. // Abort all requests
  237. rq.abort()
  238. }
  239. }
  240.  
  241. document.addEventListener('keydown', function (ev) {
  242. if (ev.keyCode === 27) {
  243. deleteOfflineLinks = ev.shiftKey
  244. toggleCheck()
  245. }
  246. }, false)
  247.  
  248. // Manual check from menu
  249. GM.registerMenuCommand('Check links', function () {
  250. toggleCheck()
  251. })
  252.  
  253. // Manual check from menu
  254. GM.registerMenuCommand('Remove offline links', function () {
  255. deleteOfflineLinks = true
  256. toggleCheck()
  257. })
  258. })()