diff --git a/mods/main.go b/mods/main.go
index 09902ad..3e57090 100644
--- a/mods/main.go
+++ b/mods/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "context"
"flag"
"log"
"math/rand"
@@ -55,6 +56,10 @@ var mods = map[string]Mod{
OnInit: initremind,
OnMessage: remind,
},
+ "search": {
+ OnInit: initsearch,
+ OnMessage: search,
+ },
}
func initmods() {
@@ -97,6 +102,8 @@ var sourcesans *string
var proverbi *string
var talktoken *string
var gapifile *string
+var gapikey *string
+var gapiCtx context.Context
func main() {
brokerAddr := flag.String("broker", "localhost:7314", "Broker address:port")
@@ -108,11 +115,14 @@ func main() {
remindpath = flag.String("remindpath", "reminders.json", "Path to reminder db (JSON)")
proverbi = flag.String("proverbi", "proverbi.txt", "Path to proverbi pairs (separated by /)")
talktoken = flag.String("apiai", "@apiai.token", "api.ai token")
- gapifile = flag.String("gapi", "gapi.json", "Google API Service Credentials file")
+ gapifile = flag.String("gapifile", "gapi.json", "Google API Service Credentials file (for STT)")
+ gapikey = flag.String("gapikey", "@gapi.key", "Google API key (for search/KG)")
disable := flag.String("disable", "", "Blacklist mods (separated by comma)")
enable := flag.String("enable", "", "Whitelist mods (separated by comma)")
flag.Parse()
+ gapiCtx = context.Background()
+
if *disable != "" {
for _, modname := range strings.Split(*disable, ",") {
modname = strings.TrimSpace(modname)
diff --git a/mods/metafora.go b/mods/metafora.go
index bb5eec1..bd766ff 100644
--- a/mods/metafora.go
+++ b/mods/metafora.go
@@ -25,9 +25,13 @@ var metaobjects = []string{
func metafora(broker *tg.Broker, update tg.APIMessage) {
if isCommand(update, "metafora") {
- n := rand.Intn(len(metaactions))
- m := rand.Intn(len(metaobjects))
- broker.SendTextMessage(update.Chat, metaactions[n]+" "+metaobjects[m], nil)
+ broker.SendTextMessage(update.Chat, metaforaAPI(), nil)
return
}
}
+
+func metaforaAPI() string {
+ n := rand.Intn(len(metaactions))
+ m := rand.Intn(len(metaobjects))
+ return metaactions[n] + " " + metaobjects[m]
+}
diff --git a/mods/search.go b/mods/search.go
new file mode 100644
index 0000000..6782a88
--- /dev/null
+++ b/mods/search.go
@@ -0,0 +1,144 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/hamcha/clessy/tg"
+)
+
+type SearchResult struct {
+ Name string
+ Description *string
+ Alternatives []string
+ None bool
+}
+
+type SearchResultData struct {
+ ItemListElement []struct {
+ Result struct {
+ Name []struct {
+ Value string `json:"@value"`
+ Lang string `json:"@language"`
+ } `json:"name,omitempty"`
+ Description []struct {
+ Value string `json:"@value"`
+ Lang string `json:"@language"`
+ } `json:"description,omitempty"`
+ DetailedDescription []struct {
+ Value string `json:"articleBody"`
+ Lang string `json:"inLanguage"`
+ } `json:"detailedDescription,omitempty"`
+ } `json:"result"`
+ } `json:"itemListElement"`
+ Error struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ } `json:"error,omitempty"`
+}
+
+const MAX_SEARCH_ALTS = 2
+
+func initsearch() {
+ if strings.HasPrefix(*gapikey, "@") {
+ data, err := ioutil.ReadFile((*gapikey)[1:])
+ if err != nil {
+ panic(err)
+ }
+ *gapikey = strings.TrimSpace(string(data))
+ }
+ if *gapikey == "" {
+ panic(fmt.Errorf("Missing GAPI key for search module (provide it or --disable search)"))
+ }
+}
+
+func search(broker *tg.Broker, update tg.APIMessage) {
+ if isCommand(update, "search") {
+ parts := strings.SplitN(*(update.Text), " ", 2)
+ if len(parts) < 2 {
+ broker.SendTextMessage(update.Chat, "Non mi hai dato niente da cercare!", &update.MessageID)
+ return
+ }
+ broker.SendChatAction(update.Chat, tg.ActionTyping)
+
+ result, err := searchAPI(parts[1])
+ if err != nil {
+ log.Printf("[search] %s\n", err.Error())
+ broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID)
+ return
+ }
+
+ if result.None {
+ broker.SendTextMessage(update.Chat, "Non ho trovato nulla, mi spiace :(", &update.MessageID)
+ return
+ }
+
+ resulttxt := fmt.Sprintf("Ecco la prima cosa che ho trovato:\n%s", result.Name)
+ if result.Description != nil {
+ resulttxt += "\n" + *(result.Description)
+ }
+ if len(result.Alternatives) > 0 {
+ resulttxt += "\nAlternativamente:\n - " + strings.Join(result.Alternatives, "\n - ")
+ }
+
+ broker.SendTextMessage(update.Chat, resulttxt, &update.MessageID)
+ }
+}
+
+func searchAPI(term string) (SearchResult, error) {
+ resp, err := http.Get("https://kgsearch.googleapis.com/v1/entities:search?indent=true&languages=it&languages=en&query=" + url.QueryEscape(term) + "&key=" + *gapikey)
+ if err != nil {
+ return SearchResult{}, err
+ }
+ defer resp.Body.Close()
+
+ var res SearchResultData
+ err = json.NewDecoder(resp.Body).Decode(&res)
+ if err != nil {
+ return SearchResult{}, err
+ }
+
+ if res.Error.Message != "" {
+ return SearchResult{}, fmt.Errorf("Request error: %d %s", res.Error.Code, res.Error.Message)
+ }
+
+ if len(res.ItemListElement) < 1 {
+ return SearchResult{None: true}, nil
+ }
+
+ out := SearchResult{None: false}
+ for _, item := range res.ItemListElement {
+ // Skip elements without a name
+ if item.Result.Name == nil || len(item.Result.Name) == 0 {
+ continue
+ }
+ namestr := item.Result.Name[0].Value
+
+ // Check if it has a small description
+ if item.Result.Description != nil && len(item.Result.Description) > 0 {
+ namestr += " (" + item.Result.Description[0].Value + ")"
+ }
+
+ // If we already had an item, add it to alternatives and find others
+ if out.Name != "" {
+ out.Alternatives = append(out.Alternatives, namestr)
+ if len(out.Alternatives) > MAX_SEARCH_ALTS {
+ break
+ }
+ continue
+ }
+ out.Name = namestr
+
+ // Check for a description
+ if item.Result.DetailedDescription != nil || len(item.Result.DetailedDescription) > 0 {
+ out.Description = &(item.Result.DetailedDescription[0].Value)
+ }
+ }
+
+ return out, nil
+}
diff --git a/mods/talk.go b/mods/talk.go
index 4f20965..8e644b7 100644
--- a/mods/talk.go
+++ b/mods/talk.go
@@ -24,14 +24,12 @@ 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 {
+ Source string `json:"source"`
+ ResolvedQuery string `json:"resolvedQuery"`
+ Action string `json:"action"`
+ ActionIncomplete bool `json:"actionIncomplete"`
+ Parameters map[string]string `json:"parameters"`
+ Contexts []struct {
Name string `json:"name"`
Parameters struct {
Name string `json:"name"`
@@ -116,6 +114,8 @@ func talk(broker *tg.Broker, update tg.APIMessage) {
return
}
+ broker.SendChatAction(update.Chat, tg.ActionTyping)
+
req.Header.Add("Authorization", "Bearer "+*talktoken)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
@@ -153,12 +153,48 @@ func talk(broker *tg.Broker, update tg.APIMessage) {
if reply[0] == '#' {
//TODO Find a better way?
if strings.HasPrefix(reply, "#metafora") {
- message := "/metafora"
- update.Text = &message
- metafora(broker, update)
+ reply = metaforaAPI()
}
return
}
+ if record.Result.Action != "" && !record.Result.ActionIncomplete {
+ switch record.Result.Action {
+ case "viaggi":
+ // Check that both parameters are given
+ if record.Result.Parameters["from-city"] != "" && record.Result.Parameters["to-city"] != "" {
+ data, err := viaggiAPI(record.Result.Parameters["from-city"], record.Result.Parameters["to-city"])
+ if err != nil {
+ log.Printf("[talk] viaggi failed:\n%s\n", record.Result.Action)
+ broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID)
+ return
+ }
+ reply += "\n" + data
+ }
+ case "search":
+ result, err := searchAPI(record.Result.Parameters["search-item"])
+ if err != nil {
+ log.Printf("[talk] search failed:\n%s\n", record.Result.Action)
+ broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID)
+ return
+ }
+ if result.None {
+ broker.SendTextMessage(update.Chat, "Non ho trovato nulla, mi spiace :(", &update.MessageID)
+ return
+ }
+
+ reply = fmt.Sprintf("%s\n%s", reply, result.Name)
+ if result.Description != nil {
+ reply += "\n" + *(result.Description)
+ }
+ if len(result.Alternatives) > 0 {
+ reply += "\nAlternativamente:\n - " + strings.Join(result.Alternatives, "\n - ")
+ }
+ default:
+ log.Printf("[talk] Unknown action called:\n%s\n", record.Result.Action)
+ broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID)
+ }
+ }
+
broker.SendTextMessage(update.Chat, reply, &update.MessageID)
}
diff --git a/mods/viaggi.go b/mods/viaggi.go
index 93f1b13..0bd8d44 100644
--- a/mods/viaggi.go
+++ b/mods/viaggi.go
@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
+ "fmt"
"log"
"net/http"
"net/url"
@@ -25,10 +26,6 @@ func viaggi(broker *tg.Broker, update tg.APIMessage) {
usage := func() {
broker.SendTextMessage(update.Chat, "Formato: /viaggi <PARTENZA> -> <DESTINAZIONE>", &update.MessageID)
}
- oops := func(err error) {
- log.Println("[viaggi] GET error:" + err.Error())
- broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID)
- }
parts := strings.SplitN(*(update.Text), " ", 2)
if len(parts) < 2 {
@@ -46,54 +43,15 @@ func viaggi(broker *tg.Broker, update tg.APIMessage) {
msgs[1] = strings.Replace(msgs[1], " ", "-", -1)
msgs[2] = strings.Replace(msgs[2], ",", "", -1)
msgs[2] = strings.Replace(msgs[2], " ", "-", -1)
- src := url.QueryEscape(msgs[1])
- dst := url.QueryEscape(msgs[2])
- url := viaggiurl + "&oName=" + src + "&dName=" + dst
- resp, err := http.Get(url)
- if err != nil {
- oops(err)
- return
- }
- defer resp.Body.Close()
- var outjson Romejson
- err = json.NewDecoder(resp.Body).Decode(&outjson)
+ out, err := viaggiAPI(msgs[1], msgs[2])
if err != nil {
- broker.SendTextMessage(update.Chat, "Hm, Rome2rio non ha trovato nulla, mi spiace :(\nForse non hai scritto bene uno dei due posti?", &update.MessageID)
- return
- }
-
- var moreeco Romeroute
- var lesstim Romeroute
- if len(outjson.Routes) < 1 {
- // Should never happen
- log.Println("[viaggi] No routes found (??)")
+ log.Println("[viaggi] " + err.Error())
broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID)
return
}
- // Calculate cheapest and fastest
- moreeco = outjson.Routes[0]
- lesstim = outjson.Routes[0]
- for _, v := range outjson.Routes {
- if v.IndicativePrice.Price < moreeco.IndicativePrice.Price {
- moreeco = v
- }
- if v.Duration < lesstim.Duration {
- lesstim = v
- }
- }
-
- broker.SendTextMessage(update.Chat,
- "Viaggio da "+outjson.Places[0].Name+
- " a "+outjson.Places[1].Name+""+
- "\n\n"+
- "Piu economico: "+moreeco.Name+" ("+parseData(moreeco)+")"+
- "\n"+
- "Piu veloce: "+lesstim.Name+" ("+parseData(lesstim)+")"+
- "\n\n"+
- "Maggiori informazioni",
- &update.MessageID)
+ broker.SendTextMessage(update.Chat, out, &update.MessageID)
}
}
@@ -138,3 +96,41 @@ type Romejson struct {
Places []Romeplace
Routes []Romeroute
}
+
+func viaggiAPI(from string, to string) (string, error) {
+ src := url.QueryEscape(from)
+ dst := url.QueryEscape(to)
+ url := viaggiurl + "&oName=" + src + "&dName=" + dst
+ resp, err := http.Get(url)
+ if err != nil {
+ return "", fmt.Errorf("GET error: %s", err.Error())
+ }
+ defer resp.Body.Close()
+
+ var outjson Romejson
+ err = json.NewDecoder(resp.Body).Decode(&outjson)
+ if err != nil {
+ return "Hm, Rome2rio non ha trovato nulla, mi spiace :(\nForse non hai scritto bene uno dei due posti?", nil
+ }
+
+ var moreeco Romeroute
+ var lesstim Romeroute
+ if len(outjson.Routes) < 1 {
+ // Should never happen
+ return "", fmt.Errorf("No routes found (??)")
+ }
+
+ // Calculate cheapest and fastest
+ moreeco = outjson.Routes[0]
+ lesstim = outjson.Routes[0]
+ for _, v := range outjson.Routes {
+ if v.IndicativePrice.Price < moreeco.IndicativePrice.Price {
+ moreeco = v
+ }
+ if v.Duration < lesstim.Duration {
+ lesstim = v
+ }
+ }
+
+ return fmt.Sprintf("Viaggio da %s a %s\n\nPiu economico: %s (%s)\nPiu veloce: %s (%s)\n\nMaggiori informazioni", outjson.Places[0].Name, outjson.Places[1].Name, moreeco.Name, parseData(moreeco), lesstim.Name, parseData(lesstim), src, dst), nil
+}