Skip to content

Commit

Permalink
Merge pull request huginn#711 from dsander/pushbullet-unification
Browse files Browse the repository at this point in the history
Pushbullet pull request unification
  • Loading branch information
dsander committed Feb 25, 2015
2 parents 7c6224d + 0b979dd commit da77a8a
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 29 deletions.
98 changes: 85 additions & 13 deletions app/models/agents/pushbullet_agent.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
module Agents
class PushbulletAgent < Agent
include FormConfigurable

cannot_be_scheduled!
cannot_create_events!

before_validation :create_device, on: :create

API_BASE = 'https://api.pushbullet.com/v2/'
TYPE_TO_ATTRIBUTES = {
'note' => [:title, :body],
'link' => [:title, :body, :url],
'address' => [:name, :address]
}
class Unauthorized < StandardError; end

description <<-MD
The Pushbullet agent sends pushes to a pushbullet device
To authenticate you need to set the `api_key`, you can find yours at your account page:
To authenticate you need to either the `api_key` or create a `pushbullet_api_key` credential, you can find yours at your account page:
`https://www.pushbullet.com/account`
Currently you need to get a the device identification manually:
If you do not select an existing device, Huginn will create a new one with the name 'Huginn'.
`curl -u <your api key here>: https://api.pushbullet.com/api/devices`
You have to provide a message `type` which has to be `note`, `link`, or `address`. The message types `checklist`, and `file` are not supported at the moment.
Put one of the retured `iden` strings into the `device_id` field.
Depending on the message `type` you can use additional fields:
You can provide a `title` and a `body`.
* note: `title` and `body`
* link: `title`, `body`, and `url`
* address: `name`, and `address`
In every value of the options hash you can use the liquid templating, learn more about it at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid).
MD
Expand All @@ -25,14 +39,39 @@ def default_options
{
'api_key' => '',
'device_id' => '',
'title' => "Hello from Huginn!",
'title' => "{{title}}",
'body' => '{{body}}',
'type' => 'note',
}
end

form_configurable :api_key, roles: :validatable
form_configurable :device_id, roles: :completable
form_configurable :type, type: :array, values: ['note', 'link', 'address']
form_configurable :title
form_configurable :body, type: :text
form_configurable :url
form_configurable :name
form_configurable :address

def validate_options
errors.add(:base, "you need to specify a pushbullet api_key") unless options['api_key'].present?
errors.add(:base, "you need to specify a pushbullet api_key") if options['api_key'].blank?
errors.add(:base, "you need to specify a device_id") if options['device_id'].blank?
errors.add(:base, "you need to specify a valid message type") if options['type'].blank? or not ['note', 'link', 'address'].include?(options['type'])
TYPE_TO_ATTRIBUTES[options['type']].each do |attr|
errors.add(:base, "you need to specify '#{attr.to_s}' for the type '#{options['type']}'") if options[attr].blank?
end
end

def validate_api_key
devices
true
rescue Unauthorized
false
end

def complete_device_id
devices.map { |d| {text: d['nickname'], id: d['iden']} }
end

def working?
Expand All @@ -41,19 +80,52 @@ def working?

def receive(incoming_events)
incoming_events.each do |event|
response = HTTParty.post "https://api.pushbullet.com/api/pushes", query_options(event)
error(response.body) if response.body.include? 'error'
safely do
response = request(:post, 'pushes', query_options(event))
end
end
end

private
def safely
yield
rescue Unauthorized => e
error(e.message)
end

def request(http_method, method, options)
response = JSON.parse(HTTParty.send(http_method, API_BASE + method, options).body)
raise Unauthorized, response['error']['message'] if response['error'].present?
response
end

def devices
response = request(:get, 'devices', basic_auth)
response['devices'].select { |d| d['pushable'] == true }
rescue Unauthorized
[]
end

def create_device
return if options['device_id'].present?
safely do
response = request(:post, 'devices', basic_auth.merge(body: {nickname: 'Huginn', type: 'stream'}))
self.options[:device_id] = response['iden']
end
end


def basic_auth
{basic_auth: {username: interpolated[:api_key].presence || credential('pushbullet_api_key'), password: ''}}
end

def query_options(event)
mo = interpolated(event)
{
:basic_auth => {:username => mo[:api_key], :password => ''},
:body => {:device_iden => mo[:device_id], :title => mo[:title], :body => mo[:body], :type => 'note'}
}
basic_auth.merge(body: {device_iden: mo[:device_id], type: mo[:type]}.merge(payload(mo)))
end

def payload(mo)
Hash[TYPE_TO_ATTRIBUTES[mo[:type]].map { |k| [k, mo[k]] }]
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class AddTypeOptionAttributeToPushbulletAgents < ActiveRecord::Migration
def up
Agents::PushbulletAgent.find_each do |agent|
if agent.options['type'].nil?
agent.options['type'] = 'note'
agent.save!
end
end
end

def down
Agents::PushbulletAgent.find_each do |agent|
if agent.options['type'].present?
agent.options.delete 'type'
agent.save(validate: false)
end
end
end
end
109 changes: 93 additions & 16 deletions spec/models/agents/pushbullet_agent_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
describe Agents::PushbulletAgent do
before(:each) do
@valid_params = {
'api_key' => 'token',
'api_key' => 'token',
'device_id' => '124',
'body' => '{{body}}',
'title' => 'hello from huginn'
'body' => '{{body}}',
'url' => 'url',
'name' => 'name',
'address' => 'address',
'title' => 'hello from huginn',
'type' => 'note'
}

@checker = Agents::PushbulletAgent.new(:name => "somename", :options => @valid_params)
Expand All @@ -29,34 +33,83 @@
expect(@checker).not_to be_valid
end

it "should require the device_id" do
it "should try do create a device_id" do
@checker.options['device_id'] = nil
expect(@checker).not_to be_valid
end

it "should require fields based on the type" do
@checker.options['type'] = 'address'
@checker.options['address'] = nil
expect(@checker).not_to be_valid
end
end

describe "helpers" do
it "should return the query_options" do
expect(@checker.send(:query_options, @event)).to eq({
:body => {:title => 'hello from huginn', :body => 'One two test', :device_iden => @checker.options[:device_id], :type => 'note'},
:basic_auth => {:username =>@checker.options[:api_key], :password=>''}
})
before(:each) do
@base_options = {
body: { device_iden: @checker.options[:device_id] },
basic_auth: { username: @checker.options[:api_key], :password=>'' }
}
end
context "#query_options" do
it "should work for a note" do
options = @base_options.deep_merge({
body: {title: 'hello from huginn', body: 'One two test', type: 'note'}
})
expect(@checker.send(:query_options, @event)).to eq(options)
end

it "should work for a link" do
@checker.options['type'] = 'link'
options = @base_options.deep_merge({
body: {title: 'hello from huginn', body: 'One two test', type: 'link', url: 'url'}
})
expect(@checker.send(:query_options, @event)).to eq(options)
end

it "should work for an address" do
@checker.options['type'] = 'address'
options = @base_options.deep_merge({
body: {name: 'name', address: 'address', type: 'address'}
})
expect(@checker.send(:query_options, @event)).to eq(options)
end
end
end

describe '#validate_api_key' do
it "should return true when working" do
mock(@checker).devices
expect(@checker.validate_api_key).to be_truthy
end

it "should return true when working" do
mock(@checker).devices { raise Agents::PushbulletAgent::Unauthorized }
expect(@checker.validate_api_key).to be_falsy
end
end

describe '#complete_device_id' do
it "should return an array" do
mock(@checker).devices { [{'iden' => '12345', 'nickname' => 'huginn'}] }
expect(@checker.complete_device_id).to eq([{:text=>"huginn", :id=>"12345"}])
end
end

describe "#receive" do
it "send a message to the hipchat" do
stub_request(:post, "https://token:@api.pushbullet.com/api/pushes").
with(:body => "device_iden=124&title=hello%20from%20huginn&body=One%20two%20test&type=note").
to_return(:status => 200, :body => "ok", :headers => {})
it "send a note" do
stub_request(:post, "https://token:@api.pushbullet.com/v2/pushes").
with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
to_return(:status => 200, :body => "{}", :headers => {})
dont_allow(@checker).error
@checker.receive([@event])
end

it "should log resquests which return an error" do
stub_request(:post, "https://token:@api.pushbullet.com/api/pushes").
with(:body => "device_iden=124&title=hello%20from%20huginn&body=One%20two%20test&type=note").
to_return(:status => 200, :body => "error", :headers => {})
stub_request(:post, "https://token:@api.pushbullet.com/v2/pushes").
with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
to_return(:status => 200, :body => '{"error": {"message": "error"}}', :headers => {})
mock(@checker).error("error")
@checker.receive([@event])
end
Expand All @@ -69,4 +122,28 @@
expect(@checker).to be_working
end
end

describe '#devices' do
it "should return an array of devices" do
stub_request(:get, "https://token:@api.pushbullet.com/v2/devices").
to_return(:status => 200, :body => '{"devices": [{"pushable": false}, {"nickname": "test", "iden": "iden", "pushable": true}]}', :headers => {})
expect(@checker.send(:devices)).to eq([{"nickname"=>"test", "iden"=>"iden", "pushable"=>true}])
end

it "should return an empty array on error" do
stub(@checker).request { raise Agents::PushbulletAgent::Unauthorized }
expect(@checker.send(:devices)).to eq([])
end
end

describe '#create_device' do
it "should create a new device and assign it to the options" do
stub_request(:post, "https://token:@api.pushbullet.com/v2/devices").
with(:body => "nickname=Huginn&type=stream").
to_return(:status => 200, :body => '{"iden": "udm0Tdjz5A7bL4NM"}', :headers => {})
@checker.options['device_id'] = nil
@checker.send(:create_device)
expect(@checker.options[:device_id]).to eq('udm0Tdjz5A7bL4NM')
end
end
end

0 comments on commit da77a8a

Please sign in to comment.