mirror of
https://github.com/tubearchivist/browser-extension.git
synced 2025-04-20 10:20:11 +00:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
017b789fe2 | |||
f9bef73540 | |||
b07acde00e | |||
60160b293b | |||
0d24520017 | |||
5f53ae52d6 | |||
c867da85f5 | |||
c8382d1d7b | |||
2703555723 | |||
68bf0fa5d7 | |||
53c5e7f077 | |||
0218d782ce | |||
c17b0172d9 | |||
c18ccd6e78 | |||
cb4082362b | |||
b28efc7960 | |||
492db3ed3c | |||
|
ea12e6bc08 | ||
bcb8f545e8 | |||
b36ac337cf | |||
|
f5152a4717 | ||
af61f2a4ab | |||
|
967a52881b | ||
d3f01b372a | |||
1d27545409 | |||
7b40dc44c2 |
13
README.md
13
README.md
@ -2,8 +2,8 @@
|
||||
|
||||
<h1 align="center">Browser Extension for Tube Archivist</h1>
|
||||
<div align="center">
|
||||
<a href="https://www.tilefy.me" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-firefox.png" alt="tubearchivist-firefox" title="TA Companion Firefox users" height="50" width="190"/></a>
|
||||
<a href="https://www.tilefy.me" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-chrome.png" alt="tubearchivist-chrome" title="TA Companion Chrome users" height="50" width="190"/></a>
|
||||
<a href="https://addons.mozilla.org/addon/tubearchivist-companion/" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-firefox.png" alt="tubearchivist-firefox" title="TA Companion Firefox users" height="50" width="190"/></a>
|
||||
<a href="https://chrome.google.com/webstore/detail/tubearchivist-companion/jjnkmicfnfojkkgobdfeieblocadmcie" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-chrome.png" alt="tubearchivist-chrome" title="TA Companion Chrome users" height="50" width="190"/></a>
|
||||
</div>
|
||||
|
||||
## Core Functionality
|
||||
@ -45,12 +45,14 @@ After a new release here on GitHub, you'll get updates automatically in your bro
|
||||
|
||||
## Setup
|
||||
- **URL**: This is where your Tube Archivist instance is located. Can be a host name or an IP address. Add the port if needed at the end, e.g. `:8000`.
|
||||
- **API key**: You can find your API key on the settings page of your Tube Archivist instance.
|
||||
- **API key**: You can find your API key on the settings page (Settings -> Application -> Integrations section -> API token) of your Tube Archivist instance.
|
||||
|
||||
A green checkmark will appear next to the *Save* button if your connection is working.
|
||||
|
||||
## Options
|
||||
- **Sync YouTube cookies**: Send your cookies to TubeArchivist to use for yt-dlp requests.
|
||||
- **Continuous Cookie Sync**: Automatically and continuously update the cookie on change.
|
||||
- **Copy Now**: Copy the cookie now to TA.
|
||||
- **Show Cookie**: Show the cookie on click, for copy paste.
|
||||
- **Autostart**: Autostart and prioritize videos send from this extension.
|
||||
|
||||
## Test this extension
|
||||
@ -70,6 +72,9 @@ Symlink/copy the correct manifest file for your browser to the expected location
|
||||
- Open the folder containing the *manifest.json* file.
|
||||
- Click on *Service Worker* to open the dev tools at background.js.
|
||||
|
||||
Note:
|
||||
- If you are running your TA dev setup outside of the container, you need to point the URL to the backend and _not_ the frontend. E.g. localhost:8000 and not localhost:3000.
|
||||
|
||||
## Compatibility
|
||||
- Verify that you are running the [latest version](https://github.com/tubearchivist/tubearchivist/releases/latest) of Tube Archivist as the API is under development and will change.
|
||||
- For testing this extension between releases, use the *unstable* builds of Tube Archivist, only for your testing environment.
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [[ $(basename "$(pwd)") != 'tubearchivist_browserextension' ]]; then
|
||||
echo 'not in tubearchivist_browserextension folder'
|
||||
if [[ $(basename "$(pwd)") != 'browser-extension' ]]; then
|
||||
echo 'not in browser-extension folder'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -47,7 +47,7 @@ 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)}`);
|
||||
if (!path.endsWith('cookie/')) console.log(`${method}: ${JSON.stringify(payload)}`);
|
||||
|
||||
try {
|
||||
const rawResponse = await fetch(url, {
|
||||
@ -77,7 +77,7 @@ async function getAccess() {
|
||||
|
||||
// check if cookie is valid
|
||||
async function getCookieState() {
|
||||
const path = 'api/cookie/';
|
||||
const path = 'api/appsettings/cookie/';
|
||||
let response = await sendGet(path);
|
||||
console.log('cookie state: ' + JSON.stringify(response));
|
||||
|
||||
@ -138,54 +138,22 @@ async function subscribe(url, subscribed) {
|
||||
async function videoExists(id) {
|
||||
const path = `api/video/${id}/`;
|
||||
let response = await sendGet(path);
|
||||
if (!response.data) return false;
|
||||
if (response?.error) return false;
|
||||
let access = await getAccess();
|
||||
return new URL(`video/${id}/`, `${access.url}:${access.port}/`).href;
|
||||
}
|
||||
|
||||
async function getChannelCache() {
|
||||
let cache = await browserType.storage.local.get('cache');
|
||||
if (cache.cache) return cache;
|
||||
return { cache: {} };
|
||||
}
|
||||
|
||||
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) {
|
||||
const path = `api/channel/search/?q=${channelHandle}`;
|
||||
let response = await sendGet(path);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
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;
|
||||
const path = `api/channel/search/?q=${channelHandle}`;
|
||||
try {
|
||||
return await sendGet(path);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function cookieStr(cookieLines) {
|
||||
const path = 'api/cookie/';
|
||||
const path = 'api/appsettings/cookie/';
|
||||
let payload = {
|
||||
cookie: cookieLines.join('\n'),
|
||||
};
|
||||
@ -195,9 +163,12 @@ async function cookieStr(cookieLines) {
|
||||
}
|
||||
|
||||
function buildCookieLine(cookie) {
|
||||
// 2nd argument controls subdomains, and must match leading dot in domain
|
||||
let includeSubdomains = cookie.domain.startsWith('.') ? 'TRUE' : 'FALSE';
|
||||
|
||||
return [
|
||||
cookie.domain,
|
||||
'TRUE',
|
||||
includeSubdomains,
|
||||
cookie.path,
|
||||
cookie.httpOnly.toString().toUpperCase(),
|
||||
Math.trunc(cookie.expirationDate) || 0,
|
||||
@ -206,10 +177,8 @@ function buildCookieLine(cookie) {
|
||||
].join('\t');
|
||||
}
|
||||
|
||||
async function sendCookies() {
|
||||
console.log('function sendCookies');
|
||||
async function getCookieLines() {
|
||||
const acceptableDomains = ['.youtube.com', 'youtube.com', 'www.youtube.com'];
|
||||
|
||||
let cookieStores = await browserType.cookies.getAllCookieStores();
|
||||
let cookieLines = [
|
||||
'# Netscape HTTP Cookie File',
|
||||
@ -229,12 +198,46 @@ async function sendCookies() {
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieLines;
|
||||
}
|
||||
|
||||
async function sendCookies() {
|
||||
console.log('function sendCookies');
|
||||
let cookieLines = await getCookieLines();
|
||||
let response = cookieStr(cookieLines);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
let listenerEnabled = false;
|
||||
let isThrottled = false;
|
||||
|
||||
async function handleContinuousCookie(checked) {
|
||||
if (checked === true) {
|
||||
browserType.cookies.onChanged.addListener(onCookieChange);
|
||||
listenerEnabled = true;
|
||||
console.log('Cookie listener enabled');
|
||||
} else {
|
||||
browserType.cookies.onChanged.removeListener(onCookieChange);
|
||||
listenerEnabled = false;
|
||||
console.log('Cookie listener disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function onCookieChange(changeInfo) {
|
||||
if (!isThrottled) {
|
||||
isThrottled = true;
|
||||
|
||||
console.log('Cookie event detected:', changeInfo);
|
||||
|
||||
sendCookies();
|
||||
|
||||
setTimeout(() => {
|
||||
isThrottled = false;
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
process and return message if needed
|
||||
the following messages are supported:
|
||||
@ -242,6 +245,8 @@ type Message =
|
||||
| { type: 'verify' }
|
||||
| { type: 'cookieState' }
|
||||
| { type: 'sendCookie' }
|
||||
| { type: 'getCookieLines' }
|
||||
| { type: 'continuousSync', checked: boolean }
|
||||
| { type: 'download', url: string }
|
||||
| { type: 'subscribe', url: string }
|
||||
| { type: 'unsubscribe', url: string }
|
||||
@ -265,6 +270,12 @@ function handleMessage(request, sender, sendResponse) {
|
||||
case 'sendCookie': {
|
||||
return await sendCookies();
|
||||
}
|
||||
case 'getCookieLines': {
|
||||
return await getCookieLines();
|
||||
}
|
||||
case 'continuousSync': {
|
||||
return await handleContinuousCookie(request.checked);
|
||||
}
|
||||
case 'download': {
|
||||
return await download(request.url);
|
||||
}
|
||||
@ -272,8 +283,8 @@ function handleMessage(request, sender, sendResponse) {
|
||||
return await subscribe(request.url, true);
|
||||
}
|
||||
case 'unsubscribe': {
|
||||
let channelId = await getChannelId(request.url);
|
||||
return await subscribe(channelId, false);
|
||||
let channel = await getChannel(request.url);
|
||||
return await subscribe(channel.channel_id, false);
|
||||
}
|
||||
case 'videoExists': {
|
||||
return await videoExists(request.videoId);
|
||||
@ -290,7 +301,7 @@ function handleMessage(request, sender, sendResponse) {
|
||||
})()
|
||||
.then(value => sendResponse({ success: true, value }))
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
console.log(e);
|
||||
let message = e?.message ?? e;
|
||||
if (message === 'Failed to fetch') {
|
||||
// chrome's error message for failed `fetch` is not very user-friendly
|
||||
@ -302,3 +313,9 @@ function handleMessage(request, sender, sendResponse) {
|
||||
}
|
||||
|
||||
browserType.runtime.onMessage.addListener(handleMessage);
|
||||
|
||||
browserType.runtime.onStartup.addListener(() => {
|
||||
browserType.storage.local.get('continuousSync', data => {
|
||||
handleContinuousCookie(data?.continuousSync?.checked || false);
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,7 @@
|
||||
<a href="#" id="ta-url" target="_blank">
|
||||
<img src="/images/logo.png" alt="ta-logo">
|
||||
</a>
|
||||
<span>v0.3.0</span>
|
||||
<span>v0.4.1</span>
|
||||
</div>
|
||||
<hr>
|
||||
<form class="login-form">
|
||||
@ -28,12 +28,21 @@
|
||||
</div>
|
||||
<div id="error-out"></div>
|
||||
<hr>
|
||||
<p>Options:</p>
|
||||
<p>Cookies:</p>
|
||||
<div class="options">
|
||||
<div>
|
||||
<input type="checkbox" id="sendCookies" name="sendCookies">
|
||||
<span>Sync YouTube cookies</span><span id="sendCookiesStatus"></span>
|
||||
<p>TA cookies: <span id="sendCookiesStatus" /></p>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="continuous-sync" name="continuous-sync">
|
||||
<span>Continuous Cookie Sync</span>
|
||||
</div>
|
||||
<button id="sendCookies">Copy Now</button>
|
||||
<button id="showCookies">Show Cookie</button><br>
|
||||
<textarea id="cookieLinesResponse" readonly></textarea>
|
||||
</div>
|
||||
<p>Download:</p>
|
||||
<div class="options">
|
||||
<div>
|
||||
<input type="checkbox" id="autostart" name="autostart">
|
||||
<span>Autostart Downloads</span>
|
||||
@ -46,7 +55,7 @@
|
||||
<img src="/images/social/reddit.svg" alt="reddit-icon"></a>
|
||||
<a href="https://www.tubearchivist.com/discord" target="_blank">
|
||||
<img src="/images/social/discord.svg" alt="discord-icon"></a>
|
||||
<a href="https://github.com/tubearchivist/tubearchivist" target="_blank">
|
||||
<a href="https://github.com/tubearchivist/browser-extension/" target="_blank">
|
||||
<img src="/images/social/github.svg" alt="github-icon">
|
||||
</a>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "TubeArchivist Companion",
|
||||
"description": "Interact with your selfhosted TA server.",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.1",
|
||||
"icons": {
|
||||
"48": "/images/icon.png",
|
||||
"128": "/images/icon128.png"
|
||||
|
@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "TubeArchivist Companion",
|
||||
"description": "Interact with your selfhosted TA server.",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.1",
|
||||
"icons": {
|
||||
"128": "/images/icon128.png"
|
||||
},
|
||||
|
@ -78,6 +78,16 @@ document.getElementById('sendCookies').addEventListener('click', function () {
|
||||
sendCookie();
|
||||
});
|
||||
|
||||
// show cookies
|
||||
document.getElementById('showCookies').addEventListener('click', function () {
|
||||
showCookies();
|
||||
});
|
||||
|
||||
// continuous sync
|
||||
document.getElementById('continuous-sync').addEventListener('click', function () {
|
||||
toggleContinuousSync();
|
||||
});
|
||||
|
||||
// autostart
|
||||
document.getElementById('autostart').addEventListener('click', function () {
|
||||
toggleAutostart();
|
||||
@ -103,8 +113,8 @@ function sendCookie() {
|
||||
|
||||
function handleResponse(message) {
|
||||
console.log('handle cookie response: ' + JSON.stringify(message));
|
||||
let cookie_validated = message.cookie_validated;
|
||||
document.getElementById('sendCookiesStatus').innerText = 'validated: ' + cookie_validated;
|
||||
let validattionMessage = `enabled, last verified ${message.validated_str}`;
|
||||
document.getElementById('sendCookiesStatus').innerText = validattionMessage;
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
@ -112,20 +122,44 @@ function sendCookie() {
|
||||
setError(error);
|
||||
}
|
||||
|
||||
let checked = document.getElementById('sendCookies').checked;
|
||||
let sending = sendMessage({ type: 'sendCookie' });
|
||||
sending.then(handleResponse, handleError);
|
||||
}
|
||||
|
||||
function showCookies() {
|
||||
console.log('popup show cookies');
|
||||
const textArea = document.getElementById('cookieLinesResponse');
|
||||
|
||||
function handleResponse(message) {
|
||||
textArea.value = message.join('\n');
|
||||
textArea.style.display = 'initial';
|
||||
}
|
||||
function handleError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
|
||||
if (textArea.value) {
|
||||
textArea.value = '';
|
||||
textArea.style.display = 'none';
|
||||
document.getElementById('showCookies').textContent = 'Show Cookie';
|
||||
} else {
|
||||
let sending = sendMessage({ type: 'getCookieLines' });
|
||||
sending.then(handleResponse, handleError);
|
||||
document.getElementById('showCookies').textContent = 'Hide Cookie';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleContinuousSync() {
|
||||
const checked = document.getElementById('continuous-sync').checked;
|
||||
let toStore = {
|
||||
sendCookies: {
|
||||
continuousSync: {
|
||||
checked: checked,
|
||||
},
|
||||
};
|
||||
browserType.storage.local.set(toStore, function () {
|
||||
console.log('stored option: ' + JSON.stringify(toStore));
|
||||
});
|
||||
if (checked === false) {
|
||||
return;
|
||||
}
|
||||
let sending = sendMessage({ type: 'sendCookie' });
|
||||
sending.then(handleResponse, handleError);
|
||||
sendMessage({ type: 'continuousSync', checked });
|
||||
}
|
||||
|
||||
function toggleAutostart() {
|
||||
@ -141,7 +175,7 @@ function toggleAutostart() {
|
||||
}
|
||||
|
||||
// send ping message to TA backend
|
||||
function pingBackend() {
|
||||
async function pingBackend() {
|
||||
clearError();
|
||||
clearTempLocalStorage();
|
||||
function handleResponse() {
|
||||
@ -170,9 +204,14 @@ function setCookieState() {
|
||||
clearError();
|
||||
function handleResponse(message) {
|
||||
console.log(message);
|
||||
document.getElementById('sendCookies').checked = message.cookie_enabled;
|
||||
if (message.validated_str) {
|
||||
document.getElementById('sendCookiesStatus').innerText = message.validated_str;
|
||||
if (!message.cookie_enabled) {
|
||||
document.getElementById('sendCookiesStatus').innerText = 'disabled';
|
||||
} else {
|
||||
let validattionMessage = 'enabled';
|
||||
if (message.validated_str) {
|
||||
validattionMessage += `, last verified ${message.validated_str}`;
|
||||
}
|
||||
document.getElementById('sendCookiesStatus').innerText = validattionMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,7 +240,7 @@ function setStatusIcon(connected) {
|
||||
|
||||
// fill in form
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
function onGot(item) {
|
||||
async function onGot(item) {
|
||||
if (!item.access) {
|
||||
console.log('no access details found');
|
||||
if (item.popupFullUrl != null && fullUrlInput.value === '') {
|
||||
@ -222,18 +261,19 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
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();
|
||||
}
|
||||
|
||||
function setAutostartOption(result) {
|
||||
async function setContinuousCookiesOptions(result) {
|
||||
if (!result.continuousSync || result.continuousSync.checked === false) {
|
||||
console.log('continuous cookie sync not set');
|
||||
return;
|
||||
}
|
||||
console.log('set options: ' + JSON.stringify(result));
|
||||
document.getElementById('continuous-sync').checked = true;
|
||||
}
|
||||
|
||||
async function setAutostartOption(result) {
|
||||
console.log(result);
|
||||
if (!result.autostart || result.autostart.checked === false) {
|
||||
console.log('autostart not set');
|
||||
@ -247,8 +287,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
onGot(result);
|
||||
});
|
||||
|
||||
browserType.storage.local.get('sendCookies', function (result) {
|
||||
setCookiesOptions(result);
|
||||
browserType.storage.local.get('continuousSync', function (result) {
|
||||
setContinuousCookiesOptions(result);
|
||||
});
|
||||
|
||||
browserType.storage.local.get('autostart', function (result) {
|
||||
|
@ -106,11 +106,13 @@ function getBrowser() {
|
||||
}
|
||||
|
||||
function getChannelContainers() {
|
||||
const elements = document.querySelectorAll('.yt-flexible-actions-view-model-wiz, #owner');
|
||||
const elements = document.querySelectorAll(
|
||||
'.page-header-view-model-wiz__page-header-flexible-actions, #owner'
|
||||
);
|
||||
const channelContainerNodes = [];
|
||||
|
||||
elements.forEach(element => {
|
||||
if (isElementVisible(element)) {
|
||||
if (isElementVisible(element) && window.location.pathname !== '/playlist') {
|
||||
channelContainerNodes.push(element);
|
||||
}
|
||||
});
|
||||
@ -194,22 +196,38 @@ function buildChannelButton(channelContainer) {
|
||||
}
|
||||
|
||||
function getChannelHandle(channelContainer) {
|
||||
let channelHandle;
|
||||
const videoOwnerRenderer = channelContainer.querySelector('.ytd-video-owner-renderer');
|
||||
function findeHandleString(container) {
|
||||
let result = null;
|
||||
|
||||
if (!videoOwnerRenderer) {
|
||||
const channelHandleContainer = document.querySelector(
|
||||
'.yt-content-metadata-view-model-wiz__metadata-text'
|
||||
);
|
||||
channelHandle = channelHandleContainer ? channelHandleContainer.innerText : null;
|
||||
} else {
|
||||
const href = videoOwnerRenderer.href;
|
||||
if (href) {
|
||||
const urlObj = new URL(href);
|
||||
channelHandle = urlObj.pathname.split('/')[1];
|
||||
function recursiveTraversal(element) {
|
||||
for (let child of element.children) {
|
||||
if (child.tagName === 'A' && child.hasAttribute('href')) {
|
||||
const href = child.getAttribute('href');
|
||||
const match = href.match(/\/@[^/]+/); // Match the path starting with "@"
|
||||
if (match) {
|
||||
// handle is in channel link
|
||||
result = match[0].substring(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (child.children.length === 0 && child.textContent.trim().startsWith('@')) {
|
||||
// handle is in channel description text
|
||||
result = child.textContent.trim();
|
||||
return;
|
||||
}
|
||||
|
||||
recursiveTraversal(child);
|
||||
if (result) return;
|
||||
}
|
||||
}
|
||||
|
||||
recursiveTraversal(container);
|
||||
return result;
|
||||
}
|
||||
|
||||
let channelHandle = findeHandleString(channelContainer.parentElement);
|
||||
|
||||
return channelHandle;
|
||||
}
|
||||
|
||||
@ -247,6 +265,7 @@ function buildChannelSubButton(channelHandle) {
|
||||
} else {
|
||||
console.log('Unknown state');
|
||||
}
|
||||
e.stopPropagation();
|
||||
});
|
||||
Object.assign(channelSubButton.style, {
|
||||
padding: '5px',
|
||||
@ -308,6 +327,7 @@ function buildChannelDownloadButton() {
|
||||
e.preventDefault();
|
||||
console.log(`download: ${currentLocation}`);
|
||||
sendDownload(channelDownloadButton);
|
||||
e.stopPropagation();
|
||||
});
|
||||
Object.assign(channelDownloadButton.style, {
|
||||
filter: 'invert()',
|
||||
@ -389,6 +409,24 @@ function buildVideoButton(titleContainer) {
|
||||
}
|
||||
|
||||
function getNearestLink(element) {
|
||||
// Check siblings
|
||||
let sibling = element;
|
||||
while (sibling) {
|
||||
sibling = sibling.previousElementSibling;
|
||||
if (sibling && sibling.tagName === 'A' && sibling.getAttribute('href') !== '#') {
|
||||
return sibling.getAttribute('href');
|
||||
}
|
||||
}
|
||||
|
||||
sibling = element;
|
||||
while (sibling) {
|
||||
sibling = sibling.nextElementSibling;
|
||||
if (sibling && sibling.tagName === 'A' && sibling.getAttribute('href') !== '#') {
|
||||
return sibling.getAttribute('href');
|
||||
}
|
||||
}
|
||||
|
||||
// Check parent elements
|
||||
for (let i = 0; i < 5 && element && element !== document; i++) {
|
||||
if (element.tagName === 'A' && element.getAttribute('href') !== '#') {
|
||||
return element.getAttribute('href');
|
||||
@ -453,12 +491,14 @@ function checkVideoExists(taButton) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
let aElem = taButton?.parentElement?.querySelector('a');
|
||||
let videoId = getVideoId(aElem);;
|
||||
if (aElem) {
|
||||
taButton.setAttribute('data-id', videoId);
|
||||
taButton.setAttribute('data-type', 'video');
|
||||
taButton.title = `TA download video: ${taButton.parentElement.innerText} [${videoId}]`;
|
||||
let videoId = taButton.dataset.id;
|
||||
if (!videoId) {
|
||||
videoId = getVideoId(taButton);
|
||||
if (videoId) {
|
||||
taButton.setAttribute('data-id', videoId);
|
||||
taButton.setAttribute('data-type', 'video');
|
||||
taButton.title = `TA download video: ${taButton.parentElement.innerText} [${videoId}]`;
|
||||
}
|
||||
}
|
||||
|
||||
let message = { type: 'videoExists', videoId };
|
||||
|
@ -18,6 +18,20 @@ hr {
|
||||
background-color: white;
|
||||
margin: 10px 0;
|
||||
}
|
||||
button {
|
||||
margin: 10px;
|
||||
border-radius: 0;
|
||||
padding: 5px 13px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: #259485;
|
||||
color: #ffffff;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #97d4c8;
|
||||
transform: scale(1.05);
|
||||
color: #00202f;
|
||||
}
|
||||
#download {
|
||||
text-align: center
|
||||
}
|
||||
@ -28,7 +42,7 @@ hr {
|
||||
position: relative;
|
||||
}
|
||||
.logo img {
|
||||
width: 100%;
|
||||
width: 400px;
|
||||
}
|
||||
.logo span {
|
||||
position: absolute;
|
||||
@ -48,20 +62,6 @@ hr {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.submit button {
|
||||
margin: 10px;
|
||||
border-radius: 0;
|
||||
padding: 5px 13px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: #259485;
|
||||
color: #ffffff;
|
||||
}
|
||||
.submit button:hover {
|
||||
background-color: #97d4c8;
|
||||
transform: scale(1.05);
|
||||
color: #00202f;
|
||||
}
|
||||
.options {
|
||||
display: block;
|
||||
padding-bottom: 10px;
|
||||
@ -81,3 +81,8 @@ hr {
|
||||
color: red;
|
||||
display: none; /* will be made visible when an error occurs */
|
||||
}
|
||||
#cookieLinesResponse {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user