recycleactor commited on
Commit
e314da3
·
verified ·
1 Parent(s): 4039d14

Upload main.rs

Browse files
Files changed (1) hide show
  1. src/main.rs +55 -8
src/main.rs CHANGED
@@ -1212,6 +1212,21 @@ fn forwarded_proto(headers: &HeaderMap) -> String {
1212
  .to_string()
1213
  }
1214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1215
  #[derive(Deserialize)]
1216
  struct PairStatusQuery {
1217
  code: String,
@@ -1337,12 +1352,17 @@ async fn pair_page(Host(host): Host, headers: HeaderMap, Query(q): Query<PairPag
1337
  let code = q.code.unwrap_or_default().trim().to_string();
1338
  let safe_code = htmlesc(&code);
1339
  let scheme = forwarded_proto(&headers);
1340
- let channel_url = format!("{scheme}://{host}/pair/channel");
1341
  let app_id = deezer_app_id();
 
 
 
 
 
 
 
1342
 
1343
  let code_js = serde_json::to_string(&code).unwrap_or_else(|_| "\"\"".to_string());
1344
- let channel_url_js = serde_json::to_string(&channel_url).unwrap_or_else(|_| "\"\"".to_string());
1345
- let app_id_js = serde_json::to_string(&app_id).unwrap_or_else(|_| "\"\"".to_string());
1346
 
1347
  let mut html = String::with_capacity(6000);
1348
  html.push_str(r#"<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">"#);
@@ -1360,16 +1380,42 @@ async fn pair_page(Host(host): Host, headers: HeaderMap, Query(q): Query<PairPag
1360
  html.push_str(r#"<script>(function(){"#);
1361
  html.push_str("var code=");
1362
  html.push_str(&code_js);
1363
- html.push_str(r#";var msg=document.getElementById('msg');function setMsg(cls,text){msg.className=cls?(cls+' muted'):'muted';msg.textContent=text;}if(!code){setMsg('err','Нет кода пары. Открой ссылку заново с QR.');return;}function postToken(at,uid){return fetch('/pair/oauth',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:code,access_token:at,user_id:String(uid||'')})}).then(function(r){return r.json().catch(function(){return {};}).then(function(j){return {ok:r.ok,json:j};});});}function start(){function initDZ(){try{if(!window.DZ||!DZ.init){return false;}DZ.init({appId:"#);
1364
- html.push_str(&app_id_js);
1365
- html.push_str(r#",channelUrl:"#);
1366
- html.push_str(&channel_url_js);
1367
- html.push_str(r#"});return true;}catch(e){return false;}}if(!initDZ()){setMsg('err','Не удалось инициализировать Deezer SDK.');return;}document.getElementById('dz-login').addEventListener('click',function(){setMsg('','Ожидаю Deezer…');DZ.login(function(resp){if(resp&&resp.authResponse&&resp.authResponse.accessToken){setMsg('','Сохраняю токен…');postToken(resp.authResponse.accessToken,resp.authResponse.userID).then(function(r){if(r.ok&&!r.json.error){setMsg('ok','Готово ✓ Вернись на телевизор.');}else{setMsg('err','Ошибка сохранения: '+(r.json.error||'error'));}}).catch(function(){setMsg('err','Ошибка сети при сохранении');});}else{setMsg('err','Вход отменён или не удалось получить токен');}},{perms:'basic_access,email,manage_library,listening_history'});});}function loadSdk(){var s=document.createElement('script');s.src='https://cdns-files.deezer.com/js/min/dz.js';s.onload=function(){start();};s.onerror=function(){setMsg('err','Не удалось загрузить Deezer SDK (dz.js). Проверь сеть/блокировщик.');};document.head.appendChild(s);}loadSdk();})();</script>"#);
1368
  html.push_str(r#"<p class="muted" style="margin:14px 0 0">Если Deezer пишет про неверный redirect/domain — нужно указать свой <b>DEEZER_APP_ID</b> для домена этого сервера.</p>"#);
1369
  html.push_str(r#"</div></body></html>"#);
1370
  Html(html)
1371
  }
1372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1373
  #[derive(Deserialize)]
1374
  struct PairCompleteForm {
1375
  code: String,
@@ -1957,6 +2003,7 @@ async fn main() {
1957
  .route("/pair", get(pair_page))
1958
  .route("/pair/channel", get(pair_channel))
1959
  .route("/pair/oauth", post(pair_oauth))
 
1960
  .route("/pair/complete", post(pair_complete))
1961
  .route("/oauth/me", post(oauth_me))
1962
  .route("/oauth/playlists", post(oauth_playlists))
 
1212
  .to_string()
1213
  }
1214
 
1215
+ fn urlenc(s: &str) -> String {
1216
+ let mut out = String::with_capacity(s.len() + 16);
1217
+ for &b in s.as_bytes() {
1218
+ let c = b as char;
1219
+ let ok = matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~');
1220
+ if ok {
1221
+ out.push(c);
1222
+ } else {
1223
+ out.push('%');
1224
+ out.push_str(&format!("{:02X}", b));
1225
+ }
1226
+ }
1227
+ out
1228
+ }
1229
+
1230
  #[derive(Deserialize)]
1231
  struct PairStatusQuery {
1232
  code: String,
 
1352
  let code = q.code.unwrap_or_default().trim().to_string();
1353
  let safe_code = htmlesc(&code);
1354
  let scheme = forwarded_proto(&headers);
 
1355
  let app_id = deezer_app_id();
1356
+ let redirect_url = format!("{scheme}://{host}/pair/oauth_cb?code={}", urlenc(&code));
1357
+ let oauth_url = format!(
1358
+ "https://connect.deezer.com/oauth/auth.php?app_id={}&redirect_uri={}&perms={}&response_type=token",
1359
+ urlenc(&app_id),
1360
+ urlenc(&redirect_url),
1361
+ urlenc("basic_access,email,manage_library,listening_history")
1362
+ );
1363
 
1364
  let code_js = serde_json::to_string(&code).unwrap_or_else(|_| "\"\"".to_string());
1365
+ let oauth_url_js = serde_json::to_string(&oauth_url).unwrap_or_else(|_| "\"\"".to_string());
 
1366
 
1367
  let mut html = String::with_capacity(6000);
1368
  html.push_str(r#"<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">"#);
 
1380
  html.push_str(r#"<script>(function(){"#);
1381
  html.push_str("var code=");
1382
  html.push_str(&code_js);
1383
+ html.push_str(r#";var oauthUrl="#);
1384
+ html.push_str(&oauth_url_js);
1385
+ html.push_str(r#";var msg=document.getElementById('msg');function setMsg(cls,text){msg.className=cls?(cls+' muted'):'muted';msg.textContent=text;}if(!code){setMsg('err','Нет кода пары. Открой ссылку заново с QR.');return;}document.getElementById('dz-login').addEventListener('click',function(){setMsg('','Открываю Deezer…');if(!oauthUrl){setMsg('err','Не удалось собрать Deezer OAuth URL');return;}window.location.href=oauthUrl;});})();</script>"#);
 
 
1386
  html.push_str(r#"<p class="muted" style="margin:14px 0 0">Если Deezer пишет про неверный redirect/domain — нужно указать свой <b>DEEZER_APP_ID</b> для домена этого сервера.</p>"#);
1387
  html.push_str(r#"</div></body></html>"#);
1388
  Html(html)
1389
  }
1390
 
1391
+ #[derive(Deserialize)]
1392
+ struct PairOauthCbQuery {
1393
+ code: Option<String>,
1394
+ }
1395
+
1396
+ async fn pair_oauth_cb(Query(q): Query<PairOauthCbQuery>) -> impl IntoResponse {
1397
+ let code = q.code.unwrap_or_default().trim().to_string();
1398
+ let safe_code = htmlesc(&code);
1399
+ let code_js = serde_json::to_string(&code).unwrap_or_else(|_| "\"\"".to_string());
1400
+
1401
+ let mut html = String::with_capacity(3500);
1402
+ html.push_str(r#"<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">"#);
1403
+ html.push_str(r#"<title>Deezer Login</title>"#);
1404
+ html.push_str(r#"<style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;background:#0b0b0b;color:#fff;margin:0;padding:20px}.box{max-width:420px;margin:0 auto;background:#151515;padding:18px;border-radius:12px}.muted{color:#aaa;font-size:13px;line-height:1.35}.ok{color:#1db954}.err{color:#ff5a5a}</style>"#);
1405
+ html.push_str(r#"</head><body><div class="box">"#);
1406
+ html.push_str(r#"<h2 style="margin:0 0 6px">Deezer: вход для ТВ</h2>"#);
1407
+ html.push_str(r#"<div class="muted">Код пары: <b>"#);
1408
+ html.push_str(&safe_code);
1409
+ html.push_str(r#"</b></div>"#);
1410
+ html.push_str(r#"<div id="msg" class="muted" style="margin:12px 0 0">Получаю токен…</div>"#);
1411
+ html.push_str(r#"<script>(function(){"#);
1412
+ html.push_str("var code=");
1413
+ html.push_str(&code_js);
1414
+ html.push_str(r#";var msg=document.getElementById('msg');function setMsg(cls,text){msg.className=cls?(cls+' muted'):'muted';msg.textContent=text;}function parseHash(){var h=window.location.hash||'';if(h.charAt(0)==='#')h=h.slice(1);var out={};h.split('&').forEach(function(p){if(!p)return;var i=p.indexOf('=');var k=i>=0?p.slice(0,i):p;var v=i>=0?p.slice(i+1):'';try{k=decodeURIComponent(k);}catch(e){}try{v=decodeURIComponent(v);}catch(e){}out[k]=v;});return out;}if(!code){setMsg('err','Нет кода пары. Вернись на телевизор и обнови QR/ссылку.');return;}var h=parseHash();var at=h.access_token||'';if(!at){setMsg('err','Deezer не вернул access_token. Попробуй ещё раз.');return;}try{history.replaceState(null,'',window.location.pathname+window.location.search);}catch(e){}fetch('/pair/oauth',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:code,access_token:at})}).then(function(r){return r.json().catch(function(){return {};}).then(function(j){return {ok:r.ok,json:j};});}).then(function(r){if(r.ok&&!r.json.error){setMsg('ok','Готово ✓ Вернись на телевизор.');}else{setMsg('err','Ошибка сохранения: '+(r.json.error||'error'));}}).catch(function(){setMsg('err','Ошибка сети при сохранении');});})();</script>"#);
1415
+ html.push_str(r#"</div></body></html>"#);
1416
+ (StatusCode::OK, Html(html)).into_response()
1417
+ }
1418
+
1419
  #[derive(Deserialize)]
1420
  struct PairCompleteForm {
1421
  code: String,
 
2003
  .route("/pair", get(pair_page))
2004
  .route("/pair/channel", get(pair_channel))
2005
  .route("/pair/oauth", post(pair_oauth))
2006
+ .route("/pair/oauth_cb", get(pair_oauth_cb))
2007
  .route("/pair/complete", post(pair_complete))
2008
  .route("/oauth/me", post(oauth_me))
2009
  .route("/oauth/playlists", post(oauth_playlists))