Multi-OCH Helper Highlight links

nopremium.pl and premiumize.me. Highlight one-click-hoster links and include Multi-OCH Helper button

Install this script?
Author's suggested script

You may also like Multi-OCH Helper.

Install this script
  1. // ==UserScript==
  2. // @name Multi-OCH Helper Highlight links
  3. // @namespace cuzi
  4. // @license MIT
  5. // @copyright 2014, cuzi (https://openuserjs.org/users/cuzi)
  6. // @description nopremium.pl and premiumize.me. Highlight one-click-hoster links and include Multi-OCH Helper button
  7. // @homepageURL https://openuserjs.org/scripts/cuzi/Multi-OCH_Helper_Highlight_links
  8. // @icon https://raw.githubusercontent.com/cvzi/Userscripts/master/Multi-OCH/icons/helper_highlight.png
  9. // @match *://*/*
  10. // @exclude *.yahoo.*
  11. // @exclude *.google.*
  12. // @exclude *.youtube.*
  13. // @exclude *.bing.com*
  14. // @exclude *.yandex.ru*
  15. // @exclude *duckduckgo.com*
  16. // @exclude *bandcamp.com*
  17. // @exclude *.tumblr.com*
  18. // @exclude *.wikipedia.org
  19. // @exclude *.amazon.*
  20. // @exclude *.ebay.*
  21. // @exclude *.netflix.com*
  22. // @version 10.20.5
  23. // @grant GM.setValue
  24. // @grant GM.getValue
  25. // @grant GM.registerMenuCommand
  26. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js
  27. // @require https://greatest.deepsurf.us/scripts/25445-och-list/code/OCH%20List.js
  28. // ==/UserScript==
  29.  
  30. /* globals getOCH, GM, $, alert, NodeFilter */
  31. /* jshint asi: true, esversion: 8 */
  32.  
  33. (async function () {
  34. 'use strict'
  35.  
  36. const MAXTEXTNODES = 10000
  37.  
  38. const scriptName = 'Multi-OCH Helper Highlight links'
  39. const mainScriptName = 'Multi-OCH Helper'
  40. const syncHostersHost = 'https://cvzi.github.io/'
  41. const syncHostersUrl = syncHostersHost + 'Userscripts/index.html?link=sync'
  42. const ignoreList = [['some.love.txt', 30]]
  43. const chrome = ~navigator.userAgent.indexOf('Chrome')
  44.  
  45. const $J = $.noConflict(true)
  46.  
  47. const config = {
  48. mouseOverDelay: 700,
  49. frameWidth: '170px',
  50. frameHeight: '200px',
  51. colorHosterAvailableBG: 'green',
  52. colorHosterAvailableFG: 'white',
  53. colorHosterUnavailableBG: 'rgba(255,0,0,0.5)',
  54. colorHosterUnavailableFG: 'white',
  55. colorLinkOfflineBG: 'rgba(255,0,0,0.5)',
  56. colorLinkOfflineFG: 'silver',
  57. maxRequestsPerPage: 2,
  58. updateHosterStatusInterval: 24 * 7 // weekly update
  59. }
  60.  
  61. // These hosters are supported by but have a X-Frame-Options enabled or simply do not work without javascript.
  62. const frameHosterWhitelist = [
  63.  
  64. ]
  65.  
  66. const multi = {
  67. 'nopremium.pl': new function () {
  68. const self = this
  69. this.key = 'nopremium.pl'
  70. this.name = 'NoPremium.pl'
  71. this.homepage = 'https://www.nopremium.pl/'
  72.  
  73. const mapHosterName = (name) => name.replace('-', '')
  74.  
  75. this.updateStatusURL = 'https://www.nopremium.pl/'
  76. this.updateStatusURLpattern = /https?:\/\/www\.nopremium\.pl.*/
  77.  
  78. this.status = {}
  79. this.init = async function () {
  80. self.lastUpdate = new Date(await GM.getValue(self.key + '_status_time', 0))
  81. self.status = JSON.parse(await GM.getValue(self.key + '_status', '{}'))
  82. }
  83.  
  84. this.updateStatus = async function () { // Update list of online hosters
  85. if (document.location.href.match(self.updateStatusURL)) {
  86. if ($J('#servers a[title]').length) {
  87. // Read and save current status of all hosters
  88. self.status = {}
  89. $J('#servers a[title]').each(function () {
  90. const name = mapHosterName(this.title)
  91. self.status[name] = true
  92. })
  93. await GM.setValue(self.key + '_status', JSON.stringify(self.status))
  94. await GM.setValue(self.key + '_status_time', '' + (new Date()))
  95. console.log(scriptName + ': ' + self.name + ': Hosters (' + Object.keys(self.status).length + ') updated')
  96. } else {
  97. console.log(scriptName + ': ' + self.name + ': Hosters: no hoster list found')
  98. }
  99. } else {
  100. alert(scriptName + '\n\nError: wrong update URL')
  101. }
  102. }
  103. this.isOnline = (hostername) => hostername in self.status && self.status[hostername]
  104. }(),
  105. 'premiumize.me': new function () {
  106. const self = this
  107. this.key = 'premiumize.me'
  108. this.name = 'premiumize'
  109. this.homepage = 'https://www.premiumize.me/'
  110.  
  111. this.updateStatusURL = 'https://www.premiumize.me/hosters'
  112. this.updateStatusURLpattern = /https:\/\/www\.premiumize\.me\/hosters\/?/
  113.  
  114. this.status = {}
  115. this.init = async function () {
  116. self.lastUpdate = new Date(await GM.getValue(self.key + '_status_time', 0))
  117. self.status = JSON.parse(await GM.getValue(self.key + '_status', '{}'))
  118. }
  119.  
  120. this.updateStatus = () => null // This works only with api key, which only the main script has
  121. this.isOnline = (hostername) => hostername in self.status && self.status[hostername]
  122. }()
  123. }
  124.  
  125. function matchHoster (str) {
  126. // Return name of first hoster that matches, otherwise return false
  127. for (let i = 0; i < ignoreList.length; i++) {
  128. if (str.indexOf(...ignoreList[i]) !== -1) {
  129. return false
  130. }
  131. }
  132. for (const name in OCH) {
  133. for (let i = 0; i < OCH[name].pattern.length; i++) {
  134. if (OCH[name].pattern[i].test(str)) {
  135. return name
  136. }
  137. }
  138. }
  139. return false
  140. }
  141.  
  142. // All suitable urls are saved in this array:
  143. const alllinks = []
  144.  
  145. function frameSrc (src) {
  146. // Prevent websites from busting the iframe by using a second "sandboxed" iframe
  147. // It's a kind of magic.
  148. const framesrc = 'data:text/html,' + encodeURIComponent(`<!DOCTYPE html>
  149. <html lang="en">
  150. <head>
  151. <meta charset="utf-8">
  152. <title>HTML5</title>
  153. <style>* { margin:0px; padding:0px; }</style>
  154. <script>
  155. function addlistener() {
  156. window.addEventListener("message", function(e){
  157. if(! "iAm" in e.data || e.data.iAm != "Unrestrict.li") return;
  158. document.getElementById("mysandyframe").contentWindow.postMessage(e.data,'*');
  159. }, true);
  160. }
  161. </script>
  162. </head>
  163. <body onload="addlistener();">
  164. <iframe
  165. id="mysandyframe"
  166. sandbox
  167. scrolling="no"
  168. frameborder="0"
  169. seamless="seamless"
  170. src="${src}"
  171. style="border: 0; width:${config.frameWidth}; height:${config.frameHeight}">
  172. </body>
  173. </html>`)
  174. return framesrc
  175. }
  176.  
  177. const orgDocumentTitle = document.title
  178. function setTitle (message) {
  179. if (message) {
  180. document.title = message + orgDocumentTitle
  181. } else {
  182. document.title = orgDocumentTitle
  183. }
  184. }
  185.  
  186. function showMenu (jlink, textContent) {
  187. // Show the button
  188.  
  189. let link
  190. if (textContent) {
  191. link = jlink.text()
  192. } else {
  193. link = jlink.attr('href')
  194. }
  195.  
  196. // Create iframe
  197. let frame
  198. if ('info' in GM && 'scriptHandler' in GM.info && GM.info.scriptHandler === 'Greasemonkey') {
  199. // Greasemonkey bug https://github.com/greasemonkey/greasemonkey/issues/2574
  200. frame = $J('<embed></embed>')
  201. } else {
  202. frame = $J('<iframe></iframe>')
  203. }
  204.  
  205. if (frameHosterWhitelist.indexOf(jlink.data('hoster')) === -1) {
  206. frame.attr('src', 'https://cvzi.github.io/Userscripts/index.html?link=' + encodeURIComponent(link))
  207. } else {
  208. frame.attr('src', frameSrc(link))
  209. }
  210.  
  211. frame.attr('scrolling', 'no')
  212. frame.attr('frameborder', 'no')
  213. frame.attr('seamless', 'seamless')
  214. const p = jlink.offset()
  215. frame.css({
  216. position: 'absolute',
  217. background: 'white',
  218. width: config.frameWidth,
  219. height: config.frameHeight,
  220. top: p.top + 15,
  221. left: p.left,
  222. padding: '1px',
  223. boxShadow: '3px 3px 5px #444',
  224. border: '4px solid #9055c5',
  225. borderRadius: '0 5px 5px 5px',
  226. zIndex: 1001
  227. })
  228. frame.appendTo(document.body)
  229.  
  230. // Send all links on this page to the "Multi-OCH Helper"
  231. setInterval(function () {
  232. if (frame[0].contentWindow) {
  233. frame[0].contentWindow.postMessage({ iAm: 'Unrestrict.li', type: 'alllinks', links: alllinks, loc: document.location.href }, '*')
  234. }
  235. }, 500)
  236.  
  237. // Check whether more links are selected
  238. const sel = window.getSelection()
  239. const selelectedLinks = []
  240. if (!sel.isCollapsed) {
  241. for (let j = 0; j < sel.rangeCount; j++) {
  242. const frag = sel.getRangeAt(j).cloneContents()
  243. const span = document.createElement('span')
  244. span.appendChild(frag)
  245. const a = span.getElementsByTagName('a')
  246. for (let i = 0; i < a.length; i++) {
  247. const url = a[i].href
  248. const m = matchHoster(url)
  249. if (url && m !== false) {
  250. selelectedLinks.push(url)
  251. }
  252. }
  253. }
  254. }
  255. if (selelectedLinks.length > 0) {
  256. const iv = setInterval(function () {
  257. if (frame[0].contentWindow) {
  258. frame[0].contentWindow.postMessage({ iAm: 'Unrestrict.li', type: 'selectedlinks', links: selelectedLinks, loc: document.location.href }, '*')
  259. }
  260. }, 500)
  261. window.setTimeout(() => clearInterval(iv), 10000)
  262. }
  263.  
  264. // Close frame on first click and prevent the <a>-element from opening a new window
  265. jlink.data('onclick', jlink[0].onclick)
  266. jlink[0].onclick = null
  267. jlink.one('click', function (event) {
  268. event.stopImmediatePropagation()
  269. event.preventDefault()
  270. const jthis = $J(this)
  271.  
  272. // Close frame
  273. frame.remove()
  274. // Restore window title
  275. setTitle()
  276. // Restore onclick event
  277. this.onclick = jthis.data('onclick')
  278. // Restore mouseover event
  279. jthis.data('mouseOverAvailable', true)
  280. jthis.data('mouseOverTimeout', false)
  281.  
  282. return false
  283. })
  284. }
  285.  
  286. let firstAttach = true
  287. const attachEvents = function () {
  288. const links = []
  289.  
  290. // Normalize hoster object: Replace single patterns with arrays [RegExp]
  291. if (firstAttach) {
  292. for (const name in OCH) {
  293. if (!Array.isArray(OCH[name].pattern)) {
  294. OCH[name].pattern = [OCH[name].pattern]
  295. }
  296. }
  297. firstAttach = false
  298. }
  299.  
  300. // Find all text nodes that contain "http://"
  301. const nodes = []
  302. const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
  303. acceptNode: function (node) {
  304. if (node.parentNode.href || node.parentNode.parentNode.href || node.parentNode.parentNode.parentNode.href) {
  305. return NodeFilter.FILTER_REJECT
  306. }
  307. if (node.parentNode.tagName === 'TEXTAREA' || node.parentNode.parentNode.tagName === 'TEXTAREA') {
  308. return NodeFilter.FILTER_REJECT
  309. }
  310. if (node.data.match(/(\s|^)https?:\/\/\w+/)) {
  311. return NodeFilter.FILTER_ACCEPT
  312. }
  313. }
  314. }, false)
  315. let node = walk.nextNode()
  316. while (node) {
  317. nodes.push(node)
  318. node = walk.nextNode()
  319. }
  320.  
  321. // For each found text nodes check whether the URL is a valid OCH URL
  322. for (let i = 0; i < nodes.length && i < MAXTEXTNODES; i++) {
  323. if (nodes[i].data === '') {
  324. continue
  325. }
  326. const httpPosition = nodes[i].data.indexOf('http')
  327. if (httpPosition === -1) {
  328. continue
  329. }
  330.  
  331. let urlnode
  332. if (httpPosition > 0) {
  333. urlnode = nodes[i].splitText(httpPosition) // Split leading text
  334. } else {
  335. urlnode = nodes[i]
  336. }
  337. const stop = urlnode.data.match(/$|\s/)[0] // Find end of URL
  338. if (stop !== '') { // If empty string, we found $ end of string
  339. const nextnode = urlnode.splitText(urlnode.data.indexOf(stop)) // Split trailing text
  340. if (nextnode.data !== '' && nextnode.data.indexOf('http') !== -1) { // The trailing text might contain another URL
  341. nodes.push(nextnode)
  342. }
  343. }
  344.  
  345. // Check whether the URL is a OCH. If so, create an <a> element
  346. const url = urlnode.data
  347. const m = matchHoster(url)
  348. if (url && url && m !== false) {
  349. // Create <a> element
  350. const a = document.createElement('a')
  351. a.href = url
  352. a.appendChild(urlnode.parentNode.replaceChild(a, urlnode))
  353.  
  354. const li = $J(a)
  355. links.push({
  356. hoster: m,
  357. url,
  358. element: li
  359. })
  360. alllinks.push(url)
  361. }
  362. }
  363.  
  364. // Find actual <a> links
  365. const al = document.getElementsByTagName('a')
  366. for (let i = 0; i < al.length; i++) {
  367. if (al[i].dataset && al[i].dataset.linkValidatedAs) {
  368. continue // Link was already checked
  369. }
  370. const url = al[i].href
  371. const mH = matchHoster(url)
  372. if (mH !== false) {
  373. const li = $J(al[i])
  374. links.push({
  375. hoster: mH,
  376. url,
  377. element: li
  378. })
  379. alllinks.push(url)
  380. }
  381. }
  382.  
  383. // Attach mouseover/out events to all the links
  384. for (let i = 0; i < links.length; i++) {
  385. const a = links[i].element
  386. const hoster = links[i].hoster
  387.  
  388. if ('attached' in links[i] || a.data('hoster')) { // Already attached 6
  389. continue
  390. }
  391.  
  392. if (OCH[hoster].multi.length === 0) { // Not supported by nopremium.pl according to hardcoded rules
  393. continue
  394. }
  395. let notsupported = true
  396. for (const debrid in multi) {
  397. if (multi[debrid].isOnline(hoster)) {
  398. notsupported = false
  399. break
  400. }
  401. }
  402.  
  403. if (notsupported) {
  404. continue // Not supported by nopremium.pl according to status
  405. }
  406.  
  407. links[i].attached = true
  408.  
  409. // if(links[i].data('hosterAvailable')) {
  410. // links[i].attr("style","background:"+config.colorHosterAvailableBG+"; color:"+config.colorHosterAvailableFG+";");
  411. // } else {
  412. // links[i].attr("style","background:"+config.colorHosterUnavailableBG+"; color:"+config.colorHosterUnavailableFG+";");
  413. // }
  414. a.attr('style', 'background:' + config.colorHosterAvailableBG + '; color:' + config.colorHosterAvailableFG + ';')
  415.  
  416. a.data('hoster', hoster)
  417.  
  418. a.data('mouseOverAvailable', true)
  419. a.data('mouseOverTimeout', false)
  420. a.on({
  421. mouseover: function () {
  422. const link = $J(this)
  423.  
  424. if (!link.data('mouseOverAvailable')) {
  425. return
  426. }
  427. link.data('mouseOverTimeout', setTimeout(function () {
  428. if (!link.data('mouseOverAvailable')) {
  429. return
  430. }
  431. link.data('mouseOverAvailable', false)
  432. showMenu(link)
  433. }, config.mouseOverDelay))
  434. },
  435. mouseout: function () {
  436. const link = $J(this)
  437.  
  438. if (link.data('mouseOverTimeout') !== false) {
  439. clearTimeout(link.data('mouseOverTimeout'))
  440. link.data('mouseOverTimeout', false)
  441. }
  442. }
  443. })
  444. }
  445.  
  446. return links.length
  447. }
  448.  
  449. // Get OCH list
  450. const OCH = getOCH()
  451.  
  452. // Init hosters
  453. for (const key in multi) {
  454. await multi[key].init()
  455. }
  456.  
  457. // Manual refresh from menu
  458. GM.registerMenuCommand('Find links', () => attachEvents())
  459.  
  460. // This is the start of everything
  461. let numberFoundLinks = 0
  462. window.setTimeout(function () {
  463. numberFoundLinks = attachEvents()
  464. }, 0)
  465. window.setTimeout(function () {
  466. if (numberFoundLinks === 0) {
  467. numberFoundLinks = attachEvents()
  468. }
  469. }, 1500) // Let's try again.
  470.  
  471. // Update hoster status
  472. for (const key in multi) {
  473. if (multi[key].updateStatusURLpattern.test(document.location.href)) {
  474. multi[key].updateStatus()
  475. break
  476. }
  477. }
  478.  
  479. // Create iframes to update hoster status:
  480. const now = new Date()
  481. for (const key in multi) {
  482. if ((now - multi[key].lastUpdate) > (7 * 24 * 60 * 60 * 1000)) {
  483. if (document.getElementById('multiochhelper')) {
  484. // Button is visible on this page
  485. window.setTimeout(() => window.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*'), 1000)
  486. } else if (chrome) {
  487. // Chrome: we can use iframe to load Multi-OCH_Helper script in the frame
  488. const $iframe = $J('<iframe>').appendTo(document.body)
  489. $iframe.bind('load', function () {
  490. const frame = this
  491. window.setTimeout(() => $J(frame).remove(), 4000)
  492.  
  493. if ($iframe[0].contentWindow) {
  494. $iframe[0].contentWindow.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*')
  495. }
  496. })
  497. $iframe.attr('src', syncHostersUrl)
  498. } else {
  499. // Greasemonkey: we need to open a new tab to communicate with the Multi-OCH_Helper script
  500. if (document.location.href.startsWith(syncHostersHost)) {
  501. window.setTimeout(() => window.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*'), 1000)
  502. } else {
  503. const w = window.open(syncHostersUrl)
  504. window.setTimeout(function () {
  505. if (w) {
  506. w.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*')
  507. }
  508. window.setTimeout(function () {
  509. if (w) {
  510. w.close()
  511. }
  512. }, 3000)
  513. }, 1000)
  514. }
  515. }
  516. break
  517. }
  518. }
  519.  
  520. // Handle messages from the button script
  521. window.addEventListener('message', async function (e) {
  522. if (typeof e.data !== 'object' || !('iAm' in e.data) || e.data.iAm !== 'Unrestrict.li') {
  523. return
  524. }
  525.  
  526. switch (e.data.type) {
  527. case 'alert':
  528. // Alert on page, not in frame
  529. alert(e.data.str)
  530. break
  531.  
  532. case 'title':
  533. // Alert on page, not in frame
  534. setTitle(e.data.str)
  535. break
  536.  
  537. case 'findLinks':
  538. // Research links
  539. window.setTimeout(function () {
  540. numberFoundLinks = attachEvents()
  541. }, 0)
  542. break
  543.  
  544. case 'hosterstatus': {
  545. // Update hoster status, this script has no API access on premiumize, so it cannot update the hosters itself
  546. const data = JSON.parse(e.data.str)
  547. const result = {}
  548. for (const key in multi) {
  549. if (data && key in data) {
  550. await GM.setValue(key + '_status', JSON.stringify(data[key]))
  551. result[key] = Object.keys(data[key]).length
  552. multi[key].status = data[key]
  553. }
  554. await GM.setValue(key + '_status_time', '' + (new Date()))
  555. }
  556. console.log(scriptName + ': Received hoster status from ' + mainScriptName + ': ' + JSON.stringify(result))
  557. break
  558. }
  559. }
  560. }, true)
  561. })()