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,172 @@
1
+ const Config = require("./config");
2
+
3
+ class DataProcessor {
4
+ static processApiData(apiData, type = "airing", include = true) {
5
+ const items = apiData.data || [];
6
+
7
+ if (!Array.isArray(items)) {
8
+ console.error(
9
+ "Unexpected API response format:",
10
+ JSON.stringify(apiData).substring(0, 200),
11
+ );
12
+ throw new Error("Unexpected API response format");
13
+ }
14
+
15
+ let paginationInfo;
16
+ include
17
+ ? (paginationInfo = this._extractPaginationInfo(apiData, type))
18
+ : paginationInfo;
19
+
20
+ const dataProcessors = {
21
+ airing: this._processAiringData,
22
+ search: this._processSearchData,
23
+ releases: this._processReleaseData,
24
+ queue: this._processQueueData,
25
+ };
26
+
27
+ if (type === "releases") items._parentId = apiData._id;
28
+
29
+ const processor = dataProcessors[type] || this._processGenericData;
30
+
31
+ const processedData = processor(items);
32
+
33
+ if (processedData.length > 0) {
34
+ console.log(`Processed ${processedData.length} items of type: ${type}`);
35
+ // console.log("Sample:", processedData[0]);
36
+ }
37
+
38
+ return paginationInfo
39
+ ? { paginationInfo, data: processedData }
40
+ : { data: processedData };
41
+ }
42
+
43
+ static _extractPaginationInfo(apiData, type) {
44
+ if (!apiData) return {};
45
+
46
+ const {
47
+ _id,
48
+ total,
49
+ per_page,
50
+ current_page,
51
+ last_page,
52
+ next_page_url,
53
+ prev_page_url,
54
+ from,
55
+ to,
56
+ } = apiData;
57
+
58
+ const urlPrefix = _id ? `${_id}/${type}?` : `${type}?`;
59
+
60
+ const replaceUrl = (url) => {
61
+ if (!url) return url;
62
+ return url
63
+ .replace(new RegExp(`^(${Config.baseUrl}|/)`), Config.hostUrl)
64
+ .replace("api?", `api/${urlPrefix}`);
65
+ };
66
+
67
+ return {
68
+ ...(total != null && { total }),
69
+ ...(per_page != null && { perPage: per_page }),
70
+ ...(current_page != null && { currentPage: current_page }),
71
+ ...(last_page != null && { lastPage: last_page }),
72
+ ...(next_page_url != null && { nextPageUrl: replaceUrl(next_page_url) }),
73
+ ...(prev_page_url != null && { prevPageUrl: replaceUrl(prev_page_url) }),
74
+ ...(from != null && { from }),
75
+ ...(to != null && { to }),
76
+ };
77
+ }
78
+
79
+ static _processAiringData(items) {
80
+ return items.map((item) => ({
81
+ id: item.id || null,
82
+ anime_id: item.anime_id || null,
83
+ title: item.anime_title || null,
84
+ episode: item.episode || null,
85
+ episode2: item.episode2 || null,
86
+ edition: item.edition || null,
87
+ fansub: item.fansub || null,
88
+ image: item.snapshot || null,
89
+ disc: item.disc || null,
90
+ session: item.anime_session || null,
91
+ anime_session: item.anime_session || null,
92
+ episode_session: item.session || null,
93
+ link:
94
+ (item.anime_session ? `${Config.getUrl("animeInfo", { animeId: item.anime_session })}` : "") ||
95
+ null,
96
+ filler: item.filler || null,
97
+ created_at: item.created_at || null,
98
+ completed: item.completed || 1,
99
+ }));
100
+ }
101
+
102
+ static _processSearchData(items) {
103
+ return items.map((item) => ({
104
+ id: item.id || null,
105
+ title: item.title || null,
106
+ status: item.status || null,
107
+ type: item.type || null,
108
+ episodes: item.episodes || null,
109
+ score: item.score || null,
110
+ year: item.year || null,
111
+ season: item.season || null,
112
+ poster: item.poster || null,
113
+ session: item.session || null,
114
+ link:
115
+ (item.session ? `${Config.getUrl("animeInfo", item.session)}` : "") ||
116
+ null,
117
+ }));
118
+ }
119
+
120
+ static _processReleaseData(items) {
121
+ return items.map((item) => ({
122
+ id: item.id || null,
123
+ anime_id: item.anime_id || null,
124
+ episode: item.episode || null,
125
+ episode2: item.episode2 || null,
126
+ edition: item.edition || null,
127
+ title: item.title || null,
128
+ snapshot: item.snapshot || null,
129
+ disc: item.disc || null,
130
+ audio: item.audio || null,
131
+ duration: item.duration || null,
132
+ session: item.session || null,
133
+ link:
134
+ (item.session
135
+ ? `${Config.getUrl("play", items._parentId, item.session)}`
136
+ : "") || null,
137
+ filler: item.filler || null,
138
+ created_at: item.created_at || null,
139
+ }));
140
+ }
141
+
142
+ static _processQueueData(items) {
143
+ return items.map((item) => ({
144
+ title: item.title || null,
145
+ episode: item.episode || null,
146
+ resolution: item.resolution || null,
147
+ filesize: item.filesize || null,
148
+ fansub: item.fansub || null,
149
+ audio: item.audio || null,
150
+ progress: item.progress || null,
151
+ instant: item.instant || null,
152
+ current_duration: item.current_duration || null,
153
+ original: item.original_duration || null,
154
+ session: item.session || null,
155
+ published: item.published || null,
156
+ }));
157
+ }
158
+
159
+ // For unknown data types
160
+ static _processGenericData(items) {
161
+ return items.map((item) => {
162
+ // Clone the item but ensure no null property values
163
+ const processed = {};
164
+ Object.keys(item).forEach((key) => {
165
+ processed[key] = item[key] || null;
166
+ });
167
+ return processed;
168
+ });
169
+ }
170
+ }
171
+
172
+ module.exports = DataProcessor;
@@ -0,0 +1,228 @@
1
+ const fs = require("fs").promises;
2
+ const path = require("path");
3
+ const crypto = require("crypto");
4
+ const { existsSync } = require("fs");
5
+
6
+ // Cache directory — use /tmp for ephemeral storage (HF Spaces compatible)
7
+ const CACHE_DIR = process.env.DISK_CACHE_DIR || path.join("/tmp", "anikuro-cache");
8
+
9
+ // ─── Fix #3: Disk cache size limit ───
10
+ // Prevents /tmp from filling up on HF Spaces (limited disk space).
11
+ // When cache exceeds 200MB, oldest entries are evicted.
12
+ const MAX_DISK_CACHE_BYTES = 200 * 1024 * 1024; // 200MB
13
+ const CLEANUP_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
14
+
15
+ let initialized = false;
16
+ let cleanupIntervalStarted = false;
17
+
18
+ /**
19
+ * Initialize the disk cache directory. Creates it if it doesn't exist.
20
+ */
21
+ async function init() {
22
+ if (initialized) return;
23
+ try {
24
+ await fs.mkdir(CACHE_DIR, { recursive: true });
25
+ initialized = true;
26
+ console.log(`[Disk Cache] Initialized at: ${CACHE_DIR}`);
27
+ } catch (error) {
28
+ console.error("[Disk Cache] Failed to initialize cache directory:", error.message);
29
+ // Don't throw — cache is optional, app should still work
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Hash a cache key into a safe filename.
35
+ */
36
+ function hashKey(key) {
37
+ return crypto.createHash("md5").update(key).digest("hex");
38
+ }
39
+
40
+ /**
41
+ * Get a cached response from disk.
42
+ * Returns null if not found or expired.
43
+ */
44
+ async function get(key) {
45
+ if (!initialized) await init();
46
+
47
+ try {
48
+ const filePath = path.join(CACHE_DIR, hashKey(key) + ".json");
49
+ if (!existsSync(filePath)) return null;
50
+
51
+ const raw = await fs.readFile(filePath, "utf8");
52
+ const entry = JSON.parse(raw);
53
+
54
+ // Check expiration
55
+ if (Date.now() - entry.timestamp > entry.duration * 1000) {
56
+ // Expired — delete it
57
+ await fs.unlink(filePath).catch(() => {});
58
+ return null;
59
+ }
60
+
61
+ console.log(`[Disk Cache] ✅ HIT: ${key.substring(0, 60)}...`);
62
+ return entry.data;
63
+ } catch (error) {
64
+ if (error.code !== "ENOENT") {
65
+ console.error("[Disk Cache] Get error:", error.message);
66
+ }
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Set a cached response on disk with TTL.
73
+ * Evicts oldest entries when cache exceeds MAX_DISK_CACHE_BYTES.
74
+ */
75
+ async function set(key, data, duration) {
76
+ if (!initialized) await init();
77
+
78
+ try {
79
+ const filePath = path.join(CACHE_DIR, hashKey(key) + ".json");
80
+ const entry = {
81
+ data,
82
+ timestamp: Date.now(),
83
+ duration, // in seconds
84
+ };
85
+
86
+ await fs.writeFile(filePath, JSON.stringify(entry), "utf8");
87
+ console.log(`[Disk Cache] 💾 SET: ${key.substring(0, 60)}...`);
88
+
89
+ // ─── Fix #3: Enforce max cache size ───
90
+ // After writing, check total size and evict oldest if over limit
91
+ try {
92
+ const files = await fs.readdir(CACHE_DIR);
93
+ const jsonFiles = files.filter(f => f.endsWith(".json"));
94
+
95
+ // Get file stats sorted by modification time (oldest first)
96
+ const fileStats = await Promise.all(
97
+ jsonFiles.map(async (file) => {
98
+ const filePath = path.join(CACHE_DIR, file);
99
+ const stat = await fs.stat(filePath);
100
+ return { path: filePath, mtime: stat.mtimeMs, size: stat.size };
101
+ })
102
+ );
103
+ fileStats.sort((a, b) => a.mtime - b.mtime);
104
+
105
+ let totalSize = fileStats.reduce((sum, f) => sum + f.size, 0);
106
+
107
+ // Evict oldest files until under limit
108
+ let evicted = 0;
109
+ for (const file of fileStats) {
110
+ if (totalSize <= MAX_DISK_CACHE_BYTES) break;
111
+ await fs.unlink(file.path).catch(() => {});
112
+ totalSize -= file.size;
113
+ evicted++;
114
+ }
115
+ if (evicted > 0) {
116
+ console.log(`[Disk Cache] Evicted ${evicted} entries (size: ${(totalSize / 1024 / 1024).toFixed(1)}MB)`);
117
+ }
118
+ } catch (err) {
119
+ // Non-fatal — cache failures should not break the app
120
+ console.error("[Disk Cache] Size enforcement error:", err.message);
121
+ }
122
+ } catch (error) {
123
+ console.error("[Disk Cache] Set error:", error.message);
124
+ // Don't throw — cache failures should not break the app
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Delete a specific cache entry.
130
+ */
131
+ async function del(key) {
132
+ if (!initialized) await init();
133
+
134
+ try {
135
+ const filePath = path.join(CACHE_DIR, hashKey(key) + ".json");
136
+ if (existsSync(filePath)) {
137
+ await fs.unlink(filePath);
138
+ }
139
+ } catch (error) {
140
+ console.error("[Disk Cache] Delete error:", error.message);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Clean all expired cache entries.
146
+ */
147
+ async function cleanExpired() {
148
+ if (!initialized) await init();
149
+
150
+ try {
151
+ const files = await fs.readdir(CACHE_DIR);
152
+ let cleaned = 0;
153
+
154
+ for (const file of files) {
155
+ if (!file.endsWith(".json")) continue;
156
+
157
+ const filePath = path.join(CACHE_DIR, file);
158
+ try {
159
+ const raw = await fs.readFile(filePath, "utf8");
160
+ const entry = JSON.parse(raw);
161
+
162
+ if (Date.now() - entry.timestamp > entry.duration * 1000) {
163
+ await fs.unlink(filePath);
164
+ cleaned++;
165
+ }
166
+ } catch {
167
+ // Corrupted file — delete it
168
+ await fs.unlink(filePath).catch(() => {});
169
+ cleaned++;
170
+ }
171
+ }
172
+
173
+ if (cleaned > 0) {
174
+ console.log(`[Disk Cache] Cleaned ${cleaned} expired entries`);
175
+ }
176
+ } catch (error) {
177
+ console.error("[Disk Cache] Clean error:", error.message);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Get cache statistics (file count, total size).
183
+ */
184
+ async function stats() {
185
+ if (!initialized) await init();
186
+
187
+ try {
188
+ const files = await fs.readdir(CACHE_DIR);
189
+ const jsonFiles = files.filter(f => f.endsWith(".json"));
190
+
191
+ let totalSize = 0;
192
+ for (const file of jsonFiles) {
193
+ const filePath = path.join(CACHE_DIR, file);
194
+ const stat = await fs.stat(filePath);
195
+ totalSize += stat.size;
196
+ }
197
+
198
+ return {
199
+ entries: jsonFiles.length,
200
+ totalSizeBytes: totalSize,
201
+ totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
202
+ };
203
+ } catch (error) {
204
+ return { entries: 0, totalSizeBytes: 0, totalSizeMB: "0.00" };
205
+ }
206
+ }
207
+
208
+ // Auto-clean expired entries on startup + periodic cleanup every 10 minutes
209
+ init().then(() => {
210
+ cleanExpired();
211
+ if (!cleanupIntervalStarted) {
212
+ cleanupIntervalStarted = true;
213
+ setInterval(() => {
214
+ cleanExpired();
215
+ }, CLEANUP_INTERVAL_MS);
216
+ }
217
+ });
218
+
219
+ module.exports = {
220
+ init,
221
+ get,
222
+ set,
223
+ del,
224
+ cleanExpired,
225
+ stats,
226
+ CACHE_DIR,
227
+ MAX_DISK_CACHE_BYTES,
228
+ };