t 0.9.9 → 1.0.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/lib/t/collectable.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'twitter'
2
+ require 'retryable'
2
3
 
3
4
  module T
4
5
  module Collectable
@@ -10,7 +11,6 @@ module T
10
11
  end
11
12
 
12
13
  def collect_with_cursor(collection=[], cursor=-1, &block)
13
- require 'retryable'
14
14
  object = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
15
15
  yield cursor
16
16
  end
@@ -19,7 +19,6 @@ module T
19
19
  end
20
20
 
21
21
  def collect_with_max_id(collection=[], max_id=nil, &block)
22
- require 'retryable'
23
22
  array = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
24
23
  yield max_id
25
24
  end
@@ -31,7 +30,7 @@ module T
31
30
  def collect_with_number(number, key, &block)
32
31
  opts = {}
33
32
  opts[key] = MAX_NUM_RESULTS
34
- statuses = collect_with_max_id do |max_id|
33
+ collect_with_max_id do |max_id|
35
34
  opts[:max_id] = max_id unless max_id.nil?
36
35
  opts[key] = number unless number >= MAX_NUM_RESULTS
37
36
  if number > 0
@@ -50,5 +49,14 @@ module T
50
49
  collect_with_number(rpp, :rpp, &block)
51
50
  end
52
51
 
52
+ def collect_with_page(collection=[], page=1, &block)
53
+ array = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
54
+ yield page
55
+ end
56
+ return collection if array.nil?
57
+ collection += array
58
+ array.empty? ? collection.flatten.uniq : collect_with_page(collection, page + 1, &block)
59
+ end
60
+
53
61
  end
54
62
  end
@@ -8,7 +8,7 @@ class String
8
8
  self.tr('@', '')
9
9
  end
10
10
 
11
- alias_method :old_to_i, :to_i
11
+ alias old_to_i to_i
12
12
 
13
13
  def to_i(base=10)
14
14
  self.tr(',', '').old_to_i(base)
data/lib/t/delete.rb CHANGED
@@ -1,39 +1,29 @@
1
1
  require 'thor'
2
2
  require 'twitter'
3
+ require 't/rcfile'
4
+ require 't/requestable'
5
+ require 't/utils'
3
6
 
4
7
  module T
5
- autoload :RCFile, 't/rcfile'
6
- autoload :Requestable, 't/requestable'
7
8
  class Delete < Thor
8
9
  include T::Requestable
10
+ include T::Utils
9
11
 
10
12
  check_unknown_options!
11
13
 
12
14
  def initialize(*)
15
+ @rcfile = T::RCFile.instance
13
16
  super
14
- @rcfile = RCFile.instance
15
17
  end
16
18
 
17
19
  desc "block USER [USER...]", "Unblock users."
18
20
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
19
21
  method_option "force", :aliases => "-f", :type => :boolean, :default => false
20
22
  def block(user, *users)
21
- users.unshift(user)
22
- require 't/core_ext/string'
23
- if options['id']
24
- users.map!(&:to_i)
25
- else
26
- users.map!(&:strip_ats)
23
+ users, number = fetch_users(users.unshift(user), options) do |users|
24
+ client.unblock(users)
27
25
  end
28
- require 't/core_ext/enumerable'
29
- require 'retryable'
30
- users = users.threaded_map do |user|
31
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
32
- client.unblock(user)
33
- end
34
- end
35
- number = users.length
36
- say "@#{@rcfile.active_profile[0]} unblocked #{number} #{number == 1 ? 'user' : 'users'}."
26
+ say "@#{@rcfile.active_profile[0]} unblocked #{pluralize(number, 'user')}."
37
27
  say
38
28
  say "Run `#{File.basename($0)} block #{users.map{|user| "@#{user.screen_name}"}.join(' ')}` to block."
39
29
  end
@@ -44,14 +34,20 @@ module T
44
34
  direct_message_ids.unshift(direct_message_id)
45
35
  require 't/core_ext/string'
46
36
  direct_message_ids.map!(&:to_i)
