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,4 @@
namespace Lagrange.OneBot.Message;
[AttributeUsage(AttributeTargets.Property)]
public class CQPropertyAttribute : Attribute;

View File

@@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class AtSegment(uint at, string? name)
{
public AtSegment() : this(0, null) { }
public AtSegment(uint at) : this(at, null) { }
[JsonPropertyName("qq")] [CQProperty] public string At { get; set; } = at.ToString();
[JsonPropertyName("name")] [CQProperty] public string? Name { get; set; } = name;
}
[SegmentSubscriber(typeof(MentionEntity), "at")]
public partial class AtSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is AtSegment atSegment) builder.Mention(atSegment.At == "all" ? 0 : uint.Parse(atSegment.At), atSegment.At == "all" ? "@\u5168\u4f53\u6210\u5458" : null);
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not MentionEntity mentionEntity) throw new ArgumentException("Invalid entity type.");
return new AtSegment(mentionEntity.Uin, mentionEntity.Name);
}
}

View File

@@ -0,0 +1,34 @@
namespace Lagrange.OneBot.Message.Entity;
public static class CommonResolver
{
private static readonly HttpClient Client = new();
public static byte[]? Resolve(string url)
{
if (url.StartsWith("base64://")) return Convert.FromBase64String(url.Replace("base64://", ""));
Uri uri = new(url);
return uri.Scheme switch
{
"http" or "https" => Client.GetAsync(uri).Result.Content.ReadAsByteArrayAsync().Result,
"file" => File.ReadAllBytes(Path.GetFullPath(uri.LocalPath)),
_ => null,
};
}
public static Stream? ResolveStream(string url)
{
if (url.StartsWith("base64://")) return new MemoryStream(Convert.FromBase64String(url.Replace("base64://", "")));
Uri uri = new(url);
return uri.Scheme switch
{
"http" or "https" => Client.GetAsync(uri).Result.Content.ReadAsStreamAsync().Result,
"file" => new FileStream(Path.GetFullPath(uri.LocalPath), FileMode.Open, FileAccess.Read, FileShare.Read),
_ => null,
};
}
}

View File

@@ -0,0 +1,26 @@
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class DiceSegment
{
}
[SegmentSubscriber(typeof(FaceEntity), "dice")]
public partial class DiceSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is DiceSegment) builder.Face(358, true);
}
public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is FaceEntity { IsLargeFace: true, FaceId: 358 }) return new DiceSegment();
return null;
}
}

View File

@@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class FaceSegment(int id)
{
public FaceSegment() : this(0) { }
[JsonPropertyName("id")] [CQProperty] public string Id { get; set; } = id.ToString();
[JsonPropertyName("large")] [CQProperty] public bool? IsLarge { get; set; }
}
[SegmentSubscriber(typeof(FaceEntity), "face")]
public partial class FaceSegment : SegmentBase
{
private static readonly ushort[] Excluded = [358, 359];
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is FaceSegment faceSegment) builder.Face(ushort.Parse(faceSegment.Id), faceSegment.IsLarge ?? false);
}
public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not FaceEntity faceEntity) throw new ArgumentException("Invalid entity type.");
return Excluded.Contains(faceEntity.FaceId) ? null : new FaceSegment(faceEntity.FaceId);
}
}

View File

@@ -0,0 +1,45 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class FileSegment(string fileName, string fileHash, string fileId, string url)
{
public FileSegment() : this("", "", "", "") { }
[JsonPropertyName("file_name")] public string Filename { get; set; } = fileName;
[JsonPropertyName("file_hash")] public string Filehash { get; set; } = fileHash;
[JsonPropertyName("file_id")] public string Fileid { get; set; } = fileId;
[JsonPropertyName("url")] public string Url { get; set; } = url;
}
[SegmentSubscriber(typeof(FileEntity), "file")]
public partial class FileSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is FileSegment fileSegment and not { Fileid: "" })
{
builder.Add(new FileEntity
{
FileName = fileSegment.Filename,
FileUrl = fileSegment.Url,
FileId = fileSegment.Fileid,
FileHash = fileSegment.Filehash
});
}
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not FileEntity fileEntity) throw new ArgumentException("Invalid entity type.");
return new FileSegment(fileEntity.FileName, fileEntity.FileHash ?? "", fileEntity.FileId ?? fileEntity.FileUuid ?? "", fileEntity.FileUrl ?? "");
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class ForwardSegment(string name)
{
public ForwardSegment() : this("") { }
[JsonPropertyName("id")] [CQProperty] public string Name { get; set; } = name;
}
[SegmentSubscriber(typeof(MultiMsgEntity), "forward")]
public partial class ForwardSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is ForwardSegment forward) builder.Add(new MultiMsgEntity(forward.Name));
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not MultiMsgEntity multiMsg) throw new ArgumentException("Invalid entity type.");
return new ForwardSegment(multiMsg.ResId ?? "");
}
}

View File

@@ -0,0 +1,46 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class ImageSegment(string url, string filename, string summary = "[图片]", int subType = 0)
{
public ImageSegment() : this("", "") { }
[JsonPropertyName("file")] [CQProperty] public string File { get; set; } = url;
[JsonPropertyName("filename")] public string Filename { get; set; } = filename;
[JsonPropertyName("url")] public string Url { get; set; } = url;
[JsonPropertyName("summary")] public string Summary { get; set; } = summary;
[JsonPropertyName("subType")] public int SubType { get; set; } = subType;
}
[SegmentSubscriber(typeof(ImageEntity), "image")]
public partial class ImageSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is ImageSegment imageSegment and not { File: "" } && CommonResolver.ResolveStream(imageSegment.File) is { } stream)
{
builder.Add(new ImageEntity
{
FilePath = imageSegment.Filename,
ImageStream = new Lazy<Stream>(stream),
Summary = imageSegment.Summary,
SubType = imageSegment.SubType
});
}
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not ImageEntity imageEntity) throw new ArgumentException("Invalid entity type.");
return new ImageSegment(imageEntity.ImageUrl, imageEntity.FilePath, imageEntity.ToPreviewText(), imageEntity.SubType);
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class JsonSegment(string data)
{
public JsonSegment(): this("") { }
[JsonPropertyName("data")] [CQProperty] public string Data { get; set; } = data;
}
[SegmentSubscriber(typeof(LightAppEntity), "json")]
public partial class JsonSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is JsonSegment json) builder.LightApp(json.Data);
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not LightAppEntity lightAppEntity) throw new ArgumentException("Invalid entity type.");
return new JsonSegment(lightAppEntity.Payload);
}
}

View File

@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class KeyboardSegment(KeyboardData content)
{
public KeyboardSegment(): this(new KeyboardData()) { }
[JsonPropertyName("content")] public KeyboardData Content { get; set; } = content;
}
[SegmentSubscriber(typeof(KeyboardEntity), "keyboard")]
public partial class KeyboardSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is KeyboardSegment keyboard) builder.Keyboard(keyboard.Content);
}
public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)
{
return null;
}
}

View File

