Spaces:
Paused
Paused
Upload 2 files
Browse files- src/main.rs +181 -116
src/main.rs
CHANGED
|
@@ -10,7 +10,7 @@ use http::{Method, header::CONTENT_TYPE};
|
|
| 10 |
use serde_json::json;
|
| 11 |
use serde::Deserialize;
|
| 12 |
use std::collections::HashMap;
|
| 13 |
-
use std::sync::
|
| 14 |
use bytes::Bytes;
|
| 15 |
use futures_util::StreamExt;
|
| 16 |
use axum::body::Body;
|
|
@@ -22,6 +22,7 @@ use reqwest::{cookie::Jar, Url};
|
|
| 22 |
use reqwest::cookie::CookieStore;
|
| 23 |
use reqwest::header::ACCEPT;
|
| 24 |
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
| 25 |
|
| 26 |
type BoxErr = Box<dyn std::error::Error + Send + Sync + 'static>;
|
| 27 |
|
|
@@ -30,7 +31,8 @@ use api::{APIClient, APIError, Format};
|
|
| 30 |
|
| 31 |
#[derive(Clone)]
|
| 32 |
struct AppState {
|
| 33 |
-
api: Arc<
|
|
|
|
| 34 |
pair: Arc<RwLock<HashMap<String, PairSession>>>,
|
| 35 |
}
|
| 36 |
|
|
@@ -38,6 +40,23 @@ fn json_error(status: StatusCode, message: impl Into<String>) -> axum::response:
|
|
| 38 |
(status, Json(json!({ "error": message.into() }))).into_response()
|
| 39 |
}
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
#[derive(Deserialize)]
|
| 42 |
struct DeezerTrackList {
|
| 43 |
data: Vec<DeezerTrack>
|
|
@@ -68,38 +87,36 @@ async fn get_url(State(state): State<AppState>, Json(req): Json<RequestParams>)
|
|
| 68 |
return (StatusCode::BAD_REQUEST, "ID list cannot be empty".to_string());
|
| 69 |
}
|
| 70 |
|
| 71 |
-
let
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Error while getting response from media.deezer.com".to_string()),
|
| 96 |
};
|
| 97 |
|
| 98 |
-
if req.arl.is_none() && client.license_token != old_license {
|
| 99 |
-
let mut client_write = state.api.write().unwrap();
|
| 100 |
-
*client_write = client;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
(StatusCode::OK, media_resp.text().await.unwrap())
|
| 104 |
}
|
| 105 |
|
|
@@ -183,47 +200,74 @@ async fn stream(State(state): State<AppState>, Query(q): Query<StreamParams>) ->
|
|
| 183 |
"AUTO" | _ => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
|
| 184 |
};
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
-
let
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
-
|
| 220 |
-
.as_str()
|
| 221 |
-
.unwrap_or("")
|
| 222 |
-
.to_string();
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
let http = reqwest::Client::builder()
|
| 229 |
.timeout(std::time::Duration::from_secs(60))
|
|
@@ -232,11 +276,18 @@ async fn stream(State(state): State<AppState>, Query(q): Query<StreamParams>) ->
|
|
| 232 |
|
| 233 |
let upstream = match http.get(url).send().await {
|
| 234 |
Ok(r) => r,
|
| 235 |
-
Err(_) =>
|
|
|
|
|
|
|
|
|
|
| 236 |
};
|
| 237 |
|
| 238 |
if !upstream.status().is_success() {
|
| 239 |
-
return (
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
}
|
| 241 |
|
| 242 |
let key = blowfish_key(q.id);
|
|
@@ -281,16 +332,19 @@ struct UserDataReq {
|
|
| 281 |
arl: String,
|
| 282 |
}
|
| 283 |
|
| 284 |
-
async fn user_data(Json(req): Json<UserDataReq>) -> impl IntoResponse {
|
| 285 |
let arl = req.arl.trim().to_string();
|
| 286 |
if arl.is_empty() {
|
| 287 |
return json_error(StatusCode::BAD_REQUEST, "ARL required");
|
| 288 |
}
|
| 289 |
|
| 290 |
-
let
|
| 291 |
-
let data =
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
| 294 |
};
|
| 295 |
|
| 296 |
let token = data["checkForm"].as_str().unwrap_or("").to_string();
|
|
@@ -306,33 +360,37 @@ struct PlaylistsReq {
|
|
| 306 |
nb: Option<u32>,
|
| 307 |
}
|
| 308 |
|
| 309 |
-
async fn playlists(Json(req): Json<PlaylistsReq>) -> impl IntoResponse {
|
| 310 |
let arl = req.arl.trim().to_string();
|
| 311 |
if arl.is_empty() {
|
| 312 |
return json_error(StatusCode::BAD_REQUEST, "ARL required");
|
| 313 |
}
|
| 314 |
|
| 315 |
-
let
|
| 316 |
-
let
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
|
|
|
|
|
|
| 320 |
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
|
| 326 |
-
|
| 327 |
-
|
| 328 |
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
|
|
|
|
|
|
| 332 |
|
| 333 |
match res {
|
| 334 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 335 |
-
Err(e) => json_error(StatusCode::
|
| 336 |
}
|
| 337 |
}
|
| 338 |
|
|
@@ -344,32 +402,38 @@ struct PlaylistTracksReq {
|
|
| 344 |
nb: Option<u32>,
|
| 345 |
}
|
| 346 |
|
| 347 |
-
async fn playlist_tracks(
|
|
|
|
|
|
|
|
|
|
| 348 |
let arl = req.arl.trim().to_string();
|
| 349 |
if arl.is_empty() {
|
| 350 |
return json_error(StatusCode::BAD_REQUEST, "ARL required");
|
| 351 |
}
|
| 352 |
|
| 353 |
-
let
|
| 354 |
-
let _ = client.user_data().await;
|
| 355 |
|
| 356 |
let start = req.start.unwrap_or(0);
|
| 357 |
let nb = req.nb.unwrap_or(100);
|
| 358 |
|
| 359 |
-
let res: Result<serde_json::Value, APIError> =
|
| 360 |
-
.
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
"
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
|
| 370 |
match res {
|
| 371 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 372 |
-
Err(e) => json_error(StatusCode::
|
| 373 |
}
|
| 374 |
}
|
| 375 |
|
|
@@ -660,7 +724,7 @@ async fn pair_start(State(state): State<AppState>) -> impl IntoResponse {
|
|
| 660 |
.map(|d| d.as_millis())
|
| 661 |
.unwrap_or(0);
|
| 662 |
{
|
| 663 |
-
let mut map = state.pair.write().
|
| 664 |
map.insert(
|
| 665 |
code.clone(),
|
| 666 |
PairSession {
|
|
@@ -682,7 +746,7 @@ async fn pair_status(State(state): State<AppState>, Query(q): Query<PairStatusQu
|
|
| 682 |
return json_error(StatusCode::BAD_REQUEST, "code required");
|
| 683 |
}
|
| 684 |
|
| 685 |
-
let map = state.pair.read().
|
| 686 |
let Some(sess) = map.get(&code) else {
|
| 687 |
return json_error(StatusCode::NOT_FOUND, "no such code");
|
| 688 |
};
|
|
@@ -794,7 +858,7 @@ async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairComplete
|
|
| 794 |
}
|
| 795 |
|
| 796 |
{
|
| 797 |
-
let map = state.pair.read().
|
| 798 |
if !map.contains_key(&code) {
|
| 799 |
return Html("<h3>Код не найден или истёк</h3>".to_string()).into_response();
|
| 800 |
}
|
|
@@ -813,7 +877,7 @@ async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairComplete
|
|
| 813 |
let access_token = match oauth_access_token(&client, &email, &pass_md5).await {
|
| 814 |
Ok(t) => t,
|
| 815 |
Err(e) => {
|
| 816 |
-
let mut map = state.pair.write().
|
| 817 |
if let Some(s) = map.get_mut(&code) {
|
| 818 |
s.error = Some(e.clone());
|
| 819 |
}
|
|
@@ -830,7 +894,7 @@ async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairComplete
|
|
| 830 |
let me = match api_deezer_get(&client, "/user/me", &access_token, &[]).await {
|
| 831 |
Ok(v) => v,
|
| 832 |
Err(e) => {
|
| 833 |
-
let mut map = state.pair.write().
|
| 834 |
if let Some(s) = map.get_mut(&code) {
|
| 835 |
s.error = Some(e.clone());
|
| 836 |
}
|
|
@@ -876,7 +940,7 @@ async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairComplete
|
|
| 876 |
}
|
| 877 |
|
| 878 |
{
|
| 879 |
-
let mut map = state.pair.write().
|
| 880 |
if let Some(s) = map.get_mut(&code) {
|
| 881 |
s.access_token = Some(access_token);
|
| 882 |
s.user_id = uid;
|
|
@@ -905,7 +969,7 @@ async fn oauth_me(Json(req): Json<OauthMeReq>) -> impl IntoResponse {
|
|
| 905 |
.unwrap();
|
| 906 |
match api_deezer_get(&client, "/user/me", &token, &[]).await {
|
| 907 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 908 |
-
Err(e) => json_error(StatusCode::
|
| 909 |
}
|
| 910 |
}
|
| 911 |
|
|
@@ -929,17 +993,17 @@ async fn oauth_playlists(Json(req): Json<OauthPlaylistsReq>) -> impl IntoRespons
|
|
| 929 |
.unwrap();
|
| 930 |
let me = match api_deezer_get(&client, "/user/me", &token, &[]).await {
|
| 931 |
Ok(v) => v,
|
| 932 |
-
Err(e) => return json_error(StatusCode::
|
| 933 |
};
|
| 934 |
let uid = me.get("id").and_then(|x| x.as_i64()).unwrap_or(0);
|
| 935 |
if uid <= 0 {
|
| 936 |
-
return json_error(StatusCode::
|
| 937 |
}
|
| 938 |
let path = format!("/user/{uid}/playlists");
|
| 939 |
let extra = [("index", index), ("limit", limit)];
|
| 940 |
match api_deezer_get(&client, &path, &token, &extra).await {
|
| 941 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 942 |
-
Err(e) => json_error(StatusCode::
|
| 943 |
}
|
| 944 |
}
|
| 945 |
|
|
@@ -969,7 +1033,7 @@ async fn oauth_playlist_tracks(Json(req): Json<OauthPlaylistTracksReq>) -> impl
|
|
| 969 |
let extra = [("index", index), ("limit", limit)];
|
| 970 |
match api_deezer_get(&client, &path, &token, &extra).await {
|
| 971 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 972 |
-
Err(e) => json_error(StatusCode::
|
| 973 |
}
|
| 974 |
}
|
| 975 |
|
|
@@ -1001,21 +1065,21 @@ async fn oauth_arl(Json(req): Json<OauthArlReq>) -> impl IntoResponse {
|
|
| 1001 |
|
| 1002 |
let ud = match gw_light_call(&client, "deezer.getUserData", "null", json!({})).await {
|
| 1003 |
Ok(v) => v,
|
| 1004 |
-
Err(e) => return json_error(StatusCode::
|
| 1005 |
};
|
| 1006 |
|
| 1007 |
let check_form = ud["checkForm"].as_str().unwrap_or("").to_string();
|
| 1008 |
if check_form.is_empty() {
|
| 1009 |
-
return json_error(StatusCode::
|
| 1010 |
}
|
| 1011 |
|
| 1012 |
let arl_v = match gw_light_call(&client, "user.getArl", &check_form, json!({})).await {
|
| 1013 |
Ok(v) => v,
|
| 1014 |
-
Err(e) => return json_error(StatusCode::
|
| 1015 |
};
|
| 1016 |
let arl = arl_v.as_str().unwrap_or("").to_string();
|
| 1017 |
if arl.len() < 20 {
|
| 1018 |
-
return json_error(StatusCode::
|
| 1019 |
}
|
| 1020 |
|
| 1021 |
(StatusCode::OK, Json(json!({ "arl": arl }))).into_response()
|
|
@@ -1028,7 +1092,8 @@ async fn main() {
|
|
| 1028 |
let port: u16 = port.parse().unwrap_or(8000);
|
| 1029 |
|
| 1030 |
let state = AppState {
|
| 1031 |
-
api: Arc::new(
|
|
|
|
| 1032 |
pair: Arc::new(RwLock::new(HashMap::new())),
|
| 1033 |
};
|
| 1034 |
|
|
|
|
| 10 |
use serde_json::json;
|
| 11 |
use serde::Deserialize;
|
| 12 |
use std::collections::HashMap;
|
| 13 |
+
use std::sync::Arc;
|
| 14 |
use bytes::Bytes;
|
| 15 |
use futures_util::StreamExt;
|
| 16 |
use axum::body::Body;
|
|
|
|
| 22 |
use reqwest::cookie::CookieStore;
|
| 23 |
use reqwest::header::ACCEPT;
|
| 24 |
use std::time::{SystemTime, UNIX_EPOCH};
|
| 25 |
+
use tokio::sync::{Mutex, RwLock};
|
| 26 |
|
| 27 |
type BoxErr = Box<dyn std::error::Error + Send + Sync + 'static>;
|
| 28 |
|
|
|
|
| 31 |
|
| 32 |
#[derive(Clone)]
|
| 33 |
struct AppState {
|
| 34 |
+
api: Arc<Mutex<APIClient>>,
|
| 35 |
+
api_by_arl: Arc<RwLock<HashMap<String, Arc<Mutex<APIClient>>>>>,
|
| 36 |
pair: Arc<RwLock<HashMap<String, PairSession>>>,
|
| 37 |
}
|
| 38 |
|
|
|
|
| 40 |
(status, Json(json!({ "error": message.into() }))).into_response()
|
| 41 |
}
|
| 42 |
|
| 43 |
+
async fn api_client_for_arl(state: &AppState, arl: Option<String>) -> Arc<Mutex<APIClient>> {
|
| 44 |
+
let Some(arl) = arl.map(|s| s.trim().to_string()).filter(|s| !s.is_empty()) else {
|
| 45 |
+
return state.api.clone();
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
let mut map = state.api_by_arl.write().await;
|
| 49 |
+
if let Some(c) = map.get(&arl) {
|
| 50 |
+
return c.clone();
|
| 51 |
+
}
|
| 52 |
+
if map.len() >= 32 {
|
| 53 |
+
map.clear();
|
| 54 |
+
}
|
| 55 |
+
let c = Arc::new(Mutex::new(APIClient::new_with_arl(arl.clone())));
|
| 56 |
+
map.insert(arl, c.clone());
|
| 57 |
+
c
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
#[derive(Deserialize)]
|
| 61 |
struct DeezerTrackList {
|
| 62 |
data: Vec<DeezerTrack>
|
|
|
|
| 87 |
return (StatusCode::BAD_REQUEST, "ID list cannot be empty".to_string());
|
| 88 |
}
|
| 89 |
|
| 90 |
+
let client = api_client_for_arl(&state, req.arl.clone()).await;
|
| 91 |
+
let media_resp = {
|
| 92 |
+
let mut client = client.lock().await;
|
| 93 |
+
let resp: Result<DeezerTrackList, APIError> = client
|
| 94 |
+
.api_call(
|
| 95 |
+
"song.getListData",
|
| 96 |
+
&json!({"sng_ids":req.ids,"array_default":["SNG_ID","TRACK_TOKEN"]}),
|
| 97 |
+
)
|
| 98 |
+
.await;
|
| 99 |
+
let track_list = match resp {
|
| 100 |
+
Ok(t) => t,
|
| 101 |
+
Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()),
|
| 102 |
+
};
|
| 103 |
|
| 104 |
+
if track_list.data.is_empty() {
|
| 105 |
+
return (StatusCode::BAD_REQUEST, "No valid IDs found".to_string());
|
| 106 |
+
}
|
| 107 |
|
| 108 |
+
let track_tokens: Vec<&str> = track_list
|
| 109 |
+
.data
|
| 110 |
+
.iter()
|
| 111 |
+
.map(|t| t.TRACK_TOKEN.as_str())
|
| 112 |
+
.collect();
|
| 113 |
|
| 114 |
+
match client.get_media(&req.formats, track_tokens).await {
|
| 115 |
+
Ok(r) => r,
|
| 116 |
+
Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()),
|
| 117 |
+
}
|
|
|
|
| 118 |
};
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
(StatusCode::OK, media_resp.text().await.unwrap())
|
| 121 |
}
|
| 122 |
|
|
|
|
| 200 |
"AUTO" | _ => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
|
| 201 |
};
|
| 202 |
|
| 203 |
+
fn extract_media_url(media_json: &serde_json::Value) -> String {
|
| 204 |
+
let data = media_json.get("data").and_then(|v| v.as_array());
|
| 205 |
+
let Some(data0) = data.and_then(|a| a.get(0)) else {
|
| 206 |
+
return String::new();
|
| 207 |
+
};
|
| 208 |
+
let media_arr = data0.get("media").and_then(|v| v.as_array());
|
| 209 |
+
let Some(media_arr) = media_arr else {
|
| 210 |
+
return String::new();
|
| 211 |
+
};
|
| 212 |
+
for m in media_arr {
|
| 213 |
+
if let Some(sources) = m.get("sources").and_then(|v| v.as_array()) {
|
| 214 |
+
for s in sources {
|
| 215 |
+
if let Some(url) = s.get("url").and_then(|v| v.as_str()) {
|
| 216 |
+
let url = url.trim();
|
| 217 |
+
if !url.is_empty() {
|
| 218 |
+
return url.to_string();
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
String::new()
|
| 225 |
+
}
|
| 226 |
|
| 227 |
+
let client = api_client_for_arl(&state, q.arl.clone()).await;
|
| 228 |
+
let url = {
|
| 229 |
+
let mut client = client.lock().await;
|
| 230 |
+
let resp: Result<DeezerTrackList, APIError> = client
|
| 231 |
+
.api_call(
|
| 232 |
+
"song.getListData",
|
| 233 |
+
&json!({"sng_ids":[q.id],"array_default":["SNG_ID","TRACK_TOKEN"]}),
|
| 234 |
+
)
|
| 235 |
+
.await;
|
| 236 |
|
| 237 |
+
let track_list = match resp {
|
| 238 |
+
Ok(t) => t,
|
| 239 |
+
Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response(),
|
| 240 |
+
};
|
| 241 |
|
| 242 |
+
if track_list.data.is_empty() {
|
| 243 |
+
return (StatusCode::BAD_REQUEST, "No valid ID".to_string()).into_response();
|
| 244 |
+
}
|
| 245 |
|
| 246 |
+
let track_token = track_list.data[0].TRACK_TOKEN.as_str();
|
| 247 |
+
let media_resp = match client.get_media(&formats, vec![track_token]).await {
|
| 248 |
+
Ok(r) => r,
|
| 249 |
+
Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response(),
|
| 250 |
+
};
|
| 251 |
|
| 252 |
+
let media_json: serde_json::Value = match media_resp.json().await {
|
| 253 |
+
Ok(v) => v,
|
| 254 |
+
Err(_) => {
|
| 255 |
+
return (StatusCode::SERVICE_UNAVAILABLE, "Bad media response".to_string())
|
| 256 |
+
.into_response()
|
| 257 |
+
}
|
| 258 |
+
};
|
| 259 |
|
| 260 |
+
let url = extract_media_url(&media_json);
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
+
if url.is_empty() {
|
| 263 |
+
return (
|
| 264 |
+
StatusCode::SERVICE_UNAVAILABLE,
|
| 265 |
+
"No url in media response".to_string(),
|
| 266 |
+
)
|
| 267 |
+
.into_response();
|
| 268 |
+
}
|
| 269 |
+
url
|
| 270 |
+
};
|
| 271 |
|
| 272 |
let http = reqwest::Client::builder()
|
| 273 |
.timeout(std::time::Duration::from_secs(60))
|
|
|
|
| 276 |
|
| 277 |
let upstream = match http.get(url).send().await {
|
| 278 |
Ok(r) => r,
|
| 279 |
+
Err(_) => {
|
| 280 |
+
return (StatusCode::SERVICE_UNAVAILABLE, "Upstream request failed".to_string())
|
| 281 |
+
.into_response()
|
| 282 |
+
}
|
| 283 |
};
|
| 284 |
|
| 285 |
if !upstream.status().is_success() {
|
| 286 |
+
return (
|
| 287 |
+
StatusCode::SERVICE_UNAVAILABLE,
|
| 288 |
+
"Upstream status not OK".to_string(),
|
| 289 |
+
)
|
| 290 |
+
.into_response();
|
| 291 |
}
|
| 292 |
|
| 293 |
let key = blowfish_key(q.id);
|
|
|
|
| 332 |
arl: String,
|
| 333 |
}
|
| 334 |
|
| 335 |
+
async fn user_data(State(state): State<AppState>, Json(req): Json<UserDataReq>) -> impl IntoResponse {
|
| 336 |
let arl = req.arl.trim().to_string();
|
| 337 |
if arl.is_empty() {
|
| 338 |
return json_error(StatusCode::BAD_REQUEST, "ARL required");
|
| 339 |
}
|
| 340 |
|
| 341 |
+
let client = api_client_for_arl(&state, Some(arl)).await;
|
| 342 |
+
let data = {
|
| 343 |
+
let mut client = client.lock().await;
|
| 344 |
+
match client.user_data().await {
|
| 345 |
+
Ok(v) => v,
|
| 346 |
+
Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e.to_string()),
|
| 347 |
+
}
|
| 348 |
};
|
| 349 |
|
| 350 |
let token = data["checkForm"].as_str().unwrap_or("").to_string();
|
|
|
|
| 360 |
nb: Option<u32>,
|
| 361 |
}
|
| 362 |
|
| 363 |
+
async fn playlists(State(state): State<AppState>, Json(req): Json<PlaylistsReq>) -> impl IntoResponse {
|
| 364 |
let arl = req.arl.trim().to_string();
|
| 365 |
if arl.is_empty() {
|
| 366 |
return json_error(StatusCode::BAD_REQUEST, "ARL required");
|
| 367 |
}
|
| 368 |
|
| 369 |
+
let client = api_client_for_arl(&state, Some(arl)).await;
|
| 370 |
+
let res = {
|
| 371 |
+
let mut client = client.lock().await;
|
| 372 |
+
let ud = match client.user_data().await {
|
| 373 |
+
Ok(v) => v,
|
| 374 |
+
Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e.to_string()),
|
| 375 |
+
};
|
| 376 |
|
| 377 |
+
let uid = ud["USER"]["USER_ID"].as_i64().unwrap_or(0);
|
| 378 |
+
if uid <= 0 {
|
| 379 |
+
return json_error(StatusCode::SERVICE_UNAVAILABLE, "No USER_ID");
|
| 380 |
+
}
|
| 381 |
|
| 382 |
+
let start = req.start.unwrap_or(0);
|
| 383 |
+
let nb = req.nb.unwrap_or(50);
|
| 384 |
|
| 385 |
+
let res: Result<serde_json::Value, APIError> = client
|
| 386 |
+
.api_call("playlist.getList", &json!({"user_id":uid,"nb":nb,"start":start}))
|
| 387 |
+
.await;
|
| 388 |
+
res
|
| 389 |
+
};
|
| 390 |
|
| 391 |
match res {
|
| 392 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 393 |
+
Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e.to_string()),
|
| 394 |
}
|
| 395 |
}
|
| 396 |
|
|
|
|
| 402 |
nb: Option<u32>,
|
| 403 |
}
|
| 404 |
|
| 405 |
+
async fn playlist_tracks(
|
| 406 |
+
State(state): State<AppState>,
|
| 407 |
+
Json(req): Json<PlaylistTracksReq>,
|
| 408 |
+
) -> impl IntoResponse {
|
| 409 |
let arl = req.arl.trim().to_string();
|
| 410 |
if arl.is_empty() {
|
| 411 |
return json_error(StatusCode::BAD_REQUEST, "ARL required");
|
| 412 |
}
|
| 413 |
|
| 414 |
+
let client = api_client_for_arl(&state, Some(arl)).await;
|
|
|
|
| 415 |
|
| 416 |
let start = req.start.unwrap_or(0);
|
| 417 |
let nb = req.nb.unwrap_or(100);
|
| 418 |
|
| 419 |
+
let res: Result<serde_json::Value, APIError> = {
|
| 420 |
+
let mut client = client.lock().await;
|
| 421 |
+
let _ = client.user_data().await;
|
| 422 |
+
client
|
| 423 |
+
.api_call(
|
| 424 |
+
"playlist.getSongs",
|
| 425 |
+
&json!({
|
| 426 |
+
"playlist_id": req.playlist_id,
|
| 427 |
+
"start": start,
|
| 428 |
+
"nb": nb
|
| 429 |
+
}),
|
| 430 |
+
)
|
| 431 |
+
.await
|
| 432 |
+
};
|
| 433 |
|
| 434 |
match res {
|
| 435 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 436 |
+
Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e.to_string()),
|
| 437 |
}
|
| 438 |
}
|
| 439 |
|
|
|
|
| 724 |
.map(|d| d.as_millis())
|
| 725 |
.unwrap_or(0);
|
| 726 |
{
|
| 727 |
+
let mut map = state.pair.write().await;
|
| 728 |
map.insert(
|
| 729 |
code.clone(),
|
| 730 |
PairSession {
|
|
|
|
| 746 |
return json_error(StatusCode::BAD_REQUEST, "code required");
|
| 747 |
}
|
| 748 |
|
| 749 |
+
let map = state.pair.read().await;
|
| 750 |
let Some(sess) = map.get(&code) else {
|
| 751 |
return json_error(StatusCode::NOT_FOUND, "no such code");
|
| 752 |
};
|
|
|
|
| 858 |
}
|
| 859 |
|
| 860 |
{
|
| 861 |
+
let map = state.pair.read().await;
|
| 862 |
if !map.contains_key(&code) {
|
| 863 |
return Html("<h3>Код не найден или истёк</h3>".to_string()).into_response();
|
| 864 |
}
|
|
|
|
| 877 |
let access_token = match oauth_access_token(&client, &email, &pass_md5).await {
|
| 878 |
Ok(t) => t,
|
| 879 |
Err(e) => {
|
| 880 |
+
let mut map = state.pair.write().await;
|
| 881 |
if let Some(s) = map.get_mut(&code) {
|
| 882 |
s.error = Some(e.clone());
|
| 883 |
}
|
|
|
|
| 894 |
let me = match api_deezer_get(&client, "/user/me", &access_token, &[]).await {
|
| 895 |
Ok(v) => v,
|
| 896 |
Err(e) => {
|
| 897 |
+
let mut map = state.pair.write().await;
|
| 898 |
if let Some(s) = map.get_mut(&code) {
|
| 899 |
s.error = Some(e.clone());
|
| 900 |
}
|
|
|
|
| 940 |
}
|
| 941 |
|
| 942 |
{
|
| 943 |
+
let mut map = state.pair.write().await;
|
| 944 |
if let Some(s) = map.get_mut(&code) {
|
| 945 |
s.access_token = Some(access_token);
|
| 946 |
s.user_id = uid;
|
|
|
|
| 969 |
.unwrap();
|
| 970 |
match api_deezer_get(&client, "/user/me", &token, &[]).await {
|
| 971 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 972 |
+
Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
|
| 973 |
}
|
| 974 |
}
|
| 975 |
|
|
|
|
| 993 |
.unwrap();
|
| 994 |
let me = match api_deezer_get(&client, "/user/me", &token, &[]).await {
|
| 995 |
Ok(v) => v,
|
| 996 |
+
Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e),
|
| 997 |
};
|
| 998 |
let uid = me.get("id").and_then(|x| x.as_i64()).unwrap_or(0);
|
| 999 |
if uid <= 0 {
|
| 1000 |
+
return json_error(StatusCode::SERVICE_UNAVAILABLE, "api:no_user_id");
|
| 1001 |
}
|
| 1002 |
let path = format!("/user/{uid}/playlists");
|
| 1003 |
let extra = [("index", index), ("limit", limit)];
|
| 1004 |
match api_deezer_get(&client, &path, &token, &extra).await {
|
| 1005 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 1006 |
+
Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
|
| 1007 |
}
|
| 1008 |
}
|
| 1009 |
|
|
|
|
| 1033 |
let extra = [("index", index), ("limit", limit)];
|
| 1034 |
match api_deezer_get(&client, &path, &token, &extra).await {
|
| 1035 |
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
|
| 1036 |
+
Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
|
| 1037 |
}
|
| 1038 |
}
|
| 1039 |
|
|
|
|
| 1065 |
|
| 1066 |
let ud = match gw_light_call(&client, "deezer.getUserData", "null", json!({})).await {
|
| 1067 |
Ok(v) => v,
|
| 1068 |
+
Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e),
|
| 1069 |
};
|
| 1070 |
|
| 1071 |
let check_form = ud["checkForm"].as_str().unwrap_or("").to_string();
|
| 1072 |
if check_form.is_empty() {
|
| 1073 |
+
return json_error(StatusCode::SERVICE_UNAVAILABLE, "no checkForm");
|
| 1074 |
}
|
| 1075 |
|
| 1076 |
let arl_v = match gw_light_call(&client, "user.getArl", &check_form, json!({})).await {
|
| 1077 |
Ok(v) => v,
|
| 1078 |
+
Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e),
|
| 1079 |
};
|
| 1080 |
let arl = arl_v.as_str().unwrap_or("").to_string();
|
| 1081 |
if arl.len() < 20 {
|
| 1082 |
+
return json_error(StatusCode::SERVICE_UNAVAILABLE, "no arl");
|
| 1083 |
}
|
| 1084 |
|
| 1085 |
(StatusCode::OK, Json(json!({ "arl": arl }))).into_response()
|
|
|
|
| 1092 |
let port: u16 = port.parse().unwrap_or(8000);
|
| 1093 |
|
| 1094 |
let state = AppState {
|
| 1095 |
+
api: Arc::new(Mutex::new(APIClient::new())),
|
| 1096 |
+
api_by_arl: Arc::new(RwLock::new(HashMap::new())),
|
| 1097 |
pair: Arc::new(RwLock::new(HashMap::new())),
|
| 1098 |
};
|
| 1099 |
|