package moon import ( "bunny-bot/internal/elog" "bunny-bot/internal/mylog" "github.com/NicoNex/echotron/v3" "github.com/davecgh/go-spew/spew" ) // Telegram main goroutine that implements bot. // // It blocks until a termination signal stops it, so the update listener can be // shut down in a defined order rather than simply abandoned when the process // exits. func Telegram(cfg MyConfig) { // A signal can arrive during startup; then this must not start at all. if !startWorker() { mylog.Info("Telegram not started: shutdown already in progress") return } defer stopWorker() Tg = echotron.NewAPI(cfg.Token) mylog.Info("Starting api update poller loop") // The sweeper calls the API too, so it may only start once Tg exists. mylog.Info("Starting greeting sweeper periodic task") go SweepGreetings() updates := echotron.PollingUpdatesOptions(cfg.Token, true, echotron.UpdateOptions{ AllowedUpdates: allowedUpdates, }) for { select { // Stop between updates, never inside one: the handler currently running // finishes, so nothing is left half-processed when we return. case <-stopRequested: mylog.Info("Telegram update listener stopped") return case u, ok := <-updates: if !ok { mylog.Info("Telegram update poller ended") return } telegramMsgParser(u) } } } // telegramMsgParser parses events generated by echotron. func telegramMsgParser(msg *echotron.Update) { // The one place an update enters the program, and therefore the one place an // entity with no id is caught. Everything downstream — the caches, the sweeper, // the bans — may then assume the ids it holds are real. if !usableUpdate(msg) { reportBrokenUpdate(msg) return } switch { // Membership status changes: joins, leaves, bans, promotions. Logged only — // moderation is the sweeper's job, on a timer, not the dispatcher's. // // This also catches the membership updates caused by our own bans, so every // ban leaves a second record in the chat log. case msg.ChatMember != nil: elog.LogUserStatus(msg.ChatMember) // New members entered to chat. Checked before the text case so that a // join service message is never mistaken for ordinary chatter. case msg.Message != nil && msg.Message.NewChatMembers != nil: newChatMembers(msg) // Ordinary text messages, used as greeting answers. case msg.Message != nil && msg.Message.Text != "": textMessage(msg) // Everything else that is still a message: a photo with no caption, a // sticker, a location, a leave, a title change. Logged, and deliberately NOT // recorded as a greeting answer — an attachment is not an introduction, and // letting one satisfy the detector would be a one-key bypass for a spammer. // // The condition is deliberately just "there is a message": elog already // renders every media and service field, and already writes nothing for a // message with no describable content, so emptiness is its decision to make. // Testing for a field being non-empty here is what lost these messages. case msg.Message != nil: logOnlyMessage(msg) // An edit revises a message already recorded, so it is a chat-log event // rather than a moderation one. case msg.EditedMessage != nil: elog.LogEditedMessage(msg.EditedMessage) default: mylog.Debug(spew.Sdump(msg)) } } /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */