/*
content script running on youtube.com
*/
'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;
}
} else {
console.log('failed to dedect browser');
throw 'browser detection error';
}
}
const downloadIcon = ``;
const checkmarkIcon = ``;
function buildButtonDiv() {
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;
}
function buildSubLink(channelContainer) {
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;
}
function buildSpacer() {
let spacer = document.createElement('span');
spacer.innerText = '|';
return spacer;
}
function buildDlLink(channelContainer) {
let dlLink = document.createElement('span');
dlLink.innerHTML = downloadIcon;
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',
});
return dlLink;
}
function buildChannelButton(channelContainer) {
let buttonDiv = buildButtonDiv();
let subLink = buildSubLink(channelContainer);
buttonDiv.appendChild(subLink);
let spacer = buildSpacer();
buttonDiv.appendChild(spacer);
let dlLink = buildDlLink(channelContainer);
buttonDiv.appendChild(dlLink);
return buttonDiv;
}
function getChannelContainers() {
let nodes = document.querySelectorAll('#inner-header-container, #owner');
return nodes;
}
function getThubnailContainers() {
let nodes = document.querySelectorAll('#thumbnail');
return nodes;
}
function buildVideoButton(thumbContainer) {
let thumbLink = thumbContainer?.href;
if (!thumbLink) return;
if (thumbLink.includes('list=') || thumbLink.includes('/shorts/')) return;
let ggp = thumbContainer?.parentElement?.parentElement;
if (ggp?.id !== 'dismissible') return;
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);
});
ggp.addEventListener('mouseover', () => {
Object.assign(dlButton.style, {
opacity: 1,
});
let videoTitle = thumbContainer.href;
dlButton.title = 'TA download: ' + videoTitle;
});
ggp.addEventListener('mouseout', () => {
Object.assign(dlButton.style, {
opacity: 0,
});
});
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,
});
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 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;
}
function ensureTALinks() {
let channelContainerNodes = getChannelContainers();
for (let channelContainer of channelContainerNodes) {
channelContainer = adjustOwner(channelContainer);
if (channelContainer.hasTA) continue;
let channelButton = buildChannelButton(channelContainer);
channelContainer.appendChild(channelButton);
channelContainer.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';
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);
}
}
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 handleError(error) {
console.log('error');
console.log(JSON.stringify(error));
}
let payload = {
youtube: {
url: url,
action: action,
},
};
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);
};
let observer = new MutationObserver(list => {
if (list.some(i => i.type === 'childList' && i.addedNodes.length > 0)) {
throttle(ensureTALinks, 700);
}
});
observer.observe(document.body, { attributes: false, childList: true, subtree: true });