t 0.3.1 → 0.4.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/.rspec +1 -1
- data/.travis.yml +4 -4
- data/README.md +19 -8
- data/lib/t/cli.rb +33 -17
- data/lib/t/cli/delete.rb +4 -31
- data/lib/t/cli/follow.rb +5 -33
- data/lib/t/cli/list.rb +2 -29
- data/lib/t/cli/list/add.rb +27 -41
- data/lib/t/cli/list/remove.rb +19 -43
- data/lib/t/cli/search.rb +54 -33
- data/lib/t/cli/set.rb +2 -29
- data/lib/t/cli/unfollow.rb +7 -35
- data/lib/t/core_ext/enumerable.rb +9 -0
- data/lib/t/rcfile.rb +0 -4
- data/lib/t/requestable.rb +37 -0
- data/lib/t/version.rb +2 -2
- data/spec/cli/delete_spec.rb +2 -1
- data/spec/cli/follow_spec.rb +2 -0
- data/spec/cli/list/add_spec.rb +60 -0
- data/spec/cli/list/remove_spec.rb +82 -92
- data/spec/cli/list_spec.rb +4 -0
- data/spec/cli/search_spec.rb +232 -0
- data/spec/cli/set_spec.rb +2 -1
- data/spec/cli/unfollow_spec.rb +2 -0
- data/spec/cli_spec.rb +81 -1
- data/spec/helper.rb +5 -2
- data/t.gemspec +3 -3
- metadata +129 -42
data/lib/t/cli/list/remove.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
1
|
+
require 'active_support/core_ext/array/grouping'
|
1
2
|
require 'retryable'
|
2
3
|
require 't/core_ext/enumerable'
|
3
4
|
require 't/core_ext/string'
|
4
5
|
require 't/collectable'
|
5
6
|
require 't/rcfile'
|
7
|
+
require 't/requestable'
|
6
8
|
require 'thor'
|
7
|
-
require 'twitter'
|
8
9
|
|
9
10
|
module T
|
10
11
|
class CLI
|
11
12
|
class List
|
12
13
|
class Remove < Thor
|
13
14
|
include T::Collectable
|
15
|
+
include T::Requestable
|
14
16
|
|
15
|
-
|
16
|
-
DEFAULT_PROTOCOL = 'https'
|
17
|
+
MAX_USERS_PER_REQUEST = 100
|
17
18
|
|
18
19
|
check_unknown_options!
|
19
20
|
|
@@ -37,14 +38,14 @@ module T
|
|
37
38
|
else
|
38
39
|
return unless yes? "Are you sure you want to remove #{number} #{number == 1 ? 'friend' : 'friends'} from the list \"#{list_name}\"?"
|
39
40
|
end
|
40
|
-
list_member_ids_to_remove.
|
41
|
+
list_member_ids_to_remove.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
|
41
42
|
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
42
|
-
client.
|
43
|
+
client.list_remove_members(list_name, user_id_group)
|
43
44
|
end
|
44
45
|
end
|
45
46
|
say "@#{@rcfile.default_profile[0]} removed #{number} #{number == 1 ? 'friend' : 'friends'} from the list \"#{list_name}\"."
|
46
47
|
say
|
47
|
-
say "Run `#{$0} list add all friends #{list_name}` to undo."
|
48
|
+
say "Run `#{File.basename($0)} list add all friends #{list_name}` to undo."
|
48
49
|
end
|
49
50
|
|
50
51
|
desc "followers LIST_NAME", "Remove all followers from a list."
|
@@ -62,14 +63,14 @@ module T
|
|
62
63
|
else
|
63
64
|
return unless yes? "Are you sure you want to remove #{number} #{number == 1 ? 'follower' : 'followers'} from the list \"#{list_name}\"?"
|
64
65
|
end
|
65
|
-
list_member_ids_to_remove.
|
66
|
+
list_member_ids_to_remove.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
|
66
67
|
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
67
|
-
client.
|
68
|
+
client.list_remove_members(list_name, user_id_group)
|
68
69
|
end
|
69
70
|
end
|
70
71
|
say "@#{@rcfile.default_profile[0]} removed #{number} #{number == 1 ? 'follower' : 'followers'} from the list \"#{list_name}\"."
|
71
72
|
say
|
72
|
-
say "Run `#{$0} list add all followers #{list_name}` to undo."
|
73
|
+
say "Run `#{File.basename($0)} list add all followers #{list_name}` to undo."
|
73
74
|
end
|
74
75
|
|
75
76
|
desc "listed FROM_LIST_NAME TO_LIST_NAME", "Remove all list members from a list."
|
@@ -87,14 +88,14 @@ module T
|
|
87
88
|
else
|
88
89
|
return unless yes? "Are you sure you want to remove #{number} #{number == 1 ? 'member' : 'members'} from the list \"#{to_list_name}\"?"
|
89
90
|
end
|
90
|
-
list_member_ids_to_remove.
|
91
|
+
list_member_ids_to_remove.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
|
91
92
|
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
92
|
-
client.
|
93
|
+
client.list_remove_members(to_list_name, user_id_group)
|
93
94
|
end
|
94
95
|
end
|
95
96
|
say "@#{@rcfile.default_profile[0]} removed #{number} #{number == 1 ? 'member' : 'members'} from the list \"#{to_list_name}\"."
|
96
97
|
say
|
97
|
-
say "Run `#{$0} list add all listed #{from_list_name} #{to_list_name}` to undo."
|
98
|
+
say "Run `#{File.basename($0)} list add all listed #{from_list_name} #{to_list_name}` to undo."
|
98
99
|
end
|
99
100
|
|
100
101
|
desc "members LIST_NAME", "Remove all members from a list."
|
@@ -105,9 +106,9 @@ module T
|
|
105
106
|
number = list_members.length
|
106
107
|
return say "The list \"#{list_name}\" doesn't have any members." if number.zero?
|
107
108
|
return unless yes? "Are you sure you want to remove #{number} #{number == 1 ? 'member' : 'members'} from the list \"#{list_name}\"?"
|
108
|
-
list_members.collect(&:id).
|
109
|
+
list_members.collect(&:id).in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
|
109
110
|
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
110
|
-
client.
|
111
|
+
client.list_remove_members(list_name, user_id_group)
|
111
112
|
end
|
112
113
|
end
|
113
114
|
say "@#{@rcfile.default_profile[0]} removed #{number} #{number == 1 ? 'member' : 'members'} from the list \"#{list_name}\"."
|
@@ -117,41 +118,16 @@ module T
|
|
117
118
|
desc "users LIST_NAME SCREEN_NAME [SCREEN_NAME...]", "Remove users from a list."
|
118
119
|
def users(list_name, screen_name, *screen_names)
|
119
120
|
screen_names.unshift(screen_name)
|
120
|
-
screen_names.
|
121
|
+
screen_names.map!(&:strip_at)
|
122
|
+
screen_names.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
|
121
123
|
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
122
|
-
client.
|
124
|
+
client.list_remove_members(list_name, user_id_group)
|
123
125
|
end
|
124
126
|
end
|
125
127
|
number = screen_names.length
|
126
128
|
say "@#{@rcfile.default_profile[0]} removed #{number} #{number == 1 ? 'user' : 'users'} from the list \"#{list_name}\"."
|
127
129
|
say
|
128
|
-
say "Run `#{$0} list add users #{list_name} #{screen_names.join(' ')}` to undo."
|
129
|
-
end
|
130
|
-
|
131
|
-
private
|
132
|
-
|
133
|
-
def base_url
|
134
|
-
"#{protocol}://#{host}"
|
135
|
-
end
|
136
|
-
|
137
|
-
def client
|
138
|
-
return @client if @client
|
139
|
-
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
140
|
-
@client = Twitter::Client.new(
|
141
|
-
:endpoint => base_url,
|
142
|
-
:consumer_key => @rcfile.default_consumer_key,
|
143
|
-
:consumer_secret => @rcfile.default_consumer_secret,
|
144
|
-
:oauth_token => @rcfile.default_token,
|
145
|
-
:oauth_token_secret => @rcfile.default_secret
|
146
|
-
)
|
147
|
-
end
|
148
|
-
|
149
|
-
def host
|
150
|
-
parent_options['host'] || DEFAULT_HOST
|
151
|
-
end
|
152
|
-
|
153
|
-
def protocol
|
154
|
-
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
130
|
+
say "Run `#{File.basename($0)} list add users #{list_name} #{screen_names.join(' ')}` to undo."
|
155
131
|
end
|
156
132
|
|
157
133
|
end
|
data/lib/t/cli/search.rb
CHANGED
@@ -3,17 +3,16 @@ require 'pager'
|
|
3
3
|
require 'retryable'
|
4
4
|
require 't/core_ext/enumerable'
|
5
5
|
require 't/rcfile'
|
6
|
+
require 't/requestable'
|
6
7
|
require 'thor'
|
7
|
-
require 'twitter'
|
8
8
|
|
9
9
|
module T
|
10
10
|
class CLI
|
11
11
|
class Search < Thor
|
12
12
|
include ActionView::Helpers::DateHelper
|
13
13
|
include Pager
|
14
|
+
include T::Requestable
|
14
15
|
|
15
|
-
DEFAULT_HOST = 'api.twitter.com'
|
16
|
-
DEFAULT_PROTOCOL = 'https'
|
17
16
|
DEFAULT_NUM_RESULTS = 20
|
18
17
|
MAX_PAGES = 16
|
19
18
|
MAX_NUM_RESULTS = 200
|
@@ -40,12 +39,12 @@ module T
|
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
43
|
-
desc "
|
44
|
-
def
|
42
|
+
desc "favorites QUERY", "Returns Tweets you've favorited that mach a specified query."
|
43
|
+
def favorites(query)
|
45
44
|
timeline = 1.upto(MAX_PAGES).threaded_map do |page|
|
46
45
|
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
47
|
-
client.
|
48
|
-
|
46
|
+
client.favorites(:page => page, :count => MAX_NUM_RESULTS).select do |status|
|
47
|
+
/#{query}/i.match(status.text)
|
49
48
|
end
|
50
49
|
end
|
51
50
|
end
|
@@ -54,15 +53,14 @@ module T
|
|
54
53
|
say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
55
54
|
end
|
56
55
|
end
|
57
|
-
map %w(
|
56
|
+
map %w(faves) => :favorites
|
58
57
|
|
59
|
-
desc "
|
60
|
-
def
|
61
|
-
screen_name = screen_name.strip_at
|
58
|
+
desc "mentions QUERY", "Returns Tweets mentioning you that mach a specified query."
|
59
|
+
def mentions(query)
|
62
60
|
timeline = 1.upto(MAX_PAGES).threaded_map do |page|
|
63
61
|
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
64
|
-
client.
|
65
|
-
|
62
|
+
client.mentions(:page => page, :count => MAX_NUM_RESULTS).select do |status|
|
63
|
+
/#{query}/i.match(status.text)
|
66
64
|
end
|
67
65
|
end
|
68
66
|
end
|
@@ -71,31 +69,54 @@ module T
|
|
71
69
|
say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
72
70
|
end
|
73
71
|
end
|
72
|
+
map %w(replies) => :mentions
|
74
73
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
:
|
87
|
-
|
88
|
-
:oauth_token => @rcfile.default_token,
|
89
|
-
:oauth_token_secret => @rcfile.default_secret
|
90
|
-
)
|
74
|
+
desc "retweets QUERY", "Returns Tweets you've retweeted that mach a specified query."
|
75
|
+
def retweets(query)
|
76
|
+
timeline = 1.upto(MAX_PAGES).threaded_map do |page|
|
77
|
+
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
78
|
+
client.retweeted_by(:page => page, :count => MAX_NUM_RESULTS).select do |status|
|
79
|
+
/#{query}/i.match(status.text)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
page unless T.env.test?
|
84
|
+
timeline.flatten.compact.each do |status|
|
85
|
+
say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
86
|
+
end
|
91
87
|
end
|
88
|
+
map %w(rts) => :retweets
|
92
89
|
|
93
|
-
|
94
|
-
|
90
|
+
desc "timeline QUERY", "Returns Tweets in your timeline that match a specified query."
|
91
|
+
def timeline(query)
|
92
|
+
timeline = 1.upto(MAX_PAGES).threaded_map do |page|
|
93
|
+
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
94
|
+
client.home_timeline(:page => page, :count => MAX_NUM_RESULTS).select do |status|
|
95
|
+
/#{query}/i.match(status.text)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
page unless T.env.test?
|
100
|
+
timeline.flatten.compact.each do |status|
|
101
|
+
say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
102
|
+
end
|
95
103
|
end
|
104
|
+
map %w(tl) => :timeline
|
96
105
|
|
97
|
-
|
98
|
-
|
106
|
+
desc "user SCREEN_NAME QUERY", "Returns Tweets in a user's timeline that match a specified query."
|
107
|
+
def user(screen_name, query)
|
108
|
+
screen_name = screen_name.strip_at
|
109
|
+
timeline = 1.upto(MAX_PAGES).threaded_map do |page|
|
110
|
+
retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
|
111
|
+
client.user_timeline(screen_name, :page => page, :count => MAX_NUM_RESULTS).select do |status|
|
112
|
+
/#{query}/i.match(status.text)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
page unless T.env.test?
|
117
|
+
timeline.flatten.compact.each do |status|
|
118
|
+
say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
119
|
+
end
|
99
120
|
end
|
100
121
|
|
101
122
|
end
|
data/lib/t/cli/set.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
require 't/core_ext/string'
|
2
2
|
require 't/rcfile'
|
3
|
+
require 't/requestable'
|
3
4
|
require 'thor'
|
4
|
-
require 'twitter'
|
5
5
|
|
6
6
|
module T
|
7
7
|
class CLI
|
8
8
|
class Set < Thor
|
9
|
-
|
10
|
-
DEFAULT_PROTOCOL = 'https'
|
9
|
+
include T::Requestable
|
11
10
|
|
12
11
|
check_unknown_options!
|
13
12
|
|
@@ -55,32 +54,6 @@ module T
|
|
55
54
|
say "@#{@rcfile.default_profile[0]}'s URL has been updated."
|
56
55
|
end
|
57
56
|
|
58
|
-
private
|
59
|
-
|
60
|
-
def base_url
|
61
|
-
"#{protocol}://#{host}"
|
62
|
-
end
|
63
|
-
|
64
|
-
def client
|
65
|
-
return @client if @client
|
66
|
-
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
67
|
-
@client = Twitter::Client.new(
|
68
|
-
:endpoint => base_url,
|
69
|
-
:consumer_key => @rcfile.default_consumer_key,
|
70
|
-
:consumer_secret => @rcfile.default_consumer_secret,
|
71
|
-
:oauth_token => @rcfile.default_token,
|
72
|
-
:oauth_token_secret => @rcfile.default_secret
|
73
|
-
)
|
74
|
-
end
|
75
|
-
|
76
|
-
def host
|
77
|
-
parent_options['host'] || DEFAULT_HOST
|
78
|
-
end
|
79
|
-
|
80
|
-
def protocol
|
81
|
-
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
82
|
-
end
|
83
|
-
|
84
57
|
end
|
85
58
|
end
|
86
59
|
end
|
data/lib/t/cli/unfollow.rb
CHANGED
@@ -3,16 +3,14 @@ require 't/core_ext/enumerable'
|
|
3
3
|
require 't/core_ext/string'
|
4
4
|
require 't/collectable'
|
5
5
|
require 't/rcfile'
|
6
|
+
require 't/requestable'
|
6
7
|
require 'thor'
|
7
|
-
require 'twitter'
|
8
8
|
|
9
9
|
module T
|
10
10
|
class CLI
|
11
11
|
class Unfollow < Thor
|
12
12
|
include T::Collectable
|
13
|
-
|
14
|
-
DEFAULT_HOST = 'api.twitter.com'
|
15
|
-
DEFAULT_PROTOCOL = 'https'
|
13
|
+
include T::Requestable
|
16
14
|
|
17
15
|
check_unknown_options!
|
18
16
|
|
@@ -36,7 +34,7 @@ module T
|
|
36
34
|
end
|
37
35
|
say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
|
38
36
|
say
|
39
|
-
say "Run `#{$0} follow all listed #{list_name}` to follow again."
|
37
|
+
say "Run `#{File.basename($0)} follow all listed #{list_name}` to follow again."
|
40
38
|
end
|
41
39
|
|
42
40
|
desc "followers", "Unfollow all followers."
|
@@ -58,7 +56,7 @@ module T
|
|
58
56
|
end
|
59
57
|
say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
|
60
58
|
say
|
61
|
-
say "Run `#{$0} follow all followers` to stop."
|
59
|
+
say "Run `#{File.basename($0)} follow all followers` to stop."
|
62
60
|
end
|
63
61
|
|
64
62
|
desc "friends", "Unfollow all friends."
|
@@ -77,7 +75,7 @@ module T
|
|
77
75
|
end
|
78
76
|
say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
|
79
77
|
say
|
80
|
-
say "Run `#{$0} follow users #{screen_names.join(' ')}` to follow again."
|
78
|
+
say "Run `#{File.basename($0)} follow users #{screen_names.join(' ')}` to follow again."
|
81
79
|
end
|
82
80
|
map %w(everyone everybody) => :friends
|
83
81
|
|
@@ -101,7 +99,7 @@ module T
|
|
101
99
|
end
|
102
100
|
say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
|
103
101
|
say
|
104
|
-
say "Run `#{$0} follow users #{screen_names.join(' ')}` to follow again."
|
102
|
+
say "Run `#{File.basename($0)} follow users #{screen_names.join(' ')}` to follow again."
|
105
103
|
end
|
106
104
|
|
107
105
|
desc "users SCREEN_NAME [SCREEN_NAME...]", "Allows you to stop following users."
|
@@ -115,33 +113,7 @@ module T
|
|
115
113
|
number = screen_names.length
|
116
114
|
say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
|
117
115
|
say
|
118
|
-
say "Run `#{$0} follow users #{screen_names.join(' ')}` to follow again."
|
119
|
-
end
|
120
|
-
|
121
|
-
private
|
122
|
-
|
123
|
-
def base_url
|
124
|
-
"#{protocol}://#{host}"
|
125
|
-
end
|
126
|
-
|
127
|
-
def client
|
128
|
-
return @client if @client
|
129
|
-
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
130
|
-
@client = Twitter::Client.new(
|
131
|
-
:endpoint => base_url,
|
132
|
-
:consumer_key => @rcfile.default_consumer_key,
|
133
|
-
:consumer_secret => @rcfile.default_consumer_secret,
|
134
|
-
:oauth_token => @rcfile.default_token,
|
135
|
-
:oauth_token_secret => @rcfile.default_secret
|
136
|
-
)
|
137
|
-
end
|
138
|
-
|
139
|
-
def host
|
140
|
-
parent_options['host'] || DEFAULT_HOST
|
141
|
-
end
|
142
|
-
|
143
|
-
def protocol
|
144
|
-
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
116
|
+
say "Run `#{File.basename($0)} follow users #{screen_names.join(' ')}` to follow again."
|
145
117
|
end
|
146
118
|
|
147
119
|
end
|
data/lib/t/rcfile.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
|
3
|
+
module T
|
4
|
+
module Requestable
|
5
|
+
DEFAULT_HOST = 'api.twitter.com'
|
6
|
+
DEFAULT_PROTOCOL = 'https'
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def base_url
|
13
|
+
"#{protocol}://#{host}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def client
|
17
|
+
return @client if @client
|
18
|
+
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
19
|
+
@client = Twitter::Client.new(
|
20
|
+
:endpoint => base_url,
|
21
|
+
:consumer_key => @rcfile.default_consumer_key,
|
22
|
+
:consumer_secret => @rcfile.default_consumer_secret,
|
23
|
+
:oauth_token => @rcfile.default_token,
|
24
|
+
:oauth_token_secret => @rcfile.default_secret
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def host
|
29
|
+
parent_options['host'] || DEFAULT_HOST
|
30
|
+
end
|
31
|
+
|
32
|
+
def protocol
|
33
|
+
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|