about summary refs log blame commit diff stats
path: root/main.go
blob: 897b9b5e1973bde12fd5a9dcbaac95226a049918 (plain) (tree)
1
2
3
4
5
6
7
8
9
            

        
               
                       

              
                   
             
            
                 
                   

                 
 
                                   

 
                               
                         
 


                                                  
                                 
                  








                                     

 
             
                                                                  

                                                                                             
 
                                                      

                                                                                
 
                    
                                           


                                                                         

                                           








                                         


                                                         

                                                                          
         
 
                                                 
                                                       


                                                                   



                                                         


                                                                   
 
                        
                                       




                                    
                                  
                                 


                                                                                
                                                                           
                                    
                                                                        

                                                          
                                                                
                                     

          
                                         





                                                                              
                                                                                    
                                                                          
                                                                                                   




                                                          
 
                                                                  
                                                                                        
                                                               




                                                                                               
                                                            
                                                          







                                                                     
                              
                 
 

                                                           
                                                                 



                                            
                                                                            
                                                      
                                      

                                                    

                              
 
                                                          
                                                                             
                                                   

                                                

                                                                           
                                                      

                                      
 
                                                                         
                                            
 
                                                         
                                                                        
                                                
                                 
 
                                                                                          
                                                        

                         
                                                    

                              


                                                             
                                                               
                                                                

                                                                                
                                                      


                                      

                                                                                           

                              




                                                            


                                                             
 
                                                          

                                                                                

                                                             

                                                                           
                                                      
                                      
                         

                                                                                                       
                              
                 

          
                                            

                                                                                                           
         
 







                                          
 
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)
	}()
}