UI自动化控制微信发送文件【解决了一个无人回答的难题,Pywin32设置文件到剪切板】「建议收藏」
大家好,又见面了,我是你们的朋友全栈君。
大家好,我是小小明。前面我在《UI自动化控制PC版微信》该系列文中更新了控制微信发送图片的方法。根据部分群友实际工作的需要,本文将分享如何控制微信发送文件。
专栏链接:https://blog.csdn.net/as604049322/category_11396772.html
按照前面的思路,我们发送文本和图片,都是采用复制粘贴操作剪切板的方式,而uiautomation 框架本身也提供了复制文本或图片的方法,却没有提供复制文件到剪切板的API。
翻遍了全网的资料,目前并没有人通过python调用windows api实现复制文件到剪切板,仅有人通过pyqt5实现了复制文件到剪切板。幸好有大佬通过C#和C++实现了该操作,假如我们能够将这些实现代码翻译成Python,或许就能实现python根据文件路径设置文件到剪切板。
即使实在实现不了代码控制复制指定文件到剪切板,那么我们也可以使用自动化的方式,点击发送文件按钮来完成这个功能。由于最终已经实现全网都没人实现的通过pywin32控制剪切板复制文件,所以我不需再演示这种简单的模拟的方法,有兴趣的童鞋也可以根据前文的思路尝试。
为了实现该功能翻遍国内博客,仅发现两篇比较有价值的参考文章:
C++实现:https://blog.csdn.net/u011393161/article/details/79671093#t9 C#实现:https://blog.csdn.net/LE_Kukly/article/details/80656845
各类问答平台有很多人也想通过pywin32实现该功能,可惜无人回答。
接下来我将激活成功教程这个Python领域的世界未解之谜,弥补无人完成这个功能的缺陷。
关于剪贴板的windowsAPI可查看:https://docs.microsoft.com/zh-cn/windows/win32/dataxchg/clipboard
不过由于win32clipboard良好的封装,我们不需要直接调用这么底层的api,代码会简化N倍。
参考stackoverflow一位国外大佬的回答:
https://stackoverflow.com/questions/19670697/how-to-set-win32clipboard-data-on-cf-hdrop-format
win32clipboard支持对STGMEDIUM和DROPFILES结构自动解码,但这位国外大佬也不知道如何构造STGMEDIUM和DROPFILES结构。
不过在我参考了前面的文章和几十次实验后,已成功构造STGMEDIUM和DROPFILES结构,最终 完成了这个功能。
Python实现修改剪切板的内容为指定文件
首先我们先看看如何通过win32clipboard获取当前复制的文件路径列表:
import win32clipboard
win32clipboard.OpenClipboard()
try:
files = win32clipboard.GetClipboardData(win32clipboard.CF_HDROP)
print(files)
finally:
win32clipboard.CloseClipboard()
现在我复制下面三个文件后执行上述代码:
结果如下:
('D:\\tmp\\股票数据.xlsx', 'D:\\tmp\\test.txt', 'D:\\tmp\\WeChat_double.bat')
说明win32clipboard
确实能自动解析STGMEDIUM和DROPFILES结构的数据获取路径。
下面我们开始尝试将指定路径的文件设置到剪切板:
阅读C++实现的代码:
//注意用\0分隔多个路径
TCHAR szFiles[300] = _T("natives_blob.bin\0snapshot_blob.bin\0locales\0");
if (OpenClipboard(hWnd)) {
EmptyClipboard();
// DROPFILES的头文件Shlobj.h
int nSize = sizeof(DROPFILES) + sizeof(szFiles);
HANDLE hData = GlobalAlloc(GHND, nSize);
LPDROPFILES pDropFiles = (LPDROPFILES)GlobalLock(hData);
pDropFiles->pFiles = sizeof(DROPFILES);
#ifdef UNICODE
pDropFiles->fWide = TRUE;
#else
pDropFiles->fWide = FALSE;
#endif
LPBYTE pData = (LPBYTE)pDropFiles + sizeof(DROPFILES);
CopyMemory(pData, szFiles, sizeof(szFiles));
GlobalUnlock(hData);
SetClipboardData(CF_HDROP, hData);
CloseClipboard();
}
可以看到本质上复制文件操作是向剪切版写入了CF_HDROP类型的消息,消息内容为DROPFILES和路径组成的字节,路径由Unicode编码的字节组成。
那么借助win32clipboard,我们只需要组织出这样的字节数据即可。
参考:
DROPFILES的结构:
typedef struct _DROPFILES {
DWORD pFiles;
POINT pt;
BOOL fNC;
BOOL fWide;
} DROPFILES, *LPDROPFILES;
参考:https://docs.microsoft.com/zh-cn/windows/win32/api/shlobj_core/ns-shlobj_core-dropfiles
和:
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT;
参考:https://docs.microsoft.com/en-us/previous-versions/dd162805(v=vs.85)
可以合并成一个结构体:
typedef struct _DROPFILES {
DWORD pFiles;
LONG x;
LONG y;
BOOL fNC;
BOOL fWide;
} DROPFILES, *LPDROPFILES;
再结合下面两行C++代码,一起翻译为了python:
pDropFiles->pFiles = sizeof(DROPFILES);
pDropFiles->fWide = TRUE;
注意:只考虑使用Unicode编码的情况,兼容中文。
from ctypes import *
class DROPFILES(Structure):
_fields_ = [
("pFiles", c_uint),
("x", c_long),
("y", c_long),
("fNC", c_int),
("fWide", c_bool),
]
pDropFiles = DROPFILES()
pDropFiles.pFiles = sizeof(DROPFILES)
pDropFiles.fWide = True
转换为字节:
bytes(pDropFiles)
b'\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00'
对于多个文本路径,我们如何将其转换为需要的Unicode双字节形式呢?
首先,我们必须清楚Unicode编码采用UCS-2格式直接存储,而UTF-16完全对应于UCS-2的,即把UCS-2规定的代码点通过Big Endian或Little Endian方式直接保存下来。UTF-16包括三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(Little Endian)。UTF-16通过在文件开头以名为BOM(Byte Order Mark,U+FEFF)的字符来表明文件是Big Endian还是Little Endian。
Python支持的编码表:https://docs.python.org/zh-cn/3/library/codecs.html?#standard-encodings
我们只需要将python字符串使用UTF-16编码后去掉开头两个字节即可得到对应的Unicode双字节。
先测试复制两个文件:
file = 'D:\\tmp\\test.txt\0D:\\tmp\\股票数据.xlsx\0\0'
data = file.encode("U16")[2:]
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(
win32clipboard.CF_HDROP, bytes(pDropFiles)+data)
finally:
win32clipboard.CloseClipboard()
执行以上代码后,尝试在微信输入框粘贴:
点击发送测试一下:
可以看到正斜杠分隔的文件路径发送出来的文件不正常,我们应该将文件路径统一封装成反斜杠的形式。
最终封装的方法如下:
import win32clipboard
from ctypes import *
class DROPFILES(Structure):
_fields_ = [
("pFiles", c_uint),
("x", c_long),
("y", c_long),
("fNC", c_int),
("fWide", c_bool),
]
pDropFiles = DROPFILES()
pDropFiles.pFiles = sizeof(DROPFILES)
pDropFiles.fWide = True
matedata = bytes(pDropFiles)
def setClipboardFiles(paths):
files = ("\0".join(paths)).replace("/", "\\")
data = files.encode("U16")[2:]+b"\0\0"
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(
win32clipboard.CF_HDROP, matedata+data)
finally:
win32clipboard.CloseClipboard()
def setClipboardFile(file):
setClipboardFiles([file])
def readClipboardFilePaths():
win32clipboard.OpenClipboard()
paths = None
try:
return win32clipboard.GetClipboardData(win32clipboard.CF_HDROP)
finally:
win32clipboard.CloseClipboard()
至此我们就通过pywin32实现了修改剪切板的内容为指定文件。
完善自动发消息功能
下面我们继续完善之前的程序,前面的发送功能支持文本和图片,下面增加支持文件的功能:
import time
import uiautomation as auto
from uiautomation.uiautomation import Bitmap
import win32clipboard
from ctypes import *
class DROPFILES(Structure):
_fields_ = [
("pFiles", c_uint),
("x", c_long),
("y", c_long),
("fNC", c_int),
("fWide", c_bool),
]
pDropFiles = DROPFILES()
pDropFiles.pFiles = sizeof(DROPFILES)
pDropFiles.fWide = True
matedata = bytes(pDropFiles)
def setClipboardFiles(paths):
files = ("\0".join(paths)).replace("/", "\\")
data = files.encode("U16")[2:]+b"\0\0"
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(
win32clipboard.CF_HDROP, matedata+data)
finally:
win32clipboard.CloseClipboard()
def setClipboardFile(file):
setClipboardFiles([file])
def readClipboardFilePaths():
win32clipboard.OpenClipboard()
paths = None
try:
return win32clipboard.GetClipboardData(win32clipboard.CF_HDROP)
finally:
win32clipboard.CloseClipboard()
wechatWindow = auto.WindowControl(
searchDepth=1, Name="微信", ClassName='WeChatMainWndForPC')
wechatWindow.SetActive()
search = wechatWindow.EditControl(Name='搜索')
edit = wechatWindow.EditControl(Name='输入')
messages = wechatWindow.ListControl(Name='消息')
sendButton = wechatWindow.ButtonControl(Name='发送(S)')
def selectSessionFromName(name, wait_time=0.1):
search.Click()
auto.SetClipboardText(name)
edit.SendKeys('{Ctrl}v')
# 等待微信索引搜索跟上
time.sleep(wait_time)
search.SendKeys("{Enter}")
def send_msg(content, msg_type=1):
if msg_type == 1:
auto.SetClipboardText(content)
elif msg_type == 2:
auto.SetClipboardBitmap(Bitmap.FromFile(content))
elif msg_type == 3:
setClipboardFile(content)
edit.SendKeys('{Ctrl}v')
sendButton.Click()
然后开始测试:
name = "小小明"
selectSessionFromName(name)
filename = r"D:\tmp\股票数据.xlsx"
send_msg(filename, msg_type=3)
filename = "D:/ZkInspector.jar"
send_msg(filename, msg_type=3)
可以看到,我们发送文件的功能已经成功实现。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/157336.html原文链接:https://javaforall.cn
相关文章
- 自由意志是否受机器控制?
- shell循环控制sleep、continue、break
- python控制键盘鼠标实现窗口程序自动化2021.10.14
- 平板远程控制电脑_平板远程桌面
- 技术分享 | OceanBase 数据处理之控制文件
- 【Shell】算术运算符、流程控制、函数使用、数组以及加载其它文件的变量
- 【iOS 开发】iOS 开发 简介 (IOS项目文件 | MVC 模式 | 事件响应机制 | Storyboard 控制界面 | 代码控制界面 | Retina 屏幕图片适配)
- Oracle 11g控制文件全部丢失从零开始重建控制文件
- Oracle控制文件重建:解决故障的利器(oracle控制文件重建)
- Linux中控制文件删除权限的技巧(linux文件删除权限)
- 控制从Oracle中控制IP访问的安全措施(oracle访问ip)
- 权限Linux SVN权限控制:管理文件安全(linuxsvn文件)
- 数据库用CMD控制Oracle数据库(cmdoracle)
- Oracle数据库恢复控制文件的技术指南(oracle恢复控制文件)
- Oracle控制文件的备份策略(oracle控制文件备份)
- 权限Linux文件权限控制之执行用户权限(linux文件执行用户)
- Linux进程:理解和控制其运行原理(linux进程原理)
- 权限控制Linux 上的文件执行权限控制(linux文件可执行)
- Efficient Ways to Delete Oracle Control Files A Guide(oracle删除控制文件)
- 远程控制Redis实现远程读写(远程redis读写)
- Oracle事件处理机制以语句控制动态系统(oracle事件语句)
- Oracle中使用换行与回车控制内容显示(oracle中换行 回车)
- Oracle中的ck控制文件的关键保护(oracle中ck是什么)
- 控制 Oracle用三种权限登录控制的安全性分析(oracle三个权限登陆)
- Oracle数据库三个控制文件的作用(oracle三个控制文件)
- 控制利用Redis设置IP访问控制(redis 设置ip访问)