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

Remora.Discord part 3 out of ∞

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
This commit is contained in:
Octol1ttle 2023-05-18 10:28:25 +05:00
parent c4835a4e78
commit 67a15f3822
Signed by: Octol1ttle
GPG key ID: B77C34313AEE1FFF
8 changed files with 322 additions and 198 deletions

View file

@ -62,7 +62,7 @@ public class Boyfriend {
services.AddTransient<IConfigurationBuilder, ConfigurationBuilder>();
services.Configure<DiscordGatewayClientOptions>(
options => options.Intents |= GatewayIntents.MessageContents);
options => options.Intents |= GatewayIntents.MessageContents | GatewayIntents.GuildMembers);
}
).ConfigureLogging(
c => c.AddConsole()

View file

@ -19,11 +19,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DiffPlex" Version="1.7.1"/>
<PackageReference Include="Humanizer.Core.ru" Version="2.14.1"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.3.23174.8"/>
<PackageReference Include="Remora.Discord" Version="2023.3.0"/>
</ItemGroup>
<!-- TODO: remove this when done -->
<ItemGroup>
<Compile Remove="old\**"/>
<Compile Update="Messages.Designer.cs">
@ -35,7 +37,7 @@
<ItemGroup>
<EmbeddedResource Remove="old\**"/>
<EmbeddedResource Update="Messages.resx.bak">
<EmbeddedResource Update="Messages.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Messages.Designer.cs</LastGenOutput>
</EmbeddedResource>

View file

@ -1,4 +1,6 @@
using System.Drawing;
using DiffPlex;
using DiffPlex.DiffBuilder;
using Microsoft.Extensions.Logging;
using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Objects;
@ -29,15 +31,16 @@ public class GuildCreateResponder : IResponder<IGuildCreate> {
var guild = gatewayEvent.Guild.AsT0;
Boyfriend.Logger.LogInformation("Joined guild \"{Name}\"", guild.Name);
var channelResult = guild.ID.GetChannel("PrivateFeedbackChannel");
var channelResult = guild.ID.GetConfigChannel("PrivateFeedbackChannel");
if (!channelResult.IsDefined(out var channel)) return Result.FromSuccess();
var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
if (guild.GetConfigBool("ReceiveStartupMessages").IsDefined(out var shouldSendStartupMessage)
&& shouldSendStartupMessage) {
Messages.Culture = guild.GetCulture();
if (!guild.GetConfigBool("ReceiveStartupMessages").IsDefined(out var shouldSendStartupMessage)
|| !shouldSendStartupMessage) return Result.FromSuccess();
Messages.Culture = guild.ID.GetGuildCulture();
var i = Random.Shared.Next(1, 4);
var embed = new EmbedBuilder()
@ -53,35 +56,31 @@ public class GuildCreateResponder : IResponder<IGuildCreate> {
return (Result)await _channelApi.CreateMessageAsync(
channel, embeds: new[] { built }!, ct: ct);
}
return Result.FromSuccess();
}
}
public class MessageDeletedResponder : IResponder<IMessageDelete> {
private readonly IDiscordRestAuditLogAPI _auditLogApi;
private readonly CacheService _cacheService;
private readonly IDiscordRestChannelAPI _channelApi;
private readonly IDiscordRestUserAPI _userApi;
public MessageDeletedResponder(
IDiscordRestChannelAPI channelApi, IDiscordRestUserAPI userApi, CacheService cacheService,
IDiscordRestAuditLogAPI auditLogApi) {
_channelApi = channelApi;
_userApi = userApi;
_cacheService = cacheService;
IDiscordRestAuditLogAPI auditLogApi, CacheService cacheService, IDiscordRestChannelAPI channelApi) {
_auditLogApi = auditLogApi;
_cacheService = cacheService;
_channelApi = channelApi;
}
public async Task<Result> RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) {
if (!gatewayEvent.GuildID.IsDefined(out var guildId)) return Result.FromSuccess();
var channelResult = guildId.GetChannel("PrivateFeedbackChannel");
if (!channelResult.IsDefined(out var channel)) return Result.FromSuccess();
var channelResult = guildId.GetConfigChannel("PrivateFeedbackChannel");
if (!channelResult.IsDefined(out var logChannel)) return Result.FromSuccess();
var messageResult = await _cacheService.TryGetValueAsync<IMessage>(
new KeyHelpers.MessageCacheKey(gatewayEvent.ChannelID, gatewayEvent.ID), ct);
if (messageResult.IsDefined(out var message)) {
if (!messageResult.IsDefined(out var message)) return Result.FromError(messageResult);
if (string.IsNullOrWhiteSpace(message.Content)) return Result.FromSuccess();
var auditLogResult = await _auditLogApi.GetGuildAuditLogAsync(
guildId, actionType: AuditLogEvent.MessageDelete, limit: 1, ct: ct);
if (!auditLogResult.IsDefined(out var auditLogPage)) return Result.FromError(auditLogResult);
@ -93,29 +92,121 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
var user = message.Author;
if (options.ChannelID == gatewayEvent.ChannelID
&& DateTimeOffset.UtcNow.Subtract(auditLog.ID.Timestamp).TotalSeconds <= 2) {
var userResult = await _userApi.GetUserAsync(auditLog.UserID!.Value, ct);
var userResult = await _cacheService.TryGetValueAsync<IUser>(
new KeyHelpers.UserCacheKey(auditLog.UserID!.Value), ct);
if (!userResult.IsDefined(out user)) return Result.FromError(userResult);
}
Messages.Culture = guildId.GetGuildCulture();
var embed = new EmbedBuilder()
.WithAuthor(string.Format(Messages.CachedMessageDeleted, message.Author))
.WithTitle(
.WithSmallTitle(
message.Author,
string.Format(
Messages.CachedMessageDeleted,
$"{message.Author.Username}#{message.Author.Discriminator:0000}"))
.WithDescription(Markdown.BlockCode(message.Content.SanitizeForBlockCode()))
message.Author.GetTag()))
.WithDescription(
$"{Mention.Channel(gatewayEvent.ChannelID)}\n{Markdown.BlockCode(message.Content.SanitizeForBlockCode())}")
.WithActionFooter(user)
.WithTimestamp(message.Timestamp)
.WithColour(Color.Crimson)
.Build();
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
return (Result)await _channelApi.CreateMessageAsync(
logChannel, embeds: new[] { built }, allowedMentions: Boyfriend.NoMentions, ct: ct);
}
}
public class MessageEditedResponder : IResponder<IMessageUpdate> {
private readonly CacheService _cacheService;
private readonly IDiscordRestChannelAPI _channelApi;
public MessageEditedResponder(CacheService cacheService, IDiscordRestChannelAPI channelApi) {
_cacheService = cacheService;
_channelApi = channelApi;
}
public async Task<Result> RespondAsync(IMessageUpdate gatewayEvent, CancellationToken ct = default) {
if (!gatewayEvent.GuildID.IsDefined(out var guildId)) return Result.FromSuccess();
if (!gatewayEvent.ChannelID.IsDefined(out var channelId))
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.ChannelID)));
if (!gatewayEvent.ID.IsDefined(out var messageId))
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.ID)));
if (!gatewayEvent.Content.IsDefined(out var newContent))
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.Content)));
if (!gatewayEvent.EditedTimestamp.IsDefined(out var timestamp))
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.EditedTimestamp)));
var messageResult = await _cacheService.TryGetValueAsync<IMessage>(
new KeyHelpers.MessageCacheKey(channelId, messageId), ct);
if (!messageResult.IsDefined(out var message)) return Result.FromError(messageResult);
if (string.IsNullOrWhiteSpace(message.Content)
|| string.IsNullOrWhiteSpace(newContent)
|| message.Content == newContent) return Result.FromSuccess();
var logChannelResult = guildId.GetConfigChannel("PrivateFeedbackChannel");
if (!logChannelResult.IsDefined(out var logChannel)) return Result.FromSuccess();
var currentUserResult = await _cacheService.TryGetValueAsync<IUser>(
new KeyHelpers.CurrentUserCacheKey(), ct);
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
var diff = new SideBySideDiffBuilder(Differ.Instance).BuildDiffModel(message.Content, newContent, true, true);
Messages.Culture = guildId.GetGuildCulture();
var embed = new EmbedBuilder()
.WithSmallTitle(
message.Author,
string.Format(Messages.CachedMessageEdited, message.Author.GetTag()),
$"https://discord.com/channels/{guildId}/{channelId}/{messageId}")
.WithDescription($"{Mention.Channel(message.ChannelID)}\n{diff.AsMarkdown()}")
.WithUserFooter(currentUser)
.WithTimestamp(timestamp.Value)
.WithColour(Color.Gold)
.Build();
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
return (Result)await _channelApi.CreateMessageAsync(
logChannel, embeds: new[] { built }, allowedMentions: Boyfriend.NoMentions, ct: ct);
}
}
public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
private readonly CacheService _cacheService;
private readonly IDiscordRestChannelAPI _channelApi;
public GuildMemberAddResponder(CacheService cacheService, IDiscordRestChannelAPI channelApi) {
_cacheService = cacheService;
_channelApi = channelApi;
}
public async Task<Result> RespondAsync(IGuildMemberAdd gatewayEvent, CancellationToken ct = default) {
if (!gatewayEvent.GuildID.GetConfigString("WelcomeMessage").IsDefined(out var welcomeMessage)
|| welcomeMessage is "off" or "disable" or "disabled")
return Result.FromSuccess();
if (welcomeMessage is "default" or "reset") {
Messages.Culture = gatewayEvent.GuildID.GetGuildCulture();
welcomeMessage = Messages.DefaultWelcomeMessage;
}
if (!gatewayEvent.GuildID.GetConfigChannel("PublicFeedbackChannel").IsDefined(out var channel))
return Result.FromSuccess();
if (!gatewayEvent.User.IsDefined(out var user))
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.User)));
var guildResult = await _cacheService.TryGetValueAsync<IGuild>(
new KeyHelpers.GuildCacheKey(gatewayEvent.GuildID), ct);
if (!guildResult.IsDefined(out var guild)) return Result.FromError(guildResult);
var embed = new EmbedBuilder()
.WithSmallTitle(user, string.Format(welcomeMessage, user.GetTag(), guild.Name))
.WithGuildFooter(guild)
.WithTimestamp(gatewayEvent.JoinedAt)
.WithColour(Color.LawnGreen)
.Build();
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
return (Result)await _channelApi.CreateMessageAsync(
channel, embeds: new[] { built }, allowedMentions: Boyfriend.NoMentions, ct: ct);
}
return (Result)messageResult;
}
}

