get real. (#1321)
* test * fix multiroom * attempt for fix rare cast bug * changes for local files: read below * added pouchdb-node * moved all logic for local files to src/main/providers/local * added new local library section on sidebar * removed dupe * added caching headers * fix * add path menu to settings-window * fix mxm for local * some test * some fix * clear this * clear log * add playlist folder class * sometest * fix * Auto focus search bar * use object instead of array for albums (#1219) * feat: 🌐 Update French language (#1218) * add pagination for library songs * Add 'go to' page * Make playlist search icon use hero color * Merge pull request #1 from vapormusic/patch-1 dont populate out-of-display doms * both infinite and paging * Add color to search button background * Add shadow to follow button * im dumb sorry * Add brightness effect of search button * add some delay to scroll v-observe-visibility * some multiroom fixes * somefixes * [Audio] Fix VBass & Added COCS * [Audio] COCS revision * some fix * fix m1 mac * fix m1 build * some fix regarding audiocontext * [Audio] Fix #1207 (discussions) Eliminates audio stuttering even when AudioContext is enabled. Make lyrics account for the extra latency introduced by Audio Lab. * idk what I did * apparently this no worki * should default to 0 * expose bitrate of localfiles * [Audio] Added CAP & Normalization behavior for local files * smh musickit * Expose more local files metadata + fix norm error * pokemon gotta catch them all * maikiwi is a clown confirmed * pokemon gotta catch them all v2 * pokemon gotta catch them all v5 * Update zh_TW.json (#1229) Fix errors, update zh-TW translation. * Change No Lyrics Message in Full Screen Player (#1210) * Modify term.noLyrics text in 5 files * Minor change in 3 files * Colorize sponsor buttons in about page Co-authored-by: ctaetcsh <48845980+ctaetcsh@users.noreply.github.com> * max size of more info panel dynamic * Add twitter social button for some developers in about page (#1232) * Big Chungus Window settings (#1230) * Initial Changes * I suposse this is the last * cryptofyre * Documentation * Revert "Big Chungus Window settings (#1230)" (#1233) This reverts commite133b2c38b
. * test * Just changed it and gonna leave for debugging * reimpe133b2c
, adjustments for macOS * resolve #1 * copied files * readded strings to en_US * adjustments to settings sidebar collapse * add multiplex * Update version.sh * Update style.css * dont break pls thx * swag * I forgot that (#1239) * Update zh_TW.json (#1236) Update TW language. * Update zh_CN.json (#1240) * because I can * Fix cider team buttons in about page * Update style.less * Float right looks better * support custom port via optional CIDER_PORT var * Scan Local Files now 46 times faster * Update .gitignore * cleanups * remove console time (1s/1600 tracks) * fix m1 * sometest * Update build-macos.yml * Fix absolutely unacceptable punctuation error (#1249) * ok * pray * revert mxm back to local * Update zh_CN.json (#1242) * Update de_DE.json (#1246) * Musixmatch fix (thx plank ily) * whoops * yes * whoops v2 * bump to e18.3.5 * don't give the lyric api id if local files * Update afterPack.js * ukie * Update afterPack.js * Update afterPack.js * how the fuck did this not throw an error * fix stupid svg smh * new mediaitem scaling method * added Maximum Element Scale * mediaitem square artwork res now adapts to window size * will not affect high dpi * fix now playing artwork * fix album genre names before: gets genre from 1st track now: gets genre from album data * clamped element scale to 1.5x * added caching for auth * adjustment to artist page * changed to v-show for list item vis * Bring back mxm lyrics (Fully tested) * fix mmx translation * fix settings view * remove useless args & nonexistent funcs * fix div hell * fixed missing end tag for local playlist * fix divs * Fix cider list * the amount of brain cells that I have lost because of mxm trans * WIP language filter * it was fun while it lasted, goodbye MXM languages * MXM changes * 4am code moments * move logic * fix lyrics translation & allow other lang * default store.ts mxm trans to be disabled * this is why you dont code at 4am * and this is why you should test your code before pushing * added Romanized langs to mxm trans * love how previous MXM changes are in vain till now * stop. uploading. this. * NEVER. CODE. AT. FOUR. AYE. EM. AGAIN. * test * Revert "test" This reverts commit256d06bbcc
. * did a funny * did another funny * yes * add prime symbol to apostrophe * Don't do anything if res != 200 * Recursive Folder Search in Cider Utils * 4am code moment * fix function (force recompile utils) * did a funny there * I need sleep * Update zh_CN.json (#1265) This is a big work * Update zh_TW.json (#1260) Update TW language. * performant logging is enabled by default * test getting rid of lyriccurrenttime * set timeout can go away now * boops forgot this * mxm moms * mxm moms * Revert "mxm moms" This reverts commit51fc09280e
. * README download link fix * Update vueapp.js * Fix settings menu (#1271) * remove unused pages * profile page (for search for now) * lol * updated recordLabel with i18n and root usage * Local Lossless Icon and more - add Cider-profile boilerplate - add local lossless icon -add hover for PPE and lossless Co-authored-by: Core <coredev-uk@users.noreply.github.com> * no coding at 4am * fix units for local lossless badge * fix units for local lossless badge * remove CAP icon for local files, cuz unsupported * mpris overhaul * Fix seeking in mpris * changed am section on sidebar to v-show allowing css manipulation * added class for css * new effect when entering fullscreen lyrics * fixes text wrapping on tab text * added is-album * linux is cooollll * Lyrics API migration * Update musickit to use api mirror by default * add div for app-playback-buttons * Add spatialization icon (#1276) * Add spatialization icon * that never happened * add checkmode func to webremote * whoops sorry * Updated config.yml * mogus * fix string matching * may Maikiwi bless your CI * MKV3 red * Go touch grass; * this was so unreadable lmao * add logic for showing spatialization icon * add space in lossless icon * Remove dead fallback token, add error log if capi call fails (#1289) I tested and the fallback key you are using is dead. 401s. Unusable. * Fix #1282 * Fix #1237 * fix default CAP * someone played with translation code * lmao yaz why * stop polluting my logs you lil POST * I18n (#1293) * Update es_ES * I18N * idk this only breaking now * added framework for c2 parity * fixes * Fix volume bar on miniplayer (#1297) * Update stale-issues.yml * removed loading bar, testing without hlscider * overwrite restriction * allow listennow "more like" nav to work * garbage gone * fix for primary-content linking * Update README.md Add QQ group info * Revert back to music metadata * gimp v2 * remove local files as experiment * just to be safe * world is now a better place * meltdown avoided * meltdown avoided * Revert "meltdown avoided" This reverts commit38e6f1b7fa
. * Revert "meltdown avoided" This reverts commit54cc6656d6
. * Revert "world is now a better place" This reverts commitc019bf9c63
. * remove quasar * add some shiz (#1313) * Update ru_RU.json keeping russian lang actual * ok * Add gradient to lyric-footer * *Commit en español Ñ (#1304) * i hate my life (#1307) * world is now a better place * meltdown avoided * meltdown avoided * stylize new listen now childs * full scale artwork, finally * dynamic width for search categories * hd all album work * Update afterPack.js * force hq quality * oops * attempt to fix * misc cleanup * why what * what was i thinking * fix duplicated text in listen now childs * Paginate/infinite scroll for albums, playlists (#1234) * Infinite scroll, pagination to album, playlists * move pagination below tracks * Make page size configurable * remove renderer * Mitigate songs / album slow app issue. * add ratings, library change to web remote (#1285) * Add compact artist header option (#1308) * Support compact artist header (optional) * Add required term Co-authored-by: h0ckerman <35598335+h0ckerman@users.noreply.github.com> Co-authored-by: vapormusic <vietanhfat@gmail.com> Co-authored-by: Monochromish <chillygamer7@gmail.com> Co-authored-by: Gabriel Davila <56521591+mefsaal@users.noreply.github.com> Co-authored-by: Core <64542347+coredev-uk@users.noreply.github.com> Co-authored-by: Maikiwi <stella@mai.kiwi> Co-authored-by: yazninja <yazlesean@gmail.com> Co-authored-by: booploops <49113086+booploops@users.noreply.github.com> Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Co-authored-by: Pedro Galhardo <pgalhardo@icloud.com> * obama (#1314) * Update ru_RU.json keeping russian lang actual * ok * Add gradient to lyric-footer * *Commit en español Ñ (#1304) * i hate my life (#1307) * world is now a better place * meltdown avoided * meltdown avoided * stylize new listen now childs * full scale artwork, finally * dynamic width for search categories * hd all album work * Update afterPack.js * force hq quality * oops * attempt to fix * misc cleanup * why what * what was i thinking * fix duplicated text in listen now childs * Paginate/infinite scroll for albums, playlists (#1234) * Infinite scroll, pagination to album, playlists * move pagination below tracks * Make page size configurable * remove renderer * Mitigate songs / album slow app issue. * add ratings, library change to web remote (#1285) * Add compact artist header option (#1308) * Support compact artist header (optional) * Add required term * improve pagination styling Co-authored-by: h0ckerman <35598335+h0ckerman@users.noreply.github.com> Co-authored-by: vapormusic <vietanhfat@gmail.com> Co-authored-by: Monochromish <chillygamer7@gmail.com> Co-authored-by: Gabriel Davila <56521591+mefsaal@users.noreply.github.com> Co-authored-by: Core <64542347+coredev-uk@users.noreply.github.com> Co-authored-by: Maikiwi <stella@mai.kiwi> Co-authored-by: yazninja <yazlesean@gmail.com> Co-authored-by: booploops <49113086+booploops@users.noreply.github.com> Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Co-authored-by: Pedro Galhardo <pgalhardo@icloud.com> Co-authored-by: yazninja <71800112+yazninja@users.noreply.github.com> * obama episode 2. (#1317) * Update ru_RU.json keeping russian lang actual * ok * Add gradient to lyric-footer * *Commit en español Ñ (#1304) * i hate my life (#1307) * world is now a better place * meltdown avoided * meltdown avoided * stylize new listen now childs * full scale artwork, finally * dynamic width for search categories * hd all album work * Update afterPack.js * force hq quality * oops * attempt to fix * misc cleanup * why what * what was i thinking * fix duplicated text in listen now childs * Paginate/infinite scroll for albums, playlists (#1234) * Infinite scroll, pagination to album, playlists * move pagination below tracks * Make page size configurable * remove renderer * Mitigate songs / album slow app issue. * add ratings, library change to web remote (#1285) * Add compact artist header option (#1308) * Support compact artist header (optional) * Add required term * improve pagination styling * Disable Fullscreen view when artist/album name is clicked. (#1315) * Disable Fullscreen view when artist/album name is clicked. idk why this change didn't exist * Seperate dash from album name * Replace `$root.showSearch()` with `app.appRoute('search')` `$root.showSearch()` prevents going back to previous page from sidebar. * Fix Anim (#1316) Co-authored-by: h0ckerman <35598335+h0ckerman@users.noreply.github.com> Co-authored-by: vapormusic <vietanhfat@gmail.com> Co-authored-by: Monochromish <chillygamer7@gmail.com> Co-authored-by: Gabriel Davila <56521591+mefsaal@users.noreply.github.com> Co-authored-by: Core <64542347+coredev-uk@users.noreply.github.com> Co-authored-by: Maikiwi <stella@mai.kiwi> Co-authored-by: yazninja <yazlesean@gmail.com> Co-authored-by: booploops <49113086+booploops@users.noreply.github.com> Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Co-authored-by: Pedro Galhardo <pgalhardo@icloud.com> Co-authored-by: Monochromish <79590499+Monochromish@users.noreply.github.com> * re add that i guess. github is fucked. * aa (#1320) * Update ru_RU.json keeping russian lang actual * ok * Add gradient to lyric-footer * *Commit en español Ñ (#1304) * i hate my life (#1307) * world is now a better place * meltdown avoided * meltdown avoided * stylize new listen now childs * full scale artwork, finally * dynamic width for search categories * hd all album work * Update afterPack.js * force hq quality * oops * attempt to fix * misc cleanup * why what * what was i thinking * fix duplicated text in listen now childs * Paginate/infinite scroll for albums, playlists (#1234) * Infinite scroll, pagination to album, playlists * move pagination below tracks * Make page size configurable * remove renderer * Mitigate songs / album slow app issue. * add ratings, library change to web remote (#1285) * Add compact artist header option (#1308) * Support compact artist header (optional) * Add required term * improve pagination styling * Disable Fullscreen view when artist/album name is clicked. (#1315) * Disable Fullscreen view when artist/album name is clicked. idk why this change didn't exist * Seperate dash from album name * Replace `$root.showSearch()` with `app.appRoute('search')` `$root.showSearch()` prevents going back to previous page from sidebar. * Fix Anim (#1316) * make tracks tab active (#1318) * welp that wasn't it. * Thnks (#1319) * Thnks * i need sleep Co-authored-by: h0ckerman <35598335+h0ckerman@users.noreply.github.com> Co-authored-by: vapormusic <vietanhfat@gmail.com> Co-authored-by: Monochromish <chillygamer7@gmail.com> Co-authored-by: Gabriel Davila <56521591+mefsaal@users.noreply.github.com> Co-authored-by: Core <64542347+coredev-uk@users.noreply.github.com> Co-authored-by: Maikiwi <stella@mai.kiwi> Co-authored-by: yazninja <yazlesean@gmail.com> Co-authored-by: booploops <49113086+booploops@users.noreply.github.com> Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Co-authored-by: Pedro Galhardo <pgalhardo@icloud.com> Co-authored-by: Monochromish <79590499+Monochromish@users.noreply.github.com> Co-authored-by: vapormusic <vietanhfat@gmail.com> Co-authored-by: booploops <49113086+booploops@users.noreply.github.com> Co-authored-by: yazninja <yazlesean@gmail.com> Co-authored-by: Pedro Galhardo <pedromgalhardo@tecnico.ulisboa.pt> Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Co-authored-by: Erwan <24718500+ErwanGit@users.noreply.github.com> Co-authored-by: Monochromish <chillygamer7@gmail.com> Co-authored-by: maikirakiwi <stella@mai.kiwi> Co-authored-by: yazninja <71800112+yazninja@users.noreply.github.com> Co-authored-by: 宥叡 <46503943+jay900604@users.noreply.github.com> Co-authored-by: Nathan Ritchie <48845980+ctaetcsh@users.noreply.github.com> Co-authored-by: Monochromish <79590499+Monochromish@users.noreply.github.com> Co-authored-by: Gabriel Davila <56521591+mefsaal@users.noreply.github.com> Co-authored-by: Core <64542347+coredev-uk@users.noreply.github.com> Co-authored-by: 椎名アヤネ <53814845+sakura0224@users.noreply.github.com> Co-authored-by: Jonathan Fenske <929220+jfenske89@users.noreply.github.com> Co-authored-by: UnbreakCode <unbreakcode@gmail.com> Co-authored-by: SoNothing <git@sonothing.com> Co-authored-by: Core <coredev-uk@users.noreply.github.com> Co-authored-by: Amaru8 <52407090+Amaru8@users.noreply.github.com> Co-authored-by: rlaphoenix <pragma.exe@gmail.com> Co-authored-by: h0ckerman <35598335+h0ckerman@users.noreply.github.com> Co-authored-by: Pedro Galhardo <pgalhardo@icloud.com>
This commit is contained in:
parent
57b2a86913
commit
c03f408ba5
157 changed files with 33489 additions and 10407 deletions
|
@ -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
|
||||
|
@ -335,7 +332,7 @@ export class AppEvents {
|
|||
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n['action.tray.playpause'],
|
||||
label: this.i18n['term.playpause'],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript('MusicKitInterop.playPause()')
|
||||
}
|
||||
|
@ -343,7 +340,7 @@ export class AppEvents {
|
|||
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n['action.tray.next'],
|
||||
label: this.i18n['term.next'],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`)
|
||||
}
|
||||
|
@ -351,7 +348,7 @@ export class AppEvents {
|
|||
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n['action.tray.previous'],
|
||||
label: this.i18n['term.previous'],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`)
|
||||
}
|
||||
|
@ -372,7 +369,7 @@ export class AppEvents {
|
|||
}
|
||||
},
|
||||
{
|
||||
label: this.i18n['action.tray.quit'],
|
||||
label: this.i18n['term.quit'],
|
||||
click: () => {
|
||||
app.quit()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {join} from "path";
|
||||
import {app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen} from "electron";
|
||||
import { join } from "path";
|
||||
import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen, dialog } from "electron";
|
||||
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 { search } from "youtube-search-without-api-key";
|
||||
import {
|
||||
existsSync,
|
||||
rmSync,
|
||||
|
@ -16,19 +16,18 @@ import {
|
|||
rmdirSync,
|
||||
lstatSync,
|
||||
} from "fs";
|
||||
import {Stream} from "stream";
|
||||
import {networkInterfaces} from "os";
|
||||
import { Stream } from "stream";
|
||||
import { networkInterfaces } from "os";
|
||||
import * as mm from 'music-metadata';
|
||||
import fetch from 'electron-fetch'
|
||||
import {wsapi} from "./wsapi";
|
||||
import {utils} from './utils';
|
||||
import {Plugins} from "./plugins";
|
||||
import {watch} from "chokidar";
|
||||
import { wsapi } from "./wsapi";
|
||||
import { utils } from './utils';
|
||||
import { Plugins } from "./plugins";
|
||||
import { watch } from "chokidar";
|
||||
import * as os from "os";
|
||||
import wallpaper from "wallpaper";
|
||||
import * as AdmZip from "adm-zip";
|
||||
import * as path from 'path';
|
||||
const { readdir } = require('fs').promises;
|
||||
import { LocalFiles } from "../providers/local/";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -40,11 +39,11 @@ const { readdir } = require('fs').promises;
|
|||
export class BrowserWindow {
|
||||
public static win: any | undefined = null;
|
||||
private devMode: boolean = !app.isPackaged;
|
||||
public static express: any | undefined = null;
|
||||
|
||||
private audioStream: any = new Stream.PassThrough();
|
||||
private headerSent: any = false;
|
||||
private chromecastIP: any = [];
|
||||
private localSongs: any = [];
|
||||
private clientPort: number = 0;
|
||||
private remotePort: number = 6942;
|
||||
private EnvironmentVariables: object = {
|
||||
|
@ -53,6 +52,7 @@ export class BrowserWindow {
|
|||
dev: app.isPackaged,
|
||||
osRelease: os.release(),
|
||||
updatable: !process.windowsStore || !process.mas,
|
||||
useV3: utils.getStoreValue('advanced.experiments').includes("ampv3"),
|
||||
components: [
|
||||
"pages/podcasts",
|
||||
"pages/apple-account-settings",
|
||||
|
@ -63,8 +63,7 @@ export class BrowserWindow {
|
|||
"pages/browse",
|
||||
"pages/groupings",
|
||||
"pages/charts",
|
||||
"pages/settings",
|
||||
"pages/installed-themes",
|
||||
//"pages/installed-themes",
|
||||
"pages/listen_now",
|
||||
"pages/radio",
|
||||
"pages/home",
|
||||
|
@ -80,14 +79,16 @@ export class BrowserWindow {
|
|||
"pages/about",
|
||||
"pages/library-videos",
|
||||
"pages/remote-pair",
|
||||
"pages/themes-github",
|
||||
"pages/plugins-github",
|
||||
//"pages/themes-github",
|
||||
//"pages/plugins-github",
|
||||
"pages/replay",
|
||||
"pages/audiolabs",
|
||||
"pages/zoo",
|
||||
"pages/plugin-renderer",
|
||||
"pages/keybinds",
|
||||
"pages/oobe",
|
||||
"pages/cider-profile",
|
||||
"components/app-content",
|
||||
"components/sidebar",
|
||||
"components/mediaitem-artwork",
|
||||
"components/artwork-material",
|
||||
"components/menu-panel",
|
||||
|
@ -118,159 +119,174 @@ export class BrowserWindow {
|
|||
"components/fullscreen",
|
||||
"components/miniplayer",
|
||||
"components/castmenu",
|
||||
"components/pathmenu",
|
||||
"components/airplay-modal",
|
||||
"components/artist-chip",
|
||||
"components/hello-world",
|
||||
"components/inline-collection-list",
|
||||
"components/settings-window",
|
||||
"components/pagination",
|
||||
"components/settings-keybinds",
|
||||
"components/settings-themes",
|
||||
"components/settings-themes-github",
|
||||
"components/settings-plugins-github",
|
||||
],
|
||||
appRoutes: [
|
||||
{
|
||||
page: "library-recentlyadded",
|
||||
component: `<cider-recentlyadded></cider-recentlyadded>`,
|
||||
condition: "page == 'library-recentlyadded'"
|
||||
condition: "$root.page == 'library-recentlyadded'"
|
||||
},
|
||||
{
|
||||
page: "plugin-renderer",
|
||||
component: `<plugin-renderer></plugin-renderer>`,
|
||||
condition: "page == 'plugin-renderer'"
|
||||
condition: "$root.page == 'plugin-renderer'"
|
||||
},
|
||||
{
|
||||
page: "zoo",
|
||||
component: "<cider-zoo></cider-zoo>",
|
||||
condition: "page == 'zoo'"
|
||||
condition: "$root.page == 'zoo'"
|
||||
},
|
||||
{
|
||||
page: "podcasts",
|
||||
component: `<apple-podcasts></apple-podcasts>`,
|
||||
condition: `page == 'podcasts'`
|
||||
condition: `$root.page == 'podcasts'`
|
||||
}, {
|
||||
page: "library-videos",
|
||||
component: `<cider-library-videos></cider-library-videos>`,
|
||||
condition: `page == 'library-videos'`
|
||||
condition: `$root.page == 'library-videos'`
|
||||
}, {
|
||||
page: "apple-account-settings",
|
||||
component: `<apple-account-settings></apple-account-settings>`,
|
||||
condition: `page == 'apple-account-settings'`
|
||||
condition: `$root.page == 'apple-account-settings'`
|
||||
}, {
|
||||
page: "about",
|
||||
component: `<about-page></about-page>`,
|
||||
condition: `page == 'about'`
|
||||
condition: `$root.page == 'about'`
|
||||
}, {
|
||||
page: "cider-artist",
|
||||
component: `<cider-artist :data="artistPage.data"></cider-artist>`,
|
||||
condition: `page == 'artist-page' && artistPage.data.attributes`
|
||||
component: `<cider-artist :data="$root.artistPage.data"></cider-artist>`,
|
||||
condition: `$root.page == 'artist-page' && $root.artistPage.data.attributes`
|
||||
}, {
|
||||
page: "collection-list",
|
||||
component: `<cider-collection-list :data="collectionList.response" :type="collectionList.type" :title="collectionList.title"></cider-collection-list>`,
|
||||
condition: `page == 'collection-list'`
|
||||
component: `<cider-collection-list :data="$root.collectionList.response" :type="$root.collectionList.type" :title="$root.collectionList.title"></cider-collection-list>`,
|
||||
condition: `$root.page == 'collection-list'`
|
||||
}, {
|
||||
page: "home",
|
||||
component: `<cider-home></cider-home>`,
|
||||
condition: `page == 'home'`
|
||||
condition: `$root.page == 'home'`
|
||||
}, {
|
||||
page: "artist-feed",
|
||||
component: `<cider-artist-feed></cider-artist-feed>`,
|
||||
condition: `page == 'artist-feed'`
|
||||
condition: `$root.page == 'artist-feed'`
|
||||
}, {
|
||||
page: "playlist-inline",
|
||||
component: `<playlist-inline :data="showingPlaylist"></playlist-inline>`,
|
||||
condition: `modals.showPlaylist`
|
||||
component: `<playlist-inline :data="$root.showingPlaylist"></playlist-inline>`,
|
||||
condition: `$root.modals.showPlaylist`
|
||||
}, {
|
||||
page: "playlist_",
|
||||
component: `<cider-playlist :data="showingPlaylist"></cider-playlist>`,
|
||||
condition: `page.includes('playlist_')`
|
||||
component: `<cider-playlist :data="$root.showingPlaylist"></cider-playlist>`,
|
||||
condition: `$root.page.includes('playlist_')`
|
||||
}, {
|
||||
page: "album_",
|
||||
component: `<cider-playlist :data="showingPlaylist"></cider-playlist>`,
|
||||
condition: `page.includes('album_')`
|
||||
component: `<cider-playlist :data="$root.showingPlaylist"></cider-playlist>`,
|
||||
condition: `$root.page.includes('album_')`
|
||||
}, {
|
||||
page: "recordLabel_",
|
||||
component: `<cider-recordlabel :data="showingPlaylist"></cider-recordlabel>`,
|
||||
condition: `page.includes('recordLabel_')`
|
||||
component: `<cider-recordlabel :data="$root.showingPlaylist"></cider-recordlabel>`,
|
||||
condition: `$root.page.includes('recordLabel_')`
|
||||
}, {
|
||||
page: "social-profiles_",
|
||||
component: `<cider-socialprofile :data="$root.showingPlaylist"></cider-socialprofile>`,
|
||||
condition: `$root.page.includes('social-profiles_')`
|
||||
}, {
|
||||
page: "multiroom",
|
||||
component: `<cider-multiroom :data="multiroom"></cider-multiroom>`,
|
||||
condition: `page.includes('multiroom')`
|
||||
component: `<cider-multiroom :data="$root.multiroom"></cider-multiroom>`,
|
||||
condition: `$root.page.includes('multiroom')`
|
||||
}, {
|
||||
page: "curator_",
|
||||
component: `<cider-recordlabel :data="showingPlaylist"></cider-recordlabel>`,
|
||||
condition: `page.includes('curator_')`
|
||||
component: `<cider-recordlabel :data="$root.showingPlaylist"></cider-recordlabel>`,
|
||||
condition: `$root.page.includes('curator_')`
|
||||
}, {
|
||||
page: "browsepage",
|
||||
component: `<cider-browse :data="browsepage"></cider-browse>`,
|
||||
condition: `page == 'browse'`,
|
||||
component: `<cider-browse :data="$root.browsepage"></cider-browse>`,
|
||||
condition: `$root.page == 'browse'`,
|
||||
onEnter: ``
|
||||
},{
|
||||
}, {
|
||||
page: "groupings",
|
||||
component: `<cider-groupings :data="browsepage"></cider-groupings>`,
|
||||
condition: `page == 'groupings'`,
|
||||
component: `<cider-groupings :data="$root.browsepage"></cider-groupings>`,
|
||||
condition: `$root.page == 'groupings'`,
|
||||
onEnter: ``
|
||||
},{
|
||||
}, {
|
||||
page: "charts",
|
||||
component: `<cider-charts :data="browsepage"></cider-charts>`,
|
||||
condition: `page == 'charts'`,
|
||||
component: `<cider-charts :data="$root.browsepage"></cider-charts>`,
|
||||
condition: `$root.page == 'charts'`,
|
||||
onEnter: ``
|
||||
}, {
|
||||
page: "listen_now",
|
||||
component: `<cider-listen-now :data="listennow"></cider-listen-now>`,
|
||||
condition: `page == 'listen_now'`,
|
||||
component: `<cider-listen-now :data="$root.listennow"></cider-listen-now>`,
|
||||
condition: `$root.page == 'listen_now'`,
|
||||
onEnter: ``
|
||||
}, {
|
||||
page: "radio",
|
||||
component: `<cider-radio :data="radio"></cider-radio>`,
|
||||
condition: `page == 'radio'`,
|
||||
component: `<cider-radio :data="$root.radio"></cider-radio>`,
|
||||
condition: `$root.page == 'radio'`,
|
||||
onEnter: ``
|
||||
}, {
|
||||
page: "settings",
|
||||
component: `<cider-settings></cider-settings>`,
|
||||
condition: `page == 'settings'`
|
||||
condition: `$root.page == 'settings'`
|
||||
}, {
|
||||
page: "installed-themes",
|
||||
component: `<installed-themes></installed-themes>`,
|
||||
condition: `page == 'installed-themes'`
|
||||
condition: `$root.page == 'installed-themes'`
|
||||
}, {
|
||||
page: "search",
|
||||
component: `<cider-search :search="search"></cider-search>`,
|
||||
condition: `page == 'search'`
|
||||
component: `<cider-search :search="$root.search"></cider-search>`,
|
||||
condition: `$root.page == 'search'`
|
||||
}, {
|
||||
page: "library-songs",
|
||||
component: `<cider-library-songs :data="library.songs"></cider-library-songs>`,
|
||||
condition: `page == 'library-songs'`,
|
||||
component: `<cider-library-songs :data="$root.library.songs"></cider-library-songs>`,
|
||||
condition: `$root.page == 'library-songs'`,
|
||||
onEnter: ``
|
||||
}, {
|
||||
page: "library-albums",
|
||||
component: `<cider-library-albums :data="library.songs"></cider-library-albums>`,
|
||||
condition: `page == 'library-albums'`,
|
||||
component: `<cider-library-albums :data="$root.library.songs"></cider-library-albums>`,
|
||||
condition: `$root.page == 'library-albums'`,
|
||||
onEnter: ``
|
||||
}, {
|
||||
page: "library-artists",
|
||||
component: `<cider-library-artists></cider-library-artists>`,
|
||||
condition: `page == 'library-artists'`,
|
||||
condition: `$root.page == 'library-artists'`,
|
||||
onEnter: ``
|
||||
}, {
|
||||
page: "appleCurator",
|
||||
component: `<cider-applecurator :data="appleCurator"></cider-applecurator>`,
|
||||
condition: `page.includes('appleCurator')`
|
||||
component: `<cider-applecurator :data="$root.appleCurator"></cider-applecurator>`,
|
||||
condition: `$root.page.includes('appleCurator')`
|
||||
}, {
|
||||
page: "themes-github",
|
||||
component: `<themes-github></themes-github>`,
|
||||
condition: `page == 'themes-github'`
|
||||
condition: `$root.page == 'themes-github'`
|
||||
}, {
|
||||
page: "plugins-github",
|
||||
component: `<plugins-github></plugins-github>`,
|
||||
condition: `page == 'plugins-github'`
|
||||
condition: `$root.page == 'plugins-github'`
|
||||
}, {
|
||||
page: "remote-pair",
|
||||
component: `<remote-pair></remote-pair>`,
|
||||
condition: `page == 'remote-pair'`
|
||||
condition: `$root.page == 'remote-pair'`
|
||||
}, {
|
||||
page: "audiolabs",
|
||||
component: `<audiolabs-page></audiolabs-page>`,
|
||||
condition: `page == 'audiolabs'`
|
||||
condition: `$root.page == 'audiolabs'`
|
||||
}, {
|
||||
page: "replay",
|
||||
component: `<replay-page></replay-page>`,
|
||||
condition: `page == 'replay'`
|
||||
condition: `$root.page == 'replay'`
|
||||
}, {
|
||||
page: "keydinds",
|
||||
component: `<keybinds-settings></keybinds-settings>`,
|
||||
condition: `$root.page == 'keybinds-settings'`
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -291,7 +307,7 @@ export class BrowserWindow {
|
|||
show: false,
|
||||
// backgroundColor: "#1E1E1E",
|
||||
titleBarStyle: 'hidden',
|
||||
trafficLightPosition: {x: 15, y: 20},
|
||||
trafficLightPosition: { x: 15, y: 20 },
|
||||
webPreferences: {
|
||||
experimentalFeatures: true,
|
||||
nodeIntegration: true,
|
||||
|
@ -357,7 +373,8 @@ export class BrowserWindow {
|
|||
* @yields {object} Electron browser window
|
||||
*/
|
||||
async createWindow(): Promise<Electron.BrowserWindow> {
|
||||
this.clientPort = await getPort({port: 9000});
|
||||
const envPort = process.env?.CIDER_PORT || '9000'
|
||||
this.clientPort = await getPort({ port: parseInt(envPort, 10) || 9000 });
|
||||
BrowserWindow.verifyFiles();
|
||||
this.StartWatcher(utils.getPath('themes'));
|
||||
|
||||
|
@ -404,9 +421,10 @@ export class BrowserWindow {
|
|||
}
|
||||
|
||||
// Start the webserver for the browser window to load
|
||||
|
||||
// LocalFiles.DB.init()
|
||||
this.startWebServer();
|
||||
|
||||
|
||||
BrowserWindow.win = new bw(this.options);
|
||||
// cant be built in CI
|
||||
// if (process.platform === "win32" && (utils.getStoreValue('visual.transparent') ?? false)) {
|
||||
|
@ -462,7 +480,7 @@ export class BrowserWindow {
|
|||
*/
|
||||
private startWebServer(): void {
|
||||
const app = express();
|
||||
|
||||
BrowserWindow.express = app;
|
||||
app.use(express.static(join(utils.getPath('srcPath'), "./renderer/")));
|
||||
app.set("views", join(utils.getPath('srcPath'), "./renderer/views"));
|
||||
app.set("view engine", "ejs");
|
||||
|
@ -494,9 +512,9 @@ export class BrowserWindow {
|
|||
app.get("/cideraudio/impulses/:file", (req, res) => {
|
||||
const impulseExternals = join(utils.getPath("externals"), "/impulses/")
|
||||
const impulseFile = join(impulseExternals, req.params.file)
|
||||
if(existsSync(impulseFile)) {
|
||||
if (existsSync(impulseFile)) {
|
||||
res.sendFile(impulseFile)
|
||||
}else{
|
||||
} else {
|
||||
res.sendFile(join(utils.getPath('srcPath'), "./renderer/audio/impulses/" + req.params.file))
|
||||
}
|
||||
})
|
||||
|
@ -546,14 +564,6 @@ export class BrowserWindow {
|
|||
res.send(`// Theme not found - ${userThemePath}`);
|
||||
}
|
||||
});
|
||||
app.get("/ciderlocal/:songs", (req, res) => {
|
||||
const audio = atob(req.params.songs.replace(/_/g, '/').replace(/-/g, '+'));
|
||||
console.log('auss', audio)
|
||||
let data = {data:
|
||||
this.localSongs.filter((f: any) => audio.split(',').includes(f.id))};
|
||||
res.send(data);
|
||||
});
|
||||
|
||||
|
||||
app.get("/themes/:theme/*", (req: { params: { theme: string, 0: string } }, res) => {
|
||||
const theme = req.params.theme;
|
||||
|
@ -614,10 +624,12 @@ 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`)
|
||||
});
|
||||
|
||||
LocalFiles.setupHandlers()
|
||||
|
||||
// [Connect] Set auth URL in store for `shell.openExternal`
|
||||
utils.setStoreValue('cc_authURL', `https://connect.cidercollective.dev/callback/discord?app=cider&appPort=${this.clientPort}`)
|
||||
console.log(`[Connect] Auth URL: ${utils.getStoreValue('cc_authURL')}`)
|
||||
|
@ -637,7 +649,7 @@ export class BrowserWindow {
|
|||
remote.use(express.static(join(utils.getPath('srcPath'), "./web-remote/")))
|
||||
remote.set("views", join(utils.getPath('srcPath'), "./web-remote/views"));
|
||||
remote.set("view engine", "ejs");
|
||||
getPort({port: 6942}).then((port: number) => {
|
||||
getPort({ port: 6942 }).then((port: number) => {
|
||||
this.remotePort = port;
|
||||
// Start Remote Discovery
|
||||
this.broadcastRemote()
|
||||
|
@ -668,13 +680,13 @@ export class BrowserWindow {
|
|||
callback({
|
||||
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`,
|
||||
});
|
||||
} else if (details.url.includes("ciderlocal")) {
|
||||
} else if (details.url.includes("ciderlocal") && !details.url.includes("https://apic-desktop.musixmatch.com") ) {
|
||||
let text = details.url.toString().includes('ids=') ? decodeURIComponent(details.url.toString()).split("?ids=")[1] : decodeURIComponent(details.url.toString().substring(details.url.toString().lastIndexOf('/') + 1));
|
||||
console.log('localurl',text)
|
||||
//console.log('localurl',text)
|
||||
callback({
|
||||
redirectURL: `http://localhost:${this.clientPort}/ciderlocal/${Buffer.from(text).toString('base64url')}`,
|
||||
});
|
||||
}else {
|
||||
} else {
|
||||
callback({
|
||||
cancel: false,
|
||||
});
|
||||
|
@ -716,7 +728,7 @@ export class BrowserWindow {
|
|||
'KHTML, like Gecko) Mobile/17D50 UCBrowser/12.8.2.1268 Mobile AliApp(TUnionSDK/0.1.20.3) '
|
||||
details.requestHeaders['Referer'] = "https://y.qq.com/portal/player.html"
|
||||
}
|
||||
callback({requestHeaders: details.requestHeaders});
|
||||
callback({ requestHeaders: details.requestHeaders });
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -773,7 +785,7 @@ export class BrowserWindow {
|
|||
const Jimp = require("jimp")
|
||||
const img = await Jimp.read(wpPath)
|
||||
const blurAmount = args.blurAmount ?? 256
|
||||
if(blurAmount) {
|
||||
if (blurAmount) {
|
||||
img.blur(blurAmount)
|
||||
}
|
||||
const screens = await screen.getAllDisplays()
|
||||
|
@ -810,7 +822,7 @@ export class BrowserWindow {
|
|||
}
|
||||
// if path is directory, delete it
|
||||
if (lstatSync(path).isDirectory()) {
|
||||
await rmdirSync(path, {recursive: true});
|
||||
await rmdirSync(path, { recursive: true });
|
||||
} else {
|
||||
// if path is file, delete it
|
||||
await unlinkSync(path);
|
||||
|
@ -841,7 +853,7 @@ export class BrowserWindow {
|
|||
// remove WidevineCDM from appdata folder
|
||||
const widevineCdmPath = join(app.getPath("userData"), "./WidevineCdm");
|
||||
if (existsSync(widevineCdmPath)) {
|
||||
rmSync(widevineCdmPath, {recursive: true, force: true})
|
||||
rmSync(widevineCdmPath, { recursive: true, force: true })
|
||||
}
|
||||
// reinstall WidevineCDM
|
||||
app.relaunch()
|
||||
|
@ -849,6 +861,7 @@ export class BrowserWindow {
|
|||
})
|
||||
|
||||
ipcMain.handle("get-github-plugin", async (event, url) => {
|
||||
await this.StopWatcher()
|
||||
const returnVal = {
|
||||
success: true,
|
||||
theme: null,
|
||||
|
@ -893,9 +906,11 @@ export class BrowserWindow {
|
|||
returnVal.success = false;
|
||||
}
|
||||
BrowserWindow.win.webContents.send("plugin-installed", returnVal);
|
||||
this.StartWatcher(utils.getPath('themes'));
|
||||
});
|
||||
|
||||
ipcMain.handle("get-github-theme", async (event, url) => {
|
||||
await this.StopWatcher()
|
||||
const returnVal = {
|
||||
success: true,
|
||||
theme: null,
|
||||
|
@ -940,6 +955,8 @@ export class BrowserWindow {
|
|||
returnVal.success = false;
|
||||
}
|
||||
BrowserWindow.win.webContents.send("theme-installed", returnVal);
|
||||
this.StartWatcher(utils.getPath('themes'));
|
||||
BrowserWindow.win.webContents.send("theme-update", "")
|
||||
});
|
||||
|
||||
ipcMain.on("get-themes", (event, _key) => {
|
||||
|
@ -1128,7 +1145,7 @@ export class BrowserWindow {
|
|||
|
||||
// Move window
|
||||
ipcMain.on("windowmove", (_event, x, y) => {
|
||||
BrowserWindow.win.setBounds({x, y});
|
||||
BrowserWindow.win.setBounds({ x, y });
|
||||
});
|
||||
|
||||
//Fullscreen
|
||||
|
@ -1143,7 +1160,7 @@ export class BrowserWindow {
|
|||
|
||||
//Fullscreen
|
||||
ipcMain.on('detachDT', (_event, _) => {
|
||||
BrowserWindow.win.webContents.openDevTools({mode: 'detach'});
|
||||
BrowserWindow.win.webContents.openDevTools({ mode: 'detach' });
|
||||
})
|
||||
|
||||
ipcMain.handle('relaunchApp', (_event, _) => {
|
||||
|
@ -1162,6 +1179,10 @@ export class BrowserWindow {
|
|||
app.quit();
|
||||
})
|
||||
|
||||
ipcMain.handle("quit-app", (_event, _) => {
|
||||
app.quit();
|
||||
})
|
||||
|
||||
app.on('before-quit', () => {
|
||||
|
||||
})
|
||||
|
@ -1176,102 +1197,17 @@ export class BrowserWindow {
|
|||
});
|
||||
|
||||
|
||||
ipcMain.on("scanLibrary", async (event, folders) => {
|
||||
async function getFiles(dir : any) {
|
||||
const dirents = await readdir(dir, { withFileTypes: true });
|
||||
const files = await Promise.all(dirents.map((dirent: any) => {
|
||||
const res = path.resolve(dir, dirent.name);
|
||||
return dirent.isDirectory() ? getFiles(res) : res;
|
||||
}));
|
||||
return Array.prototype.concat(...files);
|
||||
}
|
||||
if (folders == null || folders.length == null || folders.length == 0) folders = ["D:\\Music"]
|
||||
console.log('folders', folders)
|
||||
let files: any[] = []
|
||||
for (var folder of folders){
|
||||
// get files from the Music folder
|
||||
files = files.concat(await getFiles(folder))
|
||||
}
|
||||
|
||||
//console.log("cider.files", files2);
|
||||
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"]
|
||||
let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1)));
|
||||
// console.log("cider.files2", audiofiles, audiofiles.length);
|
||||
let metadatalist = []
|
||||
let numid = 0;
|
||||
for (var audio of audiofiles) {
|
||||
try{
|
||||
const metadata = await mm.parseFile(audio);
|
||||
if (metadata != null){
|
||||
let form = {
|
||||
"id": "ciderlocal" + numid,
|
||||
"type": "podcast-episodes",
|
||||
"href": audio,
|
||||
"attributes": {
|
||||
"artwork": {
|
||||
"width": 3000,
|
||||
"height": 3000,
|
||||
"url": metadata.common.picture != undefined ? "data:image/png;base64,"+metadata.common.picture[0].data.toString('base64')+"" : "",
|
||||
},
|
||||
"topics": [],
|
||||
"url": "",
|
||||
"subscribable": true,
|
||||
"mediaKind": "audio",
|
||||
"genreNames": [
|
||||
""
|
||||
],
|
||||
// "playParams": {
|
||||
// "id": "ciderlocal" + numid,
|
||||
// "kind": "podcast",
|
||||
// "isLibrary": true,
|
||||
// "reporting": false },
|
||||
"trackNumber": metadata.common.track?.no ?? 0,
|
||||
"discNumber": metadata.common.disk?.no ?? 0,
|
||||
"name": metadata.common.title ?? audio.substring(audio.lastIndexOf('\\') + 1),
|
||||
"albumName": metadata.common.album,
|
||||
"artistName": metadata.common.artist,
|
||||
"copyright": metadata.common.copyright ?? "",
|
||||
"assetUrl": "file:///" +audio,
|
||||
"contentAdvisory": "",
|
||||
"releaseDateTime": "2022-05-13T00:23:00Z",
|
||||
"durationInMilliseconds": Math.floor((metadata.format.duration?? 0) * 1000),
|
||||
|
||||
"offers": [
|
||||
{
|
||||
"kind": "get",
|
||||
"type": "STDQ"
|
||||
}
|
||||
],
|
||||
"contentRating": "clean"
|
||||
}
|
||||
};
|
||||
numid += 1;
|
||||
|
||||
// let form = {"id": "/ciderlocal?" + audio,
|
||||
// "type": "library-songs",
|
||||
// "href": "/ciderlocal?" + audio,
|
||||
// "artwork": {
|
||||
// "url": metadata.common.picture != undefined ? "data:image/png;base64,"+metadata.common.picture[0].data.toString('base64')+"" : "",
|
||||
// },
|
||||
// "attributes":
|
||||
// { "durationInMillis": Math.floor((metadata.format.duration?? 0) * 1000),
|
||||
// "hasLyrics": false,
|
||||
// "playParams": { "id": "/ciderlocal?" + audio, "kind": "song", "isLibrary": true, "reporting": false },
|
||||
// "trackNumber": 0,
|
||||
// "discNumber": 0,
|
||||
// "genreNames": [""],
|
||||
// "name": metadata.common.title,
|
||||
// "albumName": metadata.common.album,
|
||||
// "artistName": metadata.common.artist}}
|
||||
metadatalist.push(form)}
|
||||
} catch (e){}
|
||||
}
|
||||
// console.log('metadatalist', metadatalist);
|
||||
this.localSongs = metadatalist;
|
||||
BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist);
|
||||
}
|
||||
ipcMain.handle("scanLibrary", async (event, folders) => {
|
||||
const oldmetadatalist = await LocalFiles.sendOldLibrary()
|
||||
BrowserWindow.win.webContents.send('getUpdatedLocalList', oldmetadatalist);
|
||||
const metadatalist = await LocalFiles.scanLibrary()
|
||||
BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist);
|
||||
LocalFiles.cleanUpDB()
|
||||
})
|
||||
|
||||
)
|
||||
LocalFiles.eventEmitter.on('newtracks', (data) => {
|
||||
BrowserWindow.win.webContents.send('getUpdatedLocalList', data);
|
||||
});
|
||||
|
||||
ipcMain.on('writeWAV', (event, leftpcm, rightpcm, bufferlength) => {
|
||||
|
||||
|
@ -1439,13 +1375,13 @@ export class BrowserWindow {
|
|||
console.log('sc', SoundCheckTag)
|
||||
BrowserWindow.win.webContents.send('SoundCheckTag', SoundCheckTag)
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
});
|
||||
console.log(err)
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on('share-menu', async (_event, url) => {
|
||||
if (process.platform != 'darwin') return;
|
||||
if (process.platform !== 'darwin') return;
|
||||
//https://www.electronjs.org/docs/latest/api/share-menu
|
||||
console.log('[Share Sheet - App.ts]', url)
|
||||
const options = {
|
||||
|
@ -1464,10 +1400,17 @@ export class BrowserWindow {
|
|||
}
|
||||
|
||||
});
|
||||
|
||||
ipcMain.on('open-appdata', (_event) => {
|
||||
shell.openPath(app.getPath('userData'));
|
||||
});
|
||||
|
||||
ipcMain.handle('folderSelector', async (_event) => {
|
||||
let u = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory', 'multiSelections']
|
||||
});
|
||||
return u.filePaths
|
||||
});
|
||||
|
||||
//#region Cider Connect
|
||||
ipcMain.on('cc-auth', (_event) => {
|
||||
|
@ -1492,35 +1435,38 @@ export class BrowserWindow {
|
|||
/* *********************************************************************************************
|
||||
* Window Events
|
||||
* **********************************************************************************************/
|
||||
if (process.platform === "win32") {
|
||||
let WND_STATE = {
|
||||
MINIMIZED: 0,
|
||||
NORMAL: 1,
|
||||
MAXIMIZED: 2,
|
||||
FULL_SCREEN: 3,
|
||||
};
|
||||
let wndState = WND_STATE.NORMAL;
|
||||
let WND_STATE = {
|
||||
MINIMIZED: 0,
|
||||
NORMAL: 1,
|
||||
MAXIMIZED: 2,
|
||||
FULL_SCREEN: 3,
|
||||
};
|
||||
let wndState = WND_STATE.NORMAL;
|
||||
|
||||
BrowserWindow.win.on("resize", (_: any) => {
|
||||
const isMaximized = BrowserWindow.win.isMaximized();
|
||||
const isMinimized = BrowserWindow.win.isMinimized();
|
||||
const isFullScreen = BrowserWindow.win.isFullScreen();
|
||||
const state = wndState;
|
||||
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
||||
wndState = WND_STATE.MINIMIZED;
|
||||
BrowserWindow.win.webContents.send('window-state-changed', 'minimized');
|
||||
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
||||
wndState = WND_STATE.FULL_SCREEN;
|
||||
BrowserWindow.win.webContents.send('window-state-changed', 'fullscreen')
|
||||
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
||||
wndState = WND_STATE.MAXIMIZED;
|
||||
BrowserWindow.win.webContents.send('window-state-changed', 'maximized')
|
||||
BrowserWindow.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
|
||||
} else if (state !== WND_STATE.NORMAL) {
|
||||
wndState = WND_STATE.NORMAL;
|
||||
BrowserWindow.win.webContents.send('window-state-changed', 'normal')
|
||||
BrowserWindow.win.webContents.executeJavaScript(
|
||||
`app.chrome.maximized = false`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
BrowserWindow.win.on("resize", (_: any) => {
|
||||
const isMaximized = BrowserWindow.win.isMaximized();
|
||||
const isMinimized = BrowserWindow.win.isMinimized();
|
||||
const isFullScreen = BrowserWindow.win.isFullScreen();
|
||||
const state = wndState;
|
||||
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
||||
wndState = WND_STATE.MINIMIZED;
|
||||
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
||||
wndState = WND_STATE.FULL_SCREEN;
|
||||
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
||||
wndState = WND_STATE.MAXIMIZED;
|
||||
BrowserWindow.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
|
||||
} else if (state !== WND_STATE.NORMAL) {
|
||||
wndState = WND_STATE.NORMAL;
|
||||
BrowserWindow.win.webContents.executeJavaScript(
|
||||
`app.chrome.maximized = false`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let isQuiting = false
|
||||
|
||||
|
@ -1563,10 +1509,10 @@ export class BrowserWindow {
|
|||
// Set window Handler
|
||||
BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => {
|
||||
if (x.url.includes("apple") || x.url.includes("localhost")) {
|
||||
return {action: "allow"};
|
||||
return { action: "allow" };
|
||||
}
|
||||
shell.openExternal(x.url).catch(console.error);
|
||||
return {action: "deny"};
|
||||
return { action: "deny" };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1622,7 +1568,7 @@ export class BrowserWindow {
|
|||
"CtlN": "Cider",
|
||||
"iV": "196623"
|
||||
};
|
||||
let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, {
|
||||
let server2 = mdns.createAdvertisement(x, `${await getPort({ port: 3839 })}`, {
|
||||
name: encoded,
|
||||
txt: txt_record
|
||||
});
|
||||
|
|
|
@ -16,10 +16,10 @@ import {utils} from './utils';
|
|||
* @see {@link https://github.com/ciderapp/Cider/wiki/Plugins|Documentation}
|
||||
*/
|
||||
export class Plugins {
|
||||
private static PluginMap: any = {};
|
||||
private basePluginsPath = path.join(__dirname, '../plugins');
|
||||
private userPluginsPath = path.join(electron.app.getPath('userData'), 'Plugins');
|
||||
private readonly pluginsList: any = {};
|
||||
private static PluginMap: any = {};
|
||||
|
||||
constructor() {
|
||||
this.pluginsList = this.getPlugins();
|
||||
|
@ -35,8 +35,8 @@ export class Plugins {
|
|||
|
||||
public getPlugins(): any {
|
||||
let plugins: any = {};
|
||||
|
||||
|
||||
|
||||
|
||||
if (fs.existsSync(this.basePluginsPath)) {
|
||||
fs.readdirSync(this.basePluginsPath).forEach(file => {
|
||||
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||
|
@ -49,8 +49,8 @@ export class Plugins {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (fs.existsSync(this.userPluginsPath)) {
|
||||
fs.readdirSync(this.userPluginsPath).forEach(file => {
|
||||
// Plugins V1
|
||||
|
@ -104,10 +104,11 @@ export class Plugins {
|
|||
public callPlugins(event: string, ...args: any[]) {
|
||||
for (const plugin in this.pluginsList) {
|
||||
if (this.pluginsList[plugin][event]) {
|
||||
try{
|
||||
try {
|
||||
this.pluginsList[plugin][event](...args);
|
||||
}catch(e) {
|
||||
console.log(`[${plugin}] Plugin error: ${e}`);
|
||||
} catch (e) {
|
||||
console.error(`[${plugin}] An error was encountered: ${e}`);
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,15 +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}",
|
||||
},
|
||||
"language": "en_US", // electron.app.getLocale().replace('-', '_') this can be used in future
|
||||
"playbackNotifications": true,
|
||||
"resumeOnStartupBehavior": "local",
|
||||
|
@ -39,7 +31,8 @@ export class Store {
|
|||
"applemusic": false,
|
||||
"library": false,
|
||||
"amplaylists": false,
|
||||
"playlists": false
|
||||
"playlists": false,
|
||||
"localLibrary": false
|
||||
},
|
||||
"onStartup": {
|
||||
"enabled": false,
|
||||
|
@ -66,7 +59,7 @@ export class Store {
|
|||
"CommandOrControl",
|
||||
"G"
|
||||
],
|
||||
"songs" : [
|
||||
"songs": [
|
||||
"CommandOrControl",
|
||||
"J"
|
||||
],
|
||||
|
@ -89,23 +82,35 @@ export class Store {
|
|||
],
|
||||
"audioSettings": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift": "Alt"),
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"A"
|
||||
],
|
||||
"pluginMenu": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift": "Alt"),
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"P"
|
||||
],
|
||||
"castToDevices": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift": "Alt"),
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"C"
|
||||
],
|
||||
"settings": [
|
||||
"CommandOrControl", // Who the hell uses a different key for this? Fucking Option?
|
||||
","
|
||||
],
|
||||
"zoomn": [
|
||||
"Control",
|
||||
"numadd",
|
||||
],
|
||||
"zoomt": [
|
||||
"Control",
|
||||
"numsub",
|
||||
],
|
||||
"zoomrst": [
|
||||
"Control",
|
||||
"num0",
|
||||
],
|
||||
"openDeveloperTools": [
|
||||
"CommandOrControl",
|
||||
"Shift",
|
||||
|
@ -114,21 +119,50 @@ 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": []
|
||||
},
|
||||
"libraryPrefs": {
|
||||
"songs": {
|
||||
"scroll": "paged",
|
||||
"sort": "name",
|
||||
"sortOrder": "asc",
|
||||
"size": "normal"
|
||||
},
|
||||
"albums": {
|
||||
"scroll": "paged",
|
||||
"sort": "name",
|
||||
"sortOrder": "asc",
|
||||
"viewAs": "covers"
|
||||
},
|
||||
"playlists": {
|
||||
"scroll": "infinite"
|
||||
},
|
||||
"localPaths": [],
|
||||
"pageSize": 250
|
||||
},
|
||||
"audio": {
|
||||
"volume": 1,
|
||||
|
@ -139,18 +173,19 @@ export class Store {
|
|||
"playbackRate": 1,
|
||||
"quality": "HIGH",
|
||||
"seamless_audio": true,
|
||||
"normalization": false,
|
||||
"normalization": true,
|
||||
"dBSPL": false,
|
||||
"dBSPLcalibration": 90,
|
||||
"maikiwiAudio": {
|
||||
"ciderPPE": false,
|
||||
"ciderPPE": true,
|
||||
"ciderPPE_value": "MAIKIWI",
|
||||
"opportunisticCorrection_state": "OFF",
|
||||
"atmosphereRealizer1": false,
|
||||
"atmosphereRealizer1_value": "NATURAL_STANDARD",
|
||||
"atmosphereRealizer2": false,
|
||||
"atmosphereRealizer2_value": "NATURAL_STANDARD",
|
||||
"spatial": false,
|
||||
"spatialProfile": "71_420maikiwi",
|
||||
"spatialProfile": "BPLK",
|
||||
"vibrantBass": { // Hard coded into the app. Don't include any of this config into exporting presets in store.ts
|
||||
'frequencies': [17.182, 42.169, 53.763, 112.69, 119.65, 264.59, 336.57, 400.65, 505.48, 612.7, 838.7, 1155.3, 1175.6, 3406.8, 5158.6, 5968.1, 6999.9, 7468.6, 8862.9, 9666, 10109],
|
||||
'Q': [2.5, 0.388, 5, 5, 2.5, 7.071, 14.14, 10, 7.071, 14.14, 8.409, 0.372, 7.071, 10, 16.82, 7.071, 28.28, 20, 8.409, 40, 40],
|
||||
|
@ -206,31 +241,25 @@ export class Store {
|
|||
},
|
||||
"windowControlPosition": 0, // 0 default right
|
||||
"nativeTitleBar": false,
|
||||
"uiScale": 1.0,
|
||||
"windowColor": "#000000",
|
||||
"customAccentColor": false,
|
||||
"accentColor": "#fc3c44"
|
||||
"accentColor": "#fc3c44",
|
||||
"purplePodcastPlaybackBar": false,
|
||||
"maxElementScale": -1 // -1 default, anything else is a custom scale
|
||||
},
|
||||
"lyrics": {
|
||||
"enable_mxm": false,
|
||||
"enable_mxm": true,
|
||||
"mxm_karaoke": false,
|
||||
"mxm_language": "en",
|
||||
"mxm_language": "disabled",
|
||||
"enable_qq": false,
|
||||
"enable_yt": false,
|
||||
},
|
||||
"lastfm": {
|
||||
"enabled": false,
|
||||
"scrobble_after": 30,
|
||||
"auth_token": "",
|
||||
"enabledRemoveFeaturingArtists": true,
|
||||
"filterLoop": true,
|
||||
"NowPlaying": "true"
|
||||
},
|
||||
"advanced": {
|
||||
"AudioContext": false,
|
||||
"AudioContext": true,
|
||||
"experiments": [],
|
||||
"playlistTrackMapping": true,
|
||||
"ffmpegLocation": ""
|
||||
"ffmpegLocation": "",
|
||||
"disableLogging": true
|
||||
},
|
||||
"connectUser": {
|
||||
"auth": null,
|
||||
|
@ -241,15 +270,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'
|
||||
},
|
||||
}
|
||||
|
@ -260,57 +283,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();
|
||||
|
@ -324,7 +303,7 @@ export class Store {
|
|||
plugins: Store.cfg.store.plugins
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (Store.cfg.get('connectUser.sync.settings')) {
|
||||
syncData.push({
|
||||
general: Store.cfg.get('general'),
|
||||
|
@ -348,4 +327,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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,39 @@ 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 } 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()")
|
||||
},
|
||||
seek: (seconds: number) => {
|
||||
bw.win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${seconds})`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 +64,21 @@ export class utils {
|
|||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IPCMain
|
||||
*/
|
||||
static getIPCMain(): Electron.IpcMain {
|
||||
return ipcMain
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the Express instance
|
||||
* @returns {any}
|
||||
*/
|
||||
static getExpress(): any {
|
||||
return bw.express
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the i18n locale for the given language.
|
||||
* @param language {string} The language to fetch the locale for.
|
||||
|
@ -64,7 +100,7 @@ export class utils {
|
|||
} else {
|
||||
i18n = Object.assign(i18n, JSON.parse(fs.readFileSync(path.join(this.paths.i18nPath, `en_US.json`), "utf8")));
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if (key) {
|
||||
return i18n[key]
|
||||
|
@ -90,7 +126,6 @@ export class utils {
|
|||
return Store.cfg.store
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the store instance
|
||||
* @returns {Store}
|
||||
|
@ -116,15 +151,15 @@ export class utils {
|
|||
return Store.pushToCloud
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the browser window
|
||||
*/
|
||||
static getWindow(): Electron.BrowserWindow {
|
||||
return bw.win
|
||||
if (bw.win) {
|
||||
return bw.win
|
||||
} else {
|
||||
return BrowserWindow.getAllWindows()[0]
|
||||
}
|
||||
}
|
||||
|
||||
static loadPluginFrontend(path: string): void {
|
||||
|
@ -134,25 +169,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()")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"pages/library-artists",
|
||||
"pages/browse",
|
||||
"pages/groupings",
|
||||
"pages/settings",
|
||||
"pages/installed-themes",
|
||||
"pages/listen_now",
|
||||
"pages/radio",
|
||||
|
@ -60,6 +59,7 @@
|
|||
"components/fullscreen",
|
||||
"components/miniplayer",
|
||||
"components/castmenu",
|
||||
"components/pathmenu",
|
||||
"components/airplay-modal",
|
||||
"components/artist-chip",
|
||||
"components/hello-world",
|
||||
|
|
|
@ -69,6 +69,11 @@
|
|||
"component": "<cider-playlist :data=\"showingPlaylist\"></cider-playlist>",
|
||||
"condition": "page.includes('album_')"
|
||||
},
|
||||
{
|
||||
"page": "social-profiles_",
|
||||
"component": "<cider-socialprofile :data=\"showingPlaylist\"></cider-socialprofile>",
|
||||
"condition": "$root.page.includes('social-profiles_')"
|
||||
},
|
||||
{
|
||||
"page": "recordLabel_",
|
||||
"component": "<cider-recordlabel :data=\"showingPlaylist\"></cider-recordlabel>",
|
||||
|
|
|
@ -64,6 +64,15 @@ export class wsapi {
|
|||
electron.ipcMain.on('wsapi-returnvolumeMax', (_event: any, arg: any) => {
|
||||
this.returnmaxVolume(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on('wsapi-libraryStatus', (_event: any, inLibrary: boolean, rating: number) => {
|
||||
this.returnLibraryStatus(inLibrary, rating);
|
||||
});
|
||||
electron.ipcMain.on('wsapi-rate', (_event: any, kind: string, id: string, rating: number) => {
|
||||
this.returnRatingStatus(kind, id, rating);
|
||||
});
|
||||
electron.ipcMain.on('wsapi-change-library', (_event: any, kind: string, id: string, shouldAdd: boolean) => {
|
||||
this.returnLibraryChange(kind, id, shouldAdd);
|
||||
});
|
||||
this.wss = new WebSocketServer({
|
||||
port: this.port,
|
||||
perMessageDeflate: {
|
||||
|
@ -242,6 +251,15 @@ export class wsapi {
|
|||
case "get-currentmediaitem":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
|
||||
break;
|
||||
case "library-status":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getLibraryStatus("${data.type}", "${data.id}")`);
|
||||
break;
|
||||
case "rating":
|
||||
this._win.webContents.executeJavaScript(`wsapi.rate("${data.type}", "${data.id}", ${data.rating})`);
|
||||
break;
|
||||
case "change-library":
|
||||
this._win.webContents.executeJavaScript(`wsapi.changeLibrary("${data.type}", "${data.id}", ${data.add})`);
|
||||
break;
|
||||
case "quit":
|
||||
electron.app.quit();
|
||||
break;
|
||||
|
@ -317,4 +335,35 @@ export class wsapi {
|
|||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnLibraryStatus(inLibrary: boolean, rating: number) {
|
||||
const response: standardResponse = {
|
||||
status: 0, data: {
|
||||
inLibrary, rating
|
||||
}, message: "OK", type: "libraryStatus"
|
||||
}
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnRatingStatus(kind: string, id: string, rating: number) {
|
||||
const response: standardResponse = {
|
||||
status: 0, data: { kind, id, rating },
|
||||
message: "OK", type: "rate"
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnLibraryChange(kind: string, id: string, shouldAdd: boolean) {
|
||||
const response: standardResponse = {
|
||||
status: 0, data: { kind, id, add: shouldAdd },
|
||||
message: "OK", type: "change-library"
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
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 +32,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 +49,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 +68,16 @@ 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);
|
||||
})
|
||||
|
||||
app.on('before-quit', () => {
|
||||
CiderPlug.callPlugins('onBeforeQuit');
|
||||
app.on("before-quit", () => {
|
||||
CiderPlug.callPlugins("onBeforeQuit");
|
||||
console.warn(`${app.getName()} exited.`);
|
||||
});
|
||||
|
||||
|
@ -90,21 +86,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()
|
||||
})
|
||||
|
|
|
@ -52,29 +52,57 @@ export default class DiscordRPC {
|
|||
const self = this
|
||||
this.connect();
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
ipcMain.on('updateRPCImage', (_event, imageurl) => {
|
||||
ipcMain.on('updateRPCImage', async (_event, imageurl) => {
|
||||
if (!this._utils.getStoreValue("general.privateEnabled")) {
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
let b64data = ""
|
||||
let postbody = ""
|
||||
if (imageurl.startsWith("/ciderlocalart")){
|
||||
let port = await _win.webContents.executeJavaScript(
|
||||
`app.clientPort`
|
||||
);
|
||||
console.log("http://localhost:"+port+imageurl)
|
||||
const response = await fetch("http://localhost:"+port+imageurl)
|
||||
b64data = (await response.buffer()).toString('base64');
|
||||
postbody = JSON.stringify({data: b64data})
|
||||
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)
|
||||
method: 'POST',
|
||||
body: postbody,
|
||||
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)
|
||||
})
|
||||
} else {
|
||||
postbody = JSON.stringify({url: imageurl})
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
|
||||
method: 'POST',
|
||||
body: postbody,
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
ipcMain.on("reloadRPC", () => {
|
||||
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)
|
||||
|
@ -88,6 +116,7 @@ export default class DiscordRPC {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
|
@ -125,7 +154,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 +172,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 +190,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 +206,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 +220,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 +228,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';
|
||||
}
|
||||
|
|
|
@ -1,278 +1,236 @@
|
|||
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 _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;
|
||||
}
|
||||
|
||||
onReady(_win: Electron.BrowserWindow): void {
|
||||
this.initializeLastFM("", this._apiCredentials)
|
||||
|
||||
// Register the ipcMain handlers
|
||||
this._utils.getIPCMain().handle('lastfm:url', (event: any) => {
|
||||
console.debug(`[${lastfm.name}:url] Called.`)
|
||||
return this._lfm.getAuthenticationUrl({"cb": "cider://auth/lastfm"})
|
||||
})
|
||||
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:auth', (event: any, token: string) => {
|
||||
console.debug(`[${lastfm.name}:auth] Token: `, token)
|
||||
this.authenticateLastFM(token)
|
||||
})
|
||||
|
||||
this._utils.getIPCMain().on('lastfm:disconnect', (_event: any) => {
|
||||
this._lfm.setSessionCredentials(null, null);
|
||||
this._authenticated = false;
|
||||
console.debug(`[${lastfm.name}:disconnect] Disconnected`)
|
||||
})
|
||||
|
||||
this._utils.getIPCMain().on('lastfm:nowPlayingChange', (event: any, attributes: any) => {
|
||||
if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.updateNowPlayingTrack(attributes)
|
||||
})
|
||||
|
||||
this._utils.getIPCMain().on('lastfm:scrobbleTrack', (event: any, attributes: any) => {
|
||||
if (this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.scrobbleTrack(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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
* @param scrobble
|
||||
*/
|
||||
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
|
||||
}
|
||||
this.updateNowPlayingSong(attributes)
|
||||
this.scrobbleSong(attributes)
|
||||
onNowPlayingItemDidChange(attributes: any, scrobble = false): void {
|
||||
if (this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.updateNowPlayingTrack(attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize LastFM
|
||||
* @param token
|
||||
* @param api
|
||||
* @private
|
||||
*/
|
||||
private initializeLastFM(token: string, api: { key: string, secret: string }): void {
|
||||
console.debug(`[${lastfm.name}:initialize] Initializing LastFM`)
|
||||
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(`[${lastfm.name}:authenticate] Error: ${typeof err === "string" ? err : err.message}`);
|
||||
|
||||
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
|
||||
* @param callback
|
||||
* @private
|
||||
*/
|
||||
private verifyTrack(attributes: any, callback: Function): void {
|
||||
if (!attributes) return attributes;
|
||||
|
||||
if (!attributes.lfmAlbum) {
|
||||
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}`)
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
attributes.lfmAlbum = data
|
||||
callback(attributes)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
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}`)
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
attributes.lfmTrack = data.correction.track
|
||||
callback(attributes)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrobbles the track to lastfm
|
||||
* @param attributes
|
||||
* @private
|
||||
*/
|
||||
private scrobbleTrack(attributes: any): void {
|
||||
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
|
||||
this.verifyTrack(attributes, (a: any) => {
|
||||
this.scrobbleTrack(a)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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
|
||||
console.debug(`[${lastfm.name}:scrobble] Scrobbling ${scrobble.artist} - ${scrobble.track}`)
|
||||
|
||||
// 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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the now playing track
|
||||
* @param attributes
|
||||
* @private
|
||||
*/
|
||||
private updateNowPlayingTrack(attributes: any): void {
|
||||
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
|
||||
this.verifyTrack(attributes, (a: any) => {
|
||||
this.updateNowPlayingTrack(a)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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.debug(`[${lastfm.name}:updateNowPlaying] Now Playing Updated: ${nowPlaying.artist} - ${nowPlaying.track}`);
|
||||
this._nowPlayingCache = nowPlaying
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,8 @@ export default class Thumbar {
|
|||
* Menubar Assets
|
||||
* @private
|
||||
*/
|
||||
|
||||
private isNotMac: boolean = process.platform !== 'darwin';
|
||||
private isMac: boolean = process.platform === 'darwin';
|
||||
private _menuTemplate: any = [
|
||||
{
|
||||
|
@ -28,14 +30,14 @@ export default class Thumbar {
|
|||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.toggleprivate'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.toggleprivate'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.togglePrivateSession").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.cfg.general.privateEnabled = !app.cfg.general.privateEnabled`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.settings'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.settings'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.settings").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('settings')`)
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.openSettingsPage()`)
|
||||
},
|
||||
...(this.isMac ? [
|
||||
{type: 'separator'},
|
||||
|
@ -47,11 +49,21 @@ export default class Thumbar {
|
|||
{type: 'separator'},
|
||||
{role: 'quit'}
|
||||
] : []),
|
||||
...(this.isNotMac ? [
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.quit'),
|
||||
accelerator: 'Control+Q',
|
||||
click: () => app.quit()
|
||||
|
||||
}
|
||||
] : [])
|
||||
]
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.view'),
|
||||
submenu: [
|
||||
...(this.isMac ? [
|
||||
{role: 'reload'},
|
||||
{role: 'forceReload'},
|
||||
{role: 'toggleDevTools'},
|
||||
|
@ -62,40 +74,41 @@ export default class Thumbar {
|
|||
{type: 'separator'},
|
||||
{role: 'togglefullscreen'},
|
||||
{type: 'separator'},
|
||||
] : []),
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.search'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.search'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.search").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript('app.focusSearch()')
|
||||
},
|
||||
{type:'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.listennow'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.listenNow'),
|
||||
accelerator: utils.getStoreValue('general.keybindings.listnow').join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('listen_now')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.browse'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.browse'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.browse").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('browse')`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.recentlyAdded')
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.recentlyAdded')
|
||||
,accelerator: utils.getStoreValue("general.keybindings.recentAdd").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-recentlyadded')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.songs'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.songs'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.songs").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-songs')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.albums'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.albums'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.albums").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-albums')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.artists'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.artists'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.artists").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-artists')`)
|
||||
},
|
||||
|
@ -105,29 +118,16 @@ export default class Thumbar {
|
|||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.window'),
|
||||
submenu: [
|
||||
{role: 'minimize', label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.minimize')},
|
||||
{type: 'separator'},
|
||||
...(this.isMac ? [
|
||||
{
|
||||
label: 'Show',
|
||||
click: () => utils.getWindow().show()
|
||||
},
|
||||
{role: 'toggleDevTools'},
|
||||
{role: 'zoom'},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label:'Zoom',
|
||||
submenu: [
|
||||
{role: 'zoom'},
|
||||
{role: 'resetZoom'},
|
||||
{role: 'zoomIn'},
|
||||
{role: 'zoomOut'},
|
||||
]
|
||||
},
|
||||
{type: 'separator'},
|
||||
{role: 'togglefullscreen'},
|
||||
|
||||
|
||||
{type: 'separator'},
|
||||
{role: 'front'},
|
||||
{role: 'close'},
|
||||
{role: 'front'},
|
||||
{role: 'close'},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
|
@ -139,29 +139,74 @@ export default class Thumbar {
|
|||
{role: 'paste'},
|
||||
]
|
||||
},
|
||||
] : [
|
||||
{type: 'separator'},
|
||||
] : [ ]),
|
||||
...(this.isNotMac ? [
|
||||
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.zoom'),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.zoomin'),
|
||||
role: 'zoomIn',
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomn").join('+')
|
||||
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.zoomout'),
|
||||
role: 'zoomOut',
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomt").join('+')
|
||||
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.zoomreset'),
|
||||
role: 'resetZoom',
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomrst").join('+')
|
||||
}
|
||||
]
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.fullscreen'),
|
||||
accelerator: 'Control+Enter',
|
||||
role: 'togglefullscreen'
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'action.close'),
|
||||
accelerator: 'Control+W',
|
||||
role: 'close'
|
||||
},
|
||||
{type:'separator'},
|
||||
{role: 'reload', label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.reload')},
|
||||
{role: 'forceReload', label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.forcereload')},
|
||||
]),
|
||||
]
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.reload'),
|
||||
accelerator: 'Control+R',
|
||||
role: 'reload'
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.forcereload'),
|
||||
accelerator: 'Control+Shift+R',
|
||||
role: 'forceReload'
|
||||
},
|
||||
] : []),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.controls'),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.playpause'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.playpause'),
|
||||
accelerator: 'Space',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.SpacePause()`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.next'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.next'),
|
||||
accelerator: 'CommandOrControl+Right',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.previous'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.previous'),
|
||||
accelerator: 'CommandOrControl+Left',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`)
|
||||
},
|
||||
|
@ -178,19 +223,19 @@ export default class Thumbar {
|
|||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.cast'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.cast2'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.castToDevices").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.castMenu = true`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.webremote'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.webremote'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.webRemote").join('+'),
|
||||
sublabel: 'Opens in external window',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('remote-pair')`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.audio'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.audioSettings'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.audioSettings").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.audioSettings = true`)
|
||||
},
|
||||
|
@ -207,7 +252,7 @@ export default class Thumbar {
|
|||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.account'),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.accountsettings'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.accountSettings'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('apple-account-settings')`)
|
||||
},
|
||||
{
|
||||
|
@ -221,11 +266,11 @@ export default class Thumbar {
|
|||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale('Discord', 'menubar.options.discord'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.discord'),
|
||||
click: () => shell.openExternal("https://discord.gg/AppleMusic").catch(console.error)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale('GitHub Wiki', 'menubar.options.github'),
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.github'),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/wiki/Troubleshooting").catch(console.error)
|
||||
},
|
||||
{type: 'separator'},
|
||||
|
|
|
@ -6,7 +6,10 @@ export default class mpris {
|
|||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private static utils: any;
|
||||
|
||||
/**
|
||||
* MPRIS Service
|
||||
*/
|
||||
private static player: Player.Player;
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
|
@ -15,30 +18,17 @@ export default class mpris {
|
|||
public version: string = '1.0.0';
|
||||
public author: string = 'Core';
|
||||
|
||||
/**
|
||||
* MPRIS Service
|
||||
*/
|
||||
private static player: Player.Player;
|
||||
private static mprisEvents: Object = {
|
||||
"playpause": "playPause",
|
||||
"play": "play",
|
||||
"pause": "pause",
|
||||
"next": "next",
|
||||
"previous": "previous",
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs a media event
|
||||
* @param type - pausePlay, next, previous
|
||||
* @private
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
private static runMediaEvent(type: string) {
|
||||
console.debug(`[Plugin][${this.name}] ${type}.`);
|
||||
mpris.utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.${type}()`).catch(console.error)
|
||||
constructor(utils: any) {
|
||||
mpris.utils = utils
|
||||
|
||||
console.debug(`[Plugin][${mpris.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,7 +44,6 @@ export default class mpris {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connects to MPRIS Service
|
||||
*/
|
||||
|
@ -63,29 +52,49 @@ export default class mpris {
|
|||
const player = Player({
|
||||
name: 'cider',
|
||||
identity: 'Cider',
|
||||
supportedUriSchemes: [],
|
||||
supportedMimeTypes: [],
|
||||
supportedInterfaces: ['player']
|
||||
});
|
||||
|
||||
console.debug(`[Plugin][${mpris.name}] Successfully connected.`);
|
||||
console.debug(`[${mpris.name}:connect] Successfully connected.`);
|
||||
|
||||
const pos_atr = {durationInMillis: 0};
|
||||
player.getPosition = function () {
|
||||
const durationInMicro = pos_atr.durationInMillis * 1000;
|
||||
const percentage = parseFloat("0") || 0;
|
||||
return durationInMicro * percentage;
|
||||
const renderer = mpris.utils.getWindow().webContents
|
||||
const loopType: { [key: string]: number; } = {
|
||||
'none': 0,
|
||||
'track': 1,
|
||||
'playlist': 2,
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(mpris.mprisEvents)) {
|
||||
player.on(key, function () {
|
||||
mpris.runMediaEvent(value)
|
||||
});
|
||||
}
|
||||
player.on('next', () => mpris.utils.playback.next())
|
||||
player.on('previous', () => mpris.utils.playback.previous())
|
||||
player.on('playpause', () => mpris.utils.playback.playPause())
|
||||
player.on('play', () => mpris.utils.playback.play())
|
||||
player.on('pause', () => mpris.utils.playback.pause())
|
||||
player.on('quit', () => mpris.utils.getApp().exit())
|
||||
player.on('position', (args: { position: any; }) => mpris.utils.playback.seek(args.position / 1000 / 1000))
|
||||
player.on('loopStatus', (status: string) => renderer.executeJavaScript(`app.mk.repeatMode = ${loopType[status.toLowerCase()]}`))
|
||||
player.on('shuffle', () => renderer.executeJavaScript('app.mk.shuffleMode = (app.mk.shuffleMode === 0) ? 1 : 0'))
|
||||
|
||||
player.on('quit', function () {
|
||||
process.exit();
|
||||
});
|
||||
mpris.utils.getIPCMain().on('mpris:playbackTimeDidChange', (event: any, time: number) => {
|
||||
player.getPosition = () => time;
|
||||
})
|
||||
|
||||
mpris.utils.getIPCMain().on('repeatModeDidChange', (_e: any, mode: number) => {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
player.loopStatus = Player.LOOP_STATUS_NONE;
|
||||
break;
|
||||
case 1:
|
||||
player.loopStatus = Player.LOOP_STATUS_TRACK;
|
||||
break;
|
||||
case 2:
|
||||
player.loopStatus = Player.LOOP_STATUS_PLAYLIST;
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
mpris.utils.getIPCMain().on('shuffleModeDidChange', (_e: any, mode: number) => {
|
||||
player.shuffle = mode === 1
|
||||
})
|
||||
|
||||
mpris.player = player;
|
||||
}
|
||||
|
@ -93,9 +102,9 @@ export default class mpris {
|
|||
/**
|
||||
* Update M.P.R.I.S Player Attributes
|
||||
*/
|
||||
private static updatePlayer(attributes: any) {
|
||||
private static updateMetaData(attributes: any) {
|
||||
|
||||
const MetaData = {
|
||||
mpris.player.metadata = {
|
||||
'mpris:trackid': mpris.player.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
|
||||
'mpris:length': attributes.durationInMillis * 1000, // In microseconds
|
||||
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
|
||||
|
@ -103,33 +112,12 @@ export default class mpris {
|
|||
'xesam:album': `${attributes.albumName}`,
|
||||
'xesam:artist': [`${attributes.artistName}`],
|
||||
'xesam:genre': attributes.genreNames
|
||||
}
|
||||
|
||||
if (mpris.player.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) {
|
||||
return
|
||||
}
|
||||
|
||||
mpris.player.metadata = MetaData;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update M.P.R.I.S Player State
|
||||
* @private
|
||||
* @param attributes
|
||||
*/
|
||||
private static updatePlayerState(attributes: any) {
|
||||
switch (attributes.status) {
|
||||
case true: // Playing
|
||||
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_PLAYING;
|
||||
break;
|
||||
case false: // Paused
|
||||
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_PAUSED;
|
||||
break;
|
||||
default:
|
||||
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_STOPPED;
|
||||
break
|
||||
}
|
||||
}
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Clear state
|
||||
|
@ -143,26 +131,12 @@ export default class mpris {
|
|||
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_STOPPED;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
mpris.utils = utils
|
||||
|
||||
console.debug(`[Plugin][${mpris.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onReady(_: any): void {
|
||||
console.debug(`[Plugin][${mpris.name}] Ready.`);
|
||||
console.debug(`[${mpris.name}:onReady] Ready.`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,9 +161,8 @@ export default class mpris {
|
|||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
console.debug(`[Plugin][${mpris.name}] onPlaybackStateDidChange.`);
|
||||
mpris.updatePlayerState(attributes)
|
||||
onPlaybackStateDidChange(attributes: any): void {
|
||||
mpris.player.playbackStatus = attributes?.status ? Player.PLAYBACK_STATUS_PLAYING : Player.PLAYBACK_STATUS_PAUSED
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,8 +171,7 @@ export default class mpris {
|
|||
*/
|
||||
@mpris.linuxOnly
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
console.debug(`[Plugin][${mpris.name}] onMetadataDidChange.`);
|
||||
mpris.updatePlayer(attributes);
|
||||
mpris.updateMetaData(attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
12
src/main/providers/local/db/index.ts
Normal file
12
src/main/providers/local/db/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import * as PouchDB from 'pouchdb-node';
|
||||
import {join} from 'path';
|
||||
import {app} from "electron";
|
||||
PouchDB.plugin(require('pouchdb-upsert'));
|
||||
export class ProviderDB {
|
||||
public static db: any = null
|
||||
static init() {
|
||||
if (ProviderDB.db == null){
|
||||
ProviderDB.db = new PouchDB(join(app.getPath('userData'), 'tracksdb'))
|
||||
}
|
||||
}
|
||||
}
|
180
src/main/providers/local/index.ts
Normal file
180
src/main/providers/local/index.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import { ProviderDB } from "./db";
|
||||
import * as path from 'path';
|
||||
const { readdir } = require('fs').promises;
|
||||
import { utils } from '../../base/utils';
|
||||
import * as mm from 'music-metadata';
|
||||
import {Md5} from 'ts-md5/dist/md5';
|
||||
import e from "express";
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export class LocalFiles {
|
||||
static localSongs: any = [];
|
||||
static localSongsArts: any = [];
|
||||
public static DB = ProviderDB.db;
|
||||
static eventEmitter = new EventEmitter();
|
||||
|
||||
static getDataType(item_id : String | any){
|
||||
if ((item_id ?? ('')).startsWith('ciderlocalart'))
|
||||
return 'artwork'
|
||||
else if ((item_id ?? ('')).startsWith('ciderlocal'))
|
||||
return 'track'
|
||||
}
|
||||
|
||||
static async sendOldLibrary() {
|
||||
ProviderDB.init()
|
||||
let rows = (await ProviderDB.db.allDocs({include_docs: true,
|
||||
attachments: true})).rows.map((item: any)=>{return item.doc})
|
||||
let tracks = rows.filter((item: any) => {return this.getDataType(item._id) == "track"})
|
||||
let arts = rows.filter((item: any) => {return this.getDataType(item._id) == "artwork"})
|
||||
this.localSongs = tracks;
|
||||
this.localSongsArts = arts;
|
||||
return tracks;
|
||||
}
|
||||
|
||||
static async scanLibrary() {
|
||||
ProviderDB.init()
|
||||
let folders = utils.getStoreValue("libraryPrefs.localPaths")
|
||||
if (folders == null || folders.length == null || folders.length == 0) folders = []
|
||||
let files: any[] = []
|
||||
for (var folder of folders) {
|
||||
// get files from the Music folder
|
||||
files = files.concat(await LocalFiles.getFiles(folder))
|
||||
}
|
||||
|
||||
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"]
|
||||
let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1)));
|
||||
let metadatalist = []
|
||||
let metadatalistart = []
|
||||
let numid = 0;
|
||||
for (var audio of audiofiles) {
|
||||
try {
|
||||
const metadata = await mm.parseFile(audio);
|
||||
let lochash = Md5.hashStr(audio) ?? numid;
|
||||
if (metadata != null) {
|
||||
let form = {
|
||||
"id": "ciderlocal" + lochash,
|
||||
"_id": "ciderlocal" + lochash,
|
||||
"type": "podcast-episodes",
|
||||
"href": audio,
|
||||
"attributes": {
|
||||
"artwork": {
|
||||
"width": 3000,
|
||||
"height": 3000,
|
||||
"url": "/ciderlocalart/" + "ciderlocal" + lochash,
|
||||
},
|
||||
"topics": [],
|
||||
"url": "",
|
||||
"subscribable": true,
|
||||
"mediaKind": "audio",
|
||||
"genreNames": [
|
||||
""
|
||||
],
|
||||
// "playParams": {
|
||||
// "id": "ciderlocal" + numid,
|
||||
// "kind": "podcast",
|
||||
// "isLibrary": true,
|
||||
// "reporting": false },
|
||||
"trackNumber": metadata.common.track?.no ?? 0,
|
||||
"discNumber": metadata.common.disk?.no ?? 0,
|
||||
"name": metadata.common.title ?? audio.substring(audio.lastIndexOf('\\') + 1),
|
||||
"albumName": metadata.common.album,
|
||||
"artistName": metadata.common.artist,
|
||||
"copyright": metadata.common.copyright ?? "",
|
||||
"assetUrl": "file:///" + audio,
|
||||
"contentAdvisory": "",
|
||||
"releaseDateTime": `${metadata?.common?.year ?? '2022'}-05-13T00:23:00Z`,
|
||||
"durationInMillis": Math.floor((metadata.format.duration ?? 0) * 1000),
|
||||
"bitrate": Math.floor((metadata.format?.bitrate ?? 0) / 1000),
|
||||
"offers": [
|
||||
{
|
||||
"kind": "get",
|
||||
"type": "STDQ"
|
||||
}
|
||||
],
|
||||
"contentRating": "clean"
|
||||
},
|
||||
flavor: Math.floor((metadata.format?.bitrate ?? 0) / 1000),
|
||||
localFilesMetadata: {
|
||||
lossless: metadata.format?.lossless,
|
||||
container: metadata.format?.container,
|
||||
bitDepth: metadata.format?.bitsPerSample ?? 0,
|
||||
sampleRate: metadata.format?.sampleRate ?? 0,
|
||||
},
|
||||
};
|
||||
let art = {
|
||||
id: "ciderlocal" + lochash,
|
||||
_id: "ciderlocalart" + lochash,
|
||||
url: metadata.common.picture != undefined ? metadata.common.picture[0].data.toString('base64') : "",
|
||||
}
|
||||
metadatalistart.push(art)
|
||||
numid += 1;
|
||||
ProviderDB.db.putIfNotExists(form)
|
||||
ProviderDB.db.putIfNotExists(art)
|
||||
metadatalist.push(form)
|
||||
|
||||
if (this.localSongs.length === 0 && numid % 10 === 0) { // send updated chunks only if there is no previous database
|
||||
this.eventEmitter.emit('newtracks', metadatalist)}
|
||||
}
|
||||
} catch (e) {console.error("localfiles error:", e)}
|
||||
}
|
||||
// console.log('metadatalist', metadatalist);
|
||||
this.localSongs = metadatalist;
|
||||
this.localSongsArts = metadatalistart;
|
||||
return metadatalist;
|
||||
}
|
||||
static async getFiles(dir: any) {
|
||||
const dirents = await readdir(dir, { withFileTypes: true });
|
||||
const files = await Promise.all(dirents.map((dirent: any) => {
|
||||
const res = path.resolve(dir, dirent.name);
|
||||
return dirent.isDirectory() ? this.getFiles(res) : res;
|
||||
}));
|
||||
return Array.prototype.concat(...files);
|
||||
}
|
||||
|
||||
static async cleanUpDB(){
|
||||
let folders = utils.getStoreValue("libraryPrefs.localPaths")
|
||||
let rows = (await ProviderDB.db.allDocs({include_docs: true,
|
||||
attachments: true})).rows.map((item: any)=>{return item.doc})
|
||||
let tracks = rows.filter((item: any) => {return this.getDataType(item._id) == "track" && !folders.some((i: String) => {return item["attributes"]["assetUrl"].startsWith("file:///" + i)})})
|
||||
let hashs = tracks.map((i: any) => {return i._id})
|
||||
for (let hash of hashs){
|
||||
try{
|
||||
ProviderDB.db.get(hash).then(function (doc: any) {
|
||||
return ProviderDB.db.remove(doc);
|
||||
});} catch(e){}
|
||||
try{
|
||||
ProviderDB.db.get(hash.replace('ciderlocal','ciderlocalart')).then(function (doc: any) {
|
||||
return ProviderDB.db.remove(doc);
|
||||
});} catch(e){}
|
||||
}
|
||||
}
|
||||
|
||||
static setupHandlers () {
|
||||
const app = utils.getExpress()
|
||||
console.log("Setting up handlers for local files")
|
||||
app.get("/ciderlocal/:songs", (req: any, res: any) => {
|
||||
const audio = atob(req.params.songs.replace(/_/g, '/').replace(/-/g, '+'));
|
||||
//console.log('auss', audio)
|
||||
let data = {
|
||||
data:
|
||||
LocalFiles.localSongs.filter((f: any) => audio.split(',').includes(f.id))
|
||||
};
|
||||
res.send(data);
|
||||
});
|
||||
|
||||
app.get("/ciderlocalart/:songs", (req: any, res: any) => {
|
||||
const audio = req.params.songs;
|
||||
// metadata.common.picture[0].data.toString('base64')
|
||||
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
||||
res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString());
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
|
||||
let data =
|
||||
LocalFiles.localSongsArts.filter((f: any) => f.id == audio);
|
||||
res.status(200).send(Buffer.from(data[0]?.url, 'base64'));
|
||||
});
|
||||
|
||||
return app
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue