FB - Group Post Data Collector

Collect Facebook group posts data with selection interface and export to localhost API

// ==UserScript==
// @name         FB - Group Post Data Collector
// @description  Collect Facebook group posts data with selection interface and export to localhost API
// @namespace    https://github.com/alx/fb-group-post-export
// @supportURL   https://github.com/alx/fb-group-post-export/issues
// @version      1.1
// @author       alx (https://github.com/alx/)
// @match        https://www.facebook.com/groups/*
// @match        https://web.facebook.com/groups/*
// @noframes
// @grant        GM.info
// @grant        GM.registerMenuCommand
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @license      MIT; https://opensource.org/licenses/MIT
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB2AAAAdgB+lymcgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAanSURBVHic5ZtpjBVFEMd/u8vthsCi3OcGxQWBKJdEDEbuDypqNoGgxGgkWY8YDaIYFQ9MRCOJJ0Y80JhgVESiRpCVIAYIIAQPCEoQZCOIyuIB667IPj/8qzPzhveANzPvvX3LP5nMVHdPT1V3VVd1zQwUNroDK4A/gB3A1Pyyk1sUA18DCeBPoBH4FxiRT6ZyiXFI+C+AFsAdRj+TSSfF8fOVM0y281LgP+BwHnnJC75FM15u9BtGV+aNoxyiG7L5H3xlNUgTyjLpqFBNYDJQBKw0ejDQE9gC1GbSUaEOwCQ7r0pDN2sUA78CDUCpla1G9j86X0zlEqOQsJ8b3RaoQ6pfkmlnhWgCQXW/Ag1CNXAi086awwCcVfbfATgOHEReAGAXMone+WIql7geCbvE6F5G7wjbYaGZQFDdpwToZo99aKHrbPQypAGT0t3QnFCBhN1idAlyff8A7cJ2WkgmEFT/S4GOwDoUB4RCIQ3ARDt/ZudL7LwyRdtmhzbAMZT5aWll5wGzgfb5YiqXmIDsf3ncHReKCQTtfwDwGtA1P+zkHi7708/o142ekvaOZoQeKPvzva8sVPYnFQrBBCahuN+pf+jsTyoUygDAWbr7KwF+J4vZnxZxdJICFYQLTxvQzi5h9HCgE7AGOGp9jgGOAJujs5kd3IAECHvc7+vrYSubY/QUo9+Ni9lsrAG97LwN+NiuDwPvAVuN/s7oA0avBjbatdvpQYHa/1w0S1Xo7W0CT7hZRj9o9IdGX4yX7FhodTnJ/jRlLzAerVGrkNC9UAS4A9gf10Oa8gDkJPvTlAdgIooAXf4/K/YflxssBc5HA9rdynoDQ+z6HGAYnu12N7qD0RUo5AXoAlxtbb9Cb4FaAFeidFid3XschccNMckQGpXIR0dxfemO+faMy9LUH0RvikIjqga0QdvS1sBHQH3E/hzGo3RX0P1twlsAy9BXIq8AQ2N6bsa4CM1EnHaZKvuzyZ4zMNB2P1onWhISURdBp0Fx2uHlKORdg+y8E7L5GmBnoG09ihFCyxHVBH6280jgRsKbwGbgJ7sOrvbj0aaohuTPXzoCfYHfyPNC+CrRF7tdvv7SZX/SHfdEYb7o9E3OqI9K9JHidBTnf5JhH+vsXAE8DexGUV8Rmvmu6PO3BNK07sCzaK9RHY39+DAUMZip8AB3kzyrK5Dwg43e4Gu7By18baIwmw2UIWa/yfC+3iiOqAfeRsGO20AttutHrG2RtTsUnd3s4ChnlqfrjFT5MWA9EvIJqxuFIkC/Royzui5Gb6WJwm1XS9PUlyFbriNZwEa8V18gt1aF8ggJ5G3KUYYogbbRTRLViMGKFHW98AboLxTBzQJewhuEJWiWHc7Fyxk8ihbaBPBCVriPAe5z1QmB8g7Aj1a3DAnm0Bt98Oy04QhwJ/L9RXgmMsPKE8B9WZMgIh5HDN4cKH8e792e+5StL/IY6fz7duApu95m9y3AG4xYEHdWuMbOPX1lA4Bb0QJ5O1rlxyIfXorc2jIkJGjnNw251aHoDdBtdp/LN7rnNDm4rO1io8fiqb6L2AaiPzwSSGNap+hnttXvRKGww5dWXp7inryhGH28vAjvD46jaKFz6vwmnrY5tZ+Xoq9StBdwqv9+oH4vWixTDVpeMA5vZQ8etSjEnYYXco+wut0km18nZN/HAn34w9xi9EvML9kRJXPMRbORANYCM4E+QKs07VuhAUkAN/nKL0T7eqc5y/G8yQe+dt2sbAtNAC52/5tT/6FRhgSsxEtqbMWb/XKU80ugMNjFADM42d+PxPMkecUwpIp1nJyPKwKuRTPnojj/sZHkNz/uReeLJO9MX7bymb6y66zsuZjkCI2VxkhVoHwQml0nbB3a269GMzmVZCHddz970dfeDm1RMOQixlo73Ppwb6zSZIh+xsQekhexCXgMrgeu4tQr9RiU0U1YWz/m4Q1gre9osPLpUYWIgipj4klf2RDEbCPwAMn5ueHI17+FNKEaeQCnJQtJxnD05Wc9cEGg7lO7Z0wMcoSGy8q4ha8EL4U1x9duLMoLpHKPjSheCIazg9A+P4EW2SC2W13f6GKEh0tQuK2rW63X4tn3Ajz3uAFtYEYD/dGq77d3kMbcgvdyZVGaZx9C4XDoFHgcuAsxudQYcWrpdn/zjT4MXHOavvqj312dBp1AW95UecqWVn8gRV0kZJoUbY9Uuw+KAdohM9hHcia3FsX7qdAK+fvgTNaTXsBipPqbifgqLA70AN5B+fhTpavjPhqBh+IW5n827DI9G+eQwwAAAABJRU5ErkJggg==
// @run-at       document-start
// ==/UserScript==

/*
    FB - Group Post Data Collector
    
    This userscript collects Facebook group posts data and allows users to select
    which posts to send to a localhost API endpoint.
    
    Features:
    - Detects posts on Facebook group pages
    - Expands "See more" links to get full content
    - Shows selection dialog with checkboxes for each post
    - Sends selected posts to http://localhost:8080/fetch_post
    
    v1.0 :: January 2025
        Initial release
        Post detection and extraction
        User selection interface
        API integration with localhost endpoint
*/

