Merge branch 'main' into enhancement/search-bar

This commit is contained in:
Pedro Galhardo 2022-06-29 17:15:02 +01:00
commit 13f12121ba
No known key found for this signature in database
GPG key ID: 4740524CD85770A9
22 changed files with 569 additions and 593 deletions

View file

@ -3,7 +3,6 @@
LATEST_SHA=$(curl -s https://api.github.com/repos/ciderapp/Cider/branches/stable | grep sha | cut -d '"' -f 4 | sed 's/v//' | xargs)
COMMITSINCESTABLE=$(git rev-list $LATEST_SHA..HEAD --count)
CURRENT_VERSION=$(node -p -e "require('./package.json').version")
CIRCLE_BRANCH="main"
if [[ $CIRCLE_BRANCH == "main" && $COMMITSINCESTABLE -gt 0 ]]; then
NEW_VERSION="${CURRENT_VERSION}-beta.${COMMITSINCESTABLE}"
else

View file

@ -497,7 +497,21 @@ Update 14/06/2022 14:10 UTC
* `term.themeManaged`: Added to `en_US`
Update 15/06/2022 20:00 UTC
* `settings.notyf.connectivity.lastfmScrobble.connectError`: Added to `en_US`
* `settings.notyf.connectivity.lastfmScrobble.connectSuccess`: Added to `en_US`
* `settings.notyf.connectivity.lastfmScrobble.connecting`: Added to `en_US`
Update 19/06/2022 12:00 UTC
* `settings.option.connectivity.lastfmScrobble.filterLoop.description`: Added to `en_US`
Update 21/06/2022 20:39 UTC
* `term.showSearch`: Added to `en_US`
* `term.hideSearch`: Added to `en_US`
Update 23/06/2022 04:00 UTC
* `settings.option.connectivity.lastfmScrobble.filterTypes`: Added to `en_US`

View file

@ -239,6 +239,7 @@
"action.delete": "Delete",
"action.edit": "Edit",
"action.done": "Done",
"action.submit": "Submit",
"action.editTracklist": "Edit Tracklist",
"action.addToLibrary": "Add to Library",
"action.addToLibrary.success": "Added to Library",
@ -535,6 +536,12 @@
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable Last.fm Now Playing",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Prevent looped tracks from being scrobbled or displayed in the Now Playing list on Last.fm.",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Filter Media Types (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Enter Last.fm Token Manually",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm Connection Timed Out",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.fm Connection Successful",
"settings.notyf.connectivity.lastfmScrobble.connecting": "Connecting to Last.fm...",
"settings.header.debug": "Debug",
"settings.option.debug.copy_log": "Copy logs to clipboard",
"settings.option.debug.openAppData": "Open Cider Folder",

View file

@ -234,6 +234,7 @@
"action.delete": "Delete",
"action.edit": "Edit",
"action.done": "Done",
"action.submit": "Submit",
"action.editTracklist": "Edit Tracklist",
"action.addToLibrary": "Add to Library",
"action.addToLibrary.success": "Added to Library",
@ -522,6 +523,12 @@
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable Last.fm Now Playing",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Prevent looped tracks from being scrobbled or displayed in the Now Playing list on Last.fm.",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Filter Media Types (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Enter Last.fm Token Manually",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm Connection Timed Out",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.fm Connection Successful",
"settings.notyf.connectivity.lastfmScrobble.connecting": "Connecting to Last.fm...",
"settings.header.debug": "Debug",
"settings.option.debug.copy_log": "Copy logs to clipboard",
"settings.option.debug.openAppData": "Open Cider Folder",

View file

@ -162,13 +162,10 @@ export class AppEvents {
// LastFM Auth URL
if (arg.includes('auth')) {
let authURI = arg.split('/auth/')[1]
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', 'authenticate', authKey);
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

View file

@ -618,8 +618,7 @@ export class BrowserWindow {
//region Connect Integration
app.get("/connect/set-cc-user/:data", (req, res) => {
//utils.getStoreValue('connectUser', JSON.parse()) // [Connect] Save user in store
utils.setStoreValue('connectUser', JSON.parse(req.params.data))
utils.getWindow().reload()
utils.getWindow().webContents.send('setStoreValue', 'connectUser', JSON.parse(req.params.data))
res.redirect(`https://connect.cidercollective.dev/linked.html`)
});
// [Connect] Set auth URL in store for `shell.openExternal`

View file

@ -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)
}
}
}

View file

@ -2,6 +2,7 @@ import * as ElectronStore from 'electron-store';
import * as electron from "electron";
import {app} from "electron";
import fetch from "electron-fetch";
export class Store {
static cfg: ElectronStore;
@ -12,16 +13,6 @@ export class Store {
},
"general": {
"close_button_hide": false,
"discordrpc": {
"enabled": true,
"client": "Cider",
"clear_on_pause": true,
"hide_buttons": false,
"hide_timestamp": false,
"state_format": "by {artist}",
"details_format": "{title}",
},
"refreshInterval": 120000,
"language": "en_US", // electron.app.getLocale().replace('-', '_') this can be used in future
"playbackNotifications": true,
"resumeOnStartupBehavior": "local",
@ -127,6 +118,28 @@ export class Store {
},
"showLovedTracksInline": true
},
"connectivity": {
"discord_rpc": {
"enabled": true,
"client": "Cider",
"clear_on_pause": true,
"hide_buttons": false,
"hide_timestamp": false,
"state_format": "by {artist}",
"details_format": "{title}",
},
"lastfm": {
"enabled": false,
"scrobble_after": 50,
"filter_loop": false,
"filter_types": {},
"secrets": {
"username": "",
"key": ""
}
},
},
"home": {
"followedArtists": [],
"favoriteItems": []
@ -231,14 +244,6 @@ export class Store {
"enable_qq": false,
"enable_yt": false,
},
"lastfm": {
"enabled": false,
"scrobble_after": 30,
"auth_token": "",
"enabledRemoveFeaturingArtists": true,
"filterLoop": true,
"NowPlaying": "true"
},
"advanced": {
"AudioContext": true,
"experiments": [],
@ -255,15 +260,9 @@ export class Store {
}
},
}
private migrations: any = {
'>=1.4.3': (store: ElectronStore) => {
if (typeof store.get('general.discordrpc') == 'number' || typeof store.get('general.discordrpc') == 'string') {
store.delete('general.discordrpc');
}
},
}
private migrations: any = {}
private schema: ElectronStore.Schema<any> = {
"general.discordrpc": {
"connectivity.discord_rpc": {
type: 'object'
},
}
@ -274,57 +273,13 @@ export class Store {
defaults: this.defaults,
schema: this.schema,
migrations: this.migrations,
clearInvalidConfig: true
clearInvalidConfig: false //disabled for now
});
Store.cfg.set(this.mergeStore(this.defaults, Store.cfg.store))
this.ipcHandler();
}
/**
* Merge Configurations
* @param target The target configuration
* @param source The source configuration
*/
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (const key of Object.keys(source)) {
if (key.includes('migrations')) {
continue;
}
if (source[key] instanceof Array) {
continue
}
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
/**
* IPC Handler
*/
private ipcHandler(): void {
electron.ipcMain.handle('getStoreValue', (_event, key, defaultValue) => {
return (defaultValue ? Store.cfg.get(key, true) : Store.cfg.get(key));
});
electron.ipcMain.handle('setStoreValue', (_event, key, value) => {
Store.cfg.set(key, value);
});
electron.ipcMain.on('getStore', (event) => {
event.returnValue = Store.cfg.store
})
electron.ipcMain.on('setStore', (_event, store) => {
Store.cfg.store = store
})
}
static pushToCloud(): void {
if (Store.cfg.get('connectUser.auth') === null) return;
var syncData = Object();
@ -362,4 +317,46 @@ export class Store {
body: JSON.stringify(postBody)
})
}
/**
* Merge Configurations
* @param target The target configuration
* @param source The source configuration
*/
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (const key of Object.keys(source)) {
if (key.includes('migrations')) {
continue;
}
if (source[key] instanceof Array) {
continue
}
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
/**
* IPC Handler
*/
private ipcHandler(): void {
electron.ipcMain.handle('getStoreValue', (_event, key, defaultValue) => {
return (defaultValue ? Store.cfg.get(key, true) : Store.cfg.get(key));
});
electron.ipcMain.handle('setStoreValue', (_event, key, value) => {
Store.cfg.set(key, value);
});
electron.ipcMain.on('getStore', (event) => {
event.returnValue = Store.cfg.store
})
electron.ipcMain.on('setStore', (_event, store) => {
Store.cfg.store = store
})
}
}

View file

@ -2,18 +2,36 @@ import * as fs from "fs";
import * as path from "path";
import {Store} from "./store";
import {BrowserWindow as bw} from "./browserwindow";
import {app, dialog, ipcMain, Notification, shell, BrowserWindow} from "electron";
import {app, BrowserWindow, ipcMain} from "electron";
import fetch from "electron-fetch";
import {AppImageUpdater, NsisUpdater} from "electron-updater";
import * as log from "electron-log";
import ElectronStore from "electron-store";
export class utils {
/**
* Playback Functions
*/
static playback = {
pause: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.pause()")
},
play: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.play()")
},
playPause: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.playPause()")
},
next: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.next()")
},
previous: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.previous()")
}
}
/**
* Paths for the application to use
*/
private static paths: any = {
static paths: any = {
srcPath: path.join(__dirname, "../../src"),
rendererPath: path.join(__dirname, "../../src/renderer"),
mainPath: path.join(__dirname, "../../src/main"),
@ -43,6 +61,13 @@ export class utils {
return app;
}
/**
* Get the IPCMain
*/
static getIPCMain(): Electron.IpcMain {
return ipcMain
}
/**
* Fetches the i18n locale for the given language.
* @param language {string} The language to fetch the locale for.
@ -90,7 +115,6 @@ export class utils {
return Store.cfg.store
}
/**
* Get the store instance
* @returns {Store}
@ -116,10 +140,6 @@ export class utils {
return Store.pushToCloud
}
/**
* Gets the browser window
*/
@ -138,25 +158,4 @@ export class utils {
static loadJSFrontend(path: string): void {
bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8"));
}
/**
* Playback Functions
*/
static playback = {
pause: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.pause()")
},
play: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.play()")
},
playPause: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.playPause()")
},
next: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.next()")
},
previous: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.previous()")
}
}
}

