// Package geoip provides IP address geolocation features. package geoip import ( "errors" "net" "net/http" "strings" "code.nonshy.com/nonshy/website/pkg/config" "code.nonshy.com/nonshy/website/pkg/utility" "github.com/oschwald/geoip2-golang" ) // GetRequestCity returns the GeoIP City result for the current HTTP request. func GetRequestCity(r *http.Request) (*geoip2.City, error) { var ( addr = utility.IPAddress(r) ip = net.ParseIP(addr) ) return GetCity(ip) } // GetRequestCountryFlag returns the country flag based on the current HTTP request IP address. func GetRequestCountryFlag(r *http.Request) (string, error) { city, err := GetRequestCity(r) if err != nil { // If the remote addr is localhost (local dev testing), default to US flag. if addr := utility.IPAddress(r); addr == "127.0.0.1" || addr == "::1" { return CountryFlagEmoji("US") } return "", err } return CountryFlagEmoji(city.Country.IsoCode) } // GetRequestCountryFlagWithCode returns the flag joined with the country code by a space (like CountryFlagEmojiWithCode). func GetRequestCountryFlagWithCode(r *http.Request) (string, error) { city, err := GetRequestCity(r) if err != nil { // If the remote addr is localhost (local dev testing), default to US flag. if addr := utility.IPAddress(r); addr == "127.0.0.1" || addr == "::1" { return CountryFlagEmojiWithCode("US") } return "", err } return CountryFlagEmojiWithCode(city.Country.IsoCode) } // GetCity queries the GeoIP database for city information for an IP address. func GetCity(ip net.IP) (*geoip2.City, error) { db, err := geoip2.Open(config.GeoIPPath) if err != nil { return nil, err } return db.City(ip) } // CountryFlagEmoji returns the emoji sequence for a country flag based on // the two-letter country code. func CountryFlagEmoji(alpha2 string) (string, error) { if len(alpha2) != 2 { return "", errors.New("country code must be two letters long") } alpha2 = strings.ToLower(alpha2) var ( flagBaseIndex = '\U0001F1E6' - 'a' box = func(ch byte) string { return string(rune(ch) + flagBaseIndex) } ) return string(box(alpha2[0]) + box(alpha2[1])), nil } // CountryFlagEmojiWithCode returns a string consisting of the country flag, a space, and the alpha2 code passed in. func CountryFlagEmojiWithCode(alpha2 string) (string, error) { if emoji, err := CountryFlagEmoji(alpha2); err != nil { return emoji, err } else { return emoji + " " + alpha2, nil } }