media 流媒体与 IP 检测
要检测 VPS 的 IP 能解锁哪些流媒体和 AI 服务,运行下面的命令。脚本只用 curl 访问各网站并根据返回内容判断,同时查询 IP 的 ASN 与类型,不安装任何软件。
别名 unlock
$ curl -fsSL xyz.sh/media | bash
b86e330452726fe8e8f1f7712eac6f9e2dcaebae4de04cbacc2a8366194ed3fd
· 与 curl 拿到的脚本一致,也可访问 /media.sh.sha256
所有脚本都通过 bash -n 与 ShellCheck 检查,并在 Debian 12/11、Ubuntu 24.04/22.04/20.04/18.04、Rocky 9、AlmaLinux 9、CentOS 7 的官方 Docker 镜像中以 root 运行 curl | bash 测试(结果见各页“容器测试结果”)。容器里没有 systemd、内核模块和真实公网线路,相关部分需要在真实 VPS 上确认。
它会做什么
xyz.sh 自己写的轻量检测脚本,只用 curl 请求,不安装任何东西:Netflix(非自制剧 / 仅自制剧)、Disney+、YouTube Premium 地区、ChatGPT(含 App 是否判定 VPN)、Claude、Gemini、TikTok 地区;IP 信息(ASN、国家、机房 / 住宅 / 移动网络、VPN / 代理标记)来自免费公开接口 api.ipapi.is 与 ip-api.com(查询会把服务器 IP 发给这些接口,可用 --no-ip 跳过)。这些都是根据网站当前返回内容的启发式判断,网站经常改版,结果仅供参考。需要更全面的检测时,--full 会运行 lmc999/RegionRestrictionCheck,--ipquality 会运行 xykt/IPQuality(下载、预览后确认才执行,署名与许可见页面)。
容器测试结果
在各发行版官方 Docker 镜像中以 root、无终端方式运行 curl | bash 的结果。partial 表示容器内无法完整验证(例如没有 systemd、内核模块或测速协议被测试环境的代理拦截),需要在真实 VPS 上确认。 (2026-11-11)
| OS | 结果 |
|---|---|
| debian:12 | partial |
| debian:11 | partial |
| ubuntu:24.04 | partial |
| ubuntu:22.04 | partial |
| ubuntu:20.04 | partial |
| ubuntu:18.04 | partial |
| rockylinux:9 | partial |
| almalinux:9 | partial |
| centos:7 | partial |
常见问题
- 这个脚本安全吗?
- xyz.sh 不埋点、不偷偷执行未说明的操作,完整源码可在页面上查看,也可以用 curl -fsSL xyz.sh/<name>.sh 下载后先读再运行;每个脚本都有 /<name>.sh.sha256 校验值。把远程脚本管道给 bash 仍有风险,请先阅读源码。IP 信息查询会把服务器 IP 发给 api.ipapi.is 和 ip-api.com(可用 --no-ip 跳过)。--full 和 --ipquality 运行的是第三方脚本 lmc999/RegionRestrictionCheck 与 xykt/IPQuality,会先预览并确认。
- 支持哪些系统?
- 声明支持 Debian、Ubuntu、CentOS、Rocky、AlmaLinux。脚本会检测发行版,无法识别时会提示。已在这些系统的 Docker 容器里做过语法、--dry-run 与可运行部分的测试(结果见 GitHub 上的 tests/RESULTS.md);依赖 systemd、内核模块或公网的部分需要在真实 VPS 上验证。
- 需要 root 吗?
- 不需要。普通用户也能运行;以 root 运行时部分工具可以安装到系统目录。
- 结果为什么和别的脚本不一样?
- 各网站经常改版,所有解锁脚本都是根据当前页面或接口的返回内容推断的。不同时间、IPv4 与 IPv6、DNS 解锁方式都会影响结果。最终以实际登录使用为准。
完整源码
以下就是 curl 实际拿到的内容(公共库 + 脚本正文)。运行前请先读一遍。
#!/usr/bin/env bash
# Served by xyz.sh/media
# Human-readable page: https://xyz.sh/media
# This file is the common library followed by the script body.
# No telemetry is added by xyz.sh.
# =============================================================================
# xyz.sh common library
# Shared helpers for every script served by xyz.sh.
#
# Promises:
# - No telemetry, analytics, or background "phone-home" calls
# - No hidden installs or config changes beyond what the script announces
# - Official upstream URLs are printed before any remote code is executed
# - Destructive or system-changing steps ask for confirmation (or YES=1 / -y)
# =============================================================================
if [[ -n "${XYZ_COMMON_LOADED:-}" ]]; then
# When sourced a second time, return. When executed as a file, return fails.
# shellcheck disable=SC2317
return 0 2>/dev/null || true
fi
# shellcheck disable=SC2034
XYZ_COMMON_LOADED=1
set -euo pipefail
XYZ_VERSION="1.3.0"
XYZ_BASE="${XYZ_BASE:-https://xyz.sh}"
XYZ_NAME="${XYZ_NAME:-unknown}"
# --- language ---------------------------------------------------------------
# Override with XYZ_LANG=zh or XYZ_LANG=en.
if [[ -z "${XYZ_LANG:-}" ]]; then
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in
zh*|ZH*|cmn*|CMN*) XYZ_LANG="zh" ;;
*) XYZ_LANG="en" ;;
esac
fi
xyz_t() {
# xyz_t "English text" "中文文本"
if [[ "${XYZ_LANG}" == "zh" ]]; then
printf '%s' "${2:-$1}"
else
printf '%s' "$1"
fi
}
# --- colors (TTY only) ------------------------------------------------------
# XYZ_COLOR=always|never|auto (default auto: TTY and no NO_COLOR); --no-color.
_xyz_color=0
case "${XYZ_COLOR:-auto}" in
always) _xyz_color=1 ;;
never) _xyz_color=0 ;;
*) [[ -t 1 && -z "${NO_COLOR:-}" ]] && _xyz_color=1 ;;
esac
[[ "${XYZ_NO_COLOR:-0}" == "1" ]] && _xyz_color=0
if [[ "${_xyz_color}" == "1" ]]; then
XYZ_C_RESET=$'\033[0m'
XYZ_C_BOLD=$'\033[1m'
XYZ_C_DIM=$'\033[2m'
XYZ_C_RED=$'\033[31m'
XYZ_C_GREEN=$'\033[32m'
XYZ_C_YELLOW=$'\033[33m'
XYZ_C_CYAN=$'\033[36m'
else
XYZ_C_RESET=""
XYZ_C_BOLD=""
XYZ_C_DIM=""
XYZ_C_RED=""
XYZ_C_GREEN=""
XYZ_C_YELLOW=""
XYZ_C_CYAN=""
fi
xyz_info() { printf '%s[INFO]%s %s\n' "${XYZ_C_GREEN}" "${XYZ_C_RESET}" "$*"; }
xyz_warn() { printf '%s[WARN]%s %s\n' "${XYZ_C_YELLOW}" "${XYZ_C_RESET}" "$*" >&2; }
xyz_error() { printf '%s[ERROR]%s %s\n' "${XYZ_C_RED}" "${XYZ_C_RESET}" "$*" >&2; }
xyz_step() { printf '%s==>%s %s\n' "${XYZ_C_CYAN}" "${XYZ_C_RESET}" "$*"; }
# --- args / init ------------------------------------------------------------
XYZ_ARGS=()
XYZ_YES=0
XYZ_DRY_RUN=0
xyz_parse_global_args() {
local filtered=()
local arg
for arg in "$@"; do
case "${arg}" in
-y|--yes)
XYZ_YES=1
;;
--lang=zh|zh)
XYZ_LANG="zh"
;;
--lang=en|en)
XYZ_LANG="en"
;;
--dry-run)
XYZ_DRY_RUN=1
;;
--no-color)
XYZ_NO_COLOR=1
XYZ_C_RESET=""; XYZ_C_BOLD=""; XYZ_C_DIM=""; XYZ_C_RED=""
XYZ_C_GREEN=""; XYZ_C_YELLOW=""; XYZ_C_CYAN=""
;;
--help|-h)
XYZ_HELP=1
filtered+=("${arg}")
;;
*)
filtered+=("${arg}")
;;
esac
done
XYZ_ARGS=("${filtered[@]+"${filtered[@]}"}")
if [[ "${YES:-}" =~ ^(1|yes|true|YES)$ || "${XYZ_ASSUME_YES:-}" == "1" ]]; then
XYZ_YES=1
fi
# Exported so individual scripts can read flags set here.
export XYZ_ARGS XYZ_YES XYZ_LANG XYZ_HELP="${XYZ_HELP:-}" XYZ_DRY_RUN="${XYZ_DRY_RUN:-0}"
export XYZ_NO_COLOR="${XYZ_NO_COLOR:-0}"
if declare -F xyz_ui_init >/dev/null; then xyz_ui_init; fi
}
xyz_banner() {
local title="${XYZ_TITLE:-$XYZ_NAME}"
printf '\n%s%sxyz.sh/%s%s v%s\n' "${XYZ_C_BOLD}" "${XYZ_C_CYAN}" "${XYZ_NAME}" "${XYZ_C_RESET}" "${XYZ_VERSION}"
printf '%s\n' "${XYZ_C_DIM}----------------------------------------${XYZ_C_RESET}"
printf ' %s: %s/%s\n' "$(xyz_t "Page" "页面")" "${XYZ_BASE}" "${XYZ_NAME}"
printf ' %s: %s/%s.sh\n' "$(xyz_t "Raw" "源码")" "${XYZ_BASE}" "${XYZ_NAME}"
if [[ -n "${XYZ_UPSTREAM:-}" ]]; then
printf ' %s: %s\n' "$(xyz_t "Upstream" "上游来源")" "${XYZ_UPSTREAM}"
fi
printf ' %s\n' "$(xyz_t "No telemetry, no hidden actions." "无遥测、无隐藏操作。")"
printf '%s\n\n' "${XYZ_C_DIM}----------------------------------------${XYZ_C_RESET}"
if [[ -n "${title}" && "${title}" != "${XYZ_NAME}" ]]; then
xyz_step "${title}"
fi
}
xyz_init() {
xyz_parse_global_args "$@"
xyz_detect_os
xyz_banner
}
# --- root / OS --------------------------------------------------------------
xyz_is_root() {
[[ "${EUID:-$(id -u)}" -eq 0 ]]
}
xyz_require_root() {
if ! xyz_is_root; then
xyz_error "$(xyz_t \
"This script must be run as root. Try: sudo bash" \
"请使用 root 运行。可尝试:sudo bash")"
exit 1
fi
}
xyz_prefer_root() {
if ! xyz_is_root; then
xyz_warn "$(xyz_t \
"Not running as root; some information or tests may be limited." \
"当前不是 root,部分信息或测试可能不完整。")"
fi
}
XYZ_OS_ID=""
XYZ_OS_LIKE=""
XYZ_OS_VERSION=""
XYZ_OS_FAMILY="" # debian | rhel | unknown
XYZ_OS_PRETTY=""
xyz_detect_os() {
XYZ_OS_ID=""
XYZ_OS_LIKE=""
XYZ_OS_VERSION=""
XYZ_OS_FAMILY="unknown"
XYZ_OS_PRETTY="unknown"
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
XYZ_OS_ID="${ID:-}"
XYZ_OS_LIKE="${ID_LIKE:-}"
XYZ_OS_VERSION="${VERSION_ID:-}"
XYZ_OS_PRETTY="${PRETTY_NAME:-$XYZ_OS_ID}"
export XYZ_OS_ID XYZ_OS_LIKE XYZ_OS_VERSION XYZ_OS_FAMILY XYZ_OS_PRETTY
fi
case "${XYZ_OS_ID}" in
debian|ubuntu)
XYZ_OS_FAMILY="debian"
;;
centos|rhel|rocky|almalinux|alma|fedora)
XYZ_OS_FAMILY="rhel"
;;
*)
case " ${XYZ_OS_ID} ${XYZ_OS_LIKE} " in
*debian*|*ubuntu*) XYZ_OS_FAMILY="debian" ;;
*rhel*|*centos*|*fedora*|*rocky*|*alma*) XYZ_OS_FAMILY="rhel" ;;
*) : ;;
esac
;;
esac
}
xyz_os_supported() {
case "${XYZ_OS_ID}" in
debian|ubuntu|centos|rocky|almalinux) return 0 ;;
*) : ;;
esac
case " ${XYZ_OS_LIKE} " in
*debian*|*ubuntu*|*rhel*|*centos*|*rocky*|*alma*) return 0 ;;
*) : ;;
esac
return 1
}
xyz_require_supported_os() {
xyz_detect_os
if ! xyz_os_supported; then
xyz_warn "$(xyz_t \
"Unrecognized distribution: ${XYZ_OS_ID:-unknown} (${XYZ_OS_PRETTY}). Officially supported: Debian, Ubuntu, CentOS, Rocky, AlmaLinux." \
"未能识别的发行版:${XYZ_OS_ID:-unknown}(${XYZ_OS_PRETTY})。官方支持:Debian、Ubuntu、CentOS、Rocky、AlmaLinux。")"
if ! xyz_confirm "$(xyz_t "Continue anyway?" "仍然继续?")"; then
exit 1
fi
else
xyz_info "$(xyz_t "Detected OS:" "检测到系统:") ${XYZ_OS_PRETTY} (${XYZ_OS_FAMILY})"
fi
}
# --- packages / commands ----------------------------------------------------
xyz_need_cmd() {
command -v "$1" >/dev/null 2>&1
}
xyz_pkg_install() {
xyz_detect_os
if (( $# == 0 )); then
return 0
fi
case "${XYZ_OS_FAMILY}" in
debian)
export DEBIAN_FRONTEND=noninteractive
apt-get update -y || xyz_warn "$(xyz_t "apt-get update reported errors (a repository may be unreachable); trying to install anyway." "apt-get update 报错(可能有软件源无法访问),仍尝试安装。")"
apt-get install -y --no-install-recommends "$@"
;;
rhel)
if xyz_need_cmd dnf; then
dnf install -y "$@"
elif xyz_need_cmd yum; then
yum install -y "$@"
else
xyz_error "$(xyz_t "Neither dnf nor yum is available." "找不到 dnf 或 yum。")"
exit 1
fi
;;
*)
xyz_error "$(xyz_t "Cannot install packages on this OS automatically." "无法在此系统上自动安装软件包。")"
exit 1
;;
esac
}
xyz_ensure_cmds() {
local pkg
local missing=()
for pkg in "$@"; do
if ! xyz_need_cmd "${pkg}"; then
missing+=("${pkg}")
fi
done
if ((${#missing[@]} > 0)); then
xyz_info "$(xyz_t "Installing missing commands:" "正在安装缺失命令:") ${missing[*]}"
xyz_pkg_install "${missing[@]}"
fi
}
# --- confirmation (safe with curl | bash) -----------------------------------
# A readable /dev/tty node is not enough: in containers or curl|bash without a
# controlling terminal, open() fails with ENXIO ("No such device or address").
# [[ -r /dev/tty ]] is true in that case and must not be used as the check.
xyz_open_tty() {
# Opens /dev/tty into XYZ_TTY_FD if it is a real terminal. Caller must close.
local fd
XYZ_TTY_FD=""
# Brace group so a failed open cannot leak "No such device or address".
if { exec {fd}<>/dev/tty; } 2>/dev/null && [[ -t "${fd}" ]]; then
XYZ_TTY_FD="${fd}"
return 0
fi
if [[ -n "${fd:-}" ]]; then
exec {fd}>&- 2>/dev/null || true
fi
return 1
}
xyz_close_tty() {
if [[ -n "${XYZ_TTY_FD:-}" ]]; then
exec {XYZ_TTY_FD}>&- 2>/dev/null || true
XYZ_TTY_FD=""
fi
}
xyz_confirm() {
local prompt="$1"
if [[ "${XYZ_YES}" == "1" ]]; then
xyz_info "$(xyz_t "Auto-confirmed (-y / YES=1):" "已自动确认(-y / YES=1):") ${prompt}"
return 0
fi
local reply=""
if xyz_open_tty; then
printf '%s [y/N] ' "${prompt}" >&"${XYZ_TTY_FD}"
# shellcheck disable=SC2162
read -r reply <&"${XYZ_TTY_FD}" || true
xyz_close_tty
else
xyz_warn "$(xyz_t \
"No TTY available for confirmation. Re-run with -y or YES=1 if you accept." \
"没有可用的终端来确认。若你已了解并同意,请加 -y 或 YES=1 重试。")"
return 1
fi
case "${reply}" in
y|Y|yes|YES|Yes) return 0 ;;
*) return 1 ;;
esac
}
# --- download helpers -------------------------------------------------------
# Create the temp dir in the main shell. xyz_tmpdir is usually called inside
# $(...), a subshell, so creating it (and its EXIT trap) there would delete the
# directory as soon as the subshell returned.
xyz_init_tmpdir() {
if [[ -z "${XYZ_TMPDIR:-}" || ! -d "${XYZ_TMPDIR}" ]]; then
XYZ_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/xyzsh.XXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '${XYZ_TMPDIR}'" EXIT
fi
}
xyz_tmpdir() {
if [[ -z "${XYZ_TMPDIR:-}" || ! -d "${XYZ_TMPDIR}" ]]; then
xyz_error "internal: temp dir not initialised"
exit 1
fi
printf '%s' "${XYZ_TMPDIR}"
}
xyz_fetch() {
# xyz_fetch URL DEST
local url="$1"
local dest="$2"
xyz_info "$(xyz_t "Downloading" "正在下载"): ${url}"
xyz_info "$(xyz_t "Saving to" "保存到"): ${dest}"
if xyz_need_cmd curl; then
curl -fsSL --connect-timeout 20 --max-time 180 --retry 2 --retry-delay 1 \
-o "${dest}" "${url}"
elif xyz_need_cmd wget; then
wget -q --timeout=60 -O "${dest}" "${url}"
else
xyz_error "$(xyz_t "Need curl or wget to download files." "需要 curl 或 wget 才能下载文件。")"
exit 1
fi
if [[ ! -s "${dest}" ]]; then
xyz_error "$(xyz_t "Download failed or file is empty:" "下载失败或文件为空:") ${url}"
exit 1
fi
if xyz_need_cmd sha256sum; then
xyz_info "SHA256: $(sha256sum "${dest}" | awk '{print $1}')"
elif xyz_need_cmd shasum; then
xyz_info "SHA256: $(shasum -a 256 "${dest}" | awk '{print $1}')"
fi
}
xyz_run_upstream() {
# xyz_run_upstream URL [args...]
# Downloads an official installer, shows the URL and a short preview, then
# runs it only after confirmation (unless -y / YES=1).
local url="$1"
shift
local dest
xyz_init_tmpdir
dest="$(xyz_tmpdir)/upstream.sh"
xyz_fetch "${url}" "${dest}"
chmod +x "${dest}"
xyz_info "$(xyz_t "Preview (first 20 lines):" "预览(前 20 行):")"
sed -n '1,20p' "${dest}" || true
printf '%s\n' "${XYZ_C_DIM}...${XYZ_C_RESET}"
# Default --dry-run: verify the download and preview, do not execute.
# Scripts that forward --dry-run to upstream (e.g. docker) set
# XYZ_FORWARD_DRY_RUN=1 so the official script still runs with that flag.
if [[ "${XYZ_DRY_RUN:-0}" == "1" && "${XYZ_FORWARD_DRY_RUN:-0}" != "1" ]]; then
xyz_info "$(xyz_t \
"Dry-run: official script downloaded and previewed; not executing it." \
"演练模式:已下载并预览官方脚本,不会执行。")"
return 0
fi
if ! xyz_confirm "$(xyz_t \
"Run this official upstream script?" \
"运行这份官方上游脚本?")"; then
xyz_error "$(xyz_t "Aborted by user." "已取消。")"
exit 1
fi
xyz_step "$(xyz_t "Executing upstream script" "正在执行上游脚本")"
# Never let the upstream script inherit our stdin: under `curl | bash` that
# is the rest of this very script. Give it the terminal if there is one,
# otherwise /dev/null. Interactive installers set XYZ_UPSTREAM_NEEDS_TTY=1.
if xyz_open_tty; then
local tty_fd="${XYZ_TTY_FD}"
XYZ_TTY_FD=""
bash "${dest}" "$@" <&"${tty_fd}"
local rc=$?
exec {tty_fd}>&- 2>/dev/null || true
return "${rc}"
fi
if [[ "${XYZ_UPSTREAM_NEEDS_TTY:-0}" == "1" ]]; then
xyz_error "$(xyz_t \
"This official installer is interactive and needs a terminal. Log in with SSH and run: bash <(curl -fsSL ${XYZ_BASE}/${XYZ_NAME})" \
"这个官方安装脚本需要交互,必须在终端里运行。请用 SSH 登录后执行:bash <(curl -fsSL ${XYZ_BASE}/${XYZ_NAME})")"
exit 1
fi
bash "${dest}" "$@" </dev/null
}
# --- small shared helpers ----------------------------------------------------
xyz_strip_ansi() {
sed -e 's/\x1b\[[0-9;?]*[A-Za-z]//g' -e 's/\r//g'
}
# Effective SSH port: sshd -T (what sshd really uses) > config files > env > 22.
xyz_effective_ssh_port() {
local port=""
if xyz_need_cmd sshd; then
port="$(sshd -T 2>/dev/null | awk '$1=="port" {print $2; exit}' || true)"
fi
if [[ -z "${port}" && -d /etc/ssh/sshd_config.d ]]; then
port="$(cat /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk 'tolower($1)=="port" {print $2; exit}' || true)"
fi
if [[ -z "${port}" && -f /etc/ssh/sshd_config ]]; then
port="$(awk 'tolower($1)=="port" {print $2; exit}' /etc/ssh/sshd_config || true)"
fi
if [[ -z "${port}" && -n "${SSH_CONNECTION:-}" ]]; then
port="$(printf '%s' "${SSH_CONNECTION}" | awk '{print $4}')"
fi
printf '%s' "${port:-22}"
}
xyz_has_systemd() {
[[ -d /run/systemd/system ]] && xyz_need_cmd systemctl
}
# Back up a file once per run as FILE.xyz-bak-YYYYmmddHHMMSS and print the path.
xyz_backup_file() {
local f="$1"
local bak
bak="${f}.xyz-bak-$(date +%Y%m%d%H%M%S)"
if [[ -e "${f}" ]]; then
cp -a "${f}" "${bak}"
printf '%s' "${bak}"
fi
}
# --- misc -------------------------------------------------------------------
xyz_usage_exit() {
if [[ "${1:-}" != "" ]]; then
printf '%s\n' "$1"
fi
exit 0
}
# xyz.sh terminal report helpers: frames, colored badges, bar charts.
# Shared by speedtest-cn, route, unlock and all. Pure bash, no telemetry.
#
# Colors: on only when stdout is a terminal (or XYZ_COLOR=always). Off with
# --no-color, NO_COLOR=1 or XYZ_COLOR=never.
# Glyphs: Unicode box drawing when the locale is UTF-8, otherwise ASCII
# (force with XYZ_ASCII=1 or XYZ_UTF8=1). Frames are 78 columns wide.
# shellcheck disable=SC2034
XYZ_UI_LOADED=1
XYZ_UI_WIDTH=78
xyz_ui_init() {
local want="${XYZ_COLOR:-auto}"
[[ "${want}" == "auto" && -n "${NO_COLOR:-}" ]] && want="never"
[[ "${XYZ_NO_COLOR:-0}" == "1" ]] && want="never"
XYZ_UI_COLOR=0
case "${want}" in
always) XYZ_UI_COLOR=1 ;;
never) XYZ_UI_COLOR=0 ;;
*) [[ -t 1 ]] && XYZ_UI_COLOR=1 ;;
esac
if [[ "${XYZ_UI_COLOR}" == "1" ]]; then
U_X=$'\033[0m'; U_B=$'\033[1m'; U_D=$'\033[2m'
U_RED=$'\033[31m'; U_G=$'\033[32m'; U_Y=$'\033[33m'
U_BL=$'\033[34m'; U_M=$'\033[35m'; U_C=$'\033[36m'
else
U_X=""; U_B=""; U_D=""; U_RED=""; U_G=""; U_Y=""; U_BL=""; U_M=""; U_C=""
fi
XYZ_UI_UTF8=0
if [[ "${XYZ_ASCII:-0}" != "1" ]]; then
case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in
*[Uu][Tt][Ff]-8*|*[Uu][Tt][Ff]8*) XYZ_UI_UTF8=1 ;;
*) XYZ_UI_UTF8=0 ;;
esac
[[ "${XYZ_UTF8:-0}" == "1" ]] && XYZ_UI_UTF8=1
fi
if [[ "${XYZ_UI_UTF8}" == "1" ]]; then
G_H="─"; G_V="│"; G_TL="╭"; G_TR="╮"; G_BL="╰"; G_BR="╯"; G_LT="├"; G_RT="┤"
G_FULL="█"; G_EMPTY="░"; G_OK="✔"; G_BAD="✘"; G_UNK="?"; G_DOT="●"; G_STAR="★"
G_DOWN="↓"; G_UP="↑"; G_WARN="⚠"; G_MID="·"; G_TIMES="×"
else
G_H="-"; G_V="|"; G_TL="+"; G_TR="+"; G_BL="+"; G_BR="+"; G_LT="+"; G_RT="+"
G_FULL="#"; G_EMPTY="."; G_OK="+"; G_BAD="x"; G_UNK="?"; G_DOT="*"; G_STAR="*"
G_DOWN="v"; G_UP="^"; G_WARN="!"; G_MID="-"; G_TIMES="x"
fi
}
# Display width of a string: ANSI codes removed, CJK counted as 2 columns.
xyz_ui_w() {
local s="$1" pre rest
while [[ "${s}" == *$'\033['* ]]; do
pre="${s%%$'\033['*}"
rest="${s#*$'\033['}"
rest="${rest#*m}"
s="${pre}${rest}"
done
local LC_ALL=C
local a="${s//[^ -~]/}"
local one="${s//[^$'\xc2'-$'\xdf'$'\xe2']/}"
local two="${s//[^$'\xe3'-$'\xef']/}"
printf '%d' $(( ${#a} + ${#one} + 2 * ${#two} ))
}
xyz_ui_rep() {
# xyz_ui_rep CHAR N
local out="" n="$2"
while (( n > 0 )); do out="${out}$1"; n=$((n - 1)); done
printf '%s' "${out}"
}
xyz_ui_pad() {
# xyz_ui_pad TEXT WIDTH (left aligned, never truncates)
local w
w="$(xyz_ui_w "$1")"
printf '%s%s' "$1" "$(xyz_ui_rep ' ' $(( $2 - w )))"
}
xyz_ui_lpad() {
# xyz_ui_lpad TEXT WIDTH (right aligned)
local w
w="$(xyz_ui_w "$1")"
printf '%s%s' "$(xyz_ui_rep ' ' $(( $2 - w )))" "$1"
}
xyz_ui_top() {
local title=" $1 " w
w="$(xyz_ui_w "${title}")"
printf '%s%s%s%s%s%s%s\n' "${U_C}" "${G_TL}${G_H}" "${U_X}${U_B}" "${title}" "${U_X}${U_C}" \
"$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 3 - w )))${G_TR}" "${U_X}"
}
xyz_ui_sep() {
local label="${1:-}" w=0
if [[ -n "${label}" ]]; then
label=" ${label} "
w="$(xyz_ui_w "${label}")"
printf '%s%s%s%s%s%s%s\n' "${U_C}" "${G_LT}${G_H}" "${U_X}${U_B}" "${label}" "${U_X}${U_C}" \
"$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 3 - w )))${G_RT}" "${U_X}"
else
printf '%s%s%s\n' "${U_C}" "${G_LT}$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 2 )))${G_RT}" "${U_X}"
fi
}
xyz_ui_row() {
local text="$1" inner=$(( XYZ_UI_WIDTH - 4 )) plain
if (( $(xyz_ui_w "${text}") > inner )); then
# too wide: drop colors and cut to fit, ending with "~"
plain="$(printf '%s' "${text}" | sed 's/\x1b\[[0-9;]*m//g')"
plain="${plain:0:inner}"
while (( $(xyz_ui_w "${plain}") > inner - 1 )); do plain="${plain%?}"; done
text="${plain}~"
fi
printf '%s%s%s %s %s%s%s\n' "${U_C}" "${G_V}" "${U_X}" "$(xyz_ui_pad "${text}" "${inner}")" "${U_C}" "${G_V}" "${U_X}"
}
xyz_ui_bottom() {
printf '%s%s%s\n' "${U_C}" "${G_BL}$(xyz_ui_rep "${G_H}" $(( XYZ_UI_WIDTH - 2 )))${G_BR}" "${U_X}"
}
xyz_ui_bar() {
# xyz_ui_bar VALUE MAX WIDTH [COLOR] -> filled/empty bar (at least 1 cell if VALUE > 0)
local n color="${4:-${U_G}}"
n="$(awk -v v="${1:-0}" -v m="${2:-0}" -v w="$3" 'BEGIN {
if (m <= 0 || v <= 0) { print 0; exit }
n = int(v / m * w + 0.5); if (n < 1) n = 1; if (n > w) n = w; print n }')"
printf '%s%s%s%s%s' "${color}" "$(xyz_ui_rep "${G_FULL}" "${n}")" "${U_D}" "$(xyz_ui_rep "${G_EMPTY}" $(( $3 - n )))" "${U_X}"
}
xyz_ui_usage() {
# xyz_ui_usage PERCENT WIDTH -> colored usage bar + percent (red > 90, yellow > 75)
local p="${1:-0}" color="${U_G}"
p="${p%%.*}"; [[ "${p}" =~ ^[0-9]+$ ]] || p=0
(( p > 75 )) && color="${U_Y}"
(( p > 90 )) && color="${U_RED}"
printf '%s %s%3d%%%s' "$(xyz_ui_bar "${p}" 100 "$2" "${color}")" "${color}" "${p}" "${U_X}"
}
xyz_ui_badge() {
# xyz_ui_badge yes|no|partial|unknown|ok|skip [TEXT]
local txt="${2:-}"
case "$1" in
yes|ok) printf '%s%s %s%s' "${U_G}" "${G_OK}" "${txt:-$(xyz_t "yes" "解锁")}" "${U_X}" ;;
no|fail) printf '%s%s %s%s' "${U_RED}" "${G_BAD}" "${txt:-$(xyz_t "no" "不可用")}" "${U_X}" ;;
partial) printf '%s%s %s%s' "${U_Y}" "${G_UNK}" "${txt:-$(xyz_t "partial" "部分")}" "${U_X}" ;;
skip) printf '%s%s %s%s' "${U_D}" "-" "${txt:-$(xyz_t "skipped" "跳过")}" "${U_X}" ;;
*) printf '%s%s %s%s' "${U_Y}" "${G_UNK}" "${txt:-$(xyz_t "unknown" "未知")}" "${U_X}" ;;
esac
}
xyz_ui_line_class() {
# premium | good | ordinary | other
case "$1" in
CN2-GIA|9929/CUII|9929|CMIN2) printf 'premium' ;;
CN2-GT) printf 'good' ;;
163|4837|CMI|CMNET) printf 'ordinary' ;;
*) printf 'other' ;;
esac
}
xyz_ui_linetags() {
# xyz_ui_linetags "CN2-GIA 9929/CUII" -> colored tags
local t out="" c
for t in $1; do
case "$(xyz_ui_line_class "${t}")" in
premium) c="${U_G}${U_B}" ;;
good) c="${U_C}" ;;
ordinary) c="${U_Y}" ;;
*) c="${U_D}" ;;
esac
out="${out}${out:+ }${c}${t}${U_X}"
done
printf '%s' "${out:-${U_D}?${U_X}}"
}
xyz_ui_fill() {
# Replace empty TSV fields with "-" (bash read collapses consecutive tabs).
awk -F'\t' -v OFS='\t' '{ for (i = 1; i <= NF; i++) if ($i == "") $i = "-"; print }' "$1"
}
xyz_ui_carrier() {
case "$1" in
nearest) xyz_t "Local" "本地" ;;
ct) xyz_t "Telecom" "电信" ;;
cu) xyz_t "Unicom" "联通" ;;
cm) xyz_t "Mobile" "移动" ;;
custom) xyz_t "Custom" "自定义" ;;
hk) xyz_t "HK" "香港" ;;
tokyo) xyz_t "Tokyo" "东京" ;;
singapore) xyz_t "SGP" "新加坡" ;;
us-west) xyz_t "US West" "美西" ;;
*) printf '%s' "$1" ;;
esac
}
xyz_ui_carrier_color() {
case "$1" in
ct|tokyo) printf '%s' "${U_BL}" ;;
cu) printf '%s' "${U_RED}" ;;
cm|singapore) printf '%s' "${U_G}" ;;
hk) printf '%s' "${U_Y}" ;;
*) printf '%s' "${U_M}" ;;
esac
}
xyz_ui_fmt_num() {
# 2 decimals below 1000, 1 decimal above
awk -v v="${1:-0}" 'BEGIN { if (v >= 1000) printf "%.1f", v; else printf "%.2f", v }'
}
# xyz_ui_speed_rows FILE
# FILE: carrier id label down up ping loss status [engine] [note] (TSV, Mbps / ms)
xyz_ui_speed_rows() {
local f="$1" max carrier id label down up lat loss status engine note head
[[ -s "${f}" ]] || return 0
max="$(awk -F'\t' '$8=="ok" { if ($4+0 > m) m = $4+0; if ($5+0 > m) m = $5+0 } END { print m+0 }' "${f}")"
while IFS=$'\t' read -r carrier id label down up lat loss status engine note; do
[[ "${engine}" == "-" ]] && engine=""
head="$(xyz_ui_carrier_color "${carrier}")$(xyz_ui_pad "$(xyz_ui_carrier "${carrier}")" 8)${U_X}${U_B}${label}${U_X}${U_D} #${id}${engine:+ ${G_MID} ${engine}}${U_X}"
xyz_ui_row "${head}"
if [[ "${status}" == "ok" ]]; then
local pc="${U_G}"
lat="${lat:-?}"
awk -v p="${lat}" 'BEGIN { exit !(p+0 > 150) }' 2>/dev/null && pc="${U_Y}"
awk -v p="${lat}" 'BEGIN { exit !(p+0 > 250) }' 2>/dev/null && pc="${U_RED}"
xyz_ui_row " ${G_DOWN} $(xyz_ui_bar "${down}" "${max}" 14 "${U_G}") $(xyz_ui_lpad "$(xyz_ui_fmt_num "${down}")" 7) Mbps ${G_UP} $(xyz_ui_bar "${up}" "${max}" 14 "${U_C}") $(xyz_ui_lpad "$(xyz_ui_fmt_num "${up}")" 7) Mbps ${pc}$(xyz_ui_lpad "${lat}" 6)ms${U_X}"
else
[[ "${note}" == "-" ]] && note=""
xyz_ui_row " $(xyz_ui_badge skip "$(xyz_t "skipped" "跳过")")${U_D}${note:+ ${G_MID} ${note:0:62}}${U_X}"
fi
done < <(xyz_ui_fill "${f}")
}
xyz_ui_speed_table() {
# xyz_ui_speed_table FILE TITLE
xyz_ui_top "$2"
xyz_ui_speed_rows "$1"
xyz_ui_bottom
}
# Route grid. FILE: target label kind hops [key] (key like bj-ct)
xyz_ui_route_key() {
case "$1" in
219.141.140.10) printf 'bj-ct' ;; 202.106.195.68) printf 'bj-cu' ;; 221.179.155.161) printf 'bj-cm' ;;
202.96.209.133) printf 'sh-ct' ;; 210.22.97.1) printf 'sh-cu' ;; 211.136.112.200) printf 'sh-cm' ;;
58.60.188.222) printf 'gz-ct' ;; 210.21.196.6) printf 'gz-cu' ;; 120.196.165.24) printf 'gz-cm' ;;
*) printf '' ;;
esac
}
xyz_ui_route_rows() {
local f="$1" target label kind hops key city c cell line extra=0
local -A K=()
[[ -s "${f}" ]] || return 0
while IFS=$'\t' read -r target label kind hops key; do
[[ -z "${key}" || "${key}" == "-" ]] && key="$(xyz_ui_route_key "${target}")"
if [[ -n "${key}" ]]; then
K["${key}"]="${kind}"
else
extra=1
fi
done < <(xyz_ui_fill "${f}")
if (( ${#K[@]} > 0 )); then
xyz_ui_row "${U_D}$(xyz_ui_pad "" 10)$(xyz_ui_pad "$(xyz_ui_carrier ct)" 21)$(xyz_ui_pad "$(xyz_ui_carrier cu)" 21)$(xyz_ui_carrier cm)${U_X}"
for city in bj sh gz; do
case "${city}" in
bj) line="$(xyz_ui_pad "$(xyz_t "Beijing" "北京")" 10)" ;;
sh) line="$(xyz_ui_pad "$(xyz_t "Shanghai" "上海")" 10)" ;;
*) line="$(xyz_ui_pad "$(xyz_t "Guangzhou" "广州")" 10)" ;;
esac
for c in ct cu cm; do
cell="${K[${city}-${c}]-}"
[[ "${cell}" == *" ("* || "${cell}" == unknown* || "${cell}" == 未识别* ]] && cell="?"
if [[ -z "${cell}" ]]; then cell="${U_D}-${U_X}"; else cell="$(xyz_ui_linetags "${cell}")"; fi
line="${line}$(xyz_ui_pad "${cell}" 21)"
done
xyz_ui_row "${line}"
done
fi
if [[ "${extra}" == "1" ]]; then
while IFS=$'\t' read -r target label kind hops key; do
[[ -z "${key}" || "${key}" == "-" ]] && key="$(xyz_ui_route_key "${target}")"
[[ -n "${key}" ]] && continue
xyz_ui_row "$(xyz_ui_pad "${label:-${target}}" 22) $(xyz_ui_linetags "${kind}")"
done < <(xyz_ui_fill "${f}")
fi
xyz_ui_row "${U_D}$(xyz_t "Legend:" "图例:")${U_X} ${U_G}${U_B}$(xyz_t "premium" "优质")${U_X} CN2-GIA/9929/CMIN2 ${U_C}CN2-GT${U_X} ${U_Y}$(xyz_t "ordinary" "普通")${U_X} 163/4837/CMI"
}
xyz_ui_route_score() {
# prints 0-100 (average best class per target) or nothing
local f="$1" kind t best n=0 sum=0 cls
[[ -s "${f}" ]] || return 0
while IFS=$'\t' read -r _ _ kind _ _; do
best=0
for t in ${kind}; do
cls="$(xyz_ui_line_class "${t}")"
case "${cls}" in premium) best=100 ;; good) (( best < 75 )) && best=75 ;; ordinary) (( best < 40 )) && best=40 ;; *) : ;; esac
done
n=$((n + 1)); sum=$((sum + best))
done < "${f}"
(( n > 0 )) && printf '%d' $(( sum / n ))
}
# Unlock grid. FILE: service status region note
xyz_ui_unlock_rows() {
local f="$1" svc st reg note cells=() i cell
[[ -s "${f}" ]] || return 0
while IFS=$'\t' read -r svc st reg note; do
cell="$(xyz_ui_badge "${st}" "$(xyz_ui_pad "${svc}" 16)")"
[[ "${reg}" != "-" ]] && cell="${cell} ${U_D}${reg:0:12}${U_X}"
cells+=("${cell}")
done < <(xyz_ui_fill "${f}")
for (( i = 0; i < ${#cells[@]}; i += 2 )); do
xyz_ui_row "$(xyz_ui_pad "${cells[$i]}" 37)${cells[$((i + 1))]-}"
done
}
xyz_ui_unlock_count() {
# prints "yes total"
awk -F'\t' '{ n++; if ($2 == "yes") y++ } END { printf "%d %d\n", y, n }' "$1"
}
xyz_ui_init
XYZ_NAME="media"
# xyz.sh/unlock — streaming / AI service availability and IP quality check.
#
# A small self-written checker using plain curl requests (no installs):
# Netflix, Disney+, YouTube Premium, ChatGPT, Claude, Gemini, TikTok
# plus IP information (ASN, country, datacenter / residential / mobile,
# VPN / proxy flags) from public free APIs:
# https://api.ipapi.is (primary) http://ip-api.com (fallback)
#
# These checks are heuristics based on what each site returns to this IP
# right now. Sites change often; treat results as a hint, not a guarantee.
# For a much larger matrix, the well-known upstream projects can be run
# instead (downloaded, previewed, then executed only after confirmation):
# --full lmc999/RegionRestrictionCheck https://github.com/lmc999/RegionRestrictionCheck
# --ipquality xykt/IPQuality https://github.com/xykt/IPQuality
#
# Usage:
# curl -fsSL xyz.sh/unlock | bash
# curl -fsSL xyz.sh/unlock | bash -s -- -6
# curl -fsSL xyz.sh/unlock | bash -s -- --json
# curl -fsSL xyz.sh/unlock | bash -s -- --no-ip
# curl -fsSL xyz.sh/unlock | bash -s -- --full
# curl -fsSL xyz.sh/unlock | bash -s -- --ipquality
#
# Root is not required. Nothing is installed or written (except a temp dir).
# The IP lookup sends your server's IP to the API above (that is the lookup);
# skip it with --no-ip. No telemetry is added by xyz.sh.
set -euo pipefail
if [[ -z "${XYZ_COMMON_LOADED:-}" ]]; then
_xyz_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "${_xyz_dir}/lib/common.sh" ]]; then
# shellcheck source=lib/common.sh
source "${_xyz_dir}/lib/common.sh"
else
echo "xyz.sh: missing lib/common.sh" >&2
exit 1
fi
fi
if [[ -z "${XYZ_UI_LOADED:-}" ]]; then
_xyz_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/report.sh
source "${_xyz_dir}/lib/report.sh"
fi
XYZ_NAME="${XYZ_NAME:-unlock}"
case "${XYZ_NAME}" in unlock|ipcheck|media) : ;; *) XYZ_NAME="unlock" ;; esac
XYZ_TITLE="$(xyz_t "Streaming / AI unlock and IP quality check" "流媒体 / AI 解锁与 IP 质量检测")"
RRC_URL="https://raw.githubusercontent.com/lmc999/RegionRestrictionCheck/main/check.sh"
IPQ_URL="https://raw.githubusercontent.com/xykt/IPQuality/main/ip.sh"
# With --json, keep stdout clean: banner and logs go to stderr.
exec 3>&1
for _arg in "$@"; do
if [[ "${_arg}" == "--json" ]]; then
exec 1>&2
fi
done
xyz_init "$@"
IPV="4"
WANT_JSON=0
NO_IP=0
DO_FULL=0
DO_IPQ=0
for arg in "${XYZ_ARGS[@]+"${XYZ_ARGS[@]}"}"; do
case "${arg}" in
-4) IPV="4" ;;
-6) IPV="6" ;;
--json) WANT_JSON=1 ;;
--no-ip) NO_IP=1 ;;
--full) DO_FULL=1 ;;
--ipquality) DO_IPQ=1 ;;
--help|-h) XYZ_HELP=1 ;;
*)
xyz_error "$(xyz_t "Unknown argument:" "未知参数:") ${arg}"
exit 1
;;
esac
done
if [[ "${XYZ_HELP:-}" == "1" ]]; then
cat <<HINT
$(xyz_t "Usage:" "用法:")
curl -fsSL xyz.sh/unlock | bash
curl -fsSL xyz.sh/unlock | bash -s -- -6
curl -fsSL xyz.sh/unlock | bash -s -- --json
curl -fsSL xyz.sh/unlock | bash -s -- --no-ip
curl -fsSL xyz.sh/unlock | bash -s -- --full # lmc999/RegionRestrictionCheck
curl -fsSL xyz.sh/unlock | bash -s -- --ipquality # xykt/IPQuality
$(xyz_t "Checks: Netflix, Disney+, YouTube Premium, ChatGPT, Claude, Gemini, TikTok, and IP type." "检测:Netflix、Disney+、YouTube Premium、ChatGPT、Claude、Gemini、TikTok 以及 IP 类型。")
$(xyz_t "Results are heuristics; websites change their responses often." "结果为启发式判断,网站返回内容经常变化,仅供参考。")
HINT
exit 0
fi
if [[ "${DO_FULL}" == "1" ]]; then
XYZ_UPSTREAM="https://github.com/lmc999/RegionRestrictionCheck"
xyz_info "$(xyz_t "Running the upstream RegionRestrictionCheck by lmc999 (AGPL-3.0)." "运行 lmc999 的上游 RegionRestrictionCheck(AGPL-3.0)。")"
xyz_info "Project: ${XYZ_UPSTREAM}"
XYZ_UPSTREAM_NEEDS_TTY=1
xyz_run_upstream "${RRC_URL}"
exit 0
fi
if [[ "${DO_IPQ}" == "1" ]]; then
XYZ_UPSTREAM="https://github.com/xykt/IPQuality"
xyz_info "$(xyz_t "Running the upstream IPQuality by xykt (AGPL-3.0)." "运行 xykt 的上游 IPQuality(AGPL-3.0)。")"
xyz_info "Project: ${XYZ_UPSTREAM}"
xyz_run_upstream "${IPQ_URL}" "-${IPV}"
exit 0
fi
if ! xyz_need_cmd curl; then
xyz_error "$(xyz_t "curl is required." "需要 curl。")"
exit 1
fi
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
xyz_init_tmpdir
TMP="$(xyz_tmpdir)"
# fetch NAME URL [extra curl args...] -> body in $TMP/NAME.body, prints "code effective_url"
fetch() {
local name="$1" url="$2"
shift 2
curl "-${IPV}" -sS -L --max-time 12 --connect-timeout 6 -A "${UA}" \
-H 'Accept-Language: en-US,en;q=0.9' "$@" \
-o "${TMP}/${name}.body" -w '%{http_code} %{url_effective}' "${url}" 2>/dev/null || printf '000 %s' "${url}"
}
body_has() { grep -q -i -- "$2" "${TMP}/$1.body" 2>/dev/null; }
RESULTS=()
add() {
# add SERVICE STATUS REGION NOTE (STATUS: yes|no|partial|unknown)
RESULTS+=("$1|$2|$3|$4")
}
check_netflix() {
local a b ra rb region=""
a="$(fetch nf1 https://www.netflix.com/title/81280792)"
b="$(fetch nf2 https://www.netflix.com/title/70143836)"
ra="${a%% *}"; rb="${b%% *}"
region="$(printf '%s\n%s\n' "${a#* }" "${b#* }" | grep -o -E 'netflix\.com/[a-z]{2}(-[a-z]{2})?/' | head -1 | sed -E 's#netflix\.com/([a-z]{2}).*#\1#' | tr '[:lower:]' '[:upper:]' || true)"
if [[ -z "${region}" ]]; then
region="$(grep -o -E '"requestCountry":\{"id":"[A-Z]{2}"' "${TMP}/nf1.body" 2>/dev/null | grep -o -E '[A-Z]{2}"$' | tr -d '"' | head -1 || true)"
fi
if [[ "${ra}" == "000" && "${rb}" == "000" ]]; then
add "Netflix" unknown "" "$(xyz_t "network error" "网络错误")"
elif [[ "${ra}" == "200" || "${rb}" == "200" ]]; then
if [[ "${ra}" == "200" && "${rb}" == "200" ]]; then
add "Netflix" yes "${region:-US}" "$(xyz_t "full catalogue (non-originals)" "完整解锁(含非自制剧)")"
else
add "Netflix" partial "${region:-US}" "$(xyz_t "some licensed titles" "部分非自制剧")"
fi
elif [[ "${ra}" == "404" && "${rb}" == "404" ]]; then
add "Netflix" partial "${region}" "$(xyz_t "originals only" "仅自制剧")"
elif [[ "${ra}" == "403" || "${rb}" == "403" ]]; then
add "Netflix" no "" "$(xyz_t "blocked (HTTP 403)" "被屏蔽(HTTP 403)")"
else
add "Netflix" unknown "${region}" "HTTP ${ra}/${rb}"
fi
}
check_disney() {
local r code url
r="$(fetch dp https://www.disneyplus.com/)"
code="${r%% *}"; url="${r#* }"
if [[ "${code}" == "000" ]]; then
add "Disney+" unknown "" "$(xyz_t "network error" "网络错误")"
elif [[ "${url}" == *unavailable* ]] || body_has dp 'not available in your region'; then
add "Disney+" no "" "$(xyz_t "redirected to 'unavailable'" "跳转到不可用页面")"
elif [[ "${code}" == "403" ]]; then
add "Disney+" no "" "HTTP 403"
elif [[ "${code}" == "200" ]]; then
local region
region="$(grep -o -E '"countryCode":"[A-Z]{2}"' "${TMP}/dp.body" | head -1 | sed -E 's/.*:"([A-Z]{2})"/\1/' || true)"
[[ -z "${region}" ]] && region="$(printf '%s' "${url}" | grep -o -E 'disneyplus\.com/[a-z]{2}-([a-z]{2})' | sed -E 's#.*-##' | tr '[:lower:]' '[:upper:]' || true)"
add "Disney+" yes "${region}" "$(xyz_t "site served (sign-up not tested)" "网站可访问(未测试注册)")"
else
add "Disney+" unknown "" "HTTP ${code}"
fi
}
check_youtube() {
local r code region
r="$(fetch yt https://www.youtube.com/premium)"
code="${r%% *}"
region="$(grep -o -E '"(INNERTUBE_CONTEXT_)?GL":"[A-Z]{2}"' "${TMP}/yt.body" 2>/dev/null | head -1 | sed -E 's/.*:"([A-Z]{2})"/\1/' || true)"
[[ -z "${region}" ]] && region="$(grep -o -E '"countryCode":"[A-Z]{2}"' "${TMP}/yt.body" 2>/dev/null | head -1 | sed -E 's/.*:"([A-Z]{2})"/\1/' || true)"
if [[ "${code}" == "000" ]]; then
add "YouTube Premium" unknown "" "$(xyz_t "network error" "网络错误")"
elif body_has yt 'Premium is not available in your country'; then
add "YouTube Premium" no "${region}" "$(xyz_t "not available in this country" "此地区不可用")"
elif [[ "${region}" == "CN" ]]; then
add "YouTube Premium" no "CN" ""
elif [[ "${code}" == "200" ]] && (body_has yt 'ad-free' || body_has yt 'YouTube Premium'); then
add "YouTube Premium" yes "${region}" ""
else
add "YouTube Premium" unknown "${region}" "HTTP ${code}"
fi
}
check_chatgpt() {
local r1 r2 c1 c2 loc
r1="$(fetch oa1 https://api.openai.com/compliance/cookie_requirements -H 'authorization: Bearer null')"
r2="$(fetch oa2 https://ios.chat.openai.com/)"
c1="${r1%% *}"; c2="${r2%% *}"
fetch oatrace https://chatgpt.com/cdn-cgi/trace >/dev/null
loc="$(awk -F= '$1=="loc"{print $2}' "${TMP}/oatrace.body" 2>/dev/null | head -1 || true)"
if [[ "${c1}" == "000" && "${c2}" == "000" ]]; then
add "ChatGPT" unknown "" "$(xyz_t "network error" "网络错误")"
elif body_has oa1 'unsupported_country'; then
add "ChatGPT" no "${loc}" "$(xyz_t "unsupported country" "不支持的地区")"
elif body_has oa2 'VPN' ; then
add "ChatGPT" partial "${loc}" "$(xyz_t "web only; app flags this IP as VPN" "仅网页;App 判定为 VPN")"
else
add "ChatGPT" yes "${loc}" ""
fi
}
check_claude() {
local r code url
r="$(fetch cl https://claude.ai/)"
code="${r%% *}"; url="${r#* }"
if [[ "${code}" == "000" ]]; then
add "Claude" unknown "" "$(xyz_t "network error" "网络错误")"
elif [[ "${url}" == *unavailable* ]]; then
add "Claude" no "" "$(xyz_t "app-unavailable-in-region" "地区不可用")"
elif [[ "${code}" == "403" ]]; then
add "Claude" unknown "" "$(xyz_t "HTTP 403 (bot challenge or block)" "HTTP 403(人机验证或屏蔽)")"
else
add "Claude" yes "" "HTTP ${code}"
fi
}
check_gemini() {
local r code region
r="$(fetch gm https://gemini.google.com/)"
code="${r%% *}"
region="$(grep -o -E ',2,1,200,"[A-Z]{3}"' "${TMP}/gm.body" 2>/dev/null | head -1 | sed -E 's/.*"([A-Z]{3})"/\1/' || true)"
if [[ "${code}" == "000" ]]; then
add "Gemini" unknown "" "$(xyz_t "network error" "网络错误")"
elif grep -q -E '45631641(,null,true|\\?":true)' "${TMP}/gm.body" 2>/dev/null; then
add "Gemini" yes "${region}" ""
elif [[ "${code}" == "200" ]]; then
add "Gemini" unknown "${region}" "$(xyz_t "availability flag not found" "未找到可用标记")"
else
add "Gemini" unknown "${region}" "HTTP ${code}"
fi
}
check_tiktok() {
local r code region
r="$(fetch tt https://www.tiktok.com/explore)"
code="${r%% *}"
region="$(grep -o -E '"region":"[A-Z]{2}"' "${TMP}/tt.body" 2>/dev/null | head -1 | sed -E 's/.*:"([A-Z]{2})"/\1/' || true)"
if [[ "${code}" == "000" ]]; then
add "TikTok" unknown "" "$(xyz_t "network error" "网络错误")"
elif [[ -n "${region}" ]]; then
add "TikTok" yes "${region}" ""
elif [[ "${code}" == "200" ]]; then
add "TikTok" partial "" "$(xyz_t "page served, region not detected" "页面可访问,未识别地区")"
else
add "TikTok" no "" "HTTP ${code}"
fi
}
# ---- IP information ----------------------------------------------------------
IP_ADDR=""; IP_ASN=""; IP_ORG=""; IP_CC=""; IP_TYPE=""; IP_FLAGS=""
json_str() { grep -o -E "\"$1\": *\"[^\"]*\"" "$2" 2>/dev/null | head -1 | sed -E 's/.*: *"(.*)"/\1/' || true; }
json_bool() { grep -o -E "\"$1\": *(true|false)" "$2" 2>/dev/null | head -1 | grep -o -E 'true|false' || true; }
json_num() { grep -o -E "\"$1\": *[0-9]+" "$2" 2>/dev/null | head -1 | grep -o -E '[0-9]+$' || true; }
DC_WORDS='cloud|hosting|host|datacenter|data center|server|vps|colo|cloudflare|amazon|aws|google|microsoft|azure|oracle|digitalocean|linode|akamai|vultr|choopa|ovh|hetzner|contabo|alibaba|aliyun|tencent|huawei cloud|bandwagon|it7|leaseweb|m247|psychz|cogent|zenlayer|dmit|racknerd|buyvm|frantech|greencloud|kamatera|scaleway|upcloud|ionos|netcup|godaddy|hostinger|gcore|g-core'
check_ip() {
local f="${TMP}/ipapi.body" f2="${TMP}/ipapi2.body" f3="${TMP}/ipinfo.body"
local dc="" mobile="" vpn="" proxy="" tor="" abuser="" ctype=""
fetch ipapi https://api.ipapi.is/ >/dev/null
if [[ -s "${f}" ]] && grep -q '"ip"' "${f}"; then
IP_SRC="api.ipapi.is"
IP_ADDR="$(json_str ip "${f}")"
IP_ASN="$(grep -o -E '"asn": *"AS[0-9]+' "${f}" | head -1 | grep -o -E 'AS[0-9]+' || true)"
[[ -z "${IP_ASN}" ]] && IP_ASN="$(json_num asn "${f}")" && [[ -n "${IP_ASN}" ]] && IP_ASN="AS${IP_ASN}"
IP_ORG="$(json_str company "${f}")"
[[ -z "${IP_ORG}" ]] && IP_ORG="$(grep -o -E '"company": *\{"name": *"[^"]*"' "${f}" | head -1 | sed -E 's/.*"([^"]*)"$/\1/' || true)"
IP_CC="$(json_str country_code "${f}")"
[[ -z "${IP_CC}" ]] && IP_CC="$(json_str country "${f}")"
dc="$(json_bool is_datacenter "${f}")"; mobile="$(json_bool is_mobile "${f}")"
vpn="$(json_bool is_vpn "${f}")"; proxy="$(json_bool is_proxy "${f}")"
tor="$(json_bool is_tor "${f}")"; abuser="$(json_bool is_abuser "${f}")"
ctype="$(grep -o -E '"company": *\{[^}]*"type": *"[a-z]+"' "${f}" | head -1 | sed -E 's/.*"([a-z]+)"$/\1/' || true)"
fi
# ip-api.com has hosting / proxy / mobile flags (free endpoint is HTTP only).
fetch ipapi2 'http://ip-api.com/json/?fields=status,countryCode,isp,org,as,mobile,proxy,hosting,query' >/dev/null
if grep -q '"status":"success"' "${f2}" 2>/dev/null; then
IP_SRC="${IP_SRC:+${IP_SRC}, }ip-api.com"
[[ -z "${IP_ADDR}" ]] && IP_ADDR="$(json_str query "${f2}")"
[[ -z "${IP_ASN}" ]] && IP_ASN="$(json_str as "${f2}" | awk '{print $1}')"
[[ -z "${IP_ORG}" ]] && IP_ORG="$(json_str org "${f2}")"
[[ -z "${IP_CC}" ]] && IP_CC="$(json_str countryCode "${f2}")"
[[ -z "${dc}" ]] && dc="$(json_bool hosting "${f2}")"
[[ -z "${mobile}" ]] && mobile="$(json_bool mobile "${f2}")"
[[ -z "${proxy}" ]] && proxy="$(json_bool proxy "${f2}")"
fi
if [[ -z "${IP_ADDR}" ]]; then
fetch ipinfo https://ipinfo.io/json >/dev/null
if grep -q '"ip"' "${f3}" 2>/dev/null; then
IP_SRC="ipinfo.io"
IP_ADDR="$(json_str ip "${f3}")"
IP_ASN="$(json_str org "${f3}" | awk '{print $1}')"
IP_ORG="$(json_str org "${f3}" | cut -d' ' -f2-)"
IP_CC="$(json_str country "${f3}")"
fi
fi
[[ -z "${IP_ADDR}" ]] && return 1
if [[ "${mobile}" == "true" ]]; then
IP_TYPE="$(xyz_t "mobile" "移动网络")"
elif [[ "${dc}" == "true" || "${ctype}" == "hosting" ]]; then
IP_TYPE="$(xyz_t "datacenter" "机房 IP")"
elif [[ "${dc}" == "false" ]]; then
IP_TYPE="$(xyz_t "residential / ISP" "住宅 / 运营商")"
elif printf '%s' "${IP_ORG}" | grep -q -i -E "${DC_WORDS}"; then
IP_TYPE="$(xyz_t "datacenter (guessed from ASN name)" "机房 IP(按 ASN 名称推断)")"
else
IP_TYPE="$(xyz_t "unknown (likely ISP)" "未知(可能是运营商 IP)")"
fi
[[ -n "${ctype}" && "${ctype}" != "hosting" ]] && IP_TYPE="${IP_TYPE} (${ctype})"
[[ "${vpn}" == "true" ]] && IP_FLAGS="${IP_FLAGS} VPN"
[[ "${proxy}" == "true" ]] && IP_FLAGS="${IP_FLAGS} proxy"
[[ "${tor}" == "true" ]] && IP_FLAGS="${IP_FLAGS} Tor"
[[ "${abuser}" == "true" ]] && IP_FLAGS="${IP_FLAGS} abuser"
IP_FLAGS="${IP_FLAGS# }"
return 0
}
xyz_step "$(xyz_t "Checking (IPv${IPV})" "正在检测(IPv${IPV})")"
IP_SRC=""
if [[ "${NO_IP}" != "1" ]]; then
xyz_info "$(xyz_t "IP lookup via api.ipapi.is (fallback ip-api.com). Skip with --no-ip." "IP 信息查询:api.ipapi.is(备用 ip-api.com),可用 --no-ip 跳过。")"
check_ip || xyz_warn "$(xyz_t "IP lookup failed." "IP 信息查询失败。")"
fi
check_netflix
check_disney
check_youtube
check_chatgpt
check_claude
check_gemini
check_tiktok
if [[ "${WANT_JSON}" == "1" ]]; then
esc() { printf '%s' "$1" | sed 's/\\/\\\\/g;s/"/\\"/g'; }
{
printf '{\n "ip": {"address": "%s", "asn": "%s", "org": "%s", "country": "%s", "type": "%s", "flags": "%s", "source": "%s"},\n "services": [\n' \
"$(esc "${IP_ADDR}")" "$(esc "${IP_ASN}")" "$(esc "${IP_ORG}")" "$(esc "${IP_CC}")" "$(esc "${IP_TYPE}")" "$(esc "${IP_FLAGS}")" "$(esc "${IP_SRC}")"
n=0
for r in "${RESULTS[@]}"; do
IFS='|' read -r svc st reg note <<< "${r}"
n=$((n + 1))
sep=","; [[ ${n} -eq ${#RESULTS[@]} ]] && sep=""
printf ' {"service": "%s", "status": "%s", "region": "%s", "note": "%s"}%s\n' "$(esc "${svc}")" "${st}" "$(esc "${reg}")" "$(esc "${note}")" "${sep}"
done
printf ' ]\n}\n'
} >&3
exit 0
fi
echo
xyz_init_tmpdir
UNLOCK_VIEW="$(xyz_tmpdir)/unlock-view.tsv"
: > "${UNLOCK_VIEW}"
for r in "${RESULTS[@]}"; do
IFS='|' read -r svc st reg note <<< "${r}"
printf '%s\t%s\t%s\t%s\n' "${svc}" "${st}" "${reg}" "${note}" >> "${UNLOCK_VIEW}"
if [[ -n "${XYZ_RESULT_FILE:-}" ]]; then
printf 'unlock\t%s\t%s\t%s\t%s\n' "${svc}" "${st}" "${reg}" "${note}" >> "${XYZ_RESULT_FILE}" 2>/dev/null || true
fi
done
read -r U_YES U_TOTAL < <(xyz_ui_unlock_count "${UNLOCK_VIEW}")
xyz_ui_top "$(xyz_t "Streaming / AI unlock" "流媒体 / AI 解锁") ${U_YES}/${U_TOTAL}"
if [[ "${NO_IP}" != "1" ]]; then
ip_type_c="${U_Y}"
[[ "${IP_TYPE}" == residential* || "${IP_TYPE}" == isp* ]] && ip_type_c="${U_G}"
xyz_ui_row "$(xyz_ui_pad "IP" 10)${IP_ADDR:-n/a} ${U_D}${IP_ASN:-} ${IP_ORG:0:30}${U_X}"
xyz_ui_row "$(xyz_ui_pad "$(xyz_t "Location" "位置")" 10)${IP_CC:-n/a} ${G_MID} ${ip_type_c}${IP_TYPE:-n/a}${U_X}${IP_FLAGS:+ ${G_MID} ${U_Y}${IP_FLAGS}${U_X}} ${U_D}(${IP_SRC:-n/a})${U_X}"
xyz_ui_sep
fi
xyz_ui_unlock_rows "${UNLOCK_VIEW}"
notes="$(awk -F'\t' '$2 != "yes" && $4 != "" { printf "%s: %s\n", $1, $4 }' "${UNLOCK_VIEW}")"
if [[ -n "${notes}" ]]; then
xyz_ui_sep
while IFS= read -r line; do xyz_ui_row "${U_D}${line:0:72}${U_X}"; done <<< "${notes}"
fi
xyz_ui_bottom
if [[ -n "${XYZ_RESULT_FILE:-}" && "${NO_IP}" != "1" ]]; then
printf 'ip\t%s\t%s\t%s\t%s\t%s\n' "${IP_CC}" "${IP_ASN}" "${IP_TYPE}" "${IP_FLAGS}" "${IP_ORG}" >> "${XYZ_RESULT_FILE}" 2>/dev/null || true
fi
echo
xyz_info "$(xyz_t \
"Heuristic results from this IP right now. For a larger matrix: --full (RegionRestrictionCheck) or --ipquality (IPQuality)." \
"以上为当前 IP 的启发式检测结果。更全面的检测:--full(RegionRestrictionCheck)或 --ipquality(IPQuality)。")"
推荐 VPS
以下为占位展示,尚未接入任何商家或联盟链接,也没有可点击的购买地址。上线推广前会再填入真实套餐与合规的返佣链接。
推荐方向:美国
商家待定
机房、配置与价格将在接入后填写。当前不是购买入口。
商家待定
机房、配置与价格将在接入后填写。当前不是购买入口。
商家待定
机房、配置与价格将在接入后填写。当前不是购买入口。