#!/usr/bin/env bash SCRIPT_URL="https://github.com/Davoyan/ipregion" DEPENDENCIES=("jq" "curl" "util-linux") USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" SPINNER_SERVICE_FILE=$(mktemp "${TMPDIR:-/tmp}/ipregion_spinner_XXXXXX") VERBOSE=false JSON_OUTPUT=false GROUPS_TO_SHOW="all" CURL_TIMEOUT=6 CURL_RETRIES=1 IPV4_ONLY=false IPV6_ONLY=false PROXY_ADDR="" INTERFACE_NAME="" RESULT_JSON="" ARR_PRIMARY=() ARR_CUSTOM=() ARR_CDN=() COLOR_HEADER="1;36" COLOR_SERVICE="0;92" 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" declare -A DEPENDENCY_COMMANDS=( [jq]="jq" [curl]="curl" [util-linux]="column" ) declare -A COUNTRY_NAMES=( [AF]="Afghanistan" [AX]="Åland Islands" [AL]="Albania" [DZ]="Algeria" [AS]="American Samoa" [AD]="Andorra" [AO]="Angola" [AI]="Anguilla" [AQ]="Antarctica" [AG]="Antigua and Barbuda" [AR]="Argentina" [AM]="Armenia" [AW]="Aruba" [AU]="Australia" [AT]="Austria" [AZ]="Azerbaijan" [BS]="Bahamas" [BH]="Bahrain" [BD]="Bangladesh" [BB]="Barbados" [BY]="Belarus" [BE]="Belgium" [BZ]="Belize" [BJ]="Benin" [BM]="Bermuda" [BT]="Bhutan" [BO]="Bolivia" [BQ]="Bonaire, Sint Eustatius and Saba" [BA]="Bosnia and Herzegovina" [BW]="Botswana" [BV]="Bouvet Island" [BR]="Brazil" [IO]="British Indian Ocean Territory" [BN]="Brunei Darussalam" [BG]="Bulgaria" [BF]="Burkina Faso" [BI]="Burundi" [CV]="Cabo Verde" [KH]="Cambodia" [CM]="Cameroon" [CA]="Canada" [KY]="Cayman Islands" [CF]="Central African Republic" [TD]="Chad" [CL]="Chile" [CN]="China" [CX]="Christmas Island" [CC]="Cocos (Keeling) Islands" [CO]="Colombia" [KM]="Comoros" [CG]="Congo" [CD]="Congo (Democratic Republic of the)" [CK]="Cook Islands" [CR]="Costa Rica" [CI]="Côte d'Ivoire" [HR]="Croatia" [CU]="Cuba" [CW]="Curaçao" [CY]="Cyprus" [CZ]="Czech Republic" [DK]="Denmark" [DJ]="Djibouti" [DM]="Dominica" [DO]="Dominican Republic" [EC]="Ecuador" [EG]="Egypt" [SV]="El Salvador" [GQ]="Equatorial Guinea" [ER]="Eritrea" [EE]="Estonia" [SZ]="Eswatini" [ET]="Ethiopia" [FK]="Falkland Islands (Malvinas)" [FO]="Faroe Islands" [FJ]="Fiji" [FI]="Finland" [FR]="France" [GF]="French Guiana" [PF]="French Polynesia" [TF]="French Southern Territories" [GA]="Gabon" [GM]="Gambia" [GE]="Georgia" [DE]="Germany" [GH]="Ghana" [GI]="Gibraltar" [GR]="Greece" [GL]="Greenland" [GD]="Grenada" [GP]="Guadeloupe" [GU]="Guam" [GT]="Guatemala" [GG]="Guernsey" [GN]="Guinea" [GW]="Guinea-Bissau" [GY]="Guyana" [HT]="Haiti" [HM]="Heard Island and McDonald Islands" [VA]="Holy See" [HN]="Honduras" [HK]="Hong Kong" [HU]="Hungary" [IS]="Iceland" [IN]="India" [ID]="Indonesia" [IR]="Iran" [IQ]="Iraq" [IE]="Ireland" [IM]="Isle of Man" [IL]="Israel" [IT]="Italy" [JM]="Jamaica" [JP]="Japan" [JE]="Jersey" [JO]="Jordan" [KZ]="Kazakhstan" [KE]="Kenya" [KI]="Kiribati" [KP]="Korea (Democratic People's Republic of)" [KR]="Korea (Republic of)" [KW]="Kuwait" [KG]="Kyrgyzstan" [LA]="Lao People's Democratic Republic" [LV]="Latvia" [LB]="Lebanon" [LS]="Lesotho" [LR]="Liberia" [LY]="Libya" [LI]="Liechtenstein" [LT]="Lithuania" [LU]="Luxembourg" [MO]="Macao" [MG]="Madagascar" [MW]="Malawi" [MY]="Malaysia" [MV]="Maldives" [ML]="Mali" [MT]="Malta" [MH]="Marshall Islands" [MQ]="Martinique" [MR]="Mauritania" [MU]="Mauritius" [YT]="Mayotte" [MX]="Mexico" [FM]="Micronesia (Federated States of)" [MD]="Moldova" [MC]="Monaco" [MN]="Mongolia" [ME]="Montenegro" [MS]="Montserrat" [MA]="Morocco" [MZ]="Mozambique" [MM]="Myanmar" [NA]="Namibia" [NR]="Nauru" [NP]="Nepal" [NL]="Netherlands" [NC]="New Caledonia" [NZ]="New Zealand" [NI]="Nicaragua" [NE]="Niger" [NG]="Nigeria" [NU]="Niue" [NF]="Norfolk Island" [MK]="North Macedonia" [MP]="Northern Mariana Islands" [NO]="Norway" [OM]="Oman" [PK]="Pakistan" [PW]="Palau" [PS]="Palestine" [PA]="Panama" [PG]="Papua New Guinea" [PY]="Paraguay" [PE]="Peru" [PH]="Philippines" [PN]="Pitcairn" [PL]="Poland" [PT]="Portugal" [PR]="Puerto Rico" [QA]="Qatar" [RE]="Réunion" [RO]="Romania" [RU]="Russia" [RW]="Rwanda" [BL]="Saint Barthélemy" [SH]="Saint Helena" [KN]="Saint Kitts and Nevis" [LC]="Saint Lucia" [MF]="Saint Martin (French part)" [PM]="Saint Pierre and Miquelon" [VC]="Saint Vincent and the Grenadines" [WS]="Samoa" [SM]="San Marino" [ST]="Sao Tome and Principe" [SA]="Saudi Arabia" [SN]="Senegal" [RS]="Serbia" [SC]="Seychelles" [SL]="Sierra Leone" [SG]="Singapore" [SX]="Sint Maarten (Dutch part)" [SK]="Slovakia" [SI]="Slovenia" [SB]="Solomon Islands" [SO]="Somalia" [ZA]="South Africa" [GS]="South Georgia and the South Sandwich Islands" [SS]="South Sudan" [ES]="Spain" [LK]="Sri Lanka" [SD]="Sudan" [SR]="Suriname" [SJ]="Svalbard and Jan Mayen" [SE]="Sweden" [CH]="Switzerland" [SY]="Syrian Arab Republic" [TW]="Taiwan" [TJ]="Tajikistan" [TZ]="Tanzania" [TH]="Thailand" [TL]="Timor-Leste" [TG]="Togo" [TK]="Tokelau" [TO]="Tonga" [TT]="Trinidad and Tobago" [TN]="Tunisia" [TR]="Turkey" [TM]="Turkmenistan" [TC]="Turks and Caicos Islands" [TV]="Tuvalu" [UG]="Uganda" [UA]="Ukraine" [AE]="United Arab Emirates" [GB]="United Kingdom" [US]="United States" [UM]="United States Minor Outlying Islands" [UY]="Uruguay" [UZ]="Uzbekistan" [VU]="Vanuatu" [VE]="Venezuela" [VN]="Vietnam" [VG]="Virgin Islands (British)" [VI]="Virgin Islands (U.S.)" [WF]="Wallis and Futuna" [EH]="Western Sahara" [YE]="Yemen" [ZM]="Zambia" [ZW]="Zimbabwe" [XK]="Kosovo" [EU]="European Union" [WW]="Worldwide" ) 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|www.cloudflare.com|/cdn-cgi/trace" [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}" [IP_SB]="ip.sb|api.ip.sb|/geoip/{ip}" [2IP]="2ip.io|api.2ip.io" ) 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" "IP_SB" "2IP" ) declare -A PRIMARY_SERVICES_CUSTOM_HANDLERS=( [CLOUDFLARE]="lookup_cloudflare" [IPLOCATION_COM]="lookup_iplocation_com" [2IP]="lookup_2ip" ) declare -A SERVICE_HEADERS=( [IPREGISTRY]="Origin: https://ipregistry.co" [MAXMIND]="Referer: https://www.maxmind.com" [IP_SB]="User-Agent: ${USER_AGENT}" ) declare -A CUSTOM_SERVICES=( [GOOGLE]="Google" [GOOGLE_SEARCH_CAPTCHA]="Google Search Captcha" [YOUTUBE]="YouTube" [YOUTUBE_PREMIUM]="YouTube Premium" [YOUTUBE_MUSIC]="YouTube Music" [TWITCH]="Twitch" [CHATGPT]="ChatGPT" [NETFLIX]="Netflix" [SPOTIFY]="Spotify" [DEEZER]="Deezer" [REDDIT]="Reddit" [REDDIT_GUEST_ACCESS]="Reddit (Guest Access)" [AMAZON_PRIME]="Amazon Prime" [APPLE]="Apple" [STEAM]="Steam" [TIKTOK]="Tiktok" [YOUTUBE_CDN]="YouTube CDN" [OOKLA_SPEEDTEST]="Ookla Speedtest" [JETBRAINS]="JetBrains" [BING]="Bing" ) CUSTOM_SERVICES_ORDER=( "GOOGLE" "GOOGLE_SEARCH_CAPTCHA" "YOUTUBE" "YOUTUBE_PREMIUM" "YOUTUBE_MUSIC" "TWITCH" "CHATGPT" "NETFLIX" "SPOTIFY" "DEEZER" "REDDIT" "REDDIT_GUEST_ACCESS" "AMAZON_PRIME" "APPLE" "STEAM" "TIKTOK" "OOKLA_SPEEDTEST" "JETBRAINS" "BING" ) declare -A CUSTOM_SERVICES_HANDLERS=( [GOOGLE]="lookup_google" [GOOGLE_SEARCH_CAPTCHA]="lookup_google_search_captcha" [YOUTUBE]="lookup_youtube" [YOUTUBE_PREMIUM]="lookup_youtube_premium" [YOUTUBE_MUSIC]="lookup_youtube_music" [TWITCH]="lookup_twitch" [CHATGPT]="lookup_chatgpt" [NETFLIX]="lookup_netflix" [SPOTIFY]="lookup_spotify" [DEEZER]="lookup_deezer" [REDDIT]="lookup_reddit" [REDDIT_GUEST_ACCESS]="lookup_reddit_guest_access" [AMAZON_PRIME]="lookup_amazon_prime" [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" [BING]="lookup_bing" ) declare -A CDN_SERVICES=( [YOUTUBE_CDN]="YouTube CDN" [NETFLIX_CDN]="Netflix CDN" ) CDN_SERVICES_ORDER=( "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" ) IDENTITY_SERVICES=( "ident.me" "ifconfig.me" "api64.ipify.org" "ifconfig.co" "ifconfig.me" ) IPV6_OVER_IPV4_SERVICES=( "IPINFO_IO" ) get_tmpdir() { if [[ -n "$TMPDIR" ]]; then echo "$TMPDIR" elif [[ -d /data/data/com.termux/files/usr/tmp ]]; then echo "/data/data/com.termux/files/usr/tmp" else echo "/tmp" fi } 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 </dev/null 2>&1 } check_missing_dependencies() { local missing_pkgs=() local cmd for pkg in "${DEPENDENCIES[@]}"; do cmd="${DEPENDENCY_COMMANDS[$pkg]:-$pkg}" if ! is_installed "$cmd"; then missing_pkgs+=("$pkg") fi done echo "${missing_pkgs[@]}" } prompt_for_installation() { local missing_pkgs=("$@") echo "Missing dependencies: ${missing_pkgs[*]}" read -r -p "Do you want to install them? [y/N]: " answer answer=${answer,,} case "${answer,,}" in y | yes) return 0 ;; *) exit 0 ;; esac } get_package_manager() { # Check if the script is running in Termux if [[ -d /data/data/com.termux ]]; then echo "termux" return fi if [[ -f /etc/os-release ]]; then . /etc/os-release case "$ID" in debian | ubuntu) echo "apt" ;; arch) echo "pacman" ;; fedora) echo "dnf" ;; *) error_exit "Unknown distribution: $ID. Please install dependencies manually." ;; esac else error_exit "File /etc/os-release not found, unable to determine distribution. Please install dependencies manually." fi } install_with_package_manager() { local pkg_manager="$1" local packages=("${@:2}") local use_sudo="" if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then use_sudo="sudo" log "$LOG_INFO" "Running as non-root user, using sudo" fi case "$pkg_manager" in *apt) $use_sudo "$pkg_manager" update if [[ " ${packages[*]} " == *" util-linux "* ]]; then $use_sudo env NEEDRESTART_MODE=a "$pkg_manager" install -y util-linux bsdmainutils fi $use_sudo env NEEDRESTART_MODE=a "$pkg_manager" install -y "${packages[@]}" ;; *pacman) $use_sudo "$pkg_manager" -Syy --noconfirm "${packages[@]}" ;; *dnf) $use_sudo "$pkg_manager" install -y "${packages[@]}" ;; termux) apt update apt install -y "${packages[@]}" ;; *) error_exit "Unknown package manager: $pkg_manager" ;; esac } install_dependencies() { local missing_packages local pkg_manager log "$LOG_INFO" "Checking for dependencies" read -r -a missing_packages <<<"$(check_missing_dependencies)" if [[ ${#missing_packages[@]} -eq 0 ]]; then log "$LOG_INFO" "All dependencies are installed" return 0 fi prompt_for_installation "${missing_packages[@]}" /dev/null 2>&1 <<<"$json" } process_json() { local json="$1" local jq_filter="$2" jq -r "$jq_filter" <<<"$json" } format_value() { local value="$1" local not_available="$2" if [[ "$value" == "$not_available" ]]; then color NULL "$value" else bold "$value" fi } 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 ;; -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 } check_ip_support() { local version="$1" log "$LOG_INFO" "Checking for IPv${version} support" if ip -${version} addr show scope global 2>/dev/null | grep -q "inet${version}"; then log "$LOG_INFO" "IPv${version} is supported" return 0 fi log "$LOG_WARN" "IPv${version} is not supported on this system" return 1 } IDENTITY_SERVICES=( "ident.me" "ifconfig.me" "api64.ipify.org" "ifconfig.co" "ifconfig.me" ) get_external_ip() { local results=() local ip local counts declare -A counts if [[ "$IPV4_ONLY" == true ]] || [[ "$IPV6_ONLY" != true ]]; then log "$LOG_INFO" "Checking external IPv4 addresses" for service in "${IDENTITY_SERVICES[@]}"; do ip="$(make_request GET "https://$service" --ip-version 4 2>/dev/null)" if [[ -n "$ip" ]]; then counts[$ip]=$(( ${counts[$ip]:-0} + 1 )) log "$LOG_INFO" "[$service] IPv4: $ip" fi done EXTERNAL_IPV4="" for ip in "${!counts[@]}"; do if [[ ${counts[$ip]} -ge 2 ]]; then EXTERNAL_IPV4="$ip" log "$LOG_INFO" "Confirmed external IPv4: $EXTERNAL_IPV4" break fi done # Fallback, если совпадений нет if [[ -z "$EXTERNAL_IPV4" ]]; then for ip in "${!counts[@]}"; do EXTERNAL_IPV4="$ip" log "$LOG_INFO" "Fallback external IPv4: $EXTERNAL_IPV4" break done fi fi unset counts declare -A counts if [[ "$IPV6_ONLY" == true ]] || { [[ "$IPV6_SUPPORTED" -eq 0 ]] && [[ "$IPV4_ONLY" != true ]]; }; then log "$LOG_INFO" "Checking external IPv6 addresses" for service in "${IDENTITY_SERVICES[@]}"; do ip="$(make_request GET "https://$service" --ip-version 6 2>/dev/null)" if [[ -n "$ip" ]]; then counts[$ip]=$(( ${counts[$ip]:-0} + 1 )) log "$LOG_INFO" "[$service] IPv6: $ip" fi done EXTERNAL_IPV6="" for ip in "${!counts[@]}"; do if [[ ${counts[$ip]} -ge 2 ]]; then EXTERNAL_IPV6="$ip" log "$LOG_INFO" "Confirmed external IPv6: $EXTERNAL_IPV6" break fi done # Fallback, если совпадений нет if [[ -z "$EXTERNAL_IPV6" ]]; then for ip in "${!counts[@]}"; do EXTERNAL_IPV6="$ip" log "$LOG_INFO" "Fallback external IPv6: $EXTERNAL_IPV6" break done fi fi } get_asn() { local ip ip_version response if [[ -n "$EXTERNAL_IPV4" ]]; then ip="$EXTERNAL_IPV4" ip_version=4 else ip="$EXTERNAL_IPV6" ip_version=6 fi log "$LOG_INFO" "Getting ASN info for IP $ip" response=$(make_request GET "https://ipinfo.check.place/$ip" --ip-version "$ip_version" 2>/dev/null) asn=$(process_json "$response" ".ASN.AutonomousSystemNumber") asn_name=$(process_json "$response" ".ASN.AutonomousSystemOrganization") if [[ -z "$asn" || "$asn" == "null" || -z "$asn_name" || "$asn_name" == "null" ]]; then log "$LOG_INFO" "Primary source failed, trying backup source" response=$(make_request GET "https://geoip.oxl.app/api/ip/$ip" --ip-version "$ip_version") asn=$(process_json "$response" ".asn") asn_name=$(process_json "$response" ".organization.name") asn_name=${asn_name#null} log "$LOG_INFO" "ASN info (backup source): AS$asn $asn_name" else log "$LOG_INFO" "ASN info (primary source): AS$asn $asn_name" fi } 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 } make_request() { local method="$1" local url="$2" shift 2 local ip_version user_agent json data headers response_with_code response http_code local curl_args=( --silent --compressed --retry-connrefused --retry-all-errors --retry "$CURL_RETRIES" --connect-timeout "$CURL_TIMEOUT" --max-time "$CURL_TIMEOUT" --request "$method" -w '\n%{http_code}' ) 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 ;; esac done if [[ "$ip_version" == "4" ]]; then curl_args+=(-4) else curl_args+=(-6) fi headers+=("Accept-Encoding: gzip, deflate, br, zstd") 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 "$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=$(timeout "$CURL_TIMEOUT"s curl "${curl_args[@]}") local exit_status=$? if [[ $exit_status -eq 124 ]]; then echo "" return 0 fi http_code=$(tail -n1 <<<"$response_with_code") response=$(sed '$d' <<<"$response_with_code") if [[ "$http_code" == "403" || "$http_code" == "429" ]]; then echo "" 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 [[ -z "$response" || "$response" == *""$SPINNER_SERVICE_FILE" fi 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 ipv4_clean=${ipv4_result#null} ipv6_clean=${ipv6_result#null} add_result "primary" "$display_name" "$ipv4_clean" "$ipv6_clean" #add_result "primary" "$display_name" "$ipv4_result" "$ipv6_result" } process_custom_service() { local service="$1" local ipv4_result ipv6_result local display_name="${CUSTOM_SERVICES[$service]:-$service}" local handler_func="${CUSTOM_SERVICES_HANDLERS[$service]}" if [[ -z "$display_name" ]]; then display_name="$service" fi echo "$display_name" >"$SPINNER_SERVICE_FILE" if [[ -z "$handler_func" ]]; then log "$LOG_WARN" "Unknown custom service: $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 "custom" "$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 -Fxq "$service_name"; then log "$LOG_INFO" "Skipping service: $service_name" continue fi if [[ "$group" == "custom" ]]; then is_custom=true else is_custom=false fi if [[ "$group" == "cdn" ]]; then is_cdn=true else is_cdn=false fi if [[ "$is_custom" == true ]]; then process_service "$service_name" true elif [[ "$is_cdn" == true ]]; then handler_func="${CUSTOM_SERVICES_HANDLERS[$service_name]}" display_name="${CDN_SERVICES[$service_name]}" if [[ -n "$handler_func" ]]; then echo "$display_name" >"$SPINNER_SERVICE_FILE" result=$("$handler_func" 4) add_result "cdn" "$display_name" "$result" "" fi else process_service "$service_name" fi done } run_all_services() { local service_name for func in $(declare -F | awk '{print $3}' | grep '^lookup_'); do service_name=${func#lookup_} service_name_uppercase=${service_name^^} if printf "%s\n" "${EXCLUDED_SERVICES[@]}" | grep -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 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 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), 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' local col_width=32 [[ "$IPV6_ONLY" != true && -n "$EXTERNAL_IPV4" ]] && show_ipv4=1 [[ "$IPV4_ONLY" != true && -n "$EXTERNAL_IPV6" ]] && show_ipv6=1 if [[ "$group_title" != "GeoIP services" ]]; then #printf "%s\n\n" "$(color HEADER "$group_title")" printf "%-${col_width}s" "$(color TABLE_HEADER 'Service')" fi if [[ "$group_title" != "GeoIP services" ]]; then [[ $show_ipv4 -eq 1 ]] && printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv4')" [[ $show_ipv6 -eq 1 ]] && 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 "%-${col_width}s" "$(color SERVICE "$s")" if [[ $show_ipv4 -eq 1 ]]; then [[ "$v4" == "null" || -z "$v4" ]] && v4="$na" printf "%s%s" "$separator" "$(format_value "$v4" "$na")" fi if [[ $show_ipv6 -eq 1 ]]; then [[ "$v6" == "null" || -z "$v6" ]] && v6="$na" printf "%s%s" "$separator" "$(format_value "$v6" "$na")" fi printf "\n" done } 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 "Forked by Davoyan")" "$(color URL "$SCRIPT_URL")" if [[ -n "$EXTERNAL_IPV4" ]]; then printf "%s: %s\n" "$(color HEADER 'IPv4')" "$(bold "$(mask_ipv4 "$ipv4")")" fi if [[ -n "$EXTERNAL_IPV6" ]]; then printf "%s: %s\n" "$(color HEADER 'IPv6')" "$(bold "$(mask_ipv6 "$ipv6")")" fi printf "%s: %s\n\n" "$(color HEADER 'ASN')" "$(bold "AS$asn $asn_name")" } print_legend() { local separator="|||" echo printf "%s\n\n" "$(color HEADER 'Legend')" local stats stats=$(jq -r ' def clean: tostring | gsub("\u001b\\[[0-9;]*m"; "") | select(test("^(No|Yes|N/A|null)$") | not); def counts(stream): [stream | select(. != null) | clean] as $arr | reduce ($arr[]) as $c ({}; .[$c] += 1) | {total: ($arr | length), counts: .}; { ipv4: counts( (.results.primary[].ipv4?, .results.custom[].ipv4?) ), ipv6: counts( (.results.primary[].ipv6?, .results.custom[].ipv6?) ) } ' <<<"$RESULT_JSON") local codes codes=$(jq -r ' [ (.ipv4.counts | keys[]?), (.ipv6.counts | keys[]?) ] | unique[] ' <<<"$stats") local show_ipv4=true show_ipv6=true if [[ "${IPV4_ONLY,,}" == "true" && "${IPV6_ONLY,,}" != "true" ]]; then show_ipv6=false fi if [[ "${IPV6_ONLY,,}" == "true" && "${IPV4_ONLY,,}" != "true" ]]; then show_ipv4=false fi if [[ -n "$EXTERNAL_IPV6" ]]; then show_ipv6=true else show_ipv6=false fi { local header_parts=() header_parts+=("$(color TABLE_HEADER 'Code')") header_parts+=("$(color TABLE_HEADER 'Country')") $show_ipv4 && header_parts+=("$(color TABLE_HEADER '% IPv4')") $show_ipv6 && header_parts+=("$(color TABLE_HEADER '% IPv6')") local header="${header_parts[0]}" for ((i=1; i<${#header_parts[@]}; i++)); do header+="$separator${header_parts[i]}" done echo "$header" while read -r code; do local country ipv4_num ipv6_num ipv4_str ipv6_str country="${COUNTRY_NAMES[$code]:-Unknown}" ipv4_num=$(jq -r --arg c "$code" ' if (.ipv4.total == 0) then 0 else ((.ipv4.counts[$c] // 0) / .ipv4.total * 100 | round) end ' <<<"$stats") ipv6_num=$(jq -r --arg c "$code" ' if (.ipv6.total == 0) then 0 else ((.ipv6.counts[$c] // 0) / .ipv6.total * 100 | round) end ' <<<"$stats") ipv4_str="" ipv6_str="" [[ "$ipv4_num" -ne 0 ]] && ipv4_str="${ipv4_num}%" [[ "$ipv6_num" -ne 0 ]] && ipv6_str="${ipv6_num}%" printf '%s\t%s\t%s\t%s\t%s\t%s\n' \ "$ipv4_num" "$ipv6_num" "$(color SERVICE "$code")" "$(format_value "$country" "$country")" "$ipv4_str" "$ipv6_str" done <<< "$codes" \ | sort -t$'\t' -k1,1nr -k2,2nr \ | awk -F $'\t' -v OFS="$separator" \ "$( if $show_ipv4 && $show_ipv6; then echo '{print $3,$4,$5,$6}' elif $show_ipv4; then echo '{print $3,$4,$5}' elif $show_ipv6; then echo '{print $3,$4,$6}' else echo '{print $3,$4,$5,$6}' fi )" } | column -t -s "$separator" } 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" print_table_group "primary" "GeoIP services" print_legend #printf "\n\n-----CDN-----\n" #print_table_group "cdn" "CDN 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() { local ip_version="$1" local response response=$(make_request GET "https://www.cloudflare.com/cdn-cgi/trace" --ip-version "$ip_version") while IFS='=' read -r key value; do if [[ "$key" == "loc" ]]; then echo "$value" break fi done <<<"$response" } 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" } lookup_2ip() { local ip_version="$1" local response ip response=$(make_request GET "https://api.2ip.io" --ip-version "$ip_version" --data "ip=$ip") process_json "$response" ".code" } get_country_code() { local country="$1" local json json=$(curl --max-time 5 -s "https://restcountries.com/v3.1/all?fields=name,cca2") if [[ -z "$json" ]] || ! grep -q '"name"' <<<"$json"; then echo "" return fi jq -r --arg COUNTRY "$country" ' .[] | select(.name.common | ascii_downcase == ($COUNTRY | ascii_downcase)) | .cca2 ' <<<"$json" } lookup_google() { local ip_version="$1" local sed_filter='s/.*"[a-z]\{2\}_\([A-Z]\{2\}\)".*/\1/p' local sed_fallback_filter='s/.*"[a-z]\{2\}-\([A-Z]\{2\}\)".*/\1/p' local response result response=$(make_request GET "https://www.google.com" \ --user-agent "$USER_AGENT" \ --ip-version "$ip_version") result=$(sed -n "$sed_filter" <<<"$response") if [[ -z "$result" ]]; then result=$(sed -n "$sed_fallback_filter" <<<"$response" | tail -n 1) fi if [[ -z "$result" ]]; then local curl_ip_flag country if [[ "$ip_version" == "4" ]]; then curl_ip_flag="-4" elif [[ "$ip_version" == "6" ]]; then curl_ip_flag="-6" else curl_ip_flag="-4" fi curl_args=() if [[ -n "$PROXY_ADDR" ]]; then curl_args+=(--proxy "socks5://$PROXY_ADDR") fi if [[ -n "$INTERFACE_NAME" ]]; then curl_args+=(--interface "$INTERFACE_NAME") fi country=$(timeout "$CURL_TIMEOUT" curl $curl_ip_flag -sL "${curl_args[@]}" 'https://play.google.com/' -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' -H 'accept-language: en-US;q=0.9' -H 'priority: u=0, i' -H 'sec-ch-ua: "Chromium";v="131", "Not_A Brand";v="24", "Google Chrome";v="131"' -H 'sec-ch-ua-mobile: ?0' -H 'sec-ch-ua-platform: "Windows"' -H 'sec-fetch-dest: document' -H 'sec-fetch-mode: navigate' -H 'sec-fetch-site: none' -H 'sec-fetch-user: ?1' -H 'upgrade-insecure-requests: 1' -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' | grep -oP '
\K[^<(]+') country=$(echo "$country" | xargs) # убираем пробелы if [[ -n "$country" ]]; then result=$(get_country_code "$country") fi fi echo "$result" } lookup_twitch() { local ip_version="$1" local response response=$(make_request POST "https://gql.twitch.tv/gql" \ --header 'Client-Id: kimne78kx3ncx6brgo4mv6wki5h1ko' \ --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: client-zUdXdSTygXJdzoE0sWTkP8GKTVsUMF2IRM7ShVO2JAG") 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=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=1" --ip-version "$ip_version") if is_valid_json "$response"; then process_json "$response" ".client.location.country" else echo "" fi } lookup_spotify() { local ip_version="$1" local response response=$(make_request GET "https://accounts.spotify.com/en/login" --ip-version "$ip_version") sed -n 's/.*"geoLocationCountryCode":"\([^"]*\)".*/\1/p' <<<"$response" } lookup_deezer() { local ip_version="$1" local response response=$(make_request GET "https://www.deezer.com/en/offers" --ip-version "$ip_version") echo "$response" | sed -n "s/.*'country': '\(.*\)'.*/\1/p" } lookup_reddit() { local ip_version="$1" local basic_access_token="Basic b2hYcG9xclpZdWIxa2c6" 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_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 [[ -n "$response" ]]; 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() { local ip_version="$1" local result curl_ip_flag service_name rest ipv4 ipv6 if [[ "$ip_version" == "4" ]]; then curl_ip_flag="-4" elif [[ "$ip_version" == "6" ]]; then curl_ip_flag="-6" else curl_ip_flag="-4" fi #result=$(timeout "${CURL_TIMEOUT:-5}" curl $curl_ip_flag -s -A "$USER_AGENT" "https://www.youtube.com" \ # | grep -oP '"countryCode":"\K\w+') result=$(make_request GET "https://www.youtube.com" --ip-version "$ip_version" --user-agent "$USER_AGENT" \ | grep -oP '"countryCode":"\K\w+') if [[ -z "$result" || "$result" == "null" || "$result" == "n/a" || ${#result} -gt 7 ]]; then for entry in "${ARR_CUSTOM[@]}"; do service_name="${entry%%|||*}" if [[ "$service_name" == "Google" ]]; then rest="${entry#*|||}" ipv4="${rest%%|||*}" ipv6="${rest#*|||}" if [[ "$ip_version" == "4" ]]; then result="$ipv4" else result="$ipv6" fi break fi done #result=$(lookup_google "$ip_version") fi echo "$result" } 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=CAISNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIwMjUwNzMwLjA1X3AwGgJlbiACGgYIgPC_xAY" \ --header "Accept-Language: en-US,en;q=0.9") if [[ -z "$response" ]]; then echo "" return fi is_available=$(grep -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_youtube_music() { local ip_version="$1" local response is_available response=$(make_request GET "https://music.youtube.com/" \ --ip-version "$ip_version" \ --user-agent "$USER_AGENT" \ --header "Cookie: SOCS=CAISNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIwMjUwNzMwLjA1X3AwGgJlbiACGgYIgPC_xAY" \ --header "Accept-Language: en-US,en;q=0.9") if [[ -z "$response" ]]; then echo "" return fi is_available=$(grep -io "YouTube Music is not available in your area" <<<"$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 -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_bing() { local ip_version="$1" local curl_ip_flag country if [[ "$ip_version" == "4" ]]; then curl_ip_flag="-4" elif [[ "$ip_version" == "6" ]]; then curl_ip_flag="-6" else curl_ip_flag="-4" fi curl_args=() if [[ -n "$PROXY_ADDR" ]]; then curl_args+=(--proxy "socks5://$PROXY_ADDR") fi if [[ -n "$INTERFACE_NAME" ]]; then curl_args+=(--interface "$INTERFACE_NAME") fi local tmpresult=$(timeout "$CURL_TIMEOUT" curl -sL $curl_ip_flag "${curl_args[@]}" "https://www.bing.com/search?q=cats" --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36") local isCN=$(echo "$tmpresult" | grep 'cn.bing.com') local region=$(echo "$tmpresult" | grep -woP 'Region\s{0,}:\s{0,}"\K[^"]+') if [ -n "$isCN" ]; then region='CN' fi region="${region:0:2}" if [[ "$region" == "WW" ]]; then region="" fi echo "$region" } lookup_amazon_prime() { local ip_version="$1" local curl_ip_flag country if [[ "$ip_version" == "4" ]]; then curl_ip_flag="-4" elif [[ "$ip_version" == "6" ]]; then curl_ip_flag="-6" else curl_ip_flag="-4" fi curl_args=() if [[ -n "$PROXY_ADDR" ]]; then curl_args+=(--proxy "socks5://$PROXY_ADDR") fi if [[ -n "$INTERFACE_NAME" ]]; then curl_args+=(--interface "$INTERFACE_NAME") fi local tmpresult=$(timeout "$CURL_TIMEOUT" curl -sL $curl_ip_flag "${curl_args[@]}" "https://www.primevideo.com" --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36") local isBlocked=$(echo "$tmpresult" | grep -i 'isServiceRestricted') local region=$(echo "$tmpresult" | grep -woP '"currentTerritory":"\K[^"]+' | head -n 1) if [[ -z "$is_available" ]]; then region="${region:0:2}" echo "$region" else is_available="No" color_name="HEART" print_value_or_colored "$is_available" "$color_name" fi } 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 GET "https://store.steampowered.com" --ip-version "$ip_version") sed -n 's/.*"countrycode":"\([^"]*\)".*/\1/p' <<<"$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_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" } lookup_netflix_cdn() { local ip_version="$1" local response # TODO: Make contant response=$(make_request GET "https://api.fast.com/netflix/speedtest/v2?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&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" } main() { parse_arguments "$@" install_dependencies check_ip_support 4 IPV4_SUPPORTED=$? check_ip_support 6 IPV6_SUPPORTED=$? get_external_ip get_asn if [[ "$JSON_OUTPUT" != true && "$VERBOSE" != true ]]; then # Ловим только Ctrl+C и TERM, при этом останавливаем спиннер и завершаем скрипт trap 'spinner_stop; exit' INT TERM spinner_start fi case "$GROUPS_TO_SHOW" in custom) run_service_group "custom" ;; primary) run_service_group "primary" ;; cdn) run_service_group "cdn" ;; *) run_service_group "custom" run_service_group "primary" run_service_group "cdn" ;; esac if [[ "$JSON_OUTPUT" != true && "$VERBOSE" != true ]]; then spinner_stop trap - INT TERM fi print_results } main "$@"