Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
eBooks from my Goodreads to-read list that are available to check out from OverDrive, sorted by average rating on Goodreads
#!/usr/bin/env ruby
require 'json'
require 'logger'
require 'net/http'
require 'addressable/template'
require 'mechanize'
require 'nokogiri'
GOODREADS_ACCESS_KEY_ID = '<key>'
GOODREADS_USER_ID = '<user_id>'
LOGGER = Logger.new(STDOUT)
LOGGER.level = Logger::DEBUG
class GoodreadsClient
SHELF_URL_TEMPLATE = Addressable::Template.new('https://www.goodreads.com/review/list{?format,key,id,page,per_page,shelf,v}')
def initialize(key:)
@key = key
end
def to_read_books(user_id)
# GoodreadsResponse/reviews/review/book
review_nodes = []
5.times do |page|
doc = Nokogiri::XML(Net::HTTP.get(shelf_uri(page: page + 1, per_page: 200, shelf: 'to-read', user_id: user_id)))
nodes = doc.css('review')
break if nodes.empty?
review_nodes += nodes
end
review_nodes.map { |review_node|
{
'author' => review_node.css('book > authors > author > name').text,
'average_rating' => review_node.css('book > average_rating').text,
'title' => review_node.css('book > title').text,
}
}
end
private
def shelf_uri(page: nil, per_page: nil, shelf:, user_id:)
params = {
format: 'xml',
key: @key,
id: user_id,
shelf: shelf,
v: '2',
}
params.merge!(page: page) if page
params.merge!(per_page: per_page) if per_page
SHELF_URL_TEMPLATE.expand(params)
end
end
def available_on_overdrive?(book)
title_match = book['title'].sub(/:.*\z/, '').sub(/\([^)]+\)/, '')
agent = Mechanize.new
agent.get('http://www.plsinfo.org/ebooks')
agent.page.link_with(text: 'Browse OverDrive').click
agent.page.link_with(text: 'Advanced Search').click
search_form = agent.page.forms_with(action: 'BANGSearch.dll').first
search_form.Title = book['title']
search_form.Creator = book['author']
agent.submit(search_form)
agent.page.search('//section[@id="searchResultsContainer"]').any? { |search_results|
search_results.css('.tc-title').any? { |title_node| title_node.text.include? title_match }
}
rescue Mechanize::ResponseCodeError => e
LOGGER.warn(e.message)
false
end
def available_ebooks(books)
books.select(&method(:available_on_overdrive?))
end
client = GoodreadsClient.new(key: GOODREADS_ACCESS_KEY_ID)
books = available_ebooks(client.to_read_books(GOODREADS_USER_ID)).sort { |a, b| a['average_rating'].to_f <=> b['average_rating'].to_f }
puts books.map { |b| b['title'] }.join("\n")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.