Upload files to "/"

This commit is contained in:
2025-11-23 23:23:51 +01:00
parent 591b664375
commit c59ba1d556
5 changed files with 683 additions and 0 deletions

113
ZeroChan.js Normal file
View File

@@ -0,0 +1,113 @@
class ZeroChan {
baseUrl = "https://zerochan.net";
constructor(fetchPath, cheerioPath, browser) {
this.browser = browser;
this.type = "image-board";
}
async fetchSearchResult(query = "hello", page = 1, perPage = 48) {
const url = `${this.baseUrl}/${query.trim().replace(/\s+/g, "+")}?p=${page}`;
const data = await this.browser.scrape(
url,
() => {
const list = document.querySelectorAll("#thumbs2 li");
if (list.length === 0) {
return { results: [], hasNextPage: false };
}
const results = [];
list.forEach(li => {
const id = li.getAttribute("data-id");
if (!id) return;
const img = li.querySelector("img");
const imgUrl =
img?.getAttribute("data-src") ||
img?.getAttribute("src") ||
null;
if (!imgUrl) return;
const tagLinks = li.querySelectorAll("p a");
const tags = [...tagLinks]
.map(a => a.textContent.trim())
.filter(Boolean);
results.push({
id,
image: imgUrl,
sampleImageUrl: imgUrl,
tags,
type: "preview"
});
});
const hasNextPage = document.querySelector('nav.pagination a[rel="next"]') !== null;
return {
results,
hasNextPage
};
},
{ waitSelector: "#thumbs2 li", timeout: 15000, renderWaitTime: 3000, loadImages: true }
);
console.log(data)
return {
results: data.results,
hasNextPage: data.hasNextPage,
page
};
}
async fetchInfo(id) {
const url = `${this.baseUrl}/${id}`;
const data = await this.browser.scrape(
url,
() => {
const preview = document.querySelector("a.preview");
if (!preview) {
return {
fullImage: null,
tags: [],
createdAt: Date.now()
};
}
const fullImage = preview.getAttribute("href") || null;
const img = preview.querySelector("img");
const alt = img?.getAttribute("alt") || "";
let tags = [];
if (alt.startsWith("Tags:")) {
tags = alt
.replace("Tags:", "")
.split(",")
.map(t => t.trim())
.filter(Boolean);
}
return {
fullImage,
tags,
createdAt: Date.now()
};
},
{ waitSelector: "a.preview img", timeout: 15000 }
);
return {
id,
fullImage: data.fullImage,
tags: data.tags,
createdAt: data.createdAt,
rating: "Unknown"
};
}
}
module.exports = { ZeroChan };

154
novelfire.js Normal file
View File

