何必为难自己
This commit is contained in:
parent
b75c91c354
commit
00982ca25d
|
@ -1,6 +0,0 @@
|
|||
namespace Lagrange.OneBot.Core.Entity;
|
||||
|
||||
public class MessageConverter
|
||||
{
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace Lagrange.OneBot.Core;
|
||||
|
||||
public class MessageConverter
|
||||
{
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -12,7 +12,8 @@ internal abstract class Program
|
|||
|
||||
var hostBuilder = new LagrangeAppBuilder(args)
|
||||
.ConfigureConfiguration("appsettings.json", false, true)
|
||||
.ConfigureBots();
|
||||
.ConfigureBots()
|
||||
.ConfigureOneBot();
|
||||
|
||||
hostBuilder.Build().Run();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue