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 {
///
/// Adds a footer with the 's avatar and tag (@username or username#0000).
///
/// The builder to add the footer to.
/// The user whose tag and avatar to add.
/// The builder with the added footer.
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));
}
///
/// Adds a footer representing that an action was performed by a .
///
/// The builder to add the footer to.
/// The user that performed the action whose tag and avatar to use.
/// The builder with the added footer.
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));
}
///
/// Adds a title using the author field, making it smaller than using the title field.
///
/// The builder to add the small title to.
/// The text of the small title.
/// The user whose avatar to use in the small title.
/// The URL that will be opened if a user clicks on the small title.
/// The builder with the added small title in the author field.
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;
}
///
/// Adds a footer representing that the action was performed in the .
///
/// The builder to add the footer to.
/// The guild whose name and icon to use.
/// The builder with the added footer.
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);
return builder.WithFooter(new EmbedFooter(guild.Name, iconUrl));
}
///
/// Adds a title representing that the action happened in the .
///
/// The builder to add the title to.
/// The guild whose name and icon to use.
/// The builder with the added title.
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;
}
///
/// Adds a scheduled event's cover image.
///
/// The builder to add the image to.
/// The ID of the scheduled event whose image to use.
/// The Optional containing the image hash.
/// The builder with the added cover image.
public static EmbedBuilder WithEventCover(
this EmbedBuilder builder, Snowflake eventId, Optional 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;
}
///
/// Sanitizes a string for use in by inserting zero-width spaces in between
/// symbols used to format the string with block code.
///
/// The string to sanitize.
/// The sanitized string that can be safely used in .
private static string SanitizeForBlockCode(this string s) {
return s.Replace("```", "```");
}
///
/// Sanitizes a string (see ) and formats the string to use Markdown Block Code formatting with a specified
/// language for syntax highlighting.
///
/// The string to sanitize and format.
///
/// The sanitized string formatted to use Markdown Block Code with a specified
/// language for syntax highlighting.
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;
}
///
/// Encodes a string to allow its transmission in request headers.
///
/// Used when encountering "Request headers must contain only ASCII characters".
/// The string to encode.
/// An encoded string with spaces kept intact.
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(
this IEnumerable source, Func 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);
}
}