当前位置:   article > 正文

【微信自动化】使用c#实现微信自动化

c# 微信ui自动化

本文由网友投稿

作者:陈显达

原文标题:【微信自动化】使用c#实现微信自动化

原文链接:https://www.cnblogs.com/1996-Chinese-Chen/p/17663064.html

引言

上个月,在一个群里摸鱼划水空度日,看到了一个老哥分享的一个微信自动化的一个类库,便下载了他的Demo,其本意就是模拟鼠标来操作UI,实现UI自动化;然后自己在瞎琢磨研究,写了一个简单的例子,用来获取好友列表,获取聊天列表,以及最后一次接收或者发送消息的时间,以及最后一次聊天的内容,还有自动刷朋友圈,获取朋友圈谁发的,发的什么文字,以及配的图片是什么,什么时候发的,再就是一个根据获取的好友列表,来实现给指定好友发送消息的功能。

先贴几张图给点吸引力:

获取好友列表,发送消息

c1759863c1720fe9159c8bd56c1802e1.gif

获取聊天记录

28dfd6117944d35dd6836dee47a3c64f.gif

刷朋友圈

129cfc0c10062a4eb68a68b9a62c9f16.gif

正文

话不多说,咱们开始,首先映入眼帘的是界面,左侧是获取好友列表,然后在右边就是一个RichTextBox用来根据左侧选中的好友列表来发送消息,中间是获取聊天列表,好友名称,最后一次聊天的内容,以及最后一次聊天的时间,最右边是获取朋友圈的内容,刷朋友圈,找到好友发的朋友圈内容,以及附带的媒体是图片还是视频,发朋友圈的时间。

首先需要在Nuget下载两个包,FlaUI.Core和FlaUI.UIA3,用这两个包,来实现鼠标模拟,UI自动化的,接下来,咱们看代码。

7b898c0470048b26cce69de529b2645e.png

上面就是一整个界面的截图,接下来,咱们讲讲代码,在界面被创建的时候,去获取微信的进程ID,然后,给获取好友列表,聊天列表,朋友圈的CancelTokenSource赋值以及所关联的CancelToken,以此来实现中断取消的功能,同时在上面的List是用来存储朋友圈信息的,下面的Content存储聊天列表的内容的,Key是聊天的用户昵称,Value是最后一次的聊天内容,在往下的SendInput是我们用来模拟鼠标滚动的这样我们才可以滚动获取聊天列表,朋友圈内容,好友列表,在下面的FindWindow,GetWindowThreadProcessID是用来根据界面名称找到对应的进程Id的,因为如果双击了朋友圈在弹出界面中,使用Process找不太方便,直接就引用这个来查找朋友圈的弹出界面。

  1. private List<dynamic> list = new List<dynamic>();
  2. private Dictionary<stringstring> Content = new Dictionary<stringstring>();
  3. /// <summary>
  4. /// 滚动条模拟
  5. /// </summary>
  6. /// <param name="nInputs"></param>
  7. /// <param name="pInputs"></param>
  8. /// <param name="cbSize"></param>
  9. /// <returns></returns>
  10. [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  11. public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
  12. //根据名称获取窗体句柄
  13. [DllImport("user32.dll", EntryPoint = "FindWindow")]
  14. private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
  15. //根据句柄获取进程ID
  16. [DllImport("User32.dll", CharSet = CharSet.Auto)]
  17. public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int ID);
  18. public Form1()
  19. {
  20.      InitializeComponent();
  21.      GetWxHandle();
  22.      GetFriendTokenSource = new CancellationTokenSource();
  23.      GetFriendCancellationToken = GetFriendTokenSource.Token;
  24.      ChatListTokenSource = new CancellationTokenSource();
  25.      ChatListCancellationToken = ChatListTokenSource.Token;
  26.      FriendTokenSource = new CancellationTokenSource();
  27.      FriendCancellationToken = FriendTokenSource.Token;
  28. }
  29. private CancellationToken FriendCancellationToken { get; set; }
  30. private CancellationTokenSource FriendTokenSource { get; set; }
  31. private CancellationToken ChatListCancellationToken { get; set; }
  32. private CancellationTokenSource ChatListTokenSource { get; set; }
  33. private CancellationToken GetFriendCancellationToken { get; set; }
  34. private CancellationTokenSource GetFriendTokenSource { get; set; }
  35. private int ProcessId { get; set; }
  36. private Window wxWindow { get; set; }
  37. private bool IsInit { get; set; } = false;
  38. void GetWxHandle()
  39. {
  40.      var process = Process.GetProcessesByName("Wechat").FirstOrDefault();
  41.      if (process != null)
  42.      {
  43.           ProcessId = process.Id;
  44.      }
  45. }

