Guest User

Untitled

a guest
May 15th, 2025
47
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Invidious BBCode Floating Button
  3. // @namespace    http://tampermonkey.net/
  4. // @version      0.7
  5. // @description  Adds a floating BB button to trigger BBCode comment and video metadata copying on Invidious instances
  6. // @author       You
  7. // @match        *://yewtu.be/*
  8. // @match        *://inv.nadeko.net/*
  9. // @match        *://invidious.nerdvpn.de/*
  10. // @match        *://iv.ggtyler.dev/*
  11. // @match        *://invidious.jing.rocks/*
  12. // @match        *://invidious.perennialte.ch/*
  13. // @match        *://invidious.reallyaweso.me/*
  14. // @match        *://invidious.privacyredirect.com/*
  15. // @match        *://invidious.einfachzocken.eu/*
  16. // @match        *://inv.tux.pizza/*
  17. // @match        *://iv.nboeck.de/*
  18. // @match        *://iv.nowhere.moe/*
  19. // @match        *://invidious.adminforge.de/*
  20. // @match        *://invidious.yourdevice.ch/*
  21. // @match        *://invidious.privacydev.net/*
  22. // @grant        none
  23. // ==/UserScript==
  24.  
  25. (function() {
  26.     'use strict';
  27.  
  28.     // Create the floating button
  29.     const bbButton = document.createElement('button');
  30.     bbButton.textContent = 'BB';
  31.     bbButton.title = 'Activate BBCode Comment & Video Tool';
  32.     bbButton.style.cssText = `
  33.         position: fixed;
  34.         bottom: 10px;
  35.         left: 10px;
  36.         z-index: 9999;
  37.         background-color: #4CAF50;
  38.         color: white;
  39.         border: none;
  40.         border-radius: 50%;
  41.         width: 40px;
  42.         height: 40px;
  43.         font-size: 16px;
  44.         cursor: pointer;
  45.         box-shadow: 0 2px 5px rgba(0,0,0,0.3);
  46.     `;
  47.     document.body.appendChild(bbButton);
  48.  
  49.     // BBCode tool logic
  50.     function activateBBCodeTool() {
  51.         console.log('[Invidious BBCode Tool] Activated - Click copy buttons on comments or video metadata');
  52.  
  53.         function htmlToBBCode(html) {
  54.             return html
  55.                 .replace(/<a href="([^"]+)"[^>]*>([^<]+)<\/a>/g, (m, url, txt) => `[icode]${txt}[/icode]`)
  56.                 .replace(/<b>([^<]+)<\/b>/g, '[b]$1[/b]')
  57.                 .replace(/<i>([^<]+)<\/i>/g, '[i]$1[/i]')
  58.                 .replace(/<s>([^<]+)<\/s>/g, '[s]$1[/s]')
  59.                 .replace(/<\/?p[^>]*>/g, '\n')
  60.                 .trim();
  61.         }
  62.  
  63.         function extractTextWithLinks(html) {
  64.             const parser = new DOMParser();
  65.             const doc = parser.parseFromString(html, 'text/html');
  66.             let text = doc.body.innerText.trim();
  67.             const links = Array.from(doc.body.querySelectorAll('a'));
  68.  
  69.             let reconstructedText = text;
  70.             links.forEach(link => {
  71.                 const linkText = link.textContent.trim();
  72.                 // Check if the link text is a YouTube URL
  73.                 const isYouTubeUrl = linkText.match(/^(https?:\/\/)?(www\.)?youtube\.com/);
  74.                 let urlToUse = link.href;
  75.                 if (isYouTubeUrl) {
  76.                     // Ensure https:// protocol for YouTube URLs
  77.                     urlToUse = linkText.match(/^https?:\/\//) ? linkText : `https://${linkText}`;
  78.                 }
  79.                 reconstructedText = reconstructedText.replace(linkText, `[icode]${urlToUse}[/icode]`);
  80.             });
  81.             return reconstructedText.trim();
  82.         }
  83.  
  84.         function getParentComments(commentElement, maxDepth = 10) {
  85.             const parents = [];
  86.             const seenIds = new Set();
  87.             let currentComment = commentElement.closest('.pure-g');
  88.  
  89.             while (currentComment && parents.length < maxDepth) {
  90.                 const parentContainer = currentComment.parentElement.closest('#replies');
  91.                 if (!parentContainer) break;
  92.  
  93.                 const parentComment = parentContainer.parentElement.closest('.pure-g');
  94.                 if (!parentComment) break;
  95.  
  96.                 const commentId = parentComment.querySelector('a[title="YouTube comment permalink"]')?.href || '';
  97.                 if (seenIds.has(commentId)) break;
  98.                 seenIds.add(commentId);
  99.  
  100.                 const username = parentComment.querySelector('a[href^="/channel/"]')?.textContent.trim() || 'Unknown';
  101.                 const datetime = parentComment.querySelector('span[title]')?.getAttribute('title') || 'Unknown';
  102.                 const contentHtml = parentComment.querySelector('p[style="white-space:pre-wrap"]');
  103.                 const permalink = parentComment.querySelector('a[title="YouTube comment permalink"]')?.href || window.location.href;
  104.                 const likes = parentComment.querySelector('.icon.ion-ios-thumbs-up')?.nextSibling.textContent.trim() || '0';
  105.                 const contentText = contentHtml ? extractTextWithLinks(contentHtml.innerHTML) : 'No content';
  106.                 const contentBBCode = htmlToBBCode(contentText);
  107.  
  108.                 parents.unshift({
  109.                     permalink,
  110.                     spoilerContent: `\n${username} commented on ${datetime} | Likes: ${likes}\n\n${contentBBCode}\n`
  111.                 });
  112.                 currentComment = parentComment;
  113.             }
  114.             return parents;
  115.         }
  116.  
  117.         function createDepthDropdown(maxDepth, callback, button) {
  118.             const existingDropdown = document.querySelector('.depth-dropdown');
  119.             if (existingDropdown) existingDropdown.remove();
  120.  
  121.             const dropdown = document.createElement('select');
  122.             dropdown.className = 'depth-dropdown';
  123.             dropdown.style.position = 'absolute';
  124.             dropdown.style.marginLeft = '5px';
  125.             dropdown.style.padding = '2px';
  126.             dropdown.style.fontSize = '12px';
  127.  
  128.             for (let i = 0; i <= maxDepth; i++) {
  129.                 const option = document.createElement('option');
  130.                 option.value = i;
  131.                 option.textContent = i === 0 ? 'Only this' : `${i} parent${i > 1 ? 's' : ''}`;
  132.                 dropdown.appendChild(option);
  133.             }
  134.  
  135.             dropdown.addEventListener('change', () => {
  136.                 callback(parseInt(dropdown.value));
  137.                 dropdown.remove();
  138.             });
  139.  
  140.             dropdown.addEventListener('blur', () => dropdown.remove());
  141.             button.insertAdjacentElement('afterend', dropdown);
  142.             dropdown.focus();
  143.         }
  144.  
  145.         function createCopyButtons(element) {
  146.             const existingButtons = element.querySelectorAll('.copy-btn, .nested-btn, .video-copy-btn');
  147.             existingButtons.forEach(btn => btn.remove());
  148.  
  149.             // Comment Copy Button
  150.             const copyButton = document.createElement('button');
  151.             copyButton.textContent = '📋';
  152.             copyButton.title = 'Copy comment to BBCode';
  153.             copyButton.className = 'copy-btn';
  154.             copyButton.style.cssText = 'cursor:pointer;background:green;color:white;margin-left:5px;padding:2px 6px;border:none;border-radius:4px;';
  155.  
  156.             // Nested Comment Button
  157.             const nestedButton = document.createElement('button');
  158.             nestedButton.textContent = '📋';
  159.             nestedButton.title = 'Copy comment with nested parents';
  160.             nestedButton.className = 'nested-btn';
  161.             nestedButton.style.cssText = 'cursor:pointer;background:blue;color:white;margin-left:5px;padding:2px 6px;border:none;border-radius:4px;';
  162.  
  163.             // Video Metadata Copy Button
  164.             const videoCopyButton = document.createElement('button');
  165.             videoCopyButton.textContent = '📋';
  166.             videoCopyButton.title = 'Copy video metadata to BBCode';
  167.             videoCopyButton.className = 'video-copy-btn';
  168.             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;';
  169.  
  170.             // Handle Comment Elements
  171.             if (element.classList.contains('pure-g') && element.querySelector('a[href^="/channel/"]')) {
  172.                 const permalink = element.querySelector('a[title="YouTube comment permalink"]')?.href || window.location.href;
  173.                 const username = element.querySelector('a[href^="/channel/"]')?.textContent.trim() || 'Unknown';
  174.                 const datetime = element.querySelector('span[title]')?.getAttribute('title') || 'Unknown';
  175.                 const contentHtml = element.querySelector('p[style="white-space:pre-wrap"]');
  176.                 const likes = element.querySelector('.icon.ion-ios-thumbs-up')?.nextSibling.textContent.trim() || '0';
  177.  
  178.                 if (!contentHtml) {
  179.                     console.log('[Invidious BBCode Tool] No content found for comment');
  180.                     return;
  181.                 }
  182.  
  183.                 const contentText = extractTextWithLinks(contentHtml.innerHTML);
  184.                 const contentBBCode = htmlToBBCode(contentText);
  185.                 const spoilerContent = `\n${username} commented on ${datetime} | Likes: ${likes}\n\n${contentBBCode}\n`;
  186.                 const fullContent = `[icode]${permalink}[/icode]\n${spoilerContent}`;
  187.  
  188.                 copyButton.addEventListener('click', (e) => {
  189.                     e.preventDefault();
  190.                     e.stopPropagation();
  191.                     navigator.clipboard.writeText(fullContent)
  192.                         .then(() => showToast('Comment copied!'))
  193.                         .catch(err => console.error('[Invidious BBCode Tool] Copy failed:', err));
  194.                 });
  195.  
  196.                 nestedButton.addEventListener('click', (e) => {
  197.                     e.preventDefault();
  198.                     e.stopPropagation();
  199.                     const parents = getParentComments(element);
  200.                     const maxDepth = parents.length;
  201.  
  202.                     createDepthDropdown(maxDepth, (depth) => {
  203.                         const items = [
  204.                             ...parents.slice(0, depth),
  205.                             { permalink, spoilerContent }
  206.                         ];
  207.  
  208.                         const payload = items
  209.                             .map((item, index) => {
  210.                                 const indent = '│   '.repeat(index);
  211.                                 const commentLines = `[icode]${item.permalink}[/icode]\n${item.spoilerContent}`.split('\n');
  212.                                 return commentLines.map(line => `${indent}${line}`).join('\n');
  213.                             })
  214.                             .join('\n\n');
  215.  
  216.                         navigator.clipboard.writeText(payload)
  217.                             .then(() => showToast(`Comment with ${depth} parent${depth === 1 ? '' : 's'} copied!`))
  218.                             .catch(err => console.error('[Invidious BBCode Tool] Nested copy failed:', err));
  219.                     }, nestedButton);
  220.                 });
  221.  
  222.                 const target = element.querySelector('.pure-u-20-24, .pure-u-md-22-24') || element;
  223.                 target.appendChild(copyButton);
  224.                 target.appendChild(nestedButton);
  225.             }
  226.  
  227.             // Handle Video Metadata Element
  228.             if (element.classList.contains('h-box') && element.classList.contains('highlight') && element.querySelector('h1')) {
  229.                 console.log('[Invidious BBCode Tool] Video metadata element detected:', element);
  230.  
  231.                 const title = element.querySelector('h1')?.textContent.trim() || 'Untitled';
  232.                 const youtubeUrl = document.querySelector('#link-yt-watch')?.href || window.location.href;
  233.                 const channelName = document.querySelector('#channel-name')?.textContent.trim() || 'Unknown';
  234.                 const channelUrl = document.querySelector('a[href^="/channel/"]')?.href || '';
  235.                 const views = document.querySelector('#views')?.textContent.trim().replace(/\sViews/, '') || '0';
  236.                 const likes = document.querySelector('#likes')?.textContent.trim() || '0';
  237.                 const publishedDate = document.querySelector('#published-date b')?.textContent.trim() || 'Unknown';
  238.                 const descriptionHtml = document.querySelector('#descriptionWrapper')?.innerHTML || 'No description';
  239.                 const descriptionText = extractTextWithLinks(descriptionHtml);
  240.                 const descriptionBBCode = htmlToBBCode(descriptionText);
  241.  
  242.                 const videoContent = `[b]${title}[/b]\n` +
  243.                                     `[icode]${youtubeUrl}[/icode]\n\n` +
  244.                                     `[i]Channel:[/i] [icode]${channelUrl}[/icode] ${channelName}\n` +
  245.                                     `[i]Stats:[/i] Views: ${views} | Likes: ${likes}\n` +
  246.                                     `[i]Published:[/i] ${publishedDate}\n\n` +
  247.                                     `\n${descriptionBBCode}\n`;
  248.  
  249.                 videoCopyButton.addEventListener('click', (e) => {
  250.                     e.preventDefault();
  251.                     e.stopPropagation();
  252.                     navigator.clipboard.writeText(videoContent)
  253.                         .then(() => showToast('Video metadata copied!'))
  254.                         .catch(err => console.error('[Invidious BBCode Tool] Video copy failed:', err));
  255.                 });
  256.  
  257.                 const target = document.querySelector('#subscribe');
  258.                 if (target) {
  259.                     console.log('[Invidious BBCode Tool] Attaching video copy button under #subscribe:', target);
  260.                     target.insertAdjacentElement('afterend', videoCopyButton);
  261.                 } else {
  262.                     console.log('[Invidious BBCode Tool] #subscribe not found, falling back to element:', element);
  263.                     element.appendChild(videoCopyButton); // Fallback
  264.                 }
  265.             }
  266.         }
  267.  
  268.         function showToast(message) {
  269.             const toast = document.createElement('div');
  270.             toast.textContent = message;
  271.             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);';
  272.             document.body.appendChild(toast);
  273.             setTimeout(() => toast.remove(), 2000);
  274.         }
  275.  
  276.         // Use MutationObserver to handle dynamic DOM changes
  277.         const observer = new MutationObserver((mutations) => {
  278.             document.querySelectorAll('.pure-g').forEach(element => {
  279.                 if (element.querySelector('a[href^="/channel/"]') && !element.querySelector('.copy-btn')) {
  280.                     console.log('[Invidious BBCode Tool] Processing comment element:', element);
  281.                     createCopyButtons(element);
  282.                 }
  283.             });
  284.  
  285.             document.querySelectorAll('.h-box.highlight').forEach(element => {
  286.                 if (element.querySelector('h1') && !element.querySelector('.video-copy-btn')) {
  287.                     console.log('[Invidious BBCode Tool] Processing video element:', element);
  288.                     createCopyButtons(element);
  289.                 }
  290.             });
  291.         });
  292.  
  293.         observer.observe(document.body, { childList: true, subtree: true });
  294.  
  295.         // Initial check in case elements are already loaded
  296.         document.querySelectorAll('.pure-g').forEach(element => {
  297.             if (element.querySelector('a[href^="/channel/"]')) {
  298.                 console.log('[Invidious BBCode Tool] Initial processing comment element:', element);
  299.                 createCopyButtons(element);
  300.             }
  301.         });
  302.  
  303.         document.querySelectorAll('.h-box.highlight').forEach(element => {
  304.             if (element.querySelector('h1')) {
  305.                 console.log('[Invidious BBCode Tool] Initial processing video element:', element);
  306.                 createCopyButtons(element);
  307.             }
  308.         });
  309.     }
  310.  
  311.     // Toggle the BBCode tool on button click
  312.     let isActive = false;
  313.     bbButton.addEventListener('click', () => {
  314.         if (!isActive) {
  315.             activateBBCodeTool();
  316.             bbButton.style.backgroundColor = '#f44336'; // Red when active
  317.             bbButton.title = 'Deactivate BBCode Comment & Video Tool';
  318.             isActive = true;
  319.         } else {
  320.             // Remove all copy buttons and reset
  321.             document.querySelectorAll('.copy-btn, .nested-btn, .video-copy-btn, .depth-dropdown').forEach(el => el.remove());
  322.             bbButton.style.backgroundColor = '#4CAF50'; // Green when inactive
  323.             bbButton.title = 'Activate BBCode Comment & Video Tool';
  324.             isActive = false;
  325.         }
  326.     });
  327. })();
Add Comment
Please, Sign In to add comment