recycleactor commited on
Commit
3c7fc10
·
verified ·
1 Parent(s): e314da3

Upload main.rs

Browse files
Files changed (1) hide show
  1. src/main.rs +27 -9
src/main.rs CHANGED
@@ -1353,21 +1353,23 @@ async fn pair_page(Host(host): Host, headers: HeaderMap, Query(q): Query<PairPag
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">"#);
1369
  html.push_str(r#"<title>Deezer Login</title>"#);
1370
- 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}button{width:100%;padding:12px 14px;border-radius:10px;border:0;background:#a238ff;color:#fff;font-weight:800;font-size:16px}.muted{color:#aaa;font-size:13px;line-height:1.35}.ok{color:#1db954}.err{color:#ff5a5a}a{color:#a238ff}</style>"#);
1371
  html.push_str(r#"</head><body><div class="box">"#);
1372
  html.push_str(r#"<h2 style="margin:0 0 6px">Deezer: вход для ТВ</h2>"#);
1373
  html.push_str(r#"<div class="muted">Код пары: <b>"#);
@@ -1376,13 +1378,23 @@ async fn pair_page(Host(host): Host, headers: HeaderMap, Query(q): Query<PairPag
1376
  html.push_str(r#"<div class="muted" style="margin:10px 0 14px">Нажми кнопку, войди в Deezer и подтверди доступ. После успеха вернись на телевизор.</div>"#);
1377
  html.push_str(r#"<button id="dz-login" type="button">Войти через Deezer</button>"#);
1378
  html.push_str(r#"<div id="msg" class="muted" style="margin:12px 0 0"></div>"#);
1379
- html.push_str(r#"<div id="dz-root"></div>"#);
 
 
 
 
 
 
 
 
 
 
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)
@@ -1391,10 +1403,16 @@ async fn pair_page(Host(host): Host, headers: HeaderMap, Query(q): Query<PairPag
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
 
@@ -1409,9 +1427,9 @@ async fn pair_oauth_cb(Query(q): Query<PairOauthCbQuery>) -> impl IntoResponse {
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
  }
 
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");
1357
  let oauth_url = format!(
1358
+ "https://connect.deezer.com/oauth/auth.php?app_id={}&redirect_uri={}&perms={}&response_type=token&state={}",
1359
  urlenc(&app_id),
1360
  urlenc(&redirect_url),
1361
+ urlenc("basic_access,email,manage_library,listening_history"),
1362
+ urlenc(&code)
1363
  );
1364
 
1365
  let code_js = serde_json::to_string(&code).unwrap_or_else(|_| "\"\"".to_string());
1366
  let oauth_url_js = serde_json::to_string(&oauth_url).unwrap_or_else(|_| "\"\"".to_string());
1367
+ let redirect_url_txt = htmlesc(&redirect_url);
1368
 
1369
  let mut html = String::with_capacity(6000);
1370
  html.push_str(r#"<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">"#);
1371
  html.push_str(r#"<title>Deezer Login</title>"#);
1372
+ 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}button{width:100%;padding:12px 14px;border-radius:10px;border:0;background:#a238ff;color:#fff;font-weight:800;font-size:16px}.muted{color:#aaa;font-size:13px;line-height:1.35}.ok{color:#1db954}.err{color:#ff5a5a}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;background:#101010;border-radius:8px;padding:10px;word-break:break-all}a{color:#a238ff}input{width:100%;box-sizing:border-box;padding:12px 12px;border-radius:10px;border:1px solid #2b2b2b;background:#0f0f0f;color:#fff;outline:none}input:focus{border-color:#a238ff}.row{margin:10px 0 0}</style>"#);
1373
  html.push_str(r#"</head><body><div class="box">"#);
1374
  html.push_str(r#"<h2 style="margin:0 0 6px">Deezer: вход для ТВ</h2>"#);
1375
  html.push_str(r#"<div class="muted">Код пары: <b>"#);
 
1378
  html.push_str(r#"<div class="muted" style="margin:10px 0 14px">Нажми кнопку, войди в Deezer и подтверди доступ. После успеха вернись на телевизор.</div>"#);
1379
  html.push_str(r#"<button id="dz-login" type="button">Войти через Deezer</button>"#);
1380
  html.push_str(r#"<div id="msg" class="muted" style="margin:12px 0 0"></div>"#);
1381
+ html.push_str(r#"<div class="muted" style="margin:12px 0 8px">Если OAuth не работает (redirect_uri) — используй вход по логину/паролю ниже.</div>"#);
1382
+ html.push_str(r#"<form method="POST" action="/pair/complete" autocomplete="on" style="margin:10px 0 0">"#);
1383
+ html.push_str(r#"<input type="hidden" name="code" value="" id="pair-code-hidden">"#);
1384
+ html.push_str(r#"<div class="row"><input name="email" type="email" placeholder="Email" autocomplete="username" required></div>"#);
1385
+ html.push_str(r#"<div class="row"><input name="password" type="password" placeholder="Пароль" autocomplete="current-password" required></div>"#);
1386
+ html.push_str(r#"<div class="row"><button type="submit">Войти по логину и паролю</button></div>"#);
1387
+ html.push_str(r#"</form>"#);
1388
+ html.push_str(r#"<div class="muted" style="margin:14px 0 8px">Redirect URI для Deezer приложения:</div>"#);
1389
+ html.push_str(r#"<div class="mono">"#);
1390
+ html.push_str(&redirect_url_txt);
1391
+ html.push_str(r#"</div>"#);
1392
  html.push_str(r#"<script>(function(){"#);
1393
  html.push_str("var code=");
1394
  html.push_str(&code_js);
1395
  html.push_str(r#";var oauthUrl="#);
1396
  html.push_str(&oauth_url_js);
1397
+ 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;}var hidden=document.getElementById('pair-code-hidden');if(hidden){hidden.value=code;}document.getElementById('dz-login').addEventListener('click',function(){setMsg('','Открываю Deezer…');if(!oauthUrl){setMsg('err','Не удалось собрать Deezer OAuth URL');return;}window.location.href=oauthUrl;});})();</script>"#);
1398
  html.push_str(r#"<p class="muted" style="margin:14px 0 0">Если Deezer пишет про неверный redirect/domain — нужно указать свой <b>DEEZER_APP_ID</b> для домена этого сервера.</p>"#);
1399
  html.push_str(r#"</div></body></html>"#);
1400
  Html(html)
 
1403
  #[derive(Deserialize)]
1404
  struct PairOauthCbQuery {
1405
  code: Option<String>,
1406
+ state: Option<String>,
1407
  }
1408
 
1409
  async fn pair_oauth_cb(Query(q): Query<PairOauthCbQuery>) -> impl IntoResponse {
1410
+ let code = q
1411
+ .code
1412
+ .or(q.state)
1413
+ .unwrap_or_default()
1414
+ .trim()
1415
+ .to_string();
1416
  let safe_code = htmlesc(&code);
1417
  let code_js = serde_json::to_string(&code).unwrap_or_else(|_| "\"\"".to_string());
1418
 
 
1427
  html.push_str(r#"</b></div>"#);
1428
  html.push_str(r#"<div id="msg" class="muted" style="margin:12px 0 0">Получаю токен…</div>"#);
1429
  html.push_str(r#"<script>(function(){"#);
1430
+ html.push_str("var codeFromQuery=");
1431
  html.push_str(&code_js);
1432
+ 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;}var h=parseHash();var code=(codeFromQuery||h.state||'');if(!code){setMsg('err','Нет кода пары. Вернись на телевизор и обнови QR/ссылку.');return;}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>"#);
1433
  html.push_str(r#"</div></body></html>"#);
1434
  (StatusCode::OK, Html(html)).into_response()
1435
  }