feat: package MyAgent as Shirobot plugin
This commit is contained in:
commit
453a8e7793
12 files changed
+913
No files matched your search
@@ -0,0 +1,142 @@
|
||||
using ShiroBot.SDK.Models;
|
||||
using ShiroBot.SDK.Plugin;
|
||||
using ShiroBot.Model.QQ;
|
||||
using MyAgent;
|
||||
using MyAgent.Core;
|
||||
using MyAgent.Tools;
|
||||
using Channel = ShiroBot.SDK.Models.Channel;
|
||||
|
||||
namespace Shirobot.Plugin.MyAgent;
|
||||
|
||||
internal sealed class ShirobotGroupPlatform(IBotContext context) : IGroupPlatform
|
||||
{
|
||||
private static readonly HttpClient DownloadClient = new() { Timeout = TimeSpan.FromSeconds(20) };
|
||||
public async Task<MessageSendResult?> SendAsync(
|
||||
long groupId, string text, IReadOnlyList<ChatAttachment> attachments,
|
||||
string? replyToMessageId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using var scope = context.UsePlatform("qq");
|
||||
var segments = new List<MessageSegment>();
|
||||
if (!string.IsNullOrWhiteSpace(replyToMessageId)) segments.Add(new QuoteSegment(replyToMessageId));
|
||||
if (!string.IsNullOrWhiteSpace(text)) segments.Add(new TextSegment(text));
|
||||
foreach (var attachment in attachments)
|
||||
{
|
||||
var uri = !string.IsNullOrWhiteSpace(attachment.LocalPath) && File.Exists(attachment.LocalPath)
|
||||
? "base64://" + Convert.ToBase64String(await File.ReadAllBytesAsync(attachment.LocalPath, cancellationToken))
|
||||
: attachment.SourceUrl;
|
||||
if (!string.IsNullOrWhiteSpace(uri)) segments.Add(new ImageSegment(uri) { Summary = "[图片]" });
|
||||
}
|
||||
if (segments.Count == 0) return null;
|
||||
var sent = await context.Message.SendMessageAsync(Channel.Group(groupId.ToString()), segments);
|
||||
return new MessageSendResult { Ok = true, MessageId = sent.MessageId, Timestamp = sent.Timestamp };
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<GroupChatEvent>> GetHistoryAsync(long groupId, int limit, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using var scope = context.UsePlatform("qq");
|
||||
var messages = await context.Message.GetHistoryMessagesAsync(Channel.Group(groupId.ToString()), limit: Math.Clamp(limit, 1, 100));
|
||||
return messages.Where(message => message.Sender.Id != message.SelfId)
|
||||
.Select(message => GroupChatEvent.User(groupId, message.Sender.Id, message.Member?.Nick ?? message.Sender.Name ?? message.Sender.Id,
|
||||
message.GetPlainText(), message.Timestamp, ToAttachments(message.Segments), message.MessageId))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public async Task<GroupProfileResult> GetProfileAsync(long groupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using var scope = context.UsePlatform("qq");
|
||||
var channel = await context.Channel.GetChannelAsync(groupId.ToString());
|
||||
var members = await context.Channel.GetMembersAsync(groupId.ToString());
|
||||
return new GroupProfileResult
|
||||
{
|
||||
GroupId = groupId,
|
||||
GroupName = channel?.Name ?? groupId.ToString(),
|
||||
AvatarUrl = $"https://p.qlogo.cn/gh/{groupId}/{groupId}/100",
|
||||
MemberCount = members.Count,
|
||||
Members = members.Select(member => new GroupMemberInfo
|
||||
{
|
||||
UserId = long.TryParse(member.User.Id, out var id) ? id : 0,
|
||||
Name = member.Nick ?? member.User.Name ?? member.User.Id,
|
||||
Role = member.Role.ToString().ToLowerInvariant(),
|
||||
AvatarUrl = $"https://q1.qlogo.cn/g?b=qq&nk={member.User.Id}&s=100"
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
internal static ChatAttachment[] ToAttachments(IReadOnlyList<MessageSegment> segments) => segments
|
||||
.OfType<ResourceSegment>()
|
||||
.Select(segment => new ChatAttachment
|
||||
{
|
||||
Type = segment switch { ImageSegment => "image", AudioSegment => "audio", VideoSegment => "video", FileSegment => "file", _ => "resource" },
|
||||
Url = segment.Uri,
|
||||
SourceUrl = segment.Uri,
|
||||
Title = segment.FileName ?? "",
|
||||
Size = segment is FileSegment file ? file.FileSize ?? 0 : 0
|
||||
}).ToArray();
|
||||
|
||||
internal async Task<List<ChatAttachment>> ToAttachmentsAsync(
|
||||
IReadOnlyList<MessageSegment> segments, long groupId, DocumentTextExtractor documents,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var scope = context.UsePlatform("qq");
|
||||
var attachments = new List<ChatAttachment>();
|
||||
foreach (var segment in segments.OfType<ResourceSegment>())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var url = segment.Uri;
|
||||
if (string.IsNullOrWhiteSpace(url) && !string.IsNullOrWhiteSpace(segment.ResourceId))
|
||||
{
|
||||
if (segment is FileSegment)
|
||||
url = await (context.GetAdapterExtension<IQFileApi>()?.GetGroupFileDownloadUrlAsync(groupId, segment.ResourceId)
|
||||
?? Task.FromResult(""));
|
||||
else
|
||||
url = await context.Message.GetResourceUrlAsync(segment.ResourceId);
|
||||
}
|
||||
var attachment = new ChatAttachment
|
||||
{
|
||||
Type = segment switch { ImageSegment => "image", AudioSegment => "audio", VideoSegment => "video", FileSegment => "file", _ => "resource" },
|
||||
Url = url,
|
||||
SourceUrl = url,
|
||||
Title = segment.FileName ?? "",
|
||||
Size = segment is FileSegment file ? file.FileSize ?? 0 : 0
|
||||
};
|
||||
if (segment is FileSegment && Uri.TryCreate(url, UriKind.Absolute, out var uri) && uri.Scheme is "http" or "https")
|
||||
{
|
||||
try
|
||||
{
|
||||
var cache = Path.Combine(context.PluginDirectory, "data", "attachments");
|
||||
Directory.CreateDirectory(cache);
|
||||
var suffix = Path.GetExtension(attachment.Title);
|
||||
var path = Path.Combine(cache, Guid.NewGuid().ToString("n") + suffix);
|
||||
using var response = await DownloadClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
if (response.Content.Headers.ContentLength is > 15_000_000) throw new InvalidDataException("Attachment exceeds 15 MB");
|
||||
await using (var input = await response.Content.ReadAsStreamAsync(cancellationToken))
|
||||
await using (var output = File.Create(path))
|
||||
{
|
||||
var buffer = new byte[81920];
|
||||
long total = 0;
|
||||
int read;
|
||||
while ((read = await input.ReadAsync(buffer, cancellationToken)) > 0)
|
||||
{
|
||||
total += read;
|
||||
if (total > 15_000_000) throw new InvalidDataException("Attachment exceeds 15 MB");
|
||||
await output.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
|
||||
}
|
||||
}
|
||||
attachment.LocalPath = path;
|
||||
attachment.TextContent = await documents.TryExtractAsync(path, attachment.Title,
|
||||
response.Content.Headers.ContentType?.MediaType ?? "", cancellationToken);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
ShiroBot.SDK.Abstractions.BotLog.Warning($"Could not read group attachment: {ex.Message}");
|
||||
}
|
||||
}
|
||||
attachments.Add(attachment);
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user