@@ -0,0 +1,154 @@
class novelfire {
constructor(fetchPath, cheerioPath, browser) {
this.browser = browser;
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
this.baseUrl = "https://novelfire.net";
this.type = "book-board";
}
async fetchSearchResult(query = "", page = 1) {
let html;
if (query.trim() === "") {
const res = await this.fetch(`${this.baseUrl}/home`);
html = await res.text();
} else {
const res = await this.fetch(
`${this.baseUrl}/ajax/searchLive?inputContent=${encodeURIComponent(query)}`
);
const data = await res.json();
html = data.html;
}
const $ = this.cheerio.load(html);
const results = [];
$(".novel-item").each((_, el) => {
const a = $(el).find("a");
const href = a.attr("href") || "";
const title = $(el).find(".novel-title").text().trim();
const img = $(el).find("img");
const image = img.attr("data-src") || img.attr("src") || "";
const id = href.replace("https://novelfire.net/book/", "").replace(/\/$/, "");
results.push({
id,
title,
image,
sampleImageUrl: image,
tags: [],
type: "book"
});
});
return {
results,
hasNextPage: false,
page
};
}
async findChapters(bookId) {
const base = `https://novelfire.net/book/${bookId}/chapters`;
const chapters = [];
const firstRes = await this.fetch(base);
const firstHtml = await firstRes.text();
let $ = this.cheerio.load(firstHtml);
let totalPages = 1;
const pageLinks = $(".pagination a.page-link");
pageLinks.each((_, el) => {
const num = parseInt($(el).text().trim(), 10);
if (!isNaN(num) && num > totalPages) totalPages = num;
});
for (let page = 1; page <= totalPages; page++) {
const url = page === 1 ? base : `${base}?page=${page}`;
const res = await this.fetch(url);
const html = await res.text();
$ = this.cheerio.load(html);
$(".chapter-list li").each((_, el) => {
const a = $(el).find("a");
const href = a.attr("href");
const title = a.find(".chapter-title").text().trim();
const chapterNumber = parseInt(a.find(".chapter-no").text().trim(), 10);
chapters.push({
id: href,
title,
chapter: chapterNumber,
language: "en"
});
});
}
return chapters;
}
async findChapterPages(chapterId) {
const res = await this.fetch(chapterId);
const html = await res.text();
const $ = this.cheerio.load(html);
const contentDiv = $("#content");
if (!contentDiv || contentDiv.length === 0) {
return [{
type: "text",
content: "<p>Error: content not found</p>",
index: 0
}];
}
contentDiv.find("script").remove();
contentDiv.find("ins").remove();
contentDiv.find("[id^='pf-']").remove();
contentDiv.find(".ads").remove();
contentDiv.find(".adsbygoogle").remove();
contentDiv.find("div[style*='text-align:center']").remove();
contentDiv.find("div[align='center']").remove();
contentDiv.find(".nf-ads").remove();
contentDiv.find("nfne597").remove();
const paragraphs = contentDiv.find("p");
let cleanHtml = "";
paragraphs.each((_, el) => {
const p = $(el);
let text = p.text() || "";
text = text.replace(/△▼△▼△▼△/g, "");
text = text.replace(/[※]+/g, "");
text = text.replace(/\s{2,}/g, " ");
const htmlP = p.html()?.trim() || "";
const isEmpty = htmlP === "" || htmlP === "&nbsp;" || text.trim() === "";
const isAd = text.includes("Remove Ads") || text.includes("Buy no ads") || text.includes("novelfire");
if (!isEmpty && !isAd) {
if (p.text() !== text) p.text(text);
cleanHtml += $.html(p);
}
});
if (!cleanHtml.trim()) { cleanHtml = contentDiv.html(); }
return [
{
type: "text",
content: cleanHtml.trim(),
index: 0
}
];
}
}
module.exports = { novelupdates: novelfire };

170
realbooru.js Normal file
View File

@@ -0,0 +1,170 @@
class Realbooru {
baseUrl = "https://realbooru.com";
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
};
constructor(fetchPath, cheerioPath) {
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
this.type = "image-board";
}
LoadDoc(body) {
return this.cheerio.load(body);
}
async fetchSearchResult(query, page = 1, perPage = 42) {
if (!query) query = "original";
const offset = (page - 1) * perPage;
const url = `${this.baseUrl}/index.php?page=post&s=list&tags=${query}&pid=${offset}`;
try {
const response = await this.fetch(url, { headers: this.headers });
const data = await response.text();
const $ = this.cheerio.load(data);
const results = [];
$('#post-list a[id^="p"], #post-list a[href*="&s=view"], .thumb a').each((i, e) => {
const $a = $(e);
const href = $a.attr('href');
let id = null;
if (href) {
const idMatch = href.match(/&id=(\d+)/);
if (idMatch) {
id = idMatch[1];
}
}
if (!id) {
id = $a.closest('span, div').attr('id')?.replace('s', '').replace('post_', '');
}
const imageElement = $a.find('img').first();
let image = imageElement.attr('src');
if (image && !image.startsWith('http')) {
image = `https:${image}`;
}
let tags = imageElement.attr('alt')?.trim()?.split(' ').filter(tag => tag !== "");
if (!tags || tags.length === 0) {
tags = $a.attr('title')?.trim()?.split(' ').filter(tag => tag !== "");
}
if (id && image) {
results.push({
id: id,
image: image,
tags: tags,
type: 'preview'
});
}
});
const pagination = $('#paginator .pagination');
const lastPageLink = pagination.find('a[alt="last page"]');
let totalPages = 1;
if (lastPageLink.length > 0) {
const pid = lastPageLink.attr('href')?.split('pid=')[1];
totalPages = Math.ceil(parseInt(pid || "0", 10) / perPage) + 1;
} else {
const pageLinks = pagination.find('a');
if (pageLinks.length > 0) {
const lastLinkText = pageLinks.eq(-2).text();
totalPages = parseInt(lastLinkText, 10) || 1;
} else if (results.length > 0) {
totalPages = 1;
}
}
const currentPage = page;
const hasNextPage = currentPage < totalPages;
const next = hasNextPage ? (currentPage + 1) : 0;
const previous = currentPage > 1 ? (currentPage - 1) : 0;
const total = totalPages * perPage;
return { total, next, previous, pages: totalPages, page: currentPage, hasNextPage, results };
} catch (e) {
console.error("Error during Realbooru search:", e);
return { total: 0, next: 0, previous: 0, pages: 1, page: 1, hasNextPage: false, results: [] };
}
}
async fetchInfo(id) {
const url = `${this.baseUrl}/index.php?page=post&s=view&id=${id}`;
const fetchHeaders = { ...this.headers };
const response = await this.fetch(url, { headers: fetchHeaders });
const original = await response.text();
const $ = this.cheerio.load(original);
let fullImage = $('#image').attr('src') || $('video').attr('src');
const originalLink = $('div.link-list a:contains("Original image")').attr('href');
if (originalLink) {
fullImage = originalLink;
}
if (fullImage && !fullImage.startsWith('http')) {
fullImage = `https:${fullImage}`;
}
let resizedImageUrl = $('#image-holder img').attr('src');
if (resizedImageUrl && !resizedImageUrl.startsWith('http')) {
resizedImageUrl = `https:${resizedImageUrl}`;
} else if (!resizedImageUrl) {
resizedImageUrl = fullImage;
}
const tags = $('.tag-list a.tag-link').map((i, el) => $(el).text().trim()).get();
const stats = $('#stats ul');
const postedData = stats.find("li:contains('Posted:')").text().trim();
const postedDateMatch = postedData.match(/Posted: (.*?) by/);
const createdAt = postedDateMatch ? new Date(postedDateMatch[1]).getTime() : undefined;
const publishedByMatch = postedData.match(/by\s*(.*)/);
const publishedBy = publishedByMatch ? publishedByMatch[1].trim() : undefined;
const rating = stats.find("li:contains('Rating:')").text().trim().split("Rating: ")[1] || undefined;
const comments = $('#comment-list div').map((i, el) => {
const $el = $(el);
const id = $el.attr('id')?.replace('c', '');
const user = $el.find('.col1').text().trim().split("\n")[0];
const comment = $el.find('.col2').text().trim();
if (id && user && comment) {
return { id, user, comment };
}
return null;
}).get().filter(Boolean);
return {
id,
fullImage,
resizedImageUrl,
tags,
createdAt,
publishedBy,
rating,
comments
};
}
}
module.exports = { Realbooru };

