theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

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

  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.6
  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 an archived YNC URL
  146. *
  147. * @param {string} archiveURL
  148. * @returns {Promise<string>}
  149. */
  150. function getVideoURLFromArchive(archiveURL) {
  151. return GM_fetch(archiveURL, {
  152. method: 'GET',
  153. })
  154. .then((response) => {
  155. if (!response.ok) {
  156. console.error(response);
  157. return Promise.reject(response);
  158. }
  159. // When the page is loaded convert it to text
  160. return response;
  161. })
  162. .then((response) => response.text())
  163. .then((html) => {
  164. // Initialize the DOM parser
  165. const parser = new DOMParser();
  166.  
  167. // Parse the text
  168. const doc = parser.parseFromString(html, 'text/html');
  169.  
  170. // You can now even select part of that html as you would in the regular DOM
  171. // Example:
  172. // const docArticle = doc.querySelector('article').innerHTML
  173. const archivedScript = doc.querySelector(
  174. '[id=thisPlayer] + script'
  175. )?.textContent;
  176. if (archivedScript) {
  177. for (const [, videoURL] of archivedScript.matchAll(
  178. /thisPlayer\.setup.*?file: "(?:https?\:\/\/web.archive.org\/web\/\d*?\/)?(https?\:\/\/.*?.\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gms
  179. )) {
  180. if (videoURL) {
  181. return decodeURIComponent(videoURL);
  182. }
  183. }
  184. }
  185. return Promise.reject();
  186. });
  187. }
  188.  
  189. (() => {
  190. 'use strict';
  191.  
  192. const allowedExtensions = [
  193. 'flv',
  194. 'mpg',
  195. 'wmv',
  196. 'avi',
  197. '3gp',
  198. 'qt',
  199. 'mp4',
  200. 'mov',
  201. 'm4v',
  202. 'f4v',
  203. ];
  204.  
  205. GM_addStyle(`
  206. .loader {
  207. border: 0.25em solid #f3f3f3;
  208. border-top-width: 0.25em;
  209. border-top-style: solid;
  210. border-top-color: hsl(0, 0%, 95.3%);
  211. border-top: 0.25em solid rgb(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. waitForElement('.content-block').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 = document.createElement('div');
  251. loadingElement.classList.add('loader');
  252. undergroundLogo.appendChild(loadingElement);
  253. isValidURL(getTheYNCVideoURL(element))
  254. .then(
  255. (url) => {
  256. undergroundLogo.textContent = 'BYPASSED';
  257. undergroundLogo.style.backgroundColor = 'green';
  258. element.href = url;
  259. },
  260. () =>
  261. ['com', 'org', 'net']
  262. .reduce(
  263. (accumulator, currentTLD) =>
  264. accumulator.catch(() =>
  265. queryArchive(
  266. element.href.replace(
  267. /(^https?:\/\/theync\.)(com|org|net)(\/.*$)/gim,
  268. `$1${currentTLD}$3`
  269. )
  270. )
  271. ),
  272. Promise.reject()
  273. )
  274. .then(
  275. (url) =>
  276. getVideoURLFromArchive(url).then(
  277. (videoURL) => {
  278. undergroundLogo.textContent =
  279. 'ARCHIVED';
  280. undergroundLogo.style.backgroundColor =
  281. 'blue';
  282. element.href = videoURL;
  283. },
  284. () => {
  285. undergroundLogo.textContent =
  286. 'MAYBE ARCHIVED';
  287. undergroundLogo.style.backgroundColor =
  288. 'aqua';
  289. element.href = url;
  290. }
  291. ),
  292. () =>
  293. GM_log(
  294. `No bypass or archive found for ${element.href}`
  295. )
  296. )
  297. )
  298.  
  299. .finally(() => loadingElement.remove());
  300. }
  301. });
  302. })();