package search import ( "crypto/tls" "errors" "fmt" "io" "net/http" "os" "time" log "github.com/sirupsen/logrus" "reichard.io/antholume/metadata" ) const userAgent string = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" type Cadence string const ( CADENCE_TOP_YEAR Cadence = "y" CADENCE_TOP_MONTH Cadence = "m" ) type Source string const ( SOURCE_ANNAS_ARCHIVE Source = "Annas Archive" SOURCE_LIBGEN_FICTION Source = "LibGen Fiction" SOURCE_LIBGEN_NON_FICTION Source = "LibGen Non-fiction" ) type SearchItem struct { ID string Title string Author string Language string Series string FileType string FileSize string UploadDate string } type searchFunc func(query string) (searchResults []SearchItem, err error) type downloadFunc func(md5 string, source Source) (downloadURL []string, err error) var searchDefs = map[Source]searchFunc{ SOURCE_ANNAS_ARCHIVE: searchAnnasArchive, SOURCE_LIBGEN_FICTION: searchLibGenFiction, SOURCE_LIBGEN_NON_FICTION: searchLibGenNonFiction, } var downloadFuncs = []downloadFunc{ getLibGenDownloadURL, getLibraryDownloadURL, } func SearchBook(query string, source Source) ([]SearchItem, error) { searchFunc, found := searchDefs[source] if !found { return nil, fmt.Errorf("invalid source: %s", source) } log.Debug("Source: ", source) return searchFunc(query) } func SaveBook(md5 string, source Source, progressFunc func(float32)) (string, *metadata.MetadataInfo, error) { for _, f := range downloadFuncs { downloadURLs, err := f(md5, source) if err != nil { log.Error("failed to acquire download urls") continue } for _, bookURL := range downloadURLs { // Download File log.Info("Downloading Book: ", bookURL) fileName, err := downloadBook(bookURL, progressFunc) if err != nil { log.Error("Book URL API Failure: ", err) continue } // Get Metadata metadata, err := metadata.GetMetadata(fileName) if err != nil { log.Error("Book Metadata Failure: ", err) continue } return fileName, metadata, nil } } return "", nil, errors.New("failed to download book") } func getPage(page string) (io.ReadCloser, error) { log.Debug("URL: ", page) // Set 10s Timeout client := http.Client{Timeout: 10 * time.Second} // Start Request req, err := http.NewRequest("GET", page, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", userAgent) // Do Request resp, err := client.Do(req) if err != nil { return nil, err } // Return Body return resp.Body, err } func downloadBook(bookURL string, progressFunc func(float32)) (string, error) { log.Debug("URL: ", bookURL) // Allow Insecure client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } // Start Request req, err := http.NewRequest("GET", bookURL, nil) if err != nil { return "", err } req.Header.Set("User-Agent", userAgent) // Perform API Request resp, err := client.Do(req) if err != nil { return "", err } // Create File tempFile, err := os.CreateTemp("", "book") if err != nil { log.Error("File Create Error: ", err) return "", fmt.Errorf("failed to create temp file: %w", err) } defer tempFile.Close() // Copy File to Disk log.Info("Saving Book") counter := &writeCounter{Total: resp.ContentLength, ProgressFunction: progressFunc} _, err = io.Copy(tempFile, io.TeeReader(resp.Body, counter)) if err != nil { os.Remove(tempFile.Name()) log.Error("File Copy Error: ", err) return "", fmt.Errorf("failed to copy response to temp file: %w", err) } return tempFile.Name(), nil }