View file

@ -1,9 +1,12 @@
using System.Globalization;
using System.Text;
using DiffPlex.DiffBuilder.Model;
using Microsoft.Extensions.Configuration;
using Remora.Discord.API;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Objects;
using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core;
using Remora.Results;
@ -21,15 +24,20 @@ public static class Extensions {
return value is not null ? Result<bool>.FromSuccess(value.Value) : Result<bool>.FromError(new NotFoundError());
}
public static Result<Snowflake> GetChannel(this Snowflake guildId, string key) {
public static Result<Snowflake> GetConfigChannel(this Snowflake guildId, string key) {
var value = Boyfriend.GuildConfiguration.GetValue<ulong?>($"GuildConfigs:{guildId}:{key}");
return value is not null
? Result<Snowflake>.FromSuccess(DiscordSnowflake.New(value.Value))
: Result<Snowflake>.FromError(new NotFoundError());
}
public static CultureInfo GetCulture(this IGuild guild) {
var value = Boyfriend.GuildConfiguration.GetValue<string?>($"GuildConfigs:{guild.ID}:Language");
public static Result<string> GetConfigString(this Snowflake guildId, string key) {
var value = Boyfriend.GuildConfiguration.GetValue<string?>($"GuildConfigs:{guildId}:{key}");
return value is not null ? Result<string>.FromSuccess(value) : Result<string>.FromError(new NotFoundError());
}
public static CultureInfo GetGuildCulture(this Snowflake guildId) {
var value = Boyfriend.GuildConfiguration.GetValue<string?>($"GuildConfigs:{guildId}:Language");
return value is not null ? CultureInfoCache[value] : CultureInfoCache["en"];
}
@ -39,7 +47,7 @@ public static class Extensions {
? avatarUrlResult.Entity.AbsoluteUri
: CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
return builder.WithFooter(new EmbedFooter($"{user.Username}#{user.Discriminator:0000}", avatarUrl));
return builder.WithFooter(new EmbedFooter(user.GetTag(), avatarUrl));
}
public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user) {
@ -49,21 +57,44 @@ public static class Extensions {
: CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
return builder.WithFooter(
new EmbedFooter($"{Messages.IssuedBy}:\n{user.Username}#{user.Discriminator:0000}", avatarUrl));
new EmbedFooter($"{Messages.IssuedBy}:\n{user.GetTag()}", avatarUrl));
}
public static EmbedBuilder WithTitle(this EmbedBuilder builder, IUser avatarSource, string text) {
public static EmbedBuilder WithSmallTitle(
this EmbedBuilder builder, IUser avatarSource, string text, string? url = default) {
var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256);
var avatarUrl = avatarUrlResult.IsSuccess
? avatarUrlResult.Entity
: CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity;
builder.Author = new EmbedAuthorBuilder(text, iconUrl: avatarUrl.AbsoluteUri);
builder.Author = new EmbedAuthorBuilder(text, url, avatarUrl.AbsoluteUri);
return builder;
}
public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild) {
var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
var iconUrl = iconUrlResult.IsSuccess
? iconUrlResult.Entity.AbsoluteUri
: default(Optional<string>);
return builder.WithFooter(new EmbedFooter(guild.Name, iconUrl));
}
public static string SanitizeForBlockCode(this string s) {
return s.Replace("```", "```");
}
public static string AsMarkdown(this SideBySideDiffModel model) {
var builder = new StringBuilder();
foreach (var line in model.OldText.Lines.Where(piece => !string.IsNullOrWhiteSpace(piece.Text)))
builder.Append("-- ").AppendLine(line.Text);
foreach (var line in model.NewText.Lines) builder.Append("++ ").AppendLine(line.Text);
return Markdown.BlockCode(builder.ToString().SanitizeForBlockCode(), "diff");
}
public static string GetTag(this IUser user) {
return $"{user.Username}#{user.Discriminator:0000}";
}
}

