modde_games/tools/
release.rs1use anyhow::Result;
5use reqwest::Client;
6use serde::Deserialize;
7
8use super::{ToolReleaseAsset, ToolReleaseSummary};
9
10#[derive(Debug, Deserialize)]
11struct GitHubRelease {
12 tag_name: Option<String>,
13 name: Option<String>,
14 published_at: Option<String>,
15 assets: Vec<GitHubReleaseAsset>,
16}
17
18#[derive(Debug, Deserialize)]
19struct GitHubReleaseAsset {
20 name: String,
21 browser_download_url: String,
22 size: u64,
23}
24
25pub(crate) async fn github_json<T: for<'de> Deserialize<'de>>(
26 client: &Client,
27 url: &str,
28) -> Result<T> {
29 let mut request = client.get(url).header("User-Agent", "modde");
30 if let Ok(token) = std::env::var("GITHUB_TOKEN") {
31 request = request.header("Authorization", format!("Bearer {token}"));
32 }
33 Ok(request.send().await?.error_for_status()?.json().await?)
34}
35
36pub async fn list_github_releases(repo: &str) -> Result<Vec<ToolReleaseSummary>> {
37 let client = Client::new();
38 let releases: Vec<GitHubRelease> = github_json(
39 &client,
40 &format!("https://api.github.com/repos/{repo}/releases?per_page=100"),
41 )
42 .await?;
43 Ok(releases
44 .into_iter()
45 .filter_map(|release| {
46 Some(ToolReleaseSummary {
47 tag: release.tag_name?,
48 name: release.name,
49 published_at: release.published_at,
50 assets: release
51 .assets
52 .into_iter()
53 .map(|asset| ToolReleaseAsset {
54 name: asset.name,
55 download_url: asset.browser_download_url,
56 size: asset.size,
57 })
58 .collect(),
59 })
60 })
61 .collect())
62}
63
64#[must_use]
65pub fn prepend_latest_dedup<I>(values: I) -> Vec<String>
66where
67 I: IntoIterator<Item = String>,
68{
69 let mut out = vec!["latest".to_string()];
70 for value in values {
71 if value.trim().is_empty() || out.iter().any(|existing| existing == &value) {
72 continue;
73 }
74 out.push(value);
75 }
76 out
77}