From 87504e8f79095bf64b218047e8deb70e618116ec Mon Sep 17 00:00:00 2001 From: Matthew James Date: Tue, 24 Jan 2017 17:34:52 +0000 Subject: [PATCH] Pre-release v1.0.40 Beta Sing! Sync API functionality Should fix #66 Fixes #64 Fixes refresh bug --- app.js | 112 ++---- defaults.json | 30 ++ package.json | 2 +- windows/about/preload.js | 12 +- windows/about/shortcuts.js | 23 ++ windows/about/window.js | 60 ++++ windows/facebook/preload.js | 6 +- windows/facebook/window.js | 24 ++ windows/preferences/behaviour.js | 35 ++ windows/preferences/preferences.html | 24 +- windows/preferences/preload.js | 133 +++---- windows/preferences/shortcuts.js | 23 ++ windows/preferences/window.js | 75 ++++ windows/spotify/Sing!/sing.js | 513 ++++++++++++++++++++------- windows/spotify/Sing!/sync.js | 57 +++ windows/spotify/Sing!/time.png | Bin 706 -> 0 bytes windows/spotify/Sing!/ui.html | 204 ++++++++++- windows/spotify/appmenu.js | 61 ++++ windows/spotify/behaviour.js | 86 +++++ windows/spotify/controller.js | 440 +++++++++++------------ dbus.js => windows/spotify/dbus.js | 0 windows/spotify/interface.js | 125 ------- windows/spotify/preload.js | 214 ++++------- windows/spotify/shortcut-bar.js | 40 --- windows/spotify/shortcuts.js | 35 ++ windows/spotify/theme.js | 196 ++++++++++ windows/spotify/tray.js | 109 +++--- windows/spotify/user.js | 74 ++-- windows/spotify/window-menu.js | 84 ----- windows/spotify/window.js | 107 ++++++ windows/windows.js | 232 ------------ 31 files changed, 1855 insertions(+), 1281 deletions(-) create mode 100644 defaults.json create mode 100644 windows/about/shortcuts.js create mode 100644 windows/about/window.js create mode 100644 windows/facebook/window.js create mode 100644 windows/preferences/behaviour.js create mode 100644 windows/preferences/shortcuts.js create mode 100644 windows/preferences/window.js create mode 100644 windows/spotify/Sing!/sync.js delete mode 100755 windows/spotify/Sing!/time.png create mode 100755 windows/spotify/appmenu.js create mode 100644 windows/spotify/behaviour.js rename dbus.js => windows/spotify/dbus.js (100%) delete mode 100755 windows/spotify/interface.js delete mode 100644 windows/spotify/shortcut-bar.js create mode 100644 windows/spotify/shortcuts.js create mode 100755 windows/spotify/theme.js delete mode 100755 windows/spotify/window-menu.js create mode 100755 windows/spotify/window.js delete mode 100755 windows/windows.js diff --git a/app.js b/app.js index ba962cf..53ce313 100755 --- a/app.js +++ b/app.js @@ -2,14 +2,10 @@ * @author Matthew James * App behaviour class */ -const OS = require('os'); const FS = require('fs'); -const request = require('request'); const electron = require('electron'); const BrowserWindow = electron.BrowserWindow; const app = electron.app; -const sanitize = require('sanitize-filename') -const MXM = require('node-unofficialmxm'); //Let's load up our application if(typeof(electron) != 'object' && !electron.app){ console.log('The Electron installation cannot be found. Aborting...'); @@ -21,43 +17,8 @@ App = (function(){ constructor(){ this.settings = require('./app-settings')( `${App.paths.home}/preferences.json`, - { - CloseToTray: true, - CloseToController: false, - ShowApplicationMenu: true, - ShowTray: true, - TrayIcon: 'lime', - Notifications: { - ShowTrackChange: true, - ShowPlaybackPlaying: true, - ShowPlaybackPaused: true, - ShowPlaybackStopped: true, - OnlyWhenFocused: true - }, - NavBar: { - Follow: true, - User: true, - Radio: true, - YourMusic: true, - Browse: true, - Settings: true, - Search: true, - Sing: true - }, - AlbumCacheDisabled: false, - Theme: 'dark', - StartOnLogin: false, - StartHidden: false, - lastURL: null - } + require('./defaults.json') ); - this.settings.open((err, data) => { - if(err){ - console.log("The settings are corrupt, cannot continue."); - process.exit(-1); - } - }); - this.dbus = require('./dbus')(App.names.process); require('./plugins')(app); //Set Cache app.setPath('userData', App.paths.home); @@ -71,9 +32,19 @@ App = (function(){ process.title = App.names.process; //When Electron has loaded, start opening the window. app.on('ready', () => { - var Spotify = require('./windows/windows')(this, electron, BrowserWindow); - _spotify = new Spotify(); + this.settings.open((err, data) => { + if(err){ + console.log("The settings are corrupt, cannot continue."); + process.exit(-1); + } + var Spotify = require('./windows/spotify/window'); + _spotify = new Spotify(); + }); }); + this.setApplicationMenu = app.setApplicationMenu; + this.quit = app.quit; + this.openLink = electron.shell.openExternal; + this.setLoginItemSettings = app.setLoginItemSettings; app.on('quit', () => { console.log('Exiting...'); }); @@ -98,7 +69,7 @@ App = (function(){ _spotify.unmaximize(); }); } - get VERSION(){ + get version(){ return electron.app.getVersion(); } static get names(){ @@ -108,32 +79,14 @@ App = (function(){ project: 'Spotify Web Player for Linux' } } - get request(){ - return request; - } get names(){ return App.names; } - static get HOST(){ + static get host(){ return 'https://play.spotify.com'; } - get HOST(){ - return App.HOST; - } - get electron(){ - return electron; - } - get globalShortcut(){ - return electron.globalShortcut; - } - get mxm(){ - return MXM; - } - get process(){ - return process; - } - get console(){ - return console; + get host(){ + return App.host; } static get paths(){ var USER_PATH = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; @@ -185,35 +138,16 @@ App = (function(){ get icon(){ return App.icon; } - get os(){ - return OS; - } - static get HOSTNAME(){ - return os.hostname(); - } - get HOSTNAME(){ - return App.HOSTNAME; - } - static get FILEPATH(){ - process.cwd(); - } - get FILEPATH(){ - App.FILEPATH; - } get spotify(){ return _spotify; } - clearCache(){ - _spotify.loadURL("about:blank"); - _spotify.webContents.session.clearCache(() => { - _spotify.webContents.session.clearStorageData(() => { - console.log("Cleared session and cache."); - _spotify.loadURL(this.HOST); - }); - }); - } } return App; })(); const APP = new App(); -global.props = APP; +global.app = APP; +process.on('SIGINT', () => { + console.log('Received SIGINT (Ctrl-C). Exiting...'); + app.quit(); + process.exit(0); +}); diff --git a/defaults.json b/defaults.json new file mode 100644 index 0000000..7ac4d84 --- /dev/null +++ b/defaults.json @@ -0,0 +1,30 @@ +{ + "CloseToTray":true, + "CloseToController":false, + "ShowApplicationMenu":true, + "ShowTray":true, + "TrayIcon":"lime", + "Notifications": { + "ShowTrackChange":true, + "ShowPlaybackPlaying":true, + "ShowPlaybackPaused":true, + "ShowPlaybackStopped":true, + "OnlyWhenFocused":false + }, + "NavBar": { + "Follow":true, + "User":true, + "Radio":true, + "YourMusic":true, + "Browse":true, + "Settings":true, + "Search":true, + "Sing":true + }, + "AlbumArtSize": "large", + "AlbumCacheDisabled":false, + "Theme":"dark", + "StartOnLogin":false, + "StartHidden":false, + "lastURL":"" +} diff --git a/package.json b/package.json index e051753..7bc2c18 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "spotifywebplayer", "productName": "Spotify Web Player for Linux", - "version": "1.0.35", + "version": "1.0.40", "description": "An Electron wrapper of Spotify Web Player to increase desktop integration for a stable Spotify Player for Linux replacement", "main": "main.js", "postinstall": "install-app-deps", diff --git a/windows/about/preload.js b/windows/about/preload.js index 408f9cc..7e2b565 100755 --- a/windows/about/preload.js +++ b/windows/about/preload.js @@ -2,15 +2,11 @@ * @author Matthew James * Preload script for about window */ -global.remote = require('electron').remote; -let props = remote.getGlobal('props'); -global.props = props; - +global.MAIN = require('electron').ipcRenderer document.onreadystatechange = function(){ window.$ = window.jQuery = require('../spotify/jquery'); - interface = require('../spotify/interface'); + $('#logo').attr('src', __dirname + '/Spotify_Logo_RGB_White.png'); - $('#app_title_and_version').html(props.names.project + '
v' + props.electron.app.getVersion()); libraries = { 'Support Homepage': 'https://github.com/Quacky2200/Spotify-Web-Player-for-Linux', @@ -23,7 +19,7 @@ document.onreadystatechange = function(){ }; html = ''; function click(){ - props.electron.shell.openExternal($(this).attr('href')); + MAIN.send('message-for-Spotify', `:eval('app.openLink(\`${$(this).attr('href')}\`)')`); return false; } for (var library in libraries){ @@ -32,4 +28,4 @@ document.onreadystatechange = function(){ $('#libraries').html(html); //For all clicks, open externally $('a').click(click); -}; \ No newline at end of file +}; diff --git a/windows/about/shortcuts.js b/windows/about/shortcuts.js new file mode 100644 index 0000000..026af23 --- /dev/null +++ b/windows/about/shortcuts.js @@ -0,0 +1,23 @@ +const {globalShortcut, BrowserWindow} = require('electron'); +function Shortcuts(){ + this.toggle = (toggle) => { + if (toggle){ + var showdevtools = function(){ + var win = BrowserWindow.getFocusedWindow(); + if (!win.isDevToolsOpened()){ + win.openDevTools() + } else { + win.closeDevTools(); + } + } + globalShortcut.register('CommandOrControl+Shift+I', showdevtools); + globalShortcut.register('F12', showdevtools); + globalShortcut.register('CommandOrControl+W', () => { + BrowserWindow.getFocusedWindow().close() + }); + } else { + globalShortcut.unregisterAll(); + } + } +} +module.exports = new Shortcuts(); diff --git a/windows/about/window.js b/windows/about/window.js new file mode 100644 index 0000000..15b50d3 --- /dev/null +++ b/windows/about/window.js @@ -0,0 +1,60 @@ +const {BrowserWindow, ipcMain} = require('electron'); +const shortcuts = require('./shortcuts'); +class About extends BrowserWindow{ + constructor(){ + var ABOUT_WIDTH = 600; + var ABOUT_HEIGHT = 525; + super({ + title: 'About', + icon: App.icon, + width: ABOUT_WIDTH, + height: ABOUT_HEIGHT, + minWidth: ABOUT_WIDTH, + minHeight: ABOUT_HEIGHT, + maxWidth: ABOUT_WIDTH, + maxHeight: ABOUT_HEIGHT, + resizable: false, + show: false, + webPreferences: {preload:`${__dirname}/preload.js`} + }); + this.loadURL(`file://${__dirname}/about.html`); + this.setMenu(null); + this.webContents.once('dom-ready', () => { + theme = require('../spotify/theme'); + this.do(` + $('#app_title_and_version').html('${app.names.project}
v${app.version}'); + `); + ipcMain.on('message-for-About', function(e, a) { + console.log('ABOUT EVAL:', a); + if (typeof(a) !== 'string') return; + if (a.match(/(:eval)/)) return eval(a.slice(7, a.length - 2)); + }) + theme.refresh(); + this.show(); + }); + this.on('focus', () => shortcuts.toggle(true)); + this.on('blur', () => shortcuts.toggle(false)); + } + do(str){ + this.webContents.executeJavaScript(str); + } + doFunc(variables, func) { + var vars = ""; + for (var curvar in variables){ + if (variables.hasOwnProperty(curvar)) { + if (typeof(variables[curvar]) == 'function') { + vars += `var ${curvar} = ${variables[curvar].toString()};\n`; + } else if (typeof(variables[curvar]) == 'object'){ + vars += `var ${curvar} = ${JSON.stringify(variables[curvar])};\n`; + } else if (typeof(variables[curvar]) == 'string'){ + vars += `var ${curvar} = \`${variables[curvar]}\`;\n`; + } else { + vars += `var ${curvar} = ${variables[curvar]};\n`; //Numbers, bools? + } + } + } + vars += `\n(${func.toString()})();\n`; + this.do(vars) + } +} +module.exports = About; diff --git a/windows/facebook/preload.js b/windows/facebook/preload.js index a28f9da..937d3d1 100755 --- a/windows/facebook/preload.js +++ b/windows/facebook/preload.js @@ -1,5 +1,5 @@ const remote = require('electron').remote; -var props = remote.getGlobal('props'); +var props = remote.getGlobal('app'); var popup = remote.getCurrentWindow(); //Thanks to http://stackoverflow.com/questions/12049620/how-to-get-get-variables-value-in-javascript @@ -31,8 +31,8 @@ if(window.location.href.indexOf("oauth?") >= 0){ props.spotify.webContents.once('dom-ready', function(){ //Click the button again to make FB check the authentication with our newly created cookie props.spotify.webContents.executeJavaScript("document.getElementById('fb-signup-btn').click();"); - //Close the FB window, we don't need it anymore + //Close the FB window, we don't need it anymore popup.close(); }); } -} \ No newline at end of file +} diff --git a/windows/facebook/window.js b/windows/facebook/window.js new file mode 100644 index 0000000..f47ce20 --- /dev/null +++ b/windows/facebook/window.js @@ -0,0 +1,24 @@ +const {BrowserWindow} = require('electron'); +class FacebookPopup extends BrowserWindow{ + constructor(name, url, session){ + super({ + title: name, + minWidth: 550, + minHeight: 280, + width: 550, + height: 280, + show: true, + icon: App.icon, + session: session, + webPreferences: { + preload: `${__dirname}/facebook/preload.js`, + nodeIntegration: false, + plugins: true + } + }); + this.loadURL(url); + this.setMenu(null); + if(app.settings.ShowDevTools) this.openDevTools(); + } +} +module.exports = FacebookPopup; diff --git a/windows/preferences/behaviour.js b/windows/preferences/behaviour.js new file mode 100644 index 0000000..86b2835 --- /dev/null +++ b/windows/preferences/behaviour.js @@ -0,0 +1,35 @@ +const theme = require('../spotify/theme'); + +module.exports = function(){ + + app.spotify.PreferenceInstance.do(` + ${JSON.stringify(theme.allThemeNames)}.forEach((theme) => { + $('select[name*="Theme"]').append(\`\`); + }); + function recursivelySetupSettings(settings, skip, prefix){ + for(var setting in settings){ + if(skip.indexOf(setting) != -1) continue; + if(typeof(settings[setting]) == 'object'){ + recursivelySetupSettings(settings[setting], skip, (prefix ? prefix + '.' : '') + setting + '.'); + } else { + var selector = 'input[name="' + (prefix || '') + setting + '"]'; + $(selector).prop('checked', settings[setting]); + $(selector).on('change', function() { + MAIN.send('message', \`:eval('app.settings.\${$(this).attr('name')} = \${$(this).prop('checked')}; app.settings.save(); theme.refresh();')\`) + }); + } + } + } + recursivelySetupSettings(${JSON.stringify(app.settings)}, ['Theme', 'AlbumArtSize' , 'lastURL', 'AlbumCacheDisabled']); + + $('input[name*="AlbumCacheDisabled"]').attr('checked', ${!app.settings.AlbumCacheDisabled}); + $('select[name="Theme"]').val('${app.settings.Theme}'); + $('select[name="TrayIcon"]').val('${app.settings.TrayIcon}'); + $('select[name="AlbumArtSize"]').val('${app.settings.AlbumArtSize}'); + $('select[name="AlbumArtSize"]').change(function() { + MAIN.send('message', \`:eval('app.settings.AlbumArtSize = '\${$(this).val()}';app.settings.save()')\`); + }); + + `); + theme.refresh(); +} diff --git a/windows/preferences/preferences.html b/windows/preferences/preferences.html index 8f3c0cc..6f3d5a1 100755 --- a/windows/preferences/preferences.html +++ b/windows/preferences/preferences.html @@ -260,17 +260,17 @@

