@boodibox/api-client 0.1.0 → 0.1.2
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 +35 -9
- package/index.js +21 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
# @boodibox/api-client
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
کتابخانه جریان کامل را پوشش میدهد: آپلود تصویر (اختیاری) → نظارت روی وضعیت تا زمانی که پردازش شود → ارسال پست با `medias` که سرور تولید میکند.
|
|
3
|
+
<div dir="rtl">
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
این بسته برای ارسال خودکار **پستها** به https://boodibox.com/posts طراحی شده یعنی شما میتوانید بهسرعت پست منتشر کنید یا یک بات بسازید که محتوای شما را خودکار ارسال کند.
|
|
6
|
+
|
|
7
|
+
کتابخانه جریان کامل را پوشش میدهد: آپلود تصویر (اختیاری) ← نظارت روی وضعیت تا زمانی که پردازش شود ← ارسال پست با `medias` که سرور تولید میکند.
|
|
7
8
|
|
|
9
|
+
**آموزش گرفتن API Key:** https://boodibox.com/dev/api-key
|
|
8
10
|
|
|
9
11
|
## نصب
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
10
15
|
```bash
|
|
11
16
|
npm install @boodibox/api-client
|
|
12
|
-
|
|
17
|
+
```
|
|
18
|
+
یا
|
|
19
|
+
```bash
|
|
13
20
|
yarn add @boodibox/api-client
|
|
14
21
|
````
|
|
15
22
|
|
|
23
|
+
<div dir="rtl">
|
|
24
|
+
|
|
16
25
|
> این کتابخانه از امکانات بومی Node (fetch, FormData, Blob) استفاده میکند. از Node >= 18 استفاده کنید، یا برای محیطهایی که `FormData` / `Blob` ندارند یک polyfill نصب کنید (مثلاً `formdata-node`).
|
|
17
26
|
|
|
18
27
|
---
|
|
@@ -32,6 +41,8 @@ yarn add @boodibox/api-client
|
|
|
32
41
|
|
|
33
42
|
## استفادهٔ سریع
|
|
34
43
|
|
|
44
|
+
</div>
|
|
45
|
+
|
|
35
46
|
```js
|
|
36
47
|
const createClient = require('@boodibox/api-client');
|
|
37
48
|
|
|
@@ -52,20 +63,29 @@ await client.submitPostWithFiles({
|
|
|
52
63
|
|
|
53
64
|
---
|
|
54
65
|
|
|
66
|
+
<div dir="auto">
|
|
67
|
+
|
|
55
68
|
## APIها (توابع اصلی)
|
|
56
69
|
|
|
57
70
|
* `createClient({ baseUrl, apiKey })` – ساخت کلاینت
|
|
58
|
-
* `uploadFiles(files)` – آپلود فایلها (برمیگرداند آرایهای از
|
|
71
|
+
* `uploadFiles(files)` – آپلود فایلها (برمیگرداند آرایهای از upload ids)
|
|
59
72
|
* `pollUntilProcessed(uploadId, options)` – نظارت تا وضعیت `PROCESSED`
|
|
60
73
|
* `submitPost({ body, medias, replyPermission, quotePostID, userIP })`
|
|
61
74
|
* `submitPostWithFiles({ body, files, replyPermission, quotePostID, pollOptions, timeoutMs })`
|
|
62
75
|
|
|
63
|
-
|
|
76
|
+
---
|
|
77
|
+
* این تابع بهصورت convenience تمامی مراحل را انجام میدهد: آپلود فایلها ← poll تا پردازش ← ارسال پست با `medias` برگشتی
|
|
78
|
+
|
|
79
|
+
<!-- <div align="left"> -->
|
|
80
|
+
|
|
81
|
+
----
|
|
64
82
|
|
|
65
83
|
### شکل فایلها (files)
|
|
66
84
|
|
|
67
85
|
هر عنصر میتواند یکی از موارد باشد:
|
|
68
86
|
|
|
87
|
+
<!-- </div> -->
|
|
88
|
+
|
|
69
89
|
* `{ path: "./file.jpg" }` — خواندن از دیسک
|
|
70
90
|
* `{ buffer: Buffer, filename: "a.jpg" }`
|
|
71
91
|
* `{ file: File }` (در مرورگر)
|
|
@@ -84,12 +104,14 @@ await client.submitPostWithFiles({
|
|
|
84
104
|
|
|
85
105
|
برای اجرای تستهای یکپارچه (integration) به صورت اختیاری از متغیرهای محیطی زیر استفاده کنید:
|
|
86
106
|
|
|
87
|
-
* `API_KEY`
|
|
88
|
-
* `TEST_INTEGRATION=true`
|
|
89
|
-
* `BASE_URL`
|
|
107
|
+
* `API_KEY` :مقدار API key (فرمت: `Bearer <token>` یا فقط توکن)
|
|
108
|
+
* `TEST_INTEGRATION=true` :اگر این مقدار تنظیم نشده باشد، تست یکپارچه نادیده گرفته میشود
|
|
109
|
+
* `BASE_URL` :آدرس سرور (مثلاً `http://localhost:3000` یا `https://boodibox.com`)
|
|
90
110
|
|
|
91
111
|
نمونه:
|
|
92
112
|
|
|
113
|
+
</div>
|
|
114
|
+
|
|
93
115
|
```bash
|
|
94
116
|
export API_KEY="Bearer xxxxx"
|
|
95
117
|
export TEST_INTEGRATION=true
|
|
@@ -97,11 +119,15 @@ export BASE_URL="http://localhost:3000"
|
|
|
97
119
|
bun test
|
|
98
120
|
```
|
|
99
121
|
|
|
122
|
+
<div dir="rtl">
|
|
123
|
+
|
|
100
124
|
## لینک مفید
|
|
101
125
|
* راهنمای گرفتن API Key: [https://boodibox.com/dev/api-key](https://boodibox.com/dev/api-key)
|
|
102
126
|
|
|
103
127
|
* پست ها بودیباکس: [https://boodibox.com/posts](https://boodibox.com/posts)
|
|
104
128
|
|
|
129
|
+
</div>
|
|
130
|
+
|
|
105
131
|
## License
|
|
106
132
|
|
|
107
133
|
MIT License
|
package/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Changes:
|
|
6
6
|
* - replyPermission defaults to "PUBLIC" when omitted
|
|
7
7
|
* - explicitly empty or falsey replyPermission values are rejected
|
|
8
|
+
* - submitPost now requires either a non-empty body or at least one media
|
|
8
9
|
* - other behavior unchanged (upload -> poll -> submit)
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -186,6 +187,7 @@ function createClient(opts = {}) {
|
|
|
186
187
|
* submitPost
|
|
187
188
|
* replyPermission: if undefined -> defaults to PUBLIC
|
|
188
189
|
* if explicitly provided but empty/falsey -> throws
|
|
190
|
+
* now enforces: post must have non-empty body OR at least one media
|
|
189
191
|
*/
|
|
190
192
|
async function submitPost({ body = '', medias = [], replyPermission = undefined, quotePostID = null, userIP = null }) {
|
|
191
193
|
// Handle replyPermission defaulting and validation
|
|
@@ -199,6 +201,13 @@ function createClient(opts = {}) {
|
|
|
199
201
|
|
|
200
202
|
const qid = quotePostID == null ? null : validateQuotePostID(quotePostID);
|
|
201
203
|
|
|
204
|
+
// Validation: require either a non-empty body or at least one media
|
|
205
|
+
const bodyStr = (body == null ? '' : String(body)).trim();
|
|
206
|
+
const hasMedias = Array.isArray(medias) && medias.length > 0;
|
|
207
|
+
if (!bodyStr && !hasMedias) {
|
|
208
|
+
throw new Error('Post must include a non-empty body or at least one media.');
|
|
209
|
+
}
|
|
210
|
+
|
|
202
211
|
const url = new URL(config.postsPath, config.baseUrl).toString();
|
|
203
212
|
const payload = { body, medias, replyPermission: rp, quotePostID: qid };
|
|
204
213
|
if (userIP) payload.userIP = userIP;
|
|
@@ -222,21 +231,26 @@ function createClient(opts = {}) {
|
|
|
222
231
|
* submitPostWithFiles
|
|
223
232
|
* - If replyPermission undefined -> defaults to PUBLIC
|
|
224
233
|
* - If replyPermission explicitly provided but empty -> throw
|
|
234
|
+
* - Requires either body or files; if files provided they will be uploaded
|
|
225
235
|
*/
|
|
226
236
|
async function submitPostWithFiles({ body = '', files = [], replyPermission = undefined, quotePostID = null, pollOptions = {}, timeoutMs = 120000 }) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
237
|
+
// If no files and empty body -> reject early
|
|
238
|
+
const bodyStr = (body == null ? '' : String(body)).trim();
|
|
239
|
+
if ((!Array.isArray(files) || files.length === 0) && !bodyStr) {
|
|
240
|
+
throw new Error('Post must include a non-empty body or at least one file to upload.');
|
|
230
241
|
}
|
|
231
242
|
|
|
232
|
-
// validate upfront: default or validate
|
|
233
|
-
if (replyPermission
|
|
234
|
-
// allow defaulting later in submitPost, but keep same normalization here
|
|
235
|
-
} else {
|
|
243
|
+
// validate upfront: default or validate replyPermission and quotePostID
|
|
244
|
+
if (replyPermission !== undefined) {
|
|
236
245
|
validateReplyPermission(replyPermission);
|
|
237
246
|
}
|
|
238
247
|
if (quotePostID != null) validateQuotePostID(quotePostID);
|
|
239
248
|
|
|
249
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
250
|
+
// no files to upload, delegate to submitPost which also validates
|
|
251
|
+
return submitPost({ body, medias: [], replyPermission, quotePostID });
|
|
252
|
+
}
|
|
253
|
+
|
|
240
254
|
const uploads = await retryAsync(() => uploadFiles(files), config.maxRetries, 300).catch(err => {
|
|
241
255
|
throw new Error(`Uploading files failed: ${err.message}`);
|
|
242
256
|
});
|