paper)
18 | {
19 | if (paper.Value is null)
20 | {
21 | return "您没有记下任何内容";
22 | }
23 | return paper.Value;
24 | }
25 |
26 | // user1 <= 查看笔记
27 | // => 您没有记下任何内容
28 | // user1 <= 笔记 测试
29 | // => 写好了!
30 | // user1 <= 查看笔记
31 | // => 测试
32 | // user2 <= 笔记 test
33 | // => 写好了!
34 | // user2 <= 查看笔记
35 | // => test
36 | // user1 <= 查看笔记
37 | // => 测试
38 | ```
39 |
40 |
41 |
42 | ## 注解
43 | - 作用域
44 | - AutoData 目前仅可用于路由方法
45 | - AutoData 之间的数据互通性
46 | - 默认同一 Module 之内、参数名和参数类型一致、生命周期(是否保存至硬盘)一致的 AutoData 之间数据互通。如果需要实现 Module 之间的数据互通,请使用 SharedFrom 特性。
47 | - 性能
48 | - 本功能依赖于 DataManager 的数据托管功能,因此不推荐用于存储过于庞大的数据。不存储至硬盘的 AutoData 没有此限制
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cocoa Framework 2
5 |
6 |
7 |
8 |
9 | [](https://www.nuget.org/packages/Maila.Cocoa.Framework/)
10 |
11 | Cocoa Framework 是一个基于 mirai 的 QQ 机器人开发框架,致力于降低 QQ 机器人的开发难度,使更多人能定制属于自己的 QQ 机器人
12 |
13 | 项目名称来源于
14 | [Koi](https://zh.moegirl.org.cn/Koi)
15 | 作品
16 | [《请问您今天要来点兔子吗?》](https://zh.moegirl.org.cn/%E8%AF%B7%E9%97%AE%E6%82%A8%E4%BB%8A%E5%A4%A9%E8%A6%81%E6%9D%A5%E7%82%B9%E5%85%94%E5%AD%90%E5%90%97)
17 | 中的
18 | [保登心爱](https://zh.moegirl.org.cn/%E4%BF%9D%E7%99%BB%E5%BF%83%E7%88%B1)
19 |
20 |
21 |
22 |
23 | # 开始使用
24 |
25 | 尝试快速入门教程:
26 | - [你好,Cocoa!](./Docs/Tutorial/Hellococoa.md)
27 |
28 | 查看常见功能的案例:
29 | - [复读机](./Docs/Samples/Repeater.md)
30 | - [Cocode](./Docs/Samples/Cocode.md)
31 |
32 | 教程、API 文档和更多案例详见 [Cocoa Framework 文档](./Docs/index.md)
33 |
34 |
35 |
36 | # 相较于 1.x 版本的区别
37 |
38 | Cocoa Framework 2 在继承了 1.x 版本核心思路的同时进行了完全的重构,尽可能地使开发者只需考虑功能本身。同时,与 mirai-api-http 的通讯也改为使用 Cocoa Beans,便于及时跟进 mirai 的新特性。
39 | 更多新特性请参阅 [Cocoa Framework 2 中的新特性](./Docs/Whatsnew/NewFeatures.md) 和 [更新日志](./Docs/Whatsnew/UpdateLog.md)
40 |
41 |
42 |
43 | # 联系我们
44 | 您可以直接通过 Issue 向我们提供反馈。
45 | 如果希望与项目开发者和其他用户交流,欢迎加入 QQ 群(766230870)
46 | 如果希望加入我们,欢迎联系上述交流群群主。
--------------------------------------------------------------------------------
/Docs/Manual/API/Core/UserIdentity.md:
--------------------------------------------------------------------------------
1 | # UserIdentity 枚举
2 | 命名空间:Maila.Cocoa.Framework
3 |
4 |
5 |
6 | 标识用户身份。
7 | 此枚举有一个 FlagsAttribute 特性,允许按位组合成员值。
8 | ```C#
9 | [System.Flags]
10 | public enum UserIdentity
11 |
12 | ```
13 |
14 | ## 字段
15 | | | | |
16 | | - | - | - |
17 | | User | 0 | |
18 | | Admin | 1 | |
19 | | Owner | 2 | |
20 | | Developer | 4 | |
21 | | Debugger | 8 | |
22 | | Operator | 16 | |
23 | | Staff | 32 | |
24 | | Custom1 | 64 | |
25 | | Custom2 | 128 | |
26 | | Custom3 | 256 | |
27 | | Custom4 | 512 | |
28 | | Custom5 | 1024 | |
29 | | Custom6 | 2048 | |
30 | | Custom7 | 4096 | |
31 | | Custom8 | 8192 | |
32 | | Custom9 | 16384 | |
33 | | SU | 63 | 超级用户,包含自定义身份以外的全部身份 |
34 |
--------------------------------------------------------------------------------
/Docs/Whatsnew/NewFeatures.md:
--------------------------------------------------------------------------------
1 | # Cocoa Framework 2 中的新特性
2 |
3 | ## 基于身份的权限管理系统
4 | ---
5 | 可以为用户指定身份,然后通过身份管理用户对功能的访问。详见 [权限管理](../Manual/Permission.md)
6 |
7 |
8 |
9 | ## 开放事件侦听接口
10 | ---
11 | 允许添加对消息事件(群聊消息、好友消息、临时消息)以外事件的处理
12 |
13 |
14 |
15 | ## 新增特性(Attribute)
16 | ---
17 | - IdentityRequirementsAttribute
18 | > 用于指定身份要求,可以重复添加。每个要求的判断标准为“全部”,即用户要拥有所要求的全部身份;要求间的关系为“任意”,即满足任意要求即可。详见 [权限管理](../Manual/Permission.md)
19 | > 可用于类和方法,仅在 Module 类、路由方法和 OnMessage 方法中有效
20 | - DisableInGroupAttribute 和 DisableInPrivateAttribute
21 | > 用于禁止指定的功能在群或私聊(包括好友消息和临时消息)环境下使用
22 | > 可用于类和方法,仅在 Module 类、路由方法和 OnMessage 方法中有效
23 | - GroupNameAttribute
24 | > 用于指定变量所映射的组。详见 [路由](../Manual/Route.md)
25 | > 可用于参数,仅在正则路由方法的参数中有效
26 |
27 |
28 |
29 | ## 数据托管逻辑更改为同步
30 | ---
31 | 被托管的字段会对应于一个数据文件,之前在程序运行过程中对此数据文件的直接修改都会被覆盖,现在则会将手动修改的内容与字段的数据进行合并。但除了修改静态数据,一般不推荐手动修改数据文件。详见 [数据存储](../Manual/Data.md)
32 |
33 |
34 |
35 | ## 增强 Middleware 处理
36 | ---
37 | ```C#
38 | // protected virtual bool OnMessage(ref MessageSource src, ref QMessage msg);
39 | protected virtual void OnMessage(MessageSource src, QMessage msg, Action next);
40 | ```
41 | 这意味着 Middleware 不再受到 ref 的限制,可以使用 async/await 更为方便地进行异步处理
42 |
43 |
44 |
45 | ## AutoData
46 | ---
47 | 基于消息来源提供不同的数据,可以极大简化对简单数据的管理。详见 [AutoData](../Manual/AutoData.md)
48 |
49 |
50 |
51 | ## AsyncMeeting
52 | 用更加自然的方式实现对话。详见 [AsyncMeeting](../Manual/AsyncMeeting.md)
53 |
--------------------------------------------------------------------------------
/CocoaFramework.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31129.286
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CocoaFramework", "CocoaFramework\CocoaFramework.csproj", "{B3AA3A0C-40BE-47D0-9FF4-81CB188C98D2}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64AC6751-C57E-42C8-907C-C595EC93B1E7}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | EndProjectSection
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Release|Any CPU = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {B3AA3A0C-40BE-47D0-9FF4-81CB188C98D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {B3AA3A0C-40BE-47D0-9FF4-81CB188C98D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {B3AA3A0C-40BE-47D0-9FF4-81CB188C98D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {B3AA3A0C-40BE-47D0-9FF4-81CB188C98D2}.Release|Any CPU.Build.0 = Release|Any CPU
23 | EndGlobalSection
24 | GlobalSection(SolutionProperties) = preSolution
25 | HideSolutionNode = FALSE
26 | EndGlobalSection
27 | GlobalSection(ExtensibilityGlobals) = postSolution
28 | SolutionGuid = {2CBC5CF0-DBCE-4E54-8F02-8B6D912DDA0E}
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------
/CocoaFramework/Models/Route/BuiltIn/TextRoute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 | using System.Linq;
6 | using System.Reflection;
7 | using Maila.Cocoa.Beans.Models.Messages;
8 | using Maila.Cocoa.Framework.Support;
9 |
10 | namespace Maila.Cocoa.Framework.Models.Route.BuiltIn
11 | {
12 | internal class TextRoute : RouteInfo
13 | {
14 | private readonly string text;
15 | private readonly bool ignoreCase;
16 | private readonly bool atRequired;
17 |
18 | public TextRoute(BotModuleBase module, MethodInfo route, string text, bool ignoreCase, bool atRequired) : base(module, route)
19 | {
20 | this.text = text;
21 | this.ignoreCase = ignoreCase;
22 | this.atRequired = atRequired;
23 | }
24 |
25 | protected override bool IsMatch(MessageSource src, QMessage msg)
26 | {
27 | var msgText = msg.PlainText;
28 | if (atRequired)
29 | {
30 | if (!msg.GetSubMessages().Any(at => at.Target == BotAPI.BotQQ))
31 | {
32 | return false;
33 | }
34 |
35 | msgText = msgText.StartsWith(' ') ? msgText[1..] : msgText;
36 | }
37 |
38 | return ignoreCase
39 | ? string.Equals(msgText, text, StringComparison.CurrentCultureIgnoreCase)
40 | : msgText == text;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Docs/Manual/CustomRoute.md:
--------------------------------------------------------------------------------
1 | # 自定义路由
2 |
3 | Cocoa Framework 支持自定义路由,以便于定制特殊的消息处理逻辑。
4 |
5 |
6 |
7 | ## 示例
8 | 本示例实现限定于特定群聊的路由。相较于普通的 TextRoute,添加了对群聊的判断。为便于理解,本示例不支持 TextRoute 的 IgnoreCase 与 AtRequired 功能,且仅支持设置一个群。
9 |
10 | ```C#
11 | // 继承 RouteInfo 类,实现对消息的处理
12 | public class GroupSpecifiedTextRoute : RouteInfo
13 | {
14 | private readonly string text;
15 | private readonly long groupId;
16 |
17 | public GroupSpecifiedTextRoute(BotModuleBase module, MethodInfo route, string text, long groupId) : base(module, route)
18 | {
19 | this.text = text;
20 | this.groupId = groupId;
21 | }
22 |
23 | protected override bool IsMatch(MessageSource src, QMessage msg)
24 | {
25 | return src.Group?.Id == groupId && msg.PlainText == text;
26 | }
27 | }
28 |
29 | // 继承 RouteAttribute 类,实现对应的路由特性
30 | public sealed class GroupSpecifiedTextRouteAttribute : RouteAttribute
31 | {
32 | public string Text { get; }
33 | public long GroupId { get; }
34 |
35 | public GroupSpecifiedTextRouteAttribute(string text, long groupId)
36 | {
37 | Text = text;
38 | GroupId = groupId;
39 | }
40 |
41 | public override RouteInfo GetRouteInfo(BotModuleBase module, MethodInfo route)
42 | {
43 | return new GroupSpecifiedTextRoute(module, route, Text, GroupId);
44 | }
45 | }
46 |
47 | // 使用自定义路由
48 | [BotModule]
49 | public class Demo : BotModuleBase
50 | {
51 | // 在群 123456 中收到“ping”时回复“pong”
52 | [GroupSpecifiedTextRoute("ping", 123456)]
53 | public static void Run(MessageSource src)
54 | {
55 | src.Send("pong");
56 | }
57 | }
58 | ```
--------------------------------------------------------------------------------
/Docs/Manual/API/Meeting/ListeningTarget.md:
--------------------------------------------------------------------------------
1 | # ListeningTarget 类
2 | 命名空间:Maila.Cocoa.Framework.Models.Processing
3 |
4 |
5 |
6 | 用于更换监听目标。
7 | ```C#
8 | public class ListeningTarget
9 | ```
10 |
11 |
12 |
13 | ## 属性
14 | - All
15 | > 表示监听全部内容
16 | > ```C#
17 | > public static ListeningTarget All { get; }
18 | > ```
19 |
20 |
21 |
22 | ## 方法
23 | - FromGroup
24 | > 设置监听目标为某个群
25 | > ```C#
26 | > public static ListeningTarget FromGroup(long groupId);
27 | > public static ListeningTarget FromGroup(QGroup group);
28 | > ```
29 | >
30 | > ### 参数
31 | > `groupId` long
32 | > 要监听的群号
33 | > `group` QGroup
34 | > 要监听的群
35 | - FromUser
36 | > 设置监听目标为某个用户
37 | > ```C#
38 | > public static ListeningTarget FromUser(long userId);
39 | > public static ListeningTarget FromUser(QUser user);
40 | > ```
41 | >
42 | > ### 参数
43 | > `userId` long
44 | > 要监听的 QQ 号
45 | > `user` QUser
46 | > 要监听的用户
47 | - FromTarget
48 | > 指定具体监听目标
49 | > ```C#
50 | > public static ListeningTarget FromTarget(long groupId, long userId);
51 | > public static ListeningTarget FromTarget(MessageSource src);
52 | > ```
53 | >
54 | > ### 参数
55 | > `groupId` long
56 | > 要监听的群号
57 | > `userId` long
58 | > 要监听的 QQ 号
59 | > `src` MessageSource
60 | > 要监听的消息源
61 | - CustomTarget
62 | > 自定义监听目标
63 | > ```C#
64 | > public static ListeningTarget CustomTarget(Predicate pred);
65 | > ```
66 | >
67 | > ### 参数
68 | > `pred` Predicate\
69 | > 目标的判定规则
70 |
--------------------------------------------------------------------------------
/CocoaFramework/Models/Processing/ListeningTarget.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 |
6 | namespace Maila.Cocoa.Framework.Models.Processing
7 | {
8 | public class ListeningTarget
9 | {
10 | internal Predicate Pred;
11 |
12 | private ListeningTarget(long? groupId, long? userId)
13 | {
14 | Pred = src =>
15 | {
16 | if (src is null)
17 | {
18 | return true;
19 | }
20 | if (groupId is null && userId is null)
21 | {
22 | return true;
23 | }
24 |
25 | bool uFit = userId is null || userId == src.User.Id;
26 | bool gFit = groupId is null ? !src.IsGroup : groupId == src.Group?.Id;
27 | return gFit && uFit;
28 | };
29 | }
30 | private ListeningTarget(Predicate pred)
31 | {
32 | Pred = pred;
33 | }
34 |
35 | public static ListeningTarget All { get; } = new(null, null);
36 |
37 | public static ListeningTarget FromGroup(long groupId) => new(groupId, null);
38 | public static ListeningTarget FromGroup(QGroup group) => new(group.Id, null);
39 |
40 | public static ListeningTarget FromUser(long userId) => new(null, userId);
41 | public static ListeningTarget FromUser(QUser user) => new(null, user.Id);
42 |
43 | public static ListeningTarget FromTarget(long groupId, long userId) => new(groupId, userId);
44 | public static ListeningTarget FromTarget(MessageSource src) => new(src.Group?.Id, src.User.Id);
45 | public static ListeningTarget CustomTarget(Predicate pred) => new(pred);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Docs/Manual/API/Core/Middleware.md:
--------------------------------------------------------------------------------
1 | # Middleware
2 |
3 | Middleware 用于对消息进行进行预处理,执行顺序有严格的要求,因此由开发者手动添加,最终执行顺序与添加顺序一致。
4 |
5 |
6 |
7 | ## MiddlewareCore 类
8 | 命名空间:Maila.Cocoa.Framework.Core
9 |
10 |
11 |
12 | 管理 Middleware 的核心类
13 | ```C#
14 | public static class MiddlewareCore
15 | ```
16 |
17 | ### 属性
18 | - Middlewares
19 | > 当前所有被加载的 Middleware
20 | > ```C#
21 | > public static ImmutableArray Middlewares { get; }
22 | > ```
23 |
24 |
25 |
26 | ## BotMiddlewareBase 类
27 | 命名空间:Maila.Cocoa.Framework
28 |
29 |
30 |
31 | Middleware 的基类,所有 Middleware 应派生自此类。
32 | ```C#
33 | public abstract class BotMiddlewareBase
34 | ```
35 |
36 | ### 方法
37 | - Init
38 | > 初始化时被调用
39 | > ```C#
40 | > protected virtual void Init();
41 | > ```
42 | - Destroy
43 | > 断开连接时被调用
44 | > ```C#
45 | > protected virtual void Destroy();
46 | > ```
47 | - OnMessage
48 | > 收到消息时被调用
49 | > ```C#
50 | > protected virtual void OnMessage(MessageSource src, QMessage msg, Action next)
51 | > ```
52 | > #### 参数
53 | > `src` MessageSource
54 | > 消息来源
55 | > `msg` QMessage
56 | > 消息内容
57 | > `next` Action\
58 | > 下一个 Middleware 的 OnMessage 方法,如果允许消息继续传递需要调用此方法,如需更改消息来源或消息内容可以直接把新的内容作为 `next` 的参数。注意,`next` 在一次执行中最多允许调用一次,建议在调用 `next` 后直接使用 return 结束
59 | - OnSendMessage
60 | > 发送消息时被调用
61 | > ```C#
62 | > protected virtual bool OnSendMessage(ref long id, ref bool isGroup, ref IMessage[] chain, ref int? quote);
63 | > ```
64 | > #### 参数
65 | > `id` ref long
66 | > 发送目标
67 | > `isGroup` ref bool
68 | > 目标为群聊
69 | > `chain` ref IMessage[]
70 | > 要发送的消息链
71 | > `quote` ref int?
72 | > 要回复的消息 Id,不回复时为 null
73 | > #### 返回值
74 | > bool
75 | > 是否同意发送
--------------------------------------------------------------------------------
/CocoaFramework/QMessage.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 | using System.Collections.Immutable;
6 | using System.Diagnostics.CodeAnalysis;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 | using Maila.Cocoa.Beans.Models.Messages;
10 | using Maila.Cocoa.Framework.Support;
11 |
12 | namespace Maila.Cocoa.Framework
13 | {
14 | public class QMessage
15 | {
16 | public ImmutableArray Chain { get; }
17 | public int Id { get; }
18 | public DateTime Time { get; }
19 | public string PlainText { get; }
20 |
21 | public QMessage(IMessage[] chain)
22 | {
23 | if (chain is null || chain.Length < 2 || chain[0] is not SourceMessage sm)
24 | {
25 | throw new ArgumentException("Invalid message chain.");
26 | }
27 |
28 | Id = sm.Id;
29 | Time = DateTimeOffset.FromUnixTimeSeconds(sm.Time).LocalDateTime;
30 | Chain = ImmutableArray.Create(chain, 1, chain.Length - 1);
31 | PlainText = string.Concat(chain.Select(m => (m as PlainMessage)?.Text));
32 | }
33 |
34 | public T[] GetSubMessages() where T : IMessage
35 | => Chain.OfType()
36 | .ToArray();
37 |
38 | public override string ToString()
39 | => PlainText;
40 |
41 | [return: NotNullIfNotNull("msg")]
42 | public static implicit operator string?(QMessage? msg)
43 | => msg?.PlainText;
44 |
45 | public void Recall()
46 | => RecallAsync();
47 |
48 | public Task RecallAsync()
49 | => BotAPI.Recall(Id);
50 |
51 | public void SetEssence()
52 | => SetEssenceAsync();
53 |
54 | public Task SetEssenceAsync()
55 | => BotAPI.SetEssence(Id);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Docs/Tutorial/Hellococoa.md:
--------------------------------------------------------------------------------
1 | # 你好,Cocoa!
2 |
3 | 通过本教程,您将学会如何通过 Cocoa Framework 实现最简单的应答机器人
4 |
5 |
6 |
7 | ## 在此之前
8 | - [启动 Mirai](https://github.com/mamoe/mirai/blob/dev/docs/UserManual.md) 并 [安装 mirai-api-http](https://github.com/mamoe/mirai/blob/dev/docs/UserManual.md#%E5%A6%82%E4%BD%95%E5%AE%89%E8%A3%85%E5%AE%98%E6%96%B9%E6%8F%92%E4%BB%B6),如果使用的是 mirai-api-http 2.x 版本需手动启用 WebSocket
9 | - 安装 [Visual Studio](https://visualstudio.microsoft.com/zh-hans/) 或其他 IDE
10 |
11 |
12 |
13 | ## 新建项目
14 | 1. 启动 Visual Studio,创建新项目
15 | 2. 选择控制台应用程序,继续
16 | 3. 输入项目名,选择项目存放位置,继续
17 | 4. 目标框架选择 .NET 6.0(一般已默认选择),创建
18 |
19 |
20 |
21 | ## 添加引用
22 | 1. 在创建的项目上右键,点击“管理 Nuget 包”
23 | 2. 进入“浏览”选项卡,搜索 Maila.Cocoa.Framework 或 maila
24 | 3. 选中搜索结果中的 Maila.Cocoa.Framework,点击右侧界面中的“安装”
25 |
26 |
27 |
28 | ## 编写启动代码
29 | 将默认创建的 Program.cs 中的全部代码删除,替换为以下代码
30 | ```C#
31 | using System;
32 | using Maila.Cocoa.Framework;
33 |
34 | BotStartupConfig config = new("YourVerifyKey", 12345678); // 启动配置,请将 YourVerifyKey 改为您的 VerifyKey,12345678 改为机器人的 QQ 号
35 | var succeed = await BotStartup.ConnectAndInit(config); // 连接 Mirai 并初始化
36 | if (succeed) // 如果连接成功
37 | {
38 | Console.WriteLine("Startup OK"); // 提示连接成功
39 | while (Console.ReadLine() != "exit"); // 在用户往控制台输入“exit”前持续运行
40 | await BotStartup.DisconnectAndSaveData(); // 断开连接
41 | }
42 | else // 否则
43 | {
44 | Console.WriteLine("Failed"); // 提示连接失败
45 | }
46 | ```
47 |
48 |
49 |
50 | ## 实现简单应答
51 | 新建 Hello.cs 文件,并输入以下代码
52 | ```C#
53 | using Maila.Cocoa.Framework;
54 |
55 | [BotModule]
56 | public class Hello : BotModuleBase
57 | {
58 | [TextRoute("hello cocoa")] // 收到“hello cocoa”时调用此方法
59 | public static void Run(MessageSource src)
60 | {
61 | src.Send("Hi!"); // 向消息来源发送“Hi!”
62 | }
63 | }
64 | ```
65 |
66 |
67 |
68 | ## 完成
69 | 运行程序,进入 QQ 测试功能
70 |
71 |
72 |
73 | ## 备注
74 | 如果希望主动发送消息,例如在启动时向管理员发送通知,可以通过 BotAPI.SendFriendMessage 或 BotAPI.SendGroupMessage 实现。其他与机器人相关的 API 也位于 BotAPI 类中。
75 | ```C#
76 | BotAPI.SendFriendMessage(123456, new PlainMessage("message"));
77 | ```
78 |
--------------------------------------------------------------------------------
/Docs/Manual/Route.md:
--------------------------------------------------------------------------------
1 | # 路由
2 |
3 | 路由是一种方便消息分类的机制,通过给定路由条件,实现自动解析和自动调用,相比于手动解析更为便捷。
4 |
5 |
6 |
7 | ## 特性
8 | 特性用于标记入口,需添加于入口方法前
9 | - TextRouteAttribute
10 | > 文本路由,如消息与提供的文本一致,则进行调用
11 | - RegexRouteAttribute
12 | > 正则路由,如消息符合正则表达式,则进行调用,并自动将匹配到的组填充到同名参数中
13 | - GroupNameAttribute
14 | > 用于指定正则路由填充时参数对应的组名
15 |
16 |
17 |
18 | ## 入口方法
19 |
20 | - 参数
21 | - 参数可以任意填写,除下述情况的参数都将被传入默认值或 null
22 | - 第一个类型为 MessageSource 的参数将被传入消息的来源
23 | - 第一个类型为 QMessage 的参数将被传入消息的内容
24 | - 参数名为正则表达式中的组名且类型为 string 的参数将被传入该组匹配到的字符串,如果该组会进行多次匹配(如 (?\abc)+)则会传入匹配到的最后一个字符串。在 TextRoute 中无效
25 | - 参数名为正则表达式中的组名且类型为 string[] 或 List\ 的参数将被传入该组匹配到的全部字符串。在 TextRoute 中无效
26 | - 类型为 UserAutoData、GroupAutoData、SourceAutoData 的参数将根据消息来源提供对应的数据。详见 [AutoData](./AutoData.md)
27 | - 类型为 MessageInfo 和 AsyncMeeting 时,将被传入对应的实例
28 |
29 | - 返回值
30 | - 入口方法的返回值可以是任意类型
31 | - 如果为 void 表示一旦被调用就代表消息被处理
32 | - 如果为 bool 类型表示消息是否被处理
33 | - 如果为 string、StringBuilder 或 MessageBuilder 类型且不为空将自动向来源发送对应文本,否则表示消息未被处理
34 | - 如果为 IEnumerator 或 IEnumerable 会被自动添加为 [Meeting](./Meeting.md)
35 | - 如果为 Task,则调用时消息会被立即标记为处理。如果 Task 存在返回值,返回值会在 Task 完成后按照其余规则进行处理
36 | - 如果为其他值类型,返回结果不为默认值将代表消息被处理
37 | - 如果为其他引用类型,返回结果不为 null 将代表消息被处理
38 |
39 |
40 |
41 | ## 示例
42 | ```C#
43 | using Maila.Cocoa.Framework;
44 |
45 | [BotModule]
46 | public class Demo : BotModuleBase
47 | {
48 | // 收到“test1”时回复“ok”
49 | [TextRoute("test1")]
50 | public static void Run1(MessageSource src)
51 | {
52 | src.Send("ok");
53 | }
54 |
55 | // 收到“test2”时回复“ok”
56 | [TextRoute("test2")]
57 | public static string Run2()
58 | {
59 | return "ok";
60 | }
61 |
62 | // 收到“你好abc”时回复“我不叫abc”
63 | [RegexRoute("你好(?.+)")]
64 | public static string Run3(string name)
65 | {
66 | return "我不叫" + name;
67 | }
68 |
69 | // 收到“你好abc”时回复“我不叫abc”的另一种实现方式
70 | [RegexRoute("你好(?.+)")]
71 | public static string Run3([GroupName("name")] string wrongName)
72 | {
73 | return "我不叫" + wrongName;
74 | }
75 | }
76 |
77 | ```
--------------------------------------------------------------------------------
/CocoaFramework/CocoaFramework.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | Maila.Cocoa.Framework
6 | enable
7 | 2.1.8.3
8 | Maila
9 | Miyakowww
10 | Cocoa Framework
11 | Maila.Cocoa.Framework
12 | true
13 | An efficient, intelligent and convenient QQ robot development framework.
14 | Copyright © Maila 2021
15 |
16 | https://github.com/Miyakowww/CocoaFramework2
17 | git
18 | mirai mirai-api-http maila cocoa
19 | true
20 | true
21 | true
22 | true
23 | true
24 | AGPL-3.0-or-later
25 | README_nuget.md
26 |
27 |
28 |
29 | 1591;1701;1702
30 |
31 |
32 |
33 | 1591;1701;1702
34 |
35 |
36 |
37 |
38 | True
39 | \
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Docs/Manual/API/Meeting/AsyncTask.md:
--------------------------------------------------------------------------------
1 | # AsyncTask 类
2 | 命名空间:Maila.Cocoa.Framework.Models.Processing
3 |
4 |
5 |
6 | 用于在 Meeting 中执行异步任务。请注意,异步任务执行过程中不会阻塞消息,因此在 Meeting 等待异步任务的过程中可能由用户误操作导致重复建立 Meeting,请自行避免。
7 | ```C#
8 | public class AsyncTask
9 | ```
10 |
11 |
12 |
13 | ## 方法
14 | - Wait
15 | > 暂停执行
16 | > ```C#
17 | > public static AsyncTask Wait(int milliseconds);
18 | > public static AsyncTask Wait(int milliseconds, CancellationToken cancellationToken);
19 | > public static AsyncTask Wait(TimeSpan delay);
20 | > public static AsyncTask Wait(TimeSpan delay, CancellationToken cancellationToken);
21 | > ```
22 | >
23 | > ### 参数
24 | > `milliseconds` int
25 | > 暂停时长(毫秒)
26 | > `delay` TimeSpan
27 | > 暂停时长
28 | > `cancellationToken` CancellationToken
29 | > 用于取消执行的 Token
30 | - WaitUntil
31 | > 在指定时间之前暂停执行
32 | > ```C#
33 | > public static AsyncTask WaitUntil(DateTime time);
34 | > public static AsyncTask WaitUntil(DateTime time, CancellationToken cancellationToken);
35 | > ```
36 | >
37 | > ### 参数
38 | > `time` DateTime
39 | > 结束时间
40 | > `cancellationToken` CancellationToken
41 | > 用于取消执行的 Token
42 | - Run
43 | > 执行指定的异步任务
44 | > ```C#
45 | > public static AsyncTask Run(Action action);
46 | > public static AsyncTask Run(Action action, CancellationToken cancellationToken);
47 | > public static AsyncTask Run(Func function);
48 | > public static AsyncTask Run(Func function, CancellationToken cancellationToken);
49 | > public static AsyncTask Run(Func function, out GetValue result);
50 | > public static AsyncTask Run(Func> func, out GetValue result);
51 | > public static AsyncTask Run(Func> func, out GetValue result, CancellationToken cancellationToken);
52 | > ```
53 | >
54 | > ### 参数
55 | > `action` Action
56 | > 要异步执行的同步任务
57 | > `function` Func\
58 | > 要执行的异步任务
59 | > `function` Func\
60 | > 要异步执行的同步函数
61 | > `function` Func\>
62 | > 要执行的异步函数
63 | > `result` out GetValue\
64 | > 用于获取函数返回值的对象
65 | > `cancellationToken` CancellationToken
66 | > 用于取消执行的 Token
--------------------------------------------------------------------------------
/CocoaFramework/BotEventHandlerBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Collections.Immutable;
7 | using System.Linq;
8 | using System.Reflection;
9 | using Maila.Cocoa.Beans.Models.Events;
10 |
11 | namespace Maila.Cocoa.Framework
12 | {
13 | public abstract class BotEventHandlerBase
14 | {
15 | internal readonly ImmutableDictionary> EventListeners;
16 |
17 | private static readonly Type BaseType = typeof(BotEventHandlerBase);
18 |
19 | protected internal BotEventHandlerBase()
20 | {
21 | Type realType = GetType();
22 |
23 | Dictionary> listeners = new();
24 | foreach (var method in realType
25 | .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
26 | .Where(m => m.DeclaringType != BaseType && m.GetCustomAttribute() is null))
27 | {
28 | var parameters = method.GetParameters();
29 | if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(Event)))
30 | {
31 | continue;
32 | }
33 |
34 | var eventType = parameters[0].ParameterType;
35 | void handler(Event e)
36 | {
37 | method.Invoke(this, new object[] { e });
38 | }
39 |
40 | if (listeners.ContainsKey(eventType))
41 | {
42 | listeners[eventType] += handler;
43 | }
44 | else
45 | {
46 | listeners[eventType] = handler;
47 | }
48 | }
49 |
50 | EventListeners = listeners.ToImmutableDictionary();
51 | }
52 |
53 | internal void HandleEvent(Event evt)
54 | {
55 | if (EventListeners.TryGetValue(evt.GetType(), out var action))
56 | {
57 | action(evt);
58 | }
59 | }
60 |
61 | protected internal virtual void OnException(Exception e) { }
62 | protected internal virtual void OnDisconnect() { }
63 | }
64 | }
--------------------------------------------------------------------------------
/Docs/Manual/Permission.md:
--------------------------------------------------------------------------------
1 | # 权限管理
2 |
3 | Cocoa Framework 采了基于身份的权限管理机制。
4 |
5 |
6 |
7 | ## 用户身份
8 | 每个用户可同时拥有多个身份,在程序内使用 enum UserIdentity 记录。UserIdentity 带有 Flags 特性,意味着您可以使用按位或运算符(|)叠加多个身份。
9 |
10 |
11 |
12 | ## 身份授予和检验
13 | BotAuth 提供了身份相关的功能,这些是较为常用的方法:
14 | - public static UserIdentity GetIdentity(long qqId);
15 | > 获取用户身份
16 | - public static void SetIdentity(long qqId, UserIdentity identity);
17 | > 设置用户身份
18 | - public static UserIdentity AddIdentity(long qqId, UserIdentity identity);
19 | > 追加用户身份
20 | - public static UserIdentity RemoveIdentity(long qqId, UserIdentity identity);
21 | > 移除用户身份
22 |
23 |
24 |
25 | ## 群成员身份
26 | enum GroupPermission 记录用户的群成员身份,包括成员(MEMBER)、管理员(ADMINISTRATOR)和群主(OWNER)。可以从 MessageSource.Permission 获取。
27 |
28 |
29 |
30 | ## Cocoa Framework 内置的身份校验功能
31 | 可以在 Module 对应的类和消息处理方法前添加 IdentityRequirementsAttribute 特性以限制对相关功能的访问。
32 | ```C#
33 | using Maila.Cocoa.Framework;
34 | using Maila.Cocoa.Beans.Models;
35 |
36 | // 仅 Owner 可使用 RunA 和 RunB
37 | [BotModule]
38 | [IdentityRequirements(UserIdentity.Owner)]
39 | public class Demo1 : BotModuleBase
40 | {
41 | [TextRoute("a")]
42 | public static void RunA()
43 | {
44 | // ...
45 | }
46 | [TextRoute("b")]
47 | public static void RunB()
48 | {
49 | // ...
50 | }
51 | }
52 |
53 | [BotModule]
54 | public class Demo2 : BotModuleBase
55 | {
56 | // 所有人均可使用
57 | [TextRoute("a")]
58 | public static void RunA()
59 | {
60 | // ...
61 | }
62 |
63 | // 仅 Owner 可使用
64 | [TextRoute("b")]
65 | [IdentityRequirements(UserIdentity.Owner)]
66 | public static void RunB()
67 | {
68 | // ...
69 | }
70 |
71 | // 仅 Owner 和 Admin 可使用
72 | [TextRoute("c")]
73 | [IdentityRequirements(UserIdentity.Owner)]
74 | [IdentityRequirements(UserIdentity.Admin)]
75 | public static void RunC()
76 | {
77 | // ...
78 | }
79 |
80 | // 仅同时为 Owner 和 Admin 的用户可使用
81 | [TextRoute("d")]
82 | [IdentityRequirements(UserIdentity.Owner | UserIdentity.Admin)]
83 | public static void RunD()
84 | {
85 | // ...
86 | }
87 |
88 | // 仅群管理员及以上(群主)可使用
89 | [TextRoute("e")]
90 | [IdentityRequirements(GroupPermission.ADMINISTRATOR)]
91 | public static void RunE()
92 | {
93 | // ...
94 | }
95 | }
96 | ```
--------------------------------------------------------------------------------
/CocoaFramework/Models/QUser.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System.Threading.Tasks;
5 | using Maila.Cocoa.Beans.API;
6 | using Maila.Cocoa.Beans.Models.Messages;
7 | using Maila.Cocoa.Framework.Support;
8 |
9 | namespace Maila.Cocoa.Framework.Models
10 | {
11 | public class QUser
12 | {
13 | public long Id { get; }
14 |
15 | public UserIdentity Identity => BotAuth.GetIdentity(Id);
16 | public bool IsFriend => BotInfo.HasFriend(Id);
17 | public bool IsStranger => BotInfo.HasStranger(Id);
18 |
19 | public bool IsOwner => Identity.Fit(UserIdentity.Owner);
20 | public bool IsAdmin => Identity.Fit(UserIdentity.Admin);
21 | public bool IsDeveloper => Identity.Fit(UserIdentity.Developer);
22 | public bool IsDebugger => Identity.Fit(UserIdentity.Debugger);
23 | public bool IsOperator => Identity.Fit(UserIdentity.Operator);
24 | public bool IsStaff => Identity.Fit(UserIdentity.Staff);
25 |
26 | public QUser(long id)
27 | {
28 | Id = id;
29 | }
30 | public override bool Equals(object? obj)
31 | => obj is QUser user && user.Id == Id;
32 | public override int GetHashCode()
33 | => Id.GetHashCode();
34 |
35 | public int SendMessage(string message)
36 | => SendMessageAsync(message).Result;
37 |
38 | public int SendMessage(params IMessage[] chain)
39 | => SendMessageAsync(chain).Result;
40 |
41 | public Task SendMessageAsync(string message)
42 | => BotAPI.SendPrivateMessage(Id, new PlainMessage(message));
43 |
44 | public Task SendMessageAsync(params IMessage[] chain)
45 | => BotAPI.SendPrivateMessage(Id, chain);
46 |
47 | public int SendImage(string path)
48 | => SendImageAsync(path).Result;
49 |
50 | public async Task SendImageAsync(string path)
51 | {
52 | var image = await BotAPI.UploadImage(IsFriend ? UploadType.Friend : UploadType.Temp, path);
53 | return await BotAPI.SendPrivateMessage(Id, image);
54 | }
55 |
56 | public int SendVoice(string path)
57 | => SendVoiceAsync(path).Result;
58 |
59 | public async Task SendVoiceAsync(string path)
60 | {
61 | var voice = await BotAPI.UploadVoice(path);
62 | return await BotAPI.SendPrivateMessage(Id, voice);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Docs/Tutorial/Route.md:
--------------------------------------------------------------------------------
1 | # 使用路由
2 |
3 | 路由可以自动匹配接收到的消息并调用您指定的方法。通过本教程,您将学会如何使用路由
4 |
5 |
6 |
7 | ## 创建最基础的路由
8 | 1. 路由仅在 Module 中可用,因此你需要先创建一个 Module
9 | ```C#
10 | using Maila.Cocoa.Framework;
11 |
12 | [BotModule]
13 | public class Demo : BotModuleBase
14 | {
15 | }
16 | ```
17 |
18 |
19 |
20 | 2. 一个路由包含特性和入口方法
21 | ```C#
22 | using Maila.Cocoa.Framework;
23 |
24 | [BotModule]
25 | public class Demo : BotModuleBase
26 | {
27 | [TextRoute("hello")] // 特性
28 | public void Hello() // 入口方法
29 | {
30 | }
31 | }
32 | ```
33 | TextRoute 是文本路由。在本例中,如果机器人收到 "hello" 将会调用 Hello() 方法。同时还有 RegexRoute,当机器人收到符合提供的正则表达式的消息时将会调用相应的入口方法。
34 |
35 |
36 |
37 | 3. 参数列表
38 | ```C#
39 | using Maila.Cocoa.Framework;
40 |
41 | [BotModule]
42 | public class Demo : BotModuleBase
43 | {
44 | [TextRoute("hello")]
45 | public void Hello(MessageSource src) // 参数列表
46 | {
47 | }
48 | }
49 | ```
50 | 您可以按照自己的喜好添加参数。参数的顺序可以随意更换,Cocoa Framework 会自动按照参数的类型和名字传入您需要的内容。
51 | MessageSource 包含消息来源,您可以借助它轻松地回复消息。QMessage 包含消息的具体内容。对于其他支持的类型,请参考 [路由](../Manual/Route.md#入口方法)。
52 |
53 |
54 |
55 | 4. 可选的返回值
56 | ```C#
57 | using Maila.Cocoa.Framework;
58 |
59 | [BotModule]
60 | public class Demo : BotModuleBase
61 | {
62 | // 手动发送消息
63 | // [TextRoute("hello")]
64 | // public void Hello(MessageSource src)
65 | // {
66 | // src.Send("Hello!");
67 | // }
68 |
69 | // 通过返回值发送消息
70 | [TextRoute("hello")]
71 | public string Hello(MessageSource src)
72 | {
73 | return "Hello!";
74 | }
75 | }
76 | ```
77 | 您可以通过返回值来发送消息。如果您不想发送消息,可以返回 null。当然,Cocoa Framework 也支持其他的返回值类型,请参考 [路由](../Manual/Route.md#入口方法)。
78 |
79 |
80 |
81 | 5. RegexRoute 的参数自动匹配
82 | ```C#
83 | using Maila.Cocoa.Framework;
84 |
85 | [BotModule]
86 | public class Demo : BotModuleBase
87 | {
88 | [DisableInGroup]
89 | [RegexRoute("^hello (?[a-zA-Z]+)$")]
90 | public string Hello(MessageSource src, string name)
91 | {
92 | if (name == "cocoa")
93 | {
94 | return "Hello!";
95 | }
96 | else
97 | {
98 | return $"My name is cocoa, not {name}!";
99 | }
100 | }
101 | }
102 | ```
103 | RegexRoute 可以根据正则表达式中的组名将对应的内容传入同名参数。在此处的例子中,如果消息内容为 "hello cocoa",则会将 "cocoa" 传入 name 参数,此时机器人将回复 "Hello!"。但如果消息内容为 "hello coco",机器人将会生气地回复 "My name is cocoa, not coco!"。
104 | 你应该已经注意到了,这里我额外添加了一个 DisableInGroup 特性,它将禁止此功能在群聊中使用,以防被意外调用。当然,类似的也有 DisableInPrivate 特性禁止在私聊中使用、Disabled 特性禁止在一切时候使用。Disabled 特性同时还能用于模块、重写方法、托管字段和一些属性。
--------------------------------------------------------------------------------
/Docs/Manual/API/Startup/BotStartupConfig.md:
--------------------------------------------------------------------------------
1 | # BotStartupConfig 类
2 | 命名空间:Maila.Cocoa.Framework
3 |
4 |
5 |
6 | 用于配置启动信息。
7 | ```C#
8 | public class BotStartupConfig
9 | ```
10 |
11 |
12 |
13 | ## 构造函数
14 | - BotStartupConfig(string, long, string)
15 | > 初始化 BotStartupConfig 类的新实例,默认端口为 80
16 | > ```C#
17 | > public BotStartupConfig(string verifyKey, long qqId, string host);
18 | > ```
19 | >
20 | > ### 参数
21 | > `verifyKey` string
22 | > 连接密钥
23 | > `qqId` long
24 | > 机器人的 QQ 号
25 | > `host` string
26 | > mirai-api-http 的地址
27 | - BotStartupConfig(string, long, int)
28 | > 初始化 BotStartupConfig 类的新实例,默认 mirai-api-http 的地址为 127.0.0.1
29 | > ```C#
30 | > public BotStartupConfig(string verifyKey, long qqId, int port);
31 | > ```
32 | >
33 | > ### 参数
34 | > `verifyKey` string
35 | > 连接密钥
36 | > `qqId` long
37 | > 机器人的 QQ 号
38 | > `port` int
39 | > 端口
40 | - BotStartupConfig(string, long, string, int)
41 | > 初始化 BotStartupConfig 类的新实例,默认 mirai-api-http 的地址为 127.0.0.1,端口为 8080
42 | > ```C#
43 | > public BotStartupConfig(string verifyKey, long qqId, string host = "127.0.0.1", int port = 8080);
44 | > ```
45 | >
46 | > ### 参数
47 | > `verifyKey` string
48 | > 连接密钥
49 | > `qqId` long
50 | > 机器人的 QQ 号
51 | > `host` string
52 | > mirai-api-http 的地址
53 | > `port` int
54 | > 端口
55 |
56 | ## 字段
57 | - `host` string
58 | > mirai-api-http 的地址
59 | - `port` int
60 | > 端口
61 | - `verifyKey` string
62 | > 连接密钥
63 | - `qqId` long
64 | > 机器人的 QQ 号
65 | - `autoSave` TimeSpan
66 | > 数据托管的自动保存间隔
67 |
68 |
69 |
70 | ## 属性
71 | - Assemblies
72 | > 包含 Module 的程序集列表
73 | > ```C#
74 | > public List Assemblies { get; }
75 | > ```
76 |
77 |
78 |
79 | ## 方法
80 | - AddMiddleware
81 | > 添加 Middleware
82 | > ```C#
83 | > public BotStartupConfig AddMiddleware() where T : BotMiddlewareBase;
84 | > public BotStartupConfig AddMiddleware(Type type);
85 | > ```
86 | >
87 | > ### 参数
88 | > `type` Type
89 | > Middleware 类
90 | >
91 | > ### 返回值
92 | > BotStartupConfig
93 | > 当前 BotStartupConfig
94 | - AddAssembly
95 | > 添加包含 Module 的程序集。入口程序集会被自动添加,请勿重复添加
96 | > ```C#
97 | > public BotStartupConfig AddAssembly(Assembly assem);
98 | > ```
99 | >
100 | > ### 参数
101 | > `assem` Assembly
102 | > 要添加的程序集
103 | >
104 | > ### 返回值
105 | > BotStartupConfig
106 | > 当前 BotStartupConfig
--------------------------------------------------------------------------------
/CocoaFramework/Support/BotAuth.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Reflection;
7 |
8 | namespace Maila.Cocoa.Framework.Support
9 | {
10 | public static class BotAuth
11 | {
12 | private static Dictionary identities = new();
13 |
14 | internal static void Init()
15 | {
16 | DataHosting.AddOptimizeEnabledHosting(
17 | typeof(BotAuth).GetField(nameof(identities), BindingFlags.Static | BindingFlags.NonPublic)!,
18 | null,
19 | $"BotAuth/{BotAPI.BotQQ}");
20 | }
21 |
22 | internal static void Reset()
23 | {
24 | identities = new();
25 | }
26 |
27 | ///
28 | /// Get the user's identity.
29 | ///
30 | public static UserIdentity GetIdentity(long qqId)
31 | => identities.GetValueOrDefault(qqId, UserIdentity.User);
32 |
33 | ///
34 | /// Get all user's identities.
35 | ///
36 | public static KeyValuePair[] GetStoredIdentity()
37 | => identities.ToArray();
38 |
39 | ///
40 | /// Get all filtered user's identities.
41 | ///
42 | public static KeyValuePair[] GetStoredIdentity(UserIdentity filter)
43 | => identities.Where(p => p.Value.Fit(filter)).ToArray();
44 |
45 | ///
46 | /// Set the user's identity.
47 | ///
48 | public static void SetIdentity(long qqId, UserIdentity identity)
49 | => identities[qqId] = identity;
50 |
51 | ///
52 | /// Append specified identity to the user.
53 | ///
54 | public static UserIdentity AddIdentity(long qqId, UserIdentity identity)
55 | => identities[qqId] = identities.GetValueOrDefault(qqId, UserIdentity.User) | identity;
56 |
57 | ///
58 | /// Remove specified identity.
59 | ///
60 | public static UserIdentity RemoveIdentity(long qqId, UserIdentity identity)
61 | => identities[qqId] = identities.GetValueOrDefault(qqId, UserIdentity.User) & ~identity;
62 |
63 | ///
64 | /// Set the user's identity to .
65 | ///
66 | public static bool ClearIdentity(long qqId)
67 | {
68 | if (identities.ContainsKey(qqId))
69 | {
70 | identities.Remove(qqId);
71 | return true;
72 | }
73 | return false;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/CocoaFramework/Extensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.Reflection;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using Maila.Cocoa.Beans.API;
10 | using Maila.Cocoa.Beans.Models.Events;
11 | using Maila.Cocoa.Framework.Support;
12 |
13 | namespace Maila.Cocoa.Framework
14 | {
15 | public static partial class Extensions
16 | {
17 | #region === User Identity ===
18 |
19 | public static bool Fit(this UserIdentity identity, UserIdentity requirements)
20 | {
21 | return (identity & requirements) == requirements;
22 | }
23 |
24 | #endregion
25 |
26 | #region === Request Event ===
27 |
28 | public static Task Response(this NewFriendRequestEvent e, NewFriendRequestOperate operate, string message = "")
29 | => BotAPI.NewFriendRequestResp(e, operate, message);
30 |
31 | public static Task Response(this MemberJoinRequestEvent e, MemberJoinRequestOperate operate, string message = "")
32 | => BotAPI.MemberJoinRequestResp(e, operate, message);
33 |
34 | public static Task Response(this BotInvitedJoinGroupRequestEvent e, BotInvitedJoinGroupRequestOperate operate, string message = "")
35 | => BotAPI.BotInvitedJoinGroupRequestResp(e, operate, message);
36 |
37 | #endregion
38 |
39 | #region === Attribute ===
40 |
41 | public static bool TryGetAttribute(this MemberInfo member, [NotNullWhen(true)] out T? attribute) where T : Attribute
42 | {
43 | attribute = member?.GetCustomAttribute();
44 | return attribute != null;
45 | }
46 |
47 | public static bool HasAttribute(this MemberInfo member) where T : Attribute
48 | {
49 | return member?.IsDefined(typeof(T), true) ?? false;
50 | }
51 |
52 | public static bool TryGetAttribute(this ParameterInfo parameter, [NotNullWhen(true)] out T? attribute) where T : Attribute
53 | {
54 | attribute = parameter?.GetCustomAttribute();
55 | return attribute != null;
56 | }
57 |
58 | public static bool HasAttribute(this ParameterInfo parameter) where T : Attribute
59 | {
60 | return parameter?.IsDefined(typeof(T), true) ?? false;
61 | }
62 |
63 | #endregion
64 |
65 | internal static ushort CalculateCRC16(this string str)
66 | {
67 | if (string.IsNullOrEmpty(str))
68 | {
69 | return 0;
70 | }
71 |
72 | byte[] data = Encoding.UTF8.GetBytes(str);
73 | uint crc = 0xFFFF;
74 | foreach (var b in data)
75 | {
76 | crc ^= b;
77 | for (int i = 0; i < 8; i++)
78 | {
79 | crc = (crc & 1) != 0 ? (crc >> 1) ^ 0xA001 : crc >> 1;
80 | crc &= 0xFFFF;
81 | }
82 | }
83 | return (ushort)crc;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/CocoaFramework/AutoData.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 | using System.Collections.Concurrent;
6 | using Newtonsoft.Json.Linq;
7 |
8 | namespace Maila.Cocoa.Framework
9 | {
10 | public class UserAutoData
11 | {
12 | private readonly Func getter;
13 | private readonly Action setter;
14 |
15 | public T? Value { get => getter(); set => setter(value); }
16 |
17 | internal UserAutoData(ConcurrentDictionary> data, long key1, string key2)
18 | {
19 | var userData = data[key1];
20 | getter = () =>
21 | {
22 | object? val = userData[key2];
23 | if (val is T t)
24 | {
25 | return t;
26 | }
27 | if (val is JObject jobj)
28 | {
29 | T? newVal = jobj.ToObject();
30 | userData[key2] = newVal;
31 | return newVal;
32 | }
33 | return default;
34 | };
35 | setter = val => userData[key2] = val;
36 | }
37 | }
38 |
39 | public class GroupAutoData
40 | {
41 | private readonly Func getter;
42 | private readonly Action setter;
43 |
44 | public T? Value { get => getter(); set => setter(value); }
45 |
46 | internal GroupAutoData(ConcurrentDictionary> data, long key1, string key2)
47 | {
48 | var groupData = data[key1];
49 | getter = () =>
50 | {
51 | object? val = groupData[key2];
52 | if (val is T t)
53 | {
54 | return t;
55 | }
56 | if (val is JObject jobj)
57 | {
58 | T? newVal = jobj.ToObject();
59 | groupData[key2] = newVal;
60 | return newVal;
61 | }
62 | return default;
63 | };
64 | setter = val => groupData[key2] = val;
65 | }
66 | }
67 |
68 | public class SourceAutoData
69 | {
70 | private readonly Func getter;
71 | private readonly Action setter;
72 |
73 | public T? Value { get => getter(); set => setter(value); }
74 |
75 | internal SourceAutoData(ConcurrentDictionary<(long?, long), ConcurrentDictionary> data, (long?, long) key1, string key2)
76 | {
77 | var sourceData = data[key1];
78 | getter = () =>
79 | {
80 | object? val = sourceData[key2];
81 | if (val is T t)
82 | {
83 | return t;
84 | }
85 | if (val is JObject jobj)
86 | {
87 | T? newVal = jobj.ToObject();
88 | sourceData[key2] = newVal;
89 | return newVal;
90 | }
91 | return default;
92 | };
93 | setter = val => sourceData[key2] = val;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/CocoaFramework/BotStartup.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Reflection;
7 | using System.Threading.Tasks;
8 | using Maila.Cocoa.Framework.Core;
9 |
10 | namespace Maila.Cocoa.Framework
11 | {
12 | public static class BotStartup
13 | {
14 | public static bool Connected => BotCore.Connected;
15 |
16 | [Obsolete("Use ConnectAndInit instead.")]
17 | public static Task Connect(BotStartupConfig config)
18 | => BotCore.Connect(config);
19 |
20 | [Obsolete("Use DisconnectAndSaveData instead.")]
21 | public static Task Disconnect()
22 | => BotCore.Disconnect();
23 |
24 | /// Connect to the server, and automatically release the existing connection if it exists.
25 | public static Task ConnectAndInit(BotStartupConfig config)
26 | => BotCore.ConnectAndInit(config);
27 |
28 | /// Disconnect and release related resources.
29 | public static Task DisconnectAndSaveData()
30 | => BotCore.DisconnectAndSaveData();
31 |
32 | /// Reconnect to the server without releasing resources.
33 | public static Task Reconnect()
34 | => BotCore.Reconnect();
35 | }
36 |
37 | public class BotStartupConfig
38 | {
39 | public string host;
40 | public int port;
41 | public string verifyKey;
42 | public long qqId;
43 |
44 | internal List Middlewares { get; } = new();
45 | public List Assemblies { get; } = new();
46 | public List EventHandlers { get; } = new();
47 | public TimeSpan autoSave;
48 |
49 | public BotStartupConfig(string verifyKey, long qqId, string host) : this(verifyKey, qqId, host, 80) { }
50 | public BotStartupConfig(string verifyKey, long qqId, int port) : this(verifyKey, qqId, "127.0.0.1", port) { }
51 | public BotStartupConfig(string verifyKey, long qqId, string host = "127.0.0.1", int port = 8080)
52 | {
53 | this.verifyKey = verifyKey;
54 | this.qqId = qqId;
55 | this.host = host;
56 | this.port = port;
57 | Assemblies.Add(Assembly.GetEntryAssembly()!);
58 | autoSave = TimeSpan.FromMinutes(5);
59 | }
60 |
61 | public BotStartupConfig AddMiddleware() where T : BotMiddlewareBase
62 | {
63 | Middlewares.Add(typeof(T));
64 | return this;
65 | }
66 |
67 | public BotStartupConfig AddMiddleware(Type type)
68 | {
69 | if (type.IsAssignableTo(typeof(BotMiddlewareBase)))
70 | {
71 | Middlewares.Add(type);
72 | }
73 | return this;
74 | }
75 |
76 | public BotStartupConfig AddAssembly(Assembly assem)
77 | {
78 | Assemblies.Add(assem);
79 | return this;
80 | }
81 |
82 | public BotStartupConfig AddEventHandler(BotEventHandlerBase handler)
83 | {
84 | EventHandlers.Add(handler);
85 | return this;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/CocoaFramework/Support/DataManager.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Maila. All rights reserved.
2 | // Licensed under the GNU AGPLv3
3 |
4 | using System;
5 | using System.Collections.Concurrent;
6 | using System.IO;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using Newtonsoft.Json;
10 |
11 | namespace Maila.Cocoa.Framework.Support
12 | {
13 | public static class DataManager
14 | {
15 | public static readonly string DataRoot = "data/";
16 |
17 | internal static bool SavingData => !savingStatus.IsEmpty;
18 |
19 | private static readonly ConcurrentDictionary savingStatus = new();
20 | private static readonly ConcurrentDictionary savingStatusLock = new();
21 |
22 | public static async void SaveData(string name, object? obj, bool indented = true)
23 | {
24 | var statusLock = savingStatusLock.GetOrAdd(name, _ => new(1));
25 | await statusLock.WaitAsync();
26 |
27 | if (savingStatus.TryGetValue(name, out var status))
28 | {
29 | savingStatus[name] = (true, obj);
30 | statusLock.Release();
31 | return;
32 | }
33 | else
34 | {
35 | savingStatus[name] = (false, null);
36 | }
37 |
38 | statusLock.Release();
39 |
40 | var path = $"{DataRoot}{name}.json";
41 | var directory = Path.GetDirectoryName(path);
42 | if (directory == null)
43 | {
44 | // Error: bad save path
45 | savingStatus.TryRemove(name, out _);
46 | return;
47 | }
48 |
49 | if (!Directory.Exists(directory))
50 | {
51 | Directory.CreateDirectory(directory);
52 | }
53 |
54 | var formatting = indented ? Formatting.Indented : Formatting.None;
55 | await File.WriteAllTextAsync(path, JsonConvert.SerializeObject(obj, formatting));
56 |
57 | await statusLock.WaitAsync();
58 | while (savingStatus.TryGetValue(name, out status) && status.valueUpdated)
59 | {
60 | savingStatus[name] = (false, null);
61 | statusLock.Release();
62 |
63 | await File.WriteAllTextAsync(path, JsonConvert.SerializeObject(status.value, formatting));
64 |
65 | await statusLock.WaitAsync();
66 | }
67 |
68 | savingStatus.TryRemove(name, out _);
69 | statusLock.Release();
70 | }
71 |
72 | public static async Task LoadData(string name)
73 | {
74 | while (savingStatus.ContainsKey(name))
75 | {
76 | await Task.Delay(10);
77 | }
78 |
79 | return File.Exists($"{DataRoot}{name}.json")
80 | ? JsonConvert.DeserializeObject(await File.ReadAllTextAsync($"{DataRoot}{name}.json"))
81 | : default;
82 | }
83 |
84 | public static async Task