ani-auto 1.4.4 → 2.0.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.
Files changed (89) hide show
  1. package/anime/LICENSE +21 -0
  2. package/anime/api/index.js +110 -0
  3. package/anime/app.js +79 -0
  4. package/anime/controllers/animeInfoController.js +48 -0
  5. package/anime/controllers/animeListController.js +21 -0
  6. package/anime/controllers/homeController.js +42 -0
  7. package/anime/controllers/playController.js +140 -0
  8. package/anime/controllers/queueController.js +20 -0
  9. package/anime/controllers/testController.js +667 -0
  10. package/anime/index.js +139 -0
  11. package/anime/middleware/cache.js +64 -0
  12. package/anime/middleware/errorHandler.js +33 -0
  13. package/anime/middleware/rateLimiter.js +73 -0
  14. package/anime/models/animeInfoModel.js +193 -0
  15. package/anime/models/animeListModel.js +69 -0
  16. package/anime/models/homeModel.js +33 -0
  17. package/anime/models/playModel.js +528 -0
  18. package/anime/models/queueModel.js +22 -0
  19. package/anime/package.json +27 -0
  20. package/anime/pnpm-lock.yaml +1774 -0
  21. package/anime/readme.md +236 -0
  22. package/anime/routes/animeInfoRoutes.js +9 -0
  23. package/anime/routes/animeListRoutes.js +9 -0
  24. package/anime/routes/homeRoutes.js +10 -0
  25. package/anime/routes/playRoutes.js +11 -0
  26. package/anime/routes/queueRoutes.js +8 -0
  27. package/anime/routes/testRoutes.js +325 -0
  28. package/anime/routes/webhookRoutes.js +23 -0
  29. package/anime/scrapers/animepahe.js +1053 -0
  30. package/anime/test-server.js +10 -0
  31. package/anime/test.sh +275 -0
  32. package/anime/utils/browser.js +172 -0
  33. package/anime/utils/config.js +209 -0
  34. package/anime/utils/dataProcessor.js +172 -0
  35. package/anime/utils/diskCache.js +228 -0
  36. package/anime/utils/flaresolverr.js +361 -0
  37. package/anime/utils/jsParser.js +19 -0
  38. package/anime/utils/redis.js +64 -0
  39. package/anime/utils/requestManager.js +706 -0
  40. package/anime/utils/urlConverter.js +88 -0
  41. package/anime/utils/webhookNotifier.js +190 -0
  42. package/cookiereader.py +291 -0
  43. package/package.json +14 -6
  44. package/src/anilist.js +15 -2
  45. package/src/api/anilist.js +32 -0
  46. package/src/api/anime.js +181 -0
  47. package/src/api/cf-bypass.js +131 -0
  48. package/src/api/config.js +134 -0
  49. package/src/api/daemon.js +62 -0
  50. package/src/api/download.js +98 -0
  51. package/src/api/match.js +58 -0
  52. package/src/api/runs.js +15 -0
  53. package/src/api/update.js +96 -0
  54. package/src/cli.js +18 -2
  55. package/src/config.js +14 -2
  56. package/src/daemon.js +1 -1
  57. package/src/db.js +38 -11
  58. package/src/engine.js +62 -11
  59. package/src/scraper.js +2 -2
  60. package/src/server.js +108 -0
  61. package/utils.js +21 -4
  62. package/web/index.html +13 -0
  63. package/web/package-lock.json +1420 -0
  64. package/web/package.json +24 -0
  65. package/web/src/App.vue +200 -0
  66. package/web/src/api/client.js +75 -0
  67. package/web/src/assets/styles/base.css +961 -0
  68. package/web/src/components/UpdateModal.vue +157 -0
  69. package/web/src/components/sidebar/SidebarResizable.vue +170 -0
  70. package/web/src/components/sidebar/sidebarConfig.js +24 -0
  71. package/web/src/main.js +104 -0
  72. package/web/src/router/index.js +44 -0
  73. package/web/src/stores/config.js +27 -0
  74. package/web/src/stores/download.js +107 -0
  75. package/web/src/stores/update.js +73 -0
  76. package/web/src/views/About.vue +42 -0
  77. package/web/src/views/AniListSync.vue +122 -0
  78. package/web/src/views/AnimeDetail.vue +202 -0
  79. package/web/src/views/AnimeList.vue +110 -0
  80. package/web/src/views/CFResult.vue +312 -0
  81. package/web/src/views/DaemonControl.vue +83 -0
  82. package/web/src/views/Dashboard.vue +187 -0
  83. package/web/src/views/DownloadCenter.vue +153 -0
  84. package/web/src/views/MatchFinder.vue +131 -0
  85. package/web/src/views/OAuthCallback.vue +44 -0
  86. package/web/src/views/Onboarding.vue +93 -0
  87. package/web/src/views/RunHistory.vue +122 -0
  88. package/web/src/views/Settings.vue +410 -0
  89. package/web/vite.config.js +23 -0