接下来则是使用获取的进程ID和Flaui绑定起来,然后获取到微信的主UI界面,

  1. void InitWechat()
  2. {
  3.      IsInit = true;
  4.      //根据微信进程ID绑定FLAUI
  5.      var application = FlaUI.Core.Application.Attach(ProcessId);
  6.      var automation = new UIA3Automation();
  7.      //获取微信window自动化操作对象
  8.      wxWindow = application.GetMainWindow(automation);
  9.      //唤起微信
  10.      
  11. }

接下来是获取好友列表,判断微信界面是否加载,如果没有,就调用InitWeChat方法,然后在下面判断主界面不为空,设置界面为活动界面,然后在主界面找到ui控件的name是通讯录的,然后模拟点击,这样就从聊天界面切换到了通讯录界面,默认的界面第一条都是新朋友,而我没有做就是说当前列表在哪里就从哪里获取,虽然你点击了获取好友列表哪怕没有在最顶部的新朋友那里,也依旧是可以模拟滚动来实现获取好友列表的,然后接下来调用FindAllDescendants,获取主界面的所有子节点,在里面找到所有父节点不为空并且父节点的Name是联系人的节点,之所以是Parent的Name是联系人, 是因为我们的好友列表,都是隶属于联系人这个父节点之下的,找到之后呢,我们去遍历找到的这些联系人,名字不为空的过滤掉了,如果存在同名的也可能会过滤掉,没有做处理,并且,找到的类型必须是ListItem,因为联系人本身就是一个列表,他的子类具体的联系人肯定就是一个列表项目,就需要这样过滤,就可以找到好友列表,同时添加到界面上,在最后我们调用了Scroll方法,模拟滚动700像素,这块可能有的电脑大小不一样或者是微信最大化,可以根据具体情况设置。最后在写了取消获取好友列表的事件。

  1. /// <summary>
  2. /// 获取好友列表
  3. /// </summary>
  4. /// <param name="sender"></param>
  5. /// <param name="e"></param>
  6. private void button1_Click(object sender, EventArgs e)
  7. {
  8.      if (!IsInit)
  9.      {
  10.           InitWechat();
  11.      }
  12.      if (wxWindow != null)
  13.      {
  14.           if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null)
  15.           {
  16.           //将微信窗体设置为默认焦点状态
  17.           wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal);
  18.           }
  19.      }
  20.      wxWindow.FindAllDescendants().Where(s => s.Name == "通讯录").FirstOrDefault().Click(false);
  21.      wxWindow.FindAllDescendants().Where(s => s.Name == "新的朋友").FirstOrDefault()?.Click(false);
  22.      string LastName = string.Empty;
  23.      var list = new List<AutomationElement>();
  24.      var sync = SynchronizationContext.Current;
  25.      Task.Run(() =>
  26.      {
  27.           while (true)
  28.           {
  29.           if (GetFriendCancellationToken.IsCancellationRequested)
  30.           {
  31.                break;
  32.           }
  33.           var all = wxWindow.FindAllDescendants();
  34.           var allItem = all.Where(s => s.Parent != null && s.Parent.Name == "联系人").ToList();
  35.           var sss = all.Where(s => s.ControlType == ControlType.Text && !string.IsNullOrWhiteSpace(s.Name)).ToList();
  36.           foreach (var item in allItem)
  37.           {
  38.                if (item.Name != null && item.ControlType == ControlType.ListItem && !string.IsNullOrWhiteSpace(item.Name) && !listBox1.Items.Contains(item.Name.ToString()))
  39.                {
  40.                     sync.Post(s =>
  41.                     {
  42.                          listBox1.Items.Add(s);
  43.                     }, item.Name.ToString());
  44.                }
  45.           }
  46.           Scroll(-700);
  47.           }
  48.      }, GetFriendCancellationToken);
  49. }
  50. private void button4_Click(object sender, EventArgs e)
  51. {
  52.      GetFriendTokenSource.Cancel();
  53. }
511b034e548e4b918becfc19e1105fd6.png

