Add premium plugin detection and user guidance for manual downloads
This commit is contained in:
parent
a5e7b766ac
commit
057bba0c56
@ -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,7 +373,33 @@ impl Repository for SpigotMCCrawler {
|
||||
Ok(destination.to_string_lossy().to_string())
|
||||
},
|
||||
Err(e) => {
|
||||
// If direct download fails, try the SpiGet URL as fallback
|
||||
// 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");
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
@ -387,4 +416,5 @@ impl Repository for SpigotMCCrawler {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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?;
|
||||
|
74
src/App.css
74
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);
|
||||
}
|
||||
|
96
src/App.tsx
96
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 (
|
||||
<div className="modal-backdrop">
|
||||
<div className="premium-plugin-modal">
|
||||
<h3>Premium Plugin Detected</h3>
|
||||
<p>
|
||||
<strong>{pluginName} {pluginVersion}</strong> appears to be a premium or protected plugin
|
||||
that requires manual download.
|
||||
</p>
|
||||
<div className="premium-plugin-instructions">
|
||||
<p>To update this plugin:</p>
|
||||
<ol>
|
||||
<li>Visit the plugin page on SpigotMC</li>
|
||||
<li>Log in to your SpigotMC account</li>
|
||||
<li>Download the latest version manually</li>
|
||||
<li>Replace the current plugin file in your server's plugins folder</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div className="premium-plugin-actions">
|
||||
<a
|
||||
href={resourceUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="plugin-page-button"
|
||||
>
|
||||
Open Plugin Page
|
||||
</a>
|
||||
<button className="close-modal-button" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [serverPath, setServerPath] = useState("");
|
||||
const [serverInfo, setServerInfo] = useState<ServerInfo | null>(null);
|
||||
@ -315,6 +362,7 @@ function App() {
|
||||
// --- End New state for match selector ---
|
||||
// --- End New State Variables ---
|
||||
const [serverType, setServerType] = useState<ServerType>('Unknown');
|
||||
const [premiumPluginInfo, setPremiumPluginInfo] = useState<{name: string; version: string; url: string} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let unlistenScanStarted: UnlistenFn | undefined;
|
||||
@ -634,6 +682,8 @@ function App() {
|
||||
try {
|
||||
console.log(`Updating plugin: ${plugin.name} to version ${plugin.latest_version}`);
|
||||
|
||||
// Try to update the plugin
|
||||
try {
|
||||
const newFilePath = await invoke<string>("update_plugin", {
|
||||
pluginId: plugin.repository_id,
|
||||
version: plugin.latest_version,
|
||||
@ -673,21 +723,34 @@ function App() {
|
||||
serverPath
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
console.error(`Error updating ${plugin.name}:`, errorMessage);
|
||||
} catch (error: any) {
|
||||
console.error(`Error updating plugin: ${error}`);
|
||||
|
||||
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.`);
|
||||
// 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(`Error updating ${plugin.name}: ${errorMessage}`);
|
||||
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 && (
|
||||
<PremiumPluginModal
|
||||
pluginName={premiumPluginInfo.name}
|
||||
pluginVersion={premiumPluginInfo.version}
|
||||
resourceUrl={premiumPluginInfo.url}
|
||||
onClose={() => setPremiumPluginInfo(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
</main>
|
||||
|
||||
<footer className="app-footer">
|
||||
|
Loading…
Reference in New Issue
Block a user