0

[WIP] Implement good morning message for all willing chats, update TODO.md

This commit is contained in:
eleksir
2025-11-02 15:57:07 +03:00
parent 61208f5247
commit 1e54dcf9f1
7 changed files with 156 additions and 5 deletions

View File

@@ -25,7 +25,7 @@
1 [V] [V] цензор сообщений определённого типа. 1 [V] [V] цензор сообщений определённого типа.
2 [ ] [V] фортунка каждое утро. (+ добавить время, относительно gmt, когда показывать фортунку). См. п.13. 2 [V] [V] фортунка каждое утро. См. п.13.
3 [V] [V] Приветствие новых участников 3 [V] [V] Приветствие новых участников
@@ -68,6 +68,7 @@
[V] регулярных компакций базы с настройками. [V] регулярных компакций базы с настройками.
[ ] регулярного обслуживания таблиц с пользователями. [ ] регулярного обслуживания таблиц с пользователями.
14. [ ] в функции AppendChatListDB для нового чата добавлять дефолтные значения настроек.

View File

@@ -89,13 +89,20 @@ func main() {
go tg.SigHandler() go tg.SigHandler()
go tg.Telega(tg.Config) go tg.Telega(tg.Config)
// Periodically compact/flush pebble dbs. // Периодически обслуживаем базы с настройками чатов.
if job, err := scheduler.Every(1).Hours().NotImmediately().Run(tg.TidySettingsDB); err != nil { if job, err := scheduler.Every(1).Hours().NotImmediately().Run(tg.TidySettingsDB); err != nil {
log.Errorf("Unable to schedule periodic settings db flush: %s", err) log.Errorf("Unable to schedule periodic settings db flush: %s", err)
} else { } else {
tg.PeriodicJobs = append(tg.PeriodicJobs, job) tg.PeriodicJobs = append(tg.PeriodicJobs, job)
} }
// Отправлем "доброе утро" каждое утро для всех чатов, которые на это подписались.
if job, err := scheduler.Every().Day().At("8:10").Run(tg.SendGoodMorning); err != nil {
log.Errorf("Unable to schedule send good morning task: %s", err)
} else {
tg.PeriodicJobs = append(tg.PeriodicJobs, job)
}
// Обработчик событий от редиски. // Обработчик событий от редиски.
for msg := range redisMsgChan { for msg := range redisMsgChan {
if tg.Shutdown { if tg.Shutdown {

View File

@@ -35,10 +35,19 @@ var (
// settingsDB мапка с открытыми дескрипторами баз с настройками. // settingsDB мапка с открытыми дескрипторами баз с настройками.
settingsDB = make(map[string]*pebble.DB) settingsDB = make(map[string]*pebble.DB)
// chatListDB объектик *pebble.DB с базой, в которой лежит список чатов.
chatListDB *pebble.DB
// chatListDBName имя базы данных со списком чатов.
chatListDBName = "chat_list_db"
// chatList слайс со списоком чатов.
chatList = []string{}
// tg is var for telegram api. // tg is var for telegram api.
tg echotron.API tg echotron.API
// introduceGreet приветсвенные фразы для новых участников чата. // introduceGreet приветственные фразы для новых участников чата.
introduceGreet = [...]string{ introduceGreet = [...]string{
"Дратути", "Дратути",
"Дарована", "Дарована",

View File

@@ -2,11 +2,14 @@ package tg
import ( import (
"aleesa-telegram-go/internal/log" "aleesa-telegram-go/internal/log"
"encoding/json"
"strconv"
) )
var ( var (
// TidySettingsDB flushes regularly pebble dbs with settings. Remmended action to keeb that db in a good shape. // TidySettingsDB флашит на диск базу с настройками чатов. Рекомендуется делать это время от времени для поддержания
// Meant to be run about hourly or something like that. // производительности базы на должном уровне.
// Предполагается, что это происходит где-то раз в час, чаще смысла нет.
TidySettingsDB = func() { TidySettingsDB = func() {
if len(settingsDB) > 0 { if len(settingsDB) > 0 {
for name, db := range settingsDB { for name, db := range settingsDB {
@@ -17,6 +20,56 @@ var (
} }
} }
} }
log.Debugf("Flushing %s", chatListDBName)
if err := chatListDB.Flush(); err != nil {
log.Errorf("Unable to Flush() %s: %s", chatListDBName, err)
}
}
// SendGoodMorning отсылает сообщение "с добрым утром" (сейчас - фортунку). Предполагается, что оно прогоняется по
// всем желающим чатам раз в сутки, с утра. Утро отсчитывается от настроек локали в системе.
SendGoodMorning = func() {
for _, chatID := range chatList {
if GetSetting(chatID, "FortuneMsg") == "1" {
me, err := tg.GetMe()
// Засылаем фразу в misc-канал (в роутер).
var rmsg rMsg
rmsg.Chatid = chatID
rmsg.Userid = strconv.FormatInt(me.Result.ID, 10)
rmsg.Message = "!f"
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(me.Result)
rmsg.Misc.GoodMorning = 1
// Форматирование нужно только для вывода некоторых ответов на команды, команды мы ловим выше по тексту, так что
// смело ставим тут 0.
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))
}
}
}
} }
) )

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"slices"
"aleesa-telegram-go/internal/log" "aleesa-telegram-go/internal/log"
@@ -45,6 +46,81 @@ func FetchV(db *pebble.DB, key string) (string, error) {
return valueString, err return valueString, err
} }
// InitChatListDB открывает для пользования базу со списком чатов.
func InitChatListDB() error {
var (
err error
options pebble.Options
dbPath = Config.DataDir + "/" + chatListDBName
)
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными
// настройками порождается огромное количество файлов. Умолчальное ограничение на количество файлов - 500 штук,
// что нас не устраивает, поэтому немного снизим эту цифру до более приемлемых значений.
options.L0CompactionFileThreshold = 8
chatListDB, err = pebble.Open(dbPath, &options)
if err != nil {
return fmt.Errorf("Unable to open settings db, %s: %w\n", dbPath, err)
}
// Заполним слайс со списком чатов. Операция нудная.
iterator, err := chatListDB.NewIter(&pebble.IterOptions{})
if !iterator.First() {
return errors.New("Unable to iterate slice with chats")
}
// Попробуем извлечь список ключей и заполнить слайс с чатами.
for {
// Закончили итерировать по списку.
if !iterator.Valid() {
break
}
// Битая база?
if err := iterator.Error(); err != nil {
chatList = []string{}
return fmt.Errorf("Unable to get chat list: %w", err)
}
chatName := iterator.Key()
if string(chatName) == "" {
return errors.New("Got empty chat name when query " + chatListDBName)
}
chatList = append(chatList, string(chatName))
iterator.Next()
}
return err
}
// AppendChatListDB добавляет чат в список известных боту. Можно добавлять чаты не глядя, дубликатов не добавится.
// Bot API нам такую информацию не даёт.
func AppendChatListDB(chatID string) error {
var err error
if slices.Contains(chatList, chatID) {
return err
}
if err = StoreKV(chatListDB, chatID, "{}"); err != nil {
return fmt.Errorf("Unable to append %s to %s: %w", chatID, chatListDBName, err)
}
// TODO: fill in defaults!
// Не забываем актуализировать список чатов.
chatList = append(chatList, chatID)
slices.Sort(chatList)
return err
}
// GetSetting достаёт настройку из БД с настройками. // GetSetting достаёт настройку из БД с настройками.
func GetSetting(chatID string, setting string) string { func GetSetting(chatID string, setting string) string {
var err error var err error

View File

@@ -65,6 +65,9 @@ func SigHandler() {
} }
} }
log.Debug("Closing known chat list db")
_ = chatListDB.Close()
os.Exit(0) os.Exit(0)
} }
} }

View File

@@ -21,6 +21,8 @@ func telegramMsgParser(msg *echotron.Update) {
return return
} }
AppendChatListDB(strconv.FormatInt(msg.ChatID(), 10))
// Сообщение о том, что этот чятик изменился, например, превратился в супергруппу. // Сообщение о том, что этот чятик изменился, например, превратился в супергруппу.
if (msg.Message.MigrateFromChatID < 0) && (msg.Message.MigrateToChatID < 0) { if (msg.Message.MigrateFromChatID < 0) && (msg.Message.MigrateToChatID < 0) {
// TODO: поддержать миграцию настроек чата на новый chatId. // TODO: поддержать миграцию настроек чата на новый chatId.