@zhin.js/plugin-short-url 1.0.0 → 1.1.0
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/README.md +28 -0
- package/agent/skills/short-url.md +40 -0
- package/agent/tools/short_url.ts +17 -0
- package/commands/expand/[url].js +19 -0
- package/commands/expand/[url].ts +17 -0
- package/commands/shorten/[url].js +19 -0
- package/commands/shorten/[url].ts +17 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -79
- package/lib/index.js.map +1 -1
- package/lib/short-url-lib.d.ts +4 -0
- package/lib/short-url-lib.d.ts.map +1 -0
- package/lib/short-url-lib.js +30 -0
- package/lib/short-url-lib.js.map +1 -0
- package/package.json +45 -7
- package/plugin.js +8 -0
- package/schema.json +6 -0
- package/src/index.ts +1 -84
- package/src/short-url-lib.ts +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @zhin.js/plugin-short-url
|
|
2
|
+
|
|
3
|
+
Zhin.js plugin for short-url functionality.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @zhin.js/plugin-short-url
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Add the plugin to your `zhin.config.yml`:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
plugins:
|
|
17
|
+
- @zhin.js/plugin-short-url
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or via CLI:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
zhin install @zhin.js/plugin-short-url
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
Configure in `zhin.config.yml` under the plugin's key. Refer to the plugin source for available options.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: short-url
|
|
3
|
+
description: >-
|
|
4
|
+
短链接生成能力。当用户想缩短一个长 URL 或生成短链时使用。
|
|
5
|
+
用户说「帮我缩短这个链接」「转成短链」或给了一个很长的 URL 时应触发。
|
|
6
|
+
keywords:
|
|
7
|
+
- short-url
|
|
8
|
+
- 短链
|
|
9
|
+
- shorten
|
|
10
|
+
- URL
|
|
11
|
+
- 链接
|
|
12
|
+
- 缩短
|
|
13
|
+
tags:
|
|
14
|
+
- short-url
|
|
15
|
+
- utility
|
|
16
|
+
tools:
|
|
17
|
+
- short_url
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# 短链接生成技能
|
|
21
|
+
|
|
22
|
+
## 工具
|
|
23
|
+
|
|
24
|
+
| 工具 | 用途 | 参数 |
|
|
25
|
+
|------|------|------|
|
|
26
|
+
| `short_url` | 缩短 URL | `url`(原始链接,必须是 http/https) |
|
|
27
|
+
|
|
28
|
+
**Example:**
|
|
29
|
+
```
|
|
30
|
+
用户: 帮我缩短 https://very-long-domain.com/path/to/something?with=params&and=more
|
|
31
|
+
→ short_url(url="https://very-long-domain.com/path/to/something?with=params&and=more")
|
|
32
|
+
→ 返回: https://is.gd/xxxxx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
使用 is.gd 服务生成短链接。
|
|
36
|
+
|
|
37
|
+
## 注意
|
|
38
|
+
|
|
39
|
+
- 输入必须是合法的 http/https URL。
|
|
40
|
+
- 展开短链查看原始地址的功能通过聊天命令 `展开 <url>` 使用。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { isValidUrl, shortenUrl } from '../../src/short-url-lib.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ url: string }>({
|
|
6
|
+
description: '缩短一个 URL,返回短链接',
|
|
7
|
+
inputSchema: z.object({ url: z.string().min(1) }),
|
|
8
|
+
keywords: ['短链', '缩短', 'shorten', 'short url'],
|
|
9
|
+
async execute({ url }) {
|
|
10
|
+
if (!isValidUrl(url)) return '无效的 URL,需要 http:// 或 https:// 开头';
|
|
11
|
+
try {
|
|
12
|
+
return await shortenUrl(url);
|
|
13
|
+
} catch (e: unknown) {
|
|
14
|
+
return `缩短失败: ${e instanceof Error ? e.message : String(e)}`;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from 'zhin.js/command';
|
|
3
|
+
import { expandUrl, isValidUrl } from "../../lib/short-url-lib.js";
|
|
4
|
+
export default defineCommand({
|
|
5
|
+
description: '展开一个短链接,显示原始地址',
|
|
6
|
+
params: { url: { type: 'string' } },
|
|
7
|
+
async execute({ params }) {
|
|
8
|
+
const url = String(params.url ?? '');
|
|
9
|
+
if (!isValidUrl(url))
|
|
10
|
+
return '请提供有效的 HTTP/HTTPS 链接';
|
|
11
|
+
try {
|
|
12
|
+
const original = await expandUrl(url);
|
|
13
|
+
return `原始链接: ${original}`;
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
return `展开失败: ${e instanceof Error ? e.message : String(e)}`;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineCommand } from 'zhin.js/command';
|
|
2
|
+
import { expandUrl, isValidUrl } from '../../src/short-url-lib.js';
|
|
3
|
+
|
|
4
|
+
export default defineCommand({
|
|
5
|
+
description: '展开一个短链接,显示原始地址',
|
|
6
|
+
params: { url: { type: 'string' } },
|
|
7
|
+
async execute({ params }) {
|
|
8
|
+
const url = String(params.url ?? '');
|
|
9
|
+
if (!isValidUrl(url)) return '请提供有效的 HTTP/HTTPS 链接';
|
|
10
|
+
try {
|
|
11
|
+
const original = await expandUrl(url);
|
|
12
|
+
return `原始链接: ${original}`;
|
|
13
|
+
} catch (e: unknown) {
|
|
14
|
+
return `展开失败: ${e instanceof Error ? e.message : String(e)}`;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from 'zhin.js/command';
|
|
3
|
+
import { isValidUrl, shortenUrl } from "../../lib/short-url-lib.js";
|
|
4
|
+
export default defineCommand({
|
|
5
|
+
description: '缩短一个 URL',
|
|
6
|
+
params: { url: { type: 'string' } },
|
|
7
|
+
async execute({ params }) {
|
|
8
|
+
const url = String(params.url ?? '');
|
|
9
|
+
if (!isValidUrl(url))
|
|
10
|
+
return '请提供有效的 HTTP/HTTPS 链接';
|
|
11
|
+
try {
|
|
12
|
+
const short = await shortenUrl(url);
|
|
13
|
+
return `短链接: ${short}`;
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
return `缩短失败: ${e instanceof Error ? e.message : String(e)}`;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineCommand } from 'zhin.js/command';
|
|
2
|
+
import { isValidUrl, shortenUrl } from '../../src/short-url-lib.js';
|
|
3
|
+
|
|
4
|
+
export default defineCommand({
|
|
5
|
+
description: '缩短一个 URL',
|
|
6
|
+
params: { url: { type: 'string' } },
|
|
7
|
+
async execute({ params }) {
|
|
8
|
+
const url = String(params.url ?? '');
|
|
9
|
+
if (!isValidUrl(url)) return '请提供有效的 HTTP/HTTPS 链接';
|
|
10
|
+
try {
|
|
11
|
+
const short = await shortenUrl(url);
|
|
12
|
+
return `短链接: ${short}`;
|
|
13
|
+
} catch (e: unknown) {
|
|
14
|
+
return `缩短失败: ${e instanceof Error ? e.message : String(e)}`;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { expandUrl, isValidUrl, shortenUrl } from './short-url-lib.js';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -1,80 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
const { addCommand, addTool, logger } = usePlugin();
|
|
3
|
-
// ── helpers ──────────────────────────────────────────────
|
|
4
|
-
function isValidUrl(input) {
|
|
5
|
-
try {
|
|
6
|
-
const u = new URL(input);
|
|
7
|
-
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
async function shortenUrl(url) {
|
|
14
|
-
const api = `https://is.gd/create.php?format=json&url=${encodeURIComponent(url)}`;
|
|
15
|
-
const res = await fetch(api);
|
|
16
|
-
if (!res.ok)
|
|
17
|
-
throw new Error(`is.gd 返回 ${res.status}`);
|
|
18
|
-
const data = (await res.json());
|
|
19
|
-
if (data.errorcode)
|
|
20
|
-
throw new Error(data.errormessage ?? '缩短失败');
|
|
21
|
-
return data.shorturl;
|
|
22
|
-
}
|
|
23
|
-
async function expandUrl(url) {
|
|
24
|
-
const res = await fetch(url, { redirect: 'manual' });
|
|
25
|
-
const location = res.headers.get('location');
|
|
26
|
-
if (location)
|
|
27
|
-
return location;
|
|
28
|
-
if (res.ok)
|
|
29
|
-
return url;
|
|
30
|
-
throw new Error(`无法展开链接 (${res.status})`);
|
|
31
|
-
}
|
|
32
|
-
// ── commands ─────────────────────────────────────────────
|
|
33
|
-
addCommand(new MessageCommand('短链 <url:text>')
|
|
34
|
-
.desc('缩短一个 URL')
|
|
35
|
-
.action(async (_message, result) => {
|
|
36
|
-
const url = result.params.url;
|
|
37
|
-
if (!isValidUrl(url))
|
|
38
|
-
return '请提供有效的 HTTP/HTTPS 链接';
|
|
39
|
-
try {
|
|
40
|
-
const short = await shortenUrl(url);
|
|
41
|
-
return `短链接: ${short}`;
|
|
42
|
-
}
|
|
43
|
-
catch (e) {
|
|
44
|
-
logger.warn(formatCompact({ op: 'shorten', ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
45
|
-
return `缩短失败: ${e.message}`;
|
|
46
|
-
}
|
|
47
|
-
}));
|
|
48
|
-
addCommand(new MessageCommand('展开 <url:text>')
|
|
49
|
-
.desc('展开一个短链接,显示原始地址')
|
|
50
|
-
.action(async (_message, result) => {
|
|
51
|
-
const url = result.params.url;
|
|
52
|
-
if (!isValidUrl(url))
|
|
53
|
-
return '请提供有效的 HTTP/HTTPS 链接';
|
|
54
|
-
try {
|
|
55
|
-
const original = await expandUrl(url);
|
|
56
|
-
return `原始链接: ${original}`;
|
|
57
|
-
}
|
|
58
|
-
catch (e) {
|
|
59
|
-
logger.warn(formatCompact({ op: 'expand', ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
60
|
-
return `展开失败: ${e.message}`;
|
|
61
|
-
}
|
|
62
|
-
}));
|
|
63
|
-
// ── AI tool ──────────────────────────────────────────────
|
|
64
|
-
addTool(new ZhinTool('short_url')
|
|
65
|
-
.desc('缩短一个 URL,返回短链接')
|
|
66
|
-
.keyword('短链', '缩短', 'shorten', 'short url')
|
|
67
|
-
.param('url', { type: 'string', description: '要缩短的完整 URL' }, true)
|
|
68
|
-
.execute(async (args) => {
|
|
69
|
-
const url = args.url;
|
|
70
|
-
if (!isValidUrl(url))
|
|
71
|
-
return '无效的 URL,需要 http:// 或 https:// 开头';
|
|
72
|
-
try {
|
|
73
|
-
return await shortenUrl(url);
|
|
74
|
-
}
|
|
75
|
-
catch (e) {
|
|
76
|
-
return `缩短失败: ${e.message}`;
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
|
-
.toTool());
|
|
1
|
+
export { expandUrl, isValidUrl, shortenUrl } from './short-url-lib.js';
|
|
80
2
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"short-url-lib.d.ts","sourceRoot":"","sources":["../src/short-url-lib.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOjD;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO5D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function isValidUrl(input) {
|
|
2
|
+
try {
|
|
3
|
+
const u = new URL(input);
|
|
4
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export async function shortenUrl(url) {
|
|
11
|
+
const api = `https://is.gd/create.php?format=json&url=${encodeURIComponent(url)}`;
|
|
12
|
+
const res = await fetch(api);
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
throw new Error(`is.gd 返回 ${res.status}`);
|
|
15
|
+
const data = (await res.json());
|
|
16
|
+
if (!data.shorturl)
|
|
17
|
+
throw new Error(data.errormessage ?? '缩短失败');
|
|
18
|
+
return data.shorturl;
|
|
19
|
+
}
|
|
20
|
+
export async function expandUrl(url) {
|
|
21
|
+
// 不能用 redirect:'manual':undici 下返回 opaqueredirect(status 0、headers 空),
|
|
22
|
+
// 拿不到 location。改为跟随重定向后比较最终 URL。
|
|
23
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
24
|
+
if (res.url && res.url !== url)
|
|
25
|
+
return res.url;
|
|
26
|
+
if (res.ok)
|
|
27
|
+
return url;
|
|
28
|
+
throw new Error(`无法展开链接 (${res.status})`);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=short-url-lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"short-url-lib.js","sourceRoot":"","sources":["../src/short-url-lib.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,OAAO,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,GAAG,GAAG,4CAA4C,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;IAClF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqE,CAAC;IACpG,IAAI,CAAC,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,QAAQ,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,uEAAuE;IACvE,iCAAiC;IACjC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IACrD,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,GAAG;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC;IAC/C,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACvB,MAAM,IAAI,KAAK,CAAC,WAAW,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-short-url",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "URL shortener plugin for Zhin.js — shorten & expand URLs",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "URL shortener plugin for Zhin.js — shorten & expand URLs (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
+
"plugin.js",
|
|
18
|
+
"schema.json",
|
|
19
|
+
"commands",
|
|
17
20
|
"src",
|
|
18
21
|
"lib",
|
|
22
|
+
"agent",
|
|
19
23
|
"README.md"
|
|
20
24
|
],
|
|
21
25
|
"keywords": [
|
|
@@ -32,13 +36,29 @@
|
|
|
32
36
|
"url": "https://github.com/lc-cn"
|
|
33
37
|
},
|
|
34
38
|
"license": "MIT",
|
|
35
|
-
"dependencies": {},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"typescript": "^6.0.3",
|
|
38
|
-
"
|
|
41
|
+
"vitest": "^4.1.10",
|
|
42
|
+
"zod": "^4.4.3",
|
|
43
|
+
"@zhin.js/agent": "1.1.23",
|
|
44
|
+
"zhin.js": "1.1.0"
|
|
39
45
|
},
|
|
40
46
|
"peerDependencies": {
|
|
41
|
-
"
|
|
47
|
+
"zod": "^4.0.0",
|
|
48
|
+
"@zhin.js/agent": "^1.1.23",
|
|
49
|
+
"@zhin.js/command": "^1.1.0",
|
|
50
|
+
"zhin.js": "^1.1.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@zhin.js/agent": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"@zhin.js/command": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"zod": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
42
62
|
},
|
|
43
63
|
"repository": {
|
|
44
64
|
"type": "git",
|
|
@@ -49,8 +69,26 @@
|
|
|
49
69
|
"access": "public",
|
|
50
70
|
"registry": "https://registry.npmjs.org"
|
|
51
71
|
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
74
|
+
},
|
|
75
|
+
"zhin": {
|
|
76
|
+
"protocol": 1,
|
|
77
|
+
"type": "plugin",
|
|
78
|
+
"entry": "./plugin.js",
|
|
79
|
+
"engine": "^1.0.0",
|
|
80
|
+
"runtime": "trusted",
|
|
81
|
+
"features": [
|
|
82
|
+
{
|
|
83
|
+
"package": "@zhin.js/command",
|
|
84
|
+
"api": "^1.0.0"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"plugins": []
|
|
88
|
+
},
|
|
52
89
|
"scripts": {
|
|
53
|
-
"build": "tsc
|
|
54
|
-
"clean": "rimraf lib"
|
|
90
|
+
"build": "tsc",
|
|
91
|
+
"clean": "rimraf lib",
|
|
92
|
+
"test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/utils/short-url/tests"
|
|
55
93
|
}
|
|
56
94
|
}
|
package/plugin.js
ADDED
package/schema.json
ADDED
package/src/index.ts
CHANGED
|
@@ -1,84 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { addCommand, addTool, logger } = usePlugin()
|
|
4
|
-
|
|
5
|
-
// ── helpers ──────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
function isValidUrl(input: string): boolean {
|
|
8
|
-
try {
|
|
9
|
-
const u = new URL(input)
|
|
10
|
-
return u.protocol === 'http:' || u.protocol === 'https:'
|
|
11
|
-
} catch {
|
|
12
|
-
return false
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function shortenUrl(url: string): Promise<string> {
|
|
17
|
-
const api = `https://is.gd/create.php?format=json&url=${encodeURIComponent(url)}`
|
|
18
|
-
const res = await fetch(api)
|
|
19
|
-
if (!res.ok) throw new Error(`is.gd 返回 ${res.status}`)
|
|
20
|
-
const data = (await res.json()) as { shorturl?: string; errorcode?: number; errormessage?: string }
|
|
21
|
-
if (data.errorcode) throw new Error(data.errormessage ?? '缩短失败')
|
|
22
|
-
return data.shorturl!
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function expandUrl(url: string): Promise<string> {
|
|
26
|
-
const res = await fetch(url, { redirect: 'manual' })
|
|
27
|
-
const location = res.headers.get('location')
|
|
28
|
-
if (location) return location
|
|
29
|
-
if (res.ok) return url
|
|
30
|
-
throw new Error(`无法展开链接 (${res.status})`)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ── commands ─────────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
addCommand(
|
|
36
|
-
new MessageCommand('短链 <url:text>')
|
|
37
|
-
.desc('缩短一个 URL')
|
|
38
|
-
.action(async (_message, result) => {
|
|
39
|
-
const url = result.params.url as string
|
|
40
|
-
if (!isValidUrl(url)) return '请提供有效的 HTTP/HTTPS 链接'
|
|
41
|
-
try {
|
|
42
|
-
const short = await shortenUrl(url)
|
|
43
|
-
return `短链接: ${short}`
|
|
44
|
-
} catch (e: any) {
|
|
45
|
-
logger.warn(formatCompact( { op: 'shorten', ok: false, error: e instanceof Error ? e.message : String(e) }))
|
|
46
|
-
return `缩短失败: ${e.message}`
|
|
47
|
-
}
|
|
48
|
-
}),
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
addCommand(
|
|
52
|
-
new MessageCommand('展开 <url:text>')
|
|
53
|
-
.desc('展开一个短链接,显示原始地址')
|
|
54
|
-
.action(async (_message, result) => {
|
|
55
|
-
const url = result.params.url as string
|
|
56
|
-
if (!isValidUrl(url)) return '请提供有效的 HTTP/HTTPS 链接'
|
|
57
|
-
try {
|
|
58
|
-
const original = await expandUrl(url)
|
|
59
|
-
return `原始链接: ${original}`
|
|
60
|
-
} catch (e: any) {
|
|
61
|
-
logger.warn(formatCompact( { op: 'expand', ok: false, error: e instanceof Error ? e.message : String(e) }))
|
|
62
|
-
return `展开失败: ${e.message}`
|
|
63
|
-
}
|
|
64
|
-
}),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
// ── AI tool ──────────────────────────────────────────────
|
|
68
|
-
|
|
69
|
-
addTool(
|
|
70
|
-
new ZhinTool('short_url')
|
|
71
|
-
.desc('缩短一个 URL,返回短链接')
|
|
72
|
-
.keyword('短链', '缩短', 'shorten', 'short url')
|
|
73
|
-
.param('url', { type: 'string', description: '要缩短的完整 URL' }, true)
|
|
74
|
-
.execute(async (args) => {
|
|
75
|
-
const url = args.url as string
|
|
76
|
-
if (!isValidUrl(url)) return '无效的 URL,需要 http:// 或 https:// 开头'
|
|
77
|
-
try {
|
|
78
|
-
return await shortenUrl(url)
|
|
79
|
-
} catch (e: any) {
|
|
80
|
-
return `缩短失败: ${e.message}`
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
.toTool(),
|
|
84
|
-
)
|
|
1
|
+
export { expandUrl, isValidUrl, shortenUrl } from './short-url-lib.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function isValidUrl(input: string): boolean {
|
|
2
|
+
try {
|
|
3
|
+
const u = new URL(input);
|
|
4
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
5
|
+
} catch {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function shortenUrl(url: string): Promise<string> {
|
|
11
|
+
const api = `https://is.gd/create.php?format=json&url=${encodeURIComponent(url)}`;
|
|
12
|
+
const res = await fetch(api);
|
|
13
|
+
if (!res.ok) throw new Error(`is.gd 返回 ${res.status}`);
|
|
14
|
+
const data = (await res.json()) as { shorturl?: string; errorcode?: number; errormessage?: string };
|
|
15
|
+
if (!data.shorturl) throw new Error(data.errormessage ?? '缩短失败');
|
|
16
|
+
return data.shorturl;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function expandUrl(url: string): Promise<string> {
|
|
20
|
+
// 不能用 redirect:'manual':undici 下返回 opaqueredirect(status 0、headers 空),
|
|
21
|
+
// 拿不到 location。改为跟随重定向后比较最终 URL。
|
|
22
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
23
|
+
if (res.url && res.url !== url) return res.url;
|
|
24
|
+
if (res.ok) return url;
|
|
25
|
+
throw new Error(`无法展开链接 (${res.status})`);
|
|
26
|
+
}
|