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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -55,6 +56,10 @@ var mods = map[string]Mod{
|
||||||
OnInit: initremind,
|
OnInit: initremind,
|
||||||
OnMessage: remind,
|
OnMessage: remind,
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
OnInit: initsearch,
|
||||||
|
OnMessage: search,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func initmods() {
|
func initmods() {
|
||||||
|
@ -97,6 +102,8 @@ var sourcesans *string
|
||||||
var proverbi *string
|
var proverbi *string
|
||||||
var talktoken *string
|
var talktoken *string
|
||||||
var gapifile *string
|
var gapifile *string
|
||||||
|
var gapikey *string
|
||||||
|
var gapiCtx context.Context
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
brokerAddr := flag.String("broker", "localhost:7314", "Broker address:port")
|
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)")
|
remindpath = flag.String("remindpath", "reminders.json", "Path to reminder db (JSON)")
|
||||||
proverbi = flag.String("proverbi", "proverbi.txt", "Path to proverbi pairs (separated by /)")
|
proverbi = flag.String("proverbi", "proverbi.txt", "Path to proverbi pairs (separated by /)")
|
||||||
talktoken = flag.String("apiai", "@apiai.token", "api.ai token")
|
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)")
|
disable := flag.String("disable", "", "Blacklist mods (separated by comma)")
|
||||||
enable := flag.String("enable", "", "Whitelist mods (separated by comma)")
|
enable := flag.String("enable", "", "Whitelist mods (separated by comma)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
gapiCtx = context.Background()
|
||||||
|
|
||||||
if *disable != "" {
|
if *disable != "" {
|
||||||
for _, modname := range strings.Split(*disable, ",") {
|
for _, modname := range strings.Split(*disable, ",") {
|
||||||
modname = strings.TrimSpace(modname)
|
modname = strings.TrimSpace(modname)
|
||||||
|
|
|
@ -25,9 +25,13 @@ var metaobjects = []string{
|
||||||
|
|
||||||
func metafora(broker *tg.Broker, update tg.APIMessage) {
|
func metafora(broker *tg.Broker, update tg.APIMessage) {
|
||||||
if isCommand(update, "metafora") {
|
if isCommand(update, "metafora") {
|
||||||
n := rand.Intn(len(metaactions))
|
broker.SendTextMessage(update.Chat, metaforaAPI(), nil)
|
||||||
m := rand.Intn(len(metaobjects))
|
|
||||||
broker.SendTextMessage(update.Chat, metaactions[n]+" "+metaobjects[m], nil)
|
|
||||||
return
|
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
|
||||||
|
}
|
58
mods/talk.go
58
mods/talk.go
|
@ -24,14 +24,12 @@ type QResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
Result struct {
|
Result struct {
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
ResolvedQuery string `json:"resolvedQuery"`
|
ResolvedQuery string `json:"resolvedQuery"`
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
ActionIncomplete bool `json:"actionIncomplete"`
|
ActionIncomplete bool `json:"actionIncomplete"`
|
||||||
Parameters struct {
|
Parameters map[string]string `json:"parameters"`
|
||||||
Name string `json:"name"`
|
Contexts []struct {
|
||||||
} `json:"parameters"`
|
|
||||||
Contexts []struct {
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Parameters struct {
|
Parameters struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -116,6 +114,8 @@ func talk(broker *tg.Broker, update tg.APIMessage) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
broker.SendChatAction(update.Chat, tg.ActionTyping)
|
||||||
|
|
||||||
req.Header.Add("Authorization", "Bearer "+*talktoken)
|
req.Header.Add("Authorization", "Bearer "+*talktoken)
|
||||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
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] == '#' {
|
if reply[0] == '#' {
|
||||||
//TODO Find a better way?
|
//TODO Find a better way?
|
||||||
if strings.HasPrefix(reply, "#metafora") {
|
if strings.HasPrefix(reply, "#metafora") {
|
||||||
message := "/metafora"
|
reply = metaforaAPI()
|
||||||
update.Text = &message
|
|
||||||
metafora(broker, update)
|
|
||||||
}
|
}
|
||||||
return
|
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)
|
broker.SendTextMessage(update.Chat, reply, &update.MessageID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -25,10 +26,6 @@ func viaggi(broker *tg.Broker, update tg.APIMessage) {
|
||||||
usage := func() {
|
usage := func() {
|
||||||
broker.SendTextMessage(update.Chat, "Formato: /viaggi <i><PARTENZA></i> -> <i><DESTINAZIONE></i>", &update.MessageID)
|
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)
|
parts := strings.SplitN(*(update.Text), " ", 2)
|
||||||
if len(parts) < 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[1] = strings.Replace(msgs[1], " ", "-", -1)
|
||||||
msgs[2] = strings.Replace(msgs[2], ",", "", -1)
|
msgs[2] = strings.Replace(msgs[2], ",", "", -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
|
out, err := viaggiAPI(msgs[1], msgs[2])
|
||||||
err = json.NewDecoder(resp.Body).Decode(&outjson)
|
|
||||||
if err != nil {
|
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)
|
log.Println("[viaggi] " + err.Error())
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var moreeco Romeroute
|
|
||||||
var lesstim Romeroute
|
|
||||||
if len(outjson.Routes) < 1 {
|
|
||||||
// Should never happen
|
|
||||||
log.Println("[viaggi] No routes found (??)")
|
|
||||||
broker.SendTextMessage(update.Chat, "<b>ERRORE!</b> @hamcha controlla la console!", &update.MessageID)
|
broker.SendTextMessage(update.Chat, "<b>ERRORE!</b> @hamcha controlla la console!", &update.MessageID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate cheapest and fastest
|
broker.SendTextMessage(update.Chat, out, &update.MessageID)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,3 +96,41 @@ type Romejson struct {
|
||||||
Places []Romeplace
|
Places []Romeplace
|
||||||
Routes []Romeroute
|
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