cartoonist-twitter 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []