ani-auto 1.4.3 → 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.
- package/anime/LICENSE +21 -0
- package/anime/api/index.js +110 -0
- package/anime/app.js +79 -0
- package/anime/controllers/animeInfoController.js +48 -0
- package/anime/controllers/animeListController.js +21 -0
- package/anime/controllers/homeController.js +42 -0
- package/anime/controllers/playController.js +140 -0
- package/anime/controllers/queueController.js +20 -0
- package/anime/controllers/testController.js +667 -0
- package/anime/index.js +139 -0
- package/anime/middleware/cache.js +64 -0
- package/anime/middleware/errorHandler.js +33 -0
- package/anime/middleware/rateLimiter.js +73 -0
- package/anime/models/animeInfoModel.js +193 -0
- package/anime/models/animeListModel.js +69 -0
- package/anime/models/homeModel.js +33 -0
- package/anime/models/playModel.js +528 -0
- package/anime/models/queueModel.js +22 -0
- package/anime/package.json +27 -0
- package/anime/pnpm-lock.yaml +1774 -0
- package/anime/readme.md +236 -0
- package/anime/routes/animeInfoRoutes.js +9 -0
- package/anime/routes/animeListRoutes.js +9 -0
- package/anime/routes/homeRoutes.js +10 -0
- package/anime/routes/playRoutes.js +11 -0
- package/anime/routes/queueRoutes.js +8 -0
- package/anime/routes/testRoutes.js +325 -0
- package/anime/routes/webhookRoutes.js +23 -0
- package/anime/scrapers/animepahe.js +1053 -0
- package/anime/test-server.js +10 -0
- package/anime/test.sh +275 -0
- package/anime/utils/browser.js +172 -0
- package/anime/utils/config.js +209 -0
- package/anime/utils/dataProcessor.js +172 -0
- package/anime/utils/diskCache.js +228 -0
- package/anime/utils/flaresolverr.js +361 -0
- package/anime/utils/jsParser.js +19 -0
- package/anime/utils/redis.js +64 -0
- package/anime/utils/requestManager.js +706 -0
- package/anime/utils/urlConverter.js +88 -0
- package/anime/utils/webhookNotifier.js +190 -0
- package/cookiereader.py +291 -0
- package/package.json +14 -6
- package/src/anilist.js +19 -2
- package/src/api/anilist.js +32 -0
- package/src/api/anime.js +181 -0
- package/src/api/cf-bypass.js +131 -0
- package/src/api/config.js +134 -0
- package/src/api/daemon.js +62 -0
- package/src/api/download.js +98 -0
- package/src/api/match.js +58 -0
- package/src/api/runs.js +15 -0
- package/src/api/update.js +96 -0
- package/src/cli.js +18 -2
- package/src/config.js +14 -2
- package/src/daemon.js +1 -1
- package/src/db.js +38 -11
- package/src/engine.js +66 -13
- package/src/notify.js +82 -7
- package/src/scraper.js +2 -2
- package/src/server.js +108 -0
- package/utils.js +21 -21
- package/web/index.html +13 -0
- package/web/package-lock.json +1420 -0
- package/web/package.json +24 -0
- package/web/src/App.vue +200 -0
- package/web/src/api/client.js +75 -0
- package/web/src/assets/styles/base.css +961 -0
- package/web/src/components/UpdateModal.vue +157 -0
- package/web/src/components/sidebar/SidebarResizable.vue +170 -0
- package/web/src/components/sidebar/sidebarConfig.js +24 -0
- package/web/src/main.js +104 -0
- package/web/src/router/index.js +44 -0
- package/web/src/stores/config.js +27 -0
- package/web/src/stores/download.js +107 -0
- package/web/src/stores/update.js +73 -0
- package/web/src/views/About.vue +42 -0
- package/web/src/views/AniListSync.vue +122 -0
- package/web/src/views/AnimeDetail.vue +202 -0
- package/web/src/views/AnimeList.vue +110 -0
- package/web/src/views/CFResult.vue +312 -0
- package/web/src/views/DaemonControl.vue +83 -0
- package/web/src/views/Dashboard.vue +187 -0
- package/web/src/views/DownloadCenter.vue +153 -0
- package/web/src/views/MatchFinder.vue +131 -0
- package/web/src/views/OAuthCallback.vue +44 -0
- package/web/src/views/Onboarding.vue +93 -0
- package/web/src/views/RunHistory.vue +122 -0
- package/web/src/views/Settings.vue +410 -0
- 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
|
+
};
|