theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

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

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