0

Add golangci-lint config and fix linter warns

This commit is contained in:
eleksir
2025-03-30 16:19:20 +03:00
parent 8c24a1d8c6
commit 7bcc407857
9 changed files with 279 additions and 143 deletions

84
.golangci.yml Normal file
View File

@@ -0,0 +1,84 @@
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:
- gci
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
- vendor$

View File

@@ -11,15 +11,9 @@ import (
irc "github.com/thoj/go-ircevent"
)
// TODO: Implement 005 REPL_BOUNCE
// ircClientInit горутинка для работы с протоколом irc
// ircClientInit горутинка для работы с протоколом irc.
func ircClientRun() {
for {
if shutdown {
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо
break
}
for !shutdown {
// Иницализируем irc-клиента
serverString := fmt.Sprintf("%s:%d", config.Irc.Server, config.Irc.Port)
log.Debugf("Preparing to connect to %s", serverString)
@@ -30,10 +24,12 @@ func ircClientRun() {
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")
@@ -92,18 +88,19 @@ func ircClientRun() {
if channel == mychannel {
// Для каждого канала формат строго 1 из 4-х: #channel | @#channel | +#channel | @+#channel
switch {
case modes == "@+":
switch modes {
case "@+":
userModeUpdateUser(channel, dstNick, "+o")
userModeUpdateUser(channel, dstNick, "+v")
case modes == "@":
case "@":
userModeUpdateUser(channel, dstNick, "+o")
case modes == "+":
case "+":
userModeUpdateUser(channel, dstNick, "+v")
default:
userModeUpdateUser(channel, dstNick, "-o")
userModeUpdateUser(channel, dstNick, "-v")
}
break
}
}
@@ -123,6 +120,7 @@ func ircClientRun() {
if channel == mychannel {
for _, name := range strings.Split(namesString, " ") {
mode := name[:1]
var nick string
if mode == "@" || mode == "+" {
@@ -131,16 +129,17 @@ func ircClientRun() {
nick = name
}
switch {
case mode == "@":
switch mode {
case "@":
userModeUpdateUser(channel, nick, "+o")
case mode == "+":
case "+":
userModeUpdateUser(channel, nick, "+v")
default:
userModeUpdateUser(channel, nick, "-o")
userModeUpdateUser(channel, nick, "-v")
}
}
break
}
}
@@ -173,8 +172,9 @@ func ircClientRun() {
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}
imChan <- iMsg{ChatID: "NickServ", Text: message}
}
// TODO: wait until +R flag been set? could be implemented with waiting for channel message.
@@ -241,8 +241,9 @@ func ircClientRun() {
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}
imChan <- iMsg{ChatID: "NickServ", Text: message}
}
// TODO: wait until +R flag been set? could be implemented with waiting for channel message.
@@ -264,8 +265,11 @@ func ircClientRun() {
ircClient.AddCallback("433", func(e *irc.Event) {
log.Errorf("433 ERR_NICKNAMEINUSE, %s", e.Raw)
nickIsUsed = true
time.Sleep(60 * time.Second)
<-time.NewTimer(60 * time.Second).C // 1 minute sleep.
ircClient.Nick(config.Irc.Nick)
})
@@ -321,6 +325,7 @@ func ircClientRun() {
for _, ircChan := range config.Irc.Channels {
if ircChan == channel {
ircClient.Join(channel)
break
}
}
@@ -341,6 +346,7 @@ func ircClientRun() {
for _, ircChan := range config.Irc.Channels {
if ircChan == channel {
ircClient.Join(channel)
break
}
}
@@ -359,6 +365,7 @@ func ircClientRun() {
for _, ircChan := range config.Irc.Channels {
if ircChan == channel {
ircClient.Join(channel)
break
}
}
@@ -423,8 +430,9 @@ func ircClientRun() {
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}
imChan <- iMsg{ChatID: "NickServ", Text: message}
}
} else {
log.Infof("%s renames themself to %s", srcNick, dstNick)
@@ -485,6 +493,7 @@ func ircClientRun() {
channel := e.Arguments[0]
fullSrcNick := e.Source
srcNick := e.Nick
var dstNick string
if len(e.Arguments) >= 3 { // кого-то по-MODE-или на канале
@@ -497,6 +506,7 @@ func ircClientRun() {
}
} else { // Установка mode-а при заходе на сервер
dstNick = e.Arguments[0]
log.Infof("Server set my mode to %s", mode)
}
@@ -545,8 +555,9 @@ func ircClientRun() {
log.Debugf("Make an actual connection to irc")
if err := ircClient.Connect(serverString); err != nil {
log.Fatalf("Unable to prepare irc connection: %s", err)
time.Sleep(3 * time.Second)
log.Errorf("Unable to prepare irc connection: %s", err)
<-time.NewTimer(3 * time.Second).C // Sleep for 3 seconds.
continue
}
@@ -560,21 +571,22 @@ func ircSend() {
for {
m := <-imChan
switch {
case config.Irc.RateLimit.Type == "simple_delay":
log.Debugf("Sending to chat %s message: %s", m.ChatId, m.Text)
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)
ircClient.Action(m.ChatID, m.Text)
} else {
ircClient.Privmsg(m.ChatId, m.Text)
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.Sleep(sleepDelay)
case config.Irc.RateLimit.Type == "token_bucket":
case "token_bucket":
var currentTimeMs = time.Now().UnixMilli()
var expirationTimeMs = config.Irc.RateLimit.TokenBucket.ExpirationTime * 1000
if len(msgBucket.Timestamps) > 0 {
@@ -588,6 +600,7 @@ func ircSend() {
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 {
@@ -598,15 +611,17 @@ func ircSend() {
} 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 = sleepPeriod / int64(config.Irc.RateLimit.TokenBucket.Limit)
sleepPeriod /= int64(config.Irc.RateLimit.TokenBucket.Limit)
}
log.Debugf("Sleeping for %d milliseconds", sleepPeriod)
@@ -616,20 +631,20 @@ func ircSend() {
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 " {
ircClient.Action(m.ChatId, m.Text[4:])
ircClient.Action(m.ChatID, m.Text[4:])
} else {
ircClient.Privmsg(m.ChatId, m.Text)
ircClient.Privmsg(m.ChatID, m.Text)
}
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 " {
ircClient.Action(m.ChatId, m.Text[4:])
ircClient.Action(m.ChatID, m.Text[4:])
} else {
ircClient.Privmsg(m.ChatId, m.Text)
ircClient.Privmsg(m.ChatID, m.Text)
}
}
}
@@ -638,12 +653,12 @@ func ircSend() {
func ircSendUnrestricted() {
for {
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 " {
ircClient.Action(m.ChatId, m.Text[4:])
ircClient.Action(m.ChatID, m.Text[4:])
} else {
ircClient.Privmsg(m.ChatId, m.Text)
ircClient.Privmsg(m.ChatID, m.Text)
}
}
}

View File

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

View File

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

View File

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

View File

@@ -9,8 +9,8 @@ import (
log "github.com/sirupsen/logrus"
)
// ircMsgParser парсит сообщения, прилетевшие из IRC-ки
func ircMsgParser(channel string, nick string, user string, source string, msg string) {
// ircMsgParser парсит сообщения, прилетевшие из IRC-ки.
func ircMsgParser(channel string, nick string, user string, source string, msg string) { //nolint: revive
// nick - это выбранный пользователем nick (если он занят, то его "нарисует" сервер)
// user - это короткое имя пользователя, под которым его видит сервер
// source - это длинное имя пользователя, оно содержит в себе помимо user, ещё и ip с которого пришёл пользователь
@@ -48,50 +48,50 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
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)}
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)}
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)}
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
@@ -100,16 +100,16 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if userModeIsOped(channel, nick) {
value := getSetting(channel, "oboobs")
switch {
case value == "":
switch value {
case "":
_ = saveSetting(channel, "oboobs", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"}
case value == "0":
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"}
case value == "1":
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs включен"}
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 выключен"}
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")
if err != nil {
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs всё ещё выключен"}
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs всё ещё выключен"}
} 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":
if userModeIsOped(channel, nick) {
_ = saveSetting(channel, "oboobs", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"}
imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"}
}
return
@@ -140,16 +140,16 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if userModeIsOped(channel, nick) {
value := getSetting(channel, "obutts")
switch {
case value == "":
switch value {
case "":
_ = saveSetting(channel, "obutts", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"}
case value == "0":
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"}
case value == "1":
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts включен"}
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 выключен"}
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")
if err != nil {
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts всё ещё выключен"}
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts всё ещё выключен"}
} 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":
if userModeIsOped(channel, nick) {
_ = saveSetting(channel, "obutts", "0")
imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"}
imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"}
}
return
@@ -192,6 +192,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd == command {
done = true
outgoingMessage = msg
break
}
}
@@ -206,6 +207,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd == command {
done = true
outgoingMessage = msg
break
}
}
@@ -226,7 +228,8 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
message.Misc.Username = strings.TrimSpace(pile[1])
} else {
msg = fmt.Sprintf("Я тут не вижу участника с ником %s", userNick)
imChan <- iMsg{ChatId: channel, Text: msg}
imChan <- iMsg{ChatID: channel, Text: msg}
return
}
} // в противном случае юзер просто поставил пробел на конце команды, интерпретируем это как
@@ -234,6 +237,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
outgoingMessage = msg
done = true
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 {
done = true
outgoingMessage = msg
break
}
}
@@ -267,6 +272,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd[:cmdLen] == command {
done = true
outgoingMessage = msg
break
}
}
@@ -283,6 +289,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if cmd[:cmdLen] == command {
// done = true
outgoingMessage = msg
break
}
}
@@ -297,6 +304,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if err != nil {
log.Warnf("Unable to to serialize message for redis: %s", err)
return
}
@@ -351,6 +359,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
if err != nil {
log.Warnf("Unable to to serialize message for redis: %s", err)
return
}
@@ -363,7 +372,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
}
}
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными.
func redisMsgParser(msg string) {
if shutdown {
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо
@@ -376,43 +385,51 @@ func redisMsgParser(msg string) {
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
}
@@ -436,9 +453,9 @@ func redisMsgParser(msg string) {
for _, message := range lines {
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 {
imChan <- iMsg{ChatId: j.Chatid, Text: message}
imChan <- iMsg{ChatID: j.Chatid, Text: message}
}
}
}

View File

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

View File

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

15
util.go
View File

@@ -11,7 +11,7 @@ import (
log "github.com/sirupsen/logrus"
)
// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет
// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет.
func readConfig() {
configLoaded := false
executablePath, err := os.Executable()
@@ -40,6 +40,7 @@ func readConfig() {
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
if fileInfo.Size() > 65535 {
log.Warnf("Config file %s is too long for config, skipping", location)
continue
}
@@ -48,6 +49,7 @@ func readConfig() {
// Не удалось прочитать, попробуем следующего кандидата
if err != nil {
log.Warnf("Skip reading config file %s: %s", location, err)
continue
}
@@ -55,12 +57,14 @@ func readConfig() {
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в 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
}
@@ -69,17 +73,20 @@ func readConfig() {
// Не удалось преобразовать 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")
}
@@ -100,11 +107,13 @@ func readConfig() {
// Значения для 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")
}
@@ -181,7 +190,9 @@ func readConfig() {
config = sampleConfig
configLoaded = true
log.Infof("Using %s as config file", location)
break
}
@@ -191,7 +202,7 @@ func readConfig() {
}
}
// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения
// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
func sigHandler() {
var err error