lrclib_api_rs/
lib.rs

1use std::borrow::Cow;
2
3use http::{Method, Request};
4use url::Url;
5
6pub mod types;
7use types::LyricsPublishData;
8mod error;
9pub use error::ApiError;
10
11pub type Result<T> = std::result::Result<T, ApiError>;
12
13const BASE_URL: &str = "https://lrclib.net";
14const DEFAULT_USER_AGENT: &str = concat!("LRCAPIWrapper/", env!("CARGO_PKG_VERSION"));
15
16#[derive(Clone, Debug)]
17pub struct LRCLibAPI {
18    base_url: String,
19    user_agent: String,
20}
21
22impl Default for LRCLibAPI {
23    fn default() -> Self {
24        Self {
25            base_url: BASE_URL.into(),
26            user_agent: DEFAULT_USER_AGENT.into(),
27        }
28    }
29}
30
31impl LRCLibAPI {
32    pub fn new() -> Self {
33        Default::default()
34    }
35
36    pub fn with_base_url(base_url: String) -> Self {
37        Self {
38            base_url,
39            ..Default::default()
40        }
41    }
42
43    pub fn with_user_agent(user_agent: String) -> Self {
44        Self {
45            user_agent,
46            ..Default::default()
47        }
48    }
49
50    pub fn with_parts(base_url: String, user_agent: String) -> Self {
51        Self {
52            base_url,
53            user_agent,
54        }
55    }
56}
57
58impl LRCLibAPI {
59    /// result: `types::GetLyricsResponse`
60    pub fn get_lyrics(
61        &self,
62        track_name: &str,
63        artist_name: &str,
64        album_name: Option<&str>,
65        duration: Option<u64>,
66    ) -> Result<Request<()>> {
67        let mut query_params: Vec<(&str, Cow<'_, str>)> = vec![
68            ("track_name", track_name.into()),
69            ("artist_name", artist_name.into()),
70        ];
71
72        if let Some(album) = album_name {
73            query_params.push(("album_name", album.into()));
74        }
75        if let Some(dur) = duration {
76            let dur_s = dur.to_string();
77            query_params.push(("duration", Cow::Owned(dur_s)));
78        }
79
80        let query_string: String = query_params
81            .into_iter()
82            .map(|(key, value)| format!("{}={}", key, value))
83            .collect::<Vec<String>>()
84            .join("&");
85
86        let uri = format!("{}/api/get?{}", &self.base_url, query_string);
87        let uri = Url::parse(&uri)?;
88        let uri = uri.as_str();
89
90        Request::builder()
91            .method(Method::GET)
92            .uri(uri)
93            .header("User-Agent", &self.user_agent)
94            .body(())
95            .map_err(ApiError::from)
96    }
97
98    /// result: `types::GetLyricsResponse`
99    pub fn get_lyrics_by_id(&self, id: u64) -> Result<Request<()>> {
100        let uri = format!("{}/api/get/{}", &self.base_url, id);
101        let uri = Url::parse(&uri)?;
102        let uri = uri.as_str();
103
104        Request::builder()
105            .method(Method::GET)
106            .uri(uri)
107            .header("User-Agent", &self.user_agent)
108            .body(())
109            .map_err(ApiError::from)
110    }
111
112    pub fn search_lyrics_query(&self, query: &str) -> Result<Request<()>> {
113        let query_string = format!("q={query}");
114
115        let uri = format!("{}/api/search?{}", &self.base_url, query_string);
116        let uri = Url::parse(&uri)?;
117        let uri = uri.as_str();
118
119        Request::builder()
120            .method(Method::GET)
121            .uri(uri)
122            .header("User-Agent", &self.user_agent)
123            .body(())
124            .map_err(ApiError::from)
125    }
126
127    /// result: `Vec<types::LyricsData>`
128    pub fn search_lyrics_detailed(
129        &self,
130        track_name: &str,
131        artist_name: Option<&str>,
132        album_name: Option<&str>,
133    ) -> Result<Request<()>> {
134        let mut query_params = vec![];
135        query_params.push(("track_name", track_name));
136
137        if let Some(artist) = artist_name {
138            query_params.push(("artist_name", artist));
139        }
140        if let Some(album) = album_name {
141            query_params.push(("album_name", album));
142        }
143
144        let query_string: String = query_params
145            .into_iter()
146            .map(|(key, value)| format!("{}={}", key, value))
147            .collect::<Vec<String>>()
148            .join("&");
149
150        let uri = format!("{}/api/search?{}", &self.base_url, query_string);
151        let uri = Url::parse(&uri)?;
152        let uri = uri.as_str();
153
154        Request::builder()
155            .method(Method::GET)
156            .uri(uri)
157            .header("User-Agent", &self.user_agent)
158            .body(())
159            .map_err(ApiError::from)
160    }
161
162    /// result: `types::PublishChallenge`
163    pub fn request_publish_challenge(&self) -> Result<Request<()>> {
164        let uri = format!("{}/api/request-challenge", &self.base_url);
165        let uri = Url::parse(&uri)?;
166        let uri = uri.as_str();
167
168        Request::builder()
169            .method(Method::POST)
170            .uri(uri)
171            .header("User-Agent", &self.user_agent)
172            .body(())
173            .map_err(ApiError::from)
174    }
175
176    /// result: `types::PublishResponse`
177    pub fn publish_lyrics(
178        &self,
179        lyrics: &LyricsPublishData,
180        publish_token: &str,
181    ) -> Result<Request<String>> {
182        let uri = format!("{}/api/publish", &self.base_url);
183        let uri = Url::parse(&uri)?;
184        let uri = uri.as_str();
185
186        let body = serde_json::to_string(lyrics)?;
187
188        Request::builder()
189            .method(Method::POST)
190            .uri(uri)
191            .header("User-Agent", &self.user_agent)
192            .header("X-Publish-Token", publish_token)
193            .header("Content-Type", "application/json")
194            .body(body)
195            .map_err(ApiError::from)
196    }
197}