View difference between Paste ID: 3TVJGgbQ and QuMuJms5
SHOW: | | - or go back to the newest paste.
1-
javascript:(function() {
1+
// ==UserScript==
2-
  var textareas = document.querySelectorAll('textarea.input');
2+
// @name         Invidious BBCode Floating Button
3-
  textareas.forEach(function(textarea) {
3+
// @namespace    http://tampermonkey.net/
4-
    var text = textarea.value;
4+
// @version      0.7
5
// @description  Adds a floating BB button to trigger BBCode comment and video metadata copying on Invidious instances
6-
    
6+
// @author       You
7-
    var pattern1 = /$$\s*URL=(.*?)\s*\$\$(.*?)\s*\/\s*URL\s*$$/gi;
7+
// @match        *://yewtu.be/*
8-
    var pattern2 = /$$\s*URL\s*\$\$(.*?)\s*\/\s*URL\s*$$/gi;
8+
// @match        *://inv.nadeko.net/*
9-
    replacedText = text.replace(pattern1, '[U][URL=$1]$2[/URL][/U]');
9+
// @match        *://invidious.nerdvpn.de/*
10-
    replacedText = replacedText.replace(pattern2, '[U][URL]$1[/URL][/U]');
10+
// @match        *://iv.ggtyler.dev/*
11-
    replacedText = replacedText.replace(/$$U$$\$\$U\$\$URL=(.*?)\/URL\$\$\/U\$\$\/U$$/g, '[U][URL=$1[/URL][/U]');
11+
// @match        *://invidious.jing.rocks/*
12
// @match        *://invidious.perennialte.ch/*
13-
    
13+
// @match        *://invidious.reallyaweso.me/*
14-
    var existingUrlPattern = /(?<!\[U\])\[URL=(.*?)\](.*?)\[\/URL\](?!\[\/U\])/gi;
14+
// @match        *://invidious.privacyredirect.com/*
15-
    replacedText = replacedText.replace(existingUrlPattern, '[U][URL=$1]$2[/URL][/U]');
15+
// @match        *://invidious.einfachzocken.eu/*
16
// @match        *://inv.tux.pizza/*
17-
    var existingUrlSimplePattern = /(?<!\[U\])\[URL\](.*?)\[\/URL\](?!\[\/U\])/gi;
17+
// @match        *://iv.nboeck.de/*
18-
    replacedText = replacedText.replace(existingUrlSimplePattern, '[U][URL]$1[/URL][/U]');
18+
// @match        *://iv.nowhere.moe/*
19
// @match        *://invidious.adminforge.de/*
20-
    var pattern3 = /(\n)([A-Z"]|\d|$$B$$|$$I$$|$$IMG$$)/g;
20+
// @match        *://invidious.yourdevice.ch/*
21-
    var pattern6 = /($$HEADING=2$$|$$LIST$$|$$\*$$|$$\/LIST$$|$$IMG$$)/g;
21+
// @match        *://invidious.privacydev.net/*
22-
    replacedText = replacedText.replace(pattern6, '\n$1');
22+
// @grant        none
23-
    replacedText = replacedText.replace(/$$\/HEADING$$\n/g, '[/HEADING]\n');
23+
// ==/UserScript==
24-
    replacedText = replacedText.replace(/$$\/IMG$$\n/g, '[/IMG]\n');
24+
25-
    replacedText = replacedText.replace(/\[\/LIST\]/g, '[/LIST]\n');
25+
(function() {
26
    'use strict';
27-
    var pattern4 = /$$IMG alt="([^"]*)"/g;
27+
28-
    var pattern9 = /\$\$\/IMG$$([A-Za-z0-9])/g;
28+
    // Create the floating button
29-
    replacedText = replacedText.replace(pattern4, '\n[IMG alt="$1"] ');
29+
    const bbButton = document.createElement('button');
30-
    replacedText = replacedText.replace(pattern9, '[/IMG] $1');
30+
    bbButton.textContent = 'BB';
31
    bbButton.title = 'Activate BBCode Comment & Video Tool';
32-
    var pattern5 = /\n/g;
32+
    bbButton.style.cssText = `
33-
    var pattern10 = /^\n+|\n{2,}|\n+$/g;
33+
        position: fixed;
34-
    replacedText = replacedText.replace(pattern5, '\n');
34+
        bottom: 10px;
35-
    replacedText = replacedText.replace(pattern10, '\n');
35+
        left: 10px;
36
        z-index: 9999;
37-
    var pattern7 = /$$\/?FONT(=.*?)?$$/g;
37+
        background-color: #4CAF50;
38-
    var pattern8 = /$$\/?COLOR(=.*?)?$$/g;
38+
        color: white;
39-
    replacedText = replacedText.replace(pattern7, '');
39+
        border: none;
40-
    replacedText = replacedText.replace(pattern8, '');
40+
        border-radius: 50%;
41
        width: 40px;
42-
    replacedText = replacedText.replace(/\$\$B\$\$(.*?)\$\$\/B\$\$/g, '[B]$1[/B]');
42+
        height: 40px;
43-
    replacedText = replacedText.replace(/\$\$I\$\$(.*?)\$\$\/I\$\$/g, '[I]$1[/I]');
43+
        font-size: 16px;
44
        cursor: pointer;
45-
    var mathPattern = /\$\$(.*?)\$\$/g;
45+
        box-shadow: 0 2px 5px rgba(0,0,0,0.3);
46-
    replacedText = replacedText.replace(mathPattern, '\$$$1\$$');
46+
    `;
47
    document.body.appendChild(bbButton);
48-
    var paragraphPattern = /([^\n])\n([A-Z0-9])/g;
48+
49-
    replacedText = replacedText.replace(paragraphPattern, '$1\n\n$2');
49+
    // BBCode tool logic
50
    function activateBBCodeTool() {
51-
    var quotePattern = /$$QUOTE$$(?:.*?\n)*?(.*?)$$\/QUOTE$$/gs;
51+
        console.log('[Invidious BBCode Tool] Activated - Click copy buttons on comments or video metadata');
52-
    replacedText = replacedText.replace(quotePattern, '[QUOTE][SIZE=4]$1[/SIZE][/QUOTE]');
52+
53
        function htmlToBBCode(html) {
54-
    const emptyListItemsPattern = /\[LIST\](\n\[\*\]\s*)+\[\/LIST\]/gi;
54+
            return html
55-
    replacedText = replacedText.replace(emptyListItemsPattern, '');
55+
                .replace(/<a href="([^"]+)"[^>]*>([^<]+)<\/a>/g, (m, url, txt) => `[icode]${txt}[/icode]`)
56-
    const emptyListBlockPattern = /\[LIST\]\s*\[\/LIST\]/gi;
56+
                .replace(/<b>([^<]+)<\/b>/g, '[b]$1[/b]')
57-
    replacedText = replacedText.replace(emptyListBlockPattern, '');
57+
                .replace(/<i>([^<]+)<\/i>/g, '[i]$1[/i]')
58
                .replace(/<s>([^<]+)<\/s>/g, '[s]$1[/s]')
59-
    replacedText = replacedText.replace(/\[B\]\[\/B\]/g, '');
59+
                .replace(/<\/?p[^>]*>/g, '\n')
60-
    replacedText = replacedText.replace(/\[I\]\[\/I\]/g, '');
60+
                .trim();
61
        }
62-
    textarea.value = replacedText;
62+
63-
  });
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
})();