View file

@ -131,7 +131,7 @@
<value>Cleared message from {0} in channel {1}: {2}</value>
</data>
<data name="CachedMessageEdited" xml:space="preserve">
<value>Edited message in channel {0}: {1} -&gt; {2}</value>
<value>Edited message by {0}:</value>
</data>
<data name="DefaultWelcomeMessage" xml:space="preserve">
<value>{0}, welcome to {1}</value>

View file

@ -131,7 +131,7 @@
<value>Очищено сообщение от {0} в канале {1}: {2}</value>
</data>
<data name="CachedMessageEdited" xml:space="preserve">
<value>Отредактировано сообщение в канале {0}: {1} -&gt; {2}</value>
<value>Сообщение {0} отредактировано:</value>
</data>
<data name="DefaultWelcomeMessage" xml:space="preserve">
<value>{0}, добро пожаловать на сервер {1}</value>

View file

@ -131,7 +131,7 @@
<value>вырезано сообщение (используя `!clear`) от {0} в канале {1}: {2}</value>
</data>
<data name="CachedMessageEdited" xml:space="preserve">
<value>переделано сообщение от {0}: {1} -&gt; {2}</value>
<value>сообщение {0} переделано:</value>
</data>
<data name="DefaultWelcomeMessage" xml:space="preserve">
<value>{0}, добро пожаловать на сервер {1}</value>