mirror of
https://github.com/spacebarchat/server.git
synced 2026-05-24 14:35:27 +00:00
client: basic message parsing, gateway listening
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
@using System.Collections.ObjectModel
|
||||
@using System.Text.Json
|
||||
@using System.Text.RegularExpressions
|
||||
@using ArcaneLibs.Blazor.Components.Services
|
||||
@using ArcaneLibs.Extensions
|
||||
@using Spacebar.Client.Core
|
||||
@using Spacebar.Models.Generic
|
||||
@inject JsConsoleService jsConsole
|
||||
|
||||
@foreach (var message in Messages) {
|
||||
if (message.Type == 0 || message.Type == 19) {
|
||||
if (message.Type == 19) {
|
||||
<span>╭⎯⎯ <b>@message.ReferencedMessage?.Author.Username</b> @string.Join("", message.ReferencedMessage?.Content?.Split("\n")[0].Take(100) ?? [])</span>
|
||||
<br/>
|
||||
}
|
||||
|
||||
<b class="@(string.Join(" ", GetMemberRoles(message.GuildId.Value, message.Author.Id).Select(x => $"role_{x}")))">@message.Author.Username</b>
|
||||
<br/>
|
||||
<span>@message.Content</span>
|
||||
<br/>
|
||||
<div style="background-color: #FFFF0033;">
|
||||
@GetMessageContent(message)
|
||||
</div>
|
||||
<br/>
|
||||
<div style="background-color: #FF00FF33;">
|
||||
@GetMessageContentEnumerated(message)
|
||||
</div>
|
||||
<br/>
|
||||
@if (message.Attachments.Any()) {
|
||||
@foreach (var att in message.Attachments) {
|
||||
@if (att.ContentType.StartsWith("image/")) {
|
||||
<img src="@att.ProxyUrl" class="attachmentImage" alt="Attachment image"/>
|
||||
}
|
||||
else {
|
||||
<span class="code">@att.ToJson()</span>
|
||||
}
|
||||
|
||||
<br/>
|
||||
}
|
||||
}
|
||||
|
||||
<br/>
|
||||
}
|
||||
else {
|
||||
<span class="code" style="background-color: #772222">
|
||||
Unknown message type @message.Type
|
||||
<details>
|
||||
<summary>View raw message data</summary>
|
||||
@message.ToJson(indent: true)
|
||||
</details>
|
||||
</span>
|
||||
<br/>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public required ObservableCollection<Message> Messages { get; set; }
|
||||
|
||||
public List<string> GetMemberRoles(long guildId, long memberId) {
|
||||
// App.ClientManager.ClientState.Guilds[guildId].
|
||||
return [];
|
||||
}
|
||||
|
||||
private static string[] _partColors = [
|
||||
"#FFFF0033",
|
||||
"#FF00FF33",
|
||||
"#00FFFF33",
|
||||
"#FF000033",
|
||||
"#00FF0033",
|
||||
"#0000FF33"
|
||||
];
|
||||
|
||||
private static bool _shouldRenderMarkdownZones = true;
|
||||
private RenderFragment GetMessageContent(Message msg) => builder => {
|
||||
var fullContent = msg.Content;
|
||||
int i = 0, line = 0;
|
||||
Regex[][] groupedRegexes = [[MarkdownBoldRegex, MarkdownCodeblockRegex], [MarkdownCodeRegex, MarkdownItalicRegex]];
|
||||
Regex[] regexes = groupedRegexes.SelectMany(x => x).ToArray();
|
||||
|
||||
var lines = fullContent.Split('\n');
|
||||
foreach (var lineContent in lines) {
|
||||
var content = lineContent;
|
||||
var elemType = "span";
|
||||
var shouldBr = true;
|
||||
if (content.StartsWith("-#")) {
|
||||
elemType = "sub";
|
||||
content = content[2..].TrimStart();
|
||||
}
|
||||
else if (content.StartsWith("#")) {
|
||||
var hdrLevel = content.TakeWhile(x => x == '#').Count();
|
||||
content = content[hdrLevel..];
|
||||
shouldBr = false;
|
||||
elemType = "h" + hdrLevel;
|
||||
}
|
||||
else if (content.StartsWith("*")) {
|
||||
|
||||
}
|
||||
|
||||
var indicies = regexes.Select(r => new {
|
||||
regex = r,
|
||||
regexStr = r.ToString(),
|
||||
matchIdx = r.Match(content).Index,
|
||||
matchContent = r.Match(content).Value
|
||||
}).Where(x => x.matchIdx != 0).ToList();
|
||||
|
||||
if (indicies.Any()) {
|
||||
jsConsole.Info("Found indices: ", JsonSerializer.SerializeToElement(indicies, new JsonSerializerOptions() {
|
||||
IncludeFields = true
|
||||
}));
|
||||
|
||||
builder.OpenElement(i++, elemType);
|
||||
{
|
||||
if (_shouldRenderMarkdownZones) builder.AddAttribute(i++, "style", $"background-color: {_partColors[i % _partColors.Length]}");
|
||||
builder.AddContent(i++, content![..indicies.Min(x => x.matchIdx)]);
|
||||
content = content![..indicies.Min(x => x.matchIdx)];
|
||||
}
|
||||
builder.CloseComponent();
|
||||
}
|
||||
else {
|
||||
builder.OpenElement(i++, elemType);
|
||||
{
|
||||
if (_shouldRenderMarkdownZones && elemType != "span") builder.AddAttribute(i++, "style", $"background-color: {_partColors[i % _partColors.Length]}");
|
||||
builder.AddContent(i++, content!);
|
||||
}
|
||||
builder.CloseComponent();
|
||||
// continue;
|
||||
}
|
||||
|
||||
if (line++ <= lines.Length && shouldBr) {
|
||||
builder.AddMarkupContent(i++, "<br/>");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private RenderFragment GetMessageContentEnumerated(Message msg) => builder => {
|
||||
int i = 0;
|
||||
builder.OpenElement(i++, "div");
|
||||
builder.AddAttribute(i++, "id", "msg"+msg.Id);
|
||||
foreach (var comp in new MarkdownEnumerator().EnumerateMarkdownComponents(msg.Content)) {
|
||||
if (comp is ContainerMarkdownNode) {
|
||||
|
||||
}
|
||||
else {
|
||||
builder.OpenElement(i++, "span");
|
||||
builder.AddAttribute(i++, "class", "mdErrorBlinkBg");
|
||||
// jsConsole.Info("frames:", builder.GetFrames().Array[0].);
|
||||
// builder.AddAttribute(i++, );
|
||||
builder.AddContent(i++, $"Unknown component type: {comp.GetType().FullName}");
|
||||
builder.CloseElement();
|
||||
}
|
||||
}
|
||||
|
||||
builder.CloseElement();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Spacebar.Client.Components;
|
||||
|
||||
public partial class ChannelMessageList {
|
||||
[GeneratedRegex(@"\*\*(.*)\*\*")]
|
||||
private static partial Regex MarkdownBoldRegex { get; }
|
||||
|
||||
[GeneratedRegex(@"\*(.*)\*")]
|
||||
private static partial Regex MarkdownItalicRegex { get; }
|
||||
|
||||
[GeneratedRegex(@"```((?<lang>.*)\n)(?<content>.*)```")]
|
||||
private static partial Regex MarkdownCodeblockRegex { get; }
|
||||
|
||||
[GeneratedRegex(@"``?(.*)`?`")]
|
||||
private static partial Regex MarkdownCodeRegex { get; }
|
||||
|
||||
[GeneratedRegex(@"<#(\d*)>")]
|
||||
private static partial Regex MarkdownChannelMentionRegex { get; }
|
||||
|
||||
[GeneratedRegex(@"<@(\d*)>")]
|
||||
private static partial Regex MarkdownUserMentionRegex { get; }
|
||||
|
||||
[GeneratedRegex(@"<@&(\d*)>")]
|
||||
private static partial Regex MarkdownRoleMentionRegex { get; }
|
||||
|
||||
[GeneratedRegex(@"<:(?<name>[a-zA-Z0-9]*?):(?<emojiId>\d*>)")]
|
||||
private static partial Regex MarkdownEmojiMentionRegex { get; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.attachmentImage {
|
||||
max-height: 300px;
|
||||
max-width: 300px;
|
||||
object-position: center;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@@ -2,52 +2,95 @@
|
||||
@using ArcaneLibs.Extensions
|
||||
@using Spacebar.Client.Core
|
||||
@using Spacebar.Client.WebCore
|
||||
@using Spacebar.Client.WebCore.Client
|
||||
@using Spacebar.Models.Gateway
|
||||
@inject SessionStore sessionStore
|
||||
@inject SpacebarClientProviderService clientProvider
|
||||
@inject JsConsoleService jsConsole
|
||||
|
||||
<DebugBanner Name="@GetType().Name" @ref="_dbgBanner"/>
|
||||
<CascadingValue TValue="AuthenticatedSpacebarClient" Value="@_client">
|
||||
<CascadingValue TValue="AuthenticatedSpacebarClient" Value="@Client">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
private DebugBanner _dbgBanner = null!;
|
||||
private AuthenticatedSpacebarClient? _client { get; set; }
|
||||
private bool _readyReceived = false;
|
||||
|
||||
public ClientManager() {
|
||||
ClientAvailable = Task.Run(async () => {
|
||||
while (Client is null) await Task.Delay(50);
|
||||
ClientAvailable = null;
|
||||
});
|
||||
ClientReady = Task.Run(async () => {
|
||||
while (!_readyReceived) await Task.Delay(50);
|
||||
ClientAvailable = null;
|
||||
});
|
||||
}
|
||||
|
||||
public AuthenticatedSpacebarClient? Client { get; set; }
|
||||
|
||||
public ClientStateContainer ClientState { get; set; } = new();
|
||||
|
||||
[Parameter]
|
||||
public required RenderFragment ChildContent { get; set; }
|
||||
|
||||
public Task? ClientAvailable { get; set; }
|
||||
public Task? ClientReady { get; set; }
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
||||
if (!firstRender) return;
|
||||
await _dbgBanner.SetStatus("Preparing for launch!");
|
||||
await Task.Delay(125);
|
||||
var session = await sessionStore.GetCurrentSessionAsync();
|
||||
if (session != null) {
|
||||
_client = await clientProvider.GetAuthenticatedClientAsync(session.ServerName, session.AccessToken);
|
||||
Client = await clientProvider.GetAuthenticatedClientAsync(session.ServerName, session.AccessToken);
|
||||
await _dbgBanner.SetStatus($"Got authenticated client for {session.ProfileCache.Username}#{session.ProfileCache.Discriminator} on {session.ServerName}! Connecting to gateway...");
|
||||
_client.Gateway.IdentifyData.ClientProperties = new IdentifyClientProperties() {
|
||||
Client.Gateway.IdentifyData.ClientProperties = new IdentifyClientProperties() {
|
||||
HasClientMods = false,
|
||||
ApplicationArchitecture = "wasm"
|
||||
}.ToJsonNode().AsObject();
|
||||
StateHasChanged();
|
||||
await _client.Gateway.Connect();
|
||||
_ = _client.Gateway.Start().ContinueWith(ct => {
|
||||
await Client.Gateway.Connect();
|
||||
_ = Client.Gateway.Start().ContinueWith(ct => {
|
||||
jsConsole.Warn("[ClientManager] Heartbeat loop exited!");
|
||||
if (ct.IsFaulted) {
|
||||
jsConsole.Error("Unhandled exception during gateway connection:", ct.Exception.ToString());
|
||||
throw ct.Exception;
|
||||
}
|
||||
});
|
||||
_client.Gateway.OnceGatewayMessage.Add(async msg => {
|
||||
Client.Gateway.OnceGatewayMessage.Add(async msg => {
|
||||
if (msg is { Opcode: GatewayOpcode.S2CDispatch, DispatchEventType: "READY" }) {
|
||||
await _dbgBanner.SetStatus($"Got READY from gateway");
|
||||
await _dbgBanner.SetStatus(null, 1750);
|
||||
await _dbgBanner.SetStatus($"Got READY from gateway, deserializing...");
|
||||
var content = msg.GetData<ReadyResponse>();
|
||||
await jsConsole.Info("Parsed ready payload:", content);
|
||||
await _dbgBanner.SetStatus($"Deserialized READY from gateway, handling...");
|
||||
// ClientState.Guilds.AddRange(content.Guilds.ToDictionary(x=>x.Id, x=>x));
|
||||
foreach (var guild in content.Guilds) {
|
||||
ClientState.Guilds.Add(guild.Id, guild);
|
||||
await _dbgBanner.SetStatus($"Deserialized READY from gateway, handling... guilds ({ClientState.Guilds.Count})");
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
foreach (var guild in content.Relationships) {
|
||||
// ClientState.Relationships.Add(guild.Id, guild);
|
||||
await _dbgBanner.SetStatus($"Deserialized READY from gateway, handling... guilds ({ClientState.Guilds.Count}), relationships (0)");
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
await jsConsole.Info("Parsed ready payload:", new { original = msg.EventData, parsed = content });
|
||||
await _dbgBanner.SetStatus($"Done handling ready!");
|
||||
_readyReceived = true;
|
||||
_ = _dbgBanner.SetStatus(null, 1750);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg is { Opcode: GatewayOpcode.S2CDispatch, DispatchEventType: "READY_SUPPLEMENTAL" }) {
|
||||
await _dbgBanner.SetStatus("Received READY_SUPPLEMENTAL...");
|
||||
await jsConsole.Info("Parsed ready_supplemental payload", new { original = msg.EventData });
|
||||
_ =_dbgBanner.SetStatus(null, 1750);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -55,7 +98,6 @@
|
||||
await _dbgBanner.SetStatus("No session marked as current... :(");
|
||||
await _dbgBanner.SetStatus(null, 1750);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace Spacebar.Client.Core;
|
||||
|
||||
public class MarkdownEnumerator {
|
||||
public IEnumerable<BaseMarkdownNode> EnumerateMarkdownComponents(string text) {
|
||||
if (text.StartsWith("-#")) {
|
||||
var line = text.Split('\n')[0];
|
||||
text = text.Replace(line + "\n", "");
|
||||
yield return new ContainerMarkdownNode() {
|
||||
ComponentType = "sub",
|
||||
Contents = new MarkdownEnumerator().EnumerateMarkdownComponents(line[2..].TrimStart()).ToList()
|
||||
};
|
||||
}
|
||||
else if (text.StartsWith("#")) {
|
||||
var hdrLevel = text.TakeWhile(x => x == '#').Count();
|
||||
var line = text.Split('\n')[0];
|
||||
text = text.Replace(line + "\n", "");
|
||||
yield return new ContainerMarkdownNode() {
|
||||
ComponentType = "h" + hdrLevel,
|
||||
Contents = new MarkdownEnumerator().EnumerateMarkdownComponents(line[hdrLevel..].TrimStart()).ToList()
|
||||
};
|
||||
}
|
||||
yield return new InnerTextMarkdownNode(text);
|
||||
}
|
||||
}
|
||||
|
||||
public class BaseMarkdownNode {
|
||||
}
|
||||
|
||||
public class ContainerMarkdownNode : BaseMarkdownNode {
|
||||
public string ComponentType { get; set; }
|
||||
public List<BaseMarkdownNode> Contents { get; set; }
|
||||
}
|
||||
|
||||
public class InnerTextMarkdownNode(string Text) : BaseMarkdownNode{
|
||||
public string Text { get; set; }
|
||||
}
|
||||
@@ -32,10 +32,12 @@ public class AuthenticatedSpacebarClient {
|
||||
};
|
||||
ApiHttpClient.DefaultRequestHeaders.Authorization = new("Bearer", token);
|
||||
Gateway = new(sp.GetRequiredService<ILogger<AuthenticatedSpacebarGatewayClient>>(), wellKnown, token);
|
||||
ClientWellKnown = wellKnown;
|
||||
}
|
||||
|
||||
public HttpClient ApiHttpClient { get; set; }
|
||||
public AuthenticatedSpacebarGatewayClient Gateway { get; set; }
|
||||
public SpacebarClientWellKnown ClientWellKnown { get; set; }
|
||||
|
||||
// TODO: write a proper full user model...
|
||||
public async Task<PartialUser> GetCurrentUser() {
|
||||
@@ -48,6 +50,28 @@ public class AuthenticatedSpacebarClient {
|
||||
~AuthenticatedSpacebarClient() {
|
||||
ApiHttpClient.Dispose();
|
||||
}
|
||||
|
||||
public SpacebarClientChannel GetChannel(long channelId) {
|
||||
return new(this, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
public class SpacebarClientChannel(AuthenticatedSpacebarClient client, long channelId) {
|
||||
public long Id => channelId;
|
||||
|
||||
public async Task<List<Message>> GetMessagesAsync(long? around = null, long? before = null, long? after = null, int limit = 50) {
|
||||
var uri = $"channels/{channelId}/messages?limit={limit}";
|
||||
if (around.HasValue) uri += $"&around={around.Value}";
|
||||
if (before.HasValue) uri += $"&before={before.Value}";
|
||||
if (after.HasValue) uri += $"&after={after.Value}";
|
||||
|
||||
var resp = await client.ApiHttpClient.GetAsync(uri);
|
||||
// TODO: abstract out
|
||||
if (!resp.IsSuccessStatusCode) throw SpacebarApiException.FromJson((await resp.Content.ReadFromJsonAsync<JsonObject>())!);
|
||||
var data = await resp.Content.ReadFromJsonAsync<List<JsonObject>>();
|
||||
Console.WriteLine(data.ToJson(indent: false, ignoreNull: true));
|
||||
return data.Select(x => x.Deserialize<Message>()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthenticatedSpacebarGatewayClient(ILogger<AuthenticatedSpacebarGatewayClient> logger, SpacebarClientWellKnown wellKnown, string token) {
|
||||
@@ -87,11 +111,23 @@ public class AuthenticatedSpacebarGatewayClient(ILogger<AuthenticatedSpacebarGat
|
||||
logger.LogInformation("Got heartbeat ACK from server!");
|
||||
}
|
||||
|
||||
await Task.WhenAll(OnGatewayMessage.Select(x => x(msg)).ToArray());
|
||||
await Task.WhenAll(OnGatewayMessage.Select(async x => {
|
||||
try {
|
||||
await x(msg);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.LogError("OnGatewayMessage callback failed: {e}", e);
|
||||
}
|
||||
}).ToArray());
|
||||
foreach (var t in OnceGatewayMessage.Select((Func<GatewayPayload, Task<bool>> Callback, Task<bool> WasHandled) (cb) => (cb, cb(msg))).ToList()) {
|
||||
var handled = await t.WasHandled;
|
||||
if (handled) {
|
||||
OnceGatewayMessage.Remove(t.Callback);
|
||||
try {
|
||||
var handled = await t.WasHandled;
|
||||
if (handled) {
|
||||
OnceGatewayMessage.Remove(t.Callback);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.LogError("OnceGatewayMessage callback failed: {e}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,13 +170,14 @@ public class AuthenticatedSpacebarGatewayClient(ILogger<AuthenticatedSpacebarGat
|
||||
idx++;
|
||||
|
||||
if (msg.EndOfMessage) {
|
||||
Console.WriteLine("Got message, deserialising...");
|
||||
Console.WriteLine($"Got message, deserialising {messageParts.Count} bytes...");
|
||||
var fullMsg = messageParts.ToArray();
|
||||
trace.Add(($"LD({messageParts.Count})", sw.GetElapsedAndRestart()));
|
||||
|
||||
var d = JsonSerializer.Deserialize<GatewayPayload>(fullMsg);
|
||||
trace.Add(($"LJS({fullMsg.Length})", sw.GetElapsedAndRestart()));
|
||||
|
||||
Console.WriteLine($"Received gateway message #{d.Sequence}: {(byte)d.Opcode}/{d.Opcode.ToString().Replace("S2C", "")} {d.DispatchEventType}");
|
||||
yield return d ?? throw new InvalidDataException("Gateway message deserialisation returned null?");
|
||||
trace.Add(($"YLD", sw.GetElapsedAndRestart()));
|
||||
|
||||
|
||||
@@ -14,14 +14,22 @@
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
@foreach (var guild in App.ClientManager.ClientState.Guilds) {
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="@($"/channels/{guild.Key}/{guild.Value.Channels.First(x => x.Type == 0).Id}")">
|
||||
@{
|
||||
var guildIconUrl = string.IsNullOrWhiteSpace(guild.Value.Icon)
|
||||
? "/img/icon_white.png"
|
||||
: new Uri($"{App.ClientManager.Client?.ClientWellKnown.Cdn.BaseUrl}/icons/{guild.Key}/{guild.Value.Icon}").AbsoluteUri;
|
||||
}
|
||||
<img class="navGuildIcon" src="@guildIconUrl" alt="" aria-hidden="true">
|
||||
@guild.Value.Name
|
||||
</NavLink>
|
||||
</div>
|
||||
}
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="counter">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="weather">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
|
||||
<NavLink class="nav-link" href="/discovery/guilds">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Discover...
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -36,4 +44,8 @@
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
App.ClientManager.ClientState.Guilds.CollectionChanged += (_,_) => this.StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,26 @@
|
||||
.navGuildIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
image-rendering: high-quality;
|
||||
}
|
||||
|
||||
a:hover > .navGuildIcon {
|
||||
transition: 0.15s;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.nav-item > a.nav-link a {
|
||||
text-overflow: fade;
|
||||
text-wrap: nowrap;
|
||||
padding-left: 0;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
/* default CSS */
|
||||
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
@@ -52,7 +75,7 @@
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
@attribute [Route(PageUri)]
|
||||
@using ArcaneLibs.Blazor.Components.Services
|
||||
@using Spacebar.Client.Core
|
||||
@using Spacebar.Models.Gateway
|
||||
@inject JsConsoleService jsConsole
|
||||
|
||||
<h3>@@me</h3>
|
||||
<p>@Client?.ApiHttpClient.BaseAddress</p>
|
||||
<p>@Client?.Gateway.RawClientWebSocket.State</p>
|
||||
|
||||
@foreach (var guild in App.ClientManager.ClientState.Guilds) {
|
||||
<span class="code">@guild.Value.Name</span><br/>
|
||||
}
|
||||
|
||||
@code {
|
||||
private const string PageUri = "/channels/@me";
|
||||
|
||||
@@ -21,7 +24,8 @@
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
if (Client == null) return;
|
||||
Client.Gateway.OnGatewayMessage.Add(async msg => {
|
||||
await jsConsole.Log("Received gateway message: ", msg);
|
||||
// await jsConsole.Log("Received gateway message: ", msg);
|
||||
// StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
@page "/channels/{GuildId:long}/{ChannelId:long}"
|
||||
@page "/channels/{GuildId:long}/{ChannelId:long}/{MessageId:long}"
|
||||
@using System.Collections.ObjectModel
|
||||
@using ArcaneLibs.Blazor.Components.Services
|
||||
@using ArcaneLibs.Extensions
|
||||
@using Spacebar.Client.Components
|
||||
@using Spacebar.Models.Gateway
|
||||
@using Spacebar.Models.Generic
|
||||
@inject JsConsoleService jsConsole
|
||||
|
||||
<style>@_guildCss</style>
|
||||
<ChannelMessageList Messages="@_messages"></ChannelMessageList>
|
||||
|
||||
@code {
|
||||
|
||||
private ObservableCollection<Message> _messages { get; set; } = [];
|
||||
private string _guildCss = "";
|
||||
|
||||
[Parameter]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public long ChannelId { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public long? MessageId { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
var cid = ChannelId;
|
||||
Console.WriteLine($"OIA GuildChannel(g={GuildId},c={ChannelId},m={MessageId})");
|
||||
await (App.ClientManager.ClientAvailable ?? Task.CompletedTask);
|
||||
if (MessageId is null) {
|
||||
App.ClientManager.Client?.Gateway.OnceGatewayMessage.Add(async evt => {
|
||||
if (evt is { Opcode: GatewayOpcode.S2CDispatch, DispatchEventType: "MESSAGE_CREATE" }) {
|
||||
if (evt.EventData["channel_id"].GetValue<string>() != ChannelId.ToString()) return false;
|
||||
await jsConsole.Info("Current channel message:", evt);
|
||||
var msg = evt.GetData<Message>();
|
||||
await jsConsole.Info("... typed:", msg);
|
||||
_messages.Add(msg);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
return cid != ChannelId;
|
||||
});
|
||||
await (App.ClientManager.ClientReady ?? Task.CompletedTask);
|
||||
_messages = new ObservableCollection<Message>(Enumerable.Reverse(await App.ClientManager.Client.GetChannel(ChannelId).GetMessagesAsync(
|
||||
before: App.ClientManager.ClientState.Guilds[GuildId].Channels.First(x => x.Id == ChannelId).LastMessageId + 1,
|
||||
limit: 100
|
||||
)));
|
||||
|
||||
// build role color css...
|
||||
_guildCss = "";
|
||||
foreach (var role in App.ClientManager.ClientState.Guilds[GuildId].Roles) {
|
||||
_guildCss += $$"""
|
||||
.role_{{role.Id}} {
|
||||
color: #{{role.Colors.PrimaryColor.ToString("X6")}};
|
||||
}
|
||||
|
||||
""";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task SetParametersAsync(ParameterView parameters) {
|
||||
bool reset = false;
|
||||
if (parameters.GetValueOrDefault<long>("ChannelId") != ChannelId) reset = true;
|
||||
|
||||
await base.SetParametersAsync(parameters);
|
||||
if (reset) await OnInitializedAsync();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@page "/discovery/guilds"
|
||||
<h3>GuildDiscovery</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -14,9 +14,11 @@
|
||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||
<CompressionEnabled>false</CompressionEnabled>
|
||||
<BlazorCacheBootResources>false</BlazorCacheBootResources>
|
||||
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
|
||||
<!--<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>-->
|
||||
<WasmEnableHotReload>false</WasmEnableHotReload>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -24,7 +26,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.5" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js"/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using ArcaneLibs.Collections;
|
||||
using Spacebar.Models.Generic;
|
||||
|
||||
namespace Spacebar.Client.WebCore.Client;
|
||||
|
||||
public class ClientStateContainer {
|
||||
public ObservableDictionary<long, Guild> Guilds { get; set; } = [];
|
||||
}
|
||||
@@ -24,6 +24,25 @@ pre, .code {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.mdErrorBlinkBg {
|
||||
animation: ease-in-out 1s mdErrorBlinkBgAnim;
|
||||
border: 1px #ff00ff;
|
||||
margin-right: 1em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@keyframes mdErrorBlinkBgAnim {
|
||||
0%{
|
||||
background-color: #ffff0088;
|
||||
}
|
||||
50% {
|
||||
background-color: #ffff0000;
|
||||
}
|
||||
100% {
|
||||
background-color: #ffff0088;
|
||||
}
|
||||
}
|
||||
|
||||
#app > div > main > div {
|
||||
background-color: #333;
|
||||
border-bottom: none;
|
||||
|
||||
Reference in New Issue
Block a user