- // ==UserScript==
- // @name Multi-OCH Helper Highlight links
- // @namespace cuzi
- // @license MIT
- // @copyright 2014, cuzi (https://openuserjs.org/users/cuzi)
- // @description nopremium.pl and premiumize.me. Highlight one-click-hoster links and include Multi-OCH Helper button
- // @homepageURL https://openuserjs.org/scripts/cuzi/Multi-OCH_Helper_Highlight_links
- // @icon https://raw.githubusercontent.com/cvzi/Userscripts/master/Multi-OCH/icons/helper_highlight.png
- // @match *://*/*
- // @exclude *.yahoo.*
- // @exclude *.google.*
- // @exclude *.youtube.*
- // @exclude *.bing.com*
- // @exclude *.yandex.ru*
- // @exclude *duckduckgo.com*
- // @exclude *bandcamp.com*
- // @exclude *.tumblr.com*
- // @exclude *.wikipedia.org
- // @exclude *.amazon.*
- // @exclude *.ebay.*
- // @exclude *.netflix.com*
- // @version 10.20.5
- // @grant GM.setValue
- // @grant GM.getValue
- // @grant GM.registerMenuCommand
- // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js
- // @require https://greatest.deepsurf.us/scripts/25445-och-list/code/OCH%20List.js
- // ==/UserScript==
-
- /* globals getOCH, GM, $, alert, NodeFilter */
- /* jshint asi: true, esversion: 8 */
-
- (async function () {
- 'use strict'
-
- const MAXTEXTNODES = 10000
-
- const scriptName = 'Multi-OCH Helper Highlight links'
- const mainScriptName = 'Multi-OCH Helper'
- const syncHostersHost = 'https://cvzi.github.io/'
- const syncHostersUrl = syncHostersHost + 'Userscripts/index.html?link=sync'
- const ignoreList = [['some.love.txt', 30]]
- const chrome = ~navigator.userAgent.indexOf('Chrome')
-
- const $J = $.noConflict(true)
-
- const config = {
- mouseOverDelay: 700,
- frameWidth: '170px',
- frameHeight: '200px',
- colorHosterAvailableBG: 'green',
- colorHosterAvailableFG: 'white',
- colorHosterUnavailableBG: 'rgba(255,0,0,0.5)',
- colorHosterUnavailableFG: 'white',
- colorLinkOfflineBG: 'rgba(255,0,0,0.5)',
- colorLinkOfflineFG: 'silver',
- maxRequestsPerPage: 2,
- updateHosterStatusInterval: 24 * 7 // weekly update
- }
-
- // These hosters are supported by but have a X-Frame-Options enabled or simply do not work without javascript.
- const frameHosterWhitelist = [
-
- ]
-
- const multi = {
- 'nopremium.pl': new function () {
- const self = this
- this.key = 'nopremium.pl'
- this.name = 'NoPremium.pl'
- this.homepage = 'https://www.nopremium.pl/'
-
- const mapHosterName = (name) => name.replace('-', '')
-
- this.updateStatusURL = 'https://www.nopremium.pl/'
- this.updateStatusURLpattern = /https?:\/\/www\.nopremium\.pl.*/
-
- this.status = {}
- this.init = async function () {
- self.lastUpdate = new Date(await GM.getValue(self.key + '_status_time', 0))
- self.status = JSON.parse(await GM.getValue(self.key + '_status', '{}'))
- }
-
- this.updateStatus = async function () { // Update list of online hosters
- if (document.location.href.match(self.updateStatusURL)) {
- if ($J('#servers a[title]').length) {
- // Read and save current status of all hosters
- self.status = {}
- $J('#servers a[title]').each(function () {
- const name = mapHosterName(this.title)
- self.status[name] = true
- })
- await GM.setValue(self.key + '_status', JSON.stringify(self.status))
- await GM.setValue(self.key + '_status_time', '' + (new Date()))
- console.log(scriptName + ': ' + self.name + ': Hosters (' + Object.keys(self.status).length + ') updated')
- } else {
- console.log(scriptName + ': ' + self.name + ': Hosters: no hoster list found')
- }
- } else {
- alert(scriptName + '\n\nError: wrong update URL')
- }
- }
- this.isOnline = (hostername) => hostername in self.status && self.status[hostername]
- }(),
- 'premiumize.me': new function () {
- const self = this
- this.key = 'premiumize.me'
- this.name = 'premiumize'
- this.homepage = 'https://www.premiumize.me/'
-
- this.updateStatusURL = 'https://www.premiumize.me/hosters'
- this.updateStatusURLpattern = /https:\/\/www\.premiumize\.me\/hosters\/?/
-
- this.status = {}
- this.init = async function () {
- self.lastUpdate = new Date(await GM.getValue(self.key + '_status_time', 0))
- self.status = JSON.parse(await GM.getValue(self.key + '_status', '{}'))
- }
-
- this.updateStatus = () => null // This works only with api key, which only the main script has
- this.isOnline = (hostername) => hostername in self.status && self.status[hostername]
- }()
- }
-
- function matchHoster (str) {
- // Return name of first hoster that matches, otherwise return false
- for (let i = 0; i < ignoreList.length; i++) {
- if (str.indexOf(...ignoreList[i]) !== -1) {
- return false
- }
- }
- for (const name in OCH) {
- for (let i = 0; i < OCH[name].pattern.length; i++) {
- if (OCH[name].pattern[i].test(str)) {
- return name
- }
- }
- }
- return false
- }
-
- // All suitable urls are saved in this array:
- const alllinks = []
-
- function frameSrc (src) {
- // Prevent websites from busting the iframe by using a second "sandboxed" iframe
- // It's a kind of magic.
- const framesrc = 'data:text/html,' + encodeURIComponent(`<!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>HTML5</title>
- <style>* { margin:0px; padding:0px; }</style>
- <script>
- function addlistener() {
- window.addEventListener("message", function(e){
- if(! "iAm" in e.data || e.data.iAm != "Unrestrict.li") return;
- document.getElementById("mysandyframe").contentWindow.postMessage(e.data,'*');
- }, true);
- }
- </script>
- </head>
- <body onload="addlistener();">
- <iframe
- id="mysandyframe"
- sandbox
- scrolling="no"
- frameborder="0"
- seamless="seamless"
- src="${src}"
- style="border: 0; width:${config.frameWidth}; height:${config.frameHeight}">
- </body>
- </html>`)
- return framesrc
- }
-
- const orgDocumentTitle = document.title
- function setTitle (message) {
- if (message) {
- document.title = message + orgDocumentTitle
- } else {
- document.title = orgDocumentTitle
- }
- }
-
- function showMenu (jlink, textContent) {
- // Show the button
-
- let link
- if (textContent) {
- link = jlink.text()
- } else {
- link = jlink.attr('href')
- }
-
- // Create iframe
- let frame
- if ('info' in GM && 'scriptHandler' in GM.info && GM.info.scriptHandler === 'Greasemonkey') {
- // Greasemonkey bug https://github.com/greasemonkey/greasemonkey/issues/2574
- frame = $J('<embed></embed>')
- } else {
- frame = $J('<iframe></iframe>')
- }
-
- if (frameHosterWhitelist.indexOf(jlink.data('hoster')) === -1) {
- frame.attr('src', 'https://cvzi.github.io/Userscripts/index.html?link=' + encodeURIComponent(link))
- } else {
- frame.attr('src', frameSrc(link))
- }
-
- frame.attr('scrolling', 'no')
- frame.attr('frameborder', 'no')
- frame.attr('seamless', 'seamless')
- const p = jlink.offset()
- frame.css({
- position: 'absolute',
- background: 'white',
- width: config.frameWidth,
- height: config.frameHeight,
- top: p.top + 15,
- left: p.left,
- padding: '1px',
- boxShadow: '3px 3px 5px #444',
- border: '4px solid #9055c5',
- borderRadius: '0 5px 5px 5px',
- zIndex: 1001
- })
- frame.appendTo(document.body)
-
- // Send all links on this page to the "Multi-OCH Helper"
- setInterval(function () {
- if (frame[0].contentWindow) {
- frame[0].contentWindow.postMessage({ iAm: 'Unrestrict.li', type: 'alllinks', links: alllinks, loc: document.location.href }, '*')
- }
- }, 500)
-
- // Check whether more links are selected
- const sel = window.getSelection()
- const selelectedLinks = []
- if (!sel.isCollapsed) {
- for (let j = 0; j < sel.rangeCount; j++) {
- const frag = sel.getRangeAt(j).cloneContents()
- const span = document.createElement('span')
- span.appendChild(frag)
- const a = span.getElementsByTagName('a')
- for (let i = 0; i < a.length; i++) {
- const url = a[i].href
- const m = matchHoster(url)
- if (url && m !== false) {
- selelectedLinks.push(url)
- }
- }
- }
- }
- if (selelectedLinks.length > 0) {
- const iv = setInterval(function () {
- if (frame[0].contentWindow) {
- frame[0].contentWindow.postMessage({ iAm: 'Unrestrict.li', type: 'selectedlinks', links: selelectedLinks, loc: document.location.href }, '*')
- }
- }, 500)
- window.setTimeout(() => clearInterval(iv), 10000)
- }
-
- // Close frame on first click and prevent the <a>-element from opening a new window
- jlink.data('onclick', jlink[0].onclick)
- jlink[0].onclick = null
- jlink.one('click', function (event) {
- event.stopImmediatePropagation()
- event.preventDefault()
- const jthis = $J(this)
-
- // Close frame
- frame.remove()
- // Restore window title
- setTitle()
- // Restore onclick event
- this.onclick = jthis.data('onclick')
- // Restore mouseover event
- jthis.data('mouseOverAvailable', true)
- jthis.data('mouseOverTimeout', false)
-
- return false
- })
- }
-
- let firstAttach = true
- const attachEvents = function () {
- const links = []
-
- // Normalize hoster object: Replace single patterns with arrays [RegExp]
- if (firstAttach) {
- for (const name in OCH) {
- if (!Array.isArray(OCH[name].pattern)) {
- OCH[name].pattern = [OCH[name].pattern]
- }
- }
- firstAttach = false
- }
-
- // Find all text nodes that contain "http://"
- const nodes = []
- const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
- acceptNode: function (node) {
- if (node.parentNode.href || node.parentNode.parentNode.href || node.parentNode.parentNode.parentNode.href) {
- return NodeFilter.FILTER_REJECT
- }
- if (node.parentNode.tagName === 'TEXTAREA' || node.parentNode.parentNode.tagName === 'TEXTAREA') {
- return NodeFilter.FILTER_REJECT
- }
- if (node.data.match(/(\s|^)https?:\/\/\w+/)) {
- return NodeFilter.FILTER_ACCEPT
- }
- }
- }, false)
- let node = walk.nextNode()
- while (node) {
- nodes.push(node)
- node = walk.nextNode()
- }
-
- // For each found text nodes check whether the URL is a valid OCH URL
- for (let i = 0; i < nodes.length && i < MAXTEXTNODES; i++) {
- if (nodes[i].data === '') {
- continue
- }
- const httpPosition = nodes[i].data.indexOf('http')
- if (httpPosition === -1) {
- continue
- }
-
- let urlnode
- if (httpPosition > 0) {
- urlnode = nodes[i].splitText(httpPosition) // Split leading text
- } else {
- urlnode = nodes[i]
- }
- const stop = urlnode.data.match(/$|\s/)[0] // Find end of URL
- if (stop !== '') { // If empty string, we found $ end of string
- const nextnode = urlnode.splitText(urlnode.data.indexOf(stop)) // Split trailing text
- if (nextnode.data !== '' && nextnode.data.indexOf('http') !== -1) { // The trailing text might contain another URL
- nodes.push(nextnode)
- }
- }
-
- // Check whether the URL is a OCH. If so, create an <a> element
- const url = urlnode.data
- const m = matchHoster(url)
- if (url && url && m !== false) {
- // Create <a> element
- const a = document.createElement('a')
- a.href = url
- a.appendChild(urlnode.parentNode.replaceChild(a, urlnode))
-
- const li = $J(a)
- links.push({
- hoster: m,
- url,
- element: li
- })
- alllinks.push(url)
- }
- }
-
- // Find actual <a> links
- const al = document.getElementsByTagName('a')
- for (let i = 0; i < al.length; i++) {
- if (al[i].dataset && al[i].dataset.linkValidatedAs) {
- continue // Link was already checked
- }
- const url = al[i].href
- const mH = matchHoster(url)
- if (mH !== false) {
- const li = $J(al[i])
- links.push({
- hoster: mH,
- url,
- element: li
- })
- alllinks.push(url)
- }
- }
-
- // Attach mouseover/out events to all the links
- for (let i = 0; i < links.length; i++) {
- const a = links[i].element
- const hoster = links[i].hoster
-
- if ('attached' in links[i] || a.data('hoster')) { // Already attached 6
- continue
- }
-
- if (OCH[hoster].multi.length === 0) { // Not supported by nopremium.pl according to hardcoded rules
- continue
- }
- let notsupported = true
- for (const debrid in multi) {
- if (multi[debrid].isOnline(hoster)) {
- notsupported = false
- break
- }
- }
-
- if (notsupported) {
- continue // Not supported by nopremium.pl according to status
- }
-
- links[i].attached = true
-
- // if(links[i].data('hosterAvailable')) {
- // links[i].attr("style","background:"+config.colorHosterAvailableBG+"; color:"+config.colorHosterAvailableFG+";");
- // } else {
- // links[i].attr("style","background:"+config.colorHosterUnavailableBG+"; color:"+config.colorHosterUnavailableFG+";");
- // }
- a.attr('style', 'background:' + config.colorHosterAvailableBG + '; color:' + config.colorHosterAvailableFG + ';')
-
- a.data('hoster', hoster)
-
- a.data('mouseOverAvailable', true)
- a.data('mouseOverTimeout', false)
- a.on({
- mouseover: function () {
- const link = $J(this)
-
- if (!link.data('mouseOverAvailable')) {
- return
- }
- link.data('mouseOverTimeout', setTimeout(function () {
- if (!link.data('mouseOverAvailable')) {
- return
- }
- link.data('mouseOverAvailable', false)
- showMenu(link)
- }, config.mouseOverDelay))
- },
- mouseout: function () {
- const link = $J(this)
-
- if (link.data('mouseOverTimeout') !== false) {
- clearTimeout(link.data('mouseOverTimeout'))
- link.data('mouseOverTimeout', false)
- }
- }
- })
- }
-
- return links.length
- }
-
- // Get OCH list
- const OCH = getOCH()
-
- // Init hosters
- for (const key in multi) {
- await multi[key].init()
- }
-
- // Manual refresh from menu
- GM.registerMenuCommand('Find links', () => attachEvents())
-
- // This is the start of everything
- let numberFoundLinks = 0
- window.setTimeout(function () {
- numberFoundLinks = attachEvents()
- }, 0)
- window.setTimeout(function () {
- if (numberFoundLinks === 0) {
- numberFoundLinks = attachEvents()
- }
- }, 1500) // Let's try again.
-
- // Update hoster status
- for (const key in multi) {
- if (multi[key].updateStatusURLpattern.test(document.location.href)) {
- multi[key].updateStatus()
- break
- }
- }
-
- // Create iframes to update hoster status:
- const now = new Date()
- for (const key in multi) {
- if ((now - multi[key].lastUpdate) > (7 * 24 * 60 * 60 * 1000)) {
- if (document.getElementById('multiochhelper')) {
- // Button is visible on this page
- window.setTimeout(() => window.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*'), 1000)
- } else if (chrome) {
- // Chrome: we can use iframe to load Multi-OCH_Helper script in the frame
- const $iframe = $J('<iframe>').appendTo(document.body)
- $iframe.bind('load', function () {
- const frame = this
- window.setTimeout(() => $J(frame).remove(), 4000)
-
- if ($iframe[0].contentWindow) {
- $iframe[0].contentWindow.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*')
- }
- })
- $iframe.attr('src', syncHostersUrl)
- } else {
- // Greasemonkey: we need to open a new tab to communicate with the Multi-OCH_Helper script
- if (document.location.href.startsWith(syncHostersHost)) {
- window.setTimeout(() => window.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*'), 1000)
- } else {
- const w = window.open(syncHostersUrl)
- window.setTimeout(function () {
- if (w) {
- w.postMessage({ iAm: 'Unrestrict.li', type: 'requesthosterstatus' }, '*')
- }
- window.setTimeout(function () {
- if (w) {
- w.close()
- }
- }, 3000)
- }, 1000)
- }
- }
- break
- }
- }
-
- // Handle messages from the button script
- window.addEventListener('message', async function (e) {
- if (typeof e.data !== 'object' || !('iAm' in e.data) || e.data.iAm !== 'Unrestrict.li') {
- return
- }
-
- switch (e.data.type) {
- case 'alert':
- // Alert on page, not in frame
- alert(e.data.str)
- break
-
- case 'title':
- // Alert on page, not in frame
- setTitle(e.data.str)
- break
-
- case 'findLinks':
- // Research links
- window.setTimeout(function () {
- numberFoundLinks = attachEvents()
- }, 0)
- break
-
- case 'hosterstatus': {
- // Update hoster status, this script has no API access on premiumize, so it cannot update the hosters itself
- const data = JSON.parse(e.data.str)
- const result = {}
- for (const key in multi) {
- if (data && key in data) {
- await GM.setValue(key + '_status', JSON.stringify(data[key]))
- result[key] = Object.keys(data[key]).length
- multi[key].status = data[key]
- }
- await GM.setValue(key + '_status_time', '' + (new Date()))
- }
- console.log(scriptName + ': Received hoster status from ' + mainScriptName + ': ' + JSON.stringify(result))
- break
- }
- }
- }, true)
- })()