songzhiyun 0.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.
Files changed (3) hide show
  1. package/README.md +122 -0
  2. package/index.js +109 -0
  3. package/package.json +11 -0
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # 松智云 Node.js SDK
2
+
3
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+
5
+ 松智云网页截图 API 的 Node.js 客户端库。支持 Node.js 18+。
6
+
7
+ ## 环境要求
8
+
9
+ - Node.js 18 或更高版本
10
+ - 松智云 API Key([免费注册获取](https://songzhiyun.com))
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ npm install songzhiyun
16
+ ```
17
+
18
+ ## 快速开始
19
+
20
+ ```javascript
21
+ const { SongzhiClient } = require('songzhiyun');
22
+
23
+ const client = new SongzhiClient('sk-xxx');
24
+ const result = await client.screenshot('https://example.com');
25
+ console.log(result.imageUrl);
26
+ // → /screenshots/d55c14ca.png
27
+ ```
28
+
29
+ ESM:
30
+
31
+ ```javascript
32
+ import { SongzhiClient } from 'songzhiyun';
33
+ ```
34
+
35
+ ## 完整选项
36
+
37
+ ```javascript
38
+ const result = await client.screenshot('https://example.com', {
39
+ width: 1920,
40
+ height: 1080,
41
+ fullPage: true, // 全页长图
42
+ mobile: true, // 移动端模拟
43
+ waitMs: 2000, // 页面加载后额外等待
44
+ format: 'png', // png / jpeg / webp / pdf
45
+ quality: 95, // 图片质量 1-100(jpeg/webp)
46
+ adBlock: true, // 广告拦截
47
+ // 水印
48
+ watermark: {
49
+ text: '机密文档',
50
+ position: 'bottom-right', // top-left / top-right / center / bottom-left / bottom-right
51
+ opacity: 50, // 0-100
52
+ },
53
+ });
54
+ ```
55
+
56
+ ## 参数说明
57
+
58
+ | 参数 | 类型 | 默认值 | 说明 |
59
+ |------|------|--------|------|
60
+ | `width` | number | 1280 | 视口宽度(像素) |
61
+ | `height` | number | 720 | 视口高度(像素) |
62
+ | `fullPage` | boolean | false | 全页截图(长图) |
63
+ | `mobile` | boolean | false | 移动端 User-Agent + 375 宽视口 |
64
+ | `waitMs` | number | 0 | 页面加载后额外等待(毫秒) |
65
+ | `format` | string | "png" | 输出格式:png / jpeg / webp / pdf |
66
+ | `quality` | number | 90 | 图片质量 1-100(jpeg/webp) |
67
+ | `adBlock` | boolean | true | 广告拦截 |
68
+ | `watermark` | object | — | 水印配置 `{ text, position, opacity }` |
69
+
70
+ ## 异步模式
71
+
72
+ ```javascript
73
+ // 提交任务(立即返回 taskId,不阻塞)
74
+ const task = await client.submit('https://example.com', { width: 1280, height: 720 });
75
+ console.log('taskId:', task.taskId);
76
+
77
+ // ... 做其他事情 ...
78
+
79
+ // 轮询直到完成
80
+ const result = await client.poll(task.taskId, 60, 1000);
81
+ console.log('imageUrl:', result.imageUrl);
82
+ ```
83
+
84
+ ## 错误处理
85
+
86
+ ```javascript
87
+ const { SongzhiClient, SongzhiError } = require('songzhiyun');
88
+
89
+ try {
90
+ const result = await client.screenshot('https://example.com');
91
+ } catch (e) {
92
+ if (e instanceof SongzhiError) {
93
+ console.error(`错误码: ${e.code}, 消息: ${e.message}`);
94
+ } else {
95
+ console.error('网络错误:', e.message);
96
+ }
97
+ }
98
+ ```
99
+
100
+ 错误码说明:
101
+
102
+ | 错误码 | 说明 |
103
+ |--------|------|
104
+ | 1001 | 参数错误或资源不存在 |
105
+ | 1002 | 缺少认证信息 |
106
+ | 1003 | 本月配额已用完 |
107
+ | 1004 | 并发请求超限 |
108
+ | 1006 | 请求过于频繁 |
109
+ | 5000 | 服务器内部错误 |
110
+
111
+ ## 相关链接
112
+
113
+ - [松智云 官网](https://songzhiyun.com)
114
+ - [API 文档](https://songzhiyun.com/docs.html)
115
+ - [控制台](https://songzhiyun.com/dashboard.html)
116
+ - [Java SDK](https://github.com/songzhiyun/sdk-java)
117
+ - [Python SDK](https://github.com/songzhiyun/sdk-python)
118
+ - [CLI 工具](https://github.com/songzhiyun/cli)
119
+
120
+ ## License
121
+
122
+ MIT
package/index.js ADDED
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 松智云 Node.js SDK — 网页截图 API 客户端
5
+ *
6
+ * const { SongzhiClient } = require('songzhiyun');
7
+ * const client = new SongzhiClient('sk-xxx');
8
+ * const result = await client.screenshot('https://example.com');
9
+ * console.log(result.imageUrl);
10
+ */
11
+
12
+ class SongzhiError extends Error {
13
+ constructor(code, message) {
14
+ super(`[${code}] ${message}`);
15
+ this.code = code;
16
+ }
17
+ }
18
+
19
+ class SongzhiClient {
20
+ /**
21
+ * @param {string} apiKey
22
+ * @param {string} [baseUrl='https://api.songzhiyun.com']
23
+ */
24
+ constructor(apiKey, baseUrl = 'https://api.songzhiyun.com') {
25
+ this.apiKey = apiKey;
26
+ this.baseUrl = baseUrl.replace(/\/$/, '');
27
+ }
28
+
29
+ /**
30
+ * 提交截图并等待完成
31
+ * @param {string} url
32
+ * @param {object} [options]
33
+ * @param {number} [options.width=1280]
34
+ * @param {number} [options.height=720]
35
+ * @param {boolean} [options.fullPage=false]
36
+ * @param {boolean} [options.mobile=false]
37
+ * @param {string} [options.format='png']
38
+ * @param {number} [options.quality=90]
39
+ * @param {number} [options.waitMs=0]
40
+ * @param {boolean} [options.adBlock=true]
41
+ * @param {number} [timeout=60]
42
+ * @returns {Promise<object>} { taskId, status, imageUrl, durationMs, imageSize }
43
+ */
44
+ async screenshot(url, options = {}, timeout = 60) {
45
+ const task = await this.submit(url, options);
46
+ return this.poll(task.taskId, timeout);
47
+ }
48
+
49
+ /**
50
+ * 提交截图任务
51
+ * @returns {Promise<object>} { taskId, status, estimatedSeconds }
52
+ */
53
+ async submit(url, options = {}) {
54
+ const body = {
55
+ url,
56
+ width: options.width ?? 1280,
57
+ height: options.height ?? 720,
58
+ fullPage: options.fullPage ?? false,
59
+ mobile: options.mobile ?? false,
60
+ format: options.format ?? 'png',
61
+ quality: options.quality ?? 90,
62
+ waitMs: options.waitMs ?? 0,
63
+ adBlock: options.adBlock ?? true,
64
+ };
65
+ if (options.watermark) {
66
+ body.watermark = {
67
+ text: options.watermark.text || '',
68
+ position: options.watermark.position || 'bottom-right',
69
+ opacity: options.watermark.opacity ?? 50,
70
+ };
71
+ }
72
+ const resp = await fetch(`${this.baseUrl}/api/v1/screenshot`, {
73
+ method: 'POST',
74
+ headers: {
75
+ 'Authorization': `Bearer ${this.apiKey}`,
76
+ 'Content-Type': 'application/json',
77
+ },
78
+ body: JSON.stringify(body),
79
+ });
80
+ const data = await resp.json();
81
+ if (data.code !== 0) throw new SongzhiError(data.code, data.msg);
82
+ return data.data;
83
+ }
84
+
85
+ /**
86
+ * 轮询任务结果
87
+ * @param {string} taskId
88
+ * @param {number} [timeout=60]
89
+ * @param {number} [interval=1000]
90
+ * @returns {Promise<object>}
91
+ */
92
+ async poll(taskId, timeout = 60, interval = 1000) {
93
+ const deadline = Date.now() + timeout * 1000;
94
+ while (Date.now() < deadline) {
95
+ const resp = await fetch(`${this.baseUrl}/api/v1/screenshot/${taskId}`, {
96
+ headers: { 'Authorization': `Bearer ${this.apiKey}` },
97
+ });
98
+ const data = await resp.json();
99
+ if (data.code !== 0) throw new SongzhiError(data.code, data.msg);
100
+ const task = data.data;
101
+ if (task.status === 'done') return task;
102
+ if (task.status === 'failed') throw new SongzhiError(-1, task.error || '截图失败');
103
+ await new Promise(r => setTimeout(r, interval));
104
+ }
105
+ throw new SongzhiError(-1, `截图超时 (${timeout}s)`);
106
+ }
107
+ }
108
+
109
+ module.exports = { SongzhiClient, SongzhiError };
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "songzhiyun",
3
+ "version": "0.1.0",
4
+ "description": "松智云 网页截图 API Node.js SDK",
5
+ "main": "index.js",
6
+ "files": ["index.js"],
7
+ "keywords": ["screenshot", "songzhiyun", "api", "playwright"],
8
+ "license": "MIT",
9
+ "repository": "https://github.com/xuelb1983/sdk-node",
10
+ "engines": { "node": ">=18" }
11
+ }