接下来是获取朋友圈的事件,找到了进程ID实际上和之前Process获取的一样,此处应该可以是不需要调用Finwindow也可以,找到之后获取Window的具体操作对象,即点击朋友圈弹出的朋友圈界面,然后找到第一个项目模拟点击一下,本意在将鼠标移动过去,不然后面不可以实现自动滚动,在循环里,获取这个界面的所有子元素,同时找到父类属于朋友圈,列表这个的ListItem,找到之后,开始遍历找到的集合,由于找到的朋友圈的昵称还有媒体类型,以及时间,还有具体的朋友圈文字内容都包含在了Name里面,所以就需要我们根据他的格式去进行拆分,获取对应的时间,昵称,还有朋友圈内容,媒体类型等,最后添加到DataGridView里面。

  1. private void button3_Click(object sender, EventArgs e)
  2. {
  3.      if (!IsInit)
  4.      {
  5.           InitWechat();
  6.      }
  7.      if (wxWindow != null)
  8.      {
  9.           if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null)
  10.           {
  11.           //将微信窗体设置为默认焦点状态
  12.           wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal);
  13.           }
  14.      }
  15.      var a = Process.GetProcesses().Where(s => s.ProcessName == "朋友圈");
  16.      wxWindow.FindAllDescendants().Where(s => s.Name == "朋友圈").FirstOrDefault().Click(false);
  17.      var handls = FindWindow(null, "朋友圈");
  18.      if (handls != IntPtr.Zero)
  19.      {
  20.           GetWindowThreadProcessId(handls, out int FridId);
  21.           var applicationFrid = FlaUI.Core.Application.Attach(FridId);
  22.           var automationFrid = new UIA3Automation();
  23.           //获取微信window自动化操作对象
  24.           var Friend = applicationFrid.GetMainWindow(automationFrid);
  25.           Friend.FindAllDescendants().FirstOrDefault(s => s.ControlType == ControlType.List).Click(false);
  26.           var sync = SynchronizationContext.Current;
  27.           Task.Run(async () =>
  28.           {
  29.           while (true)
  30.           {
  31.                try
  32.                {
  33.                     if (FriendCancellationToken.IsCancellationRequested)
  34.                     {
  35.                          break;
  36.                     }
  37.                     var allInfo = Friend.FindAllDescendants();
  38.                     var itema = allInfo.Where(s => s.ControlType == ControlType.ListItem && s.Parent.Name == "朋友圈" && s.Parent.ControlType == ControlType.List);
  39.                     if (itema != null)
  40.                     {
  41.                          foreach (var item in itema)
  42.                          {
  43.                               var ass = item.FindAllDescendants().FirstOrDefault(s => s.ControlType == ControlType.Text);
  44.                               //ass.FocusNative();
  45.                               //ass.Focus();
  46.                               var index = item.Name.IndexOf(':');
  47.                               var name = item.Name.Substring(0, index);
  48.                               var content = item.Name.Substring(index + 1);
  49.                               var split = content.Split("\n");
  50.                               if (split.Length > 3)
  51.                               {
  52.                               var time = split[split.Length - 2];
  53.                               var mediaType = split[split.Length - 3];
  54.                               var FriendContent = split[0..(split.Length - 3)];
  55.                               var con = string.Join(",", FriendContent);
  56.                               if (list.Any(s => s.Content == con))
  57.                               {
  58.                                    continue;
  59.                               }
  60.                               sync.Post(s =>
  61.                               {
  62.                                    dataGridView2.Rows.Add(name, s, mediaType, time);
  63.                                    dynamic entity = new
  64.                                    {
  65.                                         Name = name,
  66.                                         Content = s,
  67.                                         MediaType = mediaType,
  68.                                         Time = time
  69.                                    };
  70.                                    list.Add(entity);
  71.                               }, con);
  72.                               }
  73.                          }
  74.                          Scroll(-500);
  75.                          await Task.Delay(100);
  76.                     }
  77.                }
  78.                catch (Exception ex)
  79.                {
  80.                     continue;
  81.                }
  82.           }
  83.           });
  84.      }
  85. }
  86. private void button6_Click(object sender, EventArgs e)
  87. {
  88.      FriendTokenSource.Cancel();
  89. }

