Multi-OCH Helper

nopremium.pl and premiumize.me. Inserts a direct download link on several one-click-hosters and some container/folder providers.

安装此脚本
作者推荐脚本

您可能也喜欢Multi-OCH Helper Highlight links

安装此脚本
  1. // ==UserScript==
  2. // @name Multi-OCH Helper
  3. // @namespace cuzi
  4. // @license MIT
  5. // @description nopremium.pl and premiumize.me. Inserts a direct download link on several one-click-hosters and some container/folder providers.
  6. // @homepageURL https://openuserjs.org/scripts/cuzi/Multi-OCH_Helper
  7. // @contributionURL https://buymeacoff.ee/cuzi
  8. // @contributionURL https://ko-fi.com/cuzicvzi
  9. // @icon https://raw.githubusercontent.com/cvzi/Userscripts/master/Multi-OCH/icons/helper.png
  10. // @version 17.1.4
  11.  
  12. // @match https://cvzi.github.io/Userscripts/index.html?link=*
  13. // @match https://www.nopremium.pl/files*
  14. // @match https://www.premiumize.me/hosters/*
  15. // @match https://www.premiumize.me/services/*
  16. // @match https://www.premiumize.me/downloader*
  17.  
  18. // @match https://*.filecrypt.cc/Container/*
  19. // @match https://*.filecrypt.cc/helper.html*
  20. // @match https://protected.to/*
  21. // @match https://rapidgator.net/folder/*
  22. // @match https://safelinking.net/p/*
  23. // @match https://multiup.org/*
  24.  
  25. // @match https://1fichier.com/*
  26. // @match https://*.1fichier.com/*
  27. // @match https://www.4shared.com/*
  28. // @match https://alfafile.net/*
  29. // @match https://*.alfafile.net/*
  30. // @match https://anonfiles.com/*
  31. // @match https://bayfiles.com/*
  32. // @match https://*.bayfiles.com/*
  33. // @match http://clicknupload.link/*
  34. // @match https://clicknupload.to/*
  35. // @match https://clicknupload.org/*
  36. // @match https://clicknupload.co/*
  37. // @match https://clicknupload.cc/*
  38. // @match https://clicknupload.to/*
  39. // @match https://clicknupload.club/*
  40. // @match https://clicknupload.click/*
  41. // @match https://clicknupload.space/*
  42. // @match https://dailyuploads.net/*
  43. // @match https://ddl.to/*
  44. // @match https://ddownload.com/*
  45. // @match https://*.dropapk.com/*
  46. // @match https://dropapk.com/*
  47. // @match https://*.drop.download.com/*
  48. // @match https://drop.download.com/*
  49. // @match https://fastclick.to/*
  50. // @match https://fastshare.cz/*
  51. // @match https://fikper.com/*
  52. // @match https://file.al/*
  53. // @match https://www.file.al/*
  54. // @match https://filefactory.com/*
  55. // @match https://www.filefactory.com/*
  56. // @match https://filenext.com/*
  57. // @match https://www.filenext.com/*
  58. // @match https://filer.net/*
  59. // @match https://filerice.com/*
  60. // @match https://filespace.com/*
  61. // @match https://filestore.to/*
  62. // @match http://fireget.com/*
  63. // @match https://fireget.com/*
  64. // @match https://hitfile.net/*
  65. // @match https://hil.to/*
  66. // @match https://isra.cloud/*
  67. // @match https://katfile.com/*
  68. // @match https://www.mediafire.com/*
  69. // @match https://mediafire.com/*
  70. // @match https://mega.nz/*
  71. // @match https://megaup.net/*
  72. // @match https://mixdrop.co/*
  73. // @match https://modsbase.com/*
  74. // @match https://nitroflare.com/*
  75. // @match https://rapidgator.net/file/*
  76. // @match https://rg.to/file/*
  77. // @match https://spicyfile.com/*
  78. // @match https://www.spicyfile.com/*
  79. // @match https://turbobit.net/*
  80. // @match https://turb.to/*
  81. // @match https://tusfiles.net/*
  82. // @match https://ubiqfile.com/*
  83. // @match https://uploadboy.com/*
  84. // @match https://uploadgig.com/*
  85. // @match https://uptobox.com/*
  86. // @match https://userscloud.com/*
  87. // @match https://usersdrive.com/*
  88. // @match https://vidoza.org/*
  89. // @match https://worldbytez.com/*
  90. // @match https://wrzucajpliki.pl/*
  91. // @match https://xubster.com/*
  92. // @match https://*.zippyshare.com/*
  93.  
  94. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js
  95. // @require https://greasyfork.org/scripts/13883-aes-js/code/aesjs.js
  96. // @grant GM.registerMenuCommand
  97. // @grant unsafeWindow
  98. // @grant GM_setClipboard
  99. // @grant GM.xmlHttpRequest
  100. // @grant GM.openInTab
  101. // @grant GM.setClipboard
  102. // @grant GM.setValue
  103. // @grant GM.getValue
  104. // @grant GM.deleteValue
  105. // @grant GM.listValues
  106. // ==/UserScript==
  107.  
  108. /* globals confirm, alert, GM, GM_setClipboard, unsafeWindow, $, atob, slowAES, cloneInto */
  109. /* eslint n/no-callback-literal: 0 */
  110. /* jshint asi: true, esversion: 8 */
  111.  
  112. (async function () {
  113. 'use strict'
  114.  
  115. // And to keep for myself whatever I may find? - Certainly. For yourself, and any friends you want to share with you.
  116.  
  117. // This program inserts a download link on One-Click-Hosters and a few folder services.
  118. // If you click on the button, the current website address (or the links on the relink website) will be sent to nopremium.pl and you'll receive a nopremium.pl download link.
  119. //
  120. // Standard actions for the button are
  121. // * left mouse click: copy the link to the clipboard
  122. // * middle/wheel click: start download of the link
  123. // * right mouse click: open the nopremium.pl website and insert the link in the text box
  124. // * hovering the mouse over the button: open a menu with all the above option
  125. //
  126.  
  127. const scriptName = 'Multi-OCH Helper'
  128. const scriptReferer = 'multiochhelper'
  129. const scriptHightligherName = 'Multi-OCH Helper Highlight links'
  130.  
  131. const chrome = ~navigator.userAgent.indexOf('Chrome')
  132. const greasemonkey = 'info' in GM && 'scriptHandler' in GM.info && GM.info.scriptHandler === 'Greasemonkey'
  133.  
  134. const config = {
  135. position: [['bottom', 'top'], ['left', 'right']],
  136. position_desc: ['vertical', 'horizontal'],
  137. position_quest: 'Position of the Button. If you use "' + scriptHightligherName + '" this has to be set to bottom left',
  138. leftClick: ['clipboard', 'download', 'showLinks', 'openWebsite', 'menu', 'sendToJD', 'none'],
  139. leftClick_desc: ['Copy link to clipboard', 'Direct download', 'Show links like on website', 'Open the multihoster website', 'Show the extended menu', 'Send links to JDownloader', 'Do nothing'],
  140. leftClick_quest: 'Action on left mouse click on button',
  141. middleClick: ['download', 'clipboard', 'showLinks', 'openWebsite', 'menu', 'sendToJD', 'none'],
  142. middleClick_desc: ['Direct download', 'Copy link to clipboard', 'Show links like on website', 'Open the multihoster website', 'Show the extended menu', 'Send links to JDownloader', 'Do nothing'],
  143. middleClick_quest: 'Action on middle mouse/wheel click on button',
  144. rightClick: ['openWebsite', 'clipboard', 'showLinks', 'download', 'menu', 'sendToJD', 'none'],
  145. rightClick_desc: ['Show links like on website', 'Copy link to clipboard', 'Direct download', 'Open the multihoster website', 'Show the extended menu', 'Send links to JDownloader', 'Do nothing'],
  146. rightClick_quest: 'Action on right mouse click on button',
  147. mouseOver: ['menu', 'clipboard', 'download', 'showLinks', 'openWebsite', 'sendToJD', 'none'],
  148. mouseOver_desc: ['Show the extended menu', 'Copy link to clipboard', 'Direct download', 'Show links like on website', 'Open the multihoster website', 'Send links to JDownloader', 'Do nothing'],
  149. mouseOver_quest: 'Action on mouse hover over button',
  150. mouseOverDelay: 'int',
  151. mouseOverDelay_range: [0, 700, 3000],
  152. mouseOverDelay_quest: 'Mouse hover time before action is executed.',
  153. mouseOverDelay_suffix: 'milliseconds',
  154. newTab: 'bool',
  155. newTab_desc: ['Open in a new tab', 'Open in the same window'],
  156. newTab_quest: 'Should websites be opened in a new tab?',
  157. updateHosterStatusInterval: 'int',
  158. updateHosterStatusInterval_range: [1, 168, 9999],
  159. updateHosterStatusInterval_quest: 'How often should the status of the hosters be updated?',
  160. updateHosterStatusInterval_prefix: 'Every',
  161. updateHosterStatusInterval_suffix: 'hours',
  162. jDownloaderSupport: 'bool',
  163. jDownloaderSupport_desc: ['Show JDownloader button if JDownloader is runnning', 'Never show JDownloader button'],
  164. jDownloaderSupport_quest: ['Show a JDownloader button in the menu?']
  165.  
  166. }
  167. const settings = {}
  168. // Load settings
  169. const savedsettings = JSON.parse(await GM.getValue('settings', '{}')) // e.g. { position : [0,1], newTab : 1 }
  170. for (const key in config) {
  171. if (key in savedsettings) { // Saved
  172. if (config[key] === 'int') { // Int
  173. settings[key] = parseInt(savedsettings[key], 10)
  174. } else if (config[key] === 'string') { // String
  175. settings[key] = savedsettings[key].toString()
  176. } else if (config[key] === 'bool') { // Bool
  177. settings[key] = (savedsettings[key] === 'true' || savedsettings[key] === true)
  178. } else if (Array.isArray(config[key][0])) { // Nested array
  179. if (!Array.isArray(savedsettings[key])) {
  180. try {
  181. const tmp = JSON.parse(savedsettings[key])
  182. if (Array.isArray(tmp)) {
  183. savedsettings[key] = tmp
  184. }
  185. } catch (e) {}
  186. }
  187. settings[key] = []
  188. for (let i = 0; i < savedsettings[key].length; i++) {
  189. settings[key].push(savedsettings[key][i])
  190. }
  191. } else { // Array
  192. settings[key] = savedsettings[key]
  193. }
  194. } else { // Default
  195. if (config[key] === 'int') { // Int
  196. settings[key] = config[key + '_range'][1]
  197. } else if (config[key] === 'string') { // String
  198. settings[key] = '' // String defaults to empty string
  199. } else if (config[key] === 'bool') { // Bool
  200. settings[key] = true
  201. } else if (Array.isArray(config[key][0])) { // Nested array defaults to first value for each array
  202. settings[key] = []
  203. for (let i = 0; i < config[key].length; i++) {
  204. settings[key].push(config[key][i][0])
  205. }
  206. } else {
  207. settings[key] = config[key][0] // Array defaults to first value
  208. }
  209. }
  210. }
  211.  
  212. const JDOWNLOADER = 'http://127.0.0.1:9666/'
  213. const SPINNERCSS = `/* http://www.designcouch.com/home/why/2013/05/23/dead-simple-pure-css-loading-spinner/ */
  214. .ochspinner {
  215. height:16px;
  216. width:16px;
  217. margin:0px auto;
  218. position:relative;
  219. animation: rotation .6s infinite linear;
  220. border-left:6px solid rgba(0,174,239,.15);
  221. border-right:6px solid rgba(0,174,239,.15);
  222. border-bottom:6px solid rgba(0,174,239,.15);
  223. border-top:6px solid rgba(0,174,239,.8);
  224. border-radius:100%;
  225. }
  226. @keyframes rotation {
  227. from {transform: rotate(0deg)}
  228. to {transform: rotate(359deg)}
  229. }
  230. `
  231. // const LOADINGBARBG = 'background: #b4e391;background: linear-gradient(to bottom, #b4e391 0%,#61c419 50%,#b4e391 100%);'
  232.  
  233. let showOneclickButton = false
  234. let showOneclickLink = ''
  235. let showOneclickFromHighlighScriptAllLinks = document.location.host === 'cvzi.github.io'
  236. let showOneclickFromHighlighScriptAllLinksLoc = false
  237. let showOneclickFromHighlighScriptAllLinksLinks = ''
  238. let showOneclickFromHighlighScriptSelectedLinks = false
  239. let showOneclickFromHighlighScriptSelectedLinksLoc = false
  240. let showOneclickFromHighlighScriptSelectedLinksLinks = ''
  241.  
  242. let linksBeforeSelection = false
  243.  
  244. const multi = {
  245. 'premiumize.me': new function () {
  246. const self = this
  247. this.config = {
  248. apikey: 'string',
  249. apikey_hidden: true,
  250. apikey_quest: 'Enter your premiumize.me API key',
  251. apikey_prefix: 'API key: ',
  252. apikey_suffix: ' find it under <a target="_blank" href="https://www.premiumize.me/account">https://www.premiumize.me/account</a>'
  253. }
  254. this.key = 'premiumize.me'
  255. this.name = 'premiumize'
  256. this.homepage = 'https://www.premiumize.me/'
  257. // this.updateStatusURL = 'https://www.premiumize.me/services';
  258. this.updateStatusURLpattern = /https:\/\/www\.premiumize\.me\/services\/?/
  259. this.updateDownloadProgressInterval = 5000
  260. this.updateDownloadProgressInterfaceInterval = 500
  261.  
  262. this.status = {}
  263.  
  264. this.init = async function () {
  265. self.status = JSON.parse(await GM.getValue(self.key + '_status', '{}'))
  266. self.lastUpdate = new Date(await GM.getValue(self.key + '_status_time', 0))
  267. }
  268.  
  269. this.settings = {}
  270. this.loadSettings = async function (silent) {
  271. // Load settings, use first value as default
  272. const savedsettings = JSON.parse(await GM.getValue(self.key + '_settings', '{}'))
  273.  
  274. for (const key in self.config) {
  275. if (key.endsWith('desc') || key.endsWith('range') || key.endsWith('quest') || key.endsWith('prefix') || key.endsWith('suffix')) {
  276. continue
  277. }
  278. if (key in savedsettings) { // Saved
  279. if (self.config[key] === 'int') { // Int
  280. self.settings[key] = parseInt(savedsettings[key], 10)
  281. } else if (self.config[key] === 'string') { // String
  282. self.settings[key] = savedsettings[key].toString()
  283. } else if (config[key] === 'bool') { // Bool
  284. self.settings[key] = savedsettings[key] === 'true' || savedsettings[key] === true
  285. } else if (Array.isArray(savedsettings[key])) { // Nested array
  286. self.settings[key] = []
  287. for (let i = 0; i < savedsettings[key].length; i++) {
  288. self.settings[key].push(savedsettings[key][i])
  289. }
  290. } else { // Array
  291. self.settings[key] = savedsettings[key]
  292. }
  293. } else { // Default
  294. if (self.config[key] === 'int') { // Int
  295. self.settings[key] = self.config[key + '_range'][1]
  296. } else if (self.config[key] === 'string') { // String
  297. self.settings[key] = '' // String defaults to empty string
  298. } else if (config[key] === 'bool') { // Bool
  299. self.settings[key] = true
  300. } else if (Array.isArray(self.config[key][0])) { // Nested array defaults to first value for each array
  301. self.settings[key] = []
  302. for (let i = 0; i < self.config[key].length; i++) {
  303. self.settings[key].push(self.config[key][i][0])
  304. }
  305. } else {
  306. self.settings[key] = self.config[key][0] // Array defaults to first value
  307. }
  308. }
  309. }
  310.  
  311. if (!self.settings.apikey && !silent) {
  312. // Try to get the apikey from the website
  313. GM.xmlHttpRequest({
  314. method: 'GET',
  315. url: self.homepage + 'account',
  316. onerror: function (response) {
  317. console.log(scriptName + ': premiumize.me API Key could not be loaded')
  318. setStatus('You have not set you premiumize.me Api key ')
  319. },
  320. onload: function (response) {
  321. let s = ''
  322. try {
  323. s = response.responseText.split('class="apipass"')[1].split('</')[0].split('>')[1]
  324. } catch (e) {
  325. }
  326. if (s) {
  327. self.settings.apikey = s
  328. GM.setValue(self.key + '_settings', JSON.stringify(self.settings))
  329.  
  330. console.log(scriptName + ': premiumize.me API Key was loaded from account and saved!')
  331. } else {
  332. setStatus('You need to set you premiumize.me Api key')
  333. }
  334. }
  335. })
  336. }
  337. }
  338.  
  339. this.updateStatus = async function () { // Update list of online hosters
  340. await self.loadSettings()
  341. if (document.location.href.match(self.updateStatusURL)) {
  342. // Read and save current status of all hosters
  343. if ($('table.table tr>td:first-child').length) {
  344. self.status = {}
  345. await GM.setValue(self.key + '_status_time', '' + (new Date()))
  346. $('table.table tr>td:first-child').each(function () {
  347. const text = $(this).text()
  348. if (text.match(/^\s*[0-9a-z-]+\.\w{0,6}\s*$/i)) {
  349. const name = text.match(/^\s*([0-9a-z-]+)\.\w{0,6}\s*$/i)[1]
  350. self.status[name.toLowerCase()] = true
  351. }
  352. })
  353. await GM.setValue(self.key + '_status', JSON.stringify(self.status))
  354. console.log(scriptName + ': ' + self.name + ': Hosters (' + Object.keys(self.status).length + ') updated')
  355. } else if (self.settings.apikey) {
  356. GM.xmlHttpRequest({
  357. method: 'GET',
  358. url: self.homepage + 'api/services/list?apikey=' + encodeURIComponent(self.settings.apikey),
  359. onerror: function (response) {
  360. console.log(scriptName + ': GM.xmlHttpRequest error: ' + self.homepage + 'api/services/list')
  361. console.log(response)
  362. },
  363. onload: async function (response) {
  364. const result = JSON.parse(response.responseText)
  365. /*
  366. { "cache": [ "uploaded.to", "filefactory.com", ... ], "directdl": [ "uploaded.to", "filefactory.com", ... ] }
  367. */
  368. if ('cache' in result && 'directdl' in result) {
  369. self.status = {}
  370. await GM.setValue(self.key + '_status_time', '' + (new Date()))
  371. result.cache.forEach(function (host) {
  372. const name = host.match(/^\s*([0-9a-z-]+)\.\w{0,6}\s*$/i)[1]
  373. self.status[name.toLowerCase()] = result.directdl.indexOf(host) !== -1
  374. })
  375.  
  376. await GM.setValue(self.key + '_status', JSON.stringify(self.status))
  377. console.log(scriptName + ': ' + self.name + ': Hosters (' + Object.keys(self.status).length + ') updated')
  378. } else {
  379. console.log(scriptName + ': GM.xmlHttpRequest error: ' + self.homepage + 'api/services/list')
  380. console.log(response)
  381. }
  382. }
  383. })
  384. } else {
  385. console.log(scriptName + ': Cannot update hosters, no html and no api key found')
  386. }
  387. } else {
  388. alert(scriptName + '\n\nError: wrong update URL')
  389. }
  390. }
  391. this.isOnline = hostername => hostername in self.status && self.status[hostername]
  392.  
  393. this.getOpenWebsiteURL = function (urls) {
  394. // Return a link to the premiumize.me website that will insert the links
  395. const url = this.homepage + 'downloader?link:' + encodeURIComponent(urls.join('\n'))
  396. return url
  397. }
  398.  
  399. this.checkLink = function (url, cb) { // check whether the link is supported and online
  400. const host = url.match(/https?:\/\/(.+?)\//)[1]
  401. let hoster = host.split('.')
  402. hoster.pop()
  403. hoster = hoster.pop().replace('-', '')
  404. cb(this.isOnline(hoster))
  405. }
  406.  
  407. this.getResults = function (urls, cb) {
  408. // cb($node,linkNumber) -- $node contains the result, linkNumber is the number of links that should be online i.e. number of hashes
  409. alert('This function does not work for ' + this.name)
  410. }
  411.  
  412. this._notLoggedIn = false
  413.  
  414. this.getLinks = async function (urls, cb) {
  415. await showConfirm('fairPointsWarning', 'You will be charged premiumize fair points for generating ' + (urls.length > 1 ? ('<b>' + urls.length + '</b> files') : ('<b>one</b> file')) + '!<br><br>Generate links?', function () { self._getLinks(urls, cb) }, function () { setStatus('Operation canceled!', 0); cb([], -1) }, self)
  416. }
  417.  
  418. this._getLinks = function (urls, cb) {
  419. setTitle('✈️' + urls.length + '🔗 ')
  420. const N = urls.length
  421. const downloadLinks = []
  422. const errors = []
  423. for (let i = 0; i < urls.length; i++) {
  424. this._addSingleTransfer(urls[i], function (downloadlink, originallink, message) {
  425. if (downloadlink) {
  426. downloadLinks.push(downloadlink)
  427. } else {
  428. errors.push([originallink, message])
  429. }
  430. })
  431. }
  432.  
  433. const checkprogress = function () {
  434. if (self._notLoggedIn) {
  435. // Stop checking and open premiumize homepage
  436. setTitle('🔑 ')
  437. setStatus(self.name + ' error: Not logged in!\nMaybe update your API key?', 0)
  438. GM.openInTab(self.homepage)
  439. cb([], -2)
  440. return
  441. }
  442.  
  443. if (N === errors.length) { // All errors
  444. setTitle('❌ ')
  445. cb(false, -1)
  446. if (errors.length === 1 && errors[0][1]) {
  447. setStatus(errors[0][1], 0)
  448. } else {
  449. alert('Errors occured\n' + errors.length + ' links failed:\n\n' + errors.join('\n'))
  450. }
  451. } else if (N === downloadLinks.length + errors.length) { // All finished
  452. setTitle(downloadLinks.length + '/' + errors.length + '✅ ')
  453. cb(downloadLinks)
  454. if (errors.length > 0) { // Errors occured
  455. alert('Errors occured\n' + errors.length + ' links failed:\n\n' + errors.join('\n'))
  456. }
  457. } else { // not finished yet
  458. setTitle(downloadLinks.length + '/' + N + '⏳ ')
  459. window.setTimeout(checkprogress, self.updateDownloadProgressInterfaceInterval)
  460. }
  461. }
  462. window.setTimeout(checkprogress, self.updateDownloadProgressInterfaceInterval * Math.max(5, N))
  463. }
  464.  
  465. this._addSingleTransfer = function (url, cb) {
  466. GM.xmlHttpRequest({
  467. method: 'POST',
  468. url: self.homepage + 'api/transfer/create',
  469. data: 'apikey=' + encodeURIComponent(self.settings.apikey) + '&src=' + encodeURIComponent(url),
  470. headers: {
  471. 'Content-Type': 'application/x-www-form-urlencoded',
  472. 'Cache-Control': 'no-cache'
  473. },
  474. onerror: function (response) {
  475. console.log(scriptName + ': GM.xmlHttpRequest error: ' + self.homepage + 'api/transfer/create')
  476. console.log(response)
  477. cb(false, url, 'GM.xmlHttpRequest error: api/transfer/create')
  478. },
  479. onload: function (response) {
  480. const result = JSON.parse(response.responseText)
  481. /*
  482. {"status":"success","type":"savetocloud","id":"gfwRtdgd5fgdfgfhgfhf","name":"test.zip"}
  483. {"status":"error","error":"duplicate","id":"gfdgd5fgFddfgfhgfhf","message":"You already have this job added."}
  484. {"status":"error","message":"This link is not available on the file hoster website"}
  485. */
  486. if ('id' in result && result.id) {
  487. window.setTimeout(function () {
  488. self._getFileFromTransfer(url, result.id, cb)
  489. }, 1000)
  490. if ('message' in result) {
  491. addStatus(result.message, -1)
  492. }
  493. } else {
  494. if ('message' in result && !self._notLoggedIn) {
  495. addStatus(result.message, -1)
  496. if (~result.message.indexOf('log')) {
  497. self._notLoggedIn = true
  498. }
  499. }
  500. cb(false, url, 'message' in result ? result.message : response.responseText)
  501. }
  502. }
  503. })
  504. }
  505.  
  506. this._getFileFromTransfer = function (url, transferId, cb) {
  507. GM.xmlHttpRequest({
  508. method: 'GET',
  509. url: self.homepage + 'api/transfer/list?apikey=' + encodeURIComponent(self.settings.apikey),
  510. headers: {
  511. 'Content-Type': 'application/x-www-form-urlencoded',
  512. 'Cache-Control': 'no-cache'
  513. },
  514. onerror: function (response) {
  515. console.log(scriptName + ': GM.xmlHttpRequest error: ' + self.homepage + 'api/transfer/list')
  516. console.log(response)
  517. cb(false, url, 'GM.xmlHttpRequest error: /api/transfer/list')
  518. },
  519. onload: function (response) {
  520. const result = JSON.parse(response.responseText)
  521. /*
  522. {
  523. "status": "success",
  524. "transfers": [
  525. {
  526. "id": "xXFDSXXFDSGD",
  527. "name": "test.zip",
  528. "message": null,
  529. "status": "finished",
  530. "progress": 0,
  531. "folder_id": "gfjdfsuigjfdoikfsadf",
  532. "file_id": "trhgf982u30fjklfsdag"
  533. }
  534. ]
  535. }
  536. {
  537. "status": "success",
  538. "transfers": [
  539. {
  540. "id":"xXFDSXXFDSGD",
  541. "name":"test.zip",
  542. "message":"Initializing Download...",
  543. "status":"running",
  544. "progress":0,
  545. "folder_id":"gfjdfsuigjfdoikfsadf",
  546. "file_id":null
  547. }
  548. ]
  549. }
  550. */
  551. if (result.status === 'success' && 'transfers' in result) {
  552. for (let i = 0; i < result.transfers.length; i++) {
  553. if (result.transfers[i].id === transferId) {
  554. if (result.transfers[i].file_id) {
  555. // Finished
  556. window.setTimeout(function () {
  557. self._getSingleLink(url, result.transfers[i].file_id, cb)
  558. }, result.transfers[i].status === 'finished' ? 10 : self.updateDownloadProgressInterval)
  559. } else {
  560. // Downloading
  561. if ('message' in result.transfers[i] && result.transfers[i].message) {
  562. setStatus(result.transfers[i].message, -1)
  563. }
  564. window.setTimeout(function () {
  565. self._getFileFromTransfer(url, transferId, cb)
  566. }, self.updateDownloadProgressInterval)
  567. }
  568.  
  569. return
  570. }
  571. }
  572. }
  573. if ('message' in result && result.message) {
  574. alert(scriptName + '\n\nCould not get /api/transfer/list\nError:\n' + result.message)
  575. }
  576. cb(false, url, 'Could not find url in transfer list')
  577. }
  578. })
  579. }
  580.  
  581. this._getSingleLink = function (url, fileId, cb) {
  582. GM.xmlHttpRequest({
  583. method: 'POST',
  584. url: self.homepage + 'api/item/details',
  585. data: 'apikey=' + encodeURIComponent(self.settings.apikey) + '&id=' + encodeURIComponent(fileId),
  586. headers: {
  587. 'Content-Type': 'application/x-www-form-urlencoded',
  588. 'Cache-Control': 'no-cache'
  589. },
  590. onerror: function (response) {
  591. console.log(scriptName + ': GM.xmlHttpRequest error: ' + self.homepage + 'api/item/details')
  592. console.log(response)
  593. cb(false, url, 'GM.xmlHttpRequest error: /api/item/details')
  594. },
  595. onload: function (response) {
  596. const result = JSON.parse(response.responseText)
  597. /*
  598. {
  599. "id": "xxXxXxxxXxxx",
  600. "name": "test.zip",
  601. "size": 156,
  602. "created_at": 1572458477,
  603. "transcode_status": "not_applicable",
  604. "folder_id": "XxXXXxxxxxx",
  605. "ip": "1.1.1.1",
  606. "acodec": "",
  607. "vcodec": "",
  608. "mime_type": "application/zip",
  609. "opensubtitles_hash": "",
  610. "resx": "",
  611. "resy": "",
  612. "duration": "",
  613. "virus_scan": "ok",
  614. "type": "file",
  615. "link": "https://down.host.example.com/dl/abcdefg/test.zip",
  616. "stream_link": null
  617. }
  618. */
  619. if ('link' in result && result.link) {
  620. cb(result.link, url)
  621. } else {
  622. window.setTimeout(function () {
  623. self._getSingleLink(url, fileId, cb)
  624. }, self.updateDownloadProgressInterval)
  625. }
  626. }
  627. })
  628. }
  629. }(),
  630.  
  631. 'nopremium.pl': new function () {
  632. const self = this
  633. this.config = {
  634. mode: ['transfer', 'premium', 'none'],
  635. mode_desc: ['Transfer User (Pakiety Transferowe)', 'Premium User (Konta Premium)', 'No account'],
  636. mode_quest: 'What kind of account do you have at nopremium.pl',
  637. downloadmode: ['direct', 'server'],
  638. downloadmode_desc: ['Direct download (TRYB SZYBKIEGO POBIERANIA)', 'Downloading via NoPremium.pl server (TRYB POBIERANIA NA SERWERY)'],
  639. downloadmode_quest: ['Which download mode do you want to use?']
  640. }
  641. this.key = 'nopremium.pl'
  642. this.name = 'NoPremium.pl'
  643. this.homepage = 'https://www.nopremium.pl/'
  644. this.updateStatusURL = 'https://www.nopremium.pl/files'
  645. this.updateStatusURLpattern = /https?:\/\/www\.nopremium\.pl\/files\/?/
  646. this.updateDownloadProgressInterval = 5000
  647. const mapHosterName = name => name.replace('-', '')
  648. this.status = {}
  649.  
  650. this.init = async function () {
  651. self.status = JSON.parse(await GM.getValue(self.key + '_status', '{}'))
  652.  
  653. self.lastUpdate = new Date(await GM.getValue(self.key + '_status_time', 0))
  654. }
  655.  
  656. this.settings = {}
  657. this.loadSettings = async function (silent) {
  658. // Load settings, use first value as default
  659. const savedsettings = JSON.parse(await GM.getValue(self.key + '_settings', '{}'))
  660.  
  661. for (const key in self.config) {
  662. if (key.endsWith('desc') || key.endsWith('range') || key.endsWith('quest') || key.endsWith('prefix') || key.endsWith('suffix')) {
  663. continue
  664. }
  665. if (key in savedsettings) { // Saved
  666. if (self.config[key] === 'int') { // Int
  667. self.settings[key] = parseInt(savedsettings[key], 10)
  668. } else if (self.config[key] === 'string') { // String
  669. self.settings[key] = savedsettings[key].toString()
  670. } else if (config[key] === 'bool') { // Bool
  671. self.settings[key] = savedsettings[key] === 'true' || savedsettings[key] === true
  672. } else if (Array.isArray(savedsettings[key])) { // Nested array
  673. self.settings[key] = []
  674. for (let i = 0; i < savedsettings[key].length; i++) {
  675. self.settings[key].push(savedsettings[key][i])
  676. }
  677. } else { // Array
  678. self.settings[key] = savedsettings[key]
  679. }
  680. } else { // Default
  681. if (self.config[key] === 'int') { // Int
  682. self.settings[key] = self.config[key + '_range'][1]
  683. } else if (self.config[key] === 'string') { // String
  684. self.settings[key] = '' // String defaults to empty string
  685. } else if (config[key] === 'bool') { // Bool
  686. self.settings[key] = true
  687. } else if (Array.isArray(self.config[key][0])) { // Nested array defaults to first value for each array
  688. self.settings[key] = []
  689. for (let i = 0; i < self.config[key].length; i++) {
  690. self.settings[key].push(self.config[key][i][0])
  691. }
  692. } else {
  693. self.settings[key] = self.config[key][0] // Array defaults to first value
  694. }
  695. }
  696. }
  697. }
  698.  
  699. this.updateStatus = async function () { // Update list of online hosters
  700. if (document.location.href.match(self.updateStatusURL)) {
  701. // Read and save current status of all hosters
  702. await GM.setValue(self.key + '_status_time', '' + (new Date()))
  703. self.status = {}
  704. $('#servers a[title]').each(function () {
  705. const name = mapHosterName(this.title)
  706. self.status[name] = true
  707. })
  708. await GM.setValue(self.key + '_status', JSON.stringify(self.status))
  709. console.log(scriptName + ': ' + self.name + ': Hosters (' + Object.keys(self.status).length + ') updated')
  710. } else {
  711. alert(scriptName + '\n\nError: wrong update URL')
  712. }
  713. }
  714. this.isOnline = hostername => hostername in self.status && self.status[hostername]
  715.  
  716. this.getOpenWebsiteURL = function (urls) {
  717. // Return a link to the nopremium.pl website that will insert the links
  718. const url = this.homepage + 'files?link:' + encodeURIComponent(urls.join('\n'))
  719. return url
  720. }
  721.  
  722. const getHashs = function (urls, cb, silent) {
  723. // cb(hashes,sizestring)
  724. setTitle('✈️ ')
  725. setStatus('Sending ' + (urls.length === 1 ? 'one link' : (urls.length + ' links')), -1)
  726. GM.xmlHttpRequest({
  727. method: 'POST',
  728. url: self.homepage + 'files',
  729. data: 'watchonline=&session=' + (Math.round(Math.random() * 1234567)) + '&links=' + encodeURIComponent(urls.join('\n')),
  730. headers: {
  731. 'Content-Type': 'application/x-www-form-urlencoded',
  732. 'Cache-Control': 'no-cache'
  733. // "Referer" : "https://www.nopremium.pl/files" // FIREFOX57
  734. },
  735. onload: function (response) {
  736. if (response.responseText.indexOf('<input type="text" name="login" placeholder="Login"/>') !== -1) {
  737. setTitle('🔑 ')
  738. setStatus(self.name + ' error: Not logged in!', 0)
  739. GM.openInTab(self.homepage)
  740. return cb([], -1)
  741. }
  742.  
  743. const hashes = []
  744. // Find hashes
  745. const re = /name="hash(\d+)" value="(\w+)"/g // <input type="checkbox" id="hash0" name="hash0" value="fab3c41988" onclick="UpdateCounter();" c
  746. let ma = re.exec(response.responseText)
  747. while (ma) {
  748. hashes.push(ma[2])
  749. ma = re.exec(response.responseText)
  750. }
  751. // Find errors
  752. ma = response.responseText.match(/Pliki nieprzetworzone \((\d+)\)/)
  753. if (ma && !silent) {
  754. addStatus('Error: ' + (parseInt(ma[1], 10) === 1 ? ('One file is offline or unsupported') : (ma[1] + ' files are offline or unsupported')), 0)
  755. }
  756.  
  757. // Find size
  758. let size = '0 Byte'
  759. if (response.responseText.indexOf('id="countSize"') !== -1) {
  760. ma = response.responseText.split('id="countSize"')[1].match(/value="(\d+.?\d*) (\w+)"/) // <input type="text" name="countSize" id="countSize" style="width:80px;" readonly="readonly" value="1.38 GB">
  761. size = ma[1] + ' ' + ma[2]
  762. }
  763.  
  764. setStatus(self.name + ' identified ' + (hashes.length === 1 ? 'one online file' : (hashes.length + ' online files')), -1)
  765. setTitle(hashes.length + '🔗 ')
  766. cb(hashes, size)
  767. }
  768. })
  769. }
  770.  
  771. this.checkLink = function (url, cb) { // check whether the link is supported and online
  772. // cb(boolresult)
  773. return getHashs([url], function (hashes, size) {
  774. cb(hashes.length === 1)
  775. }, true)
  776. }
  777.  
  778. this.getResults = function (urls, cb, hashes) {
  779. // cb($node,linkNumber) -- $node contains the result, linkNumber is the number of links that should be online i.e. number of hashes
  780. // Get download links from nopremium.pl and show the usual info about the file, that is normally shown on nopremium.pl
  781. if (typeof hashes === 'undefined') {
  782. // 1. Get hashes and show transfer warning
  783. getHashs(urls, async function (hashes, size) {
  784. if (settings.mode === 'transfer') {
  785. await showConfirm('transferWarning', 'You will be charged <b>' + size + "</b> 'Transfer' for generating " + (hashes.length > 1 ? ('<b>' + hashes.length + '</b> files') : ('<b>one</b> file')) + '!<br><br>Generate links?', function () { this.getResults(urls, cb, hashes) }, null, self)
  786. } else if (hashes.length > 0) {
  787. self.getResults(urls, cb, hashes)
  788. } else if (size === -1) { // Error was already handled (probably not logged in)
  789. console.log('getHashs->cb: Error was already handled (probably not logged in)')
  790. cb(false, -2)
  791. } else { // No files found
  792. setStatus('No online/available files', 0)
  793. cb(false, 0)
  794. }
  795. })
  796. return
  797. }
  798.  
  799. // 2. Work with hashes
  800. const $resultContainer = $('<div></div>').attr('id', 'generated-links')
  801. const mode = self.settings.downloadmode === 'direct' ? 0 : 1 // 0 -> direct , 1 -> via server
  802. GM.xmlHttpRequest({
  803. method: 'POST',
  804. url: self.homepage + 'files',
  805. data: 'insert=1&mode=' + mode + '&hh=0&hash[]=' + hashes.join('&hash[]=') + '&',
  806. headers: {
  807. 'Content-Type': 'application/x-www-form-urlencoded',
  808. 'Cache-Control': 'no-cache'
  809. // "Referer" : "https://www.nopremium.pl/files" // FIREFOX57
  810. },
  811. onload: function (response) {
  812. GM.xmlHttpRequest({
  813. method: 'POST',
  814. url: self.homepage + 'files',
  815. data: 'loadfiles=1',
  816. headers: {
  817. 'Content-Type': 'application/x-www-form-urlencoded',
  818. 'Cache-Control': 'no-cache'
  819. // "Referer" : "https://www.nopremium.pl/files" // FIREFOX57
  820. },
  821. onload: function (response) {
  822. if (mode === 0) {
  823. $resultContainer.append($('<div></div>').append(response.responseText).find('#fastFilesArea'))
  824. } else {
  825. $resultContainer.append($('<div></div>').append(response.responseText).find('#downloadFilesArea'))
  826. }
  827. $resultContainer.find('input[type=checkbox]').remove()
  828. cb($resultContainer, hashes.length)
  829. }
  830. })
  831. }
  832. })
  833. }
  834. this.getLinks = function (urls, cb) {
  835. // cb(downloadlinks)
  836.  
  837. if (this.settings.downloadmode === 'direct') {
  838. return this._getDirectLinks(urls, cb)
  839. } else {
  840. return this._getServerLinks(urls, cb)
  841. }
  842. }
  843.  
  844. this._getDirectLinks = function (urls, cb) {
  845. // Get Direct download links
  846.  
  847. this.getResults(urls, async function ($node, N) {
  848. if (!$node || N < 1) {
  849. cb(false)
  850. return
  851. }
  852.  
  853. const text = $node.html()
  854.  
  855. /*
  856. <td>16-08-2014 20:22</td>
  857. <td class="dlBox"><a href="http://direct.nopremium.pl/9091456/7895ca02bfcb2c2e43806f1079b7ff069129e/result.file"><img src="https://www.nopremium.pl/images/download_ico.png" alt="Sciagnij" title="Sciagnij"></a></td>
  858. */
  859. const files = []
  860. const re = /<td>(\d+)-(\d+)-(\d+) (\d+):(\d+)<\/td>(\s|\n)+<td class="dlBox"><a href="(.*?)"/gm
  861. let m = re.exec(text) // wholeString, 16,08,2014,20,37,#newline#,http://direct.nopremium.pl/9091456/7895ca02bfcb2c2e43806f1079b7ff069129e/result.file
  862.  
  863. while (m) {
  864. if (m[7].indexOf('//direct.nopremium.pl') === -1) {
  865. continue // Skip files via server, only use direct download links
  866. }
  867. const d = new Date(m[3], m[2], m[1], m[4], m[5], 0, 0)
  868. files.push([d, m[7]])
  869. m = re.exec(text)
  870. }
  871.  
  872. if (files.length === 0) {
  873. alert(scriptName + '\n\nAn error occured.\nCould not find download links in response.')
  874. cb(false)
  875. return
  876. }
  877. // Find youngest files by comparing their ids
  878. const pattern = /\.pl\/(\d+)\//
  879. files.sort(function (a, b) {
  880. const x = a[1].match(pattern)[1]
  881. const y = a[1].match(pattern)[1]
  882. return x > y ? -1 : x < y ? 1 : 0
  883. })
  884.  
  885. const result = []
  886. for (let i = 0; i < N; i++) {
  887. result.push(files[i][1])
  888. await cacheLink([urls[i]], files[i][0], [files[i][1]], self.key) // CACHE single URLs
  889. }
  890.  
  891. await cacheLink(urls, new Date(), result, self.key) // CACHE all URLs
  892.  
  893. cb(result)
  894. })
  895. }
  896.  
  897. this._getServerLinks = function (urls, cb) {
  898. this.getResults(urls, function ($node, N) {
  899. if (N === 0) {
  900. cb(false)
  901. } else {
  902. self._getProgress(cb, $node, N)
  903. }
  904. })
  905. }
  906.  
  907. this._getProgress = function (cb, $node, N, ids) {
  908. GM.xmlHttpRequest({
  909. method: 'POST',
  910. url: self.homepage + 'files',
  911. data: 'downloadprogress=1',
  912. headers: {
  913. 'Content-Type': 'application/x-www-form-urlencoded',
  914. 'Cache-Control': 'no-cache'
  915. // "Referer" : "https://www.nopremium.pl/files" // FIREFOX57
  916. },
  917. onerror: function () {
  918. self._getProgressBlocked = false
  919.  
  920. window.setTimeout(function () {
  921. self._getProgress(cb, $node, N, ids)
  922. }, self.updateDownloadProgressInterval)
  923. },
  924. onload: function (response) {
  925. self._getProgressBlocked = false
  926.  
  927. let data
  928. try {
  929. data = JSON.parse(response.responseText)
  930. } catch (e) {
  931. console.log(scriptName + ': ' + e)
  932. console.log(response.responseText)
  933.  
  934. if (response.responseText.indexOf('<input type="text" name="login" placeholder="Login"/>') !== -1) {
  935. setTitle('🔑 ')
  936. setStatus(self.name + ' error: Not logged in!', 0)
  937. GM.openInTab(self.homepage)
  938. cb(false, -2)
  939. } else {
  940. window.setTimeout(function () {
  941. self._getProgress(cb, $node, N, ids)
  942. }, self.updateDownloadProgressInterval)
  943. }
  944. return
  945. }
  946.  
  947. data.StandardFiles.sort(function (a, b) {
  948. const x = new Date(a.insert_date.split('-').join('/'))
  949. const y = new Date(b.insert_date.split('-').join('/'))
  950. return x > y ? -1 : x < y ? 1 : 0
  951. })
  952.  
  953. const result = []
  954. const runnning = []
  955. let percent = 0
  956. const progess = []
  957.  
  958. if (!ids) { // First run: Find the correct files: just use the first N files
  959. ids = []
  960. for (let i = 0; i < data.StandardFiles.length && i < N; i++) {
  961. ids.push(data.StandardFiles[i].id)
  962. if (data.StandardFiles[i].status === 'finish') {
  963. result.push(data.StandardFiles[i].download_url)
  964. progess.push(100)
  965. percent += 100
  966. } else {
  967. runnning.push(data.StandardFiles[i])
  968. if (parseInt(data.StandardFiles[i].status, 10) > 0) {
  969. progess.push(parseInt(data.StandardFiles[i].status, 10))
  970. percent += parseInt(data.StandardFiles[i].status, 10)
  971. }
  972. }
  973. }
  974. } else { // Consecutive runs: Use the ids from first run
  975. for (let i = 0; i < data.StandardFiles.length; i++) {
  976. if (ids.indexOf(data.StandardFiles[i].id) === -1) continue
  977. if (data.StandardFiles[i].status === 'finish') {
  978. result.push(data.StandardFiles[i].download_url)
  979. progess.push(100)
  980. percent += 100
  981. } else {
  982. runnning.push(data.StandardFiles[i])
  983. if (parseInt(data.StandardFiles[i].status, 10) > 0) {
  984. progess.push(parseInt(data.StandardFiles[i].status, 10))
  985. percent += parseInt(data.StandardFiles[i].status, 10)
  986. }
  987. }
  988. }
  989. }
  990.  
  991. /*
  992. Regarding caching in server mode:
  993. If you add a file, that is already on the server (or currently downloading), you will not be charged additional bandwith - therefore caching is not necessary at the moment.
  994. */
  995.  
  996. if (result.length === N) {
  997. setStatus((result.length === 1 ? 'One file' : (result.length + ' files')) + ' downloaded to server', 1)
  998. setTitle(result.length + '✅ ')
  999. cb(result)
  1000. } else {
  1001. // Waiting
  1002. percent = percent / N
  1003. // setStatus('Download '+result.length+'/'+N+' ('+Math.floor(percent)+'%)\n<span title="'+round(percent,2)+'%" style="display:block; width:120px; height:18px; background:white; border:1px solid black; border-radius:5px;"><span style="display:block; border-radius:5px; height:18px; width:'+Math.ceil(percent*1.2)+'px; '+LOADINGBARBG+'"> </span></span>',-1);
  1004. const dotheight = N > 2 ? 2 : 4
  1005. let h = 'Download ' + result.length + '/' + N + ' (' + Math.floor(percent) + '%)\n<div style="display:block; width:130px; height:auto; background:white; border:1px solid black; border-radius:5px; padding:2px; ">'
  1006. for (let i = 0; i < N; i++) {
  1007. if (progess[i]) {
  1008. h += '<span style="display:block; width:' + Math.ceil(progess[i] * 1.2) + 'px; height:1px; background:white; border-top:' + dotheight + 'px ' + (progess[i] > 99.9 ? 'solid' : 'dotted') + ' green; margin-bottom:1px;"></span>'
  1009. } else {
  1010. h += '<span style="display:block; width:0x; height:1px; background:white; border-top:' + dotheight + 'px dotted silver; margin-bottom:1px;"></span>'
  1011. }
  1012. }
  1013. h += '</div>'
  1014.  
  1015. setTitle(Math.floor(percent) + '%⏳ ')
  1016.  
  1017. setStatus(h)
  1018. showOnlyStatus()
  1019.  
  1020. window.setTimeout(function () {
  1021. self._getProgress(cb, $node, N, ids)
  1022. }, self.updateDownloadProgressInterval)
  1023. }
  1024. }
  1025. })
  1026. }
  1027. }()
  1028.  
  1029. }
  1030.  
  1031. const debridprovider = Object.keys(multi)
  1032. let currentdebrid = await GM.getValue('currentdebrid', debridprovider[0])
  1033.  
  1034. for (const key in multi) {
  1035. await multi[key].init()
  1036. if (key === currentdebrid) {
  1037. await multi[key].loadSettings()
  1038. continue
  1039. }
  1040. if (!greasemonkey) {
  1041. GM.registerMenuCommand(scriptName + ' - Switch to ' + multi[key].name, (function (key) {
  1042. return async function () {
  1043. if (!confirm(scriptName + '\n\nSet multi-download provider:\n' + multi[key].name)) return
  1044.  
  1045. await GM.setValue('currentdebrid', key)
  1046. currentdebrid = key
  1047. document.location.reload()
  1048. }
  1049. })(key)
  1050. )
  1051. }
  1052. }
  1053.  
  1054. if (!greasemonkey) {
  1055. GM.registerMenuCommand(scriptName + ' - Delete cached links', async function () {
  1056. if (!confirm(scriptName + '\n\nReally delete cached links?')) return
  1057.  
  1058. await GM.setValue('cachedDownloadLinks', '{}')
  1059.  
  1060. alert(scriptName + '\n\nCache is empty!')
  1061. })
  1062. GM.registerMenuCommand(scriptName + ' - Restore dialogs and warnings', async function () {
  1063. if (!confirm(scriptName + '\n\nReally restore all dialogs and warnings?')) return
  1064.  
  1065. await GM.setValue('dialogs', '[]')
  1066.  
  1067. alert(scriptName + '\n\nDialogs and warnings restored')
  1068. })
  1069. }
  1070.  
  1071. /*
  1072. function round (f, p) {
  1073. // Round f to p places after the comma
  1074. return parseFloat(parseFloat(f).toFixed(p))
  1075. }
  1076. */
  1077.  
  1078. const orgDocumentTitle = document.title
  1079. function setTitle (message) {
  1080. if (window.parent.parent !== window) {
  1081. window.parent.parent.postMessage({ iAm: 'Unrestrict.li', type: 'title', str: message }, '*')
  1082. }
  1083. if (message) {
  1084. document.title = message + orgDocumentTitle
  1085. } else {
  1086. document.title = orgDocumentTitle
  1087. }
  1088. }
  1089.  
  1090. function popUp (id, onClose, thisArg, doNotCloseOnOutsideClick) {
  1091. // Remove window scrolling
  1092. $(document.body).css('overflow', 'hidden')
  1093. let zi = getNextZIndex()
  1094. id = id || ('popup' + (new Date()).getTime())
  1095. const $par = $('<div style="position:absolute; top:0px;"></div>').attr('id', id).appendTo(document.body)
  1096. const $background = $('<div style="position:fixed; top:0px; left:0px; right:0px; bottom:0px; background:black; opacity:0.5; z-index:' + (zi++) + '"></div>').appendTo($par)
  1097. const $div = $('<div style="position:fixed; top:50px; left:100px; overflow:auto; z-index:' + (zi++) + '; background:#E6E6E6; color:Black; border:#B555C5 2px solid;border-radius:5px; padding:10px; font-family: "Ubuntu",Arial,Sans-Serif"></div>').css('maxHeight', window.innerHeight - 100).css('maxWidth', window.innerWidth - 200).appendTo($par)
  1098.  
  1099. const close = function () {
  1100. $par.remove()
  1101. if (onClose) onClose.call(thisArg)
  1102. // Restore scrolling
  1103. $(document.body).css('overflow', 'initial')
  1104. }
  1105.  
  1106. if (!doNotCloseOnOutsideClick) {
  1107. $background.click(close)
  1108. }
  1109.  
  1110. return { node: $div, close }
  1111. }
  1112.  
  1113. function configForm ($form, c, s, formid) {
  1114. for (const key in c) {
  1115. if (key.endsWith('desc') || key.endsWith('range') || key.endsWith('quest') || key.endsWith('prefix') || key.endsWith('suffix') || key.endsWith('hidden')) {
  1116. continue
  1117. }
  1118.  
  1119. const $p = $('<p>').appendTo($form)
  1120.  
  1121. if (c[key + '_quest']) {
  1122. $p.append(c[key + '_quest'])
  1123. } else {
  1124. $p.append(key)
  1125. }
  1126.  
  1127. $p.append('<br>')
  1128.  
  1129. if (c[key + '_prefix']) {
  1130. $p.append(c[key + '_prefix'] + ' ')
  1131. }
  1132.  
  1133. const hidden = (key + '_hidden') in c && c[key + '_hidden']
  1134. if (c[key] === 'int') { // Int
  1135. const $input = $('<input type="number">').addClass('form_' + formid).data('key', key).data('parse', 'int').val(s[key]).appendTo($p)
  1136. if (c[key + '_range']) {
  1137. $input.prop('min', c[key + '_range'][0])
  1138. $input.prop('max', c[key + '_range'][2])
  1139. $input.prop('title', c[key + '_range'][0] + ' - ' + c[key + '_range'][2])
  1140. }
  1141. } else if (c[key] === 'string') { // String
  1142. const $inputText = $('<input type="text">').addClass('form_' + formid).data('key', key).data('parse', 'string').appendTo($p)
  1143. if (hidden && s[key]) {
  1144. $inputText.val('## HIDDEN ##')
  1145. $inputText.data('hidden', '1')
  1146. } else {
  1147. $inputText.val(s[key])
  1148. }
  1149. } else if (c[key] === 'bool') { // Bool
  1150. const $select = $('<select></select>').addClass('form_' + formid).data('key', key).data('parse', 'bool').appendTo($p)
  1151.  
  1152. const $optionYes = $('<option></option>').val('true').appendTo($select)
  1153. if (c[key + '_desc']) {
  1154. $optionYes.html(c[key + '_desc'][0])
  1155. } else {
  1156. $optionYes.html('Yes')
  1157. }
  1158. if (s[key]) {
  1159. $optionYes[0].selected = true
  1160. }
  1161.  
  1162. const $optionNo = $('<option></option>').val('false').appendTo($select)
  1163. if (c[key + '_desc']) {
  1164. $optionNo.html(c[key + '_desc'][1])
  1165. } else {
  1166. $optionNo.html('No')
  1167. }
  1168. if (!s[key]) {
  1169. $optionNo[0].selected = true
  1170. }
  1171. } else if (Array.isArray(c[key][0])) { // Nested array
  1172. for (let j = 0; j < c[key].length; j++) {
  1173. if (c[key + '_desc'] && !Array.isArray(c[key + '_desc'][j])) {
  1174. $p.append(c[key + '_desc'][j] + ': ')
  1175. }
  1176.  
  1177. const $select = $('<select></select>').addClass('form_' + formid).data('key', key).data('index', j).appendTo($p)
  1178. for (let i = 0; i < c[key][j].length; i++) {
  1179. const $option = $('<option></option>').val(c[key][j][i]).appendTo($select)
  1180. if (c[key + '_desc'] && Array.isArray(c[key + '_desc'][0])) {
  1181. $option.html(c[key + '_desc'][j][i])
  1182. } else {
  1183. $option.html(c[key][j][i])
  1184. }
  1185. if (s[key][j] === c[key][j][i]) { $option[0].selected = true }
  1186. }
  1187. $p.append('<br>')
  1188. }
  1189. } else { // Array
  1190. const $select = $('<select></select>').addClass('form_' + formid).data('key', key).appendTo($p)
  1191. for (let i = 0; i < c[key].length; i++) {
  1192. const $option = $('<option></option>').val(c[key][i]).appendTo($select)
  1193. if (c[key + '_desc']) {
  1194. $option.html(c[key + '_desc'][i])
  1195. } else {
  1196. $option.html(c[key][i])
  1197. }
  1198. if (s[key] === c[key][i]) { $option[0].selected = true }
  1199. }
  1200. }
  1201.  
  1202. if (c[key + '_suffix']) {
  1203. $p.append(' ' + c[key + '_suffix'])
  1204. }
  1205. }
  1206. }
  1207.  
  1208. async function saveSettings (ev) {
  1209. const $body = ev.data
  1210. const $form = $body.find('.form')
  1211.  
  1212. // Save preferred hoster:
  1213. currentdebrid = $form.find('.debridhoster').val()
  1214.  
  1215. // Save options:
  1216. const newsettings = { general: {} }
  1217. for (const key in multi) {
  1218. newsettings[key] = {}
  1219. }
  1220.  
  1221. $form.find('*[class^=form_]').each(function () {
  1222. const $this = $(this)
  1223. const namespace = $this.prop('class').split('_', 2)[1]
  1224. const key = $this.data('key')
  1225. const index = $this.data('index')
  1226. let value = $this.val()
  1227. const parse = $this.data('parse')
  1228. const hiddenAndUnchanged = $this.data('hidden') && value === '## HIDDEN ##'
  1229. if (typeof index !== 'undefined') { // Nested Array
  1230. if (!(key in newsettings[namespace]) || !Array.isArray(newsettings[namespace][key])) {
  1231. newsettings[namespace][key] = []
  1232. }
  1233. newsettings[namespace][key][index] = value
  1234. } else { // Normal
  1235. if (hiddenAndUnchanged) {
  1236. value = multi[namespace].settings[key]
  1237. } else if (parse === 'int') {
  1238. value = parseInt(value, 10)
  1239. } else if (parse === 'bool') {
  1240. value = (value === 'true')
  1241. }
  1242. newsettings[namespace][key] = value
  1243. }
  1244. })
  1245.  
  1246. await GM.setValue('setup', true)
  1247. await GM.setValue('currentdebrid', currentdebrid)
  1248. await GM.setValue('settings', JSON.stringify(newsettings.general))
  1249. for (const key in multi) {
  1250. await GM.setValue(key + '_settings', JSON.stringify(newsettings[key]))
  1251. }
  1252.  
  1253. alert(scriptName + '\n\nSettings were successfully saved!')
  1254. document.location.reload()
  1255. }
  1256.  
  1257. async function aboutMe () {
  1258. const popup = popUp('multiochhelper_about', null, null, true)
  1259. const $popup = popup.node
  1260. const $frame = $('<iframe width="' + (window.innerWidth - 250) + '" height="' + (window.innerHeight - 150) + '" style="border:0">').appendTo($popup)
  1261. $frame.bind('load', async function (e) {
  1262. // Load settings for all
  1263. for (const key in multi) {
  1264. await multi[key].loadSettings(true)
  1265. }
  1266.  
  1267. const $body = $($frame[0].contentDocument.getElementsByTagName('body')[0])
  1268.  
  1269. $body.css('fontFamily', 'Ubuntu,Arial,Sans-Serif')
  1270.  
  1271. $('<div style="position:fixed; top:0px; right:5px; cursor:pointer; color:White; background:#b555c5; border: 1px solid White; border-radius:3px; padding:0px; font-weight:bold ; " title="Close menu">X</span>').click(function () { if (confirm('Settings will NOT be saved!')) popup.close() }).appendTo($body)
  1272.  
  1273. $body.append('<h2>' + scriptName + '</h2>')
  1274. $('<a>').appendTo($body).attr('target', '_blank').css('fontSize', 'small').html('https://openuserjs.org/scripts/cuzi/Multi-OCH_Helper').attr('href', 'https://openuserjs.org/scripts/cuzi/Multi-OCH_Helper')
  1275.  
  1276. const $form = $('<div class="form">').appendTo($body)
  1277.  
  1278. // General options
  1279. $form.append('<h3>Settings</h3>')
  1280. configForm($form, config, settings, 'general')
  1281.  
  1282. // Preferred multihoster
  1283. const $p = $('<p>').appendTo($form)
  1284. $p.append('Preferred multihoster:<br>')
  1285. const $select = $('<select></select>').addClass('debridhoster').appendTo($p)
  1286. for (const key in multi) {
  1287. const $option = $('<option></option>').val(key).appendTo($select)
  1288. $option.html(multi[key].name)
  1289. $option[0].selected = key === currentdebrid
  1290. }
  1291.  
  1292. // Options for multihosters
  1293. for (const key in multi) {
  1294. $('<h3>').appendTo($form).html(multi[key].name)
  1295. $('<a>').appendTo($form).css('fontSize', 'small').attr('target', '_blank').html(multi[key].homepage).attr('href', multi[key].homepage)
  1296. if (multi[key].config) {
  1297. configForm($form, multi[key].config, multi[key].settings, key)
  1298. } else {
  1299. $('<p>').appendTo($form).text('No settings available for this service.')
  1300. }
  1301. }
  1302.  
  1303. $form.append('<br>')
  1304.  
  1305. $('<input type="button">').val('Cancel').click(function () {
  1306. if (confirm('Settings will NOT be saved!')) {
  1307. popup.close()
  1308. }
  1309. }).appendTo($form)
  1310. $('<input type="button">').val('Save').click($body, saveSettings).appendTo($form)
  1311.  
  1312. $form.append('<h3>Other options</h3>')
  1313.  
  1314. $('<input type="button">').val('Clear cache (' + humanBytes((await GM.getValue('cachedDownloadLinks', '{}')).length - 2) + ')').appendTo($form).click(async function () {
  1315. if (!confirm(scriptName + '\n\nReally delete cached links?')) {
  1316. return
  1317. }
  1318.  
  1319. await GM.setValue('cachedDownloadLinks', '{}')
  1320.  
  1321. this.value = 'Clear cache (' + humanBytes((await GM.getValue('cachedDownloadLinks', '{}')).length - 2) + ')'
  1322. alert(scriptName + '\n\nCache is empty!')
  1323. })
  1324.  
  1325. $form.append('<br>')
  1326. $form.append('<br>')
  1327.  
  1328. $('<input type="button">').val('Restore dialogs and warnings').appendTo($form).click(async function () {
  1329. if (!confirm(scriptName + '\n\nReally restore all dialogs and warnings?')) {
  1330. return
  1331. }
  1332.  
  1333. await GM.setValue('dialogs', '[]')
  1334.  
  1335. alert(scriptName + '\n\nDialogs and warnings restored')
  1336. })
  1337.  
  1338. let greasemonkeyIssue = ''
  1339. if (greasemonkey) {
  1340. greasemonkeyIssue = `<li>In Greasymonkey it is not possible to select multiple links with the mouse and send them at once.<br>
  1341. The reason is this bug: <a href="https://github.com/greasemonkey/greasemonkey/issues/2574">https://github.com/greasemonkey/greasemonkey/issues/2574</a><br>
  1342. If you need this functionality, you can use Tampermonkey instead of Greasemonkey</li>`
  1343. }
  1344.  
  1345. $(`<div>
  1346. <br>
  1347. <br>
  1348. <h3>Known issues:</h3>
  1349. <ul>
  1350. <li>nopremium.pl sometimes omits a few links in folders</li>
  1351. <li>In Firefox the script sometimes does not work if the "Accept thid-parts cookies" policy is set to "Never".<br>
  1352. To resolve this problem open the Firefox options and go to the tab "Privacy". Set the "Accept thid-parts cookies" to "From visited" or "Always"<br>
  1353. Close and re-open Firefox. Log out and then log in your nopremium.pl account. Everything should work fine now.</li>
  1354. ${greasemonkeyIssue}
  1355. </ul>
  1356. </div><br><br><br>`).appendTo($body)
  1357.  
  1358. $('<input type="button">').val('Debug info').appendTo($body).click(inspectGMvalues)
  1359. })
  1360. if (chrome) {
  1361. $frame.attr('src', 'about:blank')
  1362. }
  1363. }
  1364.  
  1365. function inspectGMvalues () {
  1366. let iv
  1367. const popup = popUp('multiochhelper_inspectGM', function () {
  1368. clearInterval(iv)
  1369. })
  1370. const $popup = popup.node
  1371. const $frame = $('<iframe width="' + (window.innerWidth - 250) + '" height="' + (window.innerHeight - 150) + '" style="border:0">').appendTo($popup)
  1372. $frame.bind('load', async function (e) {
  1373. $($frame[0].contentDocument.getElementsByTagName('head')[0]).append('<style type="text/css">' + SPINNERCSS + '</style>')
  1374.  
  1375. const $body = $($frame[0].contentDocument.getElementsByTagName('body')[0])
  1376. $body.append('<h2>' + scriptName + '</h2>')
  1377.  
  1378. let keys = await GM.listValues()
  1379. if (keys.length && typeof keys[0] === 'undefined') { // Firefox 35+ workaround
  1380. keys = cloneInto(await GM.listValues(), window)
  1381. }
  1382.  
  1383. const $table = $('<table>').appendTo($body)
  1384. let $tr
  1385. $tr = $('<tr>').appendTo($table)
  1386. $('<th>').html('Key').appendTo($tr)
  1387. $('<th>').html('Value').appendTo($tr)
  1388. $('<th>').html('Type').appendTo($tr)
  1389. $('<th>').html('').appendTo($tr)
  1390.  
  1391. const deleteValue = async function (ev) {
  1392. const key = $(this).data('key')
  1393. await GM.deleteValue(key)
  1394. $(this).parent().parent().remove()
  1395. }
  1396.  
  1397. let total = 0
  1398. for (let i = 0; i < keys.length; i++) {
  1399. const value = await GM.getValue(keys[i])
  1400. let svalue = '' + value
  1401. let len = 1
  1402. if (typeof value === 'undefined') {
  1403. svalue = 'undefined'
  1404. } else if (typeof value === 'string') {
  1405. len = value.length
  1406. }
  1407. total += len
  1408. $tr = $('<tr>').appendTo($table)
  1409. $('<td>').html(keys[i]).appendTo($tr)
  1410. $('<td>').append($('<input type="text" style="width:600px">').val(svalue)).appendTo($tr)
  1411. $('<td>').append('' + (typeof value) + (typeof value === 'string' ? ('(' + len + ')') : '')).appendTo($tr)
  1412. $('<td>').append($('<input type="button">').val('Delete').data('key', keys[i]).click(deleteValue)).appendTo($tr)
  1413. }
  1414.  
  1415. $tr = $('<tr>').appendTo($table)
  1416. $('<th>').html('Total').appendTo($tr)
  1417. $('<th>').html(keys.length).appendTo($tr)
  1418. $('<th>').html('approx. ' + humanBytes(total)).appendTo($tr)
  1419.  
  1420. const $reload = $('<div>').appendTo($body)
  1421. $('<div style="display:inline-block;width:20px; height:20px;" class="ochspinner"></div>').appendTo($reload)
  1422. $reload.append(' Reload in ')
  1423. const $timer = $('<span style="pointer:cursor;" title="Click to reload now"></span>').html('20 seconds').click(function () { this.innerHTML = 0 }).appendTo($reload)
  1424. iv = window.setInterval(function () {
  1425. let s = parseInt($timer.html(), 10)
  1426. if (s === 0) {
  1427. clearInterval(iv)
  1428. popup.close()
  1429. inspectGMvalues()
  1430. } else {
  1431. s--
  1432. $timer.html(s + ' seconds')
  1433. }
  1434. }, 1000)
  1435. })
  1436. if (chrome) {
  1437. $frame.attr('src', 'about:blank')
  1438. }
  1439. }
  1440.  
  1441. function hexToBytes (s) {
  1442. return s.match(/([0-9a-fA-F]{2})/g).map(v => parseInt(v, 16))
  1443. }
  1444.  
  1445. function stringToBytes (s) {
  1446. return s.split('').map(v => v.charCodeAt(0))
  1447. }
  1448.  
  1449. function bytesToString (a) {
  1450. return String.fromCharCode.apply(String, a)
  1451. }
  1452.  
  1453. function addCSSHead (body) {
  1454. const style = document.createElement('style')
  1455. style.type = 'text/css'
  1456. style.innerHTML = body
  1457. document.head.appendChild(style)
  1458. }
  1459.  
  1460. function humanBytes (bytes, precision) {
  1461. // http://stackoverflow.com/a/18650828
  1462. bytes = parseInt(bytes, 10)
  1463. if (bytes === 0) return '0 Byte'
  1464. const k = 1024
  1465. const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
  1466. const i = Math.floor(Math.log(bytes) / Math.log(k))
  1467. return parseFloat((bytes / Math.pow(k, i)).toPrecision(2)) + ' ' + sizes[i]
  1468. }
  1469.  
  1470. function getNextZIndex () {
  1471. // Calculate: max(zIndex) + 1
  1472. let zIndexMax = 0
  1473. try {
  1474. $('div').each(function () {
  1475. const z = parseInt($(this).css('z-index'), 10)
  1476. if (z > zIndexMax) {
  1477. zIndexMax = z
  1478. }
  1479. })
  1480. } catch (e) {} finally {
  1481. if (zIndexMax < 20000) {
  1482. zIndexMax = 20006
  1483. }
  1484. }
  1485. return zIndexMax + 1
  1486. }
  1487.  
  1488. async function showConfirm (id, text, onConfirm, onNotConfirm, thisArg) {
  1489. // Skip
  1490. const dialogs = JSON.parse(await GM.getValue('dialogs', '[]'))
  1491. if (dialogs.indexOf(id) !== -1) {
  1492. onConfirm.call(thisArg)
  1493. return
  1494. }
  1495.  
  1496. const popup = popUp('confirm' + id, function () {}, thisArg)
  1497. const $div = popup.node
  1498. $div.append(text)
  1499. $div.append('<br>')
  1500. $('<input type="button" value="Yes">').click(function () {
  1501. popup.close()
  1502. onConfirm.call(thisArg)
  1503. }).appendTo($div)
  1504.  
  1505. $('<input type="button" value="No">').click(function () {
  1506. popup.close()
  1507. if (onNotConfirm) {
  1508. onNotConfirm.call(thisArg)
  1509. }
  1510. }).appendTo($div)
  1511.  
  1512. $div.append('<br>')
  1513.  
  1514. $('<input type="checkbox" value="remember">').click(async function () {
  1515. const dialogs = JSON.parse(await GM.getValue('dialogs', '[]'))
  1516. if (this.checked) {
  1517. if (dialogs.indexOf(id) === -1) {
  1518. dialogs.push(id)
  1519. await GM.setValue('dialogs', JSON.stringify(dialogs))
  1520. }
  1521. } else {
  1522. if (dialogs.indexOf(id) !== -1) {
  1523. dialogs.splice(dialogs.indexOf(id), 1)
  1524. await GM.setValue('dialogs', JSON.stringify(dialogs))
  1525. }
  1526. }
  1527. }).appendTo($div)
  1528. $div.append(' Always "Yes". Do not show this message again!')
  1529. }
  1530.  
  1531. function setStatus (text, success) {
  1532. addStatus(text, success, true)
  1533. }
  1534.  
  1535. function addStatus (text, success, clear) {
  1536. if (!document.getElementById('multiochhelper')) {
  1537. alert(`${scriptName}\n\n${text}`)
  1538. return
  1539. }
  1540. let $status = $('#multiochhelper_status')
  1541. if (!document.getElementById('multiochhelper_status_text')) {
  1542. if (!document.getElementById('multiochhelper_status')) {
  1543. const $div = $('#multiochhelper')
  1544. $status = $('<div>').prependTo($div)
  1545. $status.attr('id', 'multiochhelper_status')
  1546. } else {
  1547. $status.empty()
  1548. }
  1549. const $loader = $('<div>').appendTo($status)
  1550. $loader.attr('id', 'multiochhelper_status_loader')
  1551. const $statustext = $('<div>').appendTo($status)
  1552. $statustext.attr('id', 'multiochhelper_status_text')
  1553. const $statusclear = $('<div>').appendTo($status)
  1554. $statusclear.attr('id', 'multiochhelper_status_clear')
  1555. }
  1556.  
  1557. const $statustext = $('#multiochhelper_status_text')
  1558. if (clear) {
  1559. $statustext.empty()
  1560. } else if ($statustext.html().trim() !== '') {
  1561. $statustext.append(document.createElement('br'))
  1562. }
  1563. $status.show()
  1564.  
  1565. $statustext.append(text)
  1566. if (success === 1) {
  1567. $statustext.css('color', '#33FF99')
  1568. } else if (success === 0) {
  1569. $statustext.css('color', 'orange')
  1570. } else if (success === -1) {
  1571. $statustext.css('color', 'cyan')
  1572. } else {
  1573. $statustext.css('color', 'white')
  1574. }
  1575. }
  1576.  
  1577. function showOnlyStatus () {
  1578. const $status = $('#multiochhelper_status')
  1579. $status.siblings().not('#multiochhelper_status').remove()
  1580. }
  1581.  
  1582. function getMultiOCHWebsiteURL (links) {
  1583. return multi[currentdebrid].getOpenWebsiteURL(links)
  1584. }
  1585.  
  1586. function openWebsite (links, cb) {
  1587. // Call cb() and navigate to the website
  1588. if (!links) {
  1589. cb(false)
  1590. return
  1591. }
  1592.  
  1593. if (cb) {
  1594. cb()
  1595. }
  1596. const url = getMultiOCHWebsiteURL(links)
  1597.  
  1598. if (settings.newTab) {
  1599. if (typeof GM.openInTab === 'undefined') {
  1600. window.open(url)
  1601. } else {
  1602. GM.openInTab(url)
  1603. }
  1604. } else {
  1605. document.location.href = url
  1606. }
  1607. }
  1608.  
  1609. async function useCache (urls, cb) {
  1610. urls = '' + urls
  1611. const cachedDownloadLinks = JSON.parse(await GM.getValue('cachedDownloadLinks', '{}')) // [datestring,downloadlink,multihoster]
  1612. if (urls in cachedDownloadLinks) {
  1613. if (confirm(scriptName + '\n\nLink was found in cache.\nUse cached link?\n\nFrom: ' + (new Date(cachedDownloadLinks[urls][0])) + '\nWith: ' + cachedDownloadLinks[urls][2] + '\n' + cachedDownloadLinks[urls][1].join('\n'))) {
  1614. cb(cachedDownloadLinks[urls][1])
  1615. return true
  1616. }
  1617. }
  1618. return false
  1619. }
  1620. async function cacheLink (urls, datetime, downloadLinks, multihoster) {
  1621. if (!Array.isArray(downloadLinks)) {
  1622. const parts = downloadLinks.split('\n')
  1623. downloadLinks = []
  1624. for (let i = 0; i < parts.length; i++) {
  1625. if ($.trim(parts[i])) {
  1626. downloadLinks.push($.trim(parts[i]))
  1627. }
  1628. }
  1629. }
  1630. if (downloadLinks.length === 0) return
  1631.  
  1632. urls = '' + urls
  1633. const cachedDownloadLinks = JSON.parse(await GM.getValue('cachedDownloadLinks', '{}'))
  1634. cachedDownloadLinks[urls] = [datetime, downloadLinks, multihoster]
  1635. await GM.setValue('cachedDownloadLinks', JSON.stringify(cachedDownloadLinks))
  1636. }
  1637.  
  1638. function showExtractedLinks (links) {
  1639. if (document.querySelector('.alertlinkscont')) {
  1640. alert(links.join('\n'))
  1641. $('.alertlinkscont').remove()
  1642. return
  1643. }
  1644. $('<style type="text/css">.alertlinkscont{transition: left 500ms;}.alertlinkscont a{font-size:12px;user-select:all; font-family: monospace;} .alertlinkscont a:link,.alertlinkscont a:hover{color:black; text-decoration:none;}.alertlinkscont a:visited{color:rgb(70,0,120); text-decoration:none;}</style>').appendTo('head')
  1645. const $div = $('<div class="alertlinkscont"></div>')
  1646. $div.appendTo(document.body)
  1647. $div.css({
  1648. zIndex: 10000,
  1649. position: 'fixed',
  1650. top: '20px',
  1651. left: '20px',
  1652. minWidth: '300px',
  1653. minHeight: '300px',
  1654. background: 'white',
  1655. color: 'black',
  1656. border: '2px solid black',
  1657. borderRadius: '5px',
  1658. padding: '20px 25px 10px',
  1659. fontFamily: 'monospace',
  1660. fontSize: '12px',
  1661. overflow: 'auto'
  1662. })
  1663. for (let i = 0; i < links.length; i++) {
  1664. $div[0].innerHTML += '<a target="_blank" href="' + links[i] + '">' + links[i] + '</a><br>\n'
  1665. }
  1666. $div[0].innerHTML += '<br><br>\n'
  1667. window.setTimeout(function moveMenuIntoView () {
  1668. $div.css('maxHeight', (document.documentElement.clientHeight - 100) + 'px')
  1669. $div.css('maxWidth', (document.documentElement.clientWidth - 40) + 'px')
  1670. $div.css('left', Math.max(20, 0.5 * (document.body.clientWidth - $div[0].clientWidth)) + 'px')
  1671. }, 0)
  1672. }
  1673.  
  1674. async function generateLinks (urls, cb) {
  1675. // Check cache
  1676. if (await useCache(urls, cb)) {
  1677. return
  1678. }
  1679.  
  1680. await multi[currentdebrid].getLinks(urls, cb)
  1681. }
  1682.  
  1683. async function download (urls, cb) {
  1684. // Get one/first download link and open it immediately/start download
  1685. if (urls.length > 1) {
  1686. alert(scriptName + '\n\nOnly the first link will be opened!')
  1687. }
  1688.  
  1689. await generateLinks(urls, function (result, code) {
  1690. if (cb) {
  1691. cb()
  1692. }
  1693. if (result && result[0]) {
  1694. addStatus('Opening download...', -1)
  1695. if (window.top === window) {
  1696. document.location.href = result[0]
  1697. } else {
  1698. // Changing location may be blocked by sandboxed iframe
  1699. window.top.location.href = result[0]
  1700. }
  1701. } else if (code === -2) {
  1702. // Error was already handled
  1703. console.log('download() in generateLinks(): error already handled')
  1704. } else if (!code) {
  1705. addStatus('An error occured: No downloadlink to open', 0)
  1706. }
  1707. })
  1708. }
  1709.  
  1710. async function clipboard (urls, cb) {
  1711. // Get download links and copy them into clipboard
  1712. generateLinks(urls, function (result, code) {
  1713. if (result) {
  1714. let succeeded = false
  1715. setStatus('Trying to set clipboard', -1)
  1716. window.setTimeout(function () {
  1717. if (succeeded) {
  1718. return
  1719. }
  1720. setStatus('Trying GM_setClipboard()', -1)
  1721. try {
  1722. GM_setClipboard(result.join('\r\n'))
  1723. setStatus('Copied to clipboard', 1)
  1724. } catch (e) {
  1725. setStatus('Failed to access clipboard 02', 0)
  1726. alert('Failed to access clipboard.\n\nLinks will appear in next dialog window')
  1727. alert(result.join('\r\n'))
  1728. }
  1729. }, 3000)
  1730. try {
  1731. GM.setClipboard(result.join('\r\n')).then(function () {
  1732. setStatus('Copied to clipboard', 1)
  1733. succeeded = true
  1734. }, function () {
  1735. setStatus('Failed to access clipboard 01', 0)
  1736. })
  1737. } catch (e) {
  1738. setStatus('Clipboard not supported by this browser', 0)
  1739. alert(result.join('\n'))
  1740. };
  1741. } else if (code === -2) {
  1742. // Error was already handled
  1743. console.log('clipboard() in generateLinks(): error already handled')
  1744. } else {
  1745. setStatus('An error occured: No downloadlinks found', 0)
  1746. }
  1747. if (cb) {
  1748. cb()
  1749. }
  1750. })
  1751. }
  1752.  
  1753. async function sendToJD (urls, cb) {
  1754. // Get download links and send them to JDownloader
  1755. generateLinks(urls, function (result, code) {
  1756. if (result) {
  1757. setStatus('Waiting for JDownloader', -1)
  1758.  
  1759. // Comment should be the original page in case of multiple links
  1760. let comment = urls[0]
  1761. if (urls.length > 1) {
  1762. if (showOneclickFromHighlighScriptAllLinksLoc) {
  1763. comment = showOneclickFromHighlighScriptAllLinksLoc
  1764. } else if (showOneclickFromHighlighScriptSelectedLinksLoc) {
  1765. comment = showOneclickFromHighlighScriptAllLinksLoc
  1766. } else {
  1767. comment = document.location.href
  1768. }
  1769. }
  1770.  
  1771. GM.xmlHttpRequest({
  1772. method: 'POST',
  1773. url: JDOWNLOADER + 'flash/add',
  1774. headers: {
  1775. 'Content-Type': 'application/x-www-form-urlencoded',
  1776. Referer: scriptReferer,
  1777. 'User-Agent': scriptReferer
  1778. },
  1779. // data: "source="+encodeURIComponent(scriptReferer)+"&urls="+encodeURIComponent(result.join("\r\n")), // Moved "source" to Referer
  1780. // data: "comment="+encodeURIComponent(comment)+"&urls="+encodeURIComponent(result.join("\r\n")), // See ExternInterfaceImpl.java
  1781. data: 'source=' + encodeURIComponent(scriptReferer) + '&comment=' + encodeURIComponent(comment) + '&urls=' + encodeURIComponent(result.join('\r\n')), // See ExternInterfaceImpl.java
  1782. onload: function (resp) {
  1783. if (cb) {
  1784. cb()
  1785. }
  1786. if (resp.status === 204 || resp.responseText.startsWith('success')) {
  1787. setStatus('Sent to JDownloader', 1)
  1788. } else {
  1789. setStatus('JDownloader rejected the request', 0)
  1790. }
  1791. },
  1792. onerror: function (resp) {
  1793. if (cb) {
  1794. cb()
  1795. }
  1796. setStatus('JDownloader is not running', 0)
  1797. }
  1798.  
  1799. })
  1800. } else if (code === -2) {
  1801. // Error was already handled
  1802. console.log('sendToJD() in generateLinks(): error already handled')
  1803. if (cb) {
  1804. cb()
  1805. }
  1806. } else {
  1807. if (cb) {
  1808. cb()
  1809. }
  1810. addStatus('No links to send', 0)
  1811. }
  1812. })
  1813. }
  1814.  
  1815. function showLinks (urls, cb, append, n) {
  1816. const popup = popUp('showLinks')
  1817. const $div = popup.node
  1818. const $loader = $('<div style="width:20px; height:20px;" class="ochspinner"></div>').appendTo($div)
  1819.  
  1820. const $frame = $('<iframe width="900" height="500" style="border:0">').appendTo($div)
  1821. $frame.bind('load', function (e) {
  1822. $($frame[0].contentDocument.getElementsByTagName('head')[0]).append('<link rel="stylesheet" href="https://www.nopremium.pl/css/style.css" type="text/css" />')
  1823. const $body = $($frame[0].contentDocument.getElementsByTagName('body')[0])
  1824. multi[currentdebrid].getResults(urls, function ($node) {
  1825. $loader.remove()
  1826. $body.append($node)
  1827. $body.find('a').each(function () {
  1828. // Open links in new window
  1829. this.setAttribute('target', '_blank')
  1830. })
  1831. if (cb) {
  1832. cb()
  1833. }
  1834. })
  1835. })
  1836. if (chrome) {
  1837. $frame.attr('src', 'about:blank')
  1838. }
  1839. }
  1840.  
  1841. function decryptClickNLoad (cb, jk, cryptedBase64) {
  1842. // Get all the links by decrypting the Click'n'Load form
  1843. // return False for any error
  1844. // return True, run cb() and open the menu if Click'n'Load was successfully decoded
  1845.  
  1846. if (!cryptedBase64 && !(document.getElementsByName('crypted').length && document.getElementsByName('jk').length)) {
  1847. return false // Click'n'Load not avaiblabe
  1848. }
  1849.  
  1850. setStatus("Trying to decrypt Click'n'Load", -1)
  1851.  
  1852. try {
  1853. // Key/IV
  1854. if (!jk) {
  1855. jk = document.getElementsByName('jk')[0].value
  1856. }
  1857. if (jk.indexOf('return') !== -1) {
  1858. jk = eval(jk + '; f();') // eslint-disable-line no-eval
  1859. }
  1860.  
  1861. const key = hexToBytes(jk)
  1862. const iv = key.slice(0)
  1863.  
  1864. // Text
  1865. if (!cryptedBase64) {
  1866. cryptedBase64 = document.getElementsByName('crypted')[0].value
  1867. }
  1868. const cryptedString = atob(cryptedBase64)
  1869. const cryptedBytes = stringToBytes(cryptedString)
  1870.  
  1871. // Decrypt
  1872. const textBytes = slowAES.decrypt(cryptedBytes, slowAES.modeOfOperation.CBC, key, iv)
  1873. let text = bytesToString(textBytes)
  1874.  
  1875. text = text.replace('\r', '')
  1876.  
  1877. const splitted = text.split('\n')
  1878.  
  1879. const links = []
  1880. for (let i = 0; i < splitted.length; i++) {
  1881. // Remove any line that is not a http link
  1882. const t = $.trim(splitted[i])
  1883. if (t && t.substring(0, 4) === 'http') {
  1884. links.push(t)
  1885. }
  1886. }
  1887.  
  1888. const N = links.length
  1889.  
  1890. if (N === 0) {
  1891. return false // Click'n'Load probably failed, try another method...
  1892. }
  1893.  
  1894. if (cb) {
  1895. cb()
  1896. }
  1897. menu(links)
  1898. setStatus('Found ' + (N === 1 ? 'one link' : (N + ' links')), 1)
  1899. return true
  1900. } catch (e) {
  1901. alert("Click'N'Load failed:\n" + e)
  1902. return false // Click'n'Load probably failed, try another method...
  1903. }
  1904.  
  1905. /*
  1906. // Get all the links by decrypting the Click'n'Load form
  1907. if(!document.getElementsByName('crypted').length || !document.getElementsByName('jk').length) {
  1908. if(cb) {
  1909. cb();
  1910. }
  1911. return;
  1912. }
  1913.  
  1914. setStatus("Trying linkdecrypter.com",-1);
  1915. const crypted = document.getElementsByName('crypted')[0].value;
  1916. const jk = document.getElementsByName('jk')[0].value;
  1917.  
  1918. GM.xmlHttpRequest( {
  1919. method: "POST",
  1920. url: "http://linkdecrypter.com/api/?t=cnl2",
  1921. data: 'crypted=' + encodeURIComponent(crypted) + '&jk=' + encodeURIComponent(btoa(jk)),
  1922. headers: {
  1923. "User-agent": "Mozilla/5.0 (X11;U;Linux i686;es-ES;rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8",
  1924. "Accept": "application/atom+xml,application/xml,text/xml",
  1925. "Content-type" : "application/x-www-form-urlencoded"
  1926. },
  1927. onload: function(response) {
  1928. if(cb) {
  1929. cb();
  1930. }
  1931. const N = response.responseText.split("\n").length;
  1932. if(!response.responseText || response.responseText.indexOf("ERROR(CNL2)") !== -1 || N === 0) {
  1933. setStatus("An error occurred while handling the response of linkdecrypter.com",0);
  1934. } else {
  1935. menu(response.responseText);
  1936. setStatus("Found "+(N===1?"one link":(N+" links")),1);
  1937. }
  1938. }
  1939. });
  1940. */
  1941. }
  1942.  
  1943. function getAllSerienjunkiesLinks (cb) {
  1944. // Get all download links from a serienjunkies.org download page (i.e. the page right after the captcha)
  1945. const urls = [] // [ [partnumber0,link0] , [partnumber1,link1] , .... ]
  1946. let total = 0
  1947.  
  1948. const rap = document.getElementById('rap')
  1949. const table = rap.getElementsByTagName('table')[0]
  1950. const forms = table.getElementsByTagName('form')
  1951.  
  1952. let j = 1 // part number, in order to make sure that sorting of the links is the same as on the page.
  1953. // This is only a fallback in case there is no visible part number in the actual downloadlink/filename.
  1954. for (let i = 0; i < forms.length; i++) {
  1955. const url = forms[i].action
  1956. if (url.indexOf('mirror') !== -1 || url.indexOf('firstload') !== -1) {
  1957. continue
  1958. }
  1959. GM.xmlHttpRequest({
  1960. method: 'GET',
  1961. url,
  1962. onload: (function (j) {
  1963. return function (response) {
  1964. const loc = response.finalUrl // Actual link after posible redirections
  1965. if (response.finalUrl.match(/part*(\d+)\./)) { // Try to guess part number
  1966. const part = response.finalUrl.match(/part*(\d+)\./)[1]
  1967. urls.push([parseInt(part, 10), loc])
  1968. } else { // fallback part number
  1969. urls.push([j, loc])
  1970. }
  1971. setStatus('Decrypting: ' + urls.length + '/' + total, total === urls.length ? 1 : -1)
  1972. if (total === urls.length) {
  1973. // Got all links
  1974. cb(urls)
  1975. }
  1976. }
  1977. }(j))
  1978. })
  1979. j++
  1980. }
  1981. total = j - 1
  1982. };
  1983.  
  1984. function getSerienjunkiesLinks (cb) {
  1985. // Get all the links from the page
  1986. getAllSerienjunkiesLinks(function (urls) {
  1987. if (cb) {
  1988. cb()
  1989. }
  1990.  
  1991. urls = urls.sort(function (a, b) {
  1992. if (a[0] > b[0]) return 1
  1993. else if (a[0] < b[0]) return -1
  1994. return 0
  1995. })
  1996.  
  1997. let alllinks = ''
  1998. for (let i = 0; i < urls.length; ++i) {
  1999. alllinks += urls[i][1] + '\n'
  2000. }
  2001. menu(alllinks)
  2002. })
  2003. }
  2004.  
  2005. function getFilecryptcc (jddata, cb) {
  2006. // Get all the links by decrypting the Click'n'Load form
  2007. const fieldJk = jddata[0]
  2008. const fieldCrypted = jddata[1]
  2009.  
  2010. const r = decryptClickNLoad(cb, fieldJk, fieldCrypted)
  2011. if (!r) {
  2012. setStatus("Could not find Click'n'Load", -1)
  2013. if (cb) {
  2014. cb()
  2015. }
  2016. }
  2017. }
  2018.  
  2019. function getSafeLinkingNetLinks (cb) {
  2020. // Get all the links by following each link
  2021.  
  2022. const crypticUrls = []
  2023. $('div.links-container.result-form a.result-a').each(function () {
  2024. if (this.getAttribute('href') && this.getAttribute('href').indexOf('/d/') !== -1) { crypticUrls.push(this.getAttribute('href')) }
  2025. })
  2026.  
  2027. const urls = []
  2028. let total = 0
  2029.  
  2030. let j = 1
  2031. for (let i = 0; i < crypticUrls.length; i++) {
  2032. GM.xmlHttpRequest({
  2033. method: 'GET',
  2034. url: crypticUrls[i],
  2035. onload: (function (j) {
  2036. return function (response) {
  2037. const loc = response.finalUrl // Actual link after posible redirections
  2038. urls.push(loc)
  2039. setStatus('Decrypting: ' + urls.length + '/' + total, -1)
  2040. if (total === urls.length) {
  2041. // Got all links
  2042. cb()
  2043. menu(urls)
  2044. setStatus('Found ' + (total === 1 ? 'one link' : (total + ' links')), 1)
  2045. }
  2046. }
  2047. }(j))
  2048. })
  2049. j++
  2050. }
  2051. total = j - 1
  2052. };
  2053.  
  2054. const linkSelectorFilter = {
  2055. _filter: function (key) {
  2056. const a = Array.prototype.slice.call(arguments, 1)
  2057. return function () {
  2058. linkSelectorFilter[key].apply(linkSelectorFilter, a)
  2059. }
  2060. },
  2061.  
  2062. all: function (trs) {
  2063. for (let i = 0; i < trs.length; i++) {
  2064. trs[i].$check.prop('checked', true)
  2065. }
  2066. },
  2067. none: function (trs) {
  2068. for (let i = 0; i < trs.length; i++) {
  2069. trs[i].$check.prop('checked', false)
  2070. }
  2071. },
  2072. flip: function (trs) {
  2073. for (let i = 0; i < trs.length; i++) {
  2074. trs[i].$check.prop('checked', !trs[i].$check.prop('checked'))
  2075. }
  2076. },
  2077.  
  2078. has: function (trs, inpFilter) {
  2079. const s = inpFilter.val()
  2080. for (let i = 0; i < trs.length; i++) {
  2081. if (trs[i].link.indexOf(s) !== -1) {
  2082. trs[i].$check.prop('checked', !trs[i].$check.prop('checked'))
  2083. }
  2084. }
  2085. },
  2086.  
  2087. host: function (trs, $selHost) {
  2088. const h = $selHost.val()
  2089.  
  2090. for (let i = 0; i < trs.length; i++) {
  2091. if (trs[i].host === h) {
  2092. trs[i].$check.prop('checked', !trs[i].$check.prop('checked'))
  2093. }
  2094. }
  2095. },
  2096.  
  2097. fromto: function (trs, $table, $thead, $th) {
  2098. const _self = this
  2099.  
  2100. for (let i = 0; i < trs.length; i++) {
  2101. trs[i].$check.prop('disabled', true)
  2102. }
  2103. $table.find('td').hover(function () {
  2104. $(this).parent().find('td').each(function (i, e) {
  2105. $(e).css('background', 'PaleGreen')
  2106. })
  2107. }, function () {
  2108. $(this).parent().find('td').each(function (i, e) {
  2109. $(e).css('background', '')
  2110. })
  2111. })
  2112. $thead.find('th').css('display', 'none')
  2113. $th.css('display', '')
  2114. $th.html('Select from where to start')
  2115.  
  2116. $table.find('td').click(function () {
  2117. const from = $(this.parentNode).data('index')
  2118. $(this).parent().find('td').css('background', 'PaleGreen')
  2119. $table.find('td').unbind('click mouseenter mouseleave')
  2120. $th.html('Select where to stop')
  2121.  
  2122. $table.find('td').hover(function () {
  2123. const to = $(this.parentNode).data('index')
  2124. $table.find('td').each(function (i, e) {
  2125. const $e = $(e)
  2126. const j = $e.parent().data('index')
  2127. if (j > from && j <= to) {
  2128. $e.css('background', 'DarkSeaGreen')
  2129. } else if (j > from && j > to) {
  2130. $e.css('background', '')
  2131. }
  2132. })
  2133. if ($(this).parent().data('index') > from) $(this).parent().find('td').css('background', 'PaleGreen')
  2134. })
  2135. $table.find('td').filter(function (i, e) { return $(e.parentNode).data('index') > from }).click(function () {
  2136. const to = $(this.parentNode).data('index') + 1
  2137.  
  2138. $table.find('td').unbind('click mouseenter mouseleave')
  2139. $(this).parent().find('td').css('background', 'PaleGreen')
  2140. $table.find('td').each(function (i, e) {
  2141. const $e = $(e)
  2142. const j = $e.parent().data('index')
  2143. if (j < from || j >= to) {
  2144. $e.css('display', 'none')
  2145. }
  2146. })
  2147.  
  2148. const ntrs = trs.slice(from, to)
  2149. for (let i = 0; i < ntrs.length; i++) {
  2150. ntrs[i].$check.prop('disabled', false)
  2151. }
  2152.  
  2153. $th.html('Select ')
  2154. $('<button>').appendTo($th).text('all').click(_self._filter('all', ntrs))
  2155. $('<button>').appendTo($th).text('none').click(_self._filter('none', ntrs))
  2156. $('<button>').appendTo($th).text('flip').click(_self._filter('flip', ntrs))
  2157. $('<button>').appendTo($th).text('return to all links').click(function () {
  2158. $table.find('td').each(function (i, e) {
  2159. const $e = $(e)
  2160. $e.css('display', '')
  2161. $e.css('background', '')
  2162. })
  2163. $thead.find('th').css('display', '')
  2164. $th.css('display', 'none')
  2165. $th.html('')
  2166. for (let i = 0; i < trs.length; i++) {
  2167. trs[i].$check.prop('disabled', false)
  2168. }
  2169. })
  2170. $th[0].scrollIntoView()
  2171.  
  2172. return false
  2173. })
  2174. })
  2175. },
  2176.  
  2177. every: function (trs, $table, $thead, $th) {
  2178. const _self = this
  2179.  
  2180. for (let i = 0; i < trs.length; i++) {
  2181. trs[i].$check.prop('disabled', true)
  2182. }
  2183. $table.find('td').hover(function () {
  2184. $(this).parent().find('td').each(function (i, e) {
  2185. $(e).css('background', 'PaleGreen')
  2186. })
  2187. }, function () {
  2188. $(this).parent().find('td').each(function (i, e) {
  2189. $(e).css('background', '')
  2190. })
  2191. })
  2192. $thead.find('th').css('display', 'none')
  2193. $th.css('display', '')
  2194. $th.html('Select from where to start')
  2195.  
  2196. $table.find('td').click(function () {
  2197. const from = $(this.parentNode).data('index')
  2198. $(this).parent().find('td').css('background', 'PaleGreen')
  2199. $table.find('td').unbind('click mouseenter mouseleave')
  2200. $th.html('Select next')
  2201.  
  2202. $table.find('td').hover(function () {
  2203. const to = $(this.parentNode).data('index')
  2204. const diff = to - from
  2205. if (to < from + 2) {
  2206. $table.find('td').filter(function (i, e) { return $(e.parentNode).data('index') > from + 1 }).css('background', '')
  2207. } else {
  2208. $table.find('td').filter(function (i, e) { return $(e.parentNode).data('index') > from + 1 }).each(function (i, e) {
  2209. const j = $(this.parentNode).data('index')
  2210. if ((j - from) % diff === 0 && j > from + 1) {
  2211. $(this).css('background', 'PaleGreen')
  2212. } else {
  2213. $(this).css('background', '')
  2214. }
  2215. })
  2216. $(this).parent().find('td').css('background', 'DarkSeaGreen')
  2217. }
  2218. }).click(function () {
  2219. const to = $(this.parentNode).data('index')
  2220.  
  2221. if (to < from + 2) return false
  2222.  
  2223. $(this).parent().find('td').css('background', 'PaleGreen')
  2224.  
  2225. const diff = to - from
  2226.  
  2227. $table.find('td').unbind('click mouseenter mouseleave')
  2228.  
  2229. $table.find('td').each(function (i, e) {
  2230. const $e = $(e)
  2231. const j = $e.parent().data('index')
  2232. if ((j - from) % diff !== 0 || j < from) {
  2233. $e.css('display', 'none')
  2234. }
  2235. })
  2236.  
  2237. const ntrs = []
  2238. for (let i = 0; i < trs.length; i++) {
  2239. if ((i - from) % diff === 0 && i >= from) {
  2240. trs[i].$check.prop('disabled', false)
  2241. ntrs.push(trs[i])
  2242. }
  2243. }
  2244.  
  2245. $th.html('Select ')
  2246. $('<button>').appendTo($th).text('all').click(_self._filter('all', ntrs))
  2247. $('<button>').appendTo($th).text('none').click(_self._filter('none', ntrs))
  2248. $('<button>').appendTo($th).text('flip').click(_self._filter('flip', ntrs))
  2249. $('<button>').appendTo($th).text('return to all links').click(function () {
  2250. $table.find('td').each(function (i, e) {
  2251. const $e = $(e)
  2252. $e.css('display', '')
  2253. $e.css('background', '')
  2254. })
  2255. $thead.find('th').css('display', '')
  2256. $th.css('display', 'none')
  2257. $th.html('')
  2258. for (let i = 0; i < trs.length; i++) {
  2259. trs[i].$check.prop('disabled', false)
  2260. }
  2261. })
  2262. $th[0].scrollIntoView()
  2263.  
  2264. return false
  2265. })
  2266. })
  2267. }
  2268.  
  2269. }
  2270.  
  2271. function linkSelector (links) {
  2272. const filter = function (key) {
  2273. const a = Array.prototype.slice.call(arguments, 1)
  2274. return function () {
  2275. linkSelectorFilter[key].apply(linkSelectorFilter, a)
  2276. }
  2277. }
  2278.  
  2279. const trs = []
  2280.  
  2281. const selectedLinks = []
  2282. // Coyp array and remove empty elements
  2283. for (let i = 0; i < links.length; i++) {
  2284. const t = $.trim(links[i])
  2285. if (t) {
  2286. selectedLinks.push(t)
  2287. }
  2288. }
  2289.  
  2290. if (linksBeforeSelection === false) {
  2291. linksBeforeSelection = links.slice(0) // Save all links for later selections
  2292. }
  2293. const allLinks = linksBeforeSelection.slice(0)
  2294.  
  2295. const popup = popUp('linkSelector')
  2296. const $div = popup.node
  2297. const $loader = $('<div style="width:20px; height:20px;" class="ochspinner"></div>').appendTo($div)
  2298. $div.css('overflow', 'none')
  2299. const $frame = $('<iframe style="border:0">').appendTo($div)
  2300. $frame.attr('width', window.innerWidth - 190)
  2301. $frame.attr('height', window.innerHeight - 120)
  2302. $frame.bind('load', function (e) {
  2303. const $body = $($frame[0].contentDocument.getElementsByTagName('body')[0])
  2304.  
  2305. const $main = $('<div>').appendTo($body)
  2306.  
  2307. const $table = $('<table>').appendTo($main)
  2308. const $thead = $('<thead>').appendTo($table)
  2309.  
  2310. const $tr0 = $('<tr>').appendTo($thead)
  2311. const $th0 = $('<th>').appendTo($tr0).attr('colspan', 2)
  2312. const $tr1 = $('<tr>').appendTo($thead)
  2313. const $th1 = $('<th>').appendTo($tr1).attr('colspan', 2)
  2314. const $tr2 = $('<tr>').appendTo($thead)
  2315. const $th2 = $('<th>').appendTo($tr2).attr('colspan', 2)
  2316. const $tr3 = $('<tr>').appendTo($thead)
  2317. const $th3 = $('<th>').appendTo($tr3).attr('colspan', 2)
  2318. const $tr4 = $('<tr>').appendTo($thead)
  2319. const $th4 = $('<th>').appendTo($tr4).attr('colspan', 2)
  2320.  
  2321. $('<span>Select: <span>').appendTo($th0)
  2322. $('<button>').appendTo($th0).text('all').click(filter('all', trs))
  2323. $('<button>').appendTo($th0).text('none').click(filter('none', trs))
  2324. $('<button>').appendTo($th0).text('flip').click(filter('flip', trs))
  2325.  
  2326. $('<button>').appendTo($th1).text('Select from ... to ...').click(filter('fromto', trs, $table, $thead, $th4))
  2327. $('<button>').appendTo($th1).text('Select every ...').click(filter('every', trs, $table, $thead, $th4))
  2328.  
  2329. $('<span> Filter:<span>').appendTo($th2)
  2330. const inpFilter = $('<input>').appendTo($th2).attr('type', 'text')
  2331. $('<button>').appendTo($th2).text('Flip with filter').click(filter('has', trs, inpFilter))
  2332.  
  2333. $('<span> Host filter:<span>').appendTo($th3)
  2334. const $selHost = $('<select>').appendTo($th3)
  2335. $('<button>').appendTo($th3).text('Flip with host filter').click(filter('has', trs, $selHost))
  2336.  
  2337. const allhosts = []
  2338. for (let i = 0; i < allLinks.length; i++) {
  2339. const $tr = $('<tr>').data('index', i).appendTo($table)
  2340. const $td0 = $('<td>').appendTo($tr)
  2341. const $check = $('<input>').appendTo($td0).attr('type', 'checkbox').attr('id', 'link_checkbox_' + i).prop('checked', selectedLinks.indexOf(allLinks[i]) !== -1)
  2342. const $td1 = $('<td>').appendTo($tr)
  2343. $('<label>').attr('for', 'link_checkbox_' + i).text(allLinks[i]).css('font-family', 'monospace').appendTo($td1)
  2344.  
  2345. const host = allLinks[i].split('/')[2].replace(/^www\./, '')
  2346. if (allhosts.indexOf(host) === -1) {
  2347. allhosts.push(host)
  2348. }
  2349.  
  2350. trs.push({ $tr, $check, link: allLinks[i], host })
  2351. }
  2352.  
  2353. for (let i = 0; i < allhosts.length; i++) {
  2354. $('<option>').val(allhosts[i]).text(allhosts[i]).appendTo($selHost)
  2355. }
  2356.  
  2357. $('<button>').appendTo($main).text('Apply').click(function () {
  2358. const nlinks = []
  2359. for (let i = 0; i < trs.length; i++) {
  2360. if (trs[i].$check.prop('checked')) {
  2361. nlinks.push(trs[i].link)
  2362. }
  2363. }
  2364. if (nlinks.length === 0) {
  2365. alert('No links selected?!')
  2366. return
  2367. }
  2368. menu(nlinks)
  2369. setStatus((nlinks.length === 1 ? 'One link' : (nlinks.length + ' links')) + ' selected', 1)
  2370. popup.close()
  2371. })
  2372.  
  2373. $loader.remove()
  2374. })
  2375. if (chrome) {
  2376. $frame.attr('src', 'about:blank')
  2377. }
  2378. }
  2379.  
  2380. function menu (links) {
  2381. // normalize links:
  2382. if (!Array.isArray(links)) {
  2383. const parts = links.split('\n')
  2384. links = []
  2385. for (let i = 0; i < parts.length; i++) {
  2386. if ($.trim(parts[i])) {
  2387. links.push($.trim(parts[i]))
  2388. }
  2389. }
  2390. }
  2391.  
  2392. const $c = $('#multiochhelper ul')
  2393. $c.html('')
  2394.  
  2395. const $select = $('<select>')
  2396. const m = links[0].match(/https?:\/\/(.+?)\//)
  2397. if (!m) {
  2398. console.log(scriptName + ": Not a valid link: '" + links[0] + "'")
  2399. return
  2400. }
  2401. const host = m[1]
  2402. let hoster = host.split('.')
  2403. hoster.pop()
  2404. hoster = hoster.pop().replace('-', '')
  2405. $.each(multi, function (key, val) {
  2406. const $option = $('<option></option>').val(key).html(val.name).appendTo($select)
  2407. if (key === currentdebrid) {
  2408. $option[0].selected = true
  2409. }
  2410. if (multi[key].isOnline(hoster)) {
  2411. $option.css('color', 'green')
  2412. } else {
  2413. $option.css('color', '#F00')
  2414. }
  2415. })
  2416. let $entry = menuentry($select)
  2417. $select.bind('change', function (ev) {
  2418. const $this = $(this)
  2419. // Change hoster
  2420. currentdebrid = $this.val()
  2421.  
  2422. // Check general support
  2423. if (multi[currentdebrid].isOnline(hoster)) {
  2424. // Check first link for support on this multi hoster
  2425. multi[currentdebrid].checkLink(links[0], function (result) {
  2426. if (!result) {
  2427. alert(scriptName + '\n\n' + host + ' is not supported by this hoster or the file is offline.\n\nChecked: ' + links[0])
  2428. }
  2429. })
  2430. } else {
  2431. alert(scriptName + '\n\n' + host + ' is not supported by ' + multi[currentdebrid].name)
  2432. }
  2433.  
  2434. // Add "Remember" checkbox
  2435. if (!$this.parent().find('#remember').length) {
  2436. const $div = $('<div>')
  2437. const $check = $('<input id="remember" type="checkbox" value="remember" title="Remember selection">').click(async function () {
  2438. if (this.checked) {
  2439. currentdebrid = $select.val()
  2440. await GM.setValue('currentdebrid', currentdebrid)
  2441. setStatus('Switched to ' + multi[currentdebrid].name, 1)
  2442. $div.remove()
  2443. }
  2444. })
  2445. $div.append($check).append('Remember')
  2446. $this.parent().append($div)
  2447. }
  2448. })
  2449.  
  2450. $entry = menuentry('Direct download')
  2451. $entry.click(function () { mouse('download', links) })
  2452.  
  2453. $entry = menuentry('Copy to clipboard')
  2454. $entry.click(function () { mouse('clipboard', links) })
  2455.  
  2456. if (settings.jDownloaderSupport) {
  2457. $entry = menuentry('Send to JDownloader')
  2458. $entry.attr('id', 'multiochhelperjdbutton')
  2459. $entry.hide()
  2460. $entry.click(function () { mouse('sendToJD', links) })
  2461. GM.xmlHttpRequest({
  2462. method: 'GET',
  2463. url: JDOWNLOADER + 'flash/',
  2464. onerror: function () {
  2465. },
  2466. onload: function (resp) {
  2467. if (resp && resp.responseText && resp.responseText.startsWith('JDownloader')) {
  2468. $('#multiochhelperjdbutton').show()
  2469. }
  2470. }
  2471. })
  2472. }
  2473.  
  2474. if (!showOneclickFromHighlighScriptAllLinks) {
  2475. $entry = menuentry('Show generated links')
  2476. $entry.click(function () { mouse('showLinks', links) })
  2477. }
  2478.  
  2479. $entry = menuentry('Show extracted links')
  2480. $entry.click(function () {
  2481. if (window.parent.parent !== window) {
  2482. window.parent.parent.postMessage({ iAm: 'Unrestrict.li', type: 'alert', str: links.join('\n') }, '*')
  2483. alert(links.join('\n'))
  2484. } else {
  2485. showExtractedLinks(links)
  2486. }
  2487. })
  2488.  
  2489. if (!showOneclickFromHighlighScriptAllLinks && (links.length > 1 || linksBeforeSelection !== false)) {
  2490. $entry = menuentry('Select links')
  2491. $entry.click(function () { linkSelector(links) })
  2492. }
  2493.  
  2494. if (!showOneclickFromHighlighScriptAllLinks) {
  2495. $entry = menuentry()
  2496. $('<a style="color:white !important;">Open Website</a>').attr('href', getMultiOCHWebsiteURL(links)).appendTo($entry)
  2497. }
  2498.  
  2499. if (showOneclickFromHighlighScriptAllLinks && showOneclickFromHighlighScriptAllLinksLinks) {
  2500. $entry = $(menuentry('Use all links on page...'))
  2501. $entry.click(function () {
  2502. // Switch to all links instead of one
  2503. const links = showOneclickFromHighlighScriptAllLinksLinks
  2504. showOneclickFromHighlighScriptAllLinksLinks = ''
  2505. menu(links)
  2506. $('#multiochhelper div:empty:not(:first)').remove()
  2507. setStatus('All links!', 1)
  2508. })
  2509. }
  2510.  
  2511. if (showOneclickFromHighlighScriptSelectedLinks && showOneclickFromHighlighScriptSelectedLinksLinks) {
  2512. $entry = $(menuentry('Use selected links...'))
  2513. $entry.click(function () {
  2514. // Switch to selected links instead of one
  2515. const links = showOneclickFromHighlighScriptSelectedLinksLinks
  2516. showOneclickFromHighlighScriptSelectedLinksLinks = ''
  2517. menu(links)
  2518. $('#multiochhelper div:empty:not(:first)').remove()
  2519. setStatus('Using selected links!', 1)
  2520. })
  2521. }
  2522.  
  2523. if (!showOneclickFromHighlighScriptAllLinks) {
  2524. $entry = menuentry($('<span style="cursor:default; color:silver">Userscript menu</span>').click(function (ev) { ev.stopPropagation(); aboutMe() }))
  2525. $entry.css('cursor', 'default')
  2526. $('<span style="cursor:pointer; color:White; border: 1px solid White; border-radius:3px; padding:0px; margin-left:20px; font-weight:bold ; " title="Close menu">X</span>').click(function () { $('#multiochhelper').remove() }).appendTo($entry)
  2527. }
  2528. }
  2529.  
  2530. function loader () {
  2531. // Show an animation, return function to remove the loader
  2532. $('#multiochhelper_status_loader').parent().show()
  2533. const $div = $('<div class="ochspinner"></div>').appendTo($('#multiochhelper_status_loader'))
  2534. return function () {
  2535. $div.remove()
  2536. }
  2537. }
  2538.  
  2539. async function mouse (action, linkText) {
  2540. // decide what to do after a mouse click
  2541. const removeImg = loader()
  2542. if (action === 'download') {
  2543. await download(linkText, removeImg)
  2544. } else if (action === 'showLinks') {
  2545. showLinks(linkText, removeImg)
  2546. } else if (action === 'openWebsite') {
  2547. openWebsite(linkText)
  2548. } else if (action === 'clipboard') {
  2549. await clipboard(linkText, removeImg)
  2550. } else if (action === 'menu') {
  2551. removeImg()
  2552. menu(linkText)
  2553. } else if (action === 'sendToJD') {
  2554. await sendToJD(linkText, removeImg)
  2555. }
  2556. }
  2557.  
  2558. function menuentry (html) {
  2559. const $li = $('<li>')
  2560. if (html) {
  2561. $li.append(html)
  2562. }
  2563. $li.appendTo('#multiochhelper ul')
  2564. return $li
  2565. }
  2566.  
  2567. function button (label) {
  2568. addCSSHead(`
  2569. #multiochhelper,#multiochhelper * {
  2570. font-family:Sans-Serif !important;
  2571. padding:0px; margin:0px;
  2572. }
  2573. #multiochhelper a, #multiochhelper a:link,#multiochhelper a:visited {
  2574. text-decoration:underline !important;
  2575. color:#3788e8 !important;
  2576. font-style:none !important;
  2577. }
  2578. #multiochhelper a:hover {
  2579. text-decoration:none !important;
  2580. color:#3788e8 !important;
  2581. font-style:none !important;
  2582. }
  2583. #multiochhelper ul li,#multiochhelper_status {
  2584. margin:1px 1px;
  2585. padding:1px 5px;
  2586. font-size:13px;
  2587. text-shadow:0 -1px 0 #333333;
  2588. color:White;
  2589. border:1px solid #8B3D92;
  2590. background-color:#B555C5;
  2591. background:radial-gradient(ellipse at center, #B555C5, #8B3D92);
  2592. list-style:none outside;
  2593. }
  2594. #multiochhelper div#multiochhelper_status_loader {
  2595. float:left;
  2596. }
  2597. #multiochhelper div#multiochhelper_status_text {
  2598. float:left;
  2599. }
  2600. #multiochhelper div#multiochhelper_status_clear {
  2601. clear:left;
  2602. }
  2603. #multiochhelper ul li {
  2604. cursor:pointer;
  2605. }
  2606. #multiochhelper ul li:hover {
  2607. background-color:#CC6BDD;
  2608. background:radial-gradient(ellipse at center, #CC6BDD, #8B3D92);
  2609. }
  2610. #multiochhelper select,#multiochhelper input {
  2611. border-radius:0;
  2612. box-shadow:none;
  2613. text-shadow:none;
  2614. border:none;
  2615. background:white;
  2616. color:black;
  2617. }
  2618.  
  2619. ${SPINNERCSS}
  2620. `)
  2621.  
  2622. // div container
  2623. const zi = getNextZIndex()
  2624. const $div = $('<div>').appendTo(document.body)
  2625. $div.attr('id', 'multiochhelper')
  2626. $div.attr('style', 'z-index:' + zi + '; position:fixed; background:#E6E6E6; color:Black; border:#B555C5 2px solid;border-radius:5px; padding:3px;')
  2627. if (settings.position[0] === 'top') {
  2628. $div.css('top', '0%')
  2629. } else {
  2630. $div.css('bottom', '0%')
  2631. }
  2632. if (settings.position[1] === 'left') {
  2633. $div.css('left', '0%')
  2634. } else {
  2635. $div.css('right', '0%')
  2636. }
  2637. // Status
  2638. const $status = $('<div>').appendTo($div).hide()
  2639. $status.attr('id', 'multiochhelper_status')
  2640. const $loader = $('<div>').appendTo($status)
  2641. $loader.attr('id', 'multiochhelper_status_loader')
  2642. const $statustext = $('<div>').appendTo($status)
  2643. $statustext.attr('id', 'multiochhelper_status_text')
  2644. const $statusclear = $('<div>').appendTo($status)
  2645. $statusclear.attr('id', 'multiochhelper_status_clear')
  2646.  
  2647. const $ul = $('<ul>').appendTo($div)
  2648.  
  2649. // Button
  2650. const $entry = menuentry(label || (multi[currentdebrid].name.charAt(0).toUpperCase() + multi[currentdebrid].name.slice(1)))
  2651.  
  2652. $ul.append($entry)
  2653.  
  2654. return $entry
  2655. }
  2656.  
  2657. const isSetup = await GM.getValue('setup', false)
  2658.  
  2659. // Update hoster status
  2660. let updatinghosters = false
  2661. if (isSetup) {
  2662. for (const key in multi) {
  2663. if (multi[key].updateStatusURLpattern.test(document.location.href)) { // usually in this is true in the iframe which is defined below
  2664. multi[key].updateStatus()
  2665. updatinghosters = true
  2666. break
  2667. }
  2668. }
  2669. }
  2670.  
  2671. // Create iframes to check hoster status:
  2672. if (!updatinghosters && isSetup) {
  2673. const now = new Date()
  2674. for (const key in multi) {
  2675. if ('updateStatusURL' in multi[key] && (now - multi[key].lastUpdate) > (settings.updateHosterStatusInterval * 60 * 60 * 1000)) {
  2676. const $iframe = $('<embed>').appendTo(document.body)
  2677. $iframe.bind('load', function () {
  2678. const frame = this
  2679. window.setTimeout(function () { $(frame).remove() }, 3000)
  2680. })
  2681. $iframe.attr('src', multi[key].updateStatusURL)
  2682. }
  2683. }
  2684. }
  2685.  
  2686. // Setup
  2687. if (!updatinghosters) {
  2688. if (!isSetup) {
  2689. await aboutMe()
  2690. if (!confirm(scriptName + ' Setup\n\nPlease take some time to configure ' + scriptName + ' and then save the settings!\n\nPress cancel to continue with the default configuration!')) {
  2691. await GM.setValue('setup', true)
  2692. alert(scriptName + '\n\nDefault settings will be used.')
  2693. document.location.reload()
  2694. }
  2695. }
  2696. }
  2697.  
  2698. if (document.location.href.indexOf('nopremium.pl') !== -1) {
  2699. // nopremium.pl Website
  2700. if (document.location.search.substring(0, 6) === '?link:') {
  2701. // Insert link on nopremium.pl
  2702. $('#filesList').val(decodeURIComponent(document.location.search.substring(6)))
  2703. }
  2704. } else if (document.location.href.indexOf('www.premiumize.me') !== -1) {
  2705. // premiumize.me Website
  2706. if (document.location.search.substring(0, 6) === '?link:') {
  2707. // Insert link on nopremium.pl
  2708. $('textarea').val(decodeURIComponent(document.location.search.substring(6)))
  2709. }
  2710. } else if (document.location.href.indexOf('download.serienjunkies.org') !== -1) {
  2711. // Serienjunkies
  2712. if (!document.querySelector('.g-recaptcha')) { // if not on captcha page
  2713. const $b = button('Decrypt links')
  2714. $b.click(function (ev) {
  2715. const removeImg = loader()
  2716. getSerienjunkiesLinks(removeImg)
  2717. })
  2718. }
  2719. } else if (document.location.href === 'http://filecloud.io/download.html') {
  2720. // filecloud.io
  2721. if (unsafeWindow.__currentUrl) {
  2722. showOneclickButton = true
  2723. showOneclickLink = decodeURIComponent(unsafeWindow.__currentUrl)
  2724. }
  2725. } else if (document.location.href.indexOf('filecrypt.cc') !== -1) {
  2726. // filecrypt.cc folder
  2727. if (document.location.href.indexOf('helper.html') !== -1) { // if not on captcha page
  2728. window.addEventListener('message', function filecryptmessage (event) {
  2729. if (event.data && typeof (event.data) === 'object') {
  2730. window.opener.postMessage({ filecryptData: JSON.stringify(event.data) }, '*') // Send message back to the opening window
  2731. window.removeEventListener('message', filecryptmessage) // Prevent further messages from creating several buttons
  2732. }
  2733. }, false)
  2734. } else if (document.location.href.indexOf('Container') !== -1) { // if not on captcha page
  2735. const $b = button("Please open the Click'n'Load Popup (several times)")
  2736. $b.click(function () {
  2737. $('#cnl_btn').click()
  2738. })
  2739. window.addEventListener('message', function filecryptmessage2 (event) { // Receive messages from the popup
  2740. if (event.data && typeof (event.data) === 'object' && 'filecryptData' in event.data) {
  2741. window.removeEventListener('message', filecryptmessage2) // Prevent further messages from creating several buttons
  2742. setStatus('Decrypting', -1)
  2743. const removeImg = loader()
  2744. getFilecryptcc(JSON.parse(event.data.filecryptData), removeImg)
  2745. }
  2746. }, false)
  2747. }
  2748. } else if (document.location.href.substring(7, 22) === 'protected.to/f-') {
  2749. // http://protected.to folder
  2750. if (document.querySelectorAll('.links a').length > 0) { // If not on captcha page
  2751. showOneclickButton = true
  2752. showOneclickLink = ''
  2753. $('.links a').each(function () {
  2754. showOneclickLink += decodeURIComponent(this.href) + '\n'
  2755. })
  2756. }
  2757. } else if (document.location.href.substring(8, 23) === 'safelinking.net') {
  2758. // safelinking.net folder
  2759. if (!document.getElementById('captcha-wrapper')) {
  2760. const $b = button('Decrypt links')
  2761. $b.click(function (ev) {
  2762. const removeImg = loader()
  2763. getSafeLinkingNetLinks(removeImg)
  2764. })
  2765. }
  2766. } else if (document.location.href.indexOf('.firedrive.com/share/') !== -1) {
  2767. // firedrive.com folder
  2768. showOneclickButton = true
  2769. showOneclickLink = ''
  2770. $('a.pf_item_link:visible').each(function () {
  2771. showOneclickLink += decodeURIComponent(this.href) + '\n'
  2772. })
  2773. } else if (document.location.href.indexOf('rapidgator.net/folder/') !== -1) {
  2774. // Rapidgator folder
  2775. showOneclickButton = true
  2776. showOneclickLink = ''
  2777. $('#grid tbody a').each(function () {
  2778. showOneclickLink += decodeURIComponent(this.href) + '\n'
  2779. })
  2780. } else if (document.location.hostname === "dailyuploads.net" && currentdebrid === 'premiumize.me') {
  2781. // Dailyuploads.net: submit direct download link (after captcha was solved) to premiumize.me instead of link
  2782. if (document.querySelector('div.banner div.inner a>img[src*="redbutton.png"]')) {
  2783. showOneclickButton = true
  2784. showOneclickLink = document.querySelector('div.banner div.inner a>img[src*="redbutton.png"]').parentNode.href
  2785. } else {
  2786. showOneclickButton = false
  2787. button("Please solve the captcha first")
  2788. }
  2789. } else if (document.location.hostname === 'multiup.org') {
  2790. // Multiup.org mirrors
  2791. showOneclickButton = document.querySelectorAll('button[link]').length > 0
  2792. showOneclickLink = Array.from(document.querySelectorAll('button[link]')).map(b => b.getAttribute('link')).join('\n')
  2793. } else if (document.location.href.substring(0, 55) === 'https://cvzi.github.io/Userscripts/index.html?link=sync') {
  2794. // Window opened from Helper script to sync hoster status (see postMessage events below)
  2795. showOneclickButton = false
  2796. const message = 'Updating hoster status...'
  2797. const h1 = document.body.appendChild(document.createElement('h1'))
  2798. h1.appendChild(document.createTextNode(scriptHightligherName + ': ' + message))
  2799. setTitle('')
  2800. window.setTimeout(function () {
  2801. const h2 = document.body.appendChild(document.createElement('h2'))
  2802. h2.appendChild(document.createTextNode('You may close this tab now'))
  2803. }, 4000)
  2804. } else if (document.location.href.substring(0, 51) === 'https://cvzi.github.io/Userscripts/index.html?link=') {
  2805. // Iframe for a X-Frame-Options website
  2806. showOneclickButton = true
  2807. showOneclickLink = decodeURIComponent(document.location.search.match(/link=(.+)/)[1])
  2808. } else {
  2809. // One click hoster website
  2810. showOneclickButton = true
  2811. showOneclickLink = decodeURIComponent(document.location.href)
  2812. }
  2813.  
  2814. if (showOneclickButton) {
  2815. let mouseOverAvailable = true
  2816.  
  2817. // Split links into array
  2818. const splitted = showOneclickLink.split('\n')
  2819. showOneclickLink = []
  2820. for (let i = 0; i < splitted.length; i++) {
  2821. if ($.trim(splitted[i])) {
  2822. showOneclickLink.push($.trim(splitted[i]))
  2823. }
  2824. }
  2825.  
  2826. const $b = button()
  2827.  
  2828. $b.bind('mousedown',
  2829. function (ev) {
  2830. mouseOverAvailable = false
  2831. if (ev.which === 3) { // Right click
  2832. mouse(settings.rightClick, showOneclickLink)
  2833. } else if (ev.which === 2) { // Middle click
  2834. mouse(settings.middleClick, showOneclickLink)
  2835. } else if (ev.which === 1) { // Left click {
  2836. mouse(settings.leftClick, showOneclickLink)
  2837. }
  2838. })
  2839. if (settings.mouseOver !== 'none') {
  2840. let ti = false
  2841. $b.on({
  2842. mouseover: function () {
  2843. if (!mouseOverAvailable) { return }
  2844. ti = setTimeout(function () {
  2845. if (!mouseOverAvailable) { return }
  2846. mouseOverAvailable = false
  2847. mouse(settings.mouseOver, showOneclickLink)
  2848. }, settings.mouseOverDelay)
  2849. },
  2850. mouseout: function () {
  2851. if (ti !== false) clearTimeout(ti)
  2852. }
  2853. })
  2854. }
  2855. // Prevent context menu on right click
  2856. if (settings.rightClick !== 'none') {
  2857. $b[0].addEventListener('contextmenu', e => e.preventDefault(), false)
  2858. }
  2859. }
  2860.  
  2861. // Handle messages from the highlight script
  2862. window.addEventListener('message', function (e) {
  2863. if (typeof e.data !== 'object' || !('iAm' in e.data) || e.data.iAm !== 'Unrestrict.li') {
  2864. return
  2865. }
  2866.  
  2867. switch (e.data.type) {
  2868. case 'alllinks':
  2869. if (showOneclickFromHighlighScriptAllLinks) {
  2870. return
  2871. }
  2872. showOneclickFromHighlighScriptAllLinks = true
  2873. showOneclickFromHighlighScriptAllLinksLoc = e.data.loc
  2874. showOneclickFromHighlighScriptAllLinksLinks = e.data.links.join('\n')
  2875. if ($('#multiochhelper ul li').length > 1) { // Menu already opened
  2876. menu(showOneclickLink)
  2877. }
  2878. break
  2879. case 'selectedlinks':
  2880. if (showOneclickFromHighlighScriptSelectedLinks) {
  2881. return
  2882. }
  2883. showOneclickFromHighlighScriptSelectedLinks = true
  2884. showOneclickFromHighlighScriptSelectedLinksLoc = e.data.loc
  2885. showOneclickFromHighlighScriptSelectedLinksLinks = e.data.links.join('\n')
  2886. if ($('#multiochhelper ul li').length > 1) { // Menu already opened
  2887. menu(showOneclickLink)
  2888. }
  2889. break
  2890. case 'requesthosterstatus': {
  2891. window.setTimeout(function () {
  2892. const h3 = document.body.appendChild(document.createElement('h3'))
  2893. h3.appendChild(document.createTextNode('This will only take a few seconds'))
  2894. }, 0)
  2895. const o = {}
  2896. for (const key in multi) {
  2897. o[key] = multi[key].status
  2898. }
  2899. e.source.postMessage({ iAm: 'Unrestrict.li', type: 'hosterstatus', str: JSON.stringify(o) }, '*')
  2900. break
  2901. }
  2902. }
  2903. }, true)
  2904. })()