From 65f8b56e5cfab620aee7b38ebdf27a841a28fda2 Mon Sep 17 00:00:00 2001 From: Yahya Fadhluloh Al Fatih Date: Thu, 24 Dec 2020 01:55:28 +0700 Subject: [PATCH] add: clean up directory structure with package control in golang --- .gitignore | 1 + {coral => coral-example}/basic.yml | 0 {coral => coral-example}/example.yml | 0 {coral => coral-example}/only_command.yml | 0 {coral => coral-example}/only_schedule.yml | 0 go.mod | 2 +- main.go | 31 +- parser.go | 6 +- session.go | 22 +- builder_test.go => test/builder_test.go | 4 +- {config => test/config}/keys-example.json | 0 {config => test/config}/keys.json | 0 parser_test.go => test/parser_test.go | 6 +- builder.go => utils/builder.go | 12 +- coral.go => utils/coral.go | 2 +- struct.go => utils/struct.go | 4 +- util.go => utils/util.go | 44 ++- wa_default_handler.go | 79 ++++ wa_image_handler.go | 146 ++++++++ wa_text_handler.go | 211 +++++++++++ webhook.go | 14 +- whatsappHandler.go | 406 --------------------- 22 files changed, 517 insertions(+), 473 deletions(-) rename {coral => coral-example}/basic.yml (100%) rename {coral => coral-example}/example.yml (100%) rename {coral => coral-example}/only_command.yml (100%) rename {coral => coral-example}/only_schedule.yml (100%) rename builder_test.go => test/builder_test.go (97%) rename {config => test/config}/keys-example.json (100%) rename {config => test/config}/keys.json (100%) rename parser_test.go => test/parser_test.go (95%) rename builder.go => utils/builder.go (96%) rename coral.go => utils/coral.go (98%) rename struct.go => utils/struct.go (99%) rename util.go => utils/util.go (86%) create mode 100644 wa_default_handler.go create mode 100644 wa_image_handler.go create mode 100644 wa_text_handler.go delete mode 100644 whatsappHandler.go diff --git a/.gitignore b/.gitignore index 8ebf5f8..d49cc69 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .compile .dev .coral +coral sessions .sessions diff --git a/coral/basic.yml b/coral-example/basic.yml similarity index 100% rename from coral/basic.yml rename to coral-example/basic.yml diff --git a/coral/example.yml b/coral-example/example.yml similarity index 100% rename from coral/example.yml rename to coral-example/example.yml diff --git a/coral/only_command.yml b/coral-example/only_command.yml similarity index 100% rename from coral/only_command.yml rename to coral-example/only_command.yml diff --git a/coral/only_schedule.yml b/coral-example/only_schedule.yml similarity index 100% rename from coral/only_schedule.yml rename to coral-example/only_schedule.yml diff --git a/go.mod b/go.mod index 02c9463..446f0cf 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/eFishery/nemo +module github.com/eFishery/NeMo go 1.15 diff --git a/main.go b/main.go index 5de8abe..d3dfb97 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,8 @@ import ( cron "github.com/robfig/cron/v3" godotenv "github.com/joho/godotenv" // "github.com/davecgh/go-spew/spew" + + "github.com/eFishery/NeMo/utils" ) type waHandler struct { @@ -25,20 +27,16 @@ type waHandler struct { } -var Schedules []Schedule - -var Settings *Setting - -var BuildCommands []BuildCommand - -var BuildGreetings []BuildGreeting - func init() { if err := godotenv.Load(); err != nil { log.Print("No .env file found") } } +var Settings *utils.Setting +var BuildGreetings []utils.BuildGreeting +var BuildCommands []utils.BuildCommand + func main() { if len(os.Args) < 2 { @@ -47,18 +45,13 @@ func main() { sender := os.Args[1] - Settings = LoadSetting() - - builder() - - readGreetingsFile() + Settings = utils.LoadSetting() + Settings.Builder() + BuildGreetings = utils.ReadGreetingsFile() jadwal := cron.New() - cmds := readBuildCommandsFiles() - if !cmds { - return - } + BuildCommands = utils.ReadBuildCommandsFiles() wac, err := whatsapp.NewConn(5 * time.Second) // wac.SetClientVersion(0, 4, 2080) @@ -80,8 +73,8 @@ func main() { log.Fatalf("error pinging in: %v\n", err) } - schedule := readScheduleFiles() - if !schedule { + isLoaded, Schedules := utils.ReadScheduleFiles() + if !isLoaded { log.Println("Can't read Schedule Files") return } diff --git a/parser.go b/parser.go index 119bb14..fcbea76 100644 --- a/parser.go +++ b/parser.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "os" "strings" + + "github.com/eFishery/NeMo/utils" ) var ( @@ -27,13 +29,13 @@ var ( // if no URL is found in pesan, pesan is returned as is // if URL is found, try POST request to url // currently only supports JSON response with message key -func nemoParser(pesan string, Sessions Session) (string, error) { +func nemoParser(pesan string, Sessions utils.Session) (string, error) { urlCount := strings.Count(pesan, "{{") if urlCount == 0 { return pesan, nil } for i := 0; i < urlCount; i++ { - url := between(pesan, "{{", "}}") + url := utils.Between(pesan, "{{", "}}") r, err := req.Post(url, req.BodyJSON(Sessions)) if err != nil { pesan = strings.Replace(pesan, fmt.Sprintf("{{%s}}", url), errReqErr(url), -1) diff --git a/session.go b/session.go index 76e03a5..434b199 100644 --- a/session.go +++ b/session.go @@ -7,11 +7,13 @@ import ( "log" "fmt" "time" + + "github.com/eFishery/NeMo/utils" ) -func newSession(phone_number string, current_process string, timeout int) Session{ +func newSession(phone_number string, current_process string, timeout int) utils.Session{ - savedSession := Session { + savedSession := utils.Session { PhoneNumber: phone_number, CurrentProcess: current_process, CurrentQuestionSlug: 0, @@ -21,19 +23,19 @@ func newSession(phone_number string, current_process string, timeout int) Sessio } file, _ := json.MarshalIndent(savedSession, "", " ") - _ = ioutil.WriteFile(fileSession(phone_number), file, 0644) + _ = ioutil.WriteFile(utils.FileSession(phone_number), file, 0644) return savedSession } -func loadSession(phone_number string) (Session, error) { - var s Session - file_session, err := ioutil.ReadFile(fileSession(phone_number)) +func loadSession(phone_number string) (utils.Session, error) { + var s utils.Session + file_session, err := ioutil.ReadFile(utils.FileSession(phone_number)) if err != nil { log.Println("Create a new file") - file, _ := json.MarshalIndent(Session{}, "", " ") - _ = ioutil.WriteFile(fileSession(phone_number), file, 0644) + file, _ := json.MarshalIndent(utils.Session{}, "", " ") + _ = ioutil.WriteFile(utils.FileSession(phone_number), file, 0644) return s, fmt.Errorf("Session hasn't been created") } @@ -45,7 +47,7 @@ func loadSession(phone_number string) (Session, error) { return s, nil } -func saveSession(s Session, phone_number string) { +func saveSession(s utils.Session, phone_number string) { file, _ := json.MarshalIndent(s, "", " ") - _ = ioutil.WriteFile(fileSession(phone_number), file, 0644) + _ = ioutil.WriteFile(utils.FileSession(phone_number), file, 0644) } \ No newline at end of file diff --git a/builder_test.go b/test/builder_test.go similarity index 97% rename from builder_test.go rename to test/builder_test.go index d0bc4ce..a58dc40 100644 --- a/builder_test.go +++ b/test/builder_test.go @@ -1,9 +1,11 @@ -package main +package test import ( "io/ioutil" "testing" "os" + + "github.com/eFishery/NeMo/utils" ) func TestBuildCommandsAutoCreated( t *testing.T) { diff --git a/config/keys-example.json b/test/config/keys-example.json similarity index 100% rename from config/keys-example.json rename to test/config/keys-example.json diff --git a/config/keys.json b/test/config/keys.json similarity index 100% rename from config/keys.json rename to test/config/keys.json diff --git a/parser_test.go b/test/parser_test.go similarity index 95% rename from parser_test.go rename to test/parser_test.go index f652e00..628675d 100644 --- a/parser_test.go +++ b/test/parser_test.go @@ -1,9 +1,11 @@ -package main +package test import ( "fmt" "os" "testing" + + "github.com/eFishery/NeMo/utils" ) const ( @@ -38,7 +40,7 @@ func TestNemoParser(t *testing.T) { name: "three_url_from_file", pesan: fmt.Sprintf("hello {{%s}} hello {{%s}} hello {{%s}}", testURL1, testURL2, testURL3), res: "hello 1 hello 2 hello 3", - config: "config/keys-example.json", + config: "test/config/keys-example.json", }, } sess := Session{ diff --git a/builder.go b/utils/builder.go similarity index 96% rename from builder.go rename to utils/builder.go index 591bffb..1c3766d 100644 --- a/builder.go +++ b/utils/builder.go @@ -1,4 +1,4 @@ -package main +package utils import ( "io/ioutil" @@ -17,8 +17,8 @@ func matchYAMLFile(filename string) bool { } -func builder() { - linter := builder_linter_all() +func (Settings *Setting) Builder() { + linter := Settings.builder_linter() if len(linter) > 0 { for i:= range(linter){ log.Println(linter[i]) @@ -39,7 +39,7 @@ func builder() { log.Println("Build file " + file.Name()) processName := file.Name() var coral Coral - coral.getCoral(processName) + coral.GetCoral(processName) var commandCompile = BuildCommand { Prefix: coral.Commands.Prefix, @@ -138,14 +138,14 @@ func builder() { } } -func builder_linter_all() []string { +func (Settings *Setting) builder_linter() []string { var result []string files,_ := ioutil.ReadDir(Settings.CoralDir) for _, file := range files { if matchYAMLFile(file.Name()) { var coral Coral - coral.getCoral(file.Name()) + coral.GetCoral(file.Name()) if !coral.valAuthor() { result = append(result, file.Name() + ": Author must complete") diff --git a/coral.go b/utils/coral.go similarity index 98% rename from coral.go rename to utils/coral.go index 8f0b440..f13a688 100644 --- a/coral.go +++ b/utils/coral.go @@ -1,4 +1,4 @@ -package main +package utils func (coral *Coral) valCommands() bool{ if coral.Commands.Prefix == ""{ return false } diff --git a/struct.go b/utils/struct.go similarity index 99% rename from struct.go rename to utils/struct.go index 1fa5259..9687e1a 100644 --- a/struct.go +++ b/utils/struct.go @@ -1,4 +1,4 @@ -package main +package utils type Setting struct { @@ -108,7 +108,7 @@ type Session struct { Finished string `json:"finished"` } -type discord struct { +type Discord struct { Content string `json:"content"` } diff --git a/util.go b/utils/util.go similarity index 86% rename from util.go rename to utils/util.go index 1eefbb1..858da1a 100644 --- a/util.go +++ b/utils/util.go @@ -1,4 +1,4 @@ -package main +package utils import ( "path/filepath" @@ -53,7 +53,7 @@ func LoadSetting() *Setting { } -func between(value string, a string, b string) string { +func Between(value string, a string, b string) string { // Get substring between two strings. posFirst := strings.Index(value, a) if posFirst == -1 { @@ -70,7 +70,7 @@ func between(value string, a string, b string) string { return value[posFirstAdjusted:posLast] } -func after(value string, a string) string { +func After(value string, a string) string { // Get substring after a string. pos := strings.LastIndex(value, a) if pos == -1 { @@ -83,7 +83,7 @@ func after(value string, a string) string { return value[adjustedPos:len(value)] } -func AddFileToS3(fileDir string) string { +func (Settings *Setting) AddFileToS3(fileDir string) string { creds := credentials.NewStaticCredentials(Settings.AwsAccessKeyId, Settings.AwsSecretAccessKey, "") _, err := creds.Get() @@ -128,8 +128,9 @@ func AddFileToS3(fileDir string) string { return Settings.AwsS3EndpointUrl + Settings.AwsS3Dir + fileName } -func (c *Coral) getCoral(filename string) *Coral { - yamlFile, err := ioutil.ReadFile( Settings.CoralDir + "/" + filename) +func (c *Coral) GetCoral(filename string) *Coral { + Settings := LoadSetting() + yamlFile, err := ioutil.ReadFile(Settings.CoralDir + "/" + filename) if err != nil { log.Printf("yamlFile.Get err #%v ", err) } @@ -141,22 +142,28 @@ func (c *Coral) getCoral(filename string) *Coral { return c } -func readScheduleFiles() bool{ +func ReadScheduleFiles() (bool, []Schedule){ + var Schedules []Schedule + + Settings := LoadSetting() content, err := ioutil.ReadFile(Settings.BuildDir + "/schedules.json") if err != nil { log.Fatal(err) } - + jsonErr := json.Unmarshal(content, &Schedules) if jsonErr != nil { log.Fatal(jsonErr) } - return true + return true, Schedules } -func readBuildCommandsFiles() bool{ +func ReadBuildCommandsFiles() []BuildCommand { + var BuildCommands []BuildCommand + + Settings := LoadSetting() content, err := ioutil.ReadFile(Settings.BuildDir + "/commands.json") if err != nil { os.OpenFile(Settings.BuildDir + "/commands.json", os.O_RDONLY|os.O_CREATE, 0755) @@ -168,11 +175,14 @@ func readBuildCommandsFiles() bool{ log.Fatal(jsonErr) } - return true + return BuildCommands } -func readGreetingsFile() bool{ +func ReadGreetingsFile() []BuildGreeting{ + var BuildGreetings []BuildGreeting + + Settings := LoadSetting() content, err := ioutil.ReadFile(Settings.BuildDir + "/greetings.json") if err != nil { os.OpenFile(Settings.BuildDir + "/greetings.json", os.O_RDONLY|os.O_CREATE, 0755) @@ -182,14 +192,14 @@ func readGreetingsFile() bool{ jsonErr := json.Unmarshal(content, &BuildGreetings) if jsonErr != nil { log.Fatal(jsonErr) - } - - return true - + } + + return BuildGreetings } -func fileSession(phone_number string) string { +func FileSession(phone_number string) string { + Settings := LoadSetting() return Settings.BuildDir + "/sessions/" + phone_number + ".session" } diff --git a/wa_default_handler.go b/wa_default_handler.go new file mode 100644 index 0000000..db071a0 --- /dev/null +++ b/wa_default_handler.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "log" + "strings" + + whatsapp "github.com/Rhymen/go-whatsapp" + + "github.com/eFishery/NeMo/utils" +) + + +func currently_it_do_nothing(wac *whatsapp.Conn, RJID string) { + phone_number := strings.Split(RJID, "@")[0] + + // if the user suddenly sent the image this will trigger error because there is no available session + // need to test this + _, err := loadSession(phone_number) + if err != nil { + log.Println("I don't know what you do but it do nothing") + return + } +} + +func (wh *waHandler) HandleDocumentMessage(message whatsapp.DocumentMessage) { + if !(message.Info.Timestamp < wh.startTime) { + go currently_it_do_nothing(wh.c, message.Info.RemoteJid) + } +} + +func (wh *waHandler) HandleVideoMessage(message whatsapp.VideoMessage) { + if !(message.Info.Timestamp < wh.startTime) { + go currently_it_do_nothing(wh.c, message.Info.RemoteJid) + } +} + +func (wh *waHandler) HandleContactMessage(message whatsapp.ContactMessage) { + if !(message.Info.Timestamp < wh.startTime) { + go currently_it_do_nothing(wh.c, message.Info.RemoteJid) + } +} + +// need to test if the greeting is function well and return nothing after send message +func greeting(wac *whatsapp.Conn, RJID string, message string){ + for gIndex := range(BuildGreetings) { + for pIndex := range(BuildGreetings[gIndex].ExpectedUsers) { + if(strings.Split(RJID, "@")[1] == "g.us" && BuildGreetings[gIndex].ExpectedUsers[pIndex] == "any"){ + fmt.Println("The any default message is enabled, and only accepted by direct message") + return + } + if(BuildGreetings[gIndex].ExpectedUsers[pIndex] == RJID || BuildGreetings[gIndex].ExpectedUsers[pIndex] == "any"){ + url := BuildGreetings[gIndex].Webhook.URL + + logGreeting := utils.LogGreeting { + Message: message, + PhoneNumber: strings.Split(RJID, "@")[0], + } + + switch BuildGreetings[gIndex].Webhook.Service { + case "DISCORD": + _, LogErr := LogToDiscord(url, logGreeting) + if LogErr != nil { + log.Println("Fail to Log : " + LogErr.Error()) + } + case "WEBHOOK": + _, LogErr := LogToWebhook(url, logGreeting) + if LogErr != nil { + log.Println("Fail to Log : " + LogErr.Error()) + } + } + + go sendMessage(wac, BuildGreetings[gIndex].Message, RJID) + + return + } + } + } +} \ No newline at end of file diff --git a/wa_image_handler.go b/wa_image_handler.go new file mode 100644 index 0000000..b862326 --- /dev/null +++ b/wa_image_handler.go @@ -0,0 +1,146 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + + "os" + "fmt" + "log" + "time" + "strings" + "strconv" + + whatsapp "github.com/Rhymen/go-whatsapp" + + "github.com/eFishery/NeMo/utils" +) + +func (wh *waHandler) HandleImageMessage(message whatsapp.ImageMessage) { + if !(message.Info.Timestamp < wh.startTime) { + + phone_number := strings.Split(message.Info.RemoteJid, "@")[0] + + // if the user suddenly sent the image this will trigger error because there is no available session + Sessions, err := loadSession(phone_number) + if err != nil { + log.Println(phone_number + " sent me image but it does nothing") + return + } + + if Sessions.CurrentProcess == "" { + log.Println(phone_number + " sent me image but it does nothing") + return + } + + if Sessions.ProcessStatus == "WAIT_ANSWER" { + + var coral utils.Coral + coral.GetCoral(Sessions.CurrentProcess) + sIndex := Sessions.CurrentQuestionSlug + + // prevent for user put image on any rule + if coral.Process.Questions[sIndex].Question.Validation.Rule != "image" { + go sendMessage(wh.c, coral.Process.Questions[sIndex].Question.Validation.Message, message.Info.RemoteJid) + } + + data, err := message.Download() + if err != nil { + if err != whatsapp.ErrMediaDownloadFailedWith410 && err != whatsapp.ErrMediaDownloadFailedWith404 { + return + } + if _, err = wh.c.LoadMediaInfo(message.Info.RemoteJid, message.Info.Id, strconv.FormatBool(message.Info.FromMe)); err == nil { + data, err = message.Download() + if err != nil { + return + } + } + } + filename := fmt.Sprintf("%v/%v.%v", os.TempDir(), message.Info.Id, strings.Split(message.Type, "/")[1]) + file, err := os.Create(filename) + defer file.Close() + if err != nil { + return + } + _, err = file.Write(data) + if err != nil { + return + } + log.Printf("%v %v\n\timage received, saved at:%v\n", message.Info.Timestamp, message.Info.RemoteJid, filename) + + uploadS3 := Settings.AddFileToS3(filename) + + log.Println("Files Uploaded and here is the link : " + uploadS3) + + reply := "terminate" + + waktu, err := time.Parse(time.RFC3339, Sessions.Expired) + + if err != nil { + fmt.Println(err) + } + + if waktu.Before(time.Now()) { + reply = "Sesi anda susah habis, silahkan ulangi lagi" + Sessions.ProcessStatus = "DONE" + file, _ := json.MarshalIndent(Sessions, "", " ") + _ = ioutil.WriteFile(utils.FileSession(phone_number), file, 0644) + + if reply != "timeout" { + go sendMessage(wh.c, reply, message.Info.RemoteJid) + } + + return + } + + if sIndex >= (len(coral.Process.Questions)-1) { + reply = coral.Process.EndMessage + Sessions.ProcessStatus = "DONE" + Sessions.Finished = time.Now().Format(time.RFC3339) + }else{ + reply = coral.Process.Questions[sIndex+1].Question.Asking + Sessions.ProcessStatus = "NEXT" + Sessions.CurrentQuestionSlug = sIndex+1 + } + + dataBaru := utils.Data{ + Slug: coral.Process.Questions[sIndex].Question.Slug, + Question: coral.Process.Questions[sIndex].Question.Asking, + Answer: uploadS3, + Created: time.Now().Format(time.RFC3339), + } + + Sessions.Datas = append(Sessions.Datas, dataBaru) + + go saveSession(Sessions, phone_number) + + if coral.Commands.Record { + switch coral.Webhook.Service { + case "DISCORD": + _, errSent := SentToDiscord(coral.Webhook.URL, Sessions) + if errSent != nil { + log.Println(errSent.Error()) + } + case "WEBHOOK": + _, errSent := SentToWebhook(coral.Webhook.URL, Sessions) + if errSent != nil { + log.Println(errSent.Error()) + } + } + } + + if reply != "timeout" { + if Sessions.ProcessStatus != "WAIT_ANSWER" { + go sendMessage(wh.c, reply, message.Info.RemoteJid) + + if Sessions.ProcessStatus != "DONE" { + log.Println(Sessions.ProcessStatus) + Sessions.ProcessStatus = "WAIT_ANSWER" + } + + go saveSession(Sessions, phone_number) + } + } + } + } +} \ No newline at end of file diff --git a/wa_text_handler.go b/wa_text_handler.go new file mode 100644 index 0000000..e332038 --- /dev/null +++ b/wa_text_handler.go @@ -0,0 +1,211 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + + "fmt" + "log" + "time" + "strings" + "regexp" + + whatsapp "github.com/Rhymen/go-whatsapp" + + "github.com/eFishery/NeMo/utils" +) + + +func (wh *waHandler) HandleTextMessage(message whatsapp.TextMessage) { + + var Sessions utils.Session + + // Check the existing commands + for index := range BuildCommands { + + // if the user force a new command while in the progress of session, break session and create a new one + + phone_number := strings.Split(message.Info.RemoteJid, "@")[0] + + cur_cmd := fmt.Sprintf("%s%s", BuildCommands[index].Prefix, BuildCommands[index].Command ) + if !strings.Contains(strings.ToLower(message.Text), cur_cmd) || message.Info.Timestamp < wh.startTime { + continue + } + + reply := "timeout" + process := BuildCommands[index].RunProcess + var coral utils.Coral + coral.GetCoral(process) + + if len(coral.ExpectedUsers) > 0 { + for usersIndex := range(coral.ExpectedUsers) { + if coral.ExpectedUsers[usersIndex] == phone_number || coral.ExpectedUsers[usersIndex] == "any" { + break + } + if len(coral.ExpectedUsers)-1 == usersIndex { + log.Println(phone_number + " Trying to command " + cur_cmd + " for coral " + process + ", but not as expected users") + return + } + } + } + + sepparator := fmt.Sprintf("%s%s ", coral.Commands.Prefix, coral.Commands.Command) + var question = "" + if len(strings.Split(message.Text, strings.ToLower(sepparator))) > 1 { + question = strings.Split(message.Text, strings.ToLower(sepparator))[1] + } + + dataBaru := utils.Data{ + Slug: "", + Question: question, + Answer: "", + Created: time.Now().Format(time.RFC3339), + } + + Sessions.Datas = append(Sessions.Datas, dataBaru) + reply, parserErr := nemoParser(BuildCommands[index].Message, Sessions) + if parserErr != nil { + log.Println(parserErr.Error()) + return + } + + if reply != "timeout" { + go sendMessage(wh.c, reply, message.Info.RemoteJid) + } + + time.Sleep(time.Duration(3) * time.Second) + + if BuildCommands[index].RunProcess != "" && coral.Commands.RunProcess { + savedSession := newSession(phone_number, process, coral.Process.Timeout) + + reply = coral.Process.Questions[savedSession.CurrentQuestionSlug].Question.Asking + + if reply != "timeout" { + go sendMessage(wh.c, reply, message.Info.RemoteJid) + } + } + + return + } + + // Check the message replied + if !(message.Info.Timestamp < wh.startTime) { + + log.Println(message.Info.RemoteJid + ": " + message.Text) + + // check the previous message who send the message, if bot, check the message, if still same, just keep silent, if not continue + // if user reply then can do + + phone_number := strings.Split(message.Info.RemoteJid, "@")[0] + Sessions, err := loadSession(phone_number) + if err != nil { + go greeting(wh.c, message.Info.RemoteJid, message.Text) + return + } + + if Sessions.CurrentProcess == "" { + go greeting(wh.c, message.Info.RemoteJid, message.Text) + return + } + + if Sessions.ProcessStatus == "DONE" || Sessions.ProcessStatus == "" { + go greeting(wh.c, message.Info.RemoteJid, message.Text) + } + + if Sessions.ProcessStatus == "WAIT_ANSWER" { + reply := "terminate" + sIndex := Sessions.CurrentQuestionSlug + + var coral utils.Coral + coral.GetCoral(Sessions.CurrentProcess) + + waktu, err := time.Parse(time.RFC3339, Sessions.Expired) + if err != nil { + fmt.Println(err) + } + + if waktu.Before(time.Now()) { + Sessions.ProcessStatus = "DONE" + Sessions.Finished = time.Now().Format(time.RFC3339) + + go saveSession(Sessions, phone_number) + go sendMessage(wh.c, "Sesi anda susah habis, silahkan ulangi lagi", message.Info.RemoteJid) + + return + } + + exit_cmd := fmt.Sprintf("%s%s", coral.Process.ExitCommand.Prefix, coral.Process.ExitCommand.Command) + + if message.Text == exit_cmd { + Sessions.ProcessStatus = "DONE" + Sessions.Finished = time.Now().Format(time.RFC3339) + + go saveSession(Sessions, phone_number) + go sendMessage(wh.c, coral.Process.ExitCommand.Message, message.Info.RemoteJid) + + return + } + + if coral.Process.Questions[sIndex].Question.Validation.Rule == "image" { + go sendMessage(wh.c, coral.Process.Questions[sIndex].Question.Validation.Message, message.Info.RemoteJid) + return + } + + match, err := regexp.MatchString(coral.Process.Questions[sIndex].Question.Validation.Rule, message.Text) + if !match { + go sendMessage(wh.c, coral.Process.Questions[sIndex].Question.Validation.Message, message.Info.RemoteJid) + return + } + + if sIndex >= (len(coral.Process.Questions)-1) { + reply = coral.Process.EndMessage + Sessions.ProcessStatus = "DONE" + Sessions.Finished = time.Now().Format(time.RFC3339) + }else{ + reply = coral.Process.Questions[sIndex+1].Question.Asking + Sessions.ProcessStatus = "NEXT" + Sessions.CurrentQuestionSlug = sIndex+1 + } + + dataBaru := utils.Data{ + Slug: coral.Process.Questions[sIndex].Question.Slug, + Question: coral.Process.Questions[sIndex].Question.Asking, + Answer: message.Text, + Created: time.Now().Format(time.RFC3339), + } + + Sessions.Datas = append(Sessions.Datas, dataBaru) + + go saveSession(Sessions, phone_number) + + if coral.Commands.Record { + switch coral.Webhook.Service { + case "DISCORD": + _, errSent := SentToDiscord(coral.Webhook.URL, Sessions) + if errSent != nil { + log.Println(errSent.Error()) + } + case "WEBHOOK": + _, errSent := SentToWebhook(coral.Webhook.URL, Sessions) + if errSent != nil { + log.Println(errSent.Error()) + } + } + } + + if reply != "timeout" { + if Sessions.ProcessStatus != "WAIT_ANSWER" { + go sendMessage(wh.c, reply, message.Info.RemoteJid) + + if Sessions.ProcessStatus != "DONE" { + Sessions.ProcessStatus = "WAIT_ANSWER" + } + + file, _ := json.MarshalIndent(Sessions, "", " ") + _ = ioutil.WriteFile(utils.FileSession(phone_number), file, 0644) + } + } + } + } + return +} diff --git a/webhook.go b/webhook.go index cfe21ea..f1a146f 100644 --- a/webhook.go +++ b/webhook.go @@ -3,9 +3,11 @@ package main import ( "log" req "github.com/imroc/req" + + "github.com/eFishery/NeMo/utils" ) -func SentToWebhook(url string, Sessions Session) (int, error) { +func SentToWebhook(url string, Sessions utils.Session) (int, error) { r, err := req.Post(url, req.BodyJSON(Sessions)) if err != nil { return 500, err @@ -16,7 +18,7 @@ func SentToWebhook(url string, Sessions Session) (int, error) { return resp.StatusCode, nil } -func SentToDiscord(url string, Sessions Session) (bool, error) { +func SentToDiscord(url string, Sessions utils.Session) (bool, error) { // req.Debug = true compiled_message := "\n[" + Sessions.Created + "]\n" + Sessions.CurrentProcess for index := range(Sessions.Datas) { @@ -25,7 +27,7 @@ func SentToDiscord(url string, Sessions Session) (bool, error) { compiled_message = compiled_message + "\n" + Sessions.PhoneNumber + ": " + Sessions.Datas[index].Answer } - var Discord = discord { + var Discord = utils.Discord { Content: compiled_message, } _, err := req.Post(url, req.BodyJSON(Discord)) @@ -37,7 +39,7 @@ func SentToDiscord(url string, Sessions Session) (bool, error) { return true, nil } -func LogToWebhook(url string, logGreeting LogGreeting) (int, error) { +func LogToWebhook(url string, logGreeting utils.LogGreeting) (int, error) { r, err := req.Post(url, req.BodyJSON(logGreeting)) if err != nil { return 500, err @@ -48,11 +50,11 @@ func LogToWebhook(url string, logGreeting LogGreeting) (int, error) { return resp.StatusCode, nil } -func LogToDiscord(url string, logGreeting LogGreeting) (bool, error) { +func LogToDiscord(url string, logGreeting utils.LogGreeting) (bool, error) { // req.Debug = true compiled_message := logGreeting.PhoneNumber + " replied with " + logGreeting.Message - var Discord = discord { + var Discord = utils.Discord { Content: compiled_message, } _, err := req.Post(url, req.BodyJSON(Discord)) diff --git a/whatsappHandler.go b/whatsappHandler.go deleted file mode 100644 index 00c79e2..0000000 --- a/whatsappHandler.go +++ /dev/null @@ -1,406 +0,0 @@ -package main - -import ( - "encoding/json" - "io/ioutil" - - "os" - "fmt" - "log" - "time" - "strings" - "regexp" - "strconv" - - whatsapp "github.com/Rhymen/go-whatsapp" -) - -func (wh *waHandler) HandleImageMessage(message whatsapp.ImageMessage) { - if !(message.Info.Timestamp < wh.startTime) { - - phone_number := strings.Split(message.Info.RemoteJid, "@")[0] - - // if the user suddenly sent the image this will trigger error because there is no available session - Sessions, err := loadSession(phone_number) - if err != nil { - log.Println(phone_number + " sent me image but it does nothing") - return - } - - if Sessions.CurrentProcess == "" { - log.Println(phone_number + " sent me image but it does nothing") - return - } - - if Sessions.ProcessStatus == "WAIT_ANSWER" { - - var coral Coral - coral.getCoral(Sessions.CurrentProcess) - sIndex := Sessions.CurrentQuestionSlug - - // prevent for user put image on any rule - if coral.Process.Questions[sIndex].Question.Validation.Rule != "image" { - go sendMessage(wh.c, coral.Process.Questions[sIndex].Question.Validation.Message, message.Info.RemoteJid) - } - - data, err := message.Download() - if err != nil { - if err != whatsapp.ErrMediaDownloadFailedWith410 && err != whatsapp.ErrMediaDownloadFailedWith404 { - return - } - if _, err = wh.c.LoadMediaInfo(message.Info.RemoteJid, message.Info.Id, strconv.FormatBool(message.Info.FromMe)); err == nil { - data, err = message.Download() - if err != nil { - return - } - } - } - filename := fmt.Sprintf("%v/%v.%v", os.TempDir(), message.Info.Id, strings.Split(message.Type, "/")[1]) - file, err := os.Create(filename) - defer file.Close() - if err != nil { - return - } - _, err = file.Write(data) - if err != nil { - return - } - log.Printf("%v %v\n\timage received, saved at:%v\n", message.Info.Timestamp, message.Info.RemoteJid, filename) - - uploadS3 := AddFileToS3(filename) - - log.Println("Files Uploaded and here is the link : " + uploadS3) - - reply := "terminate" - - waktu, err := time.Parse(time.RFC3339, Sessions.Expired) - - if err != nil { - fmt.Println(err) - } - - if waktu.Before(time.Now()) { - reply = "Sesi anda susah habis, silahkan ulangi lagi" - Sessions.ProcessStatus = "DONE" - file, _ := json.MarshalIndent(Sessions, "", " ") - _ = ioutil.WriteFile(fileSession(phone_number), file, 0644) - - if reply != "timeout" { - go sendMessage(wh.c, reply, message.Info.RemoteJid) - } - - return - } - - if sIndex >= (len(coral.Process.Questions)-1) { - reply = coral.Process.EndMessage - Sessions.ProcessStatus = "DONE" - Sessions.Finished = time.Now().Format(time.RFC3339) - }else{ - reply = coral.Process.Questions[sIndex+1].Question.Asking - Sessions.ProcessStatus = "NEXT" - Sessions.CurrentQuestionSlug = sIndex+1 - } - - dataBaru := Data{ - Slug: coral.Process.Questions[sIndex].Question.Slug, - Question: coral.Process.Questions[sIndex].Question.Asking, - Answer: uploadS3, - Created: time.Now().Format(time.RFC3339), - } - - Sessions.Datas = append(Sessions.Datas, dataBaru) - - go saveSession(Sessions, phone_number) - - if coral.Commands.Record { - switch coral.Webhook.Service { - case "DISCORD": - _, errSent := SentToDiscord(coral.Webhook.URL, Sessions) - if errSent != nil { - log.Println(errSent.Error()) - } - case "WEBHOOK": - _, errSent := SentToWebhook(coral.Webhook.URL, Sessions) - if errSent != nil { - log.Println(errSent.Error()) - } - } - } - - if reply != "timeout" { - if Sessions.ProcessStatus != "WAIT_ANSWER" { - go sendMessage(wh.c, reply, message.Info.RemoteJid) - - if Sessions.ProcessStatus != "DONE" { - log.Println(Sessions.ProcessStatus) - Sessions.ProcessStatus = "WAIT_ANSWER" - } - - go saveSession(Sessions, phone_number) - } - } - } - } -} - -func (wh *waHandler) HandleTextMessage(message whatsapp.TextMessage) { - - var Sessions Session - - // Check the existing commands - for index := range(BuildCommands) { - - // if the user force a new command while in the progress of session, break session and create a new one - - phone_number := strings.Split(message.Info.RemoteJid, "@")[0] - - cur_cmd := fmt.Sprintf("%s%s", BuildCommands[index].Prefix, BuildCommands[index].Command ) - if !strings.Contains(strings.ToLower(message.Text), cur_cmd) || message.Info.Timestamp < wh.startTime { - continue - } - - reply := "timeout" - process := BuildCommands[index].RunProcess - var coral Coral - coral.getCoral(process) - - if len(coral.ExpectedUsers) > 0 { - for usersIndex := range(coral.ExpectedUsers) { - if coral.ExpectedUsers[usersIndex] == phone_number || coral.ExpectedUsers[usersIndex] == "any" { - break - } - if len(coral.ExpectedUsers)-1 == usersIndex { - log.Println(phone_number + " Trying to command " + cur_cmd + " for coral " + process + ", but not as expected users") - return - } - } - } - - sepparator := fmt.Sprintf("%s%s ", coral.Commands.Prefix, coral.Commands.Command) - var question = "" - if len(strings.Split(message.Text, strings.ToLower(sepparator))) > 1 { - question = strings.Split(message.Text, strings.ToLower(sepparator))[1] - } - - dataBaru := Data{ - Slug: "", - Question: question, - Answer: "", - Created: time.Now().Format(time.RFC3339), - } - - Sessions.Datas = append(Sessions.Datas, dataBaru) - reply, parserErr := nemoParser(BuildCommands[index].Message, Sessions) - if parserErr != nil { - log.Println(parserErr.Error()) - return - } - - if reply != "timeout" { - go sendMessage(wh.c, reply, message.Info.RemoteJid) - } - - time.Sleep(time.Duration(3) * time.Second) - - if BuildCommands[index].RunProcess != "" && coral.Commands.RunProcess { - savedSession := newSession(phone_number, process, coral.Process.Timeout) - - reply = coral.Process.Questions[savedSession.CurrentQuestionSlug].Question.Asking - - if reply != "timeout" { - go sendMessage(wh.c, reply, message.Info.RemoteJid) - } - } - - return - } - - // Check the message replied - if !(message.Info.Timestamp < wh.startTime) { - - log.Println(message.Info.RemoteJid + ": " + message.Text) - - // check the previous message who send the message, if bot, check the message, if still same, just keep silent, if not continue - // if user reply then can do - - phone_number := strings.Split(message.Info.RemoteJid, "@")[0] - Sessions, err := loadSession(phone_number) - if err != nil { - go greeting(wh.c, message.Info.RemoteJid, message.Text) - return - } - - if Sessions.CurrentProcess == "" { - go greeting(wh.c, message.Info.RemoteJid, message.Text) - return - } - - if Sessions.ProcessStatus == "DONE" || Sessions.ProcessStatus == "" { - go greeting(wh.c, message.Info.RemoteJid, message.Text) - } - - if Sessions.ProcessStatus == "WAIT_ANSWER" { - reply := "terminate" - sIndex := Sessions.CurrentQuestionSlug - - var coral Coral - coral.getCoral(Sessions.CurrentProcess) - - waktu, err := time.Parse(time.RFC3339, Sessions.Expired) - if err != nil { - fmt.Println(err) - } - - if waktu.Before(time.Now()) { - Sessions.ProcessStatus = "DONE" - Sessions.Finished = time.Now().Format(time.RFC3339) - - go saveSession(Sessions, phone_number) - go sendMessage(wh.c, "Sesi anda susah habis, silahkan ulangi lagi", message.Info.RemoteJid) - - return - } - - exit_cmd := fmt.Sprintf("%s%s", coral.Process.ExitCommand.Prefix, coral.Process.ExitCommand.Command) - - if message.Text == exit_cmd { - Sessions.ProcessStatus = "DONE" - Sessions.Finished = time.Now().Format(time.RFC3339) - - go saveSession(Sessions, phone_number) - go sendMessage(wh.c, coral.Process.ExitCommand.Message, message.Info.RemoteJid) - - return - } - - if coral.Process.Questions[sIndex].Question.Validation.Rule == "image" { - go sendMessage(wh.c, coral.Process.Questions[sIndex].Question.Validation.Message, message.Info.RemoteJid) - return - } - - match, err := regexp.MatchString(coral.Process.Questions[sIndex].Question.Validation.Rule, message.Text) - if !match { - go sendMessage(wh.c, coral.Process.Questions[sIndex].Question.Validation.Message, message.Info.RemoteJid) - return - } - - if sIndex >= (len(coral.Process.Questions)-1) { - reply = coral.Process.EndMessage - Sessions.ProcessStatus = "DONE" - Sessions.Finished = time.Now().Format(time.RFC3339) - }else{ - reply = coral.Process.Questions[sIndex+1].Question.Asking - Sessions.ProcessStatus = "NEXT" - Sessions.CurrentQuestionSlug = sIndex+1 - } - - dataBaru := Data{ - Slug: coral.Process.Questions[sIndex].Question.Slug, - Question: coral.Process.Questions[sIndex].Question.Asking, - Answer: message.Text, - Created: time.Now().Format(time.RFC3339), - } - - Sessions.Datas = append(Sessions.Datas, dataBaru) - - go saveSession(Sessions, phone_number) - - if coral.Commands.Record { - switch coral.Webhook.Service { - case "DISCORD": - _, errSent := SentToDiscord(coral.Webhook.URL, Sessions) - if errSent != nil { - log.Println(errSent.Error()) - } - case "WEBHOOK": - _, errSent := SentToWebhook(coral.Webhook.URL, Sessions) - if errSent != nil { - log.Println(errSent.Error()) - } - } - } - - if reply != "timeout" { - if Sessions.ProcessStatus != "WAIT_ANSWER" { - go sendMessage(wh.c, reply, message.Info.RemoteJid) - - if Sessions.ProcessStatus != "DONE" { - Sessions.ProcessStatus = "WAIT_ANSWER" - } - - file, _ := json.MarshalIndent(Sessions, "", " ") - _ = ioutil.WriteFile(fileSession(phone_number), file, 0644) - } - } - } - } - return -} - -func currently_it_do_nothing(wac *whatsapp.Conn, RJID string) { - phone_number := strings.Split(RJID, "@")[0] - - // if the user suddenly sent the image this will trigger error because there is no available session - // need to test this - _, err := loadSession(phone_number) - if err != nil { - log.Println("I don't know what you do but it do nothing") - return - } -} - -func (wh *waHandler) HandleDocumentMessage(message whatsapp.DocumentMessage) { - if !(message.Info.Timestamp < wh.startTime) { - go currently_it_do_nothing(wh.c, message.Info.RemoteJid) - } -} - -func (wh *waHandler) HandleVideoMessage(message whatsapp.VideoMessage) { - if !(message.Info.Timestamp < wh.startTime) { - go currently_it_do_nothing(wh.c, message.Info.RemoteJid) - } -} - -func (wh *waHandler) HandleContactMessage(message whatsapp.ContactMessage) { - if !(message.Info.Timestamp < wh.startTime) { - go currently_it_do_nothing(wh.c, message.Info.RemoteJid) - } -} - -// need to test if the greeting is function well and return nothing after send message -func greeting(wac *whatsapp.Conn, RJID string, message string){ - for gIndex := range(BuildGreetings) { - for pIndex := range(BuildGreetings[gIndex].ExpectedUsers) { - if(strings.Split(RJID, "@")[1] == "g.us" && BuildGreetings[gIndex].ExpectedUsers[pIndex] == "any"){ - fmt.Println("The any default message is enabled, and only accepted by direct message") - return - } - if(BuildGreetings[gIndex].ExpectedUsers[pIndex] == RJID || BuildGreetings[gIndex].ExpectedUsers[pIndex] == "any"){ - url := BuildGreetings[gIndex].Webhook.URL - - logGreeting := LogGreeting { - Message: message, - PhoneNumber: strings.Split(RJID, "@")[0], - } - - switch BuildGreetings[gIndex].Webhook.Service { - case "DISCORD": - _, LogErr := LogToDiscord(url, logGreeting) - if LogErr != nil { - log.Println("Fail to Log : " + LogErr.Error()) - } - case "WEBHOOK": - _, LogErr := LogToWebhook(url, logGreeting) - if LogErr != nil { - log.Println("Fail to Log : " + LogErr.Error()) - } - } - - go sendMessage(wac, BuildGreetings[gIndex].Message, RJID) - - return - } - } - } -} \ No newline at end of file