diff --git a/tracker/bot/bot.go b/tracker/bot/bot.go index ad9f0b5..58ac78a 100644 --- a/tracker/bot/bot.go +++ b/tracker/bot/bot.go @@ -6,6 +6,7 @@ import ( "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" ) @@ -14,10 +15,16 @@ type Notifier struct { cancel context.CancelFunc tgBot *bot.ConcreteBot notifChannel int64 + ipGetter ip.Getter } func (n *Notifier) SendInitMessage() error { - initMsg := fmt.Sprintf("Public IP tracked initialized at %v", time.Now()) + publicIP, err := n.ipGetter.GetCurrentPublicIP(n.ctx) + if err != nil { + return fmt.Errorf("failed to get current public IP: %w", err) + } + + initMsg := fmt.Sprintf("Public IP tracker initialized at %v, public IP is %s", time.Now(), publicIP) if err := n.tgBot.SendMessage(n.ctx, n.notifChannel, initMsg); err != nil { return fmt.Errorf("failed to send initialization message: %w", err) } @@ -33,5 +40,6 @@ func New(ctx context.Context, config *config.Config) *Notifier { cancel: cancel, tgBot: tgBot, notifChannel: config.Telegram.ChannelID, + ipGetter: ip.New(), } } diff --git a/tracker/ip/ip.go b/tracker/ip/ip.go new file mode 100644 index 0000000..fa51575 --- /dev/null +++ b/tracker/ip/ip.go @@ -0,0 +1,52 @@ +package ip + +import ( + "bytes" + "context" + "fmt" + "io" + "net" + "net/http" +) + +const ifconfigURL = "https://ifconfig.me" +const httpMaxRead = 100 + +// TODO: use a struct, but a baseHTTPClient inside it to perform unit tests +type Getter interface { + GetCurrentPublicIP(ctx context.Context) (net.IP, error) +} + +type concreteIPGetter struct { +} + +func (c *concreteIPGetter) GetCurrentPublicIP(ctx context.Context) (net.IP, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, ifconfigURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to prepare public IP request: %w", err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to get current IP from ifconfig: %w", err) + } + + if resp.ContentLength > httpMaxRead { + return nil, fmt.Errorf("response too big: %d/%d", resp.ContentLength, httpMaxRead) + } + + buf := bytes.NewBuffer([]byte{}) + if _, err := io.CopyN(buf, resp.Body, resp.ContentLength); err != nil { + return nil, fmt.Errorf("error parsing body: %w", err) + } + + content := string(buf.Bytes()) + res := net.ParseIP(content) + if res == nil { + return nil, fmt.Errorf("got an invalid public IP %q", content) + } + return res, nil +} + +func New() *concreteIPGetter { + return &concreteIPGetter{} +}