(async function() {
    'use strict';
    
    // Global variables
    const SCRIPT_VERSION = 'v' + GM.info.script.version;
    let isCollecting = false;
    let collectedPosts = [];
    let selectionDialog = null;
    
    // CSS for UI elements
    const CSS_STYLES = `
        /* Collection Button */
        .fbgpc-button {
            position: fixed;
            top: 80px;
            right: 20px;
            z-index: 10000;
            background: #1877f2;
            color: white;
            border: none;
            border-radius: 8px;
            padding: 12px 16px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            transition: all 0.2s ease;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .fbgpc-button:hover {
            background: #166fe5;
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
        }
        
        .fbgpc-button:disabled {
            background: #8a8d91;
            cursor: not-allowed;
            transform: none;
        }
        
        /* Dialog Overlay */
        .fbgpc-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.6);
            z-index: 10001;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        /* Dialog Container */
        .fbgpc-dialog {
            background: white;
            border-radius: 12px;
            width: 90%;
            max-width: 700px;
            max-height: 80vh;
            display: flex;
            flex-direction: column;
            box-shadow: 0 8px 32px rgba(0,0,0,0.3);
        }
        
        /* Dialog Header */
        .fbgpc-header {
            padding: 20px;
            border-bottom: 1px solid #e4e6ea;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .fbgpc-title {
            font-size: 18px;
            font-weight: 600;
            color: #1c1e21;
        }
        
        .fbgpc-close {
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #65676b;
            padding: 4px;
        }
        
        /* Dialog Content */
        .fbgpc-content {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
        }
        
        .fbgpc-controls {
            margin-bottom: 16px;
            display: flex;
            gap: 12px;
        }
        
        .fbgpc-control-btn {
            background: #f0f2f5;
            border: none;
            border-radius: 6px;
            padding: 6px 12px;
            font-size: 12px;
            cursor: pointer;
            color: #65676b;
        }
        
        .fbgpc-control-btn:hover {
            background: #e4e6ea;
        }
        
        /* Post List */
        .fbgpc-posts {
            display: flex;
            flex-direction: column;
            gap: 12px;
        }
        
        .fbgpc-post-item {
            border: 1px solid #e4e6ea;
            border-radius: 8px;
            padding: 16px;
            display: flex;
            gap: 12px;
        }
        
        .fbgpc-post-checkbox {
            margin-top: 4px;
        }
        
        .fbgpc-post-content {
            flex: 1;
        }
        
        .fbgpc-post-url {
            font-size: 12px;
            color: #65676b;
            margin-bottom: 8px;
            word-break: break-all;
        }
        
        .fbgpc-post-text {
            font-size: 14px;
            color: #1c1e21;
            line-height: 1.4;
        }
        
        .fbgpc-post-text.truncated {
            max-height: 60px;
            overflow: hidden;
        }
        
        /* Image Preview Styles */
        .fbgpc-post-images {
            margin-top: 12px;
        }
        
        .fbgpc-images-header {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            font-size: 12px;
            color: #65676b;
            font-weight: 600;
        }
        
        .fbgpc-images-toggle {
            background: none;
            border: none;
            cursor: pointer;
            padding: 2px;
            color: #1877f2;
            font-size: 12px;
        }
        
        .fbgpc-images-toggle:hover {
            text-decoration: underline;
        }
        
        .fbgpc-images-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
            gap: 6px;
            max-height: 200px;
            overflow-y: auto;
        }
        
        .fbgpc-image-item {
            position: relative;
            border-radius: 4px;
            overflow: hidden;
            background: #f0f2f5;
            aspect-ratio: 1;
        }
        
        .fbgpc-image-preview {
            width: 100%;
            height: 100%;
            object-fit: cover;
            cursor: pointer;
        }
        
        .fbgpc-image-checkbox {
            position: absolute;
            top: 4px;
            right: 4px;
            width: 16px;
            height: 16px;
            cursor: pointer;
        }
        
        .fbgpc-image-info {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            background: rgba(0,0,0,0.7);
            color: white;
            font-size: 10px;
            padding: 2px 4px;
            opacity: 0;
            transition: opacity 0.2s ease;
        }
        
        .fbgpc-image-item:hover .fbgpc-image-info {
            opacity: 1;
        }
        
        .fbgpc-include-images {
            display: flex;
            align-items: center;
            gap: 6px;
            margin-top: 8px;
            font-size: 12px;
        }
        
        .fbgpc-include-images input[type="checkbox"] {
            margin: 0;
        }
        
        /* Page Post Checkbox Overlay */
        .fbgpc-page-post-overlay {
            position: absolute;
            top: 8px;
            right: 8px;
            z-index: 9999;
            background: rgba(255, 255, 255, 0.95);
            border: 2px solid #1877f2;
            border-radius: 6px;
            padding: 6px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 12px;
            font-weight: 600;
            color: #1877f2;
            cursor: pointer;
            transition: all 0.2s ease;
            user-select: none;
        }
        
        .fbgpc-page-post-overlay:hover {
            background: rgba(24, 119, 242, 0.1);
            transform: scale(1.02);
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
        }
        
        .fbgpc-page-post-overlay.unchecked {
            background: rgba(245, 245, 245, 0.95);
            border-color: #8a8d91;
            color: #8a8d91;
        }
        
        .fbgpc-page-post-overlay.unchecked:hover {
            background: rgba(200, 200, 200, 0.1);
        }
        
        .fbgpc-page-post-checkbox {
            margin: 0;
            width: 16px;
            height: 16px;
            cursor: pointer;
        }
        
        .fbgpc-page-post-counter {
            font-size: 11px;
            font-weight: 500;
        }
        
        /* Mark posts with relative positioning for overlay */
        .fbgpc-post-container {
            position: relative !important;
        }
        
        /* Bulk Controls */
        .fbgpc-bulk-controls {
            position: fixed;
            top: 140px;
            right: 20px;
            z-index: 10000;
            background: white;
            border: 1px solid #e4e6ea;
            border-radius: 8px;
            padding: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            display: flex;
            flex-direction: column;
            gap: 4px;
            font-size: 12px;
        }
        
        .fbgpc-bulk-btn {
            background: #f0f2f5;
            border: none;
            border-radius: 4px;
            padding: 4px 8px;
            font-size: 12px;
            cursor: pointer;
            color: #65676b;
            transition: background 0.2s ease;
        }
        
        .fbgpc-bulk-btn:hover {
            background: #e4e6ea;
        }
        
        /* Dialog Footer */
        .fbgpc-footer {
            padding: 20px;
            border-top: 1px solid #e4e6ea;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .fbgpc-status {
            font-size: 14px;
            color: #65676b;
        }
        
        .fbgpc-actions {
            display: flex;
            gap: 12px;
        }
        
        .fbgpc-btn {
            border: none;
            border-radius: 6px;
            padding: 8px 16px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s ease;
        }
        
        .fbgpc-btn-primary {
            background: #1877f2;
            color: white;
        }
        
        .fbgpc-btn-primary:hover {
            background: #166fe5;
        }
        
        .fbgpc-btn-primary:disabled {
            background: #8a8d91;
            cursor: not-allowed;
        }
        
        .fbgpc-btn-secondary {
            background: #f0f2f5;
            color: #1c1e21;
        }
        
        .fbgpc-btn-secondary:hover {
            background: #e4e6ea;
        }
        
        /* Loading Spinner */
        .fbgpc-spinner {
            display: inline-block;
            width: 16px;
            height: 16px;
            border: 2px solid #ffffff50;
            border-top: 2px solid #ffffff;
            border-radius: 50%;
            animation: fbgpc-spin 1s linear infinite;
        }
        
        @keyframes fbgpc-spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        
        /* Toast Notification */
        .fbgpc-toast {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #42b883;
            color: white;
            padding: 12px 16px;
            border-radius: 6px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            z-index: 10002;
            font-size: 14px;
            font-weight: 500;
            animation: fbgpc-toast-in 0.3s ease;
        }
        
        .fbgpc-toast.error {
            background: #e74c3c;
        }
        
        @keyframes fbgpc-toast-in {
            from { opacity: 0; transform: translateX(100%); }
            to { opacity: 1; transform: translateX(0); }
        }
    `;
    
    // Inject CSS
    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = CSS_STYLES;
        document.head.appendChild(style);
    }
    
    // Create collection button
    function createCollectionButton() {
        const button = document.createElement('button');
        button.className = 'fbgpc-button';
        button.innerHTML = `
            <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
                <path d="M3 2a1 1 0 011-1h8a1 1 0 011 1v12a1 1 0 01-1 1H4a1 1 0 01-1-1V2zm1 0v12h8V2H4z"/>
                <path d="M6 4h4v1H6V4zm0 2h4v1H6V6zm0 2h4v1H6V8z"/>
            </svg>
            Collect Posts
        `;
        button.title = 'Collect Facebook group posts data';
        button.addEventListener('click', handleCollectionClick);
        document.body.appendChild(button);
        return button;
    }
    
    
    // Show toast notification
    function showToast(message, isError = false) {
        const toast = document.createElement('div');
        toast.className = `fbgpc-toast ${isError ? 'error' : ''}`;
        toast.textContent = message;
        document.body.appendChild(toast);
        
        setTimeout(() => {
            if (toast.parentNode) {
                toast.parentNode.removeChild(toast);
            }
        }, 4000);
    }
    
    // Collect all visible posts on the page (filtered by checkboxes)
    async function collectPosts() {
        collectedPosts = [];
        
        // Clean up removed posts first
        cleanupRemovedPosts();
        
        // Get only selected posts from our checkbox map
        const selectedPosts = Array.from(pagePostCheckboxes.entries())
            .filter(([postElement, data]) => data.checkbox.checked)
            .map(([postElement, data]) => postElement);
        
        console.log(`📊 Starting collection: Found ${selectedPosts.length} selected posts out of ${pagePostCheckboxes.size} total posts`);
        
        let successCount = 0;
        let failureCount = 0;
        
        for (let i = 0; i < selectedPosts.length; i++) {
            const post = selectedPosts[i];
            console.log(`🔄 Processing post ${i + 1}/${selectedPosts.length}...`);
            
            try {
                const postData = await extractPostData(post, i + 1, selectedPosts.length);
                if (postData) {
                    collectedPosts.push(postData);
                    successCount++;
                    console.log(`✅ Post ${i + 1}: Successfully extracted data (ID: ${postData.post_id})`);
                } else {
                    failureCount++;
                    console.warn(`⚠️ Post ${i + 1}: extractPostData returned null/empty`);
                }
            } catch (error) {
                failureCount++;
                console.error(`❌ Post ${i + 1}: Error extracting post data:`, error);
                console.error('Post element:', post);
            }
        }
        
        console.log(`📈 Collection complete: ${successCount} successful, ${failureCount} failed, ${collectedPosts.length} total posts in array`);
        
        // Additional validation
        if (selectedPosts.length > 0 && collectedPosts.length === 0) {
            console.error('🚨 CRITICAL: No posts were successfully collected despite having selected posts!');
        }
    }
    
    // Extract data from a single post
    async function extractPostData(postElement, index, total) {
        console.log(`🔍 extractPostData: Starting extraction for post ${index}/${total}`);
        
        // Update button with progress
        const button = document.querySelector('.fbgpc-button');
        button.innerHTML = `
            <div class="fbgpc-spinner"></div>
            Processing ${index}/${total}...
        `;
        
        try {
            // Generate post URL and extract ID
            const postUrl = findPostLink(postElement);
            if (!postUrl) {
                console.warn(`🔗 Post ${index}: No post URL generated`);
                console.log('Post element:', postElement);
                return null;
            }
            console.log(`🔗 Post ${index}: Generated post URL:`, postUrl);

            const postId = extractPostId(postUrl);

            if (!postId) {
                console.warn(`🆔 Post ${index}: Could not extract post ID from URL:`, postUrl);
                return null;
            }
            
            console.log(`🆔 Post ${index}: Extracted ID: ${postId}`);
            console.log(`🌐 Post ${index}: Cleaned URL: ${postUrl}`);
            
            // Use pre-expanded content if available, otherwise extract current content
            // (expandSeeMore is now called during checkbox selection)
            let postContent;
            const expandedData = expandedContentMap.get(postElement);
            
            if (expandedData && expandedData.expandedContent) {
                postContent = expandedData.expandedContent;
                console.log(`📋 Post ${index}: Using pre-expanded content (${postContent.length} chars)`);
            } else {
                // Fallback to current content extraction if no expanded content is stored
                console.log(`📄 Post ${index}: No pre-expanded content, using fallback extraction`);
                postContent = extractPostContent(postElement);
                
                if (postContent) {
                    console.log(`📄 Post ${index}: Fallback extraction successful (${postContent.length} chars)`);
                } else {
                    console.warn(`📄 Post ${index}: Fallback extraction also failed`);
                }
            }
            
            if (!postContent || postContent.trim().length === 0) {
                console.warn(`📝 Post ${index}: No content found (content: ${postContent ? 'empty string' : 'null'})`);
                return null;
            }
            
            // Extract images from post
            const postImages = extractPostImages(postElement);
            console.log(`📸 Post ${index}: Found ${postImages.length} images`);
            
            const result = {
                post_id: postId,
                post_url: postUrl,
                post_content: postContent.trim(),
                images: postImages,
                includeImages: postImages.length > 0 // Default to true if images exist
            };
            
            console.log(`✅ Post ${index}: Successfully created post data object`);
            return result;
            
        } catch (error) {
            console.error(`❌ Post ${index}: Unexpected error in extractPostData:`, error);
            console.error(`❌ Post ${index}: Error stack:`, error.stack);
            return null;
        }
    }
    
    // Generate post link from document URL (group_id) and image URL (post_id)
    function findPostLink(postElement) {
        console.log(`🔍 findPostLink: Generating post link from document URL and image URLs`);
        
        // Step 1: Extract group_id from current document URL
        const groupId = extractGroupIdFromDocument();
        if (!groupId) {
            console.warn(`⚠️ findPostLink: Could not extract group_id from document URL: ${window.location.href}`);
            return null;
        }
        console.log(`🆔 findPostLink: Extracted group_id: ${groupId}`);
        
        // Step 2: Find post_id from image URLs in the post element
        const postId = extractPostIdFromImages(postElement);
        if (!postId) {
            console.warn(`⚠️ findPostLink: Could not extract post_id from image URLs in post element`);
            return null;
        }
        console.log(`🆔 findPostLink: Extracted post_id: ${postId}`);
        
        // Step 3: Generate the Facebook group post URL
        const postUrl = `https://www.facebook.com/groups/${groupId}/posts/${postId}`;
        console.log(`✅ findPostLink: Generated post URL: ${postUrl}`);
        
        return postUrl;
    }
    
    // Extract group_id from the current document URL
    function extractGroupIdFromDocument() {
        const url = window.location.href;
        console.log(`🔍 extractGroupIdFromDocument: Parsing URL: ${url}`);
        
        // Match pattern: /groups/{groupId}
        const groupIdMatch = url.match(/\/groups\/(\d+)/);
        if (groupIdMatch && groupIdMatch[1]) {
            return groupIdMatch[1];
        }
        
        console.warn(`⚠️ extractGroupIdFromDocument: No group ID found in URL`);
        return null;
    }
    
    // Extract post_id from image URLs containing set=pcb.{postId} pattern
    function extractPostIdFromImages(postElement) {
        console.log(`🔍 extractPostIdFromImages: Searching for image URLs with post_id`);
        
        // Look for img elements with src or data-src containing set=pcb pattern
        const imgElements = postElement.querySelectorAll('img');
        console.log(`   Found ${imgElements.length} img elements to check`);
        
        for (let i = 0; i < imgElements.length; i++) {
            const img = imgElements[i];
            const src = img.src || img.getAttribute('data-src') || img.getAttribute('src');
            
            if (src) {
                console.log(`   Img ${i + 1}: src="${src}"`);
                const postId = extractPostIdFromUrl(src);
                if (postId) {
                    console.log(`   ✅ Found post_id in img src: ${postId}`);
                    return postId;
                }
            }
        }
        
        // Look for anchor elements with href containing set=pcb pattern
        const aElements = postElement.querySelectorAll('a[href*="set=pcb"]');
        console.log(`   Found ${aElements.length} anchor elements with set=pcb to check`);
        
        for (let i = 0; i < aElements.length; i++) {
            const anchor = aElements[i];
            const href = anchor.getAttribute('href');
            
            if (href) {
                console.log(`   Anchor ${i + 1}: href="${href}"`);
                const postId = extractPostIdFromUrl(href);
                if (postId) {
                    console.log(`   ✅ Found post_id in anchor href: ${postId}`);
                    return postId;
                }
            }
        }
        
        console.warn(`⚠️ extractPostIdFromImages: No post_id found in any image or anchor URLs`);
        return null;
    }
    
    // Extract post_id from URL containing set=pcb.{postId} parameter
    function extractPostIdFromUrl(url) {
        if (!url) return null;
        
        // Look for set=pcb.{postId} pattern
        const pcbMatch = url.match(/[?&]set=pcb\.(\d+)/);
        if (pcbMatch && pcbMatch[1]) {
            return pcbMatch[1];
        }
        
        return null;
    }
    
    // Extract post ID from URL
    function extractPostId(href) {
        if (!href) return null;
        
        // Try different patterns for post ID extraction
        const patterns = [
            /\/user\/(\d+)/,
            /\/posts\/(\d+)/,
            /story_fbid=(\d+)/,
            /permalink\/(\d+)/,
            /\/(\d+)\/$/
        ];
        
        for (const pattern of patterns) {
            const match = href.match(pattern);
            if (match && match[1]) {
                return match[1];
            }
        }
        
        return null;
    }
    
    // Clean and construct proper post URL
    function cleanPostUrl(href) {
        if (!href) return null;
        
        let url = href;
        
        // Convert relative URLs to absolute
        if (url.startsWith('/')) {
            url = 'https://www.facebook.com' + url;
        }
        
        // Remove query parameters and hash
        url = url.split('?')[0].split('#')[0];
        
        return url;
    }
    
    // Expand "See more" links to get full content - targets only specific Facebook pattern
    async function expandSeeMore(postElement) {
        try {
            // Add small delay to allow dynamic content to load
            await new Promise(resolve => setTimeout(resolve, 200));
            
            // Target only the specific Facebook "See more" button pattern:
            // <div role="button" tabindex="0">See more</div>
            const specificSelector = 'div[role="button"][tabindex="0"]';

            // Fresh DOM query to catch dynamically loaded content
            const candidateElements = Array.from(postElement.querySelectorAll(specificSelector));
            
            console.log(`🔍 Found ${candidateElements.length} div[role="button"][tabindex="0"] elements to check`);
            
            for (let i = 0; i < candidateElements.length; i++) {
                const element = candidateElements[i];
                const text = element.textContent?.trim().toLowerCase();
                
                // Log each candidate element
                console.log(`   Element ${i + 1}: text="${text}"`);
                
                // Exact match for "See more" only (case-insensitive)
                if (text === 'see more') {
                    // Enhanced visibility check
                    const isVisible = element.offsetParent !== null && 
                                    element.offsetWidth > 0 && 
                                    element.offsetHeight > 0 &&
                                    window.getComputedStyle(element).visibility !== 'hidden' &&
                                    window.getComputedStyle(element).display !== 'none';
                    
                    if (isVisible) {
                        console.log(`✅ Found exact Facebook "See more" button match`);
                        
                        try {
                            // Simplified click approach for this specific pattern
                            const clickSuccessful = await clickFacebookButton(element);
                            
                            if (clickSuccessful) {
                                console.log('✅ Successfully clicked Facebook "See more" button');
                                // Wait longer for content to expand (increased from 800ms to 1500ms)
                                await new Promise(resolve => setTimeout(resolve, 1500));
                                return true; // Return true if expanded
                            }
                        } catch (clickError) {
                            console.error('❌ Error clicking Facebook "See more" button:', clickError);
                        }
                    } else {
                        console.log(`⚠️  Facebook "See more" button found but not visible`);
                    }
                } else if (text && text.length > 0) {
                    // Log non-matching text for debugging
                    console.log(`   ⏭️  Skipping element with text "${text}" (not exact "see more" match)`);
                }
            }
            
            console.log('ℹ️  No Facebook "See more" button found or clicked successfully');
            return false; // Return false if no "See more" found
        } catch (error) {
            console.error('❌ Error in expandSeeMore:', error);
            return false;
        }
    }
    
    // Simplified click function optimized for Facebook "See more" buttons
    async function clickFacebookButton(element) {
        try {
            // Approach 1: Standard click (most reliable for Facebook)
            element.click();
            await new Promise(resolve => setTimeout(resolve, 100));
            
            // Approach 2: Mouse events for Facebook's event system
            // const clickEvent = new MouseEvent('click', {
            //     bubbles: true,
            //     cancelable: true,
            //     view: window,
            //     detail: 1
            // });
            // element.dispatchEvent(clickEvent);
            
            // Brief delay to allow Facebook's handlers to process
            await new Promise(resolve => setTimeout(resolve, 50));
            
            return true;
        } catch (error) {
            console.error('❌ Error in clickFacebookButton:', error);
            return false;
        }
    }
    
    
    // Extract images from post
    function extractPostImages(postElement) {
        try {
            const images = [];
            
            // Find all img elements within the post
            const imgElements = postElement.querySelectorAll('img');
            
            for (const img of imgElements) {
                const src = img.src || img.getAttribute('data-src');
                if (src && !src.startsWith('data:') && !src.includes('emoji')) {
                    // Skip small images (likely icons or avatars)
                    const width = img.naturalWidth || img.width || 0;
                    const height = img.naturalHeight || img.height || 0;
                    
                    if (width >= 100 && height >= 100) {
                        images.push({
                            url: src,
                            alt: img.alt || '',
                            width: width,
                            height: height,
                            selected: true // Default to selected
                        });
                    }
                }
            }
            
            // Also check for background images in divs
            const divsWithBgImages = postElement.querySelectorAll('div[style*="background-image"]');
            for (const div of divsWithBgImages) {
                const style = div.getAttribute('style');
                const matches = style.match(/background-image:\s*url\(['"]?([^'")]+)['"]?\)/);
                if (matches && matches[1]) {
                    const url = matches[1];
                    if (!url.startsWith('data:') && !url.includes('emoji')) {
                        images.push({
                            url: url,
                            alt: '',
                            width: 0,
                            height: 0,
                            selected: true
                        });
                    }
                }
            }
            
            console.log(`📸 Found ${images.length} images in post`);
            return images;
            
        } catch (error) {
            console.error('Error extracting post images:', error);
            return [];
        }
    }

    // Clean innerText content by removing UI elements and artifacts
    function cleanInnerTextContent(innerText) {
        if (!innerText || typeof innerText !== 'string') {
            return null;
        }
        
        console.log('🧹 Original innerText length:', innerText.length);
        console.log('🔍 First 200 chars:', innerText.substring(0, 200));
        
        // Step 1: Split into lines for processing
        let lines = innerText.split('\n');
        console.log(`📄 Total lines: ${lines.length}`);
        
        // Step 2: Advanced content start detection
        let contentStartIndex = -1;
        
        // Method 1: Look for content patterns (ALL CAPS, meaningful phrases)
        const contentPatterns = [
            /^[A-Z][A-Z\s\-_]{10,}$/,  // ALL CAPS titles like "FEMALE ROOMATE - "
            /^(Looking for|Searching for|Available|For rent|For sale)/i,
            /^(Hello|Hi|Bonjour|Hey)\s/i,
            /^(Apartment|House|Room|Car|Job)/i,
            /^\d+€|\$\d+|€\d+/,  // Price indicators
            /^[A-Za-z]{3,}.*[a-zA-Z]{3,}/  // Lines with multiple real words
        ];
        
        for (let i = 1; i < lines.length; i++) {
            const line = lines[i].trim();
            
            // Skip empty lines
            if (!line) continue;
            
            // Skip obvious UI noise (single chars, scattered chars)
            if (line.length <= 3) continue;
            if (line.match(/^[a-zA-Z]$/)) continue; // Single letters
            if (line.match(/^[0-9]$/)) continue; // Single numbers
            if (line.match(/^·$/)) continue; // Middle dots
            if (line === 'Follow') continue; // Common UI element
            
            // Skip scattered characters pattern (multiple single chars separated by spaces)
            if (line.match(/^([a-zA-Z]\s){3,}[a-zA-Z]?$/)) {
                console.log(`⏭️  Skipping scattered chars: "${line}"`);
                continue;
            }
            
            // Skip author name/UI combinations
            if (line.includes('·') && line.includes('Follow')) {
                console.log(`⏭️  Skipping author/UI line: "${line}"`);
                continue;
            }
            
            // Check for content patterns
            let isContent = false;
            for (const pattern of contentPatterns) {
                if (pattern.test(line)) {
                    console.log(`✅ Found content pattern match: "${line}"`);
                    isContent = true;
                    break;
                }
            }
            
            if (isContent) {
                contentStartIndex = i;
                break;
            }
            
            // Fallback: lines with reasonable length and multiple words
            if (line.length > 15 && 
                line.split(/\s+/).length >= 3 &&
                !line.includes('·')) {
                console.log(`📝 Found reasonable content fallback: "${line}"`);
                contentStartIndex = i;
                break;
            }
        }
        
        // Method 2: If no pattern match, look for longest meaningful lines
        if (contentStartIndex === -1) {
            console.log('🔄 No pattern match, trying longest line approach...');
            
            let bestLine = { index: -1, score: 0 };
            
            for (let i = 0; i < lines.length; i++) {
                const line = lines[i].trim();
                
                if (!line || line.length < 10) continue;
                if (line.includes('Follow') && line.includes('·')) continue;
                if (line.match(/^([a-zA-Z]\s){3,}/)) continue; // Skip scattered chars
                
                // Score based on length and word count
                const wordCount = line.split(/\s+/).length;
                const score = line.length + (wordCount * 5);
                
                if (score > bestLine.score && wordCount >= 3) {
                    bestLine = { index: i, score: score };
                    console.log(`🎯 New best line (score ${score}): "${line}"`);
                }
            }
            
            if (bestLine.index !== -1) {
                contentStartIndex = bestLine.index;
            }
        }
        
        if (contentStartIndex === -1) {
            console.log('⚠️  Could not find content start');
            return null;
        }
        
        console.log(`📍 Content starts at line ${contentStartIndex}: "${lines[contentStartIndex].trim()}"`);
        
        // Step 3: Find content end - look for engagement/comment section markers
        let contentEndIndex = lines.length;
        const endMarkers = [
            /^·\s*(See original|Rate this translation)/i,
            /^\+\d+$/,  // +8, +10, etc.
            /^All reactions:?$/i,
            /^\d+\s*\d*\s*(Like|Comment|Share)/i,  // "10 4 Like Comment Share"
            /^(Like|Comment|Share)$/i,
            /^View more comments/i,
            /^Write a public comment/i,
            /^❌$/,
            /^[A-Za-z\s]+ · Follow$/,  // "Name · Follow" pattern
            /^\d+[hdmyw]$/, // "25m", "1h", "2d", etc.
            /^See translation$/i,
            /^Reply$/i,
            /^View \d+ repl(y|ies)$/i
        ];
        
        for (let i = contentStartIndex + 1; i < lines.length; i++) {
            const line = lines[i].trim();
            
            // Check if this line matches any end marker
            for (const marker of endMarkers) {
                if (marker.test(line)) {
                    console.log(`🛑 Found end marker at line ${i}: "${line}"`);
                    contentEndIndex = i;
                    break;
                }
            }
            
            if (contentEndIndex < lines.length) break;
            
            // Also end if we hit a line that looks like user comments (Name + short message)
            if (line.length < 100 && 
                i < lines.length - 2 && 
                lines[i + 1].trim().match(/^\d+[hdmyw]$/)) { // Followed by timestamp
                contentEndIndex = i;
                console.log(`🛑 Found comment pattern at line ${i}: "${line}"`);
                break;
            }
        }
        
        console.log(`📍 Content ends at line ${contentEndIndex}`);
        
        // Step 4: Extract and clean the main content
        if (contentStartIndex === -1 || contentStartIndex >= contentEndIndex) {
            console.log('⚠️  Could not find valid content boundaries');
            return null;
        }
        
        let mainContent = lines.slice(contentStartIndex, contentEndIndex).join('\n');
        
        // Step 5: Final cleaning
        mainContent = mainContent
            .replace(/\n{3,}/g, '\n\n')  // Reduce excessive newlines
            .replace(/·\s*(See original|Rate this translation)/gi, '')  // Remove translation artifacts
            .replace(/^\s+|\s+$/g, '')   // Trim whitespace
            .replace(/\s+/g, ' ')        // Normalize spaces (after newline processing)
            .trim();
        
        console.log('✅ Cleaned content length:', mainContent.length);
        console.log('📝 Final content preview:', mainContent.substring(0, 150) + '...');
        
        return mainContent.length > 30 ? mainContent : null;
    }

    // Validate cleaned content quality
    function validateContentQuality(content) {
        if (!content || typeof content !== 'string') {
            return { valid: false, reason: 'Empty or invalid content' };
        }
        
        // Check minimum length
        if (content.length < 30) {
            return { valid: false, reason: 'Content too short' };
        }
        
        // Check for common UI artifacts that shouldn't be in cleaned content
        const uiArtifacts = [
            'Like Comment Share',
            'All reactions:',
            'Write a public comment',
            '❌',
            /^\+\d+$/,
            /^\d+\s*\d*\s*Like/
        ];
        
        for (const artifact of uiArtifacts) {
            if (typeof artifact === 'string' && content.includes(artifact)) {
                return { valid: false, reason: `Contains UI artifact: "${artifact}"` };
            } else if (artifact instanceof RegExp && artifact.test(content)) {
                return { valid: false, reason: `Contains UI pattern: ${artifact}` };
            }
        }
        
        // Check content has reasonable word-to-character ratio (should contain spaces)
        const wordCount = content.split(/\s+/).length;
        const charCount = content.length;
        if (wordCount < charCount / 20) { // Less than 1 word per 20 characters suggests poor quality
            return { valid: false, reason: 'Poor word-to-character ratio' };
        }
        
        return { valid: true, wordCount, charCount };
    }

    // Extract text content from post
    function extractPostContent(postElement) {
        console.log(`📝 extractPostContent: Starting content extraction`);
        
        try {
            // First check if we have expanded content stored for this post element
            const expandedData = expandedContentMap.get(postElement);
            if (expandedData && expandedData.expandedContent) {
                console.log(`📋 Using stored expanded content (${expandedData.expandedContent.length} chars)`);
                return expandedData.expandedContent;
            }
            
            // Primary method: Use innerText with comprehensive cleaning
            if (postElement.innerText) {
                console.log(`🎯 Trying innerText extraction (${postElement.innerText.length} chars raw)`);
                const cleanedContent = cleanInnerTextContent(postElement.innerText);
                if (cleanedContent) {
                    const validation = validateContentQuality(cleanedContent);
                    if (validation.valid) {
                        console.log(`✅ Valid innerText content: ${validation.wordCount} words, ${validation.charCount} chars`);
                        return cleanedContent;
                    } else {
                        console.log(`⚠️ innerText content quality check failed: ${validation.reason}`);
                    }
                } else {
                    console.log(`⚠️ innerText cleaning returned null/empty`);
                }
            } else {
                console.log(`⚠️ No innerText available on post element`);
            }
            
            console.log('🔄 Falling back to selector-based extraction...');
            
            // Fallback: Try to find the main content area using selectors
            const contentSelectors = [
                '[data-testid="post_message"]',
                '[data-ad-preview="message"]',
                'div[data-testid="post_message_root"]',
                'div[dir="auto"]',
                '.userContent',
                '.text_exposed_root',
                '[role="article"] > div > div > div > div'
            ];
            
            for (let i = 0; i < contentSelectors.length; i++) {
                const selector = contentSelectors[i];
                console.log(`   Trying selector ${i + 1}/${contentSelectors.length}: ${selector}`);
                
                const contentElements = postElement.querySelectorAll(selector);
                console.log(`   Found ${contentElements.length} elements`);
                
                for (let j = 0; j < contentElements.length; j++) {
                    const contentElement = contentElements[j];
                    if (contentElement && contentElement.textContent) {
                        let content = contentElement.textContent;
                        console.log(`      Element ${j + 1}: ${content.length} chars raw`);
                        
                        // Skip if this looks like a navigation or UI element
                        if (content.toLowerCase().includes('like') && 
                            content.toLowerCase().includes('comment') && 
                            content.toLowerCase().includes('share')) {
                            console.log(`      Skipping UI element`);
                            continue;
                        }
                        
                        // Clean up the content
                        content = content
                            .replace(/\s+/g, ' ')  // Normalize whitespace
                            .replace(/^\s+|\s+$/g, '')  // Trim
                            .replace(/See more\.?$/i, '')  // Remove trailing "See more"
                            .replace(/Show more\.?$/i, '')  // Remove trailing "Show more"
                            .replace(/Continue reading\.?$/i, '') // Remove "Continue reading"
                            .replace(/\.\.\./g, ''); // Remove ellipsis
                        
                        if (content.length > 30 && content.length < 10000) {  // Reasonable content length
                            console.log(`   ✅ Found valid content via selector (${content.length} chars)`);
                            return content;
                        } else {
                            console.log(`      Content length ${content.length} not in valid range (30-10000)`);
                        }
                    }
                }
            }
            
            console.log('🔄 Trying final fallback - raw text extraction...');
            
            // Enhanced fallback: get text but exclude common UI elements
            const allText = postElement.textContent || '';
            console.log(`Raw textContent: ${allText.length} chars`);
            
            if (allText.length === 0) {
                console.warn(`⚠️ Post element has no textContent`);
                return null;
            }
            
            const cleanText = allText
                .replace(/\s+/g, ' ')
                .replace(/Like.*Comment.*Share/gi, '') // Remove common UI text
                .replace(/\d+\s*(likes?|comments?|shares?)/gi, '') // Remove counts
                .trim()
                .substring(0, 2000);  // Reasonable limit
            
            console.log(`Final cleaned text: ${cleanText.length} chars`);
            
            if (cleanText.length > 30) {
                console.log(`✅ Using fallback content extraction (${cleanText.length} chars)`);
                return cleanText;
            } else {
                console.warn(`⚠️ Final fallback also failed - content too short: "${cleanText}"`);
                return null;
            }
            
        } catch (error) {
            console.error('❌ Error in extractPostContent:', error);
            console.error('Post element:', postElement);
            return null;
        }
    }
    
    // Main collection handler
    async function handleCollectionClick() {
        if (isCollecting) return;
        
        // Check if any posts are selected
        const selectedCount = Array.from(pagePostCheckboxes.values())
            .filter(data => data.checkbox.checked).length;
            
        console.log(`🎯 Collection clicked: ${selectedCount} posts selected`);
            
        if (selectedCount === 0) {
            showToast('No posts selected. Check some post checkboxes first!', true);
            return;
        }
        
        const button = document.querySelector('.fbgpc-button');
        button.disabled = true;
        button.innerHTML = `
            <div class="fbgpc-spinner"></div>
            Collecting...
        `;
        
        isCollecting = true;
        
        try {
            await collectPosts();
            
            console.log(`📊 Collection results: ${collectedPosts.length} posts collected from ${selectedCount} selected`);
            
            if (collectedPosts.length > 0) {
                console.log(`🎉 Showing selection dialog with ${collectedPosts.length} posts`);
                showSelectionDialog();
            } else {
                console.error(`🚨 PROBLEM: ${selectedCount} posts were selected but 0 were collected!`);
                showToast(`No posts could be processed from the ${selectedCount} selected posts. Check console for details.`, true);
            }
        } catch (error) {
            console.error('❌ Error in handleCollectionClick:', error);
            showToast('Error collecting posts: ' + error.message, true);
        } finally {
            isCollecting = false;
            button.disabled = false;
            updateCollectButtonCount(); // Restore count display
        }
    }
    
    // Global variables for page post management
    let pagePostCounter = 0;
    let pagePostCheckboxes = new Map(); // Map of post element -> checkbox data
    let expandedContentMap = new Map(); // Map of post element -> expanded content data
    let mutationObserver = null;
    
    // Find and add checkboxes to all posts on the page
    function scanAndMarkPosts() {
        const postSelectors = [
            'div[style*="border-radius: max(0px, min(var(--card-corner-radius), calc((100vw - 4px - 100%) * 9999))) / var(--card-corner-radius);"]'
        ];
        
        let newPostsFound = 0;
        
        for (const selector of postSelectors) {
            const elements = document.querySelectorAll(selector);
            
            for (const element of elements) {
                // Go 3 levels up to get post container
                let postContainer = element;
                for (let i = 0; i < 3; i++) {
                    postContainer = postContainer.parentElement;
                    if (!postContainer) break;
                }
                
                if (postContainer && !pagePostCheckboxes.has(postContainer)) {
                    addCheckboxToPost(postContainer);
                    newPostsFound++;
                }
            }
        }
        
        if (newPostsFound > 0) {
            console.log(`📋 Added checkboxes to ${newPostsFound} new posts`);
            updateCollectButtonCount();
        }
    }
    
    // Add a checkbox overlay to a specific post
    function addCheckboxToPost(postElement) {
        // Add relative positioning class
        postElement.classList.add('fbgpc-post-container');
        
        // Create overlay container
        const overlay = document.createElement('div');
        overlay.className = 'fbgpc-page-post-overlay';
        
        // Create checkbox
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'fbgpc-page-post-checkbox';
        checkbox.checked = false; // Default to unchecked
        
        // Create counter/label
        const counter = document.createElement('span');
        counter.className = 'fbgpc-page-post-counter';
        counter.textContent = `#${++pagePostCounter}`;
        
        // Add event listener
        checkbox.addEventListener('change', async () => {
            if (checkbox.checked) {
                overlay.classList.remove('unchecked');
                // Trigger content expansion when post is selected
                await handlePostSelection(postElement, overlay, counter);
            } else {
                overlay.classList.add('unchecked');
                // Remove expanded content from storage when unchecked
                expandedContentMap.delete(postElement);
            }
            updateCollectButtonCount();
        });
        
        // Make overlay clickable
        overlay.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            checkbox.checked = !checkbox.checked;
            checkbox.dispatchEvent(new Event('change'));
        });
        
        // Prevent checkbox click from bubbling
        checkbox.addEventListener('click', (e) => {
            e.stopPropagation();
        });
        
        // Assemble overlay
        overlay.appendChild(checkbox);
        overlay.appendChild(counter);
        
        // Add to post
        postElement.appendChild(overlay);
        
        // Store reference
        pagePostCheckboxes.set(postElement, {
            checkbox: checkbox,
            counter: counter,
            overlay: overlay,
            postId: pagePostCounter
        });
    }
    
    // Handle post selection - expand content immediately
    async function handlePostSelection(postElement, overlay, counter) {
        try {
            // Store original content before expansion
            const originalContent = extractPostContent(postElement);
            
            // Show loading indicator
            counter.textContent = '⏳';
            overlay.style.opacity = '0.7';
            
            // Attempt to expand "See more"
            const expandResult = await expandSeeMore(postElement);
            
            // Extract content after expansion attempt
            const finalContent = extractPostContent(postElement);
            
            // Store the expanded content data
            expandedContentMap.set(postElement, {
                originalContent: originalContent,
                expandedContent: finalContent,
                wasExpanded: expandResult,
                expandedAt: Date.now()
            });
            
            // Update visual feedback based on expansion result
            if (expandResult && finalContent && finalContent.length > (originalContent?.length || 0)) {
                counter.textContent = '✅'; // Successfully expanded
                console.log(`✅ Post expanded: ${originalContent?.length || 0} -> ${finalContent.length} characters`);
                console.log(`📝 Final content preview: "${finalContent.substring(0, 150)}..."`);
            } else if (!expandResult && finalContent) {
                counter.textContent = '📄'; // No "See more" found, using current content
                console.log(`📄 No expansion needed for this post (${finalContent.length} chars)`);
                console.log(`📝 Content preview: "${finalContent}"`);
            } else {
                counter.textContent = '❌'; // Failed to get content
                console.log('❌ Failed to extract content from post');
            }
            
            // Reset visual state
            overlay.style.opacity = '1';
            
        } catch (error) {
            console.error('❌ Error handling post selection:', error);
            counter.textContent = '❌';
            overlay.style.opacity = '1';
        }
    }
    
    // Update collect button with selected count
    function updateCollectButtonCount() {
        const button = document.querySelector('.fbgpc-button');
        if (!button) return;
        
        const selectedCount = Array.from(pagePostCheckboxes.values())
            .filter(data => data.checkbox.checked).length;
        const totalCount = pagePostCheckboxes.size;
        
        // Update button text
        const icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
            <path d="M3 2a1 1 0 011-1h8a1 1 0 011 1v12a1 1 0 01-1 1H4a1 1 0 01-1-1V2zm1 0v12h8V2H4z"/>
            <path d="M6 4h4v1H6V4zm0 2h4v1H6V6zm0 2h4v1H6V8z"/>
        </svg>`;
        
        button.innerHTML = `${icon} Collect Posts (${selectedCount}/${totalCount})`;
    }
    
    // Create bulk control panel
    function createBulkControls() {
        const controls = document.createElement('div');
        controls.className = 'fbgpc-bulk-controls';
        
        const selectAllBtn = document.createElement('button');
        selectAllBtn.className = 'fbgpc-bulk-btn';
        selectAllBtn.textContent = 'Select All';
        selectAllBtn.addEventListener('click', selectAllPagePosts);
        
        const selectNoneBtn = document.createElement('button');
        selectNoneBtn.className = 'fbgpc-bulk-btn';
        selectNoneBtn.textContent = 'Select None';
        selectNoneBtn.addEventListener('click', selectNoPagePosts);
        
        const rescanBtn = document.createElement('button');
        rescanBtn.className = 'fbgpc-bulk-btn';
        rescanBtn.textContent = 'Rescan Posts';
        rescanBtn.addEventListener('click', scanAndMarkPosts);
        
        controls.appendChild(selectAllBtn);
        controls.appendChild(selectNoneBtn);
        controls.appendChild(rescanBtn);
        
        document.body.appendChild(controls);
        return controls;
    }
    
    // Select all page posts
    function selectAllPagePosts() {
        pagePostCheckboxes.forEach(data => {
            if (!data.checkbox.checked) {
                data.checkbox.checked = true;
                data.overlay.classList.remove('unchecked');
            }
        });
        updateCollectButtonCount();
    }
    
    // Select no page posts
    function selectNoPagePosts() {
        pagePostCheckboxes.forEach(data => {
            if (data.checkbox.checked) {
                data.checkbox.checked = false;
                data.overlay.classList.add('unchecked');
            }
        });
        updateCollectButtonCount();
    }
    
    // Set up MutationObserver to monitor for new posts
    function setupPostMonitoring() {
        if (mutationObserver) {
            mutationObserver.disconnect();
        }
        
        mutationObserver = new MutationObserver((mutations) => {
            let shouldScan = false;
            
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList') {
                    // Check if new nodes were added
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // Check if the added node or its descendants contain post elements
                            if (node.querySelector && (
                                node.querySelector('div[style*="border-radius: max(0px, min(var(--card-corner-radius), calc((100vw - 4px - 100%) * 9999))) / var(--card-corner-radius);"]') ||
                                node.matches && node.matches('div[style*="border-radius: max(0px, min(var(--card-corner-radius), calc((100vw - 4px - 100%) * 9999))) / var(--card-corner-radius);"]')
                            )) {
                                shouldScan = true;
                            }
                        }
                    });
                }
            });
            
            if (shouldScan) {
                // Debounce scanning to avoid excessive calls
                clearTimeout(window.fbgpcScanTimeout);
                window.fbgpcScanTimeout = setTimeout(() => {
                    scanAndMarkPosts();
                }, 500);
            }
        });
        
        // Start observing
        mutationObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        console.log('🔍 Started monitoring for new posts');
    }
    
    // Clean up removed posts from our tracking
    function cleanupRemovedPosts() {
        const toRemove = [];
        
        pagePostCheckboxes.forEach((data, postElement) => {
            if (!document.contains(postElement)) {
                toRemove.push(postElement);
            }
        });
        
        toRemove.forEach(postElement => {
            pagePostCheckboxes.delete(postElement);
            expandedContentMap.delete(postElement); // Also clean up expanded content
        });
        
        if (toRemove.length > 0) {
            console.log(`🧹 Cleaned up ${toRemove.length} removed posts`);
            updateCollectButtonCount();
        }
    }

    
    // Debug status function
    function showDebugStatus() {
        console.log('🔍 === DEBUG STATUS ===');
        console.log(`📊 Total posts tracked: ${pagePostCheckboxes.size}`);
        console.log(`✅ Selected posts: ${Array.from(pagePostCheckboxes.values()).filter(data => data.checkbox.checked).length}`);
        console.log(`📦 Collected posts in memory: ${collectedPosts.length}`);
        console.log(`💾 Expanded content entries: ${expandedContentMap.size}`);
        
        // Show details of selected posts
        const selectedPosts = Array.from(pagePostCheckboxes.entries())
            .filter(([postElement, data]) => data.checkbox.checked);
            
        console.log('\n📋 Selected Posts Details:');
        selectedPosts.forEach(([postElement, data], index) => {
            const expandedData = expandedContentMap.get(postElement);
            console.log(`   ${index + 1}. Post #${data.postId}`);
            console.log(`      - Checkbox: ${data.checkbox.checked ? 'checked' : 'unchecked'}`);
            console.log(`      - In DOM: ${document.contains(postElement) ? 'yes' : 'no'}`);
            console.log(`      - Has expanded content: ${expandedData ? 'yes (' + expandedData.expandedContent?.length + ' chars)' : 'no'}`);
        });
        
        // Show collected posts
        console.log('\n📦 Collected Posts Array:');
        collectedPosts.forEach((post, index) => {
            console.log(`   ${index + 1}. ID: ${post.post_id}, URL: ${post.post_url}, Content: ${post.post_content?.length || 0} chars`);
        });
        
        // Show a status toast
        showToast(`Debug: ${pagePostCheckboxes.size} tracked, ${Array.from(pagePostCheckboxes.values()).filter(data => data.checkbox.checked).length} selected, ${collectedPosts.length} collected`);
        console.log('🔍 === END DEBUG STATUS ===\n');
    }
    
    // Test content cleaning function
    function testContentCleaning() {
        console.log('🧪 === TESTING CONTENT CLEANING ===');
        
        // Test with the provided example
        const testInput = `Esmeralda Casilla Ramos
 
·
Follow
r
n
p
o
S
s
t
d
o
e
c
4
7
1
4
4
5
t
m
7
4
1
u
f
l
2
1
g
m
2
6
6
7
l
c
7
7
0
2
u
7
t
h
t
9
2
f
f
g
g
0
5
h
l
1
2
u
4
l
6
 
·
 FEMALE ROOMATE - 
JEAN JAURES UNIVERSITY (UT2) - 2 MINUTES WALK FROM EVERYTHING M️
TM️ COL.
hello 
Looking for a new roommate to fill a 4 bedroom female roommate.
The apartment is currently occupied by three female students:
 
 Inès, 21 years old, a dynamic student and very kind.
 
 Emma, 29 years old, PhD student in archaeology, accompanied by Simone 
, her little French Bulldog (she's really too cute).
 
 Blanche, 18 years old who joins the roommate in early September
They look forward to meeting you when you visit!
 APARTMENT DESCRIPTION:
A T5 crossing East-West 
 fully furnished and equipped 80 m2 with an optimal layout and a large private garden. 
 THE ROOM:
- Bright and fully renovated.
- 2-seater bed with comfortable mattress, desk, wardrobe, and individual lock.
 THE MOST:
- Individual lease.
- Housekeeping service
- Fiber connection, wifi, large television connected.
- New appliances (washing machine, dishwasher, XXL fridge, etc.) ).
- Eligible aux APL.
 LOCATION:
- Subway at 100m, line (A).
- UT2, University of Toulouse Jean Jaurès 10 min by metro.
- UT1, Capitol University 20 min by subway
- Bus (line 13) at the foot of the apartment.
- Various shops and services less than 150 m.
 PRICE :
470 € + 30 € package charges included (electricity, water, heating, internet, cleaning, etc.) ). No expected charge adjustment.
 APL simulation available on the CAF website.
 VISIT:
Currently available for physical visits.
 YOUR APPLICATION IN 5 STEPS:
1) World fact sheet to return to me to complete.
2) Planning your meeting with tenants.
3) Tenants' decision to continue the proceedings.
4) Receipt of your candidacy application.
5) Decision of the owner.
For more information, photos or videos, hit me up!
Thank you.
 
 · See original
 
 · Rate this translation
All reactions:
5
5
8
3
Like
Comment
Share
View more comments
Anne-Sophie Heckel`;

        console.log('🧪 Testing with Facebook post example...');
        const result = cleanInnerTextContent(testInput);
        
        console.log('🧪 === TEST RESULTS ===');
        if (result) {
            console.log('✅ SUCCESS: Function returned content');
            console.log(`📏 Length: ${result.length} characters`);
            console.log('📝 Content:');
            console.log(result);
            
            // Check if it contains expected content
            if (result.includes('FEMALE ROOMATE') || result.includes('Looking for a new roommate')) {
                console.log('✅ VALIDATION: Contains expected content!');
                showToast('✅ Content cleaning test PASSED!');
            } else {
                console.log('❌ VALIDATION: Does not contain expected content');
                showToast('❌ Content cleaning test FAILED - missing expected content', true);
            }
        } else {
            console.log('❌ FAILURE: Function returned null/empty');
            showToast('❌ Content cleaning test FAILED - no content returned', true);
        }
        
        console.log('🧪 === END CONTENT CLEANING TEST ===\n');
    }
    
    // Show the post selection dialog
    function showSelectionDialog() {
        console.log(`💬 showSelectionDialog: Starting with ${collectedPosts.length} collected posts`);
        
        // Validation check
        if (!collectedPosts || collectedPosts.length === 0) {
            console.error('🚨 showSelectionDialog: No collected posts available!');
            showToast('Error: No posts were collected to display', true);
            return;
        }
        
        // Log collected post details for debugging
        collectedPosts.forEach((post, index) => {
            console.log(`   Post ${index + 1}: ID=${post.post_id}, URL=${post.post_url}, Content=${post.post_content ? post.post_content.length + ' chars' : 'null'}`);
        });
        
        // Remove existing dialog if present
        if (selectionDialog) {
            console.log(`🧹 Cleaning up existing dialog`);
            selectionDialog.remove();
        }
        
        console.log(`🎨 Creating dialog elements...`);
        
        // Create overlay
        selectionDialog = document.createElement('div');
        selectionDialog.className = 'fbgpc-overlay';
        
        // Create dialog
        const dialog = document.createElement('div');
        dialog.className = 'fbgpc-dialog';
        
        // Header
        const header = document.createElement('div');
        header.className = 'fbgpc-header';
        
        const title = document.createElement('div');
        title.className = 'fbgpc-title';
        title.textContent = `Select Posts to Send (${collectedPosts.length} found)`;
        
        const closeBtn = document.createElement('button');
        closeBtn.className = 'fbgpc-close';
        closeBtn.innerHTML = '&times;';
        
        header.appendChild(title);
        header.appendChild(closeBtn);
        
        // Content
        const content = document.createElement('div');
        content.className = 'fbgpc-content';
        
        // Controls
        const controls = document.createElement('div');
        controls.className = 'fbgpc-controls';
        
        const selectAllBtn = document.createElement('button');
        selectAllBtn.className = 'fbgpc-control-btn';
        selectAllBtn.textContent = 'Select All';
        
        const selectNoneBtn = document.createElement('button');
        selectNoneBtn.className = 'fbgpc-control-btn';
        selectNoneBtn.textContent = 'Select None';
        
        const selectionCount = document.createElement('span');
        selectionCount.className = 'fbgpc-status';
        selectionCount.id = 'fbgpc-selection-count';
        selectionCount.textContent = `${collectedPosts.length} selected`; // All selected by default
        
        controls.appendChild(selectAllBtn);
        controls.appendChild(selectNoneBtn);
        controls.appendChild(selectionCount);
        
        // Posts list
        const postsList = document.createElement('div');
        postsList.className = 'fbgpc-posts';
        postsList.id = 'fbgpc-posts-list';
        
        console.log(`📄 Creating post items for ${collectedPosts.length} posts...`);
        
        // Populate posts
        let itemsCreated = 0;
        collectedPosts.forEach((post, index) => {
            try {
                const postItem = createPostItem(post, index);
                if (postItem) {
                    postsList.appendChild(postItem);
                    itemsCreated++;
                    console.log(`   ✅ Created item ${itemsCreated} for post ${index + 1}`);
                } else {
                    console.warn(`   ⚠️ createPostItem returned null for post ${index + 1}`);
                }
            } catch (error) {
                console.error(`   ❌ Error creating item for post ${index + 1}:`, error);
            }
        });
        
        console.log(`📄 Created ${itemsCreated} post items out of ${collectedPosts.length} posts`);
        
        // Validation: ensure posts list has child elements
        if (postsList.children.length === 0) {
            console.error('🚨 CRITICAL: Posts list is empty after attempting to populate!');
            console.error('CollectedPosts array:', collectedPosts);
            
            // Add error message to the list
            const errorDiv = document.createElement('div');
            errorDiv.style.padding = '20px';
            errorDiv.style.textAlign = 'center';
            errorDiv.style.color = '#e74c3c';
            errorDiv.textContent = `Error: Failed to create post list items. Check console for details.`;
            postsList.appendChild(errorDiv);
        }
        
        content.appendChild(controls);
        content.appendChild(postsList);
        
        // Footer
        const footer = document.createElement('div');
        footer.className = 'fbgpc-footer';
        
        const apiStatus = document.createElement('div');
        apiStatus.className = 'fbgpc-status';
        apiStatus.id = 'fbgpc-api-status';
        
        const actions = document.createElement('div');
        actions.className = 'fbgpc-actions';
        
        const cancelBtn = document.createElement('button');
        cancelBtn.className = 'fbgpc-btn fbgpc-btn-secondary';
        cancelBtn.textContent = 'Cancel';
        
        const sendBtn = document.createElement('button');
        sendBtn.className = 'fbgpc-btn fbgpc-btn-primary';
        sendBtn.id = 'fbgpc-send-btn';
        sendBtn.textContent = 'Send Posts';
        sendBtn.disabled = false; // Enable since all are checked by default
        
        actions.appendChild(cancelBtn);
        actions.appendChild(sendBtn);
        
        footer.appendChild(apiStatus);
        footer.appendChild(actions);
        
        // Assemble dialog
        dialog.appendChild(header);
        dialog.appendChild(content);
        dialog.appendChild(footer);
        selectionDialog.appendChild(dialog);
        
        // Add to page
        document.body.appendChild(selectionDialog);
        console.log(`🎆 Dialog added to page`);
        
        // Add event listeners after elements are in the DOM
        closeBtn.addEventListener('click', closeSelectionDialog);
        selectAllBtn.addEventListener('click', selectAllPosts);
        selectNoneBtn.addEventListener('click', selectNonePosts);
        cancelBtn.addEventListener('click', closeSelectionDialog);
        sendBtn.addEventListener('click', sendSelectedPosts);
        
        // Add overlay click to close (click outside dialog)
        selectionDialog.addEventListener('click', (e) => {
            if (e.target === selectionDialog) {
                closeSelectionDialog();
            }
        });
        
        // Prevent dialog clicks from closing the modal
        dialog.addEventListener('click', (e) => {
            e.stopPropagation();
        });
        
        // Add keyboard support
        const handleKeydown = (e) => {
            if (e.key === 'Escape') {
                closeSelectionDialog();
                document.removeEventListener('keydown', handleKeydown);
            }
        };
        document.addEventListener('keydown', handleKeydown);
        
        // Store the keydown handler for cleanup
        selectionDialog._keydownHandler = handleKeydown;
        
        // Update selection count (should show all selected since checkboxes default to checked)
        updateSelectionCount();
        
        console.log(`✅ Dialog creation complete - ${postsList.children.length} items in list`);
        
        // Make functions globally accessible (for debugging)
        window.closeSelectionDialog = closeSelectionDialog;
        window.selectAllPosts = selectAllPosts;
        window.selectNonePosts = selectNonePosts;
        window.sendSelectedPosts = sendSelectedPosts;
        window.togglePost = togglePost;
    }
    
    // Create a post item for the selection list
    function createPostItem(post, index) {
        console.log(`🔨 createPostItem: Creating item ${index + 1}`);
        
        // Validate post data
        if (!post) {
            console.error(`❌ createPostItem: Post ${index + 1} is null/undefined`);
            return null;
        }
        
        if (!post.post_content) {
            console.error(`❌ createPostItem: Post ${index + 1} has no content:`, post);
            return null;
        }
        
        if (!post.post_url) {
            console.error(`❌ createPostItem: Post ${index + 1} has no URL:`, post);
            return null;
        }
        
        console.log(`   ✅ Post ${index + 1} validation passed (${post.post_content.length} chars)`);
        
        try {
            const item = document.createElement('div');
            item.className = 'fbgpc-post-item';
            
            // Truncate content for preview
            const previewContent = post.post_content.length > 200 
                ? post.post_content.substring(0, 200) + '...' 
                : post.post_content;
            
            // Create checkbox element
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.className = 'fbgpc-post-checkbox';
            checkbox.id = `post-${index}`;
            checkbox.checked = true; // Default to checked
            
            // Create content container
            const contentDiv = document.createElement('div');
            contentDiv.className = 'fbgpc-post-content';
            
            // Basic post info
            const postInfo = document.createElement('div');
            postInfo.innerHTML = `
                <div class="fbgpc-post-url">${post.post_url}</div>
                <div class="fbgpc-post-text ${post.post_content.length > 200 ? 'truncated' : ''}">${previewContent}</div>
            `;
            contentDiv.appendChild(postInfo);
            
            // Add images section if post has images
            if (post.images && post.images.length > 0) {
                console.log(`   📸 Adding images section (${post.images.length} images)`);
                const imagesSection = createImagesSection(post.images, index);
                if (imagesSection) {
                    contentDiv.appendChild(imagesSection);
                }
            }
            
            // Add event listener to checkbox
            checkbox.addEventListener('change', () => togglePost(index));
            
            // Assemble the item
            item.appendChild(checkbox);
            item.appendChild(contentDiv);
            
            console.log(`   ✅ Post item ${index + 1} created successfully`);
            return item;
            
        } catch (error) {
            console.error(`❌ createPostItem: Error creating item for post ${index + 1}:`, error);
            console.error('Post data:', post);
            return null;
        }
    }
    
    // Create images section for a post
    function createImagesSection(images, postIndex) {
        const imagesDiv = document.createElement('div');
        imagesDiv.className = 'fbgpc-post-images';
        
        // Images header with toggle
        const header = document.createElement('div');
        header.className = 'fbgpc-images-header';
        
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'fbgpc-images-toggle';
        toggleBtn.textContent = `📸 Images (${images.length}) - Click to expand`;
        toggleBtn.setAttribute('data-expanded', 'false');
        
        header.appendChild(toggleBtn);
        
        // Images grid (initially hidden)
        const grid = document.createElement('div');
        grid.className = 'fbgpc-images-grid';
        grid.style.display = 'none';
        
        images.forEach((image, imageIndex) => {
            const imageItem = document.createElement('div');
            imageItem.className = 'fbgpc-image-item';
            
            const img = document.createElement('img');
            img.className = 'fbgpc-image-preview';
            img.src = image.url;
            img.alt = image.alt;
            img.loading = 'lazy';
            
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.className = 'fbgpc-image-checkbox';
            checkbox.checked = image.selected;
            checkbox.addEventListener('change', () => toggleImageSelection(postIndex, imageIndex));
            
            const info = document.createElement('div');
            info.className = 'fbgpc-image-info';
            info.textContent = image.width && image.height 
                ? `${image.width}×${image.height}` 
                : 'Loading...';
            
            imageItem.appendChild(img);
            imageItem.appendChild(checkbox);
            imageItem.appendChild(info);
            grid.appendChild(imageItem);
        });
        
        // Include images toggle
        const includeDiv = document.createElement('div');
        includeDiv.className = 'fbgpc-include-images';
        includeDiv.style.display = 'none';
        
        const includeCheckbox = document.createElement('input');
        includeCheckbox.type = 'checkbox';
        includeCheckbox.checked = collectedPosts[postIndex]?.includeImages || false;
        includeCheckbox.addEventListener('change', () => toggleIncludeImages(postIndex));
        
        const includeLabel = document.createElement('span');
        includeLabel.textContent = 'Include images when sending this post';
        
        includeDiv.appendChild(includeCheckbox);
        includeDiv.appendChild(includeLabel);
        
        // Toggle functionality
        toggleBtn.addEventListener('click', (e) => {
            e.preventDefault();
            const expanded = toggleBtn.getAttribute('data-expanded') === 'true';
            
            if (expanded) {
                grid.style.display = 'none';
                includeDiv.style.display = 'none';
                toggleBtn.textContent = `📸 Images (${images.length}) - Click to expand`;
                toggleBtn.setAttribute('data-expanded', 'false');
            } else {
                grid.style.display = 'grid';
                includeDiv.style.display = 'flex';
                toggleBtn.textContent = `📸 Images (${images.length}) - Click to collapse`;
                toggleBtn.setAttribute('data-expanded', 'true');
            }
        });
        
        imagesDiv.appendChild(header);
        imagesDiv.appendChild(grid);
        imagesDiv.appendChild(includeDiv);
        
        return imagesDiv;
    }
    
    // Toggle post selection
    function togglePost(index) {
        updateSelectionCount();
    }
    
    // Toggle individual image selection
    function toggleImageSelection(postIndex, imageIndex) {
        if (collectedPosts[postIndex] && collectedPosts[postIndex].images[imageIndex]) {
            collectedPosts[postIndex].images[imageIndex].selected = 
                !collectedPosts[postIndex].images[imageIndex].selected;
            updateSelectionCount();
        }
    }
    
    // Toggle include images for a post
    function toggleIncludeImages(postIndex) {
        if (collectedPosts[postIndex]) {
            collectedPosts[postIndex].includeImages = !collectedPosts[postIndex].includeImages;
            updateSelectionCount();
        }
    }
    
    // Select all posts
    function selectAllPosts() {
        const checkboxes = document.querySelectorAll('.fbgpc-post-checkbox');
        checkboxes.forEach(checkbox => {
            checkbox.checked = true;
        });
        updateSelectionCount();
    }
    
    // Select no posts
    function selectNonePosts() {
        const checkboxes = document.querySelectorAll('.fbgpc-post-checkbox');
        checkboxes.forEach(checkbox => {
            checkbox.checked = false;
        });
        updateSelectionCount();
    }
    
    // Update selection count and button state
    function updateSelectionCount() {
        const checkboxes = document.querySelectorAll('.fbgpc-post-checkbox');
        const selectedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
        
        // Count selected images across all posts
        let totalImages = 0;
        let selectedImages = 0;
        
        collectedPosts.forEach((post, index) => {
            const postCheckbox = document.getElementById(`post-${index}`);
            if (postCheckbox && postCheckbox.checked && post.images && post.includeImages) {
                totalImages += post.images.length;
                selectedImages += post.images.filter(img => img.selected).length;
            }
        });
        
        const countElement = document.getElementById('fbgpc-selection-count');
        const sendButton = document.getElementById('fbgpc-send-btn');
        
        if (countElement) {
            let statusText = `${selectedCount} posts selected`;
            if (selectedImages > 0) {
                statusText += `, ${selectedImages} images`;
            }
            countElement.textContent = statusText;
        }
        
        if (sendButton) {
            sendButton.disabled = selectedCount === 0;
        }
    }
    
    // Close the selection dialog
    function closeSelectionDialog() {
        if (selectionDialog) {
            // Clean up keyboard event listener
            if (selectionDialog._keydownHandler) {
                document.removeEventListener('keydown', selectionDialog._keydownHandler);
            }
            
            // Remove dialog from DOM
            if (selectionDialog.parentNode) {
                selectionDialog.remove();
            }
        }
        selectionDialog = null;
        
        // Clean up global functions
        if (typeof window.closeSelectionDialog !== 'undefined') {
            delete window.closeSelectionDialog;
        }
        if (typeof window.selectAllPosts !== 'undefined') {
            delete window.selectAllPosts;
        }
        if (typeof window.selectNonePosts !== 'undefined') {
            delete window.selectNonePosts;
        }
        if (typeof window.sendSelectedPosts !== 'undefined') {
            delete window.sendSelectedPosts;
        }
        if (typeof window.togglePost !== 'undefined') {
            delete window.togglePost;
        }
    }
    
    // Download image as blob from URL
    async function downloadImage(imageUrl) {
        try {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: imageUrl,
                    responseType: 'blob',
                    onload: function(response) {
                        if (response.status === 200) {
                            resolve(response.response);
                        } else {
                            reject(new Error(`Failed to download image: ${response.status}`));
                        }
                    },
                    onerror: function(error) {
                        reject(new Error(`Error downloading image: ${error}`));
                    }
                });
            });
        } catch (error) {
            console.error('Error downloading image:', error);
            throw error;
        }
    }
    
    // Upload image to server
    async function uploadImageToServer(submissionId, imageBlob, imageName) {
        try {
            const formData = new FormData();
            formData.append('listing_id', submissionId);
            formData.append('file', imageBlob, imageName);
            
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'POST',
                    url: 'http://localhost:8080/upload',
                    data: formData,
                    onload: function(response) {
                        if (response.status === 200) {
                            try {
                                const result = JSON.parse(response.responseText);
                                resolve(result);
                            } catch (e) {
                                reject(new Error('Invalid response format'));
                            }
                        } else {
                            reject(new Error(`Upload failed: ${response.status} - ${response.responseText}`));
                        }
                    },
                    onerror: function(error) {
                        reject(new Error(`Upload error: ${error}`));
                    }
                });
            });
        } catch (error) {
            console.error('Error uploading image:', error);
            throw error;
        }
    }
    
    // Process images for selected posts
    async function processPostImages(submissionId, selectedPosts, statusElement) {
        let totalImages = 0;
        let uploadedImages = 0;
        let failedImages = 0;
        
        // Count total images to upload
        selectedPosts.forEach(post => {
            if (post.includeImages && post.images) {
                totalImages += post.images.filter(img => img.selected).length;
            }
        });
        
        if (totalImages === 0) {
            console.log('No images to upload');
            return { totalImages, uploadedImages, failedImages };
        }
        
        statusElement.textContent = `Uploading images: 0/${totalImages}...`;
        
        for (const post of selectedPosts) {
            if (!post.includeImages || !post.images) continue;
            
            for (const [imageIndex, image] of post.images.entries()) {
                if (!image.selected) continue;
                
                try {
                    statusElement.textContent = `Uploading images: ${uploadedImages}/${totalImages}...`;
                    
                    // Download image
                    const imageBlob = await downloadImage(image.url);
                    
                    // Generate filename
                    const timestamp = Date.now();
                    const filename = `post_${post.post_id}_img_${imageIndex}_${timestamp}.jpg`;
                    
                    // Upload to server
                    const uploadResult = await uploadImageToServer(submissionId, imageBlob, filename);
                    
                    if (uploadResult.success) {
                        uploadedImages++;
                        console.log(`✅ Uploaded image ${uploadedImages}/${totalImages}:`, filename);
                    } else {
                        failedImages++;
                        console.error(`❌ Failed to upload image:`, uploadResult.error);
                    }
                    
                } catch (error) {
                    failedImages++;
                    console.error(`❌ Error processing image from ${image.url}:`, error);
                }
            }
        }
        
        return { totalImages, uploadedImages, failedImages };
    }

    // Send selected posts to API
    async function sendSelectedPosts() {
        const checkboxes = document.querySelectorAll('.fbgpc-post-checkbox');
        const selectedPosts = [];
        
        checkboxes.forEach((checkbox, index) => {
            if (checkbox.checked && collectedPosts[index]) {
                // Validate post data before adding
                const post = collectedPosts[index];
                if (post.post_id && post.post_url && post.post_content) {
                    // Additional content quality validation
                    const contentValidation = validateContentQuality(post.post_content);
                    if (contentValidation.valid) {
                        selectedPosts.push(post);
                        console.log(`✅ Post ${post.post_id}: Valid content (${contentValidation.wordCount} words)`);
                    } else {
                        console.warn(`⚠️  Skipping post ${post.post_id}: ${contentValidation.reason}`);
                        console.warn('Content preview:', post.post_content);
                    }
                } else {
                    console.warn('Skipping invalid post data:', post);
                }
            }
        });
        
        if (selectedPosts.length === 0) {
            showToast('No valid posts selected', true);
            return;
        }
        
        const sendButton = document.getElementById('fbgpc-send-btn');
        const statusElement = document.getElementById('fbgpc-api-status');
        
        // Show loading state
        sendButton.disabled = true;
        sendButton.innerHTML = `
            <div class="fbgpc-spinner"></div>
            Sending...
        `;
        
        statusElement.textContent = `Sending ${selectedPosts.length} posts...`;
        
        try {
            // Step 1: Send posts to server
            const postsResponse = await new Promise((resolve, reject) => {
                const controller = new AbortController();
                const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout

                GM_xmlhttpRequest({
                    method: "POST",
                    url: 'http://localhost:8080/fetch_post',
                    headers: {
                        "Content-Type": "application/json",
                    },
                    data: JSON.stringify({
                        posts: selectedPosts.map(post => ({
                            post_id: post.post_id,
                            post_url: post.post_url,
                            post_content: post.post_content
                        })),
                        timestamp: new Date().toISOString(),
                        source: 'facebook-group-post-collector',
                        version: SCRIPT_VERSION
                    }),
                    onload: function(response) {
                        clearTimeout(timeoutId);
                        if (response.status === 200) {
                            try {
                                const responseData = JSON.parse(response.responseText);
                                resolve(responseData);
                            } catch (e) {
                                reject(new Error('Invalid response format'));
                            }
                        } else {
                            reject(new Error(`Server responded with status ${response.status}: ${response.responseText}`));
                        }
                    },
                    onerror: function(error) {
                        clearTimeout(timeoutId);
                        reject(new Error(`Cannot connect to localhost:8080 - Make sure your server is running`));
                    }
                });
            });

            console.log('✅ Posts sent successfully:', postsResponse);
            
            // Step 2: Upload images if any
            const imageResults = await processPostImages(
                postsResponse.submission_id, 
                selectedPosts, 
                statusElement
            );
            
            // Step 3: Show final results
            let successMessage = `Successfully sent ${selectedPosts.length} posts`;
            if (imageResults.totalImages > 0) {
                successMessage += ` and ${imageResults.uploadedImages}/${imageResults.totalImages} images`;
                if (imageResults.failedImages > 0) {
                    successMessage += ` (${imageResults.failedImages} images failed)`;
                }
            }
            
            showToast(successMessage);
            console.log('✅ Complete results:', { posts: postsResponse, images: imageResults });
            closeSelectionDialog();
            
        } catch (error) {
            console.error('Error sending posts:', error);
            
            let errorMessage = error.message;
            if (error.name === 'AbortError') {
                errorMessage = 'Request timed out after 30 seconds';
            } else if (error.message.includes('connect')) {
                errorMessage = 'Cannot connect to localhost:8080 - Make sure your server is running';
            }
            
            showToast(`Error sending posts: ${errorMessage}`, true);
            statusElement.textContent = `Error: ${errorMessage}`;
            
            // Restore button state
            sendButton.disabled = false;
            sendButton.textContent = 'Send Posts';
        }
    }
    
    // Initialize the script
    function initialize() {
        // Check if we're on a Facebook group page
        if (!window.location.pathname.includes('/groups/')) {
            return;
        }
        
        // Wait for page to load
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(initialize, 1000);
            });
            return;
        }
        
        // Inject styles and create buttons
        injectStyles();
        createCollectionButton();
        createBulkControls();
        
        // Set up post monitoring and initial scan
        setupPostMonitoring();
        setTimeout(() => {
            scanAndMarkPosts();
        }, 1000); // Delay to let page load
        
        // Periodic cleanup of removed posts
        setInterval(cleanupRemovedPosts, 10000); // Every 10 seconds
        
        // Register menu commands
        GM.registerMenuCommand('Collect Group Posts', handleCollectionClick);
        GM.registerMenuCommand('Rescan Posts', scanAndMarkPosts);
        GM.registerMenuCommand('Debug: Show Collection Status', showDebugStatus);
        GM.registerMenuCommand('Debug: Test Content Cleaning', testContentCleaning);
    }
    
    // Start the script
    initialize();
    
})();