Replace prazdnikisegodnyaru with celebratoday, small underhood fixes

- Replace prazdnikisegodnyaru with celebratoday as first is banned in Russia
- Update .golangci.yml rule set
- Megatons of fixes for linter
- Update README.md
This commit is contained in:
eleksir
2026-02-10 20:05:13 +03:00
parent ff57b0af7d
commit f2fc168abe
28 changed files with 1298 additions and 268 deletions

View File

@@ -1,8 +1,10 @@
// Package webapp is main package implementing application logic.
package webapp
import (
"aleesa-webapp-go/internal/anekdotru"
"aleesa-webapp-go/internal/bunicomic"
"aleesa-webapp-go/internal/celebratoday"
"aleesa-webapp-go/internal/config"
"aleesa-webapp-go/internal/flickr"
"aleesa-webapp-go/internal/log"
@@ -10,7 +12,6 @@ import (
"aleesa-webapp-go/internal/oboobs"
"aleesa-webapp-go/internal/obutts"
"aleesa-webapp-go/internal/openweathermap"
"aleesa-webapp-go/internal/prazdnikisegodnyaru"
"aleesa-webapp-go/internal/randomfox"
"aleesa-webapp-go/internal/thecatapi"
"aleesa-webapp-go/internal/xkcdru"
@@ -49,7 +50,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
return
}
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение.
if j.Misc.Fwdcnt > cfg.ForwardsMax {
log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg)
@@ -60,15 +61,15 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
sendTo = j.Plugin
// Классифицируем входящие сообщения. Первым делом, попробуем определить команды
// Классифицируем входящие сообщения. Первым делом, попробуем определить команды.
if j.Message[0:len(j.Misc.Csign)] == j.Misc.Csign {
var cmd = j.Message[len(j.Misc.Csign):]
switch {
case cmd == "cat" || cmd == "кис":
// Пытаемся вычитать ответ thecatapi 3 раза
for i := 0; i < 3; i++ {
answer, err = thecatapi.APIClient(cfg)
// Пытаемся вычитать ответ thecatapi 3 раза.
for i := range 3 {
answer, err = thecatapi.APIClient(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query api.thecatapi.com: %v", i+1, err)
@@ -83,18 +84,20 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
if answer == "" {
j.Message = "Нету кошечек, все разбежались."
} else {
// for telegram return answer in markdown format
// for telegram return answer in markdown format.
if j.Plugin == "telegram" {
cats := []string{"龴ↀ◡ↀ龴", "=^..^=", "≧◔◡◔≦", "^ↀᴥↀ^"}
cats := []string{"龴ↀ◡ↀ龴", "=^..^=", "≧◔◡◔≦", "^ↀᴥↀ^"} //nolint: gosmopolitan
j.Message = fmt.Sprintf("[%s](%s)", cats[rand.IntN(len(cats))], answer)
} else {
j.Message = answer
}
}
case cmd == "fox" || cmd == "лис":
// Пытаемся вычитать ответ randonfox.ca 3 раза
for i := 0; i < 3; i++ {
answer, err = randomfox.APIClient(cfg)
// Пытаемся вычитать ответ randonfox.ca 3 раза.
for i := range 3 {
answer, err = randomfox.APIClient(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query randomfox.ca: %v", i+1, err)
@@ -115,9 +118,10 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
j.Message = answer
}
}
case cmd == "xkcd":
// Пытаемся вычитать ответ xkcd.ru 3 раза
for i := 0; i < 3; i++ {
// Пытаемся вычитать ответ xkcd.ru 3 раза.
for i := range 3 {
answer, err = xkcdru.APIClient(cfg)
if err != nil {
@@ -139,10 +143,11 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
j.Message = answer
}
}
case cmd == "buni":
// Пытаемся вычитать ответ www.bunicomic.com 3 раза
for i := 0; i < 3; i++ {
answer, err = bunicomic.APIClient(cfg)
// Пытаемся вычитать ответ www.bunicomic.com 3 раза.
for i := range 3 {
answer, err = bunicomic.APIClient(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query www.bunicomic.com: %v", i+1, err)
@@ -163,10 +168,11 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
j.Message = answer
}
}
case cmd == "anek" || cmd == "анек" || cmd == "анекдот":
// Пытаемся вычитать ответ www.anekdot.ru 3 раза
for i := 0; i < 3; i++ {
answer, err = anekdotru.APIClient(cfg)
// Пытаемся вычитать ответ www.anekdot.ru 3 раза.
for i := range 3 {
answer, err = anekdotru.APIClient(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query www.anekdot.ru: %v", i+1, err)
@@ -187,10 +193,11 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
j.Message = answer
}
}
case cmd == "monkeyuser":
// Пытаемся вычитать ответ www.bunicomic.com 3 раза
for i := 0; i < 3; i++ {
answer, err = monkeyuser.APIClient(cfg)
// Пытаемся вычитать ответ monkeyuser.com 3 раза.
for i := range 3 {
answer, err = monkeyuser.APIClient(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query monkeyuser.com: %v", i+1, err)
@@ -211,10 +218,11 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
j.Message = answer
}
}
case cmd == "butt" || cmd == "booty" || cmd == "ass" || cmd == "попа" || cmd == "попка":
// Пытаемся вычитать ответ api.obutts.ru 3 раза
for i := 0; i < 3; i++ {
answer, err = obutts.APIClient(cfg)
// Пытаемся вычитать ответ api.obutts.ru 3 раза.
for i := range 3 {
answer, err = obutts.APIClient(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query api.obutts.ru: %v", i+1, err)
@@ -231,15 +239,17 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
} else {
if j.Plugin == "telegram" {
arts := []string{"(__(__)", "(_!_)", "(__.__)"}
j.Message = fmt.Sprintf("[%s](%s)", arts[rand.IntN(len(arts))], answer)
} else {
j.Message = answer
}
}
case cmd == "titts" || cmd == "boobs" || cmd == "tities" || cmd == "boobies" || cmd == "сиси" || cmd == "сисечки":
// Пытаемся вычитать ответ api.oboobs.ru 3 раза
for i := 0; i < 3; i++ {
answer, err = oboobs.APIClient(cfg)
// Пытаемся вычитать ответ api.oboobs.ru 3 раза.
for i := range 3 {
answer, err = oboobs.APIClient(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query api.oboobs.ru: %v", i+1, err)
@@ -256,18 +266,20 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
} else {
if j.Plugin == "telegram" {
arts := []string{"(. )( .)", "( . Y . )", "(o)(o)", "( @ )( @ )", "(.)(.)"}
j.Message = fmt.Sprintf("[%s](%s)", arts[rand.IntN(len(arts))], answer)
} else {
j.Message = answer
}
}
case cmd == "drink" || cmd == "праздник":
// Пытаемся вычитать ответ prazdniki-segodnya.ru 3 раза
for i := 0; i < 3; i++ {
answer, err = prazdnikisegodnyaru.PsrClient(cfg)
// Пытаемся вычитать ответ celebratoday.com 3 раза.
for i := range 3 {
answer, err = celebratoday.Client(cfg) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query prazdniki-segodnya.ru: %v", i+1, err)
log.Errorf("Try %d/3 unable to query celebratoday.com: %v", i+1, err)
time.Sleep(1 * time.Second)
continue
@@ -283,8 +295,8 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
}
case cmd == "frog" || cmd == "лягушка":
for i := 0; i < 3; i++ {
answer, err = flickr.SearchByTags(cfg, []string{"frog", "toad", "amphibian"})
for i := range 3 {
answer, err = flickr.SearchByTags(cfg, []string{"frog", "toad", "amphibian"}) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query flickr api about frogs: %s", i+1, err)
@@ -302,6 +314,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
// Костыль для красивого ответа в telegram.
if j.Plugin == "telegram" {
art := []string{"frog", "toad", "лягушка"}
answer = fmt.Sprintf("[%s](%s)", art[rand.IntN(len(art))], answer)
}
@@ -309,8 +322,8 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
}
case cmd == "owl" || cmd == "сова" || cmd == "сыч":
for i := 0; i < 3; i++ {
answer, err = flickr.SearchByTags(cfg, []string{"owlet", "owl", "raptor", "bird of prey", "nocturnal bird", "barn owl", "tawny owl", "brown owl"})
for i := range 3 {
answer, err = flickr.SearchByTags(cfg, []string{"owlet", "owl", "raptor", "bird of prey", "nocturnal bird", "barn owl", "tawny owl", "brown owl"}) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query flickr api about owls: %s", i+1, err)
@@ -334,8 +347,8 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
}
case cmd == "horse" || cmd == "лошадь" || cmd == "лошадка":
for i := 0; i < 3; i++ {
answer, err = flickr.SearchByTags(cfg, []string{"horse", "equine", "stallion", "mare", "steed", "thoroughbred"})
for i := range 3 {
answer, err = flickr.SearchByTags(cfg, []string{"horse", "equine", "stallion", "mare", "steed", "thoroughbred"}) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query flickr api about horses: %s", i+1, err)
@@ -353,6 +366,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
// Костыль для красивого ответа в telegram.
if j.Plugin == "telegram" {
art := []string{"horse", "лошадь", "лошадка"}
answer = fmt.Sprintf("[%s](%s)", art[rand.IntN(len(art))], answer)
}
@@ -360,8 +374,8 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
}
case cmd == "rabbit" || cmd == "bunny" || cmd == "кролик":
for i := 0; i < 3; i++ {
answer, err = flickr.SearchByTags(cfg, []string{"bunny", "rabbit", "buck"})
for i := range 3 {
answer, err = flickr.SearchByTags(cfg, []string{"bunny", "rabbit", "buck"}) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query flickr api about rabbits: %s", i+1, err)
@@ -385,8 +399,8 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
}
case cmd == "snail" || cmd == "улитка":
for i := 0; i < 3; i++ {
answer, err = flickr.SearchByTags(cfg, []string{"snail", "slug"})
for i := range 3 {
answer, err = flickr.SearchByTags(cfg, []string{"snail", "slug"}) //nolint: contextcheck // Fuck off.
if err != nil {
log.Errorf("Try %d/3 unable to query flickr api about snails: %s", i+1, err)
@@ -404,6 +418,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
// Костыль для красивого ответа в telegram.
if j.Plugin == "telegram" {
art := []string{"'-'_@_", "@╜", "@_'-'"}
answer = fmt.Sprintf("[%s](%s)", art[rand.IntN(len(art))], answer)
}
@@ -419,6 +434,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
} else {
if answer, err := openweathermap.OwmClient(cfg, city, 0); err != nil {
log.Errorf("Unable to handle city %s in openweartermap api: %s", city, err)
j.Message = "Я не знаю, какая погода в " + city
} else {
j.Message = answer
@@ -433,6 +449,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
if regexp.MustCompile(`5$`).MatchString(cmdStr) {
if answer, err := openweathermap.OwmClient(cfg, city, 5); err != nil {
log.Errorf("Unable to handle city %s in openweartermap api: %s", city, err)
j.Message = "Я не знаю, какая погода в " + city
} else {
j.Message = answer
@@ -440,6 +457,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
} else {
if answer, err := openweathermap.OwmClient(cfg, city, 0); err != nil {
log.Errorf("Unable to handle city %s in openweartermap api: %s", city, err)
j.Message = "Я не знаю, какая погода в " + city
} else {
j.Message = answer
@@ -467,7 +485,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
return
}
// Настало время формировать json и засылать его в дальше
// Настало время формировать json и засылать его в дальше.
var message SMsg
message.From = j.From
@@ -493,7 +511,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { //nolint
return
}
// Заталкиваем наш json в редиску
// Заталкиваем наш json в редиску.
if err := cfg.RedisClient.Publish(ctx, sendTo, data).Err(); err != nil {
log.Warnf("Unable to send data to redis channel %s: %s", sendTo, err)
} else {

View File

@@ -25,12 +25,12 @@ func ReadConfig() error {
for _, location := range locations {
fileInfo, err := os.Stat(location)
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
if err != nil {
continue
}
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
if fileInfo.Size() > ConfigFileSize {
log.Warnf("Config file %s is too long for config, skipping", location)
@@ -39,7 +39,7 @@ func ReadConfig() error {
buf, err := os.ReadFile(location)
// Не удалось прочитать, попробуем следующего кандидата
// Не удалось прочитать, попробуем следующего кандидата.
if err != nil {
log.Warnf("Skip reading config file %s: %s", location, err)
@@ -51,12 +51,12 @@ func ReadConfig() error {
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
var (
sampleConfig *config.MyConfig
tmp map[string]interface{}
tmp map[string]any
)
err = hjson.Unmarshal(buf, &tmp)
// Не удалось распарсить - попробуем следующего кандидата
// Не удалось распарсить - попробуем следующего кандидата.
if err != nil {
log.Warnf("Skip parsing config file %s: %s", location, err)
@@ -65,7 +65,7 @@ func ReadConfig() error {
tmpjson, err := json.Marshal(tmp)
// Не удалось преобразовать map-ку в json
// Не удалось преобразовать map-ку в json.
if err != nil {
log.Warnf("Skip parsing config file %s: %s", location, err)
@@ -78,7 +78,7 @@ func ReadConfig() error {
continue
}
// Валидируем значения из конфига
// Валидируем значения из конфига.
if sampleConfig.Server == "" {
sampleConfig.Server = Host
}
@@ -95,7 +95,7 @@ func ReadConfig() error {
sampleConfig.Loglevel = Loglevel
}
// sampleConfig.Log = "" if not set
// sampleConfig.Log = "" if not set.
if sampleConfig.Channel == "" {
log.Errorf("Channel field in config file %s must be set", location)
@@ -129,7 +129,7 @@ func ReadConfig() error {
continue
}
// sampleConfig.OpenWeatherMap.Country is false by default
// sampleConfig.OpenWeatherMap.Country is false by default.
// Теперь надо бы попробовать загрузить список User Agent-ов, с которыми ходить в разные апишки.
uaFile := sampleConfig.DataDir + "/useragents.txt"

View File

@@ -21,14 +21,14 @@ func SigHandler(cfg *config.MyConfig) {
case syscall.SIGQUIT:
log.Info("Got SIGQUIT, quitting")
// Заходим на новую итерацию, если у нас "неинтересный" сигнал
// Заходим на новую итерацию, если у нас "неинтересный" сигнал.
default:
continue
}
Shutdown = true
// Отпишемся от всех каналов и закроем коннект к редиске
// Отпишемся от всех каналов и закроем коннект к редиске.
if err = Subscriber.Unsubscribe(Ctx); err != nil {
log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
}

View File

@@ -45,21 +45,21 @@ func ValidateRmsg(cfg *config.MyConfig, j RMsg, msg string) (RMsg, error) {
return j, emsg
}
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет
// j.Misc.Botnick тоже можно не передавать, тогда будет записана пустая строка
// j.Misc.Csign если нам его не передали, возьмём значение из конфига
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет.
// j.Misc.Botnick тоже можно не передавать, тогда будет записана пустая строка.
// j.Misc.Csign если нам его не передали, возьмём значение из конфига.
if exist := j.Misc.Csign; exist == "" {
j.Misc.Csign = cfg.Csign
}
// j.Misc.Fwdcnt если нам его не передали, то будет 0
// j.Misc.Fwdcnt если нам его не передали, то будет 0.
if exist := j.Misc.Fwdcnt; exist == 0 {
j.Misc.Fwdcnt = 1
}
// j.Misc.GoodMorning может быть быть 1 или 0, по-умолчанию 0
// j.Misc.Msgformat может быть быть 1 или 0, по-умолчанию 0
// j.Misc.Username можно не передавать, тогда будет пустая строка
// j.Misc.GoodMorning может быть быть 1 или 0, по-умолчанию 0.
// j.Misc.Msgformat может быть быть 1 или 0, по-умолчанию 0.
// j.Misc.Username можно не передавать, тогда будет пустая строка.
return j, nil
}

View File

@@ -33,7 +33,7 @@ var (
// Config структурка, содержащая данные конфига.
Config *config.MyConfig
// ctx контекст редиски.
// Ctx контекст редиски.
Ctx = context.Background()
// Subscriber объектик сабскрайбера редиски.

View File

@@ -1,43 +1,45 @@
package webapp
// RMsg структурка, описывающая входящее сообщение из pubsub-канала redis-ки.
type RMsg struct {
From string `json:"from,omitempty"`
Chatid string `json:"chatid,omitempty"`
Userid string `json:"userid,omitempty"`
Threadid string `json:"threadid,omitempty"`
Message string `json:"message,omitempty"`
Plugin string `json:"plugin,omitempty"`
Mode string `json:"mode,omitempty"`
Misc struct {
Answer int64 `json:"answer,omitempty"`
Botnick string `json:"bot_nick,omitempty"`
Csign string `json:"csign,omitempty"`
Fwdcnt int64 `json:"fwd_cnt,omitempty"`
GoodMorning int64 `json:"good_morning,omitempty"`
Msgformat int64 `json:"msg_format,omitempty"`
Username string `json:"username,omitempty"`
} `json:"Misc"`
}
type (
// RMsg структурка, описывающая входящее сообщение из pubsub-канала redis-ки.
RMsg struct {
From string `json:"from,omitempty"`
Chatid string `json:"chatid,omitempty"`
Userid string `json:"userid,omitempty"`
Threadid string `json:"threadid,omitempty"`
Message string `json:"message,omitempty"`
Plugin string `json:"plugin,omitempty"`
Mode string `json:"mode,omitempty"`
Misc struct { //nolint: govet // Мне так удобнее.
Answer int64 `json:"answer,omitempty"`
Botnick string `json:"bot_nick,omitempty"`
Csign string `json:"csign,omitempty"`
Fwdcnt int64 `json:"fwd_cnt,omitempty"`
GoodMorning int64 `json:"good_morning,omitempty"`
Msgformat int64 `json:"msg_format,omitempty"`
Username string `json:"username,omitempty"`
} `json:"Misc"`
}
// SMsg структурка, описывающая исходящее сообщение в pubsub-канал redis-ки.
type SMsg struct {
From string `json:"from"`
Chatid string `json:"chatid"`
Userid string `json:"userid"`
Threadid string `json:"threadid"`
Message string `json:"message"`
Plugin string `json:"plugin"`
Mode string `json:"mode"`
Misc struct {
Answer int64 `json:"answer"`
Botnick string `json:"bot_nick"`
Csign string `json:"csign"`
Fwdcnt int64 `json:"fwd_cnt"`
GoodMorning int64 `json:"good_morning"`
Msgformat int64 `json:"msg_format"`
Username string `json:"username"`
} `json:"misc"`
}
// SMsg структурка, описывающая исходящее сообщение в pubsub-канал redis-ки.
SMsg struct {
From string `json:"from"`
Chatid string `json:"chatid"`
Userid string `json:"userid"`
Threadid string `json:"threadid"`
Message string `json:"message"`
Plugin string `json:"plugin"`
Mode string `json:"mode"`
Misc struct { //nolint: govet // Мне так удобнее.
Answer int64 `json:"answer"`
Botnick string `json:"bot_nick"`
Csign string `json:"csign"`
Fwdcnt int64 `json:"fwd_cnt"`
GoodMorning int64 `json:"good_morning"`
Msgformat int64 `json:"msg_format"`
Username string `json:"username"`
} `json:"misc"`
}
)
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */