Move all interaction classes to its own project

This commit is contained in:
Daan Boerlage 2021-11-07 00:36:20 +01:00
parent 54cbb00880
commit a460041c52
Signed by: daan
GPG key ID: FCE070E1E4956606
65 changed files with 142 additions and 140 deletions

View file

@ -1,73 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.ApplicationCommand
{
/// <see href="https://discord.com/developers/docs/interactions/application-commands#application-command-object"/>
public record Command
{
/// <summary>
/// unique id of the command
/// </summary>
[JsonPropertyName("id")]
public string Id { get; set; }
/// <summary>
/// the type of command, defaults 1 if not set
/// </summary>
[JsonPropertyName("type")]
public CommandType Type { get; set; }
/// <summary>
/// unique id of the parent application
/// </summary>
[JsonPropertyName("application_id")]
public string ApplicationId { get; set; }
/// <summary>
/// guild id of the command, if not global
/// </summary>
[JsonPropertyName("guild_id")]
public string GuildId { get; set; }
/// <summary>
/// 1-32 character name
/// </summary>
/// <remarks>
/// CHAT_INPUT command names and command option names must match the following regex ^[\w-]{1,32}$ with the unicode flag set. If there is a lowercase variant of any letters used, you must use those.
/// Characters with no lowercase variants and/or uncased letters are still allowed. USER and MESSAGE commands may be mixed case and can include spaces.
/// </remarks>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// 1-100 character description for CHAT_INPUT commands, empty string for USER and MESSAGE commands
/// </summary>
/// <remarks>
/// Exclusive: CHAT_INPUT
/// </remarks>
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// the parameters for the command, max 25
/// </summary>
/// <remarks>
/// Exclusive: CHAT_INPUT
/// </remarks>
[JsonPropertyName("options")]
public List<Option> Options { get; set; }
/// <summary>
/// whether the command is enabled by default when the app is added to a guild
/// </summary>
[JsonPropertyName("default_permission")]
public bool DefaultPermission { get; set; } = true;
/// <summary>
/// autoincrementing version identifier updated during substantial record changes
/// </summary>
[JsonPropertyName("version")]
public string Version { get; set; }
}
}

View file

@ -1,21 +0,0 @@
namespace Geekbot.Core.Interactions.ApplicationCommand
{
/// <see href="https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types"/>
public enum CommandType
{
/// <summary>
/// Slash commands; a text-based command that shows up when a user types /
/// </summary>
ChatInput = 1,
/// <summary>
/// A UI-based command that shows up when you right click or tap on a user
/// </summary>
User = 2,
/// <summary>
/// A UI-based command that shows up when you right click or tap on a message
/// </summary>
Message = 3,
}
}

View file

@ -1,51 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.ApplicationCommand
{
/// <remarks>
/// Required options must be listed before optional options
/// </remarks>
/// <see href="https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure"/>
public record Option
{
/// <summary>
/// the type of option
/// </summary>
[JsonPropertyName("type")]
public OptionType Type { get; set; }
/// <summary>
/// 1-32 character name
/// </summary>
/// <remarks>
/// Must match ^[\w-]{1,32}$
/// </remarks>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// 1-100 character description
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// if the parameter is required or optional--default false
/// </summary>
[JsonPropertyName("required")]
public bool Required { get; set; }
/// <summary>
/// choices for STRING, INTEGER, and NUMBER types for the user to pick from, max 25
/// </summary>
[JsonPropertyName("choices")]
public List<OptionChoice> Choices { get; set; }
/// <summary>
/// if the option is a subcommand or subcommand group type, this nested options will be the parameters
/// </summary>
[JsonPropertyName("options")]
public List<Option> Options { get; set; }
}
}

View file

@ -1,23 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.ApplicationCommand
{
/// <remarks>
/// If you specify choices for an option, they are the only valid values for a user to pick
/// </remarks>
/// <see href="https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure"/>
public record OptionChoice
{
/// <summary>
/// 1-100 character choice name
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// value of the choice, up to 100 characters if string
/// </summary>
[JsonPropertyName("value")]
public string Value { get; set; }
}
}

View file

@ -1,38 +0,0 @@
namespace Geekbot.Core.Interactions.ApplicationCommand
{
/// <see href="https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type"/>
public enum OptionType
{
SubCommand = 1,
SubCommandGroup = 2,
String = 3,
/// <summary>
/// Any integer between -2^53 and 2^53
/// </summary>
Integer = 4,
Boolean = 5,
User = 6,
/// <summary>
/// Includes all channel types + categories
/// </summary>
Channel = 7,
Role = 8,
/// <summary>
/// Includes users and roles
/// </summary>
Mentionable = 9,
/// <summary>
/// Any double between -2^53 and 2^53
/// </summary>
Number = 10,
}
}

View file

@ -1,130 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Discord;
using Color = System.Drawing.Color;
namespace Geekbot.Core.Interactions.Embed
{
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object" />
public class Embed
{
/// <summary>
/// title of embed
/// </summary>
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// type of embed (always "rich" for webhook embeds)
/// </summary>
[JsonPropertyName("type")]
public EmbedTypes Type { get; set; } = EmbedTypes.Rich;
/// <summary>
/// description of embed
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// url of embed
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// timestamp of embed content
/// </summary>
[JsonPropertyName("timestamp")]
public DateTime? Timestamp { get; set; }
[JsonIgnore]
private Color _color { get; set; } = System.Drawing.Color.DimGray;
/// <summary>
/// color code of the embed
/// </summary>
[JsonPropertyName("color")]
public uint Color => (uint)(_color.R << 16 | _color.G << 8 | _color.B);
/// <summary>
/// footer information
/// </summary>
[JsonPropertyName("footer")]
public EmbedFooter Footer { get; set; }
/// <summary>
/// image information
/// </summary>
[JsonPropertyName("image")]
public EmbedImage Image { get; set; }
/// <summary>
/// thumbnail information
/// </summary>
[JsonPropertyName("thumbnail")]
public EmbedThumbnail Thumbnail { get; set; }
/// <summary>
/// video information
/// </summary>
[JsonPropertyName("video")]
public EmbedVideo Video { get; set; }
/// <summary>
/// provider information
/// </summary>
[JsonPropertyName("provider")]
public EmbedProvider Provider { get; set; }
/// <summary>
/// author information
/// </summary>
[JsonPropertyName("author")]
public EmbedAuthor Author { get; set; }
/// <summary>
/// fields information
/// </summary>
[JsonPropertyName("fields")]
public List<EmbedField> Fields { get; init; } = new List<EmbedField>();
public void AddField(string name, string value, bool inline = false)
{
Fields.Add(new EmbedField()
{
Name = name,
Value = value,
Inline = inline
});
}
public void AddInlineField(string name, string value)
{
AddField(name, value, true);
}
public void SetColor(Color c)
{
_color = c;
}
public EmbedBuilder ToDiscordNetEmbed()
{
var eb = new EmbedBuilder();
if (!string.IsNullOrEmpty(Title)) eb.Title = Title;
if (!string.IsNullOrEmpty(Description)) eb.Description = Description;
if (!string.IsNullOrEmpty(Url)) eb.Url = Url;
if (Timestamp != null) eb.Timestamp = Timestamp;
if (Color != 0) eb.WithColor(new Discord.Color(_color.R, _color.G, _color.B));
if (Footer != null) eb.WithFooter(Footer.Text, Footer.IconUrl);
if (Image != null) eb.WithImageUrl(Image.Url);
if (Thumbnail != null) eb.WithThumbnailUrl(Thumbnail.Url);
if (Author != null) eb.WithAuthor(Author.Name, Author.IconUrl, Author.Url);
Fields.ForEach(field => eb.AddField(field.Name, field.Value, field.Inline));
return eb;
}
}
}

View file

@ -1,32 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
public record EmbedAuthor
{
/// <summary>
/// name of author
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// url of author
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// url of author icon (only supports http(s) and attachments)
/// </summary>
[JsonPropertyName("icon_url")]
public string IconUrl { get; set; }
/// <summary>
/// a proxied url of author icon
/// </summary>
[JsonPropertyName("proxy_icon_url")]
public string ProxyIconUrl { get; set; }
}
}

View file

@ -1,26 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure" />
public record EmbedField
{
/// <summary>
/// name of the field
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// value of the field
/// </summary>
[JsonPropertyName("value")]
public string Value { get; set; }
/// <summary>
/// whether or not this field should display inline
/// </summary>
[JsonPropertyName("inline")]
public bool Inline { get; set; }
}
}

View file

@ -1,27 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure" />
public record EmbedFooter
{
/// <summary>
/// footer text
/// </summary>
[JsonPropertyName("text")]
public string Text { get; set; }
/// <summary>
/// url of footer icon (only supports http(s) and attachments)
/// </summary>
[JsonPropertyName("icon_url")]
public string IconUrl { get; set; }
/// <summary>
/// a proxied url of footer icon
/// </summary>
[JsonPropertyName("proxy_icon_url")]
public string ProxyIconUrl { get; set; }
}
}

View file

@ -1,32 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure" />
public record EmbedImage
{
/// <summary>
/// source url of image (only supports http(s) and attachments)
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// a proxied url of the image
/// </summary>
[JsonPropertyName("proxy_url")]
public string ProxyUrl { get; set; }
/// <summary>
/// height of image
/// </summary>
[JsonPropertyName("height")]
public int Height { get; set; }
/// <summary>
/// width of image
/// </summary>
[JsonPropertyName("width")]
public int Width { get; set; }
}
}

View file

@ -1,20 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure" />
public record EmbedProvider
{
/// <summary>
/// name of provider
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// url of provider
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; }
}
}

View file

@ -1,33 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure" />
public record EmbedThumbnail
{
/// <summary>
/// source url of thumbnail (only supports http(s) and attachments)
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// a proxied url of the thumbnail
/// </summary>
[JsonPropertyName("proxy_url")]
public string ProxyUrl { get; set; }
/// <summary>
/// height of thumbnail
/// </summary>
[JsonPropertyName("height")]
public int Height { get; set; }
/// <summary>
/// width of thumbnail
/// </summary>
[JsonPropertyName("width")]
public int Width { get; set; }
}
}

View file

@ -1,51 +0,0 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
/// <summary>
/// Embed types are "loosely defined" and, for the most part, are not used by our clients for rendering.
/// Embed attributes power what is rendered.
/// Embed types should be considered deprecated and might be removed in a future API version.
/// </summary>
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object-embed-types" />
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum EmbedTypes
{
/// <summary>
/// generic embed rendered from embed attributes
/// </summary>
[EnumMember(Value = "rich")]
Rich,
/// <summary>
/// image embed
/// </summary>
[EnumMember(Value = "image")]
Image,
/// <summary>
/// video embed
/// </summary>
[EnumMember(Value = "video")]
Video,
/// <summary>
/// animated gif image embed rendered as a video embed
/// </summary>
[EnumMember(Value = "gifv")]
Gifv,
/// <summary>
/// article embed
/// </summary>
[EnumMember(Value = "article")]
Article,
/// <summary>
/// link embed
/// </summary>
[EnumMember(Value = "link")]
Link,
}
}

View file

@ -1,32 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Embed
{
/// <see href="https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure" />
public record EmbedVideo
{
/// <summary>
/// source url of video
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// a proxied url of the video
/// </summary>
[JsonPropertyName("proxy_url")]
public string ProxyUrl { get; set; }
/// <summary>
/// height of video
/// </summary>
[JsonPropertyName("height")]
public int Height { get; set; }
/// <summary>
/// width of video
/// </summary>
[JsonPropertyName("width")]
public int Width { get; set; }
}
}

View file

@ -1,14 +0,0 @@
using System;
using Geekbot.Core.Interactions.Request;
using Geekbot.Core.Interactions.Response;
namespace Geekbot.Core.Interactions
{
public interface IInteractionBase
{
void BeforeExecute(Interaction interaction);
void AfterExecute(Interaction interaction);
void OnException(Interaction interaction, Exception e);
InteractionResponse GetExceptionResponse(Interaction interaction);
}
}

View file

@ -1,14 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Geekbot.Core.Interactions.ApplicationCommand;
using Geekbot.Core.Interactions.Request;
using Geekbot.Core.Interactions.Response;
namespace Geekbot.Core.Interactions
{
public interface IInteractionCommandManager
{
Dictionary<string, Command> CommandsInfo { get; init; }
Task<InteractionResponse> RunCommand(Interaction interaction);
}
}

View file

@ -1,67 +0,0 @@
using System;
using System.Threading.Tasks;
using Geekbot.Core.Interactions.ApplicationCommand;
using Geekbot.Core.Interactions.Request;
using Geekbot.Core.Interactions.Response;
using Sentry;
namespace Geekbot.Core.Interactions
{
public abstract class InteractionBase : IInteractionBase
{
public virtual void BeforeExecute(Interaction interaction)
{
}
public virtual void AfterExecute(Interaction interaction)
{
}
public virtual void OnException(Interaction interaction, Exception exception)
{
if (!SentrySdk.IsEnabled) return;
SentrySdk.CaptureException(exception);
}
public virtual InteractionResponse GetExceptionResponse(Interaction interaction)
{
return SimpleResponse(Localization.Internal.SomethingWentWrong);
}
protected InteractionResponse SimpleResponse(string message) => new InteractionResponse()
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new()
{
Content = message
}
};
protected InteractionResponse SimpleResponse(Embed.Embed embed) => new InteractionResponse()
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new ()
{
Content = string.Empty,
Embeds = new ()
{
embed
}
}
};
public abstract Command GetCommandInfo();
public abstract Task<InteractionResponse> Exec(Interaction interaction);
void IInteractionBase.BeforeExecute(Interaction interaction)
=> this.BeforeExecute(interaction);
void IInteractionBase.AfterExecute(Interaction interaction)
=> this.AfterExecute(interaction);
void IInteractionBase.OnException(Interaction interaction, Exception e)
=> this.OnException(interaction, e);
InteractionResponse IInteractionBase.GetExceptionResponse(Interaction interaction)
=> this.GetExceptionResponse(interaction);
}
}

View file

@ -1,83 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Interactions.ApplicationCommand;
using Geekbot.Core.Interactions.Request;
using Geekbot.Core.Interactions.Response;
using Microsoft.Extensions.DependencyInjection;
namespace Geekbot.Core.Interactions
{
public class InteractionCommandManager : IInteractionCommandManager
{
private readonly IServiceProvider _provider;
private readonly IGuildSettingsManager _guildSettingsManager;
private readonly Dictionary<CommandType, Dictionary<string, Type>> _commands = new() {
{ CommandType.Message, new Dictionary<string, Type>() },
{ CommandType.User, new Dictionary<string, Type>() },
{ CommandType.ChatInput, new Dictionary<string, Type>() },
};
public Dictionary<string, Command> CommandsInfo { get; init; }
public InteractionCommandManager(IServiceProvider provider, IGuildSettingsManager guildSettingsManager)
{
_provider = provider;
_guildSettingsManager = guildSettingsManager;
var interactions = Assembly.GetCallingAssembly()
.GetTypes()
.Where(type => type.IsClass && !type.IsAbstract && type.IsSubclassOf(typeof(InteractionBase)))
.ToList();
CommandsInfo = new Dictionary<string, Command>();
foreach (var interactionType in interactions)
{
var instance = (InteractionBase)ActivatorUtilities.CreateInstance(provider, interactionType);
var commandInfo = instance.GetCommandInfo();
_commands[commandInfo.Type].Add(commandInfo.Name, interactionType);
CommandsInfo.Add($"{commandInfo.Type}-{commandInfo.Name}", commandInfo);
}
}
public async Task<InteractionResponse> RunCommand(Interaction interaction)
{
var type = _commands[interaction.Data.Type][interaction.Data.Name];
var command = ActivatorUtilities.CreateInstance(_provider, type) as InteractionBase;
if (command == null)
{
return null;
}
var guildSettings = _guildSettingsManager.GetSettings(ulong.Parse(interaction.GuildId));
var language = guildSettings.Language;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(language);
InteractionResponse response;
try
{
command.BeforeExecute(interaction);
response = await command.Exec(interaction);
}
catch (Exception e)
{
command.OnException(interaction, e);
response = command.GetExceptionResponse(interaction);
}
finally
{
command.AfterExecute(interaction);
}
return response;
}
}
}

View file

@ -1,31 +0,0 @@
namespace Geekbot.Core.Interactions.MessageComponents
{
/// <see href="https://discord.com/developers/docs/interactions/message-components#button-object-button-styles" />
public enum ButtonStyle
{
/// <summary>
/// blurple
/// </summary>
Primary = 1,
/// <summary>
/// grey
/// </summary>
Secondary = 2,
/// <summary>
/// green
/// </summary>
Success = 3,
/// <summary>
/// red
/// </summary>
Danger = 4,
/// <summary>
/// grey, navigates to a URL
/// </summary>
Link = 5
}
}

View file

@ -1,111 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.MessageComponents
{
/// <see href="https://discord.com/developers/docs/interactions/message-components#component-object-component-structure"/>
public record Component
{
/// <summary>
/// component type
/// </summary>
[JsonPropertyName("type")]
public ComponentType Type { get; set; }
/// <summary>
/// a developer-defined identifier for the component, max 100 characters
/// </summary>
/// <remarks>
/// For: Buttons, Select Menus
/// </remarks>
[JsonPropertyName("custom_id")]
public string CustomId { get; set; }
/// <summary>
/// whether the component is disabled, default false
/// </summary>
/// <remarks>
/// For: Buttons, Select Menus
/// </remarks>
[JsonPropertyName("disabled")]
public bool Disabled { get; set; }
/// <summary>
/// one of button styles
/// </summary>
/// <remarks>
/// For: Buttons
/// </remarks>
[JsonPropertyName("style")]
public ButtonStyle Style { get; set; }
/// <summary>
/// text that appears on the button, max 80 characters
/// </summary>
/// <remarks>
/// For: Buttons
/// </remarks>
[JsonPropertyName("label")]
public string Label { get; set; }
/// <summary>
/// name, id, and animated
/// </summary>
/// <remarks>
/// For: Buttons
/// </remarks>
[JsonPropertyName("emoji")]
public ComponentEmoji Emoji { get; set; }
/// <summary>
/// a url for link-style buttons
/// </summary>
/// <remarks>
/// For: Buttons
/// </remarks>
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// the choices in the select, max 25
/// </summary>
/// <remarks>
/// For: Select Menus
/// </remarks>
[JsonPropertyName("options")]
public List<SelectOption> Options { get; set; }
/// <summary>
/// custom placeholder text if nothing is selected, max 100 characters
/// </summary>
/// <remarks>
/// For: Select Menus
/// </remarks>
[JsonPropertyName("placeholder")]
public string Placeholder { get; set; }
/// <summary>
/// the minimum number of items that must be chosen; default 1, min 0, max 25
/// </summary>
/// <remarks>
/// For: Select Menus
/// </remarks>
[JsonPropertyName("min_values")]
public int MinValues { get; set; }
/// <summary>
/// the maximum number of items that can be chosen; default 1, max 25
/// </summary>
/// <remarks>
/// For: Select Menus
/// </remarks>
[JsonPropertyName("max_values")]
public int MaxValues { get; set; }
/// <summary>
/// a list of child components
/// </summary>
[JsonPropertyName("components")]
public List<Component> Components { get; set; }
}
}

View file

@ -1,33 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.MessageComponents
{
/// <remarks>
/// Partial emoji with just id, name, and animated
/// </remarks>
/// <see href="https://discord.com/developers/docs/resources/emoji#emoji-object" />
public record ComponentEmoji
{
/// <summary>
/// emoji name
/// </summary>
/// <remarks>
/// can be null only in reaction emoji objects
/// </remarks>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// emoji id
/// </summary>
/// <see href="https://discord.com/developers/docs/reference#image-formatting" />
[JsonPropertyName("id")]
public string Id { get; set; }
/// <summary>
/// whether this emoji can be used, may be false due to loss of Server Boosts
/// </summary>
[JsonPropertyName("animated")]
public bool Animated { get; set; }
}
}

View file

@ -1,21 +0,0 @@
namespace Geekbot.Core.Interactions.MessageComponents
{
/// <see href="https://discord.com/developers/docs/interactions/message-components#component-object-component-types" />
public enum ComponentType
{
/// <summary>
/// A container for other components
/// </summary>
ActionRow = 1,
/// <summary>
/// A button object
/// </summary>
Button = 2,
/// <summary>
/// A select menu for picking from choices
/// </summary>
SelectMenu = 3
}
}

View file

@ -1,38 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.MessageComponents
{
/// <see href="https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure" />
public record SelectOption
{
/// <summary>
/// the user-facing name of the option, max 100 characters
/// </summary>
[JsonPropertyName("label")]
public string Label { get; set; }
/// <summary>
/// the dev-define value of the option, max 100 characters
/// </summary>
[JsonPropertyName("value")]
public string Value { get; set; }
/// <summary>
/// an additional description of the option, max 100 characters
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// id, name, and animated
/// </summary>
[JsonPropertyName("emoji")]
public ComponentEmoji Emoji { get; set; }
/// <summary>
/// will render this option as selected by default
/// </summary>
[JsonPropertyName("default")]
public bool Default { get; set; }
}
}

View file

@ -1,77 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Geekbot.Core.Interactions.Resolved;
namespace Geekbot.Core.Interactions.Request
{
/// <see href="https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure" />
public record Interaction
{
/// <summary>
/// id of the interaction
/// </summary>
[JsonPropertyName("id")]
public string Id { get; init; }
/// <summary>
/// id of the application this interaction is for
/// </summary>
[JsonPropertyName("application_id")]
public string ApplicationId { get; init; }
/// <summary>
/// the type of interaction
/// </summary>
[JsonPropertyName("type")]
public InteractionType Type { get; init; }
/// <summary>
/// the command data payload
/// </summary>
[JsonPropertyName("data")]
public InteractionData Data { get; init; }
/// <summary>
/// the guild it was sent from
/// </summary>
[JsonPropertyName("guild_id")]
public string GuildId { get; init; }
/// <summary>
/// the channel it was sent from
/// </summary>
[JsonPropertyName("channel_id")]
public string ChannelId { get; init; }
/// <summary>
/// guild member data for the invoking user, including permissions
/// </summary>
[JsonPropertyName("member")]
public Member Member { get; init; }
/// <summary>
/// user object for the invoking user, if invoked in a DM
/// </summary>
[JsonPropertyName("user")]
public User User { get; init; }
/// <summary>
/// a continuation token for responding to the interaction
/// </summary>
[JsonPropertyName("token")]
public string Token { get; init; }
/// <summary>
/// read-only property, always 1
/// </summary>
[JsonPropertyName("version")]
public int Version { get; init; }
/// <summary>
/// object for components, the message they were attached to
/// </summary>
[JsonPropertyName("message")]
public Message Message { get; init; }
}
}

View file

@ -1,109 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Geekbot.Core.Interactions.ApplicationCommand;
using Geekbot.Core.Interactions.MessageComponents;
namespace Geekbot.Core.Interactions.Request
{
/// <see href="https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-data-structure" />
public class InteractionData
{
/// <summary>
/// the ID of the invoked command
/// </summary>
/// <remarks>
/// For: Application Command
/// </remarks>
[JsonPropertyName("id")]
public string Id { get; init; }
/// <summary>
/// the name of the invoked command
/// </summary>
/// <remarks>
/// For: Application Command
/// </remarks>
[JsonPropertyName("name")]
public string Name { get; init; }
/// <summary>
/// the type of the invoked command
/// </summary>
/// <remarks>
/// For: Application Command
/// </remarks>
[JsonPropertyName("type")]
public CommandType Type { get; init; }
/// <summary>
/// converted users + roles + channels
/// </summary>
/// <remarks>
/// For: Application Command
/// </remarks>
[JsonPropertyName("resolved")]
public InteractionResolvedData Resolved { get; init; }
/// <summary>
/// of application command interaction data option the params + values from the user
/// </summary>
/// <remarks>
/// For: Application Command
/// </remarks>
[JsonPropertyName("options")]
public List<InteractionOption> Options { get; init; }
/// <summary>
/// the custom_id of the component
/// </summary>
/// <remarks>
/// For: Component
/// </remarks>
[JsonPropertyName("custom_id")]
public string CustomId { get; init; }
/// <summary>
/// the type of the component
/// </summary>
/// <remarks>
/// For: Component
/// </remarks>
[JsonPropertyName("component_type")]
public ComponentType ComponentType { get; init; }
/// <summary>
/// of select option values the values the user selected
/// </summary>
/// <remarks>
/// Component (Select)
/// </remarks>
[JsonPropertyName("values")]
public string Values { get; init; }
/// <summary>
/// id the of user or message targetted by a user or message command
/// </summary>
/// <remarks>
/// For: Application Command
/// Present in: User Command, Message Command
/// </remarks>
[JsonPropertyName("target_id")]
public string TargetId { get; init; }
public string GetTargetNickname()
{
return GetUserNickename(TargetId);
}
public string GetUserNickename(string userId)
{
var username = Resolved.Members[userId].Nick;
if (string.IsNullOrEmpty(username))
{
username = Resolved.Users[userId].Username;
}
return username;
}
}
}

View file

@ -1,35 +0,0 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Geekbot.Core.Interactions.ApplicationCommand;
namespace Geekbot.Core.Interactions.Request
{
/// <see href="https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-interaction-data-option-structure" />
public record InteractionOption
{
/// <summary>
/// the name of the parameter
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// value of application command option type
/// </summary>
[JsonPropertyName("type")]
public OptionType Type { get; set; }
/// <summary>
/// the value of the pair
/// </summary>
[JsonPropertyName("value")]
public JsonElement Value { get; set; }
/// <summary>
/// present if this option is a group or subcommand
/// </summary>
[JsonPropertyName("options")]
public List<InteractionOption> Options { get; set; }
}
}

View file

@ -1,24 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Geekbot.Core.Interactions.Resolved;
namespace Geekbot.Core.Interactions.Request
{
public class InteractionResolvedData
{
[JsonPropertyName("users")]
public Dictionary<string, User> Users { get; set; }
[JsonPropertyName("members")]
public Dictionary<string, Member> Members { get; set; }
[JsonPropertyName("roles")]
public Dictionary<string, Role> Roles { get; set; }
[JsonPropertyName("channels")]
public Dictionary<string, Channel> Channels { get; set; }
[JsonPropertyName("messages")]
public Dictionary<string, Message> Messages { get; set; }
}
}

View file

@ -1,10 +0,0 @@
namespace Geekbot.Core.Interactions.Request
{
/// <see href="https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-type" />
public enum InteractionType
{
Ping = 1,
ApplicationCommand = 2,
MessageComponent = 3,
}
}

View file

@ -1,34 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record Attachment
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("filename")]
public string Filename { get; set; }
[JsonPropertyName("content_type")]
public string ContentType { get; set; }
[JsonPropertyName("size")]
public int Size { get; set; }
[JsonPropertyName("url")]
public string Url { get; set; }
[JsonPropertyName("proxy_url")]
public string ProxyUrl { get; set; }
[JsonPropertyName("height")]
public int Height { get; set; }
[JsonPropertyName("width")]
public int Width { get; set; }
[JsonPropertyName("ephemeral")]
public bool Ephemeral { get; set; }
}
}

View file

@ -1,25 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record Channel
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
public ChannelType Type { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("parent_id")]
public string ParentId { get; set; }
[JsonPropertyName("thread_metadata")]
public ThreadMetadata ThreadMetadata { get; set; }
[JsonPropertyName("permissions")]
public string Permissions { get; set; }
}
}

View file

@ -1,17 +0,0 @@
namespace Geekbot.Core.Interactions.Resolved
{
public enum ChannelType
{
GuildText = 0,
Dm = 1,
GuildVoice = 2,
GroupDm = 3,
GuildCategory = 4,
GuildNews = 5,
GuildStore = 6,
GuildNewsThread = 10,
GuildPublicThread = 11,
GuildPrivateThread = 12,
GuildStageVoice = 13,
}
}

View file

@ -1,32 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record Emoji
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("roles")]
public List<string> Roles { get; set; }
[JsonPropertyName("user")]
public User User { get; set; }
[JsonPropertyName("require_colons")]
public bool RequireColons { get; set; }
[JsonPropertyName("managed")]
public bool Managed { get; set; }
[JsonPropertyName("animated")]
public bool Animated { get; set; }
[JsonPropertyName("available")]
public bool Available { get; set; }
}
}

View file

@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record Member
{
[JsonPropertyName("nick")]
public string Nick { get; set; }
[JsonPropertyName("roles")]
public List<string> Roles { get; set; }
[JsonPropertyName("joined_at")]
public DateTime JoinedAt { get; set; }
[JsonPropertyName("premium_since")]
public DateTime? PremiumSince { get; set; }
[JsonPropertyName("pending")]
public bool Pending { get; set; }
[JsonPropertyName("permissions")]
public string Permissions { get; set; }
[JsonPropertyName("user")]
public User User { get; set; }
}
}

View file

@ -1,102 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record Message
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("channel_id")]
public string ChannelId { get; set; }
[JsonPropertyName("guild_id")]
public string GuildId { get; set; }
[JsonPropertyName("author")]
public User Author { get; set; }
[JsonPropertyName("member")]
public Member Member { get; set; }
[JsonPropertyName("content")]
public string Content { get; set; }
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; }
[JsonPropertyName("edited_timestamp")]
public DateTime? EditedTimestamp { get; set; }
[JsonPropertyName("tts")]
public bool Tts { get; set; }
[JsonPropertyName("mention_everyone")]
public bool MentionEveryone { get; set; }
[JsonPropertyName("mentions")]
public List<User> Mentions { get; set; }
[JsonPropertyName("mention_roles")]
public List<Role> MentionRoles { get; set; }
[JsonPropertyName("mention_channels")]
public List<Channel> MentionChannels { get; set; }
[JsonPropertyName("attachments")]
public List<Attachment> Attachments { get; set; }
[JsonPropertyName("embeds")]
public List<Embed.Embed> Embeds { get; set; }
[JsonPropertyName("reactions")]
public List<Reaction> Reactions { get; set; }
// [JsonPropertyName("nonce")]
// public string Nonce { get; set; }
[JsonPropertyName("pinned")]
public bool Pinned { get; set; }
[JsonPropertyName("webhook_id")]
public string WebhookId { get; set; }
[JsonPropertyName("type")]
public MessageType Type { get; set; }
// [JsonPropertyName("activity")]
// public string Activity { get; set; }
// [JsonPropertyName("application")]
// public string Application { get; set; }
[JsonPropertyName("application_id")]
public string ApplicationId { get; set; }
// [JsonPropertyName("message_reference")]
// public string MessageReference { get; set; }
[JsonPropertyName("flags")]
public int Flags { get; set; }
[JsonPropertyName("referenced_message")]
public Message ReferencedMessage { get; set; }
[JsonPropertyName("interaction")]
public MessageInteraction Interaction { get; set; }
[JsonPropertyName("thread")]
public Channel Thread { get; set; }
// [JsonPropertyName("components")]
// public string Components { get; set; }
// [JsonPropertyName("sticker_items")]
// public string StickerItems { get; set; }
// [JsonPropertyName("stickers")]
// public string Stickers { get; set; }
}
}

View file

@ -1,20 +0,0 @@
using System.Text.Json.Serialization;
using Geekbot.Core.Interactions.Request;
namespace Geekbot.Core.Interactions.Resolved
{
public record MessageInteraction
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
public InteractionType Type { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("user")]
public User User { get; set; }
}
}

View file

@ -1,29 +0,0 @@
namespace Geekbot.Core.Interactions.Resolved
{
public enum MessageType
{
Default = 0,
RecipientAdd = 1,
RecipientRemove = 2,
Call = 3,
ChannelNameChange = 4,
ChannelIconChange = 5,
ChannelPinnedMessage = 6,
GuildMemberJoin = 7,
UserPremiumGuildSubscription = 8,
UserPremiumGuildSubscriptionTier1 = 9,
UserPremiumGuildSubscriptionTier2 = 10,
UserPremiumGuildSubscriptionTier3 = 11,
ChannelFollowAdd = 12,
GuildDiscoveryDisqualified = 14,
GuildDiscoveryRequalified = 15,
GuildDiscoveryGracePeriodInitialWarning = 16,
GuildDiscoveryGracePeriodFinalWarning = 17,
ThreadCreated = 18,
Reply = 19,
ChatInputCommand = 20,
ThreadStarterMessage = 21,
GuildInviteReminder = 22,
ContextMenuCommand = 23,
}
}

View file

@ -1,16 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public struct Reaction
{
[JsonPropertyName("count")]
public int Count { get; set; }
[JsonPropertyName("me")]
public bool Me { get; set; }
[JsonPropertyName("emoji")]
public Emoji emoji { get; set; }
}
}

View file

@ -1,34 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record Role
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("color")]
public int Color { get; set; }
[JsonPropertyName("hoist")]
public bool Hoist { get; set; }
[JsonPropertyName("position")]
public int Position { get; set; }
[JsonPropertyName("permissions")]
public string Permissions { get; set; }
[JsonPropertyName("managed")]
public bool Managed { get; set; }
[JsonPropertyName("mentionable")]
public bool Mentionable { get; set; }
[JsonPropertyName("tags")]
public RoleTag Tags { get; set; }
}
}

View file

@ -1,16 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record RoleTag
{
[JsonPropertyName("bot_id")]
public string BotId { get; set; }
[JsonPropertyName("integration_id")]
public string IntegrationId { get; set; }
[JsonPropertyName("premium_subscriber")]
public bool PremiumSubscriber { get; set; }
}
}

View file

@ -1,23 +0,0 @@
using System;
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public record ThreadMetadata
{
[JsonPropertyName("archived")]
public bool Archived { get; set; }
[JsonPropertyName("auto_archive_duration")]
public int AutoArchiveDuration { get; set; }
[JsonPropertyName("archive_timestamp")]
public DateTime ArchiveTimestamp { get; set; }
[JsonPropertyName("locked")]
public bool Locked { get; set; }
[JsonPropertyName("invitable")]
public bool Invitable { get; set; }
}
}

View file

@ -1,64 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Resolved
{
public class User
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("discriminator")]
public string Discriminator { get; set; }
[JsonPropertyName("avatar")]
public string Avatar { get; set; }
[JsonPropertyName("bot")]
public bool Bot { get; set; }
[JsonPropertyName("system")]
public bool System { get; set; }
[JsonPropertyName("mfa_enabled")]
public bool MfaEnabled { get; set; }
[JsonPropertyName("banner")]
public string Banner { get; set; }
[JsonPropertyName("accent_color")]
public int AccentColor { get; set; }
[JsonPropertyName("locale")]
public string Locale { get; set; }
[JsonPropertyName("verified")]
public bool Verified { get; set; }
[JsonPropertyName("email")]
public string Email { get; set; }
[JsonPropertyName("flags")]
public int Flags { get; set; }
[JsonPropertyName("premium_type")]
public int PremiumType { get; set; }
[JsonPropertyName("public_flags")]
public int PublicFlags { get; set; }
public string Mention => $"<@{Id}>";
public string GetAvatarUrl()
{
if (string.IsNullOrEmpty(Avatar))
{
return "https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png";
}
return $"https://cdn.discordapp.com/avatars/{Id}/{Avatar}.webp";
}
}
}

View file

@ -1,23 +0,0 @@
using System.Text.Json.Serialization;
namespace Geekbot.Core.Interactions.Response
{
/// <summary>
/// Interactions--both receiving and responding--are webhooks under the hood. So responding to an Interaction is just like sending a webhook request!
/// </summary>
/// <see href="https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-response-structure"/>
public record InteractionResponse
{
/// <summary>
/// the type of response
/// </summary>
[JsonPropertyName("type")]
public InteractionResponseType Type { get; set; }
/// <summary>
/// an optional response message
/// </summary>
[JsonPropertyName("data")]
public InteractionResponseData Data { get; set; }
}
}

View file

@ -1,51 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Discord;
using Geekbot.Core.Interactions.MessageComponents;
using Embed = Geekbot.Core.Interactions.Embed.Embed;
namespace Geekbot.Core.Interactions.Response
{
/// <remarks>
/// Not all message fields are currently supported.
/// </remarks>
/// <see href="https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-data-structure"/>
public record InteractionResponseData
{
/// <summary>
/// is the response TTS
/// </summary>
[JsonPropertyName("tts")]
public bool Tts { get; set; } = false;
/// <summary>
/// message content
/// </summary>
[JsonPropertyName("content")]
public string Content { get; set; }
/// <summary>
/// supports up to 10 embeds
/// </summary>
[JsonPropertyName("embeds")]
public List<Embed.Embed> Embeds { get; set; }
/// <summary>
/// allowed mentions object
/// </summary>
[JsonPropertyName("allowed_mentions")]
public AllowedMentions AllowedMentions { get; set; }
/// <summary>
/// interaction callback data flags
/// </summary>
[JsonPropertyName("flags")]
public int Flags { get; set; }
/// <summary>
/// message components
/// </summary>
[JsonPropertyName("components")]
public List<Component> Components { get; set; }
}
}

View file

@ -1,34 +0,0 @@
namespace Geekbot.Core.Interactions.Response
{
/// <see href="https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type"/>
public enum InteractionResponseType
{
/// <summary>
/// ACK a Ping
/// </summary>
Pong = 1,
/// <summary>
/// respond to an interaction with a message
/// </summary>
ChannelMessageWithSource = 4,
/// <summary>
/// ACK an interaction and edit a response later, the user sees a loading state
/// </summary>
DeferredChannelMessageWithSource = 5,
/// <summary>
/// for components, ACK an interaction and edit the original message later; the user does not see a loading state
/// </summary>
DeferredUpdateMessage = 6,
/// <summary>
/// for components, edit the message the component was attached to
/// </summary>
/// <remarks>
/// Only valid for component-based interactions
/// </remarks>
UpdateMessage = 7,
}
}