mirror of https://github.com/miraclx/freyr-js
feat(Apple Music): Automated Authentication (#623)
* apple_music_token * apple_music_token
This commit is contained in:
parent
3a20b68a4b
commit
92228262ba
7
cli.js
7
cli.js
|
|
@ -1537,7 +1537,12 @@ async function init(packageJson, queries, options) {
|
||||||
const authQueue = new AsyncQueue('cli:authQueue', 1, async (service, logger) => {
|
const authQueue = new AsyncQueue('cli:authQueue', 1, async (service, logger) => {
|
||||||
async function coreAuth(loginLogger) {
|
async function coreAuth(loginLogger) {
|
||||||
if (!Config.opts.attemptAuth) return;
|
if (!Config.opts.attemptAuth) return;
|
||||||
const authHandler = service.newAuth();
|
let authHandler;
|
||||||
|
try {
|
||||||
|
authHandler = service.newAuth();
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const url = await authHandler.getUrl;
|
const url = await authHandler.getUrl;
|
||||||
if (Config.opts.autoOpenBrowser)
|
if (Config.opts.autoOpenBrowser)
|
||||||
await processPromise(open(url), loginLogger, {
|
await processPromise(open(url), loginLogger, {
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,7 @@
|
||||||
"refreshToken": "AQCbeNs5NiGfHa6He0BlOdoQwEIdo2lwBmefEvpvqVy8WlL2HV7rmGbb30_oaZHMDf9MgXtGjI0gV_QukL33PE8c2bGqJHeMqdHBUjKWhxOW19snJkGUiyxen8UvmDG-OP4"
|
"refreshToken": "AQCbeNs5NiGfHa6He0BlOdoQwEIdo2lwBmefEvpvqVy8WlL2HV7rmGbb30_oaZHMDf9MgXtGjI0gV_QukL33PE8c2bGqJHeMqdHBUjKWhxOW19snJkGUiyxen8UvmDG-OP4"
|
||||||
},
|
},
|
||||||
"apple_music": {
|
"apple_music": {
|
||||||
"storefront": "us",
|
"storefront": "us"
|
||||||
"developerToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IldlYlBsYXlLaWQifQ.eyJpc3MiOiJBTVBXZWJQbGF5IiwiaWF0IjoxNzAyNTAyMjM0LCJleHAiOjE3MDk3NTk4MzQsInJvb3RfaHR0cHNfb3JpZ2luIjpbImFwcGxlLmNvbSJdfQ.zzeMLmez71PLinP9GozYSQnF7NYyCiXHB9tKL3-cyu3LzyeRnYz0ejLj4CrNJs0dlNkFg9_mwKmMLueUAR-KRg"
|
|
||||||
},
|
},
|
||||||
"deezer": {
|
"deezer": {
|
||||||
"retries": 2
|
"retries": 2
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import xurl from 'url';
|
import xurl from 'url';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
|
import got from 'got';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import NodeCache from 'node-cache';
|
import NodeCache from 'node-cache';
|
||||||
import {Client} from '@yujinakayama/apple-music';
|
import {Client} from '@yujinakayama/apple-music';
|
||||||
|
|
@ -22,7 +23,9 @@ export default class AppleMusic {
|
||||||
// https://www.debuggex.com/r/Pv_Prjinkz1m2FOB
|
// https://www.debuggex.com/r/Pv_Prjinkz1m2FOB
|
||||||
VALID_URL:
|
VALID_URL:
|
||||||
/(?:(?:(?:(?:https?:\/\/)?(?:www\.)?)(?:(?:music|(?:geo\.itunes))\.apple.com)\/([a-z]{2})\/(song|album|artist|playlist)\/(?:([^/]+)\/)?\w+)|(?:apple_music:(track|album|artist|playlist):([\w.]+)))/,
|
/(?:(?:(?:(?:https?:\/\/)?(?:www\.)?)(?:(?:music|(?:geo\.itunes))\.apple.com)\/([a-z]{2})\/(song|album|artist|playlist)\/(?:([^/]+)\/)?\w+)|(?:apple_music:(track|album|artist|playlist):([\w.]+)))/,
|
||||||
PROP_SCHEMA: {},
|
PROP_SCHEMA: {
|
||||||
|
developerToken: {type: 'string'},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
[symbols.meta] = AppleMusic[symbols.meta];
|
[symbols.meta] = AppleMusic[symbols.meta];
|
||||||
|
|
@ -30,6 +33,8 @@ export default class AppleMusic {
|
||||||
#store = {
|
#store = {
|
||||||
cache: new NodeCache(),
|
cache: new NodeCache(),
|
||||||
core: null,
|
core: null,
|
||||||
|
axiosInstance: null,
|
||||||
|
expiry: null,
|
||||||
defaultStorefront: null,
|
defaultStorefront: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
};
|
};
|
||||||
|
|
@ -37,23 +42,35 @@ export default class AppleMusic {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
if (!config) throw new Error(`[AppleMusic] Please define a configuration object`);
|
if (!config) throw new Error(`[AppleMusic] Please define a configuration object`);
|
||||||
if (typeof config !== 'object') throw new Error(`[AppleMusic] Please define a configuration as an object`);
|
if (typeof config !== 'object') throw new Error(`[AppleMusic] Please define a configuration as an object`);
|
||||||
if (!config.developerToken)
|
this.#store.core = new Client({});
|
||||||
throw new Error(`[AppleMusic] Please define [developerToken] as a property within the configuration`);
|
this.#store.axiosInstance = this.#store.core.songs.axiosInstance;
|
||||||
this.#store.core = new Client({developerToken: config.developerToken});
|
for (let instance of [this.#store.core.albums, this.#store.core.artists, this.#store.core.playlists])
|
||||||
for (let instance of [this.#store.core.albums, this.#store.core.artists, this.#store.core.playlists, this.#store.core.songs])
|
instance.axiosInstance = this.#store.axiosInstance;
|
||||||
instance.axiosInstance.interceptors.request.use(conf => ((conf.headers.origin = 'https://music.apple.com'), conf));
|
this.#store.axiosInstance.defaults.headers['Origin'] = 'https://music.apple.com';
|
||||||
this.#store.defaultStorefront = config.storefront;
|
this.#store.defaultStorefront = config.storefront;
|
||||||
this.#store.isAuthenticated = !!config.developerToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadConfig(_config) {}
|
expiresAt(developerToken) {
|
||||||
|
let segments = developerToken.split('.');
|
||||||
|
let payload = Buffer.from(segments[1] || '', 'base64');
|
||||||
|
let parsed = JSON.parse(payload.toString());
|
||||||
|
return parsed.exp * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfig(config) {
|
||||||
|
if (config.developerToken) {
|
||||||
|
this.#store.expiry = this.expiresAt(config.developerToken);
|
||||||
|
this.#store.core.configuration.developerToken = config.developerToken;
|
||||||
|
this.#store.axiosInstance.defaults.headers['Authorization'] = `Bearer ${config.developerToken}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasOnceAuthed() {
|
hasOnceAuthed() {
|
||||||
throw Error('Unimplemented: [AppleMusic:hasOnceAuthed()]');
|
return this.#store.isAuthenticated;
|
||||||
}
|
}
|
||||||
|
|
||||||
isAuthed() {
|
isAuthed() {
|
||||||
return this.#store.isAuthenticated;
|
return Date.now() < this.#store.expiry;
|
||||||
}
|
}
|
||||||
|
|
||||||
newAuth() {
|
newAuth() {
|
||||||
|
|
@ -61,19 +78,32 @@ export default class AppleMusic {
|
||||||
}
|
}
|
||||||
|
|
||||||
canTryLogin() {
|
canTryLogin() {
|
||||||
return !!this.#store.core.configuration.developerToken;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasProps() {
|
hasProps() {
|
||||||
return false;
|
return this.#store.isAuthenticated;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps() {
|
getProps() {
|
||||||
throw Error('Unimplemented: [AppleMusic:getProps()]');
|
return {
|
||||||
|
developerToken: this.#store.core.configuration.developerToken,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async login() {
|
async login() {
|
||||||
throw Error('Unimplemented: [AppleMusic:login()]');
|
let browsePage = await got('https://music.apple.com/us/browse').text();
|
||||||
|
let scriptUri;
|
||||||
|
if (!(scriptUri = browsePage.match(/assets\/index-[a-z0-9]{8}\.js/)?.[0]))
|
||||||
|
throw new Error('Unable to extract core script from Apple Music');
|
||||||
|
let script = await got(`https://music.apple.com/${scriptUri}`).text();
|
||||||
|
let developerToken;
|
||||||
|
if (!(developerToken = script.match(/eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IldlYlBsYXlLaWQifQ[^"]+/)?.[0]))
|
||||||
|
throw new Error('Unable to extract developerToken from Apple Music core script');
|
||||||
|
this.#store.expiry = this.expiresAt(developerToken);
|
||||||
|
this.#store.core.configuration.developerToken = developerToken;
|
||||||
|
this.#store.axiosInstance.defaults.headers['Authorization'] = `Bearer ${developerToken}`;
|
||||||
|
return (this.#store.isAuthenticated = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
validateType(uri) {
|
validateType(uri) {
|
||||||
|
|
@ -94,7 +124,7 @@ export default class AppleMusic {
|
||||||
const id = isURI ? match[5] : parsedURL.query.i || path.basename(parsedURL.pathname);
|
const id = isURI ? match[5] : parsedURL.query.i || path.basename(parsedURL.pathname);
|
||||||
const type = isURI ? match[4] : collection_type == 'album' && parsedURL.query.i ? 'track' : collection_type;
|
const type = isURI ? match[4] : collection_type == 'album' && parsedURL.query.i ? 'track' : collection_type;
|
||||||
const scope = collection_type == 'track' || (collection_type == 'album' && parsedURL.query.i) ? 'song' : collection_type;
|
const scope = collection_type == 'track' || (collection_type == 'album' && parsedURL.query.i) ? 'song' : collection_type;
|
||||||
storefront = match[1] || storefront || (#store in this ? this.#store.defaultStorefront : 'us');
|
storefront = match[1] || storefront || (#store in this ? this.#store.defaultStorefront : null) || 'us';
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue