End transition to go project layout
This commit is contained in:
691
internal/irc/aleesa-irc-go-lib.go
Normal file
691
internal/irc/aleesa-irc-go-lib.go
Normal file
@@ -0,0 +1,691 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"aleesa-irc-go/internal/boolcollection"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
irc "github.com/thoj/go-ircevent"
|
||||
)
|
||||
|
||||
// ircClientInit горутинка для работы с протоколом irc.
|
||||
func IrcClientRun() {
|
||||
for !Shutdown {
|
||||
// Иницализируем irc-клиента.
|
||||
// TODO: make use of capabilities: https://defs.ircdocs.horse/defs/clientcaps .
|
||||
// TODO: make use of tags caps https://defs.ircdocs.horse/defs/tags .
|
||||
serverString := fmt.Sprintf("%s:%d", Config.Irc.Server, Config.Irc.Port)
|
||||
log.Debugf("Preparing to connect to %s", serverString)
|
||||
log.Debugf("Using nick %s and username %s", Config.Irc.Nick, Config.Irc.User)
|
||||
ircClient = irc.IRC(Config.Irc.Nick, Config.Irc.User)
|
||||
ircClient.RealName = Config.Irc.User
|
||||
ircClient.Version = "Aleesa Bot v4.something"
|
||||
|
||||
if Config.Irc.Ssl {
|
||||
log.Debug("Force use ssl for connection")
|
||||
|
||||
ircClient.UseTLS = true
|
||||
|
||||
if !Config.Irc.SslVerify {
|
||||
log.Debug("Skip server certificate validation")
|
||||
|
||||
ircClient.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
} else {
|
||||
log.Debug("Force server certificate validation")
|
||||
}
|
||||
} else {
|
||||
log.Debug("Skip ssl for connection")
|
||||
}
|
||||
|
||||
if Config.Irc.Sasl && Config.Irc.Password != "" {
|
||||
ircClient.SASLLogin = Config.Irc.User
|
||||
ircClient.SASLPassword = Config.Irc.Password
|
||||
ircClient.UseSASL = true
|
||||
}
|
||||
|
||||
// Навесим коллбэков на некоторые ответы сервера на наши запросы.
|
||||
|
||||
// 001 RPL_WELCOME уже есть в github.com/thoj/go-ircevent/irc_callback.go.
|
||||
|
||||
// Сделаем уже что-то полезное! Motd нам уже прислали и теперь можно авторизоваться и джойниться
|
||||
ircClient.AddCallback("004", func(e *irc.Event) {
|
||||
// Из это строки мы можем узнать, какие флаги для user MODE и channem MODE можно навешивать.
|
||||
// Как минимум, эта строка нам нужна, чтобы выяснить, можем ли мы взять себе +B, мы же бот :).
|
||||
e.Connection.Lock()
|
||||
|
||||
// Формат строки с MODE-ами https://datatracker.ietf.org/doc/html/rfc2812#section-5.1 .
|
||||
availableUserModes = boolcollection.NewCollection()
|
||||
availableChanModes = boolcollection.NewCollection()
|
||||
|
||||
log.Debugf("Add %s to list of available user modes", e.Arguments[3])
|
||||
|
||||
for _, mode := range strings.Split(e.Arguments[3], "") {
|
||||
availableUserModes.Set(mode, true)
|
||||
}
|
||||
|
||||
availableUserModes.Set("announced", true)
|
||||
|
||||
log.Debugf("Add %s to list available channel modes", e.Arguments[4])
|
||||
|
||||
for _, mode := range strings.Split(e.Arguments[4], "") {
|
||||
availableChanModes.Set(mode, true)
|
||||
}
|
||||
|
||||
availableChanModes.Set("announced", true)
|
||||
|
||||
e.Connection.Unlock()
|
||||
})
|
||||
|
||||
ircClient.AddCallback("005", func(e *irc.Event) {
|
||||
// TODO: Make use of it. https://defs.ircdocs.horse/defs/isupport
|
||||
})
|
||||
|
||||
ircClient.AddCallback("319", func(e *irc.Event) {
|
||||
/* Это одна из строк с данными, прилетающая в ответ на запрос whois на определённого юзера
|
||||
* Из этой строки нас интересует, на каких каналах пользователь op (то есть с префиксом @) или имеет voice
|
||||
* (то есть с префиксом +) чтобы внести его в свою базу mode-ов.
|
||||
*/
|
||||
channelsWithModes := strings.Split(e.Arguments[2], " ")
|
||||
dstNick := e.Arguments[1]
|
||||
|
||||
for _, channelWithMode := range channelsWithModes {
|
||||
for _, mychannel := range Config.Irc.Channels {
|
||||
tmp := regexp.MustCompile("#").Split(channelWithMode, 2)
|
||||
channel := tmp[1]
|
||||
modes := tmp[0]
|
||||
|
||||
if channel == mychannel {
|
||||
// Для каждого канала формат строго 1 из 4-х: #channel | @#channel | +#channel | @+#channel
|
||||
switch modes {
|
||||
case "@+":
|
||||
userModeUpdateUser(channel, dstNick, "+o")
|
||||
userModeUpdateUser(channel, dstNick, "+v")
|
||||
case "@":
|
||||
userModeUpdateUser(channel, dstNick, "+o")
|
||||
case "+":
|
||||
userModeUpdateUser(channel, dstNick, "+v")
|
||||
default:
|
||||
userModeUpdateUser(channel, dstNick, "-o")
|
||||
userModeUpdateUser(channel, dstNick, "-v")
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 433 ERR_NICKNAMEINUSE уже есть в github.com/thoj/go-ircevent/irc_callback.go.
|
||||
|
||||
ircClient.AddCallback("353", func(e *irc.Event) {
|
||||
// Это одна из строк данных, прилетающая в ответ на запрос names, на канале
|
||||
e.Connection.Lock()
|
||||
namesString := e.Arguments[3]
|
||||
channel := e.Arguments[2]
|
||||
|
||||
for _, mychannel := range Config.Irc.Channels {
|
||||
if channel == mychannel {
|
||||
for _, name := range strings.Split(namesString, " ") {
|
||||
mode := name[:1]
|
||||
|
||||
var nick string
|
||||
|
||||
if mode == "@" || mode == "+" {
|
||||
nick = name[1:]
|
||||
} else {
|
||||
nick = name
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "@":
|
||||
userModeUpdateUser(channel, nick, "+o")
|
||||
case "+":
|
||||
userModeUpdateUser(channel, nick, "+v")
|
||||
default:
|
||||
userModeUpdateUser(channel, nick, "-o")
|
||||
userModeUpdateUser(channel, nick, "-v")
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
e.Connection.Unlock()
|
||||
})
|
||||
|
||||
// Если сервер не может прочитать MOTD, то он может вернуть 422 ERR_NOMOTD, тоде самое навесим и туда тоже.
|
||||
ircClient.AddCallback("376", func(e *irc.Event) {
|
||||
// TODO: make use of modes https://defs.ircdocs.horse/defs/usermodes
|
||||
// Если у нас есть доступный +B возьмём его себе, мы же бот.
|
||||
announced, _ := availableUserModes.Get("announced")
|
||||
botFlag, _ := availableUserModes.Get("B")
|
||||
|
||||
if announced {
|
||||
if botFlag {
|
||||
log.Info("Grabbing +B flag to mark me as bot")
|
||||
ircClient.Mode(ircClient.GetNick(), "+B")
|
||||
// TODO: Wait for it.
|
||||
} else {
|
||||
log.Info("Skip +B flag, server does not support it, noone will know that I am bot")
|
||||
}
|
||||
} else {
|
||||
// Этого по идее не должно быть.
|
||||
log.Info("Skip +B flag, server did not announce modes (yet?), noone will know that I am bot")
|
||||
}
|
||||
|
||||
if !Config.Irc.Sasl && Config.Irc.Password != "" && !nickIsUsed {
|
||||
log.Info("Identifying via NickServ")
|
||||
|
||||
message := fmt.Sprintf("identify %s %s", Config.Irc.Nick, Config.Irc.Password)
|
||||
imChan <- iMsg{ChatID: "NickServ", Text: message}
|
||||
}
|
||||
// TODO: wait until +R flag been set? could be implemented with waiting for channel message.
|
||||
|
||||
log.Debug("Trying to join to preconfigured channels")
|
||||
|
||||
for _, channel := range Config.Irc.Channels {
|
||||
log.Infof("Joining to %s channel", channel)
|
||||
ircClient.Join(channel)
|
||||
}
|
||||
// TODO: wait for join or join error.
|
||||
})
|
||||
|
||||
// Навесим коллбэков на все возможные и невозможные error status code, которые мы можем получить и сдампим это
|
||||
// дело в лог. https://datatracker.ietf.org/doc/html/rfc1459 и https://datatracker.ietf.org/doc/html/rfc2812
|
||||
ircClient.AddCallback("401", func(e *irc.Event) {
|
||||
log.Warnf("401 ERR_NOSUCHNICK, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("403", func(e *irc.Event) {
|
||||
log.Warnf("403 ERR_NOSUCHCHANNEL, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("404", func(e *irc.Event) {
|
||||
log.Warnf("404 ERR_CANNOTSENDTOCHAN, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("405", func(e *irc.Event) {
|
||||
// Тут мы наткнулись на ограничение сервера, сделать с этим мы ничего не можем
|
||||
log.Errorf("405 ERR_TOOMANYCHANNELS, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("407", func(e *irc.Event) {
|
||||
log.Errorf("407 ERR_TOOMANYTARGETS, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("411", func(e *irc.Event) {
|
||||
log.Errorf("411 ERR_NORECIPIENT, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("412", func(e *irc.Event) {
|
||||
log.Errorf("412 ERR_NOTEXTTOSEND, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("421", func(e *irc.Event) {
|
||||
log.Errorf("421 ERR_UNKNOWNCOMMAND, %s", e.Raw)
|
||||
})
|
||||
|
||||
// Аналогичный коллбэк висит на 376 RPL_ENDOFMOTD.
|
||||
ircClient.AddCallback("422", func(e *irc.Event) {
|
||||
// TODO: make use of modes https://defs.ircdocs.horse/defs/usermodes
|
||||
// Если у нас есть доступный +B возьмём его себе, мы же бот.
|
||||
botFlag, ok := availableUserModes.Get("B")
|
||||
|
||||
if ok && botFlag {
|
||||
log.Info("Grabbing +B flag to mark me as bot")
|
||||
ircClient.Mode(ircClient.GetNick(), "+B")
|
||||
} else {
|
||||
if ok {
|
||||
log.Info("Skip +B flag, server does not support it, noone will know that I am bot")
|
||||
} else {
|
||||
log.Info("Skip +B flag, server did not announce modes (yet?), noone will know that I am bot")
|
||||
}
|
||||
}
|
||||
|
||||
if !Config.Irc.Sasl && Config.Irc.Password != "" && !nickIsUsed {
|
||||
log.Info("Identifying via NickServ")
|
||||
|
||||
message := fmt.Sprintf("identify %s %s", Config.Irc.Nick, Config.Irc.Password)
|
||||
imChan <- iMsg{ChatID: "NickServ", Text: message}
|
||||
}
|
||||
// TODO: wait until +R flag been set? could be implemented with waiting for channel message.
|
||||
|
||||
log.Debug("Trying to join to preconfigured channels")
|
||||
|
||||
for _, channel := range Config.Irc.Channels {
|
||||
log.Infof("Joining to %s channel", channel)
|
||||
ircClient.Join(channel)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("431", func(e *irc.Event) {
|
||||
log.Errorf("431 ERR_NONICKNAMEGIVEN, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("432", func(e *irc.Event) {
|
||||
log.Errorf("432 ERR_ERRONEUSNICKNAME, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("433", func(e *irc.Event) {
|
||||
log.Errorf("433 ERR_NICKNAMEINUSE, %s", e.Raw)
|
||||
|
||||
nickIsUsed = true
|
||||
|
||||
<-time.NewTimer(60 * time.Second).C // 1 minute sleep.
|
||||
|
||||
ircClient.Nick(Config.Irc.Nick)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("436", func(e *irc.Event) {
|
||||
// Что это за зверь такой?
|
||||
// Предположительно, тут имеется в виду ситуация, когда в конфедерации серверов ник был зареган на двух
|
||||
// серверах и теперь сервер не знает, что с этим делать
|
||||
log.Errorf("436 ERR_NICKCOLLISION, %s", e.Raw)
|
||||
})
|
||||
|
||||
// 437: ERR_UNAVAILRESOURCE уже есть в github.com/thoj/go-ircevent/irc_callback.go.
|
||||
ircClient.AddCallback("437", func(e *irc.Event) {
|
||||
log.Errorf("437 ERR_UNAVAILRESOURCE, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("441", func(e *irc.Event) {
|
||||
log.Warnf("441 ERR_USERNOTINCHANNEL, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("442", func(e *irc.Event) {
|
||||
log.Warnf("442 ERR_NOTONCHANNEL, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("443", func(e *irc.Event) {
|
||||
// Returned when a client tries to invite a user to a channel they're already on.
|
||||
log.Errorf("443 ERR_USERONCHANNEL, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("446", func(e *irc.Event) {
|
||||
// Returned by USERS when it has been disabled or not implemented.
|
||||
log.Errorf("446 ERR_USERSDISABLED, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("451", func(e *irc.Event) {
|
||||
// Предполагается, что надо авторизоваться, перед тем как что-то делать на сервере
|
||||
log.Errorf("451 ERR_NOTREGISTERED, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("461", func(e *irc.Event) {
|
||||
log.Errorf("461 ERR_NEEDMOREPARAMS, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("462", func(e *irc.Event) {
|
||||
log.Warnf("462 ERR_ALREADYREGISTRED, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("464", func(e *irc.Event) {
|
||||
log.Errorf("464 ERR_PASSWDMISMATCH, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("465", func(e *irc.Event) {
|
||||
// Этот бан на сервере целиком, если верить rfc, здесь вроде как ничего сделать нельзя... или можно?
|
||||
log.Errorf("465 ERR_YOUREBANNEDCREEP, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("467", func(e *irc.Event) {
|
||||
log.Errorf("467 ERR_KEYSET, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("471", func(e *irc.Event) {
|
||||
// Это значит, что народу на канале максимальное количество. Будем пробовать присунуться раз в 30 сек.
|
||||
channel := e.Arguments[1]
|
||||
log.Warnf("471 ERR_CHANNELISFULL, %s", e.Raw)
|
||||
<-time.NewTimer(30 * time.Second).C
|
||||
|
||||
// Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет?
|
||||
if slices.Contains(Config.Irc.Channels, channel) {
|
||||
ircClient.Join(channel)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("472", func(e *irc.Event) {
|
||||
log.Errorf("472 ERR_UNKNOWNMODE, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("473", func(e *irc.Event) {
|
||||
// Частенько такую хуйню творят, если надо "прибраться" либо в канале, либо на сервере.
|
||||
// По завершении работ +i снимают.
|
||||
channel := e.Arguments[1]
|
||||
log.Errorf("473 ERR_INVITEONLYCHAN, %s", e.Raw)
|
||||
<-time.NewTimer(30 * time.Second).C
|
||||
|
||||
// Проверяем, а должны ли мы быть заджоенными к указанному, каналу, а то вдруг нет?
|
||||
if slices.Contains(Config.Irc.Channels, channel) {
|
||||
ircClient.Join(channel)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("474", func(e *irc.Event) {
|
||||
// Это событие прилетает, (только) если мы пытаемся приджойниться к каналу, где нас забанили
|
||||
channel := e.Arguments[1]
|
||||
|
||||
log.Errorf("474 ERR_BANNEDFROMCHAN, %s", e.Raw)
|
||||
// Если нас забанили, то информацию о MODE-ах пользователей мы теряем
|
||||
<-time.NewTimer(30 * time.Second).C
|
||||
|
||||
// Вдруг, нас забанили, но какбэ не навсегда?
|
||||
// TODO: вынести в настройки?
|
||||
|
||||
// Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет?
|
||||
if slices.Contains(Config.Irc.Channels, channel) {
|
||||
ircClient.Join(channel)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("475", func(e *irc.Event) {
|
||||
log.Errorf("475 ERR_BADCHANNELKEY, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("477", func(e *irc.Event) {
|
||||
log.Errorf("477 ERR_NOCHANMODES, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("478", func(e *irc.Event) {
|
||||
log.Errorf("478 ERR_BANLISTFULL, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("481", func(e *irc.Event) {
|
||||
log.Errorf("481 ERR_NOPRIVILEGES, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("482", func(e *irc.Event) {
|
||||
log.Errorf("482 ERR_CHANOPRIVSNEEDED, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("484", func(e *irc.Event) {
|
||||
log.Errorf("484 ERR_RESTRICTED, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("485", func(e *irc.Event) {
|
||||
log.Errorf("485 ERR_UNIQOPPRIVSNEEDED, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("491", func(e *irc.Event) {
|
||||
log.Errorf("491 ERR_NOOPERHOST, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("501", func(e *irc.Event) {
|
||||
log.Errorf("501 ERR_UMODEUNKNOWNFLAG, %s", e.Raw)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("502", func(e *irc.Event) {
|
||||
log.Errorf("502 ERR_USERSDONTMATCH, %s", e.Raw)
|
||||
})
|
||||
|
||||
// Навесим коллбэков на другие, интересные нам события
|
||||
ircClient.AddCallback("KICK", func(e *irc.Event) {
|
||||
dstNick := e.Arguments[1]
|
||||
srcFullNick := e.Source
|
||||
channel := e.Arguments[0]
|
||||
|
||||
if e.Arguments[1] == ircClient.GetNick() {
|
||||
// Нас кикнули с канала, и мы теряем информацию о MODE-ах пользователей
|
||||
UserMode.Delete(channel)
|
||||
// TODO: reason?
|
||||
log.Warnf("%s kicks us from %s", srcFullNick, channel)
|
||||
<-time.NewTimer(10 * time.Second).C
|
||||
|
||||
ircClient.Join(channel)
|
||||
} else {
|
||||
// Кого-то другого кикнули с канала
|
||||
log.Infof("On %s %s kicks %s from channel", channel, srcFullNick, dstNick)
|
||||
userModeDeleteUser(channel, dstNick)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("NICK", func(e *irc.Event) {
|
||||
srcNick := e.Nick
|
||||
dstNick := e.Arguments[0]
|
||||
|
||||
if e.Nick == Config.Irc.Nick {
|
||||
nickIsUsed = false
|
||||
|
||||
if Config.Irc.Password != "" {
|
||||
log.Warn("I regain my nick")
|
||||
} else {
|
||||
log.Warn("I regain my nick, trying to identify myself via NickServ")
|
||||
|
||||
message := fmt.Sprintf("identify %s %s", Config.Irc.Nick, Config.Irc.Password)
|
||||
imChan <- iMsg{ChatID: "NickServ", Text: message}
|
||||
}
|
||||
} else {
|
||||
log.Infof("%s renames themself to %s", srcNick, dstNick)
|
||||
}
|
||||
|
||||
// Неважно чей ник сменился, надо забыть, что было и снова узнать mode-ы сменишего nick джентельмена.
|
||||
// TODO: реализовать userModeRenameUser()
|
||||
userModePurgeUser(srcNick)
|
||||
ircClient.Whois(dstNick)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("JOIN", func(e *irc.Event) {
|
||||
nick := e.Nick
|
||||
fullNick := e.Source
|
||||
channel := e.Arguments[0]
|
||||
|
||||
if nick == ircClient.GetNick() {
|
||||
// Команда names отправляется автоматом.
|
||||
log.Infof("I joined to %s", channel)
|
||||
} else {
|
||||
log.Infof("%s joined to %s", fullNick, channel)
|
||||
// Технически, тут не надо спрашивать whois на пользователя, но мы спрашиваем, чтобы уточнить mode,
|
||||
// вдруг сервер проставляет mode заранее (хотя не должен).
|
||||
ircClient.Whois(nick)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("PART", func(e *irc.Event) {
|
||||
nick := e.Nick
|
||||
fullNick := e.Source
|
||||
channel := e.Arguments[0]
|
||||
|
||||
if nick == ircClient.GetNick() {
|
||||
log.Infof("I parted from %s", channel)
|
||||
UserMode.Delete(channel)
|
||||
} else {
|
||||
log.Infof("%s parted from %s", fullNick, channel)
|
||||
userModeDeleteUser(channel, nick)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("QUIT", func(e *irc.Event) {
|
||||
nick := e.Nick
|
||||
fullNick := e.Source
|
||||
|
||||
// TODO: Quit message? But who really cares?
|
||||
if nick == ircClient.GetNick() {
|
||||
log.Info("I quit")
|
||||
} else {
|
||||
log.Infof("%s has quit", fullNick)
|
||||
// Товарищ свалил из irc, забудем про его mode-ы
|
||||
userModePurgeUser(nick)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("MODE", func(e *irc.Event) {
|
||||
mode := e.Arguments[1]
|
||||
channel := e.Arguments[0]
|
||||
fullSrcNick := e.Source
|
||||
srcNick := e.Nick
|
||||
|
||||
var dstNick string
|
||||
|
||||
if len(e.Arguments) >= 3 { // кого-то по-MODE-или на канале
|
||||
dstNick = e.Arguments[2]
|
||||
|
||||
if srcNick == ircClient.GetNick() {
|
||||
log.Infof("I set user %s mode %s on %s", dstNick, mode, channel)
|
||||
} else {
|
||||
log.Infof("%s set user %s mode %s on %s", fullSrcNick, dstNick, mode, channel)
|
||||
}
|
||||
} else { // Установка mode-а при заходе на сервер
|
||||
dstNick = e.Arguments[0]
|
||||
|
||||
log.Infof("Server set my mode to %s", mode)
|
||||
}
|
||||
|
||||
userModeUpdateUser(channel, dstNick, mode)
|
||||
})
|
||||
|
||||
ircClient.AddCallback("TOPIC", func(e *irc.Event) {
|
||||
nick := e.Nick
|
||||
fullNick := e.Source
|
||||
topic := e.Arguments[1]
|
||||
channel := e.Arguments[0]
|
||||
|
||||
if nick == ircClient.GetNick() {
|
||||
log.Infof("I set topic on %s to %s", channel, topic)
|
||||
} else {
|
||||
log.Infof("%s set topic on %s to %s", fullNick, channel, topic)
|
||||
}
|
||||
})
|
||||
|
||||
ircClient.AddCallback("INVITE", func(e *irc.Event) {
|
||||
srcNick := e.Nick
|
||||
dstNick := e.Arguments[0]
|
||||
channel := e.Arguments[1]
|
||||
|
||||
if srcNick == ircClient.GetNick() {
|
||||
log.Infof("I invite %s to %s", dstNick, channel)
|
||||
} else {
|
||||
log.Infof("%s invites me to %s", srcNick, channel)
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: Implement standard action like slap, f.ex.
|
||||
|
||||
// Здесь у нас парсер сообщений из IRC
|
||||
ircClient.AddCallback("PRIVMSG", func(e *irc.Event) {
|
||||
log.Debugf("Incoming PRIVMSG: %s", e.Raw)
|
||||
ircMsgParser(e.Arguments[0], e.Nick, e.User, e.Source, e.Arguments[1])
|
||||
})
|
||||
|
||||
ircClient.AddCallback("*", func(e *irc.Event) {
|
||||
log.Debugf("Incoming EVENT (Raw): %s", e.Raw)
|
||||
})
|
||||
|
||||
log.Debugf("Make an actual connection to irc")
|
||||
|
||||
if err := ircClient.Connect(serverString); err != nil {
|
||||
log.Errorf("Unable to prepare irc connection: %s", err)
|
||||
<-time.NewTimer(3 * time.Second).C // Sleep for 3 seconds.
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ircClient.Loop()
|
||||
}
|
||||
}
|
||||
|
||||
// ircSend отправляет сообщение в irc. Якобы с применением ratelimit-ов, но на практике длинные сообщения разделяются
|
||||
// на более короткие и тут ratelimit не срабатывает. Если не повезёт, то сервер может за такое дело и кикнуть.
|
||||
func IrcSend() {
|
||||
for {
|
||||
m := <-imChan
|
||||
|
||||
switch Config.Irc.RateLimit.Type {
|
||||
case "simple_delay":
|
||||
log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text)
|
||||
|
||||
if m.Text[0:4] == "/me " {
|
||||
ircClient.Action(m.ChatID, m.Text)
|
||||
} else {
|
||||
ircClient.Privmsg(m.ChatID, m.Text)
|
||||
}
|
||||
|
||||
sleepDelay := time.Duration(Config.Irc.RateLimit.SimpleDelay) * time.Millisecond
|
||||
log.Debugf("Due to delay type simple_delay waiting for %d milliseconds", int(sleepDelay))
|
||||
<-time.NewTimer(sleepDelay).C
|
||||
case "token_bucket":
|
||||
var currentTimeMs = time.Now().UnixMilli()
|
||||
|
||||
var expirationTimeMs = Config.Irc.RateLimit.TokenBucket.ExpirationTime * 1000
|
||||
|
||||
if len(msgBucket.Timestamps) > 0 {
|
||||
var newBucket bucket
|
||||
|
||||
for _, timestampMs := range msgBucket.Timestamps {
|
||||
if currentTimeMs-timestampMs < expirationTimeMs {
|
||||
newBucket.Timestamps = append(newBucket.Timestamps, timestampMs)
|
||||
}
|
||||
}
|
||||
|
||||
bucketFill := len(newBucket.Timestamps)
|
||||
log.Debugf("Message bucket filled with %d/%d messages", bucketFill, Config.Irc.RateLimit.TokenBucket.Size)
|
||||
|
||||
newBucket.Timestamps = append(newBucket.Timestamps, currentTimeMs)
|
||||
|
||||
if bucketFill >= Config.Irc.RateLimit.TokenBucket.Size {
|
||||
newBucket.IsFull = true
|
||||
}
|
||||
|
||||
msgBucket = newBucket
|
||||
} else {
|
||||
// Проставим время отправки сообщения
|
||||
log.Debug("Message bucket is empty")
|
||||
|
||||
msgBucket.Timestamps = append(msgBucket.Timestamps, currentTimeMs)
|
||||
}
|
||||
|
||||
if msgBucket.IsFull {
|
||||
log.Debug("Message bucket overflowed, hitting ratelimit")
|
||||
|
||||
sleepPeriod := expirationTimeMs - (currentTimeMs - msgBucket.Timestamps[len(msgBucket.Timestamps)-1])
|
||||
|
||||
if Config.Irc.RateLimit.TokenBucket.Limit > 0 {
|
||||
sleepPeriod /= int64(Config.Irc.RateLimit.TokenBucket.Limit)
|
||||
}
|
||||
|
||||
log.Debugf("Sleeping for %d milliseconds", sleepPeriod)
|
||||
<-time.NewTimer(time.Duration(sleepPeriod) * time.Millisecond).C
|
||||
|
||||
currentTimeMs = time.Now().UnixMilli()
|
||||
// Обновим время отправки сообщения
|
||||
msgBucket.Timestamps[len(msgBucket.Timestamps)-1] = currentTimeMs
|
||||
}
|
||||
|
||||
log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text)
|
||||
|
||||
if len(m.Text) > 4 && m.Text[0:4] == "/me " {
|
||||
ircClient.Action(m.ChatID, m.Text[4:])
|
||||
} else {
|
||||
ircClient.Privmsg(m.ChatID, m.Text)
|
||||
}
|
||||
default:
|
||||
log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text)
|
||||
|
||||
if len(m.Text) > 4 && m.Text[0:4] == "/me " {
|
||||
ircClient.Action(m.ChatID, m.Text[4:])
|
||||
} else {
|
||||
ircClient.Privmsg(m.ChatID, m.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func IrcSendUnrestricted() {
|
||||
for {
|
||||
m := <-imChanUnrestricted
|
||||
log.Debugf("Skipping ratelimit and sending to chat %s message: %s", m.ChatID, m.Text)
|
||||
|
||||
if len(m.Text) > 4 && m.Text[0:4] == "/me " {
|
||||
ircClient.Action(m.ChatID, m.Text[4:])
|
||||
} else {
|
||||
ircClient.Privmsg(m.ChatID, m.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
55
internal/irc/globals.go
Normal file
55
internal/irc/globals.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"aleesa-irc-go/internal/anycollection"
|
||||
"aleesa-irc-go/internal/boolcollection"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
irc "github.com/thoj/go-ircevent"
|
||||
)
|
||||
|
||||
// Config - это у нас глобальная штука :).
|
||||
var Config MyConfig
|
||||
|
||||
// To break circular message forwarding we must set some sane default, it can be overridden via config.
|
||||
var forwardMax int64 = 5
|
||||
|
||||
// Объектики irc-клиента.
|
||||
var ircClient *irc.Connection
|
||||
var nickIsUsed = false
|
||||
|
||||
// Объектики клиента-редиски.
|
||||
var RedisClient *redis.Client
|
||||
var Subscriber *redis.PubSub
|
||||
|
||||
// Main context.
|
||||
var ctx = context.Background()
|
||||
|
||||
// Ставится в true, если мы получили сигнал на выключение.
|
||||
var Shutdown = false
|
||||
|
||||
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
||||
var SigChan = make(chan os.Signal, 1)
|
||||
|
||||
// Канал, в который пишутся сообщения для отправки в IRC в обычном порядке.
|
||||
var imChan = make(chan iMsg, 10000)
|
||||
|
||||
// Канал, в который пишутся сообщения для отправки в IRC, если ограничений нет (у бота +o или +v на канале).
|
||||
var imChanUnrestricted = make(chan iMsg, 100)
|
||||
|
||||
// "Ведёрко" с timestamp-ами последних отправленных сообщений.
|
||||
var msgBucket bucket
|
||||
|
||||
// "Базюлька" с MODE-ами пользователей на каналах.
|
||||
var UserMode = anycollection.NewCollection()
|
||||
|
||||
// "Базюлька" с доступными MODE-ами пользователя.
|
||||
var availableUserModes = boolcollection.NewCollection()
|
||||
|
||||
// "Базюлька" c с доступными MODE-ами каналов.
|
||||
var availableChanModes = boolcollection.NewCollection()
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
142
internal/irc/irc-user-mode.go
Normal file
142
internal/irc/irc-user-mode.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package irc
|
||||
|
||||
import "strings"
|
||||
|
||||
/* По сути, сбор mode-ов пользователей нам пока нужен только для того, чтобы определять следующие вещи:
|
||||
- Может ли nick для определённого канала воспользоваться командой admin
|
||||
- Применять ли ограничения на исходящие сообщения (пока что в состоянии todo.) MODE-ы +v и +o для бота на libera.chat
|
||||
активируют мягкий ratelimit на стороне сервера, без них, сервер просто рвёт соединение по превышению лимитов
|
||||
*/
|
||||
|
||||
// Удаляет сведения о MODE-ах пользователя для заданного канала.
|
||||
func userModeDeleteUser(ircChan string, nick string) {
|
||||
channel, ok := UserMode.Get(ircChan)
|
||||
|
||||
if ok {
|
||||
channelData := channel.(map[string]map[string]bool)
|
||||
delete(channelData, nick)
|
||||
|
||||
if len(channelData) == 0 {
|
||||
UserMode.Delete(ircChan)
|
||||
} else {
|
||||
UserMode.Set(ircChan, channelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Удаляет сведения о MODE-ах пользователя для всех каналов, на которых есть бот.
|
||||
func userModePurgeUser(nick string) {
|
||||
for _, channel := range Config.Irc.Channels {
|
||||
userModeDeleteUser(channel, nick)
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляет или создаёт запись о mode-ах пользователей.
|
||||
func userModeUpdateUser(ircChan string, nick string, modes string) {
|
||||
var channelData map[string]map[string]bool
|
||||
|
||||
channel, ok := UserMode.Get(ircChan)
|
||||
|
||||
if ok {
|
||||
channelData = channel.(map[string]map[string]bool)
|
||||
}
|
||||
|
||||
if channelData == nil {
|
||||
channelData = make(map[string]map[string]bool)
|
||||
}
|
||||
|
||||
if channelData[nick] == nil {
|
||||
channelData[nick] = make(map[string]bool)
|
||||
}
|
||||
|
||||
switch modes[:1] {
|
||||
case "+":
|
||||
for _, mode := range strings.Split(modes[1:], "") {
|
||||
channelData[nick][mode] = true
|
||||
}
|
||||
case "-":
|
||||
for _, mode := range strings.Split(modes[1:], "") {
|
||||
channelData[nick][mode] = false
|
||||
}
|
||||
}
|
||||
|
||||
UserMode.Set(ircChan, channelData)
|
||||
}
|
||||
|
||||
// Возвращает значение MODE-а v для запрошенного ника.
|
||||
func userModeIsVoiced(ircChan string, nick string) bool {
|
||||
var channelData map[string]map[string]bool
|
||||
|
||||
channel, ok := UserMode.Get(ircChan)
|
||||
|
||||
if ok {
|
||||
channelData = channel.(map[string]map[string]bool)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData[nick] == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData[nick]["v"] {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Возвращает значение MODE-а o для запрошенного ника.
|
||||
func userModeIsOped(ircChan string, nick string) bool {
|
||||
var channelData map[string]map[string]bool
|
||||
|
||||
channel, ok := UserMode.Get(ircChan)
|
||||
|
||||
if ok {
|
||||
channelData = channel.(map[string]map[string]bool)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData[nick] == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData[nick]["o"] {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func userModeIsHere(ircChan string, nick string) bool {
|
||||
var channelData map[string]map[string]bool
|
||||
|
||||
channel, ok := UserMode.Get(ircChan)
|
||||
|
||||
if ok {
|
||||
channelData = channel.(map[string]map[string]bool)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if channelData[nick] != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
465
internal/irc/msg-parsers.go
Normal file
465
internal/irc/msg-parsers.go
Normal file
@@ -0,0 +1,465 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"aleesa-irc-go/internal/setting"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ircMsgParser парсит сообщения, прилетевшие из IRC-ки.
|
||||
func ircMsgParser(channel string, nick string, user string, source string, msg string) { //nolint: revive
|
||||
// nick - это выбранный пользователем nick (если он занят, то его "нарисует" сервер)
|
||||
// user - это короткое имя пользователя, под которым его видит сервер
|
||||
// source - это длинное имя пользователя, оно содержит в себе помимо user, ещё и ip с которого пришёл пользователь
|
||||
if Shutdown {
|
||||
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо
|
||||
return
|
||||
}
|
||||
|
||||
if channel == ircClient.GetNick() {
|
||||
// В привате бот не отвечает, чтобы не было возможности DDoS-а, ratelimit-ы в irc слишком жёсткие
|
||||
// TODO: возможно, это имеет смысл вынести в конфиг, но это если кому-то кроме меня бот будет интересен
|
||||
return
|
||||
}
|
||||
|
||||
// Ловим команды и обрабатываем их
|
||||
if (len(msg) > len(Config.Csign)) && (msg[:len(Config.Csign)] == Config.Csign) {
|
||||
var outgoingMessage string
|
||||
|
||||
var message sMsg
|
||||
message.From = Config.Redis.MyChannel
|
||||
message.Userid = user // как его видит сервер
|
||||
message.Chatid = channel // чятик, в который написал user
|
||||
message.Threadid = "" // тредиков в irc нету, поэтому это поле отправляем пустым
|
||||
message.Plugin = Config.Redis.MyChannel
|
||||
message.Mode = "public"
|
||||
// Предполагается, что на команды бот автоматом отвечает
|
||||
message.Misc.Answer = 1
|
||||
message.Misc.Fwdcnt = 0
|
||||
message.Misc.Csign = Config.Csign
|
||||
message.Misc.Username = nick
|
||||
message.Misc.Botnick = Config.Irc.Nick
|
||||
message.Misc.Msgformat = 0
|
||||
|
||||
var cmd = msg[len(Config.Csign):]
|
||||
|
||||
switch {
|
||||
case cmd == "help" || msg == "помощь":
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%shelp | %sпомощь - это сообщение", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sanek | %sанек | %sанекдот - рандомный анекдот с anekdot.ru", Config.Csign, Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sbuni - комикс-стрип hapi buni", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sbunny - кролик", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%srabbit | %sкролик - кролик", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%scat | %sкис - кошечка", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sdice | %sroll | %sкости - бросить кости", Config.Csign, Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sdig | %sкопать - заняться археологией", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sdrink | %sпраздник - какой сегодня праздник?", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfish | %sfisher - порыбачить", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sрыба | %sрыбка | %sрыбалка - порыбачить", Config.Csign, Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sf | %sф - рандомная фраза из сборника цитат fortune_mod", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfortune | %sфортунка - рандомная фраза из сборника цитат fortune_mod", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfox | %sлис - лисичка", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfriday | %sпятница - а не пятница ли сегодня?", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfrog | %sлягушка - лягушка", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%shorse | %sлошадь | %sлошадка - лошадка", Config.Csign, Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%skarma фраза - посмотреть карму фразы", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sкарма фраза - посмотреть карму фразы", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintln("фраза++ | фраза-- - повысить или понизить карму фразы")}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%slat | %sлат - сгенерировать фразу из крылатого латинского выражения", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%smonkeyuser - комикс-стрип MonkeyUser", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sowl | %sсова - сова", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sping | %sпинг - попинговать бота", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sproverb | %sпословица - рандомная русская пословица", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%ssnail | %sулитка - улитка", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%ssome_brew - выдать соответствующий напиток, бармен может налить rum, ром, vodka, водку, tequila, текила, whisky, виски, absinthe, абсент", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sver | %sversion - написать что-то про версию ПО", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sверсия - написать что-то про версию ПО", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sw <город> | %sп <город> - погода в городе", Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sxkcd - комикс-стрип с xkcb.ru", Config.Csign)}
|
||||
|
||||
if userModeIsOped(channel, nick) {
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin - настройки некоторых плагинов бота для канала", Config.Csign)}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case cmd == "admin":
|
||||
if userModeIsOped(channel, nick) {
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin oboobs # - где 1 - вкл, 0 - выкл плагина oboobs", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin oboobs показываем ли сисечки по просьбе участников чата (команды %stits, %stities, %sboobs, %sboobies, %sсиси, %sсисечки)", Config.Csign, Config.Csign, Config.Csign, Config.Csign, Config.Csign, Config.Csign, Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin obutts # - где 1 - вкл, 0 - выкл плагина obutts", Config.Csign)}
|
||||
imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin obutts показываем ли попки по просьбе участников чата (команды %sass, %sbutt, %sbooty, %sпопа, %sпопка)", Config.Csign, Config.Csign, Config.Csign, Config.Csign, Config.Csign, Config.Csign)}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case cmd == "admin oboobs":
|
||||
if userModeIsOped(channel, nick) {
|
||||
value := setting.Get(channel, "oboobs")
|
||||
|
||||
switch value {
|
||||
case "":
|
||||
_ = setting.Set(channel, "oboobs", "0")
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
|
||||
case "0":
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
|
||||
case "1":
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs включен"}
|
||||
default:
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case cmd == "admin oboobs 1":
|
||||
if userModeIsOped(channel, nick) {
|
||||
err := setting.Set(channel, "oboobs", "1")
|
||||
|
||||
if err != nil {
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs всё ещё выключен"}
|
||||
} else {
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs включен"}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case cmd == "admin oboobs 0":
|
||||
if userModeIsOped(channel, nick) {
|
||||
_ = setting.Set(channel, "oboobs", "0")
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case cmd == "admin obutts":
|
||||
if userModeIsOped(channel, nick) {
|
||||
value := setting.Get(channel, "obutts")
|
||||
|
||||
switch value {
|
||||
case "":
|
||||
_ = setting.Set(channel, "obutts", "0")
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
|
||||
case "0":
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
|
||||
case "1":
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts включен"}
|
||||
default:
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case cmd == "admin obutts 1":
|
||||
if userModeIsOped(channel, nick) {
|
||||
err := setting.Set(channel, "obutts", "1")
|
||||
|
||||
if err != nil {
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts всё ещё выключен"}
|
||||
} else {
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts включен"}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case cmd == "admin obutts 0":
|
||||
if userModeIsOped(channel, nick) {
|
||||
_ = setting.Set(channel, "obutts", "0")
|
||||
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
default:
|
||||
var done = false
|
||||
|
||||
// Общий список простых команд
|
||||
cmds := []string{"ping", "пинг", "пинх", "pong", "понг", "понх", "coin", "монетка", "roll", "dice", "кости",
|
||||
"ver", "version", "версия", "хэлп", "halp", "kde", "кде", "lat", "лат", "friday", "пятница", "proverb",
|
||||
"пословица", "пословиться", "fortune", "фортунка", "f", "ф", "anek", "анек", "анекдот", "buni", "cat",
|
||||
"кис", "drink", "праздник", "fox", "лис", "frog", "лягушка", "horse", "лошадь", "лошадка", "monkeyuser",
|
||||
"owl", "сова", "сыч", "rabbit", "bunny", "кролик", "snail", "улитка", "xkcd", "dig", "копать", "fish",
|
||||
"fishing", "рыба", "рыбка", "рыбалка", "karma", "карма", "w", "п", "weather", "погода", "погодка",
|
||||
"погадка"}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmd == command {
|
||||
done = true
|
||||
outgoingMessage = msg
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Список команд бармэна
|
||||
if !done {
|
||||
cmds := []string{"rum", "ром", "vodka", "водка", "beer", "пиво", "tequila", "текила", "whisky", "виски",
|
||||
"absinthe", "абсент"}
|
||||
|
||||
// Тихо сам с собою я веду беседу...
|
||||
for _, command := range cmds {
|
||||
if cmd == command {
|
||||
done = true
|
||||
outgoingMessage = msg
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Заказываю выпивку кому-то ещё
|
||||
if !done {
|
||||
re := regexp.MustCompile(" +")
|
||||
pile := re.Split(cmd, 2)
|
||||
|
||||
for _, command := range cmds {
|
||||
if pile[0] == command {
|
||||
userNick := strings.TrimSpace(pile[1])
|
||||
|
||||
if userNick != "" {
|
||||
if userModeIsHere(channel, userNick) {
|
||||
// Проставляем правильный в конкретно данном случае username, так как отвечать мы
|
||||
// будем ему
|
||||
message.Misc.Username = strings.TrimSpace(pile[1])
|
||||
} else {
|
||||
msg = fmt.Sprintf("Я тут не вижу участника с ником %s", userNick)
|
||||
imChan <- iMsg{ChatID: channel, Text: msg}
|
||||
|
||||
return
|
||||
}
|
||||
} // в противном случае юзер просто поставил пробел на конце команды, интерпретируем это как
|
||||
// стаканчик юзеру
|
||||
|
||||
outgoingMessage = msg
|
||||
done = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Сложные команды, например, погода
|
||||
if !done {
|
||||
cmdLen := len(cmd)
|
||||
|
||||
cmds := []string{"w ", "п ", "погода ", "погодка ", "погадка ", "weather "}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmdLen > len(command) && cmd[0:len(command)] == command {
|
||||
done = true
|
||||
outgoingMessage = msg
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Отключаемые команды
|
||||
if !done {
|
||||
value := setting.Get(channel, "obutts")
|
||||
|
||||
if value == "1" {
|
||||
cmds := []string{"butt", "booty", "ass", "попа", "попка"}
|
||||
|
||||
for _, command := range cmds {
|
||||
cmdLen := len(command)
|
||||
if cmd[:cmdLen] == command {
|
||||
done = true
|
||||
outgoingMessage = msg
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
value = setting.Get(channel, "oboobs")
|
||||
|
||||
if value == "1" {
|
||||
cmds := []string{"tits", "boobs", "tities", "boobies", "сиси", "сисечки"}
|
||||
|
||||
for _, command := range cmds {
|
||||
cmdLen := len(command)
|
||||
if cmd[:cmdLen] == command {
|
||||
// done = true
|
||||
outgoingMessage = msg
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if outgoingMessage != "" {
|
||||
message.Message = outgoingMessage
|
||||
data, err := json.Marshal(message)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Unable to to serialize message for redis: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Заталкиваем наш json в редиску
|
||||
if err := RedisClient.Publish(ctx, Config.Redis.Channel, data).Err(); err != nil {
|
||||
log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err)
|
||||
} else {
|
||||
log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Это уже просто трёп в чятике
|
||||
var message sMsg
|
||||
message.From = Config.Redis.MyChannel
|
||||
message.Userid = user // как его видит сервер
|
||||
message.Chatid = channel // чятик, в который написал user
|
||||
message.Threadid = "" // тредиков в irc нету, поэтому это поле отправляем пустым
|
||||
message.Message = msg
|
||||
message.Plugin = Config.Redis.MyChannel
|
||||
message.Mode = "public"
|
||||
|
||||
// Если во фразе содержится ключевые слова, под это есть костыль в craniac-е - (предполагаем, что) он
|
||||
// знает ставить или не ставить флажок answer
|
||||
message.Misc.Answer = 0
|
||||
|
||||
// Предполагается что в канале бот должен отвечать, только если к нему обратились, либо это была команда
|
||||
if regexp.MustCompile(ircClient.GetNick()).Match([]byte(message.Message)) {
|
||||
message.Misc.Answer = 1
|
||||
}
|
||||
|
||||
// Попробуем выискать изменение кармы
|
||||
msgLen := len(msg)
|
||||
|
||||
// ++ или -- на конце фразы - это наш кандидат
|
||||
if msgLen > len("++") {
|
||||
if msg[msgLen-len("--"):msgLen] == "--" || msg[msgLen-len("++"):msgLen] == "++" {
|
||||
// Предполагается, что менять карму мы будем для одной фразы, то есть для 1 строки
|
||||
if len(strings.Split(msg, "\n")) == 1 {
|
||||
// Костыль для кармы
|
||||
message.Misc.Answer = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.Misc.Fwdcnt = 0
|
||||
message.Misc.Csign = Config.Csign
|
||||
message.Misc.Username = nick
|
||||
message.Misc.Botnick = Config.Irc.Nick
|
||||
message.Misc.Msgformat = 0
|
||||
|
||||
data, err := json.Marshal(message)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Unable to to serialize message for redis: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Заталкиваем наш json в редиску
|
||||
if err := RedisClient.Publish(ctx, Config.Redis.Channel, data).Err(); err != nil {
|
||||
log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err)
|
||||
} else {
|
||||
log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RedisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными.
|
||||
func RedisMsgParser(msg string) {
|
||||
if Shutdown {
|
||||
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо
|
||||
return
|
||||
}
|
||||
|
||||
var j rMsg
|
||||
|
||||
log.Debugf("Incoming raw json: %s", msg)
|
||||
|
||||
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
||||
log.Warnf("Unable to to parse message from redis channel: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Validate our j
|
||||
if exist := j.From; exist == "" {
|
||||
log.Warnf("Incorrect msg from redis, no from field: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if exist := j.Chatid; exist == "" {
|
||||
log.Warnf("Incorrect msg from redis, no chatid field: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if exist := j.Userid; exist == "" {
|
||||
log.Warnf("Incorrect msg from redis, no userid field: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if exist := j.Message; exist == "" {
|
||||
log.Warnf("Incorrect msg from redis, no message field: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if exist := j.Plugin; exist == "" {
|
||||
log.Warnf("Incorrect msg from redis, no plugin field: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if exist := j.Mode; exist == "" {
|
||||
log.Warnf("Incorrect msg from redis, no mode field: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет
|
||||
if j.Misc.Answer == 0 {
|
||||
log.Debug("Field Misc->Answer = 0, skipping message")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// j.Misc.BotNick тоже можно не передавать, тогда будет записана пустая строка
|
||||
// j.Misc.CSign если нам его не передали, возьмём значение из конфига
|
||||
if exist := j.Misc.Csign; exist == "" {
|
||||
j.Misc.Csign = Config.Csign
|
||||
}
|
||||
|
||||
// j.Misc.FwdCnt если нам его не передали, то будет 0
|
||||
if exist := j.Misc.Fwdcnt; exist == 0 {
|
||||
j.Misc.Fwdcnt = 1
|
||||
}
|
||||
|
||||
// j.Misc.GoodMorning может быть быть 1 или 0, по-умолчанию 0
|
||||
// j.Misc.MsgFormat может быть быть 1 или 0, по-умолчанию 0
|
||||
// j.Misc.Username можно не передавать, тогда будет пустая строка
|
||||
|
||||
// Отвалидировались, теперь вернёмся к нашим баранам.
|
||||
lines := regexp.MustCompile("\r?\n").Split(j.Message, -1)
|
||||
|
||||
for _, message := range lines {
|
||||
if userModeIsOped(j.Chatid, ircClient.GetNick()) || userModeIsVoiced(j.Chatid, ircClient.GetNick()) {
|
||||
imChanUnrestricted <- iMsg{ChatID: j.Chatid, Text: message}
|
||||
} else {
|
||||
imChan <- iMsg{ChatID: j.Chatid, Text: message}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
90
internal/irc/types.go
Normal file
90
internal/irc/types.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package irc
|
||||
|
||||
// Конфиг.
|
||||
type MyConfig struct {
|
||||
Redis struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
MyChannel string `json:"my_channel,omitempty"`
|
||||
} `json:"redis"`
|
||||
Irc struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Ssl bool `json:"ssl,omitempty"`
|
||||
SslVerify bool `json:"ssl_verify,omitempty"`
|
||||
Nick string `json:"nick,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Sasl bool `json:"sasl,omitempty"`
|
||||
Channels []string `json:"channels"`
|
||||
RateLimit struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
SimpleDelay int `json:"simple_delay,omitempty"`
|
||||
TokenBucket struct {
|
||||
Size int `json:"size,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
ExpirationTime int64 `json:"expiration_time,omitempty"`
|
||||
} `json:"token_bucket,omitempty"`
|
||||
}
|
||||
}
|
||||
Loglevel string `json:"loglevel,omitempty"`
|
||||
Log string `json:"log,omitempty"`
|
||||
Csign string `json:"csign,omitempty"`
|
||||
ForwardsMax int64 `json:"forwards_max,omitempty"`
|
||||
DataDir string `json:"data_dir,omitempty"`
|
||||
}
|
||||
|
||||
// Входящее сообщение из pubsub-канала redis-ки.
|
||||
type rMsg struct {
|
||||
From string `json:"from,omitempty"`
|
||||
Chatid string `json:"chatid,omitempty"`
|
||||
Userid string `json:"userid,omitempty"`
|
||||
Threadid string `json:"threadid,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Plugin string `json:"plugin,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
Misc struct {
|
||||
Answer int64 `json:"answer,omitempty"`
|
||||
Botnick string `json:"bot_nick,omitempty"`
|
||||
Csign string `json:"csign,omitempty"`
|
||||
Fwdcnt int64 `json:"fwd_cnt,omitempty"`
|
||||
GoodMorning int64 `json:"good_morning,omitempty"`
|
||||
Msgformat int64 `json:"msg_format,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
} `json:"Misc"`
|
||||
}
|
||||
|
||||
// Исходящее сообщение в pubsub-канал redis-ки.
|
||||
type sMsg struct {
|
||||
From string `json:"from"`
|
||||
Chatid string `json:"chatid"`
|
||||
Userid string `json:"userid"`
|
||||
Threadid string `json:"threadid"`
|
||||
Message string `json:"message"`
|
||||
Plugin string `json:"plugin"`
|
||||
Mode string `json:"mode"`
|
||||
Misc struct {
|
||||
Answer int64 `json:"answer"`
|
||||
Botnick string `json:"bot_nick"`
|
||||
Csign string `json:"csign"`
|
||||
Fwdcnt int64 `json:"fwd_cnt"`
|
||||
GoodMorning int64 `json:"good_morning"`
|
||||
Msgformat int64 `json:"msg_format"`
|
||||
Username string `json:"username"`
|
||||
} `json:"misc"`
|
||||
}
|
||||
|
||||
// TODO: Имплементировать тип сообщения - privmsg или action.
|
||||
type iMsg struct {
|
||||
ChatID string
|
||||
Text string
|
||||
}
|
||||
|
||||
// "Ведёрко" для реализации ограничителя скорости отправки исходящих сообщений - Token Bucket.
|
||||
type bucket struct {
|
||||
Timestamps []int64
|
||||
IsFull bool
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
255
internal/irc/util.go
Normal file
255
internal/irc/util.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"aleesa-irc-go/internal/setting"
|
||||
|
||||
"github.com/hjson/hjson-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет.
|
||||
func ReadConfig() {
|
||||
configLoaded := false
|
||||
executablePath, err := os.Executable()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get current executable path: %s", err)
|
||||
}
|
||||
|
||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||
|
||||
locations := []string{
|
||||
"~/.aleesa-misc-go.json",
|
||||
"~/aleesa-misc-go.json",
|
||||
"/etc/aleesa-misc-go.json",
|
||||
configJSONPath,
|
||||
}
|
||||
|
||||
for _, location := range locations {
|
||||
fileInfo, err := os.Stat(location)
|
||||
|
||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
||||
if fileInfo.Size() > 65535 {
|
||||
log.Warnf("Config file %s is too long for config, skipping", location)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(location)
|
||||
|
||||
// Не удалось прочитать, попробуем следующего кандидата
|
||||
if err != nil {
|
||||
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
||||
var sampleConfig MyConfig
|
||||
|
||||
var tmp map[string]interface{}
|
||||
err = hjson.Unmarshal(buf, &tmp)
|
||||
|
||||
// Не удалось распарсить - попробуем следующего кандидата
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
tmpjson, err := json.Marshal(tmp)
|
||||
|
||||
// Не удалось преобразовать map-ку в json
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Валидируем значения из конфига
|
||||
if sampleConfig.Redis.Server == "" {
|
||||
sampleConfig.Redis.Server = "localhost"
|
||||
|
||||
log.Infof("Redis server is not defined in config, using localhost")
|
||||
}
|
||||
|
||||
if sampleConfig.Redis.Port == 0 {
|
||||
sampleConfig.Redis.Port = 6379
|
||||
}
|
||||
|
||||
if sampleConfig.Redis.Channel == "" {
|
||||
log.Errorf("Channel field in config file %s must be set", location)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if sampleConfig.Redis.MyChannel == "" {
|
||||
log.Errorf("My_channel field in config file %s must be set", location)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Значения для IRC-клиента
|
||||
if sampleConfig.Irc.Server == "" {
|
||||
sampleConfig.Irc.Server = "localhost"
|
||||
|
||||
log.Errorf("Irc server is not defined in config, using localhost")
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.Port == 0 {
|
||||
sampleConfig.Irc.Port = 6667
|
||||
|
||||
log.Infof("Irc port is not defined in config, using 6667")
|
||||
}
|
||||
|
||||
if !sampleConfig.Irc.Ssl {
|
||||
sampleConfig.Irc.SslVerify = false
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.Nick == "" {
|
||||
log.Errorf("Irc nick is not defined in config, quitting")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.User == "" {
|
||||
sampleConfig.Irc.User = sampleConfig.Irc.Nick
|
||||
}
|
||||
|
||||
// Если sampleConfig.Irc.Password не задан, то авторизации через Nickserv или SASL не будет
|
||||
// Если sampleConfig.Irc.Sasl не задан, то авторизация происходит через NickServ
|
||||
|
||||
// Нам бот нужен на каких-то IRC-каналах, а не "просто так"
|
||||
if len(sampleConfig.Irc.Channels) < 1 {
|
||||
log.Errorf("No irc channels defined in config, quitting")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if (sampleConfig.Irc.RateLimit.Type != "simple_delay") && (sampleConfig.Irc.RateLimit.Type != "token_bucket") {
|
||||
sampleConfig.Irc.RateLimit.Type = "none"
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.RateLimit.Type != "simple_delay" {
|
||||
if sampleConfig.Irc.RateLimit.SimpleDelay < 50 {
|
||||
sampleConfig.Irc.RateLimit.SimpleDelay = 50
|
||||
}
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.RateLimit.Type == "token_bucket" {
|
||||
if sampleConfig.Irc.RateLimit.TokenBucket.Size < 3 {
|
||||
sampleConfig.Irc.RateLimit.TokenBucket.Size = 5
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.RateLimit.TokenBucket.Limit == 0 {
|
||||
sampleConfig.Irc.RateLimit.TokenBucket.Limit = 1
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.RateLimit.TokenBucket.Size < sampleConfig.Irc.RateLimit.TokenBucket.Limit {
|
||||
sampleConfig.Irc.RateLimit.TokenBucket.Size = 5
|
||||
sampleConfig.Irc.RateLimit.TokenBucket.Limit = 1
|
||||
}
|
||||
|
||||
if sampleConfig.Irc.RateLimit.TokenBucket.ExpirationTime < 2 {
|
||||
sampleConfig.Irc.RateLimit.TokenBucket.ExpirationTime = 2
|
||||
}
|
||||
}
|
||||
|
||||
if sampleConfig.Loglevel == "" {
|
||||
sampleConfig.Loglevel = "info"
|
||||
}
|
||||
|
||||
// sampleConfig.Log = "" if not set
|
||||
|
||||
if sampleConfig.Csign == "" {
|
||||
log.Errorf("Csign field in config file %s must be set", location)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if sampleConfig.ForwardsMax == 0 {
|
||||
sampleConfig.ForwardsMax = forwardMax
|
||||
}
|
||||
|
||||
if sampleConfig.DataDir == "" {
|
||||
log.Errorf("Data_dir field in config file %s must be set", location)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
Config = sampleConfig
|
||||
configLoaded = true
|
||||
|
||||
log.Infof("Using %s as config file", location)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if !configLoaded {
|
||||
log.Error("Config was not loaded! Refusing to start.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// SigHandler хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
|
||||
func SigHandler() {
|
||||
var err error
|
||||
|
||||
for {
|
||||
var s = <-SigChan
|
||||
switch s {
|
||||
case syscall.SIGINT:
|
||||
log.Infoln("Got SIGINT, quitting")
|
||||
case syscall.SIGTERM:
|
||||
log.Infoln("Got SIGTERM, quitting")
|
||||
case syscall.SIGQUIT:
|
||||
log.Infoln("Got SIGQUIT, quitting")
|
||||
|
||||
// Заходим на новую итерацию, если у нас "неинтересный" сигнал
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// Чтобы не срать в логи ошибками от редиски, проставим shutdown state приложения в true
|
||||
Shutdown = true
|
||||
|
||||
// Отпишемся от всех каналов и закроем коннект к редиске
|
||||
if err = Subscriber.Unsubscribe(ctx); err != nil {
|
||||
log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
|
||||
} else {
|
||||
log.Debug("Unsubscribe from all redis channels")
|
||||
}
|
||||
|
||||
if err = Subscriber.Close(); err != nil {
|
||||
log.Errorf("Unable to close redis connection cleanly: %s", err)
|
||||
} else {
|
||||
log.Debug("Close redis connection")
|
||||
}
|
||||
|
||||
log.Debug("Close irc connection")
|
||||
ircClient.Quit()
|
||||
|
||||
log.Debug("Close userModeDB")
|
||||
UserMode.Close()
|
||||
|
||||
log.Debug("Close settingsDB")
|
||||
setting.CloseAll()
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
Reference in New Issue
Block a user