Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
39293031d9
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
*.wasm
|
*.wasm
|
||||||
*.ndp
|
*.ndp
|
||||||
tmp
|
tmp
|
||||||
|
.DS_Store
|
||||||
|
|||||||
10
main.go
10
main.go
@@ -28,6 +28,7 @@ const (
|
|||||||
clientIDKey = "clientid"
|
clientIDKey = "clientid"
|
||||||
usersKey = "users"
|
usersKey = "users"
|
||||||
activityNameKey = "activityname"
|
activityNameKey = "activityname"
|
||||||
|
activityNameTemplateKey = "activitynametemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Activity name display options
|
// Activity name display options
|
||||||
@@ -36,6 +37,7 @@ const (
|
|||||||
activityNameTrack = "Track"
|
activityNameTrack = "Track"
|
||||||
activityNameArtist = "Artist"
|
activityNameArtist = "Artist"
|
||||||
activityNameAlbum = "Album"
|
activityNameAlbum = "Album"
|
||||||
|
activityNameCustom = "Custom"
|
||||||
)
|
)
|
||||||
|
|
||||||
// userToken represents a user-token mapping from the config
|
// userToken represents a user-token mapping from the config
|
||||||
@@ -155,6 +157,14 @@ func (p *discordPlugin) NowPlaying(input scrobbler.NowPlayingRequest) error {
|
|||||||
activityName = input.Track.Album
|
activityName = input.Track.Album
|
||||||
case activityNameArtist:
|
case activityNameArtist:
|
||||||
activityName = input.Track.Artist
|
activityName = input.Track.Artist
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send activity update
|
// Send activity update
|
||||||
|
|||||||
61
main_test.go
61
main_test.go
@@ -230,6 +230,67 @@ var _ = Describe("discordPlugin", func() {
|
|||||||
Entry("uses track album when configured", "Album", true, "Test Album"),
|
Entry("uses track album when configured", "Album", true, "Test Album"),
|
||||||
Entry("uses track artist when configured", "Artist", true, "Test Artist"),
|
Entry("uses track artist when configured", "Artist", true, "Test Artist"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Connect mocks
|
||||||
|
host.CacheMock.On("GetInt", "discord.seq.testuser").Return(int64(0), false, errors.New("not found"))
|
||||||
|
gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`)
|
||||||
|
gatewayReq := &pdk.HTTPRequest{}
|
||||||
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://discord.com/api/gateway").Return(gatewayReq).Once()
|
||||||
|
pdk.PDKMock.On("Send", gatewayReq).Return(pdk.NewStubHTTPResponse(200, nil, gatewayResp)).Once()
|
||||||
|
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", mock.MatchedBy(func(key string) bool {
|
||||||
|
return strings.HasPrefix(key, "discord.image.")
|
||||||
|
})).Return("", false, nil)
|
||||||
|
host.CacheMock.On("SetString", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||||
|
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
|
||||||
|
assetsReq := &pdk.HTTPRequest{}
|
||||||
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, mock.MatchedBy(func(url string) bool {
|
||||||
|
return strings.Contains(url, "external-assets")
|
||||||
|
})).Return(assetsReq)
|
||||||
|
pdk.PDKMock.On("Send", assetsReq).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`{"key":"test-key"}`)))
|
||||||
|
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() {
|
Describe("Scrobble", func() {
|
||||||
|
|||||||
@@ -55,10 +55,17 @@
|
|||||||
"Default",
|
"Default",
|
||||||
"Track",
|
"Track",
|
||||||
"Album",
|
"Album",
|
||||||
"Artist"
|
"Artist",
|
||||||
|
"Custom"
|
||||||
],
|
],
|
||||||
"default": "Default"
|
"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": {
|
"uguuenabled": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"title": "Upload artwork to uguu.se (enable if Navidrome is not publicly accessible)",
|
"title": "Upload artwork to uguu.se (enable if Navidrome is not publicly accessible)",
|
||||||
@@ -111,6 +118,19 @@
|
|||||||
"format": "radio"
|
"format": "radio"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "Control",
|
||||||
|
"scope": "#/properties/activitynametemplate",
|
||||||
|
"rule": {
|
||||||
|
"effect": "SHOW",
|
||||||
|
"condition": {
|
||||||
|
"scope": "#/properties/activityname",
|
||||||
|
"schema": {
|
||||||
|
"const": "Custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "Control",
|
"type": "Control",
|
||||||
"scope": "#/properties/uguuenabled"
|
"scope": "#/properties/uguuenabled"
|
||||||
|
|||||||
Reference in New Issue
Block a user