@nekosuneprojects/nekosunevrtools 1.1.0 → 1.1.1
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 +111 -0
- package/package.json +14 -3
- package/src/cli.js +728 -14
- package/src/downloader/client.js +126 -0
- package/src/downloader/provider/compatible.js +127 -0
- package/src/downloader/providers.js +15 -0
- package/src/games/client.js +162 -0
- package/src/games/provider/nekosune-api.js +329 -0
- package/src/games/providers.js +14 -0
- package/src/index.d.ts +140 -0
- package/src/index.js +26 -2
- package/src/livestream/client.js +70 -0
- package/src/livestream/provider/nekosune-api.js +56 -0
- package/src/livestream/providers.js +14 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const { presets, providers } = require("./providers");
|
|
3
|
+
|
|
4
|
+
class DownloaderClient {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.timeoutMs = options.timeoutMs || 120000;
|
|
7
|
+
this.apiKey = options.apiKey || null;
|
|
8
|
+
this.baseUrl = resolveBaseUrl(options.baseUrl, options.preset || "nekosune");
|
|
9
|
+
this.provider = providers.compatible(this.baseUrl);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
useBaseUrl(baseUrl) {
|
|
13
|
+
this.baseUrl = resolveBaseUrl(baseUrl);
|
|
14
|
+
this.provider = providers.compatible(this.baseUrl);
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
usePreset(name) {
|
|
19
|
+
this.baseUrl = resolveBaseUrl(null, name);
|
|
20
|
+
this.provider = providers.compatible(this.baseUrl);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async createMp3Job(link, options = {}) {
|
|
25
|
+
return this.provider.createMp3Job({
|
|
26
|
+
http: this.createHttp(options),
|
|
27
|
+
apiKey: options.apiKey || this.apiKey,
|
|
28
|
+
link,
|
|
29
|
+
uploadDest: options.uploadDest || "cdn"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async createMp4Job(link, options = {}) {
|
|
34
|
+
return this.provider.createMp4Job({
|
|
35
|
+
http: this.createHttp(options),
|
|
36
|
+
apiKey: options.apiKey || this.apiKey,
|
|
37
|
+
link,
|
|
38
|
+
uploadDest: options.uploadDest || "cdn"
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getJob(jobId, options = {}) {
|
|
43
|
+
return this.provider.getJob({
|
|
44
|
+
http: this.createHttp(options),
|
|
45
|
+
apiKey: options.apiKey || this.apiKey,
|
|
46
|
+
jobId
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async waitForJob(jobId, options = {}) {
|
|
51
|
+
const intervalMs = Math.max(500, options.intervalMs || 2000);
|
|
52
|
+
const timeoutMs = Math.max(intervalMs, options.timeoutMs || 300000);
|
|
53
|
+
const started = Date.now();
|
|
54
|
+
|
|
55
|
+
while (true) {
|
|
56
|
+
const state = await this.getJob(jobId, options);
|
|
57
|
+
if (typeof options.onProgress === "function") {
|
|
58
|
+
options.onProgress(state);
|
|
59
|
+
}
|
|
60
|
+
if (state && state.status === "done") {
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
if (state && (state.status === "error" || state.status === "failed")) {
|
|
64
|
+
throw new Error(state.message || "Downloader job failed.");
|
|
65
|
+
}
|
|
66
|
+
if (Date.now() - started > timeoutMs) {
|
|
67
|
+
throw new Error("Downloader job wait timed out.");
|
|
68
|
+
}
|
|
69
|
+
await sleep(intervalMs);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getInfo(url, options = {}) {
|
|
74
|
+
return this.provider.getInfo({
|
|
75
|
+
http: this.createHttp(options),
|
|
76
|
+
url,
|
|
77
|
+
flat: Boolean(options.flat || options.flatPlaylist),
|
|
78
|
+
fields: options.fields || "basic",
|
|
79
|
+
cache: typeof options.cache === "number" ? options.cache : 1
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async searchYouTube(query, options = {}) {
|
|
84
|
+
return this.provider.searchYouTube({
|
|
85
|
+
http: this.createHttp(options),
|
|
86
|
+
query,
|
|
87
|
+
limit: typeof options.limit === "number" ? options.limit : 5
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getStreamUrl(url) {
|
|
92
|
+
return this.provider.getStreamUrl({ url });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async stream(url, options = {}) {
|
|
96
|
+
return this.provider.stream({
|
|
97
|
+
http: this.createHttp(options),
|
|
98
|
+
url,
|
|
99
|
+
responseType: options.responseType || "stream"
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createHttp(options = {}) {
|
|
104
|
+
return axios.create({ timeout: options.timeoutMs || this.timeoutMs });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveBaseUrl(baseUrl, preset) {
|
|
109
|
+
if (baseUrl) {
|
|
110
|
+
return String(baseUrl).replace(/\/+$/, "");
|
|
111
|
+
}
|
|
112
|
+
if (preset && presets[preset]) {
|
|
113
|
+
return presets[preset];
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Unknown downloader preset "${preset}". Available: ${Object.keys(presets).join(", ")}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sleep(ms) {
|
|
119
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
DownloaderClient,
|
|
124
|
+
presets,
|
|
125
|
+
providers
|
|
126
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
function createDownloaderProvider(baseUrl) {
|
|
2
|
+
const normalizedBaseUrl = String(baseUrl || "").replace(/\/+$/, "");
|
|
3
|
+
if (!normalizedBaseUrl) {
|
|
4
|
+
throw new Error("Missing downloader baseUrl.");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async function createMp3Job(context) {
|
|
8
|
+
return createJob(context, "mp3");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function createMp4Job(context) {
|
|
12
|
+
return createJob(context, "mp4");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function createJob(context, mode) {
|
|
16
|
+
const { apiKey, http, link, uploadDest } = context;
|
|
17
|
+
assertApiKey(apiKey);
|
|
18
|
+
if (!link) {
|
|
19
|
+
throw new Error(`Missing "link" for ${mode} job.`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const response = await http.post(`${normalizedBaseUrl}/api/${mode}/jobs`, {
|
|
23
|
+
link,
|
|
24
|
+
upload_dest: uploadDest || "cdn"
|
|
25
|
+
}, {
|
|
26
|
+
headers: buildAuthHeaders(apiKey)
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const body = response.data || {};
|
|
30
|
+
if (!body.job_id) {
|
|
31
|
+
throw new Error(`Downloader ${mode} job did not return job_id.`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
provider: "downloader-compatible",
|
|
36
|
+
jobId: body.job_id,
|
|
37
|
+
raw: body
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function getJob(context) {
|
|
42
|
+
const { apiKey, http, jobId } = context;
|
|
43
|
+
assertApiKey(apiKey);
|
|
44
|
+
if (!jobId) {
|
|
45
|
+
throw new Error('Missing "jobId".');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const response = await http.get(`${normalizedBaseUrl}/api/jobs/${encodeURIComponent(jobId)}`, {
|
|
49
|
+
headers: buildAuthHeaders(apiKey)
|
|
50
|
+
});
|
|
51
|
+
return response.data || {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function getInfo(context) {
|
|
55
|
+
const { http, url, flat, fields, cache } = context;
|
|
56
|
+
if (!url) {
|
|
57
|
+
throw new Error('Missing "url" for info endpoint.');
|
|
58
|
+
}
|
|
59
|
+
const response = await http.get(`${normalizedBaseUrl}/api/info`, {
|
|
60
|
+
params: {
|
|
61
|
+
url,
|
|
62
|
+
flat: flat ? 1 : undefined,
|
|
63
|
+
fields: fields || undefined,
|
|
64
|
+
cache: typeof cache === "number" ? cache : undefined
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return response.data || {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function searchYouTube(context) {
|
|
71
|
+
const { http, query, limit } = context;
|
|
72
|
+
if (!query) {
|
|
73
|
+
throw new Error('Missing "query" for youtube search.');
|
|
74
|
+
}
|
|
75
|
+
const response = await http.get(`${normalizedBaseUrl}/api/search/youtube`, {
|
|
76
|
+
params: {
|
|
77
|
+
q: query,
|
|
78
|
+
limit: typeof limit === "number" ? limit : undefined
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return response.data || {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getStreamUrl(context) {
|
|
85
|
+
const { url } = context;
|
|
86
|
+
if (!url) {
|
|
87
|
+
throw new Error('Missing "url" for stream endpoint.');
|
|
88
|
+
}
|
|
89
|
+
return `${normalizedBaseUrl}/api/stream?url=${encodeURIComponent(url)}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function stream(context) {
|
|
93
|
+
const { http, url, responseType } = context;
|
|
94
|
+
const streamUrl = getStreamUrl({ url });
|
|
95
|
+
const response = await http.get(streamUrl, {
|
|
96
|
+
responseType: responseType || "stream"
|
|
97
|
+
});
|
|
98
|
+
return response;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
createMp3Job,
|
|
103
|
+
createMp4Job,
|
|
104
|
+
getJob,
|
|
105
|
+
getInfo,
|
|
106
|
+
searchYouTube,
|
|
107
|
+
getStreamUrl,
|
|
108
|
+
stream
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function assertApiKey(apiKey) {
|
|
113
|
+
if (!apiKey) {
|
|
114
|
+
throw new Error('Missing downloader apiKey. Pass "apiKey" in client options or call options.');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildAuthHeaders(apiKey) {
|
|
119
|
+
return {
|
|
120
|
+
"x-api-key": apiKey,
|
|
121
|
+
Authorization: `Bearer ${apiKey}`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
createDownloaderProvider
|
|
127
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { createDownloaderProvider } = require("./provider/compatible");
|
|
2
|
+
|
|
3
|
+
const presets = {
|
|
4
|
+
nekosune: "https://dl.nekosunevr.co.uk",
|
|
5
|
+
ballisticok: "https://dl.ballisticok.xyz"
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const providers = {
|
|
9
|
+
compatible: createDownloaderProvider
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
presets,
|
|
14
|
+
providers
|
|
15
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const { presets, providers } = require("./providers");
|
|
3
|
+
|
|
4
|
+
class GamesClient {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.timeoutMs = options.timeoutMs || 60000;
|
|
7
|
+
this.apiKey = options.apiKey || null;
|
|
8
|
+
this.baseUrl = resolveBaseUrl(options.baseUrl, options.preset || "nekosune");
|
|
9
|
+
this.provider = providers.nekosune(this.baseUrl);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
useBaseUrl(baseUrl) {
|
|
13
|
+
this.baseUrl = resolveBaseUrl(baseUrl);
|
|
14
|
+
this.provider = providers.nekosune(this.baseUrl);
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
usePreset(name) {
|
|
19
|
+
this.baseUrl = resolveBaseUrl(null, name);
|
|
20
|
+
this.provider = providers.nekosune(this.baseUrl);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getClashOfClansClan(clanTag, options = {}) {
|
|
25
|
+
return this.provider.getClashOfClansClan({
|
|
26
|
+
http: this.createHttp(options),
|
|
27
|
+
apiKey: options.apiKey || this.apiKey,
|
|
28
|
+
clanTag
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getClashOfClansPlayer(playerTag, options = {}) {
|
|
33
|
+
return this.provider.getClashOfClansPlayer({
|
|
34
|
+
http: this.createHttp(options),
|
|
35
|
+
apiKey: options.apiKey || this.apiKey,
|
|
36
|
+
playerTag
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getDivision2Player(username, platform = "psn", options = {}) {
|
|
41
|
+
return this.provider.getDivision2Player({
|
|
42
|
+
http: this.createHttp(options),
|
|
43
|
+
apiKey: options.apiKey || this.apiKey,
|
|
44
|
+
username,
|
|
45
|
+
platform
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getFortnitePlayer(username, timeWindow = "lifetime", options = {}) {
|
|
50
|
+
return this.provider.getFortnitePlayer({
|
|
51
|
+
http: this.createHttp(options),
|
|
52
|
+
apiKey: options.apiKey || this.apiKey,
|
|
53
|
+
username,
|
|
54
|
+
timeWindow
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getFortniteCreatorCode(creatorCode, options = {}) {
|
|
59
|
+
return this.provider.getFortniteCreatorCode({
|
|
60
|
+
http: this.createHttp(options),
|
|
61
|
+
apiKey: options.apiKey || this.apiKey,
|
|
62
|
+
creatorCode
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getFortniteItemShop(options = {}) {
|
|
67
|
+
return this.provider.getFortniteItemShop({
|
|
68
|
+
http: this.createHttp(options),
|
|
69
|
+
apiKey: options.apiKey || this.apiKey
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getWynncraftProfile(username, options = {}) {
|
|
74
|
+
return this.provider.getWynncraftProfile({
|
|
75
|
+
http: this.createHttp(options),
|
|
76
|
+
apiKey: options.apiKey || this.apiKey,
|
|
77
|
+
username
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getHypixelProfile(username, options = {}) {
|
|
82
|
+
return this.provider.getHypixelProfile({
|
|
83
|
+
http: this.createHttp(options),
|
|
84
|
+
apiKey: options.apiKey || this.apiKey,
|
|
85
|
+
username
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getRocketLeaguePlayer(username, platform = "psn", options = {}) {
|
|
90
|
+
return this.provider.getRocketLeaguePlayer({
|
|
91
|
+
http: this.createHttp(options),
|
|
92
|
+
apiKey: options.apiKey || this.apiKey,
|
|
93
|
+
username,
|
|
94
|
+
platform
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getApexLegendsPlayer(username, platform = "psn", options = {}) {
|
|
99
|
+
return this.provider.getApexLegendsPlayer({
|
|
100
|
+
http: this.createHttp(options),
|
|
101
|
+
apiKey: options.apiKey || this.apiKey,
|
|
102
|
+
username,
|
|
103
|
+
platform
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getBattlefield1Player(username, platform = "psn", options = {}) {
|
|
108
|
+
return this.provider.getBattlefield1Player({
|
|
109
|
+
http: this.createHttp(options),
|
|
110
|
+
apiKey: options.apiKey || this.apiKey,
|
|
111
|
+
username,
|
|
112
|
+
platform
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getBattlefield5Player(username, platform = "psn", options = {}) {
|
|
117
|
+
return this.provider.getBattlefield5Player({
|
|
118
|
+
http: this.createHttp(options),
|
|
119
|
+
apiKey: options.apiKey || this.apiKey,
|
|
120
|
+
username,
|
|
121
|
+
platform
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getBattlefield2042Player(username, platform = "ea", options = {}) {
|
|
126
|
+
return this.provider.getBattlefield2042Player({
|
|
127
|
+
http: this.createHttp(options),
|
|
128
|
+
apiKey: options.apiKey || this.apiKey,
|
|
129
|
+
username,
|
|
130
|
+
platform
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
getBattlefield6Player(username, platform = "psn", options = {}) {
|
|
135
|
+
return this.provider.getBattlefield6Player({
|
|
136
|
+
http: this.createHttp(options),
|
|
137
|
+
apiKey: options.apiKey || this.apiKey,
|
|
138
|
+
username,
|
|
139
|
+
platform
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
createHttp(options = {}) {
|
|
144
|
+
return axios.create({ timeout: options.timeoutMs || this.timeoutMs });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolveBaseUrl(baseUrl, preset) {
|
|
149
|
+
if (baseUrl) {
|
|
150
|
+
return String(baseUrl).replace(/\/+$/, "");
|
|
151
|
+
}
|
|
152
|
+
if (preset && presets[preset]) {
|
|
153
|
+
return presets[preset];
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Unknown games preset "${preset}". Available: ${Object.keys(presets).join(", ")}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
GamesClient,
|
|
160
|
+
presets,
|
|
161
|
+
providers
|
|
162
|
+
};
|