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

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(())
}