instagram-private-api 私信链接预览响应解析:DirectInboxFeedResponseLinkContext 接口实战指南
instagram-private-api 私信链接预览响应解析:DirectInboxFeedResponseLinkContext 接口实战指南
DirectInboxFeedResponseLinkContext 是 instagram-private-api(NodeJS/TypeScript 编写的 Instagram 私有 API SDK)中用于描述 私信(DM)会话里链接消息预览信息 的响应类型接口。它承载了 Instagram 在聊天中展示链接卡片所需的四个核心字段:链接地址、标题、摘要与缩略图。本文将以该接口为骨架,结合 src/responses/direct-inbox.feed.response.ts 等源码,讲解它的字段含义、在完整响应树中的位置,以及如何通过 DirectInboxFeed 拉取收件箱并解析出链接预览数据、如何主动发送带预览的链接消息。
一、接口概览:一个保存“链接卡片”预览数据的结构
在 TypeDoc 生成的接口文档 中,DirectInboxFeedResponseLinkContext 被定义在 responses 模块下,归属于 DirectInboxFeedResponse 这一系列私信收件箱响应类型。它是一个纯数据接口(interface),内部只声明了四个必填的 string 属性,不包含任何方法。
它的 TypeScript 定义位于 src/responses/direct-inbox.feed.response.ts#L119-L124:
export interface DirectInboxFeedResponseLinkContext {
link_url: string;
link_title: string;
link_summary: string;
link_image_url: string;
}
从语义上理解,这个接口描述的是 Instagram 服务端为某条链接消息生成的一张小“链接卡片”:用户在一个会话里发送或收到包含 URL 的消息后,Instagram 会抓取该 URL 的网页信息,并以 link_context 的形式附在消息的 link 对象上,供客户端渲染成带标题、摘要和缩略图的富媒体预览。
二、四个属性逐一解析
接口文档为每个属性都提供了明确的类型签名,全部为 string(必填,非可选)。
| 属性名 | 类型 | 含义 | 典型用途 |
|---|---|---|---|
link_url |
string |
被分享链接的完整 URL 地址 | 用户点击卡片时跳转的目标地址 |
link_title |
string |
链接对应网页的标题 | 卡片标题行的渲染文本 |
link_summary |
string |
链接对应网页的摘要/描述文本 | 卡片正文的渲染文本 |
link_image_url |
string |
链接预览缩略图(Open Graph 图片等)的 URL | 卡片缩略图区域加载的图片来源 |
值得注意的几点:
- 四个字段都是必填(required)。接口文档中均标注为
• **link_url**: string这种非可选形式(没有Optional前缀,对比 DirectInboxFeedResponseItemsItem 文档 中link、text等字段的• Optional标注即可看出差异)。也就是说,只要服务端返回了link_context,理论上这四个值都会存在,解析时可以直接使用,无需再做undefined兜底。 - 四个字段都是
string类型。这与同文件中DirectInboxFeedResponseInbox.unseen_count(number)、DirectInboxFeedResponseThreadsItem.viewer_id(number)等数值型字段形成对比——链接预览相关的数据在服务端全部以字符串形态下发。
三、它在响应树中的位置:从收件箱到链接卡片
要正确使用 DirectInboxFeedResponseLinkContext,需要先理解它在整个私信收件箱响应结构中的嵌套层级。相关类型全部集中在 src/responses/direct-inbox.feed.response.ts 这一个文件中,形成如下嵌套关系:
DirectInboxFeedResponse
└── inbox: DirectInboxFeedResponseInbox
└── threads: DirectInboxFeedResponseThreadsItem[]
└── last_permanent_item / items: DirectInboxFeedResponseItemsItem[]
└── link?: DirectInboxFeedResponseLink // 可选字段
├── text: string
├── client_context: string
├── mutation_token: string
└── link_context: DirectInboxFeedResponseLinkContext // 本篇主角
├── link_url: string
├── link_title: string
├── link_summary: string
└── link_image_url: string
各层接口的源码位置与文档入口如下:
- 顶层
DirectInboxFeedResponse(定义于 src/responses/direct-inbox.feed.response.ts#L1-L8,含inbox、seq_id、snapshot_at_ms、pending_requests_total、most_recent_inviter、status字段),文档见 DirectInboxFeedResponse; - 收件箱容器
DirectInboxFeedResponseInbox(threads、has_older、unseen_count、oldest_cursor等),文档见 DirectInboxFeedResponseInbox; - 会话项
DirectInboxFeedResponseThreadsItem(含items、last_permanent_item、last_activity_at、is_pin、is_group等),每个会话的最新一条消息通常以last_permanent_item提供; - 消息项
DirectInboxFeedResponseItemsItem(item_id、item_type、可选text、可选link、可选reel_share等); - 链接对象
DirectInboxFeedResponseLink(text、link_context、client_context、mutation_token四个字段),文档见 DirectInboxFeedResponseLink,其中第 114 行明确声明link_context: DirectInboxFeedResponseLinkContext; - 最后才是本篇文章主角
DirectInboxFeedResponseLinkContext。
关键点在于:link 字段是可选的(DirectInboxFeedResponseItemsItem.link?: DirectInboxFeedResponseLink)。Instagram 只有在消息内容为链接(或包含链接)时才会携带 link 对象,因此 link_context 也只有在链接消息上才会出现。开发者在解析时应当先判断 item.link 是否存在,再进一步读取 item.link.link_context。
四、源码调用链:这些数据从哪里来、被谁消费
4.1 数据来源:DirectInboxFeed.request()
DirectInboxFeed(文档见 DirectInboxFeed)负责向 Instagram 私信收件箱接口发起请求。其核心实现位于 src/feeds/direct-inbox.feed.ts#L18-L33:
async request() {
const { body } = await this.client.request.send<DirectInboxFeedResponse>({
url: `/api/v1/direct_v2/inbox/`,
qs: {
visual_message_return_type: 'unseen',
cursor: this.cursor,
direction: this.cursor ? 'older' : void 0,
seq_id: this.seqId,
thread_message_limit: 10,
persistentBadging: true,
limit: 20,
},
});
this.state = body;
return body;
}
请求的响应体 body 会被 send<DirectInboxFeedResponse> 以泛型方式反序列化为 DirectInboxFeedResponse 结构,其中就包含了 link_context 所栖身的嵌套数据。也就是说,链接预览数据不是额外接口返回的,而是收件箱列表接口响应的一部分。
该 Feed 通过 state setter(src/feeds/direct-inbox.feed.ts#L12-L16)将 body.inbox.has_older、body.seq_id、body.inbox.oldest_cursor 分别写入 moreAvailable、seqId、cursor,用于支持分页拉取更多历史会话。每条会话最多携带 thread_message_limit: 10 条消息,因此 last_permanent_item 中的链接信息即可代表会话最新的链接消息。
DirectInboxFeed 实例通过 FeedFactory.directInbox() 创建,见 src/core/feed.factory.ts#L102-L104。此外,SDK 内置的模拟真实客户端行为的 SimulateService.postLoginFlow() 也会在登录后调用 this.client.feed.directInbox().request()(src/services/simulate.service.ts#L37),以降低账号被风控的概率——可见收件箱接口是登录后模拟真实使用的重要环节。
4.2 反向验证:链接预览如何被“制造”出来
要理解 link_context 各字段的语义,可以反向看 SDK 中发送链接的代码。DirectThreadEntity.broadcastLink() 负责向会话发送链接消息:
public async broadcastLink(link_text: string, link_urls: string[]) {
return await this.broadcast({
item: 'link',
form: {
link_text,
link_urls: JSON.stringify(link_urls),
},
});
}
客户端只提交了 link_text 与 link_urls;而 link_title、link_summary、link_image_url 这些预览信息是由 Instagram 服务端抓取链接页面后回填的,随后在收件箱/会话响应中以 link_context 形式下发。这解释了为什么 LinkContext 是一个“只读响应类型”——它的字段来自服务端,而非客户端构造。
另外,DirectThreadEntity.broadcastText() 还有一个自动识别链接的机制:
public async broadcastText(text: string, skipLinkCheck?: boolean) {
if (!skipLinkCheck) {
const urls = text.match(require('url-regex-safe')({ strict: false }));
if (urls instanceof Array) {
return this.broadcastLink(text, urls);
}
}
return await this.broadcast({ item: 'text', form: { text } });
}
当发送的文本中检测到 URL(通过 url-regex-safe 正则匹配)时,SDK 会自动改走 broadcastLink 链路,从而让服务端有机会生成链接预览卡片。这也意味着,通过 broadcastText 发送含链接的消息后,对端收到的会话消息里大概率会包含 link.link_context 数据。可通过 skipLinkCheck: true 关闭该自动识别行为。
五、实战示例:拉取收件箱并解析链接卡片
下面给出一个完整的、可直接复用的 TypeScript 示例,演示如何:登录后拉取私信收件箱 → 遍历会话 → 定位链接消息 → 安全解析 link_context 的四个字段。
import { IgApiClient } from 'instagram-private-api';
async function inspectInboxLinkCards() {
const ig = new IgApiClient();
ig.state.generateDevice('your_device_id');
await ig.account.login('username', 'password');
// 拉取收件箱(第一页),底层请求 /api/v1/direct_v2/inbox/
const inboxFeed = ig.feed.directInbox();
const response = await inboxFeed.request();
for (const thread of response.inbox.threads) {
// 每个会话取最新一条永久消息
const item = thread.last_permanent_item;
// 1) 先判断是不是链接消息
if (!item.link) {
continue;
}
// 2) 再安全读取 link_context —— 该字段必填但 link 本身可选
const link = item.link;
const ctx = link.link_context;
console.log({
threadId: thread.thread_id,
threadTitle: thread.thread_title,
itemId: item.item_id,
itemType: item.item_type,
linkText: link.text,
linkUrl: ctx.link_url, // 跳转目标
linkTitle: ctx.link_title, // 卡片标题
linkSummary: ctx.link_summary, // 卡片摘要
linkImageUrl: ctx.link_image_url, // 卡片缩略图
});
}
// 分页:如果还有更早的会话
if (inboxFeed.isMoreAvailable()) {
const older = await inboxFeed.request();
console.log(`older page seq_id=${older.seq_id}, threads=${older.inbox.threads.length}`);
}
}
运行要点:
ig.feed.directInbox()返回DirectInboxFeed实例(工厂方法见 src/core/feed.factory.ts#L102-L104);- 首屏请求默认
limit: 20(会话数)与thread_message_limit: 10(每会话消息数); item.link为可选字段,务必先判空;link_context四字段在接口层面为必填;- 若想深入某个会话查看更多历史消息,可用
ig.feed.directThread({ thread_id, oldest_cursor }, seq_id)(见 src/core/feed.factory.ts#L110-L119)创建 DirectThreadFeed 继续翻页,其实现位于 src/feeds/direct-thread.feed.ts,请求/api/v1/direct_v2/threads/${id}/,同样可能携带link与link_context。
六、主动发送可预览链接消息的完整闭环
结合上文,发送与解析可以构成一个完整闭环:发送时使用 broadcastLink 或含链接的 broadcastText;接收时从 last_permanent_item.link.link_context 读取预览数据。示例:
import { IgApiClient } from 'instagram-private-api';
const ig = new IgApiClient();
ig.state.generateDevice('device_id');
await ig.account.login('username', 'password');
// 方式一:直接发送链接(item 类型为 'link')
const thread = ig.entity.directThread(['1234567890']);
const sent = await thread.broadcastLink('查看这篇文档', ['https://example.com/docs']);
console.log(sent.payload?.thread_id || sent.message_metadata?.[0]?.thread_id);
// 方式二:发送纯文本,SDK 自动识别 URL 并转为 link 消息
await thread.broadcastText('文档地址:https://example.com/docs');
// 若不想触发链接卡片,可跳过链接检查:
await thread.broadcastText('https://example.com/docs', true);
发送后,对端会话的响应中就会携带 link_context 提供的 link_title、link_summary、link_image_url,客户端即可据此渲染富媒体卡片。
七、注意事项与边界
link_context的存在依赖服务端抓取结果。若目标 URL 不可访问、无 Open Graph 标签或抓取失败,Instagram 服务端可能仍会返回链接消息,但link_context的字段内容可能为空字符串,因此解析后仍建议对link_title、link_summary、link_image_url做空值兜底展示。link与link_context是响应类型,不是请求参数。请求端(broadcastLink)只提交link_text与link_urls,接口文档中声明的四个字段全部由服务端回填,客户端不能、也不需要构造它们。- 线程最新消息的语义:在
DirectInboxFeed默认thread_message_limit: 10的前提下,last_permanent_item是最新一条消息;若要覆盖更完整的消息列表,应使用items字段并配合DirectThreadFeed做深度分页。 - 分页状态:
DirectInboxFeed的statesetter 会自动维护has_older/oldest_cursor/seq_id,连续调用request()即可翻页;如需断点续传,可借助基类Feed的serialize()/deserialize()持久化游标(见 src/core/feed.ts)。
八、小结
DirectInboxFeedResponseLinkContext 虽然只是一个只有四个 string 字段的小接口,但它完整地刻画了 Instagram 私信链接卡片的响应模型:link_url 提供跳转目标、link_title 与 link_summary 提供文字预览、link_image_url 提供视觉预览。在实际开发中,将它放在 src/responses/direct-inbox.feed.response.ts 所定义的整个 DirectInboxFeedResponse 嵌套结构中理解,并结合 DirectInboxFeed 的请求流程与 DirectThreadEntity 的发送链路,就能在收件箱拉取、链接检测、卡片渲染、历史分页等场景中游刃有余。
相关文档与源码索引:
- 接口文档:DirectInboxFeedResponseLinkContext、DirectInboxFeedResponseLink、DirectInboxFeedResponseItemsItem
- 响应定义:src/responses/direct-inbox.feed.response.ts
- Feed 实现:src/feeds/direct-inbox.feed.ts、src/feeds/direct-thread.feed.ts
- 发送链接:src/entities/direct-thread.entity.ts#L33-L103
- 工厂与模拟流程:src/core/feed.factory.ts#L102-L119、src/services/simulate.service.ts#L37