ajour_core/repository/backend/
wowi.rs

1use super::*;
2use crate::config::Flavor;
3use crate::error::{DownloadError, RepositoryError};
4use crate::network::request_async;
5use crate::repository::{ReleaseChannel, RemotePackage, RepositoryKind, RepositoryPackage};
6
7use async_trait::async_trait;
8use chrono::{TimeZone, Utc};
9use isahc::AsyncReadResponseExt;
10use serde::Deserialize;
11
12use std::collections::HashMap;
13
14const API_ENDPOINT: &str = "https://api.mmoui.com/v4/game/WOW/filedetails";
15const ADDON_URL: &str = "https://www.wowinterface.com/downloads/info";
16
17#[derive(Debug, Clone)]
18pub struct WowI {
19    pub id: String,
20    pub flavor: Flavor,
21}
22
23#[async_trait]
24impl Backend for WowI {
25    async fn get_metadata(&self) -> Result<RepositoryMetadata, RepositoryError> {
26        let packages = fetch_remote_packages(&[self.id.clone()]).await?;
27
28        let package = packages
29            .into_iter()
30            .next()
31            .ok_or(RepositoryError::WowIMissingPackage {
32                id: self.id.clone(),
33            })?;
34
35        let metadata = metadata_from_wowi_package(package);
36
37        Ok(metadata)
38    }
39
40    async fn get_changelog(
41        &self,
42        _file_id: Option<i64>,
43        _tag_name: Option<String>,
44    ) -> Result<Option<String>, RepositoryError> {
45        Ok(None)
46    }
47}
48
49pub(crate) fn metadata_from_wowi_package(package: WowIPackage) -> RepositoryMetadata {
50    let mut remote_packages = HashMap::new();
51
52    {
53        let version = package.version.clone();
54        let download_url = package.download_uri.clone();
55        let date_time = Utc.timestamp(package.last_update / 1000, 0);
56
57        let package = RemotePackage {
58            version,
59            download_url,
60            date_time: Some(date_time),
61            file_id: None,
62            modules: vec![],
63        };
64
65        // Since WowI does not support release channels, our default is 'stable'.
66        remote_packages.insert(ReleaseChannel::Stable, package);
67    }
68
69    let mut metadata = RepositoryMetadata::empty();
70    metadata.remote_packages = remote_packages;
71
72    let website_url = addon_url(&package.id.to_string());
73    metadata.website_url = Some(website_url.clone());
74    metadata.changelog_url = Some(format!("{}/#changelog", website_url));
75    metadata.title = Some(package.title);
76
77    metadata
78}
79
80/// Return the wowi API endpoint.
81fn api_endpoint(ids: &str) -> String {
82    format!("{}/{}.json", API_ENDPOINT, ids)
83}
84
85/// Returns the addon website url.
86pub(crate) fn addon_url(id: &str) -> String {
87    format!("{}{}", ADDON_URL, id)
88}
89
90pub(crate) async fn batch_fetch_repo_packages(
91    flavor: Flavor,
92    wowi_ids: &[String],
93) -> Result<Vec<RepositoryPackage>, DownloadError> {
94    let mut wowi_repo_packages = vec![];
95
96    if wowi_ids.is_empty() {
97        return Ok(wowi_repo_packages);
98    }
99
100    let wowi_packages = wowi::fetch_remote_packages(&wowi_ids).await?;
101
102    wowi_repo_packages.extend(
103        wowi_packages
104            .into_iter()
105            .map(|package| {
106                (
107                    package.id.to_string(),
108                    wowi::metadata_from_wowi_package(package),
109                )
110            })
111            .filter_map(|(id, metadata)| {
112                RepositoryPackage::from_repo_id(flavor, RepositoryKind::WowI, id)
113                    .ok()
114                    .map(|r| r.with_metadata(metadata))
115            }),
116    );
117
118    Ok(wowi_repo_packages)
119}
120
121/// Function to fetch a remote addon package which contains
122/// information about the addon on the repository.
123pub(crate) async fn fetch_remote_packages(
124    ids: &[String],
125) -> Result<Vec<WowIPackage>, DownloadError> {
126    let url = api_endpoint(&ids.join(","));
127    let timeout = Some(30);
128    let mut resp = request_async(&url, vec![], timeout).await?;
129
130    if resp.status().is_success() {
131        let packages = resp.json().await;
132        Ok(packages?)
133    } else {
134        Err(DownloadError::InvalidStatusCode {
135            code: resp.status(),
136            url,
137        })
138    }
139}
140
141#[serde(rename_all = "camelCase")]
142#[derive(Clone, Debug, Deserialize)]
143/// Struct for applying wowi details to an `Addon`.
144pub struct WowIPackage {
145    pub id: i64,
146    pub title: String,
147    pub version: String,
148    pub download_uri: String,
149    pub last_update: i64,
150    pub author: String,
151    pub description: String,
152}