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 ( +
+
+

Premium Plugin Detected

+

+ {pluginName} {pluginVersion} appears to be a premium or protected plugin + that requires manual download. +

+
+

To update this plugin:

+
    +
  1. Visit the plugin page on SpigotMC
  2. +
  3. Log in to your SpigotMC account
  4. +
  5. Download the latest version manually
  6. +
  7. Replace the current plugin file in your server's plugins folder
  8. +
+
+
+ + Open Plugin Page + + +
+
+
+ ); +} + function App() { const [serverPath, setServerPath] = useState(""); const [serverInfo, setServerInfo] = useState(null); @@ -315,6 +362,7 @@ function App() { // --- End New state for match selector --- // --- End New State Variables --- const [serverType, setServerType] = useState('Unknown'); + const [premiumPluginInfo, setPremiumPluginInfo] = useState<{name: string; version: string; url: string} | null>(null); useEffect(() => { let unlistenScanStarted: UnlistenFn | undefined; @@ -634,60 +682,75 @@ function App() { try { console.log(`Updating plugin: ${plugin.name} to version ${plugin.latest_version}`); - const newFilePath = await invoke("update_plugin", { - pluginId: plugin.repository_id, - version: plugin.latest_version, - repository: plugin.repository_source, - currentFilePath: plugin.file_path, - serverTypeStr: serverInfo?.server_type - }); - - console.log(`Update successful for ${plugin.name}, new file path: ${newFilePath}`); - - setPlugins(currentPlugins => currentPlugins.map(p => { - if (p.file_path === plugin.file_path) { - return { - ...p, - version: p.latest_version || p.version, - has_update: false, - latest_version: p.latest_version, - file_path: newFilePath - }; - } - return p; - })); - - if (serverPath) { - await invoke("save_plugin_data", { - plugins: plugins.map(p => { - if (p.file_path === plugin.file_path) { - return { - ...p, - version: p.latest_version || p.version, - has_update: false, - file_path: newFilePath - }; - } - return p; - }), - serverPath + // Try to update the plugin + try { + const newFilePath = await invoke("update_plugin", { + pluginId: plugin.repository_id, + version: plugin.latest_version, + repository: plugin.repository_source, + currentFilePath: plugin.file_path, + serverTypeStr: serverInfo?.server_type }); - } - } catch (err) { - const errorMessage = err instanceof Error ? err.message : String(err); - console.error(`Error updating ${plugin.name}:`, errorMessage); - if (errorMessage.includes("in use") || - errorMessage.includes("server running") || - errorMessage.includes("being used by another process")) { - setWarningMessage(`Cannot update ${plugin.name}: The Minecraft server appears to be running. Please stop your server before updating plugins.`); - } else if (errorMessage.includes("download failed")) { - setUpdateError(`Failed to download update for ${plugin.name}. Please check your internet connection and try again.`); - } else if (errorMessage.includes("Critical error")) { - setWarningMessage(`${errorMessage} A backup of your original plugin is available in the backups folder.`); - } else { - setUpdateError(`Error updating ${plugin.name}: ${errorMessage}`); + console.log(`Update successful for ${plugin.name}, new file path: ${newFilePath}`); + + setPlugins(currentPlugins => currentPlugins.map(p => { + if (p.file_path === plugin.file_path) { + return { + ...p, + version: p.latest_version || p.version, + has_update: false, + latest_version: p.latest_version, + file_path: newFilePath + }; + } + return p; + })); + + if (serverPath) { + await invoke("save_plugin_data", { + plugins: plugins.map(p => { + if (p.file_path === plugin.file_path) { + return { + ...p, + version: p.latest_version || p.version, + has_update: false, + file_path: newFilePath + }; + } + return p; + }), + serverPath + }); + } + } catch (error: any) { + console.error(`Error updating plugin: ${error}`); + + // Check if this is a premium resource + const errorMsg = String(error); + if (errorMsg.startsWith("PREMIUM_RESOURCE:")) { + // Parse the premium resource error + const parts = errorMsg.split(":"); + if (parts.length >= 4) { + const resourceUrl = parts.slice(3).join(":"); // Rejoin in case URL contains colons + + // Show premium resource modal + setPremiumPluginInfo({ + name: plugin.name, + version: plugin.latest_version, + url: resourceUrl + }); + } else { + setUpdateError(`Premium plugin detected, but couldn't get resource URL: ${plugin.name}`); + } + } else { + // Standard error handling + setUpdateError(`Failed to update ${plugin.name}: ${error}`); + } } + } catch (error) { + console.error(`Error in updatePlugin: ${error}`); + setUpdateError(`Update process error: ${error}`); } finally { setPluginLoadingStates(prev => ({ ...prev, [plugin.file_path]: false })); } @@ -885,6 +948,15 @@ function App() { /> )} + {premiumPluginInfo && ( + setPremiumPluginInfo(null)} + /> + )} +