【微信自動化】使用c#實現微信自動化

2023-08-29 12:00:29

引言

    上個月,在一個群裡摸魚划水空度日,看到了一個老哥分享的一個微信自動化的一個類庫,便下載了他的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。歡迎各位大佬討論