instagram-private-api 私信链接预览响应解析:DirectInboxFeedResponseLinkContext 接口实战指南

原创2026-10-14 18:35:411,634 阅读
文章标签:后端

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,客户端即可据此渲染富媒体卡片。

七、注意事项与边界

  1. link_context 的存在依赖服务端抓取结果。若目标 URL 不可访问、无 Open Graph 标签或抓取失败,Instagram 服务端可能仍会返回链接消息,但 link_context 的字段内容可能为空字符串,因此解析后仍建议对 link_title、link_summary、link_image_url 做空值兜底展示。
  2. link 与 link_context 是响应类型,不是请求参数。请求端(broadcastLink)只提交 link_text 与 link_urls,接口文档中声明的四个字段全部由服务端回填,客户端不能、也不需要构造它们。
  3. 线程最新消息的语义:在 DirectInboxFeed 默认 thread_message_limit: 10 的前提下,last_permanent_item 是最新一条消息;若要覆盖更完整的消息列表,应使用 items 字段并配合 DirectThreadFeed 做深度分页。
  4. 分页状态:DirectInboxFeed 的 state setter 会自动维护 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 的发送链路,就能在收件箱拉取、链接检测、卡片渲染、历史分页等场景中游刃有余。

相关文档与源码索引:

登录后查看全文
instagram-private-api