diff --git a/.editorconfig b/.editorconfig
index ff9c068..adbec5a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -42,7 +42,7 @@ csharp_space_between_square_brackets = false
csharp_style_expression_bodied_accessors = false:warning
csharp_style_expression_bodied_constructors = false:warning
csharp_style_expression_bodied_methods = false:warning
-csharp_style_expression_bodied_properties = false:warning
+csharp_style_expression_bodied_properties = true:warning
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_prefer_utf8_string_literals = true:warning
csharp_style_var_elsewhere = true:warning
diff --git a/Octobot.csproj b/Octobot.csproj
index ab76400..bdfb46a 100644
--- a/Octobot.csproj
+++ b/Octobot.csproj
@@ -17,10 +17,12 @@
en
A general-purpose Discord bot for moderation written in C#
docs/octobot.ico
+ false
+
diff --git a/locale/Messages.resx b/locale/Messages.resx
index 49103c8..09bb4db 100644
--- a/locale/Messages.resx
+++ b/locale/Messages.resx
@@ -117,13 +117,13 @@
{0}, welcome to {1}
-
+
Veemo!
-
+
Woomy!
-
+
Ngyes!
@@ -585,6 +585,12 @@
Report an issue
+
+ See you soon, {0}!
+
+
+ Leave message
+
Time specified incorrectly!
@@ -657,9 +663,18 @@
Example of a valid input: `1h30m`
+
+ Version: {0}
+
Welcome messages channel
+
+ Can't report an issue in the development version
+
+
+ Open Octobot's Wiki
+
{0} received a warning
diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx
index 3933a1a..b37ea64 100644
--- a/locale/Messages.ru.resx
+++ b/locale/Messages.ru.resx
@@ -117,13 +117,13 @@
{0}, добро пожаловать на сервер {1}
-
+
Виимо!
-
+
Вууми!
-
+
Нгьес!
@@ -585,6 +585,12 @@
Сообщить о проблеме
+
+ До скорой встречи, {0}!
+
+
+ Сообщение о выходе
+
Неправильно указано время!
@@ -657,9 +663,18 @@
Пример правильного ввода: `1ч30м`
+
+ Версия: {0}
+
Канал для приветствий
+
+ Нельзя сообщить о проблеме в версии под разработкой
+
+
+ Открыть Octobot's Wiki
+
{0} получил предупреждение
diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx
index b108287..050dc6d 100644
--- a/locale/Messages.tt-ru.resx
+++ b/locale/Messages.tt-ru.resx
@@ -117,13 +117,13 @@
{0}, добро пожаловать на сервер {1}
-
+
вииимо!
-
+
вуууми!
-
+
нгьес!
@@ -585,6 +585,12 @@
зарепортить баг
+
+ ну, мы потеряли {0}
+
+
+ до свидания (типо настройка)
+
ты там правильно напиши таймспан
@@ -657,9 +663,18 @@
правильно пишут так: `1h30m`
+
+ {0}
+
канал куда говорить здравствуйте
+
+ вот иди сам и почини что сломал
+
+
+ вики Octobot (жмак)
+
{0} схлопотал варн
diff --git a/src/Attributes/StaticCallersOnlyAttribute.cs b/src/Attributes/StaticCallersOnlyAttribute.cs
new file mode 100644
index 0000000..e8787bf
--- /dev/null
+++ b/src/Attributes/StaticCallersOnlyAttribute.cs
@@ -0,0 +1,8 @@
+namespace Octobot.Attributes;
+
+///
+/// Any property marked with should only be accessed by static methods.
+/// Such properties may be used to provide dependencies where it is not possible to acquire them through normal means.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class StaticCallersOnlyAttribute : Attribute;
diff --git a/src/BuildInfo.cs b/src/BuildInfo.cs
new file mode 100644
index 0000000..fc3a089
--- /dev/null
+++ b/src/BuildInfo.cs
@@ -0,0 +1,18 @@
+namespace Octobot;
+
+public static class BuildInfo
+{
+ public static string RepositoryUrl => ThisAssembly.Git.RepositoryUrl;
+
+ public static string IssuesUrl => $"{RepositoryUrl}/issues";
+
+ public static string WikiUrl => $"{RepositoryUrl}/wiki";
+
+ private static string Commit => ThisAssembly.Git.Commit;
+
+ private static string Branch => ThisAssembly.Git.Branch;
+
+ public static bool IsDirty => ThisAssembly.Git.IsDirty;
+
+ public static string Version => IsDirty ? $"{Branch}-{Commit}-dirty" : $"{Branch}-{Commit}";
+}
diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs
index e978ec9..027e7f8 100644
--- a/src/Commands/AboutCommandGroup.cs
+++ b/src/Commands/AboutCommandGroup.cs
@@ -73,7 +73,7 @@ public class AboutCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var cfg = await _guildData.GetSettings(guildId, CancellationToken);
@@ -101,26 +101,37 @@ public class AboutCommandGroup : CommandGroup
.WithDescription(builder.ToString())
.WithColour(ColorsList.Cyan)
.WithImageUrl("https://i.ibb.co/fS6wZhh/octobot-banner.png")
+ .WithFooter(string.Format(Messages.Version, BuildInfo.Version))
.Build();
var repositoryButton = new ButtonComponent(
ButtonComponentStyle.Link,
Messages.ButtonOpenRepository,
new PartialEmoji(Name: "🌐"),
- URL: Octobot.RepositoryUrl
+ URL: BuildInfo.RepositoryUrl
+ );
+
+ var wikiButton = new ButtonComponent(
+ ButtonComponentStyle.Link,
+ Messages.ButtonOpenWiki,
+ new PartialEmoji(Name: "📖"),
+ URL: BuildInfo.WikiUrl
);
var issuesButton = new ButtonComponent(
ButtonComponentStyle.Link,
- Messages.ButtonReportIssue,
+ BuildInfo.IsDirty
+ ? Messages.ButtonDirty
+ : Messages.ButtonReportIssue,
new PartialEmoji(Name: "⚠️"),
- URL: Octobot.IssuesUrl
+ URL: BuildInfo.IssuesUrl,
+ IsDisabled: BuildInfo.IsDirty
);
return await _feedback.SendContextualEmbedResultAsync(embed,
new FeedbackMessageOptions(MessageComponents: new[]
{
- new ActionRowComponent(new[] { repositoryButton, issuesButton })
+ new ActionRowComponent(new[] { repositoryButton, wikiButton, issuesButton })
}), ct);
}
}
diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs
index 6c8b004..0c0e5f4 100644
--- a/src/Commands/BanCommandGroup.cs
+++ b/src/Commands/BanCommandGroup.cs
@@ -88,19 +88,19 @@ public class BanCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken);
if (!guildResult.IsDefined(out var guild))
{
- return Result.FromError(guildResult);
+ return ResultExtensions.FromError(guildResult);
}
var data = await _guildData.GetData(guild.ID, CancellationToken);
@@ -144,7 +144,7 @@ public class BanCommandGroup : CommandGroup
= await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Ban", ct);
if (!interactionResult.IsSuccess)
{
- return Result.FromError(interactionResult);
+ return ResultExtensions.FromError(interactionResult);
}
if (interactionResult.Entity is not null)
@@ -181,17 +181,19 @@ public class BanCommandGroup : CommandGroup
await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct);
}
+ var memberData = data.GetOrCreateMemberData(target.ID);
+ memberData.BannedUntil
+ = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue;
+
var banResult = await _guildApi.CreateGuildBanAsync(
guild.ID, target.ID, reason: $"({executor.GetTag()}) {reason}".EncodeHeader(),
ct: ct);
if (!banResult.IsSuccess)
{
- return Result.FromError(banResult.Error);
+ memberData.BannedUntil = null;
+ return ResultExtensions.FromError(banResult);
}
- var memberData = data.GetOrCreateMemberData(target.ID);
- memberData.BannedUntil
- = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue;
memberData.Roles.Clear();
var embed = new EmbedBuilder().WithSmallTitle(
@@ -240,14 +242,14 @@ public class BanCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
// Needed to get the tag and avatar
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -274,7 +276,7 @@ public class BanCommandGroup : CommandGroup
ct);
if (!unbanResult.IsSuccess)
{
- return Result.FromError(unbanResult.Error);
+ return ResultExtensions.FromError(unbanResult);
}
data.GetOrCreateMemberData(target.ID).BannedUntil = null;
diff --git a/src/Commands/ClearCommandGroup.cs b/src/Commands/ClearCommandGroup.cs
index 395810f..70afede 100644
--- a/src/Commands/ClearCommandGroup.cs
+++ b/src/Commands/ClearCommandGroup.cs
@@ -75,20 +75,20 @@ public class ClearCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var messagesResult = await _channelApi.GetChannelMessagesAsync(
channelId, limit: amount + 1, ct: CancellationToken);
if (!messagesResult.IsDefined(out var messages))
{
- return Result.FromError(messagesResult);
+ return ResultExtensions.FromError(messagesResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -133,7 +133,7 @@ public class ClearCommandGroup : CommandGroup
channelId, idList, executor.GetTag().EncodeHeader(), ct);
if (!deleteResult.IsSuccess)
{
- return Result.FromError(deleteResult.Error);
+ return ResultExtensions.FromError(deleteResult);
}
_utility.LogAction(
diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs
index 87cfc84..5fa2ea8 100644
--- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs
+++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs
@@ -20,8 +20,8 @@ namespace Octobot.Commands.Events;
[UsedImplicitly]
public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent
{
- private readonly ILogger _logger;
private readonly IFeedbackService _feedback;
+ private readonly ILogger _logger;
private readonly IDiscordRestUserAPI _userApi;
public ErrorLoggingPostExecutionEvent(ILogger logger, IFeedbackService feedback,
@@ -53,13 +53,13 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent
if (result.IsSuccess)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var botResult = await _userApi.GetCurrentUserAsync(ct);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var embed = new EmbedBuilder().WithSmallTitle(Messages.CommandExecutionFailed, bot)
@@ -70,15 +70,19 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent
var issuesButton = new ButtonComponent(
ButtonComponentStyle.Link,
- Messages.ButtonReportIssue,
+ BuildInfo.IsDirty
+ ? Messages.ButtonDirty
+ : Messages.ButtonReportIssue,
new PartialEmoji(Name: "⚠️"),
- URL: Octobot.IssuesUrl
+ URL: BuildInfo.IssuesUrl,
+ IsDisabled: BuildInfo.IsDirty
);
- return await _feedback.SendContextualEmbedResultAsync(embed,
+ return ResultExtensions.FromError(await _feedback.SendContextualEmbedResultAsync(embed,
new FeedbackMessageOptions(MessageComponents: new[]
{
new ActionRowComponent(new[] { issuesButton })
- }), ct);
+ }), ct)
+ );
}
}
diff --git a/src/Commands/Events/LoggingPreparationErrorEvent.cs b/src/Commands/Events/LoggingPreparationErrorEvent.cs
index be48e74..87b4090 100644
--- a/src/Commands/Events/LoggingPreparationErrorEvent.cs
+++ b/src/Commands/Events/LoggingPreparationErrorEvent.cs
@@ -33,6 +33,6 @@ public class LoggingPreparationErrorEvent : IPreparationErrorEvent
{
_logger.LogResult(preparationResult, "Error in slash command preparation.");
- return Task.FromResult(Result.FromSuccess());
+ return Task.FromResult(Result.Success);
}
}
diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs
index 4262487..2caacee 100644
--- a/src/Commands/KickCommandGroup.cs
+++ b/src/Commands/KickCommandGroup.cs
@@ -80,19 +80,19 @@ public class KickCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken);
if (!guildResult.IsDefined(out var guild))
{
- return Result.FromError(guildResult);
+ return ResultExtensions.FromError(guildResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -118,7 +118,7 @@ public class KickCommandGroup : CommandGroup
= await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Kick", ct);
if (!interactionResult.IsSuccess)
{
- return Result.FromError(interactionResult);
+ return ResultExtensions.FromError(interactionResult);
}
if (interactionResult.Entity is not null)
@@ -143,17 +143,19 @@ public class KickCommandGroup : CommandGroup
await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct);
}
+ var memberData = data.GetOrCreateMemberData(target.ID);
+ memberData.Kicked = true;
+
var kickResult = await _guildApi.RemoveGuildMemberAsync(
guild.ID, target.ID, $"({executor.GetTag()}) {reason}".EncodeHeader(),
ct);
if (!kickResult.IsSuccess)
{
- return Result.FromError(kickResult.Error);
+ memberData.Kicked = false;
+ return ResultExtensions.FromError(kickResult);
}
- var memberData = data.GetOrCreateMemberData(target.ID);
memberData.Roles.Clear();
- memberData.Kicked = true;
var title = string.Format(Messages.UserKicked, target.GetTag());
var description = MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason));
diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs
index 43eddf1..f7212c4 100644
--- a/src/Commands/MuteCommandGroup.cs
+++ b/src/Commands/MuteCommandGroup.cs
@@ -85,13 +85,13 @@ public class MuteCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -118,7 +118,8 @@ public class MuteCommandGroup : CommandGroup
return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken);
}
- return await MuteUserAsync(executor, target, reason, duration, guildId, data, channelId, bot, CancellationToken);
+ return await MuteUserAsync(executor, target, reason, duration, guildId, data, channelId, bot,
+ CancellationToken);
}
public async Task MuteUserAsync(
@@ -130,7 +131,7 @@ public class MuteCommandGroup : CommandGroup
guildId, executor.ID, target.ID, "Mute", ct);
if (!interactionResult.IsSuccess)
{
- return Result.FromError(interactionResult);
+ return ResultExtensions.FromError(interactionResult);
}
if (interactionResult.Entity is not null)
@@ -143,14 +144,16 @@ public class MuteCommandGroup : CommandGroup
var until = DateTimeOffset.UtcNow.Add(duration); // >:)
- var muteMethodResult = await SelectMuteMethodAsync(executor, target, reason, duration, guildId, data, bot, until, ct);
+ var muteMethodResult =
+ await SelectMuteMethodAsync(executor, target, reason, duration, guildId, data, bot, until, ct);
if (!muteMethodResult.IsSuccess)
{
- return muteMethodResult;
+ return ResultExtensions.FromError(muteMethodResult);
}
var title = string.Format(Messages.UserMuted, target.GetTag());
- var description = new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason))
+ var description = new StringBuilder()
+ .AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason))
.AppendBulletPoint(string.Format(
Messages.DescriptionActionExpiresAt, Markdown.Timestamp(until))).ToString();
@@ -257,14 +260,14 @@ public class MuteCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
// Needed to get the tag and avatar
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -291,7 +294,7 @@ public class MuteCommandGroup : CommandGroup
guildId, executor.ID, target.ID, "Unmute", ct);
if (!interactionResult.IsSuccess)
{
- return Result.FromError(interactionResult);
+ return ResultExtensions.FromError(interactionResult);
}
if (interactionResult.Entity is not null)
@@ -324,14 +327,14 @@ public class MuteCommandGroup : CommandGroup
await RemoveMuteRoleAsync(executor, target, reason, guildId, memberData, CancellationToken);
if (!removeMuteRoleAsync.IsSuccess)
{
- return Result.FromError(removeMuteRoleAsync.Error);
+ return ResultExtensions.FromError(removeMuteRoleAsync);
}
var removeTimeoutResult =
await RemoveTimeoutAsync(executor, target, reason, guildId, communicationDisabledUntil, CancellationToken);
if (!removeTimeoutResult.IsSuccess)
{
- return Result.FromError(removeTimeoutResult.Error);
+ return ResultExtensions.FromError(removeTimeoutResult);
}
var title = string.Format(Messages.UserUnmuted, target.GetTag());
@@ -348,11 +351,12 @@ public class MuteCommandGroup : CommandGroup
}
private async Task RemoveMuteRoleAsync(
- IUser executor, IUser target, string reason, Snowflake guildId, MemberData memberData, CancellationToken ct = default)
+ IUser executor, IUser target, string reason, Snowflake guildId, MemberData memberData,
+ CancellationToken ct = default)
{
if (memberData.MutedUntil is null)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var unmuteResult = await _guildApi.ModifyGuildMemberAsync(
@@ -372,7 +376,7 @@ public class MuteCommandGroup : CommandGroup
{
if (communicationDisabledUntil is null)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var unmuteResult = await _guildApi.ModifyGuildMemberAsync(
diff --git a/src/Commands/PingCommandGroup.cs b/src/Commands/PingCommandGroup.cs
index 31fa6dc..d64c6dd 100644
--- a/src/Commands/PingCommandGroup.cs
+++ b/src/Commands/PingCommandGroup.cs
@@ -64,7 +64,7 @@ public class PingCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var cfg = await _guildData.GetSettings(guildId, CancellationToken);
@@ -84,14 +84,14 @@ public class PingCommandGroup : CommandGroup
channelId, limit: 1, ct: ct);
if (!lastMessageResult.IsDefined(out var lastMessage))
{
- return Result.FromError(lastMessageResult);
+ return ResultExtensions.FromError(lastMessageResult);
}
latency = DateTimeOffset.UtcNow.Subtract(lastMessage.Single().Timestamp).TotalMilliseconds;
}
var embed = new EmbedBuilder().WithSmallTitle(bot.GetTag(), bot)
- .WithTitle($"Sound{Random.Shared.Next(1, 4)}".Localized())
+ .WithTitle($"Generic{Random.Shared.Next(1, 4)}".Localized())
.WithDescription($"{latency:F0}{Messages.Milliseconds}")
.WithColour(latency < 250 ? ColorsList.Green : latency < 500 ? ColorsList.Yellow : ColorsList.Red)
.WithCurrentTimestamp()
diff --git a/src/Commands/RemindCommandGroup.cs b/src/Commands/RemindCommandGroup.cs
index f9c006e..aa1ef7e 100644
--- a/src/Commands/RemindCommandGroup.cs
+++ b/src/Commands/RemindCommandGroup.cs
@@ -63,13 +63,13 @@ public class RemindCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -134,13 +134,13 @@ public class RemindCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -226,13 +226,13 @@ public class RemindCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -343,7 +343,7 @@ public class RemindCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs
index 9dd9e04..7441ce1 100644
--- a/src/Commands/SettingsCommandGroup.cs
+++ b/src/Commands/SettingsCommandGroup.cs
@@ -40,6 +40,7 @@ public class SettingsCommandGroup : CommandGroup
GuildSettings.Language,
GuildSettings.WarnPunishment,
GuildSettings.WelcomeMessage,
+ GuildSettings.LeaveMessage,
GuildSettings.ReceiveStartupMessages,
GuildSettings.RemoveRolesOnMute,
GuildSettings.ReturnRolesOnRejoin,
@@ -100,7 +101,7 @@ public class SettingsCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var cfg = await _guildData.GetSettings(guildId, CancellationToken);
@@ -183,13 +184,13 @@ public class SettingsCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -243,7 +244,7 @@ public class SettingsCommandGroup : CommandGroup
[DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
[RequireDiscordPermission(DiscordPermission.ManageGuild)]
- [Description("Reset settings for this server")]
+ [Description("Reset settings for this guild")]
[UsedImplicitly]
public async Task ExecuteResetSettingsAsync(
[Description("Setting to reset")] AllOptionsEnum? setting = null)
@@ -256,7 +257,7 @@ public class SettingsCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var cfg = await _guildData.GetSettings(guildId, CancellationToken);
@@ -276,7 +277,7 @@ public class SettingsCommandGroup : CommandGroup
var resetResult = option.Reset(cfg);
if (!resetResult.IsSuccess)
{
- return Result.FromError(resetResult.Error);
+ return ResultExtensions.FromError(resetResult);
}
var embed = new EmbedBuilder().WithSmallTitle(
diff --git a/src/Commands/ToolsCommandGroup.cs b/src/Commands/ToolsCommandGroup.cs
index ea91e1e..d4f3f75 100644
--- a/src/Commands/ToolsCommandGroup.cs
+++ b/src/Commands/ToolsCommandGroup.cs
@@ -35,7 +35,7 @@ public class ToolsCommandGroup : CommandGroup
public ToolsCommandGroup(
ICommandContext context, IFeedbackService feedback,
GuildDataService guildData, IDiscordRestGuildAPI guildApi,
- IDiscordRestUserAPI userApi, IDiscordRestChannelAPI channelApi)
+ IDiscordRestUserAPI userApi)
{
_context = context;
_guildData = guildData;
@@ -81,13 +81,13 @@ public class ToolsCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -262,7 +262,7 @@ public class ToolsCommandGroup : CommandGroup
///
[Command("guildinfo")]
[DiscordDefaultDMPermission(false)]
- [Description("Shows info current guild")]
+ [Description("Shows info about current guild")]
[UsedImplicitly]
public async Task ExecuteGuildInfoAsync()
{
@@ -274,13 +274,13 @@ public class ToolsCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken);
if (!guildResult.IsDefined(out var guild))
{
- return Result.FromError(guildResult);
+ return ResultExtensions.FromError(guildResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -353,7 +353,7 @@ public class ToolsCommandGroup : CommandGroup
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -439,13 +439,13 @@ public class ToolsCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
- return Result.FromError(executorResult);
+ return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
@@ -514,7 +514,7 @@ public class ToolsCommandGroup : CommandGroup
[UsedImplicitly]
public async Task ExecuteEightBallAsync(
// let the user think he's actually asking the ball a question
- string question)
+ [Description("Question to ask")] string question)
{
if (!_context.TryGetContextIDs(out var guildId, out _, out _))
{
@@ -524,7 +524,7 @@ public class ToolsCommandGroup : CommandGroup
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
diff --git a/src/Data/GuildSettings.cs b/src/Data/GuildSettings.cs
index 4e46be4..df650ec 100644
--- a/src/Data/GuildSettings.cs
+++ b/src/Data/GuildSettings.cs
@@ -15,17 +15,29 @@ public static class GuildSettings
public static readonly Option WarnPunishment = new("WarnPunishment", "disabled");
///
- /// Controls what message should be sent in when a new member joins the server.
+ /// Controls what message should be sent in when a new member joins the guild.
///
///
///
/// - No message will be sent if set to "off", "disable" or "disabled".
- /// - will be sent if set to "default" or "reset"
+ /// - will be sent if set to "default" or "reset".
///
///
///
public static readonly Option WelcomeMessage = new("WelcomeMessage", "default");
+ ///
+ /// Controls what message should be sent in when a member leaves the guild.
+ ///
+ ///
+ ///
+ /// - No message will be sent if set to "off", "disable" or "disabled".
+ /// - will be sent if set to "default" or "reset".
+ ///
+ ///
+ ///
+ public static readonly Option LeaveMessage = new("LeaveMessage", "default");
+
///
/// Controls whether or not the message should be sent
/// in on startup.
diff --git a/src/Data/MemberData.cs b/src/Data/MemberData.cs
index 2701be7..8975a25 100644
--- a/src/Data/MemberData.cs
+++ b/src/Data/MemberData.cs
@@ -5,10 +5,9 @@ namespace Octobot.Data;
///
public sealed class MemberData
{
- public MemberData(ulong id, DateTimeOffset? bannedUntil = null, List? reminders = null)
+ public MemberData(ulong id, List? reminders = null)
{
Id = id;
- BannedUntil = bannedUntil;
if (reminders is not null)
{
Reminders = reminders;
diff --git a/src/Data/Options/AllOptionsEnum.cs b/src/Data/Options/AllOptionsEnum.cs
index f3ef3eb..ab0277e 100644
--- a/src/Data/Options/AllOptionsEnum.cs
+++ b/src/Data/Options/AllOptionsEnum.cs
@@ -15,6 +15,7 @@ public enum AllOptionsEnum
[UsedImplicitly] Language,
[UsedImplicitly] WarnPunishment,
[UsedImplicitly] WelcomeMessage,
+ [UsedImplicitly] LeaveMessage,
[UsedImplicitly] ReceiveStartupMessages,
[UsedImplicitly] RemoveRolesOnMute,
[UsedImplicitly] ReturnRolesOnRejoin,
diff --git a/src/Data/Options/BoolOption.cs b/src/Data/Options/BoolOption.cs
index 130687e..6876164 100644
--- a/src/Data/Options/BoolOption.cs
+++ b/src/Data/Options/BoolOption.cs
@@ -20,7 +20,7 @@ public sealed class BoolOption : Option
}
settings[Name] = value;
- return Result.FromSuccess();
+ return Result.Success;
}
private static bool TryParseBool(string from, out bool value)
diff --git a/src/Data/Options/Option.cs b/src/Data/Options/Option.cs
index 0ba8ce1..5d703a8 100644
--- a/src/Data/Options/Option.cs
+++ b/src/Data/Options/Option.cs
@@ -35,7 +35,13 @@ public class Option : IOption
public virtual Result Set(JsonNode settings, string from)
{
settings[Name] = from;
- return Result.FromSuccess();
+ return Result.Success;
+ }
+
+ public Result Reset(JsonNode settings)
+ {
+ settings[Name] = null;
+ return Result.Success;
}
///
@@ -48,10 +54,4 @@ public class Option : IOption
var property = settings[Name];
return property != null ? property.GetValue() : DefaultValue;
}
-
- public Result Reset(JsonNode settings)
- {
- settings[Name] = null;
- return Result.FromSuccess();
- }
}
diff --git a/src/Data/Options/SnowflakeOption.cs b/src/Data/Options/SnowflakeOption.cs
index 66ada96..7118da8 100644
--- a/src/Data/Options/SnowflakeOption.cs
+++ b/src/Data/Options/SnowflakeOption.cs
@@ -32,7 +32,7 @@ public sealed partial class SnowflakeOption : Option
}
settings[Name] = parsed;
- return Result.FromSuccess();
+ return Result.Success;
}
[GeneratedRegex("[^0-9]")]
diff --git a/src/Data/Options/TimeSpanOption.cs b/src/Data/Options/TimeSpanOption.cs
index c81a02d..d237b6e 100644
--- a/src/Data/Options/TimeSpanOption.cs
+++ b/src/Data/Options/TimeSpanOption.cs
@@ -22,6 +22,6 @@ public sealed class TimeSpanOption : Option
}
settings[Name] = span.ToString();
- return Result.FromSuccess();
+ return Result.Success;
}
}
diff --git a/src/Extensions/ChannelApiExtensions.cs b/src/Extensions/ChannelApiExtensions.cs
index 12ccf35..99eff67 100644
--- a/src/Extensions/ChannelApiExtensions.cs
+++ b/src/Extensions/ChannelApiExtensions.cs
@@ -20,7 +20,7 @@ public static class ChannelApiExtensions
{
if (!embedResult.IsDefined() || !embedResult.Value.IsDefined(out var embed))
{
- return Result.FromError(embedResult.Value);
+ return ResultExtensions.FromError(embedResult.Value);
}
return (Result)await channelApi.CreateMessageAsync(channelId, message, nonce, isTextToSpeech, new[] { embed },
diff --git a/src/Extensions/CollectionExtensions.cs b/src/Extensions/CollectionExtensions.cs
index 9c873f2..2369532 100644
--- a/src/Extensions/CollectionExtensions.cs
+++ b/src/Extensions/CollectionExtensions.cs
@@ -32,7 +32,7 @@ public static class CollectionExtensions
{
return list.Count switch
{
- 0 => Result.FromSuccess(),
+ 0 => Result.Success,
1 => list[0],
_ => new AggregateError(list.Cast().ToArray())
};
diff --git a/src/Extensions/FeedbackServiceExtensions.cs b/src/Extensions/FeedbackServiceExtensions.cs
index 40e0d53..e6ef376 100644
--- a/src/Extensions/FeedbackServiceExtensions.cs
+++ b/src/Extensions/FeedbackServiceExtensions.cs
@@ -13,7 +13,7 @@ public static class FeedbackServiceExtensions
{
if (!embedResult.IsDefined(out var embed))
{
- return Result.FromError(embedResult);
+ return ResultExtensions.FromError(embedResult);
}
return (Result)await feedback.SendContextualEmbedAsync(embed, options, ct);
diff --git a/src/Extensions/GuildScheduledEventExtensions.cs b/src/Extensions/GuildScheduledEventExtensions.cs
index e3217e3..f1b6985 100644
--- a/src/Extensions/GuildScheduledEventExtensions.cs
+++ b/src/Extensions/GuildScheduledEventExtensions.cs
@@ -22,7 +22,7 @@ public static class GuildScheduledEventExtensions
}
return scheduledEvent.ScheduledEndTime.AsOptional().IsDefined(out endTime)
- ? Result.FromSuccess()
+ ? Result.Success
: new ArgumentNullError(nameof(scheduledEvent.ScheduledEndTime));
}
}
diff --git a/src/Extensions/ResultExtensions.cs b/src/Extensions/ResultExtensions.cs
new file mode 100644
index 0000000..f456dac
--- /dev/null
+++ b/src/Extensions/ResultExtensions.cs
@@ -0,0 +1,61 @@
+using System.Diagnostics;
+using Microsoft.Extensions.Logging;
+using Remora.Results;
+
+namespace Octobot.Extensions;
+
+public static class ResultExtensions
+{
+ public static Result FromError(Result result)
+ {
+ LogResultStackTrace(result);
+
+ return result;
+ }
+
+ public static Result FromError(Result result)
+ {
+ var casted = (Result)result;
+ LogResultStackTrace(casted);
+
+ return casted;
+ }
+
+ [Conditional("DEBUG")]
+ private static void LogResultStackTrace(Result result)
+ {
+ if (Octobot.StaticLogger is null || result.IsSuccess)
+ {
+ return;
+ }
+
+ Octobot.StaticLogger.LogError("{ErrorType}: {ErrorMessage}{NewLine}{StackTrace}",
+ result.Error.GetType().FullName, result.Error.Message, Environment.NewLine, ConstructStackTrace());
+
+ var inner = result.Inner;
+ while (inner is { IsSuccess: false })
+ {
+ Octobot.StaticLogger.LogError("Caused by: {ResultType}: {ResultMessage}",
+ inner.Error.GetType().FullName, inner.Error.Message);
+
+ inner = inner.Inner;
+ }
+ }
+
+ private static string ConstructStackTrace()
+ {
+ var stackArray = new StackTrace(3, true).ToString().Split(Environment.NewLine).ToList();
+ for (var i = stackArray.Count - 1; i >= 0; i--)
+ {
+ var frame = stackArray[i];
+ var trimmed = frame.TrimStart();
+ if (trimmed.StartsWith("at System.Threading", StringComparison.Ordinal)
+ || trimmed.StartsWith("at System.Runtime.CompilerServices", StringComparison.Ordinal))
+ {
+ stackArray.RemoveAt(i);
+ }
+ }
+
+ return string.Join(Environment.NewLine, stackArray);
+ }
+}
diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs
index 3d3c0d8..16cd3eb 100644
--- a/src/Messages.Designer.cs
+++ b/src/Messages.Designer.cs
@@ -66,21 +66,21 @@ namespace Octobot {
}
}
- internal static string Sound1 {
+ internal static string Generic1 {
get {
- return ResourceManager.GetString("Sound1", resourceCulture);
+ return ResourceManager.GetString("Generic1", resourceCulture);
}
}
- internal static string Sound2 {
+ internal static string Generic2 {
get {
- return ResourceManager.GetString("Sound2", resourceCulture);
+ return ResourceManager.GetString("Generic2", resourceCulture);
}
}
- internal static string Sound3 {
+ internal static string Generic3 {
get {
- return ResourceManager.GetString("Sound3", resourceCulture);
+ return ResourceManager.GetString("Generic3", resourceCulture);
}
}
@@ -1037,18 +1037,26 @@ namespace Octobot {
}
}
- internal static string InvalidTimeSpan
- {
- get
- {
+ internal static string DefaultLeaveMessage {
+ get {
+ return ResourceManager.GetString("DefaultLeaveMessage", resourceCulture);
+ }
+ }
+
+ internal static string SettingsLeaveMessage {
+ get {
+ return ResourceManager.GetString("SettingsLeaveMessage", resourceCulture);
+ }
+ }
+
+ internal static string InvalidTimeSpan {
+ get {
return ResourceManager.GetString("InvalidTimeSpan", resourceCulture);
}
}
- internal static string UserInfoKicked
- {
- get
- {
+ internal static string UserInfoKicked {
+ get {
return ResourceManager.GetString("UserInfoKicked", resourceCulture);
}
}
@@ -1185,8 +1193,13 @@ namespace Octobot {
}
}
- internal static string SettingsWelcomeMessagesChannel
- {
+ internal static string Version {
+ get {
+ return ResourceManager.GetString("Version", resourceCulture);
+ }
+ }
+
+ internal static string SettingsWelcomeMessagesChannel {
get {
return ResourceManager.GetString("SettingsWelcomeMessagesChannel", resourceCulture);
}
@@ -1240,5 +1253,17 @@ namespace Octobot {
return ResourceManager.GetString("ReceivedTooManyWarnings", resourceCulture);
}
}
+
+ internal static string ButtonDirty {
+ get {
+ return ResourceManager.GetString("ButtonDirty", resourceCulture);
+ }
+ }
+
+ internal static string ButtonOpenWiki {
+ get {
+ return ResourceManager.GetString("ButtonOpenWiki", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Octobot.cs b/src/Octobot.cs
index 1ebf7c3..a4871f4 100644
--- a/src/Octobot.cs
+++ b/src/Octobot.cs
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using Octobot.Attributes;
using Octobot.Commands.Events;
using Octobot.Services;
using Octobot.Services.Update;
@@ -22,16 +23,17 @@ namespace Octobot;
public sealed class Octobot
{
- public const string RepositoryUrl = "https://github.com/TeamOctolings/Octobot";
- public const string IssuesUrl = $"{RepositoryUrl}/issues";
-
public static readonly AllowedMentions NoMentions = new(
Array.Empty(), Array.Empty(), Array.Empty());
+ [StaticCallersOnly]
+ public static ILogger? StaticLogger { get; private set; }
+
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).UseConsoleLifetime().Build();
var services = host.Services;
+ StaticLogger = services.GetRequiredService>();
var slashService = services.GetRequiredService();
// Providing a guild ID to this call will result in command duplicates!
diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs
index a1e7d16..b03fd3f 100644
--- a/src/Responders/GuildLoadedResponder.cs
+++ b/src/Responders/GuildLoadedResponder.cs
@@ -42,7 +42,7 @@ public class GuildLoadedResponder : IResponder
{
if (!gatewayEvent.Guild.IsT0) // Guild is not IAvailableGuild
{
- return Result.FromSuccess();
+ return Result.Success;
}
var guild = gatewayEvent.Guild.AsT0;
@@ -57,7 +57,7 @@ public class GuildLoadedResponder : IResponder
var botResult = await _userApi.GetCurrentUserAsync(ct);
if (!botResult.IsDefined(out var bot))
{
- return Result.FromError(botResult);
+ return ResultExtensions.FromError(botResult);
}
if (data.DataLoadFailed)
@@ -68,27 +68,23 @@ public class GuildLoadedResponder : IResponder
var ownerResult = await _userApi.GetUserAsync(guild.OwnerID, ct);
if (!ownerResult.IsDefined(out var owner))
{
- return Result.FromError(ownerResult);
+ return ResultExtensions.FromError(ownerResult);
}
_logger.LogInformation("Loaded guild \"{Name}\" ({ID}) owned by {Owner} ({OwnerID}) with {MemberCount} members",
guild.Name, guild.ID, owner.GetTag(), owner.ID, guild.MemberCount);
- if (!GuildSettings.ReceiveStartupMessages.Get(cfg))
+ if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
+ || !GuildSettings.ReceiveStartupMessages.Get(cfg))
{
- return Result.FromSuccess();
- }
-
- if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
- {
- return Result.FromSuccess();
+ return Result.Success;
}
Messages.Culture = GuildSettings.Language.Get(cfg);
var i = Random.Shared.Next(1, 4);
var embed = new EmbedBuilder().WithSmallTitle(bot.GetTag(), bot)
- .WithTitle($"Sound{i}".Localized())
+ .WithTitle($"Generic{i}".Localized())
.WithDescription(Messages.Ready)
.WithCurrentTimestamp()
.WithColour(ColorsList.Blue)
@@ -103,7 +99,7 @@ public class GuildLoadedResponder : IResponder
var channelResult = await _utility.GetEmergencyFeedbackChannel(guild, data, ct);
if (!channelResult.IsDefined(out var channel))
{
- return Result.FromError(channelResult);
+ return ResultExtensions.FromError(channelResult);
}
var errorEmbed = new EmbedBuilder()
@@ -115,9 +111,12 @@ public class GuildLoadedResponder : IResponder
var issuesButton = new ButtonComponent(
ButtonComponentStyle.Link,
- Messages.ButtonReportIssue,
+ BuildInfo.IsDirty
+ ? Messages.ButtonDirty
+ : Messages.ButtonReportIssue,
new PartialEmoji(Name: "⚠️"),
- URL: Octobot.IssuesUrl
+ URL: BuildInfo.IssuesUrl,
+ IsDisabled: BuildInfo.IsDirty
);
return await _channelApi.CreateMessageWithEmbedResultAsync(channel, embedResult: errorEmbed,
diff --git a/src/Responders/GuildMemberJoinedResponder.cs b/src/Responders/GuildMemberJoinedResponder.cs
index 012bfad..61ef5cc 100644
--- a/src/Responders/GuildMemberJoinedResponder.cs
+++ b/src/Responders/GuildMemberJoinedResponder.cs
@@ -48,13 +48,13 @@ public class GuildMemberJoinedResponder : IResponder
var returnRolesResult = await TryReturnRolesAsync(cfg, memberData, gatewayEvent.GuildID, user.ID, ct);
if (!returnRolesResult.IsSuccess)
{
- return Result.FromError(returnRolesResult.Error);
+ return ResultExtensions.FromError(returnRolesResult);
}
if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty()
|| GuildSettings.WelcomeMessage.Get(cfg) is "off" or "disable" or "disabled")
{
- return Result.FromSuccess();
+ return Result.Success;
}
Messages.Culture = GuildSettings.Language.Get(cfg);
@@ -65,7 +65,7 @@ public class GuildMemberJoinedResponder : IResponder
var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct);
if (!guildResult.IsDefined(out var guild))
{
- return Result.FromError(guildResult);
+ return ResultExtensions.FromError(guildResult);
}
var embed = new EmbedBuilder()
@@ -85,7 +85,7 @@ public class GuildMemberJoinedResponder : IResponder
{
if (!GuildSettings.ReturnRolesOnRejoin.Get(cfg))
{
- return Result.FromSuccess();
+ return Result.Success;
}
var assignRoles = new List();
diff --git a/src/Responders/GuildMemberLeftResponder.cs b/src/Responders/GuildMemberLeftResponder.cs
new file mode 100644
index 0000000..90cc64c
--- /dev/null
+++ b/src/Responders/GuildMemberLeftResponder.cs
@@ -0,0 +1,72 @@
+using JetBrains.Annotations;
+using Octobot.Data;
+using Octobot.Extensions;
+using Octobot.Services;
+using Remora.Discord.API.Abstractions.Gateway.Events;
+using Remora.Discord.API.Abstractions.Rest;
+using Remora.Discord.Extensions.Embeds;
+using Remora.Discord.Gateway.Responders;
+using Remora.Results;
+
+namespace Octobot.Responders;
+
+///
+/// Handles sending a guild's if one is set.
+///
+///
+[UsedImplicitly]
+public class GuildMemberLeftResponder : IResponder
+{
+ private readonly IDiscordRestChannelAPI _channelApi;
+ private readonly IDiscordRestGuildAPI _guildApi;
+ private readonly GuildDataService _guildData;
+
+ public GuildMemberLeftResponder(
+ IDiscordRestChannelAPI channelApi, GuildDataService guildData, IDiscordRestGuildAPI guildApi)
+ {
+ _channelApi = channelApi;
+ _guildData = guildData;
+ _guildApi = guildApi;
+ }
+
+ public async Task RespondAsync(IGuildMemberRemove gatewayEvent, CancellationToken ct = default)
+ {
+ var user = gatewayEvent.User;
+ var data = await _guildData.GetData(gatewayEvent.GuildID, ct);
+ var cfg = data.Settings;
+
+ var memberData = data.GetOrCreateMemberData(user.ID);
+ if (memberData.BannedUntil is not null || memberData.Kicked)
+ {
+ return Result.Success;
+ }
+
+ if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty()
+ || GuildSettings.LeaveMessage.Get(cfg) is "off" or "disable" or "disabled")
+ {
+ return Result.Success;
+ }
+
+ Messages.Culture = GuildSettings.Language.Get(cfg);
+ var leaveMessage = GuildSettings.LeaveMessage.Get(cfg) is "default" or "reset"
+ ? Messages.DefaultLeaveMessage
+ : GuildSettings.LeaveMessage.Get(cfg);
+
+ var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct);
+ if (!guildResult.IsDefined(out var guild))
+ {
+ return ResultExtensions.FromError(guildResult);
+ }
+
+ var embed = new EmbedBuilder()
+ .WithSmallTitle(string.Format(leaveMessage, user.GetTag(), guild.Name), user)
+ .WithGuildFooter(guild)
+ .WithTimestamp(DateTimeOffset.UtcNow)
+ .WithColour(ColorsList.Black)
+ .Build();
+
+ return await _channelApi.CreateMessageWithEmbedResultAsync(
+ GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed,
+ allowedMentions: Octobot.NoMentions, ct: ct);
+ }
+}
diff --git a/src/Responders/GuildUnloadedResponder.cs b/src/Responders/GuildUnloadedResponder.cs
index 47bde75..b49d136 100644
--- a/src/Responders/GuildUnloadedResponder.cs
+++ b/src/Responders/GuildUnloadedResponder.cs
@@ -33,6 +33,6 @@ public class GuildUnloadedResponder : IResponder
_logger.LogInformation("Unloaded guild {GuildId}", guildId);
}
- return Task.FromResult(Result.FromSuccess());
+ return Task.FromResult(Result.Success);
}
}
diff --git a/src/Responders/MessageDeletedResponder.cs b/src/Responders/MessageDeletedResponder.cs
index bfedb22..5a69273 100644
--- a/src/Responders/MessageDeletedResponder.cs
+++ b/src/Responders/MessageDeletedResponder.cs
@@ -39,31 +39,31 @@ public class MessageDeletedResponder : IResponder
{
if (!gatewayEvent.GuildID.IsDefined(out var guildId))
{
- return Result.FromSuccess();
+ return Result.Success;
}
var cfg = await _guildData.GetSettings(guildId, ct);
if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
{
- return Result.FromSuccess();
+ return Result.Success;
}
var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct);
if (!messageResult.IsDefined(out var message))
{
- return Result.FromError(messageResult);
+ return ResultExtensions.FromError(messageResult);
}
if (string.IsNullOrWhiteSpace(message.Content))
{
- return Result.FromSuccess();
+ return Result.Success;
}
var auditLogResult = await _auditLogApi.GetGuildAuditLogAsync(
guildId, actionType: AuditLogEvent.MessageDelete, limit: 1, ct: ct);
if (!auditLogResult.IsDefined(out var auditLogPage))
{
- return Result.FromError(auditLogResult);
+ return ResultExtensions.FromError(auditLogResult);
}
var auditLog = auditLogPage.AuditLogEntries.Single();
@@ -78,15 +78,16 @@ public class MessageDeletedResponder : IResponder
if (!deleterResult.IsDefined(out var deleter))
{
- return Result.FromError(deleterResult);
+ return ResultExtensions.FromError(deleterResult);
}
Messages.Culture = GuildSettings.Language.Get(cfg);
- var builder = new StringBuilder().AppendLine(
- string.Format(Messages.DescriptionActionJumpToChannel,
- Mention.Channel(gatewayEvent.ChannelID)))
- .AppendLine(message.Content.InBlockCode());
+ var builder = new StringBuilder()
+ .AppendLine(message.Content.InBlockCode())
+ .AppendLine(
+ string.Format(Messages.DescriptionActionJumpToChannel, Mention.Channel(gatewayEvent.ChannelID))
+ );
var embed = new EmbedBuilder()
.WithSmallTitle(
diff --git a/src/Responders/MessageEditedResponder.cs b/src/Responders/MessageEditedResponder.cs
index c7426d2..1143652 100644
--- a/src/Responders/MessageEditedResponder.cs
+++ b/src/Responders/MessageEditedResponder.cs
@@ -46,30 +46,18 @@ public class MessageEditedResponder : IResponder
return new ArgumentNullError(nameof(gatewayEvent.ChannelID));
}
- if (!gatewayEvent.GuildID.IsDefined(out var guildId))
+ if (!gatewayEvent.GuildID.IsDefined(out var guildId)
+ || !gatewayEvent.Author.IsDefined(out var author)
+ || !gatewayEvent.EditedTimestamp.IsDefined(out var timestamp)
+ || !gatewayEvent.Content.IsDefined(out var newContent))
{
- return Result.FromSuccess();
- }
-
- if (gatewayEvent.Author.IsDefined(out var author) && author.IsBot.OrDefault(false))
- {
- return Result.FromSuccess();
- }
-
- if (!gatewayEvent.EditedTimestamp.IsDefined(out var timestamp))
- {
- return Result.FromSuccess(); // The message wasn't actually edited
- }
-
- if (!gatewayEvent.Content.IsDefined(out var newContent))
- {
- return Result.FromSuccess();
+ return Result.Success;
}
var cfg = await _guildData.GetSettings(guildId, ct);
- if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
+ if (author.IsBot.OrDefault(false) || GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
{
- return Result.FromSuccess();
+ return Result.Success;
}
var cacheKey = new KeyHelpers.MessageCacheKey(channelId, messageId);
@@ -78,12 +66,12 @@ public class MessageEditedResponder : IResponder
if (!messageResult.IsDefined(out var message))
{
_ = _channelApi.GetChannelMessageAsync(channelId, messageId, ct);
- return Result.FromSuccess();
+ return Result.Success;
}
if (message.Content == newContent)
{
- return Result.FromSuccess();
+ return Result.Success;
}
// Custom event responders are called earlier than responders responsible for message caching
@@ -101,10 +89,11 @@ public class MessageEditedResponder : IResponder
Messages.Culture = GuildSettings.Language.Get(cfg);
- var builder = new StringBuilder().AppendLine(
- string.Format(Messages.DescriptionActionJumpToMessage,
- $"https://discord.com/channels/{guildId}/{channelId}/{messageId}"))
- .AppendLine(diff.AsMarkdown());
+ var builder = new StringBuilder()
+ .AppendLine(diff.AsMarkdown())
+ .AppendLine(string.Format(Messages.DescriptionActionJumpToMessage,
+ $"https://discord.com/channels/{guildId}/{channelId}/{messageId}")
+ );
var embed = new EmbedBuilder()
.WithSmallTitle(string.Format(Messages.CachedMessageEdited, message.Author.GetTag()), message.Author)
diff --git a/src/Responders/MessageReceivedResponder.cs b/src/Responders/MessageReceivedResponder.cs
index 6ab7199..4c26d8d 100644
--- a/src/Responders/MessageReceivedResponder.cs
+++ b/src/Responders/MessageReceivedResponder.cs
@@ -34,6 +34,6 @@ public class MessageCreateResponder : IResponder
"лан" => "https://i.ibb.co/VYH2QLc/lan.jpg",
_ => default(Optional)
});
- return Task.FromResult(Result.FromSuccess());
+ return Task.FromResult(Result.Success);
}
}
diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs
index 7674bbe..45d0476 100644
--- a/src/Services/Update/MemberUpdateService.cs
+++ b/src/Services/Update/MemberUpdateService.cs
@@ -97,7 +97,7 @@ public sealed partial class MemberUpdateService : BackgroundService
= await _utility.CheckInteractionsAsync(guildId, null, id, "Update", ct);
if (!interactionResult.IsSuccess)
{
- return Result.FromError(interactionResult);
+ return ResultExtensions.FromError(interactionResult);
}
var canInteract = interactionResult.Entity is null;
@@ -121,7 +121,7 @@ public sealed partial class MemberUpdateService : BackgroundService
if (!canInteract)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var autoUnmuteResult = await TryAutoUnmuteAsync(guildId, id, data, ct);
@@ -148,7 +148,14 @@ public sealed partial class MemberUpdateService : BackgroundService
{
if (data.BannedUntil is null || DateTimeOffset.UtcNow <= data.BannedUntil)
{
- return Result.FromSuccess();
+ return Result.Success;
+ }
+
+ var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, id, ct);
+ if (!existingBanResult.IsDefined())
+ {
+ data.BannedUntil = null;
+ return Result.Success;
}
var unbanResult = await _guildApi.RemoveGuildBanAsync(
@@ -166,7 +173,7 @@ public sealed partial class MemberUpdateService : BackgroundService
{
if (data.MutedUntil is null || DateTimeOffset.UtcNow <= data.MutedUntil)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var unmuteResult = await _guildApi.ModifyGuildMemberAsync(
@@ -202,7 +209,7 @@ public sealed partial class MemberUpdateService : BackgroundService
if (!usernameChanged)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var newNickname = string.Concat(characterList.ToArray());
@@ -223,12 +230,13 @@ public sealed partial class MemberUpdateService : BackgroundService
{
if (DateTimeOffset.UtcNow < reminder.At)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var builder = new StringBuilder()
.AppendBulletPointLine(string.Format(Messages.DescriptionReminder, Markdown.InlineCode(reminder.Text)))
- .AppendBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage, $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}"));
+ .AppendBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage,
+ $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}"));
var embed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.Reminder, user.GetTag()), user)
@@ -240,10 +248,10 @@ public sealed partial class MemberUpdateService : BackgroundService
reminder.ChannelId.ToSnowflake(), Mention.User(user), embedResult: embed, ct: ct);
if (!messageResult.IsSuccess)
{
- return messageResult;
+ return ResultExtensions.FromError(messageResult);
}
data.Reminders.Remove(reminder);
- return Result.FromSuccess();
+ return Result.Success;
}
}
diff --git a/src/Services/Update/ScheduledEventUpdateService.cs b/src/Services/Update/ScheduledEventUpdateService.cs
index ac5c109..8168fc1 100644
--- a/src/Services/Update/ScheduledEventUpdateService.cs
+++ b/src/Services/Update/ScheduledEventUpdateService.cs
@@ -53,7 +53,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService
var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct);
if (!eventsResult.IsDefined(out var events))
{
- return Result.FromError(eventsResult);
+ return ResultExtensions.FromError(eventsResult);
}
SyncScheduledEvents(data, events);
@@ -147,7 +147,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService
|| eventData.EarlyNotificationSent
|| DateTimeOffset.UtcNow < scheduledEvent.ScheduledStartTime - offset)
{
- return Result.FromSuccess();
+ return Result.Success;
}
var sendResult = await SendEarlyEventNotificationAsync(scheduledEvent, data, ct);
@@ -182,7 +182,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService
{
if (GuildSettings.EventNotificationChannel.Get(settings).Empty())
{
- return Result.FromSuccess();
+ return Result.Success;
}
if (!scheduledEvent.Creator.IsDefined(out var creator))
@@ -204,7 +204,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService
if (!embedDescriptionResult.IsDefined(out var embedDescription))
{
- return Result.FromError(embedDescriptionResult);
+ return ResultExtensions.FromError(embedDescriptionResult);
}
var embed = new EmbedBuilder()
@@ -283,7 +283,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService
if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty())
{
- return Result.FromSuccess();
+ return Result.Success;
}
var embedDescriptionResult = scheduledEvent.EntityType switch
@@ -298,12 +298,12 @@ public sealed class ScheduledEventUpdateService : BackgroundService
scheduledEvent, data, ct);
if (!contentResult.IsDefined(out var content))
{
- return Result.FromError(contentResult);
+ return ResultExtensions.FromError(contentResult);
}
if (!embedDescriptionResult.IsDefined(out var embedDescription))
{
- return Result.FromError(embedDescriptionResult);
+ return ResultExtensions.FromError(embedDescriptionResult);
}
var startedEmbed = new EmbedBuilder()
@@ -324,7 +324,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService
if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty())
{
data.ScheduledEvents.Remove(eventData.Id);
- return Result.FromSuccess();
+ return Result.Success;
}
var completedEmbed = new EmbedBuilder()
@@ -356,7 +356,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService
if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty())
{
data.ScheduledEvents.Remove(eventData.Id);
- return Result.FromSuccess();
+ return Result.Success;
}
var embed = new EmbedBuilder()
@@ -409,14 +409,14 @@ public sealed class ScheduledEventUpdateService : BackgroundService
{
if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty())
{
- return Result.FromSuccess();
+ return Result.Success;
}
var contentResult = await _utility.GetEventNotificationMentions(
scheduledEvent, data, ct);
if (!contentResult.IsDefined(out var content))
{
- return Result.FromError(contentResult);
+ return ResultExtensions.FromError(contentResult);
}
var earlyResult = new EmbedBuilder()