从0开始使用.NET开发QQBot收发群内消息并接入语言大模型

前排提示:博主也是一个c#初学者,如遇有问题的地方欢迎斧正。
本文将要实现的效果如下:

step1.前往QQ机器人申请一个测试用的机器人,填写相关资料,并将测试用的ip地址加入腾讯的白名单。
获取机器人appsecret等信息,并将该机器人加入到你的群聊里面,要求在该群聊里面,你是管理或者群主。
step2. 申请大模型api接口。阅读API调用文档,获取apikey以及url和模型id,供我们以后接入,博主申请的是豆包api接口供测试使用。具体这三个参数是啥csdn也有介绍,这里不再赘述。
step3.使用visual studio 创建一个新的c#解决方案,创建一个c#控制台应用程序,以及用于存放类和方法的库。截止到博主发帖前,.NET原本有的专门为QQ机器人服务的nuget包QQBot4Sharp已经无法使用。故我们采用手搓的方法,安装Newtonsoft.json包用于转换json,安装QQBot4Sharp包用于一个简单的传参。(因为博主原本是用这个包写的,写到最后发现不能使用了,就开始看腾讯api文档开始手搓,前面的也懒得改了)总之基本没有使用这个包。
step4. 先实现将群友的消息接入豆包api接口的方法。

点击查看代码
using Newtonsoft.Json;
using System.Text;
namespace HuHuBot
{
    #region 接入豆包ai
    public class ChatBot
    {
        public string ApiKey = "这里填写你的apikey";
        public string url = "这里填写你的url,博主的url为https://ark.cn-beijing.volces.com/api/v3/chat/completions";
        public string id = "这里填写你的model id,比如博主的id为ep-**********-zzw92";
        public static int i = 1;
        private static readonly HttpClient client = new HttpClient();

        public async Task<string> ChatAi(string mycontent, string username)
        {
            if (i == 1)
            {
                client.DefaultRequestHeaders.Add("Authorization", "Bearer "+Apikey);//增加请求头,注意Bearer后面有一个空格
            }
            Console.WriteLine($"第{i}次回复问题...");
            i++;
            var requestBody = new
            {
                model = id,
                messages = new[]
                {
        new{role="system",content="你是小兔Bot。"},//这里填写你想让你的ai扮演的角色。
        new{role="user",content="用户询问你如下消息:"+mycontent}
    }
            };

            Console.WriteLine(requestBody);

            var RequestJson = JsonConvert.SerializeObject(requestBody);

            Console.WriteLine(RequestJson);

            var content = new StringContent(RequestJson, Encoding.UTF8, "application/json");
            try
            {
                //  client.PostAsync()
                var responseMessage = await client.PostAsync(url, content);
                responseMessage.EnsureSuccessStatusCode();
                string responseBody = await responseMessage.Content.ReadAsStringAsync();
                RootObject rootObject = JsonConvert.DeserializeObject<RootObject>(responseBody);
                //  rootObject.choices
                var replyContent = rootObject.choices[0].message.content;
                return replyContent;
            }
            catch (Exception ex)
            {
                return $"Error! {ex.Message.ToString()}";
            }

        }
    }
   

    public interface TyClient
    {
        string ChatAi(string input);
    }
    public abstract class TyBot : TyClient
    {
        protected string ConfigJson;
        public void TyClientBase(string configJson)
        {
            ConfigJson = configJson;
        }
        public abstract string ChatAi(string input);
    }
      

   public class Message
    {
        public string content { get; set; }
        public string role { get; set; }
    }

    public class Choice
    {
        public string finish_reason { get; set; }
        public int index { get; set; }
        public object logprobs { get; set; }
        public Message message { get; set; }
    }

    public class Usage
    {
        public int completion_tokens { get; set; }
        public int prompt_tokens { get; set; }
        public int total_tokens { get; set; }
    }

    public class RootObject
    {
        public Choice[] choices { get; set; }
        public long created { get; set; }
        public string id { get; set; }
        public string model { get; set; }
        public string @object { get; set; }
        public Usage usage { get; set; }
    }

    #endregion
   
}

step5.创建部分用于转换json的类,由于比较复杂,博主发现写代码的时候有很多重叠的地方,小问题无伤大雅。提示,这些比较简单的操作可以交给ai来处理,比如你把你要处理的json发给ChatGpt,告诉他使用newtonsoft处理json并生成相应的类,还是比较简单的。我这里也是用ai生成的。
点击查看代码

namespace HuHuBot
{
    public class MyRootObject
    {
        public int op { get; set; }
        public int s { get; set; }
        public string t { get; set; }
        public DObject d { get; set; }
    }

    public class DObject
    {
        public int version { get; set; }
        public string session_id { get; set; }
        public UserObject user { get; set; }
        public int[] shard { get; set; }
    }

    public class UserObject
    {
        public string id { get; set; }
        public string username { get; set; }
        public bool bot { get; set; }
        public int status { get; set; }
    }
    public class JsonDataObject
    {
        public int OperationCode { get; set; }
        public int SequenceNumber { get; set; }
        public string EventType { get; set; }
        public string Identifier { get; set; }
        public DataObject Data { get; set; }
    }

    public class DataObject
    {
        public AuthorDataObject Author { get; set; }
        public string Content { get; set; }
        public string GroupId { get; set; }
        public string GroupOpenId { get; set; }
        public string MessageId { get; set; }
        public string Timestamp { get; set; }
    }

    public class AuthorDataObject
    {
        public string AuthorId { get; set; }
        public string MemberOpenId { get; set; }
        public string UnionOpenId { get; set; }
    }
    public class Author
    {
        public string id { get; set; }
        public string member_openid { get; set; }
        public string union_openid { get; set; }
    }

    public class Data
    {
        public Author author { get; set; }
        public string content { get; set; }
        public string group_id { get; set; }
        public string group_openid { get; set; }
        public string id { get; set; }
        public string timestamp { get; set; }
    }

    public class Root
    {
        public int op { get; set; }
        public int s { get; set; }
        public string t { get; set; }
        public string id { get; set; }
        public Data d { get; set; }
    }


}


step6.实现监听群内消息和发送消息的方法。(腾讯啊腾讯,一个机器人怎么这么多规则)
点击查看代码
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using QQBot4Sharp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Reflection.Metadata;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace HuHuBot
{
   
    public class qqAPIGet
    {
        public static string BotToken;
        public static string Msg_id;
        public static int Msg_seq=1;
        static string receivedMessageStr = "";
        static string cuttentMessageStr = "";
        #region TokenGET 7200s
        public async Task<string> GetToken(BotCreateInfo botCreateInfo)
        {
            string tokenUrl = "https://bots.qq.com/app/getAppAccessToken";
            TokenPostClass tokenPostClass = new TokenPostClass()
            {
                appId = botCreateInfo.AppID,
                clientSecret = botCreateInfo.ClientSecret,
            };

            {
                var str = JsonConvert.SerializeObject(tokenPostClass);
                var client = new HttpClient();
                HttpContent content = new StringContent(str);
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
                HttpResponseMessage responseMessage = await client.PostAsync(tokenUrl, content);
                //            responseMessage.EnsureSuccessStatusCode();
                string responseBody = await responseMessage.Content.ReadAsStringAsync();
                var tokenReceiveClass = JsonConvert.DeserializeObject<TokenReceiveClass>(responseBody);
                BotToken= tokenReceiveClass.access_token;
                return tokenReceiveClass.access_token;
            }

        }
        public class TokenPostClass
        {
            public string appId { get; set; }
            public string clientSecret { get; set; }
        }
        public class TokenReceiveClass
        {
            public string access_token { get; set; }
            public int expires_in { get; set; }
        }
        #endregion
        #region 开放接口请求的权限验证
        public static HttpWebRequest HeadAddToken(HttpWebRequest request, string token)
        {
            string qqToken = "QQBot " + token;
            request.Headers.Add("Authorization", qqToken);
            return request;
        }
        #endregion
        #region get apiurl
        public static async Task<string> HttpWebRequest_Get(string token)
        {
            Encoding encoding = Encoding.UTF8;
            //creat request object.
            HttpClient client = new HttpClient();
            string url = "https://sandbox.api.sgroup.qq.com/gateway/bot";
            string qqToken = "QQBot " + token;
            client.DefaultRequestHeaders.Add("Authorization", qqToken);
            client.DefaultRequestHeaders.Add("accept", "application/json");
            var responseMessage = await client.GetStringAsync(url);
            var response = JsonConvert.DeserializeObject<ResponseClass>(responseMessage);
            return response.url;
        }

        public class ResponseClass
        {
            public string url { get; set; }
            public int shards { get; set; }
            Session_start_limit session_Start_Limit;
        }
        public class Session_start_limit
        {
            public int total { get; set; }
            public int remaining { get; set; }
            public int reset_after { get; set; }
            public int max_concurrency { get; set; }



        }
        #endregion
        #region 长连接建立,并获取数据/登录鉴权,获得session
        public async Task ConnectClientWebSocket(string url,string AccessToken)
        {
           
            var client = new ClientWebSocket();
            try
            {
                await client.ConnectAsync(new Uri("wss://sandbox.api.sgroup.qq.com/websocket"), CancellationToken.None);
                var returnMess=await ReceiveMessages(client);
                PayLoadClass payLoadClass = new PayLoadClass();
                payLoadClass.op = 2;
                payLoadClass.d.token = "QQBot " +AccessToken;
                payLoadClass.d.shard = [0, 1];
                payLoadClass.d.intents = (1 << 25);
                payLoadClass.d.properties = null;
                var OpCode2Identify=JsonConvert.SerializeObject(payLoadClass);
                await SendMessage(client, OpCode2Identify);
                var NewMessageReceived=await ReceiveMessages(client);
                MyRootObject root=JsonConvert.DeserializeObject<MyRootObject>(NewMessageReceived);
                if (root.t == "READY")
                {
                    _ = ListenForMessages(client);
                    Console.WriteLine("Sucess Connect");
                    _ = SendPeriodicMessage(client);
                    


                }
             //   return null;
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message.ToString());
                    }
        }
        static async Task SendMessage(ClientWebSocket client, string message)
        {
            var buffer = Encoding.UTF8.GetBytes(message);
            await client.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
        }
        static async Task ListenForMessages(ClientWebSocket client)
        {
            var buffer = new byte[1024];
            while (client.State == WebSocketState.Open)
            {
                
                var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                if (result.MessageType == WebSocketMessageType.Text)
                {
                    var receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
                    Console.WriteLine($"Received: {receivedMessage}");
                    receivedMessageStr = receivedMessage;
                    if(cuttentMessageStr != receivedMessage&&receivedMessage!= "{\"op\":11}")
                    {
                        cuttentMessageStr=receivedMessage;
                        Console.WriteLine($"Receive Message!Message:{cuttentMessageStr}");
                        ChatBot chatBot = new ChatBot();
                        
                        //////////////////////////////////////////////
                        ///处理信息

                        Root ReceivedMess=JsonConvert.DeserializeObject<Root>(cuttentMessageStr);
                       // msg_id=ReceivedMess
                        var User=ReceivedMess.d.author.id;
                        var SendMessage=ReceivedMess.d.content;
                        Msg_id = ReceivedMess.d.id;
                      //  msg_id=ReceivedMess.d.id;
                        var HuHuBotMessage=await chatBot.ChatAi(SendMessage, User);
                        Console.WriteLine(HuHuBotMessage);
                        if (HuHuBotMessage != null) { 
                        
                            var group_openid = ReceivedMess.d.group_openid;
                            var SendMessageUrl = "https://sandbox.api.sgroup.qq.com/" + $"v2/groups/{group_openid}/messages";
                            //记得要加head头.



                            if (BotToken != null)
                            {
                                // await se
                                await SendMessage2Group(SendMessageUrl, HuHuBotMessage);
                            }





                        }
                        
                        chatBot=null;

                    }
                }
            }
        }
        public class ThisMessageClass
        {
            public string content { get; set; }
            public int msg_type {  get; set; }

            public string msg_id = Msg_id;
            public int msg_seq = Msg_seq;
        }
        static async Task SendMessage2Group(string SendmessageUrl,string MessageContent)
        {
            try
            {
                HttpClient client = new HttpClient();
                ThisMessageClass thisMessageClass = new ThisMessageClass();
                thisMessageClass.content = MessageContent;
                thisMessageClass.msg_type = 0;
                var MessageJson = JsonConvert.SerializeObject(thisMessageClass);
                HttpContent httpContent = new StringContent(MessageJson);
                // httpContent = HeadAddToken(httpContent, BotToken);
                client.DefaultRequestHeaders.Add("Authorization", "QQBot "+BotToken);
                httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
                HttpResponseMessage returnMessage = await client.PostAsync(SendmessageUrl, httpContent);
                if (returnMessage != null)
                {
                    Console.WriteLine("Success Send Message!");
                    var a= await returnMessage.Content.ReadAsStringAsync();
                    Console.WriteLine(a);
                }
                // client = HeadAddToken(client,BotToken);
            }
            catch (Exception ex) { 
                Console.WriteLine(ex.Message.ToString());
            }
            Msg_seq = Msg_seq + 1;
        }
        static async Task SendPeriodicMessage(ClientWebSocket client)
        {
            int i = 1;
            while (client.State == WebSocketState.Open)
            {

                var message = "{\"op\": 1,\"d\": 251}";
                if (i == 1) { message = "{\"op\": 1,\"d\":}"; }
                else
                {
                              message = "{\"op\": 1,\"d\": 251}";
                }
                    var buffer = Encoding.UTF8.GetBytes(message);
                await client.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
                Console.WriteLine($"Heatbeat {i} Times");
                // 等待一段时间再发送下一次消息,这里设置为 5 秒
                i++;
                await Task.Delay(5000);
            }
        }
        static async Task<string> ReceiveMessages(ClientWebSocket client)
        {
            var buffer = new byte[1024];
            while (client.State == WebSocketState.Open)
            {
                var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                if (result.MessageType == WebSocketMessageType.Close)
                {
                    await client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                    return null;
                }
                else
                {
                    var receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
                    Console.WriteLine($"Received: {receivedMessage}");
                    return receivedMessage;
                }
            }
            return null;

            
            
        }
        public class PayLoadClass
        {
            public int op { get; set; }
            public D d= new D();
        }
        public class D
        {
            public string token { get; set; }
            public int intents { get; set; }
            public int[] shard = [0, 1];
            public string properties = "{\r\n      \"$os\": \"linux\",\r\n      \"$browser\": \"my_library\",\r\n      \"$device\": \"my_library\"\r\n    }";



        }









       
        #endregion

    }
}


虽然这里使用了QQBot4Sharp包,但是这个包完全是用来传参用的,截止博主发帖为止这个包已经无法使用了。

step7.控制台应用

点击查看代码
using HuHuBot;
using Newtonsoft.Json.Linq;
using QQBot4Sharp;
using QQBot4Sharp.Models;
using QQBot4Sharp.Models.QQ;
using Serilog;
using System.Text;
using System.Text.RegularExpressions;

//ChatBot chatbot = new ChatBot();
internal class Program
{
    
    static async Task Main(string[] arg)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .CreateLogger();
        // bot
        var info = new BotCreateInfo()
        {
            AppID = "这里填写你的appid",
            ClientSecret = "这里填写你的ClientSecret",
            Intents = Intents.ALL,
        };
        qqAPIGet qqAPIGet = new qqAPIGet();
        var str=await qqAPIGet.GetToken(info);
  
        var tt = await qqAPIGet.HttpWebRequest_Get(str);
        Console.WriteLine(tt);
        await qqAPIGet.ConnectClientWebSocket(tt,str);
        Console.ReadLine();


 


    }
}
这里没有完全写完,因为腾讯的规定,每隔一段时间都要重新更新token。可以采用Sleep的方法,每隔一段时间重新运行GetToken方法,时间间隔可参照腾讯的帮助文档,这里不再赘述。感谢SalmonFish对我的指导。
posted @ 2024-10-09 18:54  SoraDotNeter  阅读(212)  评论(0)    收藏  举报