diff --git a/CHANGELOG.md b/CHANGELOG.md index 226b262..ea27952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ *a,c,f,r,o* +## [6.0.13] - 23.03.2025 + +### Added +- Added `.linkfix <old> <new>` command + - If bot sees a message with the old link, it will reply to the message with a fixed (new) link + - ex: `.linkfix twitter.com vxtwitter.com` +- Added `.roleicon role <icon_url / server_emoji>` command to set the icon of a role +- Added a captcha option for `.fish` + +### Fixed +- fixed youtube stream notifications in case invalid channel was provided +- `.lcha` (live channel) will now let you override an existing channel template even if you're at the limit + +### Removed +- removed `.xpglb` as it is no longer used + ## [6.0.12] - 20.03.2025 ### Fixed diff --git a/src/EllieBot/EllieBot.csproj b/src/EllieBot/EllieBot.csproj index 1af72b5..ed3b6dc 100644 --- a/src/EllieBot/EllieBot.csproj +++ b/src/EllieBot/EllieBot.csproj @@ -4,7 +4,7 @@ <Nullable>enable</Nullable> <ImplicitUsings>true</ImplicitUsings> <SatelliteResourceLanguages>en</SatelliteResourceLanguages> - <Version>6.0.12</Version> + <Version>6.0.13</Version> <!-- Output/build --> <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory> diff --git a/src/EllieBot/Modules/Administration/Role/RoleCommands.cs b/src/EllieBot/Modules/Administration/Role/RoleCommands.cs index b6b0eb0..0706166 100644 --- a/src/EllieBot/Modules/Administration/Role/RoleCommands.cs +++ b/src/EllieBot/Modules/Administration/Role/RoleCommands.cs @@ -48,9 +48,9 @@ public partial class Administration }); await Response() - .Confirm(strs.setrole(Format.Bold(roleToAdd.Name), - Format.Bold(targetUser.ToString()))) - .SendAsync(); + .Confirm(strs.setrole(Format.Bold(roleToAdd.Name), + Format.Bold(targetUser.ToString()))) + .SendAsync(); } catch (Exception ex) { @@ -73,9 +73,9 @@ public partial class Administration { await targetUser.RemoveRoleAsync(roleToRemove); await Response() - .Confirm(strs.remrole(Format.Bold(roleToRemove.Name), - Format.Bold(targetUser.ToString()))) - .SendAsync(); + .Confirm(strs.remrole(Format.Bold(roleToRemove.Name), + Format.Bold(targetUser.ToString()))) + .SendAsync(); } catch { @@ -226,8 +226,8 @@ public partial class Administration if (!await CheckRoleHierarchy(role)) { await Response() - .Error(strs.hierarchy) - .SendAsync(); + .Error(strs.hierarchy) + .SendAsync(); return; } @@ -236,10 +236,83 @@ public partial class Administration await Response() - .Confirm(strs.temp_role_added(user.Mention, - Format.Bold(role.Name), - TimestampTag.FromDateTime(DateTime.UtcNow.Add(timespan.Time), TimestampTagStyles.Relative))) - .SendAsync(); + .Confirm(strs.temp_role_added(user.Mention, + Format.Bold(role.Name), + TimestampTag.FromDateTime(DateTime.UtcNow.Add(timespan.Time), TimestampTagStyles.Relative))) + .SendAsync(); + } + + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPerm.ManageRoles)] + [BotPerm(GuildPerm.ManageRoles)] + public Task RoleIcon(IRole role, Emote emote) + => RoleIcon(role, emote.Url); + + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPerm.ManageRoles)] + [BotPerm(GuildPerm.ManageRoles)] + public async Task RoleIcon(IRole role, [Leftover] string iconUrl) + { + if (!await CheckRoleHierarchy(role)) + return; + + if (string.IsNullOrWhiteSpace(iconUrl)) + { + await Response().Error(strs.userrole_icon_invalid).SendAsync(); + return; + } + + // Validate the URL format + if (!Uri.TryCreate(iconUrl, UriKind.Absolute, out var uri)) + { + await Response().Error(strs.userrole_icon_invalid).SendAsync(); + return; + } + + // Download the image + using var httpClient = new HttpClient(); + using var response = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + + // Check if the response is successful + if (!response.IsSuccessStatusCode) + { + await Response().Error(strs.userrole_icon_fail).SendAsync(); + return; + } + + // Check content type - must be image/png or image/jpeg + var contentType = response.Content.Headers.ContentType?.MediaType?.ToLower(); + if (contentType != "image/png" + && contentType != "image/jpeg" + && contentType != "image/webp") + { + await Response().Error(strs.userrole_icon_fail).SendAsync(); + return; + } + + // Check file size - Discord limit is 256KB + var contentLength = response.Content.Headers.ContentLength; + if (contentLength is > 256 * 1024) + { + await Response().Error(strs.userrole_icon_fail).SendAsync(); + return; + } + + // Save the image to a memory stream + await using var stream = await response.Content.ReadAsStreamAsync(); + await using var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream); + memoryStream.Position = 0; + + // Create Discord image from stream + using var discordImage = new Image(memoryStream); + + // Upload the image to Discord + await role.ModifyAsync(r => r.Icon = discordImage); + + await Response().Confirm(strs.userrole_icon_success(Format.Bold(role.Name))).SendAsync(); } } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Utility/LinkFixer/LinkFixerService.cs b/src/EllieBot/Modules/Utility/LinkFixer/LinkFixerService.cs index cf8ba82..3aed904 100644 --- a/src/EllieBot/Modules/Utility/LinkFixer/LinkFixerService.cs +++ b/src/EllieBot/Modules/Utility/LinkFixer/LinkFixerService.cs @@ -30,8 +30,8 @@ public partial class LinkFixerService(DbService db) : IReadyExecutor, IExecNoCom public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg) { - if(guild is null) - return; + if (guild is null) + return; var guildId = guild.Id; if (!_guildLinkFixes.TryGetValue(guildId, out var guildDict)) @@ -52,7 +52,7 @@ public partial class LinkFixerService(DbService db) : IReadyExecutor, IExecNoCom if (string.IsNullOrWhiteSpace(domain)) continue; - if(!guildDict.TryGetValue(domain, out var newDomain)) + if (!guildDict.TryGetValue(domain, out var newDomain)) continue; var newUrl = match.Groups["prefix"].Value + newDomain + match.Groups["suffix"].Value; @@ -73,10 +73,10 @@ public partial class LinkFixerService(DbService db) : IReadyExecutor, IExecNoCom public async Task<bool> AddLinkFixAsync(ulong guildId, string oldDomain, string newDomain) { oldDomain = oldDomain.ToLowerInvariant(); - + var guildDict = _guildLinkFixes.GetOrAdd(guildId, _ => new ConcurrentDictionary<string, string>()); guildDict[oldDomain] = newDomain; - + await using var uow = db.GetDbContext(); await uow.GetTable<LinkFix>() .InsertOrUpdateAsync(() => new LinkFix @@ -107,7 +107,7 @@ public partial class LinkFixerService(DbService db) : IReadyExecutor, IExecNoCom public async Task<bool> RemoveLinkFixAsync(ulong guildId, string oldDomain) { oldDomain = oldDomain.ToLowerInvariant(); - + if (!_guildLinkFixes.TryGetValue(guildId, out var guildDict) || !guildDict.TryRemove(oldDomain, out _)) return false; diff --git a/src/EllieBot/Modules/Utility/UserRole/UserRoleService.cs b/src/EllieBot/Modules/Utility/UserRole/UserRoleService.cs index d500cb0..8718b08 100644 --- a/src/EllieBot/Modules/Utility/UserRole/UserRoleService.cs +++ b/src/EllieBot/Modules/Utility/UserRole/UserRoleService.cs @@ -154,7 +154,7 @@ public sealed class UserRoleService : IUserRoleService, IEService memoryStream.Position = 0; // Create Discord image from stream - var discordImage = new Image(memoryStream); + using var discordImage = new Image(memoryStream); // Upload the image to Discord var discordSuccess = await _discordRoleManager.ModifyRoleAsync( diff --git a/src/EllieBot/data/commandlist.json b/src/EllieBot/data/commandlist.json index c1d01ca..a023e3e 100644 --- a/src/EllieBot/data/commandlist.json +++ b/src/EllieBot/data/commandlist.json @@ -1606,6 +1606,21 @@ "Administrator Server Permission" ] }, + { + "Aliases": [ + ".roleicon" + ], + "Description": "Changes the icon of a role.", + "Usage": [ + ".roleicon @Role :server_emoji_here:" + ], + "Submodule": "RoleCommands", + "Module": "Administration", + "Options": null, + "Requirements": [ + "ManageRoles Server Permission" + ] + }, { "Aliases": [ ".iam" @@ -7384,6 +7399,37 @@ "ManageChannels Channel Permission" ] }, + { + "Aliases": [ + ".linkfix", + ".lfix" + ], + "Description": "Configures automatic link fixing from one site to another.\nWhen a user posts a link containing the old domain, the bot will automatically fix it to use the new domain.\nProvide no second domain to disable link fixing.", + "Usage": [ + ".linkfix twitter.com vxtwitter.com", + ".linkfix x.com" + ], + "Submodule": "LinkFixerCommands", + "Module": "Utility", + "Options": null, + "Requirements": [ + "ManageMessages Server Permission" + ] + }, + { + "Aliases": [ + ".linkfixlist", + ".lfixlist" + ], + "Description": "Lists all configured link fixes for the server.", + "Usage": [ + ".linkfixlist" + ], + "Submodule": "LinkFixerCommands", + "Module": "Utility", + "Options": null, + "Requirements": [] + }, { "Aliases": [ ".livechadd", @@ -8218,7 +8264,7 @@ ], "Description": "Changes the icon of your assigned role.", "Usage": [ - ".userroleicon @Role :thumbsup:" + ".userroleicon @Role :server_emoji_here:" ], "Submodule": "UserRoleCommands", "Module": "Utility", diff --git a/src/EllieBot/data/fish.yml b/src/EllieBot/data/fish.yml index 9bc1c7f..e07fee1 100644 --- a/src/EllieBot/data/fish.yml +++ b/src/EllieBot/data/fish.yml @@ -21,24 +21,24 @@ chance: fish: - id: 0 name: Bass - weather: - spot: - time: + weather: + spot: + time: chance: 100 stars: 4 fluff: Very common. - condition: + condition: image: https://cdn.nadeko.bot/fish/bass.png emoji: <:bass:1328520376892002386> trash: - id: 1002 name: Plastic Bag - weather: - spot: - time: + weather: + spot: + time: chance: 50 stars: 4 fluff: Trophy of your contribution to the environment. - condition: + condition: image: https://cdn.nadeko.bot/fish/plasticbag.png - emoji: <:plasticbag:1328520895454515211> \ No newline at end of file + emoji: <:plasticbag:1328520895454515211> diff --git a/src/EllieBot/strings/aliases.yml b/src/EllieBot/strings/aliases.yml index 112c63c..1948ae4 100644 --- a/src/EllieBot/strings/aliases.yml +++ b/src/EllieBot/strings/aliases.yml @@ -165,6 +165,8 @@ deleterole: rolecolor: - rolecolor - roleclr +roleicon: + - roleicon ban: - ban - b diff --git a/src/EllieBot/strings/commands/commands.en-US.yml b/src/EllieBot/strings/commands/commands.en-US.yml index 9eb73a1..24fee76 100644 --- a/src/EllieBot/strings/commands/commands.en-US.yml +++ b/src/EllieBot/strings/commands/commands.en-US.yml @@ -599,6 +599,20 @@ rolehoist: params: - role: desc: "The role that determines the visibility of the sidebar." +roleicon: + desc: |- + Changes the icon of a role. + ex: + - '@Role :server_emoji_here:' + params: + - role: + desc: 'The role to change the icon of.' + imageUrl: + desc: 'The image url to be used as a new icon for the role.' + - role: + desc: 'The role to change the icon of.' + serverEmoji: + desc: 'The server emoji to be used as a new icon for the role.' createrole: desc: Creates a role with a given name. ex: @@ -5054,7 +5068,7 @@ userroleicon: desc: |- Changes the icon of your assigned role. ex: - - '@Role :thumbsup:' + - '@Role :server_emoji_here:' params: - role: desc: 'The assigned role to change the icon of.'