fry-send_tweet.rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +282 -0
  3. data/LICENSE.txt +24 -0
  4. data/PRY_LICENSE.txt +25 -0
  5. data/README.md +456 -0
  6. data/Vagrantfile +46 -0
  7. data/art/44-e44743a5bb.jpg +0 -0
  8. data/art/52-eec4df2edf.jpg +0 -0
  9. data/lib/pry-send_tweet.rb +75 -0
  10. data/lib/pry/pager/system_pager.rb +25 -0
  11. data/lib/pry/send_tweet/commands/base_command.rb +70 -0
  12. data/lib/pry/send_tweet/commands/easter_eggs.rb +184 -0
  13. data/lib/pry/send_tweet/commands/paging/paging_support.rb +16 -0
  14. data/lib/pry/send_tweet/commands/read_tweets.rb +93 -0
  15. data/lib/pry/send_tweet/commands/read_tweets/translate_actions.rb +94 -0
  16. data/lib/pry/send_tweet/commands/send_tweet.rb +151 -0
  17. data/lib/pry/send_tweet/commands/twitter_action.rb +118 -0
  18. data/lib/pry/send_tweet/commands/twitter_action/delete_tweet_actions.rb +19 -0
  19. data/lib/pry/send_tweet/commands/twitter_action/follow_actions.rb +66 -0
  20. data/lib/pry/send_tweet/commands/twitter_action/like_actions.rb +23 -0
  21. data/lib/pry/send_tweet/commands/twitter_action/mute_actions.rb +23 -0
  22. data/lib/pry/send_tweet/commands/twitter_action/profile_actions.rb +60 -0
  23. data/lib/pry/send_tweet/commands/twitter_action/suggested_actions.rb +20 -0
  24. data/lib/pry/send_tweet/commands/twitter_search.rb +36 -0
  25. data/lib/pry/send_tweet/renderers/tweet_renderer.rb +103 -0
  26. data/lib/pry/send_tweet/renderers/user_renderer.rb +17 -0
  27. data/lib/pry/send_tweet/tty-box.rb +73 -0
  28. data/lib/pry/send_tweet/twitter_io.rb +26 -0
  29. data/lib/pry/send_tweet/version.rb +5 -0
  30. data/lib/time-ago-in-words/README.md +14 -0
  31. data/lib/time-ago-in-words/lib/time-ago-in-words.rb +37 -0
  32. data/lib/time-ago-in-words/time-ago-in-words.gemspec +14 -0
  33. data/pry-send_tweet.gemspec +23 -0
  34. data/samples/freebsd-zshrc +5 -0
  35. data/samples/tmuxinator-vagrant.yml +11 -0
  36. data/vms/freebsd.rb +15 -0
  37. metadata +123 -0
