diff --git a/package.json b/package.json index fdceb0f6..5ca5c12b 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "get-port": "^5.1.1", "jimp": "^0.16.1", "jsonc": "^2.0.0", - "lastfm-autocorrect": "^1.0.0", "lastfmapi": "^0.1.1", "mdns-js": "git+https://github.com/ciderapp/node-mdns-js.git", "mpris-service": "^2.1.2", diff --git a/src/main/base/app.ts b/src/main/base/app.ts index 2799eb9f..24dd1e4f 100644 --- a/src/main/base/app.ts +++ b/src/main/base/app.ts @@ -164,11 +164,8 @@ export class AppEvents { if (arg.includes('auth')) { const authURI = arg.split('/auth/')[1] if (authURI.startsWith('lastfm')) { // If we wanted more auth options - // const authKey = authURI.split('lastfm?token=')[1]; - // utils.setStoreValue('lastfm.enabled', true); - // utils.setStoreValue('lastfm.auth_token', authKey); - // utils.getWindow().webContents.send('LastfmAuthenticated', authKey); - this.plugin.callPlugin('lastfm', 'authenticateUser', authURI.split('lastfm?token=')[1]); + console.log('token: ', authURI.split('lastfm?token=')[1]) + utils.getWindow().webContents.executeJavaScript(`ipcRenderer.send('lastfm:auth', "${authURI.split('lastfm?token=')[1]}")`).catch(console.error) } } // Play diff --git a/src/main/base/plugins.ts b/src/main/base/plugins.ts index d51e8a85..24402278 100644 --- a/src/main/base/plugins.ts +++ b/src/main/base/plugins.ts @@ -107,7 +107,8 @@ export class Plugins { try{ this.pluginsList[plugin][event](...args); }catch(e) { - console.log(`[${plugin}] Plugin error: ${e}`); + console.error(`[${plugin}] An error was encountered: ${e}`); + console.error(e) } } } diff --git a/src/main/base/store.ts b/src/main/base/store.ts index 53f3a42a..76b96a43 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -222,12 +222,10 @@ export class Store { "lastfm": { "enabled": false, "scrobble_after": 30, - "enabledRemoveFeaturingArtists": true, - "filterLoop": true, - "NowPlaying": "true", "secrets": { - "auth_token": "", - "session": {}, + "username": "", + "key": "", + "token": "" } }, diff --git a/src/main/plugins/lastfm.ts b/src/main/plugins/lastfm.ts new file mode 100644 index 00000000..5c3c9767 --- /dev/null +++ b/src/main/plugins/lastfm.ts @@ -0,0 +1,216 @@ +// https://github.com/maxkueng/node-lastfmapi +// https://github.com/maxkueng/lastfm-autocorrect +// @todo: add autocorrect +// @todo: add scrobble and filter to prevent no-title-found being scrobbled +// @todo: handle session keys through config to stop aids session.json + +export default class lastfm { + + /** + * Base Plugin Information + */ + public name: string = 'LastFM Plugin for Cider'; + public version: string = '2.0.0'; + public author: string = 'Core (Cider Collective)'; + + /** + * Private variables for interaction in plugins + */ + private _attributes: any; + private _apiCredentials = { + key: "f9986d12aab5a0fe66193c559435ede3", + secret: "acba3c29bd5973efa38cc2f0b63cc625" + } + /** + * Plugin Initialization + */ + private _lfm: any = null; + private _authenticated: boolean = false; + private _utils: any = null; + private _activityCache: any = { + details: '', + state: '', + largeImageKey: '', + largeImageText: '', + smallImageKey: '', + smallImageText: '', + instance: false + }; + + + /** + * Public Methods + */ + + constructor(utils: any) { + this._utils = utils; + this.initializeLastFM("", this._apiCredentials) + } + + onReady(win: Electron.BrowserWindow): void { + + // Register the ipcMain handlers + this._utils.getIPCMain().handle('lastfm:url', (event: any) => { + // console.debug('lastfm:url', event) + return this._lfm.getAuthenticationUrl({"cb": "cider://auth/lastfm"}) + }) + + this._utils.getIPCMain().on('lastfm:auth', (event: any, token: string) => { + // console.debug('lastfm:auth', event, token) + this.authenticateLastFM(token) + }) + } + + /** + * Runs on playback State Change + * @param attributes Music Attributes (attributes.status = current state) + */ + onPlaybackStateDidChange(attributes: object): void { + this._attributes = attributes + // this.scrobbleTrack(attributes) + } + + /** + * Runs on song change + * @param attributes Music Attributes + */ + onNowPlayingItemDidChange(attributes: object): void { + this._attributes = attributes + this.scrobbleTrack(attributes) + } + + /** + * Initialize LastFM + * @param token + * @param api + * @private + */ + private initializeLastFM(token: string, api: { key: string, secret: string }): void { + const LastfmAPI = require("lastfmapi") + this._lfm = new LastfmAPI({ + 'api_key': api.key, + 'secret': api.secret, + }); + + if (this._utils.getStoreValue("lastfm.secrets.username") && this._utils.getStoreValue("lastfm.secrets.key")) { + this._lfm.setSessionCredentials(this._utils.getStoreValue("lastfm.secrets.session.username"), this._utils.getStoreValue("lastfm.secrets.session.key")); + this._authenticated = true; + } else { + this.authenticateLastFM(token) + } + } + + /** + * Authenticate the user with the given token + * @param token + * @private + */ + private authenticateLastFM(token: string): void { + if (!token) return; + this._lfm.authenticate(token, (err: any, session: any) => { + if (err) { + console.error(err); + return; + } + console.log(session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"} + this._utils.setStoreValue("lastfm.secrets.token", token) + this._utils.setStoreValue('lastfm.secrets.username', session.username); + this._utils.setStoreValue('lastfm.secrets.key', session.key); + this._authenticated = true; + }); + } + + /** + * Verifies the track information with lastfm + * @param attributes + * @private + */ + private verifyTrack(attributes: any): object { + if (!attributes) return {}; + + if (!attributes.lfmAlbum) { + return this._lfm.album.getInfo({ + "artist": attributes.artistName, + "album": attributes.albumName + }, (err: any, data: any) => { + if (err) { + console.error(`[${lastfm.name}] [album.getInfo] Error: ${err}`) + return {}; + } + if (data) { + attributes.lfmAlbum = data + } + this.scrobbleTrack(attributes) + }) + } else { + return this._lfm.track.getCorrection(attributes.artistName, attributes.name, (err: any, data: any) => { + if (err) { + console.error(`[${lastfm.name}] [track.getCorrection] Error: ${err}`) + console.error(err) + return {}; + } + if (data) { + attributes.lfmTrack = data.correction.track + } + this.scrobbleTrack(attributes) + }) + } + + + } + + /** + * Scrobbles the track to lastfm + * @param attributes + * @private + */ + private scrobbleTrack(attributes: any): void { + if (!attributes?.lfmTrack || !attributes?.lfmAlbum) { + this.verifyTrack(attributes) + return + } + + if (!this._authenticated || !attributes) return; + // Scrobble + + const scrobble = { + 'artist': attributes.lfmTrack.artist.name, + 'track': attributes.lfmTrack.name, + 'album': attributes.lfmAlbum.name, + 'albumArtist': attributes.lfmAlbum.artist, + 'timestamp': new Date().getTime() / 1000, + 'trackNumber': attributes.trackNumber, + 'duration': attributes.durationInMillis / 1000, + } + if (!this._utils.getApp().isPackaged) { + console.debug(scrobble) + } + this._lfm.track.scrobble(scrobble, (err: any, res: any) => { + if (err) { + console.error(`[${lastfm.name}] [lastfm:scrobble] Scrobble failed: ${err.message}`); + } else { + console.debug(`[${lastfm.name}] [lastfm:scrobble] Track scrobbled: ${res}`); + } + }); + this._activityCache = attributes + } + + private updateNowPlaying(attributes: any): void { + if (!this._authenticated) return; + this._lfm.track.updateNowPlaying({ + 'artist': attributes.artistName, + 'track': attributes.name, + 'album': attributes.albumName, + 'albumArtist': attributes.albumName, + 'trackNumber': attributes.trackNumber, + 'duration': attributes.duration / 1000, + }, function (err: any, scrobbled: any) { + if (err) { + return console.error('[LastFM] An error occurred while updating now playing', err); + } + + console.log('[LastFM] Successfully updated now playing: ', scrobbled); + }); + } + +} \ No newline at end of file diff --git a/src/main/plugins/lfm_new.ts b/src/main/plugins/lfm_new.ts deleted file mode 100644 index 8bcbba88..00000000 --- a/src/main/plugins/lfm_new.ts +++ /dev/null @@ -1,106 +0,0 @@ -import {app} from 'electron'; - -// https://github.com/maxkueng/node-lastfmapi -// https://github.com/maxkueng/lastfm-autocorrect -// @todo: add autocorrect -// @todo: add scrobble and filter to prevent no-title-found being scrobbled -// @todo: handle session keys through config to stop aids session.json - -export default class lfm_new { - - /** - * Base Plugin Information - */ - public name: string = 'LastFM Plugin for Cider'; - public version: string = '2.0.0'; - public author: string = 'Core (Cider Collective)'; - - /** - * Private variables for interaction in plugins - */ - private _attributes: any; - private _apiCredentials = { - key: "f9986d12aab5a0fe66193c559435ede3", - secret: "acba3c29bd5973efa38cc2f0b63cc625" - } - - /** - * Plugin Initialization - */ - private _lfm: any = null; - private _authenticated: boolean = false; - private _utils: any = null; - private _activityCache: any = { - details: '', - state: '', - largeImageKey: '', - largeImageText: '', - smallImageKey: '', - smallImageText: '', - instance: false - }; - - /** - * Initialize LastFM - * @param token - * @param api - * @private - */ - private initializeLastFM(token: string, api: {key: string, secret: string}): void { - const LastfmAPI = require("lastfmapi") - this._lfm = new LastfmAPI({ - 'api_key' : api.key, - 'secret' : api.secret, - }); - - if (this._utils.getStoreValue("lastfm.secrets.session")) { - this._lfm.setSessionCredentials(this._utils.getStoreValue("lastfm.secrets.session")); - this._authenticated = true; - } else { - this.authenticateLastFM(token) - } - } - - /** - * Authenticate the user with the given token - * @param token - * @private - */ - private authenticateLastFM(token: string): void { - if (!token) return; - this._lfm.authenticate(token, (err: any, session: any) => { - if (err) { console.error(err); return; } - console.log(session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"} - this._utils.setStoreValue('lastfm.secrets.session', session); - this._authenticated = true; - }); - } - - /** - * Public Methods - */ - public authenticateUser(token: string): void { - this.initializeLastFM(token, this._apiCredentials) - } - - constructor(utils: any) { - this._utils = utils; - this.authenticateUser("") - } - - public onReady(win: Electron.BrowserWindow): void { - - this._utils.getIPCMain().handle('lfm_new:url', (event: any) => { - console.debug('lfm_new:url', event) - return this._lfm.getAuthenticationUrl({"cb": "cider://auth/lastfm"}) - }) - - this._utils.getIPCMain().on('lfm_new:auth', (event: any, token: string) => { - console.debug('lfm_new:auth', event, token) - this.authenticateUser(token) - }) - } - - - -} \ No newline at end of file diff --git a/src/renderer/main/vueapp.js b/src/renderer/main/vueapp.js index 94a39304..58f54f25 100644 --- a/src/renderer/main/vueapp.js +++ b/src/renderer/main/vueapp.js @@ -4339,37 +4339,6 @@ const app = new Vue({ } }, - LastFMDeauthorize() { - ipcRenderer.invoke('setStoreValue', 'lastfm.enabled', false).catch((e) => console.error(e)); - ipcRenderer.invoke('setStoreValue', 'lastfm.auth_token', '').catch((e) => console.error(e)); - app.cfg.lastfm.auth_token = ""; - app.cfg.lastfm.enabled = false; - const element = document.getElementById('lfmConnect'); - element.innerHTML = app.getLz('term.connect'); - element.onclick = app.LastFMAuthenticate; - }, - LastFMAuthenticate() { - console.log("[LastFM] Received LastFM authentication callback") - const element = document.getElementById('lfmConnect'); - // new key : f9986d12aab5a0fe66193c559435ede3 - window.open('https://www.last.fm/api/auth?api_key=f9986d12aab5a0fe66193c559435ede3&cb=cider://auth/lastfm'); - element.innerText = app.getLz('term.connecting') + '...'; - - /* Just a timeout for the button */ - setTimeout(() => { - if (element.innerText === app.getLz('term.connecting') + '...') { - element.innerText = app.getLz('term.connect'); - console.warn('[LastFM] Attempted connection timed out.'); - } - }, 20000); - - ipcRenderer.on('LastfmAuthenticated', function (_event, lfmAuthKey) { - app.cfg.lastfm.auth_token = lfmAuthKey; - app.cfg.lastfm.enabled = true; - element.innerHTML = `${app.getLz('term.disconnect')}\n

(${app.getLz('term.authed')}: ${lfmAuthKey})

`; - element.onclick = app.LastFMDeauthorize; - }); - }, fullscreen(flag) { this.fullscreenState = flag; if (flag) { diff --git a/src/renderer/views/pages/settings.ejs b/src/renderer/views/pages/settings.ejs index 30774200..1cca7314 100644 --- a/src/renderer/views/pages/settings.ejs +++ b/src/renderer/views/pages/settings.ejs @@ -1064,7 +1064,7 @@
@@ -1079,37 +1079,6 @@ -
-
- {{$root.getLz('settings.option.connectivity.lastfmScrobble.nowPlaying')}} -
-
- -
-
-
-
- {{$root.getLz('settings.option.connectivity.lastfmScrobble.removeFeatured')}} -
-
- -
-
-
-
- {{$root.getLz('settings.option.connectivity.lastfmScrobble.filterLoop')}} -
-
- -
-
@@ -1504,7 +1473,32 @@ }, reloadDiscordRPC() { ipcRenderer.send('reloadRPC') - } + }, + lfmDisconnect(event) { + ipcRenderer.invoke('setStoreValue', 'lastfm.enabled', false).catch((e) => console.error(e)); + ipcRenderer.invoke('setStoreValue', 'lastfm.secrets.session', {}).catch((e) => console.error(e)); + event.target.innerHTML = app.getLz('term.connect'); + event.target.onclick = this.lfmAuthorize; + }, + async lfmAuthorize(event) { + console.debug("[lastfm:authorize] Token received.") + window.open(await ipcRenderer.invoke('lastfm:url')); + event.target.innerText = app.getLz('term.connecting') + '...'; + + /* Just a timeout for the button */ + setTimeout(() => { + if (event.target.innerText === app.getLz('term.connecting') + '...') { + event.target.innerText = app.getLz('term.connect'); + console.warn('[lastfm:authorize] Last.fm authorization timed out.'); + } + }, 20000); + + ipcRenderer.on('lastfm:renderer-auth', function (event, session) { + element.innerHTML = `${app.getLz('term.disconnect')}\n

(${app.getLz('term.authed')}: ${session.username})

`; + element.onclick = this.lfmDisconnect; + }); + + }, } }) \ No newline at end of file