何必为难自己

This commit is contained in:
Linwenxuan05 2023-08-23 03:12:03 +08:00
parent b75c91c354
commit 00982ca25d
14 changed files with 263 additions and 72 deletions

View File

@ -1,6 +0,0 @@
namespace Lagrange.OneBot.Core.Entity;
public class MessageConverter
{
}

View File

@ -0,0 +1,17 @@
using System.Text.Json.Serialization;
namespace Lagrange.OneBot.Core.Entity.Meta;
[Serializable]
public class OneBotHeartBeat : OneBotMeta
{
[JsonPropertyName("interval")] public int Interval { get; set; }
[JsonPropertyName("status")] public object Status { get; set; }
public OneBotHeartBeat(uint selfId, int interval, object status) : base(selfId, "heartbeat")
{
Interval = interval;
Status = status;
}
}

View File

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace Lagrange.OneBot.Core.Entity.Meta;
[Serializable]
public class OneBotLifecycle : OneBotMeta
{
[JsonPropertyName("sub_type")] public string SubType { get; set; }
public OneBotLifecycle(uint selfId, string subType) : base(selfId, "lifecycle")
{
SubType = subType;
}
}

View File

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace Lagrange.OneBot.Core.Entity.Meta;
[Serializable]
public abstract class OneBotMeta : OneBotEntityBase
{
[JsonPropertyName("meta_event_type")] public string MetaEventType { get; set; }
protected OneBotMeta(uint selfId, string metaEventType) : base(selfId, "meta_event")
{
MetaEventType = metaEventType;
}
}

View File

@ -0,0 +1,26 @@
using System.Text.Json.Serialization;
namespace Lagrange.OneBot.Core.Entity.Meta;
[Serializable]
public class OneBotStatus
{
[JsonPropertyName("app_initialized")] public bool AppInitialized { get; set; }
[JsonPropertyName("app_enabled")] public bool AppEnabled { get; set; }
[JsonPropertyName("app_good")] public bool AppGood { get; set; }
[JsonPropertyName("online")] public bool Online { get; set; }
[JsonPropertyName("good")] public bool Good { get; set; }
public OneBotStatus(bool online, bool good)
{
AppInitialized = true;
AppEnabled = true;
AppGood = true;
Online = online;
Good = good;
}
}

View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
namespace Lagrange.OneBot.Core.Entity;
[Serializable]
public abstract class OneBotEntityBase
{
[JsonPropertyName("time")] public long Time { get; set; }
[JsonPropertyName("self_id")] public uint SelfId { get; set; }
[JsonPropertyName("post_type")] public string PostType { get; set; }
protected OneBotEntityBase(uint selfId, string postType)
{
Time = DateTimeOffset.Now.ToUnixTimeMilliseconds();
SelfId = selfId;
PostType = postType;
}
}

View File

@ -0,0 +1,8 @@
using Microsoft.Extensions.Hosting;
namespace Lagrange.OneBot.Core;
public interface ILagrangeWebService : IHostedService
{
public ValueTask SendJsonAsync<T>(T json, CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,6 @@
namespace Lagrange.OneBot.Core;
public class MessageConverter
{
}

View File

@ -1,56 +0,0 @@
using System.Net.Sockets;
namespace Lagrange.OneBot.Core.Network;
/// <summary>
/// Provide a simple WebSocket client and server
/// </summary>
internal class WebSocket : IDisposable
{
private readonly Socket _socket;
private readonly Thread _receiveLoop;
public bool IsConnected => _socket.Connected;
public event MessageReceivedEventHandler? OnMessageReceived;
public delegate void MessageReceivedEventHandler(WebSocket sender, byte[] data);
public WebSocket()
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_receiveLoop = new Thread(() =>
{
while (_socket.Connected)
{
var buffer = new byte[1024 * 1024 * 64]; // max 64MB
int length = _socket.Receive(buffer);
if (length > 0)
{
var data = buffer[..length];
OnMessageReceived?.Invoke(this, data);
}
}
});
}
public async Task<bool> ConnectAsync(string host, int port)
{
await _socket.ConnectAsync(host, port);
return true;
}
public async Task<bool> SendAsync(byte[] data)
{
await _socket.SendAsync(data, SocketFlags.None);
return true;
}
public void Dispose()
{
_socket.Disconnect(false); // The thread will exit automatically
_socket.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@ -0,0 +1,101 @@
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using Lagrange.Core.Utility.Extension;
using Lagrange.OneBot.Core.Entity.Meta;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Timer = System.Threading.Timer;
namespace Lagrange.OneBot.Core.Service;
public class ReverseWSService : ILagrangeWebService
{
private readonly ClientWebSocket _socket;
private readonly IConfiguration _config;
private readonly ILogger _logger;
private readonly Timer _timer;
public ReverseWSService(IConfiguration config, ILogger<LagrangeApp> logger)
{
_config = config;
_logger = logger;
_socket = new ClientWebSocket();
_timer = new Timer(OnHeartbeat, null, int.MaxValue, config.GetValue<int>("Implementation:ReverseWebSocket:HeartBeatInterval"));
_ = ReceiveLoop();
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await ConnectAsync(_config.GetValue<uint>("Account:Uin"), cancellationToken);
var lifecycle = new OneBotLifecycle(_config.GetValue<uint>("Account:Uin"), "connect");
await SendJsonAsync(lifecycle, cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer.Dispose();
_socket.Dispose();
return Task.CompletedTask;
}
public ValueTask SendJsonAsync<T>(T json, CancellationToken cancellationToken = default)
{
var payload = JsonSerializer.SerializeToUtf8Bytes(json);
return _socket.SendAsync(payload.AsMemory(), WebSocketMessageType.Text, true, cancellationToken);
}
private async Task<bool> ConnectAsync(uint botUin, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Connecting to reverse websocket...");
var config = _config.GetSection("Implementation").GetSection("ReverseWebSocket");
string url = $"ws://{config["Host"]}:{config["Port"]}{config["Suffix"]}";
_socket.Options.SetRequestHeader("X-Client-Role", "Universal");
_socket.Options.SetRequestHeader("X-Self-ID", botUin.ToString());
_socket.Options.SetRequestHeader("User-Agent", Constant.OneBotImpl);
if (_config["AccessToken"] != null) _socket.Options.SetRequestHeader("Authorization", $"Bearer {_config["AccessToken"]}");
await _socket.ConnectAsync(new Uri(url), cancellationToken);
_timer.Change(0, _config.GetValue<int>("Implementation:ReverseWebSocket:HeartBeatInterval"));
return true;
}
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);
}
private async Task ReceiveLoop()
{
while (true)
{
await Task.CompletedTask.ForceAsync();
if (_socket.State == WebSocketState.Open)
{
var buffer = new byte[64 * 1024 * 1024]; // 64MB
var result = await _socket.ReceiveAsync(buffer.AsMemory(), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
string payload = Encoding.UTF8.GetString(buffer[..result.Count]);
_logger.LogInformation(payload);
}
}
}
}

View File

@ -1,6 +1,8 @@
using System.Text.Json;
using Lagrange.Core;
using Lagrange.Core.Common.Interface.Api;
using Lagrange.OneBot.Core;
using Lagrange.OneBot.Core.Entity.Meta;
using Lagrange.OneBot.Utility;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -22,6 +24,8 @@ public class LagrangeApp : IHost
public BotContext Instance => Services.GetRequiredService<BotContext>();
public ILagrangeWebService WebService => Services.GetRequiredService<ILagrangeWebService>();
internal LagrangeApp(IHost host)
{
_hostApp = host;
@ -30,6 +34,7 @@ public class LagrangeApp : IHost
public async Task StartAsync(CancellationToken cancellationToken = new())
{
await _hostApp.StartAsync(cancellationToken);
Logger.LogInformation("Lagrange.OneBot Implementation has started");
Logger.LogInformation($"Protocol: {Configuration.GetValue<string>("Protocol")} | {Instance.ContextCollection.AppInfo.CurrentVersion}");
@ -43,15 +48,20 @@ public class LagrangeApp : IHost
_ => Microsoft.Extensions.Logging.LogLevel.Error
}, args.ToString());
Instance.Invoker.OnBotOnlineEvent += (_, args) =>
Instance.Invoker.OnBotOnlineEvent += async (_, args) =>
{
var keystore = Instance.UpdateKeystore();
Logger.LogInformation($"Bot Online: {keystore.Uin}");
string json = JsonSerializer.Serialize(keystore, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(Configuration["ConfigPath:Keystore"] ?? "keystore.json", json);
await File.WriteAllTextAsync(Configuration["ConfigPath:Keystore"] ?? "keystore.json", json, cancellationToken);
var lifecycle = new OneBotLifecycle(keystore.Uin, "enable");
await WebService.SendJsonAsync(lifecycle, cancellationToken);
};
await WebService.StartAsync(cancellationToken);
if (Configuration.GetValue<uint>("Account:Uin") == 0)
{
var qrCode = await Instance.FetchQrCode();
@ -76,8 +86,6 @@ public class LagrangeApp : IHost
await Instance.LoginByPassword();
}
await _hostApp.StartAsync(cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken = new())

View File

@ -1,6 +1,8 @@
using System.Text.Json;
using Lagrange.Core.Common;
using Lagrange.Core.Common.Interface;
using Lagrange.OneBot.Core;
using Lagrange.OneBot.Core.Service;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -60,10 +62,12 @@ public sealed class LagrangeAppBuilder
return this;
}
public LagrangeApp Build()
public LagrangeAppBuilder ConfigureOneBot()
{
var app = new LagrangeApp(_hostAppBuilder.Build());
return app;
Services.AddSingleton<ILagrangeWebService, ReverseWSService>();
return this;
}
public LagrangeApp Build() => new(_hostAppBuilder.Build());
}

View File

@ -12,7 +12,8 @@ internal abstract class Program
var hostBuilder = new LagrangeAppBuilder(args)
.ConfigureConfiguration("appsettings.json", false, true)
.ConfigureBots();
.ConfigureBots()
.ConfigureOneBot();
hostBuilder.Build().Run();
}

View File

@ -0,0 +1,34 @@
using System.Runtime.CompilerServices;
namespace Lagrange.OneBot.Utility;
internal static class TaskAwaiters
{
/// <summary>
/// Returns an awaitable/awaiter that will ensure the continuation is executed
/// asynchronously on the thread pool, even if the task is already completed
/// by the time the await occurs. Effectively, it is equivalent to awaiting
/// with ConfigureAwait(false) and then queuing the continuation with Task.Run,
/// but it avoids the extra hop if the continuation already executed asynchronously.
/// </summary>
public static ForceAsyncAwaiter ForceAsync(this Task task) => new(task);
}
internal readonly struct ForceAsyncAwaiter : ICriticalNotifyCompletion
{
private readonly Task _task;
internal ForceAsyncAwaiter(Task task) => _task = task;
public ForceAsyncAwaiter GetAwaiter() => this;
public bool IsCompleted => false; // the purpose of this type is to always force a continuation
public void GetResult() => _task.GetAwaiter().GetResult();
public void OnCompleted(Action action) =>
_task.ConfigureAwait(false).GetAwaiter().OnCompleted(action);
public void UnsafeOnCompleted(Action action) =>
_task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(action);
}