C#Winform实战获取TIM消息

1 篇文章 0 订阅
1 篇文章 0 订阅

目录

前言

一、采集思路是什么?

二、怎么样使用软件去实时进行采集?

1.模拟人工去窗口执行全选复制的操作

2.软件解析剪切板聊天内容

3.数据清洗输出到可视化页面

总结


前言

提示: 本文仅用于学习分享,禁止用于任何违法行为,如有侵权,请联系删除。

如题,本篇文章将对TIM消息进行实时采集,优点简单易懂,不需要了解汇编与逆向,基本没有封号风险,缺点是占用键盘鼠标,下面直接上演示图片


一、采集思路是什么?

看了前言的GIF图的小伙伴不难看出直接在聊天窗中采集到了聊天数据,包括有时间,发送人,以及内容,最关键的思路是利用了剪切板

 当我们在聊天窗使用ctrl+a+c,然后在发送框中粘贴,会发现粘贴的内容是一种特殊的格式进行显示,可以判断出剪切板中可以拿到聊天的数据内容

这里通过代码对剪切板内的内容进行分析,得到8个格式化的类型,对这8个类型逐一分析,举例其中Text格式化方式可以拿到聊天数据,但是缺少了图片的信息,经过我逐一的测试,最终 QQ_Msg_RichEdit_Format 的格式方式可以拿到比较完善的数据,感兴趣的小伙伴可以自己尝试一下。那最核心的采集部分就完结了。

二、怎么样使用软件去实时进行采集?

知道了核心的采集步骤,我们的采集思路是 模拟人工去窗口执行全选复制的操作->软件解析剪切板聊天内容->数据进行清洗输出到可视化页面

1.模拟人工去窗口执行全选复制的操作

1) 第一步我们需要拿到聊天窗口的句柄,通过窗口名称或者进程ID进行获取,这里要注意,一定要把聊天窗口双击点出单独的页面来!

代码如下(示例):

   IntPtr FindTarget(string monitorContent)
        {
            var pid = 0;

            IntPtr hwndRel = IntPtr.Zero;

            int.TryParse(monitorContent, out pid);

            if (pid > 0)
            {
                hwndRel = Process.GetProcessById(pid).MainWindowHandle;
            }

            if (pid <= 0 || hwndRel == null || hwndRel == IntPtr.Zero)
            {
                hwndRel = WindowsAPI.FindWindow(null, monitorContent);
            }

            return hwndRel;
        }

2)第二步我们需要通过Windows API去执行模拟操作

模拟点击,模拟全选复制,代码如下(示例):

   WindowsAPI.SetForegroundWindow(hwnd);//置顶聊天窗

                         RECT rECT = new RECT();

                         WindowsAPI.GetWindowRect(hwnd, ref rECT);//获取句柄位置

                         //获取聊天窗内容 偏移点击
                         var x = rECT.left + 250;
                         var y = rECT.top + 150;

                         WindowsAPI.SetCursorPos(x, y);//设置鼠标位置

                         WindowsAPI.mouse_event(WindowsAPI.MouseEventFlag.LeftDown, 0, 0, 0, 0); //模拟鼠标按下操作
                         WindowsAPI.mouse_event(WindowsAPI.MouseEventFlag.LeftUp, 0, 0, 0, 0);//模拟鼠标放开操作

                         //全选复制操作
                         WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.CTRL_key, 0, 0, 0);
                         WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.A_key, 0, 0, 0);
                         WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.A_key, 0, 2, 0);

                         WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.C_key, 0, 0, 0);
                         WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.C_key, 0, 2, 0);

                         WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.CTRL_key, 0, 2, 0);

2.软件解析剪切板聊天内容

代码如下(示例):

  IDataObject data = Clipboard.GetDataObject();

                             if (data != null)
                             {
                                 var stream = (Stream)data.GetData("QQ_Unicode_RichEdit_Format", false);

                                 using (var streamReader = new StreamReader(stream))
                                 {
                                     var str = streamReader.ReadToEnd();

                                     var md5 = GetMD5(str);

                                     var dto = new UpcomingMessageDTO
                                     {
                                         Hwnd = hwnd,
                                         Message = str,
                                         MD5 = md5
                                     };

                                     if (UpcomingMessage.Contains(dto) == false)
                                     {
                                         UpcomingMessage.Enqueue(dto);
                                     }
                                 }

                                 stream.Dispose();
                                 stream.Close();
                             }

3.数据清洗输出到可视化页面

通过以上步骤从剪切板内容拿到了一串XML数据

处理比较麻烦,我把这一串XML数据转成了Json数据

 从这串Json中不难发现聊天内容已经可以拿到,但是有不同的消息内容,@type这个字段主要标识消息内容的,经过测试分析0为文本内容,1为图片,并且聊天内容会有多条拼接的情况,我们需要对拼接的数据进行拆解分析。我们拿出某一个拼接的聊天记录,利用正则进行匹配输出,如图

最后通过正则 (\S+)\s([0-9]{2}:[0-5][0-9]:[0-5][0-9])\r\n(.+|.*?) 拿到了匹配的消息内容,当然这个正则可能还有点问题,有更严谨的小伙伴自行进行测试分析了,通过正则分组拿到了昵称,时间,内容,再通过时间以及昵称进行去重,就可以在页面上呈现出来了。

代码如下(示例):

        /// <summary>
        /// 数据清洗
        /// </summary>
        void ProcessMessage()
        {
            Task.Factory.StartNew(async () =>
          {
              while (true)
              {
                  try
                  {
                      if (UpcomingMessage.Count > 0)
                      {
                          var message = UpcomingMessage.Dequeue();

                          //xml数据转json
                          var doc = new XmlDocument();
                          doc.LoadXml(message.Message);
                          string json = Newtonsoft.Json.JsonConvert.SerializeXmlNode(doc);

                          //反序列化json数据
                          var timMessage = Newtonsoft.Json.JsonConvert.DeserializeObject<TimMessage>(json);

                          var length = timMessage.QQRichEditFormat.EditElement.Count;

                          if (length > 0)
                          {
                              var outTimMessage = new List<OutTimMessage>();

                              for (int i = 0; i < length; i++)
                              {
                                  var lastMsgItem = outTimMessage.LastOrDefault();

                                  var item = timMessage.QQRichEditFormat.EditElement[i];

                                  if (item != null)
                                  {
                                      if (item.type == "1" || item.type == "2" || item.type == "6")//图片
                                      {
                                          if (lastMsgItem != null)
                                          {
                                              lastMsgItem.SendMessage = lastMsgItem.SendMessage + $"【图片{item.filepath}】";
                                          }
                                      }
                                      else if (item.type == "0")//消息
                                      {
                                          //正则拆分消息
                                          var msgs = Regex.Matches(item.cdata_section, _msgRegexStr);

                                          if (msgs?.Count > 0)
                                          {
                                              foreach (Match msg in msgs)
                                              {

                                                  var index = msg.Index;
                                                  var valueLen = msg.Value.Length;
                                                  var sender = msg.Groups[1].Value;
                                                  var time = msg.Groups[2].Value;

                                                  var sendMessage = msg.Groups[3].Value;


                                                  if (i == 0 && index > 0 && lastMsgItem != null)//消息拼接
                                                  {
                                                      lastMsgItem.SendMessage = lastMsgItem.SendMessage + sendMessage;
                                                  }
                                                  else
                                                  {
                                                      if (Message.Where(x => x.Hwnd == message.Hwnd && x.SendTime == time).Any() == false)
                                                      {
                                                          outTimMessage.Add(new OutTimMessage
                                                          {
                                                              SendTime = time,
                                                              Hwnd = message.Hwnd,
                                                              SendMessage = sendMessage,
                                                              Sender = sender
                                                          });
                                                      }

                                                  }

                                              }
                                          }
                                      }
                                      else
                                      {
                                          Console.WriteLine($"暂未支持解析类型{Newtonsoft.Json.JsonConvert.SerializeObject(item)}");
                                      }

                                  }
                              }

                              Message.AddRange(outTimMessage);

                              foreach (var item in outTimMessage)
                              {
                                  //输出消息

                                  AppandLogs($"收到消息:{item.Sender},{item.SendTime},{item.SendMessage}", LogLevel.Info);
                              }

                          }


                      }
                  }
                  catch (Exception ex)
                  {
                      AppandLogs("消息处理出错!", LogLevel.Error);
                      return;
                  }

                  await Task.Delay(1000);
              }
          }, TaskCreationOptions.LongRunning);
        }

这里数据去重采用了正序,更好的方案应该采用逆序进行去重更加高效,感兴趣的朋友可自行摸索测试了!

以上用到的WinApi链接:Windows API

下面附上懒人链接:资源地址


总结

从本篇文章可以了解到TIM聊天信息的采集思路,以及数据的清洗去重展示,那同样QQ也是同样的操作方式,甚至还可以做到抢红包,消息回复等,更多功能交给小伙伴们自行挖掘了!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奥特曼洗澡中

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值