theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

目前為 2024-12-31 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name theYNC.com Underground bypass
  3. // @description Watch theYNC Underground videos without needing an account
  4. // @require https://cdn.jsdelivr.net/npm/@trim21/gm-fetch@0.2.1/dist/gm_fetch.js
  5. // @namespace Violentmonkey Scripts
  6. // @match *://*.theync.com/*
  7. // @match *://theync.com/*
  8. // @match *://*.theync.net/*
  9. // @match *://theync.net/*
  10. // @match *://*.theync.org/*
  11. // @match *://theync.org/*
  12. // @grant GM.xmlHttpRequest
  13. // @connect media.theync.com
  14. // @connect archive.org
  15. // @grant GM_addStyle
  16. // @grant GM_log
  17. // @version 4.3
  18. // @license MIT
  19. // @author -
  20. // ==/UserScript==
  21.  
  22. 'use strict'
  23.  
  24. function waitForElement (selector) {
  25. return new Promise(resolve => {
  26. {
  27. const element = document.querySelector(selector)
  28. if (element) {
  29. return resolve(element)
  30. }
  31. }
  32.  
  33. const observer = new MutationObserver(() => {
  34. const element = document.querySelector(selector)
  35. if (element) {
  36. observer.disconnect()
  37. resolve(element)
  38. }
  39. })
  40.  
  41. // If you get 'parameter 1 is not of type 'Node'' error, see https://stackoverflow.com/a/77855838/492336
  42. observer.observe(document.body, {
  43. childList: true,
  44. subtree: true
  45. })
  46. })
  47. }
  48.  
  49. function fetchArchive (url) {
  50. return GM_fetch(`https://archive.org/wayback/available?url=${url}`, {
  51. method: 'GET'
  52. })
  53. }
  54.  
  55. function queryArchive (url) {
  56. return fetchArchive(url)
  57. .then(archiveResponse => {
  58. if (!archiveResponse.ok) {
  59. if (archiveResponse.status === 429) {
  60. //Too many requests
  61. GM_log('Too many request, delaying fetching')
  62. return new Promise(resolve => {
  63. setTimeout(resolve, 2000)
  64. }).then(() => fetchArchive(url))
  65. }
  66. console.error(archiveResponse)
  67. return Promise.reject(archiveResponse)
  68. }
  69. return archiveResponse
  70. })
  71. .then(archiveResponse => archiveResponse.json())
  72. .then(({ archived_snapshots }) => {
  73. if (archived_snapshots.closest) {
  74. return archived_snapshots.closest.url
  75. }
  76. return Promise.reject(archived_snapshots.closest?.url)
  77. })
  78. }
  79.  
  80. GM_addStyle(`
  81. .loader {
  82. border: 0.25em solid #f3f3f3;
  83. border-top-width: 0.25em;
  84. border-top-style: solid;
  85. border-top-color: hsl(0, 0%, 95.3%);
  86. border-top: 0.25em solid rgb(0, 0, 0);
  87. border-radius: 50%;
  88. width: 1em;
  89. height: 1em;
  90. animation: spin 2s linear infinite;
  91. }
  92.  
  93. @keyframes spin {
  94. 0% {
  95. transform: rotate(0deg);
  96. }
  97.  
  98. 100% {
  99. transform: rotate(360deg);
  100. }
  101. }
  102.  
  103. .border-gold {
  104. display: flex !important;
  105. align-items: center;
  106. justify-content: center;
  107. gap: 1em;
  108. }
  109. `)
  110.  
  111. function isValidURL (address) {
  112. try {
  113. const url = new URL(address)
  114. return GM_fetch(url, { method: 'HEAD' }).then(response => {
  115. if (response.ok) {
  116. return address
  117. }
  118. return Promise.reject(address)
  119. })
  120. } catch (e) {
  121. return Promise.reject(address)
  122. }
  123. }
  124.  
  125. function getTheYNCVideoURL (element) {
  126. const thumbnailURL = element.querySelector('.image > img').src
  127. if (!thumbnailURL) return
  128. for (const [, group_url] of thumbnailURL.matchAll(
  129. /^https?:\/\/theync\.(?:com|org|net)\/media\/thumbs\/(.*?)\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v)/gim
  130. )) {
  131. if (group_url) {
  132. return `https://media.theync.com/videos/${group_url}.mp4`
  133. }
  134. }
  135. }
  136.  
  137. function getVideoURLFromArchive (archiveURL) {
  138. return GM_fetch(archiveURL, {
  139. method: 'GET'
  140. })
  141. .then(response => {
  142. // When the page is loaded convert it to text
  143. return response.text()
  144. })
  145. .then(html => {
  146. // Initialize the DOM parser
  147. const parser = new DOMParser()
  148.  
  149. // Parse the text
  150. const doc = parser.parseFromString(html, 'text/html')
  151.  
  152. // You can now even select part of that html as you would in the regular DOM
  153. // Example:
  154. // const docArticle = doc.querySelector('article').innerHTML
  155. const archivedScript = doc.querySelector('[id=thisPlayer] + script')
  156. if (!archivedScript) {
  157. return Promise.reject()
  158. }
  159. for (const [, videoURL] of archivedScript.textContent.matchAll(
  160. /thisPlayer\.setup.*?file: "(?:https?\:\/\/web.archive.org\/web\/\d*?\/)?(https?\:\/\/.*?.\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gms
  161. )) {
  162. if (videoURL) {
  163. return decodeURIComponent(videoURL)
  164. }
  165. }
  166. return Promise.reject()
  167. })
  168. }
  169.  
  170. const allowedExtensions = [
  171. 'flv',
  172. 'mpg',
  173. 'wmv',
  174. 'avi',
  175. '3gp',
  176. 'qt',
  177. 'mp4',
  178. 'mov',
  179. 'm4v',
  180. 'f4v'
  181. ]
  182.  
  183. waitForElement('.content-block').then(contentBlock => {
  184. for (const element of contentBlock.querySelectorAll(
  185. '.inner-block > a:has(> .item-info > .border-gold)'
  186. )) {
  187. const undergroundLogo = element.querySelector('.item-info > .border-gold')
  188. const loadingElement = document.createElement('div')
  189. loadingElement.classList.add('loader')
  190. undergroundLogo.appendChild(loadingElement)
  191. isValidURL(getTheYNCVideoURL(element))
  192. .then(
  193. url => {
  194. undergroundLogo.textContent = 'BYPASSED'
  195. undergroundLogo.style.backgroundColor = 'green'
  196. element.href = url
  197. },
  198. () =>
  199. ['com', 'org', 'net']
  200. .reduce(
  201. (accumulator, currentTLD) =>
  202. accumulator.catch(() =>
  203. queryArchive(
  204. element.href.replace(
  205. /(^https?:\/\/theync\.)(com|org|net)(\/.*$)/gim,
  206. `$1${currentTLD}$3`
  207. )
  208. )
  209. ),
  210. Promise.reject()
  211. )
  212. .then(
  213. url =>
  214. getVideoURLFromArchive(url).then(
  215. videoURL => {
  216. undergroundLogo.textContent = 'ARCHIVED'
  217. undergroundLogo.style.backgroundColor = 'blue'
  218. element.href = videoURL
  219. },
  220. () => {
  221. undergroundLogo.textContent = 'MAYBE ARCHIVED'
  222. undergroundLogo.style.backgroundColor = 'aqua'
  223. element.href = url
  224. }
  225. ),
  226. () => GM_log(`No bypass or archive found for ${element.href}`)
  227. )
  228. )
  229.  
  230. .finally(() => loadingElement.remove())
  231. }
  232. })