98 lines
2.7 KiB
Rust
98 lines
2.7 KiB
Rust
|
|
use std::error::Error;
|
||
|
|
|
||
|
|
use colored::Colorize;
|
||
|
|
use serde::Serialize;
|
||
|
|
use terminal_size;
|
||
|
|
use textwrap::{Options, fill};
|
||
|
|
use terminal_size::{terminal_size as get_terminal_size, Width};
|
||
|
|
|
||
|
|
use crate::models::{config::Config, search::SearchResult};
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct SearchQueryParams {
|
||
|
|
query: String,
|
||
|
|
facets: String,
|
||
|
|
offset: Option<u32>,
|
||
|
|
limit: Option<u32>,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct Search {
|
||
|
|
query: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Search {
|
||
|
|
const BASE_URL: &str = "https://api.modrinth.com/v2";
|
||
|
|
|
||
|
|
pub fn new(query: String) -> Self {
|
||
|
|
Search { query }
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn run(&self) -> Result<(), Box<dyn Error>> {
|
||
|
|
let file_res = tokio::fs::read_to_string("mcsm.toml").await;
|
||
|
|
if file_res.is_err() {
|
||
|
|
return Err("current directory is not a mcsm project".into());
|
||
|
|
}
|
||
|
|
let config: Config = toml::from_str(&file_res.unwrap())?;
|
||
|
|
|
||
|
|
if config.server.loader == "Vanilla" {
|
||
|
|
return Err("cannot search for mods on Vanilla server".into());
|
||
|
|
}
|
||
|
|
|
||
|
|
let client = reqwest::Client::new();
|
||
|
|
let search = format!("{}/search", Self::BASE_URL);
|
||
|
|
|
||
|
|
let facets = format!(
|
||
|
|
r#"[["project_type:mod"],["environment!=client_only"],["environment!=singleplayer_only"],["environment!=unknown"],["categories:{}"],["versions:{}"]]"#,
|
||
|
|
config.server.loader.to_lowercase(),
|
||
|
|
config.server.version,
|
||
|
|
);
|
||
|
|
let params = SearchQueryParams {
|
||
|
|
query: self.query.clone(),
|
||
|
|
facets: facets.into(),
|
||
|
|
offset: None,
|
||
|
|
limit: None,
|
||
|
|
};
|
||
|
|
|
||
|
|
let res: SearchResult = client
|
||
|
|
.get(search)
|
||
|
|
.query(¶ms)
|
||
|
|
.send()
|
||
|
|
.await?
|
||
|
|
.json()
|
||
|
|
.await?;
|
||
|
|
|
||
|
|
self.print_results(&res);
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn print_results(&self, res: &SearchResult) {
|
||
|
|
println!(
|
||
|
|
"{}\n",
|
||
|
|
format!("Found {} result{}", res.total_hits, if res.total_hits == 1 { "" } else { "s" })
|
||
|
|
.green()
|
||
|
|
.bold()
|
||
|
|
);
|
||
|
|
|
||
|
|
let width = get_terminal_size().map(|(Width(w), _)| w as usize).unwrap_or(80);
|
||
|
|
|
||
|
|
for (i, hit) in res.hits.iter().enumerate() {
|
||
|
|
let wrapped = fill(
|
||
|
|
&hit.description,
|
||
|
|
Options::new(width.saturating_sub(3))
|
||
|
|
.initial_indent(" ")
|
||
|
|
.subsequent_indent(" "),
|
||
|
|
);
|
||
|
|
|
||
|
|
println!(
|
||
|
|
"{} {} {}\n by {}\n{}\n",
|
||
|
|
format!("{}.", i + 1).bold(),
|
||
|
|
hit.title.bold(),
|
||
|
|
format!("({})", hit.slug).cyan(),
|
||
|
|
hit.author.yellow(),
|
||
|
|
wrapped,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|