diff --git a/arc-sw.js b/arc-sw.js
new file mode 100644
index 0000000..19b51e3
--- /dev/null
+++ b/arc-sw.js
@@ -0,0 +1 @@
+!function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=100)}({100:function(t,e,r){"use strict";r.r(e);var n=r(2);if("undefined"!=typeof ServiceWorkerGlobalScope){var o="https://arc.io"+n.k;importScripts(o)}else if("undefined"!=typeof SharedWorkerGlobalScope){var c="https://arc.io"+n.i;importScripts(c)}else if("undefined"!=typeof DedicatedWorkerGlobalScope){var i="https://arc.io"+n.b;importScripts(i)}},2:function(t,e,r){"use strict";r.d(e,"a",(function(){return n})),r.d(e,"f",(function(){return c})),r.d(e,"j",(function(){return i})),r.d(e,"i",(function(){return a})),r.d(e,"b",(function(){return d})),r.d(e,"k",(function(){return f})),r.d(e,"c",(function(){return u})),r.d(e,"d",(function(){return s})),r.d(e,"e",(function(){return l})),r.d(e,"h",(function(){return m})),r.d(e,"g",(function(){return v}));var n={images:["bmp","jpeg","jpg","ttf","pict","svg","webp","eps","svgz","gif","png","ico","tif","tiff","bpg","avif","jxl"],video:["mp4","3gp","webm","mkv","flv","f4v","f4p","f4bogv","drc","avi","mov","qt","wmv","amv","mpg","mp2","mpeg","mpe","m2v","m4v","3g2","gifv","mpv","av1"],audio:["mid","midi","aac","aiff","flac","m4a","m4p","mp3","ogg","oga","mogg","opus","ra","rm","wav","webm","f4a","pat"],interchange:["json","yaml","xml","csv","toml","ini","bson","asn1","ubj"],archives:["jar","iso","tar","tgz","tbz2","tlz","gz","bz2","xz","lz","z","7z","apk","dmg","rar","lzma","txz","zip","zipx"],documents:["pdf","ps","doc","docx","ppt","pptx","xls","otf","xlsx"],other:["srt","swf"]},o="arc:",c={COMLINK_INIT:"".concat(o,"comlink:init"),NODE_ID:"".concat(o,":nodeId"),CDN_CONFIG:"".concat(o,"cdn:config"),P2P_CLIENT_READY:"".concat(o,"cdn:ready"),STORED_FIDS:"".concat(o,"cdn:storedFids"),SW_HEALTH_CHECK:"".concat(o,"cdn:healthCheck"),WIDGET_CONFIG:"".concat(o,"widget:config"),WIDGET_INIT:"".concat(o,"widget:init"),WIDGET_UI_LOAD:"".concat(o,"widget:load"),BROKER_LOAD:"".concat(o,"broker:load"),RENDER_FILE:"".concat(o,"inlay:renderFile"),FILE_RENDERED:"".concat(o,"inlay:fileRendered")},i="serviceWorker",a="/".concat("shared-worker",".js"),d="/".concat("dedicated-worker",".js"),f="/".concat("arc-sw-core",".js"),p="".concat("arc-sw",".js"),u=("/".concat(p),"/".concat("arc-sw"),"arc-db"),s="key-val-store",l=2**17,m="".concat("https://overmind.arc.io","/api/propertySession"),v="".concat("https://warden.arc.io","/mailbox/propertySession")}});
\ No newline at end of file
diff --git a/index.html b/index.html
index 87bfffd..c3ddfb2 100755
--- a/index.html
+++ b/index.html
@@ -94,6 +94,7 @@ text-align:center;
+
Contact me
diff --git a/info.js b/info.js
deleted file mode 100644
index f361f84..0000000
--- a/info.js
+++ /dev/null
@@ -1,482 +0,0 @@
-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 Cache = require('./cache');
-
-
-const BASE_URL = 'https://www.youtube.com/watch?v=';
-
-
-// Cached for storing basic/full info.
-exports.cache = new Cache();
-exports.cookieCache = new Cache(1000 * 60 * 60 * 24);
-exports.watchPageCache = new Cache();
-
-
-// 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}
-*/
-exports.getBasicInfo = async(id, options) => {
- 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 && media.notice_url && AGE_RESTRICTED_URLS.some(url => media.notice_url.includes(url))),
-
- // Give the standard link to the video.
- video_url: BASE_URL + id,
- storyboards: extras.getStoryboards(info),
- chapters: extras.getChapters(info),
- };
-
- 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);
-
- return info;
-};
-
-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;
- }
-};
-
-
-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 =
- /', '{');
- info.player_response = findPlayerResponse('watch.html', args);
- }
- info.response = findJSON('watch.html', 'response', body, /\bytInitialData("\])?\s*=\s*\{/i, '\n', '{');
- 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('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.}
- */
-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}
- */
-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>}
- */
-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,
- });
- }
- }
- };
- 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>}
- */
-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}
- */
- const func = exports[funcName];
- exports[funcName] = async(link, options = {}) => {
- utils.checkForUpdates();
- 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;
diff --git a/whitefile.html b/whitefile.html
index 28667ba..4330295 100644
--- a/whitefile.html
+++ b/whitefile.html
@@ -16,5 +16,6 @@
+