├── .gitignore
├── LICENSE
├── QQChannelBot
├── Bot
│ ├── API
│ │ ├── APIList.cs
│ │ ├── WebSocket.cs
│ │ ├── 公告.cs
│ │ ├── 子频道.cs
│ │ ├── 子频道权限.cs
│ │ ├── 成员.cs
│ │ ├── 接口权限.cs
│ │ ├── 日程.cs
│ │ ├── 消息.cs
│ │ ├── 用户.cs
│ │ ├── 禁言.cs
│ │ ├── 私信.cs
│ │ ├── 音频.cs
│ │ ├── 频道.cs
│ │ └── 频道身份组.cs
│ ├── BotClient.cs
│ ├── BotCommandRegister.cs
│ ├── BotHttpClient.cs
│ ├── BotWebSocket.cs
│ ├── Command.cs
│ ├── Identity.cs
│ ├── InfoSDK.cs
│ ├── Log.cs
│ ├── MessageCenter.cs
│ ├── Sender.cs
│ ├── SocketEvent
│ │ ├── Intent.cs
│ │ └── Opcode.cs
│ └── StatusCode
│ │ └── StatusCodes.cs
├── Models
│ ├── APIPermissions.cs
│ ├── Announces.cs
│ ├── AudioControl.cs
│ ├── Channel.cs
│ ├── ChannelPermissions.cs
│ ├── Emoji.cs
│ ├── Guild.cs
│ ├── JsonConverters.cs
│ ├── Member.cs
│ ├── Message.cs
│ ├── MessageReaction.cs
│ ├── Mute.cs
│ ├── Role.cs
│ ├── Schedule.cs
│ ├── User.cs
│ └── WebSocketLimit.cs
├── MsgHelper
│ ├── MsgArk23.cs
│ ├── MsgArk24.cs
│ ├── MsgArk34.cs
│ ├── MsgArk37.cs
│ ├── MsgDescImg.cs
│ ├── MsgEmbed.cs
│ ├── MsgImage.cs
│ ├── MsgTag.cs
│ └── MsgText.cs
├── QQChannelBot.csproj
├── Tools
│ ├── Benchmarks.cs
│ ├── JsonHelper.cs
│ ├── StringTools.cs
│ └── Unicoder.cs
└── logo.png
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | bin/
3 | obj/
4 | *.sln
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Antecer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/WebSocket.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using System.Text.Json;
3 | using QQChannelBot.Models;
4 |
5 | namespace QQChannelBot.Bot
6 | {
7 | public partial class BotClient
8 | {
9 | ///
10 | /// 获取通用 WSS 接入点
11 | ///
12 | /// 一个用于连接 websocket 的地址
13 | public async Task GetWssUrl()
14 | {
15 | HttpResponseMessage? respone = await HttpSendAsync($"{ApiOrigin}/gateway");
16 | JsonElement? res = respone == null ? null : await respone.Content.ReadFromJsonAsync();
17 | return res?.GetProperty("url").GetString();
18 | }
19 | ///
20 | /// 获取带分片 WSS 接入点
21 | ///
22 | /// 详情查阅: QQ机器人文档
23 | ///
24 | ///
25 | /// 一个用于连接 websocket 的地址。
同时返回建议的分片数,以及目前连接数使用情况。
26 | public async Task GetWssUrlWithShared()
27 | {
28 | HttpResponseMessage? respone = await HttpSendAsync($"{ApiOrigin}/gateway/bot");
29 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/公告.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 创建频道全局公告
10 | ///
11 | /// 频道Id
12 | /// 消息所在子频道Id
13 | /// 消息Id
14 | ///
15 | ///
16 | public async Task CreateAnnouncesGlobalAsync(string guild_id, string channel_id, string message_id, Sender? sender = null)
17 | {
18 | BotAPI api = APIList.创建频道公告;
19 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, JsonContent.Create(new { channel_id, message_id }), sender);
20 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
21 | }
22 | ///
23 | /// 删除频道全局公告
24 | ///
25 | /// 频道Id
26 | /// 用于创建公告的消息Id
27 | ///
28 | ///
29 | public async Task DeleteAnnouncesGlobalAsync(string guild_id, string message_id = "all", Sender? sender = null)
30 | {
31 | BotAPI api = APIList.删除频道公告;
32 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{message_id}", message_id), api.Method, null, sender);
33 | return respone?.IsSuccessStatusCode ?? false;
34 | }
35 | ///
36 | /// 创建子频道公告
37 | ///
38 | /// 子频道Id
39 | /// 消息Id
40 | ///
41 | ///
42 | public async Task CreateAnnouncesAsync(string channel_id, string message_id, Sender? sender = null)
43 | {
44 | BotAPI api = APIList.创建子频道公告;
45 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id), api.Method, channel_id == null ? null : JsonContent.Create(new { message_id }), sender);
46 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
47 | }
48 | ///
49 | /// 删除子频道公告
50 | ///
51 | /// 子频道Id
52 | /// 用于创建公告的消息Id
53 | ///
54 | ///
55 | public async Task DeleteAnnouncesAsync(string channel_id, string message_id = "all", Sender? sender = null)
56 | {
57 | BotAPI api = APIList.删除子频道公告;
58 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{message_id}", message_id), api.Method, null, sender);
59 | return respone?.IsSuccessStatusCode ?? false;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/子频道.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取子频道详情
10 | ///
11 | /// 子频道Id
12 | ///
13 | /// Channel?
14 | public async Task GetChannelAsync(string channel_id, Sender? sender = null)
15 | {
16 | BotAPI api = APIList.获取子频道详情;
17 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id), api.Method, null, sender);
18 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
19 | }
20 | ///
21 | /// 获取频道下的子频道列表
22 | ///
23 | /// 频道Id
24 | /// 筛选子频道类型
25 | /// 筛选子频道子类型
26 | ///
27 | ///
28 | public async Task?> GetChannelsAsync(string guild_id, ChannelType? channelType = null, ChannelSubType? channelSubType = null, Sender? sender = null)
29 | {
30 | BotAPI api = APIList.获取子频道列表;
31 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, null, sender);
32 | List? channels = respone == null ? null : await respone.Content.ReadFromJsonAsync?>();
33 | if (channels != null)
34 | {
35 | if (channelType != null) channels = channels.Where(channel => channel.Type == channelType).ToList();
36 | if (channelSubType != null) channels = channels.Where(channel => channel.SubType == channelSubType).ToList();
37 | }
38 | return channels;
39 | }
40 | ///
41 | /// 创建子频道(仅私域可用)
42 | ///
43 | /// 用于创建子频道的对象(需提前填充必要字段)
44 | ///
45 | ///
46 | public async Task CreateChannelAsync(Channel channel, Sender? sender = null)
47 | {
48 | BotAPI api = APIList.创建子频道;
49 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", channel.GuildId), api.Method, JsonContent.Create(channel), sender);
50 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
51 | }
52 | ///
53 | /// 修改子频道(仅私域可用)
54 | ///
55 | /// 修改属性后的子频道对象
56 | ///
57 | ///
58 | public async Task EditChannelAsync(Channel channel, Sender? sender = null)
59 | {
60 | BotAPI api = APIList.修改子频道;
61 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel.Id), api.Method, JsonContent.Create(channel), sender);
62 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
63 | }
64 | ///
65 | /// 删除指定子频道(仅私域可用)
66 | ///
67 | /// 要删除的子频道Id
68 | ///
69 | ///
70 | public async Task DeleteChannelAsync(string channel_id, Sender? sender = null)
71 | {
72 | BotAPI api = APIList.删除子频道;
73 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id), api.Method, null, sender);
74 | return respone?.IsSuccessStatusCode ?? false;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/子频道权限.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取用户在指定子频道的权限
10 | ///
11 | /// 子频道Id
12 | /// 用户Id
13 | ///
14 | ///
15 | public async Task GetChannelPermissionsAsync(string channel_id, string user_id, Sender? sender = null)
16 | {
17 | BotAPI api = APIList.获取子频道用户权限;
18 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{user_id}", user_id), api.Method, null, sender);
19 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
20 | }
21 | ///
22 | /// 修改用户在指定子频道的权限
23 | ///
24 | /// 子频道Id
25 | /// 用户Id
26 | /// 添加的权限
27 | /// 删除的权限
28 | ///
29 | ///
30 | public async Task EditChannelPermissionsAsync(string channel_id, string user_id, string add = "0", string remove = "0", Sender? sender = null)
31 | {
32 | BotAPI api = APIList.修改子频道用户权限;
33 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{user_id}", user_id), api.Method, JsonContent.Create(new
34 | {
35 | add,
36 | remove
37 | }), sender);
38 | return respone?.IsSuccessStatusCode ?? false;
39 | }
40 | ///
41 | /// 获取指定身份组在指定子频道的权限
42 | ///
43 | /// 子频道Id
44 | /// 身份组Id
45 | ///
46 | ///
47 | public async Task GetMemberChannelPermissionsAsync(string channel_id, string role_id, Sender? sender = null)
48 | {
49 | BotAPI api = APIList.获取子频道身份组权限;
50 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{role_id}", role_id), api.Method, null, sender);
51 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
52 | }
53 | ///
54 | /// 修改指定身份组在指定子频道的权限
55 | ///
56 | /// 子频道Id
57 | /// 身份组Id
58 | /// 添加的权限
59 | /// 删除的权限
60 | ///
61 | ///
62 | public async Task EditMemberChannelPermissionsAsync(string channel_id, string role_id, string add = "0", string remove = "0", Sender? sender = null)
63 | {
64 | BotAPI api = APIList.修改子频道身份组权限;
65 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{role_id}", role_id), api.Method, JsonContent.Create(new
66 | {
67 | add,
68 | remove
69 | }), sender);
70 | return respone?.IsSuccessStatusCode ?? false;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/成员.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取频道成员列表(仅私域可用)
10 | ///
11 | /// guild_id - 频道Id
12 | /// limit - 分页大小1-1000(默认值10)
13 | /// after - 上次回包中最后一个Member的用户ID,首次请求填0
14 | ///
15 | ///
16 | /// 频道Id
17 | /// 分页大小1-1000(默认值10)
18 | /// 上次回包中最后一个Member的用户ID,首次请求填"0"
19 | ///
20 | ///
21 | public async Task?> GetGuildMembersAsync(string guild_id, int limit = 10, string? after = null, Sender? sender = null)
22 | {
23 | BotAPI api = APIList.获取频道成员列表;
24 | HttpResponseMessage? respone = await HttpSendAsync($"{api.Path.Replace("{guild_id}", guild_id)}?limit={limit}&after={after ?? "0"}", api.Method, null, sender);
25 | return respone == null ? null : await respone.Content.ReadFromJsonAsync?>();
26 | }
27 | ///
28 | /// 获取频道成员详情
29 | ///
30 | /// 频道Id
31 | /// 成员用户Id
32 | ///
33 | ///
34 | public async Task GetMemberAsync(string guild_id, string user_id, Sender? sender = null)
35 | {
36 | BotAPI api = APIList.获取成员详情;
37 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{user_id}", user_id), api.Method, null, sender);
38 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
39 | }
40 | ///
41 | /// 删除指定频道成员(仅私域可用)
42 | ///
43 | /// 频道Id
44 | /// 用户Id
45 | ///
46 | ///
47 | public async Task DeleteGuildMemberAsync(string guild_id, string user_id, Sender? sender = null)
48 | {
49 | BotAPI api = APIList.删除频道成员;
50 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{user_id}", user_id), api.Method, null, sender);
51 | return respone?.IsSuccessStatusCode ?? false;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/接口权限.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取频道可用权限列表
10 | ///
11 | /// 获取机器人在频道 guild_id 内可以使用的权限列表
12 | ///
13 | ///
14 | /// 频道Id
15 | ///
16 | ///
17 | public async Task?> GetGuildPermissionsAsync(string guild_id, Sender? sender = null)
18 | {
19 | BotAPI api = APIList.获取频道可用权限列表;
20 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, null, sender);
21 | APIPermissions? Permissions = respone == null ? null : await respone.Content.ReadFromJsonAsync();
22 | return Permissions?.List;
23 | }
24 | ///
25 | /// 创建频道 API 接口权限授权链接
26 | ///
27 | /// 频道Id
28 | /// 子频道Id
29 | /// 权限需求标识对象
30 | /// 机器人申请对应的 API 接口权限后可以使用功能的描述
31 | ///
32 | ///
33 | public async Task SendPermissionDemandAsync(string guild_id, string channel_id, APIPermissionDemandIdentify api_identify, string desc = "", Sender? sender = null)
34 | {
35 | BotAPI api = APIList.创建频道接口授权链接;
36 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, JsonContent.Create(new
37 | {
38 | channel_id,
39 | api_identify,
40 | desc
41 | }), sender);
42 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/日程.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取频道日程列表
10 | ///
11 | /// 获取某个日程子频道里中当天的日程列表;
12 | /// 若带了参数 since,则返回结束时间在 since 之后的日程列表;
13 | /// 若未带参数 since,则默认返回当天的日程列表。
14 | ///
15 | ///
16 | /// 日程子频道Id
17 | /// 筛选日程开始时间(默认为当日全天)
18 | ///
19 | /// List<Schedule>?
20 | public async Task?> GetSchedulesAsync(string channel_id, DateTime? since = null, Sender? sender = null)
21 | {
22 | BotAPI api = APIList.获取频道日程列表;
23 | string param = since == null ? "" : $"?since={new DateTimeOffset(since.Value).ToUnixTimeMilliseconds()}";
24 | HttpResponseMessage? respone = await HttpSendAsync($"{api.Path.Replace("{channel_id}", channel_id)}{param}", api.Method, null, sender);
25 | return respone == null ? null : await respone.Content.ReadFromJsonAsync?>();
26 | }
27 | ///
28 | /// 获取日程详情
29 | ///
30 | /// 日程子频道Id
31 | /// 日程Id
32 | ///
33 | /// 目标 Schedule 对象
34 | public async Task GetScheduleAsync(string channel_id, string schedule_id, Sender? sender = null)
35 | {
36 | BotAPI api = APIList.获取日程详情;
37 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{schedule_id}", schedule_id), api.Method, null, sender);
38 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
39 | }
40 | ///
41 | /// 创建日程
42 | ///
43 | /// 要求操作人具有"管理频道"的权限,如果是机器人,则需要将机器人设置为管理员。
44 | /// 创建成功后,返回创建成功的日程对象。
45 | /// 日程开始时间必须大于当前时间。
46 | ///
47 | ///
48 | /// 日程子频道Id
49 | /// 新的日程对象,不需要带Id
50 | ///
51 | /// 新创建的 Schedule 对象
52 | public async Task CreateScheduleAsync(string channel_id, Schedule schedule, Sender? sender = null)
53 | {
54 | BotAPI api = APIList.创建日程;
55 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id), api.Method, JsonContent.Create(new { schedule }), sender);
56 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
57 | }
58 | ///
59 | /// 修改日程
60 | ///
61 | /// 要求操作人具有"管理频道"的权限,如果是机器人,则需要将机器人设置为管理员。
62 | /// 修改成功后,返回修改后的日程对象。
63 | ///
64 | ///
65 | /// 日程子频道Id
66 | /// 修改后的日程对象
67 | ///
68 | /// 修改后的 Schedule 对象
69 | public async Task EditScheduleAsync(string channel_id, Schedule schedule, Sender? sender = null)
70 | {
71 | BotAPI api = APIList.修改日程;
72 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{schedule_id}", schedule.Id), api.Method, JsonContent.Create(new { schedule }), sender);
73 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
74 | }
75 | ///
76 | /// 删除日程
77 | ///
78 | /// 要求操作人具有"管理频道"的权限,如果是机器人,则需要将机器人设置为管理员。
79 | ///
80 | ///
81 | /// 日程子频道Id
82 | /// 日程对象
83 | /// 这里是为了获取日程Id,为了防错设计为传递日程对象
84 | ///
85 | /// HTTP 状态码 204
86 | public async Task DeleteScheduleAsync(string channel_id, Schedule schedule, Sender? sender = null)
87 | {
88 | BotAPI api = APIList.删除日程;
89 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{schedule_id}", schedule.Id), api.Method, null, sender);
90 | return respone?.IsSuccessStatusCode ?? false;
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/消息.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取指定消息
10 | ///
11 | /// 子频道Id
12 | /// 消息Id
13 | ///
14 | ///
15 | public async Task GetMessageAsync(string channel_id, string message_id, Sender? sender = null)
16 | {
17 | BotAPI api = APIList.获取指定消息;
18 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{message_id}", message_id), api.Method, null, sender);
19 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
20 | }
21 | ///
22 | /// 获取消息列表(2022年1月29日暂未开通)
23 | ///
24 | /// 作为坐标的消息(需要消息Id和子频道Id)
25 | /// 分页大小(1-20)
26 | /// 拉取类型(默认拉取最新消息)
27 | ///
28 | ///
29 | public async Task?> GetMessagesAsync(Message message, int limit = 20, GetMsgTypesEnum? typesEnum = GetMsgTypesEnum.latest, Sender? sender = null)
30 | {
31 | BotAPI api = APIList.获取消息列表;
32 | string type = typesEnum == null ? "" : $"&type={typesEnum}";
33 | HttpResponseMessage? respone = await HttpSendAsync($"{api.Path.Replace("{channel_id}", message.ChannelId)}?limit={limit}&id={message.Id}{type}", api.Method, null, sender);
34 | return respone == null ? null : await respone.Content.ReadFromJsonAsync?>();
35 | }
36 | ///
37 | /// 发送消息
38 | ///
39 | /// 要求操作人在该子频道具有"发送消息"的权限
40 | /// 发送成功之后,会触发一个创建消息的事件
41 | /// 被动回复消息有效期为 5 分钟
42 | /// 主动推送消息每个子频道限 2 条/天
43 | /// 发送消息接口要求机器人接口需要链接到websocket gateway 上保持在线状态
44 | ///
45 | ///
46 | /// 子频道Id
47 | /// 消息对象
48 | ///
49 | ///
50 | public async Task SendMessageAsync(string channel_id, MessageToCreate message, Sender? sender = null)
51 | {
52 | BotAPI api = APIList.发送消息;
53 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id), api.Method, JsonContent.Create(message), sender);
54 | Message? result = respone == null ? null : await respone.Content.ReadFromJsonAsync();
55 | return LastMessage(result, true);
56 | }
57 | ///
58 | /// 撤回消息
59 | ///
60 | /// 子频道Id
61 | /// 消息Id
62 | ///
63 | ///
64 | public async Task DeleteMessageAsync(string channel_id, string message_id, Sender? sender = null)
65 | {
66 | BotAPI api = APIList.撤回消息;
67 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id).Replace("{message_id}", message_id), api.Method, null, sender);
68 | return respone?.IsSuccessStatusCode ?? false;
69 | }
70 | ///
71 | /// 撤回目标用户在当前子频道发出的最后一条消息
72 | ///
73 | /// 需要传入指令发出者的消息对象
74 | /// 用于检索指令发出者所在频道信息
75 | ///
76 | ///
77 | ///
78 | /// 被撤回消息的目标用户信息
79 | /// 需要:message.GuildId、message.ChannelId、message.Author.Id
80 | ///
81 | ///
82 | ///
83 | public async Task DeleteLastMessageAsync(Message? masterMessage, Sender? sender = null)
84 | {
85 | Message? lastMessage = LastMessage(masterMessage);
86 | return lastMessage == null ? null : await DeleteMessageAsync(lastMessage.ChannelId, lastMessage.Id, sender);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/用户.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取当前用户(机器人)信息
10 | /// 此API无需任何权限
11 | ///
12 | ///
13 | /// 当前用户对象
14 | public async Task GetMeAsync(Sender? sender = null)
15 | {
16 | BotAPI api = APIList.获取用户详情;
17 | HttpResponseMessage? respone = await HttpSendAsync(api.Path, api.Method, null, sender);
18 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
19 | }
20 | ///
21 | /// 获取当前用户(机器人)已加入频道列表
22 | /// 此API无需任何权限
23 | ///
24 | /// 频道Id(作为拉取下一次列表的分页坐标使用)
25 | /// 数据拉取方向(true-向前查找 | false-向后查找)
26 | /// 数据分页(默认每次拉取100条)
27 | ///
28 | ///
29 | public async Task?> GetMeGuildsAsync(string? guild_id = null, bool route = false, int limit = 100, Sender? sender = null)
30 | {
31 | BotAPI api = APIList.获取用户频道列表;
32 | guild_id = string.IsNullOrWhiteSpace(guild_id) ? "" : $"&{(route ? "before" : "after")}={guild_id}";
33 | HttpResponseMessage? respone = await HttpSendAsync($"{api.Path}?limit={limit}{guild_id}", api.Method, null, sender);
34 | return respone == null ? null : await respone.Content.ReadFromJsonAsync?>();
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/禁言.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 频道全局禁言
10 | ///
11 | /// muteTime - 禁言时间:
12 | /// mute_end_timestamp 禁言到期时间戳,绝对时间戳,单位:秒
13 | /// mute_seconds 禁言多少秒
14 | ///
15 | ///
16 | /// 频道Id
17 | /// 禁言模式
18 | ///
19 | ///
20 | public async Task MuteGuildAsync(string guild_id, MuteTime muteTime, Sender? sender = null)
21 | {
22 | BotAPI api = APIList.禁言全员;
23 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, JsonContent.Create(muteTime), sender);
24 | return respone?.IsSuccessStatusCode ?? false;
25 | }
26 | ///
27 | /// 频道指定成员禁言
28 | ///
29 | /// muteTime - 禁言时间:
30 | /// mute_end_timestamp 禁言到期时间戳,绝对时间戳,单位:秒
31 | /// mute_seconds 禁言多少秒
32 | ///
33 | ///
34 | /// 频道Id
35 | /// 成员Id
36 | /// 禁言时间
37 | ///
38 | ///
39 | public async Task MuteMemberAsync(string guild_id, string user_id, MuteTime muteTime, Sender? sender = null)
40 | {
41 | BotAPI api = APIList.禁言指定成员;
42 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{user_id}", user_id), api.Method, JsonContent.Create(muteTime), sender);
43 | return respone?.IsSuccessStatusCode ?? false;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/私信.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 创建私信会话
10 | ///
11 | /// 接收者Id
12 | /// 源频道Id
13 | ///
14 | ///
15 | public async Task CreateDMSAsync(string recipient_id, string source_guild_id, Sender sender)
16 | {
17 | BotAPI api = APIList.创建私信会话;
18 | HttpResponseMessage? respone = await HttpSendAsync(api.Path, api.Method, JsonContent.Create(new { recipient_id, source_guild_id }), sender);
19 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
20 | }
21 | ///
22 | /// 发送私信
23 | /// 用于发送私信消息,前提是已经创建了私信会话。
24 | ///
25 | /// 私信频道Id
26 | /// 消息对象
27 | ///
28 | ///
29 | public async Task SendPMAsync(string guild_id, MessageToCreate message, Sender? sender = null)
30 | {
31 | BotAPI api = APIList.发送私信;
32 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, JsonContent.Create(message), sender);
33 | Message? result = respone == null ? null : await respone.Content.ReadFromJsonAsync();
34 | return LastMessage(result, true);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/音频.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 音频控制
10 | ///
11 | /// 子频道Id
12 | /// 音频对象
13 | ///
14 | ///
15 | public async Task AudioControlAsync(string channel_id, AudioControl audioControl, Sender? sender = null)
16 | {
17 | BotAPI api = APIList.音频控制;
18 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{channel_id}", channel_id), api.Method, JsonContent.Create(audioControl), sender);
19 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/频道.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取频道详情
10 | ///
11 | /// 频道Id
12 | ///
13 | /// Guild?
14 | public async Task GetGuildAsync(string guild_id, Sender? sender = null)
15 | {
16 | BotAPI api = APIList.获取频道详情;
17 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, null, sender);
18 | return respone == null ? null : await respone.Content.ReadFromJsonAsync();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/API/频道身份组.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Json;
2 | using QQChannelBot.Models;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | public partial class BotClient
7 | {
8 | ///
9 | /// 获取频道身份组列表
10 | ///
11 | /// 频道Id
12 | ///
13 | ///
14 | public async Task?> GetRolesAsync(string guild_id, Sender? sender = null)
15 | {
16 | BotAPI api = APIList.获取频道身份组列表;
17 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, null, sender);
18 | var result = respone == null ? null : await respone.Content.ReadFromJsonAsync();
19 | return result?.Roles;
20 | }
21 | ///
22 | /// 创建频道身份组
23 | ///
24 | /// 频道Id
25 | /// 携带需要设置的字段内容
26 | /// 标识需要设置哪些字段,若不填则根据Info自动推测
27 | ///
28 | ///
29 | public async Task CreateRoleAsync(string guild_id, Info info, Filter? filter = null, Sender? sender = null)
30 | {
31 | BotAPI api = APIList.创建频道身份组;
32 | filter ??= new Filter(!string.IsNullOrWhiteSpace(info.Name), info.Color != null, info.Hoist ?? false);
33 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id), api.Method, JsonContent.Create(new { filter, info }), sender);
34 | var result = respone == null ? null : await respone.Content.ReadFromJsonAsync();
35 | return result?.Role;
36 |
37 | }
38 | ///
39 | /// 修改频道身份组
40 | ///
41 | /// 频道Id
42 | /// 角色Id
43 | /// 携带需要修改的字段内容
44 | /// 标识需要设置哪些字段,若不填则根据Info自动推测
45 | ///
46 | ///
47 | public async Task EditRoleAsync(string guild_id, string role_id, Info info, Filter? filter = null, Sender? sender = null)
48 | {
49 | BotAPI api = APIList.修改频道身份组;
50 | filter ??= new Filter(!string.IsNullOrWhiteSpace(info.Name), info.Color != null, info.Hoist ?? false);
51 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{role_id}", role_id), api.Method, JsonContent.Create(new { filter, info }), sender);
52 | var result = respone == null ? null : await respone.Content.ReadFromJsonAsync();
53 | return result?.Role;
54 | }
55 | ///
56 | /// 删除频道身份组
57 | /// HTTP状态码 204 表示成功
58 | ///
59 | /// 频道Id
60 | /// 身份Id
61 | ///
62 | ///
63 | public async Task DeleteRoleAsync(string guild_id, string role_id, Sender? sender = null)
64 | {
65 | BotAPI api = APIList.删除频道身份组;
66 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{role_id}", role_id), api.Method, null, sender);
67 | return respone?.IsSuccessStatusCode ?? false;
68 | }
69 | ///
70 | /// 增加频道身份组成员
71 | ///
72 | /// 需要使用的 token 对应的用户具备增加身份组成员权限。如果是机器人,要求被添加为管理员。
73 | /// 如果要增加的身份组ID是(5-子频道管理员),需要增加 channel_id 来指定具体是哪个子频道。
74 | ///
75 | ///
76 | /// 频道Id
77 | /// 用户Id
78 | /// 身份组Id
79 | /// 子频道Id
80 | ///
81 | ///
82 | public async Task AddRoleMemberAsync(string guild_id, string user_id, string role_id, string? channel_id = null, Sender? sender = null)
83 | {
84 | BotAPI api = APIList.添加频道身份组成员;
85 | HttpContent? httpContent = channel_id == null ? null : JsonContent.Create(new { channel = new Channel { Id = channel_id } });
86 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{user_id}", user_id).Replace("{role_id}", role_id), api.Method, httpContent, sender);
87 | return respone?.IsSuccessStatusCode ?? false;
88 | }
89 | ///
90 | /// 删除频道身份组成员
91 | ///
92 | /// 需要使用的 token 对应的用户具备删除身份组成员权限。如果是机器人,要求被添加为管理员。
93 | /// 如果要删除的身份组ID是(5-子频道管理员),需要设置 channel_id 来指定具体是哪个子频道。
94 | /// 详情查阅 QQ机器人文档
95 | ///
96 | ///
97 | /// 频道Id
98 | /// 要加入身份组的用户Id
99 | /// 身份组Id
100 | /// 子频道Id
101 | ///
102 | ///
103 | public async Task DeleteRoleMemberAsync(string guild_id, string user_id, string role_id, string? channel_id = null, Sender? sender = null)
104 | {
105 | BotAPI api = APIList.删除频道身份组成员;
106 | HttpContent? httpContent = channel_id == null ? null : JsonContent.Create(new { channel = new Channel { Id = channel_id } });
107 | HttpResponseMessage? respone = await HttpSendAsync(api.Path.Replace("{guild_id}", guild_id).Replace("{user_id}", user_id).Replace("{role_id}", role_id), api.Method, httpContent, sender);
108 | return respone?.IsSuccessStatusCode ?? false;
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/BotClient.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Net.WebSockets;
3 | using System.Text.Json;
4 | using System.Text.RegularExpressions;
5 | using QQChannelBot.Bot.SocketEvent;
6 | using QQChannelBot.Bot.StatusCode;
7 | using QQChannelBot.Models;
8 |
9 | namespace QQChannelBot.Bot
10 | {
11 | ///
12 | /// 机器人对象
13 | ///
14 | /// 可选属性配置表:
15 | /// BotAccessInfo - 机器人鉴权登陆信息,见
16 | /// SadboxGuildId - 指定用于调试机器人的沙箱频道(DebugBot=true时有效)
17 | /// DebugBot - 指定机器人运行的模式[true:测试; false:正式];默认值=false
18 | /// Info - 机器人的 信息(在机器人鉴权通过后更新);默认值=null
19 | /// Members - 自动记录机器人在各频道内的身份组信息
20 | /// ReportApiError - 向前端消息发出者报告API错误[true:报告;false:静默];默认值=false
21 | /// SandBox - 机器人调用API的模式[true:沙箱;false:正式];默认值=false
22 | /// ApiOrigin - (只读) 获取机器人当前使用的ApiUrl
23 | /// Intents - 订阅频道事件,详见:;默认值=(GUILDS|GUILD_MEMBERS|AT_MESSAGES|GUILD_MESSAGE_REACTIONS)
24 | /// Guilds - 机器人已加入的频道列表
25 | ///
26 | ///
27 | public partial class BotClient
28 | {
29 | ///
30 | /// 记录机器人启动时间
31 | ///
32 | public static DateTime StartTime { get; private set; } = DateTime.Now;
33 |
34 | #region 可以监听的固定事件列表
35 | ///
36 | /// WebSocketClient连接后触发
37 | /// 等同于 WebSocket.OnOpen 事件
38 | ///
39 | public event Action? OnWebSocketConnected;
40 | ///
41 | /// WebSocketClient关闭后触发
42 | ///
43 | public event Action? OnWebSocketClosed;
44 | ///
45 | /// WebSocketClient发送数据前触发
46 | ///
47 | public event Action? OnWebSoketSending;
48 | ///
49 | /// WebSocketClient收到数据后触发
50 | ///
51 | public event Action? OnWebSocketReceived;
52 | ///
53 | /// 收到服务端推送的消息时触发
54 | ///
55 | public event Action? OnDispatch;
56 | ///
57 | /// 客户端发送心跳或收到服务端推送心跳时触发
58 | ///
59 | public event Action? OnHeartbeat;
60 | ///
61 | /// 客户端发送鉴权时触发
62 | ///
63 | public event Action? OnIdentify;
64 | ///
65 | /// 客户端恢复连接时触发
66 | ///
67 | public event Action? OnResume;
68 | ///
69 | /// 服务端通知客户端重新连接时触发
70 | ///
71 | public event Action? OnReconnect;
72 | ///
73 | /// 当identify或resume的时候,参数错误的时候触发
74 | ///
75 | public event Action? OnInvalidSession;
76 | ///
77 | /// 当客户端与网关建立ws连接的时候触发
78 | ///
79 | public event Action? OnHello;
80 | ///
81 | /// 客户端发送心跳被服务端接收后触发
82 | ///
83 | public event Action? OnHeartbeatACK;
84 | ///
85 | /// 鉴权连接成功后触发
86 | /// 注:此时获取的User对象只有3个属性 {id,username,bot}
87 | ///
88 | public event Action? OnReady;
89 | ///
90 | /// 恢复连接成功后触发
91 | ///
92 | public event Action? OnResumed;
93 | ///
94 | /// 频道信息变更后触发
95 | ///
96 | /// 机器人加入频道, 频道资料变更, 机器人退出频道
97 | /// BotClient - 机器人对象
98 | /// Guild - 频道对象
99 | /// string - 事件类型(GUILD_ADD | GUILD_UPDATE | GUILD_REMOVE)
100 | ///
101 | ///
102 | public event Action? OnGuildMsg;
103 | ///
104 | /// 子频道被修改后触发
105 | ///
106 | /// 子频道被创建, 子频道资料变更, 子频道被删除
107 | /// BotClient - 机器人对象
108 | /// Channel - 频道对象(没有分组Id和排序位置属性)
109 | /// string - 事件类型(CHANNEL_CREATE|CHANNEL_UPDATE|CHANNEL_DELETE)
110 | ///
111 | ///
112 | public event Action? OnChannelMsg;
113 | ///
114 | /// 成员信息变更后触发
115 | ///
116 | /// 成员加入, 资料变更, 移除成员
117 | /// BotClient - 机器人对象
118 | /// MemberWithGuildID - 成员对象
119 | /// string - 事件类型(GUILD_MEMBER_ADD | GUILD_MEMBER_UPDATE | GUILD_MEMBER_REMOVE)
120 | ///
121 | ///
122 | public event Action? OnGuildMemberMsg;
123 | ///
124 | /// 修改表情表态后触发
125 | ///
126 | /// 添加表情表态, 删除表情表态
127 | /// BotClient - 机器人对象
128 | /// MessageReaction - 表情表态对象
129 | /// string - 事件类型(MESSAGE_REACTION_ADD|MESSAGE_REACTION_REMOVE)
130 | ///
131 | ///
132 | public event Action? OnMessageReaction;
133 | ///
134 | /// 消息审核出结果后触发
135 | ///
136 | /// 消息审核通过才有MessageId属性
137 | /// BotClient - 机器人对象
138 | /// MessageAudited - 消息审核对象
139 | /// MessageAudited.IsPassed - 消息审核是否通过
140 | ///
141 | ///
142 | public event Action? OnMessageAudit;
143 | ///
144 | /// 音频状态变更后触发
145 | ///
146 | public event Action? OnAudioMsg;
147 | ///
148 | /// 频道内有人发消息就触发 (包含 @机器人 消息)
149 | ///
150 | /// Sender - 发件人对象
151 | /// 包含消息类别(公开,AT机器人,AT全员,私聊)
152 | ///
153 | ///
154 | public event Action? OnMsgCreate;
155 | ///
156 | /// API调用出错时触发
157 | ///
158 | /// Sender - 发件人对象(仅当调用API的主体为Sender时才有)
159 | /// List<string> - API错误信息{接口地址,请求方式,异常代码,异常原因}
160 | /// FreezeTime - 对访问出错的接口,暂停使用的时间
161 | ///
162 | ///
163 | public event Action? OnApiError;
164 | ///
165 | /// 消息过滤器
166 | /// 当返回值为True时,该消息将被拦截并丢弃
167 | ///
168 | public Func? MessageFilter;
169 | #endregion
170 |
171 | ///
172 | /// 鉴权信息
173 | /// 可在这里查询 QQ机器人开发设置
174 | ///
175 | public Identity BotAccessInfo { get; set; }
176 | ///
177 | /// 向前端指令发出者报告API错误
178 | /// 注:该属性作为 的默认值使用
179 | ///
180 | public bool ReportApiError { get; set; }
181 | ///
182 | /// 机器人用户信息
183 | ///
184 | public User Info { get; private set; } = new User();
185 | ///
186 | /// 缓存机器人已加入的频道
187 | ///
188 | public ConcurrentDictionary Guilds { get; private set; } = new();
189 | ///
190 | /// 保存机器人在各频道内的角色信息
191 | ///
192 | /// string - GUILD_ID
193 | /// Member - 角色信息
194 | ///
195 | ///
196 | public ConcurrentDictionary Members { get; private set; } = new();
197 | ///
198 | /// 缓存消息
199 | ///
200 | /// string - UserId
201 | /// List<Message> - 用户消息列表
202 | ///
203 | ///
204 | private ConcurrentDictionary> StackMessage { get; set; } = new();
205 | ///
206 | /// 存取最后一次发送的消息
207 | /// 注:目的是为了用于撤回消息(自动删除已过5分钟的记录)
208 | ///
209 | /// 需要存储的msg,或者用于检索同频道的msg
210 | /// fase-出栈;true-入栈
211 | private Message? LastMessage(Message? msg, bool push = false)
212 | {
213 | if (msg == null) return null;
214 | DateTime outTime = DateTime.Now.AddMinutes(-10);
215 | string userId = msg.Author.Id;
216 | StackMessage.TryGetValue(userId, out var messages);
217 | if (push)
218 | {
219 | messages?.RemoveAll(m => m.CreateTime < outTime);
220 | messages ??= new();
221 | messages.Add(msg);
222 | StackMessage[userId] = messages;
223 | }
224 | else
225 | {
226 | int stackIndex = messages?.FindLastIndex(m => m.GuildId.Equals(msg.GuildId) && m.ChannelId.Equals(msg.ChannelId)) ?? -1;
227 | if (stackIndex >= 0)
228 | {
229 | msg = messages![stackIndex];
230 | messages.RemoveAt(stackIndex);
231 | }
232 | else msg = null;
233 | }
234 | return msg;
235 | }
236 | #region Http客户端配置
237 | ///
238 | /// 正式环境
239 | ///
240 | private static string ReleaseApi => "https://api.sgroup.qq.com";
241 | ///
242 | /// 沙箱环境
243 | ///
244 | /// 沙箱环境只会收到测试频道的事件,且调用openapi仅能操作测试频道
245 | ///
246 | ///
247 | private static string SandboxApi => "https://sandbox.api.sgroup.qq.com";
248 | ///
249 | /// 机器人接口域名
250 | ///
251 | public string ApiOrigin { get; init; }
252 | ///
253 | /// 集中处理机器人的HTTP请求
254 | ///
255 | /// 请求网址
256 | /// 请求类型(默认GET)
257 | /// 请求数据
258 | /// 指示本次请求由谁发起的
259 | ///
260 | public async Task HttpSendAsync(string url, HttpMethod? method = null, HttpContent? content = null, Sender? sender = null)
261 | {
262 | method ??= HttpMethod.Get;
263 | HttpRequestMessage request = new() { RequestUri = new(new(ApiOrigin), url), Content = content, Method = method };
264 | request.Headers.Authorization = new("Bot", $"{BotAccessInfo.BotAppId}.{BotAccessInfo.BotToken}");
265 | // 捕获Http请求错误
266 | return await BotHttpClient.SendAsync(request, async (response, freezeTime) =>
267 | {
268 | ApiErrorInfo errInfo = new(url.TrimStart(ApiOrigin), method.Method, response.StatusCode.GetHashCode(), "此错误类型未收录!", freezeTime);
269 | if (response.Content.Headers.ContentType?.MediaType == "application/json")
270 | {
271 | ApiStatus? err = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync());
272 | if (err?.Code != null) errInfo.Code = err.Code;
273 | if (err?.Message != null) errInfo.Detail = err.Message;
274 | }
275 | if (StatusCodes.OpenapiCode.TryGetValue(errInfo.Code, out string? value)) errInfo.Detail = value;
276 | string senderMaster = (sender?.Bot.Guilds.TryGetValue(sender.GuildId, out var guild) == true ? guild.Name : null) ?? sender?.Author.UserName ?? "";
277 | Log.Error($"[接口访问失败]{senderMaster} 代码:{errInfo.Code},详情:{errInfo.Detail}");
278 |
279 | OnApiError?.Invoke(sender, errInfo);
280 | if (sender != null)
281 | {
282 | if (sender.ReportError != true) Log.Info($"[接口访问失败] 机器人配置ReportError!=true,取消推送给发件人");
283 | else if (sender.Message.CreateTime.AddMinutes(5) < DateTime.Now) Log.Info($"[接口访问失败] 被动可回复时间已超时,取消推送给发件人");
284 | else
285 | {
286 | _ = Task.Run(async delegate
287 | {
288 | sender.ReportError = false;
289 | await sender.ReplyAsync(string.Join('\n',
290 | "❌接口访问失败",
291 | "接口地址:" + errInfo.Path,
292 | "请求方式:" + errInfo.Method,
293 | "异常代码:" + errInfo.Code,
294 | "异常详情:" + errInfo.Detail,
295 | errInfo.FreezeTime.EndTime > DateTime.Now ? $"接口冻结:暂停使用此接口 {errInfo.FreezeTime.AddTime.Minutes}分{errInfo.FreezeTime.AddTime.Seconds}秒\n解冻时间:{errInfo.FreezeTime.EndTime:yyyy-MM-dd HH:mm:ss}" : null
296 | ));
297 | });
298 | }
299 | }
300 | else Log.Info($"[接口访问失败] 访问接口的主体不是Sender,取消推送给发件人");
301 | });
302 | }
303 | #endregion
304 |
305 | #region Socket客户端配置
306 | ///
307 | /// Socket客户端
308 | ///
309 | private ClientWebSocket WebSocketClient { get; set; } = new();
310 | ///
311 | /// Socket客户端收到的最新的消息的s,如果是第一次连接,传null
312 | ///
313 | private int WebSocketLastSeq { get; set; } = 1;
314 | ///
315 | /// 此次连接所需要接收的事件
316 | /// 具体可参考 事件订阅
317 | ///
318 | public Intent Intents { get; set; } = SocketEvent.Intents.Public;
319 | ///
320 | /// 会话分片信息
321 | ///
322 | private WebSocketLimit? GateLimit { get; set; }
323 | ///
324 | /// 会话分片id
325 | ///
326 | /// 分片是按照频道id进行哈希的,同一个频道的信息会固定从同一个链接推送。
327 | /// 详见 Shard机制
328 | ///
329 | ///
330 | public int ShardId { get; set; } = 0;
331 | ///
332 | /// Socket客户端存储的SessionId
333 | ///
334 | private string? WebSoketSessionId { get; set; }
335 | ///
336 | /// 断线重连状态标志
337 | ///
338 | private bool IsResume { get; set; } = false;
339 | ///
340 | /// Socket心跳定时器
341 | ///
342 | private System.Timers.Timer HeartBeatTimer { get; set; } = new();
343 | #endregion
344 |
345 | ///
346 | /// QQ频道机器人
347 | ///
348 | /// 机器人鉴权信息
349 | /// 使用沙箱API
350 | /// 向前端用户反馈API错误
351 | public BotClient(Identity identity, bool sandBoxApi = false, bool reportApiError = false)
352 | {
353 | BotAccessInfo = identity;
354 | ApiOrigin = sandBoxApi ? SandboxApi : ReleaseApi;
355 | ReportApiError = reportApiError;
356 | // 绑定定时器回调,用于定时发送心跳包
357 | HeartBeatTimer.Elapsed += async (sender, e) => await ExcuteCommand(JsonDocument.Parse("{\"op\":" + (int)Opcode.Heartbeat + "}").RootElement);
358 | }
359 |
360 | ///
361 | /// 关闭机器人并释放所有占用的资源
362 | ///
363 | public void Close()
364 | {
365 | WebSocketClient.Abort();
366 | }
367 | ///
368 | /// 启动机器人
369 | /// RetryCount 连接服务器失败后的重试次数
370 | ///
371 | /// 连接服务器失败后的重试次数
372 | public async void Start(int RetryCount = 3)
373 | {
374 | StartTime = DateTime.Now;
375 | await ConnectAsync(RetryCount);
376 | }
377 | ///
378 | /// 上帝ID
379 | /// 仅用于测试,方便机器人开发者验证功能
380 | ///
381 | public string GodId { get; set; } = "15524401336961673551";
382 | }
383 | }
--------------------------------------------------------------------------------
/QQChannelBot/Bot/BotCommandRegister.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 |
3 | namespace QQChannelBot.Bot
4 | {
5 | public partial class BotClient
6 | {
7 | ///
8 | /// 自定义指令前缀
9 | ///
10 | /// 当机器人识别到消息的头部包含指令前缀时触发指令识别功能
11 | /// 默认值:"/"
12 | ///
13 | ///
14 | public string CommandPrefix { get; set; } = "/";
15 | ///
16 | /// 缓存动态注册的消息指令事件
17 | ///
18 | private ConcurrentDictionary Commands { get; } = new();
19 | ///
20 | /// 添加消息指令
21 | ///
22 | /// 注1:指令匹配忽略消息前的 @机器人 标签,并移除所有前导和尾随空白字符。
23 | /// 注2:被指令命中的消息不会再触发 OnAtMessage 和 OnMsgCreate 事件
24 | ///
25 | ///
26 | /// 指令对象
27 | /// 指令名称重复的处理办法true:替换, false:忽略
28 | ///
29 | public BotClient AddCommand(Command command, bool overwrite = false)
30 | {
31 | string cmdName = command.Name;
32 | if (Commands.ContainsKey(cmdName))
33 | {
34 | if (overwrite)
35 | {
36 | Log.Warn($"[CommandManager] 指令 {cmdName} 已存在,已替换新注册的功能!");
37 | Commands[cmdName] = command;
38 | }
39 | else Log.Warn($"[CommandManager] 指令 {cmdName} 已存在,已忽略新功能的注册!");
40 | }
41 | else
42 | {
43 | Log.Info($"[CommandManager] 指令 {cmdName} 已注册.");
44 | Commands[cmdName] = command;
45 | }
46 | return this;
47 | }
48 | ///
49 | /// 删除消息指令
50 | ///
51 | /// 指令名称
52 | ///
53 | public BotClient DelCommand(string cmdName)
54 | {
55 | if (Commands.ContainsKey(cmdName))
56 | {
57 | if (Commands.Remove(cmdName, out _)) Log.Info($"[CommandManager] 指令 {cmdName} 已删除.");
58 | else Log.Warn($"[CommandManager] 指令 {cmdName} 删除失败.");
59 | }
60 | else Log.Warn($"[CommandManager] 指令 {cmdName} 不存在!");
61 | return this;
62 | }
63 | ///
64 | /// 获取所有已注册的指令
65 | ///
66 | public List GetCommands => Commands.Values.ToList();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/BotHttpClient.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Http.Headers;
3 | using System.Text.Json;
4 | using System.Text.RegularExpressions;
5 | using QQChannelBot.Bot.StatusCode;
6 | using QQChannelBot.Tools;
7 |
8 | namespace QQChannelBot.Bot
9 | {
10 | ///
11 | /// 时间冻结类
12 | ///
13 | public class FreezeTime
14 | {
15 | ///
16 | /// 结束时间
17 | ///
18 | public DateTime EndTime { get; set; } = DateTime.MinValue;
19 | ///
20 | /// 附加时间
21 | ///
22 | public TimeSpan AddTime { get; set; } = TimeSpan.Zero;
23 | }
24 | ///
25 | /// 经过封装的HttpClient
26 | /// 内置了请求日志功能
27 | ///
28 | public static class BotHttpClient
29 | {
30 | ///
31 | /// URL访问失败的默认冻结时间
32 | ///
33 | public static TimeSpan FreezeAddTime { get; set; } = TimeSpan.FromSeconds(30);
34 | ///
35 | /// URL访问失败的最高冻结时间
36 | ///
37 | public static TimeSpan FreezeMaxTime { get; set; } = TimeSpan.FromHours(1);
38 | ///
39 | /// 临时冻结无权限访问的URL
40 | ///
41 | /// value.Item1 - 解封时间(DateTime)
42 | /// value.Item2 - 再次封禁增加的时间(TimeSpan)
43 | ///
44 | ///
45 | private static readonly Dictionary FreezeUrl = new();
46 | ///
47 | /// Http客户端
48 | /// 这里设置禁止重定向:AllowAutoRedirect = false
49 | /// 这里设置超时时间为15s
50 | ///
51 | public static HttpClient HttpClient { get; } = new(new HttpLoggingHandler(new HttpClientHandler() { AllowAutoRedirect = false })) { Timeout = TimeSpan.FromSeconds(15) };
52 |
53 | ///
54 | /// 发起HTTP异步请求
55 | ///
56 | /// 请求消息
57 | /// 请求失败的回调函数
58 | ///
59 | public static async Task SendAsync(HttpRequestMessage request, Action? action = null)
60 | {
61 | string reqUrl = request.RequestUri!.ToString();
62 | if (FreezeUrl.TryGetValue(reqUrl, out FreezeTime? freezeTime) && freezeTime.EndTime > DateTime.Now)
63 | {
64 | Log.Warn($"[HttpSend] 目标接口处于冻结状态,暂时无法访问:{reqUrl}");
65 | return null;
66 | }
67 | HttpResponseMessage response = await HttpClient.SendAsync(request).ConfigureAwait(false);
68 | if (response.IsSuccessStatusCode)
69 | {
70 | if (FreezeUrl.ContainsKey(reqUrl)) FreezeUrl.Remove(reqUrl);
71 | return response;
72 | }
73 | if (response.Content.Headers.ContentType?.MediaType == "application/json")
74 | {
75 | string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
76 | ApiStatus? err = JsonSerializer.Deserialize(responseContent);
77 | if (err?.Code >= 400)
78 | {
79 | if (FreezeUrl.TryGetValue(reqUrl, out freezeTime))
80 | {
81 | freezeTime.AddTime *= 2;
82 | if (freezeTime.AddTime > FreezeMaxTime) freezeTime.AddTime = FreezeMaxTime;
83 | freezeTime.EndTime = DateTime.Now + freezeTime.AddTime;
84 | }
85 | else
86 | {
87 | freezeTime = new FreezeTime() { AddTime = TimeSpan.FromSeconds(5) };
88 | // 重点打击11264和11265错误,无接口访问权限;轻微处理其它错误
89 | freezeTime.EndTime = DateTime.Now + ((err?.Code == 11264 || err?.Code == 11265) ? FreezeAddTime : freezeTime.AddTime);
90 | }
91 | }
92 | }
93 | freezeTime ??= new FreezeTime() { EndTime = DateTime.Now, AddTime = TimeSpan.FromSeconds(5) };
94 | action?.Invoke(response, freezeTime);
95 | return null;
96 | }
97 |
98 | ///
99 | /// HTTP异步GET
100 | ///
101 | /// 请求地址
102 | ///
103 | public static async Task GetAsync(string url)
104 | {
105 | HttpRequestMessage request = new() { RequestUri = new Uri(url), Content = null, Method = HttpMethod.Get };
106 | return await HttpClient.SendAsync(request).ConfigureAwait(false);
107 | }
108 |
109 | ///
110 | /// HTTP异步Post
111 | ///
112 | /// 请求地址
113 | /// 请求内容
114 | ///
115 | public static async Task PostAsync(string url, HttpContent content)
116 | {
117 | HttpRequestMessage request = new() { RequestUri = new Uri(url), Content = content, Method = HttpMethod.Post };
118 | return await HttpClient.SendAsync(request).ConfigureAwait(false);
119 | }
120 |
121 | ///
122 | /// HTTP异步Put
123 | ///
124 | /// 请求地址
125 | /// 请求内容
126 | ///
127 | public static async Task PutAsync(string url, HttpContent content)
128 | {
129 | HttpRequestMessage request = new() { RequestUri = new Uri(url), Content = content, Method = HttpMethod.Put };
130 | return await HttpClient.SendAsync(request).ConfigureAwait(false);
131 | }
132 |
133 | ///
134 | /// HTTP异步Delete
135 | ///
136 | /// 请求地址
137 | /// 请求内容
138 | ///
139 | public static async Task DeleteAsync(string url, HttpContent content)
140 | {
141 | HttpRequestMessage request = new() { RequestUri = new Uri(url), Content = content, Method = HttpMethod.Delete };
142 | return await HttpClient.SendAsync(request).ConfigureAwait(false);
143 | }
144 | }
145 | ///
146 | /// HttpClient请求拦截器
147 | ///
148 | public class HttpLoggingHandler : DelegatingHandler
149 | {
150 | const int printLength = 1024;
151 | ///
152 | /// HttpClient请求拦截器构造函数
153 | ///
154 | ///
155 | public HttpLoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
156 | ///
157 | /// 发起异步Http请求
158 | ///
159 | ///
160 | ///
161 | ///
162 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
163 | {
164 | string requestString = Regex.Replace(request.ToString(), @"(?<=Bot\s+)[^\n]+", (m) => Regex.Replace(m.Groups[0].Value, @"[^\.]", "*")); // 敏感信息脱敏
165 | string requestContent = request.Content == null ? "" : await request.Content.ReadAsStringAsync(CancellationToken.None);
166 | MediaTypeHeaderValue? requestContentType = request.Content?.Headers.ContentType;
167 | if (requestContent.Length > printLength) requestContent = requestContent[..printLength];
168 | if ((requestContentType?.CharSet != null) || (requestContentType?.MediaType == "application/json")) { }
169 | else if (string.IsNullOrWhiteSpace(requestContent)) requestContent = "(没有内容)";
170 | else requestContent = "(内容无法解码)";
171 | requestContent = $"[HttpHandler][Request]{Environment.NewLine}{requestString}{Environment.NewLine}{requestContent}";
172 |
173 | HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
174 | if (cancellationToken.IsCancellationRequested)
175 | {
176 | Log.Error(requestContent + "\n请求已取消!");
177 | return response; // 请求已取消
178 | }
179 |
180 | string responseString = response.ToString();
181 | string responseContent = response.Content == null ? "" : await response.Content.ReadAsStringAsync(CancellationToken.None);
182 | HttpStatusCode responseStatusCode = response.StatusCode;
183 | MediaTypeHeaderValue? responseContentType = response.Content?.Headers.ContentType;
184 |
185 | if (responseContent.Length > printLength) responseContent = responseContent[..printLength];
186 | if ((responseContentType?.CharSet != null) || (responseContentType?.MediaType == "application/json")) { }
187 | else if (string.IsNullOrWhiteSpace(responseContent)) responseContent = "(没有内容)";
188 | else responseContent = "(内容无法解码)";
189 | responseContent = $"[HttpHandler][Response]{Environment.NewLine}{responseString}{Environment.NewLine}{responseContent}{Environment.NewLine}";
190 | if (responseStatusCode < HttpStatusCode.BadRequest) Log.Debug(requestContent + '\n' + responseContent);
191 | else Log.Error(requestContent + '\n' + responseContent);
192 | return response;
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/BotWebSocket.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Net.WebSockets;
3 | using System.Text;
4 | using System.Text.Json;
5 | using System.Text.RegularExpressions;
6 | using QQChannelBot.Bot.SocketEvent;
7 | using QQChannelBot.Models;
8 |
9 | namespace QQChannelBot.Bot
10 | {
11 | public partial class BotClient
12 | {
13 | ///
14 | /// 建立到服务器的连接
15 | /// RetryCount 连接失败后允许的重试次数
16 | ///
17 | /// 连接失败后允许的重试次数
18 | ///
19 | public async Task ConnectAsync(int RetryCount)
20 | {
21 | int retryEndTime = 10;
22 | int retryAddTime = 10;
23 | while (RetryCount-- > 0)
24 | {
25 | try
26 | {
27 | GateLimit = await GetWssUrlWithShared();
28 | if (Uri.TryCreate(GateLimit?.Url, UriKind.Absolute, out Uri? webSocketUri))
29 | {
30 | if (WebSocketClient.State != WebSocketState.Open && WebSocketClient.State != WebSocketState.Connecting)
31 | {
32 | await WebSocketClient.ConnectAsync(webSocketUri, CancellationToken.None);
33 | OnWebSocketConnected?.Invoke(this);
34 | _ = ReceiveAsync();
35 | }
36 | break;
37 | }
38 | Log.Error($"[WebSocket][Connect] 使用网关地址<{GateLimit?.Url}> 建立连接失败!");
39 | }
40 | catch (Exception e)
41 | {
42 | Log.Error($"[WebSocket][Connect] {e.Message} | Status:{WebSocketClient.CloseStatus} | Description:{WebSocketClient.CloseStatusDescription}");
43 | }
44 | if (RetryCount > 0)
45 | {
46 | for (int i = retryEndTime; 0 < i; --i)
47 | {
48 | Log.Info($"[WebSocket] {i} 秒后再次尝试连接(剩余重试次数:{RetryCount})...");
49 | await Task.Delay(TimeSpan.FromSeconds(1));
50 | }
51 | retryEndTime += retryAddTime;
52 | }
53 | else
54 | {
55 | Log.Error($"[WebSocket] 重连次数已耗尽,无法与频道服务器建立连接!");
56 | }
57 | }
58 | }
59 | ///
60 | /// 鉴权连接
61 | ///
62 | ///
63 | private async Task SendIdentifyAsync()
64 | {
65 | var data = new
66 | {
67 | op = Opcode.Identify,
68 | d = new
69 | {
70 | token = $"Bot {BotAccessInfo.BotAppId}.{BotAccessInfo.BotToken}",
71 | intents = Intents.GetHashCode(),
72 | shared = new[] { ShardId % (GateLimit?.Shards ?? 1), GateLimit?.Shards ?? 1 }
73 | }
74 | };
75 | string sendMsg = JsonSerializer.Serialize(data);
76 | Log.Debug("[WebSocket][SendIdentify] " + Regex.Replace(sendMsg, @"(?<=Bot\s+)[^""]+", (m) => Regex.Replace(m.Groups[0].Value, @"[^\.]", "*"))); // 敏感信息脱敏处理
77 | await WebSocketSendAsync(sendMsg, WebSocketMessageType.Text, true);
78 | }
79 | ///
80 | /// 发送心跳
81 | ///
82 | ///
83 | private async Task SendHeartBeatAsync()
84 | {
85 | if (WebSocketClient.State == WebSocketState.Open)
86 | {
87 | string sendMsg = "{\"op\": 1, \"d\":" + WebSocketLastSeq + "}";
88 | Log.Debug($"[WebSocket][SendHeartbeat] {sendMsg}");
89 | await WebSocketSendAsync(sendMsg, WebSocketMessageType.Text, true);
90 | }
91 | else Log.Error($"[WebSocket][Heartbeat] 未建立连接!");
92 | }
93 | ///
94 | /// 恢复连接
95 | ///
96 | ///
97 | private async Task SendResumeAsync()
98 | {
99 | try
100 | {
101 | var data = new
102 | {
103 | op = Opcode.Resume,
104 | d = new
105 | {
106 | token = $"Bot {BotAccessInfo.BotAppId}.{BotAccessInfo.BotToken}",
107 | session_id = WebSoketSessionId,
108 | seq = WebSocketLastSeq
109 | }
110 | };
111 | string sendMsg = JsonSerializer.Serialize(data);
112 | Log.Debug($"[WebSocket][SendResume] {sendMsg}");
113 | await WebSocketSendAsync(sendMsg, WebSocketMessageType.Text, true);
114 | }
115 | catch (Exception e)
116 | {
117 | Log.Error($"[WebSocket] Resume Error: {e.Message}");
118 | }
119 | }
120 | ///
121 | /// WebScoket发送数据到服务端
122 | ///
123 | /// 要发送的数据
124 | /// WebSocket消息类型
125 | /// 表示数据已发送结束
126 | /// 用于传播应取消此操作的通知的取消令牌。
127 | ///
128 | private async Task WebSocketSendAsync(string data, WebSocketMessageType msgType = WebSocketMessageType.Text, bool endOfMsg = true, CancellationToken? cancelToken = null)
129 | {
130 | OnWebSoketSending?.Invoke(this, data);
131 | await WebSocketClient.SendAsync(Encoding.UTF8.GetBytes(data), msgType, endOfMsg, cancelToken ?? CancellationToken.None);
132 | }
133 | ///
134 | /// WebSocket接收服务端数据
135 | ///
136 | ///
137 | private async Task ReceiveAsync()
138 | {
139 | while (WebSocketClient.State == WebSocketState.Open)
140 | {
141 | try
142 | {
143 | using IMemoryOwner memory = MemoryPool.Shared.Rent(1024 * 64);
144 | ValueWebSocketReceiveResult result = await WebSocketClient.ReceiveAsync(memory.Memory, CancellationToken.None);
145 | if (result.MessageType == WebSocketMessageType.Text)
146 | {
147 | JsonElement json = JsonDocument.Parse(memory.Memory[..result.Count]).RootElement;
148 | OnWebSocketReceived?.Invoke(this, json.GetRawText());
149 | await ExcuteCommand(json);
150 | continue;
151 | }
152 | Log.Info($"[WebSocket][Receive] SocketType:{result.MessageType} | Status:{WebSocketClient.CloseStatus}");
153 | }
154 | catch (Exception e)
155 | {
156 | Log.Error($"[WebSocket][Receive] {e.Message} | Status:{WebSocketClient.CloseStatus}{Environment.NewLine}");
157 | }
158 | // 关闭代码4009表示频道服务器要求的重连,可以进行Resume
159 | if (WebSocketClient.CloseStatus.GetHashCode() == 4009) IsResume = true;
160 | WebSocketClient.Abort();
161 | break;
162 | }
163 | if (HeartBeatTimer.Enabled) HeartBeatTimer.Enabled = false;
164 | OnWebSocketClosed?.Invoke(this);
165 | Log.Warn($"[WebSocket] 重新建立到服务器的连接...");
166 | await Task.Delay(TimeSpan.FromSeconds(1));
167 | WebSocketClient = new();
168 | await ConnectAsync(30);
169 | }
170 | ///
171 | /// 根据收到的数据分析用途
172 | ///
173 | /// Wss接收的数据
174 | ///
175 | private async Task ExcuteCommand(JsonElement wssJson)
176 | {
177 | Opcode opcode = (Opcode)wssJson.GetProperty("op").GetInt32();
178 | switch (opcode)
179 | {
180 | // Receive 服务端进行消息推送
181 | case Opcode.Dispatch:
182 | OnDispatch?.Invoke(this, wssJson);
183 | WebSocketLastSeq = wssJson.GetProperty("s").GetInt32();
184 | if (!wssJson.TryGetProperty("t", out JsonElement t) || !wssJson.TryGetProperty("d", out JsonElement d))
185 | {
186 | Log.Warn($"[WebSocket][Op00][Dispatch] {wssJson.GetRawText()}");
187 | break;
188 | }
189 | string data = d.GetRawText();
190 | string? type = t.GetString();
191 | switch (type)
192 | {
193 | case "DIRECT_MESSAGE_CREATE": // 机器人收到私信事件
194 | case "AT_MESSAGE_CREATE": // 收到 @机器人 消息事件
195 | case "MESSAGE_CREATE": // 频道内有人发言(仅私域)
196 | Message? message = d.Deserialize();
197 | if (message == null)
198 | {
199 | Log.Warn($"[WebSocket][{type}] {data}");
200 | return;
201 | }
202 | Log.Debug($"[WebSocket][{type}] {data}");
203 | _ = MessageCenter(message, type); // 处理消息,不需要等待结果
204 | break;
205 | case "GUILD_CREATE":
206 | case "GUILD_UPDATE":
207 | case "GUILD_DELETE":
208 | /*频道事件*/
209 | Log.Debug($"[WebSocket][{type}] {data}");
210 | Guild guild = d.Deserialize()!;
211 | switch (type)
212 | {
213 | case "GUILD_CREATE":
214 | case "GUILD_UPDATE":
215 | guild.APIPermissions = await GetGuildPermissionsAsync(guild.Id);
216 | Guilds[guild.Id] = guild;
217 | break;
218 | case "GUILD_DELETE":
219 | Guilds.Remove(guild.Id, out _);
220 | break;
221 | }
222 | OnGuildMsg?.Invoke(this, guild, type);
223 | break;
224 | case "CHANNEL_CREATE":
225 | case "CHANNEL_UPDATE":
226 | case "CHANNEL_DELETE":
227 | /*子频道事件*/
228 | Log.Debug($"[WebSocket][{type}] {data}");
229 | Channel channel = d.Deserialize()!;
230 | OnChannelMsg?.Invoke(this, channel, type);
231 | break;
232 | case "GUILD_MEMBER_ADD":
233 | case "GUILD_MEMBER_UPDATE":
234 | case "GUILD_MEMBER_REMOVE":
235 | /*频道成员事件*/
236 | Log.Debug($"[WebSocket][{type}] {data}");
237 | MemberWithGuildID memberWithGuild = d.Deserialize()!;
238 | OnGuildMemberMsg?.Invoke(this, memberWithGuild, type);
239 | break;
240 | case "MESSAGE_REACTION_ADD":
241 | case "MESSAGE_REACTION_REMOVE":
242 | /*表情表态事件*/
243 | Log.Debug($"[WebSocket][{type}] {data}");
244 | MessageReaction messageReaction = d.Deserialize()!;
245 | OnMessageReaction?.Invoke(this, messageReaction, type);
246 | break;
247 | case "MESSAGE_AUDIT_PASS":
248 | case "MESSAGE_AUDIT_REJECT":
249 | /*消息审核事件*/
250 | Log.Info($"[WebSocket][{type}] {data}");
251 | MessageAudited? messageAudited = d.Deserialize()!;
252 | messageAudited.IsPassed = type == "MESSAGE_AUDIT_PASS";
253 | OnMessageAudit?.Invoke(this, messageAudited);
254 | break;
255 | case "AUDIO_START":
256 | case "AUDIO_FINISH":
257 | case "AUDIO_ON_MIC":
258 | case "AUDIO_OFF_MIC":
259 | /*音频事件*/
260 | Log.Info($"[WebSocket][{type}] {data}");
261 | OnAudioMsg?.Invoke(this, wssJson);
262 | break;
263 | case "RESUMED":
264 | Log.Info($"[WebSocket][Op00][RESUMED] 已恢复与服务器的连接");
265 | await ExcuteCommand(JsonDocument.Parse("{\"op\":" + (int)Opcode.Heartbeat + "}").RootElement);
266 | OnResumed?.Invoke(this, d);
267 | break;
268 | case "READY":
269 | Log.Debug($"[WebSocket][READY] {data}");
270 | Log.Info($"[WebSocket][Op00] 服务端 鉴权成功");
271 | await ExcuteCommand(JsonDocument.Parse("{\"op\":" + (int)Opcode.Heartbeat + "}").RootElement);
272 | WebSoketSessionId = d.GetProperty("session_id").GetString();
273 |
274 | Log.Info($"[WebSocket][GetGuilds] 获取已加入的频道列表,分页大小:100");
275 | string? guildNext = null;
276 | for (int page = 1; ; ++page)
277 | {
278 | List? guilds = await GetMeGuildsAsync(guildNext);
279 | if (guilds == null)
280 | {
281 | Log.Info($"[WebSocket][GetGuilds] 获取已加入的频道列表,第 {page:00} 页失败");
282 | break;
283 | }
284 | if (guilds.Count == 0)
285 | {
286 | Log.Info($"[WebSocket][GetGuilds] 获取已加入的频道列表,第 {page:00} 页为空,操作结束");
287 | break;
288 | }
289 | Log.Info($"[WebSocket][GetGuilds] 获取已加入的频道列表,第 {page:00} 页成功,数量:{guilds.Count}");
290 | Parallel.ForEach(guilds, (guild, state, i) =>
291 | {
292 | guild.APIPermissions = GetGuildPermissionsAsync(guild.Id).Result;
293 | Guilds[guild.Id] = guild;
294 | });
295 | guildNext = guilds.Last().Id;
296 | }
297 | Log.Info($"[WebSocket][GetGuilds] 机器人已加入 {Guilds.Count} 个频道");
298 |
299 | Info = d.GetProperty("user").Deserialize()!;
300 | Info.Avatar = (await GetMeAsync())?.Avatar;
301 | OnReady?.Invoke(Info);
302 | break;
303 | default:
304 | Log.Warn($"[WebSocket][{type}] 未知事件");
305 | break;
306 | }
307 | break;
308 | // Send&Receive 客户端或服务端发送心跳
309 | case Opcode.Heartbeat:
310 | Log.Debug($"[WebSocket][Op01] {(wssJson.Get("d") == null ? "客户端" : "服务器")} 发送心跳包");
311 | OnHeartbeat?.Invoke(this, wssJson);
312 | await SendHeartBeatAsync();
313 | HeartBeatTimer.Enabled = true;
314 | break;
315 | // Send 客户端发送鉴权
316 | case Opcode.Identify:
317 | Log.Info($"[WebSocket][Op02] 客户端 发起鉴权");
318 | OnIdentify?.Invoke(this, wssJson);
319 | await SendIdentifyAsync();
320 | break;
321 | // Send 客户端恢复连接
322 | case Opcode.Resume:
323 | Log.Info($"[WebSocket][Op06] 客户端 尝试恢复连接..");
324 | IsResume = false;
325 | OnResume?.Invoke(this, wssJson);
326 | await SendResumeAsync();
327 | break;
328 | // Receive 服务端通知客户端重新连接
329 | case Opcode.Reconnect:
330 | Log.Info($"[WebSocket][Op07] 服务器 要求客户端重连");
331 | OnReconnect?.Invoke(this, wssJson);
332 | break;
333 | // Receive 当identify或resume的时候,如果参数有错,服务端会返回该消息
334 | case Opcode.InvalidSession:
335 | Log.Warn($"[WebSocket][Op09] 客户端鉴权信息错误");
336 | OnInvalidSession?.Invoke(this, wssJson);
337 | break;
338 | // Receive 当客户端与网关建立ws连接之后,网关下发的第一条消息
339 | case Opcode.Hello:
340 | Log.Info($"[WebSocket][Op10][成功与网关建立连接] {wssJson.GetRawText()}");
341 | OnHello?.Invoke(this, wssJson);
342 | int heartbeat_interval = wssJson.Get("d")?.Get("heartbeat_interval")?.GetInt32() ?? 30000;
343 | HeartBeatTimer.Interval = heartbeat_interval < 30000 ? heartbeat_interval : 30000; // 设置心跳时间为30s
344 | await ExcuteCommand(JsonDocument.Parse("{\"op\":" + (int)(IsResume ? Opcode.Resume : Opcode.Identify) + "}").RootElement);
345 | break;
346 | // Receive 当发送心跳成功之后,就会收到该消息
347 | case Opcode.HeartbeatACK:
348 | Log.Debug($"[WebSocket][Op11] 服务器 收到心跳包");
349 | OnHeartbeatACK?.Invoke(this, wssJson);
350 | break;
351 | // 未知操作码
352 | default:
353 | Log.Warn($"[WebSocket][OpNC] 未知操作码: {opcode}");
354 | break;
355 | }
356 | }
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/Command.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace QQChannelBot.Bot
4 | {
5 | ///
6 | /// 指令对象
7 | /// 提前封装的指令类,用于方便的处理指令
8 | ///
9 | public class Command
10 | {
11 | ///
12 | /// 构造指令对象
13 | ///
14 | /// 匹配指令将调用 Rule 属性进行正则匹配
15 | /// 若rule未赋值,默认为 "^name(?=\s|\n|<@!\d+>|$)"
16 | ///
17 | ///
18 | /// 指令名称
19 | /// 回调函数
20 | /// 匹配指令用的正则表达式默认值:Regex("^name(?=\s|\n|<@!\d+>|$)")
21 | /// 需要管理员权限
22 | /// 备注,用户自定义属性功能用途
23 | public Command(string name, Action? callBack = null, Regex? rule = null, bool needAdmin = false, string? note = null)
24 | {
25 | Name = name;
26 | rule ??= new Regex($@"^{Regex.Escape(name)}\s*(?=\s|\d|\n|<@!\d+>|$)");
27 | CompiledRule = new Regex(rule.ToString(), rule.Options | RegexOptions.Compiled);
28 | CallBack = callBack;
29 | NeedAdmin = needAdmin;
30 | Note = note;
31 | }
32 | ///
33 | /// 指令名称
34 | ///
35 | public string Name { get; set; }
36 | ///
37 | /// 指令命中后的回调函数
38 | /// 例:(sender, args)=>{}
39 | ///
40 | public Action? CallBack { get; set; }
41 | ///
42 | /// 编译为程序集的正则表达式(可加快正则匹配速度)
43 | ///
44 | private Regex CompiledRule { get; set; }
45 | ///
46 | /// 匹配规则
47 | ///
48 | public Regex Rule
49 | {
50 | get => CompiledRule;
51 | set
52 | {
53 | CompiledRule = new(value.ToString(), value.Options | RegexOptions.Compiled);
54 | }
55 | }
56 | ///
57 | /// 管理员权限
58 | /// 若设定为true,将只有管理员才能触发该指令
59 | ///
60 | public bool NeedAdmin { get; set; } = false;
61 | ///
62 | /// 备注
63 | /// 用户自定义属性功能用途
64 | ///
65 | public string? Note { get; set; }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/Identity.cs:
--------------------------------------------------------------------------------
1 | namespace QQChannelBot.Bot
2 | {
3 | ///
4 | /// 鉴权信息
5 | ///
6 | public struct Identity
7 | {
8 | ///
9 | /// 机器人Id
10 | ///
11 | public string BotAppId { get; set; }
12 | ///
13 | /// 机器人Token
14 | ///
15 | public string BotToken { get; set; }
16 | ///
17 | /// 机器人密钥
18 | ///
19 | public string BotSecret { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/InfoSDK.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace QQChannelBot.Bot
4 | {
5 | ///
6 | /// SDK信息
7 | ///
8 | public static class InfoSDK
9 | {
10 | private static AssemblyName sdk = typeof(BotClient).Assembly.GetName();
11 | ///
12 | /// SDK名称
13 | ///
14 | public static string? Name => sdk.Name;
15 | ///
16 | /// SDK版本
17 | ///
18 | public static Version? Version => sdk.Version;
19 | ///
20 | /// SDK开源地址HTTPS
21 | ///
22 | public static string GitHTTPS => "https://github\uFEFF.com/Antecer/QQChannelBot";
23 | ///
24 | /// SDK开源地址SSH
25 | ///
26 | public static string GitSSH => @"git@github.com:Antecer/QQChannelBot.git";
27 | ///
28 | /// 版权信息
29 | ///
30 | public static string Copyright => "Copyright © 2021 Antecer. All rights reserved.";
31 | }
32 |
33 | public partial class BotClient
34 | {
35 | ///
36 | /// 返回SDK相关信息
37 | ///
38 | /// 框架名称_版本号
39 | /// 代码仓库地址
40 | /// 版权信息
41 | /// 作者夹带的一点私货
42 | ///
43 | ///
44 | public static string SDK => $"{InfoSDK.Name}_{InfoSDK.Version}\n{InfoSDK.GitHTTPS}\n{InfoSDK.Copyright}";
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/Log.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using QQChannelBot.Tools;
3 |
4 | namespace QQChannelBot.Bot
5 | {
6 | ///
7 | /// 集中处理日志信息
8 | ///
9 | public static class Log
10 | {
11 | ///
12 | /// 日志数据结构体
13 | ///
14 | ///
15 | ///
16 | ///
17 | public record struct LogEntry(LogLevel Level, string Message, string TimeStamp);
18 | ///
19 | /// 自定义日志输出
20 | ///
21 | public static event Action? LogTo;
22 | ///
23 | /// 日志记录级别
24 | ///
25 | public static LogLevel LogLevel { get; set; } = LogLevel.INFO;
26 | ///
27 | /// 时间格式化器
28 | /// 定义日志输出的时间戳格式 (默认值 HH:mm:ss.fff)
29 | ///
30 | public static string TimeFormatter { get; set; } = "HH:mm:ss.f";
31 |
32 | ///
33 | /// 获取格式化的日期标签
34 | ///
35 | private static string TimeStamp { get => $"[{DateTime.Now.ToString(TimeFormatter)}]"; }
36 | ///
37 | /// 日志输出队列
38 | ///
39 | private static readonly ConcurrentQueue LogQueue = new();
40 |
41 | private static bool IsWorking = false;
42 | ///
43 | /// 打印日志
44 | ///
45 | private static void Print(LogEntry logItem)
46 | {
47 | Task.Run(() =>
48 | {
49 | LogQueue.Enqueue(logItem);
50 | if (IsWorking) return;
51 | IsWorking = true;
52 | while (LogQueue.TryDequeue(out LogEntry entry))
53 | {
54 | if (LogLevel <= entry.Level)
55 | {
56 | Console.ForegroundColor = ConsoleColor.White;
57 | Console.Write($"{entry.TimeStamp}[{entry.Level.ToString()[0]}]");
58 | Console.ForegroundColor = entry.Level switch
59 | {
60 | LogLevel.DEBUG => ConsoleColor.Gray,
61 | LogLevel.INFO => ConsoleColor.DarkGreen,
62 | LogLevel.WARRNING => ConsoleColor.DarkYellow,
63 | LogLevel.ERROR => ConsoleColor.DarkRed,
64 | _ => ConsoleColor.Magenta,
65 | };
66 | Console.WriteLine(Unicoder.Decode(entry.Message));
67 | LogTo?.Invoke(entry);
68 | }
69 | }
70 | IsWorking = false;
71 | });
72 | }
73 |
74 | ///
75 | /// 打印调试
76 | ///
77 | ///
78 | public static void Debug(string message) => Print(new(LogLevel.DEBUG, message, TimeStamp));
79 |
80 | ///
81 | /// 打印日志
82 | ///
83 | ///
84 | public static void Info(string message) => Print(new(LogLevel.INFO, message, TimeStamp));
85 |
86 | ///
87 | /// 打印警告
88 | ///
89 | ///
90 | public static void Warn(string message) => Print(new(LogLevel.WARRNING, message, TimeStamp));
91 |
92 | ///
93 | /// 打印错误
94 | ///
95 | ///
96 | public static void Error(string message) => Print(new(LogLevel.ERROR, message, TimeStamp));
97 | }
98 |
99 | ///
100 | /// 日志记录等级
101 | ///
102 | public enum LogLevel
103 | {
104 | ///
105 | /// 调试
106 | ///
107 | DEBUG,
108 | ///
109 | /// 消息
110 | ///
111 | INFO,
112 | ///
113 | /// 警告
114 | ///
115 | WARRNING,
116 | ///
117 | /// 错误
118 | ///
119 | ERROR
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/MessageCenter.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Text.RegularExpressions;
3 | using QQChannelBot.Bot.SocketEvent;
4 | using QQChannelBot.Models;
5 |
6 | namespace QQChannelBot.Bot
7 | {
8 | public partial class BotClient
9 | {
10 | ///
11 | /// 集中处理聊天消息
12 | ///
13 | /// 消息对象
14 | /// 消息类型
15 | ///
16 | /// DIRECT_MESSAGE_CREATE - 私信
17 | /// AT_MESSAGE_CREATE - 频道内 @机器人
18 | /// MESSAGE_CREATE - 频道内任意消息(仅私域支持)
19 | ///
20 | ///
21 | private async Task MessageCenter(Message message, string type)
22 | {
23 | // 记录Sender信息
24 | Sender sender = new(message, this);
25 | // 识别消息类型(私聊,AT全员,AT机器人)
26 | if (message.DirectMessage) sender.MessageType = MessageType.Private;
27 | else if (message.MentionEveryone) sender.MessageType = MessageType.AtAll;
28 | else if (message.Mentions?.Any(user => user.Id == Info.Id) == true) sender.MessageType = MessageType.AtMe;
29 | else if (message.Content.Contains($"@{Info.UserName}")) sender.MessageType = MessageType.AtMe;
30 | // 记录机器人在当前频道下的身份组信息
31 | if ((sender.MessageType != MessageType.Private) && !Members.ContainsKey(message.GuildId))
32 | {
33 | Members[message.GuildId] = await GetMemberAsync(message.GuildId, Info.Id);
34 | }
35 | // 若已经启用全局消息接收,将不单独响应 AT_MESSAGES 事件,否则会造成重复响应。
36 | if (Intents.HasFlag(Intent.MESSAGE_CREATE) && type.StartsWith("A")) return;
37 | // 调用消息拦截器
38 | if (MessageFilter?.Invoke(sender) == true) return;
39 | // 记录收到的消息
40 | LastMessage(message, true);
41 | // 预判收到的消息
42 | string content = message.Content.Trim();
43 | if (content.StartsWith(Info.Tag)) content = content.TrimStart(Info.Tag).TrimStart();
44 | else if (content.StartsWith($"@{Info.UserName}")) content = content.TrimStart($"@{Info.UserName}").TrimStart();
45 | // 识别指令
46 | bool hasCommand = content.StartsWith(CommandPrefix);
47 | content = content.TrimStart(CommandPrefix).TrimStart();
48 | if ((hasCommand || (sender.MessageType == MessageType.AtMe) || (sender.MessageType == MessageType.Private)) && (content.Length > 0))
49 | {
50 | // 输出日志信息 (替换 <@!botid> 为 <@botname>)
51 | string msgContent = Regex.Replace(message.Content, @"<@!\d+>", m => message.Mentions?.Find(user => user.Tag == m.Groups[0].Value)?.UserName.Insert(0, "<@") + ">" ?? m.Value);
52 | string senderMaster = (sender.Bot.Guilds.TryGetValue(sender.GuildId, out var guild) ? guild.Name : null) ?? sender.Author.UserName;
53 | Log.Info($"[{senderMaster}][{message.Author.UserName}] {msgContent.Replace("\xA0", " ")}"); // 替换 \xA0 字符为普通空格
54 | // 并行遍历指令列表,提升效率
55 | Command? selectedCMD = Commands.Values.AsParallel().FirstOrDefault(cmd =>
56 | {
57 | Match? cmdMatch = cmd?.Rule.Match(content);
58 | if (cmdMatch?.Success != true) return false;
59 | content = content.TrimStart(cmdMatch.Groups[0].Value).TrimStart();
60 | return true;
61 | }, null);
62 | if (selectedCMD != null)
63 | {
64 | if (selectedCMD.NeedAdmin && !(message.Member.Roles.Any(r => "24".Contains(r)) || message.Author.Id.Equals(GodId)))
65 | {
66 | if (sender.MessageType == MessageType.AtMe) _ = sender.ReplyAsync($"{message.Author.Tag} 你无权使用该命令!");
67 | else return;
68 | }
69 | else selectedCMD.CallBack?.Invoke(sender, content);
70 | return;
71 | }
72 | }
73 |
74 | // 触发Message到达事件
75 | OnMsgCreate?.Invoke(sender);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/SocketEvent/Intent.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Bot.SocketEvent
4 | {
5 | ///
6 | /// 事件订阅权限
7 | ///
8 | /// 基础事件(默认有订阅权限):GUILDS, GUILD_MEMBERS, AT_MESSAGES, GUILD_MESSAGE_REACTIONS
9 | /// 详见:官方文档
10 | ///
11 | ///
12 | [Flags]
13 | public enum Intent
14 | {
15 | ///
16 | ///
17 | /// GUILD_CREATE - 当机器人加入新guild时
18 | /// GUILD_UPDATE - 当guild资料发生变更时
19 | /// GUILD_DELETE - 当机器人退出guild时
20 | /// CHANNEL_CREATE - 当channel被创建时
21 | /// CHANNEL_UPDATE - 当channel被更新时
22 | /// CHANNEL_DELETE - 当channel被删除时
23 | ///
24 | ///
25 | GUILDS = 1 << 0,
26 |
27 | ///
28 | ///
29 | /// GUILD_MEMBER_ADD - 当成员加入时
30 | /// GUILD_MEMBER_UPDATE - 当成员资料变更时
31 | /// GUILD_MEMBER_REMOVE - 当成员被移除时
32 | ///
33 | ///
34 | GUILD_MEMBERS = 1 << 1,
35 |
36 | ///
37 | /// MESSAGE_CREATE - 频道内发送的所有消息的事件(仅私域可用)
38 | ///
39 | MESSAGE_CREATE = 1 << 9,
40 |
41 | ///
42 | ///
43 | /// MESSAGE_REACTION_ADD - 为消息添加表情表态
44 | /// MESSAGE_REACTION_REMOVE - 为消息删除表情表态
45 | ///
46 | ///
47 | GUILD_MESSAGE_REACTIONS = 1 << 10,
48 |
49 | ///
50 | /// DIRECT_MESSAGE_CREATE - 当收到用户发给机器人的私信消息时
51 | ///
52 | DIRECT_MESSAGE_CREATE = 1 << 12,
53 |
54 | ///
55 | /// MESSAGE_AUDIT_PASS - 消息审核通过
56 | /// MESSAGE_AUDIT_REJECT - 消息审核被拒绝
57 | ///
58 | MESSAGE_AUDIT = 1 << 27,
59 |
60 | ///
61 | ///
62 | /// THREAD_CREATE - 当用户创建主题时
63 | /// THREAD_UPDATE - 当用户更新主题时
64 | /// THREAD_DELETE - 当用户删除主题时
65 | /// POST_CREATE - 当用户创建帖子时
66 | /// POST_DELETE - 当用户删除帖子时
67 | /// REPLY_CREATE - 当用户回复评论时
68 | /// REPLY_DELETE - 当用户删除评论时
69 | ///
70 | ///
71 | FORUM_EVENT = 1 << 28,
72 |
73 | ///
74 | ///
75 | /// AUDIO_START - 音频播放开始时
76 | /// AUDIO_FINISH - 音频播放结束时
77 | /// AUDIO_ON_MIC - 上麦时
78 | /// AUDIO_OFF_MIC - 下麦时
79 | ///
80 | ///
81 | AUDIO_ACTION = 1 << 29,
82 |
83 | ///
84 | /// AT_MESSAGE_CREATE - 当收到@机器人的消息时
85 | ///
86 | AT_MESSAGE_CREATE = 1 << 30,
87 | }
88 |
89 | ///
90 | /// 提供事件订阅的默认配置
91 | ///
92 | public static class Intents
93 | {
94 | ///
95 | /// 公域机器人默认开通的事件
96 | ///
97 | public const Intent Public = Intent.GUILDS
98 | | Intent.GUILD_MEMBERS
99 | | Intent.GUILD_MESSAGE_REACTIONS
100 | | Intent.DIRECT_MESSAGE_CREATE
101 | | Intent.MESSAGE_AUDIT
102 | | Intent.AUDIO_ACTION
103 | | Intent.AT_MESSAGE_CREATE;
104 | ///
105 | /// 私域机器人默认开通的事件
106 | ///
107 | public const Intent Private = Public | Intent.MESSAGE_CREATE;
108 | }
109 |
110 | // 服务器推送的通知类型
111 | //public enum ActionType
112 | //{
113 | // GUILD_CREATE,
114 | // GUILD_UPDATE,
115 | // GUILD_DELETE,
116 | // CHANNEL_CREATE,
117 | // CHANNEL_UPDATE,
118 | // CHANNEL_DELETE,
119 | // GUILD_MEMBER_ADD,
120 | // GUILD_MEMBER_UPDATE,
121 | // GUILD_MEMBER_REMOVE,
122 | // MESSAGE_REACTION_ADD,
123 | // MESSAGE_REACTION_REMOVE,
124 | // DIRECT_MESSAGE_CREATE,
125 | // AUDIO_START,
126 | // AUDIO_FINISH,
127 | // AUDIO_OFF_MIC,
128 | // AT_MESSAGE_CREATE,
129 | //}
130 | }
131 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/SocketEvent/Opcode.cs:
--------------------------------------------------------------------------------
1 | namespace QQChannelBot.Bot.SocketEvent
2 | {
3 | ///
4 | /// 枚举操作码
5 | ///
6 | public enum Opcode
7 | {
8 | ///
9 | /// 服务端进行消息推送
10 | /// 客户端操作:Receive
11 | ///
12 | Dispatch = 0,
13 | ///
14 | /// 客户端或服务端发送心跳
15 | /// 客户端操作:Send/Receive
16 | ///
17 | Heartbeat = 1,
18 | ///
19 | /// 客户端发送鉴权
20 | /// 客户端操作:Send
21 | ///
22 | Identify = 2,
23 | ///
24 | /// 客户端恢复连接
25 | /// 客户端操作:Send
26 | ///
27 | Resume = 6,
28 | ///
29 | /// 服务端通知客户端重新连接
30 | /// 客户端操作:Receive
31 | ///
32 | Reconnect = 7,
33 | ///
34 | /// 当identify或resume的时候,如果参数有错,服务端会返回该消息
35 | /// 客户端操作:Receive
36 | ///
37 | InvalidSession = 9,
38 | ///
39 | /// 当客户端与网关建立ws连接之后,网关下发的第一条消息
40 | /// 客户端操作:Receive
41 | ///
42 | Hello = 10,
43 | ///
44 | /// 当发送心跳成功之后,就会收到该消息
45 | /// 客户端操作:Receive
46 | ///
47 | HeartbeatACK = 11,
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/QQChannelBot/Bot/StatusCode/StatusCodes.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Bot.StatusCode
4 | {
5 | ///
6 | /// 状态码对照表
7 | ///
8 | public static class StatusCodes
9 | {
10 | ///
11 | /// API请求状态码对照表
12 | ///
13 | public static readonly Dictionary OpenapiCode = new()
14 | {
15 | //{ 100, "Continue 指示客户端可能继续其请求" },
16 | //{ 101, "SwitchingProtocols 指示正在更改协议版本或协议" },
17 | { 200, "OK 请求成功,且请求的信息包含在响应中。" },
18 | { 201, "Created 请求成功并且服务器创建了新的资源。" },
19 | { 202, "Accepted 服务器已接受请求,但尚未处理。" },
20 | { 203, "NonAuthoritativeInformation 返回的元信息来自缓存副本而不是原始服务器,因此可能不正确。" },
21 | { 204, "NoContent 服务器成功处理了请求,但没有返回任何内容。" },
22 | { 205, "ResetContent 客户端应重置(或重新加载)当前资源。" },
23 | { 206, "PartialContent 已部分下载了一个文件,可以续传损坏的下载。" },
24 | { 300, "MultipleChoices 请求的信息有多种表示形式。" },
25 | { 301, "MovedPermanently 请求的信息已永久移动到 Location 头中指定的 URI。" },
26 | { 302, "Redirect 服务器目前使用其它位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。" },
27 | { 303, "SeeOther 将客户端自动重定向到 Location 头中指定的 URI。" },
28 | { 304, "NotModified 客户端的缓存副本是最新的。 未传输此资源的内容。" },
29 | { 305, "UseProxy 请求应使用位于 Location 头中指定的 URI 的代理服务器进行访问。" },
30 | { 307, "TemporaryRedirect 服务器目前使用其它位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。" },
31 | { 400, "BadRequest 服务器未能识别请求。" },
32 | { 401, "Unauthorized 请求的资源要求身份验证。" },
33 | { 403, "Forbidden 服务器拒绝请求。" },
34 | { 404, "NotFound 服务器找不到请求的资源。" },
35 | { 405, "MethodNotAllowed 禁止使用当前指定的方法进行请求。" },
36 | { 406, "NotAcceptable 无法使用请求的内容特性响应请求。" },
37 | { 407, "ProxyAuthenticationRequired 表示请求的代理要求身份验证。" },
38 | { 408, "RequestTimeout 服务器等候请求时发生超时。" },
39 | { 409, "Conflict 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。" },
40 | { 410, "Gone 请求的资源已永久删除。" },
41 | { 411, "LengthRequired 缺少必需的 Content-length 头。" },
42 | { 429, "频率限制" },
43 | { 500, "InternalServerError 服务器遇到错误,无法完成请求。" },
44 | { 501, "NotImplemented 服务器不支持请求的函数方法。" },
45 | { 502, "BadGateway 服务器作为网关或代理,从上游服务器收到无效响应。" },
46 | { 503, "ServiceUnavailable 服务器暂时不可用,通常是由于过载或维护。" },
47 | { 504, "GatewayTimeout 服务器作为网关或代理,但是没有及时从上游服务器收到请求。" },
48 | { 505, "HttpVersionNotSupported 服务器不支持请求的HTTP 版本。" },
49 | { 4001, "无效的 opcode" },
50 | { 4002, "无效的 payload" },
51 | { 4006, "无效的 session_id,无法继续 resume,请重新发起 identify" },
52 | { 4007, "seq 错误" },
53 | { 4008, "发送 payload 过快,请重新连接,并遵守连接后返回的频控信息" },
54 | { 4009, "连接过期,请重连" },
55 | { 4010, "无效的 shard" },
56 | { 4011, "连接需要处理的 guild 过多,请进行合理的分片" },
57 | { 4012, "无效的 version" },
58 | { 4013, "无效的 intent" },
59 | { 4014, "intent 无权限" },
60 | { 4900, " 4900~4913 内部错误,请重连" },
61 | { 4914, "机器人已下架,只允许连接沙箱环境,请断开连接,检验当前连接环境" },
62 | { 4915, "机器人已封禁,不允许连接,请断开连接,申请解封后再连接" },
63 | { 10001, "UnknownAccount 账号异常" },
64 | { 10003, "UnknownChannel 子频道异常" },
65 | { 10004, "UnknownGuild 频道异常" },
66 | { 11281, "ErrorCheckAdminFailed 检查是否是管理员失败,系统错误,一般重试一次会好,最多只能重试一次" },
67 | { 11282, "ErrorCheckAdminNotPass 检查是否是管理员未通过,该接口需要管理员权限,但是用户在添加机器人的时候并未授予该权限,属于逻辑错误,可以提示用户进行授权" },
68 | { 11251, "ErrorWrongAppid 参数中的 appid 错误,开发者填的 token 错误,appid 无法识别" },
69 | { 11252, "ErrorCheckAppPrivilegeFailed 检查应用权限失败,系统错误,一般重试一次会好,最多只能重试一次" },
70 | { 11253, "ErrorCheckAppPrivilegeNotPass 检查应用权限不通过,该机器人应用未获得调用该接口的权限,需要向平台申请" },
71 | { 11254, "ErrorInterfaceForbidden 应用接口被封禁,该机器人虽然获得了该接口权限,但是被封禁了。" },
72 | { 11261, "ErrorWrongAppid 参数中缺少 appid,同 11251" },
73 | { 11262, "ErrorCheckRobot 当前接口不支持使用机器人 Bot Token 调用" },
74 | { 11263, "ErrorCheckGuildAuth 检查频道权限失败,系统错误,一般重试一次会好,最多只能重试一次" },
75 | { 11264, "ErrorGuildAuthNotPass 检查小站权限未通过,管理员添加机器人的时候未授予该接口权限,属于逻辑错误,可提示用户进行授权" },
76 | { 11265, "ErrorRobotHasBaned 机器人已经被封禁" },
77 | { 11241, "ErrorWrongToken 参数中缺少 token" },
78 | { 11242, "ErrorCheckTokenFailed 校验 token 失败,系统错误,一般重试一次会好,最多只能重试一次" },
79 | { 11243, "ErrorCheckTokenNotPass 校验 token 未通过,用户填充的 token 错误,需要开发者进行检查" },
80 | { 11273, "ErrorCheckUserAuth 检查用户权限失败,当前接口不支持使用 Bearer Token 调用" },
81 | { 11274, "ErrorUserAuthNotPass 检查用户权限未通过,用户 OAuth 授权时未给与该接口权限,可提示用户重新进行授权" },
82 | { 11275, "ErrorWrongAppid 无 appid ,同 11251" },
83 | { 12001, "ReplaceIDFailed 替换 id 失败" },
84 | { 12002, "RequestInvalid 请求体错误" },
85 | { 12003, "ResponseInvalid 回包错误" },
86 | { 20028, "ChannelHitWriteRateLimit 子频道消息触发限频" },
87 | { 50006, "CannotSendEmptyMessage 消息为空" },
88 | { 50035, "InvalidFormBody form-data 内容异常" },
89 | { 50037, "只能发送 markdown 消息" },
90 | { 50038, "非同频道同子频道" },
91 | { 50039, "获取消息失败" },
92 | { 50040, "消息模版类型错误" },
93 | { 50041, "markdown 有空值" },
94 | { 50042, "markdown 列表长达最大值" },
95 | { 50043, "guild_id 转换失败" },
96 | { 50045, "不能回复机器人自己产生的消息" },
97 | { 50046, "非 at 机器人消息" },
98 | { 50047, "非机器人产生的消息 或者 at 机器人消息" },
99 | { 301000, "参数错误" },
100 | { 301001, "查询频道信息错误" },
101 | { 301002, "查询子频道权限错误" },
102 | { 301003, "修改子频道权限错误" },
103 | { 301004, "私密子频道关联的人数到达上限" },
104 | { 301005, "调用 Rpc 服务失败" },
105 | { 301006, "非群成员没有查询权限" },
106 | { 301007, "参数超过数量限制" },
107 | { 302000, "参数错误" },
108 | { 302001, "查询频道信息错误" },
109 | { 302002, "查询日程列表失败" },
110 | { 302003, "查询日程失败" },
111 | { 302004, "修改日程失败" },
112 | { 302005, "删除日程失败" },
113 | { 302006, "创建日程失败" },
114 | { 302007, "获取创建者信息失败" },
115 | { 302008, "子频道 ID 不能为空" },
116 | { 302009, "频道系统错误,请联系客服" },
117 | { 302010, "暂无修改日程权限" },
118 | { 302011, "日程活动已被删除" },
119 | { 302012, "每天只能创建 10 个日程,明天再来吧!" },
120 | { 302013, "创建日程触发安全打击" },
121 | { 302014, "日程持续时间超过 7 天,请重新选择" },
122 | { 302015, "开始时间不能早于当前时间" },
123 | { 302016, "结束时间不能早于开始时间" },
124 | { 302017, "Schedule 对象为空" },
125 | { 302018, "参数类型转换失败" },
126 | { 302019, "调用下游失败,请联系客服" },
127 | { 302020, "日程内容违规、账号违规" },
128 | { 302021, "频道内当日新增活动达上限" },
129 | { 302022, "不能绑定非当前频道的子频道" },
130 | { 302023, "开始时跳转不可绑定日程子频道" },
131 | { 302024, "绑定的子频道不存在" },
132 | { 502000, "禁言相关错误" },
133 | { 502001, "频道 id 无效" },
134 | { 502002, "频道 id 为空" },
135 | { 502003, "用户 id 无效" },
136 | { 502004, "用户 id 为空" },
137 | { 502005, "timestamp 不合法" },
138 | { 502006, "timestamp 无效" },
139 | { 502007, "参数转换错误" },
140 | { 502008, "rpc 调用失败" },
141 | { 502009, "安全打击" },
142 | { 502010, "请求头错误" },
143 | { 304003, "URL_NOT_ALLOWED url 未报备" },
144 | { 304004, "ARK_NOT_ALLOWED 没有发 ark 消息权限" },
145 | { 304005, "EMBED_LIMIT embed 长度超限" },
146 | { 304006, "SERVER_CONFIG 后台配置错误" },
147 | { 304007, "GET_GUILD 查询频道异常" },
148 | { 304008, "GET_BOT 查询机器人异常" },
149 | { 304009, "GET_CHENNAL 查询子频道异常" },
150 | { 304010, "CHANGE_IMAGE_URL 图片转存错误" },
151 | { 304011, "NO_TEMPLATE 模板不存在" },
152 | { 304012, "GET_TEMPLATE 取模板错误" },
153 | { 304014, "TEMPLATE_PRIVILEGE 没有模板权限" },
154 | { 304016, "SEND_ERROR 发消息错误" },
155 | { 304017, "UPLOAD_IMAGE 图片上传错误" },
156 | { 304018, "SESSION_NOT_EXIST 机器人没连上 gateway" },
157 | { 304019, "AT_EVERYONE_TIMES @全体成员 次数超限" },
158 | { 304020, "FILE_SIZE 文件大小超限" },
159 | { 304021, "GET_FILE 下载文件错误" },
160 | { 304022, "PUSH_TIME 推送消息时间限制" },
161 | { 304023, "PUSH_MSG_ASYNC_OK 推送消息异步调用成功, 等待人工审核" },
162 | { 304024, "REPLY_MSG_ASYNC_OK 回复消息异步调用成功, 等待人工审核" },
163 | { 304025, "BEAT 消息被打击" },
164 | { 304026, "MSG_ID 回复的消息 id 错误" },
165 | { 304027, "MSG_EXPIRE 回复的消息过期" },
166 | { 304028, "MSG_PROTECT 非 At 当前用户的消息不允许回复" },
167 | { 304029, "CORPUS_ERROR 调语料服务错误" },
168 | { 304030, "CORPUS_NOT_MATCH 语料不匹配" },
169 | { 306001, "param invalid 撤回消息参数错误" },
170 | { 306002, "msgid error 消息 id 错误" },
171 | { 306003, "fail to get message 获取消息错误(可重试)" },
172 | { 306004, "no permission to delete message 没有撤回此消息的权限" },
173 | { 306005, "retract message error 消息撤回失败(可重试)" },
174 | { 306006, "fail to get channel 获取子频道失败(可重试)" },
175 | { 500000, "公告错误" },
176 | { 501001, "参数校验失败" },
177 | { 501002, "创建子频道公告失败(可重试)" },
178 | { 501003, "删除子频道公告失败(可重试)" },
179 | { 501004, "获取频道信息失败(可重试)" },
180 | { 501005, "MessageID 错误" },
181 | { 501006, "创建频道全局公告失败(可重试)" },
182 | { 501007, "删除频道全局公告失败(可重试)" },
183 | { 501008, "MessageID 不存在" },
184 | { 610000, "频道权限错误" },
185 | { 610001, "获取频道 ID 失败" },
186 | { 610002, "获取 HTTP 头失败" },
187 | { 610003, "获取机器人号码失败" },
188 | { 610004, "获取机器人角色失败" },
189 | { 610005, "获取机器人角色内部错误" },
190 | { 610006, "拉取机器人权限列表失败" },
191 | { 610007, "机器人不在频道内" },
192 | { 610008, "无效参数" },
193 | { 610009, "获取 API 接口详情失败" },
194 | { 610010, "API 接口已授权" },
195 | { 610011, "获取机器人信息失败" },
196 | { 610012, "限频失败" },
197 | { 610013, "已限频" },
198 | { 610014, "api 授权链接发送失败" },
199 | { 1000000, "发消息错误" },
200 | { 1100100, "安全打击:消息被限频" },
201 | { 1100101, "安全打击:内容涉及敏感,请返回修改" },
202 | { 1100102, "安全打击:抱歉,暂未获得新功能体验资格" },
203 | { 1100103, "安全打击" },
204 | { 1100104, "安全打击:该群已失效或当前群已不存在" },
205 | { 1100300, "系统内部错误" },
206 | { 1100301, "调用方不是群成员" },
207 | { 1100302, "获取指定频道名称失败" },
208 | { 1100303, "主页频道非管理员不允许发消息" },
209 | { 1100304, "@次数鉴权失败" },
210 | { 1100305, "TinyId 转换 Uin 失败" },
211 | { 1100306, "非私有频道成员" },
212 | { 1100307, "非白名单应用子频道" },
213 | { 1100308, "触发频道内限频" },
214 | { 1100499, "其他错误" },
215 | };
216 | }
217 |
218 | ///
219 | /// API请求状态
220 | ///
221 | public class ApiStatus
222 | {
223 | ///
224 | /// 代码
225 | ///
226 | [JsonPropertyName("code")]
227 | public int Code { get; set; }
228 | ///
229 | /// 原因
230 | ///
231 | [JsonPropertyName("message")]
232 | public string? Message { get; set; }
233 | }
234 |
235 | ///
236 | /// API请求出错的关键信息
237 | ///
238 | public class ApiErrorInfo
239 | {
240 | ///
241 | /// API请求出错的关键信息
242 | ///
243 | /// 接口地址
244 | /// 请求方式
245 | /// 错误代码
246 | /// 错误详情
247 | /// 接口被暂时停用的时间
248 | public ApiErrorInfo(string path, string method, int code, string detail, FreezeTime freezeTime)
249 | {
250 | Path = path;
251 | Method = method;
252 | Code = code;
253 | Detail = detail;
254 | FreezeTime = freezeTime;
255 | }
256 | ///
257 | /// 接口地址
258 | ///
259 | public string Path { get; init; }
260 | ///
261 | /// 请求方式
262 | ///
263 | public string Method { get; init; }
264 | ///
265 | /// 错误代码
266 | ///
267 | public int Code { get; set; }
268 | ///
269 | /// 错误信息
270 | ///
271 | public string Detail { get; set; }
272 | ///
273 | /// 接口被暂时停用的时间
274 | ///
275 | public FreezeTime FreezeTime { get; init; }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/APIPermissions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 接口权限对象
7 | ///
8 | public class APIPermission
9 | {
10 | ///
11 | /// 接口地址
12 | ///
13 | /// 例:/guilds/{guild_id}/members/{user_id}
14 | ///
15 | ///
16 | [JsonPropertyName("path")]
17 | public string Path { get; set; } = string.Empty;
18 | ///
19 | /// 请求方法,例:GET
20 | ///
21 | [JsonPropertyName("method")]
22 | public string Method { get; set; } = "GET";
23 | ///
24 | /// API 接口名称,例:获取频道信息
25 | ///
26 | [JsonPropertyName("desc")]
27 | public string Desc { get; set; } = string.Empty;
28 | ///
29 | /// 授权状态
30 | ///
31 | /// 0 - 未授权
32 | /// 1 - 已授权
33 | ///
34 | ///
35 | [JsonPropertyName("auth_status")]
36 | public int AuthStatus { get; set; } = 0;
37 | }
38 | ///
39 | /// 接口权限列表对象
40 | ///
41 | public class APIPermissions
42 | {
43 | ///
44 | /// 接口权限列表
45 | ///
46 | [JsonPropertyName("apis")]
47 | public List? List { get; set; }
48 | }
49 |
50 | ///
51 | /// 接口权限需求对象
52 | ///
53 | public class APIPermissionDemand
54 | {
55 | ///
56 | /// 申请接口权限的频道 id
57 | ///
58 | [JsonPropertyName("guild_id")]
59 | public string? GuildId { get; set; }
60 | ///
61 | /// 接口权限需求授权链接发送的子频道 id
62 | ///
63 | [JsonPropertyName("channel_id")]
64 | public string? ChannelId { get; set; }
65 | ///
66 | /// 权限接口唯一标识
67 | ///
68 | [JsonPropertyName("api_identify")]
69 | public APIPermissionDemandIdentify ApiIdentify { get; set; }
70 | ///
71 | /// 接口权限链接中的接口权限描述信息
72 | ///
73 | [JsonPropertyName("title")]
74 | public string Title { get; set; } = string.Empty;
75 | ///
76 | /// 接口权限链接中的机器人可使用功能的描述信息
77 | ///
78 | [JsonPropertyName("desc")]
79 | public string Desc { get; set; } = string.Empty;
80 | }
81 | ///
82 | /// 接口权限需求标识对象
83 | ///
84 | public record struct APIPermissionDemandIdentify
85 | {
86 | ///
87 | /// 接口地址
88 | ///
89 | /// 例:/guilds/{guild_id}/members/{user_id}
90 | ///
91 | ///
92 | [JsonPropertyName("path")]
93 | public string Path { get; init; }
94 | ///
95 | /// 请求方法,例:GET
96 | ///
97 | [JsonPropertyName("method")]
98 | public string Method { get; init; }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Announces.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 公告对象
7 | ///
8 | public class Announces
9 | {
10 | ///
11 | /// 频道 id
12 | ///
13 | [JsonPropertyName("guild_id")]
14 | public string? GuildId { get; set; }
15 | ///
16 | /// 子频道 id
17 | ///
18 | [JsonPropertyName("channel_id")]
19 | public string? ChannelId { get; set; }
20 | ///
21 | /// 消息 id
22 | ///
23 | [JsonPropertyName("message_id")]
24 | public string? MessageId { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/AudioControl.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 音频对象
7 | ///
8 | public class AudioControl
9 | {
10 | ///
11 | /// 构建音频消息
12 | ///
13 | /// 播放状态
14 | /// 音频数据URL
15 | /// 状态文本
16 | public AudioControl(STATUS status, string? audioUrl = null, string? text = null)
17 | {
18 | AudioUrl = audioUrl;
19 | Status = status;
20 | Text = text;
21 | }
22 | ///
23 | /// 音频数据的url status为0时传
24 | ///
25 | [JsonPropertyName("audio_url")]
26 | public string? AudioUrl { get; set; }
27 | ///
28 | /// 状态文本(比如:简单爱-周杰伦),可选,status为0时传,其他操作不传
29 | ///
30 | [JsonPropertyName("text")]
31 | public string? Text { get; set; }
32 | ///
33 | /// 播放状态,参考 STATUS
34 | ///
35 | [JsonPropertyName("status")]
36 | public STATUS Status { get; set; }
37 | }
38 | ///
39 | /// 枚举播放状态
40 | ///
41 | public enum STATUS
42 | {
43 | ///
44 | /// 开始播放操作
45 | ///
46 | START,
47 | ///
48 | /// 暂停播放操作
49 | ///
50 | PAUSE,
51 | ///
52 | /// 继续播放操作
53 | ///
54 | RESUME,
55 | ///
56 | /// 停止播放操作
57 | ///
58 | STOP
59 | }
60 | ///
61 | /// 语音Action
62 | ///
63 | public class AudioAction
64 | {
65 | ///
66 | /// 频道id
67 | ///
68 | [JsonPropertyName("guild_id")]
69 | public string? GuildId { get; set; }
70 | ///
71 | /// 子频道id
72 | ///
73 | [JsonPropertyName("channel_id")]
74 | public string? ChannelId { get; set; }
75 | ///
76 | /// 音频数据的url status为0时传
77 | ///
78 | [JsonPropertyName("audio_url")]
79 | public string? AudioUrl { get; set; }
80 | ///
81 | /// 状态文本(比如:简单爱-周杰伦),可选,status为0时传,其他操作不传
82 | ///
83 | [JsonPropertyName("text")]
84 | public string? Text { set; get; }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Channel.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 子频道对象
7 | ///
8 | public class Channel
9 | {
10 | ///
11 | /// 子频道id
12 | ///
13 | [JsonPropertyName("id")]
14 | public string Id { get; set; } = string.Empty;
15 | ///
16 | /// 频道id
17 | ///
18 | [JsonPropertyName("guild_id")]
19 | public string GuildId { get; set; } = string.Empty;
20 | ///
21 | /// 子频道名称
22 | ///
23 | [JsonPropertyName("name")]
24 | public string Name { get; set; } = string.Empty;
25 | ///
26 | /// 子频道类型
27 | ///
28 | [JsonPropertyName("type")]
29 | public ChannelType Type { get; set; }
30 | ///
31 | /// 子频道子类型
32 | ///
33 | [JsonPropertyName("sub_type")]
34 | public ChannelSubType? SubType { get; set; }
35 | ///
36 | /// 频道位置排序,非必填,但不能够和其他子频道的值重复
37 | ///
38 | [JsonPropertyName("position")]
39 | public int Possition { get; set; }
40 | ///
41 | /// 分组Id
42 | ///
43 | [JsonPropertyName("parent_id")]
44 | public string? ParentId { get; set; }
45 | ///
46 | /// 创建人Id
47 | ///
48 | [JsonPropertyName("owner_id")]
49 | public string? OwerId { get; set; }
50 | ///
51 | /// 子频道私密类型
52 | ///
53 | [JsonPropertyName("private_type")]
54 | public ChannelPrivateType PrivateType { get; set; }
55 | ///
56 | /// 子频道私密类型成员Id
57 | ///
58 | [JsonPropertyName("private_user_ids")]
59 | public List? PrivateUserIds { get; set; }
60 | ///
61 | /// 子频道发言权限
62 | ///
63 | [JsonPropertyName("speak_permission")]
64 | public ChannelSpeakPermission SpeakPermission { get; set; }
65 | ///
66 | /// 用于标识应用子频道应用类型,仅应用子频道时会使用该字段
67 | /// 具体定义请参考 应用子频道的应用类型
68 | ///
69 | [JsonPropertyName("application_id")]
70 | public string? ApplicationId { get; set; }
71 | ///
72 | /// 应用子频道类型列表
73 | ///
74 | public static Dictionary AppType => new()
75 | {
76 | { "1000000", "王者开黑大厅" },
77 | { "1000001", "互动小游戏" },
78 | { "1000010", "腾讯投票" },
79 | { "1000051", "飞车开黑大厅" },
80 | { "1000050", "日程提醒" },
81 | { "1000070", "CoDM开黑大厅" },
82 | { "1010000", "和平精英开黑大厅" },
83 | };
84 | ///
85 | /// #频道名 标签
86 | /// 数据内容为:<#ChannelId>
87 | ///
88 | [JsonIgnore]
89 | public string Tag => $"<#{Id}>";
90 | }
91 | ///
92 | /// 子频道类型
93 | ///
94 | public enum ChannelType
95 | {
96 | ///
97 | /// 文字子频道
98 | ///
99 | 文字 = 0,
100 | ///
101 | /// 保留,不可用
102 | ///
103 | Reserve1 = 1,
104 | ///
105 | /// 语音子频道
106 | ///
107 | 语音 = 2,
108 | ///
109 | /// 保留,不可用
110 | ///
111 | Reserve2 = 3,
112 | ///
113 | /// 子频道分组
114 | ///
115 | 分组 = 4,
116 | ///
117 | /// 直播子频道
118 | ///
119 | 直播 = 10005,
120 | ///
121 | /// 应用子频道
122 | ///
123 | 应用 = 10006,
124 | ///
125 | /// 论坛子频道
126 | ///
127 | 论坛 = 10007
128 | }
129 | ///
130 | /// 子频道子类型(目前只有文字子频道有)
131 | ///
132 | public enum ChannelSubType
133 | {
134 | ///
135 | /// 闲聊
136 | ///
137 | 闲聊,
138 | ///
139 | /// 公告
140 | ///
141 | 公告,
142 | ///
143 | /// 攻略
144 | ///
145 | 攻略,
146 | ///
147 | /// 开黑
148 | ///
149 | 开黑
150 | }
151 | ///
152 | /// 子频道私密类型
153 | ///
154 | public enum ChannelPrivateType
155 | {
156 | ///
157 | /// 公开频道
158 | ///
159 | Public,
160 | ///
161 | /// 群主和管理员可见
162 | ///
163 | Admin,
164 | ///
165 | /// 群主和管理员+指定成员可见
166 | ///
167 | Members
168 | }
169 | ///
170 | /// 子频道发言权限
171 | ///
172 | public enum ChannelSpeakPermission
173 | {
174 | ///
175 | /// 无效类型
176 | ///
177 | Null,
178 | ///
179 | /// 所有人
180 | ///
181 | Everyone,
182 | ///
183 | /// 群主和管理员+指定成员
184 | ///
185 | Members
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/ChannelPermissions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 子频道权限对象
7 | ///
8 | public class ChannelPermissions
9 | {
10 | ///
11 | /// 子频道Id
12 | ///
13 | [JsonPropertyName("channel_id")]
14 | public string ChaannelId { get; set; } = "";
15 | ///
16 | /// 用户Id
17 | /// 此属性和RoleId只会同时存在一个
18 | ///
19 | [JsonPropertyName("user_id")]
20 | public string? UserId { get; set; }
21 | ///
22 | /// 身份组Id
23 | /// 此属性和UserId只会同时存在一个
24 | ///
25 | [JsonPropertyName("role_id")]
26 | public string? RoleId { get; set; }
27 | ///
28 | /// 用户拥有的子频道权限
29 | ///
30 | /// "1" - 查看
31 | /// "2" - 管理
32 | /// "4" - 发言
33 | ///
34 | ///
35 | [JsonPropertyName("permissions"), JsonConverter(typeof(PrivacyTypeToStringNumberConverter))]
36 | public PrivacyType Permissions { get; set; }
37 | }
38 |
39 | ///
40 | /// 子频道私密权限
41 | ///
42 | [Flags]
43 | public enum PrivacyType
44 | {
45 | ///
46 | /// 没有任何权限
47 | ///
48 | 隐藏 = 0,
49 | ///
50 | /// 可查看子频道
51 | ///
52 | 查看 = 1 << 0,
53 | ///
54 | /// 可管理子频道
55 | ///
56 | 管理 = 1 << 1,
57 | ///
58 | /// 可发言子频道
59 | ///
60 | 发言 = 1 << 2
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Emoji.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 表情类型
7 | ///
8 | public enum EmojiType
9 | {
10 | ///
11 | /// 系统表情
12 | ///
13 | System = 1,
14 | ///
15 | /// emoji表情
16 | ///
17 | Emoji = 2,
18 | }
19 |
20 | ///
21 | /// 表情对象
22 | ///
23 | public class Emoji
24 | {
25 | ///
26 | /// 表情ID
27 | ///
28 | /// 系统表情使用数字为ID,emoji使用emoji本身为id,参考 EmojiType 列表
29 | ///
30 | ///
31 | [JsonPropertyName("id")]
32 | public string? Id { get; set; }
33 | ///
34 | /// 表情类型
35 | ///
36 | [JsonPropertyName("type")]
37 | public EmojiType Type { get; set; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Guild.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 频道对象
7 | ///
8 | public class Guild
9 | {
10 | ///
11 | /// 频道ID
12 | ///
13 | [JsonPropertyName("id")]
14 | public string Id { get; set; } = string.Empty;
15 | ///
16 | /// 频道名称
17 | ///
18 | [JsonPropertyName("name")]
19 | public string? Name { get; set; }
20 | ///
21 | /// 频道头像地址
22 | ///
23 | [JsonPropertyName("icon")]
24 | public string? Icon { get; set; }
25 | ///
26 | /// 频道创建人用户ID
27 | ///
28 | [JsonPropertyName("owner_id")]
29 | public string? OwnerId { get; set; }
30 | ///
31 | /// 当前人是否是频道创建人
32 | ///
33 | [JsonPropertyName("owner")]
34 | public bool Owner { get; set; }
35 | ///
36 | /// 成员数
37 | ///
38 | [JsonPropertyName("member_count")]
39 | public int MemberCount { get; set; }
40 | ///
41 | /// 最大成员数
42 | ///
43 | [JsonPropertyName("max_members")]
44 | public int MaxMembers { get; set; }
45 | ///
46 | /// 频道描述
47 | ///
48 | [JsonPropertyName("description")]
49 | public string? Description { get; set; }
50 | ///
51 | /// 频道创建时间
52 | ///
53 | [JsonPropertyName("joined_at"), JsonConverter(typeof(DateTimeToStringTimestamp))]
54 | public DateTime JoinedAt { get; set; }
55 | ///
56 | /// 机器人在本频道内拥有的权限的列表
57 | ///
58 | [JsonIgnore]
59 | public List? APIPermissions { get; set; }
60 | }
61 |
62 | ///
63 | /// 频道信息
64 | ///
65 | public class GuildInfo : Guild
66 | {
67 | ///
68 | /// 子频道列表
69 | ///
70 | public HashSet Channels { get; set; } = new();
71 | ///
72 | /// 角色列表
73 | ///
74 | public HashSet Roles { get; set; } = new();
75 | ///
76 | /// 成员列表
77 | ///
78 | public HashSet Members { get; set; } = new();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/JsonConverters.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 | using System.Text.RegularExpressions;
5 | using QQChannelBot.Bot.SocketEvent;
6 |
7 | namespace QQChannelBot.Models
8 | {
9 | ///
10 | /// JSON序列化时将 bool 转换为 int
11 | /// JSON反序列化时将 int 转换为 bool
12 | ///
13 | public class BoolToInt32Converter : JsonConverter
14 | {
15 | ///
16 | /// 序列化JSON时 bool 转 int
17 | ///
18 | ///
19 | ///
20 | ///
21 | public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
22 | {
23 | writer.WriteNumberValue(value ? 1 : 0);
24 | }
25 | ///
26 | /// 反序列化JSON时 int 转 bool
27 | ///
28 | ///
29 | ///
30 | ///
31 | ///
32 | public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
33 | {
34 | return reader.TokenType switch
35 | {
36 | JsonTokenType.True => true,
37 | JsonTokenType.False => false,
38 | JsonTokenType.Number => reader.TryGetInt32(out int num) && Convert.ToBoolean(num),
39 | _ => false,
40 | };
41 | }
42 | }
43 |
44 | ///
45 | /// JSON序列化时将 Color 转换为 UInt32
46 | /// JSON反序列化时将 UInt32 转换为 Color
47 | ///
48 | public class ColorToUint32Converter : JsonConverter
49 | {
50 | ///
51 | /// 序列化JSON时 Color 转 int
52 | ///
53 | ///
54 | ///
55 | ///
56 | public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
57 | {
58 | writer.WriteNumberValue((uint)value.ToArgb());
59 | }
60 | ///
61 | /// 反序列化JSON时 int 转 Color
62 | ///
63 | ///
64 | ///
65 | ///
66 | ///
67 | public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
68 | {
69 | return reader.TokenType switch
70 | {
71 | JsonTokenType.Number => Color.FromArgb((int)reader.GetUInt32()),
72 | _ => Color.Black,
73 | };
74 | }
75 | }
76 |
77 | ///
78 | /// JSON序列化时将 RemindType 转换为 StringNumber
79 | /// JSON反序列化时将 StringNumber 转换为 RemindType
80 | ///
81 | public class RemindTypeToStringNumberConverter : JsonConverter
82 | {
83 | ///
84 | /// JSON序列化时将 RemindType 转 StringNumber
85 | ///
86 | ///
87 | ///
88 | ///
89 | public override void Write(Utf8JsonWriter writer, RemindType value, JsonSerializerOptions options)
90 | {
91 | writer.WriteStringValue(value.ToString("D"));
92 | }
93 | ///
94 | /// JSON反序列化时将 StringNumber 转 RemindType
95 | ///
96 | ///
97 | ///
98 | ///
99 | ///
100 | public override RemindType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
101 | {
102 | int numCode = reader.TokenType switch
103 | {
104 | JsonTokenType.String => int.TryParse(reader.GetString(), out int value) ? value : 0,
105 | JsonTokenType.Number => reader.GetInt32(),
106 | _ => 0
107 | };
108 | return Enum.GetNames(typeof(RemindType)).Length > numCode ? (RemindType)numCode : RemindType.Never;
109 | }
110 | }
111 |
112 | ///
113 | /// JSON序列化时将 PrivacyType 转换为 StringNumber
114 | /// JSON反序列化时将 StringNumber 转换为 PrivacyType
115 | ///
116 | public class PrivacyTypeToStringNumberConverter : JsonConverter
117 | {
118 | ///
119 | /// JSON序列化时将 PrivacyType 转 StringNumber
120 | ///
121 | ///
122 | ///
123 | ///
124 | public override void Write(Utf8JsonWriter writer, PrivacyType value, JsonSerializerOptions options)
125 | {
126 | writer.WriteStringValue(value.ToString("D"));
127 | }
128 | ///
129 | /// JSON反序列化时将 StringNumber 转 PrivacyType
130 | ///
131 | ///
132 | ///
133 | ///
134 | ///
135 | public override PrivacyType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
136 | {
137 | int numCode = reader.TokenType switch
138 | {
139 | JsonTokenType.String => int.TryParse(reader.GetString(), out int value) ? value : 0,
140 | JsonTokenType.Number => reader.GetInt32(),
141 | _ => 0
142 | };
143 | return (PrivacyType)numCode;
144 | }
145 | }
146 |
147 | ///
148 | /// JSON序列化JSON时将 DateTime 转换为 Timestamp
149 | /// JSON反序列化JSON时将 Timestamp 转换为 DateTime
150 | ///
151 | public class DateTimeToStringTimestamp : JsonConverter
152 | {
153 | ///
154 | /// 序列化JSON时 DateTime 转 Timestamp
155 | ///
156 | ///
157 | ///
158 | ///
159 | public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
160 | {
161 | writer.WriteStringValue($"{value:yyyy-MM-ddTHH:mm:sszzz}");
162 | }
163 | ///
164 | /// 反序列化JSON时 Timestamp 转 DateTime
165 | ///
166 | ///
167 | ///
168 | ///
169 | ///
170 | public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
171 | {
172 | switch (reader.TokenType)
173 | {
174 | case JsonTokenType.Number:
175 | return DateTime.MinValue.AddMilliseconds(reader.GetDouble());
176 | case JsonTokenType.String:
177 | string timeStamp = reader.GetString() ?? "0";
178 | Match timeMatch = Regex.Match(timeStamp, @"^\d+$");
179 | if (timeMatch.Success)
180 | {
181 | double offsetVal = double.Parse(timeMatch.Groups[0].Value);
182 | return timeStamp.Length <= 10 ? DateTime.MinValue.AddSeconds(offsetVal) : DateTime.MinValue.AddMilliseconds(offsetVal);
183 | }
184 | else
185 | {
186 | return DateTime.TryParse(timeStamp, out DateTime result) ? result : DateTime.MinValue;
187 | }
188 | default:
189 | return DateTime.MinValue;
190 | }
191 | }
192 | }
193 |
194 | ///
195 | /// JSON序列化时将 Intent 转换为 StringArray
196 | /// JSON反序列化时将 StringArray 转换为 Intent
197 | ///
198 | public class IntentToStringArrayConverter : JsonConverter
199 | {
200 | ///
201 | /// JSON序列化时将 RemindType 转 StringNumber
202 | ///
203 | ///
204 | ///
205 | ///
206 | public override void Write(Utf8JsonWriter writer, Intent value, JsonSerializerOptions options)
207 | {
208 | JsonSerializer.Serialize(writer, value.ToString().Split(',').Select(f => f.Trim()), options);
209 | }
210 | ///
211 | /// JSON反序列化时将 StringNumber 转 RemindType
212 | ///
213 | ///
214 | ///
215 | ///
216 | ///
217 | public override Intent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
218 | {
219 | var intents = JsonSerializer.Deserialize>(ref reader, options)?.Select(s => Enum.Parse(s));
220 | return intents?.Aggregate((a, b) => a | b) ?? Intents.Public;
221 | }
222 | }
223 | }
--------------------------------------------------------------------------------
/QQChannelBot/Models/Member.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 成员
7 | ///
8 | public class Member
9 | {
10 | ///
11 | /// 用户基础信息,来自QQ资料,只有成员相关接口中会填充此信息
12 | ///
13 | [JsonPropertyName("user")]
14 | public User? User { get; set; }
15 | ///
16 | /// 用户在频道内的昵称(默认为空)
17 | ///
18 | [JsonPropertyName("nick")]
19 | public string? Nick { get; set; }
20 | ///
21 | /// 用户在频道内的身份组ID, 默认值可参考DefaultRoles
22 | ///
23 | [JsonPropertyName("roles")]
24 | public List Roles { get; set; } = new() { "1" };
25 | ///
26 | /// 用户加入频道的时间 ISO8601 timestamp
27 | ///
28 | [JsonPropertyName("joined_at"), JsonConverter(typeof(DateTimeToStringTimestamp))]
29 | public DateTime? JoinedAt { get; set; }
30 | ///
31 | /// 该字段作用未知,等待官方文档更新
32 | ///
33 | [JsonPropertyName("deaf")]
34 | public bool? Deaf { get; set; }
35 | ///
36 | /// 该成员是否被禁言
37 | ///
38 | [JsonPropertyName("mute")]
39 | public bool? Mute { get; set; }
40 | ///
41 | /// 该字段作用未知,等待官方文档更新
42 | ///
43 | [JsonPropertyName("pending")]
44 | public bool? Pending { get; set; }
45 | }
46 |
47 | ///
48 | /// 有频道ID的成员
49 | ///
50 | public class MemberWithGuildID : Member
51 | {
52 | ///
53 | /// 频道id
54 | ///
55 | [JsonPropertyName("guild_id")]
56 | public string GuildId { get; set; } = string.Empty;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Message.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 消息类型枚举
7 | ///
8 | public enum MessageType
9 | {
10 | ///
11 | /// 公共
12 | ///
13 | Public,
14 | ///
15 | /// @机器人
16 | ///
17 | AtMe,
18 | ///
19 | /// @全员
20 | ///
21 | AtAll,
22 | ///
23 | /// 私聊
24 | ///
25 | Private
26 | }
27 |
28 | ///
29 | /// 消息对象
30 | ///
31 | public class Message
32 | {
33 | ///
34 | /// 消息id
35 | ///
36 | [JsonPropertyName("id")]
37 | public string Id { get; set; } = "";
38 | ///
39 | /// 子频道 id
40 | ///
41 | [JsonPropertyName("channel_id")]
42 | public string ChannelId { get; set; } = "";
43 | ///
44 | /// 频道 id
45 | ///
46 | [JsonPropertyName("guild_id")]
47 | public string GuildId { get; set; } = "";
48 | ///
49 | /// 消息内容
50 | ///
51 | [JsonPropertyName("content")]
52 | public string Content { get; set; } = "";
53 | ///
54 | /// 是否 私聊消息
55 | ///
56 | [JsonPropertyName("direct_message")]
57 | public bool DirectMessage { get; set; }
58 | ///
59 | /// 是否 @全员消息
60 | ///
61 | [JsonPropertyName("mention_everyone")]
62 | public bool MentionEveryone { get; set; }
63 | ///
64 | /// 消息创建时间
65 | ///
66 | [JsonPropertyName("timestamp"), JsonConverter(typeof(DateTimeToStringTimestamp))]
67 | public DateTime CreateTime { get; set; }
68 | ///
69 | /// 消息编辑时间
70 | ///
71 | [JsonPropertyName("edited_timestamp"), JsonConverter(typeof(DateTimeToStringTimestamp))]
72 | public DateTime EditedTime { get; set; }
73 | ///
74 | /// 消息创建者
75 | ///
76 | [JsonPropertyName("author")]
77 | public User Author { get; set; } = new();
78 | ///
79 | /// 附件(可多个)
80 | ///
81 | [JsonPropertyName("attachments")]
82 | public List? Attachments { get; set; }
83 | ///
84 | /// embed
85 | ///
86 | [JsonPropertyName("embeds")]
87 | public List? Embeds { get; set; }
88 | ///
89 | /// 消息中@的人
90 | ///
91 | [JsonPropertyName("mentions")]
92 | public List? Mentions { get; set; }
93 | ///
94 | /// 消息创建者的member信息
95 | ///
96 | [JsonPropertyName("member")]
97 | public Member Member { get; set; } = new();
98 | ///
99 | /// ark消息
100 | ///
101 | [JsonPropertyName("ark")]
102 | public MessageArk? Ark { get; set; }
103 | }
104 |
105 | ///
106 | /// 消息体结构
107 | ///
108 | public class MessageToCreate
109 | {
110 | ///
111 | /// 消息内容,文本内容,支持内嵌格式
112 | ///
113 | [JsonPropertyName("content")]
114 | public string? Content { get; set; }
115 | ///
116 | /// embed 消息,一种特殊的 ark
117 | ///
118 | [JsonPropertyName("embed")]
119 | public MessageEmbed? Embed { get; set; }
120 | ///
121 | /// ark 消息
122 | ///
123 | [JsonPropertyName("ark")]
124 | public MessageArk? Ark { get; set; }
125 | ///
126 | /// 引用消息(需要传递被引用的消息Id)
127 | ///
128 | [JsonPropertyName("message_reference")]
129 | public MessageReference? Reference { get; set; }
130 | ///
131 | /// 图片 url 地址
132 | ///
133 | [JsonPropertyName("image")]
134 | public string? Image { get; set; }
135 | ///
136 | /// 要回复的目标消息Id
137 | /// 带了 id 视为被动回复消息,否则视为主动推送消息
138 | ///
139 | [JsonPropertyName("msg_id")]
140 | public string? Id { get; set; }
141 | }
142 |
143 | ///
144 | /// embed消息
145 | ///
146 | public class MessageEmbed
147 | {
148 | ///
149 | /// 标题
150 | ///
151 | [JsonPropertyName("title")]
152 | public string? Title { get; set; }
153 | ///
154 | /// 描述 (见NodeSDK文档)
155 | ///
156 | [JsonPropertyName("description")]
157 | public string? Description { get; set; }
158 | ///
159 | /// 消息弹窗内容
160 | ///
161 | [JsonPropertyName("prompt")]
162 | public string? Prompt { get; set; }
163 | ///
164 | /// 缩略图
165 | ///
166 | [JsonPropertyName("thumbnail")]
167 | public MessageEmbedThumbnail? Thumbnail { get; set; }
168 | ///
169 | /// 消息列表
170 | ///
171 | [JsonPropertyName("fields")]
172 | public List? Fields { get; set; }
173 | }
174 |
175 | ///
176 | /// 引用消息
177 | ///
178 | public class MessageReference
179 | {
180 | ///
181 | /// 需要引用回复的消息 id
182 | ///
183 | [JsonPropertyName("message_id")]
184 | public string? MessageId { get; set; }
185 | ///
186 | /// 是否忽略获取引用消息详情错误,默认否
187 | ///
188 | [JsonPropertyName("ignore_get_message_error")]
189 | public bool IgnoreGetMessageError { get; set; } = false;
190 | }
191 |
192 | ///
193 | /// 缩略图对象
194 | ///
195 | public class MessageEmbedThumbnail
196 | {
197 | ///
198 | /// 图片地址
199 | ///
200 | [JsonPropertyName("url")]
201 | public string? Url { get; set; }
202 | }
203 |
204 | ///
205 | /// Embed行内容
206 | ///
207 | public class MessageEmbedField
208 | {
209 | ///
210 | /// 构造函数
211 | ///
212 | ///
213 | public MessageEmbedField(string? name = null) { Name = name; }
214 | ///
215 | /// 字段名
216 | ///
217 | [JsonPropertyName("name")]
218 | public string? Name { get; set; }
219 | }
220 |
221 | ///
222 | /// 附件对象
223 | ///
224 | public class MessageAttachment
225 | {
226 | ///
227 | /// 附件Id
228 | ///
229 | [JsonPropertyName("id")]
230 |
231 | public string? Id { get; set; }
232 | ///
233 | /// 附件类型
234 | ///
235 | [JsonPropertyName("content_type")]
236 | public string? ContentType { get; set; }
237 | ///
238 | /// 下载地址
239 | ///
240 | [JsonPropertyName("url")]
241 | public string Url { set; get; } = string.Empty;
242 | ///
243 | /// 文件名
244 | ///
245 | [JsonPropertyName("filename")]
246 | public string? FileName { get; set; }
247 | ///
248 | /// 附件大小
249 | ///
250 | [JsonPropertyName("size")]
251 | public long? Size { get; set; }
252 | ///
253 | /// 图片宽度
254 | /// 仅附件为图片时才有
255 | ///
256 | [JsonPropertyName("width")]
257 | public int? Width { get; set; }
258 | ///
259 | /// 图片高度
260 | /// 仅附件为图片时才有
261 | ///
262 | [JsonPropertyName("height")]
263 | public int? Height { get; set; }
264 | }
265 |
266 | ///
267 | /// ark消息
268 | ///
269 | public class MessageArk
270 | {
271 | ///
272 | /// ark模板id(需要先申请)
273 | ///
274 | [JsonPropertyName("template_id")]
275 | public int TemplateId { get; set; }
276 | ///
277 | /// kv值列表
278 | ///
279 | [JsonPropertyName("kv")]
280 | public List Kv { get; set; } = new();
281 | }
282 |
283 | ///
284 | /// ark的键值对
285 | ///
286 | public class MessageArkKv
287 | {
288 | ///
289 | /// 键
290 | ///
291 | [JsonPropertyName("key")]
292 | public string Key { get; set; } = "";
293 | ///
294 | /// 值
295 | ///
296 | [JsonPropertyName("value")]
297 | public string? Value { get; set; }
298 | ///
299 | /// ark obj类型的列表
300 | ///
301 | [JsonPropertyName("obj")]
302 | public List? Obj { get; set; }
303 | }
304 |
305 | ///
306 | /// ark obj类型
307 | ///
308 | public class MessageArkObj
309 | {
310 | ///
311 | /// ark objkv列表
312 | ///
313 | [JsonPropertyName("obj_kv")]
314 | public List? ObjKv { get; set; }
315 | }
316 |
317 | ///
318 | /// ark obj键值对
319 | ///
320 | public class MessageArkObjKv
321 | {
322 | ///
323 | /// 构造函数
324 | ///
325 | public MessageArkObjKv() { }
326 | ///
327 | /// ark obj键值对构造函数
328 | ///
329 | ///
330 | ///
331 | public MessageArkObjKv(string key, string value)
332 | {
333 | Key = key;
334 | Value = value;
335 | }
336 | ///
337 | /// 键
338 | ///
339 | [JsonPropertyName("key")]
340 | public string Key { get; set; } = "";
341 | ///
342 | /// 值
343 | ///
344 | [JsonPropertyName("value")]
345 | public string? Value { get; set; }
346 | }
347 |
348 | ///
349 | /// 拉取消息的操作类型
350 | ///
351 | public enum GetMsgTypesEnum
352 | {
353 | ///
354 | /// 获取目标id前后的消息
355 | ///
356 | around,
357 | ///
358 | /// 获取目标id之前的消息
359 | ///
360 | before,
361 | ///
362 | /// 获取目标id之后的消息
363 | ///
364 | after,
365 | ///
366 | /// 最新limit的消息
367 | ///
368 | latest
369 | }
370 |
371 | ///
372 | /// 消息审核对象
373 | ///
374 | public class MessageAudited
375 | {
376 | ///
377 | /// 消息审核Id
378 | ///
379 | [JsonPropertyName("audit_id")]
380 | public string AuditId { get; set; } = string.Empty;
381 | ///
382 | /// 被审核的消息Id
383 | /// 只有审核通过事件才会有值
384 | ///
385 | [JsonPropertyName("message_id")]
386 | public string? MessageId { get; set; }
387 | ///
388 | /// 频道Id
389 | ///
390 | [JsonPropertyName("guild_id")]
391 | public string GuildId { get; set; } = string.Empty;
392 | ///
393 | /// 子频道Id
394 | ///
395 | [JsonPropertyName("channel_id")]
396 | public string ChannelId { get; set; } = string.Empty;
397 | ///
398 | /// 消息审核时间
399 | ///
400 | [JsonPropertyName("audit_time"), JsonConverter(typeof(DateTimeToStringTimestamp))]
401 | public DateTime AuditTime { get; set; }
402 | ///
403 | /// 消息创建时间
404 | ///
405 | [JsonPropertyName("create_time"), JsonConverter(typeof(DateTimeToStringTimestamp))]
406 | public DateTime CreateTime { get; set; }
407 | ///
408 | /// 扩展属性,用于标注审核是否通过
409 | ///
410 | [JsonIgnore]
411 | public bool IsPassed { get; set; } = false;
412 | }
413 |
414 | ///
415 | /// 私信会话对象(DMS)
416 | ///
417 | public record struct DirectMessageSource
418 | {
419 | ///
420 | /// 私信会话关联的频道Id
421 | ///
422 | [JsonPropertyName("guild_id")]
423 | public string GuildId { get; init; }
424 | ///
425 | /// 私信会话关联的子频道Id
426 | ///
427 | [JsonPropertyName("channel_id")]
428 | public string ChannelId { get; init; }
429 | ///
430 | /// 创建私信会话时间戳
431 | ///
432 | [JsonPropertyName("create_time"), JsonConverter(typeof(DateTimeToStringTimestamp))]
433 | public DateTime CreateTime { get; init; }
434 | }
435 | }
--------------------------------------------------------------------------------
/QQChannelBot/Models/MessageReaction.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 表情表态
7 | ///
8 | public class MessageReaction
9 | {
10 | ///
11 | /// 用户Id
12 | ///
13 | [JsonPropertyName("user_id")]
14 | public string UserId { get; set; } = string.Empty;
15 | ///
16 | /// 频道Id
17 | ///
18 | [JsonPropertyName("guild_id")]
19 | public string GuildId { get; set; } = string.Empty;
20 | ///
21 | /// 子频道Id
22 | ///
23 | [JsonPropertyName("channel_id")]
24 | public string? ChannelId { get; set; }
25 | ///
26 | /// 表态对象
27 | ///
28 | [JsonPropertyName("target")]
29 | public ReactionTarget? Target { get; set; }
30 | ///
31 | /// 表态所用表情
32 | ///
33 | [JsonPropertyName("emoji")]
34 | public Emoji? Emoji { get; set; }
35 | }
36 | ///
37 | /// 表态对象
38 | ///
39 | public class ReactionTarget
40 | {
41 | ///
42 | /// 表态对象ID
43 | ///
44 | [JsonPropertyName("id")]
45 | public string? Id { get; set; }
46 | ///
47 | /// 表态对象类型
48 | ///
49 | [JsonPropertyName("type")]
50 | public ReactionTargetType Type { get; set; }
51 | }
52 | ///
53 | /// 表态对象类型
54 | ///
55 | public enum ReactionTargetType
56 | {
57 | ///
58 | /// 消息
59 | ///
60 | 消息,
61 | ///
62 | /// 帖子
63 | ///
64 | 帖子,
65 | ///
66 | /// 评论
67 | ///
68 | 评论,
69 | ///
70 | /// 回复
71 | ///
72 | 回复,
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Mute.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using System.Text.RegularExpressions;
3 |
4 | namespace QQChannelBot.Models
5 | {
6 | ///
7 | /// 禁言时间
8 | ///
9 | public class MuteTime
10 | {
11 | ///
12 | /// 禁言时间(默认1分钟)
13 | ///
14 | public MuteTime()
15 | {
16 | MuteSeconds = "60";
17 | }
18 | ///
19 | /// 禁言指定的时长
20 | ///
21 | /// 禁言多少秒
22 | public MuteTime(int seconds)
23 | {
24 | MuteSeconds = seconds.ToString();
25 | }
26 | ///
27 | /// 禁言到指定时间
28 | ///
29 | /// 解禁时间戳
30 | ///
31 | /// 格式:"yyyy-MM-dd HH:mm:ss"
32 | /// 示例:"2077-01-01 08:00:00"
33 | ///
34 | public MuteTime(string timestamp)
35 | {
36 | MuteEndTimestamp = new DateTimeOffset(Convert.ToDateTime(timestamp)).ToUnixTimeSeconds().ToString();
37 | }
38 | ///
39 | /// 禁言到期时间戳,绝对时间戳,单位:秒
40 | ///
41 | [JsonPropertyName("mute_end_timestamp")]
42 | public string? MuteEndTimestamp { get; set; }
43 | ///
44 | /// 禁言多少秒
45 | ///
46 | [JsonPropertyName("mute_seconds")]
47 | public string? MuteSeconds { get; set; }
48 | }
49 |
50 | ///
51 | /// 根据时间字符串构建禁言时间
52 | ///
53 | public class MuteMaker : MuteTime
54 | {
55 | readonly Regex TypeTimeStamp = new(@"(\d{4})[-年](\d\d)[-月](\d\d)[\s日]*(\d\d)[:点时](\d\d)[:分](\d\d)秒?");
56 | readonly Regex TypeTimeDelay = new(@"(\d+)\s*(年|星期|周|日|天|小?时|分钟?|秒钟?)");
57 | ///
58 | /// 构造禁言时间
59 | ///
60 | /// 支持以下正则匹配的格式 (优先使用时间戳模式):
61 | /// 时间戳模式 - "^(\d{4})[-年](\d\d)[-月](\d\d)[\s日]*(\d\d)[:点时](\d\d)[:分](\d\d)秒?\s*$"
62 | /// 倒计时模式 - "^(\d+)\s*(年|星期|周|日|天|小?时|分钟?|秒钟?)?\s*$"
63 | ///
64 | ///
65 | public MuteMaker(string timeString = "1分钟")
66 | {
67 | if (string.IsNullOrEmpty(timeString)) return;
68 | Match mts = TypeTimeStamp.Match(timeString);
69 | if (mts.Success)
70 | {
71 | string timstamp = $"{mts.Groups[1].Value}-{mts.Groups[2].Value}-{mts.Groups[3].Value} {mts.Groups[4].Value}:{mts.Groups[5].Value}:{mts.Groups[6].Value}";
72 | MuteEndTimestamp = new DateTimeOffset(Convert.ToDateTime(timstamp)).ToUnixTimeSeconds().ToString();
73 | }
74 | else
75 | {
76 | Match mtd = TypeTimeDelay.Match(timeString);
77 | if (mtd.Success)
78 | {
79 | int seconds = mtd.Groups[2].Value switch
80 | {
81 | "年" => 60 * 60 * 24 * 365,
82 | "星期" => 60 * 60 * 24 * 7,
83 | "周" => 60 * 60 * 24 * 7,
84 | "日" => 60 * 60 * 24,
85 | "天" => 60 * 60 * 24,
86 | "小时" => 60 * 60,
87 | "时" => 60 * 60,
88 | "分钟" => 60,
89 | "分" => 60,
90 | _ => 1
91 | };
92 | if (int.TryParse(mtd.Groups[1].Value, out int timeVal)) seconds *= timeVal;
93 | MuteSeconds = seconds.ToString();
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Role.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace QQChannelBot.Models
5 | {
6 | ///
7 | /// 身分组对象
8 | ///
9 | public class Role
10 | {
11 | ///
12 | /// 身份组ID, 默认值可参考
13 | ///
14 | [JsonPropertyName("id")]
15 | public string Id { get; set; } = string.Empty;
16 | ///
17 | /// 身分组名称
18 | ///
19 | [JsonPropertyName("name")]
20 | public string Name { get; set; } = string.Empty;
21 | ///
22 | /// 身份组颜色
23 | ///
24 | [JsonPropertyName("color"), JsonConverter(typeof(ColorToUint32Converter))]
25 | public Color Color { get; set; }
26 | ///
27 | /// ARGB颜色值的HTML表现形式(如:#FFFFFFFF)
28 | ///
29 | [JsonIgnore]
30 | public string? ColorHtml { get => $"#{Color.ToArgb():X8}"; }
31 | ///
32 | /// 该身分组是否在成员列表中单独展示
33 | ///
34 | [JsonPropertyName("hoist"), JsonConverter(typeof(BoolToInt32Converter))]
35 | public bool Hoist { get; set; }
36 | ///
37 | /// 该身分组的人数
38 | ///
39 | [JsonPropertyName("number")]
40 | public uint Number { get; set; }
41 | ///
42 | /// 成员上限
43 | ///
44 | [JsonPropertyName("member_limit")]
45 | public uint MemberLimit { get; set; }
46 | }
47 | ///
48 | /// 频道身份组列表
49 | ///
50 | public class GuildRoles
51 | {
52 | ///
53 | /// 频道Id
54 | ///
55 | [JsonPropertyName("guild_id")]
56 | public string? GuildId { get; set; }
57 |
58 | ///
59 | /// 身份组
60 | ///
61 | [JsonPropertyName("roles")]
62 | public List? Roles { get; set; }
63 | ///
64 | /// 默认分组上限
65 | ///
66 | [JsonPropertyName("role_num_limit")]
67 | public string? RoleNumLimit { get; set; }
68 | }
69 | ///
70 | /// 新建频道身份组的返回值
71 | ///
72 | public class CreateRoleRes
73 | {
74 | ///
75 | /// 身份组ID
76 | ///
77 | [JsonPropertyName("role_id")]
78 | public string? RoleId { get; set; }
79 | ///
80 | /// 新创建的频道身份组对象
81 | ///
82 | [JsonPropertyName("role")]
83 | public Role? Role { get; set; }
84 | }
85 | ///
86 | /// 修改频道身份组的返回值
87 | ///
88 | public class ModifyRolesRes
89 | {
90 | ///
91 | /// 身份组ID
92 | ///
93 | [JsonPropertyName("guild_id")]
94 | public string? GuildId { get; set; }
95 | ///
96 | /// 身份组ID
97 | ///
98 | [JsonPropertyName("role_id")]
99 | public string? RoleId { get; set; }
100 | ///
101 | /// 新创建的频道身份组对象
102 | ///
103 | [JsonPropertyName("role")]
104 | public Role? Role { get; set; }
105 | }
106 | ///
107 | /// 标识需要设置哪些字段
108 | ///
109 | public class Filter
110 | {
111 | ///
112 | /// 配置筛选器
113 | ///
114 | /// 设置名称
115 | /// 设置颜色
116 | /// 设置在成员列表中单独展示
117 | public Filter(bool setName = false, bool setColor = false, bool setHoist = false)
118 | {
119 | Name = setName;
120 | Color = setColor;
121 | Hoist = setHoist;
122 | }
123 | ///
124 | /// 是否设置名称
125 | ///
126 | [JsonPropertyName("name"), JsonConverter(typeof(BoolToInt32Converter))]
127 | public bool Name { get; set; }
128 | ///
129 | /// 是否设置颜色
130 | ///
131 | [JsonPropertyName("color"), JsonConverter(typeof(BoolToInt32Converter))]
132 | public bool Color { get; set; }
133 | ///
134 | /// 是否设置在成员列表中单独展示
135 | ///
136 | [JsonPropertyName("hoist"), JsonConverter(typeof(BoolToInt32Converter))]
137 | public bool Hoist { get; set; }
138 | }
139 | ///
140 | /// 携带需要设置的字段内容
141 | ///
142 | public class Info
143 | {
144 | ///
145 | /// 构造身份组信息
146 | ///
147 | /// 名称
148 | /// 颜色
149 | /// 在成员列表中单独展示
150 | public Info(string? name = null, Color? color = null, bool? hoist = null)
151 | {
152 | Name = name;
153 | Color = color;
154 | Hoist = hoist;
155 | }
156 | ///
157 | /// 构造身份组信息
158 | ///
159 | /// 名称
160 | /// ARGB颜色值的HTML表现形式(如:#FFFFFFFF)
161 | /// 在成员列表中单独展示
162 | public Info(string? name = null, string? colorHtml = null, bool? hoist = null)
163 | {
164 | Name = name;
165 | ColorHtml = colorHtml;
166 | Hoist = hoist;
167 | }
168 | ///
169 | /// 名称
170 | ///
171 | [JsonPropertyName("name")]
172 | public string? Name { get; set; }
173 | ///
174 | /// 颜色
175 | ///
176 | [JsonPropertyName("color"), JsonConverter(typeof(ColorToUint32Converter))]
177 | public Color? Color { get; set; }
178 | ///
179 | /// ARGB的HTML十六进制颜色值
180 | ///
181 | /// 支持这些格式(忽略大小写和前后空白字符):
182 | /// #FFFFFFFF #FFFFFF #FFFF #FFF
183 | ///
184 | /// 注: 因官方API有BUG,框架暂时强制Alpha通道固定为1.0,对功能无影响。 [2021-12-21]
185 | ///
186 | [JsonIgnore]
187 | public string? ColorHtml
188 | {
189 | get
190 | {
191 | return Color == null ? null : ColorTranslator.ToHtml(Color.Value);
192 | }
193 | set
194 | {
195 | value = value?.TrimStart("0x", "#");
196 | value = value?.Length switch
197 | {
198 | 4 => $"#{value[1]}{value[1]}{value[2]}{value[2]}{value[3]}{value[3]}",
199 | 5 => $"#{value[1]}{value[1]}{value[2]}{value[2]}{value[3]}{value[3]}{value[4]}{value[4]}",
200 | _ => value
201 | };
202 | Color = string.IsNullOrWhiteSpace(value) ? null : ColorTranslator.FromHtml(value);
203 | }
204 | }
205 | ///
206 | /// 在成员列表中单独展示
207 | ///
208 | [JsonPropertyName("hoist"), JsonConverter(typeof(BoolToInt32Converter))]
209 | public bool? Hoist { get; set; }
210 | }
211 | ///
212 | /// 系统默认身份组
213 | ///
214 | public static class DefaultRoles
215 | {
216 | ///
217 | /// 获取系统默认身份组名称
218 | ///
219 | ///
220 | ///
221 | public static string? Get(string roleId)
222 | {
223 | return roleId switch
224 | {
225 | "1" => "普通成员",
226 | "2" => "管理员",
227 | "4" => "频道主",
228 | "5" => "子频道管理员",
229 | _ => null
230 | };
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/Schedule.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 日程对象
7 | ///
8 | public class Schedule
9 | {
10 | ///
11 | /// 构造日程
12 | ///
13 | public Schedule()
14 | {
15 | StartTime = DateTime.Now.AddMinutes(10);
16 | EndTime = StartTime.AddHours(1);
17 | }
18 | ///
19 | /// 新建日程
20 | ///
21 | /// 注1:开始时间必须大于当前时间
22 | /// 注2:结束时间必须大于开始时间
23 | /// 注3:调用API每日创建日程数量有限
24 | ///
25 | ///
26 | /// 日程名称
27 | /// 日程描述
28 | /// 开始时间(默认五分钟后)
29 | /// 结束时间(默认持续一小时)
30 | /// 日程开始时跳转的频道
31 | /// 日程时间即将到达时的提醒方式
32 | public Schedule(
33 | string name = "新建日程",
34 | string desc = "新的日程",
35 | DateTime? startTime = null,
36 | DateTime? endTime = null,
37 | Channel? jumpChannel = null,
38 | RemindType remindType = RemindType.Never)
39 | {
40 | Name = name;
41 | Description = desc;
42 | StartTime = startTime ?? DateTime.Now.AddMinutes(10);
43 | EndTime = endTime ?? StartTime.AddHours(1);
44 | JumpChannelId = jumpChannel?.Id;
45 | RemindType = remindType;
46 | }
47 | ///
48 | /// 日程 id
49 | ///
50 | [JsonPropertyName("id")]
51 | public string? Id { get; set; }
52 | ///
53 | /// 日程名称
54 | ///
55 | [JsonPropertyName("name")]
56 | public string? Name { get; set; }
57 | ///
58 | /// 日程描述
59 | ///
60 | [JsonPropertyName("description")]
61 | public string? Description { get; set; }
62 | ///
63 | /// 日程开始时间戳(ms)
64 | /// 必须大于当前时间
65 | ///
66 | [JsonPropertyName("start_timestamp")]
67 | public string StartTimestamp
68 | {
69 | get => new DateTimeOffset(StartTime).ToUnixTimeMilliseconds().ToString();
70 | set => StartTime = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(value)).DateTime;
71 | }
72 | ///
73 | /// 日程开始时间
74 | /// 必须大于当前时间
75 | ///
76 | [JsonIgnore]
77 | public DateTime StartTime { get; set; }
78 | ///
79 | /// 日程结束时间戳(ms)
80 | /// 必须大于开始时间
81 | ///
82 | [JsonPropertyName("end_timestamp")]
83 | public string EndTimestamp
84 | {
85 | get => new DateTimeOffset(EndTime).ToUnixTimeMilliseconds().ToString();
86 | set => EndTime = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(value)).DateTime;
87 | }
88 | ///
89 | /// 日程结束时间
90 | /// 必须大于开始时间
91 | ///
92 | [JsonIgnore]
93 | public DateTime EndTime { get; set; }
94 | ///
95 | /// 创建者
96 | ///
97 | [JsonPropertyName("creator")]
98 | public Member? Creator { get; set; }
99 | ///
100 | /// 日程开始时跳转到的子频道 id
101 | ///
102 | [JsonPropertyName("jump_channel_id")]
103 | public string? JumpChannelId { get; set; }
104 | ///
105 | /// 日程提醒类型
106 | ///
107 | [JsonPropertyName("remind_type"), JsonConverter(typeof(RemindTypeToStringNumberConverter))]
108 | public RemindType RemindType { get; set; }
109 | }
110 |
111 | ///
112 | /// 日程提醒方式
113 | ///
114 | public enum RemindType
115 | {
116 | ///
117 | /// 不提醒
118 | ///
119 | Never,
120 | ///
121 | /// 开始时提醒
122 | ///
123 | OnStart,
124 | ///
125 | /// 开始前5分钟提醒
126 | ///
127 | Early5Min,
128 | ///
129 | /// 开始前15分钟提醒
130 | ///
131 | Early15Min,
132 | ///
133 | /// 开始前30分钟提醒
134 | ///
135 | Early30Min,
136 | ///
137 | /// 开始前60分钟提醒
138 | ///
139 | Early60Min
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/User.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// 用户
7 | ///
8 | public class User
9 | {
10 | ///
11 | /// 用户 id
12 | ///
13 | [JsonPropertyName("id")]
14 | public string Id { get; set; } = "";
15 | ///
16 | /// 用户名
17 | ///
18 | [JsonPropertyName("username")]
19 | public string UserName { get; set; } = "";
20 | ///
21 | /// 用户头像地址
22 | ///
23 | [JsonPropertyName("avatar")]
24 | public string? Avatar { get; set; }
25 | ///
26 | /// 是否机器人
27 | ///
28 | [JsonPropertyName("bot")]
29 | public bool Bot { get; set; } = true;
30 | ///
31 | /// 特殊关联应用的 openid,需要特殊申请并配置后才会返回。如需申请,请联系平台运营人员。
32 | ///
33 | [JsonPropertyName("union_openid")]
34 | public string? UnionOpenid { get; set; }
35 | ///
36 | /// 机器人关联的互联应用的用户信息,与 UnionOpenid 关联的应用是同一个。如需申请,请联系平台运营人员。
37 | ///
38 | [JsonPropertyName("union_user_account")]
39 | public string? UnionUserAccount { get; set; }
40 | ///
41 | /// @用户 标签
42 | /// 数据内容为:<@!UserId>
43 | ///
44 | [JsonIgnore]
45 | public string Tag => $"<@!{Id}>";
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/QQChannelBot/Models/WebSocketLimit.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace QQChannelBot.Models
4 | {
5 | ///
6 | /// WebSocket连接分片信息
7 | ///
8 | public class WebSocketLimit
9 | {
10 | ///
11 | /// WebSocket 的连接地址
12 | ///
13 | [JsonPropertyName("url")]
14 | public string? Url { get; set; }
15 | ///
16 | /// 建议的 shard 数
17 | ///
18 | [JsonPropertyName("shards")]
19 | public int Shards { get; set; } = 1;
20 | ///
21 | /// 创建Session限制信息
22 | ///
23 | [JsonPropertyName("session_start_limit")]
24 | public SessionStartLimit? SessionLimit { get; set; }
25 | }
26 |
27 | ///
28 | /// Session开始限制
29 | ///
30 | public class SessionStartLimit
31 | {
32 | ///
33 | /// 每 24 小时可创建 Session 数
34 | ///
35 | [JsonPropertyName("total")]
36 | public int Total { get; set; }
37 | ///
38 | /// 目前还可以创建的 Session 数
39 | /// 每个机器人创建的连接数不能超过 remaining 剩余连接数
40 | ///
41 | [JsonPropertyName("remaining")]
42 | public int Remaining { get; set; }
43 | ///
44 | /// 重置计数的剩余时间(ms)
45 | ///
46 | [JsonPropertyName("reset_after")]
47 | public int ResetAfter { get; set; }
48 | ///
49 | /// 每 5s 可以创建的 Session 数
50 | ///
51 | [JsonPropertyName("max_concurrency")]
52 | public int MaxConcurrency { get; set; }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgArk23.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 模板消息 id=23
7 | /// 链接+文本列表模板
8 | ///
9 | public class MsgArk23 : MessageToCreate
10 | {
11 | ///
12 | /// 构造模板消息
13 | ///
14 | /// 描述
15 | /// 提示
16 | /// 多行内容
17 | /// 要回复的消息id
18 | public MsgArk23(
19 | string? desc = null,
20 | string? prompt = null,
21 | List? msgLines = null,
22 | string? replyMsgId = null
23 | )
24 | {
25 | Id = replyMsgId;
26 | Desc = desc;
27 | Prompt = prompt;
28 | MsgLines = msgLines ?? new();
29 | Ark = new()
30 | {
31 | TemplateId = 23,
32 | Kv = new()
33 | {
34 | ArkDesc,
35 | ArkPrompt,
36 | new() { Key = "#LIST#", Obj = MsgLines }
37 | }
38 | };
39 | }
40 | private readonly MessageArkKv ArkDesc = new() { Key = "#DESC#", Value = null };
41 | private readonly MessageArkKv ArkPrompt = new() { Key = "#PROMPT#", Value = null };
42 |
43 | ///
44 | /// 设置要回复的目标消息
45 | ///
46 | /// 目标消息的Id
47 | ///
48 | public MsgArk23 SetReplyMsgId(string? msgId) { Id = msgId; return this; }
49 | ///
50 | /// 描述
51 | ///
52 | public string? Desc { get => ArkDesc.Value; set => ArkDesc.Value = value; }
53 | ///
54 | /// 设置描述
55 | ///
56 | /// 描述内容
57 | ///
58 | public MsgArk23 SetDesc(string? desc) { Desc = desc; return this; }
59 | ///
60 | /// 提示消息
61 | ///
62 | public string? Prompt { get => ArkPrompt.Value; set => ArkPrompt.Value = value; }
63 | ///
64 | /// 设置提示
65 | ///
66 | /// 提示内容
67 | ///
68 | public MsgArk23 SetPrompt(string? prompt) { Prompt = prompt; return this; }
69 | ///
70 | /// 内容列表
71 | ///
72 | public List MsgLines { get; set; }
73 | ///
74 | /// 添加一行内容
75 | ///
76 | /// content - 本行要显示的文字
77 | /// link - 本行文字绑定的超链接(URL需要审核通过才能用)
78 | ///
79 | ///
80 | /// 内容描述
81 | /// 内容链接 [可选]
82 | ///
83 | public MsgArk23 AddLine(string? content, string? link = null)
84 | {
85 | List ojbk = new()
86 | {
87 | new() { Key = "desc", Value = content }
88 | };
89 | if (link != null) ojbk.Add(new() { Key = "link", Value = link });
90 |
91 | MsgLines.Add(new() { ObjKv = ojbk });
92 | return this;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgArk24.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 模板消息 id=24
7 | /// 文本、缩略图模板
8 | ///
9 | public class MsgArk24 : MessageToCreate
10 | {
11 | ///
12 | /// 构造模板消息
13 | ///
14 | /// 描述
15 | /// 提示
16 | /// 标题
17 | /// 详情
18 | /// 图片URL
19 | /// 跳转链接
20 | /// 子标题
21 | /// 要回复的消息id
22 | public MsgArk24(
23 | string? desc = null,
24 | string? prompt = null,
25 | string? title = null,
26 | string? metaDesc = null,
27 | string? image = null,
28 | string? link = null,
29 | string? subTitle = null,
30 | string? replyMsgId = null
31 | )
32 | {
33 | Id = replyMsgId;
34 | Desc = desc;
35 | Prompt = prompt;
36 | Title = title;
37 | MetaDesc = metaDesc;
38 | Img = image;
39 | Link = link;
40 | SubTitle = subTitle;
41 | Ark = new()
42 | {
43 | TemplateId = 24,
44 | Kv = new()
45 | {
46 | ArkDesc,
47 | ArkPrompt,
48 | ArkTitle,
49 | ArkMetaDesc,
50 | ArkImage,
51 | ArkLink,
52 | ArkSubTitle
53 | }
54 | };
55 | }
56 | private readonly MessageArkKv ArkDesc = new() { Key = "#DESC#", Value = null };
57 | private readonly MessageArkKv ArkPrompt = new() { Key = "#PROMPT#", Value = null };
58 | private readonly MessageArkKv ArkTitle = new() { Key = "#TITLE#", Value = null };
59 | private readonly MessageArkKv ArkMetaDesc = new() { Key = "#METADESC#", Value = null };
60 | private readonly MessageArkKv ArkImage = new() { Key = "#IMG#", Value = null };
61 | private readonly MessageArkKv ArkLink = new() { Key = "#LINK#", Value = null };
62 | private readonly MessageArkKv ArkSubTitle = new() { Key = "#SUBTITLE#", Value = null };
63 |
64 | ///
65 | /// 设置要回复的目标消息
66 | ///
67 | /// 目标消息的Id
68 | ///
69 | public MsgArk24 SetReplyMsgId(string? msgId) { Id = msgId; return this; }
70 | ///
71 | /// 描述
72 | ///
73 | public string? Desc { get => ArkDesc.Value; set => ArkDesc.Value = value; }
74 | ///
75 | /// 设置描述
76 | ///
77 | /// 描述内容
78 | ///
79 | public MsgArk24 SetDesc(string? desc) { Desc = desc; return this; }
80 | ///
81 | /// 提示
82 | ///
83 | public string? Prompt { get => ArkPrompt.Value; set => ArkPrompt.Value = value; }
84 | ///
85 | /// 设置提示
86 | ///
87 | /// 提示内容
88 | ///
89 | public MsgArk24 SetPrompt(string? prompt) { Prompt = prompt; return this; }
90 | ///
91 | /// 标题
92 | ///
93 | public string? Title { get => ArkTitle.Value; set => ArkTitle.Value = value; }
94 | ///
95 | /// 设置标题
96 | ///
97 | /// 标题内容
98 | ///
99 | public MsgArk24 SetTitle(string? title) { Title = title; return this; }
100 | ///
101 | /// 详情
102 | ///
103 | public string? MetaDesc { get => ArkMetaDesc.Value; set => ArkMetaDesc.Value = value; }
104 | ///
105 | /// 设置详情
106 | ///
107 | /// 详情内容
108 | ///
109 | public MsgArk24 SetMetaDesc(string? metaDesc) { MetaDesc = metaDesc; return this; }
110 | ///
111 | /// 图片URL
112 | ///
113 | public string? Img { get => ArkImage.Value; set => ArkImage.Value = value; }
114 | ///
115 | /// 设置图片
116 | ///
117 | /// 图片URL
118 | ///
119 | public MsgArk24 SetImage(string? imgLink) { Img = imgLink; return this; }
120 | ///
121 | /// 跳转链接
122 | ///
123 | public string? Link { get => ArkLink.Value; set => ArkLink.Value = value; }
124 | ///
125 | /// 设置链接
126 | ///
127 | /// 跳转链接
128 | ///
129 | public MsgArk24 SetLink(string? link) { Link = link; return this; }
130 | ///
131 | /// 子标题
132 | /// 子标题显示在模板消息底部
133 | ///
134 | public string? SubTitle { get => ArkSubTitle.Value; set => ArkSubTitle.Value = value; }
135 | ///
136 | /// 设置子标题
137 | ///
138 | /// 子标题内容
139 | ///
140 | public MsgArk24 SetSubTitle(string? subTitle) { SubTitle = subTitle; return this; }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgArk34.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 模板消息 id=34
7 | /// 大图模板
8 | ///
9 | public class MsgArk34 : MessageToCreate
10 | {
11 | ///
12 | /// 构造模板消息
13 | ///
14 | /// 描述
15 | /// 提示
16 | /// 标题
17 | /// 详情
18 | /// 小图标URL
19 | /// 大图URL
20 | /// 跳转链接
21 | /// 要回复的消息id
22 | public MsgArk34(
23 | string? desc = null,
24 | string? prompt = null,
25 | string? metaTitle = null,
26 | string? metaDesc = null,
27 | string? metaIcon = null,
28 | string? metaPreview = null,
29 | string? metaUrl = null,
30 | string? replyMsgId = null
31 | )
32 | {
33 | Id = replyMsgId;
34 | Desc = desc;
35 | Prompt = prompt;
36 | MetaTitle = metaTitle;
37 | MetaDesc = metaDesc;
38 | MetaIcon = metaIcon;
39 | MetaPreview = metaPreview;
40 | MetaUrl = metaUrl;
41 | Ark = new()
42 | {
43 | TemplateId = 34,
44 | Kv = new()
45 | {
46 | ArkDesc,
47 | ArkPrompt,
48 | ArkMetaTitle,
49 | ArkMetaDesc,
50 | ArkMetaIcon,
51 | ArkMetaPreview,
52 | ArkMetaUrl
53 | }
54 | };
55 | }
56 | private readonly MessageArkKv ArkDesc = new() { Key = "#DESC#", Value = null };
57 | private readonly MessageArkKv ArkPrompt = new() { Key = "#PROMPT#", Value = null };
58 | private readonly MessageArkKv ArkMetaTitle = new() { Key = "#METATITLE#", Value = null };
59 | private readonly MessageArkKv ArkMetaDesc = new() { Key = "#METADESC#", Value = null };
60 | private readonly MessageArkKv ArkMetaIcon = new() { Key = "#METAICON#", Value = null };
61 | private readonly MessageArkKv ArkMetaPreview = new() { Key = "#METAPREVIEW#", Value = null };
62 | private readonly MessageArkKv ArkMetaUrl = new() { Key = "#METAURL#", Value = null };
63 |
64 | ///
65 | /// 设置要回复的目标消息
66 | ///
67 | /// 目标消息的Id
68 | ///
69 | public MsgArk34 SetReplyMsgId(string? msgId) { Id = msgId; return this; }
70 | ///
71 | /// 描述
72 | ///
73 | public string? Desc { get => ArkDesc.Value; set => ArkDesc.Value = value; }
74 | ///
75 | /// 设置描述
76 | ///
77 | /// 描述内容
78 | ///
79 | public MsgArk34 SetDesc(string? desc) { Desc = desc; return this; }
80 | ///
81 | /// 提示
82 | ///
83 | public string? Prompt { get => ArkPrompt.Value; set => ArkPrompt.Value = value; }
84 | ///
85 | /// 设置提示
86 | ///
87 | /// 提示内容
88 | ///
89 | public MsgArk34 SetPrompt(string? prompt) { Prompt = prompt; return this; }
90 | ///
91 | /// 标题
92 | ///
93 | public string? MetaTitle { get => ArkMetaTitle.Value; set => ArkMetaTitle.Value = value; }
94 | ///
95 | /// 设置标题
96 | ///
97 | /// 标题内容
98 | ///
99 | public MsgArk34 SetMetaTitle(string? metaTitle) { MetaTitle = metaTitle; return this; }
100 | ///
101 | /// 详情
102 | ///
103 | public string? MetaDesc { get => ArkMetaDesc.Value; set => ArkMetaDesc.Value = value; }
104 | ///
105 | /// 设置详情
106 | ///
107 | /// 详情内容
108 | ///
109 | public MsgArk34 SetMetaDesc(string? metaDesc) { MetaDesc = metaDesc; return this; }
110 | ///
111 | /// 小图标URL
112 | ///
113 | public string? MetaIcon { get => ArkMetaIcon.Value; set => ArkMetaIcon.Value = value; }
114 | ///
115 | /// 设置小图标
116 | ///
117 | /// 小图标URL
118 | ///
119 | public MsgArk34 SetMetaIcon(string? iconLink) { MetaIcon = iconLink; return this; }
120 | ///
121 | /// 大图URL
122 | ///
123 | public string? MetaPreview { get => ArkMetaPreview.Value; set => ArkMetaPreview.Value = value; }
124 | ///
125 | /// 设置大图
126 | ///
127 | /// 大图URL
128 | ///
129 | public MsgArk34 SetMetaPreview(string? metaPreview) { MetaPreview = metaPreview; return this; }
130 | ///
131 | /// 跳转链接
132 | ///
133 | public string? MetaUrl { get => ArkMetaUrl.Value; set => ArkMetaUrl.Value = value; }
134 | ///
135 | /// 设置跳转链接
136 | ///
137 | /// 跳转链接
138 | ///
139 | public MsgArk34 SetMetaUrl(string? metaUrl) { MetaUrl = metaUrl; return this; }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgArk37.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 模板消息 id=37
7 | ///
8 | /// 大图模板
9 | /// 尺寸为 975*540
10 | ///
11 | ///
12 | public class MsgArk37 : MessageToCreate
13 | {
14 | ///
15 | /// 构造模板消息
16 | ///
17 | /// 描述
18 | /// 标题
19 | /// 子标题
20 | /// 大图URL
21 | /// 跳转链接
22 | /// 要回复的消息id
23 | public MsgArk37(
24 | string? prompt = null,
25 | string? metaTitle = null,
26 | string? metaSubTitle = null,
27 | string? metaCover = null,
28 | string? metaUrl = null,
29 | string? replyMsgId = null
30 | )
31 | {
32 | Id = replyMsgId;
33 | Prompt = prompt;
34 | MetaTitle = metaTitle;
35 | MetaSubTitle = metaSubTitle;
36 | MetaCover = metaCover;
37 | MetaUrl = metaUrl;
38 | Ark = new()
39 | {
40 | TemplateId = 37,
41 | Kv = new()
42 | {
43 | ArkPrompt,
44 | ArkMetaTitle,
45 | ArkMetaSubTitle,
46 | ArkMetaCover,
47 | ArkMetaUrl
48 | }
49 | };
50 | }
51 | private readonly MessageArkKv ArkPrompt = new() { Key = "#PROMPT#", Value = null };
52 | private readonly MessageArkKv ArkMetaTitle = new() { Key = "#METATITLE#", Value = null };
53 | private readonly MessageArkKv ArkMetaSubTitle = new() { Key = "#METASUBTITLE#", Value = null };
54 | private readonly MessageArkKv ArkMetaCover = new() { Key = "#METACOVER#", Value = null };
55 | private readonly MessageArkKv ArkMetaUrl = new() { Key = "#METAURL#", Value = null };
56 |
57 | ///
58 | /// 设置要回复的目标消息
59 | ///
60 | /// 目标消息的Id
61 | ///
62 | public MsgArk37 SetReplyMsgId(string? msgId) { Id = msgId; return this; }
63 | ///
64 | /// 提示
65 | ///
66 | public string? Prompt { get => ArkPrompt.Value; set => ArkPrompt.Value = value; }
67 | ///
68 | /// 设置提示
69 | ///
70 | /// 提示内容
71 | ///
72 | public MsgArk37 SetPrompt(string? prompt) { Prompt = prompt; return this; }
73 | ///
74 | /// 标题
75 | ///
76 | public string? MetaTitle { get => ArkMetaTitle.Value; set => ArkMetaTitle.Value = value; }
77 | ///
78 | /// 设置标题
79 | ///
80 | /// 标题内容
81 | ///
82 | public MsgArk37 SetMetaTitle(string? metaTitle) { MetaTitle = metaTitle; return this; }
83 | ///
84 | /// 子标题
85 | ///
86 | public string? MetaSubTitle { get => ArkMetaSubTitle.Value; set => ArkMetaSubTitle.Value = value; }
87 | ///
88 | /// 设置子标题
89 | ///
90 | /// 子标题内容
91 | ///
92 | public MsgArk37 SetMetaSubTitle(string? subTitle) { MetaSubTitle = subTitle; return this; }
93 |
94 | ///
95 | /// 大图URL
96 | ///
97 | public string? MetaCover { get => ArkMetaCover.Value; set => ArkMetaCover.Value = value; }
98 | ///
99 | /// 设置大图
100 | ///
101 | /// 大图URL
102 | ///
103 | public MsgArk37 SetMetaCover(string? cover) { MetaCover = cover; return this; }
104 | ///
105 | /// 跳转链接
106 | ///
107 | public string? MetaUrl { get => ArkMetaUrl.Value; set => ArkMetaUrl.Value = value; }
108 | ///
109 | /// 设置跳转链接
110 | ///
111 | /// 跳转链接
112 | ///
113 | public MsgArk37 SetMetaUrl(string? metaUrl) { MetaUrl = metaUrl; return this; }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgDescImg.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 图片消息对象
7 | ///
8 | public class MsgDescImg : MessageToCreate
9 | {
10 | ///
11 | /// 构建文字和图片同时存在的消息
12 | ///
13 | /// 消息内容
14 | /// 图片URL
15 | /// 要回复的消息id
16 | public MsgDescImg(
17 | string? content = null,
18 | string? image = null,
19 | string? replyMsgId = null
20 | )
21 | {
22 | Id = replyMsgId;
23 | Image = image;
24 | Content = content;
25 | }
26 | ///
27 | /// 设置要回复的目标消息
28 | ///
29 | /// 目标消息的Id
30 | ///
31 | public MsgDescImg SetReplyMsgId(string? msgId) { Id = msgId; return this; }
32 | ///
33 | /// 设置消息内容
34 | ///
35 | /// 文字内容
36 | ///
37 | public MsgDescImg SetContent(string? content) { Content = content; return this; }
38 | ///
39 | /// 设置图片URL
40 | ///
41 | /// 图片URL
42 | ///
43 | public MsgDescImg SetImage(string? image) { Image = image; return this; }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgEmbed.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// Embed消息对象
7 | ///
8 | public class MsgEmbed : MessageToCreate
9 | {
10 | ///
11 | /// 构建Embed消息
12 | /// 详情查阅QQ机器人文档 embed消息
13 | ///
14 | /// 标题
15 | /// 提示
16 | /// 缩略图URL
17 | /// 内容列表
18 | /// 要回复的消息id
19 | public MsgEmbed(
20 | string? title = null,
21 | string? prompt = null,
22 | string? thumbnail = null,
23 | List? embedFields = null,
24 | string? replyMsgId = null
25 | )
26 | {
27 | Id = replyMsgId;
28 | EmbedFields = embedFields ?? new();
29 | EmbedClass = new MessageEmbed()
30 | {
31 | Thumbnail = EmbedThumbnail,
32 | Fields = EmbedFields
33 | };
34 | Title = title;
35 | Prompt = prompt;
36 | Thumbnail = thumbnail;
37 | Embed = EmbedClass;
38 | }
39 | private MessageEmbedThumbnail EmbedThumbnail { get; set; } = new();
40 | private MessageEmbed EmbedClass { get; set; }
41 |
42 | ///
43 | /// 设置要回复的目标消息
44 | ///
45 | /// 目标消息的Id
46 | ///
47 | public MsgEmbed SetReplyMsgId(string? msgId) { Id = msgId; return this; }
48 | ///
49 | /// 标题
50 | ///
51 | public string? Title { get => EmbedClass.Title; set => EmbedClass.Title = value; }
52 | ///
53 | /// 设置标题
54 | ///
55 | /// 标题内容
56 | ///
57 | public MsgEmbed SetTitle(string? title) { Title = title; return this; }
58 | ///
59 | /// 提示
60 | ///
61 | public string? Prompt { get => EmbedClass.Prompt; set => EmbedClass.Prompt = value; }
62 | ///
63 | /// 设置提示
64 | ///
65 | /// 提示内容
66 | ///
67 | public MsgEmbed SetPrompt(string? prompt) { Prompt = prompt; return this; }
68 | ///
69 | /// 缩略图URL
70 | ///
71 | public string? Thumbnail { get => EmbedThumbnail.Url; set => EmbedThumbnail.Url = value; }
72 | ///
73 | /// 设置缩略图
74 | ///
75 | /// 缩略图URL
76 | ///
77 | public MsgEmbed SetThumbnail(string? thumbnail) { Thumbnail = thumbnail; return this; }
78 | ///
79 | /// 消息列表
80 | ///
81 | public List EmbedFields { get; set; }
82 | ///
83 | /// 添加一行内容
84 | ///
85 | /// 行内容
86 | ///
87 | public MsgEmbed AddLine(string? content) { EmbedFields.Add(new(content)); return this; }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgImage.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 图片消息
7 | ///
8 | public class MsgImage : MessageToCreate
9 | {
10 | ///
11 | /// 构建图片消息
12 | ///
13 | /// 图片URL
14 | /// 要回复的消息id
15 | public MsgImage(
16 | string? image = null,
17 | string? replyMsgId = null
18 | )
19 | {
20 | Id = replyMsgId;
21 | Image = image;
22 | }
23 | ///
24 | /// 设置要回复的目标消息
25 | ///
26 | /// 目标消息的Id
27 | ///
28 | public MsgImage SetReplyMsgId(string? msgId) { Id = msgId; return this; }
29 | ///
30 | /// 设置图片网址
31 | ///
32 | /// 图片URL
33 | ///
34 | public MsgImage SetImage(string? image) { Image = image; return this; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgTag.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 消息内嵌标签
7 | /// 仅作用于content中
8 | ///
9 | public static class MsgTag
10 | {
11 | ///
12 | /// 创建 @用户 标签
13 | ///
14 | /// 用户id
15 | ///
16 | public static string User(string userId)
17 | {
18 | return $"<@!{userId}>";
19 | }
20 | ///
21 | /// 创建 #子频道 标签
22 | ///
23 | /// 子频道id
24 | ///
25 | public static string Channel(string channelId)
26 | {
27 | return $"<#{channelId}>";
28 | }
29 |
30 | ///
31 | /// 创建 @用户 标签
32 | ///
33 | /// 用户对象
34 | ///
35 | public static string Tag(this User user) => User(user.Id);
36 |
37 | ///
38 | /// 创建 #子频道 标签
39 | ///
40 | /// 子频道对象
41 | ///
42 | public static string Tag(this Channel channel) => Channel(channel.Id);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/QQChannelBot/MsgHelper/MsgText.cs:
--------------------------------------------------------------------------------
1 | using QQChannelBot.Models;
2 |
3 | namespace QQChannelBot.MsgHelper
4 | {
5 | ///
6 | /// 文字消息
7 | ///
8 | public class MsgText : MessageToCreate
9 | {
10 | ///
11 | /// 构建文字消息
12 | ///
13 | /// 消息内容
14 | /// 要回复的消息Id
15 | public MsgText(
16 | string? content = null,
17 | string? replyMsgId = null
18 | )
19 | {
20 | Id = replyMsgId;
21 | Content = content;
22 | }
23 | ///
24 | /// 设置要回复的目标消息
25 | ///
26 | /// 目标消息的Id
27 | ///
28 | public MsgText SetReplyMsgId(string? msgId) { Id = msgId; return this; }
29 | ///
30 | /// 设置消息内容
31 | ///
32 | /// 文字内容
33 | ///
34 | public MsgText SetContent(string? content) { Content = content; return this; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/QQChannelBot/QQChannelBot.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | 1.6.3
8 | QQ机器人框架
9 | Copyright © 2021 Antecer. All rights reserved.
10 | https://github.com/Antecer/QQChannelBot
11 | https://github.com/Antecer/QQChannelBot.git
12 | QQ;Channel;Bot;频道;机器人
13 | True
14 | MIT
15 | README.md
16 | git
17 | 优化指令查询方法;
18 | True
19 | logo.png
20 | True
21 |
22 |
23 |
24 |
25 | True
26 | \
27 |
28 |
29 |
30 |
31 |
32 | True
33 | \
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/QQChannelBot/Tools/JsonHelper.cs:
--------------------------------------------------------------------------------
1 | namespace System.Text.Json
2 | {
3 | ///
4 | /// Json访问帮助类
5 | ///
6 | public static class JsonHelper
7 | {
8 | ///
9 | /// 查找JSON对象
10 | ///
11 | ///
12 | /// json的key
13 | ///
14 | public static JsonElement? Get(this JsonElement element, string name)
15 | {
16 | if (element.ValueKind != JsonValueKind.Object) return null;
17 | return element.TryGetProperty(name, out JsonElement value) ? value : null;
18 | }
19 | ///
20 | /// 索引JSON数组
21 | ///
22 | ///
23 | /// json的index
24 | ///
25 | public static JsonElement? Get(this JsonElement element, int index)
26 | {
27 | if (element.ValueKind != JsonValueKind.Array)
28 | {
29 | if (element.ValueKind == JsonValueKind.Object) return element.Get(index.ToString());
30 | return null;
31 | }
32 | JsonElement value = element.EnumerateArray().ElementAtOrDefault(index);
33 | return value.ValueKind != JsonValueKind.Undefined ? value : null;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/QQChannelBot/Tools/StringTools.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace System
4 | {
5 | ///
6 | /// 字符串处理工具类
7 | ///
8 | public static class StringTools
9 | {
10 | ///
11 | /// 替换字符串开始位置的字符串
12 | ///
13 | /// 源字符串
14 | /// 查找的串
15 | /// 替换的串
16 | /// 查找字符串时忽略大小写
17 | ///
18 | public static string TrimStart(this string input, string oldValue, string newValue = "", bool ignoreCase = false)
19 | {
20 | if (string.IsNullOrEmpty(input)) return input;
21 | if (!input.StartsWith(oldValue, ignoreCase, null)) return input;
22 | return string.Concat(newValue, input.AsSpan(oldValue.Length));
23 | }
24 | ///
25 | /// 替换字符串末尾位置的字符串
26 | ///
27 | /// 源字符串
28 | /// 查找的串
29 | /// 替换的串
30 | /// 查找字符串时忽略大小写
31 | ///
32 | public static string TrimEnd(this string input, string oldValue, string newValue = "", bool ignoreCase = false)
33 | {
34 | if (string.IsNullOrEmpty(input)) return input;
35 | if (!input.EndsWith(oldValue, ignoreCase, null)) return input;
36 | return string.Concat(input.Remove(input.Length - oldValue.Length), newValue);
37 | }
38 | ///
39 | /// 替换字符串开始和末尾位置的字符串
40 | ///
41 | /// 源字符串
42 | /// 查找的串
43 | /// 替换的串
44 | /// 查找字符串时忽略大小写
45 | ///
46 | public static string Trim(this string input, string oldValue, string newValue = "", bool ignoreCase = false)
47 | {
48 | return input.TrimStart(oldValue, newValue, ignoreCase).TrimEnd(oldValue, newValue, ignoreCase);
49 | }
50 | ///
51 | /// 判断字符串是否为空白
52 | /// 效果等同于string.IsNullOrWhiteSpace()
53 | ///
54 | ///
55 | ///
56 | public static bool IsBlank(this string? input)
57 | {
58 | return string.IsNullOrWhiteSpace(input);
59 | }
60 |
61 | ///
62 | /// 判断指定的字符串是 null还是空字符串
63 | ///
64 | ///
65 | ///
66 | public static bool IsNullOrEmpty(this string? input) => string.IsNullOrEmpty(input);
67 | ///
68 | /// 判断指定的字符串是 null、空还是仅由空白字符组成
69 | ///
70 | ///
71 | ///
72 | public static bool IsNullOrWhiteSpace(this string? input) => string.IsNullOrWhiteSpace(input);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/QQChannelBot/Tools/Unicoder.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Text.RegularExpressions;
3 |
4 | namespace QQChannelBot.Tools
5 | {
6 | ///
7 | /// Unicode编解码器
8 | ///
9 | public static class Unicoder
10 | {
11 | private static readonly Regex reUnicode = new(@"\\u([0-9a-fA-F]{4})", RegexOptions.Compiled);
12 | ///
13 | /// Unicode编码(\uxxxx)序列转字符串
14 | ///
15 | ///
16 | ///
17 | public static string Decode(string s)
18 | {
19 | return reUnicode.Replace(s, m =>
20 | {
21 | if (short.TryParse(m.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out short c))
22 | {
23 | return "" + (char)c;
24 | }
25 | return m.Value;
26 | });
27 | }
28 |
29 | private static readonly Regex reUnicodeChar = new(@"[^\u0000-\u00ff]", RegexOptions.Compiled);
30 | ///
31 | /// 字符串转Unicode编码(\uxxxx)序列
32 | ///
33 | ///
34 | ///
35 | public static string Encode(string s)
36 | {
37 | return reUnicodeChar.Replace(s, m => string.Format(@"\u{0:x4}", (short)m.Value[0]));
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/QQChannelBot/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Antecer/QQChannelBot/18b48c60246e518878dd89e33a72608adabe59c6/QQChannelBot/logo.png
--------------------------------------------------------------------------------