diff --git a/lgtm_hd.gemspec b/lgtm_hd.gemspec index f9ad35a..96ae4c3 100644 --- a/lgtm_hd.gemspec +++ b/lgtm_hd.gemspec @@ -11,8 +11,8 @@ Gem::Specification.new do |spec| spec.authors = ["Huy Dinh"] spec.email = ["phradion@gmail.com"] - spec.summary = %q{Generating LGTM image to clipboard.} - spec.description = %q{Generating images from user input with LGTM text on it, or fetching images from LGTM.in based on user's query. Finally put the image to clipboard.} + spec.summary = %q{Generate LGTM image from a source image and copy to clipboard.} + spec.description = %q{Generate LGTM image from URL or file with smart text colors and positions. Support direct clipboard paste to github's comment box or Slack on MacOSX.} spec.homepage = "http://github.com/phradion/lgtm_hd" spec.license = "MIT" @@ -34,7 +34,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "3.6.0" spec.add_development_dependency "simplecov" + spec.add_development_dependency "pry" spec.add_development_dependency "codeclimate-test-reporter", "~> 1.0.0" + spec.add_runtime_dependency "open_uri_redirections" spec.add_runtime_dependency "commander", "4.4.3" spec.add_runtime_dependency "clipboard", "1.1.1" spec.add_runtime_dependency "os", "1.0.0" diff --git a/lib/lgtm_hd.rb b/lib/lgtm_hd.rb index 0394712..8b5790b 100644 --- a/lib/lgtm_hd.rb +++ b/lib/lgtm_hd.rb @@ -1,5 +1,6 @@ require "lgtm_hd/version" require "lgtm_hd/configuration" require "lgtm_hd/utilities" +require "lgtm_hd/lgtmdotin" require "lgtm_hd/meme_generator" require "lgtm_hd/cli" diff --git a/lib/lgtm_hd/cli.rb b/lib/lgtm_hd/cli.rb index ed46ba9..7aa66c0 100644 --- a/lib/lgtm_hd/cli.rb +++ b/lib/lgtm_hd/cli.rb @@ -2,7 +2,10 @@ require 'commander' require 'os' require 'clipboard' +require 'net/http' +require 'open-uri' require 'uri' +require 'pry' module LgtmHD class CLI @@ -12,80 +15,116 @@ def run program :name, LgtmHD::Configuration::PROGRAM_NAME program :version, LgtmHD::VERSION program :description, LgtmHD::Configuration::DESCRIPTION + default_command :transform - command :transform do |c| - c.syntax = 'lgtm_hd [--clipboard] [--interactive]' - c.summary = 'Generate a LGTM image from source_uri (local path or URL) into output folder' + global_option '-c', '--clipboard', 'Copy the end result (LGTM image) to OS\'s clipboard for direct pasting to other programs' + global_option '-i', '--interactive', 'Turn on interactive Mode. In case you forgot all these super complexive args and options' do say "-- LGTM HD Interactive Mode --" end + + command :random do |c| + c.syntax = 'lgtm_hd random [--clipboard -c] [--interactive -i]'.freeze + c.summary = 'Fetch random images from LGTM.in and put into destination directory'.freeze c.description = '' - c.example '', 'lgtm_hd export http://domain.com/image.png /path/to/lgtm.png' - c.option '--clipboard', 'Copy the end result (LGTM image) to OS\'s clipboard' - c.option '--interactive', 'In case you forgot all these super complexive args and options' + c.example 'Example', 'lgtm_hd random -c' c.action do |args, options| - to_clipboard = options.clipboard + if options.interactive # Interactive mode! + input_dir = ask('[?] Destination Directory: ') + options.clipboard ||= agree("[?] Copy LGTM image to clipboard afterward? [Y/N]") + end + dest_dir = CLI.format_destination_dir(input_dir) + dest_file_prefix = CLI.format_destination_file_prefix + say "\\ Fetching random image from lgtm.in" + dest_uri,image_markdown = LgtmDotIn.fetch_random_image(dest_dir,dest_file_prefix) do |url, markdown| + say "\\ Loading image at #{url}" + end + say "\\ Exported image to #{dest_uri}" + if options.clipboard + copy_file_to_clipboard(dest_uri) + say "\\ Or you can copy the markdown format by lgtm.in directly below\n\n#{image_markdown}" + end + end + end + + command :transform do |c| + c.syntax = 'lgtm_hd [--clipboard|-c] [--interactive|-i]'.freeze + c.summary = 'Generate a LGTM image from source_uri (local path or URL) into output folder'.freeze + c.description = '' + c.example 'Example', 'lgtm_hd transform http://domain.com/image.png /Users/lgtm/' + + c.action do |args, options| # ARGS validation! - if args.length == 2 + if args.length >= 2 source_uri = args[0] - output_uri = args[1] + dest_dir = args[1] elsif options.interactive # Interactive mode! - say "-- LGTM Interactive mode --" - source_uri = ask('Source (URL or Path/to/file): ') - output_uri = ask('Output Folder: ') - to_clipboard = agree("Copy to clipboard afterward? [Y/N]") + source_uri = ask('Source Image (URL or Path/to/file): ') + dest_dir = ask('Destination Directory: ') + options.clipboard ||= agree("Copy exported image to clipboard afterward? [Y/N]") else - say "usage: lgtm_hd [--clipboard] [--interactive]" - raise ArgumentError, "Too few or too many arguments provided, need 2: source and output URIs" + say "usage: lgtm_hd [--clipboard] [--interactive]" + raise ArgumentError, "Too few arguments provided. Need to provide and " end - # Validate the inputs - output_folder = File.expand_path(output_uri) - output_file = File.join(output_folder, - LgtmHD::Configuration::OUTPUT_PREFIX + - Time.now.strftime('%Y-%m-%d_%H-%M-%S') + - File.extname(source_uri)) - raise "Source is not proper URIs (URL or Path/to/file)" unless source_uri =~ URI::regexp || File.exist?(source_uri) - raise "Output is invalid path or directory" unless File.exist?(output_folder) && File.directory?(output_folder) - - + dest_file = CLI.format_destination_uri(source_uri, dest_uri) + CLI.check_uris(dest_dir, source_uri) # Do stuff with our LGTM meme say "- Reading and inspecting source" - meme_generator = MemeGenerator.new(input_image_uri:source_uri, output_image_uri:output_file) + meme_generator = MemeGenerator.new(input_image_uri:source_uri, output_image_uri:dest_file) say "- Rendering output" meme_generator.draw # Export and play around with the clipboard say "- exporting to file" meme_generator.export do |output| - say "LGTM image has been generated at #{output}." - if to_clipboard then - if OS.mac? then - applescript "set the clipboard to (read (POSIX file \"#{output}\") as GIF picture)" - say "I see you are using MacOSX. Content of the file has been copied to your clipboard." - - # Apple Script Command reference - # Sample: `osascript -e 'set the clipboard to (read (POSIX file "#{output}") as JPEG picture)'` - # - # Currently Github allow pasting image directly to comment box. - # However it does not support pure text content produced by pbcopy so we have to use direct Applescript - # No Universal solution as for now. - # - # Apple Script reference: http://www.macosxautomation.com/applescript/imageevents/08.html - else - Clipboard.copy(output) - say "Path to LGTM file has been copied to your clipboard." - end + say "- Exported LGTM image to #{output}." + if options.clipboard then + CLI.copy_file_to_clipboard(output_file) end # end of if to_clipboard end # end of meme_generator.export block + end # end of action + end # end of command transform - end + run! + end # end run def + + private + + def copy_file_to_clipboard(output_file) + if OS.mac? then + applescript "set the clipboard to (read (POSIX file \"#{output_file}\") as GIF picture)" + say "\\ Copied file to OS's clipboard for direct pasting to Github comments or Slack" + # Apple Script Command reference + # Sample: `osascript -e 'set the clipboard to (read (POSIX file "#{output}") as JPEG picture)'` + # + # Currently Github allow pasting image directly to comment box. + # However it does not support pure text content produced by pbcopy so we have to use direct Applescript + # No Universal solution as for now. + # + # Apple Script reference: http://www.macosxautomation.com/applescript/imageevents/08.html + else + Clipboard.copy(output_file) + say "\\ Copied file's path to OS's clipboard" end + end - run! + def self.check_uris(dest_dir, source_uri = nil) + raise "Source is not proper URIs (URL or Path/to/file)" unless source_uri =~ URI::regexp || File.exist?(source_uri) + if source_uri then return end + raise "Output is invalid path or directory" unless File.exist?(dest_dir) && File.directory?(dest_dir) + end + + def self.format_destination_dir(dest_dir) + dest_dir ||= LgtmHD::Configuration::OUTPUT_PATH_DEFAULT + File.expand_path(dest_dir) + end + def self.format_destination_file_prefix + LgtmHD::Configuration::OUTPUT_PREFIX + Time.now.strftime('%Y%m%d%H%M%S') end - end -end + + end # end of Class +end # end of Module diff --git a/lib/lgtm_hd/configuration.rb b/lib/lgtm_hd/configuration.rb index 6526b44..f8a1471 100644 --- a/lib/lgtm_hd/configuration.rb +++ b/lib/lgtm_hd/configuration.rb @@ -2,9 +2,14 @@ module LgtmHD module Configuration # Program configurations PROGRAM_NAME = "lgtm_hd".freeze + AUTHOR = "Huy Dinh ".freeze DESCRIPTION = "Generating images from user input with LGTM text on it, or fetching images from LGTM.in based on user's query. Finally put the image to clipboard.".freeze # Output Image configurations + OUTPUT_PATH_DEFAULT = "/tmp".freeze + if (File.exist?("~/Desktop")) + OUTPUT_PATH_DEFAULT = File.expand("~/Desktop").freeze + end OUTPUT_PREFIX = "lgtm_hd_".freeze OUTPUT_MAX_WIDTH = 500.freeze OUTPUT_MAX_HEIGHT = 500.freeze diff --git a/lib/lgtm_hd/lgtmdotin.rb b/lib/lgtm_hd/lgtmdotin.rb new file mode 100644 index 0000000..2328a8f --- /dev/null +++ b/lib/lgtm_hd/lgtmdotin.rb @@ -0,0 +1,57 @@ +require 'resolv-replace' +require 'open-uri' + +module LgtmHD + class LgtmDotIn + API_STARTING_ENDPOINT = "http://www.lgtm.in/g".freeze + ACTUAL_IMAGE_URL_USE_SSL = false + TRY_FETCHING_IMAGE_LIMIT = 3 + TRY_FETCHING_META_LIMIT = 3 + + def self.fetch_random_image(dest_path = nil, file_prefix = nil) + # LGTM.in has so many broken images + # So we loop until a good image is found + limit = TRY_FETCHING_IMAGE_LIMIT + begin + json_data = fetch_meta_data + image_url = json_data["actualImageUrl"] + image_markdown = json_data["markdown"] + yield image_url, image_markdown + + # fetching image data + dest_file = File.join(dest_path ||= '/tmp', (file_prefix ||= 'lgtmdotin_') + File.extname(image_url)) + uri = URI.parse(image_url) + + uri.open(redirect: false, ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) do |input_stream| + File.open(dest_file, 'wb') do |output_stream| + IO.copy_stream(input_stream, output_stream) + end + end + [dest_file, image_markdown] + rescue OpenURI::HTTPError, SocketError, Net::ReadTimeout => error + retry if (limit -= 1) > 0 + raise error, "We have tried 3 times but all images are broken. Either LGTM.in is trash or you are super unlucky" + end + end + + + private + def self.fetch_meta_data(uri = API_STARTING_ENDPOINT, limit = TRY_FETCHING_META_LIMIT) + uri = URI.parse(API_STARTING_ENDPOINT) + ## + # LGTM.in has a JSON endpoint that + # .forwards to an SSL HTTP address that + # .has no valid SSL certificate + # Hence the loop + # + begin + data = uri.open('Accept' => 'application/json', redirect: false, ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) + json = JSON.parse data.readlines.join("") + rescue OpenURI::HTTPRedirect => redirect + uri = redirect.uri # assigned from the "Location" response header + retry if (limit -= 1) > 0 + raise IOError, "There maybe a network issue. The program failed to contact LGTM.in JSON API" + end + end + end +end diff --git a/lib/lgtm_hd/meme_generator.rb b/lib/lgtm_hd/meme_generator.rb index 7c07fe4..1307a92 100644 --- a/lib/lgtm_hd/meme_generator.rb +++ b/lib/lgtm_hd/meme_generator.rb @@ -2,7 +2,8 @@ module LgtmHD class MemeGenerator - + @@caption_positions = {caption_position_top: "north center", caption_position_bottom: "south center"} + # TODO make options list for this class # TODO pass BLOB data into this class instead of paths def initialize(input_image_uri:, output_image_uri:) @@ -54,11 +55,8 @@ def image_max_size # Default value is top # def caption_position - if not [:caption_position_top, :caption_position_bottom].include? (@caption_position) - @caption_position = :caption_position_bottom - end - return {:caption_position_top => "north center", - :caption_position_bottom => "south center"}[@caption_position] + @caption_position = :caption_position_bottom unless [:caption_position_top, :caption_position_bottom].include? @caption_position + @@caption_positions[@caption_position] end def caption_font diff --git a/lib/lgtm_hd/utilities.rb b/lib/lgtm_hd/utilities.rb index f0b7d8b..c62c6e3 100644 --- a/lib/lgtm_hd/utilities.rb +++ b/lib/lgtm_hd/utilities.rb @@ -1,3 +1,6 @@ +require 'open-uri' +require 'net/http' + module LgtmHD def self.gem_root File.expand_path '../../..', __FILE__ diff --git a/lib/lgtm_hd/version.rb b/lib/lgtm_hd/version.rb index cfb19ca..1c85bb0 100644 --- a/lib/lgtm_hd/version.rb +++ b/lib/lgtm_hd/version.rb @@ -1,3 +1,3 @@ module LgtmHD - VERSION = "0.1.4".freeze + VERSION = "0.1.5".freeze end