Updated to 3 providers to add more reliability

This commit is contained in:
DeveloperDurp 2025-03-16 08:58:40 -05:00
parent 06c9569e82
commit 03b36127db

249
main.go
View file

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v6"
"github.com/cloudflare/cloudflare-go/v4" "github.com/cloudflare/cloudflare-go/v4"
@ -12,11 +13,14 @@ import (
"io" "io"
"log" "log"
"net/http" "net/http"
"regexp"
"time" "time"
) )
type IPResponse struct { var ipPattern = `^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`
IPAddress string `json:"YourFuckingIPAddress"`
type WTFIPResponse struct {
IP string `json:"YourFuckingIPAddress"`
Location string `json:"YourFuckingLocation"` Location string `json:"YourFuckingLocation"`
Hostname string `json:"YourFuckingHostname"` Hostname string `json:"YourFuckingHostname"`
ISP string `json:"YourFuckingISP"` ISP string `json:"YourFuckingISP"`
@ -26,10 +30,111 @@ type IPResponse struct {
CountryCode string `json:"YourFuckingCountryCode"` CountryCode string `json:"YourFuckingCountryCode"`
} }
type IpifyResponse struct {
IP string `json:"ip"`
}
type IpInfoResponse struct {
IP string `json:"ip"`
Hostname string `json:"hostname"`
City string `json:"city"`
Region string `json:"region"`
Country string `json:"country"`
Loc string `json:"loc"`
Org string `json:"org"`
Postal string `json:"postal"`
Timezone string `json:"timezone"`
Readme string `json:"readme"`
}
type Response struct {
IPAddress string
}
type ResponseInterface interface {
getIP() Response
}
func (r *WTFIPResponse) getIP() Response {
return Response{IPAddress: r.IP}
}
func (r *IpInfoResponse) getIP() Response {
return Response{IPAddress: r.IP}
}
func (r *IpifyResponse) getIP() Response {
return Response{IPAddress: r.IP}
}
func NewIpifyProvider() *IpProvider {
return &IpProvider{
url: "https://api.ipify.org?format=json",
resp: new(IpifyResponse),
client: http.Client{
Timeout: 5 * time.Second,
},
}
}
func NewIpInfoProvider() *IpProvider {
return &IpProvider{
url: "https://ipinfo.io/json",
resp: new(IpInfoResponse),
client: http.Client{
Timeout: 5 * time.Second,
},
}
}
func NewWTFProvider() *IpProvider {
return &IpProvider{
url: "https://myip.wtf/json",
resp: new(WTFIPResponse),
client: http.Client{
Timeout: 5 * time.Second,
},
}
}
type IpProvider struct {
url string
resp interface{}
client http.Client
}
func (p *IpProvider) get() *Response {
body, err := p.client.Get(p.url)
if err != nil {
return nil
}
defer body.Body.Close()
data, err := io.ReadAll(body.Body)
if err != nil {
return nil
}
err = json.Unmarshal(data, &p.resp)
if err != nil {
return nil
}
resp := p.resp.(ResponseInterface).getIP()
pattern := regexp.MustCompile(ipPattern)
if !pattern.MatchString(resp.IPAddress) {
return nil
}
return &resp
}
type Config struct { type Config struct {
CloudflareZoneID string `env:"CLOUDFLARE_ZONE_ID"` CloudflareZoneID string `env:"CLOUDFLARE_ZONE_ID"`
CloudFlareAPIKey string `env:"CLOUDFLARE_API_KEY"` CloudFlareAPIKey string `env:"CLOUDFLARE_API_KEY"`
CloudflareEmail string `env:"CLOUDFLARE_EMAIL"` CloudflareEmail string `env:"CLOUDFLARE_EMAIL"`
IpProviders []*IpProvider
Client *cloudflare.Client
Timeout time.Duration
} }
func main() { func main() {
@ -40,7 +145,9 @@ func main() {
return return
} }
config := &Config{} config := &Config{
Timeout: 5 * time.Second,
}
err = env.Parse(config) err = env.Parse(config)
if err != nil { if err != nil {
log.Fatal("Failed to load env file") log.Fatal("Failed to load env file")
@ -52,81 +159,107 @@ func main() {
return return
} }
client := cloudflare.NewClient( config.Client = cloudflare.NewClient(
option.WithAPIKey(config.CloudFlareAPIKey), option.WithAPIKey(config.CloudFlareAPIKey),
option.WithAPIEmail(config.CloudflareEmail), option.WithAPIEmail(config.CloudflareEmail),
) )
ExistingIP, err := getIP() config.IpProviders = append(
if err != nil { config.IpProviders,
fmt.Println(err) NewWTFProvider(),
return )
} config.IpProviders = append(
config.IpProviders,
NewIpifyProvider(),
)
config.IpProviders = append(
config.IpProviders,
NewIpInfoProvider(),
)
ExistingIP, err := GetIPAddress(config)
fmt.Printf("The current IP address is: %s\n", ExistingIP)
err = updateDNSRecords( err = updateDNSRecords(
client, config,
config.CloudflareZoneID, ExistingIP,
ExistingIP.IPAddress,
) )
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
fmt.Println("Your public IP address is:", ExistingIP.IPAddress)
do: do:
for { for {
CurrentIP, err := getIP() CurrentIP, err := GetIPAddress(config)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
continue do continue do
} }
if ExistingIP.IPAddress != CurrentIP.IPAddress { if ExistingIP == CurrentIP {
ExistingIP = CurrentIP time.Sleep(config.Timeout)
fmt.Println( continue do
"Your public IP address has updated:",
ExistingIP.IPAddress,
)
err := updateDNSRecords(
client,
config.CloudflareZoneID,
CurrentIP.IPAddress,
)
if err != nil {
fmt.Println(err)
break
}
} else {
fmt.Println("Your public IP address has not changed.")
} }
time.Sleep(10 * time.Second) ExistingIP = CurrentIP
fmt.Printf(
"Your IP address has changed: %s\n",
CurrentIP,
)
err = updateDNSRecords(
config,
CurrentIP,
)
if err != nil {
fmt.Println(err)
}
time.Sleep(config.Timeout)
continue do continue do
} }
} }
func getIP() (*IPResponse, error) { func GetIPAddress(config *Config) (string, error) {
client := &http.Client{ responses := []*Response{}
Timeout: time.Second * 5,
}
var ip IPResponse
resp, err := client.Get("https://myip.wtf/json")
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body) for _, provider := range config.IpProviders {
if err != nil { resp := provider.get()
return nil, err responses = append(responses, resp)
} }
err = json.Unmarshal(data, &ip) // Count the occurrences of each IPAddress
return &ip, err counts := map[string]int{}
for _, r := range responses {
if r != nil {
IP := r.IPAddress
if count, ok := counts[IP]; ok {
counts[IP] = count + 1
} else {
counts[IP] = 1
}
}
}
// Find the IPAddress that repeats the most
maxCount := 0
CurrentIP := ""
for IP, count := range counts {
if count > maxCount {
maxCount = count
CurrentIP = IP
}
}
pattern := regexp.MustCompile(ipPattern)
if !pattern.MatchString(CurrentIP) {
return "", errors.New("Did not find a valid IP")
}
return CurrentIP, nil
} }
func getDNSRecords( func getDNSRecords(
@ -154,17 +287,23 @@ func getDNSRecords(
} }
func updateDNSRecords( func updateDNSRecords(
client *cloudflare.Client, config *Config,
zoneID string,
ipAddress string, ipAddress string,
) error { ) error {
records, err := getDNSRecords(client, zoneID) records, err := getDNSRecords(
config.Client,
config.CloudflareZoneID,
)
if err != nil { if err != nil {
return err return err
} }
for _, record := range *records { for _, record := range *records {
if record.Content == ipAddress {
continue
}
NewDNS := dns.ARecordParam{ NewDNS := dns.ARecordParam{
Comment: cloudflare.F(record.Comment), Comment: cloudflare.F(record.Comment),
Content: cloudflare.F(ipAddress), Content: cloudflare.F(ipAddress),
@ -174,9 +313,9 @@ func updateDNSRecords(
Type: cloudflare.F(dns.ARecordTypeA), Type: cloudflare.F(dns.ARecordTypeA),
} }
_, err = client.DNS.Records.Edit( _, err = config.Client.DNS.Records.Edit(
context.TODO(), record.ID, dns.RecordEditParams{ context.TODO(), record.ID, dns.RecordEditParams{
ZoneID: cloudflare.F(zoneID), ZoneID: cloudflare.F(config.CloudflareZoneID),
Record: NewDNS, Record: NewDNS,
}, },
) )