1
0
Fork 1
mirror of https://github.com/TeamOctolings/Octobot.git synced 2025-01-31 00:19:00 +03:00

i think this should work

This commit is contained in:
Macintxsh 2024-06-02 09:56:12 +05:00
commit 006f0888de
Signed by: mctaylors
GPG key ID: 4EEF4F949A266EE2
68 changed files with 617 additions and 627 deletions

View file

@ -15,6 +15,10 @@ updates:
labels: labels:
- "type: change" - "type: change"
- "area: build/ci" - "area: build/ci"
# For all packages, ignore all patch updates
ignore:
- dependency-name: "*"
update-types: [ "version-update:semver-patch" ]
- package-ecosystem: "nuget" # See documentation for possible values - package-ecosystem: "nuget" # See documentation for possible values
directory: "/" # Location of package manifests directory: "/" # Location of package manifests
@ -30,3 +34,7 @@ updates:
remora: remora:
patterns: patterns:
- "Remora.Discord.*" - "Remora.Discord.*"
# For all packages, ignore all patch updates
ignore:
- dependency-name: "*"
update-types: [ "version-update:semver-patch" ]

View file

@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: ReSharper CLI InspectCode - name: ReSharper CLI InspectCode
uses: muno92/resharper_inspectcode@1.11.8 uses: muno92/resharper_inspectcode@1.11.10
with: with:
solutionPath: ./Octobot.sln solutionPath: ./Octobot.sln
ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement, ConvertToPrimaryConstructor ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement, ConvertToPrimaryConstructor

View file

@ -1,6 +1,6 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octobot", "Octobot.csproj", "{9CA7A44F-167C-46D4-923D-88CE71044144}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamOctolings.Octobot", "TeamOctolings.Octobot\TeamOctolings.Octobot.csproj", "{A1679BA2-3A36-4D98-80C0-EEE771398FBD}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -8,9 +8,9 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9CA7A44F-167C-46D4-923D-88CE71044144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9CA7A44F-167C-46D4-923D-88CE71044144}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9CA7A44F-167C-46D4-923D-88CE71044144}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9CA7A44F-167C-46D4-923D-88CE71044144}.Release|Any CPU.Build.0 = Release|Any CPU {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -1,4 +1,4 @@
namespace Octobot.Attributes; namespace TeamOctolings.Octobot.Attributes;
/// <summary> /// <summary>
/// Any property marked with <see cref="StaticCallersOnlyAttribute"/> should only be accessed by static methods. /// Any property marked with <see cref="StaticCallersOnlyAttribute"/> should only be accessed by static methods.

View file

@ -1,4 +1,4 @@
namespace Octobot; namespace TeamOctolings.Octobot;
public static class BuildInfo public static class BuildInfo
{ {

View file

@ -1,6 +1,6 @@
using System.Drawing; using System.Drawing;
namespace Octobot; namespace TeamOctolings.Octobot;
/// <summary> /// <summary>
/// Contains all colors used in embeds. /// Contains all colors used in embeds.

View file

@ -1,9 +1,6 @@
using System.ComponentModel; using System.ComponentModel;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -18,14 +15,17 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles the command to show information about this bot: /about. /// Handles the command to show information about this bot: /about.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class AboutCommandGroup : CommandGroup public sealed class AboutCommandGroup : CommandGroup
{ {
private static readonly (string Username, Snowflake Id)[] Developers = private static readonly (string Username, Snowflake Id)[] Developers =
[ [
@ -36,9 +36,9 @@ public class AboutCommandGroup : CommandGroup
private readonly ICommandContext _context; private readonly ICommandContext _context;
private readonly IFeedbackService _feedback; private readonly IFeedbackService _feedback;
private readonly IDiscordRestGuildAPI _guildApi;
private readonly GuildDataService _guildData; private readonly GuildDataService _guildData;
private readonly IDiscordRestUserAPI _userApi; private readonly IDiscordRestUserAPI _userApi;
private readonly IDiscordRestGuildAPI _guildApi;
public AboutCommandGroup( public AboutCommandGroup(
ICommandContext context, GuildDataService guildData, ICommandContext context, GuildDataService guildData,

View file

@ -2,11 +2,6 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Parsers;
using Octobot.Services;
using Octobot.Services.Update;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -19,14 +14,19 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Parsers;
using TeamOctolings.Octobot.Services;
using TeamOctolings.Octobot.Services.Update;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles commands related to ban management: /ban and /unban. /// Handles commands related to ban management: /ban and /unban.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class BanCommandGroup : CommandGroup public sealed class BanCommandGroup : CommandGroup
{ {
private readonly AccessControlService _access; private readonly AccessControlService _access;
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;

View file

@ -1,9 +1,6 @@
using System.ComponentModel; using System.ComponentModel;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -16,14 +13,17 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles the command to clear messages in a channel: /clear. /// Handles the command to clear messages in a channel: /clear.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class ClearCommandGroup : CommandGroup public sealed class ClearCommandGroup : CommandGroup
{ {
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
private readonly ICommandContext _context; private readonly ICommandContext _context;
@ -64,6 +64,7 @@ public class ClearCommandGroup : CommandGroup
public async Task<Result> ExecuteClear( public async Task<Result> ExecuteClear(
[Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)] [Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)]
int amount, int amount,
[Description("Ignore messages except from the specified author")]
IUser? author = null) IUser? author = null)
{ {
if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId))

View file

@ -1,6 +1,5 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Extensions;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects; using Remora.Discord.API.Objects;
@ -11,14 +10,15 @@ using Remora.Discord.Commands.Services;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Extensions;
namespace Octobot.Commands.Events; namespace TeamOctolings.Octobot.Commands.Events;
/// <summary> /// <summary>
/// Handles error logging for slash command groups. /// Handles error logging for slash command groups.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent public sealed class ErrorLoggingPostExecutionEvent : IPostExecutionEvent
{ {
private readonly IFeedbackService _feedback; private readonly IFeedbackService _feedback;
private readonly ILogger<ErrorLoggingPostExecutionEvent> _logger; private readonly ILogger<ErrorLoggingPostExecutionEvent> _logger;

View file

@ -1,17 +1,17 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Extensions;
using Remora.Discord.Commands.Contexts; using Remora.Discord.Commands.Contexts;
using Remora.Discord.Commands.Services; using Remora.Discord.Commands.Services;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Extensions;
namespace Octobot.Commands.Events; namespace TeamOctolings.Octobot.Commands.Events;
/// <summary> /// <summary>
/// Handles error logging for slash commands that couldn't be successfully prepared. /// Handles error logging for slash commands that couldn't be successfully prepared.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class LoggingPreparationErrorEvent : IPreparationErrorEvent public sealed class LoggingPreparationErrorEvent : IPreparationErrorEvent
{ {
private readonly ILogger<LoggingPreparationErrorEvent> _logger; private readonly ILogger<LoggingPreparationErrorEvent> _logger;

View file

@ -2,10 +2,6 @@ using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Parsers;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -17,14 +13,17 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles tool commands: /userinfo, /guildinfo, /random, /timestamp, /8ball. /// Handles info commands: /userinfo, /guildinfo.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class ToolsCommandGroup : CommandGroup public sealed class InfoCommandGroup : CommandGroup
{ {
private readonly ICommandContext _context; private readonly ICommandContext _context;
private readonly IFeedbackService _feedback; private readonly IFeedbackService _feedback;
@ -32,7 +31,7 @@ public class ToolsCommandGroup : CommandGroup
private readonly GuildDataService _guildData; private readonly GuildDataService _guildData;
private readonly IDiscordRestUserAPI _userApi; private readonly IDiscordRestUserAPI _userApi;
public ToolsCommandGroup( public InfoCommandGroup(
ICommandContext context, IFeedbackService feedback, ICommandContext context, IFeedbackService feedback,
GuildDataService guildData, IDiscordRestGuildAPI guildApi, GuildDataService guildData, IDiscordRestGuildAPI guildApi,
IDiscordRestUserAPI userApi) IDiscordRestUserAPI userApi)
@ -327,235 +326,4 @@ public class ToolsCommandGroup : CommandGroup
return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); return _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
} }
/// <summary>
/// A slash command that generates a random number using maximum and minimum numbers.
/// </summary>
/// <param name="first">The first number used for randomization.</param>
/// <param name="second">The second number used for randomization. Default value: 0</param>
/// <returns>
/// A feedback sending result which may or may not have succeeded.
/// </returns>
[Command("random")]
[DiscordDefaultDMPermission(false)]
[Description("Generates a random number")]
[UsedImplicitly]
public async Task<Result> ExecuteRandomAsync(
[Description("First number")] long first,
[Description("Second number (Default: 0)")]
long? second = null)
{
if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId))
{
return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
Messages.Culture = GuildSettings.Language.Get(data.Settings);
return await SendRandomNumberAsync(first, second, executor, CancellationToken);
}
private Task<Result> SendRandomNumberAsync(long first, long? secondNullable,
IUser executor, CancellationToken ct)
{
const long secondDefault = 0;
var second = secondNullable ?? secondDefault;
var min = Math.Min(first, second);
var max = Math.Max(first, second);
var i = Random.Shared.NextInt64(min, max + 1);
var description = new StringBuilder().Append("# ").Append(i);
description.AppendLine().AppendBulletPoint(string.Format(
Messages.RandomMin, Markdown.InlineCode(min.ToString())));
if (secondNullable is null && first >= secondDefault)
{
description.Append(' ').Append(Messages.Default);
}
description.AppendLine().AppendBulletPoint(string.Format(
Messages.RandomMax, Markdown.InlineCode(max.ToString())));
if (secondNullable is null && first < secondDefault)
{
description.Append(' ').Append(Messages.Default);
}
var embedColor = ColorsList.Blue;
if (secondNullable is not null && min == max)
{
description.AppendLine().Append(Markdown.Italicise(Messages.RandomMinMaxSame));
embedColor = ColorsList.Red;
}
var embed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.RandomTitle, executor.GetTag()), executor)
.WithDescription(description.ToString())
.WithColour(embedColor)
.Build();
return _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
}
private static readonly TimestampStyle[] AllStyles =
[
TimestampStyle.ShortDate,
TimestampStyle.LongDate,
TimestampStyle.ShortTime,
TimestampStyle.LongTime,
TimestampStyle.ShortDateTime,
TimestampStyle.LongDateTime,
TimestampStyle.RelativeTime
];
/// <summary>
/// A slash command that shows the current timestamp with an optional offset in all styles supported by Discord.
/// </summary>
/// <param name="stringOffset">The offset for the current timestamp.</param>
/// <returns>
/// A feedback sending result which may or may not have succeeded.
/// </returns>
[Command("timestamp")]
[DiscordDefaultDMPermission(false)]
[Description("Shows a timestamp in all styles")]
[UsedImplicitly]
public async Task<Result> ExecuteTimestampAsync(
[Description("Offset from current time")] [Option("offset")]
string? stringOffset = null)
{
if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId))
{
return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
}
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
Messages.Culture = GuildSettings.Language.Get(data.Settings);
if (stringOffset is null)
{
return await SendTimestampAsync(null, executor, CancellationToken);
}
var parseResult = TimeSpanParser.TryParse(stringOffset);
if (!parseResult.IsDefined(out var offset))
{
var failedEmbed = new EmbedBuilder()
.WithSmallTitle(Messages.InvalidTimeSpan, bot)
.WithDescription(Messages.TimeSpanExample)
.WithColour(ColorsList.Red)
.Build();
return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken);
}
return await SendTimestampAsync(offset, executor, CancellationToken);
}
private Task<Result> SendTimestampAsync(TimeSpan? offset, IUser executor, CancellationToken ct)
{
var timestamp = DateTimeOffset.UtcNow.Add(offset ?? TimeSpan.Zero).ToUnixTimeSeconds();
var description = new StringBuilder().Append("# ").AppendLine(timestamp.ToString());
if (offset is not null)
{
description.AppendLine(string.Format(
Messages.TimestampOffset, Markdown.InlineCode(offset.ToString() ?? string.Empty))).AppendLine();
}
foreach (var markdownTimestamp in AllStyles.Select(style => Markdown.Timestamp(timestamp, style)))
{
description.AppendBulletPoint(Markdown.InlineCode(markdownTimestamp))
.Append(" → ").AppendLine(markdownTimestamp);
}
var embed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.TimestampTitle, executor.GetTag()), executor)
.WithDescription(description.ToString())
.WithColour(ColorsList.Blue)
.Build();
return _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
}
/// <summary>
/// A slash command that shows a random answer from the Magic 8-Ball.
/// </summary>
/// <param name="question">Unused input.</param>
/// <remarks>
/// The 8-Ball answers were taken from <a href="https://en.wikipedia.org/wiki/Magic_8_Ball#Possible_answers">Wikipedia</a>.
/// </remarks>
/// <returns>
/// A feedback sending result which may or may not have succeeded.
/// </returns>
[Command("8ball")]
[DiscordDefaultDMPermission(false)]
[Description("Ask the Magic 8-Ball a question")]
[UsedImplicitly]
public async Task<Result> ExecuteEightBallAsync(
// let the user think he's actually asking the ball a question
[Description("Question to ask")] string question)
{
if (!_context.TryGetContextIDs(out var guildId, out _, out _))
{
return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
}
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
return ResultExtensions.FromError(botResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
Messages.Culture = GuildSettings.Language.Get(data.Settings);
return await AnswerEightBallAsync(bot, CancellationToken);
}
private static readonly string[] AnswerTypes =
[
"Positive", "Questionable", "Neutral", "Negative"
];
private Task<Result> AnswerEightBallAsync(IUser bot, CancellationToken ct)
{
var typeNumber = Random.Shared.Next(0, 4);
var embedColor = typeNumber switch
{
0 => ColorsList.Blue,
1 => ColorsList.Green,
2 => ColorsList.Yellow,
3 => ColorsList.Red,
_ => throw new ArgumentOutOfRangeException(null, nameof(typeNumber))
};
var answer = $"EightBall{AnswerTypes[typeNumber]}{Random.Shared.Next(1, 6)}".Localized();
var embed = new EmbedBuilder().WithSmallTitle(answer, bot)
.WithColour(embedColor)
.Build();
return _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
}
} }

View file

@ -1,9 +1,6 @@
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -15,14 +12,17 @@ using Remora.Discord.Commands.Feedback.Services;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles the command to kick members of a guild: /kick. /// Handles the command to kick members of a guild: /kick.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class KickCommandGroup : CommandGroup public sealed class KickCommandGroup : CommandGroup
{ {
private readonly AccessControlService _access; private readonly AccessControlService _access;
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;

View file

@ -2,11 +2,6 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Parsers;
using Octobot.Services;
using Octobot.Services.Update;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -19,14 +14,19 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Parsers;
using TeamOctolings.Octobot.Services;
using TeamOctolings.Octobot.Services.Update;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles commands related to mute management: /mute and /unmute. /// Handles commands related to mute management: /mute and /unmute.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class MuteCommandGroup : CommandGroup public sealed class MuteCommandGroup : CommandGroup
{ {
private readonly AccessControlService _access; private readonly AccessControlService _access;
private readonly ICommandContext _context; private readonly ICommandContext _context;

View file

@ -1,8 +1,5 @@
using System.ComponentModel; using System.ComponentModel;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -15,14 +12,17 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway; using Remora.Discord.Gateway;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles the command to get the time taken for the gateway to respond to the last heartbeat: /ping /// Handles the command to get the time taken for the gateway to respond to the last heartbeat: /ping
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class PingCommandGroup : CommandGroup public sealed class PingCommandGroup : CommandGroup
{ {
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
private readonly DiscordGatewayClient _client; private readonly DiscordGatewayClient _client;

View file

@ -2,9 +2,6 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -17,21 +14,24 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using Octobot.Parsers; using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Parsers;
using TeamOctolings.Octobot.Services;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles commands to manage reminders: /remind, /listremind, /delremind /// Handles commands to manage reminders: /remind, /listremind, /delremind
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class RemindCommandGroup : CommandGroup public sealed class RemindCommandGroup : CommandGroup
{ {
private readonly IInteractionCommandContext _context; private readonly IInteractionCommandContext _context;
private readonly IFeedbackService _feedback; private readonly IFeedbackService _feedback;
private readonly GuildDataService _guildData; private readonly GuildDataService _guildData;
private readonly IDiscordRestUserAPI _userApi;
private readonly IDiscordRestInteractionAPI _interactionApi; private readonly IDiscordRestInteractionAPI _interactionApi;
private readonly IDiscordRestUserAPI _userApi;
public RemindCommandGroup( public RemindCommandGroup(
IInteractionCommandContext context, GuildDataService guildData, IFeedbackService feedback, IInteractionCommandContext context, GuildDataService guildData, IFeedbackService feedback,

View file

@ -3,10 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Text; using System.Text;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Data.Options;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -19,23 +15,27 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Data.Options;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
/// <summary> /// <summary>
/// Handles the commands to list and modify per-guild settings: /settings and /settings list. /// Handles the commands to list and modify per-guild settings: /settings and /settings list.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class SettingsCommandGroup : CommandGroup public sealed class SettingsCommandGroup : CommandGroup
{ {
/// <summary> /// <summary>
/// Represents all options as an array of objects implementing <see cref="IOption" />. /// Represents all options as an array of objects implementing <see cref="IGuildOption" />.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// WARNING: If you update this array in any way, you must also update <see cref="AllOptionsEnum" /> and make sure /// WARNING: If you update this array in any way, you must also update <see cref="AllOptionsEnum" /> and make sure
/// that the orders match. /// that the orders match.
/// </remarks> /// </remarks>
private static readonly IOption[] AllOptions = private static readonly IGuildOption[] AllOptions =
[ [
GuildSettings.Language, GuildSettings.Language,
GuildSettings.WarnPunishment, GuildSettings.WarnPunishment,
@ -202,7 +202,7 @@ public class SettingsCommandGroup : CommandGroup
} }
private async Task<Result> EditSettingAsync( private async Task<Result> EditSettingAsync(
IOption option, string value, GuildData data, Snowflake channelId, IUser executor, IUser bot, IGuildOption option, string value, GuildData data, Snowflake channelId, IUser executor, IUser bot,
CancellationToken ct = default) CancellationToken ct = default)
{ {
var setResult = option.Set(data.Settings, value); var setResult = option.Set(data.Settings, value);
@ -273,7 +273,7 @@ public class SettingsCommandGroup : CommandGroup
} }
private async Task<Result> ResetSingleSettingAsync(JsonNode cfg, IUser bot, private async Task<Result> ResetSingleSettingAsync(JsonNode cfg, IUser bot,
IOption option, CancellationToken ct = default) IGuildOption option, CancellationToken ct = default)
{ {
var resetResult = option.Reset(cfg); var resetResult = option.Reset(cfg);
if (!resetResult.IsSuccess) if (!resetResult.IsSuccess)

View file

@ -0,0 +1,272 @@
using System.ComponentModel;
using System.Text;
using JetBrains.Annotations;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.Commands.Attributes;
using Remora.Discord.Commands.Contexts;
using Remora.Discord.Commands.Feedback.Services;
using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting;
using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Parsers;
using TeamOctolings.Octobot.Services;
namespace TeamOctolings.Octobot.Commands;
/// <summary>
/// Handles tool commands: /random, /timestamp, /8ball.
/// </summary>
[UsedImplicitly]
public sealed class ToolsCommandGroup : CommandGroup
{
private static readonly TimestampStyle[] AllStyles =
[
TimestampStyle.ShortDate,
TimestampStyle.LongDate,
TimestampStyle.ShortTime,
TimestampStyle.LongTime,
TimestampStyle.ShortDateTime,
TimestampStyle.LongDateTime,
TimestampStyle.RelativeTime
];
private static readonly string[] AnswerTypes =
[
"Positive", "Questionable", "Neutral", "Negative"
];
private readonly ICommandContext _context;
private readonly IFeedbackService _feedback;
private readonly GuildDataService _guildData;
private readonly IDiscordRestUserAPI _userApi;
public ToolsCommandGroup(
ICommandContext context, IFeedbackService feedback,
GuildDataService guildData, IDiscordRestUserAPI userApi)
{
_context = context;
_guildData = guildData;
_feedback = feedback;
_userApi = userApi;
}
/// <summary>
/// A slash command that generates a random number using maximum and minimum numbers.
/// </summary>
/// <param name="first">The first number used for randomization.</param>
/// <param name="second">The second number used for randomization. Default value: 0</param>
/// <returns>
/// A feedback sending result which may or may not have succeeded.
/// </returns>
[Command("random")]
[DiscordDefaultDMPermission(false)]
[Description("Generates a random number")]
[UsedImplicitly]
public async Task<Result> ExecuteRandomAsync(
[Description("First number")] long first,
[Description("Second number (Default: 0)")]
long? second = null)
{
if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId))
{
return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
Messages.Culture = GuildSettings.Language.Get(data.Settings);
return await SendRandomNumberAsync(first, second, executor, CancellationToken);
}
private Task<Result> SendRandomNumberAsync(long first, long? secondNullable,
IUser executor, CancellationToken ct)
{
const long secondDefault = 0;
var second = secondNullable ?? secondDefault;
var min = Math.Min(first, second);
var max = Math.Max(first, second);
var i = Random.Shared.NextInt64(min, max + 1);
var description = new StringBuilder().Append("# ").Append(i);
description.AppendLine().AppendBulletPoint(string.Format(
Messages.RandomMin, Markdown.InlineCode(min.ToString())));
if (secondNullable is null && first >= secondDefault)
{
description.Append(' ').Append(Messages.Default);
}
description.AppendLine().AppendBulletPoint(string.Format(
Messages.RandomMax, Markdown.InlineCode(max.ToString())));
if (secondNullable is null && first < secondDefault)
{
description.Append(' ').Append(Messages.Default);
}
var embedColor = ColorsList.Blue;
if (secondNullable is not null && min == max)
{
description.AppendLine().Append(Markdown.Italicise(Messages.RandomMinMaxSame));
embedColor = ColorsList.Red;
}
var embed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.RandomTitle, executor.GetTag()), executor)
.WithDescription(description.ToString())
.WithColour(embedColor)
.Build();
return _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
}
/// <summary>
/// A slash command that shows the current timestamp with an optional offset in all styles supported by Discord.
/// </summary>
/// <param name="stringOffset">The offset for the current timestamp.</param>
/// <returns>
/// A feedback sending result which may or may not have succeeded.
/// </returns>
[Command("timestamp")]
[DiscordDefaultDMPermission(false)]
[Description("Shows a timestamp in all styles")]
[UsedImplicitly]
public async Task<Result> ExecuteTimestampAsync(
[Description("Offset from current time")] [Option("offset")]
string? stringOffset = null)
{
if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId))
{
return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
}
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
return ResultExtensions.FromError(botResult);
}
var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
if (!executorResult.IsDefined(out var executor))
{
return ResultExtensions.FromError(executorResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
Messages.Culture = GuildSettings.Language.Get(data.Settings);
if (stringOffset is null)
{
return await SendTimestampAsync(null, executor, CancellationToken);
}
var parseResult = TimeSpanParser.TryParse(stringOffset);
if (!parseResult.IsDefined(out var offset))
{
var failedEmbed = new EmbedBuilder()
.WithSmallTitle(Messages.InvalidTimeSpan, bot)
.WithDescription(Messages.TimeSpanExample)
.WithColour(ColorsList.Red)
.Build();
return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken);
}
return await SendTimestampAsync(offset, executor, CancellationToken);
}
private Task<Result> SendTimestampAsync(TimeSpan? offset, IUser executor, CancellationToken ct)
{
var timestamp = DateTimeOffset.UtcNow.Add(offset ?? TimeSpan.Zero).ToUnixTimeSeconds();
var description = new StringBuilder().Append("# ").AppendLine(timestamp.ToString());
if (offset is not null)
{
description.AppendLine(string.Format(
Messages.TimestampOffset, Markdown.InlineCode(offset.ToString() ?? string.Empty))).AppendLine();
}
foreach (var markdownTimestamp in AllStyles.Select(style => Markdown.Timestamp(timestamp, style)))
{
description.AppendBulletPoint(Markdown.InlineCode(markdownTimestamp))
.Append(" → ").AppendLine(markdownTimestamp);
}
var embed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.TimestampTitle, executor.GetTag()), executor)
.WithDescription(description.ToString())
.WithColour(ColorsList.Blue)
.Build();
return _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
}
/// <summary>
/// A slash command that shows a random answer from the Magic 8-Ball.
/// </summary>
/// <param name="question">Unused input.</param>
/// <remarks>
/// The 8-Ball answers were taken from <a href="https://en.wikipedia.org/wiki/Magic_8_Ball#Possible_answers">Wikipedia</a>.
/// </remarks>
/// <returns>
/// A feedback sending result which may or may not have succeeded.
/// </returns>
[Command("8ball")]
[DiscordDefaultDMPermission(false)]
[Description("Ask the Magic 8-Ball a question")]
[UsedImplicitly]
public async Task<Result> ExecuteEightBallAsync(
// let the user think he's actually asking the ball a question
[Description("Question to ask")] string question)
{
if (!_context.TryGetContextIDs(out var guildId, out _, out _))
{
return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
}
var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!botResult.IsDefined(out var bot))
{
return ResultExtensions.FromError(botResult);
}
var data = await _guildData.GetData(guildId, CancellationToken);
Messages.Culture = GuildSettings.Language.Get(data.Settings);
return await AnswerEightBallAsync(bot, CancellationToken);
}
private Task<Result> AnswerEightBallAsync(IUser bot, CancellationToken ct)
{
var typeNumber = Random.Shared.Next(0, 4);
var embedColor = typeNumber switch
{
0 => ColorsList.Blue,
1 => ColorsList.Green,
2 => ColorsList.Yellow,
3 => ColorsList.Red,
_ => throw new ArgumentOutOfRangeException(null, nameof(typeNumber))
};
var answer = $"EightBall{AnswerTypes[typeNumber]}{Random.Shared.Next(1, 6)}".Localized();
var embed = new EmbedBuilder().WithSmallTitle(answer, bot)
.WithColour(embedColor)
.Build();
return _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
}
}

View file

@ -4,9 +4,6 @@ using System.ComponentModel.DataAnnotations;
using System.Text; using System.Text;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -19,9 +16,12 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
using static System.DateTimeOffset; using static System.DateTimeOffset;
namespace Octobot.Commands; namespace TeamOctolings.Octobot.Commands;
[UsedImplicitly] [UsedImplicitly]
public class WarnCommandGroup : CommandGroup public class WarnCommandGroup : CommandGroup

View file

@ -1,7 +1,7 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Remora.Rest.Core; using Remora.Rest.Core;
namespace Octobot.Data; namespace TeamOctolings.Octobot.Data;
/// <summary> /// <summary>
/// Stores information about a guild. This information is not accessible via the Discord API. /// Stores information about a guild. This information is not accessible via the Discord API.

View file

@ -1,8 +1,8 @@
using Octobot.Data.Options;
using Octobot.Responders;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using TeamOctolings.Octobot.Data.Options;
using TeamOctolings.Octobot.Responders;
namespace Octobot.Data; namespace TeamOctolings.Octobot.Data;
/// <summary> /// <summary>
/// Contains all per-guild settings that can be set by a member /// Contains all per-guild settings that can be set by a member
@ -24,7 +24,7 @@ public static class GuildSettings
/// </list> /// </list>
/// </remarks> /// </remarks>
/// <seealso cref="GuildMemberJoinedResponder" /> /// <seealso cref="GuildMemberJoinedResponder" />
public static readonly Option<string> WelcomeMessage = new("WelcomeMessage", "default"); public static readonly GuildOption<string> WelcomeMessage = new("WelcomeMessage", "default");
/// <summary> /// <summary>
/// Controls what message should be sent in <see cref="PublicFeedbackChannel" /> when a member leaves the guild. /// Controls what message should be sent in <see cref="PublicFeedbackChannel" /> when a member leaves the guild.
@ -36,7 +36,7 @@ public static class GuildSettings
/// </list> /// </list>
/// </remarks> /// </remarks>
/// <seealso cref="GuildMemberLeftResponder" /> /// <seealso cref="GuildMemberLeftResponder" />
public static readonly Option<string> LeaveMessage = new("LeaveMessage", "default"); public static readonly GuildOption<string> LeaveMessage = new("LeaveMessage", "default");
/// <summary> /// <summary>
/// Controls whether or not the <see cref="Messages.Ready" /> message should be sent /// Controls whether or not the <see cref="Messages.Ready" /> message should be sent

View file

@ -1,4 +1,4 @@
namespace Octobot.Data; namespace TeamOctolings.Octobot.Data;
/// <summary> /// <summary>
/// Stores information about a member /// Stores information about a member

View file

@ -1,7 +1,7 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Commands; using TeamOctolings.Octobot.Commands;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
/// <summary> /// <summary>
/// Represents all options as enums. /// Represents all options as enums.

View file

@ -1,9 +1,9 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Remora.Results; using Remora.Results;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
public sealed class BoolOption : Option<bool> public sealed class BoolOption : GuildOption<bool>
{ {
public BoolOption(string name, bool defaultValue) : base(name, defaultValue) { } public BoolOption(string name, bool defaultValue) : base(name, defaultValue) { }

View file

@ -2,18 +2,18 @@ using System.Text.Json.Nodes;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Results; using Remora.Results;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
/// <summary> /// <summary>
/// Represents an per-guild option. /// Represents a per-guild option.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the option.</typeparam> /// <typeparam name="T">The type of the option.</typeparam>
public class Option<T> : IOption public class GuildOption<T> : IGuildOption
where T : notnull where T : notnull
{ {
protected readonly T DefaultValue; protected readonly T DefaultValue;
public Option(string name, T defaultValue) public GuildOption(string name, T defaultValue)
{ {
Name = name; Name = name;
DefaultValue = defaultValue; DefaultValue = defaultValue;

View file

@ -1,9 +1,9 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Remora.Results; using Remora.Results;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
public interface IOption public interface IGuildOption
{ {
string Name { get; } string Name { get; }
string Display(JsonNode settings); string Display(JsonNode settings);

View file

@ -1,9 +1,9 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Remora.Results; using Remora.Results;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
public sealed class IntOption : Option<int> public sealed class IntOption : GuildOption<int>
{ {
public IntOption(string name, int defaultValue) : base(name, defaultValue) { } public IntOption(string name, int defaultValue) : base(name, defaultValue) { }

View file

@ -3,10 +3,10 @@ using System.Text.Json.Nodes;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Results; using Remora.Results;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
/// <inheritdoc /> /// <inheritdoc />
public sealed class LanguageOption : Option<CultureInfo> public sealed class LanguageOption : GuildOption<CultureInfo>
{ {
private static readonly Dictionary<string, CultureInfo> CultureInfoCache = new() private static readonly Dictionary<string, CultureInfo> CultureInfoCache = new()
{ {

View file

@ -1,10 +1,10 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Remora.Results; using Remora.Results;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
/// <inheritdoc /> /// <inheritdoc />
public sealed class PunishmentOption : Option<string> public sealed class PunishmentOption : GuildOption<string>
{ {
private static readonly List<string> AllowedValues = private static readonly List<string> AllowedValues =
[ [

View file

@ -1,13 +1,13 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Octobot.Extensions;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Extensions;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
public sealed partial class SnowflakeOption : Option<Snowflake> public sealed partial class SnowflakeOption : GuildOption<Snowflake>
{ {
public SnowflakeOption(string name) : base(name, 0UL.ToSnowflake()) { } public SnowflakeOption(string name) : base(name, 0UL.ToSnowflake()) { }

View file

@ -1,10 +1,10 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Octobot.Parsers;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Parsers;
namespace Octobot.Data.Options; namespace TeamOctolings.Octobot.Data.Options;
public sealed class TimeSpanOption : Option<TimeSpan> public sealed class TimeSpanOption : GuildOption<TimeSpan>
{ {
public TimeSpanOption(string name, TimeSpan defaultValue) : base(name, defaultValue) { } public TimeSpanOption(string name, TimeSpan defaultValue) : base(name, defaultValue) { }

View file

@ -1,4 +1,4 @@
namespace Octobot.Data; namespace TeamOctolings.Octobot.Data;
public struct Reminder public struct Reminder
{ {

View file

@ -1,7 +1,7 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
namespace Octobot.Data; namespace TeamOctolings.Octobot.Data;
/// <summary> /// <summary>
/// Stores information about scheduled events. This information is not provided by the Discord API. /// Stores information about scheduled events. This information is not provided by the Discord API.

View file

@ -1,4 +1,4 @@
namespace Octobot.Data; namespace TeamOctolings.Octobot.Data;
public struct Warn public struct Warn
{ {

View file

@ -5,18 +5,19 @@ using Remora.Discord.API.Objects;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class ChannelApiExtensions public static class ChannelApiExtensions
{ {
public static async Task<Result> CreateMessageWithEmbedResultAsync(this IDiscordRestChannelAPI channelApi, public static async Task<Result> CreateMessageWithEmbedResultAsync(this IDiscordRestChannelAPI channelApi,
Snowflake channelId, Optional<string> message = default, Optional<string> nonce = default, Snowflake channelId, Optional<string> message = default, Optional<string> nonce = default,
Optional<bool> isTextToSpeech = default, Optional<Result<Embed>> embedResult = default, Optional<bool> isTextToSpeech = default, Optional<Result<Embed>> embedResult = default,
Optional<IAllowedMentions> allowedMentions = default, Optional<IMessageReference> messageRefenence = default, Optional<IAllowedMentions> allowedMentions = default, Optional<IMessageReference> messageReference = default,
Optional<IReadOnlyList<IMessageComponent>> components = default, Optional<IReadOnlyList<IMessageComponent>> components = default,
Optional<IReadOnlyList<Snowflake>> stickerIds = default, Optional<IReadOnlyList<Snowflake>> stickerIds = default,
Optional<IReadOnlyList<OneOf<FileData, IPartialAttachment>>> attachments = default, Optional<IReadOnlyList<OneOf<FileData, IPartialAttachment>>> attachments = default,
Optional<MessageFlags> flags = default, CancellationToken ct = default) Optional<MessageFlags> flags = default, Optional<bool> enforceNonce = default,
Optional<IPollCreateRequest> poll = default, CancellationToken ct = default)
{ {
if (!embedResult.IsDefined() || !embedResult.Value.IsDefined(out var embed)) if (!embedResult.IsDefined() || !embedResult.Value.IsDefined(out var embed))
{ {
@ -24,6 +25,6 @@ public static class ChannelApiExtensions
} }
return (Result)await channelApi.CreateMessageAsync(channelId, message, nonce, isTextToSpeech, new[] { embed }, return (Result)await channelApi.CreateMessageAsync(channelId, message, nonce, isTextToSpeech, new[] { embed },
allowedMentions, messageRefenence, components, stickerIds, attachments, flags, ct); allowedMentions, messageReference, components, stickerIds, attachments, flags, enforceNonce, poll, ct);
} }
} }

View file

@ -1,6 +1,6 @@
using Remora.Results; using Remora.Results;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class CollectionExtensions public static class CollectionExtensions
{ {

View file

@ -2,7 +2,7 @@
using Remora.Discord.Commands.Extensions; using Remora.Discord.Commands.Extensions;
using Remora.Rest.Core; using Remora.Rest.Core;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class CommandContextExtensions public static class CommandContextExtensions
{ {

View file

@ -1,7 +1,7 @@
using System.Text; using System.Text;
using DiffPlex.DiffBuilder.Model; using DiffPlex.DiffBuilder.Model;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class DiffPaneModelExtensions public static class DiffPaneModelExtensions
{ {

View file

@ -4,7 +4,7 @@ using Remora.Discord.API.Objects;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Rest.Core; using Remora.Rest.Core;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class EmbedBuilderExtensions public static class EmbedBuilderExtensions
{ {

View file

@ -3,7 +3,7 @@ using Remora.Discord.Commands.Feedback.Messages;
using Remora.Discord.Commands.Feedback.Services; using Remora.Discord.Commands.Feedback.Services;
using Remora.Results; using Remora.Results;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class FeedbackServiceExtensions public static class FeedbackServiceExtensions
{ {

View file

@ -2,7 +2,7 @@
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class GuildScheduledEventExtensions public static class GuildScheduledEventExtensions
{ {

View file

@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Remora.Results; using Remora.Results;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class LoggerExtensions public static class LoggerExtensions
{ {

View file

@ -1,4 +1,4 @@
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class MarkdownExtensions public static class MarkdownExtensions
{ {

View file

@ -2,7 +2,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Remora.Results; using Remora.Results;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class ResultExtensions public static class ResultExtensions
{ {
@ -21,21 +21,25 @@ public static class ResultExtensions
return casted; return casted;
} }
[Conditional("DEBUG")]
private static void LogResultStackTrace(Result result) private static void LogResultStackTrace(Result result)
{ {
if (Octobot.StaticLogger is null || result.IsSuccess) if (result.IsSuccess)
{ {
return; return;
} }
Octobot.StaticLogger.LogError("{ErrorType}: {ErrorMessage}{NewLine}{StackTrace}", if (Utility.StaticLogger is null)
{
throw new InvalidOperationException();
}
Utility.StaticLogger.LogError("{ErrorType}: {ErrorMessage}{NewLine}{StackTrace}",
result.Error.GetType().FullName, result.Error.Message, Environment.NewLine, ConstructStackTrace()); result.Error.GetType().FullName, result.Error.Message, Environment.NewLine, ConstructStackTrace());
var inner = result.Inner; var inner = result.Inner;
while (inner is { IsSuccess: false }) while (inner is { IsSuccess: false })
{ {
Octobot.StaticLogger.LogError("Caused by: {ResultType}: {ResultMessage}", Utility.StaticLogger.LogError("Caused by: {ResultType}: {ResultMessage}",
inner.Error.GetType().FullName, inner.Error.Message); inner.Error.GetType().FullName, inner.Error.Message);
inner = inner.Inner; inner = inner.Inner;

View file

@ -1,6 +1,6 @@
using Remora.Rest.Core; using Remora.Rest.Core;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class SnowflakeExtensions public static class SnowflakeExtensions
{ {

View file

@ -1,6 +1,6 @@
using System.Text; using System.Text;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class StringBuilderExtensions public static class StringBuilderExtensions
{ {

View file

@ -1,7 +1,7 @@
using System.Net; using System.Net;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class StringExtensions public static class StringExtensions
{ {

View file

@ -1,7 +1,7 @@
using Remora.Discord.API; using Remora.Discord.API;
using Remora.Rest.Core; using Remora.Rest.Core;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class UInt64Extensions public static class UInt64Extensions
{ {

View file

@ -1,6 +1,6 @@
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
namespace Octobot.Extensions; namespace TeamOctolings.Octobot.Extensions;
public static class UserExtensions public static class UserExtensions
{ {

View file

@ -7,7 +7,10 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Octobot { namespace TeamOctolings.Octobot {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
@ -25,7 +28,7 @@ namespace Octobot {
internal static System.Resources.ResourceManager ResourceManager { internal static System.Resources.ResourceManager ResourceManager {
get { get {
if (object.Equals(null, resourceMan)) { if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Octobot.locale.Messages", typeof(Messages).Assembly); System.Resources.ResourceManager temp = new System.Resources.ResourceManager("TeamOctolings.Octobot.Messages", typeof(Messages).Assembly);
resourceMan = temp; resourceMan = temp;
} }
return resourceMan; return resourceMan;
@ -120,9 +123,9 @@ namespace Octobot {
} }
} }
internal static string SettingsLang { internal static string SettingsLanguage {
get { get {
return ResourceManager.GetString("SettingsLang", resourceCulture); return ResourceManager.GetString("SettingsLanguage", resourceCulture);
} }
} }
@ -204,18 +207,6 @@ namespace Octobot {
} }
} }
internal static string InvalidRole {
get {
return ResourceManager.GetString("InvalidRole", resourceCulture);
}
}
internal static string InvalidChannel {
get {
return ResourceManager.GetString("InvalidChannel", resourceCulture);
}
}
internal static string DurationRequiredForTimeOuts { internal static string DurationRequiredForTimeOuts {
get { get {
return ResourceManager.GetString("DurationRequiredForTimeOuts", resourceCulture); return ResourceManager.GetString("DurationRequiredForTimeOuts", resourceCulture);
@ -282,6 +273,12 @@ namespace Octobot {
} }
} }
internal static string MissingUser {
get {
return ResourceManager.GetString("MissingUser", resourceCulture);
}
}
internal static string UserCannotBanMembers { internal static string UserCannotBanMembers {
get { get {
return ResourceManager.GetString("UserCannotBanMembers", resourceCulture); return ResourceManager.GetString("UserCannotBanMembers", resourceCulture);
@ -300,9 +297,15 @@ namespace Octobot {
} }
} }
internal static string UserCannotModerateMembers { internal static string UserCannotMuteMembers {
get { get {
return ResourceManager.GetString("UserCannotModerateMembers", resourceCulture); return ResourceManager.GetString("UserCannotMuteMembers", resourceCulture);
}
}
internal static string UserCannotUnmuteMembers {
get {
return ResourceManager.GetString("UserCannotUnmuteMembers", resourceCulture);
} }
} }
@ -702,6 +705,12 @@ namespace Octobot {
} }
} }
internal static string SettingsRenameHoistedUsers {
get {
return ResourceManager.GetString("SettingsRenameHoistedUsers", resourceCulture);
}
}
internal static string Page { internal static string Page {
get { get {
return ResourceManager.GetString("Page", resourceCulture); return ResourceManager.GetString("Page", resourceCulture);
@ -798,21 +807,15 @@ namespace Octobot {
} }
} }
internal static string InformationAbout {
get {
return ResourceManager.GetString("InformationAbout", resourceCulture);
}
}
internal static string UserInfoDisplayName { internal static string UserInfoDisplayName {
get { get {
return ResourceManager.GetString("UserInfoDisplayName", resourceCulture); return ResourceManager.GetString("UserInfoDisplayName", resourceCulture);
} }
} }
internal static string UserInfoDiscordUserSince { internal static string InformationAbout {
get { get {
return ResourceManager.GetString("UserInfoDiscordUserSince", resourceCulture); return ResourceManager.GetString("InformationAbout", resourceCulture);
} }
} }
@ -822,6 +825,12 @@ namespace Octobot {
} }
} }
internal static string UserInfoDiscordUserSince {
get {
return ResourceManager.GetString("UserInfoDiscordUserSince", resourceCulture);
}
}
internal static string UserInfoBanned { internal static string UserInfoBanned {
get { get {
return ResourceManager.GetString("UserInfoBanned", resourceCulture); return ResourceManager.GetString("UserInfoBanned", resourceCulture);
@ -882,157 +891,122 @@ namespace Octobot {
} }
} }
internal static string RandomTitle internal static string RandomTitle {
{
get { get {
return ResourceManager.GetString("RandomTitle", resourceCulture); return ResourceManager.GetString("RandomTitle", resourceCulture);
} }
} }
internal static string RandomMinMaxSame internal static string RandomMinMaxSame {
{
get { get {
return ResourceManager.GetString("RandomMinMaxSame", resourceCulture); return ResourceManager.GetString("RandomMinMaxSame", resourceCulture);
} }
} }
internal static string RandomMax internal static string RandomMin {
{
get {
return ResourceManager.GetString("RandomMax", resourceCulture);
}
}
internal static string RandomMin
{
get { get {
return ResourceManager.GetString("RandomMin", resourceCulture); return ResourceManager.GetString("RandomMin", resourceCulture);
} }
} }
internal static string Default internal static string RandomMax {
{ get {
return ResourceManager.GetString("RandomMax", resourceCulture);
}
}
internal static string Default {
get { get {
return ResourceManager.GetString("Default", resourceCulture); return ResourceManager.GetString("Default", resourceCulture);
} }
} }
internal static string TimestampTitle internal static string TimestampTitle {
{ get {
get
{
return ResourceManager.GetString("TimestampTitle", resourceCulture); return ResourceManager.GetString("TimestampTitle", resourceCulture);
} }
} }
internal static string TimestampOffset internal static string TimestampOffset {
{ get {
get
{
return ResourceManager.GetString("TimestampOffset", resourceCulture); return ResourceManager.GetString("TimestampOffset", resourceCulture);
} }
} }
internal static string GuildInfoDescription internal static string GuildInfoDescription {
{ get {
get
{
return ResourceManager.GetString("GuildInfoDescription", resourceCulture); return ResourceManager.GetString("GuildInfoDescription", resourceCulture);
} }
} }
internal static string GuildInfoCreatedAt internal static string GuildInfoCreatedAt {
{ get {
get
{
return ResourceManager.GetString("GuildInfoCreatedAt", resourceCulture); return ResourceManager.GetString("GuildInfoCreatedAt", resourceCulture);
} }
} }
internal static string GuildInfoOwner internal static string GuildInfoOwner {
{ get {
get
{
return ResourceManager.GetString("GuildInfoOwner", resourceCulture); return ResourceManager.GetString("GuildInfoOwner", resourceCulture);
} }
} }
internal static string GuildInfoServerBoost internal static string GuildInfoServerBoost {
{ get {
get
{
return ResourceManager.GetString("GuildInfoServerBoost", resourceCulture); return ResourceManager.GetString("GuildInfoServerBoost", resourceCulture);
} }
} }
internal static string GuildInfoBoostTier internal static string GuildInfoBoostTier {
{ get {
get
{
return ResourceManager.GetString("GuildInfoBoostTier", resourceCulture); return ResourceManager.GetString("GuildInfoBoostTier", resourceCulture);
} }
} }
internal static string GuildInfoBoostCount internal static string GuildInfoBoostCount {
{ get {
get
{
return ResourceManager.GetString("GuildInfoBoostCount", resourceCulture); return ResourceManager.GetString("GuildInfoBoostCount", resourceCulture);
} }
} }
internal static string NoMessagesToClear internal static string NoMessagesToClear {
{ get {
get
{
return ResourceManager.GetString("NoMessagesToClear", resourceCulture); return ResourceManager.GetString("NoMessagesToClear", resourceCulture);
} }
} }
internal static string MessagesClearedFiltered internal static string MessagesClearedFiltered {
{ get {
get
{
return ResourceManager.GetString("MessagesClearedFiltered", resourceCulture); return ResourceManager.GetString("MessagesClearedFiltered", resourceCulture);
} }
} }
internal static string DataLoadFailedTitle internal static string DataLoadFailedTitle {
{ get {
get
{
return ResourceManager.GetString("DataLoadFailedTitle", resourceCulture); return ResourceManager.GetString("DataLoadFailedTitle", resourceCulture);
} }
} }
internal static string DataLoadFailedDescription internal static string DataLoadFailedDescription {
{ get {
get
{
return ResourceManager.GetString("DataLoadFailedDescription", resourceCulture); return ResourceManager.GetString("DataLoadFailedDescription", resourceCulture);
} }
} }
internal static string CommandExecutionFailed internal static string CommandExecutionFailed {
{ get {
get
{
return ResourceManager.GetString("CommandExecutionFailed", resourceCulture); return ResourceManager.GetString("CommandExecutionFailed", resourceCulture);
} }
} }
internal static string ContactDevelopers internal static string ContactDevelopers {
{ get {
get
{
return ResourceManager.GetString("ContactDevelopers", resourceCulture); return ResourceManager.GetString("ContactDevelopers", resourceCulture);
} }
} }
internal static string ButtonReportIssue internal static string ButtonReportIssue {
{ get {
get
{
return ResourceManager.GetString("ButtonReportIssue", resourceCulture); return ResourceManager.GetString("ButtonReportIssue", resourceCulture);
} }
} }
@ -1205,62 +1179,53 @@ namespace Octobot {
} }
} }
internal static string UserWarned internal static string UserWarned {
{
get { get {
return ResourceManager.GetString("UserWarned", resourceCulture); return ResourceManager.GetString("UserWarned", resourceCulture);
} }
} }
internal static string UserWarnsRemoved internal static string UserWarnsRemoved {
{
get { get {
return ResourceManager.GetString("UserWarnsRemoved", resourceCulture); return ResourceManager.GetString("UserWarnsRemoved", resourceCulture);
} }
} }
internal static string YouHaveBeenWarned internal static string YouHaveBeenWarned {
{
get { get {
return ResourceManager.GetString("YouHaveBeenWarned", resourceCulture); return ResourceManager.GetString("YouHaveBeenWarned", resourceCulture);
} }
} }
internal static string YourWarningsHaveBeenRevoked internal static string YourWarningsHaveBeenRevoked {
{
get { get {
return ResourceManager.GetString("YourWarningsHaveBeenRevoked", resourceCulture); return ResourceManager.GetString("YourWarningsHaveBeenRevoked", resourceCulture);
} }
} }
internal static string DescriptionWarns internal static string DescriptionWarns {
{
get { get {
return ResourceManager.GetString("DescriptionWarns", resourceCulture); return ResourceManager.GetString("DescriptionWarns", resourceCulture);
} }
} }
internal static string UserHasNoWarnings internal static string UserHasNoWarnings {
{
get { get {
return ResourceManager.GetString("UserHasNoWarnings", resourceCulture); return ResourceManager.GetString("UserHasNoWarnings", resourceCulture);
} }
} }
internal static string YouHaveNoWarnings internal static string YouHaveNoWarnings {
{
get { get {
return ResourceManager.GetString("YouHaveNoWarnings", resourceCulture); return ResourceManager.GetString("YouHaveNoWarnings", resourceCulture);
} }
} }
internal static string ReceivedTooManyWarnings internal static string ReceivedTooManyWarnings {
{
get { get {
return ResourceManager.GetString("ReceivedTooManyWarnings", resourceCulture); return ResourceManager.GetString("ReceivedTooManyWarnings", resourceCulture);
} }
} }
internal static string ButtonDirty { internal static string ButtonDirty {
get { get {
return ResourceManager.GetString("ButtonDirty", resourceCulture); return ResourceManager.GetString("ButtonDirty", resourceCulture);
@ -1273,82 +1238,74 @@ namespace Octobot {
} }
} }
internal static string ListTargetWarnsTitle internal static string SettingsModeratorRole {
{ get {
return ResourceManager.GetString("SettingsModeratorRole", resourceCulture);
}
}
internal static string ListTargetWarnsTitle {
get { get {
return ResourceManager.GetString("ListTargetWarnsTitle", resourceCulture); return ResourceManager.GetString("ListTargetWarnsTitle", resourceCulture);
} }
} }
internal static string ReceivedOn internal static string ReceivedOn {
{
get { get {
return ResourceManager.GetString("ReceivedOn", resourceCulture); return ResourceManager.GetString("ReceivedOn", resourceCulture);
} }
} }
internal static string UserWarnRemoved internal static string UserWarnRemoved {
{
get { get {
return ResourceManager.GetString("UserWarnRemoved", resourceCulture); return ResourceManager.GetString("UserWarnRemoved", resourceCulture);
} }
} }
internal static string YourWarningHasBeenRevoked internal static string YourWarningHasBeenRevoked {
{
get { get {
return ResourceManager.GetString("YourWarningHasBeenRevoked", resourceCulture); return ResourceManager.GetString("YourWarningHasBeenRevoked", resourceCulture);
} }
} }
internal static string WrongWarningNumberSelected internal static string WrongWarningNumberSelected {
{
get { get {
return ResourceManager.GetString("WrongWarningNumberSelected", resourceCulture); return ResourceManager.GetString("WrongWarningNumberSelected", resourceCulture);
} }
} }
internal static string ListExecutorWarnsTitle internal static string ListExecutorWarnsTitle {
{
get { get {
return ResourceManager.GetString("ListExecutorWarnsTitle", resourceCulture); return ResourceManager.GetString("ListExecutorWarnsTitle", resourceCulture);
} }
} }
internal static string DescriptionPunishmentType internal static string DescriptionPunishmentType {
{
get { get {
return ResourceManager.GetString("DescriptionPunishmentType", resourceCulture); return ResourceManager.GetString("DescriptionPunishmentType", resourceCulture);
} }
} }
internal static string WarnThresholdExceeded internal static string WarnThresholdExceeded {
{
get { get {
return ResourceManager.GetString("WarnThresholdExceeded", resourceCulture); return ResourceManager.GetString("WarnThresholdExceeded", resourceCulture);
} }
} }
internal static string WarnPunishmentDurationNotSet internal static string WarnPunishmentDurationNotSet {
{ get {
get
{
return ResourceManager.GetString("WarnPunishmentDurationNotSet", resourceCulture); return ResourceManager.GetString("WarnPunishmentDurationNotSet", resourceCulture);
} }
} }
internal static string WarnThresholdExceededDescription internal static string WarnThresholdExceededDescription {
{ get {
get
{
return ResourceManager.GetString("WarnThresholdExceededDescription", resourceCulture); return ResourceManager.GetString("WarnThresholdExceededDescription", resourceCulture);
} }
} }
internal static string InvalidWarnPunishment internal static string InvalidWarnPunishment {
{ get {
get
{
return ResourceManager.GetString("InvalidWarnPunishment", resourceCulture); return ResourceManager.GetString("InvalidWarnPunishment", resourceCulture);
} }
} }

View file

@ -4,7 +4,7 @@ using JetBrains.Annotations;
using Remora.Commands.Parsers; using Remora.Commands.Parsers;
using Remora.Results; using Remora.Results;
namespace Octobot.Parsers; namespace TeamOctolings.Octobot.Parsers;
/// <summary> /// <summary>
/// Parses <see cref="TimeSpan"/>s. /// Parses <see cref="TimeSpan"/>s.

View file

@ -2,13 +2,8 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Attributes;
using Octobot.Commands.Events;
using Octobot.Services;
using Octobot.Services.Update;
using Remora.Discord.API.Abstractions.Gateway.Commands; using Remora.Discord.API.Abstractions.Gateway.Commands;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Objects;
using Remora.Discord.Caching.Extensions; using Remora.Discord.Caching.Extensions;
using Remora.Discord.Caching.Services; using Remora.Discord.Caching.Services;
using Remora.Discord.Commands.Extensions; using Remora.Discord.Commands.Extensions;
@ -16,24 +11,20 @@ using Remora.Discord.Commands.Services;
using Remora.Discord.Extensions.Extensions; using Remora.Discord.Extensions.Extensions;
using Remora.Discord.Gateway; using Remora.Discord.Gateway;
using Remora.Discord.Hosting.Extensions; using Remora.Discord.Hosting.Extensions;
using Remora.Rest.Core;
using Serilog.Extensions.Logging; using Serilog.Extensions.Logging;
using TeamOctolings.Octobot.Commands.Events;
using TeamOctolings.Octobot.Services;
using TeamOctolings.Octobot.Services.Update;
namespace Octobot; namespace TeamOctolings.Octobot;
public sealed class Octobot public sealed class Program
{ {
public static readonly AllowedMentions NoMentions = new(
Array.Empty<MentionType>(), Array.Empty<Snowflake>(), Array.Empty<Snowflake>());
[StaticCallersOnly]
public static ILogger<Octobot>? StaticLogger { get; private set; }
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
var host = CreateHostBuilder(args).UseConsoleLifetime().Build(); var host = CreateHostBuilder(args).UseConsoleLifetime().Build();
var services = host.Services; var services = host.Services;
StaticLogger = services.GetRequiredService<ILogger<Octobot>>(); Utility.StaticLogger = services.GetRequiredService<ILogger<Program>>();
var slashService = services.GetRequiredService<SlashService>(); var slashService = services.GetRequiredService<SlashService>();
// Providing a guild ID to this call will result in command duplicates! // Providing a guild ID to this call will result in command duplicates!
@ -82,8 +73,8 @@ public sealed class Octobot
// Init // Init
.AddDiscordCaching() .AddDiscordCaching()
.AddDiscordCommands(true, false) .AddDiscordCommands(true, false)
.AddRespondersFromAssembly(typeof(Octobot).Assembly) .AddRespondersFromAssembly(typeof(Program).Assembly)
.AddCommandGroupsFromAssembly(typeof(Octobot).Assembly) .AddCommandGroupsFromAssembly(typeof(Program).Assembly)
// Slash command event handlers // Slash command event handlers
.AddPreparationErrorEvent<LoggingPreparationErrorEvent>() .AddPreparationErrorEvent<LoggingPreparationErrorEvent>()
.AddPostExecutionEvent<ErrorLoggingPostExecutionEvent>() .AddPostExecutionEvent<ErrorLoggingPostExecutionEvent>()

View file

@ -1,8 +1,5 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
@ -11,15 +8,18 @@ using Remora.Discord.API.Objects;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders; using Remora.Discord.Gateway.Responders;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Responders; namespace TeamOctolings.Octobot.Responders;
/// <summary> /// <summary>
/// Handles sending a <see cref="Ready" /> message to a guild that has just initialized if that guild /// Handles sending a <see cref="Ready" /> message to a guild that has just initialized if that guild
/// has <see cref="GuildSettings.ReceiveStartupMessages" /> enabled /// has <see cref="GuildSettings.ReceiveStartupMessages" /> enabled
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class GuildLoadedResponder : IResponder<IGuildCreate> public sealed class GuildLoadedResponder : IResponder<IGuildCreate>
{ {
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
private readonly GuildDataService _guildData; private readonly GuildDataService _guildData;

View file

@ -1,16 +1,16 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders; using Remora.Discord.Gateway.Responders;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Responders; namespace TeamOctolings.Octobot.Responders;
/// <summary> /// <summary>
/// Handles sending a guild's <see cref="GuildSettings.WelcomeMessage" /> if one is set. /// Handles sending a guild's <see cref="GuildSettings.WelcomeMessage" /> if one is set.
@ -18,7 +18,7 @@ namespace Octobot.Responders;
/// </summary> /// </summary>
/// <seealso cref="GuildSettings.WelcomeMessage" /> /// <seealso cref="GuildSettings.WelcomeMessage" />
[UsedImplicitly] [UsedImplicitly]
public class GuildMemberJoinedResponder : IResponder<IGuildMemberAdd> public sealed class GuildMemberJoinedResponder : IResponder<IGuildMemberAdd>
{ {
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
private readonly IDiscordRestGuildAPI _guildApi; private readonly IDiscordRestGuildAPI _guildApi;
@ -77,7 +77,7 @@ public class GuildMemberJoinedResponder : IResponder<IGuildMemberAdd>
return await _channelApi.CreateMessageWithEmbedResultAsync( return await _channelApi.CreateMessageWithEmbedResultAsync(
GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed,
allowedMentions: Octobot.NoMentions, ct: ct); allowedMentions: Utility.NoMentions, ct: ct);
} }
private async Task<Result> TryReturnRolesAsync( private async Task<Result> TryReturnRolesAsync(

View file

@ -1,21 +1,21 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders; using Remora.Discord.Gateway.Responders;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Responders; namespace TeamOctolings.Octobot.Responders;
/// <summary> /// <summary>
/// Handles sending a guild's <see cref="GuildSettings.LeaveMessage" /> if one is set. /// Handles sending a guild's <see cref="GuildSettings.LeaveMessage" /> if one is set.
/// </summary> /// </summary>
/// <seealso cref="GuildSettings.LeaveMessage" /> /// <seealso cref="GuildSettings.LeaveMessage" />
[UsedImplicitly] [UsedImplicitly]
public class GuildMemberLeftResponder : IResponder<IGuildMemberRemove> public sealed class GuildMemberLeftResponder : IResponder<IGuildMemberRemove>
{ {
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
private readonly IDiscordRestGuildAPI _guildApi; private readonly IDiscordRestGuildAPI _guildApi;
@ -67,6 +67,6 @@ public class GuildMemberLeftResponder : IResponder<IGuildMemberRemove>
return await _channelApi.CreateMessageWithEmbedResultAsync( return await _channelApi.CreateMessageWithEmbedResultAsync(
GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed,
allowedMentions: Octobot.NoMentions, ct: ct); allowedMentions: Utility.NoMentions, ct: ct);
} }
} }

View file

@ -1,18 +1,18 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Data;
using Octobot.Services;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.Gateway.Responders; using Remora.Discord.Gateway.Responders;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Services;
namespace Octobot.Responders; namespace TeamOctolings.Octobot.Responders;
/// <summary> /// <summary>
/// Handles removing guild ID from <see cref="GuildData" /> if the guild becomes unavailable. /// Handles removing guild ID from <see cref="GuildData" /> if the guild becomes unavailable.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class GuildUnloadedResponder : IResponder<IGuildDelete> public sealed class GuildUnloadedResponder : IResponder<IGuildDelete>
{ {
private readonly GuildDataService _guildData; private readonly GuildDataService _guildData;
private readonly ILogger<GuildUnloadedResponder> _logger; private readonly ILogger<GuildUnloadedResponder> _logger;

View file

@ -1,8 +1,5 @@
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
@ -10,15 +7,18 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Discord.Gateway.Responders; using Remora.Discord.Gateway.Responders;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Responders; namespace TeamOctolings.Octobot.Responders;
/// <summary> /// <summary>
/// Handles logging the contents of a deleted message and the user who deleted the message /// Handles logging the contents of a deleted message and the user who deleted the message
/// to a guild's <see cref="GuildSettings.PrivateFeedbackChannel" /> if one is set. /// to a guild's <see cref="GuildSettings.PrivateFeedbackChannel" /> if one is set.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class MessageDeletedResponder : IResponder<IMessageDelete> public sealed class MessageDeletedResponder : IResponder<IMessageDelete>
{ {
private readonly IDiscordRestAuditLogAPI _auditLogApi; private readonly IDiscordRestAuditLogAPI _auditLogApi;
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
@ -102,6 +102,6 @@ public class MessageDeletedResponder : IResponder<IMessageDelete>
return await _channelApi.CreateMessageWithEmbedResultAsync( return await _channelApi.CreateMessageWithEmbedResultAsync(
GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed,
allowedMentions: Octobot.NoMentions, ct: ct); allowedMentions: Utility.NoMentions, ct: ct);
} }
} }

View file

@ -1,9 +1,6 @@
using System.Text; using System.Text;
using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder;
using JetBrains.Annotations; using JetBrains.Annotations;
using Octobot.Data;
using Octobot.Extensions;
using Octobot.Services;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
@ -12,15 +9,18 @@ using Remora.Discord.Caching.Services;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders; using Remora.Discord.Gateway.Responders;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
using TeamOctolings.Octobot.Services;
namespace Octobot.Responders; namespace TeamOctolings.Octobot.Responders;
/// <summary> /// <summary>
/// Handles logging the difference between an edited message's old and new content /// Handles logging the difference between an edited message's old and new content
/// to a guild's <see cref="GuildSettings.PrivateFeedbackChannel" /> if one is set. /// to a guild's <see cref="GuildSettings.PrivateFeedbackChannel" /> if one is set.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class MessageEditedResponder : IResponder<IMessageUpdate> public sealed class MessageEditedResponder : IResponder<IMessageUpdate>
{ {
private readonly CacheService _cacheService; private readonly CacheService _cacheService;
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
@ -104,6 +104,6 @@ public class MessageEditedResponder : IResponder<IMessageUpdate>
return await _channelApi.CreateMessageWithEmbedResultAsync( return await _channelApi.CreateMessageWithEmbedResultAsync(
GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed,
allowedMentions: Octobot.NoMentions, ct: ct); allowedMentions: Utility.NoMentions, ct: ct);
} }
} }

View file

@ -5,13 +5,13 @@ using Remora.Discord.Gateway.Responders;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
namespace Octobot.Responders; namespace TeamOctolings.Octobot.Responders;
/// <summary> /// <summary>
/// Handles sending replies to easter egg messages. /// Handles sending replies to easter egg messages.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class MessageCreateResponder : IResponder<IMessageCreate> public sealed class MessageCreateResponder : IResponder<IMessageCreate>
{ {
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;

View file

@ -1,11 +1,11 @@
using Octobot.Data; using Remora.Discord.API.Abstractions.Objects;
using Octobot.Extensions;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
namespace Octobot.Services; namespace TeamOctolings.Octobot.Services;
public sealed class AccessControlService public sealed class AccessControlService
{ {
@ -20,18 +20,17 @@ public sealed class AccessControlService
_userApi = userApi; _userApi = userApi;
} }
private static bool CheckPermission(IEnumerable<IRole> roles, GuildData data, Snowflake memberId, private static bool CheckPermission(IEnumerable<IRole> roles, GuildData data, MemberData memberData,
IGuildMember member,
DiscordPermission permission) DiscordPermission permission)
{ {
var moderatorRole = GuildSettings.ModeratorRole.Get(data.Settings); var moderatorRole = GuildSettings.ModeratorRole.Get(data.Settings);
if (!moderatorRole.Empty() && data.GetOrCreateMemberData(memberId).Roles.Contains(moderatorRole.Value)) if (!moderatorRole.Empty() && memberData.Roles.Contains(moderatorRole.Value))
{ {
return true; return true;
} }
return roles return roles
.Where(r => member.Roles.Contains(r.ID)) .Where(r => memberData.Roles.Contains(r.ID.Value))
.Any(r => .Any(r =>
r.Permissions.HasPermission(permission) r.Permissions.HasPermission(permission)
); );
@ -80,38 +79,23 @@ public sealed class AccessControlService
return Result<string?>.FromError(botResult); return Result<string?>.FromError(botResult);
} }
var botMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct);
if (!botMemberResult.IsDefined(out var botMember))
{
return Result<string?>.FromError(botMemberResult);
}
var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct);
if (!targetMemberResult.IsDefined(out var targetMember))
{
return Result<string?>.FromSuccess(null);
}
var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct);
if (!rolesResult.IsDefined(out var roles)) if (!rolesResult.IsDefined(out var roles))
{ {
return Result<string?>.FromError(rolesResult); return Result<string?>.FromError(rolesResult);
} }
var data = await _data.GetData(guildId, ct);
var targetData = data.GetOrCreateMemberData(targetId);
var botData = data.GetOrCreateMemberData(bot.ID);
if (interacterId is null) if (interacterId is null)
{ {
return CheckInteractions(action, guild, roles, targetMember, botMember, botMember); return CheckInteractions(action, guild, roles, targetData, botData, botData);
} }
var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId.Value, ct); var interacterData = data.GetOrCreateMemberData(interacterId.Value);
if (!interacterResult.IsDefined(out var interacter)) var hasPermission = CheckPermission(roles, data, interacterData,
{
return Result<string?>.FromError(interacterResult);
}
var data = await _data.GetData(guildId, ct);
var hasPermission = CheckPermission(roles, data, interacterId.Value, interacter,
action switch action switch
{ {
"Ban" => DiscordPermission.BanMembers, "Ban" => DiscordPermission.BanMembers,
@ -122,31 +106,26 @@ public sealed class AccessControlService
}); });
return hasPermission return hasPermission
? CheckInteractions(action, guild, roles, targetMember, botMember, interacter) ? CheckInteractions(action, guild, roles, targetData, botData, interacterData)
: Result<string?>.FromSuccess($"UserCannot{action}Members".Localized()); : Result<string?>.FromSuccess($"UserCannot{action}Members".Localized());
} }
private static Result<string?> CheckInteractions( private static Result<string?> CheckInteractions(
string action, IGuild guild, IReadOnlyList<IRole> roles, IGuildMember targetMember, IGuildMember botMember, string action, IGuild guild, IReadOnlyList<IRole> roles, MemberData targetData, MemberData botData,
IGuildMember interacter) MemberData interacterData)
{ {
if (!targetMember.User.IsDefined(out var targetUser)) if (botData.Id == targetData.Id)
{
return new ArgumentNullError(nameof(targetMember.User));
}
if (botMember.User == targetMember.User)
{ {
return Result<string?>.FromSuccess($"UserCannot{action}Bot".Localized()); return Result<string?>.FromSuccess($"UserCannot{action}Bot".Localized());
} }
if (targetUser.ID == guild.OwnerID) if (targetData.Id == guild.OwnerID)
{ {
return Result<string?>.FromSuccess($"UserCannot{action}Owner".Localized()); return Result<string?>.FromSuccess($"UserCannot{action}Owner".Localized());
} }
var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList(); var targetRoles = roles.Where(r => targetData.Roles.Contains(r.ID.Value)).ToList();
var botRoles = roles.Where(r => botMember.Roles.Contains(r.ID)); var botRoles = roles.Where(r => botData.Roles.Contains(r.ID.Value));
var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position); var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position);
if (targetBotRoleDiff >= 0) if (targetBotRoleDiff >= 0)
@ -154,7 +133,7 @@ public sealed class AccessControlService
return Result<string?>.FromSuccess($"BotCannot{action}Target".Localized()); return Result<string?>.FromSuccess($"BotCannot{action}Target".Localized());
} }
var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID)); var interacterRoles = roles.Where(r => interacterData.Roles.Contains(r.ID.Value));
var targetInteracterRoleDiff var targetInteracterRoleDiff
= targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position); = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position);
return targetInteracterRoleDiff < 0 return targetInteracterRoleDiff < 0

View file

@ -3,10 +3,10 @@ using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Data;
using Remora.Rest.Core; using Remora.Rest.Core;
using TeamOctolings.Octobot.Data;
namespace Octobot.Services; namespace TeamOctolings.Octobot.Services;
/// <summary> /// <summary>
/// Handles saving, loading, initializing and providing <see cref="GuildData" />. /// Handles saving, loading, initializing and providing <see cref="GuildData" />.

View file

@ -2,16 +2,16 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Data;
using Octobot.Extensions;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
namespace Octobot.Services.Update; namespace TeamOctolings.Octobot.Services.Update;
public sealed partial class MemberUpdateService : BackgroundService public sealed partial class MemberUpdateService : BackgroundService
{ {

View file

@ -1,8 +1,6 @@
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Octobot.Data;
using Octobot.Extensions;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects; using Remora.Discord.API.Objects;
@ -10,8 +8,10 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
namespace Octobot.Services.Update; namespace TeamOctolings.Octobot.Services.Update;
public sealed class ScheduledEventUpdateService : BackgroundService public sealed class ScheduledEventUpdateService : BackgroundService
{ {

View file

@ -4,7 +4,7 @@ using Remora.Discord.API.Gateway.Commands;
using Remora.Discord.API.Objects; using Remora.Discord.API.Objects;
using Remora.Discord.Gateway; using Remora.Discord.Gateway;
namespace Octobot.Services.Update; namespace TeamOctolings.Octobot.Services.Update;
public sealed class SongUpdateService : BackgroundService public sealed class SongUpdateService : BackgroundService
{ {

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@ -16,31 +16,31 @@
<Company>TeamOctolings</Company> <Company>TeamOctolings</Company>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<Description>A general-purpose Discord bot for moderation written in C#</Description> <Description>A general-purpose Discord bot for moderation written in C#</Description>
<ApplicationIcon>docs/octobot.ico</ApplicationIcon> <ApplicationIcon>../docs/octobot.ico</ApplicationIcon>
<GitVersion>false</GitVersion> <GitVersion>false</GitVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DiffPlex" Version="1.7.2" /> <PackageReference Include="DiffPlex" Version="1.7.2" />
<PackageReference Include="GitInfo" Version="3.3.4" /> <PackageReference Include="GitInfo" Version="3.3.5" />
<PackageReference Include="Humanizer.Core.ru" Version="2.14.1" /> <PackageReference Include="Humanizer.Core.ru" Version="2.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" /> <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" /> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Remora.Commands" Version="10.0.5" /> <PackageReference Include="Remora.Commands" Version="10.0.5" />
<PackageReference Include="Remora.Discord.Caching" Version="38.0.1" /> <PackageReference Include="Remora.Discord.Caching" Version="39.0.0" />
<PackageReference Include="Remora.Discord.Extensions" Version="5.3.4" /> <PackageReference Include="Remora.Discord.Extensions" Version="5.3.5" />
<PackageReference Include="Remora.Discord.Hosting" Version="6.0.9" /> <PackageReference Include="Remora.Discord.Hosting" Version="6.0.10" />
<PackageReference Include="Remora.Discord.Interactivity" Version="4.5.3" /> <PackageReference Include="Remora.Discord.Interactivity" Version="4.5.4" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" /> <PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Update="locale\Messages.resx"> <EmbeddedResource Update="Messages.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Messages.Designer.cs</LastGenOutput> <LastGenOutput>Messages.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="CodeAnalysis\BannedSymbols.txt" /> <AdditionalFiles Include="..\CodeAnalysis\BannedSymbols.txt" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,16 +1,19 @@
using System.Drawing; using System.Drawing;
using System.Text; using System.Text;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Octobot.Data; using Microsoft.Extensions.Logging;
using Octobot.Extensions;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting; using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core; using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
using TeamOctolings.Octobot.Attributes;
using TeamOctolings.Octobot.Data;
using TeamOctolings.Octobot.Extensions;
namespace Octobot.Services; namespace TeamOctolings.Octobot;
/// <summary> /// <summary>
/// Provides utility methods that cannot be transformed to extension methods because they require usage /// Provides utility methods that cannot be transformed to extension methods because they require usage
@ -18,6 +21,9 @@ namespace Octobot.Services;
/// </summary> /// </summary>
public sealed class Utility public sealed class Utility
{ {
public static readonly AllowedMentions NoMentions = new(
Array.Empty<MentionType>(), Array.Empty<Snowflake>(), Array.Empty<Snowflake>());
private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestChannelAPI _channelApi;
private readonly IDiscordRestGuildScheduledEventAPI _eventApi; private readonly IDiscordRestGuildScheduledEventAPI _eventApi;
private readonly IDiscordRestGuildAPI _guildApi; private readonly IDiscordRestGuildAPI _guildApi;
@ -30,6 +36,9 @@ public sealed class Utility
_guildApi = guildApi; _guildApi = guildApi;
} }
[StaticCallersOnly]
public static ILogger<Program>? StaticLogger { get; set; }
/// <summary> /// <summary>
/// Gets the string mentioning the <see cref="GuildSettings.EventNotificationRole" /> and event subscribers related to /// Gets the string mentioning the <see cref="GuildSettings.EventNotificationRole" /> and event subscribers related to
/// a scheduled /// a scheduled