@nekosuneprojects/nekosunevrtools 1.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/.github/workflows/ci.yml +33 -0
- package/.github/workflows/npm-publish-github-packages.yml +35 -0
- package/.github/workflows/npm-publish.yml +34 -0
- package/README.md +201 -0
- package/bin/nekosunevrtools.js +13 -0
- package/package.json +31 -0
- package/src/cli.js +550 -0
- package/src/earnings.js +128 -0
- package/src/index.d.ts +174 -0
- package/src/index.js +25 -0
- package/src/shorturl/provider/adlinkfly-compatible.js +73 -0
- package/src/shorturl/provider/presets.js +37 -0
- package/src/shorturl/providers.js +11 -0
- package/src/upload/client.js +57 -0
- package/src/upload/provider/catbox.js +68 -0
- package/src/upload/provider/fileio.js +64 -0
- package/src/upload/provider/transfersh.js +58 -0
- package/src/upload/provider/upfiles.js +67 -0
- package/src/upload/providers.js +11 -0
- package/src/upload-client.js +1 -0
- package/src/utils/file.js +66 -0
- package/src/video/provider/doodstream.js +228 -0
- package/src/video/providers.js +5 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const axios = require("axios");
|
|
3
|
+
const { UploadClient, EarningsClient, shortenerPresets } = require("./index");
|
|
4
|
+
|
|
5
|
+
async function runCli(argv = process.argv.slice(2)) {
|
|
6
|
+
const parsed = parseArgs(argv);
|
|
7
|
+
|
|
8
|
+
if (parsed.help || !parsed.command) {
|
|
9
|
+
printHelp();
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (parsed.command === "upload") {
|
|
14
|
+
return runUploadCommand(parsed);
|
|
15
|
+
}
|
|
16
|
+
if (parsed.command === "shorten") {
|
|
17
|
+
return runShortenCommand(parsed);
|
|
18
|
+
}
|
|
19
|
+
if (parsed.command === "upload-shorten") {
|
|
20
|
+
return runUploadShortenCommand(parsed);
|
|
21
|
+
}
|
|
22
|
+
if (parsed.command === "video-upload") {
|
|
23
|
+
return runVideoUploadCommand(parsed);
|
|
24
|
+
}
|
|
25
|
+
if (parsed.command === "list-shorteners") {
|
|
26
|
+
process.stdout.write(`${Object.keys(shortenerPresets).join("\n")}\n`);
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
printHelp();
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runUploadCommand(parsed) {
|
|
35
|
+
const files = parsed.files;
|
|
36
|
+
if (files.length === 0) {
|
|
37
|
+
throw new Error("No file path provided. Use --file <path>.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const platform = parsed.platform || "upfiles";
|
|
41
|
+
const concurrency = Math.max(1, parsed.parallel || 1);
|
|
42
|
+
const retries = Math.max(0, parsed.retries || 0);
|
|
43
|
+
const timeoutMs = parsed.timeoutMs || 60000;
|
|
44
|
+
const metadata = parsed.metadata;
|
|
45
|
+
const apiKey = parsed.apiKey || null;
|
|
46
|
+
|
|
47
|
+
const client = new UploadClient({
|
|
48
|
+
platform,
|
|
49
|
+
apiKey,
|
|
50
|
+
timeoutMs
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const discord = parsed.discordWebhook ? new DiscordProgressReporter(parsed.discordWebhook, parsed.discordTitle) : null;
|
|
54
|
+
const tasks = files.map((filePath) => () => uploadWithRetries({
|
|
55
|
+
client,
|
|
56
|
+
filePath,
|
|
57
|
+
platform,
|
|
58
|
+
retries,
|
|
59
|
+
apiKey,
|
|
60
|
+
metadata,
|
|
61
|
+
discord
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
const results = await runConcurrently(tasks, concurrency);
|
|
65
|
+
return printResults(results, parsed.json);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function runShortenCommand(parsed) {
|
|
69
|
+
if (!parsed.url) {
|
|
70
|
+
throw new Error("Missing --url for shorten command.");
|
|
71
|
+
}
|
|
72
|
+
const client = new EarningsClient({
|
|
73
|
+
timeoutMs: parsed.timeoutMs || 60000
|
|
74
|
+
});
|
|
75
|
+
const result = await client.shortenUrl(parsed.url, {
|
|
76
|
+
provider: "adlinkfly-compatible",
|
|
77
|
+
apiKey: parsed.apiKey || null,
|
|
78
|
+
preset: parsed.shortenerPreset || null,
|
|
79
|
+
baseUrl: parsed.baseUrl || null,
|
|
80
|
+
alias: parsed.alias || null,
|
|
81
|
+
adsType: Number.isFinite(parsed.adsType) ? parsed.adsType : null
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (parsed.json) {
|
|
85
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
86
|
+
} else {
|
|
87
|
+
process.stdout.write(`${result.shortUrl}\n`);
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function runUploadShortenCommand(parsed) {
|
|
93
|
+
const files = parsed.files;
|
|
94
|
+
if (files.length === 0) {
|
|
95
|
+
throw new Error("No file path provided. Use --file <path>.");
|
|
96
|
+
}
|
|
97
|
+
if (!parsed.apiKey && (parsed.uploadPlatform || parsed.platform || "upfiles") === "upfiles") {
|
|
98
|
+
throw new Error("Missing --apikey for upfiles upload.");
|
|
99
|
+
}
|
|
100
|
+
if (!parsed.shortenerApiKey && !parsed.apiKey) {
|
|
101
|
+
throw new Error("Missing shortener API key. Use --shortener-apikey or --apikey.");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const earnings = new EarningsClient({
|
|
105
|
+
timeoutMs: parsed.timeoutMs || 60000,
|
|
106
|
+
upload: {
|
|
107
|
+
platform: parsed.uploadPlatform || parsed.platform || "upfiles",
|
|
108
|
+
apiKey: parsed.apiKey || null
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const discord = parsed.discordWebhook ? new DiscordProgressReporter(parsed.discordWebhook, parsed.discordTitle) : null;
|
|
112
|
+
|
|
113
|
+
const tasks = files.map((filePath) => async () => {
|
|
114
|
+
try {
|
|
115
|
+
const fileName = path.basename(filePath);
|
|
116
|
+
const result = await earnings.uploadAndShorten(filePath, {
|
|
117
|
+
uploadPlatform: parsed.uploadPlatform || parsed.platform || "upfiles",
|
|
118
|
+
uploadApiKey: parsed.apiKey || null,
|
|
119
|
+
uploadMetadata: parsed.metadata,
|
|
120
|
+
shortenerProvider: "adlinkfly-compatible",
|
|
121
|
+
shortenerApiKey: parsed.shortenerApiKey || parsed.apiKey || null,
|
|
122
|
+
shortenerPreset: parsed.shortenerPreset || null,
|
|
123
|
+
shortenerBaseUrl: parsed.baseUrl || null,
|
|
124
|
+
alias: parsed.alias || null,
|
|
125
|
+
adsType: Number.isFinite(parsed.adsType) ? parsed.adsType : null,
|
|
126
|
+
onProgress: createProgressHandler({
|
|
127
|
+
fileName,
|
|
128
|
+
attempt: 1,
|
|
129
|
+
retries: 0,
|
|
130
|
+
discord
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
if (discord) {
|
|
134
|
+
await discord.complete(fileName, result.shortlink.shortUrl);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
ok: true,
|
|
138
|
+
file: filePath,
|
|
139
|
+
result
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const message = error && error.message ? error.message : String(error);
|
|
143
|
+
if (discord) {
|
|
144
|
+
await discord.fail(path.basename(filePath), message);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
file: filePath,
|
|
149
|
+
error: message
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const results = await runConcurrently(tasks, Math.max(1, parsed.parallel || 1));
|
|
155
|
+
return printResults(results, parsed.json);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function runVideoUploadCommand(parsed) {
|
|
159
|
+
const files = parsed.files;
|
|
160
|
+
if (files.length === 0) {
|
|
161
|
+
throw new Error("No file path provided. Use --file <path>.");
|
|
162
|
+
}
|
|
163
|
+
if (!parsed.apiKey) {
|
|
164
|
+
throw new Error("Missing --apikey for video upload provider.");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const provider = parsed.videoProvider || "doodstream";
|
|
168
|
+
const earnings = new EarningsClient({
|
|
169
|
+
timeoutMs: parsed.timeoutMs || 60000
|
|
170
|
+
});
|
|
171
|
+
const discord = parsed.discordWebhook ? new DiscordProgressReporter(parsed.discordWebhook, parsed.discordTitle) : null;
|
|
172
|
+
|
|
173
|
+
const tasks = files.map((filePath) => async () => {
|
|
174
|
+
try {
|
|
175
|
+
const fileName = path.basename(filePath);
|
|
176
|
+
const result = await earnings.uploadVideo(filePath, {
|
|
177
|
+
provider,
|
|
178
|
+
apiKey: parsed.apiKey,
|
|
179
|
+
onProgress: createProgressHandler({
|
|
180
|
+
fileName,
|
|
181
|
+
attempt: 1,
|
|
182
|
+
retries: 0,
|
|
183
|
+
discord
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
if (discord) {
|
|
187
|
+
await discord.complete(fileName, result.watchUrl);
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
ok: true,
|
|
191
|
+
file: filePath,
|
|
192
|
+
result
|
|
193
|
+
};
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const message = error && error.message ? error.message : String(error);
|
|
196
|
+
if (discord) {
|
|
197
|
+
await discord.fail(path.basename(filePath), message);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
ok: false,
|
|
201
|
+
file: filePath,
|
|
202
|
+
error: message
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const results = await runConcurrently(tasks, Math.max(1, parsed.parallel || 1));
|
|
208
|
+
return printResults(results, parsed.json);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function printResults(results, asJson) {
|
|
212
|
+
const success = results.filter((item) => item.ok).length;
|
|
213
|
+
const failed = results.length - success;
|
|
214
|
+
if (asJson) {
|
|
215
|
+
process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
|
|
216
|
+
} else {
|
|
217
|
+
for (const result of results) {
|
|
218
|
+
if (result.ok) {
|
|
219
|
+
const finalUrl = getResultUrl(result.result);
|
|
220
|
+
process.stdout.write(`[ok] ${result.file} -> ${finalUrl}\n`);
|
|
221
|
+
} else {
|
|
222
|
+
process.stdout.write(`[fail] ${result.file} -> ${result.error}\n`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
process.stdout.write(`Done: ${success} success, ${failed} failed.\n`);
|
|
226
|
+
}
|
|
227
|
+
return failed > 0 ? 1 : 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getResultUrl(result) {
|
|
231
|
+
if (!result) {
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
if (result.url) {
|
|
235
|
+
return result.url;
|
|
236
|
+
}
|
|
237
|
+
if (result.watchUrl) {
|
|
238
|
+
return result.watchUrl;
|
|
239
|
+
}
|
|
240
|
+
if (result.shortlink && result.shortlink.shortUrl) {
|
|
241
|
+
return result.shortlink.shortUrl;
|
|
242
|
+
}
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function uploadWithRetries(params) {
|
|
247
|
+
const { client, filePath, platform, retries, apiKey, metadata, discord } = params;
|
|
248
|
+
let attempt = 0;
|
|
249
|
+
while (attempt <= retries) {
|
|
250
|
+
attempt += 1;
|
|
251
|
+
try {
|
|
252
|
+
const fileName = path.basename(filePath);
|
|
253
|
+
const progressHandler = createProgressHandler({ fileName, attempt, retries, discord });
|
|
254
|
+
|
|
255
|
+
const result = await client.upload(filePath, {
|
|
256
|
+
platform,
|
|
257
|
+
apiKey,
|
|
258
|
+
metadata,
|
|
259
|
+
onProgress: progressHandler
|
|
260
|
+
});
|
|
261
|
+
await maybeReportComplete(discord, fileName, result.url);
|
|
262
|
+
return {
|
|
263
|
+
ok: true,
|
|
264
|
+
file: filePath,
|
|
265
|
+
attempts: attempt,
|
|
266
|
+
result
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const message = error && error.message ? error.message : String(error);
|
|
270
|
+
if (attempt > retries) {
|
|
271
|
+
await maybeReportFail(discord, path.basename(filePath), message);
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
file: filePath,
|
|
275
|
+
attempts: attempt,
|
|
276
|
+
error: message
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
process.stdout.write(`[retry] ${filePath} attempt ${attempt}/${retries + 1}: ${message}\n`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function createProgressHandler({ fileName, attempt, retries, discord }) {
|
|
285
|
+
let lastPercent = -1;
|
|
286
|
+
return (progress) => {
|
|
287
|
+
const percent = typeof progress.percent === "number" ? progress.percent : 0;
|
|
288
|
+
if (percent !== lastPercent && (percent % 5 === 0 || percent === 100)) {
|
|
289
|
+
process.stdout.write(`[${fileName}] ${percent}% (attempt ${attempt}/${retries + 1})\n`);
|
|
290
|
+
lastPercent = percent;
|
|
291
|
+
}
|
|
292
|
+
if (discord) {
|
|
293
|
+
discord.update(fileName, percent, progress.phase || "uploading").catch(() => {});
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function maybeReportComplete(discord, fileName, url) {
|
|
299
|
+
if (!discord) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
await discord.complete(fileName, url);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function maybeReportFail(discord, fileName, message) {
|
|
306
|
+
if (!discord) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
await discord.fail(fileName, message);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
class DiscordProgressReporter {
|
|
313
|
+
constructor(webhookUrl, title) {
|
|
314
|
+
this.webhookUrl = webhookUrl;
|
|
315
|
+
this.title = title || "Upload Progress";
|
|
316
|
+
this.messages = new Map();
|
|
317
|
+
this.lastUpdate = new Map();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async update(fileName, percent, phase) {
|
|
321
|
+
const now = Date.now();
|
|
322
|
+
const key = fileName;
|
|
323
|
+
const last = this.lastUpdate.get(key) || 0;
|
|
324
|
+
if (percent !== 100 && now - last < 3000 && percent % 10 !== 0) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
this.lastUpdate.set(key, now);
|
|
328
|
+
|
|
329
|
+
const embed = {
|
|
330
|
+
title: this.title,
|
|
331
|
+
description: `${fileName}\n${buildProgressBar(percent)} ${percent}%`,
|
|
332
|
+
color: 0x3498db,
|
|
333
|
+
fields: [
|
|
334
|
+
{ name: "Status", value: phase || "uploading", inline: true }
|
|
335
|
+
],
|
|
336
|
+
timestamp: new Date().toISOString()
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
await this.upsertMessage(fileName, embed);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async complete(fileName, url) {
|
|
343
|
+
const embed = {
|
|
344
|
+
title: this.title,
|
|
345
|
+
description: `${fileName}\n${buildProgressBar(100)} 100%`,
|
|
346
|
+
color: 0x2ecc71,
|
|
347
|
+
fields: [
|
|
348
|
+
{ name: "Status", value: "completed", inline: true },
|
|
349
|
+
{ name: "URL", value: truncate(url, 1024), inline: false }
|
|
350
|
+
],
|
|
351
|
+
timestamp: new Date().toISOString()
|
|
352
|
+
};
|
|
353
|
+
await this.upsertMessage(fileName, embed);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async fail(fileName, message) {
|
|
357
|
+
const embed = {
|
|
358
|
+
title: this.title,
|
|
359
|
+
description: `${fileName}\n${buildProgressBar(0)} 0%`,
|
|
360
|
+
color: 0xe74c3c,
|
|
361
|
+
fields: [
|
|
362
|
+
{ name: "Status", value: "failed", inline: true },
|
|
363
|
+
{ name: "Error", value: truncate(message, 1024), inline: false }
|
|
364
|
+
],
|
|
365
|
+
timestamp: new Date().toISOString()
|
|
366
|
+
};
|
|
367
|
+
await this.upsertMessage(fileName, embed);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async upsertMessage(fileName, embed) {
|
|
371
|
+
const messageId = this.messages.get(fileName);
|
|
372
|
+
if (!messageId) {
|
|
373
|
+
const response = await axios.post(`${this.webhookUrl}?wait=true`, { embeds: [embed] });
|
|
374
|
+
const createdId = response && response.data && response.data.id;
|
|
375
|
+
if (createdId) {
|
|
376
|
+
this.messages.set(fileName, createdId);
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
await axios.patch(`${this.webhookUrl}/messages/${messageId}`, { embeds: [embed] });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function buildProgressBar(percent) {
|
|
385
|
+
const clamped = Math.max(0, Math.min(100, Math.floor(percent)));
|
|
386
|
+
const size = 20;
|
|
387
|
+
const filled = Math.round((clamped / 100) * size);
|
|
388
|
+
return `[${"#".repeat(filled)}${"-".repeat(size - filled)}]`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function truncate(text, max) {
|
|
392
|
+
const value = String(text || "");
|
|
393
|
+
if (value.length <= max) {
|
|
394
|
+
return value;
|
|
395
|
+
}
|
|
396
|
+
return `${value.slice(0, max - 3)}...`;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function runConcurrently(tasks, concurrency) {
|
|
400
|
+
const results = [];
|
|
401
|
+
let index = 0;
|
|
402
|
+
const workers = Array.from({ length: concurrency }, async () => {
|
|
403
|
+
while (index < tasks.length) {
|
|
404
|
+
const taskIndex = index;
|
|
405
|
+
index += 1;
|
|
406
|
+
results[taskIndex] = await tasks[taskIndex]();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
await Promise.all(workers);
|
|
410
|
+
return results;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function parseArgs(argv) {
|
|
414
|
+
const firstToken = argv[0];
|
|
415
|
+
const hasCommand = firstToken && !String(firstToken).startsWith("-");
|
|
416
|
+
const result = {
|
|
417
|
+
command: hasCommand ? firstToken : null,
|
|
418
|
+
files: [],
|
|
419
|
+
metadata: {},
|
|
420
|
+
help: false,
|
|
421
|
+
json: false
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
for (let i = hasCommand ? 1 : 0; i < argv.length; i += 1) {
|
|
425
|
+
const token = argv[i];
|
|
426
|
+
if (token === "--help" || token === "-h") {
|
|
427
|
+
result.help = true;
|
|
428
|
+
} else if (token === "--json") {
|
|
429
|
+
result.json = true;
|
|
430
|
+
} else if (token === "--file" || token === "-f") {
|
|
431
|
+
const value = argv[i + 1];
|
|
432
|
+
i += 1;
|
|
433
|
+
if (value) {
|
|
434
|
+
result.files.push(value);
|
|
435
|
+
}
|
|
436
|
+
} else if (token === "--url") {
|
|
437
|
+
result.url = argv[i + 1];
|
|
438
|
+
i += 1;
|
|
439
|
+
} else if (token === "--platform" || token === "-p") {
|
|
440
|
+
result.platform = argv[i + 1];
|
|
441
|
+
i += 1;
|
|
442
|
+
} else if (token === "--upload-platform") {
|
|
443
|
+
result.uploadPlatform = argv[i + 1];
|
|
444
|
+
i += 1;
|
|
445
|
+
} else if (token === "--video-provider") {
|
|
446
|
+
result.videoProvider = argv[i + 1];
|
|
447
|
+
i += 1;
|
|
448
|
+
} else if (token === "--apikey" || token === "--api-key" || token === "-k") {
|
|
449
|
+
result.apiKey = argv[i + 1];
|
|
450
|
+
i += 1;
|
|
451
|
+
} else if (token === "--shortener-apikey") {
|
|
452
|
+
result.shortenerApiKey = argv[i + 1];
|
|
453
|
+
i += 1;
|
|
454
|
+
} else if (token === "--shortener-preset") {
|
|
455
|
+
result.shortenerPreset = argv[i + 1];
|
|
456
|
+
i += 1;
|
|
457
|
+
} else if (token === "--base-url") {
|
|
458
|
+
result.baseUrl = argv[i + 1];
|
|
459
|
+
i += 1;
|
|
460
|
+
} else if (token === "--alias") {
|
|
461
|
+
result.alias = argv[i + 1];
|
|
462
|
+
i += 1;
|
|
463
|
+
} else if (token === "--ads-type") {
|
|
464
|
+
result.adsType = Number(argv[i + 1]);
|
|
465
|
+
i += 1;
|
|
466
|
+
} else if (token === "--timeout") {
|
|
467
|
+
result.timeoutMs = Number(argv[i + 1]);
|
|
468
|
+
i += 1;
|
|
469
|
+
} else if (token === "--retry" || token === "--retries" || token === "-r") {
|
|
470
|
+
result.retries = Number(argv[i + 1]);
|
|
471
|
+
i += 1;
|
|
472
|
+
} else if (token === "--parallel") {
|
|
473
|
+
result.parallel = Number(argv[i + 1]);
|
|
474
|
+
i += 1;
|
|
475
|
+
} else if (token === "--discord-webhook") {
|
|
476
|
+
result.discordWebhook = argv[i + 1];
|
|
477
|
+
i += 1;
|
|
478
|
+
} else if (token === "--discord-title") {
|
|
479
|
+
result.discordTitle = argv[i + 1];
|
|
480
|
+
i += 1;
|
|
481
|
+
} else if (token === "--expires") {
|
|
482
|
+
result.metadata.expires = argv[i + 1];
|
|
483
|
+
i += 1;
|
|
484
|
+
} else if (token === "--meta") {
|
|
485
|
+
const pair = argv[i + 1];
|
|
486
|
+
i += 1;
|
|
487
|
+
if (pair && pair.includes("=")) {
|
|
488
|
+
const eqIndex = pair.indexOf("=");
|
|
489
|
+
const key = pair.slice(0, eqIndex);
|
|
490
|
+
const value = pair.slice(eqIndex + 1);
|
|
491
|
+
result.metadata[key] = value;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function printHelp() {
|
|
500
|
+
const helpText = `
|
|
501
|
+
upload2earn CLI
|
|
502
|
+
|
|
503
|
+
Commands:
|
|
504
|
+
upload Upload files
|
|
505
|
+
shorten Convert a URL into monetized short link
|
|
506
|
+
upload-shorten Upload file then monetize resulting URL
|
|
507
|
+
video-upload Upload video to video-earn provider
|
|
508
|
+
list-shorteners Show built-in shortlink preset names
|
|
509
|
+
|
|
510
|
+
Usage:
|
|
511
|
+
upload2earn upload --file <path> [options]
|
|
512
|
+
upload2earn shorten --url <url> --shortener-preset shrinkme --apikey <key>
|
|
513
|
+
upload2earn upload-shorten --file <path> --upload-platform upfiles --apikey <upload-key> --shortener-preset shrinkme --shortener-apikey <short-key>
|
|
514
|
+
upload2earn video-upload --file <video.mp4> --video-provider doodstream --apikey <key>
|
|
515
|
+
|
|
516
|
+
Shared options:
|
|
517
|
+
--json JSON output
|
|
518
|
+
--help, -h Show help
|
|
519
|
+
|
|
520
|
+
Upload options:
|
|
521
|
+
--platform, -p <name> Platform: upfiles|fileio|catbox|transfersh (default: upfiles)
|
|
522
|
+
--file, -f <path> File path (repeatable)
|
|
523
|
+
--apikey, --api-key, -k API key for provider
|
|
524
|
+
--expires <value> file.io expiration metadata (example: 1w)
|
|
525
|
+
--meta key=value Additional metadata field (repeatable)
|
|
526
|
+
--retry, --retries, -r <n> Retry count per file (default: 0)
|
|
527
|
+
--parallel <n> Parallel uploads (default: 1)
|
|
528
|
+
--timeout <ms> Request timeout in milliseconds (default: 60000)
|
|
529
|
+
|
|
530
|
+
Short-link options:
|
|
531
|
+
--url <url> URL to shorten
|
|
532
|
+
--shortener-preset <name> Preset from list-shorteners
|
|
533
|
+
--base-url <url> Custom AdLinkFly-compatible API base URL
|
|
534
|
+
--shortener-apikey <key> API key for shortener (or reuse --apikey)
|
|
535
|
+
--alias <value> Custom short alias
|
|
536
|
+
--ads-type <1|2> 1=interstitial, 2=banner for many AdLinkFly APIs
|
|
537
|
+
|
|
538
|
+
Video-earn options:
|
|
539
|
+
--video-provider <name> Currently: doodstream
|
|
540
|
+
|
|
541
|
+
Discord progress options:
|
|
542
|
+
--discord-webhook <url> Discord webhook URL for embed progress updates
|
|
543
|
+
--discord-title <text> Discord embed title (default: Upload Progress)
|
|
544
|
+
`;
|
|
545
|
+
process.stdout.write(helpText);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
module.exports = {
|
|
549
|
+
runCli
|
|
550
|
+
};
|
package/src/earnings.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const { UploadClient } = require("./upload/client");
|
|
3
|
+
const { providers: shorturlProviders, presets: shortenerPresets } = require("./shorturl/providers");
|
|
4
|
+
const videoProviders = require("./video/providers");
|
|
5
|
+
|
|
6
|
+
class EarningsClient {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.timeoutMs = options.timeoutMs || 60000;
|
|
9
|
+
this.uploadClient = options.uploadClient || new UploadClient(options.upload || {});
|
|
10
|
+
this.shorteners = { ...shorturlProviders };
|
|
11
|
+
this.videos = { ...videoProviders };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
registerShortener(name, adapter) {
|
|
15
|
+
if (!name || typeof name !== "string") {
|
|
16
|
+
throw new Error("Shortener name must be a non-empty string.");
|
|
17
|
+
}
|
|
18
|
+
if (!adapter || typeof adapter.shorten !== "function") {
|
|
19
|
+
throw new Error("Shortener adapter must provide shorten(context).");
|
|
20
|
+
}
|
|
21
|
+
this.shorteners[name] = adapter;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
registerVideoProvider(name, adapter) {
|
|
26
|
+
if (!name || typeof name !== "string") {
|
|
27
|
+
throw new Error("Video provider name must be a non-empty string.");
|
|
28
|
+
}
|
|
29
|
+
if (!adapter || typeof adapter.upload !== "function") {
|
|
30
|
+
throw new Error("Video adapter must provide upload(context).");
|
|
31
|
+
}
|
|
32
|
+
this.videos[name] = adapter;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async shortenUrl(longUrl, options = {}) {
|
|
37
|
+
if (!longUrl || typeof longUrl !== "string") {
|
|
38
|
+
throw new Error("longUrl is required and must be a string.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const providerName = options.provider || "adlinkfly-compatible";
|
|
42
|
+
const adapter = this.shorteners[providerName];
|
|
43
|
+
if (!adapter) {
|
|
44
|
+
throw new Error(`Unsupported shortener provider "${providerName}".`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resolved = resolveShortenerOptions(providerName, options);
|
|
48
|
+
return adapter.shorten({
|
|
49
|
+
longUrl,
|
|
50
|
+
apiKey: options.apiKey || null,
|
|
51
|
+
options: resolved,
|
|
52
|
+
http: axios.create({ timeout: options.timeoutMs || this.timeoutMs })
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async uploadAndShorten(filePath, options = {}) {
|
|
57
|
+
const uploadResult = await this.uploadClient.upload(filePath, {
|
|
58
|
+
platform: options.uploadPlatform,
|
|
59
|
+
apiKey: options.uploadApiKey,
|
|
60
|
+
metadata: options.uploadMetadata,
|
|
61
|
+
headers: options.uploadHeaders,
|
|
62
|
+
timeoutMs: options.timeoutMs || this.timeoutMs,
|
|
63
|
+
onProgress: options.onProgress
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const shortResult = await this.shortenUrl(uploadResult.url, {
|
|
67
|
+
provider: options.shortenerProvider || "adlinkfly-compatible",
|
|
68
|
+
apiKey: options.shortenerApiKey,
|
|
69
|
+
preset: options.shortenerPreset,
|
|
70
|
+
baseUrl: options.shortenerBaseUrl,
|
|
71
|
+
alias: options.alias,
|
|
72
|
+
adsType: options.adsType,
|
|
73
|
+
responseFormat: options.responseFormat,
|
|
74
|
+
timeoutMs: options.timeoutMs || this.timeoutMs
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
upload: uploadResult,
|
|
80
|
+
shortlink: shortResult
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async uploadVideo(filePath, options = {}) {
|
|
85
|
+
const provider = options.provider || "doodstream";
|
|
86
|
+
const adapter = this.videos[provider];
|
|
87
|
+
if (!adapter) {
|
|
88
|
+
throw new Error(`Unsupported video provider "${provider}".`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return adapter.upload({
|
|
92
|
+
filePath,
|
|
93
|
+
apiKey: options.apiKey || null,
|
|
94
|
+
metadata: options.metadata || {},
|
|
95
|
+
onProgress: options.onProgress,
|
|
96
|
+
http: axios.create({ timeout: options.timeoutMs || this.timeoutMs })
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveShortenerOptions(provider, options) {
|
|
102
|
+
if (provider !== "adlinkfly-compatible") {
|
|
103
|
+
return options;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let baseUrl = options.baseUrl || null;
|
|
107
|
+
if (!baseUrl && options.preset) {
|
|
108
|
+
baseUrl = shortenerPresets[options.preset] || null;
|
|
109
|
+
}
|
|
110
|
+
if (!baseUrl) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Missing shortener base URL. Use "baseUrl" or "preset". Presets: ${Object.keys(shortenerPresets).join(", ")}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
provider,
|
|
118
|
+
baseUrl,
|
|
119
|
+
alias: options.alias || null,
|
|
120
|
+
adsType: typeof options.adsType === "number" ? options.adsType : null,
|
|
121
|
+
responseFormat: options.responseFormat || "json"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
EarningsClient,
|
|
127
|
+
shortenerPresets
|
|
128
|
+
};
|