cartoonist-twitter 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *~
2
+ /cartoonist-twitter*.gem
@@ -0,0 +1,38 @@
1
+ class Admin::TweetsController < CartoonistController
2
+ before_filter :ensure_ssl!
3
+ before_filter :check_admin!
4
+
5
+ def index
6
+ @untweeted = Tweet.untweeted.created_chronological.not_disabled
7
+ @tweeted = Tweet.tweeted.reverse_chronological
8
+ end
9
+
10
+ def edit
11
+ @tweet = Tweet.find params[:id].to_i
12
+ end
13
+
14
+ def update
15
+ @tweet = Tweet.update_tweet params
16
+ send_tweet_if_requested
17
+ tweet_or_entity_redirect
18
+ end
19
+
20
+ def create
21
+ @tweet = Tweet.create_tweet params
22
+ send_tweet_if_requested
23
+ tweet_or_entity_redirect
24
+ end
25
+
26
+ private
27
+ def send_tweet_if_requested
28
+ @tweet.manual_tweet! if params[:tweet_now].present?
29
+ end
30
+
31
+ def tweet_or_entity_redirect
32
+ if params[:destination] == "entity_edit" && @tweet.entity.entity_edit_url
33
+ redirect_to @tweet.entity.entity_edit_url
34
+ else
35
+ redirect_to "/admin/tweets/#{@tweet.id}/edit"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,214 @@
1
+ class Tweet < ActiveRecord::Base
2
+ validate :doesnt_update_tweet_after_tweeted
3
+ validate :entity_doesnt_change, :on => :update
4
+ attr_accessible :entity_id, :entity_type, :tweet, :tweeted_at
5
+
6
+ def allow_tweet_now?
7
+ if entity.kind_of? Postable
8
+ posted = entity.posted?
9
+ else
10
+ posted = true
11
+ end
12
+
13
+ allow_save? && !disabled? && posted
14
+ end
15
+
16
+ def allow_save?
17
+ !tweeted?
18
+ end
19
+
20
+ def disabled?
21
+ tweet_style == :disabled
22
+ end
23
+
24
+ def entity
25
+ @entity ||= Cartoonist::Entity[entity_type.to_sym].find(entity_id)
26
+ end
27
+
28
+ def description
29
+ "#{entity.entity_localized_label}: #{entity.entity_description}"
30
+ end
31
+
32
+ def formatted_tweeted_at(default_msg = "not yet tweeted")
33
+ return default_msg unless tweeted_at
34
+ tweeted_at.localtime.strftime "%-m/%-d/%Y at %-l:%M %P"
35
+ end
36
+
37
+ def manual_tweet!
38
+ raise "Tweeting is not allowed!" unless allow_tweet_now?
39
+ return if tweeted?
40
+ return if disabled?
41
+ send_tweet!
42
+ end
43
+
44
+ def auto_tweet!
45
+ return if tweeted?
46
+ return unless expected_tweet_time
47
+ return unless Time.now >= expected_tweet_time
48
+ send_tweet!
49
+ end
50
+
51
+ def tweet_style
52
+ Setting[:"#{entity.entity_type}_tweet_style"]
53
+ end
54
+
55
+ def tweet_time
56
+ Setting[:"#{entity.entity_type}_tweet_time"]
57
+ end
58
+
59
+ def form_path
60
+ if new_record?
61
+ "/admin/tweets"
62
+ else
63
+ "/admin/tweets/#{id}"
64
+ end
65
+ end
66
+
67
+ def form_method
68
+ if new_record?
69
+ :post
70
+ else
71
+ :put
72
+ end
73
+ end
74
+
75
+ def expected_tweet_time
76
+ case tweet_style
77
+ when :disabled
78
+ nil
79
+ when :manual
80
+ nil
81
+ when :automatic
82
+ entity.posted_at
83
+ when :automatic_timed
84
+ if tweet_time.blank?
85
+ entity.posted_at
86
+ elsif entity.posted_at
87
+ parsed = DateTime.strptime("#{entity.posted_at.year}-#{entity.posted_at.month}-#{entity.posted_at.day} #{tweet_time.downcase}", "%Y-%m-%d %I:%M %p").to_time
88
+ result = Time.local entity.posted_at.year, entity.posted_at.month, entity.posted_at.day, parsed.hour, parsed.min
89
+ result = result + 1.day if result < entity.posted_at.to_time
90
+ result
91
+ end
92
+ else
93
+ raise "Invalid tweet style #{tweet_style}"
94
+ end
95
+ end
96
+
97
+ def tweeted?
98
+ tweeted_at.present?
99
+ end
100
+
101
+ private
102
+ def send_tweet!
103
+ if Rails.env.production?
104
+ Twitter.update tweet
105
+ else
106
+ logger.info "Fake Tweet: #{tweet}"
107
+ end
108
+
109
+ self.tweeted_at = Time.now
110
+ save!
111
+ end
112
+
113
+ def doesnt_update_tweet_after_tweeted
114
+ if !tweeted_at_was.nil? && tweet_changed?
115
+ errors.add :tweet, "can't change after the tweet has been sent"
116
+ end
117
+ end
118
+
119
+ def entity_doesnt_change
120
+ if entity_id_changed?
121
+ errors.add :entity_id, "can't change"
122
+ end
123
+
124
+ if entity_type_changed?
125
+ errors.add :entity_id, "can't change"
126
+ end
127
+ end
128
+
129
+ class << self
130
+ def update_tweet(params)
131
+ tweet = find params[:id].to_i
132
+ raise "Saving is not allowed!" unless tweet.allow_save?
133
+ tweet.tweet = params[:tweet]
134
+ tweet.save!
135
+ tweet
136
+ end
137
+
138
+ def create_tweet(params)
139
+ raise "The entity_id is required!" if params[:entity_id].blank?
140
+ raise "The entity_type is required!" if params[:entity_type].blank?
141
+ create :entity_id => params[:entity_id].to_i, :entity_type => params[:entity_type], :tweet => params[:tweet]
142
+ end
143
+
144
+ def tweet_for(entity)
145
+ result = find_for entity
146
+
147
+ if result
148
+ result
149
+ else
150
+ new options_for(entity)
151
+ end
152
+ end
153
+
154
+ def create_for(entity)
155
+ create options_for(entity)
156
+ end
157
+
158
+ def options_for(entity)
159
+ { :entity_id => entity.id, :entity_type => entity.entity_type, :tweet => SimpleTemplate[Setting[:"#{entity.entity_type}_default_tweet"], :self => entity] }
160
+ end
161
+
162
+ def find_for(entity)
163
+ raise "Can only find tweets for entities!" unless entity.kind_of? Entity
164
+ where({ :entity_id => entity.id, :entity_type => entity.entity_type }).first
165
+ end
166
+
167
+ def tweeted
168
+ where "tweeted_at IS NOT NULL"
169
+ end
170
+
171
+ def untweeted
172
+ where "tweeted_at IS NULL"
173
+ end
174
+
175
+ def chronological
176
+ order "tweeted_at ASC"
177
+ end
178
+
179
+ def reverse_chronological
180
+ order "tweeted_at DESC"
181
+ end
182
+
183
+ def created_chronological
184
+ order "created_at ASC"
185
+ end
186
+
187
+ def created_reverse_chronological
188
+ order "created_at DESC"
189
+ end
190
+
191
+ def not_disabled
192
+ result = all
193
+
194
+ types = result.map(&:entity_type).uniq.select do |t|
195
+ Setting[:"#{t}_tweet_style"] != :disabled
196
+ end
197
+
198
+ result.select { |x| types.include? x.entity_type }
199
+ end
200
+
201
+ def styles(entity)
202
+ results = []
203
+ results << { :value => :disabled, :label => "tweet.style.disabled" }
204
+ results << { :value => :manual, :label => "tweet.style.manual" }
205
+
206
+ if Cartoonist::Entity[entity].include? Postable
207
+ results << { :value => :automatic, :label => "tweet.style.automatic" }
208
+ results << { :value => :automatic_timed, :label => "tweet.style.automatic_timed" }
209
+ end
210
+
211
+ results
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,22 @@
1
+ <% tweet = Tweet.tweet_for entity %>
2
+ <% if tweet.tweet_style != :disabled %>
3
+ <% destination = :entity_edit if destination.nil? %>
4
+ <%= form_tag tweet.form_path, :method => tweet.form_method do %>
5
+ <input type="hidden" name="entity_id" value="<%= tweet.entity_id %>" />
6
+ <input type="hidden" name="entity_type" value="<%= tweet.entity_type %>" />
7
+ <input type="hidden" name="destination" value="<%= destination %>" />
8
+
9
+ <p>
10
+ <%= t "admin.tweets.entity_tweet.tweet" %><br />
11
+ <textarea name="tweet" rows="2" cols="100" <%= 'disabled="disabled"'.html_safe unless tweet.allow_save? %>><%= tweet.tweet %></textarea>
12
+ </p>
13
+
14
+ <p>
15
+ <input type="submit" name="save" value="<%= t "admin.tweets.entity_tweet.save" %>" <%= 'disabled="disabled"'.html_safe unless tweet.allow_save? %> />
16
+ </p>
17
+
18
+ <p>
19
+ <input type="submit" name="tweet_now" value="<%= t "admin.tweets.entity_tweet.tweet_now" %>" <%= 'disabled="disabled"'.html_safe unless tweet.allow_tweet_now? %> />
20
+ </p>
21
+ <% end %>
22
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <p>
2
+ <%= @tweet.description %>
3
+ </p>
4
+
5
+ <%= render :partial => "admin/tweets/entity_tweet", :locals => { :entity => @tweet.entity, :destination => :tweet } %>
@@ -0,0 +1,21 @@
1
+ <h2><%= t "admin.tweets.index.not_tweeted" %></h2>
2
+
3
+ <ul>
4
+ <% @untweeted.each do |tweet| %>
5
+ <li>
6
+ <%= tweet.formatted_tweeted_at "" %>
7
+ <a href="/admin/tweets/<%= tweet.id %>/edit"><%= tweet.description %></a>
8
+ </li>
9
+ <% end %>
10
+ </ul>
11
+
12
+ <h2><%= t "admin.tweets.index.tweeted" %></h2>
13
+
14
+ <ul>
15
+ <% @tweeted.each do |tweet| %>
16
+ <li>
17
+ <%= tweet.formatted_tweeted_at "" %>
18
+ <a href="/admin/tweets/<%= tweet.id %>/edit"><%= tweet.description %></a>
19
+ </li>
20
+ <% end %>
21
+ </ul>
@@ -0,0 +1,3 @@
1
+ <% content_for :page_title, t("admin.tweets.layout.section") %>
2
+ <% content_for(:content) { yield } %>
3
+ <%= render :template => "layouts/admin" %>
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ raise "Cannot find version file!" unless File.exists?(File.join(File.dirname(__FILE__), "../CARTOONIST_VERSION"))
3
+ cartoonist_version = File.read(File.join(File.dirname(__FILE__), "../CARTOONIST_VERSION")).strip
4
+ s.name = "cartoonist-twitter"
5
+ s.version = cartoonist_version
6
+ s.date = Time.now.strftime "%Y-%m-%d"
7
+ s.summary = "Cartoonist Twitter"
8
+ s.description = "Plugin to allow tweeting of cartoonist entities."
9
+ s.authors = ["Mike Virata-Stone"]
10
+ s.email = "reasonnumber@gmail.com"
11
+ s.files = `git ls-files`.split("\n")
12
+ s.require_paths = ["lib"]
13
+ s.homepage = "http://reasonnumber.com/cartoonist"
14
+ s.add_dependency "cartoonist", cartoonist_version
15
+ s.add_dependency "twitter", "~> 2.2.0"
16
+ end
@@ -0,0 +1,43 @@
1
+ en:
2
+ admin:
3
+ layout:
4
+ tab:
5
+ tweets: Tweets
6
+ tweets:
7
+ entity_tweet:
8
+ save: Save
9
+ tweet: "Tweet:"
10
+ tweet_now: Tweet Now
11
+ index:
12
+ not_tweeted: Not yet tweeted
13
+ tweeted: Tweeted
14
+ layout:
15
+ section: Tweets
16
+ cartoonist:
17
+ layout:
18
+ navigation:
19
+ follow_on_twitter: Follow us on Twitter
20
+ follow_on_twitter_title: Follow us on Twitter
21
+ settings:
22
+ show:
23
+ errors:
24
+ invalid_tweet_time: "Invalid tweet time '%{value}' should be formatted like: '1:22 pm' or '11:30 am'"
25
+ sections:
26
+ social_and_analytics:
27
+ twitter: Twitter
28
+ settings:
29
+ default_tweet: "%{entity} Default Tweet: "
30
+ tweet_style: "%{entity} Tweet Style: "
31
+ tweet_time: "%{entity} Tweet Time: "
32
+ tweet_time_info: "Time to tweet when style is \"Automatic at a specific time\" (format example: 3:30 pm)"
33
+ twitter_consumer_key: "Twitter Consumer Key: "
34
+ twitter_consumer_secret: "Twitter Consumer Secret: "
35
+ twitter_handle: "Twitter Handle: "
36
+ twitter_oauth_token: "Twitter Oauth Token: "
37
+ twitter_oauth_token_secret: "Twitter Oauth Token Secret: "
38
+ tweet:
39
+ style:
40
+ automatic: Automatic after posted
41
+ automatic_timed: Automatic at a specific time
42
+ disabled: Disabled
43
+ manual: Manual only
@@ -0,0 +1,14 @@
1
+ class CreateTweets < ActiveRecord::Migration
2
+ def change
3
+ create_table :tweets do |t|
4
+ t.integer :entity_id, :null => false
5
+ t.string :entity_type, :null => false
6
+ t.string :tweet, :null => false, :length => 140
7
+ t.datetime :tweeted_at
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :tweets, :tweeted_at
12
+ add_index :tweets, [:entity_id, :entity_type], :unique => true
13
+ end
14
+ end
@@ -0,0 +1,67 @@
1
+ module CartoonistTwitter
2
+ class EntityHooks
3
+ class << self
4
+ def after_entity_save(entity)
5
+ return if Setting[:"#{entity.entity_type}_tweet_style"] == :disabled
6
+ result = Tweet.find_for entity
7
+ Tweet.create_for entity unless result
8
+ end
9
+
10
+ def edit_entity_before_partial
11
+ "admin/tweets/entity_tweet"
12
+ end
13
+ end
14
+ end
15
+
16
+ class Engine < ::Rails::Engine
17
+ config.to_prepare do
18
+ twitter_auth_changed = lambda do
19
+ Twitter.configure do |twitter_config|
20
+ twitter_config.consumer_key = Setting[:twitter_consumer_key]
21
+ twitter_config.consumer_secret = Setting[:twitter_consumer_secret]
22
+ twitter_config.oauth_token = Setting[:twitter_oauth_token]
23
+ twitter_config.oauth_token_secret = Setting[:twitter_oauth_token_secret]
24
+ end
25
+ end
26
+
27
+ Setting::Section.define :twitter, :order => 2, :tab => :social_and_analytics do
28
+ order = 0
29
+
30
+ Cartoonist::Entity.all.each do |entity|
31
+ Setting.define :"#{entity.entity_type}_tweet_style", :type => :symbol, :order => (order += 1), :label => lambda { I18n.t("settings.show.settings.tweet_style", :entity => entity.entity_localized_label) }, :select_from => lambda { Tweet.styles(entity.entity_type) }, :default => :disabled
32
+ Setting.define :"#{entity.entity_type}_tweet_time", :order => (order += 1), :label => lambda { I18n.t("settings.show.settings.tweet_time", :entity => entity.entity_localized_label) }, :info_label => "settings.show.settings.tweet_time_info", :validation => lambda { |value| raise Setting::InvalidError.new I18n.t("settings.show.errors.invalid_tweet_time", :value => value) unless value =~ /^((1[0-2]|[1-9])\:[0-5]\d (am|pm|AM|PM))?$/ }
33
+ Setting.define :"#{entity.entity_type}_default_tweet", :order => (order += 1), :label => lambda { I18n.t("settings.show.settings.default_tweet", :entity => entity.entity_localized_label) }
34
+ end
35
+
36
+ Setting.define :twitter_handle, :order => (order += 1)
37
+ Setting.define :twitter_consumer_key, :onchange => twitter_auth_changed, :order => (order += 1)
38
+ Setting.define :twitter_consumer_secret, :onchange => twitter_auth_changed, :order => (order += 1)
39
+ Setting.define :twitter_oauth_token, :onchange => twitter_auth_changed, :order => (order += 1)
40
+ Setting.define :twitter_oauth_token_secret, :onchange => twitter_auth_changed, :order => (order += 1)
41
+ end
42
+
43
+ twitter_auth_changed.call
44
+ end
45
+
46
+ Cartoonist::Admin::Tab.add :tweets, :url => "/admin/tweets"
47
+ Cartoonist::Navigation::Link.add :url => (lambda { "https://twitter.com/#{Setting[:twitter_handle]}" }), :class => "follow-us", :label => "cartoonist.layout.navigation.follow_on_twitter", :title => "cartoonist.layout.navigation.follow_on_twitter_title", :order => 2
48
+ Cartoonist::Migration.add_for self
49
+ Cartoonist::Entity.register_hooks CartoonistTwitter::EntityHooks
50
+
51
+ Cartoonist::Backup.for :tweets do
52
+ Tweet.order(:id).all
53
+ end
54
+
55
+ Cartoonist::Cron.add do
56
+ Tweet.untweeted.each do |tweet|
57
+ tweet.auto_tweet!
58
+ end
59
+ end
60
+
61
+ Cartoonist::Routes.add do
62
+ namespace :admin do
63
+ resources :tweets
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ require "twitter"
2
+
3
+ module CartoonistTwitter
4
+ require "cartoonist-twitter/engine"
5
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cartoonist-twitter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.9
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mike Virata-Stone
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cartoonist
16
+ requirement: &17879120 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 0.0.9
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *17879120
25
+ - !ruby/object:Gem::Dependency
26
+ name: twitter
27
+ requirement: &17878040 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 2.2.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *17878040
36
+ description: Plugin to allow tweeting of cartoonist entities.
37
+ email: reasonnumber@gmail.com
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - .gitignore
43
+ - app/controllers/admin/tweets_controller.rb
44
+ - app/models/tweet.rb
45
+ - app/views/admin/tweets/_entity_tweet.html.erb
46
+ - app/views/admin/tweets/edit.html.erb
47
+ - app/views/admin/tweets/index.html.erb
48
+ - app/views/layouts/admin/tweets.html.erb
49
+ - cartoonist-twitter.gemspec
50
+ - config/locales/en.yml
51
+ - db/migrate/20120501083235_create_tweets.rb
52
+ - lib/cartoonist-twitter.rb
53
+ - lib/cartoonist-twitter/engine.rb
54
+ homepage: http://reasonnumber.com/cartoonist
55
+ licenses: []
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 1.8.17
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Cartoonist Twitter
78
+ test_files: []