import axios from "axios"; import * as cheerio from "cheerio"; export function getSlugFromUrl(url) { try { const parsed = new URL(url); const parts = parsed.pathname.split("/").filter(Boolean); return parts[parts.length - 1] || ''; } catch (error) { try { const parts = url.split("/").filter(Boolean); return parts[parts.length - 1] || ''; } catch { return ''; } } } function parseUpdateToMs(updateText) { const now = Date.now(); const match = updateText.match(/(\d+)\s*(detik|menit|jam|hari|minggu|bulan|tahun)/i); if (!match) return 0; const value = parseInt(match[1]); const unit = match[2].toLowerCase(); const msPerUnit = { 'detik': 1000, 'menit': 60 * 1000, 'jam': 60 * 60 * 1000, 'hari': 24 * 60 * 60 * 1000, 'minggu': 7 * 24 * 60 * 60 * 1000, 'bulan': 30 * 24 * 60 * 60 * 1000, 'tahun': 365 * 24 * 60 * 60 * 1000 }; const ms = msPerUnit[unit] || 0; return now - (value * ms); } function resizeThumbnail(url, width = 540, height = 350) { if (!url) return url; if (url.includes('?resize=')) { return url.replace(/\?resize=\d+,\d+/, `?resize=${width},${height}`); } return url; } export class Komiku { constructor() { this.BASE_URL = "https://komiku.org"; this.API_URL = "https://api.komiku.org"; this.CREATED_BY = "Ditzzy"; this.NOTE = "Thank you for using this scrape, I hope you appreciate me for making this scrape by not deleting wm"; } wrapResponse(data) { return { created_by: this.CREATED_BY, note: this.NOTE, results: data }; } async search(query, postType = "manga") { try { const { data } = await axios.get(`${this.API_URL}/?post_type=${postType}&s=${encodeURIComponent(query)}`); const $ = cheerio.load(data); const results = []; const $containers = $('div.bge'); for (let index = 0; index < $containers.length; index++) { const el = $containers[index]; try { let thumbnailUrl = ''; const imgElement = $(el).find('img').first(); if (imgElement.length > 0) { thumbnailUrl = imgElement.attr('src') || imgElement.attr('data-src') || ''; thumbnailUrl = resizeThumbnail(thumbnailUrl); } let type = ''; let genre = ''; const typeGenreElement = $(el).find('div.tpe1_inf, .tpe1_inf'); if (typeGenreElement.length > 0) { const text = typeGenreElement.text().trim(); const parts = text.split(/\s+/); if (parts.length > 0) { type = parts[0].replace(/<\/?b>/g, '').trim(); genre = parts.slice(1).join(' ').trim(); } } let title = ''; let mangaUrl = ''; const h3Element = $(el).find('h3').first(); if (h3Element.length > 0) { title = h3Element.text().trim(); const parentLink = h3Element.parent('a'); if (parentLink.length > 0) { mangaUrl = parentLink.attr('href') || ''; } else { const nearbyLink = h3Element.closest('div').find('a[href*="/manga/"]').first(); if (nearbyLink.length > 0) { mangaUrl = nearbyLink.attr('href') || ''; } } } if (!title || !mangaUrl) { $(el).find('a[href*="/manga/"]').each((_, linkEl) => { const h3 = $(linkEl).find('h3'); if (h3.length > 0) { title = h3.text().trim(); mangaUrl = $(linkEl).attr('href') || ''; return false; } }); } if (mangaUrl && !mangaUrl.startsWith('http')) { mangaUrl = this.BASE_URL + mangaUrl; } let lastUpdateMs = 0; $(el).find('p').each((_, pEl) => { const text = $(pEl).text().trim(); if (text.toLowerCase().includes('update')) { lastUpdateMs = parseUpdateToMs(text); return false; } }); let firstChapter = null; let latestChapter = null; $(el).find('div.new1, .new1').each((_, newEl) => { const link = $(newEl).find('a'); if (link.length > 0) { const spans = link.find('span'); if (spans.length >= 2) { const label = spans.first().text().trim().toLowerCase(); const chapterTitle = spans.last().text().trim(); const chapterUrl = link.attr('href') || ''; const fullUrl = chapterUrl && !chapterUrl.startsWith('http') ? this.BASE_URL + chapterUrl : chapterUrl; const chapterSlug = getSlugFromUrl(chapterUrl); if (label.includes('awal') || label.includes('first')) { firstChapter = { title: chapterTitle, url: fullUrl, slug: chapterSlug }; } else if (label.includes('terbaru') || label.includes('latest')) { latestChapter = { title: chapterTitle, url: fullUrl, slug: chapterSlug }; } } } }); if (title && mangaUrl) { const slug = getSlugFromUrl(mangaUrl); const detail = await this.getDetail(slug); results.push({ title, mangaUrl, thumbnailUrl, type, genre, lastUpdateMs, firstChapter, latestChapter, detail }); } } catch (error) { console.error('Error parsing search item:', error); } } return this.wrapResponse(results); } catch (error) { if (axios.isAxiosError(error)) { console.error('Axios error on search:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText }); } else { console.error('Error on search:', error); } return []; } } async getDetail(slug) { try { const { data } = await axios.get(`${this.BASE_URL}/manga/${slug}`); const $ = cheerio.load(data); let results = null; $('.series').each((_, el) => { const keyMap = { 'Judul Komik': 'title', 'Judul Indonesia': 'indonesia_title', 'Jenis Komik': 'type', 'Pengarang': 'author', 'Status': 'status' }; const info = {}; $(el).find('table.inftable tr').each((_, el) => { const key = $(el).find('td:first-child').text().trim(); const value = $(el).find('td:last-child').text().trim(); if (keyMap[key]) { info[keyMap[key]] = value; } }); const genre = []; $('ul.genre li.genre span[itemprop="genre"]').each((_, el) => { genre.push($(el).text().trim()); }); const synopsis = $('p.desc').text().trim(); let thumbnailUrl = $('div.ims img[itemprop="image"]').attr("src")?.trim() || ''; thumbnailUrl = resizeThumbnail(thumbnailUrl); const chapters = []; $('table#Daftar_Chapter tr:not(:first-child)').each((_, el) => { const chapter = $(el).find('td.judulseries a span').text().trim(); const slug_chapter = $(el).find('td.judulseries a').attr('href')?.replace(/\//g, '') || ''; const views = $(el).find('td.pembaca i').text().trim(); const date = $(el).find('td.tanggalseries').text().trim(); chapters.push({ chapter, slug_chapter, views, date }); }); results = { ...info, thumbnailUrl, synopsis, genre, chapters }; }); return this.wrapResponse(results); } catch (error) { if (axios.isAxiosError(error)) { console.error('Axios error fetching detail:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText }); } else { console.error('Error fetching detail:', error); } return this.wrapResponse(null); } } async readChapter(chapterSlug) { try { const { data } = await axios.get(`${this.BASE_URL}/${chapterSlug}/`); const $ = cheerio.load(data); const title = $('#Judul h1').text().trim(); const images = []; $('#Baca_Komik img').each((_, el) => { const imageUrl = $(el).attr('src') || ''; const index = parseInt($(el).attr('id') || '0'); if (imageUrl && index) { images.push({ index, imageUrl }); } }); images.sort((a, b) => a.index - b.index); const chapterNumber = chapterSlug.match(/chapter-(\d+)/)?.[1] || ''; const seriesTitle = title.split('Chapter')[0].trim(); const seriesSlug = chapterSlug.split('-chapter-')[0]; const seriesUrl = `${this.BASE_URL}/manga/${seriesSlug}`; const result = { title, chapterNumber, seriesTitle, seriesUrl, totalImages: images.length, images, }; return this.wrapResponse(result); } catch (error) { if (axios.isAxiosError(error)) { console.error('Axios error reading chapter:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText }); } else { console.error('Error reading chapter:', error); } return this.wrapResponse(null); } } async getLatestPopularManga() { try { const { data } = await axios.get(this.BASE_URL); const $ = cheerio.load(data); const results = []; $(".home #Komik_Hot_Manga article.ls2").each((_, el) => { try { const title = $(el).find(".ls2j h3 a").text().trim(); const mangaUrlPath = $(el).find(".ls2j h3 a").attr("href"); const mangaUrl = mangaUrlPath ? `${this.BASE_URL}${mangaUrlPath}` : ''; const slug = mangaUrl ? getSlugFromUrl(mangaUrl) : ''; let thumbnailUrl = $(el).find("img").attr("data-src") || $(el).find("img").attr("src") || ''; thumbnailUrl = resizeThumbnail(thumbnailUrl); const genreView = $(el).find(".ls2t").text().trim(); const latestChapter = $(el).find(".ls2l").text().trim(); const chapterUrlPath = $(el).find(".ls2l").attr("href"); const chapterUrl = chapterUrlPath ? `${this.BASE_URL}${chapterUrlPath}` : ''; if (title && mangaUrl) { results.push({ title, mangaUrl, thumbnailUrl, genreView, slug, latestChapter, chapterUrl }); } } catch (error) { console.error('Error parsing manga item:', error); } }); return this.wrapResponse(results); } catch (error) { if (axios.isAxiosError(error)) { console.error('Axios error fetching latest manga:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText }); } else { console.error('Error fetching latest manga:', error); } return []; } } async getLatestPopularManhwa() { try { const { data } = await axios.get(this.BASE_URL); const $ = cheerio.load(data); const results = []; $(".home #Komik_Hot_Manhwa article.ls2").each((_, el) => { try { const title = $(el).find(".ls2j h3 a").text().trim(); const mangaUrlPath = $(el).find(".ls2j h3 a").attr("href"); const mangaUrl = mangaUrlPath ? `${this.BASE_URL}${mangaUrlPath}` : ''; const slug = mangaUrl ? getSlugFromUrl(mangaUrl) : ''; let thumbnailUrl = $(el).find("img").attr("data-src") || $(el).find("img").attr("src") || ''; thumbnailUrl = resizeThumbnail(thumbnailUrl); const genreView = $(el).find(".ls2t").text().trim(); const latestChapter = $(el).find(".ls2l").text().trim(); const chapterUrlPath = $(el).find(".ls2l").attr("href"); const chapterUrl = chapterUrlPath ? `${this.BASE_URL}${chapterUrlPath}` : ''; if (title && mangaUrl) { results.push({ title, mangaUrl, thumbnailUrl, genreView, slug, latestChapter, chapterUrl }); } } catch (error) { console.error('Error parsing manga item:', error); } }); return this.wrapResponse(results); } catch (error) { if (axios.isAxiosError(error)) { console.error('Axios error fetching latest manga:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText }); } else { console.error('Error fetching latest manga:', error); } return []; } } async getLatestPopularManhua() { try { const { data } = await axios.get(this.BASE_URL); const $ = cheerio.load(data); const results = []; $(".home #Komik_Hot_Manhua article.ls2").each((_, el) => { try { const title = $(el).find(".ls2j h3 a").text().trim(); const mangaUrlPath = $(el).find(".ls2j h3 a").attr("href"); const mangaUrl = mangaUrlPath ? `${this.BASE_URL}${mangaUrlPath}` : ''; const slug = mangaUrl ? getSlugFromUrl(mangaUrl) : ''; let thumbnailUrl = $(el).find("img").attr("data-src") || $(el).find("img").attr("src") || ''; thumbnailUrl = resizeThumbnail(thumbnailUrl); const genreView = $(el).find(".ls2t").text().trim(); const latestChapter = $(el).find(".ls2l").text().trim(); const chapterUrlPath = $(el).find(".ls2l").attr("href"); const chapterUrl = chapterUrlPath ? `${this.BASE_URL}${chapterUrlPath}` : ''; if (title && mangaUrl) { results.push({ title, mangaUrl, thumbnailUrl, genreView, slug, latestChapter, chapterUrl }); } } catch (error) { console.error('Error parsing manga item:', error); } }); return this.wrapResponse(results); } catch (error) { if (axios.isAxiosError(error)) { console.error('Axios error fetching latest manga:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText }); } else { console.error('Error fetching latest manga:', error); } return []; } } }