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 +1 -1
- data/README.md +81 -48
- data/lib/t/cli.rb +181 -153
- data/lib/t/cli/delete.rb +112 -0
- data/lib/t/cli/follow.rb +66 -0
- data/lib/t/cli/follow/all.rb +99 -0
- data/lib/t/cli/list.rb +108 -0
- data/lib/t/cli/list/add.rb +64 -0
- data/lib/t/cli/list/add/all.rb +169 -0
- data/lib/t/cli/list/remove.rb +67 -0
- data/lib/t/cli/list/remove/all.rb +162 -0
- data/lib/t/cli/set.rb +86 -0
- data/lib/t/cli/unfollow.rb +66 -0
- data/lib/t/cli/unfollow/all.rb +122 -0
- data/lib/t/version.rb +1 -1
- data/spec/cli/delete_spec.rb +332 -0
- data/spec/cli/follow/all_spec.rb +159 -0
- data/spec/cli/follow_spec.rb +74 -0
- data/spec/cli/list/add/all_spec.rb +435 -0
- data/spec/cli/list/add_spec.rb +65 -0
- data/spec/cli/list/remove/all_spec.rb +315 -0
- data/spec/cli/list/remove_spec.rb +42 -0
- data/spec/cli/list_spec.rb +80 -0
- data/spec/{set_spec.rb → cli/set_spec.rb} +16 -16
- data/spec/cli/unfollow/all_spec.rb +223 -0
- data/spec/cli/unfollow_spec.rb +74 -0
- data/spec/cli_spec.rb +115 -70
- data/spec/fixtures/501_ids.json +1 -0
- data/spec/fixtures/501_users_list.json +1 -0
- data/spec/fixtures/empty_cursor.json +1 -0
- data/spec/fixtures/followers_ids.json +1 -0
- data/spec/fixtures/friends_ids.json +1 -0
- data/spec/fixtures/gem.json +1 -0
- data/spec/fixtures/list.json +1 -0
- data/spec/fixtures/search.json +1 -0
- data/spec/fixtures/users_list.json +1 -0
- data/t.gemspec +1 -0
- metadata +93 -34
- data/lib/t/delete.rb +0 -101
- data/lib/t/set.rb +0 -83
- data/spec/delete_spec.rb +0 -251
data/Gemfile
CHANGED
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
|
-
|
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
|
51
|
-
|
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
|
61
|
+
### <a name="dm"></a>Send a direct message
|
76
62
|
|
77
|
-
t dm sferik "Want to get dinner
|
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>
|
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>
|
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
|
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
|
85
|
+
### <a name="follow-users"></a>Start following users
|
86
|
+
|
87
|
+
t follow users sferik gem
|
100
88
|
|
101
|
-
|
89
|
+
### <a name="follow-all-followers"></a>Follow all followers (i.e. follow back)
|
102
90
|
|
103
|
-
|
91
|
+
t follow all followers
|
104
92
|
|
105
|
-
|
93
|
+
### <a name="unfollow-users"></a>Stop following users
|
106
94
|
|
107
|
-
|
95
|
+
t unfollow users sferik gem
|
108
96
|
|
109
|
-
|
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
|
164
|
-
5. Run `bundle exec rake
|
165
|
-
|
166
|
-
6.
|
167
|
-
7.
|
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
|
|
data/lib/t/cli.rb
CHANGED
@@ -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
|
-
|
64
|
+
screen_name = oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
|
68
65
|
@rcfile.path = options['profile'] if options['profile']
|
69
|
-
@rcfile[
|
66
|
+
@rcfile[screen_name] = {
|
70
67
|
options['consumer_key'] => {
|
71
|
-
'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' =>
|
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
|
83
|
-
def block(
|
84
|
-
|
85
|
-
user = client.block(
|
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 "
|
101
|
-
def
|
102
|
-
|
103
|
-
client.
|
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
|
118
|
-
def favorite(
|
119
|
-
|
120
|
-
user = client.user(
|
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 "
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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(
|
139
|
+
map %w(faves) => :favorites
|
147
140
|
|
148
|
-
desc "get
|
149
|
-
def get(
|
150
|
-
|
151
|
-
user = client.user(
|
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
|
-
|
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
|
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(
|
174
|
-
|
175
|
-
Launchy.open("https://twitter.com/#{
|
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
|
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(
|
181
|
-
|
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(
|
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
|
193
|
-
def retweet(
|
194
|
-
|
195
|
-
user = client.user(
|
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 "
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
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 "
|
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
|
276
|
-
def whois(
|
277
|
-
|
278
|
-
user = client.user(
|
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
|
-
|
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
|
-
|
314
|
+
require 't/cli/set'
|
315
|
+
subcommand 'set', CLI::Set
|
291
316
|
|
292
|
-
|
317
|
+
desc "unfollow SUBCOMMAND ...ARGS", "Unfollow users."
|
318
|
+
require 't/cli/unfollow'
|
319
|
+
subcommand 'unfollow', CLI::Unfollow
|
293
320
|
|
294
|
-
|
295
|
-
"#{protocol}://#{host}"
|
296
|
-
end
|
321
|
+
private
|
297
322
|
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
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
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
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
|
-
|
333
|
-
|
334
|
-
|
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
|
-
|
343
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
371
|
+
def pin_auth_parameters
|
372
|
+
{:oauth_callback => 'oob'}
|
373
|
+
end
|
349
374
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
return unless STDOUT.tty?
|
375
|
+
def protocol
|
376
|
+
options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
377
|
+
end
|
354
378
|
|
355
|
-
|
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
|
-
|
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
|
-
|
366
|
-
|
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
|
-
|
394
|
+
# Parent process, become pager
|
395
|
+
STDIN.reopen(read)
|
396
|
+
read.close
|
397
|
+
write.close
|
371
398
|
|
372
|
-
|
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
|