Files
zhamao-framework/src/ZM/Utils/OneBot12FileUploader.php
2023-01-04 23:21:41 +08:00

129 lines
4.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace ZM\Utils;
use OneBot\V12\Object\ActionResponse;
use ZM\Context\BotContext;
/**
* 一个支持 OneBot12 标准的文件传输器之上传部分
*/
class OneBot12FileUploader
{
private string $err = '';
/**
* 构建一个文件上传器
*
* @param BotContext $ctx 机器人上下文,用于调用发送动作
* @param int $buffer_size 分片传输的大小,默认为 65536 字节,建议调整小于 2MB
*/
public function __construct(private BotContext $ctx, private int $buffer_size = 1048576)
{
}
/**
* 通过文件内容上传一个文件
* 当上传失败时返回 False上传成功则返回 file_id
*
* @param string $filename 要上传的文件名
* @param string $content 要上传的文件内容
* @return bool|string 上传失败返回 false可通过 getErr() 获取错误信息。上传成功返回 file_id
* @throws \Throwable
*/
public function uploadFromString(string $filename, string $content): bool|string
{
logger()->info('Uploading file, size: ' . strlen($content));
$size = strlen($content);
$offset = 0;
// 文件本身小于分片大小,直接一个包发送
if ($size <= $this->buffer_size) {
$obj = $this->ctx->sendAction('upload_file', [
'type' => 'data',
'name' => $filename,
'data' => base64_encode($content),
'sha256' => hash('sha256', $content),
]);
if (!$obj instanceof ActionResponse) {
$this->err = 'prepare stage returns an non-response object';
return false;
}
if ($obj->retcode !== 0) {
$this->err = 'prepare stage returns an error: ' . $obj->retcode;
return false;
}
return $obj->data['file_id'];
}
// 其他情况,使用分片的方式发送,依次调用 prepare, transfer, finish
$obj = $this->ctx->sendAction('upload_file_fragmented', [
'stage' => 'prepare',
'name' => $filename,
'total_size' => strlen($content),
]);
if (!$obj instanceof ActionResponse) {
$this->err = 'prepare stage returns an non-response object';
return false;
}
if ($obj->retcode !== 0) {
$this->err = 'prepare stage returns an error: ' . $obj->retcode;
return false;
}
$file_id = $obj->data['file_id'];
while ($offset < $size) {
$data = substr($content, $offset, $this->buffer_size);
$this->ctx->sendAction('upload_file_fragmented', [
'stage' => 'transfer',
'file_id' => $file_id,
'offset' => $offset,
'data' => base64_encode($data),
]);
if (strlen($data) < $this->buffer_size) {
break;
}
$offset += $this->buffer_size;
}
$final_obj = $this->ctx->sendAction('upload_file_fragmented', [
'stage' => 'finish',
'file_id' => $file_id,
'sha256' => hash('sha256', $content),
]);
if (!$final_obj instanceof ActionResponse) {
$this->err = 'finish error with non-object';
return false;
}
if ($final_obj->retcode !== 0) {
$this->err = 'finish error: ' . $final_obj->retcode;
return false;
}
return $final_obj->data['file_id'];
}
/**
* 从本地路径上传一个文件
*
* @throws \Throwable
*/
public function uploadFromPath(string $file_path): bool|string
{
if (file_exists($file_path)) {
$name = pathinfo($file_path, PATHINFO_BASENAME);
$content = file_get_contents($file_path);
return $this->uploadFromString($name, $content);
}
$this->err = 'File from path ' . $file_path . ' does not exist.';
return false;
}
/**
* 获取错误消息
*/
public function getErr(): string
{
return $this->err;
}
}