feat: get songs from jellyfin

This commit is contained in:
Alexander Navarro 2025-10-18 19:31:45 -03:00
parent 49cf01c668
commit 12ad788452
7 changed files with 131 additions and 18 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
.env

59
Cargo.lock generated
View file

@ -924,6 +924,15 @@ dependencies = [
"zerovec",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
@ -948,6 +957,35 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
@ -1439,6 +1477,7 @@ dependencies = [
"anyhow",
"chrono",
"clap",
"rand",
"reqwest",
"serde",
"serde_json",
@ -1891,6 +1930,26 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.6"

View file

@ -7,6 +7,7 @@ edition = "2024"
anyhow = "1.0.100"
chrono = { version = "0.4.42", features = ["serde"] }
clap = { version = "4.5.49", features = ["derive"] }
rand = "0.9.2"
reqwest = { version = "0.12.24", features = ["gzip", "json"] }
serde = "1.0.228"
serde_json = "1.0.145"

View file

@ -1,2 +1,5 @@
[env]
_.file = '.env'
[tasks.dev]
run = "cargo run"

View file

@ -1,12 +1,11 @@
use anyhow::anyhow;
use rand::seq::IteratorRandom;
use reqwest::header::{HeaderMap, HeaderValue};
use crate::jellyfin::models::GetItemsResponseModel;
use crate::jellyfin::models::{GetItemsQueryString, GetItemsResponseModel, ItemModel};
pub mod models;
static JELLYFIN_VGM_FOLDER: &str = "";
pub struct Client {
_client: reqwest::Client,
host: String,
@ -18,7 +17,7 @@ impl Client {
let mut headers = HeaderMap::new();
let auth_str = format!(
r#"MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFgxMTsgTGludXggeDg2XzY0OyBydjo5NC4wKSBHZWNrby8yMDEwMDEwMSBGaXJlZm94Lzk0LjB8MTYzODA1MzA2OTY4Mw11", Version="10.7.6", Token="{}""#,
"e9d4caa8c94f429280b43ba45a50ba6b"
std::env::var("JELLYFIN_API_KEY")?
);
headers.insert("Authorization", HeaderValue::from_str(auth_str.as_str())?);
@ -32,24 +31,24 @@ impl Client {
Ok(Self {
_client,
host: host.into(),
user_id: String::from("4a80af8c258e417e98f86e870e17d929"),
user_id: std::env::var("JELLYFIN_USER_ID")?,
})
}
pub async fn get_items<T: Into<String> + std::fmt::Display>(
&self,
parent_id: T,
query: GetItemsQueryString,
) -> anyhow::Result<GetItemsResponseModel> {
// INFO: needs to be created manually, escaped characters mess with Jellyfin server
let query = format!(
"parentId={parent_id}&sort_by=SortName&fields=Path,RecursiveItemCount&enable_images=false"
);
let response = self
._client
.get(format!(
"{}/Users/{}/items?{}",
self.host, self.user_id, query
self.host,
self.user_id,
query.build(parent_id)
))
.send()
.await?;
@ -69,4 +68,42 @@ impl Client {
Ok(context)
}
pub async fn get_random_vgm_album(&self) -> anyhow::Result<ItemModel> {
let response_folders = self
.get_items(
std::env::var("JELLYFIN_VGM_FOLDER")?,
GetItemsQueryString::Folder,
)
.await?;
let mut rng = rand::rng();
let folder = response_folders
.items
.into_iter()
.filter(|item| item.recursive_item_count > 0)
.choose(&mut rng)
.ok_or(anyhow!("Failed to get a VGM folder from Jellyfin!"))?;
let response_albums = self
.get_items(&folder.id, GetItemsQueryString::Album)
.await?;
let folder = response_albums
.items
.into_iter()
.filter(|item| item.recursive_item_count > 0)
.choose(&mut rng)
.ok_or(anyhow!("Failed to get a VGM album from Jellyfin!"))?;
Ok(folder)
}
pub async fn get_songs<T: Into<String> + std::fmt::Display>(
&self,
album_id: T,
) -> anyhow::Result<GetItemsResponseModel> {
self.get_items(album_id, GetItemsQueryString::Songs).await
}
}

View file

@ -1,12 +1,22 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetItemsQueryStringModel {
pub parent_id: String,
pub sort_by: String,
pub fields: String,
pub enable_images: bool,
pub enum GetItemsQueryString {
Folder,
Album,
Songs,
}
impl GetItemsQueryString {
pub fn build(&self, parent_id: impl Into<String> + std::fmt::Display) -> String {
let common = format!("parentId={parent_id}&enable_images=false");
match self {
Self::Folder => format!("{common}&SortBy=SortName&fields=Path,RecursiveItemCount"),
Self::Album => format!("{common}&SortBy=SortName&fields=Path,RecursiveItemCount"),
Self::Songs => format!(
"{common}&SortBy=ParentIndexNumber,IndexNumber,SortName&fields=ItemCounts,MediaSourceCount,Path"
),
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -32,6 +42,7 @@ pub struct ItemModel {
pub album_artist: Option<String>,
pub album_artists: Option<Vec<AlbumArtistModel>>,
pub media_type: String,
#[serde(default)]
pub recursive_item_count: u64,
}

View file

@ -12,8 +12,9 @@ async fn main() -> anyhow::Result<()> {
let jelly = jellyfin::Client::new("https://media.hoshikusu.xyz")?;
let items = jelly.get_items("574563ff9a41f2c98c234db69157cc87").await?;
println!("{:#?}", items);
let item = jelly.get_random_vgm_album().await?;
let songs = jelly.get_songs(item.id).await?;
println!("{:#?}", songs);
Ok(())
}