上個月,在一個群裡摸魚划水空度日,看到了一個老哥分享的一個微信自動化的一個類庫,便下載了他的Demo,其本意就是模擬滑鼠來操作UI,實現UI自動化;然後自己在瞎琢磨研究,寫了一個簡單的例子,用來獲取好友列表,獲取聊天列表,以及最後一次接收或者傳送訊息的時間,以及最後一次聊天的內容,還有自動刷朋友圈,獲取朋友圈誰發的,發的什麼文字,以及配的圖片是什麼,什麼時候發的,再就是一個根據獲取的好友列表,來實現給指定好友傳送訊息的功能。
話不多說,咱們開始,首先映入眼簾的是介面,左側是獲取好友列表,然後在右邊就是一個RichTextBox用來根據左側選中的好友列表來傳送訊息,中間是獲取聊天列表,好友名稱,最後一次聊天的內容,以及最後一次聊天的時間,最右邊是獲取朋友圈的內容,刷朋友圈,找到好友發的朋友圈內容,以及附帶的媒體是圖片還是視訊,發朋友圈的時間。
首先需要在Nuget下載兩個包,FlaUI.Core和FlaUI.UIA3,用這兩個包,來實現滑鼠模擬,UI自動化的,接下來,咱們看程式碼。
上面就是一整個介面的截圖,接下來,咱們講講程式碼,在介面被建立的時候,去獲取微信的程序ID,然後,給獲取好友列表,聊天列表,朋友圈的CancelTokenSource賦值以及所關聯的CancelToken,以此來實現中斷取消的功能,同時在上面的List是用來儲存朋友圈資訊的,下面的Content儲存聊天列表的內容的,Key是聊天的使用者暱稱,Value是最後一次的聊天內容,在往下的SendInput是我們用來模擬滑鼠捲動的這樣我們才可以捲動獲取聊天列表,朋友圈內容,好友列表,在下面的FindWindow,GetWindowThreadProcessID是用來根據介面名稱找到對應的程序Id的,因為如果雙擊了朋友圈在彈出介面中,使用Process找不太方便,直接就參照這個來查詢朋友圈的彈出介面。
private List<dynamic> list = new List<dynamic>(); private Dictionary<string, string> Content = new Dictionary<string, string>(); /// <summary> /// 卷軸模擬 /// </summary> /// <param name="nInputs"></param> /// <param name="pInputs"></param> /// <param name="cbSize"></param> /// <returns></returns> [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); //根據名稱獲取表單控制程式碼 [DllImport("user32.dll", EntryPoint = "FindWindow")] private extern static IntPtr FindWindow(string lpClassName, string lpWindowName); //根據控制程式碼獲取程序ID [DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int ID); public Form1() { InitializeComponent(); GetWxHandle(); GetFriendTokenSource = new CancellationTokenSource(); GetFriendCancellationToken = GetFriendTokenSource.Token; ChatListTokenSource = new CancellationTokenSource(); ChatListCancellationToken = ChatListTokenSource.Token; FriendTokenSource = new CancellationTokenSource(); FriendCancellationToken = FriendTokenSource.Token; } private CancellationToken FriendCancellationToken { get; set; } private CancellationTokenSource FriendTokenSource { get; set; } private CancellationToken ChatListCancellationToken { get; set; } private CancellationTokenSource ChatListTokenSource { get; set; } private CancellationToken GetFriendCancellationToken { get; set; } private CancellationTokenSource GetFriendTokenSource { get; set; } private int ProcessId { get; set; } private Window wxWindow { get; set; } private bool IsInit { get; set; } = false; void GetWxHandle() { var process = Process.GetProcessesByName("Wechat").FirstOrDefault(); if (process != null) { ProcessId = process.Id; } }
接下來則是使用獲取的程序ID和Flaui繫結起來,然後獲取到微信的主UI介面,
void InitWechat() { IsInit = true; //根據微信程序ID繫結FLAUI var application = FlaUI.Core.Application.Attach(ProcessId); var automation = new UIA3Automation(); //獲取微信window自動化操作物件 wxWindow = application.GetMainWindow(automation); //喚起微信 }
接下來是獲取好友列表,判斷微信介面是否載入,如果沒有,就呼叫InitWeChat方法,然後在下面判斷主介面不為空,設定介面為活動介面,然後在主介面找到ui控制元件的name是通訊錄的,然後模擬點選,這樣就從聊天介面切換到了通訊錄介面,預設的介面第一條都是新朋友,而我沒有做就是說當前列表在哪裡就從哪裡獲取,雖然你點選了獲取好友列表哪怕沒有在最頂部的新朋友那裡,也依舊是可以模擬捲動來實現獲取好友列表的,然後接下來呼叫FindAllDescendants,獲取主介面的所有子節點,在裡面找到所有父節點不為空並且父節點的Name是聯絡人的節點,之所以是Parent的Name是聯絡人, 是因為我們的好友列表,都是隸屬於聯絡人這個父節點之下的,找到之後呢,我們去遍歷找到的這些聯絡人,名字不為空的過濾掉了,如果存在同名的也可能會過濾掉,沒有做處理,並且,找到的型別必須是ListItem,因為聯絡人本身就是一個列表,他的子類具體的聯絡人肯定就是一個列表專案,就需要這樣過濾,就可以找到好友列表,同時新增到介面上,在最後我們呼叫了Scroll方法,模擬捲動700畫素,這塊可能有的電腦大小不一樣或者是微信最大化,可以根據具體情況設定。最後在寫了取消獲取好友列表的事件。
/// <summary> /// 獲取好友列表 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { if (!IsInit) { InitWechat(); } if (wxWindow != null) { if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null) { //將微信表單設定為預設焦點狀態 wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal); } } wxWindow.FindAllDescendants().Where(s => s.Name == "通訊錄").FirstOrDefault().Click(false); wxWindow.FindAllDescendants().Where(s => s.Name == "新的朋友").FirstOrDefault()?.Click(false); string LastName = string.Empty; var list = new List<AutomationElement>(); var sync = SynchronizationContext.Current; Task.Run(() => { while (true) { if (GetFriendCancellationToken.IsCancellationRequested) { break; } var all = wxWindow.FindAllDescendants(); var allItem = all.Where(s => s.Parent != null && s.Parent.Name == "聯絡人").ToList(); var sss = all.Where(s => s.ControlType == ControlType.Text && !string.IsNullOrWhiteSpace(s.Name)).ToList(); foreach (var item in allItem) { if (item.Name != null && item.ControlType == ControlType.ListItem && !string.IsNullOrWhiteSpace(item.Name) && !listBox1.Items.Contains(item.Name.ToString())) { sync.Post(s => { listBox1.Items.Add(s); }, item.Name.ToString()); } } Scroll(-700); } }, GetFriendCancellationToken); }
private void button4_Click(object sender, EventArgs e)
{
GetFriendTokenSource.Cancel();
}
接下來是獲取朋友圈的事件,找到了程序ID實際上和之前Process獲取的一樣,此處應該可以是不需要呼叫Finwindow也可以,找到之後獲取Window的具體操作物件,即點選朋友圈彈出的朋友圈介面,然後找到第一個專案模擬點選一下,本意在將滑鼠移動過去,不然後面不可以實現自動捲動,在迴圈裡,獲取這個介面的所有子元素,同時找到父類別屬於朋友圈,列表這個的ListItem,找到之後,開始遍歷找到的集合,由於找到的朋友圈的暱稱還有媒體型別,以及時間,還有具體的朋友圈文字內容都包含在了Name裡面,所以就需要我們根據他的格式去進行拆分,獲取對應的時間,暱稱,還有朋友圈內容,媒體型別等,最後新增到DataGridView裡面。
private void button3_Click(object sender, EventArgs e) { if (!IsInit) { InitWechat(); } if (wxWindow != null) { if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null) { //將微信表單設定為預設焦點狀態 wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal); } } var a = Process.GetProcesses().Where(s => s.ProcessName == "朋友圈"); wxWindow.FindAllDescendants().Where(s => s.Name == "朋友圈").FirstOrDefault().Click(false); var handls = FindWindow(null, "朋友圈"); if (handls != IntPtr.Zero) { GetWindowThreadProcessId(handls, out int FridId); var applicationFrid = FlaUI.Core.Application.Attach(FridId); var automationFrid = new UIA3Automation(); //獲取微信window自動化操作物件 var Friend = applicationFrid.GetMainWindow(automationFrid); Friend.FindAllDescendants().FirstOrDefault(s => s.ControlType == ControlType.List).Click(false); var sync = SynchronizationContext.Current; Task.Run(async () => { while (true) { try { if (FriendCancellationToken.IsCancellationRequested) { break; } var allInfo = Friend.FindAllDescendants(); var itema = allInfo.Where(s => s.ControlType == ControlType.ListItem && s.Parent.Name == "朋友圈" && s.Parent.ControlType == ControlType.List); if (itema != null) { foreach (var item in itema) { var ass = item.FindAllDescendants().FirstOrDefault(s => s.ControlType == ControlType.Text); //ass.FocusNative(); //ass.Focus(); var index = item.Name.IndexOf(':'); var name = item.Name.Substring(0, index); var content = item.Name.Substring(index + 1); var split = content.Split("\n"); if (split.Length > 3) { var time = split[split.Length - 2]; var mediaType = split[split.Length - 3]; var FriendContent = split[0..(split.Length - 3)]; var con = string.Join(",", FriendContent); if (list.Any(s => s.Content == con)) { continue; } sync.Post(s => { dataGridView2.Rows.Add(name, s, mediaType, time); dynamic entity = new { Name = name, Content = s, MediaType = mediaType, Time = time }; list.Add(entity); }, con); } } Scroll(-500); await Task.Delay(100); } } catch (Exception ex) { continue; } } }); } } private void button6_Click(object sender, EventArgs e) { FriendTokenSource.Cancel(); }
然後接下來就是獲取聊天列表,以及給指定好友傳送訊息的功能了,在下面這段程式碼裡,上面都是判斷有沒有設定為活動介面,然後找到所有的子元素,找到屬於對談的子節點,並且子節點是ListItem,過濾掉摺疊的群聊,如果點選倒摺疊的群聊,就得在模擬點選回退回來,這裡我沒有寫具體的程式碼,不過也很簡單,找到對應的聊天列表之後,開始遍歷每一個聊天物件,根據Xpath,我們找到了符合條件的Text,這Text包括我們的時間,內容,還有暱稱,關於Xpath,不熟悉結構的可以看看我們的C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64路徑下,可能有的Bin裡面的版本不是我這個版本,你們可以根據自己的系統版本去找對應64或者32位元裡面的有一個程式叫做inspect.exe這塊可以看需要操作介面的UI結構,然後根據這個去寫Xpath就行,在獲取倒這些內容之後,我們新增到介面上面去,然後模擬捲動去獲取聊天列表,
private void button2_Click(object sender, EventArgs e) { if (!IsInit) { InitWechat(); } if (wxWindow != null) { if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null) { //將微信表單設定為預設焦點狀態 wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal); } } wxWindow.FindAllDescendants().Where(s => s.Name == "聊天").FirstOrDefault().Click(false); wxWindow.FindAllDescendants().Where(s => s.Name == "媽媽").FirstOrDefault().Click(false); var sync = SynchronizationContext.Current; Task.Run(async () => { object obj; while (true) { var all = wxWindow.FindAllDescendants(); try { if (ChatListCancellationToken.IsCancellationRequested) { break; } var allItem = all.Where(s => s.ControlType == ControlType.ListItem && !string.IsNullOrEmpty(s.Name) && s.Parent.Name == "對談" && s.Name != "摺疊的群聊"); foreach (var item in allItem) { var allText = item.FindAllByXPath("//*/Text"); if (allText != null && allText.Length >= 3) { var name = allText[0].Name; var time = allText[1].Name; var content = allText[2].Name; if (Content.ContainsKey(name)) { var val = Content[name]; if (val != content) { Content.Remove(name); Content.Add(name, content); } } else { Content.Add(name, content); } sync.Post(s => { dataGridView1.Rows.Add(item.Name, content, time); }, null); } } Scroll(-700); await Task.Delay(100); } catch (Exception) { continue; } } }, ChatListCancellationToken); } private void button5_Click(object sender, EventArgs e) { ChatListTokenSource.Cancel(); }
接下來有一個傳送的按鈕的事件,主要功能就是根據所選擇的好友列表,去傳送RichTextBox的訊息,在主要程式碼塊中,我們是獲取了PC微信的搜尋方塊,然後設定焦點,然後模擬點選,模擬點選之後將我們選擇的好友名稱輸入到搜尋方塊中,等待500毫秒之後,在重新獲取介面的子元素,這樣我們的查詢結果才可以在介面上顯示出來,不等待的話是獲取不到的,找到了之後呢,我們拿到預設的第一個然後模擬點選,就到了聊天介面,獲取到了聊天介面,然後獲取輸入資訊的 文字方塊,也就是程式碼的MsgBox,將他的Text的值設定為我們在Richtextbox輸入的值,然後找到傳送的按鈕,模擬點選傳送,即可實現自動傳送。
private async void button7_Click(object sender, EventArgs e) { var sendMsg=richTextBox1.Text.Trim(); var itemName = listBox1.SelectedItem?.ToString(); if (!IsInit) { InitWechat(); } if (wxWindow != null) { if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null) { //將微信表單設定為預設焦點狀態 wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal); } } var search=wxWindow.FindAllDescendants().FirstOrDefault(s => s.Name == "搜尋"); search.FocusNative(); search.Focus(); search.Click(); await Task.Delay(500); var text=wxWindow.FindAllDescendants().FirstOrDefault(s => s.Name == "搜尋").Parent; if (text!=null) { await Task.Delay(500); var txt=text.FindAllChildren().FirstOrDefault(s=>s.ControlType==ControlType.Text) .AsTextBox(); txt.Text = itemName; await Task.Delay(500); var item = wxWindow.FindAllDescendants().Where(s => s.Name==itemName&&s.ControlType==ControlType.ListItem).ToList(); wxWindow.FocusNative(); if (item!=null&& item.Count>0&&!string.IsNullOrWhiteSpace(sendMsg)) { if (item.Count<=1) { item.FirstOrDefault().Click(); } else { item.FirstOrDefault(s => s.Parent != null && s.Parent.Name.Contains("@str:IDS_FAV_SEARCH_RESULT")).Click(); } var msgBox = wxWindow.FindFirstDescendant(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.Text)).AsTextBox(); msgBox.Text = sendMsg; var button = wxWindow.FindAllDescendants().Where(s => s.Name == "傳送(S)").FirstOrDefault(); button?.Click(); } } }
下圖是我獲取的好友列表,朋友圈列表,以及聊天列表的資訊。
下面是使用c#呼叫win api模擬滑鼠捲動的程式碼。有關SendInput的講解,詳情請看官網https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput。
#region Scroll Event void Scroll(int scroll) { INPUT[] inputs = new INPUT[1]; // 設定滑鼠捲動事件 inputs[0].type = InputType.INPUT_MOUSE; inputs[0].mi.dwFlags = MouseEventFlags.MOUSEEVENTF_WHEEL; inputs[0].mi.mouseData = (uint)scroll; // 傳送輸入事件 SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT))); } public struct INPUT { public InputType type; public MouseInput mi; } // 輸入型別 public enum InputType : uint { INPUT_MOUSE = 0x0000, INPUT_KEYBOARD = 0x0001, INPUT_HARDWARE = 0x0002 } // 滑鼠輸入結構體 public struct MouseInput { public int dx; public int dy; public uint mouseData; public MouseEventFlags dwFlags; public uint time; public IntPtr dwExtraInfo; } // 滑鼠事件標誌位 [Flags] public enum MouseEventFlags : uint { MOUSEEVENTF_MOVE = 0x0001, MOUSEEVENTF_LEFTDOWN = 0x0002, MOUSEEVENTF_LEFTUP = 0x0004, MOUSEEVENTF_RIGHTDOWN = 0x0008, MOUSEEVENTF_RIGHTUP = 0x0010, MOUSEEVENTF_MIDDLEDOWN = 0x0020, MOUSEEVENTF_MIDDLEUP = 0x0040, MOUSEEVENTF_XDOWN = 0x0080, MOUSEEVENTF_XUP = 0x0100, MOUSEEVENTF_WHEEL = 0x0800, MOUSEEVENTF_HWHEEL = 0x1000, MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000, MOUSEEVENTF_VIRTUALDESK = 0x4000, MOUSEEVENTF_ABSOLUTE = 0x8000 } const int MOUSEEVENTF_WHEEL = 0x800; #endregion
使用這個類庫當然可以實現一個自動回覆機器人,以及訊息朋友圈某人更新訂閱,訊息訂閱等等,公眾號啊 一些資訊的收錄。
以上是使用FlaUi模擬微信自動化的一個簡單Demo,記得好像也可以模擬QQ的,之前簡單的嘗試了一下,可以獲取一些東西,程式碼地址:https://gitee.com/cxd199645/we-chat-auto.git。歡迎各位大佬討論