吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1648|回复: 19
收起左侧

[其他原创] 一种采用Wechaty的VX机器人

[复制链接]
houshanwen 发表于 2026-5-15 16:36
本帖最后由 houshanwen 于 2026-5-15 16:37 编辑

最近有个项目是获取特定微信群名特定关键词聊天记录,然后通过RPA机器人自动进行相关操作,本身想着微信的QClaw,但是好像不太符合项目需求,然后又另外寻求了俩种工具,一种是wxHelper,但是搜着说封号的几率很大,另外一种是Wechaty,用deepseek对比了一下两种方案


对比维度
Wechaty
wxhelper
核心原理基于协议,模拟合法客户端操作基于Hook,直接在内存级别注入DLL操作微信
数据获取能力实时监听群聊消息并持久化
导出聊天记录较间接,需自行实现
通过Hook实时获取群聊消息
可获取消息附件
可解密历史数据库,获取完整记录
可导出群聊和成员列表
可获取历史聊天记录(通过本地数据库)
技术要求低,JavaScript/TypeScript编程基础即可高,需了解Windows逆向、DLL注入,并能处理版本兼容
环境依赖跨平台,支持Node.js环境仅限Windows,且须配合微信特定旧版本
安全与稳定性中风险,生态成熟,但协议可能变动高危,Hook操作极易触发风控,账号被封可能性极大。稳定性完全依赖特定微信版本
社区与支持非常活跃,文档和教程众多社区较小,主要由逆向爱好者组成


最终选择了Wechaty方案,然后查询了官网了解了一下,有很多VX机器人相关的工具
整个项目主要还是Vibe Coding

架构

架构

前端页面

前端页面

excel

excel

这是前端调用的页面吧,可以自动配置需要获取信息的群聊名,也可以配置需要抓取的关键词信息
因为现在做的是物流相关的,可能单号和操作不在同一行信息,也做了上下文检索的配置信息
分享出来也只是学习交流,不知道还有没有更好的方案。下面是前端的源码,第一次写这种分享
[HTML] 纯文本查看 复制代码
<!doctype html><html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>采集管理台</title>
    <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
    <link rel="stylesheet" href="/styles.css" />
  </head>
  <body>
    <div id="app" v-cloak>
      <el-container class="app-shell">
        <el-header class="topbar">
          <div>
            <h1>采集管理台</h1>
            <p>配置群聊和匹配词,启动机器人后扫码登录。</p>
          </div>
          <el-tag :type="statusTagType" size="large">{{ statusText }}</el-tag>
        </el-header>

        <el-main class="main-grid">
          <section class="panel hero-panel">
            <div class="hero-copy">
              <span class="eyebrow">机器人控制</span>
              <h2>{{ status.loginUserName || '等待启动' }}</h2>
              <p>已处理 {{ status.totalProcessedCount || 0 }} 条,待写入 {{ status.pendingCount || 0 }} 条。</p>
            </div>
            <div class="actions">
              <el-button type="primary" size="large" :loading="starting" @click="startBot">
                启动并获取二维码
              </el-button>
              <el-button size="large" :loading="stopping" @click="stopBot">停止</el-button>
            </div>
          </section>

          <section class="panel config-panel">
            <div class="section-title">
              <div>
                <h2>匹配配置</h2>
                <p>群聊名称需与微信里的群名一致,匹配词命中后会提取消息里的单号。</p>
              </div>
              <el-button type="primary" :loading="saving" @click="saveConfig">保存配置</el-button>
            </div>

            <div class="form-grid">
              <div class="field-block">
                <label>群聊名称</label>
                <div class="inline-editor">
                  <el-input v-model="newRoom" placeholder="输入群聊名称" @keyup.enter="addRoom" />
                  <el-button @click="addRoom">添加</el-button>
                </div>
                <div class="tag-list">
                  <el-tag
                    v-for="room in config.rooms"
                    :key="room"
                    closable
                    effect="plain"
                    @close="removeRoom(room)"
                  >
                    {{ room }}
                  </el-tag>
                </div>
              </div>

              <div class="field-block">
                <label>匹配词</label>
                <div class="inline-editor">
                  <el-input v-model="newWord" placeholder="例如:退货、拦截" @keyup.enter="addWord" />
                  <el-button @click="addWord">添加</el-button>
                </div>
                <div class="tag-list">
                  <el-tag
                    v-for="word in config.matchWords"
                    :key="word"
                    closable
                    type="success"
                    effect="plain"
                    @close="removeWord(word)"
                  >
                    {{ word }}
                  </el-tag>
                </div>
              </div>
            </div>

            <div class="field-block excel-mode">
              <label>Excel 生成方式</label>
              <el-radio-group v-model="config.excelMode">
                <el-radio-button label="single">单个 Excel,保留群聊名称列</el-radio-button>
                <el-radio-button label="perRoom">每个群聊一个 Excel</el-radio-button>
              </el-radio-group>
            </div>

            <div class="matching-grid">
              <div class="field-block">
                <label>单号正则</label>
                <el-input v-model="config.matching.numberPattern" />
              </div>
              <div class="field-block">
                <label>上下文消息条数</label>
                <el-input-number v-model="config.matching.contextMessageWindow" :min="0" :max="20" />
              </div>
              <div class="field-block">
                <label>上下文有效分钟数</label>
                <el-input-number v-model="config.matching.contextExpireMinutes" :min="1" :max="120" />
              </div>
              <div class="field-block switch-row">
                <el-switch
                  v-model="config.matching.contextSameSenderOnly"
                  active-text="仅匹配同一发送人"
                />
                <el-switch
                  v-model="config.matching.allowDefaultAction"
                  active-text="无动作默认退货"
                />
              </div>
            </div>
          </section>
        </el-main>
      </el-container>

      <el-dialog v-model="qrcodeVisible" title="微信扫码登录" width="360px" align-center>
        <div class="qrcode-box" v-loading="!qrcode.qrcode">
          <img v-if="qrcode.qrcode" :src="qrcodeImageSrc" alt="微信登录二维码" />
          <p v-else>正在等待 Wechaty 返回二维码...</p>
        </div>
      </el-dialog>
    </div>

    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    <script src="https://unpkg.com/element-plus/dist/index.full.min.js"></script>
    <script>
      const { createApp } = Vue;

      createApp({
        data() {
          return {
            // 后端 /api/status 返回的机器人运行状态,用于顶部状态和统计数字。
            status: { state: 'stopped', totalProcessedCount: 0, pendingCount: 0 },
            // 后端 /api/config 返回的业务配置,对应页面上的群聊、匹配词、Excel 和匹配策略控件。
            config: {
              // 需要监听的微信群名称列表。
              rooms: [],
              // 动作词列表,例如退货、拦截、拒收、召回。
              matchWords: [],
              // Excel 输出方式:single 单文件,perRoom 按群拆分。
              excelMode: 'single',
              // 匹配策略配置,和 src/config-store.js 的 DEFAULT_MATCHING_OPTIONS 保持一致。
              matching: {
                // 从聊天文本中提取单号的正则字符串。
                numberPattern: '[A-Z]{1,8}[0-9][A-Z0-9-]{5,}|[0-9A-Z-]{8,}',
                // 动作词和单号分开发送时,向前参考的消息条数。
                contextMessageWindow: 1,
                // 上下文消息在多少分钟内有效。
                contextExpireMinutes: 5,
                // 是否要求上下文消息必须来自同一个发送人。
                contextSameSenderOnly: false,
                // 是否允许没有动作词时默认按退货处理。
                allowDefaultAction: false
              }
            },
            // 最近一次二维码接口返回值,包含原始 qrcode 和状态。
            qrcode: {},
            // 二维码图片缓存刷新 token,变化后 img 会重新请求 /api/qrcode.png。
            qrcodeImageToken: Date.now(),
            // 控制二维码弹窗显隐。
            qrcodeVisible: false,
            // 启动按钮 loading 状态。
            starting: false,
            // 停止按钮 loading 状态。
            stopping: false,
            // 保存配置按钮 loading 状态。
            saving: false,
            // 群聊输入框的临时值。
            newRoom: '',
            // 匹配词输入框的临时值。
            newWord: '',
            // 页面轮询状态和二维码的定时器句柄,组件卸载时需要清理。
            pollTimer: null
          };
        },
        computed: {
          // 将后端状态码转换成页面上可读的中文状态。
          statusText() {
            const map = {
              running: '运行中',
              starting: '启动中',
              stopping: '停止中',
              stopped: '已停止'
            };
            return map[this.status.state] || this.status.state || '未知';
          },
          // 根据运行状态切换标签颜色,让当前状态更容易扫一眼看懂。
          statusTagType() {
            if (this.status.state === 'running') return 'success';
            if (this.status.state === 'starting') return 'warning';
            if (this.status.state === 'stopping') return 'warning';
            return 'info';
          },
          // 二维码图片接口加时间戳,避免浏览器缓存旧二维码。
          qrcodeImageSrc() {
            return `/api/qrcode.png?t=${this.qrcodeImageToken}`;
          }
        },
        mounted() {
          this.loadAll();
          this.pollTimer = setInterval(this.refreshStatusAndQrcode, 2500);
        },
        beforeUnmount() {
          clearInterval(this.pollTimer);
        },
        methods: {
          // 前端请求统一入口:负责发送请求、解析 JSON,并把错误转成异常。
          async request(path, options = {}) {
            const response = await fetch(path, {
              headers: { 'Content-Type': 'application/json' },
              ...options
            });
            const payload = await response.json();
            if (!response.ok) throw new Error(payload.error || '请求失败');
            return payload;
          },
          // 页面加载时同时读取机器人状态和匹配配置,减少首次等待时间。
          async loadAll() {
            const [status, config] = await Promise.all([
              this.request('/api/status'),
              this.request('/api/config')
            ]);
            this.status = status;
            this.config = this.normalizeConfig(config);
            await this.refreshQrcode();
          },
          // 兼容旧配置文件没有 matching 字段的情况,保证页面控件都有默认值。
          normalizeConfig(config) {
            return {
              rooms: config.rooms || [],
              matchWords: config.matchWords || [],
              excelMode: config.excelMode || 'single',
              matching: {
                numberPattern:
                  config.matching?.numberPattern || '[A-Z]{1,8}[0-9][A-Z0-9-]{5,}|[0-9A-Z-]{8,}',
                contextMessageWindow: config.matching?.contextMessageWindow ?? 1,
                contextExpireMinutes: config.matching?.contextExpireMinutes ?? 5,
                contextSameSenderOnly: config.matching?.contextSameSenderOnly === true,
                allowDefaultAction: config.matching?.allowDefaultAction === true
              }
            };
          },
          // 定时轮询状态和二维码,确保扫码状态不用手动刷新页面。
          async refreshStatusAndQrcode() {
            this.status = await this.request('/api/status');
            await this.refreshQrcode();
          },
          // 读取最近一次二维码;如果二维码已经生成,就自动打开登录弹窗。
          async refreshQrcode() {
            this.qrcode = await this.request('/api/qrcode');
            if (this.qrcode.qrcode) {
              this.qrcodeImageToken = Date.now();
              if (this.status.state === 'starting' || this.status.state === 'running') {
                this.qrcodeVisible = true;
              }
            }
          },
          // 启动机器人并立刻打开弹窗,后续通过轮询等待二维码返回。
          async startBot() {
            this.starting = true;
            this.qrcodeVisible = true;
            try {
              this.status = await this.request('/api/start', { method: 'POST' });
              await this.refreshQrcode();
              ElementPlus.ElMessage.success('机器人启动请求已发送');
            } catch (error) {
              ElementPlus.ElMessage.error(error.message);
            } finally {
              this.starting = false;
            }
          },
          // 停止当前 Wechaty 实例,并关闭二维码弹窗。
          async stopBot() {
            this.stopping = true;
            try {
              this.status = await this.request('/api/stop', { method: 'POST' });
              this.qrcodeVisible = false;
              ElementPlus.ElMessage.success('机器人已停止');
            } catch (error) {
              ElementPlus.ElMessage.error(error.message);
            } finally {
              this.stopping = false;
            }
          },
          // 保存群聊、匹配词和 Excel 模式到后端配置文件。
          async saveConfig() {
            this.saving = true;
            try {
              this.config = this.normalizeConfig(await this.request('/api/config', {
                method: 'PUT',
                body: JSON.stringify(this.config)
              }));
              ElementPlus.ElMessage.success('配置已保存');
            } catch (error) {
              ElementPlus.ElMessage.error(error.message);
            } finally {
              this.saving = false;
            }
          },
          // 添加一个群聊名称,重复项会被忽略。
          addRoom() {
            const room = this.newRoom.trim();
            if (room && !this.config.rooms.includes(room)) this.config.rooms.push(room);
            this.newRoom = '';
          },
          // 从当前配置中删除指定群聊。
          removeRoom(room) {
            this.config.rooms = this.config.rooms.filter((item) => item !== room);
          },
          // 添加一个动态匹配词,消息中包含该词时会尝试提取单号。
          addWord() {
            const word = this.newWord.trim();
            if (word && !this.config.matchWords.includes(word)) this.config.matchWords.push(word);
            this.newWord = '';
          },
          // 从当前配置中删除指定匹配词。
          removeWord(word) {
            this.config.matchWords = this.config.matchWords.filter((item) => item !== word);
          }
        }
      }).use(ElementPlus).mount('#app');
    </script>
  </body>
