diff --git a/.gitignore b/.gitignore
index 47d221c8d..43981ba3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ assets/cache
.env
config.json
assets/cacheMisses
+http-client.private.env.json
.vscode/settings.json
diff --git a/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/Member.cs b/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/Member.cs
index 2a61b75ae..1693edd3b 100644
--- a/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/Member.cs
+++ b/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/Member.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using Spacebar.Models.Generic;
namespace Spacebar.DataMappings.Generic;
@@ -13,10 +14,10 @@ public static class MemberExtensions
Avatar = string.IsNullOrWhiteSpace(member.Avatar) ? null : member.Avatar,
Banner = string.IsNullOrWhiteSpace(member.Banner) ? null : member.Banner,
Collectibles = member.Collectibles,
- DisplayNameStyles = member.DisplayNameStyles,
+ DisplayNameStyles = JsonSerializer.Deserialize<DisplayNameStyle>(member.DisplayNameStyles ?? "null"),
Bio = string.IsNullOrWhiteSpace(member.Bio) ? null : member.Bio,
Nick = string.IsNullOrWhiteSpace(member.Nick) ? null : member.Nick,
- Roles = member.Roles.Select(x=>x.Id).ToList()
+ Roles = member.Roles.Select(x => x.Id).ToList()
};
}
}
\ No newline at end of file
diff --git a/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/User.cs b/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/User.cs
index 607af9411..95bce884a 100644
--- a/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/User.cs
+++ b/extra/admin-api/DataMappings/Spacebar.DataMappings.Generic/User.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using Spacebar.Models.Generic;
namespace Spacebar.DataMappings.Generic;
@@ -6,7 +7,8 @@ public static class User
{
public static PartialUser ToPartialUser(this Models.Db.Models.User user)
{
- return new PartialUser() {
+ return new PartialUser()
+ {
Id = user.Id,
Discriminator = user.Discriminator,
Username = user.Username,
@@ -16,7 +18,7 @@ public static class User
Banner = user.Banner,
Bot = user.Bot,
Collectibles = user.Collectibles,
- DisplayNameStyles = user.DisplayNameStyles,
+ DisplayNameStyles = JsonSerializer.Deserialize<DisplayNameStyle>(user.DisplayNameStyles ?? "null"),
// GlobalName = x.GlobalName,
PrimaryGuild = user.PrimaryGuild,
PublicFlags = user.PublicFlags,
diff --git a/extra/admin-api/Models/Spacebar.Models.Gateway/GuildMemberListUpdate.cs b/extra/admin-api/Models/Spacebar.Models.Gateway/GuildMemberListUpdate.cs
index 45d28467f..d1692cc79 100644
--- a/extra/admin-api/Models/Spacebar.Models.Gateway/GuildMemberListUpdate.cs
+++ b/extra/admin-api/Models/Spacebar.Models.Gateway/GuildMemberListUpdate.cs
@@ -3,7 +3,8 @@ using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Spacebar.Models.Generic;
-public class GuildMemberListUpdate {
+public class GuildMemberListUpdate
+{
public const string EventId = "GUILD_MEMBER_LIST_UPDATE";
[JsonPropertyName("id")]
@@ -22,11 +23,12 @@ public class GuildMemberListUpdate {
public List<GuildMemberListUpdateOperation> Operations { get; set; } = null!;
[JsonPropertyName("groups")]
- public List<GuildMemberListSyncItem.RoleEntry> Groups { get; set; } = null!;
+ public List<GuildMemberListSyncItem.RoleEntry.Content> Groups { get; set; } = null!;
}
// i cba to write a dictionary converter for this...
-public class GuildMemberListGroupCount {
+public class GuildMemberListGroupCount
+{
/// <summary>
/// Role ID, "online" or "offline"
/// </summary>
@@ -40,24 +42,28 @@ public class GuildMemberListGroupCount {
public static implicit operator GuildMemberListGroupCount(KeyValuePair<string, int> kvp) => new() { Id = kvp.Key, Count = kvp.Value };
}
-public enum GuildMemberListUpdateOperationType {
- [JsonStringEnumMemberName("sync")] Sync = 0,
- [JsonStringEnumMemberName("insert")] Insert = 1,
- [JsonStringEnumMemberName("update")] Update = 2,
- [JsonStringEnumMemberName("delete")] Delete = 3,
+[JsonConverter(typeof(JsonStringEnumConverter<GuildMemberListUpdateOperationType>))]
+public enum GuildMemberListUpdateOperationType
+{
+ [JsonStringEnumMemberName("SYNC")] Sync = 0,
+ [JsonStringEnumMemberName("INSERT")] Insert = 1,
+ [JsonStringEnumMemberName("UPDATE")] Update = 2,
+ [JsonStringEnumMemberName("DELETE")] Delete = 3,
- [JsonStringEnumMemberName("invalidate")]
+ [JsonStringEnumMemberName("INVALIDATE")]
Invalidate = 4
}
#region Operations
[JsonConverter(typeof(GuildMemberListUpdateOperationJsonConverter))]
-public class GuildMemberListUpdateOperation {
+public class GuildMemberListUpdateOperation
+{
[JsonPropertyName("op")]
public GuildMemberListUpdateOperationType Operation { get; set; }
-
- public class SyncOperation : GuildMemberListUpdateOperation {
+
+ public class SyncOperation : GuildMemberListUpdateOperation
+ {
[JsonPropertyName("range")]
public int[] Range { get; set; } = null!;
@@ -67,25 +73,36 @@ public class GuildMemberListUpdateOperation {
}
[JsonConverter(typeof(GuildMemberListSyncItemJsonConverter))]
-public class GuildMemberListSyncItem {
- public class RoleEntry : GuildMemberListSyncItem {
- [JsonPropertyName("id")]
- public string Id { get; set; }
+public class GuildMemberListSyncItem
+{
+ public class RoleEntry : GuildMemberListSyncItem
+ {
+ [JsonPropertyName("group")]
+ public Content Group { get; set; }
+
+ public class Content
+ {
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
- [JsonPropertyName("count")]
- public long Count { get; set; }
+ [JsonPropertyName("count")]
+ public long Count { get; set; }
+ }
}
- public class MemberEntry : GuildMemberListSyncItem {
+ public class MemberEntry : GuildMemberListSyncItem
+ {
[JsonPropertyName("member")]
public Member Member { get; set; } = null!;
}
}
-public class GuildMemberListSyncItemJsonConverter : JsonConverter<GuildMemberListSyncItem> {
- public override GuildMemberListSyncItem? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
+public class GuildMemberListSyncItemJsonConverter : JsonConverter<GuildMemberListSyncItem>
+{
+ public override GuildMemberListSyncItem? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
var n = JsonSerializer.Deserialize<JsonObject>(ref reader, options);
- if (n.ContainsKey("id") && n.ContainsKey("count")) return n.Deserialize<GuildMemberListSyncItem.RoleEntry>();
+ if (n.ContainsKey("group")) return n.Deserialize<GuildMemberListSyncItem.RoleEntry>();
if (n.ContainsKey("member")) return n.Deserialize<GuildMemberListSyncItem>();
throw new InvalidOperationException("Could not determine sync item type for keys " + string.Join(", ", n.Select(x => x.Key)));
}
@@ -94,11 +111,14 @@ public class GuildMemberListSyncItemJsonConverter : JsonConverter<GuildMemberLis
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
-public class GuildMemberListUpdateOperationJsonConverter : JsonConverter<GuildMemberListUpdateOperation> {
- public override GuildMemberListUpdateOperation? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
+public class GuildMemberListUpdateOperationJsonConverter : JsonConverter<GuildMemberListUpdateOperation>
+{
+ public override GuildMemberListUpdateOperation? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
var n = JsonSerializer.Deserialize<JsonObject>(ref reader, options);
- return n!["op"]!.GetValue<string>() switch {
- "sync" => n.Deserialize<GuildMemberListUpdateOperation.SyncOperation>(),
+ return n!["op"]!.GetValue<string>() switch
+ {
+ "SYNC" => n.Deserialize<GuildMemberListUpdateOperation.SyncOperation>(),
_ => throw new InvalidCastException("Unknown operation: " + n["op"]!.GetValue<string>())
};
}
diff --git a/extra/admin-api/Models/Spacebar.Models.Gateway/LazyRequest.cs b/extra/admin-api/Models/Spacebar.Models.Gateway/LazyRequest.cs
index c144eb727..ee7895afc 100644
--- a/extra/admin-api/Models/Spacebar.Models.Gateway/LazyRequest.cs
+++ b/extra/admin-api/Models/Spacebar.Models.Gateway/LazyRequest.cs
@@ -12,8 +12,8 @@ public class LazyRequest
[JsonPropertyName("channels")]
public Dictionary<string, List<List<int>>> Channels { get; set; }
- [JsonPropertyName("members")]
- public bool Members { get; set; }
+ [JsonPropertyName("members"), JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public List<long>? Members { get; set; }
[JsonPropertyName("threads")]
public bool Threads { get; set; }
diff --git a/extra/admin-api/Models/Spacebar.Models.Generic/Member.cs b/extra/admin-api/Models/Spacebar.Models.Generic/Member.cs
index fa217a377..b11a041b2 100644
--- a/extra/admin-api/Models/Spacebar.Models.Generic/Member.cs
+++ b/extra/admin-api/Models/Spacebar.Models.Generic/Member.cs
@@ -23,8 +23,8 @@ public class Member {
[JsonPropertyName("collectibles")]
public object? Collectibles { get; set; }
- [JsonPropertyName("display_name_styles")]
- public object? DisplayNameStyles { get; set; }
+ [JsonPropertyName("display_name_styles"), JsonIgnore(Condition = JsonIgnoreCondition.Never)]
+ public DisplayNameStyle? DisplayNameStyles { get; set; }
[JsonPropertyName("banner")]
public string? Banner { get; set; }
diff --git a/extra/admin-api/Models/Spacebar.Models.Generic/User.cs b/extra/admin-api/Models/Spacebar.Models.Generic/User.cs
index 428794afb..ad0028d82 100644
--- a/extra/admin-api/Models/Spacebar.Models.Generic/User.cs
+++ b/extra/admin-api/Models/Spacebar.Models.Generic/User.cs
@@ -1,10 +1,12 @@
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Text.Json.Serialization;
namespace Spacebar.Models.Generic;
[DebuggerDisplay("{Id} ({Username}#{Discriminator})")]
-public class PartialUser {
+public class PartialUser
+{
[JsonPropertyName("id"), JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public required long Id { get; set; }
@@ -26,8 +28,8 @@ public class PartialUser {
[JsonPropertyName("collectibles")]
public object? Collectibles { get; set; }
- [JsonPropertyName("display_name_styles")]
- public object? DisplayNameStyles { get; set; }
+ [JsonPropertyName("display_name_styles"), JsonIgnore(Condition = JsonIgnoreCondition.Never)]
+ public DisplayNameStyle? DisplayNameStyles { get; set; }
[JsonPropertyName("primary_guild")]
public object? PrimaryGuild { get; set; }
@@ -46,4 +48,43 @@ public class PartialUser {
[JsonPropertyName("public_flags")]
public ulong? PublicFlags { get; set; }
+}
+
+public class DisplayNameStyle
+{
+ [JsonPropertyName("font_id")]
+ public Font FontId { get; set; } = Font.Default;
+
+ [JsonPropertyName("effect_id")]
+ public Effect EffectId { get; set; } = Effect.Solid;
+
+ [JsonPropertyName("colors"), MaxLength(2)]
+ public List<int> Colors { get; set; } = [];
+
+ // https://docs.discord.food/resources/user#display-name-font - unsure if these are just really close or directly used by discord while being renamed...
+ public enum Font
+ {
+ Bangers = 1,
+ BioRhyme = 2,
+ CherryBomb = 3,
+ Chicle = 4,
+ Compagnon = 5,
+ MuseoModerno = 6,
+ NeoCastel = 7,
+ Pixelify = 8,
+ Ribes = 9,
+ Sinistre = 10,
+ Default = 11,
+ ZillaSlab = 12
+ }
+
+ public enum Effect
+ {
+ Solid = 1,
+ Gradient = 2,
+ Neon = 3,
+ Toon = 4,
+ Pop = 5,
+ Glow = 6
+ }
}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Offload/Controllers/Op14Controller.cs b/extra/admin-api/Spacebar.Offload/Controllers/Op14Controller.cs
index f34455542..79369de1e 100644
--- a/extra/admin-api/Spacebar.Offload/Controllers/Op14Controller.cs
+++ b/extra/admin-api/Spacebar.Offload/Controllers/Op14Controller.cs
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using ArcaneLibs.Extensions;
@@ -12,44 +13,56 @@ using Spacebar.Interop.Replication.Abstractions;
using Spacebar.Models.Db.Contexts;
using Spacebar.Models.Gateway;
using Spacebar.Models.Generic;
+using Spacebar.Models.Generic.Constants;
+using Spacebar.Offload.Extensions;
namespace Spacebar.GatewayOffload.Controllers;
[ApiController]
[Route("/_spacebar/offload/gateway/LazyRequest")]
-public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthenticationService authService, SpacebarDbContext db, IServiceProvider sp) : ControllerBase {
+public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthenticationService authService, SpacebarDbContext db, IServiceProvider sp) : ControllerBase
+{
[HttpPost]
// TODO: actually return something?
- public async IAsyncEnumerable<ContentlessReplicationMessage> DoLazyRequest([FromBody] LazyRequest payload) {
+ public async IAsyncEnumerable<ContentlessReplicationMessage> DoLazyRequest([FromBody] LazyRequest payload)
+ {
var user = await TraceResult.TraceAsync("getAuthUser", () => authService.GetCurrentUserAsync(Request));
var session = await TraceResult.TraceAsync("getAuthSession", () => authService.GetCurrentSessionAsync(Request));
- if (!await db.Members.AsNoTracking().AnyAsync(m => m.GuildId == payload.GuildId && m.Id == user.Result.Id)) {
+ if (!await db.Members.AsNoTracking().AnyAsync(m => m.GuildId == payload.GuildId && m.Id == user.Result.Id))
+ {
logger.LogWarning("User {user} requested lazy member list for guild {guildId}, but is not a member", user.Result.Id, payload.GuildId);
yield break;
}
- if (payload.Channels.Count == 0) {
- logger.LogWarning("User {user} requested lazy member list for guild {guildId}, but is not a member", user.Result.Tag, payload.GuildId);
+ if (payload.Channels.Count == 0)
+ {
+ logger.LogWarning("User {user} requested lazy member list for guild {guildId}, but did not request a channel range", user.Result.Tag, payload.GuildId);
yield break;
}
var memberList = await GetGuildMemberListAsync(db, payload.GuildId);
- yield return new ReplicationMessage<GuildMemberListUpdate>() {
+ yield return new ReplicationMessage<GuildMemberListUpdate>()
+ {
UserId = user.Result.Id,
Event = GuildMemberListUpdate.EventId,
Origin = "Offload/LazyRequest",
CreatedAt = DateTime.UtcNow,
- Payload = new GuildMemberListUpdate() {
+ Payload = new GuildMemberListUpdate()
+ {
GuildId = payload.GuildId,
- ListId = payload.GuildId.ToString(),
- OnlineCount = memberList.TakeWhile(x => x is not RoleEntry { Id: "offline" }).Count(),
+ // this doesnt appear to work currently, skip it
+ ListId = "everyone", // await GetMemberListIdAsync(db, guildId: payload.GuildId, channelId: long.Parse(payload.Channels.Keys.First())),
+ OnlineCount = memberList.TakeWhile(x => x is not RoleEntry { Group.Id: "offline" }).Count(),
MemberCount = await db.Members.CountAsync(x => x.GuildId == payload.GuildId),
- Operations = [
- new GuildMemberListUpdateOperation.SyncOperation() {
+ Operations =
+ [
+ new GuildMemberListUpdateOperation.SyncOperation()
+ {
+ Operation = GuildMemberListUpdateOperationType.Sync,
Items = memberList.Select<IMemberListEntry, GuildMemberListSyncItem>(item => item is RoleEntry re
- ? new GuildMemberListSyncItem.RoleEntry() { Id = re.Id, Count = re.Count }
+ ? new GuildMemberListSyncItem.RoleEntry() { Group = new() { Id = re.Group.Id, Count = re.Group.Count } }
: item is MemberEntry me
? new GuildMemberListSyncItem.MemberEntry() { Member = me.Member }
: throw new InvalidCastException("List item was neither RoleEntry nor MemberEntry???"))
@@ -57,7 +70,8 @@ public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
Range = [0, memberList.Count]
}
],
- Groups = memberList.OfType<RoleEntry>().Select(re => new GuildMemberListSyncItem.RoleEntry() { Id = re.Id, Count = re.Count }).ToList()
+ Groups = memberList.OfType<RoleEntry>().Select(re => new GuildMemberListSyncItem.RoleEntry.Content() { Id = re.Group.Id, Count = re.Group.Count })
+ .ToList()
}
// TODO: send presence updates
// TODO: handle subscriptions
@@ -66,18 +80,33 @@ public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
};
}
- private async Task<string?> GetMemberListIdAsync(SpacebarDbContext db, long guildId, long channelId) {
+ private async Task<string?> GetMemberListIdAsync(SpacebarDbContext db, long guildId, long channelId)
+ {
var channel = await db.Channels.AsNoTracking().FirstOrDefaultAsync(c => c.Id == channelId && c.GuildId == guildId);
if (channel == null) return null;
- if (string.IsNullOrWhiteSpace(channel.PermissionOverwrites) || channel.PermissionOverwrites == "[]") {
+
+ if (string.IsNullOrWhiteSpace(channel.PermissionOverwrites) || channel.PermissionOverwrites == "[]")
+ {
return "everyone";
}
- return null; // TODO
+ List<string> perms = [];
+ foreach (var overwrite in channel.MappedPermissionOverwrites)
+ {
+ if (((Permissions) overwrite.Allow).HasFlag(Permissions.ViewChannel)) perms.Add($"allow:{overwrite}");
+ else if (((Permissions) overwrite.Deny).HasFlag(Permissions.ViewChannel)) perms.Add($"deny:{overwrite}");
+ }
+
+ perms.Sort();
+
+ ReadOnlySpan<byte> hashData = Encoding.UTF8.GetBytes(string.Join(",", perms));
+ var hashResult = MurmurHash3.Hash32(ref hashData);
+ return hashResult.ToString();
}
- private async Task<List<IMemberListEntry>> GetGuildMemberListAsync(SpacebarDbContext db, long guildId) {
+ private async Task<List<IMemberListEntry>> GetGuildMemberListAsync(SpacebarDbContext db, long guildId)
+ {
var memberList = new List<IMemberListEntry>();
// Fetch hoisted roles for the guild to define groups
@@ -90,10 +119,13 @@ public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
logger.LogDebug("Got hoisted roles: {roleIds}", hoistedRoles.Select(x => x.Id).ToList());
List<long> handledRoles = [];
- foreach (var roleObj in hoistedRoles) {
+ foreach (var roleObj in hoistedRoles)
+ {
var role = roleObj.Id;
var members = await db.Members.AsNoTracking()
.Include(x => x.IdNavigation)
+ .ThenInclude(x => x.Sessions.Where(s => s.Status != "offline" && s.Status != "invisible" && s.Status != "unknown"))
+ .Include(x => x.Roles)
.Where(x =>
x.GuildId == guildId
&& x.Roles.Any(r => r.Id == role)
@@ -107,8 +139,11 @@ public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
members.Count, role, roleObj.Name, string.Join("\n - ", members.Take(10).Select(x => $"{x.Id} {x.Nick ?? x.IdNavigation.Tag}"))
);
- memberList.Add(new RoleEntry() { Id = role.ToString(), Count = members.Count });
- memberList.AddRange(members.Select(m => (IMemberListEntry)new MemberEntry() { Member = m.ToPublicMember() }));
+ if (members.Count > 0)
+ {
+ memberList.Add(new RoleEntry() { Group = new() { Id = role.ToString(), Count = members.Count } });
+ memberList.AddRange(members.Select(m => (IMemberListEntry)new MemberEntry() { Member = m.ToPublicMember() }));
+ }
handledRoles.Add(role);
}
@@ -129,12 +164,14 @@ public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
onlineMembers.Count, "online", "online", string.Join("\n - ", onlineMembers.Take(10).Select(x => $"{x.Id} {x.Nick ?? x.IdNavigation.Tag}"))
);
- if (onlineMembers.Count > 0) {
- memberList.Add(new RoleEntry() { Id = "online", Count = onlineMembers.Count });
+ if (onlineMembers.Count > 0)
+ {
+ memberList.Add(new RoleEntry() { Group = new() { Id = "online", Count = onlineMembers.Count } });
memberList.AddRange(onlineMembers.Select(m => (IMemberListEntry)new MemberEntry() { Member = m.ToPublicMember() }));
}
- if (memberList.Count < 2000) {
+ if (memberList.Count < 2000)
+ {
logger.LogInformation("Less than 2000 members, including offline members...");
var offlineMembers = await db.Members.AsNoTracking()
.Include(x => x.IdNavigation)
@@ -151,8 +188,12 @@ public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
offlineMembers.Count, "offline", "offline", string.Join("\n - ", offlineMembers.Take(10).Select(x => $"{x.Id} {x.Nick ?? x.IdNavigation.Tag}"))
);
- if (offlineMembers.Count > 0) {
- memberList.Add(new RoleEntry() { Id = "offline", Count = offlineMembers.Count });
+ if (offlineMembers.Count > 0)
+ {
+ memberList.Add(new RoleEntry()
+ {
+ Group = new() { Id = "offline", Count = offlineMembers.Count }
+ });
memberList.AddRange(offlineMembers.Select(m => (IMemberListEntry)new MemberEntry() { Member = m.ToPublicMember() }));
}
}
@@ -162,17 +203,28 @@ public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
}
}
-internal interface IMemberListEntry { }
+// TODO: either remove these classes by using the schema ones, or compress these down?
+internal interface IMemberListEntry
+{
+}
+
+internal struct RoleEntry : IMemberListEntry
+{
+ [JsonPropertyName("group")]
+ public Content Group { get; set; }
-internal struct RoleEntry : IMemberListEntry {
- [JsonPropertyName("id")]
- public string Id { get; set; }
+ internal struct Content
+ {
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
- [JsonPropertyName("count")]
- public int Count { get; set; }
+ [JsonPropertyName("count")]
+ public int Count { get; set; }
+ }
}
-internal struct MemberEntry : IMemberListEntry {
+internal struct MemberEntry : IMemberListEntry
+{
[JsonPropertyName("member")]
public Member Member { get; set; }
}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Offload/Extensions/MurmurHash3.cs b/extra/admin-api/Spacebar.Offload/Extensions/MurmurHash3.cs
new file mode 100644
index 000000000..dfa9f3018
--- /dev/null
+++ b/extra/admin-api/Spacebar.Offload/Extensions/MurmurHash3.cs
@@ -0,0 +1,47 @@
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using static System.Numerics.BitOperations;
+
+namespace Spacebar.Offload.Extensions;
+
+// https://github.com/JeremyEspresso/MurmurHash/blob/master/src/MurmurHash/MurmurHash3.cs
+// Changes: Default seed to 0
+public static class MurmurHash3
+{
+ /// <summary>
+ /// Hashes the <paramref name="bytes"/> into a MurmurHash3 as a <see cref="uint"/>.
+ /// </summary>
+ /// <param name="bytes">The span.</param>
+ /// <param name="seed">The seed for this algorithm.</param>
+ /// <returns>The MurmurHash3 as a <see cref="uint"/></returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint Hash32(ref ReadOnlySpan<byte> bytes, uint seed = 0)
+ {
+ ref byte bp = ref MemoryMarshal.GetReference(bytes);
+ ref uint endPoint = ref Unsafe.Add(ref Unsafe.As<byte, uint>(ref bp), bytes.Length >> 2);
+ if (bytes.Length >= 4)
+ {
+ do
+ {
+ seed = RotateLeft(seed ^ RotateLeft(Unsafe.ReadUnaligned<uint>(ref bp) * 3432918353U, 15) * 461845907U, 13) * 5 - 430675100;
+ bp = ref Unsafe.Add(ref bp, 4);
+ } while (Unsafe.IsAddressLessThan(ref Unsafe.As<byte, uint>(ref bp), ref endPoint));
+ }
+
+ var remainder = bytes.Length & 3;
+ if (remainder > 0)
+ {
+ uint num = 0;
+ if (remainder > 2) num ^= Unsafe.Add(ref endPoint, 2) << 16;
+ if (remainder > 1) num ^= Unsafe.Add(ref endPoint, 1) << 8;
+ num ^= endPoint;
+
+ seed ^= RotateLeft(num * 3432918353U, 15) * 461845907U;
+ }
+
+ seed ^= (uint)bytes.Length;
+ seed = (uint)((seed ^ (seed >> 16)) * -2048144789);
+ seed = (uint)((seed ^ (seed >> 13)) * -1028477387);
+ return seed ^ seed >> 16;
+ }
+}
\ No newline at end of file
diff --git a/src/gateway/util/Utils.ts b/src/gateway/util/Utils.ts
index ad5cd21f2..bbb054363 100644
--- a/src/gateway/util/Utils.ts
+++ b/src/gateway/util/Utils.ts
@@ -113,7 +113,7 @@ export async function handleOffloadedGatewayRequest(socket: WebSocket, url: stri
if (!resp.ok) {
const text = await resp.text();
console.error(`[Gateway] Offloaded request to ${url} failed with status ${resp.status}: ${text}`);
- if (resp.status === 415) console.log(typeof body, body);
+ if (resp.status === 415 || resp.status === 400) console.log(typeof body, body);
throw new Error(`Offloaded request failed with status ${resp.status}: ${text}`);
}
|