data_output_agent.rb 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. module Agents
  2. class DataOutputAgent < Agent
  3. cannot_be_scheduled!
  4. description do
  5. <<-MD
  6. The Agent outputs received events as either RSS or JSON. Use it to output a public or private stream of Huginn data.
  7. This Agent will output data at:
  8. `https://#{ENV['DOMAIN']}/users/#{user.id}/web_requests/#{id || '<id>'}/:secret.xml`
  9. where `:secret` is one of the allowed secrets specified in your options and the extension can be `xml` or `json`.
  10. You can setup multiple secrets so that you can individually authorize external systems to
  11. access your Huginn data.
  12. Options:
  13. * `secrets` - An array of tokens that the requestor must provide for light-weight authentication.
  14. * `expected_receive_period_in_days` - How often you expect data to be received by this Agent from other Agents.
  15. * `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.
  16. * `events_to_show` - The number of events to output in RSS or JSON. (default: `40`)
  17. * `ttl` - A value for the <ttl> element in RSS output. (default: `60`)
  18. MD
  19. end
  20. def default_options
  21. {
  22. "secrets" => ["a-secret-key"],
  23. "expected_receive_period_in_days" => 2,
  24. "template" => {
  25. "title" => "XKCD comics as a feed",
  26. "description" => "This is a feed of recent XKCD comics, generated by Huginn",
  27. "item" => {
  28. "title" => "{{title}}",
  29. "description" => "Secret hovertext: {{hovertext}}",
  30. "link" => "{{url}}",
  31. }
  32. }
  33. }
  34. end
  35. #"guid" => "",
  36. # "pubDate" => ""
  37. def working?
  38. last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
  39. end
  40. def validate_options
  41. unless options['secrets'].is_a?(Array) && options['secrets'].length > 0
  42. errors.add(:base, "Please specify one or more secrets for 'authenticating' incoming feed requests")
  43. end
  44. unless options['expected_receive_period_in_days'].present? && options['expected_receive_period_in_days'].to_i > 0
  45. 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")
  46. end
  47. unless options['template'].present? && options['template']['item'].present? && options['template']['item'].is_a?(Hash)
  48. errors.add(:base, "Please provide template and template.item")
  49. end
  50. end
  51. def events_to_show
  52. (interpolated['events_to_show'].presence || 40).to_i
  53. end
  54. def feed_ttl
  55. (interpolated['ttl'].presence || 60).to_i
  56. end
  57. def feed_title
  58. interpolated['template']['title'].presence || "#{name} Event Feed"
  59. end
  60. def feed_link
  61. interpolated['template']['link'].presence || "https://#{ENV['DOMAIN']}"
  62. end
  63. def feed_description
  64. interpolated['template']['description'].presence || "A feed of Events received by the '#{name}' Huginn Agent"
  65. end
  66. def receive_web_request(params, method, format)
  67. if interpolated['secrets'].include?(params['secret'])
  68. items = received_events.order('id desc').limit(events_to_show).map do |event|
  69. interpolated = interpolate_options(options['template']['item'], event)
  70. interpolated['guid'] = event.id
  71. date_string = interpolated['pubDate'].to_s
  72. date =
  73. begin
  74. Time.zone.parse(date_string) # may return nil
  75. rescue => e
  76. error "Error parsing a \"pubDate\" value \"#{date_string}\": #{e.message}"
  77. nil
  78. end || event.created_at
  79. interpolated['pubDate'] = date.rfc2822.to_s
  80. interpolated
  81. end
  82. if format =~ /json/
  83. content = {
  84. 'title' => feed_title,
  85. 'description' => feed_description,
  86. 'pubDate' => Time.now,
  87. 'items' => items
  88. }
  89. return [content, 200]
  90. else
  91. content = Utils.unindent(<<-XML)
  92. <?xml version="1.0" encoding="UTF-8" ?>
  93. <rss version="2.0">
  94. <channel>
  95. <title>#{feed_title.encode(:xml => :text)}</title>
  96. <description>#{feed_description.encode(:xml => :text)}</description>
  97. <link>#{feed_link.encode(:xml => :text)}</link>
  98. <lastBuildDate>#{Time.now.rfc2822.to_s.encode(:xml => :text)}</lastBuildDate>
  99. <pubDate>#{Time.now.rfc2822.to_s.encode(:xml => :text)}</pubDate>
  100. <ttl>#{feed_ttl}</ttl>
  101. XML
  102. content += items.to_xml(:skip_types => true, :root => "items", :skip_instruct => true, :indent => 1).gsub(/^<\/?items>/, '').strip
  103. content += Utils.unindent(<<-XML)
  104. </channel>
  105. </rss>
  106. XML
  107. return [content, 200, 'text/xml']
  108. end
  109. else
  110. if format =~ /json/
  111. return [{ :error => "Not Authorized" }, 401]
  112. else
  113. return ["Not Authorized", 401]
  114. end
  115. end
  116. end
  117. end
  118. end