UNPKG

bookgrabs

Version:

Interactive CLI tool for LibGen ebook searches and downloads with batch processing support

141 lines (117 loc) 4.53 kB
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; } }