Ben 11 mesiacov pred
rodič
commit
d921672dbc
10 zmenil súbory, kde vykonal 2624 pridanie a 1542 odobranie
  1. 1154 0
      js/example.json
  2. 0 250
      js/format-utils.js
  3. 0 524
      js/formats.js
  4. 8 0
      js/index.html
  5. 119 482
      js/info.js
  6. 36 0
      js/nginx.conf
  7. 2 0
      js/run_nginx.sh
  8. 9 0
      js/test.js
  9. 0 286
      js/utils.js
  10. 1296 0
      js/ytb.html

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1154 - 0
js/example.json


+ 0 - 250
js/format-utils.js

@@ -1,250 +0,0 @@
-const utils = require('./utils');
-const FORMATS = require('./formats');
-
-
-// Use these to help sort formats, higher index is better.
-const audioEncodingRanks = [
-  'mp4a',
-  'mp3',
-  'vorbis',
-  'aac',
-  'opus',
-  'flac',
-];
-const videoEncodingRanks = [
-  'mp4v',
-  'avc1',
-  'Sorenson H.283',
-  'MPEG-4 Visual',
-  'VP8',
-  'VP9',
-  'H.264',
-];
-
-const getVideoBitrate = format => format.bitrate || 0;
-const getVideoEncodingRank = format =>
-  videoEncodingRanks.findIndex(enc => format.codecs && format.codecs.includes(enc));
-const getAudioBitrate = format => format.audioBitrate || 0;
-const getAudioEncodingRank = format =>
-  audioEncodingRanks.findIndex(enc => format.codecs && format.codecs.includes(enc));
-
-
-/**
- * Sort formats by a list of functions.
- *
- * @param {Object} a
- * @param {Object} b
- * @param {Array.<Function>} sortBy
- * @returns {number}
- */
-const sortFormatsBy = (a, b, sortBy) => {
-  let res = 0;
-  for (let fn of sortBy) {
-    res = fn(b) - fn(a);
-    if (res !== 0) {
-      break;
-    }
-  }
-  return res;
-};
-
-
-const sortFormatsByVideo = (a, b) => sortFormatsBy(a, b, [
-  format => parseInt(format.qualityLabel),
-  getVideoBitrate,
-  getVideoEncodingRank,
-]);
-
-
-const sortFormatsByAudio = (a, b) => sortFormatsBy(a, b, [
-  getAudioBitrate,
-  getAudioEncodingRank,
-]);
-
-
-/**
- * Sort formats from highest quality to lowest.
- *
- * @param {Object} a
- * @param {Object} b
- * @returns {number}
- */
-exports.sortFormats = (a, b) => sortFormatsBy(a, b, [
-  // Formats with both video and audio are ranked highest.
-  format => +!!format.isHLS,
-  format => +!!format.isDashMPD,
-  format => +(format.contentLength > 0),
-  format => +(format.hasVideo && format.hasAudio),
-  format => +format.hasVideo,
-  format => parseInt(format.qualityLabel) || 0,
-  getVideoBitrate,
-  getAudioBitrate,
-  getVideoEncodingRank,
-  getAudioEncodingRank,
-]);
-
-
-/**
- * Choose a format depending on the given options.
- *
- * @param {Array.<Object>} formats
- * @param {Object} options
- * @returns {Object}
- * @throws {Error} when no format matches the filter/format rules
- */
-exports.chooseFormat = (formats, options) => {
-  if (typeof options.format === 'object') {
-    if (!options.format.url) {
-      throw Error('Invalid format given, did you use `ytdl.getInfo()`?');
-    }
-    return options.format;
-  }
-
-  if (options.filter) {
-    formats = exports.filterFormats(formats, options.filter);
-  }
-
-  // We currently only support HLS-Formats for livestreams
-  // So we (now) remove all non-HLS streams
-  if (formats.some(fmt => fmt.isHLS)) {
-    formats = formats.filter(fmt => fmt.isHLS || !fmt.isLive);
-  }
-
-  let format;
-  const quality = options.quality || 'highest';
-  switch (quality) {
-    case 'highest':
-      format = formats[0];
-      break;
-
-    case 'lowest':
-      format = formats[formats.length - 1];
-      break;
-
-    case 'highestaudio': {
-      formats = exports.filterFormats(formats, 'audio');
-      formats.sort(sortFormatsByAudio);
-      // Filter for only the best audio format
-      const bestAudioFormat = formats[0];
-      formats = formats.filter(f => sortFormatsByAudio(bestAudioFormat, f) === 0);
-      // Check for the worst video quality for the best audio quality and pick according
-      // This does not loose default sorting of video encoding and bitrate
-      const worstVideoQuality = formats.map(f => parseInt(f.qualityLabel) || 0).sort((a, b) => a - b)[0];
-      format = formats.find(f => (parseInt(f.qualityLabel) || 0) === worstVideoQuality);
-      break;
-    }
-
-    case 'lowestaudio':
-      formats = exports.filterFormats(formats, 'audio');
-      formats.sort(sortFormatsByAudio);
-      format = formats[formats.length - 1];
-      break;
-
-    case 'highestvideo': {
-      formats = exports.filterFormats(formats, 'video');
-      formats.sort(sortFormatsByVideo);
-      // Filter for only the best video format
-      const bestVideoFormat = formats[0];
-      formats = formats.filter(f => sortFormatsByVideo(bestVideoFormat, f) === 0);
-      // Check for the worst audio quality for the best video quality and pick according
-      // This does not loose default sorting of audio encoding and bitrate
-      const worstAudioQuality = formats.map(f => f.audioBitrate || 0).sort((a, b) => a - b)[0];
-      format = formats.find(f => (f.audioBitrate || 0) === worstAudioQuality);
-      break;
-    }
-
-    case 'lowestvideo':
-      formats = exports.filterFormats(formats, 'video');
-      formats.sort(sortFormatsByVideo);
-      format = formats[formats.length - 1];
-      break;
-
-    default:
-      format = getFormatByQuality(quality, formats);
-      break;
-  }
-
-  if (!format) {
-    throw Error(`No such format found: ${quality}`);
-  }
-  return format;
-};
-
-/**
- * Gets a format based on quality or array of quality's
- *
- * @param {string|[string]} quality
- * @param {[Object]} formats
- * @returns {Object}
- */
-const getFormatByQuality = (quality, formats) => {
-  let getFormat = itag => formats.find(format => `${format.itag}` === `${itag}`);
-  if (Array.isArray(quality)) {
-    return getFormat(quality.find(q => getFormat(q)));
-  } else {
-    return getFormat(quality);
-  }
-};
-
-
-/**
- * @param {Array.<Object>} formats
- * @param {Function} filter
- * @returns {Array.<Object>}
- */
-exports.filterFormats = (formats, filter) => {
-  let fn;
-  switch (filter) {
-    case 'videoandaudio':
-    case 'audioandvideo':
-      fn = format => format.hasVideo && format.hasAudio;
-      break;
-
-    case 'video':
-      fn = format => format.hasVideo;
-      break;
-
-    case 'videoonly':
-      fn = format => format.hasVideo && !format.hasAudio;
-      break;
-
-    case 'audio':
-      fn = format => format.hasAudio;
-      break;
-
-    case 'audioonly':
-      fn = format => !format.hasVideo && format.hasAudio;
-      break;
-
-    default:
-      if (typeof filter === 'function') {
-        fn = filter;
-      } else {
-        throw TypeError(`Given filter (${filter}) is not supported`);
-      }
-  }
-  return formats.filter(format => !!format.url && fn(format));
-};
-
-
-/**
- * @param {Object} format
- * @returns {Object}
- */
-exports.addFormatMeta = format => {
-  format = Object.assign({}, FORMATS[format.itag], format);
-  format.hasVideo = !!format.qualityLabel;
-  format.hasAudio = !!format.audioBitrate;
-  format.container = format.mimeType ?
-    format.mimeType.split(';')[0].split('/')[1] : null;
-  format.codecs = format.mimeType ?
-    utils.between(format.mimeType, 'codecs="', '"') : null;
-  format.videoCodec = format.hasVideo && format.codecs ?
-    format.codecs.split(', ')[0] : null;
-  format.audioCodec = format.hasAudio && format.codecs ?
-    format.codecs.split(', ').slice(-1)[0] : null;
-  format.isLive = /\bsource[/=]yt_live_broadcast\b/.test(format.url);
-  format.isHLS = /\/manifest\/hls_(variant|playlist)\//.test(format.url);
-  format.isDashMPD = /\/manifest\/dash\//.test(format.url);
-  return format;
-};

