package telegram import ( "context" "log/slog" "sync/atomic" "time" "github.com/alex-savin/go-receipt-tracker/bus" "github.com/alex-savin/go-receipt-tracker/config" tele "gopkg.in/telebot.v4" ) type Bot struct { id string // opts *config.Telegram // client *tele.Bot // subscriptions map[string]chan bus.Event // bus *bus.Bus // log *slog.Logger // cancel context.CancelFunc // end uint32 // ensure the close methods are only called once } func NewTelegramBot(cfg *config.Telegram, bus *bus.Bus) (*Bot, error) { bot := &Bot{ id: "telegram", opts: cfg, bus: bus, } return bot, nil } func (b *Bot) ID() string { return b.id } func (b *Bot) Type() string { return "telegram" } func (b *Bot) Init(log *slog.Logger) error { var err error b.log = log pref := tele.Settings{ Token: b.opts.ApiKey, Poller: &tele.LongPoller{ Timeout: 10 * time.Second, }, } b.client, err = tele.NewBot(pref) if err != nil { b.log.Debug("cannot create telegram bot", "error", err) return err } b.client.Handle(tele.OnText, func(c tele.Context) error { return c.Send("") }) b.client.Handle(tele.OnPhoto, func(c tele.Context) error { photo := c.Message().Photo b.client.Download(&photo.File, "./"+photo.UniqueID+".jpg") msg := &bus.Message{ Image: &bus.Image{ ID: photo.UniqueID, Filename: b.opts.StoragePath + "/" + photo.UniqueID + ".jpg", }, ReplyType: "send", TbContext: c, } err := b.bus.Publish("processor:image", msg) if err != nil { b.log.Error("couldn't publish to the channel", "channel", "processor:image", "error", err.Error()) } return c.Send("") }) b.client.Handle(tele.OnCallback, func(c tele.Context) error { b.log.Debug("CALLBACK (BEFORE) %s // %s", c.Callback().ID, c.Callback().Data) tbc := new(TelegramBotCommand) err := tbc.Unmarshal(c.Callback().Data) if err != nil { panic(err) } b.log.Debug("CALLBACK (AFTER) %d|%d|%d|%d|%d|%d|%s", tbc.Cmd, tbc.ID, tbc.RefCmd, tbc.RefID, tbc.TelegramID, tbc.Pagination, tbc.Extra) switch tbc.Cmd { case btnCmd["user_add"]: return c.Send("") case btnCmd["receipt_add"]: return c.Send("") case btnCmd["receipt_edit"]: return c.Send("") case btnCmd["receipts_list"]: case btnCmd["items_list"]: } cbr := &tele.CallbackResponse{ CallbackID: c.Callback().ID, Text: "No unique", ShowAlert: false, } return c.Respond(cbr) }) b.client.Handle("/start", func(c tele.Context) error { err = b.bus.Publish("processor:start", nil) if err != nil { b.log.Error("couldn't publish to a channel", "channel", "telegram:publish", "error", err.Error()) } return c.Send("") }) b.client.Handle("/receipts", func(c tele.Context) error { return c.Send("") }) b.client.Handle("/reports", func(c tele.Context) error { return c.Send("") }) return nil } func (b *Bot) Serve() { if atomic.LoadUint32(&b.end) == 1 { return } b.subscribe("messenger:" + b.ID()) b.client.Start() ctx, cancel := context.WithCancel(context.Background()) b.cancel = cancel go b.eventLoop(ctx) // if atomic.LoadUint32(&b.end) == 0 { // go func() { // if !w.mqtt.IsConnected() { // b.bus.Publish("app:connected", &bus.ConnectionStatus{WorkerID: b.ID(), WorkerType: b.Type(), IsConnected: false}) // b.log.Warn(b.ID() + " client is disconnected") // } // }() // } } func (b *Bot) Stop() { b.client.Stop() } func (b *Bot) subscribe(chn string) error { s, err := b.bus.Subscribe(chn, b.Type()+":"+b.ID()) if err != nil { b.log.Error("couldn't subscribe to a channel", "channel", chn, "error", err.Error()) return err } b.subscriptions[chn] = s return nil } // eventLoop loops forever func (b *Bot) eventLoop(ctx context.Context) { b.log.Debug(b.ID() + " communication event loop started") defer b.log.Debug(b.ID() + " communication event loop halted") for { for chn, ch := range b.subscriptions { select { case event := <-ch: b.log.Debug("got a new message to a channel", "channel", chn) for _, msg := range event.Payload.([]*bus.Message) { b.log.Debug("publishing telegram message", "topic", msg) // w.mqtt.Publish(message.Topic, message.QOS, message.Retained, message.Payload) } case <-ctx.Done(): b.log.Info("stopping " + b.ID() + " communication event loop") return } } } // for { // select { // case event := <-chMQTTPublishStatus: // for _, message := range event.Data.([]*bus.Message) { // b.log.Debug("publishing mqtt message", "topic", message.Topic, "qos", message.QOS, "retained", message.Retained, "payload", message.Payload) // // w.mqtt.Publish(message.Topic, message.QOS, message.Retained, message.Payload) // } // case event := <-chMQTTSubscribeCommand: // for _, message := range event.Data.([]*bus.Message) { // b.log.Debug("subscribing to a topic", "topic", message.Topic, "qos", message.QOS) // // w.mqtt.SubscribeMultiple() // // b.mqtt.Subscribe(message.Topic, message.QOS, w.mqttHandlers.publish) // } // case <-ctx.Done(): // b.log.Info("stopping mqtt communication event loop") // return // } // } }