import fetch , { Response } from 'node-fetch' ; import { JSDOM } from 'jsdom' ; export type Entry = { id : string ; author : string ; title : string ; publisher : string ; year : string ; pages : string ; language : string ; size : string ; extension : string ; mirror : string ; } ; const Columns = { id : 1 , author : 2 , title : 3 , publisher : 4 , year : 5 , pages : 6 , language : 7 , size : 8 , extension : 9 , mirror : 10 } ; export const CssSelectors = { tableContainer : '.c tbody' , row : '.c tbody tr' , downloadURL : '#info h2 a' , cellSelector : '{tableContainerSelector} tr:nth-child({row}) td:nth-child({col})' } ; const sleep = ( ms ) : Promise < void > => ( new Promise ( ( resolve ) => setTimeout ( ( ) => resolve ( ) , ms ) ) ) ; const constructURL = ( searchReqPattern , mirror , query , pageNumber , pageSize ) : string => ( searchReqPattern . replace ( '{mirror}' , mirror ) . replace ( '{query}' , query ) . replace ( '{pageNumber}' , pageNumber . toString ( ) ) . replace ( '{pageSize}' , pageSize . toString ( ) ) ) ; const getResponse = async ( URL ) : Promise < Response | null > => { try { const response = await fetch ( URL ) ; return ( response . status == 200 && ! response . redirected ) ? response : null ; } catch ( error ) { return null ; } } ; const getDocument = async ( URL ) : Promise < HTMLDocument | null > => { let response = await getResponse ( URL ) ; if ( response == null ) { return null ; } const plainText = await response . text ( ) ; return new JSDOM ( plainText ) . window . document ; } ; const getText = ( document , selector ) : string => ( document . querySelector ( selector ) ?. textContent || ' ' ) ; const getCellSelector = ( row , col ) : string => ( CssSelectors . cellSelector . replace ( '{tableContainerSelector}' , CssSelectors . tableContainer ) . replace ( '{row}' , row . toString ( ) ) . replace ( '{col}' , col . toString ( ) ) ) ; const getEntrySelectors = ( row ) : Entry => ( { id : getCellSelector ( row , Columns . id ) , author : getCellSelector ( row , Columns . author ) , title : getCellSelector ( row , Columns . title ) , publisher : getCellSelector ( row , Columns . publisher ) , year : getCellSelector ( row , Columns . year ) , pages : getCellSelector ( row , Columns . pages ) , language : getCellSelector ( row , Columns . language ) , size : getCellSelector ( row , Columns . size ) , extension : getCellSelector ( row , Columns . extension ) , mirror : `${ getCellSelector ( row , Columns . mirror ) } a` } ) ; const clearChineseChars = ( str ) : string => ( str . replace ( /[\u4e00-\u9fff\u3400-\u4dff\uf900-\ufaff]/g , '' ) ) ; const getEntryData = ( document , selector ) : Entry => ( { id : getText ( document , selector . id ) , author : getText ( document , selector . author ) , title : clearChineseChars ( getText ( document , selector . title ) ) , publisher : getText ( document , selector . publisher ) , year : getText ( document , selector . year ) , pages : getText ( document , selector . pages ) , language : getText ( document , selector . language ) , size : getText ( document , selector . size ) , extension : getText ( document , selector . extension ) , mirror : document . querySelector ( selector . mirror ) ?. getAttribute ( 'href' ) || ' ' } ) ; export const getEntries = ( document ) : Entry [ ] => { const entries = [ ] ; const entryLength = document . querySelectorAll ( CssSelectors . row ) . length ; for ( let i = 1 ; i < entryLength ; i ++ ) { const selectors = getEntrySelectors ( i + 1 ) ; entries . push ( getEntryData ( document , selectors ) ) ; } return entries ; } ; export const doRequest = ( reqURL , onErr , errTolarance , delay ) : Promise < Response | null > => ( new Promise ( async ( resolve ) => { let errCount = 0 ; let response = null ; while ( errCount < errTolarance ) { response = await getResponse ( reqURL ) ; if ( response == null ) { errCount ++ ; onErr ( errCount , errTolarance ) ; await sleep ( delay ) ; } else { resolve ( response ) ; break ; } } resolve ( null ) ; } ) ) ; export const findMirror = async ( mirrorList ) : Promise < string | null > => { for ( let i = 0 ; i < mirrorList . length ; i ++ ) { const response = await getResponse ( mirrorList [ i ] ) ; if ( response != null ) return mirrorList [ i ] ; } return null ; } ; export const isPageExist = async ( searchReqPattern , mirror , query , pageNumber , pageSize ) : Promise < boolean > => { const searchURL = constructURL ( searchReqPattern , mirror , query , pageNumber , pageSize ) ; const document = await getDocument ( searchURL ) ; return ( document && document . querySelectorAll ( CssSelectors . row ) . length > 1 ) ? true : false ; } ; export const search = async ( searchReqPattern , mirror , query , pageNumber , pageSize ) : Promise < Entry [ ] | null > => { const searchURL = constructURL ( searchReqPattern , mirror , query , pageNumber , pageSize ) ; const document = await getDocument ( searchURL ) ; if ( document == null ) return null ; let entries = getEntries ( document ) ; return entries ; } ;