t 0.1.0 → 0.2.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/Gemfile +1 -1
- data/README.md +81 -48
- data/lib/t/cli.rb +181 -153
- data/lib/t/cli/delete.rb +112 -0
- data/lib/t/cli/follow.rb +66 -0
- data/lib/t/cli/follow/all.rb +99 -0
- data/lib/t/cli/list.rb +108 -0
- data/lib/t/cli/list/add.rb +64 -0
- data/lib/t/cli/list/add/all.rb +169 -0
- data/lib/t/cli/list/remove.rb +67 -0
- data/lib/t/cli/list/remove/all.rb +162 -0
- data/lib/t/cli/set.rb +86 -0
- data/lib/t/cli/unfollow.rb +66 -0
- data/lib/t/cli/unfollow/all.rb +122 -0
- data/lib/t/version.rb +1 -1
- data/spec/cli/delete_spec.rb +332 -0
- data/spec/cli/follow/all_spec.rb +159 -0
- data/spec/cli/follow_spec.rb +74 -0
- data/spec/cli/list/add/all_spec.rb +435 -0
- data/spec/cli/list/add_spec.rb +65 -0
- data/spec/cli/list/remove/all_spec.rb +315 -0
- data/spec/cli/list/remove_spec.rb +42 -0
- data/spec/cli/list_spec.rb +80 -0
- data/spec/{set_spec.rb → cli/set_spec.rb} +16 -16
- data/spec/cli/unfollow/all_spec.rb +223 -0
- data/spec/cli/unfollow_spec.rb +74 -0
- data/spec/cli_spec.rb +115 -70
- data/spec/fixtures/501_ids.json +1 -0
- data/spec/fixtures/501_users_list.json +1 -0
- data/spec/fixtures/empty_cursor.json +1 -0
- data/spec/fixtures/followers_ids.json +1 -0
- data/spec/fixtures/friends_ids.json +1 -0
- data/spec/fixtures/gem.json +1 -0
- data/spec/fixtures/list.json +1 -0
- data/spec/fixtures/search.json +1 -0
- data/spec/fixtures/users_list.json +1 -0
- data/t.gemspec +1 -0
- metadata +93 -34
- data/lib/t/delete.rb +0 -101
- data/lib/t/set.rb +0 -83
- data/spec/delete_spec.rb +0 -251
data/lib/t/cli/delete.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 't/core_ext/string'
|
2
|
+
require 't/rcfile'
|
3
|
+
require 'thor'
|
4
|
+
require 'twitter'
|
5
|
+
|
6
|
+
module T
|
7
|
+
class CLI
|
8
|
+
class Delete < Thor
|
9
|
+
DEFAULT_HOST = 'api.twitter.com'
|
10
|
+
DEFAULT_PROTOCOL = 'https'
|
11
|
+
|
12
|
+
check_unknown_options!
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
@rcfile = RCFile.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "block SCREEN_NAME", "Unblock a user."
|
20
|
+
def block(screen_name)
|
21
|
+
screen_name = screen_name.strip_at
|
22
|
+
user = client.unblock(screen_name, :include_entities => false)
|
23
|
+
say "@#{@rcfile.default_profile[0]} unblocked @#{user.screen_name}."
|
24
|
+
say
|
25
|
+
say "Run `#{$0} block #{user.screen_name}` to block."
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "dm", "Delete the last Direct Message sent."
|
29
|
+
def dm
|
30
|
+
direct_message = client.direct_messages_sent(:count => 1, :include_entities => false).first
|
31
|
+
if direct_message
|
32
|
+
unless parent_options['force']
|
33
|
+
return unless yes? "Are you sure you want to permanently delete the direct message to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\"?"
|
34
|
+
end
|
35
|
+
direct_message = client.direct_message_destroy(direct_message.id, :include_entities => false)
|
36
|
+
say "@#{direct_message.sender.screen_name} deleted the direct message sent to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\""
|
37
|
+
else
|
38
|
+
raise Thor::Error, "Direct Message not found"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
map %w(m) => :dm
|
42
|
+
|
43
|
+
desc "favorite", "Deletes the last favorite."
|
44
|
+
def favorite
|
45
|
+
status = client.favorites(:count => 1, :include_entities => false).first
|
46
|
+
if status
|
47
|
+
unless parent_options['force']
|
48
|
+
return unless yes? "Are you sure you want to delete the favorite of @#{status.user.screen_name}'s latest status: \"#{status.text}\"?"
|
49
|
+
end
|
50
|
+
client.unfavorite(status.id, :include_entities => false)
|
51
|
+
say "@#{@rcfile.default_profile[0]} unfavorited @#{status.user.screen_name}'s latest status: \"#{status.text}\""
|
52
|
+
say
|
53
|
+
say "Run `#{$0} favorite #{status.user.screen_name}` to favorite."
|
54
|
+
else
|
55
|
+
raise Thor::Error, "Tweet not found"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
map %w(fave) => :favorite
|
59
|
+
|
60
|
+
desc "list LIST_NAME", "Delete a list."
|
61
|
+
def list(list_name)
|
62
|
+
unless parent_options['force']
|
63
|
+
return unless yes? "Are you sure you want to permanently delete the list \"#{list_name}\"?"
|
64
|
+
end
|
65
|
+
status = client.list_destroy(list_name)
|
66
|
+
say "@#{@rcfile.default_profile[0]} deleted the list \"#{list_name}\"."
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "status", "Delete a Tweet."
|
70
|
+
def status
|
71
|
+
user = client.user(:include_entities => false)
|
72
|
+
if user.status
|
73
|
+
unless parent_options['force']
|
74
|
+
return unless yes? "Are you sure you want to permanently delete @#{@rcfile.default_profile[0]}'s latest status: \"#{user.status.text}\"?"
|
75
|
+
end
|
76
|
+
status = client.status_destroy(user.status.id, :include_entities => false, :trim_user => true)
|
77
|
+
say "@#{@rcfile.default_profile[0]} deleted the status: \"#{status.text}\""
|
78
|
+
else
|
79
|
+
raise Thor::Error, "Tweet not found"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
map %w(post tweet update) => :status
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def base_url
|
87
|
+
"#{protocol}://#{host}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def client
|
91
|
+
return @client if @client
|
92
|
+
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
93
|
+
@client = Twitter::Client.new(
|
94
|
+
:endpoint => base_url,
|
95
|
+
:consumer_key => @rcfile.default_consumer_key,
|
96
|
+
:consumer_secret => @rcfile.default_consumer_secret,
|
97
|
+
:oauth_token => @rcfile.default_token,
|
98
|
+
:oauth_token_secret => @rcfile.default_secret
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
def host
|
103
|
+
parent_options['host'] || DEFAULT_HOST
|
104
|
+
end
|
105
|
+
|
106
|
+
def protocol
|
107
|
+
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/t/cli/follow.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 't/core_ext/string'
|
2
|
+
require 't/rcfile'
|
3
|
+
require 'thor'
|
4
|
+
require 'twitter'
|
5
|
+
|
6
|
+
module T
|
7
|
+
class CLI
|
8
|
+
class Follow < Thor
|
9
|
+
DEFAULT_HOST = 'api.twitter.com'
|
10
|
+
DEFAULT_PROTOCOL = 'https'
|
11
|
+
|
12
|
+
check_unknown_options!
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
@rcfile = RCFile.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "users SCREEN_NAME [SCREEN_NAME...]", "Allows you to start following users."
|
20
|
+
def users(screen_name, *screen_names)
|
21
|
+
screen_names.unshift(screen_name)
|
22
|
+
users = screen_names.map do |screen_name|
|
23
|
+
screen_name = screen_name.strip_at
|
24
|
+
user = client.follow(screen_name, :include_entities => false)
|
25
|
+
say "@#{@rcfile.default_profile[0]} is now following @#{user.screen_name}."
|
26
|
+
user
|
27
|
+
end
|
28
|
+
number = users.length
|
29
|
+
say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
|
30
|
+
say
|
31
|
+
say "Run `#{$0} unfollow users #{screen_names.join(' ')}` to stop."
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "all SUBCOMMAND ...ARGS", "Follow all users."
|
35
|
+
require 't/cli/follow/all'
|
36
|
+
subcommand 'all', CLI::Follow::All
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def base_url
|
41
|
+
"#{protocol}://#{host}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def client
|
45
|
+
return @client if @client
|
46
|
+
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
47
|
+
@client = Twitter::Client.new(
|
48
|
+
:endpoint => base_url,
|
49
|
+
:consumer_key => @rcfile.default_consumer_key,
|
50
|
+
:consumer_secret => @rcfile.default_consumer_secret,
|
51
|
+
:oauth_token => @rcfile.default_token,
|
52
|
+
:oauth_token_secret => @rcfile.default_secret
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def host
|
57
|
+
parent_options['host'] || DEFAULT_HOST
|
58
|
+
end
|
59
|
+
|
60
|
+
def protocol
|
61
|
+
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 't/rcfile'
|
2
|
+
require 'thor'
|
3
|
+
require 'twitter'
|
4
|
+
|
5
|
+
module T
|
6
|
+
class CLI
|
7
|
+
class Follow
|
8
|
+
class All < Thor
|
9
|
+
DEFAULT_HOST = 'api.twitter.com'
|
10
|
+
DEFAULT_PROTOCOL = 'https'
|
11
|
+
|
12
|
+
check_unknown_options!
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
@rcfile = RCFile.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "followers", "Follow all followers."
|
20
|
+
def followers
|
21
|
+
follower_ids = []
|
22
|
+
cursor = -1
|
23
|
+
until cursor == 0
|
24
|
+
followers = client.follower_ids(:cursor => cursor)
|
25
|
+
follower_ids += followers.ids
|
26
|
+
cursor = followers.next_cursor
|
27
|
+
end
|
28
|
+
friend_ids = []
|
29
|
+
cursor = -1
|
30
|
+
until cursor == 0
|
31
|
+
friends = client.friend_ids(:cursor => cursor)
|
32
|
+
friend_ids += friends.ids
|
33
|
+
cursor = friends.next_cursor
|
34
|
+
end
|
35
|
+
follow_ids = (follower_ids - friend_ids)
|
36
|
+
number = follow_ids.length
|
37
|
+
return say "@#{@rcfile.default_profile[0]} is already following all of his or her followers." if number.zero?
|
38
|
+
return unless yes? "Are you sure you want to follow #{number} #{number == 1 ? 'user' : 'users'}?"
|
39
|
+
screen_names = follow_ids.map do |follow_id|
|
40
|
+
user = client.follow(follow_id, :include_entities => false)
|
41
|
+
say "@#{@rcfile.default_profile[0]} is now following @#{user.screen_name}."
|
42
|
+
user.screen_name
|
43
|
+
end
|
44
|
+
say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
|
45
|
+
say
|
46
|
+
say "Run `#{$0} unfollow users #{screen_names.join(' ')}` to stop."
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "listed LIST_NAME", "Follow all members of a list."
|
50
|
+
def listed(list_name)
|
51
|
+
list_member_collection = []
|
52
|
+
cursor = -1
|
53
|
+
until cursor == 0
|
54
|
+
list_members = client.list_members(list_name, :cursor => cursor, :skip_status => true, :include_entities => false)
|
55
|
+
list_member_collection += list_members.users
|
56
|
+
cursor = list_members.next_cursor
|
57
|
+
end
|
58
|
+
number = list_member_collection.length
|
59
|
+
return say "@#{@rcfile.default_profile[0]} is already following all list members." if number.zero?
|
60
|
+
return unless yes? "Are you sure you want to follow #{number} #{number == 1 ? 'user' : 'users'}?"
|
61
|
+
list_member_collection.each do |list_member|
|
62
|
+
user = client.follow(list_member.id, :include_entities => false)
|
63
|
+
say "@#{@rcfile.default_profile[0]} is now following @#{user.screen_name}."
|
64
|
+
end
|
65
|
+
say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
|
66
|
+
say
|
67
|
+
say "Run `#{$0} unfollow all listed #{list_name}` to stop."
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def base_url
|
73
|
+
"#{protocol}://#{host}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def client
|
77
|
+
return @client if @client
|
78
|
+
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
79
|
+
@client = Twitter::Client.new(
|
80
|
+
:endpoint => base_url,
|
81
|
+
:consumer_key => @rcfile.default_consumer_key,
|
82
|
+
:consumer_secret => @rcfile.default_consumer_secret,
|
83
|
+
:oauth_token => @rcfile.default_token,
|
84
|
+
:oauth_token_secret => @rcfile.default_secret
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def host
|
89
|
+
parent_options['host'] || DEFAULT_HOST
|
90
|
+
end
|
91
|
+
|
92
|
+
def protocol
|
93
|
+
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/t/cli/list.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
require 't/rcfile'
|
3
|
+
require 'thor'
|
4
|
+
require 'twitter'
|
5
|
+
|
6
|
+
module T
|
7
|
+
class CLI
|
8
|
+
class List < Thor
|
9
|
+
include ActionView::Helpers::DateHelper
|
10
|
+
|
11
|
+
DEFAULT_HOST = 'api.twitter.com'
|
12
|
+
DEFAULT_PROTOCOL = 'https'
|
13
|
+
|
14
|
+
check_unknown_options!
|
15
|
+
|
16
|
+
def initialize(*)
|
17
|
+
super
|
18
|
+
@rcfile = RCFile.instance
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "create LIST_NAME [DESCRIPTION]", "Create a new list."
|
22
|
+
method_option :private, :aliases => "-p", :type => :boolean
|
23
|
+
def create(list_name, description="")
|
24
|
+
hash = description.blank? ? {} : {:description => description}
|
25
|
+
hash.merge!(:mode => 'private') if options['private']
|
26
|
+
client.list_create(list_name, hash)
|
27
|
+
say "@#{@rcfile.default_profile[0]} created the list \"#{list_name}\"."
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "timeline LIST_NAME", "Show tweet timeline for members of the specified list."
|
31
|
+
method_option :number, :aliases => "-n", :type => :numeric, :default => 20
|
32
|
+
method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
|
33
|
+
def timeline(list_name)
|
34
|
+
hash = {:include_entities => false}
|
35
|
+
hash.merge!(:per_page => options['number']) if options['number']
|
36
|
+
timeline = client.list_timeline(list_name, hash)
|
37
|
+
timeline.reverse! if options['reverse']
|
38
|
+
run_pager
|
39
|
+
timeline.map do |status|
|
40
|
+
say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
map %w(tl) => :timeline
|
44
|
+
|
45
|
+
desc "add SUBCOMMAND ...ARGS", "Add users to a list."
|
46
|
+
require 't/cli/list/add'
|
47
|
+
subcommand 'add', CLI::List::Add
|
48
|
+
|
49
|
+
desc "remove SUBCOMMAND ...ARGS", "Remove users from a list."
|
50
|
+
require 't/cli/list/remove'
|
51
|
+
subcommand 'remove', CLI::List::Remove
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def base_url
|
56
|
+
"#{protocol}://#{host}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def client
|
60
|
+
return @client if @client
|
61
|
+
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
62
|
+
@client = Twitter::Client.new(
|
63
|
+
:endpoint => base_url,
|
64
|
+
:consumer_key => @rcfile.default_consumer_key,
|
65
|
+
:consumer_secret => @rcfile.default_consumer_secret,
|
66
|
+
:oauth_token => @rcfile.default_token,
|
67
|
+
:oauth_token_secret => @rcfile.default_secret
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def host
|
72
|
+
parent_options['host'] || DEFAULT_HOST
|
73
|
+
end
|
74
|
+
|
75
|
+
def protocol
|
76
|
+
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
77
|
+
end
|
78
|
+
|
79
|
+
def run_pager
|
80
|
+
return if RUBY_PLATFORM =~ /win32/
|
81
|
+
return if ENV["T_ENV"] == "test"
|
82
|
+
return unless STDOUT.tty?
|
83
|
+
|
84
|
+
read, write = IO.pipe
|
85
|
+
|
86
|
+
unless Kernel.fork # Child process
|
87
|
+
STDOUT.reopen(write)
|
88
|
+
STDERR.reopen(write) if STDERR.tty?
|
89
|
+
read.close
|
90
|
+
write.close
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
# Parent process, become pager
|
95
|
+
STDIN.reopen(read)
|
96
|
+
read.close
|
97
|
+
write.close
|
98
|
+
|
99
|
+
ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
|
100
|
+
|
101
|
+
Kernel.select [STDIN] # Wait until we have input before we start the pager
|
102
|
+
pager = ENV['PAGER'] || 'less'
|
103
|
+
exec pager rescue exec "/bin/sh", "-c", pager
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 't/core_ext/string'
|
2
|
+
require 't/rcfile'
|
3
|
+
require 'thor'
|
4
|
+
require 'twitter'
|
5
|
+
|
6
|
+
module T
|
7
|
+
class CLI
|
8
|
+
class List
|
9
|
+
class Add < Thor
|
10
|
+
DEFAULT_HOST = 'api.twitter.com'
|
11
|
+
DEFAULT_PROTOCOL = 'https'
|
12
|
+
|
13
|
+
check_unknown_options!
|
14
|
+
|
15
|
+
def initialize(*)
|
16
|
+
super
|
17
|
+
@rcfile = RCFile.instance
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "users LIST_NAME SCREEN_NAME [SCREEN_NAME...]", "Add users to a list."
|
21
|
+
def users(list_name, screen_name, *screen_names)
|
22
|
+
screen_names.unshift(screen_name)
|
23
|
+
screen_names.map!{|screen_name| screen_name.strip_at}
|
24
|
+
client.list_add_members(list_name, screen_names)
|
25
|
+
number = screen_names.length
|
26
|
+
say "@#{@rcfile.default_profile[0]} added #{number} #{number == 1 ? 'user' : 'users'} to the list \"#{list_name}\"."
|
27
|
+
say
|
28
|
+
say "Run `#{$0} list remove users #{list_name} #{screen_names.join(' ')}` to undo."
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "all SUBCOMMAND ...ARGS", "Add all users to a list."
|
32
|
+
require 't/cli/list/add/all'
|
33
|
+
subcommand 'all', CLI::List::Add::All
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def base_url
|
38
|
+
"#{protocol}://#{host}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def client
|
42
|
+
return @client if @client
|
43
|
+
@rcfile.path = parent_options['profile'] if parent_options['profile']
|
44
|
+
@client = Twitter::Client.new(
|
45
|
+
:endpoint => base_url,
|
46
|
+
:consumer_key => @rcfile.default_consumer_key,
|
47
|
+
:consumer_secret => @rcfile.default_consumer_secret,
|
48
|
+
:oauth_token => @rcfile.default_token,
|
49
|
+
:oauth_token_secret => @rcfile.default_secret
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def host
|
54
|
+
parent_options['host'] || DEFAULT_HOST
|
55
|
+
end
|
56
|
+
|
57
|
+
def protocol
|
58
|
+
parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|