UI自动化控制微信发送文件【解决了一个无人回答的难题,Pywin32设置文件到剪切板】「建议收藏」

UI自动化控制微信发送文件【解决了一个无人回答的难题,Pywin32设置文件到剪切板】「建议收藏」大家好,我是小小明。前面我在《UI自动化控制PC版微信》该系列文中更新了控制微信发送图片的方法。链接:https://blog.csdn.net/as604049322/category_11396772.html根据部分群友实际工作的需要,本文将分享如何控制微信发送文件。按照前面的思路,我们发送文本和图片,都是采用复制粘贴操作剪切板的方式,而uiautomation框架本身也提供了复制文本或图片的方法。但是如果需要复制文件到剪切板,uiautomation并没有提供相应的api。翻遍了全网的资

大家好,又见面了,我是你们的朋友全栈君。

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()

现在我复制下面三个文件后执行上述代码:

image-20211006195806836

结果如下:

('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()

执行以上代码后,尝试在微信输入框粘贴:

image-20211006211839750

点击发送测试一下:

image-20211006211831815

可以看到正斜杠分隔的文件路径发送出来的文件不正常,我们应该将文件路径统一封装成反斜杠的形式。

最终封装的方法如下:

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)

20211006

可以看到,我们发送文件的功能已经成功实现。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/157336.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 个人博客网站搭建[通俗易懂]

    个人博客网站搭建[通俗易懂]个人博客网站搭建VuePress介绍本人的个人博客网站,网站地址,是基于VuePress进行搭建。什么是VuePress根据官网:VuePress由两部分组成:第一部分是一个极简静态网站生成

    2022年7月1日
    20
  • kali虚拟机安装教程(超详细)

    kali虚拟机安装教程(超详细)一、官方下载KaliLinux官方网址:www.Kali.org下载方式分两种:http下载和bt下载(由于是国外网站http方式下载会非常慢),选择对应版本点击即可下载。二、创建新的虚拟1、新建虚拟机(使用自定义方式),点击”下一步“,然后选择自己所安装的虚拟机版本,再点击”下一步“;2、选择”稍后安装操作系统“,点击”下一步“,选择虚拟机可支持的Linux版本,这里使用Debian8.x,然后点击”下一步“;3、输入虚拟机名称并选择安装位置,点击”.

    2022年5月2日
    69
  • python多行注释出错_解决python多行注释引发缩进错误的问题

    python多行注释出错_解决python多行注释引发缩进错误的问题如下所示:m_start=date+’09:00’m_end=date+’13:00’rsv_1={‘act’:’set_resv’,’dev_id’:dev_id,’start’:m_start,’end’:m_end,}”’rsv_2={‘_’:”,’act’:’set_resv’,’dev_id’:dev_id,’start’:’2018-05-2113:00′,’en…

    2022年10月30日
    0
  • Java取余和取模

    Java取余和取模抛开高级语言的实现,取余运算和取模运算本身并不完全一致,区别在于对负整数进行取商时操作不同。虽然这样说,但是取余运算和取模运算的公式都一样。对于x和y两个整数(int),通过以下两个操作获取余数或模数:step1、求商:intz=x/ystep2、求余数或模数:intresult=x-y*z它们的差别在于,如果z的值…

    2022年4月29日
    51
  • goland2022激活码 key is invalid【中文破解版】2022.02.23

    (goland2022激活码 key is invalid)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年4月1日
    1.8K
  • FPN(特征图金字塔网络)理论基础与具体实现

    FPN(特征图金字塔网络)理论基础与具体实现论文地址:FeaturePyramidNetworksforObjectDetection项目地址:FPN_pytorch0x00前言我们在做目标检测和超分辨率重建等问题的时候,我们一般是对同一个尺寸的图片进行网络训练。我们希望我们的网络能够适应更多尺寸的图片,我们传统的做法使用图像金字塔,但是这种做法从侧面提升了计算的复杂度,我们希望可以改善这个问题,所以本文就提出了…

    2022年6月9日
    49

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号