OhMyDitzzy commited on
Commit
f535bab
·
1 Parent(s): 8dc4abf

Add: komiku

Browse files
src/components/PluginCard.tsx CHANGED
@@ -255,7 +255,7 @@ export function PluginCard({ plugin }: PluginCardProps) {
255
 
256
  <div className="w-full">
257
  <h3 className="text-xl font-bold text-white mb-2">{plugin.name}</h3>
258
- <p className="text-gray-400 text-sm leading-relaxed">{plugin.description}</p>
259
 
260
  {/* Tags */}
261
  {plugin.tags && plugin.tags.length > 0 && (
 
255
 
256
  <div className="w-full">
257
  <h3 className="text-xl font-bold text-white mb-2">{plugin.name}</h3>
258
+ <p className="text-gray-400 text-sm leading-relaxed">{plugin.description || "No description provided"}</p>
259
 
260
  {/* Tags */}
261
  {plugin.tags && plugin.tags.length > 0 && (
src/server/plugin-loader.ts CHANGED
@@ -174,10 +174,6 @@ export class PluginLoader {
174
  return { valid: false, reason: 'name is missing or empty' };
175
  }
176
 
177
- if (!handler.description || typeof handler.description !== 'string' || handler.description.trim() === '') {
178
- return { valid: false, reason: 'description is missing or empty' };
179
- }
180
-
181
  return { valid: true };
182
  }
183
 
 
174
  return { valid: false, reason: 'name is missing or empty' };
175
  }
176
 
 
 
 
 
177
  return { valid: true };
178
  }
179
 
src/server/plugins/anime/komiku.js ADDED
@@ -0,0 +1,498 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from "axios";
2
+ import * as cheerio from "cheerio";
3
+
4
+ export function getSlugFromUrl(url) {
5
+ try {
6
+ const parsed = new URL(url);
7
+ const parts = parsed.pathname.split("/").filter(Boolean);
8
+ return parts[parts.length - 1] || '';
9
+ } catch (error) {
10
+ try {
11
+ const parts = url.split("/").filter(Boolean);
12
+ return parts[parts.length - 1] || '';
13
+ } catch {
14
+ return '';
15
+ }
16
+ }
17
+ }
18
+
19
+ function parseUpdateToMs(updateText) {
20
+ const now = Date.now();
21
+
22
+ const match = updateText.match(/(\d+)\s*(detik|menit|jam|hari|minggu|bulan|tahun)/i);
23
+
24
+ if (!match) return 0;
25
+
26
+ const value = parseInt(match[1]);
27
+ const unit = match[2].toLowerCase();
28
+
29
+ const msPerUnit = {
30
+ 'detik': 1000,
31
+ 'menit': 60 * 1000,
32
+ 'jam': 60 * 60 * 1000,
33
+ 'hari': 24 * 60 * 60 * 1000,
34
+ 'minggu': 7 * 24 * 60 * 60 * 1000,
35
+ 'bulan': 30 * 24 * 60 * 60 * 1000,
36
+ 'tahun': 365 * 24 * 60 * 60 * 1000
37
+ };
38
+
39
+ const ms = msPerUnit[unit] || 0;
40
+ return now - (value * ms);
41
+ }
42
+
43
+ function resizeThumbnail(url, width = 540, height = 350) {
44
+ if (!url) return url;
45
+
46
+ if (url.includes('?resize=')) {
47
+ return url.replace(/\?resize=\d+,\d+/, `?resize=${width},${height}`);
48
+ }
49
+
50
+ return url;
51
+ }
52
+
53
+ export class Komiku {
54
+ constructor() {
55
+ this.BASE_URL = "https://komiku.org";
56
+ this.API_URL = "https://api.komiku.org";
57
+ this.CREATED_BY = "Ditzzy";
58
+ this.NOTE = "Thank you for using this scrape, I hope you appreciate me for making this scrape by not deleting wm";
59
+ }
60
+
61
+ wrapResponse(data) {
62
+ return {
63
+ created_by: this.CREATED_BY,
64
+ note: this.NOTE,
65
+ results: data
66
+ };
67
+ }
68
+
69
+ async search(query, postType = "manga") {
70
+ try {
71
+ const { data } = await axios.get(`${this.API_URL}/?post_type=${postType}&s=${encodeURIComponent(query)}`);
72
+ const $ = cheerio.load(data);
73
+
74
+ const results = [];
75
+ const $containers = $('div.bge');
76
+
77
+ for (let index = 0; index < $containers.length; index++) {
78
+ const el = $containers[index];
79
+
80
+ try {
81
+ let thumbnailUrl = '';
82
+ const imgElement = $(el).find('img').first();
83
+ if (imgElement.length > 0) {
84
+ thumbnailUrl = imgElement.attr('src') || imgElement.attr('data-src') || '';
85
+ thumbnailUrl = resizeThumbnail(thumbnailUrl);
86
+ }
87
+
88
+ let type = '';
89
+ let genre = '';
90
+ const typeGenreElement = $(el).find('div.tpe1_inf, .tpe1_inf');
91
+ if (typeGenreElement.length > 0) {
92
+ const text = typeGenreElement.text().trim();
93
+ const parts = text.split(/\s+/);
94
+ if (parts.length > 0) {
95
+ type = parts[0].replace(/<\/?b>/g, '').trim();
96
+ genre = parts.slice(1).join(' ').trim();
97
+ }
98
+ }
99
+
100
+ let title = '';
101
+ let mangaUrl = '';
102
+
103
+ const h3Element = $(el).find('h3').first();
104
+ if (h3Element.length > 0) {
105
+ title = h3Element.text().trim();
106
+
107
+ const parentLink = h3Element.parent('a');
108
+ if (parentLink.length > 0) {
109
+ mangaUrl = parentLink.attr('href') || '';
110
+ } else {
111
+ const nearbyLink = h3Element.closest('div').find('a[href*="/manga/"]').first();
112
+ if (nearbyLink.length > 0) {
113
+ mangaUrl = nearbyLink.attr('href') || '';
114
+ }
115
+ }
116
+ }
117
+
118
+ if (!title || !mangaUrl) {
119
+ $(el).find('a[href*="/manga/"]').each((_, linkEl) => {
120
+ const h3 = $(linkEl).find('h3');
121
+ if (h3.length > 0) {
122
+ title = h3.text().trim();
123
+ mangaUrl = $(linkEl).attr('href') || '';
124
+ return false;
125
+ }
126
+ });
127
+ }
128
+
129
+ if (mangaUrl && !mangaUrl.startsWith('http')) {
130
+ mangaUrl = this.BASE_URL + mangaUrl;
131
+ }
132
+
133
+ let lastUpdateMs = 0;
134
+ $(el).find('p').each((_, pEl) => {
135
+ const text = $(pEl).text().trim();
136
+ if (text.toLowerCase().includes('update')) {
137
+ lastUpdateMs = parseUpdateToMs(text);
138
+ return false;
139
+ }
140
+ });
141
+
142
+ let firstChapter = null;
143
+ let latestChapter = null;
144
+
145
+ $(el).find('div.new1, .new1').each((_, newEl) => {
146
+ const link = $(newEl).find('a');
147
+ if (link.length > 0) {
148
+ const spans = link.find('span');
149
+
150
+ if (spans.length >= 2) {
151
+ const label = spans.first().text().trim().toLowerCase();
152
+ const chapterTitle = spans.last().text().trim();
153
+ const chapterUrl = link.attr('href') || '';
154
+
155
+ const fullUrl = chapterUrl && !chapterUrl.startsWith('http')
156
+ ? this.BASE_URL + chapterUrl
157
+ : chapterUrl;
158
+
159
+ const chapterSlug = getSlugFromUrl(chapterUrl);
160
+
161
+ if (label.includes('awal') || label.includes('first')) {
162
+ firstChapter = {
163
+ title: chapterTitle,
164
+ url: fullUrl,
165
+ slug: chapterSlug
166
+ };
167
+ } else if (label.includes('terbaru') || label.includes('latest')) {
168
+ latestChapter = {
169
+ title: chapterTitle,
170
+ url: fullUrl,
171
+ slug: chapterSlug
172
+ };
173
+ }
174
+ }
175
+ }
176
+ });
177
+
178
+ if (title && mangaUrl) {
179
+ const slug = getSlugFromUrl(mangaUrl);
180
+ const detail = await this.getDetail(slug);
181
+
182
+ results.push({
183
+ title,
184
+ mangaUrl,
185
+ thumbnailUrl,
186
+ type,
187
+ genre,
188
+ lastUpdateMs,
189
+ firstChapter,
190
+ latestChapter,
191
+ detail
192
+ });
193
+ }
194
+ } catch (error) {
195
+ console.error('Error parsing search item:', error);
196
+ }
197
+ }
198
+
199
+ return this.wrapResponse(results);
200
+ } catch (error) {
201
+ if (axios.isAxiosError(error)) {
202
+ console.error('Axios error on search:', {
203
+ message: error.message,
204
+ status: error.response?.status,
205
+ statusText: error.response?.statusText
206
+ });
207
+ } else {
208
+ console.error('Error on search:', error);
209
+ }
210
+ return [];
211
+ }
212
+ }
213
+
214
+ async getDetail(slug) {
215
+ try {
216
+ const { data } = await axios.get(`${this.BASE_URL}/manga/${slug}`);
217
+ const $ = cheerio.load(data);
218
+ let results = null;
219
+
220
+ $('.series').each((_, el) => {
221
+ const keyMap = {
222
+ 'Judul Komik': 'title',
223
+ 'Judul Indonesia': 'indonesia_title',
224
+ 'Jenis Komik': 'type',
225
+ 'Pengarang': 'author',
226
+ 'Status': 'status'
227
+ };
228
+
229
+ const info = {};
230
+
231
+ $(el).find('table.inftable tr').each((_, el) => {
232
+ const key = $(el).find('td:first-child').text().trim();
233
+ const value = $(el).find('td:last-child').text().trim();
234
+
235
+ if (keyMap[key]) {
236
+ info[keyMap[key]] = value;
237
+ }
238
+ });
239
+
240
+ const genre = [];
241
+ $('ul.genre li.genre span[itemprop="genre"]').each((_, el) => {
242
+ genre.push($(el).text().trim());
243
+ });
244
+
245
+ const synopsis = $('p.desc').text().trim();
246
+ let thumbnailUrl = $('div.ims img[itemprop="image"]').attr("src")?.trim() || '';
247
+ thumbnailUrl = resizeThumbnail(thumbnailUrl);
248
+
249
+ const chapters = [];
250
+ $('table#Daftar_Chapter tr:not(:first-child)').each((_, el) => {
251
+ const chapter = $(el).find('td.judulseries a span').text().trim();
252
+ const slug_chapter = $(el).find('td.judulseries a').attr('href')?.replace(/\//g, '') || '';
253
+ const views = $(el).find('td.pembaca i').text().trim();
254
+ const date = $(el).find('td.tanggalseries').text().trim();
255
+
256
+ chapters.push({ chapter, slug_chapter, views, date });
257
+ });
258
+
259
+ results = {
260
+ ...info,
261
+ thumbnailUrl,
262
+ synopsis,
263
+ genre,
264
+ chapters
265
+ };
266
+ });
267
+
268
+ return this.wrapResponse(results);
269
+ } catch (error) {
270
+ if (axios.isAxiosError(error)) {
271
+ console.error('Axios error fetching detail:', {
272
+ message: error.message,
273
+ status: error.response?.status,
274
+ statusText: error.response?.statusText
275
+ });
276
+ } else {
277
+ console.error('Error fetching detail:', error);
278
+ }
279
+ return this.wrapResponse(null);
280
+ }
281
+ }
282
+
283
+ async readChapter(chapterSlug) {
284
+ try {
285
+ const { data } = await axios.get(`${this.BASE_URL}/${chapterSlug}/`);
286
+ const $ = cheerio.load(data);
287
+ const title = $('#Judul h1').text().trim();
288
+ const images = [];
289
+
290
+ $('#Baca_Komik img').each((_, el) => {
291
+ const imageUrl = $(el).attr('src') || '';
292
+ const index = parseInt($(el).attr('id') || '0');
293
+
294
+ if (imageUrl && index) {
295
+ images.push({
296
+ index,
297
+ imageUrl
298
+ });
299
+ }
300
+ });
301
+
302
+ images.sort((a, b) => a.index - b.index);
303
+
304
+ const chapterNumber = chapterSlug.match(/chapter-(\d+)/)?.[1] || '';
305
+ const seriesTitle = title.split('Chapter')[0].trim();
306
+ const seriesSlug = chapterSlug.split('-chapter-')[0];
307
+ const seriesUrl = `${this.BASE_URL}/manga/${seriesSlug}`;
308
+
309
+ const result = {
310
+ title,
311
+ chapterNumber,
312
+ seriesTitle,
313
+ seriesUrl,
314
+ totalImages: images.length,
315
+ images,
316
+ };
317
+
318
+ return this.wrapResponse(result);
319
+
320
+ } catch (error) {
321
+ if (axios.isAxiosError(error)) {
322
+ console.error('Axios error reading chapter:', {
323
+ message: error.message,
324
+ status: error.response?.status,
325
+ statusText: error.response?.statusText
326
+ });
327
+ } else {
328
+ console.error('Error reading chapter:', error);
329
+ }
330
+ return this.wrapResponse(null);
331
+ }
332
+ }
333
+
334
+ async getLatestPopularManga() {
335
+ try {
336
+ const { data } = await axios.get(this.BASE_URL);
337
+ const $ = cheerio.load(data);
338
+ const results = [];
339
+
340
+ $(".home #Komik_Hot_Manga article.ls2").each((_, el) => {
341
+ try {
342
+ const title = $(el).find(".ls2j h3 a").text().trim();
343
+ const mangaUrlPath = $(el).find(".ls2j h3 a").attr("href");
344
+ const mangaUrl = mangaUrlPath ? `${this.BASE_URL}${mangaUrlPath}` : '';
345
+ const slug = mangaUrl ? getSlugFromUrl(mangaUrl) : '';
346
+
347
+ let thumbnailUrl =
348
+ $(el).find("img").attr("data-src") ||
349
+ $(el).find("img").attr("src") ||
350
+ '';
351
+ thumbnailUrl = resizeThumbnail(thumbnailUrl);
352
+
353
+ const genreView = $(el).find(".ls2t").text().trim();
354
+ const latestChapter = $(el).find(".ls2l").text().trim();
355
+ const chapterUrlPath = $(el).find(".ls2l").attr("href");
356
+ const chapterUrl = chapterUrlPath ? `${this.BASE_URL}${chapterUrlPath}` : '';
357
+
358
+ if (title && mangaUrl) {
359
+ results.push({
360
+ title,
361
+ mangaUrl,
362
+ thumbnailUrl,
363
+ genreView,
364
+ slug,
365
+ latestChapter,
366
+ chapterUrl
367
+ });
368
+ }
369
+ } catch (error) {
370
+ console.error('Error parsing manga item:', error);
371
+ }
372
+ });
373
+
374
+ return this.wrapResponse(results);
375
+ } catch (error) {
376
+ if (axios.isAxiosError(error)) {
377
+ console.error('Axios error fetching latest manga:', {
378
+ message: error.message,
379
+ status: error.response?.status,
380
+ statusText: error.response?.statusText
381
+ });
382
+ } else {
383
+ console.error('Error fetching latest manga:', error);
384
+ }
385
+ return [];
386
+ }
387
+ }
388
+
389
+ async getLatestPopularManhwa() {
390
+ try {
391
+ const { data } = await axios.get(this.BASE_URL);
392
+ const $ = cheerio.load(data);
393
+ const results = [];
394
+
395
+ $(".home #Komik_Hot_Manhwa article.ls2").each((_, el) => {
396
+ try {
397
+ const title = $(el).find(".ls2j h3 a").text().trim();
398
+ const mangaUrlPath = $(el).find(".ls2j h3 a").attr("href");
399
+ const mangaUrl = mangaUrlPath ? `${this.BASE_URL}${mangaUrlPath}` : '';
400
+ const slug = mangaUrl ? getSlugFromUrl(mangaUrl) : '';
401
+
402
+ let thumbnailUrl =
403
+ $(el).find("img").attr("data-src") ||
404
+ $(el).find("img").attr("src") ||
405
+ '';
406
+ thumbnailUrl = resizeThumbnail(thumbnailUrl);
407
+
408
+ const genreView = $(el).find(".ls2t").text().trim();
409
+ const latestChapter = $(el).find(".ls2l").text().trim();
410
+ const chapterUrlPath = $(el).find(".ls2l").attr("href");
411
+ const chapterUrl = chapterUrlPath ? `${this.BASE_URL}${chapterUrlPath}` : '';
412
+
413
+ if (title && mangaUrl) {
414
+ results.push({
415
+ title,
416
+ mangaUrl,
417
+ thumbnailUrl,
418
+ genreView,
419
+ slug,
420
+ latestChapter,
421
+ chapterUrl
422
+ });
423
+ }
424
+ } catch (error) {
425
+ console.error('Error parsing manga item:', error);
426
+ }
427
+ });
428
+
429
+ return this.wrapResponse(results);
430
+ } catch (error) {
431
+ if (axios.isAxiosError(error)) {
432
+ console.error('Axios error fetching latest manga:', {
433
+ message: error.message,
434
+ status: error.response?.status,
435
+ statusText: error.response?.statusText
436
+ });
437
+ } else {
438
+ console.error('Error fetching latest manga:', error);
439
+ }
440
+ return [];
441
+ }
442
+ }
443
+
444
+ async getLatestPopularManhua() {
445
+ try {
446
+ const { data } = await axios.get(this.BASE_URL);
447
+ const $ = cheerio.load(data);
448
+ const results = [];
449
+
450
+ $(".home #Komik_Hot_Manhua article.ls2").each((_, el) => {
451
+ try {
452
+ const title = $(el).find(".ls2j h3 a").text().trim();
453
+ const mangaUrlPath = $(el).find(".ls2j h3 a").attr("href");
454
+ const mangaUrl = mangaUrlPath ? `${this.BASE_URL}${mangaUrlPath}` : '';
455
+ const slug = mangaUrl ? getSlugFromUrl(mangaUrl) : '';
456
+
457
+ let thumbnailUrl =
458
+ $(el).find("img").attr("data-src") ||
459
+ $(el).find("img").attr("src") ||
460
+ '';
461
+ thumbnailUrl = resizeThumbnail(thumbnailUrl);
462
+
463
+ const genreView = $(el).find(".ls2t").text().trim();
464
+ const latestChapter = $(el).find(".ls2l").text().trim();
465
+ const chapterUrlPath = $(el).find(".ls2l").attr("href");
466
+ const chapterUrl = chapterUrlPath ? `${this.BASE_URL}${chapterUrlPath}` : '';
467
+
468
+ if (title && mangaUrl) {
469
+ results.push({
470
+ title,
471
+ mangaUrl,
472
+ thumbnailUrl,
473
+ genreView,
474
+ slug,
475
+ latestChapter,
476
+ chapterUrl
477
+ });
478
+ }
479
+ } catch (error) {
480
+ console.error('Error parsing manga item:', error);
481
+ }
482
+ });
483
+
484
+ return this.wrapResponse(results);
485
+ } catch (error) {
486
+ if (axios.isAxiosError(error)) {
487
+ console.error('Axios error fetching latest manga:', {
488
+ message: error.message,
489
+ status: error.response?.status,
490
+ statusText: error.response?.statusText
491
+ });
492
+ } else {
493
+ console.error('Error fetching latest manga:', error);
494
+ }
495
+ return [];
496
+ }
497
+ }
498
+ }
src/server/plugins/anime/komiku_get_details.js ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js";
2
+ import { Komiku } from "./komiku.js"
3
+
4
+ /** @type {import("../../types/plugin.ts").ApiPluginHandler}*/
5
+ const handler = {
6
+ name: "Komiku: Get detail of comic",
7
+ method: "GET",
8
+ version: "1.0.0",
9
+ category: ["komiku"],
10
+ alias: ["getDetail"],
11
+ tags: ["comic"],
12
+ parameters: {
13
+ query: [
14
+ {
15
+ name: "slug",
16
+ type: "string",
17
+ required: true,
18
+ description: "Slug comic",
19
+ example: "solo-leveling-id"
20
+ }
21
+ ],
22
+ body: [],
23
+ headers: []
24
+ },
25
+ responses: {
26
+ 200: {
27
+ status: 200,
28
+ description: "Successfully retrieved data",
29
+ example: {
30
+ status: 200,
31
+ author: "Ditzzy",
32
+ note: "Thank you for using this API!",
33
+ results: {}
34
+ }
35
+ },
36
+ 400: {
37
+ status: 400,
38
+ description: "Invalid Slug provided",
39
+ example: {
40
+ status: 400,
41
+ message: "Invalid slug - must be a valid of Comic slug"
42
+ }
43
+ },
44
+ 404: {
45
+ status: 404,
46
+ description: "Missing required parameter",
47
+ example: {
48
+ status: 404,
49
+ message: "Missing required parameter: ..."
50
+ }
51
+ },
52
+ 500: {
53
+ status: 500,
54
+ description: "Server error or unavailable",
55
+ example: {
56
+ status: 500,
57
+ message: "An error occurred, please try again later."
58
+ }
59
+ }
60
+ },
61
+ exec: async (req, res) => {
62
+ const { slug } = req.query;
63
+ if (!slug) return ErrorResponses.missingParameter(res, "slug");
64
+
65
+ const regex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
66
+
67
+ if (!regex.test(slug)) return ErrorResponses.invalidUrl(res, "Invalid slug - must be a valid of Comic slug");
68
+
69
+ const komik = new Komiku();
70
+ try {
71
+ const detail = await komik.getDetail(slug);
72
+
73
+ if (detail.results === null) return ErrorResponses.notFound(res);
74
+
75
+ sendSuccess(res, detail.results)
76
+ } catch (e) {
77
+ ErrorResponses.serverError(res, "An error occurred, try again later.");
78
+ }
79
+ }
80
+ }
81
+
82
+ export default handler;
src/server/plugins/anime/komiku_get_manga.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js";
2
+ import { Komiku } from "./komiku.js"
3
+
4
+ /** @type {import("../../types/plugin.ts").ApiPluginHandler}*/
5
+ const handler = {
6
+ name: "Komiku: Get the latest manga",
7
+ method: "GET",
8
+ version: "1.0.0",
9
+ category: ["komiku"],
10
+ alias: ["getLatestManga"],
11
+ tags: ["comic"],
12
+ responses: {
13
+ 200: {
14
+ status: 200,
15
+ description: "Successfully retrieved data",
16
+ example: {
17
+ status: 200,
18
+ author: "Ditzzy",
19
+ note: "Thank you for using this API!",
20
+ results: {}
21
+ }
22
+ },
23
+ 500: {
24
+ status: 500,
25
+ description: "Server error or unavailable",
26
+ example: {
27
+ status: 500,
28
+ message: "An error occurred, please try again later."
29
+ }
30
+ }
31
+ },
32
+ exec: async (_req, res) => {
33
+ const komik = new Komiku();
34
+ try {
35
+ const latest = await komik.getLatestPopularManga();
36
+
37
+ sendSuccess(res, latest.results)
38
+ } catch (e) {
39
+ ErrorResponses.serverError(res, "An error occurred, try again later.");
40
+ }
41
+ }
42
+ }
43
+
44
+ export default handler;
src/server/plugins/anime/komiku_get_manhua.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js";
2
+ import { Komiku } from "./komiku.js"
3
+
4
+ /** @type {import("../../types/plugin.ts").ApiPluginHandler}*/
5
+ const handler = {
6
+ name: "Komiku: Get the latest manhua",
7
+ method: "GET",
8
+ version: "1.0.0",
9
+ category: ["komiku"],
10
+ alias: ["getLatestManhua"],
11
+ tags: ["comic"],
12
+ responses: {
13
+ 200: {
14
+ status: 200,
15
+ description: "Successfully retrieved data",
16
+ example: {
17
+ status: 200,
18
+ author: "Ditzzy",
19
+ note: "Thank you for using this API!",
20
+ results: {}
21
+ }
22
+ },
23
+ 500: {
24
+ status: 500,
25
+ description: "Server error or unavailable",
26
+ example: {
27
+ status: 500,
28
+ message: "An error occurred, please try again later."
29
+ }
30
+ }
31
+ },
32
+ exec: async (_req, res) => {
33
+ const komik = new Komiku();
34
+ try {
35
+ const latest = await komik.getLatestPopularManhua();
36
+
37
+ sendSuccess(res, latest.results)
38
+ } catch (e) {
39
+ ErrorResponses.serverError(res, "An error occurred, try again later.");
40
+ }
41
+ }
42
+ }
43
+
44
+ export default handler;
src/server/plugins/anime/komiku_get_manhwa.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js";
2
+ import { Komiku } from "./komiku.js"
3
+
4
+ /** @type {import("../../types/plugin.ts").ApiPluginHandler}*/
5
+ const handler = {
6
+ name: "Komiku: Get the latest manhwa",
7
+ method: "GET",
8
+ version: "1.0.0",
9
+ category: ["komiku"],
10
+ alias: ["getLatestManhwa"],
11
+ tags: ["comic"],
12
+ responses: {
13
+ 200: {
14
+ status: 200,
15
+ description: "Successfully retrieved data",
16
+ example: {
17
+ status: 200,
18
+ author: "Ditzzy",
19
+ note: "Thank you for using this API!",
20
+ results: {}
21
+ }
22
+ },
23
+ 500: {
24
+ status: 500,
25
+ description: "Server error or unavailable",
26
+ example: {
27
+ status: 500,
28
+ message: "An error occurred, please try again later."
29
+ }
30
+ }
31
+ },
32
+ exec: async (_req, res) => {
33
+ const komik = new Komiku();
34
+ try {
35
+ const latest = await komik.getLatestPopularManhwa();
36
+
37
+ sendSuccess(res, latest.results)
38
+ } catch (e) {
39
+ ErrorResponses.serverError(res, "An error occurred, try again later.");
40
+ }
41
+ }
42
+ }
43
+
44
+ export default handler;
src/server/plugins/anime/komiku_read_chapter.js ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js";
2
+ import { Komiku } from "./komiku.js"
3
+
4
+ /** @type {import("../../types/plugin.ts").ApiPluginHandler}*/
5
+ const handler = {
6
+ name: "Komiku: Read chapter",
7
+ method: "GET",
8
+ version: "1.0.0",
9
+ category: ["komiku"],
10
+ alias: ["readChapter"],
11
+ tags: ["comic"],
12
+ parameters: {
13
+ query: [
14
+ {
15
+ name: "slug",
16
+ type: "string",
17
+ required: true,
18
+ description: "Chapter slug of comic",
19
+ example: "otonari-no-tenshi-sama-ni-itsunomanika-dame-ningen-ni-sareteita-ken-chapter-00"
20
+ }
21
+ ],
22
+ body: [],
23
+ headers: []
24
+ },
25
+ responses: {
26
+ 200: {
27
+ status: 200,
28
+ description: "Successfully retrieved data",
29
+ example: {
30
+ status: 200,
31
+ author: "Ditzzy",
32
+ note: "Thank you for using this API!",
33
+ results: {}
34
+ }
35
+ },
36
+ 400: {
37
+ status: 400,
38
+ description: "Invalid Slug provided",
39
+ example: {
40
+ status: 400,
41
+ message: "Invalid slug - must be a valid of Comic slug"
42
+ }
43
+ },
44
+ 404: {
45
+ status: 404,
46
+ description: "Missing required parameter",
47
+ example: {
48
+ status: 404,
49
+ message: "Missing required parameter: ..."
50
+ }
51
+ },
52
+ 500: {
53
+ status: 500,
54
+ description: "Server error or unavailable",
55
+ example: {
56
+ status: 500,
57
+ message: "An error occurred, please try again later."
58
+ }
59
+ }
60
+ },
61
+ exec: async (req, res) => {
62
+ const { slug } = req.query;
63
+ if (!slug) return ErrorResponses.missingParameter(res, "slug");
64
+
65
+ const regex = /^[a-z0-9]+(?:-[a-z0-9]+)*-chapter-\d+(?:-\d+)?$/;
66
+
67
+ if (!regex.test(slug)) return ErrorResponses.invalidUrl(res, "Invalid slug chapter - must be a valid slug chapter of Comic");
68
+
69
+ const komik = new Komiku();
70
+ try {
71
+ const detail = await komik.readChapter(slug);
72
+
73
+ if (detail.results === null) return ErrorResponses.notFound(res);
74
+
75
+ sendSuccess(res, detail.results)
76
+ } catch (e) {
77
+ ErrorResponses.serverError(res, "An error occurred, try again later.");
78
+ }
79
+ }
80
+ }
81
+
82
+ export default handler;
src/server/plugins/anime/komiku_search.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js";
2
+ import { Komiku } from "./komiku.js"
3
+
4
+ /** @type {import("../../types/plugin.ts").ApiPluginHandler}*/
5
+ const handler = {
6
+ name: "Komiku: Search Comic",
7
+ method: "GET",
8
+ version: "1.0.0",
9
+ category: ["komiku"],
10
+ alias: ["search"],
11
+ tags: ["comic"],
12
+ parameters: {
13
+ query: [
14
+ {
15
+ name: "query",
16
+ type: "string",
17
+ required: true,
18
+ description: "Your search, Write a title",
19
+ example: "Otonari no tenshi"
20
+ }
21
+ ],
22
+ body: [],
23
+ headers: []
24
+ },
25
+ responses: {
26
+ 200: {
27
+ status: 200,
28
+ description: "Successfully retrieved data",
29
+ example: {
30
+ status: 200,
31
+ author: "Ditzzy",
32
+ note: "Thank you for using this API!",
33
+ results: {}
34
+ }
35
+ },
36
+ 404: {
37
+ status: 404,
38
+ description: "Missing required parameter",
39
+ example: {
40
+ status: 404,
41
+ message: "Missing required parameter: ..."
42
+ }
43
+ },
44
+ 500: {
45
+ status: 500,
46
+ description: "Server error or unavailable",
47
+ example: {
48
+ status: 500,
49
+ message: "An error occurred, please try again later."
50
+ }
51
+ }
52
+ },
53
+ exec: async (req, res) => {
54
+ const { query } = req.query;
55
+
56
+ if (!query) return ErrorResponses.missingParameter(res, "query");
57
+
58
+ const komik = new Komiku();
59
+ try {
60
+ const search = await komik.search(query);
61
+
62
+ if (search.results === null) return ErrorResponses.notFound(res);
63
+
64
+ sendSuccess(res, search.results)
65
+ } catch (e) {
66
+ ErrorResponses.serverError(res, "An error occurred, try again later.");
67
+ }
68
+ }
69
+ }
70
+
71
+ export default handler;
src/server/plugins/downloader/youtube.js CHANGED
@@ -129,7 +129,7 @@ const handler = {
129
 
130
  return sendSuccess(res, download.results);
131
  } catch (e) {
132
- console.error("TikTok download error:", e);
133
  return ErrorResponses.serverError(res);
134
  }
135
  }
 
129
 
130
  return sendSuccess(res, download.results);
131
  } catch (e) {
132
+ console.error("Error:", e);
133
  return ErrorResponses.serverError(res);
134
  }
135
  }