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.
- 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 +15 -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 +62 -11
- package/src/scraper.js +2 -2
- package/src/server.js +108 -0
- package/utils.js +21 -4
- 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,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test server wrapper for standalone pahe-api testing.
|
|
3
|
+
* Usage: node test-server.js
|
|
4
|
+
*/
|
|
5
|
+
const app = require('./app');
|
|
6
|
+
const PORT = process.env.PORT || 3000;
|
|
7
|
+
|
|
8
|
+
app.listen(PORT, () => {
|
|
9
|
+
console.log(`API running on http://localhost:${PORT}`);
|
|
10
|
+
});
|
package/anime/test.sh
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================
|
|
3
|
+
# pahe-api — Full endpoint test with response validation
|
|
4
|
+
# Usage: chmod +x test.sh && ./test.sh
|
|
5
|
+
# ============================================================
|
|
6
|
+
|
|
7
|
+
BASE="http://localhost:3000"
|
|
8
|
+
PASS=0
|
|
9
|
+
FAIL=0
|
|
10
|
+
WARN=0
|
|
11
|
+
|
|
12
|
+
# Colors
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
RED='\033[0;31m'
|
|
15
|
+
YELLOW='\033[1;33m'
|
|
16
|
+
CYAN='\033[0;36m'
|
|
17
|
+
NC='\033[0m'
|
|
18
|
+
|
|
19
|
+
# Helper: validate JSON structure
|
|
20
|
+
check() {
|
|
21
|
+
local name="$1"
|
|
22
|
+
local json="$2"
|
|
23
|
+
local path="$3"
|
|
24
|
+
local expected_type="$4"
|
|
25
|
+
|
|
26
|
+
local val
|
|
27
|
+
val=$(echo "$json" | python3 -c "
|
|
28
|
+
import sys, json
|
|
29
|
+
try:
|
|
30
|
+
d = json.load(sys.stdin)
|
|
31
|
+
keys = '$path'.split('.')
|
|
32
|
+
obj = d
|
|
33
|
+
for k in keys:
|
|
34
|
+
if k.isdigit():
|
|
35
|
+
obj = obj[int(k)]
|
|
36
|
+
else:
|
|
37
|
+
obj = obj[k]
|
|
38
|
+
print(type(obj).__name__, repr(obj)[:80])
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print('ERROR', str(e))
|
|
41
|
+
" 2>&1)
|
|
42
|
+
|
|
43
|
+
local actual_type="${val%% *}"
|
|
44
|
+
local actual_val="${val#* }"
|
|
45
|
+
|
|
46
|
+
if [[ "$val" == ERROR* ]]; then
|
|
47
|
+
echo -e " ${RED}✗ FAIL${NC} $name → path '$path' not found: ${actual_val#ERROR }"
|
|
48
|
+
((FAIL++))
|
|
49
|
+
elif [[ -n "$expected_type" && "$actual_type" != "$expected_type" ]]; then
|
|
50
|
+
echo -e " ${YELLOW}⚠ WARN${NC} $name → $path: expected $expected_type, got $actual_type ($actual_val)"
|
|
51
|
+
((WARN++))
|
|
52
|
+
else
|
|
53
|
+
echo -e " ${GREEN}✓ PASS${NC} $name → $path: $actual_type $actual_val"
|
|
54
|
+
((PASS++))
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Helper: check HTTP status
|
|
59
|
+
status_check() {
|
|
60
|
+
local name="$1"
|
|
61
|
+
local url="$2"
|
|
62
|
+
local expected_code="$3"
|
|
63
|
+
local max_time="${4:-30}"
|
|
64
|
+
|
|
65
|
+
local response
|
|
66
|
+
response=$(curl -s -w "\n%{http_code}" --connect-timeout 10 --max-time "$max_time" "$url" 2>&1)
|
|
67
|
+
local http_code
|
|
68
|
+
http_code=$(echo "$response" | tail -1)
|
|
69
|
+
local body
|
|
70
|
+
body=$(echo "$response" | sed '$d')
|
|
71
|
+
|
|
72
|
+
if [[ "$http_code" == "$expected_code" ]]; then
|
|
73
|
+
echo -e "${GREEN}✓ PASS${NC} [$http_code] $name"
|
|
74
|
+
((PASS++))
|
|
75
|
+
echo "$body"
|
|
76
|
+
else
|
|
77
|
+
echo -e "${RED}✗ FAIL${NC} [$http_code] $name (expected $expected_code)"
|
|
78
|
+
((FAIL++))
|
|
79
|
+
echo "$body" | head -5
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
echo -e "${CYAN}============================================${NC}"
|
|
84
|
+
echo -e "${CYAN} pahe-api — Endpoint Validation Tests${NC}"
|
|
85
|
+
echo -e "${CYAN}============================================${NC}"
|
|
86
|
+
echo ""
|
|
87
|
+
|
|
88
|
+
# ────────────────────────────────────────────────────────────
|
|
89
|
+
echo -e "${CYAN}[1/13] GET /airing${NC}"
|
|
90
|
+
json=$(status_check "Airing anime" "$BASE/airing" "200")
|
|
91
|
+
echo ""
|
|
92
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
93
|
+
check "paginationInfo.total" "$json" "paginationInfo.total" "int"
|
|
94
|
+
check "paginationInfo.perPage" "$json" "paginationInfo.perPage" "int"
|
|
95
|
+
check "paginationInfo.currentPage" "$json" "paginationInfo.currentPage" "int"
|
|
96
|
+
check "data (array)" "$json" "data" "list"
|
|
97
|
+
check "data[0].id" "$json" "data.0.id" "int"
|
|
98
|
+
check "data[0].title" "$json" "data.0.title" "str"
|
|
99
|
+
check "data[0].episode" "$json" "data.0.episode" "int"
|
|
100
|
+
check "data[0].fansub" "$json" "data.0.fansub" "str"
|
|
101
|
+
check "data[0].image (URL)" "$json" "data.0.image" "str"
|
|
102
|
+
check "data[0].session" "$json" "data.0.session" "str"
|
|
103
|
+
fi
|
|
104
|
+
echo ""
|
|
105
|
+
|
|
106
|
+
# ────────────────────────────────────────────────────────────
|
|
107
|
+
echo -e "${CYAN}[2/13] GET /search?q=naruto${NC}"
|
|
108
|
+
json=$(status_check "Search anime" "$BASE/search?q=naruto" "200")
|
|
109
|
+
echo ""
|
|
110
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
111
|
+
check "paginationInfo.total" "$json" "paginationInfo.total" "int"
|
|
112
|
+
check "data (array)" "$json" "data" "list"
|
|
113
|
+
check "data[0].id" "$json" "data.0.id" "int"
|
|
114
|
+
check "data[0].title" "$json" "data.0.title" "str"
|
|
115
|
+
check "data[0].status" "$json" "data.0.status" "str"
|
|
116
|
+
check "data[0].type" "$json" "data.0.type" "str"
|
|
117
|
+
check "data[0].episodes" "$json" "data.0.episodes" "int"
|
|
118
|
+
check "data[0].score" "$json" "data.0.score" "float"
|
|
119
|
+
check "data[0].year" "$json" "data.0.year" "int"
|
|
120
|
+
check "data[0].poster (URL)" "$json" "data.0.poster" "str"
|
|
121
|
+
check "data[0].session" "$json" "data.0.session" "str"
|
|
122
|
+
fi
|
|
123
|
+
echo ""
|
|
124
|
+
|
|
125
|
+
# ────────────────────────────────────────────────────────────
|
|
126
|
+
echo -e "${CYAN}[3/13] GET /search (page 2)?q=naruto&page=2${NC}"
|
|
127
|
+
json=$(status_check "Search (page 2)" "$BASE/search?q=naruto&page=2" "200")
|
|
128
|
+
echo ""
|
|
129
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
130
|
+
check "paginationInfo.currentPage" "$json" "paginationInfo.currentPage" "int"
|
|
131
|
+
check "data (array)" "$json" "data" "list"
|
|
132
|
+
fi
|
|
133
|
+
echo ""
|
|
134
|
+
|
|
135
|
+
# ────────────────────────────────────────────────────────────
|
|
136
|
+
echo -e "${CYAN}[4/13] GET /list (A-Z) (A-Z List)${NC}"
|
|
137
|
+
json=$(status_check "Anime list" "$BASE/list" "200")
|
|
138
|
+
echo ""
|
|
139
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
140
|
+
check "data (array)" "$json" "0" "list"
|
|
141
|
+
check "[0].title" "$json" "0.title" "str"
|
|
142
|
+
check "[0].url" "$json" "0.url" "str"
|
|
143
|
+
fi
|
|
144
|
+
echo ""
|
|
145
|
+
|
|
146
|
+
# ────────────────────────────────────────────────────────────
|
|
147
|
+
echo -e "${CYAN}[5/13] GET /list?tab=N (by letter)${NC}"
|
|
148
|
+
json=$(status_check "Anime list (letter N)" "$BASE/list?tab=N" "200")
|
|
149
|
+
echo ""
|
|
150
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
151
|
+
check "[0].title" "$json" "0.title" "str"
|
|
152
|
+
check "[0].url" "$json" "0.url" "str"
|
|
153
|
+
fi
|
|
154
|
+
echo ""
|
|
155
|
+
|
|
156
|
+
# ────────────────────────────────────────────────────────────
|
|
157
|
+
echo -e "${CYAN}[6/13] GET /list/genre/action (by tag)${NC}"
|
|
158
|
+
json=$(status_check "Anime list (genre/action)" "$BASE/list/genre/action" "200")
|
|
159
|
+
echo ""
|
|
160
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
161
|
+
check "[0].title" "$json" "0.title" "str"
|
|
162
|
+
check "[0].url" "$json" "0.url" "str"
|
|
163
|
+
fi
|
|
164
|
+
echo ""
|
|
165
|
+
|
|
166
|
+
# ────────────────────────────────────────────────────────────
|
|
167
|
+
echo -e "${CYAN}[7/13] GET /:id (Anime Info) (Anime Info)${NC}"
|
|
168
|
+
ANIME_ID="66e575d9-1c71-2117-bf09-acef2f6a0deb"
|
|
169
|
+
json=$(status_check "Anime info (Naruto)" "$BASE/$ANIME_ID" "200")
|
|
170
|
+
echo ""
|
|
171
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
172
|
+
check "title" "$json" "title" "str"
|
|
173
|
+
check "image (URL)" "$json" "image" "str"
|
|
174
|
+
check "synopsis" "$json" "synopsis" "str"
|
|
175
|
+
check "type" "$json" "type" "str"
|
|
176
|
+
check "episodes (str)" "$json" "episodes" "str"
|
|
177
|
+
check "status" "$json" "status" "str"
|
|
178
|
+
check "season" "$json" "season" "str"
|
|
179
|
+
check "studio" "$json" "studio" "str"
|
|
180
|
+
check "ids.animepahe_id" "$json" "ids.animepahe_id" "str"
|
|
181
|
+
check "ids.mal" "$json" "ids.mal" "str"
|
|
182
|
+
check "ids.anilist" "$json" "ids.anilist" "str"
|
|
183
|
+
fi
|
|
184
|
+
echo ""
|
|
185
|
+
|
|
186
|
+
# ────────────────────────────────────────────────────────────
|
|
187
|
+
echo -e "${CYAN}[8/13] GET /:id/releases (Episodes) (Episodes)${NC}"
|
|
188
|
+
json=$(status_check "Episodes (Naruto)" "$BASE/$ANIME_ID/releases" "200")
|
|
189
|
+
echo ""
|
|
190
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
191
|
+
check "paginationInfo.total" "$json" "paginationInfo.total" "int"
|
|
192
|
+
check "paginationInfo.perPage" "$json" "paginationInfo.perPage" "int"
|
|
193
|
+
check "paginationInfo.lastPage" "$json" "paginationInfo.lastPage" "int"
|
|
194
|
+
check "data (array)" "$json" "data" "list"
|
|
195
|
+
check "data[0].id" "$json" "data.0.id" "int"
|
|
196
|
+
check "data[0].episode" "$json" "data.0.episode" "int"
|
|
197
|
+
check "data[0].audio" "$json" "data.0.audio" "str"
|
|
198
|
+
check "data[0].duration" "$json" "data.0.duration" "str"
|
|
199
|
+
check "data[0].session" "$json" "data.0.session" "str"
|
|
200
|
+
fi
|
|
201
|
+
echo ""
|
|
202
|
+
|
|
203
|
+
# ────────────────────────────────────────────────────────────
|
|
204
|
+
echo -e "${CYAN}[9/13] GET /:id/releases (sorted)?sort=&page=2${NC}"
|
|
205
|
+
EP_ID="9a11cba2022e39f2531d2d885a89558263c6a54bb07bb5fb46ef48a5b7e24c82"
|
|
206
|
+
json=$(status_check "Episodes (sorted + paged)" "$BASE/$ANIME_ID/releases?sort=episode_desc&page=2" "200")
|
|
207
|
+
echo ""
|
|
208
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
209
|
+
check "paginationInfo.currentPage" "$json" "paginationInfo.currentPage" "int"
|
|
210
|
+
check "data (array)" "$json" "data" "list"
|
|
211
|
+
fi
|
|
212
|
+
echo ""
|
|
213
|
+
|
|
214
|
+
# ────────────────────────────────────────────────────────────
|
|
215
|
+
echo -e "${CYAN}[10/13] GET /:id/:ep (Stream + Downloads)${NC}"
|
|
216
|
+
json=$(status_check "Play (Naruto Ep 220)" "$BASE/$ANIME_ID/$EP_ID" "200" "120")
|
|
217
|
+
echo ""
|
|
218
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
219
|
+
check "provider" "$json" "provider" "str"
|
|
220
|
+
check "episode" "$json" "episode" "str"
|
|
221
|
+
check "anime_title" "$json" "anime_title" "str"
|
|
222
|
+
check "sources (array)" "$json" "sources" "list"
|
|
223
|
+
check "sources[0].url (m3u8)" "$json" "sources.0.url" "str"
|
|
224
|
+
check "sources[0].isM3U8" "$json" "sources.0.isM3U8" "bool"
|
|
225
|
+
check "sources[0].embed (URL)" "$json" "sources.0.embed" "str"
|
|
226
|
+
check "sources[0].resolution (str)" "$json" "sources.0.resolution" "str"
|
|
227
|
+
check "sources[0].download (URL)" "$json" "sources.0.download" "str"
|
|
228
|
+
check "downloads (array)" "$json" "downloads" "list"
|
|
229
|
+
check "downloads[0].fansub" "$json" "downloads.0.fansub" "str"
|
|
230
|
+
check "downloads[0].quality" "$json" "downloads.0.quality" "str"
|
|
231
|
+
check "downloads[0].filesize" "$json" "downloads.0.filesize" "str"
|
|
232
|
+
check "downloads[0].isDub" "$json" "downloads.0.isDub" "bool"
|
|
233
|
+
check "downloads[0].pahe (URL)" "$json" "downloads.0.pahe" "str"
|
|
234
|
+
check "downloads[0].download (URL)" "$json" "downloads.0.download" "str"
|
|
235
|
+
fi
|
|
236
|
+
echo ""
|
|
237
|
+
|
|
238
|
+
# ────────────────────────────────────────────────────────────
|
|
239
|
+
echo -e "${CYAN}[11/13] GET /:id/:ep?downloads=false (Stream Only)${NC}"
|
|
240
|
+
json=$(status_check "Play (stream only)" "$BASE/$ANIME_ID/$EP_ID?downloads=false" "200" "60")
|
|
241
|
+
echo ""
|
|
242
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
243
|
+
check "sources (array)" "$json" "sources" "list"
|
|
244
|
+
check "downloads (empty)" "$json" "downloads" "list"
|
|
245
|
+
fi
|
|
246
|
+
echo ""
|
|
247
|
+
|
|
248
|
+
# ────────────────────────────────────────────────────────────
|
|
249
|
+
echo -e "${CYAN}[12/13] GET /download-links?url= (DEPRECATED)${NC}"
|
|
250
|
+
json=$(status_check "Download links (deprecated)" "$BASE/download-links?url=https://pahe.win/ecJzv" "200" "60")
|
|
251
|
+
echo ""
|
|
252
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
253
|
+
check "status" "$json" "status" "int"
|
|
254
|
+
check "message (deprecated)" "$json" "message" "str"
|
|
255
|
+
fi
|
|
256
|
+
echo ""
|
|
257
|
+
|
|
258
|
+
# ────────────────────────────────────────────────────────────
|
|
259
|
+
echo -e "${CYAN}[13/13] GET /queue${NC}"
|
|
260
|
+
json=$(status_check "Queue" "$BASE/queue" "200")
|
|
261
|
+
echo ""
|
|
262
|
+
if echo "$json" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
263
|
+
check "data (array)" "$json" "data" "list"
|
|
264
|
+
fi
|
|
265
|
+
echo ""
|
|
266
|
+
|
|
267
|
+
# ────────────────────────────────────────────────────────────
|
|
268
|
+
echo -e "${CYAN}============================================${NC}"
|
|
269
|
+
echo -e "${GREEN}PASSED: $PASS${NC} | ${RED}FAILED: $FAIL${NC} | ${YELLOW}WARNINGS: $WARN${NC}"
|
|
270
|
+
echo -e "${CYAN}============================================${NC}"
|
|
271
|
+
|
|
272
|
+
if [[ $FAIL -gt 0 ]]; then
|
|
273
|
+
exit 1
|
|
274
|
+
fi
|
|
275
|
+
exit 0
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const fs = require('fs/promises');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { existsSync } = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
function resolveBrowserRuntime() {
|
|
7
|
+
let chromiumBinary = null;
|
|
8
|
+
let chromium = null;
|
|
9
|
+
let useServerlessChromium = false;
|
|
10
|
+
|
|
11
|
+
if (String(process.env.USE_STEALTH).toLowerCase() === 'true') {
|
|
12
|
+
try {
|
|
13
|
+
const playwrightExtra = require('playwright-extra');
|
|
14
|
+
const stealth = require('playwright-extra-plugin-stealth')();
|
|
15
|
+
chromium = playwrightExtra.chromium;
|
|
16
|
+
chromium.use(stealth);
|
|
17
|
+
console.log('Using playwright-extra with stealth plugin (USE_STEALTH=true)');
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.warn('USE_STEALTH=true but playwright-extra or stealth plugin not installed; falling back to regular browser runtime');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
if (!chromium) {
|
|
25
|
+
chromiumBinary = require('@sparticuz/chromium');
|
|
26
|
+
chromium = require('playwright-core').chromium;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (os.platform() === 'linux') {
|
|
30
|
+
useServerlessChromium = true;
|
|
31
|
+
} else {
|
|
32
|
+
console.warn('⚠️ Detected non-Linux OS. Disabling @sparticuz/chromium for local dev.');
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
if (!chromium) {
|
|
36
|
+
try {
|
|
37
|
+
chromium = require('playwright').chromium;
|
|
38
|
+
console.warn('Falling back to full Playwright (probably running locally)');
|
|
39
|
+
} catch (inner) {
|
|
40
|
+
const error = new Error(
|
|
41
|
+
'Browser runtime is not installed. Set FLARESOLVERR_URL or reinstall the Playwright browser stack.'
|
|
42
|
+
);
|
|
43
|
+
error.cause = inner;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { chromium, chromiumBinary, useServerlessChromium };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Launches a Chromium browser with appropriate settings for the current environment.
|
|
54
|
+
* Automatically supports both serverless and local development.
|
|
55
|
+
*/
|
|
56
|
+
async function launchBrowser() {
|
|
57
|
+
const { chromium, chromiumBinary, useServerlessChromium } = resolveBrowserRuntime();
|
|
58
|
+
const isServerless = process.env.VERCEL || process.env.NETLIFY || process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
59
|
+
const isContainerizedLinux =
|
|
60
|
+
os.platform() === 'linux' &&
|
|
61
|
+
(
|
|
62
|
+
process.env.CONTAINER === 'true' ||
|
|
63
|
+
process.env.DOCKER === 'true' ||
|
|
64
|
+
process.env.KUBERNETES_SERVICE_HOST ||
|
|
65
|
+
existsSync('/.dockerenv')
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const baseArgs = [
|
|
69
|
+
'--no-sandbox',
|
|
70
|
+
'--disable-setuid-sandbox',
|
|
71
|
+
'--disable-dev-shm-usage',
|
|
72
|
+
'--disable-blink-features=AutomationControlled',
|
|
73
|
+
'--disable-gpu',
|
|
74
|
+
'--disable-background-networking',
|
|
75
|
+
'--disable-default-apps',
|
|
76
|
+
'--disable-extensions',
|
|
77
|
+
'--disable-infobars',
|
|
78
|
+
'--disable-notifications',
|
|
79
|
+
'--disable-offline-sync',
|
|
80
|
+
'--disable-sync',
|
|
81
|
+
'--disable-translate',
|
|
82
|
+
'--no-first-run',
|
|
83
|
+
'--no-zygote'
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// Serverless-specific optimizations
|
|
87
|
+
const serverlessArgs = [
|
|
88
|
+
'--disable-background-timer-throttling',
|
|
89
|
+
'--disable-backgrounding-occluded-windows',
|
|
90
|
+
'--disable-renderer-backgrounding',
|
|
91
|
+
'--disable-features=TranslateUI',
|
|
92
|
+
'--disable-ipc-flooding-protection',
|
|
93
|
+
'--disable-hang-monitor',
|
|
94
|
+
'--disable-prompt-on-repost',
|
|
95
|
+
'--disable-domain-reliability',
|
|
96
|
+
'--disable-component-extensions-with-background-pages',
|
|
97
|
+
'--memory-pressure-off',
|
|
98
|
+
'--max_old_space_size=4096'
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
// Default headless behavior:
|
|
102
|
+
// - in serverless environments we want headless=true unless explicitly overridden
|
|
103
|
+
// - allow overriding with CHROME_HEADLESS env var ("true"/"false") for local testing
|
|
104
|
+
const envHeadless = typeof process.env.CHROME_HEADLESS !== 'undefined'
|
|
105
|
+
? String(process.env.CHROME_HEADLESS).toLowerCase() === 'true'
|
|
106
|
+
: null;
|
|
107
|
+
|
|
108
|
+
// Containers usually do not have a usable display server, so default to headless there too.
|
|
109
|
+
const defaultHeadless = isServerless || isContainerizedLinux ? true : false;
|
|
110
|
+
|
|
111
|
+
const launchOptions = {
|
|
112
|
+
headless: envHeadless === null ? defaultHeadless : envHeadless,
|
|
113
|
+
args: isServerless ? [...baseArgs, ...serverlessArgs] : baseArgs,
|
|
114
|
+
// Reduced timeouts for serverless
|
|
115
|
+
timeout: isServerless ? 30000 : 60000
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Add user agent
|
|
119
|
+
launchOptions.args.push(
|
|
120
|
+
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (useServerlessChromium && chromiumBinary) {
|
|
124
|
+
console.log('Using serverless Chromium binary');
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const executablePath = await chromiumBinary.executablePath();
|
|
128
|
+
|
|
129
|
+
if (existsSync(executablePath)) {
|
|
130
|
+
launchOptions.executablePath = executablePath;
|
|
131
|
+
// Use chromium.args but merge with our custom args
|
|
132
|
+
// Prepend serverless chromium recommended args so they run first
|
|
133
|
+
launchOptions.args = [...chromiumBinary.args, ...launchOptions.args];
|
|
134
|
+
|
|
135
|
+
console.log('Serverless Chromium configured successfully');
|
|
136
|
+
} else {
|
|
137
|
+
console.warn('⚠️ Chromium binary not found at expected path. Falling back to default.');
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Error setting up serverless Chromium:', error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log('Launching browser with headless=%s and args count=%d', launchOptions.headless, launchOptions.args.length);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const browser = await chromium.launch(launchOptions);
|
|
148
|
+
console.log('Browser launched successfully');
|
|
149
|
+
return browser;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Failed to launch browser:', error);
|
|
152
|
+
|
|
153
|
+
// Fallback with minimal args
|
|
154
|
+
console.log('Attempting fallback launch with minimal configuration...');
|
|
155
|
+
const fallbackOptions = {
|
|
156
|
+
headless: true,
|
|
157
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (useServerlessChromium && chromiumBinary) {
|
|
161
|
+
try {
|
|
162
|
+
fallbackOptions.executablePath = await chromiumBinary.executablePath();
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.warn('Could not set executable path for fallback');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return await chromium.launch(fallbackOptions);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = { launchBrowser };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const dotenv = require("dotenv");
|
|
4
|
+
|
|
5
|
+
dotenv.config({
|
|
6
|
+
path: path.join(__dirname, "..", "..", ".env"),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
class Config {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.hostUrl = "";
|
|
12
|
+
this.baseUrl = "https://animepahe.pw";
|
|
13
|
+
this.iframeBaseUrl = "uwucdn.top";
|
|
14
|
+
this.userAgent =
|
|
15
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
16
|
+
this.extraHTTPHeaders = {
|
|
17
|
+
Accept:
|
|
18
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
|
19
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
20
|
+
"Accept-Encoding": "gzip, deflate, br",
|
|
21
|
+
DNT: "1",
|
|
22
|
+
Connection: "keep-alive",
|
|
23
|
+
"Upgrade-Insecure-Requests": "1",
|
|
24
|
+
};
|
|
25
|
+
this.cfClearance = "";
|
|
26
|
+
this.cookiesRefreshInterval = 14 * 24 * 60 * 60 * 1000;
|
|
27
|
+
this.proxies = [];
|
|
28
|
+
this.proxyEnabled = false;
|
|
29
|
+
|
|
30
|
+
// Environment-specific settings
|
|
31
|
+
this.isServerless = !!(
|
|
32
|
+
process.env.VERCEL ||
|
|
33
|
+
process.env.NETLIFY ||
|
|
34
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME
|
|
35
|
+
);
|
|
36
|
+
this.maxRetries = this.isServerless ? 1 : 3; // Reduced retries on Vercel
|
|
37
|
+
this.requestTimeout = this.isServerless ? 10000 : 30000; // Reduced timeout on Vercel
|
|
38
|
+
this.challengeTimeout = this.isServerless ? 10000 : 30000; // Reduced timeout on Vercel
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setHostUrl(protocol, host) {
|
|
42
|
+
if (!this.hostUrl && protocol && host) {
|
|
43
|
+
this.hostUrl = `${protocol}://${host}`;
|
|
44
|
+
console.log(`Host URL set to: ${this.hostUrl}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setCfClearance(value) {
|
|
49
|
+
if (!value) {
|
|
50
|
+
console.warn("Warning: cf_clearance value is empty");
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
this.cfClearance = value.replace(/^cf_clearance=/, "");
|
|
54
|
+
console.log("\x1b[36m%s\x1b[0m", "cf_clearance set");
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setCookies(cookieString) {
|
|
59
|
+
if (!cookieString) return false;
|
|
60
|
+
const match = cookieString.match(/cf_clearance=([^;]+)/);
|
|
61
|
+
if (match) {
|
|
62
|
+
this.cfClearance = match[1];
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get cookies() {
|
|
69
|
+
return this.cfClearance ? `cf_clearance=${this.cfClearance}` : "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setProxy(proxyString) {
|
|
73
|
+
if (!proxyString) {
|
|
74
|
+
console.warn("Warning: Proxy string is empty");
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Validate proxy format (basic check)
|
|
80
|
+
const proxyUrl = new URL(
|
|
81
|
+
proxyString.startsWith("http") ? proxyString : `http://${proxyString}`,
|
|
82
|
+
);
|
|
83
|
+
if (proxyUrl.hostname) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(`Invalid proxy format: ${proxyString}`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getRandomProxy() {
|
|
94
|
+
if (this.proxies.length === 0) {
|
|
95
|
+
console.log("No proxies available.");
|
|
96
|
+
}
|
|
97
|
+
return this.proxies[Math.floor(Math.random() * this.proxies.length)];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Method to update proxies dynamically
|
|
101
|
+
updateProxies(newProxies) {
|
|
102
|
+
this.proxies = newProxies;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getUrl(section, params = {}) {
|
|
106
|
+
const { page, tab, genre } = params;
|
|
107
|
+
const primary = params.primary || params.animeId || params.id || "";
|
|
108
|
+
const secondary = params.secondary || params.episodeId || params.ep || "";
|
|
109
|
+
|
|
110
|
+
const paths = {
|
|
111
|
+
home: "/",
|
|
112
|
+
queue: "/queue",
|
|
113
|
+
animeInfo: `/anime/${primary}`,
|
|
114
|
+
animeList: "/anime",
|
|
115
|
+
play: `/play/${primary}/${secondary}`,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (!paths[section]) {
|
|
119
|
+
throw new Error(`Invalid section: ${section}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let url = `${this.baseUrl}${paths[section]}`;
|
|
123
|
+
|
|
124
|
+
// Add query params for animeList
|
|
125
|
+
if (section === "animeList") {
|
|
126
|
+
const queryParams = new URLSearchParams();
|
|
127
|
+
if (page) queryParams.append("page", page);
|
|
128
|
+
if (tab) queryParams.append("tab", tab);
|
|
129
|
+
if (genre) queryParams.append("genre", genre);
|
|
130
|
+
|
|
131
|
+
const queryString = queryParams.toString();
|
|
132
|
+
if (queryString) {
|
|
133
|
+
url += `?${queryString}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return url;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
loadFromEnv() {
|
|
141
|
+
if (process.env.BASE_URL) {
|
|
142
|
+
this.baseUrl = process.env.BASE_URL;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (process.env.USER_AGENT) {
|
|
146
|
+
this.userAgent = process.env.USER_AGENT;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (process.env.HOST_URL) {
|
|
150
|
+
this.hostUrl = process.env.HOST_URL;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (process.env.cookiesRefreshInterval) {
|
|
154
|
+
this.cookiesRefreshInterval = process.env.cookiesRefreshInterval;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle cf_clearance
|
|
158
|
+
if (process.env.CF_CLEARANCE) {
|
|
159
|
+
this.setCfClearance(process.env.CF_CLEARANCE);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Handle proxies
|
|
163
|
+
if (process.env.PROXIES) {
|
|
164
|
+
try {
|
|
165
|
+
const proxyList = process.env.PROXIES.split(",").map((proxy) =>
|
|
166
|
+
proxy.trim(),
|
|
167
|
+
);
|
|
168
|
+
const validProxies = proxyList.filter((proxy) => this.setProxy(proxy));
|
|
169
|
+
|
|
170
|
+
if (validProxies.length === 0) {
|
|
171
|
+
console.warn("No valid proxies found in environment variables");
|
|
172
|
+
this.proxies = [];
|
|
173
|
+
} else {
|
|
174
|
+
this.proxies = validProxies;
|
|
175
|
+
console.log(`Successfully loaded ${validProxies.length} proxies`);
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(
|
|
179
|
+
"Error processing proxies from environment variables:",
|
|
180
|
+
error.message,
|
|
181
|
+
);
|
|
182
|
+
this.proxies = [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (process.env.IFRAME_BASE_URL) {
|
|
187
|
+
this.iframeBaseUrl = process.env.IFRAME_BASE_URL;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.proxyEnabled = process.env.USE_PROXY === "true";
|
|
191
|
+
if (this.proxyEnabled && this.proxies.length === 0) {
|
|
192
|
+
console.warn(
|
|
193
|
+
"Proxy usage is enabled but no valid proxies are configured",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
validate() {
|
|
199
|
+
if (!this.baseUrl) {
|
|
200
|
+
throw new Error("Base URL is required in configuration.");
|
|
201
|
+
}
|
|
202
|
+
if (!this.userAgent) {
|
|
203
|
+
throw new Error("User-Agent is required in configuration.");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const config = new Config();
|
|
209
|
+
module.exports = config;
|