192 lines
5.4 KiB
Rust
192 lines
5.4 KiB
Rust
use crate::{config::Config, util, util::CliStyle};
|
|
use addonscript::manifest::{
|
|
Contributor,
|
|
File,
|
|
Manifest,
|
|
Meta,
|
|
Relation,
|
|
Repository,
|
|
RepositoryType,
|
|
};
|
|
use anyhow::Context;
|
|
use log::{debug, info};
|
|
use reqwest::Client;
|
|
use serde::Serialize;
|
|
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
|
use tera::Tera;
|
|
use twitch::api::AddonInfoResponse;
|
|
|
|
const TEMPLATE: &str = include_str!("../../assets/modlist.html.tera");
|
|
|
|
pub async fn run(
|
|
(_config, mut manifest): (Config, Manifest),
|
|
outfile: PathBuf,
|
|
) -> anyhow::Result<()> {
|
|
let version = manifest
|
|
.versions
|
|
.pop()
|
|
.context("Manifest has no versions!")?;
|
|
let repos = util::repo_map(manifest.repositories);
|
|
|
|
info!("instatiating tera engine");
|
|
let mut tera = Tera::default();
|
|
tera.add_raw_template("modlist", TEMPLATE)?;
|
|
|
|
let http = Arc::new(Client::new());
|
|
|
|
println!("{}", "Resolving metas.".info());
|
|
let mut metas = vec![];
|
|
let mut cf_rels = vec![];
|
|
for rel in version.relations {
|
|
let mi = get_meta(rel, &repos)?;
|
|
|
|
match mi {
|
|
MetaInfo::Meta(meta) => metas.push(meta),
|
|
MetaInfo::CfId(id) => cf_rels.push(id),
|
|
}
|
|
}
|
|
|
|
if !cf_rels.is_empty() {
|
|
println!("{}", "Querying CF metas.".info());
|
|
|
|
info!(
|
|
"Requesting addon info for {} curseforge mods",
|
|
cf_rels.len()
|
|
);
|
|
|
|
let res = http
|
|
.post("https://addons-ecs.forgesvc.net/api/v2/addon")
|
|
.body(
|
|
serde_json::to_string(&cf_rels)
|
|
.context("Failed to serialize curseforge relation IDs")?,
|
|
)
|
|
.header("Content-Type", "application/json")
|
|
.send()
|
|
.await
|
|
.context("Failed sending CF relation request")?
|
|
.bytes()
|
|
.await
|
|
.context("Failed getting CF relation response body")?;
|
|
|
|
let cf_metas = serde_json::from_slice::<Vec<AddonInfoResponse>>(&res)
|
|
.context("Failed deserializing CF relation response")?;
|
|
|
|
info!("Converting CF metas to AS metas");
|
|
let cf_metas = cf_metas.into_iter().map(|m| Meta {
|
|
name: m.name,
|
|
contributors: m
|
|
.authors
|
|
.into_iter()
|
|
.map(|a| Contributor {
|
|
name: a.name,
|
|
roles: vec!["author".into()],
|
|
})
|
|
.collect(),
|
|
description: Some(m.summary),
|
|
icon_url: m
|
|
.attachments
|
|
.into_iter()
|
|
.find(|a| a.is_default)
|
|
.map(|a| a.url.to_string()),
|
|
website_url: Some(m.website_url),
|
|
});
|
|
|
|
metas.extend(cf_metas);
|
|
}
|
|
|
|
metas.sort_by_key(|m| m.name.to_ascii_lowercase());
|
|
|
|
println!("{}", "Rendering modlist.".info());
|
|
|
|
let rendered = tera.render(
|
|
"modlist",
|
|
&tera::Context::from_serialize(&ModListContent {
|
|
metas,
|
|
pack_meta: manifest.meta,
|
|
})?,
|
|
)?;
|
|
|
|
println!("{}", "Writing file.".info());
|
|
if let Some(parent) = outfile.parent() {
|
|
tokio::fs::create_dir_all(parent).await?;
|
|
}
|
|
tokio::fs::write(outfile, rendered).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_meta(rel: Relation, repos: &HashMap<String, Repository>) -> anyhow::Result<MetaInfo> {
|
|
debug!("getting meta for {:?}", &rel);
|
|
if let Some(meta) = rel.meta {
|
|
return Ok(MetaInfo::Meta(meta));
|
|
}
|
|
|
|
if rel.file.is_none() {
|
|
return Ok(MetaInfo::Meta(Meta {
|
|
name: rel.id.clone(),
|
|
contributors: vec![],
|
|
description: rel.versions.map(|v| format!("version {}", v)),
|
|
icon_url: None,
|
|
website_url: None,
|
|
}));
|
|
}
|
|
|
|
let file = rel.file.unwrap();
|
|
|
|
match file {
|
|
File::Link { link, id, .. } => {
|
|
Ok(MetaInfo::Meta(Meta {
|
|
name: if let Some(id) = id {
|
|
id
|
|
} else {
|
|
util::link_file_name(&link)?
|
|
},
|
|
contributors: vec![],
|
|
// this is fine, since if a link relation is used you should probably add a local
|
|
// meta object anyways, in which case this code won't be reached.
|
|
description: Some(format!("{:?}", &link)),
|
|
icon_url: None,
|
|
website_url: None,
|
|
}))
|
|
},
|
|
File::Maven {
|
|
repository,
|
|
artifact,
|
|
..
|
|
} => {
|
|
let repo = repos
|
|
.get(&repository)
|
|
.context("File references unknown repository!")?;
|
|
|
|
match repo.repo_type {
|
|
RepositoryType::Maven => Ok(MetaInfo::Meta(Meta {
|
|
name: artifact,
|
|
contributors: vec![],
|
|
description: None,
|
|
icon_url: None,
|
|
website_url: None,
|
|
})),
|
|
RepositoryType::Curseforge => {
|
|
let (p_id, _) = util::parse_curseforge_artifact(&artifact)?;
|
|
let p_id = p_id
|
|
.parse::<u32>()
|
|
.context("Failed to parse curseforge ID")?;
|
|
|
|
Ok(MetaInfo::CfId(p_id))
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct ModListContent {
|
|
metas: Vec<Meta>,
|
|
pack_meta: Meta,
|
|
}
|
|
|
|
enum MetaInfo {
|
|
Meta(Meta),
|
|
CfId(u32),
|
|
}
|