package main import ( "bytes" "fmt" "io" "io/ioutil" "math/rand" "mime/multipart" "net/http" "net/textproto" "os" "strconv" "strings" "time" "github.com/nlopes/slack" "github.com/unixvoid/glogger" "gopkg.in/gcfg.v1" "gopkg.in/redis.v5" ) type Config struct { Lorebot struct { Loglevel string SlackDebug bool BootstrapDelay time.Duration AuthFile string } Redis struct { Host string Password string } } var ( config = Config{} ) func main() { // read in config file readConf() // initialize the logger with the configured loglevel initLogger(config.Lorebot.Loglevel) // initialize redis connection redisClient, err := initRedisConnection() if err != nil { glogger.Debug.Printf("redis conneciton cannot be made, trying again in %d seconds", config.Lorebot.BootstrapDelay) time.Sleep(config.Lorebot.BootstrapDelay * time.Second) redisClient, err = initRedisConnection() if err != nil { glogger.Error.Println("redis connection cannot be made.") os.Exit(1) } } glogger.Debug.Println("connection to redis succeeded.") glogger.Info.Println("link to redis on", config.Redis.Host) // read in creds credentials, _ := ioutil.ReadFile(config.Lorebot.AuthFile) lines := strings.Split(string(credentials), "\n") auth := lines[0] api := slack.New(auth) rtm := api.NewRTM() go rtm.ManageConnection() Loop: for { select { case msg := <-rtm.IncomingEvents: switch ev := msg.Data.(type) { case *slack.MessageEvent: if ev.Type == "message" { glogger.Debug.Printf("processing: %s", ev.Text) // see if the message begins with '.', it could be a command if len(ev.Text) == 0 { // nil message, dont print //glogger.Debug.Println("breaking on blank message") break } else { comm := fmt.Sprintf("%c", ev.Text[0]) if comm == "." { if strings.Count(ev.Text, " ") == 0 { // no spaces, its a static command staticCommandHandler(rtm, ev, redisClient) } else { // spaces, dynamic handler dynamicCommandHandler(rtm, ev, redisClient) } } } } case *slack.RTMError: fmt.Printf("Error: %s\n", ev.Error()) case *slack.InvalidAuthEvent: fmt.Printf("Invalid credentials") break Loop default: // ignore all other events //fmt.Printf("Unknown error") //fmt.Printf("%v\n", msg.Data) } } } } func readConf() { // init config file err := gcfg.ReadFileInto(&config, "config.gcfg") if err != nil { panic(fmt.Sprintf("Could not load config.gcfg, error: %s\n", err)) } } func initLogger(logLevel string) { // init logger if logLevel == "debug" { glogger.LogInit(os.Stdout, os.Stdout, os.Stdout, os.Stderr) } else if logLevel == "cluster" { glogger.LogInit(os.Stdout, os.Stdout, ioutil.Discard, os.Stderr) } else if logLevel == "info" { glogger.LogInit(os.Stdout, ioutil.Discard, ioutil.Discard, os.Stderr) } else { glogger.LogInit(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr) } } func initRedisConnection() (*redis.Client, error) { // init redis connection redisClient := redis.NewClient(&redis.Options{ Addr: config.Redis.Host, Password: config.Redis.Password, DB: 0, }) _, redisErr := redisClient.Ping().Result() return redisClient, redisErr } func dynamicCommandHandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { message := ev.Text if strings.Count(message, " ") < 1 { glogger.Debug.Println("not proper syntax") } // verify syntax s := strings.SplitN(message, " ", 2) command, content := s[0], s[1] trimmedPrefix := strings.TrimPrefix(command, ".") switch trimmedPrefix { case "lore": s := strings.SplitN(string(ev.Text), " ", 2) contenthandler(rtm, ev, s[1], "lore", redisClient) case "newlore": if strings.Contains(content, " ") { newhandler(rtm, ev, content, "lore", redisClient) } else { rtm.SendMessage(rtm.NewOutgoingMessage("not proper syntax", ev.Channel)) return } case "newdubs": s := strings.SplitN(string(ev.Text), " ", 2) newdubshandler(rtm, ev, s[1], redisClient) case "rmdubs": s := strings.SplitN(string(ev.Text), " ", 2) rmdubshandler(rtm, ev, s[1], redisClient) case "newlunch": s := strings.SplitN(string(ev.Text), " ", 2) newlunchhandler(rtm, ev, s[1], redisClient) case "rmlunch": s := strings.SplitN(string(ev.Text), " ", 2) rmlunchhandler(rtm, ev, s[1], redisClient) case "newbrb": s := strings.SplitN(string(ev.Text), " ", 2) newbrbhandler(rtm, ev, s[1], redisClient) case "rmbrb": s := strings.SplitN(string(ev.Text), " ", 2) rmbrbhandler(rtm, ev, s[1], redisClient) case "rmlore": rmhandler(rtm, ev, content, "lore", redisClient) case "rek": s := strings.SplitN(string(ev.Text), " ", 2) rekhandler(rtm, ev, s[1], redisClient) case "rip": s := strings.SplitN(string(ev.Text), " ", 2) riphandler(rtm, ev, s[1]) case "summon": s := strings.SplitN(string(ev.Text), " ", 2) summonhandler(rtm, ev, s[1]) case "gnu": s := strings.SplitN(string(ev.Text), " ", 2) gnuhandler(rtm, ev, s[1]) case "created": attrhandler(rtm, ev, "lore", content, "cdate", redisClient) case "owner": attrhandler(rtm, ev, "lore", content, "owner", redisClient) case "removedby": attrhandler(rtm, ev, "lore", content, "rmby", redisClient) case "removed": attrhandler(rtm, ev, "lore", content, "rmdate", redisClient) } } func staticCommandHandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { s := strings.SplitN(ev.Text, " ", 2) command := s[0] trimmedPrefix := strings.TrimPrefix(command, ".") switch trimmedPrefix { case "help": helpmsg(rtm, ev) case "help2": advancedhelpmsg(rtm, ev) case "rng": rnghandler(rtm, ev, redisClient) case "egg": egghandler(rtm, ev, redisClient) case "giveup": giveuphandler(rtm, ev, redisClient) case "depricatedrng": depricatedrnghandler(rtm, ev, redisClient) case "dubs": dubshandler(rtm, ev, redisClient) case "lunch": lunchhandler(rtm, ev, redisClient) case "brb": brbhandler(rtm, ev, redisClient) case "roll": rollhandler(rtm, ev) case "rr": rrhandler(rtm, ev) case "dice": dicerollhandler(rtm, ev) case "ceelo": ceelorollhandler(rtm, ev) case "listen": rtm.SendMessage(rtm.NewOutgoingMessage("*it's what you hearin*", ev.Channel)) case "lorelist": listhandler(redisClient, rtm, ev, "lore", "added") case "removedlore": listhandler(redisClient, rtm, ev, "lore", "removed") case "commandlist": listhandler(redisClient, rtm, ev, "command", "added") case "removedcommands": listhandler(redisClient, rtm, ev, "command", "removed") case "lorestatus": lorestatus(rtm, ev, redisClient) } // get that dice handler if len(ev.Text) >= 2 { if strings.Contains(ev.Text, ".d") { trm := strings.Replace(ev.Text, ".d", "", -1) // check if the rest of the string is an int num, err := strconv.Atoi(trm) if err == nil { //glogger.Debug.Printf("rolling for %d", num) dhandler(rtm, ev, num) } } } } func helpmsg(rtm *slack.RTM, ev *slack.MessageEvent) { help1 := "I am the lore archival bot, I support the following commands:\n" + "```" + ".lore: view lore\n" + ".newlore: add new lore\n" + ".rmlore: remove lore\n" + ".lorelist: view lore database\n" + "use '.help2' to see advanced lookup commands." + "```" rtm.SendMessage(rtm.NewOutgoingMessage(help1, ev.Channel)) } func advancedhelpmsg(rtm *slack.RTM, ev *slack.MessageEvent) { help2 := "```" + ".created view creation timestamp\n" + ".owner: who created the lore\n" + ".removed: view removal timestamp\n" + ".removedby: who removed the lore" + "```" rtm.SendMessage(rtm.NewOutgoingMessage(help2, ev.Channel)) } func contenthandler(rtm *slack.RTM, ev *slack.MessageEvent, message, queryType string, redisClient *redis.Client) { loreTerm := strings.Replace(message, ".lore", "", -1) glogger.Debug.Printf("searching for: %s\n", loreTerm) content, err := loreCheck(loreTerm, queryType, redisClient) if err == nil { // lore found, print it rtm.SendMessage(rtm.NewOutgoingMessage(content, ev.Channel)) } else { // lore not found, print error rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("lore '%s' not found\n", loreTerm), ev.Channel)) } } func attrhandler(rtm *slack.RTM, ev *slack.MessageEvent, queryType, queryAttr, message string, redisClient *redis.Client) { query, err := loreAttrQuery(message, queryType, queryAttr, redisClient) if err == nil { rtm.SendMessage(rtm.NewOutgoingMessage(query, ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("attribute does not exist", ev.Channel)) } } func rekhandler(rtm *slack.RTM, ev *slack.MessageEvent, target string, redisClient *redis.Client) { rektBit, _ := redisClient.SRandMember("index:rekt").Result() rekFmt := fmt.Sprintf("%s %s", target, rektBit) rtm.SendMessage(rtm.NewOutgoingMessage(rekFmt, ev.Channel)) } func summonhandler(rtm *slack.RTM, ev *slack.MessageEvent, target string) { sumFmt := fmt.Sprintf("つ◕_◕)つ %s つ◕_◕)つ", target) rtm.SendMessage(rtm.NewOutgoingMessage(sumFmt, ev.Channel)) } func riphandler(rtm *slack.RTM, ev *slack.MessageEvent, target string) { // create a timestamp to use t := time.Now() tstamp := t.Format("01-02-2006") ripFmt := fmt.Sprintf("here lies %s, disrespected to death %s..", target, tstamp) rtm.SendMessage(rtm.NewOutgoingMessage(ripFmt, ev.Channel)) } func rollhandler(rtm *slack.RTM, ev *slack.MessageEvent) { //fmt.Print(rand.Intn(100)) rndStr := fmt.Sprintf("%08d", rand.Int63n(1e8)) rtm.SendMessage(rtm.NewOutgoingMessage(rndStr, ev.Channel)) } func dicerollhandler(rtm *slack.RTM, ev *slack.MessageEvent) { //fmt.Print(rand.Intn(100)) rand.Seed(time.Now().UnixNano()) rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("%d", rand.Intn(6)+1), ev.Channel)) } func ceelorollhandler(rtm *slack.RTM, ev *slack.MessageEvent) { rand.Seed(time.Now().UnixNano()) // set condition hand := false for !hand { roll0 := rand.Intn(6) + 1 roll1 := rand.Intn(6) + 1 roll2 := rand.Intn(6) + 1 rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("%d, %d, %d", roll0, roll1, roll2), ev.Channel)) // check for pair if (roll0 == roll1) || (roll0 == roll2) || (roll1 == roll2) { hand = true } a := fmt.Sprintf("%d%d%d", roll0, roll1, roll2) // check for instant win if strings.Contains(a, "4") && strings.Contains(a, "5") && strings.Contains(a, "6") { hand = true } // check for instant loss if strings.Contains(a, "1") && strings.Contains(a, "2") && strings.Contains(a, "3") { hand = true } time.Sleep(1000 * time.Millisecond) } } func rrhandler(rtm *slack.RTM, ev *slack.MessageEvent) { //fmt.Print(rand.Intn(100)) dice := []string{"*click*", "*BANG*", "*click*", "*click*", "*click*", "*click*"} rand.Seed(time.Now().UnixNano()) rndStr := dice[rand.Intn(len(dice)-1)] rtm.SendMessage(rtm.NewOutgoingMessage(rndStr, ev.Channel)) } func dhandler(rtm *slack.RTM, ev *slack.MessageEvent, num int) { if (num > 0) && (num < 99999999) { rand.Seed(time.Now().UnixNano()) rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("%d", rand.Intn(num)+1), ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("na fam, we need a different number. stay lit tho", ev.Channel)) } } func depricatedrnghandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { // get random lore from list rngLore, err := redisClient.SRandMember("index:lore:added").Result() if err != nil { fmt.Printf("%v\n", err) } // get lore content rngContent, err := loreCheck(rngLore, "lore", redisClient) if err != nil { fmt.Printf("%v\n", err) rtm.SendMessage(rtm.NewOutgoingMessage("beep boop.. broken bot..", ev.Channel)) return } rngMsg := fmt.Sprintf("Random lore: `%s`\n%s", rngLore, rngContent) rtm.SendMessage(rtm.NewOutgoingMessage(rngMsg, ev.Channel)) } func rnghandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { // aka index:command:added newType := fmt.Sprintf("index:lore:added") //command, err := botfunc.Query(newType, redisClient) command, err := loreQuery(newType, redisClient) if err != nil { glogger.Error.Println(err) println(err) } // if the array is empty, exit early if len(command) <= 0 { return } // pick a random one rand.Seed(time.Now().UnixNano()) rngLore := fmt.Sprint(command[rand.Intn(len(command))]) glogger.Debug.Printf("rng lore: %s\n", rngLore) // get lore content rngContent, err := loreCheck(rngLore, "lore", redisClient) if err != nil { glogger.Error.Printf("%v\n", err) rtm.SendMessage(rtm.NewOutgoingMessage("beep boop.. broken bot..", ev.Channel)) return } glogger.Debug.Printf("rng content: %s\n", rngContent) rngMsg := fmt.Sprintf("Random lore: `%s`\n%s", rngLore, rngContent) rtm.SendMessage(rtm.NewOutgoingMessage(rngMsg, ev.Channel)) } func egghandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { // aka index:command:added newType := fmt.Sprintf("index:lore:added") //command, err := botfunc.Query(newType, redisClient) command, err := loreQuery(newType, redisClient) if err != nil { glogger.Error.Println(err) println(err) } // if the array is empty, exit early if len(command) <= 0 { return } // pick a random one rand.Seed(time.Now().UnixNano()) rngLore := fmt.Sprint(command[rand.Intn(len(command))]) println(rngLore) // get lore content rngContent, err := loreCheck(rngLore, "lore", redisClient) if err != nil { fmt.Printf("%v\n", err) rtm.SendMessage(rtm.NewOutgoingMessage("beep boop.. broken bot..", ev.Channel)) return } // set the lore in case we cant find it. redisClient.Del("index:egg") err = redisClient.Set("index:egg", rngLore, 0).Err() if err != nil { glogger.Error.Println("error updating redis egg index") } rngMsg := fmt.Sprintf("begin the hunt boiiz...\n ```%s```", rngContent) rtm.SendMessage(rtm.NewOutgoingMessage(rngMsg, ev.Channel)) } func giveuphandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { giveup, err := redisClient.Get("index:egg").Result() if err != nil { glogger.Error.Println("error getting redis egg index") glogger.Error.Println(err) } giveupMsg := fmt.Sprintf("foolz.. it was `%s`", giveup) rtm.SendMessage(rtm.NewOutgoingMessage(giveupMsg, ev.Channel)) } func gnuhandler(rtm *slack.RTM, ev *slack.MessageEvent, target string) { gnuFmt := fmt.Sprintf("slaps the nonfree software out of %s's hands", target) rtm.SendMessage(rtm.NewOutgoingMessage(gnuFmt, ev.Channel)) } func listhandler(redisClient *redis.Client, rtm *slack.RTM, ev *slack.MessageEvent, listType, listAttr string) { // aka index:command:added newType := fmt.Sprintf("%s:%s:%s", "index", listType, listAttr) //command, err := botfunc.Query(newType, redisClient) command, err := loreQuery(newType, redisClient) if err != nil { glogger.Error.Println(err) println(err) } fCommand := strings.Join(command, "\n") // initialize buffer bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) // create header and body. filename must be set mh := make(textproto.MIMEHeader) mh.Set("Content-Type", "application/octet-stream") mh.Set("Content-Disposition", "form-data; name=\"file\"; filename=\" \"") // write body with mime header partWriter, _ := bodyWriter.CreatePart(mh) io.Copy(partWriter, bytes.NewBufferString(fCommand)) contentType := bodyWriter.FormDataContentType() bodyWriter.Close() // set content type to multipart/form-data, required for bitnuke.io h := make(textproto.MIMEHeader) h.Set("Content-Type", "multipart/form-data") bodyWriter.CreatePart(h) bodyWriter.Close() // POST to bitnuke.io resp, err := http.Post("https://bitnuke.io/upload", contentType, bodyBuf) if err != nil { println(err) } defer resp.Body.Close() resp_body, err := ioutil.ReadAll(resp.Body) if err != nil { println(err) } // format response to be link fResponse := fmt.Sprintf("https://bitnuke.io/%s", resp_body[:]) // print to channel rtm.SendMessage(rtm.NewOutgoingMessage(fResponse, ev.Channel)) } func lorestatus(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { command, _ := loreQuery("index:lore:added", redisClient) loreammount := len(command) status := fmt.Sprintf("the lore db has %d entries", loreammount) rtm.SendMessage(rtm.NewOutgoingMessage(status, ev.Channel)) } func newhandler(rtm *slack.RTM, ev *slack.MessageEvent, message, newType string, redisClient *redis.Client) { // get nick from slack api user, err := rtm.GetUserInfo(ev.User) if err != nil { fmt.Printf("%s\n", err) return } nick := fmt.Sprintf("%s", user.Name) err = loreNewString(nick, message, newType, redisClient) if err == nil { rtm.SendMessage(rtm.NewOutgoingMessage("entry created", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("entry could not be created", ev.Channel)) } } func newdubshandler(rtm *slack.RTM, ev *slack.MessageEvent, newContent string, redisClient *redis.Client) { // prep content for dubsification // aka remove the '<' and '>' that slack adds newContent = strings.Replace(newContent, "<", "", -1) newContent = strings.Replace(newContent, ">", "", -1) err := redisClient.SAdd("index:dubs", newContent) if err != nil { rtm.SendMessage(rtm.NewOutgoingMessage("dubs checked", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("dubs check failed", ev.Channel)) } } func rmdubshandler(rtm *slack.RTM, ev *slack.MessageEvent, rmContent string, redisClient *redis.Client) { // prep content for dubs removal // aka remove the '<' and '>' that slack adds rmContent = strings.Replace(rmContent, "<", "", -1) rmContent = strings.Replace(rmContent, ">", "", -1) err := redisClient.SRem("index:dubs", rmContent) if err != nil { rtm.SendMessage(rtm.NewOutgoingMessage("dubs unchecked", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("dubs check failed", ev.Channel)) } } func dubshandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { // aka index:command:added newType := fmt.Sprintf("index:dubs") command, err := loreQuery(newType, redisClient) if err != nil { glogger.Error.Println(err) } // if the array is empty, exit early if len(command) <= 0 { return } // pick a random one rand.Seed(time.Now().UnixNano()) rngDubs := fmt.Sprint(command[rand.Intn(len(command))]) rtm.SendMessage(rtm.NewOutgoingMessage(rngDubs, ev.Channel)) // roll after we've proccessed dubs // you already know tho.. //re := regexp.MustCompile(`taylor|swift|2725225`) //if re.MatchString(rngDubs) { // rndInt := 1 + rand.Intn(10-1) // rndStr := fmt.Sprintf("%06d", rand.Int63n(1e6)) // finStr := fmt.Sprintf("%s%d%d", rndStr, rndInt, rndInt) // rtm.SendMessage(rtm.NewOutgoingMessage(finStr, ev.Channel)) //} else { // rollhandler(rtm, ev) //} rollhandler(rtm, ev) } func newlunchhandler(rtm *slack.RTM, ev *slack.MessageEvent, newContent string, redisClient *redis.Client) { // prep content for lunchification // aka remove the '<' and '>' that slack adds newContent = strings.Replace(newContent, "<", "", -1) newContent = strings.Replace(newContent, ">", "", -1) err := redisClient.SAdd("index:lunch", newContent) if err != nil { rtm.SendMessage(rtm.NewOutgoingMessage("lunch location added", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("lunch location addition failed", ev.Channel)) } } func rmlunchhandler(rtm *slack.RTM, ev *slack.MessageEvent, rmContent string, redisClient *redis.Client) { // prep content for lunch removal // aka remove the '<' and '>' that slack adds rmContent = strings.Replace(rmContent, "<", "", -1) rmContent = strings.Replace(rmContent, ">", "", -1) err := redisClient.SRem("index:lunch", rmContent) if err != nil { rtm.SendMessage(rtm.NewOutgoingMessage("lunch location removed", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("lunch location removal failed", ev.Channel)) } } func lunchhandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { // aka index:command:added newType := fmt.Sprintf("index:lunch") command, err := loreQuery(newType, redisClient) if err != nil { glogger.Error.Println(err) } // if the array is empty, exit early if len(command) <= 0 { return } // pick a random one rand.Seed(time.Now().UnixNano()) rngDubs := fmt.Sprint(command[rand.Intn(len(command))]) rtm.SendMessage(rtm.NewOutgoingMessage(rngDubs, ev.Channel)) } func newbrbhandler(rtm *slack.RTM, ev *slack.MessageEvent, newContent string, redisClient *redis.Client) { // prep content for brbification // aka remove the '<' and '>' that slack adds newContent = strings.Replace(newContent, "<", "", -1) newContent = strings.Replace(newContent, ">", "", -1) err := redisClient.SAdd("index:brb", newContent) if err != nil { rtm.SendMessage(rtm.NewOutgoingMessage("tjancism added", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("tjanceism addition failed", ev.Channel)) } } func rmbrbhandler(rtm *slack.RTM, ev *slack.MessageEvent, rmContent string, redisClient *redis.Client) { // prep content for brb removal // aka remove the '<' and '>' that slack adds rmContent = strings.Replace(rmContent, "<", "", -1) rmContent = strings.Replace(rmContent, ">", "", -1) err := redisClient.SRem("index:brb", rmContent) if err != nil { rtm.SendMessage(rtm.NewOutgoingMessage("tjancism removed", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("tjancism removal failed", ev.Channel)) } } func brbhandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { // aka index:command:added newType := fmt.Sprintf("index:brb") command, err := loreQuery(newType, redisClient) if err != nil { glogger.Error.Println(err) } // if the array is empty, exit early if len(command) <= 0 { return } // pick a random one rand.Seed(time.Now().UnixNano()) rngDubs := fmt.Sprint(command[rand.Intn(len(command))]) rtm.SendMessage(rtm.NewOutgoingMessage(rngDubs, ev.Channel)) } func rmhandler(rtm *slack.RTM, ev *slack.MessageEvent, message, rmType string, redisClient *redis.Client) { // get nick from slack api user, err := rtm.GetUserInfo(ev.User) if err != nil { fmt.Printf("%s\n", err) return } nick := fmt.Sprintf("%s", user.Name) err = loreRmString(nick, message, rmType, redisClient) if err == nil { rtm.SendMessage(rtm.NewOutgoingMessage("entry removed", ev.Channel)) } else { rtm.SendMessage(rtm.NewOutgoingMessage("entry could not be removed", ev.Channel)) } }