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.4
  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 (address) {
  50. try {
  51. const url = new URL('https://archive.org/wayback/available')
  52. url.searchParams.append('url', address)
  53. return GM_fetch(url, {
  54. method: 'GET'
  55. })
  56. } catch (e) {
  57. return Promise.reject()
  58. }
  59. }
  60.  
  61. function queryArchive (url) {
  62. return fetchArchive(url)
  63. .then(archiveResponse => {
  64. if (!archiveResponse.ok) {
  65. if (archiveResponse.status === 429) {
  66. //Too many requests
  67. GM_log('Too many request, delaying fetching')
  68. return new Promise(resolve => {
  69. setTimeout(resolve, 2000)
  70. }).then(() => fetchArchive(url))
  71. }
  72. console.error(archiveResponse)
  73. return Promise.reject(archiveResponse)
  74. }
  75. return archiveResponse
  76. })
  77. .then(archiveResponse => archiveResponse.json())
  78. .then(({ archived_snapshots }) => {
  79. if (archived_snapshots.closest) {
  80. return archived_snapshots.closest.url
  81. }
  82. return Promise.reject(archived_snapshots.closest?.url)
  83. })
  84. }
  85.  
  86. GM_addStyle(`
  87. .loader {
  88. border: 0.25em solid #f3f3f3;
  89. border-top-width: 0.25em;
  90. border-top-style: solid;
  91. border-top-color: hsl(0, 0%, 95.3%);
  92. border-top: 0.25em solid rgb(0, 0, 0);
  93. border-radius: 50%;
  94. width: 1em;
  95. height: 1em;
  96. animation: spin 2s linear infinite;
  97. }
  98.  
  99. @keyframes spin {
  100. 0% {
  101. transform: rotate(0deg);
  102. }
  103.  
  104. 100% {
  105. transform: rotate(360deg);
  106. }
  107. }
  108.  
  109. .border-gold {
  110. display: flex !important;
  111. align-items: center;
  112. justify-content: center;
  113. gap: 1em;
  114. }
  115. `)
  116.  
  117. function isValidURL (address) {
  118. try {
  119. const url = new URL(address)
  120. return GM_fetch(url, { method: 'HEAD' }).then(response => {
  121. if (response.ok) {
  122. return address
  123. }
  124. return Promise.reject(address)
  125. })
  126. } catch (e) {
  127. return Promise.reject(address)
  128. }
  129. }
  130.  
  131. function getTheYNCVideoURL (element) {
  132. const thumbnailURL = element.querySelector('.image > img').src
  133. if (!thumbnailURL) return
  134. for (const [, group_url] of thumbnailURL.matchAll(
  135. /^https?:\/\/theync\.(?:com|org|net)\/media\/thumbs\/(.*?)\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v)/gim
  136. )) {
  137. if (group_url) {
  138. return `https://media.theync.com/videos/${group_url}.mp4`
  139. }
  140. }
  141. }
  142.  
  143. function getVideoURLFromArchive (archiveURL) {
  144. return GM_fetch(archiveURL, {
  145. method: 'GET'
  146. })
  147. .then(response => {
  148. // When the page is loaded convert it to text
  149. return response.text()
  150. })
  151. .then(html => {
  152. // Initialize the DOM parser
  153. const parser = new DOMParser()
  154.  
  155. // Parse the text
  156. const doc = parser.parseFromString(html, 'text/html')
  157.  
  158. // You can now even select part of that html as you would in the regular DOM
  159. // Example:
  160. // const docArticle = doc.querySelector('article').innerHTML
  161. const archivedScript = doc.querySelector('[id=thisPlayer] + script')
  162. if (!archivedScript) {
  163. return Promise.reject()
  164. }
  165. for (const [, videoURL] of archivedScript.textContent.matchAll(
  166. /thisPlayer\.setup.*?file: "(?:https?\:\/\/web.archive.org\/web\/\d*?\/)?(https?\:\/\/.*?.\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gms
  167. )) {
  168. if (videoURL) {
  169. return decodeURIComponent(videoURL)
  170. }
  171. }
  172. return Promise.reject()
  173. })
  174. }
  175.  
  176. const allowedExtensions = [
  177. 'flv',
  178. 'mpg',
  179. 'wmv',
  180. 'avi',
  181. '3gp',
  182. 'qt',
  183. 'mp4',
  184. 'mov',
  185. 'm4v',
  186. 'f4v'
  187. ]
  188.  
  189. waitForElement('.content-block').then(contentBlock => {
  190. for (const element of contentBlock.querySelectorAll(
  191. '.inner-block > a:has(> .item-info > .border-gold)'
  192. )) {
  193. const undergroundLogo = element.querySelector('.item-info > .border-gold')
  194. const loadingElement = document.createElement('div')
  195. loadingElement.classList.add('loader')
  196. undergroundLogo.appendChild(loadingElement)
  197. isValidURL(getTheYNCVideoURL(element))
  198. .then(
  199. url => {
  200. undergroundLogo.textContent = 'BYPASSED'
  201. undergroundLogo.style.backgroundColor = 'green'
  202. element.href = url
  203. },
  204. () =>
  205. ['com', 'org', 'net']
  206. .reduce(
  207. (accumulator, currentTLD) =>
  208. accumulator.catch(() =>
  209. queryArchive(
  210. element.href.replace(
  211. /(^https?:\/\/theync\.)(com|org|net)(\/.*$)/gim,
  212. `$1${currentTLD}$3`
  213. )
  214. )
  215. ),
  216. Promise.reject()
  217. )
  218. .then(
  219. url =>
  220. getVideoURLFromArchive(url).then(
  221. videoURL => {
  222. undergroundLogo.textContent = 'ARCHIVED'
  223. undergroundLogo.style.backgroundColor = 'blue'
  224. element.href = videoURL
  225. },
  226. () => {
  227. undergroundLogo.textContent = 'MAYBE ARCHIVED'
  228. undergroundLogo.style.backgroundColor = 'aqua'
  229. element.href = url
  230. }
  231. ),
  232. () => GM_log(`No bypass or archive found for ${element.href}`)
  233. )
  234. )
  235.  
  236. .finally(() => loadingElement.remove())
  237. }
  238. })