0

4 Commits
v1.3 ... v1.4

Author SHA1 Message Date
eleksir
484bfa9ba0 Add more debug, small optimizations
* Replace all time.Sleep() with <-time.NewTimer().C
* Add log.debug callback to all incomming network events
* Use slices.Contains() instead of iterating slices in for Loop
2025-03-30 19:49:55 +03:00
eleksir
8f8073ca3e More linter fixes, disable gci linter as it conflicts with goimports 2025-03-30 16:59:10 +03:00
eleksir
7bcc407857 Add golangci-lint config and fix linter warns 2025-03-30 16:19:20 +03:00
eleksir
8c24a1d8c6 Implement grabbing +B flag, connect command sequence fixes
* Implement i-am-bot, +B, flag grabbing.
* Move collections to internal packages.
* Postpone nickserv login sequence to 376 and 422 events, as it should be after motd message
* Postpone join channels to 376 and 422 events, as it should be after motd message
2025-03-30 15:41:46 +03:00
12 changed files with 519 additions and 214 deletions

83
.golangci.yml Normal file
View File

@@ -0,0 +1,83 @@
version: "2"
run:
go: "1.21"
modules-download-mode: readonly
tests: false
allow-parallel-runners: true
linters:
default: none
enable:
- asasalint
- asciicheck
- bodyclose
- containedctx
- contextcheck
- decorder
- durationcheck
- errcheck
- errchkjson
- errorlint
- exhaustive
- ginkgolinter
- gocheckcompilerdirectives
- gocritic
- godot
- gosec
- gosmopolitan
- govet
- grouper
- importas
- ineffassign
- ireturn
- makezero
- mirror
- nakedret
- nilerr
- nilnil
- nlreturn
- noctx
- nolintlint
- nonamedreturns
- predeclared
- promlinter
- reassign
- revive
- rowserrcheck
- staticcheck
- unconvert
- unused
- wastedassign
- whitespace
- wsl
settings:
gosec:
excludes:
# Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
- G404
# Не еби мозги. Сертификаты для локалхоста или для локальной сети могут быть невалидными, если ты сможешь
# выписать валидный серт для локалхоста, тебе миллионы разработчиков поставят золотой памятник. Просто потому
# что эти пидоры-безпасники всрали всё что только можно всрать своими дебильными политиками.
- G402
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
- vendor$

View File

@@ -6,8 +6,7 @@ BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath
all: clean build all: clean build
build: build:
${GOOPTS} go build ${BUILDOPTS} -o aleesa-irc-go types.go globals.go collection.go irc-user-mode.go \ ${GOOPTS} go build ${BUILDOPTS} -o aleesa-irc-go .
settings-db-util.go util.go msg-parsers.go aleesa-irc-go-lib.go main.go
clean: clean:
go clean go clean

View File

@@ -4,22 +4,22 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"regexp" "regexp"
"slices"
"strings" "strings"
"time" "time"
"aleesa-irc-go/internal/boolcollection"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
irc "github.com/thoj/go-ircevent" irc "github.com/thoj/go-ircevent"
) )
// ircClientInit горутинка для работы с протоколом irc // ircClientInit горутинка для работы с протоколом irc.
func ircClientRun() { func ircClientRun() {
for { for !shutdown {
if shutdown { // Иницализируем irc-клиента.
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо // TODO: make use of capabilities: https://defs.ircdocs.horse/defs/clientcaps .
break // TODO: make use of tags caps https://defs.ircdocs.horse/defs/tags .
}
// Иницализируем irc-клиента
serverString := fmt.Sprintf("%s:%d", config.Irc.Server, config.Irc.Port) serverString := fmt.Sprintf("%s:%d", config.Irc.Server, config.Irc.Port)
log.Debugf("Preparing to connect to %s", serverString) log.Debugf("Preparing to connect to %s", serverString)
log.Debugf("Using nick %s and username %s", config.Irc.Nick, config.Irc.User) log.Debugf("Using nick %s and username %s", config.Irc.Nick, config.Irc.User)
@@ -29,10 +29,12 @@ func ircClientRun() {
if config.Irc.Ssl { if config.Irc.Ssl {
log.Debug("Force use ssl for connection") log.Debug("Force use ssl for connection")
ircClient.UseTLS = true ircClient.UseTLS = true
if !config.Irc.SslVerify { if !config.Irc.SslVerify {
log.Debug("Skip server certificate validation") log.Debug("Skip server certificate validation")
ircClient.TLSConfig = &tls.Config{InsecureSkipVerify: true} ircClient.TLSConfig = &tls.Config{InsecureSkipVerify: true}
} else { } else {
log.Debug("Force server certificate validation") log.Debug("Force server certificate validation")
@@ -47,13 +49,48 @@ func ircClientRun() {
ircClient.UseSASL = true 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) { ircClient.AddCallback("319", func(e *irc.Event) {
/* Это одна из строк с данными, прилетающая в ответ на запрос whois на определённого юзера /* Это одна из строк с данными, прилетающая в ответ на запрос whois на определённого юзера
* Из этой строки нас интересует, на каких каналах пользователь op (то есть с префиксом @) или имеет voice * Из этой строки нас интересует, на каких каналах пользователь op (то есть с префиксом @) или имеет voice
* (то есть с префиксом +) чтобы внести его в свою базу mode-ов. * (то есть с префиксом +) чтобы внести его в свою базу mode-ов.
*/ */
log.Debugf("319 RPL_WHOISCHANNELS, %s", e.Raw)
channelsWithModes := strings.Split(e.Arguments[2], " ") channelsWithModes := strings.Split(e.Arguments[2], " ")
dstNick := e.Arguments[1] dstNick := e.Arguments[1]
@@ -65,27 +102,30 @@ func ircClientRun() {
if channel == mychannel { if channel == mychannel {
// Для каждого канала формат строго 1 из 4-х: #channel | @#channel | +#channel | @+#channel // Для каждого канала формат строго 1 из 4-х: #channel | @#channel | +#channel | @+#channel
switch { switch modes {
case modes == "@+": case "@+":
userModeUpdateUser(channel, dstNick, "+o") userModeUpdateUser(channel, dstNick, "+o")
userModeUpdateUser(channel, dstNick, "+v") userModeUpdateUser(channel, dstNick, "+v")
case modes == "@": case "@":
userModeUpdateUser(channel, dstNick, "+o") userModeUpdateUser(channel, dstNick, "+o")
case modes == "+": case "+":
userModeUpdateUser(channel, dstNick, "+v") userModeUpdateUser(channel, dstNick, "+v")
default: default:
userModeUpdateUser(channel, dstNick, "-o") userModeUpdateUser(channel, dstNick, "-o")
userModeUpdateUser(channel, dstNick, "-v") userModeUpdateUser(channel, dstNick, "-v")
} }
break break
} }
} }
} }
}) })
// 433 ERR_NICKNAMEINUSE уже есть в github.com/thoj/go-ircevent/irc_callback.go.
ircClient.AddCallback("353", func(e *irc.Event) { ircClient.AddCallback("353", func(e *irc.Event) {
// Это одна из строк данных, прилетающая в ответ на запрос names, на канале // Это одна из строк данных, прилетающая в ответ на запрос names, на канале
log.Debugf("353 RPL_NAMREPLY, %s", e.Raw) e.Connection.Lock()
namesString := e.Arguments[3] namesString := e.Arguments[3]
channel := e.Arguments[2] channel := e.Arguments[2]
@@ -93,6 +133,7 @@ func ircClientRun() {
if channel == mychannel { if channel == mychannel {
for _, name := range strings.Split(namesString, " ") { for _, name := range strings.Split(namesString, " ") {
mode := name[:1] mode := name[:1]
var nick string var nick string
if mode == "@" || mode == "+" { if mode == "@" || mode == "+" {
@@ -101,25 +142,58 @@ func ircClientRun() {
nick = name nick = name
} }
switch { switch mode {
case mode == "@": case "@":
userModeUpdateUser(channel, nick, "+o") userModeUpdateUser(channel, nick, "+o")
case mode == "+": case "+":
userModeUpdateUser(channel, nick, "+v") userModeUpdateUser(channel, nick, "+v")
default: default:
userModeUpdateUser(channel, nick, "-o") userModeUpdateUser(channel, nick, "-o")
userModeUpdateUser(channel, nick, "-v") userModeUpdateUser(channel, nick, "-v")
} }
} }
break break
} }
} }
e.Connection.Unlock()
}) })
ircClient.AddCallback("352", func(e *irc.Event) { // Если сервер не может прочитать MOTD, то он может вернуть 422 ERR_NOMOTD, тоде самое навесим и туда тоже.
// Относительно бесполезная для нас команда. Не возвращает канала (в случае с ngircd), так что узнать mode ircClient.AddCallback("376", func(e *irc.Event) {
// юзера на конкретном канале из этого невозможно. А остальные данные пока непонятно куда применять. // TODO: make use of modes https://defs.ircdocs.horse/defs/usermodes
log.Warnf("352 RPL_WHOREPLY, %s", e.Raw) // Если у нас есть доступный +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, которые мы можем получить и сдампим это // Навесим коллбэков на все возможные и невозможные error status code, которые мы можем получить и сдампим это
@@ -157,6 +231,39 @@ func ircClientRun() {
log.Errorf("421 ERR_UNKNOWNCOMMAND, %s", e.Raw) 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) { ircClient.AddCallback("431", func(e *irc.Event) {
log.Errorf("431 ERR_NONICKNAMEGIVEN, %s", e.Raw) log.Errorf("431 ERR_NONICKNAMEGIVEN, %s", e.Raw)
}) })
@@ -167,8 +274,11 @@ func ircClientRun() {
ircClient.AddCallback("433", func(e *irc.Event) { ircClient.AddCallback("433", func(e *irc.Event) {
log.Errorf("433 ERR_NICKNAMEINUSE, %s", e.Raw) log.Errorf("433 ERR_NICKNAMEINUSE, %s", e.Raw)
nickIsUsed = true nickIsUsed = true
time.Sleep(60 * time.Second)
<-time.NewTimer(60 * time.Second).C // 1 minute sleep.
ircClient.Nick(config.Irc.Nick) ircClient.Nick(config.Irc.Nick)
}) })
@@ -179,6 +289,7 @@ func ircClientRun() {
log.Errorf("436 ERR_NICKCOLLISION, %s", e.Raw) 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) { ircClient.AddCallback("437", func(e *irc.Event) {
log.Errorf("437 ERR_UNAVAILRESOURCE, %s", e.Raw) log.Errorf("437 ERR_UNAVAILRESOURCE, %s", e.Raw)
}) })
@@ -191,6 +302,16 @@ func ircClientRun() {
log.Warnf("442 ERR_NOTONCHANNEL, %s", e.Raw) 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) { ircClient.AddCallback("451", func(e *irc.Event) {
// Предполагается, что надо авторизоваться, перед тем как что-то делать на сервере // Предполагается, что надо авторизоваться, перед тем как что-то делать на сервере
log.Errorf("451 ERR_NOTREGISTERED, %s", e.Raw) log.Errorf("451 ERR_NOTREGISTERED, %s", e.Raw)
@@ -213,18 +334,19 @@ func ircClientRun() {
log.Errorf("465 ERR_YOUREBANNEDCREEP, %s", e.Raw) 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) { ircClient.AddCallback("471", func(e *irc.Event) {
// Это значит, что народу на канале максимальное количество. Будем пробовать присунуться раз в 30 сек. // Это значит, что народу на канале максимальное количество. Будем пробовать присунуться раз в 30 сек.
channel := e.Arguments[1] channel := e.Arguments[1]
log.Warnf("471 ERR_CHANNELISFULL, %s", e.Raw) log.Warnf("471 ERR_CHANNELISFULL, %s", e.Raw)
time.Sleep(30 * time.Second) <-time.NewTimer(30 * time.Second).C
// Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет? // Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет?
for _, ircChan := range config.Irc.Channels { if slices.Contains(config.Irc.Channels, channel) {
if ircChan == channel {
ircClient.Join(channel) ircClient.Join(channel)
break
}
} }
}) })
@@ -237,14 +359,11 @@ func ircClientRun() {
// По завершении работ +i снимают. // По завершении работ +i снимают.
channel := e.Arguments[1] channel := e.Arguments[1]
log.Errorf("473 ERR_INVITEONLYCHAN, %s", e.Raw) log.Errorf("473 ERR_INVITEONLYCHAN, %s", e.Raw)
time.Sleep(30 * time.Second) <-time.NewTimer(30 * time.Second).C
// Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет? // Проверяем, а должны ли мы быть заджоенными к указанному, каналу, а то вдруг нет?
for _, ircChan := range config.Irc.Channels { if slices.Contains(config.Irc.Channels, channel) {
if ircChan == channel {
ircClient.Join(channel) ircClient.Join(channel)
break
}
} }
}) })
@@ -254,18 +373,21 @@ func ircClientRun() {
log.Errorf("474 ERR_BANNEDFROMCHAN, %s", e.Raw) log.Errorf("474 ERR_BANNEDFROMCHAN, %s", e.Raw)
// Если нас забанили, то информацию о MODE-ах пользователей мы теряем // Если нас забанили, то информацию о MODE-ах пользователей мы теряем
time.Sleep(30 * time.Second) <-time.NewTimer(30 * time.Second).C
// Вдруг, нас забанили, но какбэ не навсегда? // Вдруг, нас забанили, но какбэ не навсегда?
// TODO: вынести в настройки?
// Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет? // Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет?
for _, ircChan := range config.Irc.Channels { if slices.Contains(config.Irc.Channels, channel) {
if ircChan == channel {
ircClient.Join(channel) ircClient.Join(channel)
break
}
} }
}) })
ircClient.AddCallback("475", func(e *irc.Event) {
log.Errorf("475 ERR_BADCHANNELKEY, %s", e.Raw)
})
ircClient.AddCallback("477", func(e *irc.Event) { ircClient.AddCallback("477", func(e *irc.Event) {
log.Errorf("477 ERR_NOCHANMODES, %s", e.Raw) log.Errorf("477 ERR_NOCHANMODES, %s", e.Raw)
}) })
@@ -274,6 +396,10 @@ func ircClientRun() {
log.Errorf("478 ERR_BANLISTFULL, %s", e.Raw) 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) { ircClient.AddCallback("482", func(e *irc.Event) {
log.Errorf("482 ERR_CHANOPRIVSNEEDED, %s", e.Raw) log.Errorf("482 ERR_CHANOPRIVSNEEDED, %s", e.Raw)
}) })
@@ -286,6 +412,10 @@ func ircClientRun() {
log.Errorf("485 ERR_UNIQOPPRIVSNEEDED, %s", e.Raw) 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) { ircClient.AddCallback("501", func(e *irc.Event) {
log.Errorf("501 ERR_UMODEUNKNOWNFLAG, %s", e.Raw) log.Errorf("501 ERR_UMODEUNKNOWNFLAG, %s", e.Raw)
}) })
@@ -294,22 +424,6 @@ func ircClientRun() {
log.Errorf("502 ERR_USERSDONTMATCH, %s", e.Raw) log.Errorf("502 ERR_USERSDONTMATCH, %s", e.Raw)
}) })
// Сделаем уже что-то полезное! Motd нам уже прислали и теперь можно авторизоваться и джойниться
ircClient.AddCallback("001", func(e *irc.Event) {
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}
}
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("KICK", func(e *irc.Event) { ircClient.AddCallback("KICK", func(e *irc.Event) {
dstNick := e.Arguments[1] dstNick := e.Arguments[1]
@@ -321,7 +435,8 @@ func ircClientRun() {
userMode.Delete(channel) userMode.Delete(channel)
// TODO: reason? // TODO: reason?
log.Warnf("%s kicks us from %s", srcFullNick, channel) log.Warnf("%s kicks us from %s", srcFullNick, channel)
time.Sleep(10 * time.Second) <-time.NewTimer(10 * time.Second).C
ircClient.Join(channel) ircClient.Join(channel)
} else { } else {
// Кого-то другого кикнули с канала // Кого-то другого кикнули с канала
@@ -341,14 +456,15 @@ func ircClientRun() {
log.Warn("I regain my nick") log.Warn("I regain my nick")
} else { } else {
log.Warn("I regain my nick, trying to identify myself via NickServ") log.Warn("I regain my nick, trying to identify myself via NickServ")
message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password) message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password)
imChan <- iMsg{ChatId: "NickServ", Text: message} imChan <- iMsg{ChatID: "NickServ", Text: message}
} }
} else { } else {
log.Infof("%s renames themself to %s", srcNick, dstNick) log.Infof("%s renames themself to %s", srcNick, dstNick)
} }
// Неважно чей ник сменился, надо забыть, что было и снова узнать mode-ы сменишего nick джентельмена // Неважно чей ник сменился, надо забыть, что было и снова узнать mode-ы сменишего nick джентельмена.
// TODO: реализовать userModeRenameUser() // TODO: реализовать userModeRenameUser()
userModePurgeUser(srcNick) userModePurgeUser(srcNick)
ircClient.Whois(dstNick) ircClient.Whois(dstNick)
@@ -360,12 +476,12 @@ func ircClientRun() {
channel := e.Arguments[0] channel := e.Arguments[0]
if nick == ircClient.GetNick() { if nick == ircClient.GetNick() {
// Команда names отправляется автоматом.
log.Infof("I joined to %s", channel) log.Infof("I joined to %s", channel)
// Команда names отправляется автоматом
} else { } else {
log.Infof("%s joined to %s", fullNick, channel) log.Infof("%s joined to %s", fullNick, channel)
// Технически, тут не надо спрашивать whois на пользователя, но мы спрашиваем, чтобы уточнить mode, // Технически, тут не надо спрашивать whois на пользователя, но мы спрашиваем, чтобы уточнить mode,
// вдруг сервер проставляет mode заранее (хотя не должен) // вдруг сервер проставляет mode заранее (хотя не должен).
ircClient.Whois(nick) ircClient.Whois(nick)
} }
}) })
@@ -403,6 +519,7 @@ func ircClientRun() {
channel := e.Arguments[0] channel := e.Arguments[0]
fullSrcNick := e.Source fullSrcNick := e.Source
srcNick := e.Nick srcNick := e.Nick
var dstNick string var dstNick string
if len(e.Arguments) >= 3 { // кого-то по-MODE-или на канале if len(e.Arguments) >= 3 { // кого-то по-MODE-или на канале
@@ -415,6 +532,7 @@ func ircClientRun() {
} }
} else { // Установка mode-а при заходе на сервер } else { // Установка mode-а при заходе на сервер
dstNick = e.Arguments[0] dstNick = e.Arguments[0]
log.Infof("Server set my mode to %s", mode) log.Infof("Server set my mode to %s", mode)
} }
@@ -454,17 +572,16 @@ func ircClientRun() {
ircMsgParser(e.Arguments[0], e.Nick, e.User, e.Source, e.Arguments[1]) ircMsgParser(e.Arguments[0], e.Nick, e.User, e.Source, e.Arguments[1])
}) })
/*
ircClient.AddCallback("*", func(e *irc.Event) { ircClient.AddCallback("*", func(e *irc.Event) {
log.Debugf("Incoming EVENT (Raw): %s", e.Raw) log.Debugf("Incoming EVENT (Raw): %s", e.Raw)
}) })
*/
log.Debugf("Make an actual connection to irc") log.Debugf("Make an actual connection to irc")
if err := ircClient.Connect(serverString); err != nil { if err := ircClient.Connect(serverString); err != nil {
log.Fatalf("Unable to prepare irc connection: %s", err) log.Errorf("Unable to prepare irc connection: %s", err)
time.Sleep(3 * time.Second) <-time.NewTimer(3 * time.Second).C // Sleep for 3 seconds.
continue continue
} }
@@ -478,21 +595,22 @@ func ircSend() {
for { for {
m := <-imChan m := <-imChan
switch { switch config.Irc.RateLimit.Type {
case config.Irc.RateLimit.Type == "simple_delay": case "simple_delay":
log.Debugf("Sending to chat %s message: %s", m.ChatId, m.Text) log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text)
if m.Text[0:4] == "/me " { if m.Text[0:4] == "/me " {
ircClient.Action(m.ChatId, m.Text) ircClient.Action(m.ChatID, m.Text)
} else { } else {
ircClient.Privmsg(m.ChatId, m.Text) ircClient.Privmsg(m.ChatID, m.Text)
} }
sleepDelay := time.Duration(config.Irc.RateLimit.SimpleDelay) * time.Millisecond sleepDelay := time.Duration(config.Irc.RateLimit.SimpleDelay) * time.Millisecond
log.Debugf("Due to delay type simple_delay waiting for %d milliseconds", int(sleepDelay)) log.Debugf("Due to delay type simple_delay waiting for %d milliseconds", int(sleepDelay))
time.Sleep(sleepDelay) <-time.NewTimer(sleepDelay).C
case config.Irc.RateLimit.Type == "token_bucket": case "token_bucket":
var currentTimeMs = time.Now().UnixMilli() var currentTimeMs = time.Now().UnixMilli()
var expirationTimeMs = config.Irc.RateLimit.TokenBucket.ExpirationTime * 1000 var expirationTimeMs = config.Irc.RateLimit.TokenBucket.ExpirationTime * 1000
if len(msgBucket.Timestamps) > 0 { if len(msgBucket.Timestamps) > 0 {
@@ -506,6 +624,7 @@ func ircSend() {
bucketFill := len(newBucket.Timestamps) bucketFill := len(newBucket.Timestamps)
log.Debugf("Message bucket filled with %d/%d messages", bucketFill, config.Irc.RateLimit.TokenBucket.Size) log.Debugf("Message bucket filled with %d/%d messages", bucketFill, config.Irc.RateLimit.TokenBucket.Size)
newBucket.Timestamps = append(newBucket.Timestamps, currentTimeMs) newBucket.Timestamps = append(newBucket.Timestamps, currentTimeMs)
if bucketFill >= config.Irc.RateLimit.TokenBucket.Size { if bucketFill >= config.Irc.RateLimit.TokenBucket.Size {
@@ -516,38 +635,41 @@ func ircSend() {
} else { } else {
// Проставим время отправки сообщения // Проставим время отправки сообщения
log.Debug("Message bucket is empty") log.Debug("Message bucket is empty")
msgBucket.Timestamps = append(msgBucket.Timestamps, currentTimeMs) msgBucket.Timestamps = append(msgBucket.Timestamps, currentTimeMs)
} }
if msgBucket.IsFull { if msgBucket.IsFull {
log.Debug("Message bucket overflowed, hitting ratelimit") log.Debug("Message bucket overflowed, hitting ratelimit")
sleepPeriod := expirationTimeMs - (currentTimeMs - msgBucket.Timestamps[len(msgBucket.Timestamps)-1]) sleepPeriod := expirationTimeMs - (currentTimeMs - msgBucket.Timestamps[len(msgBucket.Timestamps)-1])
if config.Irc.RateLimit.TokenBucket.Limit > 0 { if config.Irc.RateLimit.TokenBucket.Limit > 0 {
sleepPeriod = sleepPeriod / int64(config.Irc.RateLimit.TokenBucket.Limit) sleepPeriod /= int64(config.Irc.RateLimit.TokenBucket.Limit)
} }
log.Debugf("Sleeping for %d milliseconds", sleepPeriod) log.Debugf("Sleeping for %d milliseconds", sleepPeriod)
time.Sleep(time.Duration(sleepPeriod) * time.Millisecond) <-time.NewTimer(time.Duration(sleepPeriod) * time.Millisecond).C
currentTimeMs = time.Now().UnixMilli() currentTimeMs = time.Now().UnixMilli()
// Обновим время отправки сообщения // Обновим время отправки сообщения
msgBucket.Timestamps[len(msgBucket.Timestamps)-1] = currentTimeMs msgBucket.Timestamps[len(msgBucket.Timestamps)-1] = currentTimeMs
} }
log.Debugf("Sending to chat %s message: %s", m.ChatId, m.Text) log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text)
if len(m.Text) > 4 && m.Text[0:4] == "/me " { if len(m.Text) > 4 && m.Text[0:4] == "/me " {
ircClient.Action(m.ChatId, m.Text[4:]) ircClient.Action(m.ChatID, m.Text[4:])
} else { } else {
ircClient.Privmsg(m.ChatId, m.Text) ircClient.Privmsg(m.ChatID, m.Text)
} }
default: default:
log.Debugf("Sending to chat %s message: %s", m.ChatId, m.Text) log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text)
if len(m.Text) > 4 && m.Text[0:4] == "/me " { if len(m.Text) > 4 && m.Text[0:4] == "/me " {
ircClient.Action(m.ChatId, m.Text[4:]) ircClient.Action(m.ChatID, m.Text[4:])
} else { } else {
ircClient.Privmsg(m.ChatId, m.Text) ircClient.Privmsg(m.ChatID, m.Text)
} }
} }
} }
@@ -556,12 +678,12 @@ func ircSend() {
func ircSendUnrestricted() { func ircSendUnrestricted() {
for { for {
m := <-imChanUnrestricted m := <-imChanUnrestricted
log.Debugf("Skipping ratelimit and sending to chat %s message: %s", m.ChatId, m.Text) 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 " { if len(m.Text) > 4 && m.Text[0:4] == "/me " {
ircClient.Action(m.ChatId, m.Text[4:]) ircClient.Action(m.ChatID, m.Text[4:])
} else { } else {
ircClient.Privmsg(m.ChatId, m.Text) ircClient.Privmsg(m.ChatID, m.Text)
} }
} }
} }

View File

@@ -4,47 +4,56 @@ import (
"context" "context"
"os" "os"
"aleesa-irc-go/internal/anycollection"
"aleesa-irc-go/internal/boolcollection"
"github.com/cockroachdb/pebble" "github.com/cockroachdb/pebble"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
irc "github.com/thoj/go-ircevent" irc "github.com/thoj/go-ircevent"
) )
// Config - это у нас глобальная штука :) // Config - это у нас глобальная штука :).
var config myConfig var config myConfig
// To break circular message forwarding we must set some sane default, it can be overridden via config // To break circular message forwarding we must set some sane default, it can be overridden via config.
var forwardMax int64 = 5 var forwardMax int64 = 5
// Объектики irc-клиента // Объектики irc-клиента.
var ircClient *irc.Connection var ircClient *irc.Connection
var nickIsUsed = false var nickIsUsed = false
// Объектики клиента-редиски // Объектики клиента-редиски.
var redisClient *redis.Client var redisClient *redis.Client
var subscriber *redis.PubSub var subscriber *redis.PubSub
// Main context // Main context.
var ctx = context.Background() var ctx = context.Background()
// Ставится в true, если мы получили сигнал на выключение // Ставится в true, если мы получили сигнал на выключение.
var shutdown = false var shutdown = false
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов // Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
var sigChan = make(chan os.Signal, 1) var sigChan = make(chan os.Signal, 1)
// Канал, в который пишутся сообщения для отправки в IRC в обычном порядке // Канал, в который пишутся сообщения для отправки в IRC в обычном порядке.
var imChan = make(chan iMsg, 10000) var imChan = make(chan iMsg, 10000)
// Канал, в который пишутся сообщения для отправки в IRC, если ограничений нет (у бота +o или +v на канале) // Канал, в который пишутся сообщения для отправки в IRC, если ограничений нет (у бота +o или +v на канале).
var imChanUnrestricted = make(chan iMsg, 100) var imChanUnrestricted = make(chan iMsg, 100)
// "Ведёрко" с timestamp-ами последних отправленных сообщений // "Ведёрко" с timestamp-ами последних отправленных сообщений.
var msgBucket bucket var msgBucket bucket
// Мапка с открытыми дескрипторами баз с настройками // Мапка с открытыми дескрипторами баз с настройками.
var settingsDB = make(map[string]*pebble.DB) var settingsDB = make(map[string]*pebble.DB)
// "Базюлька" с MODE-ами пользователей на каналах // "Базюлька" с MODE-ами пользователей на каналах.
var userMode = NewCollection() 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: */ /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -1,21 +1,21 @@
package main package anycollection
import ( import (
"sync" "sync"
) )
// Collection это sync.Map-ка, хранящая в себе произвольные итемы // Collection это sync.Map-ка, хранящая в себе произвольные итемы.
type Collection struct { type Collection struct {
items sync.Map items sync.Map
close chan struct{} close chan struct{}
} }
// Item представляет собой интерфейс для хранения произвольных данных // Item представляет собой интерфейс для хранения произвольных данных.
type item struct { type item struct {
data interface{} data any
} }
// NewCollection создаёт инстанс коллекции // NewCollection создаёт инстанс коллекции.
func NewCollection() *Collection { func NewCollection() *Collection {
c := &Collection{ c := &Collection{
close: make(chan struct{}), close: make(chan struct{}),
@@ -24,8 +24,8 @@ func NewCollection() *Collection {
return c return c
} }
// Get достаёт данные по заданному ключу из коллекции // Get достаёт данные по заданному ключу из коллекции.
func (collection *Collection) Get(key interface{}) (interface{}, bool) { func (collection *Collection) Get(key any) (any, bool) {
obj, exists := collection.items.Load(key) obj, exists := collection.items.Load(key)
if !exists { if !exists {
@@ -37,16 +37,16 @@ func (collection *Collection) Get(key interface{}) (interface{}, bool) {
return item.data, true return item.data, true
} }
// Set сохраняет данные с заданным ключом в коллекцию // Set сохраняет данные с заданным ключом в коллекцию.
func (collection *Collection) Set(key interface{}, value interface{}) { func (collection *Collection) Set(key any, value any) {
collection.items.Store(key, item{ collection.items.Store(key, item{
data: value, data: value,
}) })
} }
// Range применяет функцию f ко всем ключам в коллекции // Range применяет функцию f ко всем ключам в коллекции.
func (collection *Collection) Range(f func(key, value interface{}) bool) { func (collection *Collection) Range(f func(key, value any) bool) {
fn := func(key, value interface{}) bool { fn := func(key, value any) bool {
item := value.(item) item := value.(item)
return f(key, item.data) return f(key, item.data)
@@ -55,12 +55,12 @@ func (collection *Collection) Range(f func(key, value interface{}) bool) {
collection.items.Range(fn) collection.items.Range(fn)
} }
// Delete удаляет ключ и значение из коллекции данных // Delete удаляет ключ и значение из коллекции данных.
func (collection *Collection) Delete(key interface{}) { func (collection *Collection) Delete(key any) {
collection.items.Delete(key) collection.items.Delete(key)
} }
// Close очищает и высвобождает ресурсы, занятые коллекцией // Close очищает и высвобождает ресурсы, занятые коллекцией.
func (collection *Collection) Close() { func (collection *Collection) Close() {
collection.close <- struct{}{} collection.close <- struct{}{}
collection.items = sync.Map{} collection.items = sync.Map{}

View File

@@ -0,0 +1,58 @@
package boolcollection
import (
"sync"
)
// Collection это sync.Map-ка, хранящая в себе итемы типа bool.
type Collection struct {
items sync.Map
close chan struct{}
}
// Item представляет собой интерфейс для хранения данных типа bool.
type item struct {
data bool
}
// NewCollection создаёт инстанс коллекции.
func NewCollection() *Collection {
c := &Collection{
close: make(chan struct{}),
}
return c
}
// Get достаёт данные по заданному ключу из коллекции.
func (collection *Collection) Get(key string) (bool, bool) {
obj, exists := collection.items.Load(key)
if !exists {
return false, false
}
item := obj.(item)
return item.data, true
}
// Set сохраняет данные с заданным ключом в коллекцию.
func (collection *Collection) Set(key string, value bool) {
collection.items.Store(key, item{
data: value,
})
}
// Delete удаляет ключ и значение из коллекции данных.
func (collection *Collection) Delete(key string) {
collection.items.Delete(key)
}
// Close очищает и высвобождает ресурсы, занятые коллекцией.
func (collection *Collection) Close() {
collection.close <- struct{}{}
collection.items = sync.Map{}
}
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -8,7 +8,7 @@ import "strings"
активируют мягкий ratelimit на стороне сервера, без них, сервер просто рвёт соединение по превышению лимитов активируют мягкий ratelimit на стороне сервера, без них, сервер просто рвёт соединение по превышению лимитов
*/ */
// Удаляет сведения о MODE-ах пользователя для заданного канала // Удаляет сведения о MODE-ах пользователя для заданного канала.
func userModeDeleteUser(ircChan string, nick string) { func userModeDeleteUser(ircChan string, nick string) {
channel, ok := userMode.Get(ircChan) channel, ok := userMode.Get(ircChan)
@@ -24,16 +24,17 @@ func userModeDeleteUser(ircChan string, nick string) {
} }
} }
// Удаляет сведения о MODE-ах пользователя для всех каналов, на которых есть бот // Удаляет сведения о MODE-ах пользователя для всех каналов, на которых есть бот.
func userModePurgeUser(nick string) { func userModePurgeUser(nick string) {
for _, channel := range config.Irc.Channels { for _, channel := range config.Irc.Channels {
userModeDeleteUser(channel, nick) userModeDeleteUser(channel, nick)
} }
} }
// Обновляет или создаёт запись о mode-ах пользователей // Обновляет или создаёт запись о mode-ах пользователей.
func userModeUpdateUser(ircChan string, nick string, modes string) { func userModeUpdateUser(ircChan string, nick string, modes string) {
var channelData map[string]map[string]bool var channelData map[string]map[string]bool
channel, ok := userMode.Get(ircChan) channel, ok := userMode.Get(ircChan)
if ok { if ok {
@@ -48,12 +49,12 @@ func userModeUpdateUser(ircChan string, nick string, modes string) {
channelData[nick] = make(map[string]bool) channelData[nick] = make(map[string]bool)
} }
switch { switch modes[:1] {
case modes[:1] == "+": case "+":
for _, mode := range strings.Split(modes[1:], "") { for _, mode := range strings.Split(modes[1:], "") {
channelData[nick][mode] = true channelData[nick][mode] = true
} }
case modes[:1] == "-": case "-":
for _, mode := range strings.Split(modes[1:], "") { for _, mode := range strings.Split(modes[1:], "") {
channelData[nick][mode] = false channelData[nick][mode] = false
} }
@@ -62,9 +63,10 @@ func userModeUpdateUser(ircChan string, nick string, modes string) {
userMode.Set(ircChan, channelData) userMode.Set(ircChan, channelData)
} }
// Возвращает значение MODE-а v для запрошенного ника // Возвращает значение MODE-а v для запрошенного ника.
func userModeIsVoiced(ircChan string, nick string) bool { func userModeIsVoiced(ircChan string, nick string) bool {
var channelData map[string]map[string]bool var channelData map[string]map[string]bool
channel, ok := userMode.Get(ircChan) channel, ok := userMode.Get(ircChan)
if ok { if ok {
@@ -88,9 +90,10 @@ func userModeIsVoiced(ircChan string, nick string) bool {
return false return false
} }
// Возвращает значение MODE-а o для запрошенного ника // Возвращает значение MODE-а o для запрошенного ника.
func userModeIsOped(ircChan string, nick string) bool { func userModeIsOped(ircChan string, nick string) bool {
var channelData map[string]map[string]bool var channelData map[string]map[string]bool
channel, ok := userMode.Get(ircChan) channel, ok := userMode.Get(ircChan)
if ok { if ok {
@@ -116,6 +119,7 @@ func userModeIsOped(ircChan string, nick string) bool {
func userModeIsHere(ircChan string, nick string) bool { func userModeIsHere(ircChan string, nick string) bool {
var channelData map[string]map[string]bool var channelData map[string]map[string]bool
channel, ok := userMode.Get(ircChan) channel, ok := userMode.Get(ircChan)
if ok { if ok {

View File

@@ -11,7 +11,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// Производит некоторую инициализацию перед запуском main() // Производит некоторую инициализацию перед запуском main().
func init() { func init() {
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
DisableQuote: true, DisableQuote: true,
@@ -52,7 +52,7 @@ func init() {
userMode.Delete("channel") userMode.Delete("channel")
} }
// Собственно, какбэ "точка входа" - основная процедура в нашем боте // Собственно, какбэ "точка входа" - основная процедура в нашем боте.
func main() { func main() {
// Main context // Main context
var ctx = context.Background() var ctx = context.Background()

View File

@@ -9,8 +9,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// ircMsgParser парсит сообщения, прилетевшие из IRC-ки // ircMsgParser парсит сообщения, прилетевшие из IRC-ки.
func ircMsgParser(channel string, nick string, user string, source string, msg string) { func ircMsgParser(channel string, nick string, user string, source string, msg string) { //nolint: revive
// nick - это выбранный пользователем nick (если он занят, то его "нарисует" сервер) // nick - это выбранный пользователем nick (если он занят, то его "нарисует" сервер)
// user - это короткое имя пользователя, под которым его видит сервер // user - это короткое имя пользователя, под которым его видит сервер
// source - это длинное имя пользователя, оно содержит в себе помимо user, ещё и ip с которого пришёл пользователь // source - это длинное имя пользователя, оно содержит в себе помимо user, ещё и ip с которого пришёл пользователь
@@ -48,50 +48,50 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
switch { switch {
case cmd == "help" || msg == "помощь": case cmd == "help" || msg == "помощь":
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%shelp | %sпомощь - это сообщение", config.Csign, config.Csign)} 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("%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("%sbuni - комикс-стрип hapi buni", config.Csign)}
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sbunny - кролик", 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("%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("%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("%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("%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("%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("%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("%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("%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("%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("%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("%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("%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("%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("%skarma фраза - посмотреть карму фразы", config.Csign)}
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sкарма фраза - посмотреть карму фразы", 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.Sprintln("фраза++ | фраза-- - повысить или понизить карму фразы")}
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%slat | %sлат - сгенерировать фразу из крылатого латинского выражения", config.Csign, config.Csign)} 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("%smonkeyuser - комикс-стрип MonkeyUser", config.Csign)}
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sowl | %sсова - сова", config.Csign, 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("%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("%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("%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("%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("%sver | %sversion - написать что-то про версию ПО", config.Csign, config.Csign)}
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sверсия - написать что-то про версию ПО", 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("%sw <город> | %sп <город> - погода в городе", config.Csign, config.Csign)}
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sxkcd - комикс-стрип с xkcb.ru", config.Csign)} imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sxkcd - комикс-стрип с xkcb.ru", config.Csign)}
if userModeIsOped(channel, nick) { if userModeIsOped(channel, nick) {
imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sadmin - настройки некоторых плагинов бота для канала", config.Csign)} imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin - настройки некоторых плагинов бота для канала", config.Csign)}
} }
return return
case cmd == "admin": case cmd == "admin":
if userModeIsOped(channel, nick) { 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 # - где 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 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 # - где 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)} 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 return
@@ -100,16 +100,16 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if userModeIsOped(channel, nick) { if userModeIsOped(channel, nick) {
value := getSetting(channel, "oboobs") value := getSetting(channel, "oboobs")
switch { switch value {
case value == "": case "":
_ = saveSetting(channel, "oboobs", "0") _ = saveSetting(channel, "oboobs", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
case value == "0": case "0":
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
case value == "1": case "1":
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs включен"} imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs включен"}
default: default:
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
} }
} }
@@ -120,9 +120,9 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
err := saveSetting(channel, "oboobs", "1") err := saveSetting(channel, "oboobs", "1")
if err != nil { if err != nil {
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs всё ещё выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs всё ещё выключен"}
} else { } else {
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs включен"} imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs включен"}
} }
} }
@@ -131,7 +131,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
case cmd == "admin oboobs 0": case cmd == "admin oboobs 0":
if userModeIsOped(channel, nick) { if userModeIsOped(channel, nick) {
_ = saveSetting(channel, "oboobs", "0") _ = saveSetting(channel, "oboobs", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
} }
return return
@@ -140,16 +140,16 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if userModeIsOped(channel, nick) { if userModeIsOped(channel, nick) {
value := getSetting(channel, "obutts") value := getSetting(channel, "obutts")
switch { switch value {
case value == "": case "":
_ = saveSetting(channel, "obutts", "0") _ = saveSetting(channel, "obutts", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
case value == "0": case "0":
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
case value == "1": case "1":
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts включен"} imChan <- iMsg{ChatID: nick, Text: "Плагин obutts включен"}
default: default:
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
} }
} }
@@ -160,9 +160,9 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
err := saveSetting(channel, "obutts", "1") err := saveSetting(channel, "obutts", "1")
if err != nil { if err != nil {
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts всё ещё выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин obutts всё ещё выключен"}
} else { } else {
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts включен"} imChan <- iMsg{ChatID: nick, Text: "Плагин obutts включен"}
} }
} }
@@ -171,7 +171,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
case cmd == "admin obutts 0": case cmd == "admin obutts 0":
if userModeIsOped(channel, nick) { if userModeIsOped(channel, nick) {
_ = saveSetting(channel, "obutts", "0") _ = saveSetting(channel, "obutts", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
} }
return return
@@ -192,6 +192,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd == command { if cmd == command {
done = true done = true
outgoingMessage = msg outgoingMessage = msg
break break
} }
} }
@@ -206,6 +207,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd == command { if cmd == command {
done = true done = true
outgoingMessage = msg outgoingMessage = msg
break break
} }
} }
@@ -226,7 +228,8 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
message.Misc.Username = strings.TrimSpace(pile[1]) message.Misc.Username = strings.TrimSpace(pile[1])
} else { } else {
msg = fmt.Sprintf("Я тут не вижу участника с ником %s", userNick) msg = fmt.Sprintf("Я тут не вижу участника с ником %s", userNick)
imChan <- iMsg{ChatId: channel, Text: msg} imChan <- iMsg{ChatID: channel, Text: msg}
return return
} }
} // в противном случае юзер просто поставил пробел на конце команды, интерпретируем это как } // в противном случае юзер просто поставил пробел на конце команды, интерпретируем это как
@@ -234,6 +237,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
outgoingMessage = msg outgoingMessage = msg
done = true done = true
break break
} }
} }
@@ -250,6 +254,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmdLen > len(command) && cmd[0:len(command)] == command { if cmdLen > len(command) && cmd[0:len(command)] == command {
done = true done = true
outgoingMessage = msg outgoingMessage = msg
break break
} }
} }
@@ -267,6 +272,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd[:cmdLen] == command { if cmd[:cmdLen] == command {
done = true done = true
outgoingMessage = msg outgoingMessage = msg
break break
} }
} }
@@ -283,6 +289,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd[:cmdLen] == command { if cmd[:cmdLen] == command {
// done = true // done = true
outgoingMessage = msg outgoingMessage = msg
break break
} }
} }
@@ -297,6 +304,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if err != nil { if err != nil {
log.Warnf("Unable to to serialize message for redis: %s", err) log.Warnf("Unable to to serialize message for redis: %s", err)
return return
} }
@@ -351,6 +359,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if err != nil { if err != nil {
log.Warnf("Unable to to serialize message for redis: %s", err) log.Warnf("Unable to to serialize message for redis: %s", err)
return return
} }
@@ -361,11 +370,9 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data)) log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data))
} }
} }
return
} }
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными // redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными.
func redisMsgParser(msg string) { func redisMsgParser(msg string) {
if shutdown { if shutdown {
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо // Если мы завершаем работу программы, то нам ничего обрабатывать не надо
@@ -378,43 +385,51 @@ func redisMsgParser(msg string) {
if err := json.Unmarshal([]byte(msg), &j); err != nil { if err := json.Unmarshal([]byte(msg), &j); err != nil {
log.Warnf("Unable to to parse message from redis channel: %s", err) log.Warnf("Unable to to parse message from redis channel: %s", err)
return return
} }
// Validate our j // Validate our j
if exist := j.From; exist == "" { if exist := j.From; exist == "" {
log.Warnf("Incorrect msg from redis, no from field: %s", msg) log.Warnf("Incorrect msg from redis, no from field: %s", msg)
return return
} }
if exist := j.Chatid; exist == "" { if exist := j.Chatid; exist == "" {
log.Warnf("Incorrect msg from redis, no chatid field: %s", msg) log.Warnf("Incorrect msg from redis, no chatid field: %s", msg)
return return
} }
if exist := j.Userid; exist == "" { if exist := j.Userid; exist == "" {
log.Warnf("Incorrect msg from redis, no userid field: %s", msg) log.Warnf("Incorrect msg from redis, no userid field: %s", msg)
return return
} }
if exist := j.Message; exist == "" { if exist := j.Message; exist == "" {
log.Warnf("Incorrect msg from redis, no message field: %s", msg) log.Warnf("Incorrect msg from redis, no message field: %s", msg)
return return
} }
if exist := j.Plugin; exist == "" { if exist := j.Plugin; exist == "" {
log.Warnf("Incorrect msg from redis, no plugin field: %s", msg) log.Warnf("Incorrect msg from redis, no plugin field: %s", msg)
return return
} }
if exist := j.Mode; exist == "" { if exist := j.Mode; exist == "" {
log.Warnf("Incorrect msg from redis, no mode field: %s", msg) log.Warnf("Incorrect msg from redis, no mode field: %s", msg)
return return
} }
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет // j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет
if j.Misc.Answer == 0 { if j.Misc.Answer == 0 {
log.Debug("Field Misc->Answer = 0, skipping message") log.Debug("Field Misc->Answer = 0, skipping message")
return return
} }
@@ -438,13 +453,11 @@ func redisMsgParser(msg string) {
for _, message := range lines { for _, message := range lines {
if userModeIsOped(j.Chatid, ircClient.GetNick()) || userModeIsVoiced(j.Chatid, ircClient.GetNick()) { if userModeIsOped(j.Chatid, ircClient.GetNick()) || userModeIsVoiced(j.Chatid, ircClient.GetNick()) {
imChanUnrestricted <- iMsg{ChatId: j.Chatid, Text: message} imChanUnrestricted <- iMsg{ChatID: j.Chatid, Text: message}
} else { } else {
imChan <- iMsg{ChatId: j.Chatid, Text: message} imChan <- iMsg{ChatID: j.Chatid, Text: message}
} }
} }
return
} }
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -11,9 +11,10 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// StoreKV сохраняет в указанной бд ключ и значение // StoreKV сохраняет в указанной бд ключ и значение.
func StoreKV(db *pebble.DB, key string, value string) error { func StoreKV(db *pebble.DB, key string, value string) error {
var kArray = []byte(key) var kArray = []byte(key)
var vArray = []byte(value) var vArray = []byte(value)
err := db.Set(kArray, vArray, pebble.Sync) err := db.Set(kArray, vArray, pebble.Sync)
@@ -21,11 +22,12 @@ func StoreKV(db *pebble.DB, key string, value string) error {
return err return err
} }
// FetchV достаёт значение по ключу // FetchV достаёт значение по ключу.
func FetchV(db *pebble.DB, key string) (string, error) { func FetchV(db *pebble.DB, key string) (string, error) {
var kArray = []byte(key) var kArray = []byte(key)
var vArray []byte var vArray []byte
var valueString = "" var valueString = ""
vArray, closer, err := db.Get(kArray) vArray, closer, err := db.Get(kArray)
@@ -40,7 +42,7 @@ func FetchV(db *pebble.DB, key string) (string, error) {
return valueString, err return valueString, err
} }
// Достанем настройку из БД с настройками // Достанем настройку из БД с настройками.
func getSetting(chatID string, setting string) string { func getSetting(chatID string, setting string) string {
var err error var err error
@@ -58,6 +60,7 @@ func getSetting(chatID string, setting string) string {
if err != nil { if err != nil {
log.Errorf("Unable to open settings db, %s: %s\n", database, err) log.Errorf("Unable to open settings db, %s: %s\n", database, err)
return "" return ""
} }
} }
@@ -66,13 +69,14 @@ func getSetting(chatID string, setting string) string {
// Если из базы ничего не вынулось, по каким-то причинам, то просто вернём пустую строку // Если из базы ничего не вынулось, по каким-то причинам, то просто вернём пустую строку
if err != nil { if err != nil {
if errors.Is(err, pebble.ErrNotFound) { switch {
case errors.Is(err, pebble.ErrNotFound):
log.Debugf("Unable to get value for %s: no record found in db %s", setting, database) log.Debugf("Unable to get value for %s: no record found in db %s", setting, database)
} else if errors.Is(err, fs.ErrNotExist) { case errors.Is(err, fs.ErrNotExist):
log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database) log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database)
} else if errors.Is(err, oserror.ErrNotExist) { case errors.Is(err, oserror.ErrNotExist):
log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database) log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database)
} else { default:
log.Errorf("Unable to get value for %s in db dir %s: %s", setting, database, err) log.Errorf("Unable to get value for %s in db dir %s: %s", setting, database, err)
} }
@@ -82,10 +86,12 @@ func getSetting(chatID string, setting string) string {
return value return value
} }
// Сохраним настройку в БД с настройками // Сохраним настройку в БД с настройками.
func saveSetting(chatID string, setting string, value string) error { func saveSetting(chatID string, setting string, value string) error {
var chatHash = sha256.Sum256([]byte(chatID)) var chatHash = sha256.Sum256([]byte(chatID))
var database = fmt.Sprintf("settings_db/%x", chatHash) var database = fmt.Sprintf("settings_db/%x", chatHash)
var err error var err error
// Если БД не открыта, откроем её // Если БД не открыта, откроем её
@@ -100,6 +106,7 @@ func saveSetting(chatID string, setting string, value string) error {
if err != nil { if err != nil {
log.Errorf("Unable to open settings db, %s: %s\n", database, err) log.Errorf("Unable to open settings db, %s: %s\n", database, err)
return err return err
} }
} }

View File

@@ -1,6 +1,6 @@
package main package main
// Конфиг // Конфиг.
type myConfig struct { type myConfig struct {
Redis struct { Redis struct {
Server string `json:"server,omitempty"` Server string `json:"server,omitempty"`
@@ -35,7 +35,7 @@ type myConfig struct {
DataDir string `json:"data_dir,omitempty"` DataDir string `json:"data_dir,omitempty"`
} }
// Входящее сообщение из pubsub-канала redis-ки // Входящее сообщение из pubsub-канала redis-ки.
type rMsg struct { type rMsg struct {
From string `json:"from,omitempty"` From string `json:"from,omitempty"`
Chatid string `json:"chatid,omitempty"` Chatid string `json:"chatid,omitempty"`
@@ -55,7 +55,7 @@ type rMsg struct {
} `json:"Misc"` } `json:"Misc"`
} }
// Исходящее сообщение в pubsub-канал redis-ки // Исходящее сообщение в pubsub-канал redis-ки.
type sMsg struct { type sMsg struct {
From string `json:"from"` From string `json:"from"`
Chatid string `json:"chatid"` Chatid string `json:"chatid"`
@@ -75,14 +75,13 @@ type sMsg struct {
} `json:"misc"` } `json:"misc"`
} }
// Сообщение в irc // TODO: Имплементировать тип сообщения - privmsg или action.
// TODO: Имплементировать тип сообщения - privmsg или action
type iMsg struct { type iMsg struct {
ChatId string ChatID string
Text string Text string
} }
// "Ведёрко" для реализации ограничителя скорости отправки исходящих сообщений - Token Bucket // "Ведёрко" для реализации ограничителя скорости отправки исходящих сообщений - Token Bucket.
type bucket struct { type bucket struct {
Timestamps []int64 Timestamps []int64
IsFull bool IsFull bool

17
util.go
View File

@@ -11,7 +11,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет // Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет.
func readConfig() { func readConfig() {
configLoaded := false configLoaded := false
executablePath, err := os.Executable() executablePath, err := os.Executable()
@@ -40,6 +40,7 @@ func readConfig() {
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата // Конфиг-файл длинноват для конфига, попробуем следующего кандидата
if fileInfo.Size() > 65535 { if fileInfo.Size() > 65535 {
log.Warnf("Config file %s is too long for config, skipping", location) log.Warnf("Config file %s is too long for config, skipping", location)
continue continue
} }
@@ -48,6 +49,7 @@ func readConfig() {
// Не удалось прочитать, попробуем следующего кандидата // Не удалось прочитать, попробуем следующего кандидата
if err != nil { if err != nil {
log.Warnf("Skip reading config file %s: %s", location, err) log.Warnf("Skip reading config file %s: %s", location, err)
continue continue
} }
@@ -55,12 +57,14 @@ func readConfig() {
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется. // сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
var sampleConfig myConfig var sampleConfig myConfig
var tmp map[string]interface{} var tmp map[string]interface{}
err = hjson.Unmarshal(buf, &tmp) err = hjson.Unmarshal(buf, &tmp)
// Не удалось распарсить - попробуем следующего кандидата // Не удалось распарсить - попробуем следующего кандидата
if err != nil { if err != nil {
log.Warnf("Skip parsing config file %s: %s", location, err) log.Warnf("Skip parsing config file %s: %s", location, err)
continue continue
} }
@@ -69,17 +73,20 @@ func readConfig() {
// Не удалось преобразовать map-ку в json // Не удалось преобразовать map-ку в json
if err != nil { if err != nil {
log.Warnf("Skip parsing config file %s: %s", location, err) log.Warnf("Skip parsing config file %s: %s", location, err)
continue continue
} }
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
log.Warnf("Skip parsing config file %s: %s", location, err) log.Warnf("Skip parsing config file %s: %s", location, err)
continue continue
} }
// Валидируем значения из конфига // Валидируем значения из конфига
if sampleConfig.Redis.Server == "" { if sampleConfig.Redis.Server == "" {
sampleConfig.Redis.Server = "localhost" sampleConfig.Redis.Server = "localhost"
log.Infof("Redis server is not defined in config, using localhost") log.Infof("Redis server is not defined in config, using localhost")
} }
@@ -100,15 +107,17 @@ func readConfig() {
// Значения для IRC-клиента // Значения для IRC-клиента
if sampleConfig.Irc.Server == "" { if sampleConfig.Irc.Server == "" {
sampleConfig.Irc.Server = "localhost" sampleConfig.Irc.Server = "localhost"
log.Errorf("Irc server is not defined in config, using localhost") log.Errorf("Irc server is not defined in config, using localhost")
} }
if sampleConfig.Irc.Port == 0 { if sampleConfig.Irc.Port == 0 {
sampleConfig.Irc.Port = 6667 sampleConfig.Irc.Port = 6667
log.Infof("Irc port is not defined in config, using 6667") log.Infof("Irc port is not defined in config, using 6667")
} }
if sampleConfig.Irc.Ssl != true { if !sampleConfig.Irc.Ssl {
sampleConfig.Irc.SslVerify = false sampleConfig.Irc.SslVerify = false
} }
@@ -181,7 +190,9 @@ func readConfig() {
config = sampleConfig config = sampleConfig
configLoaded = true configLoaded = true
log.Infof("Using %s as config file", location) log.Infof("Using %s as config file", location)
break break
} }
@@ -191,7 +202,7 @@ func readConfig() {
} }
} }
// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения // Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
func sigHandler() { func sigHandler() {
var err error var err error