replicate-predictions-downloader 2.0.2 ā 2.0.3
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/example.js +5 -5
- package/index.js +101 -11
- package/package.json +12 -5
package/example.js
CHANGED
|
@@ -17,19 +17,19 @@ async function example() {
|
|
|
17
17
|
|
|
18
18
|
// Example 1: Download all predictions (default behavior)
|
|
19
19
|
console.log('\nš„ Example 1: Download all predictions');
|
|
20
|
-
const
|
|
20
|
+
const _downloader1 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN);
|
|
21
21
|
// await downloader1.run(); // Uncomment to run
|
|
22
22
|
|
|
23
23
|
// Example 2: Download predictions since a specific date
|
|
24
24
|
console.log('\nš
Example 2: Download predictions since 2024-01-01');
|
|
25
|
-
const
|
|
25
|
+
const _downloader2 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
26
26
|
since: '2024-01-01'
|
|
27
27
|
});
|
|
28
28
|
// await downloader2.run(); // Uncomment to run
|
|
29
29
|
|
|
30
30
|
// Example 3: Download predictions in a date range
|
|
31
31
|
console.log('\nš
Example 3: Download predictions between 2024-01-01 and 2024-01-31');
|
|
32
|
-
const
|
|
32
|
+
const _downloader3 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
33
33
|
since: '2024-01-01',
|
|
34
34
|
until: '2024-01-31'
|
|
35
35
|
});
|
|
@@ -37,7 +37,7 @@ async function example() {
|
|
|
37
37
|
|
|
38
38
|
// Example 4: Incremental download (since last run)
|
|
39
39
|
console.log('\nš Example 4: Incremental download since last successful run');
|
|
40
|
-
const
|
|
40
|
+
const _downloader4 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
41
41
|
lastRun: true
|
|
42
42
|
});
|
|
43
43
|
// await downloader4.run(); // Uncomment to run
|
|
@@ -46,7 +46,7 @@ async function example() {
|
|
|
46
46
|
console.log('\nš
Example 5: Download predictions from the last 7 days');
|
|
47
47
|
const sevenDaysAgo = new Date();
|
|
48
48
|
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
49
|
-
const
|
|
49
|
+
const _downloader5 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
50
50
|
since: sevenDaysAgo.toISOString()
|
|
51
51
|
});
|
|
52
52
|
// await downloader5.run(); // Uncomment to run
|
package/index.js
CHANGED
|
@@ -66,17 +66,54 @@ class ReplicateDownloader {
|
|
|
66
66
|
// Parse date string to ISO format
|
|
67
67
|
parseDate(dateStr) {
|
|
68
68
|
if (!dateStr) return null;
|
|
69
|
-
|
|
70
|
-
//
|
|
69
|
+
|
|
70
|
+
// Support common relative phrases
|
|
71
|
+
const input = String(dateStr).trim().toLowerCase();
|
|
72
|
+
const nowMs = Date.now();
|
|
73
|
+
|
|
74
|
+
// Yesterday
|
|
75
|
+
if (input === 'yesterday') {
|
|
76
|
+
return new Date(nowMs - 24 * 60 * 60 * 1000).toISOString();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// "N <unit> ago" where unit ā minutes/hours/days/weeks/months/years
|
|
80
|
+
const relMatch = input.match(/^(\d+)\s*(minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)\s+ago$/i);
|
|
81
|
+
if (relMatch) {
|
|
82
|
+
const amount = parseInt(relMatch[1], 10);
|
|
83
|
+
const unit = relMatch[2];
|
|
84
|
+
|
|
85
|
+
const unitToMs = {
|
|
86
|
+
minute: 60 * 1000,
|
|
87
|
+
minutes: 60 * 1000,
|
|
88
|
+
hour: 60 * 60 * 1000,
|
|
89
|
+
hours: 60 * 60 * 1000,
|
|
90
|
+
day: 24 * 60 * 60 * 1000,
|
|
91
|
+
days: 24 * 60 * 60 * 1000,
|
|
92
|
+
week: 7 * 24 * 60 * 60 * 1000,
|
|
93
|
+
weeks: 7 * 24 * 60 * 60 * 1000,
|
|
94
|
+
// Approximations for broader ranges
|
|
95
|
+
month: 30 * 24 * 60 * 60 * 1000,
|
|
96
|
+
months: 30 * 24 * 60 * 60 * 1000,
|
|
97
|
+
year: 365 * 24 * 60 * 60 * 1000,
|
|
98
|
+
years: 365 * 24 * 60 * 60 * 1000
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const deltaMs = amount * (unitToMs[unit] || 0);
|
|
102
|
+
if (deltaMs > 0) {
|
|
103
|
+
return new Date(nowMs - deltaMs).toISOString();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fallback to native parser (ISO, RFC, etc.)
|
|
71
108
|
const date = new Date(dateStr);
|
|
72
109
|
if (isNaN(date.getTime())) {
|
|
73
110
|
throw new Error(`Invalid date format: ${dateStr}. Use formats like "2024-01-15", "2024-01-15T10:30:00Z", or "2 days ago"`);
|
|
74
111
|
}
|
|
75
|
-
|
|
112
|
+
|
|
76
113
|
return date.toISOString();
|
|
77
114
|
}
|
|
78
115
|
|
|
79
|
-
// Get date filter parameters for API
|
|
116
|
+
// Get date filter parameters for API (server may ignore these)
|
|
80
117
|
getDateFilterParams() {
|
|
81
118
|
const params = new URLSearchParams();
|
|
82
119
|
|
|
@@ -108,6 +145,28 @@ class ReplicateDownloader {
|
|
|
108
145
|
return params.toString();
|
|
109
146
|
}
|
|
110
147
|
|
|
148
|
+
// Compute client-side date bounds for filtering
|
|
149
|
+
getDateFilterBounds() {
|
|
150
|
+
let sinceDate = this.dateFilters.since;
|
|
151
|
+
if (this.dateFilters.lastRun) {
|
|
152
|
+
const state = this.loadState();
|
|
153
|
+
if (state.lastSuccessfulRun) {
|
|
154
|
+
sinceDate = state.lastSuccessfulRun;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let sinceIsoBuffer = null;
|
|
158
|
+
if (sinceDate) {
|
|
159
|
+
const parsedSince = this.parseDate(sinceDate);
|
|
160
|
+
const sinceBuffer = new Date(new Date(parsedSince).getTime() - 1000);
|
|
161
|
+
sinceIsoBuffer = sinceBuffer.toISOString();
|
|
162
|
+
}
|
|
163
|
+
let untilIso = null;
|
|
164
|
+
if (this.dateFilters.until) {
|
|
165
|
+
untilIso = this.parseDate(this.dateFilters.until);
|
|
166
|
+
}
|
|
167
|
+
return { sinceIsoBuffer, untilIso };
|
|
168
|
+
}
|
|
169
|
+
|
|
111
170
|
// Make authenticated request to Replicate API
|
|
112
171
|
async makeRequest(url) {
|
|
113
172
|
return new Promise((resolve, reject) => {
|
|
@@ -172,7 +231,7 @@ class ReplicateDownloader {
|
|
|
172
231
|
}
|
|
173
232
|
|
|
174
233
|
// Try to find any string input that looks like a prompt
|
|
175
|
-
for (const [
|
|
234
|
+
for (const [_key, value] of Object.entries(input)) {
|
|
176
235
|
if (typeof value === 'string' && value.length > 5 && value.length < 200) {
|
|
177
236
|
return this.sanitizeFilename(value);
|
|
178
237
|
}
|
|
@@ -219,11 +278,17 @@ class ReplicateDownloader {
|
|
|
219
278
|
let nextUrl = `${this.baseUrl}/predictions`;
|
|
220
279
|
let page = 1;
|
|
221
280
|
|
|
222
|
-
// Apply date filtering
|
|
281
|
+
// Apply date filtering (server may ignore these; we also filter client-side)
|
|
223
282
|
const dateParams = this.getDateFilterParams();
|
|
224
283
|
if (dateParams) {
|
|
225
284
|
nextUrl += `?${dateParams}`;
|
|
226
|
-
console.log('Applying date filters:', dateParams);
|
|
285
|
+
console.log('Applying date filters (server or client-side):', dateParams);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Client-side bounds
|
|
289
|
+
const { sinceIsoBuffer, untilIso } = this.getDateFilterBounds();
|
|
290
|
+
if (sinceIsoBuffer || untilIso) {
|
|
291
|
+
console.log('Client-side date bounds:', { sinceIsoBuffer, untilIso });
|
|
227
292
|
}
|
|
228
293
|
|
|
229
294
|
console.log('Starting to fetch predictions...');
|
|
@@ -235,9 +300,19 @@ class ReplicateDownloader {
|
|
|
235
300
|
const response = await this.makeRequest(nextUrl);
|
|
236
301
|
|
|
237
302
|
if (response.results) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.log(`
|
|
303
|
+
const pageResults = response.results;
|
|
304
|
+
this.allPredictions.push(...pageResults);
|
|
305
|
+
console.log(`Found ${pageResults.length} predictions on page ${page}`);
|
|
306
|
+
console.log(`Total so far (pre-filter): ${this.allPredictions.length}`);
|
|
307
|
+
|
|
308
|
+
// Heuristic early stop: if all items are older than since bound, stop paginating
|
|
309
|
+
if (sinceIsoBuffer && pageResults.length > 0) {
|
|
310
|
+
const allOlder = pageResults.every(p => p.created_at && p.created_at < sinceIsoBuffer);
|
|
311
|
+
if (allOlder) {
|
|
312
|
+
console.log('Reached items older than since bound; stopping pagination early');
|
|
313
|
+
nextUrl = null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
241
316
|
}
|
|
242
317
|
|
|
243
318
|
// Handle pagination with date filters
|
|
@@ -264,7 +339,22 @@ class ReplicateDownloader {
|
|
|
264
339
|
}
|
|
265
340
|
}
|
|
266
341
|
|
|
267
|
-
|
|
342
|
+
// Final client-side filter within bounds
|
|
343
|
+
if (this.allPredictions.length > 0) {
|
|
344
|
+
const before = this.allPredictions.length;
|
|
345
|
+
this.allPredictions = this.allPredictions.filter(p => {
|
|
346
|
+
const created = p.created_at;
|
|
347
|
+
if (!created) return true;
|
|
348
|
+
if (sinceIsoBuffer && created < sinceIsoBuffer) return false;
|
|
349
|
+
if (untilIso && created > untilIso) return false;
|
|
350
|
+
return true;
|
|
351
|
+
});
|
|
352
|
+
if (sinceIsoBuffer || untilIso) {
|
|
353
|
+
console.log(`Applied client-side date filter: ${before} -> ${this.allPredictions.length}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
console.log(`\nTotal predictions fetched (post-filter): ${this.allPredictions.length}`);
|
|
268
358
|
return this.allPredictions;
|
|
269
359
|
}
|
|
270
360
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replicate-predictions-downloader",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "A Node.js script to download and organize all your Replicate predictions, including images, metadata, and other outputs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start": "node index.js",
|
|
9
9
|
"download": "node index.js",
|
|
10
|
-
"example": "node example.js"
|
|
10
|
+
"example": "node example.js",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"lint:fix": "eslint . --fix"
|
|
11
13
|
},
|
|
12
14
|
"keywords": [
|
|
13
15
|
"replicate",
|
|
@@ -41,13 +43,18 @@
|
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"archiver": "^5.3.1",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
+
"commander": "^11.0.0",
|
|
47
|
+
"dotenv": "^16.3.1"
|
|
46
48
|
},
|
|
47
49
|
"files": [
|
|
48
50
|
"index.js",
|
|
49
51
|
"example.js",
|
|
50
52
|
"README.md",
|
|
51
53
|
"LICENSE.md"
|
|
52
|
-
]
|
|
54
|
+
],
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^9.35.0",
|
|
57
|
+
"eslint": "^9.13.0",
|
|
58
|
+
"globals": "^16.3.0"
|
|
59
|
+
}
|
|
53
60
|
}
|