> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aitoearn.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 通过插件发布

> 使用 AiToEarn 浏览器插件发布小红书、微信视频号和抖音内容。

AiToEarn 浏览器插件会复用当前浏览器中的平台登录态，让你的网页可以一键发布到小红书、微信视频号和抖音。网页不需要让用户复制 Cookie。

| 平台 | `platform` | 视频 | 图文 | 主要要求 |
| - | - | - | - | - |
| 小红书 | `xhs` | 支持 | 支持，最多 9 张 | 视频必须提供封面 |
| 微信视频号 | `wxSph` | 支持 | 暂不支持 | 视频必须提供封面 |
| 抖音 | `douyin` | 支持 | 支持，1～35 张 | 标题最多 30 字、正文最多 1000 字、话题最多 5 个 |

<Info>
  你可以先下载并运行[开放平台
  Demo](https://ai-to-earn.oss-cn-beijing.aliyuncs.com/comment/assets/open-platform-demo.zip)，在页面顶部切换到“浏览器插件”，再对照本教程接入。
</Info>

## 第一步：安装插件

先按[插件安装教程](https://aitoearn.cn/zh-CN/websit/plugin-guide)安装 AiToEarn 浏览器插件。

## 第二步：选择“仅授权”

打开插件侧栏，点击 **Authorize Only（仅授权）**。这种方式不要求用户登录 AiToEarn 网页账号。

<img src="https://mintcdn.com/aitoearn/9tS87rncy5CjWAGA/assets/use/plugin-publish/01-authorize-only.png?fit=max&auto=format&n=9tS87rncy5CjWAGA&q=85&s=d28b144d308ebe409e032170eb17ef11" alt="在插件登录页点击 Authorize Only（仅授权）" width="378" height="949" data-path="assets/use/plugin-publish/01-authorize-only.png" />

<Info>
  `Authorize Only` 只省略 AiToEarn 网页账号登录，不会禁用 Web
  API。完成下一步的域名授权后，已授权网页可以调用 `login()`、`publish()`
  以及本页列出的辅助方法。
</Info>

## 第三步：配置可注入域名

在“允许注入的域名”中填写**调用插件的网页地址**。运行本地 Demo 时填写：

```text theme={null}
http://localhost:5173/
```

正式部署时改为网页的实际来源，例如 `https://demo.example.com/`。每行填写一个 HTTP 或 HTTPS 地址。

<Warning>
  这里填写的是你自己的 Demo
  或业务网页来源，不是小红书、微信视频号或抖音的网址。插件会按来源保存匹配规则，例如
  `http://localhost:5173/` 会保存为
  `http://localhost:5173/*`。只添加你信任的域名。
</Warning>

点击 **Confirm and authorize（确认并授权）**，然后刷新网页，让插件注入 `window.AIToEarnPlugin`。

<img src="https://mintcdn.com/aitoearn/9tS87rncy5CjWAGA/assets/use/plugin-publish/02-configure-injectable-domains.png?fit=max&auto=format&n=9tS87rncy5CjWAGA&q=85&s=69ac31b5afd336894cff55f4e170498e" alt="填写允许注入 AiToEarn Web API 的 Demo 域名" width="383" height="929" data-path="assets/use/plugin-publish/02-configure-injectable-domains.png" />

## 第四步：检测插件并读取平台账号

先检查全局对象、插件版本和权限，再读取当前浏览器中的平台登录态：

```js theme={null}
const plugin = window.AIToEarnPlugin;

if (!plugin) {
  // 在产品中应展示本教程地址，而不是继续调用 API
  throw new Error("未检测到 AiToEarn 浏览器插件");
}

const [{ version }, permission] = await Promise.all([
  plugin.getVersion(),
  plugin.checkPermission(),
]);

if (!permission.granted) {
  throw new Error(permission.error || "插件尚未授权");
}

const account = await plugin.login("xhs");

// 业务界面只保留需要展示的账号摘要
const accountSummary = {
  type: account.type,
  uid: account.uid,
  account: account.account,
  avatar: account.avatar,
  nickname: account.nickname,
  fansCount: account.fansCount,
};

console.log("插件版本", version);
console.log("当前账号", accountSummary);
```

<Info>
  `login(platform)` 的名字表示“获取指定发布平台的登录信息”。它不会登录
  AiToEarn，也不会弹出平台登录表单；用户应先在同一个浏览器中登录目标平台。如果平台登录态不存在或已过期，Promise
  会抛出错误。
</Info>

<Warning>
  `login()` 的原始返回中包含
  `loginCookie`。这是敏感字段，不要展示、打印日志、持久化或传给第三方。只需要显示账号时，请像上面的示例一样立即映射为昵称、头像和
  UID 等摘要。
</Warning>

## 第五步：发布内容

下面示例将本地视频发布到小红书，并接收下载、上传和发布进度：

```js theme={null}
const result = await window.AIToEarnPlugin.publish(
  {
    platform: "xhs",
    type: "video",
    title: "今天的创作记录",
    desc: "这是一条通过浏览器插件发布的内容。",
    video: videoFile,
    cover: coverFile,
    topics: ["创作记录"],
    visibility: "public",
  },
  (event) => {
    console.log(event.stage, event.progress, event.message);
  },
);

if (!result.success) {
  throw new Error(result.failReason || "发布失败");
}

console.log("作品 ID", result.workId);
console.log("作品链接", result.shareLink);
```

`video`、`cover` 和 `images` 都支持浏览器 `File` 对象或可访问的 HTTP/HTTPS URL。图文发布示例：

```js theme={null}
const result = await window.AIToEarnPlugin.publish(
  {
    platform: "douyin",
    type: "image",
    title: "旅行照片",
    desc: "记录今天看到的风景。",
    images: [firstImageFile, "https://example.com/second-image.jpg"],
    topics: ["旅行", "摄影"],
  },
  ({ stage, progress }) => {
    console.log(`${stage}: ${progress}%`);
  },
);
```

<Warning>
  URL 素材会先由插件下载。请确保 URL
  可直接访问、未过期，并允许插件获取；不要传本地路径、内网地址或必须登录后才能打开的
  URL。
</Warning>

## `Authorize Only` 支持的方法

配置可注入域名后，`Authorize Only` 模式支持以下 Web API：

| 方法 | 用途 | 返回值 |
| - | - | - |
| `checkPermission()` | 检查插件权限 | `Promise<CheckPermissionResult>` |
| `getVersion()` | 获取插件版本 | `Promise<GetVersionResult>` |
| `login(platform)` | 读取并验证平台登录态 | `Promise<PlatAccountInfo>` |
| `publish(params, onProgress?)` | 发布视频或图文 | `Promise<PublishResult>` |
| `xhsSearchLocation(params)` | 搜索小红书位置 | `Promise<XhsLocationItem[]>` |
| `douyinSearchLocation(params)` | 搜索抖音位置 | `Promise<DouyinLocationSearchResult>` |
| `wxSphSearchLocation(params)` | 搜索视频号位置 | `Promise<WxSphLocationItem[]>` |
| `wxSphSearchActivity(params)` | 搜索视频号活动 | `Promise<WxSphEventInfo[]>` |
| `wxSphStartLinkPolling(params)` | 启动视频号作品链接轮询 | `Promise<WxSphStartLinkPollingResult>` |
| `douyinInteraction(params)` | 抖音点赞、收藏或评论 | `Promise<DouyinInteractionResult>` |
| `douyinDirectMessage(params)` | 发送抖音私信 | `Promise<DouyinDirectMessageResult>` |
| `unifiedInteraction(params)` | 对小红书或抖音作品执行点赞、收藏或评论 | `Promise<UnifiedInteractionResult>` |

## 基础方法参数与返回值

### `checkPermission()`

无需参数，返回：

| 字段 | 类型 | 说明 |
| - | - | - |
| `granted` | `boolean` | 是否已授予插件所需权限 |
| `permissions` | `string[]`（可选） | 已授予的权限列表 |
| `hostAccess` | `boolean`（可选） | 是否已授予站点访问权限 |
| `error` | `string`（可选） | 检查失败时的错误说明 |

### `getVersion()`

无需参数，返回：

| 字段 | 类型 | 说明 |
| - | - | - |
| `version` | `string`（可选） | 当前插件版本号 |
| `error` | `string`（可选） | 获取失败时的错误说明 |

### `login(platform)`

`platform` 只能使用 `xhs`、`wxSph` 或 `douyin`。成功时返回：

| 字段 | 类型 | 说明 |
| - | - | - |
| `type` | `"xhs" \| "wxSph" \| "douyin"` | 平台标识 |
| `loginCookie` | `string` | 平台登录 Cookie；敏感字段，业务页面不应消费 |
| `uid` | `string` | 平台用户 ID |
| `account` | `string` | 平台账号 |
| `avatar` | `string` | 头像地址 |
| `nickname` | `string` | 昵称 |
| `fansCount` | `number`（可选） | 粉丝数 |
| `xhsLoginStatus` | `{ home: boolean; creator: boolean }`（可选） | 小红书主页和创作中心登录状态 |
| `wxSphLoginStatus` | `{ channels: boolean }`（可选） | 视频号登录状态 |

失败时 Promise 会抛出 `Error`；部分错误还带有 `code` 和 `errorCode`。

## `publish()` 参数

调用形式：

```ts theme={null}
plugin.publish(params, onProgress?): Promise<PublishResult>
```

`params` 字段如下：

| 字段 | 类型 | 必填 | 说明 |
| - | - | - | - |
| `platform` | `"xhs" \| "wxSph" \| "douyin"` | 是 | 目标平台 |
| `type` | `"video" \| "image"` | 是 | 内容类型；视频号只能传 `video` |
| `title` | `string` | 否 | 标题 |
| `desc` | `string` | 否 | 正文或描述 |
| `video` | `File \| string` | 视频必填 | 视频文件或 URL |
| `images` | `(File \| string)[]` | 图文必填 | 图片文件或 URL 数组 |
| `cover` | `File \| string` | 小红书和视频号视频必填 | 封面文件或 URL |
| `topics` | `string[]` | 否 | 话题名称，不需要包含 `#` |
| `location` | `LocationInfo` | 否 | 位置；建议先调用对应平台的位置搜索方法 |
| `visibility` | `"public" \| "private" \| "friends"` | 否 | 可见范围；当前用于小红书 |
| `mentionedUsers` | `{ id: string; nickname: string }[]` | 否 | @用户；当前用于小红书 |
| `scheduledTime` | `number` | 否 | 定时发布时间，毫秒时间戳；按平台能力使用 |
| `platformConfig` | `object` | 否 | 平台扩展配置 |

`LocationInfo` 的结构：

```ts theme={null}
interface LocationInfo {
  id: string;
  name: string;
  poiType?: number;
  address?: string;
  simpleAddress?: string;
  latitude?: number;
  longitude?: number;
  cityCode?: string;
  cityName?: string;
}
```

小红书声明可放在 `platformConfig`：

```js theme={null}
platformConfig: {
  originalStatement: true,
  userDeclarationBind: {
    // 1=虚拟演绎，仅供娱乐；2=笔记含 AI 合成内容；3=内容包含营销广告
    origin: 2,
  },
}
```

### 平台参数差异

| 能力 | 小红书 | 微信视频号 | 抖音 |
| - | - | - | - |
| `type: "video"` | 支持，`video`、`cover` 必填 | 支持，`video`、`cover` 必填 | 支持，`video` 必填 |
| `type: "image"` | 支持，1～9 张 | 不支持 | 支持，1～35 张 |
| 话题 | 支持 | 支持，最多 10 个 | 支持，最多 5 个 |
| 位置搜索 | `xhsSearchLocation()` | `wxSphSearchLocation()` | `douyinSearchLocation()` |
| 定时发布 | 不建议使用 | 支持 | 支持 |
| 可见范围、@用户 | 支持 | 不支持 | 暂不支持 |

平台规则可能随平台调整。业务页面应把插件返回的参数校验或发布错误展示给用户，不要静默忽略。

## 发布进度回调

`onProgress` 每次接收一个 `ProgressEvent`：

| 字段 | 类型 | 说明 |
| - | - | - |
| `stage` | `"download" \| "upload" \| "publish" \| "complete" \| "error"` | 当前阶段 |
| `progress` | `number` | 进度百分比，范围 0～100 |
| `message` | `string`（可选） | 当前进度说明 |
| `data` | `object`（可选） | 当前阶段的附加数据 |
| `timestamp` | `number`（可选） | 事件时间戳 |

不同阶段的 `data`：

| `stage` | `data` 字段 |
| - | - |
| `download` | `loaded`、`total`、`speed?`，单位为字节，速度单位为 bytes/s |
| `upload` | `loaded`、`total`、`chunkIndex?`、`totalChunks?` |
| `publish` | `step`：`preparing`、`signing`、`submitting` 或 `verifying` |
| `complete` | `workId`、`shareLink?`、`platformData?` |
| `error` | `code?`、`error` |

## 发布返回值

`publish()` 完成后返回 `PublishResult`：

| 字段 | 类型 | 说明 |
| - | - | - |
| `success` | `boolean` | 是否发布成功 |
| `workId` | `string`（可选） | 平台作品 ID |
| `shareLink` | `string`（可选） | 可打开的作品链接；部分平台可能不会立即返回 |
| `publishTime` | `number`（可选） | 发布时间戳 |
| `failReason` | `string`（可选） | 失败原因 |
| `errorCode` | `string`（可选） | 失败错误码 |
| `platformData` | `unknown`（可选） | 平台扩展数据；使用前应做类型检查 |

通信失败、超时或平台错误也可能让 Promise 直接抛出异常。建议同时处理返回结果和 `catch`：

```js theme={null}
try {
  const result = await window.AIToEarnPlugin.publish(params, onProgress);

  if (!result.success) {
    showError(result.failReason || result.errorCode || "发布失败");
    return;
  }

  showSuccess(result.shareLink);
} catch (error) {
  showError(error.message || "插件调用失败");
}
```

## 位置与视频号辅助方法

### 搜索位置

```ts theme={null}
xhsSearchLocation({
  keyword?: string;
  latitude: number;
  longitude: number;
  page?: number;
  size?: number;
}): Promise<Array<{
  poiId: string;
  poiType?: number;
  name: string;
  address?: string;
  fullAddress?: string;
  cityName?: string;
  latitude?: number;
  longitude?: number;
}>>;

douyinSearchLocation({
  keyword?: string;
  latitude?: number;
  longitude?: number;
  cityCode?: string;
  cityName?: string;
  searchType?: 0 | 7;
  page?: number;
  count?: number;
}): Promise<{
  items: Array<{
    poiId: string;
    name: string;
    address?: string;
    cityCode?: string;
    cityName?: string;
    latitude?: number;
    longitude?: number;
    distance?: string;
    poiType?: number;
  }>;
  cities: Array<{ code: string; name: string; isDefault: boolean }>;
  hasMore: boolean;
  nextPage?: number;
}>;

wxSphSearchLocation({
  query?: string;
  longitude?: number;
  latitude?: number;
}): Promise<Array<{
  uid: string;
  name: string;
  longitude: number;
  latitude: number;
  address?: string;
  province?: string;
  city?: string;
  region?: string;
  fullAddress?: string;
  poiCheckSum?: string;
}>>;
```

位置选择后，将平台返回项映射到 `publish()` 的顶层 `location`。视频号发布如需保留 `poiCheckSum` 等专有字段，可将完整位置项映射到 `platformConfig.wxSph.poiInfo`。

### 视频号活动与作品链接

```ts theme={null}
wxSphSearchActivity({
  query: string;
}): Promise<Array<{
  eventTopicId: string;
  eventName: string;
  eventCreatorNickname?: string;
  eventAttendCount?: number;
}>>;

wxSphStartLinkPolling({
  recordId: string;
  mediaMd5sum: string;
  apiBaseUrl?: string;
  authToken?: string;
  accountId?: string;
  videoClipTaskId?: string;
  scheduledTime?: number;
}): Promise<{
  success: boolean;
  status?: "pending" | "ready" | "failed";
  error?: string;
  code?: string;
}>;
```

`wxSphSearchActivity()` 的选中结果可传入 `platformConfig.wxSph.event`。视频号发布结果没有立即提供作品链接时，可使用 `platformData` 中的 `mediaMd5sum` 等字段启动链接轮询；`platformData` 是平台扩展数据，读取前先验证字段是否存在。

## 互动方法

这些方法也在 `Authorize Only` 白名单内，但不是发布内容的必需步骤：

```ts theme={null}
douyinInteraction({
  action: "like" | "favorite" | "comment";
  workId: string;
  targetState: boolean;
  content?: string; // comment 时必填
}): Promise<{
  success: boolean;
  currentState?: boolean;
  message?: string;
  error?: string;
}>;

douyinDirectMessage({
  workId?: string; // 与 authorUrl 二选一
  authorUrl?: string;
  content: string;
}): Promise<{
  success: boolean;
  message?: string;
  error?: string;
}>;

unifiedInteraction({
  platform: "xhs" | "douyin";
  action: "like" | "favorite" | "comment";
  workLink: string;
  targetState: boolean;
  content?: string; // comment 时必填
  needScreenshot?: boolean;
}): Promise<{
  success: boolean;
  currentState?: boolean;
  message?: string;
  screenshot?: string;
  needHumanAssist?: boolean;
  verificationReason?: string;
  error?: string;
}>;
```

自动化互动可能遇到验证码或平台风控。`needHumanAssist: true` 时，应停止自动重试，并用 `verificationReason` 提示用户人工处理。

## 常见问题

* **未检测到 `window.AIToEarnPlugin`**：确认插件已安装、当前网页来源已加入可注入域名，然后刷新页面。未安装时请向用户展示本教程链接：`https://docs.aitoearn.ai/zh/use/plugin-publish`。
* **`Authorize Only` 后仍不能调用 `login()`**：确认使用的是包含 `login()` 白名单支持的最新插件版本，并在修改域名配置后刷新网页。
* **账号检查失败**：先在同一浏览器登录目标平台，再回到网页重新调用 `login(platform)`。
* **发布失败**：同时展示 Promise 异常、`failReason` 和 `errorCode`；再检查素材 URL、平台登录状态、内容类型及封面要求。
* **发布中没有立即拿到作品链接**：先使用 `workId` 保存结果。视频号可按上文使用 `wxSphStartLinkPolling()` 启动链接轮询。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.