@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.
Files changed (3) hide show
  1. package/README.md +35 -9
  2. package/index.js +21 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,18 +1,27 @@
1
1
  # @boodibox/api-client
2
2
 
3
- این بسته برای **ارسال خودکار پست‌ها به `https://boodibox.com/posts`** طراحی شده — یعنی شما می‌توانید به‌سرعت پست منتشر کنید یا یک بات بسازید که محتوای شما را خودکار ارسال کند.
4
- کتابخانه جریان کامل را پوشش می‌دهد: آپلود تصویر (اختیاری) → نظارت روی وضعیت تا زمانی که پردازش شود → ارسال پست با `medias` که سرور تولید می‌کند.
3
+ <div dir="rtl">
5
4
 
6
- **آموزش گرفتن API Key:** https://boodibox.com/dev/api-key
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)` – آپلود فایل‌ها (برمی‌گرداند آرایه‌ای از apiupload ids)
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
- * این تابع به‌صورت convenience تمامی مراحل را انجام می‌دهد: آپلود فایل‌ها → poll تا پردازش → ارسال پست با `medias` برگشتی
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` مقدار API key (فرمت: `Bearer <token>` یا فقط توکن)
88
- * `TEST_INTEGRATION=true` اگر این مقدار تنظیم نشده باشد، تست یکپارچه نادیده گرفته می‌شود
89
- * `BASE_URL` آدرس سرور (مثلاً `http://localhost:3000` یا `https://boodibox.com`)
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
- if (!Array.isArray(files) || files.length === 0) {
228
- // still require replyPermission defaulting behavior
229
- return submitPost({ body, medias: [], replyPermission, quotePostID });
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 === undefined) {
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boodibox/api-client",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Minimal client for BoodiBox API - submit posts - automate - upload media.",
5
5
  "main": "index.js",
6
6
  "license": "MIT",