mirror of
https://github.com/spacebarchat/server.git
synced 2026-10-22 21:53:46 +00:00
More C# progress
This commit is contained in:
1 parent
f233fdb9de
commit
c11629268b
20 files changed
+400
-35
No files matched your search
@@ -0,0 +1,13 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
namespace Spacebar.DataMappings.Generic;
|
||||
|
||||
public static class Channel {
|
||||
extension(Models.Db.Models.Channel channel) {
|
||||
[JsonIgnore]
|
||||
public IEnumerable<ChannelPermissionOverwrite>? MappedPermissionOverwrites =>
|
||||
channel.PermissionOverwrites is null ? [] : JsonSerializer.Deserialize<List<ChannelPermissionOverwrite>>(channel.PermissionOverwrites);
|
||||
}
|
||||
}
|
||||
+5
@@ -23,4 +23,9 @@ public class SpacebarAspNetAuthenticationService(SpacebarAuthenticationService a
|
||||
var token = GetTokenAsync(request);
|
||||
return await authService.GetCurrentUserAsync(token);
|
||||
}
|
||||
|
||||
public Task<Session> GetCurrentSessionAsync(HttpRequest request) {
|
||||
var token = GetTokenAsync(request);
|
||||
return authService.GetCurrentSessionAsync(token);
|
||||
}
|
||||
}
|
||||
+1
@@ -11,6 +11,7 @@ public class SpacebarAuthenticationConfiguration {
|
||||
public required string PublicKeyPath { get; set; }
|
||||
|
||||
public string? OverrideUid { get; set; }
|
||||
public string? OverrideDid { get; set; }
|
||||
public bool DisableAuthentication { get; set; } = false;
|
||||
public bool Enforce2FA { get; set; } = true;
|
||||
public TimeSpan AuthCacheExpiry { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
+6
-5
@@ -10,6 +10,7 @@ namespace Spacebar.Interop.Authentication;
|
||||
|
||||
public class SpacebarAuthenticationService(ILogger<SpacebarAuthenticationService> logger, SpacebarDbContext db, SpacebarAuthenticationConfiguration config) {
|
||||
private static readonly ExpiringSemaphoreCache<User> UserCache = new();
|
||||
private static readonly ExpiringSemaphoreCache<Session> SessionCache = new();
|
||||
|
||||
public async Task<TokenValidationResult?> ValidateTokenAsync(string token) {
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
@@ -47,13 +48,13 @@ public class SpacebarAuthenticationService(ILogger<SpacebarAuthenticationService
|
||||
config.AuthCacheExpiry);
|
||||
}
|
||||
|
||||
public async Task<User> GetCurrentSessionAsync(string token) {
|
||||
public async Task<Session> GetCurrentSessionAsync(string token) {
|
||||
var res = await ValidateTokenAsync(token);
|
||||
return await UserCache.GetOrAdd(token,
|
||||
return await SessionCache.GetOrAdd(token,
|
||||
async () => {
|
||||
var uid = config.OverrideUid ?? res?.ClaimsIdentity.Claims.First(x => x.Type == "id").Value;
|
||||
if (string.IsNullOrWhiteSpace(uid)) throw new InvalidOperationException("No user ID specified, is the access token valid?");
|
||||
return await db.Users.FindAsync(uid) ?? throw new InvalidOperationException();
|
||||
var did = config.OverrideDid ?? res?.ClaimsIdentity.Claims.First(x => x.Type == "did").Value;
|
||||
if (string.IsNullOrWhiteSpace(did)) throw new InvalidOperationException("No device ID specified, is the access token valid?");
|
||||
return await db.Sessions.FindAsync(did) ?? throw new InvalidOperationException();
|
||||
},
|
||||
config.AuthCacheExpiry);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Spacebar.Models.Gateway;
|
||||
|
||||
public enum CloseCode {
|
||||
UnknownError = 4000,
|
||||
UnknownOpcode = 4001,
|
||||
DecodeError = 4002,
|
||||
NotAuthenticated = 4003,
|
||||
AuthenticationFailed = 4004,
|
||||
AlreadyAuthenticated = 4005,
|
||||
InvalidSession = 4006,
|
||||
InvalidSeq = 4007,
|
||||
RateLimited = 4008,
|
||||
SessionTimedOut = 4009,
|
||||
InvalidShard = 4010,
|
||||
ShardingRequired = 4011,
|
||||
InvalidApiVersion = 4012,
|
||||
InvalidIntent = 4013,
|
||||
DisallowedIntent = 4014,
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
public class GuildMemberListUpdate {
|
||||
[JsonPropertyName("id")]
|
||||
public string ListId { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("guild_id")]
|
||||
public string GuildId { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("online_count")]
|
||||
public int OnlineCount { get; set; }
|
||||
|
||||
[JsonPropertyName("member_count")]
|
||||
public int MemberCount { get; set; }
|
||||
|
||||
[JsonPropertyName("ops")]
|
||||
public List<BaseGuildMemberListUpdateOperation> Operations { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("groups")]
|
||||
public List<GuildMemberListGroupCount> Groups { get; set; } = null!;
|
||||
}
|
||||
|
||||
// i cba to write a dictionary converter for this...
|
||||
public class GuildMemberListGroupCount {
|
||||
/// <summary>
|
||||
/// Role ID, "online" or "offline"
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
public static implicit operator KeyValuePair<string, int>(GuildMemberListGroupCount groupCount) => new(groupCount.Id, groupCount.Count);
|
||||
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,
|
||||
|
||||
[JsonStringEnumMemberName("invalidate")]
|
||||
Invalidate = 4
|
||||
}
|
||||
|
||||
#region Operations
|
||||
|
||||
public class BaseGuildMemberListUpdateOperation {
|
||||
[JsonPropertyName("op")]
|
||||
public GuildMemberListUpdateOperationType Operation { get; set; }
|
||||
}
|
||||
|
||||
public class GuildMemberListSyncOperation : BaseGuildMemberListUpdateOperation {
|
||||
[JsonPropertyName("range")]
|
||||
public int[] Range { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("items")]
|
||||
public List<GuildMemberListSyncItem> Items { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class GuildMemberListSyncItem {
|
||||
public class GuildMemberListMemberSyncItem : GuildMemberListSyncItem {
|
||||
[JsonPropertyName("member")]
|
||||
public Member Member { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: diff algo
|
||||
// TODO: snapshots
|
||||
@@ -8,7 +8,7 @@ public class GuildSyncResponse {
|
||||
public string GuildId { get; set; }
|
||||
|
||||
[JsonPropertyName("presences")]
|
||||
public List<PresenceResponse> Presences { get; set; }
|
||||
public List<Presence> Presences { get; set; }
|
||||
|
||||
[JsonPropertyName("members")]
|
||||
public List<Member> Members { get; set; }
|
||||
|
||||
@@ -3,8 +3,7 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace Spacebar.Models.Gateway;
|
||||
|
||||
public class IdentifyRequest
|
||||
{
|
||||
public class IdentifyRequest {
|
||||
[JsonPropertyName("token")]
|
||||
public string Token { get; set; }
|
||||
|
||||
@@ -34,8 +33,7 @@ public class IdentifyRequest
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum GatewayIntentFlags
|
||||
{
|
||||
public enum GatewayIntentFlags : ulong {
|
||||
Guilds = 1,
|
||||
GuildMembers = 1 << 1,
|
||||
GuildModeration = 1 << 2,
|
||||
@@ -68,8 +66,7 @@ public enum GatewayIntentFlags
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum GatewayCapabilityFlags
|
||||
{
|
||||
public enum GatewayCapabilityFlags {
|
||||
LazyUserNotes = 1,
|
||||
NoAffineUserIds = 1 << 1,
|
||||
VersionedReadStates = 1 << 2,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Spacebar.Models.Gateway;
|
||||
|
||||
public class QoSPayload {
|
||||
[JsonPropertyName("ver")]
|
||||
public int Version { get; set; }
|
||||
|
||||
[JsonPropertyName("active")]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[JsonPropertyName("reasons")]
|
||||
public List<string> Reasons { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Spacebar.Models.Gateway;
|
||||
|
||||
public class ReadyResponse { }
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Spacebar.Models.Gateway;
|
||||
|
||||
// TODO: move to interop
|
||||
public class SbWebsocketMeta
|
||||
{
|
||||
[JsonPropertyName("user_id")]
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("session_id")]
|
||||
public string SessionId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("accessToken")]
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("encoding")]
|
||||
public string Encoding { get; set; } = "json";
|
||||
|
||||
[JsonPropertyName("compress")]
|
||||
public string? Compress { get; set; }
|
||||
|
||||
[JsonPropertyName("ipAddress")]
|
||||
public string? IpAddress { get; set; }
|
||||
|
||||
[JsonPropertyName("userAgent")]
|
||||
public string? UserAgent { get; set; }
|
||||
|
||||
[JsonPropertyName("fingerprint")]
|
||||
public string? Fingerprint { get; set; }
|
||||
|
||||
[JsonPropertyName("shard_count")]
|
||||
public int? ShardCount { get; set; }
|
||||
|
||||
[JsonPropertyName("shard_id")]
|
||||
public int? ShardId { get; set; }
|
||||
|
||||
[JsonPropertyName("intents")]
|
||||
public GatewayIntentFlags Intents { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("sequence")]
|
||||
public long Sequence { get; set; }
|
||||
|
||||
[JsonPropertyName("capabilities")]
|
||||
public GatewayCapabilityFlags? Capabilities { get; set; }
|
||||
|
||||
[JsonPropertyName("large_threshold")]
|
||||
public int LargeThreshold { get; set; }
|
||||
|
||||
[JsonPropertyName("qos")]
|
||||
public QoSPayload? Qos { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Spacebar.Models.Generic;
|
||||
|
||||
public class ChannelPermissionOverwrite {
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
[JsonPropertyName("allow"), JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
public ulong Allow { get; set; }
|
||||
|
||||
[JsonPropertyName("deny"), JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
public ulong Deny { get; set; }
|
||||
}
|
||||
@@ -28,4 +28,10 @@ public class Member {
|
||||
|
||||
[JsonPropertyName("bio")]
|
||||
public string? Bio { get; set; }
|
||||
}
|
||||
|
||||
// Unsure if this is used anywhere outside of op14...?
|
||||
public class MemberWithPresence : Member {
|
||||
[JsonPropertyName("presence")]
|
||||
public Presence? Presence { get; set; }
|
||||
}
|
||||
+10
-2
@@ -3,9 +3,9 @@ using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
namespace Spacebar.Models.Gateway;
|
||||
namespace Spacebar.Models.Generic;
|
||||
|
||||
public class PresenceResponse {
|
||||
public class Presence {
|
||||
[JsonPropertyName("user")]
|
||||
public required PartialUser User { get; set; }
|
||||
|
||||
@@ -29,6 +29,14 @@ public class PresenceResponse {
|
||||
[JsonPropertyName("has_played_game")]
|
||||
public bool? HasPlayedGame { get; set; }
|
||||
|
||||
// Unsure if this is used outside of op14
|
||||
[JsonPropertyName("game")]
|
||||
public JsonObject? Game { get; set; }
|
||||
|
||||
// Unsure if used outside of op14
|
||||
[JsonPropertyName("processed_at_timestamp")]
|
||||
public ulong? ProcessedAtTimestamp { get; set; }
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
public class ClientStatuses {
|
||||
[JsonPropertyName("desktop"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Spacebar.Models.Generic;
|
||||
|
||||
public class Trace {
|
||||
[JsonPropertyName("micros")]
|
||||
public long Micros { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Trace>? Calls { get; set; }
|
||||
|
||||
[JsonPropertyName("calls")]
|
||||
// zipped array of [string, Trace]
|
||||
public JsonArray? ZippedCalls {
|
||||
get {
|
||||
if (Calls == null) return null;
|
||||
var arr = new JsonArray();
|
||||
foreach (var t in Calls) {
|
||||
arr.Add(t.Name);
|
||||
arr.Add(t);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
public JsonArray AsRoot() {
|
||||
return new() {
|
||||
Name,
|
||||
this
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class TraceResult {
|
||||
public static async Task<TraceResult<T>> TraceAsync<T>(string name, Func<Task<T>> func) {
|
||||
var sw = Stopwatch.StartNew();
|
||||
var result = await func();
|
||||
sw.Stop();
|
||||
return new TraceResult<T> {
|
||||
Name = name,
|
||||
Micros = sw.Elapsed.Microseconds,
|
||||
Result = result
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<TraceResult<T>> Trace<T>(string name, Func<T> func) {
|
||||
var sw = Stopwatch.StartNew();
|
||||
var result = func();
|
||||
sw.Stop();
|
||||
return new TraceResult<T> {
|
||||
Name = name,
|
||||
Micros = sw.Elapsed.Microseconds,
|
||||
Result = result
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class TraceResult<T> : Trace {
|
||||
[JsonIgnore]
|
||||
public T Result { get; set; }
|
||||
}
|
||||
@@ -1,13 +1,52 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Spacebar.Interop.Authentication;
|
||||
using Spacebar.Interop.Authentication.AspNetCore;
|
||||
using Spacebar.Interop.Replication.Abstractions;
|
||||
using Spacebar.Models.Db.Contexts;
|
||||
using Spacebar.Models.Gateway;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
namespace Spacebar.GatewayOffload.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("/_spacebar/offload/gateway/Identify")]
|
||||
public class IdentifyController(ILogger<IdentifyController> logger, SpacebarAuthenticationService authService, SpacebarDbContext db, IServiceProvider sp) : ControllerBase
|
||||
{
|
||||
|
||||
public class IdentifyController(ILogger<IdentifyController> logger, SpacebarAuthenticationService authService, SpacebarDbContext db, IServiceProvider sp) : ControllerBase {
|
||||
[HttpPost("")]
|
||||
public async IAsyncEnumerable<ReplicationMessage> DoIdentify(IdentifyRequest payload) {
|
||||
var user = await TraceResult.TraceAsync("getAuthUser", () => authService.GetCurrentUserAsync(payload.Token));
|
||||
var session = await TraceResult.TraceAsync("getAuthSession", () => authService.GetCurrentSessionAsync(payload.Token));
|
||||
|
||||
var socketMeta = new SbWebsocketMeta() {
|
||||
// Auth data
|
||||
AccessToken = payload.Token,
|
||||
UserId = user.Result.Id,
|
||||
SessionId = session.Result.SessionId,
|
||||
// Client capabilities
|
||||
Capabilities = payload.Capabilities ??= 0,
|
||||
LargeThreshold = payload.LargeTreshold ??= user.Result.Bot ? 20 : 250,
|
||||
Intents = payload.Intents ??= (GatewayIntentFlags)0b_110_11111111_11111111_11111111_11111111,
|
||||
// Sharding info
|
||||
ShardId = payload.Shard?[0],
|
||||
ShardCount = payload.Shard?[1],
|
||||
};
|
||||
|
||||
if (socketMeta is { ShardId: not null, ShardCount: not null }) {
|
||||
if (socketMeta.ShardId < 0 || socketMeta.ShardCount <= 0 || socketMeta.ShardId >= socketMeta.ShardCount) {
|
||||
logger.LogWarning("Invalid sharding from {userId}: {shardId}/{shardCount}", user.Result.Id, socketMeta.ShardId, socketMeta.ShardCount);
|
||||
yield return this.Close(CloseCode.InvalidShard);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
yield return new() {
|
||||
Payload = new ReadyResponse { },
|
||||
};
|
||||
}
|
||||
|
||||
private ReplicationMessage Close(CloseCode closeCode) => new() {
|
||||
Origin = "IdentifyController",
|
||||
Event = "SB_GW_CLOSE",
|
||||
Payload = new {
|
||||
code = closeCode,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
@@ -10,6 +11,7 @@ using Spacebar.Interop.Replication.Abstractions;
|
||||
using Spacebar.Models.Db.Contexts;
|
||||
using Spacebar.Models.Db.Models;
|
||||
using Spacebar.Models.Gateway;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
namespace Spacebar.GatewayOffload.Controllers;
|
||||
|
||||
@@ -21,7 +23,7 @@ public class Op12Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
|
||||
public async IAsyncEnumerable<ReplicationMessage> DoGuildSync(List<string> guildIds)
|
||||
{
|
||||
var user = await authService.GetCurrentUserAsync(Request);
|
||||
guildIds = (await db.Members.Where(x => x.Id == user.Id).Select(x => x.GuildId).ToListAsync())
|
||||
guildIds = (await db.Members.AsNoTracking().Where(x => x.Id == user.Id).Select(x => x.GuildId).ToListAsync())
|
||||
.Intersect(guildIds)
|
||||
.OrderByDescending(gi => db.Members.Count(m => m.GuildId == gi))
|
||||
.ToList();
|
||||
@@ -41,44 +43,44 @@ public class Op12Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthen
|
||||
}
|
||||
|
||||
// TODO: figure out how to abstract this to a function without EFCore complaining about not being translatable...
|
||||
//private static Func<Session, bool> IsOnline = (Session session) => session.Status != "offline" && session.Status != "invisible" && session.Status != "unknown";
|
||||
private static Expression<Func<Session, bool>> IsOnline = (Session session) => session.Status != "offline" && session.Status != "invisible" && session.Status != "unknown";
|
||||
|
||||
private async Task<GuildSyncResponse> GetGuildSyncAsync(string guildId)
|
||||
{
|
||||
await using var sc = sp.CreateAsyncScope();
|
||||
var _db = sc.ServiceProvider.GetRequiredService<SpacebarDbContext>();
|
||||
var memberCount = await _db.Members.Where(x => x.GuildId == guildId).CountAsync();
|
||||
var memberCount = await _db.Members.AsNoTracking().Where(x => x.GuildId == guildId).CountAsync();
|
||||
|
||||
var offlineTreshold = DateTime.Now.Subtract(TimeSpan.FromDays(14));
|
||||
var isLargeGuild = memberCount > 10000;
|
||||
|
||||
var members = await _db.Members.Where(x => x.GuildId == guildId)
|
||||
var members = await _db.Members.AsNoTracking().Where(x => x.GuildId == guildId)
|
||||
.Include(x => x.IdNavigation)
|
||||
.ThenInclude(x => x.Sessions.Where(s =>
|
||||
!s.IsAdminSession && (
|
||||
// see TODO on IsOnline
|
||||
// see TODO on IsOnline - somehow need to replicate `IsOnline(s)`
|
||||
s.Status != "offline" && s.Status != "invisible" && s.Status != "unknown"
|
||||
) && (!isLargeGuild || s.LastSeen >= offlineTreshold)))
|
||||
.Where(x => x.IdNavigation.Sessions.Count > 0) // ignore members without sessions
|
||||
.ToListAsync();
|
||||
|
||||
var mappedPartialUsers = members.Select(x => x.IdNavigation).ToDictionary(x => x.Id, x => x.ToPartialUser());
|
||||
var mappedMembers = members.ToDictionary(m => m.Id, m => m.ToPublicMember(mappedPartialUsers[m.Id]));
|
||||
var mappedPartialUsers = members.Select(x => x.IdNavigation).ToFrozenDictionary(x => x.Id, x => x.ToPartialUser());
|
||||
var mappedMembers = members.ToFrozenDictionary(m => m.Id, m => m.ToPublicMember(mappedPartialUsers[m.Id]));
|
||||
|
||||
var presences = members.Select(x => x.IdNavigation).Where(x => x.Sessions.Count > 0).ToDictionary(x => x.Id, x =>
|
||||
var presences = members.Select(x => x.IdNavigation).Where(x => x.Sessions.Count > 0).ToFrozenDictionary(x => x.Id, x =>
|
||||
{
|
||||
var sortedSessions = x.Sessions.OrderByDescending(s => s.LastSeen).ToList();
|
||||
return new PresenceResponse()
|
||||
return new Presence()
|
||||
{
|
||||
GuildId = guildId,
|
||||
User = mappedPartialUsers[x.Id],
|
||||
Activities = x.Sessions.Where(s => s.Status is not ("offline" or "invisible" or "unknown"))
|
||||
.SelectMany(s => JsonSerializer.Deserialize<JsonObject[]>(s.Activities) ?? []).ToList(),
|
||||
Status = sortedSessions.FirstOrDefault(s => !string.IsNullOrWhiteSpace(s.Status))?.Status ?? "offline",
|
||||
ClientStatus = JsonSerializer.Deserialize<PresenceResponse.ClientStatuses>(sortedSessions.First(s => !string.IsNullOrWhiteSpace(s.ClientStatus)).ClientStatus) ??
|
||||
ClientStatus = JsonSerializer.Deserialize<Presence.ClientStatuses>(sortedSessions.First(s => !string.IsNullOrWhiteSpace(s.ClientStatus)).ClientStatus) ??
|
||||
new()
|
||||
};
|
||||
}).Where(x => x.Value.Activities.Count > 0).ToDictionary();
|
||||
}).Where(x => x.Value.Activities.Count > 0).ToFrozenDictionary();
|
||||
|
||||
var r = new GuildSyncResponse()
|
||||
{
|
||||
|
||||
@@ -1,13 +1,50 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Spacebar.GatewayOffload.Extensions.Db;
|
||||
using Spacebar.Interop.Authentication.AspNetCore;
|
||||
using Spacebar.Interop.Replication.Abstractions;
|
||||
using Spacebar.Models.Db.Contexts;
|
||||
using Spacebar.Models.Gateway;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
namespace Spacebar.GatewayOffload.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("/_spacebar/offload/gateway/LazyRequest")]
|
||||
public class Op14Controller : ControllerBase {
|
||||
public class Op14Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthenticationService authService, SpacebarDbContext db, IServiceProvider sp) : ControllerBase {
|
||||
[HttpPost]
|
||||
public async IAsyncEnumerable<ReplicationMessage> DoLazyRequest() {
|
||||
yield break;
|
||||
public async IAsyncEnumerable<ReplicationMessage> 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)) {
|
||||
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);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Fetch hoisted roles for the guild to define groups
|
||||
var hoistedRoles = await db.Roles
|
||||
.AsNoTracking()
|
||||
.Where(r => r.GuildId == payload.GuildId && r.Hoist)
|
||||
.OrderByDescending(r => r.Position)
|
||||
.Select(r => new { r.Id })
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task<string?> GetMemberListIdAsync(SpacebarDbContext db, string guildId, string 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 == "[]") {
|
||||
return "everyone";
|
||||
}
|
||||
|
||||
return null; // TODO
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Spacebar.Models.Db.Models;
|
||||
|
||||
namespace Spacebar.GatewayOffload.Extensions.Db;
|
||||
|
||||
public static class UserExtensions {
|
||||
extension(User user) {
|
||||
public string Tag => $"{user.Username}#{user.Discriminator}";
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArcaneLibs.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Spacebar.Interop.Authentication;
|
||||
using Spacebar.Interop.Authentication.AspNetCore;
|
||||
using Spacebar.Models.Db.Contexts;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("APPSETTINGS_PATH")))
|
||||
@@ -18,9 +21,7 @@ builder.Services.AddControllers(options => {
|
||||
options.JsonSerializerOptions.WriteIndented = true;
|
||||
options.JsonSerializerOptions.MaxDepth = 100;
|
||||
// options.JsonSerializerOptions.DefaultBufferSize = ;
|
||||
}).AddMvcOptions(o=> {
|
||||
o.SuppressOutputFormatterBuffering = true;
|
||||
});
|
||||
}).AddMvcOptions(o => { o.SuppressOutputFormatterBuffering = true; });
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
|
||||
Reference in new issue
Block a user