openilink / openilink-sdk-php
PHP SDK for the OpenILink Bot API.
0.6.0
2026-04-06 04:36 UTC
Requires
- php: ^8.1
- ext-curl: *
- ext-json: *
- ext-openssl: *
README
微信 iLink Bot API 的 PHP SDK。
composer require openilink/openilink-sdk-php
特性
- 扫码登录,支持扫码/过期回调
- 长轮询消息监听,自动重试与退避,动态超时
- 主动推送(自动缓存
context_token) - 发送图片、视频、文件,MIME 自动路由
- CDN 加密上传/下载(AES-128-ECB)
- 语音消息解码(可插拔 SILK 解码器 + WAV 封装)
- 输入状态指示器、Bot 配置
- 可注入自定义 transport,便于测试或接入自有 HTTP 栈
- 结构化错误类型(
APIError、HTTPError、NoContextTokenException) - 轻量依赖,仅依赖 PHP 扩展
要求
- PHP 8.1+
ext-curlext-jsonext-openssl
快速开始
<?php declare(strict_types=1); require __DIR__ . '/vendor/autoload.php'; use OpenILink\Client; use OpenILink\MessageHelper; $client = new Client(''); $result = $client->loginWithQr([ 'on_qrcode' => static function (string $img): void { echo "请扫码:\n{$img}\n"; }, 'on_scanned' => static function (): void { echo "已扫码,请在微信中确认...\n"; }, ]); if (!($result['connected'] ?? false)) { throw new RuntimeException((string) ($result['message'] ?? '登录失败')); } echo '已连接 BotID=' . (string) ($result['bot_id'] ?? '') . PHP_EOL; $syncBufFile = __DIR__ . '/sync_buf.dat'; $savedBuf = is_file($syncBufFile) ? (string) file_get_contents($syncBufFile) : ''; $client->monitor( static function (array $message) use ($client): void { $text = MessageHelper::extractText($message); if ($text === '') { return; } $client->push((string) $message['from_user_id'], '收到: ' . $text); }, [ 'initial_buf' => $savedBuf, 'on_response' => static function (array $response): void { echo (string) ($response['sync_buf'] ?? '') . PHP_EOL; echo (string) ($response['raw_response']['status_code'] ?? 0) . PHP_EOL; }, 'on_buf_update' => static function (string $buf) use ($syncBufFile): void { file_put_contents($syncBufFile, $buf); }, ], );
API
创建客户端
use OpenILink\Client; $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', 'transport' => static function ( string $method, string $url, array $headers, ?string $body, int $timeoutMs, ): array { return [ 'status_code' => 200, 'body' => '{}', 'headers' => ['content-type' => 'application/json'], ]; }, 'silk_decoder' => static function (string $silkData, int $sampleRate): string { return decodeSilkSomehow($silkData, $sampleRate); }, ]);
扫码登录
$result = $client->loginWithQr([ 'on_qrcode' => static function (string $imgContent): void {}, 'on_scanned' => static function (): void {}, 'on_expired' => static function (int $attempt, int $max): void {}, ]);
登录成功后,客户端的 Token 和 BaseURL 会自动更新。
接收消息
use OpenILink\MessageHelper; $client->monitor( static function (array $message): void { $text = MessageHelper::extractText($message); // $message['from_user_id'], $message['context_token'], $message['item_list'] }, [ 'initial_buf' => $savedBuf, 'on_response' => static function (array $response): void {}, 'on_buf_update' => static function (string $buf): void {}, 'on_error' => static function (Throwable $error): void {}, 'on_session_expired' => static function (): void {}, 'should_continue' => static function (): bool { return true; }, ], );
monitor() 会自动缓存每个用户的 context_token,供 push() 使用。服务端返回的 longpolling_timeout_ms 会被自动采纳;成功响应里的 sync_buf 和原始 HTTP 元数据可通过 on_response / raw_response 读取。
发送文本
$client->sendText($userId, '你好', $contextToken); $client->push($userId, '这是一条定时通知');
发送媒体
use OpenILink\Constants; $data = file_get_contents('photo.jpg'); // 高级接口:自动识别 MIME 类型 -> 上传 -> 发送 $client->sendMediaFile($userId, $contextToken, $data, 'photo.jpg', '看看这张图'); // 分步操作:上传 -> 发送 $uploaded = $client->uploadFile($data, $userId, Constants::MEDIA_IMAGE); $client->sendImage($userId, $contextToken, $uploaded); $client->sendVideo($userId, $contextToken, $uploaded); $client->sendFileAttachment($userId, $contextToken, 'report.pdf', $uploaded);
下载媒体
use OpenILink\Constants; foreach (($message['item_list'] ?? []) as $item) { switch ($item['type'] ?? null) { case Constants::ITEM_TYPE_IMAGE: $data = $client->downloadMedia($item['image_item']['media'] ?? null); break; case Constants::ITEM_TYPE_VOICE: $wav = $client->downloadVoice($item['voice_item'] ?? null); break; } }
语音解码
SDK 通过可插拔的 silk_decoder 支持语音消息解码,保持对外部解码器的开放性:
use OpenILink\Client; use OpenILink\Voice; $client = new Client($token, [ 'silk_decoder' => static function (string $silkData, int $sampleRate): string { return decodeSilkSomehow($silkData, $sampleRate); }, ]); $wav = $client->downloadVoice($voiceItem);
也可以单独使用 WAV 封装:
$wav = Voice::buildWav($pcmBytes, 24000, 1, 16);
其他
use OpenILink\Constants; use OpenILink\MessageHelper; use OpenILink\Mime; $client->sendTyping($userId, $typingTicket, Constants::TYPING); $client->sendTyping($userId, $typingTicket, Constants::CANCEL_TYPING); $config = $client->getConfig($userId, $contextToken); $text = MessageHelper::extractText($message); $isMedia = MessageHelper::isMediaItem($item); $mime = Mime::mimeFromFilename('photo.jpg'); // image/jpeg $ext = Mime::extensionFromMime('image/jpg'); // .jpg $isImage = Mime::isImageMime('image/png'); // true $isVideo = Mime::isVideoMime('video/mp4'); // true
错误处理
use OpenILink\Exception\APIError; use OpenILink\Exception\HTTPError; use OpenILink\Exception\NoContextTokenException; use OpenILink\Exception\RequestException; try { $client->push($userId, 'hello'); } catch (APIError $error) { if ($error->isSessionExpired()) { // 需要重新登录 } } catch (HTTPError $error) { echo $error->getStatusCode(); } catch (NoContextTokenException $error) { // 该用户尚未发送过消息,无法主动推送 } catch (RequestException $error) { if ($error->isTimeout()) { // 请求超时 } }
常量
use OpenILink\Constants; Constants::MEDIA_IMAGE; // 1 Constants::MEDIA_VIDEO; // 2 Constants::MEDIA_FILE; // 3 Constants::MEDIA_VOICE; // 4 Constants::MESSAGE_TYPE_USER; // 1 Constants::MESSAGE_TYPE_BOT; // 2 Constants::ITEM_TYPE_TEXT; // 1 Constants::ITEM_TYPE_IMAGE; // 2 Constants::ITEM_TYPE_VOICE; // 3 Constants::ITEM_TYPE_FILE; // 4 Constants::ITEM_TYPE_VIDEO; // 5 Constants::MESSAGE_STATE_NEW; // 0 Constants::MESSAGE_STATE_GENERATING; // 1 Constants::MESSAGE_STATE_FINISH; // 2
许可证
MIT