t 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
3
  platforms :jruby do
4
4
  gem 'jruby-openssl', '~> 0.7'
data/README.md CHANGED
@@ -1,39 +1,18 @@
1
- # Twitter CLI
1
+ # Twitter CLI [![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium]
2
2
  A command-line interface for Twitter, powered by the [twitter gem][gem]. The
3
3
  CLI attempts to mimic the [Twitter SMS commands][sms] wherever possible,
4
4
  however it offers more commands than are available via SMS.
5
5
 
6
+ [travis]: http://travis-ci.org/sferik/t
7
+ [gemnasium]: https://gemnasium.com/sferik/t
6
8
  [gem]: https://rubygems.org/gems/twitter
7
9
  [sms]: https://support.twitter.com/articles/14020-twitter-sms-command
8
10
 
9
- ## <a name="history"></a>History
10
- ![History](http://twitter.rubyforge.org/images/terminal_output.png "History")
11
-
12
- The [twitter gem][gem] previously contained a command-line interface, up until
13
- version 0.5.0, when it was [removed][]. This project is offered as a sucessor
14
- to that effort, however it is a clean room implementation that contains none of
15
- John Nunemaker's original code.
16
-
17
- [removed]: https://github.com/jnunemaker/twitter/commit/dd2445e3e2c97f38b28a3f32ea902536b3897adf
18
-
19
11
  ## <a name="installation"></a>Installation
20
12
  gem install t
21
13
 
22
- ## <a name="build"></a>Build Status
23
- [![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis]
24
-
25
- [travis]: http://travis-ci.org/sferik/t
26
-
27
- ## <a name="dependencies"></a>Dependency Status
28
- [![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium]
29
14
 
30
- [gemnasium]: https://gemnasium.com/sferik/t
31
-
32
- ## <a name="examples"></a>Usage Examples
33
- Typing `t help` will give you a list of all the available commands. You can
34
- type `t help TASK` to get help for a specific command.
35
-
36
- t help
15
+ ## <a name="configuration"></a>Configuration
37
16
 
38
17
  Because Twitter requires OAuth for most of its functionality, you'll need to
39
18
  register a new application at <http://dev.twitter.com/apps/new>. Once you
@@ -47,8 +26,8 @@ secret, which you can use to authorize your Twitter account.
47
26
  t authorize --consumer-key YOUR_CONSUMER_KEY --consumer-secret YOUR_CONSUMER_SECRET
48
27
 
49
28
  This will open a new browser window where you can authenticate to Twitter and
50
- then enter the returned PIN back into the terminal. Assuming all that works
51
- well, you will be authorized to make requests with the API.
29
+ then enter the returned PIN back into the terminal. Assuming that works,
30
+ you'll be authorized to use the CLI.
52
31
 
53
32
  You can see a list of all the accounts you've authorized.
54
33
 
@@ -62,25 +41,32 @@ You can see a list of all the accounts you've authorized.
62
41
 
63
42
  Notice that one account is marked as the default. To change the default use the
64
43
  `set` subcommand, passing either just the username, if it's unambiguous, or the
65
- username and consumer key pair:
44
+ username and consumer key pair, like so:
66
45
 
67
46
  t set default sferik thG9EfWoADtIr6NjbL9ON
68
47
 
69
48
  Account information is stored in the YAML-formatted file `~/.trc`.
70
49
 
50
+ ## <a name="examples"></a>Usage Examples
51
+
52
+ Typing `t help` will give you a list of all the available commands. You can
53
+ type `t help TASK` to get help for a specific command.
54
+
55
+ t help
56
+
71
57
  ### <a name="update"></a>Update your status
72
58
 
73
59
  t update "I'm tweeting from the command line. Isn't that special?"
74
60
 
75
- ### <a name="dm"></a>Send a user a private message
61
+ ### <a name="dm"></a>Send a direct message
76
62
 
77
- t dm sferik "Want to get dinner together tonight?"
63
+ t dm sferik "Want to get dinner tonight?"
78
64
 
79
65
  ### <a name="location"></a>Update the location field in your profile
80
66
 
81
- t set location San Francisco
67
+ t set location "San Francisco"
82
68
 
83
- ### <a name="get"></a>Retrieve the latest Tweet posted by a user
69
+ ### <a name="get"></a>Get the latest Tweet posted by a user
84
70
 
85
71
  t get sferik
86
72
 
@@ -88,30 +74,70 @@ Account information is stored in the YAML-formatted file `~/.trc`.
88
74
 
89
75
  t whois sferik
90
76
 
91
- ### <a name="stats"></a>Get stats about a user
77
+ ### <a name="stats"></a>Retrieve stats about a user
92
78
 
93
79
  t stats sferik
94
80
 
95
- ### <a name="suggest"></a>Return a listing of users you might enjoy following
81
+ ### <a name="suggest"></a>Return a user you might enjoy following
96
82
 
97
83
  t suggest
98
84
 
99
- ### <a name="follow"></a>Start following a user
85
+ ### <a name="follow-users"></a>Start following users
86
+
87
+ t follow users sferik gem
100
88
 
101
- t follow sferik
89
+ ### <a name="follow-all-followers"></a>Follow all followers (i.e. follow back)
102
90
 
103
- ### <a name="leave"></a>Stop following a user
91
+ t follow all followers
104
92
 
105
- t unfollow sferik
93
+ ### <a name="unfollow-users"></a>Stop following users
106
94
 
107
- ### <a name="timeline"></a>Retrieve the timeline of status updates from users you are following
95
+ t unfollow users sferik gem
108
96
 
109
- t timeline
97
+ ### <a name="unfollow-all-nonfollowers"></a>Unfollow all non-followers
98
+
99
+ t unfollow all nonfollowers
100
+
101
+ ### <a name="list-create"></a>Create a list
102
+
103
+ t list create presidents
104
+
105
+ ### <a name="list-add-followers"></a>Add users to a list
106
+
107
+ t list add users presidents BarackObama Jasonfinn
108
+
109
+ ### <a name="list-add-friends"></a>Add all friends to a list
110
+
111
+ t list add all friends presidents
112
+
113
+ ### <a name="list-add-followers"></a>Add all followers to a list
114
+
115
+ t list add all followers presidents
116
+
117
+ ### <a name="list-add-followers"></a>Add all members of one list to another
118
+
119
+ t list add all listed democrats presidents
120
+
121
+ ### <a name="follow-all-listed"></a>Follow all members of a list
122
+
123
+ t follow all listed presidents
124
+
125
+ ### <a name="unfollow-all-listed"></a>Unfollow all members of a list
126
+
127
+ t unfollow all listed presidents
128
+
129
+ ### <a name="list-timeline"></a>Retrieve the timeline of status updates from a list
130
+
131
+ t list timeline presidents
110
132
 
111
133
  ### <a name="mentions"></a>Retrieve the timeline of status updates that mention you
112
134
 
113
135
  t mentions
114
136
 
137
+ ### <a name="favorites"></a>Retrieve the timeline of status updates that you favorited
138
+
139
+ t favorites
140
+
115
141
  ### <a name="reply"></a>Reply to a Tweet
116
142
 
117
143
  t reply sferik "Thanks Erik"
@@ -124,6 +150,16 @@ Account information is stored in the YAML-formatted file `~/.trc`.
124
150
 
125
151
  t favorite sferik
126
152
 
153
+ ## <a name="history"></a>History
154
+ ![History](http://twitter.rubyforge.org/images/terminal_output.png "History")
155
+
156
+ The [twitter gem][gem] previously contained a command-line interface, up until
157
+ version 0.5.0, when it was [removed][]. This project is offered as a sucessor
158
+ to that effort, however it is a clean room implementation that contains none of
159
+ John Nunemaker's original code.
160
+
161
+ [removed]: https://github.com/jnunemaker/twitter/commit/dd2445e3e2c97f38b28a3f32ea902536b3897adf
162
+
127
163
  ## <a name="contributing"></a>Contributing
128
164
  In the spirit of [free software][fsf], **everyone** is encouraged to help
129
165
  improve this project.
@@ -160,14 +196,11 @@ bug report should include a pull request with failing specs.
160
196
  1. Fork the project.
161
197
  2. Create a topic branch.
162
198
  3. Implement your feature or bug fix.
163
- 4. Add documentation for your feature or bug fix.
164
- 5. Run `bundle exec rake doc:yard`. If your changes are not 100%
165
- documented, go back to step 4.
166
- 6. Add specs for your feature or bug fix.
167
- 7. Run `bundle exec rake spec`. If your changes are not 100% covered, go
168
- back to step 6.
169
- 8. Commit and push your changes.
170
- 9. Submit a pull request. Please do not include changes to the gemspec,
199
+ 4. Add specs for your feature or bug fix.
200
+ 5. Run `bundle exec rake spec`. If your changes are not 100% covered, go back
201
+ to step 4.
202
+ 6. Commit and push your changes.
203
+ 7. Submit a pull request. Please do not include changes to the gemspec,
171
204
  version, or history file. (If you want to create your own version for some
172
205
  reason, please do so in a separate commit.)
173
206
 
@@ -2,9 +2,7 @@ require 'action_view'
2
2
  require 'launchy'
3
3
  require 'oauth'
4
4
  require 't/core_ext/string'
5
- require 't/delete'
6
5
  require 't/rcfile'
7
- require 't/set'
8
6
  require 'thor'
9
7
  require 'time'
10
8
  require 'twitter'
@@ -39,7 +37,6 @@ module T
39
37
  end
40
38
  end
41
39
  end
42
- map %w(list ls) => :accounts
43
40
 
44
41
  desc "authorize", "Allows an application to request user authorization"
45
42
  method_option :consumer_key, :aliases => "-c", :required => true
@@ -64,25 +61,25 @@ module T
64
61
  pin = ask "Paste in the supplied PIN:"
65
62
  access_token = request_token.get_access_token(:oauth_verifier => pin.chomp)
66
63
  oauth_response = access_token.get('/1/account/verify_credentials.json')
67
- username = oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
64
+ screen_name = oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
68
65
  @rcfile.path = options['profile'] if options['profile']
69
- @rcfile[username] = {
66
+ @rcfile[screen_name] = {
70
67
  options['consumer_key'] => {
71
- 'username' => username,
68
+ 'username' => screen_name,
72
69
  'consumer_key' => options['consumer_key'],
73
70
  'consumer_secret' => options['consumer_secret'],
74
71
  'token' => access_token.token,
75
72
  'secret' => access_token.secret,
76
73
  }
77
74
  }
78
- @rcfile.default_profile = {'username' => username, 'consumer_key' => options['consumer_key']}
75
+ @rcfile.default_profile = {'username' => screen_name, 'consumer_key' => options['consumer_key']}
79
76
  say "Authorization successful"
80
77
  end
81
78
 
82
- desc "block USERNAME", "Block a user."
83
- def block(username)
84
- username = username.strip_at
85
- user = client.block(username)
79
+ desc "block SCREEN_NAME", "Block a user."
80
+ def block(screen_name)
81
+ screen_name = screen_name.strip_at
82
+ user = client.block(screen_name, :include_entities => false)
86
83
  say "@#{@rcfile.default_profile[0]} blocked @#{user.screen_name}"
87
84
  say
88
85
  say "Run `#{$0} delete block #{user.screen_name}` to unblock."
@@ -91,36 +88,27 @@ module T
91
88
  desc "direct_messages", "Returns the 20 most recent Direct Messages sent to you."
92
89
  def direct_messages
93
90
  run_pager
94
- client.direct_messages.map do |direct_message|
91
+ client.direct_messages(:include_entities => false).map do |direct_message|
95
92
  say "#{direct_message.sender.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
96
93
  end
97
94
  end
98
95
  map %w(dms) => :direct_messages
99
96
 
100
- desc "sent_messages", "Returns the 20 most recent Direct Messages sent to you."
101
- def sent_messages
102
- run_pager
103
- client.direct_messages_sent.map do |direct_message|
104
- say "#{direct_message.recipient.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
105
- end
106
- end
107
- map %w(sms) => :sent_messages
108
-
109
- desc "dm USERNAME MESSAGE", "Sends that person a Direct Message."
110
- def dm(username, message)
111
- username = username.strip_at
112
- direct_message = client.direct_message_create(username, message)
97
+ desc "dm SCREEN_NAME MESSAGE", "Sends that person a Direct Message."
98
+ def dm(screen_name, message)
99
+ screen_name = screen_name.strip_at
100
+ direct_message = client.direct_message_create(screen_name, message, :include_entities => false)
113
101
  say "Direct Message sent from @#{@rcfile.default_profile[0]} to @#{direct_message.recipient.screen_name} (#{time_ago_in_words(direct_message.created_at)} ago)"
114
102
  end
115
103
  map %w(m) => :dm
116
104
 
117
- desc "favorite USERNAME", "Marks that user's last Tweet as one of your favorites."
118
- def favorite(username)
119
- username = username.strip_at
120
- user = client.user(username)
105
+ desc "favorite SCREEN_NAME", "Marks that user's last Tweet as one of your favorites."
106
+ def favorite(screen_name)
107
+ screen_name = screen_name.strip_at
108
+ user = client.user(screen_name, :include_entities => false)
121
109
  if user.status
122
- client.favorite(user.status.id)
123
- say "@#{@rcfile.default_profile[0]} favorited @#{user.screen_name}'s latest status: #{user.status.text}"
110
+ client.favorite(user.status.id, :include_entities => false)
111
+ say "@#{@rcfile.default_profile[0]} favorited @#{user.screen_name}'s latest status: \"#{user.status.text}\""
124
112
  say
125
113
  say "Run `#{$0} delete favorite` to unfavorite."
126
114
  else
@@ -128,27 +116,32 @@ module T
128
116
  end
129
117
  rescue Twitter::Error::Forbidden => error
130
118
  if error.message =~ /You have already favorited this status\./
131
- say "@#{@rcfile.default_profile[0]} favorited @#{user.screen_name}'s latest status: #{user.status.text}"
119
+ say "@#{@rcfile.default_profile[0]} favorited @#{user.screen_name}'s latest status: \"#{user.status.text}\""
132
120
  else
133
121
  raise
134
122
  end
135
123
  end
136
124
  map %w(fave) => :favorite
137
125
 
138
- desc "follow USERNAME", "Allows you to start following a specific user."
139
- def follow(username)
140
- username = username.strip_at
141
- user = client.follow(username)
142
- say "@#{@rcfile.default_profile[0]} is now following @#{user.screen_name}."
143
- say
144
- say "Run `#{$0} unfollow #{user.screen_name}` to stop."
126
+ desc "favorites", "Returns the 20 most recent Tweets you favorited."
127
+ method_option :number, :aliases => "-n", :type => :numeric, :default => 20
128
+ method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
129
+ def favorites
130
+ hash = {:include_entities => false}
131
+ hash.merge!(:count => options['number']) if options['number']
132
+ timeline = client.favorites(hash)
133
+ timeline.reverse! if options['reverse']
134
+ run_pager
135
+ timeline.map do |status|
136
+ say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
137
+ end
145
138
  end
146
- map %w(befriend) => :follow
139
+ map %w(faves) => :favorites
147
140
 
148
- desc "get USERNAME", "Retrieves the latest update posted by the user."
149
- def get(username)
150
- username = username.strip_at
151
- user = client.user(username)
141
+ desc "get SCREEN_NAME", "Retrieves the latest update posted by the user."
142
+ def get(screen_name)
143
+ screen_name = screen_name.strip_at
144
+ user = client.user(screen_name, :include_entities => false)
152
145
  if user.status
153
146
  say "#{user.status.text} (#{time_ago_in_words(user.status.created_at)} ago)"
154
147
  else
@@ -157,9 +150,12 @@ module T
157
150
  end
158
151
 
159
152
  desc "mentions", "Returns the 20 most recent Tweets mentioning you."
153
+ method_option :number, :aliases => "-n", :type => :numeric, :default => 20
160
154
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
161
155
  def mentions
162
- timeline = client.mentions
156
+ hash = {:include_entities => false}
157
+ hash.merge!(:count => options['number']) if options['number']
158
+ timeline = client.mentions(hash)
163
159
  timeline.reverse! if options['reverse']
164
160
  run_pager
165
161
  timeline.map do |status|
@@ -168,20 +164,20 @@ module T
168
164
  end
169
165
  map %w(replies) => :mentions
170
166
 
171
- desc "open USERNAME", "Opens that user's profile in a web browser."
167
+ desc "open SCREEN_NAME", "Opens that user's profile in a web browser."
172
168
  method_option :dry_run, :type => :boolean
173
- def open(username)
174
- username = username.strip_at
175
- Launchy.open("https://twitter.com/#{username}", :dry_run => options.fetch('dry_run', false))
169
+ def open(screen_name)
170
+ screen_name = screen_name.strip_at
171
+ Launchy.open("https://twitter.com/#{screen_name}", :dry_run => options.fetch('dry_run', false))
176
172
  end
177
173
 
178
- desc "reply USERNAME MESSAGE", "Post your Tweet as a reply directed at another person."
174
+ desc "reply SCREEN_NAME MESSAGE", "Post your Tweet as a reply directed at another person."
179
175
  method_option :location, :aliases => "-l", :type => :boolean, :default => true
180
- def reply(username, message)
181
- username = username.strip_at
182
- hash = {}
176
+ def reply(screen_name, message)
177
+ screen_name = screen_name.strip_at
178
+ hash = {:include_entities => false, :trim_user => true}
183
179
  hash.merge!(:lat => location.lat, :long => location.lng) if options['location']
184
- user = client.user(username)
180
+ user = client.user(screen_name, :include_entities => false)
185
181
  hash.merge!(:in_reply_to_status_id => user.status.id) if user.status
186
182
  status = client.update("@#{user.screen_name} #{message}", hash)
187
183
  say "Reply created by @#{@rcfile.default_profile[0]} to @#{user.screen_name} (#{time_ago_in_words(status.created_at)} ago)"
@@ -189,13 +185,13 @@ module T
189
185
  say "Run `#{$0} delete status` to delete."
190
186
  end
191
187
 
192
- desc "retweet USERNAME", "Sends that user's latest Tweet to your followers."
193
- def retweet(username)
194
- username = username.strip_at
195
- user = client.user(username)
188
+ desc "retweet SCREEN_NAME", "Sends that user's latest Tweet to your followers."
189
+ def retweet(screen_name)
190
+ screen_name = screen_name.strip_at
191
+ user = client.user(screen_name, :include_entities => false)
196
192
  if user.status
197
- client.retweet(user.status.id)
198
- say "@#{@rcfile.default_profile[0]} retweeted @#{user.screen_name}'s latest status: #{user.status.text}"
193
+ client.retweet(user.status.id, :include_entities => false, :trim_user => true)
194
+ say "@#{@rcfile.default_profile[0]} retweeted @#{user.screen_name}'s latest status: \"#{user.status.text}\""
199
195
  say
200
196
  say "Run `#{$0} delete status` to undo."
201
197
  else
@@ -203,19 +199,45 @@ module T
203
199
  end
204
200
  rescue Twitter::Error::Forbidden => error
205
201
  if error.message =~ /sharing is not permissable for this status \(Share validations failed\)/
206
- say "@#{@rcfile.default_profile[0]} retweeted @#{user.screen_name}'s latest status: #{user.status.text}"
202
+ say "@#{@rcfile.default_profile[0]} retweeted @#{user.screen_name}'s latest status: \"#{user.status.text}\""
207
203
  else
208
204
  raise
209
205
  end
210
206
  end
211
207
  map %w(rt) => :retweet
212
208
 
213
- desc "stats USERNAME", "Retrieves the given user's number of followers and how many people they're following."
214
- def stats(username)
215
- username = username.strip_at
216
- user = client.user(username)
217
- say "Followers: #{number_with_delimiter(user.followers_count)}"
209
+ desc "search QUERY", "Returns the 20 most recent Tweets that match a specified query."
210
+ method_option :number, :aliases => "-n", :type => :numeric, :default => 20
211
+ method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
212
+ def search(query)
213
+ hash = {:include_entities => false}
214
+ hash.merge!(:rpp => options['number']) if options['number']
215
+ timeline = client.search(query, hash)
216
+ timeline.reverse! if options['reverse']
217
+ run_pager
218
+ timeline.map do |status|
219
+ say "#{status.from_user.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
220
+ end
221
+ end
222
+
223
+ desc "sent_messages", "Returns the 20 most recent Direct Messages sent to you."
224
+ def sent_messages
225
+ run_pager
226
+ client.direct_messages_sent(:include_entities => false).map do |direct_message|
227
+ say "#{direct_message.recipient.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
228
+ end
229
+ end
230
+ map %w(sms) => :sent_messages
231
+
232
+ desc "stats SCREEN_NAME", "Retrieves the given user's number of followers and how many people they're following."
233
+ def stats(screen_name)
234
+ screen_name = screen_name.strip_at
235
+ user = client.user(screen_name, :include_entities => false)
236
+ say "Tweets: #{number_with_delimiter(user.statuses_count)}"
218
237
  say "Following: #{number_with_delimiter(user.friends_count)}"
238
+ say "Followers: #{number_with_delimiter(user.followers_count)}"
239
+ say "Favorites: #{number_with_delimiter(user.favorites_count)}"
240
+ say "Listed: #{number_with_delimiter(user.listed_count)}"
219
241
  say
220
242
  say "Run `#{$0} whois #{user.screen_name}` to view profile."
221
243
  end
@@ -223,7 +245,7 @@ module T
223
245
  desc "status MESSAGE", "Post a Tweet."
224
246
  method_option :location, :aliases => "-l", :type => :boolean, :default => true
225
247
  def status(message)
226
- hash = {}
248
+ hash = {:include_entities => false, :trim_user => true}
227
249
  hash.merge!(:lat => location.lat, :long => location.lng) if options['location']
228
250
  status = client.update(message, hash)
229
251
  say "Tweet created by @#{@rcfile.default_profile[0]} (#{time_ago_in_words(status.created_at)} ago)"
@@ -234,7 +256,7 @@ module T
234
256
 
235
257
  desc "suggest", "This command returns a listing of Twitter users' accounts we think you might enjoy following."
236
258
  def suggest
237
- recommendation = client.recommendations(:limit => 1).first
259
+ recommendation = client.recommendations(:limit => 1, :include_entities => false).first
238
260
  if recommendation
239
261
  say "Try following @#{recommendation.screen_name}."
240
262
  say
@@ -245,9 +267,12 @@ module T
245
267
  end
246
268
 
247
269
  desc "timeline", "Returns the 20 most recent Tweets posted by you and the users you follow."
270
+ method_option :number, :aliases => "-n", :type => :numeric, :default => 20
248
271
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
249
272
  def timeline
250
- timeline = client.home_timeline
273
+ hash = {:include_entities => false}
274
+ hash.merge!(:count => options['number']) if options['number']
275
+ timeline = client.home_timeline(hash)
251
276
  timeline.reverse! if options['reverse']
252
277
  run_pager
253
278
  timeline.map do |status|
@@ -256,26 +281,16 @@ module T
256
281
  end
257
282
  map %w(tl) => :timeline
258
283
 
259
- desc "unfollow USERNAME", "Allows you to stop following a specific user."
260
- def unfollow(username)
261
- username = username.strip_at
262
- user = client.unfollow(username)
263
- say "@#{@rcfile.default_profile[0]} is no longer following @#{user.screen_name}."
264
- say
265
- say "Run `#{$0} follow #{user.screen_name}` to follow again."
266
- end
267
- map %w(defriend) => :unfollow
268
-
269
- desc "version", "Show version"
284
+ desc "version", "Show version."
270
285
  def version
271
286
  say T::Version
272
287
  end
273
288
  map %w(-v --version) => :version
274
289
 
275
- desc "whois USERNAME", "Retrieves profile information for the user."
276
- def whois(username)
277
- username = username.strip_at
278
- user = client.user(username)
290
+ desc "whois SCREEN_NAME", "Retrieves profile information for the user."
291
+ def whois(screen_name)
292
+ screen_name = screen_name.strip_at
293
+ user = client.user(screen_name, :include_entities => false)
279
294
  say "#{user.name}, since #{user.created_at.strftime("%b %Y")}."
280
295
  say "bio: #{user.description}"
281
296
  say "location: #{user.location}"
@@ -284,96 +299,109 @@ module T
284
299
 
285
300
  desc "delete SUBCOMMAND ...ARGS", "Delete Tweets, Direct Messages, etc."
286
301
  method_option :force, :aliases => "-f", :type => :boolean
287
- subcommand 'delete', Delete
302
+ require 't/cli/delete'
303
+ subcommand 'delete', CLI::Delete
304
+
305
+ desc "follow SUBCOMMAND ...ARGS", "Follow users."
306
+ require 't/cli/follow'
307
+ subcommand 'follow', CLI::Follow
308
+
309
+ desc "list SUBCOMMAND ...ARGS", "Do various things with lists."
310
+ require 't/cli/list'
311
+ subcommand 'list', CLI::List
288
312
 
289
313
  desc "set SUBCOMMAND ...ARGS", "Change various account settings."
290
- subcommand 'set', Set
314
+ require 't/cli/set'
315
+ subcommand 'set', CLI::Set
291
316
 
292
- no_tasks do
317
+ desc "unfollow SUBCOMMAND ...ARGS", "Unfollow users."
318
+ require 't/cli/unfollow'
319
+ subcommand 'unfollow', CLI::Unfollow
293
320
 
294
- def base_url
295
- "#{protocol}://#{host}"
296
- end
321
+ private
297
322
 
298
- def client
299
- return @client if @client
300
- @rcfile.path = options['profile'] if options['profile']
301
- @client = Twitter::Client.new(
302
- :endpoint => base_url,
303
- :consumer_key => @rcfile.default_consumer_key,
304
- :consumer_secret => @rcfile.default_consumer_secret,
305
- :oauth_token => @rcfile.default_token,
306
- :oauth_token_secret => @rcfile.default_secret
307
- )
308
- end
323
+ def base_url
324
+ "#{protocol}://#{host}"
325
+ end
309
326
 
310
- def consumer
311
- OAuth::Consumer.new(
312
- options['consumer_key'],
313
- options['consumer_secret'],
314
- :site => base_url
315
- )
316
- end
327
+ def client
328
+ return @client if @client
329
+ @rcfile.path = options['profile'] if options['profile']
330
+ @client = Twitter::Client.new(
331
+ :endpoint => base_url,
332
+ :consumer_key => @rcfile.default_consumer_key,
333
+ :consumer_secret => @rcfile.default_consumer_secret,
334
+ :oauth_token => @rcfile.default_token,
335
+ :oauth_token_secret => @rcfile.default_secret
336
+ )
337
+ end
317
338
 
318
- def generate_authorize_url(request_token)
319
- request = consumer.create_signed_request(:get, consumer.authorize_path, request_token, pin_auth_parameters)
320
- params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map do |param|
321
- key, value = param.split('=')
322
- value =~ /"(.*?)"/
323
- "#{key}=#{CGI::escape($1)}"
324
- end.join('&')
325
- "#{base_url}#{request.path}?#{params}"
326
- end
339
+ def consumer
340
+ OAuth::Consumer.new(
341
+ options['consumer_key'],
342
+ options['consumer_secret'],
343
+ :site => base_url
344
+ )
345
+ end
327
346
 
328
- def host
329
- options['host'] || DEFAULT_HOST
330
- end
347
+ def generate_authorize_url(request_token)
348
+ request = consumer.create_signed_request(:get, consumer.authorize_path, request_token, pin_auth_parameters)
349
+ params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map do |param|
350
+ key, value = param.split('=')
351
+ value =~ /"(.*?)"/
352
+ "#{key}=#{CGI::escape($1)}"
353
+ end.join('&')
354
+ "#{base_url}#{request.path}?#{params}"
355
+ end
331
356
 
332
- def location
333
- return @location if @location
334
- require 'geokit'
335
- require 'open-uri'
336
- ip_address = Kernel::open("http://checkip.dyndns.org/") do |body|
337
- /(?:\d{1,3}\.){3}\d{1,3}/.match(body.read)[0]
338
- end
339
- @location = Geokit::Geocoders::MultiGeocoder.geocode(ip_address)
340
- end
357
+ def host
358
+ options['host'] || DEFAULT_HOST
359
+ end
341
360
 
342
- def pin_auth_parameters
343
- {:oauth_callback => 'oob'}
361
+ def location
362
+ return @location if @location
363
+ require 'geokit'
364
+ require 'open-uri'
365
+ ip_address = Kernel::open("http://checkip.dyndns.org/") do |body|
366
+ /(?:\d{1,3}\.){3}\d{1,3}/.match(body.read)[0]
344
367
  end
368
+ @location = Geokit::Geocoders::MultiGeocoder.geocode(ip_address)
369
+ end
345
370
 
346
- def protocol
347
- options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
348
- end
371
+ def pin_auth_parameters
372
+ {:oauth_callback => 'oob'}
373
+ end
349
374
 
350
- def run_pager
351
- return if RUBY_PLATFORM =~ /win32/
352
- return if ENV["T_ENV"] == "test"
353
- return unless STDOUT.tty?
375
+ def protocol
376
+ options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
377
+ end
354
378
 
355
- read, write = IO.pipe
379
+ def run_pager
380
+ return if RUBY_PLATFORM =~ /win32/
381
+ return if ENV["T_ENV"] == "test"
382
+ return unless STDOUT.tty?
356
383
 
357
- unless Kernel.fork # Child process
358
- STDOUT.reopen(write)
359
- STDERR.reopen(write) if STDERR.tty?
360
- read.close
361
- write.close
362
- return
363
- end
384
+ read, write = IO.pipe
364
385
 
365
- # Parent process, become pager
366
- STDIN.reopen(read)
386
+ unless Kernel.fork # Child process
387
+ STDOUT.reopen(write)
388
+ STDERR.reopen(write) if STDERR.tty?
367
389
  read.close
368
390
  write.close
391
+ return
392
+ end
369
393
 
370
- ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
394
+ # Parent process, become pager
395
+ STDIN.reopen(read)
396
+ read.close
397
+ write.close
371
398
 
372
- Kernel.select [STDIN] # Wait until we have input before we start the pager
373
- pager = ENV['PAGER'] || 'less'
374
- exec pager rescue exec "/bin/sh", "-c", pager
375
- end
399
+ ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
376
400
 
401
+ Kernel.select [STDIN] # Wait until we have input before we start the pager
402
+ pager = ENV['PAGER'] || 'less'
403
+ exec pager rescue exec "/bin/sh", "-c", pager
377
404
  end
405
+
378
406
  end
379
407
  end