47
- direct_message_ids.each do |direct_message_id|
48
- unless options['force']
37
+ if options['force']
38
+ direct_messages = client.direct_message_destroy(direct_message_ids)
39
+ direct_messages.each do |direct_message|
40
+ say "@#{@rcfile.active_profile[0]} deleted the direct message sent to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\""
41
+ end
42
+ else
43
+ direct_message_ids.each do |direct_message_id|
49
44
  direct_message = client.direct_message(direct_message_id)
50
45
  return unless yes? "Are you sure you want to permanently delete the direct message to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\"? [y/N]"
46
+ client.direct_message_destroy(direct_message_id)
47
+ say "@#{@rcfile.active_profile[0]} deleted the direct message sent to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\""
51
48
  end
52
- direct_message = client.direct_message_destroy(direct_message_id)
53
- say "@#{@rcfile.active_profile[0]} deleted the direct message sent to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\""
54
49
  end
50
+
55
51
  end
56
52
  map %w(d m) => :dm
57
53
 
@@ -61,13 +57,18 @@ module T
61
57
  status_ids.unshift(status_id)
62
58
  require 't/core_ext/string'
63
59
  status_ids.map!(&:to_i)
64
- status_ids.each do |status_id|
65
- unless options['force']
60
+ if options['force']
61
+ statuses = client.unfavorite(status_ids)
62
+ statuses.each do |status|
63
+ say "@#{@rcfile.active_profile[0]} unfavorited @#{status.from_user}'s status: \"#{status.full_text}\""
64
+ end
65
+ else
66
+ status_ids.each do |status_id|
66
67
  status = client.status(status_id, :include_my_retweet => false, :trim_user => true)
67
68
  return unless yes? "Are you sure you want to remove @#{status.from_user}'s status: \"#{status.full_text}\" from your favorites? [y/N]"
69
+ client.unfavorite(status_id)
70
+ say "@#{@rcfile.active_profile[0]} unfavorited @#{status.from_user}'s status: \"#{status.full_text}\""
68
71
  end
69
- status = client.unfavorite(status_id)
70
- say "@#{@rcfile.active_profile[0]} unfavorited @#{status.from_user}'s status: \"#{status.full_text}\""
71
72
  end
72
73
  end
73
74
  map %w(fave favourite) => :favorite
@@ -94,13 +95,18 @@ module T
94
95
  status_ids.unshift(status_id)
95
96
  require 't/core_ext/string'
96
97
  status_ids.map!(&:to_i)
97
- status_ids.each do |status_id|
98
- unless options['force']
98
+ if options['force']
99
+ statuses = client.status_destroy(status_ids, :trim_user => true)
100
+ statuses.each do |status|
101
+ say "@#{@rcfile.active_profile[0]} deleted the status: \"#{status.full_text}\""
102
+ end
103
+ else
104
+ status_ids.each do |status_id|
99
105
  status = client.status(status_id, :include_my_retweet => false, :trim_user => true)
100
106
  return unless yes? "Are you sure you want to permanently delete @#{status.from_user}'s status: \"#{status.full_text}\"? [y/N]"
107
+ client.status_destroy(status_id, :trim_user => true)
108
+ say "@#{@rcfile.active_profile[0]} deleted the status: \"#{status.full_text}\""
101
109
  end
102
- status = client.status_destroy(status_id, :trim_user => true)
103
- say "@#{@rcfile.active_profile[0]} deleted the status: \"#{status.full_text}\""
104
110
  end
105
111
  end
106
112
  map %w(post tweet update) => :status
data/lib/t/list.rb CHANGED
@@ -1,50 +1,37 @@
1
1
  require 'thor'
2
2
  require 'twitter'
3
+ require 't/collectable'
4
+ require 't/printable'
5
+ require 't/rcfile'
6
+ require 't/requestable'
7
+ require 't/utils'
3
8
 
4
9
  module T
5
- autoload :Collectable, 't/collectable'
6
- autoload :FormatHelpers, 't/format_helpers'
7
- autoload :Printable, 't/printable'
8
- autoload :RCFile, 't/rcfile'
9
- autoload :Requestable, 't/requestable'
10
10
  class List < Thor
11
11
  include T::Collectable
12
12
  include T::Printable
13
13
  include T::Requestable
14
- include T::FormatHelpers
14
+ include T::Utils
15
15
 
16
16
  DEFAULT_NUM_RESULTS = 20
17
- MAX_SCREEN_NAME_SIZE = 20
18
17
  MAX_USERS_PER_LIST = 500
19
18
  MAX_USERS_PER_REQUEST = 100
20
19
 
21
20
  check_unknown_options!
22
21
 
23
22
  def initialize(*)
23
+ @rcfile = T::RCFile.instance
24
24
  super
25
- @rcfile = RCFile.instance
26
25
  end
27
26
 
28
27
  desc "add LIST USER [USER...]", "Add members to a list."
29
28
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
30
29
  def add(list, user, *users)
31
- users.unshift(user)
32
- require 't/core_ext/string'
33
- if options['id']
34
- users.map!(&:to_i)
35
- else
36
- users.map!(&:strip_ats)
37
- end
38
- require 'active_support/core_ext/array/grouping'
39
- require 't/core_ext/enumerable'
40
- require 'retryable'
41
- users.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
42
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
43
- client.list_add_members(list, user_id_group)
44
- end
30
+ users, number = fetch_users(users.unshift(user), options) do |users|
31
+ client.list_add_members(list, users)
32
+ users
45
33
  end
46
- number = users.length
47
- say "@#{@rcfile.active_profile[0]} added #{number} #{number == 1 ? 'member' : 'members'} to the list \"#{list}\"."
34
+ say "@#{@rcfile.active_profile[0]} added #{pluralize(number, 'member')} to the list \"#{list}\"."
48
35
  say
49
36
  if options['id']
50
37
  say "Run `#{File.basename($0)} list remove --id #{list} #{users.join(' ')}` to undo."
@@ -65,18 +52,7 @@ module T
65
52
  desc "information [USER/]LIST", "Retrieves detailed information about a Twitter list."
66
53
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
67
54
  def information(list)
68
- owner, list = list.split('/')
69
- if list.nil?
70
- list = owner
71
- owner = @rcfile.active_profile[0]
72
- else
73
- require 't/core_ext/string'
74
- owner = if options['id']
75
- owner.to_i
76
- else
77
- owner.strip_ats
78
- end
79
- end
55
+ owner, list = extract_owner(list, options)
80
56
  list = client.list(owner, list)
81
57
  if options['csv']
82
58
  require 'csv'
@@ -102,29 +78,13 @@ module T
102
78
 
103
79
  desc "members [USER/]LIST", "Returns the members of a Twitter list."
104
80
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
105
- method_option "followers", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by total number of favorites."
106
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by total number of followers."
107
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by total number of friends."
108
81
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
109
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
110
82
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
111
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
112
83
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
113
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by total number of Tweets."
84
+ 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"
114
85
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
115
86
  def members(list)
116
- owner, list = list.split('/')
117
- if list.nil?
118
- list = owner
119
- owner = @rcfile.active_profile[0]
120
- else
121
- require 't/core_ext/string'
122
- owner = if options['id']
123
- owner.to_i
124
- else
125
- owner.strip_ats
126
- end
127
- end
87
+ owner, list = extract_owner(list, options)
128
88
  users = collect_with_cursor do |cursor|
129
89
  client.list_members(owner, list, :cursor => cursor, :skip_status => true)
130
90
  end
@@ -134,23 +94,11 @@ module T
134
94
  desc "remove LIST USER [USER...]", "Remove members from a list."
135
95
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
136
96
  def remove(list, user, *users)
137
- users.unshift(user)
138
- require 't/core_ext/string'
139
- if options['id']
140
- users.map!(&:to_i)
141
- else
142
- users.map!(&:strip_ats)
97
+ users, number = fetch_users(users.unshift(user), options) do |users|
98
+ client.list_remove_members(list, users)
99
+ users
143
100
  end
144
- require 'active_support/core_ext/array/grouping'
145
- require 't/core_ext/enumerable'
146
- require 'retryable'
147
- users.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
148
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
149
- client.list_remove_members(list, user_id_group)
150
- end
151
- end
152
- number = users.length
153
- say "@#{@rcfile.active_profile[0]} removed #{number} #{number == 1 ? 'member' : 'members'} from the list \"#{list}\"."
101
+ say "@#{@rcfile.active_profile[0]} removed #{pluralize(number, 'member')} from the list \"#{list}\"."
154
102
  say
155
103
  if options['id']
156
104
  say "Run `#{File.basename($0)} list add --id #{list} #{users.join(' ')}` to undo."
@@ -166,18 +114,7 @@ module T
166
114
  method_option "number", :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
167
115
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
168
116
  def timeline(list)
169
- owner, list = list.split('/')
170
- if list.nil?
171
- list = owner
172
- owner = @rcfile.active_profile[0]
173
- else
174
- require 't/core_ext/string'
175
- owner = if options['id']
176
- owner.to_i
177
- else
178
- owner.strip_ats
179
- end
180
- end
117
+ owner, list = extract_owner(list, options)
181
118
  per_page = options['number'] || DEFAULT_NUM_RESULTS
182
119
  statuses = collect_with_per_page(per_page) do |opts|
183
120
  client.list_timeline(owner, list, opts)
data/lib/t/printable.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  module T
2
2
  module Printable
3
- MAX_SCREEN_NAME_SIZE = 20
4
3
  LIST_HEADINGS = ["ID", "Created at", "Screen name", "Slug", "Members", "Subscribers", "Mode", "Description"]
5
4
  STATUS_HEADINGS = ["ID", "Posted at", "Screen name", "Text"]
6
- USER_HEADINGS = ["ID", "Since", "Tweets", "Favorites", "Listed", "Following", "Followers", "Screen name", "Name"]
5
+ USER_HEADINGS = ["ID", "Since", "Last tweeted at", "Tweets", "Favorites", "Listed", "Following", "Followers", "Screen name", "Name"]
6
+ MONTH_IN_SECONDS = 2592000
7
7
 
8
8
  private
9
9
 
@@ -17,20 +17,19 @@ module T
17
17
  end
18
18
 
19
19
  def build_long_user(user)
20
- [user.id, ls_formatted_time(user), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, "@#{user.screen_name}", user.name]
20
+ [user.id, ls_formatted_time(user), ls_formatted_time(user.status), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, "@#{user.screen_name}", user.name]
21
21
  end
22
22
 
23
23
  def csv_formatted_time(object, key=:created_at)
24
+ return nil if object.nil?
24
25
  time = object.send(key.to_sym)
25
26
  time.utc.strftime("%Y-%m-%d %H:%M:%S %z")
26
27
  end
27
28
 
28
29
  def ls_formatted_time(object, key=:created_at)
30
+ return "" if object.nil?
29
31
  time = T.local_time(object.send(key.to_sym))
30
- require 'active_support/core_ext/date/calculations'
31
- require 'active_support/core_ext/integer/time'
32
- require 'active_support/core_ext/numeric/time'
33
- if time > 6.months.ago
32
+ if time > Time.now - MONTH_IN_SECONDS * 6
34
33
  time.strftime("%b %e %H:%M")
35
34
  else
36
35
  time.strftime("%b %e %Y")
@@ -53,20 +52,22 @@ module T
53
52
  def print_csv_user(user)
54
53
  require 'csv'
55
54
  require 'fastercsv' unless Array.new.respond_to?(:to_csv)
56
- say [user.id, csv_formatted_time(user), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, user.screen_name, user.name].to_csv
55
+ say [user.id, csv_formatted_time(user), csv_formatted_time(user.status), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, user.screen_name, user.name].to_csv
57
56
  end
58
57
 
59
58
  def print_lists(lists)
