From a35c2ad3435afa2071232c85defb453795a6489f Mon Sep 17 00:00:00 2001 From: MsNecromancer Date: Wed, 18 Mar 2026 13:45:57 -0400 Subject: [PATCH 1/3] Replace Imgur with ImgBB since Imgur's API is broken. Swapped out the Imgur uploader for ImgBB. Works the same way but actually works. Remove ImgurClient/ folder and ImgurUploader.cs Add ImgBBUploader.cs using plain HttpClient Remove RestSharp dependency (no longer needed) Rename ImgurClientId setting to ImgBBApiKey Users need a free API key from https://api.imgbb.com --- DiscordBee.cs | 10 +- DiscordBee.csproj | 16 +- DiscordTools/Assets/Uploader/ImgBBUploader.cs | 162 ++++++++++++++++++ DiscordTools/Assets/Uploader/ImgurUploader.cs | 149 ---------------- ImgurClient/ImgurAuthenticator.cs | 15 -- ImgurClient/ImgurClient.cs | 100 ----------- ImgurClient/RateLimitHandler.cs | 51 ------ ImgurClient/Types/ImgurAlbum.cs | 50 ------ ImgurClient/Types/ImgurImage.cs | 54 ------ ImgurClient/Types/ImgurResponse.cs | 14 -- Settings.cs | 18 +- UI/SettingsWindow.Designer.cs | 2 +- UI/SettingsWindow.cs | 12 +- 13 files changed, 181 insertions(+), 472 deletions(-) create mode 100644 DiscordTools/Assets/Uploader/ImgBBUploader.cs delete mode 100644 DiscordTools/Assets/Uploader/ImgurUploader.cs delete mode 100644 ImgurClient/ImgurAuthenticator.cs delete mode 100644 ImgurClient/ImgurClient.cs delete mode 100644 ImgurClient/RateLimitHandler.cs delete mode 100644 ImgurClient/Types/ImgurAlbum.cs delete mode 100644 ImgurClient/Types/ImgurImage.cs delete mode 100644 ImgurClient/Types/ImgurResponse.cs diff --git a/DiscordBee.cs b/DiscordBee.cs index 9da9812..4798814 100644 --- a/DiscordBee.cs +++ b/DiscordBee.cs @@ -30,7 +30,6 @@ public partial class Plugin private SettingsWindow _settingsWindow; private readonly Timer _updateTimer = new Timer(300); private string _imgurAssetCachePath; - private string _imgurAlbum; public Plugin() { @@ -78,15 +77,14 @@ public PluginInfo Initialise(IntPtr apiInterfacePtr) var workingDir = _mbApiInterface.Setting_GetPersistentStoragePath() + _about.Name; var settingsFilePath = $"{workingDir}\\{_about.Name}.settings"; - _imgurAssetCachePath = $"{workingDir}\\{_about.Name}-Imgur.cache"; - _imgurAlbum = $"{workingDir}\\{_about.Name}-Imgur.album"; + _imgurAssetCachePath = $"{workingDir}\\{_about.Name}-ImgBB.cache"; _settings = Settings.GetInstance(settingsFilePath); _settings.SettingChanged += SettingChangedCallback; _discordClient.ArtworkUploadEnabled = _settings.UploadArtwork; _discordClient.DiscordId = _settings.DiscordAppId; - UpdateAssetManager(_imgurAssetCachePath, new ImgurUploader(_imgurAlbum, _settings.ImgurClientId)); + UpdateAssetManager(_imgurAssetCachePath, new ImgBBUploader(_settings.ImgBBApiKey)); ToolStripMenuItem mainMenuItem = (ToolStripMenuItem)_mbApiInterface.MB_AddMenuItem($"mnuTools/{_about.Name}", null, null); mainMenuItem.DropDown.Items.Add("Uploader Health", null, ShowUploaderHealth); @@ -138,9 +136,9 @@ private void SettingChangedCallback(object sender, Settings.SettingChangedEventA { _discordClient.ArtworkUploadEnabled = _settings.UploadArtwork; } - if (e.SettingProperty.Equals("ImgurClientId")) + if (e.SettingProperty.Equals("ImgBBApiKey")) { - UpdateAssetManager(_imgurAssetCachePath, new ImgurUploader(_imgurAlbum, _settings.ImgurClientId)); + UpdateAssetManager(_imgurAssetCachePath, new ImgBBUploader(_settings.ImgBBApiKey)); } } diff --git a/DiscordBee.csproj b/DiscordBee.csproj index 544aaa5..9dce096 100644 --- a/DiscordBee.csproj +++ b/DiscordBee.csproj @@ -96,16 +96,11 @@ - + - - - - - - + @@ -177,12 +172,7 @@ 13.0.3 - - 109.0.1 - - - 109.0.1 - + diff --git a/DiscordTools/Assets/Uploader/ImgBBUploader.cs b/DiscordTools/Assets/Uploader/ImgBBUploader.cs new file mode 100644 index 0000000..2ba7e81 --- /dev/null +++ b/DiscordTools/Assets/Uploader/ImgBBUploader.cs @@ -0,0 +1,162 @@ +namespace MusicBeePlugin.DiscordTools.Assets.Uploader +{ + using Newtonsoft.Json.Linq; + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Net.Http; + using System.Threading.Tasks; + + public class ImgBBUploader : IAssetUploader + { + private const string UploadEndpoint = "https://api.imgbb.com/1/upload"; + + private static readonly HttpClient _httpClient = new HttpClient(); + + private string _apiKey; + private bool _isRateLimited; + private string _rateLimitInfo = ""; + + public ImgBBUploader(string apiKey) + { + _apiKey = apiKey ?? ""; + } + + public void UpdateApiKey(string apiKey) + { + _apiKey = apiKey ?? ""; + _isRateLimited = false; + _rateLimitInfo = ""; + } + + public Task Init() + { + // ImgBB has no album setup step needed - just return true + return Task.FromResult(true); + } + + public bool IsAssetCached(AlbumCoverData assetData) + { + return false; + } + + public async Task UploadAsset(AlbumCoverData assetData) + { + var link = await UploadAsync(assetData.ImageB64, assetData.Hash).ConfigureAwait(false); + return new UploadResult { Hash = assetData.Hash, Link = link }; + } + + public Task DeleteAsset(AlbumCoverData assetData) + { + // ImgBB anonymous uploads cannot be deleted via the API without the delete URL + // stored at upload time - not implemented for now + throw new NotImplementedException(); + } + + public Task> GetAssets() + { + // ImgBB has no album/listing concept for anonymous uploads + return Task.FromResult(new Dictionary()); + } + + public UploaderHealthInfo GetHealth() + { + var health = new UploaderHealthInfo(); + + if (string.IsNullOrWhiteSpace(_apiKey)) + { + health.IsHealthy = false; + health.AddInfo("No ImgBB API key set. Get a free key at https://api.imgbb.com"); + } + else if (_isRateLimited) + { + health.IsHealthy = false; + health.AddInfo(_rateLimitInfo); + } + else + { + health.IsHealthy = true; + health.AddInfo("OK"); + } + + return health; + } + + public void Dispose() + { + // HttpClient is static/shared - nothing to dispose + } + + // ── Internal upload logic ──────────────────────────────────────────────── + + private async Task UploadAsync(string base64ImageData, string name = null) + { + if (string.IsNullOrWhiteSpace(_apiKey)) + { + Debug.WriteLine("ImgBBUploader: No API key configured.", "DiscordBee"); + return null; + } + + try + { + using (var content = new MultipartFormDataContent()) + { + content.Add(new StringContent(StripDataUriPrefix(base64ImageData)), "image"); + + if (!string.IsNullOrWhiteSpace(name)) + { + content.Add(new StringContent(name), "name"); + } + + var requestUri = $"{UploadEndpoint}?key={Uri.EscapeDataString(_apiKey)}"; + var response = await _httpClient.PostAsync(requestUri, content).ConfigureAwait(false); + var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + Debug.WriteLine($"ImgBBUploader: HTTP {(int)response.StatusCode} – {responseBody}", "DiscordBee"); + + // ImgBB returns 429 when rate limited + if ((int)response.StatusCode == 429) + { + _isRateLimited = true; + _rateLimitInfo = "ImgBB rate limit reached. Uploads will resume automatically."; + return null; + } + + if (!response.IsSuccessStatusCode) + { + _isRateLimited = true; + _rateLimitInfo = $"ImgBB upload failed: HTTP {(int)response.StatusCode} {response.ReasonPhrase}"; + return null; + } + + var json = JObject.Parse(responseBody); + var url = json["data"]?["url"]?.ToString(); + + if (string.IsNullOrEmpty(url)) + { + Debug.WriteLine("ImgBBUploader: Response did not contain an image URL.", "DiscordBee"); + return null; + } + + _isRateLimited = false; + _rateLimitInfo = ""; + return url; + } + } + catch (Exception ex) + { + _isRateLimited = true; + _rateLimitInfo = $"ImgBB upload exception: {ex.Message}"; + Debug.WriteLine($"ImgBBUploader: Exception – {ex}", "DiscordBee"); + return null; + } + } + + private static string StripDataUriPrefix(string data) + { + if (data == null) return data; + var idx = data.IndexOf(','); + return idx >= 0 && data.StartsWith("data:") ? data.Substring(idx + 1) : data; + } + } +} diff --git a/DiscordTools/Assets/Uploader/ImgurUploader.cs b/DiscordTools/Assets/Uploader/ImgurUploader.cs deleted file mode 100644 index 4fbfd86..0000000 --- a/DiscordTools/Assets/Uploader/ImgurUploader.cs +++ /dev/null @@ -1,149 +0,0 @@ -namespace MusicBeePlugin.DiscordTools.Assets.Uploader -{ - using MusicBeePlugin.ImgurClient.Types; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - - public class ImgurUploader : IAssetUploader - { - private readonly ImgurClient.ImgurClient _client; - private readonly string _albumSavePath; - private ImgurAlbum _album; - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - - public ImgurUploader(string albumSavePath, string imgurClientId) - { - _albumSavePath = albumSavePath ?? throw new ArgumentNullException(nameof(albumSavePath)); - _client = new ImgurClient.ImgurClient(imgurClientId); - } - - public Task DeleteAsset(AlbumCoverData assetData) - { - throw new System.NotImplementedException(); - } - - public void Dispose() - { - _client.Dispose(); - if (!File.Exists(_albumSavePath) && _album != null) - { - File.WriteAllText(_albumSavePath, JsonConvert.SerializeObject(_album)); - } - } - - public async Task> GetAssets() - { - var ret = new Dictionary(); - if (_album == null) - { - return ret; - } - var images = await _client.GetAlbumImages(_album.Id); - - foreach (var image in images) - { - ret[image.Title] = image.Link; - } - - return ret; - } - - public async Task Init() - { - Debug.WriteLine(" ---> Waiting for semaphore"); - await _semaphore.WaitAsync(); - Debug.WriteLine(" <--- Waiting for semaphore"); - try - { - if (_album != null) - { - return true; - } - Debug.WriteLine(" ---> Creating Album"); - await GetAlbum(); - Debug.WriteLine(" <--- Creating Album"); - } - finally - { - Debug.WriteLine(" ---> Releasing semaphore"); - _semaphore.Release(); - } - return _album != null; - } - - private async Task GetAlbum() - { - ImgurAlbum tmpAlbum = null; - - if (File.Exists(_albumSavePath)) - { - tmpAlbum = JsonConvert.DeserializeObject(File.ReadAllText(_albumSavePath)); - if (string.IsNullOrEmpty(tmpAlbum.DeleteHash)) - { - File.Delete(_albumSavePath); - tmpAlbum = null; - } - else - { - try - { - _ = await _client.GetAlbum(tmpAlbum.Id); - } - catch - { - Debug.WriteLine($"Album does not exist: {tmpAlbum} with id: {tmpAlbum.Id} -> creating new one"); - File.Delete(_albumSavePath); - tmpAlbum = null; - } - } - } - else if (Path.GetDirectoryName(_albumSavePath) != null && !Directory.Exists(Path.GetDirectoryName(_albumSavePath))) - { - Directory.CreateDirectory(Path.GetDirectoryName(_albumSavePath) ?? throw new InvalidOperationException()); - } - - if (tmpAlbum == null) - { - tmpAlbum = await _client.CreateAlbum(); - Debug.WriteLine($"Created album: {tmpAlbum} with deleteHash: {tmpAlbum.DeleteHash}"); - } - - if (tmpAlbum != null) - { - _album = tmpAlbum; - File.WriteAllText(_albumSavePath, JsonConvert.SerializeObject(_album)); - } - } - - public bool IsAssetCached(AlbumCoverData assetData) - { - return false; - } - - public UploaderHealthInfo GetHealth() - { - var health = new UploaderHealthInfo(); - var (status, info) = _client.IsRateLimited(); - - health.IsHealthy = !status; - health.AddInfo(info); - - return health; - } - - public async Task UploadAsset(AlbumCoverData assetData) - { - if (_album == null) - { - return new UploadResult { Hash = assetData.Hash, Link = null }; - } - var uploaded = await _client.UploadImage(assetData.Hash, assetData.ImageB64, _album.DeleteHash); - return new UploadResult { Hash = assetData.Hash, Link = uploaded.Link }; - } - } -} diff --git a/ImgurClient/ImgurAuthenticator.cs b/ImgurClient/ImgurAuthenticator.cs deleted file mode 100644 index 2c020cf..0000000 --- a/ImgurClient/ImgurAuthenticator.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MusicBeePlugin.ImgurClient -{ - using RestSharp; - using RestSharp.Authenticators; - using System.Threading.Tasks; - - internal class ImgurAuthenticator : AuthenticatorBase - { - public ImgurAuthenticator(string token) : base(token) - { - } - - protected override ValueTask GetAuthenticationParameter(string accessToken) => new ValueTask(new HeaderParameter(KnownHeaders.Authorization, $"Client-ID {Token}")); - } -} diff --git a/ImgurClient/ImgurClient.cs b/ImgurClient/ImgurClient.cs deleted file mode 100644 index a2dee4a..0000000 --- a/ImgurClient/ImgurClient.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace MusicBeePlugin.ImgurClient -{ - using MusicBeePlugin.ImgurClient.Types; - using RestSharp; - using RestSharp.Serializers.NewtonsoftJson; - using System; - using System.Net; - using System.Threading; - using System.Threading.Tasks; - - public class ImgurClient : IDisposable - { - public const string ImgurApiUrl = "https://api.imgur.com/3"; - private readonly RestClient _client; - private RateLimitHandler _rateLimitHandler; - - public ImgurClient(string clientId) - { - var options = new RestClientOptions(ImgurApiUrl) - { - ThrowOnAnyError = true, - FollowRedirects = true, - PreAuthenticate = true, - MaxTimeout = 20 * 1000, - //Proxy = new WebProxy("http://localhost:8080"), - ConfigureMessageHandler = orig => - { - _rateLimitHandler = new RateLimitHandler(orig); - return _rateLimitHandler; - }, - Authenticator = new ImgurAuthenticator(clientId) - }; - _client = new RestClient(options, configureSerialization: s => s.UseNewtonsoftJson()); - } - - public async Task CreateAlbum() - { - var request = new RestRequest("album"); - request.AddParameter("title", "DiscordBee"); - request.Method = Method.Post; - try - { - var response = await _client.PostAsync>(request); - return GetResponseData(response); - } - catch - { - return null; - } - } - - public async Task UploadImage(string title, string dataB64, string albumHash = null) - { - var request = new RestRequest("upload"); - - request.AddParameter("image", dataB64); - if (!string.IsNullOrEmpty(albumHash)) - { - request.AddParameter("album", albumHash); - } - request.AddParameter("type", "base64"); - request.AddParameter("title", title); - request.AddParameter("name", "cover.png"); - var response = await _client.PostAsync>(request); - return GetResponseData(response); - } - - public async Task GetAlbum(string albumHash) - { - var response = await _client.GetJsonAsync>("album/{albumHash}", new { albumHash }); - return GetResponseData(response); - } - - public async Task GetAlbumImages(string albumHash) - { - var response = await _client.GetJsonAsync>("album/{albumHash}/images", new { albumHash }); - return GetResponseData(response); - } - - private static T GetResponseData(ImgurResponse response) - { - if (response != null) - { - return response.Data; - } - return default; - } - - public (bool status, string info) IsRateLimited() - { - return (_rateLimitHandler?.IsRateLimited == true, _rateLimitHandler?.RateLimitInfo); - } - - public void Dispose() - { - _client?.Dispose(); - GC.SuppressFinalize(this); - } - } -} diff --git a/ImgurClient/RateLimitHandler.cs b/ImgurClient/RateLimitHandler.cs deleted file mode 100644 index 8b68964..0000000 --- a/ImgurClient/RateLimitHandler.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace MusicBeePlugin.ImgurClient -{ - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Threading; - using System.Threading.Tasks; - - public class RateLimitHandler : DelegatingHandler - { - private readonly Dictionary _rateLimitHeader = new Dictionary() { - { "X-RateLimit-UserRemaining", "IP is rate limited" }, - { "X-RateLimit-ClientRemaining", "Imgur app is rate limited" }, - { "X-Post-Rate-Limit-Remaining", "Post requests are rate limited" } - }; - private string rateLimitInfo; - // 20 because one upload uses 10 tokens - public const int MinTokensLeft = 20; - public bool IsRateLimited { get; private set; } - public string RateLimitInfo => IsRateLimited ? rateLimitInfo : ""; - - public RateLimitHandler(HttpMessageHandler innerHandler) : base(innerHandler) - { - IsRateLimited = false; - } - - protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var response = await base.SendAsync(request, cancellationToken); - - if (response != null) - { - foreach (var header in _rateLimitHeader.Keys) - { - if (response.Headers.Contains(header)) - { - int tokensLeft = int.Parse(response.Headers.GetValues(header).First()); - IsRateLimited = tokensLeft <= MinTokensLeft; - if (IsRateLimited) - { - rateLimitInfo = _rateLimitHeader[header]; - break; - } - } - } - } - - return response; - } - } -} diff --git a/ImgurClient/Types/ImgurAlbum.cs b/ImgurClient/Types/ImgurAlbum.cs deleted file mode 100644 index 274f6c1..0000000 --- a/ImgurClient/Types/ImgurAlbum.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace MusicBeePlugin.ImgurClient.Types -{ - using System.Text.Json.Serialization; - - public class ImgurAlbum - { - [JsonPropertyName("id")] - public string Id { get; set; } - [JsonPropertyName("title")] - public string Title { get; set; } - [JsonPropertyName("description")] - public string Description { get; set; } - [JsonPropertyName("datetime")] - public int Datetime { get; set; } - [JsonPropertyName("cover")] - public string Cover { get; set; } - [JsonPropertyName("cover_width")] - public int CoverWidth { get; set; } - [JsonPropertyName("cover_height")] - public int CoverHeight { get; set; } - [JsonPropertyName("account_url")] - public string AccountUrl { get; set; } - [JsonPropertyName("account_id")] - public int AccountId { get; set; } - [JsonPropertyName("privacy")] - public string Privacy { get; set; } - [JsonPropertyName("layout")] - public string Layout { get; set; } - [JsonPropertyName("views")] - public long Views { get; set; } - [JsonPropertyName("link")] - public string Link { get; set; } - [JsonPropertyName("favorite")] - public bool Favorite { get; set; } - [JsonPropertyName("nsfw")] - public bool Nsfw { get; set; } - [JsonPropertyName("section")] - public string Section { get; set; } - [JsonPropertyName("order")] - public int Order { get; set; } - [JsonPropertyName("deletehash")] - public string DeleteHash { get; set; } - [JsonPropertyName("images_count")] - public long ImagesCount { get; set; } - [JsonPropertyName("images")] - public ImgurImage[] Images { get; set; } - [JsonPropertyName("in_gallery")] - public bool InGallery { get; set; } - } -} diff --git a/ImgurClient/Types/ImgurImage.cs b/ImgurClient/Types/ImgurImage.cs deleted file mode 100644 index a01f155..0000000 --- a/ImgurClient/Types/ImgurImage.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace MusicBeePlugin.ImgurClient.Types -{ - using System.Text.Json.Serialization; - - public class ImgurImage - { - [JsonPropertyName("id")] - public string Id { get; set; } - [JsonPropertyName("title")] - public string Title { get; set; } - [JsonPropertyName("description")] - public string Description { get; set; } - [JsonPropertyName("datetime")] - public int Datetime { get; set; } - [JsonPropertyName("type")] - public string Type { get; set; } - [JsonPropertyName("animated")] - public bool Animated { get; set; } - [JsonPropertyName("width")] - public int Width { get; set; } - [JsonPropertyName("height")] - public int Height { get; set; } - [JsonPropertyName("size")] - public int Size { get; set; } - [JsonPropertyName("views")] - public long Views { get; set; } - [JsonPropertyName("bandwidth")] - public long Bandwidth { get; set; } - [JsonPropertyName("deletehash")] - public string DeleteHash { get; set; } - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("section")] - public string Section { get; set; } - [JsonPropertyName("link")] - public string Link { get; set; } - [JsonPropertyName("gifv")] - public string Gifv { get; set; } - [JsonPropertyName("mp4")] - public string Mp4 { get; set; } - [JsonPropertyName("mp4_size")] - public long Mp4Size { get; set; } - [JsonPropertyName("looping")] - public bool Looping { get; set; } - [JsonPropertyName("favorite")] - public bool Favorite { get; set; } - [JsonPropertyName("nsfw ")] - public bool Nsfw { get; set; } - [JsonPropertyName("vote")] - public string Vote { get; set; } - [JsonPropertyName("in_gallery")] - public bool InGallery { get; set; } - } -} diff --git a/ImgurClient/Types/ImgurResponse.cs b/ImgurClient/Types/ImgurResponse.cs deleted file mode 100644 index 658cad4..0000000 --- a/ImgurClient/Types/ImgurResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MusicBeePlugin.ImgurClient.Types -{ - using System.Text.Json.Serialization; - - public class ImgurResponse - { - [JsonPropertyName("data")] - public T Data { get; set; } - [JsonPropertyName("success")] - public bool Success { get; set; } - [JsonPropertyName("status")] - public int Status { get; set; } - } -} diff --git a/Settings.cs b/Settings.cs index 82bc38f..9be8d48 100644 --- a/Settings.cs +++ b/Settings.cs @@ -26,7 +26,7 @@ public class Settings {"ButtonUrl", "https://www.last.fm/music/[Artist]/_/[TrackTitle]"}, {"DiscordAppId", "409394531948298250"}, // prod //{"DiscordAppId", "408977077799354379"}, // dev - {"ImgurClientId", "09bef4c058080cd"}, + {"ImgBBApiKey", ""}, }; public event EventHandler SettingChanged; @@ -107,20 +107,12 @@ public string DiscordAppId } } - [DataMember] private string _imgurClientId; + [DataMember] private string _imgBBApiKey; - public string ImgurClientId + public string ImgBBApiKey { - get => _imgurClientId ?? defaults["ImgurClientId"]; - set - { - if (value?.Equals(defaults["ImgurClientId"]) == true) - { - _imgurClientId = null; - return; - } - SetIfChanged("_imgurClientId", value); - } + get => _imgBBApiKey ?? defaults["ImgBBApiKey"]; + set => SetIfChanged("_imgBBApiKey", value); } [DataMember] private bool? _updatePresenceWhenStopped; diff --git a/UI/SettingsWindow.Designer.cs b/UI/SettingsWindow.Designer.cs index af52651..a39332c 100644 --- a/UI/SettingsWindow.Designer.cs +++ b/UI/SettingsWindow.Designer.cs @@ -325,7 +325,7 @@ private void InitializeComponent() this.labelImgurClientId.Name = "labelImgurClientId"; this.labelImgurClientId.Size = new System.Drawing.Size(88, 15); this.labelImgurClientId.TabIndex = 19; - this.labelImgurClientId.Text = "Imgur Client ID"; + this.labelImgurClientId.Text = "ImgBB API Key:"; // // textBoxImgurClientId // diff --git a/UI/SettingsWindow.cs b/UI/SettingsWindow.cs index 2784dc4..8420d69 100644 --- a/UI/SettingsWindow.cs +++ b/UI/SettingsWindow.cs @@ -58,7 +58,7 @@ private void UpdateValues(Settings settings) textBoxSmallImage.Text = settings.SmallImageText; textBoxSeparator.Text = settings.Separator; textBoxDiscordAppId.Text = settings.DiscordAppId.Equals(Settings.defaults["DiscordAppId"]) ? "" : settings.DiscordAppId; - textBoxImgurClientId.Text = settings.ImgurClientId.Equals(Settings.defaults["ImgurClientId"]) ? "" : settings.ImgurClientId; + textBoxImgurClientId.Text = settings.ImgBBApiKey.Equals(Settings.defaults["ImgBBApiKey"]) ? "" : settings.ImgBBApiKey; checkBoxPresenceUpdate.Checked = settings.UpdatePresenceWhenStopped; checkBoxShowTime.Checked = settings.ShowTime; checkBoxShowRemainingTime.Checked = settings.ShowRemainingTime; @@ -102,7 +102,7 @@ private void buttonSaveClose_Click(object sender, EventArgs e) _settings.SmallImageText = textBoxSmallImage.Text; _settings.Separator = textBoxSeparator.Text; _settings.DiscordAppId = string.IsNullOrWhiteSpace(textBoxDiscordAppId.Text) ? null : textBoxDiscordAppId.Text; - _settings.ImgurClientId = string.IsNullOrWhiteSpace(textBoxImgurClientId.Text) ? null : textBoxImgurClientId.Text; + _settings.ImgBBApiKey = string.IsNullOrWhiteSpace(textBoxImgurClientId.Text) ? null : textBoxImgurClientId.Text; _settings.UpdatePresenceWhenStopped = checkBoxPresenceUpdate.Checked; _settings.ShowTime = checkBoxShowTime.Checked; _settings.ShowRemainingTime = checkBoxShowRemainingTime.Checked; @@ -156,10 +156,10 @@ bool validateDiscordId() return false; } - bool validateImgurClientId() + bool validateImgBBApiKey() { - if (textBoxImgurClientId.Text.Length != Settings.defaults["ImgurClientId"].Length - || textBoxImgurClientId.Text.Equals(Settings.defaults["ImgurClientId"])) + // ImgBB API keys are 32 hex characters + if (textBoxImgurClientId.Text.Length != 32) { textBoxImgurClientId.BackColor = Color.PaleVioletRed; return false; @@ -168,7 +168,7 @@ bool validateImgurClientId() return true; } - if (checkBoxArtworkUpload.Checked && textBoxImgurClientId.Text.Length > 0 && !validateImgurClientId()) + if (checkBoxArtworkUpload.Checked && textBoxImgurClientId.Text.Length > 0 && !validateImgBBApiKey()) { return false; } From a7837d893da8427ae7e48274af86584652da32f7 Mon Sep 17 00:00:00 2001 From: MsNecro Date: Wed, 18 Mar 2026 13:50:46 -0400 Subject: [PATCH 2/3] Change album cover upload from Imgur to ImgBB Updated album cover upload feature to use ImgBB instead of Imgur, and added note about requiring a free API key. --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d70a687..a70fa8f 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,13 @@ If you are unhappy with your changes, you can always restore the defaults and sa ### Album Covers -If the cover upload feature is enabled your album covers will be uploaded to an anonymous Imgur album. This album is unique to every instance of the plugin, so nothing is shared between users of the plugin. -You don't need an Imgur account for this feature to work. +If the cover upload feature is enabled your album covers will be uploaded to ImgBB. You will need a free API key from https://api.imgbb.com to use this feature. -When enabling this feature it can take a few minutes until the uploads are working (suspectedly because of a delay in album creation). This only happens when the album is created the first time. +You can check the status of the uploader in MusicBee under `Tools -> DiscordBee -> Uploader Health`. -Imgur has rate limits regarding uploads, those are handled by the plugin. There is a global rate limit per application as well as per user. That means even if you did not upload any covers today, it might still not work for you. You can check the status of the uploader in MusicBee under `tools -> DiscordBee -> Uploader Health`. +![Tools Menu](Screenshots/tools_menu.png) + +If the uploader becomes unhealthy only cached covers will work until it becomes healthy again. ![Tools Menu](Screenshots/tools_menu.png) From e8636d16517569dc248ebbe3d0749184a672054b Mon Sep 17 00:00:00 2001 From: MsNecro Date: Wed, 18 Mar 2026 13:51:18 -0400 Subject: [PATCH 3/3] Remove duplicate content from README Removed duplicate Tools Menu image and related text. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index a70fa8f..5a15a69 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,6 @@ You can check the status of the uploader in MusicBee under `Tools -> DiscordBee If the uploader becomes unhealthy only cached covers will work until it becomes healthy again. -![Tools Menu](Screenshots/tools_menu.png) - -If the uploader becomes unhealthy only cached covers will work until it becomes healthy again. - ### Buttons If you configure a custom button and would like to test it, you cannot click on the button yourself in the Discord client, this is per design and a limitation by Discord.