t 0.1.0 → 0.2.0
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/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 [][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
|
-

|
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
|
-
[][travis]
|
24
|
-
|
25
|
-
[travis]: http://travis-ci.org/sferik/t
|
26
|
-
|
27
|
-
## <a name="dependencies"></a>Dependency Status
|
28
|
-
[][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
|
+

|
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
|