-
Notifications
You must be signed in to change notification settings - Fork 215
/
main.rb
executable file
·258 lines (228 loc) · 7.03 KB
/
main.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#!/usr/bin/env ruby
require 'logger'
$URL = 'https://github.com/pickhardt/betty'
$VERSION = '0.1.7'
$executors = []
$LOG = Logger.new(File.open(ENV['HOME'] + '/.betty_history', 'a+'))
Dir[File.dirname(__FILE__) + '/lib/*.rb'].each {|file|
begin
require file
rescue Exception => e
puts "Module #{file} could not be loaded because of #{e.message.split('\n')[0]}"
end
}
BettyConfig.initialize
def get_input_integer(min, max, options={})
while true
input = STDIN.gets.strip
if options[:allow_no] && ['n', 'no'].include?(input.downcase)
return false
end
input_integer = input.to_i
if input_integer.to_s == input && min <= input_integer && input_integer <= max
return input_integer
end
say "Sorry, please enter an integer between #{ min } and #{ max }#{ options[:allow_no] ? ', or N for no' : '' }."
end
end
def get_input_y_n
while true
case STDIN.gets.strip.downcase
when 'y', 'yes'
return true
when 'n', 'no'
return false
end
say "Sorry, please enter Y for Yes or N for No."
end
end
def interpret(command)
responses = []
$executors.each do |executor|
executors_responses = executor.interpret(command)
responses = responses.concat(executors_responses)
if executors_responses.length == 1 and executors_responses[0][:command]
$LOG.info('main_interpret') {"#{command} ==> #{executor.name} ==> #{executors_responses[0][:command]}"}
end
end
responses
end
def help(command)
# betty --help find
# betty I need help with the find command
command = command.sub("I need","").sub("help","").sub("me","").sub("with","").sub("the","").sub("command","").sub("--","").strip
responses = []
$executors.each do |executor|
responses = responses.concat(executor.help)
end
if command != ""
command = command.strip
response = responses.detect{|h| h[:category].downcase == "#{ command.split.first.downcase }"}
if response
say "I can do that if you help me. Check out the following examples"
# say "#{ response[:usage] }", :no_name => true
response[:usage].each do |usage|
say usage, :no_name => true
end
say "Please note: I am case sensitive. Watch out for my feelings...", :no_name => true
else
say "I don't understand. Hopefully someone will make a pull request so that one day I will understand."
#puts "I do know how to\n" + responses.map{|x| x[:category] + ":\t\t" + x[:usage].sample }.join("\n")
end
else
say "What can I help you with?"
end
end
def run(response)
if response[:call_before]
response[:call_before].call
end
if response[:say]
say response[:say]
end
if response[:call]
response[:call].call
end
if response[:command]
command=response[:command]
say "Running #{ command }" if not command.match(/^echo/)
res = `#{command}`
puts res
if BettyConfig.get("speech")
speak(res)
end
end
end
def sanitize(text)
begin
color = text.match(/\\(\S+)/)[1]
replace_with = color[7..-8]
text = text.gsub(color, replace_with)
rescue
end
text = text.split(/ [+-]/)[0]
text = text.gsub(' ', '%20')
text = text.gsub('\\', '')
return text
end
def speak(text)
if User.has_command?('say')
say = 'say'
voice=BettyConfig.get("voice")
say += " -v '#{voice}'" if voice
system("#{say} \"#{ text }\"") # formerly mpg123 -q
else
has_afplay = User.has_command?('afplay')
has_mpg123 = User.has_command?('mpg123')
has_spd_say = User.has_command?('spd-say')
if Internet.connection_enable?
if has_afplay || has_mpg123
require 'open-uri'
text = sanitize(text)
speech_path = '/tmp/betty_speech.mp3'
if text != ''
url = 'http://translate.google.com/translate_tts?tl=en&q=' + text
open(speech_path, 'wb') do |file|
file << open(url).read
end
if has_afplay
system("afplay #{ speech_path }")
elsif has_mpg123
system("mpg123 -q #{ speech_path }")
end
system("rm #{ speech_path }")
end
else
if has_spd_say
system("spd-say -t female2 -m some -r 5 -p -25 -s \"#{text}\"")
end
end
else
if has_spd_say
system("spd-say -t female2 -m some -r 5 -p -25 -s \"#{text}\"")
end
end
end
end
# needs BettyConfig.get("web")!="false"
# edit ~/.bettyconfig or say 'use web'
# example: "betty what is the weather"
def web_query(command)
require 'net/http'
encoded = URI.escape(command)
chatmode = BettyConfig.get("chat")
web_service = "https://ask.pannous.com"
path = "/api?"
path += "input=#{ encoded }"
path += "&timeZone="+Time.now.zone
path += "&exclude=ChatBot,Dialogues" if not chatmode
begin
require 'json'
rescue
path += "&out=simple" #no json, just text
end
# puts web_service+path
url = URI.parse(web_service)
req = Net::HTTP::Get.new(path)
begin
puts "Asking the internet..." if not chatmode
puts "Thinking..." if chatmode
res = Net::HTTP.start(url.host, url.port, :use_ssl => true, :read_timeout => 5) {|https|
https.request(req)
}
answer=res.body
return "web api error" if answer.match /^</
return answer if not answer.match /^\{/ # no json, just text
json=JSON.parse answer
actions=json['output'][0]['actions']
url=actions['open']['url'] rescue nil
image=actions['show']['images'][0] rescue nil
`open #{url}` if url and BettyConfig.get("web")
`open #{image}` if image and BettyConfig.get("web") # or ascii-art ;}
return actions['say']['text']
rescue Exception =>e
puts $!
"error querying web service #{e}"
end
end
def say(phrase, options={})
my_name = BettyConfig.get("name")
puts "#{ options[:no_name] ? '' : my_name + ': ' }#{ phrase }"
if BettyConfig.get("speech")
speak(phrase)
end
end
def main(commands)
command = commands.join(' ')
responses = interpret(command)
if responses.length == 1
response = responses[0]
if response[:ask_first]
say "I found the command '#{ responses[0][:command] }'"
say " #{ responses[0][:explanation] }", :no_name => true
say "Do you want me to run this? Y/n"
if get_input_y_n
run(response)
end
else
run(response)
end
elsif responses.length > 1
say "Okay, I have multiple ways to respond."
say "Enter the number of the command you want me to run, or N (no) if you don't want me to run any."
responses.each_with_index do |response, index|
say "[#{ index + 1 }] #{ response[:command] }", :no_name => true
say(" #{ response[:explanation] }", :no_name => true) if response[:explanation]
end
which_to_run = get_input_integer(1, responses.length, :allow_no => true)
run(responses[which_to_run - 1]) if which_to_run
else
# edit ~/.bettyconfig or say 'use web'
if BettyConfig.get("web") && !command.empty? && !command.match("help")
say web_query(command).sub("Jeannie",BettyConfig.get("name"))
else
help(command)
end
end
end
main(ARGV)