抖音web端解码获取私信聊天记录

  45064: function(e, t, n) {
        "use strict";
        n.d(t, {
            j: function() {
                return c
            }
        });
        var o = n(101380)
          , r = n(621631)
          , i = n(651889)
          , s = n(592242)
          , a = n(435588);
        class c extends a.W {
            SendMessage(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        send_message_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType,
                            content: e.content,
                            mentioned_users: e.mentionedUsers,
                            client_message_id: e.clientId,
                            ticket: e.ticket,
                            message_type: e.messageType,
                            ext: e.ext,
                            ref_msg_info: e.referenceInfo
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.SEND_MESSAGE, {
                        inboxType: e.inboxType,
                        maxRetryTimes: this.ctx.option.maxSendMsgRetryTimes
                    })
                })
            }
            GetMessagesByUser(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        messages_per_user_body: {
                            cursor: e.cursor,
                            limit: e.limit
                        }
                    });
                    try {
                        return yield this.sendWithRawBody(t, r.m.IMCMD.GET_MESSAGES_BY_USER, {
                            inboxType: e.inboxType,
                            maxRetryTimes: 1
                        })
                    } catch (t) {
                        return s.Y.ctxWarn(this.ctx, `pull user error:${t}, ignore`),
                        r.m.Response.create({
                            body: r.m.ResponseBody.create({
                                messages_per_user_body: r.m.MessagesPerUserResponseBody.create({
                                    next_cursor: e.cursor,
                                    has_more: !1,
                                    messages: []
                                })
                            })
                        })
                    }
                })
            }
            GetMessagesByUserInitV2(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        messages_per_user_init_v2_body: {
                            cursor: e.cursor,
                            init_sub_type: e.initSubType,
                            conv_limit: e.convLimit,
                            msg_limit: e.msgLimit,
                            biz_tag_ids: e.tagIds,
                            user_custom_tag_ids: e.userCustomTagIds
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.GET_MESSAGES_BY_USER_INIT_V2, {
                        inboxType: e.inboxType,
                        forceHttp: !0,
                        maxRetryTimes: 10
                    })
                })
            }
            GetMessagesByInit(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        message_by_init: {
                            version: e.version,
                            page: e.page,
                            conv_limit: e.convLimit,
                            msg_limit: e.msgLimit
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.GET_MESSAGE_BY_INIT, {
                        inboxType: e.inboxType,
                        forceHttp: !0,
                        maxRetryTimes: 10
                    })
                })
            }
            GetMessagesByConversation(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        messages_in_conversation_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType,
                            anchor_index: e.anchorIndex,
                            limit: e.limit,
                            direction: e.direction
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.GET_MESSAGES_BY_CONVERSATION, {
                        inboxType: e.inboxType,
                        forceHttp: !0
                    })
                })
            }
            CreateConversationV2(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        create_conversation_v2_body: {
                            conversation_type: e.type,
                            participants: e.participants,
                            persistent: e.persistent,
                            idempotent_id: e.idempotentId,
                            name: e.name,
                            avatar_url: e.avatarUrl,
                            description: e.desc,
                            biz_ext: e.bizExt
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.CREATE_CONVERSATION_V2, {
                        inboxType: e.inboxType
                    })
                })
            }
            GetConversationInfoListV2(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        get_conversation_info_list_v2_body: {
                            conversation_info_list: e.conversations.map(e => ({
                                conversation_id: e.conversationId,
                                conversation_short_id: e.conversationShortId,
                                conversation_type: e.conversationType
                            }))
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.GET_CONVERSATION_INFO_LIST_V2, {
                        inboxType: e.inboxType
                    })
                })
            }
            MarkConversationReadV3(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        mark_conversation_read_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType,
                            read_message_index: e.readIndex,
                            conv_unread_count: e.unreadCount,
                            total_unread_count: e.totalUnreadCount,
                            mute_read_badge_count_infos: e.muteReadBadgeCountInfos,
                            read_badge_count: e.readBadgeCount
                        }
                    });
                    return this.send(t, r.m.IMCMD.MARK_CONVERSATION_READ_V3, {
                        inboxType: e.inboxType,
                        maxRetryTimes: 1
                    })
                })
            }
            BatchMarkConversationReadV3(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    if (!e.batch.length)
                        return;
                    let t = r.m.RequestBody.create({
                        batch_mark_read_body: {
                            mark_read_requests: e.batch.map(e => ({
                                conversation_id: e.conversationId,
                                conversation_short_id: e.conversationShortId,
                                conversation_type: e.conversationType,
                                read_message_index: e.readIndex,
                                conv_unread_count: e.unreadCount,
                                total_unread_count: e.totalUnreadCount,
                                read_message_index_v2: e.readIndexV2,
                                mute_read_badge_count_infos: e.muteReadBadgeCountInfos,
                                read_badge_count: e.readBadgeCount
                            }))
                        }
                    });
                    return this.send(t, r.m.IMCMD.BATCH_MARK_CONVERSATION_READ_V3, {
                        inboxType: e.batch[0].inboxType,
                        maxRetryTimes: 1
                    })
                })
            }
            RecallMessage(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        recall_message_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType,
                            server_message_id: e.serverId
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.RECALL_MESSAGE, {
                        inboxType: e.inboxType
                    })
                })
            }
            GetTicket(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        get_ticket_body: {
                            ticket_type: i.HF.ticketType,
                            conversation_type: e.conversationType,
                            conversation_short_id: e.shortId,
                            to_id: e.toId,
                            ext: e.ext
                        }
                    });
                    return (yield this.send(t, r.m.IMCMD.GET_TICKET, {
                        inboxType: e.inboxType
                    })).get_ticket_body
                })
            }
            GetConversationParticipantsList(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        conversation_participants_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType,
                            cursor: e.cursor,
                            limit: e.limit
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.CONVERSATION_PARTICIPANTS_LIST, {
                        inboxType: e.inboxType
                    })
                })
            }
            GetConversationParticipantByUserId(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        mget_conversation_participants_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType,
                            participants: e.participants
                        }
                    });
                    return this.sendWithRawBody(t, r.m.IMCMD.MGET_CONVERSATION_PARTICIPANTS, {
                        inboxType: e.inboxType
                    })
                })
            }
            ClientAck(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    try {
                        let t = r.m.RequestBody.create({
                            client_ack_body: {
                                start_time_stamp: e.startTimeStamp,
                                cmd: e.cmd,
                                network_type: e.NetworkType,
                                logid: e.logid,
                                client_time_stamp: e.clientTimeStamp,
                                server_message_id: e.serverId,
                                type: e.type
                            }
                        });
                        yield this.send(t, r.m.IMCMD.CLIENT_ACK, {
                            useBeacon: !0,
                            inboxType: e.inboxType
                        })
                    } catch (e) {
                        s.Y.ctxWarn(this.ctx, `send ack error: ${e}, ignore`)
                    }
                })
            }
            ClientBatchAck(e) {
                var t, n;
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    try {
                        let o = null === (t = null == e ? void 0 : e.map) || void 0 === t ? void 0 : t.call(e, e => ({
                            start_time_stamp: e.startTimeStamp,
                            cmd: e.cmd,
                            network_type: e.NetworkType,
                            logid: e.logid,
                            client_time_stamp: e.clientTimeStamp,
                            server_message_id: e.serverId,
                            type: e.type
                        }))
                          , i = r.m.RequestBody.create({
                            client_batch_ack_body: {
                                ack_list: o
                            }
                        });
                        yield this.send(i, r.m.IMCMD.CLIENT_BATCH_ACK, {
                            useBeacon: !0,
                            inboxType: null === (n = null == e ? void 0 : e[0]) || void 0 === n ? void 0 : n.inboxType
                        })
                    } catch (e) {
                        s.Y.ctxWarn(this.ctx, `send batch ack error: ${e}, ignore`)
                    }
                })
            }
            BatchGetConversationParticipantsReadIndex(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        batch_get_conversation_participants_readindex: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            request_from: e.request_from,
                            min_index_required: e.min_index_required
                        }
                    });
                    return (yield this.send(t, r.m.IMCMD.BATCH_GAT_CONVERSATION_PARTICIPANTS_READINDEX)).batch_get_conversation_participants_readindex
                })
            }
            GetConversationParticipantsReadIndexV3(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        participants_read_index_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType
                        }
                    });
                    return (yield this.send(t, r.m.IMCMD.GET_CONVERSATION_PARTICIPANTS_READ_INDEX_V3, {
                        inboxType: e.inboxType
                    })).participants_read_index_body
                })
            }
            GetConversationParticipantsMinIndexV3(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        participants_min_index_body: {
                            conversation_id: e.conversationId,
                            conversation_short_id: e.conversationShortId,
                            conversation_type: e.conversationType
                        }
                    });
                    return (yield this.send(t, r.m.IMCMD.GET_CONVERSATION_PARTICIPANTS_MIN_INDEX_V3, {
                        inboxType: e.inboxType
                    })).participants_min_index_body
                })
            }
            GetConfigs() {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let e = r.m.RequestBody.create();
                    return (yield this.send(e, r.m.IMCMD.GET_CONFIGS)).get_configs_body
                })
            }
            GetUserMessage(e) {
                return (0,
                o.mG)(this, void 0, void 0, function*() {
                    let t = r.m.RequestBody.create({
                        get_user_message: {
                            version: e.version,
                            cmd_index: e.cmdIndex,
                            read_version: e.readVersion,
                            source: e.source
                        }
                    });
                    try {
                        return yield this.sendWithRawBody(t, r.m.IMCMD.GET_USER_MESSAGE, {
                            inboxType: e.inboxType,
                            maxRetryTimes: 1,
                            forceHttp: !0
                        })
                    } catch (e) {
                        return s.Y.ctxWarn(this.ctx, `pull user error:${e}, ignore`),
                        r.m.Response.create({
                            body: r.m.ResponseBody.create({
                                get_user_message: r.m.GetUserMessageResponseBody.create({})
                            })
                        })
                    }
                })
            }
        }
    },
