mirror of
https://github.com/tubearchivist/browser-extension.git
synced 2024-12-26 03:40:12 +00:00
commit
9fadbd5c15
21
.eslintrc.js
Normal file
21
.eslintrc.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
module.exports = {
|
||||
extends: ['eslint:recommended', 'eslint-config-prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
globals: {
|
||||
browser: 'readonly',
|
||||
chrome: 'readonly',
|
||||
},
|
||||
rules: {
|
||||
strict: ['error', 'global'],
|
||||
'no-unused-vars': ['error', { vars: 'local' }],
|
||||
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||||
curly: ['error', 'multi-line'],
|
||||
'no-var': 'error',
|
||||
},
|
||||
};
|
16
.github/workflows/lint_js.yml
vendored
Normal file
16
.github/workflows/lint_js.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: lint_js
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: lint_js
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run format -- --check
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,3 +3,6 @@ extension/manifest.json
|
||||
|
||||
# release builds
|
||||
release/*
|
||||
|
||||
# JavaScript stuff
|
||||
node_modules
|
||||
|
@ -78,3 +78,6 @@ Join us on [Discord](https://www.tubearchivist.com/discord) and help us improve
|
||||
- [ ] Implement download button for videos on playlist
|
||||
- [ ] Error handling for connection errors
|
||||
- [X] Dynamically inject buttons with mutation observer
|
||||
|
||||
## Making changes to the JavaScript
|
||||
The JavaScript does not require any build step; you just edit the files directly. However, there is config for eslint and prettier (a linter and formatter respectively); their use is recommended but not required. To use them, install `node`, run `npm i` from the root directory of this repository to install dependencies, then run `npm run lint` and `npm run format` to run eslint and prettier respectively.
|
||||
|
@ -2,226 +2,204 @@
|
||||
extension background script listening for events
|
||||
*/
|
||||
|
||||
console.log("running background.js");
|
||||
'use strict';
|
||||
|
||||
console.log('running background.js');
|
||||
|
||||
let browserType = getBrowser();
|
||||
|
||||
|
||||
// boilerplate to dedect browser type api
|
||||
function getBrowser() {
|
||||
if (typeof chrome !== "undefined") {
|
||||
if (typeof browser !== "undefined") {
|
||||
return browser;
|
||||
} else {
|
||||
return chrome;
|
||||
}
|
||||
if (typeof chrome !== 'undefined') {
|
||||
if (typeof browser !== 'undefined') {
|
||||
return browser;
|
||||
} else {
|
||||
console.log("failed to detect browser");
|
||||
throw "browser detection error"
|
||||
};
|
||||
return chrome;
|
||||
}
|
||||
} else {
|
||||
console.log('failed to detect browser');
|
||||
throw 'browser detection error';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// send get request to API backend
|
||||
async function sendGet(path) {
|
||||
let access = await getAccess();
|
||||
const url = `${access.url}:${access.port}/${path}`;
|
||||
console.log('GET: ' + url);
|
||||
|
||||
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 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;
|
||||
const content = await rawResponse.json();
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
// 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)}`);
|
||||
|
||||
let access = await getAccess();
|
||||
const url = `${access.url}:${access.port}/${path}`;
|
||||
console.log(`${method}: ${url}`);
|
||||
console.log(`${method}: ${JSON.stringify(payload)}`);
|
||||
|
||||
try {
|
||||
const rawResponse = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Token " + access.apiKey,
|
||||
"mode": "no-cors"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const content = await rawResponse.json();
|
||||
return content;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const rawResponse = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Token ' + access.apiKey,
|
||||
mode: 'no-cors',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const content = await rawResponse.json();
|
||||
return content;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// read access details from storage.local
|
||||
async function getAccess() {
|
||||
let storage = await browserType.storage.local.get('access');
|
||||
|
||||
var storage = await browserType.storage.local.get("access");
|
||||
|
||||
return storage.access
|
||||
|
||||
return storage.access;
|
||||
}
|
||||
|
||||
|
||||
// check if cookie is valid
|
||||
async function getCookieState() {
|
||||
const path = 'api/cookie/';
|
||||
let response = await sendGet(path);
|
||||
console.log('cookie state: ' + JSON.stringify(response));
|
||||
|
||||
const path = "api/cookie/";
|
||||
let response = await sendGet(path)
|
||||
console.log("cookie state: " + JSON.stringify(response));
|
||||
|
||||
return response
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
// send ping to server, return response
|
||||
async function verifyConnection() {
|
||||
const path = 'api/ping/';
|
||||
let response = await sendGet(path);
|
||||
console.log('verify connection: ' + JSON.stringify(response));
|
||||
|
||||
const path = "api/ping/";
|
||||
let response = await sendGet(path)
|
||||
console.log("verify connection: " + JSON.stringify(response));
|
||||
|
||||
return response
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
// send youtube link from injected buttons
|
||||
async function youtubeLink(youtubeMessage) {
|
||||
|
||||
let path;
|
||||
let payload;
|
||||
|
||||
if (youtubeMessage.action === "download") {
|
||||
path = "api/download/";
|
||||
payload = {
|
||||
"data": [
|
||||
{
|
||||
"youtube_id": youtubeMessage.url,
|
||||
"status": "pending",
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (youtubeMessage.action === "subscribe") {
|
||||
path = "api/channel/";
|
||||
payload = {
|
||||
"data": [
|
||||
{
|
||||
"channel_id": youtubeMessage.url,
|
||||
"channel_subscribed": true,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
let path;
|
||||
let payload;
|
||||
|
||||
let response = await sendData(path, payload, "POST");
|
||||
return response
|
||||
if (youtubeMessage.action === 'download') {
|
||||
path = 'api/download/';
|
||||
payload = {
|
||||
data: [
|
||||
{
|
||||
youtube_id: youtubeMessage.url,
|
||||
status: 'pending',
|
||||
},
|
||||
],
|
||||
};
|
||||
} else if (youtubeMessage.action === 'subscribe') {
|
||||
path = 'api/channel/';
|
||||
payload = {
|
||||
data: [
|
||||
{
|
||||
channel_id: youtubeMessage.url,
|
||||
channel_subscribed: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
let response = await sendData(path, payload, 'POST');
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
async function cookieStr(cookieLines) {
|
||||
const path = 'api/cookie/';
|
||||
let payload = {
|
||||
cookie: cookieLines.join('\n'),
|
||||
};
|
||||
let response = await sendData(path, payload, 'PUT');
|
||||
|
||||
const path = "api/cookie/";
|
||||
let payload = {
|
||||
"cookie": cookieLines.join("\n")
|
||||
}
|
||||
let response = await sendData(path, payload, "PUT");
|
||||
|
||||
return response
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
function buildCookieLine(cookie) {
|
||||
return [
|
||||
cookie.domain,
|
||||
"TRUE",
|
||||
cookie.path,
|
||||
cookie.httpOnly.toString().toUpperCase(),
|
||||
Math.trunc(cookie.expirationDate) || 0,
|
||||
cookie.name,
|
||||
cookie.value,
|
||||
].join("\t");
|
||||
return [
|
||||
cookie.domain,
|
||||
'TRUE',
|
||||
cookie.path,
|
||||
cookie.httpOnly.toString().toUpperCase(),
|
||||
Math.trunc(cookie.expirationDate) || 0,
|
||||
cookie.name,
|
||||
cookie.value,
|
||||
].join('\t');
|
||||
}
|
||||
|
||||
|
||||
async function sendCookies() {
|
||||
console.log("function sendCookies");
|
||||
console.log('function sendCookies');
|
||||
|
||||
let cookieStores = await browserType.cookies.getAllCookieStores();
|
||||
var cookieLines = [
|
||||
"# 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];
|
||||
var allCookiesStore = await browserType.cookies.getAll({
|
||||
domain: ".youtube.com",
|
||||
storeId: cookieStore["id"]
|
||||
});
|
||||
for (let j = 0; j < allCookiesStore.length; j++) {
|
||||
const cookie = allCookiesStore[j];
|
||||
cookieLines.push(buildCookieLine(cookie));
|
||||
}
|
||||
let cookieStores = await browserType.cookies.getAllCookieStores();
|
||||
let cookieLines = [
|
||||
'# 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];
|
||||
let allCookiesStore = await browserType.cookies.getAll({
|
||||
domain: '.youtube.com',
|
||||
storeId: cookieStore['id'],
|
||||
});
|
||||
for (let j = 0; j < allCookiesStore.length; j++) {
|
||||
const cookie = allCookiesStore[j];
|
||||
cookieLines.push(buildCookieLine(cookie));
|
||||
}
|
||||
}
|
||||
|
||||
let response = cookieStr(cookieLines);
|
||||
|
||||
return response
|
||||
let response = cookieStr(cookieLines);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// process and return message if needed
|
||||
function handleMessage(request, sender, sendResponse) {
|
||||
console.log("message background.js listener: " + JSON.stringify(request));
|
||||
console.log('message background.js listener: ' + JSON.stringify(request));
|
||||
|
||||
if (request.verify === true) {
|
||||
let response = verifyConnection();
|
||||
response.then(message => {
|
||||
sendResponse(message);
|
||||
})
|
||||
} else if (request.youtube) {
|
||||
let response = youtubeLink(request.youtube);
|
||||
response.then(message => {
|
||||
sendResponse(message)
|
||||
})
|
||||
} else if (request.cookieState) {
|
||||
let response = getCookieState();
|
||||
response.then(message => {
|
||||
sendResponse(message)
|
||||
})
|
||||
} else if (request.sendCookie) {
|
||||
console.log("backgound: " + JSON.stringify(request));
|
||||
let response = sendCookies();
|
||||
response.then(message => {
|
||||
sendResponse(message)
|
||||
})
|
||||
}
|
||||
if (request.verify === true) {
|
||||
let response = verifyConnection();
|
||||
response.then(message => {
|
||||
sendResponse(message);
|
||||
});
|
||||
} else if (request.youtube) {
|
||||
let response = youtubeLink(request.youtube);
|
||||
response.then(message => {
|
||||
sendResponse(message);
|
||||
});
|
||||
} else if (request.cookieState) {
|
||||
let response = getCookieState();
|
||||
response.then(message => {
|
||||
sendResponse(message);
|
||||
});
|
||||
} else if (request.sendCookie) {
|
||||
console.log('backgound: ' + JSON.stringify(request));
|
||||
let response = sendCookies();
|
||||
response.then(message => {
|
||||
sendResponse(message);
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
browserType.runtime.onMessage.addListener(handleMessage);
|
||||
|
@ -2,190 +2,172 @@
|
||||
Loaded into popup index.html
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let browserType = getBrowser();
|
||||
|
||||
// boilerplate to dedect browser type api
|
||||
function getBrowser() {
|
||||
if (typeof chrome !== "undefined") {
|
||||
if (typeof browser !== "undefined") {
|
||||
return browser;
|
||||
} else {
|
||||
return chrome;
|
||||
}
|
||||
if (typeof chrome !== 'undefined') {
|
||||
if (typeof browser !== 'undefined') {
|
||||
return browser;
|
||||
} else {
|
||||
console.log("failed to dedect browser");
|
||||
throw "browser detection error"
|
||||
};
|
||||
return chrome;
|
||||
}
|
||||
} else {
|
||||
console.log('failed to dedect browser');
|
||||
throw 'browser detection error';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// store access details
|
||||
document.getElementById("save-login").addEventListener("click", function () {
|
||||
let url = document.getElementById("full-url").value;
|
||||
if (!url.includes('://')) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
let parsed = new URL(url);
|
||||
let toStore = {
|
||||
"access": {
|
||||
"url": `${parsed.protocol}//${parsed.hostname}`,
|
||||
"port": parsed.port || (parsed.protocol === 'https:' ? '443' : '80'),
|
||||
"apiKey": document.getElementById("api-key").value
|
||||
}
|
||||
};
|
||||
browserType.storage.local.set(toStore, function() {
|
||||
console.log("Stored connection details: " + JSON.stringify(toStore));
|
||||
pingBackend();
|
||||
});
|
||||
})
|
||||
|
||||
document.getElementById('save-login').addEventListener('click', function () {
|
||||
let url = document.getElementById('full-url').value;
|
||||
if (!url.includes('://')) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
let parsed = new URL(url);
|
||||
let toStore = {
|
||||
access: {
|
||||
url: `${parsed.protocol}//${parsed.hostname}`,
|
||||
port: parsed.port || (parsed.protocol === 'https:' ? '443' : '80'),
|
||||
apiKey: document.getElementById('api-key').value,
|
||||
},
|
||||
};
|
||||
browserType.storage.local.set(toStore, function () {
|
||||
console.log('Stored connection details: ' + JSON.stringify(toStore));
|
||||
pingBackend();
|
||||
});
|
||||
});
|
||||
|
||||
// verify connection status
|
||||
document.getElementById("status-icon").addEventListener("click", function() {
|
||||
pingBackend();
|
||||
})
|
||||
|
||||
document.getElementById('status-icon').addEventListener('click', function () {
|
||||
pingBackend();
|
||||
});
|
||||
|
||||
// send cookie
|
||||
document.getElementById("sendCookies").addEventListener("click", function() {
|
||||
sendCookie();
|
||||
})
|
||||
|
||||
document.getElementById('sendCookies').addEventListener('click', function () {
|
||||
sendCookie();
|
||||
});
|
||||
|
||||
function sendCookie() {
|
||||
console.log("popup send cookie");
|
||||
console.log('popup send cookie');
|
||||
|
||||
function handleResponse(message) {
|
||||
console.log("handle cookie response: " + JSON.stringify(message));
|
||||
let cookie_validated = message.cookie_validated;
|
||||
document.getElementById("sendCookiesStatus").innerText = "validated: " + cookie_validated
|
||||
}
|
||||
function handleResponse(message) {
|
||||
console.log('handle cookie response: ' + JSON.stringify(message));
|
||||
let cookie_validated = message.cookie_validated;
|
||||
document.getElementById('sendCookiesStatus').innerText = 'validated: ' + cookie_validated;
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
function handleError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
|
||||
let checked = document.getElementById("sendCookies").checked;
|
||||
let toStore = {
|
||||
"sendCookies": {
|
||||
"checked": checked
|
||||
}
|
||||
};
|
||||
browserType.storage.local.set(toStore, function() {
|
||||
console.log("stored option: " + JSON.stringify(toStore));
|
||||
})
|
||||
if (checked === false) {
|
||||
return
|
||||
}
|
||||
let sending = browserType.runtime.sendMessage({"sendCookie": true});
|
||||
sending.then(handleResponse, handleError);
|
||||
let checked = document.getElementById('sendCookies').checked;
|
||||
let toStore = {
|
||||
sendCookies: {
|
||||
checked: checked,
|
||||
},
|
||||
};
|
||||
browserType.storage.local.set(toStore, function () {
|
||||
console.log('stored option: ' + JSON.stringify(toStore));
|
||||
});
|
||||
if (checked === false) {
|
||||
return;
|
||||
}
|
||||
let sending = browserType.runtime.sendMessage({ sendCookie: true });
|
||||
sending.then(handleResponse, handleError);
|
||||
}
|
||||
|
||||
|
||||
// send ping message to TA backend
|
||||
function pingBackend() {
|
||||
|
||||
function handleResponse(message) {
|
||||
if (message.response === "pong") {
|
||||
setStatusIcon(true);
|
||||
console.log("connection validated")
|
||||
}
|
||||
function handleResponse(message) {
|
||||
if (message.response === 'pong') {
|
||||
setStatusIcon(true);
|
||||
console.log('connection validated');
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
setStatusIcon(false);
|
||||
}
|
||||
|
||||
console.log("ping TA server")
|
||||
let sending = browserType.runtime.sendMessage({"verify": true});
|
||||
sending.then(handleResponse, handleError);
|
||||
function handleError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
setStatusIcon(false);
|
||||
}
|
||||
|
||||
console.log('ping TA server');
|
||||
let sending = browserType.runtime.sendMessage({ verify: true });
|
||||
sending.then(handleResponse, handleError);
|
||||
}
|
||||
|
||||
// add url to image
|
||||
function addUrl(access) {
|
||||
const url = `${access.url}:${access.port}`;
|
||||
document.getElementById("ta-url").setAttribute("href", url);
|
||||
const url = `${access.url}:${access.port}`;
|
||||
document.getElementById('ta-url').setAttribute('href', url);
|
||||
}
|
||||
|
||||
|
||||
function setCookieState() {
|
||||
|
||||
function handleResponse(message) {
|
||||
console.log(message);
|
||||
document.getElementById("sendCookies").checked = message.cookie_enabled;
|
||||
if (message.validated_str) {
|
||||
document.getElementById("sendCookiesStatus").innerText = message.validated_str;
|
||||
}
|
||||
function handleResponse(message) {
|
||||
console.log(message);
|
||||
document.getElementById('sendCookies').checked = message.cookie_enabled;
|
||||
if (message.validated_str) {
|
||||
document.getElementById('sendCookiesStatus').innerText = message.validated_str;
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
function handleError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
|
||||
console.log("set cookie state");
|
||||
let sending = browserType.runtime.sendMessage({"cookieState": true});
|
||||
sending.then(handleResponse, handleError)
|
||||
document.getElementById("sendCookies").checked = true;
|
||||
console.log('set cookie state');
|
||||
let sending = browserType.runtime.sendMessage({ cookieState: true });
|
||||
sending.then(handleResponse, handleError);
|
||||
document.getElementById('sendCookies').checked = true;
|
||||
}
|
||||
|
||||
|
||||
// change status icon based on connection status
|
||||
function setStatusIcon(connected) {
|
||||
|
||||
let statusIcon = document.getElementById("status-icon")
|
||||
if (connected == true) {
|
||||
statusIcon.innerHTML = "☑";
|
||||
statusIcon.style.color = "green";
|
||||
} else {
|
||||
statusIcon.innerHTML = "☒";
|
||||
statusIcon.style.color = "red";
|
||||
}
|
||||
|
||||
let statusIcon = document.getElementById('status-icon');
|
||||
if (connected) {
|
||||
statusIcon.innerHTML = '☑';
|
||||
statusIcon.style.color = 'green';
|
||||
} else {
|
||||
statusIcon.innerHTML = '☒';
|
||||
statusIcon.style.color = 'red';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// fill in form
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
|
||||
function onGot(item) {
|
||||
if (!item.access) {
|
||||
console.log("no access details found");
|
||||
setStatusIcon(false);
|
||||
return
|
||||
}
|
||||
let { url, port } = item.access;
|
||||
let fullUrl = url;
|
||||
if (!(url.startsWith('http://') && port === '80')) {
|
||||
fullUrl += `:${port}`;
|
||||
}
|
||||
document.getElementById("full-url").value = fullUrl;
|
||||
document.getElementById("api-key").value = item.access.apiKey;
|
||||
pingBackend();
|
||||
addUrl(item.access);
|
||||
};
|
||||
|
||||
function setCookiesOptions(result) {
|
||||
if (!result.sendCookies || result.sendCookies.checked === false) {
|
||||
console.log("sync cookies not set");
|
||||
return
|
||||
}
|
||||
console.log("set options: " + JSON.stringify(result));
|
||||
setCookieState();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
function onGot(item) {
|
||||
if (!item.access) {
|
||||
console.log('no access details found');
|
||||
setStatusIcon(false);
|
||||
return;
|
||||
}
|
||||
let { url, port } = item.access;
|
||||
let fullUrl = url;
|
||||
if (!(url.startsWith('http://') && port === '80')) {
|
||||
fullUrl += `:${port}`;
|
||||
}
|
||||
document.getElementById('full-url').value = fullUrl;
|
||||
document.getElementById('api-key').value = item.access.apiKey;
|
||||
pingBackend();
|
||||
addUrl(item.access);
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
};
|
||||
function setCookiesOptions(result) {
|
||||
if (!result.sendCookies || result.sendCookies.checked === false) {
|
||||
console.log('sync cookies not set');
|
||||
return;
|
||||
}
|
||||
console.log('set options: ' + JSON.stringify(result));
|
||||
setCookieState();
|
||||
}
|
||||
|
||||
browserType.storage.local.get("access", function(result) {
|
||||
onGot(result)
|
||||
});
|
||||
browserType.storage.local.get('access', function (result) {
|
||||
onGot(result);
|
||||
});
|
||||
|
||||
browserType.storage.local.get("sendCookies", function(result) {
|
||||
setCookiesOptions(result)
|
||||
})
|
||||
|
||||
})
|
||||
browserType.storage.local.get('sendCookies', function (result) {
|
||||
setCookiesOptions(result);
|
||||
});
|
||||
});
|
||||
|
@ -2,23 +2,24 @@
|
||||
content script running on youtube.com
|
||||
*/
|
||||
|
||||
let browserType = getBrowser();
|
||||
'use strict';
|
||||
|
||||
let browserType = getBrowser();
|
||||
|
||||
// boilerplate to dedect browser type api
|
||||
function getBrowser() {
|
||||
if (typeof chrome !== "undefined") {
|
||||
if (typeof browser !== "undefined") {
|
||||
console.log("detected firefox");
|
||||
return browser;
|
||||
} else {
|
||||
console.log("detected chrome");
|
||||
return chrome;
|
||||
}
|
||||
if (typeof chrome !== 'undefined') {
|
||||
if (typeof browser !== 'undefined') {
|
||||
console.log('detected firefox');
|
||||
return browser;
|
||||
} else {
|
||||
console.log("failed to dedect browser");
|
||||
throw "browser detection error"
|
||||
};
|
||||
console.log('detected chrome');
|
||||
return chrome;
|
||||
}
|
||||
} else {
|
||||
console.log('failed to dedect browser');
|
||||
throw 'browser detection error';
|
||||
}
|
||||
}
|
||||
|
||||
const downloadIcon = `<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
@ -100,308 +101,295 @@ viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="pres
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`
|
||||
|
||||
</svg>`;
|
||||
|
||||
function buildButtonDiv() {
|
||||
var buttonDiv = document.createElement("div");
|
||||
buttonDiv.setAttribute("id", "ta-channel-button");
|
||||
let buttonDiv = document.createElement('div');
|
||||
buttonDiv.setAttribute('id', 'ta-channel-button');
|
||||
|
||||
Object.assign(buttonDiv.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#00202f",
|
||||
color: "#fff",
|
||||
fontSize: "14px",
|
||||
padding: "5px",
|
||||
margin: "0 5px",
|
||||
borderRadius: "8px",
|
||||
});
|
||||
return buttonDiv
|
||||
Object.assign(buttonDiv.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#00202f',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
padding: '5px',
|
||||
margin: '0 5px',
|
||||
borderRadius: '8px',
|
||||
});
|
||||
return buttonDiv;
|
||||
}
|
||||
|
||||
function buildSubLink(channelContainer) {
|
||||
var subLink = document.createElement("span");
|
||||
subLink.innerText = "Subscribe";
|
||||
subLink.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
var currentLocation = window.location.href;
|
||||
console.log("subscribe to: " + currentLocation);
|
||||
sendUrl(currentLocation, "subscribe", subLink);
|
||||
});
|
||||
subLink.addEventListener("mouseover", e => {
|
||||
let subText
|
||||
if (window.location.pathname == "/watch") {
|
||||
var currentLocation = window.location.href;
|
||||
subText = currentLocation;
|
||||
} else {
|
||||
subText = channelContainer.querySelector("#text").textContent;
|
||||
};
|
||||
let subLink = document.createElement('span');
|
||||
subLink.innerText = 'Subscribe';
|
||||
subLink.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
let currentLocation = window.location.href;
|
||||
console.log('subscribe to: ' + currentLocation);
|
||||
sendUrl(currentLocation, 'subscribe', subLink);
|
||||
});
|
||||
subLink.addEventListener('mouseover', e => {
|
||||
let subText;
|
||||
if (window.location.pathname === '/watch') {
|
||||
let currentLocation = window.location.href;
|
||||
subText = currentLocation;
|
||||
} else {
|
||||
subText = channelContainer.querySelector('#text').textContent;
|
||||
}
|
||||
|
||||
e.target.title = "TA Subscribe: " + subText;
|
||||
});
|
||||
Object.assign(subLink.style, {
|
||||
padding: "5px",
|
||||
cursor: "pointer",
|
||||
});
|
||||
|
||||
return subLink
|
||||
e.target.title = 'TA Subscribe: ' + subText;
|
||||
});
|
||||
Object.assign(subLink.style, {
|
||||
padding: '5px',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
|
||||
return subLink;
|
||||
}
|
||||
|
||||
function buildSpacer() {
|
||||
var spacer = document.createElement("span");
|
||||
spacer.innerText = "|";
|
||||
let spacer = document.createElement('span');
|
||||
spacer.innerText = '|';
|
||||
|
||||
return spacer
|
||||
return spacer;
|
||||
}
|
||||
|
||||
function buildDlLink(channelContainer) {
|
||||
var dlLink = document.createElement("span");
|
||||
dlLink.innerHTML = downloadIcon;
|
||||
let dlLink = document.createElement('span');
|
||||
dlLink.innerHTML = downloadIcon;
|
||||
|
||||
dlLink.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
var currentLocation = window.location.href;
|
||||
console.log("download: " + currentLocation)
|
||||
sendUrl(currentLocation, "download", dlLink);
|
||||
});
|
||||
dlLink.addEventListener("mouseover", e => {
|
||||
let subText
|
||||
if (window.location.pathname == "/watch") {
|
||||
var currentLocation = window.location.href;
|
||||
subText = currentLocation;
|
||||
} else {
|
||||
subText = channelContainer.querySelector("#text").textContent;
|
||||
};
|
||||
dlLink.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
let currentLocation = window.location.href;
|
||||
console.log('download: ' + currentLocation);
|
||||
sendUrl(currentLocation, 'download', dlLink);
|
||||
});
|
||||
dlLink.addEventListener('mouseover', e => {
|
||||
let subText;
|
||||
if (window.location.pathname === '/watch') {
|
||||
let currentLocation = window.location.href;
|
||||
subText = currentLocation;
|
||||
} else {
|
||||
subText = channelContainer.querySelector('#text').textContent;
|
||||
}
|
||||
|
||||
e.target.title = "TA Download: " + subText;
|
||||
});
|
||||
Object.assign(dlLink.style, {
|
||||
filter: "invert()",
|
||||
width: "20px",
|
||||
padding: "0 5px",
|
||||
cursor: "pointer",
|
||||
});
|
||||
e.target.title = 'TA Download: ' + subText;
|
||||
});
|
||||
Object.assign(dlLink.style, {
|
||||
filter: 'invert()',
|
||||
width: '20px',
|
||||
padding: '0 5px',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
|
||||
return dlLink
|
||||
return dlLink;
|
||||
}
|
||||
|
||||
|
||||
function buildChannelButton(channelContainer) {
|
||||
let buttonDiv = buildButtonDiv();
|
||||
|
||||
var buttonDiv = buildButtonDiv()
|
||||
|
||||
var subLink = buildSubLink(channelContainer);
|
||||
buttonDiv.appendChild(subLink)
|
||||
let subLink = buildSubLink(channelContainer);
|
||||
buttonDiv.appendChild(subLink);
|
||||
|
||||
var spacer = buildSpacer()
|
||||
buttonDiv.appendChild(spacer);
|
||||
let spacer = buildSpacer();
|
||||
buttonDiv.appendChild(spacer);
|
||||
|
||||
var dlLink = buildDlLink(channelContainer)
|
||||
buttonDiv.appendChild(dlLink);
|
||||
|
||||
return buttonDiv
|
||||
let dlLink = buildDlLink(channelContainer);
|
||||
buttonDiv.appendChild(dlLink);
|
||||
|
||||
return buttonDiv;
|
||||
}
|
||||
|
||||
function getChannelContainers() {
|
||||
var nodes = document.querySelectorAll("#inner-header-container, #owner");
|
||||
return nodes
|
||||
let nodes = document.querySelectorAll('#inner-header-container, #owner');
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function getThubnailContainers() {
|
||||
var nodes = document.querySelectorAll('#thumbnail');
|
||||
return nodes
|
||||
let nodes = document.querySelectorAll('#thumbnail');
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function buildVideoButton(thumbContainer) {
|
||||
var thumbLink = thumbContainer?.href;
|
||||
if (!thumbLink) return;
|
||||
if (thumbLink.includes('list=') || thumbLink.includes('/shorts/')) return;
|
||||
let thumbLink = thumbContainer?.href;
|
||||
if (!thumbLink) return;
|
||||
if (thumbLink.includes('list=') || thumbLink.includes('/shorts/')) return;
|
||||
|
||||
var dlButton = document.createElement("a");
|
||||
dlButton.setAttribute("id", "ta-video-button");
|
||||
dlButton.href = '#'
|
||||
|
||||
dlButton.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
let videoLink = thumbContainer.href;
|
||||
console.log("download: " + videoLink);
|
||||
sendUrl(videoLink, "download", dlButton)
|
||||
});
|
||||
dlButton.addEventListener('mouseover', e => {
|
||||
Object.assign(dlButton.style, {
|
||||
opacity: 1,
|
||||
});
|
||||
let videoTitle = thumbContainer.href;
|
||||
e.target.title = "TA download: " + videoTitle;
|
||||
})
|
||||
dlButton.addEventListener('mouseout', e => {
|
||||
Object.assign(dlButton.style, {
|
||||
opacity: 0,
|
||||
});
|
||||
})
|
||||
let dlButton = document.createElement('a');
|
||||
dlButton.setAttribute('id', 'ta-video-button');
|
||||
dlButton.href = '#';
|
||||
|
||||
dlButton.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
let videoLink = thumbContainer.href;
|
||||
console.log('download: ' + videoLink);
|
||||
sendUrl(videoLink, 'download', dlButton);
|
||||
});
|
||||
dlButton.addEventListener('mouseover', e => {
|
||||
Object.assign(dlButton.style, {
|
||||
display: "flex",
|
||||
position: "absolute",
|
||||
top: "5px",
|
||||
left: "5px",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#00202f",
|
||||
color: "#fff",
|
||||
fontSize: "1.4rem",
|
||||
textDecoration: "none",
|
||||
borderRadius: "8px",
|
||||
cursor: "pointer",
|
||||
opacity: 0,
|
||||
transition: "all 0.3s ease 0.3s",
|
||||
opacity: 1,
|
||||
});
|
||||
|
||||
var dlIcon = document.createElement("span");
|
||||
dlIcon.innerHTML = downloadIcon;
|
||||
Object.assign(dlIcon.style, {
|
||||
filter: "invert()",
|
||||
width: "20px",
|
||||
padding: "10px 13px",
|
||||
let videoTitle = thumbContainer.href;
|
||||
e.target.title = 'TA download: ' + videoTitle;
|
||||
});
|
||||
dlButton.addEventListener('mouseout', () => {
|
||||
Object.assign(dlButton.style, {
|
||||
opacity: 0,
|
||||
});
|
||||
|
||||
dlButton.appendChild(dlIcon);
|
||||
});
|
||||
|
||||
return dlButton
|
||||
Object.assign(dlButton.style, {
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
left: '5px',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#00202f',
|
||||
color: '#fff',
|
||||
fontSize: '1.4rem',
|
||||
textDecoration: 'none',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
opacity: 0,
|
||||
transition: 'all 0.3s ease 0.3s',
|
||||
});
|
||||
|
||||
let dlIcon = document.createElement('span');
|
||||
dlIcon.innerHTML = downloadIcon;
|
||||
Object.assign(dlIcon.style, {
|
||||
filter: 'invert()',
|
||||
width: '20px',
|
||||
padding: '10px 13px',
|
||||
});
|
||||
|
||||
dlButton.appendChild(dlIcon);
|
||||
|
||||
return dlButton;
|
||||
}
|
||||
|
||||
|
||||
// fix positioning of #owner div to fit new button
|
||||
function adjustOwner(channelContainer) {
|
||||
let sponsorButton = channelContainer.querySelector('#sponsor-button');
|
||||
if (sponsorButton === null) {
|
||||
return channelContainer
|
||||
}
|
||||
let sponsorButton = channelContainer.querySelector('#sponsor-button');
|
||||
if (sponsorButton === null) {
|
||||
return channelContainer;
|
||||
}
|
||||
|
||||
let variableMinWidth
|
||||
if (sponsorButton.hasChildNodes()) {
|
||||
variableMinWidth = '140px';
|
||||
} else {
|
||||
variableMinWidth = '45px';
|
||||
}
|
||||
let variableMinWidth;
|
||||
if (sponsorButton.hasChildNodes()) {
|
||||
variableMinWidth = '140px';
|
||||
} else {
|
||||
variableMinWidth = '45px';
|
||||
}
|
||||
|
||||
Object.assign(channelContainer.firstElementChild.style, {
|
||||
minWidth: variableMinWidth
|
||||
})
|
||||
Object.assign(channelContainer.style, {
|
||||
minWidth: 'calc(40% + 50px)'
|
||||
})
|
||||
return channelContainer
|
||||
Object.assign(channelContainer.firstElementChild.style, {
|
||||
minWidth: variableMinWidth,
|
||||
});
|
||||
Object.assign(channelContainer.style, {
|
||||
minWidth: 'calc(40% + 50px)',
|
||||
});
|
||||
return channelContainer;
|
||||
}
|
||||
|
||||
|
||||
function ensureTALinks() {
|
||||
let channelContainerNodes = getChannelContainers();
|
||||
|
||||
var channelContainerNodes = getChannelContainers()
|
||||
for (let channelContainer of channelContainerNodes) {
|
||||
channelContainer = adjustOwner(channelContainer);
|
||||
if (channelContainer.hasTA) continue;
|
||||
let channelButton = buildChannelButton(channelContainer);
|
||||
channelContainer.appendChild(channelButton);
|
||||
channelContainer.hasTA = true;
|
||||
}
|
||||
|
||||
for (var channelContainer of channelContainerNodes) {
|
||||
channelContainer = adjustOwner(channelContainer);
|
||||
if (channelContainer.hasTA) continue;
|
||||
var channelButton = buildChannelButton(channelContainer);
|
||||
channelContainer.appendChild(channelButton);
|
||||
channelContainer.hasTA = true;
|
||||
}
|
||||
|
||||
var thumbContainerNodes = getThubnailContainers();
|
||||
|
||||
for (var thumbContainer of thumbContainerNodes) {
|
||||
if (thumbContainer.hasTA) continue;
|
||||
var videoButton = buildVideoButton(thumbContainer);
|
||||
if (videoButton == null) continue;
|
||||
thumbContainer.parentElement.appendChild(videoButton);
|
||||
thumbContainer.hasTA = true;
|
||||
}
|
||||
let thumbContainerNodes = getThubnailContainers();
|
||||
|
||||
for (let thumbContainer of thumbContainerNodes) {
|
||||
if (thumbContainer.hasTA) continue;
|
||||
let videoButton = buildVideoButton(thumbContainer);
|
||||
if (videoButton == null) continue;
|
||||
thumbContainer.parentElement.appendChild(videoButton);
|
||||
thumbContainer.hasTA = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function buttonError(button) {
|
||||
let buttonSpan = button.querySelector("span");
|
||||
if (buttonSpan === null) {
|
||||
buttonSpan = button
|
||||
}
|
||||
buttonSpan.style.filter = "invert(19%) sepia(93%) saturate(7472%) hue-rotate(359deg) brightness(105%) contrast(113%)";
|
||||
buttonSpan.style.color = "red";
|
||||
let buttonSpan = button.querySelector('span');
|
||||
if (buttonSpan === null) {
|
||||
buttonSpan = button;
|
||||
}
|
||||
buttonSpan.style.filter =
|
||||
'invert(19%) sepia(93%) saturate(7472%) hue-rotate(359deg) brightness(105%) contrast(113%)';
|
||||
buttonSpan.style.color = 'red';
|
||||
|
||||
button.style.opacity = 1;
|
||||
button.addEventListener('mouseout', e => {
|
||||
Object.assign(button.style, {
|
||||
opacity: 1,
|
||||
});
|
||||
})
|
||||
button.style.opacity = 1;
|
||||
button.addEventListener('mouseout', () => {
|
||||
Object.assign(button.style, {
|
||||
opacity: 1,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function buttonSuccess(button) {
|
||||
let buttonSpan = button.querySelector("span");
|
||||
if (buttonSpan === null) {
|
||||
buttonSpan = button;
|
||||
}
|
||||
if (buttonSpan.innerHTML === "Subscribe") {
|
||||
buttonSpan.innerHTML = "Success";
|
||||
setTimeout(() => {
|
||||
buttonSpan.innerHTML = "Subscribe";
|
||||
}, 2000);
|
||||
} else {
|
||||
buttonSpan.innerHTML = checkmarkIcon;
|
||||
setTimeout(() => {
|
||||
buttonSpan.innerHTML = downloadIcon;
|
||||
}, 2000);
|
||||
}
|
||||
let buttonSpan = button.querySelector('span');
|
||||
if (buttonSpan === null) {
|
||||
buttonSpan = button;
|
||||
}
|
||||
if (buttonSpan.innerHTML === 'Subscribe') {
|
||||
buttonSpan.innerHTML = 'Success';
|
||||
setTimeout(() => {
|
||||
buttonSpan.innerHTML = 'Subscribe';
|
||||
}, 2000);
|
||||
} else {
|
||||
buttonSpan.innerHTML = checkmarkIcon;
|
||||
setTimeout(() => {
|
||||
buttonSpan.innerHTML = downloadIcon;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function sendUrl(url, action, button) {
|
||||
|
||||
function handleResponse(message) {
|
||||
console.log("sendUrl response: " + JSON.stringify(message));
|
||||
if (message === null || message.detail === "Invalid token.") {
|
||||
buttonError(button);
|
||||
} else {
|
||||
buttonSuccess(button);
|
||||
}
|
||||
function handleResponse(message) {
|
||||
console.log('sendUrl response: ' + JSON.stringify(message));
|
||||
if (message === null || message.detail === 'Invalid token.') {
|
||||
buttonError(button);
|
||||
} else {
|
||||
buttonSuccess(button);
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
console.log("error");
|
||||
console.log(JSON.stringify(error));
|
||||
}
|
||||
function handleError(error) {
|
||||
console.log('error');
|
||||
console.log(JSON.stringify(error));
|
||||
}
|
||||
|
||||
let payload = {
|
||||
"youtube": {
|
||||
"url": url,
|
||||
"action": action,
|
||||
}
|
||||
}
|
||||
let payload = {
|
||||
youtube: {
|
||||
url: url,
|
||||
action: action,
|
||||
},
|
||||
};
|
||||
|
||||
console.log("youtube link: " + JSON.stringify(payload));
|
||||
|
||||
let sending = browserType.runtime.sendMessage(payload);
|
||||
sending.then(handleResponse, handleError);
|
||||
|
||||
};
|
||||
console.log('youtube link: ' + JSON.stringify(payload));
|
||||
|
||||
let sending = browserType.runtime.sendMessage(payload);
|
||||
sending.then(handleResponse, handleError);
|
||||
}
|
||||
|
||||
let throttleBlock;
|
||||
const throttle = (callback, time) => {
|
||||
if (throttleBlock) return;
|
||||
throttleBlock = true;
|
||||
setTimeout(() => {
|
||||
callback();
|
||||
throttleBlock = false;
|
||||
}, time);
|
||||
if (throttleBlock) return;
|
||||
throttleBlock = true;
|
||||
setTimeout(() => {
|
||||
callback();
|
||||
throttleBlock = false;
|
||||
}, time);
|
||||
};
|
||||
|
||||
let observer = new MutationObserver(list => {
|
||||
if (list.some(i => i.type === 'childList' && i.addedNodes.length > 0)) {
|
||||
throttle(ensureTALinks, 700);
|
||||
}
|
||||
if (list.some(i => i.type === 'childList' && i.addedNodes.length > 0)) {
|
||||
throttle(ensureTALinks, 700);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, { attributes: false, childList: true, subtree: true });
|
||||
|
1960
package-lock.json
generated
Normal file
1960
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "tubearchivist-browser-extension",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint 'extension/**/*.js'",
|
||||
"format": "prettier --write 'extension/**/*.js'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.26.0",
|
||||
"prettier": "^2.7.1",
|
||||
"eslint-config-prettier": "^8.5.0"
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 100
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user