gбновление зависимостей, рефакторинг кода, чтобы он выглядел как гошный проект
This commit is contained in:
137
internal/lib/config.go
Normal file
137
internal/lib/config.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hjson/hjson-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет
|
||||
func ReadConfig() MyConfig {
|
||||
configLoaded := false
|
||||
var config MyConfig
|
||||
executablePath, err := os.Executable()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get current executable path: %s", err)
|
||||
}
|
||||
|
||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||
|
||||
locations := []string{
|
||||
"~/.aleesa-phrases-go.json",
|
||||
"~/aleesa-phrases-go.json",
|
||||
"/etc/aleesa-phrases-go.json",
|
||||
configJSONPath,
|
||||
}
|
||||
|
||||
for _, location := range locations {
|
||||
fileInfo, err := os.Stat(location)
|
||||
|
||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
||||
if fileInfo.Size() > 65535 {
|
||||
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 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
|
||||
}
|
||||
|
||||
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 = "localhost"
|
||||
}
|
||||
|
||||
if sampleConfig.Port == 0 {
|
||||
sampleConfig.Port = 6379
|
||||
}
|
||||
|
||||
if sampleConfig.LogLevel == "" {
|
||||
sampleConfig.LogLevel = "info"
|
||||
}
|
||||
|
||||
// sampleConfig.Log = "" if not set
|
||||
// Если путь не задан или является относительным, вычисляем его относительно бинарника
|
||||
if sampleConfig.DataDir == "" || sampleConfig.DataDir[0:1] != "/" {
|
||||
binaryPath, err := os.Executable()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get binary name: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if sampleConfig.DataDir == "" {
|
||||
sampleConfig.DataDir = fmt.Sprintf("%s/data", filepath.Dir(binaryPath))
|
||||
log.Infof("Field data_dir is not set in config, using %s", sampleConfig.DataDir)
|
||||
} else {
|
||||
sampleConfig.DataDir = fmt.Sprintf("%s/%s", filepath.Dir(binaryPath), sampleConfig.DataDir)
|
||||
}
|
||||
}
|
||||
|
||||
if sampleConfig.Channel == "" {
|
||||
log.Errorf("Channel field in config file %s must be set", location)
|
||||
}
|
||||
|
||||
if sampleConfig.CSign == "" {
|
||||
log.Errorf("CSign field in config file %s must be set", location)
|
||||
}
|
||||
|
||||
if sampleConfig.ForwardsMax == 0 {
|
||||
sampleConfig.ForwardsMax = ForwardMax
|
||||
}
|
||||
|
||||
config = sampleConfig
|
||||
configLoaded = true
|
||||
log.Infof("Using %s as config file", location)
|
||||
break
|
||||
}
|
||||
|
||||
if !configLoaded {
|
||||
log.Error("Config was not loaded! Refusing to start.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
20
internal/lib/fortune.go
Normal file
20
internal/lib/fortune.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Фортунка
|
||||
func fortune() string {
|
||||
var answer = "There is no knowledge that is not power."
|
||||
phrase, err := FetchRandomV(PebbleDB.Fortune.DB, PebbleDB.Fortune.Name)
|
||||
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return answer
|
||||
}
|
||||
|
||||
return phrase
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
44
internal/lib/friday.go
Normal file
44
internal/lib/friday.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// А не пятница ли сейчас?
|
||||
func friday() string {
|
||||
var answer = fmt.Sprintf("Today is %s", time.Now().Weekday().String())
|
||||
var phrase string
|
||||
var err error
|
||||
|
||||
// Но нам-то интересно какое-то более доругое высказывание относительно текущего дня недели...
|
||||
dayNum := int(time.Now().Weekday())
|
||||
|
||||
switch dayNum {
|
||||
case 0:
|
||||
phrase, err = FetchRandomV(PebbleDB.Sunday.DB, PebbleDB.Sunday.Name)
|
||||
case 1:
|
||||
phrase, err = FetchRandomV(PebbleDB.Monday.DB, PebbleDB.Monday.Name)
|
||||
case 2:
|
||||
phrase, err = FetchRandomV(PebbleDB.Tuesday.DB, PebbleDB.Tuesday.Name)
|
||||
case 3:
|
||||
phrase, err = FetchRandomV(PebbleDB.Wednesday.DB, PebbleDB.Wednesday.Name)
|
||||
case 4:
|
||||
phrase, err = FetchRandomV(PebbleDB.Thursday.DB, PebbleDB.Thursday.Name)
|
||||
case 5:
|
||||
phrase, err = FetchRandomV(PebbleDB.Friday.DB, PebbleDB.Friday.Name)
|
||||
case 6:
|
||||
phrase, err = FetchRandomV(PebbleDB.Saturday.DB, PebbleDB.Saturday.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return answer
|
||||
}
|
||||
|
||||
return phrase
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
98
internal/lib/globals.go
Normal file
98
internal/lib/globals.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"os"
|
||||
|
||||
"github.com/cockroachdb/pebble"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
// To break circular message forwarding we must set some sane default, it can be overridden via config
|
||||
var ForwardMax int64 = 5
|
||||
|
||||
// Config - это у нас глобальная штука :)
|
||||
var Config MyConfig
|
||||
|
||||
// Просто счётчик, использован в seed-программах
|
||||
var Counter = 0
|
||||
|
||||
var Dow = []string{
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
"sunday",
|
||||
}
|
||||
|
||||
// Struct с дескрипторами БД
|
||||
var PebbleDB struct {
|
||||
// Счётчик записей для каждой БД
|
||||
Count struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
// База модуля пословиц
|
||||
Proverb struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
// База модуля fortune
|
||||
Fortune struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
// 7 баз по дням недели модуля friday
|
||||
Monday struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
Tuesday struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
Wednesday struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
Thursday struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
Friday struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
Saturday struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
Sunday struct {
|
||||
DB *pebble.DB
|
||||
Name string
|
||||
}
|
||||
}
|
||||
|
||||
// Мапка с открытыми дескрипторами баз с кармой
|
||||
var KarmaDB = make(map[string]*pebble.DB)
|
||||
|
||||
// Дефолтный конфиг клиента-редиски
|
||||
var RedisClient *redis.Client
|
||||
var Subscriber *redis.PubSub
|
||||
|
||||
// Создадим источник рандома
|
||||
var Random *rand.Rand
|
||||
|
||||
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов
|
||||
var SigChan = make(chan os.Signal, 1)
|
||||
|
||||
// Ставится в true, если мы получили сигнал на выключение
|
||||
var Shutdown = false
|
||||
|
||||
// Main context
|
||||
var Ctx = context.Background()
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
171
internal/lib/karma.go
Normal file
171
internal/lib/karma.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors/oserror"
|
||||
"github.com/cockroachdb/pebble"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Изменим карму фразе
|
||||
func changeKarma(chatID string, phrase string, action string) string {
|
||||
var chatHash = sha256.Sum256([]byte(chatID))
|
||||
var database = fmt.Sprintf("karma_db/%x", chatHash)
|
||||
var karmaInt int
|
||||
var answer string
|
||||
var err error
|
||||
|
||||
if phrase = strings.TrimSpace(phrase); phrase == "" {
|
||||
phrase = " "
|
||||
}
|
||||
|
||||
if _, ok := KarmaDB[database]; !ok {
|
||||
var options pebble.Options
|
||||
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
|
||||
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
|
||||
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений
|
||||
options.L0CompactionFileThreshold = 8
|
||||
|
||||
KarmaDB[database], err = pebble.Open(Config.DataDir+"/"+database, &options)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Unable to open karma db, %s: %s\n", database, err)
|
||||
|
||||
if phrase == " " {
|
||||
return "Карма пустоты равна 0"
|
||||
} else {
|
||||
return fmt.Sprintf("Карма %s равна 0", phrase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
karmaString, err := FetchV(KarmaDB[database], phrase)
|
||||
|
||||
if err != nil {
|
||||
// Такого ключа в базе нет
|
||||
if errors.Is(err, pebble.ErrNotFound) {
|
||||
karmaInt = 0
|
||||
// Такой бд нет
|
||||
} else if errors.Is(err, fs.ErrNotExist) {
|
||||
karmaInt = 0
|
||||
} else if errors.Is(err, oserror.ErrNotExist) {
|
||||
karmaInt = 0
|
||||
// Какая-то ещё ошибка
|
||||
} else {
|
||||
log.Errorf("Unable to Get karma of %s in database %s: %s", phrase, database, err)
|
||||
|
||||
if phrase == "" {
|
||||
return fmt.Sprint("Карма пустоты равна 0")
|
||||
} else {
|
||||
return fmt.Sprintf("Карма %s равна 0", phrase)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if karmaInt, err = strconv.Atoi(karmaString); err != nil {
|
||||
karmaInt = 0
|
||||
log.Errorf("Got garbage instead of karma for %s in database %s: %s", phrase, database, err)
|
||||
}
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "++":
|
||||
karmaInt += 1
|
||||
case "--":
|
||||
karmaInt -= 1
|
||||
default:
|
||||
log.Errorf("Function changeKarma got strange action neither ++ nor --: %s", action)
|
||||
|
||||
if phrase == "" {
|
||||
return fmt.Sprint("Карма пустоты равна 0")
|
||||
} else {
|
||||
return fmt.Sprintf("Карма %s равна 0", phrase)
|
||||
}
|
||||
}
|
||||
|
||||
// Поменяли карму, теперь пора превратить значение int в string :)
|
||||
karmaString = fmt.Sprintf("%d", karmaInt)
|
||||
|
||||
if err := StoreKV(KarmaDB[database], "", phrase, karmaString, false); err != nil {
|
||||
log.Errorf("Unable to Set karma of %s in database %s: %s", phrase, database, err)
|
||||
}
|
||||
|
||||
// Каждые -5 пунктов пробивают дно :)
|
||||
if karmaInt < -5 && math.Mod(float64(karmaInt), 5) == -1 {
|
||||
answer = "Зарегистрировано пробитие дна: к"
|
||||
} else {
|
||||
answer = "K"
|
||||
}
|
||||
|
||||
if phrase == " " {
|
||||
return fmt.Sprintf("%sарма пустоты равна %s", answer, karmaString)
|
||||
} else {
|
||||
return fmt.Sprintf("%sарма %s равна %s", answer, phrase, karmaString)
|
||||
}
|
||||
}
|
||||
|
||||
// Достанем карму фразы
|
||||
func getKarma(chatID string, phrase string) string {
|
||||
var err error
|
||||
|
||||
chatHash := sha256.Sum256([]byte(chatID))
|
||||
database := fmt.Sprintf("karma_db/%x", chatHash)
|
||||
|
||||
if phrase = strings.Trim(phrase, "\n\r\t "); phrase == "" {
|
||||
phrase = " "
|
||||
}
|
||||
|
||||
if _, ok := KarmaDB[database]; !ok {
|
||||
var options pebble.Options
|
||||
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
|
||||
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
|
||||
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений
|
||||
options.L0CompactionFileThreshold = 8
|
||||
KarmaDB[database], err = pebble.Open(Config.DataDir+"/"+database, &options)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Unable to open karma db, %s: %s\n", database, err)
|
||||
|
||||
if phrase == " " {
|
||||
return "Карма пустоты равна 0"
|
||||
} else {
|
||||
return fmt.Sprintf("Карма %s равна 0", phrase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
answer, err := FetchV(KarmaDB[database], phrase)
|
||||
|
||||
// Если из базы ничего не вынулось, по каким-то причинам, то просто соврём, что карма равна нулю
|
||||
if err != nil {
|
||||
if errors.Is(err, pebble.ErrNotFound) {
|
||||
log.Debugf("Unable to get karma for %s: no record found in db %s", phrase, database)
|
||||
} else if errors.Is(err, fs.ErrNotExist) {
|
||||
log.Debugf("Unable to get karma for %s: db dir %s does not exist", phrase, database)
|
||||
} else if errors.Is(err, oserror.ErrNotExist) {
|
||||
log.Debugf("Unable to get karma for %s: db dir %s does not exist", phrase, database)
|
||||
} else {
|
||||
log.Errorf("Unable to get karma for %s in db dir %s: %s", phrase, database, err)
|
||||
}
|
||||
|
||||
if phrase == " " {
|
||||
return "Карма пустоты равна 0"
|
||||
} else {
|
||||
return fmt.Sprintf("Карма %s равна 0", phrase)
|
||||
}
|
||||
}
|
||||
|
||||
if phrase == " " {
|
||||
return fmt.Sprintf("Карма пустоты равна %s", answer)
|
||||
} else {
|
||||
return fmt.Sprintf("Карма %s равна %s", phrase, answer)
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
202
internal/lib/msg-parser.go
Normal file
202
internal/lib/msg-parser.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Горутинка, которая парсит json-чики прилетевшие из REDIS-ки
|
||||
func MsgParser(ctx context.Context, msg string) {
|
||||
var sendTo string
|
||||
var j rMsg
|
||||
var message sMsg
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// j.ThreadID может быть пустым, это нормально
|
||||
|
||||
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
|
||||
} else {
|
||||
sendTo = j.Plugin
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// j.Misc.BotNick тоже можно не передавать, тогда будет записана пустая строка
|
||||
// j.Misc.CSign если нам его не передали, возьмём значение из конфига
|
||||
if exist := j.Misc.CSign; exist == "" {
|
||||
j.Misc.CSign = Config.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 можно не передавать, тогда будет пустая строка
|
||||
|
||||
// Отвалидировались, теперь вернёмся к нашим баранам.
|
||||
|
||||
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение
|
||||
if j.Misc.FwdCnt > Config.ForwardsMax {
|
||||
log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg)
|
||||
return
|
||||
} else {
|
||||
j.Misc.FwdCnt++
|
||||
}
|
||||
|
||||
// Классифицирем входящие сообщения. Первым делом, попробуем определить команды
|
||||
msgLen := len(j.Message)
|
||||
|
||||
if j.Message[0:len(j.Misc.CSign)] == j.Misc.CSign { //nolint:gocritic
|
||||
cmd := j.Message[len(j.Misc.CSign):]
|
||||
|
||||
if cmd == "friday" || cmd == "пятница" {
|
||||
j.Message = friday()
|
||||
} else if (cmd == "proverb") || (cmd == "пословица") || (cmd == "пословиться") {
|
||||
j.Message = proverb()
|
||||
} else if (cmd == "f") || (cmd == "ф") {
|
||||
j.Message = fortune()
|
||||
} else if (cmd == "fortune") || (cmd == "фортунка") {
|
||||
j.Message = fortune()
|
||||
} else if (cmd == "karma") || (cmd == "карма") {
|
||||
j.Message = getKarma(j.ChatID, "")
|
||||
} else if match, _ := regexp.MatchString("^(rum|ром)[[:space:]]?([[:space:]].+)*$", cmd); match {
|
||||
format := "/me притаскивает на подносе стопку рома для %s, края стопки искрятся кристаллами соли."
|
||||
j.Message = fmt.Sprintf(format, j.Misc.Username)
|
||||
} else if match, _ := regexp.MatchString("^(vodka|водка)[[:space:]]?([[:space:]].+)*$", cmd); match {
|
||||
format := "/me подаёт шот водки с небольшим маринованным огурчиком на блюдце для %s. Из огурчика торчит "
|
||||
format += "небольшая вилочка."
|
||||
j.Message = fmt.Sprintf(format, j.Misc.Username)
|
||||
} else if match, _ := regexp.MatchString("^(beer|пиво)[[:space:]]?([[:space:]].+)*$", cmd); match {
|
||||
format := "/me бахает об стол перед %s кружкой холодного пива, часть пенной шапки сползает по запотевшей "
|
||||
format += "стенке кружки."
|
||||
j.Message = fmt.Sprintf(format, j.Misc.Username)
|
||||
} else if match, _ := regexp.MatchString("^(tequila|текила)[[:space:]]?([[:space:]].+)*$", cmd); match {
|
||||
format := "/me ставит рядом с %s шот текилы, аккуратно на ребро стопки насаживает дольку лайма и ставит "
|
||||
format += "кофейное блюдце с горочкой соли."
|
||||
j.Message = fmt.Sprintf(format, j.Misc.Username)
|
||||
} else if match, _ := regexp.MatchString("^(whisky|виски)[[:space:]]?([[:space:]].+)*$", cmd); match {
|
||||
format := "/me демонстративно достаёт из морозилки пару кубических камушков, бросает их в толстодонный "
|
||||
format += "стакан и аккуратно наливает Jack Daniels. Запускает стакан вдоль барной стойки, он "
|
||||
format += "останавливается около %s."
|
||||
j.Message = fmt.Sprintf(format, j.Misc.Username)
|
||||
} else if match, _ := regexp.MatchString("^(absinth|абсент)[[:space:]]?([[:space:]].+)*$", cmd); match {
|
||||
format := "/me наливает абсент в стопку. Смочив кубик сахара в абсенте кладёт его на дырявую ложечку и "
|
||||
format += "поджигает. Как только пламя потухнет, %s размешивает оплавившийся кубик в абсенте и подносит "
|
||||
format += "стопку %s."
|
||||
j.Message = fmt.Sprintf(format, j.Misc.BotNick, j.Misc.Username)
|
||||
} else if match, _ := regexp.MatchString("^fuck[[:space:]]*$", cmd); match {
|
||||
j.Message = "Не ругайся."
|
||||
} else {
|
||||
cmdLen := len(cmd)
|
||||
cmds := []string{"karma ", "карма "}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmdLen > len(command) && cmd[0:len(command)] == command {
|
||||
j.Message = getKarma(j.ChatID, cmd[len(command):])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Это последняя проверка, поэтому во всех else мы исходящее сообщение можем смело ставить в пустую строку
|
||||
} else if msgLen > len("++") {
|
||||
if j.Message[msgLen-len("--"):msgLen] == "--" || j.Message[msgLen-len("++"):msgLen] == "++" {
|
||||
|
||||
// Предполагается, что менять карму мы будем для одной фразы, то есть для 1 строки
|
||||
if len(strings.Split(j.Message, "\n")) == 1 {
|
||||
if j.Message[msgLen-len("--"):msgLen] == "--" {
|
||||
j.Message = changeKarma(j.ChatID, j.Message[0:msgLen-len("--")], "--")
|
||||
} else if j.Message[msgLen-len("++"):msgLen] == "++" {
|
||||
j.Message = changeKarma(j.ChatID, j.Message[0:msgLen-len("++")], "++")
|
||||
} else {
|
||||
j.Message = ""
|
||||
}
|
||||
} else {
|
||||
j.Message = ""
|
||||
}
|
||||
} else {
|
||||
j.Message = ""
|
||||
}
|
||||
} else {
|
||||
j.Message = ""
|
||||
}
|
||||
|
||||
if j.Message != "" {
|
||||
// Настало время формировать json и засылать его в дальше
|
||||
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
|
||||
|
||||
data, err := json.Marshal(message)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Unable to to serialize message for redis: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Заталкиваем наш json в редиску
|
||||
if err := 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: */
|
||||
112
internal/lib/pebble.go
Normal file
112
internal/lib/pebble.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"strconv"
|
||||
|
||||
"github.com/cockroachdb/pebble"
|
||||
)
|
||||
|
||||
// StoreKV сохраняет в указанной бд ключ и значение
|
||||
func StoreKV(db *pebble.DB, dbName string, key string, value string, useCounter bool) error {
|
||||
var kArray = []byte(key)
|
||||
var vArray = []byte(value)
|
||||
|
||||
err := db.Set(kArray, vArray, pebble.Sync)
|
||||
|
||||
if useCounter {
|
||||
// У pebble нет в арсенале функции count(), так что "изобретём" её
|
||||
if err == nil {
|
||||
amount, err := FetchV(PebbleDB.Count.DB, dbName)
|
||||
|
||||
if err != nil {
|
||||
// Такого ключа в базе нет
|
||||
if errors.Is(err, pebble.ErrNotFound) {
|
||||
amount = fmt.Sprintf("%d", 0)
|
||||
// Такой базы нет
|
||||
} else if errors.Is(err, fs.ErrNotExist) {
|
||||
amount = fmt.Sprintf("%d", 0)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
amountNum, err := strconv.Atoi(amount)
|
||||
|
||||
// Хуйня в базе, данные должны проходить конвертацию в int
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amountNum += 1
|
||||
amount = fmt.Sprintf("%d", amountNum)
|
||||
|
||||
err = PebbleDB.Count.DB.Set([]byte(dbName), []byte(amount), pebble.Sync)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if err != nil {
|
||||
return valueString, err
|
||||
}
|
||||
|
||||
valueString = string(vArray)
|
||||
err = closer.Close()
|
||||
|
||||
return valueString, err
|
||||
}
|
||||
|
||||
// FetchRandomV достаёт рандомное значение из указанной базы (count_db для указанной db должна содержать количество записей)
|
||||
func FetchRandomV(db *pebble.DB, dbName string) (string, error) {
|
||||
answer := ""
|
||||
amount, err := FetchV(PebbleDB.Count.DB, dbName)
|
||||
|
||||
if err != nil {
|
||||
// Такого ключа в базе нет
|
||||
if errors.Is(err, pebble.ErrNotFound) {
|
||||
return answer, fmt.Errorf("looks like %s is not seeded or count_db is missing", dbName)
|
||||
// Такой базы нет
|
||||
} else if errors.Is(err, fs.ErrNotExist) {
|
||||
return answer, fmt.Errorf("unable to get random value from %s: count_db is missing", dbName)
|
||||
} else {
|
||||
return answer, fmt.Errorf("something going wrong with %s: %w", dbName, err)
|
||||
}
|
||||
}
|
||||
|
||||
amountNum, err := strconv.Atoi(amount)
|
||||
|
||||
// Хуйня в базе, данные должны проходить конвертацию в int
|
||||
if err != nil {
|
||||
return answer, fmt.Errorf("garbage in count_db while getting info about %s: %w", dbName, err)
|
||||
}
|
||||
|
||||
phraseNum := Random.Intn(amountNum + 1)
|
||||
phraseItem := fmt.Sprintf("%d", phraseNum)
|
||||
|
||||
answer, err = FetchV(db, phraseItem)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("an error occured while getting answer from %s: %s", dbName, err)
|
||||
}
|
||||
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
20
internal/lib/proverb.go
Normal file
20
internal/lib/proverb.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Пословица
|
||||
func proverb() string {
|
||||
var answer = "There is no knowledge that is not power."
|
||||
phrase, err := FetchRandomV(PebbleDB.Proverb.DB, PebbleDB.Proverb.Name)
|
||||
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return answer
|
||||
}
|
||||
|
||||
return phrase
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
137
internal/lib/sighandler.go
Normal file
137
internal/lib/sighandler.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Хэндлер сигналов закрывает все бд и сваливает из приложения
|
||||
func SigHandler() {
|
||||
var err error
|
||||
|
||||
for {
|
||||
var s = <-SigChan
|
||||
switch s {
|
||||
case syscall.SIGINT:
|
||||
log.Infoln("Got SIGINT, quitting")
|
||||
case syscall.SIGTERM:
|
||||
log.Infoln("Got SIGTERM, quitting")
|
||||
case syscall.SIGQUIT:
|
||||
log.Infoln("Got SIGQUIT, quitting")
|
||||
|
||||
// Заходим на новую итерацию, если у нас "неинтересный" сигнал
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// Чтобы не срать в логи ошибками от редиски, проставим shutdown state приложения в true
|
||||
Shutdown = true
|
||||
|
||||
// Отпишемся от всех каналов и закроем коннект к редиске
|
||||
if err = Subscriber.Unsubscribe(Ctx); err != nil {
|
||||
log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
|
||||
}
|
||||
|
||||
if err = Subscriber.Close(); err != nil {
|
||||
log.Errorf("Unable to close redis connection cleanly: %s", err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Fortune.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Fortune.Name, err)
|
||||
}
|
||||
|
||||
// Закроем статичные базы с фразами, тут их многовато :(
|
||||
if err = PebbleDB.Fortune.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Fortune.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Proverb.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Proverb.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Proverb.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Proverb.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Count.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Count.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Count.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Count.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Monday.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Monday.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Monday.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Monday.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Tuesday.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Tuesday.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Tuesday.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Tuesday.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Wednesday.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Wednesday.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Wednesday.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Wednesday.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Thursday.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Thursday.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Thursday.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Thursday.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Friday.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Friday.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Friday.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Friday.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Saturday.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Saturday.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Saturday.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Saturday.Name, err)
|
||||
}
|
||||
|
||||
if err := PebbleDB.Sunday.DB.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s db: %s", PebbleDB.Sunday.Name, err)
|
||||
}
|
||||
|
||||
if err = PebbleDB.Sunday.DB.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s db: %s", PebbleDB.Sunday.Name, err)
|
||||
}
|
||||
|
||||
// Закроем базы с кармой
|
||||
for dbName, karma := range KarmaDB {
|
||||
if err := karma.Flush(); err != nil {
|
||||
log.Errorf("Unable to flush %s karma db: %s", dbName, err)
|
||||
}
|
||||
|
||||
if err := karma.Close(); err != nil {
|
||||
log.Errorf("Unable to close %s karma db: %s", dbName, err)
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
55
internal/lib/types.go
Normal file
55
internal/lib/types.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package lib
|
||||
|
||||
// MyConfig описывает структурку, получаемую при загрузке и распарсивании конфига
|
||||
type MyConfig struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
LogLevel string `json:"loglevel,omitempty"`
|
||||
Log string `json:"log,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
DataDir string `json:"data_dir,omitempty"`
|
||||
CSign string `json:"csign,omitempty"`
|
||||
ForwardsMax int64 `json:"forwards_max,omitempty"`
|
||||
}
|
||||
|
||||
// Входящее сообщение из pubsub-канала redis-ки
|
||||
type rMsg struct {
|
||||
From string `json:"from,omitempty"`
|
||||
ChatID string `json:"chatid,omitempty"`
|
||||
UserID string `json:"userid,omitempty"`
|
||||
ThreadID string `json:"threadid"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// Исходящее сообщение в 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