recycleactor commited on
Commit
bb3a894
·
verified ·
1 Parent(s): 7b4fe8e

Upload 2 files

Browse files
Files changed (1) hide show
  1. src/main.rs +219 -4
src/main.rs CHANGED
@@ -143,14 +143,31 @@ fn extract_media_url(media_json: &serde_json::Value) -> String {
143
  };
144
  for m in media_arr {
145
  if let Some(sources) = m.get("sources").and_then(|v| v.as_array()) {
 
 
 
 
146
  for s in sources {
147
- if let Some(url) = s.get("url").and_then(|v| v.as_str()) {
148
- let url = url.trim();
149
- if !url.is_empty() {
150
- return url.to_string();
151
  }
152
  }
153
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  }
155
  }
156
  String::new()
@@ -2237,6 +2254,86 @@ async fn api_deezer_get(
2237
  Ok(v)
2238
  }
2239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2240
  async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairCompleteForm>) -> impl IntoResponse {
2241
  let code = f.code.trim().to_string();
2242
  let email = f.email.trim().to_string();
@@ -2723,6 +2820,118 @@ async fn oauth_flow(Json(req): Json<OauthFlowReq>) -> impl IntoResponse {
2723
  }
2724
  }
2725
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2726
  #[derive(Debug, Deserialize)]
2727
  struct OauthRecoReq {
2728
  access_token: String,
@@ -2964,6 +3173,12 @@ async fn main() {
2964
  .route("/oauth/reco_artists", post(oauth_reco_artists))
2965
  .route("/oauth/reco_tracks", post(oauth_reco_tracks))
2966
  .route("/oauth/history", post(oauth_history))
 
 
 
 
 
 
2967
  .with_state(state)
2968
  .layer(cors)
2969
  .layer(CompressionLayer::new())
 
143
  };
144
  for m in media_arr {
145
  if let Some(sources) = m.get("sources").and_then(|v| v.as_array()) {
146
+ let mut preferred = Vec::new();
147
+ if let Some(s1) = sources.get(1).and_then(|s| s.get("url")).and_then(|u| u.as_str()) {
148
+ preferred.push(s1);
149
+ }
150
  for s in sources {
151
+ if let Some(u) = s.get("url").and_then(|v| v.as_str()) {
152
+ if u.contains("dzcdn.net/media/") {
153
+ preferred.push(u);
 
154
  }
155
  }
156
  }
157
+ if let Some(s0) = sources.get(0).and_then(|s| s.get("url")).and_then(|u| u.as_str()) {
158
+ preferred.push(s0);
159
+ }
160
+ for s in sources {
161
+ if let Some(u) = s.get("url").and_then(|v| v.as_str()) {
162
+ preferred.push(u);
163
+ }
164
+ }
165
+ for p in preferred {
166
+ let p = p.trim();
167
+ if !p.is_empty() {
168
+ return p.to_string();
169
+ }
170
+ }
171
  }
172
  }
173
  String::new()
 
2254
  Ok(v)
2255
  }
2256
 
