diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c14a297 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +go.sum \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cb46462 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/owenrummage/ipinfo + +go 1.22.0 + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/urfave/cli/v2 v2.27.1 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/sys v0.14.0 // indirect +) diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..e4b83b2 --- /dev/null +++ b/src/main.go @@ -0,0 +1,161 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net" + "net/http" + "os" + + "strings" + "text/template" + + "github.com/fatih/color" + "github.com/urfave/cli/v2" +) + +type AddressInformation struct { + Ip string + City string + Region string + Country string + Log string + Org string + Postal string + Timezone string +} + +func format(s string, v interface{}) string { + t, b := new(template.Template), new(strings.Builder) + template.Must(t.Parse(s)).Execute(b, v) + return b.String() +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func IsIPv4(address string) bool { + return strings.Count(address, ":") < 2 +} + +func IsIPv6(address string) bool { + return strings.Count(address, ":") >= 2 +} + +func main() { + app := &cli.App{ + Name: "ipinfo", + Usage: "Get IP address information.", + Commands: []*cli.Command{ + { + Name: "local", + Aliases: []string{"l"}, + Usage: "get local IP address information", + Action: func(cCtx *cli.Context) error { + validNames := []string{"en0", "utun10"} + + ifaces, err := net.Interfaces() + if err != nil { + fmt.Print(fmt.Errorf("localAddresses: %+v\n", err.Error())) + return nil + } + + fmt.Println(color.MagentaString("REMOTE ADDRESSES:")) + resp, httpErr := http.Get("https://ifconfig.me/ip") + + if httpErr != nil { + fmt.Println(httpErr) + } + + body, ioErr := io.ReadAll(resp.Body) + + if ioErr != nil { + fmt.Println(ioErr) + } + + fmt.Println(" " + color.GreenString("WAN: ") + string(body)) + + fmt.Println(color.MagentaString("LOCAL INTERFACES:")) + for _, i := range ifaces { + if contains(validNames, i.Name) == false { + continue + } + + var v4Addr string + var v6Addr string + + addrs, err := i.Addrs() + if err != nil { + fmt.Print(fmt.Errorf("localAddresses: %+v\n", err.Error())) + continue + } + for _, a := range addrs { + + if IsIPv4(a.String()) { + v4Addr = a.String() + } + if IsIPv6(a.String()) { + v6Addr = a.String() + } + + // switch v := a.(type) { + // case *net.IPAddr: + // fmt.Printf("%v : %s (%s)\n", i.Name, v, v.IP.DefaultMask()) + // } + + } + + fmt.Printf(color.GreenString(" "+i.Name+": ")+" %s (%s)\n", v4Addr, v6Addr) + } + + return nil + }, + }, + { + Name: "address", + Aliases: []string{"a"}, + Usage: "lookup an IP address", + Action: func(cCtx *cli.Context) error { + if cCtx.Args().First() == "" { + fmt.Println("IP address argument is required.") + } + + resp, httpErr := http.Get("https://ipinfo.io/" + cCtx.Args().First()) + + if httpErr != nil { + fmt.Println(httpErr) + } + + body, ioErr := io.ReadAll(resp.Body) + + if ioErr != nil { + fmt.Println(ioErr) + } + + var infoObject AddressInformation + + jsonErr := json.Unmarshal(body, &infoObject) + + if jsonErr != nil { + fmt.Println(jsonErr) + } + + fmt.Printf(format(color.MagentaString("IPINFO - Address Information")+"\nAddress: {{.Ip}}\nLocation: {{.City}}, {{.Region}} {{.Country}} ({{.Postal}})\nOrganization: {{.Org}}", infoObject)) + return nil + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +}