mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
feat: update user configuration handling to use user key prefix for improved clarity
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
32bae38ffd
commit
32172f2fce
@ -31,10 +31,12 @@ This plugin implements three capabilities to demonstrate the nd-host library:
|
||||
|
||||
Configure in the Navidrome UI (Settings → Plugins → discord-rich-presence):
|
||||
|
||||
| Key | Description | Example |
|
||||
|------------|----------------------------------------------------------|--------------------------------|
|
||||
| `clientid` | Your Discord application ID | `123456789012345678` |
|
||||
| `users` | Comma-separated list of `username:token` pairs | `alice:token123,bob:token456` |
|
||||
| Key | Description | Example |
|
||||
|---------------|-------------------------------------------|--------------------------------|
|
||||
| `clientid` | Your Discord application ID | `123456789012345678` |
|
||||
| `user.<name>` | Discord token for the specified user | `user.alice` = `token123` |
|
||||
|
||||
Each user is configured as a separate key with the `user.` prefix.
|
||||
|
||||
|
||||
### Getting Configuration Values
|
||||
@ -43,9 +45,10 @@ Configure in the Navidrome UI (Settings → Plugins → discord-rich-presence):
|
||||
|
||||
2. **Discord Token**: This requires extracting your user token from Discord (not recommended for security reasons)
|
||||
|
||||
3. **Multiple Users**: Separate user mappings with commas:
|
||||
3. **Multiple Users**: Add multiple user keys:
|
||||
```properties
|
||||
users = "user1:token1,user2:token2"
|
||||
user.user1 = "token1"
|
||||
user.user2 = "token2"
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
//! ```toml
|
||||
//! [PluginConfig.discord-rich-presence-rs]
|
||||
//! clientid = "YOUR_DISCORD_APPLICATION_ID"
|
||||
//! users = "username1:discord_token1,username2:discord_token2"
|
||||
//! "user.username1" = "discord_token1"
|
||||
//! "user.username2" = "discord_token2"
|
||||
//! ```
|
||||
//!
|
||||
//! **WARNING**: This plugin is for demonstration purposes only. Storing Discord tokens
|
||||
@ -47,7 +48,7 @@ nd_pdk::register_websocket_close!(DiscordPlugin);
|
||||
// ============================================================================
|
||||
|
||||
const CLIENT_ID_KEY: &str = "clientid";
|
||||
const USERS_KEY: &str = "users";
|
||||
const USER_KEY_PREFIX: &str = "user.";
|
||||
const PAYLOAD_HEARTBEAT: &str = "heartbeat";
|
||||
const PAYLOAD_CLEAR_ACTIVITY: &str = "clear-activity";
|
||||
|
||||
@ -68,15 +69,14 @@ fn get_config() -> Result<(String, std::collections::HashMap<String, String>), E
|
||||
.filter(|s| !s.is_empty())
|
||||
.ok_or_else(|| Error::msg("missing clientid in configuration"))?;
|
||||
|
||||
let users_config = config::get(USERS_KEY)?
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_default();
|
||||
// Get all user keys with the "user." prefix
|
||||
let user_keys = config::keys(USER_KEY_PREFIX)?;
|
||||
|
||||
let mut users = std::collections::HashMap::new();
|
||||
for user in users_config.split(',') {
|
||||
let parts: Vec<&str> = user.split(':').collect();
|
||||
if parts.len() == 2 {
|
||||
users.insert(parts[0].trim().to_string(), parts[1].trim().to_string());
|
||||
for key in user_keys {
|
||||
let username = key.strip_prefix(USER_KEY_PREFIX).unwrap_or(&key);
|
||||
if let Some(token) = config::get(&key)?.filter(|s| !s.is_empty()) {
|
||||
users.insert(username.to_string(), token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -65,10 +65,12 @@ To work within this model the plugin stores no in-memory state. Connections are
|
||||
|
||||
Configure in the Navidrome UI (Settings → Plugins → discord-rich-presence):
|
||||
|
||||
| Key | Description | Example |
|
||||
|------------|----------------------------------------------------------|--------------------------------|
|
||||
| `clientid` | Your Discord application ID | `123456789012345678` |
|
||||
| `users` | Comma-separated list of `username:token` pairs | `alice:token123,bob:token456` |
|
||||
| Key | Description | Example |
|
||||
|---------------|-------------------------------------------|--------------------------------|
|
||||
| `clientid` | Your Discord application ID | `123456789012345678` |
|
||||
| `user.<name>` | Discord token for the specified user | `user.alice` = `token123` |
|
||||
|
||||
Each user is configured as a separate key with the `user.` prefix.
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
@ -24,8 +24,8 @@ import (
|
||||
|
||||
// Configuration keys
|
||||
const (
|
||||
clientIDKey = "clientid"
|
||||
usersKey = "users"
|
||||
clientIDKey = "clientid"
|
||||
userKeyPrefix = "user."
|
||||
)
|
||||
|
||||
// discordPlugin implements the scrobbler and scheduler interfaces.
|
||||
@ -49,20 +49,27 @@ func getConfig() (clientID string, users map[string]string, err error) {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
cfgUsers, ok := pdk.GetConfig(usersKey)
|
||||
if !ok || cfgUsers == "" {
|
||||
// Get all user keys with the "user." prefix
|
||||
userKeys := host.ConfigKeys(userKeyPrefix)
|
||||
if len(userKeys) == 0 {
|
||||
pdk.Log(pdk.LogWarn, "no users configured")
|
||||
return clientID, nil, nil
|
||||
}
|
||||
|
||||
users = make(map[string]string)
|
||||
for _, user := range strings.Split(cfgUsers, ",") {
|
||||
tuple := strings.Split(user, ":")
|
||||
if len(tuple) != 2 {
|
||||
return clientID, nil, fmt.Errorf("invalid user config: %s", user)
|
||||
for _, key := range userKeys {
|
||||
username := strings.TrimPrefix(key, userKeyPrefix)
|
||||
token, exists := host.ConfigGet(key)
|
||||
if exists && token != "" {
|
||||
users[username] = token
|
||||
}
|
||||
users[strings.TrimSpace(tuple[0])] = strings.TrimSpace(tuple[1])
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
pdk.Log(pdk.LogWarn, "no users configured")
|
||||
return clientID, nil, nil
|
||||
}
|
||||
|
||||
return clientID, users, nil
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,8 @@ var _ = Describe("discordPlugin", func() {
|
||||
pdk.ResetMock()
|
||||
host.CacheMock.ExpectedCalls = nil
|
||||
host.CacheMock.Calls = nil
|
||||
host.ConfigMock.ExpectedCalls = nil
|
||||
host.ConfigMock.Calls = nil
|
||||
host.WebSocketMock.ExpectedCalls = nil
|
||||
host.WebSocketMock.Calls = nil
|
||||
host.SchedulerMock.ExpectedCalls = nil
|
||||
@ -39,7 +41,9 @@ var _ = Describe("discordPlugin", func() {
|
||||
Describe("getConfig", func() {
|
||||
It("returns config values when properly set", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("user1:token1,user2:token2", true)
|
||||
host.ConfigMock.On("Keys", userKeyPrefix).Return([]string{"user.user1", "user.user2"})
|
||||
host.ConfigMock.On("Get", "user.user1").Return("token1", true)
|
||||
host.ConfigMock.On("Get", "user.user2").Return("token2", true)
|
||||
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
|
||||
|
||||
clientID, users, err := getConfig()
|
||||
@ -62,7 +66,7 @@ var _ = Describe("discordPlugin", func() {
|
||||
|
||||
It("returns nil users when users not configured", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("", false)
|
||||
host.ConfigMock.On("Keys", userKeyPrefix).Return([]string{})
|
||||
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
|
||||
|
||||
clientID, users, err := getConfig()
|
||||
@ -70,16 +74,6 @@ var _ = Describe("discordPlugin", func() {
|
||||
Expect(clientID).To(Equal("test-client-id"))
|
||||
Expect(users).To(BeNil())
|
||||
})
|
||||
|
||||
It("returns error for invalid user format", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("invalid-format", true)
|
||||
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
|
||||
|
||||
_, _, err := getConfig()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("invalid user config"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("IsAuthorized", func() {
|
||||
@ -89,7 +83,8 @@ var _ = Describe("discordPlugin", func() {
|
||||
|
||||
It("returns true for authorized user", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("testuser:token123", true)
|
||||
host.ConfigMock.On("Keys", userKeyPrefix).Return([]string{"user.testuser"})
|
||||
host.ConfigMock.On("Get", "user.testuser").Return("token123", true)
|
||||
|
||||
authorized, err := plugin.IsAuthorized(scrobbler.IsAuthorizedRequest{
|
||||
Username: "testuser",
|
||||
@ -100,7 +95,8 @@ var _ = Describe("discordPlugin", func() {
|
||||
|
||||
It("returns false for unauthorized user", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("otheruser:token123", true)
|
||||
host.ConfigMock.On("Keys", userKeyPrefix).Return([]string{"user.otheruser"})
|
||||
host.ConfigMock.On("Get", "user.otheruser").Return("token123", true)
|
||||
|
||||
authorized, err := plugin.IsAuthorized(scrobbler.IsAuthorizedRequest{
|
||||
Username: "testuser",
|
||||
@ -108,16 +104,6 @@ var _ = Describe("discordPlugin", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(authorized).To(BeFalse())
|
||||
})
|
||||
|
||||
It("returns error when config parsing fails", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("invalid-format", true)
|
||||
|
||||
_, err := plugin.IsAuthorized(scrobbler.IsAuthorizedRequest{
|
||||
Username: "testuser",
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("NowPlaying", func() {
|
||||
@ -125,21 +111,10 @@ var _ = Describe("discordPlugin", func() {
|
||||
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
|
||||
})
|
||||
|
||||
It("returns error when config parsing fails", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("invalid-format", true)
|
||||
|
||||
err := plugin.NowPlaying(scrobbler.NowPlayingRequest{
|
||||
Username: "testuser",
|
||||
Track: scrobbler.TrackInfo{Title: "Test Song"},
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("failed to get config"))
|
||||
})
|
||||
|
||||
It("returns not authorized error when user not in config", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("otheruser:token", true)
|
||||
host.ConfigMock.On("Keys", userKeyPrefix).Return([]string{"user.otheruser"})
|
||||
host.ConfigMock.On("Get", "user.otheruser").Return("token", true)
|
||||
|
||||
err := plugin.NowPlaying(scrobbler.NowPlayingRequest{
|
||||
Username: "testuser",
|
||||
@ -151,7 +126,8 @@ var _ = Describe("discordPlugin", func() {
|
||||
|
||||
It("successfully sends now playing update", func() {
|
||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||
pdk.PDKMock.On("GetConfig", usersKey).Return("testuser:test-token", true)
|
||||
host.ConfigMock.On("Keys", userKeyPrefix).Return([]string{"user.testuser"})
|
||||
host.ConfigMock.On("Get", "user.testuser").Return("test-token", true)
|
||||
|
||||
// Connect mocks (isConnected check via heartbeat)
|
||||
host.CacheMock.On("GetInt", "discord.seq.testuser").Return(int64(0), false, errors.New("not found"))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user