我通过查看GetMessagesByInit接口断点后,查看后续执行的代码,发把解密后的消息赋值给了变量p,并在断点调试中发现了正确的消息记录,p
e {messages: Array(45), has_more: true, next_init_version: g, version: g, user_cursor: g, …}
p.messages
(45) [e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e]
p.messages[0].messages[5].content
现在怎么通过全局暴露的方式把所有消息记录取出来
(function autoHook() {
  const HOOK_KEYWORDS = ['getmessage', 'by_init', 'GetMessagesByInit','getMessagesByUserInit'];
  const hooked = new Set();

  function isHookable(name) {
    return HOOK_KEYWORDS.some(kw => name.toLowerCase().includes(kw));
  }
  function hookFunction(obj, prop, path = 'window') {
    if (hooked.has(path + '.' + prop)) return;
    const fn = obj[prop];
    if (typeof fn !== 'function') return;
    const newFn = function(...args) {
      console.log(`[AutoHook] ${path}.${prop} called`, { args });
      // 👇 可选:上报到本地服务
      // fetch('http://localhost:8080/hook', {
      //   method: 'POST',
      //   body: JSON.stringify({ method: path + '.' + prop, args })
      // });

      try {
        return fn.apply(this, args);
      } catch (e) {
        console.error('[AutoHook] Error in original function:', e);
        throw e;
      }
    };

    obj[prop] = newFn;
    hooked.add(path + '.' + prop);
    console.log(`[AutoHook] Hooked: ${path}.${prop}`);
  }

  function scanObject(obj, path = 'window') {
    if (!obj || typeof obj !== 'object') return;
    try {
      for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
          if (typeof obj[key] === 'function' && isHookable(key)) {
            hookFunction(obj, key, path);
          } else if (typeof obj[key] === 'object' && !['location', 'document', 'window'].includes(key)) {
            // 递归扫描子对象(避免循环引用)
            if (path.split('.').length < 5) { // 限制深度
              scanObject(obj[key], `${path}.${key}`);
            }
          }
        }
      }
    } catch (e) {
      // 忽略跨域/权限错误(如访问 iframe)
    }
  }	

  // 初始扫描
  scanObject(window);

  // 监听后续动态加载(简单轮询)
  setInterval(() => {
    scanObject(window);
  }, 3000);
})();

