diff --git a/Flandre.sln b/Flandre.sln
index f08c748..e24b411 100644
--- a/Flandre.sln
+++ b/Flandre.sln
@@ -2,8 +2,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Core", "src\Flandre.Core\Flandre.Core.csproj", "{347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.ExampleBot", "src\Flandre.ExampleBot\Flandre.ExampleBot.csproj", "{49D31127-B126-4734-B6E4-C1A6A6A41A5E}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Adapters.Konata", "src\Flandre.Adapters.Konata\Flandre.Adapters.Konata.csproj", "{723E8DD2-55F1-4095-93D7-E6AD600A63EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Core.Tests", "tests\Flandre.Core.Tests\Flandre.Core.Tests.csproj", "{22726A9B-8E54-4513-AB04-61B4A34DDC2A}"
@@ -22,10 +20,6 @@ Global
{347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}.Release|Any CPU.Build.0 = Release|Any CPU
- {49D31127-B126-4734-B6E4-C1A6A6A41A5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {49D31127-B126-4734-B6E4-C1A6A6A41A5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {49D31127-B126-4734-B6E4-C1A6A6A41A5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {49D31127-B126-4734-B6E4-C1A6A6A41A5E}.Release|Any CPU.Build.0 = Release|Any CPU
{723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/src/Flandre.Adapters.Konata/KonataBot.cs b/src/Flandre.Adapters.Konata/KonataBot.cs
index 8c0eac1..2c8d6e1 100644
--- a/src/Flandre.Adapters.Konata/KonataBot.cs
+++ b/src/Flandre.Adapters.Konata/KonataBot.cs
@@ -23,6 +23,9 @@ public sealed class KonataBot : FlandreBot
///
public override string Platform => _config.PlatformOverride ?? "konata";
+ ///
+ public override string SelfId { get; }
+
///
/// Konata 内部 bot
///
@@ -52,6 +55,10 @@ internal KonataBot(KonataBotConfig config, Logger logger)
Internal = BotFather.Create(config.Konata, config.Device, config.KeyStore);
_config = config;
+ SelfId = string.IsNullOrWhiteSpace(_config.SelfId)
+ ? Internal.Uin.ToString()
+ : _config.SelfId;
+
Internal.OnFriendMessage += InnerOnFriendMessage;
Internal.OnGroupMessage += InnerOnGroupMessage;
Internal.OnGroupInvite += InnerOnGroupInvite;
diff --git a/src/Flandre.Adapters.Mock/MockBot.cs b/src/Flandre.Adapters.Mock/MockBot.cs
index 86218d9..d7e2f00 100644
--- a/src/Flandre.Adapters.Mock/MockBot.cs
+++ b/src/Flandre.Adapters.Mock/MockBot.cs
@@ -15,6 +15,8 @@ public class MockBot : Bot
///
public override string Platform => "mock";
+ public override string SelfId => _selfId;
+
private readonly Logger _logger;
private readonly string _selfId = Guid.NewGuid().ToString();
@@ -83,4 +85,4 @@ internal void ReceiveMessage(Message message, TaskCompletionSource? OnGuildInvited;
public override event BotEventHandler? OnGuildJoinRequested;
public override event BotEventHandler? OnFriendRequested;
-}
\ No newline at end of file
+}
diff --git a/src/Flandre.Adapters.OneBot/CqCodeParser.cs b/src/Flandre.Adapters.OneBot/CqCodeParser.cs
index df06639..23a925d 100644
--- a/src/Flandre.Adapters.OneBot/CqCodeParser.cs
+++ b/src/Flandre.Adapters.OneBot/CqCodeParser.cs
@@ -30,6 +30,7 @@ public static MessageContent ParseCqMessage(string message)
"face" => ParseFace(sections[1..]),
"record" => ParseRecord(sections[1..]),
"image" => ParseImage(sections[1..]),
+ "at" => ParseAt(sections[1..]),
_ => new TextSegment(code)
});
}
@@ -145,4 +146,9 @@ private static OneBotImageSegment ParseImage(string[] data)
return segment;
}
+
+ private static AtSegment ParseAt(string[] data)
+ {
+ return new AtSegment(data[0][3..]); // qq=xxx
+ }
}
\ No newline at end of file
diff --git a/src/Flandre.Adapters.OneBot/GuildBot.cs b/src/Flandre.Adapters.OneBot/GuildBot.cs
index ee14d0c..621d0c4 100644
--- a/src/Flandre.Adapters.OneBot/GuildBot.cs
+++ b/src/Flandre.Adapters.OneBot/GuildBot.cs
@@ -16,6 +16,11 @@ public class OneBotGuildBot : Bot
///
public override string Platform => "qqguild";
+ public override string SelfId => _selfId;
+
+ private string _selfId = "";
+ private bool _isSelfIdSet;
+
public OneBotGuildInternalBot Internal { get; }
private readonly OneBotBot _mainBot;
@@ -35,6 +40,12 @@ internal OneBotGuildBot(OneBotBot mainBot)
internal void InvokeMessageEvent(OneBotApiGuildMessageEvent e)
{
+ if (!_isSelfIdSet)
+ {
+ _selfId = e.SelfId.ToString();
+ _isSelfIdSet = true;
+ }
+
OnMessageReceived?.Invoke(this, new BotMessageReceivedEvent(new Message
{
Time = DateTimeOffset.FromUnixTimeSeconds(e.Time).DateTime,
diff --git a/src/Flandre.Adapters.OneBot/OneBotBot.cs b/src/Flandre.Adapters.OneBot/OneBotBot.cs
index ed95931..7d2b342 100644
--- a/src/Flandre.Adapters.OneBot/OneBotBot.cs
+++ b/src/Flandre.Adapters.OneBot/OneBotBot.cs
@@ -14,6 +14,9 @@ public abstract class OneBotBot : Bot
///
public override string Platform => "onebot";
+ ///
+ public override string SelfId { get; }
+
internal readonly OneBotGuildBot GuildBot;
internal readonly Logger Logger;
@@ -24,8 +27,9 @@ public abstract class OneBotBot : Bot
protected override Logger GetLogger() => Logger;
- internal OneBotBot(Logger logger)
+ internal OneBotBot(string selfId, Logger logger)
{
+ SelfId = selfId;
Internal = new OneBotInternalBot(this);
GuildBot = new OneBotGuildBot(this);
Logger = logger;
diff --git a/src/Flandre.Adapters.OneBot/WebSocketBot.cs b/src/Flandre.Adapters.OneBot/WebSocketBot.cs
index 494c1f9..c6287e9 100644
--- a/src/Flandre.Adapters.OneBot/WebSocketBot.cs
+++ b/src/Flandre.Adapters.OneBot/WebSocketBot.cs
@@ -25,7 +25,7 @@ public class OneBotWebSocketBot : OneBotBot
public override event BotEventHandler? OnGuildJoinRequested;
public override event BotEventHandler? OnFriendRequested;
- internal OneBotWebSocketBot(OneBotBotConfig config, Logger logger) : base(logger)
+ internal OneBotWebSocketBot(OneBotBotConfig config, Logger logger) : base(config.SelfId, logger)
{
_config = config;
diff --git a/src/Flandre.Core/Attributes/CommandAttribute.cs b/src/Flandre.Core/Attributes/CommandAttribute.cs
index 6f5a394..0690a4b 100644
--- a/src/Flandre.Core/Attributes/CommandAttribute.cs
+++ b/src/Flandre.Core/Attributes/CommandAttribute.cs
@@ -58,8 +58,7 @@ public CommandAttribute(string pattern)
}
Parameters.Add(info);
-
- FlandreApp.Logger.Debug(Parameters.Count.ToString());
+
parser.SkipSpaces();
}
}
diff --git a/src/Flandre.Core/Common/Bot.cs b/src/Flandre.Core/Common/Bot.cs
index ee941c0..1028e7b 100644
--- a/src/Flandre.Core/Common/Bot.cs
+++ b/src/Flandre.Core/Common/Bot.cs
@@ -15,6 +15,11 @@ public abstract class Bot
///
public abstract string Platform { get; }
+ ///
+ /// Bot 自身 ID
+ ///
+ public abstract string SelfId { get; }
+
///
/// 获取 Logger
///
diff --git a/src/Flandre.Core/Common/Command.cs b/src/Flandre.Core/Common/Command.cs
index 8d6b6d5..b194eaa 100644
--- a/src/Flandre.Core/Common/Command.cs
+++ b/src/Flandre.Core/Common/Command.cs
@@ -59,9 +59,8 @@ internal Command(Plugin plugin, CommandAttribute info, MethodInfo innerMethod,
{
var peek = parser.SkipSpaces().Peek(' ');
- if (peek.StartsWith("--"))
+ if (peek.StartsWith("--")) // option (full)
{
- // option (full)
var optName = parser.Read(' ').TrimStart('-');
var optNo = false;
@@ -96,9 +95,8 @@ internal Command(Plugin plugin, CommandAttribute info, MethodInfo innerMethod,
break;
}
}
- else if (peek.StartsWith('-'))
+ else if (peek.StartsWith('-')) // option (short)
{
- // option (short)
var opts = parser.Read(' ').TrimStart('-');
parser.SkipSpaces();
@@ -128,25 +126,31 @@ internal Command(Plugin plugin, CommandAttribute info, MethodInfo innerMethod,
}
}
}
- else
+ else // argument
{
- // argument
if (argIndex >= CommandInfo.Parameters.Count)
return (args, "参数过多,请检查指令格式。");
var param = CommandInfo.Parameters[argIndex];
- if (param.Type == "string")
+ switch (param.Type)
{
- args.Arguments.ArgumentList.Add(
- new KeyValuePair(param.Name, parser.ReadQuoted()));
- }
- else
- {
- if (CommandUtils.TryParseType(parser.Read(' '),
- param.Type, out var result, false))
- args.Arguments.ArgumentList.Add(new KeyValuePair(param.Name, result));
- else return (args, $"参数 {param.Name} 类型错误,应为 {param.Type}。");
+ case "string":
+ args.Arguments.ArgumentList.Add(
+ new KeyValuePair(param.Name, parser.ReadQuoted()));
+ break;
+
+ case "text":
+ args.Arguments.ArgumentList.Add(
+ new KeyValuePair(param.Name, parser.ReadToEnd()));
+ break;
+
+ default:
+ if (CommandUtils.TryParseType(parser.Read(' '),
+ param.Type, out var result, false))
+ args.Arguments.ArgumentList.Add(new KeyValuePair(param.Name, result));
+ else return (args, $"参数 {param.Name} 类型错误,应为 {param.Type}。");
+ break;
}
providedArgs.Add(param.Name);
diff --git a/src/Flandre.Core/Common/Context.cs b/src/Flandre.Core/Common/Context.cs
index 6eb5961..23aae57 100644
--- a/src/Flandre.Core/Common/Context.cs
+++ b/src/Flandre.Core/Common/Context.cs
@@ -30,4 +30,9 @@ public Context(FlandreApp app, Bot bot)
/// Bot 所在平台,等同于 Bot.Platform。
///
public string Platform => Bot.Platform;
+
+ ///
+ /// Bot 自身 ID,等同于 Bot.SelfId。
+ ///
+ public string SelfId => Bot.SelfId;
}
\ No newline at end of file
diff --git a/src/Flandre.Core/Extensions/AppExtensions.cs b/src/Flandre.Core/Extensions/AppExtensions.cs
new file mode 100644
index 0000000..6b72460
--- /dev/null
+++ b/src/Flandre.Core/Extensions/AppExtensions.cs
@@ -0,0 +1,30 @@
+namespace Flandre.Core.Extensions;
+
+///
+/// 的扩展方法
+///
+public static class AppExtensions
+{
+ ///
+ /// 设置群组代理 Bot(主 Bot)
+ ///
+ /// FlandreApp 实例
+ /// 平台
+ /// 群组 ID
+ /// Bot.SelfId
+ public static void SetGuildAssignee(this FlandreApp app, string platform, string guildId, string botId)
+ {
+ app.GuildAssignees.AddOrUpdate($"{platform}:{guildId}", botId, (_, _) => botId);
+ }
+
+ ///
+ /// 检查群组是否已被代理(已设置主 bot)
+ ///
+ /// FlandreApp 实例
+ /// 平台
+ /// Bot.SelfId
+ public static bool IsGuildAssigned(this FlandreApp app, string platform, string botId)
+ {
+ return app.GuildAssignees.ContainsKey($"{platform}:{botId}");
+ }
+}
\ No newline at end of file
diff --git a/src/Flandre.Core/Extensions/ContextExtensions.cs b/src/Flandre.Core/Extensions/ContextExtensions.cs
new file mode 100644
index 0000000..5c8f4ba
--- /dev/null
+++ b/src/Flandre.Core/Extensions/ContextExtensions.cs
@@ -0,0 +1,19 @@
+using Flandre.Core.Messaging;
+
+namespace Flandre.Core.Extensions;
+
+///
+/// 上下文扩展方法
+///
+public static class ContextExtensions
+{
+ ///
+ /// 将 Bot 设置为群组主 Bot
+ ///
+ /// 消息上下文
+ public static void SetBotAsGuildAssignee(this MessageContext ctx)
+ {
+ if (ctx.GuildId is null) return;
+ ctx.App.SetGuildAssignee(ctx.Platform, ctx.GuildId, ctx.SelfId);
+ }
+}
\ No newline at end of file
diff --git a/src/Flandre.Core/Flandre.Core.csproj b/src/Flandre.Core/Flandre.Core.csproj
index c3514af..4653000 100644
--- a/src/Flandre.Core/Flandre.Core.csproj
+++ b/src/Flandre.Core/Flandre.Core.csproj
@@ -3,7 +3,7 @@
Flandre.Core
$(Title)
- 0.5.0
+ 0.6.0
$(PackageVersion)
b1acksoil
跨平台,低耦合的聊天机器人框架,一次编写,多处运行。
diff --git a/src/Flandre.Core/FlandreApp.cs b/src/Flandre.Core/FlandreApp.cs
index f3a8937..1338bdd 100644
--- a/src/Flandre.Core/FlandreApp.cs
+++ b/src/Flandre.Core/FlandreApp.cs
@@ -1,4 +1,5 @@
-using System.Runtime.CompilerServices;
+using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
using Flandre.Core.Common;
using Flandre.Core.Events.App;
using Flandre.Core.Events.Plugin;
@@ -17,7 +18,7 @@ namespace Flandre.Core;
///
/// 应用基本框架
///
-public class FlandreApp
+public partial class FlandreApp
{
internal readonly List Adapters = new();
internal readonly List> Middlewares = new();
@@ -32,6 +33,8 @@ public class FlandreApp
internal Dictionary ShortcutMap { get; } = new();
+ internal ConcurrentDictionary GuildAssignees { get; } = new();
+
///
/// App 配置
///
@@ -253,22 +256,14 @@ void CatchAndLog(Action action) => Task.Run(() =>
}
}
+ // 群组 assignee 检查
+ Use(CheckAssigneeMiddleware);
+
// 插件 OnMessageReceived
- Use((ctx, next) =>
- {
- foreach (var plugin in Plugins)
- plugin.OnMessageReceived(ctx);
- next();
- });
+ Use(PluginMessageReceivedMiddleware);
// 指令解析中间件
- Use((ctx, next) =>
- {
- var content = ParseCommand(ctx);
- if (content is not null)
- ctx.Bot.SendMessage(ctx.Message, content);
- next();
- });
+ Use(ParseCommandMiddleware);
}
private void ExecuteMiddlewares(MessageContext ctx, int index)
@@ -305,7 +300,8 @@ private void ExecuteMiddlewares(MessageContext ctx, int index)
}
var commandStr = ctx.Message.GetText().Trim();
- if (commandStr == Config.CommandPrefix) return null;
+ if (commandStr == Config.CommandPrefix)
+ return null;
var parser = new StringParser(commandStr);
@@ -314,7 +310,9 @@ private void ExecuteMiddlewares(MessageContext ctx, int index)
if (ShortcutMap.TryGetValue(root, out var command))
return ParseAndInvoke(command, parser);
-
+ if (!string.IsNullOrWhiteSpace(Config.CommandPrefix)
+ && !root.StartsWith(Config.CommandPrefix))
+ return null;
root = root.TrimStart(Config.CommandPrefix);
parser.SkipSpaces();
diff --git a/src/Flandre.Core/Messaging/MessageContent.cs b/src/Flandre.Core/Messaging/MessageContent.cs
index f33bf10..b91f53b 100644
--- a/src/Flandre.Core/Messaging/MessageContent.cs
+++ b/src/Flandre.Core/Messaging/MessageContent.cs
@@ -8,7 +8,7 @@ namespace Flandre.Core.Messaging;
///
public class MessageContent : IEnumerable
{
- private readonly IEnumerable _segments;
+ internal IEnumerable Segments { get; }
///
/// 使用消息段构造消息内容
@@ -16,7 +16,7 @@ public class MessageContent : IEnumerable
///
public MessageContent(IEnumerable segments)
{
- _segments = segments;
+ Segments = segments;
}
///
@@ -25,7 +25,7 @@ public MessageContent(IEnumerable segments)
/// 消息段类型
public TSegment? GetSegment() where TSegment : MessageSegment
{
- return (TSegment?)_segments.FirstOrDefault(segment => segment is TSegment);
+ return (TSegment?)Segments.FirstOrDefault(segment => segment is TSegment);
}
///
@@ -34,7 +34,7 @@ public MessageContent(IEnumerable segments)
/// 消息段类型
public IEnumerable GetSegments() where TSegment : MessageSegment
{
- return _segments.Where(segment => segment is TSegment).Cast();
+ return Segments.Where(segment => segment is TSegment).Cast();
}
///
@@ -83,7 +83,7 @@ public static implicit operator MessageContent(string text)
///
public IEnumerator GetEnumerator()
{
- return _segments.GetEnumerator();
+ return Segments.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
diff --git a/src/Flandre.Core/Middlewares.cs b/src/Flandre.Core/Middlewares.cs
new file mode 100644
index 0000000..7000dd1
--- /dev/null
+++ b/src/Flandre.Core/Middlewares.cs
@@ -0,0 +1,38 @@
+using Flandre.Core.Messaging;
+using Flandre.Core.Messaging.Segments;
+
+namespace Flandre.Core;
+
+public partial class FlandreApp
+{
+ internal void CheckAssigneeMiddleware(MessageContext ctx, Action next)
+ {
+ var segment = ctx.Message.Content.Segments.FirstOrDefault();
+ if (segment is AtSegment ats)
+ {
+ if (ats.UserId == ctx.SelfId)
+ next();
+ }
+ // 如果没找到群组的 assignee
+ else if (!GuildAssignees.TryGetValue($"{ctx.Platform}:{ctx.GuildId}", out var assignee))
+ next();
+ // 如果找到了群组的 assignee,且是自己
+ else if (ctx.SelfId == assignee)
+ next();
+ }
+
+ internal void PluginMessageReceivedMiddleware(MessageContext ctx, Action next)
+ {
+ foreach (var plugin in Plugins)
+ plugin.OnMessageReceived(ctx);
+ next();
+ }
+
+ internal void ParseCommandMiddleware(MessageContext ctx, Action next)
+ {
+ var content = ParseCommand(ctx);
+ if (content is not null)
+ ctx.Bot.SendMessage(ctx.Message, content);
+ next();
+ }
+}
\ No newline at end of file
diff --git a/tests/Flandre.Core.Tests/FlandreAppTests.cs b/tests/Flandre.Core.Tests/FlandreAppTests.cs
index 9508ad7..4dbb16e 100644
--- a/tests/Flandre.Core.Tests/FlandreAppTests.cs
+++ b/tests/Flandre.Core.Tests/FlandreAppTests.cs
@@ -1,6 +1,8 @@
using Flandre.Adapters.Mock;
using Flandre.Core.Utils;
+// ReSharper disable StringLiteralTypo
+
namespace Flandre.Core.Tests;
public class FlandreAppTests
@@ -32,6 +34,10 @@ public void TestFlandreApp()
content = friendClient.SendForReply("test1 -bo 111.444 --no-trueopt false").Result;
Assert.Equal("arg1: False opt: 111.444 b: True t: False",
content?.GetText());
+
+ content = friendClient.SendForReply("test2 some text aaa bbb ").Result;
+ Assert.Equal("some text aaa bbb",
+ content?.GetText());
app.Stop();
};
@@ -50,7 +56,7 @@ public void TestShortcutAndAlias()
app.OnAppReady += (_, _) =>
{
- Assert.Equal(5, app.CommandMap.Count);
+ Assert.Equal(6, app.CommandMap.Count);
Assert.Equal(2, app.ShortcutMap.Count);
Assert.NotNull(app.CommandMap.GetValueOrDefault("test1"));
diff --git a/tests/Flandre.Core.Tests/TestPlugin.cs b/tests/Flandre.Core.Tests/TestPlugin.cs
index 25bba26..ed07b8e 100644
--- a/tests/Flandre.Core.Tests/TestPlugin.cs
+++ b/tests/Flandre.Core.Tests/TestPlugin.cs
@@ -29,6 +29,13 @@ public static MessageContent OnTest1(MessageContext ctx, ParsedArgs args)
var trueOpt = args.GetOption("trueopt");
return $"arg1: {arg1} opt: {opt} b: {boolOpt} t: {trueOpt}";
}
+
+ [Command("test2 ")]
+ public static MessageContent OnTest2(MessageContext _, ParsedArgs args)
+ {
+ var arg1 = args.GetArgument("arg1");
+ return arg1;
+ }
[Command("sub.test")]
[Alias("sssuuubbb")]