theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

目前为 2025-01-17 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name theYNC.com Underground bypass
  3. // @description Watch theYNC Underground videos without needing an account
  4. // @namespace Violentmonkey Scripts
  5. // @match *://*.theync.com/*
  6. // @match *://theync.com/*
  7. // @match *://*.theync.net/*
  8. // @match *://theync.net/*
  9. // @match *://*.theync.org/*
  10. // @match *://theync.org/*
  11. // @match *://archive.ph/*
  12. // @match *://archive.today/*
  13. // @include /https?:\/\/web\.archive\.org\/web\/\d+?\/https?:\/\/theync\.(?:com|org|net)/
  14. // @require https://update.greasyfork.org/scripts/523012/1519437/WaitForKeyElement.js
  15. // @grant GM.xmlHttpRequest
  16. // @connect media.theync.com
  17. // @connect archive.org
  18. // @grant GM_addStyle
  19. // @grant GM_log
  20. // @grant GM_addElement
  21. // @version 8.7
  22. // @supportURL https://greasyfork.org/en/scripts/520352-theync-com-underground-bypass/feedback
  23. // @license MIT
  24. // @author https://greasyfork.org/en/users/1409235-paywalldespiser
  25. // ==/UserScript==
  26.  
  27. /**
  28. * Fetches available archives of a given address and retrieves their URLs.
  29. *
  30. * @param {string} address
  31. * @returns {Promise<string>}
  32. */
  33. function queryArchive(address) {
  34. try {
  35. const url = new URL('https://archive.org/wayback/available');
  36. url.searchParams.append('url', address);
  37.  
  38. return GM.xmlHttpRequest({
  39. method: 'GET',
  40. url: url,
  41. redirect: 'follow',
  42. responseType: 'json',
  43. })
  44. .then((result) => {
  45. if (result.status >= 300) {
  46. console.error(result.status);
  47. return Promise.reject(result);
  48. }
  49.  
  50. return result;
  51. })
  52. .then((result) => result.response)
  53. .then((result) => {
  54. if (
  55. result.archived_snapshots &&
  56. result.archived_snapshots.closest
  57. ) {
  58. return result.archived_snapshots.closest.url;
  59. }
  60. return Promise.reject();
  61. });
  62. } catch (e) {
  63. return Promise.reject();
  64. }
  65. }
  66.  
  67. /**
  68. * Checks whether a URL is valid and accessible.
  69. *
  70. * @param {string} address
  71. * @returns {Promise<string>}
  72. */
  73. function isValidURL(address) {
  74. if (!address) {
  75. return Promise.reject(address);
  76. }
  77. try {
  78. const url = new URL(address);
  79. return GM.xmlHttpRequest({ url, method: 'HEAD' }).then((result) => {
  80. if (result.status === 404) {
  81. return Promise.reject(address);
  82. }
  83. return address;
  84. });
  85. } catch {
  86. return Promise.reject(address);
  87. }
  88. }
  89.  
  90. /**
  91. * Tries to guess the video URL of a given theYNC video via the thumbnail URL.
  92. * Only works on videos published before around May 2023.
  93. *
  94. * @param {Element} element
  95. * @returns {string | undefined}
  96. */
  97. function getTheYNCVideoURL(element) {
  98. const thumbnailURL = element.querySelector('.image > img')?.src;
  99. if (!thumbnailURL) return;
  100. for (const [, group_url] of thumbnailURL.matchAll(
  101. /^https?:\/\/(?:media\.theync\.(?:com|org|net)|(www\.)?theync\.(?:com|org|net)\/media)\/thumbs\/(.+?)\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v)/gim
  102. )) {
  103. if (group_url) {
  104. return `https://media.theync.com/videos/${group_url}.mp4`;
  105. }
  106. }
  107. }
  108.  
  109. /**
  110. * Retrieves the video URL from a theYNC video page
  111. *
  112. * @param {Element} element
  113. * @returns {string | undefined}
  114. */
  115. function retrieveVideoURL(element = document) {
  116. if (location.host === 'archive.ph' || location.host === 'archive.today') {
  117. const videoElement = element.querySelector(
  118. '[id="thisPlayer"] video[old-src]'
  119. );
  120. if (videoElement) {
  121. return videoElement.getAttribute('old-src');
  122. }
  123. }
  124. const videoSrc = element.querySelector(
  125. '.stage-video > .inner-stage video[src]'
  126. )?.src;
  127. if (videoSrc) {
  128. return videoSrc;
  129. }
  130. const playerSetupScript = element.querySelector(
  131. '[id=thisPlayer] + script'
  132. )?.textContent;
  133. if (playerSetupScript) {
  134. // TODO: Find a non-regex solution to this that doesn't involve eval
  135. for (const [, videoURL] of playerSetupScript.matchAll(
  136. /(?<=file\:) *?"(?:https?:\/\/web.archive.org\/web\/\d+?\/)?(https?:\/\/(?:(?:www\.)?theync\.(?:com|org|net)\/media|media.theync\.(?:com|org|net))\/videos\/.+?\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gim
  137. )) {
  138. if (videoURL) {
  139. return decodeURIComponent(videoURL);
  140. }
  141. }
  142. }
  143. }
  144.  
  145. /**
  146. * Retrieves the video URL from an archived YNC URL
  147. *
  148. * @param {string} archiveURL
  149. * @returns {Promise<string>}
  150. */
  151. function getVideoURLFromArchive(archiveURL) {
  152. return GM.xmlHttpRequest({ url: archiveURL, method: 'GET' })
  153. .then((result) => {
  154. if (result.status >= 300) {
  155. console.error(result.status);
  156. return Promise.reject(result);
  157. }
  158. return result;
  159. })
  160.  
  161. .then((result) => {
  162. // Initialize the DOM parser
  163. const parser = new DOMParser();
  164.  
  165. // Parse the text
  166. const doc = parser.parseFromString(
  167. result.responseText,
  168. 'text/html'
  169. );
  170.  
  171. // You can now even select part of that html as you would in the regular DOM
  172. // Example:
  173. // const docArticle = doc.querySelector('article').innerHTML
  174. const videoURL = retrieveVideoURL(doc);
  175. if (videoURL) {
  176. return videoURL;
  177. }
  178. return Promise.reject();
  179. });
  180. }
  181.  
  182. /**
  183. * set Link of element
  184. *
  185. * @param {HTMLLinkElement} element
  186. * @param {string} url
  187. */
  188. function setElementLink(element, url) {
  189. element.href = url;
  190. }
  191.  
  192. (function () {
  193. 'use strict';
  194.  
  195. const allowedExtensions = [
  196. 'flv',
  197. 'mpg',
  198. 'wmv',
  199. 'avi',
  200. '3gp',
  201. 'qt',
  202. 'mp4',
  203. 'mov',
  204. 'm4v',
  205. 'f4v',
  206. ];
  207.  
  208. GM_addStyle(`
  209. .loader {
  210. border: 0.25em solid #f3f3f3;
  211. border-top: 0.25em solid rgba(0, 0, 0, 0);
  212. border-radius: 50%;
  213. width: 1em;
  214. height: 1em;
  215. animation: spin 2s linear infinite;
  216. }
  217. @keyframes spin {
  218. 0% {
  219. transform: rotate(0deg);
  220. }
  221. 100% {
  222. transform: rotate(360deg);
  223. }
  224. }
  225. .border-gold {
  226. display: flex !important;
  227. align-items: center;
  228. justify-content: center;
  229. gap: 1em;
  230. }
  231. `);
  232.  
  233. waitForKeyElement(
  234. '[id="content"],[id="related-videos"] .content-block'
  235. ).then((contentBlock) => {
  236. for (const element of contentBlock.querySelectorAll(
  237. '.upgrade-profile > .upgrade-info-block > .image-block'
  238. )) {
  239. isValidURL(getTheYNCVideoURL(element)).then(
  240. (url) => (location.href = url)
  241. );
  242. }
  243. for (const element of contentBlock.querySelectorAll(
  244. '.inner-block > a'
  245. )) {
  246. const undergroundLogo = element.querySelector(
  247. '.item-info > .border-gold'
  248. );
  249. if (!undergroundLogo) {
  250. continue;
  251. }
  252. const loadingElement = GM_addElement('div');
  253. loadingElement.classList.add('loader');
  254. undergroundLogo.appendChild(loadingElement);
  255. isValidURL(getTheYNCVideoURL(element))
  256. .then(
  257. (url) => ({
  258. url: url,
  259. text: 'BYPASSED',
  260. color: 'green',
  261. }),
  262. () => {
  263. const match = element.href.match(
  264. /(^https?:\/\/(?:www\.)?theync\.)(?:com|org|net)(\/.*$)/im
  265. );
  266. if (!match || !match[1]) {
  267. return Promise.reject(
  268. `Error with the URL: ${element.href}`
  269. );
  270. }
  271. const [, secondLevelDomain, path] = match
  272. return ['com', 'org', 'net']
  273. .reduce(
  274. (accumulator, currentTLD) =>
  275. accumulator.catch(() =>
  276. queryArchive(
  277. secondLevelDomain +
  278. currentTLD +
  279. path
  280. )
  281. ),
  282. Promise.reject()
  283. )
  284. .then((archiveURL) =>
  285. getVideoURLFromArchive(archiveURL).then(
  286. (videoURL) => ({
  287. url: videoURL,
  288. text: 'ARCHIVED',
  289. color: 'blue',
  290. }),
  291. () => ({
  292. url: archiveURL,
  293. text: 'MAYBE ARCHIVED',
  294. color: 'aqua',
  295. })
  296. )
  297. );
  298. }
  299. )
  300. .catch(() => ({
  301. url: `https://archive.ph/${encodeURIComponent(
  302. element.href
  303. )}`,
  304. text: 'Try archive.today',
  305. color: 'red',
  306. }))
  307. .then(({ url, text, color }) => {
  308. undergroundLogo.textContent = text;
  309. undergroundLogo.style.backgroundColor = color;
  310. element.href = url;
  311. })
  312. .finally(() => loadingElement.remove());
  313. }
  314. });
  315. waitForKeyElement('[id="stage"]:has([id="thisPlayer"])').then((stage) => {
  316. const videoURL = retrieveVideoURL();
  317. if (videoURL) {
  318. stage.innerHTML = '';
  319. stage.style.textAlign = 'center';
  320.  
  321. const video = GM_addElement(stage, 'video', {
  322. controls: 'controls',
  323. });
  324. video.style.width = 'auto';
  325. video.style.height = '100%';
  326. const source = GM_addElement(video, 'source');
  327. source.src = videoURL;
  328. source.type = 'video/mp4';
  329. }
  330. });
  331. })();