You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.go 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package main
  2. import (
  3. "crypto/tls"
  4. "fmt"
  5. "github.com/deckarep/golang-set"
  6. "github.com/fsnotify/fsnotify"
  7. "github.com/irccloud/go-ircevent"
  8. "github.com/irccloud/irccat/httplistener"
  9. "github.com/irccloud/irccat/tcplistener"
  10. "github.com/juju/loggo"
  11. "github.com/spf13/viper"
  12. "os"
  13. "os/signal"
  14. "strings"
  15. "syscall"
  16. )
  17. var log = loggo.GetLogger("main")
  18. var branch string
  19. var revision string
  20. type IRCCat struct {
  21. auth_channel string
  22. channels mapset.Set
  23. auth_users map[string]bool
  24. irc *irc.Connection
  25. tcp *tcplistener.TCPListener
  26. signals chan os.Signal
  27. }
  28. func main() {
  29. loggo.ConfigureLoggers("<root>=INFO")
  30. log.Infof("IRCCat %s (%s) starting...", branch, revision)
  31. viper.SetConfigName("irccat")
  32. viper.AddConfigPath("/etc")
  33. viper.AddConfigPath(".")
  34. var err error
  35. err = viper.ReadInConfig()
  36. if err != nil {
  37. log.Errorf("Error reading config file - exiting. I'm looking for irccat.[json|yaml|toml|hcl] in . or /etc")
  38. return
  39. }
  40. irccat := IRCCat{auth_users: map[string]bool{},
  41. signals: make(chan os.Signal, 1),
  42. channels: mapset.NewSet(),
  43. auth_channel: viper.GetString("commands.auth_channel")}
  44. viper.WatchConfig()
  45. viper.OnConfigChange(irccat.handleConfigChange)
  46. signal.Notify(irccat.signals, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
  47. go irccat.signalHandler()
  48. irccat.tcp, err = tcplistener.New()
  49. if err != nil {
  50. log.Criticalf("Error starting TCP listener: %s", err)
  51. return
  52. }
  53. err = irccat.connectIRC()
  54. if err != nil {
  55. log.Criticalf("Error connecting to IRC server: %s", err)
  56. return
  57. }
  58. if viper.IsSet("http") {
  59. httplistener.New(irccat.irc)
  60. }
  61. irccat.tcp.Run(irccat.irc)
  62. irccat.irc.Loop()
  63. }
  64. func (i *IRCCat) signalHandler() {
  65. sig := <-i.signals
  66. log.Infof("Exiting on %s", sig)
  67. i.irc.QuitMessage = fmt.Sprintf("Exiting on %s", sig)
  68. i.irc.Quit()
  69. }
  70. func (i *IRCCat) connectIRC() error {
  71. irccon := irc.IRC(viper.GetString("irc.nick"), viper.GetString("irc.realname"))
  72. i.irc = irccon
  73. // requesting any caps breaks SASL
  74. // irccon.RequestCaps = []string{"away-notify", "account-notify", "draft/message-tags-0.2"}
  75. irccon.UseTLS = viper.GetBool("irc.tls")
  76. if viper.IsSet("irc.sasl_pass") && viper.GetString("irc.sasl_pass") != "" {
  77. if viper.IsSet("irc.sasl_login") && viper.GetString("irc.sasl_login") != "" {
  78. irccon.SASLLogin = viper.GetString("irc.sasl_login")
  79. } else {
  80. irccon.SASLLogin = viper.GetString("irc.nick")
  81. }
  82. irccon.SASLPassword = viper.GetString("irc.sasl_pass")
  83. irccon.UseSASL = true
  84. }
  85. if viper.GetBool("irc.tls_skip_verify") {
  86. irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
  87. }
  88. irccon.Password = viper.GetString("irc.server_pass")
  89. err := irccon.Connect(viper.GetString("irc.server"))
  90. if err != nil {
  91. return err
  92. }
  93. irccon.AddCallback("001", i.handleWelcome)
  94. irccon.AddCallback("PRIVMSG", func(event *irc.Event) {
  95. msg := event.Message()
  96. if (msg[0] == '?' || msg[0] == '!') && len(msg) > 1 {
  97. go i.handleCommand(event)
  98. }
  99. })
  100. irccon.AddCallback("353", i.handleNames)
  101. irccon.AddCallback("JOIN", i.handleJoin)
  102. irccon.AddCallback("PART", i.handlePart)
  103. irccon.AddCallback("QUIT", i.handleQuit)
  104. irccon.AddCallback("KILL", i.handleQuit)
  105. irccon.AddCallback("NICK", i.handleNick)
  106. return nil
  107. }
  108. func (i *IRCCat) handleWelcome(e *irc.Event) {
  109. log.Infof("Negotiated IRCv3 capabilities: %v", i.irc.AcknowledgedCaps)
  110. if viper.IsSet("irc.identify_pass") && viper.GetString("irc.identify_pass") != "" {
  111. i.irc.SendRawf("NICKSERV IDENTIFY %s", viper.GetString("irc.identify_pass"))
  112. }
  113. log.Infof("Connected, joining channels...")
  114. for _, channel := range viper.GetStringSlice("irc.channels") {
  115. key_var := fmt.Sprintf("irc.keys.%s", channel)
  116. if strings.ContainsAny(channel, " \t") {
  117. log.Errorf("Channel name '%s' contains whitespace. Set a channel key by setting the config variable irc.keys.#channel",
  118. channel)
  119. continue
  120. }
  121. if viper.IsSet(key_var) {
  122. i.irc.Join(channel + " " + viper.GetString(key_var))
  123. } else {
  124. i.irc.Join(channel)
  125. }
  126. i.channels.Add(channel)
  127. }
  128. }
  129. func (i *IRCCat) handleConfigChange(e fsnotify.Event) {
  130. log.Infof("Reloaded config")
  131. new_channels := mapset.NewSet()
  132. for _, channel := range viper.GetStringSlice("irc.channels") {
  133. new_channels.Add(channel)
  134. if !i.channels.Contains(channel) {
  135. log.Infof("Joining new channel %s", channel)
  136. i.irc.Join(channel)
  137. i.channels.Add(channel)
  138. }
  139. }
  140. it := i.channels.Difference(new_channels).Iterator()
  141. for channel := range it.C {
  142. log.Infof("Leaving channel %s", channel)
  143. i.irc.Part(channel.(string))
  144. i.channels.Remove(channel)
  145. }
  146. }