├── package.json ├── images ├── icon128.png ├── icon16.png └── icon48.png ├── thinking-out-loud ├── manifest.json ├── .traerules ├── README.md ├── popup.js ├── content.js ├── icon-generator.html ├── popup.html └── background.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } -------------------------------------------------------------------------------- /images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saarthakkj/ghibli-image-scraper/HEAD/images/icon128.png -------------------------------------------------------------------------------- /images/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saarthakkj/ghibli-image-scraper/HEAD/images/icon16.png -------------------------------------------------------------------------------- /images/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saarthakkj/ghibli-image-scraper/HEAD/images/icon48.png -------------------------------------------------------------------------------- /thinking-out-loud: -------------------------------------------------------------------------------- 1 | project-name : ghibli-image-scraper 2 | what it does? 3 | - a chrome extension that actiavtes on x.com 4 | - realtime scrapes all images 5 | - call an llm-api (a meta model) with image and ask if the image is "studio ghibli style" 6 | - llm-api should return a true/false 7 | - if true, download the image -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Ghibli Image Scraper", 4 | "version": "1.0", 5 | "description": "Scrapes images from X.com that match Studio Ghibli art style", 6 | "permissions": [ 7 | "storage", 8 | "downloads", 9 | "activeTab", 10 | "scripting" 11 | ], 12 | "host_permissions": [ 13 | "https://x.com/*" 14 | ], 15 | "background": { 16 | "service_worker": "background.js" 17 | }, 18 | "content_scripts": [ 19 | { 20 | "matches": ["https://twitter.com/*", "https://x.com/*"], 21 | "js": ["content.js"] 22 | } 23 | ], 24 | "action": { 25 | "default_popup": "popup.html", 26 | "default_icon": { 27 | "16": "images/icon16.png", 28 | "48": "images/icon48.png", 29 | "128": "images/icon128.png" 30 | } 31 | }, 32 | "icons": { 33 | "16": "images/icon16.png", 34 | "48": "images/icon48.png", 35 | "128": "images/icon128.png" 36 | } 37 | } -------------------------------------------------------------------------------- /.traerules: -------------------------------------------------------------------------------- 1 | 2 | ## Implementation Steps 3 | 4 | ### 1. Setup Chrome Extension Structure 5 | - Create the basic extension structure with manifest.json 6 | - Configure permissions for X.com domain access 7 | - Set up background and content scripts 8 | - Design a simple popup interface 9 | 10 | ### 2. Image Scraping Implementation 11 | - Develop content script to identify and extract images from X.com 12 | - Create observers to detect new images as they appear during scrolling 13 | - Implement filtering to avoid processing duplicate images 14 | - Add functionality to extract image URLs and metadata 15 | 16 | ### 3. LLM API Integration 17 | - Set up connection to a Meta model API (like Llama 2 or similar) 18 | - Create functions to send images to the API for analysis 19 | - Implement prompt engineering to accurately identify Studio Ghibli style 20 | - Handle API responses and error conditions 21 | 22 | ### 4. Image Processing and Storage 23 | - Develop logic to process API responses (true/false for Ghibli style) 24 | - Create image download functionality for positively identified images 25 | - Implement local storage management for downloaded images 26 | - Add options for customizing save locations and file naming 27 | 28 | ### 5. User Interface and Settings 29 | - Design an intuitive popup interface 30 | - Add toggle for enabling/disabling the extension 31 | - Implement statistics tracking (images processed, downloaded) 32 | - Create settings for API key management and download preferences 33 | 34 | ### 6. Testing and Optimization 35 | - Test on various X.com layouts and scenarios 36 | - Optimize image detection for performance 37 | 38 | ### 7. Packaging and Distribution 39 | - Package the extension for Chrome Web Store 40 | - Implement update mechanism 41 | 42 | ## Technical Considerations 43 | - Use MutationObserver to detect dynamically loaded images 44 | - Consider using chrome local storage for caching processed images 45 | - Ensure proper error handling for network issues 46 | - Implement privacy-focused design (only process images, no text content) 47 | 48 | ## API Integration Details 49 | - Use a vision-capable LLM model (e.g., GPT-4 Vision, Llama 2 with vision capabilities) 50 | - Craft a specific sytem prompt like: "Is this image in Studio Ghibli art style? Answer only with true or false." 51 | - Consider implementing a confidence threshold for more accurate results 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ghibli Image Scraper 2 | 3 | A Chrome extension that automatically identifies and downloads images in Studio Ghibli art style from X.com . 4 | 5 | ## Overview 6 | 7 | Ghibli Image Scraper uses Google's Gemini AI to analyze images on X.com in real-time and identify those that match the distinctive Studio Ghibli art style. When a matching image is found, it's automatically downloaded to your specified folder. 8 | ## Features 9 | 10 | - Real-time image detection on X.com 11 | - AI-powered Studio Ghibli style recognition 12 | - Customizable confidence threshold for detection accuracy 13 | - Statistics tracking for processed and downloaded images 14 | - Configurable download path 15 | 16 | ## Installation 17 | 18 | ### From Source 19 | 20 | 1. Clone this repository or download the source code: 21 | 22 | ```bash 23 | git clone https://github.com/saarthakkj/ghibli-image-scraper.git 24 | ``` 25 | 26 | 2. Open Chrome and navigate to `chrome://extensions/` 27 | 28 | 3. Enable "Developer mode" by toggling the switch in the top right corner 29 | 30 | 4. Click "Load unpacked" and select the directory containing the extension files 31 | 32 | 5. The extension should now be installed and visible in your Chrome toolbar 33 | 34 | ## Configuration 35 | 36 | Before using the extension, you need to configure it with your Gemini API key: 37 | 38 | 1. Click on the extension icon in your Chrome toolbar 39 | 2. Go to the "Settings" tab 40 | 3. Enter your Gemini API key 41 | 4. (Optional) Adjust other settings: 42 | - API Base URL: The base URL for the Gemini API 43 | - Download Path: Where images will be saved (relative to your Downloads folder) 44 | - Confidence Threshold: Minimum confidence level for image classification (0.0-1.0) 45 | 5. Click "Save Settings" 46 | 47 | ## Usage 48 | 49 | 1. Navigate to X.com (or Twitter.com) 50 | 2. The extension will automatically start scanning images as you browse 51 | 3. Images identified as Studio Ghibli style will be downloaded to your specified folder 52 | 4. You can view statistics about processed and downloaded images in the extension popup 53 | 54 | ## How It Works 55 | 56 | 1. The extension uses a MutationObserver to detect images as they appear on X.com 57 | 2. Each image is sent to the Gemini API for analysis 58 | 3. The API determines if the image matches Studio Ghibli art style 59 | 4. If the confidence level exceeds your threshold, the image is downloaded 60 | 61 | ## Technical Details 62 | 63 | - Built with JavaScript using Chrome Extension Manifest V3 64 | - Uses Google's Gemini API for image analysis 65 | - Implements efficient image processing to avoid duplicates 66 | - Respects user privacy by only processing image data 67 | 68 | ## Requirements 69 | 70 | - Google Chrome browser 71 | - Gemini API key (obtain from [Google AI Studio](https://ai.google.dev/)) 72 | - Active internet connection 73 | 74 | ## License 75 | 76 | [MIT License](LICENSE) 77 | 78 | ## Contributing 79 | 80 | Contributions are welcome! Please feel free to submit a Pull Request. 81 | 82 | ## Disclaimer 83 | 84 | This extension is not affiliated with Studio Ghibli or X.com. It is intended for personal use only. Please respect copyright and terms of service when using downloaded images. -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | // Tab navigation 3 | const mainTab = document.getElementById('mainTab'); 4 | const settingsTab = document.getElementById('settingsTab'); 5 | const mainContent = document.getElementById('mainContent'); 6 | const settingsContent = document.getElementById('settingsContent'); 7 | 8 | mainTab.addEventListener('click', function() { 9 | mainContent.style.display = 'block'; 10 | settingsContent.style.display = 'none'; 11 | mainTab.classList.add('active'); 12 | settingsTab.classList.remove('active'); 13 | }); 14 | 15 | settingsTab.addEventListener('click', function() { 16 | mainContent.style.display = 'none'; 17 | settingsContent.style.display = 'block'; 18 | mainTab.classList.remove('active'); 19 | settingsTab.classList.add('active'); 20 | }); 21 | 22 | // Main tab elements 23 | const enableToggle = document.getElementById('enableToggle'); 24 | const processedCount = document.getElementById('processed'); 25 | const downloadedCount = document.getElementById('downloaded'); 26 | 27 | // Settings tab elements 28 | const apiKeyInput = document.getElementById('apiKey'); 29 | const apiBaseUrlInput = document.getElementById('apiBaseUrl'); // Make sure this is defined 30 | const downloadPathInput = document.getElementById('downloadPath'); 31 | const confidenceThresholdInput = document.getElementById('confidenceThreshold'); 32 | const saveSettingsButton = document.getElementById('saveSettings'); 33 | 34 | // Load current settings 35 | chrome.storage.local.get( 36 | ['enabled', 'imagesProcessed', 'imagesDownloaded', 'apiKey', 'apiBaseUrl', 'downloadPath', 'confidenceThreshold'], 37 | function(data) { 38 | // Main tab settings 39 | enableToggle.checked = data.enabled !== undefined ? data.enabled : true; 40 | processedCount.textContent = data.imagesProcessed || 0; 41 | downloadedCount.textContent = data.imagesDownloaded || 0; 42 | 43 | // Settings tab 44 | apiKeyInput.value = data.apiKey || ''; 45 | apiBaseUrlInput.value = data.apiBaseUrl || 'https://api.studio.nebius.com/v1/'; 46 | downloadPathInput.value = data.downloadPath || 'GhibliImages'; 47 | confidenceThresholdInput.value = data.confidenceThreshold || 0.7; 48 | } 49 | ); 50 | 51 | // Toggle extension enabled/disabled 52 | enableToggle.addEventListener('change', function() { 53 | chrome.storage.local.set({ enabled: this.checked }); 54 | 55 | // Notify content script about the status change 56 | chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { 57 | if (tabs[0]) { 58 | chrome.tabs.sendMessage(tabs[0].id, { 59 | type: 'TOGGLE_STATUS', 60 | enabled: enableToggle.checked 61 | }); 62 | } 63 | }); 64 | }); 65 | 66 | // Save settings 67 | saveSettingsButton.addEventListener('click', function() { 68 | const settings = { 69 | apiKey: apiKeyInput.value, 70 | apiBaseUrl: apiBaseUrlInput.value, 71 | downloadPath: downloadPathInput.value, 72 | confidenceThreshold: parseFloat(confidenceThresholdInput.value) 73 | }; 74 | 75 | chrome.runtime.sendMessage({ 76 | type: 'UPDATE_SETTINGS', 77 | settings: settings 78 | }, function(response) { 79 | if (response && response.status === 'settings_updated') { 80 | // Show a success message or visual feedback 81 | saveSettingsButton.textContent = 'Saved!'; 82 | setTimeout(() => { 83 | saveSettingsButton.textContent = 'Save Settings'; 84 | }, 2000); 85 | } 86 | }); 87 | }); 88 | 89 | // Update stats in real-time 90 | setInterval(function() { 91 | chrome.storage.local.get(['imagesProcessed', 'imagesDownloaded'], function(data) { 92 | processedCount.textContent = data.imagesProcessed || 0; 93 | downloadedCount.textContent = data.imagesDownloaded || 0; 94 | }); 95 | }, 1000); 96 | }); -------------------------------------------------------------------------------- /content.js: -------------------------------------------------------------------------------- 1 | // Set to store processed image URLs to avoid duplicates 2 | const processedImages = new Set(); 3 | let isEnabled = true; 4 | 5 | // Initialize extension 6 | function init() { 7 | // Check if extension is enabled 8 | chrome.storage.local.get(['enabled'], function(data) { 9 | isEnabled = data.enabled !== undefined ? data.enabled : true; 10 | 11 | if (isEnabled) { 12 | // Start observing the page for images 13 | setupImageObserver(); 14 | // Process any images already on the page 15 | processExistingImages(); 16 | } 17 | }); 18 | 19 | // Listen for status toggle from popup 20 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 21 | if (message.type === 'TOGGLE_STATUS') { 22 | isEnabled = message.enabled; 23 | 24 | if (isEnabled) { 25 | setupImageObserver(); 26 | processExistingImages(); 27 | } 28 | 29 | sendResponse({ status: 'updated' }); 30 | } 31 | return true; 32 | }); 33 | } 34 | 35 | // Process images that are already on the page 36 | function processExistingImages() { 37 | if (!isEnabled) return; 38 | 39 | // Find all images on the page 40 | const images = document.querySelectorAll('img'); 41 | images.forEach(img => { 42 | processImage(img); 43 | }); 44 | } 45 | 46 | // Set up MutationObserver to detect new images as they load 47 | function setupImageObserver() { 48 | // Create an observer instance 49 | const observer = new MutationObserver((mutations) => { 50 | if (!isEnabled) return; 51 | 52 | mutations.forEach(mutation => { 53 | // Check for added nodes 54 | if (mutation.addedNodes && mutation.addedNodes.length > 0) { 55 | for (let node of mutation.addedNodes) { 56 | // Check if the added node is an image 57 | if (node.nodeName === 'IMG') { 58 | processImage(node); 59 | } 60 | 61 | // Check if the added node contains images 62 | if (node.querySelectorAll) { 63 | const images = node.querySelectorAll('img'); 64 | images.forEach(img => { 65 | processImage(img); 66 | }); 67 | } 68 | } 69 | } 70 | }); 71 | }); 72 | 73 | // Start observing the document with the configured parameters 74 | observer.observe(document.body, { 75 | childList: true, 76 | subtree: true 77 | }); 78 | } 79 | 80 | // Process an individual image 81 | function processImage(imgElement) { 82 | if (!isEnabled) return; 83 | 84 | // Get the image URL 85 | const imageUrl = imgElement.src; 86 | 87 | // Skip if already processed or if it's a tiny image (likely an icon) 88 | if (processedImages.has(imageUrl) || 89 | !imageUrl || 90 | imageUrl.includes('profile_images') || 91 | imgElement.width < 100 || 92 | imgElement.height < 100) { 93 | return; 94 | } 95 | 96 | // Mark as processed 97 | processedImages.add(imageUrl); 98 | 99 | // Extract metadata 100 | const metadata = { 101 | width: imgElement.naturalWidth || imgElement.width, 102 | height: imgElement.naturalHeight || imgElement.height, 103 | alt: imgElement.alt || '', 104 | pageUrl: window.location.href 105 | }; 106 | 107 | // Send the image URL to the background script 108 | chrome.runtime.sendMessage({ 109 | type: 'IMAGE_FOUND', 110 | imageUrl: imageUrl, 111 | metadata: metadata 112 | }, response => { 113 | // Update processed count 114 | chrome.storage.local.get(['imagesProcessed'], function(data) { 115 | const newCount = (data.imagesProcessed || 0) + 1; 116 | chrome.storage.local.set({ imagesProcessed: newCount }); 117 | }); 118 | }); 119 | } 120 | 121 | // Create a folder for icons 122 | function createIconsFolder() { 123 | // This would be implemented in a real project 124 | // For this example, we'll just log a message 125 | console.log('Icon folder would be created here in a real implementation'); 126 | } 127 | 128 | // Initialize when the page is loaded 129 | window.addEventListener('load', init); 130 | 131 | // Also run init immediately in case the page is already loaded 132 | init(); -------------------------------------------------------------------------------- /icon-generator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ghibli Icon Generator 5 | 28 | 29 | 30 |

Ghibli Icon Generator

31 | 32 |
33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 | 41 | 42 | 43 |
44 | 45 | 127 | 128 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ghibli Image Scraper 5 | 118 | 119 | 120 |

Ghibli Image Scraper

121 | 122 |
123 | 124 | 125 |
126 | 127 |
128 |
129 | 133 | Enable Scraping 134 |
135 | 136 |
137 |

Images Processed: 0

138 |

Images Downloaded: 0

139 |
140 |
141 | 142 |
143 |
144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
159 |
160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | // Background script for Ghibli Image Scraper 2 | chrome.runtime.onInstalled.addListener(() => { 3 | // Initialize extension settings 4 | chrome.storage.local.set({ 5 | enabled: true, 6 | imagesProcessed: 0, 7 | imagesDownloaded: 0, 8 | downloadPath: 'GhibliImages', 9 | apiKey: '', // Store Gemini API key 10 | apiBaseUrl: 'https://generativelanguage.googleapis.com/v1beta/models/', // Gemini API base URL 11 | confidenceThreshold: 0.7 // Default confidence threshold 12 | }); 13 | }); 14 | 15 | // Function to check if an image is in Ghibli style using Gemini API 16 | async function checkGhibliStyle(imageUrl, metadata) { 17 | try { 18 | // Get API key and other settings from storage 19 | const data = await chrome.storage.local.get(['apiKey', 'apiBaseUrl', 'confidenceThreshold']); 20 | const apiKey = data.apiKey; 21 | const apiBaseUrl = data.apiBaseUrl || 'https://generativelanguage.googleapis.com/v1beta/models/'; 22 | const confidenceThreshold = data.confidenceThreshold || 0.7; 23 | 24 | if (!apiKey) { 25 | console.error('API key not set. Please set your Gemini API key in the extension settings.'); 26 | return { isGhibli: false, error: 'API key not set' }; 27 | } 28 | 29 | // First, we need to fetch the image and convert it to a base64 string 30 | let imageBase64; 31 | let mimeType; 32 | try { 33 | const imgResponse = await fetch(imageUrl); 34 | const blob = await imgResponse.blob(); 35 | mimeType = blob.type || 'image/jpeg'; 36 | 37 | // Use a more reliable method to convert blob to base64 38 | imageBase64 = await new Promise((resolve, reject) => { 39 | const reader = new FileReader(); 40 | reader.onloadend = () => { 41 | // Extract only the base64 part after the comma 42 | const base64Data = reader.result.split(',')[1]; 43 | resolve(base64Data); 44 | }; 45 | reader.onerror = reject; 46 | reader.readAsDataURL(blob); 47 | }); 48 | 49 | // Ensure the base64 string doesn't have any invalid characters 50 | imageBase64 = imageBase64.trim().replace(/\s/g, ''); 51 | 52 | // Validate that it's a proper base64 string 53 | if (!/^[A-Za-z0-9+/=]+$/.test(imageBase64)) { 54 | console.error('Invalid base64 string generated'); 55 | return { isGhibli: false, error: 'Invalid base64 encoding' }; 56 | } 57 | 58 | // Log a small sample of the base64 string for debugging 59 | console.log('Base64 sample (first 50 chars):', imageBase64.substring(0, 50)); 60 | 61 | } catch (error) { 62 | console.error('Error fetching image:', error); 63 | return { isGhibli: false, error: 'Failed to fetch image' }; 64 | } 65 | 66 | // Create the endpoint URL with the API key as a query parameter 67 | const endpoint = `${apiBaseUrl}gemini-2.0-flash:generateContent?key=${apiKey}`; 68 | 69 | // Prepare the request body according to Gemini API documentation 70 | // Note: Using inline_data instead of inlineData to match API expectations 71 | const requestBody = { 72 | contents: [ 73 | { 74 | parts: [ 75 | { 76 | text: "Is this image in Studio Ghibli art style? Answer only with 'true' or 'false'." 77 | }, 78 | { 79 | inline_data: { 80 | mime_type: mimeType, 81 | data: imageBase64 82 | } 83 | } 84 | ] 85 | } 86 | ] 87 | }; 88 | 89 | // Log the request details (with truncated base64 for readability) 90 | const logRequestBody = JSON.parse(JSON.stringify(requestBody)); 91 | if (logRequestBody.contents && logRequestBody.contents[0] && 92 | logRequestBody.contents[0].parts && logRequestBody.contents[0].parts[1] && 93 | logRequestBody.contents[0].parts[1].inline_data) { 94 | const base64String = logRequestBody.contents[0].parts[1].inline_data.data; 95 | logRequestBody.contents[0].parts[1].inline_data.data = 96 | base64String.substring(0, 50) + '...[truncated]...'; 97 | } 98 | 99 | console.log('API Request URL:', endpoint); 100 | console.log('API Request Body:', JSON.stringify(logRequestBody, null, 2)); 101 | 102 | // Make the API request 103 | const response = await fetch(endpoint, { 104 | method: 'POST', 105 | headers: { 106 | 'Content-Type': 'application/json' 107 | }, 108 | body: JSON.stringify(requestBody) 109 | }); 110 | 111 | // Log the response status 112 | console.log('API Response Status:', response.status); 113 | 114 | if (!response.ok) { 115 | const errorText = await response.text(); 116 | console.error('API Error Response:', errorText); 117 | throw new Error(`API request failed with status ${response.status}: ${errorText}`); 118 | } 119 | 120 | const result = await response.json(); 121 | 122 | // Log the successful response 123 | console.log('API Response:', JSON.stringify(result, null, 2)); 124 | 125 | // Parse the response to extract true/false answer 126 | const responseContent = result.candidates && 127 | result.candidates[0] && 128 | result.candidates[0].content && 129 | result.candidates[0].content.parts && 130 | result.candidates[0].content.parts[0] && 131 | result.candidates[0].content.parts[0].text; 132 | 133 | const isGhibli = responseContent && responseContent.toLowerCase().includes('true'); 134 | 135 | // For confidence, we could use some heuristics based on the response 136 | const confidence = isGhibli ? 0.8 : 0.2; // Simplified confidence estimation 137 | 138 | return { 139 | isGhibli: isGhibli && confidence >= confidenceThreshold, 140 | confidence: confidence, 141 | rawResponse: responseContent 142 | }; 143 | } catch (error) { 144 | console.error('Error checking Ghibli style:', error); 145 | return { isGhibli: false, error: error.message }; 146 | } 147 | } 148 | 149 | // Helper function to convert Blob to base64 150 | function blobToBase64(blob) { 151 | return new Promise((resolve, reject) => { 152 | const reader = new FileReader(); 153 | reader.onloadend = () => resolve(reader.result); 154 | reader.onerror = reject; 155 | reader.readAsDataURL(blob); 156 | }); 157 | } 158 | 159 | // Function to download an image 160 | async function downloadGhibliImage(imageUrl, metadata) { 161 | try { 162 | const data = await chrome.storage.local.get(['downloadPath']); 163 | const downloadPath = data.downloadPath || 'GhibliImages'; 164 | 165 | // Generate a filename based on the image URL 166 | const filename = `${downloadPath}/${Date.now()}_ghibli_image.jpg`; 167 | 168 | // Use Chrome's download API to save the image 169 | chrome.downloads.download({ 170 | url: imageUrl, 171 | filename: filename, 172 | saveAs: false 173 | }, (downloadId) => { 174 | if (chrome.runtime.lastError) { 175 | console.error('Download failed:', chrome.runtime.lastError); 176 | } else { 177 | // Update downloaded count 178 | chrome.storage.local.get(['imagesDownloaded'], function(data) { 179 | const newCount = (data.imagesDownloaded || 0) + 1; 180 | chrome.storage.local.set({ imagesDownloaded: newCount }); 181 | }); 182 | } 183 | }); 184 | 185 | return { success: true, filename: filename }; 186 | } catch (error) { 187 | console.error('Error downloading image:', error); 188 | return { success: false, error: error.message }; 189 | } 190 | } 191 | // Listen for messages from content script 192 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 193 | if (message.type === 'IMAGE_FOUND') { 194 | // Process the image 195 | console.log('Image found:', message.imageUrl); 196 | 197 | // Check if the image is in Ghibli style 198 | checkGhibliStyle(message.imageUrl, message.metadata) 199 | .then(result => { 200 | console.log('Ghibli check result:', result); 201 | 202 | // If it's a Ghibli style image, download it 203 | if (result.isGhibli) { 204 | return downloadGhibliImage(message.imageUrl, message.metadata); 205 | } 206 | return { success: false, reason: 'Not Ghibli style' }; 207 | }) 208 | .then(downloadResult => { 209 | console.log('Download result:', downloadResult); 210 | sendResponse({ 211 | status: 'processed', 212 | isGhibli: downloadResult.success, 213 | downloadResult: downloadResult 214 | }); 215 | }) 216 | .catch(error => { 217 | console.error('Error processing image:', error); 218 | sendResponse({ 219 | status: 'error', 220 | error: error.message 221 | }); 222 | }); 223 | 224 | return true; // Required for async response 225 | } 226 | 227 | // Handle API key updates 228 | if (message.type === 'UPDATE_SETTINGS') { 229 | chrome.storage.local.set(message.settings, () => { 230 | sendResponse({ status: 'settings_updated' }); 231 | }); 232 | return true; 233 | } 234 | }); 235 | --------------------------------------------------------------------------------