|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/xml"
|
|
|
|
"errors"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
"gosrc.io/xmpp"
|
|
|
|
"gosrc.io/xmpp/stanza"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// APIEndpoint = "https://hodlhodl.com/api/v1"
|
|
|
|
//TestAPIEndpoint = "https://hhtestnet.com/api/v1"
|
|
|
|
//APIkey = "***REMOVED***"
|
|
|
|
HodlHodlCheckTimer = 5 * time.Second
|
|
|
|
commandSymbol = "/"
|
|
|
|
|
|
|
|
// Config
|
|
|
|
infoFormat = "====== "
|
|
|
|
defaultConfigFilePath = "./"
|
|
|
|
configFileName = "config"
|
|
|
|
configType = "yaml"
|
|
|
|
logStanzasOn = "logger_on"
|
|
|
|
logFilePath = "logfile_path"
|
|
|
|
|
|
|
|
//Keys in config
|
|
|
|
serverAddressKey = "full_address"
|
|
|
|
clientJid = "jid"
|
|
|
|
clientPass = "pass"
|
|
|
|
configContactSep = ";"
|
|
|
|
APIEndPoint = "testapiendpoint"
|
|
|
|
APIKey = "apikey"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
textChan = make(chan string, 5)
|
|
|
|
rawTextChan = make(chan string, 5)
|
|
|
|
killChan = make(chan error, 1)
|
|
|
|
errChan = make(chan error)
|
|
|
|
rosterChan = make(chan struct{})
|
|
|
|
|
|
|
|
logger *log.Logger
|
|
|
|
disconnectErr = errors.New("disconnecting client")
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
Commands = map[string]Command{
|
|
|
|
"hello": &HelloCmd{},
|
|
|
|
"help": &HelpCmd{},
|
|
|
|
"default": &DefaultCmd{},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Notification export
|
|
|
|
type Notification struct {
|
|
|
|
Status string `json:"status"`
|
|
|
|
Notifications []struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
Body string `json:"body"`
|
|
|
|
Link string `json:"link"`
|
|
|
|
} `json:"notifications"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type config struct {
|
|
|
|
Server map[string]string `mapstructure:"server"`
|
|
|
|
Client map[string]string `mapstructure:"client"`
|
|
|
|
Recipient string `string:"recipient"`
|
|
|
|
Hodlhodl map[string]string `mapstructure:"hodlhodl"`
|
|
|
|
LogStanzas map[string]string `mapstructure:"logstanzas"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Command interface {
|
|
|
|
Run(args []string) (string, error)
|
|
|
|
Help(args []string) string
|
|
|
|
}
|
|
|
|
|
|
|
|
type DefaultCmd struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
type HelloCmd struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
type HelpCmd struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*DefaultCmd) Run(args []string) (string, error) {
|
|
|
|
return "Type help to get available commands", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*DefaultCmd) Help(args []string) string {
|
|
|
|
return "Help of Default"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*HelpCmd) Run(args []string) (string, error) {
|
|
|
|
return "this is help", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*HelpCmd) Help(args []string) string {
|
|
|
|
return "this is help of help"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*HelloCmd) Run(args []string) (string, error) {
|
|
|
|
return "Hi, My name is Skynet 1.0. \n How can I help you ? ", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*HelloCmd) Help(args []string) string {
|
|
|
|
return "Help of Hello"
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleCommand(command Command, args []string) string {
|
|
|
|
|
|
|
|
if len(args) != 0 && args[0] == "help" {
|
|
|
|
fmt.Println("Inside 1")
|
|
|
|
result := command.Help(args)
|
|
|
|
return result
|
|
|
|
} else if len(args) != 0 && args[0] != "help" {
|
|
|
|
return "Unkown command"
|
|
|
|
}
|
|
|
|
|
|
|
|
result, err := command.Run(args)
|
|
|
|
if err != nil {
|
|
|
|
return "error"
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
// ============================================================
|
|
|
|
// Parse the flag with the config directory path as argument
|
|
|
|
flag.String("c", defaultConfigFilePath, "Provide a path to the directory that contains the configuration"+
|
|
|
|
" file you want to use. Config file should be named \"config\" and be in YAML format..")
|
|
|
|
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
|
|
|
pflag.Parse()
|
|
|
|
|
|
|
|
// ==========================
|
|
|
|
// Read configuration
|
|
|
|
c := readConfig()
|
|
|
|
|
|
|
|
// Setup logger
|
|
|
|
on, err := strconv.ParseBool(c.LogStanzas[logStanzasOn])
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln(err)
|
|
|
|
}
|
|
|
|
if on {
|
|
|
|
f, err := os.OpenFile(path.Join(c.LogStanzas[logFilePath], "logs.txt"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln(err)
|
|
|
|
}
|
|
|
|
logger = log.New(f, "", log.Lshortfile|log.Ldate|log.Ltime)
|
|
|
|
logger.SetOutput(f)
|
|
|
|
defer f.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
startClient(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func startClient(config *config) {
|
|
|
|
|
|
|
|
// Client Setup
|
|
|
|
clientCfg := &xmpp.Config{
|
|
|
|
TransportConfiguration: xmpp.TransportConfiguration{
|
|
|
|
Address: config.Server[serverAddressKey],
|
|
|
|
},
|
|
|
|
Jid: config.Client[clientJid],
|
|
|
|
Credential: xmpp.Password(config.Client[clientPass]),
|
|
|
|
Insecure: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
var client *xmpp.Client
|
|
|
|
router := xmpp.NewRouter()
|
|
|
|
router.HandleFunc("message", answerMessage)
|
|
|
|
|
|
|
|
errorHandler := func(err error) {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
if client, err = xmpp.NewClient(clientCfg, router, errorHandler); err != nil {
|
|
|
|
log.Panicln(fmt.Sprintf("Could not create a new client ! %s", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client connection
|
|
|
|
if err = client.Connect(); err != nil {
|
|
|
|
fmt.Sprintf("%sXMPP connection failed: %s", infoFormat, err)
|
|
|
|
fmt.Println("Failed to connect to server. Exiting...")
|
|
|
|
cm := xmpp.NewStreamManager(client, nil)
|
|
|
|
log.Fatal(cm.Run())
|
|
|
|
servConnFail := errors.New("failed to connect to server. Check your configuration ? Exiting")
|
|
|
|
errChan <- servConnFail
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
fmt.Println("Client is running")
|
|
|
|
}
|
|
|
|
|
|
|
|
timer := time.NewTicker(HodlHodlCheckTimer)
|
|
|
|
notifications := make(chan string, 100)
|
|
|
|
|
|
|
|
go hodlNotifications(client, config, notifications)
|
|
|
|
|
|
|
|
fmt.Println("checking notifs every ", HodlHodlCheckTimer)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-timer.C:
|
|
|
|
//fmt.Println("checking notifs...")
|
|
|
|
notifications <- gethdlNotif(config.Hodlhodl[APIKey], config.Hodlhodl[APIEndPoint])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func answerMessage(s xmpp.Sender, p stanza.Packet) {
|
|
|
|
msg, ok := p.(stanza.Message)
|
|
|
|
if !ok {
|
|
|
|
_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
|
|
|
|
|
|
|
|
if len(msg.Body) > 0 && msg.Body[0:1] == commandSymbol {
|
|
|
|
fmt.Println("keep the message", msg.Body)
|
|
|
|
receivedCmd := strings.Replace(strings.Fields(msg.Body)[0], "/", "", -1)
|
|
|
|
args := strings.Fields(msg.Body)[1:]
|
|
|
|
fmt.Println("command is : ", receivedCmd)
|
|
|
|
fmt.Println("args are : ", args)
|
|
|
|
|
|
|
|
// if command is handled
|
|
|
|
|
|
|
|
// Commands is a dict of Commands=map[ string ]Command
|
|
|
|
// [ regCommandName ]regCommand
|
|
|
|
|
|
|
|
for regCommandName, regCommand := range Commands {
|
|
|
|
|
|
|
|
var result string
|
|
|
|
if receivedCmd == regCommandName {
|
|
|
|
result = handleCommand(regCommand, args)
|
|
|
|
} /*else {
|
|
|
|
result = handleCommand(Commands["default"], args)
|
|
|
|
}*/
|
|
|
|
|
|
|
|
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: result}
|
|
|
|
s.Send(reply)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func hodlNotifications(client xmpp.Sender, config *config, notifications chan string) {
|
|
|
|
fmt.Println("START MESSAGING")
|
|
|
|
|
|
|
|
currentContact := strings.Split(config.Contacts, configContactSep)[1]
|
|
|
|
fmt.Println(infoFormat + "Now sending messages to " + currentContact + " in a private conversation\n")
|
|
|
|
fmt.Println("currentContacts", currentContact)
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case notif := <-notifications:
|
|
|
|
|
|
|
|
// Test if notif is nil or skip this loop
|
|
|
|
if notif == "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println("sending notification through xmpp")
|
|
|
|
|
|
|
|
reply := stanza.Message{Attrs: stanza.Attrs{To: recipient, Type: stanza.MessageTypeChat}, Body: notif}
|
|
|
|
if logger != nil {
|
|
|
|
raw, _ := xml.Marshal(reply)
|
|
|
|
logger.Println(string(raw))
|
|
|
|
}
|
|
|
|
err := client.Send(reply)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("There was a problem sending the message : %v", reply)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case <-rosterChan:
|
|
|
|
fmt.Println("rosterchan")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func gethdlNotif(APIKey string, APIEndPoint string) string {
|
|
|
|
//fmt.Println("get notif")
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", APIEndPoint+"/notifications/read", nil)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
req.Header.Add("Authorization", "Bearer "+APIKey)
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
//fmt.Println(resp.Status)
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
} else {
|
|
|
|
res := Notification{}
|
|
|
|
json.Unmarshal([]byte(body), &res)
|
|
|
|
if len(res.Notifications) > 0 {
|
|
|
|
fmt.Println("Join: ", strings.Join([]string{res.Notifications[0].Title, res.Notifications[0].Body}, " "))
|
|
|
|
notif := strings.Join([]string{res.Notifications[0].Title, res.Notifications[0].Body}, " ")
|
|
|
|
return notif
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func readConfig() *config {
|
|
|
|
viper.SetConfigName(configFileName) // name of config file (without extension)
|
|
|
|
viper.BindPFlags(pflag.CommandLine)
|
|
|
|
viper.AddConfigPath(viper.GetString("c")) // path to look for the config file in
|
|
|
|
err := viper.ReadInConfig() // Find and read the config file
|
|
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
|
|
|
log.Fatalf("%s %s", err, "Please make sure you give a path to the directory of the config and not to the config itself.")
|
|
|
|
} else {
|
|
|
|
log.Panicln(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
viper.SetConfigType(configType)
|
|
|
|
var config config
|
|
|
|
err = viper.Unmarshal(&config)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("Unable to decode Config: %s \n", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we have contacts to message
|
|
|
|
if len(strings.TrimSpace(config.Recipient)) == 0 {
|
|
|
|
log.Panicln("You appear to have no contacts to message !")
|
|
|
|
}
|
|
|
|
// Check logging
|
|
|
|
config.LogStanzas[logFilePath] = path.Clean(config.LogStanzas[logFilePath])
|
|
|
|
on, err := strconv.ParseBool(config.LogStanzas[logStanzasOn])
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln(err)
|
|
|
|
}
|
|
|
|
if d, e := isDirectory(config.LogStanzas[logFilePath]); (e != nil || !d) && on {
|
|
|
|
log.Panicln("The log file path could not be found or is not a directory.")
|
|
|
|
}
|
|
|
|
fmt.Println("config in ReadConfig :", &config)
|
|
|
|
return &config
|
|
|
|
}
|
|
|
|
|
|
|
|
// If an error occurs, this is used to kill the client
|
|
|
|
func errorHandler(err error) {
|
|
|
|
killChan <- err
|
|
|
|
}
|
|
|
|
|
|
|
|
func isDirectory(path string) (bool, error) {
|
|
|
|
fileInfo, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return fileInfo.IsDir(), err
|
|
|
|
}
|