neufelry-twitter 0.4.2

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.
Files changed (68) hide show
  1. data/History +122 -0
  2. data/License +19 -0
  3. data/Manifest +66 -0
  4. data/README +84 -0
  5. data/Rakefile +40 -0
  6. data/bin/twitter +14 -0
  7. data/examples/blocks.rb +15 -0
  8. data/examples/direct_messages.rb +29 -0
  9. data/examples/favorites.rb +20 -0
  10. data/examples/friends_followers.rb +25 -0
  11. data/examples/friendships.rb +13 -0
  12. data/examples/identica_timeline.rb +7 -0
  13. data/examples/location.rb +8 -0
  14. data/examples/posting.rb +9 -0
  15. data/examples/replies.rb +27 -0
  16. data/examples/search.rb +18 -0
  17. data/examples/sent_messages.rb +27 -0
  18. data/examples/timeline.rb +34 -0
  19. data/examples/twitter.rb +27 -0
  20. data/examples/verify_credentials.rb +13 -0
  21. data/lib/twitter.rb +42 -0
  22. data/lib/twitter/base.rb +270 -0
  23. data/lib/twitter/cli.rb +334 -0
  24. data/lib/twitter/cli/config.rb +9 -0
  25. data/lib/twitter/cli/helpers.rb +109 -0
  26. data/lib/twitter/cli/migrations/20080722194500_create_accounts.rb +13 -0
  27. data/lib/twitter/cli/migrations/20080722194508_create_tweets.rb +16 -0
  28. data/lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb +9 -0
  29. data/lib/twitter/cli/migrations/20080722214606_create_configurations.rb +13 -0
  30. data/lib/twitter/cli/models/account.rb +33 -0
  31. data/lib/twitter/cli/models/configuration.rb +13 -0
  32. data/lib/twitter/cli/models/tweet.rb +20 -0
  33. data/lib/twitter/direct_message.rb +22 -0
  34. data/lib/twitter/easy_class_maker.rb +43 -0
  35. data/lib/twitter/rate_limit_status.rb +19 -0
  36. data/lib/twitter/search.rb +101 -0
  37. data/lib/twitter/search_result.rb +83 -0
  38. data/lib/twitter/search_result_info.rb +82 -0
  39. data/lib/twitter/status.rb +22 -0
  40. data/lib/twitter/user.rb +37 -0
  41. data/lib/twitter/version.rb +3 -0
  42. data/spec/base_spec.rb +127 -0
  43. data/spec/cli/helper_spec.rb +49 -0
  44. data/spec/direct_message_spec.rb +35 -0
  45. data/spec/fixtures/followers.xml +706 -0
  46. data/spec/fixtures/friends.xml +609 -0
  47. data/spec/fixtures/friends_for.xml +584 -0
  48. data/spec/fixtures/friends_lite.xml +192 -0
  49. data/spec/fixtures/friends_timeline.xml +66 -0
  50. data/spec/fixtures/friendship_already_exists.xml +5 -0
  51. data/spec/fixtures/friendship_created.xml +12 -0
  52. data/spec/fixtures/public_timeline.xml +148 -0
  53. data/spec/fixtures/rate_limit_status.xml +7 -0
  54. data/spec/fixtures/search_result_info.yml +147 -0
  55. data/spec/fixtures/search_results.json +1 -0
  56. data/spec/fixtures/status.xml +25 -0
  57. data/spec/fixtures/user.xml +38 -0
  58. data/spec/fixtures/user_timeline.xml +465 -0
  59. data/spec/search_spec.rb +100 -0
  60. data/spec/spec.opts +1 -0
  61. data/spec/spec_helper.rb +23 -0
  62. data/spec/status_spec.rb +40 -0
  63. data/spec/user_spec.rb +42 -0
  64. data/twitter.gemspec +45 -0
  65. data/website/css/common.css +47 -0
  66. data/website/images/terminal_output.png +0 -0
  67. data/website/index.html +159 -0
  68. metadata +181 -0
