Free YouTube Downloader was reported 2021-07-18 as an unauthorized copy of iTubeGo YouTube Downloader.

iTubeGo YouTube Downloader uses the MIT License license. Visit tldrlegal.com for help understanding what this license means.

Script License Created Updated
Reported Free YouTube Downloader N/A 2021-03-14 09:56:38 UTC 2021-03-14 09:56:38 UTC
Original iTubeGo YouTube Downloader MIT 2020-05-08 04:15:37 UTC 2020-06-04 09:12:24 UTC
  • // ==UserScript==
  • -// @name iTubeGo YouTube Downloader
  • -// @namespace https://itubego.com/
  • -// @version 1.0.1
  • -// @date 2020-06-04
  • -// @description Download YouTube videos and audios for free without external service, convert YouTube to all formats.
  • -// @homepage https://itubego.com/
  • -// @icon https://keepvid.pro/assets/images/itubego.png
  • -// @updateURL https://itubego.com/chrome/itubego.meta.js
  • -// @downloadURL https://itubego.com/chrome/itubego.user.js
  • -// @author iTubeGo
  • +// @name Free YouTube Downloader
  • +// @version 1.1
  • +// @description Download YouTube videos without external service.
  • +// @author Burt G
  • // @match https://*.youtube.com/*
  • // @require https://unpkg.com/vue@2.6.10/dist/vue.js
  • // @require https://unpkg.com/xfetch-js@0.3.4/xfetch.min.js
  • +// @require https://unpkg.com/@ffmpeg/ffmpeg@0.6.1/dist/ffmpeg.min.js
  • // @require https://bundle.run/p-queue@6.3.0
  • // @grant GM_xmlhttpRequest
  • // @connect googlevideo.com
  • // @compatible firefox >=52
  • // @compatible chrome >=55
  • -// @license MIT
  • // ==/UserScript==
  • ;(function() {
  • const RESTORE_ORIGINAL_TITLE_FOR_CURRENT_VIDEO = true
  • const createLogger = (console, tag) =>
  • Object.keys(console)
  • - .map(k => [
  • - k,
  • - (...args) =>
  • - DEBUG
  • - ? console[k](tag + ': ' + args[0], ...args.slice(1))
  • - : void 0
  • - ])
  • + .map(k => [k, (...args) => (DEBUG ? console[k](tag + ': ' + args[0], ...args.slice(1)) : void 0)])
  • .reduce((acc, [k, fn]) => ((acc[k] = fn), acc), {})
  • const logger = createLogger(console, 'YTDL')
  • + const sleep = ms => new Promise(res => setTimeout(res, ms))
  • const LANG_FALLBACK = 'en'
  • const LOCALE = {
  • en: {
  • - togglelinks: 'Other formats',
  • + togglelinks: 'Show/Hide Links',
  • stream: 'Stream',
  • adaptive: 'Adaptive',
  • + videoid: 'Video ID: ',
  • + inbrowser_adaptive_merger: 'Online Adaptive Video & Audio Merger (FFmpeg)',
  • + dlmp4: 'Download high-resolution mp4 in one click',
  • get_video_failed:
  • - 'You seems to have AdBlocking extension installed, which blocks %s.
  • -Please add the following rule to the rule set, or it will prevent Local YouTube Downloader from working.
  • -
  • -PS: If it refuse to add that rule, you should uninstall it and use "uBlock Origin" instead.
  • -If you still don\'t understand what I am saying, just disable or uninstall all your ad blockers...'
  • + 'You seems to have ad-blocking extension installed, which blocks %s.
  • +Please add the following rule to the rule set, or it will prevent Local YouTube Downloader from working.
  • +
  • +P.S.: If adding of the rule is being refused, you should uninstall it and use uBlock Origin instead.
  • +If you still dont understand what I am saying, just disable or uninstall all your ad-blockers...',
  • + live_stream_disabled_message: 'Local YouTube Downloader is not available for live stream'
  • },
  • - 'zh-tw': {
  • - togglelinks: '顯示 / 隱藏連結',
  • - stream: '串流 Stream',
  • - adaptive: '自適應 Adaptive',
  • - get_video_failed:
  • - '您看起來有在使用擋廣告的擴充功能,而它將 %s 給阻擋了。\n請將下方的規則加入你的廣告阻擋器中,否則本地 YouTube 下載器無法正常運作。\n\nPS: 如它拒絕加入該規則,請將它移除並改為使用 "uBlock Origin"。\n如果你仍無法理解我在說什麼,那就直接把全部的廣告阻擋器停用或是移除掉...'
  • - },
  • - 'zh-hk': {
  • - togglelinks: '顯示 / 隱藏連結',
  • - stream: '串流 Stream',
  • - adaptive: '自適應 Adaptive',
  • - get_video_failed:
  • - '您睇來有用阻擋廣告嘅擴充功能,而佢阻擋咗 %s。\n請將下面嘅規則加到你嘅廣告阻擋器,否則本地 YouTube 下載器唔能夠正常運作。\n\nPS: 如果佢拒絕加入呢個規則,請將佢移除並改用 "uBlock Origin"。\n如果你仍然唔明我講乜,咁就直接停用或者移除全部廣告阻擋器...'
  • - },
  • - zh: {
  • - togglelinks: '显示 / 隐藏链接',
  • - stream: '串流 Stream',
  • - adaptive: '自适应 Adaptive',
  • - get_video_failed:
  • - '您看起来有在使用挡广告的扩充功能,而它将 %s 给阻挡了。\n请将下方的规则加入你的广告阻挡器中,否则本地 YouTube 下载器无法正常运作。\n\nPS: 如它拒绝加入该规则,请将它移除并改为使用 "uBlock Origin"。\n如果你仍无法理解我在说什么,那就直接把全部的广告阻挡器停用或是移除掉...'
  • - },
  • - kr: {
  • - togglelinks: '링크 보이기/숨기기',
  • - stream: '스트리밍',
  • - adaptive: '조정 가능한',
  • - },
  • - es: {
  • - togglelinks: 'Mostrar/Ocultar Links',
  • - stream: 'Stream',
  • - adaptive: 'Adaptable',
  • - },
  • - he: {
  • - togglelinks: 'הצג/הסתר קישורים',
  • - stream: 'סטרים',
  • - adaptive: 'אדפטיבי',
  • - },
  • - ru: {
  • - togglelinks: 'Показать/Скрыть ссылки',
  • - stream: 'Stream',
  • - adaptive: 'Адаптивная',
  • - get_video_failed:
  • - 'Похоже у вас установлено расширение AdBlock, которое блокирует %s.\nДобавьте следующее правило в исключение, иначе это помешает работе локального загрузчика YouTube.\n\nЗЫ: Если расширение отказывается добавить это правило, его следует удалить и использовать "uBlock Origin".\nЕсли вы все ещё не понимаете, о чём я говорю, просто отключите или удалите все свои блокировщики рекламы...'
  • }
  • + for (const [lang, data] of Object.entries(LOCALE)) {
  • + if (lang === LANG_FALLBACK) continue
  • + for (const key of Object.keys(LOCALE[LANG_FALLBACK])) {
  • + if (!(key in data)) {
  • + data[key] = LOCALE[LANG_FALLBACK][key]
  • }
  • + }
  • + }
  • +
  • const findLang = l => {
  • - // language resolution logic: zh-tw --(if not exists)--> zh --(if not exists)--> LANG_FALLBACK(en)
  • l = l.toLowerCase().replace('_', '-')
  • if (l in LOCALE) return l
  • else if (l.length > 2) return findLang(l.split('-')[0])
  • Object.assign(el, opts)
  • return el
  • }
  • -
  • const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
  • - const parseDecsig = function a(data){try{if(data.startsWith("var script")){const obj={},document={createElement:()=>obj,head:{appendChild:()=>{}}};eval(data),data=obj.innerHTML}const fnnameresult=/=([a-zA-Z0-9\$]+?)\(decodeURIComponent/.exec(data),fnname=fnnameresult[1],_argnamefnbodyresult=new RegExp(escapeRegExp(fnname)+"=function\\((.+?)\\){(.+?)}").exec(data),[_,argname,fnbody]=_argnamefnbodyresult,helpernameresult=/;(.+?)\..+?\(/.exec(fnbody),helpername=helpernameresult[1],helperresult=new RegExp("var "+escapeRegExp(helpername)+"={[\\s\\S]+?};").exec(data),helper=helperresult[0];return logger.log("parsedecsig result: %s=>{%s
  • -%s}",argname,helper,fnbody),new Function([argname],helper+"\n"+fnbody)}catch(e){logger.error("parsedecsig error: %o",e),logger.info("script content: %s",data),logger.info('If you encounter this error, please copy the full "script content" to https://pastebin.com/ for me.')}}
  • -
  • - const parseQuery = s =>
  • - [...new URLSearchParams(s).entries()].reduce(
  • - (acc, [k, v]) => ((acc[k] = v), acc),
  • - {}
  • -
  • + const parseDecsig = data => {
  • + try {
  • + if (data.startsWith('var script')) {
  • + // they inject the script via script tag
  • + const obj = {}
  • + const document = {
  • + createElement: () => obj,
  • + head: { appendChild: () => {} }
  • + }
  • + eval(data)
  • + data = obj.innerHTML
  • + }
  • + const fnnameresult = /=([a-zA-Z0-9\$]+?)\(decodeURIComponent/.exec(data)
  • + const fnname = fnnameresult[1]
  • + const _argnamefnbodyresult = new RegExp(escapeRegExp(fnname) + '=function\\((.+?)\\){(.+?)}').exec(data)
  • + const [_, argname, fnbody] = _argnamefnbodyresult
  • + const helpernameresult = /;(.+?)\..+?\(/.exec(fnbody)
  • + const helpername = helpernameresult[1]
  • + const helperresult = new RegExp('var ' + escapeRegExp(helpername) + '={[\\s\\S]+?};').exec(data)
  • + const helper = helperresult[0]
  • + logger.log(`parsedecsig result: %s=>{%s
  • +%s}`, argname, helper, fnbody)
  • + return new Function([argname], helper + '\n' + fnbody)
  • + } catch (e) {
  • + logger.error('parsedecsig error: %o', e)
  • + logger.info('script content: %s', data)
  • + logger.info(
  • + 'If you encounter this error, please copy the full "script content" to https://pastebin.com/ for me.'
  • + )
  • + }
  • + }
  • + const parseQuery = s => [...new URLSearchParams(s).entries()].reduce((acc, [k, v]) => ((acc[k] = v), acc), {})
  • + const getVideo = async (id, decsig) => {
  • + const data = await xf
  • + .get(`https://www.youtube.com/get_video_info?video_id=${id}&el=detailpage`)
  • + .text()
  • + .catch(err => null)
  • + if (!data) return 'Adblock conflict'
  • + const obj = parseQuery(data)
  • + const playerResponse = JSON.parse(obj.player_response)
  • + logger.log(`video %s data: %o`, id, obj)
  • + logger.log(`video %s playerResponse: %o`, id, playerResponse)
  • + if (obj.status === 'fail') {
  • + throw obj
  • + }
  • + let stream = []
  • + if (playerResponse.streamingData.formats) {
  • + stream = playerResponse.streamingData.formats.map(x =>
  • + Object.assign({}, x, parseQuery(x.cipher || x.signatureCipher))
  • +
  • )
  • - const getVideo = async function a(e,o){const c=await xf.get(`https://www.youtube.com/get_video_info?video_id=${e}&el=detailpage`).text().catch(e=>null);if(!c)return"Adblock conflict";const t=parseQuery(c),s=JSON.parse(t.player_response);if(logger.log("video %s data: %o",e,t),logger.log("video %s playerResponse: %o",e,s),"fail"===t.status)throw t;function a(e,o,c){return e.split(o).join(c)}let i=[];if(s.streamingData.formats){(i=s.streamingData.formats.map(e=>Object.assign({},e,parseQuery(e.cipher||e.signatureCipher)))).sort((e,o)=>e.qualityLabel>o.qualityLabel?-1:1);for(const e of i){const o=s.videoDetails.title,c=new URL(e.url);c.host="redirector.googlevideo.com",c.search+="&title="+encodeURI(o),e.url=c.href;const t=e.mimeType.split(";");e.format=t[0].split("/")[1].toUpperCase(),codecs=t[1].split("=")[1];const i=t[0].split("/")[0];"video"==i&&(e.vcodec=codecs.split(",")[0],e.acodec="none",codecs.split(",")[1]&&(e.acodec=codecs.split(",")[1])),"audio"==i&&(e.vcodec="none",e.acodec=codecs.split(",")[0]),e.vcodec=a(e.vcodec,'"',""),e.vcodec=a(e.vcodec," ",""),e.vcodec=e.vcodec.split(".")[0],e.acodec=a(e.acodec,'"',""),e.acodec=a(e.acodec," ",""),e.acodec=e.vcodec.split(".")[0]}if(logger.log("video %s stream: %o",e,i),i[0].sp&&i[0].sp.includes("sig"))for(const e of i)e.s=o(e.s),e.url+=`&sig=${e.s}`}let d=[];if(s.streamingData.adaptiveFormats){d=s.streamingData.adaptiveFormats.map(e=>Object.assign({},e,parseQuery(e.cipher||e.signatureCipher)));for(const e of d){const o=s.videoDetails.title,c=new URL(e.url);c.host="redirector.googlevideo.com",c.search+="&title="+encodeURI(o),e.url=c.href;const t=e.mimeType.split(";");e.format=t[0].split("/")[1].toUpperCase(),codecs=t[1].split("=")[1];const i=t[0].split("/")[0];"video"==i&&(e.vcodec=codecs.split(",")[0],e.acodec="none",codecs.split(",")[1]&&(e.acodec=codecs.split(",")[1])),"audio"==i&&(e.vcodec="none",e.acodec=codecs.split(",")[0],e.qualityLabel=parseInt(e.averageBitrate/1e3).toString()+"kbps"),e.vcodec=a(e.vcodec,'"',""),e.vcodec=a(e.vcodec," ",""),e.vcodec=e.vcodec.split(".")[0],e.acodec=a(e.acodec,'"',""),e.acodec=a(e.acodec," ",""),e.acodec=e.acodec.split(".")[0]}if(logger.log("video %s adaptive: %o",e,d),d[0].sp&&d[0].sp.includes("sig"))for(const e of d)e.s=o(e.s),e.url+=`&sig=${e.s}`}return logger.log("video %s result: %o",e,{stream:i,adaptive:d}),{stream:i,adaptive:d,meta:t}}
  • + logger.log(`video %s stream: %o`, id, stream)
  • + if (stream[0].sp && stream[0].sp.includes('sig')) {
  • + for (const obj of stream) {
  • + obj.s = decsig(obj.s)
  • + obj.url += `&sig=${obj.s}`
  • + }
  • + }
  • + }
  • + let adaptive = []
  • + if (playerResponse.streamingData.adaptiveFormats) {
  • + adaptive = playerResponse.streamingData.adaptiveFormats.map(x =>
  • + Object.assign({}, x, parseQuery(x.cipher || x.signatureCipher))
  • + )
  • + logger.log(`video %s adaptive: %o`, id, adaptive)
  • + if (adaptive[0].sp && adaptive[0].sp.includes('sig')) {
  • + for (const obj of adaptive) {
  • + obj.s = decsig(obj.s)
  • + obj.url += `&sig=${obj.s}`
  • + }
  • + }
  • + }
  • + logger.log(`video %s result: %o`, id, { stream, adaptive })
  • + return { stream, adaptive, meta: obj, playerResponse }
  • + }
  • const workerMessageHandler = async e => {
  • const decsig = await xf.get(e.data.path).text(parseDecsig)
  • try {
  • }
  • }
  • const ytdlWorkerCode = `
  • -importScripts('https://unpkg.com/vue@2.6.10/dist/vue.js')
  • importScripts('https://unpkg.com/xfetch-js@0.3.4/xfetch.min.js')
  • const DEBUG=${DEBUG}
  • const logger=(${createLogger})(console, 'YTDL')
  • const parseDecsig=${parseDecsig}
  • const getVideo=${getVideo}
  • self.onmessage=${workerMessageHandler}`
  • - const ytdlWorker = new Worker(
  • - URL.createObjectURL(new Blob([ytdlWorkerCode]))
  • - )
  • + const ytdlWorker = new Worker(URL.createObjectURL(new Blob([ytdlWorkerCode])))
  • const workerGetVideo = (id, path) => {
  • logger.log(`workerGetVideo start: %s %s`, id, path)
  • return new Promise((res, rej) => {
  • })
  • }
  • + const determineChunksNum = size => {
  • + const n = Math.ceil(size / (1024 * 1024 * 3)) // 3 MB
  • + return n
  • + }
  • + // video downloader
  • + const xhrDownloadUint8Array = async ({ url, contentLength }, progressCb) => {
  • + if (typeof contentLength === 'string') contentLength = parseInt(contentLength)
  • + progressCb({
  • + loaded: 0,
  • + total: contentLength,
  • + speed: 0
  • + })
  • + const chunkSize = Math.floor(contentLength / determineChunksNum(contentLength))
  • + const getBuffer = (start, end) =>
  • + new Promise((res, rej) => {
  • + const xhr = {}
  • + xhr.responseType = 'arraybuffer'
  • + xhr.method = 'GET'
  • + xhr.url = url
  • + xhr.headers = {
  • + 'User-Agent':
  • + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.124 Safari/537.36',
  • + Range: `bytes=${start}-${end ? end - 1 : ''}`,
  • + 'Accept-Encoding': 'identity',
  • + 'Accept-Language': 'en-us,en;q=0.5',
  • + 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'
  • + }
  • + xhr.onload = obj => res(obj.response)
  • + GM_xmlhttpRequest(xhr)
  • + })
  • + const data = new Uint8Array(contentLength)
  • + let downloaded = 0
  • + const queue = new pQueue.default({ concurrency: 5 })
  • + const startTime = Date.now()
  • + const ps = []
  • + for (let start = 0; start < contentLength; start += chunkSize) {
  • + const exceeded = start + chunkSize > contentLength
  • + const curChunkSize = exceeded ? contentLength - start : chunkSize
  • + const end = exceeded ? null : start + chunkSize
  • + const p = queue.add(() =>
  • + getBuffer(start, end).then(buf => {
  • + downloaded += curChunkSize
  • + data.set(new Uint8Array(buf), start)
  • + const ds = (Date.now() - startTime + 1) / 1000
  • + progressCb({
  • + loaded: downloaded,
  • + total: contentLength,
  • + speed: downloaded / ds
  • + })
  • + })
  • + )
  • + ps.push(p)
  • + }
  • + await Promise.all(ps)
  • + return data
  • + }
  • +
  • + const ffWorker = FFmpeg.createWorker({
  • + logger: DEBUG ? m => logger.log(m.message) : () => {}
  • + })
  • + let ffWorkerLoaded = false
  • + const mergeVideo = async (video, audio) => {
  • + if (!ffWorkerLoaded) await ffWorker.load()
  • + await ffWorker.write('video.mp4', video)
  • + await ffWorker.write('audio.mp4', audio)
  • + await ffWorker.run('-i video.mp4 -i audio.mp4 -c copy output.mp4', {
  • + input: ['video.mp4', 'audio.mp4'],
  • + output: 'output.mp4'
  • + })
  • + const { data } = await ffWorker.read('output.mp4')
  • + await ffWorker.remove('output.mp4')
  • + return data
  • + }
  • + const triggerDownload = (url, filename) => {
  • + const a = document.createElement('a')
  • + a.href = url
  • + a.download = filename
  • + document.body.appendChild(a)
  • + a.click()
  • + a.remove()
  • + }
  • + const dlModalTemplate = `
  • +<div style="width: 100%; height: 100%;">
  • + <div v-if="merging" style="height: 100%; width: 100%; display: flex; justify-content: center; align-items: center; font-size: 24px;">Merging video, please wait...</div>
  • + <div v-else style="height: 100%; width: 100%; display: flex; flex-direction: column;">
  • + <div style="flex: 1; margin: 10px;">
  • + <p style="font-size: 24px;">Video</p>
  • + <progress style="width: 100%;" :value="video.progress" min="0" max="100"></progress>
  • + <div style="display: flex; justify-content: space-between;">
  • + <span>{{video.speed}} kB/s</span>
  • + <span>{{video.loaded}}/{{video.total}} MB</span>
  • + </div>
  • + </div>
  • + <div style="flex: 1; margin: 10px;">
  • + <p style="font-size: 24px;">Audio</p>
  • + <progress style="width: 100%;" :value="audio.progress" min="0" max="100"></progress>
  • + <div style="display: flex; justify-content: space-between;">
  • + <span>{{audio.speed}} kB/s</span>
  • + <span>{{audio.loaded}}/{{audio.total}} MB</span>
  • + </div>
  • + </div>
  • + </div>
  • +</div>
  • +`
  • + function openDownloadModel(adaptive, title) {
  • + const win = open(
  • + '',
  • + 'Video Download',
  • + `toolbar=no,height=${screen.height / 2},width=${screen.width / 2},left=${screenLeft},top=${screenTop}`
  • + )
  • + const div = win.document.createElement('div')
  • + win.document.body.appendChild(div)
  • + win.document.title = `Downloading "${title}"`
  • + const dlModalApp = new Vue({
  • + template: dlModalTemplate,
  • + data() {
  • + return {
  • + video: {
  • + progress: 0,
  • + total: 0,
  • + loaded: 0,
  • + speed: 0
  • + },
  • + audio: {
  • + progress: 0,
  • + total: 0,
  • + loaded: 0,
  • + speed: 0
  • + },
  • + merging: false
  • + }
  • + },
  • + methods: {
  • + async start(adaptive, title) {
  • + win.onbeforeunload = () => true
  • + // YouTube's default order is descending by video quality
  • + const videoObj = adaptive
  • + .filter(x => x.mimeType.includes('video/mp4'))
  • + .map(v => {
  • + const [_, quality, fps] = /(\d+)p(\d*)/.exec(v.qualityLabel)
  • + v.qualityNum = parseInt(quality)
  • + v.fps = fps ? parseInt(fps) : 30
  • + return v
  • + })
  • + .sort((a, b) => {
  • + if (a.qualityNum === b.qualityNum) return b.fps - a.fps // ex: 30-60=-30, then a will be put before b
  • + return b.qualityNum - a.qualityNum
  • + })[0]
  • + const audioObj = adaptive.find(x => x.mimeType.includes('audio/mp4'))
  • + const vPromise = xhrDownloadUint8Array(videoObj, e => {
  • + this.video.progress = (e.loaded / e.total) * 100
  • + this.video.loaded = (e.loaded / 1024 / 1024).toFixed(2)
  • + this.video.total = (e.total / 1024 / 1024).toFixed(2)
  • + this.video.speed = (e.speed / 1024).toFixed(2)
  • + })
  • + const aPromise = xhrDownloadUint8Array(audioObj, e => {
  • + this.audio.progress = (e.loaded / e.total) * 100
  • + this.audio.loaded = (e.loaded / 1024 / 1024).toFixed(2)
  • + this.audio.total = (e.total / 1024 / 1024).toFixed(2)
  • + this.audio.speed = (e.speed / 1024).toFixed(2)
  • + })
  • + const [varr, aarr] = await Promise.all([vPromise, aPromise])
  • + this.merging = true
  • + win.onunload = () => {
  • + // trigger download when user close it
  • + const bvurl = URL.createObjectURL(new Blob([varr]))
  • + const baurl = URL.createObjectURL(new Blob([aarr]))
  • + triggerDownload(bvurl, title + '-videoonly.mp4')
  • + triggerDownload(baurl, title + '-audioonly.mp4')
  • + }
  • + const result = await Promise.race([mergeVideo(varr, aarr), sleep(1000 * 25).then(() => null)])
  • + if (!result) {
  • + alert('An error has occurred when merging video')
  • + const bvurl = URL.createObjectURL(new Blob([varr]))
  • + const baurl = URL.createObjectURL(new Blob([aarr]))
  • + triggerDownload(bvurl, title + '-videoonly.mp4')
  • + triggerDownload(baurl, title + '-audioonly.mp4')
  • + return this.close()
  • + }
  • + this.merging = false
  • + const url = URL.createObjectURL(new Blob([result]))
  • + triggerDownload(url, title + '.mp4')
  • + win.onbeforeunload = null
  • + win.onunload = null
  • + win.close()
  • + }
  • + }
  • + }).$mount(div)
  • + dlModalApp.start(adaptive, title)
  • + }
  • +
  • const template = `
  • <div class="box" :class="{'dark':dark}">
  • - <div v-if="1" class="of-h t-center lh-20 button-container">
  • - <a class="button c-pointer" :href="stream[0].url" target="_blank">
  • - <svg t="1588821961308" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11214" width="24" height="24"><path d="M906.960201 111.639098h-187.989975a37.213033 37.213033 0 0 0-36.250627 36.250626 37.213033 37.213033 0 0 0 36.250627 36.250627h187.989975a46.516291 46.516291 0 0 1 43.949874 48.761905v668.230576a46.516291 46.516291 0 0 1-43.949874 48.761905H117.145664a46.516291 46.516291 0 0 1-43.949875-48.761905V233.223058a46.516291 46.516291 0 0 1 43.949875-48.441103h189.914787a36.571429 36.571429 0 0 0 0-72.822055H117.145664A118.37594 118.37594 0 0 0 0.052932 233.864662v668.230576a119.659148 119.659148 0 0 0 117.092732 121.904762h792.380953a119.659148 119.659148 0 0 0 117.092731-121.904762V233.223058a121.58396 121.58396 0 0 0-119.659147-121.58396z" fill="#ffffff" p-id="11215"></path><path d="M305.135639 481.203008a34.646617 34.646617 0 0 0 0 51.32832l179.969925 179.969925 2.566416 2.566416a2.566416 2.566416 0 0 1 2.566416 2.566416c2.566416 2.566416 5.132832 2.566416 7.378446 5.132832s2.566416 0 5.132832 2.566416 4.81203 2.566416 9.62406 2.566416a16.360902 16.360902 0 0 0 9.62406-2.566416c2.566416 0 2.566416 0 5.132833-2.566416s5.132832-2.566416 7.057644-5.132832a2.566416 2.566416 0 0 0 2.566416-2.566416l2.566416-2.566416 180.290727-179.969925a36.250627 36.250627 0 1 0-51.328321-51.32832l-119.017544 119.338345V36.250627a36.571429 36.571429 0 0 0-72.822055 0v563.007518L357.105564 481.203008a35.929825 35.929825 0 0 0-51.969925 0z" fill="#ffffff" p-id="11216"></path></svg>
  • - <span v-text="'MP4 (' + stream[0].qualityLabel + ')'"></span>
  • - </a>
  • - <a class="button c-pointer" href="https://itubego.com/youtube-downloader/?utm_source=Social&utm_medium=mp4_button&utm_campaign=Extension" target="_blank">
  • - <svg t="1588821961308" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11214" width="24" height="24"><path d="M906.960201 111.639098h-187.989975a37.213033 37.213033 0 0 0-36.250627 36.250626 37.213033 37.213033 0 0 0 36.250627 36.250627h187.989975a46.516291 46.516291 0 0 1 43.949874 48.761905v668.230576a46.516291 46.516291 0 0 1-43.949874 48.761905H117.145664a46.516291 46.516291 0 0 1-43.949875-48.761905V233.223058a46.516291 46.516291 0 0 1 43.949875-48.441103h189.914787a36.571429 36.571429 0 0 0 0-72.822055H117.145664A118.37594 118.37594 0 0 0 0.052932 233.864662v668.230576a119.659148 119.659148 0 0 0 117.092732 121.904762h792.380953a119.659148 119.659148 0 0 0 117.092731-121.904762V233.223058a121.58396 121.58396 0 0 0-119.659147-121.58396z" fill="#ffffff" p-id="11215"></path><path d="M305.135639 481.203008a34.646617 34.646617 0 0 0 0 51.32832l179.969925 179.969925 2.566416 2.566416a2.566416 2.566416 0 0 1 2.566416 2.566416c2.566416 2.566416 5.132832 2.566416 7.378446 5.132832s2.566416 0 5.132832 2.566416 4.81203 2.566416 9.62406 2.566416a16.360902 16.360902 0 0 0 9.62406-2.566416c2.566416 0 2.566416 0 5.132833-2.566416s5.132832-2.566416 7.057644-5.132832a2.566416 2.566416 0 0 0 2.566416-2.566416l2.566416-2.566416 180.290727-179.969925a36.250627 36.250627 0 1 0-51.328321-51.32832l-119.017544 119.338345V36.250627a36.571429 36.571429 0 0 0-72.822055 0v563.007518L357.105564 481.203008a35.929825 35.929825 0 0 0-51.969925 0z" fill="#ffffff" p-id="11216"></path></svg>
  • - <span>MP4 (HD)</span>
  • - </a>
  • - <a class="button c-pointer" href="https://itubego.com/youtube-to-mp3-downloader/?utm_source=Social&utm_medium=mp3_button&utm_campaign=Extension" target="_blank">
  • - <svg t="1588821961308" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11214" width="24" height="24"><path d="M906.960201 111.639098h-187.989975a37.213033 37.213033 0 0 0-36.250627 36.250626 37.213033 37.213033 0 0 0 36.250627 36.250627h187.989975a46.516291 46.516291 0 0 1 43.949874 48.761905v668.230576a46.516291 46.516291 0 0 1-43.949874 48.761905H117.145664a46.516291 46.516291 0 0 1-43.949875-48.761905V233.223058a46.516291 46.516291 0 0 1 43.949875-48.441103h189.914787a36.571429 36.571429 0 0 0 0-72.822055H117.145664A118.37594 118.37594 0 0 0 0.052932 233.864662v668.230576a119.659148 119.659148 0 0 0 117.092732 121.904762h792.380953a119.659148 119.659148 0 0 0 117.092731-121.904762V233.223058a121.58396 121.58396 0 0 0-119.659147-121.58396z" fill="#ffffff" p-id="11215"></path><path d="M305.135639 481.203008a34.646617 34.646617 0 0 0 0 51.32832l179.969925 179.969925 2.566416 2.566416a2.566416 2.566416 0 0 1 2.566416 2.566416c2.566416 2.566416 5.132832 2.566416 7.378446 5.132832s2.566416 0 5.132832 2.566416 4.81203 2.566416 9.62406 2.566416a16.360902 16.360902 0 0 0 9.62406-2.566416c2.566416 0 2.566416 0 5.132833-2.566416s5.132832-2.566416 7.057644-5.132832a2.566416 2.566416 0 0 0 2.566416-2.566416l2.566416-2.566416 180.290727-179.969925a36.250627 36.250627 0 1 0-51.328321-51.32832l-119.017544 119.338345V36.250627a36.571429 36.571429 0 0 0-72.822055 0v563.007518L357.105564 481.203008a35.929825 35.929825 0 0 0-51.969925 0z" fill="#ffffff" p-id="11216"></path></svg>
  • - <span>MP3 (320kbps)</span>
  • - </a>
  • - </div>
  • - <div class="t-center t-hint fs-14px">Note: Right-click the Download button if video not download, choose 'Save link as...' or 'Download link as...' option.</div>
  • - <div class="box-toggle div-a t-center fs-14px other-formats-btn">
  • - <span @click="hide=!hide" class="c-pointer">Other formats</span>
  • - <img src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABkAAD/7gAOQWRvYmUAZMAAAAAB/9sAhAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgICAgICAgICAgIDAwMDAwMDAwMDAQEBAQEBAQIBAQICAgECAgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwP/wAARCAAPAA8DAREAAhEBAxEB/8QAbgAAAwEAAAAAAAAAAAAAAAAAAAEDCQEAAgMAAAAAAAAAAAAAAAAAAQMAAgkQAAEFAQACAwEAAAAAAAAAAAYBAgMEBQcREgAiExURAAICAgICAgEFAAAAAAAAAAIDAQQFBhEHEhMAFCIhMXFSI//aAAwDAQACEQMRAD8A3T4vzwO6gLkuXWybd7qodZgNs/Fbq2alLooFnLAhQJ1GsSR9AnqxKs1WaH7TNejPT6Ok+Ya9P6JqHZGt38amox/ZeJaF9aPexYZTGrkYt0g4mfVbGJkksXESfkAyPAsONWuwtqz+mZmndc8F6TkAKobfUJHQus5+vZLniDrlPAsE/wBAmPLy/IR+HS+XhYCd86KYJtXW4B0mzjk2LowyI/bYLN0aLTAXsPZ6qwmG4pnwOTyq+XRqrlk90avsTrbT9I3fAbIgrFno7YG17aGxMy6KftX96oUxwUWawkQf24IOSlkM8ZqG57Fs+s5bCtFKO0cQDa7VzHCZs+s/q2Bif3r2JiDj+D4jx8Zm3EjCEdFDDCDxY72OhlyMoFZMJY0WtfEONV31ZSxRmKCSazBs6sitjsW7LIa0Ef5+HteiOV/Te2VsDrOUwuq4zOW9+yswu7bo1xc2jghkJufTgSIxsNmYE3NFalxKpg4MBkqdi4BmVzdDJZ+7jK+qUOTrV7TZWFrKlBRW+xMxAkpcckClybDLz5GRmY+PqZ7KTn3NSAoASUe4Zh/waPPw61QtZ0OjzTEvUF125Fywtejq627VarrduKeRFmlY10ytYx3wdmbw3Yt513O7Hg8hQ6ZpTWXjKLEmoW4pDFS/0EcipzrC4/1aDCjklATZEBL4NL1gMNrGXxeGylO12TZ9x3bQmLJXkHAfq9oD5GtSS4haiAfxEihcSRR8/9k=" />
  • + <template v-if="!isLiveStream">
  • + <div v-if="adaptive.length" class="of-h t-center c-pointer lh-20">
  • + <a class="fs-14px" @click="dlmp4" v-text="strings.dlmp4"></a>
  • </div>
  • + <div @click="hide=!hide" class="box-toggle div-a t-center fs-14px c-pointer lh-20" v-text="strings.togglelinks"></div>
  • <div :class="{'hide':hide}">
  • - <table class="other-formats-table">
  • - <tr>
  • - <th>Format</th>
  • - <th>Codecs</th>
  • - <th>Quality</th>
  • - <th>Download</th>
  • - </tr>
  • - <tr>
  • - <td>MP4</td>
  • - <td>video=<b>h264</b>, audio=<b>aac</b></td>
  • - <td>4k (iTubeGo)</td>
  • - <td style="color:#F59A23"><a href="https://itubego.com/youtube-downloader/?utm_source=Social&utm_medium=4k&utm_campaign=Extension" target="_blank">Install</a></td>
  • - </tr>
  • - <tr>
  • - <td>MP4</td>
  • - <td>video=<b>h264</b>, audio=<b>aac</b></td>
  • - <td>1080p (iTubeGo)</td>
  • - <td style="color:#F59A23"><a href="https://itubego.com/youtube-downloader/?utm_source=Social&utm_medium=1080p&utm_campaign=Extension" target="_blank">Install</a></td>
  • - </tr>
  • - <tr>
  • - <td>MP3</td>
  • - <td>audio=<b>mp3</b></td>
  • - <td>320kpbs (Musify)</td>
  • - <td style="color:#F59A23"><a href="https://itubego.com/youtube-to-mp3-downloader/?utm_source=Social&utm_medium=320kbps&utm_campaign=Extension" target="_blank">Install</a></td>
  • - </tr>
  • - <tr v-for="vid in stream">
  • - <td v-text="vid.format"></td>
  • - <td v-text="'video=' + vid.vcodec + ', audio=' + vid.acodec"></td>
  • - <td v-text="vid.qualityLabel"></td>
  • - <td style="color:#F59A23"><a :href="vid.url" target="_blank">Download</a></td>
  • - </tr>
  • - <tr v-for="vid in adaptive">
  • - <td v-text="vid.format"></td>
  • - <td v-text="'video=' + vid.vcodec + ', audio=' + vid.acodec"></td>
  • - <td v-text="vid.qualityLabel"></td>
  • - <td style="color:#F59A23"><a :href="vid.url" target="_blank">Download</a></td>
  • - </tr>
  • - </table>
  • + <div class="t-center fs-14px" v-text="strings.videoid+id"></div>
  • + <div class="d-flex">
  • + <div class="f-1 of-h">
  • + <div class="t-center fs-14px" v-text="strings.stream"></div>
  • + <a class="ytdl-link-btn fs-14px" target="_blank" v-for="vid in stream" :href="vid.url" :title="vid.type" v-text="formatStreamText(vid)"></a>
  • + </div>
  • + <div class="f-1 of-h">
  • + <div class="t-center fs-14px" v-text="strings.adaptive"></div>
  • + <a class="ytdl-link-btn fs-14px" target="_blank" v-for="vid in adaptive" :href="vid.url" :title="vid.type" v-text="formatAdaptiveText(vid)"></a>
  • + </div>
  • + </div>
  • + <div class="of-h t-center">
  • + <a class="fs-14px" href="https://maple3142.github.io/mergemp4/" target="_blank" v-text="strings.inbrowser_adaptive_merger"></a>
  • </div>
  • </div>
  • + </template>
  • + <template v-else>
  • + <div class="t-center fs-14px lh-20" v-text="strings.live_stream_disabled_message"></div>
  • + </template>
  • +</div>
  • `.slice(1)
  • const app = new Vue({
  • data() {
  • return {
  • hide: true,
  • id: '',
  • + isLiveStream: false,
  • stream: [],
  • adaptive: [],
  • meta: null,
  • }
  • },
  • methods: {
  • + dlmp4() {
  • + const r = JSON.parse(this.meta.player_response)
  • + openDownloadModel(this.adaptive, r.videoDetails.title)
  • + },
  • + formatStreamText(vid) {
  • + return [vid.qualityLabel, vid.quality].filter(x => x).join(': ')
  • + },
  • + formatAdaptiveText(vid) {
  • + let str = [vid.qualityLabel, vid.mimeType].filter(x => x).join(': ')
  • + if (vid.mimeType.includes('audio')) {
  • + str += ` ${Math.round(vid.bitrate / 1000)}kbps`
  • + }
  • + return str
  • + }
  • },
  • template
  • })
  • // attach element
  • const shadowHost = $el('div')
  • - const shadow = shadowHost.attachShadow
  • - ? shadowHost.attachShadow({ mode: 'closed' })
  • - : shadowHost // no shadow dom
  • + const shadow = shadowHost.attachShadow ? shadowHost.attachShadow({ mode: 'closed' }) : shadowHost // no shadow dom
  • logger.log('shadowHost: %o', shadowHost)
  • const container = $el('div')
  • shadow.appendChild(container)
  • } else {
  • return navigator.language
  • }
  • - return null
  • - }
  • - const textToHtml = t => {
  • - // URLs starting with http://, https://
  • - t = t.replace(
  • - /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim,
  • - '<a href="$1" target="_blank">$1</a>'
  • - )
  • - t = t.replace(/\n/g, '<br>')
  • - return t
  • }
  • const applyOriginalTitle = meta => {
  • + console.log(meta.player_response)
  • const data = eval(`(${meta.player_response})`).videoDetails // not a valid json, so JSON.parse won't work
  • if ($('#eow-title')) {
  • // legacy youtube
  • $('#eow-title').textContent = data.title
  • - $('#eow-description').innerHTML = textToHtml(data.shortDescription)
  • } else if ($('h1.title')) {
  • // new youtube (polymer)
  • $('h1.title').textContent = data.title
  • - $('yt-formatted-string.content').innerHTML = textToHtml(
  • - data.shortDescription
  • - )
  • }
  • }
  • const load = async id => {
  • try {
  • const basejs =
  • - typeof ytplayer !== 'undefined' && ytplayer.config
  • + (typeof ytplayer !== 'undefined' && 'config' in ytplayer && ytplayer.config.assets
  • ? 'https://' + location.host + ytplayer.config.assets.js
  • - : $('script[src$="base.js"]').src
  • + : 'web_player_context_config' in ytplayer
  • + ? 'https://' + location.host + ytplayer.web_player_context_config.jsUrl
  • + : null) || $('script[src$="base.js"]').src
  • const data = await workerGetVideo(id, basejs)
  • logger.log('video loaded: %s', id)
  • + app.isLiveStream = data.playerResponse.playabilityStatus.liveStreamability != null
  • if (RESTORE_ORIGINAL_TITLE_FOR_CURRENT_VIDEO) {
  • try {
  • applyOriginalTitle(data.meta)
  • } catch (e) {
  • - // just make sure the main function will work even if original title applier doesn't work
  • }
  • }
  • app.id = id
  • '%s',
  • `https://www.youtube.com/get_video_info?video_id=${id}&el=detailpage`
  • )
  • - prompt(
  • - str,
  • - '@@||www.youtube.com/get_video_info?*=detailpage$xhr,domain=youtube.com'
  • - )
  • + prompt(str, '@@||www.youtube.com/get_video_info?*=detailpage$xhr,domain=youtube.com')
  • }
  • logger.error('load', err)
  • }
  • }
  • let prev = null
  • setInterval(() => {
  • - const el = $('ytd-video-primary-info-renderer>#container')
  • + const el =
  • + $('#info-contents') ||
  • + $('#watch-header') ||
  • + $('.page-container:not([hidden]) ytm-item-section-renderer>lazy-list')
  • if (el && !el.contains(shadowHost)) {
  • - el.insertBefore(shadowHost, el.childNodes[el.childNodes.length-1])
  • + el.appendChild(shadowHost)
  • }
  • -
  • if (location.href !== prev) {
  • logger.log(`page change: ${prev} -> ${location.href}`)
  • prev = location.href
  • app.dark = $html.getAttribute('dark') === 'true'
  • const css = `
  • -.button-container {
  • - display: flex;
  • - justify-content: center;
  • -}
  • -.button-container .button {
  • - margin: 10px;
  • - font-size: 15px;
  • - color: black;
  • - display: flex;
  • - align-items: center;
  • -}
  • -.button svg {
  • - background: #F59A23;
  • - padding: 6px;
  • - border-top-left-radius: 3px;
  • - border-bottom-left-radius: 3px;
  • - display: inline-block;
  • -}
  • -.button span {
  • - background: white;
  • - border: 1px #bdbdbd solid;
  • - padding: 7px 8px;
  • - border-left: 0px;
  • - border-top-right-radius: 3px;
  • - border-bottom-right-radius: 3px;
  • - background-color: white;
  • - display: inline-block;
  • - width: 110px;
  • -}
  • -.other-formats-btn {
  • - display: flex;
  • - justify-content: center;
  • - align-items: center;
  • -}
  • -.other-formats-btn span {
  • - margin-right: 5px;
  • - color: #F59A23;
  • -}
  • -
  • -.t-hint {
  • - font-style: italic;
  • - margin-bottom: 5px;
  • - color: #666666;
  • -}
  • -
  • .hide{
  • display: none;
  • }
  • overflow: hidden;
  • }
  • .box{
  • + padding-top: .5em;
  • + padding-bottom: .5em;
  • border-bottom: 1px solid var(--yt-border-color);
  • font-family: Arial;
  • - padding: 15px;
  • - margin-bottom: 10px;
  • }
  • .box-toggle{
  • margin: 3px;
  • user-select: none;
  • -moz-user-select: -moz-none;
  • }
  • -
  • -.other-formats-table {
  • - margin: 0px auto;
  • - margin-top: 15px;
  • - font-size: 14px;
  • - width: 90%;
  • - border-collapse: collapse;
  • -}
  • -
  • -td, th {
  • - border: 1px solid #dddddd;
  • - text-align: center;
  • - padding: 8px;
  • -}
  • -
  • .ytdl-link-btn{
  • display: block;
  • border: 1px solid !important;
  • text-decoration: none;
  • color: var(--yt-button-color, inherit);
  • }
  • +a:hover, .div-a:hover{
  • + color: var(--yt-spec-call-to-action, blue);
  • +}
  • .box.dark{
  • color: var(--ytd-video-primary-info-renderer-title-color, var(--yt-primary-text-color));
  • }

Axzyte g (the reported user) has made:

This report has been upheld by a moderator.

The concept of open source doesn't mean you can modify and publish code without proper attribution. The description should link to the original or the script should specify the URL, author, and license by using @original-script, @original-author, and @original-license. After you fix and update this deleted script, ask a moderator to undelete it.