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
package/anime/readme.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# AnimepaheAPI
|
|
2
|
+
|
|
3
|
+
An unofficial REST API for [Animepahe](https://animepahe.pw/) that provides access to anime information, episodes, and streaming links with direct download support.
|
|
4
|
+
|
|
5
|
+
> **⚠️ IMPORTANT NOTICE: API MAYBE Temporarily Paused**
|
|
6
|
+
> The API maybe temporarily paused due to suspiciously too many requests. My hosted version of this API is only for testing purposes. **You MUST host your own instance** to use the API.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🎯 Get currently airing anime
|
|
11
|
+
- 🔍 Search for specific anime
|
|
12
|
+
- 📋 Browse complete anime list
|
|
13
|
+
- 📺 Get anime details and episodes
|
|
14
|
+
- 🎬 Get streaming links with multiple resolutions
|
|
15
|
+
- 📥 Direct download links with quality options
|
|
16
|
+
- ⚡ Optimized parallel processing for faster responses
|
|
17
|
+
- 📱 Check encoding queue status
|
|
18
|
+
- 🐋 Redis support for improved performance
|
|
19
|
+
- 🛡️ Built-in DDoS protection bypass
|
|
20
|
+
- 🔄 Automatic cookie management
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/aor-rex/pahe-api.git
|
|
26
|
+
cd animepahe-api
|
|
27
|
+
npm install
|
|
28
|
+
npx playwright install
|
|
29
|
+
copy .env.example .env
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage as a Library
|
|
33
|
+
|
|
34
|
+
You can also use this project as a **Node.js library** directly in your code without running a server.
|
|
35
|
+
|
|
36
|
+
**Installation:**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install github:aor-rex/pahe-api
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Example Usage:**
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
const animepahe = require("pahe-api");
|
|
46
|
+
|
|
47
|
+
// Search for anime
|
|
48
|
+
const searchResults = await animepahe.search("title");
|
|
49
|
+
console.log(searchResults);
|
|
50
|
+
|
|
51
|
+
// Get anime info
|
|
52
|
+
const info = await animepahe.getInfo("title");
|
|
53
|
+
console.log(info);
|
|
54
|
+
|
|
55
|
+
// Get episode releases
|
|
56
|
+
const episodes = await animepahe.getReleases("title", "episode_desc", 1);
|
|
57
|
+
console.log(episodes);
|
|
58
|
+
|
|
59
|
+
// Get streaming links (m3u8 URLs and download links)
|
|
60
|
+
const streamingData = await animepahe.getStreamingLinks(
|
|
61
|
+
"anime-session-id",
|
|
62
|
+
"episode-session-id",
|
|
63
|
+
);
|
|
64
|
+
console.log(streamingData.sources); // HLS streams
|
|
65
|
+
console.log(streamingData.downloads); // Direct download links
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Deploy
|
|
69
|
+
|
|
70
|
+
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FElijahCodes12345%2Fanimepahe-api)
|
|
71
|
+
[](https://heroku.com/deploy?template=https://github.com/ElijahCodes12345/animepahe-api)
|
|
72
|
+
[](https://railway.app/new/template/animepahe-api?referralCode=EgKNlg)
|
|
73
|
+
[](https://render.com/deploy?repo=https://github.com/ElijahCodes12345/animepahe-api)
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
It works as it is but if you want you can create a `.env` file in the root directory:
|
|
78
|
+
|
|
79
|
+
```env
|
|
80
|
+
PORT=3000 # Optional
|
|
81
|
+
BASE_URL=https://animepahe.pw # Optional
|
|
82
|
+
USER_AGENT= # Optional
|
|
83
|
+
COOKIES= # Optional - for manual cookie management
|
|
84
|
+
USE_PROXY=false
|
|
85
|
+
IFRAME_BASE_URL=kwik.cx # Optional - Base URL for the video iframe (default: kwik.cx)
|
|
86
|
+
PROXIES= # Optional - comma-separated proxy URLs
|
|
87
|
+
REDIS_URL= # Optional - Redis connection URL for caching (e.g., redis://user:pass@host:port)
|
|
88
|
+
ALLOWED_ORIGINS= # Optional - CORS configuration (see below)
|
|
89
|
+
RATE_LIMIT_SECRET= # Only set this if you want rate limiting (see Rate Limiting section below)
|
|
90
|
+
RATE_LIMIT_MAX=100 # Max requests per window (when rate limiting is active)
|
|
91
|
+
RATE_LIMIT_WINDOW=900 # Time window in seconds (when rate limiting is active)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### CORS Configuration
|
|
95
|
+
|
|
96
|
+
By default, the API will allow requests from **all origins** (`*`). To restrict access to specific domains:
|
|
97
|
+
|
|
98
|
+
```env
|
|
99
|
+
# Allow all origins (default)
|
|
100
|
+
ALLOWED_ORIGINS=*
|
|
101
|
+
|
|
102
|
+
# Allow single origin
|
|
103
|
+
ALLOWED_ORIGINS=http://localhost:5173
|
|
104
|
+
|
|
105
|
+
# Allow multiple origins (comma-separated)
|
|
106
|
+
ALLOWED_ORIGINS=http://localhost:5173,https://mywebsite.com,https://another-site.com
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Redis Caching
|
|
110
|
+
|
|
111
|
+
The API supports Redis caching to improve performance and reduce load on the Animepahe servers. When `REDIS_URL` is provided, responses will be cached with the following durations:
|
|
112
|
+
|
|
113
|
+
- Queue status: 30 seconds
|
|
114
|
+
- Anime list: 1 hour
|
|
115
|
+
- Anime info: 1 day
|
|
116
|
+
- Play/stream info: 5 hours
|
|
117
|
+
|
|
118
|
+
You may edit these values as you see fit.
|
|
119
|
+
|
|
120
|
+
If `REDIS_URL` is not provided, the API will still work without caching.
|
|
121
|
+
|
|
122
|
+
## Rate Limiting
|
|
123
|
+
|
|
124
|
+
This API includes optional rate limiting that only activates when the `RATE_LIMIT_SECRET` environment variable is set. This allows you to add rate limiting to your own deployment (like your Vercel instance) without affecting other people who might host their own instances.
|
|
125
|
+
|
|
126
|
+
To enable rate limiting on your deployment:
|
|
127
|
+
|
|
128
|
+
1. Set a unique `RATE_LIMIT_SECRET` value (e.g., a random UUID)
|
|
129
|
+
2. Configure `RATE_LIMIT_MAX` (default: 100 requests per window)
|
|
130
|
+
3. Configure `RATE_LIMIT_WINDOW` in seconds (default: 900 seconds = 15 minutes)
|
|
131
|
+
4. Configure `REDIS_URL` for persistent rate limiting across server instances (recommended for production use)
|
|
132
|
+
|
|
133
|
+
**Note**: Rate limiting requires Redis to function properly. If `REDIS_URL` is not configured, rate limiting will be automatically disabled even if `RATE_LIMIT_SECRET` is set. This ensures that other deployments without Redis continue to work normally.
|
|
134
|
+
|
|
135
|
+
When rate limiting is active, the API will return HTTP 429 status codes when limits are exceeded, along with rate limit headers:
|
|
136
|
+
|
|
137
|
+
- `X-RateLimit-Limit`: Maximum requests per window
|
|
138
|
+
- `X-RateLimit-Remaining`: Requests remaining in current window
|
|
139
|
+
- `X-RateLimit-Reset`: Time when the current window resets
|
|
140
|
+
|
|
141
|
+
## API Endpoints
|
|
142
|
+
|
|
143
|
+
### Airing Anime
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
GET /api/airing
|
|
147
|
+
GET /api/airing?page=2
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Search Anime
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
GET /api/search?q=your_search_query
|
|
154
|
+
GET /api/search?q=your_search_query&page=2
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Anime List
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
GET /api/anime
|
|
161
|
+
GET /api/anime?tab=A
|
|
162
|
+
GET /api/anime/:tag1/:tag2
|
|
163
|
+
GET /api/anime/:tag1/:tag2?tab=D
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
tag1 being eg: genre, Theme, etc. tag2 being eg: action, adventure, historical etc.
|
|
167
|
+
Note: For tab use 'hash' instead of '#'
|
|
168
|
+
|
|
169
|
+
### Anime Information
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
GET /api/:session # Get anime details using anime session ID
|
|
173
|
+
GET /api/:session/releases?sort=episode_desc&page=1 # Get episode list
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Streaming & Downloads
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
GET /api/play/:session?episodeId=example # With downloads (default)
|
|
180
|
+
GET /api/play/:session?episodeId=example&downloads=false # Without downloads (faster)
|
|
181
|
+
GET /api/play/download-links?url=https://pahe.win/XYZ # To only get one download link
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> **Note:**
|
|
185
|
+
>
|
|
186
|
+
> - `:session` is the anime's unique identifier
|
|
187
|
+
> - `episodeId` is the episode's unique identifier from the /releases endpoint
|
|
188
|
+
> - Use `downloads=false` for faster responses if you only need streaming links
|
|
189
|
+
|
|
190
|
+
### Queue Status
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
GET /api/queue
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Error Handling
|
|
197
|
+
|
|
198
|
+
The API returns errors in this format:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"status": 503,
|
|
203
|
+
"message": "Request failed"
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Technologies Used
|
|
208
|
+
|
|
209
|
+
- Node.js
|
|
210
|
+
- Express
|
|
211
|
+
- Playwright
|
|
212
|
+
- @sparticuz/chromium
|
|
213
|
+
- Cheerio
|
|
214
|
+
- Axios
|
|
215
|
+
- cloudscraper
|
|
216
|
+
- jsdom
|
|
217
|
+
- vm
|
|
218
|
+
|
|
219
|
+
## Acknowledgments
|
|
220
|
+
|
|
221
|
+
Special thanks to:
|
|
222
|
+
|
|
223
|
+
- [Pal-droid](https://github.com/Pal-droid) for assistance in improving the project's logic and features.
|
|
224
|
+
- All the developers whose libraries made this project possible.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
This project is licensed under the MIT License.
|
|
229
|
+
|
|
230
|
+
## Disclaimer
|
|
231
|
+
|
|
232
|
+
This project is not affiliated with or endorsed by Animepahe. It's an unofficial API created for educational purposes.
|
|
233
|
+
|
|
234
|
+
## Support
|
|
235
|
+
|
|
236
|
+
If you find this project helpful, please give it a ⭐️ on GitHub!
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const AnimeInfoController = require('../controllers/animeInfoController');
|
|
3
|
+
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
router.get('/:id', AnimeInfoController.getAnimeInfo);
|
|
7
|
+
router.get('/:id/releases', AnimeInfoController.getAnimeReleases);
|
|
8
|
+
|
|
9
|
+
module.exports = router;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const AnimeListController = require("../controllers/animeListController");
|
|
3
|
+
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
// Browse anime list with query params: ?page=2&tab=A&genre=action
|
|
7
|
+
router.get("/list", AnimeListController.getAllAnime);
|
|
8
|
+
|
|
9
|
+
module.exports = router;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cache = require('../middleware/cache');
|
|
3
|
+
const HomeController = require('../controllers/homeController');
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
router.get('/airing', cache(30), HomeController.getAiringAnime);
|
|
8
|
+
router.get('/search', cache(120), HomeController.searchAnime);
|
|
9
|
+
|
|
10
|
+
module.exports = router;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const PlayController = require("../controllers/playController");
|
|
3
|
+
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
// NOTE: /download-links endpoint removed — was deprecated (501) due to
|
|
7
|
+
// Cloudflare on kwik.cx. Use /:id/:ep → sources[].download instead.
|
|
8
|
+
router.get("/download-proxy", PlayController.proxyDownload);
|
|
9
|
+
router.get("/:id/:ep", PlayController.getStreamingLinks);
|
|
10
|
+
|
|
11
|
+
module.exports = router;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const TestController = require("../controllers/testController");
|
|
4
|
+
const { launchBrowser } = require("../utils/browser");
|
|
5
|
+
|
|
6
|
+
router.get("/kwik-test", TestController.resolveKwik);
|
|
7
|
+
|
|
8
|
+
router.get("/downlod-test", TestController.download);
|
|
9
|
+
|
|
10
|
+
router.get("/test", async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const playPageUrl =
|
|
13
|
+
"https://animepahe.pw/play/9a16dfb8-8ffc-a0b0-6508-1b291afa04a7/b3a2934c2694eb256d0258ea1fea00dbf620eddd57cbadb97bec7019dc18dcc9";
|
|
14
|
+
|
|
15
|
+
const browser = await launchBrowser();
|
|
16
|
+
const context = await browser.newContext({
|
|
17
|
+
userAgent:
|
|
18
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
19
|
+
viewport: { width: 1280, height: 800 },
|
|
20
|
+
javaScriptEnabled: true,
|
|
21
|
+
// Add extra headers to appear more like a real browser
|
|
22
|
+
extraHTTPHeaders: {
|
|
23
|
+
Accept:
|
|
24
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
|
25
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
26
|
+
"Accept-Encoding": "gzip, deflate, br",
|
|
27
|
+
DNT: "1",
|
|
28
|
+
Connection: "keep-alive",
|
|
29
|
+
"Upgrade-Insecure-Requests": "1",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const cookies = await context.cookies();
|
|
34
|
+
const relevantCookies = cookies.filter(
|
|
35
|
+
(cookie) =>
|
|
36
|
+
cookie.name.includes("cf_clearance") ||
|
|
37
|
+
cookie.name.includes("srvs") ||
|
|
38
|
+
cookie.name.includes("__cf") ||
|
|
39
|
+
cookie.name.includes("_cflb") ||
|
|
40
|
+
cookie.domain.includes("kwik.si") ||
|
|
41
|
+
cookie.domain.includes(".si"),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
console.log(relevantCookies);
|
|
45
|
+
|
|
46
|
+
const page = await context.newPage();
|
|
47
|
+
|
|
48
|
+
// Enhanced stealth protections
|
|
49
|
+
await page.addInitScript(() => {
|
|
50
|
+
// Hide webdriver property
|
|
51
|
+
Object.defineProperty(navigator, "webdriver", { get: () => undefined });
|
|
52
|
+
|
|
53
|
+
// Mock plugins
|
|
54
|
+
Object.defineProperty(navigator, "plugins", {
|
|
55
|
+
get: () => [
|
|
56
|
+
{ name: "Chrome PDF Plugin" },
|
|
57
|
+
{ name: "Chrome PDF Viewer" },
|
|
58
|
+
{ name: "Native Client" },
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Mock languages
|
|
63
|
+
Object.defineProperty(navigator, "languages", {
|
|
64
|
+
get: () => ["en-US", "en"],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Mock chrome object
|
|
68
|
+
window.chrome = { runtime: {} };
|
|
69
|
+
|
|
70
|
+
// Mock permissions
|
|
71
|
+
const originalQuery = window.navigator.permissions.query;
|
|
72
|
+
window.navigator.permissions.query = (parameters) =>
|
|
73
|
+
parameters.name === "notifications"
|
|
74
|
+
? Promise.resolve({ state: Notification.permission })
|
|
75
|
+
: originalQuery(parameters);
|
|
76
|
+
|
|
77
|
+
// Mock screen properties
|
|
78
|
+
Object.defineProperty(screen, "colorDepth", { get: () => 24 });
|
|
79
|
+
Object.defineProperty(screen, "pixelDepth", { get: () => 24 });
|
|
80
|
+
|
|
81
|
+
// Remove automation indicators
|
|
82
|
+
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
|
|
83
|
+
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
|
|
84
|
+
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Block ads and tracking
|
|
88
|
+
await page.route("**/*", (route) => {
|
|
89
|
+
const url = route.request().url();
|
|
90
|
+
if (
|
|
91
|
+
/ads|doubleclick|popunder|popads|brunetsmolted|duelistdoesnt|kryptonnutlet|whitebit|garsilgilpey|analytics|googletagmanager|facebook|twitter/.test(
|
|
92
|
+
url,
|
|
93
|
+
)
|
|
94
|
+
) {
|
|
95
|
+
return route.abort();
|
|
96
|
+
}
|
|
97
|
+
route.continue();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
console.log("Navigating to Animepahe...");
|
|
101
|
+
await page.goto(playPageUrl, { waitUntil: "networkidle", timeout: 60000 });
|
|
102
|
+
|
|
103
|
+
// Wait a bit for any initial scripts to load
|
|
104
|
+
await page.waitForTimeout(3000);
|
|
105
|
+
|
|
106
|
+
let kwikResponse = null;
|
|
107
|
+
let kwikUrl = null;
|
|
108
|
+
|
|
109
|
+
// Enhanced route interception with better headers
|
|
110
|
+
await page.route("**/kwik.si/e/**", async (route) => {
|
|
111
|
+
const url = route.request().url();
|
|
112
|
+
console.log("Intercepting kwik route:", url);
|
|
113
|
+
kwikUrl = url;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
117
|
+
|
|
118
|
+
const response = await route.fetch({
|
|
119
|
+
headers: {
|
|
120
|
+
...route.request().headers(),
|
|
121
|
+
Referer: playPageUrl,
|
|
122
|
+
Origin: "https://animepahe.pw",
|
|
123
|
+
"Sec-Fetch-Dest": "iframe",
|
|
124
|
+
"Sec-Fetch-Mode": "navigate",
|
|
125
|
+
"Sec-Fetch-Site": "cross-site",
|
|
126
|
+
"Sec-Ch-Ua":
|
|
127
|
+
'"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
|
|
128
|
+
"Sec-Ch-Ua-Mobile": "?0",
|
|
129
|
+
"Sec-Ch-Ua-Platform": '"Windows"',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const responseText = await response.text();
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
responseText.includes("Just a moment") ||
|
|
137
|
+
responseText.includes("challenge")
|
|
138
|
+
) {
|
|
139
|
+
console.log(
|
|
140
|
+
"Received Cloudflare challenge, trying alternative approach...",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
await route.continue();
|
|
144
|
+
|
|
145
|
+
setTimeout(async () => {
|
|
146
|
+
try {
|
|
147
|
+
const delayedResponse = await page.evaluate(async (url) => {
|
|
148
|
+
const response = await fetch(url, {
|
|
149
|
+
headers: {
|
|
150
|
+
"User-Agent": navigator.userAgent,
|
|
151
|
+
Referer: window.location.href,
|
|
152
|
+
Accept: "*/*",
|
|
153
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
154
|
+
"Cache-Control": "no-cache",
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return await response.text();
|
|
158
|
+
}, url);
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
delayedResponse &&
|
|
162
|
+
!delayedResponse.includes("Just a moment")
|
|
163
|
+
) {
|
|
164
|
+
kwikResponse = delayedResponse;
|
|
165
|
+
console.log(
|
|
166
|
+
"Successfully bypassed Cloudflare via delayed fetch",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error("Delayed fetch failed:", err.message);
|
|
171
|
+
}
|
|
172
|
+
}, 5000);
|
|
173
|
+
} else {
|
|
174
|
+
kwikResponse = responseText;
|
|
175
|
+
console.log("Captured kwik response via route interception");
|
|
176
|
+
|
|
177
|
+
await route.fulfill({
|
|
178
|
+
response: response,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error("Route interception failed:", err.message);
|
|
183
|
+
route.continue();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Click the load button
|
|
188
|
+
await page.waitForSelector(".click-to-load .reload", { timeout: 45000 });
|
|
189
|
+
await page.click(".click-to-load .reload");
|
|
190
|
+
console.log("Clicked load button, waiting for kwik response...");
|
|
191
|
+
|
|
192
|
+
// Wait longer for Cloudflare bypass
|
|
193
|
+
const maxWait = 60000;
|
|
194
|
+
const interval = 2000;
|
|
195
|
+
let elapsed = 0;
|
|
196
|
+
|
|
197
|
+
while (!kwikResponse && elapsed < maxWait) {
|
|
198
|
+
await page.waitForTimeout(interval);
|
|
199
|
+
elapsed += interval;
|
|
200
|
+
console.log(`Waiting for kwik response... ${elapsed / 1000}s`);
|
|
201
|
+
|
|
202
|
+
// Try alternative approach if we're waiting too long
|
|
203
|
+
if (elapsed === 20000 && kwikUrl && !kwikResponse) {
|
|
204
|
+
console.log("Trying direct iframe navigation...");
|
|
205
|
+
try {
|
|
206
|
+
// Navigate to the kwik URL directly in a new page
|
|
207
|
+
const kwikPage = await context.newPage();
|
|
208
|
+
await kwikPage.goto(kwikUrl, {
|
|
209
|
+
waitUntil: "networkidle",
|
|
210
|
+
timeout: 30000,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Wait for Cloudflare challenge to complete
|
|
214
|
+
await kwikPage.waitForTimeout(10000);
|
|
215
|
+
|
|
216
|
+
const content = await kwikPage.content();
|
|
217
|
+
if (!content.includes("Just a moment")) {
|
|
218
|
+
kwikResponse = content;
|
|
219
|
+
console.log("Successfully got content via direct navigation");
|
|
220
|
+
console.log("Cookies", cookies);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await kwikPage.close();
|
|
224
|
+
} catch (err) {
|
|
225
|
+
console.error("Direct navigation failed:", err.message);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
await browser.close();
|
|
231
|
+
|
|
232
|
+
if (!kwikResponse) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`kwik response not captured within time limit. URL detected: ${kwikUrl || "none"}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if we still have Cloudflare challenge in the response
|
|
239
|
+
if (
|
|
240
|
+
kwikResponse.includes("Just a moment") ||
|
|
241
|
+
kwikResponse.includes("Checking your browser")
|
|
242
|
+
) {
|
|
243
|
+
return res.status(202).json({
|
|
244
|
+
message: "Cloudflare challenge detected",
|
|
245
|
+
kwikUrl: kwikUrl,
|
|
246
|
+
note: "Try again in a few moments or implement additional bypass techniques",
|
|
247
|
+
preview: kwikResponse.slice(0, 500),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Extract video URL from kwik.si page
|
|
252
|
+
let videoUrl = null;
|
|
253
|
+
let extractedData = {};
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
// Look for common video URL patterns in kwik.si
|
|
257
|
+
const videoUrlMatches =
|
|
258
|
+
kwikResponse.match(/source.*?src=["']([^"']+)["']/i) ||
|
|
259
|
+
kwikResponse.match(/file:\s*["']([^"']+)["']/i) ||
|
|
260
|
+
kwikResponse.match(/src=["']([^"']+\.mp4[^"']*)["']/i) ||
|
|
261
|
+
kwikResponse.match(/https?:\/\/[^"'\s]+\.mp4[^"'\s]*/i);
|
|
262
|
+
|
|
263
|
+
if (videoUrlMatches) {
|
|
264
|
+
videoUrl = videoUrlMatches[1] || videoUrlMatches[0];
|
|
265
|
+
extractedData.videoUrl = videoUrl;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Look for video title
|
|
269
|
+
const titleMatch = kwikResponse.match(/<title>([^<]+)<\/title>/i);
|
|
270
|
+
if (titleMatch) {
|
|
271
|
+
extractedData.title = titleMatch[1].replace(/\.mp4$/, "");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Look for Plyr or other video player configurations
|
|
275
|
+
const plyrMatch = kwikResponse.match(
|
|
276
|
+
/new\s+Plyr[^{]*{[^}]*source[^}]*}[^}]*}/i,
|
|
277
|
+
);
|
|
278
|
+
if (plyrMatch) {
|
|
279
|
+
extractedData.playerConfig = plyrMatch[0];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Look for JavaScript video source assignments
|
|
283
|
+
const jsVideoMatch =
|
|
284
|
+
kwikResponse.match(/video\.src\s*=\s*["']([^"']+)["']/i) ||
|
|
285
|
+
kwikResponse.match(
|
|
286
|
+
/setAttribute\s*\(\s*["']src["']\s*,\s*["']([^"']+)["']/i,
|
|
287
|
+
);
|
|
288
|
+
if (jsVideoMatch) {
|
|
289
|
+
extractedData.jsVideoSrc = jsVideoMatch[1];
|
|
290
|
+
if (!videoUrl) videoUrl = jsVideoMatch[1];
|
|
291
|
+
}
|
|
292
|
+
} catch (parseError) {
|
|
293
|
+
console.error("Error parsing kwik response:", parseError.message);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return res.json({
|
|
297
|
+
message: "Success",
|
|
298
|
+
kwikUrl: kwikUrl,
|
|
299
|
+
videoUrl: videoUrl,
|
|
300
|
+
extractedData: extractedData,
|
|
301
|
+
preview: kwikResponse.slice(0, 500),
|
|
302
|
+
contentLength: kwikResponse.length,
|
|
303
|
+
captureMethod: "Enhanced route interception with Cloudflare bypass",
|
|
304
|
+
hasVideoUrl: !!videoUrl,
|
|
305
|
+
});
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error("Error in /test:", error.message);
|
|
308
|
+
return res.status(500).json({
|
|
309
|
+
error: error.message,
|
|
310
|
+
details: "Check server logs for more information",
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
module.exports = router;
|
|
316
|
+
|
|
317
|
+
// const express = require('express');
|
|
318
|
+
// const router = express.Router();
|
|
319
|
+
// const { launchBrowser } = require('../utils/browser');
|
|
320
|
+
|
|
321
|
+
// router.get('/test', async (req, res) => {
|
|
322
|
+
// res.status(200).json({ message: "A testing route"});
|
|
323
|
+
// });
|
|
324
|
+
|
|
325
|
+
// module.exports = router;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const { runTick } = require("../utils/webhookNotifier");
|
|
3
|
+
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
router.post("/webhook-check", express.json(), async (req, res) => {
|
|
7
|
+
const expected = process.env.WEBHOOK_SECRET || "";
|
|
8
|
+
const provided = req.headers["x-webhook-secret"] || "";
|
|
9
|
+
|
|
10
|
+
if (expected && provided !== expected) {
|
|
11
|
+
return res.status(401).json({ error: "unauthorized" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const animeSession = String(req.body?.anime_session || "").trim() || null;
|
|
16
|
+
const result = await runTick({ animeSession });
|
|
17
|
+
return res.json({ ok: true, ...result });
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return res.status(500).json({ error: err.message || "check failed" });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
module.exports = router;
|