{"id":17842173,"url":"https://github.com/antecer/qqchannelbot","last_synced_at":"2025-03-20T02:31:12.211Z","repository":{"id":46380772,"uuid":"439660155","full_name":"Antecer/QQChannelBot","owner":"Antecer","description":"QQ频道机器人","archived":false,"fork":false,"pushed_at":"2023-07-13T08:50:38.000Z","size":256,"stargazers_count":59,"open_issues_count":1,"forks_count":10,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-14T03:43:15.027Z","etag":null,"topics":["bot","chatbot","qq","qqbot"],"latest_commit_sha":null,"homepage":"https://Antecer.github.io/QQChaannelBot/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Antecer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-18T16:17:13.000Z","updated_at":"2025-01-14T04:52:16.000Z","dependencies_parsed_at":"2024-10-27T21:42:30.066Z","dependency_job_id":null,"html_url":"https://github.com/Antecer/QQChannelBot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antecer%2FQQChannelBot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antecer%2FQQChannelBot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antecer%2FQQChannelBot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antecer%2FQQChannelBot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Antecer","download_url":"https://codeload.github.com/Antecer/QQChannelBot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244538579,"owners_count":20468741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","chatbot","qq","qqbot"],"created_at":"2024-10-27T21:10:24.265Z","updated_at":"2025-03-20T02:31:11.801Z","avatar_url":"https://github.com/Antecer.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# QQ机器人框架\n\n[![NuGet](https://buildstats.info/nuget/QQChannelBot)](https://www.nuget.org/packages/QQChannelBot)\n[![License](https://img.shields.io/github/license/Antecer/QQChannelBot?logo=GitHub)](https://www.nuget.org/packages/QQChannelBot)\n[![Platform](https://img.shields.io/badge/platform-.net6-blue?logo=Visual%20Studio)](https://www.nuget.org/packages/QQChannelBot)\n[![Benchmarks](https://img.shields.io/badge/benchmarks-%E2%9C%9437%20%7C%20%E2%9C%982%20%7C%20%E2%9E%9C2-orange?logo=TestCafe)](https://github.com/Antecer/QQChannelBot/blob/main/QQChannelBot/Tools/Benchmarks.cs)\n\n使用.NET6技术封装的QQ机器人通信框架，无需任何第三方依赖。   \n傻瓜式封装，极简的调用接口，让新手也能快速入门！\n\n## 使用说明\n\n1.配置日志输出等级（默认值：LogLevel.INFO）\n``` cs\nLog.LogLevel = LogLevel.DEBUG;\n```\n\n2.创建机器人, 配置参数请查阅 [QQ机器管理后台](https://bot.q.qq.com/#/developer/developer-setting)\n```cs\n// 配置鉴权信息\nBotClient bot = new(new()\n{\n    BotAppId = 开发者ID,\n    BotToken = 机器人令牌,\n    BotSecret = 机器人密钥\n});\n// 配置频道事件监听\nbot.Intents = Intents.Public;\n// 配置自定义指令前缀（私域机器人可自定义前缀触发指令匹配功能，默认值\"/\"；公域机器人无需配置）\nbot.CommandPrefix = \"/\";\n// 配置消息过滤器（结果为true的消息将被拦截）\nbot.MessageFilter = (sender) =\u003e\n{\n    return sender.ChannelId != \"2500191\";\n};\n```\n\n3.框架事件订阅演示\n```\n// 订阅 OnReady 事件，这里根据机器人信息修改控制台标题\nbot.OnReady += (sender) =\u003e\n{\n    var sdk = typeof(BotClient).Assembly.GetName();\n    consoleTitle = $\"{sender.UserName}{(bot.PrivateGuilds.Any() ? \"_Private\" : \"_Public\")} \u003c{sender.Id}\u003e - SDK版本：{sdk.Name}_{sdk.Version}；　连接状态：\";\n    Console.Title = consoleTitle;\n};\n```\n\n4.如何接收 @机器人 消息（注：OnAtMessage 事件已弃用）\n```cs\n// 订阅消息创建事件,处理所有收到的消息\n// 注1：如果该消息包含 @机器人 标签，则isAt=true\n// 注2：被 AddCommand 指令命中的消息不会出现在这里\n// sender是Message对象(自动内嵌BotClient对象，以支持快速回复)\n// 这里示例快速回复用户的AT消息，并修改 @机器人 为 @用户\nbot.OnMsgCreate += async (sender, isAt) =\u003e\n{\n    if (isAt)\n    {\n        string replyMsg = sender.Content.Replace(sender.Bot.Info.Tag, sender.Author.Tag);\n        await sender.ReplyAsync(replyMsg);\n    }\n};\n```\n\n5.注册自定义消息命令\n```cs\n// 注册自定义命令，这里让机器人复读用户的消息\n// 如:用户发送 @机器人 复读 123\n// 　 机器回复 @用户 123\nbot.AddCommand(new Command(\"复读\", async (sender, msg) =\u003e\n{\n    await sender.ReplyAsync($\"{sender.Author.Tag} {msg}\");\n}));\n```\n\n6.启动机器人(开始运行并监听频道消息)\n```cs\nbot.Start();\n```\n\n## 以下是API调用玩法示例\n#### 注：AddCommand注册指令；[点此查看Command指令对象详细参数](https://github.com/Antecer/QQChannelBot/blob/main/QQChannelBot/Bot/Command.cs)\n```\n// 指令格式：@机器人 菜单\n// 注：这里排除了Command属性Note有内容的指令\nbot.AddCommand(new Command(\"菜单\", async (sender, args) =\u003e\n{\n    await sender.ReplyAsync(string.join('、', sender.Bot.GetCommands.Where(cmd =\u003e string.IsNullOrWhiteSpace(cmd.Note)).Select(cmd =\u003e cmd.Name).ToList()));\n}, note: \"hide\"));\n// 注册自定义命令，这里测试embed消息 ( 实现功能为获取用户信息，指令格式： @机器人 UserInfo @用户 )\nbot.AddCommand(new Command(\"用户信息\", async (sender, msg) =\u003e\n{\n    string userId = Regex.IsMatch(msg, @\"\u003c@!(\\d+)\u003e\") ? Regex.Match(msg, @\"\u003c@!(\\d+)\u003e\").Groups[1].Value : sender.Author.Id;\n    Member member = await bot.GetMemberAsync(sender.GuildId, userId) ?? new()\n    {\n        User = sender.Author,\n        Roles = sender.Member.Roles,\n        JoinedAt = sender.Member.JoinedAt,\n    };\n    List\u003cRole\u003e? roles = await bot.GetRolesAsync(sender.GuildId);\n    List\u003cstring\u003e roleNames = roles?.Where(role =\u003e member.Roles.Contains(role.Id)).Select(role =\u003e role.Name).ToList() ?? new List\u003cstring\u003e { \"无权获取身份组列表\" };\n    MsgEmbed ReplyEmbed = new MsgEmbed()\n    {\n        Title = member.User!.UserName,\n        Prompt = $\"{member.User.UserName} 的信息卡\",\n        Thumbnail = member.User.Avatar\n    }\n    .AddLine($\"用户昵称：{member.Nick}\")\n    .AddLine($\"账户类别：{(member.User.Bot ? \"机器人\" : \"人类\")}\")\n    .AddLine($\"角色分类：{string.Join(\"、\", roleNames)}\")\n    .AddLine($\"加入时间：{member.JoinedAt!.Remove(member.JoinedAt.IndexOf('+'))}\");\n    await sender.ReplyAsync(ReplyEmbed);\n}));\n// 指令格式：@机器人 创建公告 公告内容\nbot.AddCommand(new Command(\"创建公告\", async (sender, args) =\u003e\n{\n    if (args.IsBlank())\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 未指定公告内容！正确格式：\\n创建公告 公告内容\");\n        return;\n    }\n    Message? sendmsg = await sender.ReplyAsync(args);\n    Announces? announces = null;\n    if (sendmsg != null) announces = await sender.CreateAnnouncesAsync(sendmsg);\n    if (sendmsg == null || announces == null) await sender.ReplyAsync($\"{sender.Author.Tag} 公告创建失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n    else await sender.ReplyAsync($\"{sender.Author.Tag} 公告已发布\");\n}, null, true));\n// 指令格式：@机器人 删除公告\nbot.AddCommand(new Command(\"删除公告\", async (sender, args) =\u003e\n{\n    if (await sender.DeleteAnnouncesAsync()) await sender.ReplyAsync($\"{sender.Author.Tag} 公告已删除！\");\n    else await sender.ReplyAsync($\"{sender.Bot.Info.UserName} 无权删除公告！\");\n}, null, true));\n// 指令格式：@机器人 创建全局公告 公告内容\nbot.AddCommand(new Command(\"创建全局公告\", async (sender, args) =\u003e\n{\n    if (args.IsBlank())\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 未指定公告内容！正确格式：\\n创建全局公告 公告内容\");\n        return;\n    }\n    Message? sendmsg = await sender.ReplyAsync(args);\n    Announces? announces = null;\n    if (sendmsg != null) announces = await sender.CreateAnnouncesGlobalAsync(sendmsg);\n    if (sendmsg == null || announces == null) await sender.ReplyAsync($\"{sender.Author.Tag} 全局公告创建失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n    else await sender.ReplyAsync($\"{sender.Author.Tag} 全局公告已发布\");\n}, null, true));\n// 指令格式：@机器人 删除全局公告\nbot.AddCommand(new Command(\"删除全局公告\", async (sender, args) =\u003e\n{\n    if (await sender.DeleteAnnouncesGlobalAsync()) await sender.ReplyAsync($\"{sender.Author.Tag} 全局公告已删除！\");\n    else await sender.ReplyAsync($\"{sender.Bot.Info.UserName} 无权删除全局公告！\");\n}, null, true));\n// 指令格式：@机器人 禁言 @用户 10天(或：到2077-12-12 23:59:59)\nbot.AddCommand(new Command(\"禁言\", async (sender, args) =\u003e\n{\n    List\u003cUser\u003e? users = sender.Mentions?.ToHashSet().ToList();\n    users?.RemoveAll(user =\u003e user.Id == sender.Bot.Info.Id); // 排除机器人自己\n    if (users?.Any() != true)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 未指定禁言的用户！正确格式：\\n禁言 @用户 禁言时间(年|星期|周|日|天|小时|分|秒；默认单位分钟)\\n禁言 禁言时间 @用户(可多个)\");\n        return;\n    }\n    string? muteMakerAfter = null;\n    string muteMakerDelay = \"1分钟\";\n    args = Regex.Replace(args, @\"\u003c@!\\d+\u003e\", \"\");\n    Match tsm = Regex.Match(args, @\"(\\d{4})[-年](\\d\\d)[-月](\\d\\d)[\\s日]*(\\d\\d)[:点时](\\d\\d)[:分](\\d\\d)秒?\");\n    if (tsm.Success) muteMakerAfter = tsm.Groups[0].Value;\n    else\n    {\n        Match tdm = Regex.Match(args, @\"(\\d+)\\s*(年|星期|周|日|天|小?时|分钟?|秒钟?)\");\n        if (tdm.Success) muteMakerDelay = tdm.Groups[0].Value;\n        else\n        {\n            Match ttm = Regex.Match(args, @\"\\d+\");\n            if (ttm.Success) muteMakerDelay = ttm.Groups[0].Value + \"分钟\";\n        }\n    }\n    string muteTimeAt = muteMakerAfter != null ? $\"解除时间：{muteMakerAfter}\" : $\"持续时间：{muteMakerDelay}\";\n    MuteMaker muteMaker = new(muteMakerAfter ?? muteMakerDelay);\n    foreach (var user in users)\n    {\n        if (await sender.MuteMemberAsync(user, muteMaker))\n        {\n            await sender.ReplyAsync($\"{user.UserName} 已被禁言，{muteTimeAt}\");\n        }\n        else await sender.ReplyAsync($\"禁言失败，{sender.Bot.Info.UserName} 无权禁言用户：{user.UserName}\");\n    }\n}, new Regex(@\"^禁言(?=(\\d|\\s|\u003c@!\\d+\u003e)|$)\"), true));\n// 指令格式：@机器人 解除禁言 @用户\nbot.AddCommand(new Command(\"解除禁言\", async (sender, args) =\u003e\n{\n    List\u003cUser\u003e? users = sender.Mentions;\n    users?.RemoveAll(user =\u003e user.Id == sender.Bot.Info.Id); // 排除机器人自己\n    if (users?.Any() != true)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 未指定解禁的用户!\\n正确格式：解除禁言 @用户(可多个)\");\n        return;\n    }\n    MuteTime muteTime = new(0);\n    foreach (var user in users)\n    {\n        if (await sender.MuteMemberAsync(user, muteTime))\n        {\n            await sender.ReplyAsync($\"{user.UserName} 已解除禁言\");\n        }\n        else await sender.ReplyAsync($\"解禁失败，{sender.Bot.Info.UserName} 无权解禁用户：{user.UserName}\");\n    }\n}, new Regex(@\"^(解除|取消)禁言(?=(\\s|\u003c@!\\d+\u003e)|$)\"), true));\n// 指令格式：@机器人 全体禁言 10天(或：到2077年12月12日23点59分59秒)\nbot.AddCommand(new Command(\"全员禁言\", async (sender, args) =\u003e\n{\n    Match tsm = Regex.Match(args, @\"(\\d{4})[-年](\\d\\d)[-月](\\d\\d)[\\s日]*(\\d\\d)[:点时](\\d\\d)[:分](\\d\\d)秒?\");\n    string? muteMakerAfter = null;\n    string muteMakerDelay = \"1分钟\";\n    if (tsm.Success) muteMakerAfter = tsm.Groups[0].Value;\n    else\n    {\n        Match tdm = Regex.Match(args, @\"(\\d+)\\s*(年|星期|周|日|天|小?时|分钟?|秒钟?)\");\n        if (tdm.Success) muteMakerDelay = tdm.Groups[0].Value;\n        else\n        {\n            await sender.ReplyAsync($\"{sender.Author.Tag} 时间格式不正确！正确格式：\\n全员禁言 禁言时间(年|星期|周|日|天|小时|分|秒)\\n全员禁言 禁言时间(xxxx年xx月xx日xx点(时)xx分xx秒)\");\n            return;\n        }\n    }\n    string muteTimeAt = muteMakerAfter != null ? $\"解除时间：{muteMakerAfter}\" : $\"持续时间：{muteMakerDelay}\";\n    if (await sender.MuteGuildAsync(new MuteMaker(muteMakerAfter ?? muteMakerDelay)))\n    {\n        await sender.ReplyAsync($\"已启用全员禁言，{muteTimeAt}\");\n    }\n    else await sender.ReplyAsync($\"全员禁言失败，{sender.Bot.Info.UserName} 无权启用全员禁言!\");\n}, null, true));\n// 指令格式：@机器人 解除全体禁言\nbot.AddCommand(new Command(\"解除全员禁言\", async (sender, args) =\u003e\n{\n    bool isOk = await sender.MuteGuildAsync(new MuteTime(0));\n    await sender.ReplyAsync(isOk ? \"已解除全员禁言\" : $\"解除全员禁言失败，{sender.Bot.Info.UserName} 无权解除全员禁言!\");\n}, new Regex(@\"^(解除|取消)全员禁言$\"), true));\n// 指令格式：@机器人 创建身份组\nbot.AddCommand(new Command(\"创建角色\", async (sender, args) =\u003e\n{\n    Match roleParams = Regex.Match(args, @\"([^#;\\s]+);(#[0-9a-fA-F]+)(;分组)?\");\n    if (!roleParams.Success)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 参数不正确！正确格式(使用';'连接参数)：\\n￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣\\n创建角色 名称;颜色(格式#FFFFFF);分组(可选)\\n＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿\\n例：创建角色 新角色;#FF7788;分组\\n　　新建角色 新角色2;#F0F\");\n        return;\n    }\n    string roleName = roleParams.Groups[1].Value;\n    string roleColor = roleParams.Groups[2].Value;\n    bool roleGroup = !string.IsNullOrWhiteSpace(roleParams.Groups[3].Value);\n    Role? role = await sender.CreateRoleAsync(new Info(roleName, roleColor, roleGroup));\n    if (role != null) await sender.ReplyAsync($\"{sender.Author.Tag} 成功创建新角色：\\n{role.Name};{role.ColorHtml};{(role.Hoist ? \"已\" : \"未\")}分组\");\n    else await sender.ReplyAsync($\"{sender.Author.Tag} 新角色创建失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n}, new Regex(@\"^(创|新)建角色(\\s|\\n|$)\"), true));\n// 指令格式：@机器人 遍历身份组\nbot.AddCommand(new Command(\"统计角色\", async (sender, args) =\u003e\n{\n    Guild? guild = await sender.GetGuildAsync();\n    string guildInfo = guild == null ? \"\" : $\"{guild.Name}，成员总数：{guild.MemberCount}\\n\";\n    List\u003cRole\u003e? roles = await sender.GetRolesAsync();\n    if (roles == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 获取角色列表失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n        return;\n    }\n    var roleInfoList = roles.Select(r =\u003e $\"{r.Name}：{r.Number}；{r.ColorHtml}；{(r.Hoist ? \"已\" : \"未\") }分组；编号:{r.Id}\");\n    await sender.ReplyAsync($\"{sender.Author.Tag} {guildInfo}共找到 {roleInfoList.Count()} 个身份角色：\\n\" + string.Join('\\n', roleInfoList));\n    return;\n}, null, true));\n// 指令格式：@机器人 修改身份组\nbot.AddCommand(new Command(\"修改角色\", async (sender, args) =\u003e\n{\n    Match m = Regex.Match(args, @\"(\\S+)\\n\\s*([^#;\\s]+)?;?(#[0-9a-fA-F]+)?;?(分组)?\", RegexOptions.RightToLeft);\n    string rNameL = m.Groups[1].Value;\n    string? rNameR = m.Groups[2].Value;\n    if (rNameR.IsBlank()) rNameR = null;\n    string? rColor = m.Groups[3].Value;\n    if (rColor.IsBlank()) rColor = null;\n    bool rGroup = !m.Groups[4].Value.IsBlank();\n    if (!m.Success || rNameL.IsBlank())\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 参数不正确！正确格式(使用';'连接参数)：\\n￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣\\n修改角色 原角色名(或角色ID)\\n新角色名(可选);颜色(格式#FFFFFF|可选);分组(可选)\");\n        return;\n    }\n    List\u003cRole\u003e? roles = await sender.GetRolesAsync();\n    if (roles == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 遍历角色列表失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n        return;\n    }\n    Role? role = roles.Find(x =\u003e x.Id == rNameL);\n    if (role == null)\n    {\n        roles.RemoveAll(r =\u003e r.Name != rNameL);\n        if (!roles.Any()) role = null;\n        else if (roles.Count == 1) role = roles[0];\n        else\n        {\n            var roleInfoList = roles.Select(r =\u003e $\"{r.Name}；{r.ColorHtml}；{(r.Hoist ? \"已\" : \"未\") }分组；编号:{r.Id}\");\n            await sender.ReplyAsync($\"{sender.Author.Tag} 找到多个同名角色，请使用角色ID重新发送指令：\\n\" + string.Join('\\n', roleInfoList));\n            return;\n        }\n    }\n    if (role == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 在列表中找不到角色：{rNameL}\");\n        return;\n    }\n    role = await sender.EditRoleAsync(role.Id, new Info(rNameR, rColor, rGroup));\n    if (role != null) await sender.ReplyAsync($\"{sender.Author.Tag} 成功修改角色：\\n{role.Name}；{role.ColorHtml}；{(role.Hoist ? \"已\" : \"未\") }分组；编号:{role.Id}\");\n    else await sender.ReplyAsync($\"{sender.Author.Tag} 修改角色失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n}, null, true));\n// 指令格式：@机器人 删除身份组\nbot.AddCommand(new Command(\"删除角色\", async (sender, args) =\u003e\n{\n    string rName = args;\n    if (rName.IsBlank())\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 参数不正确！正确格式：\\n￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣\\n删除角色 角色名(或角色ID)\");\n        return;\n    }\n    List\u003cRole\u003e? roles = await sender.GetRolesAsync();\n    if (roles == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 遍历角色列表失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n        return;\n    }\n    Role? role = roles.Find(x =\u003e x.Id == rName);\n    if (role == null)\n    {\n        roles.RemoveAll(r =\u003e r.Name != rName);\n        if (!roles.Any()) role = null;\n        else if (roles.Count == 1) role = roles[0];\n        else\n        {\n            var roleInfoList = roles.Select(r =\u003e $\"{r.Name}；{r.ColorHtml}；{(r.Hoist ? \"已\" : \"未\") }分组；编号:{r.Id}\");\n            await sender.ReplyAsync($\"{sender.Author.Tag} 找到多个同名角色，请使用角色ID重新发送指令：\\n\" + string.Join('\\n', roleInfoList));\n            return;\n        }\n    }\n    if (role == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 在列表中找不到角色：{rName}\");\n        return;\n    }\n    if (await sender.DeleteRoleAsync(role.Id))\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 成功删除角色：\\n{role.Name}；{role.ColorHtml}；{(role.Hoist ? \"已\" : \"未\") }分组；编号:{role.Id}\");\n    }\n    else await sender.ReplyAsync($\"{sender.Author.Tag} 删除角色失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n}, null, true));\n// 指令格式：@机器人 增加身份组成员 @用户 @用户2\nbot.AddCommand(new Command(\"增加角色成员\", async (sender, args) =\u003e\n{\n    string rName = Regex.Replace(args, @\"\u003c@!\\d+\u003e\", \"\").Trim();\n    List\u003cUser\u003e? users = sender.Mentions;\n    if (!args.Contains(sender.Bot.Info.Tag)) users?.RemoveAll(user =\u003e user.Id == sender.Bot.Info.Id); // 排除机器人自己\n    users = users?.ToHashSet().ToList();\n    if (rName.IsBlank() || users?.Any() != true)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 参数不正确！正确格式：\\n￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣\\n增加角色成员 角色名(或角色ID) @用户(可同时添加多个)\");\n        return;\n    }\n    List\u003cRole\u003e? roles = await sender.GetRolesAsync();\n    if (roles == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 遍历角色列表失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n        return;\n    }\n    Role? role = roles.Find(x =\u003e x.Id == rName);\n    if (role == null)\n    {\n        roles.RemoveAll(r =\u003e r.Name != rName);\n        if (!roles.Any()) role = null;\n        else if (roles.Count == 1) role = roles[0];\n        else\n        {\n            var roleInfoList = roles.Select(r =\u003e $\"{r.Name}；{r.ColorHtml}；{(r.Hoist ? \"已\" : \"未\") }分组；编号:{r.Id}\");\n            await sender.ReplyAsync($\"{sender.Author.Tag} 找到多个同名角色，请使用角色ID重新发送指令：\\n\" + string.Join('\\n', roleInfoList));\n            return;\n        }\n    }\n    if (role == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 在列表中找不到角色：{rName}\");\n        return;\n    }\n    foreach (var user in users)\n    {\n        bool isOk = await sender.AddRoleMemberAsync(user, role.Id, role.Id == \"5\" ? sender.ChannelId : null);\n        string replyMsg = isOk ? \"成功\" : $\"失败，{sender.Bot.Info.UserName} 无权执行该操作！\";\n        await sender.ReplyAsync($\"{user.Tag} 加入角色组 {role.Name} {replyMsg}\");\n    }\n}, null, true));\n// 指令格式：@机器人 删除身份组成员 @用户 @用户2\nbot.AddCommand(new Command(\"删除角色成员\", async (sender, args) =\u003e\n{\n    string rName = Regex.Replace(args, @\"\u003c@!\\d+\u003e\", \"\").Trim();\n    List\u003cUser\u003e? users = sender.Mentions;\n    if (!args.Contains(sender.Bot.Info.Tag)) users?.RemoveAll(user =\u003e user.Id == sender.Bot.Info.Id); // 排除机器人自己\n    users = users?.ToHashSet().ToList();\n    if (rName.IsBlank() || users?.Any() != true)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 参数不正确！正确格式：\\n￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣\\n删除角色成员 角色名(或角色ID) @用户(可同时添加多个)\");\n        return;\n    }\n    List\u003cRole\u003e? roles = await sender.GetRolesAsync();\n    if (roles == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 遍历角色列表失败，{sender.Bot.Info.UserName} 无权执行该操作！\");\n        return;\n    }\n    Role? role = roles.Find(x =\u003e x.Id == rName);\n    if (role == null)\n    {\n        roles.RemoveAll(r =\u003e r.Name != rName);\n        if (!roles.Any()) role = null;\n        else if (roles.Count == 1) role = roles[0];\n        else\n        {\n            var roleInfoList = roles.Select(r =\u003e $\"{r.Name}；{r.ColorHtml}；{(r.Hoist ? \"已\" : \"未\") }分组；编号:{r.Id}\");\n            await sender.ReplyAsync($\"{sender.Author.Tag} 找到多个同名角色，请使用角色ID重新发送指令：\\n\" + string.Join('\\n', roleInfoList));\n            return;\n        }\n    }\n    if (role == null)\n    {\n        await sender.ReplyAsync($\"{sender.Author.Tag} 在列表中找不到角色：{rName}\");\n        return;\n    }\n    foreach (var user in users)\n    {\n        bool isOk = await sender.DeleteRoleMemberAsync(user, role.Id, role.Id == \"5\" ? sender.ChannelId : null);\n        string replyMsg = isOk ? \"成功\" : $\"失败，{sender.Bot.Info.UserName} 无权执行该操作！\";\n        await sender.ReplyAsync($\"{user.Tag} 移出角色组 {role.Name} {replyMsg}\");\n    }\n}, null, true));\n```\n\n## Ark模板消息构建方法\n#### 注：模板消息的构造支持多种方式，以下尽量展示了不同的构造方式，也可以混合使用各种构造方式。\n```cs\nbot.AddCommand(new Command(\"ark测试\", async (sender, args) =\u003e\n{\n    // Ark23测试通过\n    await sender.ReplyAsync(new MsgArk23()\n        .SetDesc(\"描述\")\n        .SetPrompt(\"提示消息\")\n        .AddLine(\"第一行内容\")\n        .AddLine(\"第二行内容\")\n        .AddLine(\"百度\")\n        .AddLine(\"淘宝\")\n        .AddLine(\"腾讯\")\n        .AddLine(\"微软\")\n        .AddLine(\"最后一行\"));\n\n    // Ark24测试通过\n    await sender.ReplyAsync(new MsgArk24()\n        .SetDesc(\"描述\")\n        .SetPrompt(\"提示\")\n        .SetTitle(\"标题\")\n        .SetMetaDesc(\"详情\")\n        .SetImage(\"\")\n        .SetLink(\"\")\n        .SetSubTitle(\"子标题\"));\n\n    // Ark34测试通过\n    await sender.ReplyAsync(new MsgArk34()\n    {\n        Desc = \"描述\",\n        Prompt = \"提示\",\n        MetaTitle = \"标题\",\n        MetaDesc = \"详情\",\n        MetaIcon = \"\",\n        MetaPreview = \"\",\n        MetaUrl = \"\"\n    });\n\n    // Ark37测试通过\n    await sender.ReplyAsync(new MsgArk37(\"提示\", \"标题\", \"子标题\"));\n}));\n```\n\n### 更多玩法请参考 [Benchmarks](https://github.com/Antecer/QQChannelBot/blob/main/QQChannelBot/Tools/Benchmarks.cs)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantecer%2Fqqchannelbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantecer%2Fqqchannelbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantecer%2Fqqchannelbot/lists"}