@zhin.js/plugin-qrcode 1.1.25 → 1.1.26
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/LICENSE +21 -0
- package/README.md +5 -1
- package/commands/gen/index.js +2 -2
- package/commands/gen/index.ts +2 -2
- package/commands/scan/[url]/index.js +7 -2
- package/commands/scan/[url]/index.ts +6 -2
- package/lib/i18n.d.ts +1 -1
- package/lib/i18n.d.ts.map +1 -1
- package/lib/i18n.js +4 -0
- package/lib/i18n.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/qrcode-lib.d.ts +9 -3
- package/lib/qrcode-lib.d.ts.map +1 -1
- package/lib/qrcode-lib.js +21 -6
- package/lib/qrcode-lib.js.map +1 -1
- package/package.json +12 -17
- package/pages/index/index.js +1 -1
- package/pages/index/index.ts +1 -1
- package/plugin.js +4 -9
- package/skills/qrcode/SKILL.md +1 -1
- package/skills/qrcode/tools/qrcode_generate/index.js +2 -3
- package/skills/qrcode/tools/qrcode_generate/index.ts +2 -3
- package/src/i18n.ts +4 -0
- package/src/index.ts +1 -1
- package/src/qrcode-lib.ts +20 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 凉菜
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -27,13 +27,17 @@ This plugin currently has no configuration fields. Its [`schema.json`](./schema.
|
|
|
27
27
|
|
|
28
28
|
- `gen hello beautiful world`: generate a 300 × 300 QR image.
|
|
29
29
|
- `gen hello --size 512` / `gen hello -s 512`: choose 64–1024 pixels.
|
|
30
|
-
- `scan https://example.com/qr.png`: decode an existing QR image.
|
|
30
|
+
- `scan https://example.com/qr.png`: decode an existing QR image. The image URL (http/https only) is sent to the public `api.qrserver.com` decoder; the request times out after 10 seconds.
|
|
31
31
|
- `gen --help`: show parameters, constraints and defaults.
|
|
32
32
|
|
|
33
33
|
The Endpoint determines any command prefix. An explicit `commandNamespace` adds a plugin
|
|
34
34
|
namespace; neither is hardcoded in the command. Generation declares text length (1–2048)
|
|
35
35
|
and image size in `defineCommand`; invalid values are rejected before creating the image.
|
|
36
36
|
|
|
37
|
+
QR images are generated locally (`zhin.js/qrcode`) by the `gen` command, the `qrcode_generate`
|
|
38
|
+
Agent tool and the Console page alike, and are sent as PNG data URLs; the content is never
|
|
39
|
+
forwarded to a third-party service.
|
|
40
|
+
|
|
37
41
|
## Language / 语言
|
|
38
42
|
|
|
39
43
|
命令说明和执行反馈跟随当前操作语言(简体中文或英文);用户输入与第三方内容保持原样,不进行自动翻译。语言与时区、权限和实际执行行为独立。参见[官方插件语言说明](../../README.md#语言与内容)。
|
package/commands/gen/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { defineCommand } from 'zhin.js/command';
|
|
3
3
|
import { raw } from '@zhin.js/core/runtime';
|
|
4
|
-
import {
|
|
4
|
+
import { buildQrDataUrl } from "../../lib/qrcode-lib.js";
|
|
5
5
|
export default defineCommand({
|
|
6
6
|
description: '根据文本或链接生成二维码图片', descriptionI18n: { en: "Generate a QR code image from text or a link" },
|
|
7
7
|
params: { text: { type: 'text', min: 1, max: 2048, description: '二维码内容(可含空格)', descriptionI18n: { en: "QR code content (spaces allowed)" } } },
|
|
8
8
|
options: { size: { type: 'integer', alias: 's', default: 300, min: 64, max: 1024, description: '图片宽高(像素)', descriptionI18n: { en: "Image width and height in pixels" } } },
|
|
9
|
-
execute: ({ params, options }) => raw({ type: 'image', data: { url:
|
|
9
|
+
execute: async ({ params, options }) => raw({ type: 'image', data: { url: await buildQrDataUrl(params.text, options.size) } }),
|
|
10
10
|
});
|
package/commands/gen/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { defineCommand } from 'zhin.js/command';
|
|
2
2
|
import { raw } from '@zhin.js/core/runtime';
|
|
3
|
-
import {
|
|
3
|
+
import { buildQrDataUrl } from '../../src/qrcode-lib.js';
|
|
4
4
|
|
|
5
5
|
export default defineCommand({
|
|
6
6
|
description: '根据文本或链接生成二维码图片', descriptionI18n: { en: "Generate a QR code image from text or a link" },
|
|
7
7
|
params: { text: { type: 'text', min: 1, max: 2048, description: '二维码内容(可含空格)', descriptionI18n: { en: "QR code content (spaces allowed)" } } },
|
|
8
8
|
options: { size: { type: 'integer', alias: 's', default: 300, min: 64, max: 1024, description: '图片宽高(像素)', descriptionI18n: { en: "Image width and height in pixels" } } },
|
|
9
|
-
execute: ({ params, options }) => raw({ type: 'image', data: { url:
|
|
9
|
+
execute: async ({ params, options }) => raw({ type: 'image', data: { url: await buildQrDataUrl(params.text, options.size) } }),
|
|
10
10
|
});
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { defineCommand } from 'zhin.js/command';
|
|
3
3
|
import { messages as t } from "../../../lib/i18n.js";
|
|
4
|
+
import { isScannableImageUrl } from "../../../lib/qrcode-lib.js";
|
|
4
5
|
const QR_API_BASE = 'https://api.qrserver.com/v1';
|
|
6
|
+
const SCAN_TIMEOUT_MS = 10_000;
|
|
5
7
|
export default defineCommand({
|
|
6
8
|
description: '识别图片中的二维码内容', descriptionI18n: { en: "Read QR code content from an image" },
|
|
7
9
|
params: { url: { type: 'string' } },
|
|
8
10
|
async execute({ params, locale }) {
|
|
9
|
-
const imageUrl = String(params.url);
|
|
11
|
+
const imageUrl = String(params.url).trim();
|
|
12
|
+
if (!isScannableImageUrl(imageUrl))
|
|
13
|
+
return t(locale, 'invalidUrl');
|
|
10
14
|
const apiUrl = `${QR_API_BASE}/read-qr-code/?fileurl=${encodeURIComponent(imageUrl)}`;
|
|
11
15
|
try {
|
|
12
|
-
const response = await fetch(apiUrl);
|
|
16
|
+
const response = await fetch(apiUrl, { signal: AbortSignal.timeout(SCAN_TIMEOUT_MS) });
|
|
13
17
|
if (!response.ok) {
|
|
18
|
+
await response.body?.cancel();
|
|
14
19
|
return t(locale, 'requestFailed');
|
|
15
20
|
}
|
|
16
21
|
const data = (await response.json());
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { defineCommand } from 'zhin.js/command';
|
|
2
2
|
import { messages as t } from '../../../src/i18n.js';
|
|
3
|
+
import { isScannableImageUrl } from '../../../src/qrcode-lib.js';
|
|
3
4
|
|
|
4
5
|
const QR_API_BASE = 'https://api.qrserver.com/v1';
|
|
6
|
+
const SCAN_TIMEOUT_MS = 10_000;
|
|
5
7
|
|
|
6
8
|
export default defineCommand({
|
|
7
9
|
description: '识别图片中的二维码内容', descriptionI18n: { en: "Read QR code content from an image" },
|
|
8
10
|
params: { url: { type: 'string' } },
|
|
9
11
|
async execute({ params, locale }) {
|
|
10
|
-
const imageUrl = String(params.url);
|
|
12
|
+
const imageUrl = String(params.url).trim();
|
|
13
|
+
if (!isScannableImageUrl(imageUrl)) return t(locale, 'invalidUrl');
|
|
11
14
|
const apiUrl = `${QR_API_BASE}/read-qr-code/?fileurl=${encodeURIComponent(imageUrl)}`;
|
|
12
15
|
try {
|
|
13
|
-
const response = await fetch(apiUrl);
|
|
16
|
+
const response = await fetch(apiUrl, { signal: AbortSignal.timeout(SCAN_TIMEOUT_MS) });
|
|
14
17
|
if (!response.ok) {
|
|
18
|
+
await response.body?.cancel();
|
|
15
19
|
return t(locale, 'requestFailed');
|
|
16
20
|
}
|
|
17
21
|
const data = (await response.json()) as Array<{
|
package/lib/i18n.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const messages: (locale: import("@zhin.js/plugin-runtime").Locale | undefined, key: "requestFailed" | "notFound" | "result" | "failed", values?: import("@zhin.js/plugin-runtime").MessageValues) => string;
|
|
1
|
+
export declare const messages: (locale: import("@zhin.js/plugin-runtime").Locale | undefined, key: "invalidUrl" | "requestFailed" | "notFound" | "result" | "failed", values?: import("@zhin.js/plugin-runtime").MessageValues) => string;
|
|
2
2
|
//# sourceMappingURL=i18n.d.ts.map
|
package/lib/i18n.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,4MAqBnB,CAAC"}
|
package/lib/i18n.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createMessages } from 'zhin.js/i18n';
|
|
2
2
|
export const messages = createMessages({
|
|
3
|
+
"invalidUrl": {
|
|
4
|
+
"zh-CN": "请提供有效的 HTTP/HTTPS 图片链接",
|
|
5
|
+
"en": "Provide a valid HTTP or HTTPS image URL."
|
|
6
|
+
},
|
|
3
7
|
"requestFailed": {
|
|
4
8
|
"zh-CN": "二维码识别服务请求失败",
|
|
5
9
|
"en": "The QR code service request failed."
|
package/lib/i18n.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,CAAC,MAAM,QAAQ,GAAG,cAAc,CAAC;IACrC,eAAe,EAAE;QACf,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,qCAAqC;KAC5C;IACD,UAAU,EAAE;QACV,OAAO,EAAE,mBAAmB;QAC5B,IAAI,EAAE,kCAAkC;KACzC;IACD,QAAQ,EAAE;QACR,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,yBAAyB;KAChC;IACD,QAAQ,EAAE;QACR,OAAO,EAAE,qBAAqB;QAC9B,IAAI,EAAE,kDAAkD;KACzD;CACF,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,CAAC,MAAM,QAAQ,GAAG,cAAc,CAAC;IACrC,YAAY,EAAE;QACZ,OAAO,EAAE,wBAAwB;QACjC,IAAI,EAAE,0CAA0C;KACjD;IACD,eAAe,EAAE;QACf,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,qCAAqC;KAC5C;IACD,UAAU,EAAE;QACV,OAAO,EAAE,mBAAmB;QAC5B,IAAI,EAAE,kCAAkC;KACzC;IACD,QAAQ,EAAE;QACR,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,yBAAyB;KAChC;IACD,QAAQ,EAAE;QACR,OAAO,EAAE,qBAAqB;QAC9B,IAAI,EAAE,kDAAkD;KACzD;CACF,CAAC,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { buildQrDataUrl, isScannableImageUrl, qrImageSegment } from './qrcode-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":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { buildQrDataUrl, isScannableImageUrl, qrImageSegment } from './qrcode-lib.js';
|
|
2
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,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
|
package/lib/qrcode-lib.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Render `text` as a PNG data URL in this process. The content never leaves the host:
|
|
3
|
+
* chat command, Agent tool and Console action all use this same local generator.
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildQrDataUrl(text: string, size?: number): Promise<string>;
|
|
6
|
+
export declare function qrImageSegment(text: string, size?: number): Promise<{
|
|
3
7
|
type: "image";
|
|
4
8
|
data: {
|
|
5
9
|
url: string;
|
|
6
10
|
};
|
|
7
|
-
}[]
|
|
11
|
+
}[]>;
|
|
12
|
+
/** `scan` hands the image URL to the decoding service: only plain http(s) URLs are forwarded. */
|
|
13
|
+
export declare function isScannableImageUrl(input: string): boolean;
|
|
8
14
|
//# sourceMappingURL=qrcode-lib.d.ts.map
|
package/lib/qrcode-lib.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qrcode-lib.d.ts","sourceRoot":"","sources":["../src/qrcode-lib.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"qrcode-lib.d.ts","sourceRoot":"","sources":["../src/qrcode-lib.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG9E;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAM;;;;;KAE5D;AAED,iGAAiG;AACjG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQ1D"}
|
package/lib/qrcode-lib.js
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { generateQrCode } from 'zhin.js/qrcode';
|
|
2
|
+
/**
|
|
3
|
+
* Render `text` as a PNG data URL in this process. The content never leaves the host:
|
|
4
|
+
* chat command, Agent tool and Console action all use this same local generator.
|
|
5
|
+
*/
|
|
6
|
+
export async function buildQrDataUrl(text, size = 300) {
|
|
3
7
|
if (!Number.isInteger(size) || size < 64 || size > 1024)
|
|
4
8
|
throw new TypeError('QR size must be an integer between 64 and 1024');
|
|
5
|
-
return
|
|
9
|
+
return (await generateQrCode(text, { width: size })).toDataUrl();
|
|
6
10
|
}
|
|
7
|
-
export function qrImageSegment(text) {
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
export async function qrImageSegment(text, size = 300) {
|
|
12
|
+
return [{ type: 'image', data: { url: await buildQrDataUrl(text, size) } }];
|
|
13
|
+
}
|
|
14
|
+
/** `scan` hands the image URL to the decoding service: only plain http(s) URLs are forwarded. */
|
|
15
|
+
export function isScannableImageUrl(input) {
|
|
16
|
+
if (input.length > 2048)
|
|
17
|
+
return false;
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(input);
|
|
20
|
+
return (url.protocol === 'http:' || url.protocol === 'https:') && !url.username && !url.password && url.hostname.includes('.');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
10
25
|
}
|
|
11
26
|
//# sourceMappingURL=qrcode-lib.js.map
|
package/lib/qrcode-lib.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qrcode-lib.js","sourceRoot":"","sources":["../src/qrcode-lib.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"qrcode-lib.js","sourceRoot":"","sources":["../src/qrcode-lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,IAAI,GAAG,GAAG;IAC3D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,IAAI,IAAI,GAAG,IAAI;QAAE,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IAC/H,OAAO,CAAC,MAAM,cAAc,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,IAAI,GAAG,GAAG;IAC3D,OAAO,CAAC,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;AACvF,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjI,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-qrcode",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.26",
|
|
4
4
|
"description": "QR code generator & reader plugin for Zhin.js (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"pages",
|
|
20
20
|
"src",
|
|
21
21
|
"lib",
|
|
22
|
-
"tools",
|
|
23
22
|
"skills",
|
|
24
23
|
"README.md"
|
|
25
24
|
],
|
|
@@ -38,30 +37,26 @@
|
|
|
38
37
|
},
|
|
39
38
|
"license": "MIT",
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"
|
|
42
|
-
"@zhin.js/
|
|
43
|
-
"@zhin.js/
|
|
44
|
-
"@zhin.js/
|
|
45
|
-
"@zhin.js/skill": "1.1.
|
|
40
|
+
"zod": "^4.6.7",
|
|
41
|
+
"@zhin.js/console-contract": "1.1.6",
|
|
42
|
+
"@zhin.js/core": "1.2.0",
|
|
43
|
+
"@zhin.js/page": "1.1.18",
|
|
44
|
+
"@zhin.js/skill": "1.1.17",
|
|
45
|
+
"@zhin.js/tool": "1.1.19"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"typescript": "^6.0.3",
|
|
49
49
|
"vitest": "^4.1.10",
|
|
50
|
-
"
|
|
51
|
-
"zhin.js": "1.1.25"
|
|
50
|
+
"zhin.js": "1.1.26"
|
|
52
51
|
},
|
|
53
52
|
"peerDependencies": {
|
|
54
|
-
"
|
|
55
|
-
"@zhin.js/
|
|
56
|
-
"
|
|
57
|
-
"zhin.js": "^1.1.25"
|
|
53
|
+
"@zhin.js/command": "^1.2.0",
|
|
54
|
+
"@zhin.js/page": "^1.1.18",
|
|
55
|
+
"zhin.js": "^1.1.26"
|
|
58
56
|
},
|
|
59
57
|
"peerDependenciesMeta": {
|
|
60
58
|
"@zhin.js/command": {
|
|
61
59
|
"optional": true
|
|
62
|
-
},
|
|
63
|
-
"zod": {
|
|
64
|
-
"optional": true
|
|
65
60
|
}
|
|
66
61
|
},
|
|
67
62
|
"repository": {
|
|
@@ -105,6 +100,6 @@
|
|
|
105
100
|
"scripts": {
|
|
106
101
|
"build": "tsc",
|
|
107
102
|
"clean": "rimraf lib",
|
|
108
|
-
"test": "
|
|
103
|
+
"test": "vitest run --root ../../.. plugins/utils/qrcode/tests"
|
|
109
104
|
}
|
|
110
105
|
}
|
package/pages/index/index.js
CHANGED
package/pages/index/index.ts
CHANGED
package/plugin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { definePlugin, definePluginCapabilityGraph, definePluginConsoleRpc, PluginConsoleRpcError, pluginConsoleRpcToken, } from 'zhin.js';
|
|
4
|
-
import {
|
|
4
|
+
import { buildQrDataUrl } from "./lib/qrcode-lib.js";
|
|
5
5
|
const MAX_HISTORY_ITEMS = 20;
|
|
6
6
|
export default definePlugin({
|
|
7
7
|
name: 'qrcode',
|
|
@@ -20,6 +20,8 @@ export default definePlugin({
|
|
|
20
20
|
setup() {
|
|
21
21
|
const history = [];
|
|
22
22
|
const rpc = definePluginConsoleRpc({
|
|
23
|
+
// Day-to-day edits belong to operators; `:write` would otherwise infer `manage` (ADR 0049).
|
|
24
|
+
permissions: [{ id: 'qrcode:write', level: 'operate', description: '生成二维码' }],
|
|
23
25
|
status: {
|
|
24
26
|
page: 'index',
|
|
25
27
|
demo: true,
|
|
@@ -46,9 +48,7 @@ export default definePlugin({
|
|
|
46
48
|
const width = Number.isFinite(rawWidth) && rawWidth >= 64 && rawWidth <= 1024
|
|
47
49
|
? Math.round(rawWidth)
|
|
48
50
|
: 280;
|
|
49
|
-
|
|
50
|
-
const qr = await generateQrCode(rawText, { width });
|
|
51
|
-
const dataUrl = qr.toDataUrl();
|
|
51
|
+
const dataUrl = await buildQrDataUrl(rawText, width);
|
|
52
52
|
const entry = {
|
|
53
53
|
id: randomUUID(),
|
|
54
54
|
content: rawText,
|
|
@@ -79,11 +79,6 @@ export default definePlugin({
|
|
|
79
79
|
},
|
|
80
80
|
},
|
|
81
81
|
});
|
|
82
|
-
const methods = rpc.methods;
|
|
83
|
-
const clearHistoryMethod = methods.get('clear-history');
|
|
84
|
-
if (clearHistoryMethod) {
|
|
85
|
-
methods.set('clearHistory', clearHistoryMethod);
|
|
86
|
-
}
|
|
87
82
|
return {
|
|
88
83
|
consoleRpc: rpc,
|
|
89
84
|
};
|
package/skills/qrcode/SKILL.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import {
|
|
4
|
+
import { qrImageSegment } from "../../../../lib/qrcode-lib.js";
|
|
5
5
|
export default defineAgentTool({
|
|
6
6
|
description: '根据文本或 URL 生成二维码图片',
|
|
7
7
|
inputSchema: z.object({ text: z.string().min(1) }),
|
|
8
8
|
keywords: ['二维码', 'QR', 'qrcode'],
|
|
9
9
|
tags: ['qrcode', 'image'],
|
|
10
10
|
async execute({ text }) {
|
|
11
|
-
|
|
12
|
-
return JSON.stringify([{ type: 'image', data: { url: qrUrl } }]);
|
|
11
|
+
return JSON.stringify(await qrImageSegment(text));
|
|
13
12
|
},
|
|
14
13
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
3
|
+
import { qrImageSegment } from '../../../../src/qrcode-lib.js';
|
|
4
4
|
|
|
5
5
|
export default defineAgentTool<{ text: string }>({
|
|
6
6
|
description: '根据文本或 URL 生成二维码图片',
|
|
@@ -8,7 +8,6 @@ export default defineAgentTool<{ text: string }>({
|
|
|
8
8
|
keywords: ['二维码', 'QR', 'qrcode'],
|
|
9
9
|
tags: ['qrcode', 'image'],
|
|
10
10
|
async execute({ text }) {
|
|
11
|
-
|
|
12
|
-
return JSON.stringify([{ type: 'image', data: { url: qrUrl } }]);
|
|
11
|
+
return JSON.stringify(await qrImageSegment(text));
|
|
13
12
|
},
|
|
14
13
|
});
|
package/src/i18n.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createMessages } from 'zhin.js/i18n';
|
|
2
2
|
|
|
3
3
|
export const messages = createMessages({
|
|
4
|
+
"invalidUrl": {
|
|
5
|
+
"zh-CN": "请提供有效的 HTTP/HTTPS 图片链接",
|
|
6
|
+
"en": "Provide a valid HTTP or HTTPS image URL."
|
|
7
|
+
},
|
|
4
8
|
"requestFailed": {
|
|
5
9
|
"zh-CN": "二维码识别服务请求失败",
|
|
6
10
|
"en": "The QR code service request failed."
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { buildQrDataUrl, isScannableImageUrl, qrImageSegment } from './qrcode-lib.js';
|
package/src/qrcode-lib.ts
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import { generateQrCode } from 'zhin.js/qrcode';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Render `text` as a PNG data URL in this process. The content never leaves the host:
|
|
5
|
+
* chat command, Agent tool and Console action all use this same local generator.
|
|
6
|
+
*/
|
|
7
|
+
export async function buildQrDataUrl(text: string, size = 300): Promise<string> {
|
|
4
8
|
if (!Number.isInteger(size) || size < 64 || size > 1024) throw new TypeError('QR size must be an integer between 64 and 1024');
|
|
5
|
-
return
|
|
9
|
+
return (await generateQrCode(text, { width: size })).toDataUrl();
|
|
6
10
|
}
|
|
7
11
|
|
|
8
|
-
export function qrImageSegment(text: string) {
|
|
9
|
-
const
|
|
10
|
-
|
|
12
|
+
export async function qrImageSegment(text: string, size = 300) {
|
|
13
|
+
return [{ type: 'image' as const, data: { url: await buildQrDataUrl(text, size) } }];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** `scan` hands the image URL to the decoding service: only plain http(s) URLs are forwarded. */
|
|
17
|
+
export function isScannableImageUrl(input: string): boolean {
|
|
18
|
+
if (input.length > 2048) return false;
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(input);
|
|
21
|
+
return (url.protocol === 'http:' || url.protocol === 'https:') && !url.username && !url.password && url.hostname.includes('.');
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
11
25
|
}
|