modde_sources/nexus/
mod.rs1pub mod api;
5pub mod auth;
6pub mod cdn;
7pub mod graphql;
8pub mod install;
9pub mod oauth;
10pub mod updates;
11
12pub use api::NexusApi;
13
14const DEFAULT_BASE_URL: &str = "https://api.nexusmods.com/v1";
15const DEFAULT_GRAPHQL_URL: &str = "https://api.nexusmods.com/v2/graphql";
16
17#[must_use]
23pub fn base_url() -> String {
24 std::env::var("MODDE_NEXUS_BASE_URL").unwrap_or_else(|_| DEFAULT_BASE_URL.to_string())
25}
26
27#[must_use]
30pub fn graphql_url() -> String {
31 std::env::var("MODDE_NEXUS_GRAPHQL_URL").unwrap_or_else(|_| DEFAULT_GRAPHQL_URL.to_string())
32}
33
34use std::collections::HashMap;
35use std::path::Path;
36
37use anyhow::Result;
38use reqwest::Client;
39use tracing::debug;
40
41use modde_core::manifest::wabbajack::DownloadDirective;
42
43use crate::common::simple_download;
44use crate::error::{SourceError, SourceResult};
45use crate::traits::{DownloadHandle, DownloadSource, ProgressCallback, VerifiedFile};
46
47pub struct NexusSource {
51 client: Client,
52 api_key: String,
53}
54
55impl NexusSource {
56 pub fn new(client: Client) -> Result<Self> {
58 let api_key = auth::load_api_key()?;
59 Ok(Self { client, api_key })
60 }
61
62 #[must_use]
64 pub fn with_api_key(client: Client, api_key: String) -> Self {
65 Self { client, api_key }
66 }
67}
68
69impl DownloadSource for NexusSource {
70 fn can_handle(&self, directive: &DownloadDirective) -> bool {
71 matches!(directive, DownloadDirective::Nexus { .. })
72 }
73
74 async fn resolve(&self, directive: &DownloadDirective) -> SourceResult<DownloadHandle> {
75 let DownloadDirective::Nexus {
76 game_id,
77 mod_id,
78 file_id,
79 hash,
80 } = directive
81 else {
82 return Err(SourceError::other(anyhow::anyhow!("not a Nexus directive")));
83 };
84
85 let download_url = cdn::generate_download_link(
86 &self.client,
87 &self.api_key,
88 game_id.as_str(),
89 *mod_id,
90 *file_id,
91 )
92 .await?;
93
94 debug!(url = %download_url, "resolved Nexus CDN download URL");
95
96 Ok(DownloadHandle {
97 url: download_url,
98 candidate_urls: Vec::new(),
99 headers: HashMap::new(),
100 expected_hash: *hash,
101 size_hint: None,
102 })
103 }
104
105 async fn download_with_progress(
106 &self,
107 handle: DownloadHandle,
108 dest: &Path,
109 progress: ProgressCallback,
110 ) -> SourceResult<VerifiedFile> {
111 simple_download(&self.client, &handle, dest, &progress).await
112 }
113}