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" 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 }