diff --git a/templates/where.html b/templates/where.html new file mode 100644 index 0000000..0a5f2cc --- /dev/null +++ b/templates/where.html @@ -0,0 +1,32 @@ +{{define "where"}} + + + + $ where + + + + + + + +

$ where

↑ up + +

Last Updated:

+ + {{range .Users}} +
+

~{{.Name}}

{{Location .Region .Country}} +
+ {{end}} + + + +{{end}} diff --git a/where/where.go b/where/where.go new file mode 100644 index 0000000..ca06f36 --- /dev/null +++ b/where/where.go @@ -0,0 +1,161 @@ +package main + +import ( + "encoding/json" + "strings" + "bufio" + "os" + "io/ioutil" + "os/exec" + "fmt" + "regexp" + "net/http" + "flag" + "time" + "text/template" +) + +func main() { + // Get arguments + outFilePtr := flag.String("f", "where", "Outputted HTML filename (without .html)") + flag.Parse() + + // Get online users with `who` + users := who() + + // Fetch user locations based on IP address + for i := range users { + getGeo(&users[i]) + } + + // Generate page + generate(users, *outFilePtr) +} + +type User struct { + Name string + IP string + Region string + Country string +} + +var ipRegex = regexp.MustCompile("(([0-9]{1,3}[.-]){3}[0-9]{1,3})") + +func who() []User { + fmt.Println("who --ips") + + cmd := exec.Command("who", "--ips") + stdout, err := cmd.StdoutPipe() + if err != nil { + fmt.Println(err) + } + if err := cmd.Start(); err != nil { + fmt.Println(err) + } + + ips := make(map[string]string) + + r := bufio.NewReader(stdout) + scanner := bufio.NewScanner(r) + for scanner.Scan() { + lineParts := strings.Split(scanner.Text(), " ") + name := lineParts[0] + + fmt.Println(name) + + // Extract IP address + ipMatch := ipRegex.FindAllString(scanner.Text(), 1) + if len(ipMatch) == 0 { + continue + } + + // Normalize any host names with dashes + newIp := strings.Replace(ipMatch[0], "-", ".", -1) + + ips[newIp] = name + } + + users := make([]User, len(ips)) + i := 0 + for ip, name := range ips { + users[i] = User{Name: name, IP: ip} + i++ + } + + return users +} + +func getGeo(u *User) { + fmt.Printf("Fetching %s location...\n", u.Name) + + response, err := http.Get(fmt.Sprintf("https://freegeoip.net/json/%s", u.IP)) + if err != nil { + fmt.Printf("%s", err) + os.Exit(1) + } else { + defer response.Body.Close() + contents, err := ioutil.ReadAll(response.Body) + if err != nil { + fmt.Printf("%s", err) + os.Exit(1) + } + + var dat map[string]interface{} + + if err := json.Unmarshal(contents, &dat); err != nil { + fmt.Println(err) + return + } + region := dat["region_name"].(string) + country := dat["country_name"].(string) + + u.Region = region + u.Country = country + } +} + +func prettyLocation(region, country string) string { + if region != "" { + return fmt.Sprintf("%s, %s", region, country) + } + return country +} + +type Page struct { + Users []User + Updated string + UpdatedForHumans string +} + +func generate(users []User, outputFile string) { + fmt.Println("Generating page.") + + f, err := os.Create(os.Getenv("HOME") + "/public_html/" + strings.ToLower(outputFile) + ".html") + if err != nil { + panic(err) + } + + defer f.Close() + + funcMap := template.FuncMap { + "Location": prettyLocation, + } + + w := bufio.NewWriter(f) + template, err := template.New("").Funcs(funcMap).ParseFiles("../templates/where.html") + if err != nil { + panic(err) + } + + // Extra page data + curTime := time.Now().UTC() + updatedReadable := curTime.Format(time.RFC1123) + updated := curTime.Format(time.RFC3339) + + // Generate the page + page := &Page{Users: users, UpdatedForHumans: updatedReadable, Updated: updated} + template.ExecuteTemplate(w, "where", page) + w.Flush() + + fmt.Println("DONE!") +}