diff --git a/src-tauri/src/crawlers/spigotmc.rs b/src-tauri/src/crawlers/spigotmc.rs index 1de75ef..708a6e3 100644 --- a/src-tauri/src/crawlers/spigotmc.rs +++ b/src-tauri/src/crawlers/spigotmc.rs @@ -355,6 +355,9 @@ impl Repository for SpigotMCCrawler { // First, get the plugin details let details = self.get_plugin_details(plugin_id).await?; + // Get the plugin page URL for potential manual download instructions + let plugin_page_url = details.page_url.clone(); + // Use the SpigotMC direct download URL instead of SpiGet // SpigotMC has a direct download URL pattern for resources let direct_download_url = format!("https://www.spigotmc.org/resources/{}/download", plugin_id); @@ -370,19 +373,46 @@ impl Repository for SpigotMCCrawler { Ok(destination.to_string_lossy().to_string()) }, Err(e) => { - // If direct download fails, try the SpiGet URL as fallback - println!("Direct SpigotMC download failed: {}. Trying SpiGet URL as fallback...", e); + // If direct download fails with 403, it might be a premium resource + let error_message = format!("{}", e); + if error_message.contains("403 Forbidden") { + println!("Plugin appears to be a premium/protected resource on SpigotMC"); - let download_url = &details.download_url; - if download_url.is_empty() { - return Err(format!("No download URL found for SpigotMC resource {}", plugin_id)); - } + // Try the SpiGet URL as a fallback just to be sure + let download_url = &details.download_url; + if download_url.is_empty() { + return Err(format!("PREMIUM_RESOURCE:{}", plugin_page_url)); + } - println!("Falling back to SpiGet download URL: {}", download_url); + println!("Falling back to SpiGet download URL: {}", download_url); - match self.client.download(download_url, destination).await { - Ok(_) => Ok(destination.to_string_lossy().to_string()), - Err(e) => Err(format!("Failed to download from SpiGet: {}", e)) + match self.client.download(download_url, destination).await { + Ok(_) => Ok(destination.to_string_lossy().to_string()), + Err(spiget_err) => { + // If both methods fail, this is very likely a premium plugin + if format!("{}", spiget_err).contains("404 Not Found") { + println!("Confirmed premium resource - both direct and SpiGet downloads failed"); + Err(format!("PREMIUM_RESOURCE:{}", plugin_page_url)) + } else { + Err(format!("Failed to download from SpiGet: {}", spiget_err)) + } + } + } + } else { + // For other errors with direct download, try SpiGet + println!("Direct SpigotMC download failed: {}. Trying SpiGet URL as fallback...", e); + + let download_url = &details.download_url; + if download_url.is_empty() { + return Err(format!("No download URL found for SpigotMC resource {}", plugin_id)); + } + + println!("Falling back to SpiGet download URL: {}", download_url); + + match self.client.download(download_url, destination).await { + Ok(_) => Ok(destination.to_string_lossy().to_string()), + Err(e) => Err(format!("Failed to download from SpiGet: {}", e)) + } } } } diff --git a/src-tauri/src/services/update_manager/plugin_updater.rs b/src-tauri/src/services/update_manager/plugin_updater.rs index 05b8ef5..05e5ada 100644 --- a/src-tauri/src/services/update_manager/plugin_updater.rs +++ b/src-tauri/src/services/update_manager/plugin_updater.rs @@ -65,13 +65,25 @@ pub async fn replace_plugin( // Download the new plugin version let server_type = server_info.as_ref().map(|info| &info.server_type); - crate::lib_download_plugin_from_repository( + let download_result = crate::lib_download_plugin_from_repository( &plugin_id, &version, repository, &download_path.to_string_lossy(), server_type - ).await?; + ).await; + + // Check for premium resource indicator + if let Err(error) = &download_result { + if error.starts_with("PREMIUM_RESOURCE:") { + // Extract the resource URL from the error + let resource_url = error.strip_prefix("PREMIUM_RESOURCE:").unwrap_or_default(); + return Err(format!("PREMIUM_RESOURCE:{}:{}:{}", plugin_id, version, resource_url)); + } + } + + // If other error, propagate it + let _ = download_result?; // Backup the original file backup_plugin(current_file_path.clone()).await?; diff --git a/src/App.css b/src/App.css index c97845a..ff427fa 100644 --- a/src/App.css +++ b/src/App.css @@ -642,3 +642,77 @@ button { .warning-content .close-button:hover { background-color: #e69500; } + +/* Premium Plugin Modal Styles */ +.premium-plugin-modal { + background-color: var(--surface-color); + border-radius: 8px; + padding: 1.5rem; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + width: 90%; + max-width: 600px; + position: relative; + color: var(--text-color); + border: 1px solid var(--border-color); +} + +.premium-plugin-modal h3 { + color: var(--warning-color, #ff9800); + margin-top: 0; + margin-bottom: 1rem; + font-size: 1.4rem; +} + +.premium-plugin-instructions { + margin: 1.2rem 0; + background-color: rgba(255, 255, 255, 0.05); + padding: 1rem; + border-radius: 4px; + border-left: 3px solid var(--accent-color); +} + +.premium-plugin-instructions ol { + margin-top: 0.5rem; + padding-left: 1.5rem; +} + +.premium-plugin-instructions li { + margin-bottom: 0.5rem; +} + +.premium-plugin-actions { + display: flex; + justify-content: center; + gap: 1rem; + margin-top: 1.5rem; +} + +.plugin-page-button { + display: inline-block; + padding: 0.75rem 1.5rem; + background-color: var(--accent-color); + color: white; + text-decoration: none; + border-radius: 4px; + font-weight: bold; + transition: background-color 0.2s; +} + +.plugin-page-button:hover { + background-color: var(--accent-hover-color, #1967d2); +} + +.close-modal-button { + padding: 0.75rem 1.5rem; + background-color: var(--surface-alt-color, #424242); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.2s; +} + +.close-modal-button:hover { + background-color: var(--surface-hover-color, #616161); +} diff --git a/src/App.tsx b/src/App.tsx index 1a60cf5..5509a73 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -293,6 +293,53 @@ function PluginMatchSelector({ ); } +// After the WarningModal component, add this new component +function PremiumPluginModal({ + pluginName, + pluginVersion, + resourceUrl, + onClose +}: { + pluginName: string; + pluginVersion: string; + resourceUrl: string; + onClose: () => void; +}) { + return ( +
+ {pluginName} {pluginVersion} appears to be a premium or protected plugin + that requires manual download. +
+To update this plugin:
+