From 4d5b2f009d515eb8b42573c7bae9aeab24d4b048 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 18:30:30 +0800 Subject: [PATCH 01/11] Add Chinese localization and comments shortcut --- app/src/main/assets/ytpro/bgplay.js | 248 ++ app/src/main/assets/ytpro/innertube.js | 1160 +++++++ app/src/main/assets/ytpro/script.js | 2926 +++++++++++++++++ .../pro/webview/YTProWebViewClient.java | 29 +- app/src/main/res/values-zh-rCN/strings.xml | 7 + scripts/script.js | 253 +- 6 files changed, 4591 insertions(+), 32 deletions(-) create mode 100644 app/src/main/assets/ytpro/bgplay.js create mode 100644 app/src/main/assets/ytpro/innertube.js create mode 100644 app/src/main/assets/ytpro/script.js create mode 100644 app/src/main/res/values-zh-rCN/strings.xml diff --git a/app/src/main/assets/ytpro/bgplay.js b/app/src/main/assets/ytpro/bgplay.js new file mode 100644 index 00000000..4feaafe9 --- /dev/null +++ b/app/src/main/assets/ytpro/bgplay.js @@ -0,0 +1,248 @@ +/*****YTPRO******* +Author: Prateek Chaubey +Version: 3.9.2 +URI: https://github.com/prateek-chaubey/YTPRO +*/ + +if (typeof MediaMetadata === 'undefined') { +window.MediaMetadata = class { +constructor(data = {}) { +this.title = data.title || ''; +this.artist = data.artist || ''; +this.album = data.album || ''; +this.artwork = data.artwork || []; +} +}; + +} + + + +if (!('mediaSession' in navigator)) { + +window.handlers = {}; +window.serviceRunning=false; + + + + +let _state = 'none'; +let _metadata = null; + +Object.defineProperty(navigator, 'mediaSession', { +value: {}, +configurable: true +}); + +Object.defineProperty(navigator.mediaSession, 'metadata', { +get() { +return _metadata; +}, +set(value) { +//console.log("metadata set:", value); +bgPlay(value); +_metadata = value; +}, +configurable: true +}); + + + + +navigator.mediaSession.setActionHandler = (action, handler) => { + +if (typeof handler === 'function') { +handlers[action] = handler; +} + +//console.log(action,handler) + + +}; + + + + + + +Object.defineProperty(navigator.mediaSession, 'playbackState', { +get() { +return _state; +}, +set(value) { + +//console.log("Custom playbackState set to:", value); + + +_state = value; + + +var ytproAud = document.getElementsByClassName('video-stream')[0]; + +if (value === 'playing') { +setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100); +} else if (value === 'paused' && (pauseAllowed || PIPause)) { +setTimeout(()=>{Android.bgPause(ytproAud.currentTime*1000);},100); +}else if(value === "none" && !(window.location.href.indexOf("youtube.com/watch") > -1 || window.location.href.indexOf("youtube.com/shorts") > -1 )){ +Android.bgStop(); +window.serviceRunning=false; +} + + + +}, +configurable: true +}); + + + +} + + + + + + + +async function bgPlay(info){ + + +if(!(window.location.href.indexOf("youtube.com/watch") > -1 || window.location.href.indexOf("youtube.com/shorts") > -1 )) return; + + +if(!info) return; + + +var ytproAud = document.getElementsByClassName('video-stream')[0]; + + +if(!ytproAud) return; + + +var iconBase64="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="; + + +var img = new Image(); +img.crossOrigin="anonymous"; +img.src=info?.artwork?.[0]?.src; + + +var canvas = document.createElement('canvas'); +canvas.style.width = "1600px"; +canvas.style.height = "900px"; +canvas.style.background="black"; +var context = canvas.getContext('2d'); + +canvas.width = 160; +canvas.height = 90; + +//var z=performance.now(); + + +await new Promise((res,rej)=>{ +img.onload=()=>res(); +}); + + +try{ +context.drawImage(img, 0,0 ,160,90); +iconBase64 = canvas.toDataURL('image/png',1.0); +}catch{} + + + + + + + + + + + + +if(window.serviceRunning){ +setTimeout(()=>{Android.bgUpdate(iconBase64.replace("data:image/png;base64,", ""),info.title,info.artist,ytproAud.duration*1000);},50); +setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100); +} +else{ +window.serviceRunning=true; +setTimeout(()=>{Android.bgStart(iconBase64.replace("data:image/png;base64,", ""),info.title,info.artist,ytproAud.duration*1000);},50); +setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100); +} + + + + + + + + + + + +} + + + + + + + + + + + + + + + + + + +/*When user hits the notification*/ +function seekTo(t){ +handlers.seekto({ seekTime: t/1000 }); +} + +/*Daamm , its play*/ +function playVideo(){ + + +if(!pauseAllowed){ +window.PIPause = false; +navigator.mediaSession.playbackState = 'playing'; +} + +handlers.play(); +} + +/*Daamm , its pause*/ +function pauseVideo(){ + + + +if(!pauseAllowed){ +window.PIPause=true; +navigator.mediaSession.playbackState = 'paused'; +} +handlers.pause(); + + + + +} + + + +/*Alexa , play da next song*/ +async function playNext(){ +handlers.nexttrack(); +} + + + + +/*Alexa , play the f**ng song once again */ +function playPrev(){ +handlers.previoustrack(); +} diff --git a/app/src/main/assets/ytpro/innertube.js b/app/src/main/assets/ytpro/innertube.js new file mode 100644 index 00000000..09044194 --- /dev/null +++ b/app/src/main/assets/ytpro/innertube.js @@ -0,0 +1,1160 @@ +/*****YTPRO******* +Author: Prateek Chaubey +Version: 3.9.8 +URI: https://github.com/prateek-chaubey/YTPRO +Last Updated On: 1 May , 2026 , 19:25 IST +*/ + + + +window.ytproSabrDownload= async function() { + + +var ytproDownDiv=getDownloadElement(); + +ytproDownDiv.querySelector("#videoViewDiv").innerHTML="Loading..."; + + +//Get Video ID +var videoId =""; + +if(window.location.pathname.indexOf("shorts") > -1){ +videoId=window.location.pathname.substr(8,window.location.pathname.length); +} +else{ +videoId=new URLSearchParams(window.location.search).get("v"); +} + + +//videoId="vY31qIX7LzQ"; + + +if (!videoId) { window.Android?.showToast?.('No video ID found in URL.'); return; } + +// Imports +const { Innertube, Platform, Constants } = await import( +'https://cdn.jsdelivr.net/npm/youtubei.js@17.0.1/bundle/browser.min.js' +); +const { SabrStream } = await import('https://esm.sh/googlevideo@4.0.4/sabr-stream'); +const { buildSabrFormat , EnabledTrackTypes } = await import('https://esm.sh/googlevideo@4.0.4/utils'); +const { BG, buildURL, getHeaders } = await import('https://esm.sh/bgutils-js@3.2.0'); + +Platform.shim.eval = async (data, env) => { +const props = []; +if (env.n) props.push(`n: exportedVars.nFunction("${env.n}")`); +if (env.sig) props.push(`sig: exportedVars.sigFunction("${env.sig}")`); +return new Function(`${data.output}\nreturn { ${props.join(', ')} }`)(); +}; + +// Create Innertube (WEB Client Setup & Proxy) +const cookies = window.Android?.getAllCookies?.('https://www.youtube.com') ?? ''; + +const yt = await Innertube.create({ +cookie: cookies, +retrieve_player: true, +generate_session_locally: true, +fetch: async (input, init = {}) => { + + +const reqUrl = input instanceof Request ? input.url : input.toString(); +const url = new URL(reqUrl); +const method = init.method ?? (input instanceof Request ? input.method : 'GET'); +const headers = new Headers(); + +if (input instanceof Request) input.headers.forEach((v, k) => headers.set(k, v)); +if (init.headers) new Headers(init.headers).forEach((v, k) => headers.set(k, v)); + +headers.set('User-Agent', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); +headers.set('Sec-Ch-Ua', '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"'); +headers.set('Sec-Ch-Ua-Mobile', '?0'); +headers.set('Sec-Ch-Ua-Platform', '"Windows"'); + +const playerId = Array.from(document.scripts) +.map(s => s.src.match(/player\/(.*?)\/player/)) +.find(m => m)?.[1] || '4b0d80ee'; + +if (url.pathname === '/iframe_api') { +const mockedApiCode = `var scriptUrl = 'https:\\/\\/www.youtube.com\\/s\\/player\\/${playerId}\\/www-widgetapi.vflset\\/www-widgetapi.js';try{var ttPolicy=window.trustedTypes.createPolicy("youtube-widget-api",{createScriptURL:function(x){return x}});scriptUrl=ttPolicy.createScriptURL(scriptUrl)}catch(e){}var YT;if(!window["YT"])YT={loading:0,loaded:0};var YTConfig;if(!window["YTConfig"])YTConfig={"host":"https://www.youtube.com"};\nif(!YT.loading){YT.loading=1;(function(){var l=[];YT.ready=function(f){if(YT.loaded)f();else l.push(f)};window.onYTReady=function(){YT.loaded=1;var i=0;for(;i]/g, '-'); + +// Fallback size formatter just in case window.formatFileSize isn't ready +const formatBytes = (bytes) => { +if (window.formatFileSize) return window.formatFileSize(bytes); +if (bytes === 0 || isNaN(bytes)) return "Unknown Size"; +const k = 1024; +const sizes = ['Bytes', 'KB', 'MB', 'GB']; +const i = Math.floor(Math.log(bytes) / Math.log(k)); +return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; + +// Helper to standardize format objects +const cleanFormat = (f) => { +const durationSec = (f.approxDurationMs || f.approx_duration_ms || info.basic_info.duration * 1000 || 0) / 1000; +const bytes = f.contentLength ? parseInt(f.contentLength) : (f.bitrate ? Math.floor((f.bitrate * durationSec) / 8) : 0); +const mime = f.mimeType || f.mime_type || ""; +const isWebm = mime.includes('webm'); +const isMp4 = mime.includes('mp4'); +const codec = mime.match(/codecs="(.*?)"/)?.[1] || ""; + +return { +itag: f.itag, +mimeType: mime, +container: isWebm ? 'webm' : (isMp4 ? 'mp4' : 'other'), +codec: codec, +qualityLabel: f.qualityLabel || f.quality_label || null, +bitrate: f.bitrate, +width: f.width, +hasVideo: !!f.width, +hasAudio: !!f.audioSampleRate || !!f.audio_sample_rate || mime.startsWith('audio/'), +languageId: f.language || f.audioTrack?.id || f.audio_track?.id || 'default', +languageName: f.audioTrack?.displayName || f.audio_track?.display_name || 'Default', +isDefaultAudio: f.audioTrack?.audioIsDefault || f.audio_track?.audio_is_default || (!f.audioTrack && !f.audio_track), +sizeBytes: bytes, +audioQuality:f.audio_quality || null, +audioTrackId:f.audio_track?.id, +sizeFormatted: formatBytes(bytes) +}; +}; + +// Extract raw lists +const rawFormats = streamingData.formats || []; +const rawAdaptive = streamingData.adaptive_formats || []; + +const preMuxed = rawFormats.map(cleanFormat); +const adaptive = rawAdaptive.map(cleanFormat); + +// Filter adaptive for matching +const videoOnly = adaptive.filter(f => f.hasVideo && !f.hasAudio); +const audioOnly = adaptive.filter(f => f.hasAudio && !f.hasVideo); + +// ── BUILD CATEGORY 2: MUXABLE COMBINATIONS ── +const muxableOptions = []; + +// Get all unique video qualities (e.g., "1080p60", "1080p", "720p") +const uniqueQualities = [...new Set(videoOnly.map(v => v.qualityLabel).filter(Boolean))] +.sort((a, b) => parseInt(b) - parseInt(a)); // Sort High to Low + +// Get all unique audio languages +const uniqueLanguages = []; +const langMap = new Map(); +audioOnly.forEach(a => { +if (!langMap.has(a.languageId)) { +langMap.set(a.languageId, { id: a.languageId, name: a.languageName, isDefault: a.isDefaultAudio }); +uniqueLanguages.push(langMap.get(a.languageId)); +} +}); + +// Create explicit safe pairs +uniqueQualities.forEach(quality => { +// Ban AV1 to protect Android MediaMuxer +const vForQuality = videoOnly.filter(v => v.qualityLabel === quality && !v.codec.includes('av01')); + +// Sort to get highest bitrate video for the container +const mp4Video = vForQuality.filter(v => v.container === 'mp4').sort((a,b) => b.bitrate - a.bitrate)[0]; +const webmVideo = vForQuality.filter(v => v.container === 'webm').sort((a,b) => b.bitrate - a.bitrate)[0]; + +uniqueLanguages.forEach(lang => { +const aForLang = audioOnly.filter(a => a.languageId === lang.id); + +// Sort to get highest bitrate audio for the container +const mp4Audio = aForLang.filter(a => a.container === 'mp4').sort((a,b) => b.bitrate - a.bitrate)[0]; +const webmAudio = aForLang.filter(a => a.container === 'webm').sort((a,b) => b.bitrate - a.bitrate)[0]; + +// Add matching MP4 pair +if (mp4Video && mp4Audio) { +muxableOptions.push({ +type: 'muxable', +qualityLabel: quality, +language: lang.name, +languageId: lang.id, +isDefaultLanguage: lang.isDefault, +container: 'mp4', +totalBytes: mp4Video.sizeBytes + mp4Audio.sizeBytes, +totalSizeFormatted: formatBytes(mp4Video.sizeBytes + mp4Audio.sizeBytes), +videoItag: mp4Video.itag, +audioItag: mp4Audio.itag, +videoDetails: mp4Video, +audioDetails: mp4Audio +}); +} + +// Add matching WebM pair +if (webmVideo && webmAudio) { +muxableOptions.push({ +type: 'muxable', +qualityLabel: quality, +language: lang.name, +languageId: lang.id, +isDefaultLanguage: lang.isDefault, +container: 'webm', +totalBytes: webmVideo.sizeBytes + webmAudio.sizeBytes, +totalSizeFormatted: formatBytes(webmVideo.sizeBytes + webmAudio.sizeBytes), +videoItag: webmVideo.itag, +audioItag: webmAudio.itag, +videoDetails: webmVideo, +audioDetails: webmAudio +}); +} +}); +}); + +// Final Master Object +const ytproMediaData = { +title: info.basic_info.title, +videoId: videoId, +durationSec: info.basic_info.duration || 0, +categories: { +"muxable": muxableOptions, +"audioOnly": audioOnly, +"videoOnly": videoOnly +} +}; + + + +ytproDownDiv.insertAdjacentHTML('beforeend',``); + + + + +ytproDownDiv.querySelector("#videoViewDiv").innerHTML=``; + + +var langList=document.createElement("select"); +langList.setAttribute("id","selectLang") + +uniqueLanguages.forEach(l=>{ +var sl=document.createElement("option"); +sl.textContent=l.name; +sl.value=l.id; +if (l.isDefault === true) { +sl.selected = true; +} +langList.appendChild(sl); +}); + + + +ytproDownDiv.querySelector("#videoViewDiv").appendChild(langList); + + +langList.addEventListener("change",(e)=>{ +updateMuxFormats(e.target.value); +updateAudioOnlyFormats(e.target.value); +}) + + + + + + + + + +//var defaultLangId=uniqueLanguages.filter( arr => { return arr.isDefault;})[0].id; + +var createAndAppend=()=>{ +var div=document.createElement("div"); +ytproDownDiv.querySelector("#videoViewDiv").appendChild(div); +return div; +} + + +var muxedDiv=createAndAppend(); +var audioOnlyDiv=createAndAppend(); +var videoOnlyDiv=createAndAppend(); + + + + +function updateMuxFormats(langId=uniqueLanguages.filter( arr => { return arr.isDefault;})[0].id){ + +muxedDiv.innerHTML=""; + +muxableOptions.forEach(mux =>{ +if(mux.languageId != langId) return; + +var formatLi=document.createElement("li"); +/*formatLi.dataset.audioItag=mux.audioItag; +formatLi.dataset.videoItag=mux.videoItag; +formatLi.dataset.langId=mux.languageId; +formatLi.dataset.isWebm=mux.container == "webm"; +*/ + + +Object.assign(formatLi.dataset,{ +langId:mux.audioDetails.audioTrackId, +isWebm:mux.container == "webm", +audioItag:mux.audioItag, +videoItag:mux.videoItag +}); + + +formatLi.innerHTML=`${downBtn}${mux.qualityLabel} | ${mux.container.toUpperCase()} | ${mux.totalSizeFormatted}`; +muxedDiv.appendChild(formatLi); +}); + + + + +} + + + +function updateAudioOnlyFormats(langId=uniqueLanguages.filter( arr => { return arr.isDefault;})[0].id){ + +audioOnlyDiv.innerHTML=""; + +var formatDivider=document.createElement("li"); + +formatDivider.innerHTML=` +Audio Only (${uniqueLanguages.filter( arr => { return arr.id==langId;})[0].name}) + + + + + +`; +Object.assign(formatDivider.style,{ +minHeight:"20px", +borderRadius:"5px", +background:"#0000" +}) + +audioOnlyDiv.appendChild(formatDivider); + +formatDivider.addEventListener("click",()=>{ +Array.from(formatDivider.parentElement.children).forEach((c,i)=>{ +if(i == 0) { +c.children[1].style.transform = c.children[1].style.transform === "rotate(180deg)" ? "rotate(0deg)" : "rotate(180deg)"; +return; +} +c.style.display = c.style.display === "none" ? "flex" : "none"; +}) +}); + + +audioOnly.forEach(aud =>{ +if(aud.languageId != langId) return; + +var formatLi=document.createElement("li"); +/*formatLi.dataset.audioItag=aud.itag; +formatLi.dataset.isWebm=aud.container == "webm"; +formatLi.dataset.langId=mux.languageId;*/ + +Object.assign(formatLi.dataset,{ +langId:aud.audioTrackId, +isWebm:aud.container == "webm", +audioItag:aud.itag +}); + +formatLi.innerHTML=`${downBtn}${aud.audioQuality.replaceAll("AUDIO_QUALITY_"," ")} | ${aud.sizeFormatted}`; +audioOnlyDiv.appendChild(formatLi); +}); + + + + +} + + + +function updateVideoOnlyFormats(){ + +videoOnlyDiv.innerHTML=""; + +var formatDivider=document.createElement("li"); + +formatDivider.innerHTML=` +Video Only + + + + + +`; +Object.assign(formatDivider.style,{ +minHeight:"20px", +borderRadius:"5px", +background:"#0000" +}) + +videoOnlyDiv.appendChild(formatDivider); + +formatDivider.addEventListener("click",()=>{ +Array.from(formatDivider.parentElement.children).forEach((c,i)=>{ +if(i == 0) { +c.children[1].style.transform = c.children[1].style.transform === "rotate(180deg)" ? "rotate(0deg)" : "rotate(180deg)"; +return; +} +c.style.display = c.style.display === "none" ? "flex" : "none"; +}) +}); + + +videoOnly.forEach(vid =>{ + +var formatLi=document.createElement("li"); +formatLi.dataset.videoItag=vid.itag; +formatLi.dataset.isWebm=vid.container == "webm"; +formatLi.innerHTML=`${downBtn}${vid.qualityLabel} | ${vid.container.toUpperCase()} | ${vid.sizeFormatted}`; +videoOnlyDiv.appendChild(formatLi); +}); + + + + +} + + + + + +function updateThumbnails(){ +var div=ytproDownDiv.querySelector("#thumbViewDiv"); +div.innerHTML=""; + +var thumbs=info.basic_info.thumbnail; + +thumbs.forEach(thumb=>{ +div.innerHTML+=`
  • +
    +${downBtn}${thumb.height} ✕ ${thumb.width} +
  • ` +}) + + +div.addEventListener("click",(e)=>{ +var el=e.target.closest("[data-url]"); +if(!el) return; + +Android.downvid(el.dataset.title,el.dataset.url,"image/jpg"); + +}); +} + + +function updateCaptions(){ +var div=ytproDownDiv.querySelector("#captionsViewDiv"); +div.innerHTML=``; + +var captions=info?.captions?.caption_tracks; + +if(!captions) return div.innerHTML=`No Captions Found`; + +var t=`Captions ${safeTitle} YTPRO`; + +captions.forEach(cap=>{ + +cap.baseUrl = cap.base_url.replace("&fmt=srv3",""); + +div.innerHTML+=` +${cap?.name?.text} +

    +
    +${downBtn}
    .txt
    +${downBtn}
    .srt
    +${downBtn}
    .xml
    +${downBtn}
    .vtt
    +${downBtn}
    .srv1
    ${downBtn}
    .ttml
    +
    +

    +

    `; +}); + + + +div.addEventListener("click",(e)=>{ +var el=e.target.closest("[data-url]"); +if(!el) return; + +Android.downvid(el.dataset.title+el.dataset.ext,el.dataset.url,"plain/text"); + +}); + + +} + + + +/*EVENT LISTENERS**/ +muxedDiv.addEventListener("click",(e)=>{ +var el=e.target.closest("[data-audio-itag]"); +if(!el) return; +downloadSABRStream(el.dataset.videoItag,el.dataset.audioItag,el.dataset.isWebm,el.dataset.langId,EnabledTrackTypes.VIDEO_AND_AUDIO); + + +}); + + + +audioOnlyDiv.addEventListener("click",(e)=>{ +var el=e.target.closest("[data-audio-itag]"); +if(!el) return; +downloadSABRStream(null,el.dataset.audioItag,el.dataset.isWebm,el.dataset.langId,EnabledTrackTypes.AUDIO_ONLY); +}); + + + +videoOnlyDiv.addEventListener("click",(e)=>{ +var el=e.target.closest("[data-video-itag]"); +if(!el) return; +downloadSABRStream(el.dataset.videoItag,null,el.dataset.isWebm,null,EnabledTrackTypes.VIDEO_ONLY); +}); + + + + + + +if(info?.basic_info?.is_live || info?.basic_info?.is_live_content){ + +ytproDownDiv.querySelector("#videoViewDiv").innerHTML="Downloading live streams
    aren't supported at the moment"; +}else{ +updateMuxFormats(); +updateAudioOnlyFormats(); +updateVideoOnlyFormats(); +} +updateThumbnails(); +updateCaptions(); + + + + +// ── 7. Extract SABR URL & Config ────────────────────────────────────────── +async function extractSabrConfig(playerInfo) { +const url = await player.decipher(playerInfo.streaming_data?.server_abr_streaming_url); +const cfg = playerInfo.player_config +?.media_common_config +?.media_ustreamer_request_config +?.video_playback_ustreamer_config; +return { url, cfg }; +} + +const { url: serverAbrUrl, cfg: ustreamerConfig } = await extractSabrConfig(info); +if (!serverAbrUrl || !ustreamerConfig) { +window.Android?.showToast?.('Missing SABR config.'); +return; +} + +const rawUstreamerConfig = typeof ustreamerConfig === 'string' ? ustreamerConfig : JSON.stringify(ustreamerConfig); +const adaptiveFormats = streamingData.adaptive_formats ?? []; +const sabrFormats = adaptiveFormats.map(f => buildSabrFormat(f)); + + + + + + +async function downloadSABRStream(videoItag,audioItag,isWebm,langId,enabledTrack){ + + +if(!Android.isWebViewSupported()){ + Android.showToast("Please Update your WebView."); + return; +} +if(!Android.hasStoragePermission()){ + return; +} + +Android.showToast("Download Started"); + + +const containerExt = isWebm == "true" ? 'webm' : 'mp4'; + +// ── Grab the absolute lowest qualities to feed to the Black Hole + +const lowestAudio = audioOnly.sort((a, b) => (a.bitrate || 0) - (b.bitrate || 0))[0].itag; + +const lowestVideo = adaptiveFormats +.filter(f => f.width) +.sort((a, b) => (a.bitrate || 0) - (b.bitrate || 0))[0].itag; + +const trashSabrAudio = sabrFormats.filter(s=> s.itag==lowestAudio)[0]; +const trashSabrVideo =sabrFormats.filter(s=> s.itag==lowestVideo)[0]; + + +const targetSabrVideo=sabrFormats.filter(s=> s.itag==videoItag)[0] || trashSabrVideo; + +var targetSabrAudio; + +if(langId != "undefined"){ +targetSabrAudio = sabrFormats.filter(s=> s.itag==audioItag && s.audioTrackId == langId)[0] || trashSabrAudio; +}else{ +targetSabrAudio = sabrFormats.filter(s=> s.itag==audioItag)[0] || trashSabrAudio; +} + + +const sabrStream = new SabrStream({ +videoId: videoId, +cpn: info.cpn, +serverAbrStreamingUrl: serverAbrUrl, +videoPlaybackUstreamerConfig: rawUstreamerConfig, +formats: sabrFormats, +poToken: placeholderPoToken ?? undefined, +clientInfo: { +clientName: 1, // WEB +clientVersion: yt.session.context.client.clientVersion, +osName: 'Windows', +osVersion: '10.0', +}, +durationMs: (info.basic_info.duration ?? 0) * 1000, +fetch: async (input, init = {}) => { +const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url; +return fetch(url, { ...init, mode: 'cors', credentials: 'include' }); +}, +}); + +sabrStream.on('reloadPlayerResponse', async () => { +try { +const freshInfo = await yt.getBasicInfo(videoId, { client: 'WEB' }); +const { url: newUrl, cfg: newCfg } = await extractSabrConfig(freshInfo); +if (newUrl) sabrStream.setStreamingURL(newUrl); +if (newCfg) sabrStream.setUstreamerConfig(typeof newCfg === 'string' ? newCfg : JSON.stringify(newCfg)); +} catch (e) {} +}); + +let isTokenApplied = false; +sabrStream.on('streamProtectionStatusUpdate', async (data) => { +if ((data.status === 2 || data.status === 3) && !isTokenApplied) { +isTokenApplied = true; +try { +const fullToken = await fullTokenPromise; +if (fullToken) sabrStream.poToken = fullToken; +} catch (err) {} +} +}); + + +const { videoStream ,audioStream} = await sabrStream.start({ +preferMp4: !isWebm, +preferH264: !isWebm, +videoFormat: () => targetSabrVideo, +audioFormat: () => targetSabrAudio, +enabledTrackTypes:enabledTrack, +}); + +const durationSec = info.basic_info.duration || 0; + + +createDownloaderStatus(); +createDownloaderIndicator(); + +var downloaderDiv=document.querySelector("#ytProDownloaderDiv"); + + + +function createProgreses(streamName){ + +var elProgressBar=document.createElement("div"); +var elProgress=document.createElement("div"); +var elDetails=document.createElement("span"); + +elDetails.className="ytproDetails"; +elProgressBar.className="ytproProgressBar"; +elProgress.className="ytproProgress"; +elProgressBar.appendChild(elProgress); + +elDetails.innerHTML=`${streamName}: ` + +downloaderDiv.appendChild(elDetails) +downloaderDiv.appendChild(elProgressBar); + +return {elDetails,elProgress}; +} + + + +//video only +if(enabledTrack==EnabledTrackTypes.VIDEO_ONLY){ + +const estVideoBytes = targetSabrVideo.contentLength || (targetSabrVideo.bitrate ? Math.floor((targetSabrVideo.bitrate * durationSec) / 8) : 0); + + +downloaderDiv.insertAdjacentHTML("beforeend",` +

    Title: ${safeTitle}
    `) + +var fileName=`${safeTitle}_video${new Date().getTime()}.${containerExt}`; + +var {elDetails,elProgress} = createProgreses("Video Stream"); + +await pipeToDisk(videoStream,fileName, estVideoBytes,elDetails,elProgress); + + + +}else if(enabledTrack==EnabledTrackTypes.AUDIO_ONLY){ +//audio only + + +const estAudioBytes = targetSabrAudio.contentLength || +(targetSabrAudio.bitrate ? Math.floor((targetSabrAudio.bitrate * durationSec) / 8) : 0); + +downloaderDiv.insertAdjacentHTML("beforeend",` +

    Title: ${safeTitle}
    `) + + +var {elDetails,elProgress} = createProgreses("Audio Stream"); + +var fileName=`${safeTitle}_audio${new Date().getTime()}.${containerExt}`; + +await pipeToDisk(audioStream,fileName, estAudioBytes,elDetails,elProgress); + + + +}else if(enabledTrack==EnabledTrackTypes.VIDEO_AND_AUDIO){ +//both + + + +const estVideoBytes = targetSabrVideo.contentLength || (targetSabrVideo.bitrate ? Math.floor((targetSabrVideo.bitrate * durationSec) / 8) : 0); + + +const estAudioBytes = targetSabrAudio.contentLength || +(targetSabrAudio.bitrate ? Math.floor((targetSabrAudio.bitrate * durationSec) / 8) : 0); + + +downloaderDiv.insertAdjacentHTML("beforeend",` +

    Title: ${safeTitle}
    `) + + +var videoEl= createProgreses("Video Stream"); +var audioEl= createProgreses("Audio Stream"); + + +var videoFileName=`${safeTitle}_video${new Date().getTime()}.${containerExt}`; + +var audioFileName=`${safeTitle}_audio${new Date().getTime()}.${containerExt}`; + + +const downloadTasks = []; + +if (videoStream) { +downloadTasks.push(pipeToDisk(videoStream, videoFileName, estVideoBytes,videoEl.elDetails,videoEl.elProgress)); +} + +if (audioStream) { +downloadTasks.push(pipeToDisk(audioStream, audioFileName, estAudioBytes,audioEl.elDetails,audioEl.elProgress)); +} + +await Promise.all(downloadTasks); + + +window.Android.showToast('Muxing formats...'); + +window.Android?.muxVideoAudio?.(videoFileName,audioFileName,`${safeTitle}_${new Date().getTime()}.${containerExt +}`); + +} + + + + + + + + + +} + + +} + + + + +function getDownloadElement() { +const isExisting = (id) => document.getElementById(id); + +// Reuse or create outer + inner divs +const ytproDown = isExisting("outerdownytprodiv") || document.createElement("div"); +const ytproDownDiv = isExisting("downytprodiv") || document.createElement("div"); + +ytproDown.id = "outerdownytprodiv"; +ytproDownDiv.id = "downytprodiv"; + +Object.assign(ytproDown.style, { +height: "100%", width: "100%", position: "fixed", +top: "0", left: "0", display: "flex", +justifyContent: "center", background: "rgba(0,0,0,0.4)", zIndex: "9" +}); + +Object.assign(ytproDownDiv.style, { +height: "65%", width: "85%", overflow: "auto", +background: isD ? "#212121" : "#f1f1f1", +position: "absolute", bottom: "20px", zIndex: "99", +padding: "20px", borderRadius: "25px", textAlign: "center" +}); + +ytproDown.addEventListener("click", (ev) => { +if (!ytproDownDiv.contains(ev.target)) history.back(); +}); + +// Build tabs declaratively +const TABS = [ +{ label: "Formats", viewId: "videoViewDiv" }, +{ label: "Thumbnails", viewId: "thumbViewDiv" }, +{ label: "Captions", viewId: "captionsViewDiv" }, +]; + +const tabStyle = { +height: "100%", +width: "calc((100% - 10px) / 3)", +borderRadius: "25px", +lineHeight: "30px" +}; + +const tabs = document.createElement("div"); +Object.assign(tabs.style, { +height: "30px", width: "95%", display: "flex", +gap: "5px", position: "absolute", top: "10px", left: "2.5%" +}); + +const views = []; + +TABS.forEach(({ label, viewId }) => { +const tab = document.createElement("div"); +Object.assign(tab.style, tabStyle); +tab.textContent = label; +tab.dataset.view = `#${viewId}`; +tabs.appendChild(tab); + +const view = document.createElement("div"); +view.id = viewId; +view.style.paddingTop="40px"; +view.style.display = "none"; +ytproDownDiv.appendChild(view); +views.push(view); +}); + +tabs.addEventListener("click", (e) => { +const el = e.target.closest("[data-view]"); +if (!el) return; + +[...tabs.children].forEach(child => child.style.background = "transparent"); +views.forEach(v => v.style.display = "none"); + +document.querySelector(el.dataset.view).style.display = "block"; +el.style.background = d; +}); + +document.body.appendChild(ytproDown); +ytproDown.appendChild(ytproDownDiv); +ytproDownDiv.prepend(tabs); // tabs sit above views + +tabs.children[0].style.background=d; +document.querySelector("#videoViewDiv").style.display = "block" + +return ytproDownDiv; +} + + + + +// 1. Global registry to catch ports when Android sends them back +const pendingStreams = {}; + +window.addEventListener("message", (event) => { +if (typeof event.data === "string" && event.data.startsWith("PORT_FOR:") && event.ports.length > 0) { +const fileName = event.data.substring(9); +if (pendingStreams[fileName]) { +pendingStreams[fileName](event.ports[0]); // Hand the port back to pipeToDisk +delete pendingStreams[fileName]; +} +} +}); + +// 2. Helper function to request a dedicated pipe +function createDedicatedPipe(fileName) { +return new Promise((resolve) => { +pendingStreams[fileName] = resolve; +window.Android?.requestBinaryPort?.(fileName); +}); +} + +// 3. pipeToDisk +async function pipeToDisk(stream, fileName, expectedTotalBytesStr, elDetails, elProgress) { +const expectedBytes = parseInt(expectedTotalBytesStr || "0", 10); +const totalMB = expectedBytes > 0 ? (expectedBytes / (1024 * 1024)).toFixed(2) : '?'; + +const filePort = await createDedicatedPipe(fileName); +if (!filePort) { +console.error(`[YTPRO] Failed to get port for ${fileName}`); +return 0; +} + +const reader = stream.getReader(); +let total = 0; +let lastLogMB = -1; + +try { +const CHUNK_SIZE = 1024 * 512; + +while (true) { +const { done, value } = await reader.read(); +if (done) break; + +if (value?.length > 0) { +let offset = 0; +while (offset < value.length) { + const chunkBuffer = value.slice(offset, offset + CHUNK_SIZE).buffer; + + // Send the binary chunk down this file's specific port + filePort.postMessage(chunkBuffer); + + const bytesWritten = chunkBuffer.byteLength; + offset += bytesWritten; + total += bytesWritten; + + const currentMBFloor = Math.floor(total / (1024 * 1024)); + if (currentMBFloor > lastLogMB) { + const downloadedMB = (total / (1024 * 1024)).toFixed(2); + const percent = expectedBytes > 0 ? Math.round((total / expectedBytes) * 100) : -1; + + elDetails.children[0].innerHTML = ` ${downloadedMB} MB / ${totalMB} MB`; + elProgress.style.width = percent + "%"; + elProgress.innerHTML = percent + "%"; + + window.Android?.onDownloadProgress?.(percent, total); + lastLogMB = currentMBFloor; + } + + await new Promise(r => setTimeout(r, 5)); +} +} +} +} finally { +// Tell Android THIS specific port is finished, so Java can close the file and kill the port +filePort.postMessage("END"); +} + +const finalMB = (total / (1024 * 1024)).toFixed(2); +elDetails.children[0].innerHTML = ` ${finalMB} MB / ${totalMB} MB`; +elProgress.style.width = "100%"; +elProgress.innerHTML = "100%"; + +return total; +} + + + + +function createDownloaderStatus(){ + +if(document.querySelector("#ytProDownloaderDiv")) return; + +var div=document.createElement("div"); + +div.id="ytProDownloaderDiv"; + + + +Object.assign(div.style,{ +height:"50%", +overflow:"auto", +width:"calc(95% - 20px)", +zIndex:999999, +position:"fixed", +padding:"10px", +bottom:"10px", +display:"none", +left:"2.5%", +background:isD ? "#212121" : "#f1f1f1", +borderRadius:"25px", +textAlign:"center", +boxShadow:"1px 1px 2px black" +}); + +div.innerHTML=` + +
    + INFO: Do NOT close YTPRO while we are downloading the files
    +(SABR streams are limited with 1-2 MBps speed by youtube servers)
    +
    +`; + + +document.body.appendChild(div); + +} + + + + + + +function createDownloaderIndicator(){ +if(document.querySelector("#ytproDownloadIndicator") ) return; +var div=document.createElement("div"); +div.id="ytproDownloadIndicator"; + +Object.assign(div.style,{ +height:"50px", +width:"50px", +zIndex:999999, +position:"fixed", +bottom:"calc(40px)", +right:"20px", +background:isD ? "#212121" : "#f1f1f1", +borderRadius:"50%", +border:`1px solid ${c}`, +display:"grid", +placeItems:"center" +}); + +div.innerHTML=``; + + +document.body.appendChild(div) + +div.addEventListener("click",()=>{ +var el=document.querySelector("#ytProDownloaderDiv"); + +if(el.style.display=="block"){ +el.style.display="none"; +div.style.bottom="70px"; +}else{ +el.style.display="block"; +div.style.bottom="calc(50% + 40px)"; +} +}) + +} + + + diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js new file mode 100644 index 00000000..c8c46a58 --- /dev/null +++ b/app/src/main/assets/ytpro/script.js @@ -0,0 +1,2926 @@ +/*****YTPRO******* +Author: Prateek Chaubey +Version: 3.9.8 +URI: https://github.com/prateek-chaubey/YTPRO +Last Updated On: 1 May , 2026 , 19:25 IST +*/ + + + + +if(window.eruda == null && localStorage.getItem("devMode") == "true"){ +//ERUDA +var script = document.createElement('script'); script.src="//youtube.com/ytpro_cdn/npm/eruda"; document.body.appendChild(script); script.onload=()=>{eruda.init();} +} +/**/ + +if(!YTProVer){ + +/*Few Stupid Inits*/ +var YTProVer="3.98"; +var ytoldV=""; +var isF=false; //what is this for? +var isAp=false; // oh it's for bg play +const originalPause = HTMLMediaElement.prototype.pause; // well long story short , save the original pause function +window.PIPause = false; // for pausing video when in PIP +window.isPIP=false; +window.pauseAllowed = true; // allow pause by default +var sTime=[]; +var webUrls=["m.youtube.com","youtube.com","yout.be","accounts.google.com"]; +var GeminiAT=""; +var YTProLocales = { + en: { + settings: "YT PRO Settings", + enterUrl: "Enter YouTube URL", + likedVideos: "Liked Videos", + checkUpdates: "Check for Updates", + autoskipSponsors: "Autoskip Sponsors", + gestureControls: "Gesture Controls", + miniplayerGesture: "Miniplayer Gesture", + forceZoom: "Force Zoom", + backgroundPlay: "Background Play", + hideShorts: "Hide Shorts", + singleGeminiChat: "Use single Gemini chat", + selectGeminiModel: "Select Gemini Model", + editGeminiPrompt: "Edit Gemini Prompt", + disableCodecs: "Disable Codecs", + reportBugs: "Report Bugs", + sponsor: "Become a Sponsor", + developerMode: "Developer Mode", + disclaimer: "Disclaimer", + disclaimerText: "This is an educational project aimed at showcasing javascript injection into a webview to enhance productivity.", + sourceCode: "You can find the source code at", + madeWith: "Made with", + by: "by Prateek Chaubey", + language: "Language", + english: "English", + chinese: "Simplified Chinese", + languageChanged: "Language changed. Reloading...", + upToDate: "Your app is up to date", + comments: "Comments", + commentsUnavailable: "Could not find the original YouTube comments on this page", + commentsOnlyWatch: "Comments are available on video pages", + openYouTubeComments: "Open YouTube comments", + originalCommentsOpened: "Opened original YouTube comments.", + download: "Download", + heart: "Heart", + pipMode: "PIP Mode", + noVideosFound: "No Videos Found", + likedVideosTitle: "Liked Videos" + }, + zh: { + settings: "YT PRO 设置", + enterUrl: "输入 YouTube 链接", + likedVideos: "收藏的视频", + checkUpdates: "检查更新", + autoskipSponsors: "自动跳过赞助片段", + gestureControls: "手势控制", + miniplayerGesture: "小窗手势", + forceZoom: "强制缩放", + backgroundPlay: "后台播放", + hideShorts: "隐藏 Shorts", + singleGeminiChat: "使用单个 Gemini 对话", + selectGeminiModel: "选择 Gemini 模型", + editGeminiPrompt: "编辑 Gemini 提示词", + disableCodecs: "禁用编解码器", + reportBugs: "反馈问题", + sponsor: "赞助作者", + developerMode: "开发者模式", + disclaimer: "免责声明", + disclaimerText: "本项目用于展示如何通过 WebView 注入 JavaScript 来增强使用体验。", + sourceCode: "你可以在这里查看源代码:", + madeWith: "Made with", + by: "by Prateek Chaubey", + language: "语言", + english: "English", + chinese: "简体中文", + languageChanged: "语言已切换,正在重新加载...", + upToDate: "当前已是最新版本", + comments: "评论", + commentsUnavailable: "没有在当前页面找到 YouTube 原生评论区", + commentsOnlyWatch: "评论区仅在视频页面可用", + openYouTubeComments: "打开 YouTube 评论", + originalCommentsOpened: "已打开 YouTube 原生评论区。", + download: "下载", + heart: "收藏", + pipMode: "画中画", + noVideosFound: "暂无视频", + likedVideosTitle: "收藏的视频" + } +}; + +function ytproLang(){ + var saved = localStorage.getItem("ytproLang"); + if(saved == "zh" || saved == "en") return saved; + return ((navigator.language || "").toLowerCase().indexOf("zh") == 0) ? "zh" : "en"; +} + +function ytproT(key){ + var lang = ytproLang(); + return (YTProLocales[lang] && YTProLocales[lang][key]) || YTProLocales.en[key] || key; +} + +var GeminiModels = { + "3.0 Pro": '[1,null,null,null,"9d8ca3786ebdfbea",null,null,0,[4],null,null,1]', + "3.0 Flash": '[1,null,null,null,"fbb127bbb056c959",null,null,0,[4],null,null,1]', + "3.0 Flash Thinking": '[1,null,null,null,"5bf011840784117a",null,null,0,[4],null,null,1]', + "3.0 Pro Plus": '[1,null,null,null,"e6fa609c3fa255c0",null,null,0,[4],null,null,4]', + "3.0 Flash Plus": '[1,null,null,null,"56fdd199312815e2",null,null,0,[4],null,null,4]', + "3.0 Flash Thinking Plus": '[1,null,null,null,"e051ce1aa80aa576",null,null,0,[4],null,null,4]', + "3.0 Pro Advanced": '[1,null,null,null,"e6fa609c3fa255c0",null,null,0,[4],null,null,2]', + "3.0 Flash Advanced": '[1,null,null,null,"56fdd199312815e2",null,null,0,[4],null,null,2]', + "3.0 Flash Thinking Advanced": '[1,null,null,null,"e051ce1aa80aa576",null,null,0,[4],null,null,2]' +}; + +var YTPROCodecs={ +video:["AV1","VP8","VP9","H264"], +audio:["Opus","Mp4a"] +} + +let touchstartY = 0; +let touchendY = 0; +let initialDistance=null; + +//swipe controls +var sens=0.005; +var vol=Android.getVolume(); +var brt = Android.getBrightness()/100; + +if(localStorage.getItem("saveCInfo") == null || localStorage.getItem("gesC") == null || localStorage.getItem("gesM") == null || localStorage.getItem("bgplay") == null){ +localStorage.setItem("autoSpn","true"); +localStorage.setItem("bgplay","true"); +localStorage.setItem("gesC","true"); +localStorage.setItem("gesM","false"); +localStorage.setItem("fzoom","false"); +localStorage.setItem("saveCInfo","true"); +localStorage.setItem("geminiModel","3.0 Flash"); +localStorage.setItem("prompt","Give me details about this YouTube video Id: {videoId} , a detailed summary of timestamps with facts , resources and reviews of the main content"); +localStorage.setItem("devMode","false"); + +localStorage.setItem("block_60fps","false"); + +YTPROCodecs.video.forEach((x)=>{ +localStorage.setItem(x,"true"); +}); + +YTPROCodecs.audio.forEach((x)=>{ +localStorage.setItem(x,"true"); +}); + +} +if(localStorage.getItem("fzoom") == "true"){ +document.getElementsByName("viewport")[0].setAttribute("content",""); +} + +if (["2.0 Flash", "2.0 Flash Thinking", "2.5 Flash", "2.5 Pro"].includes(localStorage.getItem('geminiModel'))) { +localStorage.setItem('geminiModel', "3.0 Flash"); +} + + +if(window.location.pathname.indexOf("shorts") > -1){ +ytoldV=window.location.pathname; +} +else{ +ytoldV=(new URLSearchParams(window.location.search)).get('v') ; +} + + +/*Dark and Light Mode*/ +var c="#000"; +var d="#f2f2f2"; +var dc="#fff"; +var isD=false; +var dislikes="..."; + + +if(document.cookie.indexOf("f6=40000") > -1){ +dc ="#000";c ="#fff";d="rgba(255,255,255,0.1)"; +isD=true; +}else{ +dc ="#fff";c="#000";d="rgba(0,0,0,0.05)"; +isD=false; +} + +var downBtn=` + + + +`; + + + + + + + + + + +function override() { + +var videoElem = document.createElement('video'); +var origCanPlayType = videoElem.canPlayType.bind(videoElem); +videoElem.__proto__.canPlayType = makeModifiedTypeChecker(origCanPlayType); + +var mse = window.MediaSource; + +if (mse === undefined) return; +var origIsTypeSupported = mse.isTypeSupported.bind(mse); +mse.isTypeSupported = makeModifiedTypeChecker(origIsTypeSupported); +} + + +function makeModifiedTypeChecker(origChecker) { + + +return function (type) { +if (type === undefined) return ''; +var disallowed_types = []; +if (localStorage['H264'] === 'false') { +disallowed_types.push('avc'); +} +if (localStorage['VP8'] === 'false') { +disallowed_types.push('vp8'); +} +if (localStorage['VP9'] === 'false') { +disallowed_types.push('vp9', 'vp09'); +} +if (localStorage['AV1'] === 'true') { +disallowed_types.push('av01', 'av99'); +} +if (localStorage['Opus'] === 'false') { +disallowed_types.push('opus'); +} +if (localStorage['Mp4a'] === 'false') { +disallowed_types.push('mp4a'); +} + +// If video type is in disallowed_types, say we don't support them +for (var i = 0; i < disallowed_types.length; i++) { +if (type.indexOf(disallowed_types[i]) !== -1) return ''; +} + +if (localStorage['block_60fps'] === 'true') { +var match = /framerate=(\d+)/.exec(type); +if (match && match[1] > 30) return ''; +} + +return origChecker(type); +}; +} + +override(); + + + + + +function insertAfter(referenceNode, newNode) { +try{ +referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); +}catch{} +} + + +/*wait for the element , using observer*/ +async function waitForElement(selector,vid) { +return new Promise((resolve) => { +const element = document.querySelector(selector); +if(element){ +if(vid && element.src != "") return resolve(element); +if(!vid) return resolve(element); +} +const observer = new MutationObserver(() => { +const el = document.querySelector(selector); +if (el){ + +if(vid && el.src) resolve(el),observer.disconnect();; +if(!vid) resolve(el),observer.disconnect();; +} +}); +observer.observe(document.body, { +childList: true, +subtree: true +}); +}); +} + + +/*Add Settings Tab*/ +var addSettingsTab=()=>{ +if(document.getElementById("setDiv") == null){ +var setDiv=document.createElement("div"); +setDiv.setAttribute("style",` +z-index:9999999999; +font-size:22px; +text-align:center; +line-height:35px; +pointer-events:auto; +`); +setDiv.setAttribute("id","setDiv"); +var svg=document.createElement("ytm-pivot-bar-item-renderer"); +svg.innerHTML=` +`; +setDiv.appendChild(svg); +insertAfter(document.getElementsByTagName("ytm-home-logo")[0],setDiv) +if(document.getElementById("hSett") != null){ +document.getElementById("hSett").addEventListener("click", +function(ev){ +window.location.hash="settings"; +}); +} +} + + +}; + + + + +/*Dislikes To Locale, Credits: Return YT Dislikes*/ +function getDislikesInLocale(num){ +var nn=num; +if (num < 1000){ +nn = num; +} +else{ +const int = Math.floor(Math.log10(num) - 2); +const decimal = int + (int % 3 ? 1 : 0); +const value = Math.floor(num / 10 ** decimal); +nn= value * 10 ** decimal; +} +let userLocales; +if (document.documentElement.lang) { +userLocales = document.documentElement.lang; +} else if (navigator.language) { +userLocales = navigator.language; +} else { +try { +userLocales = new URL( +Array.from(document.querySelectorAll("head > link[rel='search']")) +?.find((n) => n?.getAttribute("href")?.includes("?locale=")) +?.getAttribute("href") +)?.searchParams?.get("locale"); +} catch { +userLocales = "en"; +} +} +return Intl.NumberFormat(userLocales, { +notation: "compact", +compactDisplay: "short", +}).format(nn); +} + + + +/*Skips the bad part :)*/ +async function skipSponsor(){ +var sDiv=document.createElement("div"); +sDiv.setAttribute("style",`height:3px;pointer-events:none;width:100%;position:absolute;z-index:99;`) +sDiv.setAttribute("id","sDiv"); +var player = document.getElementsByClassName("video-stream")[0]; +var dur=player.duration; + +if(isNaN(dur)) return; + +for(var x in sTime){ +var s1=document.createElement("div"); +var s2=sTime[x]; +s1.setAttribute("style",`height:3px;width:${(100/dur) * (s2[1]-s2[0])}%;background:#0f8;position:absolute;z-index:9;left:${(100/dur) * s2[0]}%;`) +sDiv.appendChild(s1); +} + + + + +var e=await waitForElement("yt-progress-bar",false); + + +if(document.getElementById("sDiv") == null){ +if(document.getElementsByClassName('ytPlayerProgressBarHost')[0] != null){ +document.getElementsByClassName('ytPlayerProgressBarHost')[0].appendChild(sDiv); +}else{ +try{document.getElementsByClassName('ytProgressBarLineProgressBarLine')[0].appendChild(sDiv);}catch{} +} +} + + + + +} + + + + + +/*Fetch The Dislikes*/ +async function fDislikes(url){ +var Url=new URL(url); +var vID=""; +if(Url.pathname.indexOf("shorts") > -1){ +vID=Url.pathname.substr(8,Url.pathname.length); +} +else if(Url.pathname.indexOf("watch") > -1){ +vID=Url.searchParams.get("v"); +} + + +fetch("https://returnyoutubedislikeapi.com/votes?videoId="+vID) +.then(response => { +return response.json(); +}).then(jsonObject => { +if('dislikes' in jsonObject){ +dislikes=getDislikesInLocale(parseInt(jsonObject.dislikes)); +} +}).catch(error => {}); + +} + + + +/*Check For Sponsorships*/ +async function checkSponsors(Url){ + + +if(Url.indexOf("watch") > -1){ + +sTime=[]; + +await fetch("https://sponsor.ajay.app/api/skipSegments?videoID="+new URL(Url).searchParams.get("v")) +.then(response => { +return response.json(); +}).then(jsonObject => { +for(var x in jsonObject){ +var time=jsonObject[x].segment; +sTime.push(time); +} +}).catch(error => {}); + + + +/*Skip the Sponsor*/ +var player = await waitForElement(".video-stream",true); + + +player.ontimeupdate=()=>{ +skipSponsor(); +var cur=player.currentTime; +for(var x in sTime){ +var s2=sTime[x]; +if(Math.floor(cur) == Math.floor(s2[0])){ +if(localStorage.getItem("autoSpn") == "true"){ +player.currentTime=s2[1]; +addSkipper(s2[0]); +} +} +} +}; + + + + + +} + +} + + +//DEBUG +/* +s1: FoQR9rLpRy8 +s2: PN51tJhZscE +*/ +/*Add Skip Sponsor Element*/ +function addSkipper(sT){ +var sSDiv=document.createElement("div"); +sSDiv.setAttribute("style",` +height:50px;${(screen.width > screen.height) ? "width:50%;" : "width:80%;"}overflow:auto;background:rgba(130,130,130,.3); +backdrop-filter:blur(6px); +position:absolute;bottom:40px; +line-height:50px; +left:calc(15% / 2 );padding-left:10px;padding-right:10px; +z-index:99999999999999;text-align:center;border-radius:25px; +color:white;text-align:center; +`); +sSDiv.innerHTML=`Skipped Sponsor + + + + + + + + +`; +document.getElementById("player-control-container").appendChild(sSDiv); + + +sSDiv.addEventListener("click",(e)=>{ + var el=e.target.closest("[data-action]"); + + if(!el) return; + var action=el.dataset.action; + + if(action == "close"){ +el.parentElement.parentElement.remove(); + }else if(action == "rewind"){ + el.parentElement.parentElement.remove(); + document.getElementsByClassName('video-stream')[0].currentTime=sT+1; + } + +}); + + +setTimeout(()=>{sSDiv.remove();},5000); +} + + +fDislikes(window.location.href); +checkSponsors(window.location.href); + + +if((window.location.pathname.indexOf("watch") > -1) || (window.location.pathname.indexOf("shorts") > -1)){ +var unV=setInterval(() => { + + +/*Unmute The Video*/ + +document.getElementsByClassName('video-stream')[0].muted=false; + +if(!document.getElementsByClassName('video-stream')[0].muted){ +clearInterval(unV); + +} + +}, 5); + +} + +/*Funtion to set Element Styles*/ +function sty(e,v){ +var s={ +display:"flex", +alignItems:"center", +justifyContent:"center", +fontWeight:"550", +height:"65%", +minWidth:"80px", +width:"auto", +borderRadius:"20px", +background:d, +fontSize:"12px", +marginRight:"5px", +textAlign:"center", +}; +for(x in s){ +e.style[x]=s[x]; +} +} + + +function getGeminiModels(){ +var t=""; + +for(var x in GeminiModels){ + + +t+=`
    +`; +} + +return t; + +} + + +/*Get Codecs*/ +function getYTPROCodecs(){ +var t=`

    This feature is experimental , this may break YTPro if not configured correctly. By default all the codecs are enabled , tap on the buttons below to switch them.


    Video Codecs
    `; + +for(var y in YTPROCodecs.video){ + +var x=YTPROCodecs.video[y]; + +t+=``; +} + +t+=`

    Audio Codecs
    ` +for(var y in YTPROCodecs.audio){ + +var x=YTPROCodecs.audio[y]; + +t+=``; +} + +t+=`

    +
    Block 60FPS
    `; + +t+=`

    `; + + +return t; + +} + + +function setRemoveCodec(x,y){ + + +if(localStorage[x] == "true"){ +localStorage.setItem(x,"false"); +y.style.background=isD ? "rgba(255,255,255,.1)" : "rgba(0,0,0,.1)"; +y.style.color=c; +y.children[0].style.display="none"; +}else{ +localStorage.setItem(x,"true"); +y.style.background=c; +y.style.color=dc; +y.children[0].style.display="block"; +} + + + + +} + + +/*The settings tab*/ +async function ytproSettings(){ +var ytpSet=document.createElement("div"); +var ytpSetI=document.createElement("div"); +ytpSet.setAttribute("id","settingsprodiv"); +ytpSetI.setAttribute("id","ssprodivI"); +ytpSet.setAttribute("style",` +height:100%;width:100%;position:fixed;top:0;left:0; +display:flex;justify-content:center; +background:rgba(0,0,0,0.7); +z-index:9999; +`); +ytpSet.addEventListener("click", +function(ev){ + +if(!(ev.target == ytpSetI || ytpSetI.contains(ev.target))){ + +history.back(); +} +}); + +ytpSetI.setAttribute("style",` +height:65%;width:calc(95% - 20px);overflow:auto; +background:${isD ? "#212121" : "#f1f1f1"}; +position:fixed; +bottom:20px; +z-index:99999999999999;padding:10px;text-align:center;border-radius:25px;color:${c};text-align:center; +color:${isD ? "#ccc" : "#444"};`); + +ytpSetI.innerHTML=``; +ytpSetI.innerHTML+=`
    ${ytproT("settings")} +v${YTProVer} +

    +
    + + +
    Please follow Habitius on InstagramFor daily habit,lifestyle and health tips
    + + + + + +
    + +
    +
    + +
    + +
    +
    ${ytproT("autoskipSponsors")}
    +
    +
    ${ytproT("gestureControls")}
    +
    +
    ${ytproT("miniplayerGesture")}
    +
    +
    ${ytproT("forceZoom")}
    +
    +
    ${ytproT("backgroundPlay")}
    +
    +
    ${ytproT("hideShorts")}
    +
    +
    ${ytproT("singleGeminiChat")}
    +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    ${ytproT("developerMode")}
    +

    +

    ${ytproT("disclaimer")}: ${ytproT("disclaimerText")}
    +${ytproT("sourceCode")} https://github.com/prateek-chaubey/YTPRO +




    + +
    + +
    + + +
    + + + + +

    +
    + + +
    + +
    + + +
    +${ytproT("madeWith")} + + + + + + + +${ytproT("by")} +
    +`; + + +document.body.appendChild(ytpSet); +ytpSet.appendChild(ytpSetI); + + +document.getElementById("ytproUrlInput").addEventListener("keyup",searchUrl); + + + + +var actionsList={ + follow:()=>{ + Android.oplink("https://www.instagram.com/habitius.daily"); + }, + hearts:()=>{ + window.location.hash='#hearts'; + }, + checkUpdate:()=>{ + checkUpdates(); + }, + sttCnf:(button,action)=>{ + sttCnf(button,action); + }, + geminiModels:()=>{ + document.getElementsByClassName('geminiModels')[0].style.display='block';document.getElementsByClassName('geminiModels')[0].innerHTML=getGeminiModels(); + }, + geminiPrompt:()=>{ + document.getElementsByClassName('geminiPrompt')[0].style.display='block'; + }, + issues:()=>{ + Android.oplink('https://github.com/prateek-chaubey/YTPRO/issues'); + }, + disableCodecs:()=>{ + document.getElementsByClassName('disableCodecs')[0].style.display='block';document.getElementsByClassName('disableCodecs')[0].innerHTML=getYTPROCodecs(); + }, + sponsor:()=>{ + Android.oplink('https://github.com/sponsors/prateek-chaubey'); + }, + toggleLang:()=>{ + localStorage.setItem("ytproLang", ytproLang() == "zh" ? "en" : "zh"); + Android.showToast(ytproT("languageChanged")); + window.location.reload(); + }, + savePrompt:(el)=>{ + localStorage.setItem('prompt',el.previousElementSibling.value);el.parentElement.style.display='none'; + }, + done:(el)=>{ + el.parentElement.style.display='none'; + }, + setRemoveCodec:(el,value)=>{ + setRemoveCodec(value,el) + }, + block_60fps:(el)=>{ + sttCnf(el,"block_60fps"); + }, + saveModel:(el,value)=>{ + localStorage.removeItem('geminiChatInfo'); + localStorage.setItem('geminiModel',value); + el.parentElement.style.display='none'; + } +} + +//buttons and switches +ytpSetI.querySelectorAll("[data-action]").forEach(button =>{ + button.addEventListener("click",()=>{ + + if(button.dataset.action== "sttCnf"){ + actionsList[button.dataset.action](button,button.dataset.value); + }else{ + actionsList[button.dataset.action](button); + } + }) +}); + + +//disable Codecs +ytpSetI.querySelector(".disableCodecs").addEventListener("click",(e)=>{ + var el = e.target.closest("[data-action]"); + if(!el) return; + + actionsList[el.dataset.action](el,el.dataset.value); + +}) + + +//gemini model selector +ytpSetI.querySelector(".geminiModels").addEventListener("click",(e)=>{ + var el = e.target.closest("[data-action]"); + if(!el) return; + + actionsList[el.dataset.action](el,el.dataset.value); + +}) + + + +} + + + +function searchUrl(e){ + + +if(e.keyCode === 13 || e === "Enter"){ + +var url=e.target.value; +const regex = /(?:https?:\/\/)?(?:www\.|m\.)?(?:youtu\.be\/|youtube(?:-nocookie)?\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|shorts|live)\/))([a-zA-Z0-9_-]{11})/; + +const match = url.match(regex); +var id=match ? match[1] : null; +if(id){ + return navigateInternalYtMweb(id); +} + + +var a=document.createElement("a"); +a.href=url; +document.body.appendChild(a); +try{document.getElementById("settingsprodiv").remove();}catch{} +a.click(); + +} +} + +function getVideoIdFromUrl(){ +if(window.location.pathname.indexOf("shorts") > -1){ +return window.location.pathname.replace("/shorts/",""); +} +return new URLSearchParams(window.location.search).get("v"); +} + +function openOriginalComments(){ +var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; +for(var i = 0; i < selectors.length; i++){ +var el = document.querySelector(selectors[i]); +if(el){ +el.scrollIntoView({behavior:"smooth", block:"start"}); +return true; +} +} + +var anchors = Array.from(document.querySelectorAll('a')); +for(var j = 0; j < anchors.length; j++){ +var href = anchors[j].href || ""; +if(href.indexOf("comment") > -1 || href.indexOf("replies") > -1){ +anchors[j].click(); +return true; +} +} +return false; +} + +function ensureCommentButton(){ +if(window.location.href.indexOf("youtube.com/watch") < 0 && window.location.href.indexOf("youtube.com/shorts") < 0) return; +if(document.getElementById("ytproCommentsBtn") != null) return; +var host = document.getElementById('ytproMainDivE'); +if(!host || !host.querySelector("div")) return; +var btn = document.createElement("div"); +sty(btn); +btn.id = "ytproCommentsBtn"; +btn.style.width = "110px"; +btn.innerHTML = `${ytproT("comments")}`; +btn.addEventListener("click", ytproCommentsPanel); +host.querySelector("div").appendChild(btn); +} + +function ytproCommentsPanel(){ +var existing = document.getElementById("ytproCommentsDiv"); +if(existing){ existing.remove(); } + +if(!/youtube\.com\/(watch|shorts)/.test(window.location.href)){ +Android.showToast(ytproT("commentsOnlyWatch")); +return; +} + +if(openOriginalComments()){ +Android.showToast(ytproT("originalCommentsOpened")); +return; +} + +var comments = document.createElement("div"); +comments.id = "ytproCommentsDiv"; +comments.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; + +var inner = document.createElement("div"); +inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:auto;padding:12px;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; + +var vid = getVideoIdFromUrl(); +inner.innerHTML = '
    ' + + '' + ytproT("comments") + '' + + '' + + '
    ' + + '
    Loading...
    ' + + '
    ' + (vid ? vid : "") + '
    '; + +comments.addEventListener("click", function(ev){ +if(ev.target === comments){ comments.remove(); } +var btn = ev.target.closest("[data-action]"); +if(btn && btn.dataset.action === "closeComments"){ comments.remove(); } +}); + +comments.appendChild(inner); +document.body.appendChild(comments); + +setTimeout(function(){ +document.getElementById("ytproCommentsBody").innerHTML = ytproT("commentsUnavailable") + '

    '; +document.getElementById("ytproCommentsBody").querySelector("[data-action='openNativeComments']").addEventListener("click", function(){ + Android.oplink("https://m.youtube.com/watch?v=" + (vid || "")); +}); +}, 50); +} + +function checkUpdates(){ +if(parseFloat(Android.getInfo()) < parseFloat(YTProVer) ){ +updateModel(); +}else{ +Android.showToast(ytproT("upToDate")); +} + +fetch('https://youtube.com/ytpro_local/script.js', {cache: 'reload'}); +fetch('https://youtube.com/ytpro_local/bgplay.js', {cache: 'reload'}); +fetch('https://youtube.com/ytpro_local/innertube.js', {cache: 'reload'}); +} + + +/*Set Configration*/ +function sttCnf(x,z,y){ + +/*Way too complex to understand*/ +if(isD){ +var s=["#000","#717171","#fff"]; +}else{ +var s=["#fff","#909090","#151515"]; +} + + + +if(typeof y == "string"){ + +if(localStorage.getItem(y) != "true"){ +if(z == 1){ +return `background:${s[0]};left:2px;`; +}else{ +return `background:${s[1]};`; +} +}else{ +if(z == 1){ +return `background:${s[0]};`; +}else{ +return `background:${s[2]};`; +} +} +} +if(localStorage.getItem(z) == "true"){ +localStorage.setItem(z,"false"); +x.style.background=s[1]; +x.children[0].style.left="2px"; +x.children[0].style.background=s[0]; +} +else{ +localStorage.setItem(z,"true"); +x.style.background=s[2]; +x.children[0].style.left="auto"; +x.children[0].style.right="2px"; +x.children[0].style.background=s[0]; +} + +if(localStorage.getItem("fzoom") == "false"){ +document.getElementsByName("viewport")[0].setAttribute("content","width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,"); +}else{ +document.getElementsByName("viewport")[0].setAttribute("content",""); +} + + + +if(localStorage.getItem("bgplay") == "true"){ +Android.setBgPlay(true); +}else{ +Android.setBgPlay(false); +} + + +if(localStorage.getItem("gesC") != "true"){ +try{ +document.getElementById("brtS").remove(); +document.getElementById("volS").remove(); +}catch{} + +} + +if(localStorage.getItem("devMode") == "false"){ +try{eruda.destroy();}catch{} +}else if(!window.eruda && localStorage.getItem("devMode") == "true"){ +var script = document.createElement('script'); script.src="//youtube.com/ytpro_cdn/npm/eruda"; document.body.appendChild(script); script.onload=()=>{ eruda.init();} +} + + + +} + + + + +/*Format File Size*/ +function formatFileSize(bytes){ +var s=parseInt(bytes); +let ss = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] +for (var i=0; s > 1024; i++) s /= 1024; +return `${s.toFixed(1)} ${ss[i]}`; +} + +/*Video Downloader*/ +async function ytproDownVid(){ + +window.ytproSabrDownload(); + +} + + + + + +function showHideAdaptives(){ +var z=document.querySelectorAll(".adpFormats"); +z.forEach((x)=>{ +if(x.style.display=="none"){ +x.style.display="flex"; +}else{ +x.style.display="none"; +} + +}); + +} + +/*Add the meme type and extensions lol*/ +function downCap(x,t){ +Android.downvid(t,x,"plain/text"); +} + +/*Send to Download Manager*/ +function YTDownVid(o,ex){ +var mtype=""; +if(ex ==".png"){ +mtype="image/png"; +}else if(ex ==".mp4"){ +mtype="video/mp4"; +} +else if(ex ==".mp3"){ +mtype="audio/mp3"; +} + +//console.log(o.getAttribute("data-ytprourl")) + +Android.downvid((o.getAttribute("data-ytprotit")+ex),o.getAttribute("data-ytprourl"),mtype); +} + + + + + + + + +var stopProp = false; +var zoomIn=false; +var scale=1; + + +/*Checks the Direction of the Swipe*/ +function checkDirection(e) { +if ((touchendY > touchstartY) && (touchendY - touchstartY > 20)) { +minimize(true); +}else if ((touchendY < touchstartY) && (touchstartY - touchendY > 20)) { +minimize(false); +//console.log((touchstartY - touchendY )) +} +} + +/*for zoom in and out*/ +function getDistance(touches) { +const [a, b] = touches; +return Math.hypot(b.pageX - a.pageX, b.pageY - a.pageY); +} + + + +/*touch start*/ +document.body.addEventListener('touchstart', e => { +touchstartY = e.changedTouches[0].screenY; +if (e.touches.length === 2) { +initialDistance = getDistance(e.touches); +} +}, { capture: true }); + + + + +/*touch move*/ +document.body.addEventListener('touchmove', (e) => { + + +if(stopProp){ +e.stopPropagation(); +} + +if (e.touches.length === 2 && initialDistance !== null) { +const currentDistance = getDistance(e.touches); +const z = currentDistance / initialDistance; + +stopProp=true; + + +if((e.target.className.toString().includes("video-stream") || e.target.className.toString().includes("player-controls-background")) && document.fullscreenElement){ + +if (z > 1.05) { +var Vv=document.getElementsByClassName('video-stream')[0]; +zoomIn=true; +scale=Math.max((screen.height / Vv.offsetHeight) , (screen.width / Vv.offsetWidth)); +addMaxButton(); +} else if (z < 0.95) { +zoomIn=false; +scale=1; +addMaxButton(); +} +} + + + +} +},{capture:true}); + + + + + + +/*touch end*/ +document.body.addEventListener('touchend', e => { + + +touchendY = e.changedTouches[0].screenY; + +if((e.target.className.toString().includes("video-stream") || e.target.className.toString().includes("player-controls-background")) && !document.fullscreenElement && localStorage.getItem("gesM") == "true"){ +checkDirection(); +} + +if (e.touches.length < 2) { +initialDistance = null; // reset + +setTimeout(()=>{ +stopProp=false; +},500) + +} + +}, { capture: true }); + + + + + +navigation.addEventListener("navigate", e => { +if(e.destination.url.indexOf("watch") > -1 || e.destination.url.indexOf("shorts") > -1){ + dislikes="..."; +fDislikes(e.destination.url); +checkSponsors(e.destination.url); +} +}); + + +/*minimize function to mini the video*/ +function minimize(yes){ + + +const createIframe=()=>{ + +var iframe=document.createElement("iframe"); +iframe.setAttribute("id",`miniIframe`); +iframe.setAttribute("style",` +height:99.999%;width:100%; +background:${c}; +top:0px; +line-height:50px; +position:fixed; +left:0; +z-index:999; +border:0; +`); + + +iframe.src="https://m.youtube.com/"; +document.body.appendChild(iframe); + + +var iwindow = iframe.contentWindow || iframe.contentDocument.defaultView; +var doc = iwindow.document; + +if (doc.readyState == 'complete' ) { +if (iwindow.trustedTypes && iwindow.trustedTypes.createPolicy && !iwindow.trustedTypes.defaultPolicy) { +iwindow.trustedTypes.createPolicy('default', {createHTML: (string) => string,createScriptURL: string => string, createScript: string => string, }); +} +} + +iwindow.navigation.addEventListener("navigate", e => { +if(e.destination.url.indexOf("youtube.com") > -1){ +if(e.destination.url.indexOf("/watch") > -1 || e.destination.url.indexOf("/shorts") > -1){ +window.location.href=e.destination.url; +} +var script = doc.createElement("script"); +var scriptSource=`window.addEventListener('DOMContentLoaded', function() { +var script2 = document.createElement('script'); +script2.src="//youtube.com/ytpro_cdn/npm/ytpro"; +document.body.appendChild(script2); +}); +`; +} +else{ +window.location.href=e.destination.url; +} + + +}); + +var script = doc.createElement("script"); +var scriptSource=`window.addEventListener('DOMContentLoaded', function() { +var script2 = document.createElement('script'); +script2.src="//youtube.com/ytpro_cdn/npm/ytpro"; +document.body.appendChild(script2); +}); +`; + +/* +var script = document.createElement('script'); +script.src="//cdn.jsdelivr.net/npm/eruda"; +document.body.appendChild(script); +script.onload = function () { eruda.init() } ; +*/ + + + +var source = doc.createTextNode(scriptSource); +script.appendChild(source); +doc.body.appendChild(script); + +return iframe; + +} + + + +var iframe = document.getElementById("miniIframe") || createIframe(); +var player=document.getElementById("player-container-id"); + + + + +//var ogCss=getComputedStyle(player); + +if(yes){ + +iframe.style.display="block"; + + +player.setAttribute("ogTop",getComputedStyle(player).top) + + +player.style.transform="scale(0.65)"; +player.style.top=(window.screen.height-(player.getBoundingClientRect().height*2.5))+"px"; +player.style.zIndex="9999"; + + +}else{ + +iframe.style.display="none"; + + + +player.style.transform="scale(1)"; +player.style.top=player.getAttribute("ogTop"); +player.style.zIndex="normal"; + +player.removeAttribute("ogTop"); + + +} +} + + + +/*JAVA Callback for AccessToken*/ +function callbackSNlM0e(){ +return new Promise(resolve => { +callbackSNlM0e.resolve = resolve; +}); +} + +/*JAVA Callback for Gemini Response*/ +function callbackGeminiClient(){ +return new Promise(resolve => { +callbackGeminiClient.resolve = resolve; +}); +} + + + + + +/*Handles the reponse*/ +function handleGeminiResponse(res){ + + +/*Extract the body from the response*/ +const getBody=(x)=>{ +for(var i in x){ +try{ +var json=JSON.parse(x[i][2]); +if(json[4]?.[0]?.[0].indexOf("rc_") > -1) return json; +}catch(e){console.log("JSON parse error: "+e);}} +} + +/*Modifies the timestamps , to handle them inside the video element*/ +const modifyTimestamps=(x)=>{ +var html=x; +var hrefs=html.match(/href="([^"]*)"/g) || []; +var urls= [...hrefs].map(url => url.replace(/href="|"/g, "")); +hrefs.forEach((x,i)=>{ +var time=new URL(urls[i]).searchParams.get("t"); +if(time != null){ +html=html.replace(x,`href="javascript:void(0);" onclick="document.getElementsByClassName('video-stream')[0].currentTime='${time}'"`) +}else if(urls[i].indexOf("youtube.com") < 0 && urls[i].indexOf("youtu.be") < 0){ +html=html.replace(x,`href="javascript:void(0);" onclick="try{document.getElementsByClassName('video-stream')[0].pause();}catch{}Android.oplink('${urls[i]}')"`) +} +}) +return html; +} + + + + + + +/*checks if the object is empty*/ +var response=res.stream; + +if (response == undefined) return document.getElementById("GeminiResponse").innerHTML=`
    An error Occurred while connecting to Gemini`; + +var lines=response.split("\n"); +var responseJson=JSON.parse(lines[2]) + + +var body=getBody(responseJson) || []; + +//console.log(body) + +var chat=[]; + +chat.push(body?.[1]?.[0]); +chat.push(body?.[1]?.[1]); +chat.push(body?.[4]?.[0]?.[0]); + +/*Stores the recent chat info*/ +localStorage.setItem("geminiChatInfo",chat.toString()); + + +body=body?.[4]?.[0]; + +var text=body?.[1]?.[0] || ""; +text=text.replace(/http:\/\/googleusercontent\.com\/\S+/g,''); +var thoughts = body?.[37]?.[0]?.[0] || null; +var images=[]; + +for(var i in body?.[12]?.[1]){ +var img=body?.[12]?.[1]?.[i] +images.push({ +url:img[0][0][0], +alt:img[0][4], +title:img[7][0] +}); + +text+=`
    ${img[0][4]}
    `; +} + +//console.log(text,"\n\n\n-------- \n\n",thoughts) + + + + + + +let converter = new showdown.Converter(); +converter.setFlavor('github'); +let html = modifyTimestamps(converter.makeHtml(text)); + + +let thoughtsHtml=(thoughts != null) ? ` +
    +
    +${converter.makeHtml(thoughts)} + + +

    ` : ""; + +document.getElementById("GeminiResponse").innerHTML=`Go to the chat

    + +${thoughtsHtml} + + + +
    +${html} +
    +`; + + +} + + + + + +/*Main Gemini Function*/ +async function geminiInfo(){ +if(document.getElementById("GeminiResponse") == null){ +var GeminiRes=document.createElement("div"); +GeminiRes.setAttribute("style",`min-height:80px;max-height:400px;display:block;height:auto;overflow:scroll;font-weight:400;width:calc(92% - 20px);font-size:14px;padding:10px;position:relative;margin:auto;background:${d};border-radius:15px;margin-bottom:8px;`); +GeminiRes.setAttribute("id","GeminiResponse"); + + +insertAfter(document.getElementById('ytproMainDivE'),GeminiRes); + +}else{ +var GeminiRes=document.getElementById("GeminiResponse"); +} + + +document.getElementById("GeminiResponse").innerHTML=` +
    `; + +var cookies=Android.getAllCookies(window.location.href); + +if(cookies.indexOf("__Secure-1PSID=") < 0){ +GeminiRes.innerHTML=` +
    +Sign in to use Gemini +

    + + + +

    + +
    `; + +return; + +} + + +/*checks if the user is logged in*/ +cookies=cookies.split(";"); + +var secured=""; + +cookies.forEach((x)=>{ +if(x.indexOf("__Secure-1PSID=") > -1 || x.indexOf("__Secure-1PSIDTS=") > -1) +secured+=x+";"; +}) + + + +var endpoint="https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"; +var headers=JSON.stringify({ +"accept": "*/*", +"accept-language": "en", +"content-type":"application/x-www-form-urlencoded;charset=UTF-8", +"x-goog-ext-525001261-jspb": GeminiModels[localStorage.getItem('geminiModel')], +"x-same-domain": "1", +"cookie": secured, +"Referer": "https://gemini.google.com/", +"Referrer-Policy": "origin" +}); + + +if(GeminiAT == ""){ +Android.getSNlM0e(secured); +GeminiAT=await callbackSNlM0e(); + +var sd = document.createElement('script'); +sd.src="//youtube.com/ytpro_cdn/npm/showdown/dist/showdown.min.js"; +document.body.appendChild(sd); + +} + + + + +var prompt=localStorage.getItem('prompt').replaceAll("{url}",window.location.href).replaceAll("{videoId}",new URL(window.location.href).searchParams.get("v")).replaceAll("{title}",document.getElementsByClassName('slim-video-metadata-header')[0].textContent.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","")); +//`send me details with timestamps and images related to this youtube com video ${}`; +// , including all the aspects and scopes with timestamp , add facts in the analysis as well ,Here's the youtube + + + +var chat = null; + +if(localStorage.getItem("saveCInfo") == "true" && localStorage.getItem("geminiChatInfo") != null){ +chat = localStorage.getItem("geminiChatInfo").split(","); +} + +const formData = new URLSearchParams(); +formData.append("f.req", JSON.stringify([ +null, +JSON.stringify([[prompt],null,chat]) +])); + +formData.append("at", GeminiAT); + + + +Android.GeminiClient(endpoint,headers,formData.toString()); +var response=await callbackGeminiClient(); + +handleGeminiResponse(response); + +} + + +var volSvg=``; +var brtSvg=``; + + +/*THE 0NE AND 0NLY FUNCTION*/ +async function pkc(){ + +if(window.location.href.indexOf("youtube.com/watch") > -1){ + + +try{ +var elm=document.getElementsByTagName("dislike-button-view-model")[0].children[0]; +elm.children[0].children[0].style.width="auto"; +elm.children[0].children[0].style.paddingRight="15px"; + +if(!document.getElementById("diskl")){ + var diskl=document.createElement("span"); + diskl.setAttribute("id","diskl"); + diskl.innerHTML=dislikes; + diskl.style.marginLeft="5px"; + +insertAfter(elm.getElementsByClassName("yt-spec-button-shape-next__icon")[0],diskl); + +}else{ +document.getElementById("diskl").innerHTML=dislikes; +} + +}catch(e){} + + +//Volume and brightness slider +try{ + +if(localStorage.getItem("gesC") == "true"){ + + +var v= document.getElementById("player-container-id"); +var rect=v.getBoundingClientRect(); + +var elStyle={ +height:"70%", +width:rect.width*0.14+"px", +display:"flex", +"flex-direction":"column", +"align-items":"center", +"justify-content":"center", +position:"absolute", +top:"16%", +right:"0px", +opacity:"0", +//background:"#a57a" +}; + + + +var el=document.createElement("div"); +var elB=document.createElement("div"); +elB.setAttribute("id","brtS"); +el.setAttribute("id","volS"); + +Object.assign(el.style,elStyle); +Object.assign(elB.style,elStyle); +elB.style.left="0"; + +el.innerHTML=`${volSvg}
    `; +elB.innerHTML=`${brtSvg}
    `; + + +if(!document.getElementById("brtS")){ +document.getElementById("player-container-id").appendChild(elB); + +elB.addEventListener("touchmove",(e)=>{ +e.preventDefault(); +elB.style.opacity="1"; + +var diff= touchstartY - e.touches[0].pageY; + +if(diff > 0){ +brt +=sens; +}else{ +brt -=sens; +} + +if(brt > 1) brt=1; +if(brt < 0) brt =0; + +touchstartY=e.touches[0].pageY; + +Android.setBrightness(brt); +document.getElementById("brtIS").style.height=brt*100+"%"; + +},{ passive: false }) + + +//hide the element after touch endas +elB.addEventListener("touchend",(e)=>{ +elB.style.opacity="0"; +},{ passive: false }); + +} + + + + + +if(!document.getElementById("volS")){ +document.getElementById("player-container-id").appendChild(el); + +el.addEventListener("touchmove",(e)=>{ +e.preventDefault(); +el.style.opacity="1"; + +var diff= touchstartY - e.touches[0].pageY; + +if(diff > 0){ +vol +=sens; +}else{ +vol -=sens; +} + +if(vol > 1) vol=1; +if(vol < 0) vol =0; + +touchstartY=e.touches[0].pageY; + +Android.setVolume(vol); +document.getElementById("volIS").style.height=vol * 100 +"%"; + +},{ passive: false }) + + + +//hide the element after touch endas , yes endas +el.addEventListener("touchend",(e)=>{ +el.style.opacity="0"; +},{ passive: false }); + +} + +} + + + +}catch(e){ + console.log(e) +} + + + + + + + + + + + +/*Check If Element Already Exists*/ +if(document.getElementById("ytproMainDivE") == null){ + + + +var ytproMainDivA=document.createElement("div"); +ytproMainDivA.setAttribute("id","ytproMainDivE"); +ytproMainDivA.setAttribute("style",` +height:50px;width:100%;display:block;overflow:auto; +`); + +insertAfter(document.getElementsByClassName('slim-video-action-bar-actions')[0],ytproMainDivA); + +var ytproMainDiv=document.createElement("div"); +ytproMainDiv.setAttribute("style",` +height:50px;width:100%;display:flex;overflow:auto; +align-items:center;justify-content:center;padding-left:20px;padding-right:10px; +`); +ytproMainDivA.appendChild(ytproMainDiv); + +/*Gemini Button*/ +var ytproGemini=document.createElement("div"); +sty(ytproGemini); +ytproGemini.style.width="115px"; +ytproGemini.style.height="calc(65% - 4.5px)"; +ytproGemini.style.position="relative"; +ytproGemini.style.background=`linear-gradient(${isD ? "#272727,#272727" : "#f2f2f2,#f2f2f2"}) padding-box , linear-gradient(16deg ,#4285f4 ,#9b72cb ,#d96570) border-box`; +ytproGemini.style.border="2px solid transparent"; +ytproGemini.innerHTML=` + +Gemini + +`; + + + + + + +ytproMainDiv.appendChild(ytproGemini); + + +ytproGemini.addEventListener("click", +async function(){ + + +if(parseFloat(Android.getInfo()) < parseFloat(YTProVer)){ +updateModel(); + +return; +} + +geminiInfo(); + + +}); + + + + + + + + + + + +/*Heart Button*/ +var ytproFavElem=document.createElement("div"); +sty(ytproFavElem); +if(!isHeart()){ +ytproFavElem.innerHTML=`${ytproT("heart")}`; +}else{ +ytproFavElem.innerHTML=`${ytproT("heart")}`; +} +ytproMainDiv.appendChild(ytproFavElem); +ytproFavElem.addEventListener("click",()=>{ytProHeart(ytproFavElem);}); + + + +/*Download Button*/ +var ytproDownVidElem=document.createElement("div"); +sty(ytproDownVidElem); +ytproDownVidElem.style.width="140px"; +ytproDownVidElem.innerHTML=`${downBtn.replace('width="18"','width="24"').replace('height="18"','height="24"')}${ytproT("download")}`; +ytproMainDiv.appendChild(ytproDownVidElem); +ytproDownVidElem.addEventListener("click", +function(){ +window.location.hash="download"; +}); + +/*PIP Button*/ +var ytproPIPVidElem=document.createElement("div"); +sty(ytproPIPVidElem); +ytproPIPVidElem.style.width="140px"; +ytproPIPVidElem.innerHTML=`${ytproT("pipMode")}`; +ytproMainDiv.appendChild(ytproPIPVidElem); +ytproPIPVidElem.addEventListener("click", +function(){ +PIPlayer(true); +}); + + + + + +} + + + + + +}else if(window.location.href.indexOf("youtube.com/shorts") > -1){ + + +let b = document.getElementById("brtS"); +let v = document.getElementById("volS"); +if (b) b.remove(); +if (v) v.remove(); + + +if(document.getElementById("ytproMainSDivE") == null){ +var ys=document.createElement("div"); +ys.setAttribute("id","ytproMainSDivE"); +ys.setAttribute("style",`width:50px;height:auto;position:relative;display:block;`); + + +/*Download Button*/ +ysDown=document.createElement("div"); +ysDown.setAttribute("style",` +height:48px;width:48px;display:flex;align-items:center;justify-content:center; +filter:drop-shadow(0 0 1px #0009); +border-radius:50%; +`); +ysDown.innerHTML=downBtn.replaceAll(`${c}`,`#fff`).replace(`width="24"`,`width="30"`).replace(`height="24"`,`height="30"`); + + +ysDown.addEventListener("click", +function(){ +window.location.hash="download"; +}); + + +/*Heart Button*/ +ysHeart=document.createElement("div"); +ysHeart.setAttribute("style",` +height:48px;width:48px; +display:flex;align-items:center;justify-content:center; +filter:drop-shadow(0 0 1px #0009); +border-radius:50%;margin-bottom:0px; +`); + + +if(!isHeart()){ +ysHeart.innerHTML=``; +}else{ +ysHeart.innerHTML=``; +} + + +ysHeart.addEventListener("click", +function(){ +ytProHeart(ysHeart); +}); + + + + + +try{ + + if(document.getElementsByClassName("reel-player-overlay-actions")[0].children[0]){ + +document.getElementsByClassName("reel-player-overlay-actions")[0].insertBefore(ys,document.getElementsByClassName("reel-player-overlay-actions")[0].children[1]); + +ys.appendChild(ysDown); +ys.appendChild(ysHeart); +} +}catch{} + +} + +try{document.querySelectorAll('dislike-button-view-model')[0].children[0].children[0].children[0].children[1].children[0].innerHTML=dislikes;}catch{} + + + + + +/*Watch The old and New URL* +if(ytoldV != window.location.pathname){ +fDislikes(); +ytoldV=window.location.pathname; +}*/ + + +} + +} + + +setInterval(pkc,0); + + + + + +/*SHOW HEARTS*/ +async function showHearts(){ +var ytproH=document.createElement("div"); +var ytproHh=document.createElement("div"); +ytproHh.setAttribute("id","heartytprodiv"); +ytproH.setAttribute("id","outerheartsdiv"); +ytproH.setAttribute("style",` +height:100%;width:100%;position:fixed;top:0;left:0; +display:flex;justify-content:center; +background:rgba(0,0,0,0.4); +z-index:99; +`); + +ytproHh.setAttribute("style",` +height:50%;width:85%;overflow:auto;background:${isD ? "#212121" : "#f1f1f1"}; +position:absolute;bottom:20px; +z-index:9;padding:20px;text-align:center;border-radius:25px;text-align:center; +`); +ytproHh.innerHTML=``; +ytproHh.innerHTML+="Liked Videos
      "; + + +ytproHh.innerHTML+=""; + +document.body.appendChild(ytproH); +ytproH.appendChild(ytproHh); + +ytproH.addEventListener("click", +function(ev){ +if(!event.composedPath().includes(ytproHh)){ +history.back(); +} +}); + + + +if(localStorage.getItem("hearts") == null){ +ytproHh.innerHTML+="No Videos Found"; +}else{ + +var v=JSON.parse(localStorage.getItem("hearts")); + +if(Object.keys(v).length === 0){ +return ytproHh.innerHTML+="No Videos Found"; +} + +for(var n=Object.keys(v).length - 1; n > -1 ; n--){ +var x=Object.keys(v)[n]; +ytproHh.innerHTML+=`
    • +
      +
      ${v[x].title}
      +
      + + + + +
      +
    • `; +await new Promise(r => setTimeout(r, 1)); +} + + +ytproHh.addEventListener("click",(e)=>{ + var el=e.target.closest("[data-action]"); + + if(!el) return; + if(el.dataset.action == "navigateInternalYtMweb"){ + navigateInternalYtMweb(el.dataset.id); + }else if(el.dataset.action == "remHeart"){ + remHeart(el,el.dataset.id); + } + +}); + +} + + + + + +} + + +function navigateInternalYtMweb(videoId) { + window.location.hash=""; + const link = document.createElement('a'); + link.href = `/watch?v=${videoId}`; + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + link.remove(); +} + + +/*Dil hata diya vro*/ +function remHeart(y,x){ +if(localStorage.getItem("hearts")?.indexOf(x) > -1){ +y.parentElement.parentElement.remove(); +var j=JSON.parse(localStorage.getItem("hearts") || "{}"); +delete j[x]; +localStorage.setItem("hearts",JSON.stringify(j)); +} + +} + +function ytProHeart(x){ + + +var vid=(new URLSearchParams(window.location.search)).get('v') || window.location.pathname.replace("/shorts/",""); + +var video=document.getElementsByClassName('video-stream')[0]; +var canvas = document.createElement('canvas'); +canvas.style.width = "1600px"; +canvas.style.height = "900px"; +canvas.style.background="black"; +var context = canvas.getContext('2d'); + +(window.location.pathname.indexOf("shorts") > -1) ? context.drawImage(video,105, 0, 90,160) : context.drawImage(video,0, 0, 320,180); + +var dataURI = canvas.toDataURL('image/jpeg'); + + +if(window.location.pathname.indexOf("shorts") > -1){ + +var vDetails={ +thumb:dataURI, +title:document.getElementsByClassName('ytShortsVideoTitleViewModelShortsVideoTitle')[0].textContent.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","") +}; + +}else{ + +var vDetails={ +thumb:dataURI, +title:document.getElementsByClassName('slim-video-metadata-header')[0].textContent.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","") +} + +/* +var vDetails={ +thumb:[...ytplayer.config.args.raw_player_response?.videoDetails?.thumbnail?.thumbnails].pop().url, +title:ytplayer.config.args.raw_player_response?.videoDetails?.title.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","") +};*/ + +} + + + +var g="16"; +var h=`Heart`; +(window.location.href.indexOf('youtube.com/shorts') > -1) ? h=``:h=`Heart`; +(window.location.href.indexOf('youtube.com/shorts') > -1) ? g="24" : g="24" ; + + + +if(localStorage.getItem("hearts")?.indexOf(vid) > -1){ +var j=JSON.parse(localStorage.getItem("hearts") || "{}"); +delete j[vid]; +localStorage.setItem("hearts",JSON.stringify(j)); +x.innerHTML=` +${h}`; +}else{ +var j=JSON.parse(localStorage.getItem("hearts") || "{}"); +j[vid]=vDetails; +localStorage.setItem("hearts",JSON.stringify(j)); +x.innerHTML=`${h}`; +} + +} + + + +/*Dil diya hai ya nhi diya!!*/ +function isHeart(){ + +if((localStorage.getItem("hearts")?.indexOf((new URLSearchParams(window.location.search)).get('v')) > -1) || (localStorage.getItem("hearts")?.indexOf(window.location.pathname.replace("/shorts/","")) > -1)){ +return true; +}else{ +return false; + +} +} + + + + + + +///PIP MODE CONFIG +function removePIP(){ + +isPIP=false; +pauseAllowed = true; +document.exitFullscreen(); + +document.getElementsByClassName('video-stream')[0].pause(); +setTimeout(()=>{ +document.getElementsByClassName('video-stream')[0].play(); +},5); + + +} + + + + +function PIPlayer(pip = false){ + +var v=document.getElementsByClassName('video-stream')[0]; + + +if(pip){ + +if(v.getBoundingClientRect().height > v.getBoundingClientRect().width){ +Android.pipvid("portrait"); +} +else{ +Android.pipvid("landscape"); +} + +return; +} + + +v.requestFullscreen(); +v.play(); +pauseAllowed = false; +isPIP=true; + +} + + + + + + + + + + + + + + + + + + + +// well this is for bypassing the pause function of Youtube when video is in +// PIP mode , its a workaround for now , until i find a proper method +// to allow the pip mode for the video element , like chromium browsers + +HTMLMediaElement.prototype.pause = function(){ + +if (pauseAllowed || PIPause) { +return originalPause.apply(this, arguments); +} + +if (this.paused) { +this.play().catch(() => {}); +} +}; + + + + + + + + + +const originalExitFullscreen = document.exitFullscreen; +const originalRequestFullscreen = Element.prototype.requestFullscreen; + +//exit full screen +document.exitFullscreen = function (...args) { + if(!isPIP){ return originalExitFullscreen.apply(this, args);} +}; + + +//request full screen +Element.prototype.requestFullscreen = function (...args) { +var video = document.getElementsByClassName('video-stream')[0]; + +if(video.getBoundingClientRect().height > video.getBoundingClientRect().width){ +Android.fullScreen(true); +} +else{ +Android.fullScreen(false); +} + +return originalRequestFullscreen.apply(this, args); +}; + + + + + + +/*Check The Hash Change*/ +window.onhashchange=()=>{ +try{document.getElementById("outerdownytprodiv").remove();}catch{} +try{document.getElementById("outerheartsdiv").remove();}catch{} +try{document.getElementById("settingsprodiv").remove();}catch{} +try{document.getElementById("ytproCommentsDiv").remove();}catch{} +//try{document.querySelector("#ytproDownloadIndicator").remove();}catch{} +//try{document.querySelector("#ytProDownloaderDiv").remove();}catch{} +if(window.location.hash == "#download"){ +ytproDownVid(); +}else if(window.location.hash == "#settings"){ +ytproSettings(); +} +else if(window.location.hash == "#hearts"){ +showHearts(); +}else if(window.location.hash == "#comments"){ +ytproCommentsPanel(); +} + + +} + + + +// AdBlocker which removes the ad contents from the fetch requests itself !! +(() => { +const _origFetch = window.fetch; +window.fetch = async function(input, init) { +try { +const url = (typeof input === 'string') ? input : input.url; + + + +//block ad urls +if(url.includes("googleads.g.doubleclick.net") || url.includes("youtube.com/youtubei/v1/player/ad_break") || url.includes("youtube.com/pagead/adview") || url.includes("youtube.com/api/stats/ads")){ + +//console.log("Blocked",url); +return ""; +}else if(url.includes("youtube.com/youtubei/")){ + + +const response = await _origFetch.apply(this, arguments); + + + +try { + +const clone = response.clone(); +let data = await clone.json(); + + +//older version +if(data?.responseContext?.webResponseContextExtensionData?.webResponseContextPreloadData?.preloadMessageNames?.[0] == "adSlotRenderer" || data?.responseContext?.webResponseContextExtensionData?.webResponseContextPreloadData?.preloadMessageNames?.[0] == "shortsAdsRenderer"){ +data={}; +} + + +//remove the ad content +delete data?.adSlots; +delete data?.playerAds; +delete data?.adPlacements; +delete data?.adBreakHeartbeatParams; + + +//newer version update: 09 Feb , 2026 23:27 IST +delete data?.[0]?.playerResponse?.adSlots; +delete data?.[0]?.playerResponse?.playerAds; +delete data?.[0]?.playerResponse?.adPlacements; +delete data?.[0]?.playerResponse?.adBreakHeartbeatParams; + + +const newBody = JSON.stringify(data); + +// Build new headers (update content-length + content-type) +const newHeaders = new Headers(response.headers); +newHeaders.set("content-length", String(newBody.length)); +newHeaders.set("content-type", "application/json"); + +// Return modified Response +return new Response(newBody, { +status: response.status, +statusText: response.statusText, +headers: newHeaders +}); +} catch (e) { +// not JSON, return original +return response; +} + + + +} + +return _origFetch.apply(this, arguments); + +} catch (e) { /* ignore logging errors */ } + +return _origFetch.apply(this, arguments); + + +}; + + +})(); + + + +//modified XHR for the same purpose +const XHR = window.XMLHttpRequest; +const origOpen = XHR.prototype.open; +const origSend = XHR.prototype.send; + +XHR.prototype.open = function(method, url, ...rest) { +this._interceptedMethod = method; +this._interceptedUrl = url; +return origOpen.apply(this, [method, url, ...rest]); +}; + +XHR.prototype.send = function(body) { +// Block certain URLs +if ( +this._interceptedUrl.includes("googleads.g.doubleclick.net") || +this._interceptedUrl.includes("youtube.com/youtubei/v1/player/ad_break") || +this._interceptedUrl.includes("youtube.com/pagead/adview") || +this._interceptedUrl.includes("youtube.com/api/stats/ads") +) { +//console.warn("Blocked:", this._interceptedUrl); +return; +} + +return origSend.apply(this, arguments); +}; + + + + + + + + + + + + +/****** I LOVE YOU <3 *****/ +/*YT ADS BLOCKER*/ +function adsBlock(){ + + +try{ +document.getElementsByClassName('video-stream')[0].removeAttribute('disablepictureinpicture'); +}catch{} + + +/*Block Ads*/ +var ads=document.getElementsByTagName("ad-slot-renderer"); +for(var x in ads){ +try{ads[x].remove();}catch{} +} +try{ +document.getElementsByClassName("ad-interrupting")[0].getElementsByTagName("video")[0].currentTime=document.getElementsByClassName("ad-interrupting")[0].getElementsByTagName("video")[0].duration; +document.getElementsByClassName("ytp-ad-skip-button-modern")[0].click(); + +}catch{} + + + + +/*Block Ads*/ +try{ +document.getElementsByTagName("ytm-promoted-sparkles-web-renderer")[0].remove(); +}catch{} +try{ +document.getElementsByTagName("ytm-companion-ad-renderer")[0].remove(); +}catch{} + +/*Remove Open App*/ +try{ +document.querySelectorAll('a').forEach(a => { +if (a.href.indexOf("intent://") > -1) { +a.style.display = 'none'; +} +}); +}catch{} +/*Remove Promotion Element*/ +try{document.getElementsByTagName("ytm-paid-content-overlay-renderer")[0].style.display="none";}catch{} + +/*Hide Shorts*/ +if(localStorage.getItem("shorts") == "true"){ + + +for( x in document.getElementsByClassName("big-shorts-singleton")){ +try{document.getElementsByClassName("big-shorts-singleton")[x].remove(); +}catch{} +} + +for( x in document.getElementsByTagName("ytm-reel-shelf-renderer")){ +try{document.getElementsByTagName("ytm-reel-shelf-renderer")[x].remove(); +}catch{} + +for( x in document.getElementsByTagName("ytm-shorts-lockup-view-model")){ +try{document.getElementsByTagName("ytm-shorts-lockup-view-model")[x].remove(); +}catch{} + +} + +} +} + + + + +} + + + + + +//Add Maximize Gesture +function addMaxButton(){ + + +var pElem=document.getElementById('player-container-id'); +var Ve=document.getElementById('player'); +var Vv=document.getElementsByClassName('video-stream')[0]; + + + +if(pElem === document.fullscreenElement){ + + +try{ +if(zoomIn){ +Ve.style.transform=`scale(${scale})`; +}else{ +Ve.style.transform="scale(1)"; +} +}catch{} + + +}else{ +try{ +Ve.style.transform="scale(1)"; +}catch{} +} + + +} + + +async function extraSpeed(){ + var el=document.querySelector(".ytwVariableSpeedControllerViewModelButtonContainer"); + if(!el) return; + + +const slider = document.getElementById("slider"); + +if(slider.max==10) return; + +slider.max = 10; +slider.ariaValueMax = "10"; + +slider.addEventListener("input", () => { + const video = document.querySelector('.video-stream'); + if (video) video.playbackRate = parseFloat(slider.value); +}); + +if(el.children.length >= 6) el.children[0].remove(); + +if(!document.getElementById("10xSpeed")){ + +var elm=document.createElement("ytw-variable-speed-controller-speed-button-view-model"); + +elm.id="10xSpeed"; +elm.className="ytwVariableSpeedControllerSpeedButtonViewModelHost ytwVariableSpeedControllerViewModelPlaybackSpeedButton"; + + +elm.insertAdjacentHTML("beforeend",``); + +elm.addEventListener("click", () => { + // document.querySelector('.video-stream').playbackRate = 10; + slider.value=10; + slider.dispatchEvent(new Event("input", { bubbles: true })); + +}); + +el.appendChild(elm); + + +} + +} + + +//https://youtube.com/watch?v=SInH_fP0deQ + + + +/*Mutation Observer*/ +//as i have been developing YTPRO for almost 4 years now +//thus it still contains the code which i used when i was a +//totally noob in copy pasting , that time i wasn't aware of +//plenty of things and by which i used `setInterval` instead +//of mutation observer , i shall be optimizing the code in future +//releases but rn only a few code blocks will be in the obesrver + +const targetNode = document.body; +const config = { childList: true, subtree: true }; + +const observer = new MutationObserver(() => { + + +//speed + +extraSpeed(); + +//ads Block +adsBlock(); + + +//mE button +addMaxButton(); + +//settingsTab +addSettingsTab(); +ensureCommentButton(); + + +try{ +var video = document.getElementsByClassName('video-stream')[0]; +if(video.getBoundingClientRect().height > video.getBoundingClientRect().width){ +Android.fullScreen(true); +} +else{ +Android.fullScreen(false); +}} +catch{} + + +}); + +// Start observing changes in the body +observer.observe(targetNode, config); + + + + + +/*Update your app bruh*/ +function updateModel(){ +var x=document.createElement("div"); + +x.setAttribute("style",`height:100%;width:100%;position:fixed;display:grid;align-items:center;top:0;left:0;background:rgba(0,0,0,.6);z-index:99999;`); + +x.innerHTML=` +
      +

      Mandatory Update


      +Latest Version ${YTProVer} of YTPRO is available , update the YTPRO to get latest features. +
      - This update is mandatory as it fixes a ton of bugs and improves functionality
      +- Fixed Downloads, switched to SABR downloader
      +- Added muxing to the youtube videos
      +- Fixed gestures for brightness and volume control
      +- Optimized the UI of both Download and Settings menu
      +- Added speed increase upto 10x
      +- Fixed bugs and improved functionality
      +- for the full list click here +
      +
      +
      + + +
      + +
      +`; + +x.addEventListener("click",(e)=>{ + var el=e.target.closest("[data-action]"); + if(!el) return; + var action=el.dataset.action; + + if(action == "url"){ + Android.oplink('https://github.com/prateek-chaubey/YTPRO/releases'); + }else if(action == "download"){ + Android.downvid('YTPRO.zip','https://nightly.link/prateek-chaubey/YTPro/workflows/gradle/main/YTPRO.zip','application/zip'); + }else if(action =="cancel"){ + el.parentElement.parentElement.parentElement.remove(); + } + +}) + +document.body.appendChild(x); +} + + + + + +window.onload = function(){ +if(parseFloat(Android.getInfo()) < parseFloat(YTProVer) && (window.location.href == "https://m.youtube.com/" || window.location.href == "https://m.youtube.com") ){ +updateModel(); +} + +}; + + + + +document.addEventListener('click',(event) => { + +let anchor = event.target.closest('a'); +if (anchor){ + + +if(anchor.href.includes("www.youtube.com/redirect")){ + +try{ +document.getElementsByClassName('video-stream')[0].pause(); +}catch{} + +const url=new URL(anchor.href).searchParams.get("q"); + +setTimeout(()=>{Android.oplink(url)},50); + +event.preventDefault(); +event.stopPropagation(); + +} + + +} +}, +true); + + + + +} diff --git a/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java b/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java index f5d05afd..8552d32f 100644 --- a/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java +++ b/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java @@ -34,6 +34,10 @@ public YTProWebViewClient(MainActivity activity, YTProWebView web) { public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { String url = request.getUrl().toString(); + if (url.contains("youtube.com/ytpro_local/")) { + return getLocalYtProAsset(url); + } + if (url.contains("accounts.google.com") || url.contains("myaccount.google.com") || url.contains("accounts.youtube.com") || @@ -181,13 +185,32 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque return super.shouldInterceptRequest(view, request); } + + private WebResourceResponse getLocalYtProAsset(String url) { + try { + String fileName = url.substring(url.lastIndexOf('/') + 1); + String mimeType = fileName.endsWith(".js") ? "application/javascript" : "text/plain"; + InputStream stream = activity.getAssets().open("ytpro/" + fileName); + + Map headers = new HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Access-Control-Allow-Methods", "GET, OPTIONS"); + headers.put("Access-Control-Allow-Headers", "*"); + headers.put("Cache-Control", "no-cache, no-store, must-revalidate"); + + return new WebResourceResponse(mimeType, "utf-8", 200, "OK", headers, stream); + } catch (Exception e) { + Log.e("YTPRO_WVC", "Local asset fetch failed: " + e.getMessage()); + return null; + } + } @Override public void onPageFinished(WebView view, String url) { web.evaluateJavascript("if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {window.trustedTypes.createPolicy('default', {createHTML: (string) => string,createScriptURL: string => string, createScript: string => string, });}", null); - web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_cdn/npm/ytpro@latest'; document.body.appendChild(script); })();", null); - web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_cdn/npm/ytpro@latest/bgplay.js'; document.body.appendChild(script); })();", null); - web.evaluateJavascript("(function () { var script = document.createElement('script');script.type='module';script.src='https://youtube.com/ytpro_cdn/npm/ytpro@latest/innertube.js'; document.body.appendChild(script); })();", null); + web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_local/script.js'; document.body.appendChild(script); })();", null); + web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_local/bgplay.js'; document.body.appendChild(script); })();", null); + web.evaluateJavascript("(function () { var script = document.createElement('script');script.type='module';script.src='https://youtube.com/ytpro_local/innertube.js'; document.body.appendChild(script); })();", null); diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..d33b2647 --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,7 @@ + + 请授予麦克风权限以使用语音识别 + 请授予存储权限以下载文件 + 当前 Android 版本不支持画中画 + 已开始下载 + 下载 + diff --git a/scripts/script.js b/scripts/script.js index 5dcfd105..c8c46a58 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -28,6 +28,98 @@ window.pauseAllowed = true; // allow pause by default var sTime=[]; var webUrls=["m.youtube.com","youtube.com","yout.be","accounts.google.com"]; var GeminiAT=""; +var YTProLocales = { + en: { + settings: "YT PRO Settings", + enterUrl: "Enter YouTube URL", + likedVideos: "Liked Videos", + checkUpdates: "Check for Updates", + autoskipSponsors: "Autoskip Sponsors", + gestureControls: "Gesture Controls", + miniplayerGesture: "Miniplayer Gesture", + forceZoom: "Force Zoom", + backgroundPlay: "Background Play", + hideShorts: "Hide Shorts", + singleGeminiChat: "Use single Gemini chat", + selectGeminiModel: "Select Gemini Model", + editGeminiPrompt: "Edit Gemini Prompt", + disableCodecs: "Disable Codecs", + reportBugs: "Report Bugs", + sponsor: "Become a Sponsor", + developerMode: "Developer Mode", + disclaimer: "Disclaimer", + disclaimerText: "This is an educational project aimed at showcasing javascript injection into a webview to enhance productivity.", + sourceCode: "You can find the source code at", + madeWith: "Made with", + by: "by Prateek Chaubey", + language: "Language", + english: "English", + chinese: "Simplified Chinese", + languageChanged: "Language changed. Reloading...", + upToDate: "Your app is up to date", + comments: "Comments", + commentsUnavailable: "Could not find the original YouTube comments on this page", + commentsOnlyWatch: "Comments are available on video pages", + openYouTubeComments: "Open YouTube comments", + originalCommentsOpened: "Opened original YouTube comments.", + download: "Download", + heart: "Heart", + pipMode: "PIP Mode", + noVideosFound: "No Videos Found", + likedVideosTitle: "Liked Videos" + }, + zh: { + settings: "YT PRO 设置", + enterUrl: "输入 YouTube 链接", + likedVideos: "收藏的视频", + checkUpdates: "检查更新", + autoskipSponsors: "自动跳过赞助片段", + gestureControls: "手势控制", + miniplayerGesture: "小窗手势", + forceZoom: "强制缩放", + backgroundPlay: "后台播放", + hideShorts: "隐藏 Shorts", + singleGeminiChat: "使用单个 Gemini 对话", + selectGeminiModel: "选择 Gemini 模型", + editGeminiPrompt: "编辑 Gemini 提示词", + disableCodecs: "禁用编解码器", + reportBugs: "反馈问题", + sponsor: "赞助作者", + developerMode: "开发者模式", + disclaimer: "免责声明", + disclaimerText: "本项目用于展示如何通过 WebView 注入 JavaScript 来增强使用体验。", + sourceCode: "你可以在这里查看源代码:", + madeWith: "Made with", + by: "by Prateek Chaubey", + language: "语言", + english: "English", + chinese: "简体中文", + languageChanged: "语言已切换,正在重新加载...", + upToDate: "当前已是最新版本", + comments: "评论", + commentsUnavailable: "没有在当前页面找到 YouTube 原生评论区", + commentsOnlyWatch: "评论区仅在视频页面可用", + openYouTubeComments: "打开 YouTube 评论", + originalCommentsOpened: "已打开 YouTube 原生评论区。", + download: "下载", + heart: "收藏", + pipMode: "画中画", + noVideosFound: "暂无视频", + likedVideosTitle: "收藏的视频" + } +}; + +function ytproLang(){ + var saved = localStorage.getItem("ytproLang"); + if(saved == "zh" || saved == "en") return saved; + return ((navigator.language || "").toLowerCase().indexOf("zh") == 0) ? "zh" : "en"; +} + +function ytproT(key){ + var lang = ytproLang(); + return (YTProLocales[lang] && YTProLocales[lang][key]) || YTProLocales.en[key] || key; +} + var GeminiModels = { "3.0 Pro": '[1,null,null,null,"9d8ca3786ebdfbea",null,null,0,[4],null,null,1]', "3.0 Flash": '[1,null,null,null,"fbb127bbb056c959",null,null,0,[4],null,null,1]', @@ -714,7 +806,7 @@ margin-right:2%; color:${c}; } `; -ytpSetI.innerHTML+=`
      YT PRO Settings +ytpSetI.innerHTML+=`
      ${ytproT("settings")} v${YTProVer}

      @@ -728,69 +820,75 @@ ytpSetI.innerHTML+=`
      YT PRO Settings
      -
      +

      -
      -
      -
      Autoskip Sponsors
      +
      ${ytproT("autoskipSponsors")}

      -
      Gesture Controls
      +
      ${ytproT("gestureControls")}

      -
      Miniplayer Gesture
      +
      ${ytproT("miniplayerGesture")}

      -
      Force Zoom
      +
      ${ytproT("forceZoom")}

      -
      Background Play
      +
      ${ytproT("backgroundPlay")}

      -
      Hide Shorts
      +
      ${ytproT("hideShorts")}

      -
      Use single Gemini chat
      +
      ${ytproT("singleGeminiChat")}

      -
      -
      -
      -
      -
      -
      Developer Mode
      + +
      +
      ${ytproT("developerMode")}


      -

      Disclaimer: This is an educational project aimed at showcasing javascript injection into a webview to enhance productivity.
      -You can find the source code at https://github.com/prateek-chaubey/YTPRO +

      ${ytproT("disclaimer")}: ${ytproT("disclaimerText")}
      +${ytproT("sourceCode")} https://github.com/prateek-chaubey/YTPRO




      @@ -815,7 +913,7 @@ ${localStorage.getItem("prompt")}
      -Made with +${ytproT("madeWith")} by Prateek Chaubey +${ytproT("by")}
      `; @@ -866,6 +964,11 @@ var actionsList={ sponsor:()=>{ Android.oplink('https://github.com/sponsors/prateek-chaubey'); }, + toggleLang:()=>{ + localStorage.setItem("ytproLang", ytproLang() == "zh" ? "en" : "zh"); + Android.showToast(ytproT("languageChanged")); + window.location.reload(); + }, savePrompt:(el)=>{ localStorage.setItem('prompt',el.previousElementSibling.value);el.parentElement.style.display='none'; }, @@ -947,16 +1050,104 @@ a.click(); } } +function getVideoIdFromUrl(){ +if(window.location.pathname.indexOf("shorts") > -1){ +return window.location.pathname.replace("/shorts/",""); +} +return new URLSearchParams(window.location.search).get("v"); +} + +function openOriginalComments(){ +var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; +for(var i = 0; i < selectors.length; i++){ +var el = document.querySelector(selectors[i]); +if(el){ +el.scrollIntoView({behavior:"smooth", block:"start"}); +return true; +} +} + +var anchors = Array.from(document.querySelectorAll('a')); +for(var j = 0; j < anchors.length; j++){ +var href = anchors[j].href || ""; +if(href.indexOf("comment") > -1 || href.indexOf("replies") > -1){ +anchors[j].click(); +return true; +} +} +return false; +} + +function ensureCommentButton(){ +if(window.location.href.indexOf("youtube.com/watch") < 0 && window.location.href.indexOf("youtube.com/shorts") < 0) return; +if(document.getElementById("ytproCommentsBtn") != null) return; +var host = document.getElementById('ytproMainDivE'); +if(!host || !host.querySelector("div")) return; +var btn = document.createElement("div"); +sty(btn); +btn.id = "ytproCommentsBtn"; +btn.style.width = "110px"; +btn.innerHTML = `${ytproT("comments")}`; +btn.addEventListener("click", ytproCommentsPanel); +host.querySelector("div").appendChild(btn); +} + +function ytproCommentsPanel(){ +var existing = document.getElementById("ytproCommentsDiv"); +if(existing){ existing.remove(); } + +if(!/youtube\.com\/(watch|shorts)/.test(window.location.href)){ +Android.showToast(ytproT("commentsOnlyWatch")); +return; +} + +if(openOriginalComments()){ +Android.showToast(ytproT("originalCommentsOpened")); +return; +} + +var comments = document.createElement("div"); +comments.id = "ytproCommentsDiv"; +comments.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; + +var inner = document.createElement("div"); +inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:auto;padding:12px;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; + +var vid = getVideoIdFromUrl(); +inner.innerHTML = '
      ' + + '' + ytproT("comments") + '' + + '' + + '
      ' + + '
      Loading...
      ' + + '
      ' + (vid ? vid : "") + '
      '; + +comments.addEventListener("click", function(ev){ +if(ev.target === comments){ comments.remove(); } +var btn = ev.target.closest("[data-action]"); +if(btn && btn.dataset.action === "closeComments"){ comments.remove(); } +}); + +comments.appendChild(inner); +document.body.appendChild(comments); + +setTimeout(function(){ +document.getElementById("ytproCommentsBody").innerHTML = ytproT("commentsUnavailable") + '

      '; +document.getElementById("ytproCommentsBody").querySelector("[data-action='openNativeComments']").addEventListener("click", function(){ + Android.oplink("https://m.youtube.com/watch?v=" + (vid || "")); +}); +}, 50); +} + function checkUpdates(){ if(parseFloat(Android.getInfo()) < parseFloat(YTProVer) ){ updateModel(); }else{ -Android.showToast("Your app is up to date"); +Android.showToast(ytproT("upToDate")); } -fetch('https://youtube.com/ytpro_cdn/npm/ytpro', {cache: 'reload'}); -fetch('https://youtube.com/ytpro_cdn/npm/ytpro/bgplay.js', {cache: 'reload'}); -fetch('https://youtube.com/ytpro_cdn/npm/ytpro/innertube.js', {cache: 'reload'}); +fetch('https://youtube.com/ytpro_local/script.js', {cache: 'reload'}); +fetch('https://youtube.com/ytpro_local/bgplay.js', {cache: 'reload'}); +fetch('https://youtube.com/ytpro_local/innertube.js', {cache: 'reload'}); } @@ -1862,9 +2053,9 @@ geminiInfo(); var ytproFavElem=document.createElement("div"); sty(ytproFavElem); if(!isHeart()){ -ytproFavElem.innerHTML=`Heart`; +ytproFavElem.innerHTML=`${ytproT("heart")}`; }else{ -ytproFavElem.innerHTML=`Heart`; +ytproFavElem.innerHTML=`${ytproT("heart")}`; } ytproMainDiv.appendChild(ytproFavElem); ytproFavElem.addEventListener("click",()=>{ytProHeart(ytproFavElem);}); @@ -1875,7 +2066,7 @@ ytproFavElem.addEventListener("click",()=>{ytProHeart(ytproFavElem);}); var ytproDownVidElem=document.createElement("div"); sty(ytproDownVidElem); ytproDownVidElem.style.width="140px"; -ytproDownVidElem.innerHTML=`${downBtn.replace('width="18"','width="24"').replace('height="18"','height="24"')}Download`; +ytproDownVidElem.innerHTML=`${downBtn.replace('width="18"','width="24"').replace('height="18"','height="24"')}${ytproT("download")}`; ytproMainDiv.appendChild(ytproDownVidElem); ytproDownVidElem.addEventListener("click", function(){ @@ -1886,7 +2077,7 @@ window.location.hash="download"; var ytproPIPVidElem=document.createElement("div"); sty(ytproPIPVidElem); ytproPIPVidElem.style.width="140px"; -ytproPIPVidElem.innerHTML=`PIP Mode`; +ytproPIPVidElem.innerHTML=`${ytproT("pipMode")}`; ytproMainDiv.appendChild(ytproPIPVidElem); ytproPIPVidElem.addEventListener("click", function(){ @@ -2300,6 +2491,7 @@ window.onhashchange=()=>{ try{document.getElementById("outerdownytprodiv").remove();}catch{} try{document.getElementById("outerheartsdiv").remove();}catch{} try{document.getElementById("settingsprodiv").remove();}catch{} +try{document.getElementById("ytproCommentsDiv").remove();}catch{} //try{document.querySelector("#ytproDownloadIndicator").remove();}catch{} //try{document.querySelector("#ytProDownloaderDiv").remove();}catch{} if(window.location.hash == "#download"){ @@ -2309,6 +2501,8 @@ ytproSettings(); } else if(window.location.hash == "#hearts"){ showHearts(); +}else if(window.location.hash == "#comments"){ +ytproCommentsPanel(); } @@ -2617,6 +2811,7 @@ addMaxButton(); //settingsTab addSettingsTab(); +ensureCommentButton(); try{ From f64d09cbf6f27ad15e1d4c70f6b6adb6582daf59 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 19:06:20 +0800 Subject: [PATCH 02/11] Enable APK build workflow for feature branch --- .github/workflows/gradle.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 0396f5b4..82b90e73 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,8 +1,9 @@ name: Gradle on: + workflow_dispatch: push: - branches: [ main ] + branches: [ main, codex-zh-comments ] pull_request: branches: [ main ] From 2d781fa68ba850fa2e56324ecad485fcc2c2e75e Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 19:38:54 +0800 Subject: [PATCH 03/11] Add live chat panel for livestreams --- app/src/main/assets/ytpro/script.js | 68 ++++++++++++++++++++++++++++- scripts/script.js | 68 ++++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js index c8c46a58..74f58fe5 100644 --- a/app/src/main/assets/ytpro/script.js +++ b/app/src/main/assets/ytpro/script.js @@ -62,6 +62,9 @@ var YTProLocales = { commentsOnlyWatch: "Comments are available on video pages", openYouTubeComments: "Open YouTube comments", originalCommentsOpened: "Opened original YouTube comments.", + liveChat: "Live chat", + openLiveChat: "Open live chat", + liveChatUnavailable: "Live chat could not be loaded for this video", download: "Download", heart: "Heart", pipMode: "PIP Mode", @@ -101,6 +104,9 @@ var YTProLocales = { commentsOnlyWatch: "评论区仅在视频页面可用", openYouTubeComments: "打开 YouTube 评论", originalCommentsOpened: "已打开 YouTube 原生评论区。", + liveChat: "直播聊天", + openLiveChat: "打开直播聊天", + liveChatUnavailable: "当前视频无法加载直播聊天", download: "下载", heart: "收藏", pipMode: "画中画", @@ -1057,6 +1063,61 @@ return window.location.pathname.replace("/shorts/",""); return new URLSearchParams(window.location.search).get("v"); } +function isLiveVideoPage(){ +try{ +if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; +}catch{} +if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; +return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ +var url = el.href || el.src || ""; +return url.indexOf("live_chat") > -1; +}); +} + +function findLiveChatUrl(){ +var existing = Array.from(document.querySelectorAll("a, iframe")).map(function(el){ return el.href || el.src || ""; }).find(function(url){ return url.indexOf("live_chat") > -1; }); +if(existing) return existing; +var vid = getVideoIdFromUrl(); +if(!vid) return ""; +return "https://www.youtube.com/live_chat?v=" + encodeURIComponent(vid) + "&is_popout=1"; +} + +function openLiveChatPanel(){ +var vid = getVideoIdFromUrl(); +var liveChatUrl = findLiveChatUrl(); +if(!vid || !liveChatUrl){ +Android.showToast(ytproT("liveChatUnavailable")); +return false; +} + +var existing = document.getElementById("ytproLiveChatDiv"); +if(existing){ existing.remove(); } + +var chat = document.createElement("div"); +chat.id = "ytproLiveChatDiv"; +chat.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; + +var inner = document.createElement("div"); +inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:hidden;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; +inner.innerHTML = '
      ' + + '' + ytproT("liveChat") + '' + + '' + + '' + + '
      ' + + ''; + +chat.addEventListener("click", function(ev){ +if(ev.target === chat){ chat.remove(); } +var btn = ev.target.closest("[data-action]"); +if(btn && btn.dataset.action === "closeLiveChat"){ chat.remove(); } +if(btn && btn.dataset.action === "openLiveChatExternal"){ Android.oplink(liveChatUrl); } +}); + +chat.appendChild(inner); +document.body.appendChild(chat); +return true; +} + function openOriginalComments(){ var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; for(var i = 0; i < selectors.length; i++){ @@ -1087,7 +1148,7 @@ var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${ytproT("comments")}`; +btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } @@ -1101,6 +1162,10 @@ Android.showToast(ytproT("commentsOnlyWatch")); return; } +if(isLiveVideoPage() && openLiveChatPanel()){ +return; +} + if(openOriginalComments()){ Android.showToast(ytproT("originalCommentsOpened")); return; @@ -2492,6 +2557,7 @@ try{document.getElementById("outerdownytprodiv").remove();}catch{} try{document.getElementById("outerheartsdiv").remove();}catch{} try{document.getElementById("settingsprodiv").remove();}catch{} try{document.getElementById("ytproCommentsDiv").remove();}catch{} +try{document.getElementById("ytproLiveChatDiv").remove();}catch{} //try{document.querySelector("#ytproDownloadIndicator").remove();}catch{} //try{document.querySelector("#ytProDownloaderDiv").remove();}catch{} if(window.location.hash == "#download"){ diff --git a/scripts/script.js b/scripts/script.js index c8c46a58..74f58fe5 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -62,6 +62,9 @@ var YTProLocales = { commentsOnlyWatch: "Comments are available on video pages", openYouTubeComments: "Open YouTube comments", originalCommentsOpened: "Opened original YouTube comments.", + liveChat: "Live chat", + openLiveChat: "Open live chat", + liveChatUnavailable: "Live chat could not be loaded for this video", download: "Download", heart: "Heart", pipMode: "PIP Mode", @@ -101,6 +104,9 @@ var YTProLocales = { commentsOnlyWatch: "评论区仅在视频页面可用", openYouTubeComments: "打开 YouTube 评论", originalCommentsOpened: "已打开 YouTube 原生评论区。", + liveChat: "直播聊天", + openLiveChat: "打开直播聊天", + liveChatUnavailable: "当前视频无法加载直播聊天", download: "下载", heart: "收藏", pipMode: "画中画", @@ -1057,6 +1063,61 @@ return window.location.pathname.replace("/shorts/",""); return new URLSearchParams(window.location.search).get("v"); } +function isLiveVideoPage(){ +try{ +if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; +}catch{} +if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; +return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ +var url = el.href || el.src || ""; +return url.indexOf("live_chat") > -1; +}); +} + +function findLiveChatUrl(){ +var existing = Array.from(document.querySelectorAll("a, iframe")).map(function(el){ return el.href || el.src || ""; }).find(function(url){ return url.indexOf("live_chat") > -1; }); +if(existing) return existing; +var vid = getVideoIdFromUrl(); +if(!vid) return ""; +return "https://www.youtube.com/live_chat?v=" + encodeURIComponent(vid) + "&is_popout=1"; +} + +function openLiveChatPanel(){ +var vid = getVideoIdFromUrl(); +var liveChatUrl = findLiveChatUrl(); +if(!vid || !liveChatUrl){ +Android.showToast(ytproT("liveChatUnavailable")); +return false; +} + +var existing = document.getElementById("ytproLiveChatDiv"); +if(existing){ existing.remove(); } + +var chat = document.createElement("div"); +chat.id = "ytproLiveChatDiv"; +chat.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; + +var inner = document.createElement("div"); +inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:hidden;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; +inner.innerHTML = '
      ' + + '' + ytproT("liveChat") + '' + + '' + + '' + + '
      ' + + ''; + +chat.addEventListener("click", function(ev){ +if(ev.target === chat){ chat.remove(); } +var btn = ev.target.closest("[data-action]"); +if(btn && btn.dataset.action === "closeLiveChat"){ chat.remove(); } +if(btn && btn.dataset.action === "openLiveChatExternal"){ Android.oplink(liveChatUrl); } +}); + +chat.appendChild(inner); +document.body.appendChild(chat); +return true; +} + function openOriginalComments(){ var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; for(var i = 0; i < selectors.length; i++){ @@ -1087,7 +1148,7 @@ var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${ytproT("comments")}`; +btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } @@ -1101,6 +1162,10 @@ Android.showToast(ytproT("commentsOnlyWatch")); return; } +if(isLiveVideoPage() && openLiveChatPanel()){ +return; +} + if(openOriginalComments()){ Android.showToast(ytproT("originalCommentsOpened")); return; @@ -2492,6 +2557,7 @@ try{document.getElementById("outerdownytprodiv").remove();}catch{} try{document.getElementById("outerheartsdiv").remove();}catch{} try{document.getElementById("settingsprodiv").remove();}catch{} try{document.getElementById("ytproCommentsDiv").remove();}catch{} +try{document.getElementById("ytproLiveChatDiv").remove();}catch{} //try{document.querySelector("#ytproDownloadIndicator").remove();}catch{} //try{document.querySelector("#ytProDownloaderDiv").remove();}catch{} if(window.location.hash == "#download"){ From b05f3f3d897a5757faff6a06ac8072a6687e8c45 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 19:52:19 +0800 Subject: [PATCH 04/11] Improve live chat detection --- app/src/main/assets/ytpro/script.js | 22 ++++++++++++++++++---- scripts/script.js | 22 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js index 74f58fe5..a0e99dc7 100644 --- a/app/src/main/assets/ytpro/script.js +++ b/app/src/main/assets/ytpro/script.js @@ -1067,7 +1067,15 @@ function isLiveVideoPage(){ try{ if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; }catch{} -if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; +if(document.querySelector('meta[itemprop="isLiveBroadcast"][content="True"], meta[itemprop="isLiveBroadcast"][content="true"], ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer')) return true; +try{ +var pageData = Array.from(document.scripts).map(function(script){ return script.textContent || ""; }).join("\n"); +if(pageData.indexOf('"isLiveContent":true') > -1 || pageData.indexOf('"isLive":true') > -1 || pageData.indexOf('liveChatRenderer') > -1 || pageData.indexOf('liveChatEndpoint') > -1) return true; +}catch{} +try{ +var watchText = (document.querySelector("ytm-watch") || document.body).innerText || ""; +if(/\bLIVE\b|Live chat|Top chat|直播聊天|实时聊天|正在直播|直播中/.test(watchText)) return true; +}catch{} return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ var url = el.href || el.src || ""; return url.indexOf("live_chat") > -1; @@ -1119,7 +1127,7 @@ return true; } function openOriginalComments(){ -var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; +var selectors = ["ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytm-comment-section-renderer ytm-item-section-renderer"]; for(var i = 0; i < selectors.length; i++){ var el = document.querySelector(selectors[i]); if(el){ @@ -1141,14 +1149,20 @@ return false; function ensureCommentButton(){ if(window.location.href.indexOf("youtube.com/watch") < 0 && window.location.href.indexOf("youtube.com/shorts") < 0) return; -if(document.getElementById("ytproCommentsBtn") != null) return; +var existing = document.getElementById("ytproCommentsBtn"); +var label = ytproT(isLiveVideoPage() ? "liveChat" : "comments"); +if(existing != null){ +var span = existing.querySelector("span"); +if(span) span.textContent = label; +return; +} var host = document.getElementById('ytproMainDivE'); if(!host || !host.querySelector("div")) return; var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; +btn.innerHTML = `${label}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } diff --git a/scripts/script.js b/scripts/script.js index 74f58fe5..a0e99dc7 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -1067,7 +1067,15 @@ function isLiveVideoPage(){ try{ if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; }catch{} -if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; +if(document.querySelector('meta[itemprop="isLiveBroadcast"][content="True"], meta[itemprop="isLiveBroadcast"][content="true"], ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer')) return true; +try{ +var pageData = Array.from(document.scripts).map(function(script){ return script.textContent || ""; }).join("\n"); +if(pageData.indexOf('"isLiveContent":true') > -1 || pageData.indexOf('"isLive":true') > -1 || pageData.indexOf('liveChatRenderer') > -1 || pageData.indexOf('liveChatEndpoint') > -1) return true; +}catch{} +try{ +var watchText = (document.querySelector("ytm-watch") || document.body).innerText || ""; +if(/\bLIVE\b|Live chat|Top chat|直播聊天|实时聊天|正在直播|直播中/.test(watchText)) return true; +}catch{} return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ var url = el.href || el.src || ""; return url.indexOf("live_chat") > -1; @@ -1119,7 +1127,7 @@ return true; } function openOriginalComments(){ -var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; +var selectors = ["ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytm-comment-section-renderer ytm-item-section-renderer"]; for(var i = 0; i < selectors.length; i++){ var el = document.querySelector(selectors[i]); if(el){ @@ -1141,14 +1149,20 @@ return false; function ensureCommentButton(){ if(window.location.href.indexOf("youtube.com/watch") < 0 && window.location.href.indexOf("youtube.com/shorts") < 0) return; -if(document.getElementById("ytproCommentsBtn") != null) return; +var existing = document.getElementById("ytproCommentsBtn"); +var label = ytproT(isLiveVideoPage() ? "liveChat" : "comments"); +if(existing != null){ +var span = existing.querySelector("span"); +if(span) span.textContent = label; +return; +} var host = document.getElementById('ytproMainDivE'); if(!host || !host.querySelector("div")) return; var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; +btn.innerHTML = `${label}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } From 86153e87a07b7d1ace3a23a719510fabb6a2f5ae Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 20:29:09 +0800 Subject: [PATCH 05/11] Revert "Improve live chat detection" This reverts commit b05f3f3d897a5757faff6a06ac8072a6687e8c45. --- app/src/main/assets/ytpro/script.js | 22 ++++------------------ scripts/script.js | 22 ++++------------------ 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js index a0e99dc7..74f58fe5 100644 --- a/app/src/main/assets/ytpro/script.js +++ b/app/src/main/assets/ytpro/script.js @@ -1067,15 +1067,7 @@ function isLiveVideoPage(){ try{ if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; }catch{} -if(document.querySelector('meta[itemprop="isLiveBroadcast"][content="True"], meta[itemprop="isLiveBroadcast"][content="true"], ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer')) return true; -try{ -var pageData = Array.from(document.scripts).map(function(script){ return script.textContent || ""; }).join("\n"); -if(pageData.indexOf('"isLiveContent":true') > -1 || pageData.indexOf('"isLive":true') > -1 || pageData.indexOf('liveChatRenderer') > -1 || pageData.indexOf('liveChatEndpoint') > -1) return true; -}catch{} -try{ -var watchText = (document.querySelector("ytm-watch") || document.body).innerText || ""; -if(/\bLIVE\b|Live chat|Top chat|直播聊天|实时聊天|正在直播|直播中/.test(watchText)) return true; -}catch{} +if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ var url = el.href || el.src || ""; return url.indexOf("live_chat") > -1; @@ -1127,7 +1119,7 @@ return true; } function openOriginalComments(){ -var selectors = ["ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytm-comment-section-renderer ytm-item-section-renderer"]; +var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; for(var i = 0; i < selectors.length; i++){ var el = document.querySelector(selectors[i]); if(el){ @@ -1149,20 +1141,14 @@ return false; function ensureCommentButton(){ if(window.location.href.indexOf("youtube.com/watch") < 0 && window.location.href.indexOf("youtube.com/shorts") < 0) return; -var existing = document.getElementById("ytproCommentsBtn"); -var label = ytproT(isLiveVideoPage() ? "liveChat" : "comments"); -if(existing != null){ -var span = existing.querySelector("span"); -if(span) span.textContent = label; -return; -} +if(document.getElementById("ytproCommentsBtn") != null) return; var host = document.getElementById('ytproMainDivE'); if(!host || !host.querySelector("div")) return; var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${label}`; +btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } diff --git a/scripts/script.js b/scripts/script.js index a0e99dc7..74f58fe5 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -1067,15 +1067,7 @@ function isLiveVideoPage(){ try{ if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; }catch{} -if(document.querySelector('meta[itemprop="isLiveBroadcast"][content="True"], meta[itemprop="isLiveBroadcast"][content="true"], ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer')) return true; -try{ -var pageData = Array.from(document.scripts).map(function(script){ return script.textContent || ""; }).join("\n"); -if(pageData.indexOf('"isLiveContent":true') > -1 || pageData.indexOf('"isLive":true') > -1 || pageData.indexOf('liveChatRenderer') > -1 || pageData.indexOf('liveChatEndpoint') > -1) return true; -}catch{} -try{ -var watchText = (document.querySelector("ytm-watch") || document.body).innerText || ""; -if(/\bLIVE\b|Live chat|Top chat|直播聊天|实时聊天|正在直播|直播中/.test(watchText)) return true; -}catch{} +if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ var url = el.href || el.src || ""; return url.indexOf("live_chat") > -1; @@ -1127,7 +1119,7 @@ return true; } function openOriginalComments(){ -var selectors = ["ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytm-comment-section-renderer ytm-item-section-renderer"]; +var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; for(var i = 0; i < selectors.length; i++){ var el = document.querySelector(selectors[i]); if(el){ @@ -1149,20 +1141,14 @@ return false; function ensureCommentButton(){ if(window.location.href.indexOf("youtube.com/watch") < 0 && window.location.href.indexOf("youtube.com/shorts") < 0) return; -var existing = document.getElementById("ytproCommentsBtn"); -var label = ytproT(isLiveVideoPage() ? "liveChat" : "comments"); -if(existing != null){ -var span = existing.querySelector("span"); -if(span) span.textContent = label; -return; -} +if(document.getElementById("ytproCommentsBtn") != null) return; var host = document.getElementById('ytproMainDivE'); if(!host || !host.querySelector("div")) return; var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${label}`; +btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } From 33be9dd2a4ad5a11c88863db2a1d3e5e96abf573 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 20:29:09 +0800 Subject: [PATCH 06/11] Revert "Add live chat panel for livestreams" This reverts commit 2d781fa68ba850fa2e56324ecad485fcc2c2e75e. --- app/src/main/assets/ytpro/script.js | 68 +---------------------------- scripts/script.js | 68 +---------------------------- 2 files changed, 2 insertions(+), 134 deletions(-) diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js index 74f58fe5..c8c46a58 100644 --- a/app/src/main/assets/ytpro/script.js +++ b/app/src/main/assets/ytpro/script.js @@ -62,9 +62,6 @@ var YTProLocales = { commentsOnlyWatch: "Comments are available on video pages", openYouTubeComments: "Open YouTube comments", originalCommentsOpened: "Opened original YouTube comments.", - liveChat: "Live chat", - openLiveChat: "Open live chat", - liveChatUnavailable: "Live chat could not be loaded for this video", download: "Download", heart: "Heart", pipMode: "PIP Mode", @@ -104,9 +101,6 @@ var YTProLocales = { commentsOnlyWatch: "评论区仅在视频页面可用", openYouTubeComments: "打开 YouTube 评论", originalCommentsOpened: "已打开 YouTube 原生评论区。", - liveChat: "直播聊天", - openLiveChat: "打开直播聊天", - liveChatUnavailable: "当前视频无法加载直播聊天", download: "下载", heart: "收藏", pipMode: "画中画", @@ -1063,61 +1057,6 @@ return window.location.pathname.replace("/shorts/",""); return new URLSearchParams(window.location.search).get("v"); } -function isLiveVideoPage(){ -try{ -if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; -}catch{} -if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; -return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ -var url = el.href || el.src || ""; -return url.indexOf("live_chat") > -1; -}); -} - -function findLiveChatUrl(){ -var existing = Array.from(document.querySelectorAll("a, iframe")).map(function(el){ return el.href || el.src || ""; }).find(function(url){ return url.indexOf("live_chat") > -1; }); -if(existing) return existing; -var vid = getVideoIdFromUrl(); -if(!vid) return ""; -return "https://www.youtube.com/live_chat?v=" + encodeURIComponent(vid) + "&is_popout=1"; -} - -function openLiveChatPanel(){ -var vid = getVideoIdFromUrl(); -var liveChatUrl = findLiveChatUrl(); -if(!vid || !liveChatUrl){ -Android.showToast(ytproT("liveChatUnavailable")); -return false; -} - -var existing = document.getElementById("ytproLiveChatDiv"); -if(existing){ existing.remove(); } - -var chat = document.createElement("div"); -chat.id = "ytproLiveChatDiv"; -chat.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; - -var inner = document.createElement("div"); -inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:hidden;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; -inner.innerHTML = '
      ' + - '' + ytproT("liveChat") + '' + - '' + - '' + - '
      ' + - ''; - -chat.addEventListener("click", function(ev){ -if(ev.target === chat){ chat.remove(); } -var btn = ev.target.closest("[data-action]"); -if(btn && btn.dataset.action === "closeLiveChat"){ chat.remove(); } -if(btn && btn.dataset.action === "openLiveChatExternal"){ Android.oplink(liveChatUrl); } -}); - -chat.appendChild(inner); -document.body.appendChild(chat); -return true; -} - function openOriginalComments(){ var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; for(var i = 0; i < selectors.length; i++){ @@ -1148,7 +1087,7 @@ var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; +btn.innerHTML = `${ytproT("comments")}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } @@ -1162,10 +1101,6 @@ Android.showToast(ytproT("commentsOnlyWatch")); return; } -if(isLiveVideoPage() && openLiveChatPanel()){ -return; -} - if(openOriginalComments()){ Android.showToast(ytproT("originalCommentsOpened")); return; @@ -2557,7 +2492,6 @@ try{document.getElementById("outerdownytprodiv").remove();}catch{} try{document.getElementById("outerheartsdiv").remove();}catch{} try{document.getElementById("settingsprodiv").remove();}catch{} try{document.getElementById("ytproCommentsDiv").remove();}catch{} -try{document.getElementById("ytproLiveChatDiv").remove();}catch{} //try{document.querySelector("#ytproDownloadIndicator").remove();}catch{} //try{document.querySelector("#ytProDownloaderDiv").remove();}catch{} if(window.location.hash == "#download"){ diff --git a/scripts/script.js b/scripts/script.js index 74f58fe5..c8c46a58 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -62,9 +62,6 @@ var YTProLocales = { commentsOnlyWatch: "Comments are available on video pages", openYouTubeComments: "Open YouTube comments", originalCommentsOpened: "Opened original YouTube comments.", - liveChat: "Live chat", - openLiveChat: "Open live chat", - liveChatUnavailable: "Live chat could not be loaded for this video", download: "Download", heart: "Heart", pipMode: "PIP Mode", @@ -104,9 +101,6 @@ var YTProLocales = { commentsOnlyWatch: "评论区仅在视频页面可用", openYouTubeComments: "打开 YouTube 评论", originalCommentsOpened: "已打开 YouTube 原生评论区。", - liveChat: "直播聊天", - openLiveChat: "打开直播聊天", - liveChatUnavailable: "当前视频无法加载直播聊天", download: "下载", heart: "收藏", pipMode: "画中画", @@ -1063,61 +1057,6 @@ return window.location.pathname.replace("/shorts/",""); return new URLSearchParams(window.location.search).get("v"); } -function isLiveVideoPage(){ -try{ -if(window.ytInitialPlayerResponse?.videoDetails?.isLive || window.ytInitialPlayerResponse?.videoDetails?.isLiveContent) return true; -}catch{} -if(document.querySelector("ytm-live-chat-renderer, ytd-live-chat-frame, ytm-live-chat-header-renderer")) return true; -return Array.from(document.querySelectorAll("a, iframe")).some(function(el){ -var url = el.href || el.src || ""; -return url.indexOf("live_chat") > -1; -}); -} - -function findLiveChatUrl(){ -var existing = Array.from(document.querySelectorAll("a, iframe")).map(function(el){ return el.href || el.src || ""; }).find(function(url){ return url.indexOf("live_chat") > -1; }); -if(existing) return existing; -var vid = getVideoIdFromUrl(); -if(!vid) return ""; -return "https://www.youtube.com/live_chat?v=" + encodeURIComponent(vid) + "&is_popout=1"; -} - -function openLiveChatPanel(){ -var vid = getVideoIdFromUrl(); -var liveChatUrl = findLiveChatUrl(); -if(!vid || !liveChatUrl){ -Android.showToast(ytproT("liveChatUnavailable")); -return false; -} - -var existing = document.getElementById("ytproLiveChatDiv"); -if(existing){ existing.remove(); } - -var chat = document.createElement("div"); -chat.id = "ytproLiveChatDiv"; -chat.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; - -var inner = document.createElement("div"); -inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:hidden;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; -inner.innerHTML = '
      ' + - '' + ytproT("liveChat") + '' + - '' + - '' + - '
      ' + - ''; - -chat.addEventListener("click", function(ev){ -if(ev.target === chat){ chat.remove(); } -var btn = ev.target.closest("[data-action]"); -if(btn && btn.dataset.action === "closeLiveChat"){ chat.remove(); } -if(btn && btn.dataset.action === "openLiveChatExternal"){ Android.oplink(liveChatUrl); } -}); - -chat.appendChild(inner); -document.body.appendChild(chat); -return true; -} - function openOriginalComments(){ var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; for(var i = 0; i < selectors.length; i++){ @@ -1148,7 +1087,7 @@ var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "110px"; -btn.innerHTML = `${ytproT(isLiveVideoPage() ? "liveChat" : "comments")}`; +btn.innerHTML = `${ytproT("comments")}`; btn.addEventListener("click", ytproCommentsPanel); host.querySelector("div").appendChild(btn); } @@ -1162,10 +1101,6 @@ Android.showToast(ytproT("commentsOnlyWatch")); return; } -if(isLiveVideoPage() && openLiveChatPanel()){ -return; -} - if(openOriginalComments()){ Android.showToast(ytproT("originalCommentsOpened")); return; @@ -2557,7 +2492,6 @@ try{document.getElementById("outerdownytprodiv").remove();}catch{} try{document.getElementById("outerheartsdiv").remove();}catch{} try{document.getElementById("settingsprodiv").remove();}catch{} try{document.getElementById("ytproCommentsDiv").remove();}catch{} -try{document.getElementById("ytproLiveChatDiv").remove();}catch{} //try{document.querySelector("#ytproDownloadIndicator").remove();}catch{} //try{document.querySelector("#ytProDownloaderDiv").remove();}catch{} if(window.location.hash == "#download"){ From 2f4c5873354872e53b278500d62fc89737e64f43 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 21:03:01 +0800 Subject: [PATCH 07/11] Make comments shortcut easier to reach --- app/src/main/assets/ytpro/script.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js index c8c46a58..0634cfbb 100644 --- a/app/src/main/assets/ytpro/script.js +++ b/app/src/main/assets/ytpro/script.js @@ -1086,10 +1086,16 @@ if(!host || !host.querySelector("div")) return; var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; -btn.style.width = "110px"; +btn.style.width = "96px"; btn.innerHTML = `${ytproT("comments")}`; btn.addEventListener("click", ytproCommentsPanel); -host.querySelector("div").appendChild(btn); +var toolbar = host.querySelector("div"); +var anchor = toolbar.children.length > 1 ? toolbar.children[1] : null; +if(anchor){ +toolbar.insertBefore(btn, anchor); +}else{ +toolbar.appendChild(btn); +} } function ytproCommentsPanel(){ @@ -1928,7 +1934,7 @@ insertAfter(document.getElementsByClassName('slim-video-action-bar-actions')[0], var ytproMainDiv=document.createElement("div"); ytproMainDiv.setAttribute("style",` height:50px;width:100%;display:flex;overflow:auto; -align-items:center;justify-content:center;padding-left:20px;padding-right:10px; +align-items:center;justify-content:flex-start;padding-left:20px;padding-right:10px; `); ytproMainDivA.appendChild(ytproMainDiv); From b08b84708cd00dd573fdba28521493f8a59707d9 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 21:19:47 +0800 Subject: [PATCH 08/11] Open native comments from shortcut --- app/src/main/assets/ytpro/script.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js index 0634cfbb..9f638131 100644 --- a/app/src/main/assets/ytpro/script.js +++ b/app/src/main/assets/ytpro/script.js @@ -1058,11 +1058,25 @@ return new URLSearchParams(window.location.search).get("v"); } function openOriginalComments(){ -var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; +var selectors = ["ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytd-comments-header-renderer", "ytd-comments"]; for(var i = 0; i < selectors.length; i++){ var el = document.querySelector(selectors[i]); if(el){ -el.scrollIntoView({behavior:"smooth", block:"start"}); +el.scrollIntoView({behavior:"smooth", block:"center"}); +var clickable = el.querySelector("button, a, [role='button']") || el; +setTimeout(function(target, fallback){ +try{ target.click(); }catch(e){ try{ fallback.click(); }catch(_){} } +}, 120, clickable, el); +return true; +} +} + +var sections = Array.from(document.querySelectorAll("ytm-item-section-renderer")); +for(var k = 0; k < sections.length; k++){ +var text = sections[k].innerText || ""; +if(text.indexOf(ytproT("comments")) > -1 || text.indexOf("Comments") > -1 || text.indexOf("评论") > -1){ +sections[k].scrollIntoView({behavior:"smooth", block:"center"}); +setTimeout(function(target){ try{ target.click(); }catch(e){} }, 120, sections[k]); return true; } } From 5f244d4ab1d752ded2de17d2299ef16a89939e7b Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 21:53:32 +0800 Subject: [PATCH 09/11] Use CDN scripts for localization changes --- .github/workflows/gradle.yml | 3 +- app/src/main/assets/ytpro/bgplay.js | 248 -- app/src/main/assets/ytpro/innertube.js | 1160 ------- app/src/main/assets/ytpro/script.js | 2946 ----------------- .../pro/webview/YTProWebViewClient.java | 29 +- scripts/script.js | 44 +- 6 files changed, 43 insertions(+), 4387 deletions(-) delete mode 100644 app/src/main/assets/ytpro/bgplay.js delete mode 100644 app/src/main/assets/ytpro/innertube.js delete mode 100644 app/src/main/assets/ytpro/script.js diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 82b90e73..0396f5b4 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,9 +1,8 @@ name: Gradle on: - workflow_dispatch: push: - branches: [ main, codex-zh-comments ] + branches: [ main ] pull_request: branches: [ main ] diff --git a/app/src/main/assets/ytpro/bgplay.js b/app/src/main/assets/ytpro/bgplay.js deleted file mode 100644 index 4feaafe9..00000000 --- a/app/src/main/assets/ytpro/bgplay.js +++ /dev/null @@ -1,248 +0,0 @@ -/*****YTPRO******* -Author: Prateek Chaubey -Version: 3.9.2 -URI: https://github.com/prateek-chaubey/YTPRO -*/ - -if (typeof MediaMetadata === 'undefined') { -window.MediaMetadata = class { -constructor(data = {}) { -this.title = data.title || ''; -this.artist = data.artist || ''; -this.album = data.album || ''; -this.artwork = data.artwork || []; -} -}; - -} - - - -if (!('mediaSession' in navigator)) { - -window.handlers = {}; -window.serviceRunning=false; - - - - -let _state = 'none'; -let _metadata = null; - -Object.defineProperty(navigator, 'mediaSession', { -value: {}, -configurable: true -}); - -Object.defineProperty(navigator.mediaSession, 'metadata', { -get() { -return _metadata; -}, -set(value) { -//console.log("metadata set:", value); -bgPlay(value); -_metadata = value; -}, -configurable: true -}); - - - - -navigator.mediaSession.setActionHandler = (action, handler) => { - -if (typeof handler === 'function') { -handlers[action] = handler; -} - -//console.log(action,handler) - - -}; - - - - - - -Object.defineProperty(navigator.mediaSession, 'playbackState', { -get() { -return _state; -}, -set(value) { - -//console.log("Custom playbackState set to:", value); - - -_state = value; - - -var ytproAud = document.getElementsByClassName('video-stream')[0]; - -if (value === 'playing') { -setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100); -} else if (value === 'paused' && (pauseAllowed || PIPause)) { -setTimeout(()=>{Android.bgPause(ytproAud.currentTime*1000);},100); -}else if(value === "none" && !(window.location.href.indexOf("youtube.com/watch") > -1 || window.location.href.indexOf("youtube.com/shorts") > -1 )){ -Android.bgStop(); -window.serviceRunning=false; -} - - - -}, -configurable: true -}); - - - -} - - - - - - - -async function bgPlay(info){ - - -if(!(window.location.href.indexOf("youtube.com/watch") > -1 || window.location.href.indexOf("youtube.com/shorts") > -1 )) return; - - -if(!info) return; - - -var ytproAud = document.getElementsByClassName('video-stream')[0]; - - -if(!ytproAud) return; - - -var iconBase64="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="; - - -var img = new Image(); -img.crossOrigin="anonymous"; -img.src=info?.artwork?.[0]?.src; - - -var canvas = document.createElement('canvas'); -canvas.style.width = "1600px"; -canvas.style.height = "900px"; -canvas.style.background="black"; -var context = canvas.getContext('2d'); - -canvas.width = 160; -canvas.height = 90; - -//var z=performance.now(); - - -await new Promise((res,rej)=>{ -img.onload=()=>res(); -}); - - -try{ -context.drawImage(img, 0,0 ,160,90); -iconBase64 = canvas.toDataURL('image/png',1.0); -}catch{} - - - - - - - - - - - - -if(window.serviceRunning){ -setTimeout(()=>{Android.bgUpdate(iconBase64.replace("data:image/png;base64,", ""),info.title,info.artist,ytproAud.duration*1000);},50); -setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100); -} -else{ -window.serviceRunning=true; -setTimeout(()=>{Android.bgStart(iconBase64.replace("data:image/png;base64,", ""),info.title,info.artist,ytproAud.duration*1000);},50); -setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100); -} - - - - - - - - - - - -} - - - - - - - - - - - - - - - - - - -/*When user hits the notification*/ -function seekTo(t){ -handlers.seekto({ seekTime: t/1000 }); -} - -/*Daamm , its play*/ -function playVideo(){ - - -if(!pauseAllowed){ -window.PIPause = false; -navigator.mediaSession.playbackState = 'playing'; -} - -handlers.play(); -} - -/*Daamm , its pause*/ -function pauseVideo(){ - - - -if(!pauseAllowed){ -window.PIPause=true; -navigator.mediaSession.playbackState = 'paused'; -} -handlers.pause(); - - - - -} - - - -/*Alexa , play da next song*/ -async function playNext(){ -handlers.nexttrack(); -} - - - - -/*Alexa , play the f**ng song once again */ -function playPrev(){ -handlers.previoustrack(); -} diff --git a/app/src/main/assets/ytpro/innertube.js b/app/src/main/assets/ytpro/innertube.js deleted file mode 100644 index 09044194..00000000 --- a/app/src/main/assets/ytpro/innertube.js +++ /dev/null @@ -1,1160 +0,0 @@ -/*****YTPRO******* -Author: Prateek Chaubey -Version: 3.9.8 -URI: https://github.com/prateek-chaubey/YTPRO -Last Updated On: 1 May , 2026 , 19:25 IST -*/ - - - -window.ytproSabrDownload= async function() { - - -var ytproDownDiv=getDownloadElement(); - -ytproDownDiv.querySelector("#videoViewDiv").innerHTML="Loading..."; - - -//Get Video ID -var videoId =""; - -if(window.location.pathname.indexOf("shorts") > -1){ -videoId=window.location.pathname.substr(8,window.location.pathname.length); -} -else{ -videoId=new URLSearchParams(window.location.search).get("v"); -} - - -//videoId="vY31qIX7LzQ"; - - -if (!videoId) { window.Android?.showToast?.('No video ID found in URL.'); return; } - -// Imports -const { Innertube, Platform, Constants } = await import( -'https://cdn.jsdelivr.net/npm/youtubei.js@17.0.1/bundle/browser.min.js' -); -const { SabrStream } = await import('https://esm.sh/googlevideo@4.0.4/sabr-stream'); -const { buildSabrFormat , EnabledTrackTypes } = await import('https://esm.sh/googlevideo@4.0.4/utils'); -const { BG, buildURL, getHeaders } = await import('https://esm.sh/bgutils-js@3.2.0'); - -Platform.shim.eval = async (data, env) => { -const props = []; -if (env.n) props.push(`n: exportedVars.nFunction("${env.n}")`); -if (env.sig) props.push(`sig: exportedVars.sigFunction("${env.sig}")`); -return new Function(`${data.output}\nreturn { ${props.join(', ')} }`)(); -}; - -// Create Innertube (WEB Client Setup & Proxy) -const cookies = window.Android?.getAllCookies?.('https://www.youtube.com') ?? ''; - -const yt = await Innertube.create({ -cookie: cookies, -retrieve_player: true, -generate_session_locally: true, -fetch: async (input, init = {}) => { - - -const reqUrl = input instanceof Request ? input.url : input.toString(); -const url = new URL(reqUrl); -const method = init.method ?? (input instanceof Request ? input.method : 'GET'); -const headers = new Headers(); - -if (input instanceof Request) input.headers.forEach((v, k) => headers.set(k, v)); -if (init.headers) new Headers(init.headers).forEach((v, k) => headers.set(k, v)); - -headers.set('User-Agent', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); -headers.set('Sec-Ch-Ua', '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"'); -headers.set('Sec-Ch-Ua-Mobile', '?0'); -headers.set('Sec-Ch-Ua-Platform', '"Windows"'); - -const playerId = Array.from(document.scripts) -.map(s => s.src.match(/player\/(.*?)\/player/)) -.find(m => m)?.[1] || '4b0d80ee'; - -if (url.pathname === '/iframe_api') { -const mockedApiCode = `var scriptUrl = 'https:\\/\\/www.youtube.com\\/s\\/player\\/${playerId}\\/www-widgetapi.vflset\\/www-widgetapi.js';try{var ttPolicy=window.trustedTypes.createPolicy("youtube-widget-api",{createScriptURL:function(x){return x}});scriptUrl=ttPolicy.createScriptURL(scriptUrl)}catch(e){}var YT;if(!window["YT"])YT={loading:0,loaded:0};var YTConfig;if(!window["YTConfig"])YTConfig={"host":"https://www.youtube.com"};\nif(!YT.loading){YT.loading=1;(function(){var l=[];YT.ready=function(f){if(YT.loaded)f();else l.push(f)};window.onYTReady=function(){YT.loaded=1;var i=0;for(;i]/g, '-'); - -// Fallback size formatter just in case window.formatFileSize isn't ready -const formatBytes = (bytes) => { -if (window.formatFileSize) return window.formatFileSize(bytes); -if (bytes === 0 || isNaN(bytes)) return "Unknown Size"; -const k = 1024; -const sizes = ['Bytes', 'KB', 'MB', 'GB']; -const i = Math.floor(Math.log(bytes) / Math.log(k)); -return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -}; - -// Helper to standardize format objects -const cleanFormat = (f) => { -const durationSec = (f.approxDurationMs || f.approx_duration_ms || info.basic_info.duration * 1000 || 0) / 1000; -const bytes = f.contentLength ? parseInt(f.contentLength) : (f.bitrate ? Math.floor((f.bitrate * durationSec) / 8) : 0); -const mime = f.mimeType || f.mime_type || ""; -const isWebm = mime.includes('webm'); -const isMp4 = mime.includes('mp4'); -const codec = mime.match(/codecs="(.*?)"/)?.[1] || ""; - -return { -itag: f.itag, -mimeType: mime, -container: isWebm ? 'webm' : (isMp4 ? 'mp4' : 'other'), -codec: codec, -qualityLabel: f.qualityLabel || f.quality_label || null, -bitrate: f.bitrate, -width: f.width, -hasVideo: !!f.width, -hasAudio: !!f.audioSampleRate || !!f.audio_sample_rate || mime.startsWith('audio/'), -languageId: f.language || f.audioTrack?.id || f.audio_track?.id || 'default', -languageName: f.audioTrack?.displayName || f.audio_track?.display_name || 'Default', -isDefaultAudio: f.audioTrack?.audioIsDefault || f.audio_track?.audio_is_default || (!f.audioTrack && !f.audio_track), -sizeBytes: bytes, -audioQuality:f.audio_quality || null, -audioTrackId:f.audio_track?.id, -sizeFormatted: formatBytes(bytes) -}; -}; - -// Extract raw lists -const rawFormats = streamingData.formats || []; -const rawAdaptive = streamingData.adaptive_formats || []; - -const preMuxed = rawFormats.map(cleanFormat); -const adaptive = rawAdaptive.map(cleanFormat); - -// Filter adaptive for matching -const videoOnly = adaptive.filter(f => f.hasVideo && !f.hasAudio); -const audioOnly = adaptive.filter(f => f.hasAudio && !f.hasVideo); - -// ── BUILD CATEGORY 2: MUXABLE COMBINATIONS ── -const muxableOptions = []; - -// Get all unique video qualities (e.g., "1080p60", "1080p", "720p") -const uniqueQualities = [...new Set(videoOnly.map(v => v.qualityLabel).filter(Boolean))] -.sort((a, b) => parseInt(b) - parseInt(a)); // Sort High to Low - -// Get all unique audio languages -const uniqueLanguages = []; -const langMap = new Map(); -audioOnly.forEach(a => { -if (!langMap.has(a.languageId)) { -langMap.set(a.languageId, { id: a.languageId, name: a.languageName, isDefault: a.isDefaultAudio }); -uniqueLanguages.push(langMap.get(a.languageId)); -} -}); - -// Create explicit safe pairs -uniqueQualities.forEach(quality => { -// Ban AV1 to protect Android MediaMuxer -const vForQuality = videoOnly.filter(v => v.qualityLabel === quality && !v.codec.includes('av01')); - -// Sort to get highest bitrate video for the container -const mp4Video = vForQuality.filter(v => v.container === 'mp4').sort((a,b) => b.bitrate - a.bitrate)[0]; -const webmVideo = vForQuality.filter(v => v.container === 'webm').sort((a,b) => b.bitrate - a.bitrate)[0]; - -uniqueLanguages.forEach(lang => { -const aForLang = audioOnly.filter(a => a.languageId === lang.id); - -// Sort to get highest bitrate audio for the container -const mp4Audio = aForLang.filter(a => a.container === 'mp4').sort((a,b) => b.bitrate - a.bitrate)[0]; -const webmAudio = aForLang.filter(a => a.container === 'webm').sort((a,b) => b.bitrate - a.bitrate)[0]; - -// Add matching MP4 pair -if (mp4Video && mp4Audio) { -muxableOptions.push({ -type: 'muxable', -qualityLabel: quality, -language: lang.name, -languageId: lang.id, -isDefaultLanguage: lang.isDefault, -container: 'mp4', -totalBytes: mp4Video.sizeBytes + mp4Audio.sizeBytes, -totalSizeFormatted: formatBytes(mp4Video.sizeBytes + mp4Audio.sizeBytes), -videoItag: mp4Video.itag, -audioItag: mp4Audio.itag, -videoDetails: mp4Video, -audioDetails: mp4Audio -}); -} - -// Add matching WebM pair -if (webmVideo && webmAudio) { -muxableOptions.push({ -type: 'muxable', -qualityLabel: quality, -language: lang.name, -languageId: lang.id, -isDefaultLanguage: lang.isDefault, -container: 'webm', -totalBytes: webmVideo.sizeBytes + webmAudio.sizeBytes, -totalSizeFormatted: formatBytes(webmVideo.sizeBytes + webmAudio.sizeBytes), -videoItag: webmVideo.itag, -audioItag: webmAudio.itag, -videoDetails: webmVideo, -audioDetails: webmAudio -}); -} -}); -}); - -// Final Master Object -const ytproMediaData = { -title: info.basic_info.title, -videoId: videoId, -durationSec: info.basic_info.duration || 0, -categories: { -"muxable": muxableOptions, -"audioOnly": audioOnly, -"videoOnly": videoOnly -} -}; - - - -ytproDownDiv.insertAdjacentHTML('beforeend',``); - - - - -ytproDownDiv.querySelector("#videoViewDiv").innerHTML=``; - - -var langList=document.createElement("select"); -langList.setAttribute("id","selectLang") - -uniqueLanguages.forEach(l=>{ -var sl=document.createElement("option"); -sl.textContent=l.name; -sl.value=l.id; -if (l.isDefault === true) { -sl.selected = true; -} -langList.appendChild(sl); -}); - - - -ytproDownDiv.querySelector("#videoViewDiv").appendChild(langList); - - -langList.addEventListener("change",(e)=>{ -updateMuxFormats(e.target.value); -updateAudioOnlyFormats(e.target.value); -}) - - - - - - - - - -//var defaultLangId=uniqueLanguages.filter( arr => { return arr.isDefault;})[0].id; - -var createAndAppend=()=>{ -var div=document.createElement("div"); -ytproDownDiv.querySelector("#videoViewDiv").appendChild(div); -return div; -} - - -var muxedDiv=createAndAppend(); -var audioOnlyDiv=createAndAppend(); -var videoOnlyDiv=createAndAppend(); - - - - -function updateMuxFormats(langId=uniqueLanguages.filter( arr => { return arr.isDefault;})[0].id){ - -muxedDiv.innerHTML=""; - -muxableOptions.forEach(mux =>{ -if(mux.languageId != langId) return; - -var formatLi=document.createElement("li"); -/*formatLi.dataset.audioItag=mux.audioItag; -formatLi.dataset.videoItag=mux.videoItag; -formatLi.dataset.langId=mux.languageId; -formatLi.dataset.isWebm=mux.container == "webm"; -*/ - - -Object.assign(formatLi.dataset,{ -langId:mux.audioDetails.audioTrackId, -isWebm:mux.container == "webm", -audioItag:mux.audioItag, -videoItag:mux.videoItag -}); - - -formatLi.innerHTML=`${downBtn}${mux.qualityLabel} | ${mux.container.toUpperCase()} | ${mux.totalSizeFormatted}`; -muxedDiv.appendChild(formatLi); -}); - - - - -} - - - -function updateAudioOnlyFormats(langId=uniqueLanguages.filter( arr => { return arr.isDefault;})[0].id){ - -audioOnlyDiv.innerHTML=""; - -var formatDivider=document.createElement("li"); - -formatDivider.innerHTML=` -Audio Only (${uniqueLanguages.filter( arr => { return arr.id==langId;})[0].name}) - - - - - -`; -Object.assign(formatDivider.style,{ -minHeight:"20px", -borderRadius:"5px", -background:"#0000" -}) - -audioOnlyDiv.appendChild(formatDivider); - -formatDivider.addEventListener("click",()=>{ -Array.from(formatDivider.parentElement.children).forEach((c,i)=>{ -if(i == 0) { -c.children[1].style.transform = c.children[1].style.transform === "rotate(180deg)" ? "rotate(0deg)" : "rotate(180deg)"; -return; -} -c.style.display = c.style.display === "none" ? "flex" : "none"; -}) -}); - - -audioOnly.forEach(aud =>{ -if(aud.languageId != langId) return; - -var formatLi=document.createElement("li"); -/*formatLi.dataset.audioItag=aud.itag; -formatLi.dataset.isWebm=aud.container == "webm"; -formatLi.dataset.langId=mux.languageId;*/ - -Object.assign(formatLi.dataset,{ -langId:aud.audioTrackId, -isWebm:aud.container == "webm", -audioItag:aud.itag -}); - -formatLi.innerHTML=`${downBtn}${aud.audioQuality.replaceAll("AUDIO_QUALITY_"," ")} | ${aud.sizeFormatted}`; -audioOnlyDiv.appendChild(formatLi); -}); - - - - -} - - - -function updateVideoOnlyFormats(){ - -videoOnlyDiv.innerHTML=""; - -var formatDivider=document.createElement("li"); - -formatDivider.innerHTML=` -Video Only - - - - - -`; -Object.assign(formatDivider.style,{ -minHeight:"20px", -borderRadius:"5px", -background:"#0000" -}) - -videoOnlyDiv.appendChild(formatDivider); - -formatDivider.addEventListener("click",()=>{ -Array.from(formatDivider.parentElement.children).forEach((c,i)=>{ -if(i == 0) { -c.children[1].style.transform = c.children[1].style.transform === "rotate(180deg)" ? "rotate(0deg)" : "rotate(180deg)"; -return; -} -c.style.display = c.style.display === "none" ? "flex" : "none"; -}) -}); - - -videoOnly.forEach(vid =>{ - -var formatLi=document.createElement("li"); -formatLi.dataset.videoItag=vid.itag; -formatLi.dataset.isWebm=vid.container == "webm"; -formatLi.innerHTML=`${downBtn}${vid.qualityLabel} | ${vid.container.toUpperCase()} | ${vid.sizeFormatted}`; -videoOnlyDiv.appendChild(formatLi); -}); - - - - -} - - - - - -function updateThumbnails(){ -var div=ytproDownDiv.querySelector("#thumbViewDiv"); -div.innerHTML=""; - -var thumbs=info.basic_info.thumbnail; - -thumbs.forEach(thumb=>{ -div.innerHTML+=`
    • -
      -${downBtn}${thumb.height} ✕ ${thumb.width} -
    • ` -}) - - -div.addEventListener("click",(e)=>{ -var el=e.target.closest("[data-url]"); -if(!el) return; - -Android.downvid(el.dataset.title,el.dataset.url,"image/jpg"); - -}); -} - - -function updateCaptions(){ -var div=ytproDownDiv.querySelector("#captionsViewDiv"); -div.innerHTML=``; - -var captions=info?.captions?.caption_tracks; - -if(!captions) return div.innerHTML=`No Captions Found`; - -var t=`Captions ${safeTitle} YTPRO`; - -captions.forEach(cap=>{ - -cap.baseUrl = cap.base_url.replace("&fmt=srv3",""); - -div.innerHTML+=` -${cap?.name?.text} -

      -
      -${downBtn}
      .txt
      -${downBtn}
      .srt
      -${downBtn}
      .xml
      -${downBtn}
      .vtt
      -${downBtn}
      .srv1
      ${downBtn}
      .ttml
      -
      -

      -

      `; -}); - - - -div.addEventListener("click",(e)=>{ -var el=e.target.closest("[data-url]"); -if(!el) return; - -Android.downvid(el.dataset.title+el.dataset.ext,el.dataset.url,"plain/text"); - -}); - - -} - - - -/*EVENT LISTENERS**/ -muxedDiv.addEventListener("click",(e)=>{ -var el=e.target.closest("[data-audio-itag]"); -if(!el) return; -downloadSABRStream(el.dataset.videoItag,el.dataset.audioItag,el.dataset.isWebm,el.dataset.langId,EnabledTrackTypes.VIDEO_AND_AUDIO); - - -}); - - - -audioOnlyDiv.addEventListener("click",(e)=>{ -var el=e.target.closest("[data-audio-itag]"); -if(!el) return; -downloadSABRStream(null,el.dataset.audioItag,el.dataset.isWebm,el.dataset.langId,EnabledTrackTypes.AUDIO_ONLY); -}); - - - -videoOnlyDiv.addEventListener("click",(e)=>{ -var el=e.target.closest("[data-video-itag]"); -if(!el) return; -downloadSABRStream(el.dataset.videoItag,null,el.dataset.isWebm,null,EnabledTrackTypes.VIDEO_ONLY); -}); - - - - - - -if(info?.basic_info?.is_live || info?.basic_info?.is_live_content){ - -ytproDownDiv.querySelector("#videoViewDiv").innerHTML="Downloading live streams
      aren't supported at the moment"; -}else{ -updateMuxFormats(); -updateAudioOnlyFormats(); -updateVideoOnlyFormats(); -} -updateThumbnails(); -updateCaptions(); - - - - -// ── 7. Extract SABR URL & Config ────────────────────────────────────────── -async function extractSabrConfig(playerInfo) { -const url = await player.decipher(playerInfo.streaming_data?.server_abr_streaming_url); -const cfg = playerInfo.player_config -?.media_common_config -?.media_ustreamer_request_config -?.video_playback_ustreamer_config; -return { url, cfg }; -} - -const { url: serverAbrUrl, cfg: ustreamerConfig } = await extractSabrConfig(info); -if (!serverAbrUrl || !ustreamerConfig) { -window.Android?.showToast?.('Missing SABR config.'); -return; -} - -const rawUstreamerConfig = typeof ustreamerConfig === 'string' ? ustreamerConfig : JSON.stringify(ustreamerConfig); -const adaptiveFormats = streamingData.adaptive_formats ?? []; -const sabrFormats = adaptiveFormats.map(f => buildSabrFormat(f)); - - - - - - -async function downloadSABRStream(videoItag,audioItag,isWebm,langId,enabledTrack){ - - -if(!Android.isWebViewSupported()){ - Android.showToast("Please Update your WebView."); - return; -} -if(!Android.hasStoragePermission()){ - return; -} - -Android.showToast("Download Started"); - - -const containerExt = isWebm == "true" ? 'webm' : 'mp4'; - -// ── Grab the absolute lowest qualities to feed to the Black Hole - -const lowestAudio = audioOnly.sort((a, b) => (a.bitrate || 0) - (b.bitrate || 0))[0].itag; - -const lowestVideo = adaptiveFormats -.filter(f => f.width) -.sort((a, b) => (a.bitrate || 0) - (b.bitrate || 0))[0].itag; - -const trashSabrAudio = sabrFormats.filter(s=> s.itag==lowestAudio)[0]; -const trashSabrVideo =sabrFormats.filter(s=> s.itag==lowestVideo)[0]; - - -const targetSabrVideo=sabrFormats.filter(s=> s.itag==videoItag)[0] || trashSabrVideo; - -var targetSabrAudio; - -if(langId != "undefined"){ -targetSabrAudio = sabrFormats.filter(s=> s.itag==audioItag && s.audioTrackId == langId)[0] || trashSabrAudio; -}else{ -targetSabrAudio = sabrFormats.filter(s=> s.itag==audioItag)[0] || trashSabrAudio; -} - - -const sabrStream = new SabrStream({ -videoId: videoId, -cpn: info.cpn, -serverAbrStreamingUrl: serverAbrUrl, -videoPlaybackUstreamerConfig: rawUstreamerConfig, -formats: sabrFormats, -poToken: placeholderPoToken ?? undefined, -clientInfo: { -clientName: 1, // WEB -clientVersion: yt.session.context.client.clientVersion, -osName: 'Windows', -osVersion: '10.0', -}, -durationMs: (info.basic_info.duration ?? 0) * 1000, -fetch: async (input, init = {}) => { -const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url; -return fetch(url, { ...init, mode: 'cors', credentials: 'include' }); -}, -}); - -sabrStream.on('reloadPlayerResponse', async () => { -try { -const freshInfo = await yt.getBasicInfo(videoId, { client: 'WEB' }); -const { url: newUrl, cfg: newCfg } = await extractSabrConfig(freshInfo); -if (newUrl) sabrStream.setStreamingURL(newUrl); -if (newCfg) sabrStream.setUstreamerConfig(typeof newCfg === 'string' ? newCfg : JSON.stringify(newCfg)); -} catch (e) {} -}); - -let isTokenApplied = false; -sabrStream.on('streamProtectionStatusUpdate', async (data) => { -if ((data.status === 2 || data.status === 3) && !isTokenApplied) { -isTokenApplied = true; -try { -const fullToken = await fullTokenPromise; -if (fullToken) sabrStream.poToken = fullToken; -} catch (err) {} -} -}); - - -const { videoStream ,audioStream} = await sabrStream.start({ -preferMp4: !isWebm, -preferH264: !isWebm, -videoFormat: () => targetSabrVideo, -audioFormat: () => targetSabrAudio, -enabledTrackTypes:enabledTrack, -}); - -const durationSec = info.basic_info.duration || 0; - - -createDownloaderStatus(); -createDownloaderIndicator(); - -var downloaderDiv=document.querySelector("#ytProDownloaderDiv"); - - - -function createProgreses(streamName){ - -var elProgressBar=document.createElement("div"); -var elProgress=document.createElement("div"); -var elDetails=document.createElement("span"); - -elDetails.className="ytproDetails"; -elProgressBar.className="ytproProgressBar"; -elProgress.className="ytproProgress"; -elProgressBar.appendChild(elProgress); - -elDetails.innerHTML=`${streamName}: ` - -downloaderDiv.appendChild(elDetails) -downloaderDiv.appendChild(elProgressBar); - -return {elDetails,elProgress}; -} - - - -//video only -if(enabledTrack==EnabledTrackTypes.VIDEO_ONLY){ - -const estVideoBytes = targetSabrVideo.contentLength || (targetSabrVideo.bitrate ? Math.floor((targetSabrVideo.bitrate * durationSec) / 8) : 0); - - -downloaderDiv.insertAdjacentHTML("beforeend",` -

      Title: ${safeTitle}
      `) - -var fileName=`${safeTitle}_video${new Date().getTime()}.${containerExt}`; - -var {elDetails,elProgress} = createProgreses("Video Stream"); - -await pipeToDisk(videoStream,fileName, estVideoBytes,elDetails,elProgress); - - - -}else if(enabledTrack==EnabledTrackTypes.AUDIO_ONLY){ -//audio only - - -const estAudioBytes = targetSabrAudio.contentLength || -(targetSabrAudio.bitrate ? Math.floor((targetSabrAudio.bitrate * durationSec) / 8) : 0); - -downloaderDiv.insertAdjacentHTML("beforeend",` -

      Title: ${safeTitle}
      `) - - -var {elDetails,elProgress} = createProgreses("Audio Stream"); - -var fileName=`${safeTitle}_audio${new Date().getTime()}.${containerExt}`; - -await pipeToDisk(audioStream,fileName, estAudioBytes,elDetails,elProgress); - - - -}else if(enabledTrack==EnabledTrackTypes.VIDEO_AND_AUDIO){ -//both - - - -const estVideoBytes = targetSabrVideo.contentLength || (targetSabrVideo.bitrate ? Math.floor((targetSabrVideo.bitrate * durationSec) / 8) : 0); - - -const estAudioBytes = targetSabrAudio.contentLength || -(targetSabrAudio.bitrate ? Math.floor((targetSabrAudio.bitrate * durationSec) / 8) : 0); - - -downloaderDiv.insertAdjacentHTML("beforeend",` -

      Title: ${safeTitle}
      `) - - -var videoEl= createProgreses("Video Stream"); -var audioEl= createProgreses("Audio Stream"); - - -var videoFileName=`${safeTitle}_video${new Date().getTime()}.${containerExt}`; - -var audioFileName=`${safeTitle}_audio${new Date().getTime()}.${containerExt}`; - - -const downloadTasks = []; - -if (videoStream) { -downloadTasks.push(pipeToDisk(videoStream, videoFileName, estVideoBytes,videoEl.elDetails,videoEl.elProgress)); -} - -if (audioStream) { -downloadTasks.push(pipeToDisk(audioStream, audioFileName, estAudioBytes,audioEl.elDetails,audioEl.elProgress)); -} - -await Promise.all(downloadTasks); - - -window.Android.showToast('Muxing formats...'); - -window.Android?.muxVideoAudio?.(videoFileName,audioFileName,`${safeTitle}_${new Date().getTime()}.${containerExt -}`); - -} - - - - - - - - - -} - - -} - - - - -function getDownloadElement() { -const isExisting = (id) => document.getElementById(id); - -// Reuse or create outer + inner divs -const ytproDown = isExisting("outerdownytprodiv") || document.createElement("div"); -const ytproDownDiv = isExisting("downytprodiv") || document.createElement("div"); - -ytproDown.id = "outerdownytprodiv"; -ytproDownDiv.id = "downytprodiv"; - -Object.assign(ytproDown.style, { -height: "100%", width: "100%", position: "fixed", -top: "0", left: "0", display: "flex", -justifyContent: "center", background: "rgba(0,0,0,0.4)", zIndex: "9" -}); - -Object.assign(ytproDownDiv.style, { -height: "65%", width: "85%", overflow: "auto", -background: isD ? "#212121" : "#f1f1f1", -position: "absolute", bottom: "20px", zIndex: "99", -padding: "20px", borderRadius: "25px", textAlign: "center" -}); - -ytproDown.addEventListener("click", (ev) => { -if (!ytproDownDiv.contains(ev.target)) history.back(); -}); - -// Build tabs declaratively -const TABS = [ -{ label: "Formats", viewId: "videoViewDiv" }, -{ label: "Thumbnails", viewId: "thumbViewDiv" }, -{ label: "Captions", viewId: "captionsViewDiv" }, -]; - -const tabStyle = { -height: "100%", -width: "calc((100% - 10px) / 3)", -borderRadius: "25px", -lineHeight: "30px" -}; - -const tabs = document.createElement("div"); -Object.assign(tabs.style, { -height: "30px", width: "95%", display: "flex", -gap: "5px", position: "absolute", top: "10px", left: "2.5%" -}); - -const views = []; - -TABS.forEach(({ label, viewId }) => { -const tab = document.createElement("div"); -Object.assign(tab.style, tabStyle); -tab.textContent = label; -tab.dataset.view = `#${viewId}`; -tabs.appendChild(tab); - -const view = document.createElement("div"); -view.id = viewId; -view.style.paddingTop="40px"; -view.style.display = "none"; -ytproDownDiv.appendChild(view); -views.push(view); -}); - -tabs.addEventListener("click", (e) => { -const el = e.target.closest("[data-view]"); -if (!el) return; - -[...tabs.children].forEach(child => child.style.background = "transparent"); -views.forEach(v => v.style.display = "none"); - -document.querySelector(el.dataset.view).style.display = "block"; -el.style.background = d; -}); - -document.body.appendChild(ytproDown); -ytproDown.appendChild(ytproDownDiv); -ytproDownDiv.prepend(tabs); // tabs sit above views - -tabs.children[0].style.background=d; -document.querySelector("#videoViewDiv").style.display = "block" - -return ytproDownDiv; -} - - - - -// 1. Global registry to catch ports when Android sends them back -const pendingStreams = {}; - -window.addEventListener("message", (event) => { -if (typeof event.data === "string" && event.data.startsWith("PORT_FOR:") && event.ports.length > 0) { -const fileName = event.data.substring(9); -if (pendingStreams[fileName]) { -pendingStreams[fileName](event.ports[0]); // Hand the port back to pipeToDisk -delete pendingStreams[fileName]; -} -} -}); - -// 2. Helper function to request a dedicated pipe -function createDedicatedPipe(fileName) { -return new Promise((resolve) => { -pendingStreams[fileName] = resolve; -window.Android?.requestBinaryPort?.(fileName); -}); -} - -// 3. pipeToDisk -async function pipeToDisk(stream, fileName, expectedTotalBytesStr, elDetails, elProgress) { -const expectedBytes = parseInt(expectedTotalBytesStr || "0", 10); -const totalMB = expectedBytes > 0 ? (expectedBytes / (1024 * 1024)).toFixed(2) : '?'; - -const filePort = await createDedicatedPipe(fileName); -if (!filePort) { -console.error(`[YTPRO] Failed to get port for ${fileName}`); -return 0; -} - -const reader = stream.getReader(); -let total = 0; -let lastLogMB = -1; - -try { -const CHUNK_SIZE = 1024 * 512; - -while (true) { -const { done, value } = await reader.read(); -if (done) break; - -if (value?.length > 0) { -let offset = 0; -while (offset < value.length) { - const chunkBuffer = value.slice(offset, offset + CHUNK_SIZE).buffer; - - // Send the binary chunk down this file's specific port - filePort.postMessage(chunkBuffer); - - const bytesWritten = chunkBuffer.byteLength; - offset += bytesWritten; - total += bytesWritten; - - const currentMBFloor = Math.floor(total / (1024 * 1024)); - if (currentMBFloor > lastLogMB) { - const downloadedMB = (total / (1024 * 1024)).toFixed(2); - const percent = expectedBytes > 0 ? Math.round((total / expectedBytes) * 100) : -1; - - elDetails.children[0].innerHTML = ` ${downloadedMB} MB / ${totalMB} MB`; - elProgress.style.width = percent + "%"; - elProgress.innerHTML = percent + "%"; - - window.Android?.onDownloadProgress?.(percent, total); - lastLogMB = currentMBFloor; - } - - await new Promise(r => setTimeout(r, 5)); -} -} -} -} finally { -// Tell Android THIS specific port is finished, so Java can close the file and kill the port -filePort.postMessage("END"); -} - -const finalMB = (total / (1024 * 1024)).toFixed(2); -elDetails.children[0].innerHTML = ` ${finalMB} MB / ${totalMB} MB`; -elProgress.style.width = "100%"; -elProgress.innerHTML = "100%"; - -return total; -} - - - - -function createDownloaderStatus(){ - -if(document.querySelector("#ytProDownloaderDiv")) return; - -var div=document.createElement("div"); - -div.id="ytProDownloaderDiv"; - - - -Object.assign(div.style,{ -height:"50%", -overflow:"auto", -width:"calc(95% - 20px)", -zIndex:999999, -position:"fixed", -padding:"10px", -bottom:"10px", -display:"none", -left:"2.5%", -background:isD ? "#212121" : "#f1f1f1", -borderRadius:"25px", -textAlign:"center", -boxShadow:"1px 1px 2px black" -}); - -div.innerHTML=` - -
      - INFO: Do NOT close YTPRO while we are downloading the files
      -(SABR streams are limited with 1-2 MBps speed by youtube servers)
      -
      -`; - - -document.body.appendChild(div); - -} - - - - - - -function createDownloaderIndicator(){ -if(document.querySelector("#ytproDownloadIndicator") ) return; -var div=document.createElement("div"); -div.id="ytproDownloadIndicator"; - -Object.assign(div.style,{ -height:"50px", -width:"50px", -zIndex:999999, -position:"fixed", -bottom:"calc(40px)", -right:"20px", -background:isD ? "#212121" : "#f1f1f1", -borderRadius:"50%", -border:`1px solid ${c}`, -display:"grid", -placeItems:"center" -}); - -div.innerHTML=``; - - -document.body.appendChild(div) - -div.addEventListener("click",()=>{ -var el=document.querySelector("#ytProDownloaderDiv"); - -if(el.style.display=="block"){ -el.style.display="none"; -div.style.bottom="70px"; -}else{ -el.style.display="block"; -div.style.bottom="calc(50% + 40px)"; -} -}) - -} - - - diff --git a/app/src/main/assets/ytpro/script.js b/app/src/main/assets/ytpro/script.js deleted file mode 100644 index 9f638131..00000000 --- a/app/src/main/assets/ytpro/script.js +++ /dev/null @@ -1,2946 +0,0 @@ -/*****YTPRO******* -Author: Prateek Chaubey -Version: 3.9.8 -URI: https://github.com/prateek-chaubey/YTPRO -Last Updated On: 1 May , 2026 , 19:25 IST -*/ - - - - -if(window.eruda == null && localStorage.getItem("devMode") == "true"){ -//ERUDA -var script = document.createElement('script'); script.src="//youtube.com/ytpro_cdn/npm/eruda"; document.body.appendChild(script); script.onload=()=>{eruda.init();} -} -/**/ - -if(!YTProVer){ - -/*Few Stupid Inits*/ -var YTProVer="3.98"; -var ytoldV=""; -var isF=false; //what is this for? -var isAp=false; // oh it's for bg play -const originalPause = HTMLMediaElement.prototype.pause; // well long story short , save the original pause function -window.PIPause = false; // for pausing video when in PIP -window.isPIP=false; -window.pauseAllowed = true; // allow pause by default -var sTime=[]; -var webUrls=["m.youtube.com","youtube.com","yout.be","accounts.google.com"]; -var GeminiAT=""; -var YTProLocales = { - en: { - settings: "YT PRO Settings", - enterUrl: "Enter YouTube URL", - likedVideos: "Liked Videos", - checkUpdates: "Check for Updates", - autoskipSponsors: "Autoskip Sponsors", - gestureControls: "Gesture Controls", - miniplayerGesture: "Miniplayer Gesture", - forceZoom: "Force Zoom", - backgroundPlay: "Background Play", - hideShorts: "Hide Shorts", - singleGeminiChat: "Use single Gemini chat", - selectGeminiModel: "Select Gemini Model", - editGeminiPrompt: "Edit Gemini Prompt", - disableCodecs: "Disable Codecs", - reportBugs: "Report Bugs", - sponsor: "Become a Sponsor", - developerMode: "Developer Mode", - disclaimer: "Disclaimer", - disclaimerText: "This is an educational project aimed at showcasing javascript injection into a webview to enhance productivity.", - sourceCode: "You can find the source code at", - madeWith: "Made with", - by: "by Prateek Chaubey", - language: "Language", - english: "English", - chinese: "Simplified Chinese", - languageChanged: "Language changed. Reloading...", - upToDate: "Your app is up to date", - comments: "Comments", - commentsUnavailable: "Could not find the original YouTube comments on this page", - commentsOnlyWatch: "Comments are available on video pages", - openYouTubeComments: "Open YouTube comments", - originalCommentsOpened: "Opened original YouTube comments.", - download: "Download", - heart: "Heart", - pipMode: "PIP Mode", - noVideosFound: "No Videos Found", - likedVideosTitle: "Liked Videos" - }, - zh: { - settings: "YT PRO 设置", - enterUrl: "输入 YouTube 链接", - likedVideos: "收藏的视频", - checkUpdates: "检查更新", - autoskipSponsors: "自动跳过赞助片段", - gestureControls: "手势控制", - miniplayerGesture: "小窗手势", - forceZoom: "强制缩放", - backgroundPlay: "后台播放", - hideShorts: "隐藏 Shorts", - singleGeminiChat: "使用单个 Gemini 对话", - selectGeminiModel: "选择 Gemini 模型", - editGeminiPrompt: "编辑 Gemini 提示词", - disableCodecs: "禁用编解码器", - reportBugs: "反馈问题", - sponsor: "赞助作者", - developerMode: "开发者模式", - disclaimer: "免责声明", - disclaimerText: "本项目用于展示如何通过 WebView 注入 JavaScript 来增强使用体验。", - sourceCode: "你可以在这里查看源代码:", - madeWith: "Made with", - by: "by Prateek Chaubey", - language: "语言", - english: "English", - chinese: "简体中文", - languageChanged: "语言已切换,正在重新加载...", - upToDate: "当前已是最新版本", - comments: "评论", - commentsUnavailable: "没有在当前页面找到 YouTube 原生评论区", - commentsOnlyWatch: "评论区仅在视频页面可用", - openYouTubeComments: "打开 YouTube 评论", - originalCommentsOpened: "已打开 YouTube 原生评论区。", - download: "下载", - heart: "收藏", - pipMode: "画中画", - noVideosFound: "暂无视频", - likedVideosTitle: "收藏的视频" - } -}; - -function ytproLang(){ - var saved = localStorage.getItem("ytproLang"); - if(saved == "zh" || saved == "en") return saved; - return ((navigator.language || "").toLowerCase().indexOf("zh") == 0) ? "zh" : "en"; -} - -function ytproT(key){ - var lang = ytproLang(); - return (YTProLocales[lang] && YTProLocales[lang][key]) || YTProLocales.en[key] || key; -} - -var GeminiModels = { - "3.0 Pro": '[1,null,null,null,"9d8ca3786ebdfbea",null,null,0,[4],null,null,1]', - "3.0 Flash": '[1,null,null,null,"fbb127bbb056c959",null,null,0,[4],null,null,1]', - "3.0 Flash Thinking": '[1,null,null,null,"5bf011840784117a",null,null,0,[4],null,null,1]', - "3.0 Pro Plus": '[1,null,null,null,"e6fa609c3fa255c0",null,null,0,[4],null,null,4]', - "3.0 Flash Plus": '[1,null,null,null,"56fdd199312815e2",null,null,0,[4],null,null,4]', - "3.0 Flash Thinking Plus": '[1,null,null,null,"e051ce1aa80aa576",null,null,0,[4],null,null,4]', - "3.0 Pro Advanced": '[1,null,null,null,"e6fa609c3fa255c0",null,null,0,[4],null,null,2]', - "3.0 Flash Advanced": '[1,null,null,null,"56fdd199312815e2",null,null,0,[4],null,null,2]', - "3.0 Flash Thinking Advanced": '[1,null,null,null,"e051ce1aa80aa576",null,null,0,[4],null,null,2]' -}; - -var YTPROCodecs={ -video:["AV1","VP8","VP9","H264"], -audio:["Opus","Mp4a"] -} - -let touchstartY = 0; -let touchendY = 0; -let initialDistance=null; - -//swipe controls -var sens=0.005; -var vol=Android.getVolume(); -var brt = Android.getBrightness()/100; - -if(localStorage.getItem("saveCInfo") == null || localStorage.getItem("gesC") == null || localStorage.getItem("gesM") == null || localStorage.getItem("bgplay") == null){ -localStorage.setItem("autoSpn","true"); -localStorage.setItem("bgplay","true"); -localStorage.setItem("gesC","true"); -localStorage.setItem("gesM","false"); -localStorage.setItem("fzoom","false"); -localStorage.setItem("saveCInfo","true"); -localStorage.setItem("geminiModel","3.0 Flash"); -localStorage.setItem("prompt","Give me details about this YouTube video Id: {videoId} , a detailed summary of timestamps with facts , resources and reviews of the main content"); -localStorage.setItem("devMode","false"); - -localStorage.setItem("block_60fps","false"); - -YTPROCodecs.video.forEach((x)=>{ -localStorage.setItem(x,"true"); -}); - -YTPROCodecs.audio.forEach((x)=>{ -localStorage.setItem(x,"true"); -}); - -} -if(localStorage.getItem("fzoom") == "true"){ -document.getElementsByName("viewport")[0].setAttribute("content",""); -} - -if (["2.0 Flash", "2.0 Flash Thinking", "2.5 Flash", "2.5 Pro"].includes(localStorage.getItem('geminiModel'))) { -localStorage.setItem('geminiModel', "3.0 Flash"); -} - - -if(window.location.pathname.indexOf("shorts") > -1){ -ytoldV=window.location.pathname; -} -else{ -ytoldV=(new URLSearchParams(window.location.search)).get('v') ; -} - - -/*Dark and Light Mode*/ -var c="#000"; -var d="#f2f2f2"; -var dc="#fff"; -var isD=false; -var dislikes="..."; - - -if(document.cookie.indexOf("f6=40000") > -1){ -dc ="#000";c ="#fff";d="rgba(255,255,255,0.1)"; -isD=true; -}else{ -dc ="#fff";c="#000";d="rgba(0,0,0,0.05)"; -isD=false; -} - -var downBtn=` - - - -`; - - - - - - - - - - -function override() { - -var videoElem = document.createElement('video'); -var origCanPlayType = videoElem.canPlayType.bind(videoElem); -videoElem.__proto__.canPlayType = makeModifiedTypeChecker(origCanPlayType); - -var mse = window.MediaSource; - -if (mse === undefined) return; -var origIsTypeSupported = mse.isTypeSupported.bind(mse); -mse.isTypeSupported = makeModifiedTypeChecker(origIsTypeSupported); -} - - -function makeModifiedTypeChecker(origChecker) { - - -return function (type) { -if (type === undefined) return ''; -var disallowed_types = []; -if (localStorage['H264'] === 'false') { -disallowed_types.push('avc'); -} -if (localStorage['VP8'] === 'false') { -disallowed_types.push('vp8'); -} -if (localStorage['VP9'] === 'false') { -disallowed_types.push('vp9', 'vp09'); -} -if (localStorage['AV1'] === 'true') { -disallowed_types.push('av01', 'av99'); -} -if (localStorage['Opus'] === 'false') { -disallowed_types.push('opus'); -} -if (localStorage['Mp4a'] === 'false') { -disallowed_types.push('mp4a'); -} - -// If video type is in disallowed_types, say we don't support them -for (var i = 0; i < disallowed_types.length; i++) { -if (type.indexOf(disallowed_types[i]) !== -1) return ''; -} - -if (localStorage['block_60fps'] === 'true') { -var match = /framerate=(\d+)/.exec(type); -if (match && match[1] > 30) return ''; -} - -return origChecker(type); -}; -} - -override(); - - - - - -function insertAfter(referenceNode, newNode) { -try{ -referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); -}catch{} -} - - -/*wait for the element , using observer*/ -async function waitForElement(selector,vid) { -return new Promise((resolve) => { -const element = document.querySelector(selector); -if(element){ -if(vid && element.src != "") return resolve(element); -if(!vid) return resolve(element); -} -const observer = new MutationObserver(() => { -const el = document.querySelector(selector); -if (el){ - -if(vid && el.src) resolve(el),observer.disconnect();; -if(!vid) resolve(el),observer.disconnect();; -} -}); -observer.observe(document.body, { -childList: true, -subtree: true -}); -}); -} - - -/*Add Settings Tab*/ -var addSettingsTab=()=>{ -if(document.getElementById("setDiv") == null){ -var setDiv=document.createElement("div"); -setDiv.setAttribute("style",` -z-index:9999999999; -font-size:22px; -text-align:center; -line-height:35px; -pointer-events:auto; -`); -setDiv.setAttribute("id","setDiv"); -var svg=document.createElement("ytm-pivot-bar-item-renderer"); -svg.innerHTML=` -`; -setDiv.appendChild(svg); -insertAfter(document.getElementsByTagName("ytm-home-logo")[0],setDiv) -if(document.getElementById("hSett") != null){ -document.getElementById("hSett").addEventListener("click", -function(ev){ -window.location.hash="settings"; -}); -} -} - - -}; - - - - -/*Dislikes To Locale, Credits: Return YT Dislikes*/ -function getDislikesInLocale(num){ -var nn=num; -if (num < 1000){ -nn = num; -} -else{ -const int = Math.floor(Math.log10(num) - 2); -const decimal = int + (int % 3 ? 1 : 0); -const value = Math.floor(num / 10 ** decimal); -nn= value * 10 ** decimal; -} -let userLocales; -if (document.documentElement.lang) { -userLocales = document.documentElement.lang; -} else if (navigator.language) { -userLocales = navigator.language; -} else { -try { -userLocales = new URL( -Array.from(document.querySelectorAll("head > link[rel='search']")) -?.find((n) => n?.getAttribute("href")?.includes("?locale=")) -?.getAttribute("href") -)?.searchParams?.get("locale"); -} catch { -userLocales = "en"; -} -} -return Intl.NumberFormat(userLocales, { -notation: "compact", -compactDisplay: "short", -}).format(nn); -} - - - -/*Skips the bad part :)*/ -async function skipSponsor(){ -var sDiv=document.createElement("div"); -sDiv.setAttribute("style",`height:3px;pointer-events:none;width:100%;position:absolute;z-index:99;`) -sDiv.setAttribute("id","sDiv"); -var player = document.getElementsByClassName("video-stream")[0]; -var dur=player.duration; - -if(isNaN(dur)) return; - -for(var x in sTime){ -var s1=document.createElement("div"); -var s2=sTime[x]; -s1.setAttribute("style",`height:3px;width:${(100/dur) * (s2[1]-s2[0])}%;background:#0f8;position:absolute;z-index:9;left:${(100/dur) * s2[0]}%;`) -sDiv.appendChild(s1); -} - - - - -var e=await waitForElement("yt-progress-bar",false); - - -if(document.getElementById("sDiv") == null){ -if(document.getElementsByClassName('ytPlayerProgressBarHost')[0] != null){ -document.getElementsByClassName('ytPlayerProgressBarHost')[0].appendChild(sDiv); -}else{ -try{document.getElementsByClassName('ytProgressBarLineProgressBarLine')[0].appendChild(sDiv);}catch{} -} -} - - - - -} - - - - - -/*Fetch The Dislikes*/ -async function fDislikes(url){ -var Url=new URL(url); -var vID=""; -if(Url.pathname.indexOf("shorts") > -1){ -vID=Url.pathname.substr(8,Url.pathname.length); -} -else if(Url.pathname.indexOf("watch") > -1){ -vID=Url.searchParams.get("v"); -} - - -fetch("https://returnyoutubedislikeapi.com/votes?videoId="+vID) -.then(response => { -return response.json(); -}).then(jsonObject => { -if('dislikes' in jsonObject){ -dislikes=getDislikesInLocale(parseInt(jsonObject.dislikes)); -} -}).catch(error => {}); - -} - - - -/*Check For Sponsorships*/ -async function checkSponsors(Url){ - - -if(Url.indexOf("watch") > -1){ - -sTime=[]; - -await fetch("https://sponsor.ajay.app/api/skipSegments?videoID="+new URL(Url).searchParams.get("v")) -.then(response => { -return response.json(); -}).then(jsonObject => { -for(var x in jsonObject){ -var time=jsonObject[x].segment; -sTime.push(time); -} -}).catch(error => {}); - - - -/*Skip the Sponsor*/ -var player = await waitForElement(".video-stream",true); - - -player.ontimeupdate=()=>{ -skipSponsor(); -var cur=player.currentTime; -for(var x in sTime){ -var s2=sTime[x]; -if(Math.floor(cur) == Math.floor(s2[0])){ -if(localStorage.getItem("autoSpn") == "true"){ -player.currentTime=s2[1]; -addSkipper(s2[0]); -} -} -} -}; - - - - - -} - -} - - -//DEBUG -/* -s1: FoQR9rLpRy8 -s2: PN51tJhZscE -*/ -/*Add Skip Sponsor Element*/ -function addSkipper(sT){ -var sSDiv=document.createElement("div"); -sSDiv.setAttribute("style",` -height:50px;${(screen.width > screen.height) ? "width:50%;" : "width:80%;"}overflow:auto;background:rgba(130,130,130,.3); -backdrop-filter:blur(6px); -position:absolute;bottom:40px; -line-height:50px; -left:calc(15% / 2 );padding-left:10px;padding-right:10px; -z-index:99999999999999;text-align:center;border-radius:25px; -color:white;text-align:center; -`); -sSDiv.innerHTML=`Skipped Sponsor - - - - - - - - -`; -document.getElementById("player-control-container").appendChild(sSDiv); - - -sSDiv.addEventListener("click",(e)=>{ - var el=e.target.closest("[data-action]"); - - if(!el) return; - var action=el.dataset.action; - - if(action == "close"){ -el.parentElement.parentElement.remove(); - }else if(action == "rewind"){ - el.parentElement.parentElement.remove(); - document.getElementsByClassName('video-stream')[0].currentTime=sT+1; - } - -}); - - -setTimeout(()=>{sSDiv.remove();},5000); -} - - -fDislikes(window.location.href); -checkSponsors(window.location.href); - - -if((window.location.pathname.indexOf("watch") > -1) || (window.location.pathname.indexOf("shorts") > -1)){ -var unV=setInterval(() => { - - -/*Unmute The Video*/ - -document.getElementsByClassName('video-stream')[0].muted=false; - -if(!document.getElementsByClassName('video-stream')[0].muted){ -clearInterval(unV); - -} - -}, 5); - -} - -/*Funtion to set Element Styles*/ -function sty(e,v){ -var s={ -display:"flex", -alignItems:"center", -justifyContent:"center", -fontWeight:"550", -height:"65%", -minWidth:"80px", -width:"auto", -borderRadius:"20px", -background:d, -fontSize:"12px", -marginRight:"5px", -textAlign:"center", -}; -for(x in s){ -e.style[x]=s[x]; -} -} - - -function getGeminiModels(){ -var t=""; - -for(var x in GeminiModels){ - - -t+=`
      -`; -} - -return t; - -} - - -/*Get Codecs*/ -function getYTPROCodecs(){ -var t=`

      This feature is experimental , this may break YTPro if not configured correctly. By default all the codecs are enabled , tap on the buttons below to switch them.


      Video Codecs
      `; - -for(var y in YTPROCodecs.video){ - -var x=YTPROCodecs.video[y]; - -t+=``; -} - -t+=`

      Audio Codecs
      ` -for(var y in YTPROCodecs.audio){ - -var x=YTPROCodecs.audio[y]; - -t+=``; -} - -t+=`

      -
      Block 60FPS
      `; - -t+=`

      `; - - -return t; - -} - - -function setRemoveCodec(x,y){ - - -if(localStorage[x] == "true"){ -localStorage.setItem(x,"false"); -y.style.background=isD ? "rgba(255,255,255,.1)" : "rgba(0,0,0,.1)"; -y.style.color=c; -y.children[0].style.display="none"; -}else{ -localStorage.setItem(x,"true"); -y.style.background=c; -y.style.color=dc; -y.children[0].style.display="block"; -} - - - - -} - - -/*The settings tab*/ -async function ytproSettings(){ -var ytpSet=document.createElement("div"); -var ytpSetI=document.createElement("div"); -ytpSet.setAttribute("id","settingsprodiv"); -ytpSetI.setAttribute("id","ssprodivI"); -ytpSet.setAttribute("style",` -height:100%;width:100%;position:fixed;top:0;left:0; -display:flex;justify-content:center; -background:rgba(0,0,0,0.7); -z-index:9999; -`); -ytpSet.addEventListener("click", -function(ev){ - -if(!(ev.target == ytpSetI || ytpSetI.contains(ev.target))){ - -history.back(); -} -}); - -ytpSetI.setAttribute("style",` -height:65%;width:calc(95% - 20px);overflow:auto; -background:${isD ? "#212121" : "#f1f1f1"}; -position:fixed; -bottom:20px; -z-index:99999999999999;padding:10px;text-align:center;border-radius:25px;color:${c};text-align:center; -color:${isD ? "#ccc" : "#444"};`); - -ytpSetI.innerHTML=``; -ytpSetI.innerHTML+=`
      ${ytproT("settings")} -v${YTProVer} -

      -
      - - -
      Please follow Habitius on InstagramFor daily habit,lifestyle and health tips
      - - - - - -
      - -
      -
      - -
      - -
      -
      ${ytproT("autoskipSponsors")}
      -
      -
      ${ytproT("gestureControls")}
      -
      -
      ${ytproT("miniplayerGesture")}
      -
      -
      ${ytproT("forceZoom")}
      -
      -
      ${ytproT("backgroundPlay")}
      -
      -
      ${ytproT("hideShorts")}
      -
      -
      ${ytproT("singleGeminiChat")}
      -
      - -
      - -
      - -
      - -
      - -
      - -
      -
      ${ytproT("developerMode")}
      -

      -

      ${ytproT("disclaimer")}: ${ytproT("disclaimerText")}
      -${ytproT("sourceCode")} https://github.com/prateek-chaubey/YTPRO -




      - -
      - -
      - - -
      - - - - -

      -
      - - -
      - -
      - - -
      -${ytproT("madeWith")} - - - - - - - -${ytproT("by")} -
      -`; - - -document.body.appendChild(ytpSet); -ytpSet.appendChild(ytpSetI); - - -document.getElementById("ytproUrlInput").addEventListener("keyup",searchUrl); - - - - -var actionsList={ - follow:()=>{ - Android.oplink("https://www.instagram.com/habitius.daily"); - }, - hearts:()=>{ - window.location.hash='#hearts'; - }, - checkUpdate:()=>{ - checkUpdates(); - }, - sttCnf:(button,action)=>{ - sttCnf(button,action); - }, - geminiModels:()=>{ - document.getElementsByClassName('geminiModels')[0].style.display='block';document.getElementsByClassName('geminiModels')[0].innerHTML=getGeminiModels(); - }, - geminiPrompt:()=>{ - document.getElementsByClassName('geminiPrompt')[0].style.display='block'; - }, - issues:()=>{ - Android.oplink('https://github.com/prateek-chaubey/YTPRO/issues'); - }, - disableCodecs:()=>{ - document.getElementsByClassName('disableCodecs')[0].style.display='block';document.getElementsByClassName('disableCodecs')[0].innerHTML=getYTPROCodecs(); - }, - sponsor:()=>{ - Android.oplink('https://github.com/sponsors/prateek-chaubey'); - }, - toggleLang:()=>{ - localStorage.setItem("ytproLang", ytproLang() == "zh" ? "en" : "zh"); - Android.showToast(ytproT("languageChanged")); - window.location.reload(); - }, - savePrompt:(el)=>{ - localStorage.setItem('prompt',el.previousElementSibling.value);el.parentElement.style.display='none'; - }, - done:(el)=>{ - el.parentElement.style.display='none'; - }, - setRemoveCodec:(el,value)=>{ - setRemoveCodec(value,el) - }, - block_60fps:(el)=>{ - sttCnf(el,"block_60fps"); - }, - saveModel:(el,value)=>{ - localStorage.removeItem('geminiChatInfo'); - localStorage.setItem('geminiModel',value); - el.parentElement.style.display='none'; - } -} - -//buttons and switches -ytpSetI.querySelectorAll("[data-action]").forEach(button =>{ - button.addEventListener("click",()=>{ - - if(button.dataset.action== "sttCnf"){ - actionsList[button.dataset.action](button,button.dataset.value); - }else{ - actionsList[button.dataset.action](button); - } - }) -}); - - -//disable Codecs -ytpSetI.querySelector(".disableCodecs").addEventListener("click",(e)=>{ - var el = e.target.closest("[data-action]"); - if(!el) return; - - actionsList[el.dataset.action](el,el.dataset.value); - -}) - - -//gemini model selector -ytpSetI.querySelector(".geminiModels").addEventListener("click",(e)=>{ - var el = e.target.closest("[data-action]"); - if(!el) return; - - actionsList[el.dataset.action](el,el.dataset.value); - -}) - - - -} - - - -function searchUrl(e){ - - -if(e.keyCode === 13 || e === "Enter"){ - -var url=e.target.value; -const regex = /(?:https?:\/\/)?(?:www\.|m\.)?(?:youtu\.be\/|youtube(?:-nocookie)?\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|shorts|live)\/))([a-zA-Z0-9_-]{11})/; - -const match = url.match(regex); -var id=match ? match[1] : null; -if(id){ - return navigateInternalYtMweb(id); -} - - -var a=document.createElement("a"); -a.href=url; -document.body.appendChild(a); -try{document.getElementById("settingsprodiv").remove();}catch{} -a.click(); - -} -} - -function getVideoIdFromUrl(){ -if(window.location.pathname.indexOf("shorts") > -1){ -return window.location.pathname.replace("/shorts/",""); -} -return new URLSearchParams(window.location.search).get("v"); -} - -function openOriginalComments(){ -var selectors = ["ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytd-comments-header-renderer", "ytd-comments"]; -for(var i = 0; i < selectors.length; i++){ -var el = document.querySelector(selectors[i]); -if(el){ -el.scrollIntoView({behavior:"smooth", block:"center"}); -var clickable = el.querySelector("button, a, [role='button']") || el; -setTimeout(function(target, fallback){ -try{ target.click(); }catch(e){ try{ fallback.click(); }catch(_){} } -}, 120, clickable, el); -return true; -} -} - -var sections = Array.from(document.querySelectorAll("ytm-item-section-renderer")); -for(var k = 0; k < sections.length; k++){ -var text = sections[k].innerText || ""; -if(text.indexOf(ytproT("comments")) > -1 || text.indexOf("Comments") > -1 || text.indexOf("评论") > -1){ -sections[k].scrollIntoView({behavior:"smooth", block:"center"}); -setTimeout(function(target){ try{ target.click(); }catch(e){} }, 120, sections[k]); -return true; -} -} - -var anchors = Array.from(document.querySelectorAll('a')); -for(var j = 0; j < anchors.length; j++){ -var href = anchors[j].href || ""; -if(href.indexOf("comment") > -1 || href.indexOf("replies") > -1){ -anchors[j].click(); -return true; -} -} -return false; -} - -function ensureCommentButton(){ -if(window.location.href.indexOf("youtube.com/watch") < 0 && window.location.href.indexOf("youtube.com/shorts") < 0) return; -if(document.getElementById("ytproCommentsBtn") != null) return; -var host = document.getElementById('ytproMainDivE'); -if(!host || !host.querySelector("div")) return; -var btn = document.createElement("div"); -sty(btn); -btn.id = "ytproCommentsBtn"; -btn.style.width = "96px"; -btn.innerHTML = `${ytproT("comments")}`; -btn.addEventListener("click", ytproCommentsPanel); -var toolbar = host.querySelector("div"); -var anchor = toolbar.children.length > 1 ? toolbar.children[1] : null; -if(anchor){ -toolbar.insertBefore(btn, anchor); -}else{ -toolbar.appendChild(btn); -} -} - -function ytproCommentsPanel(){ -var existing = document.getElementById("ytproCommentsDiv"); -if(existing){ existing.remove(); } - -if(!/youtube\.com\/(watch|shorts)/.test(window.location.href)){ -Android.showToast(ytproT("commentsOnlyWatch")); -return; -} - -if(openOriginalComments()){ -Android.showToast(ytproT("originalCommentsOpened")); -return; -} - -var comments = document.createElement("div"); -comments.id = "ytproCommentsDiv"; -comments.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; - -var inner = document.createElement("div"); -inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:auto;padding:12px;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; - -var vid = getVideoIdFromUrl(); -inner.innerHTML = '
      ' + - '' + ytproT("comments") + '' + - '' + - '
      ' + - '
      Loading...
      ' + - '
      ' + (vid ? vid : "") + '
      '; - -comments.addEventListener("click", function(ev){ -if(ev.target === comments){ comments.remove(); } -var btn = ev.target.closest("[data-action]"); -if(btn && btn.dataset.action === "closeComments"){ comments.remove(); } -}); - -comments.appendChild(inner); -document.body.appendChild(comments); - -setTimeout(function(){ -document.getElementById("ytproCommentsBody").innerHTML = ytproT("commentsUnavailable") + '

      '; -document.getElementById("ytproCommentsBody").querySelector("[data-action='openNativeComments']").addEventListener("click", function(){ - Android.oplink("https://m.youtube.com/watch?v=" + (vid || "")); -}); -}, 50); -} - -function checkUpdates(){ -if(parseFloat(Android.getInfo()) < parseFloat(YTProVer) ){ -updateModel(); -}else{ -Android.showToast(ytproT("upToDate")); -} - -fetch('https://youtube.com/ytpro_local/script.js', {cache: 'reload'}); -fetch('https://youtube.com/ytpro_local/bgplay.js', {cache: 'reload'}); -fetch('https://youtube.com/ytpro_local/innertube.js', {cache: 'reload'}); -} - - -/*Set Configration*/ -function sttCnf(x,z,y){ - -/*Way too complex to understand*/ -if(isD){ -var s=["#000","#717171","#fff"]; -}else{ -var s=["#fff","#909090","#151515"]; -} - - - -if(typeof y == "string"){ - -if(localStorage.getItem(y) != "true"){ -if(z == 1){ -return `background:${s[0]};left:2px;`; -}else{ -return `background:${s[1]};`; -} -}else{ -if(z == 1){ -return `background:${s[0]};`; -}else{ -return `background:${s[2]};`; -} -} -} -if(localStorage.getItem(z) == "true"){ -localStorage.setItem(z,"false"); -x.style.background=s[1]; -x.children[0].style.left="2px"; -x.children[0].style.background=s[0]; -} -else{ -localStorage.setItem(z,"true"); -x.style.background=s[2]; -x.children[0].style.left="auto"; -x.children[0].style.right="2px"; -x.children[0].style.background=s[0]; -} - -if(localStorage.getItem("fzoom") == "false"){ -document.getElementsByName("viewport")[0].setAttribute("content","width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,"); -}else{ -document.getElementsByName("viewport")[0].setAttribute("content",""); -} - - - -if(localStorage.getItem("bgplay") == "true"){ -Android.setBgPlay(true); -}else{ -Android.setBgPlay(false); -} - - -if(localStorage.getItem("gesC") != "true"){ -try{ -document.getElementById("brtS").remove(); -document.getElementById("volS").remove(); -}catch{} - -} - -if(localStorage.getItem("devMode") == "false"){ -try{eruda.destroy();}catch{} -}else if(!window.eruda && localStorage.getItem("devMode") == "true"){ -var script = document.createElement('script'); script.src="//youtube.com/ytpro_cdn/npm/eruda"; document.body.appendChild(script); script.onload=()=>{ eruda.init();} -} - - - -} - - - - -/*Format File Size*/ -function formatFileSize(bytes){ -var s=parseInt(bytes); -let ss = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] -for (var i=0; s > 1024; i++) s /= 1024; -return `${s.toFixed(1)} ${ss[i]}`; -} - -/*Video Downloader*/ -async function ytproDownVid(){ - -window.ytproSabrDownload(); - -} - - - - - -function showHideAdaptives(){ -var z=document.querySelectorAll(".adpFormats"); -z.forEach((x)=>{ -if(x.style.display=="none"){ -x.style.display="flex"; -}else{ -x.style.display="none"; -} - -}); - -} - -/*Add the meme type and extensions lol*/ -function downCap(x,t){ -Android.downvid(t,x,"plain/text"); -} - -/*Send to Download Manager*/ -function YTDownVid(o,ex){ -var mtype=""; -if(ex ==".png"){ -mtype="image/png"; -}else if(ex ==".mp4"){ -mtype="video/mp4"; -} -else if(ex ==".mp3"){ -mtype="audio/mp3"; -} - -//console.log(o.getAttribute("data-ytprourl")) - -Android.downvid((o.getAttribute("data-ytprotit")+ex),o.getAttribute("data-ytprourl"),mtype); -} - - - - - - - - -var stopProp = false; -var zoomIn=false; -var scale=1; - - -/*Checks the Direction of the Swipe*/ -function checkDirection(e) { -if ((touchendY > touchstartY) && (touchendY - touchstartY > 20)) { -minimize(true); -}else if ((touchendY < touchstartY) && (touchstartY - touchendY > 20)) { -minimize(false); -//console.log((touchstartY - touchendY )) -} -} - -/*for zoom in and out*/ -function getDistance(touches) { -const [a, b] = touches; -return Math.hypot(b.pageX - a.pageX, b.pageY - a.pageY); -} - - - -/*touch start*/ -document.body.addEventListener('touchstart', e => { -touchstartY = e.changedTouches[0].screenY; -if (e.touches.length === 2) { -initialDistance = getDistance(e.touches); -} -}, { capture: true }); - - - - -/*touch move*/ -document.body.addEventListener('touchmove', (e) => { - - -if(stopProp){ -e.stopPropagation(); -} - -if (e.touches.length === 2 && initialDistance !== null) { -const currentDistance = getDistance(e.touches); -const z = currentDistance / initialDistance; - -stopProp=true; - - -if((e.target.className.toString().includes("video-stream") || e.target.className.toString().includes("player-controls-background")) && document.fullscreenElement){ - -if (z > 1.05) { -var Vv=document.getElementsByClassName('video-stream')[0]; -zoomIn=true; -scale=Math.max((screen.height / Vv.offsetHeight) , (screen.width / Vv.offsetWidth)); -addMaxButton(); -} else if (z < 0.95) { -zoomIn=false; -scale=1; -addMaxButton(); -} -} - - - -} -},{capture:true}); - - - - - - -/*touch end*/ -document.body.addEventListener('touchend', e => { - - -touchendY = e.changedTouches[0].screenY; - -if((e.target.className.toString().includes("video-stream") || e.target.className.toString().includes("player-controls-background")) && !document.fullscreenElement && localStorage.getItem("gesM") == "true"){ -checkDirection(); -} - -if (e.touches.length < 2) { -initialDistance = null; // reset - -setTimeout(()=>{ -stopProp=false; -},500) - -} - -}, { capture: true }); - - - - - -navigation.addEventListener("navigate", e => { -if(e.destination.url.indexOf("watch") > -1 || e.destination.url.indexOf("shorts") > -1){ - dislikes="..."; -fDislikes(e.destination.url); -checkSponsors(e.destination.url); -} -}); - - -/*minimize function to mini the video*/ -function minimize(yes){ - - -const createIframe=()=>{ - -var iframe=document.createElement("iframe"); -iframe.setAttribute("id",`miniIframe`); -iframe.setAttribute("style",` -height:99.999%;width:100%; -background:${c}; -top:0px; -line-height:50px; -position:fixed; -left:0; -z-index:999; -border:0; -`); - - -iframe.src="https://m.youtube.com/"; -document.body.appendChild(iframe); - - -var iwindow = iframe.contentWindow || iframe.contentDocument.defaultView; -var doc = iwindow.document; - -if (doc.readyState == 'complete' ) { -if (iwindow.trustedTypes && iwindow.trustedTypes.createPolicy && !iwindow.trustedTypes.defaultPolicy) { -iwindow.trustedTypes.createPolicy('default', {createHTML: (string) => string,createScriptURL: string => string, createScript: string => string, }); -} -} - -iwindow.navigation.addEventListener("navigate", e => { -if(e.destination.url.indexOf("youtube.com") > -1){ -if(e.destination.url.indexOf("/watch") > -1 || e.destination.url.indexOf("/shorts") > -1){ -window.location.href=e.destination.url; -} -var script = doc.createElement("script"); -var scriptSource=`window.addEventListener('DOMContentLoaded', function() { -var script2 = document.createElement('script'); -script2.src="//youtube.com/ytpro_cdn/npm/ytpro"; -document.body.appendChild(script2); -}); -`; -} -else{ -window.location.href=e.destination.url; -} - - -}); - -var script = doc.createElement("script"); -var scriptSource=`window.addEventListener('DOMContentLoaded', function() { -var script2 = document.createElement('script'); -script2.src="//youtube.com/ytpro_cdn/npm/ytpro"; -document.body.appendChild(script2); -}); -`; - -/* -var script = document.createElement('script'); -script.src="//cdn.jsdelivr.net/npm/eruda"; -document.body.appendChild(script); -script.onload = function () { eruda.init() } ; -*/ - - - -var source = doc.createTextNode(scriptSource); -script.appendChild(source); -doc.body.appendChild(script); - -return iframe; - -} - - - -var iframe = document.getElementById("miniIframe") || createIframe(); -var player=document.getElementById("player-container-id"); - - - - -//var ogCss=getComputedStyle(player); - -if(yes){ - -iframe.style.display="block"; - - -player.setAttribute("ogTop",getComputedStyle(player).top) - - -player.style.transform="scale(0.65)"; -player.style.top=(window.screen.height-(player.getBoundingClientRect().height*2.5))+"px"; -player.style.zIndex="9999"; - - -}else{ - -iframe.style.display="none"; - - - -player.style.transform="scale(1)"; -player.style.top=player.getAttribute("ogTop"); -player.style.zIndex="normal"; - -player.removeAttribute("ogTop"); - - -} -} - - - -/*JAVA Callback for AccessToken*/ -function callbackSNlM0e(){ -return new Promise(resolve => { -callbackSNlM0e.resolve = resolve; -}); -} - -/*JAVA Callback for Gemini Response*/ -function callbackGeminiClient(){ -return new Promise(resolve => { -callbackGeminiClient.resolve = resolve; -}); -} - - - - - -/*Handles the reponse*/ -function handleGeminiResponse(res){ - - -/*Extract the body from the response*/ -const getBody=(x)=>{ -for(var i in x){ -try{ -var json=JSON.parse(x[i][2]); -if(json[4]?.[0]?.[0].indexOf("rc_") > -1) return json; -}catch(e){console.log("JSON parse error: "+e);}} -} - -/*Modifies the timestamps , to handle them inside the video element*/ -const modifyTimestamps=(x)=>{ -var html=x; -var hrefs=html.match(/href="([^"]*)"/g) || []; -var urls= [...hrefs].map(url => url.replace(/href="|"/g, "")); -hrefs.forEach((x,i)=>{ -var time=new URL(urls[i]).searchParams.get("t"); -if(time != null){ -html=html.replace(x,`href="javascript:void(0);" onclick="document.getElementsByClassName('video-stream')[0].currentTime='${time}'"`) -}else if(urls[i].indexOf("youtube.com") < 0 && urls[i].indexOf("youtu.be") < 0){ -html=html.replace(x,`href="javascript:void(0);" onclick="try{document.getElementsByClassName('video-stream')[0].pause();}catch{}Android.oplink('${urls[i]}')"`) -} -}) -return html; -} - - - - - - -/*checks if the object is empty*/ -var response=res.stream; - -if (response == undefined) return document.getElementById("GeminiResponse").innerHTML=`
      An error Occurred while connecting to Gemini`; - -var lines=response.split("\n"); -var responseJson=JSON.parse(lines[2]) - - -var body=getBody(responseJson) || []; - -//console.log(body) - -var chat=[]; - -chat.push(body?.[1]?.[0]); -chat.push(body?.[1]?.[1]); -chat.push(body?.[4]?.[0]?.[0]); - -/*Stores the recent chat info*/ -localStorage.setItem("geminiChatInfo",chat.toString()); - - -body=body?.[4]?.[0]; - -var text=body?.[1]?.[0] || ""; -text=text.replace(/http:\/\/googleusercontent\.com\/\S+/g,''); -var thoughts = body?.[37]?.[0]?.[0] || null; -var images=[]; - -for(var i in body?.[12]?.[1]){ -var img=body?.[12]?.[1]?.[i] -images.push({ -url:img[0][0][0], -alt:img[0][4], -title:img[7][0] -}); - -text+=`
      ${img[0][4]}
      `; -} - -//console.log(text,"\n\n\n-------- \n\n",thoughts) - - - - - - -let converter = new showdown.Converter(); -converter.setFlavor('github'); -let html = modifyTimestamps(converter.makeHtml(text)); - - -let thoughtsHtml=(thoughts != null) ? ` -
      -
      -${converter.makeHtml(thoughts)} - - -

      ` : ""; - -document.getElementById("GeminiResponse").innerHTML=`Go to the chat

      - -${thoughtsHtml} - - - -
      -${html} -
      -`; - - -} - - - - - -/*Main Gemini Function*/ -async function geminiInfo(){ -if(document.getElementById("GeminiResponse") == null){ -var GeminiRes=document.createElement("div"); -GeminiRes.setAttribute("style",`min-height:80px;max-height:400px;display:block;height:auto;overflow:scroll;font-weight:400;width:calc(92% - 20px);font-size:14px;padding:10px;position:relative;margin:auto;background:${d};border-radius:15px;margin-bottom:8px;`); -GeminiRes.setAttribute("id","GeminiResponse"); - - -insertAfter(document.getElementById('ytproMainDivE'),GeminiRes); - -}else{ -var GeminiRes=document.getElementById("GeminiResponse"); -} - - -document.getElementById("GeminiResponse").innerHTML=` -
      `; - -var cookies=Android.getAllCookies(window.location.href); - -if(cookies.indexOf("__Secure-1PSID=") < 0){ -GeminiRes.innerHTML=` -
      -Sign in to use Gemini -

      - - - -

      - -
      `; - -return; - -} - - -/*checks if the user is logged in*/ -cookies=cookies.split(";"); - -var secured=""; - -cookies.forEach((x)=>{ -if(x.indexOf("__Secure-1PSID=") > -1 || x.indexOf("__Secure-1PSIDTS=") > -1) -secured+=x+";"; -}) - - - -var endpoint="https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"; -var headers=JSON.stringify({ -"accept": "*/*", -"accept-language": "en", -"content-type":"application/x-www-form-urlencoded;charset=UTF-8", -"x-goog-ext-525001261-jspb": GeminiModels[localStorage.getItem('geminiModel')], -"x-same-domain": "1", -"cookie": secured, -"Referer": "https://gemini.google.com/", -"Referrer-Policy": "origin" -}); - - -if(GeminiAT == ""){ -Android.getSNlM0e(secured); -GeminiAT=await callbackSNlM0e(); - -var sd = document.createElement('script'); -sd.src="//youtube.com/ytpro_cdn/npm/showdown/dist/showdown.min.js"; -document.body.appendChild(sd); - -} - - - - -var prompt=localStorage.getItem('prompt').replaceAll("{url}",window.location.href).replaceAll("{videoId}",new URL(window.location.href).searchParams.get("v")).replaceAll("{title}",document.getElementsByClassName('slim-video-metadata-header')[0].textContent.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","")); -//`send me details with timestamps and images related to this youtube com video ${}`; -// , including all the aspects and scopes with timestamp , add facts in the analysis as well ,Here's the youtube - - - -var chat = null; - -if(localStorage.getItem("saveCInfo") == "true" && localStorage.getItem("geminiChatInfo") != null){ -chat = localStorage.getItem("geminiChatInfo").split(","); -} - -const formData = new URLSearchParams(); -formData.append("f.req", JSON.stringify([ -null, -JSON.stringify([[prompt],null,chat]) -])); - -formData.append("at", GeminiAT); - - - -Android.GeminiClient(endpoint,headers,formData.toString()); -var response=await callbackGeminiClient(); - -handleGeminiResponse(response); - -} - - -var volSvg=``; -var brtSvg=``; - - -/*THE 0NE AND 0NLY FUNCTION*/ -async function pkc(){ - -if(window.location.href.indexOf("youtube.com/watch") > -1){ - - -try{ -var elm=document.getElementsByTagName("dislike-button-view-model")[0].children[0]; -elm.children[0].children[0].style.width="auto"; -elm.children[0].children[0].style.paddingRight="15px"; - -if(!document.getElementById("diskl")){ - var diskl=document.createElement("span"); - diskl.setAttribute("id","diskl"); - diskl.innerHTML=dislikes; - diskl.style.marginLeft="5px"; - -insertAfter(elm.getElementsByClassName("yt-spec-button-shape-next__icon")[0],diskl); - -}else{ -document.getElementById("diskl").innerHTML=dislikes; -} - -}catch(e){} - - -//Volume and brightness slider -try{ - -if(localStorage.getItem("gesC") == "true"){ - - -var v= document.getElementById("player-container-id"); -var rect=v.getBoundingClientRect(); - -var elStyle={ -height:"70%", -width:rect.width*0.14+"px", -display:"flex", -"flex-direction":"column", -"align-items":"center", -"justify-content":"center", -position:"absolute", -top:"16%", -right:"0px", -opacity:"0", -//background:"#a57a" -}; - - - -var el=document.createElement("div"); -var elB=document.createElement("div"); -elB.setAttribute("id","brtS"); -el.setAttribute("id","volS"); - -Object.assign(el.style,elStyle); -Object.assign(elB.style,elStyle); -elB.style.left="0"; - -el.innerHTML=`${volSvg}
      `; -elB.innerHTML=`${brtSvg}
      `; - - -if(!document.getElementById("brtS")){ -document.getElementById("player-container-id").appendChild(elB); - -elB.addEventListener("touchmove",(e)=>{ -e.preventDefault(); -elB.style.opacity="1"; - -var diff= touchstartY - e.touches[0].pageY; - -if(diff > 0){ -brt +=sens; -}else{ -brt -=sens; -} - -if(brt > 1) brt=1; -if(brt < 0) brt =0; - -touchstartY=e.touches[0].pageY; - -Android.setBrightness(brt); -document.getElementById("brtIS").style.height=brt*100+"%"; - -},{ passive: false }) - - -//hide the element after touch endas -elB.addEventListener("touchend",(e)=>{ -elB.style.opacity="0"; -},{ passive: false }); - -} - - - - - -if(!document.getElementById("volS")){ -document.getElementById("player-container-id").appendChild(el); - -el.addEventListener("touchmove",(e)=>{ -e.preventDefault(); -el.style.opacity="1"; - -var diff= touchstartY - e.touches[0].pageY; - -if(diff > 0){ -vol +=sens; -}else{ -vol -=sens; -} - -if(vol > 1) vol=1; -if(vol < 0) vol =0; - -touchstartY=e.touches[0].pageY; - -Android.setVolume(vol); -document.getElementById("volIS").style.height=vol * 100 +"%"; - -},{ passive: false }) - - - -//hide the element after touch endas , yes endas -el.addEventListener("touchend",(e)=>{ -el.style.opacity="0"; -},{ passive: false }); - -} - -} - - - -}catch(e){ - console.log(e) -} - - - - - - - - - - - -/*Check If Element Already Exists*/ -if(document.getElementById("ytproMainDivE") == null){ - - - -var ytproMainDivA=document.createElement("div"); -ytproMainDivA.setAttribute("id","ytproMainDivE"); -ytproMainDivA.setAttribute("style",` -height:50px;width:100%;display:block;overflow:auto; -`); - -insertAfter(document.getElementsByClassName('slim-video-action-bar-actions')[0],ytproMainDivA); - -var ytproMainDiv=document.createElement("div"); -ytproMainDiv.setAttribute("style",` -height:50px;width:100%;display:flex;overflow:auto; -align-items:center;justify-content:flex-start;padding-left:20px;padding-right:10px; -`); -ytproMainDivA.appendChild(ytproMainDiv); - -/*Gemini Button*/ -var ytproGemini=document.createElement("div"); -sty(ytproGemini); -ytproGemini.style.width="115px"; -ytproGemini.style.height="calc(65% - 4.5px)"; -ytproGemini.style.position="relative"; -ytproGemini.style.background=`linear-gradient(${isD ? "#272727,#272727" : "#f2f2f2,#f2f2f2"}) padding-box , linear-gradient(16deg ,#4285f4 ,#9b72cb ,#d96570) border-box`; -ytproGemini.style.border="2px solid transparent"; -ytproGemini.innerHTML=` - -Gemini - -`; - - - - - - -ytproMainDiv.appendChild(ytproGemini); - - -ytproGemini.addEventListener("click", -async function(){ - - -if(parseFloat(Android.getInfo()) < parseFloat(YTProVer)){ -updateModel(); - -return; -} - -geminiInfo(); - - -}); - - - - - - - - - - - -/*Heart Button*/ -var ytproFavElem=document.createElement("div"); -sty(ytproFavElem); -if(!isHeart()){ -ytproFavElem.innerHTML=`${ytproT("heart")}`; -}else{ -ytproFavElem.innerHTML=`${ytproT("heart")}`; -} -ytproMainDiv.appendChild(ytproFavElem); -ytproFavElem.addEventListener("click",()=>{ytProHeart(ytproFavElem);}); - - - -/*Download Button*/ -var ytproDownVidElem=document.createElement("div"); -sty(ytproDownVidElem); -ytproDownVidElem.style.width="140px"; -ytproDownVidElem.innerHTML=`${downBtn.replace('width="18"','width="24"').replace('height="18"','height="24"')}${ytproT("download")}`; -ytproMainDiv.appendChild(ytproDownVidElem); -ytproDownVidElem.addEventListener("click", -function(){ -window.location.hash="download"; -}); - -/*PIP Button*/ -var ytproPIPVidElem=document.createElement("div"); -sty(ytproPIPVidElem); -ytproPIPVidElem.style.width="140px"; -ytproPIPVidElem.innerHTML=`${ytproT("pipMode")}`; -ytproMainDiv.appendChild(ytproPIPVidElem); -ytproPIPVidElem.addEventListener("click", -function(){ -PIPlayer(true); -}); - - - - - -} - - - - - -}else if(window.location.href.indexOf("youtube.com/shorts") > -1){ - - -let b = document.getElementById("brtS"); -let v = document.getElementById("volS"); -if (b) b.remove(); -if (v) v.remove(); - - -if(document.getElementById("ytproMainSDivE") == null){ -var ys=document.createElement("div"); -ys.setAttribute("id","ytproMainSDivE"); -ys.setAttribute("style",`width:50px;height:auto;position:relative;display:block;`); - - -/*Download Button*/ -ysDown=document.createElement("div"); -ysDown.setAttribute("style",` -height:48px;width:48px;display:flex;align-items:center;justify-content:center; -filter:drop-shadow(0 0 1px #0009); -border-radius:50%; -`); -ysDown.innerHTML=downBtn.replaceAll(`${c}`,`#fff`).replace(`width="24"`,`width="30"`).replace(`height="24"`,`height="30"`); - - -ysDown.addEventListener("click", -function(){ -window.location.hash="download"; -}); - - -/*Heart Button*/ -ysHeart=document.createElement("div"); -ysHeart.setAttribute("style",` -height:48px;width:48px; -display:flex;align-items:center;justify-content:center; -filter:drop-shadow(0 0 1px #0009); -border-radius:50%;margin-bottom:0px; -`); - - -if(!isHeart()){ -ysHeart.innerHTML=``; -}else{ -ysHeart.innerHTML=``; -} - - -ysHeart.addEventListener("click", -function(){ -ytProHeart(ysHeart); -}); - - - - - -try{ - - if(document.getElementsByClassName("reel-player-overlay-actions")[0].children[0]){ - -document.getElementsByClassName("reel-player-overlay-actions")[0].insertBefore(ys,document.getElementsByClassName("reel-player-overlay-actions")[0].children[1]); - -ys.appendChild(ysDown); -ys.appendChild(ysHeart); -} -}catch{} - -} - -try{document.querySelectorAll('dislike-button-view-model')[0].children[0].children[0].children[0].children[1].children[0].innerHTML=dislikes;}catch{} - - - - - -/*Watch The old and New URL* -if(ytoldV != window.location.pathname){ -fDislikes(); -ytoldV=window.location.pathname; -}*/ - - -} - -} - - -setInterval(pkc,0); - - - - - -/*SHOW HEARTS*/ -async function showHearts(){ -var ytproH=document.createElement("div"); -var ytproHh=document.createElement("div"); -ytproHh.setAttribute("id","heartytprodiv"); -ytproH.setAttribute("id","outerheartsdiv"); -ytproH.setAttribute("style",` -height:100%;width:100%;position:fixed;top:0;left:0; -display:flex;justify-content:center; -background:rgba(0,0,0,0.4); -z-index:99; -`); - -ytproHh.setAttribute("style",` -height:50%;width:85%;overflow:auto;background:${isD ? "#212121" : "#f1f1f1"}; -position:absolute;bottom:20px; -z-index:9;padding:20px;text-align:center;border-radius:25px;text-align:center; -`); -ytproHh.innerHTML=``; -ytproHh.innerHTML+="Liked Videos
        "; - - -ytproHh.innerHTML+=""; - -document.body.appendChild(ytproH); -ytproH.appendChild(ytproHh); - -ytproH.addEventListener("click", -function(ev){ -if(!event.composedPath().includes(ytproHh)){ -history.back(); -} -}); - - - -if(localStorage.getItem("hearts") == null){ -ytproHh.innerHTML+="No Videos Found"; -}else{ - -var v=JSON.parse(localStorage.getItem("hearts")); - -if(Object.keys(v).length === 0){ -return ytproHh.innerHTML+="No Videos Found"; -} - -for(var n=Object.keys(v).length - 1; n > -1 ; n--){ -var x=Object.keys(v)[n]; -ytproHh.innerHTML+=`
      • -
        -
        ${v[x].title}
        -
        - - - - -
        -
      • `; -await new Promise(r => setTimeout(r, 1)); -} - - -ytproHh.addEventListener("click",(e)=>{ - var el=e.target.closest("[data-action]"); - - if(!el) return; - if(el.dataset.action == "navigateInternalYtMweb"){ - navigateInternalYtMweb(el.dataset.id); - }else if(el.dataset.action == "remHeart"){ - remHeart(el,el.dataset.id); - } - -}); - -} - - - - - -} - - -function navigateInternalYtMweb(videoId) { - window.location.hash=""; - const link = document.createElement('a'); - link.href = `/watch?v=${videoId}`; - link.style.display = 'none'; - document.body.appendChild(link); - link.click(); - link.remove(); -} - - -/*Dil hata diya vro*/ -function remHeart(y,x){ -if(localStorage.getItem("hearts")?.indexOf(x) > -1){ -y.parentElement.parentElement.remove(); -var j=JSON.parse(localStorage.getItem("hearts") || "{}"); -delete j[x]; -localStorage.setItem("hearts",JSON.stringify(j)); -} - -} - -function ytProHeart(x){ - - -var vid=(new URLSearchParams(window.location.search)).get('v') || window.location.pathname.replace("/shorts/",""); - -var video=document.getElementsByClassName('video-stream')[0]; -var canvas = document.createElement('canvas'); -canvas.style.width = "1600px"; -canvas.style.height = "900px"; -canvas.style.background="black"; -var context = canvas.getContext('2d'); - -(window.location.pathname.indexOf("shorts") > -1) ? context.drawImage(video,105, 0, 90,160) : context.drawImage(video,0, 0, 320,180); - -var dataURI = canvas.toDataURL('image/jpeg'); - - -if(window.location.pathname.indexOf("shorts") > -1){ - -var vDetails={ -thumb:dataURI, -title:document.getElementsByClassName('ytShortsVideoTitleViewModelShortsVideoTitle')[0].textContent.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","") -}; - -}else{ - -var vDetails={ -thumb:dataURI, -title:document.getElementsByClassName('slim-video-metadata-header')[0].textContent.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","") -} - -/* -var vDetails={ -thumb:[...ytplayer.config.args.raw_player_response?.videoDetails?.thumbnail?.thumbnails].pop().url, -title:ytplayer.config.args.raw_player_response?.videoDetails?.title.replaceAll("|","").replaceAll("\\","").replaceAll("?","").replaceAll("*","").replaceAll("<","").replaceAll("/","").replaceAll(":","").replaceAll('"',"").replaceAll(">","") -};*/ - -} - - - -var g="16"; -var h=`Heart`; -(window.location.href.indexOf('youtube.com/shorts') > -1) ? h=``:h=`Heart`; -(window.location.href.indexOf('youtube.com/shorts') > -1) ? g="24" : g="24" ; - - - -if(localStorage.getItem("hearts")?.indexOf(vid) > -1){ -var j=JSON.parse(localStorage.getItem("hearts") || "{}"); -delete j[vid]; -localStorage.setItem("hearts",JSON.stringify(j)); -x.innerHTML=` -${h}`; -}else{ -var j=JSON.parse(localStorage.getItem("hearts") || "{}"); -j[vid]=vDetails; -localStorage.setItem("hearts",JSON.stringify(j)); -x.innerHTML=`${h}`; -} - -} - - - -/*Dil diya hai ya nhi diya!!*/ -function isHeart(){ - -if((localStorage.getItem("hearts")?.indexOf((new URLSearchParams(window.location.search)).get('v')) > -1) || (localStorage.getItem("hearts")?.indexOf(window.location.pathname.replace("/shorts/","")) > -1)){ -return true; -}else{ -return false; - -} -} - - - - - - -///PIP MODE CONFIG -function removePIP(){ - -isPIP=false; -pauseAllowed = true; -document.exitFullscreen(); - -document.getElementsByClassName('video-stream')[0].pause(); -setTimeout(()=>{ -document.getElementsByClassName('video-stream')[0].play(); -},5); - - -} - - - - -function PIPlayer(pip = false){ - -var v=document.getElementsByClassName('video-stream')[0]; - - -if(pip){ - -if(v.getBoundingClientRect().height > v.getBoundingClientRect().width){ -Android.pipvid("portrait"); -} -else{ -Android.pipvid("landscape"); -} - -return; -} - - -v.requestFullscreen(); -v.play(); -pauseAllowed = false; -isPIP=true; - -} - - - - - - - - - - - - - - - - - - - -// well this is for bypassing the pause function of Youtube when video is in -// PIP mode , its a workaround for now , until i find a proper method -// to allow the pip mode for the video element , like chromium browsers - -HTMLMediaElement.prototype.pause = function(){ - -if (pauseAllowed || PIPause) { -return originalPause.apply(this, arguments); -} - -if (this.paused) { -this.play().catch(() => {}); -} -}; - - - - - - - - - -const originalExitFullscreen = document.exitFullscreen; -const originalRequestFullscreen = Element.prototype.requestFullscreen; - -//exit full screen -document.exitFullscreen = function (...args) { - if(!isPIP){ return originalExitFullscreen.apply(this, args);} -}; - - -//request full screen -Element.prototype.requestFullscreen = function (...args) { -var video = document.getElementsByClassName('video-stream')[0]; - -if(video.getBoundingClientRect().height > video.getBoundingClientRect().width){ -Android.fullScreen(true); -} -else{ -Android.fullScreen(false); -} - -return originalRequestFullscreen.apply(this, args); -}; - - - - - - -/*Check The Hash Change*/ -window.onhashchange=()=>{ -try{document.getElementById("outerdownytprodiv").remove();}catch{} -try{document.getElementById("outerheartsdiv").remove();}catch{} -try{document.getElementById("settingsprodiv").remove();}catch{} -try{document.getElementById("ytproCommentsDiv").remove();}catch{} -//try{document.querySelector("#ytproDownloadIndicator").remove();}catch{} -//try{document.querySelector("#ytProDownloaderDiv").remove();}catch{} -if(window.location.hash == "#download"){ -ytproDownVid(); -}else if(window.location.hash == "#settings"){ -ytproSettings(); -} -else if(window.location.hash == "#hearts"){ -showHearts(); -}else if(window.location.hash == "#comments"){ -ytproCommentsPanel(); -} - - -} - - - -// AdBlocker which removes the ad contents from the fetch requests itself !! -(() => { -const _origFetch = window.fetch; -window.fetch = async function(input, init) { -try { -const url = (typeof input === 'string') ? input : input.url; - - - -//block ad urls -if(url.includes("googleads.g.doubleclick.net") || url.includes("youtube.com/youtubei/v1/player/ad_break") || url.includes("youtube.com/pagead/adview") || url.includes("youtube.com/api/stats/ads")){ - -//console.log("Blocked",url); -return ""; -}else if(url.includes("youtube.com/youtubei/")){ - - -const response = await _origFetch.apply(this, arguments); - - - -try { - -const clone = response.clone(); -let data = await clone.json(); - - -//older version -if(data?.responseContext?.webResponseContextExtensionData?.webResponseContextPreloadData?.preloadMessageNames?.[0] == "adSlotRenderer" || data?.responseContext?.webResponseContextExtensionData?.webResponseContextPreloadData?.preloadMessageNames?.[0] == "shortsAdsRenderer"){ -data={}; -} - - -//remove the ad content -delete data?.adSlots; -delete data?.playerAds; -delete data?.adPlacements; -delete data?.adBreakHeartbeatParams; - - -//newer version update: 09 Feb , 2026 23:27 IST -delete data?.[0]?.playerResponse?.adSlots; -delete data?.[0]?.playerResponse?.playerAds; -delete data?.[0]?.playerResponse?.adPlacements; -delete data?.[0]?.playerResponse?.adBreakHeartbeatParams; - - -const newBody = JSON.stringify(data); - -// Build new headers (update content-length + content-type) -const newHeaders = new Headers(response.headers); -newHeaders.set("content-length", String(newBody.length)); -newHeaders.set("content-type", "application/json"); - -// Return modified Response -return new Response(newBody, { -status: response.status, -statusText: response.statusText, -headers: newHeaders -}); -} catch (e) { -// not JSON, return original -return response; -} - - - -} - -return _origFetch.apply(this, arguments); - -} catch (e) { /* ignore logging errors */ } - -return _origFetch.apply(this, arguments); - - -}; - - -})(); - - - -//modified XHR for the same purpose -const XHR = window.XMLHttpRequest; -const origOpen = XHR.prototype.open; -const origSend = XHR.prototype.send; - -XHR.prototype.open = function(method, url, ...rest) { -this._interceptedMethod = method; -this._interceptedUrl = url; -return origOpen.apply(this, [method, url, ...rest]); -}; - -XHR.prototype.send = function(body) { -// Block certain URLs -if ( -this._interceptedUrl.includes("googleads.g.doubleclick.net") || -this._interceptedUrl.includes("youtube.com/youtubei/v1/player/ad_break") || -this._interceptedUrl.includes("youtube.com/pagead/adview") || -this._interceptedUrl.includes("youtube.com/api/stats/ads") -) { -//console.warn("Blocked:", this._interceptedUrl); -return; -} - -return origSend.apply(this, arguments); -}; - - - - - - - - - - - - -/****** I LOVE YOU <3 *****/ -/*YT ADS BLOCKER*/ -function adsBlock(){ - - -try{ -document.getElementsByClassName('video-stream')[0].removeAttribute('disablepictureinpicture'); -}catch{} - - -/*Block Ads*/ -var ads=document.getElementsByTagName("ad-slot-renderer"); -for(var x in ads){ -try{ads[x].remove();}catch{} -} -try{ -document.getElementsByClassName("ad-interrupting")[0].getElementsByTagName("video")[0].currentTime=document.getElementsByClassName("ad-interrupting")[0].getElementsByTagName("video")[0].duration; -document.getElementsByClassName("ytp-ad-skip-button-modern")[0].click(); - -}catch{} - - - - -/*Block Ads*/ -try{ -document.getElementsByTagName("ytm-promoted-sparkles-web-renderer")[0].remove(); -}catch{} -try{ -document.getElementsByTagName("ytm-companion-ad-renderer")[0].remove(); -}catch{} - -/*Remove Open App*/ -try{ -document.querySelectorAll('a').forEach(a => { -if (a.href.indexOf("intent://") > -1) { -a.style.display = 'none'; -} -}); -}catch{} -/*Remove Promotion Element*/ -try{document.getElementsByTagName("ytm-paid-content-overlay-renderer")[0].style.display="none";}catch{} - -/*Hide Shorts*/ -if(localStorage.getItem("shorts") == "true"){ - - -for( x in document.getElementsByClassName("big-shorts-singleton")){ -try{document.getElementsByClassName("big-shorts-singleton")[x].remove(); -}catch{} -} - -for( x in document.getElementsByTagName("ytm-reel-shelf-renderer")){ -try{document.getElementsByTagName("ytm-reel-shelf-renderer")[x].remove(); -}catch{} - -for( x in document.getElementsByTagName("ytm-shorts-lockup-view-model")){ -try{document.getElementsByTagName("ytm-shorts-lockup-view-model")[x].remove(); -}catch{} - -} - -} -} - - - - -} - - - - - -//Add Maximize Gesture -function addMaxButton(){ - - -var pElem=document.getElementById('player-container-id'); -var Ve=document.getElementById('player'); -var Vv=document.getElementsByClassName('video-stream')[0]; - - - -if(pElem === document.fullscreenElement){ - - -try{ -if(zoomIn){ -Ve.style.transform=`scale(${scale})`; -}else{ -Ve.style.transform="scale(1)"; -} -}catch{} - - -}else{ -try{ -Ve.style.transform="scale(1)"; -}catch{} -} - - -} - - -async function extraSpeed(){ - var el=document.querySelector(".ytwVariableSpeedControllerViewModelButtonContainer"); - if(!el) return; - - -const slider = document.getElementById("slider"); - -if(slider.max==10) return; - -slider.max = 10; -slider.ariaValueMax = "10"; - -slider.addEventListener("input", () => { - const video = document.querySelector('.video-stream'); - if (video) video.playbackRate = parseFloat(slider.value); -}); - -if(el.children.length >= 6) el.children[0].remove(); - -if(!document.getElementById("10xSpeed")){ - -var elm=document.createElement("ytw-variable-speed-controller-speed-button-view-model"); - -elm.id="10xSpeed"; -elm.className="ytwVariableSpeedControllerSpeedButtonViewModelHost ytwVariableSpeedControllerViewModelPlaybackSpeedButton"; - - -elm.insertAdjacentHTML("beforeend",``); - -elm.addEventListener("click", () => { - // document.querySelector('.video-stream').playbackRate = 10; - slider.value=10; - slider.dispatchEvent(new Event("input", { bubbles: true })); - -}); - -el.appendChild(elm); - - -} - -} - - -//https://youtube.com/watch?v=SInH_fP0deQ - - - -/*Mutation Observer*/ -//as i have been developing YTPRO for almost 4 years now -//thus it still contains the code which i used when i was a -//totally noob in copy pasting , that time i wasn't aware of -//plenty of things and by which i used `setInterval` instead -//of mutation observer , i shall be optimizing the code in future -//releases but rn only a few code blocks will be in the obesrver - -const targetNode = document.body; -const config = { childList: true, subtree: true }; - -const observer = new MutationObserver(() => { - - -//speed - -extraSpeed(); - -//ads Block -adsBlock(); - - -//mE button -addMaxButton(); - -//settingsTab -addSettingsTab(); -ensureCommentButton(); - - -try{ -var video = document.getElementsByClassName('video-stream')[0]; -if(video.getBoundingClientRect().height > video.getBoundingClientRect().width){ -Android.fullScreen(true); -} -else{ -Android.fullScreen(false); -}} -catch{} - - -}); - -// Start observing changes in the body -observer.observe(targetNode, config); - - - - - -/*Update your app bruh*/ -function updateModel(){ -var x=document.createElement("div"); - -x.setAttribute("style",`height:100%;width:100%;position:fixed;display:grid;align-items:center;top:0;left:0;background:rgba(0,0,0,.6);z-index:99999;`); - -x.innerHTML=` -
        -

        Mandatory Update


        -Latest Version ${YTProVer} of YTPRO is available , update the YTPRO to get latest features. -
        - This update is mandatory as it fixes a ton of bugs and improves functionality
        -- Fixed Downloads, switched to SABR downloader
        -- Added muxing to the youtube videos
        -- Fixed gestures for brightness and volume control
        -- Optimized the UI of both Download and Settings menu
        -- Added speed increase upto 10x
        -- Fixed bugs and improved functionality
        -- for the full list click here -
        -
        -
        - - -
        - -
        -`; - -x.addEventListener("click",(e)=>{ - var el=e.target.closest("[data-action]"); - if(!el) return; - var action=el.dataset.action; - - if(action == "url"){ - Android.oplink('https://github.com/prateek-chaubey/YTPRO/releases'); - }else if(action == "download"){ - Android.downvid('YTPRO.zip','https://nightly.link/prateek-chaubey/YTPro/workflows/gradle/main/YTPRO.zip','application/zip'); - }else if(action =="cancel"){ - el.parentElement.parentElement.parentElement.remove(); - } - -}) - -document.body.appendChild(x); -} - - - - - -window.onload = function(){ -if(parseFloat(Android.getInfo()) < parseFloat(YTProVer) && (window.location.href == "https://m.youtube.com/" || window.location.href == "https://m.youtube.com") ){ -updateModel(); -} - -}; - - - - -document.addEventListener('click',(event) => { - -let anchor = event.target.closest('a'); -if (anchor){ - - -if(anchor.href.includes("www.youtube.com/redirect")){ - -try{ -document.getElementsByClassName('video-stream')[0].pause(); -}catch{} - -const url=new URL(anchor.href).searchParams.get("q"); - -setTimeout(()=>{Android.oplink(url)},50); - -event.preventDefault(); -event.stopPropagation(); - -} - - -} -}, -true); - - - - -} diff --git a/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java b/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java index 8552d32f..f5d05afd 100644 --- a/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java +++ b/app/src/main/java/com/google/android/youtube/pro/webview/YTProWebViewClient.java @@ -34,10 +34,6 @@ public YTProWebViewClient(MainActivity activity, YTProWebView web) { public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { String url = request.getUrl().toString(); - if (url.contains("youtube.com/ytpro_local/")) { - return getLocalYtProAsset(url); - } - if (url.contains("accounts.google.com") || url.contains("myaccount.google.com") || url.contains("accounts.youtube.com") || @@ -185,32 +181,13 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque return super.shouldInterceptRequest(view, request); } - - private WebResourceResponse getLocalYtProAsset(String url) { - try { - String fileName = url.substring(url.lastIndexOf('/') + 1); - String mimeType = fileName.endsWith(".js") ? "application/javascript" : "text/plain"; - InputStream stream = activity.getAssets().open("ytpro/" + fileName); - - Map headers = new HashMap<>(); - headers.put("Access-Control-Allow-Origin", "*"); - headers.put("Access-Control-Allow-Methods", "GET, OPTIONS"); - headers.put("Access-Control-Allow-Headers", "*"); - headers.put("Cache-Control", "no-cache, no-store, must-revalidate"); - - return new WebResourceResponse(mimeType, "utf-8", 200, "OK", headers, stream); - } catch (Exception e) { - Log.e("YTPRO_WVC", "Local asset fetch failed: " + e.getMessage()); - return null; - } - } @Override public void onPageFinished(WebView view, String url) { web.evaluateJavascript("if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {window.trustedTypes.createPolicy('default', {createHTML: (string) => string,createScriptURL: string => string, createScript: string => string, });}", null); - web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_local/script.js'; document.body.appendChild(script); })();", null); - web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_local/bgplay.js'; document.body.appendChild(script); })();", null); - web.evaluateJavascript("(function () { var script = document.createElement('script');script.type='module';script.src='https://youtube.com/ytpro_local/innertube.js'; document.body.appendChild(script); })();", null); + web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_cdn/npm/ytpro@latest'; document.body.appendChild(script); })();", null); + web.evaluateJavascript("(function () { var script = document.createElement('script'); script.src='https://youtube.com/ytpro_cdn/npm/ytpro@latest/bgplay.js'; document.body.appendChild(script); })();", null); + web.evaluateJavascript("(function () { var script = document.createElement('script');script.type='module';script.src='https://youtube.com/ytpro_cdn/npm/ytpro@latest/innertube.js'; document.body.appendChild(script); })();", null); diff --git a/scripts/script.js b/scripts/script.js index c8c46a58..06674cca 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -1057,12 +1057,40 @@ return window.location.pathname.replace("/shorts/",""); return new URLSearchParams(window.location.search).get("v"); } +function ytproTapElement(el){ +if(!el) return; +var rect = el.getBoundingClientRect(); +var x = rect.left + (rect.width / 2); +var y = rect.top + (rect.height / 2); +var opts = {bubbles:true, cancelable:true, composed:true, view:window, clientX:x, clientY:y}; +try{ el.dispatchEvent(new PointerEvent("pointerdown", opts)); }catch(e){} +try{ el.dispatchEvent(new MouseEvent("mousedown", opts)); }catch(e){} +try{ el.dispatchEvent(new PointerEvent("pointerup", opts)); }catch(e){} +try{ el.dispatchEvent(new MouseEvent("mouseup", opts)); }catch(e){} +try{ el.dispatchEvent(new MouseEvent("click", opts)); }catch(e){ try{ el.click(); }catch(_){} } +} + function openOriginalComments(){ -var selectors = ["ytm-item-section-renderer", "ytd-comments", "ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer"]; +var selectors = ["ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytd-comments-header-renderer", "ytd-comments"]; for(var i = 0; i < selectors.length; i++){ var el = document.querySelector(selectors[i]); if(el){ -el.scrollIntoView({behavior:"smooth", block:"start"}); +el.scrollIntoView({behavior:"smooth", block:"center"}); +var clickable = el.querySelector("button, a, [role='button']") || el; +setTimeout(function(target, fallback){ +ytproTapElement(target); +if(target !== fallback) ytproTapElement(fallback); +}, 120, clickable, el); +return true; +} +} + +var sections = Array.from(document.querySelectorAll("ytm-item-section-renderer")); +for(var k = 0; k < sections.length; k++){ +var text = sections[k].innerText || ""; +if(text.indexOf(ytproT("comments")) > -1 || text.indexOf("Comments") > -1 || text.indexOf("评论") > -1){ +sections[k].scrollIntoView({behavior:"smooth", block:"center"}); +setTimeout(function(target){ ytproTapElement(target); }, 120, sections[k]); return true; } } @@ -1086,10 +1114,16 @@ if(!host || !host.querySelector("div")) return; var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; -btn.style.width = "110px"; +btn.style.width = "96px"; btn.innerHTML = `${ytproT("comments")}`; btn.addEventListener("click", ytproCommentsPanel); -host.querySelector("div").appendChild(btn); +var toolbar = host.querySelector("div"); +var anchor = toolbar.children.length > 1 ? toolbar.children[1] : null; +if(anchor){ +toolbar.insertBefore(btn, anchor); +}else{ +toolbar.appendChild(btn); +} } function ytproCommentsPanel(){ @@ -1928,7 +1962,7 @@ insertAfter(document.getElementsByClassName('slim-video-action-bar-actions')[0], var ytproMainDiv=document.createElement("div"); ytproMainDiv.setAttribute("style",` height:50px;width:100%;display:flex;overflow:auto; -align-items:center;justify-content:center;padding-left:20px;padding-right:10px; +align-items:center;justify-content:flex-start;padding-left:20px;padding-right:10px; `); ytproMainDivA.appendChild(ytproMainDiv); From ce370c537512e48593a5be275d707da8f1d66f6d Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 25 Jul 2026 22:02:03 +0800 Subject: [PATCH 10/11] Open native live chat from comments shortcut --- scripts/script.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/scripts/script.js b/scripts/script.js index 06674cca..b3ea5c51 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -62,6 +62,8 @@ var YTProLocales = { commentsOnlyWatch: "Comments are available on video pages", openYouTubeComments: "Open YouTube comments", originalCommentsOpened: "Opened original YouTube comments.", + liveChat: "Live chat", + liveChatOpened: "Opened YouTube live chat.", download: "Download", heart: "Heart", pipMode: "PIP Mode", @@ -101,6 +103,8 @@ var YTProLocales = { commentsOnlyWatch: "评论区仅在视频页面可用", openYouTubeComments: "打开 YouTube 评论", originalCommentsOpened: "已打开 YouTube 原生评论区。", + liveChat: "直播聊天", + liveChatOpened: "已打开 YouTube 直播聊天。", download: "下载", heart: "收藏", pipMode: "画中画", @@ -1057,6 +1061,28 @@ return window.location.pathname.replace("/shorts/",""); return new URLSearchParams(window.location.search).get("v"); } +function ytproPlayerResponse(){ +try{ if(window.ytInitialPlayerResponse) return window.ytInitialPlayerResponse; }catch(e){} +try{ if(ytplayer?.config?.args?.raw_player_response) return ytplayer.config.args.raw_player_response; }catch(e){} +try{ if(window.ytplayer?.config?.args?.raw_player_response) return window.ytplayer.config.args.raw_player_response; }catch(e){} +return null; +} + +function ytproIsLiveVideo(){ +var response = ytproPlayerResponse(); +try{ +var details = response?.videoDetails || {}; +var microformat = response?.microformat?.playerMicroformatRenderer || {}; +if(details.isLive || details.isLiveContent || microformat.liveBroadcastDetails || microformat.isLiveBroadcast) return true; +}catch(e){} + +var selectors = ["ytm-live-chat-entry-point-renderer", "ytm-live-chat-renderer", "ytd-live-chat-frame", "yt-live-chat-app"]; +for(var i = 0; i < selectors.length; i++){ +if(document.querySelector(selectors[i])) return true; +} +return false; +} + function ytproTapElement(el){ if(!el) return; var rect = el.getBoundingClientRect(); @@ -1070,6 +1096,42 @@ try{ el.dispatchEvent(new MouseEvent("mouseup", opts)); }catch(e){} try{ el.dispatchEvent(new MouseEvent("click", opts)); }catch(e){ try{ el.click(); }catch(_){} } } +function openYouTubeLiveChat(){ +var selectors = ["ytm-live-chat-entry-point-renderer", "ytm-live-chat-renderer", "ytd-live-chat-frame", "yt-live-chat-app"]; +for(var i = 0; i < selectors.length; i++){ +var el = document.querySelector(selectors[i]); +if(el){ +el.scrollIntoView({behavior:"smooth", block:"center"}); +var clickable = el.querySelector("button, a, [role='button']") || el; +setTimeout(function(target, fallback){ +ytproTapElement(target); +if(target !== fallback) ytproTapElement(fallback); +}, 120, clickable, el); +return true; +} +} + +var liveLabels = ["Live chat", "Chat", "实时聊天", "直播聊天", "聊天室"]; +var candidates = Array.from(document.querySelectorAll("button, a, [role='button'], ytm-button-renderer, ytm-toggle-button-renderer")); +for(var k = 0; k < candidates.length; k++){ +var label = ((candidates[k].innerText || "") + " " + (candidates[k].getAttribute("aria-label") || "") + " " + (candidates[k].title || "")).trim(); +for(var j = 0; j < liveLabels.length; j++){ +if(label.indexOf(liveLabels[j]) > -1){ +candidates[k].scrollIntoView({behavior:"smooth", block:"center"}); +setTimeout(function(target){ ytproTapElement(target); }, 120, candidates[k]); +return true; +} +} +} + +var vid = getVideoIdFromUrl(); +if(vid){ +Android.oplink("https://www.youtube.com/live_chat?v=" + encodeURIComponent(vid) + "&is_popout=1"); +return true; +} +return false; +} + function openOriginalComments(){ var selectors = ["ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytd-comments-header-renderer", "ytd-comments"]; for(var i = 0; i < selectors.length; i++){ @@ -1115,7 +1177,7 @@ var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "96px"; -btn.innerHTML = `${ytproT("comments")}`; +btn.innerHTML = `${ytproT(ytproIsLiveVideo() ? "liveChat" : "comments")}`; btn.addEventListener("click", ytproCommentsPanel); var toolbar = host.querySelector("div"); var anchor = toolbar.children.length > 1 ? toolbar.children[1] : null; @@ -1135,6 +1197,11 @@ Android.showToast(ytproT("commentsOnlyWatch")); return; } +if(ytproIsLiveVideo() && openYouTubeLiveChat()){ +Android.showToast(ytproT("liveChatOpened")); +return; +} + if(openOriginalComments()){ Android.showToast(ytproT("originalCommentsOpened")); return; From 45e6db836989dc4af4e55a4e5c11119f7aac5c9e Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 26 Jul 2026 00:40:02 +0800 Subject: [PATCH 11/11] Improve inline comments and live chat --- scripts/script.js | 175 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 137 insertions(+), 38 deletions(-) diff --git a/scripts/script.js b/scripts/script.js index b3ea5c51..fce2e73b 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -1080,6 +1080,19 @@ var selectors = ["ytm-live-chat-entry-point-renderer", "ytm-live-chat-renderer", for(var i = 0; i < selectors.length; i++){ if(document.querySelector(selectors[i])) return true; } + +try{ +var scripts = document.querySelectorAll("script"); +for(var j = 0; j < scripts.length; j++){ +var scriptText = scripts[j].textContent || ""; +if(scriptText.indexOf('"isLiveContent":true') > -1 || scriptText.indexOf('"isLive":true') > -1 || scriptText.indexOf('liveChatRenderer') > -1 || scriptText.indexOf('liveChatEndpoint') > -1 || scriptText.indexOf('liveBroadcastDetails') > -1) return true; +} +}catch(e){} + +try{ +var watchText = ((document.querySelector("ytm-watch") || document.body).innerText || ""); +if(/正在直播|直播中|实时聊天|直播聊天|聊天室|Live chat|Top chat|Chat replay/.test(watchText)) return true; +}catch(e){} return false; } @@ -1111,7 +1124,14 @@ return true; } } -var liveLabels = ["Live chat", "Chat", "实时聊天", "直播聊天", "聊天室"]; +var links = Array.from(document.querySelectorAll('a[href*="live_chat"], a[href*="live-chat"]')); +for(var h = 0; h < links.length; h++){ +links[h].scrollIntoView({behavior:"smooth", block:"center"}); +setTimeout(function(target){ ytproTapElement(target); }, 120, links[h]); +return true; +} + +var liveLabels = ["Live chat", "Top chat", "Chat replay", "Open chat", "Show chat", "Chat", "实时聊天", "直播聊天", "直播聊天室", "热门聊天", "聊天室", "打开聊天", "显示聊天"]; var candidates = Array.from(document.querySelectorAll("button, a, [role='button'], ytm-button-renderer, ytm-toggle-button-renderer")); for(var k = 0; k < candidates.length; k++){ var label = ((candidates[k].innerText || "") + " " + (candidates[k].getAttribute("aria-label") || "") + " " + (candidates[k].title || "")).trim(); @@ -1124,14 +1144,53 @@ return true; } } -var vid = getVideoIdFromUrl(); -if(vid){ -Android.oplink("https://www.youtube.com/live_chat?v=" + encodeURIComponent(vid) + "&is_popout=1"); -return true; -} return false; } +function ytproLiveChatUrl(vid){ +if(!vid) return ""; +return "https://www.youtube.com/live_chat?v=" + encodeURIComponent(vid) + "&embed_domain=m.youtube.com&is_popout=1"; +} + +function ytproOpenInternalUrl(url){ +if(!url) return; +try{ +var link = document.createElement("a"); +link.href = url; +link.target = "_self"; +link.rel = "noreferrer"; +link.style.display = "none"; +document.body.appendChild(link); +link.click(); +link.remove(); +}catch(e){ +window.location.href = url; +} +} + +function ytproEmbedLiveChat(host, vid){ +var url = ytproLiveChatUrl(vid); +if(!host || !url) return; +var existing = document.getElementById("ytproLiveChatInline"); +if(existing){ existing.remove(); } + +var wrap = document.createElement("div"); +wrap.id = "ytproLiveChatInline"; +wrap.style.cssText = "margin-top:14px;border-radius:0;overflow:hidden;background:" + (isD ? "#0f0f0f" : "#fff") + ";border-top:1px solid " + (isD ? "#333" : "#ddd") + ";border-bottom:1px solid " + (isD ? "#333" : "#ddd") + ";"; +wrap.innerHTML = '
        ' + + '' + ytproT("liveChat") + '' + + '' + + '
        ' + + ''; + +wrap.addEventListener("click", function(ev){ +var btn = ev.target.closest("[data-action]"); +if(btn && btn.dataset.action === "closeInlineLiveChat") wrap.remove(); +}); +host.appendChild(wrap); +wrap.scrollIntoView({behavior:"smooth", block:"center"}); +} + function openOriginalComments(){ var selectors = ["ytm-comments-entry-point-header-renderer", "ytm-comment-section-renderer", "ytd-comments-header-renderer", "ytd-comments"]; for(var i = 0; i < selectors.length; i++){ @@ -1177,8 +1236,27 @@ var btn = document.createElement("div"); sty(btn); btn.id = "ytproCommentsBtn"; btn.style.width = "96px"; +btn.style.position = "relative"; +btn.style.zIndex = "2147483647"; +btn.style.touchAction = "manipulation"; btn.innerHTML = `${ytproT(ytproIsLiveVideo() ? "liveChat" : "comments")}`; -btn.addEventListener("click", ytproCommentsPanel); +var ytproLastCommentsTouch = 0; +function ytproActivateCommentsButton(ev){ +var now = Date.now(); +if(ev.type === "click" && now - ytproLastCommentsTouch < 700){ +ev.preventDefault(); +ev.stopPropagation(); +if(ev.stopImmediatePropagation) ev.stopImmediatePropagation(); +return; +} +if(ev.type !== "click") ytproLastCommentsTouch = now; +ev.preventDefault(); +ev.stopPropagation(); +if(ev.stopImmediatePropagation) ev.stopImmediatePropagation(); +ytproCommentsPanel(); +} +btn.addEventListener("touchend", ytproActivateCommentsButton, {capture:true, passive:false}); +btn.addEventListener("click", ytproActivateCommentsButton, true); var toolbar = host.querySelector("div"); var anchor = toolbar.children.length > 1 ? toolbar.children[1] : null; if(anchor){ @@ -1188,6 +1266,50 @@ toolbar.appendChild(btn); } } +function ytproShowInlineCommentsFallback(vid, autoLive){ +var existing = document.getElementById("ytproCommentsDiv"); +if(existing){ existing.remove(); } + +var isLiveFallback = !!autoLive; +var comments = document.createElement("div"); +comments.id = "ytproCommentsDiv"; +comments.style.cssText = "width:100%;max-width:none;margin:8px 0 14px 0;padding:16px 0;border-radius:0;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";box-sizing:border-box;font-size:14px;line-height:1.45;"; +comments.innerHTML = '
        ' + + '' + ytproT(isLiveFallback ? "liveChat" : "comments") + '' + + '' + + '
        ' + + (isLiveFallback ? '' : '
        ' + ytproT("commentsUnavailable") + '
        ') + + (isLiveFallback ? '' : '
        ' + + (vid ? '' : '') + + '' + + '
        ') + + '
        ' + (vid ? vid : "") + '
        '; + +comments.addEventListener("click", function(ev){ +var btn = ev.target.closest("[data-action]"); +if(!btn) return; +ev.preventDefault(); +ev.stopPropagation(); +if(ev.stopImmediatePropagation) ev.stopImmediatePropagation(); +if(btn.dataset.action === "closeComments") comments.remove(); +if(btn.dataset.action === "openNativeLiveChat") ytproEmbedLiveChat(comments, vid); +if(btn.dataset.action === "openNativeComments"){ +if(!openOriginalComments()) ytproOpenInternalUrl("https://m.youtube.com/watch?v=" + encodeURIComponent(vid || "") + "#comments"); +} +}); + +var host = document.getElementById("ytproMainDivE") || document.getElementById("player-container-id") || document.querySelector("ytm-watch"); +if(host && host.parentNode){ +host.parentNode.insertBefore(comments, host.nextSibling); +}else{ +document.body.appendChild(comments); +} +if(isLiveFallback && vid){ +setTimeout(function(){ ytproEmbedLiveChat(comments, vid); }, 120); +} +comments.scrollIntoView({behavior:"smooth", block:"center"}); +} + function ytproCommentsPanel(){ var existing = document.getElementById("ytproCommentsDiv"); if(existing){ existing.remove(); } @@ -1197,46 +1319,23 @@ Android.showToast(ytproT("commentsOnlyWatch")); return; } -if(ytproIsLiveVideo() && openYouTubeLiveChat()){ +if(openYouTubeLiveChat()){ Android.showToast(ytproT("liveChatOpened")); return; } +var vid = getVideoIdFromUrl(); +if(ytproIsLiveVideo()){ +ytproShowInlineCommentsFallback(vid, true); +return; +} + if(openOriginalComments()){ Android.showToast(ytproT("originalCommentsOpened")); return; } -var comments = document.createElement("div"); -comments.id = "ytproCommentsDiv"; -comments.style.cssText = "position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,.72);display:flex;align-items:flex-end;justify-content:center;"; - -var inner = document.createElement("div"); -inner.style.cssText = "width:calc(100% - 12px);max-width:900px;height:78%;background:" + (isD ? "#202020" : "#f4f4f4") + ";color:" + (isD ? "#f5f5f5" : "#222") + ";border-radius:18px 18px 0 0;overflow:auto;padding:12px;box-shadow:0 -2px 12px rgba(0,0,0,.35);"; - -var vid = getVideoIdFromUrl(); -inner.innerHTML = '
        ' + - '' + ytproT("comments") + '' + - '' + - '
        ' + - '
        Loading...
        ' + - '
        ' + (vid ? vid : "") + '
        '; - -comments.addEventListener("click", function(ev){ -if(ev.target === comments){ comments.remove(); } -var btn = ev.target.closest("[data-action]"); -if(btn && btn.dataset.action === "closeComments"){ comments.remove(); } -}); - -comments.appendChild(inner); -document.body.appendChild(comments); - -setTimeout(function(){ -document.getElementById("ytproCommentsBody").innerHTML = ytproT("commentsUnavailable") + '

        '; -document.getElementById("ytproCommentsBody").querySelector("[data-action='openNativeComments']").addEventListener("click", function(){ - Android.oplink("https://m.youtube.com/watch?v=" + (vid || "")); -}); -}, 50); +ytproShowInlineCommentsFallback(vid); } function checkUpdates(){