2023-01-25 19:42:18 +00:00
|
|
|
package bot
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2023-01-28 13:29:39 +00:00
|
|
|
"net"
|
2023-02-04 18:07:04 +00:00
|
|
|
"strings"
|
2023-01-25 19:42:18 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.faercol.me/faercol/public-ip-tracker/tracker/config"
|
2023-01-25 20:06:00 +00:00
|
|
|
"git.faercol.me/faercol/public-ip-tracker/tracker/ip"
|
2023-02-04 17:14:23 +00:00
|
|
|
"git.faercol.me/faercol/public-ip-tracker/tracker/logger"
|
2023-01-25 19:42:18 +00:00
|
|
|
"github.com/ahugues/go-telegram-api/bot"
|
2023-01-29 17:50:43 +00:00
|
|
|
"github.com/ahugues/go-telegram-api/notifier"
|
|
|
|
"github.com/ahugues/go-telegram-api/structs"
|
2023-02-04 17:14:23 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2023-01-25 19:42:18 +00:00
|
|
|
)
|
|
|
|
|
2023-02-04 18:07:04 +00:00
|
|
|
func formatInitMsg(currentTime time.Time, publicIP net.IP, hostname string) string {
|
|
|
|
formattedHostname := fmt.Sprintf("`%s`", hostname)
|
|
|
|
formattedIP := strings.ReplaceAll(publicIP.String(), ".", "\\.")
|
|
|
|
return fmt.Sprintf(`\[Host %s\] %s
|
|
|
|
Public IP tracker initialized\. Current IP is %s`, formattedHostname, currentTime.Format(time.RFC1123), formattedIP)
|
|
|
|
}
|
|
|
|
|
|
|
|
func formatUpdate(currentTime time.Time, publicIP net.IP, hostname string) string {
|
|
|
|
formattedHostname := fmt.Sprintf("`%s`", hostname)
|
|
|
|
formattedIP := strings.ReplaceAll(publicIP.String(), ".", "\\.")
|
|
|
|
return fmt.Sprintf(`\[Host %s\] %s
|
|
|
|
Public IP has changed, new IP is %s`, formattedHostname, currentTime.Format(time.RFC1123), formattedIP)
|
|
|
|
}
|
|
|
|
|
2023-01-25 19:42:18 +00:00
|
|
|
type Notifier struct {
|
2023-01-28 13:29:39 +00:00
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
tgBot bot.Bot
|
|
|
|
tgChatID int64
|
2023-01-29 17:50:43 +00:00
|
|
|
tgWatcher notifier.EventNotifier
|
2023-01-28 13:29:39 +00:00
|
|
|
ipGetter ip.IPGetter
|
|
|
|
timeGetter func() time.Time
|
|
|
|
currentIP net.IP
|
|
|
|
frequency time.Duration
|
|
|
|
errChan chan error
|
|
|
|
changesChan chan net.IP
|
|
|
|
exitChan chan struct{}
|
2023-02-04 17:14:23 +00:00
|
|
|
logger logrus.Logger
|
2023-02-04 18:07:04 +00:00
|
|
|
hostname string
|
2023-01-25 19:42:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Notifier) SendInitMessage() error {
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debug("Getting current public IP")
|
2023-01-25 20:06:00 +00:00
|
|
|
publicIP, err := n.ipGetter.GetCurrentPublicIP(n.ctx)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get current public IP: %w", err)
|
|
|
|
}
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debugf("Current public IP is %s", publicIP.String())
|
2023-01-25 20:06:00 +00:00
|
|
|
|
2023-01-28 13:29:39 +00:00
|
|
|
n.currentIP = publicIP
|
|
|
|
currentTime := n.timeGetter()
|
|
|
|
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debug("Sending init message to Telegram")
|
2023-02-04 18:07:04 +00:00
|
|
|
if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, formatInitMsg(currentTime, publicIP, n.hostname), structs.FormattingMarkdownV2); err != nil {
|
2023-01-25 19:42:18 +00:00
|
|
|
return fmt.Errorf("failed to send initialization message: %w", err)
|
|
|
|
}
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debug("Message sent")
|
2023-01-25 19:42:18 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-28 13:29:39 +00:00
|
|
|
func (n *Notifier) sendUpdatedIPMsg() error {
|
2023-02-04 18:07:04 +00:00
|
|
|
if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, formatUpdate(n.timeGetter(), n.currentIP, n.hostname), structs.FormattingMarkdownV2); err != nil {
|
2023-01-28 13:29:39 +00:00
|
|
|
return fmt.Errorf("failed to send update message: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-29 17:50:43 +00:00
|
|
|
func (n *Notifier) sendCurrentIP() error {
|
|
|
|
statusMsg := fmt.Sprintf("Current public IP is %s", n.currentIP)
|
2023-02-04 18:07:04 +00:00
|
|
|
if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, statusMsg, structs.FormattingMarkdownV2); err != nil {
|
2023-01-29 17:50:43 +00:00
|
|
|
return fmt.Errorf("failed to send message: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Notifier) watchTG() {
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debug("Subscribing to messages notificator")
|
2023-01-29 17:50:43 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-28 13:29:39 +00:00
|
|
|
func (n *Notifier) Run() {
|
2023-01-29 17:50:43 +00:00
|
|
|
go n.watchTG()
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Infof("Start watching for public IP changes, polling frequency is %v", n.frequency)
|
2023-01-28 13:29:39 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-time.After(n.frequency):
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debug("Checking if current IP has changed")
|
2023-01-28 13:29:39 +00:00
|
|
|
newIP, err := n.ipGetter.GetCurrentPublicIP(n.ctx)
|
|
|
|
if err != nil {
|
|
|
|
n.errChan <- fmt.Errorf("failed to update public IP: %w", err)
|
|
|
|
continue
|
|
|
|
}
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debugf("Got new public IP %s", newIP.String())
|
2023-01-28 13:29:39 +00:00
|
|
|
if !newIP.Equal(n.currentIP) {
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Debug("New public IP is different from previous IP")
|
|
|
|
n.logger.Warnf("Public IP has changed from %s to %s", n.currentIP.String(), newIP.String())
|
2023-01-28 13:29:39 +00:00
|
|
|
n.currentIP = newIP
|
|
|
|
if err := n.sendUpdatedIPMsg(); err != nil {
|
|
|
|
n.errChan <- err
|
|
|
|
}
|
2023-02-04 17:14:23 +00:00
|
|
|
} else {
|
|
|
|
n.logger.Debug("Public IP has not changed")
|
2023-01-28 13:29:39 +00:00
|
|
|
}
|
|
|
|
case <-n.ctx.Done():
|
2023-02-04 17:14:23 +00:00
|
|
|
n.logger.Info("Stopping notification daemon")
|
2023-01-28 13:29:39 +00:00
|
|
|
n.exitChan <- struct{}{}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Notifier) ErrChan() <-chan error {
|
|
|
|
return n.errChan
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Notifier) Exit() <-chan struct{} {
|
|
|
|
return n.exitChan
|
|
|
|
}
|
|
|
|
|
2023-01-25 19:42:18 +00:00
|
|
|
func New(ctx context.Context, config *config.Config) *Notifier {
|
|
|
|
subCtx, cancel := context.WithCancel(ctx)
|
|
|
|
tgBot := bot.New(config.Telegram.Token)
|
|
|
|
|
|
|
|
return &Notifier{
|
2023-01-28 13:29:39 +00:00
|
|
|
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,
|
2023-01-29 17:50:43 +00:00
|
|
|
tgWatcher: notifier.New(config.Telegram.Token),
|
2023-02-04 17:14:23 +00:00
|
|
|
logger: logger.L,
|
2023-02-04 18:07:04 +00:00
|
|
|
hostname: config.Hostname,
|
2023-01-25 19:42:18 +00:00
|
|
|
}
|
|
|
|
}
|