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.
Files changed (90) 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 +19 -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 +66 -13
  59. package/src/notify.js +82 -7
  60. package/src/scraper.js +2 -2
  61. package/src/server.js +108 -0
  62. package/utils.js +21 -21
  63. package/web/index.html +13 -0
  64. package/web/package-lock.json +1420 -0
  65. package/web/package.json +24 -0
  66. package/web/src/App.vue +200 -0
  67. package/web/src/api/client.js +75 -0
  68. package/web/src/assets/styles/base.css +961 -0
  69. package/web/src/components/UpdateModal.vue +157 -0
  70. package/web/src/components/sidebar/SidebarResizable.vue +170 -0
  71. package/web/src/components/sidebar/sidebarConfig.js +24 -0
  72. package/web/src/main.js +104 -0
  73. package/web/src/router/index.js +44 -0
  74. package/web/src/stores/config.js +27 -0
  75. package/web/src/stores/download.js +107 -0
  76. package/web/src/stores/update.js +73 -0
  77. package/web/src/views/About.vue +42 -0
  78. package/web/src/views/AniListSync.vue +122 -0
  79. package/web/src/views/AnimeDetail.vue +202 -0
  80. package/web/src/views/AnimeList.vue +110 -0
  81. package/web/src/views/CFResult.vue +312 -0
  82. package/web/src/views/DaemonControl.vue +83 -0
  83. package/web/src/views/Dashboard.vue +187 -0
  84. package/web/src/views/DownloadCenter.vue +153 -0
  85. package/web/src/views/MatchFinder.vue +131 -0
  86. package/web/src/views/OAuthCallback.vue +44 -0
  87. package/web/src/views/Onboarding.vue +93 -0
  88. package/web/src/views/RunHistory.vue +122 -0
  89. package/web/src/views/Settings.vue +410 -0
  90. package/web/vite.config.js +23 -0
@@ -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
+ [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FElijahCodes12345%2Fanimepahe-api)
71
+ [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/ElijahCodes12345/animepahe-api)
72
+ [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/animepahe-api?referralCode=EgKNlg)
73
+ [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](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,8 @@
1
+ const express = require('express');
2
+ const QueueController = require('../controllers/queueController');
3
+
4
+ const router = express.Router();
5
+
6
+ router.get('/queue', QueueController.getQueue);
7
+
8
+ 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;