@w-xuefeng/cfib 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -0
- package/README.md +102 -0
- package/bun.lock +40 -0
- package/package.json +23 -0
- package/skills/random/SKILL.md +48 -0
- package/skills/remove/SKILL.md +48 -0
- package/skills/upload/SKILL.md +45 -0
- package/src/image-bed.ts +194 -0
- package/src/index.ts +216 -0
- package/src/utils.ts +83 -0
- package/test/banner.png +0 -0
- package/test/index.test.ts +82 -0
- package/tsconfig.json +24 -0
package/.editorconfig
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# CFIB - Cloudflare Image Bed CLI & Agent Skills
|
|
2
|
+
|
|
3
|
+
> 💡 **特别说明与致敬**
|
|
4
|
+
>
|
|
5
|
+
> 本项目是对优秀的开源图床解决方案 **[CloudFlare-ImgBed](https://github.com/MarSeventh/CloudFlare-ImgBed)** 的生态扩展。十分感谢作者为大家提供如此出色的服务级支持!在此对原项目致以最诚挚的敬意。
|
|
6
|
+
|
|
7
|
+
**CFIB** 是原项目 **[CloudFlare-ImgBed](https://github.com/MarSeventh/CloudFlare-ImgBed)** 的一个由 [Bun](https://bun.sh/) 驱动的高效命令行工具实现,并且提供了 AI Agent Skills 集合,用于管理和操作搭建在 Cloudflare 上的图床资源。
|
|
8
|
+
|
|
9
|
+
通过此项目,你可以在终端中快速实现图片的上传、远程文件的删除、随机图片的获取,同时它还在 `skills/` 目录下提供了标准化的 Agent Skills 描述文件,方便外部 AI Agent 直接学习并接入这些能力。
|
|
10
|
+
|
|
11
|
+
## 📦 安装与使用
|
|
12
|
+
|
|
13
|
+
确保您的环境中已安装 [Bun](https://bun.sh/) 运行时。
|
|
14
|
+
|
|
15
|
+
你可以通过以下两种方式之一来使用本项目:
|
|
16
|
+
|
|
17
|
+
### 选项 A:全局安装 (推荐)
|
|
18
|
+
|
|
19
|
+
一键全局安装,方便你在系统的任意位置直接使用 `cfib` 唤起命令:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun install @w-xuefeng/cfib -g
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
安装成功后即可通过 `cfib -h` 查看帮助菜单。
|
|
26
|
+
|
|
27
|
+
### 选项 B:免安装立即调用 (bunx)
|
|
28
|
+
|
|
29
|
+
如果你不想进行全局安装,也可以利用 `bunx` 直接动态执行:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bunx @w-xuefeng/cfib -h
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. 环境配置
|
|
36
|
+
|
|
37
|
+
所有底层服务调用均需绑定目标图床地址和对应的凭证密钥。你可以通过创建 `.env` 文件或在每次使用命令时直接通过长参数(配合环境变量)覆盖。
|
|
38
|
+
|
|
39
|
+
**支持的全局参数(同时支持对应的环境变量):**
|
|
40
|
+
- `--origin` | `IB_ORIGIN` **(必配)**:图床服务的根 URL。
|
|
41
|
+
- `--upload-auth-code` | `IB_UPLOAD_AUTH_CODE` **(上传/删除 必配)**:访问及调用特权的身份验证 Cookie / Header 码。
|
|
42
|
+
- `--upload-token` | `IB_UPLOAD_TOKEN`:上传文件专用的 Token。
|
|
43
|
+
- `--delete-token` | `IB_DELETE_TOKEN`:删除远端资源的 Token。
|
|
44
|
+
- `--trace`:自定义日志与跨服务追踪的 `Trace-Id`。
|
|
45
|
+
- `--lang`:请求支持的 `Accept-Language`。
|
|
46
|
+
- `--log` / `--runtime-path` / `--log-root`:本地日志存储的路径设定。
|
|
47
|
+
|
|
48
|
+
## 🛠 功能介绍
|
|
49
|
+
|
|
50
|
+
获取详细介绍,通过执行:
|
|
51
|
+
```bash
|
|
52
|
+
cfib -h
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 1. 图片上传 (`upload`)
|
|
56
|
+
|
|
57
|
+
将本地文件流式上传至服务并直接从云端返回可用地址。支持定义高级上传设置如服务器压缩状态、存储频道(例如 telegram, S3 等)。
|
|
58
|
+
|
|
59
|
+
**用法:**
|
|
60
|
+
```bash
|
|
61
|
+
cfib upload <file> [options]
|
|
62
|
+
```
|
|
63
|
+
**示例:**
|
|
64
|
+
```bash
|
|
65
|
+
cfib upload ./my-picture.png --origin "https://example.com" --upload-auth-code "xxxxxxxx"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. 资源删除 (`remove`)
|
|
69
|
+
|
|
70
|
+
移除图床中已保存的文件,或者配合 `--folder` 直接移除指定的文件夹。
|
|
71
|
+
|
|
72
|
+
**用法:**
|
|
73
|
+
```bash
|
|
74
|
+
cfib remove <path> [options]
|
|
75
|
+
```
|
|
76
|
+
**示例:**
|
|
77
|
+
```bash
|
|
78
|
+
cfib remove "images/delete_this.png" --origin "https://example.com" --upload-auth-code "xxxxxxxx"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. 获取随机图 (`random`)
|
|
82
|
+
|
|
83
|
+
自图床中抽取一张随机媒体。既可以返回其对应的元数据(JSON),也可以通过 `--type img` 开启流响应将二进制内容直接落盘成你指定的本地实体图片:
|
|
84
|
+
|
|
85
|
+
**用法:**
|
|
86
|
+
```bash
|
|
87
|
+
cfib random [dest] [options]
|
|
88
|
+
```
|
|
89
|
+
**示例(直接下载):**
|
|
90
|
+
```bash
|
|
91
|
+
cfib random output.jpg --type img --origin "https://example.com"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 🤖 关于 AI Agent Skills
|
|
95
|
+
|
|
96
|
+
在项目的 `skills/` 文件夹下包含了可以直接被 AI Agent(例如 Claude, AutoGPT 工具链)解析吸收的能力配置清单(`SKILL.md`)。
|
|
97
|
+
|
|
98
|
+
这些说明文档内置了完整的调用说明,AI 助手在读取这些配置后便能安全、无障碍地为你执行上述所列的图床业务。
|
|
99
|
+
|
|
100
|
+
- `skills/upload/SKILL.md`
|
|
101
|
+
- `skills/remove/SKILL.md`
|
|
102
|
+
- `skills/random/SKILL.md`
|
package/bun.lock
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "cloudflare-image-bed-skills",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@w-xuefeng/bkit": "^0.0.5",
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/bun": "latest",
|
|
12
|
+
"local-diamond": "^0.0.3",
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"typescript": "^5",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
"packages": {
|
|
20
|
+
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
|
21
|
+
|
|
22
|
+
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
|
23
|
+
|
|
24
|
+
"@w-xuefeng/bkit": ["@w-xuefeng/bkit@0.0.5", "", { "dependencies": { "js-base64": "^3.7.8" }, "peerDependencies": { "typescript": "^5" } }, "sha512-r34CUMKuCHMeSqVVZWN0lKBeuAGXvftPk3JMfqtBPgumNkXYXx50xdNhE9JpcF5FXcLMXi1hUEUlBOgIxuml3A=="],
|
|
25
|
+
|
|
26
|
+
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
|
27
|
+
|
|
28
|
+
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
|
29
|
+
|
|
30
|
+
"hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
|
31
|
+
|
|
32
|
+
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
|
|
33
|
+
|
|
34
|
+
"local-diamond": ["local-diamond@0.0.3", "", { "dependencies": { "commander": "^14.0.3", "hono": "^4.12.2" }, "peerDependencies": { "typescript": "^5" }, "bin": { "lod": "src/bin.ts" } }, "sha512-xqia8ihwisnyRyxkQHekaS8xVElqYn7Pxb87s7OVexxb3EzmfIHxWflEC1ZwP7bvYkP01H5q8N2318W0e1s5pA=="],
|
|
35
|
+
|
|
36
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
37
|
+
|
|
38
|
+
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@w-xuefeng/cfib",
|
|
3
|
+
"description": "cloudflare-image-bed CLI tools and Agent Skills",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"module": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"cfib": "src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "bun test/index.test.ts"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/bun": "latest",
|
|
15
|
+
"local-diamond": "^0.0.3"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"typescript": "^5"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@w-xuefeng/bkit": "^0.0.5"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Get a random image from the Cloudflare Image Bed
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Random Image Skill
|
|
6
|
+
|
|
7
|
+
This tool fetches a random file or image from the Cloudflare Image Bed server. It can either return the JSON metadata or download the actual file.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use the `run_command` tool to execute the cfib script. If the package is installed globally, you can use `cfib`. Otherwise, use `bunx cfib` or `npx cfib`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cfib random [options] [local_save_path]
|
|
15
|
+
# OR
|
|
16
|
+
bunx @w-xuefeng/cfib random [options] [local_save_path]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Options (All Optional):
|
|
20
|
+
- `[local_save_path]`: If you specify `--type img`, you must provide a local filepath to save the binary image data (defaults to `random-image.jpg`).
|
|
21
|
+
- `--type <path|img>`: (default: path) If set to `img`, returns and saves the file directly. If set to `path`, returns JSON info.
|
|
22
|
+
- `--content <image|video>`: (default: image) The type of content to filter.
|
|
23
|
+
- `--form <json|text>`: (default: json) The response data format.
|
|
24
|
+
- `--dir <path>`: Filter random files only within a specific server directory.
|
|
25
|
+
- `--orientation <landscape|portrait|square|auto>`: Filter by image orientation.
|
|
26
|
+
|
|
27
|
+
### Global Required Options:
|
|
28
|
+
- `--origin <url>`: The origin URL of the image bed server.
|
|
29
|
+
|
|
30
|
+
### Global Optional Options:
|
|
31
|
+
- `--trace <id>`: Set an explicit Trace-Id.
|
|
32
|
+
- `--lang <language>`: Set an explicit Accept-Language.
|
|
33
|
+
- `--log <true|false>`: Enable or disable logging.
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
Get JSON metadata of a random image:
|
|
38
|
+
```bash
|
|
39
|
+
cfib random --origin "https://my-image-bed.com"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Download a random image directly to `test.jpg`:
|
|
43
|
+
```bash
|
|
44
|
+
cfib random test.jpg --type img --origin "https://my-image-bed.com"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Success Criteria
|
|
48
|
+
Normally outputs JSON metadata of the file. If `--type img` is used, the image is saved to the provided local path and outputs `Image saved to <path>`.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Remove an image or folder from the Cloudflare Image Bed
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Remove Image or Folder Skill
|
|
6
|
+
|
|
7
|
+
This tool allows you to delete an existing file or directory from the Cloudflare Image Bed server.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use the `run_command` tool to execute the cfib script. If the package is installed globally, you can use `cfib`. Otherwise, use `bunx cfib` or `npx cfib`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cfib remove <remote_path> [options]
|
|
15
|
+
# OR
|
|
16
|
+
bunx @w-xuefeng/cfib remove <remote_path> [options]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Required Arguments:
|
|
20
|
+
- `<remote_path>`: The path on the server referencing the file or folder.
|
|
21
|
+
|
|
22
|
+
### Global Required Options:
|
|
23
|
+
- `--origin <url>`: The origin URL of the image bed server.
|
|
24
|
+
- `--upload-auth-code <code>`: The auth code cookie value.
|
|
25
|
+
|
|
26
|
+
### Global Optional Options:
|
|
27
|
+
- `--delete-token <token>`: The authorization token required for deletion.
|
|
28
|
+
- `--trace <id>`: Set an explicit Trace-Id.
|
|
29
|
+
- `--lang <language>`: Set an explicit Accept-Language.
|
|
30
|
+
- `--log <true|false>`: Enable or disable logging.
|
|
31
|
+
|
|
32
|
+
### Options (All Optional):
|
|
33
|
+
- `--folder`: Pass this flag if the `<remote_path>` is a folder and you want to delete the whole folder instead of a target file.
|
|
34
|
+
|
|
35
|
+
## Example
|
|
36
|
+
|
|
37
|
+
Delete a single file:
|
|
38
|
+
```bash
|
|
39
|
+
cfib remove "12345.png" --origin "https://my-image-bed.com" --upload-auth-code "my-auth-code" --delete-token "my-secret-token"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Delete a full folder:
|
|
43
|
+
```bash
|
|
44
|
+
cfib remove "my_folder_name" --folder --origin "https://my-image-bed.com" --upload-auth-code "my-auth-code"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Success Criteria
|
|
48
|
+
The command outputs a JSON response with `success: true` when the file or folder is successfully deleted.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Upload a local image to the Cloudflare Image Bed
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Upload Image Skill
|
|
6
|
+
|
|
7
|
+
This tool allows you to upload a local generic file or image to a Cloudflare Image Bed instance.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use the `run_command` tool to execute the cfib script. If the package is installed globally, you can use `cfib`. Otherwise, use `bunx cfib` or `npx cfib`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cfib upload <local_filepath> [options]
|
|
15
|
+
# OR
|
|
16
|
+
bunx @w-xuefeng/cfib upload <local_filepath> [options]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Required Arguments:
|
|
20
|
+
- `<local_filepath>`: The absolute or relative path to the local file you wish to upload.
|
|
21
|
+
|
|
22
|
+
### Global Required Options:
|
|
23
|
+
- `--origin <url>`: The origin URL of the image bed server.
|
|
24
|
+
- `--upload-auth-code <code>`: The auth code cookie value to bypass authentication.
|
|
25
|
+
|
|
26
|
+
### Global Optional Environment & Tracing Options:
|
|
27
|
+
- `--upload-token <token>`: The authorization token for uploading.
|
|
28
|
+
- `--trace <id>`: Set an explicit Trace-Id.
|
|
29
|
+
- `--lang <language>`: Set an explicit Accept-Language.
|
|
30
|
+
- `--log <true|false>`: Enable or disable logging.
|
|
31
|
+
|
|
32
|
+
### Upload Specific Options (All Optional):
|
|
33
|
+
- `--server-compress <true|false>`: Whether the server should compress the image.
|
|
34
|
+
- `--upload-channel <channel>`: The channel to use (telegram, cfr2, s3, discord, huggingface).
|
|
35
|
+
- `--upload-folder <folder>`: A specific folder on the server to upload the file to.
|
|
36
|
+
- `--auth-code <code>`: Specific authCode for upload.
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cfib upload /path/to/my/image.png --origin "https://my-image-bed.com" --upload-auth-code "my-auth-code" --upload-token "my-token"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Success Criteria
|
|
45
|
+
The command outputs a JSON response. A successful upload will contain a `success: true` and the `data.src` / `data.url` pointing to the uploaded file.
|
package/src/image-bed.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { pathJoin, R } from "@w-xuefeng/bkit";
|
|
2
|
+
import { type Context, filterEmptyField, logger } from "./utils";
|
|
3
|
+
|
|
4
|
+
export interface IBUploadOptions {
|
|
5
|
+
authCode?: string;
|
|
6
|
+
serverCompress?: "true" | "false";
|
|
7
|
+
uploadChannel?: "telegram" | "cfr2" | "s3" | "discord" | "huggingface";
|
|
8
|
+
channelName?: string;
|
|
9
|
+
autoRetry?: "true" | "false";
|
|
10
|
+
uploadNameType?: "default" | "index" | "origin" | "short";
|
|
11
|
+
returnFormat?: "default" | "full";
|
|
12
|
+
uploadFolder?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IBRandomOptions {
|
|
16
|
+
/**
|
|
17
|
+
* @default "image"
|
|
18
|
+
* 文件类型过滤,可选值有 [image, video],多个使用 , 分隔
|
|
19
|
+
*/
|
|
20
|
+
content?: "image" | "vidoe" | "image, video";
|
|
21
|
+
/**
|
|
22
|
+
* @default "path"
|
|
23
|
+
* 返回内容类型,设为 img 时直接返回图片(此时 form 不生效),设为 url 时返回完整 url 链接
|
|
24
|
+
*/
|
|
25
|
+
type?: "path" | "img";
|
|
26
|
+
/**
|
|
27
|
+
* @default "json"
|
|
28
|
+
* 响应格式,设为 text 时直接返回文本
|
|
29
|
+
*/
|
|
30
|
+
form?: "json" | "text";
|
|
31
|
+
/**
|
|
32
|
+
* 指定目录,使用相对路径,例如 img/test 会返回该目录以及所有子目录下的文件
|
|
33
|
+
*/
|
|
34
|
+
dir?: string;
|
|
35
|
+
/**
|
|
36
|
+
* 图片方向筛选,可选值:landscape(横图)、portrait(竖图)、square(方图)、auto(自适应设备方向)
|
|
37
|
+
*/
|
|
38
|
+
orientation?: "landscape" | "portrait" | "square" | "auto";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type IBUploadResponse = { src: string }[];
|
|
42
|
+
|
|
43
|
+
interface IBDeleteSingleFileResponse {
|
|
44
|
+
success: true;
|
|
45
|
+
fileId: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface IBDeleteFolderResponse {
|
|
49
|
+
success: true;
|
|
50
|
+
deleted: string[];
|
|
51
|
+
failed: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface IBDeleteFolderErrorResponse {
|
|
55
|
+
success: false;
|
|
56
|
+
error: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type IBDeleteResponse =
|
|
60
|
+
| IBDeleteSingleFileResponse
|
|
61
|
+
| IBDeleteFolderResponse
|
|
62
|
+
| IBDeleteFolderErrorResponse;
|
|
63
|
+
|
|
64
|
+
export function upload(c: Context, file: File, options?: IBUploadOptions) {
|
|
65
|
+
const traceId = c.headers.get("Trace-Id");
|
|
66
|
+
const formData = new FormData();
|
|
67
|
+
formData.append("file", file);
|
|
68
|
+
const searchParams = new URLSearchParams(filterEmptyField({
|
|
69
|
+
uploadChannel: "telegram",
|
|
70
|
+
serverCompress: "true",
|
|
71
|
+
uploadNameType: "default",
|
|
72
|
+
returnFormat: "default",
|
|
73
|
+
autoRetry: "false",
|
|
74
|
+
...options,
|
|
75
|
+
}));
|
|
76
|
+
const url = `${Bun.env.IB_ORIGIN}/upload?${searchParams.toString()}`;
|
|
77
|
+
logger.info(
|
|
78
|
+
`[trace-id: ${traceId}][image-bed upload params]: POST ${url} formData::[file:${file.name} ${file.size} ${file.type}]`,
|
|
79
|
+
);
|
|
80
|
+
return fetch(
|
|
81
|
+
url,
|
|
82
|
+
{
|
|
83
|
+
method: "POST",
|
|
84
|
+
body: formData,
|
|
85
|
+
headers: {
|
|
86
|
+
cookie: `authCode=${String(Bun.env.IB_UPLOAD_AUTH_CODE)}`,
|
|
87
|
+
authCode: String(Bun.env.IB_UPLOAD_AUTH_CODE),
|
|
88
|
+
referer: String(Bun.env.IB_ORIGIN),
|
|
89
|
+
Authorization: `Bearer ${Bun.env.IB_UPLOAD_TOKEN}`,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
).then((rs) => rs.json()).then((rs) => {
|
|
93
|
+
logger.info(
|
|
94
|
+
`[trace-id: ${traceId}][image-bed upload result]: ${JSON.stringify(rs)}`,
|
|
95
|
+
);
|
|
96
|
+
if (
|
|
97
|
+
!rs || !(rs as IBUploadResponse).length ||
|
|
98
|
+
!(rs as IBUploadResponse)[0]?.src
|
|
99
|
+
) {
|
|
100
|
+
return R.unifail("FILE_UPLOAD_FAIL", rs, c.headers);
|
|
101
|
+
}
|
|
102
|
+
return R.unisuccess({
|
|
103
|
+
src: (rs as IBUploadResponse)[0]!.src,
|
|
104
|
+
url: pathJoin(
|
|
105
|
+
String(Bun.env.IB_ORIGIN),
|
|
106
|
+
(rs as IBUploadResponse)[0]!.src,
|
|
107
|
+
),
|
|
108
|
+
}, c.headers);
|
|
109
|
+
}, (error) => {
|
|
110
|
+
logger.error(
|
|
111
|
+
`[trace-id: ${traceId}][image-bed upload error]: ${
|
|
112
|
+
JSON.stringify(error)
|
|
113
|
+
}`,
|
|
114
|
+
);
|
|
115
|
+
return R.unifail("SERVER_ERROR", error, c.headers);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function remove(c: Context, path: string, folder: boolean = false) {
|
|
120
|
+
const traceId = c.headers?.get("Trace-Id");
|
|
121
|
+
if (path.startsWith(`${Bun.env.IB_ORIGIN}/`)) {
|
|
122
|
+
path = path.replace(`${Bun.env.IB_ORIGIN}/`, "");
|
|
123
|
+
}
|
|
124
|
+
if (path.startsWith("file/")) {
|
|
125
|
+
path = path.replace("file/", "");
|
|
126
|
+
}
|
|
127
|
+
if (path.startsWith("/file/")) {
|
|
128
|
+
path = path.replace("/file/", "");
|
|
129
|
+
}
|
|
130
|
+
return fetch(
|
|
131
|
+
`${Bun.env.IB_ORIGIN}/api/manage/delete/${path}${
|
|
132
|
+
folder ? "?folder=true" : ""
|
|
133
|
+
}`,
|
|
134
|
+
{
|
|
135
|
+
method: "DELETE",
|
|
136
|
+
headers: {
|
|
137
|
+
cookie: `authCode=${String(Bun.env.IB_UPLOAD_AUTH_CODE)}`,
|
|
138
|
+
authCode: String(Bun.env.IB_UPLOAD_AUTH_CODE),
|
|
139
|
+
referer: String(Bun.env.IB_ORIGIN),
|
|
140
|
+
Authorization: `Bearer ${Bun.env.IB_DELETE_TOKEN}`,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
).then((rs) => rs.json()).then((rs) => {
|
|
144
|
+
logger.info(
|
|
145
|
+
`[trace-id: ${traceId}][image-bed remove result]: ${JSON.stringify(rs)}`,
|
|
146
|
+
);
|
|
147
|
+
if (!rs || !(rs as IBDeleteResponse).success) {
|
|
148
|
+
return R.unifail("REQ_EXCEPTION", rs, c.headers);
|
|
149
|
+
}
|
|
150
|
+
return R.unisuccess(rs as IBDeleteResponse, c.headers);
|
|
151
|
+
}, (error) => {
|
|
152
|
+
logger.error(
|
|
153
|
+
`[trace-id: ${traceId}][image-bed remove error]: ${
|
|
154
|
+
JSON.stringify(error)
|
|
155
|
+
}`,
|
|
156
|
+
);
|
|
157
|
+
return R.unifail("SERVER_ERROR", error, c.headers);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function random(c: Context, options?: IBRandomOptions) {
|
|
162
|
+
const traceId = c.headers.get("Trace-Id");
|
|
163
|
+
const searchParams = new URLSearchParams(filterEmptyField({
|
|
164
|
+
content: "image",
|
|
165
|
+
type: "path",
|
|
166
|
+
form: "json",
|
|
167
|
+
...options,
|
|
168
|
+
}));
|
|
169
|
+
const url = `${Bun.env.IB_ORIGIN}/random?${searchParams.toString()}`;
|
|
170
|
+
logger.info(
|
|
171
|
+
`[trace-id: ${traceId}][image-bed random params]: GET ${url}`,
|
|
172
|
+
);
|
|
173
|
+
return fetch(url, {
|
|
174
|
+
method: "GET",
|
|
175
|
+
headers: {
|
|
176
|
+
referer: String(Bun.env.IB_ORIGIN),
|
|
177
|
+
},
|
|
178
|
+
}).then((rs) => {
|
|
179
|
+
if (options?.type === "img") {
|
|
180
|
+
return rs.blob();
|
|
181
|
+
}
|
|
182
|
+
if (options?.form === "text") {
|
|
183
|
+
return rs.text();
|
|
184
|
+
}
|
|
185
|
+
return rs.json();
|
|
186
|
+
}).catch((error) => {
|
|
187
|
+
logger.error(
|
|
188
|
+
`[trace-id: ${traceId}][image-bed random error]: ${
|
|
189
|
+
JSON.stringify(error)
|
|
190
|
+
}`,
|
|
191
|
+
);
|
|
192
|
+
return R.unifail("SERVER_ERROR", error, c.headers);
|
|
193
|
+
});
|
|
194
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { Context, initEvn, logger } from "./utils";
|
|
5
|
+
import {
|
|
6
|
+
type IBRandomOptions,
|
|
7
|
+
type IBUploadOptions,
|
|
8
|
+
random,
|
|
9
|
+
remove,
|
|
10
|
+
upload,
|
|
11
|
+
} from "./image-bed";
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const { values, positionals } = parseArgs({
|
|
16
|
+
args,
|
|
17
|
+
options: {
|
|
18
|
+
origin: { type: "string" },
|
|
19
|
+
log: { type: "string" },
|
|
20
|
+
"runtime-path": { type: "string" },
|
|
21
|
+
"log-root": { type: "string" },
|
|
22
|
+
"upload-token": { type: "string" },
|
|
23
|
+
"delete-token": { type: "string" },
|
|
24
|
+
"upload-auth-code": { type: "string" },
|
|
25
|
+
trace: { type: "string" },
|
|
26
|
+
lang: { type: "string" },
|
|
27
|
+
|
|
28
|
+
// Upload Options
|
|
29
|
+
"auth-code": { type: "string" },
|
|
30
|
+
"server-compress": { type: "string" },
|
|
31
|
+
"upload-channel": { type: "string" },
|
|
32
|
+
"channel-name": { type: "string" },
|
|
33
|
+
"auto-retry": { type: "string" },
|
|
34
|
+
"upload-name-type": { type: "string" },
|
|
35
|
+
"return-format": { type: "string" },
|
|
36
|
+
"upload-folder": { type: "string" },
|
|
37
|
+
|
|
38
|
+
// Remove Options
|
|
39
|
+
folder: { type: "boolean" },
|
|
40
|
+
|
|
41
|
+
// Random Options
|
|
42
|
+
content: { type: "string" },
|
|
43
|
+
type: { type: "string" },
|
|
44
|
+
form: { type: "string" },
|
|
45
|
+
dir: { type: "string" },
|
|
46
|
+
orientation: { type: "string" },
|
|
47
|
+
|
|
48
|
+
help: { type: "boolean", short: "h" },
|
|
49
|
+
},
|
|
50
|
+
strict: false,
|
|
51
|
+
allowPositionals: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (values.help || positionals.length === 0) {
|
|
55
|
+
console.log(`
|
|
56
|
+
Usage: cfib <command> [args] [options...]
|
|
57
|
+
|
|
58
|
+
Commands:
|
|
59
|
+
upload <file> (Required <file>) Upload a local file
|
|
60
|
+
remove <path> (Required <path>) Remove a remote file or folder
|
|
61
|
+
random [dest] (Optional [dest]) Get a random image and save it to [dest] if --type is img
|
|
62
|
+
|
|
63
|
+
Global Options:
|
|
64
|
+
--origin <url> (Required) Set IB_ORIGIN
|
|
65
|
+
--log <true|false> Set IB_LOG
|
|
66
|
+
--runtime-path <path> Set IB_RUNTIME_PATH
|
|
67
|
+
--log-root <path> Set IB_LOG_ROOT
|
|
68
|
+
--upload-token <token> Set IB_UPLOAD_TOKEN
|
|
69
|
+
--delete-token <token> Set IB_DELETE_TOKEN
|
|
70
|
+
--upload-auth-code <code> (Required for upload/remove) Set IB_UPLOAD_AUTH_CODE
|
|
71
|
+
--trace <id> Set Trace-Id
|
|
72
|
+
--lang <language> Set Accept-Language
|
|
73
|
+
|
|
74
|
+
Upload Options (All Optional):
|
|
75
|
+
--auth-code <code> Provide authCode for upload
|
|
76
|
+
--server-compress <true|false> Whether to compress image on server
|
|
77
|
+
--upload-channel <channel> Upload channel (telegram|cfr2|s3|discord|huggingface)
|
|
78
|
+
--channel-name <name> Channel target name
|
|
79
|
+
--auto-retry <true|false> Whether to auto-retry
|
|
80
|
+
--upload-name-type <type> Name type (default|index|origin|short)
|
|
81
|
+
--return-format <format> Return format (default|full)
|
|
82
|
+
--upload-folder <folder> Target upload folder
|
|
83
|
+
|
|
84
|
+
Remove Options (All Optional):
|
|
85
|
+
--folder Remove a folder instead of a single file
|
|
86
|
+
|
|
87
|
+
Random Options (All Optional):
|
|
88
|
+
--content <image|video> Filter content type
|
|
89
|
+
--type <path|img> Return type, use 'img' to return raw binary and save to local
|
|
90
|
+
--form <json|text> Response format
|
|
91
|
+
--dir <path> Filter by specific directory
|
|
92
|
+
--orientation <direction> Orientation (landscape|portrait|square|auto)
|
|
93
|
+
`.trim());
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Initialize env
|
|
98
|
+
initEvn({
|
|
99
|
+
IB_ORIGIN: values.origin as string | undefined,
|
|
100
|
+
IB_LOG: values.log as "true" | "false" | undefined,
|
|
101
|
+
IB_RUNTIME_PATH: values["runtime-path"] as string | undefined,
|
|
102
|
+
IB_LOG_ROOT: values["log-root"] as string | undefined,
|
|
103
|
+
IB_UPLOAD_TOKEN: values["upload-token"] as string | undefined,
|
|
104
|
+
IB_DELETE_TOKEN: values["delete-token"] as string | undefined,
|
|
105
|
+
IB_UPLOAD_AUTH_CODE: values["upload-auth-code"] as string | undefined,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!Bun.env.IB_ORIGIN) {
|
|
109
|
+
console.error(
|
|
110
|
+
"Error: Missing required global configuration: --origin. Please provide it via CLI or environment variables.",
|
|
111
|
+
);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const command = positionals[0];
|
|
116
|
+
if (
|
|
117
|
+
(command === "upload" || command === "remove") &&
|
|
118
|
+
!Bun.env.IB_UPLOAD_AUTH_CODE
|
|
119
|
+
) {
|
|
120
|
+
console.error(
|
|
121
|
+
`Error: Missing required configuration: --upload-auth-code for the '${command}' command.`,
|
|
122
|
+
);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
const c = new Context(
|
|
126
|
+
values.trace as string | undefined,
|
|
127
|
+
values.lang as string | undefined,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
if (command === "upload") {
|
|
132
|
+
const filepath = positionals[1];
|
|
133
|
+
if (!filepath) {
|
|
134
|
+
console.error("Error: Please provide a filepath to upload.");
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const bunFile = Bun.file(filepath);
|
|
138
|
+
if (!(await bunFile.exists())) {
|
|
139
|
+
console.error(`Error: File not found at ${filepath}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const options: IBUploadOptions = {
|
|
144
|
+
authCode: values["auth-code"] as string | undefined,
|
|
145
|
+
serverCompress:
|
|
146
|
+
values["server-compress"] as IBUploadOptions["serverCompress"],
|
|
147
|
+
uploadChannel:
|
|
148
|
+
values["upload-channel"] as IBUploadOptions["uploadChannel"],
|
|
149
|
+
channelName: values["channel-name"] as string | undefined,
|
|
150
|
+
autoRetry: values["auto-retry"] as IBUploadOptions["autoRetry"],
|
|
151
|
+
uploadNameType:
|
|
152
|
+
values["upload-name-type"] as IBUploadOptions["uploadNameType"],
|
|
153
|
+
returnFormat:
|
|
154
|
+
values["return-format"] as IBUploadOptions["returnFormat"],
|
|
155
|
+
uploadFolder: values["upload-folder"] as string | undefined,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const result = await upload(c, bunFile as unknown as File, options);
|
|
159
|
+
if (result.success === false) {
|
|
160
|
+
console.error(JSON.stringify(result, null, 2));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
console.log(JSON.stringify(result, null, 2));
|
|
164
|
+
} else if (command === "remove") {
|
|
165
|
+
const pathToRemove = positionals[1];
|
|
166
|
+
if (!pathToRemove) {
|
|
167
|
+
console.error("Error: Please provide a path to remove.");
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const isFolder = Boolean(values.folder);
|
|
172
|
+
const result = await remove(c, pathToRemove, isFolder);
|
|
173
|
+
if (result.success === false) {
|
|
174
|
+
console.error(JSON.stringify(result, null, 2));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
console.log(JSON.stringify(result, null, 2));
|
|
178
|
+
} else if (command === "random") {
|
|
179
|
+
const options: IBRandomOptions = {
|
|
180
|
+
content: values.content as IBRandomOptions["content"],
|
|
181
|
+
type: values.type as IBRandomOptions["type"],
|
|
182
|
+
form: values.form as IBRandomOptions["form"],
|
|
183
|
+
dir: values.dir as string | undefined,
|
|
184
|
+
orientation: values.orientation as IBRandomOptions["orientation"],
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const result = await random(c, options);
|
|
188
|
+
if (options.type === "img") {
|
|
189
|
+
const dest = positionals[1] || "random-image.jpg";
|
|
190
|
+
if (result instanceof Blob) {
|
|
191
|
+
await Bun.write(dest, result);
|
|
192
|
+
console.log(`Image saved to ${dest}`);
|
|
193
|
+
} else {
|
|
194
|
+
console.error("Error: Expected Blob, got", result);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
} else if (options.form === "text") {
|
|
198
|
+
console.log(result);
|
|
199
|
+
} else {
|
|
200
|
+
if (result && (result as any).success === false) {
|
|
201
|
+
console.error(JSON.stringify(result, null, 2));
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
console.log(JSON.stringify(result, null, 2));
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
console.error(`Error: Unknown command "${command}"`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
} catch (err: any) {
|
|
211
|
+
console.error("Execution error:", err.message || err);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
main();
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Logger, pathJoin } from "@w-xuefeng/bkit";
|
|
2
|
+
|
|
3
|
+
interface IBEnv {
|
|
4
|
+
IB_ORIGIN: string;
|
|
5
|
+
IB_LOG: "true" | "false";
|
|
6
|
+
IB_RUNTIME_PATH: string;
|
|
7
|
+
IB_LOG_ROOT: string;
|
|
8
|
+
IB_UPLOAD_TOKEN: string;
|
|
9
|
+
IB_DELETE_TOKEN: string;
|
|
10
|
+
IB_UPLOAD_AUTH_CODE: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const IB_RUNTIME_PATH = Bun.env.IB_RUNTIME_PATH || "runtime";
|
|
14
|
+
const IB_LOG_ROOT = Bun.env.IB_LOG_ROOT || "logs";
|
|
15
|
+
|
|
16
|
+
const getLogConfig = () => {
|
|
17
|
+
return {
|
|
18
|
+
enabled: Bun.env.IB_LOG === "true",
|
|
19
|
+
type: "both" as const,
|
|
20
|
+
root: pathJoin(IB_RUNTIME_PATH, IB_LOG_ROOT),
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const logger = new Logger(getLogConfig());
|
|
25
|
+
const initLogger = () => {
|
|
26
|
+
const loggerConfig = getLogConfig();
|
|
27
|
+
logger.enabled = loggerConfig.enabled;
|
|
28
|
+
logger.root = loggerConfig.root;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function initEvn(env?: Partial<IBEnv>) {
|
|
32
|
+
Bun.env.IB_ORIGIN = Bun.env.IB_ORIGIN || env?.IB_ORIGIN;
|
|
33
|
+
Bun.env.IB_LOG = Bun.env.IB_LOG || env?.IB_LOG;
|
|
34
|
+
Bun.env.IB_RUNTIME_PATH = Bun.env.IB_RUNTIME_PATH || env?.IB_RUNTIME_PATH;
|
|
35
|
+
Bun.env.IB_LOG_ROOT = Bun.env.IB_LOG_ROOT || env?.IB_LOG_ROOT;
|
|
36
|
+
Bun.env.IB_UPLOAD_TOKEN = Bun.env.IB_UPLOAD_TOKEN || env?.IB_UPLOAD_TOKEN;
|
|
37
|
+
Bun.env.IB_DELETE_TOKEN = Bun.env.IB_DELETE_TOKEN || env?.IB_DELETE_TOKEN;
|
|
38
|
+
Bun.env.IB_UPLOAD_AUTH_CODE = Bun.env.IB_UPLOAD_AUTH_CODE ||
|
|
39
|
+
env?.IB_UPLOAD_AUTH_CODE;
|
|
40
|
+
initLogger();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class Context {
|
|
44
|
+
headers: Headers;
|
|
45
|
+
constructor(traceId?: string, acceptLanguage = "en") {
|
|
46
|
+
this.headers = new Headers();
|
|
47
|
+
this.headers.set("Trace-Id", traceId || Bun.randomUUIDv7());
|
|
48
|
+
this.headers.set("Accept-Language", acceptLanguage);
|
|
49
|
+
}
|
|
50
|
+
setLanguage(lang: string) {
|
|
51
|
+
this.headers.set("Accept-Language", lang);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
setTraceId(traceId?: string) {
|
|
55
|
+
this.headers.set("Trace-Id", traceId || Bun.randomUUIDv7());
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
setHeaders(headers: Headers) {
|
|
59
|
+
this.headers = headers;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function filterEmptyField<T extends Record<string, any>>(
|
|
65
|
+
data?: Record<string, any>,
|
|
66
|
+
deep = false,
|
|
67
|
+
emptyArray = ["", void 0, null],
|
|
68
|
+
) {
|
|
69
|
+
if (!data) {
|
|
70
|
+
return {} as T;
|
|
71
|
+
}
|
|
72
|
+
return Object.keys(data).reduce((res, key) => {
|
|
73
|
+
if (
|
|
74
|
+
data[key] && typeof data[key] === "object" && !Array.isArray(data[key]) &&
|
|
75
|
+
deep
|
|
76
|
+
) {
|
|
77
|
+
res[key as keyof T] = filterEmptyField(data[key]);
|
|
78
|
+
} else if (!emptyArray.includes(data[key])) {
|
|
79
|
+
res[key as keyof T] = data[key];
|
|
80
|
+
}
|
|
81
|
+
return res;
|
|
82
|
+
}, {} as T);
|
|
83
|
+
}
|
package/test/banner.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from "bun:test";
|
|
2
|
+
import { random, remove, upload } from "../src/image-bed";
|
|
3
|
+
import { Context, initEvn } from "../src/utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 在执行本测试脚本之前,
|
|
7
|
+
* 请确保您本地安装了 local-diamond,并且使用下列命令配置好了环境变量
|
|
8
|
+
* lod set cfib/origin <origin>
|
|
9
|
+
* lod set cfib/upload-auth-code <upload-auth-code>
|
|
10
|
+
* lod set cfib/upload-token <upload-token>
|
|
11
|
+
* lod set cfib/delete-token <delete-token>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function getLodConfig(key: string): string {
|
|
15
|
+
const proc = Bun.spawnSync(["bunx", "lod", "get", key]);
|
|
16
|
+
if (!proc.success) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Failed to fetch ${key} from lod. Make sure local-diamond is configured properly.`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return proc.stdout.toString().trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("Cloudflare Image Bed API Tests", () => {
|
|
25
|
+
let c: Context;
|
|
26
|
+
let testImagePath = "";
|
|
27
|
+
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
const origin = getLodConfig("cfib/origin");
|
|
30
|
+
const uploadAuthCode = getLodConfig("cfib/upload-auth-code");
|
|
31
|
+
const uploadToken = getLodConfig("cfib/upload-token");
|
|
32
|
+
const deleteToken = getLodConfig("cfib/delete-token");
|
|
33
|
+
|
|
34
|
+
initEvn({
|
|
35
|
+
IB_ORIGIN: origin,
|
|
36
|
+
IB_UPLOAD_AUTH_CODE: uploadAuthCode,
|
|
37
|
+
IB_UPLOAD_TOKEN: uploadToken,
|
|
38
|
+
IB_DELETE_TOKEN: deleteToken,
|
|
39
|
+
IB_LOG: "true",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
c = new Context();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should upload a local image", async () => {
|
|
46
|
+
const file = Bun.file(`${import.meta.dir}/banner.png`);
|
|
47
|
+
expect(await file.exists()).toBe(true);
|
|
48
|
+
const buffer = await file.arrayBuffer();
|
|
49
|
+
const webFile = new File([buffer], "banner.png", { type: file.type });
|
|
50
|
+
const result = await upload(c, webFile, { uploadFolder: "wallpaper/test" });
|
|
51
|
+
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
console.error(
|
|
54
|
+
"Upload failed with result:",
|
|
55
|
+
JSON.stringify(result, null, 2),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
expect(result.success).toBe(true);
|
|
60
|
+
expect(result.data).toBeDefined();
|
|
61
|
+
expect(result.data?.src).toBeDefined();
|
|
62
|
+
expect(result.data?.url).toBeDefined();
|
|
63
|
+
expect(typeof result.data?.src).toBe("string");
|
|
64
|
+
expect(typeof result.data?.url).toBe("string");
|
|
65
|
+
|
|
66
|
+
testImagePath = result.data!.url;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should remove the uploaded image", async () => {
|
|
70
|
+
expect(testImagePath).not.toBe("");
|
|
71
|
+
const result = await remove(c, testImagePath, false);
|
|
72
|
+
expect(result.success).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should get a random image", async () => {
|
|
76
|
+
const result = await random(c, { type: "path", dir: "wallpaper" });
|
|
77
|
+
expect(result).toBeDefined();
|
|
78
|
+
if ((result as any).success !== undefined) {
|
|
79
|
+
expect((result as any).success).toBe(true);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"ESNext"
|
|
5
|
+
],
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"module": "Preserve",
|
|
8
|
+
"moduleDetection": "force",
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"allowJs": true,
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedIndexedAccess": true,
|
|
19
|
+
"noImplicitOverride": true,
|
|
20
|
+
"noUnusedLocals": false,
|
|
21
|
+
"noUnusedParameters": false,
|
|
22
|
+
"noPropertyAccessFromIndexSignature": false
|
|
23
|
+
}
|
|
24
|
+
}
|