#!/bin/bash

services=(
    "plain:https://ifconfig.me/ip"
    "plain:https://ipinfo.io/ip"
    "plain:https://icanhazip.com"
    "plain:https://api.ipify.org"
    "plain:https://ident.me"
    "plain:https://checkip.amazonaws.com"
    "plain:https://ipecho.net/plain"
    "plain:https://ipapi.co/ip"
    "plain:https://wtfismyip.com/text"
    "plain:https://ifconfig.co/ip"

    "dns:dig +short myip.opendns.com @resolver1.opendns.com"
    "dns:dig +short txt ch whoami.cloudflare @1.0.0.1"
    "dns:dig +short txt o-o.myaddr.l.google.com @ns1.google.com | awk -F'\"' '{print \$2}'"

    "json:https://jsonip.com"
    "json:https://ifconfig.co/json"
    "json:https://ipinfo.io/json"
    "json:https://ipapi.co/json"
    "json:https://api64.ipify.org?format=json"
)

ipv4_regex='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
ipv6_regex='^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(([0-9a-fA-F]{1,4}:){1,7}:)$|^(:{2}([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})$'

shuffle_services() {
    local i j temp
    for ((i=${#services[@]} - 1; i > 0; i--)); do
        j=$((RANDOM % (i + 1)))
        temp="${services[i]}"
        services[i]="${services[j]}"
        services[j]="$temp"
    done
}

is_valid_ip() {
    local ip=$1
    if [[ $ip =~ $ipv4_regex ]]; then
        IFS='.' read -r -a octets <<< "$ip"
        for octet in "${octets[@]}"; do
            if (( octet > 255 )); then
                return 1
            fi
        done
        return 0
    elif [[ $ip =~ $ipv6_regex ]]; then
        return 0
    else
        return 1
    fi
}

get_public_ip() {
    shuffle_services

    for entry in "${services[@]}"; do
        type="${entry%%:*}"
        service="${entry#*:}"

        if [[ "$type" == "plain" ]]; then
            response=$(curl -s --max-time 10 -w "%{http_code}" -H "Accept: text/plain" "$service")
            http_status="${response: -3}"
            public_ip="${response::-3}"
            public_ip="${public_ip//[$'\t\r\n ']}"  # Trim whitespace

            if [[ $public_ip =~ \<.*\> ]]; then
                logger -t getmyip "Service $service returned HTML content, skipping: $public_ip"
                continue
            fi

            if [[ "$http_status" == "200" ]] && is_valid_ip "$public_ip"; then
                logger -t getmyip "Service used: $service, IP found: $public_ip"
                echo "$public_ip"
                return
            else
                logger -t getmyip "Service $service failed or invalid IP returned"
            fi

        elif [[ "$type" == "json" ]]; then
            response=$(curl -s --max-time 10 -H "Accept: application/json" "$service")
            if command -v jq >/dev/null 2>&1; then
                public_ip=$(echo "$response" | jq -r '.ip // .query // empty')
            else
                public_ip=$(echo "$response" | grep -oP '(?<="ip"\s*:\s*")[^"]+|(?<="query"\s*:\s*")[^"]+')
            fi
            public_ip="${public_ip//[$'\t\r\n ']}"  # Trim whitespace

            if is_valid_ip "$public_ip"; then
                logger -t getmyip "JSON service used: $service, IP found: $public_ip"
                echo "$public_ip"
                return
            else
                logger -t getmyip "JSON service $service returned invalid IP or no IP found"
            fi

        elif [[ "$type" == "dns" ]]; then
            fallback_ip=$(eval "$service")
            fallback_ip="${fallback_ip//\"/}"
            fallback_ip="${fallback_ip//[$'\t\r\n ']}"
            if is_valid_ip "$fallback_ip"; then
                logger -t getmyip "DNS service used: $service, IP found: $fallback_ip"
                echo "$fallback_ip"
                return
            else
                logger -t getmyip "DNS service $service failed or returned invalid IP"
            fi
        fi
    done

    echo "Failed to retrieve public IP using all services." >&2
    logger -t getmyip "Error: Failed to retrieve public IP using all services."
    exit 1
}

get_public_ip
