摘要:前面我在自动化控制版微信该系列文中更新了控制微信发送图片的方法。根据部分群友实际工作的需要,本文将分享如何控制微信发送文件。接下来我将破解这个领域的世界未解之谜,弥补无人完成这个功能的缺陷。
大家好,我是小小明。前面我在《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结构,最终 完成了这个功能。
首先我们先看看如何通过win32clipboard获取当前复制的文件路径列表:
import win32clipboardwin32clipboard.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 win32clipboardfrom 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 = Truematedata = 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 timeimport uiautomation as autofrom uiautomation.uiautomation import Bitmapimport win32clipboardfrom 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 = Truematedata = 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://www.ucloud.cn/yun/121857.html
摘要:四层负载均衡不会引起超时。动态修改包的目标地址,并转发数据包使其到达不同的机器上来实现负载均衡的目的,因此节点不会引起超时。七层负载均衡等待上游响应超时。例如使用多线程并发减少远程查询的总体时间如需数据有序,可以使用方案。 B端业务经常要提供下载报表的功能,一般的方法是先查询出所有数据,然后在内存中组装成报表(如XLS/XLSX格式)后统一输出。但是如果生成报表需要查询的数据量很大,远...
摘要:此类文件仅被处理。小白表示没看懂众所周知,是的核心配置文件,在启动时被读取,那么目录的其他文件也是可以被识别,官方还说了除了主之外,还会在每个目录下扫描文件,从被执行的文件所在目录开始一直上升到根目录所指定的。 神秘的.user.ini文件 .user.ini究竟是个神秘东东? 我们看看官方怎么说: http://php.net/manual/zh/conf...自 PHP 5.3....
摘要:所以,我们这个小游戏发布以后,我们就开始花了很多很多时间来打击外挂。二距离判断像素点判断该方法采用自目前最火的跳一跳小游戏辅助程序。 作者:Hahn, 腾讯高级UI工程师商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。 原文链接:http://wetest.qq.com/lab/view/364.html WeTest 导读 张小龙:这个游戏发布以后,其实它的效果有点超...
阅读 3193·2021-10-27 14:20
阅读 2508·2021-10-08 10:05
阅读 1607·2021-09-09 09:33
阅读 2879·2019-08-30 13:16
阅读 1415·2019-08-29 18:34
阅读 1153·2019-08-29 10:58
阅读 1202·2019-08-28 18:22
阅读 1206·2019-08-26 13:33