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.
@@ -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
- DEFAULT_HOST = 'api.twitter.com'
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.threaded_map do |list_member_id|
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.list_remove_member(list_name, list_member_id)
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.threaded_map do |list_member_id|
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.list_remove_member(list_name, list_member_id)
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.threaded_map do |list_member_id|
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.list_remove_member(to_list_name, list_member_id)
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).threaded_map do |list_member_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.list_remove_member(list_name, list_member_id)
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.threaded_map do |screen_name|
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.list_remove_member(list_name, screen_name)
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
@@ -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 "timeline QUERY", "Returns Tweets in your timeline that match a specified query."
44
- def timeline(query)
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.home_timeline(:page => page, :count => MAX_NUM_RESULTS).map do |status|
48
- status if /#{query}/i.match(status.text)
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(tl) => :timeline
56
+ map %w(faves) => :favorites
58
57
 
59
- desc "user SCREEN_NAME QUERY", "Returns Tweets in a user's timeline that match a specified query."
60
- def user(screen_name, query)
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.user_timeline(screen_name, :page => page, :count => MAX_NUM_RESULTS).map do |status|
65
- status if /#{query}/i.match(status.text)
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
- private
76
-
77
- def base_url
78
- "#{protocol}://#{host}"
79
- end
80
-
81
- def client
82
- return @client if @client
83
- @rcfile.path = parent_options['profile'] if parent_options['profile']
84
- @client = Twitter::Client.new(
85
- :endpoint => base_url,
86
- :consumer_key => @rcfile.default_consumer_key,
87
- :consumer_secret => @rcfile.default_consumer_secret,
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
- def host
94
- parent_options['host'] || DEFAULT_HOST
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
- def protocol
98
- parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
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
@@ -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
- DEFAULT_HOST = 'api.twitter.com'
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
@@ -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
@@ -1,5 +1,14 @@
1
1
  module Enumerable
2
2
 
3
+ def threaded_each
4
+ threads = []
5
+ result = each do |object|
6
+ threads << Thread.new{yield object}
7
+ end
8
+ threads.each(&:join)
9
+ result
10
+ end
11
+
3
12
  def threaded_map
4
13
  results = map{nil}
5
14
  threads = []
@@ -1,7 +1,3 @@
1
- # rcfile = RCFile.instance (should load current data from)
2
- # rcfile[profile] = {}
3
- # rcfile.write
4
-
5
1
  require 'singleton'
6
2
  require 'yaml'
7
3
 
@@ -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