Merge branch 'ciderapp:develop' into develop
This commit is contained in:
commit
4105f84e6f
26 changed files with 872 additions and 433 deletions
|
@ -4,7 +4,18 @@ import * as windowStateKeeper from "electron-window-state";
|
|||
import * as express from "express";
|
||||
import * as getPort from "get-port";
|
||||
import {search} from "youtube-search-without-api-key";
|
||||
import {existsSync, rmSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync} from "fs";
|
||||
import {
|
||||
existsSync,
|
||||
rmSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
statSync,
|
||||
unlinkSync,
|
||||
rmdirSync,
|
||||
lstatSync
|
||||
} from "fs";
|
||||
import {Stream} from "stream";
|
||||
import {networkInterfaces} from "os";
|
||||
import * as mm from 'music-metadata';
|
||||
|
@ -46,6 +57,7 @@ export class BrowserWindow {
|
|||
"pages/library-artists",
|
||||
"pages/browse",
|
||||
"pages/settings",
|
||||
"pages/installed-themes",
|
||||
"pages/listen_now",
|
||||
"pages/home",
|
||||
"pages/artist-feed",
|
||||
|
@ -178,6 +190,10 @@ export class BrowserWindow {
|
|||
page: "settings",
|
||||
component: `<cider-settings></cider-settings>`,
|
||||
condition: `page == 'settings'`
|
||||
}, {
|
||||
page: "installed-themes",
|
||||
component: `<installed-themes></installed-themes>`,
|
||||
condition: `page == 'installed-themes'`
|
||||
}, {
|
||||
page: "search",
|
||||
component: `<cider-search :search="search"></cider-search>`,
|
||||
|
@ -256,8 +272,10 @@ export class BrowserWindow {
|
|||
},
|
||||
};
|
||||
|
||||
public static watcher: any;
|
||||
|
||||
StartWatcher(path: string) {
|
||||
const watcher = watch(path, {
|
||||
BrowserWindow.watcher = watch(path, {
|
||||
ignored: /[\/\\]\./,
|
||||
persistent: true
|
||||
});
|
||||
|
@ -267,7 +285,7 @@ export class BrowserWindow {
|
|||
}
|
||||
|
||||
// Declare the listeners of the watcher
|
||||
watcher
|
||||
BrowserWindow.watcher
|
||||
.on('add', function (path: string) {
|
||||
// console.log('File', path, 'has been added');
|
||||
})
|
||||
|
@ -294,6 +312,10 @@ export class BrowserWindow {
|
|||
});
|
||||
}
|
||||
|
||||
async StopWatcher() {
|
||||
await BrowserWindow.watcher.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the browser window
|
||||
* @generator
|
||||
|
@ -698,6 +720,50 @@ export class BrowserWindow {
|
|||
};
|
||||
})
|
||||
|
||||
ipcMain.handle("uninstall-theme", async (event, path) => {
|
||||
await this.StopWatcher()
|
||||
const themesDir = utils.getPath("themes")
|
||||
// validate the path is in the themes directory
|
||||
try {
|
||||
if (path.startsWith(themesDir)) {
|
||||
// get last dir in path, can be either / or \ and may have a trailing slash
|
||||
const themeName = path.split(/[\\\/]/).pop()
|
||||
if (themeName == "Themes" || themeName == "themes") {
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
status: 3
|
||||
});
|
||||
return
|
||||
}
|
||||
// if path is directory, delete it
|
||||
if (lstatSync(path).isDirectory()) {
|
||||
await rmdirSync(path, {recursive: true});
|
||||
} else {
|
||||
// if path is file, delete it
|
||||
await unlinkSync(path);
|
||||
}
|
||||
// return the path
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
status: 0
|
||||
});
|
||||
} else {
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
status: 1
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
message: e.message,
|
||||
status: 2
|
||||
});
|
||||
}
|
||||
|
||||
this.StartWatcher(utils.getPath('themes'))
|
||||
})
|
||||
|
||||
ipcMain.handle("reinstall-widevine-cdm", () => {
|
||||
// remove WidevineCDM from appdata folder
|
||||
const widevineCdmPath = join(app.getPath("userData"), "./WidevineCdm");
|
||||
|
@ -813,7 +879,7 @@ export class BrowserWindow {
|
|||
} else if (statSync(join(utils.getPath("themes"), file)).isDirectory()) {
|
||||
let subFiles = readdirSync(join(utils.getPath("themes"), file));
|
||||
for (let subFile of subFiles) {
|
||||
if (subFile.endsWith(".less")) {
|
||||
if (subFile.endsWith("index.less")) {
|
||||
themes.push(join(file, subFile));
|
||||
}
|
||||
}
|
||||
|
@ -832,15 +898,20 @@ export class BrowserWindow {
|
|||
themePath = themePath.slice(0, -10);
|
||||
}
|
||||
if (existsSync(join(themePath, "theme.json"))) {
|
||||
let themeJson = JSON.parse(readFileSync(join(themePath, "theme.json"), "utf8"));
|
||||
themeObjects.push({
|
||||
name: themeJson.name || themeName,
|
||||
description: themeJson.description || themeDescription,
|
||||
path: themePath,
|
||||
file: theme,
|
||||
github_repo: themeJson.github_repo || "",
|
||||
commit: themeJson.commit || ""
|
||||
});
|
||||
try {
|
||||
let themeJson = JSON.parse(readFileSync(join(themePath, "theme.json"), "utf8"));
|
||||
themeObjects.push({
|
||||
name: themeJson.name || themeName,
|
||||
description: themeJson.description || themeDescription,
|
||||
path: themePath,
|
||||
file: theme,
|
||||
github_repo: themeJson.github_repo || "",
|
||||
commit: themeJson.commit || "",
|
||||
pack: themeJson.pack || false,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
themeObjects.push({
|
||||
name: themeName,
|
||||
|
@ -848,7 +919,8 @@ export class BrowserWindow {
|
|||
path: themePath,
|
||||
file: theme,
|
||||
github_repo: "",
|
||||
commit: ""
|
||||
commit: "",
|
||||
pack: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -971,6 +1043,11 @@ export class BrowserWindow {
|
|||
BrowserWindow.win.setResizable(!lock);
|
||||
});
|
||||
|
||||
// Move window
|
||||
ipcMain.on("windowmove", (_event, x, y) => {
|
||||
BrowserWindow.win.setBounds({x, y});
|
||||
});
|
||||
|
||||
//Fullscreen
|
||||
ipcMain.on('setFullScreen', (_event, flag) => {
|
||||
BrowserWindow.win.setFullScreen(flag)
|
||||
|
@ -1215,7 +1292,7 @@ export class BrowserWindow {
|
|||
shell.openPath(app.getPath('userData'));
|
||||
});
|
||||
|
||||
|
||||
|
||||
//#region Cider Connect
|
||||
ipcMain.on('cc-auth', (_event) => {
|
||||
shell.openExternal(String(utils.getStoreValue('cc_authURL')));
|
||||
|
@ -1279,7 +1356,7 @@ export class BrowserWindow {
|
|||
BrowserWindow.win.webContents.executeJavaScript(`
|
||||
window.localStorage.setItem("currentTrack", JSON.stringify(app.mk.nowPlayingItem));
|
||||
window.localStorage.setItem("currentTime", JSON.stringify(app.mk.currentPlaybackTime));
|
||||
window.localStorage.setItem("currentQueue", JSON.stringify(app.mk.queue.items));
|
||||
window.localStorage.setItem("currentQueue", JSON.stringify(app.mk.queue._unplayedQueueItems));
|
||||
ipcRenderer.send('stopGCast','');`)
|
||||
BrowserWindow.win.destroy();
|
||||
}
|
||||
|
|
|
@ -52,11 +52,11 @@ export class Store {
|
|||
"keybindings": {
|
||||
"search": [
|
||||
process.platform == "darwin" ? "Command" : "Control",
|
||||
"S"
|
||||
"F"
|
||||
],
|
||||
"albums": [
|
||||
process.platform == "darwin" ? "Command" : "Control",
|
||||
"F"
|
||||
"S"
|
||||
],
|
||||
"artists": [
|
||||
process.platform == "darwin" ? "Command" : "Control",
|
||||
|
@ -123,6 +123,8 @@ export class Store {
|
|||
"quality": "HIGH",
|
||||
"seamless_audio": true,
|
||||
"normalization": false,
|
||||
"dBSPL": false,
|
||||
"dBSPLcalibration": 90,
|
||||
"maikiwiAudio": {
|
||||
"ciderPPE": false,
|
||||
"ciderPPE_value": "MAIKIWI",
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
import * as RPC from 'discord-rpc'
|
||||
import {AutoClient} from 'discord-auto-rpc'
|
||||
import {ipcMain} from "electron";
|
||||
import fetch from 'electron-fetch'
|
||||
|
||||
export default class DiscordRPC {
|
||||
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _app: any;
|
||||
private _attributes: any;
|
||||
private _connection: boolean = false;
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'Discord Rich Presence';
|
||||
public description: string = 'Discord RPC plugin for Cider';
|
||||
public version: string = '1.0.0';
|
||||
public version: string = '1.1.0';
|
||||
public author: string = 'vapormusic/Core (Cider Collective)';
|
||||
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _attributes: any;
|
||||
private ready: boolean = false;
|
||||
|
||||
/**
|
||||
* Plugin Initialization
|
||||
*/
|
||||
private _client: any = null;
|
||||
private _activity: RPC.Presence = {
|
||||
private _activityCache: any = {
|
||||
details: '',
|
||||
state: '',
|
||||
largeImageKey: '',
|
||||
|
@ -34,15 +33,72 @@ export default class DiscordRPC {
|
|||
instance: false
|
||||
};
|
||||
|
||||
private _activityCache: RPC.Presence = {
|
||||
details: '',
|
||||
state: '',
|
||||
largeImageKey: '',
|
||||
largeImageText: '',
|
||||
smallImageKey: '',
|
||||
smallImageText: '',
|
||||
instance: false
|
||||
};
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(_win: any): void {
|
||||
const self = this
|
||||
this.connect();
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
ipcMain.on('updateRPCImage', (_event, imageurl) => {
|
||||
if (!this._utils.getStoreValue("general.privateEnabled")) {
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
|
||||
method: 'POST',
|
||||
body: JSON.stringify({url: imageurl}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': _win.webContents.getUserAgent()
|
||||
},
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url
|
||||
self.setActivity(self._attributes)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.setActivity(attributes)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.setActivity(attributes)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
|
@ -58,58 +114,86 @@ export default class DiscordRPC {
|
|||
}
|
||||
const clientId = this._utils.getStoreValue("general.discordrpc.client") === "Cider" ? '911790844204437504' : '886578863147192350';
|
||||
|
||||
// Apparently needed for ask to join, join, spectate etc.
|
||||
RPC.register(clientId)
|
||||
|
||||
// Create the client
|
||||
this._client = new RPC.Client({transport: "ipc"});
|
||||
this._client = new AutoClient({transport: "ipc"});
|
||||
|
||||
// Runs on Ready
|
||||
this._client.on('ready', () => {
|
||||
this._client.once('ready', () => {
|
||||
console.info(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${this._client.user.id}.`);
|
||||
|
||||
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
|
||||
console.info(`[DiscordRPC][connect] Restoring activity cache.`);
|
||||
this._client.setActivity(this._activityCache)
|
||||
}
|
||||
})
|
||||
|
||||
// Handles Errors
|
||||
this._client.on('error', (err: any) => {
|
||||
console.error(`[DiscordRPC] ${err}`);
|
||||
this.disconnect()
|
||||
});
|
||||
|
||||
// If Discord is closed, allow reconnecting
|
||||
this._client.transport.once('close', () => {
|
||||
console.info(`[DiscordRPC] Connection closed`);
|
||||
this.disconnect()
|
||||
});
|
||||
|
||||
// Login to Discord
|
||||
this._client.login({clientId})
|
||||
this._client.endlessLogin({clientId: clientId})
|
||||
.then(() => {
|
||||
this._connection = true;
|
||||
this.ready = true
|
||||
})
|
||||
.catch((e: any) => console.error(`[DiscordRPC][connect] ${e}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from Discord RPC
|
||||
* Sets the activity
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
private disconnect() {
|
||||
private setActivity(attributes: any) {
|
||||
if (!this._client) {
|
||||
return
|
||||
}
|
||||
|
||||
this._client.destroy().then(() => {
|
||||
this._connection = false;
|
||||
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
|
||||
}).catch((e: any) => console.error(`[DiscordRPC][disconnect] ${e}`));
|
||||
// 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"),
|
||||
largeImageKey: attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024'),
|
||||
largeImageText: attributes.albumName,
|
||||
instance: false // Whether the activity is in a game session
|
||||
}
|
||||
|
||||
// Clean up, allow creating a new connection
|
||||
this._client = null;
|
||||
// Filter the activity
|
||||
activity = this.filterActivity(activity, attributes)
|
||||
|
||||
if (!this.ready) {
|
||||
this._activityCache = activity
|
||||
return
|
||||
}
|
||||
|
||||
// Set the activity
|
||||
if (!attributes.status && this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
|
||||
this._client.clearActivity()
|
||||
} else if (activity && this._activityCache !== activity) {
|
||||
this._client.setActivity(activity)
|
||||
}
|
||||
this._activityCache = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the Discord activity object
|
||||
*/
|
||||
private static filterActivity(activity: any, attributes: any): Object {
|
||||
private filterActivity(activity: any, attributes: any): Object {
|
||||
|
||||
// Add the buttons if people want them
|
||||
if (!this._utils.getStoreValue("general.discordrpc.hide_buttons")) {
|
||||
activity.buttons = [
|
||||
{label: 'Listen on Cider', url: attributes.url.cider},
|
||||
{label: 'View on Apple Music', url: attributes.url.appleMusic}
|
||||
] //To change attributes.url => preload/cider-preload.js
|
||||
}
|
||||
|
||||
// Add the timestamp if its playing
|
||||
if (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")) {
|
||||
activity.smallImageKey = attributes.status ? 'play' : 'pause';
|
||||
activity.smallImageText = attributes.status ? 'Playing' : 'Paused';
|
||||
}
|
||||
|
||||
/**
|
||||
* Works with:
|
||||
|
@ -173,138 +257,4 @@ export default class DiscordRPC {
|
|||
}
|
||||
return activity
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity
|
||||
* @param {activity} activity
|
||||
*/
|
||||
private setActivity(activity: any) {
|
||||
if (!this._connection || !this._client || !activity) {
|
||||
return
|
||||
}
|
||||
|
||||
// Filter the activity
|
||||
activity = DiscordRPC.filterActivity(activity, this._attributes)
|
||||
|
||||
// Set the activity
|
||||
if (!this._attributes.status && this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
|
||||
this._client.clearActivity()
|
||||
} else if (this._activity && this._activityCache !== this._activity && this._activity.details) {
|
||||
this._client.setActivity(activity)
|
||||
this._activityCache = this._activity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity of the client
|
||||
* @param {object} attributes
|
||||
*/
|
||||
private updateActivity(attributes: any) {
|
||||
if (!this._utils.getStoreValue("general.discordrpc.enabled") || this._utils.getStoreValue("general.privateEnabled")) {
|
||||
return
|
||||
} else if (!this._client || !this._connection) {
|
||||
this.connect()
|
||||
}
|
||||
|
||||
// Check if show buttons is (true) or (false)
|
||||
this._activity = {
|
||||
details: this._utils.getStoreValue("general.discordrpc.details_format"),
|
||||
state: this._utils.getStoreValue("general.discordrpc.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
|
||||
}
|
||||
|
||||
// Add the buttons if people want them
|
||||
if (!this._utils.getStoreValue("general.discordrpc.hide_buttons")) {
|
||||
this._activity.buttons = [
|
||||
{label: 'Listen on Cider', url: attributes.url.cider},
|
||||
{label: 'View on Apple Music', url: attributes.url.appleMusic}
|
||||
] //To change attributes.url => preload/cider-preload.js
|
||||
}
|
||||
|
||||
// Add the timestamp if its playing
|
||||
if (attributes.status) {
|
||||
this._activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime)
|
||||
this._activity.endTimestamp = attributes.endTime
|
||||
}
|
||||
|
||||
// If the user wants to keep the activity when paused
|
||||
if (!this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
|
||||
this._activity.smallImageKey = attributes.status ? 'play' : 'pause';
|
||||
this._activity.smallImageText = attributes.status ? 'Playing' : 'Paused';
|
||||
}
|
||||
|
||||
this.setActivity(this._activity)
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: { getStore: () => any; getApp: () => any; }) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
this._app = utils.getApp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(_win: any): void {
|
||||
let self = this
|
||||
this.connect();
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
ipcMain.on('updateRPCImage', (_event, imageurl) => {
|
||||
if (!this._utils.getStoreValue("general.privateEnabled")) {
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
|
||||
method: 'POST',
|
||||
body: JSON.stringify({url: imageurl}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': _win.webContents.getUserAgent()
|
||||
},
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url
|
||||
self.updateActivity(self._attributes)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
if (this._client) {
|
||||
this.disconnect()
|
||||
}
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.updateActivity(attributes)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.updateActivity(attributes)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ export default class RAOP {
|
|||
this.portairplay = ipport;
|
||||
this.device = this.airtunes.add(ipv4, {
|
||||
port: ipport,
|
||||
volume: 60,
|
||||
volume: 50,
|
||||
password: sepassword,
|
||||
txt: txt
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue