143 lines
7.4 KiB
C#
143 lines
7.4 KiB
C#
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;
|
|
}
|
|
}
|