t 3.1.0 → 4.1.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.
- checksums.yaml +5 -5
- data/LICENSE.md +1 -1
- data/README.md +8 -9
- data/bin/t +21 -21
- data/lib/t/cli.rb +470 -463
- data/lib/t/collectable.rb +9 -8
- data/lib/t/core_ext/kernel.rb +3 -3
- data/lib/t/core_ext/string.rb +2 -2
- data/lib/t/delete.rb +44 -41
- data/lib/t/editor.rb +5 -5
- data/lib/t/identicon.rb +1 -1
- data/lib/t/list.rb +51 -51
- data/lib/t/printable.rb +69 -65
- data/lib/t/rcfile.rb +18 -17
- data/lib/t/requestable.rb +3 -2
- data/lib/t/search.rb +82 -82
- data/lib/t/set.rb +21 -21
- data/lib/t/stream.rb +67 -60
- data/lib/t/utils.rb +25 -25
- data/lib/t/version.rb +2 -2
- data/lib/t.rb +2 -2
- data/t.gemspec +22 -21
- metadata +29 -49
data/lib/t/collectable.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require 'set'
|
1
|
+
require "twitter"
|
2
|
+
require "retryable"
|
4
3
|
|
5
4
|
module T
|
6
5
|
module Collectable
|
7
6
|
MAX_NUM_RESULTS = 200
|
8
7
|
MAX_PAGE = 51
|
9
8
|
|
10
|
-
def collect_with_max_id(collection = [], max_id = nil, &
|
9
|
+
def collect_with_max_id(collection = [], max_id = nil, &)
|
11
10
|
tweets = Retryable.retryable(tries: 3, on: Twitter::Error, sleep: 0) do
|
12
11
|
yield(max_id)
|
13
12
|
end
|
14
13
|
return collection if tweets.nil?
|
14
|
+
|
15
15
|
collection += tweets
|
16
|
-
tweets.empty? ? collection.flatten : collect_with_max_id(collection, tweets.last.id - 1, &
|
16
|
+
tweets.empty? ? collection.flatten : collect_with_max_id(collection, tweets.last.id - 1, &)
|
17
17
|
end
|
18
18
|
|
19
19
|
def collect_with_count(count)
|
@@ -22,7 +22,7 @@ module T
|
|
22
22
|
collect_with_max_id do |max_id|
|
23
23
|
opts[:max_id] = max_id unless max_id.nil?
|
24
24
|
opts[:count] = count unless count >= MAX_NUM_RESULTS
|
25
|
-
if count
|
25
|
+
if count.positive?
|
26
26
|
tweets = yield opts
|
27
27
|
count -= tweets.length
|
28
28
|
tweets
|
@@ -30,13 +30,14 @@ module T
|
|
30
30
|
end.flatten.compact
|
31
31
|
end
|
32
32
|
|
33
|
-
def collect_with_page(collection = ::Set.new, page = 1, previous = nil, &
|
33
|
+
def collect_with_page(collection = ::Set.new, page = 1, previous = nil, &)
|
34
34
|
tweets = Retryable.retryable(tries: 3, on: Twitter::Error, sleep: 0) do
|
35
35
|
yield page
|
36
36
|
end
|
37
37
|
return collection if tweets.nil? || tweets == previous || page >= MAX_PAGE
|
38
|
+
|
38
39
|
collection += tweets
|
39
|
-
tweets.empty? ? collection.flatten : collect_with_page(collection, page + 1, tweets, &
|
40
|
+
tweets.empty? ? collection.flatten : collect_with_page(collection, page + 1, tweets, &)
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
data/lib/t/core_ext/kernel.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Kernel
|
2
|
-
def Bignum(arg, base = 0) # rubocop:disable MethodName
|
2
|
+
def Bignum(arg, base = 0) # rubocop:disable Naming/MethodName
|
3
3
|
Integer(arg, base)
|
4
4
|
end
|
5
5
|
|
6
|
-
def Fixnum(arg, base = 0) # rubocop:disable MethodName
|
6
|
+
def Fixnum(arg, base = 0) # rubocop:disable Naming/MethodName
|
7
7
|
Integer(arg, base)
|
8
8
|
end
|
9
9
|
|
10
|
-
def NilClass(_) # rubocop:disable MethodName
|
10
|
+
def NilClass(_) # rubocop:disable Naming/MethodName
|
11
11
|
nil
|
12
12
|
end
|
13
13
|
end
|
data/lib/t/core_ext/string.rb
CHANGED
data/lib/t/delete.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require "thor"
|
2
|
+
require "twitter"
|
3
|
+
require "t/rcfile"
|
4
|
+
require "t/requestable"
|
5
|
+
require "t/utils"
|
6
6
|
|
7
7
|
module T
|
8
8
|
class Delete < Thor
|
@@ -16,9 +16,9 @@ module T
|
|
16
16
|
super
|
17
17
|
end
|
18
18
|
|
19
|
-
desc
|
20
|
-
method_option
|
21
|
-
method_option
|
19
|
+
desc "block USER [USER...]", "Unblock users."
|
20
|
+
method_option "id", aliases: "-i", type: :boolean, desc: "Specify input as Twitter user IDs instead of screen names."
|
21
|
+
method_option "force", aliases: "-f", type: :boolean
|
22
22
|
def block(user, *users)
|
23
23
|
unblocked_users, number = fetch_users(users.unshift(user), options) do |users_to_unblock|
|
24
24
|
client.unblock(users_to_unblock)
|
@@ -28,35 +28,37 @@ module T
|
|
28
28
|
say "Run `#{File.basename($PROGRAM_NAME)} block #{unblocked_users.collect { |unblocked_user| "@#{unblocked_user.screen_name}" }.join(' ')}` to block."
|
29
29
|
end
|
30
30
|
|
31
|
-
desc
|
32
|
-
method_option
|
31
|
+
desc "dm [DIRECT_MESSAGE_ID] [DIRECT_MESSAGE_ID...]", "Delete the last Direct Message sent."
|
32
|
+
method_option "force", aliases: "-f", type: :boolean
|
33
33
|
def dm(direct_message_id, *direct_message_ids)
|
34
34
|
direct_message_ids.unshift(direct_message_id)
|
35
|
-
require
|
35
|
+
require "t/core_ext/string"
|
36
36
|
direct_message_ids.collect!(&:to_i)
|
37
|
-
if options[
|
38
|
-
|
39
|
-
|
40
|
-
say "@#{@rcfile.active_profile[0]} deleted the direct message sent to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\""
|
41
|
-
end
|
37
|
+
if options["force"]
|
38
|
+
client.destroy_direct_message(*direct_message_ids)
|
39
|
+
say "@#{@rcfile.active_profile[0]} deleted #{direct_message_ids.size} direct message#{direct_message_ids.size == 1 ? '' : 's'}."
|
42
40
|
else
|
43
41
|
direct_message_ids.each do |direct_message_id_to_delete|
|
44
42
|
direct_message = client.direct_message(direct_message_id_to_delete)
|
45
|
-
next unless
|
43
|
+
next unless direct_message
|
44
|
+
|
45
|
+
recipient = client.user(direct_message.recipient_id)
|
46
|
+
next unless yes? "Are you sure you want to permanently delete the direct message to @#{recipient.screen_name}: \"#{direct_message.text}\"? [y/N]"
|
47
|
+
|
46
48
|
client.destroy_direct_message(direct_message_id_to_delete)
|
47
|
-
say "@#{@rcfile.active_profile[0]} deleted the direct message sent to @#{
|
49
|
+
say "@#{@rcfile.active_profile[0]} deleted the direct message sent to @#{recipient.screen_name}: \"#{direct_message.text}\""
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|
51
|
-
map %w
|
53
|
+
map %w[d m] => :dm
|
52
54
|
|
53
|
-
desc
|
54
|
-
method_option
|
55
|
+
desc "favorite TWEET_ID [TWEET_ID...]", "Delete favorites."
|
56
|
+
method_option "force", aliases: "-f", type: :boolean
|
55
57
|
def favorite(status_id, *status_ids)
|
56
58
|
status_ids.unshift(status_id)
|
57
|
-
require
|
59
|
+
require "t/core_ext/string"
|
58
60
|
status_ids.collect!(&:to_i)
|
59
|
-
if options[
|
61
|
+
if options["force"]
|
60
62
|
tweets = client.unfavorite(status_ids)
|
61
63
|
tweets.each do |status|
|
62
64
|
say "@#{@rcfile.active_profile[0]} unfavorited @#{status.user.screen_name}'s status: \"#{status.full_text}\""
|
@@ -65,32 +67,32 @@ module T
|
|
65
67
|
status_ids.each do |status_id_to_unfavorite|
|
66
68
|
status = client.status(status_id_to_unfavorite, include_my_retweet: false)
|
67
69
|
next unless yes? "Are you sure you want to remove @#{status.user.screen_name}'s status: \"#{status.full_text}\" from your favorites? [y/N]"
|
70
|
+
|
68
71
|
client.unfavorite(status_id_to_unfavorite)
|
69
72
|
say "@#{@rcfile.active_profile[0]} unfavorited @#{status.user.screen_name}'s status: \"#{status.full_text}\""
|
70
73
|
end
|
71
74
|
end
|
72
75
|
end
|
73
|
-
map %w
|
76
|
+
map %w[fave favourite] => :favorite
|
74
77
|
|
75
|
-
desc
|
76
|
-
method_option
|
77
|
-
method_option
|
78
|
+
desc "list LIST", "Delete a list."
|
79
|
+
method_option "force", aliases: "-f", type: :boolean
|
80
|
+
method_option "id", aliases: "-i", type: :boolean, desc: "Specify list via ID instead of slug."
|
78
81
|
def list(list)
|
79
|
-
if options[
|
80
|
-
require
|
82
|
+
if options["id"]
|
83
|
+
require "t/core_ext/string"
|
81
84
|
list = list.to_i
|
82
85
|
end
|
83
86
|
list = client.list(list)
|
84
|
-
|
85
|
-
|
86
|
-
end
|
87
|
+
return if !options["force"] && !(yes? "Are you sure you want to permanently delete the list \"#{list.name}\"? [y/N]")
|
88
|
+
|
87
89
|
client.destroy_list(list)
|
88
90
|
say "@#{@rcfile.active_profile[0]} deleted the list \"#{list.name}\"."
|
89
91
|
end
|
90
92
|
|
91
|
-
desc
|
92
|
-
method_option
|
93
|
-
method_option
|
93
|
+
desc "mute USER [USER...]", "Unmute users."
|
94
|
+
method_option "id", aliases: "-i", type: :boolean, desc: "Specify input as Twitter user IDs instead of screen names."
|
95
|
+
method_option "force", aliases: "-f", type: :boolean
|
94
96
|
def mute(user, *users)
|
95
97
|
unmuted_users, number = fetch_users(users.unshift(user), options) do |users_to_unmute|
|
96
98
|
client.unmute(users_to_unmute)
|
@@ -100,7 +102,7 @@ module T
|
|
100
102
|
say "Run `#{File.basename($PROGRAM_NAME)} mute #{unmuted_users.collect { |unmuted_user| "@#{unmuted_user.screen_name}" }.join(' ')}` to mute."
|
101
103
|
end
|
102
104
|
|
103
|
-
desc
|
105
|
+
desc "account SCREEN_NAME [CONSUMER_KEY]", "delete account or consumer key from t"
|
104
106
|
def account(account, key = nil)
|
105
107
|
if key && @rcfile.profiles[account].keys.size > 1
|
106
108
|
@rcfile.delete_key(account, key)
|
@@ -109,13 +111,13 @@ module T
|
|
109
111
|
end
|
110
112
|
end
|
111
113
|
|
112
|
-
desc
|
113
|
-
method_option
|
114
|
+
desc "status TWEET_ID [TWEET_ID...]", "Delete Tweets."
|
115
|
+
method_option "force", aliases: "-f", type: :boolean
|
114
116
|
def status(status_id, *status_ids)
|
115
117
|
status_ids.unshift(status_id)
|
116
|
-
require
|
118
|
+
require "t/core_ext/string"
|
117
119
|
status_ids.collect!(&:to_i)
|
118
|
-
if options[
|
120
|
+
if options["force"]
|
119
121
|
tweets = client.destroy_status(status_ids, trim_user: true)
|
120
122
|
tweets.each do |status|
|
121
123
|
say "@#{@rcfile.active_profile[0]} deleted the Tweet: \"#{status.full_text}\""
|
@@ -124,11 +126,12 @@ module T
|
|
124
126
|
status_ids.each do |status_id_to_delete|
|
125
127
|
status = client.status(status_id_to_delete, include_my_retweet: false)
|
126
128
|
next unless yes? "Are you sure you want to permanently delete @#{status.user.screen_name}'s status: \"#{status.full_text}\"? [y/N]"
|
129
|
+
|
127
130
|
client.destroy_status(status_id_to_delete, trim_user: true)
|
128
131
|
say "@#{@rcfile.active_profile[0]} deleted the Tweet: \"#{status.full_text}\""
|
129
132
|
end
|
130
133
|
end
|
131
134
|
end
|
132
|
-
map %w
|
135
|
+
map %w[post tweet update] => :status
|
133
136
|
end
|
134
137
|
end
|
data/lib/t/editor.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "tempfile"
|
2
|
+
require "shellwords"
|
3
3
|
|
4
4
|
module T
|
5
5
|
class Editor
|
@@ -14,7 +14,7 @@ module T
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def tempfile
|
17
|
-
Tempfile.new(
|
17
|
+
Tempfile.new("TWEET_EDITMSG")
|
18
18
|
end
|
19
19
|
|
20
20
|
def edit(path)
|
@@ -22,11 +22,11 @@ module T
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def editor
|
25
|
-
ENV[
|
25
|
+
ENV["VISUAL"] || ENV["EDITOR"] || system_editor
|
26
26
|
end
|
27
27
|
|
28
28
|
def system_editor
|
29
|
-
RbConfig::CONFIG[
|
29
|
+
/mswin|mingw/.match?(RbConfig::CONFIG["host_os"]) ? "notepad" : "vi"
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
data/lib/t/identicon.rb
CHANGED
data/lib/t/list.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
1
|
+
require "thor"
|
2
|
+
require "twitter"
|
3
|
+
require "t/collectable"
|
4
|
+
require "t/printable"
|
5
|
+
require "t/rcfile"
|
6
|
+
require "t/requestable"
|
7
|
+
require "t/utils"
|
8
8
|
|
9
9
|
module T
|
10
10
|
class List < Thor
|
@@ -22,8 +22,8 @@ module T
|
|
22
22
|
super
|
23
23
|
end
|
24
24
|
|
25
|
-
desc
|
26
|
-
method_option
|
25
|
+
desc "add LIST USER [USER...]", "Add members to a list."
|
26
|
+
method_option "id", aliases: "-i", type: :boolean, desc: "Specify input as Twitter user IDs instead of screen names."
|
27
27
|
def add(list_name, user, *users)
|
28
28
|
added_users, number = fetch_users(users.unshift(user), options) do |users_to_add|
|
29
29
|
client.add_list_members(list_name, users_to_add)
|
@@ -31,63 +31,63 @@ module T
|
|
31
31
|
end
|
32
32
|
say "@#{@rcfile.active_profile[0]} added #{pluralize(number, 'member')} to the list \"#{list_name}\"."
|
33
33
|
say
|
34
|
-
if options[
|
34
|
+
if options["id"]
|
35
35
|
say "Run `#{File.basename($PROGRAM_NAME)} list remove --id #{list_name} #{added_users.join(' ')}` to undo."
|
36
36
|
else
|
37
37
|
say "Run `#{File.basename($PROGRAM_NAME)} list remove #{list_name} #{added_users.collect { |added_user| "@#{added_user}" }.join(' ')}` to undo."
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
desc
|
42
|
-
method_option
|
41
|
+
desc "create LIST [DESCRIPTION]", "Create a new list."
|
42
|
+
method_option "private", aliases: "-p", type: :boolean
|
43
43
|
def create(list_name, description = nil)
|
44
|
-
opts = description ? {description:
|
45
|
-
opts[:mode] =
|
44
|
+
opts = description ? {description:} : {}
|
45
|
+
opts[:mode] = "private" if options["private"]
|
46
46
|
client.create_list(list_name, opts)
|
47
47
|
say "@#{@rcfile.active_profile[0]} created the list \"#{list_name}\"."
|
48
48
|
end
|
49
49
|
|
50
|
-
desc
|
51
|
-
method_option
|
50
|
+
desc "information [USER/]LIST", "Retrieves detailed information about a Twitter list."
|
51
|
+
method_option "csv", aliases: "-c", type: :boolean, desc: "Output in CSV format."
|
52
52
|
def information(user_list)
|
53
53
|
owner, list_name = extract_owner(user_list, options)
|
54
54
|
list = client.list(owner, list_name)
|
55
|
-
if options[
|
56
|
-
require
|
57
|
-
say [
|
55
|
+
if options["csv"]
|
56
|
+
require "csv"
|
57
|
+
say ["ID", "Description", "Slug", "Screen name", "Created at", "Members", "Subscribers", "Following", "Mode", "URL"].to_csv
|
58
58
|
say [list.id, list.description, list.slug, list.user.screen_name, csv_formatted_time(list), list.member_count, list.subscriber_count, list.following?, list.mode, list.uri].to_csv
|
59
59
|
else
|
60
60
|
array = []
|
61
|
-
array << [
|
62
|
-
array << [
|
63
|
-
array << [
|
64
|
-
array << [
|
65
|
-
array << [
|
66
|
-
array << [
|
67
|
-
array << [
|
68
|
-
array << [
|
69
|
-
array << [
|
70
|
-
array << [
|
61
|
+
array << ["ID", list.id.to_s]
|
62
|
+
array << ["Description", list.description] unless list.description.nil?
|
63
|
+
array << ["Slug", list.slug]
|
64
|
+
array << ["Screen name", "@#{list.user.screen_name}"]
|
65
|
+
array << ["Created at", "#{ls_formatted_time(list, :created_at, false)} (#{time_ago_in_words(list.created_at)} ago)"]
|
66
|
+
array << ["Members", number_with_delimiter(list.member_count)]
|
67
|
+
array << ["Subscribers", number_with_delimiter(list.subscriber_count)]
|
68
|
+
array << ["Status", list.following? ? "Following" : "Not following"]
|
69
|
+
array << ["Mode", list.mode]
|
70
|
+
array << ["URL", list.uri]
|
71
71
|
print_table(array)
|
72
72
|
end
|
73
73
|
end
|
74
|
-
map %w
|
74
|
+
map %w[details] => :information
|
75
75
|
|
76
|
-
desc
|
77
|
-
method_option
|
78
|
-
method_option
|
79
|
-
method_option
|
80
|
-
method_option
|
81
|
-
method_option
|
82
|
-
method_option
|
76
|
+
desc "members [USER/]LIST", "Returns the members of a Twitter list."
|
77
|
+
method_option "csv", aliases: "-c", type: :boolean, desc: "Output in CSV format."
|
78
|
+
method_option "id", aliases: "-i", type: :boolean, desc: "Specify user via ID instead of screen name."
|
79
|
+
method_option "long", aliases: "-l", type: :boolean, desc: "Output in long format."
|
80
|
+
method_option "reverse", aliases: "-r", type: :boolean, desc: "Reverse the order of the sort."
|
81
|
+
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"
|
82
|
+
method_option "unsorted", aliases: "-u", type: :boolean, desc: "Output is not sorted."
|
83
83
|
def members(user_list)
|
84
84
|
owner, list_name = extract_owner(user_list, options)
|
85
85
|
users = client.list_members(owner, list_name).to_a
|
86
86
|
print_users(users)
|
87
87
|
end
|
88
88
|
|
89
|
-
desc
|
90
|
-
method_option
|
89
|
+
desc "remove LIST USER [USER...]", "Remove members from a list."
|
90
|
+
method_option "id", aliases: "-i", type: :boolean, desc: "Specify input as Twitter user IDs instead of screen names."
|
91
91
|
def remove(list_name, user, *users)
|
92
92
|
removed_users, number = fetch_users(users.unshift(user), options) do |users_to_remove|
|
93
93
|
client.remove_list_members(list_name, users_to_remove)
|
@@ -95,31 +95,31 @@ module T
|
|
95
95
|
end
|
96
96
|
say "@#{@rcfile.active_profile[0]} removed #{pluralize(number, 'member')} from the list \"#{list_name}\"."
|
97
97
|
say
|
98
|
-
if options[
|
98
|
+
if options["id"]
|
99
99
|
say "Run `#{File.basename($PROGRAM_NAME)} list add --id #{list_name} #{removed_users.join(' ')}` to undo."
|
100
100
|
else
|
101
101
|
say "Run `#{File.basename($PROGRAM_NAME)} list add #{list_name} #{removed_users.collect { |removed_user| "@#{removed_user}" }.join(' ')}` to undo."
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
-
desc
|
106
|
-
method_option
|
107
|
-
method_option
|
108
|
-
method_option
|
109
|
-
method_option
|
110
|
-
method_option
|
111
|
-
method_option
|
112
|
-
method_option
|
105
|
+
desc "timeline [USER/]LIST", "Show tweet timeline for members of the specified list."
|
106
|
+
method_option "csv", aliases: "-c", type: :boolean, desc: "Output in CSV format."
|
107
|
+
method_option "decode_uris", aliases: "-d", type: :boolean, desc: "Decodes t.co URLs into their original form."
|
108
|
+
method_option "id", aliases: "-i", type: :boolean, desc: "Specify user via ID instead of screen name."
|
109
|
+
method_option "long", aliases: "-l", type: :boolean, desc: "Output in long format."
|
110
|
+
method_option "number", aliases: "-n", type: :numeric, default: DEFAULT_NUM_RESULTS, desc: "Limit the number of results."
|
111
|
+
method_option "relative_dates", aliases: "-a", type: :boolean, desc: "Show relative dates."
|
112
|
+
method_option "reverse", aliases: "-r", type: :boolean, desc: "Reverse the order of the sort."
|
113
113
|
def timeline(user_list)
|
114
114
|
owner, list_name = extract_owner(user_list, options)
|
115
|
-
count = options[
|
115
|
+
count = options["number"] || DEFAULT_NUM_RESULTS
|
116
116
|
opts = {}
|
117
|
-
opts[:include_entities] = !!options[
|
117
|
+
opts[:include_entities] = !!options["decode_uris"]
|
118
118
|
tweets = collect_with_count(count) do |count_opts|
|
119
119
|
client.list_timeline(owner, list_name, count_opts.merge(opts))
|
120
120
|
end
|
121
121
|
print_tweets(tweets)
|
122
122
|
end
|
123
|
-
map %w
|
123
|
+
map %w[tl] => :timeline
|
124
124
|
end
|
125
125
|
end
|