OhMyDitzzy
Add: komiku
f535bab
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 [];
}
}
}