theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

当前为 2025-01-01 提交的版本,查看 最新版本

  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
  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 5.1
  18. // @license MIT
  19. // @author -
  20. // ==/UserScript==
  21.  
  22. /**
  23. * Description placeholder
  24. *
  25. * @param {string} selector
  26. * @returns {Promise<Element>}
  27. */
  28. function waitForElement(selector) {
  29. return new Promise((resolve) => {
  30. {
  31. const element = document.querySelector(selector);
  32. if (element) {
  33. return resolve(element);
  34. }
  35. }
  36.  
  37. const observer = new MutationObserver(() => {
  38. const element = document.querySelector(selector);
  39. if (element) {
  40. observer.disconnect();
  41. resolve(element);
  42. }
  43. });
  44.  
  45. // If you get 'parameter 1 is not of type 'Node'' error, see https://stackoverflow.com/a/77855838/492336
  46. observer.observe(document.body, {
  47. childList: true,
  48. subtree: true,
  49. });
  50. });
  51. }
  52.  
  53. /**
  54. * Fetches available archives of a given address.
  55. *
  56. * @param {string} address
  57. * @returns {Promise<Response>}
  58. */
  59. function fetchArchive(address) {
  60. try {
  61. const url = new URL('https://archive.org/wayback/available');
  62. url.searchParams.append('url', address);
  63. return GM_fetch(url, {
  64. method: 'GET',
  65. });
  66. } catch (e) {
  67. return Promise.reject();
  68. }
  69. }
  70.  
  71. /**
  72. * Fetches available archives of a given address and retrieves their URLs.
  73. *
  74. * @param {string} address
  75. * @returns {Promise<string>}
  76. */
  77. function queryArchive(address) {
  78. return fetchArchive(address)
  79. .then((archiveResponse) => {
  80. if (!archiveResponse.ok) {
  81. console.error(archiveResponse);
  82. return Promise.reject(archiveResponse);
  83. }
  84. return archiveResponse;
  85. })
  86. .then((archiveResponse) => archiveResponse.json())
  87. .then(({ archived_snapshots }) => {
  88. if (archived_snapshots.closest) {
  89. return archived_snapshots.closest.url;
  90. }
  91. return Promise.reject(archived_snapshots.closest?.url);
  92. })
  93. .then((url) => {
  94. // Avoid "Mixed content"
  95. if (location.protocol === 'https:') {
  96. return url.replace(/^http:\/\//i, 'https://');
  97. }
  98. return url;
  99. });
  100. }
  101.  
  102. /**
  103. * Checks whether a URL is valid and accessible.
  104. *
  105. * @param {string} address
  106. * @returns {Promise<string>}
  107. */
  108. function isValidURL(address) {
  109. if (address) {
  110. try {
  111. const url = new URL(address);
  112. return fetch(url, { method: 'HEAD' }).then((response) => {
  113. if (response.ok) {
  114. return address;
  115. }
  116. return Promise.reject(address);
  117. });
  118. } catch {
  119. return Promise.reject(address);
  120. }
  121. }
  122. return Promise.reject(address);
  123. }
  124.  
  125. /**
  126. * Tries to guess the video URL of a given theYNC video via the thumbnail URL.
  127. * Only works on videos published before around May 2023.
  128. *
  129. * @param {Element} element
  130. * @returns {string | undefined}
  131. */
  132. function getTheYNCVideoURL(element) {
  133. const thumbnailURL = element.querySelector('.image > img')?.src;
  134. if (!thumbnailURL) return;
  135. for (const [, group_url] of thumbnailURL.matchAll(
  136. /^https?:\/\/theync\.(?:com|org|net)\/media\/thumbs\/(.*?)\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v)/gim
  137. )) {
  138. if (group_url) {
  139. return `https://media.theync.com/videos/${group_url}.mp4`;
  140. }
  141. }
  142. }
  143.  
  144. /**
  145. * Retrieves the video URL from an archived YNC URL
  146. *
  147. * @param {string} archiveURL
  148. * @returns {Promise<string>}
  149. */
  150. function getVideoURLFromArchive(archiveURL) {
  151. return GM_fetch(archiveURL, {
  152. method: 'GET',
  153. })
  154. .then((response) => {
  155. if (!response.ok) {
  156. console.error(response);
  157. return Promise.reject(response);
  158. }
  159. // When the page is loaded convert it to text
  160. return response;
  161. })
  162. .then((response) => response.text())
  163. .then((html) => {
  164. // Initialize the DOM parser
  165. const parser = new DOMParser();
  166.  
  167. // Parse the text
  168. const doc = parser.parseFromString(html, 'text/html');
  169.  
  170. // You can now even select part of that html as you would in the regular DOM
  171. // Example:
  172. // const docArticle = doc.querySelector('article').innerHTML
  173. const archivedScript = doc.querySelector(
  174. '[id=thisPlayer] + script'
  175. )?.textContent;
  176. if (archivedScript) {
  177. for (const [, videoURL] of archivedScript.matchAll(
  178. /thisPlayer\.setup.*?file: "(?:https?\:\/\/web.archive.org\/web\/\d*?\/)?(https?\:\/\/.*?.\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gms
  179. )) {
  180. if (videoURL) {
  181. return decodeURIComponent(videoURL);
  182. }
  183. }
  184. }
  185. return Promise.reject();
  186. });
  187. }
  188.  
  189. const allowedExtensions = [
  190. 'flv',
  191. 'mpg',
  192. 'wmv',
  193. 'avi',
  194. '3gp',
  195. 'qt',
  196. 'mp4',
  197. 'mov',
  198. 'm4v',
  199. 'f4v',
  200. ];
  201.  
  202. GM_addStyle(`
  203. .loader {
  204. border: 0.25em solid #f3f3f3;
  205. border-top-width: 0.25em;
  206. border-top-style: solid;
  207. border-top-color: hsl(0, 0%, 95.3%);
  208. border-top: 0.25em solid rgb(0, 0, 0);
  209. border-radius: 50%;
  210. width: 1em;
  211. height: 1em;
  212. animation: spin 2s linear infinite;
  213. }
  214.  
  215. @keyframes spin {
  216. 0% {
  217. transform: rotate(0deg);
  218. }
  219.  
  220. 100% {
  221. transform: rotate(360deg);
  222. }
  223. }
  224.  
  225. .border-gold {
  226. display: flex !important;
  227. align-items: center;
  228. justify-content: center;
  229. gap: 1em;
  230. }
  231. `);
  232.  
  233. (() => {
  234. 'use strict';
  235. waitForElement('.content-block').then((contentBlock) => {
  236. for (const element of contentBlock.querySelectorAll(
  237. '.inner-block > a:has(> .item-info > .border-gold)'
  238. )) {
  239. const undergroundLogo = element.querySelector(
  240. '.item-info > .border-gold'
  241. );
  242. const loadingElement = document.createElement('div');
  243. loadingElement.classList.add('loader');
  244. undergroundLogo.appendChild(loadingElement);
  245. isValidURL(getTheYNCVideoURL(element))
  246. .then(
  247. (url) => {
  248. undergroundLogo.textContent = 'BYPASSED';
  249. undergroundLogo.style.backgroundColor = 'green';
  250. element.href = url;
  251. },
  252. () =>
  253. ['com', 'org', 'net']
  254. .reduce(
  255. (accumulator, currentTLD) =>
  256. accumulator.catch(() =>
  257. queryArchive(
  258. element.href.replace(
  259. /(^https?:\/\/theync\.)(com|org|net)(\/.*$)/gim,
  260. `$1${currentTLD}$3`
  261. )
  262. )
  263. ),
  264. Promise.reject()
  265. )
  266. .then(
  267. (url) =>
  268. getVideoURLFromArchive(url).then(
  269. (videoURL) => {
  270. undergroundLogo.textContent =
  271. 'ARCHIVED';
  272. undergroundLogo.style.backgroundColor =
  273. 'blue';
  274. element.href = videoURL;
  275. },
  276. () => {
  277. undergroundLogo.textContent =
  278. 'MAYBE ARCHIVED';
  279. undergroundLogo.style.backgroundColor =
  280. 'aqua';
  281. element.href = url;
  282. }
  283. ),
  284. () =>
  285. GM_log(
  286. `No bypass or archive found for ${element.href}`
  287. )
  288. )
  289. )
  290.  
  291. .finally(() => loadingElement.remove());
  292. }
  293. });
  294. })();