Advertisement
Guest User

Untitled

a guest
Jan 12th, 2021
722
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.83 KB | None | 0 0
  1. // ==UserScript==
  2. // @name YouTube: Age Verification Bypass
  3. // @namespace https://greasyfork.org/users/221926
  4. // @version 1.6
  5. // @description watch restricted videos without having to log in
  6. // @include https://www.youtube.com/*
  7. // @grant none
  8. // @run-at document-end
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict'
  13.  
  14. const NEW_LAYOUT_PLAYER_CONTAINER_ID = 'player-container'
  15. const NEW_LAYOUT_ERROR_SCREEN_ID = 'error-screen'
  16. const OLD_LAYOUT_SIDEBAR_MODULES_ID = 'watch7-sidebar-modules'
  17. const NEW_LAYOUT_RELATED_ITEM_TEMPLATE = rv => `<a href="/watch?v=${rv.id}" style="text-decoration:none;display:block;margin-bottom:8px;" title="${rv.title}"><table style="border-collapse:collapse"><td style="position:relative;padding:0"><img src="${rv.iurlmq}" style="width:168px;height:94px;display:block;margin-right:8px"><span style="position:absolute;bottom:0;right:8px;margin:4px;color:var(--ytd-thumbnail-badge_-_color,#fff);background-color:var(--ytd-thumbnail-badge_-_background-color,rgba(0,0,0,.8));padding:2px 4px;border-radius:2px;letter-spacing:.5px;font-size:1.2rem;font-weight:500;line-height:1.2rem">${rv.duration}</span></td><td style="vertical-align:top;"><span style="display:block;margin:0 0 4px 0;max-height:3.2rem;overflow:hidden;font-size:1.4rem;font-weight:500;line-height:1.6rem;color:var(--yt-primary-text-color,rgba(255,255,255,0.88));">${rv.title}</span><div style="color:var(--ytd-metadata-line-color,var(--yt-spec-text-secondary,#aaa));font-size:1.3rem;font-weight:400;line-height:1.8rem;">${rv.author}<br>${rv.short_view_count_text}</div></td></table></a>`
  18. const OLD_LAYOUT_RELATED_ITEM_TEMPLATE = rv => `<div class="video-list-item related-list-item show-video-time related-list-item-compact-video"><div class="content-wrapper"><a href="/watch?v=${rv.id}" class="content-link spf-link yt-uix-sessionlink spf-link"><span dir="ltr" class="title">${rv.title}</span><span class="stat attribution"><span class="">${rv.author}</span></span><span class="stat view-count">${rv.short_view_count_text}</span></a></div><div class="thumb-wrapper"><a href="/watch?v=${rv.id}" class="thumb-link spf-link yt-uix-sessionlink" tabindex="-1" rel=" spf-prefetch nofollow" aria-hidden="true"><span class="yt-uix-simple-thumb-wrap yt-uix-simple-thumb-related"><img alt="" src="${rv.iurlmq}" style="top: 0px" aria-hidden="true" width="168" height="94"><span class="video-time">${rv.duration}</span></span></a></div></div>`
  19.  
  20. let player = null
  21. let related = null
  22. let currentVideoId = null
  23.  
  24. // General
  25. function escapeHTML (str) {
  26. return document.createElement('div').appendChild(document.createTextNode(str)).parentNode.innerHTML
  27. }
  28.  
  29. // DOM
  30. function removeNode (n) {
  31. if (n != null && n.parentNode != null) { n.parentNode.removeChild(n) }
  32. }
  33.  
  34. function asyncQuerySelector (query, token = {}, document = window.document) {
  35. return new Promise((resolve, reject) => {
  36. const ival = setInterval(function () {
  37. const el = document.querySelector(query)
  38. if (el != null) { clearInterval(ival); resolve(el) }
  39. }, 100)
  40. token.cancel = () => { clearInterval(ival); reject() }
  41. })
  42. }
  43.  
  44. // YouTube
  45. function getVideoId () {
  46. return new URLSearchParams(location.search).get('v')
  47. }
  48.  
  49. function getPlaylistId () {
  50. return new URLSearchParams(location.search).get('list')
  51. }
  52.  
  53. function getVideoStart () {
  54. const t = new URLSearchParams(location.search).get('t') || 0
  55. if (!isNaN(t)) { return +t }
  56. let multipliers = { h: 3600, m: 60, s: 1 }
  57. return t.match(/[0-9]+[a-z]/g)
  58. .map(str => str.slice(0, -1) * multipliers[str.slice(-1)])
  59. .reduce((a, b) => a + b)
  60. }
  61.  
  62. function isInitialVideoAndAgeRestricted (videoId = getVideoId()) {
  63. // https://greasyfork.org/scripts/371261
  64. return window.ytInitialPlayerResponse != null &&
  65. typeof window.ytInitialPlayerResponse.playabilityStatus.desktopLegacyAgeGateReason != 'undefined' &&
  66. window.ytInitialPlayerResponse.playabilityStatus.desktopLegacyAgeGateReason &&
  67. window.ytInitialPlayerResponse.videoDetails.videoId === videoId
  68. }
  69.  
  70. // Script
  71. const newLayout = Object.freeze({
  72. restrictedVideoIds: [],
  73. fallbackLink: (() => {
  74. const span = document.createElement('span')
  75. span.innerText = 'Click here if the video is age restricted'
  76. span.style = 'font-size:1.6rem;margin-top:1rem;color:#fff;cursor:pointer;text-decoration:underline'
  77. span.onclick = () => { reset(); newLayout.unrestrict() }
  78. return span
  79. })(),
  80. checkDOMAndPrepare () {
  81. let signInButton = null
  82. const errorScreenInfoDiv = document.querySelector('#error-screen #info')
  83. if (errorScreenInfoDiv != null) {
  84. // signInButton
  85. signInButton = errorScreenInfoDiv.getElementsByTagName('yt-button-renderer')[0]
  86. removeNode(signInButton) // avoids false positives
  87. // fallbackLink
  88. removeNode(newLayout.fallbackLink)
  89. errorScreenInfoDiv.appendChild(newLayout.fallbackLink)
  90. }
  91. return signInButton != null
  92. },
  93. checkAndPrepare (videoId = getVideoId()) {
  94. const DOMCheck = newLayout.checkDOMAndPrepare()
  95. const inArray = newLayout.restrictedVideoIds.includes(videoId) // signInButton may not have been recreated while navigating back/forward, check array too
  96. if (DOMCheck || inArray || isInitialVideoAndAgeRestricted(videoId)) {
  97. if (!inArray) { newLayout.restrictedVideoIds.push(videoId) }
  98. return true
  99. }
  100. return false
  101. },
  102. unrestrict (videoId = getVideoId(), options = {}) {
  103. const oldPlayer = document.getElementById(NEW_LAYOUT_PLAYER_CONTAINER_ID)
  104. // pause video (useful when coming back from an unrestricted video)
  105. document.querySelectorAll('video').forEach(el => el.pause())
  106. // player
  107. createPlayer(videoId, oldPlayer.parentNode)
  108. player.id = oldPlayer.id
  109. player.className = oldPlayer.className
  110. // related
  111. const rs = document.getElementById('related-skeleton')
  112. if (rs != null && rs.parentNode != null) {
  113. rs.style.display = 'none'
  114. showRelatedVideos(videoId, rs.parentNode, NEW_LAYOUT_RELATED_ITEM_TEMPLATE)
  115. }
  116. // remove/hide blocking elements
  117. document.querySelectorAll('[player-unavailable]').forEach(el => el.removeAttribute('player-unavailable'))
  118. removeNode(document.querySelector('#player.skeleton'))
  119. oldPlayer.style.display = 'none';
  120. (options.errorScreen || document.getElementById(NEW_LAYOUT_ERROR_SCREEN_ID)).style.display = 'none'
  121. // cancelPlaylistVideoSkip
  122. newLayout.cancelPlaylistVideoSkip(videoId)
  123. },
  124. cancelPlaylistVideoSkip (videoId) {
  125. if (getPlaylistId() == null) return
  126. const manager = document.querySelector('yt-playlist-manager')
  127. if (!manager || !manager.cancelVideoSkip) return // greasemonkey
  128. manager.cancelVideoSkip()
  129. if (manager.skipAgeUserScript !== getPlaylistId()) { // cancelVideoSkip does not seem to work on the first video
  130. manager.skipAgeUserScript = getPlaylistId()
  131. const rollback = () => {
  132. killRollback()
  133. asyncQuerySelector(`ytd-playlist-panel-video-renderer a[href*="${videoId}"]`).then(e => e.click())
  134. }
  135. const killRollback = () => {
  136. removeEventListener('yt-navigate-finish', rollback)
  137. removeEventListener('click', killRollback)
  138. }
  139. addEventListener('yt-navigate-finish', rollback)
  140. setTimeout(() => killRollback, 10 * 1000) // if no redirect after 10 seconds, yt-navigate was probably not due to the video being restricted
  141. addEventListener('click', killRollback)
  142. }
  143. },
  144. checkAndUnrestrict (videoId, options) {
  145. if (newLayout.checkAndPrepare(videoId)) { newLayout.unrestrict(videoId, options) }
  146. },
  147. reset () {
  148. (document.getElementById(NEW_LAYOUT_PLAYER_CONTAINER_ID) || { style: {} }).style.display = '';
  149. (document.getElementById(NEW_LAYOUT_ERROR_SCREEN_ID) || { style: {} }).style.display = ''
  150. }
  151. })
  152.  
  153. const oldLayout = Object.freeze({
  154. check () {
  155. return document.getElementById('watch7-player-age-gate-content') != null
  156. },
  157. unrestrict (videoId = getVideoId(), options = {}) {
  158. const playerParentNode = document.getElementById('player-unavailable')
  159. playerParentNode.innerHTML = ''
  160. createPlayer(videoId, playerParentNode)
  161. showRelatedVideos(videoId, options.sidebarModulesContainer || document.getElementById(OLD_LAYOUT_SIDEBAR_MODULES_ID), OLD_LAYOUT_RELATED_ITEM_TEMPLATE).then(() => { related.className = 'video-list' })
  162. },
  163. checkAndUnrestrict (videoId, options) {
  164. if (oldLayout.check()) { oldLayout.unrestrict(videoId, options) }
  165. },
  166. reset () {}
  167. })
  168.  
  169. function createPlayer (videoId, parentNode) {
  170. player = document.createElement('iframe')
  171. player.onload = () => checkAndUnrestrictEmbed(player.contentDocument) // greasemonkey
  172. player.src = `https://www.youtube.com/embed/${videoId}?start=${getVideoStart()}&autoplay=1`
  173. player.style = 'border:0;width:100%;height:100%'
  174. player.setAttribute('allowfullscreen', '') // firefox (https://greasyfork.org/en/scripts/375525/discussions/43480)
  175. parentNode.appendChild(player)
  176. }
  177.  
  178. async function showRelatedVideos (videoId, parentNode, itemTemplate) {
  179. let innerHTML = ''
  180. const videoInfo = await fetch('https://www.youtube.com/get_video_info?asv=3&video_id=' + videoId).then(res => res.text())
  181. if (videoId !== getVideoId()) { return }
  182. new URLSearchParams(videoInfo).get('rvs').split(',').forEach(str => {
  183. const rv = new URLSearchParams(str)
  184. if (rv.has('title')) {
  185. innerHTML += itemTemplate({
  186. id: rv.get('id'),
  187. author: escapeHTML(rv.get('author')),
  188. title: escapeHTML(rv.get('title')),
  189. duration: Math.floor(rv.get('length_seconds') / 60) + ':' + ('0' + (rv.get('length_seconds') % 60)).substr(-2),
  190. iurlmq: rv.get('iurlmq'),
  191. short_view_count_text: rv.get('short_view_count_text')
  192. })
  193. }
  194. })
  195. related = document.createElement('div')
  196. related.innerHTML = innerHTML
  197. parentNode.appendChild(related)
  198. }
  199.  
  200. function reset () {
  201. removeNode(player)
  202. removeNode(related)
  203. newLayout.reset()
  204. oldLayout.reset()
  205. }
  206.  
  207. function checkAndUnrestrict () {
  208. const videoId = getVideoId()
  209. if (videoId === currentVideoId) { return }
  210. currentVideoId = videoId
  211. reset() // useful when coming back from a restricted video
  212. if (videoId == null) { return }
  213.  
  214. const newLayoutToken = { cancel: () => {} }
  215. const oldLayoutToken = { cancel: () => {} }
  216. asyncQuerySelector('#' + NEW_LAYOUT_ERROR_SCREEN_ID, newLayoutToken).then(errorScreen => {
  217. oldLayoutToken.cancel()
  218. if (videoId !== currentVideoId) { return }
  219. newLayout.checkAndUnrestrict(videoId, { errorScreen })
  220. }).catch(() => {})
  221. asyncQuerySelector('#' + OLD_LAYOUT_SIDEBAR_MODULES_ID, oldLayoutToken).then(sidebarModulesContainer => {
  222. newLayoutToken.cancel()
  223. if (videoId !== currentVideoId) { return }
  224. oldLayout.checkAndUnrestrict(videoId, { sidebarModulesContainer })
  225. }).catch(() => {})
  226. }
  227.  
  228. async function checkAndUnrestrictEmbed (document = window.document) {
  229. const match = document.location.pathname.match(/^\/embed\/([a-zA-Z0-9_\-]+)$/)
  230. if (match == null) return
  231. if (document.skipAgeUserScript) return
  232. document.skipAgeUserScript = true
  233. await asyncQuerySelector('.ytp-error-content, .playing-mode', {}, document)
  234. if (document.querySelector('.ytp-error-content') == null) return
  235. const banner = document.createElement('div')
  236. banner.innerText = 'Checking for sources...'
  237. banner.style = "background-color:purple;color:white;padding:1em;position:absolute;z-index:99999;top:0;left:0;width:100%"
  238. document.body.prepend(banner)
  239. const videoInfo = await fetch('https://www.youtube.com/get_video_info?asv=3&video_id=' + match[1]).then(res => res.text())
  240. const formats = ((JSON.parse(new URLSearchParams(videoInfo).get('player_response')).streamingData || {}).formats || []).filter(f => f.url)
  241. if (formats.length === 0) {
  242. banner.style.backgroundColor = 'red'
  243. banner.innerText = 'Could not find any source !'
  244. return
  245. }
  246. removeNode(banner)
  247. document.body.outerHTML = '<body style="background-color:black;display:flex;align-items:center;justify-content:center;flex-wrap:wrap"></body>'
  248. formats.forEach(f => {
  249. const button = document.createElement('button')
  250. button.innerText = f.qualityLabel
  251. button.style = 'padding:1rem;margin:1rem'
  252. button.onclick = () => { document.body.innerHTML = `<video controls autoplay height="100%" width="100%"><source src="${f.url}"></video>` }
  253. document.body.appendChild(button)
  254. })
  255. }
  256.  
  257. // new layout; chrome: prevents redirection to the last unrestricted video or /watch?v=undefined when leaving fullscreen; non-theater: prevents the parent nodes of the iframe from being hidden
  258. addEventListener('fullscreenchange', (ev) => { if (newLayout.restrictedVideoIds.includes(getVideoId())) { ev.stopImmediatePropagation() } }, true)
  259.  
  260. // embed (https://support.google.com/youtube/answer/2802167#:~:text=embedded%20player%2C%20will%20be%20redirected%20to%20YouTube%2C%20where%20they%20will%20only%20be%20able%20to%20view%20the%20content%20when%20signed-in)
  261. checkAndUnrestrictEmbed()
  262. // new layout; getEventListeners(window)
  263. addEventListener('yt-navigate-start', reset)
  264. addEventListener('yt-navigate-finish', checkAndUnrestrict)
  265. // old layout; getEventListeners(window)
  266. addEventListener('spfdone', checkAndUnrestrict)
  267. // fallback
  268. setTimeout(checkAndUnrestrict, 100)
  269. setTimeout(checkAndUnrestrict, 2000)
  270. new MutationObserver(checkAndUnrestrict).observe(document.getElementsByTagName('title')[0] || document, { childList: true, subtree: true })
  271. })()
  272.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement