Skip to content
Snippets Groups Projects
Commit 5b0e5c7e authored by mjzou2's avatar mjzou2
Browse files

Upload New File

parent ada63200
No related branches found
No related tags found
No related merge requests found
/*
* MMM-OnSpotify
* MIT license
*
* By Fabrizz <3 | https://github.com/Fabrizz/MMM-OnSpotify
*/
"use strict";
/* FUTURE: Add a "guessTrackTime" option that still changes the timer even when the update interval window is higher */
Module.register("MMM-OnSpotify", {
defaults: {
name: "MMM-OnSpotify",
/* configDeepMerge: true, deepMerge: true, <-- Does not work */
// Sends notifications containin them locking. Works with other DynamicTheme modules.
advertisePlayerTheme: true,
// What to display when the player is idle
// user | affinity | both | none | logo
displayWhenEmpty: "both",
// Show tracks instead of albums when the player is idle.
userAffinityUseTracks: false,
// Prefers larger images. Affects display and the "NOW_PLAYING" broadcast.
prefersLargeImageSize: false,
// If you selected a high interval, you can hide the progress timestamp
// and animate the seekbar to the timing of the updateInterval, making it look better.
hideTrackLenghtAndAnimateProgress: false,
// Shows the Vibrant output in the console as a palette and color data.
showDebugPalette: false,
// Max age in seconds for personal data. If set to 0 they update when the player changes
// state, as user data does not change that much, this prevents unnecessary api calls.
userDataMaxAge: 14400,
userAffinityMaxAge: 36000,
updateInterval: {
isPlaying: 1,
isEmpty: 2,
isPlayingHidden: 1,
isEmptyHidden: 4,
// When there is a connection error
onReconnecting: 4,
// If the errors persist, use a wider window between calls.
onError: 8,
},
theming: {
// As RPIs are not powerful you can disable some of the animations.
mediaAnimations: false,
fadeAnimations: false,
transitionAnimations: true,
// Show the Spotify Code Bar [EXPERIMENTAL]
spotifyCodeExperimentalShow: true,
// Themes the code and bars using the cover art
// Using this option uses a brighter color palette, to allow better
// scans using the camera. Also affects other colors in the module.
spotifyCodeExperimentalUseColor: true,
// If the code should be shown standalone or separated from the cover art.
spotifyCodeExperimentalSeparateItem: true,
// Round cover art and Spotify Code corners
roundMediaCorners: true,
roundProgressBar: true,
useColorInProgressBar: true,
useColorInTitle: true,
// Get colors from the used profile image
useColorInUserData: true,
// Show the blurred color background to give a depth to the module
showBlurBackground: true,
// Blur less on the side that the module touches the frame, if your actual mirror
// is larger than your screen, this deletes the color overflow in the corners [bottom | left | right]
blurCorrectionInFrameSide: false,
// Blur less in all sides, useful if you like less color or you dont like the blur difference just
// in the frame sides.
blurCorrectionInAllSides: false,
// Depending on the device, the device icon changes, you can use always the dault if you dont like it
alwaysUseDefaultDeviceIcon: false,
},
// Internal, if you want to change the "GET_PLAYING" notification mapping
events: {
GET_PLAYING: "GET_PLAYING",
ONSPOTIFY_GET: "ONSPOTIFY_GET",
LIVELYRICS_NOTICE: "LIVELYRICS_NOTICE",
ALL_MODULES_STARTED: "ALL_MODULES_STARTED",
ONSPOTIFY_SHOW: "ONSPOTIFY_SHOW",
ONSPOTIFY_HIDE: "ONSPOTIFY_HIDE",
},
// Allow config with or without
isPlaying: 1,
isEmpty: 2,
isPlayingHidden: 1,
isEmptyHidden: 3,
onReconnecting: 4,
onError: 8,
mediaAnimations: false,
fadeAnimations: false,
transitionAnimations: true,
spotifyCodeExperimentalShow: true,
spotifyCodeExperimentalUseColor: true,
spotifyCodeExperimentalSeparateItem: true,
roundMediaCorners: true,
roundProgressBar: true,
useColorInProgressBar: true,
useColorInTitle: true,
useColorInUserData: true,
showBlurBackground: true,
blurCorrectionInFrameSide: false,
blurCorrectionInAllSides: false,
alwaysUseDefaultDeviceIcon: false,
},
start: function () {
this.configFix();
this.logBadge();
this.isConnectedToSpotify = false;
this.currentIntervalId = null;
this.isFirstCall = true;
this.nowDisplaying = null;
this.globalThemeSelected = null;
this.currentStatus = "";
this.retries = 0;
this.lastPrivate = [null, null];
this.lastStatus = "isPlaying";
this.muduleHidden = false;
this.firstSongOnLoad = true;
///////////////////////
this.version = "2.2.0";
///////////////////////
this.displayUser =
this.config.displayWhenEmpty.toLowerCase() === "user" ||
this.config.displayWhenEmpty.toLowerCase() === "both"
? true
: false;
this.displayAffinity =
this.config.displayWhenEmpty.toLowerCase() === "affinity" ||
this.config.displayWhenEmpty.toLowerCase() === "both"
? true
: false;
this.loadVibrantPalette =
this.config.advertisePlayerTheme ||
this.config.theming.useColorInProgressBar ||
this.config.theming.useColorInTitle ||
this.config.theming.useColorInTitleBorder ||
this.config.theming.showBlurBackground ||
this.config.theming.useColorInUserData ||
(this.config.theming.spotifyCodeExperimentalShow &&
this.config.theming.spotifyCodeExperimentalUseColor)
? true
: false;
// eslint-disable-next-line no-undef
this.builder = new SpotifyDomBuilder(
this.file(""),
this.config,
{
useVibrantOnChange: this.loadVibrantPalette,
moduleSide: this.data.position,
version: this.version,
},
(a, b) => this.translate(a, b),
);
/* Future update:
* Maybe cache music lyrics based on the queue based on module notfs ? (MMM-Lyrics)
* Show queue instead of nowplaying or (nowplaying + next)
* cache next song images on slow networs ? <--- Nah, more api calls
*/
this.userData = null;
this.playerData = null;
this.affinityData = null;
// this.queueData = null;
// this.recentData = null;
this.root = document.querySelector(":root");
this.sendSocketNotification("SET_CREDENTIALS_REFRESH", {
preferences: {
userAffinityUseTracks: this.config.userAffinityUseTracks,
},
credentials: {
clientId: this.config.clientID,
clientSecret: this.config.clientSecret,
accessToken: this.config.accessToken,
refreshToken: this.config.refreshToken,
},
});
this.updateFetchingLoop(this.config.updateInterval[this.lastStatus]);
},
getDom: function () {
if (this.playerData && this.playerData.deviceIsPrivate) {
this.nowDisplaying = "private";
return this.builder.privateSession(this.playerData);
}
if (this.playerData && !this.playerData.statusIsPlayerEmpty) {
this.nowDisplaying = "player";
if (!this.globalThemeSelected) {
this.sendNotification("THEME_PREFERENCE", {
provider: "MMM-OnSpotify",
// --[ONSP]-[VIBRANT]-[VIBRANT-SCHEME-COLOR]
providerPrefix: "ONSP",
providerScheme: "VIBRANT",
// Lock on [MMM-OnSpotify] until [provider] decides (player is empty)
set: "lock",
until: "provider",
// Override other providers as its an ongoing event
priority: true,
});
this.globalThemeSelected = true;
}
return this.builder.nowPlaying(this.playerData);
} else {
this.nowDisplaying = "empty";
if (this.globalThemeSelected) {
this.sendNotification("THEME_PREFERENCE", {
provider: "MMM-OnSpotify",
set: "unlock",
});
this.globalThemeSelected = false;
}
switch (this.config.displayWhenEmpty.toLowerCase()) {
case "both":
return this.builder.userAffinity(this.userData, this.affinityData);
case "user":
return this.builder.user(this.userData);
case "affinity":
return this.builder.affinity(this.affinityData);
case "none":
return this.builder.empty(false);
default:
return this.builder.spotifyLogo();
}
}
},
getStyles: function () {
return [this.file("css/included.css"), this.file("css/custom.css")];
},
getScripts: function () {
let files = [
this.file(
"node_modules/moment-duration-format/lib/moment-duration-format.js",
),
this.file("utils/SpotifyDomBuilder.js"),
// Use a custom build of the "node-vibrant" library that fixes the webworker usage
this.file("vendor/vibrant.worker.min.js"),
// MM2 loader cannot load .map files // this.file("vendor/vibrant.worker.min.js.map"),
];
// eslint-disable-next-line no-undef
if (
this.config.theming.spotifyCodeExperimentalShow &&
// Check if other modules load DOMPurify
!("DOMPurify" in window)
)
files.push(this.file("node_modules/dompurify/dist/purify.min.js"));
return files;
},
getTranslations: function () {
return {
en: "translations/en.json",
es: "translations/es.json",
};
},
suspend: function () {
this.moduleHidden = true;
this.smartUpdate();
console.info(
"%c· MMM-OnSpotify %c %c[INFO]%c " + this.translate("SUSPEND"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:darkcyan;color:black;",
"",
);
},
resume: function () {
console.info(
"%c· MMM-OnSpotify %c %c[INFO]%c " + this.translate("RESUME"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:darkcyan;color:black;",
"",
);
this.moduleHidden = false;
this.smartUpdate();
},
socketNotificationReceived: function (notification, payload) {
switch (notification) {
case "PLAYER_DATA":
this.isConnectedToSpotify = true;
this.connectionRetries = 0;
if (!payload.statusPlayerUpdating) this.playerData = payload;
this.smartUpdate("PLAYER_DATA");
if (payload.statusIsNewSong || this.firstSongOnLoad) {
if (payload.itemName)
this.sendNotification("NOW_PLAYING", {
playerIsEmpty: false,
name: payload.itemName,
image: this.getImage(
this.playerData.itemImages,
this.config.prefersLargeImageSize,
),
uri: this.playerData.itemUri,
artist: payload.itemArtist,
artists: payload.itemArtists,
type: payload.playerMediaType,
device: payload.deviceName,
deviceType: payload.deviceType,
});
this.firstSongOnLoad = false;
}
if (payload.statusIsChangeToEmptyPlayer)
this.sendNotification("NOW_PLAYING", { playerIsEmpty: true });
if (payload.statusIsDeviceChange)
this.sendNotification("DEVICE_CHANGE", {
device: payload.deviceName,
type: payload.deviceType,
});
break;
case "USER_DATA":
this.userData = payload;
this.userData.age = Date.now();
this.requestUserData = false;
this.smartUpdate("USER_DATA");
if (this.userData.product !== "premium")
console.warn(
"%c· MMM-OnSpotify %c %c[WARN]%c " +
this.translate("PRODUCT_WARNING"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:orange;color:black;",
"",
);
break;
case "AFFINITY_DATA":
this.affinityData = payload;
this.affinityData.age = Date.now();
this.requestAffinityData = false;
this.smartUpdate("AFFINITY_DATA");
break;
case "CONNECTION_ERRONED":
if (this.isConnectedToSpotify) {
console.info(
"%c· MMM-OnSpotify %c %c[WARN]%c " +
this.translate("CONNECTION_WARNING"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:orange;color:black;",
"",
);
}
this.isConnectedToSpotify = false;
this.smartUpdate("PLAYER_DATA");
break;
}
},
notificationReceived: function (notification, payload) {
this.config.events[notification]?.split(" ").forEach((e) => {
switch (e) {
case "GET_PLAYING":
this.sendNotification("NOW_PLAYING", {
playerIsEmpty: this.playerData.isEmpty,
name: this.playerData.itemName,
image: this.getImage(
this.playerData.itemImages,
this.config.prefersLargeImageSize,
),
uri: this.playerData.itemUri,
artist: this.playerData.itemArtist,
artists: this.playerData.itemArtists,
type: this.playerData.playerMediaType,
device: this.playerData.deviceName,
deviceType: this.playerData.deviceType,
});
break;
case "ONSPOTIFY_GET":
this.sendNotification("ONSPOTIFY_NOTICE", {
version: this.version,
directColorData:
this.loadVibrantPalette && this.config.advertisePlayerTheme,
loadsSpotifyCode: this.config.theming.spotifyCodeExperimentalShow,
});
break;
case "ONSPOTIFY_HIDE":
this.hide();
break;
case "ONSPOTIFY_SHOW":
this.show();
break;
case "LIVELYRICS_NOTICE":
console.info(
"%c· MMM-OnSpotify %c %c[INFO]%c " +
this.translate("LIVELYRICS_NOTICE"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:darkcyan;color:black;",
"",
);
break;
case "ALL_MODULES_STARTED":
if (this.config.showDebugPalette)
console.info(
"%c· MMM-OnSpotify %c %c[INFO]%c " +
this.translate("DEBUG_COLORS"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:darkcyan;color:black;",
"",
);
if (this.config.theming.spotifyCodeExperimentalShow)
console.info(
"%c· MMM-OnSpotify %c %c[WARN]%c " +
this.translate("SPOTIFYCODE_EXPERIMENTAL"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:orange;color:black;",
"",
);
this.sendNotification("LIVELYRICS_GET");
break;
default:
break;
}
});
},
/* Utils */
updateFetchingLoop: function (n) {
if (this.currentIntervalId) clearInterval(this.currentIntervalId);
this.currentIntervalId = setInterval(() => {
if (this.isConnectedToSpotify || this.currentStatus === "onReconnecting")
this.instantUpdate();
}, n * 1000);
},
instantUpdate: function () {
this.sendSocketNotification("REFRESH_PLAYER");
// To prevent multiple api calls, the call is requested but its only called
// here, following the interval of the player.
if (this.requestUserData) this.sendSocketNotification("REFRESH_USER");
if (this.requestAffinityData)
this.sendSocketNotification("REFRESH_AFFINITY");
},
smartUpdate: function (type) {
// Request data to display when the player is empty
// Update only if there is no data or the player is changing state
this.requestUserData =
this.displayUser &&
this.isConnectedToSpotify &&
(!this.userData ||
(this.playerData
? this.playerData.statusIsChangeToEmptyPlayer
? Date.now() > this.userData.age + this.config.userDataMaxAge * 1000
: false
: false));
this.requestAffinityData =
this.displayAffinity &&
this.isConnectedToSpotify &&
(!this.affinityData ||
(this.playerData
? this.playerData.statusIsChangeToEmptyPlayer
? Date.now() >
this.affinityData.age + this.config.userAffinityMaxAge * 1000
: false
: false))
? true
: false;
this.currentStatus = this.isConnectedToSpotify
? this.playerData
? this.playerData.playerIsPlaying
? this.moduleHidden
? "isPlayingHidden"
: "isPlaying"
: this.moduleHidden
? "isEmptyHidden"
: "isEmpty"
: null
: "onReconnecting";
if (this.currentStatus !== this.lastStatus) {
if (this.isFirstCall) {
this.instantUpdate();
this.isFirstCall = false;
}
this.updateFetchingLoop(this.config.updateInterval[this.currentStatus]);
this.lastStatus = this.currentStatus;
}
if (this.currentStatus === "onReconnecting") {
this.retries = this.retries > 25 ? this.retries : this.retries + 1;
if (this.retries === 25) {
console.error(
"%c· MMM-OnSpotify %c %c[ERRO]%c " +
this.translate("CONNECTION_ERROR"),
"background-color:#84CC16;color:black;border-radius:0.4em",
"",
"background-color:darkred;color:black;",
"",
);
this.playerData = {
statusIsPlayerEmpty: true,
statusIsNewSong: false,
statusIsChangeToEmptyPlayer: true,
statusIsChangeToMediaPlayer: false,
statusPlayerUpdating: false,
statusIsDeviceChange: false,
};
this.updateFetchingLoop(this.config.updateInterval.onError);
return this.updateDom();
}
return;
} else {
this.retries = 0;
}
if (
this.playerData &&
this.playerData.deviceIsPrivate &&
!this.lastPrivate[0]
) {
this.lastPrivate = [true];
return this.updateDom();
}
if (
this.playerData &&
!this.playerData.deviceIsPrivate &&
this.lastPrivate[0]
) {
this.lastPrivate = [false];
return this.updateDom();
}
if (this.playerData) {
if (
this.playerData.statusIsChangeToEmptyPlayer ||
(this.nowDisplaying === "empty" && !this.playerData.statusIsPlayerEmpty)
)
return this.updateDom();
if (
this.playerData.statusIsChangeToMediaPlayer ||
(this.nowDisplaying === "player" && this.playerData.statusIsPlayerEmpty)
)
return this.updateDom();
if (
this.nowDisplaying === "player" &&
!this.playerData.statusIsPlayerEmpty &&
type === "PLAYER_DATA"
)
return this.builder.updatePlayerData(this.playerData);
}
if (type === "USER_DATA") this.builder.updateUserData(this.userData);
if (type === "AFFINITY_DATA")
this.builder.updateAffinityData(this.affinityData);
},
getImage: (im, prefersLarge) =>
im ? (prefersLarge ? im.large : im.medium) : null,
logBadge: function () {
console.log(
` ⠖ %c by Fabrizz %c ${this.name}`,
"background-color: #555;color: #fff;margin: 0.4em 0em 0.4em 0.4em;padding: 5px 3px 5px 5px;border-radius: 7px 0 0 7px;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;",
"background-color: #bc81e0;background-image: linear-gradient(90deg, #3F6212, #84CC16);color: #fff;margin: 0.4em 0.4em 0.4em 0em;padding: 5px 5px 5px 3px;border-radius: 0 7px 7px 0;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)",
);
},
/* Fix for older version that have object based cfg */
/* Its not pretty, but the default does not work */
configFix: function () {
typeof this.config.isPlaying === "number"
? (this.config.updateInterval.isPlaying = this.config.isPlaying)
: null;
typeof this.config.isEmpty === "number"
? (this.config.updateInterval.isEmpty = this.config.isEmpty)
: null;
typeof this.config.isPlayingHidden === "number"
? (this.config.updateInterval.isPlayingHidden =
this.config.isPlayingHidden)
: null;
typeof this.config.isEmptyHidden === "number"
? (this.config.updateInterval.isEmptyHidden = this.config.isEmptyHidden)
: null;
typeof this.config.onReconnecting === "number"
? (this.config.updateInterval.onReconnecting = this.config.onReconnecting)
: null;
typeof this.config.onError === "number"
? (this.config.updateInterval.onError = this.config.onError)
: null;
typeof this.config.mediaAnimations === "boolean"
? (this.config.theming.mediaAnimations = this.config.mediaAnimations)
: null;
typeof this.config.fadeAnimations === "boolean"
? (this.config.theming.fadeAnimations = this.config.fadeAnimations)
: null;
typeof this.config.transitionAnimations === "boolean"
? (this.config.theming.transitionAnimations =
this.config.transitionAnimations)
: null;
typeof this.config.spotifyCodeExperimentalShow === "boolean"
? (this.config.theming.spotifyCodeExperimentalShow =
this.config.spotifyCodeExperimentalShow)
: null;
typeof this.config.spotifyCodeExperimentalUseColor === "boolean"
? (this.config.theming.spotifyCodeExperimentalUseColor =
this.config.spotifyCodeExperimentalUseColor)
: null;
typeof this.config.spotifyCodeExperimentalSeparateItem === "boolean"
? (this.config.theming.spotifyCodeExperimentalSeparateItem =
this.config.spotifyCodeExperimentalSeparateItem)
: null;
typeof this.config.roundMediaCorners === "boolean"
? (this.config.theming.roundMediaCorners = this.config.roundMediaCorners)
: null;
typeof this.config.roundProgressBar === "boolean"
? (this.config.theming.roundProgressBar = this.config.roundProgressBar)
: null;
typeof this.config.useColorInProgressBar === "boolean"
? (this.config.theming.useColorInProgressBar =
this.config.useColorInProgressBar)
: null;
typeof this.config.useColorInTitle === "boolean"
? (this.config.theming.useColorInTitle = this.config.useColorInTitle)
: null;
typeof this.config.useColorInUserData === "boolean"
? (this.config.theming.useColorInUserData =
this.config.useColorInUserData)
: null;
typeof this.config.showBlurBackground === "boolean"
? (this.config.theming.showBlurBackground =
this.config.showBlurBackground)
: null;
typeof this.config.blurCorrectionInFrameSide === "boolean"
? (this.config.theming.blurCorrectionInFrameSide =
this.config.blurCorrectionInFrameSide)
: null;
typeof this.config.blurCorrectionInAllSides === "boolean"
? (this.config.theming.blurCorrectionInAllSides =
this.config.blurCorrectionInAllSides)
: null;
typeof this.config.alwaysUseDefaultDeviceIcon === "boolean"
? (this.config.theming.alwaysUseDefaultDeviceIcon =
this.config.alwaysUseDefaultDeviceIcon)
: null;
},
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment