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" />
|
<ProjectReference Include="..\Core\Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Sodium.Core" Version="1.2.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -31,7 +31,10 @@ namespace Geekbot.Web
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddControllers();
|
services.AddControllers().AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||||
|
});
|
||||||
services.AddCors(options =>
|
services.AddCors(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy("AllowSpecificOrigin",
|
options.AddPolicy("AllowSpecificOrigin",
|
||||||
|
|
Loading…
Reference in a new issue