2022-04-01 01:40:39 +00:00
|
|
|
/*
|
|
|
|
extension background script listening for events
|
|
|
|
*/
|
|
|
|
|
2022-12-03 02:57:04 +00:00
|
|
|
'use strict';
|
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
console.log('running background.js');
|
2022-04-01 01:40:39 +00:00
|
|
|
|
|
|
|
let browserType = getBrowser();
|
|
|
|
|
|
|
|
// boilerplate to dedect browser type api
|
|
|
|
function getBrowser() {
|
2022-12-03 02:51:15 +00:00
|
|
|
if (typeof chrome !== 'undefined') {
|
|
|
|
if (typeof browser !== 'undefined') {
|
|
|
|
return browser;
|
2022-04-01 01:40:39 +00:00
|
|
|
} else {
|
2022-12-03 02:51:15 +00:00
|
|
|
return chrome;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.log('failed to detect browser');
|
|
|
|
throw 'browser detection error';
|
|
|
|
}
|
2022-04-01 01:40:39 +00:00
|
|
|
}
|
|
|
|
|
2022-04-03 13:54:29 +00:00
|
|
|
// send get request to API backend
|
2022-06-20 11:40:17 +00:00
|
|
|
async function sendGet(path) {
|
2022-12-03 02:51:15 +00:00
|
|
|
let access = await getAccess();
|
|
|
|
const url = `${access.url}:${access.port}/${path}`;
|
|
|
|
console.log('GET: ' + url);
|
|
|
|
|
|
|
|
const rawResponse = await fetch(url, {
|
|
|
|
method: 'GET',
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/json',
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
Authorization: 'Token ' + access.apiKey,
|
|
|
|
mode: 'no-cors',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const content = await rawResponse.json();
|
|
|
|
return content;
|
|
|
|
}
|
2022-04-03 13:54:29 +00:00
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
// send post/put request to API backend
|
|
|
|
async function sendData(path, payload, method) {
|
|
|
|
let access = await getAccess();
|
|
|
|
const url = `${access.url}:${access.port}/${path}`;
|
|
|
|
console.log(`${method}: ${url}`);
|
|
|
|
console.log(`${method}: ${JSON.stringify(payload)}`);
|
2022-04-03 13:54:29 +00:00
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
try {
|
2022-04-03 13:54:29 +00:00
|
|
|
const rawResponse = await fetch(url, {
|
2022-12-03 02:51:15 +00:00
|
|
|
method: method,
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/json',
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
Authorization: 'Token ' + access.apiKey,
|
|
|
|
mode: 'no-cors',
|
|
|
|
},
|
|
|
|
body: JSON.stringify(payload),
|
2022-04-03 13:54:29 +00:00
|
|
|
});
|
|
|
|
const content = await rawResponse.json();
|
|
|
|
return content;
|
2022-12-03 02:51:15 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
return null;
|
|
|
|
}
|
2022-04-03 13:54:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// read access details from storage.local
|
|
|
|
async function getAccess() {
|
2022-12-03 02:52:33 +00:00
|
|
|
let storage = await browserType.storage.local.get('access');
|
2022-04-01 01:40:39 +00:00
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
return storage.access;
|
2022-04-03 13:54:29 +00:00
|
|
|
}
|
2022-04-01 01:40:39 +00:00
|
|
|
|
2022-06-25 13:15:19 +00:00
|
|
|
// check if cookie is valid
|
|
|
|
async function getCookieState() {
|
2022-12-03 02:51:15 +00:00
|
|
|
const path = 'api/cookie/';
|
|
|
|
let response = await sendGet(path);
|
|
|
|
console.log('cookie state: ' + JSON.stringify(response));
|
2022-06-25 13:15:19 +00:00
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
return response;
|
2022-06-25 13:15:19 +00:00
|
|
|
}
|
|
|
|
|
2023-02-20 03:24:27 +00:00
|
|
|
// send ping to server
|
2022-04-03 13:54:29 +00:00
|
|
|
async function verifyConnection() {
|
2022-12-03 02:51:15 +00:00
|
|
|
const path = 'api/ping/';
|
2023-02-20 03:24:27 +00:00
|
|
|
let message = await sendGet(path);
|
|
|
|
console.log('verify connection: ' + JSON.stringify(message));
|
2022-04-01 01:40:39 +00:00
|
|
|
|
2023-02-20 03:24:27 +00:00
|
|
|
if (message?.response === 'pong') {
|
|
|
|
return true;
|
|
|
|
} else if (message?.detail) {
|
|
|
|
throw new Error(message.detail);
|
|
|
|
} else {
|
|
|
|
throw new Error(`got unknown message ${JSON.stringify(message)}`);
|
|
|
|
}
|
2022-04-03 13:54:29 +00:00
|
|
|
}
|
2022-04-01 01:40:39 +00:00
|
|
|
|
2022-11-24 01:36:22 +00:00
|
|
|
// send youtube link from injected buttons
|
2023-02-20 03:24:27 +00:00
|
|
|
async function download(url) {
|
2023-05-10 13:39:40 +00:00
|
|
|
let apiURL = 'api/download/';
|
|
|
|
let autostart = await browserType.storage.local.get('autostart');
|
|
|
|
if (Object.keys(autostart).length > 0 && autostart.autostart.checked) {
|
|
|
|
apiURL += '?autostart=true';
|
|
|
|
}
|
2023-02-20 03:24:27 +00:00
|
|
|
return await sendData(
|
2023-05-10 13:39:40 +00:00
|
|
|
apiURL,
|
2023-02-20 03:24:27 +00:00
|
|
|
{
|
2022-12-03 02:51:15 +00:00
|
|
|
data: [
|
|
|
|
{
|
2023-02-20 03:24:27 +00:00
|
|
|
youtube_id: url,
|
2022-12-03 02:51:15 +00:00
|
|
|
status: 'pending',
|
|
|
|
},
|
|
|
|
],
|
2023-02-20 03:24:27 +00:00
|
|
|
},
|
|
|
|
'POST'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:34:58 +00:00
|
|
|
async function subscribe(url, subscribed) {
|
2023-02-20 03:24:27 +00:00
|
|
|
return await sendData(
|
|
|
|
'api/channel/',
|
|
|
|
{
|
2022-12-03 02:51:15 +00:00
|
|
|
data: [
|
|
|
|
{
|
2023-02-20 03:24:27 +00:00
|
|
|
channel_id: url,
|
2023-08-26 10:34:58 +00:00
|
|
|
channel_subscribed: subscribed,
|
2022-12-03 02:51:15 +00:00
|
|
|
},
|
|
|
|
],
|
2023-02-20 03:24:27 +00:00
|
|
|
},
|
|
|
|
'POST'
|
|
|
|
);
|
2022-05-30 11:02:42 +00:00
|
|
|
}
|
|
|
|
|
2023-08-24 14:20:15 +00:00
|
|
|
async function videoExists(id) {
|
|
|
|
const path = `api/video/${id}/`;
|
|
|
|
let response = await sendGet(path);
|
2023-08-26 15:39:37 +00:00
|
|
|
if (!response.data) return false;
|
|
|
|
let access = await getAccess();
|
2023-09-21 10:31:19 +00:00
|
|
|
return new URL(`video/${id}/`, `${access.url}:${access.port}/`).href;
|
2023-08-24 14:20:15 +00:00
|
|
|
}
|
|
|
|
|
2023-08-26 10:34:58 +00:00
|
|
|
async function getChannelCache() {
|
|
|
|
let cache = await browserType.storage.local.get('cache');
|
2023-08-26 13:05:29 +00:00
|
|
|
if (cache.cache) return cache;
|
|
|
|
return { cache: {} };
|
2023-08-26 10:34:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function setChannel(channelHandler, channelId) {
|
|
|
|
let cache = await getChannelCache();
|
|
|
|
cache.cache[channelHandler] = { id: channelId, timestamp: Date.now() };
|
|
|
|
browserType.storage.local.set(cache);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getChannelId(channelHandle) {
|
|
|
|
let cache = await getChannelCache();
|
|
|
|
|
|
|
|
if (cache.cache[channelHandle]) {
|
|
|
|
return cache.cache[channelHandle]?.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
let channel = await searchChannel(channelHandle);
|
|
|
|
if (channel) setChannel(channelHandle, channel.channel_id);
|
|
|
|
|
|
|
|
return channel.channel_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function searchChannel(channelHandle) {
|
2023-08-25 12:28:42 +00:00
|
|
|
const path = `api/channel/search/?q=${channelHandle}`;
|
|
|
|
let response = await sendGet(path);
|
|
|
|
return response.data;
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:34:58 +00:00
|
|
|
async function getChannel(channelHandle) {
|
|
|
|
let channelId = await getChannelId(channelHandle);
|
|
|
|
if (!channelId) return;
|
|
|
|
|
|
|
|
const path = `api/channel/${channelId}/`;
|
|
|
|
let response = await sendGet(path);
|
|
|
|
|
|
|
|
return response.data;
|
|
|
|
}
|
|
|
|
|
2022-06-22 12:46:37 +00:00
|
|
|
async function cookieStr(cookieLines) {
|
2022-12-03 02:51:15 +00:00
|
|
|
const path = 'api/cookie/';
|
|
|
|
let payload = {
|
|
|
|
cookie: cookieLines.join('\n'),
|
|
|
|
};
|
|
|
|
let response = await sendData(path, payload, 'PUT');
|
2022-06-22 12:46:37 +00:00
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
return response;
|
2022-06-22 12:46:37 +00:00
|
|
|
}
|
|
|
|
|
2022-06-20 10:35:57 +00:00
|
|
|
function buildCookieLine(cookie) {
|
2024-11-26 08:51:44 +00:00
|
|
|
// 2nd argument controls subdomains, and must match leading dot in domain
|
|
|
|
let includeSubdomains = cookie.domain.startsWith('.') ? 'TRUE' : 'FALSE';
|
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
return [
|
|
|
|
cookie.domain,
|
2024-11-26 08:51:44 +00:00
|
|
|
includeSubdomains,
|
2022-12-03 02:51:15 +00:00
|
|
|
cookie.path,
|
|
|
|
cookie.httpOnly.toString().toUpperCase(),
|
|
|
|
Math.trunc(cookie.expirationDate) || 0,
|
|
|
|
cookie.name,
|
|
|
|
cookie.value,
|
|
|
|
].join('\t');
|
2022-06-20 10:35:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function sendCookies() {
|
2022-12-03 02:51:15 +00:00
|
|
|
console.log('function sendCookies');
|
2024-05-11 15:02:46 +00:00
|
|
|
const acceptableDomains = ['.youtube.com', 'youtube.com', 'www.youtube.com'];
|
2022-12-03 02:51:15 +00:00
|
|
|
|
|
|
|
let cookieStores = await browserType.cookies.getAllCookieStores();
|
2022-12-03 02:52:33 +00:00
|
|
|
let cookieLines = [
|
2022-12-03 02:51:15 +00:00
|
|
|
'# Netscape HTTP Cookie File',
|
|
|
|
'# https://curl.haxx.se/rfc/cookie_spec.html',
|
|
|
|
'# This is a generated file! Do not edit.\n',
|
|
|
|
];
|
|
|
|
for (let i = 0; i < cookieStores.length; i++) {
|
|
|
|
const cookieStore = cookieStores[i];
|
2022-12-03 02:52:33 +00:00
|
|
|
let allCookiesStore = await browserType.cookies.getAll({
|
2022-12-03 02:51:15 +00:00
|
|
|
domain: '.youtube.com',
|
|
|
|
storeId: cookieStore['id'],
|
|
|
|
});
|
|
|
|
for (let j = 0; j < allCookiesStore.length; j++) {
|
|
|
|
const cookie = allCookiesStore[j];
|
2024-05-11 15:02:46 +00:00
|
|
|
if (acceptableDomains.includes(cookie.domain)) {
|
|
|
|
cookieLines.push(buildCookieLine(cookie));
|
|
|
|
}
|
2022-06-20 10:35:57 +00:00
|
|
|
}
|
2022-12-03 02:51:15 +00:00
|
|
|
}
|
2022-06-22 12:46:37 +00:00
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
let response = cookieStr(cookieLines);
|
2022-06-20 10:35:57 +00:00
|
|
|
|
2022-12-03 02:51:15 +00:00
|
|
|
return response;
|
2022-06-20 10:35:57 +00:00
|
|
|
}
|
|
|
|
|
2023-02-20 03:24:27 +00:00
|
|
|
/*
|
|
|
|
process and return message if needed
|
|
|
|
the following messages are supported:
|
|
|
|
type Message =
|
|
|
|
| { type: 'verify' }
|
|
|
|
| { type: 'cookieState' }
|
|
|
|
| { type: 'sendCookie' }
|
|
|
|
| { type: 'download', url: string }
|
|
|
|
| { type: 'subscribe', url: string }
|
2023-08-26 10:34:58 +00:00
|
|
|
| { type: 'unsubscribe', url: string }
|
|
|
|
| { type: 'videoExists', id: string }
|
|
|
|
| { type: 'getChannel', url: string }
|
2023-02-20 03:24:27 +00:00
|
|
|
*/
|
2022-04-04 04:58:50 +00:00
|
|
|
function handleMessage(request, sender, sendResponse) {
|
2023-02-20 03:24:27 +00:00
|
|
|
console.log('message background.js listener got message', request);
|
|
|
|
|
|
|
|
// this function must return the value `true` in chrome to signal the response will be async;
|
|
|
|
// it cannot return a promise
|
|
|
|
// so in order to use async/await, we need a wrapper
|
|
|
|
(async () => {
|
|
|
|
switch (request.type) {
|
|
|
|
case 'verify': {
|
|
|
|
return await verifyConnection();
|
|
|
|
}
|
|
|
|
case 'cookieState': {
|
|
|
|
return await getCookieState();
|
|
|
|
}
|
|
|
|
case 'sendCookie': {
|
|
|
|
return await sendCookies();
|
|
|
|
}
|
|
|
|
case 'download': {
|
|
|
|
return await download(request.url);
|
|
|
|
}
|
|
|
|
case 'subscribe': {
|
2023-08-26 10:34:58 +00:00
|
|
|
return await subscribe(request.url, true);
|
|
|
|
}
|
|
|
|
case 'unsubscribe': {
|
|
|
|
let channelId = await getChannelId(request.url);
|
|
|
|
return await subscribe(channelId, false);
|
2023-02-20 03:24:27 +00:00
|
|
|
}
|
2023-08-24 14:20:15 +00:00
|
|
|
case 'videoExists': {
|
|
|
|
return await videoExists(request.videoId);
|
|
|
|
}
|
2023-08-25 12:28:42 +00:00
|
|
|
case 'getChannel': {
|
|
|
|
return await getChannel(request.channelHandle);
|
|
|
|
}
|
2023-02-20 03:24:27 +00:00
|
|
|
default: {
|
|
|
|
let err = new Error(`unknown message type ${JSON.stringify(request.type)}`);
|
|
|
|
console.log(err);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})()
|
|
|
|
.then(value => sendResponse({ success: true, value }))
|
|
|
|
.catch(e => {
|
|
|
|
console.error(e);
|
|
|
|
let message = e?.message ?? e;
|
|
|
|
if (message === 'Failed to fetch') {
|
|
|
|
// chrome's error message for failed `fetch` is not very user-friendly
|
|
|
|
message = 'Could not connect to server';
|
|
|
|
}
|
|
|
|
sendResponse({ success: false, value: message });
|
2022-12-03 02:51:15 +00:00
|
|
|
});
|
|
|
|
return true;
|
2022-04-04 04:58:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
browserType.runtime.onMessage.addListener(handleMessage);
|