</html>



免费评分

参与人数 2吾爱币 +6 热心值 +1 收起 理由
苏紫方璇 + 5 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
weidechan + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

7900031 发表于 2026-5-16 10:44
gblw 发表于 2026-5-16 09:31
能提供一下hook逆的思路嘛?我公司也需要用到微信的这些功能

直接搜,有人发过教程,不过是旧版的,思路是一样的,只是4.0版本会有一些不一样,发消息是异步的,你找到发的消息后要利用消息结构体中的固定指针继续向上找,看看是哪里使用了这些指针去构建了消息体,找到之后再往下找就能找到推送发消息的方法了,跟它一样构建消息体去发消息就行。
7900031 发表于 2026-5-15 20:42
我公司采用hook方式,协议我没法把控,被封禁了我更新不了,hook我能继续逆向新版本,目前运行快2年了,从3.9版本到现在4.1几
gunxsword 发表于 2026-5-15 17:03
要是分享的是完整的就好了,不太了解这个东西,看着不错!
picoyiyi 发表于 2026-5-15 17:04
用这个就不会被封号吗?
zhu086 发表于 2026-5-15 17:05
这个很厉害了啊,可以应用到很多工作当中,提高工作效率
gp865542 发表于 2026-5-15 17:50
有这个项目吗?  感觉很多场景都可以使用到
lmyx2008 发表于 2026-5-16 05:52
用机器人的思路很大纵化
gblw 发表于 2026-5-16 09:31
7900031 发表于 2026-5-15 20:42
我公司采用hook方式,协议我没法把控,被封禁了我更新不了,hook我能继续逆向新版本,目前运行快2年了,从3 ...

能提供一下hook逆的思路嘛?我公司也需要用到微信的这些功能
woritengxu 发表于 2026-5-16 10:29
求完整版
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-26 11:12

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表