Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Realize ForwardWebSocketService #22

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
Lagrange.Core/Utility/Crypto/Provider/Dandelion/*.cs
Lagrange.Core/Utility/Crypto/Provider/Dandelion/*.cs
.vscode/
6 changes: 4 additions & 2 deletions Lagrange.OneBot/Core/Entity/Action/OneBotResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class OneBotResult(object? data, int retCode, string status, string echo)
[JsonPropertyName("retcode")] public int RetCode { get; set; } = retCode;

[JsonPropertyName("data")] public object? Data { get; set; } = data;

[JsonPropertyName("echo")] public string Echo { get; set; } = echo;
}

[JsonIgnore] public string? Identifier { get; internal set; }
}
116 changes: 116 additions & 0 deletions Lagrange.OneBot/Core/Network/ForwardWSService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System.Text;
using System.Text.Json;
using Lagrange.OneBot.Core.Entity.Action;
using Lagrange.OneBot.Core.Entity.Meta;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using PHS.Networking.Enums;
using WebsocketsSimple.Server;
using WebsocketsSimple.Server.Events.Args;
using WebsocketsSimple.Server.Models;
using Timer = System.Threading.Timer;

namespace Lagrange.OneBot.Core.Network;

public sealed class ForwardWSService : ILagrangeWebService
{
public event EventHandler<MsgRecvEventArgs> OnMessageReceived = delegate { };

private readonly WebsocketServer _server;

private readonly IConfiguration _config;

private readonly ILogger _logger;

private readonly Timer _timer;

private readonly bool _shouldAuthenticate;

private static readonly Encoding _utf8 = new UTF8Encoding(false);

public ForwardWSService(IConfiguration config, ILogger<LagrangeApp> logger)
{
_config = config;
_logger = logger;

var ws = _config.GetSection("Implementation").GetSection("ForwardWebSocket");

_timer = new Timer(OnHeartbeat, null, int.MaxValue, ws.GetValue<int>("HeartBeatInterval"));
_shouldAuthenticate = !string.IsNullOrEmpty(_config["AccessToken"]);

_server = new WebsocketServer(new ParamsWSServer(ws.GetValue<int>("Port")));
_server.MessageEvent += OnMessage;

if (_shouldAuthenticate)
_server.ConnectionEvent += OnConnection;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
await _server.StartAsync(cancellationToken);
var lifecycle = new OneBotLifecycle(_config.GetValue<uint>("Account:Uin"), "connect");
await SendJsonAsync(lifecycle, cancellationToken);
}

public Task StopAsync(CancellationToken cancellationToken)
{
_timer.Dispose();
_server.Dispose();

return Task.CompletedTask;
}

public async Task SendJsonAsync<T>(T json, CancellationToken cancellationToken = default)
{
var payload = JsonSerializer.Serialize(json);

if (json is OneBotResult oneBotResult)
{
var connection = _server.Connections.FirstOrDefault(
(c) => c.ConnectionId == oneBotResult.Identifier
);

if (connection is not null)
await _server.SendToConnectionAsync(payload, connection);
}
else
await _server.BroadcastToAllConnectionsAsync(payload, cancellationToken);
}

private void OnHeartbeat(object? sender)
{
var status = new OneBotStatus(true, true);
var heartBeat = new OneBotHeartBeat(
_config.GetValue<uint>("Account:Uin"),
_config.GetValue<int>("Implementation:ForwardWebSocket:HeartBeatInterval"),
status
);

SendJsonAsync(heartBeat).GetAwaiter().GetResult();
}

private void OnConnection(object sender, WSConnectionServerEventArgs e)
{
if (
_shouldAuthenticate
&& e.ConnectionEventType == ConnectionEventType.Connected
&& (
e.RequestHeaders is null
|| !e.RequestHeaders.TryGetValue("Authorization", out string? authorization)
|| authorization != $"Bearer {_config["AccessToken"]}"
)
)
{
e.Connection.Websocket.Abort();
}
}

private void OnMessage(object sender, WSMessageServerEventArgs e)
{
if (e.MessageEventType == MessageEventType.Receive)
{
string text = _utf8.GetString(e.Bytes);
OnMessageReceived.Invoke(this, new(e.Message ?? "", e.Connection.ConnectionId));
}
}
}
2 changes: 1 addition & 1 deletion Lagrange.OneBot/Core/Network/HttpPostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Lagrange.OneBot.Core.Network;

public sealed class HttpPostService : ILagrangeWebService
{
public event EventHandler<string>? OnMessageReceived = delegate { };
public event EventHandler<MsgRecvEventArgs>? OnMessageReceived = delegate { };

private readonly HttpClient _client;

Expand Down
2 changes: 1 addition & 1 deletion Lagrange.OneBot/Core/Network/ILagrangeWebService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Lagrange.OneBot.Core.Network;

public interface ILagrangeWebService : IHostedService
{
public event EventHandler<string> OnMessageReceived;
public event EventHandler<MsgRecvEventArgs> OnMessageReceived;

public Task SendJsonAsync<T>(T json, CancellationToken cancellationToken = default);
}
10 changes: 10 additions & 0 deletions Lagrange.OneBot/Core/Network/MsgRecvEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.Extensions.Hosting;

namespace Lagrange.OneBot.Core.Network;

public class MsgRecvEventArgs(string data, string? identifier) : EventArgs
{
public string? Identifier { get; init; } = identifier;

public string Data { get; init; } = data;
}
92 changes: 46 additions & 46 deletions Lagrange.OneBot/Core/Network/ReverseWSService.cs
Original file line number Diff line number Diff line change
@@ -1,88 +1,88 @@
using System.Net.WebSockets;
using System.Text.Json;
using Lagrange.OneBot.Core.Entity.Meta;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Websocket.Client;
using WebsocketsSimple.Client;
using WebsocketsSimple.Client.Models;
using Timer = System.Threading.Timer;

namespace Lagrange.OneBot.Core.Network;

public sealed class ReverseWSService : ILagrangeWebService
{
public event EventHandler<string> OnMessageReceived = delegate { };
private readonly WebsocketClient _socket;
public event EventHandler<MsgRecvEventArgs> OnMessageReceived = delegate { };

private readonly WebsocketClient _websocketClient;

private readonly IConfiguration _config;

private readonly ILogger _logger;

private readonly Timer _timer;

public ReverseWSService(IConfiguration config, ILogger<LagrangeApp> logger)
{
_config = config;
_logger = logger;

var ws = _config.GetSection("Implementation").GetSection("ReverseWebSocket");
string url = $"ws://{ws["Host"]}:{ws["Port"]}{ws["Suffix"]}";

_socket = new WebsocketClient(new Uri(url), () =>
var headers = new Dictionary<string, string>()
{
var socket = new ClientWebSocket();

SetRequestHeader(socket, new Dictionary<string, string>
{
{ "X-Client-Role", "Universal" },
{ "X-Self-ID", _config.GetValue<uint>("Account:Uin").ToString() },
{ "User-Agent", Constant.OneBotImpl }
});
socket.Options.KeepAliveInterval = TimeSpan.FromSeconds(5);
if (_config["AccessToken"] != null) socket.Options.SetRequestHeader("Authorization", $"Bearer {_config["AccessToken"]}");

return socket;
});

_timer = new Timer(OnHeartbeat, null, int.MaxValue, config.GetValue<int>("Implementation:ReverseWebSocket:HeartBeatInterval"));
_socket.MessageReceived.Subscribe(resp => OnMessageReceived.Invoke(this, resp.Text ?? ""));
{ "X-Client-Role", "Universal" },
{ "X-Self-ID", _config.GetValue<uint>("Account:Uin").ToString() },
{ "User-Agent", Constant.OneBotImpl }
};

if (_config["AccessToken"] != null)
headers.Add("Authorization", $"Bearer {_config["AccessToken"]}");

_websocketClient = new WebsocketClient(
new ParamsWSClient(
ws["Host"],
ws.GetValue<int>("Port"),
false,
string.Empty,
ws["Suffix"],
keepAliveIntervalSec: ws.GetValue<int>("ReconnectInterval") / 1000,
requestHeaders: headers
)
);
_websocketClient.MessageEvent += (_, e) =>
OnMessageReceived.Invoke(this, new(e.Message ?? "", null));

_timer = new Timer(OnHeartbeat, null, int.MaxValue, ws.GetValue<int>("HeartBeatInterval"));
}

public async Task StartAsync(CancellationToken cancellationToken)
{
await _socket.Start();
await _websocketClient.ConnectAsync();

var lifecycle = new OneBotLifecycle(_config.GetValue<uint>("Account:Uin"), "connect");
await SendJsonAsync(lifecycle, cancellationToken);
}

public Task StopAsync(CancellationToken cancellationToken)
public async Task StopAsync(CancellationToken cancellationToken)
{
_timer.Dispose();
_socket.Dispose();

return Task.CompletedTask;
await _websocketClient.DisconnectAsync();
}

public Task SendJsonAsync<T>(T json, CancellationToken cancellationToken = default)
{
var payload = JsonSerializer.SerializeToUtf8Bytes(json);
return _socket.SendInstant(payload);
return _websocketClient.SendAsync(payload);
}

private void OnHeartbeat(object? sender)
{
var status = new OneBotStatus(true, true);
var heartBeat = new OneBotHeartBeat(
_config.GetValue<uint>("Account:Uin"),
_config.GetValue<int>("Implementation:ReverseWebSocket:HeartBeatInterval"),
status);

SendJsonAsync(heartBeat);
}
_config.GetValue<uint>("Account:Uin"),
_config.GetValue<int>("Implementation:ReverseWebSocket:HeartBeatInterval"),
status
);

private static void SetRequestHeader(ClientWebSocket webSocket, Dictionary<string, string> headers)
{
foreach (var (key, value) in headers) webSocket.Options.SetRequestHeader(key, value);
SendJsonAsync(heartBeat);
}
}
}
21 changes: 13 additions & 8 deletions Lagrange.OneBot/Core/Operation/OperationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,35 @@ public OperationService(BotContext bot, ILagrangeWebService service)
{
_bot = bot;
_service = service;
_operations = new Dictionary<string, IOperation>();
_operations = [];

foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
var attribute = type.GetCustomAttribute<OperationAttribute>();
if (attribute != null) _operations[attribute.Api] = (IOperation)type.CreateInstance(false);
if (attribute != null)
_operations[attribute.Api] = (IOperation)type.CreateInstance(false);
}

service.OnMessageReceived += async (_, s) => await HandleOperation(s);
service.OnMessageReceived += async (_, e) => await HandleOperation(e);
}

private async Task HandleOperation(string data)
private async Task HandleOperation(MsgRecvEventArgs e)
{
var action = JsonSerializer.Deserialize<OneBotAction>(data);
var action = JsonSerializer.Deserialize<OneBotAction>(e.Data);

if (action != null)
{
var handler = _operations[action.Action];
var result = await handler.HandleOperation(action.Echo, _bot, action.Params);

if (!string.IsNullOrEmpty(e.Identifier)) // add an identifier for `ForwardWSService`
result.Identifier = e.Identifier;

await _service.SendJsonAsync(result);
}
else
{
throw new Exception("action deserialized failed");
}
}
}
}
9 changes: 5 additions & 4 deletions Lagrange.OneBot/Lagrange.OneBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Websocket.Client" Version="5.0.0" />
<PackageReference Include="Net.Codecrete.QrCodeGenerator" Version="1.6.1" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="WebsocketsSimple.Client" Version="7.0.7" />
<PackageReference Include="WebsocketsSimple.Server" Version="7.0.7" />
<PackageReference Include="Net.Codecrete.QrCodeGenerator" Version="1.6.1" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 1 addition & 3 deletions Lagrange.OneBot/LagrangeAppBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System.Text.Json;
using Lagrange.Core.Common;
using Lagrange.Core.Common.Interface;
using Lagrange.OneBot.Core;
using Lagrange.OneBot.Core.Message;
using Lagrange.OneBot.Core.Network;
using Lagrange.OneBot.Core.Operation;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -76,6 +73,7 @@ public LagrangeAppBuilder ConfigureBots()
public LagrangeAppBuilder ConfigureOneBot()
{
Services.AddSingleton<ILagrangeWebService, ReverseWSService>();
Services.AddSingleton<ILagrangeWebService, ForwardWSService>();
return this;
}

Expand Down
Loading
Loading