mods: Add search and refactor mods
This commit is contained in:
parent
76c4a672fc
commit
c514fb4678
5 changed files with 251 additions and 61 deletions
12
mods/main.go
12
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)
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
144
mods/search.go
Normal file
144
mods/search.go
Normal file
|
@ -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, "<b>ERRORE!</b> @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<b>%s</b>", 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
|
||||
}
|
48
mods/talk.go
48
mods/talk.go
|
@ -28,9 +28,7 @@ type QResponse struct {
|
|||
ResolvedQuery string `json:"resolvedQuery"`
|
||||
Action string `json:"action"`
|
||||
ActionIncomplete bool `json:"actionIncomplete"`
|
||||
Parameters struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"parameters"`
|
||||
Parameters map[string]string `json:"parameters"`
|
||||
Contexts []struct {
|
||||
Name string `json:"name"`
|
||||
Parameters struct {
|
||||
|
@ -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, "<b>ERRORE!</b> @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, "<b>ERRORE!</b> @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<b>%s</b>", 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, "<b>ERRORE!</b> @hamcha controlla la console!", &update.MessageID)
|
||||
}
|
||||
}
|
||||
|
||||
broker.SendTextMessage(update.Chat, reply, &update.MessageID)
|
||||
}
|
||||
|
|
|
@ -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 <i><PARTENZA></i> -> <i><DESTINAZIONE></i>", &update.MessageID)
|
||||
}
|
||||
oops := func(err error) {
|
||||
log.Println("[viaggi] GET error:" + err.Error())
|
||||
broker.SendTextMessage(update.Chat, "<b>ERRORE!</b> @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, "<b>ERRORE!</b> @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 <b>"+outjson.Places[0].Name+
|
||||
"</b> a <b>"+outjson.Places[1].Name+"</b>"+
|
||||
"\n\n"+
|
||||
"Piu economico: <b>"+moreeco.Name+"</b> ("+parseData(moreeco)+")"+
|
||||
"\n"+
|
||||
"Piu veloce: <b>"+lesstim.Name+"</b> ("+parseData(lesstim)+")"+
|
||||
"\n\n"+
|
||||
"<a href=\"http://www.rome2rio.com/it/s/"+src+"/"+dst+"\">Maggiori informazioni</a>",
|
||||
&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 <b>%s</b> a <b>%s</b>\n\nPiu economico: <b>%s</b> (%s)\nPiu veloce: <b>%s</b> (%s)\n\n<a href=\"http://www.rome2rio.com/it/s/%s/%s\">Maggiori informazioni</a>", outjson.Places[0].Name, outjson.Places[1].Name, moreeco.Name, parseData(moreeco), lesstim.Name, parseData(lesstim), src, dst), nil
|
||||
}
|
||||
|
|
Reference in a new issue