Skip to main content

modde_sources/nexus/
mod.rs

1pub mod api;
2pub mod auth;
3pub mod cdn;
4pub mod graphql;
5pub mod install;
6pub mod oauth;
7pub mod updates;
8
9pub use api::NexusApi;
10
11use std::collections::HashMap;
12use std::path::Path;
13
14use anyhow::Result;
15use reqwest::Client;
16use tracing::debug;
17
18use modde_core::manifest::wabbajack::DownloadDirective;
19
20use crate::common::simple_download;
21use crate::traits::{DownloadHandle, DownloadSource, ProgressCallback, VerifiedFile};
22
23/// NexusMods download source.
24///
25/// Requires a Nexus Premium account and API key.
26pub struct NexusSource {
27    client: Client,
28    api_key: String,
29}
30
31impl NexusSource {
32    /// Create a new NexusSource, loading the API key from environment or keyring.
33    pub fn new(client: Client) -> Result<Self> {
34        let api_key = auth::load_api_key()?;
35        Ok(Self { client, api_key })
36    }
37
38    /// Create a new NexusSource with an explicit API key.
39    pub fn with_api_key(client: Client, api_key: String) -> Self {
40        Self { client, api_key }
41    }
42}
43
44impl DownloadSource for NexusSource {
45    fn can_handle(&self, directive: &DownloadDirective) -> bool {
46        matches!(directive, DownloadDirective::Nexus { .. })
47    }
48
49    async fn resolve(&self, directive: &DownloadDirective) -> Result<DownloadHandle> {
50        let DownloadDirective::Nexus {
51            game_id,
52            mod_id,
53            file_id,
54            hash,
55        } = directive
56        else {
57            anyhow::bail!("not a Nexus directive");
58        };
59
60        let download_url = cdn::generate_download_link(
61            &self.client,
62            &self.api_key,
63            game_id.as_str(),
64            *mod_id,
65            *file_id,
66        )
67        .await?;
68
69        debug!(url = %download_url, "resolved Nexus CDN download URL");
70
71        Ok(DownloadHandle {
72            url: download_url,
73            headers: HashMap::new(),
74            expected_hash: *hash,
75            size_hint: None,
76        })
77    }
78
79    async fn download_with_progress(
80        &self,
81        handle: DownloadHandle,
82        dest: &Path,
83        progress: ProgressCallback,
84    ) -> Result<VerifiedFile> {
85        simple_download(&self.client, &handle, dest, &progress).await
86    }
87}