// ==UserScript== // @name tera-download // @version 0.4 // @description Simple script that downlaod terabox file without app // @namespace https://github.com/tttt369/tera-download // @run-at document-start // @grant unsafeWindow // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @match *://*.1024-terabox.com/* // @match *://*.1024terabox.com/* // @match *://*.1024tera.com/* // @match *://*.bestclouddrive.com/* // @match *://*.dubox.com/* // @match *://*.freeterabox.com/* // @match *://*.gibibox.com/* // @match *://*.mirrobox.com/* // @match *://*.nephobox.com/* // @match *://*.pebibox.com/* // @match *://*.tera1024box.com/* // @match *://*.terabox1024.com/* // @match *://*.terabox.app/* // @match *://*.terabox.com/* // @match *://*.teraboxfree.com/* // @match *://*.terastream.fun/* // @match *://*.terabox.link/* // @match *://*.teraboxlink.com/* // @match *://*.terabox-share.com/* // @match *://*.teraboxshare.com/* // @match *://*.terabox.space/* // @match *://*.teraboxurl.com/* // @match *://*.terabox.zone/* // @match *://*.terafileshare.com/* // @match *://*.teralinkshare.com/* // @match *://*.terareferral.com/* // @match *://*.tera-share.com/* // @match *://*.terasharefile.com/* // @match *://*.terasharelink.com/* // @match *://*.terashareus.com/* // @match *://*.terabox.best/* // @match *://*.teraboxapp.com/* // @license MIT // ==/UserScript== // from: https://global-staticplat.cdn.bcebos.com/general-conf/domain.json (function() { 'use strict'; const UTIL = { update_menu() { function register(label, handler) { const id = GM_registerMenuCommand(label, handler); STATE.registeredMenuIds.push(id); }; STATE.registeredMenuIds.forEach(GM_unregisterMenuCommand); STATE.registeredMenuIds = []; const data = Object.values(STATE.data); const [filteredData] = UTIL.clean_data(data) if (!filteredData.length) return; filteredData.slice(0, 9).forEach(dict => { register(dict.name, () => UTIL.batch_download([dict])); }); register("download all", () => { UTIL.batch_download(data); }); }, clean_data(data) { const dirData = [] const filteredData = data.filter(dict => { if (dict.isdir) { dirData.push(dict.name) return false } return true }) const log = dirData.length ? `${dirData.join(", ")} is folder, open folder to get download link` : "" return [filteredData, log] }, async batch_download(data) { if (!UTIL.check()) return; const [filteredData, log] = UTIL.clean_data(data) if (log.length) alert(log) for (const [idx, dict] of filteredData.entries()) { if (!dict || !dict.url) continue; if (idx > 0) { await new Promise(r => setTimeout(r, 2000)); } UTIL.download_file(dict.url, dict.name); } }, download_file(url, filename) { if (!url.length) return const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); }, parse_response(data) { if (data.error_msg && data.error_msg !== TARGET.succMsg) { STATE.islogin = false; } if (!data.list) return; data.list.forEach(dict => { const fsId = String(dict.fs_id); if (dict.isdir) { STATE.data[fsId] = { name: dict.server_filename, url: "", isdir: 1, }; } else if (dict.dlink) { STATE.data[fsId] = { name: dict.server_filename, url: dict.dlink, isdir: 0, }; } }); UTIL.update_menu(); }, check() { if (!STATE.islogin) { alert("you need to login to download") return 0 } return 1 }, get_query(url, param) { const params = new URL(url).searchParams; const fid = params.get(param); return fid } } const INTERCEPT = { xhr() { function handle_xhr_response(xhr) { const url = xhr._url; if (url.includes(TARGET.membership) || url.includes(TARGET.list)) { const responseData = JSON.parse(xhr.responseText); UTIL.parse_response(responseData); } } const xhrProto = STATE.win.XMLHttpRequest.prototype; const originalOpen = xhrProto.open; const originalSend = xhrProto.send; xhrProto.open = function(method, url) { this._url = url; return originalOpen.apply(this, arguments); }; xhrProto.send = function(body) { this.addEventListener("load", function() { handle_xhr_response(this); }); return originalSend.apply(this, arguments); }; }, default_download() { document.addEventListener('click', async function(e) { const downloadBtn = e.target.closest(TARGET.downloadClass); if (!downloadBtn) return; e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); const selectedElem = document.querySelectorAll(TARGET.selectedCss); if (selectedElem.length === 0) { alert('please select file'); return; } const dataToDownload = []; for (const elem of selectedElem) { let data = null; const nameElem = elem.querySelector(TARGET.fileNameClass); const name = nameElem ? nameElem.textContent.trim() : ""; const srcElem = elem.querySelector(TARGET.fileIdclass); const fsId = srcElem ? UTIL.get_query(srcElem.src, "fid") : null; if (fsId && STATE.data[fsId]) { data = STATE.data[fsId]; } else { data = Object.values(STATE.data).find(f => f.name === name); } if (data) dataToDownload.push(data); } UTIL.batch_download(dataToDownload); }, true); }, directory() { function start_body_observer() { if (document.body) { bodyObserver.observe(document.body, { childList: true, subtree: true, characterData: true }); } } function debounced_update_menu() { if (timeoutId) clearTimeout(timeoutId); timeoutId = setTimeout(() => { UTIL.update_menu(); }, 500); } let currentTarget = null; let timeoutId = null; const bodyObserver = new MutationObserver(() => { const newTarget = document.querySelector(TARGET.directoryClass); if (!newTarget) return; if (newTarget !== currentTarget) { currentTarget = newTarget; debounced_update_menu(); } }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start_body_observer); } else { start_body_observer(); } } } const UNBLOCK = { constructor() { function spoof_window_size() { Object.defineProperty(STATE.win, "outerWidth", { get: function() { return STATE.win.innerWidth; } }); Object.defineProperty(STATE.win, "outerHeight", { get: function() { return STATE.win.innerHeight; } }); } function block_anti_debug_intervals() { const originalSetInterval = STATE.win.setInterval; STATE.win.setInterval = function(fn, delay) { if (typeof fn === 'function') { const fnStr = fn.toString(); if (delay === 2000 && (fnStr.includes('outerWidth') || fnStr.includes('debugger'))) { console.log("Anti-debug loop blocked."); return null; } } return originalSetInterval.apply(this, arguments); }; } const originalConstructor = STATE.win.Function.prototype.constructor; STATE.win.Function.prototype.constructor = function(str) { if (str && str.includes('debugger')) { return function() {}; } return originalConstructor.apply(this, arguments); }; spoof_window_size() block_anti_debug_intervals() }, keyboard() { STATE.win.addEventListener('keydown', function(e) { if (e.keyCode === 123 || (e.ctrlKey && e.shiftKey && e.keyCode === 73)) { e.stopImmediatePropagation(); } }, true); }, click() { STATE.win.addEventListener('contextmenu', function(e) { e.stopImmediatePropagation(); }, true); }, } const STATE = { // you need to use unsafeWindow to unblock debugger detection. win: typeof unsafeWindow !== 'undefined' ? unsafeWindow : window, registeredMenuIds: [GM_registerMenuCommand("Initializing...", () => {})], data: {}, islogin: true, } const TARGET = { membership: "/membership/proxy/user?", list: "/share/list?", succMsg: "succ", downloadClass: ".download-btn", selectedCss: ".file-item-listmode.checked", fileNameClass: ".file-item-name", fileIdclass: ".file-icon-img", directoryClass: '.breadcrumb-item.is-current' } INTERCEPT.xhr() UNBLOCK.constructor() UNBLOCK.click() UNBLOCK.keyboard() INTERCEPT.default_download() INTERCEPT.directory() })();