Added books

This commit is contained in:
Óscar M. Lage 2024-10-10 14:13:40 +02:00
parent afb40f9a2d
commit 6a6217ae31
5 changed files with 299 additions and 13 deletions

View File

@ -0,0 +1,188 @@
package books
import (
"encoding/json"
"fmt"
"hugo-medialog/utils"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
)
const googleBooksAPIBaseURL = "https://www.googleapis.com/books/v1/volumes?q=intitle:%s&key=%s"
// Function to search for a book by title
func SearchBookByTitle(title string, book *Book) error {
apiKey := os.Getenv("GOOGLE_BOOKS_API_KEY")
url := fmt.Sprintf(googleBooksAPIBaseURL, url.QueryEscape(title), apiKey)
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
items := result["items"].([]interface{})
if len(items) == 0 {
return fmt.Errorf("Book %s not found", title)
}
// Get the ISBN from any of the results
isbn, err := extractISBNFromResults(items)
if err != nil {
fmt.Printf("ISBN not found\n", isbn)
} else {
book.ISBN = isbn
}
bookInfo := items[0].(map[string]interface{})["volumeInfo"].(map[string]interface{})
book.ID = items[0].(map[string]interface{})["id"].(string)
book.Title = bookInfo["title"].(string)
book.Author = bookInfo["authors"].([]interface{})[0].(string)
book.Link = bookInfo["infoLink"].(string)
if pageCount, ok := bookInfo["pageCount"].(float64); ok {
book.Pages = int(pageCount)
}
if publishedDate, ok := bookInfo["publishedDate"].(string); ok {
if len(publishedDate) >= 4 {
book.Year, _ = strconv.Atoi(publishedDate[:4])
}
}
book.Image = getBookCoverByTitle(title)
if err != nil {
fmt.Printf("Image not found: %s\n", err)
}
return nil
}
func extractISBNFromResults(items []interface{}) (string, error) {
// Loop over all the results and returns first ISBN found (ISBN_13 or
// ISBN_10)
for _, item := range items {
bookInfo := item.(map[string]interface{})["volumeInfo"].(map[string]interface{})
industryIdentifiers, ok := bookInfo["industryIdentifiers"].([]interface{})
if !ok {
continue
}
var isbn string
for _, identifier := range industryIdentifiers {
id := identifier.(map[string]interface{})
// Primero intentamos con ISBN_13
if id["type"] == "ISBN_13" {
isbn = id["identifier"].(string)
return isbn, nil
} else if id["type"] == "ISBN_10" && isbn == "" {
// Si no hay ISBN_13, intentamos con ISBN_10
isbn = id["identifier"].(string)
return isbn, nil
}
}
}
return "", fmt.Errorf("no ISBN found in any result")
}
func getBookCoverByISBN(isbn string) string {
openLibraryURL := fmt.Sprintf("https://covers.openlibrary.org/b/isbn/%s-L.jpg", isbn)
if checkImageExists(openLibraryURL) {
return openLibraryURL
}
amazonURL := fmt.Sprintf("https://images-na.ssl-images-amazon.com/images/P/%s.jpg", isbn)
if checkImageExists(amazonURL) {
return amazonURL
}
return ""
}
func getBookCoverByTitle(title string) string {
apiKey := os.Getenv("GOOGLE_CUSTOM_SEARCH_ENGINE_API_KEY")
cx := os.Getenv("GOOGLE_CX")
searchURL := fmt.Sprintf("https://www.googleapis.com/customsearch/v1?q=%s+book+cover&key=%s&searchType=image&num=1&cx=%s", url.QueryEscape(title), apiKey, cx)
resp, err := http.Get(searchURL)
if err != nil {
fmt.Printf("Error fetching cover from Google Images API: %s\n", err)
return "default-cover.jpg"
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("Error fetching cover, status code: %d\n", resp.StatusCode)
return "default-cover.jpg"
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
fmt.Printf("Error decoding JSON response: %s\n", err)
return "default-cover.jpg"
}
if items, ok := result["items"].([]interface{}); ok && len(items) > 0 {
if link, ok := items[0].(map[string]interface{})["link"].(string); ok {
return link
}
}
return "default-cover.jpg"
}
func checkImageExists(imageURL string) bool {
resp, err := http.Head(imageURL)
if err != nil || resp.StatusCode != http.StatusOK {
return false
}
return true
}
func DownloadImage(url, slug string) error {
imageDir := filepath.Join(os.Getenv("MARKDOWN_OUTPUT_BOOKS_DIR"), os.Getenv("IMAGES_OUTPUT_DIR"))
if err := utils.CreateDirIfNotExists(imageDir); err != nil {
return err
}
filename := fmt.Sprintf("%s.jpg", slug)
filePath := filepath.Join(imageDir, filename)
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("error downloading image: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("non-200 response while downloading image: %d", resp.StatusCode)
}
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("error creating image file: %w", err)
}
defer file.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error reading image data: %w", err)
}
_, err = file.Write(body)
if err != nil {
return fmt.Errorf("error writing image data: %w", err)
}
fmt.Printf(" - Image saved successfully at: %s\n", filePath)
return nil
}

View File

@ -1 +1,86 @@
package books
import (
"fmt"
"hugo-medialog/utils"
"os"
"path/filepath"
"gopkg.in/yaml.v2"
)
func LoadBooks() ([]Book, error) {
booksFile := os.Getenv("OBSIDIAN_BOOKS_FILE")
fileData, err := os.ReadFile(booksFile)
if err != nil {
return nil, err
}
var books []Book
err = yaml.Unmarshal(fileData, &books)
if err != nil {
return nil, err
}
return books, nil
}
func ProcessBooks(books []Book) error {
utils.Sep()
for _, book := range books {
fmt.Printf("Title: %s, Author: %s, ID: %s\n", book.Title, book.Author, book.ID)
// If we dont have ID, search movie by Title and get the ID
if book.ID == "" {
err := SearchBookByTitle(book.Title, &book)
if err != nil {
fmt.Printf("Error searching book by title %s: %s\n", book.Title, err)
continue
}
}
book.Slug = utils.Sluggify(book.Title)
// Now we need to get the image
imageURL := getBookCoverByTitle(book.Title)
if imageURL != "" {
err := DownloadImage(imageURL, book.Slug)
if err != nil {
fmt.Printf("Error downloading %s: %s\n", imageURL, err)
}
}
book.Image = fmt.Sprintf("%s.jpg", book.Slug)
err := generateBookMarkdown(book)
if err != nil {
return err
}
utils.Sep()
}
return nil
}
func generateBookMarkdown(book Book) error {
templatePath := filepath.Join(os.Getenv("TEMPLATES_DIR"), "book.md.tpl")
outputDir := os.Getenv("MARKDOWN_OUTPUT_BOOKS_DIR")
if err := utils.CreateDirIfNotExists(outputDir); err != nil {
return err
}
outputPath := filepath.Join(outputDir, fmt.Sprintf("%s.md", book.Slug))
data := map[string]interface{}{
"Title": book.Title,
"Author": book.Author,
"Link": book.Link,
"Subtitle": book.Year,
"Year": book.Year,
"Rate": book.Rate,
"Pages": book.Pages,
"Progress": book.Progress,
"Image": book.Image,
"Date": book.Date,
"Tags": "reading",
}
return utils.GenerateMarkdown(templatePath, outputPath, data)
}

View File

@ -2,14 +2,16 @@ package books
type Book struct {
Title string `yaml:"title"`
Slug string `yaml:"slug"`
Author string `yaml:"author"`
Link string `yaml:"link"`
Year int `yaml:"year"`
ID string `yaml:"id"`
ISBN string `yaml:"isbn"`
Rate float64 `yaml:"rate"`
Pages int `yaml:"pages"`
Progress string `yaml:"progress"`
Image string `yaml:"image"`
Poster string `yaml:"poster-image"`
Background string `yaml:"background-image"`
Date string `yaml:"date"`
Tags []string
}

12
main.go
View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"hugo-medialog/internal/books"
"hugo-medialog/internal/movies"
"hugo-medialog/internal/series"
"hugo-medialog/utils"
@ -33,4 +34,15 @@ func main() {
fmt.Printf("Error processing series: %v\n", err)
}
// Books
booksList, err := books.LoadBooks()
if err != nil {
fmt.Printf("Error reading books file: %v\n", err)
return
}
err = books.ProcessBooks(booksList)
if err != nil {
fmt.Printf("Error processing books: %v\n", err)
}
}

View File

@ -5,10 +5,9 @@ subtitle: "{{ .Subtitle }}"
author: "{{ .Author }}"
year: {{ .Year }}
rate: {{ .Rate }}
pages: {{ .Pages }}
progress: {{ .Progress }}
image: {{ .Image }}
poster-image: {{ .PosterImage }}
background-image: {{ .BackgroundImage }}
date: {{ .Date }}
draft: false
tags: {{ .Tags }}