theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

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

  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 6.0
  18. // @license MIT
  19. // @author -
  20. // ==/UserScript==
  21.  
  22. /**
  23. * Waits for a element of a given selector.
  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 GM_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 a theYNC video page
  146. *
  147. * @param {Element} element
  148. * @returns {string | undefined}
  149. */
  150. function retrieveVideoURL(element = document) {
  151. const archivedScript = element.querySelector(
  152. '[id=thisPlayer] + script'
  153. )?.textContent;
  154. if (archivedScript) {
  155. for (const [, videoURL] of archivedScript.matchAll(
  156. /(?<=thisPlayer\.setup\(\{).*?file:\ *"(https?\:\/\/.+?.\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gms
  157. )) {
  158. if (videoURL) {
  159. return videoURL;
  160. }
  161. }
  162. }
  163. }
  164.  
  165. /**
  166. * Retrieves the video URL from an archived YNC URL
  167. *
  168. * @param {string} archiveURL
  169. * @returns {Promise<string>}
  170. */
  171. function getVideoURLFromArchive(archiveURL) {
  172. return GM_fetch(archiveURL, {
  173. method: 'GET',
  174. })
  175. .then((response) => {
  176. if (!response.ok) {
  177. console.error(response);
  178. return Promise.reject(response);
  179. }
  180. // When the page is loaded convert it to text
  181. return response;
  182. })
  183. .then((response) => response.text())
  184. .then((html) => {
  185. // Initialize the DOM parser
  186. const parser = new DOMParser();
  187.  
  188. // Parse the text
  189. const doc = parser.parseFromString(html, 'text/html');
  190.  
  191. // You can now even select part of that html as you would in the regular DOM
  192. // Example:
  193. // const docArticle = doc.querySelector('article').innerHTML
  194. const videoURL = retrieveVideoURL(doc);
  195. if (videoURL) {
  196. return videoURL;
  197. }
  198. return Promise.reject();
  199. });
  200. }
  201.  
  202. (() => {
  203. 'use strict';
  204.  
  205. const allowedExtensions = [
  206. 'flv',
  207. 'mpg',
  208. 'wmv',
  209. 'avi',
  210. '3gp',
  211. 'qt',
  212. 'mp4',
  213. 'mov',
  214. 'm4v',
  215. 'f4v',
  216. ];
  217.  
  218. GM_addStyle(`
  219. .loader {
  220. border: 0.25em solid #f3f3f3;
  221. border-top-width: 0.25em;
  222. border-top-style: solid;
  223. border-top-color: hsl(0, 0%, 95.3%);
  224. border-top: 0.25em solid rgb(0, 0, 0);
  225. border-radius: 50%;
  226. width: 1em;
  227. height: 1em;
  228. animation: spin 2s linear infinite;
  229. }
  230. @keyframes spin {
  231. 0% {
  232. transform: rotate(0deg);
  233. }
  234. 100% {
  235. transform: rotate(360deg);
  236. }
  237. }
  238. .border-gold {
  239. display: flex !important;
  240. align-items: center;
  241. justify-content: center;
  242. gap: 1em;
  243. }
  244. `);
  245.  
  246. waitForElement('[id="content"],[id="related-videos"] .content-block').then(
  247. (contentBlock) => {
  248. for (const element of contentBlock.querySelectorAll(
  249. '.upgrade-profile > .upgrade-info-block > .image-block'
  250. )) {
  251. isValidURL(getTheYNCVideoURL(element)).then(
  252. (url) => (location.href = url)
  253. );
  254. }
  255. for (const element of contentBlock.querySelectorAll(
  256. '.inner-block > a'
  257. )) {
  258. const undergroundLogo = element.querySelector(
  259. '.item-info > .border-gold'
  260. );
  261. if (!undergroundLogo) {
  262. continue;
  263. }
  264. const loadingElement = document.createElement('div');
  265. loadingElement.classList.add('loader');
  266. undergroundLogo.appendChild(loadingElement);
  267. isValidURL(getTheYNCVideoURL(element))
  268. .then(
  269. (url) => {
  270. undergroundLogo.textContent = 'BYPASSED';
  271. undergroundLogo.style.backgroundColor = 'green';
  272. element.href = url;
  273. },
  274. () =>
  275. ['com', 'org', 'net']
  276. .reduce(
  277. (accumulator, currentTLD) =>
  278. accumulator.catch(() =>
  279. queryArchive(
  280. element.href.replace(
  281. /(^https?:\/\/theync\.)(com|org|net)(\/.*$)/gim,
  282. `$1${currentTLD}$3`
  283. )
  284. )
  285. ),
  286. Promise.reject()
  287. )
  288. .then(
  289. (url) =>
  290. getVideoURLFromArchive(url).then(
  291. (videoURL) => {
  292. undergroundLogo.textContent =
  293. 'ARCHIVED';
  294. undergroundLogo.style.backgroundColor =
  295. 'blue';
  296. element.href = videoURL;
  297. },
  298. () => {
  299. undergroundLogo.textContent =
  300. 'MAYBE ARCHIVED';
  301. undergroundLogo.style.backgroundColor =
  302. 'aqua';
  303. element.href = url;
  304. }
  305. ),
  306. () =>
  307. GM_log(
  308. `No bypass or archive found for ${element.href}`
  309. )
  310. )
  311. )
  312.  
  313. .finally(() => loadingElement.remove());
  314. }
  315. }
  316. );
  317. })();