# 写在前面

本教程介绍如何使用 Jie2GG 的 SDK 编写原生酷 Q 插件。编写出的插件可以在各种酷 Q 兼容框架上运行。 跟随教程最终将编写出一个复读机

# 相关资源

  • Visual Studio
  • SDK Github
  • cqp.cc

# 下载

  1. 安装 Visual Studio ,本教程使用的为 VS2022 。需要具备基础的 C# 开发环境
  2. 在上方 相关资源-SDK Github-Release 中下载最后一个版本,并解压 file

# 配置

# .net Framework 升级

  1. 在上一步解压出的文件中,打开 Native.sln ,这是工程的解决方案文件。

  2. 此时 VS 应当弹出弹窗,提示 .net Framework 4.5 不受支持 file

  3. 点击右侧的继续,将所有项目的框架版本更新至 .net Framework 4.8

  4. 弹出是否执行文本模板的提示,点击确定执行。此文本模板用于生成与酷 Q 框架对接的代码部分 file

  5. SDK 结构 file

    1. AppData.cs 插件公用部分
    2. CQExport.tt 文本模板,用于将 app.json 翻译为插件需要的代码
    3. app.json 插件的描述文本,插件名称、版本、声明的事件均在此定义
    4. CQMain.cs 事件注册入口,事件需要在这里注册才能被调用到
    5. Native.Sdk SDK 主体部分,事件参数描述、API 调用以及 Dll 方法原型都在这里
    6. Native.Tool 预先封装好的工具类,不需要时可以裁剪以减小插件文件大小
    7. 此处为预先实现的 Http 工具类、Ini 配置工具类以及 Sqlite 数据库工具类

# 配置 AppID

AppID 为插件的唯一标识符,部分兼容框架会以此作为插件标识,重复可能导致不可预料的后果。

  1. 右键 Native.Core-属性

  2. 编辑 程序集名称 为你需要的 AppID,按 Ctrl+S 保存 file

  3. AppID 拥有命名规则,但是由于酷 Q 已经不存在,所以也没那么严格,保证能用就行。大致规则是这样的:

    1. AppID 由两部分构成:个人网址倒写以及插件的名称
    2. 个人网址倒写:假如你的个人网站为 hellobaka.xyz ,那么倒写就是 xyz.hellobaka ;若没有个人网站可以使用酷 Q 的个人首页: cqp.me ,倒写也就是 me.cqp 。由于是通用网址,所以还需要加上作者的名称加以区分,也就变成了 me.cqp.xxx
    3. 插件名称:能够描述插件的作用。比如复读机可以叫做 Repeater ,词云可以叫做 WordCloud
    4. 连在一起便构成了 AppID: xyz.hellobaka.Repeater me.cqp.luohuaming.WordCloud

# 配置 app.json

此文件为描述插件的名称、版本、作者、描述,以及已经实现的事件、窗口、悬浮窗说明和使用到的权限 ID。 Json 文件对格式敏感,使用带有语法提示或者格式检查的编辑器进行修改。

# 主节点

1
2
3
4
5
6
7
8
{
"ret": 1,
"apiver": 9,
"name": "酷Q样例应用 for C#",
"version": "1.0.0",
"version_id": 1,
"author": "JieGG",
"description": "酷Q样例应用(V9应用机制)",
  • ret : 返回值。固定返回 1
  • apiver : API 版本号。固定返回 9.
  • name : 应用名称。该参数将被显示在酷 Q 的应用列表中
  • version : 版本号。使用 x.x.x 形式。该参数将被显示在酷 Q 的应用列表中
  • version_id : 版本号 ID。建议每次更新版本时该参数至少加 1.
  • author : 应用作者。 该参数将会被显示在酷 Q 的应用列表中
  • description : 应用描述。该参数将被显示在应用列表的详细信息处。建议在描述中添加触发应用的命令语句,方便用户上手。使用 \r\n 可以在应用描述中换行

# 事件节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"event": [
{
"id": 1,
"type": 21,
"name": "私聊消息处理",
"function": "_eventPrivateMsg",
"priority": 30000
},
{
"id": 2,
"type": 2,
"name": "群消息处理",
"function": "_eventGroupMsg",
"priority": 30000
},
...
  • id : 事件唯一 ID, 整数类型 (Int32), 每个事件的唯一标识

  • type : 事件类型,整数类型 (Int32), 用于标识每个事件在 SDK 中的实现方式 目前支持的事件列表如下:

    • 21 : 私聊消息 如:好友消息,群临时私聊,讨论组临时私聊,临时消息
    • 2 : 群聊消息
    • 4 : 讨论组消息
    • 11 : 群文件上传
    • 101 : 群管理员变动 如:群管理员增加,群管理员减少
    • 102 : 群成员减少 如:群成员退群,群成员被移除
    • 103 : 群成员增加 如:群成员入群,群成员被邀请
    • 104 : 群禁言处理 如:群成员被禁言,群成员被解除禁言
    • 201 : 好友已添加
    • 301 : 好友添加请求
    • 302 : 群添加请求
    • 1001 : 酷 Q 启动事件
    • 1002 : 酷 Q 退出事件
    • 1003 : 应用启用事件
    • 1004 : 应用停用事件
  • name : 事件名称,字符串类型 (String), 每个事件的事件名称,该参数会被显示在酷 Q 的事件优先级调整器中

  • function : 函数名称,字符串类型 (String), 事件在被酷 Q 调用时,约定好的函数名称。该函数将在 SDK 中定义

  • priority : 调用优先级,整数类型 (Int32), 事件在被酷 Q 调用时,将依照相同事件的优先级,以从小到大的顺序调用插件。即数值越大,优先级越低。目前支持以下几种优先级:

    • 10000 最高:监控类应用,无法拦截消息 (如:消息统计等)
    • 20000 高:消息控制类应用,可用于拦截消息 (如:机器人开关等)
    • 30000 一般:普通功能类应用 (如:天气查询,游戏类等)
    • 40000 低:聊天对话类应用

请按照实际情况选择优先级。如果一个事件需要不同的优先级执行,可以申请多个事件,并使用不同的 functionpriority 。Native.SDK 会根据 Json 的定义生成需要的函数

  • regex : 正则消息过滤器,Json 对象类型 (JObject), 其定义方式为子 Json 节点。在 regex 节点中,包含以下两个部分:
    • Key : 标识组,Json 数组类型 (JArray), 内部的元素由 "字符串 (String)" 组成,表示随后在 SDK 中获取该结果时使用的 id
    • expression : 正则表达式组,Json 数组类型 (JArray), 内部元素由 "字符串 (String)" 组成,表示酷 Q 用来匹配消息的正则表达式。

# 菜单节点

1
2
3
4
5
6
7
8
9
10
"menu": [
{
"name": "设置A", //菜单名称
"function": "_menuA" //菜单对应函数
},
{
"name": "设置B",
"function": "_menuB"
}
],
  • name : 菜单名称,字符串类型 (String), 该字符串是显示在酷 Q 应用管理器菜单按钮下的项目,一个 Json 表示一个项目
  • function : 函数名称,字符串类型 (String), 事件在被酷 Q 调用时,约定好的函数名称。该函数将在 SDK 中定义

# 悬浮窗节点

此节点几乎很少被使用,框架也很少实现,可留空

# 权限信息

1
2
3
4
5
6
7
8
9
10
11
"auth": [
//20, //[敏感]取Cookies getCookies / getCsrfToken
//30, //接收语音 getRecord
101, //发送群消息 sendGroupMsg
103, //发送讨论组消息 sendDiscussMsg
106, //发送私聊消息 sendPrivateMsg
//110, //[敏感]发送赞 sendLike
120, //置群员移除 setGroupKick
121, //置群员禁言 setGroupBan
122, //置群管理员 setGroupAdmin
...

此处填写本插件需要使用的权限信息,不需要的权限删除

# 示例

本插件仅需使用 群消息处理 事件以及 发送群消息 权限,故修改后的 json 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"ret": 1,
"apiver": 9,
"name": "复读机示例",
"version": "1.0.0",
"version_id": 1,
"author": "Hellobaka",
"description": "复读复读复读",
"event": [
{
"id": 2,
"type": 2,
"name": "群消息处理",
"function": "_eventGroupMsg",
"priority": 30000
}
],
"menu": [
],
"status": [
],
"auth": [
101
]
}

部分框架不支持注释,建议还是删除所有注释

# 执行文本模板

file

# 尝试编译

在修改完 AppID 与 app.json 后,尝试编译一下项目,看看有没有报错。 file

file 有类似输出即可认为通过,接下来就是编写插件逻辑部分

# 编写逻辑

# 创建 Code 项目

建议将插件逻辑与公用模块分开,故重新创建一个类库

# 创建项目

file
file
file

# 引用项目

新生成的项目需要引用 Sdk 项目

  1. file
  2. file
  3. 点击确定完成

# 实现接口

我们为了增加可维护性,将默认的 Class1.cs 重命名为 Event_GroupMessage.cs 标识这个文件内实现了 GroupMessage 事件

  1. 之后打开这个文件,在 Using 部分添加引用

    1
    2
    using Native.Sdk.Cqp.EventArgs;
    using Native.Sdk.Cqp.Interface;
  2. 在类名后添加接口 IGroupMessage ,并添加默认接口实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    using Native.Sdk.Cqp.EventArgs;
    using Native.Sdk.Cqp.Interface;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    namespace xyz.Hellobaka.Repeater.Code
    {
    public class Event_GroupMessage : IGroupMessage
    {
    public void GroupMessage(object sender, CQGroupMessageEventArgs e)
    {
    throw new NotImplementedException();
    }
    }
    }

# 注册接口

  1. Core 项目添加对 Code 项目的引用
  2. 打开 Native.Core - CQMain.cs
  3. 添加 using 引用, using xyz.Hellobaka.Repeater.Code;
  4. 添加以下代码以实现接口注册
1
2
3
4
5
6
7
8
9
10
11
public class CQMain
{
/// <summary>
/// 在应用被加载时将调用此方法进行事件注册, 请在此方法里向 <see cref="IUnityContainer"/> 容器中注册需要使用的事件
/// </summary>
/// <param name="container">用于注册的 IOC 容器 </param>
public static void Register (IUnityContainer unityContainer)
{
unityContainer.RegisterType<IGroupMessage, Event_GroupMessage>("群消息处理");// 此处的名称需要与json中的事件字段对应
}
}

# 编写逻辑

复读机,逻辑很简单,将收到的消息再发出去就是复读。 打开在上一步中创建的 Event_GroupMessage.cs 文件,在实现的接口部分添加以下代码:

1
2
3
4
5
6
public void GroupMessage(object sender, CQGroupMessageEventArgs e)
{
long groupId = e.FromGroup.Id;
e.CQApi.SendGroupMessage(groupId, e.Message.Text);
e.Handler = true;
}

上面的代码即是我们刚才介绍的逻辑在代码上的实现,下面是讲解:

  1. 参数 e ,为群消息事件的参数

  2. e.FromGroup 是消息来源的群,里面包含了群号。 e.FromGroup.Id 即是群号

  3. e.Message 是消息的类,里面包含了消息的文本、CQ 码与消息类型 (图片、语音)。 .Text 即是这个消息的文本

  4. e.CQApi 表示根据这个参数创建的 CQApi 实例。此实例用于调用框架方法,比如:发送群消息、发送私聊消息、处理好友添加请求。此处使用了 SendGroupMessage 来发送群组消息,参数为群号与消息内容。

    注: CQApi 实例创建需要传递一个 Authcode ,此 Authcode 是由框架传递而来,不应当由用户创建,故需要使用参数传递来的或者使用在 AppData 中储存的 CQAPI 实例

  5. e.Handler 表示了框架如何处理这个消息及以后的消息。

    • True 表示阻塞,本插件处理完成之后其他插件不再处理这个消息。
    • False 表示放行,本插件处理完成之后,其他插件仍会继续处理。

我们将这部分逻辑扩充一下,插件在返回消息时会发送群号以及发送者的 ID,之后再接上内容:

1
2
3
4
5
public void GroupMessage(object sender, CQGroupMessageEventArgs e)
{
e.FromGroup.SendGroupMessage($"群: {e.FromGroup} QQ: {e.FromQQ} 发送了以下消息: {e.Message.Text}");
e.Handler = true;
}

好像短了很多呢。下面是讲解:

  1. e.FromGroup 是消息来源的群,里面包含了群号。SDK 对这个类进行了二次封装,添加了直接根据这个类调用 API 的方法,故可以直接 .SendGroupMessage 来发送消息
  2. e.Message 是消息的类,里面包含了消息的文本、CQ 码与消息类型 (图片、语音)。 .Text 即是这个消息的文本
  3. $""内插字符串
  4. SDK 对常用类实现了转换方法,当需要时类可以不添加显式转换,会隐式转换为 longstring 。故此处内插字符串时不需要 .Id

# 生成发布

  1. Native.Core 项目重新生成

  2. 在没有错误的情况下,即可认为是发布完成

  3. 打开生成目录, Native.Core\bin\x86\Debug\xyz.Hellobaka.Repeater file

  4. app.dllapp.json 即是生成的插件,文件没有必要叫这个,可以改成自己的 AppID。但是要求 dll 文件与 json 文件同名

  5. 将插件放在框架中,进行功能测试:

    注意,由于插件会复读所有收到的消息,如果你的机器人消息繁忙可能会引起一些问题,所以建议选择消息少的机器人或者对代码进行改造,添加群来源的限制:

1
2
3
4
5
6
7
8
public void GroupMessage(object sender, CQGroupMessageEventArgs e)
{
if (e.FromGroup == 123456789)
{
e.FromGroup.SendGroupMessage($"群: {e.FromGroup} QQ: {e.FromQQ} 发送了以下消息: {e.Message.Text}");
e.Handler = true;
}
}

file