60
- lists = lists.sort_by{|list| list.slug.downcase} unless options['unsorted']
61
- if options['posted']
62
- lists = lists.sort_by{|user| user.created_at}
63
- elsif options['members']
64
- lists = lists.sort_by{|user| user.member_count}
65
- elsif options['mode']
66
- lists = lists.sort_by{|user| user.mode}
67
- elsif options['subscribers']
68
- lists = lists.sort_by{|user| user.subscriber_count}
69
- end
59
+ lists = case options['sort']
60
+ when 'members'
61
+ lists.sort_by{|user| user.member_count}
62
+ when 'mode'
63
+ lists.sort_by{|user| user.mode}
64
+ when 'posted'
65
+ lists.sort_by{|user| user.created_at}
66
+ when 'subscribers'
67
+ lists.sort_by{|user| user.subscriber_count}
68
+ else
69
+ lists.sort_by{|list| list.slug.downcase}
70
+ end unless options['unsorted']
70
71
  lists.reverse! if options['reverse']
71
72
  if options['csv']
72
73
  require 'csv'
@@ -112,19 +113,19 @@ module T
112
113
  end
113
114
  end
114
115
 
115
- def print_status(status)
116
+ def print_message(from_user, message)
116
117
  if STDOUT.tty? && !options['no-color']
117
- say(" @#{status.from_user}", [:bold, :yellow])
118
+ say(" @#{from_user}", [:bold, :yellow])
118
119
  else
119
- say(" @#{status.from_user}")
120
+ say(" @#{from_user}")
120
121
  end
121
122
  require 'htmlentities'
122
- print_wrapped(HTMLEntities.new.decode(status.full_text), :indent => 3)
123
+ print_wrapped(HTMLEntities.new.decode(message), :indent => 3)
123
124
  say
124
125
  end
125
126
 
126
127
  def print_statuses(statuses)
127
- statuses.reverse! if options['reverse'] || options['stream']
128
+ statuses.reverse! if options['reverse']
128
129
  if options['csv']
129
130
  require 'csv'
130
131
  require 'fastercsv' unless Array.new.respond_to?(:to_csv)
@@ -140,26 +141,30 @@ module T
140
141
  print_table_with_headings(array, STATUS_HEADINGS, format)
141
142
  else
142
143
  statuses.each do |status|
143
- print_status(status)
144
+ print_message(status.user.screen_name, status.full_text)
144
145
  end
145
146
  end
146
147
  end
147
148
 
148
149
  def print_users(users)
149
- users = users.sort_by{|user| user.screen_name.downcase} unless options['unsorted']
150
- if options['posted']
151
- users = users.sort_by{|user| user.created_at}
152
- elsif options['favorites']
153
- users = users.sort_by{|user| user.favourites_count}
154
- elsif options['followers']
155
- users = users.sort_by{|user| user.followers_count}
156
- elsif options['friends']
157
- users = users.sort_by{|user| user.friends_count}
158
- elsif options['listed']
159
- users = users.sort_by{|user| user.listed_count}
160
- elsif options['tweets']
161
- users = users.sort_by{|user| user.statuses_count}
162
- end
150
+ users = case options['sort']
151
+ when 'favorites'
152
+ users.sort_by{|user| user.favourites_count}
153
+ when 'followers'
154
+ users.sort_by{|user| user.followers_count}
155
+ when 'friends'
156
+ users.sort_by{|user| user.friends_count}
157
+ when 'listed'
158
+ users.sort_by{|user| user.listed_count}
159
+ when 'since'
160
+ users.sort_by{|user| user.created_at}
161
+ when 'tweets'
162
+ users.sort_by{|user| user.statuses_count}
163
+ when 'tweeted'
164
+ users.sort_by{|user| user.status.created_at rescue Time.at(0)}
165
+ else
166
+ users.sort_by{|user| user.screen_name.downcase}
167
+ end unless options['unsorted']
163
168
  users.reverse! if options['reverse']
164
169
  if options['csv']
165
170
  require 'csv'