@@ -0,0 +1,361 @@
1
+ const SESSION_BASE = "animepahe";
2
+ const MAX_TIMEOUT = parseInt(
3
+ process.env.FLARESOLVERR_MAX_TIMEOUT || "60000",
4
+ 10,
5
+ );
6
+ const MAX_PROXY_ROTATIONS = parseInt(
7
+ process.env.FLARESOLVERR_PROXY_MAX_ROTATIONS || "6",
8
+ 10,
9
+ );
10
+
11
+ let stickyToken = newStickyToken();
12
+
13
+ function newStickyToken() {
14
+ return (
15
+ "r" +
16
+ Date.now().toString(36) +
17
+ Math.random().toString(36).slice(2, 6)
18
+ );
19
+ }
20
+
21
+ function proxyConfig() {
22
+ const raw = process.env.FLARESOLVERR_PROXY;
23
+ if (!raw) return null;
24
+
25
+ let url = raw;
26
+ let username;
27
+ let password = "";
28
+
29
+ try {
30
+ const parsed = new URL(raw);
31
+ url = `${parsed.protocol}//${parsed.host}`;
32
+ if (parsed.username) username = decodeURIComponent(parsed.username);
33
+ if (parsed.password) password = decodeURIComponent(parsed.password);
34
+ } catch (_) {
35
+ return { url: raw };
36
+ }
37
+
38
+ const sticky = process.env.FLARESOLVERR_PROXY_STICKY;
39
+ if (sticky) password += sticky.replace("{id}", stickyToken);
40
+
41
+ const cfg = { url };
42
+ if (username) cfg.username = username;
43
+ if (password) cfg.password = password;
44
+ return cfg;
45
+ }
46
+
47
+ function proxyHostTokens() {
48
+ const raw = process.env.FLARESOLVERR_PROXY_HOSTS;
49
+ if (raw === undefined) return ["kwik"];
50
+ const trimmed = raw.trim();
51
+ if (trimmed === "" || trimmed === "*") return ["*"];
52
+ return trimmed
53
+ .split(",")
54
+ .map((s) => s.trim().toLowerCase())
55
+ .filter(Boolean);
56
+ }
57
+
58
+ function shouldProxy(url) {
59
+ if (!proxyConfig()) return false;
60
+ const tokens = proxyHostTokens();
61
+ if (tokens.includes("*")) return true;
62
+
63
+ let host = "";
64
+ try {
65
+ host = new URL(url).host.toLowerCase();
66
+ } catch (_) {}
67
+
68
+ return tokens.some((t) => host.includes(t));
69
+ }
70
+
71
+ const sessions = new Map();
72
+ const locks = new Map();
73
+
74
+ function sessionKey(useProxy) {
75
+ return useProxy ? `${SESSION_BASE}-proxy-${stickyToken}` : SESSION_BASE;
76
+ }
77
+
78
+ function runExclusive(cacheKey, fn) {
79
+ const prev = locks.get(cacheKey) || Promise.resolve();
80
+ const run = prev.then(fn, fn);
81
+ locks.set(cacheKey, run.then(() => {}, () => {}));
82
+ return run;
83
+ }
84
+
85
+ function isEnabled() {
86
+ return !!process.env.FLARESOLVERR_URL;
87
+ }
88
+
89
+ function endpoint() {
90
+ return process.env.FLARESOLVERR_URL.replace(/\/+$/, "") + "/v1";
91
+ }
92
+
93
+ async function postV1(payload) {
94
+ const controller = new AbortController();
95
+ const httpTimeout = MAX_TIMEOUT + 15000;
96
+ const timer = setTimeout(() => controller.abort(), httpTimeout);
97
+
98
+ try {
99
+ const res = await fetch(endpoint(), {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify(payload),
103
+ signal: controller.signal,
104
+ });
105
+ const data = await res.json().catch(() => null);
106
+ if (!data) {
107
+ throw new Error(
108
+ `FlareSolverr returned non-JSON (HTTP ${res.status})`,
109
+ );
110
+ }
111
+ return data;
112
+ } catch (err) {
113
+ if (err.name === "AbortError") {
114
+ throw new Error(
115
+ `FlareSolverr request timed out after ${httpTimeout}ms`,
116
+ );
117
+ }
118
+ throw err;
119
+ } finally {
120
+ clearTimeout(timer);
121
+ }
122
+ }
123
+
124
+ async function ensureSession(useProxy) {
125
+ const cacheKey = useProxy ? "proxy" : "direct";
126
+ if (sessions.has(cacheKey)) return sessions.get(cacheKey);
127
+
128
+ const promise = (async () => {
129
+ const name = sessionKey(useProxy);
130
+ const payload = { cmd: "sessions.create", session: name };
131
+ if (useProxy) payload.proxy = proxyConfig();
132
+
133
+ try {
134
+ const data = await postV1(payload);
135
+ if (
136
+ data.status !== "ok" &&
137
+ !/already exists/i.test(data.message || "")
138
+ ) {
139
+ throw new Error(
140
+ `FlareSolverr sessions.create failed: ${data.message || data.status}`,
141
+ );
142
+ }
143
+ console.log(
144
+ `[flaresolverr] session "${name}" ready${useProxy ? " (via proxy)" : ""}`,
145
+ );
146
+ return name;
147
+ } catch (err) {
148
+ sessions.delete(cacheKey);
149
+ throw err;
150
+ }
151
+ })();
152
+
153
+ sessions.set(cacheKey, promise);
154
+ return promise;
155
+ }
156
+
157
+ function resetSession(useProxy) {
158
+ sessions.delete(useProxy ? "proxy" : "direct");
159
+ }
160
+
161
+ async function destroySession(name) {
162
+ try {
163
+ await postV1({ cmd: "sessions.destroy", session: name });
164
+ } catch (_) {}
165
+ }
166
+
167
+ async function rotateProxyIp() {
168
+ const oldName = sessionKey(true);
169
+ await destroySession(oldName);
170
+ resetSession(true);
171
+ stickyToken = newStickyToken();
172
+ console.log("[flaresolverr] rotated proxy IP (sticky token refreshed)");
173
+ }
174
+
175
+ async function solveOnce(useProxy, makePayload) {
176
+ let session = await ensureSession(useProxy);
177
+ let data = await postV1(makePayload(session));
178
+
179
+ if (
180
+ data.status !== "ok" &&
181
+ /session.*(not.*exist|no.*found)/i.test(data.message || "")
182
+ ) {
183
+ resetSession(useProxy);
184
+ session = await ensureSession(useProxy);
185
+ data = await postV1(makePayload(session));
186
+ }
187
+
188
+ return data;
189
+ }
190
+
191
+ function toResult(data) {
192
+ return {
193
+ status: data.solution.status,
194
+ body: data.solution.response,
195
+ cookies: data.solution.cookies || [],
196
+ userAgent: data.solution.userAgent,
197
+ };
198
+ }
199
+
200
+ function looksLikeBrowserError(body) {
201
+ if (typeof body !== "string") return false;
202
+ return /id=["']main-frame-error["']|chrome-error:\/\/|--error-code-color|ERR_(TIMED_OUT|TUNNEL_CONNECTION_FAILED|PROXY_CONNECTION_FAILED|CONNECTION_(RESET|CLOSED|TIMED_OUT)|EMPTY_RESPONSE|NAME_NOT_RESOLVED)/i.test(
203
+ body,
204
+ );
205
+ }
206
+
207
+ function solutionError(data, url) {
208
+ const status = data?.solution?.status;
209
+ const msg = data?.message || data?.status || "unknown error";
210
+ const err = new Error(`FlareSolverr failed for ${url}: ${msg}`);
211
+ err.statusCode = status && status >= 400 ? status : 503;
212
+ return err;
213
+ }
214
+
215
+ async function get(url) {
216
+ const useProxy = shouldProxy(url);
217
+ const cacheKey = useProxy ? "proxy" : "direct";
218
+
219
+ return runExclusive(cacheKey, async () => {
220
+ const maxAttempts = useProxy ? MAX_PROXY_ROTATIONS + 1 : 1;
221
+ let lastErr;
222
+
223
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
224
+ let data;
225
+ try {
226
+ data = await solveOnce(useProxy, (session) => {
227
+ const payload = {
228
+ cmd: "request.get",
229
+ url,
230
+ session,
231
+ maxTimeout: MAX_TIMEOUT,
232
+ };
233
+ if (useProxy) payload.proxy = proxyConfig();
234
+ return payload;
235
+ });
236
+ } catch (err) {
237
+ lastErr = err;
238
+ if (useProxy && attempt < maxAttempts) {
239
+ await rotateProxyIp();
240
+ continue;
241
+ }
242
+ throw err;
243
+ }
244
+
245
+ if (data.status === "ok" && data.solution) {
246
+ if (useProxy && looksLikeBrowserError(data.solution.response)) {
247
+ lastErr = new Error(
248
+ `FlareSolverr returned a browser error page for ${url} (proxy load failed)`,
249
+ );
250
+ if (attempt < maxAttempts) {
251
+ console.log(
252
+ `[flaresolverr] solved but page failed to load on attempt ${attempt}, rotating...`,
253
+ );
254
+ await rotateProxyIp();
255
+ continue;
256
+ }
257
+ throw lastErr;
258
+ }
259
+ if (useProxy && attempt > 1) {
260
+ console.log(
261
+ `[flaresolverr] solved after trying ${attempt} proxy IP(s)`,
262
+ );
263
+ }
264
+ return toResult(data);
265
+ }
266
+
267
+ lastErr = solutionError(data, url);
268
+ if (useProxy && attempt < maxAttempts) {
269
+ console.log(
270
+ `[flaresolverr] proxy IP blocked on attempt ${attempt}, rotating...`,
271
+ );
272
+ await rotateProxyIp();
273
+ continue;
274
+ }
275
+
276
+ throw lastErr;
277
+ }
278
+
279
+ throw lastErr || new Error(`FlareSolverr failed for ${url}`);
280
+ });
281
+ }
282
+
283
+ async function post(url, postData = {}) {
284
+ const useProxy = shouldProxy(url);
285
+ const cacheKey = useProxy ? "proxy" : "direct";
286
+
287
+ return runExclusive(cacheKey, async () => {
288
+ let session = await ensureSession(useProxy);
289
+ let data = await postV1({
290
+ cmd: "request.post",
291
+ url,
292
+ session,
293
+ postData,
294
+ maxTimeout: MAX_TIMEOUT,
295
+ ...(useProxy ? { proxy: proxyConfig() } : {}),
296
+ });
297
+
298
+ if (
299
+ data.status !== "ok" &&
300
+ /session.*(not.*exist|no.*found)/i.test(data.message || "")
301
+ ) {
302
+ resetSession(useProxy);
303
+ session = await ensureSession(useProxy);
304
+ data = await postV1({
305
+ cmd: "request.post",
306
+ url,
307
+ session,
308
+ postData,
309
+ maxTimeout: MAX_TIMEOUT,
310
+ ...(useProxy ? { proxy: proxyConfig() } : {}),
311
+ });
312
+ }
313
+
314
+ if (data.status === "ok" && data.solution) {
315
+ return toResult(data);
316
+ }
317
+
318
+ throw solutionError(data, url);
319
+ });
320
+ }
321
+
322
+ function extractJson(body) {
323
+ if (typeof body !== "string") return body;
324
+
325
+ const preMatch = body.match(/<pre[^>]*>([\s\S]*?)<\/pre>/i);
326
+ if (preMatch) return JSON.parse(preMatch[1].trim());
327
+
328
+ const bodyMatch = body.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
329
+ if (bodyMatch) return JSON.parse(bodyMatch[1].trim());
330
+
331
+ return JSON.parse(body);
332
+ }
333
+
334
+ async function fetchCookies(url) {
335
+ if (!isEnabled()) return "";
336
+ const useProxy = shouldProxy(url);
337
+ const data = await solveOnce(useProxy, (session) => ({
338
+ cmd: "request.get",
339
+ url,
340
+ session,
341
+ maxTimeout: MAX_TIMEOUT,
342
+ }));
343
+ if (data.status !== "ok" || !data.solution) {
344
+ throw solutionError(data, url);
345
+ }
346
+ const cookies = data.solution.cookies || [];
347
+ const header = cookies
348
+ .filter((c) => c.name && c.value)
349
+ .map((c) => `${c.name}=${c.value}`)
350
+ .join("; ");
351
+ if (!header) throw new Error("FlareSolverr returned no cookies");
352
+ return header;
353
+ }
354
+
355
+ module.exports = {
356
+ isEnabled,
357
+ get,
358
+ post,
359
+ fetchCookies,
360
+ extractJson,
361
+ };
@@ -0,0 +1,19 @@
1
+ function getJsVariable(html, variableName) {
2
+ try {
3
+ const sanitizedName = variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4
+
5
+ const pattern = new RegExp(
6
+ `(?:let|var|const)\\s+${sanitizedName}\\s*=\\s*` +
7
+ `(["'\`])([^"'\\\\\`]*)(?:\\\\.[^"'\\\\\`]*)*\\1\\s*;`,
8
+ 'm'
9
+ );
10
+
11
+ const match = html.match(pattern);
12
+ return match?.[2]?.trim() || null;
13
+ } catch (error) {
14
+ return null;
15
+ }
16
+ }
17
+
18
+
19
+ module.exports = { getJsVariable };
@@ -0,0 +1,64 @@
1
+ const Redis = require("redis");
2
+
3
+ // Check if Redis should be enabled
4
+ const REDIS_ENABLED = !!process.env.REDIS_URL;
5
+
6
+ let redisClient = null;
7
+ let enabled = REDIS_ENABLED;
8
+
9
+ if (REDIS_ENABLED) {
10
+ redisClient = Redis.createClient({
11
+ url: process.env.REDIS_URL,
12
+ socket: {
13
+ reconnectStrategy: (retries) => {
14
+ if (retries > 10) {
15
+ console.log("Max reconnection attempts reached");
16
+ enabled = false;
17
+ return new Error("Max reconnection attempts reached");
18
+ }
19
+ return Math.min(retries * 100, 3000);
20
+ },
21
+ },
22
+ });
23
+
24
+ redisClient.on("error", (err) => {
25
+ console.error("Redis Client Error:", err.message);
26
+ });
27
+
28
+ // Connect to Redis
29
+ (async () => {
30
+ try {
31
+ await redisClient.connect();
32
+ console.log("\x1b[32m%s\x1b[0m", "Redis Client Connected");
33
+ } catch (error) {
34
+ console.error("Redis connection failed:", error.message);
35
+ enabled = false;
36
+ }
37
+ })();
38
+ }
39
+
40
+ const get = async (key) => {
41
+ if (!enabled) return null;
42
+ try {
43
+ return await redisClient.get(key);
44
+ } catch (error) {
45
+ console.error("Redis get error:", error.message);
46
+ return null;
47
+ }
48
+ };
49
+
50
+ const setEx = async (key, duration, value) => {
51
+ if (!enabled) return;
52
+ try {
53
+ await redisClient.setEx(key, duration, value);
54
+ } catch (error) {
55
+ console.error("Redis setEx error:", error.message);
56
+ }
57
+ };
58
+
59
+ module.exports = {
60
+ redisClient,
61
+ get,
62
+ setEx,
63
+ enabled,
64
+ };