theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

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

  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.7
  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 MutationObsever(() => {
  38. const element = document.querySrelector(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('.content-block').then((contentBlock) => {
  247. for (const element of contentBlock.querySelectorAll(
  248. '.upgrade-profile > .upgrade-info-block > .image-block'
  249. )) {
  250. isValidURL(getTheYNCVideoURL(element)).then(
  251. (url) => (location.href = url)
  252. );
  253. }
  254. for (const element of contentBlock.querySelectorAll(
  255. '.inner-block > a'
  256. )) {
  257. const undergroundLogo = element.querySelector(
  258. '.item-info > .border-gold'
  259. );
  260. if (!undergroundLogo) {
  261. continue;
  262. }
  263. const loadingElement = document.createElement('div');
  264. loadingElement.classList.add('loader');
  265. undergroundLogo.appendChild(loadingElement);
  266. isValidURL(getTheYNCVideoURL(element))
  267. .then(
  268. (url) => {
  269. undergroundLogo.textContent = 'BYPASSED';
  270. undergroundLogo.style.backgroundColor = 'green';
  271. element.href = url;
  272. },
  273. () =>
  274. ['com', 'org', 'net']
  275. .reduce(
  276. (accumulator, currentTLD) =>
  277. accumulator.catch(() =>
  278. queryArchive(
  279. element.href.replace(
  280. /(^https?:\/\/theync\.)(com|org|net)(\/.*$)/gim,
  281. `$1${currentTLD}$3`
  282. )
  283. )
  284. ),
  285. Promise.reject()
  286. )
  287. .then(
  288. (url) =>
  289. getVideoURLFromArchive(url).then(
  290. (videoURL) => {
  291. undergroundLogo.textContent =
  292. 'ARCHIVED';
  293. undergroundLogo.style.backgroundColor =
  294. 'blue';
  295. element.href = videoURL;
  296. },
  297. () => {
  298. undergroundLogo.textContent =
  299. 'MAYBE ARCHIVED';
  300. undergroundLogo.style.backgroundColor =
  301. 'aqua';
  302. element.href = url;
  303. }
  304. ),
  305. () =>
  306. GM_log(
  307. `No bypass or archive found for ${element.href}`
  308. )
  309. )
  310. )
  311.  
  312. .finally(() => loadingElement.remove());
  313. }
  314. });
  315. })();