From 318a3f8d16437ca18f5959ed6041535550d19ac2 Mon Sep 17 00:00:00 2001 From: Hamcha Date: Tue, 9 May 2017 13:34:52 +0200 Subject: [PATCH] mods: Add api.ai module (replaces wit.ai WIP module) --- mods/main.go | 13 ++-- mods/talk.go | 176 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 143 insertions(+), 46 deletions(-) diff --git a/mods/main.go b/mods/main.go index fd5f996..3d83613 100644 --- a/mods/main.go +++ b/mods/main.go @@ -43,11 +43,10 @@ var mods = map[string]Mod{ OnInit: initproverbio, OnMessage: proverbio, }, - /* - "talk": { - OnInit: inittalk, - OnMessage: talk, - },*/ + "talk": { + OnInit: inittalk, + OnMessage: talk, + }, "stt": { OnInit: initstt, OnMessage: stt, @@ -96,7 +95,7 @@ var impact *string var gillmt *string var sourcesans *string var proverbi *string -var wittoken *string +var talktoken *string var gapifile *string func main() { @@ -108,7 +107,7 @@ func main() { macropath = flag.String("macropath", "macros.json", "Path to macros db (JSON)") remindpath = flag.String("remindpath", "reminders.json", "Path to reminder db (JSON)") proverbi = flag.String("proverbi", "proverbi.txt", "Path to proverbi pairs (separated by /)") - wittoken = flag.String("wit", "", "Wit.ai token") + talktoken = flag.String("apiai", "", "api.ai token") gapifile = flag.String("gapi", "gapi.json", "Google API Service Credentials file") disable := flag.String("disable", "", "Blacklist mods (separated by comma)") enable := flag.String("enable", "", "Whitelist mods (separated by comma)") diff --git a/mods/talk.go b/mods/talk.go index 0dccee5..5b2b910 100644 --- a/mods/talk.go +++ b/mods/talk.go @@ -1,58 +1,156 @@ package main import ( - "errors" + "bytes" + "encoding/json" "fmt" + "log" + "net/http" + "strconv" + "strings" + "time" "github.com/hamcha/clessy/tg" - "github.com/kurrik/witgo/v1/witgo" ) -type ClessyHandler struct { +type QRequest struct { + SessionID string `json:"sessionId"` + Query string `json:"query"` + Language string `json:"lang"` } -var wit *witgo.Witgo -var witHandler ClessyHandler +type QResponse struct { + ID string `json:"id"` + Timestamp time.Time `json:"timestamp"` + Result struct { + Source string `json:"source"` + ResolvedQuery string `json:"resolvedQuery"` + Action string `json:"action"` + ActionIncomplete bool `json:"actionIncomplete"` + Parameters struct { + Name string `json:"name"` + } `json:"parameters"` + Contexts []struct { + Name string `json:"name"` + Parameters struct { + Name string `json:"name"` + } `json:"parameters"` + Lifespan int `json:"lifespan"` + } `json:"contexts"` + Metadata struct { + IntentID string `json:"intentId"` + IntentName string `json:"intentName"` + } `json:"metadata"` + Fulfillment struct { + Speech string `json:"speech"` + } `json:"fulfillment"` + } `json:"result"` + Status struct { + Code int `json:"code"` + ErrorType string `json:"errorType"` + } `json:"status"` +} + +const talkBaseURL = "https://api.api.ai/v1" func inittalk() { - // Disable if token is not provided - if *wittoken == "" { - panic(errors.New("No or empty wit token provided (-wit)")) + if *talktoken == "" { + panic(fmt.Errorf("API token for api.ai must be provided! (provide it or --disable talk)")) } - - witgo.NewWitgo(witgo.NewClient(*wittoken), &witHandler) } func talk(broker *tg.Broker, update tg.APIMessage) { -} - -func (h *ClessyHandler) Action(session *witgo.Session, action string) (response *witgo.Session, err error) { - response = session - response.Context.Set("forecast", "sunny") - return -} - -func (h *ClessyHandler) Say(session *witgo.Session, msg string) (response *witgo.Session, err error) { - response = session - fmt.Printf("< %v\n", msg) - return -} - -func (h *ClessyHandler) Merge(session *witgo.Session, entities witgo.EntityMap) (response *witgo.Session, err error) { - var ( - value string - ) - response = session - if value, err = entities.FirstEntityValue("location"); err != nil { - response.Context = witgo.Context{} - err = nil - } else { - response.Context.Merge(witgo.Context{ - "loc": value, - }) + // Must be a text message + if update.Text == nil { + return } - return -} + text := *(update.Text) -func (h *ClessyHandler) Error(session *witgo.Session, msg string) { + // Make sure it's aimed at Clessy + if strings.Index(text, "@"+*botname) >= 0 { + // @maudbot + text = strings.Replace(text, "@"+*botname, "", 1) + } else if idx := strings.Index(strings.ToLower(text), "clessy"); idx == 0 && len(text) > 7 { + // Clessy, + text = strings.TrimLeft(text[len("clessy"):], ":,") + } else if text[0] != '/' && update.ReplyTo != nil && update.ReplyTo.User.Username == *botname { + // Reply to Clessy (previous prompt?) which is not a command (such as unsplash), pass + } else if update.Chat.Username != nil { + // Private chat, pass + } else { + return + } + text = strings.TrimSpace(text) + + // Generate unique id for user + id := strconv.FormatInt(update.User.UserID, 36) + if len(id) > 36 { + id = id[:36] + } + + // Create POST body + data, err := json.Marshal(QRequest{ + SessionID: id, + Query: text, + Language: "it", + }) + if err != nil { + log.Printf("[talk] Could not create JSON body: %s\n", err.Error()) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + + // Build the request + req, err := http.NewRequest("POST", talkBaseURL+"/query?v=20150910", bytes.NewReader(data)) + if err != nil { + log.Printf("[talk] Could not create POST request: %s\n", err.Error()) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + + req.Header.Add("Authorization", "Bearer "+*talktoken) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Printf("[talk] Request error: %s\n", err.Error()) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + defer resp.Body.Close() + + var record QResponse + if err := json.NewDecoder(resp.Body).Decode(&record); err != nil { + log.Printf("[talk] Could not decode JSON response: %s\n", err.Error()) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + + if record.Status.ErrorType != "success" { + body, _ := json.MarshalIndent(record, "", " ") + log.Printf("[talk] Non-success status, full response body follows:\n%s\n", body) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + + reply := record.Result.Fulfillment.Speech + + // Replace tokens if found + if strings.Index(reply, "$") >= 0 { + reply = strings.Replace(reply, "$name", update.User.FirstName, -1) + } + + // Call command if needed + if reply[0] == '#' { + //TODO Find a better way? + if strings.HasPrefix(reply, "#metafora") { + message := "/metafora" + update.Text = &message + metafora(broker, update) + } + return + } + + broker.SendTextMessage(update.Chat, reply, &update.MessageID) }