すごい:C#でFlaUIとchatGPTを組み合わせて微信AIの質疑応答を実現

すごい:C#でFlaUIとchatGPTを組み合わせて微信AIの質疑応答を実現

FlaUI自動化+chatGPTに基づいて微信の自動応答を実現

最終更新 2023/08/30 19:57
且听风吟
読了目安 7 分
カテゴリ
.NET
タグ
.NET C# AI

本文はネットユーザーからの投稿です

作者:且听风吟

原文タイトル:FlaUIオートメーション+chatGPTによるWeChat自動返信の実装

原文リンク:https://blog.csdn.net/ftfmatlab/article/details/132589169

1. まず効果画像を見る

WeChat友達リストを取得

自動応答効果

2. 本記事で実装する機能

今回は自動返信を実装する方法を紹介します:

  1. ファイル転送アシスタントを最上部に固定し、マウスクリックをシミュレートします;

  2. セッションリストを常に更新し、新しいメッセージがあれば内容を返信します;

  3. 新しいメッセージを更新したとき、対応する会話相手をマウスクリックで選択し、その際にグループチャットか個人かを判断し、グループチャットの場合は返信しません。

  4. メッセージを取得した後、chatGPTに転送し、同時にchatGPTの返信を待ちます。

  5. chatGPTの内容を取得した後、その内容をWeChatのチャットボックスに入力し、マウスクリックで送信ボタンを押します。

  6. マウスクリックでファイル転送アシスタントを選択し、他のメッセージを待ちます。。。。

3. 機能コード

3.1. チャットメッセージの取得と送信

void GetChatInfo()
{
     if (!IsInit)
     {
          return;
     }
     if (wxWindow == null)
     {
          return;
     }

     //wxWindow.FindAllDescendants(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.Button)).AsParallel().FirstOrDefault(s =>s!=null && s.Name == "聊天")?.Click(false);
     wxWindow.FindFirstDescendant(cf => cf.ByName("聊天"))?.Click(false);
     Task.Run(() =>
     {
          AutomationElement? assFirst = null;
          object obj = new object();
          while (true)
          {
          if (ChatListCancellationToken.IsCancellationRequested)
          {
               break;
          }

          try
          {
               DateTime dateTime3 = DateTime.Now;
               var searchTextBox = wxWindow.FindFirstDescendant(cf => cf.ByName("会话")).AsListBoxItem();

               if (searchTextBox != null)
               {
                    var list = searchTextBox.FindAllChildren();
                    if (assFirst == null)
                    {
                         assFirst = list.AsParallel().FirstOrDefault(t => t != null && "文件传输助手".Equals(t.Name));// 並列検索---ファイル転送アシスタントを最上部に固定する必要あり
                         assFirst?.Click();
                         continue;
                    }

                    Parallel.ForEach(list, item =>
                    {
                         if (item != null && !string.IsNullOrEmpty(item.Name) && !"折叠置顶聊天".Equals(item.Name)
                              && !"腾讯新闻".Equals(item.Name) && !"群聊".Contains(item.Name))
                         {
                              DateTime t1= DateTime.Now;
                              var allText = item.FindAllByXPath(".//Text");//   要素のローカル検索: .//Text;   グローバル検索: //*/Text
                              DateTime t2 = DateTime.Now;
                              Trace.WriteLine($"allText所用時間:{(t2 - t1).TotalMilliseconds}ms");
                              // まだ返信していないメッセージリストを返信
                              if (allText != null && allText.Length >= 4)
                              {
                              if (int.TryParse(allText[3].Name, out var count) && count > 0)
                              {
                                   lock (obj)
                                   {
                                        var name = allText[0].Name;
                                        var time = allText[1].Name;
                                        var content = allText[2].Name;
                                        if (wxWindow.Patterns.Window.PatternOrDefault != null)
                                        {
                                             //WeChatウィンドウをデフォルトのフォーカス状態に設定
                                             wxWindow.Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal);
                                        }

                                        item.Click();
                                        DateTime t7= DateTime.Now;
                                        var itemFirst = wxWindow.FindAllDescendants(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.Text)).AsParallel()
                                        .FirstOrDefault(t =>t!=null && t.Parent.ControlType == ControlType.Pane && !t.IsOffscreen && t.Name.Trim().IsSpecificNumbers());
                                        DateTime t8= DateTime.Now;
                                        Trace.WriteLine($"itemFirst:{(t8 - t7).TotalMilliseconds}ms");
                                        // グループチャットかどうかを判断
                                        if (itemFirst == null)
                                        {
                                             AutoGetMesg(content);
                                        }
                                        assFirst?.Click();
                                   }
                              }
                              }
                         }
                    });

                    DateTime dateTime4 = DateTime.Now;
                    Trace.WriteLine($"タスク888の所要時間:{(dateTime4 - dateTime3).TotalMilliseconds}ms");
               }
               else
               {
                    Thread.Sleep(10);
                    continue;
               }

               //ScrollEvent(-700);

          }
          catch (Exception ex)
          {
               continue;
          }
          finally
          {
               //await Task.Delay(1);
          }
          }
     }, ChatListCancellationToken);
}

