抖音聊天监控与自动回复

抖音聊天监控与自动回复

前言

抖音PC端没有开放聊天API,想做消息监控或自动回复只能自己动手。传统方案是抓包拦截WebSocket,门槛高且容易被检测。本文介绍一种纯前端注入方案,零依赖,浏览器控制台粘贴即用。

核心思路

  • MutationObserver 监听DOM变化,实时捕获新消息
  • data-index="0" 始终指向最新一条消息,避免全量扫描
  • classList 判断发送/接收方向(messageMessageBoxisFromMe
  • document.execCommand 向Slate富文本编辑器写入内容并模拟发送

踩坑记录

消息重复输出

[class*="message"] 选择器太宽泛,匹配到了消息容器的子元素,一条消息被处理多次。改用精确类名 .messageMessageBoxmessageBox 解决。

昵称互相覆盖

用一个变量 lastNick 记住上一个昵称,自己和对方的昵称会互相覆盖。拆分为 lastSelfNick / lastOtherNick 分别记录。

发送后旧消息重复

DOM重新渲染后元素引用失效,Set 去重失败。改用 data-index="0" 只监听最新消息,配合 lastContent 内容对比去重。

两个data-index="0"

左侧会话列表也有 data-index="0"querySelector 会误匹配。遍历所有 data-index="0",只取包含 .messageMessageBoxmessageBox 的那个。

写入输入框但发送按钮禁用

直接改 textContent 不会触发Slate编辑器内部状态更新。改用 document.execCommand('insertText') + Selection API,Slate能正确识别。

连发相同消息被吞

按内容去重导致相同内容被过滤。改为只对比 lastContent,新消息插入后 data-index="0" 的DOM元素变了,自然不会被误判。

功能

  • 实时监听:文本、图片、大表情、小表情、分享视频
  • 昵称识别:连续消息无头像时自动继承上一条昵称
  • 关键词自动回复:可配置多条规则,3秒冷却防刷屏
  • 重复注入自动清理:断开旧Observer,重新启动

使用方法

  1. 打开抖音PC端,进入聊天页面
  2. F12打开控制台,粘贴代码回车
  3. 修改 autoReply 数组配置关键词和回复
const autoReply = [
    { keyword: '你好', reply: '你好呀~' },
    { keyword: '在吗', reply: '在的~' },
    { keyword: '加群', reply: '群主,群众中有坏蛋' },
];

完整代码

(function() {
    if (window.__chatMonitorObserver) {
        window.__chatMonitorObserver.disconnect();
        window.__chatMonitorObserver = null;
        console.log('[已移除旧注入,重新启动]');
    }
    window.__chatMonitorRunning = true;
    let lastContent = '';
    let lastSelfNick = '';
    let lastOtherNick = '';
    const autoReply = [{
            keyword: '你好',
            reply: '你好呀~'
        },
        {
            keyword: '在吗',
            reply: '在的~'
        },
        {
            keyword: '加群',
            reply: '群主,群众中有坏蛋'
        },
    ];
    let replyCooldown = false;

    function getNick(el) {
        const nameEl = el.querySelector('.MessageBoxMessageTitleavatarName');
        if (nameEl) return nameEl.textContent?.trim() || '';
        return '';
    }

    function getContent(el) {
        const shareBox = el.querySelector('.MessageItemShareAwemecontainer');
        if (shareBox) {
            const autorName = shareBox.querySelector('.MessageItemShareAwemeautorName');
            const coverImg = shareBox.querySelector('img.commonMyImageimgReal');
            let result = '[分享]';
            if (autorName) result += ' @' + autorName.textContent?.trim();
            if (coverImg && coverImg.src) result += ' ' + coverImg.src;
            return result;
        }
        const imgBox = el.querySelector('.MessageItemImageImageBox');
        if (imgBox) {
            const img = imgBox.querySelector('img');
            if (img && img.src) return '[图片] ' + img.src;
            return '[图片]';
        }
        const emojiBox = el.querySelector('.MessageItemEmojiemojiBox');
        if (emojiBox) {
            const img = emojiBox.querySelector('img');
            if (img && img.src) return '[大表情] ' + img.src;
            return '[大表情]';
        }
        const textEl = el.querySelector('.TextMessageTextpureText');
        if (textEl) return textEl.textContent?.trim() || '';
        const emojiEl = el.querySelector('.TextMessageTextemoji img');
        if (emojiEl) {
            const title = emojiEl.getAttribute('title');
            const src = emojiEl.src;
            if (title && src) return title + ' ' + src;
            if (title) return title;
            if (src) return '[表情] ' + src;
            return '[表情]';
        }
        return '';
    }

    function sendMessage(text) {
        const editor = document.querySelector('[data-slate-editor="true"]');
        if (!editor) {
            console.log('[自动回复] 未找到输入框');
            return;
        }
        editor.focus();
        setTimeout(() => {
            const sel = window.getSelection();
            const range = document.createRange();
            range.selectNodeContents(editor);
            sel.removeAllRanges();
            sel.addRange(range);
            const result = document.execCommand('insertText', false, text);
            setTimeout(() => {
                const sendBtn = document.querySelector('.e2e-send-msg-btn');
                if (sendBtn) {
                    sendBtn.dispatchEvent(new MouseEvent('click', {
                        bubbles: true,
                        cancelable: true
                    }));
                    console.log(`[自动回复] 已发送: ${text}`);
                } else {
                    console.log('[自动回复] 未找到发送按钮');
                }
            }, 800);
        }, 300);
    }

    function checkAutoReply(content, isSelf) {
        if (isSelf || replyCooldown) return;
        for (const rule of autoReply) {
            if (content.includes(rule.keyword)) {
                replyCooldown = true;
                setTimeout(() => {
                    replyCooldown = false;
                }, 3000);
                setTimeout(() => sendMessage(rule.reply), 500);
                // console.log(`[自动回复] 匹配关键词"${rule.keyword}",将回复"${rule.reply}"`);
                return;
            }
        }
    }

    function findChatIndex0() {
        const all = document.querySelectorAll('[data-index="0"]');
        for (const el of all) {
            if (el.querySelector('.messageMessageBoxmessageBox')) return el;
        }
        return null;
    }

    function checkLatest() {
        const indexEl = findChatIndex0();
        if (!indexEl) return;
        const msgBox = indexEl.querySelector('.messageMessageBoxmessageBox');
        if (!msgBox) return;
        if (msgBox.querySelector('.MessageBoxRecalledrecallLayout')) return;
        const contentBox = msgBox.querySelector('.messageMessageBoxcontentBox');
        if (!contentBox) return;
        const content = getContent(msgBox);
        if (!content) return;
        if (content === '已读' || content === '发送中') return;
        if (content === lastContent) return;
        lastContent = content;
        const isSelf = contentBox.classList.contains('messageMessageBoxisFromMe');
        let nick = '';
        const hasAvatar = msgBox.querySelector('.commonIMAvataravatarContainer');
        if (hasAvatar) {
            nick = getNick(msgBox);
            if (isSelf) lastSelfNick = nick;
            else lastOtherNick = nick;
        } else {
            nick = isSelf ? lastSelfNick : lastOtherNick;
        }
        if (!nick) nick = isSelf ? '我' : '对方';
        const action = isSelf ? '发送' : '收到';
        console.log(`[${action}] 昵称:${nick} 消息:${content}`);
        checkAutoReply(content, isSelf);
    }
    const observer = new MutationObserver(() => {
        setTimeout(checkLatest, 200);
    });
    window.__chatMonitorObserver = observer;
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
    setTimeout(checkLatest, 500);
    console.log('[监测已启动] 软件/网站/小程序开发,JS逆向,微信: Akaxz_');
    console.log('[自动回复规则] ' + autoReply.map(r => `"${r.keyword}"→"${r.reply}"`).join(', '));
})();

扩展方向

  • 对接后端API,将消息推送到服务器
  • 接入AI接口实现智能回复
  • 添加更多消息类型支持(语音、视频通话等)
  • 打包成Chrome插件,一键启用