From 02089a35ee099b565fa1b7267ee4d49afb1e5de8 Mon Sep 17 00:00:00 2001 From: Yevhen Kuzmovych Date: Thu, 25 Sep 2025 06:08:15 +0200 Subject: [PATCH] Feature: Your spotify widget (#5813) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/yourspotify.md | 28 ++++++++++ mkdocs.yml | 1 + public/locales/en/common.json | 5 ++ src/utils/config/service-helpers.js | 8 +++ src/widgets/components.js | 1 + src/widgets/widgets.js | 2 + src/widgets/yourspotify/component.jsx | 76 +++++++++++++++++++++++++++ src/widgets/yourspotify/widget.js | 27 ++++++++++ 8 files changed, 148 insertions(+) create mode 100644 docs/widgets/services/yourspotify.md create mode 100644 src/widgets/yourspotify/component.jsx create mode 100644 src/widgets/yourspotify/widget.js diff --git a/docs/widgets/services/yourspotify.md b/docs/widgets/services/yourspotify.md new file mode 100644 index 000000000..b5318af0e --- /dev/null +++ b/docs/widgets/services/yourspotify.md @@ -0,0 +1,28 @@ +--- +title: Your Spotify +description: Your Spotify Widget Configuration +--- + +Learn more about [Your Spotify](https://github.com/Yooooomi/your_spotify). + +Find your API key under `Settings > Account > Public token`, click `Generate` if not yet generated, copy key after +`?token=`. + +Allowed fields: `["songs", "time", "artists"]`. + +```yaml +widget: + type: yourspotify + url: http://your-spotify-server.host.or.ip # if using lsio image, add /api/ + key: apikeyapikeyapikeyapikeyapikey + interval: month # optional, defaults to week +``` + +#### Interval + +Allowed values for `interval`: `day`, `week`, `month`, `year`, `all`. + +!!! note + + `interval` is different from predefined intervals you see in `Your Spotify`'s UI. + For example, `This week` in UI means _from the start of this week_, here `week` means _past 7 days_. diff --git a/mkdocs.yml b/mkdocs.yml index d46382923..8be043596 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -176,6 +176,7 @@ nav: - widgets/services/wgeasy.md - widgets/services/whatsupdocker.md - widgets/services/xteve.md + - widgets/services/yourspotify.md - widgets/services/zabbix.md - "Information Widgets": - widgets/info/index.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 49d1325d5..97ee9988b 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1114,5 +1114,10 @@ "num_success_latest": "Succeeding", "num_failure_latest": "Failing", "bytes_added_30": "Bytes Added" + }, + "yourspotify": { + "songs": "Songs", + "time": "Time", + "artists": "Artists" } } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 9cdff1da0..23cb3be1b 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -408,6 +408,9 @@ export function cleanServiceGroups(groups) { // wgeasy threshold, + // yourspotify + interval, + // technitium range, @@ -623,6 +626,11 @@ export function cleanServiceGroups(groups) { if (pool3) widget.pool3 = pool3; if (pool4) widget.pool4 = pool4; } + if (type === "yourspotify") { + if (interval !== undefined) { + widget.interval = interval; + } + } return widget; }); return cleanedService; diff --git a/src/widgets/components.js b/src/widgets/components.js index 4b8d4e715..d6b150b85 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -150,6 +150,7 @@ const components = { wgeasy: dynamic(() => import("./wgeasy/component")), whatsupdocker: dynamic(() => import("./whatsupdocker/component")), xteve: dynamic(() => import("./xteve/component")), + yourspotify: dynamic(() => import("./yourspotify/component")), zabbix: dynamic(() => import("./zabbix/component")), }; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index bc9f137d3..90cd7bfc8 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -141,6 +141,7 @@ import watchtower from "./watchtower/widget"; import wgeasy from "./wgeasy/widget"; import whatsupdocker from "./whatsupdocker/widget"; import xteve from "./xteve/widget"; +import yourspotify from "./yourspotify/widget"; import zabbix from "./zabbix/widget"; const widgets = { @@ -291,6 +292,7 @@ const widgets = { wgeasy, whatsupdocker, xteve, + yourspotify, zabbix, }; diff --git a/src/widgets/yourspotify/component.jsx b/src/widgets/yourspotify/component.jsx new file mode 100644 index 000000000..e186440eb --- /dev/null +++ b/src/widgets/yourspotify/component.jsx @@ -0,0 +1,76 @@ +import Block from "components/services/widget/block"; +import Container from "components/services/widget/container"; +import { useTranslation } from "next-i18next"; +import { useMemo } from "react"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +function getStartDate(interval) { + const d = new Date(); + switch (interval) { + case "day": + d.setDate(d.getDate() - 1); + break; + case "week": + d.setDate(d.getDate() - 7); + break; + case "month": + d.setMonth(d.getMonth() - 1); + break; + case "year": + d.setFullYear(d.getFullYear() - 1); + break; + } + return d.toISOString(); +} + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + const interval = widget?.interval || "week"; + + const date = useMemo(() => { + return interval === "all" ? "2006-04-23T00:00:00.000Z" : getStartDate(interval); + }, [interval]); + + const params = { + timeSplit: "all", + start: date, + }; + + const { data: songsListened, error: songsError } = useWidgetAPI(widget, "songs", params); + const { data: timeListened, error: timeError } = useWidgetAPI(widget, "time", params); + const { data: artistsListened, error: artistsError } = useWidgetAPI(widget, "artists", params); + + if (songsError || timeError || artistsError) { + return ; + } + + if (isNaN(songsListened) || isNaN(timeListened) || isNaN(artistsListened)) { + return ( + + + + + + ); + } + + return ( + + + + 0 ? "common.duration" : "common.number", // Display 0 if duration is 0 + { + value: timeListened / 1000, + }, + )} + /> + + + ); +} diff --git a/src/widgets/yourspotify/widget.js b/src/widgets/yourspotify/widget.js new file mode 100644 index 000000000..c28edd0ac --- /dev/null +++ b/src/widgets/yourspotify/widget.js @@ -0,0 +1,27 @@ +import { asJson } from "utils/proxy/api-helpers"; +import genericProxyHandler from "utils/proxy/handlers/generic"; + +const widget = { + api: "{url}/spotify/{endpoint}?token={key}", + proxyHandler: genericProxyHandler, + + mappings: { + songs: { + endpoint: "songs_per", + params: ["start", "timeSplit"], + map: (data) => asJson(data)[0]?.count || 0, + }, + time: { + endpoint: "time_per", + params: ["start", "timeSplit"], + map: (data) => asJson(data)[0]?.count || 0, + }, + artists: { + endpoint: "different_artists_per", + params: ["start", "timeSplit"], + map: (data) => asJson(data)[0]?.artists?.length || 0, + }, + }, +}; + +export default widget;