Show 'Your Music' Icon

- +

Show Follow Icon

@@ -328,7 +328,19 @@

Store Album Art?

-

Clean AlbumCache

+

Album Art Cache Size

+ Choose the size of the Album Art that will be cached
Note: Larger images will take up more disk space over time
+ + + + + + +

Clean Album Cache

This will remove all album art covers @@ -337,7 +349,7 @@

Clean AlbumCache

-

Clean LyricCache

+

Clean Lyric Cache

This will remove all the lyrics you have saved @@ -348,4 +360,4 @@

Clean LyricCache

- \ No newline at end of file + diff --git a/windows/preferences/preload.js b/windows/preferences/preload.js index 37a16b5..d060f6e 100755 --- a/windows/preferences/preload.js +++ b/windows/preferences/preload.js @@ -2,107 +2,70 @@ * @author Matthew James * Preload script for preferences screen */ -global.remote = require('electron').remote; -let fs = require('fs'); -let props = remote.getGlobal('props'); -var AutoLaunch = require('auto-launch'); -let autolaunch = new AutoLaunch({ - name: props.names.project, - //A 10 second sleep allows the desktop to load, otherwise no dice... :( - path: '/bin/bash -c "sleep 10 && . ' + props.process.cwd() + '/spotifywebplayer"' -}); -global.props = props; +global.MAIN = require('electron').ipcRenderer; -var deleteFolderRecursive = function(path) { - if( fs.existsSync(path) ) { - fs.readdirSync(path).forEach(function(file,index){ - var curPath = path + "/" + file; - if(fs.lstatSync(curPath).isDirectory()) { // recurse - deleteFolderRecursive(curPath); - } else { // delete file - fs.unlinkSync(curPath); - } - }); - fs.rmdirSync(path); - } -}; document.onreadystatechange = function(){ if (document.readyState !== 'complete') return; window.$ = window.jQuery = require('../spotify/jquery'); - interface = require('../spotify/interface'); - function recursivelySetupSettings(settings, skip, prefix){ - for(var setting in settings){ - if(skip.indexOf(setting) != -1) continue; - if(typeof(settings[setting]) == 'object'){ - recursivelySetupSettings(settings[setting], skip, (prefix ? prefix + '.' : '') + setting + '.'); - } else { - var selector = 'input[name=\'' + (prefix ? prefix : '') + setting + '\']'; - $(selector).prop('checked', settings[setting]); - $(selector).attr('onchange', - 'props.settings.' + (prefix ? prefix : '') + setting + ' = $(this).prop(\'checked\');props.settings.save();'); - } - } - } - recursivelySetupSettings(props.settings, ['Theme', 'AlbumCacheDisabled']); - + MAIN.send('message-for-Preferences', `:eval('require(\'./behaviour\')()')`); $('input[name=\'StartOnLogin\']').change(function(){ - if (props.process.platform == 'linux'){ - if($(this).prop('checked')){ - autolaunch.enable(); + MAIN.send('message-for-Preferences', `:eval(' + if (process.platform == 'linux'){ + if(${$(this).prop('checked')}) { + autolaunch.enable(); + } else { + autolaunch.disable(); + } } else { - autolaunch.disable(); + app.setLoginItemSettings({openAtLogin: ${$(this).prop('checked')}}) } - } else { - app.setLoginItemSettings({openAtLogin: $(this).prop('checked')}); - } + ')`); }); - $('input[name*="ShowTray"]').change(function(){ - props.settings.ShowTray = $(this).prop('checked'); - props.settings.save(); - props.spotify.webContents.executeJavaScript('tray.toggleTray(' + props.settings.ShowTray + ')'); + $('input[name*="ShowTray"]').change(function() { + MAIN.send('message-for-Spotify', `:eval(' + app.settings.ShowTray = ${$(this).prop('checked')}; + app.settings.save(); + tray.toggle(app.settings.ShowTray); + ')`) }); - $('input[name*="ShowApplicationMenu"]').change(function(){ - props.settings.ShowApplicationMenu = $(this).prop('checked'); - props.settings.save(); - props.spotify.webContents.executeJavaScript('appMenu.toggleMenu(' + props.settings.ShowApplicationMenu + ')'); + $('input[name*="ShowApplicationMenu"]').change(function() { + MAIN.send('message-for-Spotify', `:eval(' + app.settings.ShowApplicationMenu = ${$(this).prop('checked')}; + app.settings.save(); + appMenu.toggle(app.settings.ShowApplicationMenu); + ')`) }); - $('input[name*="AlbumCacheDisabled"]').change(function(){ - props.settings.AlbumCacheDisabled = !$(this).prop('checked'); - props.settings.save(); - props.spotify.webContents.executeJavaScript('controller.albumCacheDisabled = ' + props.settings.AlbumCacheDisabled + ';'); + $('input[name*="AlbumCacheDisabled"]').change(function() { + MAIN.send('message-for-Spotify', `:eval(' + app.settings.AlbumCacheDisabled = ${!$(this).prop('checked')}; + app.settings.save(); + controller.albumCacheDisabled = app.settings.AlbumCacheDisabled; + ')`) }); - $('input[name*=\'NavBar\'], select').change(() => { - interface.refresh(); - props.spotify.webContents.executeJavaScript('interface.refresh()'); - }); - interface.allThemeNames.forEach((theme) => { - $('select[name*=\'Theme\']').append(``); - }); $('select[name=\'Theme\']').change(function(){ - interface.themeName = $(this).val(); - props.spotify.webContents.executeJavaScript('interface.refresh()'); - setTimeout(() => { - console.log(props.spotify.AboutInstance) - if(props.spotify.AboutInstance) props.spotify.AboutInstance.webContents.executeJavaScript('interface.refresh()'); - }, 50) + MAIN.send('message', `:eval(' + theme.name = '${$(this).val()}'; + theme.refresh(); + ')`); }); + $('select[name=\'TrayIcon\']').change(function(){ - props.settings.TrayIcon = $(this).val(); - props.spotify.webContents.executeJavaScript('tray.toggleTray(false);tray.toggleTray(true);'); - props.settings.save(); + console.log($(this).val()); + MAIN.send('message-for-Spotify', `:eval(' + app.settings.TrayIcon = '${$(this).val()}'; + app.settings.save(); + tray.toggle(false); + tray.toggle(true); + ')`); }); - if(!props.settings.AlbumCacheDisabled) $('input[name*="AlbumCacheDisabled"]').attr('checked', 'true'); - $('select[name=\'Theme\']').val(props.settings.Theme); - $('select[name=\'TrayIcon\']').val(props.settings.TrayIcon); - $('a.clean-album-cache').click(() => { - deleteFolderRecursive(props.albumCache); - }); + $('a.clean-album-cache').click(() => MAIN.send('message-for-Preferences', `:eval(' + deleteFolderRecursive(app.paths.caches.albums); + ')`)); - $('a.clean-lyric-cache').click(() => { - deleteFolderRecursive(props.lyricCache); - }); - interface.refresh(); -}; \ No newline at end of file + $('a.clean-lyric-cache').click(() => MAIN.send('message-for-Preferences', `:eval(' + deleteFolderRecursive(app.paths.caches.lyrics); + ')`)); +}; diff --git a/windows/preferences/shortcuts.js b/windows/preferences/shortcuts.js new file mode 100644 index 0000000..026af23 --- /dev/null +++ b/windows/preferences/shortcuts.js @@ -0,0 +1,23 @@ +const {globalShortcut, BrowserWindow} = require('electron'); +function Shortcuts(){ + this.toggle = (toggle) => { + if (toggle){ + var showdevtools = function(){ + var win = BrowserWindow.getFocusedWindow(); + if (!win.isDevToolsOpened()){ + win.openDevTools() + } else { + win.closeDevTools(); + } + } + globalShortcut.register('CommandOrControl+Shift+I', showdevtools); + globalShortcut.register('F12', showdevtools); + globalShortcut.register('CommandOrControl+W', () => { + BrowserWindow.getFocusedWindow().close() + }); + } else { + globalShortcut.unregisterAll(); + } + } +} +module.exports = new Shortcuts(); diff --git a/windows/preferences/window.js b/windows/preferences/window.js new file mode 100644 index 0000000..d62b858 --- /dev/null +++ b/windows/preferences/window.js @@ -0,0 +1,75 @@ +const {BrowserWindow, ipcMain} = require('electron'); +const shortcuts = require('./shortcuts'); +const fs = require('fs'); +class Preferences extends BrowserWindow{ + constructor(){ + var PREF_WIDTH = 800; + var PREF_HEIGHT = 450; + super({ + title: 'Preferences', + icon: app.icon, + width: PREF_WIDTH, + height: PREF_HEIGHT, + minWidth: PREF_WIDTH, + minHeight: PREF_HEIGHT, + maxWidth: PREF_WIDTH, + maxHeight: PREF_HEIGHT, + resizable: false, + show: false, + webPreferences: {preload: `${__dirname}/preload.js`} + }); + this.loadURL(`file://${__dirname}/preferences.html`); + this.setMenu(null); + this.webContents.once('dom-ready', () => { + const AutoLaunch = require('auto-launch'); + let autolaunch = new AutoLaunch({ + name: app.names.project, + //A 10 second sleep allows the desktop to load, otherwise no dice... :( + path: '/bin/bash -c "sleep 10 && . ' + process.cwd() + '/spotifywebplayer"' + }); + var deleteFolderRecursive = function(path) { + if( fs.existsSync(path) ) { + fs.readdirSync(path).forEach(function(file,index){ + var curPath = path + "/" + file; + if(fs.lstatSync(curPath).isDirectory()) { // recurse + deleteFolderRecursive(curPath); + } else { // delete file + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(path); + } + }; + ipcMain.on('message-for-Preferences', (e, a) => { + console.log('PREFERENCES EVAL:', a); + if (typeof(a) !== 'string') return; + if (a.match(/(:eval)/)) return eval(a.slice(7, a.length - 2)); + }); + this.show(); + }); + this.on('focus', () => shortcuts.toggle(true)); + this.on('blur', () => shortcuts.toggle(false)); + } + do(str){ + this.webContents.executeJavaScript(str); + } + doFunc(variables, func) { + var vars = ""; + for (var curvar in variables){ + if (variables.hasOwnProperty(curvar)) { + if (typeof(variables[curvar]) == 'function') { + vars += `var ${curvar} = ${variables[curvar].toString()};\n`; + } else if (typeof(variables[curvar]) == 'object'){ + vars += `var ${curvar} = ${JSON.stringify(variables[curvar])};\n`; + } else if (typeof(variables[curvar]) == 'string'){ + vars += `var ${curvar} = \`${variables[curvar]}\`;\n`; + } else { + vars += `var ${curvar} = ${variables[curvar]};\n`; //Numbers, bools? + } + } + } + vars += `\n(${func.toString()})();\n`; + this.do(vars) + } +} +module.exports = Preferences; diff --git a/windows/spotify/Sing!/sing.js b/windows/spotify/Sing!/sing.js index 2e3ad44..6ba2f25 100755 --- a/windows/spotify/Sing!/sing.js +++ b/windows/spotify/Sing!/sing.js @@ -1,148 +1,397 @@ -//Elements -let ui, navButton, timeButton, timer; -let mxm = props.mxm; -//Load UI Element first -props.getUTF8File(`${__dirname}/ui.html`, function(err, data){ - if(err) console.log(err); - ui = data.toString(); - //Load the timer icon - props.getImageFileAsBase64(`${__dirname}/time.png`, function(err, data){ - if(err) console.log(err); - $('#sing-ui #timeButton').attr('src', `data:image/png;base64,${data}`); - $('#sing-ui #timeButton').click(function(){ - toggleLyricScroller(!$(this).is('active')); - $(this).toggleClass('active'); - }); - }); - //Read the microphone icon and add as a navbar icon - props.getImageFileAsBase64(__dirname + '/microphone.png', function(err, data){ - if (err) console.err(err); - button = ` -
  • +let mxm = require('node-unofficialmxm'); +let sync = require('./sync'); +let fs = require('fs'); +const Sing = function(){ + this.upload = sync.upload; + this.lastTrackURI = null; + this._isUIShowing = false; + //Load the UI + this.init = () => { + this.lastTrackURI = null; + this._isUIShowing = false; + app.getUTF8File(`${__dirname}/ui.html`, function(err, data){ + if(err) return console.error(err); + ui = data.toString(); + //Read the microphone icon and add as a navbar icon + app.getImageFileAsBase64(__dirname + '/microphone.png', function(err, data){ + if (err) return console.err(err); + button = ` +
  • - - Sing! + + Sing! -
  • - - `; - $('#main-nav #nav-items').append(button); - $('#nav-sing').click(()=>{ - if($('#sing-ui').length == 0){ - $('#wrapper').prepend(ui); - $('#nav-sing').addClass('active'); - singFuncs.load(controller.track.id, controller.track.name, controller.track.artists); - } else { - singFuncs.toggleUI(!singFuncs.isOpen()); - singFuncs.load(controller.track.id, controller.track.name, controller.track.artists); - } - }); - $('a[id*=\'nav-\']').not('#nav-sing').click(function(){ - if(singFuncs.isOpen()) singFuncs.toggleUI(false); - }); - }); -}); -function toggleLyricScroller(toggle){ - if(toggle && !timer){ - timer = setInterval(() => { - var currentTime = $('#bar-inner', $('#app-player').contents()).outerWidth(); - var fullTime = $('#bar-outer', $('#app-player').contents()).outerWidth(); - $('#sing-ui').scrollTop((currentTime / fullTime) * $('#sing-ui').outerHeight()); - }, 4000); - } else if(!toggle && timer) { - clearInterval(timer); - } -} -const singFuncs = { - load: (trackURI, trackName, trackArtist) => { - //Make lyrics cache if it doesn't exist - props.checkPathExists(props.paths.caches.lyrics, (err) => { - if (err){ - props.createDirectory(props.paths.caches.lyrics, (err) => { - if (err) console.log(err); - }); - } - }); - if (singFuncs.isOpen() && props.settings.NavBar.Sing && $('#sing-ui').attr('data-ref') != trackURI && trackURI && trackName && trackArtist){ - singFuncs.showLoader(); - var filepath = `${props.paths.caches.lyrics}/${trackArtist.split(',')[0]}-${trackName.match(/(\w+)/g).join('-')}.html`; - $('#sing-ui').attr('data-ref', trackURI); - props.checkPathExists(filepath, (err, data) => { - if (err){ - //Either exists or corrupt, let's get it again - mxm(trackName, trackArtist, (err, result) => { - singFuncs.hideLoader(); - if (err){ - $('#sing-ui #sing-container').html(`

    Sorry, I couldn't find the lyrics to this song.

    (${err})

    `); - } else { - lyrics = `

    ${result.name}

    ${result.artists}

    ${result.lyrics.replace(/\n/g, '
    ')}


    View on MusixMatch
    Lyrics from MusixMatch, using node-unofficialmxm

    `; - props.createUTF8File(filepath, lyrics, (err) => { - if (err) console.log(err); - $('#sing-ui #sing-container').html(lyrics); - $('#sing-ui #sing-container a').click(function(){ - props.electron.shell.openExternal($(this).attr('href')); - return false; + + `; + app.spotify.doFunc({button: button, ui: ui}, () => { + if ($('#nav-sing').length == 0) { + $('#main-nav #nav-items').append(button); + $('#wrapper').prepend(ui); + $('#nav-sing').click(() => { + var visible = $('#sing-ui').is('.active'); + $('#sing-ui').toggleClass('active', !visible); + $('#nav-sing').toggleClass('active', !visible); + MAIN.sendFunc({v: !visible}, () => sing._isUIShowing = v); + if (!visible) MAIN.sendFunc({}, () => { + sing.load(controller.track.id, controller.track.name, controller.track.artists) + }); + }); + $('#sing-ui #overlaydialog a:contains(\'Yes\')').click(() => { + //Update file and send online + MAIN.sendFunc({obj: obj}, () => { + obj.username = user.username; + obj.type = 'sync'; + app.createUTF8File(`${app.paths.caches.lyrics}/${obj.uri}.json`, JSON.stringify(obj), (err) => { + if(err) console.error(err); + sing.upload(obj.uri, obj.lyrics, obj.sync, obj.username, (err, data) => { + app.spotify.doFunc({}, () => { + $('#sing-ui #overlaydialog').toggleClass('active', false); + $('#sing-ui #overlaydialog a:contains(\'Save\')').off(); + $('#sing-sync').click(); + }); + sing.lastTrackURI = null; + }); }); }); - } + }); + $('#sing-ui #overlaydialog a:contains(\'No\')').click(() => { + //Put everything back to way it was + $('#sing-ui #overlaydialog').toggleClass('active', false); + $('#sing-sync').click(); + }); + $('a[id*=\'nav-\']').not('#nav-sing').click(function(){ + $('#sing-ui').fadeOut(function() { + var v = $(this).is(':visible'); + $('#nav-sing').toggleClass('active', v); + MAIN.sendFunc({v:v}, () => sing._isUIShowing = v); + }); + }); + } + }); + }); + }); + }; + this.showLoader = () => app.spotify.do(`$('#sing-ui #sing-container').html(\`
    \`)`); + this.hideLoader = () => app.spotify.do(`$('#sing-ui #sing-container').removeClass('.loader'); if($('#sing-ui #sing-container #lyrics')) MAIN.send(\`:eval('sing.toggleLyricScroller(\${$(this).is('active')})')\`);`); + //Fetch some lyrics + const fetchOffline = (filepath, cb) => { + //Load it into view! + app.getUTF8File(filepath, (err, data) => { + if (err) return cb(err); + var obj = JSON.parse(data); + var showLyrics = () => app.spotify.doFunc({obj: obj, art: controller.track.art[controller.track.art.length - 1][1]}, function(){ + $('#sing-sync').attr('title', (!!obj.sync ? 'Prompt me to sing' : 'Record lyrics to music')); + $('#sing-sync').toggleClass('available', !!obj.sync); + var footer = (obj.type == 'mxm' ? ` + View on MusixMatch
    Lyrics from MusixMatch, using node-unofficialmxm + ` : `Lyrics from Quacky2200 Sync API, posted by ${obj.username}`); + let scroll = function(i){ + $('#sing-ui').stop().animate({ + scrollTop: + ($('#lyrics span.part').eq(i).offset().top - $('#lyrics').offset().top) - + ($('#sing-container span').eq(i).height() / 2) }); - } else { - props.getUTF8File(filepath, (error, data) => { - singFuncs.hideLoader(); - if (err){ - $('#sing-ui #sing-container').html(`

    Sorry, I couldn't get the lyrics to this song.

    (${error})

    `); + $('#sing-container').toggleClass('sync', true); + $('#lyrics span.active').removeClass('active'); + $(`#lyrics span.part:eq(${i})`).addClass('active'); + }; + $('#sing-container').removeClass('record'); + $('#sing-sync').off(); + $('#sing-sync').click(function(){ + var hasSync = !!obj.sync; + $(this).toggleClass('active'); + $('#sing-ui').css('background-image', ``); + if ($(this).is('.active')){ + $('#sing-sync').attr('title', (hasSync ? 'Stop prompt' : 'Cancel synchronise recording')); + $('#sing-container').toggleClass('sync', true); + if (!hasSync) { + $('#sing-container span.buffer').toggleClass('active', true); + $('#sing-ui').animate({ + scrollTop: $('#sing-container span.buffer').offset().top - $('#lyrics').offset().top + }); + $('#sing-container').addClass('record'); + MAIN.sendFunc({obj: obj, scroll: scroll}, () => { + if (stopSync) stopSync(false); + var lyrics = obj.lyrics.split('\n'); + var sync = []; + var keys = require('electron').globalShortcut; + var last = {time: null, accumulate: 0}; + var lyricTimer = null; + var lyricTimerFunc = () => { + if (!controller.isPlaying) return + last.accumulate += 100; + if (last.time !== controller.track.position){ + last.accumulate = 0; + last.time = controller.track.position; + } + if (!sing._isUIShowing) return clearTimeout(lyricTimer); + }; + var syncPart = () => { + if (!sing._isUIShowing) return stopSync(false); + var i = (sync.length > 0 ? sync[sync.length - 1].index + 1 : 0); + sync.push({index: i, time: ((last.time / 1000) + last.accumulate)}); + app.spotify.doFunc({scroll: scroll, i: i}, () => {scroll(i)}); + if (i == (lyrics.length - 1)) { + obj.sync = sync; + controller.playPause(); + stopSync(false); + app.spotify.doFunc({obj: obj}, () => { + $('#sing-ui #overlaydialog').toggleClass('active', true); + }); + } + }; + var newLinePart = () => { syncPart(); syncPart(); } + var goBackPart = () => { + if(!sing._isUIShowing) return stopSync(false); + //Commented below due to being confirmed unstable + //Otherwise, later sync'd lyrics get delayed -_- + // if (sync.length > 1) { + // var lastItem = sync[sync.length - 2]; + // sync.pop(sync[sync.length - 1]);//Remove old! + // controller.seek(lastItem.time);//Go back to before we got where we were + // app.spotify.doFunc({i: lastItem.index}, () => {scroll(i)}); + // } else { + controller.seek(0); + app.spotify.doFunc({}, () => { + $('#lyrics span').removeClass('active'); + $('#lyrics span.buffer:eq(0)').addClass('active'); + $('#sing-ui').stop().animate({ + scrollTop: $('#lyrics span.buffer:eq(0)').offset().top - $('#lyrics').offset().top + }); + }); + sync = []; + //} + }; + var blurFunc = () => {stopSync(true)}; + var stopSync = (tidy) => { + sync = []; + clearTimeout(lyricTimer); + keys.unregister('DOWN', syncPart); + keys.unregister('UP', goBackPart); + keys.unregister('ENTER', newLinePart); + app.spotify.removeListener('blur', blurFunc); + if (tidy) app.spotify.doFunc({}, () => { + $('#sing-sync').click(); + $('#sing-sync').attr('title', 'Record lyrics to music'); + $('#sing-container span').toggleClass('active', false); + }); + } + stopSync(false); + keys.register('DOWN', syncPart); + keys.register('UP', goBackPart); + keys.register('ENTER', newLinePart); + app.spotify.once('blur', blurFunc); + controller.seek(0); + lyricTimer = setInterval(lyricTimerFunc, 100); + if (!controller.isPlaying) controller.play(); + }); } else { - $('#sing-ui #sing-container').html(data.toString()); - $('#sing-ui #sing-container a').click(function(){ - props.electron.shell.openExternal($(this).attr('href')); - return false; + $('#sing-ui').css({ + 'background-image': `url('${art}')`, + 'background-position': 'center', + 'background-repeat': 'no-repeat', + 'background-size': 'cover' + }); + $('#sing-container span.buffer').toggleClass('active', true); + $('#sing-ui').animate({ + scrollTop: $('#lyrics span.buffer').offset().top - $('#lyrics').offset().top + }); + MAIN.sendFunc({obj: obj, scroll: scroll}, () => { + var last = {time: null, accumulate: 0, lyricIndex: null}; + var lyricTimer = setInterval(() => { + if (!controller.isPlaying) return + last.accumulate += 100; + if (last.time !== controller.track.position){ + last.accumulate = 0; + last.time = controller.track.position; + } + var lyricsPassed = obj.sync.filter((e) => {return e.time < ((last.time / 1000) + last.accumulate)}); + if (controller.track.position < obj.sync.filter((e) => e.index == 0)[0].time){ + app.spotify.doFunc({}, () => { + $('#lyrics span').removeClass('active'); + $('#lyrics span.buffer:eq(0)').addClass('active'); + $('#sing-ui').stop().animate({ + scrollTop: $('#lyrics span.buffer:eq(0)').offset().top - $('#lyrics').offset().top + }); + }) + } + if (!lyricsPassed.length) return; + var currentLyric = lyricsPassed[lyricsPassed.length - 1]; + if (currentLyric.index !== last.lyricIndex) { + last.lyricIndex = currentLyric.index; + app.spotify.doFunc({scroll: scroll, i: currentLyric.index}, () => {if($('#sing-container').is('.sync')) {scroll(i)}}); + } + if (!sing._isUIShowing) return clearTimeout(lyricTimer); + }, 100); + controller.once('trackChange', () => clearTimeout(lyricTimer)); }); } - }); + } else { + $('#sing-sync').attr('title', (!!obj.sync ? 'Let the lyrics prompt you when to sing' : 'Syncronise lyrics with the music')); + $('#sing-container span.buffer').removeClass('active'); + $('#sing-container').removeClass('sync'); + $('#sing-container').removeClass('record'); + MAIN.sendFunc({obj: obj, scroll: scroll}, () => { + var keys = require('electron').globalShortcut; + keys.unregister('UP'); + keys.unregister('DOWN'); + keys.unregister('ENTER'); + shortcuts.toggle(app.spotify.isFocused); + app.spotify.removeAllListeners('blur'); + app.spotify.on('blur', () => shortcuts.toggle(false)); + }); + } + }); + $('#sing-ui #sing-container').html(` + +
    +
    + •••
    + ${obj.lyrics.split('\n').map((e) => { + return `${(e ? e : '•••')}`; + }).join('
    ')} +
    +

    +

    ${footer}

    +
    + + `); + $('#sing-ui #sing-container a:not([href=\'javascript:;\'])').click(function() { + MAIN.sendFunc({link: $(this).attr('href')}, () => app.openLink(link)); + return false; + }); + }); + sync.retrieve(obj.uri, (err, resp) => { + if (resp.error.code !== '200' || resp.error.code == '404') return showLyrics(); + resp.data[0].sync = JSON.parse(resp.data[0].sync); + if(obj.sync !== resp.data[0].sync){ + obj.sync = resp.data[0].sync; + obj.lyrics = resp.data[0].lyrics; + obj.username = resp.data[0].username; + obj.type = 'sync'; + obj.url = resp.url; + app.createUTF8File(filepath, JSON.stringify(obj), (err) => { + if (err) return fetchError(`Couldn't update Lyrics (${obj.uri}) with Sync`); + showLyrics(); + }) + } else { + showLyrics(); } }); - } - }, - hide: () => { - if(timer) clearInterval(timer); - if (singFuncs.isOpen()) $('#sing-ui').fadeToggle(() => { - $('#nav-sing').toggleClass('active'); }); - }, - show: () => { - if (!singFuncs.isOpen()) $('#sing-ui').fadeToggle(() => { - $('#nav-sing').toggleClass('active'); + }; + //Returns lyrics from an online source (MXM and Sync) - returns cb error and filepath to lyrics + const fetchOnline = (uri, name, artist, filepath, cb) => { + sync.retrieve(uri, (err, obj) => { + if (obj && obj.error.code == 200) { + var lyrics = { + uri: uri, + name: name, + artists: artist, + lyrics: obj.data[0].lyrics, + sync: JSON.parse(obj.data[0].sync), + username: obj.data[0].username, + type: 'sync', + url: obj.url + }; + app.createUTF8File(filepath, JSON.stringify(lyrics), (err) => cb(err, filepath)); + } else if (obj.error.code == 404 || obj.error.code == 500 || obj.error.code == 503) { + mxm(name, artist, (err, result) => { + if (err) return cb(err); + var lyrics = { + uri: uri, + name: result.name, + artists: result.artists, + lyrics: result.lyrics, + sync: null, + username: user.username, + type: 'mxm', + url: result.url + }; + app.createUTF8File(filepath, JSON.stringify(lyrics), (err) => cb(err, filepath)); + }); + } else { + cb(obj.error); + } }); - }, - toggleButton: (toggle) => { - if (toggle && $('#nav-sing').has('.disabled')){ - $('#nav-sing').removeClass('disabled'); - } else if (!toggle && !$('#nav-sing').has('.disabled')){ - $('#nav-sing').addClass('disabled'); - } - }, - toggleUI: (toggle) => { - if(toggle) { - singFuncs.show(); + } + const fetchError = (err, cb) => { + this.hideLoader(); + app.spotify.do(` + $('#sing-ui #sing-container').html(\` +
    +
    +

    Sorry, I couldn't retrieve any lyrics to this song.

    +

    (${err})

    +
    +
    + \`) + `); + if (cb) cb(); + } + this.load = (uri, name, artist) => { + if (!this._isUIShowing || !app.settings.NavBar.Sing || this.lastTrackURI == uri || !name || !artist) return; + if (uri == null) return fetchError(new Error('No URI found with current track'), null); + var oldpath = `${app.paths.caches.lyrics}/${artist.split(',')[0]}-${name.match(/(\w+)/g).join('-')}.html`; + var filepath = `${app.paths.caches.lyrics}/${uri}.json`; + this.lastTrackURI = uri; + app.spotify.doFunc({}, () => { + $('#sing-container').removeClass('sync'); + $('#sing-ui').css('background-image', ''); + $('#sing-sync').removeClass('active'); + }); + //Allow us to convert old lyrics + fs.access(oldpath, fs.F_OK, (err) => { + if(!err) { + //Convert old + app.getUTF8File(oldpath, (err, data) => { + if (err) return fetchError(err); + var newLyrics = { + uri: uri, + name: name, + artists: artist, + lyrics: (/(?:
    (.*)<\/div>)/g).exec(data)[1].replace(//g, '\n'), + sync: null, + type: 'mxm', + username: user.username, + url: (/(?:View on)/g).exec(data)[1] + }; + app.createUTF8File(filepath, JSON.stringify(newLyrics), (err) => { + if (err) return fetchError(createErr); + fs.unlinkSync(oldpath); + fetchOffline(filepath, (err) => fetchErr); + }); + }); + } else { + this.showLoader(); + app.checkPathExists(filepath, (err) => { + if (err) { + fetchOnline(uri, name, artist, filepath, (err, file) => { + if (err) return fetchError(err); + return fetchOffline(file, (err, callback) => fetchError(err)); + }); + } else { + fetchOffline(filepath, (err) => fetchError(err)); + } + }); + } + }); + } + this.toggleButton = (toggle) => { + if (toggle) { + app.spotify.do(`if($('#nav-sing').has('.disabled')) $('#nav-sing').removeClass('disabled');`); } else { - singFuncs.hide(); - } - }, - isOpen: () => { - return $('#sing-ui').is(':visible'); - }, - showLoader: () => { - if(timer) clearInterval(timer); - $('#sing-ui #sing-container').html(`
    `); - }, - hideLoader: () => { - $('#sing-ui #sing-container').removeClass('.loader'); - if($('#sing-ui #sing-container #lyrics')){ - toggleLyricScroller($(this).is('active')); + app.spotify.do(`if(!$('#nav-sing').has('.disabled')) $('#nav-sing').addClass('disabled');`); } - } -}; -module.exports = singFuncs; \ No newline at end of file + }; +} +module.exports = new Sing(); diff --git a/windows/spotify/Sing!/sync.js b/windows/spotify/Sing!/sync.js new file mode 100644 index 0000000..c662dbe --- /dev/null +++ b/windows/spotify/Sing!/sync.js @@ -0,0 +1,57 @@ +const request = require('request'); +const cookieJar = request.jar(); +let available = false; +let retrieveWait = null; +var Sync = function() { + this.agent = app.spotify.webContents.session.getUserAgent(); + this.host = 'http://quacky2200.co.nf/projects/spotifywebplayer/v1.0.0'; + const go = (url, cb) => { + request({url: url, jar: cookieJar, headers: {'User-Agent': this.agent}}, + (err, resp, data) => cb(err, resp, data)); + }; + //Gain cookies to gain 3 minute upload slot + go(this.host, (err, resp, data) => { + if (resp.statusCode == 200) available = true; + }); + + this.retrieve = (uri, cb) => { + if (!available) return cb(new Error('Not ready!')) + if (retrieveWait && retrieveWait.uri !== uri) clearTimeout(retrieveWait.time); + //Try getting a track + go(`${this.host}/track/${uri}`, (err, resp, data) => { + if (resp.statusCode == 429) { + retrieveWait = { + uri: uri, + time: setTimeout(() => this.retrieve(uri, cb), 3000) + } + } else if (resp.statusCode == 200) { + var obj = JSON.parse(data); + obj.url = `${this.host}/track/${uri}`; + cb(null, obj); + } else { + var obj = JSON.parse(data); + cb(obj.error, obj); + } + }); + }, + this.upload = (uri, lyrics, syncd, username, cb) => { + request.post({ + url: `${this.host}/track`, + jar: cookieJar, + form: { + uri: uri, + lyrics: lyrics, + sync: JSON.stringify(syncd), + username: username + } + }, (err, resp, data) => { + if (resp.statusCode == 201) { + cb(null, data); + } else { + //var obj = JSON.parse(data); + cb(err, data); + } + }); + }; +} +module.exports = new Sync(); diff --git a/windows/spotify/Sing!/time.png b/windows/spotify/Sing!/time.png deleted file mode 100755 index b0e162867b6d51f2024440037a3c7a4369157973..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 706 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5m^kh={g8AI%&+V01C2~c>21sKj36#)RkSdp&n=g;{;C^#}JR>YeQ}G4;cv5_W6AG znfSsXAgt}2hwLuHPu5yr#a(V%D=zW+%RDvUIM+qSmCov?`X4Cs{AmwzSnu5XSi*VH zr1I02`>JNVvd@(Wtl60Fa*Vy{fW^;$fk9rME=Ht_NVCnlW`$iDJZowkFK z!9?M!X3ay^aJL@^yu>p!@;SeKi7EBVIo=I&2$Ycbq!2I3=FMIOs$M8wGE7{3=E9nO2Eg zgSM<#98iNK$cEtjw370~qEv?R@^Zb*yzJuS#DY}4{G#;P?`))iiWody{an^LB{Ts5 DopuzI diff --git a/windows/spotify/Sing!/ui.html b/windows/spotify/Sing!/ui.html index 164f8fc..8f69e80 100755 --- a/windows/spotify/Sing!/ui.html +++ b/windows/spotify/Sing!/ui.html @@ -1,27 +1,179 @@
    +
    +
    +
    +
    Are you happy with your attempt to syncronise the lyrics?
    + + + + + +
    No, Start AgainYes, I'm Done!
    +
    +
    +
    +
    -
    - -
    -
    \ No newline at end of file +
    diff --git a/windows/spotify/appmenu.js b/windows/spotify/appmenu.js new file mode 100755 index 0000000..055ba91 --- /dev/null +++ b/windows/spotify/appmenu.js @@ -0,0 +1,61 @@ +/* + * @author Matthew James + * Window menu functionality + */ +let Menu = require('electron').Menu; +function AppMenu(){ + this.menu = Menu.buildFromTemplate([ + { + label: 'File', + submenu: [{ + label: 'Search', + accelerator: 'CmdOrCtrl+S', + visible: false, + click: () => app.spotify.do(` + $('#suggest-area').toggleClass('show'); + $($('.form-control'), $('iframe#suggest').contents()).click(); + `) + }, + {label: 'Logout', click: () => tray.contextMenu.logout.click()}, + { + label: 'Quit', + accelerator: 'CmdOrCtrl+Q', + click: () => { + user.LoggedIn = false; + tray.toggle(false); + app.quit(); + } + }] + }, + { + label: 'View', + submenu: [{ + label: 'Toggle Full Screen', + accelerator: (process.platform === 'darwin' ? 'Ctrl+Command+F' : 'F11'), + click: () => {app.spotify.setFullScreen(!app.spotify.isFullScreen())} + }, + { + label: 'Preferences', + click: () => {app.spotify.showPreferences()} + }] + }, + { + label: 'Controls', + submenu: [ + {label: 'Play/Pause', accelerator: 'MediaPlayPause', click: () => controller.playPause()}, + {label: 'Next', accelerator: 'MediaNextTrack', click: () => controller.next()}, + {label: 'Previous', accelerator: 'MediaPreviousTrack', click: () => controller.previous()} + ] + }, + { + label: 'Help', + role: 'help', + submenu: [{ + label: 'About', + click: () => app.spotify.showAbout() + }] + } + ]); + this.toggle = (toggle) => app.setApplicationMenu((toggle ? this.menu : null)) +}; +module.exports = new AppMenu(); diff --git a/windows/spotify/behaviour.js b/windows/spotify/behaviour.js new file mode 100644 index 0000000..5de1e10 --- /dev/null +++ b/windows/spotify/behaviour.js @@ -0,0 +1,86 @@ +//LOAD +const {ipcMain, globalShortcut, BrowserWindow} = require('electron'); +module.exports = function(){ + global.controller = require('./controller'); + global.theme = require('./theme'); + global.tray = require('./tray'); + global.appMenu = require('./appmenu'); + global.sing = require('./Sing!/sing'); + global.shortcuts = require('./shortcuts'); + global.user = require('./user'); + + controller.albumCache = app.paths.caches.albums; + controller.albumCacheDisabled = app.settings.AlbumCacheDisabled; + controller.on('Quit', () => tray.items.quit.click()); + controller.on('Raise', () => { app.spotify.show(); app.spotify.focus(); }); + + controller.on('trackChange', (controller) => { + var usable = !(controller.isStopped || controller.isAdvertisement); + var needFocus = app.settings.Notifications.OnlyWhenFocused && app.spotify.isFocused(); + sing.toggleButton(usable); + theme.refresh(); + tray.toggleMediaButtons(usable); + sing.load(controller.track.id, controller.track.name, controller.track.artists); + if(app.settings.Notifications.ShowTrackChange && !needFocus) controller.notify(); + }); + + controller.on('playbackChange', (controller) => { + var usable = !(controller.isStopped || controller.isAdvertisement); + var needFocus = app.settings.Notifications.OnlyWhenFocused && app.spotify.isFocused(); + if(app.settings.Notifications[`ShowPlayback${controller.status}`] && !needFocus) controller.notify(); + sing.toggleButton(usable); + tray.toggleMediaButtons(usable); + theme.refresh(); + sing.load(controller.track.id, controller.track.name, controller.track.artists); + }); + ipcMain.on('message-for-Spotify', (e, a) => { + if (typeof(a) !== 'string') return; + if (a.match(/(:eval)/)) return eval(a.slice(7, a.length - 2)); + if (a.match(/(user:impression)/)) theme.refresh() + if (a.match(/(user:impression).*(player_loaded)/)) { + app.spotify.do(` + var player = document.getElementById('app-player'); + player.contentWindow.addEventListener('message', (e) => { + MAIN.send('track-metadata', e.data); + }); + `); + sing.init(); + } + if (a.match(/(USER_ACTIVE|spb-connected)/)) { + user.login(); + tray.toggle(app.settings.ShowTray); + appMenu.toggle(app.settings.ShowApplicationMenu); + app.settings.lastURL = app.spotify.webContents.getURL(); + app.settings.save(); + } + }); + ipcMain.on('message', (e, a) => { + console.log('SPOTIFY EVAL:', a); + if (typeof(a) !== 'string') return; + try { + if (a.match(/(:eval)/)) return eval(a.slice(7, a.length - 2)); + } catch (e) { + if (e instanceof SyntaxError){ + console.log(e.message); + } else { + throw e; + } + } + }) + app.spotify.on('close', (event) => { + var alreadyHidden = app.spotify.isVisible() && !app.spotify.isMinimized(); + if(user.loggedIn && ((app.settings.CloseToTray && app.settings.ShowTray) || app.settings.CloseToController)){ + app.spotify.hide(); + event.preventDefault(); + } else if (user.loggedIn && app.settings.CloseToTray && !app.settings.ShowTray){ + app.spotify.minimize(); + event.preventDefault(); + } + controller.dispose(); + appMenu.toggle(false); + tray.toggle(false); + }); + app.spotify.on('focus', () => shortcuts.toggle(true)); + app.spotify.on('blur', () => shortcuts.toggle(false)); + shortcuts.toggle(app.spotify.isFocused()); +} diff --git a/windows/spotify/controller.js b/windows/spotify/controller.js index ad9330f..b337c4d 100755 --- a/windows/spotify/controller.js +++ b/windows/spotify/controller.js @@ -3,251 +3,241 @@ * Controller for the player */ const EventEmitter = require('events'); +const request = require('request'); +const sanitizeFilename = require('sanitize-filename'); +const {ipcMain} = require('electron'); +const dbus = require('./dbus')(app.names.process); +class Controller extends EventEmitter { + constructor (player) { + super(); + this.track = null; + this.player_id = 'main'; + this.repeat = 'None'; + this.shuffle = false; + this.status = 'Stopped'; + this.track = null; + this.volume = 1.0; + this.albumCache = ''; + this.albumCacheDisabled = false; -module.exports = (function() { - const dbus = props.dbus; - updateMetadata = function(info){ - if (dbus && info.track){ - dbus.mpris.metadata = { - 'mpris:trackid': dbus.mpris.objectPath('track/' + info.track.trackNumber), - 'mpris:length': info.track.length, - 'mpris:artUrl': info.track.art, - 'xesam:title': info.track.name.replace(/(\'| - .*| \(.*)/i, ''), //Remove long track titles - 'xesam:album': info.track.album.replace(/(\'| - .*| \(.*)/i, ''), //Remove long album names - 'xesam:artist': info.track.artists, - 'xesam:url': info.track.url - }; - dbus.mpris.volume = info.volume; - dbus.mpris.position = info.track.position; - dbus.mpris.playbackStatus = info.status; - dbus.mpris.shuffle = info.shuffle; - dbus.mpris.repeat = info.repeat; + this.playPause = () => { + //API doesn't control advertisement playback... SpotifyApi.api.request('player_pause', [this.player_id]); + app.spotify.do(`$('button#play-pause', $(\`${player}\`).contents()).click()`); } - } - class Controller extends EventEmitter { - constructor (player) { - super(); - - this.track = null; - this.player_id = 'main'; - this.repeat = 'None'; - this.shuffle = false; - this.status = 'Stopped'; - this.track = null; - this.volume = 1.0; - this.albumCache = ''; - this.albumCacheDisabled = false; - - this.playPause = () => { - //API doesn't control advertisement playback... SpotifyApi.api.request('player_pause', [this.player_id]); - $('button#play-pause', $(player).contents()).click(); - } - //Update track information with the details provided - var update = (details) => { - var trackChange, playbackChange; - //If a track is present (i.e. we are listening to a track) - if(details.track){ - var currentPlayback = (details.playing ? 'Playing' : 'Paused'); - if(!this.track) this.track = {}; - this.isAdvertisement = false; + //Update track information with the details provided + var update = (details) => { + var trackChange, playbackChange; + //If a track is present (i.e. we are listening to a track) + if(details.track){ + var currentPlayback = (details.playing ? 'Playing' : 'Paused'); + if(!this.track) this.track = {}; + this.isAdvertisement = false; - trackChange = this.track.uri != details.track.uri; - playbackChange = this.status != currentPlayback; + trackChange = this.track.uri != details.track.uri; + playbackChange = this.status != currentPlayback; - this.status = currentPlayback; - //If we have a track, update the information for it (position update and track change) - this.track.trackNumber = details.track.number; - this.track.discNumber = details.track.disc; - this.track.id = details.track.uri.replace('spotify:track:', ''); - this.track.uri = details.track.uri; - this.track.name = details.track.name; - this.track.album = details.track.album.name; - this.track.artists = details.track.artistName; - this.track.popularity = details.track.popularity / 100; - this.track.length = details.duration * 1000; //Must be in Microseconds (from milliseconds) - this.track.position = details.position * 1000; - this.track.art = details.track.images[0][1]; //Retrieve the smallest image as our album art - this.track.url = 'https://play.spotify.com/track/' + this.track.uri; - this.shuffled = details.shuffle; - this.repeat = (details.repeat == 0 ? 'None' : (details.repeat == 1 ? 'Playlist' : 'Track')); + this.status = currentPlayback; + //If we have a track, update the information for it (position update and track change) + this.track.trackNumber = details.track.number; + this.track.discNumber = details.track.disc; + this.track.id = details.track.uri.replace('spotify:track:', ''); + this.track.uri = details.track.uri; + this.track.name = details.track.name; + this.track.album = details.track.album.name; + this.track.artists = details.track.artistName; + this.track.popularity = details.track.popularity / 100; + this.track.length = details.duration * 1000; //Must be in Microseconds (from milliseconds) + this.track.position = details.position * 1000; + this.track.art = details.track.images; //Retrieve the smallest image as our album art + this.track.url = 'https://play.spotify.com/track/' + this.track.uri; + this.shuffled = details.shuffle; + this.repeat = (details.repeat == 0 ? 'None' : (details.repeat == 1 ? 'Playlist' : 'Track')); - } else if (details.type == 'AD_BREAK_CHANGED') { - var currentPlayback = (details.params.playing ? 'Playing' : 'Paused'); + } else if (details.type == 'AD_BREAK_CHANGED') { + var currentPlayback = (details.params.playing ? 'Playing' : 'Paused'); - trackChange = !!this.track.uri; - playbackChange = this.status != currentPlayback; - this.status = currentPlayback; - this.isAdvertisement = true; - this.track.id = null; - this.track.discNumber = 1; - this.track.trackNumber = 0; - this.track.uri = null; - this.track.name = details.params.title; - this.track.album = details.params.description; - this.track.popularity = 1; - this.track.artists = 'Spotify'; - this.track.art = details.params.imageUrl; - this.track.url = details.params.clickUrl; - this.track.position = details.params.position * 1000; - this.track.length = details.params.duration * 1000; - } else { - playbackChange = this.status != 'Stopped'; - this.status = 'Stopped'; - } - //Try to update mpris information - updateMetadata(this); - if(trackChange || playbackChange) this.emit((trackChange ? 'track' : 'playback') + 'Change', this); - }; - //Add an event hook on the messages going through the player iframe element - player.contentWindow.addEventListener('message', (e) => { - //Make sure to only try and update the information if we have a payload containing data (an object/associative array) - if(e.data.match(/\"payload\":{/)){ - var obj = JSON.parse(e.data); - if (obj.payload.type == "ads_break_change" || obj.payload.type == 'change'){ - update(obj.payload.data); - } else if (obj.payload.track){ - update(obj.payload); - } + trackChange = !!this.track.uri; + playbackChange = this.status != currentPlayback; + this.status = currentPlayback; + this.isAdvertisement = true; + this.track.id = null; + this.track.discNumber = 1; + this.track.trackNumber = 0; + this.track.uri = null; + this.track.name = details.params.title; + this.track.album = details.params.description; + this.track.popularity = 1; + this.track.artists = 'Spotify'; + this.track.art = [['undetermined-size', details.params.imageUrl]]; + this.track.url = details.params.clickUrl; + this.track.position = details.params.position * 1000; + this.track.length = details.params.duration * 1000; + } else { + playbackChange = this.status != 'Stopped'; + this.status = 'Stopped'; + } + if (dbus && this.track){ + var artSizeRequested = this.getArtSizes()[app.settings.AlbumArtSize]; + var artSize = (artSizeRequested < this.track.art.length ? artSizeRequested : this.track.art.length - 1); + dbus.mpris.metadata = { + 'mpris:trackid': dbus.mpris.objectPath('track/' + this.track.trackNumber), + 'mpris:length': this.track.length, + 'mpris:artUrl': this.track.art[artSize][1], + 'xesam:title': this.track.name.replace(/(\'| - .*| \(.*)/i, ''), //Remove long track titles + 'xesam:album': this.track.album.replace(/(\'| - .*| \(.*)/i, ''), //Remove long album names + 'xesam:artist': this.track.artists, + 'xesam:url': this.track.url + }; + dbus.mpris.volume = this.volume; + dbus.mpris.position = this.track.position; + dbus.mpris.playbackStatus = this.status; + dbus.mpris.shuffle = this.shuffle; + dbus.mpris.repeat = this.repeat; + } + this.emit('liveChange', this); + if(trackChange || playbackChange) this.emit((trackChange ? 'track' : 'playback') + 'Change', this); + }; + //Add an event hook on the messages going through the player iframe element + //player.contentWindow.addEventListener('message', (e) => { + ipcMain.on('track-metadata', (e, args) => { + //Make sure to only try and update the information if we have a payload containing data (an object/associative array) + if(args.match(/\"payload\":{/)){ + var obj = JSON.parse(args); + if (obj.payload.type == "ads_break_change" || obj.payload.type == 'change'){ + update(obj.payload.data); + } else if (obj.payload.track){ + update(obj.payload); } - }); + } + }); - if (dbus) { - this.dispose(); + if (dbus) { + this.dispose(); - dbus.mediakeys.on('Play', () => this.playPause()); - dbus.mediakeys.on('Stop', () => this.stop()); - dbus.mediakeys.on('Next', () => this.next()); - dbus.mediakeys.on('Previous', () => this.previous()); + dbus.mediakeys.on('Play', () => this.playPause()); + dbus.mediakeys.on('Stop', () => this.stop()); + dbus.mediakeys.on('Next', () => this.next()); + dbus.mediakeys.on('Previous', () => this.previous()); - dbus.mpris.on('play', () => this.play()); - dbus.mpris.on('playpause', () => this.playPause()); - dbus.mpris.on('next', () => this.next()); - dbus.mpris.on('previous', () => this.previous()); - dbus.mpris.on('stop', () => this.stop()); - dbus.mpris.on('openuri', (e) => {if(e.uri.indexOf('spotify:track:') > -1){this.playTrack(e.uri)}}); - dbus.mpris.on('quit', () => { this.emit('Quit'); }); - dbus.mpris.on('raise', () => { this.emit('Raise'); }); - dbus.mpris.on('volume', (volume) => {this.setVolume(volume);}); - dbus.mpris.on('shuffle', (shuffle) => {this.setShuffle(shuffle);}); - dbus.mpris.on('loopStatus', (loop) => {this.setLoop(loop);}); - dbus.mpris.on('seek', (mms) => {this.seek(mms.delta/1000);}); - dbus.mpris.on('position', (track,pos) => {console.log('SetPosition not yet implemented')}); - } - } - dispose(){ - if(dbus){ - dbus.mediakeys.removeAllListeners(); - dbus.mpris.removeAllListeners(); - } - } - pause() { - if(this.status == 'Playing') this.playPause(); - } - play() { - if(this.status !== 'Playing') this.playPause(); - } - stop () { - SpotifyApi.api.request('player_stop', [this.player_id]) + dbus.mpris.on('play', () => this.play()); + dbus.mpris.on('playpause', () => this.playPause()); + dbus.mpris.on('next', () => this.next()); + dbus.mpris.on('previous', () => this.previous()); + dbus.mpris.on('stop', () => this.stop()); + dbus.mpris.on('openuri', (e) => {if(e.uri.indexOf('spotify:track:') > -1){this.playTrack(e.uri)}}); + dbus.mpris.on('quit', () => { this.emit('Quit'); }); + dbus.mpris.on('raise', () => { this.emit('Raise'); }); + dbus.mpris.on('volume', (volume) => {this.setVolume(volume);}); + dbus.mpris.on('shuffle', (shuffle) => {this.setShuffle(shuffle);}); + dbus.mpris.on('loopStatus', (loop) => {this.setLoop(loop);}); + dbus.mpris.on('seek', (mms) => {this.seek(mms.delta/1000);}); + dbus.mpris.on('position', (track,pos) => {console.log('SetPosition not yet implemented')}); } - previous () { - SpotifyApi.api.request('player_skip_to_prev', [this.player_id]); - } - next () { - SpotifyApi.api.request('player_skip_to_next', [this.player_id]); - } - setVolume (decimal) { - this.volume = decimal; - SpotifyApi.api.request('player_set_volume', [this.player_id, decimal]); - } - seek(ms) { - SpotifyApi.api.request('player_seek', [this.player_id, ms]); - } - setLoop(loop) { - SpotifyApi.api.request('player_set_repeat', [this.player_id, loop == 'Playlist']); + } + dispose(){ + if(dbus){ + dbus.mediakeys.removeAllListeners(); + dbus.mpris.removeAllListeners(); } - setShuffle(shuffle) { - SpotifyApi.api.request('player_set_shuffle', [this.player_id, shuffle]); + } + pause() { + if(this.status == 'Playing') this.playPause(); + } + play() { + if(this.status !== 'Playing') this.playPause(); + } + stop () { + app.spotify.do(`SpotifyApi.api.request('player_stop', [\'${this.player_id}\'])`); + } + previous () { + if ((controller.track.position / 1e6) < 5){ + //Go to previous track if we're close enough to it + app.spotify.do(`SpotifyApi.api.request('player_skip_to_prev', [\'${this.player_id}\'])`); + } else { + //Otherwise we want to go back to the start of the track + this.seek(0); } - getPlaylists(){ - //Gets the playlists with the play button element attached (Playlist name : Playlist play button) - if ($('#section-collection > div.root').length == 0) return; //Hasn't been loaded yet - var playlists = {}; - $('.list-group a', $('#section-collection iframe').contents()).each(function(){ - var button = $(this).find('.btn.btn-large.btn-play'); - var playlistTitle = $(this).find('.item-data span:first-child').text(); - playlists[playlistTitle] = button; + } + next () { + app.spotify.do(`SpotifyApi.api.request('player_skip_to_next', [\'${this.player_id}\'])`); + } + setVolume (decimal) { + this.volume = decimal; + app.spotify.do(`SpotifyApi.api.request('player_set_volume', [\'${this.player_id}\', ${decimal}])`); + } + seek(ms) { + app.spotify.do(`SpotifyApi.api.request('player_seek', [\'${this.player_id}\', ${ms}])`); + } + setLoop(loop) { + app.spotify.do(`SpotifyApi.api.request('player_set_repeat', [\'${this.player_id}\', ${loop == 'Playlist'}])`); + } + setShuffle(shuffle) { + app.spotify.do(`SpotifyApi.api.request('player_set_shuffle', [\'${this.player_id}\', ${shuffle}])`); + } + notify() { + if (dbus) { + this.downloadAlbumArt((err, file) => { + if (err) return console.log(err); + dbus.notifications.notify( + (this.isAdvertisement ? 'Spotify Advertisement (' + this.status + ')' : (this.status == 'Playing' ? 'Now Playing' : this.status)), + `${this.track.name.replace(/( - .*| \(.*)/i, '')}\n${this.track.album.replace(/( - .*| \(.*)/i, '')}\n${this.track.artists}`, + file + ); }); - return playlists; + } else { + app.spotify.do(`new Notification( + ${this.status == 'Playing' ? 'Now Playing' : this.status}, + { + icon: ${this.track.art}, + body: ${this.track.name} + "\n" + ${this.track.album} + "\n" + ${this.track.artists} + } + );`) } - notify() { - if (dbus) { - this.downloadAlbumArt((err, file) => { - if (err) return console.log(err); - dbus.notifications.notify( - (this.isAdvertisement ? 'Spotify Advertisement (' + this.status + ')' : (this.status == 'Playing' ? 'Now Playing' : this.status)), - `${this.track.name.replace(/( - .*| \(.*)/i, '')}\n${this.track.album.replace(/( - .*| \(.*)/i, '')}\n${this.track.artists}`, - file - ); - }); - } else { - new Notification( - (this.status == 'Playing' ? 'Now Playing' : this.status), - { - icon: this.track.art, - body: this.track.name + "\n" + this.track.album + "\n" + this.track.artists - } - ); - } + }; + getArtSizes(){ + return { + large: 2, + medium: 1, + small: 0 }; - // getArtSizes(){ - // return { - // large: 2, - // medium: 1, - // small: 0 - // }; - // } - downloadAlbumArt(cb){ - if (!this.track || (!this.track.uri && !this.isAdvertisement)) return cb(new Error('No track present')); - var filepath = (this.albumCacheDisabled || this.isAdvertisement ? '/tmp' : this.albumCache); - var downloadArt = () => { - var album = this.track.album.match(/(?:(?! [^\w]).)*/)[0]; - var file = (this.track.art ? `${filepath}/${props.sanitizeFilename(album)}.jpeg` : process.cwd() + '/icons/spotify.png'); - props.checkPathExists(file, (err) => { - if (err) return props.request(this.track.art, {encoding: 'binary'}, (error, response, body) => { - if(error) return cb(err);//console.log(error); - props.createFile(file, body, 'binary', (err) => { + } + downloadAlbumArt(cb){ + if (!this.track || (!this.track.uri && !this.isAdvertisement)) return cb(new Error('No track present')); + var filepath = (this.albumCacheDisabled || this.isAdvertisement ? '/tmp' : this.albumCache); + var downloadArt = () => { + var album = sanitizeFilename(this.track.album.match(/(?:(?! [^\w]).)*/)[0]); + var artist = sanitizeFilename(this.track.artists.split(',')[0]); + var file = (this.track.art ? `${filepath}/${artist}/${album}.jpeg` : process.cwd() + '/icons/spotify.png'); + app.checkPathExists(file, (err) => { + var artSizeRequested = this.getArtSizes()[app.settings.AlbumArtSize]; + var artSize = (artSizeRequested < this.track.art.length ? artSizeRequested : this.track.art.length - 1); + if (err) return request(this.track.art[artSize][1], {encoding: 'binary'}, (error, response, body) => { + if(error) return cb(err);//console.log(error); + app.checkPathExists(`${filepath}/${artist}/`, (err) => { + var createArt = () => app.createFile(file, body, 'binary', (err) => { if (err) return cb(err);//console.log(err); cb(null, file); }); - }); - cb(null, file); - }); - } - if (!this.albumCacheDisabled) props.checkPathExists(filepath, (err) => { - if (err) return props.createDirectory(filepath, (err) => { - if (err) return cb(new err); - downloadArt(); + if (err) return app.createDirectory(`${filepath}/${artist}`, (err) => { + if (err) return cb(err); + createArt(); + }); + createArt(); + }) }); - downloadArt(); + cb(null, file); }); } - toggleGlobalMediaButtons(toggle) { - return; - var shortcuts = { - MediaNextTrack: () => {this.next()}, - MediaPlayPause: () => {this.playPause()}, - MediaPreviousTrack: () => {this.previous()}, - }; - for (var shortcut in shortcuts){ - if (shortcuts.hasOwnProperty(shortcut)){ - if (toggle && !globalShortcut.isRegistered(shortcut)){ - globalShortcut.register(shortcut, shortcuts[shortcut]); - } else if (!toggle && globalShortcut.isRegistered(shortcut)){ - globalShortcut.unregister(shortcut); - } - } - } - } + if (!this.albumCacheDisabled) app.checkPathExists(filepath, (err) => { + if (err) return app.createDirectory(filepath, (err) => { + if (err) return cb(new err); + downloadArt(); + }); + downloadArt(); + }); } - return Controller; -})(); \ No newline at end of file + get isPlaying() { return this.status == 'Playing' } + get isPaused() { return this.status == 'Paused' } + get isStopped() { return this.status == 'Stopped' } +} +module.exports = new Controller('#app-player'); diff --git a/dbus.js b/windows/spotify/dbus.js similarity index 100% rename from dbus.js rename to windows/spotify/dbus.js diff --git a/windows/spotify/interface.js b/windows/spotify/interface.js deleted file mode 100755 index c4c21d4..0000000 --- a/windows/spotify/interface.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * @author Matthew James - * Interface controller that modifies the webpage to create - * non-intrusive advertising and optional themes. - */ - -module.exports = (function() { - //Cache for the loaded CSS - let CURRENT_THEME = { - BASETHEME_CACHE: '', - THEME_CACHE: '', - THEME_NAME: '' - }; - //A selector that can find all iframes needed to be styled - const ALL_IFRAMES = "div[id*='section-'] iframe[id*='app-spotify:'], #app-player, #context-actions"; - //Reduce the prevelence of advertisements - let tameAdvertisements = function(){ - var frames = $('.root iframe'); - for (var i = 0; i < frames.length; i++) { - var frame = $(frames[i]).contents(); - var advertContainer = "#header.container,.hpto-container" - var potentialAdvert = $(advertContainer, frame); - if ($(potentialAdvert).has("#hpto") && $(`body > *:last-child:not(${advertContainer})`, frame).length == 1){ - //Place the advert onto the bottom if it's at the top - var advert = potentialAdvert.detach(); - advert.appendTo($('body', frame)); - } - } - } - //Syncronously read a theme - let loadTheme = function(name, cb){ - props.getUTF8File(`${props.paths.themes}/${name}-theme.css`, (err, newcss) => { - if (err) return cb(err); - CURRENT_THEME.THEME_CACHE = `/* Theme Styling */\n${newcss}`; - CURRENT_THEME.THEME_NAME = name; - cb(err); - }); - } - //Behaviour for the interface - class Interface{ - constructor(){ - //Load the base theme - props.getUTF8File(`${__dirname}/base-theme.css`, (err, data) => { - if (err) return console.error(err); - CURRENT_THEME.BASETHEME_CACHE = `/* Base Theming */\n${data}`; - }) - //Load current theme into cache - loadTheme(props.settings.Theme, (err) => { - if (err) return console.error(err); - //Apply the interface behaviour - this.refresh(); - }); - } - //Set the current theme and automatically try to adapt to it - set themeName (name) { - //Check to make sure we can use the theme by checking with those that exist - //Also check to see that we're not trying to save the same name as before - //However, make sure to set the theme if it - if (name == CURRENT_THEME.THEME_NAME || name == this.themeName) return; - this.isThemeAvailable(name, (err) => { - if (err) return console.error(err); - props.settings.Theme = name; - props.settings.save(); - loadTheme(name, (err) => { - this.refresh(); - }); - }); - } - isThemeAvailable(name, cb){ - props.checkPathExists(this.getThemeFilePath(name), (err) => cb(err)); - } - getThemeFilePath(name){ - return `${props.paths.themes}/${name}-theme.css`; - } - //Get the current theme name - get themeName () { - return props.settings.Theme; - } - //Get the current theme CSS - get themeCSS(){ - return CURRENT_THEME.BASETHEME_CACHE + CURRENT_THEME.THEME_CACHE; - } - //Get the current NavBar theming - get navbarCSS(){ - var CSS = `/*NavBar Items*/`; - var navItems = Object.keys(props.settings.NavBar) - for (var navItem in navItems){ - if (navItems.hasOwnProperty(navItem)) { - var property = navItems[navItem].toLowerCase() - property = (property == 'user' ? 'li.item-profile' : `li #nav-${property.replace('yourmusic', 'collection')}`) - CSS += `\n#nav-items ${property} {display: ${(props.settings.NavBar[navItems[navItem]] ? 'block' : 'none !important')}}`; - } - } - return CSS; - } - //Get all the themes available - get allThemeNames () { - //Return a list of all theme names in the themes directory - //(without -theme.css) and capitalised. (e.g. ['dark', 'light']) - return props.getFilesInDir(`${__dirname}/themes`).map((e) => {return e.replace('-theme.css','')}); - } - //Delete any theming present and reload it - refresh() { - if (this.themeName != CURRENT_THEME.THEME_NAME){ - loadTheme(this.themeName, (err) => { - if (err) return console.error(err); - this.refresh() - }) - } else { - tameAdvertisements(); - var interfaceClass = 'controlbot'; - CSS = ``; - $('body').prepend(CSS); - $('body', $(ALL_IFRAMES).contents()).prepend(CSS); - $('style.controlbot:not(:first-child)', $(ALL_IFRAMES).contents()).remove(); - $('style.controlbot:not(:first-child)').remove(); - //Always make sure sing button is at the bottom - $('li').has('#nav-sing').appendTo('#nav-items'); - //Hide the ugly flash player box - $('#core object').height(0).width(0); - } - } - } - return new Interface(); -})(); \ No newline at end of file diff --git a/windows/spotify/preload.js b/windows/spotify/preload.js index 9499479..4f26e36 100644 --- a/windows/spotify/preload.js +++ b/windows/spotify/preload.js @@ -1,15 +1,31 @@ -/* +/** * @author Matthew James - * Spotify Player Injection tool - * Allows for Spotify Web Player for Linux to implement native-like features + * Spotify new preload */ require('electron-cookies'); -window.windowHook = false; -window.remote = require('electron').remote; -window.require = require; -window._loaded = false; +window.MAIN = require('electron').ipcRenderer; +MAIN.sendFunc = (variables, func) => { + var vars = ""; + for (var curvar in variables){ + if (variables.hasOwnProperty(curvar)) { + if (typeof(variables[curvar]) == 'function') { + vars += `var ${curvar} = ${variables[curvar].toString()};\n`; + } else if (typeof(variables[curvar]) == 'object'){ + vars += `var ${curvar} = ${JSON.stringify(variables[curvar])};\n`; + } else if (typeof(variables[curvar]) == 'string'){ + vars += `var ${curvar} = \`${variables[curvar]}\`;\n`; + } else { + vars += `var ${curvar} = ${variables[curvar]};\n`; + } + } + } + vars += `\n(${func.toString()})();\n`; + MAIN.send('message-for-Spotify', `:eval('${vars}')`); +} +global.require = require; //If the window is a pop-up window if (window.opener){ + var remote = require('electron').remote; var popupWindow = remote.getCurrentWindow(); //Set our default properties for the popup window and escape. popupWindow.setSize(800, 600); @@ -17,149 +33,63 @@ if (window.opener){ popupWindow.show(); return; } -/* - * Make sure to run everything when we are ready - */ -document.onreadystatechange = function(){ - window.props = remote.getGlobal('props'); - window.$ = window.jQuery = require('./jquery'); - window.interface = require('./interface'); - if (_loaded) return; - //Load our theming - interface.refresh(); - /* - * Check for any updates and show an update button when one is available - */ - function checkForUpdates(){ - $.getJSON("https://api.github.com/repos/Quacky2200/Spotify-Web-Player-for-Linux/releases", (data) => { - var updateAvailable = (() => { - var version_update_tag = data[0].tag_name.match(/([0-9\.]+)/)[1].split('.'); - var version_now_tag = props.electron.app.getVersion().match(/([0-9\.]+)/)[1].split('.'); - for(var num in version_update_tag){ - if(parseInt(version_update_tag[num]) > parseInt(version_now_tag[num])) { - return true; - } else if (parseInt(version_update_tag[num]) < parseInt(version_now_tag[num])){ - return false - } +document.addEventListener("visibilitychange", function(){ + MAIN.send('message', ':eval(\'tray.toggle(app.settings.ShowTray);\')') +}); +function checkForUpdates(){ + $.getJSON("https://api.github.com/repos/Quacky2200/Spotify-Web-Player-for-Linux/releases", (data) => { + var updateAvailable = (() => { + var version_update_tag = data[0].tag_name.match(/([0-9\.]+)/)[1].split('.'); + var version_now_tag = navigator.userAgent.match(/(?:spotifywebplayer\/([0-9\.]+))/)[1].split('.'); + for(var num in version_update_tag){ + if(parseInt(version_update_tag[num]) > parseInt(version_now_tag[num])) { + return true; + } else if (parseInt(version_update_tag[num]) < parseInt(version_now_tag[num])){ + return false } - })(); - if(updateAvailable){ - var updateAvailableButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAA21BMVEX///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAABm7yWAAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgCRsAHhHuaRzgAAAD9ElEQVR42u2dvY7aQBhF50oUrqjcrKjpqFZCgnbe/6GiREqyq7AbsOf7m3vPA4DPsTFmZmxaE0IIIYQQQgghhBBCCCGEEEIIIYQI4oBPHBZa9Y8cmeUJIqx4lhnt3/ASp8n0sQFy/YkSYAcT6J+xD+bdP8HpEENg9wdWbv2qBwHAXQCDOdfSv2A8TF/+1QvACHb/KgUA7gIAd4EjuAu8A9wFzP3TXBFdrp+G9d38MxwCX47uLsD0BW5IQJj9HTlYZz2/pT4EkApyffeRUuSDXN+1ALgDIC3s/mD39yhwB3eAFaAuAHAHyO//ZxCC1d/0EFjJA9wB7gIgDwDyAGX8jdYVoxAKQO4Pdn+Lk0CtAGD3VwB2fwVYyP1HjwnUCwB2fwVgHAYxCwAF4A5wYw/gs0EKoABZA6zsAaAAFQMc2AM0BRjFpWaAy7AA16InQST7BPgHGLV4vm6AQQdB5QBDFo+XDjDiICgeAPQBQB9g74mgfgBc2QPs+Bic/bYh4Qjhu+smpBsjPTlvgXGAV+8tX913gfUw+Us/D28Bx2CmiYKQ988zVRL09mkmizBvAATqVwmAuQOEbkSOOfjEl+PxAToIAiDMP0uAc5R/lgBfbccClgDIeiEaGwBEAZD0KiwyALgCwGoCvGwAsAW4JLsE81+PG7IQNm0AEAZY81yABS1JV4Ac119xAQ7sAWDwrm/NlJ4+AHohf5gcdr2O/++TAIoU6EYngeGv28v4w+jE26v4WwWwKGAzXPvzlU3uiO01/GF36dFL+P/6GkCBAnbTFYYXn72CPyz/9qUX8Ifpr4+e3982wJgCttOVxr8/e3Z/6wD7C5hP11u/Qc/tD/sH5PbU/nAYguqZ/T0CbC/g4O8SYGsBD3+fANsKuPg7BdhSwMffK8DrBZz8D24TET2lPxa/mZie0R+eU1E9ob9rgOcL+Pn7Bni2gKO/c4DnCnj6H8c9HGVYAU9/uK0OfL6Aq7/htMDWAr7+EQG+L+Dsf4wI8F0BZ3+zqdGtBbz9YXSD+NYC7v5oQYfA4wJR/iGPC+4Z/CMD/FsgwP/kvE782wIB/ghanPmwQIR/dICPBWL9wx6b30P9Q9bKPy4Q7h/33Pwe5x9zv8yjAhn8A/86oQf5o2UpkMOfLsCpkRdo5AHib1aKZWUPkOF+tXz+PAUaeYBzIy+Q57bVfP4MBf4zM3kn9w+ZJUnlP/mHIPRhqlX8Jy4Q+Djt5Nc/FN8FLy7UZfefrcCW/5qi3v1zJThuvmOJevfPkmD3favk+rUTDHt6QUn7UxvJnXbn+z9xM/Jrb4JfSYbyf58+fE2pfliaEEIIIYQQQgghhBBCCCGEEEIIIcRjfgDj86cKVDgsEwAAAABJRU5ErkJggg=='; - button = ` -
  • - - - Update - -
  • - - `; - $('#main-nav #nav-items').append(button); - $('#nav-update').click(function(){ - props.electron.shell.openExternal($(this).attr('data-href')); - }); - } - }); - } - /* - * Handler for JSON messages from Spotify - */ - function onMessage(e){ - if(!(typeof e.data == 'string')) return - if (e.data.indexOf('USER_ACTIVE') > 0 || e.data.indexOf("spb-connected") > 0){ - //Whenever the user is active (or when the whole interface is loaded) - var loggedIn = user.isLoggedIn(); - //Try to prevent the window from moving away now that we're logged in - windowHook = loggedIn; - //Make sure to optionally show the Spotify tray icon - tray.toggleTray(loggedIn && props.settings.ShowTray); - //Make sure to optionally show the application menu - appMenu.toggleMenu(loggedIn && props.settings.ShowApplicationMenu); - //Make sure to set persistence - if (props.settings.lastURL !== window.location.href){ - props.settings.lastURL = window.location.href; - props.settings.save(); } - } else if (e.data.indexOf("user:impression") > 0){ - if(e.data.indexOf('player_loaded') > -1) { - require('./shortcut-bar'); - var Controller = require('./controller'); - window.controller = new Controller(document.getElementById('app-player')); - var isFocusWorthy = () => { - return (props.settings.Notifications.OnlyWhenFocused ? !props.spotify.isFocused() : true); - }; - controller.albumCache = props.paths.caches.albums; - controller.albumCacheDisabled = props.settings.AlbumCacheDisabled; - - controller.on('Quit', () => { - tray.contextMenu.quit.click(); - }); - - controller.on('Raise', () => { - props.spotify.show(); - props.spotify.focus(); - }); - - controller.on('trackChange', (controller) => { - sing.toggleButton(true); - interface.refresh(); - tray.toggleMediaButtons(controller.status !== 'Stopped'); - sing.load(controller.track.id, controller.track.name, controller.track.artists); - if(isFocusWorthy() && props.settings.Notifications.ShowTrackChange) controller.notify(); - }); - - controller.on('playbackChange', (controller) => { - var notificationSwitchTable = { - Playing: props.settings.Notifications.ShowPlaybackPlaying, - Paused: props.settings.Notifications.ShowPlaybackPaused, - Stopped: props.settings.Notifications.ShowPlaybackStopped - }; - var isMediaSwitchable = controller.status != 'Stopped'; - if(isFocusWorthy() && notificationSwitchTable[controller.status]) controller.notify(); - sing.toggleButton(isMediaSwitchable); - sing.load(controller.track.id, controller.track.name, controller.track.artists); - tray.toggleMediaButtons(isMediaSwitchable); - controller.toggleGlobalMediaButtons(isMediaSwitchable); - }); + })(); + if(updateAvailable){ + var updateAvailableButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAA21BMVEX///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAABm7yWAAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgCRsAHhHuaRzgAAAD9ElEQVR42u2dvY7aQBhF50oUrqjcrKjpqFZCgnbe/6GiREqyq7AbsOf7m3vPA4DPsTFmZmxaE0IIIYQQQgghhBBCCCGEEEIIIYQI4oBPHBZa9Y8cmeUJIqx4lhnt3/ASp8n0sQFy/YkSYAcT6J+xD+bdP8HpEENg9wdWbv2qBwHAXQCDOdfSv2A8TF/+1QvACHb/KgUA7gIAd4EjuAu8A9wFzP3TXBFdrp+G9d38MxwCX47uLsD0BW5IQJj9HTlYZz2/pT4EkApyffeRUuSDXN+1ALgDIC3s/mD39yhwB3eAFaAuAHAHyO//ZxCC1d/0EFjJA9wB7gIgDwDyAGX8jdYVoxAKQO4Pdn+Lk0CtAGD3VwB2fwVYyP1HjwnUCwB2fwVgHAYxCwAF4A5wYw/gs0EKoABZA6zsAaAAFQMc2AM0BRjFpWaAy7AA16InQST7BPgHGLV4vm6AQQdB5QBDFo+XDjDiICgeAPQBQB9g74mgfgBc2QPs+Bic/bYh4Qjhu+smpBsjPTlvgXGAV+8tX913gfUw+Us/D28Bx2CmiYKQ988zVRL09mkmizBvAATqVwmAuQOEbkSOOfjEl+PxAToIAiDMP0uAc5R/lgBfbccClgDIeiEaGwBEAZD0KiwyALgCwGoCvGwAsAW4JLsE81+PG7IQNm0AEAZY81yABS1JV4Ac119xAQ7sAWDwrm/NlJ4+AHohf5gcdr2O/++TAIoU6EYngeGv28v4w+jE26v4WwWwKGAzXPvzlU3uiO01/GF36dFL+P/6GkCBAnbTFYYXn72CPyz/9qUX8Ifpr4+e3982wJgCttOVxr8/e3Z/6wD7C5hP11u/Qc/tD/sH5PbU/nAYguqZ/T0CbC/g4O8SYGsBD3+fANsKuPg7BdhSwMffK8DrBZz8D24TET2lPxa/mZie0R+eU1E9ob9rgOcL+Pn7Bni2gKO/c4DnCnj6H8c9HGVYAU9/uK0OfL6Aq7/htMDWAr7+EQG+L+Dsf4wI8F0BZ3+zqdGtBbz9YXSD+NYC7v5oQYfA4wJR/iGPC+4Z/CMD/FsgwP/kvE782wIB/ghanPmwQIR/dICPBWL9wx6b30P9Q9bKPy4Q7h/33Pwe5x9zv8yjAhn8A/86oQf5o2UpkMOfLsCpkRdo5AHib1aKZWUPkOF+tXz+PAUaeYBzIy+Q57bVfP4MBf4zM3kn9w+ZJUnlP/mHIPRhqlX8Jy4Q+Djt5Nc/FN8FLy7UZfefrcCW/5qi3v1zJThuvmOJevfPkmD3favk+rUTDHt6QUn7UxvJnXbn+z9xM/Jrb4JfSYbyf58+fE2pfliaEEIIIYQQQgghhBBCCCGEEEIIIcRjfgDj86cKVDgsEwAAAABJRU5ErkJggg=='; + button = ` +
  • + + + Update + +
  • + + `; + $('#main-nav #nav-items').append(button); + $('#nav-update').click(function(){ + MAIN.send('message', `:eval('app.openLink(\'${$(this).attr('data-href')}\')')`) + }); } - } + }); +} +document.onreadystatechange = function() { + MAIN.send('message-for-Spotify', `:eval('theme.refresh()')`); + window.$ = window.jQuery = require('./jquery'); + if (document.readyState !== 'complete') return; + MAIN.send('message-for-Spotify', `:eval('app.spotify.hasRadio = ${!~$('#nav-items').has('#nav-radio')}')`); + window.addEventListener('message', function(e){ + if (typeof(e.data) == 'string' && + e.data.match(/(USER_ACTIVE|spb-connected|user:impression)/) + ) { + MAIN.send('message-for-Spotify', e.data); + } + }); + MAIN.send('message-for-Spotify', 'user:impression player_loaded'); //Make sure we don't get stuck when we cannot connect to Spotify setInterval(() => { if($('#modal-notification-area').is(':visible')) { - windowHook = false; window.location.reload(); } }, 10000); - /** - * When the window closes, hide or close depending on preferred behaviour - */ - window.onbeforeunload = function(e) { - var alreadyHidden = props.spotify.isVisible() && !props.spotify.isMinimized(); - if(windowHook && alreadyHidden && ((props.settings.CloseToTray && props.settings.ShowTray) || props.settings.CloseToController)){ - props.spotify.hide(); - return false; - } else if (windowHook && alreadyHidden && props.settings.CloseToTray && !props.settings.ShowTray){ - props.spotify.minimize(); - return false; - } - controller.dispose(); - appMenu.toggleMenu(false); - tray.toggleTray(false); - }; - window.user = require('./user'); - window.tray = require('./tray'); - window.sing = require('./Sing!/sing'); - window.appMenu = require('./window-menu'); - //Check for message events from Spotify - window.addEventListener('message', onMessage); checkForUpdates(); - //Check for updates every 6 hours (1/4 day) - setInterval(checkForUpdates, 2.16e+7); - _loaded = true; -} \ No newline at end of file + setInterval(checkForUpdates, 2.16e+7) +} diff --git a/windows/spotify/shortcut-bar.js b/windows/spotify/shortcut-bar.js deleted file mode 100644 index 0bea042..0000000 --- a/windows/spotify/shortcut-bar.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * @author Matthew James - * Load the shortcut bar - */ -var preferencesButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAIVBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmOFP9JAAAACnRSTlMAAxkfPEBRZLT566NXKgAAAFJJREFUCJljYOBYNZkBDEK1Vi0LBQGGVVWrlq8CAQYGzlVLILIMzKGODNiA5aoGoF4QyFq1CKgXBED6IbJeqyZA1YmEGmDRCLIXrBFsL8xi/BoBkA8ma/taJbEAAAAASUVORK5CYII='; -var infoButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAwFBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZkGrGOUAAAAP3RSTlMAAQIEBQYIDBEUFxobHB4pKi4xMzU2ODw9R0pWV1hcXmFihYaIjo+Rl5iam6uvt77Fx9HV2dri5Ovt8/X3+fskIZj9AAAAnUlEQVQYGXXBV1ICUQAAwdl1wRwwYsSEYCCIsoqKc/9b8UJplR92k6ye9wePV5v8aj6ZjVfItmZqE9TvfaLlD4MKjNYJno1OWgdGbyVs+9cx3Br0CExG8GpwTWBWMDd4OS3BbInapAKzgnuTCkwmcGhSgckFFLXaJeqoXw1gQ70hOlP3iI70cxi96yXZ2tRstsOPYvduOq8f2iX/WAAO6Cu/XBWcagAAAABJRU5ErkJggg=='; -var fullscreenButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAwFBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZkGrGOUAAAAP3RSTlMAAQIFBwgJCwwNDg8QERITFxgbHiMmKy80OEJDRFlcXV9jZGlse36AgoOFl6WqrbzMztXa4OLk5uvt8fX5+/1tvrB5AAAAsElEQVQYGX3B6TbDYBRA0ZNWiKGooIaY5yF1qVZVOO//Vr7wh2Ute2NrtE6y/GwSmNxU+11YfIiRGpiMC75k92pgo04Kkk6tNoGx+aaTAjpD9fAsOA5Wx85K6Dd+7HJ6ABXk9RLJxksJ2/yS8a+cn7o7cNSUJIPHBbiAusputenBnk77eI4ntXoJrEz1fWBg64pW79UkMLnm29pMDUyeYjgH81V1pwa2IifZmpoEf30CDzAiSWVMIPcAAAAASUVORK5CYII='; -var exitButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAARVBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZkBVNYgAAAAFnRSTlMAAQMECQ8QGR4iJCw8RF+RqKvKzODmqz18nwAAAF1JREFUGFdlz9kSgCAIQFHIwnZb+f9PbUhLlPvknAdAYBXEmDePby4TcyB5oSbm3tIYqasIPBytInTSfTaZ5nTHjobWchbBtZiNgz1C3zXVH/pKROEfnxOsSNBQ2QMFEwvTxy9znQAAAABJRU5ErkJggg=='; -let appPreferencesButton = ` -
    - - - - - - - -
    - - - - - - - -
    -
    `; -$('body', $('#app-player').contents()).append(appPreferencesButton); -$('.info.appbutton', $('#app-player').contents()).click(() => { - props.spotify.showAbout(); -}); -$('.fullscreen.appbutton', $('#app-player').contents()).click(() => { - props.spotify.setFullScreen(!props.spotify.isFullScreen()) -}); -$('.preferences.appbutton', $('#app-player').contents()).click(() => { - props.spotify.showPreferences(); -}); -$('.quit.appbutton', $('#app-player').contents()).click(() => { - tray.contextMenu.quit.click(); -}); \ No newline at end of file diff --git a/windows/spotify/shortcuts.js b/windows/spotify/shortcuts.js new file mode 100644 index 0000000..7836b58 --- /dev/null +++ b/windows/spotify/shortcuts.js @@ -0,0 +1,35 @@ +const {globalShortcut, BrowserWindow} = require('electron'); +const bind = (s,e) => globalShortcut.register(s,e); +const unbind = globalShortcut.unregister; +const getFocusedWindow = BrowserWindow.getFocusedWindow; +const media = { + MediaNextTrack: () => controller.next(), + MediaPlayPause: () => controller.playPause(), + MediaPreviousTrack: () => controller.previous(), +}; +for (var key in media) if (media.hasOwnProperty(key)) bind(key, media[key]); +module.exports = { + toggle: function(toggle){ + if (toggle) { + bind('CommandOrControl+S', () => app.spotify.do('$(\'#suggest-area\').toggleClass(\'show\');')); + bind('CommandOrControl+Shift+P', () => app.spotify.showPreferences()); + bind('F1', () => app.spotify.showAbout()); + bind('Shift+<', () => controller.previous()) + bind('Shift+>', () => controller.next()) + bind('F11', () => app.spotify.setFullScreen(!app.spotify.isFullScreen())); + bind('CommandOrControl+Shift+L', () => tray.contextMenu.logout.click()); + bind('CommandOrControl+W', () => app.spotify.close()); + var showdevtools = function(){ + if (app.spotify.isDevToolsOpened()){ + app.spotify.closeDevTools(); + } else { + app.spotify.closeDevTools() + } + } + bind('CommandOrControl+Shift+I', showdevtools); + bind('F12', showdevtools); + } else { + globalShortcut.unregisterAll(); + } + } +}; diff --git a/windows/spotify/theme.js b/windows/spotify/theme.js new file mode 100755 index 0000000..9beefaf --- /dev/null +++ b/windows/spotify/theme.js @@ -0,0 +1,196 @@ +/* + * @author Matthew James + * Interface controller that modifies the webpage to create + * non-intrusive advertising and optional themes. + */ + +module.exports = (function() { + var preferencesButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAIVBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmOFP9JAAAACnRSTlMAAxkfPEBRZLT566NXKgAAAFJJREFUCJljYOBYNZkBDEK1Vi0LBQGGVVWrlq8CAQYGzlVLILIMzKGODNiA5aoGoF4QyFq1CKgXBED6IbJeqyZA1YmEGmDRCLIXrBFsL8xi/BoBkA8ma/taJbEAAAAASUVORK5CYII='; + var infoButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAwFBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZkGrGOUAAAAP3RSTlMAAQIEBQYIDBEUFxobHB4pKi4xMzU2ODw9R0pWV1hcXmFihYaIjo+Rl5iam6uvt77Fx9HV2dri5Ovt8/X3+fskIZj9AAAAnUlEQVQYGXXBV1ICUQAAwdl1wRwwYsSEYCCIsoqKc/9b8UJplR92k6ye9wePV5v8aj6ZjVfItmZqE9TvfaLlD4MKjNYJno1OWgdGbyVs+9cx3Br0CExG8GpwTWBWMDd4OS3BbInapAKzgnuTCkwmcGhSgckFFLXaJeqoXw1gQ70hOlP3iI70cxi96yXZ2tRstsOPYvduOq8f2iX/WAAO6Cu/XBWcagAAAABJRU5ErkJggg=='; + var fullscreenButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAwFBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZkGrGOUAAAAP3RSTlMAAQIFBwgJCwwNDg8QERITFxgbHiMmKy80OEJDRFlcXV9jZGlse36AgoOFl6WqrbzMztXa4OLk5uvt8fX5+/1tvrB5AAAAsElEQVQYGX3B6TbDYBRA0ZNWiKGooIaY5yF1qVZVOO//Vr7wh2Ute2NrtE6y/GwSmNxU+11YfIiRGpiMC75k92pgo04Kkk6tNoGx+aaTAjpD9fAsOA5Wx85K6Dd+7HJ6ABXk9RLJxksJ2/yS8a+cn7o7cNSUJIPHBbiAusputenBnk77eI4ntXoJrEz1fWBg64pW79UkMLnm29pMDUyeYjgH81V1pwa2IifZmpoEf30CDzAiSWVMIPcAAAAASUVORK5CYII='; + var exitButtonIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAARVBMVEUAAACZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZkBVNYgAAAAFnRSTlMAAQMECQ8QGR4iJCw8RF+RqKvKzODmqz18nwAAAF1JREFUGFdlz9kSgCAIQFHIwnZb+f9PbUhLlPvknAdAYBXEmDePby4TcyB5oSbm3tIYqasIPBytInTSfTaZ5nTHjobWchbBtZiNgz1C3zXVH/pKROEfnxOsSNBQ2QMFEwvTxy9znQAAAABJRU5ErkJggg=='; + let appPreferencesButton = ` +
    + + + + + + + +
    + + + + + + + +
    +
    `; + //Cache for the loaded CSS + let CURRENT_THEME = { + BASETHEME_CACHE: '', + THEME_CACHE: '', + THEME_NAME: '' + }; + //A selector that can find all iframes needed to be styled + const ALL_IFRAMES = "div[id*='section-'] iframe[id*='app-spotify:'], #app-player, #context-actions"; + //Reduce the prevelence of advertisements + let tameAdvertisements = function(){ + var frames = $('.root iframe') || 0; + for (var i = 0; i < frames.length; i++) { + var frame = $(frames[i]).contents(); + var advertContainer = "#header.container,.hpto-container" + var potentialAdvert = $(advertContainer, frame); + if ($(potentialAdvert).has("#hpto") && $(`body > *:last-child:not(${advertContainer})`, frame).length == 1){ + //Place the advert onto the bottom if it's at the top + var advert = potentialAdvert.detach(); + advert.appendTo($('body', frame)); + } + } + } + //Syncronously read a theme + let loadTheme = function(name, cb){ + app.getUTF8File(`${app.paths.themes}/${name}-theme.css`, (err, newcss) => { + if (err) return cb(err); + CURRENT_THEME.THEME_CACHE = `/* Theme Styling */\n${newcss}`; + CURRENT_THEME.THEME_NAME = name; + cb(err); + }); + } + //Behaviour for the interface + class Theme{ + constructor(){ + //Load the base theme + app.getUTF8File(`${__dirname}/base-theme.css`, (err, data) => { + if (err) return console.error(err); + CURRENT_THEME.BASETHEME_CACHE = `/* Base Theming */\n${data}`; + }) + //Load current theme into cache + loadTheme(app.settings.Theme, (err) => { + if (err) return console.error(err); + //Apply the interface behaviour + this.refresh(); + }); + } + //Set the current theme and automatically try to adapt to it + set name (newstr) { + //Check to make sure we can use the theme by checking with those that exist + //Also check to see that we're not trying to save the same name as before + //However, make sure to set the theme if it + if (newstr == CURRENT_THEME.THEME_NAME || newstr == this.name) return; + this.isThemeAvailable(newstr, (err) => { + if (err) return console.error(err); + app.settings.Theme = newstr; + app.settings.save(); + loadTheme(newstr, (err) => { + this.refresh(); + }); + }); + } + isThemeAvailable(name, cb){ + app.checkPathExists(this.getThemeFilePath(name), (err) => cb(err)); + } + getThemeFilePath(name){ + return `${app.paths.themes}/${name}-theme.css`; + } + //Get the current theme name + get name () { + return app.settings.Theme; + } + //Get the current theme CSS + get themeCSS(){ + return CURRENT_THEME.BASETHEME_CACHE + CURRENT_THEME.THEME_CACHE; + } + //Get the current NavBar theming + get navbarCSS(){ + var CSS = `/*NavBar Items*/`; + var navItems = Object.keys(app.settings.NavBar) + for (var navItem in navItems){ + if (navItems.hasOwnProperty(navItem)) { + var property = navItems[navItem].toLowerCase() + property = (property == 'user' ? 'li.item-profile' : `li #nav-${property.replace('yourmusic', 'collection')}`) + CSS += `\n#nav-items ${property} {display: ${(app.settings.NavBar[navItems[navItem]] ? 'block' : 'none !important')}}`; + } + } + return CSS; + } + //Get all the themes available + get allThemeNames () { + //Return a list of all theme names in the themes directory + //(without -theme.css) and capitalised. (e.g. ['dark', 'light']) + return app.getFilesInDir(`${__dirname}/themes`).map((e) => {return e.replace('-theme.css','')}); + } + //Delete any theming present and reload it + refresh() { + if (this.name != CURRENT_THEME.THEME_NAME){ + loadTheme(this.name, (err) => { + if (err) return console.error(err); + this.refresh() + }) + } else { + //tameAdvertisements(); + var interfaceClass = 'controlbot'; + var CSS = ``; + var setTheme = () => { + $('body').prepend(CSS); + $('body', $(ALL_IFRAMES).contents()).prepend(CSS); + $('style.controlbot:not(:first-child)', $(ALL_IFRAMES).contents()).remove(); + $('style.controlbot:not(:first-child)').remove(); + }; + if (app.spotify.AboutInstance) { + app.spotify.AboutInstance.doFunc( + { + CSS: CSS, + ALL_IFRAMES: ALL_IFRAMES + }, + setTheme + ); + } + if (app.spotify.PreferenceInstance) { + app.spotify.PreferenceInstance.doFunc( + { + CSS: CSS, + ALL_IFRAMES: ALL_IFRAMES + }, + setTheme + ); + } + app.spotify.doFunc( + { + tameAdvertisements: tameAdvertisements, + CSS: CSS, + ALL_IFRAMES: ALL_IFRAMES, + appPreferencesButton: appPreferencesButton, + setTheme: setTheme + }, () => { + setTheme(); + tameAdvertisements(); + //Always make sure sing button is at the bottom + $('li').has('#nav-update').appendTo('#nav-items'); + $('li').has('#nav-sing').appendTo('#nav-items'); + //Hide the ugly flash player box + $('#core object').height(0).width(0); + + $('#infobar', $('#app-player').contents()).remove(); + $('body', $('#app-player').contents()).append(appPreferencesButton); + $('.info.appbutton', $('#app-player').contents()).click(() => { + MAIN.sendFunc({}, () => app.spotify.showAbout()); + }); + $('.fullscreen.appbutton', $('#app-player').contents()).click(() => { + MAIN.sendFunc({}, () => app.spotify.setFullScreen(!app.spotify.isFullScreen())); + }); + $('.preferences.appbutton', $('#app-player').contents()).click(() => { + MAIN.sendFunc({}, () => app.spotify.showPreferences()); + }); + $('.quit.appbutton', $('#app-player').contents()).click(() => { + MAIN.sendFunc({}, () => tray.items.quit.click()); + }); + } + ); + } + } + } + return new Theme(); +})(); diff --git a/windows/spotify/tray.js b/windows/spotify/tray.js index 848060e..cd3b040 100755 --- a/windows/spotify/tray.js +++ b/windows/spotify/tray.js @@ -2,78 +2,65 @@ * @author Matthew James * System Tray */ -const {Menu,Tray} = require('electron').remote; -const tray = { - appIcon: null, - contextMenu: { - togglePlayback: {label: "Play/Pause", enabled: false, click: () => { - controller.playPause() - }}, - previous: {label: "Previous", enabled: false, click: () => { - controller.previous() - }}, - next: {label: "Next", enabled: false, click: () => { - controller.next() - }}, - toggleSpotifyAppearance: {label: "Hide Spotify", click: function(){ +const {Menu,Tray} = require('electron'); +const TrayMenu = function() { + this.appIcon = null; + this.items = { + togglePlayback: {label: "Play/Pause", enabled: false, click: () => controller.playPause()}, + previous: {label: "Previous", enabled: false, click: () => controller.previous()}, + next: {label: "Next", enabled: false, click: () => controller.next()}, + toggleVisibility: {label: "Hide Spotify", click: () => { var prefix; - if (props.spotify.isVisible() && !props.spotify.isMinimized()){ + if (app.spotify.isVisible() && !app.spotify.isMinimized()){ prefix = 'Show'; - props.spotify.hide(); + app.spotify.hide(); } else { prefix = 'Hide'; - props.spotify.show(); - props.spotify.focus(); + app.spotify.show(); + app.spotify.focus(); } - tray.contextMenu.toggleSpotifyAppearance.label = prefix + ' Spotify'; - tray.toggleTray(props.settings.ShowTray); - }}, - appPreferences: {label: "App Preferences", click: function(){ - props.spotify.showPreferences(); - }}, - logout: {label: "Logout", click: function(){ - user.logout(); + this.items.toggleVisibility.label = prefix + ' Spotify'; + this.toggle(app.settings.ShowTray); }}, - quit: {label: "Quit", click:function(){ - tray.toggleTray(false); - appMenu.toggleMenu(false); - windowHook = false; - props.electron.app.quit(); + appPreferences: {label: "App Preferences", click: () => app.spotify.showPreferences()}, + logout: {label: "Logout", click: () => user.logout()}, + quit: {label: "Quit", click: () => { + this.toggle(false); + appMenu.toggle(false); + user.loggedIn = false; + app.quit(); }} - }, - toggleTray: function(toggle){ - if (toggle && props.settings.ShowTray){ - if (!tray.appIcon) tray.appIcon = new Tray(`${props.paths.icons}/spotify-ico-small-${props.settings.TrayIcon}.png`); - tray.appIcon.setContextMenu(Menu.buildFromTemplate([ - tray.contextMenu.togglePlayback, - tray.contextMenu.previous, - tray.contextMenu.next, + }; + this.toggle = (toggle) => { + if (toggle && app.settings.ShowTray){ + this.items.toggleVisibility.label = (app.spotify.isMinimized() || !app.spotify.isVisible() ? 'Show' : 'Hide') + ' Spotify'; + if (!this.appIcon) this.appIcon = new Tray(`${app.paths.icons}/spotify-ico-small-${app.settings.TrayIcon}.png`); + this.appIcon.setContextMenu(Menu.buildFromTemplate([ + this.items.togglePlayback, + this.items.previous, + this.items.next, {type:'separator'}, - tray.contextMenu.toggleSpotifyAppearance, - tray.contextMenu.appPreferences, - tray.contextMenu.logout, - tray.contextMenu.quit + this.items.toggleVisibility, + this.items.appPreferences, + this.items.logout, + this.items.quit ])); - tray.appIcon.on('click', () => { - props.spotify.show(); - props.spotify.focus(); + this.appIcon.on('click', () => { + app.spotify.show(); + app.spotify.focus(); }); - } else if (!toggle && tray.appIcon != null){ - tray.appIcon.destroy(); - tray.appIcon = null; + } else if (!toggle && this.appIcon != null){ + this.appIcon.destroy(); + this.appIcon = null; } - }, - toggleMediaButtons: function(toggle){ + }; + this.toggleMediaButtons = function(toggle){ //Show the media buttons on show value - tray.contextMenu.togglePlayback.enabled = toggle; - tray.contextMenu.previous.enabled = toggle; - tray.contextMenu.next.enabled = toggle; + this.items.togglePlayback.enabled = toggle; + this.items.previous.enabled = toggle; + this.items.next.enabled = toggle; //Make the changes apparent by reloading the menu - tray.toggleTray(true); - } + this.toggle(true); + }; }; -document.addEventListener("visibilitychange", function(){ - tray.contextMenu.toggleSpotifyAppearance.label = (props.spotify.isMinimized() || !props.spotify.isVisible() ? 'Show' : 'Hide') + ' Spotify'; - tray.toggleTray(props.settings.ShowTray); -}); -module.exports = tray; \ No newline at end of file +module.exports = new TrayMenu(); diff --git a/windows/spotify/user.js b/windows/spotify/user.js index fd169d7..ae3d37e 100755 --- a/windows/spotify/user.js +++ b/windows/spotify/user.js @@ -1,45 +1,41 @@ /* * @author Matthew James - * User information/controller + * User controller */ -//Grab a hook of the window to prevent it going anywhere -global.windowHook = false; -/** - * When the user clicks on the logout button in the Web Player - */ -$(document).on('click', 'a#logout-settings[href="/logout"]', function(e){ - logoutUser(); - //e.preventDefault(); -}); -// var config = JSON.parse( -// $('script').filter( -// function(){ -// return $(this).text().indexOf('var login = new Spotify.Web.Login') > -1 -// } -// ) -// .text() -// .match(/(\{.*\})/)[0] //Get the JSON out of the script text -// ); -module.exports = { - /* - * Returns if a user is logged into Spotify - * @returns {Boolean} - */ - isLoggedIn: function(){ - return !$('#login').is(":visible"); - }, - /** - * Logout the Spotify user by removing all cache - */ - logout: function(){ +const {ipcMain} = require('electron'); +function User(){ + this.loggedIn = false; + //Simple functionality + this.login = () => { + this.loggedIn = true; + this.username = null; + //Hook onto logout and get user info + ipcMain.once('getUsername', (e, a) => this.username = a); + ipcMain.once('userLogout', () => this.logout()); + app.spotify.do(` + $(document).on('click', 'a#logout-settings[href="/logout"]', () => { + MAIN.send('userLogout', '') + }); + + var {username} = JSON.parse( + $('script') + .filter((i,e) => !!~$(e).text().indexOf('var login')) + .text() + .match(/({.*})/)[0] + ); + MAIN.send('getUsername', username); + `); + } + this.logout = () => { tray.toggleMediaButtons(false); - tray.toggleTray(false); - appMenu.toggleMenu(false); + tray.toggle(false); + appMenu.toggle(false); controller.dispose(); - props.settings.lastURL = null; - props.settings.save(); - windowHook = false; - if (!props.spotify.isVisible()) props.spotify.show(); - props.clearCache(); + app.settings.lastURL = null; + app.settings.save(); + this.loggedIn = false; + if (!app.spotify.isVisible()) app.spotify.show(); + app.spotify.clearCache(); } -}; \ No newline at end of file +}; +module.exports = new User(); diff --git a/windows/spotify/window-menu.js b/windows/spotify/window-menu.js deleted file mode 100755 index 8f88ed4..0000000 --- a/windows/spotify/window-menu.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * @author Matthew James - * Window menu functionality - */ -function toggleMenu(toggle){ - let Menu = props.electron.Menu; - if(toggle && !toggleMenu.menu){ - toggleMenu.menu = Menu.buildFromTemplate([ - { - label: 'File', - submenu: [ - { - label: 'Search', - accelerator: 'CmdOrCtrl+S', - visible: false, - click: () => { - $('#suggest-area').toggleClass('show'); - $($('.form-control'), $('iframe#suggest').contents()).click(); - } - }, - { - label: 'Logout', - click: () => { - user.logout(); - } - }, - { - label: 'Quit', - accelerator: 'CmdOrCtrl+Q', - click: () => { - tray.toggleTray(false); - windowHook = false; - props.electron.app.quit(); - props.process.exit(0); - } - } - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Toggle Full Screen', - accelerator: (process.platform === 'darwin' ? 'Ctrl+Command+F' : 'F11'), - click: () => {props.spotify.setFullScreen(!props.spotify.isFullScreen())} - }, - { - label: 'Preferences', - click: () => {props.spotify.showPreferences()} - } - ] - }, - { - label: 'Controls', - submenu: [ - {label: 'Play/Pause', accelerator: 'MediaPlayPause', click: () => { - controller.playPause() - }}, - {label: 'Next', accelerator: 'MediaNextTrack', click: () => { - controller.next() - }}, - {label: 'Previous', accelerator: 'MediaPreviousTrack', click: () => { - controller.previous() - }} - ] - }, - { - label: 'Help', - role: 'help', - submenu: [{ - label: 'About', - click: () => { - props.spotify.showAbout(); - } - }] - } - ]); - props.electron.app.setApplicationMenu(toggleMenu.menu); - } else if(!toggle && toggleMenu.menu){ - props.electron.app.setApplicationMenu(null); - toggleMenu.menu = null; - } -} -module.exports = {toggleMenu:toggleMenu}; \ No newline at end of file diff --git a/windows/spotify/window.js b/windows/spotify/window.js new file mode 100755 index 0000000..e8110dc --- /dev/null +++ b/windows/spotify/window.js @@ -0,0 +1,107 @@ +/** + * @author Matthew James + * Spotify Window + */ +const {BrowserWindow} = require('electron'); +const FacebookPopup = require('../facebook/window'); +const About = require('../about/window'); +const Preferences = require('../preferences/window'); +const appBehaviour = require('./behaviour'); +let _preferencesInstance, _aboutInstance; +const shortcuts = require('./shortcuts'); +class Spotify extends BrowserWindow{ + constructor(){ + super({ + title: "Spotify Web Player", + icon: app.icon, + width: 1200, + height: 700, + show: true, + backgroundColor: "#121314", + minWidth: 800, + minHeight: 600, + webPreferences: { + nodeIntegration: false, + preload: `${__dirname}/preload.js`, + plugins: true, + webSecurity: false, + allowDisplayingInsecureContent: true, + allowRunningInsecureContent: true + } + }); + this.hasRadio = true; + this.openDevTools(); + this.on('page-title-updated', (event) => event.preventDefault()); + this.on('closed', () => { app.quit(); process.exit(0) }); + this.setMenu(null); + this.webContents.on('new-window', function(event, url, name, disposition){ + if(!~url.indexOf("facebook.com")) return; + (new FacebookPopup(name, url, this.webContents.session)).show(); + event.preventDefault(); + }); + this.webContents.once('dom-ready', () => { + this.show(); + appBehaviour(); + if(app.settings.StartHidden) this.minimize(); + }); + var url = (app.settings.lastURL && !!~app.settings.lastURL.indexOf('play.spotify.com') ? app.settings.lastURL : app.host); + this.loadURL(url); + this.on('focus', () => shortcuts.toggle(true)); + this.on('blur', () => shortcuts.toggle(false)); + } + do(str) { this.webContents.executeJavaScript(str); } + doFunc(variables, func) { + var vars = ""; + for (var curvar in variables){ + if (variables.hasOwnProperty(curvar)) { + if (typeof(variables[curvar]) == 'function') { + vars += `var ${curvar} = ${variables[curvar].toString()};\n`; + } else if (typeof(variables[curvar]) == 'object'){ + vars += `var ${curvar} = ${JSON.stringify(variables[curvar])};\n`; + } else if (typeof(variables[curvar]) == 'string'){ + vars += `var ${curvar} = \`${variables[curvar]}\`;\n`; + } else { + vars += `var ${curvar} = ${variables[curvar]};\n`; //Numbers, bools? + } + } + } + vars += `\n(${func.toString()})();\n`; + this.do(vars) + } + send(channel, obj){ this.webContents.send(channel, obj); } + clearCache(){ + this.loadURL("about:blank"); + this.webContents.session.clearCache(() => { + this.webContents.session.clearStorageData(() => { + console.log("Cleared session and cache."); + this.loadURL(app.host); + }); + }); + } + showAbout(){ + if(_aboutInstance) return this.AboutInstance.show(); + _aboutInstance = new About(); + _aboutInstance.on('closed', function(){ + _aboutInstance = null; + }); + } + get AboutInstance(){ + return _aboutInstance + } + get PreferenceInstance(){ + return _preferencesInstance; + } + showPreferences(){ + if(_preferencesInstance) return this.PreferenceInstance.show(); + _preferencesInstance = new Preferences(); + if (!this.hasRadio) _preferencesInstance.webContents.on('dom-ready', () => { + _preferencesInstance.do(` + $('input[name="NavBar.Radio"]').parents('tr').hide() + `); + }); + _preferencesInstance.on('closed', function(){ + _preferencesInstance = null; + }) + } +} +module.exports = Spotify; diff --git a/windows/windows.js b/windows/windows.js deleted file mode 100755 index 9355514..0000000 --- a/windows/windows.js +++ /dev/null @@ -1,232 +0,0 @@ -/** - * @author Matthew James - * Spotify Web Player for Linux - */ -//app from SWP4L App class, electron and electron's BrowserWindow (app !== electron.app) -module.exports = function(app, electron, BrowserWindow){ - const {globalShortcut} = require('electron'); - let _preferencesInstance, _aboutInstance; - class Preferences extends BrowserWindow{ - constructor(){ - var PREF_WIDTH = 800; - var PREF_HEIGHT = 450; - super({ - title: 'Preferences', - icon: App.icon, - width: PREF_WIDTH, - height: PREF_HEIGHT, - minWidth: PREF_WIDTH, - minHeight: PREF_HEIGHT, - maxWidth: PREF_WIDTH, - maxHeight: PREF_HEIGHT, - resizable: false, - show: false, - webPreferences: {preload: `${__dirname}/preferences/preload.js`} - }); - this.loadURL(`file://${__dirname}/preferences/preferences.html`); - this.setMenu(null); - this.webContents.once('dom-ready', () => { - this.show(); - }); - var focusKeys = function(){ - var showdevtools = function(){ - var win = BrowserWindow.getFocusedWindow(); - if (!win.isDevToolsOpened()){ - win.openDevTools() - } else { - win.closeDevTools(); - } - } - globalShortcut.register('CommandOrControl+Shift+I', showdevtools); - globalShortcut.register('F12', showdevtools); - globalShortcut.register('CommandOrControl+W', () => { - var win = BrowserWindow.getFocusedWindow(); - win.close(); - }); - } - this.on('focus', focusKeys); - this.on('blur', () => { - globalShortcut.unregisterAll(); - }); - } - } - class About extends BrowserWindow{ - constructor(){ - var ABOUT_WIDTH = 600; - var ABOUT_HEIGHT = 525; - super({ - title: 'About', - icon: App.icon, - width: ABOUT_WIDTH, - height: ABOUT_HEIGHT, - minWidth: ABOUT_WIDTH, - minHeight: ABOUT_HEIGHT, - maxWidth: ABOUT_WIDTH, - maxHeight: ABOUT_HEIGHT, - resizable: false, - show: false, - webPreferences: {preload:`${__dirname}/about/preload.js`} - }); - this.loadURL(`file://${__dirname}/about/about.html`); - this.setMenu(null); - this.webContents.once('dom-ready', () => { - this.show(); - }); - var focusKeys = function(){ - var showdevtools = function(){ - var win = BrowserWindow.getFocusedWindow(); - if (!win.isDevToolsOpened()){ - win.openDevTools() - } else { - win.closeDevTools(); - } - } - globalShortcut.register('CommandOrControl+Shift+I', showdevtools); - globalShortcut.register('F12', showdevtools); - globalShortcut.register('CommandOrControl+W', () => { - var win = BrowserWindow.getFocusedWindow(); - win.close(); - }); - } - this.on('focus', focusKeys); - this.on('blur', () => { - globalShortcut.unregisterAll(); - }); - } - } - class FacebookPopup extends BrowserWindow{ - constructor(name, url, session){ - super({ - title: name, - minWidth: 550, - minHeight: 280, - width: 550, - height: 280, - show: true, - icon: App.icon, - session: session, - webPreferences: { - preload: `${__dirname}/facebook/preload.js`, - nodeIntegration: false, - plugins: true - } - }); - this.loadURL(url); - this.setMenu(null); - if(app.settings.ShowDevTools) this.openDevTools(); - } - } - class Spotify extends BrowserWindow{ - constructor(){ - super({ - title: "Spotify Web Player", - icon: App.icon, - width: 1200, - height: 700, - show: true, - backgroundColor: "#121314", - minWidth: 800, - minHeight: 600, - webPreferences: { - nodeIntegration: false, - preload: `${__dirname}/spotify/preload.js`, - plugins: true, - webSecurity: false, - allowDisplayingInsecureContent: true, - allowRunningInsecureContent: true - } - }); - this.on('page-title-updated', function(event){ - event.preventDefault(); - }); - this.on('closed', () => { - electron.app.quit(); - process.exit(0); - }); - this.setMenu(null); - this.webContents.on('new-window', function(event, url, name, disposition){ - if(!~url.indexOf("facebook.com")) return; - (new FacebookPopup(name, url, this.webContents.session)).show(); - event.preventDefault(); - }); - this.webContents.once('dom-ready', () => { - this.show(); - if(app.settings.StartHidden) this.minimize(); - }); - this.webContents.session.setUserAgent('Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36'); - this.loadURL((app.settings.lastURL && app.settings.lastURL.indexOf('play.spotify.com') > -1 ? app.settings.lastURL : App.HOST)); - this.on('show', () => { - if(props.settings.ShowDevTools) this.openDevTools() - }); - var focusKeys = function(){ - globalShortcut.register('CommandOrControl+S', () => { - var win = BrowserWindow.getFocusedWindow(); - if (win == this) this.webContents.executeJavaScript('$(\'#suggest-area\').toggleClass(\'show\');'); - }); - globalShortcut.register('CommandOrControl+Shift+P', () => { - var win = BrowserWindow.getFocusedWindow(); - if (win == this) this.showPreferences(); - }); - globalShortcut.register('F1', () => { - var win = BrowserWindow.getFocusedWindow(); - if (win == this) this.showAbout(); - }); - globalShortcut.register('Shift+<', () => { - var win = BrowserWindow.getFocusedWindow(); - if (win == this) this.webContents.executeJavaScript('controller.previous();'); - }); - globalShortcut.register('Shift+>', () => { - var win = BrowserWindow.getFocusedWindow(); - if (win == this) this.webContents.executeJavaScript('controller.next();'); - }); - var showdevtools = function(){ - var win = BrowserWindow.getFocusedWindow(); - if (!win.isDevToolsOpened()){ - win.openDevTools() - } else { - win.closeDevTools(); - } - } - globalShortcut.register('CommandOrControl+Shift+I', showdevtools); - globalShortcut.register('F12', showdevtools); - globalShortcut.register('F11', () => { - var win = BrowserWindow.getFocusedWindow(); - if (win == this) this.setFullScreen(!this.isFullScreen()); - }); - globalShortcut.register('CommandOrControl+Shift+L', () => { - var win = BrowserWindow.getFocusedWindow(); - if (win == this) this.webContents.executeJavaScript('user.logout()'); - }); - globalShortcut.register('CommandOrControl+W', () => { - var win = BrowserWindow.getFocusedWindow(); - win.close(); - }); - } - this.on('focus', focusKeys); - this.on('blur', () => { - globalShortcut.unregisterAll(); - }); - } - showAbout(){ - if(_aboutInstance) return this.AboutInstance.show(); - _aboutInstance = new About(); - _aboutInstance.on('closed', function(){ - _aboutInstance = null; - }); - } - get AboutInstance(){ - return _aboutInstance - } - get PreferenceInstance(){ - return _preferencesInstance; - } - showPreferences(){ - if(_preferencesInstance) return this.About.show(); - _preferencesInstance = new Preferences(); - _preferencesInstance.on('closed', function(){ - _preferencesInstance = null; - }) - } - } - return Spotify; -}; \ No newline at end of file