Loading... ## 系列导航 <div class="preview"> <div class="post-inser post box-shadow-wrap-normal"> <a href="https://traceless.tech/index.php/archives/20/" target="_blank" class="post_inser_a no-external-link no-underline-link"> <div class="inner-image bg" style="background-image: url(https://traceless.tech/usr/themes/handsome/assets/img/sj/6.jpg);background-size: cover;"></div> <div class="inner-content" > <p class="inser-title">【手摸手系列】教你开发QQ机器人 - 目录导航</p> <div class="inster-summary text-muted"> 教程背景<p style="color:red">由于发生了众所周知的机器人平台事... </div> </div> </a> <!-- .inner-content #####--> </div> <!-- .post-inser ####--> </div> 本文系列源码:[点我进入][1] ### 本期目标 以获取群公告为例,教你利用功能接口实现调用 ### 1.找接口 <div class="tip inlineBlock info"> 所谓接口,就是可以获取到你想要的信息的方法,接口获取渠道有很多。 1. 抓包工具 2. 浏览器F12 3. 作者特意开放出来并有附带说明文档的 [比如这个][2] 4. 口口相传、坊间流传 </div> <div class="tip inlineBlock success"> 一般的HTTP接口常用两种方式:POST\\GET 简单地说, GET请求,只需要一个URL就可以获取到请求信息的,参数附带在URL里。 POST请求,GET+一个POST请求体,参数可以在URL里也可以在POST里,POST体有请求格式要求,比较常用的是JSON/XML,也可能POST方法但是不需要请求体 </div> 这里我们采用“坊间流传的群公告获取接口” <div class="tip inlineBlock info"> > 接口地址:https://web.qun.qq.com/cgi-bin/announce/get_t_list > 方式:POST > 请求体:无 > URL参数:?bkn=QQ认证TOKEN&qid=群号&ft=23&s=-1&n=10&ni=1&i=1 > cookie:uin=uin值; skey=密钥 > </div> ### 2.添加HTTP工具类 <div class="tip inlineBlock info"> 此工具类我们只针对常用的POST/GET方法(网上很多,这里贴一个我自用的,支持HTTPS) </div> <div class="tip inlineBlock success"> 首先,增加JSON解析库 Site.Traceless.Demo.Code-右键-管理NUGET包-浏览-选中Newtonsoft.Json-安装 </div> Site.Traceless.Demo.Code-新建文件夹-Tools 存放工具类 添加HttpHelper如下: <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-f316dbb824384a0781a33e73a071f4fc34" aria-expanded="true"><div class="accordion-toggle"><span style="">HttpHelper.cs</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-f316dbb824384a0781a33e73a071f4fc34" class="collapse collapse-content"><p></p> ```c# using Newtonsoft.Json; using System.IO; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace Site.Traceless.Demo.Code.Tools { /// <summary> /// Http访问的操作类 /// </summary> public static class HttpHelper { /// <summary> /// 调用GET API /// </summary> /// <param name="url"></param> /// <returns></returns> public static string GetAPI(string url) { System.Net.HttpWebRequest request = System.Net.WebRequest.Create(url) as System.Net.HttpWebRequest; request.Method = "GET"; request.UserAgent = DefaultUserAgent; System.Net.HttpWebResponse result = request.GetResponse() as System.Net.HttpWebResponse; System.IO.StreamReader sr = new System.IO.StreamReader(result.GetResponseStream(), System.Text.Encoding.UTF8); string strResult = sr.ReadToEnd(); sr.Close(); //Console.WriteLine(strResult); return strResult.Replace(" ", "").Replace("\n", ""); } /// <summary> /// 调用POST API /// </summary> /// <param name="url"></param> /// <returns></returns> public static string PostAPI(string url, string cookieStr) { System.Net.HttpWebRequest request = System.Net.WebRequest.Create(url) as System.Net.HttpWebRequest; request.Method = "POST"; request.UserAgent = DefaultUserAgent; request.Headers.Add("cookie", cookieStr); System.Net.HttpWebResponse result = request.GetResponse() as System.Net.HttpWebResponse; System.IO.StreamReader sr = new System.IO.StreamReader(result.GetResponseStream(), System.Text.Encoding.UTF8); string strResult = sr.ReadToEnd(); sr.Close(); //Console.WriteLine(strResult); return strResult.Replace(" ", "").Replace("\n", ""); } /// <summary> /// 调用GET API /// </summary> /// <param name="url"></param> /// <returns></returns> public static T GetAPI<T>(string url) where T : class { ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; System.Net.HttpWebRequest request = System.Net.WebRequest.Create(url) as HttpWebRequest; request.Method = "GET"; request.UserAgent = DefaultUserAgent; System.Net.HttpWebResponse result = request.GetResponse() as HttpWebResponse; System.IO.StreamReader sr = new System.IO.StreamReader(result.GetResponseStream(), System.Text.Encoding.UTF8); string strResult = sr.ReadToEnd(); var res = JsonConvert.DeserializeObject<T>(strResult.Replace(" ", "").Replace("\n", "")); sr.Close(); //Console.WriteLine(strResult); return res; } public static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } /// <summary> /// 调用POST API /// </summary> /// <param name="url"></param> /// <returns></returns> public static T PostAPI<T>(string url, string cookieStr) where T : class { System.Net.HttpWebRequest request = System.Net.WebRequest.Create(url) as System.Net.HttpWebRequest; request.Method = "POST"; request.UserAgent = DefaultUserAgent; request.Headers.Add("cookie", cookieStr); System.Net.HttpWebResponse result = request.GetResponse() as System.Net.HttpWebResponse; System.IO.StreamReader sr = new System.IO.StreamReader(result.GetResponseStream(), System.Text.Encoding.UTF8); string strResult = sr.ReadToEnd(); var res = JsonConvert.DeserializeObject<T>(strResult.Replace(" ", "").Replace("\n", "")); sr.Close(); //Console.WriteLine(strResult); return res; } private static readonly string DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"; public static string DownUrlPic(string url, string path, string name) { using (System.Net.WebClient wc = new System.Net.WebClient()) { wc.Headers.Add("User-Agent", DefaultUserAgent); wc.DownloadFile(url, Path.Combine(path, name)); return name; } } } } ``` <p></p></div></div></div> ### 3.快速生成接口业务模型 <div class="tip inlineBlock info"> 由于是POST接口,我们不能使用浏览器直接输入地址,我们需要使用HTTP调试工具,常用的有[Postman][3]、各种在线HTTP工具(随手百度即可找到) </div> <div class="tip inlineBlock success"> 首先我们要试试接口!我这里使用Postman 由于这个接口要使用**QQ认证TOKEN**和**认证cookie**,一般来说我们需要想办法计算这个TOKEN 但是!由于是QQ接口,酷Q已经提供了这个Token和cookie!我们可以在插件启动事件增加打印这两个值,或者私聊给主人(每次酷Q重启,都会变) ```c# e.CQApi.SendPrivateMessage(你的QQ, e.CQApi.GetCsrfToken());//这个是token,假如结果是“831679459” e.CQApi.SendPrivateMessage(你的QQ, e.CQApi.GetCookies("qun.qq.com"));//这个是cookie,假如结果是“uin=o3164170991; skey=MC8kbvaMTQ” ``` </div> 在Postman中,选择POST方法,填写对应的URL、到Headers中添加key为**cookie**,Value为**uin=o3164170991; skey=MC8kbvaMTQ** 点击“Send” <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-8f260de0ffb72072b6a7e63818638eb730" aria-expanded="true"><div class="accordion-toggle"><span style="">接口返回,点我展开</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-8f260de0ffb72072b6a7e63818638eb730" class="collapse collapse-content"><p></p> ```json { //对比一下实际信息,我们可以分析出来 "ec": 0, "em": "", "ltsm": 1584515429, "srv_code": 0, "read_only": 0, "role": 3, "feeds": [ { "u": 415206409,//发公告人的QQ "fid": "91b2c31e0000000093c8715e58b70300",//公告的唯一ID "pubt": 1584515219,//公告发送时间戳 "msg": { "text": "这里是测试公告2",//公告内容 "text_face": "这里是测试公告2",//公告摘要 "title": "群公告"//公告标题 }, "type": 6,//公告类型,具体有哪些类型,谁知道呢 "fn": 0, "cn": 0, "vn": 0, "settings": {//大概是一些设置 "is_show_edit_card": 0, "remind_ts": 0, "tip_window_type": 0, "confirm_required": 0 }, "read_num": 0,//已读人数 "is_read": 0,//本QQ是否已读 "is_all_confirm": 0//是否全部已读 }, { "u": 415206409, "fid": "91b2c31e000000008dc8715e5f020100", "pubt": 1584515213, "msg": { "text": "这里是测试公告1", "text_face": "这里是测试公告1", "title": "群公告" }, "type": 6, "fn": 0, "cn": 0, "vn": 0, "settings": { "is_show_edit_card": 0, "remind_ts": 0, "tip_window_type": 0, "confirm_required": 0 }, "read_num": 0, "is_read": 0, "is_all_confirm": 0 } ], "group": { "group_id": 516141713,//群号 "class_ext": 10048//鬼知道是什么不重要 }, "sta": 1, "gln": 0, "tst": 10, "ui": { "415206409": { "n": "return true;", "f": "http://thirdqq.qlogo.cn/g?b=oidb&k=Z6ByEIFFnFl1PAzSpAictnw&s=40" }, "3164170991": { "n": "影妹", "f": "http://thirdqq.qlogo.cn/g?b=oidb&k=0e0c3Ef2tzkku3638WiavjQ&s=40" } }, "server_time": 1584515429000, "svrt": 1584515429, "ad": 0 } ``` <p></p></div></div></div> <div class="tip inlineBlock info"> 骚操作来了,复制整段JSON Model中新建一个cs,比如GroupNoticeResp.cs 顶部工具栏 **编辑-选择性粘贴-JSON粘贴为类** 奇迹发生了! <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-8c5b007042586ee390b316fdce4e114e58" aria-expanded="true"><div class="accordion-toggle"><span style="">自动生成了模型!</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-8c5b007042586ee390b316fdce4e114e58" class="collapse collapse-content"><p></p> ```c# public class Rootobject { public int ec { get; set; } public string em { get; set; } public int ltsm { get; set; } public int srv_code { get; set; } public int read_only { get; set; } public int role { get; set; } public Feed[] feeds { get; set; } public Group group { get; set; } public int sta { get; set; } public int gln { get; set; } public int tst { get; set; } public Ui ui { get; set; } public long server_time { get; set; } public int svrt { get; set; } public int ad { get; set; } } public class Group { public int group_id { get; set; } public int class_ext { get; set; } } public class Feed { public int u { get; set; } public string fid { get; set; } public int pubt { get; set; } public Msg msg { get; set; } public int type { get; set; } public int fn { get; set; } public int cn { get; set; } public int vn { get; set; } public Settings settings { get; set; } public int read_num { get; set; } public int is_read { get; set; } public int is_all_confirm { get; set; } } public class Msg { public string text { get; set; } public string text_face { get; set; } public string title { get; set; } } public class Settings { public int is_show_edit_card { get; set; } public int remind_ts { get; set; } public int tip_window_type { get; set; } public int confirm_required { get; set; } } ``` <p></p></div></div></div> 由于这个接口ui部分的特殊性,我们稍微做一些修改,由于UI部分没有用,我们把class Ui和下面两个class _qq号 删掉(要使用的话,可以修改为Object,再进行额外解析) 把新建时自动生成的 > class GroupNoticeResp{} 去掉 把Rootobject修改为GroupNoticeResp </div> ### 4.封装统一方法 根据你的需要,封装一个获取群公告的方法 - 新建一个Func文件夹,存放功能性方法 - 新建QGroupExtend <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-b248e3f74969066983630611d895b62934" aria-expanded="true"><div class="accordion-toggle"><span style="">QGroupExtend.cs</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-b248e3f74969066983630611d895b62934" class="collapse in collapse-content"><p></p> ```c# using Newtonsoft.Json; using Site.Traceless.Demo.Code.Model; using Site.Traceless.Demo.Code.Tools; namespace Site.Traceless.Demo.Code.Func { public static class QGroupExtend { public static string GROUP_NOTICE_STR = @"https://web.qun.qq.com/cgi-bin/announce/get_t_list"; public static GroupNoticeResp getGroupNotice(string bkn, long gid, string cookieStr) { string url = $"{GROUP_NOTICE_STR}?bkn={bkn}&qid={gid}&ft=23&s=-1&n=10&ni=1&i=1"; return JsonConvert.DeserializeObject<GroupNoticeResp>(JsonConvert.SerializeObject(HttpHelper.PostAPI<GroupNoticeResp>(url, cookieStr))); } } } ``` <p></p></div></div></div> ### 5.方法调用 例如:我需要在私聊机器人 "公告 群号"的时候获得该群的公告列表 首先,在AppEnable做如下更改 ```c# iObject = new IniObject { new IniSection("gcommands") { { "功能1","funcOne"}, { "功能2","funcTwo"} }, new IniSection("pcommands") { { "功能1","funcOne"}, { "功能2","funcTwo"}, { "公告","getGNotice"}, } }; ``` 在GroupApp添加getGNotice方法 <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-12b616ed34b5cd95b39dc038f7f1b6c873" aria-expanded="true"><div class="accordion-toggle"><span style="">FriendApp.cs</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-12b616ed34b5cd95b39dc038f7f1b6c873" class="collapse in collapse-content"><p></p> ```c# using Native.Sdk.Cqp.EventArgs; using Site.Traceless.Demo.Code.Func; using Site.Traceless.Demo.Code.Model; using System; using System.Text; namespace Site.Traceless.Demo.Code.Command { public class FriendApp { public static void funcOne(CQPrivateMessageEventArgs e, AnalysisMsg msg) { e.CQApi.SendPrivateMessage(e.FromQQ, $"[这里是私聊方法1]", $"参数数 {msg.OrderCount}\n", $"触发指令(第一参数 what) {msg.What}\n", $"目标(第二参数 who) {msg.Who}\n", $"怎么做(第三参数 how) {msg.How}\n", $"原始信息 {msg.OriginStr}\n", e.ToString()); } public static void funcTwo(CQPrivateMessageEventArgs e, AnalysisMsg msg) { e.CQApi.SendPrivateMessage(e.FromQQ, $"[这里是私聊方法2]", $"参数数 {msg.OrderCount}\n", $"触发指令(第一参数 what) {msg.What}\n", $"目标(第二参数 who) {msg.Who}\n", $"怎么做(第三参数 how) {msg.How}\n", $"原始信息 {msg.OriginStr}\n", e.ToString()); } public static void getGNotice(CQPrivateMessageEventArgs e, AnalysisMsg msg) { //获取公告内容 GroupNoticeResp resp = QGroupExtend.getGroupNotice(e.CQApi.GetCsrfToken() + "", Convert.ToInt32(msg.Who), e.CQApi.GetCookies("qun.qq.com")); if (resp == null || resp.feeds == null) { //公告为空 e.CQApi.SendPrivateMessage(e.FromQQ, "抱歉,公告为空"); return; } StringBuilder sb = new StringBuilder(); int i = 0; sb.AppendLine($"总计公告({resp.feeds.Length}条"); foreach (Feed feed in resp.feeds) { sb.AppendLine($"{i+1}.{feed.msg.title}"); sb.AppendLine($"内容:"); sb.AppendLine($"{feed.msg.text}"); i++; } e.CQApi.SendPrivateMessage(e.FromQQ.Id, sb.ToString()); } } } ``` <p></p></div></div></div> 如果插件已经启动过 **酷Q目录\\data\\app\\你的appid** 下,找到command.ini文件 修改为 ```ini [gcommands] 功能1=funcOne 功能2=funcTwo [pcommands] 功能1=funcOne 功能2=funcTwo 公告=getGNotice ``` ### 6.去爽一下 编译插件!打包为CPK! 只要私聊机器人 “公告 123456” 【当然,机器人要在那个群里】 即可收到机器人的返回如下: <div class="tip inlineBlock success"> 总计公告1条 1.群公告测试标题 内容: 群公告测试内容 </div> ### 下集预告 <div class="tip inlineBlock info"> 1.SQlite-net引用 2.SQlite-net使用 <div class="preview"> <div class="post-inser post box-shadow-wrap-normal"> <a href="https://traceless.tech/index.php/archives/24/" target="_blank" class="post_inser_a no-external-link no-underline-link"> <div class="inner-image bg" style="background-image: url(https://gitee.com/DotNetTraceless/mydatadb/raw/master/img/20200327161822.png);background-size: cover;"></div> <div class="inner-content" > <p class="inser-title">【手摸手系列】教你开发QQ机器人 - SQLITE工具类</p> <div class="inster-summary text-muted"> 系列导航本文系列源码:点我进入本期目标结合实际场景使用sqlite-net1.整合Sqlite-net首先新建类库... </div> </div> </a> <!-- .inner-content #####--> </div> <!-- .post-inser ####--> </div> </div> [1]: https://github.com/traceless0929/Native.Cqp.Csharp/tree/demo [2]: https://api.imjad.cn/ [3]: https://www.postman.com/ [4]: https://gitee.com/DotNetTraceless/mydatadb/raw/master/img/80CpQS.png Last modification:July © Allow specification reprint Support Appreciate the author AliPayWeChat Like 0 如果觉得我的文章对你有用,请随意赞赏