pry-send_tweet.rb 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ class Pry::SendTweet::ReadTweets < Pry::SendTweet::BaseCommand
2
+ match 'read-tweets'
3
+ description 'A command for reading tweets.'
4
+ banner <<-BANNER
5
+ read-tweets [OPTIONS]
6
+
7
+ #{description}
8
+ BANNER
9
+
10
+ def options(o)
11
+ o.on 't=', 'tweeter=',
12
+ 'A username whose timeline you want to read.'
13
+ o.on 'c=', 'count=',
14
+ "The number of tweets to read. The maximum is 200, and the default is " \
15
+ "#{Pry::SendTweet::DEFAULT_READ_SIZE}."
16
+ o.on 'l=', 'likes=',
17
+ 'Read tweets you or another user have liked.',
18
+ argument: :optional
19
+ o.on 'r=', 'replies=', 'A username whose replies you want to read'
20
+ o.on 'm', 'mentions', 'Read tweets that @mention you.'
21
+ o.on 'w', 'with-retweets', 'Include retweets.'
22
+ end
23
+
24
+ def process
25
+ super
26
+ case
27
+ when opts.present?('replies') then show_replies(user: opts['replies'])
28
+ when opts.present?('likes') then show_likes(user: opts['likes'])
29
+ when opts.present?('mentions') then show_mentions
30
+ else show_tweets_from_timeline(user: opts['tweeter'])
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def show_replies(user:)
37
+ render_tweets lambda {
38
+ username = find_usernames_in(user).first
39
+ twitter.user_timeline(username, timeline_options).select {|tweet|
40
+ tweet.reply? &&
41
+ tweet.in_reply_to_screen_name? &&
42
+ tweet.in_reply_to_screen_name.downcase != username.downcase
43
+ }
44
+ },
45
+ title: format("Replies sent by %{user}", user: bold("@#{user}"))
46
+ end
47
+
48
+ def show_likes(user:)
49
+ if user
50
+ user = find_usernames_in(user).first
51
+ render_tweets lambda { twitter.favorites(user, count: opts['count'] || Pry::SendTweet::DEFAULT_READ_SIZE)},
52
+ title: "Tweets liked by #{bright_white('@'+user)}"
53
+ else
54
+ render_tweets lambda { twitter.favorites(count: opts['count'] || Pry::SendTweet::DEFAULT_READ_SIZE) },
55
+ title: "Liked tweets"
56
+ end
57
+ end
58
+
59
+ def show_tweets_from_timeline(user:)
60
+ if user
61
+ render_tweets lambda { twitter.user_timeline(user, timeline_options) },
62
+ title: '@'+user
63
+ else
64
+ render_tweets lambda { twitter.home_timeline(timeline_options) },
65
+ title: "Twitter.com"
66
+ end
67
+ end
68
+
69
+ def show_mentions
70
+ render_tweets lambda { twitter.mentions(timeline_options) },
71
+ title: "Tweets that #{bright_white('@mention')} #{bright_blue('you')}"
72
+ end
73
+
74
+ def timeline_options
75
+ {
76
+ tweet_mode: 'extended',
77
+ include_rts: opts.present?('with-retweets'),
78
+ count: opts.present?('count') ? opts['count'] : Pry::SendTweet::DEFAULT_READ_SIZE
79
+ }
80
+ end
81
+
82
+ Pry.commands.add_command self
83
+ end
@@ -0,0 +1,141 @@
1
+ class Pry::SendTweet::SendTweet < Pry::SendTweet::BaseCommand
2
+ MAX_TWEET_SIZE = 280
3
+
4
+ match 'send-tweet'
5
+ description 'Send a tweet'
6
+ command_options argument_required: false
7
+ banner <<-BANNER
8
+ send-tweet [options]
9
+
10
+ Send a tweet.
11
+ BANNER
12
+
13
+ def options(o)
14
+ o.on 'f=', 'file=',
15
+ 'One or more paths to image(s) to attach to a tweet',
16
+ as: Array
17
+ o.on 'r=', 'reply-to=',
18
+ 'An absolute url to a tweet you want to reply to.'
19
+ o.on 's=', 'self-destruct=',
20
+ 'The number of seconds (represented as a number or a timestamp in the ' \
21
+ 'format of HH:MM:SS) to wait before automatically deleting the tweet ' \
22
+ 'asynchronously'
23
+ o.on 'd=', 'delay=',
24
+ 'The number of seconds (represented as a number or a timestamp in the ' \
25
+ 'format of HH:MM:SS) to wait before creating the tweet asynchronously'
26
+ o.on 'n', 'no-newline',
27
+ "Remove newlines (\\n) from a tweet before sending it"
28
+ end
29
+
30
+ def process(args)
31
+ super
32
+ tweet_contents = compose_tweet_with_editor
33
+ create_tweet = opts.present?(:file) ? lambda { send_tweet_with_media(tweet_contents) } :
34
+ lambda { send_textual_tweet(tweet_contents) }
35
+ if opts.present?('delay')
36
+ time_obj, sleep_seconds = parse_duration_str(opts['delay'])
37
+ Thread.new {
38
+ sleep sleep_seconds
39
+ create_tweet.call
40
+ }
41
+ publish_time = (time_obj ? time_obj : Time.now + sleep_seconds)
42
+ .getlocal
43
+ .strftime(time_format)
44
+ page bold("Tweet will be published at approximately #{publish_time}.")
45
+ else
46
+ tweet = create_tweet.call
47
+ page tweet.url
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def no_newline?
54
+ opts.present?('no-newline')
55
+ end
56
+
57
+ def send_tweet_with_media(tweet_contents)
58
+ medias = opts['file'].map{|p| File.new p, 'r'}
59
+ tweet = twitter.update_with_media(tweet_contents, medias, tweet_options)
60
+ tweet.tap {|t| self_destruct! t.id, opts['self-destruct'] }
61
+ ensure
62
+ medias.each(&:close)
63
+ end
64
+
65
+ def send_textual_tweet(tweet_contents)
66
+ tweet = twitter.update(tweet_contents, tweet_options)
67
+ tweet.tap {|t| self_destruct! t.id, opts['self-destruct'] }
68
+ end
69
+
70
+ def prepend_username_to_reply!(tweet_url, file)
71
+ username = tweet_username_from(tweet_url)
72
+ file.write "@#{username} "
73
+ end
74
+
75
+ def tweet_username_from(tweet_url)
76
+ File.basename File.dirname(File.dirname(tweet_url))
77
+ end
78
+
79
+ def replying_to_other_tweet?
80
+ opts.present?('reply-to')
81
+ end
82
+
83
+ def tweet_options
84
+ options = {}
85
+ options.merge!({
86
+ in_reply_to_status_id: Integer(File.basename(opts['reply-to']))
87
+ }) if replying_to_other_tweet?
88
+ options
89
+ end
90
+
91
+ def compose_tweet_with_editor
92
+ tweet = Tempfile.open('pry-send_tweet--compose-tweet') do |file|
93
+ if replying_to_other_tweet?
94
+ # During experimentation I noticed that without prefixing who we are
95
+ # replying to, the tweet we send will not be considered a reply even
96
+ # with reply_to_status_id being set.
97
+ prepend_username_to_reply!(opts['reply-to'], file)
98
+ end
99
+ file.rewind
100
+ Pry::Editor.new(_pry_).invoke_editor(file.path, 0)
101
+ lines = file.read.each_line
102
+ no_newline? ? lines.map(&:chomp).join(' ') : lines.to_a.join
103
+ end
104
+ validate_tweet!(tweet)
105
+ tweet
106
+ end
107
+
108
+ def validate_tweet!(tweet)
109
+ if tweet.strip.empty?
110
+ raise Pry::CommandError, "Can't post an empty tweet."
111
+ elsif tweet.size > MAX_TWEET_SIZE
112
+ raise Pry::CommandError, "The tweet: \n" +
113
+ word_wrap(tweet) +
114
+ "\nis too big to publish, try to use less characters."
115
+ end
116
+ end
117
+
118
+ def self_destruct!(tweet_id, duration)
119
+ return if !duration
120
+ _, sleep_seconds = parse_duration_str(duration)
121
+ page bold("Tweet due to self destruct in #{sleep_seconds} seconds")
122
+ Thread.new do
123
+ sleep sleep_seconds
124
+ twitter.destroy_status(tweet_id)
125
+ end
126
+ end
127
+
128
+ def parse_duration_str(str)
129
+ if str =~ /\A\d+\z/
130
+ sleep_seconds = Integer(str)
131
+ elsif str =~ /\A\d{2}:\d{2}\z/ || str =~ /\A\d{2}:\d{2}:\d{2}\z/
132
+ time_obj = Time.parse(str)
133
+ sleep_seconds = Integer(time_obj - Time.now)
134
+ else
135
+ raise Pry::CommandError, "The argument --delay='#{str}' is not " \
136
+ "something I understand."
137
+ end
138
+ [time_obj, sleep_seconds]
139
+ end
140
+ Pry.commands.add_command self
141
+ end
@@ -0,0 +1,109 @@
1
+
2
+ class Pry::SendTweet::TwitterAction < Pry::SendTweet::BaseCommand
3
+ require_relative 'twitter_action/profile_actions'
4
+ require_relative 'twitter_action/suggested_actions'
5
+ require_relative 'twitter_action/like_actions'
6
+ require_relative 'twitter_action/follow_actions'
7
+ require_relative 'twitter_action/mute_actions'
8
+ require_relative 'twitter_action/delete_tweet_actions'
9
+ include ProfileActions
10
+ include SuggestedActions
11
+ include LikeActions
12
+ include FollowActions
13
+ include MuteActions
14
+ include DeleteTweetActions
15
+
16
+ match 'twitter-action'
17
+ description 'Like, unlike, follow, unfollow and more'
18
+ banner <<-B
19
+ twitter-action OPTIONS
20
+
21
+ Like, unlike, follow, unfollow and more.
22
+ B
23
+
24
+ def options(o)
25
+ #
26
+ # Like / unlike tweets
27
+ #
28
+ o.on 'like-tweet=', 'Like one or more tweets', as: Array
29
+ o.on 'unlike-tweet=', 'Unlike one or more tweets', as: Array
30
+ #
31
+ # Follow / unfollow a user
32
+ #
33
+ o.on 'follow=', 'Follow one or more users', as: Array
34
+ o.on 'unfollow=', 'Unfollow one or more users', as: Array
35
+ #
36
+ # Delete tweets and reweets
37
+ #
38
+ o.on 'delete-retweet=', 'Delete one or more retweets', as: Array
39
+ o.on 'delete-tweet=', 'Delete one or more tweets', as: Array
40
+ #
41
+ # Retweet a tweet
42
+ #
43
+ o.on 'retweet=', 'Retweet one or more tweets', as: Array
44
+ #
45
+ # Profile management
46
+ #
47
+ o.on 'set-profile-bio', 'Set your profiles bio'
48
+ o.on 'set-profile-url=', 'Set the URL associated with your profile'
49
+ o.on 'set-profile-banner-image=', 'Set your profiles banner image'
50
+ o.on 'set-profile-name=', 'Set the name associated with your profile'
51
+ o.on 'set-profile-image=', 'Set your profile image'
52
+ #
53
+ # Suggestions
54
+ #
55
+ o.on 'suggested-topics', 'View topics suggested by Twitter'
56
+ o.on 'suggested-users=', 'A topic from which to view users Twitter ' \
57
+ 'suggests following'
58
+ o.on 'suggested-lang=', 'Restrict suggestion results to a specific language, ' \
59
+ 'given in ISO 639-1 format. Default is "en"'
60
+ #
61
+ # Muting
62
+ #
63
+ o.on 'mute-user=', 'One or more usersname to mute', as: Array
64
+ o.on 'unmute-user=', 'One or more usernames to unmute', as: Array
65
+ end
66
+
67
+ def process
68
+ super
69
+ case
70
+ when opts.present?('like-tweet') then like_tweet(opts['like-tweet'])
71
+ when opts.present?('unlike-tweet') then unlike_tweet(opts['unlike-tweet'])
72
+ when opts.present?('follow') then follow_user(opts['follow'])
73
+ when opts.present?('unfollow') then unfollow_user(opts['unfollow'])
74
+ when opts.present?('delete-tweet') then delete_tweet(opts['delete-tweet'])
75
+ when opts.present?('delete-retweet') then delete_retweet(opts['delete-retweet'])
76
+ when opts.present?('retweet') then retweet_tweet(opts['retweet'])
77
+ when opts.present?('set-profile-banner-image') then set_profile_banner_image(opts['set-profile-banner-image'])
78
+ when opts.present?('set-profile-name') then set_profile_name(opts['set-profile-name'])
79
+ when opts.present?('set-profile-image') then set_profile_image(opts['set-profile-image'])
80
+ when opts.present?('set-profile-url') then set_profile_url(opts['set-profile-url'])
81
+ when opts.present?('set-profile-bio') then set_profile_bio
82
+ when opts.present?('suggested-topics') then suggested_topics
83
+ when opts.present?('suggested-users') then suggested_users(opts['suggested-users'])
84
+ when opts.present?('mute-user') then mute_user(opts['mute-user'])
85
+ when opts.present?('unmute-user') then unmute_user(opts['unmute-user'])
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def retweet_tweet(tweets)
92
+ retweets = twitter.retweet(tweets)
93
+ error_msg = word_wrap("No retweets created. Are you sure you gave a valid reference to " \
94
+ "one or more tweets, and that you haven't already retweeted the supplied " \
95
+ "tweet(s) ..?")
96
+ retweets.size > 0 ? render_tweets(retweets, title: bright_green("Retweeted tweets"), timeout: false) :
97
+ page(twitter_error(error_msg))
98
+ rescue Twitter::Error => e
99
+ page twitter_error(e)
100
+ end
101
+
102
+ def display_followed_or_unfollowed(user, index)
103
+ sn = "@#{user.screen_name}"
104
+ "#{sn} #{bold(bright_blue('|'))} #{user.url}"
105
+ end
106
+
107
+ Pry.commands.add_command(self)
108
+ Pry.commands.alias_command "on-twitter", "twitter-action"
109
+ end
@@ -0,0 +1,16 @@
1
+ module Pry::SendTweet::TwitterAction::DeleteTweetActions
2
+ def delete_retweet(retweets)
3
+ tweets = twitter.unretweet(retweets)
4
+ tweets.size > 0 ? render_tweets(tweets, title: bold("Deleted retweets"), timeout: false) :
5
+ page(twitter_error("No retweets deleted. Are you sure you gave a valid reference to the retweet?"))
6
+ rescue Twitter::Error => e
7
+ page twitter_error(e)
8
+ end
9
+
10
+ def delete_tweet(tweets)
11
+ tweets = twitter.destroy_status(tweets)
12
+ render_tweets tweets, title: bold("Deleted tweets"), timeout: false
13
+ rescue Twitter::Error => e
14
+ page twitter_error(e)
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module Pry::SendTweet::TwitterAction::FollowActions
2
+ def follow_user(users)
3
+ users = find_usernames_in(*users)
4
+ follows = twitter.follow(users)
5
+ follows.empty? ? page("Are you sure you are not already following #{join_usernames(users)} ?") :
6
+ page(numbered_list(bold("Followed:"), follows) {|user, index|
7
+ display_followed_or_unfollowed(user, index)
8
+ })
9
+ rescue Twitter::Error => e
10
+ page twitter_error(e)
11
+ end
12
+
13
+ def unfollow_user(users)
14
+ users = find_usernames_in(*users)
15
+ unfollows = twitter.unfollow(users)
16
+ unfollows.empty? ? page("Are you sure you are following #{join_usernames(users)} ?") :
17
+ page(numbered_list(bold("Unfollowed:"), unfollows) {|user, index|
18
+ display_followed_or_unfollowed(user, index)
19
+ })
20
+ rescue Twitter::Error => e
21
+ page twitter_error(e)
22
+ end
23
+
24
+ private
25
+ def join_usernames(users)
26
+ users.map do |username|
27
+ bold("@#{username}")
28
+ end.join(', ')
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Pry::SendTweet::TwitterAction::LikeActions
2
+ def like_tweet(tweets)
3
+ liked = twitter.favorite(tweets)
4
+ liked.size > 0 ? render_tweets(liked, title: bold("Liked tweets"), timeout: false) :
5
+ page(twitter_error("Tweet(s) not liked. Maybe you liked those tweet(s) already?"))
6
+ rescue Timeout::Error => e
7
+ page twitter_error(e)
8
+ end
9
+
10
+ def unlike_tweet(tweets)
11
+ unliked = twitter.unfavorite(tweets)
12
+ unliked.size > 0 ? render_tweets(unliked, title: bold("Unliked tweets"), timeout: false) :
13
+ page(twitter_error("Tweet(s) not unliked. Maybe you haven't liked those tweet(s)?"))
14
+ rescue Twitter::Error => e
15
+ page twitter_error(e)
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Pry::SendTweet::TwitterAction::MuteActions
2
+ def mute_user(strings)
3
+ muted = twitter.mute(*find_usernames_in(*strings))
4
+ muted.empty? ? page("No users muted. Maybe you muted them already?") :
5
+ page(numbered_list("Muted users", muted) {|user, index|
6
+ display_followed_or_unfollowed(user, index)
7
+ })
8
+ rescue Twitter::Error => e
9
+ page twitter_error(e)
10
+ end
11
+
12
+ def unmute_user(strings)
13
+ unmuted = twitter.unmute(*find_usernames_in(*strings))
14
+ page numbered_list("Unmuted users", unmuted) {|user, index|
15
+ display_followed_or_unfollowed(user, index)
16
+ }
17
+ rescue Twitter::Error => e
18
+ page twitter_error(e)
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ module Pry::SendTweet::TwitterAction::ProfileActions
2
+ def set_profile_url(url)
3
+ if twitter.update_profile url: url
4
+ page "Profile URL updated"
5
+ else
6
+ raise Pry::CommandError, "Something went wrong"
7
+ end
8
+ end
9
+
10
+ def set_profile_image(path)
11
+ twitter.update_profile_image(path)
12
+ page "Profile image updated"
13
+ rescue Twitter::Error => e
14
+ page twitter_error(e)
15
+ end
16
+
17
+ def set_profile_banner_image(path)
18
+ base64 = Base64.strict_encode64 File.binread(path)
19
+ twitter.update_profile_banner(base64)
20
+ page "Profile banner updated"
21
+ rescue Twitter::Error => e
22
+ page twitter_error(e)
23
+ end
24
+
25
+ def set_profile_name(name)
26
+ twitter.update_profile name: name
27
+ page "Profile name updated"
28
+ rescue Twitter::Error => e
29
+ page twitter_error(e)
30
+ end
31
+
32
+ def set_profile_bio
33
+ Tempfile.open('pry-send_tweet-set-bio') do |file|
34
+ Pry::Editor.new(_pry_).invoke_editor(file.path, 0)
35
+ file.rewind
36
+ twitter.update_profile description: file.read
37
+ page "Profile bio updated"
38
+ end
39
+ rescue Twitter::Error => e
40
+ page twitter_error(e)
41
+ end
42
+ end