#!/bin/sh
filename="ByteVirt-TW-LXC"
# Universal embedded DDNS Shell Script
# Github:https://github.com/kkkgo/UE-DDNS
# Blog: https://blog.03k.org/post/ue-ddns.html
#func-Universal
#-DDNSINIT-
#Customizable option area

# Customize the network proxy that connects to the DNS provider API
# example1: PROXY="http://192.168.1.100:7890"
# example2: PROXY="socks5h://192.168.1.100:7890" (curl only)
PROXY=""

# Specifies a network interface is used to connect to the network (curl only)
# example: OUT="eth0"
OUT=""

# Custom Web sites that check IP addresses
# example: CHECKURL="http://ipsu.03k.org"
CHECKURL=""

# ValidateCA=1, will verify the validity of the HTTPS certificate.
# You need to configure the CA certificate environment on the current system,
# such as installing the ca-certificates package.
ValidateCA=0

# ntfy is a simple HTTP-based pub-sub notification service.
# https://ntfy.sh/
# ddns_ntfy_url="http://ntfy.sh/yourtopic"
ddns_ntfy_url=""

# Bark is an iOS App which allows you to push customed notifications to your iPhone.
# https://github.com/Finb/bark-server
# ddns_bark_url="https://api.day.app/yourkey"
ddns_bark_url=""

# sct is a message push platform(wechat).
# https://sct.ftqq.com/
# ddns_sct_url="https://sctapi.ftqq.com/yourkey.send"
ddns_sct_url=""

# pushplus is a message push platform(wechat).
# https://www.pushplus.plus/
# ddns_pushplus_url="http://www.pushplus.plus/send?token=yourkey"
ddns_pushplus_url="http://www.pushplus.plus/send?token=ef13a58d256d4fc5ab0edf3cbdbef28f"

# dingtalk group robot push.
# https://open.dingtalk.com/document/robots/custom-robot-access/
# ddns_dingtalk_url="https://oapi.dingtalk.com/robot/send?access_token=yourtoken"
ddns_dingtalk_url=""

#Customizable option end

