package main import ( "bytes" "encoding/json" "flag" "fmt" "io/ioutil" "log" "os" "os/exec" "os/signal" "strings" "time" "github.com/lrstanley/girc" ) // ZW is a zero-width character const ZW = string(0x200b) // Generic in-chat error message const errMsg = "I'm broke! Fix me! Check my logs!" // Conf holds all the config info type Conf struct { Owner string `json:"owner"` Chan string `json:"chan"` Server string `json:"server"` Port int `json:"port"` Nick string `json:"nick"` Pass string `json:"pass"` User string `json:"user"` Name string `json:"name"` SSL bool `json:"ssl"` } func main() { // check for config file specified by command line flag -c jsonLocation := flag.String("c", "config.json", "Path to config file in JSON format") jsonLocationLong := flag.String("config", "config.json", "Same as -c") // spit out config file structure if requested jsonFormat := flag.Bool("j", false, "Describes JSON config file fields") jsonFormatLong := flag.Bool("json", false, "Same as -j") flag.Parse() if *jsonFormat || *jsonFormatLong { fmt.Println(`Here is the format for the JSON config file: { "owner": "YourNickHere", "chan": "#bots", "server": "irc.tilde.chat", "port": 6697, "nick": "goofbot", "pass": "", "user": "goofbot", "name": "Goofus McBotus", "ssl": true }`) os.Exit(0) } // if the extended switch isn't the default value // and if the extended switch isn't empty if *jsonLocationLong != "config.json" && *jsonLocationLong != "" { *jsonLocation = *jsonLocationLong } // read the config file into a byte array jsonconf, err := ioutil.ReadFile(*jsonLocation) if err != nil { log.Fatalf("Error loading config: %v", err.Error()) } // unmarshal the json byte array into struct conf var conf Conf err = json.Unmarshal(jsonconf, &conf) if err != nil { log.Fatalf("Error parsing config: %v", err.Error()) } // CLIENT CONFIG client := girc.New(girc.Config{ Server: conf.Server, Port: conf.Port, Nick: conf.Nick, User: conf.User, Name: conf.Name, Out: os.Stdout, SSL: conf.SSL, }) client.Handlers.Add(girc.CONNECTED, func(c *girc.Client, e girc.Event) { // authenticate with nickserv if pass is set in config file if conf.Pass != "" { c.Cmd.Message("nickserv", "identify "+conf.Pass) time.Sleep(500 * time.Millisecond) } // join initial channel specified in config.json c.Cmd.Join(conf.Chan) }) // basic command-response handler client.Handlers.Add(girc.PRIVMSG, func(c *girc.Client, e girc.Event) { if strings.HasPrefix(e.Last(), "!hello") { c.Cmd.ReplyTo(e, "henlo good fren!!") return } // check if the command was issued by a specific person before dying // i had to delve into girc/event.go to find e.Source.Name if strings.HasPrefix(e.Last(), "die, devil bird!") && e.Source.Name == conf.Owner { c.Cmd.Reply(e, "SQUAWWWWWK!!") time.Sleep(100 * time.Millisecond) c.Close() return } // when requested by owner, join channel specified if strings.HasPrefix(e.Last(), "!join") && e.Source.Name == conf.Owner { dest := strings.Split(e.Params[1], " ") if len(dest) < 2 { c.Cmd.Reply(e, "You must specify at least one channel to join") return } c.Cmd.Reply(e, "Right away, cap'n!") time.Sleep(100 * time.Millisecond) for _, channel := range dest[1:] { if !strings.HasPrefix(channel, "#") { continue } c.Cmd.Join(channel) } return } // respond with uptime / load if strings.HasPrefix(e.Last(), "!uptime") { uptime := exec.Command("/usr/bin/uptime") var out bytes.Buffer uptime.Stdout = &out err := uptime.Run() if err != nil { log.Printf("!uptime error: %v", err.Error()) c.Cmd.Reply(e, errMsg) return } c.Cmd.Reply(e, out.String()) return } if strings.HasPrefix(e.Last(), "!users") { who := exec.Command("/usr/local/bin/showwhoison", "") var bytestream bytes.Buffer who.Stdout = &bytestream err := who.Run() if err != nil { log.Printf("!users error: %v", err.Error()) c.Cmd.Reply(e, errMsg) return } split := strings.Split(bytestream.String(), "\n") var out bytes.Buffer for i := 1; i < len(split); i++ { if split[i] == "" || len(split[i]) < 2 { continue } c := fmt.Sprintf("%s%s%s", split[i][:1], ZW, split[i][1:]) out.WriteString(c + " ") } c.Cmd.Reply(e, out.String()) return } // number of total human users on the server. // only active sessions. if strings.HasPrefix(e.Last(), "!totalusers") { userdirs, err := ioutil.ReadDir("/home") if err != nil { log.Printf("!totalusers error: %v", err.Error()) c.Cmd.Reply(e, errMsg) return } msg := fmt.Sprintf("%v user accounts on ~institute", len(userdirs)) c.Cmd.Reply(e, msg) return } // hmmm. if strings.Contains(e.Last(), "rain drop") { c.Cmd.Reply(e, "drop top") } if strings.Contains(e.Last(), "blockchain") { c.Cmd.Reply(e, "blonkchain") } if strings.HasPrefix(e.Last(), "!admin") { // gotify.sh contains a preconstructed curl request that // uses the gotify api to send a notification to admins gotify := exec.Command("./gotify.sh") err := gotify.Run() if err != nil { log.Printf("!admin error: %v", err.Error()) c.Cmd.Reply(e, errMsg) return } c.Cmd.Reply(e, "The admins have been notified that you need their assistance!") return } }) // die if there's a connection error if err := client.Connect(); err != nil { log.Fatalf("an error occurred while attempting to connect to %s: %s", client.Server(), err) } // sigint handling ctrlc := make(chan os.Signal, 1) signal.Notify(ctrlc, os.Interrupt) go func() { <-ctrlc client.Close() os.Exit(1) }() }