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