3.2. 自動応答

IChat _chat;
private void AutoGetMesg(string txt)
{
     if (_chat == null)
     {
          _chat = new ChatAchieve();
          _chat.RequestContent = GetMessage;
     }
     Trace.WriteLine($"送信メッセージ:{txt}");
     _chat.RequestGPT(txt);
}

private FlaUI.Core.AutomationElements.TextBox _mesText;
public FlaUI.Core.AutomationElements.TextBox MesText
{
     get
     {
          if (_mesText == null)
          _mesText = wxWindow.FindFirstDescendant(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.Text)).AsTextBox();

          return _mesText;
     }
}
private AutomationElement? _btnSend;
public AutomationElement? btnSend
{
     get
     {
          if (_btnSend == null)
          {
          _btnSend = wxWindow.FindFirstDescendant(cf => cf.ByName("sendBtn"));
          //_btnSend = wxWindow.FindAllDescendants(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.Button)).FirstOrDefault(s => s.Name == "发送(S)");
          }

          return _btnSend;
     }
}
const int _offSize = 300;
private void GetMessage(string mes)
{
     SendMes(mes);
     Trace.WriteLine($"返信:{mes}");
}
private void SendMes(string mes)
{
     if (wxWindow.Patterns.Window.PatternOrDefault != null)
     {
          //WeChatウィンドウをデフォルトのフォーカス状態に設定
          wxWindow.Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal);
     }
     int tempLen = 0;
     string txt = string.Empty;
     try
     {
          if (!string.IsNullOrWhiteSpace(mes))
          {
          string[] lines = mes.Split(Environment.NewLine);

          foreach (string line in lines)
          {
               tempLen += line.Length;
               txt += line + Environment.NewLine;
               if (tempLen > _offSize)
               {
                    MesText.Text = txt;
                    btnSend?.Click();
                    tempLen = 0;
                    txt = string.Empty;
               }
          }
          if (!string.IsNullOrWhiteSpace(txt))
          {
               MesText.Text = txt;
               Thread.Sleep(3);
               btnSend?.Click();
          }
          }
     }
     catch (Exception ex)
     {
          Trace.WriteLine(ex.Message);
          MesText.Text = txt;
          btnSend?.Click();
     }
}

3.3. その他のコード

/// <summary>
/// 起動
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStart_Click(object sender, EventArgs e)
{
     InitWechat();
}

private void FrmMain_Load(object sender, EventArgs e)
{

}

