@anjishnusengupta/ny-cli 3.0.3 → 3.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/backend.mjs +85 -37
- package/install.sh +3 -3
- package/ny-cli +2 -2
- package/package.json +1 -1
- package/3.0.2 +0 -0
package/backend.mjs
CHANGED
|
@@ -14,10 +14,24 @@ dns.setServers(["1.1.1.1", "8.8.8.8", "1.0.0.1", "8.8.4.4"]);
|
|
|
14
14
|
|
|
15
15
|
// Custom lookup: prefer IPv6 (bypasses SNI-based DPI blocking on many ISPs),
|
|
16
16
|
// fall back to IPv4 via dns.resolve4, then OS resolver as last resort.
|
|
17
|
+
// Note: IPv4-only consistently fails due to ISP blocking; IPv6 is required.
|
|
17
18
|
function customLookup(hostname, options, callback) {
|
|
18
19
|
if (typeof options === "function") { callback = options; options = {}; }
|
|
20
|
+
|
|
21
|
+
// Cache resolved addresses per hostname for the lifetime of this process
|
|
22
|
+
const cacheKey = hostname;
|
|
23
|
+
if (customLookup._cache && customLookup._cache[cacheKey]) {
|
|
24
|
+
const cached = customLookup._cache[cacheKey];
|
|
25
|
+
if (options.all) {
|
|
26
|
+
return callback(null, [{ address: cached.address, family: cached.family }]);
|
|
27
|
+
}
|
|
28
|
+
return callback(null, cached.address, cached.family);
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
dns.resolve6(hostname, (err6, addr6) => {
|
|
20
32
|
if (!err6 && addr6 && addr6.length > 0) {
|
|
33
|
+
if (!customLookup._cache) customLookup._cache = {};
|
|
34
|
+
customLookup._cache[cacheKey] = { address: addr6[0], family: 6 };
|
|
21
35
|
if (options.all) {
|
|
22
36
|
return callback(null, addr6.map(a => ({ address: a, family: 6 })));
|
|
23
37
|
}
|
|
@@ -25,6 +39,8 @@ function customLookup(hostname, options, callback) {
|
|
|
25
39
|
}
|
|
26
40
|
dns.resolve4(hostname, (err4, addr4) => {
|
|
27
41
|
if (!err4 && addr4 && addr4.length > 0) {
|
|
42
|
+
if (!customLookup._cache) customLookup._cache = {};
|
|
43
|
+
customLookup._cache[cacheKey] = { address: addr4[0], family: 4 };
|
|
28
44
|
if (options.all) {
|
|
29
45
|
return callback(null, addr4.map(a => ({ address: a, family: 4 })));
|
|
30
46
|
}
|
|
@@ -35,9 +51,10 @@ function customLookup(hostname, options, callback) {
|
|
|
35
51
|
});
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
// Replace global agents so ALL http/https requests use our DNS
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
// Replace global agents so ALL http/https requests use our DNS.
|
|
55
|
+
// keepAlive reduces connection overhead for multiple requests to the same host.
|
|
56
|
+
http.globalAgent = new http.Agent({ lookup: customLookup, keepAlive: true });
|
|
57
|
+
https.globalAgent = new https.Agent({ lookup: customLookup, keepAlive: true });
|
|
41
58
|
|
|
42
59
|
// Dynamic import so aniwatch picks up patched global agents
|
|
43
60
|
const { HiAnime } = await import("aniwatch");
|
|
@@ -46,6 +63,22 @@ const hianime = new HiAnime.Scraper();
|
|
|
46
63
|
|
|
47
64
|
const action = process.argv[2];
|
|
48
65
|
|
|
66
|
+
// Retry helper: retries an async fn up to `retries` times with a delay between attempts.
|
|
67
|
+
async function withRetry(fn, { retries = 2, delay = 1000, label = "" } = {}) {
|
|
68
|
+
let lastErr;
|
|
69
|
+
for (let i = 0; i <= retries; i++) {
|
|
70
|
+
try {
|
|
71
|
+
return await fn();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
lastErr = err;
|
|
74
|
+
if (i < retries) {
|
|
75
|
+
await new Promise(r => setTimeout(r, delay));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw lastErr;
|
|
80
|
+
}
|
|
81
|
+
|
|
49
82
|
async function main() {
|
|
50
83
|
try {
|
|
51
84
|
switch (action) {
|
|
@@ -57,14 +90,14 @@ async function main() {
|
|
|
57
90
|
console.log(JSON.stringify({ error: "Missing search query" }));
|
|
58
91
|
process.exit(1);
|
|
59
92
|
}
|
|
60
|
-
const data = await hianime.search(query, page);
|
|
93
|
+
const data = await withRetry(() => hianime.search(query, page), { label: "search" });
|
|
61
94
|
console.log(JSON.stringify({ success: true, data }));
|
|
62
95
|
break;
|
|
63
96
|
}
|
|
64
97
|
|
|
65
98
|
// ── Home / Trending ──
|
|
66
99
|
case "home": {
|
|
67
|
-
const data = await hianime.getHomePage();
|
|
100
|
+
const data = await withRetry(() => hianime.getHomePage(), { label: "home" });
|
|
68
101
|
console.log(JSON.stringify({ success: true, data }));
|
|
69
102
|
break;
|
|
70
103
|
}
|
|
@@ -76,7 +109,7 @@ async function main() {
|
|
|
76
109
|
console.log(JSON.stringify({ error: "Missing anime id" }));
|
|
77
110
|
process.exit(1);
|
|
78
111
|
}
|
|
79
|
-
const data = await hianime.getInfo(animeId);
|
|
112
|
+
const data = await withRetry(() => hianime.getInfo(animeId), { label: "info" });
|
|
80
113
|
console.log(JSON.stringify({ success: true, data }));
|
|
81
114
|
break;
|
|
82
115
|
}
|
|
@@ -88,7 +121,7 @@ async function main() {
|
|
|
88
121
|
console.log(JSON.stringify({ error: "Missing anime id" }));
|
|
89
122
|
process.exit(1);
|
|
90
123
|
}
|
|
91
|
-
const data = await hianime.getEpisodes(animeId);
|
|
124
|
+
const data = await withRetry(() => hianime.getEpisodes(animeId), { label: "episodes" });
|
|
92
125
|
console.log(JSON.stringify({ success: true, data }));
|
|
93
126
|
break;
|
|
94
127
|
}
|
|
@@ -100,12 +133,12 @@ async function main() {
|
|
|
100
133
|
console.log(JSON.stringify({ error: "Missing episode id" }));
|
|
101
134
|
process.exit(1);
|
|
102
135
|
}
|
|
103
|
-
const data = await hianime.getEpisodeServers(episodeId);
|
|
136
|
+
const data = await withRetry(() => hianime.getEpisodeServers(episodeId), { label: "servers" });
|
|
104
137
|
console.log(JSON.stringify({ success: true, data }));
|
|
105
138
|
break;
|
|
106
139
|
}
|
|
107
140
|
|
|
108
|
-
// ── Episode Sources (with
|
|
141
|
+
// ── Episode Sources (with sequential server fallback + retry) ──
|
|
109
142
|
case "sources": {
|
|
110
143
|
const episodeId = process.argv[3];
|
|
111
144
|
const category = process.argv[4] || "sub";
|
|
@@ -114,35 +147,41 @@ async function main() {
|
|
|
114
147
|
process.exit(1);
|
|
115
148
|
}
|
|
116
149
|
|
|
117
|
-
//
|
|
118
|
-
const PER_SERVER_TIMEOUT =
|
|
150
|
+
// Increased timeout — source extraction can take 10-15s on slow connections
|
|
151
|
+
const PER_SERVER_TIMEOUT = 20000;
|
|
119
152
|
|
|
120
|
-
// Helper: try a single server with timeout
|
|
121
|
-
const tryServer = (server, cat) =>
|
|
122
|
-
Promise.race([
|
|
123
|
-
|
|
153
|
+
// Helper: try a single server with timeout + retry
|
|
154
|
+
const tryServer = async (server, cat) => {
|
|
155
|
+
const srcData = await Promise.race([
|
|
156
|
+
withRetry(
|
|
157
|
+
() => hianime.getEpisodeSources(episodeId, server, cat),
|
|
158
|
+
{ retries: 1, delay: 800, label: `sources-${server}` }
|
|
159
|
+
),
|
|
124
160
|
new Promise((_, reject) =>
|
|
125
161
|
setTimeout(() => reject(new Error(`${server} timed out`)), PER_SERVER_TIMEOUT)
|
|
126
162
|
),
|
|
127
|
-
])
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
163
|
+
]);
|
|
164
|
+
if (srcData?.sources?.length > 0) {
|
|
165
|
+
srcData._usedServer = server;
|
|
166
|
+
srcData._usedCategory = cat;
|
|
167
|
+
return srcData;
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`${server}: no sources`);
|
|
170
|
+
};
|
|
135
171
|
|
|
136
172
|
// Preferred order: hd-1/hd-2 are fastest, then others
|
|
137
173
|
const preferredOrder = ["hd-1", "hd-2", "streamtape", "streamsb"];
|
|
138
174
|
|
|
139
|
-
//
|
|
140
|
-
const
|
|
175
|
+
// Try servers SEQUENTIALLY to avoid rate-limiting (403 errors)
|
|
176
|
+
const tryServersSequentially = async (cat) => {
|
|
141
177
|
let availableServers;
|
|
142
178
|
try {
|
|
143
179
|
const serverData = await Promise.race([
|
|
144
|
-
|
|
145
|
-
|
|
180
|
+
withRetry(
|
|
181
|
+
() => hianime.getEpisodeServers(episodeId),
|
|
182
|
+
{ retries: 1, delay: 500, label: "server-list" }
|
|
183
|
+
),
|
|
184
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("server list timeout")), 8000)),
|
|
146
185
|
]);
|
|
147
186
|
const serverList = cat === "dub" ? serverData.dub : serverData.sub;
|
|
148
187
|
availableServers = (serverList || []).map((s) => s.serverName);
|
|
@@ -153,16 +192,25 @@ async function main() {
|
|
|
153
192
|
const serversToTry = preferredOrder.filter((s) => availableServers.includes(s));
|
|
154
193
|
if (serversToTry.length === 0) serversToTry.push("hd-1");
|
|
155
194
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
195
|
+
// Try each server one at a time — avoids rate-limiting
|
|
196
|
+
let lastError;
|
|
197
|
+
for (const server of serversToTry) {
|
|
198
|
+
try {
|
|
199
|
+
const srcData = await tryServer(server, cat);
|
|
200
|
+
srcData._availableServers = availableServers;
|
|
201
|
+
srcData._triedServers = serversToTry;
|
|
202
|
+
return srcData;
|
|
203
|
+
} catch (err) {
|
|
204
|
+
lastError = err;
|
|
205
|
+
// Small delay between servers to avoid triggering rate limits
|
|
206
|
+
await new Promise(r => setTimeout(r, 300));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw lastError || new Error("No servers available");
|
|
162
210
|
};
|
|
163
211
|
|
|
164
212
|
try {
|
|
165
|
-
const srcData = await
|
|
213
|
+
const srcData = await tryServersSequentially(category);
|
|
166
214
|
console.log(JSON.stringify({ success: true, data: srcData }));
|
|
167
215
|
return;
|
|
168
216
|
} catch {
|
|
@@ -172,7 +220,7 @@ async function main() {
|
|
|
172
220
|
// If sub failed, try dub as fallback
|
|
173
221
|
if (category === "sub") {
|
|
174
222
|
try {
|
|
175
|
-
const srcData = await
|
|
223
|
+
const srcData = await tryServersSequentially("dub");
|
|
176
224
|
console.log(JSON.stringify({ success: true, data: srcData }));
|
|
177
225
|
return;
|
|
178
226
|
} catch {
|
|
@@ -192,7 +240,7 @@ async function main() {
|
|
|
192
240
|
console.log(JSON.stringify({ error: "Missing query" }));
|
|
193
241
|
process.exit(1);
|
|
194
242
|
}
|
|
195
|
-
const data = await hianime.searchSuggestions(query);
|
|
243
|
+
const data = await withRetry(() => hianime.searchSuggestions(query), { label: "suggestions" });
|
|
196
244
|
console.log(JSON.stringify({ success: true, data }));
|
|
197
245
|
break;
|
|
198
246
|
}
|
|
@@ -205,7 +253,7 @@ async function main() {
|
|
|
205
253
|
console.log(JSON.stringify({ error: "Missing category name" }));
|
|
206
254
|
process.exit(1);
|
|
207
255
|
}
|
|
208
|
-
const data = await hianime.getCategoryAnime(name, page);
|
|
256
|
+
const data = await withRetry(() => hianime.getCategoryAnime(name, page), { label: "category" });
|
|
209
257
|
console.log(JSON.stringify({ success: true, data }));
|
|
210
258
|
break;
|
|
211
259
|
}
|
package/install.sh
CHANGED
|
@@ -25,7 +25,7 @@ printf " ╚═╝ ╚═══╝ ╚═╝ ╚═════╝
|
|
|
25
25
|
printf "${RESET}\n"
|
|
26
26
|
printf "${DIM}${CYAN} ⟨ Your Gateway to Anime Streaming ⟩${RESET}\n"
|
|
27
27
|
printf "${DIM} ─────────────────────────────────────${RESET}\n"
|
|
28
|
-
printf "${DIM} v3.0
|
|
28
|
+
printf "${DIM} v3.1.0 • nyanime.tech${RESET}\n"
|
|
29
29
|
printf "\n"
|
|
30
30
|
|
|
31
31
|
REPO_URL="https://raw.githubusercontent.com/AnjishnuSengupta/ny-cli/main"
|
|
@@ -155,7 +155,7 @@ printf "\n"
|
|
|
155
155
|
printf "${GREEN}╭────────────────────────────────────────╮${RESET}\n"
|
|
156
156
|
printf "${GREEN}│${RESET} ${WHITE}Installation complete!${RESET} 🎉 ${GREEN}│${RESET}\n"
|
|
157
157
|
printf "${GREEN}├────────────────────────────────────────┤${RESET}\n"
|
|
158
|
-
printf "${GREEN}│${RESET} Run ${CYAN}ny-cli${RESET} to start watching anime
|
|
159
|
-
printf "${GREEN}│${RESET} Run ${CYAN}ny-cli --help${RESET} for options
|
|
158
|
+
printf "${GREEN}│${RESET} Run ${CYAN}ny-cli${RESET} to start watching anime ${GREEN}│${RESET}\n"
|
|
159
|
+
printf "${GREEN}│${RESET} Run ${CYAN}ny-cli --help${RESET} for options ${GREEN}│${RESET}\n"
|
|
160
160
|
printf "${GREEN}╰────────────────────────────────────────╯${RESET}\n"
|
|
161
161
|
printf "\n"
|
package/ny-cli
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
3
3
|
# NY-CLI - Terminal-based Anime Streaming Client
|
|
4
|
-
# Version: 3.0
|
|
4
|
+
# Version: 3.1.0
|
|
5
5
|
# Author: Anjishnu Sengupta
|
|
6
6
|
# Website: https://nyanime.tech
|
|
7
7
|
# Repository: https://github.com/AnjishnuSengupta/ny-cli
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# Just install and run - no external API dependency!
|
|
11
11
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
12
12
|
|
|
13
|
-
VERSION="3.0
|
|
13
|
+
VERSION="3.1.0"
|
|
14
14
|
|
|
15
15
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
16
16
|
# BACKEND CONFIGURATION (Self-hosted via aniwatch npm package)
|
package/package.json
CHANGED
package/3.0.2
DELETED
|
File without changes
|