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.
- package/README.md +122 -0
- package/index.js +109 -0
- package/package.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 松智云 Node.js SDK
|
|
2
|
+
|
|
3
|
+
[](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
|
+
}
|