145
rule34.js Normal file
View File

@@ -0,0 +1,145 @@
class Rule34 {
baseUrl = "https://rule34.xxx";
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
};
constructor(fetchPath, cheerioPath) {
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
this.type = "image-board";
}
async fetchSearchResult(query, page = 1, perPage = 42) {
if (!query) query = "alisa_mikhailovna_kujou";
const offset = (page - 1) * perPage;
const url = `${this.baseUrl}/index.php?page=post&s=list&tags=${query}&pid=${offset}`;
const response = await this.fetch(url, { headers: this.headers });
const data = await response.text();
const $ = this.cheerio.load(data);
const results = [];
$('.image-list span').each((i, e) => {
const $e = $(e);
const id = $e.attr('id')?.replace('s', '');
let image = $e.find('img').attr('src');
if (image && !image.startsWith('http')) {
image = `https:${image}`;
}
const tags = $e.find('img').attr('alt')?.trim()?.split(' ').filter(tag => tag !== "");
if (id && image) {
results.push({
id: id,
image: image,
tags: tags,
type: 'preview'
});
}
});
const pagination = $('#paginator .pagination');
const lastPageLink = pagination.find('a[alt="last page"]');
let totalPages = 1;
if (lastPageLink.length > 0) {
const pid = lastPageLink.attr('href')?.split('pid=')[1];
totalPages = Math.ceil(parseInt(pid || "0", 10) / perPage) + 1;
} else {
const pageLinks = pagination.find('a');
if (pageLinks.length > 0) {
const lastLinkText = pageLinks.eq(-2).text();
totalPages = parseInt(lastLinkText, 10) || 1;
} else if (results.length > 0) {
totalPages = 1;
}
}
const currentPage = page;
const hasNextPage = currentPage < totalPages;
const next = hasNextPage ? (currentPage + 1) : 0;
const previous = currentPage > 1 ? (currentPage - 1) : 0;
const total = totalPages * perPage;
return { total, next, previous, pages: totalPages, page: currentPage, hasNextPage, results };
}
async fetchInfo(id) {
const url = `${this.baseUrl}/index.php?page=post&s=view&id=${id}`;
const resizeCookies = {
'resize-notification': 1,
'resize-original': 1
};
const cookieString = Object.entries(resizeCookies).map(([key, value]) => `${key}=${value}`).join('; ');
const fetchHeaders = { ...this.headers };
const resizeHeaders = { ...this.headers, 'cookie': cookieString };
const [resizedResponse, nonResizedResponse] = await Promise.all([
this.fetch(url, { headers: resizeHeaders }),
this.fetch(url, { headers: fetchHeaders })
]);
const [resized, original] = await Promise.all([resizedResponse.text(), nonResizedResponse.text()]);
const $resized = this.cheerio.load(resized);
const $ = this.cheerio.load(original);
let resizedImageUrl = $resized('#image').attr('src');
if (resizedImageUrl && !resizedImageUrl.startsWith('http')) {
resizedImageUrl = `https:${resizedImageUrl}`;
}
let fullImage = $('#image').attr('src');
if (fullImage && !fullImage.startsWith('http')) {
fullImage = `https:${fullImage}`;
}
const tags = $('#image').attr('alt')?.trim()?.split(' ').filter(tag => tag !== "");
const stats = $('#stats ul');
const postedData = stats.find('li:nth-child(2)').text().trim();
const createdAt = new Date(postedData.split("Posted: ")[1].split("by")[0]).getTime();
const publishedBy = postedData.split("by")[1].trim();
const rating = stats.find("li:contains('Rating:')").text().trim().split("Rating: ")[1];
const comments = $('#comment-list div').map((i, el) => {
const $el = $(el);
const id = $el.attr('id')?.replace('c', '');
const user = $el.find('.col1').text().trim().split("\n")[0];
const comment = $el.find('.col2').text().trim();
if (id && user && comment) {
return { id, user, comment };
}
return null;
}).get().filter(Boolean);
return {
id,
fullImage,
resizedImageUrl,
tags,
createdAt,
publishedBy,
rating,
comments
};
}
}
module.exports = { Rule34 };

