抖音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.j 或 webpackChunkdouyin_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就可以连接获取