submodule对gitea不管用,所以直接拉了一份拉格兰

This commit is contained in:
2025-02-04 16:29:43 +08:00
parent b0bfc803e3
commit d149a2ea0f
1023 changed files with 43308 additions and 18 deletions

View File

@@ -0,0 +1,168 @@
using System.Buffers.Binary;
namespace Lagrange.OneBot.Utility;
public static class AudioHelper
{
public enum AudioFormat
{
Unknown,
Wav,
Mp3,
SilkV3,
TenSilkV3,
Amr,
Ogg,
}
/// <summary>
/// Detect audio type
/// </summary>
/// <param name="data"></param>
/// <param name="type"></param>
/// <returns></returns>
[Obsolete("public static AudioFormat DetectAudio(byte[] data)")]
public static bool DetectAudio(byte[] data, out AudioFormat type)
{
return (type = DetectAudio(data)) != AudioFormat.Unknown;
}
// WAV
// R I F F W A V E f m t
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |52|49|46|46| | | | |57|41|56|45|66|6D|74|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// 0 4 8 12 15
private const ulong WAV_HEAD_LOWER = 0x5249464600000000UL;
private const ulong WAV_HEAD_UPPER = 0x57415645666D7400UL;
// TENSILKV3
// # ! S I L K
// +--+--+--+--+--+--+--+
// |02|23|21|53|49|4C|4B|
// +--+--+--+--+--+--+--+
// 0 4 7
private const ulong TENSILKV3_HEAD_LOWER = 0x02232153494C4B00UL;
// AMR
// # ! A M R
// +--+--+--+--+--+
// |23|21|41|4D|52|
// +--+--+--+--+--+
// 0 4 5
private const ulong AMR_HEAD_LOWER = 0x2321414D52000000UL;
// SILKV3
// # ! S I L K
// +--+--+--+--+--+--+
// |23|21|53|49|4C|4B|
// +--+--+--+--+--+--+
// 0 4 6
private const ulong SILKV3_HEAD_LOWER = 0x232153494C4B0000UL;
// Ogg
// O g g S
// +--+--+--+--+
// |4F|67|67|53|
// +--+--+--+--+
// 0 4
private const ulong OGG_HEAD_LOWER = 0x4F67675300000000UL;
// MP3 ID3
// I D 3
// +--+--+--+
// |49|44|33|
// +--+--+--+
// 0 3
private const ulong MP3ID3_HEAD_LOWER = 0x4944330000000000UL;
// MP3 no ID3
// ÿ û
// +--+--+
// |FF|F2|
// +--+--+
// 0 2
private const ulong MP3FFF2_HEAD_LOWER = 0xFFF2000000000000UL;
// MP3 no ID3
// ÿ û
// +--+--+
// |FF|F3|
// +--+--+
// 0 2
private const ulong MP3FFF3_HEAD_LOWER = 0xFFF3000000000000UL;
// MP3 no ID3
// ÿ û
// +--+--+
// |FF|FB|
// +--+--+
// 0 2
private const ulong MP3FFFB_HEAD_LOWER = 0xFFFB000000000000UL;
/// <summary>
/// Detect audio type
/// </summary>
/// <param name="data"></param>
/// <param name="type"></param>
/// <returns></returns>
public static AudioFormat DetectAudio(byte[] data)
{
ulong lower = BinaryPrimitives.ReadUInt64BigEndian(data.AsSpan(0, sizeof(ulong)));
ulong upper = BinaryPrimitives.ReadUInt64BigEndian(data.AsSpan(8, sizeof(ulong)));
if ((lower & WAV_HEAD_LOWER) == WAV_HEAD_LOWER &&
(upper & WAV_HEAD_UPPER) == WAV_HEAD_UPPER
) return AudioFormat.Wav;
if ((lower & TENSILKV3_HEAD_LOWER) == TENSILKV3_HEAD_LOWER) return AudioFormat.TenSilkV3;
if ((lower & AMR_HEAD_LOWER) == AMR_HEAD_LOWER) return AudioFormat.Amr;
if ((lower & SILKV3_HEAD_LOWER) == SILKV3_HEAD_LOWER) return AudioFormat.SilkV3;
if ((lower & OGG_HEAD_LOWER) == OGG_HEAD_LOWER) return AudioFormat.Ogg;
if ((lower & MP3ID3_HEAD_LOWER) == MP3ID3_HEAD_LOWER) return AudioFormat.Mp3;
if ((lower & MP3FFF2_HEAD_LOWER) == MP3FFF2_HEAD_LOWER) return AudioFormat.Mp3;
if ((lower & MP3FFF3_HEAD_LOWER) == MP3FFF3_HEAD_LOWER) return AudioFormat.Mp3;
if ((lower & MP3FFFB_HEAD_LOWER) == MP3FFFB_HEAD_LOWER) return AudioFormat.Mp3;
return AudioFormat.Unknown;
}
private static readonly byte[] _silk_end = [0xFF, 0xFF];
public static double GetSilkTime(byte[] data, int offset = 0)
{
int count = 0;
for (int i = 9 + offset; i < data.Length && !data.AsSpan(i, 2).SequenceEqual(_silk_end); i += 2 + BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(i, 2)))
{
count++;
}
// Because the silk encoder encodes each 20ms sample as a block,
// So that we can calculate the total time easily.
return count * 0.02;
}
/// <summary>
/// Get silk total time
/// </summary>
/// <param name="data"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static double GetTenSilkTime(byte[] data)
{
int count = 0;
for (int i = 10; i < data.Length; i += 2 + BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(i, 2)))
{
count++;
}
// Because the silk encoder encodes each 20ms sample as a block,
// So that we can calculate the total time easily.
return count * 0.02;
}
}

