Join webbapp core modules into single package
This commit is contained in:
503
internal/webapp/MsgParser.go
Normal file
503
internal/webapp/MsgParser.go
Normal file
@@ -0,0 +1,503 @@
|
||||
package webapp
|
||||
|
||||
import (
|
||||
"aleesa-webapp-go/internal/anekdotru"
|
||||
"aleesa-webapp-go/internal/bunicomic"
|
||||
"aleesa-webapp-go/internal/config"
|
||||
"aleesa-webapp-go/internal/flickr"
|
||||
"aleesa-webapp-go/internal/log"
|
||||
"aleesa-webapp-go/internal/monkeyuser"
|
||||
"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"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки.
|
||||
func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) {
|
||||
var (
|
||||
sendTo string
|
||||
answer string
|
||||
j RMsg
|
||||
err error
|
||||
)
|
||||
|
||||
regexpWeather := regexp.MustCompile("(w|weather|п|погода|погодка|погадка)5?[[:space:]]+.+")
|
||||
|
||||
log.Debugf("Incomming raw json: %s", msg)
|
||||
|
||||
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
||||
log.Warnf("Unable to to parse message from redis channel: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
j, err = ValidateRmsg(cfg, j, msg)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение
|
||||
if j.Misc.Fwdcnt > cfg.ForwardsMax {
|
||||
log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
j.Misc.Fwdcnt++
|
||||
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query api.thecatapi.com: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету кошечек, все разбежались."
|
||||
} else {
|
||||
// for telegram return answer in markdown format
|
||||
if j.Plugin == "telegram" {
|
||||
cats := []string{"龴ↀ◡ↀ龴", "=^..^=", "≧◔◡◔≦", "^ↀᴥↀ^"}
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query randomfox.ca: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету лисичек, все разбежались."
|
||||
} else {
|
||||
if j.Plugin == "telegram" {
|
||||
j.Message = fmt.Sprintf("[-^^,--,~](%s)", answer)
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
}
|
||||
case cmd == "xkcd":
|
||||
// Пытаемся вычитать ответ xkcd.ru 3 раза
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = xkcdru.APIClient(cfg)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query xkcd.ru: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Комикс-стрип нарисовать не так-то просто :("
|
||||
} else {
|
||||
if j.Plugin == "telegram" {
|
||||
j.Message = fmt.Sprintf("[xkcdru](%s)", answer)
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
}
|
||||
case cmd == "buni":
|
||||
// Пытаемся вычитать ответ www.bunicomic.com 3 раза
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = bunicomic.APIClient(cfg)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query www.bunicomic.com: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету Buni :("
|
||||
} else {
|
||||
if j.Plugin == "telegram" {
|
||||
j.Message = fmt.Sprintf("[buni](%s)", answer)
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
}
|
||||
case cmd == "anek" || cmd == "анек" || cmd == "анекдот":
|
||||
// Пытаемся вычитать ответ www.anekdot.ru 3 раза
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = anekdotru.APIClient(cfg)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query www.anekdot.ru: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Все рассказчики анекдотов отдыхают"
|
||||
} else {
|
||||
if j.Plugin == "telegram" {
|
||||
j.Message = fmt.Sprintf("```\n%s\n```", answer)
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
}
|
||||
case cmd == "monkeyuser":
|
||||
// Пытаемся вычитать ответ www.bunicomic.com 3 раза
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = monkeyuser.APIClient(cfg)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query monkeyuser.com: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету Monkey User-ов, они все спрятались."
|
||||
} else {
|
||||
if j.Plugin == "telegram" {
|
||||
j.Message = fmt.Sprintf("[monkeyuser](%s)", answer)
|
||||
} else {
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query api.obutts.ru: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету попок, все разбежались."
|
||||
} 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)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query api.oboobs.ru: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету cисичек, все разбежались."
|
||||
} 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)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query prazdniki-segodnya.ru: %v", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Не знаю праздников - вджобываю весь день на шахтах, как проклятая."
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
|
||||
case cmd == "frog" || cmd == "лягушка":
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = flickr.SearchByTags(cfg, []string{"frog", "toad", "amphibian"})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query flickr api about frogs: %s", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Лягушки упрыгали, ни одной не нашлось."
|
||||
} else {
|
||||
// Костыль для красивого ответа в telegram.
|
||||
if j.Plugin == "telegram" {
|
||||
art := []string{"frog", "toad", "лягушка"}
|
||||
answer = fmt.Sprintf("[%s](%s)", art[rand.IntN(len(art))], answer)
|
||||
}
|
||||
|
||||
j.Message = answer
|
||||
}
|
||||
|
||||
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"})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query flickr api about owls: %s", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету сов, все разлетелись."
|
||||
} else {
|
||||
// Костыль для красивого ответа в telegram.
|
||||
if j.Plugin == "telegram" {
|
||||
answer = fmt.Sprintf("[{ O v O }](%s)", answer)
|
||||
}
|
||||
|
||||
j.Message = answer
|
||||
}
|
||||
|
||||
case cmd == "horse" || cmd == "лошадь" || cmd == "лошадка":
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = flickr.SearchByTags(cfg, []string{"horse", "equine", "stallion", "mare", "steed", "thoroughbred"})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query flickr api about horses: %s", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету коняшек, все разбежались."
|
||||
} else {
|
||||
// Костыль для красивого ответа в telegram.
|
||||
if j.Plugin == "telegram" {
|
||||
art := []string{"horse", "лошадь", "лошадка"}
|
||||
answer = fmt.Sprintf("[%s](%s)", art[rand.IntN(len(art))], answer)
|
||||
}
|
||||
|
||||
j.Message = answer
|
||||
}
|
||||
|
||||
case cmd == "rabbit" || cmd == "bunny" || cmd == "кролик":
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = flickr.SearchByTags(cfg, []string{"bunny", "rabbit", "buck"})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query flickr api about rabbits: %s", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету кроликов, все разбежались."
|
||||
} else {
|
||||
// Костыль для красивого ответа в telegram.
|
||||
if j.Plugin == "telegram" {
|
||||
answer = fmt.Sprintf("[(\\_/)](%s)", answer)
|
||||
}
|
||||
|
||||
j.Message = answer
|
||||
}
|
||||
|
||||
case cmd == "snail" || cmd == "улитка":
|
||||
for i := 0; i < 3; i++ {
|
||||
answer, err = flickr.SearchByTags(cfg, []string{"snail", "slug"})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Try %d/3 unable to query flickr api about snails: %s", i+1, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if answer == "" {
|
||||
j.Message = "Нету улиток, все расползлись."
|
||||
} else {
|
||||
// Костыль для красивого ответа в telegram.
|
||||
if j.Plugin == "telegram" {
|
||||
art := []string{"'-'_@_", "@╜", "@_'-'"}
|
||||
answer = fmt.Sprintf("[%s](%s)", art[rand.IntN(len(art))], answer)
|
||||
}
|
||||
|
||||
j.Message = answer
|
||||
}
|
||||
|
||||
case cmd == "w" || cmd == "п" || cmd == "погода" || cmd == "weather" || cmd == "погодка" || cmd == "погадка":
|
||||
// TODO: научиться различать 5-дневные и моментальные прогнозы. Пока умеем только моментальные.
|
||||
city := openweathermap.QueryOwmUserCache(cfg, j.Chatid, j.Userid)
|
||||
|
||||
if city == "" {
|
||||
j.Message = "Не припоминаю, какой город вас интересовал в прошлый раз."
|
||||
} 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 = fmt.Sprintf("Я не знаю, какая погода в %s", city)
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
}
|
||||
|
||||
case regexpWeather.MatchString(cmd):
|
||||
re := regexp.MustCompile("[[:space:]]+")
|
||||
cmdStr := re.Split(cmd, 2)[0]
|
||||
city := re.Split(cmd, 2)[1]
|
||||
|
||||
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 = fmt.Sprintf("Я не знаю, какая погода в %s", city)
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
} 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 = fmt.Sprintf("Я не знаю, какая погода в %s", city)
|
||||
} else {
|
||||
j.Message = answer
|
||||
}
|
||||
}
|
||||
|
||||
if err = openweathermap.UpdateOwmUserCache(cfg, j.Chatid, j.Userid, city); err != nil {
|
||||
log.Errorf(
|
||||
"Unable to save value to cache, for chatid %s, userid %s and city %s: %s",
|
||||
j.Chatid,
|
||||
j.Userid,
|
||||
city,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
log.Errorf("Unknown command %s, unable to handle, skipping", j.Message)
|
||||
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Message is not a command: %s, unable to handle, skipping", j.Message)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Настало время формировать json и засылать его в дальше
|
||||
var message SMsg
|
||||
message.From = j.From
|
||||
message.Userid = j.Userid
|
||||
message.Chatid = j.Chatid
|
||||
message.Threadid = j.Threadid
|
||||
message.Message = j.Message
|
||||
message.Plugin = j.Plugin
|
||||
message.Mode = j.Mode
|
||||
message.Misc.Fwdcnt = j.Misc.Fwdcnt
|
||||
message.Misc.Csign = j.Misc.Csign
|
||||
message.Misc.Username = j.Misc.Username
|
||||
message.Misc.Answer = j.Misc.Answer
|
||||
message.Misc.Botnick = j.Misc.Botnick
|
||||
message.Misc.Msgformat = j.Misc.Msgformat
|
||||
message.Misc.GoodMorning = j.Misc.GoodMorning
|
||||
|
||||
data, err := json.Marshal(message)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Unable to to serialize message for redis: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Заталкиваем наш 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 {
|
||||
log.Debugf("Send msg to redis channel %s: %s", sendTo, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
155
internal/webapp/ReadConfig.go
Normal file
155
internal/webapp/ReadConfig.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package webapp
|
||||
|
||||
import (
|
||||
"aleesa-webapp-go/internal/config"
|
||||
"aleesa-webapp-go/internal/log"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble"
|
||||
"github.com/hjson/hjson-go"
|
||||
)
|
||||
|
||||
// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге
|
||||
// нет.
|
||||
func ReadConfig() error {
|
||||
locations, err := DefaultConfigFileLocations()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(location)
|
||||
|
||||
// Не удалось прочитать, попробуем следующего кандидата
|
||||
if err != nil {
|
||||
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
||||
var (
|
||||
sampleConfig *config.MyConfig
|
||||
tmp map[string]interface{}
|
||||
)
|
||||
|
||||
err = hjson.Unmarshal(buf, &tmp)
|
||||
|
||||
// Не удалось распарсить - попробуем следующего кандидата
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
tmpjson, err := json.Marshal(tmp)
|
||||
|
||||
// Не удалось преобразовать map-ку в json
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Валидируем значения из конфига
|
||||
if sampleConfig.Server == "" {
|
||||
sampleConfig.Server = Host
|
||||
}
|
||||
|
||||
if sampleConfig.Port == 0 {
|
||||
sampleConfig.Port = RedisPort
|
||||
}
|
||||
|
||||
if sampleConfig.Timeout == 0 {
|
||||
sampleConfig.Timeout = NetworkTimeout
|
||||
}
|
||||
|
||||
if sampleConfig.Loglevel == "" {
|
||||
sampleConfig.Loglevel = Loglevel
|
||||
}
|
||||
|
||||
// sampleConfig.Log = "" if not set
|
||||
|
||||
if sampleConfig.Channel == "" {
|
||||
log.Errorf("Channel field in config file %s must be set", location)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if sampleConfig.DataDir == "" {
|
||||
sampleConfig.DataDir = DataDir
|
||||
}
|
||||
|
||||
if sampleConfig.Csign == "" {
|
||||
log.Errorf("Csign field in config file %s must be set", location)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if sampleConfig.ForwardsMax == 0 {
|
||||
sampleConfig.ForwardsMax = ForwardMax
|
||||
}
|
||||
|
||||
if unsafe.Sizeof(sampleConfig.OpenWeatherMap) == 0 {
|
||||
log.Errorf("Hash OpenWeatherMap in config file %s must be defined", location)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if sampleConfig.OpenWeatherMap.Appid == "" {
|
||||
log.Errorf("Openweathermap->appid in config file %s must be set to its valid value", location)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// sampleConfig.OpenWeatherMap.Country is false by default
|
||||
|
||||
// Теперь надо бы попробовать загрузить список User Agent-ов, с которыми ходить в разные апишки.
|
||||
uaFile := fmt.Sprintf("%s/useragents.txt", sampleConfig.DataDir)
|
||||
uaList, err := ReadLines(uaFile)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read %s: %w", uaFile, err)
|
||||
}
|
||||
|
||||
sampleConfig.UserAgents = uaList
|
||||
|
||||
sampleConfig.PcacheDB = make(map[string]*pebble.DB)
|
||||
|
||||
log.Infof("Using %s as config file", location)
|
||||
|
||||
Config = sampleConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("config was not loaded! Refusing to start")
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
36
internal/webapp/ReadLines.go
Normal file
36
internal/webapp/ReadLines.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package webapp
|
||||
|
||||
import (
|
||||
"aleesa-webapp-go/internal/log"
|
||||
"bufio"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ReadLines читает даденный файл построчно в массив строк.
|
||||
func ReadLines(path string) ([]string, error) {
|
||||
file, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to close file %s:%s", path, err)
|
||||
}
|
||||
}(file)
|
||||
|
||||
var (
|
||||
lines []string
|
||||
scanner = bufio.NewScanner(file)
|
||||
)
|
||||
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
return lines, scanner.Err()
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
66
internal/webapp/ValidateRmsg.go
Normal file
66
internal/webapp/ValidateRmsg.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package webapp
|
||||
|
||||
import (
|
||||
"aleesa-webapp-go/internal/config"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ValidateRmsg валидирует входящее сообщение.
|
||||
func ValidateRmsg(cfg *config.MyConfig, j RMsg, msg string) (RMsg, error) {
|
||||
if exist := j.From; exist == "" {
|
||||
emsg := fmt.Errorf("incorrect msg from redis, no from field: %s", msg)
|
||||
|
||||
return j, emsg
|
||||
}
|
||||
|
||||
if exist := j.Chatid; exist == "" {
|
||||
emsg := fmt.Errorf("incorrect msg from redis, no chatid field: %s", msg)
|
||||
|
||||
return j, emsg
|
||||
}
|
||||
|
||||
if exist := j.Userid; exist == "" {
|
||||
emsg := fmt.Errorf("incorrect msg from redis, no userid field: %s", msg)
|
||||
|
||||
return j, emsg
|
||||
}
|
||||
|
||||
// j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально.
|
||||
|
||||
if exist := j.Message; exist == "" {
|
||||
emsg := fmt.Errorf("incorrect msg from redis, no message field: %s", msg)
|
||||
|
||||
return j, emsg
|
||||
}
|
||||
|
||||
if exist := j.Plugin; exist == "" {
|
||||
emsg := fmt.Errorf("incorrect msg from redis, no plugin field: %s", msg)
|
||||
|
||||
return j, emsg
|
||||
}
|
||||
|
||||
if exist := j.Mode; exist == "" {
|
||||
emsg := fmt.Errorf("incorrect msg from redis, no mode field: %s", msg)
|
||||
|
||||
return j, emsg
|
||||
}
|
||||
|
||||
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет
|
||||
// j.Misc.Botnick тоже можно не передавать, тогда будет записана пустая строка
|
||||
// j.Misc.Csign если нам его не передали, возьмём значение из конфига
|
||||
if exist := j.Misc.Csign; exist == "" {
|
||||
j.Misc.Csign = cfg.Csign
|
||||
}
|
||||
|
||||
// j.Misc.Fwdcnt если нам его не передали, то будет 0
|
||||
if exist := j.Misc.Fwdcnt; exist == 0 {
|
||||
j.Misc.Fwdcnt = 1
|
||||
}
|
||||
|
||||
// j.Misc.GoodMorning может быть быть 1 или 0, по-умолчанию 0
|
||||
// j.Misc.Msgformat может быть быть 1 или 0, по-умолчанию 0
|
||||
// j.Misc.Username можно не передавать, тогда будет пустая строка
|
||||
return j, nil
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
30
internal/webapp/defaults.go
Normal file
30
internal/webapp/defaults.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package webapp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func DefaultConfigFileLocations() ([]string, error) {
|
||||
var locations []string
|
||||
|
||||
executablePath, err := os.Executable()
|
||||
|
||||
if err != nil {
|
||||
return locations, fmt.Errorf("unable to get my executable path: %w", err)
|
||||
}
|
||||
|
||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||
|
||||
locations = []string{
|
||||
"~/.aleesa-webapp-go.json",
|
||||
"~/aleesa-webapp-go.json",
|
||||
"/etc/aleesa-webapp-go.json",
|
||||
configJSONPath,
|
||||
}
|
||||
|
||||
return locations, nil
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
18
internal/webapp/globals.go
Normal file
18
internal/webapp/globals.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package webapp
|
||||
|
||||
import (
|
||||
"aleesa-webapp-go/internal/config"
|
||||
)
|
||||
|
||||
var (
|
||||
ForwardMax int64 = 5
|
||||
ConfigFileSize int64 = 65535
|
||||
DataDir = "data"
|
||||
Host = "localhost"
|
||||
Loglevel = "info"
|
||||
RedisPort = 6379
|
||||
NetworkTimeout = 10
|
||||
Config *config.MyConfig
|
||||
)
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
43
internal/webapp/types.go
Normal file
43
internal/webapp/types.go
Normal file
@@ -0,0 +1,43 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
Reference in New Issue
Block a user