ugcinc 4.5.58 → 4.5.60

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/dist/posts.d.ts CHANGED
@@ -87,6 +87,7 @@ export interface UpdatePostParams {
87
87
  postTime?: string;
88
88
  accountId?: string;
89
89
  mediaUrls?: string[];
90
+ excludePostIds?: string[];
90
91
  }
91
92
  export interface SetPostStatusParams {
92
93
  postIds: string[];
package/dist/render.js CHANGED
@@ -17,6 +17,69 @@ const RENDER_BASE_URL = "https://render.ugc.inc";
17
17
  const RENDER_SUBMIT_URL = `${RENDER_BASE_URL}/submit-job`;
18
18
  const RENDER_STATUS_URL = `${RENDER_BASE_URL}/get-status`;
19
19
  // =============================================================================
20
+ // Rate Limiting & Retry (Modal workspace limit: 200 req/s)
21
+ // =============================================================================
22
+ const MODAL_RATE_LIMIT = 150; // per second, safety margin below 200
23
+ const MAX_RETRIES = 3;
24
+ // Sliding window: timestamps of recent requests within the last second
25
+ const requestTimestamps = [];
26
+ function acquireRateSlot() {
27
+ const now = Date.now();
28
+ // Evict timestamps older than 1 second
29
+ while (requestTimestamps.length > 0 && requestTimestamps[0] < now - 1000) {
30
+ requestTimestamps.shift();
31
+ }
32
+ if (requestTimestamps.length < MODAL_RATE_LIMIT) {
33
+ requestTimestamps.push(now);
34
+ return 0; // no wait needed
35
+ }
36
+ // Calculate ms until the oldest request exits the window
37
+ return 1000 - (now - requestTimestamps[0]) + 10;
38
+ }
39
+ async function waitForRateSlot() {
40
+ let waitMs = acquireRateSlot();
41
+ while (waitMs > 0) {
42
+ await new Promise(resolve => setTimeout(resolve, waitMs));
43
+ waitMs = acquireRateSlot();
44
+ }
45
+ }
46
+ /**
47
+ * Fetch wrapper for all Modal API calls.
48
+ * Applies rate limiting (150 req/s) and retries on 429/5xx with exponential backoff.
49
+ */
50
+ async function modalFetch(url, options) {
51
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
52
+ await waitForRateSlot();
53
+ let response;
54
+ try {
55
+ response = await fetch(url, options);
56
+ }
57
+ catch (error) {
58
+ // Network error — retry with backoff
59
+ if (attempt < MAX_RETRIES) {
60
+ const backoff = Math.pow(2, attempt) * 500 + Math.random() * 500;
61
+ await new Promise(resolve => setTimeout(resolve, backoff));
62
+ continue;
63
+ }
64
+ throw error;
65
+ }
66
+ // Success or non-retryable client error (400, 401, 403, 404, etc.)
67
+ if (response.ok || (response.status >= 400 && response.status < 500 && response.status !== 429)) {
68
+ return response;
69
+ }
70
+ // Retryable: 429 (rate limit) or 5xx (server error)
71
+ if (attempt < MAX_RETRIES) {
72
+ const backoff = Math.pow(2, attempt) * 500 + Math.random() * 500;
73
+ await new Promise(resolve => setTimeout(resolve, backoff));
74
+ continue;
75
+ }
76
+ return response; // Return error response on final attempt
77
+ }
78
+ // Unreachable, but satisfies TypeScript
79
+ await waitForRateSlot();
80
+ return fetch(url, options);
81
+ }
82
+ // =============================================================================
20
83
  // DM Message Transformation Helpers
21
84
  // =============================================================================
22
85
  /**
@@ -72,7 +135,7 @@ function transformToIMessageMessages(messages, imageAttachmentUrl) {
72
135
  */