versionUA="github.com/kkkgo/UE-DDNS"
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin:/opt/sbin:$PATH"
IPREX4='([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
IPREX6="(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
PUBIPREX6="^2[0-9a-fA-F]{3}:[0-9a-fA-F:]+"
DOMAINREX="[0-9a-z-]+\.[\.0-9a-z-]+"
DEVREX="[-_0-9a-zA-Z@.]+"
date +"%Y-%m-%d %H:%M:%S %Z"
#func-DDNSINIT
ddns_provider="cloudflare"
ddns_fulldomain="tw.gogogogo.asia"
ddns_IPV="4"
ddns_IPVType="A"
ddns_main_domain="gogogogo.asia"
ddns_record_domain="tw.gogogogo.asia"
cloudflare_API_Token="o20FIJtB68yQqVrhlc1sH7kLaQJipf4OqylkJz8o"
cloudflare_zoneid="d702fdb4dc5a005fb23de60c57afb23f"
cloudflare_record_id="1627a70364b0659142061d3fffcbf573"
cloudflare_cdn="false"
stripIP() {
    if [ "$2" = "6" ]; then
        echo "$1" | grep -Eo "$IPREX6" | grep -Eo "$PUBIPREX6"
    else
        echo "$1" | grep -Eo "$IPREX4"
    fi
    return $?
}
#func-stripIP
# pingDNS example.com 4/6
pingDNS() {
    if [ "$2" = "6" ]; then
        TEST=$(stripIP "$(ping -6 -c1 -W1 "$1" 2>&1)" "6") || return 1
    else
        TEST=$(stripIP "$(ping -4 -c1 -W1 "$1" 2>&1)" "4") || return 1
    fi
    stripIP "$TEST" "$2" | tail -1
    return 0
}
#func-pingDNS
# nslookupDNS example.com 4/6 $dnsserver
nslookupDNS() {
    if NSTEST=$(nslookup "$1" "$3" 2>&1); then
        TEST=$(stripIP "$NSTEST" "$2") || return 1
        stripIP "$TEST" "$2" | grep -v "$3" | tail -1
        return 0
    fi
    return 1
}
#func-nslookupDNS
# curlDNS example.com 4/6
curlDNS() {
    if [ "$2" = "6" ]; then
        TEST=$(stripIP "$(curl -6kvsL "$1"":0" -m 1 2>&1)" "6") || return 1
    else
        TEST=$(stripIP "$(curl -4kvsL "$1"":0" -m 1 2>&1)" "4") || return 1
    fi
    stripIP "$TEST" "$2" | head -1
    return 0
}
#func-curlDNS
# wgetDNS example.com 4/6 , inet*-only option only on high version wget.
wgetDNS() {
    TEST=$(stripIP "$(wget --spider -T1 --tries=1 "$1"":0" 2>&1)" "$2") || return 1
    stripIP "$TEST" "$2" | head -1
    return 0
}
#func-wgetDNS
getDNSIP_local() {
    IPV="$2"
    if [ -z "$2" ]; then
        IPV="4"
    fi
    getDNS_list="$(pingDNS "$1" "$IPV") $(curlDNS "$1" "$IPV") $(wgetDNS "$1" "$IPV")"
    stripIP "$getDNS_list" "$2"
}
#func-getDNSIP_local
getDNSIP_resolve_1() {
    IPV="$2"
    if [ -z "$2" ]; then
        IPV="4"
    fi
    getDNS_list="$(httpDNS "$1" "$IPV") $(nslookupDNS "$1" "$IPV" "223.5.5.5") $(nslookupDNS "$1" "$IPV" "8.8.8.8")"
    stripIP "$getDNS_list" "$2"
}
#func-getDNSIP_resolve_1
getDNSIP_resolve_2() {
    IPV="$2"
    if [ -z "$2" ]; then
        IPV="4"
    fi
    getDNS_list="$(nslookupDNS "$1" "$IPV" "114.114.114.114") $(nslookupDNS "$1" "$IPV" "1.0.0.1") $(nslookupDNS "$1" "$IPV" "119.29.29.29")"
    stripIP "$getDNS_list" "$2"
}
#func-getDNSIP_resolve_2
ddns_comp_DNS() {
    ddns_DNSIP_list=$(getDNSIP_local "$ddns_fulldomain" "$ddns_IPV")
    if echo "$ddns_DNSIP_list" | grep -Eo "[^ ]+" | grep -Eqo "^""$ddns_newIP""$" 2>&1; then
        echo "IP SAME IN DNS,SKIP UPDATE."
        exit
    else
        ddns_DNSIP_list="$ddns_DNSIP_list"" ""$(getDNSIP_resolve_1 "$ddns_fulldomain" "$ddns_IPV")"
        if echo "$ddns_DNSIP_list" | grep -Eo "[^ ]+" | grep -Eqo "^""$ddns_newIP""$" 2>&1; then
            echo "IP SAME IN DNS,SKIP UPDATE."
            exit
        else
            ddns_DNSIP_list="$ddns_DNSIP_list"" ""$(getDNSIP_resolve_2 "$ddns_fulldomain" "$ddns_IPV")"
            if echo "$ddns_DNSIP_list" | grep -Eo "[^ ]+" | grep -Eqo "^""$ddns_newIP""$" 2>&1; then
                echo "IP SAME IN DNS,SKIP UPDATE."
                exit
            else
                ddns_DNSIP=$(stripIP "$ddns_DNSIP_list" "$ddns_IPV" | head -1)
                ddns_DNSIP=$(stripIP "$ddns_DNSIP" "$ddns_IPV") || ddns_DNSIP="Get ""$ddns_fulldomain"" IPV""$ddns_IPV"" DNS IP Failed."
                echo "DNS IP : ""$ddns_DNSIP"
            fi
        fi
    fi
}
#func-ddns_comp_DNS
# genfetchCMD $URL $useProxy $fetchIPV
# postMethod=;postdata=;fetchCMD http://blog.03k.org useProxy 4/6/0
genfetchCMD() {
    if echo "$3" | grep -Eqo "^[46]{1}$"; then
        fetchIPV=$3
    else
        fetchIPV=""
    fi
    export http_proxy=""
    fetchProxy=""
    if echo "$2" | grep -qEo "useProxy"; then
        if echo "$PROXY" | grep -qEo ":"; then
            fetchProxy=$PROXY
            if echo "$PROXY" | grep -qEo "http:"; then
                export http_proxy="$fetchProxy"
            fi
        fi
    fi
    fetchPost=""
    if [ -n "$postData" ]; then
        fetchPost=$postData
        postData=""
    fi
    if [ -n "$postMethod" ]; then
        fetchMethod=$postMethod
        postMethod=""
    else
        fetchMethod="POST"
    fi
    fetchLine=$OUT
    URL=$1
    fetchBIN="curl"
    curlVer=$(curl -V 2>&1) || fetchBIN="wget"
    if [ "$fetchBIN" = "curl" ]; then
        echo curl -"$fetchIPV"sL -m5 -A "$versionUA"@curl-"$(echo "$curlVer" | grep -Eo "[0-9.]+" | head -1)" \
            $(if [ "$ValidateCA" != "1" ]; then echo "-k"; fi) \
            $(if [ -n "$fetchLine" ]; then echo "--interface $fetchLine"; fi) \
            $(if [ -n "$fetchProxy" ]; then echo "-x $fetchProxy"; fi) \
            $(if [ -n "$fetchPost" ]; then echo "-X $fetchMethod -d $fetchPost"; fi) \
            "$URL"
    else
        fetchwget=$(wget -V 2>&1) || fetchwget="busybox"
        if [ "$fetchwget" = "busybox" ]; then
            # busybox wget : Not support IPV46/OUT/SOCKS/fetchMethod
            wgetVersion=$(busybox 2>&1 | grep -Eo "[0-9.]+" | head -1)
            echo wget -U "$versionUA"@wget/busybox"$wgetVersion" -q -O- -T5 -Y \
                $(if [ -n "$fetchProxy" ]; then echo "on"; else echo "off"; fi) \
                $(if [ "$ValidateCA" != "1" ]; then echo "--no-check-certificate"; fi) \
                $(if [ -n "$fetchPost" ]; then echo --post-data "$fetchPost"; fi) \
                "$URL"
        else
            # GNU wget: Not support OUT/SOKCS
            wgetVersion="GNUwget-"$(wget -V | grep -Eo "[0-9.]+" | head -1)
            echo wget -U "$versionUA"@"$wgetVersion" -q"$fetchIPV" -O- --no-check-certificate -T5 \
                $(if [ -n "$fetchProxy" ]; then echo "--no-proxy"; fi) \
                $(if [ -n "$fetchPost" ]; then echo "--method=$fetchMethod --body-data=$fetchPost"; fi) \
                "$URL"
        fi
    fi
}
#func-genfetchCMD
# httpDNS example.com 4/6
httpDNS() {
    if [ "$2" = "6" ]; then
        TEST=$(stripIP "$($(genfetchCMD "http://223.6.6.6/resolve?type=28&name=""$1" noproxy) 2>&1)" "6") || return 1
    else
        TEST=$(stripIP "$($(genfetchCMD "http://223.6.6.6/resolve?type=1&name=""$1" noproxy) 2>&1)" "4") || return 1
    fi
    stripIP "$TEST" "$2" | tail -1
    return 0
}
#func-httpDNS
fetch_cloudflare() {
    $(genfetchCMD https://api.cloudflare.com/client/v4/zones/"$1" useProxy) --header "Authorization: Bearer $cloudflare_API_Token" --header "Content-Type:application/json"
}
#func-fetch_cloudflare
getIP_cloudflare() {
    test_getIP_cloudflare=$(fetch_cloudflare "$cloudflare_zoneid"/dns_records/"$cloudflare_record_id")
    ddns_API_IP="Get ""$ddns_fulldomain"" IPV""$ddns_IPV"" API IP Failed."
    if echo "$test_getIP_cloudflare" | grep -qEo 'success":true'; then
        test_API_IP=$(stripIP "$(echo "$test_getIP_cloudflare" | grep -Eo '"type":"'"$ddns_IPVType"'","content":"[^"]+' | grep -Eo '[^"]+' | tail -1)" "$ddns_IPV") || return 1
        ddns_API_IP=$test_API_IP
    fi
}
#func-getIP_cloudflare
getURLIP() {
    IPV=$1
    if [ -z "$1" ]; then
        IPV="4"
    fi
    TESTURLS="$CHECKURL http://ipsu.03k.org/cdn-cgi/trace http://test.ipw.cn https://cf-ns.com/cdn-cgi/trace https://www.cloudflare.com/cdn-cgi/trace http://ip.gs http://ident.me/ http://4.ipw.cn/ http://6.ipw.cn/ http://checkip.synology.com/ http://checkipv6.synology.com/ https://v4.ident.me/ https://v6.ident.me/ https://1.0.0.1/cdn-cgi/trace https://[2606:4700:4700::1111]/cdn-cgi/trace"
    TESTLIST=$(echo "$TESTURLS" | grep -Eo "http[^ ]+")
    countTESTLIST=$(echo "$TESTLIST" | grep -Eo "http[^ ]+" | grep -c "")
    i=0
    while [ "$i" -ne "$countTESTLIST" ]; do
        i=$((i + 1))
        testurl=$(echo "$TESTLIST" | grep -Eo "http[^ ]+" | sed -n "$i"p) || continue
        TESTURLIP=$(stripIP "$($(genfetchCMD "$testurl" noproxy "$IPV") 2>&1)" "$IPV") || continue
        stripIP "$TESTURLIP" "$IPV" | tail -1
        return 0
    done
    echo "Get ""$ddns_fulldomain"" IPV""$IPV"" URL IP Failed."
    return 1
}
#func-getURLIP
ddns_check_URL() {
    ddns_URLIP=$(getURLIP "$ddns_IPV")
    if [ "$?" = "0" ]; then
        echo "URL IP : ""$ddns_URLIP"
        ddns_newIP=$ddns_URLIP
        ddns_comp_DNS
        getIP_"$ddns_provider"
        echo "API IP : ""$ddns_API_IP"
        if [ "$ddns_URLIP" = "$ddns_API_IP" ]; then
            echo "URL IP SAME IN API,SKIP UPDATE."
            exit
        fi
    else
        echo "$ddns_URLIP"
        exit
    fi
}
#func-ddns_check_URL
ddns_check_URL
ddns_update_cloudflare() {
    postMethod="PUT"
    postData="{\"type\":\"""$ddns_IPVType""\",\"name\":\"""$ddns_record_domain""\",\"content\":\"""$ddns_newIP""\",\"ttl\":1,\"proxiable\":true,\"proxied\":""$cloudflare_cdn""}"
    test_ddns_result=$(fetch_cloudflare "$cloudflare_zoneid"/dns_records/"$cloudflare_record_id")
    postData=""
    postMethod=""
    if echo "$test_ddns_result" | grep -q 'success":true'; then
        ddns_result="Update OK: "$(echo "$test_ddns_result" | grep -Eo '"type":"[^}]+ttl":[ 0-9]+' | head -1)
    else
        error_msg=$(echo "$test_ddns_result" | grep -Eo "errors[^]]+" | grep -Eo "\{.+")
        ddns_result="Update_failed:""$error_msg"
        ddns_newIP=$ddns_result
    fi
}
#func-ddns_update_cloudflare
check_ddns_newIP() {
    if [ -z "$ddns_newIP" ]; then
        echo "Error:Failed to get new dynamic IP values."
        exit
    fi
}
#func-check_ddns_newIP
check_ddns_newIP
echo Trying to update: "$ddns_fulldomain"" -> ""$ddns_newIP"
ddns_update_cloudflare
ddns_result_print() {
    try_en=$(echo -en 1 2>&1)
    if echo "$try_en" | grep -q "en"; then
        echo "$ddns_result"
    else
        ddns_result=$(echo -e "$ddns_result")
        echo -e "$ddns_result"
    fi
}
#func-ddns_result_print
ddns_result_print
# check_push $name $msg $succ_code
check_push() {
    if echo "$2" | grep -q "$3" 2>&1; then
        echo "$1"" push OK."
    else
        echo "$1"" push Failed.""$2"
    fi
}
#func-check_push
push_result() {
    ddns_newIP=$(echo "$ddns_newIP" | sed 's/ /_/g' | sed 's/{//g' | sed 's/}//g' | sed 's/"//g')
    #nfty
    if echo "$ddns_ntfy_url" | grep -Eoq "http"; then
        postData="$ddns_fulldomain"":""$ddns_newIP"
        check_push "Ntfy" "$($(genfetchCMD "$ddns_ntfy_url" useProxy))" "message"
        postData=""
    fi
    #bark
    if echo "$ddns_bark_url" | grep -Eoq "http"; then
        check_push "Bark" "$($(genfetchCMD "$ddns_bark_url"/"$ddns_fulldomain"/"$ddns_newIP" useProxy))" "success"
    fi
    #sct
    if echo "$ddns_sct_url" | grep -Eoq "http"; then
        check_push "Sct" "$($(genfetchCMD "$ddns_sct_url""?title=""$ddns_newIP""-""$ddns_fulldomain""&desp=""$filename"":""$ddns_newIP" useProxy))" "SUCCESS"
    fi
    #pushplus
    if echo "$ddns_pushplus_url" | grep -Eoq "http"; then
        check_push "Pushplus" "$($(genfetchCMD "$ddns_pushplus_url""&title=""$ddns_newIP""-""$ddns_fulldomain""&content=""$filename"":""$ddns_newIP" useProxy))" "200"
    fi
    #dingtalk
    if echo "$ddns_dingtalk_url" | grep -Eoq "http"; then
        nowtime=$(date +%s 2>&1)
        postData="{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"IP-change-$nowtime-ipIpiP\",\"text\":\"$filename\n>$ddns_newIP\"}}"
        check_push "Dingtalk" "$($(genfetchCMD "$ddns_dingtalk_url" useProxy) --header "Content-Type:application/json")" 'errcode":0'
        postData=""
    fi
}
#func-push_result
push_result
