module Agents class DataOutputAgent < Agent cannot_be_scheduled! description do <<-MD The Agent outputs received events as either RSS or JSON. Use it to output a public or private stream of Huginn data. This Agent will output data at: `https://#{ENV['DOMAIN']}/users/#{user.id}/web_requests/#{id || ''}/:secret.xml` where `:secret` is one of the allowed secrets specified in your options and the extension can be `xml` or `json`. You can setup multiple secrets so that you can individually authorize external systems to access your Huginn data. Options: * `secrets` - An array of tokens that the requestor must provide for light-weight authentication. * `expected_receive_period_in_days` - How often you expect data to be received by this Agent from other Agents. * `template` - A JSON object representing a mapping between item output keys and incoming event values. Use [Liquid](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to format the values. The `item` key will be repeated for every Event. The `pubDate` key for each item will have the creation time of the Event unless given. * `events_to_show` - The number of events to output in RSS or JSON. (default: `40`) * `ttl` - A value for the element in RSS output. (default: `60`) MD end def default_options { "secrets" => ["a-secret-key"], "expected_receive_period_in_days" => 2, "template" => { "title" => "XKCD comics as a feed", "description" => "This is a feed of recent XKCD comics, generated by Huginn", "item" => { "title" => "{{title}}", "description" => "Secret hovertext: {{hovertext}}", "link" => "{{url}}", } } } end #"guid" => "", # "pubDate" => "" def working? last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs? end def validate_options unless options['secrets'].is_a?(Array) && options['secrets'].length > 0 errors.add(:base, "Please specify one or more secrets for 'authenticating' incoming feed requests") end unless options['expected_receive_period_in_days'].present? && options['expected_receive_period_in_days'].to_i > 0 errors.add(:base, "Please provide 'expected_receive_period_in_days' to indicate how many days can pass before this Agent is considered to be not working") end unless options['template'].present? && options['template']['item'].present? && options['template']['item'].is_a?(Hash) errors.add(:base, "Please provide template and template.item") end end def events_to_show (interpolated['events_to_show'].presence || 40).to_i end def feed_ttl (interpolated['ttl'].presence || 60).to_i end def feed_title interpolated['template']['title'].presence || "#{name} Event Feed" end def feed_link interpolated['template']['link'].presence || "https://#{ENV['DOMAIN']}" end def feed_description interpolated['template']['description'].presence || "A feed of Events received by the '#{name}' Huginn Agent" end def receive_web_request(params, method, format) if interpolated['secrets'].include?(params['secret']) items = received_events.order('id desc').limit(events_to_show).map do |event| interpolated = interpolate_options(options['template']['item'], event) interpolated['guid'] = event.id date_string = interpolated['pubDate'].to_s date = begin Time.zone.parse(date_string) # may return nil rescue => e error "Error parsing a \"pubDate\" value \"#{date_string}\": #{e.message}" nil end || event.created_at interpolated['pubDate'] = date.rfc2822.to_s interpolated end if format =~ /json/ content = { 'title' => feed_title, 'description' => feed_description, 'pubDate' => Time.now, 'items' => items } return [content, 200] else content = Utils.unindent(<<-XML) #{feed_title.encode(:xml => :text)} #{feed_description.encode(:xml => :text)} #{feed_link.encode(:xml => :text)} #{Time.now.rfc2822.to_s.encode(:xml => :text)} #{Time.now.rfc2822.to_s.encode(:xml => :text)} #{feed_ttl} XML content += items.to_xml(:skip_types => true, :root => "items", :skip_instruct => true, :indent => 1).gsub(/^<\/?items>/, '').strip content += Utils.unindent(<<-XML) XML return [content, 200, 'text/xml'] end else if format =~ /json/ return [{ :error => "Not Authorized" }, 401] else return ["Not Authorized", 401] end end end end end