(24)实时采集微信消息(基于独立窗体)-微信UI自动化(.Net+C#)

news2024/12/29 10:54:04

整理 | 小耕家的喵大仙

出品 | CSDN(ID:lichao19897314)

Q Q | 978124155

 往期知识回顾

(1)开启探索微信自动化之路-微信UI自动化(.Net+C#)

(2)初始化微信窗体UI自动化实例-微信UI自动化(.Net+C#)

(3)采用热键终止微信采集任务-微信UI自动化(.Net+C#)

(4)采集微信通讯录和联系人-微信UI自动化(.Net+C#)

(5)实现对微信窗体元素静默操作-微信UI自动化(.Net+C#)

(6)搜索特定微信通讯录联系人-微信UI自动化(.Net+C#)

(7)定时群发微信图文消息-微信UI自动化(.Net+C#)

(8)监控微信进程运行状态-微信UI自动化(.Net+C#)

(9)监控微信网络连接状态-微信UI自动化(.Net+C#)

(10)实现微信窗体自动跟随移动-微信UI自动化(.Net+C#)

(11)实现微信窗体尺寸跟随自动调整-微信UI自动化(.Net+C#)

(12)采集微信消息记录及历史消息-微信UI自动化(.Net+C#)

(13)自动回复微信聊天消息-微信UI自动化(.Net+C#)

(14)微信窗体元素截图操作-微信UI自动化(.Net+C#)

(15)针对微信主窗体的行为控制-微信UI自动化(.Net+C#)

(16)微信多开-微信UI自动化(.Net+C#)

(17)自动采集微信聊天信息中的文件-微信UI自动化(.Net+C#)

(18)采集微信群成员信息-微信UI自动化(.Net+C#)

(19)批量添加微信好友-微信UI自动化(.Net+C#)

(20)批量将微信群成员添加为好友-微信UI自动化(.Net+C#)

(21)批量删除微信联系人-微信UI自动化(.Net+C#)

(22)采集微信通讯录详情面板-微信UI自动化(.Net+C#)

👆😀以上文章是以往使用自动化方案操作微信的一些案例!如有兴趣请点击浏览!

因为文章可能无法满足读者要求,如需源码和支持请联系本人 QQ 978124155 

本篇目的

      有同学看了(23)实时采集微信消息(基于主窗体)--微信UI自动化(.Net+C#) 文章后,在了解到这种方式的利弊后,联系我说他想在无人值守的环境下,实时采集某些特定的群或者联系人的消息,并且确保消息不丢失。

     所以针对这个需求,我们分析如下

  1. 无人值守的情况下我们可用和微信进行UI交互,不会影响到用户正常操作。
  2. 该同学只需要采集某些特定的群或者联系人,所以我们只需要监控特定的窗体对象即可。
  3. 需要确保消息的完整性,不能丢失对话消息。

经过对上述分析,我们在针对微信的UI自动化的过程中在软件执行之前可以手动(当然也可以自动 ,如需自动请参考 (6)搜索特定微信通讯录联系人-微信UI自动化(.Net+C#) 该文章)打开需要实时采集的群或者好友窗体,记住是以独立窗口的方式打开,这样才能保证消息的准确性和会话消息完整性,因为每个窗体的会话信息都完全呈现在我们的可视区域范围内。

      该种方式并不适合在需要和微信交互的情况下使用,所以针对无人值守的场景才适用。

软件视频和部分截图

各位朋友如果时间允许可观看视频直观感受下软件的执行过程,会更加直观清晰,本人将自动化速度调节的慢些,以便更加清晰的感受到自动化带来的魅力。

 该截图为微信会话独立窗体的对话消息记录

 该截图为软件抓取微信独立会话窗体的截图,通过两张截图对比内容发现微信发送的消息和软件采集的消息是一致的。

实现思路
  1. 获取当前微信号下面所有打开的独立微信窗体。
  2. 创建DTO对象,包含消息内容,消息类型,消息发送人,上一条消息(链路结构),用户头像,这里我们要重点强调为什么要定义【上一条消息(链路结构)】这个属性,因为我们UI对象是没有包含消息的唯一标识符的,在采集过程中你无法确定消息是不是已经采集或者是不是重复。增加一个这样的结构和维度来保证消息的准确性。
  3. 如果某个独立会话窗口时第一次采集则初始化消息缓存。
  4. 如果不是第一次采集则采集最近的15条消息(根据自己需要)形成消息链路,使用发送人,消息内容做对比,如果在缓存中不存在则视为新消息,如果存在则使用链路进行对比,如果链路中的消息和缓存中的链路消息不一致也视为新消息,链路一致则视为旧消息。

关于链路的解释,我们用一个实际的微信聊天截图来直观的描述,如下描述了一个消息的链路

在比较一个消息是否是新消息,有两种情况,一种是发送人+消息内容不存在缓存,一种是发送人+消息内容存在缓存中,那么这种存在缓存中的消息难道就不是新消息吗?请看下面的图片1,【你好啊】这个消息内容在下图出现了两次,第二次的【你好啊】从缓存中搜索是存在的,但是它又是一条新消息,你不能忽略这条消息,所有面对这种情况你需要采用消息链路的机制,如果链路中的消息中有比对不上的则视为新消息, 参考图2。

图1

图2

技术细节

获取当前微信号下面所有打开的独立微信窗体。

	/// <summary>
	/// 获取正在打开的聊天窗口(不包含主窗体)
	/// </summary>
	/// <returns></returns>
    public List<AutomationElement> GetWeChatWindows()
    {
		List<AutomationElement> weChat = new List<AutomationElement>();
		var source = WindowApi.GetAllDesktopWindows().ToList();
		foreach (var item in source)
		{
			if (item.szClassName == "ChatWid")
			{
				var cc = automation.FromHandle(item.hWnd);
				if (cc.ControlType == FlaUI.Core.Definitions.ControlType.Window)
					weChat.Add(cc  );
			}
		}
 
		return weChat;
    }

DTO对象定义

   /// <summary>
   /// 微信聊天消息模型
   /// </summary>
   public class WeChatContractMessageDto
   {
       public WeChatContractMessageDto() {
           MessageType = 0;
       }
       /// <summary>
       /// 消息内容
       /// </summary>
       public string Message { get; set; }
       /// <summary>
       /// 0消息 1图片 2动画表情
       /// </summary>
       public int MessageType { get; set; }

       /// <summary>
       /// 图片二进制对象
       /// </summary>
       public Bitmap ImageBitMap { get; set; }

       /// <summary>
       /// 用户头像(准确度不行)
       /// </summary>
       public Bitmap UserHeader { get; set; }

       /// <summary>
       /// 时间
       /// </summary>
       public DateTime Time { get; set; }

       /// <summary>
       /// 1发送方 2接收方 3时间 
       /// </summary>
       public int UserType { get; set; }

       /// <summary>
       /// 微信聊天用户
       /// </summary>
       public string UserName { get; set; }

       /// <summary>
       /// 消息处理状态
       /// </summary>
       public MessageExecuteStatus ExecuteStatus { get; set; }

       /// <summary>
       /// 上一条消息(链路结构)
       /// </summary>
       public WeChatContractMessageDto PreMessage { get; set; }

       public static bool Equals(WeChatContractMessageDto t1, WeChatContractMessageDto t2)
       {
           if (t1 == null && t2 == null) return true;

           if (t1 == null || t2 == null) return false;

           if (t1 != null && t2 != null)
           {

               return t1.UserName == t2.UserName && t1.Message == t2.Message;

           }
           return false;
       }
   }

构建消息的方法

      private WeChatContractMessageDto BuildChatMessage(FlaUI.Core.AutomationElements.AutomationElement item)
      {
          var my = item.FindFirstByXPath("/Pane[2]/Pane/Pane/Text");
          var to = my == null ? item.FindFirstByXPath("/Pane[1]/Pane/Pane/Text") : null;

          if (my != null)
          {
              var button = item.FindFirstByXPath("/Pane[1]/Button");
              var map = button.Capture();
              return new WeChatContractMessageDto { UserHeader = map, Message = my.Name.ToString(), UserName = button.Name.ToString(), UserType = 1 };

          }
          else if (to != null)
          {
              var button = item.FindFirstByXPath("/Pane[1]/Button");
              var map = button.Capture();
              return new WeChatContractMessageDto {  UserHeader= map, Message = to.Name.ToString(), UserName = button.Name.ToString(), UserType = 2 };
          }
        
          return null;
      }

第一次初始化消息缓存

      if (dto.Init == false)
      {
          #region 初始化第一次打开的聊天窗口记录
          foreach (var chatItem in chatElementSource)
          {
              if (chatItem.ControlType != FlaUI.Core.Definitions.ControlType.ListItem)
                  continue;

              WeChatContractMessageDto currentMessage = null;

              if (chatItem.Name != "[图片]" && chatItem.Name != "[动画表情]")
                  currentMessage = BuildChatMessage(chatItem);

              if (currentMessage == null || currentMessage.UserType == 3)
                  continue;
              var preMessage= dto.Messages.Count > 0 ? dto.Messages[dto.Messages.Count - 1] : null;
              //如果是一个人连续发送多条重复记录则忽略本条
              if (WeChatContractMessageDto.Equals(preMessage, currentMessage))
                  continue;
              //如果未初始化则全部添加到原始集合中
              currentMessage.PreMessage = preMessage;//上一条记录 构造消息链
              dto.Messages.Add(currentMessage);
              currentMessage.ExecuteStatus = MessageExecuteStatus.Complete;
              ExecuteWeChatMessageRemind(dto, currentMessage);
              currentMessage.ExecuteStatus = MessageExecuteStatus.Complete;
          }
          dto.Init = true;//设置为初始化完毕
          #endregion
      }

消息链路对比

   #region 构造最近15条有效记录作为比较的依据
 
   List<WeChatContractMessageDto> tempSource = new List<WeChatContractMessageDto>();
   var cnt = chatElementSource.Count-1;
 
   for(int i= cnt ; i >=0; i--) 
   {
       var chatItem = chatElementSource[i];

       WeChatContractMessageDto currentMessage = null;

       if (chatItem.BoundingRectangle.Bottom == 0&&
           chatItem.BoundingRectangle.Height == 0 && chatItem.BoundingRectangle.Width == 0 &&
           chatItem.BoundingRectangle.Top == 0  )
           continue;
				if (chatItem.Name == "以下是新消息")
					continue;
				if (chatItem.Name != "[图片]" && chatItem.Name != "[动画表情]")
       {
           currentMessage = BuildChatMessage(chatItem);
       }
       if (currentMessage == null || currentMessage.UserType == 3)
           continue;
       
       if (tempSource.Count >= 15)
           break;

      var temp=   tempSource.Count>0? tempSource[tempSource.Count-1]:null;

       if (!WeChatContractMessageDto.Equals(currentMessage, temp))
       {
           if (temp != null)
               temp.PreMessage = currentMessage;
           tempSource.Add(currentMessage);
       }

   }
   tempSource.Reverse();
   #endregion

   #region 分析最近15条记录-如果不存在则视为新消息,如果最近3条记录不匹配也视为新消息
    
   foreach (var ansyTemp in tempSource)
   {
       //比较最近三条记录链路 如果相等则代表消息是历史消息
       var hisEqualMsg = dto.Messages.Where(s => s.UserName == ansyTemp.UserName && s.Message == ansyTemp.Message);
       if (hisEqualMsg == null)
       {
           ansyTemp.PreMessage = dto.Messages.Count > 0 ? dto.Messages[dto.Messages.Count - 1] : null;
           dto.Messages.Add(ansyTemp);
           //执行新消息记录
           ExecuteWeChatMessageRemind(dto, ansyTemp);
           Repay(current, ansyTemp);
           ansyTemp.ExecuteStatus = MessageExecuteStatus.Complete;
       }
       else
       {
           var newMessage = true;
           foreach (var hisMsg in hisEqualMsg.ToList())
           {
               //递归比较
               var result=  MsgEqual(ansyTemp, hisMsg); 

               if(result)
                   newMessage=false;
           }
           if (newMessage)
           {
               ansyTemp.PreMessage = dto.Messages.Count > 0 ? dto.Messages[dto.Messages.Count - 1] : null;
               dto.Messages.Add(ansyTemp);
               //执行新消息记录
               ExecuteWeChatMessageRemind(dto, ansyTemp);
               Repay(current, ansyTemp);
               ansyTemp.ExecuteStatus = MessageExecuteStatus.Complete;
           }

       }
   }
   #endregion

因为文章可能无法满足读者要求,如需源码和支持请联系本人 QQ 978124155 

(23)实时采集微信消息(基于主窗体)--微信UI自动化(.Net+C#)icon-default.png?t=N7T8https://blog.csdn.net/lichao19897314/article/details/138617138

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1657230.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux网络部分——SSH远程访问及控制

目录 一、配置OpenSSH服务端【☆】 1. ssh服务端配置 vim /etc/ssh/sshd_config 2.客户端连接验证 二、使用SSH客户端程序 1.ssh 远程登录 2.scp 远程复制 3.sftp 安全FTP 三、SSH的验证方式 1. 密钥对验证【☆】 2. 如何实现免密登录 【拓展】免交互创建密钥对和免交…

Docker 安装的MySQL迁移数据库

1. 导出数据库 docker ps :查看数据库对应的 CONTAINER ID docker exec -it id /bin/bash : 进入到mysql的docker实例中 cd /usr/bin : 进入到bin目录 mysqldump -u root -p123456 study > /root/study_backup0509.sql :使用mysqldump备份库&#xff0c;注意密码与-p之间…

Java基于B/S医院绩效考核管理平台系统源码java+springboot+MySQL医院智慧绩效管理系统源码

Java基于B/S医院绩效考核管理平台系统源码javaspringbootMySQL医院智慧绩效管理系统源码 医院绩效考核系统是一个关键的管理工具&#xff0c;旨在评估和优化医院内部各部门、科室和员工的绩效。一个有效的绩效考核系统不仅能帮助医院实现其战略目标&#xff0c;还能提升医疗服…

win10 远程桌面无法连接,解决Win10远程桌面无法连接问题的方法

Windows 10操作系统中&#xff0c;远程桌面是一项非常实用的功能&#xff0c;它允许用户从另一台设备远程访问和控制另一台计算机。然而&#xff0c;有时候用户可能会遇到Win10远程桌面无法连接的问题。本文将探讨可能导致这个问题的原因&#xff0c;并提供相应的解决方案。 确…

为什么需要归档和管理合同

归档和管理合同是非常重要的&#xff0c;主要有以下几个原因&#xff1a; 1. 法律合规性&#xff1a;公司需要遵守法律和监管要求&#xff0c;合同是法律文件&#xff0c;涉及公司的权益和责任。归档和管理合同可以确保公司遵守法律法规&#xff0c;合同的内容和执行过程都符合…

【异常检测】新版异常检测库anomalib的使用

every blog every motto: There’s only one corner of the universe you can be sure of improving, and that’s your own self. https://blog.csdn.net/weixin_39190382?spm1010.2135.3001.5343 0. 前言 异常检测库anomalib的使用 1. 前提 1.1 数据组织形式 说明&#…

Linux|进程地址空间

Linux|内存地址空间 现象基本概念理解如何理解地址空间什么是划分区域&#xff1f;地址空间的理解为什么要有地址空间&#xff1f;如何进一步理解页表和写时拷贝如何理解虚拟地址 Linux真正的进程调度方案 现象 #include <stdio.h> #include <string.h> #include …

AcWing 4993 FEB

4993. FEB - AcWing题库 大佬亲笔 将原串分成三段&#xff1a; FFF|E.....B|FFF 先合并中间段&#xff0c;再合并两边的段 #include <iostream> #include <cstring> #include <algorithm> #include <string> #include <queue&g…

鸿蒙内核源码分析(忍者ninja篇) | 都忍者了能不快吗

ninja | 忍者 ninja是一个叫 Evan Martin的谷歌工程师开源的一个自定义的构建系统,最早是用于 chrome的构建,Martin给它取名 ninja(忍者)的原因是因为它strikes quickly(快速出击).这是忍者的特点,可惜Martin不了解中国文化,不然叫小李飞刀更合适些.究竟有多块呢? 用Martin自…

Fastgpt知识库接入oneapi和自定义大模型

本期教程教大家训练自己的知识库回答chatgpt回答不了的问题 FastGPT 是一个知识库问答系统,可以通过调用大模型和知识库回答特定的问题 可以做成专属 AI 客服集成到现有的APP或者网站内当作智能客服支持网络爬虫学习互联网上的很多知识可以通过flow可视化进行工作流程编排 本期…

Ubuntu20.4中复现Graspness

Ubuntu20.4中复现Graspness 文章目录 Ubuntu20.4中复现Graspness1.安装cuda和cudnn2.安装pytorch3.安装MinkowskiEngine4.编译graspnetAPI5. RuntimeError: "floor" "_vml_cpu" not implemented for IntRefernece &#x1f680;非常重要的环境配置&#x1…

深入理解DNS、ICMP协议与NAT技术:网络世界的三大基石

⭐小白苦学IT的博客主页⭐ ⭐初学者必看&#xff1a;Linux操作系统入门⭐ ⭐代码仓库&#xff1a;Linux代码仓库⭐ ❤关注我一起讨论和学习Linux系统❤ 前言 在网络世界中&#xff0c;数据的传输和交互离不开各种协议和技术的支持。其中&#xff0c;DNS&#xff08;域名系统&am…

1068 万绿丛中一点红

solution 找出满足①像素值唯一&②和相邻像素点色差大于tol 的像素点个数 若唯一&#xff0c;则输出该像素点列、行、像素值&#xff1b;若不唯一&#xff0c;则输出"Not Unique"若无&#xff0c;则输出"Not Exist" 题干和输出中&#xff0c;都是先列…

【ACM出版】第四届控制与智能机器人国际学术会议(ICCIR 2024)

第四届控制与智能机器人国际学术会议&#xff08;ICCIR 2024&#xff09; 2024 4th International Conference on Control and Intelligent Robotics 2024年6月21日-23日 | 中国-广州 官网&#xff1a;www.ic-cir.org EI、Scopus双检索 投稿免费参会、口头汇报及海报展示 四…

【MySQL数据库开发设计规范】之基础规范

欢迎点开这篇文章&#xff0c;自我介绍一下哈&#xff0c;本人笔名姑苏老陈&#xff0c;是一个JAVA开发老兵。 本文收录于 《MySQL数据库开发设计规范》专栏中&#xff0c;该专栏主要分享一些关于MySQL数据库开发设计相关的技术规范文章&#xff0c;定期更新&#xff0c;欢迎关…

Python从0到POC编写--实用小脚本02

爆破脚本&#xff1a; 爆破脚本也是我们经常使用的东西 这里就简单讲讲后台爆破脚本的编写吧 在编写之前&#xff0c;我们先通过访问网站去看看情况 首先我们可以先登录看看 输入账号 admin &#xff0c;密码 12345 后 登录失败&#xff0c;提示 用户名或密码错误 在输入…

水质监测设备预警系统

随着工业化进程的加快和城市化水平的提高&#xff0c;水质安全问题愈发受到社会各界的广泛关注。为了确保水资源的清洁与安全&#xff0c;水质监测设备预警系统成为了不可或缺的利器。在这个背景下&#xff0c;HiWoo Cloud平台凭借其先进的技术和卓越的性能&#xff0c;为水质监…

Centos中将UTC的时区改为CTS时区

date命令可以看到现在的时间以及时区&#xff0c;可以看到现在是UTC时区 而想要更改时区那么就要了解tzselect命令 tzselect 是一个 Linux 命令行工具&#xff0c;用于交互式地帮助用户选择并设置系统的时区。这个程序会通过一系列的问题引导用户&#xff0c;从而确定用户所在的…

小程序如何注销

随着移动互联网的深入发展&#xff0c;管控也越来越严格。现在小程序都要求进行ICP备案&#xff0c;不管是新注册的还是以往注册的。很多商家的小程序本身处于无运营状态&#xff0c;现在要求备案&#xff0c;还不如直接注销。下面&#xff0c;将详细介绍小程序注销的步骤和注意…

WPF容器控件之WrapPanel、布局控件

WrapPanel: 换行panel 子元素进行换行&#xff0c;当子元素的宽度或者高度超出了父元素&#xff0c;才进行换行。高度超出父元素的高度 也会另起一列 属性 Orientation布局方式 实例 <WrapPanel Orientation"Horizontal"><Label>C# 是从 C/C 衍生出来的…