整理 | 小耕家的喵大仙
出品 | CSDN(ID:lichao19897314)
Q Q | 978124155
关于项目背景和微信自动化学习介绍
因为前面写了很多关于微信自动化的文章,网上有一位朋友联系我说他是做广告行业的,有时候除了微信对接客户还需要通过QQ来处理工作,因为广告行业中有大量客户还是使用QQ传送文件给他们做处理,所以需要手动下载并根据QQ用户按天归档,每天大概有上千份文件,所以他说人工接收和归档工作量特别的大,所以联系我想帮他做个自动接收QQ文件并按照联系人和发送文件的日期进行归档的功能。经过一晚上的模式和技术准备,发现是完全可行,所以在微信自动化的基础上进行了改造后实现了QQ文件自动接收的功能,并且经过生产环境的使用和验证,效果良好,大大减少了他们的工作。
如果有兴趣的朋友可以看下我微信自动化的文章!因为底层实现和微信自动化同根同源,感兴趣的点个赞关注支持下原创。
(19)C#添加微信好友(可批量添加)--微信UI自动化(.Net)
-
QQ版本以及软件功能效果
环境要求采用的QQ版本为当前最新版本QQ👇
QQ自动接收文件的效果视频呈现
功能介绍
(1)自主监控QQ主面板的实时消息,分析实时消息中是否包含文件类型的消息,如果分析有新文件消息则自动打开与之对应的联系人聊天窗体。
(2)自动分析打开的聊天窗体中的文件传输面板UI信息,抓取待接收的文件列表信息反馈给自动化软件,并自动触发QQ接收文件按钮的事件。
(3)监控所有处于打开状态的QQ聊天窗口的文件传输面板的实时状态,将文件接收情况反馈给自动化软件。
(4)如果发现某个聊天窗口文件全部接收完毕,并且没有新文件过来则自动关闭聊天窗体,提高自动化响应速度和整体性能。
(5)按照日期和QQ用户进行文件归档操作。
注意事项
在软件执行前,请手动将【合并会话窗口】取消,防止窗口合并,本软件需要每个聊天窗口独立显示。
软件部分截图
-
技术实现思路和示例代码
![](https://i-blog.csdnimg.cn/blog_migrate/0f9fc1ceb8c509cc40d8a268387edd46.png)
以上QQ自动接收文件所实现的功能都是基于UI自动化机制,安全可靠。
- 找到QQ主窗体句柄和QQ进程ID,通过QQ进程ID构建UIA3Automation的自动化测试实例。
- 构建针对QQ自动化测试实例成功后,我们创建一个后台监听任务,监听QQ主窗体的消息面板。监听任务判断消息面板中的UI对象中的内容是否有修改,如果有修改则判断为新消息,并分析新消息是否为文件消息。
- 如果发现该新消息为文件消息,则自动打开本新消息的聊天面板,手动触发QQ接收文件的机制。
- 打开对应的聊天窗口后,我们需要实时监测每个聊天窗口的文件传输面板中的UI信息,获取到所有待接收和正在接收的文件信息,通过接收状态执行对应的操作,并收集接收状态。
- 如果发现某个聊天面板中文件全部接收完毕则强制关闭QQ聊天面板提高整体性能。
- 将成功接收到的文件从QQ默认文件夹中复制到软件的归档目录中,并按照QQ用户名和日期规则进行归档,并上传至FTP服务器保持副本,提高文件可用性。
核心示例代码
加载QQ主窗体并构建自动化测试实例
public bool Load()
{
int weChatID = 0;
IntPtr hwnd = FindWindow(null, "QQ");
if (hwnd != IntPtr.Zero)
{
GetWindowThreadProcessId(hwnd, out weChatID);
}
else
{
return false;
}
application = FlaUI.Core.Application.Attach(weChatID);
automation = new UIA3Automation();
Window = application.GetMainWindow(automation);
var windowSource = application.GetAllTopLevelWindows(automation);
if (Window == null)
return false;
startPanel = string.Format("/Pane[{0}]", Window.FindAllChildren().Length-1);
return true;
}
监控QQ主面板实时文件消息
using FlaUI.QQ.FileReceive;
using FlaUI.WinForm.UIAuto.Model;
using FlaUI.QQ.FileReceive;
using FlaUI.QQ.FileReceive.Log;
using FlaUI.QQ.FileReceive.Model;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FlaUI.WinForm.UIAuto.Business.WX.Task
{
/// <summary>
/// 新文件消息检测
/// </summary>
public class UI_New_Receive_File_Task : UI_QQ_Element_Base_Task
{
private List<QQFileMessageDTO> historyMessageSource { get; set; }
public override void Stop()
{
base.Stop();
}
public override void Start()
{
if (State == TaskState.Stop)
{
State = TaskState.Runing;
}
else
{
return;
}
Thread th = new Thread(new ThreadStart(() =>
{
while (State == TaskState.Runing)
{
try
{
LoopQQMainMessagePanel();
}
catch (Exception ex)
{
SetMessage("错误:" + ex.Message);
}
Thread.Sleep(100);
}
}));
th.Start();
base.Start();
}
private void LoopQQMainMessagePanel()
{
var currentChatList = UI_QQ_Window.Current.Find("/Pane/Pane[4]/Pane[2]/Pane[4]/Pane/Pane/Pane/Pane/Pane/Pane/Pane/Pane/Pane/Pane/Pane/Pane");
if (currentChatList == null || currentChatList.ControlType != FlaUI.Core.Definitions.ControlType.Pane)
{
Thread.Sleep(300);
return;
}
var source = currentChatList.FindAllChildren();
var tempMessageSource = new List<QQFileMessageDTO>();
source.ToList().ForEach(QQMessageItem =>
{
if (QQMessageItem.ControlType == FlaUI.Core.Definitions.ControlType.ListItem)
{
var buttonName = QQMessageItem.Name;
var lastMessage = QQMessageItem.Patterns.Value.PatternOrDefault?.Value;
var messageitem = new QQFileMessageDTO { UserName = buttonName, Message = lastMessage };
tempMessageSource.Add(messageitem);
if (historyMessageSource != null)
{
var cnt = historyMessageSource.Count(s => s.UserName == messageitem.UserName && messageitem.Message == s.Message);
if (cnt == 0)
{
var newMessage = messageitem.Message;
if (IsFileMessage(newMessage))
{
var fileName = GetFileName(newMessage);
SetMessage("来自【" + messageitem.UserName + "】新文件【" + fileName + "】");
messageitem.FileName = fileName;
UI_QQ_Window.Current.Focus();
QQMessageItem.DoubleClick();
}
}
}
}
});
historyMessageSource = tempMessageSource;
}
private bool IsFileMessage(string message) {
if(string.IsNullOrEmpty(message))
return false;
if (message.Contains("分享文件\"") && message.Substring(message.Length - 1, 1).Contains("\""))
{
return true;
}
return false;
}
private string GetFileName(string message) {
int index = message .IndexOf("分享文件\"");
message= message.Substring(index, message.Length -index );
message = message.Replace("分享文件\"", "");
message = message.Substring(0, message.Length - 1);
return message;
}
}
}
从打开的QQ聊天面板获取文件信息和接收情况
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using FlaUI.Core.AutomationElements;
using FlaUI.QQ.FileReceive.Log;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace FlaUI.QQ.FileReceive
{
/// <summary>
/// 接收QQ单窗口面板里面的文件
/// </summary>
public class UI_Window_File_Read_Task : UI_QQ_Element_Base_Task
{
public override void Start()
{
if (State == TaskState.Stop)
{
State = TaskState.Runing;
}
else
{
return;
}
Thread th = new Thread(new ThreadStart(() =>
{
while (State == TaskState.Runing)
{
try
{
FindAllChatWindow();
}
catch (Exception ex)
{
SetMessage("错误:" + ex.Message);
}
Thread.Sleep(100);
}
}));
th.Start();
base.Start();
}
private AutomationElement FindAllChatWindow()
{
var ss = WindowApi.GetAllDesktopWindows();
foreach (var item in ss)
{
if (item.szClassName != "TXGuiFoundation")
continue;
var window = UI_QQ_Window.Current.Automation.FromHandle(item.hWnd);
if (window == null)
continue;
var current = QQUserFileManger.Current.Get(window.Name, GetChatWindowType(window));
if (current.QQChatWindowType == QQChatType.User)
{
ReceiveUserFile(window, current);
}
else
{
ReceiveGroupFile(window, current);
}
}
return null;
}
/// <summary>
/// 接收用户发送的文件
/// </summary>
/// <param name="window"></param>
private void ReceiveUserFile(AutomationElement window, QQUserFileSourceDTO dto)
{
var receiveFilePanel = window.FindFirstByXPath(@"/Pane[3]/Pane/Pane/Pane/Pane[2]/Pane/Pane[1]/Pane/Pane/Pane/Pane/Pane[1]/Pane/Pane");
if (receiveFilePanel == null)
{
//可以执行关闭
dto.FileSource.Completed();
//关闭
CloseChatWindow(window);
return;
}
List<string> executeingFileSource = new List<string>();
foreach (var fileItem in receiveFilePanel.FindAllChildren())
{
if (fileItem.ControlType != Core.Definitions.ControlType.ListItem)
continue;
executeingFileSource.Add(fileItem.Name);
ClickReceiveFile(fileItem, dto, window);
}
//判断是否接收完成
foreach (var file in dto.FileSource)
{
if (executeingFileSource.Count(s => file.FileName == s) <= 0)
{
file.IsReceiveCompleted = true;
}
}
}
private void CloseChatWindow(AutomationElement window) {
var closeButton = window.FindFirstByXPath("/Pane[4]/Button[3]");
if (closeButton != null)
{
closeButton.Click();
}
}
private void ClickReceiveFile(AutomationElement fileItem, QQUserFileSourceDTO dto, AutomationElement window)
{
//如果存在
for (var i = 1; i <= 2; i++)
{
var reviButton = fileItem.FindFirstByXPath("/Pane[2]/Pane/Pane[3]/Pane[2]/Button[" + i + "]");
if (reviButton != null && reviButton.Name == "接收")
{
SystemLog.Info("开始接收新文件:" + fileItem.Name);
window.Focus();
reviButton.Click();
dto.AddFile(fileItem.Name, true);
break;
}
}
}
/// <summary>
/// 群
/// </summary>
/// <param name="window"></param>
/// <param name="dto"></param>
private void ReceiveGroupFile(AutomationElement window, QQUserFileSourceDTO dto)
{
CloseChatWindow(window);
//if (dto.GroupExecute)
// return;
//dto.GroupExecute = true;
//Task.Run(new Action(() =>
//{
// //只能从文件是否完成
// //点击文件
// var fileTab = window.FindFirstByXPath("/Pane[3]/Pane/Pane/Pane/Pane[1]/Pane/Pane/Pane/ToolBar[1]/TabItem[4]");
// if (fileTab == null)
// {
// dto.GroupExecute = false;
// return;
// }
// window.Focus();
// fileTab.Click();
// //刷新
// var moreBtn = window.FindFirstByXPath("/Pane[3]/Pane/Pane/Pane/Pane[1]/Pane/Pane/Pane/ToolBar[1]/TabItem[4]");
// if (moreBtn == null)
// {
// dto.GroupExecute = false;
// return;
// }
// moreBtn.Click();
// var refBtn = window.FindFirstByXPath("/Pane[3]/Pane/Pane/Pane/Pane[1]/Pane/Pane/Pane/ToolBar[1]/TabItem[4]");
// if (refBtn == null)
// {
// dto.GroupExecute = false;
// return;
// }
// window.Focus();
// refBtn.Click();
// dto.GroupExecute = false;
// }));
}
private QQChatType GetChatWindowType(AutomationElement window)
{
var list = window.FindFirstByXPath("/Pane[3]/Pane/Pane/Pane/Pane[2]/Pane/Pane[1]/Pane/Pane/Pane/Pane/Pane/Pane[3]/List");
if (list != null && list.Name == "成员列表" && list.ControlType == Core.Definitions.ControlType.List)
{
return QQChatType.Group;
}
return QQChatType.User;
}
public override void Stop()
{
base.Stop();
}
}
public class QQUserFileManger : List<QQUserFileSourceDTO>
{
public event EventHandler<QQUserFileSourceDTO> DataChange;
private QQUserFileManger() { }
private static QQUserFileManger current = new QQUserFileManger { };
public static QQUserFileManger Current { get { return current; } }
/// <summary>
/// 获取当前聊天对象窗体DTO
/// </summary>
/// <param name="name"></param>
/// <param name="type"></param>
/// <returns></returns>
public QQUserFileSourceDTO Get(string name, QQChatType type)
{
var current = this.FirstOrDefault(item => item.QQChatWindowName == name && item.QQChatWindowType == type);
if (current == null)
{
current = new QQUserFileSourceDTO { QQChatWindowName = name, QQChatWindowType = type };
this.Add(current);
DoDataChange();
}
return current;
}
public void DoDataChange()
{
if (DataChange != null)
{
DataChange(this, null);
}
}
public List<DisplayFileDTO> Show()
{
List<DisplayFileDTO> source = new List<DisplayFileDTO>();
foreach (var item in this)
{
foreach (var item1 in item.FileSource)
{
var log = "等待接收";
if (item1.IsReceive)
{
log = "正在接收";
if (item1.IsReceiveCompleted)
log = "接收完成";
}
source.Add(new DisplayFileDTO
{
Id = item1.Id,
QQChatName = item.QQChatWindowName,
QQChatType = item.QQChatWindowType == QQChatType.User ? "用户文件" : "群文件",
FileName = item1.FileName,
RecevieLog = log
});
}
}
return source;
}
}
public class DisplayFileDTO
{
public Guid Id { get; set; }
public string QQChatName { get; set; }
public string QQChatType { get; set; }
public string FileName { get; set; }
public string RecevieLog { get; set; }
}
/// <summary>
/// 某个用户或者群下的文件集合
/// </summary>
public class QQUserFileSourceDTO
{
public QQUserFileSourceDTO()
{
FileSource = new QQFileSource();
}
/// <summary>
/// QQ聊天窗口名称
/// </summary>
public string QQChatWindowName { get; set; }
public QQChatType QQChatWindowType { get; set; }
/// <summary>
/// 是否在处理中
/// </summary>
public bool GroupExecute { get; set; } = false;
public QQFileSource FileSource { get; set; }
public QQFileDTO AddFile(string file, bool IsReceive = false)
{
var item = new QQFileDTO { FileName = file, Id = Guid.NewGuid(), IsReceive = IsReceive };
FileSource.Add(item);
QQUserFileManger.Current.DoDataChange();
return item;
}
}
public enum QQChatType
{
/// <summary>
/// 用户
/// </summary>
User,
/// <summary>
/// 群
/// </summary>
Group
}
public class QQFileSource : List<QQFileDTO>
{
public void Completed() {
foreach (var item in this)
{
item.IsReceiveCompleted = true;
}
}
}
/// <summary>
/// QQ文件对象
/// </summary>
public class QQFileDTO
{
public Guid Id { get; set; }
/// <summary>
/// 文件名称
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 是否开始接收
/// </summary>
public bool IsReceive { get; set; } = false;
bool isReceiveCompleted = false;
/// <summary>
/// 是否接收完成
/// </summary>
public bool IsReceiveCompleted
{
get { return isReceiveCompleted; }
set
{
isReceiveCompleted = value;
QQUserFileManger.Current.DoDataChange();
}
}
}
}
以上是核心组件及代码思路,因为代码关联类比较多,无法在本篇全部博客呈现!如果需要源码请加本人QQ 978124155
上一篇 C#监听QQ消息自动回复-QQ自动化https://blog.csdn.net/lichao19897314/article/details/136038468