Skip to content

Commit

Permalink
realize forward webSocket service & update actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaitonn committed Oct 17, 2023
1 parent 3e9139c commit ed7e47a
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 56 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/Lagrange.Core-nuget-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name: Lagrange.Core NuGet Push
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

workflow_dispatch:

jobs:
build:
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/Lagrange.OneBot-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ name: Lagrange.OneBot Build

on:
push:
branches: ["main"]
paths:
- "**.cs"
branches: ["main"]
- "**.csproj"

pull_request:
branches: ["main"]
paths:
- "**.cs"
- "**.csproj"

workflow_dispatch:

Expand Down
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));
}
}
}
96 changes: 54 additions & 42 deletions Lagrange.OneBot/Core/Network/ReverseWSService.cs
Original file line number Diff line number Diff line change
@@ -1,88 +1,100 @@
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using Lagrange.OneBot.Core.Entity.Meta;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Websocket.Client;
using PHS.Networking.Enums;
using WebsocketsSimple.Client;
using WebsocketsSimple.Client.Events.Args;
using WebsocketsSimple.Client.Models;
using Timer = System.Threading.Timer;

namespace Lagrange.OneBot.Core.Network;

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

private readonly WebsocketClient _websocketClient;

private readonly IConfiguration _config;

private readonly ILogger _logger;

private readonly Timer _timer;


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

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, new(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(
host: ws["Host"],
port: ws.GetValue<int>("Port"),
path: ws["Suffix"],
isWebsocketSecured: false,
keepAliveIntervalSec: ws.GetValue<int>("ReconnectInterval") / 1000,
requestHeaders: headers
)
);
_websocketClient.MessageEvent += OnMessage;

_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);

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

SendJsonAsync(heartBeat);
}

private static void SetRequestHeader(ClientWebSocket webSocket, Dictionary<string, string> headers)
private void OnMessage(object sender, WSMessageClientEventArgs e)
{
foreach (var (key, value) in headers) webSocket.Options.SetRequestHeader(key, value);
if (e.MessageEventType == MessageEventType.Receive)
{
string text = _utf8.GetString(e.Bytes);
OnMessageReceived.Invoke(this, new(e.Message ?? ""));
}
}
}
}
19 changes: 12 additions & 7 deletions Lagrange.OneBot/Core/Operation/OperationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,34 @@ public OperationService(BotContext bot, ILagrangeWebService service)
_bot = bot;
_service = service;
_operations = new Dictionary<string, IOperation>();

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 (_, e) => await HandleOperation(e.Data);
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))
result.Identifier = e.Identifier;

await _service.SendJsonAsync(result);
}
else
{
throw new Exception("action deserialized failed");
}
}
}
}
3 changes: 2 additions & 1 deletion Lagrange.OneBot/Lagrange.OneBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<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="WebsocketsSimple.Client" Version="7.0.7" />
<PackageReference Include="WebsocketsSimple.Server" Version="7.0.7" />
<PackageReference Include="Net.Codecrete.QrCodeGenerator" Version="1.6.1" />
</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

0 comments on commit ed7e47a

Please sign in to comment.