View file

@ -1,18 +1,19 @@
require('v8-compile-cache');
const {app, components, ipcMain} = require('electron');
import {join} from 'path';
require("v8-compile-cache");
import {join} from "path";
import {app} from "electron"
if (!app.isPackaged) {
app.setPath('userData', join(app.getPath('appData'), 'Cider'));
app.setPath("userData", join(app.getPath("appData"), "Cider"));
}
import {Store} from "./base/store";
import {AppEvents} from "./base/app";
import {Plugins} from "./base/plugins";
import {BrowserWindow} from "./base/browserwindow";
import {init as Sentry} from '@sentry/electron';
import {init as Sentry} from "@sentry/electron";
import {RewriteFrames} from "@sentry/integrations";
import {components, ipcMain} from "electron"
// Analytics for debugging fun yeah.
Sentry({
@ -32,13 +33,13 @@ const CiderPlug = new Plugins();
* App Event Handlers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
app.on('ready', () => {
app.on("ready", () => {
Cider.ready(CiderPlug);
console.log('[Cider] Application is Ready. Creating Window.')
console.log("[Cider] Application is Ready. Creating Window.")
if (!app.isPackaged) {
console.info('[Cider] Running in development mode.')
require('vue-devtools').install()
console.info("[Cider] Running in development mode.")
require("vue-devtools").install()
}
components.whenReady().then(async () => {
@ -49,11 +50,11 @@ app.on('ready', () => {
console.log(gpuInfo)
})
console.log('[Cider][Widevine] Status:', components.status());
console.log("[Cider][Widevine] Status:", components.status());
Cider.bwCreated();
win.on("ready-to-show", () => {
console.debug('[Cider] Window is Ready.')
CiderPlug.callPlugins('onReady', win);
console.debug("[Cider] Window is Ready.")
CiderPlug.callPlugins("onReady", win);
win.show();
});
});
@ -68,20 +69,20 @@ ipcMain.handle("renderer-ready", (event) => {
CiderPlug.callPlugins("onRendererReady", event);
})
ipcMain.on('playbackStateDidChange', (_event, attributes) => {
CiderPlug.callPlugins('onPlaybackStateDidChange', attributes);
ipcMain.on("playbackStateDidChange", (_event, attributes) => {
CiderPlug.callPlugins("onPlaybackStateDidChange", attributes);
});
ipcMain.on('nowPlayingItemDidChange', (_event, attributes) => {
CiderPlug.callPlugins('onNowPlayingItemDidChange', attributes);
ipcMain.on("nowPlayingItemDidChange", (_event, attributes) => {
CiderPlug.callPlugins("onNowPlayingItemDidChange", attributes);
});
ipcMain.on('nowPlayingItemDidChangeLastFM', (_event, attributes) => {
CiderPlug.callPlugin('lastfm.js', 'nowPlayingItemDidChangeLastFM', attributes);
ipcMain.on("nowPlayingItemDidChangeLastFM", (_event, attributes) => {
CiderPlug.callPlugin("lastfm.js", "nowPlayingItemDidChangeLastFM", attributes);
})
app.on('before-quit', () => {
CiderPlug.callPlugins('onBeforeQuit');
app.on("before-quit", () => {
CiderPlug.callPlugins("onBeforeQuit");
console.warn(`${app.getName()} exited.`);
});
@ -90,21 +91,21 @@ app.on('before-quit', () => {
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
// @ts-ignore
app.on('widevine-ready', (version, lastVersion) => {
app.on("widevine-ready", (version, lastVersion) => {
if (null !== lastVersion) {
console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
console.log("[Cider][Widevine] Widevine " + version + ", upgraded from " + lastVersion + ", is ready to be used!")
} else {
console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
console.log("[Cider][Widevine] Widevine " + version + " is ready to be used!")
}
})
// @ts-ignore
app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
app.on("widevine-update-pending", (currentVersion, pendingVersion) => {
console.log("[Cider][Widevine] Widevine " + currentVersion + " is ready to be upgraded to " + pendingVersion + "!")
})
// @ts-ignore
app.on('widevine-error', (error) => {
console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
app.on("widevine-error", (error) => {
console.log("[Cider][Widevine] Widevine installation encountered an error: " + error)
app.exit()
})

View file

@ -74,7 +74,7 @@ export default class DiscordRPC {
console.log(`[DiscordRPC][reload] Reloading DiscordRPC.`);
this._client.destroy()
this._client.endlessLogin({clientId: this._utils.getStoreValue("general.discordrpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
this._client.endlessLogin({clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
.then(() => {
this.ready = true
this._utils.getWindow().webContents.send("rpcReloaded", this._client.user)
@ -125,7 +125,7 @@ export default class DiscordRPC {
* @private
*/
private connect() {
if (!this._utils.getStoreValue("general.discordrpc.enabled")) {
if (!this._utils.getStoreValue("connectivity.discord_rpc.enabled")) {
return;
}
@ -143,7 +143,7 @@ export default class DiscordRPC {
})
// Login to Discord
this._client.endlessLogin({clientId: this._utils.getStoreValue("general.discordrpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
this._client.endlessLogin({clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
.then(() => {
this.ready = true
})
@ -161,8 +161,8 @@ export default class DiscordRPC {
// Check if show buttons is (true) or (false)
let activity: Object = {
details: this._utils.getStoreValue("general.discordrpc.details_format"),
state: this._utils.getStoreValue("general.discordrpc.state_format"),
details: this._utils.getStoreValue("connectivity.discord_rpc.details_format"),
state: this._utils.getStoreValue("connectivity.discord_rpc.state_format"),
largeImageKey: attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024'),
largeImageText: attributes.albumName,
instance: false // Whether the activity is in a game session
@ -177,7 +177,7 @@ export default class DiscordRPC {
}
// Set the activity
if (!attributes.status && this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
if (!attributes.status && this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
this._client.clearActivity()
} else if (activity && this._activityCache !== activity) {
this._client.setActivity(activity)
@ -191,7 +191,7 @@ export default class DiscordRPC {
private filterActivity(activity: any, attributes: any): Object {
// Add the buttons if people want them
if (!this._utils.getStoreValue("general.discordrpc.hide_buttons")) {
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_buttons")) {
activity.buttons = [
{label: 'Listen on Cider', url: attributes.url.cider},
{label: 'View on Apple Music', url: attributes.url.appleMusic}
@ -199,13 +199,13 @@ export default class DiscordRPC {
}
// Add the timestamp if its playing and people want them
if (!this._utils.getStoreValue("general.discordrpc.hide_timestamp") && attributes.status) {
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_timestamp") && attributes.status) {
activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime)
activity.endTimestamp = attributes.endTime
}
// If the user wants to keep the activity when paused
if (!this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
if (!this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
activity.smallImageKey = attributes.status ? 'play' : 'pause';
activity.smallImageText = attributes.status ? 'Playing' : 'Paused';
}

View file

@ -1,278 +1,233 @@
import * as electron from 'electron';
import * as fs from 'fs';
import {resolve} from 'path';
export default class lastfm {
export default class LastFMPlugin {
private sessionPath = resolve(electron.app.getPath('userData'), 'session.json');
private apiCredentials = {
/**
* Base Plugin Information
*/
public name: string = 'LastFM Plugin';
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"
}
/**
* Private variables for interaction in plugins
* Plugin Initialization
*/
private _win: any;
private _app: any;
private _lastfm: any;
private _store: any;
private _timer: any;
private authenticateFromFile() {
let sessionData = require(this.sessionPath)
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
this._lastfm.setSessionCredentials(sessionData.username, sessionData.key)
console.log("[LastFM][authenticateFromFile] Logged in.", sessionData.username, sessionData.key)
}
authenticate() {
try {
if (this._store.lastfm.auth_token) {
this._store.lastfm.enabled = true;
}
if (!this._store.lastfm.enabled || !this._store.lastfm.auth_token) {
this._store.lastfm.enabled = false;
return
}
/// dont move this require to top , app wont load
const LastfmAPI = require('lastfmapi');
const lfmAPI = new LastfmAPI({
'api_key': this.apiCredentials.key,
'secret': this.apiCredentials.secret
});
this._lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
fs.stat(this.sessionPath, (err: any) => {
if (err) {
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
console.log("[LastFM][Auth] Beginning authentication from configuration")
console.log("[LastFM][tk]", this._store.lastfm.auth_token)
this._lastfm.authenticate(this._store.lastfm.auth_token, (err: any, session: any) => {
if (err) {
throw err;
}
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
console.log("[LastFM] Saving session info to disk.")
let tempData = JSON.stringify(session)
fs.writeFile(this.sessionPath, tempData, (err: any) => {
if (err)
console.log("[LastFM][fs]", err)
else {
console.log("[LastFM][fs] File was written successfully.")
this.authenticateFromFile()
new electron.Notification({
title: electron.app.getName(),
body: "Successfully logged into LastFM using Authentication Key."
}).show()
}
})
});
} else {
this.authenticateFromFile()
}
})
} catch (err) {
console.log(err)
}
}
private scrobbleSong(attributes: any) {
if (this._timer) clearTimeout(this._timer);
var self = this;
this._timer = setTimeout(async () => {
const currentAttributes = attributes;
if (!self._lastfm || self._lastfm.cachedAttributes === attributes) {
return
}
if (self._lastfm.cachedAttributes) {
if (self._lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
}
const artist = await this.getPrimaryArtist(attributes)
const album = this.getAlbumName(attributes)
if (currentAttributes.status && currentAttributes === attributes) {
if (fs.existsSync(this.sessionPath)) {
// Scrobble playing song.
if (attributes.status === true) {
self._lastfm.track.scrobble({
'artist': artist,
'track': attributes.name,
'album': album,
'albumArtist': artist,
'timestamp': new Date().getTime() / 1000
}, function (err: any, scrobbled: any) {
if (err) {
return console.error('[LastFM] An error occurred while scrobbling', err);
}
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
});
self._lastfm.cachedAttributes = attributes
}
} else {
self.authenticate();
}
} else {
return console.log('[LastFM] Did not add ', attributes.name, '—', artist, 'because now playing a other song.');
}
}, Math.round(attributes.durationInMillis * Math.min((self._store.lastfm.scrobble_after / 100), 0.8)));
}
private async updateNowPlayingSong(attributes: any) {
if (!this._lastfm || this._lastfm.cachedNowPlayingAttributes === attributes || !this._store.lastfm.NowPlaying) {
return
}
if (this._lastfm.cachedNowPlayingAttributes) {
if (this._lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
}
if (fs.existsSync(this.sessionPath)) {
const artist = await this.getPrimaryArtist(attributes)
const album = this.getAlbumName(attributes)
// update Now Playing
if (attributes.status === true) {
this._lastfm.track.updateNowPlaying({
'artist': artist,
'track': attributes.name,
'album': album,
'albumArtist': artist
}, function (err: any, nowPlaying: any) {
if (err) {
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
}
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
});
this._lastfm.cachedNowPlayingAttributes = attributes
}
} else {
this.authenticate()
}
}
private getAlbumName(attributes: any): string {
return attributes.albumName.replace(/ - Single| - EP/g, '');
}
private async getPrimaryArtist(attributes: any) {
const songId = attributes.playParams.catalogId || attributes.playParams.id
if (!this._store.lastfm.enabledRemoveFeaturingArtists || !songId) return attributes.artistName;
const res = await this._win.webContents.executeJavaScript(`
(async () => {
const subMk = await MusicKit.getInstance().api.v3.music("/v1/catalog/" + MusicKit.getInstance().storefrontId + "/songs/${songId}", {
include: {
songs: ["artists"]
}
})
if (!subMk) console.error('[LastFM] Request failed: /v1/catalog/us/songs/${songId}')
return subMk.data
})()
`).catch(console.error)
if (!res) return attributes.artistName
const data = res.data
if (!data.length) {
console.error(`[LastFM] Unable to locate song with id of ${songId}`)
return attributes.artistName;
}
const artists = res.data[0].relationships.artists.data
if (!artists.length) {
console.error(`[LastFM] Unable to find artists related to the song with id of ${songId}`)
return attributes.artistName;
}
const primaryArtist = artists[0]
return primaryArtist.attributes.name
}
private _lfm: any = null;
private _authenticated: boolean = false;
private _scrobbleDelay: any = null;
private _utils: any = null;
private _scrobbleCache: any = {};
private _nowPlayingCache: any = {};
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
* Public Methods
*/
public name: string = 'LastFMPlugin';
public description: string = 'LastFM plugin for Cider';
public version: string = '0.0.1';
public author: string = 'vapormusic / Cider Collective';
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(utils: { getApp: () => any; getStore: () => any; }) {
this._app = utils.getApp();
this._store = utils.getStore()
utils.getApp().on('second-instance', (_e: any, argv: any) => {
// Checks if first instance is authorized and if second instance has protocol args
argv.forEach((value: any) => {
if (value.includes('auth')) {
console.log('[LastFMPlugin ok]')
let authURI = String(argv).split('/auth/')[1];
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1];
this._store.lastfm.enabled = true;
this._store.lastfm.auth_token = authKey;
console.log(authKey);
this._win.webContents.send('LastfmAuthenticated', authKey);
this.authenticate();
}
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.name}:url`)
return this._lfm.getAuthenticationUrl({"cb": "cider://auth/lastfm"})
})
this._utils.getIPCMain().on('lastfm:auth', (event: any, token: string) => {
console.debug(`${lastfm.name}:auth`, token)
this.authenticateLastFM(token)
})
electron.app.on('open-url', (event: any, arg: any) => {
console.log('[LastFMPlugin] yes')
event.preventDefault();
if (arg.includes('auth')) {
let authURI = String(arg).split('/auth/')[1];
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1];
this._store.lastfm.enabled = true;
this._store.lastfm.auth_token = authKey;
this._win.webContents.send('LastfmAuthenticated', authKey);
console.log(authKey);
this.authenticate();
}
}
this._utils.getIPCMain().on('lastfm:disconnect', (_event: any) => {
this._lfm.setSessionCredentials(null, null);
this._authenticated = false;
console.debug(`${lastfm.name}:disconnect`)
})
this._utils.getIPCMain().on('lastfm:nowPlayingChange', (event: any, attributes: any) => {
if (this._utils.getStoreValue("connectivity.lastfm.filter_loop")) return;
this.onNowPlayingItemDidChange(attributes)
})
}
/**
* Runs on app ready
* Runs on playback State Change
* @param attributes Music Attributes (attributes.status = current state)
*/
onReady(win: any): void {
this._win = win;
this.authenticate();
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
console.log('Example plugin stopped');
onPlaybackStateDidChange(attributes: object): void {
this._attributes = attributes
// this.scrobbleTrack(attributes)
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
nowPlayingItemDidChangeLastFM(attributes: any): void {
if (!this._store.general.privateEnabled) {
attributes.status = true
if (!this._store.lastfm.filterLoop) {
this._lastfm.cachedNowPlayingAttributes = false;
this._lastfm.cachedAttributes = false
onNowPlayingItemDidChange(attributes: any): void {
if (this._utils.getStoreValue("general.privateEnabled")) return;
this._attributes = attributes
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
this.verifyTrack(attributes)
return
}
this.updateNowPlayingSong(attributes)
this.scrobbleSong(attributes)
this.scrobbleTrack(attributes)
this.updateNowPlayingTrack(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("connectivity.lastfm.secrets.username") && this._utils.getStoreValue("connectivity.lastfm.secrets.key")) {
this._lfm.setSessionCredentials(this._utils.getStoreValue("connectivity.lastfm.secrets.username"), this._utils.getStoreValue("connectivity.lastfm.secrets.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);
this._utils.getWindow().webContents.executeJavaScript(`app.notyf.error("${err.message}");`)
return;
}
this._utils.getWindow().webContents.send('lastfm:authenticated', session)
this._authenticated = true;
console.debug(`[${lastfm.name}:authenticate] Authenticated as ${session.username}`)
});
}
/**
* Verifies the track information with lastfm
* @param attributes
* @private
*/
private verifyTrack(attributes: any): object {
if (!attributes) return attributes;
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: ${typeof err === "string" ? err : err.message}`)
console.error(err)
return {};
}
if (data) {
attributes.lfmAlbum = data
}
this.onNowPlayingItemDidChange(attributes)
})
} else {
return this._lfm.track.getCorrection(attributes.artistName, attributes.name, (err: any, data: any) => {
if (err) {
console.error(`[${lastfm.name}] [track.getCorrection] Error: ${typeof err === "string" ? err : err.message}`)
console.error(err)
return {};
}
if (data) {
attributes.lfmTrack = data.correction.track
}
this.onNowPlayingItemDidChange(attributes)
})
}
}
/**
* Scrobbles the track to lastfm
* @param attributes
* @private
*/
private scrobbleTrack(attributes: any): void {
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)) return;
if (this._scrobbleDelay) {
clearTimeout(this._scrobbleDelay);
}
// Scrobble delay
this._scrobbleDelay = setTimeout(() => {
// 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,
}
// Easy Debugging
if (!this._utils.getApp().isPackaged) {
console.debug(scrobble)
}
// Scrobble the track
this._lfm.track.scrobble(scrobble, (err: any, _res: any) => {
if (err) {
console.error(`[${lastfm.name}:scrobble] Scrobble failed: ${err.message}`);
} else {
console.debug(`[${lastfm.name}:scrobble] Track scrobbled: ${scrobble.artist} - ${scrobble.track}`);
this._scrobbleCache = scrobble
}
});
}, Math.round(attributes.durationInMillis * Math.min((this._utils.getStoreValue("connectivity.lastfm.scrobble_after") / 100), 0.8)))
}
private updateNowPlayingTrack(attributes: any): void {
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)) return;
const nowPlaying = {
'artist': attributes.lfmTrack.artist.name,
'track': attributes.lfmTrack.name,
'album': attributes.lfmAlbum.name,
'trackNumber': attributes.trackNumber,
'duration': attributes.durationInMillis / 1000,
'albumArtist': attributes.lfmAlbum.artist,
}
this._lfm.track.updateNowPlaying(nowPlaying, (err: any, res: any) => {
if (err) {
console.error(`[${lastfm.name}:updateNowPlaying] Now Playing Update failed: ${err.message}`);
} else {
console.log(res)
console.debug(`[${lastfm.name}:updateNowPlaying] Now Playing Updated: ${nowPlaying.artist} - ${nowPlaying.track}`);
this._nowPlayingCache = nowPlaying
}
});
}
}

View file

@ -37,7 +37,7 @@ export default class mpris {
* @private
*/
private static runMediaEvent(type: string) {
console.debug(`[Plugin][${this.name}] ${type}.`);
// console.debug(`[Plugin][${this.name}] ${type}.`);
mpris.utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.${type}()`).catch(console.error)
}
@ -188,7 +188,7 @@ export default class mpris {
*/
@mpris.linuxOnly
onPlaybackStateDidChange(attributes: object): void {
console.debug(`[Plugin][${mpris.name}] onPlaybackStateDidChange.`);
// console.debug(`[Plugin][${mpris.name}] onPlaybackStateDidChange.`);
mpris.updatePlayerState(attributes)
}
@ -198,7 +198,7 @@ export default class mpris {
*/
@mpris.linuxOnly
onNowPlayingItemDidChange(attributes: object): void {
console.debug(`[Plugin][${mpris.name}] onMetadataDidChange.`);
// console.debug(`[Plugin][${mpris.name}] onMetadataDidChange.`);
mpris.updatePlayer(attributes);
}

View file

@ -11,9 +11,6 @@ const MusicKitInterop = {
if (MusicKitInterop.filterTrack(attributes, true, false)) {
global.ipcRenderer.send('playbackStateDidChange', attributes)
global.ipcRenderer.send('wsapi-updatePlaybackState', attributes);
// if (typeof _plugins != "undefined") {
// _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()})
// }
}
});
@ -24,18 +21,13 @@ const MusicKitInterop = {
/** wsapi */
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => {
console.debug('nowPlayingItemDidChange')
console.debug('[cider:preload] nowPlayingItemDidChange')
const attributes = MusicKitInterop.getAttributes()
const trackFilter = MusicKitInterop.filterTrack(attributes, false, true)
if (trackFilter) {
if (MusicKitInterop.filterTrack(attributes, false, true)) {
global.ipcRenderer.send('nowPlayingItemDidChange', attributes);
}
// LastFM's Custom Call
await MusicKitInterop.modifyNamesOnLocale();
if (trackFilter || !app.cfg.lastfm.filterLoop) {
global.ipcRenderer.send('nowPlayingItemDidChangeLastFM', attributes);
} else if (attributes.name !== 'no-title-found' && attributes.playParams.id !== "no-id-found") {
global.ipcRenderer.send('lastfm:nowPlayingChange', attributes);
}
if (MusicKit.getInstance().nowPlayingItem) {
@ -49,7 +41,7 @@ const MusicKitInterop = {
})
MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => {
console.warn(`[mediaPlaybackError] ${e}`);
console.warn(`[cider:preload] mediaPlaybackError] ${e}`);
})
},
@ -59,28 +51,6 @@ const MusicKitInterop = {
});
},
async modifyNamesOnLocale() {
if (app.mklang === '' || app.mklang == null) {
return;
}
const mk = MusicKit.getInstance()
const nowPlayingItem = mk.nowPlayingItem;
if ((nowPlayingItem?._songId ?? nowPlayingItem?.songId) == null){
return;
}
const id = nowPlayingItem?._songId ?? (nowPlayingItem?.songId ?? nowPlayingItem?.id)
if (id != null && id !== -1) {
try{
const query = await mk.api.v3.music(`/v1${(((nowPlayingItem?._songId ?? nowPlayingItem?.songId) != null) && ((nowPlayingItem?._songId ?? nowPlayingItem?.songId) !== -1)) ? `/catalog/${mk.storefrontId}/` : `/me/library/`}songs/${id}?l=${app.mklang}`);
if (query?.data?.data[0]){
let attrs = query?.data?.data[0]?.attributes;
if (attrs?.name) { nowPlayingItem.attributes.name = attrs?.name ?? ''}
if (attrs?.albumName) { nowPlayingItem.attributes.albumName = attrs?.albumName ?? ''}
if (attrs?.artistName) { nowPlayingItem.attributes.artistName = attrs?.artistName ?? ''}
}} catch (e) { }
} else {}
},
getAttributes: function () {
const mk = MusicKit.getInstance()
const nowPlayingItem = mk.nowPlayingItem;
@ -156,19 +126,19 @@ const MusicKitInterop = {
// } catch (e) { }
// if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null)
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);
MusicKit.getInstance().skipToNextItem().then(r => console.debug(`[MusicKitInterop.next] Skipping to Next ${r}`));
MusicKit.getInstance().skipToNextItem().then(r => console.debug(`[cider:preload] [next] Skipping to Next ${r}`));
},
previous: () => {
// if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null)
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex);
MusicKit.getInstance().skipToPreviousItem().then(r => console.debug(`[MusicKitInterop.previous] Skipping to Previous ${r}`));
MusicKit.getInstance().skipToPreviousItem().then(r => console.debug(`[cider:preload] [previous] Skipping to Previous ${r}`));
}
}
process.once('loaded', () => {
console.debug("Setting ipcRenderer")
console.debug("[cider:preload] IPC Listeners Created!")
global.MusicKitInterop = MusicKitInterop;
});

View file

@ -68,6 +68,10 @@ Vue.component("sidebar-library-item", {
required: false,
default: "",
},
svgIconName: {
type: String,
required: false
},
cdClick: {
type: Function,
required: false,

View file

@ -832,6 +832,10 @@ const app = new Vue({
MusicKit.getInstance().videoContainerElement = document.getElementById("apple-music-video-player")
ipcRenderer.on('setStoreValue', (e, key, value) => {
app.cfg[key] = value
})
ipcRenderer.on('theme-update', async (event, arg) => {
await less.refresh(true, true, true)
self.setTheme(self.cfg.visual.theme, true)
@ -4423,37 +4427,6 @@ const app = new Vue({
}
app.modals.settings = true
},
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<p style="font-size: 8px"><i>(${app.getLz('term.authed')}: ${lfmAuthKey})</i></p>`;
element.onclick = app.LastFMDeauthorize;
});
},
fullscreen(flag) {
this.fullscreenState = flag;
if (flag) {

View file

@ -198,13 +198,13 @@
</div>
<div class="app-chrome-item" v-else>
<div class="top-nav-group">
<sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" page="home">
<sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" svg-icon-name="home" page="home">
</sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg"
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow"
page="listen_now"></sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" page="browse">
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse">
</sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" page="radio">
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" page="radio">
</sidebar-library-item>
</div>
</div>

View file

@ -51,6 +51,7 @@
<sidebar-library-item
:name="$root.getLz('home.title')"
svg-icon="./assets/feather/home.svg"
svg-icon-name="home"
page="home"
>
</sidebar-library-item>
@ -67,17 +68,20 @@
<sidebar-library-item
:name="$root.getLz('term.listenNow')"
svg-icon="./assets/feather/play-circle.svg"
svg-icon-name="listenNow"
page="listen_now"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.browse')"
svg-icon="./assets/feather/globe.svg"
svg-icon-name="browse"
page="browse"
>
</sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.radio')"
svg-icon="./assets/feather/radio.svg"
svg-icon-name="radio"
page="radio"
></sidebar-library-item>
</template>
@ -94,36 +98,42 @@
<sidebar-library-item
:name="$root.getLz('term.recentlyAdded')"
svg-icon="./assets/feather/plus-circle.svg"
svg-icon-name="recentlyAdded"
v-if="cfg.general.sidebarItems.recentlyAdded"
page="library-recentlyadded"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.songs')"
svg-icon="./assets/feather/music.svg"
svg-icon-name="songs"
v-if="cfg.general.sidebarItems.songs"
page="library-songs"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.albums')"
svg-icon="./assets/feather/disc.svg"
svg-icon-name="albums"
v-if="cfg.general.sidebarItems.albums"
page="library-albums"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.artists')"
svg-icon="./assets/feather/user.svg"
svg-icon-name="artists"
v-if="cfg.general.sidebarItems.artists"
page="library-artists"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.videos')"
svg-icon="./assets/feather/video.svg"
svg-icon-name="videos"
v-if="cfg.general.sidebarItems.videos"
page="library-videos"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.podcasts')"
svg-icon="./assets/feather/mic.svg"
svg-icon-name="podcasts"
v-if="cfg.general.sidebarItems.podcasts"
page="podcasts"
>

View file

@ -994,18 +994,18 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.enabled" switch />
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.enabled" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.clientName')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" v-model="app.cfg.general.discordrpc.client">
<select class="md-select" v-model="app.cfg.connectivity.discord_rpc.client">
<option value="Cider">{{$root.getLz('app.name')}}</option>
<option value="AppleMusic">{{$root.getLz('term.appleMusic')}}
</option>
@ -1014,40 +1014,40 @@
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.clearOnPause')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.clear_on_pause" switch />
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.clear_on_pause" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.hideButtons')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.hide_buttons" switch />
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.hide_buttons" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.hideTimestamp')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.hide_timestamp" switch />
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.hide_timestamp" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.detailsFormat')}}<br />
<small>{{$root.getLz('term.variables')}}: {artist}, {composer}, {title}, {album},
@ -1055,12 +1055,12 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text" v-model="app.cfg.general.discordrpc.details_format" />
<input type="text" v-model="app.cfg.connectivity.discord_rpc.details_format" />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.stateFormat')}}
<small>{{$root.getLz('term.variables')}}: {artist}, {composer}, {title}, {album},
@ -1068,12 +1068,12 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text" v-model="app.cfg.general.discordrpc.state_format" />
<input type="text" v-model="app.cfg.connectivity.discord_rpc.state_format" />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.reload')}}
</div>
@ -1090,50 +1090,54 @@
{{$root.getLz('settings.option.connectivity.lastfmScrobble')}}
</div>
<div class="md-option-segment md-option-segment_auto" >
<button class="md-btn" id="lfmConnect" ref="lfmConnect"
@click="app.LastFMAuthenticate()">
{{$root.getLz('term.connect')}}
<button class="md-btn" id="lfmConnect" @click="app.cfg.connectivity.lastfm.enabled ? lfmDisconnect() : lfmAuthorize()">
{{$root.getLz(`term.${$root.cfg.connectivity.lastfm.enabled ? "disconnect" : "connect"}`)}}<br>
<small>{{app.cfg.connectivity.lastfm.enabled ? `${$root.getLz('term.authed')}: ${$root.cfg.connectivity.lastfm.secrets.username}` : '' }}</small>
</button>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-line" v-show="lastfmConnecting">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.manualToken')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<form @submit.prevent="submitToken">
<input type="text" autofocus id="lfmToken" />
<input type="submit" class="md-btn" @value="$root.getLz('action.submit')" />
</form>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.delay')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="number" min="50" max="100" v-model="app.cfg.lastfm.scrobble_after" />
<input type="number" min="50" max="100" v-model="app.cfg.connectivity.lastfm.scrobble_after"/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.nowPlaying')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lastfm.NowPlaying" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.removeFeatured')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lastfm.enabledRemoveFeaturingArtists"
switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-line" v-show="app.cfg.connectivity.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterLoop')}}
<small>{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterLoop.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lastfm.filterLoop" switch />
<input type="checkbox" v-model="app.cfg.connectivity.lastfm.filter_loop" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterTypes')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" @change="filterChange" value="song">{{$root.getLz('term.songs')}}<br>
<input type="checkbox" @change="filterChange" value="musicVideo">{{$root.getLz('term.musicVideos')}}<br>
</label>
</div>
</div>
@ -1421,7 +1425,8 @@
app: this.$root,
themes: ipcRenderer.sendSync("get-themes"),
tabIndex: 0,
canChangeHash: false
canChangeHash: false,
lastfmConnecting: false
}
}, watch: {
tabIndex: function (val) {
@ -1430,15 +1435,6 @@
}
}
},
mounted: function () {
if (app.cfg.lastfm.enabled) {
const element = document.getElementById('lfmConnect');
if (element) {
element.innerHTML = `Disconnect\n<p style="font-size: 8px"><i>(Authed: ${app.cfg.lastfm.auth_token})</i></p>`;
element.onclick = app.LastFMDeauthorize;
}
}
},
methods: {
close() {
this.$root.modals.settings = false
@ -1554,6 +1550,41 @@
},
reloadDiscordRPC() {
ipcRenderer.send('reloadRPC')
},
lfmDisconnect() {
this.$root.cfg.connectivity.lastfm.enabled = false;
this.$root.cfg.connectivity.lastfm.secrets.username = "";
this.$root.cfg.connectivity.lastfm.secrets.key = "";
ipcRenderer.send('lastfm:disconnect');
},
async lfmAuthorize() {
this.lastfmConnecting = true;
window.open(await ipcRenderer.invoke('lastfm:url'));
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connecting'));
/* Just a timeout for the button */
setTimeout(() => {
if (!this.$root.cfg.connectivity.lastfm.enabled) {
app.notyf.error(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectError'));
console.warn('[lastfm:authorize] Last.fm authorization timed out.');
this.lastfmConnecting = false;
}
}, 20000);
ipcRenderer.once('lastfm:authenticated', (_e, session) => {
this.$root.cfg.connectivity.lastfm.secrets.username = session.username
this.$root.cfg.connectivity.lastfm.secrets.key = session.key
this.$root.cfg.connectivity.lastfm.enabled = true
this.lastfmConnecting = false;
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectSuccess'));
})
},
filterChange(e) {
this.$root.cfg.connectivity.lastfm.filter_types[e.target.value] = e.target.checked;
},
submitToken() {
const token = document.getElementById('lfmToken').value;
ipcRenderer.send('lastfm:auth', token);
}
}
})

View file

@ -9,7 +9,7 @@
:href="item.href"
@click='clickEvent()'>
<template v-if="!renaming">
<svg-icon :url="icon"/> {{ item.attributes.name }}
<svg-icon :url="icon" name="sidebar-playlist"/> {{ item.attributes.name }}
<small class="presentNotice" v-if="hasRelatedMediaItems">(Track present)</small>
</template>
<input type="text" v-model="item.attributes.name" class="pl-rename-field" @blur="rename()" @keydown.enter="rename()" v-else>

View file

@ -123,7 +123,7 @@
<script type="text/x-template" id="sidebar-library-item">
<button class="app-sidebar-item"
:class="$parent.getSidebarItemClass(page)" @click="$root.setWindowHash(page)">
<svg-icon :url="svgIconData" v-if="svgIconData != ''" />
<svg-icon :url="svgIconData" :name="'sidebar-' + svgIconName" v-if="svgIconData != ''" />
<span class="sidebar-item-text">{{ name }}</span>
</button>
</script>

View file

@ -954,18 +954,18 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.enabled" switch/>
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.enabled" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.clientName')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" v-model="app.cfg.general.discordrpc.client">
<select class="md-select" v-model="app.cfg.connectivity.discord_rpc.client">
<option value="Cider">{{$root.getLz('app.name')}}</option>
<option value="AppleMusic">{{$root.getLz('term.appleMusic')}}
</option>
@ -974,40 +974,40 @@
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.clearOnPause')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.clear_on_pause" switch/>
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.clear_on_pause" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.hideButtons')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.hide_buttons" switch/>
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.hide_buttons" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.hideTimestamp')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.discordrpc.hide_timestamp" switch/>
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.hide_timestamp" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.detailsFormat')}}<br/>
<small>{{$root.getLz('term.variables')}}: {artist}, {composer}, {title}, {album},
@ -1015,12 +1015,12 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text" v-model="app.cfg.general.discordrpc.details_format"/>
<input type="text" v-model="app.cfg.connectivity.discord_rpc.details_format"/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.stateFormat')}}
<small>{{$root.getLz('term.variables')}}: {artist}, {composer}, {title}, {album},
@ -1028,12 +1028,12 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text" v-model="app.cfg.general.discordrpc.state_format"/>
<input type="text" v-model="app.cfg.connectivity.discord_rpc.state_format"/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.reload')}}
</div>
@ -1050,50 +1050,41 @@
{{$root.getLz('settings.option.connectivity.lastfmScrobble')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" id="lfmConnect" ref="lfmConnect"
@click="app.LastFMAuthenticate()">
{{$root.getLz('term.connect')}}
<button class="md-btn" id="lfmConnect" @click="app.cfg.connectivity.lastfm.enabled ? lfmDisconnect() : lfmAuthorize()">
{{$root.getLz(`term.${$root.cfg.connectivity.lastfm.enabled ? "disconnect" : "connect"}`)}}<br>
<small>{{app.cfg.connectivity.lastfm.enabled ? `${$root.getLz('term.authed')}: ${$root.cfg.connectivity.lastfm.secrets.username}` : '' }}</small>
</button>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-line" v-show="app.cfg.connectivity.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.delay')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="number" min="50" max="100" v-model="app.cfg.lastfm.scrobble_after"/>
<input type="number" min="50" max="100" v-model="app.cfg.connectivity.lastfm.scrobble_after"/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.nowPlaying')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lastfm.NowPlaying" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.removeFeatured')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lastfm.enabledRemoveFeaturingArtists"
switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.lastfm.enabled">
<div class="md-option-line" v-show="app.cfg.connectivity.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterLoop')}}
<small>{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterLoop.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lastfm.filterLoop" switch/>
<input type="checkbox" v-model="app.cfg.connectivity.lastfm.filter_loop" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterTypes')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" @change="filterChange" value="song">{{$root.getLz('term.songs')}}<br>
<input type="checkbox" @change="filterChange" value="musicVideo">{{$root.getLz('term.musicVideos')}}<br>
</label>
</div>
</div>
@ -1390,14 +1381,6 @@
this.canChangeHash = true
}
})
if (app.cfg.lastfm.enabled) {
const element = document.getElementById('lfmConnect');
if (element) {
element.innerHTML = `Disconnect\n<p style="font-size: 8px"><i>(Authed: ${app.cfg.lastfm.auth_token})</i></p>`;
element.onclick = app.LastFMDeauthorize;
}
}
},
methods: {
windowBgStyleChange() {
@ -1511,6 +1494,35 @@
},
reloadDiscordRPC() {
ipcRenderer.send('reloadRPC')
},
lfmDisconnect() {
this.$root.cfg.connectivity.lastfm.enabled = false;
this.$root.cfg.connectivity.lastfm.secrets.username = "";
this.$root.cfg.connectivity.lastfm.secrets.key = "";
ipcRenderer.send('lastfm:disconnect');
},
async lfmAuthorize() {
window.open(await ipcRenderer.invoke('lastfm:url'));
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connecting'));
/* Just a timeout for the button */
setTimeout(() => {
if (document.getElementById('lfmConnect').innerText === app.getLz('term.connecting') + '...') {
app.notyf.error(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectError'));
console.warn('[lastfm:authorize] Last.fm authorization timed out.');
}
}, 20000);
ipcRenderer.once('lastfm:authenticated', (_e, session) => {
this.$root.cfg.connectivity.lastfm.secrets.username = session.username
this.$root.cfg.connectivity.lastfm.secrets.key = session.key
this.$root.cfg.connectivity.lastfm.enabled = true
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectSuccess'));
})
},
filterChange(e) {
this.$root.cfg.connectivity.lastfm.filter_types[e.target.value] = e.target.checked;
}
}
})