mirror of
https://github.com/TeamOctolings/Octobot.git
synced 2025-05-05 05:26:28 +03:00
Tidy up project structure, fix bug with edit logging (#47)
The project structure has been changed because the previous one had everything in 1 folder. From this PR onwards, the following is true: - The source code is stored in `src/` - `*.resx` and `Messages.Designer.cs` is stored in `locale/` - Documentation is stored on the wiki and in `docs/` - Miscellaneous files, such as dotfiles, are stored in the root folder of the repository This PR additionally fixes an issue that would cause logs of edited messages to not be syntax highlighted. This happened because the responder of edited messages was changed to use the universal `InBlockCode` extension method which did not support syntax highlighting until this PR This PR additionally changes CODEOWNERS to be more reliable. Previously, it would be possible for some PRs to be unable to be approved because the only person who can approve them is the same person who opened the PR. --------- Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
This commit is contained in:
parent
2dd9f023ef
commit
3eb17b96c5
29 changed files with 180 additions and 179 deletions
193
src/Extensions.cs
Normal file
193
src/Extensions.cs
Normal file
|
@ -0,0 +1,193 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using DiffPlex.DiffBuilder.Model;
|
||||
using Remora.Discord.API;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.API.Objects;
|
||||
using Remora.Discord.Commands.Contexts;
|
||||
using Remora.Discord.Commands.Extensions;
|
||||
using Remora.Discord.Extensions.Embeds;
|
||||
using Remora.Discord.Extensions.Formatting;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Boyfriend;
|
||||
|
||||
public static class Extensions {
|
||||
/// <summary>
|
||||
/// Adds a footer with the <paramref name="user" />'s avatar and tag (@username or username#0000).
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to add the footer to.</param>
|
||||
/// <param name="user">The user whose tag and avatar to add.</param>
|
||||
/// <returns>The builder with the added footer.</returns>
|
||||
public static EmbedBuilder WithUserFooter(this EmbedBuilder builder, IUser user) {
|
||||
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
||||
var avatarUrl = avatarUrlResult.IsSuccess
|
||||
? avatarUrlResult.Entity.AbsoluteUri
|
||||
: CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
|
||||
|
||||
return builder.WithFooter(new EmbedFooter(user.GetTag(), avatarUrl));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a footer representing that an action was performed by a <paramref name="user" />.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to add the footer to.</param>
|
||||
/// <param name="user">The user that performed the action whose tag and avatar to use.</param>
|
||||
/// <returns>The builder with the added footer.</returns>
|
||||
public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user) {
|
||||
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
||||
var avatarUrl = avatarUrlResult.IsSuccess
|
||||
? avatarUrlResult.Entity.AbsoluteUri
|
||||
: CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
|
||||
|
||||
return builder.WithFooter(
|
||||
new EmbedFooter($"{Messages.IssuedBy}:\n{user.GetTag()}", avatarUrl));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a title using the author field, making it smaller than using the title field.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to add the small title to.</param>
|
||||
/// <param name="text">The text of the small title.</param>
|
||||
/// <param name="avatarSource">The user whose avatar to use in the small title.</param>
|
||||
/// <param name="url">The URL that will be opened if a user clicks on the small title.</param>
|
||||
/// <returns>The builder with the added small title in the author field.</returns>
|
||||
public static EmbedBuilder WithSmallTitle(
|
||||
this EmbedBuilder builder, string text, IUser? avatarSource = null, string? url = default) {
|
||||
Uri? avatarUrl = null;
|
||||
if (avatarSource is not null) {
|
||||
var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256);
|
||||
|
||||
avatarUrl = avatarUrlResult.IsSuccess
|
||||
? avatarUrlResult.Entity
|
||||
: CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity;
|
||||
}
|
||||
|
||||
builder.Author = new EmbedAuthorBuilder(text, url, avatarUrl?.AbsoluteUri);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a footer representing that the action was performed in the <paramref name="guild" />.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to add the footer to.</param>
|
||||
/// <param name="guild">The guild whose name and icon to use.</param>
|
||||
/// <returns>The builder with the added footer.</returns>
|
||||
public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild) {
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a title representing that the action happened in the <paramref name="guild" />.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to add the title to.</param>
|
||||
/// <param name="guild">The guild whose name and icon to use.</param>
|
||||
/// <returns>The builder with the added title.</returns>
|
||||
public static EmbedBuilder WithGuildTitle(this EmbedBuilder builder, IGuild guild) {
|
||||
var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
|
||||
var iconUrl = iconUrlResult.IsSuccess
|
||||
? iconUrlResult.Entity.AbsoluteUri
|
||||
: null;
|
||||
|
||||
builder.Author = new EmbedAuthorBuilder(guild.Name, iconUrl: iconUrl);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a scheduled event's cover image.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to add the image to.</param>
|
||||
/// <param name="eventId">The ID of the scheduled event whose image to use.</param>
|
||||
/// <param name="imageHashOptional">The Optional containing the image hash.</param>
|
||||
/// <returns>The builder with the added cover image.</returns>
|
||||
public static EmbedBuilder WithEventCover(
|
||||
this EmbedBuilder builder, Snowflake eventId, Optional<IImageHash?> imageHashOptional) {
|
||||
if (!imageHashOptional.IsDefined(out var imageHash)) return builder;
|
||||
|
||||
var iconUrlResult = CDN.GetGuildScheduledEventCoverUrl(eventId, imageHash, imageSize: 1024);
|
||||
return iconUrlResult.IsDefined(out var iconUrl) ? builder.WithImageUrl(iconUrl.AbsoluteUri) : builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitizes a string for use in <see cref="Markdown.BlockCode(string)" /> by inserting zero-width spaces in between
|
||||
/// symbols used to format the string with block code.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to sanitize.</param>
|
||||
/// <returns>The sanitized string that can be safely used in <see cref="Markdown.BlockCode(string)" />.</returns>
|
||||
private static string SanitizeForBlockCode(this string s) {
|
||||
return s.Replace("```", "```");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitizes a string (see <see cref="SanitizeForBlockCode" />) and formats the string to use Markdown Block Code formatting with a specified
|
||||
/// language for syntax highlighting.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to sanitize and format.</param>
|
||||
/// <param name="language"></param>
|
||||
/// <returns>The sanitized string formatted to use Markdown Block Code with a specified
|
||||
/// language for syntax highlighting.</returns>
|
||||
public static string InBlockCode(this string s, string language = "") {
|
||||
s = s.SanitizeForBlockCode();
|
||||
return
|
||||
$"```{language}\n{s.SanitizeForBlockCode()}{(s.EndsWith("`") || string.IsNullOrWhiteSpace(s) ? " " : "")}```";
|
||||
}
|
||||
|
||||
public static string Localized(this string key) {
|
||||
return Messages.ResourceManager.GetString(key, Messages.Culture) ?? key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a string to allow its transmission in request headers.
|
||||
/// </summary>
|
||||
/// <remarks>Used when encountering "Request headers must contain only ASCII characters".</remarks>
|
||||
/// <param name="s">The string to encode.</param>
|
||||
/// <returns>An encoded string with spaces kept intact.</returns>
|
||||
public static string EncodeHeader(this string s) {
|
||||
return WebUtility.UrlEncode(s).Replace('+', ' ');
|
||||
}
|
||||
|
||||
public static string AsMarkdown(this DiffPaneModel model) {
|
||||
var builder = new StringBuilder();
|
||||
foreach (var line in model.Lines) {
|
||||
if (line.Type is ChangeType.Deleted)
|
||||
builder.Append("-- ");
|
||||
if (line.Type is ChangeType.Inserted)
|
||||
builder.Append("++ ");
|
||||
if (line.Type is not ChangeType.Imaginary)
|
||||
builder.AppendLine(line.Text);
|
||||
}
|
||||
|
||||
return InBlockCode(builder.ToString(), "diff");
|
||||
}
|
||||
|
||||
public static string GetTag(this IUser user) {
|
||||
return user.Discriminator is 0000 ? $"@{user.Username}" : $"{user.Username}#{user.Discriminator:0000}";
|
||||
}
|
||||
|
||||
public static Snowflake ToDiscordSnowflake(this ulong id) {
|
||||
return DiscordSnowflake.New(id);
|
||||
}
|
||||
|
||||
public static TResult? MaxOrDefault<TSource, TResult>(
|
||||
this IEnumerable<TSource> source, Func<TSource, TResult> selector) {
|
||||
var list = source.ToList();
|
||||
return list.Any() ? list.Max(selector) : default;
|
||||
}
|
||||
|
||||
public static bool TryGetContextIDs(
|
||||
this ICommandContext context, [NotNullWhen(true)] out Snowflake? guildId,
|
||||
[NotNullWhen(true)] out Snowflake? channelId, [NotNullWhen(true)] out Snowflake? userId) {
|
||||
guildId = null;
|
||||
channelId = null;
|
||||
userId = null;
|
||||
return context.TryGetGuildID(out guildId)
|
||||
&& context.TryGetChannelID(out channelId)
|
||||
&& context.TryGetUserID(out userId);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue