2025-10-26 22:43:57 +00:00
|
|
|
|
async function searchResults(keyword) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const encodedKeyword = encodeURIComponent(keyword);
|
|
|
|
|
|
const response = await fetchv2(`https://mangakatana.com/?search=${encodedKeyword}&search_by=book_name`);
|
|
|
|
|
|
const html = await response.text();
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
|
|
|
|
const itemRegex = /<div class="item"[^>]*data-genre="[^"]*"[^>]*data-id="[^"]*"[^>]*>([\s\S]*?)(?=<div class="item"|$)/g;
|
|
|
|
|
|
|
|
|
|
|
|
let itemMatch;
|
|
|
|
|
|
while ((itemMatch = itemRegex.exec(html)) !== null) {
|
|
|
|
|
|
const itemHtml = itemMatch[1];
|
|
|
|
|
|
|
|
|
|
|
|
const titleRegex = /<h3 class="title">\s*<a href="([^"]+)"[^>]*>([^<]+)<\/a>/;
|
|
|
|
|
|
const imageRegex = /<div class="wrap_img">\s*<a[^>]*><img src="([^"]+)"/;
|
|
|
|
|
|
|
|
|
|
|
|
const titleMatch = titleRegex.exec(itemHtml);
|
|
|
|
|
|
const imageMatch = imageRegex.exec(itemHtml);
|
|
|
|
|
|
|
|
|
|
|
|
if (titleMatch && imageMatch) {
|
|
|
|
|
|
const title = titleMatch[2].trim();
|
|
|
|
|
|
const href = titleMatch[1].trim();
|
|
|
|
|
|
const image = imageMatch[1].trim();
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
title && href && image &&
|
|
|
|
|
|
!title.includes("'+") &&
|
|
|
|
|
|
!href.includes("'+") &&
|
|
|
|
|
|
href.startsWith("http")
|
|
|
|
|
|
) {
|
|
|
|
|
|
results.push({
|
|
|
|
|
|
title: title,
|
|
|
|
|
|
href: href,
|
|
|
|
|
|
image: image
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`Search results for "${keyword}":`, JSON.stringify(results));
|
|
|
|
|
|
return JSON.stringify(results);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.log('Fetch error in searchResults:', error);
|
|
|
|
|
|
return JSON.stringify([{ title: 'Error', href: '', image: '' }]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function extractDetails(url) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await soraFetch(url);
|
|
|
|
|
|
const htmlText = await response.text();
|
|
|
|
|
|
|
|
|
|
|
|
const descMatch = htmlText.match(/<div class="label">Description<\/div>\s*<p>([\s\S]*?)<\/p>/);
|
|
|
|
|
|
|
|
|
|
|
|
let description = 'No description available';
|
|
|
|
|
|
if (descMatch && descMatch[1]) {
|
|
|
|
|
|
description = descMatch[1]
|
|
|
|
|
|
.replace(/<[^>]+>/g, '')
|
|
|
|
|
|
.replace(/\s+/g, ' ')
|
|
|
|
|
|
.trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const transformedResults = [{
|
|
|
|
|
|
description,
|
|
|
|
|
|
aliases: 'N/A',
|
|
|
|
|
|
airdate: 'N/A'
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`Details for "${url}":`, JSON.stringify(transformedResults));
|
|
|
|
|
|
return JSON.stringify(transformedResults);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.log('Details error:', error);
|
|
|
|
|
|
return JSON.stringify([{
|
|
|
|
|
|
description: 'Error loading description',
|
|
|
|
|
|
aliases: 'N/A',
|
|
|
|
|
|
airdate: 'N/A'
|
|
|
|
|
|
}]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function extractChapters(url) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await soraFetch(url);
|
|
|
|
|
|
const htmlText = await response.text();
|
|
|
|
|
|
|
|
|
|
|
|
const chapterRegex = /<tr data-jump="0">[\s\S]*?<a href="([^"]+)">([\s\S]*?)<\/a>[\s\S]*?<\/tr>/g;
|
|
|
|
|
|
const chapters = [];
|
|
|
|
|
|
|
|
|
|
|
|
let match;
|
|
|
|
|
|
while ((match = chapterRegex.exec(htmlText)) !== null) {
|
|
|
|
|
|
const href = match[1].trim();
|
|
|
|
|
|
const titleMatch = /Chapter \d+[:\s]?.*/i.exec(match[2]);
|
|
|
|
|
|
const title = titleMatch ? decodeHtmlEntities(titleMatch[0].trim()) : "Unknown Chapter";
|
|
|
|
|
|
const numberMatch = /Chapter (\d+)/i.exec(title);
|
|
|
|
|
|
const number = numberMatch ? parseInt(numberMatch[1]) : NaN;
|
|
|
|
|
|
|
|
|
|
|
|
chapters.push({
|
|
|
|
|
|
number: number === 0 ? 1 : number,
|
|
|
|
|
|
href: href.startsWith("http") ? href : "https://mangakatana.com" + href,
|
|
|
|
|
|
title: title
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
chapters.reverse();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(JSON.stringify(chapters));
|
|
|
|
|
|
return chapters;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch error in extractChapters:', error);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-26 22:55:39 +00:00
|
|
|
|
|
2025-10-26 22:43:57 +00:00
|
|
|
|
async function extractText(url) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await soraFetch(url);
|
|
|
|
|
|
const htmlText = await response.text();
|
2025-10-26 22:48:56 +00:00
|
|
|
|
|
2025-10-26 22:55:39 +00:00
|
|
|
|
// Extract image URLs using regex
|
2025-10-26 22:48:56 +00:00
|
|
|
|
const regex = /'(https:\/\/[^']+\.jpg)'/g;
|
|
|
|
|
|
const matches = [...htmlText.matchAll(regex)];
|
|
|
|
|
|
const imageUrls = matches.map(match => match[1]);
|
|
|
|
|
|
|
2025-10-26 22:55:39 +00:00
|
|
|
|
// Generate HTML
|
2025-10-26 22:48:56 +00:00
|
|
|
|
const html = `<!DOCTYPE html>
|
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
2025-10-26 22:53:25 +00:00
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
2025-10-26 22:48:56 +00:00
|
|
|
|
<title>Manga</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
* {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
box-sizing: border-box;
|
2025-10-26 22:53:25 +00:00
|
|
|
|
-webkit-tap-highlight-color: transparent;
|
2025-10-26 22:48:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
|
|
|
background: #000;
|
2025-10-26 22:53:25 +00:00
|
|
|
|
-webkit-touch-callout: none;
|
2025-10-26 22:48:56 +00:00
|
|
|
|
}
|
2025-10-26 22:55:39 +00:00
|
|
|
|
.img-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: 1200px;
|
|
|
|
|
|
background: #000;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
2025-10-26 22:48:56 +00:00
|
|
|
|
img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transition: opacity 0.3s ease;
|
2025-10-26 22:53:25 +00:00
|
|
|
|
-webkit-user-select: none;
|
2025-10-26 22:55:39 +00:00
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
2025-10-26 22:48:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
img.loaded {
|
|
|
|
|
|
opacity: 1;
|
2025-10-26 22:55:39 +00:00
|
|
|
|
position: relative;
|
2025-10-26 22:48:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
2025-10-26 22:55:39 +00:00
|
|
|
|
${imageUrls.map(url => ` <div class="img-container"><img data-src="${url}" alt=""></div>`).join('\n')}
|
2025-10-26 22:48:56 +00:00
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-10-26 22:53:25 +00:00
|
|
|
|
(function() {
|
|
|
|
|
|
var images = document.querySelectorAll('img[data-src]');
|
2025-10-26 22:55:39 +00:00
|
|
|
|
var containers = document.querySelectorAll('.img-container');
|
|
|
|
|
|
var loadQueue = [];
|
|
|
|
|
|
var loading = false;
|
2025-10-26 22:53:25 +00:00
|
|
|
|
|
2025-10-26 22:55:39 +00:00
|
|
|
|
function loadImage(index) {
|
|
|
|
|
|
if (index >= images.length || loading) return;
|
|
|
|
|
|
loading = true;
|
2025-10-26 22:53:25 +00:00
|
|
|
|
|
2025-10-26 22:55:39 +00:00
|
|
|
|
var img = images[index];
|
|
|
|
|
|
var container = containers[index];
|
2025-10-26 22:53:25 +00:00
|
|
|
|
img.src = img.dataset.src;
|
|
|
|
|
|
|
|
|
|
|
|
img.onload = function() {
|
2025-10-26 22:55:39 +00:00
|
|
|
|
container.style.minHeight = img.naturalHeight + 'px';
|
2025-10-26 22:53:25 +00:00
|
|
|
|
img.classList.add('loaded');
|
2025-10-26 22:55:39 +00:00
|
|
|
|
loading = false;
|
|
|
|
|
|
loadImage(index + 1);
|
2025-10-26 22:53:25 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
img.onerror = function() {
|
2025-10-26 22:55:39 +00:00
|
|
|
|
loading = false;
|
|
|
|
|
|
loadImage(index + 1);
|
2025-10-26 22:53:25 +00:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-26 22:55:39 +00:00
|
|
|
|
loadImage(0);
|
2025-10-26 22:53:25 +00:00
|
|
|
|
})();
|
2025-10-26 22:48:56 +00:00
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>`;
|
|
|
|
|
|
|
|
|
|
|
|
return html;
|
|
|
|
|
|
|
2025-10-26 22:43:57 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("❌ Error in extractImages:", error);
|
|
|
|
|
|
return {
|
|
|
|
|
|
error: `Error loading chapter images: ${error.message}`
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function decodeHtmlEntities(str) {
|
|
|
|
|
|
const named = {
|
|
|
|
|
|
amp: '&',
|
|
|
|
|
|
lt: '<',
|
|
|
|
|
|
gt: '>',
|
|
|
|
|
|
quot: '"',
|
|
|
|
|
|
apos: "'",
|
|
|
|
|
|
nbsp: ' ',
|
|
|
|
|
|
hellip: '…',
|
|
|
|
|
|
rsquo: '’',
|
|
|
|
|
|
lsquo: '‘',
|
|
|
|
|
|
ndash: '–',
|
|
|
|
|
|
mdash: '—'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return str
|
|
|
|
|
|
.replace(/&([a-z]+);/gi, (match, name) => named[name] || match)
|
|
|
|
|
|
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
|
|
|
|
|
} catch(e) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await fetch(url, options);
|
|
|
|
|
|
} catch(error) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|