Greasy Fork is available in English.

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

Pasang skrip ini?
Sugesti pemilik skrip

Kamu mungkin juga suka Multi-OCH Helper.

Pasang skrip ini
  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 24.0
  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, crypto */
  29. /* jshint asi: true, esversion: 8 */
  30.  
  31. (function () {
  32. 'use strict'
  33.  
  34. // Maximal number of links that are checked (per website)
  35. const MAXREQUESTS = 2000
  36.  
  37. // Maximal number of links that are checked in parallel (per website)
  38. const MAXPARALLELCONNECTIONS = 50
  39.  
  40. // Maximal number of DOM nodes that are examined. Decrease this number on slow machines.
  41. const MAXTEXTNODES = 10000
  42.  
  43. // 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.
  44. const MAXDOWNLOADSIZE = 2000 // kilobytes
  45.  
  46. /*
  47. // Export
  48. var mypatterns = [];
  49. var mynames = [];
  50. var myurls = [];
  51. for(var key in OCH) {
  52. var o = OCH[key];
  53. if((""+o.check).length > 30) { // If check function implemented
  54. mypatterns.push(o.pattern.toString());
  55. mynames.push("'"+key+"'");
  56. myurls.push(" - ["+o.title+"]("+o.homepage+")");
  57. }
  58. }
  59.  
  60. alert(mypatterns.join(",\n"));
  61. alert(mynames.join(",\n"));
  62. alert(myurls.join("\n"));
  63. */
  64.  
  65. let links = [] // [ { hoster: "", url: "", element: DOMNode} , ... ]
  66. let deleteOfflineLinks = false
  67. const rq = new RequestQueue(MAXPARALLELCONNECTIONS, MAXREQUESTS)
  68.  
  69. // Get OCH list
  70. const OCH = getOCH(rq, MAXDOWNLOADSIZE)
  71.  
  72. function linkOffline (link) {
  73. link.element.style.backgroundColor = 'rgba(255, 0, 20, 0.5)'
  74. link.element.dataset.linkValidatedAs = 'offline'
  75. if (deleteOfflineLinks) {
  76. link.element.remove()
  77. }
  78. }
  79. function linkOnline (link) {
  80. link.element.style.backgroundColor = 'rgba(70, 255 ,0, 0.5)'
  81. link.element.dataset.linkValidatedAs = 'online'
  82. highlightInSidebar(link)
  83. }
  84. function linkWaiting (link) {
  85. link.element.classList.add('ochlink713')
  86. link.element.style.backgroundColor = 'rgba(255, 150, 80, 0.4)'
  87. }
  88.  
  89. function handleResult (link, result, errorstring) {
  90. if (result === 1) {
  91. linkOnline(link)
  92. } else if (result === 0) {
  93. linkOffline(link)
  94. } else if (result === -1) {
  95. link.element.style.backgroundColor = 'blue'
  96. link.element.title = errorstring
  97. console.log(errorstring)
  98. } else {
  99. console.log('handleResult(link,result,errorstring) wrong resultcode: ' + result)
  100. }
  101. }
  102.  
  103. let sidebar = null
  104. function createSidebar () {
  105. sidebar = document.body.appendChild(document.createElement('div'))
  106. sidebar.classList.add('och453_sidebar')
  107. sidebar.addEventListener('click', function (ev) {
  108. if (ev.target === sidebar) {
  109. window.scrollTo(0, ev.layerY / sidebar.clientHeight * document.body.clientHeight)
  110. } else if ('linkId' in ev.target.dataset) {
  111. document.querySelector(`.link_${ev.target.dataset.linkId}`).scrollIntoView()
  112. }
  113. })
  114. document.head.appendChild(document.createElement('style')).innerHTML = `
  115. .och453_sidebar {
  116. position: fixed;
  117. top: 17px;
  118. right: 0;
  119. bottom:17px;
  120. width: 20px;
  121. background-color: rgba(255, 255, 255, 0.5);
  122. z-index: 1000
  123. }
  124. .och453_line {
  125. position: absolute;
  126. height: 3px;
  127. width: 100%;
  128. background-color:rgba(70, 255 ,0, 0.5);
  129. cursor:pointer
  130. }
  131. `
  132. }
  133.  
  134. function cumulativeOffset (element) {
  135. // Count scrollable parent elements
  136. let counter = 0
  137. let parent = element.parentElement
  138. while (parent && parent !== document.documentElement) {
  139. if (parent.scrollHeight > parent.clientHeight) {
  140. counter++
  141. }
  142. parent = parent.parentElement
  143. }
  144.  
  145. // Skip scrollable parent elements except the top-most one
  146. parent = element.parentElement
  147. let i = 0
  148. while (parent && parent !== document.documentElement) {
  149. if (parent.scrollHeight > parent.clientHeight) {
  150. i++
  151. if (i === counter) {
  152. break
  153. }
  154. }
  155. parent = parent.parentElement
  156. }
  157.  
  158. // Calculate offset
  159. let top = 0
  160. while (parent && parent !== document.documentElement) {
  161. top += parent.offsetTop
  162. parent = parent.offsetParent
  163. }
  164. return top
  165. }
  166.  
  167. function highlightInSidebar (link) {
  168. if (!sidebar) {
  169. createSidebar()
  170. }
  171.  
  172. const top = cumulativeOffset(link.element)
  173. const totalHeight = document.body.clientHeight
  174. const topPercentage = top / totalHeight * 100
  175. const line = sidebar.appendChild(document.createElement('div'))
  176. line.classList.add('och453_line')
  177. line.style.top = `${topPercentage}%`
  178.  
  179. const id = crypto.randomUUID()
  180. link.element.classList.add(`link_${id}`)
  181. line.dataset.linkId = id
  182. }
  183.  
  184. function matchHoster (str) {
  185. // Return name of first hoster that matches, otherwise return false
  186. for (const name in OCH) {
  187. for (let i = 0; i < OCH[name].pattern.length; i++) {
  188. if (OCH[name].pattern[i].test(str)) {
  189. return name
  190. }
  191. }
  192. }
  193. return false
  194. }
  195.  
  196. let stylesheetAdded = false
  197. function addStylesheet () {
  198. if (stylesheetAdded) {
  199. return
  200. }
  201. document.head.appendChild(document.createElement('style')).innerHTML = `
  202. .ochlink713 {
  203. transition: background-color 700ms;
  204. }
  205. `
  206. stylesheetAdded = true
  207. }
  208.  
  209. function findLinks () {
  210. addStylesheet()
  211.  
  212. links = []
  213.  
  214. // Normalize hoster object: Replace single patterns with arrays [RegExp]
  215. for (const name in OCH) {
  216. if (!Array.isArray(OCH[name].pattern)) {
  217. OCH[name].pattern = [OCH[name].pattern]
  218. }
  219. }
  220.  
  221. // Find all text nodes that contain "http://"
  222. const nodes = []
  223. const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
  224. acceptNode: function (node) {
  225. if (node.parentNode.href || node.parentNode.parentNode.href || node.parentNode.parentNode.parentNode.href) { return NodeFilter.FILTER_REJECT }
  226. if (node.parentNode.tagName === 'TEXTAREA' || node.parentNode.parentNode.tagName === 'TEXTAREA') { return NodeFilter.FILTER_REJECT }
  227. if (node.data.match(/(\s|^)https?:\/\/\w+/)) { return NodeFilter.FILTER_ACCEPT }
  228. }
  229. }, false)
  230. let node = walk.nextNode()
  231. while (node) {
  232. nodes.push(node)
  233. node = walk.nextNode()
  234. }
  235.  
  236. // For each found text nodes check whether the URL is a valid OCH URL
  237. for (let i = 0; i < nodes.length && i < MAXTEXTNODES; i++) {
  238. if (nodes[i].data === '') {
  239. continue
  240. }
  241. const httpPosition = nodes[i].data.indexOf('http')
  242. if (httpPosition === -1) {
  243. continue
  244. }
  245.  
  246. let urlnode
  247. if (httpPosition > 0) {
  248. urlnode = nodes[i].splitText(httpPosition) // Split leading text
  249. } else {
  250. urlnode = nodes[i]
  251. }
  252. const stop = urlnode.data.match(/$|\s/)[0] // Find end of URL
  253. if (stop !== '') { // If empty string, we found $ end of string
  254. const nextnode = urlnode.splitText(urlnode.data.indexOf(stop)) // Split trailing text
  255. if (nextnode.data !== '' && nextnode.data.indexOf('http') !== -1) { // The trailing text might contain another URL
  256. nodes.push(nextnode)
  257. }
  258. }
  259.  
  260. // Check whether the URL is a OCH. If so, create an <a> element
  261. const url = urlnode.data
  262. const mh = matchHoster(url)
  263. if (mh !== false) {
  264. const a = document.createElement('a')
  265. a.href = url
  266. a.appendChild(urlnode.parentNode.replaceChild(a, urlnode))
  267. links.push({
  268. hoster: mh,
  269. url,
  270. element: a
  271. })
  272. }
  273. }
  274.  
  275. // Find actual <a> links
  276. const deleteNodes = []
  277. const al = document.getElementsByTagName('a')
  278. for (let i = 0; i < al.length; i++) {
  279. if (al[i].dataset.linkValidatedAs) {
  280. if (deleteOfflineLinks && al[i].dataset.linkValidatedAs === 'offline') {
  281. deleteNodes.push(al[i])
  282. }
  283. continue // Link was already checked
  284. }
  285. const mH = matchHoster(al[i].href)
  286. if (mH !== false) {
  287. links.push({
  288. hoster: mH,
  289. url: al[i].href,
  290. element: al[i]
  291. })
  292. }
  293. }
  294.  
  295. deleteNodes.forEach(e => e.remove())
  296.  
  297. return links.length
  298. }
  299.  
  300. function checkLinks () {
  301. // Check all links by calling the hoster's check function
  302. for (let i = 0; i < links.length; i++) {
  303. if (links[i] && OCH[links[i].hoster].check && typeof (OCH[links[i].hoster].check) === 'function') {
  304. linkWaiting(links[i])
  305. OCH[links[i].hoster].check(links[i], handleResult)
  306. }
  307. }
  308. }
  309.  
  310. function toggleCheck () {
  311. if (!rq.hasRunning()) {
  312. // Highlight links and check them
  313. rq.resetTotal()
  314. const n = findLinks()
  315. if (n > 0) {
  316. checkLinks()
  317. }
  318. } else {
  319. // Abort all requests
  320. rq.abort()
  321. }
  322. }
  323.  
  324. document.addEventListener('keydown', function (ev) {
  325. if (ev.key === 'Escape') {
  326. deleteOfflineLinks = ev.shiftKey
  327. toggleCheck()
  328. }
  329. }, false)
  330.  
  331. // Manual check from menu
  332. GM.registerMenuCommand('Check links', function () {
  333. toggleCheck()
  334. })
  335.  
  336. // Manual check from menu
  337. GM.registerMenuCommand('Remove offline links', function () {
  338. deleteOfflineLinks = true
  339. toggleCheck()
  340. })
  341. })()