@@ -0,0 +1,83 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
using Lagrange.OneBot.Core.Entity.Common;
using Lagrange.OneBot.Core.Operation.Converters;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class LocationSegment(float latitude, float longitude)
{
public LocationSegment() : this(0f, 0f) { }
[JsonPropertyName("lat")][CQProperty] public string Latitude { get; set; } = latitude.ToString("F5");
[JsonPropertyName("lon")][CQProperty] public string Longitude { get; set; } = longitude.ToString("F5");
[JsonPropertyName("title")] public string Title { get; set; } = string.Empty;
[JsonPropertyName("content")] public string Content { get; set; } = string.Empty;
}
[SegmentSubscriber(typeof(LightAppEntity), "location")]
public partial class LocationSegment : SegmentBase
{
private static readonly JsonSerializerOptions Options = new() { Converters = { new AutosizeConverter(), new ForwardConverter() }, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is not LocationSegment location) return;
var json = new LightApp
{
App = "com.tencent.map",
// Config = new Config
// {
// Autosize = false,
// Ctime = DateTimeOffset.UtcNow.Second,
// Token = "626399d3453d0693fe19e12cd3747c56",
// Type = "normal",
// },
Desc = "",
From = 1,
Meta = new Meta
{
LocationSearch = new LocationSearch
{
EnumRelationType = 1,
From = "plusPanel",
Id = "",
Lat = location.Latitude,
Lng = location.Longitude,
Name = location.Title,
Address = location.Content
}
},
Prompt = $"[Location]{location.Content}",
Ver = "1.1.2.21",
View = "LocationShare"
};
builder.LightApp(JsonSerializer.Serialize(json, Options));
}
public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not LightAppEntity lightApp) throw new ArgumentException("Invalid entity type.");
if (JsonSerializer.Deserialize<LightApp>(lightApp.Payload, Options) is { App: "com.tencent.map" } app)
{
return new LocationSegment
{
Latitude = app.Meta.LocationSearch.Lat,
Longitude = app.Meta.LocationSearch.Lng,
Content = app.Meta.LocationSearch.Address,
Title = app.Meta.LocationSearch.Name,
};
}
return null;
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class LongMsgSegment(string name)
{
public LongMsgSegment() : this("") { }
[JsonPropertyName("id")] [CQProperty] public string Name { get; set; } = name;
}
[SegmentSubscriber(typeof(LongMsgSegment), "longmsg")]
public partial class LongMsgSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is LongMsgSegment longMsg) builder.Add(new LongMsgEntity(longMsg.Name));
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not LongMsgEntity longMsg) throw new ArgumentException("Invalid entity type.");
return new ForwardSegment(longMsg.ResId ?? "");
}
}

View File

@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class MarkdownSegment(string content)
{
public MarkdownSegment(): this("") { }
[JsonPropertyName("content")] [CQProperty] public string Content { get; set; } = content;
}
[SegmentSubscriber(typeof(MarkdownEntity), "markdown")]
public partial class MarkdownSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is MarkdownSegment markdown) builder.Markdown(markdown.Content);
}
public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)
{
return null;
}
}

View File

@@ -0,0 +1,61 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
using Lagrange.Core.Utility.Network;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class MfaceSegment(string? url, string emojiId, int emojiPackageId, string key, string? summary)
{
[JsonPropertyName("url")] public string? Url { get; set; } = url;
[JsonPropertyName("emoji_package_id")] public int EmojiPackageId { get; set; } = emojiPackageId;
[JsonPropertyName("emoji_id")] public string EmojiId { get; set; } = emojiId;
[JsonPropertyName("key")] public string Key { get; set; } = key;
[JsonPropertyName("summary")] public string? Summary { get; set; } = summary;
public MfaceSegment() : this(null, string.Empty, default, string.Empty, null) { }
}
[SegmentSubscriber(typeof(MarketfaceEntity), "mface")]
public partial class MfaceSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is not MfaceSegment mfs) return;
if (mfs.Summary == null)
{
JsonElement tabJson = JsonDocument.Parse(
Http.GetAsync(
$"https://i.gtimg.cn/club/item/parcel/{mfs.EmojiPackageId % 10}/{mfs.EmojiPackageId}.json"
).Result
).RootElement;
foreach (JsonElement imgJson in tabJson.GetProperty("imgs").EnumerateArray())
{
if (imgJson.GetProperty("id").GetString() == mfs.EmojiId)
{
mfs.Summary = $"[{imgJson.GetProperty("name").GetString()}]" ?? "[\u5546\u57ce\u8868\u60c5]";
break;
}
}
mfs.Summary ??= "[\u5546\u57ce\u8868\u60c5]";
}
builder.Add(new MarketfaceEntity(mfs.EmojiId, mfs.EmojiPackageId, mfs.Key, mfs.Summary));
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not MarketfaceEntity mfe) throw new ArgumentException("Invalid entity type.");
return new MfaceSegment($"https://gxh.vip.qq.com/club/item/parcel/item/{mfe.EmojiId[..2]}/{mfe.EmojiId}/raw300.gif", mfe.EmojiId, mfe.EmojiPackageId, mfe.Key, mfe.Summary);
}
}

View File

@@ -0,0 +1,52 @@
using Lagrange.Core.Message.Entity;
using Lagrange.Core.Message;
using System.Text.Json.Serialization;
using Lagrange.OneBot.Utility;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class MusicSegment(string? type, string url, string audio, string title, string image, string content, string appid, string sign, string packageName)
{
public MusicSegment() : this(null, "", "", "", "", "", "", "", "") { }
[JsonPropertyName("type")][CQProperty] public string? Type { get; set; } = type;
[JsonPropertyName("id")][CQProperty] public string Id { get; set; } = String.Empty;
[JsonPropertyName("url")][CQProperty] public string Url { get; set; } = url;
[JsonPropertyName("audio")][CQProperty] public string Audio { get; set; } = audio;
[JsonPropertyName("title")][CQProperty] public string Title { get; set; } = title;
[JsonPropertyName("content")][CQProperty] public string Content { get; set; } = content;
[JsonPropertyName("image")][CQProperty] public string Image { get; set; } = image;
[JsonPropertyName("appid")] public string Appid { get; set; } = appid;
[JsonPropertyName("sign")] public string Sign { get; set; } = sign;
[JsonPropertyName("package_name")] public string PackageName { get; set; } = packageName;
}
[SegmentSubscriber(typeof(ImageEntity), "music")]
public partial class MusicSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is MusicSegment musicSegment)
{
var content = MusicSigner.Sign(musicSegment);
if (string.IsNullOrEmpty(content))
throw new ArgumentNullException("music SignerServer response Errors!");
builder.LightApp(content);
}
}
public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)
{
return null;
}
}

View File

@@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class PokeSegment(uint type, uint strength)
{
public PokeSegment() : this(0, 0) { }
[JsonPropertyName("type")] [CQProperty] public string Type { get; set; } = type.ToString();
[JsonPropertyName("strength")] public string? Strength { get; set; } = strength.ToString();
[JsonPropertyName("id")] [CQProperty] public string Id { get; set; } = "-1"; // can not get id
}
[SegmentSubscriber(typeof(PokeEntity), "poke")]
public partial class PokeSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is PokeSegment pokeSegment) builder.Poke(uint.Parse(pokeSegment.Type), pokeSegment.Strength == null ? 0 : uint.Parse(pokeSegment.Strength));
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not PokeEntity pokeEntity) throw new ArgumentException("Invalid entity type.");
return new PokeSegment(pokeEntity.Type, pokeEntity.Strength);
}
}

View File

@@ -0,0 +1,98 @@
using System.Text.Json.Serialization;
using Konata.Codec.Audio;
using Konata.Codec.Audio.Codecs;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
using Lagrange.OneBot.Utility;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class RecordSegment(string url)
{
public RecordSegment() : this("") { }
[JsonPropertyName("file")][CQProperty] public string File { get; set; } = url;
[JsonPropertyName("url")] public string Url { get; set; } = url;
}
[SegmentSubscriber(typeof(RecordEntity), "record")]
public partial class RecordSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is RecordSegment recordSegment and not { File: "" } && CommonResolver.Resolve(recordSegment.File) is { } record)
{
byte[] silk = ConvertFormat(record, out double time);
builder.Record(silk, (int)Math.Ceiling(time));
}
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not RecordEntity recordEntity) throw new ArgumentException("Invalid entity type.");
return new RecordSegment(recordEntity.AudioUrl);
}
private static byte[] ConvertFormat(byte[] audio, out double audioTime)
{
var format = AudioHelper.DetectAudio(audio);
switch (format) // Process
{
// Silk v3 for tx use
case AudioHelper.AudioFormat.TenSilkV3:
{
audioTime = AudioHelper.GetTenSilkTime(audio);
return audio;
}
// Amr format
// We no need to convert it
case AudioHelper.AudioFormat.Amr:
{
audioTime = audio.Length / 1607.0;
return audio;
}
// Normal silk v3
// We need to append a header 0x02
// and remove 0xFFFF end for it
case AudioHelper.AudioFormat.SilkV3:
{
audio = [0x02, .. audio.AsSpan(0, audio.Length - 2)];
audioTime = AudioHelper.GetTenSilkTime(audio);
return audio;
}
// Need to convert
case AudioHelper.AudioFormat.Wav:
case AudioHelper.AudioFormat.Ogg:
case AudioHelper.AudioFormat.Mp3:
{
var input = new MemoryStream(audio);
var output = new MemoryStream();
var pipeline = new AudioPipeline() {
format switch {
AudioHelper.AudioFormat.Wav => new WavCodec.Decoder(input),
AudioHelper.AudioFormat.Ogg => new VorbisCodec.Decoder(input),
AudioHelper.AudioFormat.Mp3 => new Mp3Codec.Decoder(input),
_ => throw new Exception("Unknown Fromat")
},
new AudioResampler(AudioInfo.SilkV3()),
new SilkV3Codec.Encoder(),
output
};
if (!pipeline.Start().Result) throw new Exception("Encode failed");
audioTime = pipeline.GetAudioTime();
return output.ToArray();
}
// Cannot convert unknown type
default: throw new Exception("Unknown Fromat");
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
using Lagrange.OneBot.Database;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class ReplySegment(uint messageId)
{
public ReplySegment() : this(0) { }
[JsonPropertyName("id")][CQProperty] public string MessageId { get; set; } = messageId.ToString();
}
[SegmentSubscriber(typeof(ForwardEntity), "reply")]
public partial class ReplySegment : SegmentBase
{
internal MessageChain? TargetChain { get; set; }
internal uint Sequence { get; private set; }
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is ReplySegment reply && Database is not null)
{
var messageRecord = Database.GetCollection<MessageRecord>().FindById(int.Parse(reply.MessageId));
reply.TargetChain ??= (MessageChain)messageRecord;
var build = MessagePacker.Build(reply.TargetChain, "");
var virtualElem = build.Body?.RichText?.Elems;
if (virtualElem != null) reply.TargetChain.Elements.AddRange(virtualElem);
builder.Forward(reply.TargetChain);
}
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not ForwardEntity forward || Database is null) throw new ArgumentException("The entity is not a forward entity.");
var collection = Database.GetCollection<MessageRecord>();
int hash = MessageRecord.CalcMessageHash(forward.MessageId, forward.Sequence);
var query = collection.FindById(hash);
return query == null
? new ReplySegment { MessageId = 0.ToString() }
: new ReplySegment { MessageId = query.MessageHash.ToString() };
}
}

View File

@@ -0,0 +1,26 @@
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class RpsSegment
{
}
[SegmentSubscriber(typeof(FaceEntity), "rps")]
public partial class RpsSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is RpsSegment) builder.Face(359, true);
}
public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is FaceEntity { IsLargeFace: true, FaceId: 359 }) return new RpsSegment();
return null;
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class TextSegment(string text)
{
public TextSegment() : this("") { }
[JsonPropertyName("text")] [CQProperty] public string Text { get; set; } = text;
}
[SegmentSubscriber(typeof(TextEntity), "text")]
public partial class TextSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is TextSegment textSegment) builder.Text(textSegment.Text);
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not TextEntity textEntity) throw new ArgumentException("Invalid entity type.");
return new TextSegment(textEntity.Text);
}
}

View File

@@ -0,0 +1,34 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class VideoSegment(string url)
{
public VideoSegment() : this("") { }
[JsonPropertyName("file")] [CQProperty] public string File { get; set; } = url;
[JsonPropertyName("url")] public string Url { get; set; } = url;
}
[SegmentSubscriber(typeof(VideoEntity), "video")]
public partial class VideoSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is VideoSegment videoSegment and not { File: "" } && CommonResolver.Resolve(videoSegment.File) is { } image)
{
builder.Video(image);
}
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not VideoEntity videoEntity) throw new ArgumentException("Invalid entity type.");
return new RecordSegment(videoEntity.VideoUrl);
}
}

View File

@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;
using Lagrange.Core.Message;
using Lagrange.Core.Message.Entity;
namespace Lagrange.OneBot.Message.Entity;
[Serializable]
public partial class XmlSegment(string xml, int serviceid)
{
public XmlSegment() : this("", 35) { }
[JsonPropertyName("data")] [CQProperty] public string Xml { get; set; } = xml;
[JsonPropertyName("service_id")] [CQProperty] public int ServiceId { get; set; } = serviceid;
}
[SegmentSubscriber(typeof(XmlEntity), "xml")]
public partial class XmlSegment : SegmentBase
{
public override void Build(MessageBuilder builder, SegmentBase segment)
{
if (segment is XmlSegment xml) builder.Xml(xml.Xml, xml.ServiceId);
}
public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity)
{
if (entity is not XmlEntity xmlEntity) throw new ArgumentException("Invalid entity type.");
return new XmlSegment(xmlEntity.Xml, xmlEntity.ServiceId);
}
}

View File

@@ -0,0 +1,197 @@
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Lagrange.Core;
using Lagrange.Core.Event.EventArg;
using Lagrange.Core.Message;
using Lagrange.Core.Utility.Extension;
using Lagrange.OneBot.Core.Entity.Message;
using Lagrange.OneBot.Core.Network;
using Lagrange.OneBot.Database;
using Lagrange.OneBot.Message.Entity;
using LiteDB;
using Microsoft.Extensions.Configuration;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Lagrange.OneBot.Message;
/// <summary>
/// The class that converts the OneBot message to/from MessageEntity of Lagrange.Core
/// </summary>
public sealed class MessageService
{
private readonly LagrangeWebSvcCollection _service;
private readonly LiteDatabase _context;
private readonly IConfiguration _config;
private readonly Dictionary<Type, List<(string Type, SegmentBase Factory)>> _entityToFactory;
private readonly bool _stringPost;
private static readonly JsonSerializerOptions Options;
static MessageService()
{
Options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { ModifyTypeInfo } } };
}
public MessageService(BotContext bot, LagrangeWebSvcCollection service, LiteDatabase context, IConfiguration config)
{
_service = service;
_context = context;
_config = config;
_stringPost = config.GetValue<bool>("Message:StringPost");
var invoker = bot.Invoker;
invoker.OnFriendMessageReceived += OnFriendMessageReceived;
invoker.OnGroupMessageReceived += OnGroupMessageReceived;
invoker.OnTempMessageReceived += OnTempMessageReceived;
_entityToFactory = new Dictionary<Type, List<(string, SegmentBase)>>();
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
var attribute = type.GetCustomAttribute<SegmentSubscriberAttribute>();
if (attribute != null)
{
var instance = (SegmentBase)type.CreateInstance(false);
instance.Database = _context;
if (_entityToFactory.TryGetValue(attribute.Entity, out var factories)) factories.Add((attribute.Type, instance));
else _entityToFactory[attribute.Entity] = [(attribute.Type, instance)];
}
}
}
private void OnFriendMessageReceived(BotContext bot, FriendMessageEvent e)
{
var record = (MessageRecord)e.Chain;
_context.GetCollection<MessageRecord>().Insert(new BsonValue(record.MessageHash), record);
if (_config.GetValue<bool>("Message:IgnoreSelf") && e.Chain.FriendUin == bot.BotUin) return; // ignore self message
var request = ConvertToPrivateMsg(bot.BotUin, e.Chain);
_ = _service.SendJsonAsync(request);
}
public object ConvertToPrivateMsg(uint uin, MessageChain chain)
{
var segments = Convert(chain);
int hash = MessageRecord.CalcMessageHash(chain.MessageId, chain.Sequence);
string raw = ToRawMessage(segments);
object request = _stringPost ? new OneBotPrivateStringMsg(uin, new OneBotSender(chain.FriendUin, chain.FriendInfo?.Nickname ?? string.Empty), "friend", ((DateTimeOffset)chain.Time).ToUnixTimeSeconds())
{
MessageId = hash,
UserId = chain.FriendUin,
Message = raw,
RawMessage = raw,
TargetId = chain.TargetUin,
} : new OneBotPrivateMsg(uin, new OneBotSender(chain.FriendUin, chain.FriendInfo?.Nickname ?? string.Empty), "friend", ((DateTimeOffset)chain.Time).ToUnixTimeSeconds())
{
MessageId = hash,
UserId = chain.FriendUin,
Message = segments,
RawMessage = raw,
TargetId = chain.TargetUin
};
return request;
}
private void OnGroupMessageReceived(BotContext bot, GroupMessageEvent e)
{
var record = (MessageRecord)e.Chain;
_context.GetCollection<MessageRecord>().Insert(new BsonValue(record.MessageHash), record);
if (_config.GetValue<bool>("Message:IgnoreSelf") && e.Chain.FriendUin == bot.BotUin) return; // ignore self message
var request = ConvertToGroupMsg(bot.BotUin, e.Chain);
_ = _service.SendJsonAsync(request);
}
public object ConvertToGroupMsg(uint uin, MessageChain chain)
{
var segments = Convert(chain);
int hash = MessageRecord.CalcMessageHash(chain.MessageId, chain.Sequence);
object request = _stringPost
? new OneBotGroupStringMsg(uin, chain.GroupUin ?? 0, ToRawMessage(segments), chain.GroupMemberInfo ?? throw new Exception("Group member not found"), hash, ((DateTimeOffset)chain.Time).ToUnixTimeSeconds())
: new OneBotGroupMsg(uin, chain.GroupUin ?? 0, segments, ToRawMessage(segments), chain.GroupMemberInfo ?? throw new Exception("Group member not found"), hash, ((DateTimeOffset)chain.Time).ToUnixTimeSeconds());
return request;
}
private void OnTempMessageReceived(BotContext bot, TempMessageEvent e)
{
var record = (MessageRecord)e.Chain;
_context.GetCollection<MessageRecord>().Insert(new BsonValue(record.MessageHash), record);
var segments = Convert(e.Chain);
var request = new OneBotPrivateMsg(bot.BotUin, new OneBotSender(e.Chain.FriendUin, e.Chain.FriendInfo?.Nickname ?? string.Empty), "group", ((DateTimeOffset)e.Chain.Time).ToUnixTimeSeconds())
{
MessageId = record.MessageHash,
UserId = e.Chain.FriendUin,
Message = segments,
RawMessage = ToRawMessage(segments)
};
_ = _service.SendJsonAsync(request);
}
public List<OneBotSegment> Convert(MessageChain chain)
{
var result = new List<OneBotSegment>();
foreach (var entity in chain)
{
if (_entityToFactory.TryGetValue(entity.GetType(), out var instances))
{
foreach (var instance in instances)
{
if (instance.Factory.FromEntity(chain, entity) is { } segment) result.Add(new OneBotSegment(instance.Type, segment));
}
}
}
return result;
}
private static string EscapeText(string str) => str
.Replace("&", "&amp;")
.Replace("[", "&#91;")
.Replace("]", "&#93;");
private static string EscapeCQ(string str) => EscapeText(str).Replace(",", "&#44;");
private static string ToRawMessage(List<OneBotSegment> segments)
{
var rawMessageBuilder = new StringBuilder();
foreach (var segment in segments)
{
if (segment.Data is TextSegment textSeg)
{
rawMessageBuilder.Append(EscapeText(textSeg.Text));
}
else
{
rawMessageBuilder.Append("[CQ:");
rawMessageBuilder.Append(segment.Type);
foreach (var property in JsonSerializer.SerializeToElement(segment.Data, Options).EnumerateObject())
{
rawMessageBuilder.Append(',');
rawMessageBuilder.Append(property.Name);
rawMessageBuilder.Append('=');
rawMessageBuilder.Append(EscapeCQ(property.Value.ToString()));
}
rawMessageBuilder.Append(']');
}
}
return rawMessageBuilder.ToString();
}
private static void ModifyTypeInfo(JsonTypeInfo ti)
{
if (ti.Kind != JsonTypeInfoKind.Object) return;
foreach (var info in ti.Properties.Where(x => x.AttributeProvider?.IsDefined(typeof(CQPropertyAttribute), false) == false).ToArray())
{
ti.Properties.Remove(info);
}
}
}

View File

@@ -0,0 +1,13 @@
using Lagrange.Core.Message;
using LiteDB;
namespace Lagrange.OneBot.Message;
public abstract class SegmentBase
{
public LiteDatabase? Database { protected get; set; }
public abstract void Build(MessageBuilder builder, SegmentBase segment);
public abstract SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity);
}

View File

@@ -0,0 +1,11 @@
namespace Lagrange.OneBot.Message;
[AttributeUsage(AttributeTargets.Class)]
public class SegmentSubscriberAttribute(Type entity, string type, string? sendType = null) : Attribute
{
public Type Entity { get; } = entity;
public string Type { get; } = type;
public string SendType { get; } = sendType ?? type;
}