2025-10-05 03:48:13 +03:00
#!/usr/bin/env bash
SCRIPT_URL = "https://github.com/vernette/ipregion"
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
SPINNER_SERVICE_FILE = $( mktemp " ${ TMPDIR :- /tmp } /ipregion_spinner_XXXXXX" )
DEBUG_LOG_FILE = "ipregion_debug_ $( date +%Y%m%d_%H%M%S) _ $$ .log"
SPOTIFY_API_KEY = "142b583129b2df829de3656f9eb484e6"
SPOTIFY_CLIENT_ID = "9a8d2f0ce77a4e248bb71fefcb557637"
NETFLIX_API_KEY = "YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm"
TWITCH_CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko"
CHATGPT_STATSIG_API_KEY = "client-zUdXdSTygXJdzoE0sWTkP8GKTVsUMF2IRM7ShVO2JAG"
REDDIT_BASIC_ACCESS_TOKEN = "b2hYcG9xclpZdWIxa2c6"
YOUTUBE_SOCS_COOKIE = "CAISNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIwMjUwNzMwLjA1X3AwGgJlbiACGgYIgPC_xAY"
DISNEY_PLUS_API_KEY = "ZGlzbmV5JmFuZHJvaWQmMS4wLjA.bkeb0m230uUhv8qrAXuNu39tbE_mD5EEhM_NAcohjyA"
DISNEY_PLUS_JSON_BODY = '{"query":"\n mutation registerDevice($registerDevice: RegisterDeviceInput!) {\n registerDevice(registerDevice: $registerDevice) {\n __typename\n }\n }\n ","variables":{"registerDevice":{"applicationRuntime":"android","attributes":{"operatingSystem":"Android","operatingSystemVersion":"13"},"deviceFamily":"android","deviceLanguage":"en","deviceProfile":"phone","devicePlatformId":"android"}},"operationName":"registerDevice"}'
VERBOSE = false
JSON_OUTPUT = false
GROUPS_TO_SHOW = "all"
CURL_TIMEOUT = 10
CURL_RETRIES = 1
IPV4_ONLY = false
IPV6_ONLY = false
PROXY_ADDR = ""
INTERFACE_NAME = ""
DEBUG = false
RESULT_JSON = ""
ARR_PRIMARY =()
ARR_CUSTOM =()
ARR_CDN =()
COLOR_HEADER = "1;36"
COLOR_SERVICE = "1;32"
COLOR_HEART = "1;31"
COLOR_URL = "1;90"
COLOR_ASN = "1;33"
COLOR_TABLE_HEADER = "1;97"
COLOR_TABLE_VALUE = "1"
COLOR_NULL = "0;90"
COLOR_ERROR = "1;31"
COLOR_WARN = "1;33"
COLOR_INFO = "1;36"
COLOR_RESET = "0"
LOG_INFO = "INFO"
LOG_WARN = "WARNING"
LOG_ERROR = "ERROR"
STATUS_NA = "N/A"
STATUS_DENIED = "Denied"
STATUS_RATE_LIMIT = "Rate-limit"
STATUS_SERVER_ERROR = "Server error"
declare -A DEPENDENCIES =(
[ jq]= "jq"
[ curl]= "curl"
[ column]= "util-linux"
[ nslookup]= "bind-utils"
)
declare -A PACKAGE_MAPPING =(
[ "apt:nslookup" ]= "dnsutils"
[ "apt:column" ]= "bsdmainutils"
[ "pacman:nslookup" ]= "bind"
[ "dnf:nslookup" ]= "bind-utils"
[ "yum:nslookup" ]= "bind-utils"
[ "termux:column" ]= "util-linux"
)
declare -A PRIMARY_SERVICES =(
[ MAXMIND]= "maxmind.com|geoip.maxmind.com|/geoip/v2.1/city/me"
[ RIPE]= "rdap.db.ripe.net|rdap.db.ripe.net|/ip/{ip}"
[ IPINFO_IO]= "ipinfo.io|ipinfo.io|/widget/demo/{ip}"
[ IPREGISTRY]= "ipregistry.co|api.ipregistry.co|/{ip}?hostname=true&key=sb69ksjcajfs4c"
[ IPAPI_CO]= "ipapi.co|ipapi.co|/{ip}/json"
[ CLOUDFLARE]= "cloudflare.com|speed.cloudflare.com|/meta"
[ IFCONFIG_CO]= "ifconfig.co|ifconfig.co|/country-iso?ip={ip}|plain"
[ IPLOCATION_COM]= "iplocation.com|iplocation.com"
[ COUNTRY_IS]= "country.is|api.country.is|/{ip}"
[ GEOAPIFY_COM]= "geoapify.com|api.geoapify.com|/v1/ipinfo?&ip={ip}&apiKey=b8568cb9afc64fad861a69edbddb2658"
[ GEOJS_IO]= "geojs.io|get.geojs.io|/v1/ip/country.json?ip={ip}"
[ IPAPI_IS]= "ipapi.is|api.ipapi.is|/?q={ip}"
[ IPBASE_COM]= "ipbase.com|api.ipbase.com|/v2/info?ip={ip}"
[ IPQUERY_IO]= "ipquery.io|api.ipquery.io|/{ip}"
[ IPWHO_IS]= "ipwho.is|ipwho.is|/{ip}"
[ IPAPI_COM]= "ip-api.com|demo.ip-api.com|/json/{ip}?fields=countryCode"
2025-12-12 04:45:43 -08:00
[ SCAMALYTICS]= "scamalytics.com|api12.scamalytics.com|/v3/sashasafonov080/?key=b8e893e3b25169850525bee236c2d6d4d347cc110d8479b73ab91812f7106f44&ip={ip}"
2025-10-05 03:48:13 +03:00
)
PRIMARY_SERVICES_ORDER =(
"MAXMIND"
"RIPE"
"IPINFO_IO"
"CLOUDFLARE"
"IPREGISTRY"
"IPAPI_CO"
"IFCONFIG_CO"
"IPLOCATION_COM"
"COUNTRY_IS"
"GEOAPIFY_COM"
"GEOJS_IO"
"IPAPI_IS"
"IPBASE_COM"
"IPQUERY_IO"
"IPWHO_IS"
"IPAPI_COM"
2025-12-12 04:45:43 -08:00
"SCAMALYTICS"
2025-10-05 03:48:13 +03:00
)
declare -A PRIMARY_SERVICES_CUSTOM_HANDLERS =(
[ IPLOCATION_COM]= "lookup_iplocation_com"
2025-12-12 04:45:43 -08:00
[ SCAMALYTICS]= "lookup_scamalytics"
2025-10-05 03:48:13 +03:00
)
declare -A SERVICE_HEADERS =(
[ IPREGISTRY]= "Origin: https://ipregistry.co"
[ MAXMIND]= "Referer: https://www.maxmind.com"
[ IPAPI_COM]= "Origin: https://ip-api.com"
)
declare -A CUSTOM_SERVICES =(
[ GOOGLE]= "Google"
[ YOUTUBE]= "YouTube"
[ TWITCH]= "Twitch"
[ CHATGPT]= "ChatGPT"
[ NETFLIX]= "Netflix"
[ SPOTIFY]= "Spotify"
[ REDDIT]= "Reddit"
[ DISNEY_PLUS]= "Disney+"
[ REDDIT_GUEST_ACCESS]= "Reddit (Guest Access)"
[ YOUTUBE_PREMIUM]= "YouTube Premium"
[ GOOGLE_SEARCH_CAPTCHA]= "Google Search Captcha"
[ SPOTIFY_SIGNUP]= "Spotify Signup"
[ DISNEY_PLUS_ACCESS]= "Disney+ Access"
[ APPLE]= "Apple"
[ STEAM]= "Steam"
[ TIKTOK]= "Tiktok"
[ OOKLA_SPEEDTEST]= "Ookla Speedtest"
[ JETBRAINS]= "JetBrains"
[ PLAYSTATION]= "PlayStation"
[ MICROSOFT]= "Microsoft"
)
CUSTOM_SERVICES_ORDER =(
"GOOGLE"
"YOUTUBE"
"TWITCH"
"CHATGPT"
"NETFLIX"
"SPOTIFY"
"REDDIT"
"DISNEY_PLUS"
"REDDIT_GUEST_ACCESS"
"YOUTUBE_PREMIUM"
"GOOGLE_SEARCH_CAPTCHA"
"SPOTIFY_SIGNUP"
"DISNEY_PLUS_ACCESS"
"APPLE"
"STEAM"
"TIKTOK"
"OOKLA_SPEEDTEST"
"JETBRAINS"
"PLAYSTATION"
"MICROSOFT"
)
declare -A CUSTOM_SERVICES_HANDLERS =(
[ GOOGLE]= "lookup_google"
[ YOUTUBE]= "lookup_youtube"
[ TWITCH]= "lookup_twitch"
[ CHATGPT]= "lookup_chatgpt"
[ NETFLIX]= "lookup_netflix"
[ SPOTIFY]= "lookup_spotify"
[ REDDIT]= "lookup_reddit"
[ DISNEY_PLUS]= "lookup_disney_plus"
[ REDDIT_GUEST_ACCESS]= "lookup_reddit_guest_access"
[ YOUTUBE_PREMIUM]= "lookup_youtube_premium"
[ GOOGLE_SEARCH_CAPTCHA]= "lookup_google_search_captcha"
[ SPOTIFY_SIGNUP]= "lookup_spotify_signup"
[ DISNEY_PLUS_ACCESS]= "lookup_disney_plus_access"
[ APPLE]= "lookup_apple"
[ STEAM]= "lookup_steam"
[ TIKTOK]= "lookup_tiktok"
[ CLOUDFLARE_CDN]= "lookup_cloudflare_cdn"
[ YOUTUBE_CDN]= "lookup_youtube_cdn"
[ NETFLIX_CDN]= "lookup_netflix_cdn"
[ OOKLA_SPEEDTEST]= "lookup_ookla_speedtest"
[ JETBRAINS]= "lookup_jetbrains"
[ PLAYSTATION]= "lookup_playstation"
[ MICROSOFT]= "lookup_microsoft"
)
declare -A CDN_SERVICES =(
[ CLOUDFLARE_CDN]= "Cloudflare CDN"
[ YOUTUBE_CDN]= "YouTube CDN"
[ NETFLIX_CDN]= "Netflix CDN"
)
CDN_SERVICES_ORDER =(
"CLOUDFLARE_CDN"
"YOUTUBE_CDN"
"NETFLIX_CDN"
)
declare -A SERVICE_GROUPS =(
[ primary]= " ${ PRIMARY_SERVICES_ORDER [*] } "
[ custom]= " ${ CUSTOM_SERVICES_ORDER [*] } "
[ cdn]= " ${ CDN_SERVICES_ORDER [*] } "
)
EXCLUDED_SERVICES =(
# "IPINFO_IO"
# "IPREGISTRY"
# "IPAPI_CO"
"GOOGLE_SEARCH_CAPTCHA"
)
IDENTITY_SERVICES =(
"ident.me"
"ifconfig.me"
"api64.ipify.org"
"ifconfig.co"
"ifconfig.me"
)
IPV6_OVER_IPV4_SERVICES =(
"IPINFO_IO"
)
color() {
local color_name = " $1 "
local text = " $2 "
local code
case " $color_name " in
HEADER) code = " $COLOR_HEADER " ;;
SERVICE) code = " $COLOR_SERVICE " ;;
HEART) code = " $COLOR_HEART " ;;
URL) code = " $COLOR_URL " ;;
ASN) code = " $COLOR_ASN " ;;
TABLE_HEADER) code = " $COLOR_TABLE_HEADER " ;;
TABLE_VALUE) code = " $COLOR_TABLE_VALUE " ;;
NULL) code = " $COLOR_NULL " ;;
ERROR) code = " $COLOR_ERROR " ;;
WARN) code = " $COLOR_WARN " ;;
INFO) code = " $COLOR_INFO " ;;
RESET) code = " $COLOR_RESET " ;;
*) code = " $color_name " ;;
esac
printf "\033[%sm%s\033[0m" " $code " " $text "
}
bold() {
local text = " $1 "
printf "\033[1m%s\033[0m" " $text "
}
get_timestamp() {
local format = " $1 "
date +" $format "
}
log() {
local log_level = " $1 "
local message = " ${ *: 2 } "
local timestamp
if [[ " $VERBOSE " == true ]] ; then
local color_code
timestamp = $( get_timestamp "%d.%m.%Y %H:%M:%S" )
case " $log_level " in
" $LOG_ERROR " ) color_code = ERROR ;;
" $LOG_WARN " ) color_code = WARN ;;
" $LOG_INFO " ) color_code = INFO ;;
*) color_code = RESET ;;
esac
printf "[%s] [%s]: %s\n" " $timestamp " " $( color $color_code " $log_level " ) " " $message " >& 2
fi
}
error_exit() {
local message = " $1 "
local exit_code = " ${ 2 :- 1 } "
printf "%s %s\n" " $( color ERROR '[ERROR]' ) " " $( color TABLE_HEADER " $message " ) " >& 2
display_help
exit " $exit_code "
}
display_help() {
cat <<EOF
Usage: $0 [OPTIONS]
IPRegion — determines your IP geolocation using various GeoIP services and popular websites
Options:
-h, --help Show this help message and exit
-v, --verbose Enable verbose logging
-d, --debug Enable full debug trace and save full trace to file and upload it to bashupload.com
-j, --json Output results in JSON format
-g, --group GROUP Run only one group: 'primary', 'custom', 'cdn', or 'all' (default: all)
-t, --timeout SEC Set curl request timeout in seconds (default: $CURL_TIMEOUT)
-4, --ipv4 Test only IPv4
-6, --ipv6 Test only IPv6
-p, --proxy ADDR Use SOCKS5 proxy (format: host:port)
-i, --interface IF Use specified network interface (e.g. eth1)
Examples:
$0 # Check all services with default settings
$0 -g primary # Check only GeoIP services
$0 -g custom # Check only popular websites
$0 -g cdn # Check only CDN endpoints
$0 -4 # Test only IPv4
$0 -6 # Test only IPv6
$0 -p 127.0.0.1:1080 # Use SOCKS5 proxy
$0 -i eth1 # Use network interface eth1
$0 -j # Output result as JSON
$0 -v # Enable verbose logging
$0 -d # Enable debug and save full trace to file and upload it to bashupload.com
EOF
}
setup_debug() {
if [[ " $DEBUG " != true ]] ; then
return 1
fi
exec 3>& 1 4>& 2
exec 1> >( tee -a " $DEBUG_LOG_FILE " >& 3)
exec 2> >( tee -a " $DEBUG_LOG_FILE " >& 4)
set -x
return 0
}
grep_wrapper() {
local grep_args =()
if [[ " $1 " == "--perl" ]] ; then
grep_args +=( "-oP" )
shift
fi
grep " ${ grep_args [@] } " " $@ "
}
extract_debug_url() {
local response = " $1 "
url = $( grep_wrapper bashupload.com <<< " $response " )
url = ${ url ##* }
echo " $url "
}
upload_debug() {
local ip_version = 4
local response
response = $( make_request POST "https://bashupload.com" --file " $DEBUG_LOG_FILE " --ip-version " $ip_version " )
extract_debug_url " $response "
}
cleanup_debug() {
local debug_url
if [[ ! -f " $DEBUG_LOG_FILE " ]] ; then
return 1
fi
set +x
exec 1>& 3 2>& 4 3>& - 4>& -
debug_url = " $( upload_debug) "
printf "\n%s\n %s\n %s\n\n%s\n" \
" $( color WARN 'Debug information:' ) " \
"Local file: $DEBUG_LOG_FILE " \
"Remote URL: $debug_url " \
" $( color INFO 'If you open a GitHub Issue, please download the log and attach it' ) "
}
is_command_available() {
local cmd = " $1 "
command -v " $cmd " >/dev/null 2>& 1
}
detect_distro() {
if [[ -f /etc/os-release ]] ; then
source /etc/os-release
distro = " $ID "
elif [[ -f /etc/redhat-release ]] ; then
distro = "rhel"
elif [[ -d /data/data/com.termux ]] ; then
distro = "termux"
fi
}
detect_package_manager() {
local pkg_manager
case " $distro " in
ubuntu | debian | termux)
pkg_manager = "apt"
;;
arch | manjaro)
pkg_manager = "pacman"
;;
fedora)
pkg_manager = "dnf"
;;
centos | rhel)
if is_command_available "dnf" ; then
pkg_manager = "dnf"
else
pkg_manager = "yum"
fi
;;
opensuse*)
pkg_manager = "zypper"
;;
alpine)
pkg_manager = "apk"
;;
*)
error_exit "Unknown distro: $distro "
;;
esac
echo " $pkg_manager "
}
get_missing_commands() {
local missing =()
for cmd in " ${ !DEPENDENCIES[@] } " ; do
if ! is_command_available " $cmd " ; then
missing +=( " $cmd " )
fi
done
printf '%s\n' " ${ missing [@] } "
}
get_package_name() {
local pkg_manager = " $1 "
local command = " $2 "
local mapping_key = " ${ pkg_manager } : ${ command } "
if [[ -n " ${ PACKAGE_MAPPING [ $mapping_key ] } " ]] ; then
echo " ${ PACKAGE_MAPPING [ $mapping_key ] } "
return
fi
echo " ${ DEPENDENCIES [ $command ] :- $command } "
}
is_sudo_required() {
if [[ " ${ EUID :-$( id -u) } " -eq 0 || " $distro " == "termux" ]] ; then
return 1
fi
return 0
}
get_install_args() {
local pkg_manager = " $1 "
local install_args
case " $pkg_manager " in
apt)
install_args =( "install" "-y" )
;;
pacman)
install_args =( "-Sy" "--noconfirm" )
;;
dnf | yum | zypper)
install_args =( "install" "-y" )
;;
apk)
install_args =( "add" "--no-cache" )
;;
esac
echo " ${ install_args [@] } "
}
install_packages() {
local pkg_manager = " $1 "
shift
local packages =( " $@ " )
local cmd_prefix =()
local install_cmd =()
if is_sudo_required; then
cmd_prefix =( "sudo" )
log " $LOG_INFO " "Running as non-root user, using sudo"
fi
cmd_prefix +=( " $pkg_manager " )
if [[ " $pkg_manager " == "apt" ]] ; then
log " $LOG_INFO " "Updating package lists"
if ! " ${ cmd_prefix [@] } " update; then
error_exit "Error occurred while updating package lists"
fi
fi
read -ra install_args <<< " $( get_install_args " $pkg_manager " ) "
install_cmd +=( " ${ cmd_prefix [@] } " " ${ install_args [@] } " " ${ packages [@] } " )
log " $LOG_INFO " "Running: ${ install_cmd [*] } "
if ! " ${ install_cmd [@] } " ; then
error_exit "Error occurred while installing packages"
fi
}
prompt_for_installation() {
local missing =( " $@ " )
local response
local formatted_deps = ""
for dep in " ${ missing [@] } " ; do
formatted_deps += " $dep \n"
done
printf "\n%s\n%b\n%s " \
" $( color WARN 'Missing dependencies:' ) " \
" $formatted_deps " \
" $( color INFO 'Do you want to install them? [y/N]:' ) "
read -r response
response = ${ response ,, }
case " $response " in
y | yes)
return 0
;;
*)
return 1
;;
esac
}
install_dependencies() {
local missing_dependencies =()
local missing_commands pkg_manager package_name
log " $LOG_INFO " "Checking dependencies"
mapfile -t missing_commands < <( get_missing_commands)
if [[ " ${ missing_commands [*] } " = ~ ^[[ :space:]] *$ ]] ; then
log " $LOG_INFO " "All dependencies are installed"
return 0
fi
log " $LOG_INFO " "Missing commands: ${ missing_commands [*] } "
pkg_manager = $( detect_package_manager)
log " $LOG_INFO " "Detected package manager: $pkg_manager "
for cmd in " ${ missing_commands [@] } " ; do
package_name = $( get_package_name " $pkg_manager " " $cmd " )
missing_dependencies +=( " $package_name " )
done
log " $LOG_INFO " "Missing dependencies: ${ missing_dependencies [*] } "
if ! prompt_for_installation " ${ missing_dependencies [@] } " </dev/tty; then
printf "%s\n" " $( color WARN 'Installation canceled by user' ) "
exit 1
fi
log " $LOG_INFO " "Installing missing dependencies"
install_packages " $pkg_manager " " ${ missing_dependencies [@] } "
}
is_valid_json() {
local json = " $1 "
jq -e . >/dev/null 2>& 1 <<< " $json "
}
process_json() {
local json = " $1 "
local jq_filter = " $2 "
if is_status_string " $json " ; then
echo " $json "
return
fi
jq -r " $jq_filter " <<< " $json "
}
format_value() {
local value = " $1 "
case " $value " in
" $STATUS_NA " )
color NULL " $value "
;;
" $STATUS_DENIED " | " $STATUS_SERVER_ERROR " )
color ERROR " $value "
;;
" $STATUS_RATE_LIMIT " )
color WARN " $value "
;;
*)
bold " $value "
;;
esac
}
print_value_or_colored() {
local value = " $1 "
local color_name = " $2 "
if [[ " $JSON_OUTPUT " == true ]] ; then
echo " $value "
return
fi
color " $color_name " " $value "
}
mask_ipv4() {
local ip = " $1 "
echo " ${ ip %.*.* } .*.*"
}
mask_ipv6() {
local ip = " $1 "
echo " $ip " | awk -F: '{
for(i=1;i<=NF;i++) if($i=="") $i="0";
while(NF<8) for(i=1;i<=8;i++) if($i=="0"){NF++; break;}
printf "%s:%s:%s::\n", $1, $2, $3
}'
}
parse_arguments() {
while [[ $# -gt 0 ]] ; do
case $1 in
-h | --help)
display_help
exit 0
;;
-v | --verbose)
VERBOSE = true
shift
;;
-d | --debug)
DEBUG = true
shift
;;
-j | --json)
JSON_OUTPUT = true
shift
;;
-g | --group)
GROUPS_TO_SHOW = " $2 "
shift 2
;;
-t | --timeout)
if [[ " $2 " = ~ ^[ 0-9] +$ ]] ; then
CURL_TIMEOUT = " $2 "
else
error_exit "Invalid timeout value: $2 . Timeout must be a positive integer"
fi
shift 2
;;
-4 | --ipv4)
IPV4_ONLY = true
shift
;;
-6 | --ipv6)
if ! check_ip_support 6; then
error_exit "IPv6 is not supported on this system"
fi
IPV6_ONLY = true
shift
;;
-p | --proxy)
PROXY_ADDR = " $2 "
log " $LOG_INFO " "Using SOCKS5 proxy: $PROXY_ADDR "
shift 2
;;
-i | --interface)
INTERFACE_NAME = " $2 "
log " $LOG_INFO " "Using interface: $INTERFACE_NAME "
shift 2
;;
*)
error_exit "Unknown option: $1 "
;;
esac
done
}
is_status_string() {
local value = " $1 "
case " $value " in
" $STATUS_DENIED " | " $STATUS_SERVER_ERROR " | " $STATUS_RATE_LIMIT " | " $STATUS_NA " )
return 0
;;
*)
return 1
;;
esac
}
status_from_http_code() {
local code = " $1 "
case " $code " in
403)
echo " $STATUS_DENIED "
;;
429)
echo " $STATUS_RATE_LIMIT "
;;
5*)
echo " $STATUS_SERVER_ERROR "
;;
4*)
echo " $STATUS_NA "
;;
*)
echo ""
;;
esac
}
check_ip_interfaces() {
local version = " $1 "
log " $LOG_INFO " "Checking for IPv ${ version } interfaces"
if [[ -n $( ip -" ${ version } " addr show scope global 2>/dev/null) ]] ; then
log " $LOG_INFO " "IPv ${ version } global interfaces found"
return 0
fi
log " $LOG_ERROR " "No global IPv ${ version } addresses found on interfaces"
return 1
}
check_ip_connectivity() {
local version = " $1 "
local test_hosts_v4 =( "8.8.8.8" "8.8.4.4" "1.1.1.1" "1.0.0.1" "9.9.9.9" )
local test_hosts_v6 =( "2001:4860:4860::8888" "2001:4860:4860::8844" "2606:4700:4700::1111" "2606:4700:4700::1001" "2620:fe::9" )
local timeout = 3
local count = 1
local test_hosts
log " $LOG_INFO " "Checking IPv ${ version } connectivity"
if [[ " $version " == "4" ]] ; then
test_hosts =( " ${ test_hosts_v4 [@] } " )
else
test_hosts =( " ${ test_hosts_v6 [@] } " )
fi
for host in " ${ test_hosts [@] } " ; do
if ping -" ${ version } " -c " $count " -W " $timeout " " $host " >/dev/null 2>& 1; then
log " $LOG_INFO " "IPv ${ version } connectivity confirmed via $host "
return 0
fi
done
log " $LOG_ERROR " "IPv ${ version } connectivity test failed"
return 1
}
check_ip_dns() {
local version = " $1 "
local test_domain = "google.com"
local record_type
log " $LOG_INFO " "Checking IPv ${ version } DNS resolution"
if [[ " $version " == "4" ]] ; then
record_type = "A"
else
record_type = "AAAA"
fi
if nslookup -type= " $record_type " " $test_domain " >/dev/null 2>& 1; then
log " $LOG_INFO " "IPv ${ version } DNS resolution works via nslookup"
return 0
fi
log " $LOG_ERROR " "IPv ${ version } DNS resolution failed"
return 1
}
check_ip_support() {
local version = " $1 "
local -a checks =( "interfaces" "connectivity" "dns" )
local -a failed =()
spinner_update "IPv $version support"
log " $LOG_INFO " "Starting comprehensive IPv ${ version } support check"
for check in " ${ checks [@] } " ; do
if ! "check_ip_ ${ check } " " $version " ; then
failed +=( " $check " )
fi
done
if [[ ${# failed [@] } -eq 0 ]] ; then
log " $LOG_INFO " "IPv ${ version } is fully supported ( ${ checks [*] } )"
return 0
else
log " $LOG_ERROR " "IPv ${ version } is not fully supported. Failed checks: ${ failed [*] } "
return 1
fi
}
shuffle_identity_services() {
local i tmp size rand_idx
size = ${# IDENTITY_SERVICES [@] }
for (( i = size - 1; i > 0; i--)) ; do
rand_idx = $(( RANDOM % ( i + 1 )) )
if (( rand_idx != i)) ; then
tmp = ${ IDENTITY_SERVICES [i] }
IDENTITY_SERVICES[ i]= ${ IDENTITY_SERVICES [rand_idx] }
IDENTITY_SERVICES[ rand_idx]= $tmp
fi
done
}
fetch_ip_from_service() {
local service = " $1 "
local ip_version = " $2 "
local response
response = $( make_request GET "https:// $service " --ip-version " $ip_version " )
if [[ -n " $response " ]] ; then
echo " $response "
fi
}
fetch_external_ip() {
local ip_version = " $1 "
local service ip
spinner_update "External IPv $ip_version address"
log " $LOG_INFO " "Getting external IPv ${ ip_version } address"
shuffle_identity_services
for service in " ${ IDENTITY_SERVICES [@] } " ; do
ip = $( fetch_ip_from_service " $service " " $ip_version " )
if [[ -n " $ip " ]] ; then
log " $LOG_INFO " "Successfully obtained IPv ${ ip_version } address from $service : $ip "
echo " $ip "
return
else
log " $LOG_WARN " "No response from $service for IPv ${ ip_version } "
fi
done
log " $LOG_ERROR " "Failed to obtain IPv ${ ip_version } address from any service"
}
discover_external_ips() {
if [[ " $IPV4_ONLY " == true ]] || [[ " $IPV6_ONLY " != true ]] ; then
EXTERNAL_IPV4 = $( fetch_external_ip 4)
fi
if [[ " $IPV6_ONLY " == true ]] || { [[ " $IPV6_SUPPORTED " -eq 0 ]] && [[ " $IPV4_ONLY " != true ]] ; } ; then
EXTERNAL_IPV6 = $( fetch_external_ip 6)
fi
if [[ -z " $EXTERNAL_IPV4 " ]] && [[ -z " $EXTERNAL_IPV6 " ]] ; then
error_exit "Failed to obtain external IPv4 or IPv6 address"
fi
}
get_asn() {
local ipbase_api_key = "sgiPfh4j3aXFR3l2CnjWqdKQzxpqGn9pX5b3CUsz"
local ip ip_version response connection_data
if [[ -n " $EXTERNAL_IPV4 " ]] ; then
ip = " $EXTERNAL_IPV4 "
ip_version = 4
else
ip = " $EXTERNAL_IPV6 "
ip_version = 6
fi
spinner_update "ASN info"
log " $LOG_INFO " "Getting ASN info for IP $ip "
response = $( make_request GET "https://api.ipbase.com/v2/info?apikey= $ipbase_api_key &ip= $ip " --ip-version " $ip_version " )
connection_data = $( process_json " $response " ".data.connection" )
asn = $( process_json " $connection_data " ".asn" )
asn_name = $( process_json " $connection_data " ".organization" )
log " $LOG_INFO " "ASN info: AS $asn $asn_name "
}
get_registered_country() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://geoip.maxmind.com/geoip/v2.1/city/me" \
--header "Referer: https://www.maxmind.com" \
--ip-version " $ip_version " )
process_json " $response " ".registered_country.names.en"
}
get_server_country() {
local ip_version = " $1 "
2025-12-06 04:59:46 -08:00
local response country_code
2025-10-05 03:48:13 +03:00
2025-12-06 04:59:46 -08:00
# 1. ipapi.co — самый стабильный и не ебет мозг
response = $( make_request GET "https://ipapi.co/json" --ip-version " $ip_version " )
country_code = $( process_json " $response " ".country_code" )
if [[ -n " $country_code " && " $country_code " != "null" ]] ; then
echo " $country_code "
return
2025-10-05 03:48:13 +03:00
fi
2025-12-06 04:59:46 -08:00
# 2. MaxMind — fallback
2025-10-05 03:48:13 +03:00
response = $( make_request GET "https://geoip.maxmind.com/geoip/v2.1/city/me" \
--header "Referer: https://www.maxmind.com" \
--ip-version " $ip_version " )
2025-12-06 04:59:46 -08:00
country_code = $( process_json " $response " ".country.iso_code" )
if [[ -n " $country_code " && " $country_code " != "null" ]] ; then
echo " $country_code "
return
2025-10-05 03:48:13 +03:00
fi
echo "Unknown"
}
2025-12-06 04:59:46 -08:00
2025-10-05 03:48:13 +03:00
get_iata_location() {
local iata_code = " $1 "
local url = "https://www.air-port-codes.com/api/v1/single"
local payload = "iata= $iata_code "
local apc_auth = "96dc04b3fb"
local referer = "https://www.air-port-codes.com/"
local response
response = $( make_request POST " $url " \
--header "APC-Auth: $apc_auth " \
--header "Referer: $referer " \
--data " $payload " \
--ip-version 4)
process_json " $response " ".airport.country.iso"
}
is_ipv6_over_ipv4_service() {
local service = " $1 "
for s in " ${ IPV6_OVER_IPV4_SERVICES [@] } " ; do
[[ " $s " == " $service " ]] && return 0
done
return 1
}
spinner_start() {
local delay = 0.1
# shellcheck disable=SC1003
local spinstr = '|/-\\'
local current_service
spinner_running = true
(
while $spinner_running ; do
for (( i = 0; i < ${# spinstr } ; i++)) ; do
current_service = ""
if [[ -f " $SPINNER_SERVICE_FILE " ]] ; then
current_service = " $( cat " $SPINNER_SERVICE_FILE " ) "
fi
printf "\r\033[K%s %s %s" \
" $( color HEADER " ${ spinstr : $i : 1 } " ) " \
" $( color HEADER "Checking:" ) " \
" $( color SERVICE " $current_service " ) "
sleep $delay
done
done
) &
spinner_pid = $!
}
spinner_stop() {
spinner_running = false
if [[ -n " $spinner_pid " ]] ; then
kill " $spinner_pid " 2>/dev/null
wait " $spinner_pid " 2>/dev/null
spinner_pid = ""
printf "\\r%*s\\r" 40 " "
fi
if [[ -f " $SPINNER_SERVICE_FILE " ]] ; then
rm -f " $SPINNER_SERVICE_FILE "
unset SPINNER_SERVICE_FILE
fi
}
spinner_update() {
local value = " $1 "
if [[ -n " $SPINNER_SERVICE_FILE " ]] ; then
echo " $value " >" $SPINNER_SERVICE_FILE "
fi
}
spinner_cleanup() {
spinner_stop
exit 130
}
make_request() {
local method = " $1 "
local url = " $2 "
shift 2
local ip_version user_agent json data file headers response_with_code response http_code
local curl_args =(
--silent
--compressed
--location
--retry-connrefused
--retry-all-errors
--retry " $CURL_RETRIES "
--max-time " $CURL_TIMEOUT "
-w '\n%{http_code}'
)
case " $method " in
HEAD)
curl_args +=( --head)
;;
*)
curl_args +=( --request " $method " )
;;
esac
while (( $# )) ; do
case " $1 " in
--ip-version)
ip_version = " $2 "
shift 2
;;
--user-agent)
user_agent = " $2 "
shift 2
;;
--header)
headers +=( " $2 " )
shift 2
;;
--json)
json = " $2 "
shift 2
;;
--data)
data = " $2 "
shift 2
;;
--file)
file = " $2 "
shift 2
;;
esac
done
if [[ " $ip_version " == "4" ]] ; then
curl_args +=( -4)
else
curl_args +=( -6)
fi
for h in " ${ headers [@] } " ; do
curl_args +=( -H " $h " )
done
if [[ -n " $user_agent " ]] ; then
curl_args +=( -A " $user_agent " )
fi
if [[ -n " $json " ]] ; then
curl_args +=( --data " $json " )
curl_args +=( -H 'Content-Type: application/json' )
fi
if [[ -n " $data " ]] ; then
curl_args +=( --data " $data " )
curl_args +=( -H 'Content-Type: application/x-www-form-urlencoded' )
fi
if [[ -n " $file " ]] ; then
curl_args +=( --upload-file " $file " )
fi
if [[ -n " $PROXY_ADDR " ]] ; then
curl_args +=( --proxy "socks5:// $PROXY_ADDR " )
fi
if [[ -n " $INTERFACE_NAME " ]] ; then
curl_args +=( --interface " $INTERFACE_NAME " )
fi
curl_args +=( " $url " )
response_with_code = $( curl " ${ curl_args [@] } " )
http_code = $( tail -n1 <<< " $response_with_code " )
response = $( head -n -1 <<< " $response_with_code " )
if [[ " $http_code " == 4* || " $http_code " == 5* ]] ; then
status_from_http_code " $http_code "
return 0
fi
echo " $response "
}
service_build_request() {
local service = " $1 " ip = " $2 " ip_version = " $3 "
local cfg = " ${ PRIMARY_SERVICES [ $service ] } "
local display_name domain url_template url headers_str response_format
IFS = '|' read -r display_name domain url_template response_format <<< " $cfg "
if [[ -z " $display_name " ]] ; then
display_name = " $service "
fi
url = "https:// $domain ${ url_template // \{ ip \} / $ip } "
if [[ -n " ${ SERVICE_HEADERS [ $service ] } " ]] ; then
headers_str = " ${ SERVICE_HEADERS [ $service ] } "
fi
printf "%s\n%s\n%s\n%s" " $display_name " " $url " " ${ response_format :- json } " " $headers_str "
}
probe_service() {
local service = " $1 "
local ip_version = " $2 "
local ip = " $3 "
local built display_name url response_format headers_line request_params response
mapfile -t built < <( service_build_request " $service " " $ip " " $ip_version " )
display_name = " ${ built [0] } "
url = " ${ built [1] } "
response_format = " ${ built [2] } "
headers_line = " ${ built [3] } "
if [[ -n " $headers_line " ]] ; then
IFS = '||' read -ra hs <<< " $headers_line "
for h in " ${ hs [@] } " ; do
if [[ -n " $h " ]] ; then
request_params +=( --header " $h " )
fi
done
fi
if [[ " $ip_version " == "6" ]] && is_ipv6_over_ipv4_service " $service " ; then
ip_version = "4"
fi
response = $( make_request GET " $url " " ${ request_params [@] } " --ip-version " $ip_version " )
process_response " $service " " $response " " $display_name " " $response_format "
}
process_response() {
local service = " $1 "
local response = " $2 "
local display_name = " $3 "
local response_format = " ${ 4 :- json } "
local jq_filter
if is_status_string " $response " ; then
echo " $response "
return
fi
if [[ -z " $response " || " $response " == *"<html" * ]] ; then
echo " $STATUS_NA "
return
fi
if [[ " $response_format " == "plain" ]] ; then
echo " $response " | tr -d '\r\n '
return
fi
if ! is_valid_json " $response " ; then
log " $LOG_ERROR " "Invalid JSON response from $display_name : $response "
return 1
fi
case " $service " in
MAXMIND)
jq_filter = '.country.iso_code'
;;
RIPE)
jq_filter = '.country'
;;
IPINFO_IO)
jq_filter = '.data.country'
;;
IPREGISTRY)
jq_filter = '.location.country.code'
;;
IPAPI_CO)
jq_filter = '.country'
;;
CLOUDFLARE)
jq_filter = '.country'
;;
COUNTRY_IS)
jq_filter = '.country'
;;
GEOAPIFY_COM)
jq_filter = '.country.iso_code'
;;
GEOJS_IO)
jq_filter = '.[0].country'
;;
IPAPI_IS)
jq_filter = '.location.country_code'
;;
IPBASE_COM)
jq_filter = '.data.location.country.alpha2'
;;
IPQUERY_IO)
jq_filter = '.location.country_code'
;;
IPWHO_IS)
jq_filter = '.country_code'
;;
IPAPI_COM)
jq_filter = '.countryCode'
;;
2025-12-12 04:45:43 -08:00
SCAMALYTICS)
jq_filter = '.scamalytics.scamalytics_risk'
;;
2025-10-05 03:48:13 +03:00
*)
echo " $response "
;;
esac
process_json " $response " " $jq_filter "
}
process_service() {
local service = " $1 "
local custom = " ${ 2 :- false } "
local service_config = " ${ PRIMARY_SERVICES [ $service ] } "
local display_name domain url_template response_format ipv4_result ipv6_result handler_func
IFS = '|' read -r display_name domain url_template response_format <<< " $service_config "
if [[ -z " $display_name " ]] ; then
display_name = " $service "
fi
spinner_update " $display_name "
if [[ " $custom " == true ]] ; then
process_custom_service " $service "
return
fi
if [[ -n " ${ PRIMARY_SERVICES_CUSTOM_HANDLERS [ $service ] } " ]] ; then
handler_func = " ${ PRIMARY_SERVICES_CUSTOM_HANDLERS [ $service ] } "
log " $LOG_INFO " "Checking $display_name via IPv4 (custom handler)"
ipv4_result = $( " $handler_func " 4)
if [[ " $IPV6_ONLY " == true ]] || { [[ " $IPV6_SUPPORTED " -eq 0 && -n " $EXTERNAL_IPV6 " ]] && [[ " $IPV4_ONLY " != true ]] ; } ; then
log " $LOG_INFO " "Checking $display_name via IPv6 (custom handler)"
ipv6_result = $( " $handler_func " 6)
else
ipv6_result = ""
fi
add_result "primary" " $display_name " " $ipv4_result " " $ipv6_result "
return
fi
if [[ " $IPV6_ONLY " != true ]] ; then
if [[ -n " $EXTERNAL_IPV4 " ]] ; then
log " $LOG_INFO " "Checking $display_name via IPv4"
ipv4_result = $( probe_service " $service " 4 " $EXTERNAL_IPV4 " )
fi
fi
if [[ " $IPV4_ONLY " != true ]] ; then
if [[ " $IPV6_SUPPORTED " -eq 0 && -n " $EXTERNAL_IPV6 " ]] ; then
if is_ipv6_over_ipv4_service " $service " ; then
log " $LOG_INFO " "Checking $display_name (IPv6 address, IPv4 transport)"
else
log " $LOG_INFO " "Checking $display_name via IPv6"
fi
ipv6_result = $( probe_service " $service " 6 " $EXTERNAL_IPV6 " )
fi
fi
add_result "primary" " $display_name " " $ipv4_result " " $ipv6_result "
}
process_custom_service() {
local service = " $1 "
local ipv4_result ipv6_result
local display_name handler_func group
if [[ -n " ${ CUSTOM_SERVICES [ $service ] } " ]] ; then
display_name = " ${ CUSTOM_SERVICES [ $service ] } "
handler_func = " ${ CUSTOM_SERVICES_HANDLERS [ $service ] } "
group = "custom"
elif [[ -n " ${ CDN_SERVICES [ $service ] } " ]] ; then
display_name = " ${ CDN_SERVICES [ $service ] } "
handler_func = " ${ CUSTOM_SERVICES_HANDLERS [ $service ] } "
group = "cdn"
else
display_name = " $service "
handler_func = " ${ CUSTOM_SERVICES_HANDLERS [ $service ] } "
group = "custom"
fi
spinner_update " $display_name "
if [[ -z " $handler_func " ]] ; then
log " $LOG_WARN " "Unknown service handler: $service "
return
fi
if [[ " $IPV6_ONLY " != true ]] ; then
log " $LOG_INFO " "Checking $display_name via IPv4"
ipv4_result = $( " $handler_func " 4)
else
ipv4_result = ""
fi
if [[ " $IPV4_ONLY " != true ]] && [[ " $IPV6_SUPPORTED " -eq 0 && -n " $EXTERNAL_IPV6 " ]] ; then
log " $LOG_INFO " "Checking $display_name via IPv6"
ipv6_result = $( " $handler_func " 6)
else
ipv6_result = ""
fi
add_result " $group " " $display_name " " $ipv4_result " " $ipv6_result "
}
run_service_group() {
local group = " $1 "
local services_string = " ${ SERVICE_GROUPS [ $group ] } "
local is_custom = false
local is_cdn = false
local services_array service_name handler_func display_name result
read -ra services_array <<< " $services_string "
log " $LOG_INFO " "Running $group group services"
for service_name in " ${ services_array [@] } " ; do
if printf "%s\n" " ${ EXCLUDED_SERVICES [@] } " | grep_wrapper -Fxq " $service_name " ; then
log " $LOG_INFO " "Skipping service: $service_name "
continue
fi
case " $group " in
custom)
is_custom = true
;;
cdn)
is_cdn = true
;;
esac
if [[ " $is_custom " == true ]] ; then
process_service " $service_name " true
elif [[ " $is_cdn " == true ]] ; then
process_custom_service " $service_name "
else
process_service " $service_name "
fi
done
}
run_all_services() {
local service_name
for func in $( declare -F | awk '{print $3}' | grep_wrapper '^lookup_' ) ; do
service_name = ${ func #lookup_ }
service_name_uppercase = ${ service_name ^^ }
if printf "%s\n" " ${ EXCLUDED_SERVICES [@] } " | grep_wrapper -Fxq " $service_name_uppercase " ; then
log " $LOG_INFO " "Skipping service: $service_name_uppercase "
continue
fi
if [[ -n " ${ CUSTOM_SERVICES [ $service_name_uppercase ] } " ]] ; then
process_service " $service_name_uppercase " true
continue
fi
" $func "
done
}
finalize_json() {
local t_primary t_custom t_cdn
local IFS = $'\n'
if (( ${# ARR_PRIMARY [@] } > 0)) ; then
t_primary = $( printf '%s\n' " ${ ARR_PRIMARY [@]//|||/ $'\t' } " )
fi
if (( ${# ARR_CUSTOM [@] } > 0)) ; then
t_custom = $( printf '%s\n' " ${ ARR_CUSTOM [@]//|||/ $'\t' } " )
fi
if (( ${# ARR_CDN [@] } > 0)) ; then
t_cdn = $( printf '%s\n' " ${ ARR_CDN [@]//|||/ $'\t' } " )
fi
# Добавляем страну сервера в JSON
RESULT_JSON = $(
jq -n \
--rawfile p <( printf "%s" " $t_primary " ) \
--rawfile c <( printf "%s" " $t_custom " ) \
--rawfile d <( printf "%s" " $t_cdn " ) \
--arg ipv4 " $EXTERNAL_IPV4 " \
--arg ipv6 " $EXTERNAL_IPV6 " \
--arg server_country " $SERVER_COUNTRY " \
--arg version "1" '
def lines_to_array($raw):
if ($raw | length) == 0 then [] else
($raw | split("\n"))
| map(select(length > 0))
| map(
(split("\t")) as $f
| {
service: $f[0],
ipv4: ( ($f[1] // "") | if length>0 then . else null end ),
ipv6: ( ($f[2] // "") | if length>0 then . else null end )
}
)
end;
{
version: ($version|tonumber),
ipv4: ($ipv4 | select(length > 0) // null),
ipv6: ($ipv6 | select(length > 0) // null),
server_country: ($server_country | select(length > 0) // null),
results: {
primary: lines_to_array($p),
custom: lines_to_array($c),
cdn: lines_to_array($d)
}
}
'
)
}
add_result() {
local group = " $1 "
local service = " $2 "
local ipv4 = " $3 "
local ipv6 = " $4 "
ipv4 = ${ ipv4 // $'\n' / }
ipv4 = ${ ipv4 // $'\t' / }
ipv6 = ${ ipv6 // $'\n' / }
ipv6 = ${ ipv6 // $'\t' / }
case " $group " in
primary) ARR_PRIMARY +=( " $service ||| $ipv4 ||| $ipv6 " ) ;;
custom) ARR_CUSTOM +=( " $service ||| $ipv4 ||| $ipv6 " ) ;;
cdn) ARR_CDN +=( " $service ||| $ipv4 ||| $ipv6 " ) ;;
esac
}
print_table_group() {
local group = " $1 "
local group_title = " $2 "
local na = "N/A"
local show_ipv4 = 0
local show_ipv6 = 0
local separator = $'\t'
if [[ " $IPV6_ONLY " != true && -n " $EXTERNAL_IPV4 " ]] ; then
show_ipv4 = 1
fi
if [[ " $IPV4_ONLY " != true && -n " $EXTERNAL_IPV6 " ]] ; then
show_ipv6 = 1
fi
printf "%s\n\n" " $( color HEADER " $group_title " ) "
{
printf "%s" " $( color TABLE_HEADER 'Service' ) "
if [[ $show_ipv4 -eq 1 ]] ; then
printf "%s%s" " $separator " " $( color TABLE_HEADER 'IPv4' ) "
fi
if [[ $show_ipv6 -eq 1 ]] ; then
printf "%s%s" " $separator " " $( color TABLE_HEADER 'IPv6' ) "
fi
printf "\n"
jq -r --arg group " $group " '
(.results // {}) as $r
| ($r[$group] // [])
| .[]
| [ .service, (.ipv4 // "N/A"), (.ipv6 // "N/A") ]
| @tsv
' <<< " $RESULT_JSON " | while IFS = $'\t' read -r s v4 v6; do
printf "%s" " $( color SERVICE " $s " ) "
if [[ $show_ipv4 -eq 1 ]] ; then
if [[ " $v4 " == "null" || -z " $v4 " ]] ; then
v4 = " $na "
fi
printf "%s%s" " $separator " " $( format_value " $v4 " ) "
fi
if [[ $show_ipv6 -eq 1 ]] ; then
if [[ " $v6 " == "null" || -z " $v6 " ]] ; then
v6 = " $na "
fi
printf "%s%s" " $separator " " $( format_value " $v6 " ) "
fi
printf "\n"
done
} | column -t -s " $separator "
}
print_header() {
local ipv4 ipv6
ipv4 = $( process_json " $RESULT_JSON " ".ipv4" )
ipv6 = $( process_json " $RESULT_JSON " ".ipv6" )
printf "%s\n%s\n\n" " $( color URL "Made with " )$( color HEART "<3" )$( color URL " by vernette" ) " " $( color URL " $SCRIPT_URL " ) "
# TODO: Get registered country while initializing
if [[ " $ipv4 " != "null" ]] ; then
printf "%s: %s, %s %s\n" " $( color HEADER 'IPv4' ) " " $( bold " $( mask_ipv4 " $ipv4 " ) " ) " "registered in" " $( bold " $( get_registered_country 4) " ) "
fi
if [[ " $ipv6 " != "null" ]] ; then
printf "%s: %s, %s %s\n" " $( color HEADER 'IPv6' ) " " $( bold " $( mask_ipv6 " $ipv6 " ) " ) " "registered in" " $( bold " $( get_registered_country 6) " ) "
fi
printf "%s: %s\n\n" " $( color HEADER 'ASN' ) " " $( bold "AS $asn $asn_name " ) "
}
print_results() {
finalize_json
if [[ " $JSON_OUTPUT " == true ]] ; then
echo " $RESULT_JSON " | jq
return
fi
print_header
case " $GROUPS_TO_SHOW " in
primary)
print_table_group "primary" "GeoIP services"
;;
custom)
print_table_group "custom" "Popular services"
;;
cdn)
print_table_group "cdn" "CDN services"
;;
*)
print_table_group "custom" "Popular services"
printf "\n"
print_table_group "cdn" "CDN services"
printf "\n"
print_table_group "primary" "GeoIP services"
;;
esac
}
lookup_maxmind() {
process_service "MAXMIND"
}
lookup_ripe() {
process_service "RIPE"
}
lookup_ipinfo_io() {
process_service "IPINFO_IO"
}
lookup_ipregistry() {
process_service "IPREGISTRY"
}
lookup_ipapi_co() {
process_service "IPAPI_CO"
}
lookup_cloudflare() {
process_service "CLOUDFLARE"
}
lookup_ifconfig_co() {
process_service "IFCONFIG_CO"
}
lookup_iplocation_com() {
local ip_version = " $1 "
local response ip
if [[ -n " $EXTERNAL_IPV4 " ]] ; then
ip = " $EXTERNAL_IPV4 "
else
ip = " $EXTERNAL_IPV6 "
fi
response = $( make_request POST "https://iplocation.com" --ip-version " $ip_version " --user-agent " $USER_AGENT " --data "ip= $ip " )
process_json " $response " ".country_code"
}
2025-12-12 04:45:43 -08:00
lookup_scamalytics() {
local ip_version = " $1 "
local ip response risk score bl dc vpn aws google icloud summary
if [[ -n " $EXTERNAL_IPV4 " ]] ; then
ip = " $EXTERNAL_IPV4 "
else
ip = " $EXTERNAL_IPV6 "
fi
response = $( make_request GET "https://api12.scamalytics.com/v3/sashasafonov080/?key=b8e893e3b25169850525bee236c2d6d4d347cc110d8479b73ab91812f7106f44&ip= $ip " \
--ip-version " $ip_version " )
if is_status_string " $response " ; then
echo " $response "
return
fi
if ! is_valid_json " $response " ; then
echo " $STATUS_NA "
return
fi
risk = $( process_json " $response " ".scamalytics.scamalytics_risk" )
score = $( process_json " $response " ".scamalytics.scamalytics_score" )
bl = $( process_json " $response " ".scamalytics.is_blacklisted_external" )
dc = $( process_json " $response " ".scamalytics.scamalytics_proxy.is_datacenter" )
vpn = $( process_json " $response " ".scamalytics.scamalytics_proxy.is_vpn" )
aws = $( process_json " $response " ".scamalytics.scamalytics_proxy.is_amazon_aws" )
google = $( process_json " $response " ".scamalytics.scamalytics_proxy.is_google" )
icloud = $( process_json " $response " ".scamalytics.scamalytics_proxy.is_apple_icloud_private_relay" )
summary = $( printf "risk=%s score=%s bl=%s dc=%s vpn=%s aws=%s google=%s icloud=%s" \
" ${ risk :- $STATUS_NA } " " ${ score :- $STATUS_NA } " " ${ bl :- $STATUS_NA } " \
" ${ dc :- $STATUS_NA } " " ${ vpn :- $STATUS_NA } " " ${ aws :- $STATUS_NA } " \
" ${ google :- $STATUS_NA } " " ${ icloud :- $STATUS_NA } " )
echo " $summary "
}
2025-10-05 03:48:13 +03:00
lookup_google() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://www.google.com" \
--user-agent " $USER_AGENT " \
--ip-version " $ip_version " )
grep_wrapper --perl '"MgUcDb":"\K[^"]*' <<< " $response "
}
lookup_youtube() {
local ip_version = " $1 "
local response json_result
response = $( make_request GET "https://www.youtube.com/sw.js_data" --ip-version " $ip_version " )
json_result = $( tail -n +3 <<< " $response " )
process_json " $json_result " ".[0][2][0][0][1]"
}
lookup_twitch() {
local ip_version = " $1 "
local response
response = $( make_request POST "https://gql.twitch.tv/gql" \
--header "Client-Id: $TWITCH_CLIENT_ID " \
--json '[{"operationName":"VerifyEmail_CurrentUser","variables":{},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"f9e7dcdf7e99c314c82d8f7f725fab5f99d1df3d7359b53c9ae122deec590198"}}}]' \
--ip-version " $ip_version " )
process_json " $response " ".[0].data.requestInfo.countryCode"
}
lookup_chatgpt() {
local ip_version = " $1 "
local response
response = $( make_request POST "https://ab.chatgpt.com/v1/initialize" --ip-version " $ip_version " \
--header "Statsig-Api-Key: $CHATGPT_STATSIG_API_KEY " )
process_json " $response " ".derived_fields.country"
}
lookup_netflix() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://api.fast.com/netflix/speedtest/v2?https=true&token= $NETFLIX_API_KEY &urlCount=1" --ip-version " $ip_version " )
if is_valid_json " $response " ; then
process_json " $response " ".client.location.country"
return
fi
echo " $response "
}
lookup_spotify() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://spclient.wg.spotify.com/signup/public/v1/account/?validate=1&key= $SPOTIFY_API_KEY " \
--header "X-Client-Id: $SPOTIFY_CLIENT_ID " \
--ip-version " $ip_version " )
process_json " $response " ".country"
}
lookup_reddit() {
local ip_version = " $1 "
local basic_access_token = "Basic $REDDIT_BASIC_ACCESS_TOKEN "
local user_agent = "Reddit/Version 2025.29.0/Build 2529021/Android 13"
local response access_token
response = $( make_request POST "https://www.reddit.com/auth/v2/oauth/access-token/loid" \
--ip-version " $ip_version " \
--user-agent " $user_agent " \
--header "Authorization: $basic_access_token " \
--json '{"scopes":["email"]}' )
access_token = $( process_json " $response " ".access_token" )
response = $( make_request POST "https://gql-fed.reddit.com" \
--ip-version " $ip_version " \
--user-agent " $user_agent " \
--header "Authorization: Bearer $access_token " \
--json '{"operationName":"UserLocation","variables":{},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"f07de258c54537e24d7856080f662c1b1268210251e5789c8c08f20d76cc8ab2"}}}' )
process_json " $response " ".data.userLocation.countryCode"
}
lookup_disney_plus() {
local ip_version = " $1 "
local response
response = $( make_request POST "https://disney.api.edge.bamgrid.com/graph/v1/device/graphql" \
--header "Authorization: Bearer $DISNEY_PLUS_API_KEY " \
--json " $DISNEY_PLUS_JSON_BODY " \
--ip-version " $ip_version " )
process_json " $response " ".extensions.sdk.session.location.countryCode"
}
lookup_reddit_guest_access() {
local ip_version = " $1 "
local response is_available color_name
response = $( make_request GET "https://www.reddit.com" --ip-version " $ip_version " --user-agent " $USER_AGENT " )
if [[ " $response " != "Denied" ]] ; then
is_available = "Yes"
color_name = "SERVICE"
else
is_available = "No"
color_name = "HEART"
fi
print_value_or_colored " $is_available " " $color_name "
}
lookup_youtube_premium() {
local ip_version = " $1 "
local response is_available
response = $( make_request GET "https://www.youtube.com/premium" \
--ip-version " $ip_version " \
--user-agent " $USER_AGENT " \
--header "Cookie: SOCS= $YOUTUBE_SOCS_COOKIE " \
--header "Accept-Language: en-US,en;q=0.9" )
if [[ -z " $response " ]] ; then
echo ""
return
fi
is_available = $( grep_wrapper -io "youtube premium is not available in your country" <<< " $response " )
if [[ -z " $is_available " ]] ; then
is_available = "Yes"
color_name = "SERVICE"
else
is_available = "No"
color_name = "HEART"
fi
print_value_or_colored " $is_available " " $color_name "
}
lookup_google_search_captcha() {
local ip_version = " $1 "
local response is_captcha color_name
response = $( make_request GET "https://www.google.com/search?q=cats" --ip-version " $ip_version " \
--user-agent " $USER_AGENT " \
--header "Accept-Language: en-US,en;q=0.9" )
if [[ -z " $response " ]] ; then
echo ""
return
fi
is_captcha = $( grep_wrapper -iE "unusual traffic from|is blocked|unaddressed abuse" <<< " $response " )
if [[ -z " $is_captcha " ]] ; then
is_captcha = "No"
color_name = "SERVICE"
else
is_captcha = "Yes"
color_name = "HEART"
fi
print_value_or_colored " $is_captcha " " $color_name "
}
lookup_spotify_signup() {
local ip_version = " $1 "
local response status is_country_launched available color_name
response = $( make_request GET "https://spclient.wg.spotify.com/signup/public/v1/account/?validate=1&key= $SPOTIFY_API_KEY " \
--header "X-Client-Id: $SPOTIFY_CLIENT_ID " \
--ip-version " $ip_version " )
status = $( process_json " $response " ".status" )
is_country_launched = $( process_json " $response " ".is_country_launched" )
if [[ " $status " == "120" || " $status " == "320" || " $is_country_launched " == "false" ]] ; then
available = "No"
color_name = "HEART"
else
available = "Yes"
color_name = "SERVICE"
fi
print_value_or_colored " $available " " $color_name "
}
lookup_disney_plus_access() {
local ip_version = " $1 "
local response errors_count in_supported_location is_available color_name
response = $( make_request POST "https://disney.api.edge.bamgrid.com/graph/v1/device/graphql" \
--header "Authorization: Bearer $DISNEY_PLUS_API_KEY " \
--json " $DISNEY_PLUS_JSON_BODY " \
--ip-version " $ip_version " )
errors_count = $( process_json " $response " ".errors | length" )
in_supported_location = $( process_json " $response " ".extensions.sdk.session.inSupportedLocation" )
if [[ " $errors_count " == "0" && " $in_supported_location " == "true" ]] ; then
is_available = "Yes"
color_name = "SERVICE"
else
is_available = "No"
color_name = "HEART"
fi
print_value_or_colored " $is_available " " $color_name "
}
lookup_apple() {
local ip_version = " $1 "
make_request GET "https://gspe1-ssl.ls.apple.com/pep/gcc" --ip-version " $ip_version "
}
lookup_steam() {
local ip_version = " $1 "
local response
response = $( make_request HEAD "https://store.steampowered.com" --ip-version " $ip_version " )
grep_wrapper --perl 'steamCountry=\K[^%;]*' <<< " $response "
}
lookup_tiktok() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://www.tiktok.com/api/v1/web-cookie-privacy/config?appId=1988" --ip-version " $ip_version " )
process_json " $response " ".body.appProps.region"
}
lookup_cloudflare_cdn() {
local ip_version = " $1 "
local response iata location
response = $( make_request GET "https://speed.cloudflare.com/meta" --ip-version " $ip_version " )
iata = $( process_json " $response " ".colo" )
location = $( get_iata_location " $iata " )
echo " $location ( $iata )"
}
lookup_youtube_cdn() {
local ip_version = " $1 "
local response iata location
response = $( make_request GET "https://redirector.googlevideo.com/report_mapping?di=no" --ip-version " $ip_version " )
iata = $( echo " $response " | awk '{print $3}' | cut -f2 -d'-' | cut -c1-3)
iata = ${ iata ^^ }
if [[ -z " $iata " ]] ; then
echo ""
return
fi
location = $( get_iata_location " $iata " )
echo " $location ( $iata )"
}
lookup_netflix_cdn() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://api.fast.com/netflix/speedtest/v2?https=true&token= $NETFLIX_API_KEY &urlCount=1" --ip-version " $ip_version " )
if is_valid_json " $response " ; then
process_json " $response " ".targets[0].location.country"
else
echo ""
fi
}
lookup_ookla_speedtest() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://www.speedtest.net/api/js/config-sdk" --ip-version " $ip_version " )
process_json " $response " ".location.countryCode"
}
lookup_jetbrains() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://data.services.jetbrains.com/geo" --ip-version " $ip_version " )
process_json " $response " ".code"
}
lookup_playstation() {
local ip_version = " $1 "
local response
response = $( make_request HEAD "https://www.playstation.com" --ip-version " $ip_version " )
grep_wrapper --perl 'country=\K[^;]*' <<< " $response " | head -n1
}
lookup_microsoft() {
local ip_version = " $1 "
local response
response = $( make_request GET "https://login.live.com" --ip-version " $ip_version " )
grep_wrapper --perl '"sRequestCountry":"\K[^"]*' <<< " $response "
}
main() {
parse_arguments " $@ "
setup_debug
trap spinner_cleanup EXIT INT TERM
detect_distro
install_dependencies
if [[ " $JSON_OUTPUT " != "true" && " $VERBOSE " != "true" ]] ; then
spinner_start
fi
check_ip_support 4
IPV4_SUPPORTED = $?
check_ip_support 6
IPV6_SUPPORTED = $?
discover_external_ips
get_asn
2025-12-06 04:59:46 -08:00
2025-10-05 03:48:13 +03:00
# Определяем страну сервера
if [[ -n " $EXTERNAL_IPV4 " ]] ; then
SERVER_COUNTRY = $( get_server_country 4)
elif [[ -n " $EXTERNAL_IPV6 " ]] ; then
SERVER_COUNTRY = $( get_server_country 6)
else
SERVER_COUNTRY = "Unknown"
fi
2025-12-06 04:59:46 -08:00
2025-10-05 03:48:13 +03:00
log " $LOG_INFO " "Server country detected: $SERVER_COUNTRY "
case " $GROUPS_TO_SHOW " in
primary)
run_service_group "primary"
;;
custom)
run_service_group "custom"
;;
cdn)
run_service_group "cdn"
;;
*)
run_service_group "primary"
run_service_group "custom"
run_service_group "cdn"
;;
esac
if [[ " $JSON_OUTPUT " != "true" && " $VERBOSE " != "true" ]] ; then
spinner_stop
fi
print_results
cleanup_debug
trap - EXIT INT TERM
}
main " $@ "