From 568aaa9f8cd996916fce9410960e5cd173553358 Mon Sep 17 00:00:00 2001 From: Melora Hugues Date: Wed, 25 Jan 2023 21:06:00 +0100 Subject: [PATCH] Add publig IP getter Ref #3 This commit uses a GET query to ifconfig.me in order to get the current public IP. It also modifies the initialization message to send the current public IP to the Telegram channel instead of a simple message. --- tracker/bot/bot.go | 10 ++++++++- tracker/ip/ip.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tracker/ip/ip.go 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{} +}