Browse Source

Merge pull request #549 from deanputney/master

TumblrPublishAgent
Andrew Cantino 10 years ago
parent
commit
8d6090faf9

+ 3 - 0
.env.example

@@ -89,6 +89,9 @@ THIRTY_SEVEN_SIGNALS_OAUTH_SECRET=
 GITHUB_OAUTH_KEY=
 GITHUB_OAUTH_SECRET=
 
+TUMBLR_OAUTH_KEY=
+TUMBLR_OAUTH_SECRET=
+
 #############################
 #  AWS and Mechanical Turk  #
 #############################

+ 4 - 0
Gemfile

@@ -20,6 +20,10 @@ gem 'twitter', '~> 5.8.0' # Must to be loaded before cantino-twitter-stream.
 gem 'cantino-twitter-stream', github: 'cantino/twitter-stream', branch: 'master'
 gem 'omniauth-twitter'
 
+# Tumblr Agents
+gem 'tumblr_client'
+gem 'omniauth-tumblr'
+
 # Optional Services.
 gem 'omniauth-37signals'          # BasecampAgent
 # gem 'omniauth-github'

+ 11 - 0
Gemfile.lock

@@ -214,6 +214,8 @@ GEM
       multi_json (~> 1.3)
       oauth2 (~> 0.9.3)
       omniauth (~> 1.2)
+    omniauth-tumblr (1.1)
+      omniauth-oauth (~> 1.0)
     omniauth-twitter (1.0.1)
       multi_json (~> 1.3)
       omniauth-oauth (~> 1.0)
@@ -340,6 +342,13 @@ GEM
     treetop (1.4.15)
       polyglot
       polyglot (>= 0.3.1)
+    tumblr_client (0.8.4)
+      faraday (~> 0.9.0)
+      faraday_middleware (~> 0.9.0)
+      json
+      mime-types
+      oauth
+      simple_oauth
     twilio-ruby (3.11.6)
       builder (>= 2.1.2)
       jwt (>= 0.1.2)
@@ -431,6 +440,7 @@ DEPENDENCIES
   nokogiri (~> 1.6.1)
   omniauth
   omniauth-37signals
+  omniauth-tumblr
   omniauth-twitter
   pg
   protected_attributes (~> 1.0.8)
@@ -454,6 +464,7 @@ DEPENDENCIES
   spring
   spring-commands-rspec
   therubyracer (~> 0.12.1)
+  tumblr_client
   twilio-ruby (~> 3.11.5)
   twitter (~> 5.8.0)
   typhoeus (~> 0.6.3)

+ 36 - 0
app/concerns/tumblr_concern.rb

@@ -0,0 +1,36 @@
+module TumblrConcern
+  extend ActiveSupport::Concern
+
+  included do
+    include Oauthable
+
+    valid_oauth_providers :tumblr
+  end
+
+  def tumblr_consumer_key
+    ENV['TUMBLR_OAUTH_KEY']
+  end
+
+  def tumblr_consumer_secret
+    ENV['TUMBLR_OAUTH_SECRET']
+  end
+
+  def tumblr_oauth_token
+    service.token
+  end
+
+  def tumblr_oauth_token_secret
+    service.secret
+  end
+
+  def tumblr
+    Tumblr.configure do |config|
+      config.consumer_key = tumblr_consumer_key
+      config.consumer_secret = tumblr_consumer_secret
+      config.oauth_token = tumblr_oauth_token
+      config.oauth_token_secret = tumblr_oauth_token_secret
+    end
+    
+    Tumblr::Client.new
+  end
+end

+ 1 - 1
app/helpers/application_helper.rb

@@ -43,7 +43,7 @@ module ApplicationHelper
 
   def icon_for_service(service)
     case service.to_sym
-    when :twitter, :github
+    when :twitter, :tumblr, :github
       "<i class='fa fa-#{service}'></i>".html_safe
     else
       "<i class='fa fa-lock'></i>".html_safe

+ 163 - 0
app/models/agents/tumblr_publish_agent.rb

@@ -0,0 +1,163 @@
+require "tumblr_client"
+
+module Agents
+  class TumblrPublishAgent < Agent
+    include TumblrConcern
+
+    cannot_be_scheduled!
+
+    description <<-MD
+      #{'## Include `tumblr_client` and `omniauth-tumblr` in your Gemfile to use this Agent!' if dependencies_missing?}
+
+      The TumblrPublishAgent publishes Tumblr posts from the events it receives.
+
+      To be able to use this Agent you need to authenticate with Tumblr in the [Services](/services) section first.
+
+
+
+      **Required fields:**
+
+      `blog_name` Your Tumblr URL (e.g. "mustardhamsters.tumblr.com") 
+
+      `post_type` One of [text, photo, quote, link, chat, audio, video] 
+
+
+      -------------
+
+      You may leave any of the following optional fields blank. Including a field not allowed for the specified `post_type` will cause a failure.
+
+      **Any post type**
+
+      * `state` published, draft, queue, private
+      * `tags` Comma-separated tags for this post
+      * `tweet` off, text for tweet
+      * `date` GMT date and time of the post as a string
+      * `format` html, markdown
+      * `slug` short text summary at end of the post URL
+
+      **Text** `title` `body` 
+
+      **Photo** `caption` `link`  `source`
+
+      **Quote** `quote` `source`
+
+      **Link** `title` `url` `description` 
+
+      **Chat** `title` `conversation`
+
+      **Audio** `caption` `external_url`
+
+      **Video** `caption` `embed`
+
+
+      -------------
+
+      [Full information on field options](https://www.tumblr.com/docs/en/api/v2#posting)
+
+      Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent.
+    MD
+
+    gem_dependency_check { defined?(Tumblr) }
+
+    def validate_options
+      errors.add(:base, "expected_update_period_in_days is required") unless options['expected_update_period_in_days'].present?
+    end
+
+    def working?
+      event_created_within?(interpolated['expected_update_period_in_days']) && most_recent_event && most_recent_event.payload['success'] == true && !recent_error_logs?
+    end
+
+    def default_options
+      {
+        'expected_update_period_in_days' => "10",
+        'blog_name' => "{{blog_name}}",
+        'post_type' => "{{post_type}}",
+        'options' => {
+          'state' => "{{state}}",
+          'tags' => "{{tags}}",
+          'tweet' => "{{tweet}}",
+          'date' => "{{date}}",
+          'format' => "{{format}}",
+          'slug' => "{{slug}}",
+          'title' => "{{title}}",
+          'body' => "{{body}}",
+          'caption' => "{{caption}}",
+          'link' => "{{link}}",
+          'source' => "{{source}}",
+          'quote' => "{{quote}}",
+          'url' => "{{url}}",
+          'description' => "{{description}}",
+          'conversation' => "{{conversation}}",
+          'external_url' => "{{external_url}}",
+          'embed' => "{{embed}}",
+        },
+      }
+    end
+
+    def receive(incoming_events)
+      # if there are too many, dump a bunch to avoid getting rate limited
+      if incoming_events.count > 20
+        incoming_events = incoming_events.first(20)
+      end
+      incoming_events.each do |event|
+        blog_name = interpolated(event)['blog_name']
+        post_type = interpolated(event)['post_type']
+        options = interpolated(event)['options']
+        begin
+          post = publish_post(blog_name, post_type, options)
+          create_event :payload => {
+            'success' => true,
+            'published_post' => "["+blog_name+"] "+post_type,
+            'post_id' => post["id"],
+            'agent_id' => event.agent_id,
+            'event_id' => event.id
+          }
+        end
+      end
+    end
+
+    def publish_post(blog_name, post_type, options)      
+      options_obj = { 
+          :state => options['state'],
+          :tags => options['tags'],
+          :tweet => options['tweet'],
+          :date => options['date'],
+          :format => options['format'],
+          :slug => options['slug'],
+        }
+
+      case post_type
+      when "text"
+        options_obj[:title] = options['title']
+        options_obj[:body] = options['body']
+        tumblr.text(blog_name, options_obj)
+      when "photo"
+        options_obj[:caption] = options['caption']
+        options_obj[:link] = options['link']
+        options_obj[:source] = options['source']
+        tumblr.photo(blog_name, options_obj)
+      when "quote"
+        options_obj[:quote] = options['quote']
+        options_obj[:source] = options['source']
+        tumblr.quote(blog_name, options_obj)
+      when "link"
+        options_obj[:title] = options['title']
+        options_obj[:url] = options['url']
+        options_obj[:description] = options['description']
+        tumblr.link(blog_name, options_obj)
+      when "chat"
+        options_obj[:title] = options['title']
+        options_obj[:conversation] = options['conversation']
+        tumblr.chat(blog_name, options_obj)
+      when "audio"
+        options_obj[:caption] = options['caption']
+        options_obj[:external_url] = options['external_url']
+        tumblr.audio(blog_name, options_obj)
+      when "video"
+        options_obj[:caption] = options['caption']
+        options_obj[:embed] = options['embed']
+        tumblr.video(blog_name, options_obj)
+      end
+    end
+  end
+end

+ 6 - 0
config/initializers/devise.rb

@@ -219,6 +219,12 @@ Devise.setup do |config|
     config.omniauth :twitter, key, secret, authorize_params: {force_login: 'true', use_authorize: 'true'}
   end
 
+  if defined?(OmniAuth::Strategies::Tumblr) &&
+     (key = ENV["TUMBLR_OAUTH_KEY"]).present? &&
+     (secret = ENV["TUMBLR_OAUTH_SECRET"]).present?
+    config.omniauth :'tumblr', key, secret
+  end
+
   if defined?(OmniAuth::Strategies::ThirtySevenSignals) &&
      (key = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_KEY"]).present? &&
      (secret = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_SECRET"]).present?

+ 1 - 0
config/locales/devise.en.yml

@@ -51,6 +51,7 @@ en:
       failure: 'Could not authenticate you from %{kind} because "%{reason}".'
     omniauth_providers:
       twitter: 'Twitter'
+      tumblr: 'Tumblr'
       github: 'GitHub'
       37signals: '37Signals (Basecamp)'
     mailer:

+ 24 - 27
db/schema.rb

@@ -11,14 +11,11 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20140901143732) do
-
-  # These are extensions that must be enabled in order to support this database
-  enable_extension "plpgsql"
+ActiveRecord::Schema.define(version: 20140906030139) do
 
   create_table "agent_logs", force: true do |t|
     t.integer  "agent_id",                      null: false
-    t.text     "message",           limit: 16777215,             null: false, charset: "utf8mb4", collation: "utf8mb4_bin"
+    t.text     "message",                       null: false, charset: "utf8mb4", collation: "utf8mb4_bin"
     t.integer  "level",             default: 3, null: false
     t.integer  "inbound_event_id"
     t.integer  "outbound_event_id"
@@ -28,7 +25,7 @@ ActiveRecord::Schema.define(version: 20140901143732) do
 
   create_table "agents", force: true do |t|
     t.integer  "user_id"
-    t.text     "options",               limit: 16777215,                                charset: "utf8mb4", collation: "utf8mb4_bin"
+    t.text     "options",                                                               charset: "utf8mb4", collation: "utf8mb4_bin"
     t.string   "type",                                                                                      collation: "utf8_bin"
     t.string   "name",                                                                  charset: "utf8mb4", collation: "utf8mb4_bin"
     t.string   "schedule",                                                                                  collation: "utf8_bin"
@@ -36,17 +33,17 @@ ActiveRecord::Schema.define(version: 20140901143732) do
     t.datetime "last_check_at"
     t.datetime "last_receive_at"
     t.integer  "last_checked_event_id"
-    t.datetime "created_at",                                               null: false
-    t.datetime "updated_at",                                               null: false
+    t.datetime "created_at"
+    t.datetime "updated_at"
     t.text     "memory",                limit: 2147483647,                              charset: "utf8mb4", collation: "utf8mb4_bin"
     t.datetime "last_web_request_at"
     t.integer  "keep_events_for",                          default: 0,     null: false
     t.datetime "last_event_at"
     t.datetime "last_error_log_at"
-    t.boolean  "propagate_immediately", default: false, null: false
-    t.boolean  "disabled",              default: false, null: false
-    t.string   "guid",                                                     null: false, charset: "ascii",   collation: "ascii_bin"
+    t.boolean  "propagate_immediately",                    default: false, null: false
+    t.boolean  "disabled",                                 default: false, null: false
     t.integer  "service_id"
+    t.string   "guid",                                                     null: false
   end
 
   add_index "agents", ["guid"], name: "index_agents_on_guid", using: :btree
@@ -67,8 +64,8 @@ ActiveRecord::Schema.define(version: 20140901143732) do
   create_table "delayed_jobs", force: true do |t|
     t.integer  "priority",                    default: 0
     t.integer  "attempts",                    default: 0
-    t.text     "handler",    limit: 16777215,                          charset: "utf8mb4", collation: "utf8mb4_bin"
-    t.text     "last_error", limit: 16777215,                          charset: "utf8mb4", collation: "utf8mb4_bin"
+    t.text     "handler",    limit: 16777215,             charset: "utf8mb4", collation: "utf8mb4_bin"
+    t.text     "last_error",                              charset: "utf8mb4", collation: "utf8mb4_bin"
     t.datetime "run_at"
     t.datetime "locked_at"
     t.datetime "failed_at"
@@ -83,11 +80,11 @@ ActiveRecord::Schema.define(version: 20140901143732) do
   create_table "events", force: true do |t|
     t.integer  "user_id"
     t.integer  "agent_id"
-    t.decimal  "lat",                           precision: 15, scale: 10
-    t.decimal  "lng",                           precision: 15, scale: 10
-    t.text     "payload",    limit: 2147483647,                                        charset: "utf8mb4", collation: "utf8mb4_bin"
-    t.datetime "created_at",                                              null: false
-    t.datetime "updated_at",                                              null: false
+    t.decimal  "lat",                         precision: 15, scale: 10
+    t.decimal  "lng",                         precision: 15, scale: 10
+    t.text     "payload",    limit: 16777215,                           charset: "utf8mb4", collation: "utf8mb4_bin"
+    t.datetime "created_at"
+    t.datetime "updated_at"
     t.datetime "expires_at"
   end
 
@@ -117,13 +114,13 @@ ActiveRecord::Schema.define(version: 20140901143732) do
   add_index "scenario_memberships", ["scenario_id"], name: "index_scenario_memberships_on_scenario_id", using: :btree
 
   create_table "scenarios", force: true do |t|
-    t.string   "name",                        null: false, charset: "utf8mb4", collation: "utf8mb4_bin"
-    t.integer  "user_id",                     null: false
+    t.string   "name",                         null: false, charset: "utf8mb4", collation: "utf8mb4_bin"
+    t.integer  "user_id",                      null: false
     t.datetime "created_at"
     t.datetime "updated_at"
-    t.text     "description",                              charset: "utf8mb4", collation: "utf8mb4_bin"
-    t.boolean  "public",      default: false, null: false
-    t.string   "guid",                        null: false, charset: "ascii",   collation: "ascii_bin"
+    t.text     "description",                               charset: "utf8mb4", collation: "utf8mb4_bin"
+    t.boolean  "public",       default: false, null: false
+    t.string   "guid",                         null: false, charset: "ascii",   collation: "ascii_bin"
     t.string   "source_url"
     t.string   "tag_bg_color"
     t.string   "tag_fg_color"
@@ -154,8 +151,8 @@ ActiveRecord::Schema.define(version: 20140901143732) do
     t.integer  "user_id",                           null: false
     t.string   "credential_name",                   null: false
     t.text     "credential_value",                  null: false
-    t.datetime "created_at",                        null: false
-    t.datetime "updated_at",                        null: false
+    t.datetime "created_at"
+    t.datetime "updated_at"
     t.string   "mode",             default: "text", null: false, collation: "utf8_bin"
   end
 
@@ -172,8 +169,8 @@ ActiveRecord::Schema.define(version: 20140901143732) do
     t.datetime "last_sign_in_at"
     t.string   "current_sign_in_ip"
     t.string   "last_sign_in_ip"
-    t.datetime "created_at",                                         null: false
-    t.datetime "updated_at",                                         null: false
+    t.datetime "created_at"
+    t.datetime "updated_at"
     t.boolean  "admin",                              default: false, null: false
     t.integer  "failed_attempts",                    default: 0
     t.string   "unlock_token"

+ 2 - 0
spec/env.test

@@ -1,6 +1,8 @@
 APP_SECRET_TOKEN=notarealappsecrettoken
 TWITTER_OAUTH_KEY=twitteroauthkey
 TWITTER_OAUTH_SECRET=twitteroauthsecret
+TUMBLR_OAUTH_KEY=tumblroauthsecret
+TUMBLR_OAUTH_SECRET=tumblroauthsecret
 THIRTY_SEVEN_SIGNALS_OAUTH_KEY=TESTKEY
 THIRTY_SEVEN_SIGNALS_OAUTH_SECRET=TESTSECRET
 FAILED_JOBS_TO_KEEP=2

+ 38 - 0
spec/models/agents/tumblr_publish_agent_spec.rb

@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Agents::TumblrPublishAgent do
+  before do
+    @opts = {
+      :blog_name => "huginnbot.tumblr.com",
+      :post_type => "text",
+      :expected_update_period_in_days => "2",
+      :options => {
+        :title => "{{title}}",
+        :body => "{{body}}",
+      },
+    }
+
+    @checker = Agents::TumblrPublishAgent.new(:name => "HuginnBot", :options => @opts)
+    @checker.service = services(:generic)
+    @checker.user = users(:bob)
+    @checker.save!
+
+    @event = Event.new
+    @event.agent = agents(:bob_weather_agent)
+    @event.payload = { :title => "Gonna rain...", :body => 'San Francisco is gonna get wet' }
+    @event.save!
+
+    stub.any_instance_of(Agents::TumblrPublishAgent).tumblr {
+      stub!.text(anything, anything) { { "id" => "5" } }
+    }
+  end
+
+  describe '#receive' do
+    it 'should publish any payload it receives' do
+      Agents::TumblrPublishAgent.async_receive(@checker.id, [@event.id])
+      @checker.events.count.should eq(1)
+      @checker.events.first.payload['post_id'].should eq('5')
+      @checker.events.first.payload['published_post'].should eq('[huginnbot.tumblr.com] text')
+    end
+  end
+end