由于工作需要,最近一年一直在研究和使用C#,加上最近工作上有做微信机器人的需要,在已经对接、调试稳定之后,将项目的源码分享给大家,传递开源精神。
一、环境依赖
1、开发工具:Vistual Studio 2022
2、NetFramework版本:6.0及以上
3、微信版本:WeChatSetup_3.7.0.30.exe (找不到可以私信我)
二、支持功能
目前该组件自身支持很多功能,如下所示:
- 获取通讯录
- 获取所有群成员基础信息(含wxid,群昵称,微信昵称)
- 发送文本、图片、文件、xml文章、名片、群艾特消息
- 根据wxid查询好友信息
- 根据群ID获取所有群成员WXID / 同时获取一个微信群内所有成员的群昵称
- 检测好友状态(是否好友、被删除、被拉黑)
- 接收各类消息,可写回调函数进行处理
- 群管理
- 微信多开
- 自动合并短的文本、艾特信息(可设定单条信息最大长度)
- 自动分割过长的单条信息
在这些功能的基础上我们可以结合自身的使用场景进行定制化开发和调用。目前我主要根据群聊中@和私聊这两个场景进行一些封装,我将代码进行了删减,仅保留基础部分。
三、项目结构及如何运行
1、项目结构
项目整体有三个部分构成。如下图所示:
ConsoleApp1用于自定义逻辑和启动类都写在中。–自行创建
RS.Snail.JJJ.Wechat是底层依赖组件。–可参考我上传的附件
Segments是自带中文分词器的一个组件,用于对用户问题与问题列表进行分词匹配。–下篇文章详解
2、自定义逻辑实现
启动类实现代码 Program.cs 代码如下
// See https://aka.ms/new-console-template for more information
using ConsoleApp1;
using Newtonsoft.Json.Linq;
using RS.Snail.JJJ.Wechat;
using static ConsoleApp1.MessageHandler;
Console.WriteLine("Hello, World!");
var wechat = new RS.Snail.JJJ.Wechat.Service();
String mSelf_WXID = "wxid_m2khoia398xh22";//登录
bool v = wechat.Init(
// new List<string> { "wxid_1234_" },
new List<string> { mSelf_WXID },
new Func<dynamic, Task>((msg) => {
Console.Write(msg);
;
//CDATA[,wxid_8qfg0x0do56t22
MessageParam mMessageParam = new MessageParam(mSelf_WXID,wechat, (JObject)msg);
if (mMessageParam.isAtSelf())//判断是否是群聊@微信机器人
{
List<String> atPersons = mMessageParam.getAtList();
String mTemp = ((JObject)msg).GetValue("message").ToString();
foreach (String mItem in atPersons) {
String mOld = "@" + wechat.ChatroomGetMemberNick(mSelf_WXID, ((JObject)msg).GetValue("sender").ToString(), mItem);
mTemp = mTemp.Replace("@微信客服", "").Trim();//注意,这里需要与自身微信昵称保持一致
}
ThreadParam mThreadParam = new ThreadParam() {
ObjParam = wechat,Msg=msg, MTemp=mTemp, Self_WXID=mSelf_WXID
};
new Thread(new ParameterizedThreadStart((Obj) =>
{
new MessageHandler().sendAnswer((ThreadParam)Obj);
})).Start(mThreadParam);
}
else {
String mTemp = ((JObject)msg).GetValue("message").ToString();
List<String> atPersons = mMessageParam.getAtList();
ThreadParam mThreadParam = new ThreadParam()
{
ObjParam = wechat,
Msg = msg,
MTemp = mTemp,
Self_WXID = mSelf_WXID
};
new Thread(new ParameterizedThreadStart((Obj) =>
{
new MessageHandler().transferAnser((ThreadParam)Obj);
})).Start(mThreadParam);
}
return new Task(new Action(() => {
}));
})
//msg => Console.Write(msg)
) ;
//图片保存钩子
//wechat.MsgStartImageHook(mSelf_WXID, "D:\\image");
wechat.StartReceive();
Console.ReadKey();
Console.ReadKey();
Console.ReadKey();
Console.ReadKey();
Console.ReadKey();
Console.ReadKey();
Console.ReadKey();
Console.ReadKey();
Console.ReadKey();
自定义问答逻辑类MessageParam.cs 代码如下:
using JiebaNet.Segmenter.PosSeg;
using Newtonsoft.Json.Linq;
using RS.Snail.JJJ.Wechat;
using System.Diagnostics;
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Segments;
namespace ConsoleApp1
{
public class MessageParam
{
private RS.Snail.JJJ.Wechat.Service wechat;
private JObject msg;
private String self_WXID;
public Service Wechat { get => wechat; set => wechat = value; }
public JObject Msg { get => msg; set => msg = value; }
public string Self_WXID { get => self_WXID; set => self_WXID = value; }
public MessageParam(String self_WXID, RS.Snail.JJJ.Wechat.Service wechat, JObject msg) {
this.self_WXID = self_WXID;
this.wechat= wechat;
this.msg = msg;
}
public bool isAtSelf() {
String mextrainfo = ((JObject)msg).GetValue("message").ToString();
if (mextrainfo.Contains("@微信客服"))//注意,这里需要与自身微信昵称保持一致
{
return true;
}
else {
return false;
}
}
public List<String> getAtList() {
String mextrainfo = ((JObject)msg).GetValue("extrainfo").ToString();
List<String> mStrs = new List<String>();
try
{
mStrs = mextrainfo.Split(new String[] { "<atuserlist><![CDATA[", "]]></atuserlist>" }, StringSplitOptions.None)[1].Split(",").ToList();
}
catch (Exception ex) {
mStrs = new List<String>();
}
try
{
if (mStrs.Count == 0) {
mStrs = mextrainfo.Split(new String[] { "<atuserlist>", "</atuserlist>" }, StringSplitOptions.None)[1].Split(",").ToList();
}
}
catch (Exception ex)
{
mStrs = new List<String>();
}
return mStrs;
}
}
public class MessageHandler
{
public void sendAnswer(ThreadParam mThreadParam)
{
Object Obj = mThreadParam.ObjParam;
String mSelf_WXID = mThreadParam.Self_WXID;
Object msg = mThreadParam.Msg;
String mTemp = mThreadParam.MTemp.Replace("@智能客服", "").Trim();//注意,这里需要与自身微信昵称保持一致
String roomName = getChatRoomName(mSelf_WXID, Obj, msg);
String roomId = ((JObject)msg).GetValue("sender").ToString();
Service wechat = (Service)Obj;
String sendStr = "";
String similarity = "1";
if (mTemp.Equals("智能客服")|| mTemp.Trim().Equals(""))
{
sendStr = "@" + wechat.ChatroomGetMemberNick(mSelf_WXID, ((JObject)msg).GetValue("sender").ToString(),
((JObject)msg).GetValue("wxid").ToString()) + "您好,我是微信客服。有什么问题可以直接向我提问哦~ 提问方式请参考下方模板\r\n【常见问题搜索】请输入报错码/错误关键字,例如:96000322/系统异常,请您稍后再试\r\n";
((Service)Obj).MsgSendAt(
mSelf_WXID,
((JObject)msg).GetValue("sender").ToString(),
new List<string> { ((JObject)msg).GetValue("wxid").ToString() },
sendStr, false);
}else
{
//调用分词匹配进行初筛
List<JObject> resList = Segments.Segments.selectQuestion(mTemp,CommonUtils.parseErrorList());
similarity = resList.Count.ToString();
//调用搜索接口
//List<JObject> resList = CommonUtils.QueryMsg(mTemp);
if (resList.Count == 0)//未查询到答案信息
{
sendStr = "@" + wechat.ChatroomGetMemberNick(mSelf_WXID, ((JObject)msg).GetValue("sender").ToString(),
((JObject)msg).GetValue("wxid").ToString()) + " 您发送了:" + mTemp + ",未找到相应答案,请用报错码/报错信息直接进行提问。或者@群内技术推广老师进行咨询";
((Service)Obj).MsgSendAt(
mSelf_WXID,
((JObject)msg).GetValue("sender").ToString(),
new List<string> { ((JObject)msg).GetValue("wxid").ToString() },
sendStr, false);
}
else
{
StringBuilder sb = new StringBuilder();
sb.AppendLine( " 您发送了:" + mTemp + ",为您匹配到了:" + resList.Count + "条解答,详情如下:");
List<JObject> imgList = new List<JObject>();
for (int i = 0; i < resList.Count; i++)
{
String errorDetail = resList[i]["errorDetail"].ToString();
String isImg = resList[i]["isImg"].ToString();
String errorType = CommonUtils.getErrorType(resList[i]["errorType"].ToString());
//如果isImg为1,则下载并发送图片
if (isImg.Equals("1"))
{
imgList.Add(resList[i]);
}
else
{
String solution = resList[i]["errorSolution"].ToString();
String errorCode = resList[i]["errorCode"].ToString();
sb.AppendLine("错误码:"+errorCode);
sb.AppendLine("错误类型:"+ errorType);
sb.AppendLine("错误描述:"+ errorDetail);
sb.AppendLine("解决方案:"+ solution);
sb.AppendLine("=====================");
}
}
sendStr = "@" + wechat.ChatroomGetMemberNick(mSelf_WXID, ((JObject)msg).GetValue("sender").ToString(),
((JObject)msg).GetValue("wxid").ToString()) + sb.ToString();
((Service)Obj).MsgSendAt(
mSelf_WXID,
((JObject)msg).GetValue("sender").ToString(),
new List<string> { ((JObject)msg).GetValue("wxid").ToString() },
sendStr, false);
}
}
}
// 查询群名称
public String getChatRoomName(String mSelf_WXID,Object Obj,Object msg)
{
//获取数据库句柄
JArray obj = ((Service)Obj).DatabaseGetHandles(mSelf_WXID);
//查询群名称
String sql = "select NickName from Contact where UserName = '" + ((JObject)msg).GetValue("sender").ToString() + "'";
JObject first = obj.First() as JObject;
obj = ((Service)Obj).DatabaseQuery(mSelf_WXID, (uint)first["handle"], sql);
return obj[1][0].ToString();
}
public void transferAnser(ThreadParam mThreadParam)
{
//自定义逻辑
}
}
public class ThreadParam {
Object mObj;
String self_WXID;
Object msg;
String mTemp;
public object ObjParam { get => mObj; set => mObj = value; }
public string Self_WXID { get => self_WXID; set => self_WXID = value; }
public object Msg { get => msg; set => msg = value; }
public string MTemp { get => mTemp; set => mTemp = value; }
}
}
CommonUtils.cs由于都是一些发送http请求的方法,就不放源码了,大家自行实现。
3、项目启动演示
首先需要配置启动项,我们需要将ConsoleApp1配置为启动项。
启动项目后会自动打开一个控制台,同时唤醒微信客户端。
需要扫码登录后,在控制台中输入大写的Y,即可完成登录。
这里由于我的微信自动升级了版本,就不演示了。
当你在群聊中@微信机器人时,就会按照代码中的实现逻辑自动回复你了。这其中的语料库可以自己通过查库,本地读取,或者直接接入目前主流的各类大语言模型(chatGPT,文心一言,通义千问,科大星火等等),发挥自己的想象力,尽情的尝试吧。