@@ -0,0 +1,94 @@
1
+ module Pry::SendTweet::ReadTweets::TranslateActions
2
+ YANDEX_ENDPOINT = "https://translate.yandex.net/api/v1.5/tr.json/translate?" \
3
+ "key=%{key}&text=%{text}&lang=%{lang}"
4
+
5
+ LANGUAGE_STRINGS = {
6
+ 'ar' => 'Arabic (العربية)',
7
+ 'en' => 'English',
8
+ 'de' => 'German (Deutsch)',
9
+ 'pt' => 'Portuguese (Portuguesa)',
10
+ 'fa' => 'Farsi (دریافت)',
11
+ 'ja' => 'Japanese (日本語)',
12
+ 'he' => 'Hebrew (עברית)',
13
+ 'ga' => 'Irish (Gaeilge)',
14
+ 'es' => 'Spanish (Español)',
15
+ 'it' => 'Italinao (italiano)',
16
+ 'nl' => 'Dutch (Nederlands)',
17
+ 'ru' => 'Russian (русский)',
18
+ 'uk' => 'Ukranian (країнська)',
19
+ 'ko' => 'Korean (한국어)',
20
+ 'fr' => 'French (Français)',
21
+ 'da' => 'Danish (dansk)',
22
+ 'yi' => 'Yiddish (ייִדיש)',
23
+ 'sw' => 'Swahili'
24
+ }
25
+
26
+ def translate_tweet(tweet_url, source_language)
27
+ tweet = twitter.status(tweet_url)
28
+ uri_endpoint = __build_yandex_endpoint(tweet, source_language)
29
+ res = Net::HTTP.get_response(uri_endpoint)
30
+ case res
31
+ when Net::HTTPOK
32
+ tweet.attrs[:full_text] = __translated_text_from(res)
33
+ render_tweets [tweet], title: "#{__translation_map(res)}: "
34
+ else
35
+ raise Pry::CommandError, "Bad response from Yandex (#{res.class})"
36
+ end
37
+ end
38
+
39
+ def translate_text(text, source_language)
40
+ uri_endpoint = __build_yandex_endpoint(text, source_language)
41
+ res = Net::HTTP.get_response(uri_endpoint)
42
+ case res
43
+ when Net::HTTPOK
44
+ _pry_.output.puts "#{__translation_map(res)}: \n" \
45
+ "#{__translated_text_from(res)}"
46
+ else
47
+ raise Pry::CommandError, "Bad response from Yandex (#{res.class})"
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # @api private
54
+ def __translation_map(res)
55
+ b = JSON.parse(res.body)
56
+ from, to = b['lang'].split('-')
57
+ from = LANGUAGE_STRINGS[from] || from
58
+ to = LANGUAGE_STRINGS[to] || to
59
+ "#{from} => #{to}"
60
+ end
61
+
62
+ # @api private
63
+ def __translated_text_from(res)
64
+ JSON.parse(res.body)["text"][0]
65
+ end
66
+
67
+ # @api private
68
+ def __build_yandex_endpoint(tweet, source_language)
69
+ URI.parse format(YANDEX_ENDPOINT,
70
+ lang: URI.encode_www_form_component(__yandex_lang_param(source_language)),
71
+ key: URI.encode_www_form_component(__yandex_key),
72
+ text: URI.encode_www_form_component(
73
+ Twitter::Tweet === tweet ? __read_tweet_body(tweet) : tweet
74
+ )
75
+ )
76
+ end
77
+
78
+ # @api private
79
+ def __yandex_lang_param(source_language)
80
+ from = source_language ? "#{source_language}-" : ""
81
+ to = _pry_.config.twitter.yandex_lang || "en"
82
+ from + to
83
+ end
84
+
85
+ # @api private
86
+ def __yandex_key
87
+ k = _pry_.config.twitter.yandex_key
88
+ if !k || k.strip.empty?
89
+ raise Pry::CommandError,
90
+ "fatal: _pry_.config.twitter.yandex_key is nil, false or empty"
91
+ end
92
+ k
93
+ end
94
+ end
@@ -0,0 +1,151 @@
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 file(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_creator = create_tweet_creator(compose_tweet_with_editor)
33
+ if opts.present?('delay')
34
+ time_obj, sleep_seconds = parse_duration_str(opts['delay'])
35
+ Thread.new {
36
+ sleep sleep_seconds
37
+ tweet_creator.call
38
+ }
39
+ publish_time = (time_obj ? time_obj : Time.now + sleep_seconds)
40
+ .getlocal
41
+ .strftime(time_format)
42
+ page_ok bold("Tweet will be published at approximately #{publish_time}.")
43
+ else
44
+ tweet = tweet_creator.call
45
+ page_ok tweet.url
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def paths_to_twitterio(paths)
52
+ paths.map do |path|
53
+ Pry::SendTweet::TwitterIO.new File.binread(path), File.basename(path)
54
+ end
55
+ end
56
+
57
+ def no_newline?
58
+ opts.present?('no-newline')
59
+ end
60
+
61
+ def send_tweet_with_media(tweet_contents, medias)
62
+ tweet = twitter.update_with_media(tweet_contents, medias, tweet_options)
63
+ tweet.tap {|t| self_destruct! t.id, opts['self-destruct'] }
64
+ ensure
65
+ medias.each(&:close)
66
+ end
67
+
68
+ def send_textual_tweet(tweet_contents)
69
+ tweet = twitter.update(tweet_contents, tweet_options)
70
+ tweet.tap {|t| self_destruct! t.id, opts['self-destruct'] }
71
+ end
72
+
73
+ def prepend_username_to_reply!(tweet_url, file)
74
+ tweet = twitter.status(tweet_url)
75
+ mentions = [tweet.user.screen_name].concat tweet.user_mentions.map(&:screen_name)
76
+ file.write mentions.uniq(&:downcase).map{|u| "@#{u}" }.join(' ')
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 create_tweet_creator(tweet_contents)
109
+ if opts.present?(:file)
110
+ medias = paths_to_twitterio(opts[:file])
111
+ lambda { send_tweet_with_media(tweet_contents, medias) }
112
+ else
113
+ lambda { send_textual_tweet(tweet_contents) }
114
+ end
115
+ end
116
+
117
+ def validate_tweet!(tweet)
118
+ if tweet.strip.empty?
119
+ raise Pry::CommandError, "Can't post an empty tweet."
120
+ elsif tweet.size > MAX_TWEET_SIZE
121
+ raise Pry::CommandError, "The tweet: \n" +
122
+ word_wrap(tweet) +
123
+ "\nis too big to publish, try to use less characters."
124
+ end
125
+ end
126
+
127
+ def self_destruct!(tweet_id, duration)
128
+ return if !duration
129
+ _, sleep_seconds = parse_duration_str(duration)
130
+ page bold("Tweet due to self destruct in #{sleep_seconds} seconds")
131
+ Thread.new do
132
+ sleep sleep_seconds
133
+ twitter.destroy_status(tweet_id)
134
+ end
135
+ end
136
+
137
+ def parse_duration_str(str)
138
+ if str =~ /\A\d+\z/
139
+ sleep_seconds = Integer(str)
140
+ elsif str =~ /\A\d{2}:\d{2}\z/ || str =~ /\A\d{2}:\d{2}:\d{2}\z/
141
+ time_obj = Time.parse(str)
142
+ time_obj += 3600*24 if time_obj <= Time.now
143
+ sleep_seconds = Integer(time_obj - Time.now)
144
+ else
145
+ raise Pry::CommandError, "--delay='#{str}' or --self-destruct='#{str}' is not " \
146
+ "something I understand."
147
+ end
148
+ [time_obj, sleep_seconds]
149
+ end
150
+ Pry.commands.add_command self
151
+ end
@@ -0,0 +1,118 @@
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
+ #{description}
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
+ # Following / unfollowing
32
+ #
33
+ o.on 'follow=', 'Follow one or more users', as: Array
34
+ o.on 'unfollow=', 'Unfollow one or more users', as: Array
35
+ o.on 'show-followers=', 'Show the newest users to have followed you or ' \
36
+ 'another user', argument: :optional
37
+ o.on 'show-following', 'Show the newest users you currently follow'
38
+ #
39
+ # Delete tweets and reweets
40
+ #
41
+ o.on 'delete-retweet=', 'Delete one or more retweets', as: Array
42
+ o.on 'delete-tweet=', 'Delete one or more tweets', as: Array
43
+ #
44
+ # Retweet a tweet
45
+ #
46
+ o.on 'retweet=', 'Retweet one or more tweets', as: Array
47
+ #
48
+ # Profile management
49
+ #
50
+ o.on 'set-profile-name=', 'Set the name visible on your profile'
51
+ o.on 'set-profile-bio', 'Set the description visible on your profile'
52
+ o.on 'set-profile-location', 'Set the location visible on your profile'
53
+ o.on 'set-profile-url=', 'Set the URL visible on your profile'
54
+ o.on 'set-profile-banner-image=', 'Set the banner image visible on your profile'
55
+ o.on 'set-profile-image=', 'Set profile photo'
56
+ o.on 'set-profile-link-color=', 'Set the color (as a hex value) of links that ' \
57
+ 'are visible on your profiles timeline '
58
+ #
59
+ # Suggestions
60
+ #
61
+ o.on 'suggested-topics', 'View topics suggested by Twitter'
62
+ o.on 'suggested-users=', 'A topic from which to view users Twitter ' \
63
+ 'suggests following'
64
+ o.on 'suggested-lang=', 'Restrict suggestion results to a specific language, ' \
65
+ 'given in ISO 639-1 format. Default is "en"'
66
+ #
67
+ # Muting
68
+ #
69
+ o.on 'mute-user=', 'One or more usernames to mute', as: Array
70
+ o.on 'unmute-user=', 'One or more usernames to unmute', as: Array
71
+ end
72
+
73
+ def process
74
+ super
75
+ case
76
+ when opts.present?('like-tweet') then like_tweet(opts['like-tweet'])
77
+ when opts.present?('unlike-tweet') then unlike_tweet(opts['unlike-tweet'])
78
+ when opts.present?('follow') then follow_user(opts['follow'])
79
+ when opts.present?('unfollow') then unfollow_user(opts['unfollow'])
80
+ when opts.present?('delete-tweet') then delete_tweet(opts['delete-tweet'])
81
+ when opts.present?('delete-retweet') then delete_retweet(opts['delete-retweet'])
82
+ when opts.present?('retweet') then retweet_tweet(opts['retweet'])
83
+ when opts.present?('set-profile-banner-image') then set_profile_banner_image(opts['set-profile-banner-image'])
84
+ when opts.present?('set-profile-name') then set_profile_name(opts['set-profile-name'])
85
+ when opts.present?('set-profile-image') then set_profile_image(opts['set-profile-image'])
86
+ when opts.present?('set-profile-url') then set_profile_url(opts['set-profile-url'])
87
+ when opts.present?('set-profile-location') then set_profile_location
88
+ when opts.present?('set-profile-link-color') then set_profile_link_color(opts['set-profile-link-color'])
89
+ when opts.present?('set-profile-bio') then set_profile_bio
90
+ when opts.present?('suggested-topics') then suggested_topics
91
+ when opts.present?('suggested-users') then suggested_users(opts['suggested-users'])
92
+ when opts.present?('mute-user') then mute_user(opts['mute-user'])
93
+ when opts.present?('unmute-user') then unmute_user(opts['unmute-user'])
94
+ when opts.present?('show-followers') then show_followers(opts['show-followers'])
95
+ when opts.present?('show-following') then show_following(opts['show-following'])
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def retweet_tweet(tweets)
102
+ retweets = twitter.retweet(tweets)
103
+ if retweets.size > 0
104
+ render_tweets retweets, title: bright_green("Retweeted tweets"), timeout: false
105
+ else
106
+ page_error word_wrap("Nothing retweeted. Are you sure you gave a valid reference to " \
107
+ "one or more tweets, and that you haven't already retweeted the given " \
108
+ "tweet(s) ..?")
109
+ end
110
+ rescue Twitter::Error => e
111
+ page_error word_wrap("Nothing retweeted. Are you sure you gave a valid reference to " \
112
+ "one or more tweets, and that you haven't already retweeted the given " \
113
+ "tweet(s) ..?")
114
+ end
115
+
116
+ Pry.commands.add_command(self)
117
+ Pry.commands.alias_command "on-twitter", "twitter-action"
118
+ end
@@ -0,0 +1,19 @@
1
+ module Pry::SendTweet::TwitterAction::DeleteTweetActions
2
+ def delete_retweet(retweets)
3
+ tweets = twitter.unretweet(retweets)
4
+ if tweets.size == 0
5
+ page_error "Are you sure you gave a valid reference to one or more retweets?"
6
+ else
7
+ render_tweets tweets, title: bold("Deleted retweets"), timeout: false
8
+ end
9
+ rescue Twitter::Error => e
10
+ page_error(e)
11
+ end
12
+
13
+ def delete_tweet(tweets)
14
+ tweets = twitter.destroy_status(tweets)
15
+ render_tweets tweets, title: bold("Deleted tweets"), timeout: false
16
+ rescue Twitter::Error => e
17
+ page_error(e)
18
+ end
19
+ end
@@ -0,0 +1,66 @@
1
+ module Pry::SendTweet::TwitterAction::FollowActions
2
+ def follow_user(users)
3
+ users = search_str_for_users(*users)
4
+ follows = twitter.follow(users)
5
+ if follows.size > 0
6
+ page_ok "followed #{join_usernames(follows.map(&:screen_name))}."
7
+ else
8
+ page_error "are you already following #{join_usernames(users)} ..?"
9
+ end
10
+ rescue Twitter::Error => e
11
+ page_error(e)
12
+ end
13
+
14
+ def unfollow_user(users)
15
+ users = search_str_for_users(*users)
16
+ unfollows = twitter.unfollow(users)
17
+ if unfollows.size > 0
18
+ page_ok "unfollowed #{join_usernames(unfollows.map(&:screen_name))}."
19
+ else
20
+ page_error "did you already unfollow #{join_usernames(users)} ..?"
21
+ end
22
+ rescue Twitter::Error => e
23
+ page_error(e)
24
+ end
25
+
26
+ def show_followers(pattern)
27
+ followers = Array twitter.followers(follow_request_options)
28
+ __follow_filter!(followers, pattern) if pattern
29
+ page numbered_list("Followers", followers) {|follower, index|
30
+ render_user(follower)
31
+ }
32
+ rescue Twitter::Error => e
33
+ page_error(e)
34
+ end
35
+
36
+ def show_following(pattern)
37
+ followings = Array twitter.following(follow_request_options)
38
+ __follow_filter!(followings, pattern) if pattern
39
+ page numbered_list("Following", followings) {|following, index|
40
+ render_user(following)
41
+ }
42
+ rescue Twitter::Error => e
43
+ page_error(e)
44
+ end
45
+
46
+ private
47
+
48
+ # @api private
49
+ def __follow_filter!(users, pattern)
50
+ users.select! do |u|
51
+ u.screen_name =~ /#{pattern}/
52
+ end
53
+ end
54
+
55
+ # @api private
56
+ def follow_request_options
57
+ {skip_status: true, include_user_entities: true}
58
+ end
59
+
60
+ # @api private
61
+ def join_usernames(users)
62
+ users.map do |username|
63
+ bold("@#{username}") + " ( https://twitter.com/#{username} )"
64
+ end.join(', ')
65
+ end
66
+ end