This article was submitted by netizens
Author: Chen Xianda
Original title: [WeChat Automation] Using c#to achieve WeChat Automation
Original link: www.cnblogs.com/1996-Chinese-Chen/p/17663064.html
introduction
Last month, when I was fishing and paddling in a group, I saw a class library of WeChat Automation shared by an older brother, and downloaded his Demo. The original intention was to simulate a mouse to operate the UI and realize UI automation; Then I was thinking blindly and researching, and wrote a simple example, which was used to obtain a friend list, obtain a chat list, the time of the last message received or sent, and the content of the last chat, and automatically swipe the circle of friends to obtain who posted it in the circle of friends, what text it sent, what pictures were assigned, and when it was posted. There is also a function of sending messages to designated friends based on the obtained friend list.
First post a few pictures to make it attractive:
** Get friends list and send messages **

** Get chat history **

** Brush your circle of friends **

text
Without saying much, let's start. The first thing that comes into view is the interface. On the left side is the Get Friends List, and then on the right is a RichTextBox used to send messages based on the selected friends list on the left side. In the middle is the Get Chat List, Friend Name, The content of the last chat, and the time of the last chat. On the far right side, Get the content of the circle of friends, swipe the circle of friends, find the content of the circle of friends posted by friends, and whether the accompanying media is pictures or videos. Time to post in your circle of friends.
First, we need to download two packages from NuGet, FlaUI.Core and FlaUI.UIA3. Use these two packages to implement mouse simulation and UI automation. Next, let's look at the code.

The above is a screenshot of the entire interface. Next, let's talk about the code. When the interface is created, we can obtain the WeChat process ID, and then assign the value to the CancelTokenSource for obtaining the friend list, chat list, and circle of friends and the associated CancelToken to implement the interrupt cancellation function. At the same time, the List above is used to store circle of friends information, the Content below stores the content of the chat list, and the Key is the user nickname of the chat. Value is the last chat content, and the SendInput below is used by us to simulate mouse scrolling so that we can scroll to get chat lists, circle of friends content, and friends list. In the FindWindow below, GetWindowThreadProcessID is used to find the corresponding process Id based on the interface name. Because if you double click on the circle of friends in the pop-up interface, it is inconvenient to use Process to find the pop-up interface of the circle of friends, so we can directly refer to this to find the pop-up interface of the circle of friends.
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;
}
}
The next step is to use the obtained process ID to bind it to Flaui, and then obtain it to the main UI interface of WeChat.
void InitWechat()
{
IsInit = true;
//根据微信进程ID绑定FLAUI
var application = FlaUI.Core.Application.Attach(ProcessId);
var automation = new UIA3Automation();
//获取微信window自动化操作对象
wxWindow = application.GetMainWindow(automation);
//唤起微信
}
The next step is to obtain the friends list and determine whether the WeChat interface is loaded. If not, call the InitWeChat method, then determine that the main interface is not empty below, set the interface to the active interface, and then find that the name of the ui control in the main interface is the address book, and then simulate clicking, thus switching from the chat interface to the address book interface. The first item in the default interface is new friends, but what I didn't do was to get it from wherever the current list is. Although you click Get Friends List, even if there is no new friend at the top, you can still simulate scrolling to get the friends list. Then call FindAllDescendants to get all the child nodes in the main interface, and find that all the parent nodes are not empty and the Name of the parent node is the contact node. The reason why the Parent's Name is the contact is because our friend lists all belong to the parent node of the contact. After finding it, we went through the contacts we found, and filtered out those with names that were not empty. If there were names with the same name, they might also be filtered out. There was no processing. Moreover, the type found must be ListItem, because the contact itself is a list, and the specific contact of its subclass must be a list item. You need to filter this way to find the friend list and add it to the interface at the same time. In the end, we called the Scroll method. Simulated scrolling is 700 pixels. This may have different computer sizes or WeChat maximization, which can be set according to specific circumstances. Finally, I wrote the event of canceling getting a friend list.
/// <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();
}

The next step is the event to obtain the circle of friends. The process ID is found, which is actually the same as the one obtained by Process before. It should be possible to find the specific operation object of the Window after you find it, that is, click on the circle of friends that pops up in the circle of friends. Interface, and then find the first item to simulate a click. The intention is to move the mouse over, otherwise automatic scrolling cannot be achieved later. In the loop, all sub-elements of this interface are obtained. At the same time, we find the ListItem whose parent class belongs to the circle of friends. After finding it, we start to traverse the found collection. Since the nicknames of the circle of friends found also include the media type, time, and specific text content of the circle of friends. Inside, we need to split it according to its format to obtain the corresponding time, nickname, circle of friends content, media type, etc., and finally add it to 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();
}
Then the next step is to obtain the chat list and send messages to specified friends. In the following code, it is to determine whether the interface is set as an active interface, then find all the sub-elements, find the sub-nodes belonging to the session, and the sub-nodes are ListItem, filter out the folded Group chats. If you click on the folded Group chats, you have to simulate a click to rollback back. I have not written specific code here, but it is very simple. After finding the corresponding chat list, Starting to traverse each chat object, we found a Text that meets the conditions according to the Xpath. This Text includes our time, content, and nicknames. Regarding Xpath, those who are not familiar with the structure can look at our C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64. There may be a version in the Bin that is not my version. You can find a corresponding 64-or 32-bit program called inspect.exe based on your system version. You can look at the UI structure of the interface, and then write Xpath based on this. After obtaining these contents, we add it to the interface, and then simulate scrolling to get the chat list.
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();
}
Next, there is a send button event. The main function is to send RichTextBox messages based on the selected friend list. In the main code block, we obtain the search box of PC WeChat, then set the focus, and then simulate clicking. After simulating clicking, we enter the name of the friend we selected into the search box. After waiting for 500 milliseconds, we can regain the sub-elements of the interface, so that our search results can be displayed on the interface. If we don't wait, we can't get it. After we find it, we get the default first one and simulate clicking. We go to the chat interface, get the chat interface, and then get the text box for inputting information, which is the MsgBox of the code. Set the value of its Text to the value we entered in the Richtextbox, find the send button, simulate clicking send, and realize automatic sending.
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();
}
}
}
The picture below shows the information I obtained on my friend list, friend circle list, and chat list.

下面是使用 c#调用 win api 模拟鼠标滚动的代码。有关 SendInput 的讲解,详情请看官网 SendInput function (winuser.h)。
#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
end
Using this class library, you can of course implement an automatic reply robot, as well as information updates and subscriptions for someone in the message circle of friends, message subscriptions, etc., and the collection of some information on the public account.
The above is a simple Demo of using FlaUi to simulate WeChat automation. I remember that you can also simulate QQ. I tried it briefly before and you can get some things. The code address is https://gitee.com/cxd199645/we-chat-auto.git. Welcome to discuss