73
136
  async function submitImageRenderJob(params) {
74
137
  try {
75
- const response = await fetch(RENDER_SUBMIT_URL, {
138
+ const response = await modalFetch(RENDER_SUBMIT_URL, {
76
139
  method: 'POST',
77
140
  headers: { 'Content-Type': 'application/json' },
78
141
  body: JSON.stringify({
@@ -117,7 +180,7 @@ async function submitImageRenderJob(params) {
117
180
  */
118
181
  async function submitVideoRenderJob(params) {
119
182
  try {
120
- const response = await fetch(RENDER_SUBMIT_URL, {
183
+ const response = await modalFetch(RENDER_SUBMIT_URL, {
121
184
  method: 'POST',
122
185
  headers: { 'Content-Type': 'application/json' },
123
186
  body: JSON.stringify({
@@ -155,7 +218,7 @@ async function submitVideoRenderJob(params) {
155
218
  */
156
219
  async function getRenderJobStatus(jobId) {
157
220
  try {
158
- const response = await fetch(RENDER_STATUS_URL, {
221
+ const response = await modalFetch(RENDER_STATUS_URL, {
159
222
  method: 'POST',
160
223
  headers: { 'Content-Type': 'application/json' },
161
224
  body: JSON.stringify({ job_id: jobId }),
@@ -188,7 +251,7 @@ async function getRenderJobStatus(jobId) {
188
251
  */
189
252
  async function submitDeduplicationJob(params) {
190
253
  try {
191
- const response = await fetch(RENDER_SUBMIT_URL, {
254
+ const response = await modalFetch(RENDER_SUBMIT_URL, {
192
255
  method: 'POST',
193
256
  headers: { 'Content-Type': 'application/json' },
194
257
  body: JSON.stringify({
@@ -221,7 +284,7 @@ async function submitDeduplicationJob(params) {
221
284
  */
222
285
  async function submitScreenshotAnimationRenderJob(params) {
223
286
  try {
224
- const response = await fetch(RENDER_SUBMIT_URL, {
287
+ const response = await modalFetch(RENDER_SUBMIT_URL, {
225
288
  method: 'POST',
226
289
  headers: { 'Content-Type': 'application/json' },
227
290
  body: JSON.stringify({
@@ -258,7 +321,7 @@ async function submitScreenshotAnimationRenderJob(params) {
258
321
  */
259
322
  async function submitAutoCaptionRenderJob(params) {
260
323
  try {
261
- const response = await fetch(RENDER_SUBMIT_URL, {
324
+ const response = await modalFetch(RENDER_SUBMIT_URL, {
262
325
  method: 'POST',
263
326
  headers: { 'Content-Type': 'application/json' },
264
327
  body: JSON.stringify({
@@ -302,7 +365,7 @@ async function submitInstagramDmRenderJob(params) {
302
365
  try {
303
366
  const transformedMessages = transformToInstagramMessages(params.messages, params.imageAttachmentUrl);
304
367
  const hasStoryReply = params.messages.some(msg => msg.hasImage);
305
- const response = await fetch(RENDER_SUBMIT_URL, {
368
+ const response = await modalFetch(RENDER_SUBMIT_URL, {
306
369
  method: 'POST',
307
370
  headers: { 'Content-Type': 'application/json' },
308
371
  body: JSON.stringify({
@@ -346,7 +409,7 @@ async function submitInstagramDmRenderJob(params) {
346
409
  async function submitIMessageDmRenderJob(params) {
347
410
  try {
348
411
  const transformedMessages = transformToIMessageMessages(params.messages, params.imageAttachmentUrl);
349
- const response = await fetch(RENDER_SUBMIT_URL, {
412
+ const response = await modalFetch(RENDER_SUBMIT_URL, {
350
413
  method: 'POST',
351
414
  headers: { 'Content-Type': 'application/json' },
352
415
  body: JSON.stringify({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc",
3
- "version": "4.5.58",
3
+ "version": "4.5.60",
4
4
  "description": "TypeScript/JavaScript client for the UGC Inc API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -56,7 +56,7 @@
56
56
  "typescript": "^5.0.0"
57
57
  },
58
58
  "bin": {
59
- "ugcinc": "./dist/cli.js"
59
+ "ugcinc": "dist/cli.js"
60
60
  },
61
61
  "files": [
62
62
  "dist",