2257
+ async fn api_deezer_post(
2258
+ client: &reqwest::Client,
2259
+ path: &str,
2260
+ access_token: &str,
2261
+ form: &[(&str, String)],
2262
+ ) -> Result<serde_json::Value, String> {
2263
+ let url = format!("https://api.deezer.com{path}");
2264
+ let mut req = client
2265
+ .post(url)
2266
+ .header("Authorization", format!("Bearer {}", access_token))
2267
+ .query(&[("access_token", access_token)]);
2268
+ if !form.is_empty() {
2269
+ req = req.form(form);
2270
+ }
2271
+ let r = req.send().await.map_err(|_| "api:network".to_string())?;
2272
+ let status = r.status();
2273
+ let text = r.text().await.map_err(|_| "api:read".to_string())?;
2274
+
2275
+ if text.trim() == "true" {
2276
+ return Ok(json!(true));
2277
+ }
2278
+ if text.trim() == "false" {
2279
+ return Ok(json!(false));
2280
+ }
2281
+
2282
+ let v: serde_json::Value = serde_json::from_str(&text).map_err(|_| {
2283
+ let snip: String = text.chars().take(200).collect();
2284
+ format!("api:parse:{}:{}", status.as_u16(), snip)
2285
+ })?;
2286
+ if let Some(e) = v.get("error") {
2287
+ if e.is_object() {
2288
+ let code = e.get("code").and_then(|x| x.as_i64()).unwrap_or(0);
2289
+ let msg = e.get("message").and_then(|x| x.as_str()).unwrap_or("error");
2290
+ return Err(format!("api:{code}:{msg}"));
2291
+ }
2292
+ return Err("api:error".to_string());
2293
+ }
2294
+ Ok(v)
2295
+ }
2296
+
2297
+ async fn api_deezer_delete(
2298
+ client: &reqwest::Client,
2299
+ path: &str,
2300
+ access_token: &str,
2301
+ query: &[(&str, String)],
2302
+ ) -> Result<serde_json::Value, String> {
2303
+ let url = format!("https://api.deezer.com{path}");
2304
+ let mut req = client
2305
+ .delete(url)
2306
+ .header("Authorization", format!("Bearer {}", access_token))
2307
+ .query(&[("access_token", access_token)]);
2308
+ if !query.is_empty() {
2309
+ req = req.query(query);
2310
+ }
2311
+ let r = req.send().await.map_err(|_| "api:network".to_string())?;
2312
+ let status = r.status();
2313
+ let text = r.text().await.map_err(|_| "api:read".to_string())?;
2314
+
2315
+ if text.trim() == "true" {
2316
+ return Ok(json!(true));
2317
+ }
2318
+ if text.trim() == "false" {
2319
+ return Ok(json!(false));
2320
+ }
2321
+
2322
+ let v: serde_json::Value = serde_json::from_str(&text).map_err(|_| {
2323
+ let snip: String = text.chars().take(200).collect();
2324
+ format!("api:parse:{}:{}", status.as_u16(), snip)
2325
+ })?;
2326
+ if let Some(e) = v.get("error") {
2327
+ if e.is_object() {
2328
+ let code = e.get("code").and_then(|x| x.as_i64()).unwrap_or(0);
2329
+ let msg = e.get("message").and_then(|x| x.as_str()).unwrap_or("error");
2330
+ return Err(format!("api:{code}:{msg}"));
2331
+ }
2332
+ return Err("api:error".to_string());
2333
+ }
2334
+ Ok(v)
2335
+ }
2336
+
2337
  async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairCompleteForm>) -> impl IntoResponse {
2338
  let code = f.code.trim().to_string();
2339
  let email = f.email.trim().to_string();
 
2820
  }
2821
  }
2822
 
