theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

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

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