// ==UserScript== // @name ViewXtend // @description Youtube with extra features // @icon None // @version 1.0.2 // @author Fate (https://github.com/FateNotAvailable) // @namespace https://github.com/FateNotAvailable/ViewXtend // @supportURL https://github.com/FateNotAvailable/ViewXtend/issues // @license GPL-3 // @icon https://i.ibb.co/cwvhZQN/View-Xtend.png // @match https://www.youtube.com/* // @match https://www.youtube-nocookie.com/* // @match https://m.youtube.com/* // @grant none // @run-at document-start // @compatible chrome // @compatible firefox // @compatible opera // @compatible edge // @compatible safari // @downloadURL https://update.greasyfork.icu/scripts/474406/ViewXtend.user.js // @updateURL https://update.greasyfork.icu/scripts/474406/ViewXtend.meta.js // ==/UserScript== /******/ (() => { // webpackBootstrap /******/ "use strict"; var __webpack_exports__ = {}; ;// CONCATENATED MODULE: ./src/api/ViewXtend.js class ViewXtendAPI { constructor () { } waitForElementBy(by, selector, timeout) { return new Promise((resolve, reject) => { const startTime = Date.now(); function checkElement() { let element; if (by == "q") { element = document.querySelector(selector); } else if (by == "t") { element = document.getElementsByTagName(selector); } if (element) { resolve(element); } else if (Date.now() - startTime >= timeout) { reject(new Error(`Element '${selector}' not found within ${timeout}ms`)); } else { setTimeout(checkElement, 100); // Check again in 100ms } } checkElement(); }); } waitForElementNotHidden(selector, timeout) { return new Promise((resolve, reject) => { const startTime = Date.now(); function checkElement() { let element = document.querySelector(selector); if (element) { if (element.style.display != 'none') { resolve(element); } else { setTimeout(checkElement, 100); // Check again in 100ms } } else if (Date.now() - startTime >= timeout) { reject(new Error(`Element '${selector}' not found within ${timeout}ms`)); } else { setTimeout(checkElement, 100); // Check again in 100ms } } checkElement(); }); } get_player() { /** * Gets video player element */ return this.waitForElementBy("q", "#movie_player > div.html5-video-container > video", 25000); } get_logo() { /** * Gets youtube logo */ return this.waitForElementBy("q", "#logo-icon > yt-icon-shape > icon-shape > div > svg", 25000) } get_videos() { /** * Gets videos as html tags */ //return this.waitForElementBy("t", "ytd-rich-grid-media", 25000); return document.getElementsByTagName("ytd-rich-grid-media"); } get_fake_videos() { /** * Get ads that look like videos */ //return this.waitForElementBy("t", "ytd-ad-slot-renderer", 25000); return document.getElementsByTagName("ytd-ad-slot-renderer"); } set_title(title) { /** * Set page title */ document.title = title } get_title() { /** * Get page title */ return document.title } get_video_download_button() { return this.waitForElementBy("q", "#action-button > yt-button-shape > button > div > span", 25000) } get_download_dialog() { return this.waitForElementNotHidden("body > ytd-app > ytd-popup-container > tp-yt-paper-dialog", 25000) } get_download_options() { return new Promise((resolve, reject) => { const startTime = Date.now(); function checkElement() { let element = document.querySelector("#premium-options"); if (element) { return resolve(element.childNodes); } else if (Date.now() - startTime >= timeout) { reject(new Error(`Element '${selector}' not found within ${timeout}ms`)); } else { setTimeout(checkElement, 100); // Check again in 100ms } } checkElement(); }); } get_checked_download() { let pr = document.querySelectorAll('paper-ripple'); for (let i = 0; i < pr.length; i++) { if (pr[i].getAttributeNames().includes("checked")) { return pr[i] } } return null; } create_plugins_button() { const f = document.createElement("ytd-compact-link-renderer"); f.setAttribute("injected", "true") f.className = "style-scope yt-multi-page-menu-section-renderer" f.setAttribute("compact-link-style", "") f.setAttribute("hide-secondary-string", "") f.onclick = () => { window.location.href = '/plugins'; } f.innerHTML = ` `; return f } suicidalInterval(callback, ms) { function die() { console.log("I'm ending myself!"); clearInterval(interval); } const interval = setInterval(() => { callback(die); }, ms); } } /* harmony default export */ const ViewXtend = (ViewXtendAPI); ;// CONCATENATED MODULE: ./src/utils/Plugins.js class Plugins { constructor() {} loadFromLocalStorage(key, defaultValue) { // Check if the key exists in localStorage if (localStorage.hasOwnProperty(key)) { try { // Parse the JSON data from localStorage const data = JSON.parse(localStorage.getItem(key)); // Check if the parsed data is an object (JSON) if (typeof data === 'object' && data !== null) { return data; } else { // Handle the case where the data is not a valid JSON object console.error('Data in localStorage is not a valid JSON object.'); } } catch (error) { // Handle any errors that occur during JSON parsing console.error('Error parsing data from localStorage:', error); } } return defaultValue; } load_plugins() { const pluginsData = this.loadFromLocalStorage('plugins', {}); for (const key in pluginsData) { if (pluginsData.hasOwnProperty(key)) { const value = pluginsData[key]; // Check if the URL matches the regex pattern const regex = new RegExp(value['runs_on']); const currentURL = window.location.href; if (regex.test(currentURL)) { const script = document.createElement('script'); script.src = value['url']; document.head.appendChild(script); } } } } } /* harmony default export */ const utils_Plugins = (Plugins); ;// CONCATENATED MODULE: ./src/web/index.html // Module var code = " ViewXtend Plugins
<" + "script>const ld=()=>{let e=localStorage.getItem(\"plugins\");if(e){const t=JSON.parse(e);for(const e in t){t[e];const n=document.getElementById(`plugin-${e}`);n&&(n.checked=!0)}}};let container=document.getElementsByClassName(\"vxt-grid-container\")[0];var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){4===xhr.readyState&&200===xhr.status?(JSON.parse(xhr.responseText).forEach((function(e){const t=document.createElement(\"div\");t.className=\"vxt-card\";const n=document.createElement(\"div\");n.className=\"vxt-switch\";const s=document.createElement(\"input\");s.className=\"tgl tgl-skewed\",s.type=\"checkbox\",s.id=`plugin-${e.id}`,s.addEventListener(\"click\",(()=>{switch_click(e.id,e.url,e.runs_on)}));const a=document.createElement(\"label\");a.className=\"tgl-btn\",a.setAttribute(\"data-tg-off\",\"OFF\"),a.setAttribute(\"data-tg-on\",\"ON\"),a.setAttribute(\"for\",`plugin-${e.id}`);const c=document.createElement(\"div\");c.className=\"vxt-title\",c.textContent=e.name;const l=document.createElement(\"div\");l.className=\"vxt-description\",l.textContent=e.description,n.appendChild(s),n.appendChild(a),t.appendChild(n),t.appendChild(c),t.appendChild(l),container.appendChild(t)})),ld()):4===xhr.readyState&&200!==xhr.status&&console.error(\"Failed to load data.json\")},xhr.open(\"GET\",\"https://viewxtend.pages.dev/plugins/registry.json\",!0),xhr.send();const switch_click=(e,t,n)=>{if(document.getElementById(`plugin-${e}`).checked){const s=localStorage.getItem(\"plugins\"),a=s?JSON.parse(s):{};a[e]={url:t,runs_on:n},localStorage.setItem(\"plugins\",JSON.stringify(a))}else{const t=localStorage.getItem(\"plugins\"),n=t?JSON.parse(t):{};n.hasOwnProperty(e)&&(delete n[e],localStorage.setItem(\"plugins\",JSON.stringify(n)))}}<" + "/script> "; // Exports /* harmony default export */ const web = (code); ;// CONCATENATED MODULE: ./src/index.js window.ViewXtendAPI_direct = ViewXtend; window.ViewXtendAPI = new ViewXtend; const vx_main_fun = () => { if (window.location.pathname == "/plugins") { document.open(); document.write(web); document.close(); return; } let API = new ViewXtend(); let PLG = new utils_Plugins(); PLG.load_plugins(); const shouldBeInserted = () => { let items = document.querySelector("#sections > yt-multi-page-menu-section-renderer:nth-child(1)").querySelector("#items").childNodes; for (let i = 0; i < items.length; i++) { if(items[i].getAttribute("injected") == "true") { return false; } } return true; } setInterval(() => { try { var parentElement = document.querySelector("#sections > yt-multi-page-menu-section-renderer:nth-child(1)").querySelector("#items"); if (shouldBeInserted()) { var firstItem = parentElement.firstElementChild; parentElement.insertBefore(API.create_plugins_button(), firstItem.nextSibling); var icon = document.querySelectorAll("#content-icon > yt-icon")[1]; icon.innerHTML = `
`; icon.parentNode.parentElement.querySelector("#label").innerText = "Plugins"; } } catch {}; }, 150); } vx_main_fun() /******/ })() ;