use std::{
collections::HashMap,
env::consts::{ARCH, OS},
io::Cursor,
};
use anyhow::{bail, Context};
use mcvm_core::net::download;
use mcvm_net::github::get_github_releases;
use mcvm_shared::util::TARGET_BITS_STR;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use zip::ZipArchive;
use crate::io::paths::Paths;
use super::PluginManager;
#[derive(Serialize, Deserialize)]
pub struct VerifiedPlugin {
pub id: String,
pub github_owner: String,
pub github_repo: String,
pub description: String,
}
pub async fn get_verified_plugins(
client: &Client,
) -> anyhow::Result<HashMap<String, VerifiedPlugin>> {
let mut list: HashMap<String, VerifiedPlugin> =
serde_json::from_str(include_str!("verified_plugins.json"))
.context("Failed to deserialize core verified list")?;
if let Ok(remote_list) = download::json::<HashMap<String, VerifiedPlugin>>(
"https://github.com/mcvm-launcher/mcvm/blob/main/src/plugin/verified_plugins.json",
client,
)
.await
{
list.extend(remote_list);
}
Ok(list)
}
impl VerifiedPlugin {
pub async fn install(
&self,
version: Option<&str>,
paths: &Paths,
client: &Client,
) -> anyhow::Result<()> {
let releases = get_github_releases(&self.github_owner, &self.github_repo, client)
.await
.context("Failed to get GitHub releases")?;
let mut selected_asset = None;
'outer: for release in releases {
if !release.tag_name.contains("plugin") {
continue;
}
let mut tag_parts = release.tag_name.split('-');
tag_parts.next();
tag_parts.next();
let Some(release_version) = tag_parts.next() else {
continue;
};
if let Some(requested_version) = &version {
if requested_version != &release_version {
continue;
}
}
for asset in release.assets {
if !asset.name.contains(&self.id) {
continue;
}
if !asset.name.contains("universal") && !asset.name.contains(OS) {
continue;
}
if asset.name.contains("x86")
|| asset.name.contains("arm")
|| asset.name.contains("aarch64")
{
if !asset.name.contains(ARCH) {
continue;
}
}
if asset.name.contains("32bit") || asset.name.contains("64bit") {
if !asset.name.contains(TARGET_BITS_STR) {
continue;
}
}
selected_asset = Some(asset);
break 'outer;
}
}
let Some(asset) = selected_asset else {
bail!("Could not find a release that matches your system");
};
if asset.content_type.contains("zip") {
let zip = download::bytes(asset.browser_download_url, client)
.await
.context("Failed to download zipped plugin")?;
let mut zip =
ZipArchive::new(Cursor::new(zip)).context("Failed to read zip archive")?;
PluginManager::remove_plugin(&self.id, paths)
.context("Failed to remove existing plugin")?;
let dir = paths.plugins.join(&self.id);
std::fs::create_dir_all(&dir).context("Failed to create plugin directory")?;
zip.extract(dir).context("Failed to extract plugin files")?;
} else if asset.content_type.contains("json") {
let bytes = download::bytes(asset.browser_download_url, client)
.await
.context("Failed to download plugin JSON")?;
PluginManager::uninstall_plugin(&self.id, paths)
.context("Failed to remove existing plugin")?;
let path = paths.plugins.join(format!("{}.json", &self.id));
std::fs::write(path, bytes).context("Failed to write plugin JSON")?;
} else {
bail!("Plugin files are not of the correct type");
}
Ok(())
}
}