Get the daemon to actually work
This commit is contained in:
parent
587f82932b
commit
19345e771c
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
/navidrome/target
|
.env
|
||||||
/listenbrainz/target
|
|
||||||
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
@ -10,14 +10,18 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="CargoProjects">
|
<component name="CargoProjects">
|
||||||
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
||||||
<cargoProject FILE="$PROJECT_DIR$/navidrome/Cargo.toml" />
|
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="52900e09-9584-4b6c-95ff-fbd4ed5d8b2c" name="Changes" comment="Add interface package. Start adding auth stuff for refreshing tokens.">
|
<list default="true" id="52900e09-9584-4b6c-95ff-fbd4ed5d8b2c" name="Changes" comment="Add interface package. Start adding auth stuff for refreshing tokens.">
|
||||||
|
<change afterPath="$PROJECT_DIR$/migrations/20240416175952_drop_useless_columns.sql" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/playlist.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/daemon/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/mod.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/daemon/update_playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/update_playlists.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/daemon/update_playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/update_playlists.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/models.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/models.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/subsonic.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/subsonic.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/subsonic.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/subsonic.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/track.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/track.rs" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -77,6 +81,7 @@
|
|||||||
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/client/playlists.rs": "true",
|
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/client/playlists.rs": "true",
|
||||||
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/lib.rs": "true",
|
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/lib.rs": "true",
|
||||||
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/models.rs": "true",
|
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/models.rs": "true",
|
||||||
|
"org.rust.first.attach.projects": "true",
|
||||||
"settings.editor.selected.configurable": "language.rust.cargo.check",
|
"settings.editor.selected.configurable": "language.rust.cargo.check",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
@ -152,7 +157,10 @@
|
|||||||
<workItem from="1694621211523" duration="3713000" />
|
<workItem from="1694621211523" duration="3713000" />
|
||||||
<workItem from="1708962769688" duration="6050000" />
|
<workItem from="1708962769688" duration="6050000" />
|
||||||
<workItem from="1710605458078" duration="1011000" />
|
<workItem from="1710605458078" duration="1011000" />
|
||||||
<workItem from="1710677603495" duration="13610000" />
|
<workItem from="1710677603495" duration="13803000" />
|
||||||
|
<workItem from="1710872266453" duration="225000" />
|
||||||
|
<workItem from="1713283733149" duration="180000" />
|
||||||
|
<workItem from="1713283935337" duration="7066000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Structure">
|
<task id="LOCAL-00001" summary="Structure">
|
||||||
<created>1692008860369</created>
|
<created>1692008860369</created>
|
||||||
|
8
migrations/20240416175952_drop_useless_columns.sql
Normal file
8
migrations/20240416175952_drop_useless_columns.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE "tracked_playlist"
|
||||||
|
RENAME COLUMN "rule_id" TO "id";
|
||||||
|
|
||||||
|
ALTER TABLE "tracked_playlist"
|
||||||
|
DROP COLUMN playlist_name;
|
||||||
|
|
||||||
|
ALTER TABLE "tracked_playlist"
|
||||||
|
DROP COLUMN reduce_duplication_on;
|
@ -2,7 +2,6 @@ use crate::listenbrainz;
|
|||||||
use crate::listenbrainz::StatsRange;
|
use crate::listenbrainz::StatsRange;
|
||||||
use crate::models::TrackedPlaylist;
|
use crate::models::TrackedPlaylist;
|
||||||
use crate::subsonic::Subsonic;
|
use crate::subsonic::Subsonic;
|
||||||
use crate::track::Track;
|
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -13,6 +12,8 @@ use tokio::time::{interval, MissedTickBehavior};
|
|||||||
pub enum UpdatePlaylistError {
|
pub enum UpdatePlaylistError {
|
||||||
#[error("Bad range specified")]
|
#[error("Bad range specified")]
|
||||||
BadRange,
|
BadRange,
|
||||||
|
#[error("Operation requires a playlist ID")]
|
||||||
|
NoPlaylistId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_playlists_daemon(
|
pub async fn update_playlists_daemon(
|
||||||
@ -52,45 +53,58 @@ async fn update_playlist(
|
|||||||
media_client: &Subsonic,
|
media_client: &Subsonic,
|
||||||
playlist: TrackedPlaylist,
|
playlist: TrackedPlaylist,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let range =
|
match playlist.playlist_id {
|
||||||
StatsRange::from_param(&playlist.tracking_type).ok_or(UpdatePlaylistError::BadRange)?;
|
Some(playlist_id) => {
|
||||||
|
let range = StatsRange::from_param(&playlist.tracking_type)
|
||||||
|
.ok_or(UpdatePlaylistError::BadRange)?;
|
||||||
|
|
||||||
let recordings = listenbrainz::recordings(playlist.tracking_user, range)
|
let recordings = listenbrainz::recordings(playlist.tracking_user, range)
|
||||||
.await?
|
.await?
|
||||||
.recordings;
|
.recordings;
|
||||||
|
|
||||||
let mut tracks = vec![];
|
let mut tracks = vec![];
|
||||||
for recording in recordings {
|
for recording in recordings {
|
||||||
let search_results = media_client
|
let search_results = media_client
|
||||||
.song_search(format!(
|
.song_search(format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
recording.track_name, recording.artist_name
|
recording.track_name, recording.artist_name
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let filtered = search_results.iter().find(|s| {
|
let filtered = search_results
|
||||||
alpha_compare(&s.title, &recording.track_name)
|
.iter()
|
||||||
&& alpha_compare(&s.album, &recording.release_name)
|
.find(|s| {
|
||||||
});
|
alpha_compare(&s.title, &recording.track_name)
|
||||||
|
&& alpha_compare(&s.album, &recording.release_name)
|
||||||
|
})
|
||||||
|
.cloned();
|
||||||
|
|
||||||
match filtered {
|
match filtered {
|
||||||
Some(track) => {
|
Some(track) => {
|
||||||
tracks.push(track.clone());
|
tracks.push(track.id);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!(
|
||||||
|
"Couldn't find matching track for {} - {}",
|
||||||
|
recording.track_name, recording.artist_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
println!(
|
let playlist = media_client.get_playlist(playlist_id.to_string()).await?;
|
||||||
"Couldn't find matching track for {} - {}",
|
media_client
|
||||||
recording.track_name, recording.artist_name
|
.update_playlist(
|
||||||
|
playlist_id.to_string(),
|
||||||
|
tracks,
|
||||||
|
(0..playlist.song_count).collect(),
|
||||||
)
|
)
|
||||||
}
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
None => Err(Box::new(UpdatePlaylistError::NoPlaylistId)),
|
||||||
}
|
}
|
||||||
|
|
||||||
media_client
|
|
||||||
.update_playlist(playlist.playlist_id.unwrap().to_string(), vec![], vec![])
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alpha_compare(a: &str, b: &str) -> bool {
|
fn alpha_compare(a: &str, b: &str) -> bool {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod daemon;
|
mod daemon;
|
||||||
mod listenbrainz;
|
mod listenbrainz;
|
||||||
mod models;
|
mod models;
|
||||||
|
mod playlist;
|
||||||
mod subsonic;
|
mod subsonic;
|
||||||
mod track;
|
mod track;
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/playlists", get(all_playlists))
|
.route("/playlists", get(all_playlists))
|
||||||
.route("/playlists", put(create_playlist))
|
.route("/playlists", put(create_tracking_playlist))
|
||||||
.with_state(database);
|
.with_state(database);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("localhost:3000").await?;
|
let listener = tokio::net::TcpListener::bind("localhost:3000").await?;
|
||||||
@ -68,7 +69,7 @@ enum Response<T> {
|
|||||||
Error(JsonError),
|
Error(JsonError),
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_playlist(
|
async fn create_tracking_playlist(
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
extract::Json(partial): extract::Json<PartialTrackedPlaylist>,
|
extract::Json(partial): extract::Json<PartialTrackedPlaylist>,
|
||||||
) -> Json<Response<TrackedPlaylist>> {
|
) -> Json<Response<TrackedPlaylist>> {
|
||||||
|
@ -6,11 +6,10 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct PartialTrackedPlaylist {
|
pub struct PartialTrackedPlaylist {
|
||||||
pub playlist_name: Option<String>,
|
pub playlist_id: Option<Uuid>,
|
||||||
pub playlist_size: i32,
|
pub playlist_size: i32,
|
||||||
pub tracking_user: Option<String>,
|
pub tracking_user: Option<String>,
|
||||||
pub tracking_type: Option<String>,
|
pub tracking_type: Option<String>,
|
||||||
pub reduce_duplication_on: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialTrackedPlaylist {
|
impl PartialTrackedPlaylist {
|
||||||
@ -21,15 +20,16 @@ impl PartialTrackedPlaylist {
|
|||||||
let uuid = Uuid::new_v4();
|
let uuid = Uuid::new_v4();
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"INSERT INTO "tracked_playlist"
|
r#"
|
||||||
(rule_id, playlist_name, playlist_size, tracking_user, tracking_type, reduce_duplication_on)
|
INSERT INTO "tracked_playlist"
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)"#,
|
(id, playlist_id, playlist_size, tracking_user, tracking_type)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
"#,
|
||||||
uuid,
|
uuid,
|
||||||
self.playlist_name,
|
self.playlist_id,
|
||||||
self.playlist_size,
|
self.playlist_size,
|
||||||
self.tracking_user,
|
self.tracking_user,
|
||||||
self.tracking_type,
|
self.tracking_type,
|
||||||
self.reduce_duplication_on,
|
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -40,13 +40,11 @@ impl PartialTrackedPlaylist {
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct TrackedPlaylist {
|
pub struct TrackedPlaylist {
|
||||||
pub rule_id: Uuid,
|
pub id: Uuid,
|
||||||
pub playlist_id: Option<UuidType>,
|
pub playlist_id: Option<UuidType>,
|
||||||
pub playlist_name: Option<String>,
|
|
||||||
pub playlist_size: i32,
|
pub playlist_size: i32,
|
||||||
pub tracking_user: String,
|
pub tracking_user: String,
|
||||||
pub tracking_type: String,
|
pub tracking_type: String,
|
||||||
pub reduce_duplication_on: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrackedPlaylist {
|
impl TrackedPlaylist {
|
||||||
@ -62,7 +60,7 @@ impl TrackedPlaylist {
|
|||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
r#"SELECT * FROM "tracked_playlist" WHERE rule_id = $1"#,
|
r#"SELECT * FROM "tracked_playlist" WHERE id = $1"#,
|
||||||
uuid
|
uuid
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
|
16
src/playlist.rs
Normal file
16
src/playlist.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use crate::track::Track;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
fn default_vec() -> Vec<Track> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Playlist {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "songCount")]
|
||||||
|
pub song_count: usize,
|
||||||
|
#[serde(default = "default_vec")]
|
||||||
|
pub entry: Vec<Track>,
|
||||||
|
}
|
121
src/subsonic.rs
121
src/subsonic.rs
@ -1,3 +1,4 @@
|
|||||||
|
use crate::playlist::Playlist;
|
||||||
use crate::track::Track;
|
use crate::track::Track;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -6,8 +7,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
pub struct Subsonic {
|
pub struct Subsonic {
|
||||||
username: String,
|
username: String,
|
||||||
salt: String,
|
password: String,
|
||||||
hash: String,
|
|
||||||
base: String,
|
base: String,
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
@ -29,25 +29,48 @@ struct SearchResponseSongs {
|
|||||||
song: Vec<Track>,
|
song: Vec<Track>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct GetPlaylistResponse {
|
||||||
|
playlist: Playlist,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AuthParams {
|
||||||
|
salt: String,
|
||||||
|
hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Subsonic {
|
impl Subsonic {
|
||||||
|
fn auth_params(&self) -> AuthParams {
|
||||||
|
let salt = Uuid::new_v4().simple().to_string();
|
||||||
|
let hash = format!(
|
||||||
|
"{:?}",
|
||||||
|
md5::compute(format!("{}{}", self.password, salt).into_bytes())
|
||||||
|
);
|
||||||
|
|
||||||
|
AuthParams { salt, hash }
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn song_search(
|
pub async fn song_search(
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
) -> Result<Vec<Track>, Box<dyn std::error::Error>> {
|
) -> Result<Vec<Track>, Box<dyn std::error::Error>> {
|
||||||
|
let auth_params = self.auth_params();
|
||||||
|
let query_params = [
|
||||||
|
("query", query.as_str()),
|
||||||
|
("artistCount", "0"),
|
||||||
|
("albumCount", "0"),
|
||||||
|
("u", self.username.as_str()),
|
||||||
|
("s", auth_params.salt.as_str()),
|
||||||
|
("t", auth_params.hash.as_str()),
|
||||||
|
("v", "16"),
|
||||||
|
("c", "playlistd"),
|
||||||
|
("f", "json"),
|
||||||
|
];
|
||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.client
|
.client
|
||||||
.get(format!("{}/search3", self.base))
|
.get(format!("{}/search3", self.base))
|
||||||
.query(&[
|
.query(&query_params)
|
||||||
("query", query.as_str()),
|
|
||||||
("artistCount", "0"),
|
|
||||||
("albumCount", "0"),
|
|
||||||
("u", self.username.as_str()),
|
|
||||||
("s", self.salt.as_str()),
|
|
||||||
("t", self.hash.as_str()),
|
|
||||||
("v", "16"),
|
|
||||||
("c", "playlistd"),
|
|
||||||
("f", "json"),
|
|
||||||
])
|
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.json::<SubsonicResponse<SearchResponse>>()
|
.json::<SubsonicResponse<SearchResponse>>()
|
||||||
@ -57,37 +80,67 @@ impl Subsonic {
|
|||||||
.song)
|
.song)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_playlist(
|
||||||
|
&self,
|
||||||
|
playlist_id: String,
|
||||||
|
) -> Result<Playlist, Box<dyn std::error::Error>> {
|
||||||
|
let auth_params = self.auth_params();
|
||||||
|
|
||||||
|
let get_params = [
|
||||||
|
("u", self.username.to_string()),
|
||||||
|
("s", auth_params.salt.to_string()),
|
||||||
|
("t", auth_params.hash.to_string()),
|
||||||
|
("v", String::from("16")),
|
||||||
|
("c", String::from("playlistd")),
|
||||||
|
("f", String::from("json")),
|
||||||
|
("id", playlist_id),
|
||||||
|
];
|
||||||
|
|
||||||
|
Ok(self
|
||||||
|
.client
|
||||||
|
.get(format!("{}/getPlaylist", self.base))
|
||||||
|
.query(&get_params)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<SubsonicResponse<GetPlaylistResponse>>()
|
||||||
|
.await?
|
||||||
|
.subsonic_response
|
||||||
|
.playlist)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update_playlist(
|
pub async fn update_playlist(
|
||||||
&self,
|
&self,
|
||||||
playlist_id: String,
|
playlist_id: String,
|
||||||
add: Vec<String>,
|
add: Vec<String>,
|
||||||
remove: Vec<usize>,
|
remove: Vec<usize>,
|
||||||
) -> Result<Vec<Track>, Box<dyn std::error::Error>> {
|
) -> Result<Playlist, Box<dyn std::error::Error>> {
|
||||||
let mut query_params = vec![
|
let auth_params = self.auth_params();
|
||||||
|
|
||||||
|
let mut update_query = vec![
|
||||||
("u", self.username.to_string()),
|
("u", self.username.to_string()),
|
||||||
("s", self.salt.to_string()),
|
("s", auth_params.salt.to_string()),
|
||||||
("t", self.hash.to_string()),
|
("t", auth_params.hash.to_string()),
|
||||||
("v", String::from("16")),
|
("v", String::from("16")),
|
||||||
("c", String::from("playlistd")),
|
("c", String::from("playlistd")),
|
||||||
("f", String::from("json")),
|
("f", String::from("json")),
|
||||||
|
("playlistId", playlist_id.clone()),
|
||||||
];
|
];
|
||||||
|
|
||||||
for id in add {
|
for id in add {
|
||||||
query_params.push(("songIdToAdd", id));
|
update_query.push(("songIdToAdd", id));
|
||||||
}
|
}
|
||||||
|
|
||||||
for index in remove {
|
for index in remove {
|
||||||
query_params.push(("songIndexToRemove", index.to_string()));
|
update_query.push(("songIndexToRemove", index.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self
|
self.client
|
||||||
.client
|
.get(format!("{}/updatePlaylist", self.base))
|
||||||
.get(format!("{}/search3", self.base))
|
.query(&update_query)
|
||||||
.query(&query_params)
|
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?;
|
||||||
.json::<Vec<Track>>()
|
|
||||||
.await?)
|
self.get_playlist(playlist_id).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,23 +180,9 @@ impl SubsonicBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<Subsonic, Box<dyn std::error::Error>> {
|
pub fn build(self) -> Result<Subsonic, Box<dyn std::error::Error>> {
|
||||||
let salt = Uuid::new_v4().simple().to_string();
|
|
||||||
let hash = format!(
|
|
||||||
"{:?}",
|
|
||||||
md5::compute(
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
self.password.ok_or(SubsonicBuilderError::MissingField)?,
|
|
||||||
salt
|
|
||||||
)
|
|
||||||
.as_bytes()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Subsonic {
|
Ok(Subsonic {
|
||||||
username: self.username.ok_or(SubsonicBuilderError::MissingField)?,
|
username: self.username.ok_or(SubsonicBuilderError::MissingField)?,
|
||||||
salt,
|
password: self.password.ok_or(SubsonicBuilderError::MissingField)?,
|
||||||
hash,
|
|
||||||
base: self.base.ok_or(SubsonicBuilderError::MissingField)?,
|
base: self.base.ok_or(SubsonicBuilderError::MissingField)?,
|
||||||
client: Default::default(),
|
client: Default::default(),
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
Loading…
Reference in New Issue
Block a user