大家好,我是小小明,前面我在《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://www.uj5u.com/houduan/306562.html
標籤:python
上一篇:全套Java教程_Java基礎入門教程,零基礎小白自學Java必備教程 #011 # 第十一單元 String&ArrayList #