+ 0 - 524
js/formats.js

@@ -1,524 +0,0 @@
-/**
- * http://en.wikipedia.org/wiki/YouTube#Quality_and_formats
- */
-module.exports = {
-
-  5: {
-    mimeType: 'video/flv; codecs="Sorenson H.283, mp3"',
-    qualityLabel: '240p',
-    bitrate: 250000,
-    audioBitrate: 64,
-  },
-
-  6: {
-    mimeType: 'video/flv; codecs="Sorenson H.263, mp3"',
-    qualityLabel: '270p',
-    bitrate: 800000,
-    audioBitrate: 64,
-  },
-
-  13: {
-    mimeType: 'video/3gp; codecs="MPEG-4 Visual, aac"',
-    qualityLabel: null,
-    bitrate: 500000,
-    audioBitrate: null,
-  },
-
-  17: {
-    mimeType: 'video/3gp; codecs="MPEG-4 Visual, aac"',
-    qualityLabel: '144p',
-    bitrate: 50000,
-    audioBitrate: 24,
-  },
-
-  18: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '360p',
-    bitrate: 500000,
-    audioBitrate: 96,
-  },
-
-  22: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '720p',
-    bitrate: 2000000,
-    audioBitrate: 192,
-  },
-
-  34: {
-    mimeType: 'video/flv; codecs="H.264, aac"',
-    qualityLabel: '360p',
-    bitrate: 500000,
-    audioBitrate: 128,
-  },
-
-  35: {
-    mimeType: 'video/flv; codecs="H.264, aac"',
-    qualityLabel: '480p',
-    bitrate: 800000,
-    audioBitrate: 128,
-  },
-
-  36: {
-    mimeType: 'video/3gp; codecs="MPEG-4 Visual, aac"',
-    qualityLabel: '240p',
-    bitrate: 175000,
-    audioBitrate: 32,
-  },
-
-  37: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '1080p',
-    bitrate: 3000000,
-    audioBitrate: 192,
-  },
-
-  38: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '3072p',
-    bitrate: 3500000,
-    audioBitrate: 192,
-  },
-
-  43: {
-    mimeType: 'video/webm; codecs="VP8, vorbis"',
-    qualityLabel: '360p',
-    bitrate: 500000,
-    audioBitrate: 128,
-  },
-
-  44: {
-    mimeType: 'video/webm; codecs="VP8, vorbis"',
-    qualityLabel: '480p',
-    bitrate: 1000000,
-    audioBitrate: 128,
-  },
-
-  45: {
-    mimeType: 'video/webm; codecs="VP8, vorbis"',
-    qualityLabel: '720p',
-    bitrate: 2000000,
-    audioBitrate: 192,
-  },
-
-  46: {
-    mimeType: 'audio/webm; codecs="vp8, vorbis"',
-    qualityLabel: '1080p',
-    bitrate: null,
-    audioBitrate: 192,
-  },
-
-  82: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '360p',
-    bitrate: 500000,
-    audioBitrate: 96,
-  },
-
-  83: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '240p',
-    bitrate: 500000,
-    audioBitrate: 96,
-  },
-
-  84: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '720p',
-    bitrate: 2000000,
-    audioBitrate: 192,
-  },
-
-  85: {
-    mimeType: 'video/mp4; codecs="H.264, aac"',
-    qualityLabel: '1080p',
-    bitrate: 3000000,
-    audioBitrate: 192,
-  },
-
-  91: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '144p',
-    bitrate: 100000,
-    audioBitrate: 48,
-  },
-
-  92: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '240p',
-    bitrate: 150000,
-    audioBitrate: 48,
-  },
-
-  93: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '360p',
-    bitrate: 500000,
-    audioBitrate: 128,
-  },
-
-  94: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '480p',
-    bitrate: 800000,
-    audioBitrate: 128,
-  },
-
-  95: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '720p',
-    bitrate: 1500000,
-    audioBitrate: 256,
-  },
-
-  96: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '1080p',
-    bitrate: 2500000,
-    audioBitrate: 256,
-  },
-
-  100: {
-    mimeType: 'audio/webm; codecs="VP8, vorbis"',
-    qualityLabel: '360p',
-    bitrate: null,
-    audioBitrate: 128,
-  },
-
-  101: {
-    mimeType: 'audio/webm; codecs="VP8, vorbis"',
-    qualityLabel: '360p',
-    bitrate: null,
-    audioBitrate: 192,
-  },
-
-  102: {
-    mimeType: 'audio/webm; codecs="VP8, vorbis"',
-    qualityLabel: '720p',
-    bitrate: null,
-    audioBitrate: 192,
-  },
-
-  120: {
-    mimeType: 'video/flv; codecs="H.264, aac"',
-    qualityLabel: '720p',
-    bitrate: 2000000,
-    audioBitrate: 128,
-  },
-
-  127: {
-    mimeType: 'audio/ts; codecs="aac"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 96,
-  },
-
-  128: {
-    mimeType: 'audio/ts; codecs="aac"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 96,
-  },
-
-  132: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '240p',
-    bitrate: 150000,
-    audioBitrate: 48,
-  },
-
-  133: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '240p',
-    bitrate: 200000,
-    audioBitrate: null,
-  },
-
-  134: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '360p',
-    bitrate: 300000,
-    audioBitrate: null,
-  },
-
-  135: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '480p',
-    bitrate: 500000,
-    audioBitrate: null,
-  },
-
-  136: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '720p',
-    bitrate: 1000000,
-    audioBitrate: null,
-  },
-
-  137: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '1080p',
-    bitrate: 2500000,
-    audioBitrate: null,
-  },
-
-  138: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '4320p',
-    bitrate: 13500000,
-    audioBitrate: null,
-  },
-
-  139: {
-    mimeType: 'audio/mp4; codecs="aac"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 48,
-  },
-
-  140: {
-    mimeType: 'audio/m4a; codecs="aac"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 128,
-  },
-
-  141: {
-    mimeType: 'audio/mp4; codecs="aac"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 256,
-  },
-
-  151: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '720p',
-    bitrate: 50000,
-    audioBitrate: 24,
-  },
-
-  160: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '144p',
-    bitrate: 100000,
-    audioBitrate: null,
-  },
-
-  171: {
-    mimeType: 'audio/webm; codecs="vorbis"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 128,
-  },
-
-  172: {
-    mimeType: 'audio/webm; codecs="vorbis"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 192,
-  },
-
-  242: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '240p',
-    bitrate: 100000,
-    audioBitrate: null,
-  },
-
-  243: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '360p',
-    bitrate: 250000,
-    audioBitrate: null,
-  },
-
-  244: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '480p',
-    bitrate: 500000,
-    audioBitrate: null,
-  },
-
-  247: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '720p',
-    bitrate: 700000,
-    audioBitrate: null,
-  },
-
-  248: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '1080p',
-    bitrate: 1500000,
-    audioBitrate: null,
-  },
-
-  249: {
-    mimeType: 'audio/webm; codecs="opus"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 48,
-  },
-
-  250: {
-    mimeType: 'audio/webm; codecs="opus"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 64,
-  },
-
-  251: {
-    mimeType: 'audio/webm; codecs="opus"',
-    qualityLabel: null,
-    bitrate: null,
-    audioBitrate: 160,
-  },
-
-  264: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '1440p',
-    bitrate: 4000000,
-    audioBitrate: null,
-  },
-
-  266: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '2160p',
-    bitrate: 12500000,
-    audioBitrate: null,
-  },
-
-  271: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '1440p',
-    bitrate: 9000000,
-    audioBitrate: null,
-  },
-
-  272: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '4320p',
-    bitrate: 20000000,
-    audioBitrate: null,
-  },
-
-  278: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '144p 30fps',
-    bitrate: 80000,
-    audioBitrate: null,
-  },
-
-  298: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '720p',
-    bitrate: 3000000,
-    audioBitrate: null,
-  },
-
-  299: {
-    mimeType: 'video/mp4; codecs="H.264"',
-    qualityLabel: '1080p',
-    bitrate: 5500000,
-    audioBitrate: null,
-  },
-
-  300: {
-    mimeType: 'video/ts; codecs="H.264, aac"',
-    qualityLabel: '720p',
-    bitrate: 1318000,
-    audioBitrate: 48,
-  },
-
-  302: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '720p HFR',
-    bitrate: 2500000,
-    audioBitrate: null,
-  },
-
-  303: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '1080p HFR',
-    bitrate: 5000000,
-    audioBitrate: null,
-  },
-
-  308: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '1440p HFR',
-    bitrate: 10000000,
-    audioBitrate: null,
-  },
-
-  313: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '2160p',
-    bitrate: 13000000,
-    audioBitrate: null,
-  },
-
-  315: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '2160p HFR',
-    bitrate: 20000000,
-    audioBitrate: null,
-  },
-
-  330: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '144p HDR, HFR',
-    bitrate: 80000,
-    audioBitrate: null,
-  },
-
-  331: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '240p HDR, HFR',
-    bitrate: 100000,
-    audioBitrate: null,
-  },
-
-  332: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '360p HDR, HFR',
-    bitrate: 250000,
-    audioBitrate: null,
-  },
-
-  333: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '240p HDR, HFR',
-    bitrate: 500000,
-    audioBitrate: null,
-  },
-
-  334: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '720p HDR, HFR',
-    bitrate: 1000000,
-    audioBitrate: null,
-  },
-
-  335: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '1080p HDR, HFR',
-    bitrate: 1500000,
-    audioBitrate: null,
-  },
-
-  336: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '1440p HDR, HFR',
-    bitrate: 5000000,
-    audioBitrate: null,
-  },
-
-  337: {
-    mimeType: 'video/webm; codecs="VP9"',
-    qualityLabel: '2160p HDR, HFR',
-    bitrate: 12000000,
-    audioBitrate: null,
-  },
-
-};

+ 8 - 0
js/index.html

@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>JavaScript in Browser</title>
+    <script src="info.js"></script>
+    <script src="test.js"></script>
+</head>
+</html>

+ 119 - 482
js/info.js

@@ -1,497 +1,134 @@
-const querystring = require('querystring');
-const sax = require('sax');
-const miniget = require('miniget');
-const utils = require('./utils');
-// Forces Node JS version of setTimeout for Electron based applications
-const {setTimeout} = require('timers');
-const formatUtils = require('./format-utils');
-const urlUtils = require('./url-utils');
-const extras = require('./info-extras');
-const sig = require('./sig');
-
-const BASE_URL = 'https://www.youtube.com/watch?v=';
-
-
-// Cache for cver used in getVideoInfoPage
-let cver = '2.20210622.10.00';
-
-
-// Special error class used to determine if an error is unrecoverable,
-// as in, ytdl-core should not try again to fetch the video metadata.
-// In this case, the video is usually unavailable in some way.
-class UnrecoverableError extends Error {
-}
-
-
-// List of URLs that show up in `notice_url` for age restricted videos.
-const AGE_RESTRICTED_URLS = [
-    'support.google.com/youtube/?p=age_restrictions',
-    'youtube.com/t/community_guidelines',
-];
-
-
-/**
- * Gets info from a video without getting additional formats.
- *
- * @param {string} id
- * @param {Object} options
- * @returns {Promise<Object>}
- */
-exports.getBasicInfo = async (id, options) => {
-    if (options.IPv6Block) {
-        options.requestOptions = Object.assign({}, options.requestOptions, {
-            family: 6,
-            localAddress: utils.getRandomIPv6(options.IPv6Block),
+const request = async (method, url, data = null, headers = {}) => {
+    return new Promise(function (resolve, reject) {
+        const xhr = new XMLHttpRequest();
+        xhr.open(method, url);
+
+        // 设置请求头
+        Object.keys(headers).forEach(function (key) {
+            xhr.setRequestHeader(key, headers[key]);
         });
         });
-    }
-    const retryOptions = Object.assign({}, miniget.defaultOptions, options.requestOptions);
-    options.requestOptions = Object.assign({}, options.requestOptions, {});
-    options.requestOptions.headers = Object.assign({},
-        {
-            // eslint-disable-next-line max-len
-            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Safari/537.36',
-        }, options.requestOptions.headers);
-    const validate = info => {
-        let playErr = utils.playError(info.player_response, ['ERROR'], UnrecoverableError);
-        let privateErr = privateVideoError(info.player_response);
-        if (playErr || privateErr) {
-            throw playErr || privateErr;
-        }
-        return info && info.player_response && (
-            info.player_response.streamingData || isRental(info.player_response) || isNotYetBroadcasted(info.player_response)
-        );
-    };
-    let info = await pipeline([id, options], validate, retryOptions, [
-        getWatchHTMLPage,
-        getWatchJSONPage,
-        getVideoInfoPage,
-    ]);
 
 
-    Object.assign(info, {
-        formats: parseFormats(info.player_response),
-        related_videos: extras.getRelatedVideos(info),
-    });
-
-    // Add additional properties to info.
-    const media = extras.getMedia(info);
-    const additional = {
-        author: extras.getAuthor(info),
-        media,
-        likes: extras.getLikes(info),
-        dislikes: extras.getDislikes(info),
-        age_restricted: !!(media && AGE_RESTRICTED_URLS.some(url =>
-                Object.values(media).some(v => typeof v === 'string' && v.includes(url)))
-        ),
-
-        // Give the standard link to the video.
-        video_url: BASE_URL + id,
-        storyboards: extras.getStoryboards(info),
-        chapters: extras.getChapters(info),
-    };
+        xhr.onload = function () {
+            if (xhr.status >= 200 && xhr.status < 300) {
+                resolve(xhr.responseText);
+            } else {
+                reject(new Error('Request failed with status: ' + xhr.status));
+            }
+        };
 
 
-    info.videoDetails = extras.cleanVideoDetails(Object.assign({},
-        info.player_response && info.player_response.microformat &&
-        info.player_response.microformat.playerMicroformatRenderer,
-        info.player_response && info.player_response.videoDetails, additional), info);
+        xhr.onerror = function () {
+            reject(new Error('Request failed'));
+        };
 
 
-    return info;
-};
+        xhr.send(data);
+    });
+}
 
 
-const privateVideoError = player_response => {
-    let playability = player_response && player_response.playabilityStatus;
-    if (playability && playability.status === 'LOGIN_REQUIRED' && playability.messages &&
-        playability.messages.filter(m => /This is a private video/.test(m)).length) {
-        return new UnrecoverableError(playability.reason || (playability.messages && playability.messages[0]));
-    } else {
-        return null;
+getVideoDetail = async (url) => {
+    const headers = {
+        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Safari/537.36',
     }
     }
-};
-
-
-const isRental = player_response => {
-    let playability = player_response.playabilityStatus;
-    return playability && playability.status === 'UNPLAYABLE' &&
-        playability.errorScreen && playability.errorScreen.playerLegacyDesktopYpcOfferRenderer;
-};
-
-
-const isNotYetBroadcasted = player_response => {
-    let playability = player_response.playabilityStatus;
-    return playability && playability.status === 'LIVE_STREAM_OFFLINE';
-};
-
-
-const getWatchHTMLURL = (id, options) => `${BASE_URL + id}&hl=${options.lang || 'en'}`;
-
-const getWatchHTMLPageBody = (id, options) => {
-    const url = getWatchHTMLURL(id, options);
-    return exports.watchPageCache.getOrSet(url, () => utils.exposedMiniget(url, options).text());
-};
-
-
-const EMBED_URL = 'https://www.youtube.com/embed/';
-
-const getEmbedPageBody = (id, options) => {
-    const embedUrl = `${EMBED_URL + id}?hl=${options.lang || 'en'}`;
-    return utils.exposedMiniget(embedUrl, options).text();
-};
-
-const getHTML5player = body => {
-    let html5playerRes =
-        /<script\s+src="([^"]+)"(?:\s+type="text\/javascript")?\s+name="player_ias\/base"\s*>|"jsUrl":"([^"]+)"/
-            .exec(body);
-    return html5playerRes ? html5playerRes[1] || html5playerRes[2] : null;
-};
-
+    return request('GET', url, null, headers)
+        .then(res => {
+            let regex = /var ytInitialPlayerResponse\s*=\s*({.*?});/;
+            let match = res.match(regex);
+            if (!match || !match.length) {
+                throw new Error('JSON not found.');
+            }
+            const ytInitialPlayerResponse = JSON.parse(match[1]);
+            console.log(ytInitialPlayerResponse);
+            const originVideoDetails = ytInitialPlayerResponse["videoDetails"];
+            const thumbnails = []
+            for (const item of originVideoDetails["thumbnail"]["thumbnails"]) {
+                thumbnails.push({
+                    "url": item["url"],
+                    "width": item["width"] + "",
+                    "height": item["height"] + ""
+                })
+            }
 
 
-const getIdentityToken = async (id, options, key, throwIfNotFound) => {
-    let page = await getWatchHTMLPageBody(id, options);
-    let match = page.match(/(["'])ID_TOKEN\1[:,]\s?"([^"]+)"/);
-    if (!match && throwIfNotFound) {
-        throw new UnrecoverableError('Cookie header used in request, but unable to find YouTube identity token');
-    }
-    return match && match[2];
-};
+            const formats = []
+            for (const item of ytInitialPlayerResponse["streamingData"]["formats"].concat(ytInitialPlayerResponse["streamingData"]["adaptiveFormats"])) {
+                if (item && item["signatureCipher"] && item["mimeType"]) {
+                    let urlRegex = /url=([^&]+)/;
+                    let match = item["signatureCipher"].match(urlRegex);
+                    if (!match) {
+                        continue;
+                    }
+                    const encodedUrl = match[1];
+                    const decodedUrl = decodeURIComponent(encodedUrl);
+
+                    formats.push({
+                        "width": item["width"] + "",
+                        "height": item["height"] + "",
+                        "type": item["mimeType"],
+                        "quality": item["quality"],
+                        "itag": item["itag"],
+                        "fps": item["fps"] + "",
+                        "bitrate": item["bitrate"] + "",
+                        "url": decodedUrl,
+                        "ext": "mp4",
+                        "vcodec": item["mimeType"],
+                        "acodec": item["mimeType"],
+                        "vbr": "0",
+                        "abr": "0",
+                        "container": "mp4_dash"
+                    })
+                }
+            }
 
 
-/**
- * Goes through each endpoint in the pipeline, retrying on failure if the error is recoverable.
- * If unable to succeed with one endpoint, moves onto the next one.
- *
- * @param {Array.<Object>} args
- * @param {Function} validate
- * @param {Object} retryOptions
- * @param {Array.<Function>} endpoints
- * @returns {[Object, Object, Object]}
- */
-const pipeline = async (args, validate, retryOptions, endpoints) => {
-    let info;
-    for (let func of endpoints) {
-        try {
-            const newInfo = await retryFunc(func, args.concat([info]), retryOptions);
-            if (newInfo.player_response) {
-                newInfo.player_response.videoDetails = assign(
-                    info && info.player_response && info.player_response.videoDetails,
-                    newInfo.player_response.videoDetails);
-                newInfo.player_response = assign(info && info.player_response, newInfo.player_response);
+            regex = /var ytInitialData\s*=\s*({.*?});/;
+            match = res.match(regex);
+            if (!match || !match.length) {
+                throw new Error('JSON not found.');
             }
             }
-            info = assign(info, newInfo);
-            if (validate(info, false)) {
-                break;
+            if (!match || !match.length) {
+                throw new Error('JSON not found.');
             }
             }
-        } catch (err) {
-            if (err instanceof UnrecoverableError || func === endpoints[endpoints.length - 1]) {
-                throw err;
+            const ytInitialData = JSON.parse(match[1]);
+            console.log(ytInitialData);
+            const recommendInfo = [];
+            for (const item of ytInitialData["contents"]["twoColumnWatchNextResults"]["secondaryResults"]["secondaryResults"]["results"]) {
+                if (item["compactVideoRenderer"]) {
+                    const recommendVideo = item["compactVideoRenderer"];
+                    recommendInfo.push({
+                        "type": "gridVideoRenderer",
+                        "videoId": recommendVideo["videoId"],
+                        "title": recommendVideo["title"]["simpleText"],
+                        "thumbnails": recommendVideo["thumbnail"]["thumbnails"],
+                        "channelName": recommendVideo["longBylineText"]["runs"][0]["text"],
+                        "publishedTimeText": recommendVideo["publishedTimeText"]["simpleText"],
+                        "viewCountText": recommendVideo["viewCountText"]["simpleText"],
+                        "shortViewCountText": recommendVideo["shortViewCountText"]["simpleText"],
+                        "lengthText": recommendVideo["lengthText"]["simpleText"]
+                    })
+                }
             }
             }
-            // Unable to find video metadata... so try next endpoint.
-        }
-    }
-    return info;
-};
-
-
-/**
- * Like Object.assign(), but ignores `null` and `undefined` from `source`.
- *
- * @param {Object} target
- * @param {Object} source
- * @returns {Object}
- */
-const assign = (target, source) => {
-    if (!target || !source) {
-        return target || source;
-    }
-    for (let [key, value] of Object.entries(source)) {
-        if (value !== null && value !== undefined) {
-            target[key] = value;
-        }
-    }
-    return target;
-};
-
 
 
-/**
- * Given a function, calls it with `args` until it's successful,
- * or until it encounters an unrecoverable error.
- * Currently, any error from miniget is considered unrecoverable. Errors such as
- * too many redirects, invalid URL, status code 404, status code 502.
- *
- * @param {Function} func
- * @param {Array.<Object>} args
- * @param {Object} options
- * @param {number} options.maxRetries
- * @param {Object} options.backoff
- * @param {number} options.backoff.inc
- */
-const retryFunc = async (func, args, options) => {
-    let currentTry = 0, result;
-    while (currentTry <= options.maxRetries) {
-        try {
-            result = await func(...args);
-            break;
-        } catch (err) {
-            if (err instanceof UnrecoverableError ||
-                (err instanceof miniget.MinigetError && err.statusCode < 500) || currentTry >= options.maxRetries) {
-                throw err;
+            const videoDetails = {
+                "isLiveContent": originVideoDetails["isLiveContent"],
+                "title": originVideoDetails["title"],
+                "thumbnails": thumbnails,
+                "description": originVideoDetails["shortDescription"],
+                "lengthSeconds": originVideoDetails["lengthSeconds"],
+                "viewCount": originVideoDetails["viewCount"],
+                "keywords": originVideoDetails["keywords"],
+                "author": originVideoDetails["author"],
+                "channelID": originVideoDetails["channelId"],
+                "recommendInfo": recommendInfo,
+                "channelURL": `https://www.youtube.com/channel/${originVideoDetails["channelId"]}`,
+                "videoId": originVideoDetails["videoId"]
             }
             }
-            let wait = Math.min(++currentTry * options.backoff.inc, options.backoff.max);
-            await new Promise(resolve => setTimeout(resolve, wait));
-        }
-    }
-    return result;
-};
-
-
-const jsonClosingChars = /^[)\]}'\s]+/;
-
-const parseJSON = (source, varName, json) => {
-    if (!json || typeof json === 'object') {
-        return json;
-    } else {
-        try {
-            json = json.replace(jsonClosingChars, '');
-            return JSON.parse(json);
-        } catch (err) {
-            throw Error(`Error parsing ${varName} in ${source}: ${err.message}`);
-        }
-    }
-};
-
-
-const findJSON = (source, varName, body, left, right, prependJSON) => {
-    let jsonStr = utils.between(body, left, right);
-    if (!jsonStr) {
-        throw Error(`Could not find ${varName} in ${source}`);
-    }
-    return parseJSON(source, varName, utils.cutAfterJS(`${prependJSON}${jsonStr}`));
-};
-
-
-const findPlayerResponse = (source, info) => {
-    const player_response = info && (
-        (info.args && info.args.player_response) ||
-        info.player_response || info.playerResponse || info.embedded_player_response);
-    return parseJSON(source, 'player_response', player_response);
-};
-
-
-const getWatchJSONURL = (id, options) => `${getWatchHTMLURL(id, options)}&pbj=1`;
-const getWatchJSONPage = async (id, options) => {
-    const reqOptions = Object.assign({headers: {}}, options.requestOptions);
-    let cookie = reqOptions.headers.Cookie || reqOptions.headers.cookie;
-    reqOptions.headers = Object.assign({
-        'x-youtube-client-name': '1',
-        'x-youtube-client-version': cver,
-        'x-youtube-identity-token': exports.cookieCache.get(cookie || 'browser') || '',
-    }, reqOptions.headers);
-
-    const setIdentityToken = async (key, throwIfNotFound) => {
-        if (reqOptions.headers['x-youtube-identity-token']) {
-            return;
-        }
-        reqOptions.headers['x-youtube-identity-token'] = await getIdentityToken(id, options, key, throwIfNotFound);
-    };
-
-    if (cookie) {
-        await setIdentityToken(cookie, true);
-    }
-
-    const jsonUrl = getWatchJSONURL(id, options);
-    const body = await utils.exposedMiniget(jsonUrl, options, reqOptions).text();
-    let parsedBody = parseJSON('watch.json', 'body', body);
-    if (parsedBody.reload === 'now') {
-        await setIdentityToken('browser', false);
-    }
-    if (parsedBody.reload === 'now' || !Array.isArray(parsedBody)) {
-        throw Error('Unable to retrieve video metadata in watch.json');
-    }
-    let info = parsedBody.reduce((part, curr) => Object.assign(curr, part), {});
-    info.player_response = findPlayerResponse('watch.json', info);
-    info.html5player = info.player && info.player.assets && info.player.assets.js;
-
-    return info;
-};
-
-
-const getWatchHTMLPage = async (id, options) => {
-    let body = await getWatchHTMLPageBody(id, options);
-    let info = {page: 'watch'};
-    try {
-        cver = utils.between(body, '{"key":"cver","value":"', '"}');
-        info.player_response = findJSON('watch.html', 'player_response',
-            body, /\bytInitialPlayerResponse\s*=\s*\{/i, '</script>', '{');
-    } catch (err) {
-        let args = findJSON('watch.html', 'player_response', body, /\bytplayer\.config\s*=\s*{/, '</script>', '{');
-        info.player_response = findPlayerResponse('watch.html', args);
-    }
-    info.response = findJSON('watch.html', 'response', body, /\bytInitialData("\])?\s*=\s*\{/i, '</script>', '{');
-    info.html5player = getHTML5player(body);
-    return info;
-};
-
-
-const INFO_HOST = 'www.youtube.com';
-const INFO_PATH = '/get_video_info';
-const VIDEO_EURL = 'https://youtube.googleapis.com/v/';
-const getVideoInfoPage = async (id, options) => {
-    const url = new URL(`https://${INFO_HOST}${INFO_PATH}`);
-    url.searchParams.set('video_id', id);
-    url.searchParams.set('c', 'TVHTML5');
-    url.searchParams.set('cver', `7${cver.substr(1)}`);
-    url.searchParams.set('eurl', VIDEO_EURL + id);
-    url.searchParams.set('ps', 'default');
-    url.searchParams.set('gl', 'US');
-    url.searchParams.set('hl', options.lang || 'en');
-    url.searchParams.set('html5', '1');
-    const body = await utils.exposedMiniget(url.toString(), options).text();
-    let info = querystring.parse(body);
-    info.player_response = findPlayerResponse('get_video_info', info);
-    return info;
-};
-
-
-/**
- * @param {Object} player_response
- * @returns {Array.<Object>}
- */
-const parseFormats = player_response => {
-    let formats = [];
-    if (player_response && player_response.streamingData) {
-        formats = formats
-            .concat(player_response.streamingData.formats || [])
-            .concat(player_response.streamingData.adaptiveFormats || []);
-    }
-    return formats;
-};
-
-
-/**
- * Gets info from a video additional formats and deciphered URLs.
- *
- * @param {string} id
- * @param {Object} options
- * @returns {Promise<Object>}
- */
-exports.getInfo = async (id, options) => {
-    let info = await exports.getBasicInfo(id, options);
-    const hasManifest =
-        info.player_response && info.player_response.streamingData && (
-            info.player_response.streamingData.dashManifestUrl ||
-            info.player_response.streamingData.hlsManifestUrl
-        );
-    let funcs = [];
-    if (info.formats.length) {
-        info.html5player = info.html5player ||
-            getHTML5player(await getWatchHTMLPageBody(id, options)) || getHTML5player(await getEmbedPageBody(id, options));
-        if (!info.html5player) {
-            throw Error('Unable to find html5player file');
-        }
-        const html5player = new URL(info.html5player, BASE_URL).toString();
-        funcs.push(sig.decipherFormats(info.formats, html5player, options));
-    }
-    if (hasManifest && info.player_response.streamingData.dashManifestUrl) {
-        let url = info.player_response.streamingData.dashManifestUrl;
-        funcs.push(getDashManifest(url, options));
-    }
-    if (hasManifest && info.player_response.streamingData.hlsManifestUrl) {
-        let url = info.player_response.streamingData.hlsManifestUrl;
-        funcs.push(getM3U8(url, options));
-    }
-
-    let results = await Promise.all(funcs);
-    info.formats = Object.values(Object.assign({}, ...results));
-    info.formats = info.formats.map(formatUtils.addFormatMeta);
-    info.formats.sort(formatUtils.sortFormats);
-    info.full = true;
-    return info;
-};
-
-
-/**
- * Gets additional DASH formats.
- *
- * @param {string} url
- * @param {Object} options
- * @returns {Promise<Array.<Object>>}
- */
-const getDashManifest = (url, options) => new Promise((resolve, reject) => {
-    let formats = {};
-    const parser = sax.parser(false);
-    parser.onerror = reject;
-    let adaptationSet;
-    parser.onopentag = node => {
-        if (node.name === 'ADAPTATIONSET') {
-            adaptationSet = node.attributes;
-        } else if (node.name === 'REPRESENTATION') {
-            const itag = parseInt(node.attributes.ID);
-            if (!isNaN(itag)) {
-                formats[url] = Object.assign({
-                    itag, url,
-                    bitrate: parseInt(node.attributes.BANDWIDTH),
-                    mimeType: `${adaptationSet.MIMETYPE}; codecs="${node.attributes.CODECS}"`,
-                }, node.attributes.HEIGHT ? {
-                    width: parseInt(node.attributes.WIDTH),
-                    height: parseInt(node.attributes.HEIGHT),
-                    fps: parseInt(node.attributes.FRAMERATE),
-                } : {
-                    audioSampleRate: node.attributes.AUDIOSAMPLINGRATE,
-                });
+            return {
+                "code": 200,
+                "msg": "",
+                "data": {
+                    "videoDetails": videoDetails,
+                    "streamingData": {
+                        "formats": formats
+                    }
+                },
+                "id": "MusicDetailViewModel_detail_url"
             }
             }
-        }
-    };
-    parser.onend = () => {
-        resolve(formats);
-    };
-    const req = utils.exposedMiniget(new URL(url, BASE_URL).toString(), options);
-    req.setEncoding('utf8');
-    req.on('error', reject);
-    req.on('data', chunk => {
-        parser.write(chunk);
-    });
-    req.on('end', parser.close.bind(parser));
-});
-
-
-/**
- * Gets additional formats.
- *
- * @param {string} url
- * @param {Object} options
- * @returns {Promise<Array.<Object>>}
- */
-const getM3U8 = async (url, options) => {
-    url = new URL(url, BASE_URL);
-    const body = await utils.exposedMiniget(url.toString(), options).text();
-    let formats = {};
-    body
-        .split('\n')
-        .filter(line => /^https?:\/\//.test(line))
-        .forEach(line => {
-            const itag = parseInt(line.match(/\/itag\/(\d+)\//)[1]);
-            formats[line] = {itag, url: line};
-        });
-    return formats;
-};
-
-
-// Cache get info functions.
-// In case a user wants to get a video's info before downloading.
-for (let funcName of ['getBasicInfo', 'getInfo']) {
-    /**
-     * @param {string} link
-     * @param {Object} options
-     * @returns {Promise<Object>}
-     */
-    const func = exports[funcName];
-    exports[funcName] = async (link, options = {}) => {
-        let id = await urlUtils.getVideoID(link);
-        const key = [funcName, id, options.lang].join('-');
-        return exports.cache.getOrSet(key, () => func(id, options));
-    };
+        })
 }
 }
-
-
-// Export a few helpers.
-exports.validateID = urlUtils.validateID;
-exports.validateURL = urlUtils.validateURL;
-exports.getURLVideoID = urlUtils.getURLVideoID;
-exports.getVideoID = urlUtils.getVideoID;

+ 36 - 0
js/nginx.conf

@@ -0,0 +1,36 @@
+worker_processes  2;  ## 默认1,一般建议设成CPU核数1-2倍
+error_log  /var/log/error.log; ## 错误日志路径
+pid  /var/log/nginx.pid; ## 进程id
+
+events {
+    use epoll;
+    worker_connections  2048;
+}
+
+http {
+    client_max_body_size 1024m;
+
+    default_type application/octet-stream;  # 默认文件类型
+
+    # 日志格式及access日志路径
+    log_format   main '$remote_addr - $remote_user [$time_local]  $status '
+    '"$request" $body_bytes_sent "$http_referer" '
+    '"$http_user_agent" "$http_x_forwarded_for"';
+    access_log   /var/log/access.log  main;
+    sendfile     on;
+    tcp_nopush   on; # sendfile开启时才开启。
+
+    proxy_connect_timeout 900; #单位秒
+    proxy_send_timeout 900; #单位秒
+    proxy_read_timeout 900; #单位秒
+
+    server {
+        listen       80;
+        add_header 'Access-Control-Allow-Origin' '*';
+        add_header 'Access-Control-Allow-Methods' '*';
+        add_header 'Access-Control-Allow-Headers' '*';
+        location /watch {
+            proxy_pass https://www.youtube.com/watch;
+        }
+    }
+}

+ 2 - 0
js/run_nginx.sh

@@ -0,0 +1,2 @@
+ps axu | grep 'nginx' | awk '{print "kill -9 " $2}' | sh -x
+nginx -c /root/nginx.conf

+ 9 - 0
js/test.js

@@ -0,0 +1,9 @@
+const id = "7wNb0pHyGuI"
+const url = `http://127.0.0.1/watch?v=${id}`
+getVideoDetail(url)
+    .then(res => {
+        console.log(res);
+    })
+    .catch(e => {
+        console.log(e);
+    })

+ 0 - 286
js/utils.js

@@ -1,286 +0,0 @@
-const miniget = require('miniget');
-
-
-/**
- * Extract string inbetween another.
- *
- * @param {string} haystack
- * @param {string} left
- * @param {string} right
- * @returns {string}
- */
-exports.between = (haystack, left, right) => {
-  let pos;
-  if (left instanceof RegExp) {
-    const match = haystack.match(left);
-    if (!match) { return ''; }
-    pos = match.index + match[0].length;
-  } else {
-    pos = haystack.indexOf(left);
-    if (pos === -1) { return ''; }
-    pos += left.length;
-  }
-  haystack = haystack.slice(pos);
-  pos = haystack.indexOf(right);
-  if (pos === -1) { return ''; }
-  haystack = haystack.slice(0, pos);
-  return haystack;
-};
-
-
-/**
- * Get a number from an abbreviated number string.
- *
- * @param {string} string
- * @returns {number}
- */
-exports.parseAbbreviatedNumber = string => {
-  const match = string
-    .replace(',', '.')
-    .replace(' ', '')
-    .match(/([\d,.]+)([MK]?)/);
-  if (match) {
-    let [, num, multi] = match;
-    num = parseFloat(num);
-    return Math.round(multi === 'M' ? num * 1000000 :
-      multi === 'K' ? num * 1000 : num);
-  }
-  return null;
-};
-
-/**
- * Escape sequences for cutAfterJS
- * @param {string} start the character string the escape sequence
- * @param {string} end the character string to stop the escape seequence
- * @param {undefined|Regex} startPrefix a regex to check against the preceding 10 characters
- */
-const ESCAPING_SEQUENZES = [
-  // Strings
-  { start: '"', end: '"' },
-  { start: "'", end: "'" },
-  { start: '`', end: '`' },
-  // RegeEx
-  { start: '/', end: '/', startPrefix: /(^|[[{:;,/])\s?$/ },
-];
-
-/**
- * Match begin and end braces of input JS, return only JS
- *
- * @param {string} mixedJson
- * @returns {string}
-*/
-exports.cutAfterJS = mixedJson => {
-  // Define the general open and closing tag
-  let open, close;
-  if (mixedJson[0] === '[') {
-    open = '[';
-    close = ']';
-  } else if (mixedJson[0] === '{') {
-    open = '{';
-    close = '}';
-  }
-
-  if (!open) {
-    throw new Error(`Can't cut unsupported JSON (need to begin with [ or { ) but got: ${mixedJson[0]}`);
-  }
-
-  // States if the loop is currently inside an escaped js object
-  let isEscapedObject = null;
-
-  // States if the current character is treated as escaped or not
-  let isEscaped = false;
-
-  // Current open brackets to be closed
-  let counter = 0;
-
-  let i;
-  // Go through all characters from the start
-  for (i = 0; i < mixedJson.length; i++) {
-    // End of current escaped object
-    if (!isEscaped && isEscapedObject !== null && mixedJson[i] === isEscapedObject.end) {
-      isEscapedObject = null;
-      continue;
-    // Might be the start of a new escaped object
-    } else if (!isEscaped && isEscapedObject === null) {
-      for (const escaped of ESCAPING_SEQUENZES) {
-        if (mixedJson[i] !== escaped.start) continue;
-        // Test startPrefix against last 10 characters
-        if (!escaped.startPrefix || mixedJson.substring(i - 10, i).match(escaped.startPrefix)) {
-          isEscapedObject = escaped;
-          break;
-        }
-      }
-      // Continue if we found a new escaped object
-      if (isEscapedObject !== null) {
-        continue;
-      }
-    }
-
-    // Toggle the isEscaped boolean for every backslash
-    // Reset for every regular character
-    isEscaped = mixedJson[i] === '\\' && !isEscaped;
-
-    if (isEscapedObject !== null) continue;
-
-    if (mixedJson[i] === open) {
-      counter++;
-    } else if (mixedJson[i] === close) {
-      counter--;
-    }
-
-    // All brackets have been closed, thus end of JSON is reached
-    if (counter === 0) {
-      // Return the cut JSON
-      return mixedJson.substring(0, i + 1);
-    }
-  }
-
-  // We ran through the whole string and ended up with an unclosed bracket
-  throw Error("Can't cut unsupported JSON (no matching closing bracket found)");
-};
-
-
-/**
- * Checks if there is a playability error.
- *
- * @param {Object} player_response
- * @param {Array.<string>} statuses
- * @param {Error} ErrorType
- * @returns {!Error}
- */
-exports.playError = (player_response, statuses, ErrorType = Error) => {
-  let playability = player_response && player_response.playabilityStatus;
-  if (playability && statuses.includes(playability.status)) {
-    return new ErrorType(playability.reason || (playability.messages && playability.messages[0]));
-  }
-  return null;
-};
-
-/**
- * Does a miniget request and calls options.requestCallback if present
- *
- * @param {string} url the request url
- * @param {Object} options an object with optional requestOptions and requestCallback parameters
- * @param {Object} requestOptionsOverwrite overwrite of options.requestOptions
- * @returns {miniget.Stream}
- */
-exports.exposedMiniget = (url, options = {}, requestOptionsOverwrite) => {
-  const req = miniget(url, requestOptionsOverwrite || options.requestOptions);
-  if (typeof options.requestCallback === 'function') options.requestCallback(req);
-  return req;
-};
-
-/**
- * Temporary helper to help deprecating a few properties.
- *
- * @param {Object} obj
- * @param {string} prop
- * @param {Object} value
- * @param {string} oldPath
- * @param {string} newPath
- */
-exports.deprecate = (obj, prop, value, oldPath, newPath) => {
-  Object.defineProperty(obj, prop, {
-    get: () => {
-      console.warn(`\`${oldPath}\` will be removed in a near future release, ` +
-        `use \`${newPath}\` instead.`);
-      return value;
-    },
-  });
-};
-
-
-// Check for updates.
-const pkg = require('../package.json');
-const UPDATE_INTERVAL = 1000 * 60 * 60 * 12;
-exports.lastUpdateCheck = 0;
-exports.checkForUpdates = () => {
-  if (!process.env.YTDL_NO_UPDATE && !pkg.version.startsWith('0.0.0-') &&
-    Date.now() - exports.lastUpdateCheck >= UPDATE_INTERVAL) {
-    exports.lastUpdateCheck = Date.now();
-    return miniget('https://api.github.com/repos/fent/node-ytdl-core/releases/latest', {
-      headers: { 'User-Agent': 'ytdl-core' },
-    }).text().then(response => {
-      if (JSON.parse(response).tag_name !== `v${pkg.version}`) {
-        console.warn('\x1b[33mWARNING:\x1B[0m ytdl-core is out of date! Update with "npm install ytdl-core@latest".');
-      }
-    }, err => {
-      console.warn('Error checking for updates:', err.message);
-      console.warn('You can disable this check by setting the `YTDL_NO_UPDATE` env variable.');
-    });
-  }
-  return null;
-};
-
-
-/**
- * Gets random IPv6 Address from a block
- *
- * @param {string} ip the IPv6 block in CIDR-Notation
- * @returns {string}
- */
-exports.getRandomIPv6 = ip => {
-  // Start with a fast Regex-Check
-  if (!isIPv6(ip)) throw Error('Invalid IPv6 format');
-  // Start by splitting and normalizing addr and mask
-  const [rawAddr, rawMask] = ip.split('/');
-  let base10Mask = parseInt(rawMask);
-  if (!base10Mask || base10Mask > 128 || base10Mask < 24) throw Error('Invalid IPv6 subnet');
-  const base10addr = normalizeIP(rawAddr);
-  // Get random addr to pad with
-  // using Math.random since we're not requiring high level of randomness
-  const randomAddr = new Array(8).fill(1).map(() => Math.floor(Math.random() * 0xffff));
-
-  // Merge base10addr with randomAddr
-  const mergedAddr = randomAddr.map((randomItem, idx) => {
-    // Calculate the amount of static bits
-    const staticBits = Math.min(base10Mask, 16);
-    // Adjust the bitmask with the staticBits
-    base10Mask -= staticBits;
-    // Calculate the bitmask
-    // lsb makes the calculation way more complicated
-    const mask = 0xffff - ((2 ** (16 - staticBits)) - 1);
-    // Combine base10addr and random
-    return (base10addr[idx] & mask) + (randomItem & (mask ^ 0xffff));
-  });
-  // Return new addr
-  return mergedAddr.map(x => x.toString('16')).join(':');
-};
-
-
-// eslint-disable-next-line max-len
-const IPV6_REGEX = /^(([0-9a-f]{1,4}:)(:[0-9a-f]{1,4}){1,6}|([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}|([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}|([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4})|([0-9a-f]{1,4}:){1,7}(([0-9a-f]{1,4})|:))\/(1[0-1]\d|12[0-8]|\d{1,2})$/;
-/**
- * Quick check for a valid IPv6
- * The Regex only accepts a subset of all IPv6 Addresses
- *
- * @param {string} ip the IPv6 block in CIDR-Notation to test
- * @returns {boolean} true if valid
- */
-const isIPv6 = exports.isIPv6 = ip => IPV6_REGEX.test(ip);
-
-
-/**
- * Normalise an IP Address
- *
- * @param {string} ip the IPv6 Addr
- * @returns {number[]} the 8 parts of the IPv6 as Integers
- */
-const normalizeIP = exports.normalizeIP = ip => {
-  // Split by fill position
-  const parts = ip.split('::').map(x => x.split(':'));
-  // Normalize start and end
-  const partStart = parts[0] || [];
-  const partEnd = parts[1] || [];
-  partEnd.reverse();
-  // Placeholder for full ip
-  const fullIP = new Array(8).fill(0);
-  // Fill in start and end parts
-  for (let i = 0; i < Math.min(partStart.length, 8); i++) {
-    fullIP[i] = parseInt(partStart[i], 16) || 0;
-  }
-  for (let i = 0; i < Math.min(partEnd.length, 8); i++) {
-    fullIP[7 - i] = parseInt(partEnd[i], 16) || 0;
-  }
-  return fullIP;
-};

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1296 - 0
js/ytb.html


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov