feat(webhooks): init, add service/models, add JSON to patch objects

This commit is contained in:
spiral
2021-11-02 06:08:17 -04:00
parent 08c5b78cc2
commit 71aec0d419
14 changed files with 551 additions and 2 deletions

View File

@@ -0,0 +1,109 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace PluralKit.Core
{
public enum DispatchEvent
{
PING,
UPDATE_SYSTEM,
CREATE_MEMBER,
UPDATE_MEMBER,
DELETE_MEMBER,
CREATE_GROUP,
UPDATE_GROUP,
UPDATE_GROUP_MEMBERS,
DELETE_GROUP,
LINK_ACCOUNT,
UNLINK_ACCOUNT,
UPDATE_SYSTEM_GUILD,
UPDATE_MEMBER_GUILD,
CREATE_MESSAGE,
CREATE_SWITCH,
UPDATE_SWITCH,
UPDATE_SWITCH_MEMBERS,
DELETE_SWITCH,
DELETE_ALL_SWITCHES,
}
public struct UpdateDispatchData
{
public DispatchEvent Event;
public string SystemId;
public string? EntityId;
public ulong? GuildId;
public string SigningToken;
public JObject? EventData;
}
public static class DispatchExt
{
public static StringContent GetPayloadBody(this UpdateDispatchData data)
{
var o = new JObject();
o.Add("type", data.Event.ToString());
o.Add("signing_token", data.SigningToken);
o.Add("system_id", data.SystemId);
if (data.EntityId != null)
o.Add("id", data.EntityId);
if (data.GuildId != null)
o.Add("guild_id", data.GuildId);
if (data.EventData != null)
o.Add("data", data.EventData);
return new StringContent(JsonConvert.SerializeObject(o));
}
public static JObject ToDispatchJson(this PKMessage msg, string memberRef)
{
var o = new JObject();
o.Add("timestamp", Instant.FromUnixTimeMilliseconds((long)(msg.Mid >> 22) + 1420070400000).FormatExport());
o.Add("id", msg.Mid.ToString());
o.Add("original", msg.OriginalMid.ToString());
o.Add("sender", msg.Sender.ToString());
o.Add("channel", msg.Channel.ToString());
o.Add("member", memberRef);
return o;
}
public static async Task<bool> ValidateUri(string uri)
{
IPHostEntry host = null;
try
{
host = await Dns.GetHostEntryAsync(uri);
}
catch (Exception) { }
if (host == null || host.AddressList.Length == 0)
return false;
#pragma warning disable CS0618
foreach (var address in host.AddressList)
{
if ((address.Address & 0x7f000000) == 0x7f000000) // 127.0/8
return false;
if ((address.Address & 0x0a000000) == 0x0a000000) // 10.0/8
return false;
if ((address.Address & 0xa9fe0000) == 0xa9fe0000) // 169.254/16
return false;
if ((address.Address & 0xac100000) == 0xac100000) // 172.16/12
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,209 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Autofac;
using Serilog;
namespace PluralKit.Core
{
public class DispatchService
{
private readonly ILogger _logger;
private readonly ILifetimeScope _provider;
private readonly HttpClient _client = new();
public DispatchService(ILogger logger, ILifetimeScope provider, CoreConfig cfg)
{
_logger = logger;
_provider = provider;
}
private async Task DoPostRequest(SystemId system, string webhookUrl, HttpContent content)
{
if (!await DispatchExt.ValidateUri(webhookUrl))
{
_logger.Warning("Failed to dispatch webhook for system {SystemId}: URL is invalid or points to a private address", system);
return;
}
try
{
await _client.PostAsync(webhookUrl, content);
}
catch (HttpRequestException e)
{
_logger.Error("Could not dispatch webhook request!", e);
}
}
public async Task Dispatch(SystemId systemId, UpdateDispatchData data)
{
if (data.EventData != null && data.EventData.Count == 0)
return;
var repo = _provider.Resolve<ModelRepository>();
var system = await repo.GetSystem(systemId);
if (system.WebhookUrl == null)
return;
data.SigningToken = system.WebhookToken;
data.SystemId = system.Uuid.ToString();
_logger.Debug("Dispatching webhook for system {SystemId}", systemId);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(MemberId memberId, UpdateDispatchData data)
{
if (data.EventData != null && data.EventData.Count == 0)
return;
var repo = _provider.Resolve<ModelRepository>();
var member = await repo.GetMember(memberId);
var system = await repo.GetSystem(member.System);
if (system.WebhookUrl == null)
return;
data.SigningToken = system.WebhookToken;
data.SystemId = system.Uuid.ToString();
data.EntityId = member.Uuid.ToString();
_logger.Debug("Dispatching webhook for member {MemberId} (system {SystemId})", memberId, system.Id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(GroupId groupId, UpdateDispatchData data)
{
if (data.EventData != null && data.EventData.Count == 0)
return;
var repo = _provider.Resolve<ModelRepository>();
var group = await repo.GetGroup(groupId);
var system = await repo.GetSystem(group.System);
if (system.WebhookUrl == null)
return;
data.SigningToken = system.WebhookToken;
data.SystemId = system.Uuid.ToString();
data.EntityId = group.Uuid.ToString();
_logger.Debug("Dispatching webhook for group {GroupId} (system {SystemId})", groupId, system.Id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(Dictionary<GroupId, MemberId> dict, DispatchEvent evt)
{
var repo = _provider.Resolve<ModelRepository>();
var g = await repo.GetGroup(dict.Keys.FirstOrDefault());
var system = await repo.GetSystem(g.System);
if (system.WebhookUrl == null)
return;
var data = new UpdateDispatchData();
data.Event = DispatchEvent.UPDATE_GROUP_MEMBERS;
data.SystemId = system.Uuid.ToString();
_logger.Debug("Dispatching webhook for group member update (system {SystemId})", system.Id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(SwitchId swId, UpdateDispatchData data)
{
var repo = _provider.Resolve<ModelRepository>();
var sw = await repo.GetSwitch(swId);
var system = await repo.GetSystem(sw.System);
if (system.WebhookUrl == null)
return;
data.SigningToken = system.WebhookToken;
data.SystemId = system.Uuid.ToString();
data.EntityId = sw.Uuid.ToString();
_logger.Debug("Dispatching webhook for switch {SwitchId} (system {SystemId})", sw.Id, system.Id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(SystemId systemId, PKMessage newMessage)
{
var repo = _provider.Resolve<ModelRepository>();
var system = await repo.GetSystem(systemId);
if (system.WebhookUrl == null)
return;
var member = await repo.GetMember(newMessage.Member);
var data = new UpdateDispatchData();
data.Event = DispatchEvent.CREATE_MESSAGE;
data.SystemId = system.Uuid.ToString();
data.EventData = newMessage.ToDispatchJson(member.Uuid.ToString());
_logger.Debug("Dispatching webhook for message create (system {SystemId})", system.Id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(SystemId systemId, ulong guild_id, SystemGuildPatch patch)
{
var repo = _provider.Resolve<ModelRepository>();
var system = await repo.GetSystem(systemId);
if (system.WebhookUrl == null)
return;
string memberRef = null;
if (patch.AutoproxyMember.Value != null)
{
var member = await repo.GetMember(patch.AutoproxyMember.Value.Value);
memberRef = member.Uuid.ToString();
}
var data = new UpdateDispatchData();
data.Event = DispatchEvent.UPDATE_SYSTEM_GUILD;
data.SystemId = system.Uuid.ToString();
data.GuildId = guild_id;
data.EventData = patch.ToJson(memberRef);
_logger.Debug("Dispatching webhook for system {SystemId} in guild {GuildId}", system.Id, guild_id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(MemberId memberId, ulong guild_id, MemberGuildPatch patch)
{
var repo = _provider.Resolve<ModelRepository>();
var member = await repo.GetMember(memberId);
var system = await repo.GetSystem(member.System);
if (system.WebhookUrl == null)
return;
var data = new UpdateDispatchData();
data.Event = DispatchEvent.UPDATE_MEMBER_GUILD;
data.SystemId = system.Uuid.ToString();
data.EntityId = member.Uuid.ToString();
data.GuildId = guild_id;
data.EventData = patch.ToJson();
_logger.Debug("Dispatching webhook for member {MemberId} (system {SystemId}) in guild {GuildId}", member.Id, system.Id, guild_id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
public async Task Dispatch(SystemId systemId, Guid uuid, DispatchEvent evt)
{
var repo = _provider.Resolve<ModelRepository>();
var system = await repo.GetSystem(systemId);
if (system.WebhookUrl == null)
return;
var data = new UpdateDispatchData();
data.Event = evt;
data.SigningToken = system.WebhookToken;
data.SystemId = system.Uuid.ToString();
data.EntityId = uuid.ToString();
_logger.Debug("Dispatching webhook for entity delete (system {SystemId})", system.Id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());
}
}
}