anipub 1.0.4

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/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "anipub",
3
+ "version": "1.0.4",
4
+ "description": "A full-featured JavaScript/TypeScript client for the AniPub Anime API — search, browse by genre, get streaming links, MAL data, characters & voice actors.",
5
+ "main": "src/index.cjs",
6
+ "module": "src/index.js",
7
+ "types": "src/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./src/index.js",
12
+ "require": "./src/index.cjs",
13
+ "types": "./src/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "src/",
18
+ "LICENSE",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "node scripts/build-cjs.js",
23
+ "test": "node test/index.test.js",
24
+ "prepublishOnly": "node scripts/build-cjs.js",
25
+ "example": "node examples/basic.js",
26
+ "example:advanced": "node examples/advanced.js"
27
+ },
28
+ "keywords": [
29
+ "anime",
30
+ "anipub",
31
+ "manga",
32
+ "api",
33
+ "myanimelist",
34
+ "mal",
35
+ "jikan",
36
+ "streaming",
37
+ "search",
38
+ "genre",
39
+ "characters",
40
+ "voice-actors",
41
+ "anime-api",
42
+ "japanese-animation"
43
+ ],
44
+ "author": "abdullahaladnan95@gmail.com",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/AnimePub/AniPub-API"
49
+ },
50
+ "homepage": "https://github.com/AnimePub/AniPub-API/#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/AnimePub/AniPub-API/issues"
53
+ },
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ },
57
+ "sideEffects": false
58
+ }
package/src/index.cjs ADDED
@@ -0,0 +1,385 @@
1
+ /**
2
+ * AniPub JS — A full-featured client for the AniPub Anime API
3
+ * Covers all 10 endpoints: info, getAll, find, details (v1), full details,
4
+ * findbyGenre, check, findbyrating, search, searchall
5
+ *
6
+ * @module anipub
7
+ * @see https://api.anipub.xyz
8
+ */
9
+
10
+ const BASE_URL = 'https://www.anipub.xyz';
11
+
12
+
13
+
14
+ /**
15
+ * Fix relative image paths by prepending the base domain.
16
+ * @param {string|undefined} path
17
+ * @returns {string}
18
+ */
19
+ function fixImage(path) {
20
+ if (!path) return '';
21
+ return path.startsWith('https://') ? path : `https://anipub.xyz/${path}`;
22
+ }
23
+
24
+ /**
25
+ * Fix all known image fields in an object.
26
+ * @param {object} obj
27
+ * @returns {object}
28
+ */
29
+ function fixImages(obj) {
30
+ if (!obj || typeof obj !== 'object') return obj;
31
+ const clone = { ...obj };
32
+ for (const key of ['ImagePath', 'Cover', 'Image', 'image']) {
33
+ if (clone[key]) clone[key] = fixImage(clone[key]);
34
+ }
35
+ return clone;
36
+ }
37
+
38
+ /**
39
+ * Internal fetch wrapper with error handling.
40
+ * @param {string} url
41
+ * @param {RequestInit} [options]
42
+ * @returns {Promise<any>}
43
+ */
44
+ async function apiFetch(url, options = {}) {
45
+ const res = await fetch(url, {
46
+ headers: { 'Content-Type': 'application/json' },
47
+ ...options,
48
+ });
49
+
50
+ if (!res.ok) {
51
+ const text = await res.text().catch(() => '');
52
+ throw new AniPubError(res.status, `AniPub API error [${res.status}]: ${res.statusText}. ${text}`);
53
+ }
54
+
55
+ return res.json();
56
+ }
57
+
58
+
59
+
60
+ class AniPubError extends Error {
61
+ /**
62
+ * @param {number} statusCode
63
+ * @param {string} message
64
+ */
65
+ constructor(statusCode, message) {
66
+ super(message);
67
+ this.name = 'AniPubError';
68
+ this.statusCode = statusCode;
69
+ }
70
+ }
71
+
72
+ // ─── Endpoints ───────────────────────────────────────────────────────────────
73
+
74
+ /**
75
+ * GET /api/info/:id
76
+ * Full metadata for one anime by integer ID or kebab-case slug.
77
+ *
78
+ * @param {number|string} idOrSlug - e.g. 61 or "black-clover" or "one-piece"
79
+ * @returns {Promise<AnimeInfo>}
80
+ *
81
+ * @example
82
+ * const anime = await getInfo(61);
83
+ * const anime = await getInfo('black-clover');
84
+ */
85
+ export async function getInfo(idOrSlug) {
86
+ const data = await apiFetch(`${BASE_URL}/api/info/${encodeURIComponent(idOrSlug)}`);
87
+ return fixImages(data);
88
+ }
89
+
90
+ /**
91
+ * GET /api/getAll
92
+ * Returns the total count of anime in the database.
93
+ * Use to determine the valid integer ID range for getInfo().
94
+ *
95
+ * @returns {Promise<number>}
96
+ *
97
+ * @example
98
+ * const total = await getTotal();
99
+ * console.log(`IDs 1 to ${total}`);
100
+ */
101
+ export async function getTotal() {
102
+ return apiFetch(`${BASE_URL}/api/getAll`);
103
+ }
104
+
105
+ /**
106
+ * GET /api/find/:name
107
+ * Check if an anime exists by exact name.
108
+ * Returns { exist: true, id, ep } or { exist: false }.
109
+ *
110
+ * @param {string} name - Anime title (will be URL-encoded automatically)
111
+ * @returns {Promise<FindResult>}
112
+ *
113
+ * @example
114
+ * const result = await findByName('One Piece');
115
+ * if (result.exist) console.log(`ID: ${result.id}, Episodes: ${result.ep}`);
116
+ */
117
+ export async function findByName(name) {
118
+ return apiFetch(`${BASE_URL}/api/find/${encodeURIComponent(name)}`);
119
+ }
120
+
121
+ /**
122
+ * GET /v1/api/details/:id
123
+ * Returns streaming iframe links per episode.
124
+ * - local.link = Episode 1 (strip "src=" prefix to get the raw URL)
125
+ * - local.ep[] = Episodes 2+ (ep[0] = EP2, ep[1] = EP3, ...)
126
+ *
127
+ * @param {number|string} id - Numeric anime ID
128
+ * @param {object} [options]
129
+ * @param {boolean} [options.stripSrc=true] - Auto-strip "src=" prefix from links
130
+ * @returns {Promise<StreamingDetails>}
131
+ *
132
+ * @example
133
+ * const { episodes } = await getStreamingLinks(119);
134
+ * // episodes[0] = { ep: 1, src: 'https://...' }
135
+ */
136
+ export async function getStreamingLinks(id, { stripSrc = true } = {}) {
137
+ const data = await apiFetch(`${BASE_URL}/v1/api/details/${id}`);
138
+ const local = data.local;
139
+
140
+ if (!local) return data;
141
+
142
+ const strip = (link) => (stripSrc && link ? link.replace(/^src=/, '') : link);
143
+
144
+ const episodes = [
145
+ { ep: 1, src: strip(local.link) },
146
+ ...(local.ep || []).map((e, i) => ({ ep: i + 2, src: strip(e.link) })),
147
+ ];
148
+
149
+ return { ...data, episodes };
150
+ }
151
+
152
+ /**
153
+ * GET /anime/api/details/:id
154
+ * Full details: local metadata + MAL/Jikan data + characters & voice actors.
155
+ * This is the most complete single-anime endpoint.
156
+ *
157
+ * @param {number|string} id - Numeric anime ID
158
+ * @returns {Promise<FullDetails>}
159
+ *
160
+ * @example
161
+ * const { local, jikan, characters } = await getFullDetails(119);
162
+ * console.log(local.Name, local.MALScore);
163
+ * characters.forEach(c => console.log(c.character.name, c.role));
164
+ */
165
+ export async function getFullDetails(id) {
166
+ const data = await apiFetch(`${BASE_URL}/anime/api/details/${id}`);
167
+ if (data.local) data.local = fixImages(data.local);
168
+ return data;
169
+ }
170
+
171
+ /**
172
+ * GET /api/findbyGenre/:genre
173
+ * Paginated anime list filtered by genre.
174
+ *
175
+ * @param {string} genre - e.g. "action", "harem", "romance", "ecchi"
176
+ * @param {number} [page=1] - Page number
177
+ * @returns {Promise<GenreResult>}
178
+ *
179
+ * @example
180
+ * const { currentPage, wholePage } = await findByGenre('action', 1);
181
+ * wholePage.forEach(a => console.log(a.Name));
182
+ */
183
+ export async function findByGenre(genre, page = 1) {
184
+ const data = await apiFetch(`${BASE_URL}/api/findbyGenre/${encodeURIComponent(genre)}?Page=${page}`);
185
+ if (Array.isArray(data.wholePage)) {
186
+ data.wholePage = data.wholePage.map(fixImages);
187
+ }
188
+ return data;
189
+ }
190
+
191
+ /**
192
+ * POST /api/check
193
+ * Check if an anime exists by name and genre.
194
+ *
195
+ * @param {string} name - Anime title
196
+ * @param {string|string[]} genre - Genre string or array of genres
197
+ * @returns {Promise<any>}
198
+ *
199
+ * @example
200
+ * await checkAnime('Black Clover', 'Action');
201
+ * await checkAnime('Jujutsu Kaisen', ['Action', 'Drama']);
202
+ */
203
+ export async function checkAnime(name, genre) {
204
+ return apiFetch(`${BASE_URL}/api/check`, {
205
+ method: 'POST',
206
+ body: JSON.stringify({ Name: name, Genre: genre }),
207
+ });
208
+ }
209
+
210
+ /**
211
+ * GET /api/findbyrating
212
+ * Top-rated anime sorted by MAL score descending, paginated.
213
+ *
214
+ * @param {number} [page=1] - Page number
215
+ * @returns {Promise<RatingResult>}
216
+ *
217
+ * @example
218
+ * const { AniData, currentPage } = await getTopRated(1);
219
+ * AniData.forEach(a => console.log(a.Name, a.MALScore));
220
+ */
221
+ export async function getTopRated(page = 1) {
222
+ const data = await apiFetch(`${BASE_URL}/api/findbyrating?page=${page}`);
223
+ if (Array.isArray(data.AniData)) {
224
+ data.AniData = data.AniData.map(fixImages);
225
+ }
226
+ return data;
227
+ }
228
+
229
+ /**
230
+ * GET /api/search/:name
231
+ * Quick search — flat array of matches, ideal for autocomplete.
232
+ * No pagination. Faster than searchAll.
233
+ *
234
+ * @param {string} query - Search query
235
+ * @returns {Promise<SearchResult[]>}
236
+ *
237
+ * @example
238
+ * const results = await search('One Piece');
239
+ * results.forEach(r => console.log(r.Name, r.Id));
240
+ */
241
+ export async function search(query) {
242
+ const data = await apiFetch(`${BASE_URL}/api/search/${encodeURIComponent(query)}`);
243
+ return Array.isArray(data) ? data.map(fixImages) : data;
244
+ }
245
+
246
+ /**
247
+ * GET /api/searchall/:name
248
+ * Full paginated search across all anime.
249
+ * Returns more results than search(), with pagination.
250
+ *
251
+ * @param {string} query - Search query
252
+ * @param {number} [page=1] - Page number
253
+ * @returns {Promise<SearchAllResult>}
254
+ *
255
+ * @example
256
+ * const { AniData, currentPage } = await searchAll('Naruto', 1);
257
+ * AniData.forEach(a => console.log(a.Name, a.MALScore));
258
+ */
259
+ export async function searchAll(query, page = 1) {
260
+ const data = await apiFetch(
261
+ `${BASE_URL}/api/searchall/${encodeURIComponent(query)}?page=${page}`
262
+ );
263
+ if (Array.isArray(data.AniData)) {
264
+ data.AniData = data.AniData.map(fixImages);
265
+ }
266
+ return data;
267
+ }
268
+
269
+ // ─── Convenience class ───────────────────────────────────────────────────────
270
+
271
+ /**
272
+ * AniPub client class — wraps all endpoints as instance methods.
273
+ * Useful when you want a single import and potential future config options.
274
+ *
275
+ * @example
276
+ * import AniPub from 'anipub';
277
+ * const client = new AniPub();
278
+ * const anime = await client.getInfo('one-piece');
279
+ */
280
+ class AniPub {
281
+ constructor() {}
282
+
283
+ getInfo(idOrSlug) { return getInfo(idOrSlug); }
284
+ getTotal() { return getTotal(); }
285
+ findByName(name) { return findByName(name); }
286
+ getStreamingLinks(id, opts) { return getStreamingLinks(id, opts); }
287
+ getFullDetails(id) { return getFullDetails(id); }
288
+ findByGenre(genre, page) { return findByGenre(genre, page); }
289
+ checkAnime(name, genre) { return checkAnime(name, genre); }
290
+ getTopRated(page) { return getTopRated(page); }
291
+ search(query) { return search(query); }
292
+ searchAll(query, page) { return searchAll(query, page); }
293
+ }
294
+
295
+
296
+
297
+
298
+
299
+ /**
300
+ * @typedef {object} AnimeInfo
301
+ * @property {number} _id
302
+ * @property {string} Name
303
+ * @property {string} ImagePath
304
+ * @property {string} Cover
305
+ * @property {string} Synonyms
306
+ * @property {string} Aired
307
+ * @property {string} Premiered
308
+ * @property {number} RatingsNum
309
+ * @property {string[]} Genres
310
+ * @property {string} Studios
311
+ * @property {string} DescripTion
312
+ * @property {string} Duration
313
+ * @property {string} MALScore
314
+ * @property {string} Status
315
+ * @property {number} epCount
316
+ */
317
+
318
+ /**
319
+ * @typedef {object} FindResult
320
+ * @property {boolean} exist
321
+ * @property {number} [id]
322
+ * @property {number} [ep]
323
+ */
324
+
325
+ /**
326
+ * @typedef {object} EpisodeLink
327
+ * @property {number} ep
328
+ * @property {string} src
329
+ */
330
+
331
+ /**
332
+ * @typedef {object} StreamingDetails
333
+ * @property {object} local
334
+ * @property {EpisodeLink[]} episodes
335
+ */
336
+
337
+ /**
338
+ * @typedef {object} FullDetails
339
+ * @property {AnimeInfo} local
340
+ * @property {object} jikan
341
+ * @property {object[]} characters
342
+ */
343
+
344
+ /**
345
+ * @typedef {object} GenreResult
346
+ * @property {number} currentPage
347
+ * @property {AnimeInfo[]} wholePage
348
+ */
349
+
350
+ /**
351
+ * @typedef {object} RatingResult
352
+ * @property {number} currentPage
353
+ * @property {AnimeInfo[]} AniData
354
+ */
355
+
356
+ /**
357
+ * @typedef {object} SearchResult
358
+ * @property {string} Name
359
+ * @property {number} Id
360
+ * @property {string} Image
361
+ * @property {string} finder
362
+ */
363
+
364
+ /**
365
+ * @typedef {object} SearchAllResult
366
+ * @property {number} currentPage
367
+ * @property {AnimeInfo[]} AniData
368
+ */
369
+
370
+ // CommonJS exports
371
+ module.exports = {
372
+ AniPub,
373
+ AniPubError,
374
+ getInfo,
375
+ getTotal,
376
+ findByName,
377
+ getStreamingLinks,
378
+ getFullDetails,
379
+ findByGenre,
380
+ checkAnime,
381
+ getTopRated,
382
+ search,
383
+ searchAll,
384
+ };
385
+ module.exports.default = AniPub;
package/src/index.d.ts ADDED
@@ -0,0 +1,154 @@
1
+ /**
2
+ * AniPub JS — TypeScript declarations
3
+ * Full-featured client for the AniPub Anime API
4
+ * @see https://api.anipub.xyz
5
+ */
6
+
7
+ export interface AnimeInfo {
8
+ _id: number;
9
+ Name: string;
10
+ ImagePath: string;
11
+ Cover: string;
12
+ Synonyms: string;
13
+ Aired: string;
14
+ Premiered: string;
15
+ RatingsNum: number;
16
+ Genres: string[];
17
+ Studios: string;
18
+ DescripTion: string;
19
+ Duration: string;
20
+ MALScore: string;
21
+ Status: string;
22
+ epCount: number;
23
+ finder?: string;
24
+ }
25
+
26
+ export interface FindResult {
27
+ exist: boolean;
28
+ id?: number;
29
+ ep?: number;
30
+ }
31
+
32
+ export interface EpisodeLink {
33
+ ep: number;
34
+ src: string;
35
+ }
36
+
37
+ export interface StreamingDetails {
38
+ local: {
39
+ name: string;
40
+ link: string;
41
+ ep: Array<{ link: string }>;
42
+ };
43
+ episodes: EpisodeLink[];
44
+ }
45
+
46
+ export interface Character {
47
+ character: {
48
+ mal_id: number;
49
+ url: string;
50
+ images: { jpg: { image_url: string } };
51
+ name: string;
52
+ };
53
+ role: string;
54
+ voice_actors: Array<{
55
+ person: {
56
+ mal_id: number;
57
+ url: string;
58
+ images: { jpg: { image_url: string } };
59
+ name: string;
60
+ };
61
+ language: string;
62
+ }>;
63
+ }
64
+
65
+ export interface FullDetails {
66
+ local: AnimeInfo;
67
+ jikan: {
68
+ synopsis?: string;
69
+ background?: string;
70
+ [key: string]: unknown;
71
+ } | null;
72
+ characters: Character[];
73
+ }
74
+
75
+ export interface GenreResult {
76
+ currentPage: number;
77
+ wholePage: AnimeInfo[];
78
+ }
79
+
80
+ export interface RatingResult {
81
+ currentPage: number;
82
+ AniData: AnimeInfo[];
83
+ }
84
+
85
+ export interface SearchResult {
86
+ Name: string;
87
+ Id: number;
88
+ Image: string;
89
+ finder: string;
90
+ }
91
+
92
+ export interface SearchAllResult {
93
+ currentPage: number;
94
+ AniData: AnimeInfo[];
95
+ }
96
+
97
+ export interface StreamingOptions {
98
+ stripSrc?: boolean;
99
+ }
100
+
101
+ // ─── Named exports ─────────────────────────────────────────────────────────
102
+
103
+ /** GET /api/info/:id — Full metadata by integer ID or slug */
104
+ export function getInfo(idOrSlug: number | string): Promise<AnimeInfo>;
105
+
106
+ /** GET /api/getAll — Total anime count in the database */
107
+ export function getTotal(): Promise<number>;
108
+
109
+ /** GET /api/find/:name — Check existence + episode count by exact name */
110
+ export function findByName(name: string): Promise<FindResult>;
111
+
112
+ /** GET /v1/api/details/:id — Streaming iframe links, normalized by episode */
113
+ export function getStreamingLinks(id: number | string, options?: StreamingOptions): Promise<StreamingDetails>;
114
+
115
+ /** GET /anime/api/details/:id — Full details: local + MAL + characters */
116
+ export function getFullDetails(id: number | string): Promise<FullDetails>;
117
+
118
+ /** GET /api/findbyGenre/:genre — Paginated list by genre */
119
+ export function findByGenre(genre: string, page?: number): Promise<GenreResult>;
120
+
121
+ /** POST /api/check — Verify anime exists by name and genre */
122
+ export function checkAnime(name: string, genre: string | string[]): Promise<unknown>;
123
+
124
+ /** GET /api/findbyrating — Top-rated anime, paginated */
125
+ export function getTopRated(page?: number): Promise<RatingResult>;
126
+
127
+ /** GET /api/search/:name — Quick flat search, great for autocomplete */
128
+ export function search(query: string): Promise<SearchResult[]>;
129
+
130
+ /** GET /api/searchall/:name — Full paginated search */
131
+ export function searchAll(query: string, page?: number): Promise<SearchAllResult>;
132
+
133
+ // ─── Class ─────────────────────────────────────────────────────────────────
134
+
135
+ export class AniPubError extends Error {
136
+ statusCode: number;
137
+ constructor(statusCode: number, message: string);
138
+ }
139
+
140
+ export class AniPub {
141
+ constructor();
142
+ getInfo(idOrSlug: number | string): Promise<AnimeInfo>;
143
+ getTotal(): Promise<number>;
144
+ findByName(name: string): Promise<FindResult>;
145
+ getStreamingLinks(id: number | string, options?: StreamingOptions): Promise<StreamingDetails>;
146
+ getFullDetails(id: number | string): Promise<FullDetails>;
147
+ findByGenre(genre: string, page?: number): Promise<GenreResult>;
148
+ checkAnime(name: string, genre: string | string[]): Promise<unknown>;
149
+ getTopRated(page?: number): Promise<RatingResult>;
150
+ search(query: string): Promise<SearchResult[]>;
151
+ searchAll(query: string, page?: number): Promise<SearchAllResult>;
152
+ }
153
+
154
+ export default AniPub;