feat(nexusmods): Implement additional endpoints

This commit is contained in:
Lucas Schwiderski 2023-03-07 14:44:41 +01:00
parent 5ca1ca3506
commit 2452f9b4ab
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
3 changed files with 154 additions and 56 deletions

View file

@ -13,7 +13,7 @@ serde_json = "1.0.94"
thiserror = "1.0.39"
time = { version = "0.3.20", features = ["serde"] }
tracing = "0.1.37"
url = "2.3.1"
url = { version = "2.3.1", features = ["serde"] }
[dev-dependencies]
tokio = { version = "1.26.0", features = ["rt", "macros"] }

View file

@ -2,15 +2,15 @@ use std::convert::Infallible;
use lazy_static::lazy_static;
use reqwest::header::{HeaderMap, HeaderValue, InvalidHeaderValue};
use reqwest::{Client, Url};
use serde::ser::SerializeTuple;
use serde::{Deserialize, Serialize};
use reqwest::{Client, RequestBuilder, Url};
use serde::Deserialize;
use thiserror::Error;
use time::OffsetDateTime;
mod types;
pub use types::*;
// TODO: Add OS information
const USER_AGENT: &str = concat!("DTMM/", env!("CARGO_PKG_VERSION"));
const GAME_ID: &str = "warhammer40kdarktide";
lazy_static! {
static ref BASE_URL: Url = Url::parse("https://api.nexusmods.com/v1/").unwrap();
@ -37,44 +37,6 @@ pub enum Error {
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug, Deserialize)]
pub struct UpdateInfo {
pub mod_id: u64,
#[serde(with = "time::serde::timestamp")]
pub latest_file_update: OffsetDateTime,
#[serde(with = "time::serde::timestamp")]
pub latest_mod_activity: OffsetDateTime,
}
#[derive(Copy, Clone, Debug)]
pub enum UpdatePeriod {
Day,
Week,
Month,
}
impl Default for UpdatePeriod {
fn default() -> Self {
Self::Week
}
}
impl Serialize for UpdatePeriod {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut tup = serializer.serialize_tuple(2)?;
tup.serialize_element("period")?;
tup.serialize_element(match self {
Self::Day => "1d",
Self::Week => "1w",
Self::Month => "1m",
})?;
tup.end()
}
}
pub struct Api {
client: Client,
}
@ -93,22 +55,37 @@ impl Api {
Ok(Self { client })
}
#[tracing::instrument(skip(self))]
async fn send<T>(&self, req: RequestBuilder) -> Result<T>
where
T: for<'a> Deserialize<'a>,
{
let res = req.send().await?.error_for_status()?;
tracing::trace!(?res);
let json = res.text().await?;
serde_json::from_str(&json).map_err(|error| Error::Deserialize { json, error })
}
#[tracing::instrument(skip(self))]
pub async fn user_validate(&self) -> Result<User> {
let url = BASE_URL.join("users/validate.json")?;
let req = self.client.get(url);
self.send(req).await
}
#[tracing::instrument(skip(self))]
pub async fn mods_updated(&self, period: UpdatePeriod) -> Result<Vec<UpdateInfo>> {
let url = BASE_URL_GAME.join("mods/updated.json")?;
let req = self.client.get(url).query(&[period]);
self.send(req).await
}
let res = self
.client
.get(url)
.query(&[period])
.send()
.await?
.error_for_status()?;
tracing::trace!(?res);
let json = res.text().await?;
serde_json::from_str(&json).map_err(|error| Error::Deserialize { json, error })
#[tracing::instrument(skip(self))]
pub async fn mods_id(&self, id: u64) -> Result<Mod> {
let url = BASE_URL_GAME.join(&format!("mods/{}.json", id))?;
let req = self.client.get(url);
self.send(req).await
}
}
@ -129,4 +106,23 @@ mod test {
.await
.expect("failed to query 'mods_updated'");
}
#[tokio::test]
async fn user_validate() {
let client = make_api();
client
.user_validate()
.await
.expect("failed to query 'user_validate'");
}
#[tokio::test]
async fn mods_id() {
let client = make_api();
let dmf_id = 8;
client
.mods_id(dmf_id)
.await
.expect("failed to query 'mods_id'");
}
}

102
lib/nexusmods/src/types.rs Normal file
View file

@ -0,0 +1,102 @@
use reqwest::Url;
use serde::ser::SerializeTuple;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Debug, Deserialize)]
pub struct User {
pub user_id: u64,
pub name: String,
pub profile_url: Url,
// pub is_premium: bool,
// pub is_supporter: bool,
// pub email: String,
}
#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModStatus {
Published,
}
#[derive(Copy, Clone, Debug, Deserialize)]
pub enum EndorseStatus {
Undecided,
}
#[derive(Debug, Deserialize)]
pub struct ModEndorsement {
pub endorse_status: EndorseStatus,
#[serde(with = "time::serde::timestamp::option")]
pub timestamp: Option<OffsetDateTime>,
pub version: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct Mod {
pub name: String,
pub description: String,
pub summary: String,
pub picture_url: Url,
pub uid: u64,
pub mod_id: u64,
pub category_id: u64,
pub version: String,
#[serde(with = "time::serde::timestamp")]
pub created_timestamp: OffsetDateTime,
// created_time: OffsetDateTime,
#[serde(with = "time::serde::timestamp")]
pub updated_timestamp: OffsetDateTime,
// updated_time: OffsetDateTime,
pub author: String,
pub uploaded_by: String,
pub uploaded_users_profile_url: Url,
pub status: ModStatus,
pub available: bool,
pub endorsement: ModEndorsement,
// pub mod_downloads: u64,
// pub mod_unique_downloads: u64,
// pub game_id: u64,
// pub allow_rating: bool,
// pub domain_name: String,
// pub endorsement_count: u64,
// pub contains_adult_content: bool,
}
#[derive(Clone, Debug, Deserialize)]
pub struct UpdateInfo {
pub mod_id: u64,
#[serde(with = "time::serde::timestamp")]
pub latest_file_update: OffsetDateTime,
#[serde(with = "time::serde::timestamp")]
pub latest_mod_activity: OffsetDateTime,
}
#[derive(Copy, Clone, Debug)]
pub enum UpdatePeriod {
Day,
Week,
Month,
}
impl Default for UpdatePeriod {
fn default() -> Self {
Self::Week
}
}
impl Serialize for UpdatePeriod {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut tup = serializer.serialize_tuple(2)?;
tup.serialize_element("period")?;
tup.serialize_element(match self {
Self::Day => "1d",
Self::Week => "1w",
Self::Month => "1m",
})?;
tup.end()
}
}