View File

@@ -0,0 +1,10 @@
using System.Runtime.CompilerServices;
namespace Lagrange.OneBot.Utility;
public static class DateTimeExt
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ToTimestamp(this DateTime time) =>
(uint)(time - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
}

View File

@@ -0,0 +1,29 @@
using System.Diagnostics.CodeAnalysis;
namespace Lagrange.OneBot.Utility.Fallbacks;
public class FallbackAsync
{
private readonly List<Func<CancellationToken, Task<bool>>> _executors = [];
internal static FallbackAsync Create()
{
return new();
}
public FallbackAsync Add(Func<CancellationToken, Task<bool>> executor)
{
_executors.Add(executor);
return this;
}
public async Task<bool> ExecuteAsync(CancellationToken token = default)
{
foreach (var executor in _executors)
{
if (await executor(token)) return true;
}
return false;
}
}

View File

@@ -0,0 +1,27 @@
namespace Lagrange.OneBot.Utility.Fallbacks;
public class FallbackAsync<T>
{
private readonly List<Func<CancellationToken, Task<T?>>> _executors = [];
internal static FallbackAsync<T> Create()
{
return new();
}
public FallbackAsync<T> Add(Func<CancellationToken, Task<T?>> executor)
{
_executors.Add(executor);
return this;
}
public async Task<T> ExecuteAsync(Func<CancellationToken, Task<T>> @default, CancellationToken token = default)
{
foreach (var executor in _executors)
{
var result = await executor(token);
if (result != null) return result;
}
return await @default(token);
}
}

View File

@@ -0,0 +1,49 @@
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
using LiteDB;
namespace Lagrange.OneBot.Utility;
public static class LiteDbUtility
{
public static BsonValue IMessageEntitySerialize(IMessageEntity entity)
{
var type = entity.GetType();
var result = BsonMapper.Global.Serialize(type, entity);
result["_type"] = new BsonValue(DefaultTypeNameBinder.Instance.GetName(type));
return result;
}
public static IMessageEntity IMessageEntityDeserialize(BsonValue bson)
{
if (!bson.IsDocument) throw new Exception("bson not BsonDocument");
var doc = bson.AsDocument;
if (!doc.TryGetValue("_type", out var typeBson) || !typeBson.IsString)
{
throw new Exception("no `_type` or `_type` not string");
}
var type = DefaultTypeNameBinder.Instance.GetType(typeBson.AsString);
if (type == typeof(MarkdownEntity)) return MarkdownEntityDeserialize(doc);
return (IMessageEntity)BsonMapper.Global.Deserialize(type, bson);
}
private static MarkdownEntity MarkdownEntityDeserialize(BsonDocument doc)
{
if (!doc.TryGetValue("Data", out var dataBson) || !dataBson.IsDocument)
{
throw new Exception("no `Data` or `Data` not document");
}
var dataDocument = dataBson.AsDocument;
if (!dataDocument.TryGetValue("Content", out var contentBson) || !contentBson.IsString)
{
throw new InvalidCastException("no `Data.Content` or `Data.Content` not string");
}
return new(new MarkdownData() { Content = contentBson.AsString });
}
}

View File