101
waifupics.js Normal file
View File

@@ -0,0 +1,101 @@
class WaifuPics {
baseUrl = "https://api.waifu.pics";
SFW_CATEGORIES = [
'waifu', 'neko', 'shinobu', 'megumin', 'bully', 'cuddle', 'cry', 'hug', 'awoo',
'kiss', 'lick', 'pat', 'smug', 'bonk', 'yeet', 'blush', 'smile', 'wave',
'highfive', 'handhold', 'nom', 'bite', 'glomp', 'slap', 'kill', 'kick',
'happy', 'wink', 'poke', 'dance', 'cringe'
];
constructor(fetchPath, cheerioPath) {
this.fetch = require(fetchPath);
this.type = "image-board";
}
async fetchSearchResult(query, page = 1, perPage = 42) {
if (!query) query = "waifu";
const category = query.trim().split(' ')[0];
if (!this.SFW_CATEGORIES.includes(category)) {
console.warn(`[WaifuPics] Category '${category}' not supported.`);
return {
total: 0,
next: 0,
previous: 0,
pages: 1,
page: 1,
hasNextPage: false,
results: []
};
}
try {
const response = await this.fetch(`${this.baseUrl}/many/sfw/${category}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ exclude: [] }),
});
if (!response.ok) {
throw new Error(`API returned ${response.status}: ${await response.text()}`);
}
const data = await response.json();
const results = data.files.map((url, index) => {
const id = url.substring(url.lastIndexOf('/') + 1) || `${category}-${index}`;
const uniqueId = `${page}-${id}`;
return {
id: uniqueId,
image: url,
tags: [category],
type: 'preview'
};
});
return {
total: 30,
next: page + 1,
previous: page > 1 ? page - 1 : 0,
pages: page + 1,
page: page,
hasNextPage: true,
results: results
};
} catch (error) {
console.error(`[WaifuPics] Error fetching images:`, error);
return {
total: 0,
next: 0,
previous: 0,
pages: 1,
page: 1,
hasNextPage: false,
results: []
};
}
}
async fetchInfo(id) {
console.log(`[WaifuPics] fetchInfo called for ${id}, but this API only provides direct URLs.`);
return {
id: id,
fullImage: `https://i.waifu.pics/${id}`,
resizedImageUrl: `https://i.waifu.pics/${id}`,
tags: [],
createdAt: null,
publishedBy: 'Waifu.pics',
rating: 'sfw',
comments: []
};
}
}
module.exports = { WaifuPics };