added custom activity name template
Some checks failed
Build / Build (push) Failing after 6m3s

This commit is contained in:
2026-03-09 18:01:58 -06:00
parent 9d9dce052a
commit 24fb4cf752
4 changed files with 93 additions and 6 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.wasm
*.ndp
tmp
discord-rich-presence
discord-rich-presence
.DS_Store

18
main.go
View File

@@ -25,10 +25,11 @@ import (
// Configuration keys
const (
clientIDKey = "clientid"
usersKey = "users"
activityNameKey = "activityname"
spotifyLinksKey = "spotifylinks"
clientIDKey = "clientid"
usersKey = "users"
activityNameKey = "activityname"
activityNameTemplateKey = "activitynametemplate"
spotifyLinksKey = "spotifylinks"
)
const (
@@ -45,6 +46,7 @@ const (
activityNameTrack = "Track"
activityNameArtist = "Artist"
activityNameAlbum = "Album"
activityNameCustom = "Custom"
)
// userToken represents a user-token mapping from the config
@@ -168,6 +170,14 @@ func (p *discordPlugin) NowPlaying(input scrobbler.NowPlayingRequest) error {
case activityNameArtist:
activityName = input.Track.Artist
statusDisplayType = statusDisplayName
case activityNameCustom:
template, _ := pdk.GetConfig(activityNameTemplateKey)
if template != "" {
activityName = template
activityName = strings.ReplaceAll(activityName, "{track}", input.Track.Title)
activityName = strings.ReplaceAll(activityName, "{artist}", input.Track.Artist)
activityName = strings.ReplaceAll(activityName, "{album}", input.Track.Album)
}
}
// Resolve Spotify URLs if enabled

View File

@@ -223,6 +223,62 @@ var _ = Describe("discordPlugin", func() {
Entry("uses track album when configured", "Album", true, "Test Album", 0),
Entry("uses track artist when configured", "Artist", true, "Test Artist", 0),
)
DescribeTable("custom activity name template",
func(template string, templateExists bool, expectedName string) {
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
pdk.PDKMock.On("GetConfig", usersKey).Return(`[{"username":"testuser","token":"test-token"}]`, true)
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
pdk.PDKMock.On("GetConfig", activityNameKey).Return("Custom", true)
pdk.PDKMock.On("GetConfig", activityNameTemplateKey).Return(template, templateExists)
pdk.PDKMock.On("GetConfig", spotifyLinksKey).Return("", false)
// Connect mocks
host.CacheMock.On("GetInt", "discord.seq.testuser").Return(int64(0), false, errors.New("not found"))
gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`)
host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
return req.Method == "GET" && req.URL == "https://discord.com/api/gateway"
})).Return(&host.HTTPResponse{StatusCode: 200, Body: gatewayResp}, nil)
host.WebSocketMock.On("Connect", mock.MatchedBy(func(url string) bool {
return strings.Contains(url, "gateway.discord.gg")
}), mock.Anything, "testuser").Return("testuser", nil)
// Capture the activity payload sent to Discord
var sentPayload string
host.WebSocketMock.On("SendText", "testuser", mock.Anything).Run(func(args mock.Arguments) {
sentPayload = args.Get(1).(string)
}).Return(nil)
host.SchedulerMock.On("ScheduleRecurring", mock.Anything, payloadHeartbeat, "testuser").Return("testuser", nil)
host.SchedulerMock.On("CancelSchedule", "testuser-clear").Return(nil)
// Image mocks
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
host.CacheMock.On("SetString", discordImageKey, mock.Anything, mock.Anything).Return(nil)
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`{}`)}, nil)
host.SchedulerMock.On("ScheduleOneTime", mock.Anything, payloadClearActivity, "testuser-clear").Return("testuser-clear", nil)
err := plugin.NowPlaying(scrobbler.NowPlayingRequest{
Username: "testuser",
Position: 10,
Track: scrobbler.TrackInfo{
ID: "track1",
Title: "Test Song",
Artist: "Test Artist",
Album: "Test Album",
Duration: 180,
},
})
Expect(err).ToNot(HaveOccurred())
Expect(sentPayload).To(ContainSubstring(fmt.Sprintf(`"name":"%s"`, expectedName)))
},
Entry("uses custom template with all placeholders", "{artist} - {track} ({album})", true, "Test Artist - Test Song (Test Album)"),
Entry("uses custom template with only track", "{track}", true, "Test Song"),
Entry("uses custom template with only artist", "{artist}", true, "Test Artist"),
Entry("uses custom template with only album", "{album}", true, "Test Album"),
Entry("uses custom template with plain text", "Now Playing", true, "Now Playing"),
Entry("falls back to Navidrome when template is empty", "", false, "Navidrome"),
)
})
Describe("Scrobble", func() {

View File

@@ -56,10 +56,17 @@
"Default",
"Track",
"Album",
"Artist"
"Artist",
"Custom"
],
"default": "Default"
},
"activitynametemplate": {
"type": "string",
"title": "Custom Activity Name Template",
"description": "Template for the activity name. Available placeholders: {track}, {artist}, {album}",
"default": "{artist} - {track}"
},
"uguuenabled": {
"type": "boolean",
"title": "Upload artwork to uguu.se (enable if Navidrome is not publicly accessible)",
@@ -118,6 +125,19 @@
"format": "radio"
}
},
{
"type": "Control",
"scope": "#/properties/activitynametemplate",
"rule": {
"effect": "SHOW",
"condition": {
"scope": "#/properties/activityname",
"schema": {
"const": "Custom"
}
}
}
},
{
"type": "Control",
"scope": "#/properties/uguuenabled"