-
Notifications
You must be signed in to change notification settings - Fork 5
/
wit.go
292 lines (250 loc) · 7.9 KB
/
wit.go
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
)
const WIT_VERSION = "20140510"
//WitHandler is am http request handler that looks for the "q" query parameter
//and sends it to Wit for processing.
func WitHandler(w http.ResponseWriter, r *http.Request) {
//read the "q" GET query parameter and pass it to
// the wit service
message := r.FormValue("q")
if len(message) > 0 {
intent, err := FetchIntent(message)
if err != nil {
log.Printf("Error: %+v", err)
} else {
ret := ProcessIntent(intent)
//print what we understood from your request to the browser.
msg := fmt.Sprintf("Turning light %v %s", ret.Arduino.Light, ret.Arduino.Action)
fmt.Fprintf(w, msg)
}
} else {
fmt.Fprintf(w, "Please add a /wit?q=<text here> to the url")
}
}
//FetchIntent is the whole go wit wrapper, if you call it that.
//We send the query string to wit, parse the result json
//into a struct and return it.
func FetchIntent(str string) (WitMessage, error) {
str, err := sanitizeQuerryString(str)
if err != nil {
return WitMessage{}, err
}
url := fmt.Sprintf("https://api.wit.ai/message?v=%s&q=%s", WIT_VERSION, str)
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.WitAccessToken))
res, err := client.Do(req)
if err != nil {
log.Fatalf("Requesting wit's api gave: %v", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Println("Something went really wrong with the response from Wit.ai")
errMsg := "Sorry, the machine learning service I use for my brain went down, @Diego: check the logs, there may be something for you there."
return WitMessage{}, errors.New(errMsg)
}
return ProcessWitResponse(res.Body), nil
}
func sanitizeQuerryString(str string) (string, error) {
if len(url.QueryEscape(str)) > 255 {
log.Println("Somebody talked too much, more than the 256 characters I can read.")
errMsg := "Sorry, I can only read up to 256 characters and I didn't want to just ignore the end of your message."
return "", errors.New(errMsg)
}
return url.QueryEscape(str), nil
}
//FetchVoiceIntent is like FetchIntent, but sends a wav file
// to the speech endpoint, Wit extracts the text from the sound file
//and then returns a json response with all the info we need.
func FetchVoiceIntent(filePath string) (WitMessage, error) {
log.Println("reading file")
body, err := ioutil.ReadFile(filePath)
if err != nil {
log.Printf("error: %v reading file", err)
}
if len(body) == 0 {
return WitMessage{}, errors.New("no sound in file")
}
url := "https://api.wit.ai/speech"
client := &http.Client{}
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.WitAccessToken))
req.Header.Add("Accept", fmt.Sprintf("application/vnd.wit.%s+json", WIT_VERSION))
req.Header.Add("Content-Type", "audio/wav")
log.Println("sending request")
res, err := client.Do(req)
defer res.Body.Close()
if err != nil {
log.Fatalf("Requesting wit's api gave: %v", err)
}
if res.StatusCode == 401 {
log.Fatalln("Access denied, check your wit access token ")
}
return ProcessWitResponse(res.Body), nil
}
//ProcessWitResponse gets the raw response from the http request, and
//returns a WitMessage with all the information we got from Wit
func ProcessWitResponse(message io.ReadCloser) WitMessage {
intent, _ := ioutil.ReadAll(message)
jsonString := string(intent[:])
_ = jsonString
var jsonResponse WitMessage
err := json.Unmarshal(intent, &jsonResponse)
if err != nil {
log.Println("error parsing json: ", err)
log.Printf("plain text json was %+v", jsonString)
}
var numbers []WitNumber
var number WitNumber
err = json.Unmarshal(jsonResponse.Outcome.Entities.RawGithub, &numbers)
if err != nil {
//log.Println("1 error parsing number json: ", err)
//log.Println("string number object is: ", string(jsonResponse.Outcome.Entities.RawGithub))
err = json.Unmarshal(jsonResponse.Outcome.Entities.RawGithub, &number)
if err != nil {
//log.Println("2 error parsing number json: ", err)
} else {
jsonResponse.Outcome.Entities.MultipleNumber = []WitNumber{number}
}
} else {
jsonResponse.Outcome.Entities.MultipleNumber = numbers
}
//log.Printf("a: %+v\n\n\n", jsonResponse)
//log.Printf("b: %+v\n\n\n", jsonString)
return jsonResponse
}
//ProcessIntent gets the json parsed result from wit.ai and
//depending on the intent, it calles the right service.
//So far we only have one service, the Arduino lights service
func ProcessIntent(jsonResponse WitMessage) WitResponse {
switch jsonResponse.Outcome.Intent {
case "lights":
for _, row := range jsonResponse.Outcome.Entities.MultipleNumber {
light := row.Value
action := jsonResponse.Outcome.Entities.OnOff.Value
Arduino(action, light)
msg := fmt.Sprintf("Turning light %v %s", light, action)
_ = msg
return WitResponse{
WitArduinoResponse{light, action},
WitTemperatureResponse{},
WitGithubResponse{},
witError{},
}
}
case "temperature":
unit := jsonResponse.Outcome.Entities.Temperature.Value.Unit
temperature := jsonResponse.Outcome.Entities.Temperature.Value.Temperature
return WitResponse{
WitArduinoResponse{},
WitTemperatureResponse{unit, temperature},
WitGithubResponse{},
witError{},
}
case "github":
var issues []int
for _, row := range jsonResponse.Outcome.Entities.MultipleNumber {
//log.Printf("1\n\n%+v\n\n", len(jsonResponse.Outcome.Entities.MultipleNumber))
//log.Printf("2\n\n%+v\n\n", row)
issues = append(issues, row.Value)
}
return WitResponse{
WitArduinoResponse{},
WitTemperatureResponse{},
WitGithubResponse{issues},
witError{},
}
}
return WitResponse{}
}
//These make up the different parts of the wit result
//There are more options, but I'm using only these so far.
//WitMessage represents the payload we get from Wit as a response to processing
//the text or voice file we sent.
type WitMessage struct {
MsgID string `json:"msg_id"`
MsgBody string `json:"msg_body"`
Outcome WitMessageOutcome
}
//WitMessageOutcome gives you the Intent, the entities and a confidence value
//which you can use to discard or accept the intent we get from Wit.
//Lower than 0.8 means they are not very accurate.
type WitMessageOutcome struct {
Intent string
Entities WitMessageEntities
Confidence float64
}
//WitMessageEntities contains all the possible entities we process from Wit
type WitMessageEntities struct {
Location WitLocation
OnOff WitOnOff
RawGithub json.RawMessage `json:"github_issue"`
MultipleNumber []WitNumber
SingleNumber WitNumber `json:"number"`
Temperature WitTemperature
}
//WitLocation is the Location entity
type WitLocation struct {
End int
Start int
Value string
Body string
Suggested bool
}
//WitOnOff is the on_off entity
type WitOnOff struct {
Value string
}
//WitNumber is the wit/number entity
type WitNumber struct {
End int
Start int
Value int
Body string
}
//WitTemperature gives you the numeric value plus the units as a string
type WitTemperature struct {
End int
Start int
Value WitTemperatureValue
Body string
}
//WitTemperatureValue is the actual value and unit
type WitTemperatureValue struct {
Unit string
Temperature int
}
//WitResponse holds just the information you need to act on each intent
type WitResponse struct {
Arduino WitArduinoResponse
Temperature WitTemperatureResponse
Github WitGithubResponse
Error witError
}
//WitArduinoResponse gives you the light number and a string representing on/off for the light number
type WitArduinoResponse struct {
Light int
Action string
}
//WitTemperatureResponse gives you the Unit and degrees
type WitTemperatureResponse struct {
Unit string
Degrees int
}
//WitGithubResponse gives you a slice of issue numbers
type WitGithubResponse struct {
issues []int
}
type witError struct {
msg string
}