Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Invidious BBCode Floating Button
- // @namespace http://tampermonkey.net/
- // @version 0.7
- // @description Adds a floating BB button to trigger BBCode comment and video metadata copying on Invidious instances
- // @author You
- // @match *://yewtu.be/*
- // @match *://inv.nadeko.net/*
- // @match *://invidious.nerdvpn.de/*
- // @match *://iv.ggtyler.dev/*
- // @match *://invidious.jing.rocks/*
- // @match *://invidious.perennialte.ch/*
- // @match *://invidious.reallyaweso.me/*
- // @match *://invidious.privacyredirect.com/*
- // @match *://invidious.einfachzocken.eu/*
- // @match *://inv.tux.pizza/*
- // @match *://iv.nboeck.de/*
- // @match *://iv.nowhere.moe/*
- // @match *://invidious.adminforge.de/*
- // @match *://invidious.yourdevice.ch/*
- // @match *://invidious.privacydev.net/*
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- // Create the floating button
- const bbButton = document.createElement('button');
- bbButton.textContent = 'BB';
- bbButton.title = 'Activate BBCode Comment & Video Tool';
- bbButton.style.cssText = `
- position: fixed;
- bottom: 10px;
- left: 10px;
- z-index: 9999;
- background-color: #4CAF50;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- font-size: 16px;
- cursor: pointer;
- box-shadow: 0 2px 5px rgba(0,0,0,0.3);
- `;
- document.body.appendChild(bbButton);
- // BBCode tool logic
- function activateBBCodeTool() {
- console.log('[Invidious BBCode Tool] Activated - Click copy buttons on comments or video metadata');
- function htmlToBBCode(html) {
- return html
- .replace(/<a href="([^"]+)"[^>]*>([^<]+)<\/a>/g, (m, url, txt) => `[icode]${txt}[/icode]`)
- .replace(/<b>([^<]+)<\/b>/g, '[b]$1[/b]')
- .replace(/<i>([^<]+)<\/i>/g, '[i]$1[/i]')
- .replace(/<s>([^<]+)<\/s>/g, '[s]$1[/s]')
- .replace(/<\/?p[^>]*>/g, '\n')
- .trim();
- }
- function extractTextWithLinks(html) {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
- let text = doc.body.innerText.trim();
- const links = Array.from(doc.body.querySelectorAll('a'));
- let reconstructedText = text;
- links.forEach(link => {
- const linkText = link.textContent.trim();
- // Check if the link text is a YouTube URL
- const isYouTubeUrl = linkText.match(/^(https?:\/\/)?(www\.)?youtube\.com/);
- let urlToUse = link.href;
- if (isYouTubeUrl) {
- // Ensure https:// protocol for YouTube URLs
- urlToUse = linkText.match(/^https?:\/\//) ? linkText : `https://${linkText}`;
- }
- reconstructedText = reconstructedText.replace(linkText, `[icode]${urlToUse}[/icode]`);
- });
- return reconstructedText.trim();
- }
- function getParentComments(commentElement, maxDepth = 10) {
- const parents = [];
- const seenIds = new Set();
- let currentComment = commentElement.closest('.pure-g');
- while (currentComment && parents.length < maxDepth) {
- const parentContainer = currentComment.parentElement.closest('#replies');
- if (!parentContainer) break;
- const parentComment = parentContainer.parentElement.closest('.pure-g');
- if (!parentComment) break;
- const commentId = parentComment.querySelector('a[title="YouTube comment permalink"]')?.href || '';
- if (seenIds.has(commentId)) break;
- seenIds.add(commentId);
- const username = parentComment.querySelector('a[href^="/channel/"]')?.textContent.trim() || 'Unknown';
- const datetime = parentComment.querySelector('span[title]')?.getAttribute('title') || 'Unknown';
- const contentHtml = parentComment.querySelector('p[style="white-space:pre-wrap"]');
- const permalink = parentComment.querySelector('a[title="YouTube comment permalink"]')?.href || window.location.href;
- const likes = parentComment.querySelector('.icon.ion-ios-thumbs-up')?.nextSibling.textContent.trim() || '0';
- const contentText = contentHtml ? extractTextWithLinks(contentHtml.innerHTML) : 'No content';
- const contentBBCode = htmlToBBCode(contentText);
- parents.unshift({
- permalink,
- spoilerContent: `\n${username} commented on ${datetime} | Likes: ${likes}\n\n${contentBBCode}\n`
- });
- currentComment = parentComment;
- }
- return parents;
- }
- function createDepthDropdown(maxDepth, callback, button) {
- const existingDropdown = document.querySelector('.depth-dropdown');
- if (existingDropdown) existingDropdown.remove();
- const dropdown = document.createElement('select');
- dropdown.className = 'depth-dropdown';
- dropdown.style.position = 'absolute';
- dropdown.style.marginLeft = '5px';
- dropdown.style.padding = '2px';
- dropdown.style.fontSize = '12px';
- for (let i = 0; i <= maxDepth; i++) {
- const option = document.createElement('option');
- option.value = i;
- option.textContent = i === 0 ? 'Only this' : `${i} parent${i > 1 ? 's' : ''}`;
- dropdown.appendChild(option);
- }
- dropdown.addEventListener('change', () => {
- callback(parseInt(dropdown.value));
- dropdown.remove();
- });
- dropdown.addEventListener('blur', () => dropdown.remove());
- button.insertAdjacentElement('afterend', dropdown);
- dropdown.focus();
- }
- function createCopyButtons(element) {
- const existingButtons = element.querySelectorAll('.copy-btn, .nested-btn, .video-copy-btn');
- existingButtons.forEach(btn => btn.remove());
- // Comment Copy Button
- const copyButton = document.createElement('button');
- copyButton.textContent = '📋';
- copyButton.title = 'Copy comment to BBCode';
- copyButton.className = 'copy-btn';
- copyButton.style.cssText = 'cursor:pointer;background:green;color:white;margin-left:5px;padding:2px 6px;border:none;border-radius:4px;';
- // Nested Comment Button
- const nestedButton = document.createElement('button');
- nestedButton.textContent = '📋';
- nestedButton.title = 'Copy comment with nested parents';
- nestedButton.className = 'nested-btn';
- nestedButton.style.cssText = 'cursor:pointer;background:blue;color:white;margin-left:5px;padding:2px 6px;border:none;border-radius:4px;';
- // Video Metadata Copy Button
- const videoCopyButton = document.createElement('button');
- videoCopyButton.textContent = '📋';
- videoCopyButton.title = 'Copy video metadata to BBCode';
- videoCopyButton.className = 'video-copy-btn';
- videoCopyButton.style.cssText = 'cursor:pointer;background:purple;color:white;margin-top:5px;padding:2px 6px;border:none;border-radius:4px;z-index:1000;display:block;width:fit-content;';
- // Handle Comment Elements
- if (element.classList.contains('pure-g') && element.querySelector('a[href^="/channel/"]')) {
- const permalink = element.querySelector('a[title="YouTube comment permalink"]')?.href || window.location.href;
- const username = element.querySelector('a[href^="/channel/"]')?.textContent.trim() || 'Unknown';
- const datetime = element.querySelector('span[title]')?.getAttribute('title') || 'Unknown';
- const contentHtml = element.querySelector('p[style="white-space:pre-wrap"]');
- const likes = element.querySelector('.icon.ion-ios-thumbs-up')?.nextSibling.textContent.trim() || '0';
- if (!contentHtml) {
- console.log('[Invidious BBCode Tool] No content found for comment');
- return;
- }
- const contentText = extractTextWithLinks(contentHtml.innerHTML);
- const contentBBCode = htmlToBBCode(contentText);
- const spoilerContent = `\n${username} commented on ${datetime} | Likes: ${likes}\n\n${contentBBCode}\n`;
- const fullContent = `[icode]${permalink}[/icode]\n${spoilerContent}`;
- copyButton.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- navigator.clipboard.writeText(fullContent)
- .then(() => showToast('Comment copied!'))
- .catch(err => console.error('[Invidious BBCode Tool] Copy failed:', err));
- });
- nestedButton.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- const parents = getParentComments(element);
- const maxDepth = parents.length;
- createDepthDropdown(maxDepth, (depth) => {
- const items = [
- ...parents.slice(0, depth),
- { permalink, spoilerContent }
- ];
- const payload = items
- .map((item, index) => {
- const indent = '│ '.repeat(index);
- const commentLines = `[icode]${item.permalink}[/icode]\n${item.spoilerContent}`.split('\n');
- return commentLines.map(line => `${indent}${line}`).join('\n');
- })
- .join('\n\n');
- navigator.clipboard.writeText(payload)
- .then(() => showToast(`Comment with ${depth} parent${depth === 1 ? '' : 's'} copied!`))
- .catch(err => console.error('[Invidious BBCode Tool] Nested copy failed:', err));
- }, nestedButton);
- });
- const target = element.querySelector('.pure-u-20-24, .pure-u-md-22-24') || element;
- target.appendChild(copyButton);
- target.appendChild(nestedButton);
- }
- // Handle Video Metadata Element
- if (element.classList.contains('h-box') && element.classList.contains('highlight') && element.querySelector('h1')) {
- console.log('[Invidious BBCode Tool] Video metadata element detected:', element);
- const title = element.querySelector('h1')?.textContent.trim() || 'Untitled';
- const youtubeUrl = document.querySelector('#link-yt-watch')?.href || window.location.href;
- const channelName = document.querySelector('#channel-name')?.textContent.trim() || 'Unknown';
- const channelUrl = document.querySelector('a[href^="/channel/"]')?.href || '';
- const views = document.querySelector('#views')?.textContent.trim().replace(/\sViews/, '') || '0';
- const likes = document.querySelector('#likes')?.textContent.trim() || '0';
- const publishedDate = document.querySelector('#published-date b')?.textContent.trim() || 'Unknown';
- const descriptionHtml = document.querySelector('#descriptionWrapper')?.innerHTML || 'No description';
- const descriptionText = extractTextWithLinks(descriptionHtml);
- const descriptionBBCode = htmlToBBCode(descriptionText);
- const videoContent = `[b]${title}[/b]\n` +
- `[icode]${youtubeUrl}[/icode]\n\n` +
- `[i]Channel:[/i] [icode]${channelUrl}[/icode] ${channelName}\n` +
- `[i]Stats:[/i] Views: ${views} | Likes: ${likes}\n` +
- `[i]Published:[/i] ${publishedDate}\n\n` +
- `\n${descriptionBBCode}\n`;
- videoCopyButton.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- navigator.clipboard.writeText(videoContent)
- .then(() => showToast('Video metadata copied!'))
- .catch(err => console.error('[Invidious BBCode Tool] Video copy failed:', err));
- });
- const target = document.querySelector('#subscribe');
- if (target) {
- console.log('[Invidious BBCode Tool] Attaching video copy button under #subscribe:', target);
- target.insertAdjacentElement('afterend', videoCopyButton);
- } else {
- console.log('[Invidious BBCode Tool] #subscribe not found, falling back to element:', element);
- element.appendChild(videoCopyButton); // Fallback
- }
- }
- }
- function showToast(message) {
- const toast = document.createElement('div');
- toast.textContent = message;
- toast.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:10px;border-radius:5px;background:#fff;color:#000;z-index:1000;box-shadow:0 0 10px rgba(0,0,0,0.2);';
- document.body.appendChild(toast);
- setTimeout(() => toast.remove(), 2000);
- }
- // Use MutationObserver to handle dynamic DOM changes
- const observer = new MutationObserver((mutations) => {
- document.querySelectorAll('.pure-g').forEach(element => {
- if (element.querySelector('a[href^="/channel/"]') && !element.querySelector('.copy-btn')) {
- console.log('[Invidious BBCode Tool] Processing comment element:', element);
- createCopyButtons(element);
- }
- });
- document.querySelectorAll('.h-box.highlight').forEach(element => {
- if (element.querySelector('h1') && !element.querySelector('.video-copy-btn')) {
- console.log('[Invidious BBCode Tool] Processing video element:', element);
- createCopyButtons(element);
- }
- });
- });
- observer.observe(document.body, { childList: true, subtree: true });
- // Initial check in case elements are already loaded
- document.querySelectorAll('.pure-g').forEach(element => {
- if (element.querySelector('a[href^="/channel/"]')) {
- console.log('[Invidious BBCode Tool] Initial processing comment element:', element);
- createCopyButtons(element);
- }
- });
- document.querySelectorAll('.h-box.highlight').forEach(element => {
- if (element.querySelector('h1')) {
- console.log('[Invidious BBCode Tool] Initial processing video element:', element);
- createCopyButtons(element);
- }
- });
- }
- // Toggle the BBCode tool on button click
- let isActive = false;
- bbButton.addEventListener('click', () => {
- if (!isActive) {
- activateBBCodeTool();
- bbButton.style.backgroundColor = '#f44336'; // Red when active
- bbButton.title = 'Deactivate BBCode Comment & Video Tool';
- isActive = true;
- } else {
- // Remove all copy buttons and reset
- document.querySelectorAll('.copy-btn, .nested-btn, .video-copy-btn, .depth-dropdown').forEach(el => el.remove());
- bbButton.style.backgroundColor = '#4CAF50'; // Green when inactive
- bbButton.title = 'Activate BBCode Comment & Video Tool';
- isActive = false;
- }
- });
- })();
Add Comment
Please, Sign In to add comment