mirror of
https://github.com/Fishwaldo/huginn.git
synced 2025-03-15 19:31:26 +00:00
Beeper.io is no more (#1808)
* Beeper.io is no more * Avoid event propagation or scheduling of missing agents * Update undefined_agents.html.erb with a link to the wiki
This commit is contained in:
parent
8e36fdd60c
commit
8a14a57e00
5 changed files with 41 additions and 282 deletions
|
@ -370,7 +370,7 @@ class Agent < ActiveRecord::Base
|
|||
def receive!(options={})
|
||||
Agent.transaction do
|
||||
scope = Agent.
|
||||
select("agents.id AS receiver_agent_id, events.id AS event_id").
|
||||
select("agents.id AS receiver_agent_id, sources.type AS source_agent_type, agents.type AS receiver_agent_type, events.id AS event_id").
|
||||
joins("JOIN links ON (links.receiver_id = agents.id)").
|
||||
joins("JOIN agents AS sources ON (links.source_id = sources.id)").
|
||||
joins("JOIN events ON (events.agent_id = sources.id AND events.id > links.event_id_at_creation)").
|
||||
|
@ -379,10 +379,11 @@ class Agent < ActiveRecord::Base
|
|||
scope = scope.where("agents.id in (?)", options[:only_receivers])
|
||||
end
|
||||
|
||||
sql = scope.to_sql()
|
||||
sql = scope.to_sql
|
||||
|
||||
agents_to_events = {}
|
||||
Agent.connection.select_rows(sql).each do |receiver_agent_id, event_id|
|
||||
Agent.connection.select_rows(sql).each do |receiver_agent_id, source_agent_type, receiver_agent_type, event_id|
|
||||
next unless const_defined?(source_agent_type) && const_defined?(receiver_agent_type)
|
||||
agents_to_events[receiver_agent_id.to_i] ||= []
|
||||
agents_to_events[receiver_agent_id.to_i] << event_id
|
||||
end
|
||||
|
@ -417,6 +418,7 @@ class Agent < ActiveRecord::Base
|
|||
return if schedule == 'never'
|
||||
types = where(:schedule => schedule).group(:type).pluck(:type)
|
||||
types.each do |type|
|
||||
next unless const_defined?(type)
|
||||
type.constantize.bulk_check(schedule)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
module Agents
|
||||
class BeeperAgent < Agent
|
||||
cannot_be_scheduled!
|
||||
cannot_create_events!
|
||||
no_bulk_receive!
|
||||
|
||||
description <<-MD
|
||||
Beeper agent sends messages to Beeper app on your mobile device via Push notifications.
|
||||
|
||||
You need a Beeper Application ID (`app_id`), Beeper REST API Key (`api_key`) and Beeper Sender ID (`sender_id`) [https://beeper.io](https://beeper.io)
|
||||
|
||||
You have to provide phone number (`phone`) of the recipient which have a mobile device with Beeper installed, or a `group_id` – Beeper Group ID
|
||||
|
||||
Also you have to provide a message `type` which has to be `message`, `image`, `event`, `location` or `task`.
|
||||
|
||||
Depending on message type you have to provide additional fields:
|
||||
|
||||
##### Message
|
||||
* `text` – **required**
|
||||
|
||||
##### Image
|
||||
* `image` – **required** (Image URL or Base64-encoded image)
|
||||
* `text` – optional
|
||||
|
||||
##### Event
|
||||
* `text` – **required**
|
||||
* `start_time` – **required** (Corresponding to ISO 8601)
|
||||
* `end_time` – optional (Corresponding to ISO 8601)
|
||||
|
||||
##### Location
|
||||
* `latitude` – **required**
|
||||
* `longitude` – **required**
|
||||
* `text` – optional
|
||||
|
||||
##### Task
|
||||
* `text` – **required**
|
||||
|
||||
You can see additional documentation at [Beeper website](https://beeper.io/docs)
|
||||
MD
|
||||
|
||||
BASE_URL = 'https://api.beeper.io/api'
|
||||
|
||||
TYPE_ATTRIBUTES = {
|
||||
'message' => %w(text),
|
||||
'image' => %w(text image),
|
||||
'event' => %w(text start_time end_time),
|
||||
'location' => %w(text latitude longitude),
|
||||
'task' => %w(text)
|
||||
}
|
||||
|
||||
MESSAGE_TYPES = TYPE_ATTRIBUTES.keys
|
||||
|
||||
TYPE_REQUIRED_ATTRIBUTES = {
|
||||
'message' => %w(text),
|
||||
'image' => %w(image),
|
||||
'event' => %w(text start_time),
|
||||
'location' => %w(latitude longitude),
|
||||
'task' => %w(text)
|
||||
}
|
||||
|
||||
def default_options
|
||||
{
|
||||
'type' => 'message',
|
||||
'app_id' => '',
|
||||
'api_key' => '',
|
||||
'sender_id' => '',
|
||||
'phone' => '',
|
||||
'text' => '{{title}}'
|
||||
}
|
||||
end
|
||||
|
||||
def validate_options
|
||||
%w(app_id api_key sender_id type).each do |attr|
|
||||
errors.add(:base, "you need to specify a #{attr}") if options[attr].blank?
|
||||
end
|
||||
|
||||
if options['type'].in?(MESSAGE_TYPES)
|
||||
required_attributes = TYPE_REQUIRED_ATTRIBUTES[options['type']]
|
||||
if required_attributes.any? { |attr| options[attr].blank? }
|
||||
errors.add(:base, "you need to specify a #{required_attributes.join(', ')}")
|
||||
end
|
||||
else
|
||||
errors.add(:base, 'you need to specify a valid message type')
|
||||
end
|
||||
|
||||
unless options['group_id'].blank? ^ options['phone'].blank?
|
||||
errors.add(:base, 'you need to specify a phone or group_id')
|
||||
end
|
||||
end
|
||||
|
||||
def working?
|
||||
received_event_without_error? && !recent_error_logs?
|
||||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
incoming_events.each do |event|
|
||||
send_message(event)
|
||||
end
|
||||
end
|
||||
|
||||
def send_message(event)
|
||||
mo = interpolated(event)
|
||||
begin
|
||||
response = HTTParty.post(endpoint_for(mo['type']), body: payload_for(mo), headers: headers)
|
||||
error(response.body) if response.code != 201
|
||||
rescue HTTParty::Error => e
|
||||
error(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def headers
|
||||
{
|
||||
'X-Beeper-Application-Id' => options['app_id'],
|
||||
'X-Beeper-REST-API-Key' => options['api_key'],
|
||||
'Content-Type' => 'application/json'
|
||||
}
|
||||
end
|
||||
|
||||
def payload_for(mo)
|
||||
mo.slice(*TYPE_ATTRIBUTES[mo['type']], 'sender_id', 'phone', 'group_id').to_json
|
||||
end
|
||||
|
||||
def endpoint_for(type)
|
||||
"#{BASE_URL}/#{type}s.json"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,20 +19,20 @@
|
|||
</ul>
|
||||
<br/>
|
||||
<p>
|
||||
The issue most probably occurred because of one or more of the following reasons:
|
||||
This issue probably occurred for one or more of the following reasons:
|
||||
</p>
|
||||
<ul>
|
||||
<li>If the respective Agent is distributed as part of the Huginn application codebase, it may have been removed or moved to an Agent gem. Please see <a href="https://github.com/cantino/huginn/wiki/Dealing-with-Deleted-Agent-Types" target="_blank">this wiki page for more information</a>.</li>
|
||||
<li>If the respective Agent is distributed as a Ruby gem, it might have been removed from the <code>ADDITIONAL_GEMS</code> environment setting.</li>
|
||||
<li>If the respective Agent is distributed as part of the Huginn application codebase, it might have been removed from that either on purpose (because the Agent has been deprecated or been moved to an Agent gem) or accidentally. Please check if the Agent(s) in question are available in your Huginn codebase under the path <code>app/models/agents/</code>.</li>
|
||||
</ul>
|
||||
<br/>
|
||||
<p>
|
||||
You can fix the issue by adding the Agent(s) back to the application codebase by
|
||||
You can fix this issue by:
|
||||
</p>
|
||||
<ul>
|
||||
<li>deleting the respective Agent(s) from the database using the button below.</li>
|
||||
<li>adding the respective Agent(s) to the the <code>ADDITIONAL_GEMS</code> environment setting. Please see <a href="https://github.com/cantino/huginn_agent" target="_blank">https://github.com/cantino/huginn_agent</a> for documentation on how to properly set it.</li>
|
||||
<li>adding the respective Agent(s) code to the Huginn application codebase (in case it was deleted accidentally).</li>
|
||||
<li>deleting the respective Agent(s) from the database using the button below.</li>
|
||||
</ul>
|
||||
<br/>
|
||||
<div class="btn-group">
|
||||
|
|
|
@ -74,6 +74,13 @@ describe Agent do
|
|||
Agent.run_schedule("midnight")
|
||||
end
|
||||
|
||||
it "ignores unknown types" do
|
||||
Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
|
||||
mock(Agents::WeatherAgent).bulk_check("midnight").once
|
||||
mock(Agents::WebsiteAgent).bulk_check("midnight").once
|
||||
Agent.run_schedule("midnight")
|
||||
end
|
||||
|
||||
it "only runs agents with the given schedule" do
|
||||
do_not_allow(Agents::WebsiteAgent).async_check
|
||||
Agent.run_schedule("blah")
|
||||
|
@ -283,13 +290,37 @@ describe Agent do
|
|||
Agent.receive!
|
||||
end
|
||||
|
||||
it "should not propogate to disabled Agents" do
|
||||
it "should not propagate to disabled Agents" do
|
||||
Agent.async_check(agents(:bob_weather_agent).id)
|
||||
agents(:bob_rain_notifier_agent).update_attribute :disabled, true
|
||||
mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
|
||||
Agent.receive!
|
||||
end
|
||||
|
||||
it "should not propagate to Agents with unknown types" do
|
||||
Agent.async_check(agents(:jane_weather_agent).id)
|
||||
Agent.async_check(agents(:bob_weather_agent).id)
|
||||
|
||||
Agent.where(id: agents(:bob_rain_notifier_agent).id).update_all type: 'UnknownTypeAgent'
|
||||
|
||||
mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
|
||||
mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
|
||||
|
||||
Agent.receive!
|
||||
end
|
||||
|
||||
it "should not propagate from Agents with unknown types" do
|
||||
Agent.async_check(agents(:jane_weather_agent).id)
|
||||
Agent.async_check(agents(:bob_weather_agent).id)
|
||||
|
||||
Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
|
||||
|
||||
mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
|
||||
mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
|
||||
|
||||
Agent.receive!
|
||||
end
|
||||
|
||||
it "should log exceptions" do
|
||||
mock.any_instance_of(Agents::TriggerAgent).receive(anything).once {
|
||||
raise "foo"
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
|
||||
describe Agents::BeeperAgent do
|
||||
let(:base_params) {
|
||||
{
|
||||
'type' => 'message',
|
||||
'app_id' => 'some-app-id',
|
||||
'api_key' => 'some-api-key',
|
||||
'sender_id' => 'sender-id',
|
||||
'phone' => '+111111111111',
|
||||
'text' => 'Some text'
|
||||
}
|
||||
}
|
||||
|
||||
subject {
|
||||
agent = described_class.new(name: 'beeper-agent', options: base_params)
|
||||
agent.user = users(:jane)
|
||||
agent.save! and return agent
|
||||
}
|
||||
|
||||
context 'validation' do
|
||||
it 'valid' do
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
|
||||
[:type, :app_id, :api_key, :sender_id].each do |attr|
|
||||
it "invalid without #{attr}" do
|
||||
subject.options[attr] = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
it 'invalid with group_id and phone' do
|
||||
subject.options['group_id'] ='some-group-id'
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
context '#message' do
|
||||
it 'requires text' do
|
||||
subject.options[:text] = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context '#image' do
|
||||
before(:each) do
|
||||
subject.options[:type] = 'image'
|
||||
end
|
||||
|
||||
it 'invalid without image' do
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it 'valid with image' do
|
||||
subject.options[:image] = 'some-url'
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context '#event' do
|
||||
before(:each) do
|
||||
subject.options[:type] = 'event'
|
||||
end
|
||||
|
||||
it 'invalid without start_time' do
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it 'valid with start_time' do
|
||||
subject.options[:start_time] = Time.now
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context '#location' do
|
||||
before(:each) do
|
||||
subject.options[:type] = 'location'
|
||||
end
|
||||
|
||||
it 'invalid without latitude and longitude' do
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it 'valid with latitude and longitude' do
|
||||
subject.options[:latitude] = 15.0
|
||||
subject.options[:longitude] = 16.0
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context '#task' do
|
||||
before(:each) do
|
||||
subject.options[:type] = 'task'
|
||||
end
|
||||
|
||||
it 'valid with text' do
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'payload_for' do
|
||||
it 'removes unwanted attributes' do
|
||||
result = subject.send(:payload_for, {'type' => 'message', 'text' => 'text',
|
||||
'sender_id' => 'sender', 'phone' => '+1', 'random_attribute' => 'unwanted'})
|
||||
expect(result).to eq('{"text":"text","sender_id":"sender","phone":"+1"}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'headers' do
|
||||
it 'sets X-Beeper-Application-Id header with app_id' do
|
||||
expect(subject.send(:headers)['X-Beeper-Application-Id']).to eq(base_params['app_id'])
|
||||
end
|
||||
|
||||
it 'sets X-Beeper-REST-API-Key header with api_key' do
|
||||
expect(subject.send(:headers)['X-Beeper-REST-API-Key']).to eq(base_params['api_key'])
|
||||
end
|
||||
|
||||
it 'sets Content-Type' do
|
||||
expect(subject.send(:headers)['Content-Type']).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'endpoint_for' do
|
||||
it 'returns valid URL for message' do
|
||||
expect(subject.send(:endpoint_for, 'message')).to eq('https://api.beeper.io/api/messages.json')
|
||||
end
|
||||
|
||||
it 'returns valid URL for image' do
|
||||
expect(subject.send(:endpoint_for, 'image')).to eq('https://api.beeper.io/api/images.json')
|
||||
end
|
||||
|
||||
it 'returns valid URL for event' do
|
||||
expect(subject.send(:endpoint_for, 'event')).to eq('https://api.beeper.io/api/events.json')
|
||||
end
|
||||
|
||||
it 'returns valid URL for location' do
|
||||
expect(subject.send(:endpoint_for, 'location')).to eq('https://api.beeper.io/api/locations.json')
|
||||
end
|
||||
it 'returns valid URL for task' do
|
||||
expect(subject.send(:endpoint_for, 'task')).to eq('https://api.beeper.io/api/tasks.json')
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue