SaveTube

Download videos from video sharing web sites.

当前为 2024-08-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name SaveTube
  3. // @version 2024.08.09
  4. // @description Download videos from video sharing web sites.
  5. // @author sebaro
  6. // @namespace http://sebaro.pro/savetube
  7. // @icon https://gitlab.com/sebaro/savetube/raw/master/savetube.png
  8. // @include http://youtube.com*
  9. // @include http://www.youtube.com*
  10. // @include https://youtube.com*
  11. // @include https://www.youtube.com*
  12. // @include http://m.youtube.com*
  13. // @include https://m.youtube.com*
  14. // @include http://dailymotion.com*
  15. // @include http://www.dailymotion.com*
  16. // @include https://dailymotion.com*
  17. // @include https://www.dailymotion.com*
  18. // @include http://vimeo.com*
  19. // @include http://www.vimeo.com*
  20. // @include https://vimeo.com*
  21. // @include https://www.vimeo.com*
  22. // @include http://veoh.com*
  23. // @include http://www.veoh.com*
  24. // @include https://veoh.com*
  25. // @include https://www.veoh.com*
  26. // @include http://imdb.com*
  27. // @include http://www.imdb.com*
  28. // @include https://imdb.com*
  29. // @include https://www.imdb.com*
  30. // @noframes
  31. // @grant none
  32. // @run-at document-end
  33. // ==/UserScript==
  34.  
  35.  
  36. /*
  37.  
  38. Copyright (C) 2010 - 2024 Sebastian Luncan
  39.  
  40. This program is free software: you can redistribute it and/or modify
  41. it under the terms of the GNU General Public License as published by
  42. the Free Software Foundation, either version 3 of the License, or
  43. (at your option) any later version.
  44.  
  45. This program is distributed in the hope that it will be useful,
  46. but WITHOUT ANY WARRANTY; without even the implied warranty of
  47. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  48. GNU General Public License for more details.
  49.  
  50. You should have received a copy of the GNU General Public License
  51. along with this program. If not, see <http://www.gnu.org/licenses/>.
  52.  
  53. Website: http://sebaro.pro/savetube
  54. Contact: http://sebaro.pro/contact
  55.  
  56. */
  57.  
  58.  
  59. (function() {
  60.  
  61.  
  62. // Don't run on frames or iframes
  63. if (window.top != window.self) return;
  64.  
  65.  
  66. // ==========Variables========== //
  67.  
  68. // Userscript
  69. var userscript = 'SaveTube';
  70. var website = 'http://sebaro.pro/savetube';
  71. var contact = 'http://sebaro.pro/contact';
  72.  
  73. // Page
  74. var page = {win: window, doc: window.document, body: window.document.body, url: window.location.href, site: window.location.hostname.match(/([^.]+)\.[^.]+$/)[1]};
  75.  
  76. // Saver
  77. var saver = {};
  78. var panelHeight = 30;
  79.  
  80. // Features/Options
  81. var feature = {'definition': true, 'container': true, 'openpagelink': true, 'autosave': true, 'savedash': false, 'showsavelink': true};
  82. var option = {'definition': 'High Definition', 'container': 'MP4', 'openpagelink': false, 'autosave': false, 'savedash': false, 'showsavelink': false, 'hidden': false};
  83.  
  84. // Media
  85. var mediatypes = {'MP4': 'video/mp4', 'WebM': 'video/webm', 'M3U8': 'application/x-mpegURL', 'WebVTT': 'text/vtt'};
  86.  
  87. // Sources
  88. var sources = {};
  89.  
  90.  
  91. // ==========Functions========== //
  92.  
  93. function createMyElement(type, properties, event, listener) {
  94. var obj = page.doc.createElement(type);
  95. for (var propertykey in properties) {
  96. if (propertykey == 'target') obj.setAttribute('target', properties[propertykey]);
  97. else if (propertykey == 'innerHTML') {
  98. try {
  99. obj[propertykey] = properties[propertykey];
  100. }
  101. catch(e) {
  102. if (window.trustedTypes) {
  103. if (!window.trustedTypes.defaultPolicy) {
  104. if (window.trustedTypes.createPolicy) {
  105. window.trustedTypes.createPolicy('default', {
  106. createHTML: (string, sink) => string
  107. });
  108. }
  109. }
  110. if (window.trustedTypes.defaultPolicy) {
  111. obj[propertykey] = window.trustedTypes.defaultPolicy.createHTML(properties[propertykey]);
  112. }
  113. }
  114. }
  115. }
  116. else obj[propertykey] = properties[propertykey];
  117. }
  118. if (event && listener) {
  119. obj.addEventListener(event, listener, false);
  120. }
  121. return obj;
  122. }
  123.  
  124. function modifyMyElement(obj, properties, event, listener) {
  125. for (var propertykey in properties) {
  126. if (propertykey == 'target') obj.setAttribute('target', properties[propertykey]);
  127. else if (propertykey == 'innerHTML') {
  128. try {
  129. obj[propertykey] = properties[propertykey];
  130. }
  131. catch(e) {
  132. if (window.trustedTypes) {
  133. if (!window.trustedTypes.defaultPolicy) {
  134. if (window.trustedTypes.createPolicy) {
  135. window.trustedTypes.createPolicy('default', {
  136. createHTML: (string, sink) => string
  137. });
  138. }
  139. }
  140. if (window.trustedTypes.defaultPolicy) {
  141. obj[propertykey] = window.trustedTypes.defaultPolicy.createHTML(properties[propertykey]);
  142. }
  143. }
  144. }
  145. }
  146. else obj[propertykey] = properties[propertykey];
  147. }
  148. if (event && listener) {
  149. obj.addEventListener(event, listener, false);
  150. }
  151. }
  152.  
  153. function styleMyElement(obj, styles) {
  154. for (var stylekey in styles) {
  155. obj.style[stylekey] = styles[stylekey];
  156. }
  157. }
  158.  
  159. function cleanMyElement(obj, hide) {
  160. if (hide) {
  161. for (var i = 0; i < obj.children.length; i++) {
  162. styleMyElement(obj.children[i], {display: 'none'});
  163. }
  164. }
  165. else {
  166. if (obj.hasChildNodes()) {
  167. while (obj.childNodes.length >= 1) {
  168. obj.removeChild(obj.firstChild);
  169. }
  170. }
  171. }
  172. }
  173.  
  174. function getMyElement(obj, type, from, value, child, content) {
  175. var getObj, chObj, coObj;
  176. var pObj = (!obj) ? page.doc : obj;
  177. if (type == 'body') {
  178. getObj = pObj.body;
  179. }
  180. else {
  181. if (from == 'id') getObj = pObj.getElementById(value);
  182. else if (from == 'class') getObj = pObj.getElementsByClassName(value);
  183. else if (from == 'tag') getObj = pObj.getElementsByTagName(type);
  184. else if (from == 'ns') {
  185. if (pObj.getElementsByTagNameNS) getObj = pObj.getElementsByTagNameNS(value, type);
  186. }
  187. else if (from == 'query') {
  188. if (child > 0) {
  189. if (pObj.querySelectorAll) getObj = pObj.querySelectorAll(value);
  190. }
  191. else {
  192. if (pObj.querySelector) getObj = pObj.querySelector(value);
  193. }
  194. }
  195. }
  196. chObj = (getObj && child >= 0) ? getObj[child] : getObj;
  197. if (content && chObj) {
  198. if (type == 'html' || type == 'body' || type == 'div' || type == 'option') coObj = chObj.innerHTML;
  199. else if (type == 'object') coObj = chObj.data;
  200. else if (type == 'img' || type == 'video' || type == 'embed') coObj = chObj.src;
  201. else coObj = chObj.textContent;
  202. return coObj;
  203. }
  204. else {
  205. return chObj;
  206. }
  207. }
  208.  
  209. function appendMyElement(parent, child) {
  210. parent.appendChild(child);
  211. }
  212.  
  213. function removeMyElement(parent, child) {
  214. parent.removeChild(child);
  215. }
  216.  
  217. function replaceMyElement(parent, orphan, child) {
  218. parent.replaceChild(orphan, child);
  219. }
  220.  
  221. function cleanMyContent(content, unesc, extra) {
  222. if (unesc) content = unescape(content);
  223. content = content.replace(/\\u0025/g, '%');
  224. content = content.replace(/\\u0026/g, '&');
  225. content = content.replace(/\\u002F/g, '/');
  226. content = content.replace(/\\/g, '');
  227. content = content.replace(/\n/g, '');
  228. if (extra) {
  229. content = content.replace(/&quot;/g, '\'').replace(/&#34;/g, '\'').replace(/&#034;/g, '\'').replace(/["“”„‘’]/g, '\'');
  230. content = content.replace(/&#39;/g, '\'').replace(/&#039;/g, '\'').replace(/'/g, '\'');
  231. content = content.replace(/&amp;/g, 'and').replace(/&/g, 'and');
  232. //content = content.replace(/[^\x20-\xFF]/g, '');
  233. content = content.replace(/[\/\|]/g, '-');
  234. content = content.replace(/[<>#:\*\?]/g, '');
  235. content = content.replace(/^\s+|\s+$/, '').replace(/\s+/g, ' ').replace(/\.+$/g, '');
  236. }
  237. return content;
  238. }
  239.  
  240. function parseMyContent(content, pattern) {
  241. var parse, response;
  242. content = content.replace(/(\r\n|\n|\r)/gm, '');
  243. parse = content.match(pattern);
  244. if (parse) {
  245. response = (/g$/.test(pattern)) ? parse : parse[1];
  246. }
  247. return response;
  248. }
  249.  
  250. function getMyContent(url, pattern) {
  251. var urle, data, headers, xhr, response;
  252. if (url.indexOf('|') != -1) {
  253. headers = url.split('|')[2];
  254. data = url.split('|')[1];
  255. url = url.split('|')[0];
  256. }
  257. if (data) {
  258. //console.log('SaveTube: POST [' + pattern + '] ' + url + '\n' + data + '\n' + headers);
  259. urle = btoa(url + data + headers);
  260. if (!sources[urle]) {
  261. xhr = new XMLHttpRequest();
  262. xhr.open('POST', url, false);
  263. if (data.indexOf('{') != -1) {
  264. xhr.setRequestHeader('Content-Type', 'application/json');
  265. }
  266. else {
  267. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  268. }
  269. if (headers) {
  270. headers = JSON.parse(headers);
  271. if (headers['withCredentials']) {
  272. xhr.withCredentials = true;
  273. delete headers['withCredentials']
  274. }
  275. for (var header in headers) {
  276. xhr.setRequestHeader(header, headers[header]);
  277. }
  278. }
  279. try {
  280. xhr.send(data);
  281. }
  282. catch(e) {
  283. }
  284. sources[urle] = (xhr.responseText) ? xhr.responseText : xhr.responseXML;
  285. }
  286. }
  287. else {
  288. //console.log('SaveTube: GET [' + pattern + '] ' + url + '\n' + headers);
  289. urle = btoa(url + headers);
  290. if (!sources[urle]) {
  291. xhr = new XMLHttpRequest();
  292. xhr.open('GET', url, false);
  293. if (headers) {
  294. headers = JSON.parse(headers);
  295. if (headers['withCredentials']) {
  296. xhr.withCredentials = true;
  297. delete headers['withCredentials']
  298. }
  299. for (var header in headers) {
  300. xhr.setRequestHeader(header, headers[header]);
  301. }
  302. }
  303. try {
  304. xhr.send();
  305. }
  306. catch(e) {
  307. }
  308. sources[urle] = (xhr.responseText) ? xhr.responseText : xhr.responseXML;
  309. }
  310. }
  311. if (sources[urle]) {
  312. response = sources[urle];
  313. if (pattern) {
  314. response = parseMyContent(response, pattern);
  315. }
  316. }
  317. return response;
  318. }
  319.  
  320. function createMySaver() {
  321. /* The Panel */
  322. saver['saverPanel'] = createMyElement('div');
  323. styleMyElement(saver['saverPanel'], {position: 'fixed', fontFamily: 'sans-serif', fontSize: '10px', minHeight: panelHeight + 'px', lineHeight: panelHeight + 'px', backgroundColor: '#FFFFFF', padding: '0px 10px 5px 10px', bottom: '0px', right: '25px', zIndex: '2000000000', borderTop: '1px solid #CCCCCC', borderLeft: '1px solid #CCCCCC', borderRight: '1px solid #CCCCCC', borderRadius: '5px 5px 0px 0px', textAlign: 'center', boxSizing: 'content-box'});
  324. appendMyElement(page.body, saver['saverPanel']);
  325.  
  326. /* Panel Hide/Show Toggle Button */
  327. saver['buttonHide'] = createMyElement('div', {title: '{Hide/Show: click to hide/show this panel}'}, 'click', function() {
  328. toggleMySaver();
  329. });
  330. styleMyElement(saver['buttonHide'], {width: '0px', height: '0px', display: 'inline-block', borderTop: '8px solid transparent', borderBottom: '8px solid transparent', borderLeft: '15px solid #777777', borderRight: '0px solid #777777', lineHeight: 'normal', verticalAlign: 'middle', marginLeft: '5px', marginRight: '10px', cursor: 'pointer', boxSizing: 'content-box'});
  331. appendMyElement(saver['saverPanel'], saver['buttonHide']);
  332.  
  333. /* Panel Logo */
  334. saver['panelLogo'] = createMyElement('div', {title: '{SaveTube: click to visit the script wesite}', textContent: userscript}, 'click', function() {
  335. page.win.location.href = website;
  336. });
  337. styleMyElement(saver['panelLogo'], {display: 'inline-block', color: '#32D132', fontSize: '14px', fontWeight: 'bold', border: '1px solid #32D132', borderRadius: '3px', padding: '0px 4px', marginRight: '10px', lineHeight: 'normal', verticalAlign: 'middle', cursor: 'pointer', boxSizing: 'content-box'});
  338. appendMyElement(saver['saverPanel'], saver['panelLogo']);
  339.  
  340. /* Warnings */
  341. if (saver['warnMess']) {
  342. saver['saverMessage'] = createMyElement('div');
  343. styleMyElement(saver['saverMessage'], {display: 'inline-block', fontSize: '12px', color: '#AD0000'});
  344. appendMyElement(saver['saverPanel'], saver['saverMessage']);
  345. if (saver['warnContent']) showMyMessage(saver['warnMess'], saver['warnContent']);
  346. else showMyMessage(saver['warnMess']);
  347. return;
  348. }
  349.  
  350. /* Panel Video Menu */
  351. saver['videoMenu'] = createMyElement('select', {title: '{Videos: select the video format for download}'}, 'change', function() {
  352. saver['videoSave'] = this.value;
  353. if (saver['isShowingLink']) {
  354. cleanMyElement(saver['buttonSaveLink'], false);
  355. saver['isShowingLink'] = false;
  356. }
  357. if (option['autosave']) {
  358. saveMyVideo();
  359. }
  360. });
  361. styleMyElement(saver['videoMenu'], {display: 'inline-block', width: 'auto', height: '20px', fontFamily: 'inherit', fontSize: '14px', fontWeight: 'bold', padding: '0px 3px', overflow: 'hidden', border: '1px solid #CCCCCC', color: '#777777', backgroundColor: '#FFFFFF', lineHeight: 'normal', verticalAlign: 'middle', cursor: 'pointer', boxSizing: 'content-box'});
  362. appendMyElement(saver['saverPanel'], saver['videoMenu']);
  363. if (feature['openpagelink']) {
  364. saver['videoList']['Page Link'] = page.url;
  365. }
  366. var videosProgressive = [];
  367. var videosAdaptiveHLS = [];
  368. var videosAdaptiveDASHVideo = [];
  369. var videosAdaptiveDASHAudio = [];
  370. var videosAdaptiveDASHMuxed = [];
  371. var videosExtra = [];
  372. for (var videoCode in saver['videoList']) {
  373. if (videoCode.indexOf('Video') != -1) videosAdaptiveDASHVideo.push(videoCode);
  374. else if (videoCode.indexOf('Audio') != -1) videosAdaptiveDASHAudio.push(videoCode);
  375. else if (saver['videoList'][videoCode] == 'DASH') videosAdaptiveDASHMuxed.push(videoCode);
  376. else if (videoCode.indexOf('M3U8') != -1) videosAdaptiveHLS.push(videoCode);
  377. else if (videoCode.indexOf('MP4') != -1 || videoCode.indexOf('WebM') != -1) videosProgressive.push(videoCode);
  378. else videosExtra.push(videoCode);
  379. }
  380. if (videosProgressive.length > 0) {
  381. for (var i = 0; i < videosProgressive.length; i++) {
  382. saver['videoItem'] = createMyElement('option', {value: videosProgressive[i], textContent: videosProgressive[i]});
  383. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', cursor: 'pointer'});
  384. appendMyElement(saver['videoMenu'], saver['videoItem']);
  385. }
  386. }
  387. if (videosAdaptiveHLS.length > 0) {
  388. saver['videoItem'] = createMyElement('option', {value: 'HLS', textContent: 'HLS'});
  389. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', color: '#FF0000'});
  390. saver['videoItem'].disabled = 'disabled';
  391. appendMyElement(saver['videoMenu'], saver['videoItem']);
  392. for (var i = 0; i < videosAdaptiveHLS.length; i++) {
  393. saver['videoItem'] = createMyElement('option', {value: videosAdaptiveHLS[i], textContent: videosAdaptiveHLS[i]});
  394. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', cursor: 'pointer'});
  395. appendMyElement(saver['videoMenu'], saver['videoItem']);
  396. }
  397. }
  398. if (videosAdaptiveDASHVideo.length > 0) {
  399. saver['videoItem'] = createMyElement('option', {value: 'DASH (Video Only)', textContent: 'DASH (Video Only)'});
  400. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', color: '#FF0000'});
  401. saver['videoItem'].disabled = 'disabled';
  402. appendMyElement(saver['videoMenu'], saver['videoItem']);
  403. for (var i = 0; i < videosAdaptiveDASHVideo.length; i++) {
  404. saver['videoItem'] = createMyElement('option', {value: videosAdaptiveDASHVideo[i], textContent: videosAdaptiveDASHVideo[i]});
  405. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', cursor: 'pointer'});
  406. appendMyElement(saver['videoMenu'], saver['videoItem']);
  407. }
  408. }
  409. if (videosAdaptiveDASHAudio.length > 0) {
  410. saver['videoItem'] = createMyElement('option', {value: 'DASH (Audio Only)', textContent: 'DASH (Audio Only)'});
  411. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', color: '#FF0000'});
  412. saver['videoItem'].disabled = 'disabled';
  413. appendMyElement(saver['videoMenu'], saver['videoItem']);
  414. for (var i = 0; i < videosAdaptiveDASHAudio.length; i++) {
  415. saver['videoItem'] = createMyElement('option', {value: videosAdaptiveDASHAudio[i], textContent: videosAdaptiveDASHAudio[i]});
  416. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', cursor: 'pointer'});
  417. appendMyElement(saver['videoMenu'], saver['videoItem']);
  418. }
  419. }
  420. if (videosAdaptiveDASHMuxed.length > 0) {
  421. feature['savedash'] = true;
  422. if (option['savedash']) {
  423. saver['videoItem'] = createMyElement('option', {value: 'DASH (Video With Audio)', textContent: 'DASH (Video With Audio)'});
  424. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', color: '#FF0000'});
  425. saver['videoItem'].disabled = 'disabled';
  426. appendMyElement(saver['videoMenu'], saver['videoItem']);
  427. for (var i = 0; i < videosAdaptiveDASHMuxed.length; i++) {
  428. saver['videoItem'] = createMyElement('option', {value: videosAdaptiveDASHMuxed[i], textContent: videosAdaptiveDASHMuxed[i]});
  429. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', cursor: 'pointer'});
  430. appendMyElement(saver['videoMenu'], saver['videoItem']);
  431. }
  432. }
  433. else {
  434. for (var videoCode in saver['videoList']) {
  435. if (saver['videoList'][videoCode] == 'DASH') delete saver['videoList'][videoCode];
  436. }
  437. }
  438. }
  439. if (videosExtra.length > 0) {
  440. saver['videoItem'] = createMyElement('option', {value: 'Extra', textContent: 'Extra'});
  441. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', color: '#FF0000'});
  442. saver['videoItem'].disabled = 'disabled';
  443. appendMyElement(saver['videoMenu'], saver['videoItem']);
  444. for (var i = 0; i < videosExtra.length; i++) {
  445. saver['videoItem'] = createMyElement('option', {value: videosExtra[i], textContent: videosExtra[i]});
  446. styleMyElement(saver['videoItem'], {fontSize: '14px', fontWeight: 'bold', cursor: 'pointer'});
  447. appendMyElement(saver['videoMenu'], saver['videoItem']);
  448. }
  449. }
  450.  
  451. /* Panel Options Button */
  452. saver['buttonOptions'] = createMyElement('div', {title: '{Options: click to show the available options}'}, 'click', function() {
  453. if (saver['showsOptions']) {
  454. saver['showsOptions'] = false;
  455. styleMyElement(saver['optionsContent'], {display: 'none'})
  456. }
  457. else {
  458. saver['showsOptions'] = true;
  459. styleMyElement(saver['optionsContent'], {display: 'block'})
  460. }
  461. });
  462. styleMyElement(saver['buttonOptions'], {width: '1px', height: '8px', borderTop: '4px dotted #777777', borderBottom: '4px dotted #777777', borderLeft: '4px dotted #777777', display: 'inline-block', lineHeight: 'normal', verticalAlign: 'middle', marginLeft: '20px', cursor: 'pointer', boxSizing: 'content-box'});
  463. appendMyElement(saver['saverPanel'], saver['buttonOptions']);
  464.  
  465. /* Panel Save Button */
  466. saver['buttonSave'] = createMyElement('div', {title: '{Save: click to download the selected video format}'}, 'click', function() {
  467. saveMyVideo();
  468. });
  469. styleMyElement(saver['buttonSave'], {width: '0px', height: '0px', display: 'inline-block', borderLeft: '8px solid transparent', borderRight: '8px solid transparent', borderTop: '15px solid #777777', borderBottom: '0px solid #777777', lineHeight: 'normal', verticalAlign: 'middle', marginTop: '1px', marginLeft: '20px', cursor: 'pointer', boxSizing: 'content-box'});
  470. appendMyElement(saver['saverPanel'], saver['buttonSave']);
  471.  
  472. /* Panel Save Button Link */
  473. saver['buttonSaveLink'] = createMyElement('div', {title: '{Save: right click & save as to download the selected video format}'});
  474. styleMyElement(saver['buttonSaveLink'], {display: 'inline-block', color: '#777777', fontSize: '14px', fontWeight: 'bold', lineHeight: 'normal', verticalAlign: 'middle', marginLeft: '5px', marginBottom: '2px', boxSizing: 'content-box'});
  475. appendMyElement(saver['saverPanel'], saver['buttonSaveLink']);
  476.  
  477. /* Disable Features */
  478. if (saver['videoDefinitions'].length < 2) feature['definition'] = false;
  479. if (saver['videoContainers'].length < 2) feature['container'] = false;
  480.  
  481. /* Select The Video */
  482. if (feature['definition'] || feature['container'] || feature['openpagelink']) {
  483. if (!option['definition'] || saver['videoDefinitions'].indexOf(option['definition']) == -1) option['definition'] = saver['videoSave'].replace(/Definition.*/, 'Definition');
  484. if (!option['container'] || saver['videoContainers'].indexOf(option['container']) == -1) option['container'] = saver['videoSave'].replace(/.*\s/, '');
  485. selectMyVideo();
  486. }
  487.  
  488. /* Save The Video On Autosave */
  489. if (option['autosave']) saveMyVideo();
  490.  
  491. /* Panel Options */
  492. saver['optionsContent'] = createMyElement('div');
  493. styleMyElement(saver['optionsContent'], {display: 'none', fontSize: '14px', fontWeight: 'bold', padding: '10px', textAlign: 'center', boxSizing: 'content-box'});
  494. appendMyElement(saver['saverPanel'], saver['optionsContent']);
  495.  
  496. /* Options Object => option: [label, options, new line, change video] */
  497. var options = {
  498. 'definition': ['Definition', saver['videoDefinitions'], true, true],
  499. 'container': ['Container', saver['videoContainers'], false, true],
  500. 'openpagelink': ['Open Page Link', ['On', 'Off'], true, true],
  501. 'autosave': ['Autosave', ['On', 'Off'], true, true],
  502. 'showsavelink': ['Show Save Link', ['On', 'Off'], false, true],
  503. 'savedash': ['Save DASH (Video With Audio)', ['On', 'Off'], true, false]
  504. };
  505.  
  506. /* Options */
  507. var optionsBox, optionBox, optionLabel, optionMenu, optionMenuItem;
  508. for (var o in options) {
  509. if (feature[o] === false) continue;
  510. if (options[o][2]) {
  511. optionsBox = createMyElement('div');
  512. styleMyElement(optionsBox, {display: 'block', padding: '5px 0px 5px 0px'});
  513. appendMyElement(saver['optionsContent'], optionsBox);
  514. }
  515. optionBox = createMyElement('div');
  516. styleMyElement(optionBox, {display: 'inline-block'});
  517. optionLabel = createMyElement('div', {textContent: options[o][0]});
  518. styleMyElement(optionLabel, {display: 'inline-block', color: '#777777', marginRight: '10px', verticalAlign: 'middle'});
  519. optionMenu = createMyElement('select', {id: 'savetube-option-' + o}, 'change', function() {
  520. var id = this.id.replace('savetube-option-', '');
  521. if (this.value == 'On' || this.value == 'Off') {
  522. option[id] = (this.value == 'On') ? true : false;
  523. }
  524. else {
  525. option[id] = this.value;
  526. }
  527. setMyOptions(id, option[id]);
  528. if (options[id][3]) {
  529. if (saver['isShowingLink']) {
  530. cleanMyElement(saver['buttonSaveLink'], false);
  531. saver['isShowingLink'] = false;
  532. }
  533. selectMyVideo();
  534. if (option['autosave']) {
  535. saveMyVideo();
  536. }
  537. }
  538. });
  539. styleMyElement(optionMenu, {display: 'inline-block', width: 'auto', height: '20px', color: '#777777', backgroundColor: '#FFFFFF', border: '1px solid #CCCCCC', fontFamily: 'inherit', fontSize: '14px', fontWeight: 'bold', marginRight: '10px', verticalAlign: 'middle'});
  540. appendMyElement(optionBox, optionLabel);
  541. appendMyElement(optionBox, optionMenu);
  542. appendMyElement(optionsBox, optionBox);
  543. for (var i = 0; i < options[o][1].length; i++) {
  544. optionMenuItem = createMyElement('option', {value: options[o][1][i], textContent: options[o][1][i]});
  545. styleMyElement(optionMenuItem, {fontSize: '14px', fontWeight: 'bold', cursor: 'pointer'});
  546. appendMyElement(optionMenu, optionMenuItem);
  547. }
  548. if (optionMenu.value == 'On' || optionMenu.value == 'Off') {
  549. if (option[o]) optionMenu.value = 'On';
  550. else optionMenu.value = 'Off';
  551. }
  552. else {
  553. optionMenu.value = option[o];
  554. }
  555. }
  556.  
  557. /* Hide */
  558. if (option['hidden']) {
  559. toggleMySaver('hide');
  560. }
  561. }
  562.  
  563. function toggleMySaver(toggle) {
  564. if (toggle == 'hide') {
  565. styleMyElement(saver['saverPanel'], {right: '-' + (saver['saverPanel'].offsetWidth - 40) + 'px', backgroundColor: 'transparent', borderColor: '#777777'});
  566. styleMyElement(saver['buttonHide'], {borderTop: '8px solid transparent', borderBottom: '8px solid transparent', borderLeft: '0px solid #777777', borderRight: '15px solid #777777'});
  567. }
  568. else {
  569. if (option['hidden']) {
  570. styleMyElement(saver['saverPanel'], {right: '25px', backgroundColor: '#FFFFFF', borderColor: '#CCCCCC', transition: 'right 2s, background-color 5s, border-color 5s'});
  571. styleMyElement(saver['buttonHide'], {borderTop: '8px solid transparent', borderBottom: '8px solid transparent', borderLeft: '15px solid #777777', borderRight: '0px solid #777777'});
  572. option['hidden'] = false;
  573. }
  574. else {
  575. styleMyElement(saver['saverPanel'], {right: '-' + (saver['saverPanel'].offsetWidth - 40) + 'px', backgroundColor: 'transparent', borderColor: '#777777', transition: 'right 2s, background-color 5s, border-color 5s'});
  576. styleMyElement(saver['buttonHide'], {borderTop: '8px solid transparent', borderBottom: '8px solid transparent', borderLeft: '0px solid #777777', borderRight: '15px solid #777777'});
  577. option['hidden'] = true;
  578. }
  579. setMyOptions('hidden', option['hidden']);
  580. }
  581. }
  582.  
  583. function setMyOptions(key, value) {
  584. key = page.site + '_' + userscript.toLowerCase() + '_' + key;
  585. try {
  586. localStorage.setItem(key, value);
  587. if (localStorage.getItem(key) == value) return;
  588. else throw false;
  589. }
  590. catch(e) {
  591. var date = new Date();
  592. date.setTime(date.getTime() + (356*24*60*60*1000));
  593. var expires = '; expires=' + date.toGMTString();
  594. page.doc.cookie = key + '=' + value + expires + '; path=/';
  595. }
  596. }
  597.  
  598. function getMyOptions() {
  599. for (var opt in option) {
  600. var key = page.site + '_' + userscript.toLowerCase() + '_' + opt;
  601. try {
  602. if (localStorage.getItem(key)) {
  603. option[opt] = localStorage.getItem(key);
  604. continue;
  605. }
  606. else throw false;
  607. }
  608. catch(e) {
  609. var cookies = page.doc.cookie.split(';');
  610. for (var i=0; i < cookies.length; i++) {
  611. var cookie = cookies[i];
  612. while (cookie.charAt(0) == ' ') cookie = cookie.substring(1, cookie.length);
  613. option[opt] = (cookie.indexOf(key) == 0) ? cookie.substring(key.length + 1, cookie.length) : option[opt];
  614. }
  615. }
  616. }
  617. var boolOptions = ['openpagelink', 'autosave', 'showsavelink', 'savedash', 'hidden'];
  618. for (var i = 0; i < boolOptions.length; i++) {
  619. option[boolOptions[i]] = (option[boolOptions[i]] === true || option[boolOptions[i]] == 'true') ? true : false;
  620. }
  621. }
  622.  
  623. function selectMyVideo() {
  624. if (option['openpagelink']) {
  625. saver['videoSave'] = 'Page Link';
  626. }
  627. else {
  628. var vdoCont = (option['container'] != 'Any') ? [option['container']] : saver['videoContainers'];
  629. var vdoDef = saver['videoDefinitions'];
  630. var vdoList = {};
  631. for (var vC = 0; vC < vdoCont.length; vC++) {
  632. if (vdoCont[vC] != 'Any') {
  633. for (var vD = 0; vD < vdoDef.length; vD++) {
  634. var format = vdoDef[vD] + ' ' + vdoCont[vC];
  635. if (!vdoList[vdoDef[vD]]) {
  636. for (var vL in saver['videoList']) {
  637. if (vL == format) {
  638. vdoList[vdoDef[vD]] = vL;
  639. break;
  640. }
  641. }
  642. }
  643. }
  644. }
  645. }
  646. var vdoDef2 = [];
  647. var keepDef = false;
  648. for (var vD = 0; vD < vdoDef.length; vD++) {
  649. if (vdoDef[vD] == option['definition'] && keepDef == false) keepDef = true;
  650. if (keepDef == true) vdoDef2.push(vdoDef[vD])
  651. }
  652. for (var vD = 0; vD < vdoDef2.length; vD++) {
  653. if (vdoList[vdoDef2[vD]]) {
  654. saver['videoSave'] = vdoList[vdoDef2[vD]];
  655. break;
  656. }
  657. }
  658. }
  659. saver['videoMenu'].value = saver['videoSave'];
  660. }
  661.  
  662. function saveMyVideo() {
  663. var vdoURL = saver['videoList'][saver['videoSave']];
  664. var vdoDef = ' (' + saver['videoSave'].split(' ').slice(0, -1).join('').match(/[A-Z]/g).join('') + ')';
  665. var vdoExt = saver['videoSave'].split(' ').slice(-1).join('');
  666. var vdoTle = (saver['videoTitle']) ? saver['videoTitle'] + vdoDef : page.url.replace(/https?:\/\//, '').replace(/[^0-9a-zA-Z]/g, '-') + vdoDef;
  667. if (saver['videoSave'] == 'Page Link' || vdoURL == 'DASH' || (vdoExt == 'M3U8' && !option['showsavelink'])) {
  668. var vdoVdo, vdoAdo;
  669. if (saver['videoSave'] == 'Page Link' || vdoExt == 'M3U8') {
  670. vdoVdo = vdoURL;
  671. vdoAdo = '';
  672. vdoDef = '';
  673. }
  674. else {
  675. if (saver['videoSave'].indexOf('MP4') != -1) {
  676. vdoVdo = saver['videoList'][saver['videoSave'].replace('MP4', 'Video MP4')];
  677. vdoAdo = saver['videoList']['Medium Bitrate Audio MP4'] || saver['videoList'][saver['videoSave'].replace('MP4', 'Audio MP4')];
  678. }
  679. else {
  680. vdoVdo = saver['videoList'][saver['videoSave'].replace('WebM', 'Video WebM')];
  681. vdoAdo = saver['videoList']['High Bitrate Audio WebM'] || saver['videoList']['Medium Bitrate Audio WebM'] || saver['videoList']['Medium Bitrate Audio MP4'];
  682. }
  683. }
  684. page.win.location.href = 'savetube:' + vdoTle + 'SEPARATOR' + vdoVdo + 'SEPARATOR' + vdoAdo;
  685. }
  686. else {
  687. if (page.site == 'youtube' && saver['videoSave'] == 'High Definition MP4') {
  688. vdoURL = vdoURL + '&title=' + vdoTle;
  689. }
  690. var vdoLnk = createMyElement('a', {href: vdoURL, target: '_blank', textContent: '[Link]'});
  691. styleMyElement(vdoLnk, {color: '#777777', textDecoration: 'underline'});
  692. if (option['showsavelink'] || vdoExt == 'M3U8') {
  693. if (!saver['isShowingLink']) {
  694. appendMyElement(saver['buttonSaveLink'], vdoLnk);
  695. saver['isShowingLink'] = true;
  696. if (page.site == 'youtube' && saver['videoSave'] == 'High Definition MP4') {
  697. page.win.location.href = vdoURL;
  698. }
  699. }
  700. }
  701. else {
  702. if (!saver['isSaving']) {
  703. if (page.site == 'youtube' && saver['videoSave'] == 'High Definition MP4') {
  704. page.win.location.href = vdoURL;
  705. }
  706. else {
  707. if (page.win.URL && page.win.URL.createObjectURL) {
  708. saver['isSaving'] = true;
  709. styleMyElement(saver['buttonSave'], {borderBottomWidth: '1px', cursor: 'none'});
  710. var vdoLnkBlob, vdoBlob, vdoBlobLnk;
  711. vdoLnkBlob = createMyElement('a');
  712. styleMyElement(vdoLnkBlob, {display: 'none'});
  713. appendMyElement(page.body, vdoLnkBlob);
  714. var XHRequest = new XMLHttpRequest();
  715. XHRequest.open('GET', vdoURL);
  716. XHRequest.responseType = 'arraybuffer';
  717. XHRequest.onload = function() {
  718. if (this.status === 200 && this.response) {
  719. vdoBlob = new Blob([this.response], {type: mediatypes[vdoExt]});
  720. vdoBlobLnk = page.win.URL.createObjectURL(vdoBlob);
  721. modifyMyElement(vdoLnkBlob, {href: vdoBlobLnk, target: '_blank', download: vdoTle + '.' + vdoExt.toLowerCase()});
  722. vdoLnkBlob.click();
  723. page.win.URL.revokeObjectURL(vdoBlobLnk);
  724. removeMyElement(page.body, vdoLnkBlob);
  725. saver['isSaving'] = false;
  726. styleMyElement(saver['buttonSave'], {borderBottomWidth: '0px', cursor: 'pointer'});
  727. }
  728. else {
  729. saver['isSaving'] = false;
  730. styleMyElement(saver['buttonSave'], {borderBottomWidth: '0px', cursor: 'pointer'});
  731. if (!saver['isShowingLink']) {
  732. appendMyElement(saver['buttonSaveLink'], vdoLnk);
  733. saver['isShowingLink'] = true;
  734. }
  735. }
  736. }
  737. XHRequest.onerror = function() {
  738. saver['isSaving'] = false;
  739. styleMyElement(saver['buttonSave'], {borderBottomWidth: '0px', cursor: 'pointer'});
  740. if (!saver['isShowingLink']) {
  741. appendMyElement(saver['buttonSaveLink'], vdoLnk);
  742. saver['isShowingLink'] = true;
  743. }
  744. }
  745. XHRequest.send();
  746. }
  747. else {
  748. appendMyElement(saver['buttonSaveLink'], vdoLnk);
  749. saver['isShowingLink'] = true;
  750. }
  751. }
  752. }
  753. }
  754. }
  755. }
  756.  
  757. function showMyMessage(cause, content) {
  758. if (cause == '!content') {
  759. modifyMyElement(saver['saverMessage'], {innerHTML: 'Couldn\'t get the videos content. Please report it <a href="' + contact + '" style="color:#00892C">here</a>.'});
  760. }
  761. else if (cause == '!videos') {
  762. modifyMyElement(saver['saverMessage'], {innerHTML: 'Couldn\'t get any video. Please report it <a href="' + contact + '" style="color:#00892C">here</a>.'});
  763. }
  764. else if (cause == '!support') {
  765. modifyMyElement(saver['saverMessage'], {innerHTML: 'This video uses the RTMP protocol which is not supported.'});
  766. }
  767. else if (cause == 'embed') {
  768. modifyMyElement(saver['saverMessage'], {innerHTML: 'This is an embedded video. You can get it <a href="' + content + '" style="color:#00892C">here</a>.'});
  769. }
  770. else if (cause == 'other') {
  771. modifyMyElement(saver['saverMessage'], {innerHTML: content});
  772. }
  773. }
  774.  
  775.  
  776. // ==========Websites========== //
  777.  
  778. function SaveTube() {
  779.  
  780. // =====YouTube===== //
  781.  
  782. if (page.url.indexOf('youtube.com/watch') != -1) {
  783.  
  784. /* Video Availability */
  785. if (getMyContent(page.url, /"playabilityStatus":\{"status":"(ERROR|UNPLAYABLE|LIVE_STREAM_OFFLINE)"/)) return;
  786.  
  787. /* Get Video ID */
  788. var ytVideoId = parseMyContent(page.url, /(?:\?|&)v=(.*?)(&|$)/);
  789.  
  790. /* Create Saver */
  791. var ytVideoTitle, ytVideoAuthor;
  792. var ytVideoList = {};
  793. var ytDefaultVideo = 'Low Definition MP4';
  794. function ytCreateSaver(data) {
  795. if (data) {
  796. saver = data;
  797. }
  798. else {
  799. saver = {
  800. 'videoList': ytVideoList,
  801. 'videoDefinitions': ['Ultra High Definition', 'Quad High Definition', 'Full High Definition', 'High Definition', 'Standard Definition', 'Low Definition', 'Very Low Definition'],
  802. 'videoContainers': ['MP4', 'WebM', 'M3U8', 'Any'],
  803. 'videoSave': ytDefaultVideo,
  804. 'videoTitle': ytVideoTitle
  805. };
  806. }
  807. createMySaver();
  808. }
  809.  
  810. /* Script */
  811. var ytScriptUrl;
  812. function ytGetScriptUrl() {
  813. if (!ytScriptUrl) {
  814. ytScriptUrl = getMyContent(page.url, /"js(?:Url)?":\s*"(.*?)"/);
  815. if (!ytScriptUrl) {
  816. ytScriptUrl = getMyContent(page.url.replace(/watch.*?v=/, 'embed/').replace(/&.*$/, ''), /"js(?:Url)?":\s*"(.*?)"/);
  817. }
  818. if (ytScriptUrl && ytScriptUrl.indexOf('//') == -1) {
  819. ytScriptUrl = page.win.location.protocol + '//' + page.win.location.hostname + ytScriptUrl;
  820. }
  821. if (!ytScriptUrl) {
  822. showMyMessage('other', 'Couldn\'t get the script link. Please report it <a href="' + contact + '" style="color:#00892C">here</a>.');
  823. }
  824. }
  825. }
  826.  
  827. /* Parameter Unscrambler */
  828. var ytUnscrambleParam = {};
  829. function ytGetUnscrambleParamFunc() {
  830. ytGetScriptUrl();
  831. var ytMainFuncName, ytMainFuncBody, ytExtraFuncName, ytExtraFuncBody;
  832. /* s */
  833. ytMainFuncName = getMyContent(ytScriptUrl, /c&&\([\w$]+=([\w$]+)\(decodeURIComponent/);
  834. if (ytMainFuncName) {
  835. ytMainFuncBody = getMyContent(ytScriptUrl, new RegExp('(?:^|;)' + ytMainFuncName.replace(/\$/, '\\$') + '\\s*=\\s*function\\s*' + '\\s*\\(\\w+\\)\\s*\\{(.*?\\))\\};'));
  836. if (ytMainFuncBody) {
  837. ytExtraFuncName = parseMyContent(ytMainFuncBody, /([\w$]+)\.[\w$]+\(\w,[0-9]+\)/);
  838. if (ytExtraFuncName) {
  839. ytExtraFuncBody = getMyContent(ytScriptUrl, new RegExp('var\\s+' + ytExtraFuncName.replace(/\$/, '\\$') + '=\\s*\\{(.*?)\\};'));
  840. if (ytExtraFuncBody) {
  841. ytMainFuncBody = 'var ' + ytExtraFuncName + '={' + ytExtraFuncBody + '};' + ytMainFuncBody;
  842. ytMainFuncBody = 'try {' + ytMainFuncBody + '} catch(e) {return null}';
  843. ytUnscrambleParam['s'] = new Function('a', ytMainFuncBody);
  844. }
  845. }
  846. }
  847. }
  848. /* n */
  849. ytMainFuncName = getMyContent(ytScriptUrl, /&&\([\w$]+=([\w$]+)\(\w+\),\w+\.set\("n"/);
  850. if (!ytMainFuncName) ytMainFuncName = getMyContent(ytScriptUrl, /set\("n".*?\|\|([\w$]+)\(/);
  851. if (!ytMainFuncName) ytMainFuncName = getMyContent(ytScriptUrl, /String.fromCharCode\(110.*?\|\|([\w$]+)\(/);
  852. if (!ytMainFuncName) ytMainFuncName = getMyContent(ytScriptUrl, /b="nn".*?\|\|([\w$]+)\(/);
  853. if (ytMainFuncName) {
  854. ytMainFuncBody = getMyContent(ytScriptUrl, new RegExp('(?:^|;)' + ytMainFuncName.replace(/\$/, '\\$') + '\\s*=\\s*function\\s*' + '\\s*\\(\\w+\\)\\s*\\{(.*?\\))\\};'));
  855. if (ytMainFuncBody) {
  856. ytMainFuncBody = ytMainFuncBody.replace(/(\d+)--(\d+)/, '$1- -$2', ytMainFuncBody);
  857. ytMainFuncBody = 'try {' + ytMainFuncBody + '} catch(e) {return null}';
  858. ytUnscrambleParam['n'] = new Function('a', ytMainFuncBody);
  859. }
  860. }
  861. }
  862.  
  863. /* Get Videos Content */
  864. var ytVideosContent = {};
  865. var ytVideosContentHLS;
  866. var ytVideoInfoUrl = page.win.location.protocol + '//' + page.win.location.hostname + '/youtubei/v1/player?prettyPrint=false';
  867. var ytVideoInfoClientVersion = {'WEB': '2.20240726.00.00', 'WEB_CREATOR': '1.20240723.03.00', 'TVHTML5_SIMPLY_EMBEDDED_PLAYER': '2.0', 'IOS': '19.29.1'};
  868. var ytVideoSignatureTimestamp = 19173;
  869. var ytVideoInfoDataRequest = {};
  870. function ytGetVideos(api, client, embed) {
  871. if (api) {
  872. ytVideoInfoDataRequest = {};
  873. ytVideoInfoDataRequest['context'] = {};
  874. ytVideoInfoDataRequest['context']['client'] = {};
  875. ytVideoInfoDataRequest['context']['client']['clientName'] = client;
  876. ytVideoInfoDataRequest['context']['client']['clientVersion'] = ytVideoInfoClientVersion[client];
  877. if (embed) {
  878. ytVideoInfoDataRequest['context']['client']['clientScreen'] = 'EMBED';
  879. ytVideoInfoDataRequest['context']['thirdParty'] = {};
  880. ytVideoInfoDataRequest['context']['thirdParty']['embedUrl'] = 'https://www.youtube.com';
  881. }
  882. ytVideoInfoDataRequest['playbackContext'] = {};
  883. ytVideoInfoDataRequest['playbackContext']['contentPlaybackContext'] = {};
  884. ytVideoInfoDataRequest['playbackContext']['contentPlaybackContext']['html5Preference'] = 'HTML5_PREF_WANTS';
  885. ytGetScriptUrl();
  886. ytVideoSignatureTimestamp = getMyContent(ytScriptUrl, /signatureTimestamp:(\d+)/);
  887. ytVideoInfoDataRequest['playbackContext']['contentPlaybackContext']['signatureTimestamp'] = ytVideoSignatureTimestamp;
  888. ytVideoInfoDataRequest['videoId'] = ytVideoId;
  889. ytVideosContent = getMyContent(ytVideoInfoUrl + '|' + JSON.stringify(ytVideoInfoDataRequest));
  890. }
  891. else {
  892. ytVideosContent = getMyContent(page.url, /ytInitialPlayerResponse\s*=\s*({.*?});/);
  893. }
  894. try {
  895. ytVideosContent = JSON.parse(ytVideosContent);
  896. if (!ytVideosContentHLS) ytVideosContentHLS = ytVideosContent['streamingData']['hlsManifestUrl'];
  897. if (ytVideosContent['videoDetails'] && ytVideosContent['videoDetails']['videoId'] != ytVideoId) {
  898. ytVideosContent = {};
  899. }
  900. if (ytVideosContent['videoDetails']) {
  901. ytVideoTitle = ytVideosContent['videoDetails']['title'];
  902. ytVideoAuthor = ytVideosContent['videoDetails']['author'];
  903. ytVideoTitle = ytVideoTitle + ' by ' + ytVideoAuthor;
  904. ytVideoTitle = cleanMyContent(ytVideoTitle, false, true);
  905. if (ytVideosContent['captions'] && ytVideosContent['captions']['playerCaptionsTracklistRenderer']) {
  906. ytSubtitlesContent = ytVideosContent['captions']['playerCaptionsTracklistRenderer']['captionTracks'];
  907. }
  908. }
  909. }
  910. catch(e) {
  911. ytVideosContent = {};
  912. }
  913. ytVideosContent = (ytVideosContent['streamingData']) ? ytVideosContent['streamingData'] : {};
  914. }
  915.  
  916. /* Get Videos */
  917. ytGetVideos(true, 'WEB_CREATOR', false);
  918. if (!ytVideosContent['formats']) {
  919. ytGetVideos(true, 'TVHTML5_SIMPLY_EMBEDDED_PLAYER', true);
  920. }
  921. if (ytVideosContent['formats']) {
  922. var ytVideoFormats = {
  923. '18': 'Low Definition MP4',
  924. '22': 'High Definition MP4',
  925. '43': 'Low Definition WebM',
  926. '133': 'Very Low Definition Video MP4',
  927. '134': 'Low Definition Video MP4',
  928. '135': 'Standard Definition Video MP4',
  929. '136': 'High Definition Video MP4',
  930. '137': 'Full High Definition Video MP4',
  931. '140': 'Medium Bitrate Audio MP4',
  932. '242': 'Very Low Definition Video WebM',
  933. '243': 'Low Definition Video WebM',
  934. '244': 'Standard Definition Video WebM',
  935. '247': 'High Definition Video WebM',
  936. '248': 'Full High Definition Video WebM',
  937. '249': 'Low Bitrate Audio WebM',
  938. '250': 'Medium Bitrate Audio WebM',
  939. '251': 'High Bitrate Audio WebM',
  940. '264': 'Quad High Definition Video MP4',
  941. '271': 'Quad High Definition Video WebM',
  942. '272': 'Ultra High Definition Video WebM',
  943. '298': 'High Definition Video MP4',
  944. '299': 'Full High Definition Video MP4',
  945. '302': 'High Definition Video WebM',
  946. '303': 'Full High Definition Video WebM',
  947. '308': 'Quad High Definition Video WebM',
  948. '313': 'Ultra High Definition Video WebM',
  949. '315': 'Ultra High Definition Video WebM',
  950. '333': 'Standard Definition Video WebM',
  951. '334': 'High Definition Video WebM',
  952. '335': 'Full High Definition Video WebM',
  953. '337': 'Ultra High Definition Video WebM'
  954. };
  955. var ytVideoFound = false;
  956. var ytVideos = (ytVideosContent['adaptiveFormats']) ? ytVideosContent['formats'].concat(ytVideosContent['adaptiveFormats']) : ytVideosContent['formats']
  957. var ytVideoParse, ytVideoCodeParse, ytVideoCode, myVideoCode, ytVideo, ytSParam, ytSParamName, ytNParam;
  958. if (ytVideos[0]['signatureCipher'] || ytVideos[0]['cipher'] || (ytVideos[0]['url'] && parseMyContent(ytVideos[0]['url'], /(?:&|&amp;)n=(.*?)(&|&amp;|$)/))) {
  959. ytGetUnscrambleParamFunc();
  960. }
  961. for (var i = 0; i < ytVideos.length; i++) {
  962. if (ytVideos[i]['signatureCipher'] || ytVideos[i]['cipher']) {
  963. ytVideo = ytVideos[i]['signatureCipher'] || ytVideos[i]['cipher'];
  964. ytVideo = cleanMyContent(ytVideo, true);
  965. ytVideoParse = ytVideo.match(/(.*)(url=.*$)/);
  966. if (ytVideoParse) {
  967. ytVideo = ytVideoParse[2] + '&' + ytVideoParse[1];
  968. ytVideo = ytVideo.replace(/url=/, '').replace(/&$/, '');
  969. }
  970. ytSParam = parseMyContent(ytVideo, /&s=(.*?)(&|$)/);
  971. if (ytSParam && ytUnscrambleParam['s']) {
  972. ytSParam = ytUnscrambleParam['s'](ytSParam);
  973. if (ytSParam) {
  974. ytSParamName = parseMyContent(ytVideo, /&sp=(.*?)(&|$)/);
  975. ytSParamName = (ytSParamName) ? ytSParamName : ((/&lsig=/.test(ytVideo)) ? 'sig' : 'signature');
  976. ytVideo = ytVideo.replace(/&s=.*?(&|$)/, '&' + ytSParamName + '=' + ytSParam + '$1');
  977. }
  978. else ytVideo = '';
  979. }
  980. else ytVideo = '';
  981. }
  982. else {
  983. ytVideo = ytVideos[i]['url'];
  984. ytVideo = cleanMyContent(ytVideo, true);
  985. if (/&sig=/.test(ytVideo) && !/&lsig=/.test(ytVideo)) {
  986. ytVideo = ytVideo.replace(/&sig=/, '&signature=');
  987. }
  988. }
  989. ytVideoCode = ytVideos[i]['itag'];
  990. if (!ytVideoCode) continue;
  991. myVideoCode = ytVideoFormats[ytVideoCode];
  992. if (!myVideoCode) continue;
  993. if (myVideoCode.indexOf('Video') != -1) {
  994. if (ytVideo.indexOf('source=yt_otf') != -1) continue;
  995. }
  996. ytVideo = cleanMyContent(ytVideo, true);
  997. ytNParam = parseMyContent(ytVideo, /&n=(.*?)(&|$)/);
  998. if (ytNParam && ytUnscrambleParam['n']) {
  999. ytNParam = ytUnscrambleParam['n'](ytNParam);
  1000. if (ytNParam) {
  1001. ytVideo = ytVideo.replace(/&n=.*?(&|$)/, '&n=' + ytNParam + '$1');
  1002. }
  1003. }
  1004. if (ytVideo.indexOf('ratebypass') == -1) ytVideo += '&ratebypass=yes';
  1005. if (ytVideo && ytVideo.indexOf('http') == 0) {
  1006. if (!ytVideoFound) ytVideoFound = true;
  1007. ytVideoList[myVideoCode] = ytVideo;
  1008. }
  1009. }
  1010. if (ytVideoFound) {
  1011. /* DASH */
  1012. if (ytVideoList['Medium Bitrate Audio MP4'] || ytVideoList['Medium Bitrate Audio WebM']) {
  1013. for (var myVideoCode in ytVideoList) {
  1014. if (myVideoCode.indexOf('Video') != -1) {
  1015. if (!ytVideoList[myVideoCode.replace(' Video', '')]) {
  1016. ytVideoList[myVideoCode.replace(' Video', '')] = 'DASH';
  1017. }
  1018. }
  1019. }
  1020. }
  1021. /* HLS */
  1022. if (!ytVideosContentHLS) {
  1023. ytGetVideos(true, 'IOS', false);
  1024. }
  1025. if (ytVideosContentHLS) {
  1026. ytVideoList["Multi Definition M3U8"] = ytVideosContentHLS;
  1027. ytDefaultVideo = 'Multi Definition M3U8';
  1028. }
  1029. ytCreateSaver();
  1030. }
  1031. else {
  1032. ytCreateSaver({'warnMess': '!videos'});
  1033. }
  1034. }
  1035. else {
  1036. showMyMessage('!content');
  1037. }
  1038.  
  1039. }
  1040.  
  1041. // =====DailyMotion===== //
  1042.  
  1043. else if (page.url.indexOf('dailymotion.com/video') != -1) {
  1044.  
  1045. /* Video Source */
  1046. var dmMetadataUrl = page.url.replace(/\/video\//, "/player/metadata/video/");
  1047.  
  1048. /* Video Availability */
  1049. if (getMyContent(dmMetadataUrl, /"error":\{"title":"(.*?)"/)) return;
  1050. if (getMyContent(dmMetadataUrl, /"error_title":"(.*?)"/)) return;
  1051.  
  1052. /* Get Video Title */
  1053. var dmVideoTitle = getMyContent(dmMetadataUrl, /"title":"((\\"|[^"])*?)"/);
  1054. if (dmVideoTitle) {
  1055. var dmVideoAuthor = getMyContent(dmMetadataUrl, /"screenname":"((\\"|[^"])*?)"/);
  1056. if (dmVideoAuthor) dmVideoTitle = dmVideoTitle + ' by ' + dmVideoAuthor;
  1057. dmVideoTitle = cleanMyContent(dmVideoTitle, false, true);
  1058. }
  1059.  
  1060. /* Get Videos Content */
  1061. var dmVideosContent = getMyContent(dmMetadataUrl, /"qualities":\{(.*?)\]\},/);
  1062.  
  1063. /* Get Videos */
  1064. if (dmVideosContent) {
  1065. var dmVideoFormats = {'auto': 'Low Definition MP4', '240': 'Very Low Definition MP4', '380': 'Low Definition MP4',
  1066. '480': 'Standard Definition MP4', '720': 'High Definition MP4', '1080': 'Full High Definition MP4'};
  1067. var dmVideoList = {};
  1068. var dmVideoFound = false;
  1069. var myVideoCode, dmVideo;
  1070. for (var dmVideoCode in dmVideoFormats) {
  1071. dmVideo = parseMyContent(dmVideosContent, new RegExp('"' + dmVideoCode + '".*?"type":"video.*?mp4","url":"(.*?)"'));
  1072. if (dmVideo) {
  1073. if (!dmVideoFound) dmVideoFound = true;
  1074. dmVideo = cleanMyContent(dmVideo, true);
  1075. myVideoCode = dmVideoFormats[dmVideoCode];
  1076. if (!dmVideoList[myVideoCode]) dmVideoList[myVideoCode] = dmVideo;
  1077. }
  1078. }
  1079. if (!dmVideoFound) {
  1080. var dmHLSManifest = parseMyContent(dmVideosContent, /"type":"application.*?mpegURL","url":"(.*?)"/);
  1081. if (dmHLSManifest) {
  1082. dmVideoFound = true;
  1083. dmHLSManifest = cleanMyContent(dmHLSManifest, true);
  1084. dmVideoList["Multi Definition M3U8"] = dmHLSManifest;
  1085. for (var dmVideoCode in dmVideoFormats) {
  1086. dmVideo = getMyContent(dmHLSManifest, new RegExp('NAME="' + dmVideoCode + '.*?",PROGRESSIVE-URI="(.*?)(#EXT|$)'));
  1087. if (dmVideo) {
  1088. myVideoCode = dmVideoFormats[dmVideoCode];
  1089. if (!dmVideoList[myVideoCode] && dmVideo.split('"')[0]) {
  1090. dmVideoList[myVideoCode] = dmVideo.split('"')[0];
  1091. }
  1092. myVideoCode = dmVideoFormats[dmVideoCode].replace('MP4', 'M3U8');
  1093. if (!dmVideoList[myVideoCode] && dmVideo.split('"')[1]) {
  1094. dmVideoList[myVideoCode] = dmVideo.split('"')[1];
  1095. }
  1096. }
  1097. }
  1098. }
  1099. }
  1100.  
  1101. if (dmVideoFound) {
  1102. /* Create Saver */
  1103. var dmDefaultVideo = 'Low Definition MP4';
  1104. if (!dmVideoList[dmDefaultVideo]) dmDefaultVideo = 'Low Definition M3U8';
  1105. saver = {
  1106. 'videoList': dmVideoList,
  1107. 'videoDefinitions': ['Full High Definition', 'High Definition', 'Standard Definition', 'Low Definition', 'Very Low Definition'],
  1108. 'videoContainers': ['MP4'],
  1109. 'videoSave': dmDefaultVideo,
  1110. 'videoTitle': dmVideoTitle
  1111. };
  1112. createMySaver();
  1113. }
  1114. else {
  1115. saver = {'warnMess': '!videos'};
  1116. createMySaver();
  1117. }
  1118. }
  1119. else {
  1120. saver = {'warnMess': '!content'};
  1121. createMySaver();
  1122. }
  1123.  
  1124. }
  1125.  
  1126. // =====Vimeo===== //
  1127.  
  1128. else if (page.url.indexOf('vimeo.com/') != -1) {
  1129.  
  1130. /* Page Type */
  1131. var viPageType = getMyContent(page.url, /meta\s+property="og:type"\s+content="(.*?)"/);
  1132. if (!viPageType || viPageType.indexOf('video') == -1) return;
  1133.  
  1134. /* Get Video Title */
  1135. var viVideoTitle;
  1136. if (viPageType.indexOf('video') != -1) {
  1137. viVideoTitle = getMyContent(page.url, /meta\s+property="og:title"\s+content="(.*?)"/);
  1138. }
  1139. else {
  1140. viVideoTitle = getMyContent(page.url, /"title":"((\\"|[^"])*?)"/);
  1141. }
  1142. if (viVideoTitle) {
  1143. viVideoTitle = viVideoTitle.replace(/\s*on\s*Vimeo$/, '');
  1144. var viVideoAuthor = getMyContent(page.url, /"display_name":"((\\"|[^"])*?)"/);
  1145. if (viVideoAuthor) viVideoTitle = viVideoTitle + ' by ' + viVideoAuthor;
  1146. viVideoTitle = cleanMyContent(viVideoTitle, false, true);
  1147. }
  1148.  
  1149. /* Get Content Source */
  1150. var viVideoSource = getMyContent(page.url, /config_url":"(.*?)"/);
  1151. if (viVideoSource) viVideoSource = cleanMyContent(viVideoSource, false);
  1152. else {
  1153. viVideoSource = getMyContent(page.url, /data-config-url="(.*?)"/);
  1154. if (viVideoSource) viVideoSource = viVideoSource.replace(/&amp;/g, '&');
  1155. else viVideoSource = getMyContent(page.url, /embedUrl":"(.*?)"/);
  1156. }
  1157.  
  1158. /* Get Videos Content */
  1159. var viVideosContent;
  1160. if (viVideoSource) {
  1161. viVideosContent = getMyContent(viVideoSource);
  1162. try {
  1163. viVideosContent = JSON.parse(viVideosContent);
  1164. if (viVideosContent['request'] && viVideosContent['request']['files']) {
  1165. viVideosContent = viVideosContent['request']['files'];
  1166. }
  1167. else {
  1168. viVideosContent = '';
  1169. }
  1170. }
  1171. catch(e) {
  1172. viVideosContent = '';
  1173. }
  1174. if (!viVideosContent) {
  1175. viVideosContent = getMyContent(viVideoSource + '||' + JSON.stringify({'withCredentials':true}));
  1176. try {
  1177. viVideosContent = JSON.parse(viVideosContent);
  1178. if (viVideosContent['request'] && viVideosContent['request']['files']) {
  1179. viVideosContent = viVideosContent['request']['files'];
  1180. }
  1181. else {
  1182. viVideosContent = '';
  1183. }
  1184. }
  1185. catch(e) {
  1186. viVideosContent = '';
  1187. }
  1188. }
  1189. if (!viVideosContent) {
  1190. var viAPIHeaders = {'X-Requested-With':'XMLHttpRequest'};
  1191. var viAPIToken = getMyContent('https://vimeo.com/_next/jwt||' + JSON.stringify(viAPIHeaders), /"token":"((\\"|[^"])*?)"/);
  1192. var viAPIUrl = '';
  1193. if (page.url.replace(/\/$/, '').split('/').length > 4) {
  1194. viAPIUrl = 'https://api.vimeo.com/videos/' + page.url.split('/')[3] + ':' + page.url.split('/')[4] + '?fields=config_url';
  1195. }
  1196. else {
  1197. viAPIUrl = 'https://api.vimeo.com/videos/' + page.url.split('/')[3] + '?fields=config_url';
  1198. }
  1199. if (viAPIToken) {
  1200. viAPIHeaders = {'Authorization':'jwt ' + viAPIToken};
  1201. viVideoSource = getMyContent(viAPIUrl + '||' + JSON.stringify(viAPIHeaders), /config_url":\s*"(.*?)"/);
  1202. if (viVideoSource) {
  1203. viVideoSource = cleanMyContent(viVideoSource, false);
  1204. viVideosContent = getMyContent(viVideoSource);
  1205. try {
  1206. viVideosContent = JSON.parse(viVideosContent);
  1207. if (viVideosContent['request'] && viVideosContent['request']['files']) {
  1208. viVideosContent = viVideosContent['request']['files'];
  1209. }
  1210. else {
  1211. viVideosContent = '';
  1212. }
  1213. }
  1214. catch(e) {
  1215. viVideosContent = '';
  1216. }
  1217. if (!viVideosContent) {
  1218. viVideosContent = getMyContent(viVideoSource + '||' + JSON.stringify({'withCredentials':true}));
  1219. try {
  1220. viVideosContent = JSON.parse(viVideosContent);
  1221. if (viVideosContent['request'] && viVideosContent['request']['files']) {
  1222. viVideosContent = viVideosContent['request']['files'];
  1223. }
  1224. else {
  1225. viVideosContent = '';
  1226. }
  1227. }
  1228. catch(e) {
  1229. viVideosContent = '';
  1230. }
  1231. }
  1232. }
  1233. }
  1234. }
  1235. }
  1236.  
  1237. /* Get Videos */
  1238. if (viVideosContent) {
  1239. var viVideoFormats = {'1440p': 'Quad High Definition MP4', '1080p': 'Full High Definition MP4', '720p': 'High Definition MP4', '540p': 'Standard Definition MP4',
  1240. '414': 'Standard Definition MP4', '480p': 'Standard Definition MP4', '360p': 'Low Definition MP4', '270p': 'Very Low Definition MP4', '240p': 'Very Low Definition MP4'};
  1241. var viVideoList = {};
  1242. var viVideoFound = false;
  1243. var viVideo, myVideoCode;
  1244. if (viVideosContent['progressive']) {
  1245. var viVideos = viVideosContent['progressive'];
  1246. for (var i = 0; i < viVideos.length; i++) {
  1247. for (var viVideoCode in viVideoFormats) {
  1248. if (viVideos[i]['quality'] == viVideoCode || viVideos[i]['height'] == viVideoCode) {
  1249. viVideo = viVideos[i]['url'];
  1250. if (viVideo) {
  1251. if (!viVideoFound) viVideoFound = true;
  1252. myVideoCode = viVideoFormats[viVideoCode];
  1253. viVideoList[myVideoCode] = viVideo;
  1254. }
  1255. }
  1256. }
  1257. }
  1258. }
  1259. if (viVideosContent['hls']) {
  1260. if (viVideosContent['hls']['cdns']) {
  1261. if (viVideosContent['hls']['cdns']['akfire_interconnect_quic']) {
  1262. viVideoList["Multi Definition M3U8"] = viVideosContent['hls']['cdns']['akfire_interconnect_quic']['url'];
  1263. if (!viVideoFound) viVideoFound = true;
  1264. }
  1265. else if (viVideosContent['hls']['cdns']['fastly_skyfire']) {
  1266. viVideoList["Multi Definition M3U8"] = viVideosContent['hls']['cdns']['fastly_skyfire']['url'];
  1267. if (!viVideoFound) viVideoFound = true;
  1268. }
  1269. }
  1270. }
  1271.  
  1272. if (viVideoFound) {
  1273. /* Create Saver */
  1274. var viDefaultVideo = 'Low Definition MP4';
  1275. if (!viVideoList[viDefaultVideo]) {
  1276. viDefaultVideo = 'Multi Definition M3U8';
  1277. }
  1278. saver = {
  1279. 'videoList': viVideoList,
  1280. 'videoDefinitions': ['Quad High Definition', 'Full High Definition', 'High Definition', 'Standard Definition', 'Low Definition', 'Very Low Definition'],
  1281. 'videoContainers': ['MP4'],
  1282. 'videoSave': viDefaultVideo,
  1283. 'videoTitle': viVideoTitle
  1284. };
  1285. createMySaver();
  1286. }
  1287. else {
  1288. saver = {'warnMess': '!videos'};
  1289. createMySaver();
  1290. }
  1291. }
  1292. else {
  1293. saver = {'warnMess': '!content'};
  1294. createMySaver();
  1295. }
  1296.  
  1297. }
  1298.  
  1299. // =====Veoh===== //
  1300.  
  1301. else if (page.url.indexOf('veoh.com/watch') != -1) {
  1302.  
  1303. /* Video Info */
  1304. var veVideoInfoUrl = page.url.replace(/\/watch\//, '/watch/getVideo/');
  1305.  
  1306. /* Get Video Availability */
  1307. if (getMyElement('', 'div', 'class', 'veoh-video-player-error', 0, false)) return;
  1308.  
  1309. /* Get Video Title */
  1310. var veVideoTitle = getMyContent(veVideoInfoUrl, /"title":"((\\"|[^"])*?)"/);
  1311. if (!veVideoTitle) {
  1312. veVideoTitle = getMyContent(page.url, /meta\s+name="og:title"\s+content="(.*?)"/);
  1313. }
  1314. if (veVideoTitle) veVideoTitle = cleanMyContent(veVideoTitle, false, true);
  1315.  
  1316. /* Get Videos Content */
  1317. var veVideosContent = getMyContent(veVideoInfoUrl, /"src"\s*:\s*\{(.*?)\}/);
  1318.  
  1319. /* Get Videos */
  1320. if (veVideosContent) {
  1321. var veVideoFormats = {'Regular': 'Low Definition MP4', 'HQ': 'Standard Definition MP4'};
  1322. var veVideoList = {};
  1323. var veVideoFound = false;
  1324. var veVideo, myVideoCode;
  1325. for (var veVideoCode in veVideoFormats) {
  1326. veVideo = parseMyContent(veVideosContent, new RegExp(veVideoCode + '":"(.*?)"'));
  1327. if (veVideo) {
  1328. if (!veVideoFound) veVideoFound = true;
  1329. myVideoCode = veVideoFormats[veVideoCode];
  1330. veVideoList[myVideoCode] = cleanMyContent(veVideo, false);
  1331. }
  1332. }
  1333.  
  1334. if (veVideoFound) {
  1335. /* Create Saver */
  1336. var veDefaultVideo = 'Low Definition MP4';
  1337. saver = {
  1338. 'videoList': veVideoList,
  1339. 'videoDefinitions': ['Standard Definition', 'Low Definition'],
  1340. 'videoContainers': ['MP4'],
  1341. 'videoSave': veDefaultVideo,
  1342. 'videoTitle': veVideoTitle
  1343. };
  1344. createMySaver();
  1345. }
  1346. else {
  1347. saver = {};
  1348. var ytVideoId = getMyContent(page.url, /youtube.com\/embed\/(.*?)("|\?)/);
  1349. if (!ytVideoId) ytVideoId = getMyContent(page.url, /"videoId":"yapi-(.*?)"/);
  1350. if (ytVideoId) {
  1351. var ytVideoLink = 'http://youtube.com/watch?v=' + ytVideoId;
  1352. saver['warnMess'] = 'embed';
  1353. saver['warnContent'] = ytVideoLink;
  1354. }
  1355. else saver['warnMess'] = '!videos';
  1356. createMySaver();
  1357. }
  1358. }
  1359. else {
  1360. saver = {'warnMess': '!content'};
  1361. createMySaver();
  1362. }
  1363.  
  1364. }
  1365.  
  1366. // =====IMDB===== //
  1367.  
  1368. else if (page.url.indexOf('imdb.com') != -1) {
  1369.  
  1370. /* Redirect To Video Page */
  1371. if (page.url.indexOf('/video/') == -1 && page.url.indexOf('/videoplayer/') == -1) {
  1372. page.doc.addEventListener('click', function(e) {
  1373. var p = e.target.parentNode;
  1374. while (p) {
  1375. if (p.tagName === 'A' && p.href.indexOf('/video/imdb') != -1) {
  1376. page.win.location.href = p.href.replace(/imdb\/inline.*/, '');
  1377. }
  1378. p = p.parentNode;
  1379. }
  1380. }, false);
  1381. return;
  1382. }
  1383.  
  1384. /* Get Video Title */
  1385. var imdbVideoTitle = getMyContent(page.url, /meta\s+property="og:title"\s+content="(.*?)"/);
  1386. if (imdbVideoTitle) imdbVideoTitle = cleanMyContent(imdbVideoTitle, false, true);
  1387.  
  1388. /* Get Videos Content */
  1389. var imdbVideosContent = getMyContent(page.url, /"playbackURLs":(\[.*?\])/);
  1390. try {
  1391. imdbVideosContent = JSON.parse(imdbVideosContent);
  1392. }
  1393. catch(e) {
  1394. imdbVideosContent = {};
  1395. }
  1396.  
  1397. /* Get Videos */
  1398. var imdbVideoList = {};
  1399. if (imdbVideosContent) {
  1400. var imdbVideoFormats = {'1080p': 'Full High Definition MP4', '720p': 'High Definition MP4', '480p': 'Standard Definition MP4',
  1401. '360p': 'Low Definition MP4', 'SD': 'Low Definition MP4', '240p': 'Very Low Definition MP4', 'AUTO': 'Multi Definition M3U8'};
  1402. var imdbVideoFound = false;
  1403. var myVideoCode, imdbVideo;
  1404. for (var imdbVideoCode in imdbVideoFormats) {
  1405. for (var i = 0; i < imdbVideosContent.length; i++) {
  1406. imdbVideo = parseMyContent(JSON.stringify(imdbVideosContent[i]), new RegExp('"url":"(.*?)".*?"value":"' + imdbVideoCode + '"'));
  1407. if (imdbVideo) {
  1408. imdbVideo = cleanMyContent(imdbVideo, false);
  1409. if (!imdbVideoFound) imdbVideoFound = true;
  1410. myVideoCode = imdbVideoFormats[imdbVideoCode];
  1411. if (!imdbVideoList[myVideoCode]) imdbVideoList[myVideoCode] = imdbVideo;
  1412. }
  1413. }
  1414. }
  1415.  
  1416. if (imdbVideoFound) {
  1417. /* Create Saver */
  1418. var imdbDefaultVideo = 'Low Definition MP4';
  1419. saver = {
  1420. 'videoList': imdbVideoList,
  1421. 'videoDefinitions': ['Full High Definition', 'High Definition', 'Standard Definition', 'Low Definition', 'Very Low Definition'],
  1422. 'videoContainers': ['MP4', 'M3U8', 'Any'],
  1423. 'videoSave': imdbDefaultVideo,
  1424. 'videoTitle': imdbVideoTitle
  1425. };
  1426. createMySaver();
  1427. }
  1428. else {
  1429. saver = {'warnMess': '!videos'};
  1430. createMySaver();
  1431. }
  1432. }
  1433. else {
  1434. saver = {'warnMess': '!content'};
  1435. createMySaver();
  1436. }
  1437.  
  1438. }
  1439.  
  1440. }
  1441.  
  1442.  
  1443. // ==========Run========== //
  1444.  
  1445. getMyOptions();
  1446. SaveTube();
  1447.  
  1448. page.win.setInterval(function() {
  1449. if (page.url != page.win.location.href.replace(page.win.location.hash, '')) {
  1450. if (saver['saverPanel'] && saver['saverPanel'].parentNode) {
  1451. removeMyElement(saver['saverPanel'].parentNode, saver['saverPanel']);
  1452. }
  1453. page.doc = page.win.document;
  1454. page.body = page.doc.body;
  1455. page.url = page.win.location.href.replace(page.win.location.hash, '');
  1456. SaveTube();
  1457. }
  1458. }, 500);
  1459.  
  1460. })();