Facebook Comment Saver

Save and manage Facebook comments for later reference

Size

15.5 KB

Version

1.0.1

Created

Nov 28, 2025

Updated

15 days ago

1// ==UserScript==
2// @name		Facebook Comment Saver
3// @description		Save and manage Facebook comments for later reference
4// @version		1.0.1
5// @match		https://*.facebook.com/*
6// @icon		https://static.xx.fbcdn.net/rsrc.php/y1/r/ay1hV6OlegS.ico?_nc_eui2=AeEiD2vGYDgZiQQ_33AGzOMUDLqwI5i5CZMMurAjmLkJkxJRNI1Qr4eZ28h8TKPkusBJEkFTlp8LjCOrNIMOpv2k
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Facebook Comment Saver extension loaded');
12
13    // Debounce function to prevent excessive calls
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25
26    // Add custom styles for the save buttons and panel
27    TM_addStyle(`
28        .fb-comment-save-btn {
29            background: #1877f2;
30            color: white;
31            border: none;
32            padding: 6px 12px;
33            border-radius: 6px;
34            cursor: pointer;
35            font-size: 13px;
36            font-weight: 600;
37            margin-left: 8px;
38            transition: background 0.2s;
39        }
40        .fb-comment-save-btn:hover {
41            background: #166fe5;
42        }
43        .fb-comment-save-btn.saved {
44            background: #42b72a;
45        }
46        .fb-saved-comments-panel {
47            position: fixed;
48            top: 60px;
49            right: 20px;
50            width: 400px;
51            max-height: 80vh;
52            background: white;
53            border-radius: 8px;
54            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
55            z-index: 9999;
56            display: none;
57            flex-direction: column;
58        }
59        .fb-saved-comments-panel.show {
60            display: flex;
61        }
62        .fb-panel-header {
63            padding: 16px;
64            border-bottom: 1px solid #e4e6eb;
65            display: flex;
66            justify-content: space-between;
67            align-items: center;
68            background: #f0f2f5;
69            border-radius: 8px 8px 0 0;
70        }
71        .fb-panel-header h3 {
72            margin: 0;
73            font-size: 17px;
74            font-weight: 600;
75            color: #050505;
76        }
77        .fb-panel-close {
78            background: none;
79            border: none;
80            font-size: 24px;
81            cursor: pointer;
82            color: #65676b;
83            padding: 0;
84            width: 36px;
85            height: 36px;
86            border-radius: 50%;
87            display: flex;
88            align-items: center;
89            justify-content: center;
90        }
91        .fb-panel-close:hover {
92            background: #e4e6eb;
93        }
94        .fb-panel-content {
95            padding: 16px;
96            overflow-y: auto;
97            flex: 1;
98        }
99        .fb-saved-comment-item {
100            background: #f0f2f5;
101            padding: 12px;
102            border-radius: 8px;
103            margin-bottom: 12px;
104        }
105        .fb-saved-comment-author {
106            font-weight: 600;
107            color: #050505;
108            margin-bottom: 4px;
109        }
110        .fb-saved-comment-text {
111            color: #050505;
112            margin-bottom: 8px;
113            line-height: 1.4;
114        }
115        .fb-saved-comment-meta {
116            font-size: 12px;
117            color: #65676b;
118            margin-bottom: 8px;
119        }
120        .fb-saved-comment-actions {
121            display: flex;
122            gap: 8px;
123        }
124        .fb-comment-action-btn {
125            background: #e4e6eb;
126            border: none;
127            padding: 6px 12px;
128            border-radius: 6px;
129            cursor: pointer;
130            font-size: 12px;
131            font-weight: 600;
132            color: #050505;
133        }
134        .fb-comment-action-btn:hover {
135            background: #d8dadf;
136        }
137        .fb-comment-action-btn.delete {
138            background: #f02849;
139            color: white;
140        }
141        .fb-comment-action-btn.delete:hover {
142            background: #d02343;
143        }
144        .fb-view-saved-btn {
145            position: fixed;
146            bottom: 20px;
147            right: 20px;
148            background: #1877f2;
149            color: white;
150            border: none;
151            padding: 12px 20px;
152            border-radius: 24px;
153            cursor: pointer;
154            font-size: 14px;
155            font-weight: 600;
156            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
157            z-index: 9998;
158            display: flex;
159            align-items: center;
160            gap: 8px;
161        }
162        .fb-view-saved-btn:hover {
163            background: #166fe5;
164        }
165        .fb-saved-count {
166            background: #42b72a;
167            padding: 2px 8px;
168            border-radius: 12px;
169            font-size: 12px;
170        }
171        .fb-empty-state {
172            text-align: center;
173            padding: 40px 20px;
174            color: #65676b;
175        }
176    `);
177
178    // Function to extract comment data
179    function extractCommentData(commentElement) {
180        try {
181            // Find author name
182            const authorElement = commentElement.querySelector('a[role="link"] span');
183            const author = authorElement ? authorElement.textContent.trim() : 'Unknown User';
184
185            // Find comment text - try multiple selectors
186            let commentText = '';
187            const textSelectors = [
188                'div[dir="auto"][style*="text-align"]',
189                'div.x193iq5w span',
190                'div[data-ad-comet-preview="message"] span'
191            ];
192            
193            for (const selector of textSelectors) {
194                const textElement = commentElement.querySelector(selector);
195                if (textElement && textElement.textContent.trim()) {
196                    commentText = textElement.textContent.trim();
197                    break;
198                }
199            }
200
201            // Find timestamp
202            const timeElement = commentElement.querySelector('a[href*="comment_id"] span');
203            const timestamp = timeElement ? timeElement.textContent.trim() : new Date().toLocaleString();
204
205            // Find post URL
206            const postLink = commentElement.querySelector('a[href*="comment_id"]');
207            const postUrl = postLink ? postLink.href : window.location.href;
208
209            return {
210                author,
211                text: commentText,
212                timestamp,
213                postUrl,
214                savedAt: new Date().toISOString()
215            };
216        } catch (error) {
217            console.error('Error extracting comment data:', error);
218            return null;
219        }
220    }
221
222    // Function to save a comment
223    async function saveComment(commentData) {
224        try {
225            const savedComments = await GM.getValue('savedComments', []);
226            
227            // Generate unique ID
228            const commentId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
229            commentData.id = commentId;
230            
231            savedComments.push(commentData);
232            await GM.setValue('savedComments', savedComments);
233            
234            console.log('Comment saved successfully:', commentData);
235            updateSavedCount();
236            return true;
237        } catch (error) {
238            console.error('Error saving comment:', error);
239            return false;
240        }
241    }
242
243    // Function to delete a saved comment
244    async function deleteComment(commentId) {
245        try {
246            const savedComments = await GM.getValue('savedComments', []);
247            const filteredComments = savedComments.filter(c => c.id !== commentId);
248            await GM.setValue('savedComments', filteredComments);
249            console.log('Comment deleted successfully');
250            updateSavedCount();
251            return true;
252        } catch (error) {
253            console.error('Error deleting comment:', error);
254            return false;
255        }
256    }
257
258    // Function to add save button to comments
259    function addSaveButtonToComment(commentElement) {
260        // Check if button already exists
261        if (commentElement.querySelector('.fb-comment-save-btn')) {
262            return;
263        }
264
265        // Find the action bar (like, reply buttons area)
266        const actionBar = commentElement.querySelector('div[role="toolbar"], div[aria-label*="actions"]');
267        if (!actionBar) {
268            return;
269        }
270
271        // Create save button
272        const saveBtn = document.createElement('button');
273        saveBtn.className = 'fb-comment-save-btn';
274        saveBtn.textContent = 'šŸ’¾ Save';
275        saveBtn.title = 'Save this comment';
276
277        saveBtn.addEventListener('click', async (e) => {
278            e.preventDefault();
279            e.stopPropagation();
280
281            const commentData = extractCommentData(commentElement);
282            if (commentData && commentData.text) {
283                const success = await saveComment(commentData);
284                if (success) {
285                    saveBtn.textContent = 'āœ“ Saved';
286                    saveBtn.classList.add('saved');
287                    setTimeout(() => {
288                        saveBtn.textContent = 'šŸ’¾ Save';
289                        saveBtn.classList.remove('saved');
290                    }, 2000);
291                }
292            } else {
293                alert('Could not extract comment data. Please try again.');
294            }
295        });
296
297        actionBar.appendChild(saveBtn);
298    }
299
300    // Function to update saved count badge
301    async function updateSavedCount() {
302        const savedComments = await GM.getValue('savedComments', []);
303        const countBadge = document.querySelector('.fb-saved-count');
304        if (countBadge) {
305            countBadge.textContent = savedComments.length;
306        }
307    }
308
309    // Function to render saved comments panel
310    async function renderSavedCommentsPanel() {
311        const savedComments = await GM.getValue('savedComments', []);
312        const panel = document.querySelector('.fb-saved-comments-panel');
313        const content = panel.querySelector('.fb-panel-content');
314
315        if (savedComments.length === 0) {
316            content.innerHTML = '<div class="fb-empty-state">No saved comments yet.<br>Click the "šŸ’¾ Save" button on any comment to save it.</div>';
317            return;
318        }
319
320        content.innerHTML = savedComments.map(comment => `
321            <div class="fb-saved-comment-item" data-comment-id="${comment.id}">
322                <div class="fb-saved-comment-author">${comment.author}</div>
323                <div class="fb-saved-comment-text">${comment.text}</div>
324                <div class="fb-saved-comment-meta">
325                    Saved: ${new Date(comment.savedAt).toLocaleString()}<br>
326                    Original: ${comment.timestamp}
327                </div>
328                <div class="fb-saved-comment-actions">
329                    <button class="fb-comment-action-btn view-post" data-url="${comment.postUrl}">View Post</button>
330                    <button class="fb-comment-action-btn copy-text">Copy Text</button>
331                    <button class="fb-comment-action-btn delete">Delete</button>
332                </div>
333            </div>
334        `).join('');
335
336        // Add event listeners to action buttons
337        content.querySelectorAll('.view-post').forEach(btn => {
338            btn.addEventListener('click', () => {
339                const url = btn.getAttribute('data-url');
340                window.open(url, '_blank');
341            });
342        });
343
344        content.querySelectorAll('.copy-text').forEach(btn => {
345            btn.addEventListener('click', async () => {
346                const commentItem = btn.closest('.fb-saved-comment-item');
347                const text = commentItem.querySelector('.fb-saved-comment-text').textContent;
348                await GM.setClipboard(text);
349                btn.textContent = 'Copied!';
350                setTimeout(() => {
351                    btn.textContent = 'Copy Text';
352                }, 1500);
353            });
354        });
355
356        content.querySelectorAll('.delete').forEach(btn => {
357            btn.addEventListener('click', async () => {
358                const commentItem = btn.closest('.fb-saved-comment-item');
359                const commentId = commentItem.getAttribute('data-comment-id');
360                if (confirm('Are you sure you want to delete this saved comment?')) {
361                    await deleteComment(commentId);
362                    await renderSavedCommentsPanel();
363                }
364            });
365        });
366    }
367
368    // Function to create the saved comments panel
369    function createSavedCommentsPanel() {
370        const panel = document.createElement('div');
371        panel.className = 'fb-saved-comments-panel';
372        panel.innerHTML = `
373            <div class="fb-panel-header">
374                <h3>Saved Comments</h3>
375                <button class="fb-panel-close">Ɨ</button>
376            </div>
377            <div class="fb-panel-content"></div>
378        `;
379
380        document.body.appendChild(panel);
381
382        // Close button
383        panel.querySelector('.fb-panel-close').addEventListener('click', () => {
384            panel.classList.remove('show');
385        });
386
387        return panel;
388    }
389
390    // Function to create the view saved button
391    async function createViewSavedButton() {
392        const savedComments = await GM.getValue('savedComments', []);
393        
394        const btn = document.createElement('button');
395        btn.className = 'fb-view-saved-btn';
396        btn.innerHTML = `
397            šŸ“‹ Saved Comments
398            <span class="fb-saved-count">${savedComments.length}</span>
399        `;
400
401        btn.addEventListener('click', async () => {
402            const panel = document.querySelector('.fb-saved-comments-panel');
403            if (panel.classList.contains('show')) {
404                panel.classList.remove('show');
405            } else {
406                await renderSavedCommentsPanel();
407                panel.classList.add('show');
408            }
409        });
410
411        document.body.appendChild(btn);
412    }
413
414    // Function to scan and add buttons to all comments
415    function scanForComments() {
416        // Find all comment elements - Facebook uses various selectors
417        const commentSelectors = [
418            'div[role="article"]',
419            'div[aria-label*="Comment"]'
420        ];
421
422        const allComments = new Set();
423        commentSelectors.forEach(selector => {
424            document.querySelectorAll(selector).forEach(el => {
425                // Check if it looks like a comment (has text content and action buttons)
426                if (el.querySelector('div[role="toolbar"]') || el.querySelector('div[aria-label*="actions"]')) {
427                    allComments.add(el);
428                }
429            });
430        });
431
432        allComments.forEach(comment => {
433            addSaveButtonToComment(comment);
434        });
435
436        console.log(`Scanned and found ${allComments.size} comments`);
437    }
438
439    // Debounced scan function
440    const debouncedScan = debounce(scanForComments, 1000);
441
442    // Initialize the extension
443    async function init() {
444        console.log('Initializing Facebook Comment Saver...');
445
446        // Wait for body to be ready
447        TM_runBody(() => {
448            // Create UI elements
449            createSavedCommentsPanel();
450            createViewSavedButton();
451
452            // Initial scan
453            setTimeout(scanForComments, 2000);
454
455            // Set up MutationObserver to detect new comments
456            const observer = new MutationObserver(debouncedScan);
457            observer.observe(document.body, {
458                childList: true,
459                subtree: true
460            });
461
462            console.log('Facebook Comment Saver initialized successfully');
463        });
464    }
465
466    // Start the extension
467    init();
468})();
Facebook Comment Saver | Robomonkey