2823
+ #[derive(Debug, Deserialize)]
2824
+ struct OauthActionReq {
2825
+ access_token: String,
2826
+ track_id: Option<String>,
2827
+ playlist_id: Option<String>,
2828
+ title: Option<String>,
2829
+ }
2830
+
2831
+ async fn oauth_favorite_add(Json(req): Json<OauthActionReq>) -> impl IntoResponse {
2832
+ let token = req.access_token.trim().to_string();
2833
+ let track_id = req.track_id.unwrap_or_default().trim().to_string();
2834
+ if token.is_empty() || track_id.is_empty() {
2835
+ return json_error(StatusCode::BAD_REQUEST, "access_token and track_id required");
2836
+ }
2837
+ let client = reqwest::Client::builder().no_proxy().timeout(std::time::Duration::from_secs(15)).build().unwrap();
2838
+ let me = match api_deezer_get(&client, "/user/me", &token, &[]).await {
2839
+ Ok(v) => v,
2840
+ Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2841
+ };
2842
+ let uid = me.get("id").and_then(|x| x.as_i64()).unwrap_or(0);
2843
+
2844
+ let path = format!("/user/{uid}/tracks");
2845
+ match api_deezer_post(&client, &path, &token, &[("track_id", track_id)]).await {
2846
+ Ok(v) => (StatusCode::OK, Json(v)).into_response(),
2847
+ Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2848
+ }
2849
+ }
2850
+
2851
+ async fn oauth_favorite_remove(Json(req): Json<OauthActionReq>) -> impl IntoResponse {
2852
+ let token = req.access_token.trim().to_string();
2853
+ let track_id = req.track_id.unwrap_or_default().trim().to_string();
2854
+ if token.is_empty() || track_id.is_empty() {
2855
+ return json_error(StatusCode::BAD_REQUEST, "access_token and track_id required");
2856
+ }
2857
+ let client = reqwest::Client::builder().no_proxy().timeout(std::time::Duration::from_secs(15)).build().unwrap();
2858
+ let me = match api_deezer_get(&client, "/user/me", &token, &[]).await {
2859
+ Ok(v) => v,
2860
+ Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2861
+ };
2862
+ let uid = me.get("id").and_then(|x| x.as_i64()).unwrap_or(0);
2863
+
2864
+ let path = format!("/user/{uid}/tracks");
2865
+ match api_deezer_delete(&client, &path, &token, &[("track_id", track_id)]).await {
2866
+ Ok(v) => (StatusCode::OK, Json(v)).into_response(),
2867
+ Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2868
+ }
2869
+ }
2870
+
2871
+ async fn oauth_playlist_add(Json(req): Json<OauthActionReq>) -> impl IntoResponse {
2872
+ let token = req.access_token.trim().to_string();
2873
+ let track_id = req.track_id.unwrap_or_default().trim().to_string();
2874
+ let playlist_id = req.playlist_id.unwrap_or_default().trim().to_string();
2875
+ if token.is_empty() || track_id.is_empty() || playlist_id.is_empty() {
2876
+ return json_error(StatusCode::BAD_REQUEST, "access_token, track_id, playlist_id required");
2877
+ }
2878
+ let client = reqwest::Client::builder().no_proxy().timeout(std::time::Duration::from_secs(15)).build().unwrap();
2879
+ let path = format!("/playlist/{playlist_id}/tracks");
2880
+ match api_deezer_post(&client, &path, &token, &[("songs", track_id)]).await {
2881
+ Ok(v) => (StatusCode::OK, Json(v)).into_response(),
2882
+ Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2883
+ }
2884
+ }
2885
+
2886
+ async fn oauth_playlist_create(Json(req): Json<OauthActionReq>) -> impl IntoResponse {
2887
+ let token = req.access_token.trim().to_string();
2888
+ let title = req.title.unwrap_or_default().trim().to_string();
2889
+ if token.is_empty() || title.is_empty() {
2890
+ return json_error(StatusCode::BAD_REQUEST, "access_token and title required");
2891
+ }
2892
+ let client = reqwest::Client::builder().no_proxy().timeout(std::time::Duration::from_secs(15)).build().unwrap();
2893
+ let me = match api_deezer_get(&client, "/user/me", &token, &[]).await {
2894
+ Ok(v) => v,
2895
+ Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2896
+ };
2897
+ let uid = me.get("id").and_then(|x| x.as_i64()).unwrap_or(0);
2898
+
2899
+ let path = format!("/user/{uid}/playlists");
2900
+ match api_deezer_post(&client, &path, &token, &[("title", title)]).await {
2901
+ Ok(v) => (StatusCode::OK, Json(v)).into_response(),
2902
+ Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2903
+ }
2904
+ }
2905
+
2906
+ async fn oauth_playlist_update(Json(req): Json<OauthActionReq>) -> impl IntoResponse {
2907
+ let token = req.access_token.trim().to_string();
2908
+ let title = req.title.unwrap_or_default().trim().to_string();
2909
+ let playlist_id = req.playlist_id.unwrap_or_default().trim().to_string();
2910
+ if token.is_empty() || title.is_empty() || playlist_id.is_empty() {
2911
+ return json_error(StatusCode::BAD_REQUEST, "access_token, title, playlist_id required");
2912
+ }
2913
+ let client = reqwest::Client::builder().no_proxy().timeout(std::time::Duration::from_secs(15)).build().unwrap();
2914
+ let path = format!("/playlist/{playlist_id}");
2915
+ match api_deezer_post(&client, &path, &token, &[("title", title)]).await {
2916
+ Ok(v) => (StatusCode::OK, Json(v)).into_response(),
2917
+ Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2918
+ }
2919
+ }
2920
+
2921
+ async fn oauth_playlist_delete(Json(req): Json<OauthActionReq>) -> impl IntoResponse {
2922
+ let token = req.access_token.trim().to_string();
2923
+ let playlist_id = req.playlist_id.unwrap_or_default().trim().to_string();
2924
+ if token.is_empty() || playlist_id.is_empty() {
2925
+ return json_error(StatusCode::BAD_REQUEST, "access_token and playlist_id required");
2926
+ }
2927
+ let client = reqwest::Client::builder().no_proxy().timeout(std::time::Duration::from_secs(15)).build().unwrap();
2928
+ let path = format!("/playlist/{playlist_id}");
2929
+ match api_deezer_delete(&client, &path, &token, &[]).await {
2930
+ Ok(v) => (StatusCode::OK, Json(v)).into_response(),
2931
+ Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
2932
+ }
2933
+ }
2934
+
2935
  #[derive(Debug, Deserialize)]
2936
  struct OauthRecoReq {
2937
  access_token: String,
 
3173
  .route("/oauth/reco_artists", post(oauth_reco_artists))
3174
  .route("/oauth/reco_tracks", post(oauth_reco_tracks))
3175
  .route("/oauth/history", post(oauth_history))
3176
+ .route("/oauth/favorite_add", post(oauth_favorite_add))
3177
+ .route("/oauth/favorite_remove", post(oauth_favorite_remove))
3178
+ .route("/oauth/playlist_add", post(oauth_playlist_add))
3179
+ .route("/oauth/playlist_create", post(oauth_playlist_create))
3180
+ .route("/oauth/playlist_update", post(oauth_playlist_update))
3181
+ .route("/oauth/playlist_delete", post(oauth_playlist_delete))
3182
  .with_state(state)
3183
  .layer(cors)
3184
  .layer(CompressionLayer::new())