Skip to content

Instantly share code, notes, and snippets.

@MikaelSmith
Last active June 11, 2019 23:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MikaelSmith/ce008b2d2d4a00c1f20105fe7d5d6855 to your computer and use it in GitHub Desktop.
Save MikaelSmith/ce008b2d2d4a00c1f20105fe7d5d6855 to your computer and use it in GitHub Desktop.
Wash External Plugin Example
#!/usr/bin/env ruby
# A script for exploring your Goodreads library with Wash
#
# Requirements:
# - `gem install oauth nokogiri --user-install`
# Note that BitBar runs without shell setup, so it will likely use the system ruby.
# - A developer key/secret from https://www.goodreads.com/api/keys
# stored in GOODREADS_KEY, GOODREADS_SECRET environment variables.
require 'json'
require 'oauth'
require 'nokogiri'
# Step 1: figure out authentication
consumer = OAuth::Consumer.new(ENV['GOODREADS_KEY'],
ENV['GOODREADS_SECRET'],
site: 'https://www.goodreads.com')
# If we don't have an access token, request one.
# TODO: module-specific configuration of where to cache it.
# Will need to move this to init, and pass through as plugin state.
TOKEN_CACHE = File.join(Dir.home, '.puppetlabs', 'wash', 'goodreads.token')
begin
token_text = File.read(TOKEN_CACHE)
obj = JSON.parse(token_text)
@access_token = OAuth::AccessToken.new(consumer, obj['token'], obj['secret'])
rescue StandardError
request_token = consumer.get_request_token
puts "Please authorize at #{request_token.authorize_url}, then press enter once done"
gets
@access_token = request_token.get_access_token
File.write(TOKEN_CACHE, { token: @access_token.token, secret: @access_token.secret }.to_json)
end
# Returns an XML response body on OK status, else raises an error
def get(path)
response = @access_token.get(path)
if response.code_type != Net::HTTPOK
raise "#{response.code}: #{response.body}"
end
return Nokogiri::XML(response.body)
end
# Converts an XML book format from the goodreads API to a json representation of a book
def book_to_json(doc)
result = {}
doc.elements.each do |node|
unless node.text.empty?
result[node.name] = node.text
end
# TODO: handle things with more structure
# Handle HTML text: https://stackoverflow.com/questions/2505104/html-to-plain-text-with-ruby
end
return result
end
method, *rest = ARGV
case method
when 'init'
# Step 2: provide a basic init method
doc = get('/api/auth_user')
userid = doc.at('user').attribute('id')
puts ({ name: 'goodreads', methods: ['list'], state: JSON.dump(userid: userid) }).to_json
when 'list'
path, state = rest
state = JSON.parse(state) unless state.nil? || state.empty?
children = []
if path == '/goodreads'
# Step 3: list the plugin root
doc = get('/shelf/list.xml')
doc.search('shelves user_shelf').each do |bookshelf|
name = bookshelf.at('name').content
meta = {}
bookshelf.elements.each do |node|
next if node.name == 'name'
meta[node.name] = node.content
end
# Step 4: add state we can use to keep track of what we're interacting with. TODO: use Oj for object serialization?
count = bookshelf.at('book_count').content.to_i
shelf = { type: 'shelf', name: name, userid: state['userid'], count: count }
children << { name: name, methods: ['list'], attributes: {meta: meta}, state: JSON.dump(shelf) }
end
elsif state['type'] == 'shelf' && state['count'] > 0
# Step 5: list books on a shelf. TODO: Iterate over paginated results based on shelf count.
doc = get("/review/list/#{state['userid']}.xml?v=2&shelf=#{state['name']}&per_page=10")
doc.search('reviews review').each do |review|
name = review.at('book title').content
meta = book_to_json(review.at('book'))
children << { name: name, methods: [], attributes: {meta: meta} }
end
end
puts children.sort_by {|obj| obj['name']}.to_json
else
STDERR.puts "Unknown method invoked"
exit 1
end
@MikaelSmith
Copy link
Author

@MikaelSmith
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment