Greasy Fork 还支持 简体中文。

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.6
  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,
  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. return result;
  50. })
  51. .then((result) => result.response)
  52. .then((result) => {
  53. if (
  54. result.archived_snapshots &&
  55. result.archived_snapshots.closest
  56. ) {
  57. return result.archived_snapshots.closest.url;
  58. }
  59. return Promise.reject();
  60. });
  61. } catch (e) {
  62. return Promise.reject();
  63. }
  64. }
  65.  
  66. /**
  67. * Checks whether a URL is valid and accessible.
  68. *
  69. * @param {string} address
  70. * @returns {Promise<string>}
  71. */
  72. function isValidURL(address) {
  73. if (!address) {
  74. return Promise.reject(address);
  75. }
  76. try {
  77. const url = new URL(address);
  78. return GM.xmlHttpRequest({ url, method: 'HEAD' }).then((result) => {
  79. if (result.status === 404) {
  80. return Promise.reject(address);
  81. }
  82. return address;
  83. });
  84. } catch {
  85. return Promise.reject(address);
  86. }
  87. }
  88.  
  89. /**
  90. * Tries to guess the video URL of a given theYNC video via the thumbnail URL.
  91. * Only works on videos published before around May 2023.
  92. *
  93. * @param {Element} element
  94. * @returns {string | undefined}
  95. */
  96. function getTheYNCVideoURL(element) {
  97. const thumbnailURL = element.querySelector('.image > img')?.src;
  98. if (!thumbnailURL) return;
  99. for (const [, group_url] of thumbnailURL.matchAll(
  100. /^https?:\/\/(?:media\.theync\.(?:com|org|net)|(www\.)?theync\.(?:com|org|net)\/media)\/thumbs\/(.+?)\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v)/gim
  101. )) {
  102. if (group_url) {
  103. return `https://media.theync.com/videos/${group_url}.mp4`;
  104. }
  105. }
  106. }
  107.  
  108. /**
  109. * Retrieves the video URL from a theYNC video page
  110. *
  111. * @param {Element} element
  112. * @returns {string | undefined}
  113. */
  114. function retrieveVideoURL(element = document) {
  115. if (location.host === 'archive.ph' || location.host === 'archive.today') {
  116. const videoElement = element.querySelector(
  117. '[id="thisPlayer"] video[old-src]'
  118. );
  119. if (videoElement) {
  120. return videoElement.getAttribute('old-src');
  121. }
  122. }
  123. const videoSrc = element.querySelector(
  124. '.stage-video > .inner-stage video[src]'
  125. )?.src;
  126. if (videoSrc) {
  127. return videoSrc;
  128. }
  129. const playerSetupScript = element.querySelector(
  130. '[id=thisPlayer] + script'
  131. )?.textContent;
  132. if (playerSetupScript) {
  133. // TODO: Find a non-regex solution to this that doesn't involve eval
  134. for (const [, videoURL] of playerSetupScript.matchAll(
  135. /(?<=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
  136. )) {
  137. if (videoURL) {
  138. return decodeURIComponent(videoURL);
  139. }
  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.xmlHttpRequest({ url: archiveURL, method: 'GET' })
  152. .then((result) => {
  153. if (result.status >= 300) {
  154. console.error(result.status);
  155. return Promise.reject(result);
  156. }
  157. return result;
  158. })
  159.  
  160. .then((result) => {
  161. // Initialize the DOM parser
  162. const parser = new DOMParser();
  163.  
  164. // Parse the text
  165. const doc = parser.parseFromString(
  166. result.responseText,
  167. 'text/html'
  168. );
  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 videoURL = retrieveVideoURL(doc);
  174. if (videoURL) {
  175. return videoURL;
  176. }
  177. return Promise.reject();
  178. });
  179. }
  180.  
  181. /**
  182. * set Link of element
  183. *
  184. * @param {HTMLLinkElement} element
  185. * @param {string} url
  186. */
  187. function setElementLink(element, url) {
  188. element.href = url;
  189. }
  190.  
  191. (function () {
  192. 'use strict';
  193.  
  194. const allowedExtensions = [
  195. 'flv',
  196. 'mpg',
  197. 'wmv',
  198. 'avi',
  199. '3gp',
  200. 'qt',
  201. 'mp4',
  202. 'mov',
  203. 'm4v',
  204. 'f4v',
  205. ];
  206.  
  207. GM_addStyle(`
  208. .loader {
  209. border: 0.25em solid #f3f3f3;
  210. border-top: 0.25em solid rgba(0, 0, 0, 0);
  211. border-radius: 50%;
  212. width: 1em;
  213. height: 1em;
  214. animation: spin 2s linear infinite;
  215. }
  216. @keyframes spin {
  217. 0% {
  218. transform: rotate(0deg);
  219. }
  220. 100% {
  221. transform: rotate(360deg);
  222. }
  223. }
  224. .border-gold {
  225. display: flex !important;
  226. align-items: center;
  227. justify-content: center;
  228. gap: 1em;
  229. }
  230. `);
  231.  
  232. waitForKeyElement(
  233. '[id="content"],[id="related-videos"] .content-block'
  234. ).then((contentBlock) => {
  235. for (const element of contentBlock.querySelectorAll(
  236. '.upgrade-profile > .upgrade-info-block > .image-block'
  237. )) {
  238. isValidURL(getTheYNCVideoURL(element)).then(
  239. (url) => (location.href = url)
  240. );
  241. }
  242. for (const element of contentBlock.querySelectorAll(
  243. '.inner-block > a'
  244. )) {
  245. const undergroundLogo = element.querySelector(
  246. '.item-info > .border-gold'
  247. );
  248. if (!undergroundLogo) {
  249. continue;
  250. }
  251. const loadingElement = GM_addElement('div');
  252. loadingElement.classList.add('loader');
  253. undergroundLogo.appendChild(loadingElement);
  254. isValidURL(getTheYNCVideoURL(element))
  255. .then(
  256. (url) => ({
  257. url: url,
  258. text: 'BYPASSED',
  259. color: 'green',
  260. }),
  261. () => {
  262. const match = element.href.match(
  263. /(^https?:\/\/(?:www\.)?theync\.)(?:com|org|net)(\/.*$)/im
  264. );
  265. if (!match || !match[1]) {
  266. return Promise.reject(
  267. `Error with the URL: ${element.href}`
  268. );
  269. }
  270. const [, secondLevelDomain, path] = match;
  271.  
  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:
  302. 'https://archive.ph/' +
  303. encodeURIComponent(element.href),
  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. })();
  332. // ==UserScript==
  333. // @name theYNC.com Underground bypass
  334. // @description Watch theYNC Underground videos without needing an account
  335. // @namespace Violentmonkey Scripts
  336. // @match *://*.theync.com/*
  337. // @match *://theync.com/*
  338. // @match *://*.theync.net/*
  339. // @match *://theync.net/*
  340. // @match *://*.theync.org/*
  341. // @match *://theync.org/*
  342. // @match *://archive.ph/*
  343. // @match *://archive.today/*
  344. // @include /https?:\/\/web\.archive\.org\/web\/\d+?\/https?:\/\/theync\.(?:com|org|net)/
  345. // @require https://update.greasyfork.org/scripts/523012/1519437/WaitForKeyElement.js
  346. // @grant GM.xmlHttpRequest
  347. // @connect media.theync.com
  348. // @connect archive.org
  349. // @grant GM_addStyle
  350. // @grant GM_log
  351. // @grant GM_addElement
  352. // @version 8.6
  353. // @supportURL https://greasyfork.org/en/scripts/520352-theync-com-underground-bypass/feedback
  354. // @license MIT
  355. // @author https://greasyfork.org/en/users/1409235-paywalldespiser
  356. // ==/UserScript==
  357.  
  358. /**
  359. * Fetches available archives of a given address and retrieves their URLs.
  360. *
  361. * @param {string} address
  362. * @returns {Promise<string>}
  363. */
  364. function queryArchive(address) {
  365. try {
  366. const url = new URL('https://archive.org/wayback/available');
  367. url.searchParams.append('url', address);
  368.  
  369. return GM.xmlHttpRequest({
  370. method: 'GET',
  371. url,
  372. redirect: 'follow',
  373. responseType: 'json',
  374. })
  375. .then((result) => {
  376. if (result.status >= 300) {
  377. console.error(result.status);
  378. return Promise.reject(result);
  379. }
  380. return result;
  381. })
  382. .then((result) => result.response)
  383. .then((result) => {
  384. if (
  385. result.archived_snapshots &&
  386. result.archived_snapshots.closest
  387. ) {
  388. return result.archived_snapshots.closest.url;
  389. }
  390. return Promise.reject();
  391. });
  392. } catch (e) {
  393. return Promise.reject();
  394. }
  395. }
  396.  
  397. /**
  398. * Checks whether a URL is valid and accessible.
  399. *
  400. * @param {string} address
  401. * @returns {Promise<string>}
  402. */
  403. function isValidURL(address) {
  404. if (!address) {
  405. return Promise.reject(address);
  406. }
  407. try {
  408. const url = new URL(address);
  409. return GM.xmlHttpRequest({ url, method: 'HEAD' }).then((result) => {
  410. if (result.status === 404) {
  411. return Promise.reject(address);
  412. }
  413. return address;
  414. });
  415. } catch {
  416. return Promise.reject(address);
  417. }
  418. }
  419.  
  420. /**
  421. * Tries to guess the video URL of a given theYNC video via the thumbnail URL.
  422. * Only works on videos published before around May 2023.
  423. *
  424. * @param {Element} element
  425. * @returns {string | undefined}
  426. */
  427. function getTheYNCVideoURL(element) {
  428. const thumbnailURL = element.querySelector('.image > img')?.src;
  429. if (!thumbnailURL) return;
  430. for (const [, group_url] of thumbnailURL.matchAll(
  431. /^https?:\/\/(?:media\.theync\.(?:com|org|net)|(www\.)?theync\.(?:com|org|net)\/media)\/thumbs\/(.+?)\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v)/gim
  432. )) {
  433. if (group_url) {
  434. return `https://media.theync.com/videos/${group_url}.mp4`;
  435. }
  436. }
  437. }
  438.  
  439. /**
  440. * Retrieves the video URL from a theYNC video page
  441. *
  442. * @param {Element} element
  443. * @returns {string | undefined}
  444. */
  445. function retrieveVideoURL(element = document) {
  446. if (location.host === 'archive.ph' || location.host === 'archive.today') {
  447. const videoElement = element.querySelector(
  448. '[id="thisPlayer"] video[old-src]'
  449. );
  450. if (videoElement) {
  451. return videoElement.getAttribute('old-src');
  452. }
  453. }
  454. const videoSrc = element.querySelector(
  455. '.stage-video > .inner-stage video[src]'
  456. )?.src;
  457. if (videoSrc) {
  458. return videoSrc;
  459. }
  460. const playerSetupScript = element.querySelector(
  461. '[id=thisPlayer] + script'
  462. )?.textContent;
  463. if (playerSetupScript) {
  464. // TODO: Find a non-regex solution to this that doesn't involve eval
  465. for (const [, videoURL] of playerSetupScript.matchAll(
  466. /(?<=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
  467. )) {
  468. if (videoURL) {
  469. return decodeURIComponent(videoURL);
  470. }
  471. }
  472. }
  473. }
  474.  
  475. /**
  476. * Retrieves the video URL from an archived YNC URL
  477. *
  478. * @param {string} archiveURL
  479. * @returns {Promise<string>}
  480. */
  481. function getVideoURLFromArchive(archiveURL) {
  482. return GM.xmlHttpRequest({ url: archiveURL, method: 'GET' })
  483. .then((result) => {
  484. if (result.status >= 300) {
  485. console.error(result.status);
  486. return Promise.reject(result);
  487. }
  488. return result;
  489. })
  490.  
  491. .then((result) => {
  492. // Initialize the DOM parser
  493. const parser = new DOMParser();
  494.  
  495. // Parse the text
  496. const doc = parser.parseFromString(
  497. result.responseText,
  498. 'text/html'
  499. );
  500.  
  501. // You can now even select part of that html as you would in the regular DOM
  502. // Example:
  503. // const docArticle = doc.querySelector('article').innerHTML
  504. const videoURL = retrieveVideoURL(doc);
  505. if (videoURL) {
  506. return videoURL;
  507. }
  508. return Promise.reject();
  509. });
  510. }
  511.  
  512. /**
  513. * set Link of element
  514. *
  515. * @param {HTMLLinkElement} element
  516. * @param {string} url
  517. */
  518. function setElementLink(element, url) {
  519. element.href = url;
  520. }
  521.  
  522. (function () {
  523. 'use strict';
  524.  
  525. const allowedExtensions = [
  526. 'flv',
  527. 'mpg',
  528. 'wmv',
  529. 'avi',
  530. '3gp',
  531. 'qt',
  532. 'mp4',
  533. 'mov',
  534. 'm4v',
  535. 'f4v',
  536. ];
  537.  
  538. GM_addStyle(`
  539. .loader {
  540. border: 0.25em solid #f3f3f3;
  541. border-top: 0.25em solid rgba(0, 0, 0, 0);
  542. border-radius: 50%;
  543. width: 1em;
  544. height: 1em;
  545. animation: spin 2s linear infinite;
  546. }
  547. @keyframes spin {
  548. 0% {
  549. transform: rotate(0deg);
  550. }
  551. 100% {
  552. transform: rotate(360deg);
  553. }
  554. }
  555. .border-gold {
  556. display: flex !important;
  557. align-items: center;
  558. justify-content: center;
  559. gap: 1em;
  560. }
  561. `);
  562.  
  563. waitForKeyElement(
  564. '[id="content"],[id="related-videos"] .content-block'
  565. ).then((contentBlock) => {
  566. for (const element of contentBlock.querySelectorAll(
  567. '.upgrade-profile > .upgrade-info-block > .image-block'
  568. )) {
  569. isValidURL(getTheYNCVideoURL(element)).then(
  570. (url) => (location.href = url)
  571. );
  572. }
  573. for (const element of contentBlock.querySelectorAll(
  574. '.inner-block > a'
  575. )) {
  576. const undergroundLogo = element.querySelector(
  577. '.item-info > .border-gold'
  578. );
  579. if (!undergroundLogo) {
  580. continue;
  581. }
  582. const loadingElement = GM_addElement('div');
  583. loadingElement.classList.add('loader');
  584. undergroundLogo.appendChild(loadingElement);
  585. isValidURL(getTheYNCVideoURL(element))
  586. .then(
  587. (url) => ({
  588. url: url,
  589. text: 'BYPASSED',
  590. color: 'green',
  591. }),
  592. () => {
  593. const match = element.href.match(
  594. /(^https?:\/\/(?:www\.)?theync\.)(?:com|org|net)(\/.*$)/im
  595. );
  596. if (!match || !match[1]) {
  597. return Promise.reject(
  598. `Error with the URL: ${element.href}`
  599. );
  600. }
  601. const [, secondLevelDomain, path] = match;
  602.  
  603. return ['com', 'org', 'net']
  604. .reduce(
  605. (accumulator, currentTLD) =>
  606. accumulator.catch(() =>
  607. queryArchive(
  608. secondLevelDomain +
  609. currentTLD +
  610. path
  611. )
  612. ),
  613. Promise.reject()
  614. )
  615. .then((archiveURL) =>
  616. getVideoURLFromArchive(archiveURL).then(
  617. (videoURL) => ({
  618. url: videoURL,
  619. text: 'ARCHIVED',
  620. color: 'blue',
  621. }),
  622. () => ({
  623. url: archiveURL,
  624. text: 'MAYBE ARCHIVED',
  625. color: 'aqua',
  626. })
  627. )
  628. );
  629. }
  630. )
  631. .catch(() => ({
  632. url:
  633. 'https://archive.ph/' +
  634. encodeURIComponent(element.href),
  635. text: 'Try archive.today',
  636. color: 'red',
  637. }))
  638. .then(({ url, text, color }) => {
  639. undergroundLogo.textContent = text;
  640. undergroundLogo.style.backgroundColor = color;
  641. element.href = url;
  642. })
  643. .finally(() => loadingElement.remove());
  644. }
  645. });
  646. waitForKeyElement('[id="stage"]:has([id="thisPlayer"])').then((stage) => {
  647. const videoURL = retrieveVideoURL();
  648. if (videoURL) {
  649. stage.innerHTML = '';
  650. stage.style.textAlign = 'center';
  651.  
  652. const video = GM_addElement(stage, 'video', {
  653. controls: 'controls',
  654. });
  655. video.style.width = 'auto';
  656. video.style.height = '100%';
  657. const source = GM_addElement(video, 'source');
  658. source.src = videoURL;
  659. source.type = 'video/mp4';
  660. }
  661. });
  662. })();