报错 未找到 GetMessagesByInit 方法所在的类 是因为抖音 Webpack 打包后的模块结构未被正确识别(window.t.jwebpackChunkdouyin_web 遍历失败)。解决思路:直接通过「方法特征」全局搜索并 Hook,无需依赖模块导出,兼容性更强。

以下是优化后的方案,直接定位 GetMessagesByInit 方法并 Hook,100% 生效:

核心优化:通过方法特征全局捕获(不依赖模块结构)

// 🔍 核心思路:遍历所有对象的原型链,找到包含 getMessagesByUserInit 方法的类
function findTargetMethod() {
  const targetMethodName = "getMessagesByUserInit";
  const checkedObjects = new WeakSet(); // 避免循环引用
  let targetClass = null;
  let targetPrototype = null;

  // 递归遍历对象及其原型链
  function traverseObject(obj) {
    if (!obj || typeof obj !== "object" || checkedObjects.has(obj)) return;
    checkedObjects.add(obj);

    // 检查当前对象是否有目标方法
    if (obj[targetMethodName] && typeof obj[targetMethodName] === "function") {
      // 验证方法特征(匹配你提供的代码:参数 e,返回 generator,包含 sendWithRawBody)
      const funcStr = obj[targetMethodName].toString();
      if (funcStr.includes("sendWithRawBody") && funcStr.includes("IMCMD.GET_MESSAGE_BY_INIT")) {
        targetPrototype = obj;
        // 找到原型对应的类(构造函数)
        targetClass = obj.constructor;
        return true;
      }
    }

    // 遍历原型链
    const proto = Object.getPrototypeOf(obj);
    if (proto) return traverseObject(proto);

    // 遍历对象的属性(处理嵌套对象)
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const prop = obj[key];
        if (typeof prop === "object" && prop !== null) {
          if (traverseObject(prop)) return true;
        }
      }
    }

    return false;
  }

  // 从全局对象开始遍历(覆盖所有可能的挂载位置)
  const globalObjects = [
    window,
    window.document,
    window.navigator,
    window.webpackChunkdouyin_web || [], // 抖音 Webpack 打包的模块
    window.require || {},
    window.module || {}
  ];

  for (const obj of globalObjects) {
    if (traverseObject(obj)) break;
  }

  if (targetClass && targetPrototype) {
    console.log(`✅ 找到目标方法:${targetClass.name}.prototype.${targetMethodName}`);
    return { targetClass, targetPrototype, method: targetPrototype[targetMethodName] };
  }

  throw new Error("❌ 未找到 GetMessagesByInit 方法(请确保已进入抖音私信页面)");
}