然后接下来就是获取聊天列表,以及给指定好友发送消息的功能了,在下面这段代码里,上面都是判断有没有设置为活动界面,然后找到所有的子元素,找到属于会话的子节点,并且子节点是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就行,在获取倒这些内容之后,我们添加到界面上面去,然后模拟滚动去获取聊天列表,

  1. private void button2_Click(object sender, EventArgs e)
  2. {
  3.      if (!IsInit)
  4.      {
  5.           InitWechat();
  6.      }
  7.      if (wxWindow != null)
  8.      {
  9.           if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null)
  10.           {
  11.           //将微信窗体设置为默认焦点状态
  12.           wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal);
  13.           }
  14.      }
  15.      wxWindow.FindAllDescendants().Where(s => s.Name == "聊天").FirstOrDefault().Click(false);
  16.      wxWindow.FindAllDescendants().Where(s => s.Name == "妈妈").FirstOrDefault().Click(false);
  17.      var sync = SynchronizationContext.Current;
  18.      Task.Run(async () =>
  19.      {
  20.           object obj;
  21.           while (true)
  22.           {
  23.           var all = wxWindow.FindAllDescendants();
  24.           try
  25.           {
  26.                if (ChatListCancellationToken.IsCancellationRequested)
  27.                {
  28.                     break;
  29.                }
  30.                var allItem = all.Where(s => s.ControlType == ControlType.ListItem && !string.IsNullOrEmpty(s.Name) && s.Parent.Name == "会话" && s.Name != "折叠的群聊");
  31.                foreach (var item in allItem)
  32.                {
  33.                     var allText = item.FindAllByXPath("//*/Text");
  34.                     if (allText != null && allText.Length >= 3)
  35.                     {
  36.                          var name = allText[0].Name;
  37.                          var time = allText[1].Name;
  38.                          var content = allText[2].Name;
  39.                          if (Content.ContainsKey(name))
  40.                          {
  41.                               var val = Content[name];
  42.                               if (val != content)
  43.                               {
  44.                               Content.Remove(name);
  45.                               Content.Add(name, content);
  46.                               }
  47.                          }
  48.                          else
  49.                          {
  50.                               Content.Add(name, content);
  51.                          }
  52.                          sync.Post(s =>
  53.                          {
  54.                               dataGridView1.Rows.Add(item.Name, content, time);
  55.                          }, null);
  56.                     }
  57.                }
  58.                Scroll(-700);
  59.                await Task.Delay(100);
  60.           }
  61.           catch (Exception)
  62.           {
  63.                continue;
  64.           }
  65.           }
  66.      }, ChatListCancellationToken);
  67. }
  68. private void button5_Click(object sender, EventArgs e)
  69. {
  70.      ChatListTokenSource.Cancel();
  71. }

接下来有一个发送的按钮的事件,主要功能就是根据所选择的好友列表,去发送RichTextBox的消息,在主要代码块中,我们是获取了PC微信的搜索框,然后设置焦点,然后模拟点击,模拟点击之后将我们选择的好友名称输入到搜索框中,等待500毫秒之后,在重新获取界面的子元素,这样我们的查找结果才可以在界面上显示出来,不等待的话是获取不到的,找到了之后呢,我们拿到默认的第一个然后模拟点击,就到了聊天界面,获取到了聊天界面,然后获取输入信息的 文本框,也就是代码的MsgBox,将他的Text的值设置为我们在Richtextbox输入的值,然后找到发送的按钮,模拟点击发送,即可实现自动发送。

  1. private async void button7_Click(object sender, EventArgs e)
  2. {
  3.      var sendMsg=richTextBox1.Text.Trim();
  4.      var itemName = listBox1.SelectedItem?.ToString();
  5.      if (!IsInit)
  6.      {
  7.           InitWechat();
  8.      }
  9.      if (wxWindow != null)
  10.      {
  11.           if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null)
  12.           {
  13.           //将微信窗体设置为默认焦点状态
  14.           wxWindow.AsWindow().Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal);
  15.           }
  16.      }
  17.      var search=wxWindow.FindAllDescendants().FirstOrDefault(s => s.Name == "搜索");
  18.      search.FocusNative();
  19.      search.Focus();
  20.      search.Click();
  21.      await Task.Delay(500);
  22.      var text=wxWindow.FindAllDescendants().FirstOrDefault(s => s.Name == "搜索").Parent;
  23.      if (text!=null)
  24.      {
  25.           await Task.Delay(500);
  26.           var txt=text.FindAllChildren().FirstOrDefault(s=>s.ControlType==ControlType.Text) .AsTextBox();
  27.           txt.Text = itemName;
  28.           await Task.Delay(500);
  29.           var item = wxWindow.FindAllDescendants().Where(s =>  s.Name==itemName&&s.ControlType==ControlType.ListItem).ToList();
  30.           wxWindow.FocusNative();
  31.           if (item!=null&& item.Count>0&&!string.IsNullOrWhiteSpace(sendMsg))
  32.           {
  33.           if (item.Count<=1)
  34.           {
  35.                item.FirstOrDefault().Click();
  36.           }
  37.           else
  38.           {
  39.                item.FirstOrDefault(s => s.Parent != null && s.Parent.Name.Contains("@str:IDS_FAV_SEARCH_RESULT")).Click();
  40.           }
  41.           var msgBox = wxWindow.FindFirstDescendant(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.Text)).AsTextBox();
  42.           msgBox.Text = sendMsg;
  43.           var button = wxWindow.FindAllDescendants().Where(s => s.Name == "发送(S)").FirstOrDefault();
  44.           button?.Click();
  45.           }
  46.      }
  47. }

