Skip to content

openilink/openilink-sdk-php

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

openilink-sdk-php

微信 iLink Bot API 的 PHP SDK。

composer require openilink/openilink-sdk-php

特性

  • 扫码登录,支持扫码/过期回调
  • 长轮询消息监听,自动重试与退避,动态超时
  • 主动推送(自动缓存 context_token
  • 发送图片、视频、文件,MIME 自动路由
  • CDN 加密上传/下载(AES-128-ECB)
  • 语音消息解码(可插拔 SILK 解码器 + WAV 封装)
  • 输入状态指示器、Bot 配置
  • 可注入自定义 transport,便于测试或接入自有 HTTP 栈
  • 结构化错误类型(APIErrorHTTPErrorNoContextTokenException
  • 轻量依赖,仅依赖 PHP 扩展

要求

  • PHP 8.1+
  • ext-curl
  • ext-json
  • ext-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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages