Writing an Exist importer for Day One - Part 3 plugging in
Jun 12, 2016Well, it’s taken an embarrassingly long time to finish writing about it, but here’s the final post on my Exist plugin for Slogger.
If you’ve not seen the other sections, you’ll want to start by reading Part 1 and Part 2, as this article just pulls the parts of the previous two together and into a Slogger plugin.
Writing plugins is pretty straightforward, and there’s a
template
to help too. I started turning all my hackery from the previous two parts in
this series into a plugin by copying that into the plugins directory, and
picking a name for it. I close existlogger
for the filename, and
ExistLogger
for the class name.
I then populated the config
block in the class with the constants needed to
authenticate with Exist.
config = {
'description' => [
'Exist.io Logger',
'https://exist.io'
],
'redirect_uri' => "https://hackerific.net/slogger/exist/",
'client_id' => 'd26998cab3eaa34d5aca',
'client_secret' => 'e854ed9aa67b1b4680734035dba6aa475b621a4e',
'tags' => '#personal #fitness'
}
Next, I split my previous proof of concept script into a few more subs and pasted them into the plugin class, and then set about trying to work out how to plumb in the oAuth code.
I found that when Slogger first runs, it creates a config section containing the data specified in the config hash at the top of the plugin, so if you want to tell whether a user has actually set a specific variable, the easiest thing to do with wait until after the first run. Then once that run has complete you’ll have access to the config hash on the next run, and will be able to tell if there’s an oAuth access token set, so that’s what I did. It took a while for me to work out how to do it, but it seems to work.
I moved the template out of the DATA section of the script and into a heredoc, like this:
template = <<END
## Exist data for <%= username %>
<% attributes.each do |attr| %>
### <%= attr.label %>
| Item | Value | Source |
|------|-------|--------|
<% attr.items.each do |item| -%>
| <%= item.label %> | <%= item.value %> | <%= item.service %> |
<% end -%>
<% end %>
Data grabbed at: <%= local_time %>
END
Because it’s not possible to unambiguously use the DATA constant in
multi-script projects. This is a little disappointing, but no big deal. I
considered reading the current file and finding the __END__
marker, but
didn’t think the added complexity was worth it.
Finally, I tested the plugin a few times to check it works OK, and surprise! it seems too, so that’s cool.
Here’s the current code:
=begin
Plugin: Exist Logger
Description: Logs your Exist data
Author: [Matt Foster](https://hackerific.net)
Configuration:
redirect_uri: 'oAuth2 redirect URI'
client_id: 'oAuth2 client ID'
client_secret: 'oAuth2 client secret'
access_token: 'oAuth2 access token'
tags: '#personal #fitness'
Notes:
- Downloads your Exist data for today, and saves all attributes.
=end
config = {
'description' => [
'Exist.io Logger',
'https://exist.io'
],
'redirect_uri' => "https://hackerific.net/slogger/exist/",
'client_id' => 'd26998cab3eaa34d5aca',
'client_secret' => 'e854ed9aa67b1b4680734035dba6aa475b621a4e',
'tags' => '#personal #fitness'
}
$slog.register_plugin({ 'class' => 'ExistLogger', 'config' => config })
require 'rest-client'
require 'json'
class ExistLogger < Slogger
# @config is available with all of the keys defined in "config" above
# @timespan and @dayonepath are also available
# returns: nothing
def do_log
if @config.key?(self.class.name)
@exist_config = @config[self.class.name]
else
@log.warn("Exist has not been configured. Please authenticate by running on the command line.")
return
end
# We need to have seen one run to have empty config, then we need to get a token
if ! @exist_config.key?('access_token')
return user_auth
end
template = <<END
## Exist data for <%= username %>
<% attributes.each do |attr| %>
### <%= attr.label %>
| Item | Value | Source |
|------|-------|--------|
<% attr.items.each do |item| -%>
| <%= item.label %> | <%= item.value %> | <%= item.service %> |
<% end -%>
<% end %>
Data grabbed at: <%= local_time %>
END
data = grab_today
entry = erb(template, data)
@log.info("Logging Exist data")
tags = config['tags'] || ''
today = @timespan
DayOne.new.to_dayone({ 'content' => entry })
end
def auth_url
"https://exist.io/oauth2/authorize?response_type=code&client_id=#{@exist_config['client_id']}&redirect_uri=#{@exist_config['redirect_uri']}&scope=read"
end
def token_url
"https://exist.io/oauth2/access_token"
end
def user_auth
# Start by asking the user to authorise the client.
print "Please copy the code from your web browser, and then paste it below:\n>>";
%x{open "#{auth_url}"}
# Now get the code from the user.
code = gets.strip
# Check it looks reasonable
if code and code =~ /[0-9a-f]+/
puts "\nThanks!"
else
puts "\nInvalid code"
exit
end
# And exchange it for a token
response = RestClient.post(
token_url,
{
'grant_type' => 'authorization_code',
'code' => code,
'client_id' => @exist_config['client_id'],
'client_secret' => @exist_config['client_secret'],
'redirect_uri' => @exist_config['redirect_uri'],
}
)
# Return the interesting bits
data = JSON.parse(response.to_str)
config['access_token'] = data['access_token']
config['expires'] = Time.now + data['expires_in']
config
end
def grab_today
begin
today = RestClient::Request.execute(
method: :get,
url: 'https://exist.io/api/1/users/$self/today/',
headers: {'Authorization' => "Bearer #{@exist_config['access_token']}" }
)
puts today
rescue => e
puts e.response
end
JSON.parse(today, object_class: OpenStruct)
end
def erb(template, vars)
ERB.new(template, nil, '-').result(vars.instance_eval { binding })
end
end
If I tweak this in future, changes will be on GitHub, rather than here.
So, there you have it, a slightly waffly drawn out journey through building a command line based script which can authenticate with oAuth 2, and then convert JSON data to Markdown. I hope you’ve enjoyed it!