This article was contributed by a netizen.
Author: Chen Xianda
Original title: [WeChat Automation] Using C# to Implement WeChat Automation
Original link: https://www.cnblogs.com/1996-Chinese-Chen/p/17663064.html
Introduction
Last month, while idling in a group, I came across a library shared by a fellow for WeChat automation. I downloaded his demo, which essentially simulates mouse operations to achieve UI automation. I then did some tinkering on my own and wrote a simple example to: get a friend list, get the chat list, retrieve the time of the last received or sent message, get the content of the last chat, auto-scroll through Moments, fetch who posted in Moments, what text they posted, what images were attached, and when they posted. Additionally, based on the retrieved friend list, I implemented the ability to send messages to specific friends.
Let me first post a few screenshots to grab your attention:
Get friend list, send messages

Get chat records

Browse Moments

Main Content
Let’s get started without further ado. First, take a look at the interface: on the left side, we have the friend list retrieval; in the middle, a RichTextBox is used to send messages based on the selected friend from the left list; the middle section displays the chat list, friend names, the content of the last chat, and the time of the last chat; on the right side, we retrieve Moments content, browse through Moments, find posts from friends, the attached media (image or video), and the posting time.
First, we need to install two NuGet packages: FlaUI.Core and FlaUI.UIA3. These two packages are used to simulate mouse operations and achieve UI automation. Let’s now look at the code.

The above is a screenshot of the entire interface. Next, let’s talk about the code. When the form is created, we obtain the process ID of WeChat, then assign values to the CancelTokenSource and their associated CancellationToken for retrieving the friend list, chat list, and Moments. This allows us to implement cancellation functionality. The List variable is used to store Moments information, while Content is a dictionary storing the chat list content: the key is the user nickname, and the value is the last chat content. SendInput is used to simulate mouse scrolling so we can scroll through the chat list, Moments content, and friend list. FindWindow and GetWindowThreadProcessId are used to find the corresponding process ID based on the window name. This is necessary because if the Moments pop-up window is opened by double-clicking, using Process to find it is inconvenient; instead, we directly invoke this to locate the Moments pop-up window.
private List<dynamic> list = new List<dynamic>();
private Dictionary<string, string> Content = new Dictionary<string, string>();
/// <summary>
/// Simulate scroll bar
/// </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);
// Get window handle by name
[DllImport("user32.dll", EntryPoint = "FindWindow")]
private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
// Get process ID by handle
[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;
}
}
Next, we bind the obtained process ID with FlaUI to get the main UI window of WeChat.
void InitWechat()
{
IsInit = true;
// Bind FlaUI to the WeChat process ID
var application = FlaUI.Core.Application.Attach(ProcessId);
var automation = new UIA3Automation();
// Get the WeChat window automation operation object
wxWindow = application.GetMainWindow(automation);
// Bring WeChat to the foreground
}
Next is the retrieval of the friend list. We check whether the WeChat interface is loaded; if not, we call the InitWeChat method. Then we ensure the main window is not null and set it as the active window. In the main window, we find the UI control with the name "通讯录" (Contacts) and simulate a click to switch from the chat interface to the contacts interface. By default, the first item on the contacts list is "新的朋友" (New Friends). I didn’t implement logic to start from wherever the current list position is. Even if you click "Get Friend List" without being at the top (New Friends), it’s still possible to simulate scrolling to retrieve the friend list. Then we call FindAllDescendants to get all child nodes of the main window, find all nodes where the parent is not null and the parent’s name is "联系人" (Contacts). The reason for checking that the parent’s name is "联系人" is that our friend list is under this parent node. After finding these, we iterate through the matching contacts, filtering out those with empty names. If there are duplicate names, they might also be filtered out (no handling for duplicates). Additionally, the found type must be ListItem because the contacts themselves are a list, and each specific contact child is a list item; this filtering is necessary. Then we add them to the interface. Finally, we call the Scroll method to simulate scrolling by 700 pixels. Depending on the screen size or whether WeChat is maximized, you can adjust this value accordingly. At the end, we add an event to cancel the friend list retrieval.
/// <summary>
/// Get friend list
/// </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)
{
// Set the WeChat window to the default focus state
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();
}

Next is the event for retrieving Moments. We find the process ID—presumably the same as previously obtained via Process. Here, using FindWindow might not be necessary. After finding the window, we get the specific window operation object (the Moments pop-up window that appears when clicking Moments). Then we find the first item and simulate a click, intending to move the mouse over it; otherwise, auto-scrolling won’t work. In the loop, we retrieve all child elements of this window, find those that are ListItem with a parent named "朋友圈" (Moments) and type List. After finding them, we iterate through the collection. The friend’s nickname, media type, time, and specific Moments text content are all contained within the Name property, so we need to split them according to the format to extract the time, nickname, Moments content, media type, etc. Finally, we add them to the DataGridView.
private void button3_Click(object sender, EventArgs e)
{
if (!IsInit)
{
InitWechat();
}
if (wxWindow != null)
{
if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null)
{
// Set the WeChat window to the default focus state
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();
// Get the WeChat window automation operation object
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();
}
Next, we have the functionality to retrieve the chat list and send messages to a specific friend. In the code below, the beginning checks whether the window is set to active. Then we find all child elements, locate those that are child nodes under "会话" (Chats) and are ListItem, filtering out collapsed group chats. If we click on a collapsed group chat, we would need to simulate clicking back; I haven’t written that code here, but it’s straightforward. After finding the corresponding chat list, we iterate through each chat object. Using XPath, we find the matching Text elements, which include the time, content, and nickname. Regarding XPath, if you’re unfamiliar with the UI structure, you can check the directory C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64 (the version in the Bin folder might differ). Depending on your system version, find the appropriate 64-bit or 32-bit directory. There is a tool called inspect.exe that can help you view the UI structure of the target window, and you can write XPath accordingly. After retrieving these contents, we add them to the interface and simulate scrolling to fetch more chat list items.
private void button2_Click(object sender, EventArgs e)
{
if (!IsInit)
{
InitWechat();
}
if (wxWindow != null)
{
if (wxWindow.AsWindow().Patterns.Window.PatternOrDefault != null)
{
// Set the WeChat window to the default focus state
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. Its main function is to send the message from the RichTextBox to the selected friend from the friend list. In the main code block, we get the search box of the PC WeChat client, set focus to it, simulate a click, then input the selected friend’s name into the search box. After waiting 500 milliseconds, we retrieve the child elements of the interface again so that the search results will be displayed on the interface. Without waiting, we cannot retrieve them. After finding the first search result (default), we simulate clicking to enter the chat window. Once in the chat window, we get the input text box (MsgBox in the code), set its Text value to the message entered in the RichTextBox, then find the send button and simulate clicking to send, thereby achieving automated 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)
{
// Set the WeChat window to the default focus state
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 image below shows the friend list, Moments list, and chat list I retrieved.

Below is the code to simulate mouse scrolling using C# to call the Win API. For an explanation of SendInput, please refer to the official documentation: SendInput function (winuser.h).
#region Scroll Event
void Scroll(int scroll)
{
INPUT[] inputs = new INPUT[1];
// Set the mouse scroll event
inputs[0].type = InputType.INPUT_MOUSE;
inputs[0].mi.dwFlags = MouseEventFlags.MOUSEEVENTF_WHEEL;
inputs[0].mi.mouseData = (uint)scroll;
// Send the input event
SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT)));
}
public struct INPUT
{
public InputType type;
public MouseInput mi;
}
// Input types
public enum InputType : uint
{
INPUT_MOUSE = 0x0000,
INPUT_KEYBOARD = 0x0001,
INPUT_HARDWARE = 0x0002
}
// Mouse input structure
public struct MouseInput
{
public int dx;
public int dy;
public uint mouseData;
public MouseEventFlags dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
// Mouse event flags
[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
Conclusion
Using this library, you can certainly implement an auto-reply bot, message subscriptions when a friend updates their Moments, and other features like collecting articles from official accounts, etc.
The above is a simple demo of simulating WeChat automation using FlaUI. I recall it can also simulate QQ; I briefly tried it and was able to retrieve some data. The code repository: https://gitee.com/cxd199645/we-chat-auto.git. Feel free to discuss.