Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.DS_Store

config.rb
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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~<PROJECT_ID>/apiui/api)
4. Go to [Credentials](https://console.developers.google.com/project/apps~<PROJECT_ID>/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
------------

Expand Down
3 changes: 3 additions & 0 deletions config.rb.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module ApiConfig
GOOGLE_SEARCH_API_KEY = "AIzaSyCxPfXTDuuTjBCTmDlQB5HJsbKfANFlwso"
end
115 changes: 115 additions & 0 deletions lib/stackoverflow.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions utils/string.rb
Original file line number Diff line number Diff line change
@@ -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