// ==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://greasyfork.org/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)
})()