下图是我获取的好友列表,朋友圈列表,以及聊天列表的信息。

9b11db78595c1ce138096f9a2b5a157d.png

下面是使用c#调用win api模拟鼠标滚动的代码。有关SendInput的讲解,详情请看官网 SendInput function (winuser.h)[1]

  1. #region Scroll Event
  2. void Scroll(int scroll)
  3. {
  4.      INPUT[] inputs = new INPUT[1];
  5.      // 设置鼠标滚动事件
  6.      inputs[0].type = InputType.INPUT_MOUSE;
  7.      inputs[0].mi.dwFlags = MouseEventFlags.MOUSEEVENTF_WHEEL;
  8.      inputs[0].mi.mouseData = (uint)scroll;
  9.      // 发送输入事件
  10.      SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT)));
  11. }
  12. public struct INPUT
  13. {
  14.      public InputType type;
  15.      public MouseInput mi;
  16. }
  17. // 输入类型
  18. public enum InputType : uint
  19. {
  20.      INPUT_MOUSE = 0x0000,
  21.      INPUT_KEYBOARD = 0x0001,
  22.      INPUT_HARDWARE = 0x0002
  23. }
  24. // 鼠标输入结构体
  25. public struct MouseInput
  26. {
  27.      public int dx;
  28.      public int dy;
  29.      public uint mouseData;
  30.      public MouseEventFlags dwFlags;
  31.      public uint time;
  32.      public IntPtr dwExtraInfo;
  33. }
  34. // 鼠标事件标志位
  35. [Flags]
  36. public enum MouseEventFlags : uint
  37. {
  38.      MOUSEEVENTF_MOVE = 0x0001,
  39.      MOUSEEVENTF_LEFTDOWN = 0x0002,
  40.      MOUSEEVENTF_LEFTUP = 0x0004,
  41.      MOUSEEVENTF_RIGHTDOWN = 0x0008,
  42.      MOUSEEVENTF_RIGHTUP = 0x0010,
  43.      MOUSEEVENTF_MIDDLEDOWN = 0x0020,
  44.      MOUSEEVENTF_MIDDLEUP = 0x0040,
  45.      MOUSEEVENTF_XDOWN = 0x0080,
  46.      MOUSEEVENTF_XUP = 0x0100,
  47.      MOUSEEVENTF_WHEEL = 0x0800,
  48.      MOUSEEVENTF_HWHEEL = 0x1000,
  49.      MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000,
  50.      MOUSEEVENTF_VIRTUALDESK = 0x4000,
  51.      MOUSEEVENTF_ABSOLUTE = 0x8000
  52. }
  53. const int MOUSEEVENTF_WHEEL = 0x800;
  54. #endregion

结尾

使用这个类库当然可以实现一个自动回复机器人,以及消息朋友圈某人更新订阅,消息订阅等等,公众号啊 一些信息的收录。

以上是使用FlaUi模拟微信自动化的一个简单Demo,记得好像也可以模拟QQ的,之前简单的尝试了一下,可以获取一些东西,代码地址:https://gitee.com/cxd199645/we-chat-auto.git。欢迎各位大佬讨论,可添加站长微信(dotnet9com)加你入群与大佬勾兑哦。

参考

[1]

SendInput function (winuser.h): https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/376523
推荐阅读
相关标签
  

闽ICP备14008679号