private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
     this.Dispose();
     GC.Collect();
}
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; }
/// <summary>
/// WeChatのプロセスID
/// </summary>
private int ProcessId { get; set; }
/// <summary>
/// WeChatウィンドウ
/// </summary>
private Window wxWindow { get; set; }
private bool IsInit { get; set; }
/// <summary>
/// 取得
/// </summary>
void GetWxHandle()
{
     var process = Process.GetProcessesByName("Wechat").FirstOrDefault();
     if (process != null)
     {
          ProcessId = process.Id;
     }
}
/// <summary>
/// WeChatを読み込み
/// </summary>
void InitWechat()
{
     IsInit = true;
     GetWxHandle();
     GetFriendTokenSource = new CancellationTokenSource();
     GetFriendCancellationToken = GetFriendTokenSource.Token;
     ChatListTokenSource = new CancellationTokenSource();
     ChatListCancellationToken = ChatListTokenSource.Token;
     FriendTokenSource = new CancellationTokenSource();
     FriendCancellationToken = FriendTokenSource.Token;
     //WeChatプロセスIDに基づいてFLAUIをバインド
     try
     {
          var application = FlaUI.Core.Application.Attach(ProcessId);
          var automation = new UIA3Automation();
          //WeChatウィンドウの自動操作オブジェクトを取得
          wxWindow = application.GetMainWindow(automation);
     }
     catch (Exception ex)
     {
          if (MessageBox.Show(ex.Message, "異常", MessageBoxButtons.OK, MessageBoxIcon.Error) == DialogResult.OK)
          this.Close();
     }


     // 友達を読み込み
     IsListenCronyList = true;
     // チャット情報を読み込み
     GetChatInfo();
}
/// <summary>
/// 友達リストを取得
/// </summary>
void GetFriends()
{
     if (!IsInit)
     {
          return;
     }
     if (wxWindow == null)
     {
          return;
     }

     if (wxWindow.Patterns.Window.PatternOrDefault != null)
     {
          //WeChatウィンドウをデフォルトのフォーカス状態に設定
          wxWindow.Patterns.Window.Pattern.SetWindowVisualState(FlaUI.Core.Definitions.WindowVisualState.Normal);
     }

     wxWindow.FindAllDescendants(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.Button)).AsParallel()
          .FirstOrDefault(item => item != null && item.Name == "通讯录")?.Click(false);

     string lastName = string.Empty;
     var list = new List<AutomationElement>();
     var sync = SynchronizationContext.Current;
     Task.Run(async () =>
     {
          while (true)
          {
          if (GetFriendCancellationToken.IsCancellationRequested)
               break;
          var all = wxWindow.FindAllDescendants(x => x.ByControlType(FlaUI.Core.Definitions.ControlType.ListItem));
          var allItem = all.AsParallel().Where(s => s != null && s.Parent != null && "联系人".Equals(s.Parent?.Name)).ToList();
          foreach (var item in allItem)
          {
               if (!string.IsNullOrWhiteSpace(item.Name) && !listBox1.Items.Contains(item.Name.ToString()))
               {
                    sync.Post(s =>
                    {
                         listBox1.Items.Add(s);
                    }, item.Name.ToString());
               }

          }
          //ScrollEvent(-700);

          await Task.Delay(1);
          }
     }, GetFriendCancellationToken);
}
/// <summary>
/// 友達リストを監視
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnListenCronyList_Click(object sender, EventArgs e)
{
     IsListenCronyList = !IsListenCronyList;
}
private bool _isListenCronyList = false;
public bool IsListenCronyList
{
     set
     {
          if (_isListenCronyList == value)
          return;

          _isListenCronyList = value;
          string txt = string.Empty;
          if (value)
          {
          txt = "友達リストの監視を停止";
          GetFriends();
          }
          else
          {
          txt = "友達リストの監視を開始";
          GetFriendTokenSource.Cancel();
          }
          btnListenCronyList.ExecBeginInvoke(() =>
          {
          btnListenCronyList.Text = txt;
          });
     }
     get => this._isListenCronyList;
}

3.4. 拡張メソッド

internal static class SystemEx
{
     /// <summary>
     /// スレッドを越えてコントロールを操作
     /// </summary>
     /// <param name="con"></param>
     /// <param name="action"></param>
     public static void ExecBeginInvoke(this Control con, Action action)
     {
          if (action == null) return;
          if (con.InvokeRequired)
          {
               con.BeginInvoke(new Action(action));
          }
          else
          {
               action();
          }
     }
     public static void ExecInvoke(this Control con, Action action)
     {
          if (action == null) return;
          if (con.InvokeRequired)
          {
               con.Invoke(new Action(action));
          }
          else
          {
               action();
          }
     }
     const string PARRERN = @"^\(\d+\)$";
     public static bool IsSpecificNumbers(this string txt)
     {
          return Regex.IsMatch(txt, PARRERN);
     }
}

注:ChatAchieveはIChatの実装であり、chatGPTの実装です

public interface IChat
{
     Action<string> RequestContent { get; set; }
     void RequestGPT(string content);
}

作者は面倒くさがりで、コードをリポジトリに置きたがらないため、ソースコードを直接サイト管理者に渡しました。必要な方はこちらからダウンロードしてください:https://img1.dotnet9.com/2023/08/WeChat.Automation.zip

技術交流のためのQQグループ:771992300

またはサイト管理者の微信(codewf、備考「加群」)をスキャンして微信技術交流グループにご参加ください:

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/01/05

すべての .NET 開発者に贈る 2025 年度総括

今年、皆さんは「申し訳ありません、C# は第一線から脱落しました」といった類の記事を何度も目にしたことでしょう。.NET エコシステムは実際どうなのか、本記事では 2025 年に .NET 開発者が最も注目すべき技術トレンドと重要イベントを体系的に整理します。AI の発展、.NET の進化、そして両者の融合に関する最新動向とトレンドを網羅し、皆さんが自らの立ち位置を正確に把握し、将来の挑戦と機会に備える手助けをします。

続きを読む
同じカテゴリ / 同じタグ 2023/11/17

.NET8 正式リリース、C#12 の新機能

8 では、人工知能、クラウドネイティブ、パフォーマンス、ネイティブ AOT など、多くの面での強化がもたらされましたが、私はやはり C# 言語とフレームワークレベルの変更に最も注目しています。以下では、C# 12 とフレームワークにおける実用的な新機能を紹介します。

続きを読む