From cb2ea3604b77d9cd2bba404490ed0f414e2396df Mon Sep 17 00:00:00 2001 From: eleksir Date: Sun, 2 Nov 2025 15:57:07 +0300 Subject: [PATCH] [WIP] Implement good morning message for all willing chats, update TODO.md --- TODO.md | 3 +- cmd/aleesa-telegram-go/main.go | 9 +++- internal/tg/globals.go | 11 ++++- internal/tg/periodicJobs.go | 57 +++++++++++++++++++++++- internal/tg/settings-db-util.go | 76 ++++++++++++++++++++++++++++++++ internal/tg/telegramMsgParser.go | 2 + 6 files changed, 153 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index a21e0db..0f160e2 100644 --- a/TODO.md +++ b/TODO.md @@ -25,7 +25,7 @@ 1 [V] [V] цензор сообщений определённого типа. - 2 [ ] [V] фортунка каждое утро. (+ добавить время, относительно gmt, когда показывать фортунку). См. п.13. + 2 [V] [V] фортунка каждое утро. См. п.13. 3 [V] [V] Приветствие новых участников @@ -68,6 +68,7 @@ [V] регулярных компакций базы с настройками. [ ] регулярного обслуживания таблиц с пользователями. +14. [ ] в функции AppendChatListDB для нового чата добавлять дефолтные значения настроек. diff --git a/cmd/aleesa-telegram-go/main.go b/cmd/aleesa-telegram-go/main.go index 3206e45..f503f5d 100644 --- a/cmd/aleesa-telegram-go/main.go +++ b/cmd/aleesa-telegram-go/main.go @@ -89,13 +89,20 @@ func main() { go tg.SigHandler() go tg.Telega(tg.Config) - // Periodically compact/flush pebble dbs. + // Периодически обслуживаем базы с настройками чатов. if job, err := scheduler.Every(1).Hours().NotImmediately().Run(tg.TidySettingsDB); err != nil { log.Errorf("Unable to schedule periodic settings db flush: %s", err) } else { 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 { if tg.Shutdown { diff --git a/internal/tg/globals.go b/internal/tg/globals.go index db8c4dc..d622542 100644 --- a/internal/tg/globals.go +++ b/internal/tg/globals.go @@ -35,10 +35,19 @@ var ( // settingsDB мапка с открытыми дескрипторами баз с настройками. 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 echotron.API - // introduceGreet приветсвенные фразы для новых участников чата. + // introduceGreet приветственные фразы для новых участников чата. introduceGreet = [...]string{ "Дратути", "Дарована", diff --git a/internal/tg/periodicJobs.go b/internal/tg/periodicJobs.go index eaf26a8..4f8d418 100644 --- a/internal/tg/periodicJobs.go +++ b/internal/tg/periodicJobs.go @@ -2,11 +2,14 @@ package tg import ( "aleesa-telegram-go/internal/log" + "encoding/json" + "strconv" ) var ( - // TidySettingsDB flushes regularly pebble dbs with settings. Remmended action to keeb that db in a good shape. - // Meant to be run about hourly or something like that. + // TidySettingsDB флашит на диск базу с настройками чатов. Рекомендуется делать это время от времени для поддержания + // производительности базы на должном уровне. + // Предполагается, что это происходит где-то раз в час, чаще смысла нет. TidySettingsDB = func() { if len(settingsDB) > 0 { 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)) + } + } + } } ) diff --git a/internal/tg/settings-db-util.go b/internal/tg/settings-db-util.go index 98374b1..fdc761f 100644 --- a/internal/tg/settings-db-util.go +++ b/internal/tg/settings-db-util.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/fs" + "slices" "aleesa-telegram-go/internal/log" @@ -45,6 +46,81 @@ func FetchV(db *pebble.DB, key string) (string, error) { 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 достаёт настройку из БД с настройками. func GetSetting(chatID string, setting string) string { var err error diff --git a/internal/tg/telegramMsgParser.go b/internal/tg/telegramMsgParser.go index a4b7955..20fe869 100644 --- a/internal/tg/telegramMsgParser.go +++ b/internal/tg/telegramMsgParser.go @@ -21,6 +21,8 @@ func telegramMsgParser(msg *echotron.Update) { return } + AppendChatListDB(strconv.FormatInt(msg.ChatID(), 10)) + // Сообщение о том, что этот чятик изменился, например, превратился в супергруппу. if (msg.Message.MigrateFromChatID < 0) && (msg.Message.MigrateToChatID < 0) { // TODO: поддержать миграцию настроек чата на новый chatId.