1
0
Fork 1
mirror of https://github.com/TeamOctolings/Octobot.git synced 2025-04-19 16:33:36 +03:00

Merge branch 'master' into left-guild-message

Signed-off-by: Macintxsh <95250141+mctaylors@users.noreply.github.com>
This commit is contained in:
Macintxsh 2023-12-21 18:40:34 +03:00 committed by GitHub
commit f9a6ff2fa8
Signed by: GitHub
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 216 additions and 47 deletions

View file

@ -584,11 +584,14 @@
</data> </data>
<data name="ButtonReportIssue" xml:space="preserve"> <data name="ButtonReportIssue" xml:space="preserve">
<value>Report an issue</value> <value>Report an issue</value>
</data> </data
<data name="DefaultLeaveMessage" xml:space="preserve"> <data name="DefaultLeaveMessage" xml:space="preserve">
<value>See you soon, {0}!</value> <value>See you soon, {0}!</value>
</data> </data>
<data name="SettingsLeaveMessage" xml:space="preserve"> <data name="SettingsLeaveMessage" xml:space="preserve">
<value>Leave message</value> <value>Leave message</value>
</data> </data>
<data name="UserInfoKicked" xml:space="preserve">
<value>Kicked</value>
</data>
</root> </root>

View file

@ -591,4 +591,7 @@
<data name="SettingsLeaveMessage" xml:space="preserve"> <data name="SettingsLeaveMessage" xml:space="preserve">
<value>Сообщение о выходе</value> <value>Сообщение о выходе</value>
</data> </data>
<data name="UserInfoKicked" xml:space="preserve">
<value>Выгнан</value>
</data>
</root> </root>

View file

@ -502,7 +502,7 @@
<value>приколы полученные по заслугам</value> <value>приколы полученные по заслугам</value>
</data> </data>
<data name="UserInfoBannedPermanently" xml:space="preserve"> <data name="UserInfoBannedPermanently" xml:space="preserve">
<value>забанен</value> <value>пермабан</value>
</data> </data>
<data name="UserInfoNotOnGuild" xml:space="preserve"> <data name="UserInfoNotOnGuild" xml:space="preserve">
<value>вышел из сервера</value> <value>вышел из сервера</value>
@ -591,4 +591,7 @@
<data name="SettingsLeaveMessage" xml:space="preserve"> <data name="SettingsLeaveMessage" xml:space="preserve">
<value>до свидания (типо настройка)</value> <value>до свидания (типо настройка)</value>
</data> </data>
<data name="UserInfoKicked" xml:space="preserve">
<value>кикнут</value>
</data>
</root> </root>

View file

@ -151,7 +151,9 @@ public class KickCommandGroup : CommandGroup
return Result.FromError(kickResult.Error); return Result.FromError(kickResult.Error);
} }
data.GetOrCreateMemberData(target.ID).Roles.Clear(); var memberData = data.GetOrCreateMemberData(target.ID);
memberData.Roles.Clear();
memberData.Kicked = true;
var title = string.Format(Messages.UserKicked, target.GetTag()); var title = string.Format(Messages.UserKicked, target.GetTag());
var description = MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason)); var description = MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason));

View file

@ -300,9 +300,9 @@ public class MuteCommandGroup : CommandGroup
} }
var memberData = data.GetOrCreateMemberData(target.ID); var memberData = data.GetOrCreateMemberData(target.ID);
var isMuted = memberData.MutedUntil is not null || communicationDisabledUntil is not null; var wasMuted = memberData.MutedUntil is not null || communicationDisabledUntil is not null;
if (!isMuted) if (!wasMuted)
{ {
var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserNotMuted, bot) var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserNotMuted, bot)
.WithColour(ColorsList.Red).Build(); .WithColour(ColorsList.Red).Build();

View file

@ -122,32 +122,21 @@ public class ToolsCommandGroup : CommandGroup
embedColor = AppendGuildInformation(embedColor, guildMember, builder); embedColor = AppendGuildInformation(embedColor, guildMember, builder);
} }
var isMuted = (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) || var wasMuted = (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) ||
communicationDisabledUntil is not null; communicationDisabledUntil is not null;
var wasBanned = memberData.BannedUntil is not null;
var wasKicked = memberData.Kicked;
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, target.ID, ct); if (wasMuted || wasBanned || wasKicked)
if (isMuted || existingBanResult.IsDefined())
{ {
builder.Append("### ") builder.Append("### ")
.AppendLine(Markdown.Bold(Messages.UserInfoPunishments)); .AppendLine(Markdown.Bold(Messages.UserInfoPunishments));
embedColor = AppendPunishmentsInformation(wasMuted, wasKicked, wasBanned, memberData,
builder, embedColor, communicationDisabledUntil);
} }
if (isMuted) if (!guildMemberResult.IsSuccess && !wasBanned)
{
AppendMuteInformation(memberData, communicationDisabledUntil, builder);
embedColor = ColorsList.Red;
}
if (existingBanResult.IsDefined())
{
AppendBanInformation(memberData, builder);
embedColor = ColorsList.Black;
}
if (!guildMemberResult.IsSuccess && !existingBanResult.IsDefined())
{ {
builder.Append("### ") builder.Append("### ")
.AppendLine(Markdown.Bold(Messages.UserInfoNotOnGuild)); .AppendLine(Markdown.Bold(Messages.UserInfoNotOnGuild));
@ -166,6 +155,29 @@ public class ToolsCommandGroup : CommandGroup
return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
} }
private static Color AppendPunishmentsInformation(bool wasMuted, bool wasKicked, bool wasBanned,
MemberData memberData, StringBuilder builder, Color embedColor, DateTimeOffset? communicationDisabledUntil)
{
if (wasMuted)
{
AppendMuteInformation(memberData, communicationDisabledUntil, builder);
embedColor = ColorsList.Red;
}
if (wasKicked)
{
builder.AppendBulletPointLine(Messages.UserInfoKicked);
}
if (wasBanned)
{
AppendBanInformation(memberData, builder);
embedColor = ColorsList.Black;
}
return embedColor;
}
private static Color AppendGuildInformation(Color color, IGuildMember guildMember, StringBuilder builder) private static Color AppendGuildInformation(Color color, IGuildMember guildMember, StringBuilder builder)
{ {
if (guildMember.Nickname.IsDefined(out var nickname)) if (guildMember.Nickname.IsDefined(out var nickname))

View file

@ -18,6 +18,7 @@ public sealed class MemberData
public ulong Id { get; } public ulong Id { get; }
public DateTimeOffset? BannedUntil { get; set; } public DateTimeOffset? BannedUntil { get; set; }
public DateTimeOffset? MutedUntil { get; set; } public DateTimeOffset? MutedUntil { get; set; }
public bool Kicked { get; set; }
public List<ulong> Roles { get; set; } = []; public List<ulong> Roles { get; set; } = [];
public List<Reminder> Reminders { get; } = []; public List<Reminder> Reminders { get; } = [];
} }

View file

@ -1048,5 +1048,13 @@ namespace Octobot {
return ResourceManager.GetString("SettingsLeaveMessage", resourceCulture); return ResourceManager.GetString("SettingsLeaveMessage", resourceCulture);
} }
} }
internal static string UserInfoKicked
{
get
{
return ResourceManager.GetString("UserInfoKicked", resourceCulture);
}
}
} }
} }

View file

@ -2,11 +2,10 @@ 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.Commands;
using Octobot.Commands.Events; using Octobot.Commands.Events;
using Octobot.Services; using Octobot.Services;
using Octobot.Services.Profiler;
using Octobot.Services.Update; using Octobot.Services.Update;
using Remora.Commands.Extensions;
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.API.Objects;
@ -14,8 +13,8 @@ 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;
using Remora.Discord.Commands.Services; using Remora.Discord.Commands.Services;
using Remora.Discord.Extensions.Extensions;
using Remora.Discord.Gateway; using Remora.Discord.Gateway;
using Remora.Discord.Gateway.Extensions;
using Remora.Discord.Hosting.Extensions; using Remora.Discord.Hosting.Extensions;
using Remora.Rest.Core; using Remora.Rest.Core;
using Serilog.Extensions.Logging; using Serilog.Extensions.Logging;
@ -82,34 +81,20 @@ public sealed class Octobot
// Init // Init
.AddDiscordCaching() .AddDiscordCaching()
.AddDiscordCommands(true, false) .AddDiscordCommands(true, false)
.AddRespondersFromAssembly(typeof(Octobot).Assembly)
.AddCommandGroupsFromAssembly(typeof(Octobot).Assembly)
// Slash command event handlers // Slash command event handlers
.AddPreparationErrorEvent<LoggingPreparationErrorEvent>() .AddPreparationErrorEvent<LoggingPreparationErrorEvent>()
.AddPostExecutionEvent<ErrorLoggingPostExecutionEvent>() .AddPostExecutionEvent<ErrorLoggingPostExecutionEvent>()
// Services // Services
.AddTransient<Profiler>()
.AddSingleton<ProfilerFactory>()
.AddSingleton<Utility>() .AddSingleton<Utility>()
.AddSingleton<GuildDataService>() .AddSingleton<GuildDataService>()
.AddHostedService<GuildDataService>(provider => provider.GetRequiredService<GuildDataService>()) .AddHostedService<GuildDataService>(provider => provider.GetRequiredService<GuildDataService>())
.AddHostedService<MemberUpdateService>() .AddHostedService<MemberUpdateService>()
.AddHostedService<ScheduledEventUpdateService>() .AddHostedService<ScheduledEventUpdateService>()
.AddHostedService<SongUpdateService>() .AddHostedService<SongUpdateService>();
// Slash commands
.AddCommandTree()
.WithCommandGroup<AboutCommandGroup>()
.WithCommandGroup<BanCommandGroup>()
.WithCommandGroup<ClearCommandGroup>()
.WithCommandGroup<KickCommandGroup>()
.WithCommandGroup<MuteCommandGroup>()
.WithCommandGroup<PingCommandGroup>()
.WithCommandGroup<RemindCommandGroup>()
.WithCommandGroup<SettingsCommandGroup>()
.WithCommandGroup<ToolsCommandGroup>();
var responderTypes = typeof(Octobot).Assembly
.GetExportedTypes()
.Where(t => t.IsResponder());
foreach (var responderType in responderTypes)
{
services.AddResponder(responderType);
}
} }
).ConfigureLogging( ).ConfigureLogging(
c => c.AddConsole() c => c.AddConsole()

View file

@ -43,6 +43,8 @@ public class GuildMemberJoinedResponder : IResponder<IGuildMemberAdd>
var cfg = data.Settings; var cfg = data.Settings;
var memberData = data.GetOrCreateMemberData(user.ID); var memberData = data.GetOrCreateMemberData(user.ID);
memberData.Kicked = false;
var returnRolesResult = await TryReturnRolesAsync(cfg, memberData, gatewayEvent.GuildID, user.ID, ct); var returnRolesResult = await TryReturnRolesAsync(cfg, memberData, gatewayEvent.GuildID, user.ID, ct);
if (!returnRolesResult.IsSuccess) if (!returnRolesResult.IsSuccess)
{ {

View file

@ -0,0 +1,114 @@
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Logging;
using Remora.Results;
// TODO: remove in future profiler PRs
// ReSharper disable All
namespace Octobot.Services.Profiler;
/// <summary>
/// Provides the ability to profile how long certain parts of code take to complete using <see cref="Stopwatch"/>es.
/// </summary>
/// <remarks>Resolve <see cref="ProfilerFactory"/> instead in singletons.</remarks>
public sealed class Profiler
{
private const int MaxProfilerTime = 1000; // milliseconds
private readonly List<ProfilerEvent> _events = [];
private readonly ILogger<Profiler> _logger;
public Profiler(ILogger<Profiler> logger)
{
_logger = logger;
}
/// <summary>
/// Pushes an event to the profiler.
/// </summary>
/// <param name="id">The ID of the event.</param>
public void Push(string id)
{
_events.Add(new ProfilerEvent
{
Id = id,
Stopwatch = Stopwatch.StartNew()
});
}
/// <summary>
/// Pops the last pushed event from the profiler.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the profiler contains no events.</exception>
public void Pop()
{
if (_events.Count is 0)
{
throw new InvalidOperationException("Nothing to pop");
}
_events.Last().Stopwatch.Stop();
}
/// <summary>
/// If the profiler took too long to execute, this will log a warning with per-event time usage
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
private void Report()
{
var main = _events[0];
if (main.Stopwatch.ElapsedMilliseconds < MaxProfilerTime)
{
return;
}
var unprofiled = main.Stopwatch.ElapsedMilliseconds;
var builder = new StringBuilder().AppendLine();
for (var i = 1; i < _events.Count; i++)
{
var profilerEvent = _events[i];
if (profilerEvent.Stopwatch.IsRunning)
{
throw new InvalidOperationException(
$"Tried to report on a profiler with running stopwatches: {profilerEvent.Id}");
}
builder.AppendLine($"{profilerEvent.Id}: {profilerEvent.Stopwatch.ElapsedMilliseconds}ms");
unprofiled -= profilerEvent.Stopwatch.ElapsedMilliseconds;
}
builder.AppendLine($"<unprofiled>: {unprofiled}ms");
_logger.LogWarning("Profiler {ID} took {Elapsed} milliseconds to execute (max: {Max}ms):{Events}", main.Id,
main.Stopwatch.ElapsedMilliseconds, MaxProfilerTime, builder.ToString());
}
/// <summary>
/// <see cref="Pop"/> the profiler and <see cref="Report"/> on it afterwards.
/// </summary>
public void PopAndReport()
{
Pop();
Report();
}
/// <summary>
/// <see cref="PopAndReport"/> on the profiler and return a <see cref="Result{TEntity}"/>.
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public Result ReportWithResult(Result result)
{
PopAndReport();
return result;
}
/// <summary>
/// Calls <see cref="ReportWithResult"/> with <see cref="Result.FromSuccess"/>
/// </summary>
/// <returns>A successful result.</returns>
public Result ReportWithSuccess()
{
return ReportWithResult(Result.FromSuccess());
}
}

View file

@ -0,0 +1,9 @@
using System.Diagnostics;
namespace Octobot.Services.Profiler;
public struct ProfilerEvent
{
public string Id { get; init; }
public Stopwatch Stopwatch { get; init; }
}

View file

@ -0,0 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
namespace Octobot.Services.Profiler;
/// <summary>
/// Provides a method to create a <see cref="Profiler"/>. Useful in singletons.
/// </summary>
public sealed class ProfilerFactory
{
private readonly IServiceScopeFactory _scopeFactory;
public ProfilerFactory(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
/// <summary>
/// Creates a new <see cref="Profiler"/>.
/// </summary>
/// <returns>A new <see cref="Profiler"/>.</returns>
// TODO: remove in future profiler PRs
// ReSharper disable once UnusedMember.Global
public Profiler Create()
{
return _scopeFactory.CreateScope().ServiceProvider.GetRequiredService<Profiler>();
}
}