您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Strip all 'ouo' link protector from download links and fix file download names
// ==UserScript== // @name animes.vision - Strip all 'ouo' link protector from download links and fix file download names // @name:pt-BR animes.vision - Tira o protetor de links 'ouo' das URLs de download e corrige o nome dos arquivos // @namespace secretx_scripts // @match *://animes.vision/*/*/*/*/download // @version 2024.02.19 // @author SecretX // @description Strip all 'ouo' link protector from download links and fix file download names // @description:pt-br Remove das URLs de download do site 'ouo', transformando-os em download direto com o nome do episódio corrigido (chega de nomes aleatórios) // @grant GM_addStyle // @grant GM_getResourceText // @grant GM.xmlHttpRequest // @run-at document-start // @require https://cdn.jsdelivr.net/npm/[email protected]/vanillatoasts.min.js // @resource VANILLA_TOASTS_CSS https://cdn.jsdelivr.net/npm/[email protected]/vanillatoasts.min.css // @icon https://animes.vision/favicon.ico // @license GNU LGPLv3 // ==/UserScript== const downloadButtonSectionName = "Vision Oficial FREE"; const tagName = "wire:initial-data"; const interestedInResponseWith = "download.links"; const linkProtectorRegex = /^https?:\/\/ouo.\w{2,3}\/\w+\/[a-z0-9]+\?s=(.+)$/i; const qualityMapSelectors = { keyUHD: "uhd", keyFHD: "fullhd", keyHD: "hd", keySD: "sd", }; const downloadButtons = {}; function findDownloadButtons() { for (let i = 1; i < 10; i++) { const elems = Array.from(document.querySelector(`div.download-links:nth-child(${i})`)?.children ?? []); if (elems.length === 0) { break; } const divTitle = elems.find(a => a.nodeName === "SPAN")?.innerText; if (divTitle?.toLowerCase() !== downloadButtonSectionName.toLowerCase()) { // not the right section, try again continue; } const downloadBtns = elems.find(a => a.nodeName === "DIV").children; for (let btn of downloadBtns) { const quality = btn.innerText.toLowerCase(); if (quality.includes("uhd") || quality.includes("ultra hd") || quality.includes("4k")) { downloadButtons.uhd = btn; } else if (quality.includes("fhd") || quality.includes("fullhd")) { downloadButtons.fullhd = btn; } else if (quality.includes("hd")) { downloadButtons.hd = btn; } else if (quality.includes("sd")) { downloadButtons.sd = btn; } else { console.warn(`Unknown episode quality: ${quality}`); } } if (downloadButtons.length > 0) { break; } } } function extractDataFromDiv(stringDiv){ const div = document.createElement("div"); div.innerHTML = stringDiv; // extracts the tag that contains the data we want return div.children[0].getAttribute(tagName); } function cssCopyCat(elem1, elem2) { // get computed styles of original element const styles = window.getComputedStyle(elem1); let cssText = styles.cssText; if (!cssText) { cssText = Array.from(styles).reduce((str, property) => { return `${str}${property}:${styles.getPropertyValue(property)};`; }, ''); } // assign css styles to element elem2.style.cssText = cssText; } function doRequest(httpMethod, url) { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: httpMethod.toUpperCase(), url: url, onload: resolve, onerror: reject, responseType: "text", timeout: 6000, }); }); } function toast(title, message, type, timeout) { return VanillaToasts.create({ title: title, text: message, type: type, // success, info, warning, error / optional parameter timeout: timeout, }); } const successToast = (title, message) => toast(title, message, "success", 1800); const errorToast = (title, message) => toast(title, message, "error", 3800); function cleanupLink(link) { const match = link.match(linkProtectorRegex); if (match == null) { return link; } return match[1]; } function getEpisodeName() { return document.querySelector(".cat-heading")?.innerText; } function replaceDownloadButtons() { const url = window.location.href; doRequest("GET", url) .then(response => { const html = (new DOMParser()).parseFromString(response.responseText, 'text/html'); const dataJson = Array.from(html.getElementsByTagName("div")) .map(div => div.getAttribute(tagName)) .find(json => json != null && json.includes(interestedInResponseWith)); if (dataJson == null) { console.error("Wasn't able to find any episode div, thus didn't extract any download links from the page"); return; } const links = JSON.parse(dataJson)?.serverMemo?.data?.episodiosLink; if (links == null) { console.error(`Wasn't able to find links inside ${tagName} div, thus didn't extract any download links from the page`); return; } for (let keyQuality in links) { replaceDownloadButton(links, keyQuality); } if (!isMobile) { try { successToast("AnimesVision Script", "Download links injected successfully!") } catch (e) {} } }) .catch(error => { const msg = `Could not bypass native ${window.location.host} links on page ${url} with error: ${error}`; console.error(msg); try { errorToast("AnimesVision Script", msg) } catch (e) {} }); } function replaceDownloadButton(links, keyQuality) { const link = cleanupLink(links[keyQuality]); const quality = qualityMapSelectors[keyQuality]; if (quality == null) { console.warn(`Unknown quality received from backend response: ${keyQuality}`); return; } const downloadBtn = downloadButtons[quality]; if (downloadBtn == null) { console.warn(`No download button found for quality received from backend response: ${quality}`); return; } const newBtn = document.createElement("a"); cssCopyCat(downloadBtn, newBtn); newBtn.innerText = downloadBtn.innerText; newBtn.href = link; const episodeName = getEpisodeName(); if (episodeName != null) { newBtn.download = episodeName; } // remove the old button with the new 'a' tag that downloads properly named files and doesn't have any link protector downloadBtn.replaceWith(newBtn); } function replaceExternalDownloadLinks() { Array.from(document.getElementsByTagName("a")) .filter(a => a.href != null && a.href.match(linkProtectorRegex) != null) .forEach(a => a.href = cleanupLink(a.href)); } function injectVanillaToastsCss() { const containerRegex = /(#vanillatoasts-container){([^}]+)/; const titleRegex = /(\.vanillatoasts-title){([^}]+)}/; const msgRegex = /(\.vanillatoasts-text){([^}]+?);color:#\d+}/; const css = GM_getResourceText("VANILLA_TOASTS_CSS") .replace(containerRegex, "$1{$2;z-index:1000000") .replace(titleRegex, "$1{$2;color:rgb(70,70,70)}") .replace(msgRegex, "$1{$2;color:rgb(110,110,110)}"); GM_addStyle(css); } async function copyToClipboard(text) { if (navigator && navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text) .then(() => { console.info(`Successfully copied '${text}' to clipboard using navigator API`); return true }, reason => { console.error("Copy to clipboard using navigator API failed", reason); return false }); } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { const textarea = document.createElement("textarea"); textarea.textContent = text; textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in Microsoft Edge. document.body.appendChild(textarea); textarea.select(); try { return document.execCommand("copy"); // Security exception may be thrown by some browsers. } catch (e) { console.warn("Copy to clipboard using query command 'copy' failed", e); } finally { document.body.removeChild(textarea); } } else if (window.clipboardData && window.clipboardData.setData) { // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible. window.clipboardData.setData("Text", text); return true; } else { console.warn("This browser has NO support for clipboard copying whatsoever!"); return false; } } function addCopyTitleButton() { const episodeName = getEpisodeName(); if (episodeName == null) { console.info("No episode name found, skipping copy title button"); return; } const copyTitleBtn = document.createElement("button"); copyTitleBtn.innerText = "Copy"; copyTitleBtn.setAttribute("class", "btn btn-control btn-secondary btn-md"); copyTitleBtn.addEventListener("click", () => { copyToClipboard(episodeName).then(sucessfullyCopied => { if (sucessfullyCopied && !isMobile) { successToast("AnimesVision Script", "Episode name copied to clipboard!"); } }); }); document.querySelector(".main-az").appendChild(copyTitleBtn); } window.addEventListener("DOMContentLoaded", () => { 'use strict'; findDownloadButtons(); injectVanillaToastsCss(); addCopyTitleButton(); }, false); window.addEventListener("load", () => { 'use strict'; replaceDownloadButtons(); replaceExternalDownloadLinks(); }, false); const isMobile = (function(){ let check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); return check; })();