pry-twitter 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +13 -0
  3. data/LICENSE.txt +24 -0
  4. data/PRY_LICENSE.txt +25 -0
  5. data/README.md +546 -0
  6. data/Vagrantfile +48 -0
  7. data/art/16-60174b5812.jpg +0 -0
  8. data/art/16-a31deea6f0.jpg +0 -0
  9. data/art/17-b3d5d6f6c2.jpg +0 -0
  10. data/art/44-e44743a5bb.jpg +0 -0
  11. data/art/51-5e4dc5fd92.jpg +0 -0
  12. data/art/51-82bde81305.jpg +0 -0
  13. data/art/51-ad821fa377.jpg +0 -0
  14. data/art/51-b90d5dd6a6.jpg +0 -0
  15. data/art/51-da43a4ef4f.jpg +0 -0
  16. data/art/51-e94c3387e1.jpg +0 -0
  17. data/art/52-eec4df2edf.jpg +0 -0
  18. data/art/53-9d231893e1.jpg +0 -0
  19. data/art/54-39b1063483.jpg +0 -0
  20. data/art/55-e41f9896d0.jpg +0 -0
  21. data/art/56-c82d4e5b61.jpg +0 -0
  22. data/art/59-2a2c9cd962.jpg +0 -0
  23. data/art/61-3970c79d47.jpg +0 -0
  24. data/art/62-5704cc1652.jpg +0 -0
  25. data/art/62-b0aceb0fa6.jpg +0 -0
  26. data/art/62-da0faf972e.jpg +0 -0
  27. data/art/frankewilde.jpg +0 -0
  28. data/art/rollingstone.jpg +0 -0
  29. data/art/tide.jpg +0 -0
  30. data/art/windows10.png +0 -0
  31. data/lib/pry-send_tweet.rb +77 -0
  32. data/lib/pry/pager/system_pager.rb +25 -0
  33. data/lib/pry/send_tweet/commands/base_command.rb +72 -0
  34. data/lib/pry/send_tweet/commands/easter_eggs.rb +564 -0
  35. data/lib/pry/send_tweet/commands/paging/paging_support.rb +16 -0
  36. data/lib/pry/send_tweet/commands/read_tweets.rb +93 -0
  37. data/lib/pry/send_tweet/commands/read_tweets/translate_actions.rb +94 -0
  38. data/lib/pry/send_tweet/commands/send_tweet.rb +164 -0
  39. data/lib/pry/send_tweet/commands/twitter_action.rb +118 -0
  40. data/lib/pry/send_tweet/commands/twitter_action/delete_tweet_actions.rb +19 -0
  41. data/lib/pry/send_tweet/commands/twitter_action/follow_actions.rb +66 -0
  42. data/lib/pry/send_tweet/commands/twitter_action/like_actions.rb +23 -0
  43. data/lib/pry/send_tweet/commands/twitter_action/mute_actions.rb +23 -0
  44. data/lib/pry/send_tweet/commands/twitter_action/profile_actions.rb +60 -0
  45. data/lib/pry/send_tweet/commands/twitter_action/suggested_actions.rb +20 -0
  46. data/lib/pry/send_tweet/commands/twitter_search.rb +36 -0
  47. data/lib/pry/send_tweet/renderers/tweet_renderer.rb +103 -0
  48. data/lib/pry/send_tweet/renderers/user_renderer.rb +17 -0
  49. data/lib/pry/send_tweet/tty-box.rb +73 -0
  50. data/lib/pry/send_tweet/twitter_io.rb +26 -0
  51. data/lib/pry/send_tweet/version.rb +6 -0
  52. data/lib/time-ago-in-words/README.md +14 -0
  53. data/lib/time-ago-in-words/lib/time-ago-in-words.rb +37 -0
  54. data/lib/time-ago-in-words/time-ago-in-words.gemspec +14 -0
  55. data/pry-send_tweet.gemspec +23 -0
  56. data/samples/freebsd-zshrc +5 -0
  57. data/samples/loader.conf +1 -0
  58. data/samples/tmuxinator-vagrant.yml +11 -0
  59. data/vms/freebsd.rb +18 -0
  60. metadata +145 -0
@@ -0,0 +1,16 @@
1
+ module Pry::SendTweet::PagingSupport
2
+ def page(str)
3
+ _pry_.pager.page(str.to_s)
4
+ end
5
+
6
+ def page_ok(str)
7
+ prefix = bright_green "OK: "
8
+ page "#{prefix}#{str}"
9
+ end
10
+
11
+ def page_error(str)
12
+ str = str.respond_to?(:message) ? str.message : str
13
+ prefix = bright_red "Error: "
14
+ page "#{prefix}#{str}"
15
+ end
16
+ end
@@ -0,0 +1,93 @@
1
+ class Pry::SendTweet::ReadTweets < Pry::SendTweet::BaseCommand
2
+ require_relative 'read_tweets/translate_actions'
3
+ include TranslateActions
4
+
5
+ match 'read-tweets'
6
+ description 'Read tweets.'
7
+ banner <<-BANNER
8
+ read-tweets [OPTIONS]
9
+
10
+ #{description}
11
+ BANNER
12
+
13
+ def options(o)
14
+ o.on 't=', 'tweeter=',
15
+ 'A username whose timeline you want to read.'
16
+ o.on 'c=', 'count=',
17
+ "The number of tweets to read. The maximum is 200, and the default is " \
18
+ "#{default_read_size}."
19
+ o.on 'l=', 'likes=',
20
+ 'Read tweets you or another user have liked.',
21
+ argument: :optional
22
+ o.on 'r=', 'replies=', 'A username whose replies you want to read.'
23
+ o.on 'm', 'mentions', 'Read tweets that @mention you.'
24
+ o.on 'x=', 'translate=', 'Translate a tweet.'
25
+ o.on 'tx=', nil, 'Translate a string of text.'
26
+ o.on 'sl=', 'source-lang=', '[optional] The source language of the ' \
27
+ 'text or tweet being translated'
28
+ o.on 'w', 'with-retweets', 'Include retweets.'
29
+ end
30
+
31
+ def process
32
+ super
33
+ case
34
+ when opts.present?('translate') then translate_tweet(opts['x'], opts['sl'])
35
+ when opts.present?('tx') then translate_text(opts['tx'], opts['sl'])
36
+ when opts.present?('replies') then show_replies(user: opts['replies'])
37
+ when opts.present?('likes') then show_likes(user: opts['likes'])
38
+ when opts.present?('mentions') then show_mentions
39
+ else show_tweets_from_timeline(user: opts['tweeter'])
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def show_replies(user:)
46
+ username = search_str_for_users(user).first
47
+ render_tweets lambda {
48
+ twitter.user_timeline(username, timeline_options).select {|tweet|
49
+ tweet.reply? &&
50
+ tweet.in_reply_to_screen_name? &&
51
+ tweet.in_reply_to_screen_name.downcase != username.downcase
52
+ }
53
+ },
54
+ title: "#{'@'+user} replies"
55
+ end
56
+
57
+ def show_likes(user:)
58
+ if user
59
+ user = search_str_for_users(user).first
60
+ render_tweets lambda { twitter.favorites(user, count: opts['count'] || default_read_size)},
61
+ title: "#{'@'+user} likes"
62
+ else
63
+ render_tweets lambda { twitter.favorites(count: opts['count'] || default_read_size) },
64
+ title: "Your likes"
65
+ end
66
+ end
67
+
68
+ def show_tweets_from_timeline(user:)
69
+ if user
70
+ user = search_str_for_users(user).first
71
+ render_tweets lambda { twitter.user_timeline(user, timeline_options) },
72
+ title: '@'+user
73
+ else
74
+ render_tweets lambda { twitter.home_timeline(timeline_options) },
75
+ title: "Twitter"
76
+ end
77
+ end
78
+
79
+ def show_mentions
80
+ render_tweets lambda { twitter.mentions(timeline_options) },
81
+ title: "@mentions"
82
+ end
83
+
84
+ def timeline_options
85
+ {
86
+ tweet_mode: 'extended',
87
+ include_rts: opts.present?('with-retweets'),
88
+ count: opts.present?('count') ? opts['count'] : default_read_size
89
+ }
90
+ end
91
+
92
+ Pry.commands.add_command self
93
+ end
@@ -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)}: ", timeout: false
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,164 @@
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
+ o.on 'v', 'version', 'Print the version information of fry-send_tweet.rb'
29
+ end
30
+
31
+ def process(args)
32
+ super
33
+ return __print_version_information if opts.version?
34
+ tweet_creator = create_tweet_creator(compose_tweet_with_editor)
35
+ if opts.present?('delay')
36
+ time_obj, sleep_seconds = parse_duration_str(opts['delay'])
37
+ Thread.new {
38
+ sleep sleep_seconds
39
+ tweet_creator.call
40
+ }
41
+ publish_time = (time_obj ? time_obj : Time.now + sleep_seconds)
42
+ .getlocal
43
+ .strftime(time_format)
44
+ page_ok bold("Tweet will be published at approximately #{publish_time}.")
45
+ else
46
+ tweet = tweet_creator.call
47
+ page_ok tweet.url
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def paths_to_twitterio(paths)
54
+ paths.map do |path|
55
+ Pry::SendTweet::TwitterIO.new File.binread(path), File.basename(path)
56
+ end
57
+ end
58
+
59
+ def no_newline?
60
+ opts.present?('no-newline')
61
+ end
62
+
63
+ def send_tweet_with_media(tweet_contents, medias)
64
+ tweet = twitter.update_with_media(tweet_contents, medias, tweet_options)
65
+ tweet.tap {|t| self_destruct! t.id, opts['self-destruct'] }
66
+ ensure
67
+ medias.each(&:close)
68
+ end
69
+
70
+ def send_textual_tweet(tweet_contents)
71
+ tweet = twitter.update(tweet_contents, tweet_options)
72
+ tweet.tap {|t| self_destruct! t.id, opts['self-destruct'] }
73
+ end
74
+
75
+ def prepend_username_to_reply!(tweet_url, file)
76
+ tweet = twitter.status(tweet_url)
77
+ mentions = [tweet.user.screen_name].concat tweet.user_mentions.map(&:screen_name)
78
+ file.write mentions.uniq(&:downcase).map{|u| "@#{u}" }.join(' ')
79
+ end
80
+
81
+ def replying_to_other_tweet?
82
+ opts.present?('reply-to')
83
+ end
84
+
85
+ def tweet_options
86
+ options = {}
87
+ options.merge!({
88
+ in_reply_to_status_id: Integer(File.basename(opts['reply-to']))
89
+ }) if replying_to_other_tweet?
90
+ options
91
+ end
92
+
93
+ def compose_tweet_with_editor
94
+ tweet = Tempfile.open('pry-send_tweet--compose-tweet') do |file|
95
+ if replying_to_other_tweet?
96
+ # During experimentation I noticed that without prefixing who we are
97
+ # replying to, the tweet we send will not be considered a reply even
98
+ # with reply_to_status_id being set.
99
+ prepend_username_to_reply!(opts['reply-to'], file)
100
+ end
101
+ file.rewind
102
+ Pry::Editor.new(_pry_).invoke_editor(file.path, 0)
103
+ lines = file.read.each_line
104
+ no_newline? ? lines.map(&:chomp).join(' ') : lines.to_a.join
105
+ end
106
+ validate_tweet!(tweet)
107
+ tweet
108
+ end
109
+
110
+ def create_tweet_creator(tweet_contents)
111
+ if opts.present?(:file)
112
+ medias = paths_to_twitterio(opts[:file])
113
+ lambda { send_tweet_with_media(tweet_contents, medias) }
114
+ else
115
+ lambda { send_textual_tweet(tweet_contents) }
116
+ end
117
+ end
118
+
119
+ def validate_tweet!(tweet)
120
+ if tweet.strip.empty?
121
+ raise Pry::CommandError, "Can't post an empty tweet."
122
+ elsif tweet.size > MAX_TWEET_SIZE
123
+ raise Pry::CommandError, "The tweet: \n" +
124
+ word_wrap(tweet) +
125
+ "\nis too big to publish, try to use less characters."
126
+ end
127
+ end
128
+
129
+ def self_destruct!(tweet_id, duration)
130
+ return if !duration
131
+ _, sleep_seconds = parse_duration_str(duration)
132
+ page bold("Tweet due to self destruct in #{sleep_seconds} seconds")
133
+ Thread.new do
134
+ sleep sleep_seconds
135
+ twitter.destroy_status(tweet_id)
136
+ end
137
+ end
138
+
139
+ def parse_duration_str(str)
140
+ if str =~ /\A\d+\z/
141
+ sleep_seconds = Integer(str)
142
+ elsif str =~ /\A\d{2}:\d{2}\z/ || str =~ /\A\d{2}:\d{2}:\d{2}\z/
143
+ time_obj = Time.parse(str)
144
+ time_obj += 3600*24 if time_obj <= Time.now
145
+ sleep_seconds = Integer(time_obj - Time.now)
146
+ else
147
+ raise Pry::CommandError, "--delay='#{str}' or --self-destruct='#{str}' is not " \
148
+ "something I understand."
149
+ end
150
+ [time_obj, sleep_seconds]
151
+ end
152
+
153
+ def __print_version_information
154
+ side1 = [
155
+ bright_green('fry'),
156
+ bold('-send_tweet.rb '),
157
+ bright_red("v#{Fry::SendTweet::VERSION}")
158
+ ].join
159
+ side2 = bright_yellow(Fry::SendTweet::CODENAME)
160
+ _pry_.pager.page [side1, side2].join(" | ")
161
+ end
162
+
163
+ Pry.commands.add_command self
164
+ 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