Skip to content

openilink/openilink-sdk-node

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@openilink/openilink-sdk-node

微信 iLink Bot API 的 Node.js SDK。

npm install @openilink/openilink-sdk-node

特性

  • 扫码登录,支持扫码/过期回调
  • 长轮询消息监听,自动重试与退避,动态超时
  • 主动推送(自动缓存 context_token
  • 发送图片、视频、文件,MIME 自动路由
  • CDN 加密上传/下载(AES-128-ECB)
  • 语音消息解码(可插拔 SILK 解码器 + WAV 封装)
  • 输入状态指示器、Bot 配置
  • 可注入自定义 fetch 实现,便于测试、代理或自定义 Agent
  • fetch + AbortController 传输层,便于在 Node 18+ 环境直接使用
  • 结构化错误类型(APIErrorHTTPErrorNoContextTokenErrorRequestError
  • 零运行时依赖

要求

  • Node.js 18+

快速开始

import { Client, extractText } from "@openilink/openilink-sdk-node";
import { readFileSync, writeFileSync } from "node:fs";

const client = new Client("");

const result = await client.loginWithQr({
  on_qrcode: (img) => {
    console.log(`请扫码:\n${img}`);
  },
  on_scanned: () => {
    console.log("已扫码,请在微信中确认...");
  },
});

if (!result.connected) {
  throw new Error(result.message);
}

console.log(`已连接 BotID=${result.bot_id ?? ""}`);

const syncBufFile = new URL("./sync_buf.dat", import.meta.url);
let savedBuf = "";

try {
  savedBuf = readFileSync(syncBufFile, "utf8");
} catch {}

await client.monitor(
  async (message) => {
    const text = extractText(message);
    if (!text) {
      return;
    }

    await client.push(String(message.from_user_id), `收到: ${text}`);
  },
  {
    initial_buf: savedBuf,
    on_response: (response) => {
      console.log(response.sync_buf ?? "");
      console.log(response.raw_response?.status_code ?? 0);
    },
    on_buf_update: (buf) => {
      writeFileSync(syncBufFile, buf);
    },
  },
);

API

创建客户端

import { Client } from "@openilink/openilink-sdk-node";

const client = new Client(token, {
  base_url: "https://custom.endpoint.com",
  cdn_base_url: "https://custom.cdn.com/c2c",
  bot_type: "3",
  version: "2.1.6",
  route_tag: "my-route-tag",
  fetch_impl: async (input, init) => {
    return fetch(input, init);
  },
  silk_decoder: async (silkData, sampleRate) => {
    return decodeSilkSomehow(silkData, sampleRate);
  },
});

扫码登录

const result = await client.loginWithQr({
  on_qrcode: (imgContent) => {},
  on_scanned: () => {},
  on_expired: (attempt, max) => {},
});

登录成功后,客户端的 token 和 base_url 会自动更新。

接收消息

import { extractText } from "@openilink/openilink-sdk-node";

await client.monitor(
  async (message) => {
    const text = extractText(message);
    // message.from_user_id, message.context_token, message.item_list
  },
  {
    initial_buf: savedBuf,
    on_response: (response) => {},
    on_buf_update: (buf) => {},
    on_error: (error) => {},
    on_session_expired: () => {},
    should_continue: () => true,
  },
);

monitor() 会自动缓存每个用户的 context_token,供 push() 使用。服务端返回的 longpolling_timeout_ms 会被自动采纳;成功响应里的 sync_buf 和原始 HTTP 元数据可通过 on_response / raw_response 读取。

发送文本

await client.sendText(userId, "你好", contextToken);
await client.push(userId, "这是一条定时通知");

发送媒体

import { MEDIA_IMAGE } from "@openilink/openilink-sdk-node";
import { readFileSync } from "node:fs";

const data = readFileSync("photo.jpg");

// 高级接口:自动识别 MIME 类型 -> 上传 -> 发送
await client.sendMediaFile(userId, contextToken, data, "photo.jpg", "看看这张图");

// 分步操作:上传 -> 发送
const uploaded = await client.uploadFile(data, userId, MEDIA_IMAGE);
await client.sendImage(userId, contextToken, uploaded);
await client.sendVideo(userId, contextToken, uploaded);
await client.sendFileAttachment(userId, contextToken, "report.pdf", uploaded);

下载媒体

import { ITEM_TYPE_IMAGE, ITEM_TYPE_VOICE } from "@openilink/openilink-sdk-node";

for (const item of message.item_list ?? []) {
  switch (item.type) {
    case ITEM_TYPE_IMAGE:
      await client.downloadMedia(item.image_item?.media);
      break;

    case ITEM_TYPE_VOICE:
      await client.downloadVoice(item.voice_item);
      break;
  }
}

语音解码

SDK 通过可插拔的 silk_decoder 支持语音消息解码,保持对外部解码器的开放性:

import { buildWAV, Client } from "@openilink/openilink-sdk-node";

const client = new Client(token, {
  silk_decoder: async (silkData, sampleRate) => {
    return decodeSilkSomehow(silkData, sampleRate);
  },
});

const wav = await client.downloadVoice(voiceItem);

也可以单独使用 WAV 封装:

const wav = buildWAV(pcmBytes, 24_000, 1, 16);

其他

import {
  CANCEL_TYPING,
  TYPING,
  ExtensionFromMIME,
  MIMEFromFilename,
  extractText,
  isImageMIME,
  isMediaItem,
  isVideoMIME,
} from "@openilink/openilink-sdk-node";
await client.sendTyping(userId, typingTicket, TYPING);
await client.sendTyping(userId, typingTicket, CANCEL_TYPING);

const config = await client.getConfig(userId, contextToken);

const text = extractText(message);
const media = isMediaItem(message.item_list?.[0]);

const mime = MIMEFromFilename("photo.jpg");     // image/jpeg
const ext = ExtensionFromMIME("image/jpg");     // .jpg
const image = isImageMIME("image/png");         // true
const video = isVideoMIME("video/mp4");         // true

错误处理

import {
  APIError,
  HTTPError,
  NoContextTokenError,
  RequestError,
} from "@openilink/openilink-sdk-node";

try {
  await client.push(userId, "hello");
} catch (error) {
  if (error instanceof APIError && error.isSessionExpired()) {
    // 需要重新登录
  } else if (error instanceof HTTPError) {
    console.log(error.statusCode);
  } else if (error instanceof NoContextTokenError) {
    // 该用户尚未发送过消息,无法主动推送
  } else if (error instanceof RequestError && error.isTimeout()) {
    // 请求超时
  }
}

常量

import {
  ITEM_TYPE_FILE,
  ITEM_TYPE_IMAGE,
  ITEM_TYPE_TEXT,
  ITEM_TYPE_VIDEO,
  ITEM_TYPE_VOICE,
  MEDIA_FILE,
  MEDIA_IMAGE,
  MEDIA_VIDEO,
  MEDIA_VOICE,
  MESSAGE_STATE_FINISH,
  MESSAGE_STATE_GENERATING,
  MESSAGE_STATE_NEW,
  MESSAGE_TYPE_BOT,
  MESSAGE_TYPE_USER,
} from "@openilink/openilink-sdk-node";

MEDIA_IMAGE;              // 1
MEDIA_VIDEO;              // 2
MEDIA_FILE;               // 3
MEDIA_VOICE;              // 4

MESSAGE_TYPE_USER;        // 1
MESSAGE_TYPE_BOT;         // 2

ITEM_TYPE_TEXT;           // 1
ITEM_TYPE_IMAGE;          // 2
ITEM_TYPE_VOICE;          // 3
ITEM_TYPE_FILE;           // 4
ITEM_TYPE_VIDEO;          // 5

MESSAGE_STATE_NEW;        // 0
MESSAGE_STATE_GENERATING; // 1
MESSAGE_STATE_FINISH;     // 2

许可证

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors