bookgrabs
Version:
Interactive CLI tool for LibGen ebook searches and downloads with batch processing support
141 lines (117 loc) • 4.53 kB
JavaScript
import axios from 'axios';
import * as cheerio from 'cheerio';
import { loadBlacklist, filterBlacklistedResults } from './blacklist.js';
import { filterByFormat, filterByLanguage } from './utils.js';
// Global abort controller for cancelling operations
let globalAbortController = null;
// Enhanced searchLibGen with LibGen search parameters
export async function searchLibGen(query, options = {}, signal = null) {
// Default options
const defaultOptions = {
maxResults: 25,
topics: ['l', 'f'], // libgen + fiction by default
objects: ['f', 'e'], // files + editions
columns: ['t', 'a', 's', 'y', 'p', 'i'], // title, author, series, year, publisher, isbn
resultsPerPage: 100,
fileStatus: 'all', // all, sorted, uns
language: 'eng' // English by default (ISO 639-3 code)
};
const searchOptions = { ...defaultOptions, ...options };
// Use provided signal or create a new one
if (!signal) {
globalAbortController = new AbortController();
signal = globalAbortController.signal;
}
// Load blacklist
const blacklist = await loadBlacklist();
// Build URL with parameters
let searchUrl = `https://libgen.bz/index.php?req=${encodeURIComponent(query)}`;
// Add columns
searchOptions.columns.forEach(col => {
searchUrl += `&columns[]=${col}`;
});
// Add objects
searchOptions.objects.forEach(obj => {
searchUrl += `&objects[]=${obj}`;
});
// Add topics
searchOptions.topics.forEach(topic => {
searchUrl += `&topics[]=${topic}`;
});
// Add other parameters
searchUrl += `&res=${searchOptions.resultsPerPage}`;
searchUrl += `&filesuns=${searchOptions.fileStatus}`;
// Add language filter if specified (may not work reliably on LibGen)
if (searchOptions.language) {
searchUrl += `&lang=${searchOptions.language}`;
}
try {
// Check if operation was cancelled
if (signal.aborted) {
return [];
}
const response = await axios.get(searchUrl, {
timeout: 30000, // 30 second timeout
signal: signal, // Allow cancellation
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'
}
});
// Check if operation was cancelled after request
if (signal.aborted) {
return [];
}
const $ = cheerio.load(response.data);
const results = [];
$('#tablelibgen tbody tr').each((i, row) => {
// Don't limit results during parsing - let language filtering handle it later
const cells = $(row).find('td');
if (cells.length > 0) {
const title = $(cells[0]).text().trim().split('\n')[0];
const author = $(cells[1]).text().trim() || 'Unknown';
const year = $(cells[3]).text().trim();
const language = $(cells[4]).text().trim() || 'Unknown';
const ext = $(cells[7]).text().trim();
const md5Mirrors = [];
$(cells[8]).find('a').each((j, link) => {
const href = $(link).attr('href');
if (href && href.includes('md5=')) {
const md5 = href.split('md5=')[1].split('&')[0];
md5Mirrors.push(md5);
}
});
if (md5Mirrors.length > 0) {
const result = { title, author, year, language, ext, md5: md5Mirrors[0] };
results.push(result);
}
}
});
// Apply client-side filtering: blacklist first, then format, then language
const blacklistFiltered = filterBlacklistedResults(results, blacklist);
const formatFiltered = filterByFormat(blacklistFiltered);
const languageFiltered = filterByLanguage(formatFiltered, searchOptions.language);
// Apply maxResults limit after filtering
const limitedResults = languageFiltered.slice(0, searchOptions.maxResults);
if (blacklist.size > 0) {
const filteredCount = results.length - blacklistFiltered.length;
if (filteredCount > 0) {
console.log(`Filtered out ${filteredCount} blacklisted result(s)`);
}
}
return limitedResults;
} catch (error) {
if (error.name === 'AbortError' || error.name === 'CanceledError') {
console.log('Search cancelled');
return [];
}
console.error('Error fetching or parsing search results:', error.message);
return [];
}
}
// Function to cancel all active searches
export function cancelAllSearches() {
if (globalAbortController) {
globalAbortController.abort();
globalAbortController = null;
}
}