Spaces:
Sleeping
Sleeping
| """ | |
| Pinterest Automation Bot - Fixed Version | |
| """ | |
| import gradio as gr | |
| import os | |
| import logging | |
| import tempfile | |
| import shutil | |
| import stat | |
| from pathlib import Path | |
| import datetime | |
| import re | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Global Pinterest automation instance for session management | |
| pinterest_bot = None | |
| def get_pinterest_bot(): | |
| """Get or create the global Pinterest bot instance""" | |
| global pinterest_bot | |
| if pinterest_bot is None: | |
| try: | |
| from pinterest_automation_portable import PortablePinterestAutomation | |
| pinterest_bot = PortablePinterestAutomation(headless=True, fast_typing=True) | |
| logger.info("Created new Pinterest bot instance") | |
| except Exception as e: | |
| logger.error(f"Failed to create Pinterest bot: {e}") | |
| return None | |
| return pinterest_bot | |
| def cleanup_pinterest_bot(): | |
| """Cleanup the global Pinterest bot instance""" | |
| global pinterest_bot | |
| if pinterest_bot is not None: | |
| try: | |
| pinterest_bot.quit() | |
| logger.info("Pinterest bot instance cleaned up") | |
| except Exception as e: | |
| logger.error(f"Error cleaning up Pinterest bot: {e}") | |
| finally: | |
| pinterest_bot = None | |
| def reset_session(): | |
| """Reset the Pinterest session""" | |
| cleanup_pinterest_bot() | |
| return "π Session reset successfully! You can now login with different credentials or refresh the connection." | |
| def ensure_executable_permissions(): | |
| """Ensure Chrome binary and ChromeDriver have executable permissions""" | |
| try: | |
| # Define paths to executables | |
| chrome_binary = Path("chrome_portable/chrome") | |
| chromedriver_binary = Path("drivers/chromedriver") | |
| # Set executable permissions if files exist | |
| if chrome_binary.exists(): | |
| current_permissions = chrome_binary.stat().st_mode | |
| chrome_binary.chmod(current_permissions | stat.S_IEXEC | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) | |
| logger.info(f"Set executable permission for: {chrome_binary}") | |
| else: | |
| logger.warning(f"Chrome binary not found: {chrome_binary}") | |
| if chromedriver_binary.exists(): | |
| current_permissions = chromedriver_binary.stat().st_mode | |
| chromedriver_binary.chmod(current_permissions | stat.S_IEXEC | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) | |
| logger.info(f"Set executable permission for: {chromedriver_binary}") | |
| else: | |
| logger.warning(f"ChromeDriver not found: {chromedriver_binary}") | |
| except Exception as e: | |
| logger.error(f"Failed to set executable permissions: {str(e)}") | |
| def upload_pin(email, password, image, title, description, board_name, link_url, headless): | |
| """Main function to upload a pin to Pinterest with session management""" | |
| # Input validation | |
| if not email or not password: | |
| return "β Please enter your Pinterest email and password" | |
| if not image: | |
| return "β Please upload an image" | |
| if not title or not description or not board_name: | |
| return "β Please fill in title, description, and board name" | |
| # Validate URL format if provided | |
| if link_url: | |
| # Simple URL validation | |
| url_pattern = re.compile( | |
| r'^https?://' # http:// or https:// | |
| r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain... | |
| r'localhost|' # localhost... | |
| r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip | |
| r'(?::\d+)?' # optional port | |
| r'(?:/?|[/?]\S+)$', re.IGNORECASE) | |
| if not url_pattern.match(link_url): | |
| return "β Please enter a valid URL (starting with http:// or https://)" | |
| # Create uploads directory if it doesn't exist | |
| uploads_dir = Path("uploads") | |
| uploads_dir.mkdir(exist_ok=True) | |
| # Generate unique filename with timestamp | |
| timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") | |
| image_extension = Path(image).suffix or '.jpg' | |
| stored_image_name = f"upload_{timestamp}{image_extension}" | |
| stored_image_path = uploads_dir / stored_image_name | |
| try: | |
| # Get or create Pinterest bot instance | |
| pinterest = get_pinterest_bot() | |
| if not pinterest: | |
| return "β Failed to initialize Pinterest bot" | |
| # Check if we need to setup driver | |
| if not hasattr(pinterest, 'driver') or not pinterest.driver: | |
| logger.info("Setting up Chrome driver...") | |
| if not pinterest._setup_driver(): | |
| return "β Failed to setup Chrome driver. Please try again." | |
| # Check if we need to login or if user changed | |
| if not pinterest.is_logged_in or pinterest.current_email != email: | |
| logger.info(f"Logging in as {email}...") | |
| if not pinterest.login(email, password): | |
| return "β Login failed. Please check your credentials." | |
| else: | |
| logger.info(f"Already logged in as {email}, continuing with existing session") | |
| try: | |
| # Ensure Chrome and ChromeDriver have executable permissions | |
| ensure_executable_permissions() | |
| # Store image in uploads directory | |
| shutil.copy2(image, stored_image_path) | |
| logger.info(f"Image stored at: {stored_image_path}") | |
| # Navigate to pin creation page (reuse existing session) | |
| logger.info("Navigating to pin creation page...") | |
| pinterest.driver.get("https://www.pinterest.com/pin-builder/") | |
| pinterest._human_delay(3, 5) | |
| # Create temp directory for processing | |
| temp_dir = tempfile.mkdtemp() | |
| temp_image_path = os.path.join(temp_dir, "uploaded_image.jpg") | |
| shutil.copy2(image, temp_image_path) | |
| # Upload image | |
| if not pinterest.upload_image(temp_image_path): | |
| return "β Failed to upload image." | |
| # Set pin details | |
| if not pinterest.set_title(title): | |
| return "β Failed to set title." | |
| if not pinterest.set_description(description): | |
| return "β Failed to set description." | |
| # Set optional link (only if valid URL provided) | |
| if link_url and link_url.strip(): | |
| if not pinterest.set_link(link_url.strip()): | |
| return "β Failed to set link. Please check the URL format." | |
| # Select board | |
| if not pinterest.select_board(board_name): | |
| return "β Failed to select board. Make sure it exists in your Pinterest." | |
| # Publish pin | |
| if not pinterest.publish_pin(): | |
| return "β Failed to publish pin. Please check for validation errors." | |
| # Cleanup temp directory | |
| shutil.rmtree(temp_dir) | |
| # If successful, clean up stored image | |
| if stored_image_path.exists(): | |
| stored_image_path.unlink() | |
| logger.info(f"Cleaned up stored image: {stored_image_path}") | |
| logger.info("Pin uploaded successfully! Bot session maintained for next upload.") | |
| return f"β Success! Pin '{title}' uploaded to board '{board_name}'. Session maintained for faster future uploads." | |
| except Exception as inner_e: | |
| # If there's an error, keep the stored image for debugging | |
| logger.error(f"Upload failed, keeping image for debugging: {stored_image_path}") | |
| return f"β Upload failed: {str(inner_e)}. Image saved at {stored_image_path} for debugging." | |
| except ImportError as e: | |
| return f"β Setup error: {str(e)}. Chrome setup is missing." | |
| except Exception as e: | |
| logger.error(f"Error: {str(e)}") | |
| return f"β Error: {str(e)}" | |
| # Simple interface that should work with Gradio 4.40.0 | |
| with gr.Blocks(title="Pinterest Automation Bot") as app: | |
| gr.Markdown("# π Pinterest Automation Bot") | |
| gr.Markdown("Upload pins to Pinterest automatically with advanced automation.") | |
| # Status indicator | |
| with gr.Row(): | |
| status_text = gr.Markdown("π’ **Status**: Ready to upload pins!") | |
| # Login section | |
| gr.Markdown("### π Pinterest Login") | |
| email = gr.Textbox(label="Email", placeholder="your-email@example.com") | |
| password = gr.Textbox(label="Password", type="password") | |
| # Pin details section | |
| gr.Markdown("### π Pin Details") | |
| image = gr.File(label="Image") | |
| title = gr.Textbox(label="Title", placeholder="Enter pin title...") | |
| description = gr.Textbox(label="Description", lines=3, placeholder="Enter pin description...") | |
| board_name = gr.Textbox(label="Board Name", placeholder="Enter existing board name...") | |
| link_url = gr.Textbox(label="Link (Optional)", placeholder="https://example.com") | |
| # Options | |
| gr.Markdown("### βοΈ Options") | |
| headless = gr.Checkbox(label="Run in headless mode (recommended)", value=True) | |
| # Submit button | |
| submit_btn = gr.Button("π Upload Pin to Pinterest", variant="primary") | |
| # Session reset button | |
| reset_btn = gr.Button("π Reset Session", variant="secondary") | |
| # Output | |
| result = gr.Textbox(label="Result", lines=3, interactive=False) | |
| # Event handlers | |
| submit_btn.click( | |
| upload_pin, | |
| inputs=[email, password, image, title, description, board_name, link_url, headless], | |
| outputs=result | |
| ) | |
| reset_btn.click( | |
| reset_session, | |
| inputs=[], | |
| outputs=result | |
| ) | |
| gr.Markdown(""" | |
| ### π Instructions | |
| 1. Enter your Pinterest credentials | |
| 2. Upload an image (PNG, JPG, GIF, WebP) | |
| 3. Fill in pin details (title, description, board name) | |
| 4. Optionally add a destination link | |
| 5. Click "Upload Pin to Pinterest" | |
| **Note**: Board must already exist in your Pinterest account. | |
| ### π Session Management | |
| - The bot maintains your login session between uploads for faster operation | |
| - Use "Reset Session" to logout and start fresh (useful when switching accounts or if errors occur) | |
| - Session is automatically maintained until you reset or restart the application | |
| """) | |
| if __name__ == "__main__": | |
| try: | |
| logger.info("=== Starting Pinterest Bot Application ===") | |
| # Ensure executable permissions are set | |
| logger.info("Setting executable permissions...") | |
| ensure_executable_permissions() | |
| logger.info("Executable permissions set successfully") | |
| # Check if running on Hugging Face Spaces | |
| is_spaces = os.getenv('SPACE_ID') is not None | |
| logger.info(f"Running on Hugging Face Spaces: {is_spaces}") | |
| # For HF Spaces, start the app quickly and do heavy initialization later | |
| if is_spaces: | |
| logger.info("Quick launch for HF Spaces...") | |
| app.queue() # Enable queuing for better performance | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True, | |
| quiet=False, | |
| favicon_path=None, | |
| enable_monitoring=False | |
| ) | |
| else: | |
| # For local development, do full initialization | |
| logger.info("Full initialization for local development...") | |
| # Test imports to make sure everything is working | |
| logger.info("Testing module imports...") | |
| try: | |
| from pinterest_automation_portable import PortablePinterestAutomation | |
| logger.info("β Pinterest automation module imported successfully") | |
| except Exception as e: | |
| logger.error(f"β Failed to import Pinterest automation module: {e}") | |
| try: | |
| from chrome_wrapper import get_chrome_manager | |
| logger.info("β Chrome wrapper imported successfully") | |
| # Test chrome manager creation (but don't start browser) | |
| chrome_manager = get_chrome_manager() | |
| config_info = chrome_manager.get_config_info() | |
| logger.info(f"β Chrome setup verified: {config_info['chrome_binary']}") | |
| except Exception as e: | |
| logger.error(f"β Failed to setup Chrome wrapper: {e}") | |
| logger.info("Launching Gradio app...") | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7861, | |
| share=True, | |
| show_error=True, | |
| quiet=False | |
| ) | |
| except Exception as e: | |
| logger.error(f"Failed to start application: {e}") | |
| raise | |