Add initial interaction support
This commit is contained in:
parent
85d06b76e0
commit
d81fb2a3d9
20 changed files with 336 additions and 1 deletions
90
src/Web/Controllers/Interactions/InteractionController.cs
Normal file
90
src/Web/Controllers/Interactions/InteractionController.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.GlobalSettings;
|
||||
using Geekbot.Web.Controllers.Interactions.Model;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sodium;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions
|
||||
{
|
||||
public class InteractionController : Controller
|
||||
{
|
||||
private readonly byte[] publicKeyBytes;
|
||||
|
||||
public InteractionController(IGlobalSettings globalSettings)
|
||||
{
|
||||
var publicKey = globalSettings.GetKey("DiscordPublicKey");
|
||||
publicKeyBytes = Convert.FromHexString(publicKey.AsSpan());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/interactions")]
|
||||
public async Task<IActionResult> HandleInteraction(
|
||||
[FromHeader(Name = "X-Signature-Ed25519")] string signature,
|
||||
[FromHeader(Name = "X-Signature-Timestamp")] string timestamp
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(timestamp))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
Request.EnableBuffering();
|
||||
if (!(await HasValidSignature(signature, timestamp)))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (Request.Body.CanSeek) Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
var interaction = await JsonSerializer.DeserializeAsync<Interaction>(Request.Body, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }).ConfigureAwait(false);
|
||||
if (interaction is null) throw new JsonException("Failed to deserialize JSON body");
|
||||
|
||||
return (interaction.Type, interaction.Version) switch
|
||||
{
|
||||
(InteractionType.Ping, 1) => Ping(),
|
||||
(InteractionType.ApplicationCommand, 1) => ApplicationCommand(interaction),
|
||||
(InteractionType.MessageComponent, 1) => MessageComponent(interaction),
|
||||
_ => StatusCode(501)
|
||||
};
|
||||
}
|
||||
|
||||
private IActionResult Ping()
|
||||
{
|
||||
var response = new InteractionResponse()
|
||||
{
|
||||
Type = InteractionResponseType.Pong,
|
||||
};
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
private IActionResult ApplicationCommand(Interaction interaction)
|
||||
{
|
||||
return StatusCode(501);
|
||||
}
|
||||
|
||||
private IActionResult MessageComponent(Interaction interaction)
|
||||
{
|
||||
return StatusCode(501);
|
||||
}
|
||||
|
||||
private async Task<bool> HasValidSignature(string signature, string timestamp)
|
||||
{
|
||||
var timestampBytes = Encoding.Default.GetBytes(timestamp);
|
||||
var signatureBytes = Convert.FromHexString(signature.AsSpan());
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
await Request.Body.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||
var body = memoryStream.ToArray();
|
||||
|
||||
var timestampLength = timestampBytes.Length;
|
||||
Array.Resize(ref timestampBytes, timestampLength + body.Length);
|
||||
Array.Copy(body, 0, timestampBytes, timestampLength, body.Length);
|
||||
|
||||
return PublicKeyAuth.VerifyDetached(signatureBytes, timestampBytes, publicKeyBytes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public enum ApplicationCommandOption
|
||||
{
|
||||
SubCommand = 1,
|
||||
SubCommandGroup = 2,
|
||||
String = 3,
|
||||
Integer = 4,
|
||||
Boolean = 5,
|
||||
User = 6,
|
||||
Channel = 7,
|
||||
Role = 8,
|
||||
Mentionable = 9,
|
||||
Number = 10,
|
||||
}
|
||||
}
|
22
src/Web/Controllers/Interactions/Model/Interaction.cs
Normal file
22
src/Web/Controllers/Interactions/Model/Interaction.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public record Interaction
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string ApplicationId { get; init; }
|
||||
[Required]
|
||||
public InteractionType Type { get; set; }
|
||||
public InteractionData Data { get; init; }
|
||||
public string GuildId { get; init; }
|
||||
public string Name { get; init; }
|
||||
public string Description { get; init; }
|
||||
public List<InteractionOption> Options { get; init; }
|
||||
public bool DefaultPermission { get; init; }
|
||||
[Required]
|
||||
public int Version { get; set; }
|
||||
}
|
||||
}
|
14
src/Web/Controllers/Interactions/Model/InteractionData.cs
Normal file
14
src/Web/Controllers/Interactions/Model/InteractionData.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public record InteractionData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Type { get; set;}
|
||||
public InteractionResolvedData Resolved { get; set; }
|
||||
public List<InteractionOption> Options { get; set; }
|
||||
public string TargetId { get; set; }
|
||||
}
|
||||
}
|
12
src/Web/Controllers/Interactions/Model/InteractionOption.cs
Normal file
12
src/Web/Controllers/Interactions/Model/InteractionOption.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public record InteractionOption
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public ApplicationCommandOption Type { get; set; }
|
||||
public string Value { get; set; }
|
||||
public List<InteractionOption> Options { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using Discord;
|
||||
using Geekbot.Web.Controllers.Interactions.Model.Resolved;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public class InteractionResolvedData
|
||||
{
|
||||
public Dictionary<string, User> Users { get; set; }
|
||||
public Dictionary<string, Member> Members { get; set; }
|
||||
public Dictionary<string, Roles> Roles { get; set; }
|
||||
public Dictionary<string, Channel> Channels { get; set; }
|
||||
// public Dictionary<string, IMessage> Messages { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public record InteractionResponse
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public InteractionResponseType Type { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public InteractionData Data { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using Discord;
|
||||
using Geekbot.Web.Controllers.Interactions.Model.MessageComponents;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public record InteractionResponseData
|
||||
{
|
||||
public bool Tts { get; set; } = false;
|
||||
public string Content { get; set; }
|
||||
public List<string> Embeds { get; set; }
|
||||
public AllowedMentions AllowedMentions { get; set; }
|
||||
public int Flags { get; set; }
|
||||
public List<Component> Components { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public enum InteractionResponseType
|
||||
{
|
||||
Pong = 1,
|
||||
ChannelMessageWithSource = 4,
|
||||
DeferredChannelMessageWithSource = 5,
|
||||
DeferredUpdateMessage = 6,
|
||||
UpdateMessage = 7,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model
|
||||
{
|
||||
public enum InteractionType
|
||||
{
|
||||
Ping = 1,
|
||||
ApplicationCommand = 2,
|
||||
MessageComponent = 3,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model.MessageComponents
|
||||
{
|
||||
public record Component
|
||||
{
|
||||
|
||||
}
|
||||
}
|
12
src/Web/Controllers/Interactions/Model/Resolved/Channel.cs
Normal file
12
src/Web/Controllers/Interactions/Model/Resolved/Channel.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model.Resolved
|
||||
{
|
||||
public record Channel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public ChannelType Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public ThreadMetadata ThreadMetadata { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model.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,
|
||||
}
|
||||
}
|
16
src/Web/Controllers/Interactions/Model/Resolved/Member.cs
Normal file
16
src/Web/Controllers/Interactions/Model/Resolved/Member.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model.Resolved
|
||||
{
|
||||
public record Member
|
||||
{
|
||||
// public User User { get; set; }
|
||||
public string Nick { get; set; }
|
||||
public List<string> Roles { get; set; }
|
||||
public DateTime JoinedAt { get; set; }
|
||||
public DateTime PremiumSince { get; set; }
|
||||
public bool Pending { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model.Resolved
|
||||
{
|
||||
public record RoleTag
|
||||
{
|
||||
public string BotId { get; set; }
|
||||
public string IntegrationId { get; set; }
|
||||
public bool PremiumSubscriber { get; set; }
|
||||
}
|
||||
}
|
15
src/Web/Controllers/Interactions/Model/Resolved/Roles.cs
Normal file
15
src/Web/Controllers/Interactions/Model/Resolved/Roles.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model.Resolved
|
||||
{
|
||||
public record Roles
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Color { get; set; }
|
||||
public bool Hoist { get; set; }
|
||||
public int Position { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
public bool Managed { get; set; }
|
||||
public bool Mentionable { get; set; }
|
||||
public RoleTag Tags { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Web.Controllers.Interactions.Model.Resolved
|
||||
{
|
||||
public record ThreadMetadata
|
||||
{
|
||||
public bool Archived { get; set; }
|
||||
public int AutoArchiveDuration { get; set; }
|
||||
public DateTime ArchiveTimestamp { get; set; }
|
||||
public bool Locked { get; set; }
|
||||
public bool Invitable { get; set; }
|
||||
}
|
||||
}
|
21
src/Web/Controllers/Interactions/Model/Resolved/User.cs
Normal file
21
src/Web/Controllers/Interactions/Model/Resolved/User.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace Geekbot.Web.Controllers.Interactions.Model.Resolved
|
||||
{
|
||||
public record User
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Discriminator { get; set; }
|
||||
public string Avatar { get; set; }
|
||||
public bool Bot { get; set; }
|
||||
public bool System { get; set; }
|
||||
public bool MfaEnabled { get; set; }
|
||||
public string Banner { get; set; }
|
||||
public int AccentColor { get; set; }
|
||||
public string Locale { get; set; }
|
||||
public bool Verified { get; set; }
|
||||
public string Email { get; set; }
|
||||
public int Flags { get; set; }
|
||||
public int PremiumType { get; set; }
|
||||
public int PublicFlags { get; set; }
|
||||
}
|
||||
}
|
|
@ -16,4 +16,8 @@
|
|||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Sodium.Core" Version="1.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -31,7 +31,10 @@ namespace Geekbot.Web
|
|||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddControllers();
|
||||
services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
});
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowSpecificOrigin",
|
||||
|
|
Loading…
Reference in a new issue