diff --git a/config/slskd.example.yml b/config/slskd.example.yml index 9cd9584fd..c73118e57 100644 --- a/config/slskd.example.yml +++ b/config/slskd.example.yml @@ -192,6 +192,7 @@ # password: ~ # description: | # A slskd user. https://github.com/slskd/slskd +# picture: path/to/slsk-profile-picture.jpg # listen_ip_address: 0.0.0.0 # listen_port: 50300 # diagnostic_level: Info diff --git a/docs/config.md b/docs/config.md index 92798196d..c83e26c77 100644 --- a/docs/config.md +++ b/docs/config.md @@ -680,14 +680,19 @@ soulseek: ## Other +Users can configure "profile" information for other users on the network to view, including a description and a photo. Note that Soulseek NS doesn't support .PNG, +so formats .JPG/.JPEG, .GIF, and .BMP are advised. + | Command-Line | Environment Variable | Description | | -------------------- | -------------------------- | --------------------------------------------- | | `--slsk-description` | `SLSKD_SLSK_DESCRIPTION` | The user description for the Soulseek network | +| `--slsk-picture` | `SLSKD_SLSK_PICTURE` | The user picture for the Soulseek network | #### **YAML** ```yaml soulseek: description: A slskd user. https://github.com/slskd/slskd + picture: path/to/slsk-profile-picture.jpg ``` ## Connection Options diff --git a/src/slskd/Application.cs b/src/slskd/Application.cs index e56814424..9179987ea 100644 --- a/src/slskd/Application.cs +++ b/src/slskd/Application.cs @@ -1662,13 +1662,32 @@ private void State_OnChange((State Previous, State Current) state) /// A Task resolving the UserInfo instance. private async Task UserInfoResolver(string username, IPEndPoint endpoint) { + byte[] pictureBytes = null; + + if (!string.IsNullOrWhiteSpace(Options.Soulseek.Picture)) + { + try + { + // note: the Picture setting is validated at startup to ensure it exists and that + // it is readable + pictureBytes = await System.IO.File.ReadAllBytesAsync(Options.Soulseek.Picture); + } + catch (Exception ex) + { + // this isn't a serious enough problem to prevent us from continuing, so we'll just + // log a warning and continue, omitting the picture + Log.Warning("Failed to read Soulseek picture {Picture}: {Message}", Options.Soulseek.Picture, ex.Message); + } + } + if (Users.IsBlacklisted(username, endpoint.Address)) { return new UserInfo( description: Options.Soulseek.Description, uploadSlots: 0, queueLength: int.MaxValue, - hasFreeUploadSlot: false); + hasFreeUploadSlot: false, + picture: pictureBytes); } try @@ -1688,11 +1707,13 @@ private async Task UserInfoResolver(string username, IPEndPoint endpoi // i want to know how many slots they have, which gives me an idea of how fast their // queue moves, and the length of the queue *ahead of me*, meaning how long i'd have to // wait until my first download starts. + // revisited 3 years later: why was it important to leave this comment?? var info = new UserInfo( description: Options.Soulseek.Description, uploadSlots: group.Slots, queueLength: forecastedPosition, - hasFreeUploadSlot: forecastedPosition == 0); + hasFreeUploadSlot: forecastedPosition == 0, + picture: pictureBytes); return info; } diff --git a/src/slskd/Core/Options.cs b/src/slskd/Core/Options.cs index 266d04aa0..cc6624443 100644 --- a/src/slskd/Core/Options.cs +++ b/src/slskd/Core/Options.cs @@ -1531,6 +1531,15 @@ public class SoulseekOptions [Description("user description for the Soulseek network")] public string Description { get; init; } = "A slskd user. https://github.com/slskd/slskd"; + /// + /// Gets the file path for the user's profile picture. + /// + [Argument(default, "slsk-picture")] + [EnvironmentVariable("SLSK_PICTURE")] + [Description("user picture for the Soulseek network")] + [FileExists(FileAccess.Read)] + public string Picture { get; init; } = null; + /// /// Gets the local IP address on which to listen for incoming connections. /// diff --git a/tests/slskd.Tests.Unit/Users/UserServiceTests.cs b/tests/slskd.Tests.Unit/Users/UserServiceTests.cs index 1fd21c325..ca45d5b9d 100644 --- a/tests/slskd.Tests.Unit/Users/UserServiceTests.cs +++ b/tests/slskd.Tests.Unit/Users/UserServiceTests.cs @@ -2,7 +2,6 @@ { using System.Collections.Generic; using AutoFixture.Xunit2; - using Microsoft.EntityFrameworkCore; using Moq; using slskd.Users; using Soulseek; @@ -120,7 +119,7 @@ public void Gives_Lowest_Priority_Group_To_Users_Appearing_In_Multiple_Groups(st } } - private static (UserService governor, Mocks mocks) GetFixture(Options options = null) + private static (UserService service, Mocks mocks) GetFixture(Options options = null) { var mocks = new Mocks(options); var service = new UserService(