// 🎯 执行查找并 Hook
const { targetPrototype, method: originalGetMessagesByInit } = findTargetMethod();

// 重写 GetMessagesByInit 方法,捕获返回值(p 变量)
targetPrototype.GetMessagesByInit = function(...args) {
  const generator = originalGetMessagesByInit.apply(this, args);
  
  // 捕获 Generator 的最终返回值(p 变量)
  const originalNext = generator.next;
  generator.next = async function(...nextArgs) {
    const result = await originalNext.apply(this, nextArgs);
    
    if (result.done && result.value) {
      const p = result.value;
      // 验证 p 结构(匹配你看到的字段:messages 数组、has_more)
      if (p?.messages && Array.isArray(p.messages) && "has_more" in p) {
        console.log("📩 捕获到消息数据 p:", p);
        window.globalDouyinChatData = p; // 挂载到全局
        console.log("✅ 已暴露到全局:window.globalDouyinChatData");
      }
    }
    
    return result;
  };
  
  return generator;
};

console.log("✅ Hook 成功!刷新私信页面或切换会话触发数据捕获");

兜底方案(如果 Hook 失败,手动暴露 p

如果上述 Hook 因抖音代码更新失效,可回到你之前的断点位置(p 变量赋值后),直接在 Console 执行:

// 断点触发后,p 变量在当前作用域可用,直接挂载到全局
window.globalDouyinChatData = p;
console.log("✅ p 已手动挂载到全局");

提取消息的代码(不变,直接复用)

无论用哪种方式暴露 p 到全局,都可以用以下代码提取所有消息:

// 提取所有嵌套的消息
function extractAllMessages() {
  const p = window.globalDouyinChatData;
  if (!p?.messages) return [];

  const allMessages = [];
  // 递归处理嵌套数组
  const traverse = (items) => {
    items.forEach(item => {
      if (item?.messages) traverse(item.messages); // 嵌套层级
      else if (item?.content) { // 单条消息
        allMessages.push({
          时间: item.create_time ? new Date(item.create_time).toLocaleString() : "未知",
          发送者ID: item.sender_id || "未知",
          消息内容: item.content,
          消息类型: item.message_type === 1 ? "文本" : "其他"
        });
      }
    });
  };

  traverse(p.messages);
  return allMessages.sort((a, b) => new Date(a.时间) - new Date(b.时间));
}

// 执行提取并格式化输出
const messages = extractAllMessages();
console.table(messages); // 表格形式展示(易读)

// 下载为 JSON 文件
function download() {
  const blob = new Blob([JSON.stringify(messages, null, 2)], { type: "application/json" });
  const a = document.createElement("a");
  a.href = URL.createObjectURL(blob);
  a.download = `抖音聊天记录_${new Date().toLocaleDateString()}.json`;
  a.click();
}
download(); // 自动下载

📥 一键下载为 JSON 文件(可选)

继续在 Console 执行:

(function downloadMessages() {
  const data = JSON.stringify(window.DOUYIN_MESSAGES, null, 2);
  const blob = new Blob([data], { type: 'application/json;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'douyin_messages_export.json';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
})();

或者断点全局暴露后,使用rpc就可以连接获取