t 2.0.1 → 2.0.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.
- data.tar.gz.sig +0 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +12 -12
- data/Rakefile +11 -1
- data/bin/t +11 -11
- data/lib/t.rb +7 -9
- data/lib/t/cli.rb +299 -360
- data/lib/t/collectable.rb +2 -4
- data/lib/t/core_ext/kernel.rb +3 -5
- data/lib/t/core_ext/string.rb +4 -6
- data/lib/t/delete.rb +27 -29
- data/lib/t/editor.rb +1 -3
- data/lib/t/list.rb +44 -45
- data/lib/t/printable.rb +43 -44
- data/lib/t/rcfile.rb +4 -5
- data/lib/t/requestable.rb +1 -3
- data/lib/t/search.rb +42 -55
- data/lib/t/set.rb +11 -12
- data/lib/t/stream.rb +24 -20
- data/lib/t/utils.rb +20 -28
- data/lib/t/version.rb +1 -3
- data/spec/cli_spec.rb +1241 -1242
- data/spec/delete_spec.rb +122 -122
- data/spec/editor_spec.rb +42 -42
- data/spec/fixtures/lists.json +1 -1
- data/spec/fixtures/locations.json +1 -1
- data/spec/helper.rb +17 -13
- data/spec/list_spec.rb +202 -202
- data/spec/rcfile_spec.rb +73 -73
- data/spec/search_spec.rb +398 -398
- data/spec/set_spec.rb +72 -72
- data/spec/stream_spec.rb +56 -56
- data/spec/utils_spec.rb +29 -29
- data/t.gemspec +1 -1
- metadata +56 -32
- metadata.gz.sig +0 -0
- checksums.yaml +0 -7
- checksums.yaml.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/CONTRIBUTING.md
CHANGED
@@ -37,7 +37,7 @@ Ideally, a bug report should include a pull request with failing specs.
|
|
37
37
|
3. Add specs for your unimplemented feature or bug fix.
|
38
38
|
4. Run `bundle exec rake spec`. If your specs pass, return to step 3.
|
39
39
|
5. Implement your feature or bug fix.
|
40
|
-
6. Run `bundle exec rake
|
40
|
+
6. Run `bundle exec rake default`. If your specs fail, return to step 5.
|
41
41
|
7. Run `open coverage/index.html`. If your changes are not completely covered
|
42
42
|
by your tests, return to step 3.
|
43
43
|
8. Add, commit, and push your changes.
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@
|
|
15
15
|
[gittip]: https://www.gittip.com/sferik/
|
16
16
|
|
17
17
|
#### A command-line power tool for Twitter.
|
18
|
-
The CLI takes syntactic cues from the [Twitter SMS commands][sms],
|
18
|
+
The CLI takes syntactic cues from the [Twitter SMS commands][sms], but it
|
19
19
|
offers vastly more commands and capabilities than are available via SMS.
|
20
20
|
|
21
21
|
[sms]: https://support.twitter.com/articles/14020-twitter-sms-command
|
@@ -29,7 +29,7 @@ First, make sure you have Ruby installed.
|
|
29
29
|
|
30
30
|
If the output looks something like this, you're in good shape:
|
31
31
|
|
32
|
-
ruby 1.9.
|
32
|
+
ruby 1.9.3p484 (2013-11-22 revision 43786) [x86_64-darwin13.0.0]
|
33
33
|
|
34
34
|
If the output looks more like this, you need to [install Ruby][ruby]:
|
35
35
|
[ruby]: http://www.ruby-lang.org/en/downloads/
|
@@ -59,14 +59,14 @@ public key as a trusted certificate (you only need to do this once):
|
|
59
59
|
|
60
60
|
gem cert --add <(curl -Ls https://raw.github.com/sferik/t/master/certs/sferik.pem)
|
61
61
|
|
62
|
-
Then, install the gem with the
|
62
|
+
Then, install the gem with the medium security trust policy:
|
63
63
|
|
64
|
-
gem install t -P
|
64
|
+
gem install t -P MediumSecurity
|
65
65
|
|
66
66
|
## Configuration
|
67
67
|
Twitter API v1.1 requires OAuth for all of its functionality, so you'll need a
|
68
68
|
registered Twitter application. If you've never registered a Twitter
|
69
|
-
application before, it's easy! Just sign-in using your Twitter account and
|
69
|
+
application before, it's easy! Just sign-in using your Twitter account and then
|
70
70
|
fill out the short form at <http://dev.twitter.com/apps/new>. If you've
|
71
71
|
previously registered a Twitter application, it should be listed at
|
72
72
|
<http://dev.twitter.com/apps>. Once you've registered an application, make sure
|
@@ -138,8 +138,8 @@ obviously can't contain any apostrophes.)
|
|
138
138
|
t does_follow @ev @sferik
|
139
139
|
|
140
140
|
**Note**: If the first user does not follow the second, `t` will exit with a
|
141
|
-
non-zero exit code. This allows you to execute commands conditionally
|
142
|
-
example, send a user a direct message only if
|
141
|
+
non-zero exit code. This allows you to execute commands conditionally. For
|
142
|
+
example, here's how to send a user a direct message only if they already follow you:
|
143
143
|
|
144
144
|
t does_follow @ev && t dm @ev "What's up, bro?"
|
145
145
|
|
@@ -179,13 +179,13 @@ example, send a user a direct message only if he already follows you:
|
|
179
179
|
#### Start streaming your timeline (Control-C to stop)
|
180
180
|
t stream timeline
|
181
181
|
|
182
|
-
#### Count the number of
|
183
|
-
t list members twitter/
|
182
|
+
#### Count the number of official Twitter engineering accounts
|
183
|
+
t list members twitter/engineering | wc -l
|
184
184
|
|
185
185
|
#### Search Twitter for the 20 most recent Tweets that match a specified query
|
186
186
|
t search all "query"
|
187
187
|
|
188
|
-
#### Download the latest Linux kernel via BitTorrent (possibly NSFW, depending where you work)
|
188
|
+
#### Download the latest Linux kernel via BitTorrent (possibly NSFW, depending on where you work)
|
189
189
|
t search all "lang:en filter:links linux torrent" -n 1 | grep -o "http://t.co/[0-9A-Za-z]*" | xargs open
|
190
190
|
|
191
191
|
#### Search Tweets you've favorited that match a specified query
|
@@ -204,7 +204,7 @@ example, send a user a direct message only if he already follows you:
|
|
204
204
|
t search timeline @sferik "query"
|
205
205
|
|
206
206
|
## Features
|
207
|
-
* Deep search: Instead of using the Twitter Search API, [which only
|
207
|
+
* Deep search: Instead of using the Twitter Search API, [which only goes
|
208
208
|
back 6-9 days][search], `t search` fetches up to 3,200 tweets via the REST API
|
209
209
|
and then checks each one against a regular expression.
|
210
210
|
* Multi-threaded: Whenever possible, Twitter API requests are made in parallel,
|
@@ -275,7 +275,7 @@ shell?
|
|
275
275
|
|
276
276
|
## History
|
277
277
|
The [twitter gem][gem] previously contained a command-line interface, up until
|
278
|
-
version 0.5.0, when it was [removed][]. This project is offered as a
|
278
|
+
version 0.5.0, when it was [removed][]. This project is offered as a successor
|
279
279
|
to that effort, however it is a clean room implementation that contains none of
|
280
280
|
the original code.
|
281
281
|
|
data/Rakefile
CHANGED
@@ -4,8 +4,18 @@ Bundler::GemHelper.install_tasks
|
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
RSpec::Core::RakeTask.new(:spec)
|
6
6
|
|
7
|
+
begin
|
8
|
+
require 'rubocop/rake_task'
|
9
|
+
Rubocop::RakeTask.new
|
10
|
+
rescue LoadError
|
11
|
+
desc 'Run RuboCop'
|
12
|
+
task :rubocop do
|
13
|
+
$stderr.puts 'Rubocop is disabled'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
7
17
|
Dir.glob('tasks/*.rake').each { |r| import r }
|
8
18
|
|
9
19
|
task :release => 'completion:zsh'
|
10
20
|
task :test => :spec
|
11
|
-
task :default => :spec
|
21
|
+
task :default => [:spec, :rubocop]
|
data/bin/t
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Trap interrupts to quit cleanly. See
|
4
4
|
# https://twitter.com/mitchellh/status/283014103189053442
|
5
|
-
Signal.trap(
|
5
|
+
Signal.trap('INT') { exit 1 }
|
6
6
|
|
7
7
|
require 'oauth'
|
8
8
|
require 't'
|
@@ -11,7 +11,7 @@ require 'twitter'
|
|
11
11
|
# Output message to $stderr, prefixed with the program name
|
12
12
|
def pute(*args)
|
13
13
|
first = args.shift.dup
|
14
|
-
first.insert(0, "#{$
|
14
|
+
first.insert(0, "#{$PROGRAM_NAME}: ")
|
15
15
|
args.unshift(first)
|
16
16
|
$stderr.puts(*args)
|
17
17
|
end
|
@@ -19,29 +19,29 @@ end
|
|
19
19
|
begin
|
20
20
|
T::CLI.start(ARGV)
|
21
21
|
rescue Interrupt
|
22
|
-
pute
|
22
|
+
pute 'Quitting...'
|
23
23
|
exit 1
|
24
24
|
rescue OAuth::Unauthorized
|
25
|
-
pute
|
25
|
+
pute 'Authorization failed'
|
26
26
|
exit 1
|
27
27
|
rescue Twitter::Error::TooManyRequests => error
|
28
28
|
pute error.message,
|
29
|
-
|
30
|
-
|
29
|
+
"The rate limit for this request will reset in #{error.rate_limit.reset_in} seconds.",
|
30
|
+
'While you wait, consider making a polite request for Twitter to increase the API rate limit at https://dev.twitter.com/discussions/10644'
|
31
31
|
exit 1
|
32
32
|
rescue Twitter::Error::BadRequest => error
|
33
33
|
pute error.message,
|
34
|
-
|
34
|
+
'Run `t authorize` to authorize.'
|
35
35
|
exit 1
|
36
36
|
rescue Twitter::Error::Forbidden, Twitter::Error::Unauthorized => error
|
37
37
|
pute error.message
|
38
|
-
if error.message ==
|
39
|
-
error.message ==
|
38
|
+
if error.message == 'Error processing your OAuth request: Read-only application cannot POST' ||
|
39
|
+
error.message == 'This application is not allowed to access or delete your direct messages'
|
40
40
|
$stderr.puts(%q(Make sure to set your Twitter application's Access Level to "Read, Write and Access direct messages".))
|
41
41
|
require 'thor'
|
42
|
-
Thor::Shell::Basic.new.ask
|
42
|
+
Thor::Shell::Basic.new.ask 'Press [Enter] to open the Twitter Developer site.'
|
43
43
|
require 'launchy'
|
44
|
-
Launchy.open(
|
44
|
+
Launchy.open('https://dev.twitter.com/apps') { |u, o, e| $stderr.puts "Manually open #{u}" }
|
45
45
|
end
|
46
46
|
exit 1
|
47
47
|
rescue Twitter::Error => error
|
data/lib/t.rb
CHANGED
@@ -3,7 +3,6 @@ require 'time'
|
|
3
3
|
|
4
4
|
module T
|
5
5
|
class << self
|
6
|
-
|
7
6
|
# Convert time to local time by applying the `utc_offset` setting.
|
8
7
|
def local_time(time)
|
9
8
|
utc_offset ? (time.dup.utc + utc_offset) : time.localtime
|
@@ -15,14 +14,13 @@ module T
|
|
15
14
|
|
16
15
|
def utc_offset=(offset)
|
17
16
|
@utc_offset = case offset
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
when String
|
18
|
+
Time.zone_offset(offset)
|
19
|
+
when NilClass
|
20
|
+
nil
|
21
|
+
else
|
22
|
+
offset.to_i
|
23
|
+
end
|
25
24
|
end
|
26
|
-
|
27
25
|
end
|
28
26
|
end
|
data/lib/t/cli.rb
CHANGED
@@ -24,20 +24,20 @@ module T
|
|
24
24
|
include T::Utils
|
25
25
|
|
26
26
|
DEFAULT_NUM_RESULTS = 20
|
27
|
-
DIRECT_MESSAGE_HEADINGS = [
|
28
|
-
TREND_HEADINGS = [
|
27
|
+
DIRECT_MESSAGE_HEADINGS = ['ID', 'Posted at', 'Screen name', 'Text']
|
28
|
+
TREND_HEADINGS = ['WOEID', 'Parent ID', 'Type', 'Name', 'Country']
|
29
29
|
|
30
30
|
check_unknown_options!
|
31
31
|
|
32
|
-
class_option
|
33
|
-
class_option
|
32
|
+
class_option 'color', :aliases => '-C', :type => :string, :enum => %w(auto never), :default => 'auto', :desc => 'Control how color is used in output'
|
33
|
+
class_option 'profile', :aliases => '-P', :type => :string, :default => File.join(File.expand_path('~'), T::RCFile::FILE_NAME), :desc => 'Path to RC file', :banner => 'FILE'
|
34
34
|
|
35
35
|
def initialize(*)
|
36
36
|
@rcfile = T::RCFile.instance
|
37
37
|
super
|
38
38
|
end
|
39
39
|
|
40
|
-
desc
|
40
|
+
desc 'accounts', 'List accounts'
|
41
41
|
def accounts
|
42
42
|
@rcfile.path = options['profile'] if options['profile']
|
43
43
|
@rcfile.profiles.each do |profile|
|
@@ -48,53 +48,53 @@ module T
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
desc
|
52
|
-
method_option
|
51
|
+
desc 'authorize', 'Allows an application to request user authorization'
|
52
|
+
method_option 'display-uri', :aliases => '-d', :type => :boolean, :default => false, :desc => 'Display the authorization URL instead of attempting to open it.'
|
53
53
|
def authorize
|
54
54
|
@rcfile.path = options['profile'] if options['profile']
|
55
55
|
if @rcfile.empty?
|
56
56
|
say "Welcome! Before you can use t, you'll first need to register an"
|
57
|
-
say
|
58
|
-
say
|
57
|
+
say 'application with Twitter. Just follow the steps below:'
|
58
|
+
say ' 1. Sign in to the Twitter Developer site and click'
|
59
59
|
say " \"Create a new application\"."
|
60
|
-
say
|
61
|
-
say
|
60
|
+
say ' 2. Complete the required fields and submit the form.'
|
61
|
+
say ' Note: Your application must have a unique name.'
|
62
62
|
say " We recommend: \"<your handle>/t\"."
|
63
|
-
say
|
63
|
+
say ' 3. Go to the Settings tab of your application, and change the'
|
64
64
|
say " Access setting to \"Read, Write and Access direct messages\"."
|
65
|
-
say
|
65
|
+
say ' 4. Go to the Details tab to view the consumer key and secret,'
|
66
66
|
say " which you'll need to copy and paste below when prompted."
|
67
67
|
say
|
68
|
-
ask
|
68
|
+
ask 'Press [Enter] to open the Twitter Developer site.'
|
69
69
|
say
|
70
70
|
else
|
71
71
|
say "It looks like you've already registered an application with Twitter."
|
72
|
-
say
|
73
|
-
say
|
72
|
+
say 'To authorize a new account, just follow the steps below:'
|
73
|
+
say ' 1. Sign in to the Twitter Developer site.'
|
74
74
|
say " 2. Select the application for which you'd like to authorize an account."
|
75
|
-
say
|
75
|
+
say ' 3. Copy and paste the consumer key and secret below when prompted.'
|
76
76
|
say
|
77
|
-
ask
|
77
|
+
ask 'Press [Enter] to open the Twitter Developer site.'
|
78
78
|
say
|
79
79
|
end
|
80
80
|
require 'launchy'
|
81
|
-
open_or_print(
|
82
|
-
key = ask
|
83
|
-
secret = ask
|
81
|
+
open_or_print('https://dev.twitter.com/apps', :dry_run => options['display-uri'])
|
82
|
+
key = ask 'Enter your consumer key:'
|
83
|
+
secret = ask 'Enter your consumer secret:'
|
84
84
|
consumer = OAuth::Consumer.new(key, secret, :site => Twitter::REST::Client::ENDPOINT)
|
85
85
|
request_token = consumer.get_request_token
|
86
86
|
uri = generate_authorize_uri(consumer, request_token)
|
87
87
|
say
|
88
|
-
say
|
89
|
-
say
|
90
|
-
say
|
88
|
+
say 'In a moment, you will be directed to the Twitter app authorization page.'
|
89
|
+
say 'Perform the following steps to complete the authorization process:'
|
90
|
+
say ' 1. Sign in to Twitter.'
|
91
91
|
say " 2. Press \"Authorize app\"."
|
92
|
-
say
|
92
|
+
say ' 3. Copy and paste the supplied PIN below when prompted.'
|
93
93
|
say
|
94
|
-
ask
|
94
|
+
ask 'Press [Enter] to open the Twitter app authorization page.'
|
95
95
|
say
|
96
96
|
open_or_print(uri, :dry_run => options['display-uri'])
|
97
|
-
pin = ask
|
97
|
+
pin = ask 'Enter the supplied PIN:'
|
98
98
|
access_token = request_token.get_access_token(:oauth_verifier => pin.chomp)
|
99
99
|
oauth_response = access_token.get('/1.1/account/verify_credentials.json?include_entities=false&skip_status=true')
|
100
100
|
screen_name = oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
|
@@ -108,25 +108,25 @@ module T
|
|
108
108
|
}
|
109
109
|
}
|
110
110
|
@rcfile.active_profile = {'username' => screen_name, 'consumer_key' => key}
|
111
|
-
say
|
111
|
+
say 'Authorization successful.'
|
112
112
|
end
|
113
113
|
|
114
|
-
desc
|
115
|
-
method_option
|
114
|
+
desc 'block USER [USER...]', 'Block users.'
|
115
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify input as Twitter user IDs instead of screen names.'
|
116
116
|
def block(user, *users)
|
117
|
-
|
118
|
-
client.block(
|
117
|
+
blocked_users, number = fetch_users(users.unshift(user), options) do |users_to_block|
|
118
|
+
client.block(users_to_block)
|
119
119
|
end
|
120
120
|
say "@#{@rcfile.active_profile[0]} blocked #{pluralize(number, 'user')}."
|
121
121
|
say
|
122
|
-
say "Run `#{File.basename($
|
122
|
+
say "Run `#{File.basename($PROGRAM_NAME)} delete block #{blocked_users.map { |blocked_user| "@#{blocked_user.screen_name}" }.join(' ')}` to unblock."
|
123
123
|
end
|
124
124
|
|
125
|
-
desc
|
126
|
-
method_option
|
127
|
-
method_option
|
128
|
-
method_option
|
129
|
-
method_option
|
125
|
+
desc 'direct_messages', "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages sent to you."
|
126
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
127
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
128
|
+
method_option 'number', :aliases => '-n', :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => 'Limit the number of results.'
|
129
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
130
130
|
def direct_messages
|
131
131
|
count = options['number'] || DEFAULT_NUM_RESULTS
|
132
132
|
direct_messages = collect_with_count(count) do |count_opts|
|
@@ -144,7 +144,7 @@ module T
|
|
144
144
|
array = direct_messages.map do |direct_message|
|
145
145
|
[direct_message.id, ls_formatted_time(direct_message), "@#{direct_message.sender.screen_name}", HTMLEntities.new.decode(direct_message.text).gsub(/\n+/, ' ')]
|
146
146
|
end
|
147
|
-
format = options['format'] || DIRECT_MESSAGE_HEADINGS.size.times.map{
|
147
|
+
format = options['format'] || DIRECT_MESSAGE_HEADINGS.size.times.map { '%s' }
|
148
148
|
print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS, format)
|
149
149
|
else
|
150
150
|
direct_messages.each do |direct_message|
|
@@ -154,11 +154,11 @@ module T
|
|
154
154
|
end
|
155
155
|
map %w(directmessages dms) => :direct_messages
|
156
156
|
|
157
|
-
desc
|
158
|
-
method_option
|
159
|
-
method_option
|
160
|
-
method_option
|
161
|
-
method_option
|
157
|
+
desc 'direct_messages_sent', "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages you've sent."
|
158
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
159
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
160
|
+
method_option 'number', :aliases => '-n', :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => 'Limit the number of results.'
|
161
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
162
162
|
def direct_messages_sent
|
163
163
|
count = options['number'] || DEFAULT_NUM_RESULTS
|
164
164
|
direct_messages = collect_with_count(count) do |count_opts|
|
@@ -176,7 +176,7 @@ module T
|
|
176
176
|
array = direct_messages.map do |direct_message|
|
177
177
|
[direct_message.id, ls_formatted_time(direct_message), "@#{direct_message.recipient.screen_name}", HTMLEntities.new.decode(direct_message.text).gsub(/\n+/, ' ')]
|
178
178
|
end
|
179
|
-
format = options['format'] || DIRECT_MESSAGE_HEADINGS.size.times.map{
|
179
|
+
format = options['format'] || DIRECT_MESSAGE_HEADINGS.size.times.map { '%s' }
|
180
180
|
print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS, format)
|
181
181
|
else
|
182
182
|
direct_messages.each do |direct_message|
|
@@ -186,24 +186,20 @@ module T
|
|
186
186
|
end
|
187
187
|
map %w(directmessagessent sent_messages sentmessages sms) => :direct_messages_sent
|
188
188
|
|
189
|
-
desc
|
190
|
-
method_option
|
191
|
-
method_option
|
192
|
-
method_option
|
193
|
-
method_option
|
194
|
-
method_option
|
195
|
-
method_option
|
196
|
-
def groupies(user=nil)
|
189
|
+
desc 'groupies [USER]', "Returns the list of people who follow you but you don't follow back."
|
190
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
191
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
192
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
193
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
194
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => 'screen_name', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
195
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
196
|
+
def groupies(user = nil)
|
197
197
|
user = if user
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
204
|
-
else
|
205
|
-
client.verify_credentials.screen_name
|
206
|
-
end
|
198
|
+
require 't/core_ext/string'
|
199
|
+
options['id'] ? user.to_i : user.strip_ats
|
200
|
+
else
|
201
|
+
client.verify_credentials.screen_name
|
202
|
+
end
|
207
203
|
follower_ids = Thread.new do
|
208
204
|
client.follower_ids(user).to_a
|
209
205
|
end
|
@@ -219,33 +215,25 @@ module T
|
|
219
215
|
end
|
220
216
|
map %w(disciples) => :groupies
|
221
217
|
|
222
|
-
desc
|
223
|
-
method_option
|
218
|
+
desc 'dm USER MESSAGE', 'Sends that person a Direct Message.'
|
219
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
224
220
|
def dm(user, message)
|
225
221
|
require 't/core_ext/string'
|
226
|
-
user =
|
227
|
-
|
228
|
-
else
|
229
|
-
user.strip_ats
|
230
|
-
end
|
231
|
-
direct_message = client.direct_message_create(user, message)
|
222
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
223
|
+
direct_message = client.create_direct_message(user, message)
|
232
224
|
say "Direct Message sent from @#{@rcfile.active_profile[0]} to @#{direct_message.recipient.screen_name}."
|
233
225
|
end
|
234
226
|
map %w(d m) => :dm
|
235
227
|
|
236
|
-
desc
|
237
|
-
method_option
|
238
|
-
def does_contain(list, user=nil)
|
228
|
+
desc 'does_contain [USER/]LIST USER', 'Find out whether a list contains a user.'
|
229
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
230
|
+
def does_contain(list, user = nil)
|
239
231
|
owner, list = extract_owner(list, options)
|
240
232
|
if user.nil?
|
241
233
|
user = @rcfile.active_profile[0]
|
242
234
|
else
|
243
235
|
require 't/core_ext/string'
|
244
|
-
user =
|
245
|
-
user = client.user(user.to_i).screen_name
|
246
|
-
else
|
247
|
-
user.strip_ats
|
248
|
-
end
|
236
|
+
user = options['id'] ? client.user(user.to_i).screen_name : user.strip_ats
|
249
237
|
end
|
250
238
|
if client.list_member?(owner, list, user)
|
251
239
|
say "Yes, #{list} contains @#{user}."
|
@@ -256,23 +244,15 @@ module T
|
|
256
244
|
end
|
257
245
|
map %w(dc doescontain) => :does_contain
|
258
246
|
|
259
|
-
desc
|
260
|
-
method_option
|
261
|
-
def does_follow(user1, user2=nil)
|
247
|
+
desc 'does_follow USER [USER]', 'Find out whether one user follows another.'
|
248
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
249
|
+
def does_follow(user1, user2 = nil)
|
262
250
|
require 't/core_ext/string'
|
263
|
-
user1 =
|
264
|
-
client.user(user1.to_i).screen_name
|
265
|
-
else
|
266
|
-
user1.strip_ats
|
267
|
-
end
|
251
|
+
user1 = options['id'] ? client.user(user1.to_i).screen_name : user1.strip_ats
|
268
252
|
if user2.nil?
|
269
253
|
user2 = @rcfile.active_profile[0]
|
270
254
|
else
|
271
|
-
user2 =
|
272
|
-
client.user(user2.to_i).screen_name
|
273
|
-
else
|
274
|
-
user2.strip_ats
|
275
|
-
end
|
255
|
+
user2 = options['id'] ? client.user(user2.to_i).screen_name : user2.strip_ats
|
276
256
|
end
|
277
257
|
if client.friendship?(user1, user2)
|
278
258
|
say "Yes, @#{user1} follows @#{user2}."
|
@@ -283,7 +263,7 @@ module T
|
|
283
263
|
end
|
284
264
|
map %w(df doesfollow) => :does_follow
|
285
265
|
|
286
|
-
desc
|
266
|
+
desc 'favorite TWEET_ID [TWEET_ID...]', 'Marks Tweets as favorites.'
|
287
267
|
def favorite(status_id, *status_ids)
|
288
268
|
status_ids.unshift(status_id)
|
289
269
|
status_ids.map!(&:to_i)
|
@@ -294,19 +274,19 @@ module T
|
|
294
274
|
number = favorites.length
|
295
275
|
say "@#{@rcfile.active_profile[0]} favorited #{pluralize(number, 'tweet')}."
|
296
276
|
say
|
297
|
-
say "Run `#{File.basename($
|
277
|
+
say "Run `#{File.basename($PROGRAM_NAME)} delete favorite #{status_ids.join(' ')}` to unfavorite."
|
298
278
|
end
|
299
279
|
map %w(fave favourite) => :favorite
|
300
280
|
|
301
|
-
desc
|
302
|
-
method_option
|
303
|
-
method_option
|
304
|
-
method_option
|
305
|
-
method_option
|
306
|
-
method_option
|
307
|
-
method_option
|
308
|
-
method_option
|
309
|
-
def favorites(user=nil)
|
281
|
+
desc 'favorites [USER]', "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets you favorited."
|
282
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
283
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
284
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
285
|
+
method_option 'max_id', :aliases => '-m', :type => :numeric, :desc => 'Returns only the results with an ID less than the specified ID.'
|
286
|
+
method_option 'number', :aliases => '-n', :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => 'Limit the number of results.'
|
287
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
288
|
+
method_option 'since_id', :aliases => '-s', :type => :numeric, :desc => 'Returns only the results with an ID greater than the specified ID.'
|
289
|
+
def favorites(user = nil)
|
310
290
|
count = options['number'] || DEFAULT_NUM_RESULTS
|
311
291
|
opts = {}
|
312
292
|
opts[:exclude_replies] = true if options['exclude'] == 'replies'
|
@@ -315,11 +295,7 @@ module T
|
|
315
295
|
opts[:since_id] = options['since_id'] if options['since_id']
|
316
296
|
if user
|
317
297
|
require 't/core_ext/string'
|
318
|
-
user =
|
319
|
-
user.to_i
|
320
|
-
else
|
321
|
-
user.strip_ats
|
322
|
-
end
|
298
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
323
299
|
end
|
324
300
|
tweets = collect_with_count(count) do |count_opts|
|
325
301
|
client.favorites(user, count_opts.merge(opts))
|
@@ -328,32 +304,28 @@ module T
|
|
328
304
|
end
|
329
305
|
map %w(faves favourites) => :favorites
|
330
306
|
|
331
|
-
desc
|
332
|
-
method_option
|
307
|
+
desc 'follow USER [USER...]', 'Allows you to start following users.'
|
308
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify input as Twitter user IDs instead of screen names.'
|
333
309
|
def follow(user, *users)
|
334
|
-
|
335
|
-
client.follow(
|
310
|
+
followed_users, number = fetch_users(users.unshift(user), options) do |users_to_follow|
|
311
|
+
client.follow(users_to_follow)
|
336
312
|
end
|
337
313
|
say "@#{@rcfile.active_profile[0]} is now following #{pluralize(number, 'more user')}."
|
338
314
|
say
|
339
|
-
say "Run `#{File.basename($
|
315
|
+
say "Run `#{File.basename($PROGRAM_NAME)} unfollow #{followed_users.map { |followed_user| "@#{followed_user.screen_name}" }.join(' ')}` to stop."
|
340
316
|
end
|
341
317
|
|
342
|
-
desc
|
343
|
-
method_option
|
344
|
-
method_option
|
345
|
-
method_option
|
346
|
-
method_option
|
347
|
-
method_option
|
348
|
-
method_option
|
349
|
-
def followings(user=nil)
|
318
|
+
desc 'followings [USER]', 'Returns a list of the people you follow on Twitter.'
|
319
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
320
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
321
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
322
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
323
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => 'screen_name', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
324
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
325
|
+
def followings(user = nil)
|
350
326
|
if user
|
351
327
|
require 't/core_ext/string'
|
352
|
-
user =
|
353
|
-
user.to_i
|
354
|
-
else
|
355
|
-
user.strip_ats
|
356
|
-
end
|
328
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
357
329
|
end
|
358
330
|
following_ids = client.friend_ids(user).to_a
|
359
331
|
require 'retryable'
|
@@ -363,21 +335,17 @@ module T
|
|
363
335
|
print_users(users)
|
364
336
|
end
|
365
337
|
|
366
|
-
desc
|
367
|
-
method_option
|
368
|
-
method_option
|
369
|
-
method_option
|
370
|
-
method_option
|
371
|
-
method_option
|
372
|
-
method_option
|
373
|
-
def followers(user=nil)
|
338
|
+
desc 'followers [USER]', 'Returns a list of the people who follow you on Twitter.'
|
339
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
340
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
341
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
342
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
343
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => 'screen_name', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
344
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
345
|
+
def followers(user = nil)
|
374
346
|
if user
|
375
347
|
require 't/core_ext/string'
|
376
|
-
user =
|
377
|
-
user.to_i
|
378
|
-
else
|
379
|
-
user.strip_ats
|
380
|
-
end
|
348
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
381
349
|
end
|
382
350
|
follower_ids = client.follower_ids(user).to_a
|
383
351
|
require 'retryable'
|
@@ -387,24 +355,20 @@ module T
|
|
387
355
|
print_users(users)
|
388
356
|
end
|
389
357
|
|
390
|
-
desc
|
391
|
-
method_option
|
392
|
-
method_option
|
393
|
-
method_option
|
394
|
-
method_option
|
395
|
-
method_option
|
396
|
-
method_option
|
397
|
-
def friends(user=nil)
|
358
|
+
desc 'friends [USER]', 'Returns the list of people who you follow and follow you back.'
|
359
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
360
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
361
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
362
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
363
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => 'screen_name', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
364
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
365
|
+
def friends(user = nil)
|
398
366
|
user = if user
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
end
|
405
|
-
else
|
406
|
-
client.verify_credentials.screen_name
|
407
|
-
end
|
367
|
+
require 't/core_ext/string'
|
368
|
+
options['id'] ? user.to_i : user.strip_ats
|
369
|
+
else
|
370
|
+
client.verify_credentials.screen_name
|
371
|
+
end
|
408
372
|
following_ids = Thread.new do
|
409
373
|
client.friend_ids(user).to_a
|
410
374
|
end
|
@@ -419,24 +383,20 @@ module T
|
|
419
383
|
print_users(users)
|
420
384
|
end
|
421
385
|
|
422
|
-
desc
|
423
|
-
method_option
|
424
|
-
method_option
|
425
|
-
method_option
|
426
|
-
method_option
|
427
|
-
method_option
|
428
|
-
method_option
|
429
|
-
def leaders(user=nil)
|
386
|
+
desc 'leaders [USER]', "Returns the list of people who you follow but don't follow you back."
|
387
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
388
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
389
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
390
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
391
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => 'screen_name', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
392
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
393
|
+
def leaders(user = nil)
|
430
394
|
user = if user
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
end
|
437
|
-
else
|
438
|
-
client.verify_credentials.screen_name
|
439
|
-
end
|
395
|
+
require 't/core_ext/string'
|
396
|
+
options['id'] ? user.to_i : user.strip_ats
|
397
|
+
else
|
398
|
+
client.verify_credentials.screen_name
|
399
|
+
end
|
440
400
|
following_ids = Thread.new do
|
441
401
|
client.friend_ids(user).to_a
|
442
402
|
end
|
@@ -451,36 +411,32 @@ module T
|
|
451
411
|
print_users(users)
|
452
412
|
end
|
453
413
|
|
454
|
-
desc
|
455
|
-
method_option
|
456
|
-
method_option
|
457
|
-
method_option
|
458
|
-
method_option
|
459
|
-
method_option
|
460
|
-
method_option
|
461
|
-
def lists(user=nil)
|
414
|
+
desc 'lists [USER]', 'Returns the lists created by a user.'
|
415
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
416
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
417
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
418
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
419
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(members mode since slug subscribers), :default => 'slug', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
420
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
421
|
+
def lists(user = nil)
|
462
422
|
lists = if user
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
client.lists(user)
|
470
|
-
else
|
471
|
-
client.lists
|
472
|
-
end
|
423
|
+
require 't/core_ext/string'
|
424
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
425
|
+
client.lists(user)
|
426
|
+
else
|
427
|
+
client.lists
|
428
|
+
end
|
473
429
|
print_lists(lists)
|
474
430
|
end
|
475
431
|
|
476
|
-
desc
|
477
|
-
def_delegator :"T::Stream.new", :matrix
|
432
|
+
desc 'matrix', 'Unfortunately, no one can be told what the Matrix is. You have to see it for yourself.'
|
433
|
+
def_delegator :"T::Stream.new", :matrix # rubocop:disable SymbolName
|
478
434
|
|
479
|
-
desc
|
480
|
-
method_option
|
481
|
-
method_option
|
482
|
-
method_option
|
483
|
-
method_option
|
435
|
+
desc 'mentions', "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets mentioning you."
|
436
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
437
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
438
|
+
method_option 'number', :aliases => '-n', :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => 'Limit the number of results.'
|
439
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
484
440
|
def mentions
|
485
441
|
count = options['number'] || DEFAULT_NUM_RESULTS
|
486
442
|
tweets = collect_with_count(count) do |count_opts|
|
@@ -490,10 +446,10 @@ module T
|
|
490
446
|
end
|
491
447
|
map %w(replies) => :mentions
|
492
448
|
|
493
|
-
desc
|
494
|
-
method_option
|
495
|
-
method_option
|
496
|
-
method_option
|
449
|
+
desc 'open USER', "Opens that user's profile in a web browser."
|
450
|
+
method_option 'display-uri', :aliases => '-d', :type => :boolean, :default => false, :desc => 'Display the requested URL instead of attempting to open it.'
|
451
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
452
|
+
method_option 'status', :aliases => '-s', :type => :boolean, :default => false, :desc => 'Specify input as a Twitter status ID instead of a screen name.'
|
497
453
|
def open(user)
|
498
454
|
require 'launchy'
|
499
455
|
if options['id']
|
@@ -508,9 +464,9 @@ module T
|
|
508
464
|
end
|
509
465
|
end
|
510
466
|
|
511
|
-
desc
|
512
|
-
method_option
|
513
|
-
method_option
|
467
|
+
desc 'reply TWEET_ID MESSAGE', 'Post your Tweet as a reply directed at another person.'
|
468
|
+
method_option 'all', :aliases => '-a', :type => :boolean, :default => false, :desc => 'Reply to all users mentioned in the Tweet.'
|
469
|
+
method_option 'location', :aliases => '-l', :type => :string, :default => 'location', :desc => "Add location information. If the optional 'latitude,longitude' parameter is not supplied, looks up location by IP address."
|
514
470
|
def reply(status_id, message)
|
515
471
|
status = client.status(status_id.to_i, :include_my_retweet => false)
|
516
472
|
users = Array(status.user.screen_name)
|
@@ -525,20 +481,20 @@ module T
|
|
525
481
|
reply = client.update("#{users.join(' ')} #{message}", opts)
|
526
482
|
say "Reply posted by @#{@rcfile.active_profile[0]} to #{users.join(' ')}."
|
527
483
|
say
|
528
|
-
say "Run `#{File.basename($
|
484
|
+
say "Run `#{File.basename($PROGRAM_NAME)} delete status #{reply.id}` to delete."
|
529
485
|
end
|
530
486
|
|
531
|
-
desc
|
532
|
-
method_option
|
487
|
+
desc 'report_spam USER [USER...]', 'Report users for spam.'
|
488
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify input as Twitter user IDs instead of screen names.'
|
533
489
|
def report_spam(user, *users)
|
534
|
-
|
535
|
-
client.report_spam(
|
490
|
+
_, number = fetch_users(users.unshift(user), options) do |users_to_report|
|
491
|
+
client.report_spam(users_to_report)
|
536
492
|
end
|
537
493
|
say "@#{@rcfile.active_profile[0]} reported #{pluralize(number, 'user')}."
|
538
494
|
end
|
539
495
|
map %w(report reportspam spam) => :report_spam
|
540
496
|
|
541
|
-
desc
|
497
|
+
desc 'retweet TWEET_ID [TWEET_ID...]', 'Sends Tweets to your followers.'
|
542
498
|
def retweet(status_id, *status_ids)
|
543
499
|
status_ids.unshift(status_id)
|
544
500
|
status_ids.map!(&:to_i)
|
@@ -549,99 +505,95 @@ module T
|
|
549
505
|
number = retweets.length
|
550
506
|
say "@#{@rcfile.active_profile[0]} retweeted #{pluralize(number, 'tweet')}."
|
551
507
|
say
|
552
|
-
say "Run `#{File.basename($
|
508
|
+
say "Run `#{File.basename($PROGRAM_NAME)} delete status #{retweets.map { |tweet| tweet.retweeted_status.id }.join(' ')}` to undo."
|
553
509
|
end
|
554
510
|
map %w(rt) => :retweet
|
555
511
|
|
556
|
-
desc
|
557
|
-
method_option
|
558
|
-
method_option
|
559
|
-
method_option
|
560
|
-
method_option
|
561
|
-
method_option
|
562
|
-
def retweets(user=nil)
|
512
|
+
desc 'retweets [USER]', "Returns the #{DEFAULT_NUM_RESULTS} most recent Retweets by a user."
|
513
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
514
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
515
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
516
|
+
method_option 'number', :aliases => '-n', :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => 'Limit the number of results.'
|
517
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
518
|
+
def retweets(user = nil)
|
563
519
|
count = options['number'] || DEFAULT_NUM_RESULTS
|
564
520
|
tweets = if user
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
collect_with_count(count) do |count_opts|
|
576
|
-
client.retweeted_by_me(count_opts)
|
577
|
-
end
|
578
|
-
end
|
521
|
+
require 't/core_ext/string'
|
522
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
523
|
+
collect_with_count(count) do |count_opts|
|
524
|
+
client.retweeted_by_user(user, count_opts)
|
525
|
+
end
|
526
|
+
else
|
527
|
+
collect_with_count(count) do |count_opts|
|
528
|
+
client.retweeted_by_me(count_opts)
|
529
|
+
end
|
530
|
+
end
|
579
531
|
print_tweets(tweets)
|
580
532
|
end
|
581
533
|
map %w(rts) => :retweets
|
582
534
|
|
583
|
-
desc
|
584
|
-
method_option
|
535
|
+
desc 'ruler', 'Prints a 140-character ruler'
|
536
|
+
method_option 'indent', :aliases => '-i', :type => :numeric, :default => 0, :desc => 'The number of space to print before the ruler.'
|
585
537
|
def ruler
|
586
538
|
markings = '----|'.chars.cycle.take(140).join
|
587
539
|
say "#{' ' * options['indent'].to_i}#{markings}"
|
588
540
|
end
|
589
541
|
|
590
|
-
desc
|
591
|
-
method_option
|
592
|
-
method_option
|
593
|
-
def status(status_id)
|
542
|
+
desc 'status TWEET_ID', 'Retrieves detailed information about a Tweet.'
|
543
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
544
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
545
|
+
def status(status_id) # rubocop:disable CyclomaticComplexity
|
594
546
|
status = client.status(status_id.to_i, :include_my_retweet => false)
|
595
547
|
location = if status.place?
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
status_headings = [
|
548
|
+
if status.place.name && status.place.attributes && status.place.attributes[:street_address] && status.place.attributes[:locality] && status.place.attributes[:region] && status.place.country
|
549
|
+
[status.place.name, status.place.attributes[:street_address], status.place.attributes[:locality], status.place.attributes[:region], status.place.country].join(', ')
|
550
|
+
elsif status.place.name && status.place.attributes && status.place.attributes[:locality] && status.place.attributes[:region] && status.place.country
|
551
|
+
[status.place.name, status.place.attributes[:locality], status.place.attributes[:region], status.place.country].join(', ')
|
552
|
+
elsif status.place.full_name && status.place.attributes && status.place.attributes[:region] && status.place.country
|
553
|
+
[status.place.full_name, status.place.attributes[:region], status.place.country].join(', ')
|
554
|
+
elsif status.place.full_name && status.place.country
|
555
|
+
[status.place.full_name, status.place.country].join(', ')
|
556
|
+
elsif status.place.full_name
|
557
|
+
status.place.full_name
|
558
|
+
else
|
559
|
+
status.place.name
|
560
|
+
end
|
561
|
+
elsif status.geo?
|
562
|
+
reverse_geocode(status.geo)
|
563
|
+
end
|
564
|
+
status_headings = ['ID', 'Posted at', 'Screen name', 'Text', 'Retweets', 'Favorites', 'Source', 'Location']
|
613
565
|
if options['csv']
|
614
566
|
require 'csv'
|
615
567
|
say status_headings.to_csv
|
616
568
|
say [status.id, csv_formatted_time(status), status.user.screen_name, decode_full_text(status), status.retweet_count, status.favorite_count, strip_tags(status.source), location].to_csv
|
617
569
|
elsif options['long']
|
618
570
|
array = [status.id, ls_formatted_time(status), "@#{status.user.screen_name}", decode_full_text(status).gsub(/\n+/, ' '), status.retweet_count, status.favorite_count, strip_tags(status.source), location]
|
619
|
-
format = options['format'] || status_headings.size.times.map{
|
571
|
+
format = options['format'] || status_headings.size.times.map { '%s' }
|
620
572
|
print_table_with_headings([array], status_headings, format)
|
621
573
|
else
|
622
574
|
array = []
|
623
|
-
array << [
|
624
|
-
array << [
|
625
|
-
array << [
|
626
|
-
array << [
|
627
|
-
array << [
|
628
|
-
array << [
|
629
|
-
array << [
|
630
|
-
array << [
|
575
|
+
array << ['ID', status.id.to_s]
|
576
|
+
array << ['Text', decode_full_text(status).gsub(/\n+/, ' ')]
|
577
|
+
array << ['Screen name', "@#{status.user.screen_name}"]
|
578
|
+
array << ['Posted at', "#{ls_formatted_time(status)} (#{time_ago_in_words(status.created_at)} ago)"]
|
579
|
+
array << ['Retweets', number_with_delimiter(status.retweet_count)]
|
580
|
+
array << ['Favorites', number_with_delimiter(status.favorite_count)]
|
581
|
+
array << ['Source', strip_tags(status.source)]
|
582
|
+
array << ['Location', location] unless location.nil?
|
631
583
|
print_table(array)
|
632
584
|
end
|
633
585
|
end
|
634
586
|
|
635
|
-
desc
|
636
|
-
method_option
|
637
|
-
method_option
|
638
|
-
method_option
|
639
|
-
method_option
|
640
|
-
method_option
|
641
|
-
method_option
|
642
|
-
method_option
|
643
|
-
method_option
|
644
|
-
def timeline(user=nil)
|
587
|
+
desc 'timeline [USER]', "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets posted by a user."
|
588
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
589
|
+
method_option 'exclude', :aliases => '-e', :type => :string, :enum => %w(replies retweets), :desc => 'Exclude certain types of Tweets from the results.', :banner => 'TYPE'
|
590
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
591
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
592
|
+
method_option 'max_id', :aliases => '-m', :type => :numeric, :desc => 'Returns only the results with an ID less than the specified ID.'
|
593
|
+
method_option 'number', :aliases => '-n', :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => 'Limit the number of results.'
|
594
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
595
|
+
method_option 'since_id', :aliases => '-s', :type => :numeric, :desc => 'Returns only the results with an ID greater than the specified ID.'
|
596
|
+
def timeline(user = nil)
|
645
597
|
count = options['number'] || DEFAULT_NUM_RESULTS
|
646
598
|
opts = {}
|
647
599
|
opts[:exclude_replies] = true if options['exclude'] == 'replies'
|
@@ -650,11 +602,7 @@ module T
|
|
650
602
|
opts[:since_id] = options['since_id'] if options['since_id']
|
651
603
|
if user
|
652
604
|
require 't/core_ext/string'
|
653
|
-
user =
|
654
|
-
user.to_i
|
655
|
-
else
|
656
|
-
user.strip_ats
|
657
|
-
end
|
605
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
658
606
|
tweets = collect_with_count(count) do |count_opts|
|
659
607
|
client.user_timeline(user, count_opts.merge(opts))
|
660
608
|
end
|
@@ -667,35 +615,35 @@ module T
|
|
667
615
|
end
|
668
616
|
map %w(tl) => :timeline
|
669
617
|
|
670
|
-
desc
|
671
|
-
method_option
|
672
|
-
def trends(woe_id=1)
|
618
|
+
desc 'trends [WOEID]', 'Returns the top 10 trending topics.'
|
619
|
+
method_option 'exclude-hashtags', :aliases => '-x', :type => :boolean, :default => false, :desc => 'Remove all hashtags from the trends list.'
|
620
|
+
def trends(woe_id = 1)
|
673
621
|
opts = {}
|
674
|
-
opts.merge!(:exclude =>
|
622
|
+
opts.merge!(:exclude => 'hashtags') if options['exclude-hashtags']
|
675
623
|
trends = client.trends(woe_id, opts)
|
676
624
|
print_attribute(trends, :name)
|
677
625
|
end
|
678
626
|
|
679
|
-
desc
|
680
|
-
method_option
|
681
|
-
method_option
|
682
|
-
method_option
|
683
|
-
method_option
|
684
|
-
method_option
|
627
|
+
desc 'trend_locations', 'Returns the locations for which Twitter has trending topic information.'
|
628
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
629
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
630
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
631
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(country name parent type woeid), :default => 'name', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
632
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
685
633
|
def trend_locations
|
686
634
|
places = client.trend_locations
|
687
635
|
places = case options['sort']
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
636
|
+
when 'country'
|
637
|
+
places.sort_by { |place| place.country.downcase }
|
638
|
+
when 'parent'
|
639
|
+
places.sort_by { |place| place.parent_id.to_i }
|
640
|
+
when 'type'
|
641
|
+
places.sort_by { |place| place.place_type.downcase }
|
642
|
+
when 'woeid'
|
643
|
+
places.sort_by { |place| place.woeid.to_i }
|
644
|
+
else
|
645
|
+
places.sort_by { |place| place.name.downcase }
|
646
|
+
end unless options['unsorted']
|
699
647
|
places.reverse! if options['reverse']
|
700
648
|
if options['csv']
|
701
649
|
require 'csv'
|
@@ -707,7 +655,7 @@ module T
|
|
707
655
|
array = places.map do |place|
|
708
656
|
[place.woeid, place.parent_id, place.place_type, place.name, place.country]
|
709
657
|
end
|
710
|
-
format = options['format'] || TREND_HEADINGS.size.times.map{
|
658
|
+
format = options['format'] || TREND_HEADINGS.size.times.map { '%s' }
|
711
659
|
print_table_with_headings(array, TREND_HEADINGS, format)
|
712
660
|
else
|
713
661
|
print_attribute(places, :name)
|
@@ -715,110 +663,102 @@ module T
|
|
715
663
|
end
|
716
664
|
map %w(locations trendlocations) => :trend_locations
|
717
665
|
|
718
|
-
desc
|
719
|
-
method_option
|
666
|
+
desc 'unfollow USER [USER...]', 'Allows you to stop following users.'
|
667
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify input as Twitter user IDs instead of screen names.'
|
720
668
|
def unfollow(user, *users)
|
721
|
-
|
722
|
-
client.unfollow(
|
669
|
+
unfollowed_users, number = fetch_users(users.unshift(user), options) do |users_to_unfollow|
|
670
|
+
client.unfollow(users_to_unfollow)
|
723
671
|
end
|
724
672
|
say "@#{@rcfile.active_profile[0]} is no longer following #{pluralize(number, 'user')}."
|
725
673
|
say
|
726
|
-
say "Run `#{File.basename($
|
674
|
+
say "Run `#{File.basename($PROGRAM_NAME)} follow #{unfollowed_users.map { |unfollowed_user| "@#{unfollowed_user.screen_name}" }.join(' ')}` to follow again."
|
727
675
|
end
|
728
676
|
|
729
|
-
desc
|
730
|
-
method_option
|
731
|
-
method_option
|
732
|
-
def update(message=nil)
|
677
|
+
desc 'update [MESSAGE]', 'Post a Tweet.'
|
678
|
+
method_option 'location', :aliases => '-l', :type => :string, :default => 'location', :desc => "Add location information. If the optional 'latitude,longitude' parameter is not supplied, looks up location by IP address."
|
679
|
+
method_option 'file', :aliases => '-f', :type => :string, :desc => 'The path to an image to attach to your tweet.'
|
680
|
+
def update(message = nil)
|
733
681
|
message = T::Editor.gets if message.nil? || message.empty?
|
734
682
|
opts = {:trim_user => true}
|
735
683
|
add_location!(options, opts)
|
736
684
|
status = if options['file']
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
685
|
+
client.update_with_media(message, File.new(File.expand_path(options['file'])), opts)
|
686
|
+
else
|
687
|
+
client.update(message, opts)
|
688
|
+
end
|
741
689
|
say "Tweet posted by @#{@rcfile.active_profile[0]}."
|
742
690
|
say
|
743
|
-
say "Run `#{File.basename($
|
691
|
+
say "Run `#{File.basename($PROGRAM_NAME)} delete status #{status.id}` to delete."
|
744
692
|
end
|
745
693
|
map %w(post tweet) => :update
|
746
694
|
|
747
|
-
desc
|
748
|
-
method_option
|
749
|
-
method_option
|
750
|
-
method_option
|
751
|
-
method_option
|
752
|
-
method_option
|
753
|
-
method_option
|
695
|
+
desc 'users USER [USER...]', 'Returns a list of users you specify.'
|
696
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
697
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify input as Twitter user IDs instead of screen names.'
|
698
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
699
|
+
method_option 'reverse', :aliases => '-r', :type => :boolean, :default => false, :desc => 'Reverse the order of the sort.'
|
700
|
+
method_option 'sort', :aliases => '-s', :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => 'screen_name', :desc => 'Specify the order of the results.', :banner => 'ORDER'
|
701
|
+
method_option 'unsorted', :aliases => '-u', :type => :boolean, :default => false, :desc => 'Output is not sorted.'
|
754
702
|
def users(user, *users)
|
755
703
|
users.unshift(user)
|
756
704
|
require 't/core_ext/string'
|
757
|
-
|
758
|
-
users.map!(&:to_i)
|
759
|
-
else
|
760
|
-
users.map!(&:strip_ats)
|
761
|
-
end
|
705
|
+
options['id'] ? users.map!(&:to_i) : users.map!(&:strip_ats)
|
762
706
|
users = client.users(users)
|
763
707
|
print_users(users)
|
764
708
|
end
|
765
709
|
map %w(stats) => :users
|
766
710
|
|
767
|
-
desc
|
711
|
+
desc 'version', 'Show version.'
|
768
712
|
def version
|
769
713
|
require 't/version'
|
770
714
|
say T::Version
|
771
715
|
end
|
772
716
|
map %w(-v --version) => :version
|
773
717
|
|
774
|
-
desc
|
775
|
-
method_option
|
776
|
-
method_option
|
777
|
-
method_option
|
718
|
+
desc 'whois USER', 'Retrieves profile information for the user.'
|
719
|
+
method_option 'csv', :aliases => '-c', :type => :boolean, :default => false, :desc => 'Output in CSV format.'
|
720
|
+
method_option 'id', :aliases => '-i', :type => :boolean, :default => false, :desc => 'Specify user via ID instead of screen name.'
|
721
|
+
method_option 'long', :aliases => '-l', :type => :boolean, :default => false, :desc => 'Output in long format.'
|
778
722
|
def whois(user)
|
779
723
|
require 't/core_ext/string'
|
780
|
-
user =
|
781
|
-
user.to_i
|
782
|
-
else
|
783
|
-
user.strip_ats
|
784
|
-
end
|
724
|
+
user = options['id'] ? user.to_i : user.strip_ats
|
785
725
|
user = client.user(user)
|
786
726
|
require 'htmlentities'
|
787
727
|
if options['csv'] || options['long']
|
788
728
|
print_users([user])
|
789
729
|
else
|
790
730
|
array = []
|
791
|
-
array << [
|
792
|
-
array << [
|
793
|
-
array << [
|
794
|
-
array << [
|
795
|
-
array << [user.verified ?
|
796
|
-
array << [
|
797
|
-
array << [
|
798
|
-
array << [
|
799
|
-
array << [
|
800
|
-
array << [
|
801
|
-
array << [
|
802
|
-
array << [
|
803
|
-
array << [
|
731
|
+
array << ['ID', user.id.to_s]
|
732
|
+
array << ['Since', "#{ls_formatted_time(user)} (#{time_ago_in_words(user.created_at)} ago)"]
|
733
|
+
array << ['Last update', "#{decode_full_text(user.status).gsub(/\n+/, ' ')} (#{time_ago_in_words(user.status.created_at)} ago)"] unless user.status.nil?
|
734
|
+
array << ['Screen name', "@#{user.screen_name}"]
|
735
|
+
array << [user.verified ? 'Name (Verified)' : 'Name', user.name] unless user.name.nil?
|
736
|
+
array << ['Tweets', number_with_delimiter(user.statuses_count)]
|
737
|
+
array << ['Favorites', number_with_delimiter(user.favorites_count)]
|
738
|
+
array << ['Listed', number_with_delimiter(user.listed_count)]
|
739
|
+
array << ['Following', number_with_delimiter(user.friends_count)]
|
740
|
+
array << ['Followers', number_with_delimiter(user.followers_count)]
|
741
|
+
array << ['Bio', user.description.gsub(/\n+/, ' ')] unless user.description.nil?
|
742
|
+
array << ['Location', user.location] unless user.location.nil?
|
743
|
+
array << ['URL', user.website] unless user.website.nil?
|
804
744
|
print_table(array)
|
805
745
|
end
|
806
746
|
end
|
807
747
|
map %w(user) => :whois
|
808
748
|
|
809
|
-
desc
|
749
|
+
desc 'delete SUBCOMMAND ...ARGS', 'Delete Tweets, Direct Messages, etc.'
|
810
750
|
subcommand 'delete', T::Delete
|
811
751
|
|
812
|
-
desc
|
752
|
+
desc 'list SUBCOMMAND ...ARGS', 'Do various things with lists.'
|
813
753
|
subcommand 'list', T::List
|
814
754
|
|
815
|
-
desc
|
755
|
+
desc 'search SUBCOMMAND ...ARGS', 'Search through Tweets.'
|
816
756
|
subcommand 'search', T::Search
|
817
757
|
|
818
|
-
desc
|
758
|
+
desc 'set SUBCOMMAND ...ARGS', 'Change various account settings.'
|
819
759
|
subcommand 'set', T::Set
|
820
760
|
|
821
|
-
desc
|
761
|
+
desc 'stream SUBCOMMAND ...ARGS', 'Commands for streaming Tweets.'
|
822
762
|
subcommand 'stream', T::Stream
|
823
763
|
|
824
764
|
private
|
@@ -844,7 +784,7 @@ module T
|
|
844
784
|
params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map do |param|
|
845
785
|
key, value = param.split('=')
|
846
786
|
value =~ /"(.*?)"/
|
847
|
-
"#{key}=#{CGI
|
787
|
+
"#{key}=#{CGI.escape(Regexp.last_match[1])}"
|
848
788
|
end.join('&')
|
849
789
|
"#{Twitter::REST::Client::ENDPOINT}#{request.path}?#{params}"
|
850
790
|
end
|
@@ -864,7 +804,7 @@ module T
|
|
864
804
|
return @location if @location
|
865
805
|
require 'geokit'
|
866
806
|
require 'open-uri'
|
867
|
-
ip_address = Kernel
|
807
|
+
ip_address = Kernel.open('http://checkip.dyndns.org/') do |body|
|
868
808
|
/(?:\d{1,3}\.){3}\d{1,3}/.match(body.read)[0]
|
869
809
|
end
|
870
810
|
@location = Geokit::Geocoders::MultiGeocoder.geocode(ip_address)
|
@@ -874,13 +814,12 @@ module T
|
|
874
814
|
require 'geokit'
|
875
815
|
geoloc = Geokit::Geocoders::MultiGeocoder.reverse_geocode(geo.coordinates)
|
876
816
|
if geoloc.city && geoloc.state && geoloc.country
|
877
|
-
[geoloc.city, geoloc.state, geoloc.country].join(
|
817
|
+
[geoloc.city, geoloc.state, geoloc.country].join(', ')
|
878
818
|
elsif geoloc.state && geoloc.country
|
879
|
-
[geoloc.state, geoloc.country].join(
|
819
|
+
[geoloc.state, geoloc.country].join(', ')
|
880
820
|
else
|
881
821
|
geoloc.country
|
882
822
|
end
|
883
823
|
end
|
884
|
-
|
885
824
|
end
|
886
825
|
end
|