My personal site cherry.computer
htmx tailwind axum askama
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

build: set up workflow to get my Apple Music user token

cherry.computer 8f4535e8 dae4de89

verified
+79 -15
+1 -1
frontend/esbuild.js
··· 50 50 }; 51 51 const url = new URL(`http://localhost${req.url}`); 52 52 const route = 53 - url.pathname === "/" || url.pathname === "/scrobbles" 53 + url.pathname === "/" || url.pathname === "/dev/am-auth-flow" 54 54 ? { hostname: "127.0.0.1", port: 8080 } 55 55 : { hostname: hosts[0], port }; 56 56 const routedOptions = { ...options, ...route };
+1 -1
justfile
··· 5 5 6 6 [working-directory: 'frontend'] 7 7 serve-js: 8 - npm start 8 + watchexec --restart --watch esbuild.js npm start 9 9 10 10 [working-directory: 'server'] 11 11 serve-rs $RUST_LOG=env('RUST_LOG', 'debug,selectors=warn,html5ever=warn'):
+16
server/src/am_auth_flow.rs
··· 1 + use askama::Template; 2 + 3 + use crate::scrapers::apple_music; 4 + 5 + #[derive(Template, Debug, Clone)] 6 + #[template(path = "am-auth-flow.html")] 7 + pub struct AuthFlowTemplate { 8 + token: String, 9 + } 10 + 11 + impl AuthFlowTemplate { 12 + pub fn new() -> anyhow::Result<Self> { 13 + let token = apple_music::build_developer_token()?; 14 + Ok(Self { token }) 15 + } 16 + }
+22
server/src/main.rs
··· 1 + #[cfg(debug_assertions)] 2 + mod am_auth_flow; 1 3 mod index; 2 4 mod scrapers; 3 5 4 6 use std::net::SocketAddr; 5 7 8 + #[cfg(debug_assertions)] 9 + use crate::am_auth_flow::AuthFlowTemplate; 6 10 use crate::index::RootTemplate; 7 11 8 12 use askama::Template; ··· 24 28 25 29 let app = Router::new() 26 30 .route("/", get(render_index_handler)) 31 + .route("/dev/am-auth-flow", get(render_apple_music_auth_flow)) 27 32 .fallback(get_service(ServeDir::new("."))) 28 33 .layer( 29 34 ServiceBuilder::new() ··· 50 55 StatusCode::INTERNAL_SERVER_ERROR 51 56 }) 52 57 } 58 + 59 + async fn render_apple_music_auth_flow() -> impl IntoResponse { 60 + #[cfg(not(debug_assertions))] 61 + return StatusCode::NOT_FOUND; 62 + 63 + #[cfg(debug_assertions)] 64 + { 65 + let template = AuthFlowTemplate::new(); 66 + template 67 + .and_then(|template| Ok(template.render()?)) 68 + .map(Html) 69 + .map_err(|err| { 70 + tracing::error!("failed to render Apple Music auth flow: {err:?}"); 71 + StatusCode::INTERNAL_SERVER_ERROR 72 + }) 73 + } 74 + }
+16 -13
server/src/scrapers/apple_music.rs
··· 53 53 54 54 impl AppleMusic { 55 55 pub async fn fetch() -> anyhow::Result<Self> { 56 - let mut header = Header::new(Algorithm::ES256); 57 - header.kid = Some( 58 - env::var("APPLE_DEVELOPER_TOKEN_KEY_ID").context("missing apple developer key ID")?, 59 - ); 60 - let team_id = 61 - env::var("APPLE_DEVELOPER_TOKEN_TEAM_ID").context("missing apple developer team ID")?; 62 - let claims = Claims::new(team_id); 63 - let auth_key = env::var("APPLE_DEVELOPER_TOKEN_AUTH_KEY") 64 - .context("missing apple developer auth key")?; 65 - let key = EncodingKey::from_ec_pem(auth_key.as_bytes()) 66 - .context("failed to parse appple developer auth key")?; 67 - let jwt = jsonwebtoken::encode(&header, &claims, &key) 68 - .context("failed to encode apple developer JWT")?; 56 + let jwt = build_developer_token()?; 69 57 let user_token = env::var("APPLE_USER_TOKEN").context("missing apple user token")?; 70 58 71 59 let client = Client::new(); ··· 102 90 .map_err(|error| tracing::warn!(?error, "failed to call Apple Music")) 103 91 .ok() 104 92 } 93 + 94 + pub fn build_developer_token() -> anyhow::Result<String> { 95 + let mut header = Header::new(Algorithm::ES256); 96 + header.kid = 97 + Some(env::var("APPLE_DEVELOPER_TOKEN_KEY_ID").context("missing apple developer key ID")?); 98 + let team_id = 99 + env::var("APPLE_DEVELOPER_TOKEN_TEAM_ID").context("missing apple developer team ID")?; 100 + let claims = Claims::new(team_id); 101 + let auth_key = 102 + env::var("APPLE_DEVELOPER_TOKEN_AUTH_KEY").context("missing apple developer auth key")?; 103 + let key = EncodingKey::from_ec_pem(auth_key.as_bytes()) 104 + .context("failed to parse appple developer auth key")?; 105 + 106 + jsonwebtoken::encode(&header, &claims, &key).context("failed to encode apple developer JWT") 107 + }
+23
server/templates/am-auth-flow.html
··· 1 + <!doctype html> 2 + <html> 3 + <head> 4 + <title>cherry.computer's Apple Music Sign-In</title> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <meta name="apple-music-developer-token" content={{ token }} /> 7 + <meta name="apple-music-app-name" content="cherry.computer sign-in" /> 8 + <meta name="apple-music-app-build" content="1.0" /> 9 + <script src="https://js-cdn.music.apple.com/musickit/v3/musickit.js" async></script> 10 + <script> 11 + document.addEventListener('musickitloaded', async function () { 12 + const music = MusicKit.getInstance(); 13 + const userToken = await music.authorize(); 14 + 15 + const codeEl = document.createElement("code"); 16 + codeEl.textContent = userToken; 17 + document.body.appendChild(codeEl); 18 + }); 19 + </script> 20 + </head> 21 + <body> 22 + </body> 23 + </html>