Merge branch 'feat/webhooks' into main
This commit is contained in:
		@@ -1,3 +1,5 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
using Myriad.Extensions;
 | 
			
		||||
@@ -9,14 +11,15 @@ using PluralKit.Core;
 | 
			
		||||
 | 
			
		||||
namespace PluralKit.Bot
 | 
			
		||||
{
 | 
			
		||||
    public class Token
 | 
			
		||||
    public class Api
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IDatabase _db;
 | 
			
		||||
        private readonly ModelRepository _repo;
 | 
			
		||||
        public Token(IDatabase db, ModelRepository repo)
 | 
			
		||||
        private readonly DispatchService _dispatch;
 | 
			
		||||
        private static readonly Regex _webhookRegex = new("https://(?:\\w+.)?discord(?:app)?.com/api(?:/v.*)?/webhooks/(.*)");
 | 
			
		||||
        public Api(ModelRepository repo, DispatchService dispatch)
 | 
			
		||||
        {
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _repo = repo;
 | 
			
		||||
            _dispatch = dispatch;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task GetToken(Context ctx)
 | 
			
		||||
@@ -91,5 +94,63 @@ namespace PluralKit.Bot
 | 
			
		||||
                    await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task SystemWebhook(Context ctx)
 | 
			
		||||
        {
 | 
			
		||||
            ctx.CheckSystem().CheckDMContext();
 | 
			
		||||
 | 
			
		||||
            if (!ctx.HasNext(false))
 | 
			
		||||
            {
 | 
			
		||||
                if (ctx.System.WebhookUrl == null)
 | 
			
		||||
                    await ctx.Reply("Your system does not have a webhook URL set. Set one with `pk;system webhook <url>`!");
 | 
			
		||||
                else
 | 
			
		||||
                    await ctx.Reply($"Your system's webhook URL is <{ctx.System.WebhookUrl}>.");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (await ctx.MatchClear("your system's webhook URL"))
 | 
			
		||||
            {
 | 
			
		||||
                await _repo.UpdateSystem(ctx.System.Id, new()
 | 
			
		||||
                {
 | 
			
		||||
                    WebhookUrl = null,
 | 
			
		||||
                    WebhookToken = null,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                await ctx.Reply($"{Emojis.Success} System webhook URL removed.");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var newUrl = ctx.RemainderOrNull();
 | 
			
		||||
            if (!await DispatchExt.ValidateUri(newUrl))
 | 
			
		||||
                throw new PKError($"The URL {newUrl.AsCode()} is invalid or I cannot access it. Are you sure this is a valid, publicly accessible URL?");
 | 
			
		||||
 | 
			
		||||
            if (_webhookRegex.IsMatch(newUrl))
 | 
			
		||||
                throw new PKError("PluralKit does not currently support setting a Discord webhook URL as your system's webhook URL.");
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await _dispatch.DoPostRequest(ctx.System.Id, newUrl, null, true);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                throw new PKError($"Could not verify that the new URL is working: {e.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var newToken = StringUtils.GenerateToken();
 | 
			
		||||
 | 
			
		||||
            await _repo.UpdateSystem(ctx.System.Id, new()
 | 
			
		||||
            {
 | 
			
		||||
                WebhookUrl = newUrl,
 | 
			
		||||
                WebhookToken = newToken,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            await ctx.Reply($"{Emojis.Success} Successfully the new webhook URL for your system."
 | 
			
		||||
                + $"\n\n{Emojis.Warn} The following token is used to authenticate requests from PluralKit to you."
 | 
			
		||||
                + " If it leaks, you should clear and re-set the webhook URL to get a new token."
 | 
			
		||||
                + "\ntodo: add link to docs or something"
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            await ctx.Reply(newToken);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -152,9 +152,9 @@ namespace PluralKit.Bot
 | 
			
		||||
                return ctx.Execute<SystemLink>(Unlink, m => m.UnlinkAccount(ctx));
 | 
			
		||||
            if (ctx.Match("token"))
 | 
			
		||||
                if (ctx.Match("refresh", "renew", "invalidate", "reroll", "regen"))
 | 
			
		||||
                    return ctx.Execute<Token>(TokenRefresh, m => m.RefreshToken(ctx));
 | 
			
		||||
                    return ctx.Execute<Api>(TokenRefresh, m => m.RefreshToken(ctx));
 | 
			
		||||
                else
 | 
			
		||||
                    return ctx.Execute<Token>(TokenGet, m => m.GetToken(ctx));
 | 
			
		||||
                    return ctx.Execute<Api>(TokenGet, m => m.GetToken(ctx));
 | 
			
		||||
            if (ctx.Match("import"))
 | 
			
		||||
                return ctx.Execute<ImportExport>(Import, m => m.Import(ctx));
 | 
			
		||||
            if (ctx.Match("export"))
 | 
			
		||||
@@ -286,6 +286,8 @@ namespace PluralKit.Bot
 | 
			
		||||
                await ctx.Execute<SystemEdit>(SystemAvatar, m => m.Avatar(ctx));
 | 
			
		||||
            else if (ctx.Match("delete", "remove", "destroy", "erase", "yeet"))
 | 
			
		||||
                await ctx.Execute<SystemEdit>(SystemDelete, m => m.Delete(ctx));
 | 
			
		||||
            else if (ctx.Match("webhook", "hook"))
 | 
			
		||||
                await ctx.Execute<Api>(null, m => m.SystemWebhook(ctx));
 | 
			
		||||
            else if (ctx.Match("timezone", "tz"))
 | 
			
		||||
                await ctx.Execute<SystemEdit>(SystemTimezone, m => m.SystemTimezone(ctx));
 | 
			
		||||
            else if (ctx.Match("proxy"))
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,8 @@ using Dapper;
 | 
			
		||||
 | 
			
		||||
using Humanizer;
 | 
			
		||||
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
 | 
			
		||||
using NodaTime;
 | 
			
		||||
 | 
			
		||||
using Myriad.Builders;
 | 
			
		||||
@@ -24,13 +26,15 @@ namespace PluralKit.Bot
 | 
			
		||||
        private readonly ModelRepository _repo;
 | 
			
		||||
        private readonly EmbedService _embeds;
 | 
			
		||||
        private readonly HttpClient _client;
 | 
			
		||||
        private readonly DispatchService _dispatch;
 | 
			
		||||
 | 
			
		||||
        public Groups(IDatabase db, ModelRepository repo, EmbedService embeds, HttpClient client)
 | 
			
		||||
        public Groups(IDatabase db, ModelRepository repo, EmbedService embeds, HttpClient client, DispatchService dispatch)
 | 
			
		||||
        {
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _repo = repo;
 | 
			
		||||
            _embeds = embeds;
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _dispatch = dispatch;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task CreateGroup(Context ctx)
 | 
			
		||||
@@ -59,6 +63,12 @@ namespace PluralKit.Bot
 | 
			
		||||
 | 
			
		||||
            var newGroup = await _repo.CreateGroup(ctx.System.Id, groupName);
 | 
			
		||||
 | 
			
		||||
            _ = _dispatch.Dispatch(newGroup.Id, new UpdateDispatchData()
 | 
			
		||||
            {
 | 
			
		||||
                Event = DispatchEvent.CREATE_GROUP,
 | 
			
		||||
                EventData = JObject.FromObject(new { name = groupName }),
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
                .Description($"Your new group, **{groupName}**, has been created, with the group ID **`{newGroup.Hid}`**.\nBelow are a couple of useful commands:")
 | 
			
		||||
                .Field(new("View the group card", $"> pk;group **{newGroup.Reference()}**"))
 | 
			
		||||
 
 | 
			
		||||
@@ -21,13 +21,15 @@ namespace PluralKit.Bot
 | 
			
		||||
        private readonly ModelRepository _repo;
 | 
			
		||||
        private readonly EmbedService _embeds;
 | 
			
		||||
        private readonly HttpClient _client;
 | 
			
		||||
        private readonly DispatchService _dispatch;
 | 
			
		||||
 | 
			
		||||
        public Member(EmbedService embeds, IDatabase db, ModelRepository repo, HttpClient client)
 | 
			
		||||
        public Member(EmbedService embeds, IDatabase db, ModelRepository repo, HttpClient client, DispatchService dispatch)
 | 
			
		||||
        {
 | 
			
		||||
            _embeds = embeds;
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _repo = repo;
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _dispatch = dispatch;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task NewMember(Context ctx)
 | 
			
		||||
@@ -62,12 +64,20 @@ namespace PluralKit.Bot
 | 
			
		||||
            // Try to match an image attached to the message
 | 
			
		||||
            var avatarArg = ctx.Message.Attachments.FirstOrDefault();
 | 
			
		||||
            Exception imageMatchError = null;
 | 
			
		||||
            bool sentDispatch = false;
 | 
			
		||||
            if (avatarArg != null)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await AvatarUtils.VerifyAvatarOrThrow(_client, avatarArg.Url);
 | 
			
		||||
                    await _repo.UpdateMember(member.Id, new MemberPatch { AvatarUrl = avatarArg.Url });
 | 
			
		||||
                    await _db.Execute(conn => _repo.UpdateMember(member.Id, new MemberPatch { AvatarUrl = avatarArg.Url }, conn));
 | 
			
		||||
 | 
			
		||||
                    _ = _dispatch.Dispatch(member.Id, new()
 | 
			
		||||
                    {
 | 
			
		||||
                        Event = DispatchEvent.CREATE_MEMBER,
 | 
			
		||||
                        EventData = JObject.FromObject(new { name = memberName, avatar_url = avatarArg.Url }),
 | 
			
		||||
                    });
 | 
			
		||||
                    sentDispatch = true;
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception e)
 | 
			
		||||
                {
 | 
			
		||||
@@ -75,6 +85,13 @@ namespace PluralKit.Bot
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!sentDispatch)
 | 
			
		||||
                _ = _dispatch.Dispatch(member.Id, new()
 | 
			
		||||
                {
 | 
			
		||||
                    Event = DispatchEvent.CREATE_MEMBER,
 | 
			
		||||
                    EventData = JObject.FromObject(new { name = memberName }),
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            // Send confirmation and space hint
 | 
			
		||||
            await ctx.Reply($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member");
 | 
			
		||||
            // todo: move this to ModelRepository
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user