Improve API error handling and refresh README response examples

This commit is contained in:
Md Tahseen Hussain
2026-04-03 19:52:19 +05:30
parent aa265aa6bb
commit d663dff0d6
3 changed files with 87 additions and 31 deletions

View File

@@ -74,7 +74,7 @@ Output:
---
```http
GET /episodes?session=27a95751-0311-47ed-dbce-7f0680d5074a
GET /episodes?session=77bbe16e-fd87-13d9-a18c-4edb06884a33
```
Output:
@@ -82,18 +82,18 @@ Output:
```json
[
{
"id": 70730,
"id": 15779,
"number": 1,
"title": "Episode 1",
"snapshot": "https://i.animepahe.si/uploads/snapshots/22c034f704a286b5ce17cc33a3dccf9258cc83038e5bafbcc5a196b2584c3454.jpg",
"session": "800a1f7d29d6ebb94d2bfd320b2001b95d00decff4aaecaa6fbef5916379a762"
"snapshot": "https://i.animepahe.com/snapshots/6693c23144b7f3d2bc73242740527ea77baa1d2649180bdb63f35b6a0ef9188b.jpg",
"session": "18ea551da39ccf31e77f9702365193b45636c5ffe7168f209830a56607f2a9d3"
},
{
"id": 70823,
"id": 15780,
"number": 2,
"title": "Episode 2",
"snapshot": "https://i.animepahe.si/uploads/snapshots/baf28a9ea1fecf9bbee49844cf3b782632e487ff49d3ba5c93b56241719fab05.jpg",
"session": "4dd535ded2d2773bb3285881839d018c5787619de262dffb801ab4f78cf20123"
"snapshot": "https://i.animepahe.com/snapshots/44025fa8399d9d68584903c2b5b23c579fd1d73f33570d2d95e783895d97f757.jpg",
"session": "38517b2d536b4c2a2c0c082f1995eb26b04c8af98889afebdb26f344a33c324b"
}
]
```
@@ -101,7 +101,7 @@ Output:
---
```http
GET /sources?anime_session=27a95751-0311-47ed-dbce-7f0680d5074a&episode_session=800a1f7d29d6ebb94d2bfd320b2001b95d00decff4aaecaa6fbef5916379a762
GET /sources?anime_session=77bbe16e-fd87-13d9-a18c-4edb06884a33&episode_session=18ea551da39ccf31e77f9702365193b45636c5ffe7168f209830a56607f2a9d3
```
Output:
@@ -109,21 +109,15 @@ Output:
```json
[
{
"url": "https://kwik.si/e/KcfYGhr86Ww2",
"quality": "1080p",
"fansub": "KawaSubs",
"url": "https://kwik.cx/e/4q3V1NDdRVy9",
"quality": "800p",
"fansub": "df68",
"audio": "jpn"
},
{
"url": "https://kwik.si/e/Sr2gRRoVz6wy",
"quality": "720p",
"fansub": "KawaSubs",
"audio": "jpn"
},
{
"url": "https://kwik.si/e/J7jBHBSJhTEv",
"url": "https://kwik.cx/e/GDFF1EyEUCLD",
"quality": "360p",
"fansub": "KawaSubs",
"fansub": "df68",
"audio": "jpn"
}
]
@@ -132,14 +126,20 @@ Output:
---
```http
GET /m3u8?url=https://kwik.si/e/uEPQKLMzFpaz
GET /m3u8?url=https://kwik.cx/e/4q3V1NDdRVy9
```
Output:
```json
{
"m3u8": "https://vault-12.owocdn.top/stream/12/12/1478df0e98f767de547ac36d33bc92b73b9a5b7318fe3f3e81328fa31fc1eac3/uwu.m3u8"
"m3u8": "https://vault-01.uwucdn.top/stream/01/03/b92a392054c041a3f9c6eecabeb0e127183f44e547828447b10bca8d77523e6f/uwu.m3u8",
"referer": "https://kwik.cx/e/4q3V1NDdRVy9",
"headers": {
"Referer": "https://kwik.cx/e/4q3V1NDdRVy9",
"Origin": "https://kwik.cx"
},
"proxy_url": "/proxy?url=https%3A%2F%2Fvault-01.uwucdn.top%2Fstream%2F01%2F03%2Fb92a392054c041a3f9c6eecabeb0e127183f44e547828447b10bca8d77523e6f%2Fuwu.m3u8&referer=https%3A%2F%2Fkwik.cx%2Fe%2F4q3V1NDdRVy9"
}
```

View File

@@ -15,6 +15,14 @@ app.use(express.static('public'));
// Create AnimePahe instance
const pahe = new AnimePahe();
function mapErrorToStatusCode(message) {
const text = String(message || '').toLowerCase();
if (text.includes('not found')) return 404;
if (text.includes('blocked') || text.includes('anti-bot')) return 503;
if (text.includes('forbidden')) return 403;
return 500;
}
// Routes
app.get('/', (req, res) => {
res.json({
@@ -51,7 +59,7 @@ app.get('/search', async (req, res) => {
res.json(results);
} catch (error) {
console.error('Search error:', error);
res.status(500).json({ error: error.message });
res.status(mapErrorToStatusCode(error.message)).json({ error: error.message });
}
});
@@ -65,7 +73,7 @@ app.get('/episodes', async (req, res) => {
res.json(episodes);
} catch (error) {
console.error('Episodes error:', error);
res.status(500).json({ error: error.message });
res.status(mapErrorToStatusCode(error.message)).json({ error: error.message });
}
});
@@ -81,7 +89,7 @@ app.get('/sources', async (req, res) => {
res.json(sources);
} catch (error) {
console.error('Sources error:', error);
res.status(500).json({ error: error.message });
res.status(mapErrorToStatusCode(error.message)).json({ error: error.message });
}
});
@@ -95,7 +103,7 @@ app.get('/ids', async (req, res) => {
res.json(ids);
} catch (error) {
console.error('IDs error:', error);
res.status(500).json({ error: error.message });
res.status(mapErrorToStatusCode(error.message)).json({ error: error.message });
}
});
@@ -119,7 +127,7 @@ app.get('/m3u8', async (req, res) => {
});
} catch (error) {
console.error('M3U8 resolution error:', error);
res.status(500).json({ error: error.message });
res.status(mapErrorToStatusCode(error.message)).json({ error: error.message });
}
});

View File

@@ -104,6 +104,54 @@ class AnimePahe {
return animeSession;
}
/**
* Convert noisy upstream errors into concise API-safe messages
* @param {string} context - Operation context (search, episodes, etc)
* @param {Error|any} error - Raw error
* @returns {string} Public error message
* @private
*/
_formatUpstreamError(context, error) {
const rawMessage = String(error?.message || error || 'Unknown error');
const statusMatch = rawMessage.match(/^(\d{3})\s*-\s*/);
const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : null;
if (statusCode === 404 || /Oops\.\.\.\s*404|404\s+Not\s+Found/i.test(rawMessage)) {
if (context === 'episodes') {
return 'Anime session not found. Use /search first to get a valid session id.';
}
if (context === 'sources') {
return 'Anime or episode session not found. Use /episodes first to get a valid episode_session.';
}
if (context === 'ids') {
return 'Anime session not found. Use /search first to get a valid session id.';
}
}
if (statusCode === 403 && /ddos-guard|checking your browser|cloudflare/i.test(rawMessage)) {
return 'Upstream blocked the request (anti-bot challenge). Please retry shortly.';
}
let cleaned = rawMessage
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.replace(/^\d{3}\s*-\s*"?/, '')
.replace(/"$/, '')
.trim();
if (!cleaned) {
cleaned = 'Unexpected upstream error';
}
if (cleaned.length > 220) {
cleaned = `${cleaned.slice(0, 220)}...`;
}
return cleaned;
}
/**
* Search for anime by query
* @param {string} query - Search query
@@ -138,7 +186,7 @@ class AnimePahe {
return results;
} catch (error) {
throw new Error(`Search failed: ${error.message}`);
throw new Error(`Search failed: ${this._formatUpstreamError('search', error)}`);
}
}
@@ -208,7 +256,7 @@ class AnimePahe {
return formattedEpisodes;
} catch (error) {
throw new Error(`Failed to get episodes: ${error.message}`);
throw new Error(`Failed to get episodes: ${this._formatUpstreamError('episodes', error)}`);
}
}
@@ -288,7 +336,7 @@ class AnimePahe {
return uniqueSources;
} catch (error) {
throw new Error(`Failed to get sources: ${error.message}`);
throw new Error(`Failed to get sources: ${this._formatUpstreamError('sources', error)}`);
}
}
@@ -394,7 +442,7 @@ ${transformedScript}
throw new Error(`Could not resolve .m3u8. Node output (first 2000 chars):\n${nodeOutput.substring(0, 2000)}`);
} catch (error) {
throw new Error(`Failed to resolve Kwik URL: ${error.message}`);
throw new Error(`Failed to resolve Kwik URL: ${this._formatUpstreamError('m3u8', error)}`);
}
}
@@ -419,7 +467,7 @@ ${transformedScript}
myanimelist: malId ? parseInt(malId, 10) : null
};
} catch (error) {
throw new Error(`Failed to get IDs: ${error.message}`);
throw new Error(`Failed to get IDs: ${this._formatUpstreamError('ids', error)}`);
}
}