0

[WIP] Handle leaving chat for users, parse message more thoroughly in public chats.

Add parser for users that left channel
Add semi-stub (partial) censor functionality
Add bot name cut-out in public chat
Add sending functionality for public chats
This commit is contained in:
eleksir
2024-06-30 18:48:13 +03:00
parent e75d9cabb5
commit 301eb52675
6 changed files with 307 additions and 40 deletions

View File

@@ -34,7 +34,7 @@ func parseConfig(path string) (myConfig, error) {
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется. // сериализуем в json, а потом json превращааем в структурку. Не очень эффективно, но он и не часто требуется.
var ( var (
sampleConfig myConfig sampleConfig myConfig
tmp map[string]interface{} tmp map[string]interface{}

View File

@@ -19,7 +19,7 @@ var forwardMax int64 = 5
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, если мы получили сигнал на выключение

View File

@@ -86,7 +86,7 @@ func main() {
log.SetOutput(logfile) log.SetOutput(logfile)
} }
// Инициализируем redis-клиента // Инициализируем redis-клиента.
redisClient = redis.NewClient(&redis.Options{ redisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port), Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port),
}) })
@@ -95,8 +95,6 @@ func main() {
subscriber = redisClient.Subscribe(ctx, config.Redis.MyChannel) subscriber = redisClient.Subscribe(ctx, config.Redis.MyChannel)
redisMsgChan := subscriber.Channel() redisMsgChan := subscriber.Channel()
// telego init here
// Самое время поставить траппер сигналов. // Самое время поставить траппер сигналов.
signal.Notify(sigChan, signal.Notify(sigChan,
syscall.SIGINT, syscall.SIGINT,
@@ -106,7 +104,7 @@ func main() {
go sigHandler() go sigHandler()
go telega(config) go telega(config)
// Обработчик событий от редиски // Обработчик событий от редиски.
for msg := range redisMsgChan { for msg := range redisMsgChan {
if shutdown { if shutdown {
continue continue

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"github.com/NicoNex/echotron/v3" "github.com/NicoNex/echotron/v3"
@@ -12,7 +13,7 @@ import (
// telego msg parser // telego msg parser
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными. // redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём json-чики должны быть относительно валидными.
func redisMsgParser(msg string) { func redisMsgParser(msg string) {
if shutdown { if shutdown {
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо. // Если мы завершаем работу программы, то нам ничего обрабатывать не надо.
@@ -120,20 +121,36 @@ func telegramMsgParser(msg *echotron.Update) {
} }
// Сообщение о том, что этот чятик изменился, например превратился в супергруппу. // Сообщение о том, что этот чятик изменился, например превратился в супергруппу.
if msg.Message.MigrateFromChatID < 0 && msg.Message.MigrateToChatID < 0 { if (msg.Message.MigrateFromChatID < 0) && (msg.Message.MigrateToChatID < 0) {
// TODO: поддержать миграцию настроек чата га новый chatId. // TODO: поддержать миграцию настроек чата на новый chatId.
// TODO: подумать, что можно сделать с настройками бэкэндов. Кажись, ничего, но надо глянуть. // TODO: подумать, что можно сделать с настройками бэкэндов. Кажись, ничего, но надо глянуть.
} }
// Люди пришли в чят. // Люди пришли в чят.
if msg.Message.NewChatMembers != nil { if msg.Message.NewChatMembers != nil {
// TODO: проверить, надо ли приветсвовать, выбрать одну из приветственных фраз, потянуть время, типа, мы пишем // TODO: проверить, надо ли приветствовать, выбрать одну из приветственных фраз, потянуть время, типа, мы пишем
// текст и поприветсвовать вновьприбывшего // текст и поприветствовать вновь прибывшего.
} }
// Человек ушёл из чата. // Человек ушёл из чата.
if msg.Message.LeftChatMember != nil { if msg.Message.LeftChatMember != nil {
// TODO: проверить, надо ли прощаться с ушедшим участником чата и попрощаться, если надо if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GoodbyeMsg") == "1" {
user := ConstructUserFirstLastName(msg.Message.From)
goodbye := fmt.Sprintf("Прощаемся с %s", user)
opts := echotron.MessageOptions{DisableNotification: true, ParseMode: "MarkdownV2"}
if msg.Message.ThreadID != 0 {
opts.MessageThreadID = int64(msg.Message.ThreadID)
}
if res, err := tg.SendMessage(goodbye, msg.Message.Chat.ID, &opts); err != nil {
log.Errorf("Unable to send message to telegram api: %s", err)
} else if !res.Ok {
log.Errorf("Unable to send message to telegram api: %s", res.Description)
}
}
} }
var ( var (
@@ -145,11 +162,14 @@ func telegramMsgParser(msg *echotron.Update) {
if err != nil { if err != nil {
log.Errorf("Unable to get info about myself, %s", err) log.Errorf("Unable to get info about myself, %s", err)
errorOccured = true errorOccured = true
} }
// Типа, при некоторых обстоятельствах, мы можем получить более внятное сообщение. Но это надо проверять.
if !me.Ok { if !me.Ok {
log.Errorf("Unable to get info about myself: %d %s", me.ErrorCode, me.Description) log.Errorf("Unable to get info about myself: %d %s", me.ErrorCode, me.Description)
errorOccured = true errorOccured = true
} }
@@ -164,12 +184,11 @@ func telegramMsgParser(msg *echotron.Update) {
return return
} }
// handle private messages // Считаем, что приватное сообщение всегда нуждается в ответе, иначе, зачем писать боту?
rmsg.Misc.Answer = 1 rmsg.Misc.Answer = 1
// если фраза является командой - засылаем её в парсер команд
// Засылаем фразу в misc-канал (в роутер) // Засылаем фразу в misc-канал (в роутер).
rmsg.Chatid = fmt.Sprintf("%d", msg.ChatID()) rmsg.Chatid = fmt.Sprintf("%d", msg.Message.Chat.ID)
rmsg.Userid = fmt.Sprintf("%d", msg.Message.From.ID) rmsg.Userid = fmt.Sprintf("%d", msg.Message.From.ID)
rmsg.Message = msg.Message.Text rmsg.Message = msg.Message.Text
rmsg.Mode = "private" rmsg.Mode = "private"
@@ -177,9 +196,10 @@ func telegramMsgParser(msg *echotron.Update) {
rmsg.From = "telegram" rmsg.From = "telegram"
rmsg.Misc.Csign = config.Csign rmsg.Misc.Csign = config.Csign
rmsg.Misc.Fwdcnt = 1 rmsg.Misc.Fwdcnt = 1
rmsg.Misc.Botnick = me.Result.Username rmsg.Misc.Botnick = ConstructPartialUserUsername(me.Result)
rmsg.Misc.Username = "" rmsg.Misc.Username = ConstructPartialUserUsername(msg.Message.From)
rmsg.Misc.GoodMorning = 0 rmsg.Misc.GoodMorning = 0
// TODO: детектить нужно ли форматировать ответ. Для этого фактически надо парсить простые команды, как минимум. // TODO: детектить нужно ли форматировать ответ. Для этого фактически надо парсить простые команды, как минимум.
rmsg.Misc.Msgformat = 0 rmsg.Misc.Msgformat = 0
@@ -191,7 +211,7 @@ func telegramMsgParser(msg *echotron.Update) {
return return
} }
// Заталкиваем наш json в редиску // Заталкиваем наш json в редиску.
if err := redisClient.Publish(ctx, config.Redis.Channel, data).Err(); err != nil { if err := redisClient.Publish(ctx, config.Redis.Channel, data).Err(); err != nil {
log.Warnf("Unable to send data to redis channel %s: %s", config.Redis.Channel, err) log.Warnf("Unable to send data to redis channel %s: %s", config.Redis.Channel, err)
} else { } else {
@@ -199,14 +219,136 @@ func telegramMsgParser(msg *echotron.Update) {
} }
case "group", "supergroup": case "group", "supergroup":
// handle public (super)group messages // Обрабатываем сообщения для групп и супергрупп. По сути это примерно одинаковые вещи/сущности.
// Здесь работает цензор, если он включён.
// Здесь же, если человек ответил на авто-приветствие, это надо обнаруживать и пропускать. // Цензор удаляет сообщения из чятика, если они "неправильные" - от имени других каналов, аудиосообщения,
// Здесь же, если фраза менее 3-х букв, просто игнорируем её // содержат картинки, видео аудио итп, это настраивается через команду !admin censor.
// Здесь же, если фраза является командой - засылаем её в парсер команд if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "censor") == "1" {
// Если цензор отработал, больше делать нечего, сообщения уже нету.
if Censor(msg) {
return
}
}
// Если фраза менее 3-х букв, просто игнорируем её.
if len(msg.Message.Text) <= 3 {
return
}
// TODO: если человек ответил на авто-приветствие, это надо обнаруживать и пропускать.
// Если фраза является командой - засылаем её в парсер команд.
if msg.Message.Text[0:len(config.Csign)] == config.Csign {
if err := cmdParser(msg.Message.Text); err != nil {
log.Errorf(
"Unable to parse message from telegram api as a command. Message was: %s, error: %s",
msg.Message.Text,
err,
)
}
}
// Здесь же, если фраза обращена к боту (ник или имя), выставляем флажок, что надо ответить // Здесь же, если фраза обращена к боту (ник или имя), выставляем флажок, что надо ответить
// Здесь же, пытаемся убрать из фразы ник или имя бота quotedBotUsername := regexp.QuoteMeta(me.Result.Username)
// Засылаем фразу в misc-канал (в роутер) quotedBotFirstName := regexp.QuoteMeta(me.Result.FirstName)
quotedBotLastName := regexp.QuoteMeta(me.Result.LastName)
if messageContainsBotUsername, err := regexp.MatchString(quotedBotUsername, msg.Message.Text); err != nil {
log.Errorf("An error occured while matching message with bot username: %s", err)
return
} else if messageContainsBotUsername {
rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotUsername)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
}
// TODO: обращение может содержать знаки препинания, пробелы перед, пробелы после. Надо бы это поддержать.
switch {
// Если у на есть и имя и фамилия бота, то попробуем их слепить вместе, через "1 и более" пробел и вырезать
// полученное из входящей фразы.
case quotedBotFirstName != "" && quotedBotLastName != "":
messageContainsBotName, err := regexp.MatchString(
quotedBotFirstName+"[[:space:]]+"+quotedBotLastName,
msg.Message.Text,
)
if err != nil {
log.Errorf("An error occured while matching message with bot firstname and lastname: %s", err)
return
}
if messageContainsBotName {
rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotFirstName + "[[:space:]]+" + quotedBotLastName)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
}
// Если у нас есть только имя, то попробуем его вырезать из входящей фразы.
case quotedBotFirstName != "":
messageContainsBotName, err := regexp.MatchString(quotedBotFirstName, msg.Message.Text)
if err != nil {
log.Errorf("An error occured while matching message with bot firstname: %s", err)
return
}
if messageContainsBotName {
rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotFirstName)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
}
// Если у нас есть только фамилия, то попробуем его вырезать из входящей фразы.
case quotedBotLastName != "":
messageContainsBotName, err := regexp.MatchString(quotedBotLastName, msg.Message.Text)
if err != nil {
log.Errorf("An error occured while matching message with bot lastname: %s", err)
return
}
if messageContainsBotName {
rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotLastName)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
}
}
// Засылаем фразу в misc-канал (в роутер).
rmsg.Chatid = fmt.Sprintf("%d", msg.Message.Chat.ID)
rmsg.Userid = fmt.Sprintf("%d", msg.Message.From.ID)
rmsg.Message = msg.Message.Text
rmsg.Mode = "public"
rmsg.Plugin = "telegram"
rmsg.From = "telegram"
rmsg.Misc.Csign = config.Csign
rmsg.Misc.Fwdcnt = 1
rmsg.Misc.Botnick = ConstructPartialUserUsername(me.Result)
rmsg.Misc.Username = ConstructPartialUserUsername(msg.Message.From)
rmsg.Misc.GoodMorning = 0
// TODO: детектить нужно ли форматировать ответ. Для этого фактически надо парсить простые команды, как минимум.
rmsg.Misc.Msgformat = 0
data, err := json.Marshal(rmsg)
if err != nil {
log.Warnf("Unable to to serialize message for redis: %s", err)
return
}
// Заталкиваем наш json в редиску.
if err := redisClient.Publish(ctx, config.Redis.Channel, data).Err(); err != nil {
log.Warnf("Unable to send data to redis channel %s: %s", config.Redis.Channel, err)
} else {
log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data))
}
case "channel": case "channel":
// Тут мы ничего сделать не можем. // Тут мы ничего сделать не можем.
} //nolint:wsl } //nolint:wsl
@@ -218,4 +360,131 @@ func cmdParser(cmd string) error {
return err return err
} }
// Censor парсит сообщения в поисках непотребных данных и если он их находит, то сообщение удаляется.
// Непотребными могут быть аудиосообщения, аудиофайлы, видеосообщения, сообщения от имени других каналов итп.
// Это могут настроить админы чятика через команду !admin censor.
// TODO: Реализовать censor-а.
func Censor(msg *echotron.Update) bool {
result := false
if GetSetting(fmt.Sprintf("%d", msg.ChatID()), "VoiceMsg") == "1" {
result = true
if res, err := tg.DeleteMessage(msg.ChatID(), msg.Message.ID); err != nil {
chat := ConstructFullChatName(msg.Message.SenderChat)
user := ConstructFullUserName(msg.Message.From)
log.Errorf("Unable to delete message %d in chat %s from %s via telegram api: %s", msg.Message.ID, chat, user, err)
} else if !res.Ok {
chat := ConstructFullChatName(msg.Message.SenderChat)
user := ConstructFullUserName(msg.Message.From)
log.Errorf("Unable to delete message %d in chat %s from %s via telegram api: %s", msg.ID, chat, user, res.Description)
}
}
return result
}
// ConstructFullUserName выковыривает из сообщения полный username, в формате @username FirstName LastName (id)
func ConstructFullUserName(u *echotron.User) string {
user := fmt.Sprintf("(%d)", u.ID)
if u.LastName != "" {
user = fmt.Sprintf("%s %s", u.LastName, user)
}
if u.FirstName != "" {
user = fmt.Sprintf("%s %s", u.FirstName, user)
}
if u.Username != "" {
user = fmt.Sprintf("@%s %s", u.Username, user)
}
return user
}
// ConstructFullChatName выковыривает из сообщения полный username чата, в формате @username FirstName LastName (id)
func ConstructFullChatName(c *echotron.Chat) string {
chat := fmt.Sprintf("(%d)", c.ID)
if c.LastName != "" {
chat = fmt.Sprintf("%s %s", c.LastName, chat)
}
if c.FirstName != "" {
chat = fmt.Sprintf("%s %s", c.FirstName, chat)
}
if c.Username != "" {
chat = fmt.Sprintf("@%s %s", c.Username, chat)
}
return chat
}
// ConstructPartialUserUsername пытается найти и вытащить username, если такового нет, вытаскивает First/Last Name, если
// такового нет, то возвращает ID.
func ConstructPartialUserUsername(u *echotron.User) string {
switch {
case u.Username != "":
return fmt.Sprintf("@%s", u.Username)
case u.FirstName != "" && u.LastName != "":
return fmt.Sprintf("%s %s", u.FirstName, u.LastName)
case u.FirstName != "":
return u.FirstName
case u.LastName != "":
return u.LastName
default:
return fmt.Sprintf("%d", u.ID)
}
}
// ConstructPartialUserUsername пытается найти и вытащить username, если такового нет, вытаскивает First/Last Name, если
// такового нет, то возвращает ID.
func ConstructPartialChatUsername(c *echotron.Chat) string {
switch {
case c.Username != "":
return fmt.Sprintf("@%s", c.Username)
case c.FirstName != "" && c.LastName != "":
return fmt.Sprintf("%s %s", c.FirstName, c.LastName)
case c.FirstName != "":
return c.FirstName
case c.LastName != "":
return c.LastName
default:
return fmt.Sprintf("%d", c.ID)
}
}
// ConstructUserFirstLastName Пытается найти и вытащить first name и last name пользователя, если не получается, то
// вначале пытается фоллбэчиться на first name, потом на last name, потом на username.
func ConstructUserFirstLastName(u *echotron.User) string {
var user string
switch {
case u.FirstName != "" && u.LastName != "":
user = fmt.Sprintf("%s %s", u.FirstName, u.LastName)
case u.FirstName != "":
user = u.FirstName
case u.LastName != "":
user = u.LastName
case u.Username != "":
user = fmt.Sprintf("@%s", u.Username)
default:
user = fmt.Sprintf("%d", u.ID)
}
return user
}
/* 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

@@ -21,7 +21,7 @@ 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)
@@ -41,7 +41,7 @@ func FetchV(db *pebble.DB, key string) (string, error) {
} }
// getSetting достаёт настройку из БД с настройками. // getSetting достаёт настройку из БД с настройками.
func getSetting(chatID string, setting string) string { func GetSetting(chatID string, setting string) string {
var err error var err error
chatHash := sha256.Sum256([]byte(chatID)) chatHash := sha256.Sum256([]byte(chatID))
@@ -52,7 +52,7 @@ func getSetting(chatID string, setting string) string {
var options pebble.Options var options pebble.Options
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными // По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук, // настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений // что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений.
options.L0CompactionFileThreshold = 8 options.L0CompactionFileThreshold = 8
settingsDB[database], err = pebble.Open(config.DataDir+"/"+database, &options) settingsDB[database], err = pebble.Open(config.DataDir+"/"+database, &options)
@@ -64,7 +64,7 @@ func getSetting(chatID string, setting string) string {
value, err := FetchV(settingsDB[database], setting) value, err := FetchV(settingsDB[database], setting)
// Если из базы ничего не вынулось, по каким-то причинам, то просто вернём пустую строку // Если из базы ничего не вынулось, по каким-то причинам, то просто вернём пустую строку.
if err != nil { if err != nil {
if errors.Is(err, pebble.ErrNotFound) { if 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)
@@ -82,18 +82,18 @@ func getSetting(chatID string, setting string) string {
return value return value
} }
// saveSetting сохраняет настройку в БД с настройками // saveSetting сохраняет настройку в БД с настройками.
func saveSetting(chatID string, setting string, value string) error { func GaveSetting(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
// Если БД не открыта, откроем её // Если БД не открыта, откроем её.
if _, ok := settingsDB[database]; !ok { if _, ok := settingsDB[database]; !ok {
var options pebble.Options var options pebble.Options
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными // По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук, // настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений // что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений.
options.L0CompactionFileThreshold = 8 options.L0CompactionFileThreshold = 8
settingsDB[database], err = pebble.Open(config.DataDir+"/"+database, &options) settingsDB[database], err = pebble.Open(config.DataDir+"/"+database, &options)

12
util.go
View File

@@ -32,12 +32,12 @@ func readConfig() {
for _, location := range locations { for _, location := range locations {
fileInfo, err := os.Stat(location) fileInfo, err := os.Stat(location)
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
if err != nil { if err != nil {
continue continue
} }
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата // Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
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)
@@ -46,7 +46,7 @@ func readConfig() {
buf, err := os.ReadFile(location) buf, err := os.ReadFile(location)
// Не удалось прочитать, попробуем следующего кандидата // Не удалось прочитать, попробуем следующего кандидата.
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)
@@ -63,7 +63,7 @@ func readConfig() {
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)
@@ -72,7 +72,7 @@ func readConfig() {
tmpjson, err := json.Marshal(tmp) tmpjson, err := json.Marshal(tmp)
// Не удалось преобразовать 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)
@@ -154,7 +154,7 @@ func sigHandler() {
case syscall.SIGQUIT: case syscall.SIGQUIT:
log.Infoln("Got SIGQUIT, quitting") log.Infoln("Got SIGQUIT, quitting")
// Заходим на новую итерацию, если у нас "неинтересный" сигнал // Заходим на новую итерацию, если у нас "неинтересный" сигнал.
default: default:
continue continue
} }