Skip to content
Snippets Groups Projects
openlibrary.go 3.65 KiB
Newer Older
package liber

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

type openLibraryRefiner struct{}

func (r *openLibraryRefiner) Name() string {
	return "openlibrary"
}

type olSearchResults struct {
	Docs []olSearchResult `json:"docs"`
}

type olSearchResult struct {
	Title  string `json:"title"`
	Author string `json:"author_name"`
	Date   string `json:"first_publish_year"`
	OLKey  string `json:"key"`
}

type olAuthor struct {
	Name string `json:"name"`
}

type olPublisher olAuthor

type olExcerpt struct {
	Text string `json:"text"`
}

type olMetadata struct {
	Key         string              `json:"key"`
	Title       string              `json:"title"`
	Identifiers map[string][]string `json:"identifiers"`
	Authors     []*olAuthor         `json:"authors"`
	Excerpts    []*olExcerpt        `json:"excerpts"`
	Publishers  []*olPublisher      `json:"publishers"`
	PublishDate string              `json:"publish_date"`
	Cover       map[string]string   `json:"cover"`
	Language    []string
}

var dateRx = regexp.MustCompile(`(\d{4})`)

func (m *olMetadata) toMetadata() *Metadata {
	out := &Metadata{
		Title:    m.Title,
		Language: m.Language,
	}
	for _, a := range m.Authors {
		out.Creator = append(out.Creator, a.Name)
	}
	for _, p := range m.Publishers {
		out.Publisher = append(out.Publisher, p.Name)
	}
	for _, attr := range []string{"isbn_10", "isbn_13"} {
		if isbn, ok := m.Identifiers[attr]; ok {
			out.ISBN = append(out.ISBN, isbn...)
		}
	}
	if match := dateRx.FindString(m.PublishDate); match != "" {
		out.Date = match
	}
	for _, e := range m.Excerpts {
		out.Description = e.Text
		break
	}
	return out
}

var olSemaphore = make(chan bool, 3)

func openlibraryQuery(uri string, out interface{}) error {
	olSemaphore <- true
	defer func() {
		<-olSemaphore
	}()

	resp, err := http.Get(uri)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return fmt.Errorf("HTTP status %s", resp.Status)
	}
	if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
		return err
	}
	return nil
}

// Return book information given an ID.
func (r *openLibraryRefiner) queryBibKeys(bibkeys []string) ([]*Metadata, error) {
	values := make(url.Values)
	values.Set("bibkeys", strings.Join(bibkeys, ","))
	values.Set("jscmd", "data")
	values.Set("format", "json")
	uri := fmt.Sprintf("https://openlibrary.org/api/books?%s", values.Encode())

	var result map[string]*olMetadata
	if err := openlibraryQuery(uri, &result); err != nil {
		return nil, err
	}

	var out []*Metadata
	for _, m := range result {
		out = append(out, m.toMetadata())
	}
	return out, nil
}

// Find a book given one or more ISBN numbers.
func (r *openLibraryRefiner) queryISBN(isbn []string) ([]*Metadata, error) {
	var bibkeys []string
	for _, i := range isbn {
		bibkeys = append(bibkeys, fmt.Sprintf("ISBN:%s", i))
	}
	return r.queryBibKeys(bibkeys)
}

// Find a book using title/author.
func (r *openLibraryRefiner) queryTitleAuthor(m *Metadata) ([]*Metadata, error) {
	values := make(url.Values)
	values.Set("title", m.Title)
	if len(m.Creator) > 0 {
		values.Set("author", m.Creator[0])
	}
	uri := fmt.Sprintf("https://openlibrary.org/search.json?%s", values.Encode())

	var result olSearchResults
	if err := openlibraryQuery(uri, &result); err != nil {
		return nil, err
	}

	var bibkeys []string
	for _, doc := range result.Docs {
		bibkeys = append(bibkeys, fmt.Sprintf("OLID:%s", doc.OLKey))
	}
	return r.queryBibKeys(bibkeys)
}

func (r *openLibraryRefiner) Lookup(m *Metadata) ([]*Metadata, error) {
	if len(m.ISBN) > 0 {
		return r.queryISBN(m.ISBN)
	}
	return r.queryTitleAuthor(m)
}

func (r *openLibraryRefiner) GetBookCover(m *Metadata) ([]byte, error) {
	return nil, nil
}