@@ -0,0 +1,334 @@
1
+ require 'rubygems'
2
+
3
+ gem 'main', '>= 2.8.2'
4
+ gem 'highline', '>= 1.4.0'
5
+ gem 'activerecord', '= 2.2.2'
6
+ gem 'sqlite3-ruby', '>= 1.2.1'
7
+
8
+ require 'main'
9
+ require 'highline/import'
10
+ require 'activerecord'
11
+ require 'sqlite3'
12
+
13
+ HighLine.track_eof = false
14
+ CLI_ROOT = File.expand_path(File.join(File.dirname(__FILE__), 'cli'))
15
+ require CLI_ROOT + '/config'
16
+ require CLI_ROOT + '/helpers'
17
+ Dir[CLI_ROOT + '/models/*.rb'].each { |m| require m }
18
+
19
+ include Twitter::CLI::Helpers
20
+
21
+ Main {
22
+ def run
23
+ puts "twitter [command] --help for usage instructions."
24
+ puts "The available commands are: \n install, uninstall, add, remove, list, change, post, befriend, defriend, follow, leave, d and timeline."
25
+ end
26
+
27
+ mode 'install' do
28
+ description 'Creates the sqlite3 database and runs the migrations.'
29
+ def run
30
+ migrate
31
+ attempt_import
32
+ say 'Twitter installed.'
33
+ end
34
+ end
35
+
36
+ mode 'uninstall' do
37
+ description 'Removes the sqlite3 database. There is no undo for this.'
38
+ def run
39
+ FileUtils.rm(Twitter::CLI::Config[:database]) if File.exists?(Twitter::CLI::Config[:database])
40
+ say 'Twitter gem uninstalled.'
41
+ end
42
+ end
43
+
44
+ mode 'add' do
45
+ description 'Adds a new twitter account to the database. Prompts for username and password.'
46
+ argument('username', 'u') {
47
+ optional
48
+ description 'optional username'
49
+ }
50
+ argument('password', 'p') {
51
+ optional
52
+ description 'optional password'
53
+ }
54
+
55
+ def run
56
+ account = Hash.new
57
+ say "Add New Account:"
58
+
59
+ # allows optional username arg
60
+ if params['username'].given?
61
+ account[:username] = params['username'].value
62
+ else
63
+ account[:username] = ask('Username: ') do |q|
64
+ q.validate = /\S+/
65
+ end
66
+ end
67
+
68
+ # allows optional password arg
69
+ if params['password'].given?
70
+ account[:password] = params['password'].value
71
+ else
72
+ account[:password] = ask("Password (won't be displayed): ") do |q|
73
+ q.echo = false
74
+ q.validate = /\S+/
75
+ end
76
+ end
77
+
78
+ do_work do
79
+ base(account[:username], account[:password]).verify_credentials
80
+ Account.add(account)
81
+ say 'Account added.'
82
+ end
83
+ end
84
+ end
85
+
86
+ mode 'remove' do
87
+ description 'Removes a twitter account from the database. If username provided it removes that username else it prompts with list and asks for which one you would like to remove.'
88
+ argument( 'username' ) {
89
+ optional
90
+ description 'username of account you would like to remove'
91
+ }
92
+
93
+ def run
94
+ do_work do
95
+ if params['username'].given?
96
+ account = Account.find_by_username(params['username'].value)
97
+ else
98
+ Account.find(:all, :order => 'username').each do |a|
99
+ say "#{a.id}. #{a}"
100
+ end
101
+ account_id = ask 'Account to remove (enter number): ' do |q|
102
+ q.validate = /\d+/
103
+ end
104
+ end
105
+
106
+ begin
107
+ account = account_id ? Account.find(account_id) : account
108
+ account_name = account.username
109
+ account.destroy
110
+ Account.set_current(Account.first) if Account.new_active_needed?
111
+ say "#{account_name} has been removed.\n"
112
+ rescue ActiveRecord::RecordNotFound
113
+ say "ERROR: Account could not be found. Try again. \n"
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ mode 'list' do
120
+ description 'Lists all the accounts that have been added and puts a * by the current one that is used for posting, etc.'
121
+ def run
122
+ do_work do
123
+ if Account.count == 0
124
+ say 'No accounts have been added.'
125
+ else
126
+ say 'Account List'
127
+ Account.find(:all, :order => 'username').each do |a|
128
+ say a
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ mode 'change' do
136
+ description 'Changes the current account being used for posting etc. to the username provided. If no username is provided, a list is presented and you can choose the account from there.'
137
+ argument( 'username' ) {
138
+ optional
139
+ description 'username of account you would like to switched to'
140
+ }
141
+
142
+ def run
143
+ do_work do
144
+ if params['username'].given?
145
+ new_current = Account.find_by_username(params['username'].value)
146
+ else
147
+ Account.find(:all, :order => 'username').each do |a|
148
+ say "#{a.id}. #{a}"
149
+ end
150
+ new_current = ask 'Change current account to (enter number): ' do |q|
151
+ q.validate = /\d+/
152
+ end
153
+ end
154
+
155
+ begin
156
+ current = Account.set_current(new_current)
157
+ say "#{current} is now the current account.\n"
158
+ rescue ActiveRecord::RecordNotFound
159
+ say "ERROR: Account could not be found. Try again. \n"
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ mode 'post' do
166
+ description "Posts a message to twitter using the current account. The following are all valid examples from the command line:
167
+ $ twitter post 'my update'
168
+ $ twitter post my update with quotes
169
+ $ echo 'my update from stdin' | twitter post"
170
+ def run
171
+ do_work do
172
+ post = ARGV.size > 1 ? ARGV.join(" ") : ARGV.shift
173
+ say "Sending twitter update"
174
+ finished, status = false, nil
175
+ progress_thread = Thread.new { until finished; print "."; $stdout.flush; sleep 0.5; end; }
176
+ post_thread = Thread.new(binding()) do |b|
177
+ status = base.post(post, :source => Twitter::SourceName)
178
+ finished = true
179
+ end
180
+ post_thread.join
181
+ progress_thread.join
182
+ say "Got it! New tweet created at: #{status.created_at}\n"
183
+ end
184
+ end
185
+ end
186
+
187
+ mode 'befriend' do
188
+ description "Allows you to add a user as a friend"
189
+ argument('username') {
190
+ required
191
+ description 'username or id of twitterrer to befriend'
192
+ }
193
+
194
+ def run
195
+ do_work do
196
+ username = params['username'].value
197
+ base.create_friendship(username)
198
+ say "#{username} has been added as a friend. follow notifications with 'twitter follow #{username}'"
199
+ end
200
+ end
201
+ end
202
+
203
+ mode 'defriend' do
204
+ description "Allows you to remove a user from being a friend"
205
+ argument('username') {
206
+ required
207
+ description 'username or id of twitterrer to defriend'
208
+ }
209
+
210
+ def run
211
+ do_work do
212
+ username = params['username'].value
213
+ base.destroy_friendship(username)
214
+ say "#{username} has been removed from your friends"
215
+ end
216
+ end
217
+ end
218
+
219
+ mode 'follow' do
220
+ description "Allows you to add notifications for a user (aka Follow Them)"
221
+ argument('username') {
222
+ required
223
+ description 'username or id of twitterrer to follow'
224
+ }
225
+
226
+ def run
227
+ do_work do
228
+ username = params['username'].value
229
+ base.follow(username)
230
+ say "You are now following notifications from #{username}"
231
+ end
232
+ end
233
+ end
234
+
235
+ mode 'leave' do
236
+ description "Allows you to turn off notifications for a user"
237
+ argument('username') {
238
+ required
239
+ description 'username or id of twitterrer to leave'
240
+ }
241
+
242
+ def run
243
+ do_work do
244
+ username = params['username'].value
245
+ base.leave(username)
246
+ say "You are no longer following notifications from #{username}"
247
+ end
248
+ end
249
+ end
250
+
251
+ mode 'd' do
252
+ description "Allows you to direct message a user. The following are all valid examples from the command line:
253
+ $ twitter d jnunemaker 'yo homeboy'
254
+ $ twitter d jnunemaker yo homeboy
255
+ $ echo 'yo homeboy' | twitter d jnunemaker"
256
+ argument('username') {
257
+ required
258
+ description 'username or id of twitterrer to direct message'
259
+ }
260
+
261
+ def run
262
+ do_work do
263
+ username = params['username'].value
264
+ post = ARGV.size > 1 ? ARGV.join(" ") : ARGV.shift
265
+ base.d(username, post)
266
+ say "Direct message sent to #{username}"
267
+ end
268
+ end
269
+ end
270
+
271
+ mode 'timeline' do
272
+ description "Allows you to view your timeline, your friends or the public one"
273
+ argument( 'timeline' ) {
274
+ description 'the timeline you wish to see (friends, public, me)'
275
+ default 'friends'
276
+ }
277
+ option('force', 'f') {
278
+ description "Ignore since_id and show first page of results even if there aren't new ones"
279
+ }
280
+ option('reverse', 'r') {
281
+ description 'Reverse the output so the oldest tweets are at the top'
282
+ }
283
+
284
+ def run
285
+ do_work do
286
+ timeline = params['timeline'].value == 'me' ? 'user' : params['timeline'].value
287
+ options, since_id = {}, Configuration["#{timeline}_since_id"]
288
+ options[:since_id] = since_id if !since_id.nil? && !params['force'].given?
289
+ reverse = params['reverse'].given? ? true : false
290
+ cache = [:friends, :user].include?(timeline)
291
+ collection = base.timeline(timeline.to_sym, options)
292
+ output_tweets(collection, {:cache => cache, :since_prefix => timeline, :reverse => reverse})
293
+ end
294
+ end
295
+ end
296
+
297
+ mode 'replies' do
298
+ description 'Allows you to view all @replies sent to you'
299
+ option('force', 'f') {
300
+ description "Ignore since_id and show first page of replies even if there aren't new ones"
301
+ }
302
+
303
+ def run
304
+ do_work do
305
+ options, since_id = {}, Configuration["replies_since_id"]
306
+ options[:since_id] = since_id if !since_id.nil? && !params['force'].given?
307
+ collection = base.replies(options)
308
+ output_tweets(collection, {:since_prefix => 'replies'})
309
+ end
310
+ end
311
+ end
312
+
313
+ mode 'clear_config' do
314
+ def run
315
+ do_work do
316
+ count = Configuration.count
317
+ Configuration.destroy_all
318
+ say("#{count} configuration entries cleared.")
319
+ end
320
+ end
321
+ end
322
+
323
+ mode 'open' do
324
+ description 'Opens the given twitter user in a browser window'
325
+ argument('username') {
326
+ required
327
+ description "username or id of twitterrer who's page you would like to see"
328
+ }
329
+
330
+ def run
331
+ `open http://twitter.com/#{params['username'].value}`
332
+ end
333
+ end
334
+ }
@@ -0,0 +1,9 @@
1
+ module Twitter
2
+ module CLI
3
+ Config = {
4
+ :adapter => 'sqlite3',
5
+ :database => File.join(ENV['HOME'], '.twitter.db'),
6
+ :timeout => 5000
7
+ }
8
+ end
9
+ end
@@ -0,0 +1,109 @@
1
+ module Twitter
2
+ module CLI
3
+ module Helpers
4
+ class NoActiveAccount < StandardError; end
5
+ class NoAccounts < StandardError; end
6
+
7
+ def output_tweets(collection, options={})
8
+ options = {
9
+ :cache => false,
10
+ :since_prefix => '',
11
+ :empty_msg => 'Nothing new since your last check.',
12
+ :reverse => false
13
+ }.merge(options)
14
+
15
+ if collection.size > 0
16
+ justify = collection.collect { |s| s.user.screen_name }.max { |a,b| a.length <=> b.length }.length rescue 0
17
+ indention = ' ' * (justify + 3)
18
+ say("\n#{indention}#{collection.size} new tweet(s) found.\n\n")
19
+ collection.reverse! if options[:reverse]
20
+ collection.each do |s|
21
+ Tweet.create_from_tweet(current_account, s) if options[:cache]
22
+
23
+ occurred_at = Time.parse(s.created_at).strftime('On %b %d at %l:%M%P')
24
+ formatted_time = '-' * occurred_at.length + "\n#{indention}#{occurred_at}"
25
+ formatted_name = s.user.screen_name.rjust(justify + 1)
26
+ formatted_msg = ''
27
+
28
+ s.text.split(' ').each_with_index do |word, idx|
29
+ formatted_msg += "#{word} "
30
+
31
+ sixth_word = idx != 0 && idx % 6 == 0
32
+ formatted_msg += "\n#{indention}" if sixth_word
33
+ end
34
+
35
+ say "#{CGI::unescapeHTML(formatted_name)}: #{CGI::unescapeHTML(formatted_msg)}\n#{indention}#{formatted_time}\n\n"
36
+ end
37
+
38
+ Configuration["#{options[:since_prefix]}_since_id"] = options[:reverse] ? collection.last.id : collection.first.id
39
+ else
40
+ say(options[:empty_msg])
41
+ end
42
+ end
43
+
44
+ def base(username=current_account.username, password=current_account.password)
45
+ @base ||= Twitter::Base.new(username, password)
46
+ end
47
+
48
+ def current_account
49
+ @current_account ||= Account.active
50
+ raise Account.count == 0 ? NoAccounts : NoActiveAccount if @current_account.nil?
51
+ @current_account
52
+ end
53
+
54
+ def attempt_import(&block)
55
+ tweet_file = File.join(ENV['HOME'], '.twitter')
56
+ if File.exists?(tweet_file)
57
+ say '.twitter file found, attempting import...'
58
+ config = YAML::load(File.read(tweet_file))
59
+ if !config['email'].nil? && !config['password'].nil?
60
+ Account.add(:username => config['email'], :password => config['password'])
61
+ say 'Account imported'
62
+ block.call if block_given?
63
+ true
64
+ else
65
+ say "Either your username or password were blank in your .twitter file so I could not import. Use 'twitter add' to add an account."
66
+ false
67
+ end
68
+ end
69
+ end
70
+
71
+ def do_work(&block)
72
+ connect
73
+ begin
74
+ block.call
75
+ rescue Twitter::RateExceeded
76
+ say("Twitter says you've been making too many requests. Wait for a bit and try again.")
77
+ rescue Twitter::Unavailable
78
+ say("Twitter is unavailable right now. Try again later.")
79
+ rescue Twitter::CantConnect => msg
80
+ say("Can't connect to twitter because: #{msg}")
81
+ rescue Twitter::CLI::Helpers::NoActiveAccount
82
+ say("You have not set an active account. Use 'twitter change' to set one now.")
83
+ rescue Twitter::CLI::Helpers::NoAccounts
84
+ unless attempt_import { block.call }
85
+ say("You have not created any accounts. Use 'twitter add' to create one now.")
86
+ end
87
+ end
88
+ end
89
+
90
+ def connect
91
+ ActiveRecord::Base.logger = Logger.new('/tmp/twitter_ar_logger.log')
92
+ ActiveRecord::Base.establish_connection(Twitter::CLI::Config)
93
+ ActiveRecord::Base.connection
94
+ end
95
+
96
+ def migrate
97
+ connect
98
+ ActiveRecord::Migrator.migrate("#{CLI_ROOT}/migrations/")
99
+ end
100
+
101
+ def connect_and_migrate
102
+ say('Attempting to establish connection...')
103
+ connect
104
+ say('Connection established...migrating database...')
105
+ migrate
106
+ end
107
+ end
108
+ end
109
+ end