diff --git a/.gitignore b/.gitignore index e43b0f9..d305b18 100755 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .DS_Store + +config.rb \ No newline at end of file diff --git a/README.md b/README.md index 5868a9a..785e8c6 100755 --- a/README.md +++ b/README.md @@ -153,6 +153,29 @@ The following is a non-exhaustive list of things you can do: betty turn web on betty please tell me what is the weather like in London + Stackoverflow + betty so how to post json data with curl + betty stackoverflow get local time in command line + betty stack open the browser from the console in linux + +Stackoverflow +------------- + +You can automate the following process with Betty: + +1. Search in Google a technical question (Google is good at natural language processing!) +2. Fetch and display the first Stackoverflow result + +Unfortunately, due to API restrictions, an API Key needs to be activated. To get one, here are the steps: +1. Go to [the Google Console](https://console.developers.google.com/project) +2. Create a project +3. Activate [Google Custom Search](https://console.developers.google.com/project/apps~/apiui/api) +4. Go to [Credentials](https://console.developers.google.com/project/apps~/apiui/credential) +5. Click on `CREATE NEW KEY` button and get your API Key +6. Execute `cp config.rb.example config.rb` and add your API key there : `vim config.rb`. + +All set + Contributing ------------ diff --git a/config.rb.example b/config.rb.example new file mode 100644 index 0000000..ca2cbb9 --- /dev/null +++ b/config.rb.example @@ -0,0 +1,3 @@ +module ApiConfig + GOOGLE_SEARCH_API_KEY = "AIzaSyCxPfXTDuuTjBCTmDlQB5HJsbKfANFlwso" +end \ No newline at end of file diff --git a/lib/stackoverflow.rb b/lib/stackoverflow.rb new file mode 100644 index 0000000..a147fea --- /dev/null +++ b/lib/stackoverflow.rb @@ -0,0 +1,115 @@ +module Stackoverflow + require 'cgi' + + def self.interpret(command) + responses = [] + matches = command.match(/^(so|stackoverflow|stack)\s+(.+)/) + + if matches + return responses if !require(File.expand_path("../../config.rb", __FILE__)) || !defined? ApiConfig || !defined? ApiConfig::GOOGLE_SEARCH_API_KEY || ApiConfig::GOOGLE_SEARCH_API_KEY.nil? || ApiConfig::GOOGLE_SEARCH_API_KEY == "" + + query = matches[-1] + google_results = get_remote_json("https://www.googleapis.com/customsearch/v1?cx=012211726421152102993%3Autn0onfqvdc&key=#{ApiConfig::GOOGLE_SEARCH_API_KEY}", {:q => query})['items'] + stackoverflow_answers = google_results.select{ |result| result['link'].start_with?("http://stackoverflow.com/questions")} + return responses if Array(stackoverflow_answers).length == 0 + + links_array = stackoverflow_answers[0]['link'].split('/') + id = links_array[links_array.index('questions')+1].to_i + + return responses if id == 0 + + stackoverflow_result = get_remote_json("https://api.stackexchange.com/2.2/questions/#{id}?order=desc&sort=votes&site=stackoverflow&filter=!ay7uLWNahtxLpA") + + responses << { + :command => "open #{stackoverflow_result['items'][0]['link']}", + :explanation => handle_stackoverflow_data(stackoverflow_result['items'][0]), + :ask_first => true + } + end + + responses + end + + def self.help + commands = [] + commands << { + :category => "Stackoverflow", + :description => "Fetch stackoverflow responses. \nNeeds an API key: please execute cp #{File.expand_path("../../config.rb", __FILE__)}.example #{File.expand_path("../../config.rb", __FILE__)}\n\nOr get your own api key there: ", + :usage => ["so how to post json data with curl", + "stackoverflow get local time in command line", + "stack open the browser from the console in linux"] + } + commands + end + + private + + def self.handle_stackoverflow_data(data) + "\n"+ + "-----------------------\n"+ + "Stackoverflow question:\n"+ + "-----------------------\n\n"+ + data['title'].bold + + "\n\n"+ + code_in_bold(CGI.unescapeHTML(data['body_markdown']))+ + "\n\n"+ + "------------\n"+ + "Best Answer:\n"+ + "------------\n\n"+ + code_in_bold(CGI.unescapeHTML(data['answers'][0]['body_markdown'])) + end + + def self.code_in_bold(phrase) + phrase.split("\n").map do |line| + if line.start_with?(' ') + line.bold + else + unquoted = true + line.split('`').map{ |string| if unquoted then string else string.bold; unquoted ^= true end}.join('') + end + end.join("\n") + end + + def self.get_remote_json(url, params={}) + require 'uri' + require 'net/http' + require "json" + uri = URI(url) + if !params.empty? + if uri.query.nil? || uri.query == "" + uri.query = URI.encode_www_form(params) + else + uri.query += "&"+URI.encode_www_form(params) + end + end + + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = uri.scheme == 'https' + res = http.request(Net::HTTP::Get.new(uri.request_uri)) + + case res + when Net::HTTPSuccess then + begin + if res.header[ 'Content-Encoding' ].eql?('gzip') then + # puts "Performing gzip decompression for response body." if debug_mode + sio = StringIO.new(res.body) + gz = Zlib::GzipReader.new(sio) + content = gz.read() + # puts "Finished decompressing gzipped response body." if debug_mode + else + # puts "Page is not compressed. Using text response body. " if debug_mode + content = res.body + end + rescue Exception + puts "Error occurred (#{$!.message})" + # handle errors + raise $!.message + end + end + + return JSON.parse(content) + end + +end + +$executors << Stackoverflow diff --git a/main.rb b/main.rb index 77fdce2..ad372ec 100755 --- a/main.rb +++ b/main.rb @@ -6,7 +6,7 @@ $executors = [] $LOG = Logger.new(File.open(ENV['HOME'] + '/.betty_history', 'a+')) -Dir[File.dirname(__FILE__) + '/lib/*.rb'].each {|file| +(Dir[File.dirname(__FILE__) + '/lib/*.rb']+Dir[File.dirname(__FILE__) + '/utils/*.rb']).each {|file| begin require file rescue Exception => e diff --git a/utils/string.rb b/utils/string.rb new file mode 100644 index 0000000..f2fd6cd --- /dev/null +++ b/utils/string.rb @@ -0,0 +1,20 @@ +class String + def black; "\033[30m#{self}\033[0m" end + def red; "\033[31m#{self}\033[0m" end + def green; "\033[32m#{self}\033[0m" end + def brown; "\033[33m#{self}\033[0m" end + def blue; "\033[34m#{self}\033[0m" end + def magenta; "\033[35m#{self}\033[0m" end + def cyan; "\033[36m#{self}\033[0m" end + def gray; "\033[37m#{self}\033[0m" end + def bg_black; "\033[40m#{self}\0330m" end + def bg_red; "\033[41m#{self}\033[0m" end + def bg_green; "\033[42m#{self}\033[0m" end + def bg_brown; "\033[43m#{self}\033[0m" end + def bg_blue; "\033[44m#{self}\033[0m" end + def bg_magenta; "\033[45m#{self}\033[0m" end + def bg_cyan; "\033[46m#{self}\033[0m" end + def bg_gray; "\033[47m#{self}\033[0m" end + def bold; "\033[1m#{self}\033[22m" end + def reverse_color; "\033[7m#{self}\033[27m" end +end \ No newline at end of file