@@ -0,0 +1,77 @@
using Lagrange.OneBot.Message.Entity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Net.Http.Json;
using System.Text.Json.Nodes;
namespace Lagrange.OneBot.Utility;
public class MusicSigner
{
private static string? _signServer;
private readonly static HttpClient _client = new();
public MusicSigner(IConfiguration config, ILogger<MusicSigner> logger)
{
_signServer = config["MusicSignServerUrl"] ?? "";
if (string.IsNullOrEmpty(_signServer))
{
logger.LogWarning("MusicSignServer is not available, sign may be failed");
}
else
{
logger.LogInformation("MusicSignServer Service is successfully established");
}
}
public static string? Sign(MusicSegment musicSegment)
{
if (string.IsNullOrEmpty(_signServer)) return null;
JsonObject payload;
if (musicSegment.Type != null)
{
payload = new JsonObject()
{
{ "type" , musicSegment.Type },
{ "url" , musicSegment.Url },
{ "audio" , musicSegment.Audio },
{ "title" , musicSegment.Title },
{ "image" , musicSegment.Image },
{ "singer" , musicSegment.Content },
};
}
else
{
payload = new JsonObject()
{
{ "url" , musicSegment.Url },
{ "audio" , musicSegment.Audio },
{ "title" , musicSegment.Title },
{ "image" , musicSegment.Image },
{ "singer" , musicSegment.Content },
{ "appid", ulong.Parse(musicSegment.Appid) },
{ "sign", musicSegment.Sign },
{ "package_name", musicSegment.PackageName },
};
}
if (musicSegment.Id.Length > 0)
{
payload.Add("id", musicSegment.Id);
}
try
{
var message = _client.PostAsJsonAsync(_signServer, payload).Result;
return message.Content.ReadAsStringAsync().Result;
}
catch
{
return null;
}
}
}

View File

@@ -0,0 +1,16 @@
using static Lagrange.Core.Common.Entity.BotUserInfo;
namespace Lagrange.OneBot.Utility;
public static class GenderExt
{
public static string ToOneBotString(this GenderInfo info)
{
return info switch
{
GenderInfo.Male => "male",
GenderInfo.Female => "female",
GenderInfo.Unset or _ => "unknown"
};
}
}

View File

@@ -0,0 +1,148 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Lagrange.Core.Common;
using Lagrange.Core.Utility.Sign;
using Lagrange.OneBot.Utility.Fallbacks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Lagrange.OneBot.Utility;
public class OneBotSigner : SignProvider
{
private readonly IConfiguration _configuration;
private readonly ILogger<OneBotSigner> _logger;
private const string Url = "https://sign.lagrangecore.org/api/sign/30366";
private readonly string? _signServer;
private readonly HttpClient _client;
private readonly BotAppInfo? _info;
private readonly string platform;
private readonly string version;
public OneBotSigner(IConfiguration config, ILogger<OneBotSigner> logger)
{
_configuration = config;
_logger = logger;
_signServer = string.IsNullOrEmpty(config["SignServerUrl"]) ? Url : config["SignServerUrl"];
string? signProxyUrl = config["SignProxyUrl"]; // Only support HTTP proxy
_client = new HttpClient(handler: new HttpClientHandler
{
Proxy = string.IsNullOrEmpty(signProxyUrl) ? null : new WebProxy()
{
Address = new Uri(signProxyUrl),
BypassProxyOnLocal = false,
UseDefaultCredentials = false,
},
}, disposeHandler: true);
if (string.IsNullOrEmpty(_signServer)) logger.LogWarning("Signature Service is not available, login may be failed");
_info ??= GetAppInfo();
platform = _info.Os switch
{
"Windows" => "Windows",
"Mac" => "MacOs",
"Linux" => "Linux",
_ => "Unknown"
};
version = _info.CurrentVersion;
}
public override byte[]? Sign(string cmd, uint seq, byte[] body, [UnscopedRef] out byte[]? e, [UnscopedRef] out string? t)
{
e = null;
t = null;
if (!WhiteListCommand.Contains(cmd)) return null;
if (_signServer == null) throw new Exception("Sign server is not configured");
using var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(_signServer),
Content = JsonContent.Create(new JsonObject
{
{ "cmd", cmd },
{ "seq", seq },
{ "src", Convert.ToHexString(body) }
})
};
using var message = _client.Send(request);
if (message.StatusCode != HttpStatusCode.OK) throw new Exception($"Signer server returned a {message.StatusCode}");
var json = JsonDocument.Parse(message.Content.ReadAsStream()).RootElement;
if (json.TryGetProperty("platform", out JsonElement platformJson))
{
if (platformJson.GetString() != platform) throw new Exception("Signer platform mismatch");
}
else
{
_logger.LogWarning("Signer platform miss");
}
if (json.TryGetProperty("version", out JsonElement versionJson))
{
if (versionJson.GetString() != version) throw new Exception("Signer version mismatch");
}
else
{
_logger.LogWarning("Signer version miss");
}
var valueJson = json.GetProperty("value");
var extraJson = valueJson.GetProperty("extra");
var tokenJson = valueJson.GetProperty("token");
var signJson = valueJson.GetProperty("sign");
string? token = tokenJson.GetString();
string? extra = extraJson.GetString();
e = extra != null ? Convert.FromHexString(extra) : [];
t = token != null ? Encoding.UTF8.GetString(Convert.FromHexString(token)) : "";
string sign = signJson.GetString() ?? throw new Exception("Signer server returned an empty sign");
return Convert.FromHexString(sign);
}
public BotAppInfo GetAppInfo()
{
if (_info != null) return _info;
return FallbackAsync<BotAppInfo>.Create()
.Add(async token =>
{
try { return await _client.GetFromJsonAsync<BotAppInfo>($"{_signServer}/appinfo", token); }
catch { return null; }
})
.Add(token =>
{
string path = _configuration["ConfigPath:AppInfo"] ?? "appinfo.json";
if (!File.Exists(path)) return Task.FromResult(null as BotAppInfo);
try { return Task.FromResult(JsonSerializer.Deserialize<BotAppInfo>(File.ReadAllText(path))); }
catch { return Task.FromResult(null as BotAppInfo); }
})
.ExecuteAsync(token => Task.FromResult(
BotAppInfo.ProtocolToAppInfo[_configuration["Account:Protocol"] switch
{
"Windows" => Protocols.Windows,
"MacOs" => Protocols.MacOs,
_ => Protocols.Linux,
}]
))
.Result;
}
}

View File

@@ -0,0 +1,49 @@
using Net.Codecrete.QrCodeGenerator;
namespace Lagrange.OneBot.Utility;
internal static class QrCodeHelper
{
// This part of the code is from "https://github.com/eric2788/Lagrange.Core/blob/fd20a5aec81cacd56d60f3130cf057461300fd3f/Lagrange.OneBot/Utility/QrCodeHelper.cs#L30C52-L30C52"
// Thanks to "https://github.com/eric2788"
internal static void Output(string text, bool compatibilityMode)
{
var segments = QrSegment.MakeSegments(text);
var qrCode = QrCode.EncodeSegments(segments, QrCode.Ecc.Low);
var (bottomHalfBlock, topHalfBlock, emptyBlock, fullBlock) = compatibilityMode ? (".", "^", " ", "@") : ("▄", "▀", " ", "█");
for (var y = 0; y < qrCode.Size + 2; y += 2)
{
for (var x = 0; x < qrCode.Size + 2; ++x)
{
var foregroundBlack = qrCode.GetModule(x - 1, y - 1);
var backgroundBlack = qrCode.GetModule(x - 1, y) || y > qrCode.Size;
if (foregroundBlack && !backgroundBlack)
{
Console.Write(bottomHalfBlock);
}
else if (!foregroundBlack && backgroundBlack)
{
Console.Write(topHalfBlock);
}
else if (foregroundBlack && backgroundBlack)
{
Console.Write(emptyBlock);
}
else if (!foregroundBlack && !backgroundBlack)
{
Console.Write(fullBlock);
}
}
Console.Write("\n");
}
if (compatibilityMode)
{
Console.WriteLine("Please scan this QR code from a distance with your smart phone.\nScanning may fail if you are too close.");
}
}
}

View File

@@ -0,0 +1,23 @@
using Lagrange.Core.Utility.Network;
namespace Lagrange.OneBot.Utility;
public class SongHelper
{
public async Task<dynamic> GetQMusicInfo(string id)
{
const string template = """https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={"comm":{"ct":24,"cv":0},"songinfo":{"method":"get_song_detail_yqq","param":{"song_type":0,"song_mid":"","song_id":*},"module":"music.pf_song_detail_svr"}}""";
string url = template.Replace("*", id);
string response = await Http.GetAsync(url);
return response;
}
public async Task<dynamic> GetNetEaseMusicInfo(string id)
{
string url = $"http://music.163.com/api/song/detail/?id={id}&ids=[{id}]";
string response = await Http.GetAsync(url);
return response;
}
}

View File

@@ -0,0 +1,22 @@
using System.Numerics;
using LiteDB;
namespace Lagrange.OneBot.Utility;
public static class Vector2Mapper
{
public static void RegisterType() => BsonMapper.Global.RegisterType(Serialize, Deserialize);
public static BsonValue Serialize(Vector2 parameter) => new BsonDocument(new Dictionary<string, BsonValue>
{
{ "X", parameter.X },
{ "Y", parameter.Y }
});
public static Vector2 Deserialize(BsonValue bsonValue)
{
float x = (float)bsonValue["X"].AsDouble;
float y = (float)bsonValue["Y"].AsDouble;
return new Vector2(x, y);
}
}