mirror of
https://github.com/TeamOctolings/Octobot.git
synced 2025-04-20 00:43:36 +03:00
Add xmldocs.
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
This commit is contained in:
parent
1cd309e498
commit
e883e143eb
15 changed files with 271 additions and 54 deletions
|
@ -5,6 +5,9 @@
|
||||||
|
|
||||||
namespace Boyfriend;
|
namespace Boyfriend;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains all colors used in embeds.
|
||||||
|
/// </summary>
|
||||||
public static class ColorsList {
|
public static class ColorsList {
|
||||||
public static readonly Color Default = Color.Gray;
|
public static readonly Color Default = Color.Gray;
|
||||||
public static readonly Color Red = Color.Firebrick;
|
public static readonly Color Red = Color.Firebrick;
|
||||||
|
|
|
@ -19,6 +19,9 @@ using Remora.Results;
|
||||||
|
|
||||||
namespace Boyfriend.Commands;
|
namespace Boyfriend.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles commands related to ban management: /ban and unban.
|
||||||
|
/// </summary>
|
||||||
public class BanCommandGroup : CommandGroup {
|
public class BanCommandGroup : CommandGroup {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
private readonly ICommandContext _context;
|
private readonly ICommandContext _context;
|
||||||
|
@ -41,12 +44,26 @@ public class BanCommandGroup : CommandGroup {
|
||||||
_utility = utility;
|
_utility = utility;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("ban")]
|
/// <summary>
|
||||||
|
/// A slash command that bans a Discord user with the specified reason.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The user to ban.</param>
|
||||||
|
/// <param name="reason">
|
||||||
|
/// The reason for this ban. Must be encoded with <see cref="WebUtility.UrlEncode" /> when passed to
|
||||||
|
/// <see cref="IDiscordRestGuildAPI.CreateGuildBanAsync" />.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A feedback sending result which may or may not have succeeded. A successful result does not mean that the user
|
||||||
|
/// was banned and vice-versa.
|
||||||
|
/// </returns>
|
||||||
|
/// <seealso cref="UnBanUserAsync" />
|
||||||
|
[Command("ban", "бан")]
|
||||||
[RequireContext(ChannelContext.Guild)]
|
[RequireContext(ChannelContext.Guild)]
|
||||||
[RequireDiscordPermission(DiscordPermission.BanMembers)]
|
[RequireDiscordPermission(DiscordPermission.BanMembers)]
|
||||||
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
|
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
|
||||||
[Description("банит пидора")]
|
[Description("банит пидора")]
|
||||||
public async Task<Result> BanUserAsync([Description("Юзер, кого банить")] IUser target, string reason) {
|
public async Task<Result> BanUserAsync([Description("Юзер, кого банить")] IUser target, string reason) {
|
||||||
|
// Data checks
|
||||||
if (!_context.TryGetGuildID(out var guildId))
|
if (!_context.TryGetGuildID(out var guildId))
|
||||||
return Result.FromError(new ArgumentNullError(nameof(guildId)));
|
return Result.FromError(new ArgumentNullError(nameof(guildId)));
|
||||||
if (!_context.TryGetUserID(out var userId))
|
if (!_context.TryGetUserID(out var userId))
|
||||||
|
@ -54,6 +71,7 @@ public class BanCommandGroup : CommandGroup {
|
||||||
if (!_context.TryGetChannelID(out var channelId))
|
if (!_context.TryGetChannelID(out var channelId))
|
||||||
return Result.FromError(new ArgumentNullError(nameof(channelId)));
|
return Result.FromError(new ArgumentNullError(nameof(channelId)));
|
||||||
|
|
||||||
|
// The current user's avatar is used when sending error messages
|
||||||
var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken);
|
var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken);
|
||||||
if (!currentUserResult.IsDefined(out var currentUser))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
@ -110,6 +128,7 @@ public class BanCommandGroup : CommandGroup {
|
||||||
return Result.FromError(logEmbed);
|
return Result.FromError(logEmbed);
|
||||||
|
|
||||||
var builtArray = new[] { logBuilt };
|
var builtArray = new[] { logBuilt };
|
||||||
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PrivateFeedbackChannel != channelId.Value)
|
if (cfg.PrivateFeedbackChannel != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||||
|
@ -127,12 +146,26 @@ public class BanCommandGroup : CommandGroup {
|
||||||
return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken);
|
return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A slash command that unbans a Discord user with the specified reason.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The user to unban.</param>
|
||||||
|
/// <param name="reason">
|
||||||
|
/// The reason for this unban. Must be encoded with <see cref="WebUtility.UrlEncode" /> when passed to
|
||||||
|
/// <see cref="IDiscordRestGuildAPI.RemoveGuildBanAsync" />.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A feedback sending result which may or may not have succeeded. A successful result does not mean that the user
|
||||||
|
/// was unbanned and vice-versa.
|
||||||
|
/// </returns>
|
||||||
|
/// <seealso cref="BanUserAsync" />
|
||||||
[Command("unban")]
|
[Command("unban")]
|
||||||
[RequireContext(ChannelContext.Guild)]
|
[RequireContext(ChannelContext.Guild)]
|
||||||
[RequireDiscordPermission(DiscordPermission.BanMembers)]
|
[RequireDiscordPermission(DiscordPermission.BanMembers)]
|
||||||
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
|
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
|
||||||
[Description("разбанит пидора")]
|
[Description("разбанит пидора")]
|
||||||
public async Task<Result> UnBanUserAsync([Description("Юзер, кого разбанить")] IUser target, string reason) {
|
public async Task<Result> UnBanUserAsync([Description("Юзер, кого разбанить")] IUser target, string reason) {
|
||||||
|
// Data checks
|
||||||
if (!_context.TryGetGuildID(out var guildId))
|
if (!_context.TryGetGuildID(out var guildId))
|
||||||
return Result.FromError(new ArgumentNullError(nameof(guildId)));
|
return Result.FromError(new ArgumentNullError(nameof(guildId)));
|
||||||
if (!_context.TryGetUserID(out var userId))
|
if (!_context.TryGetUserID(out var userId))
|
||||||
|
@ -140,6 +173,7 @@ public class BanCommandGroup : CommandGroup {
|
||||||
if (!_context.TryGetChannelID(out var channelId))
|
if (!_context.TryGetChannelID(out var channelId))
|
||||||
return Result.FromError(new ArgumentNullError(nameof(channelId)));
|
return Result.FromError(new ArgumentNullError(nameof(channelId)));
|
||||||
|
|
||||||
|
// The current user's avatar is used when sending error messages
|
||||||
var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken);
|
var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken);
|
||||||
if (!currentUserResult.IsDefined(out var currentUser))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
@ -158,8 +192,7 @@ public class BanCommandGroup : CommandGroup {
|
||||||
return (Result)await _feedbackService.SendContextualEmbedAsync(alreadyBuilt, ct: CancellationToken);
|
return (Result)await _feedbackService.SendContextualEmbedAsync(alreadyBuilt, ct: CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<Embed> responseEmbed;
|
// Needed to get the tag and avatar
|
||||||
|
|
||||||
var userResult = await _userApi.GetUserAsync(userId.Value, CancellationToken);
|
var userResult = await _userApi.GetUserAsync(userId.Value, CancellationToken);
|
||||||
if (!userResult.IsDefined(out var user))
|
if (!userResult.IsDefined(out var user))
|
||||||
return Result.FromError(userResult);
|
return Result.FromError(userResult);
|
||||||
|
@ -170,7 +203,7 @@ public class BanCommandGroup : CommandGroup {
|
||||||
if (!unbanResult.IsSuccess)
|
if (!unbanResult.IsSuccess)
|
||||||
return Result.FromError(unbanResult.Error);
|
return Result.FromError(unbanResult.Error);
|
||||||
|
|
||||||
responseEmbed = new EmbedBuilder().WithSmallTitle(
|
var responseEmbed = new EmbedBuilder().WithSmallTitle(
|
||||||
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
||||||
.WithColour(ColorsList.Green).Build();
|
.WithColour(ColorsList.Green).Build();
|
||||||
|
|
||||||
|
@ -187,6 +220,8 @@ public class BanCommandGroup : CommandGroup {
|
||||||
return Result.FromError(logEmbed);
|
return Result.FromError(logEmbed);
|
||||||
|
|
||||||
var builtArray = new[] { logBuilt };
|
var builtArray = new[] { logBuilt };
|
||||||
|
|
||||||
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PrivateFeedbackChannel != channelId.Value)
|
if (cfg.PrivateFeedbackChannel != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||||
|
|
|
@ -7,6 +7,9 @@ using Remora.Results;
|
||||||
|
|
||||||
namespace Boyfriend.Commands;
|
namespace Boyfriend.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles error logging for slash command groups.
|
||||||
|
/// </summary>
|
||||||
public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent {
|
public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent {
|
||||||
private readonly ILogger<ErrorLoggingPostExecutionEvent> _logger;
|
private readonly ILogger<ErrorLoggingPostExecutionEvent> _logger;
|
||||||
|
|
||||||
|
@ -14,6 +17,14 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent {
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs a warning using the injected <see cref="ILogger" /> if the <paramref name="commandResult" /> has not
|
||||||
|
/// succeeded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context of the slash command. Unused.</param>
|
||||||
|
/// <param name="commandResult">The result whose success is checked.</param>
|
||||||
|
/// <param name="ct">The cancellation token for this operation. Unused.</param>
|
||||||
|
/// <returns>A result which has succeeded.</returns>
|
||||||
public Task<Result> AfterExecutionAsync(
|
public Task<Result> AfterExecutionAsync(
|
||||||
ICommandContext context, IResult commandResult, CancellationToken ct = default) {
|
ICommandContext context, IResult commandResult, CancellationToken ct = default) {
|
||||||
if (!commandResult.IsSuccess)
|
if (!commandResult.IsSuccess)
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
|
||||||
namespace Boyfriend.Data;
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores per-guild settings that can be set by a member
|
||||||
|
/// with <see cref="DiscordPermission.ManageGuild" /> using the /settings command
|
||||||
|
/// </summary>
|
||||||
public class GuildConfiguration {
|
public class GuildConfiguration {
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a scheduled event notification receiver.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Used to selectively mention guild members when a scheduled event has started or is about to start.
|
||||||
|
/// </remarks>
|
||||||
public enum NotificationReceiver {
|
public enum NotificationReceiver {
|
||||||
Interested,
|
Interested,
|
||||||
Role
|
Role
|
||||||
|
@ -15,21 +26,61 @@ public class GuildConfiguration {
|
||||||
};
|
};
|
||||||
|
|
||||||
public string Language { get; set; } = "en";
|
public string Language { get; set; } = "en";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls what message should be sent in <see cref="PublicFeedbackChannel" /> when a new member joins the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>No message will be sent if set to "off", "disable" or "disabled".</item>
|
||||||
|
/// <item><see cref="Messages.DefaultWelcomeMessage" /> will be sent if set to "default" or "reset"</item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="GuildMemberAddResponder" />
|
||||||
public string WelcomeMessage { get; set; } = "default";
|
public string WelcomeMessage { get; set; } = "default";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls whether or not the <see cref="Messages.Ready" /> message should be sent
|
||||||
|
/// in <see cref="PrivateFeedbackChannel" /> on startup.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="GuildCreateResponder" />
|
||||||
public bool ReceiveStartupMessages { get; set; }
|
public bool ReceiveStartupMessages { get; set; }
|
||||||
|
|
||||||
public bool RemoveRolesOnMute { get; set; }
|
public bool RemoveRolesOnMute { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls whether or not a guild member's roles are returned if he/she leaves and then joins back.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Roles will not be returned if the member left the guild because of /ban or /kick.</remarks>
|
||||||
public bool ReturnRolesOnRejoin { get; set; }
|
public bool ReturnRolesOnRejoin { get; set; }
|
||||||
|
|
||||||
public bool AutoStartEvents { get; set; }
|
public bool AutoStartEvents { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls what channel should all public messages be sent to.
|
||||||
|
/// </summary>
|
||||||
public ulong PublicFeedbackChannel { get; set; }
|
public ulong PublicFeedbackChannel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls what channel should all private, moderator-only messages be sent to.
|
||||||
|
/// </summary>
|
||||||
public ulong PrivateFeedbackChannel { get; set; }
|
public ulong PrivateFeedbackChannel { get; set; }
|
||||||
|
|
||||||
public ulong EventNotificationChannel { get; set; }
|
public ulong EventNotificationChannel { get; set; }
|
||||||
public ulong StarterRole { get; set; }
|
public ulong DefaultRole { get; set; }
|
||||||
public ulong MuteRole { get; set; }
|
public ulong MuteRole { get; set; }
|
||||||
public ulong EventNotificationRole { get; set; }
|
public ulong EventNotificationRole { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls what guild members should be mentioned when a scheduled event has started or is about to start.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="NotificationReceiver" />
|
||||||
public List<NotificationReceiver> EventStartedReceivers { get; set; }
|
public List<NotificationReceiver> EventStartedReceivers { get; set; }
|
||||||
= new() { NotificationReceiver.Interested, NotificationReceiver.Role };
|
= new() { NotificationReceiver.Interested, NotificationReceiver.Role };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the amount of time before a scheduled event to send a reminder in <see cref="EventNotificationChannel" />.
|
||||||
|
/// </summary>
|
||||||
public TimeSpan EventEarlyNotificationOffset { get; set; } = TimeSpan.Zero;
|
public TimeSpan EventEarlyNotificationOffset { get; set; } = TimeSpan.Zero;
|
||||||
|
|
||||||
public CultureInfo Culture => CultureInfoCache[Language];
|
public CultureInfo Culture => CultureInfoCache[Language];
|
||||||
|
|
|
@ -2,6 +2,10 @@ using System.Globalization;
|
||||||
|
|
||||||
namespace Boyfriend.Data;
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores information about a guild. This information is not accessible via the Discord API.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This information is stored on disk as a JSON file.</remarks>
|
||||||
public class GuildData {
|
public class GuildData {
|
||||||
public readonly GuildConfiguration Configuration;
|
public readonly GuildConfiguration Configuration;
|
||||||
public readonly string ConfigurationPath;
|
public readonly string ConfigurationPath;
|
||||||
|
|
|
@ -2,6 +2,10 @@ using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
|
||||||
namespace Boyfriend.Data;
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores information about scheduled events. This information is not provided by the Discord API.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This information is stored on disk as a JSON file.</remarks>
|
||||||
public class ScheduledEventData {
|
public class ScheduledEventData {
|
||||||
public DateTimeOffset? ActualStartTime;
|
public DateTimeOffset? ActualStartTime;
|
||||||
public GuildScheduledEventStatus Status;
|
public GuildScheduledEventStatus Status;
|
||||||
|
|
|
@ -21,6 +21,10 @@ using Remora.Results;
|
||||||
|
|
||||||
namespace Boyfriend;
|
namespace Boyfriend;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles sending a <see cref="Messages.Ready" /> message to a guild that has just initialized if that guild
|
||||||
|
/// has <see cref="GuildConfiguration.ReceiveStartupMessages" /> enabled
|
||||||
|
/// </summary>
|
||||||
public class GuildCreateResponder : IResponder<IGuildCreate> {
|
public class GuildCreateResponder : IResponder<IGuildCreate> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
private readonly GuildDataService _dataService;
|
private readonly GuildDataService _dataService;
|
||||||
|
@ -37,7 +41,7 @@ public class GuildCreateResponder : IResponder<IGuildCreate> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IGuildCreate gatewayEvent, CancellationToken ct = default) {
|
public async Task<Result> RespondAsync(IGuildCreate gatewayEvent, CancellationToken ct = default) {
|
||||||
if (!gatewayEvent.Guild.IsT0) return Result.FromSuccess(); // is IAvailableGuild
|
if (!gatewayEvent.Guild.IsT0) return Result.FromSuccess(); // Guild isn't IAvailableGuild
|
||||||
|
|
||||||
var guild = gatewayEvent.Guild.AsT0;
|
var guild = gatewayEvent.Guild.AsT0;
|
||||||
_logger.LogInformation("Joined guild \"{Name}\"", guild.Name);
|
_logger.LogInformation("Joined guild \"{Name}\"", guild.Name);
|
||||||
|
@ -68,6 +72,10 @@ public class GuildCreateResponder : IResponder<IGuildCreate> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles logging the contents of a deleted message and the user who deleted the message
|
||||||
|
/// to a guild's <see cref="GuildConfiguration.PrivateFeedbackChannel" /> if one is set.
|
||||||
|
/// </summary>
|
||||||
public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
||||||
private readonly IDiscordRestAuditLogAPI _auditLogApi;
|
private readonly IDiscordRestAuditLogAPI _auditLogApi;
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
|
@ -129,6 +137,10 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles logging the difference between an edited message's old and new content
|
||||||
|
/// to a guild's <see cref="GuildConfiguration.PrivateFeedbackChannel" /> if one is set.
|
||||||
|
/// </summary>
|
||||||
public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
||||||
private readonly CacheService _cacheService;
|
private readonly CacheService _cacheService;
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
|
@ -153,7 +165,7 @@ public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
||||||
if (!gatewayEvent.Content.IsDefined(out var newContent))
|
if (!gatewayEvent.Content.IsDefined(out var newContent))
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
if (!gatewayEvent.EditedTimestamp.IsDefined(out var timestamp))
|
if (!gatewayEvent.EditedTimestamp.IsDefined(out var timestamp))
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess(); // The message wasn't actually edited
|
||||||
|
|
||||||
if (!gatewayEvent.ChannelID.IsDefined(out var channelId))
|
if (!gatewayEvent.ChannelID.IsDefined(out var channelId))
|
||||||
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.ChannelID)));
|
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.ChannelID)));
|
||||||
|
@ -199,6 +211,10 @@ public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles sending a guild's <see cref="GuildConfiguration.WelcomeMessage" /> if one is set.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="GuildConfiguration.WelcomeMessage" />
|
||||||
public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
private readonly GuildDataService _dataService;
|
private readonly GuildDataService _dataService;
|
||||||
|
@ -243,6 +259,12 @@ public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles sending a notification, mentioning the <see cref="GuildConfiguration.EventNotificationRole" /> if one is
|
||||||
|
/// set,
|
||||||
|
/// when a scheduled event is created
|
||||||
|
/// in a guild's <see cref="GuildConfiguration.EventNotificationChannel" /> if one is set.
|
||||||
|
/// </summary>
|
||||||
public class GuildScheduledEventCreateResponder : IResponder<IGuildScheduledEventCreate> {
|
public class GuildScheduledEventCreateResponder : IResponder<IGuildScheduledEventCreate> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
private readonly GuildDataService _dataService;
|
private readonly GuildDataService _dataService;
|
||||||
|
@ -339,6 +361,12 @@ public class GuildScheduledEventCreateResponder : IResponder<IGuildScheduledEven
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles sending a notification, mentioning the <see cref="GuildConfiguration.EventNotificationRole" /> if one is
|
||||||
|
/// set,
|
||||||
|
/// when a scheduled event has started or completed
|
||||||
|
/// in a guild's <see cref="GuildConfiguration.EventNotificationChannel" /> if one is set.
|
||||||
|
/// </summary>
|
||||||
public class GuildScheduledEventUpdateResponder : IResponder<IGuildScheduledEventUpdate> {
|
public class GuildScheduledEventUpdateResponder : IResponder<IGuildScheduledEventUpdate> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
private readonly GuildDataService _dataService;
|
private readonly GuildDataService _dataService;
|
||||||
|
@ -353,10 +381,16 @@ public class GuildScheduledEventUpdateResponder : IResponder<IGuildScheduledEven
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IGuildScheduledEventUpdate gatewayEvent, CancellationToken ct = default) {
|
public async Task<Result> RespondAsync(IGuildScheduledEventUpdate gatewayEvent, CancellationToken ct = default) {
|
||||||
var guildData = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
var guildData = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
||||||
if (gatewayEvent.Status == guildData.ScheduledEvents[gatewayEvent.ID.Value].Status
|
if (guildData.Configuration.EventNotificationChannel is 0)
|
||||||
|| guildData.Configuration.EventNotificationChannel is 0) return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
|
if (!guildData.ScheduledEvents.TryGetValue(gatewayEvent.ID.Value, out var data)) {
|
||||||
|
guildData.ScheduledEvents.Add(gatewayEvent.ID.Value, new ScheduledEventData(gatewayEvent.Status));
|
||||||
|
} else {
|
||||||
|
if (gatewayEvent.Status == data.Status)
|
||||||
|
return Result.FromSuccess();
|
||||||
|
|
||||||
guildData.ScheduledEvents[gatewayEvent.ID.Value].Status = gatewayEvent.Status;
|
guildData.ScheduledEvents[gatewayEvent.ID.Value].Status = gatewayEvent.Status;
|
||||||
|
}
|
||||||
|
|
||||||
var embed = new EmbedBuilder();
|
var embed = new EmbedBuilder();
|
||||||
StringBuilder? content = null;
|
StringBuilder? content = null;
|
||||||
|
@ -442,11 +476,15 @@ public class GuildScheduledEventUpdateResponder : IResponder<IGuildScheduledEven
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GuildScheduledEventResponder : IResponder<IGuildScheduledEventDelete> {
|
/// <summary>
|
||||||
|
/// Handles sending a notification when a scheduled event has been cancelled
|
||||||
|
/// in a guild's <see cref="GuildConfiguration.EventNotificationChannel" /> if one is set.
|
||||||
|
/// </summary>
|
||||||
|
public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEventDelete> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
private readonly GuildDataService _dataService;
|
private readonly GuildDataService _dataService;
|
||||||
|
|
||||||
public GuildScheduledEventResponder(IDiscordRestChannelAPI channelApi, GuildDataService dataService) {
|
public GuildScheduledEventDeleteResponder(IDiscordRestChannelAPI channelApi, GuildDataService dataService) {
|
||||||
_channelApi = channelApi;
|
_channelApi = channelApi;
|
||||||
_dataService = dataService;
|
_dataService = dataService;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,12 @@ using Remora.Rest.Core;
|
||||||
namespace Boyfriend;
|
namespace Boyfriend;
|
||||||
|
|
||||||
public static class Extensions {
|
public static class Extensions {
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a footer with the <paramref name="user" />'s avatar and tag (username#0000).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder to add the footer to.</param>
|
||||||
|
/// <param name="user">The user whose tag and avatar to add.</param>
|
||||||
|
/// <returns>The builder with the added footer.</returns>
|
||||||
public static EmbedBuilder WithUserFooter(this EmbedBuilder builder, IUser user) {
|
public static EmbedBuilder WithUserFooter(this EmbedBuilder builder, IUser user) {
|
||||||
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
||||||
var avatarUrl = avatarUrlResult.IsSuccess
|
var avatarUrl = avatarUrlResult.IsSuccess
|
||||||
|
@ -19,6 +25,12 @@ public static class Extensions {
|
||||||
return builder.WithFooter(new EmbedFooter(user.GetTag(), avatarUrl));
|
return builder.WithFooter(new EmbedFooter(user.GetTag(), avatarUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a footer representing that an action was performed by a <paramref name="user" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder to add the footer to.</param>
|
||||||
|
/// <param name="user">The user that performed the action whose tag and avatar to use.</param>
|
||||||
|
/// <returns>The builder with the added footer.</returns>
|
||||||
public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user) {
|
public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user) {
|
||||||
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
||||||
var avatarUrl = avatarUrlResult.IsSuccess
|
var avatarUrl = avatarUrlResult.IsSuccess
|
||||||
|
@ -29,6 +41,14 @@ public static class Extensions {
|
||||||
new EmbedFooter($"{Messages.IssuedBy}:\n{user.GetTag()}", avatarUrl));
|
new EmbedFooter($"{Messages.IssuedBy}:\n{user.GetTag()}", avatarUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a title using the author field, making it smaller than using the title field.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder to add the small title to.</param>
|
||||||
|
/// <param name="text">The text of the small title.</param>
|
||||||
|
/// <param name="avatarSource">The user whose avatar to use in the small title.</param>
|
||||||
|
/// <param name="url">The URL that will be opened if a user clicks on the small title.</param>
|
||||||
|
/// <returns>The builder with the added small title in the author field.</returns>
|
||||||
public static EmbedBuilder WithSmallTitle(
|
public static EmbedBuilder WithSmallTitle(
|
||||||
this EmbedBuilder builder, string text, IUser? avatarSource = null, string? url = default) {
|
this EmbedBuilder builder, string text, IUser? avatarSource = null, string? url = default) {
|
||||||
Uri? avatarUrl = null;
|
Uri? avatarUrl = null;
|
||||||
|
@ -44,6 +64,12 @@ public static class Extensions {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a footer representing that the action was performed in the <paramref name="guild" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder to add the footer to.</param>
|
||||||
|
/// <param name="guild">The guild whose name and icon to use.</param>
|
||||||
|
/// <returns>The builder with the added footer.</returns>
|
||||||
public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild) {
|
public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild) {
|
||||||
var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
|
var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
|
||||||
var iconUrl = iconUrlResult.IsSuccess
|
var iconUrl = iconUrlResult.IsSuccess
|
||||||
|
@ -53,6 +79,13 @@ public static class Extensions {
|
||||||
return builder.WithFooter(new EmbedFooter(guild.Name, iconUrl));
|
return builder.WithFooter(new EmbedFooter(guild.Name, iconUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a scheduled event's cover image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder to add the image to.</param>
|
||||||
|
/// <param name="eventId">The ID of the scheduled event whose image to use.</param>
|
||||||
|
/// <param name="imageHashOptional">The Optional containing the image hash.</param>
|
||||||
|
/// <returns>The builder with the added cover image.</returns>
|
||||||
public static EmbedBuilder WithEventCover(
|
public static EmbedBuilder WithEventCover(
|
||||||
this EmbedBuilder builder, Snowflake eventId, Optional<IImageHash?> imageHashOptional) {
|
this EmbedBuilder builder, Snowflake eventId, Optional<IImageHash?> imageHashOptional) {
|
||||||
if (!imageHashOptional.IsDefined(out var imageHash)) return builder;
|
if (!imageHashOptional.IsDefined(out var imageHash)) return builder;
|
||||||
|
@ -61,6 +94,12 @@ public static class Extensions {
|
||||||
return iconUrlResult.IsDefined(out var iconUrl) ? builder.WithImageUrl(iconUrl.AbsoluteUri) : builder;
|
return iconUrlResult.IsDefined(out var iconUrl) ? builder.WithImageUrl(iconUrl.AbsoluteUri) : builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sanitizes a string for use in <see cref="Markdown.BlockCode(string)" /> by inserting zero-width spaces in between
|
||||||
|
/// symbols used to format the string with block code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s">The string to sanitize.</param>
|
||||||
|
/// <returns>The sanitized string that can be safely used in <see cref="Markdown.BlockCode(string)" />.</returns>
|
||||||
public static string SanitizeForBlockCode(this string s) {
|
public static string SanitizeForBlockCode(this string s) {
|
||||||
return s.Replace("```", "```");
|
return s.Replace("```", "```");
|
||||||
}
|
}
|
||||||
|
@ -82,7 +121,6 @@ public static class Extensions {
|
||||||
return $"{user.Username}#{user.Discriminator:0000}";
|
return $"{user.Username}#{user.Discriminator:0000}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Snowflake ToDiscordSnowflake(this ulong id) {
|
public static Snowflake ToDiscordSnowflake(this ulong id) {
|
||||||
return DiscordSnowflake.New(id);
|
return DiscordSnowflake.New(id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ using Remora.Results;
|
||||||
|
|
||||||
namespace Boyfriend;
|
namespace Boyfriend;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles responding to various interactions.
|
||||||
|
/// </summary>
|
||||||
public class InteractionResponders : InteractionGroup {
|
public class InteractionResponders : InteractionGroup {
|
||||||
private readonly FeedbackService _feedbackService;
|
private readonly FeedbackService _feedbackService;
|
||||||
|
|
||||||
|
@ -16,6 +19,11 @@ public class InteractionResponders : InteractionGroup {
|
||||||
_feedbackService = feedbackService;
|
_feedbackService = feedbackService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A button that will output an ephemeral embed containing the information about a scheduled event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">The ID of the guild and scheduled event, encoded as "guildId:eventId".</param>
|
||||||
|
/// <returns>A feedback sending result which may or may not have succeeded.</returns>
|
||||||
[Button("scheduled-event-details")]
|
[Button("scheduled-event-details")]
|
||||||
public async Task<Result> OnStatefulButtonClicked(string? state = null) {
|
public async Task<Result> OnStatefulButtonClicked(string? state = null) {
|
||||||
if (state is null) return Result.FromError(new ArgumentNullError(nameof(state)));
|
if (state is null) return Result.FromError(new ArgumentNullError(nameof(state)));
|
||||||
|
|
4
Messages.Designer.cs
generated
4
Messages.Designer.cs
generated
|
@ -705,9 +705,9 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string SettingsStarterRole {
|
internal static string SettingsDefaultRole {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("SettingsStarterRole", resourceCulture);
|
return ResourceManager.GetString("SettingsDefaultRole", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -435,8 +435,8 @@
|
||||||
<data name="UserNotFound" xml:space="preserve">
|
<data name="UserNotFound" xml:space="preserve">
|
||||||
<value>I could not find this user in any guild I'm a member of! Check if the ID is correct and that the user was on this server no longer than 30 days ago</value>
|
<value>I could not find this user in any guild I'm a member of! Check if the ID is correct and that the user was on this server no longer than 30 days ago</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsStarterRole" xml:space="preserve">
|
<data name="SettingsDefaultRole" xml:space="preserve">
|
||||||
<value>Starter role</value>
|
<value>Default role</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandDescriptionRemind" xml:space="preserve">
|
<data name="CommandDescriptionRemind" xml:space="preserve">
|
||||||
<value>Adds a reminder</value>
|
<value>Adds a reminder</value>
|
||||||
|
|
|
@ -435,8 +435,8 @@
|
||||||
<data name="UserNotFound" xml:space="preserve">
|
<data name="UserNotFound" xml:space="preserve">
|
||||||
<value>Я не смог найти этого пользователя ни в одном из серверов, в которых я есть. Проверь правильность ID и нахождение пользователя на этом сервере максимум 30 дней назад</value>
|
<value>Я не смог найти этого пользователя ни в одном из серверов, в которых я есть. Проверь правильность ID и нахождение пользователя на этом сервере максимум 30 дней назад</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsStarterRole" xml:space="preserve">
|
<data name="SettingsDefaultRole" xml:space="preserve">
|
||||||
<value>Начальная роль</value>
|
<value>Общая роль</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandDescriptionRemind" xml:space="preserve">
|
<data name="CommandDescriptionRemind" xml:space="preserve">
|
||||||
<value>Добавляет напоминание</value>
|
<value>Добавляет напоминание</value>
|
||||||
|
|
|
@ -435,8 +435,8 @@
|
||||||
<data name="UserNotFound" xml:space="preserve">
|
<data name="UserNotFound" xml:space="preserve">
|
||||||
<value>у нас такого шизоида нету, проверь, валиден ли ID уважаемого (я забываю о шизоидах если они ливнули минимум месяц назад)</value>
|
<value>у нас такого шизоида нету, проверь, валиден ли ID уважаемого (я забываю о шизоидах если они ливнули минимум месяц назад)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsStarterRole" xml:space="preserve">
|
<data name="SettingsDefaultRole" xml:space="preserve">
|
||||||
<value>базовое звание</value>
|
<value>дефолтное звание</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandDescriptionRemind" xml:space="preserve">
|
<data name="CommandDescriptionRemind" xml:space="preserve">
|
||||||
<value>крафтит напоминалку</value>
|
<value>крафтит напоминалку</value>
|
||||||
|
|
|
@ -5,6 +5,9 @@ using Remora.Rest.Core;
|
||||||
|
|
||||||
namespace Boyfriend.Services.Data;
|
namespace Boyfriend.Services.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles saving, loading, initializing and providing <see cref="GuildData" />.
|
||||||
|
/// </summary>
|
||||||
public class GuildDataService : IHostedService {
|
public class GuildDataService : IHostedService {
|
||||||
private readonly Dictionary<Snowflake, GuildData> _datas = new();
|
private readonly Dictionary<Snowflake, GuildData> _datas = new();
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,10 @@ using Remora.Results;
|
||||||
|
|
||||||
namespace Boyfriend.Services;
|
namespace Boyfriend.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides utility methods that cannot be transformed to extension methods because they require usage
|
||||||
|
/// of some Discord APIs.
|
||||||
|
/// </summary>
|
||||||
public class UtilityService : IHostedService {
|
public class UtilityService : IHostedService {
|
||||||
private readonly IDiscordRestGuildAPI _guildApi;
|
private readonly IDiscordRestGuildAPI _guildApi;
|
||||||
private readonly IDiscordRestUserAPI _userApi;
|
private readonly IDiscordRestUserAPI _userApi;
|
||||||
|
@ -22,6 +26,24 @@ public class UtilityService : IHostedService {
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether or not a member can interact with another member
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">The ID of the guild in which an operation is being performed.</param>
|
||||||
|
/// <param name="interacterId">The executor of the operation.</param>
|
||||||
|
/// <param name="targetId">The target of the operation.</param>
|
||||||
|
/// <param name="action">The operation.</param>
|
||||||
|
/// <param name="ct">The cancellation token for this operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>A result which has succeeded with a null string if the member can interact with the target.</item>
|
||||||
|
/// <item>
|
||||||
|
/// A result which has succeeded with a non-null string containing the error message if the member cannot
|
||||||
|
/// interact with the target.
|
||||||
|
/// </item>
|
||||||
|
/// <item>A result which has failed if an error occurred during the execution of this method.</item>
|
||||||
|
/// </list>
|
||||||
|
/// </returns>
|
||||||
public async Task<Result<string?>> CheckInteractionsAsync(
|
public async Task<Result<string?>> CheckInteractionsAsync(
|
||||||
Snowflake guildId, Snowflake interacterId, Snowflake targetId, string action, CancellationToken ct = default) {
|
Snowflake guildId, Snowflake interacterId, Snowflake targetId, string action, CancellationToken ct = default) {
|
||||||
if (interacterId == targetId)
|
if (interacterId == targetId)
|
||||||
|
|
Loading…
Add table
Reference in a new issue