package bot import ( "context" "fmt" "net" "time" "git.faercol.me/faercol/public-ip-tracker/tracker/config" "git.faercol.me/faercol/public-ip-tracker/tracker/ip" "github.com/ahugues/go-telegram-api/bot" "github.com/ahugues/go-telegram-api/notifier" "github.com/ahugues/go-telegram-api/structs" ) type Notifier struct { ctx context.Context cancel context.CancelFunc tgBot bot.Bot tgChatID int64 tgWatcher notifier.EventNotifier ipGetter ip.IPGetter timeGetter func() time.Time currentIP net.IP frequency time.Duration errChan chan error changesChan chan net.IP exitChan chan struct{} } func (n *Notifier) SendInitMessage() error { publicIP, err := n.ipGetter.GetCurrentPublicIP(n.ctx) if err != nil { return fmt.Errorf("failed to get current public IP: %w", err) } n.currentIP = publicIP currentTime := n.timeGetter() initMsg := fmt.Sprintf("Public IP tracker initialized at %v, public IP is %s", currentTime, publicIP) if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, initMsg); err != nil { return fmt.Errorf("failed to send initialization message: %w", err) } return nil } func (n *Notifier) sendUpdatedIPMsg() error { updateMsg := fmt.Sprintf("Public IP has been changed, is now %s", n.currentIP) if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, updateMsg); err != nil { return fmt.Errorf("failed to send update message: %w", err) } return nil } func (n *Notifier) sendCurrentIP() error { statusMsg := fmt.Sprintf("Current public IP is %s", n.currentIP) if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, statusMsg); err != nil { return fmt.Errorf("failed to send message: %w", err) } return nil } func (n *Notifier) watchTG() { id, updateChan := n.tgWatcher.Subscribe([]structs.UpdateType{structs.UpdateMessage}) go n.tgWatcher.Run(n.ctx) for { select { case update := <-updateChan: if update.Message.Text == "/getIP" { if err := n.sendCurrentIP(); err != nil { n.errChan <- fmt.Errorf("failed to reply current public IP: %w", err) } } case <-n.ctx.Done(): n.tgWatcher.Unsubscribe(id) return } } } func (n *Notifier) Run() { go n.watchTG() for { select { case <-time.After(n.frequency): newIP, err := n.ipGetter.GetCurrentPublicIP(n.ctx) if err != nil { n.errChan <- fmt.Errorf("failed to update public IP: %w", err) continue } if !newIP.Equal(n.currentIP) { n.currentIP = newIP if err := n.sendUpdatedIPMsg(); err != nil { n.errChan <- err } } case <-n.ctx.Done(): n.exitChan <- struct{}{} return } } } func (n *Notifier) ErrChan() <-chan error { return n.errChan } func (n *Notifier) Exit() <-chan struct{} { return n.exitChan } func New(ctx context.Context, config *config.Config) *Notifier { subCtx, cancel := context.WithCancel(ctx) tgBot := bot.New(config.Telegram.Token) return &Notifier{ ctx: subCtx, cancel: cancel, tgBot: tgBot, tgChatID: config.Telegram.ChannelID, timeGetter: time.Now, ipGetter: ip.New(), errChan: make(chan error, 10), exitChan: make(chan struct{}, 1), frequency: config.PollingFrequency, tgWatcher: notifier.New(config.Telegram.Token), } }