#!/bin/bash # FFmpeg / Nginx / Openresty / V2ray / Xray / Cloudflare / IBM Cloud Foundry / Armbian / Proxmox VE / ... # Copyright (C) 2019-2023 # Released under GPL Version 3 License set -euo pipefail sh_ver="1.87.9" sh_debug=0 export LANGUAGE= export LC_ALL= export LANG=en_US.UTF-8 SH_LINK="https://woniuzfb.github.io/iptv/iptv.sh" SH_FALLBACK="cn.epub.fun" SH_FILE="/usr/local/bin/tv" i18n_FILE="/usr/local/bin/tv-i18n" OR_FILE="/usr/local/bin/or" NX_FILE="/usr/local/bin/nx" XC_FILE="/usr/local/bin/cx" ARM_FILE="/usr/local/bin/arm" PVE_FILE="/usr/local/bin/pve" IPTV_ROOT="/usr/local/iptv" JQ_FILE="$IPTV_ROOT/jq" CURL_IMPERSONATE_FILE=/usr/local/bin/curl-impersonate CHANNELS_FILE="$IPTV_ROOT/channels.json" LOCK_FILE="$IPTV_ROOT/lock" MONITOR_LOG="$IPTV_ROOT/monitor.log" LOGROTATE_CONFIG="$IPTV_ROOT/logrotate" CRON_FILE="$IPTV_ROOT/cron" XTREAM_CODES="$IPTV_ROOT/xtream_codes" XTREAM_CODES_EXAM="$IPTV_ROOT/xtream_codes_exam" NODE_ROOT="$IPTV_ROOT/node" IP_DENY="$IPTV_ROOT/ip.deny" IP_LOG="$IPTV_ROOT/ip.log" FFMPEG_LOG_ROOT="$IPTV_ROOT/ffmpeg" # create your own mirror: tv ffmpeg BACKUP_ROOT="$HOME"/iptv_sh_backup FFMPEG_MIRROR_LINK="http://pngquant.com/ffmpeg" V2_FILE="/usr/local/bin/v2" V2_LINK="https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh" V2_LINK_FALLBACK="$FFMPEG_MIRROR_LINK/v2ray_install-release.sh" V2CTL_FILE="/usr/local/bin/v2ctl" V2_CONFIG="/usr/local/etc/v2ray/config.json" X_FILE="/usr/local/bin/x" X_CONFIG="/usr/local/etc/xray/config.json" FFMPEG_MIRROR_ROOT="$IPTV_ROOT/ffmpeg" LIVE_ROOT="$IPTV_ROOT/live" SERVICES_FILE="$IPTV_ROOT/services.json" VIP_FILE="$IPTV_ROOT/vip.json" VIP_CHANNELS_LINK="$FFMPEG_MIRROR_LINK/vip_channels.json" VIP_CHANNELS_FILE="$IPTV_ROOT/vip_channels.json" VIP_ROOT="$IPTV_ROOT/vip" VIP_USERS_ROOT="$VIP_ROOT/users" C_ROOT="$IPTV_ROOT/c" MD5SUM_FILE="$C_ROOT/md5sum" MD5SUM_LINK="https://raw.githubusercontent.com/woniuzfb/iptv/master/scripts/md5sum.c" MD5SUM_LINK_FALLBACK="$FFMPEG_MIRROR_LINK/md5sum.c" CF_FILE="/usr/local/bin/cf" CF_CONFIG="$HOME/cloudflare.json" CF_WORKERS_ROOT="$HOME/workers" CF_WORKERS_FILE="$CF_WORKERS_ROOT/cloudflare_workers.py" CF_WORKERS_LINK="https://raw.githubusercontent.com/woniuzfb/iptv/master/scripts/cloudflare_workers.py" CF_WORKERS_LINK_FALLBACK="$FFMPEG_MIRROR_LINK/cloudflare_workers.py" STREAM_PROXY_LINK="https://raw.githubusercontent.com/woniuzfb/iptv/master/scripts/stream_proxy.js" STREAM_PROXY_LINK_FALLBACK="$FFMPEG_MIRROR_LINK/stream_proxy.js" XTREAM_CODES_PROXY_LINK="https://raw.githubusercontent.com/woniuzfb/iptv/master/scripts/xtream_codes_proxy.js" XTREAM_CODES_PROXY_LINK_FALLBACK="$FFMPEG_MIRROR_LINK/xtream_codes_proxy.js" IBM_FILE="/usr/local/bin/ibm" IBM_APPS_ROOT="$HOME/ibm_apps" IBM_CONFIG="$HOME/ibm.json" DEFAULT_DEMOS="default.json" TS_CHANNELS="channels.json" XTREAM_CODES_CHANNELS="xtream_codes" USER_AGENT_BROWSER="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36" USER_AGENT_TV="Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3" USER_AGENT_PHONE="iPhone; CPU iPhone OS 15_2 like Mac OS X" monitor=false red='\033[31m' green='\033[32m' yellow='\033[33m' cyan='\033[36m' white='\033[37m' blue='\033[94m' normal='\033[0m' dim_underlined='\033[37;4;2m' indent_6='\r\033[6C' indent_20='\r\033[20C' shopt -s extglob Println() { printf '\n%b\n' "$1" } DistCheck() { [ -n "${dist:-}" ] && return 0 dist_files=( /etc/issue /etc/os-release /proc/version ) dist="" for dist_file in "${dist_files[@]}" do if [ ! -s "$dist_file" ] then continue fi if grep -Eqi "Red Hat|redhat|CentOS|Fedora|Amazon" < "$dist_file" then dist="rpm" break elif grep -qi "Ubuntu" < "$dist_file" then dist="ubu" break elif grep -qi "Debian" < "$dist_file" then dist="deb" break fi done if [ -z "$dist" ] then Println "${red}[ERROR]${normal} not support yet...\n" exit 1 fi } ArchCheck() { [ -n "${arch:-}" ] && return 0 arch=$(uname -m) if grep -Eqi "x86_64|amd64" <<< "$arch" then arch="x86_64" elif grep -Eqi "i386|i686|x86" <<< "$arch" then arch="i386" elif grep -Eqi "aarch64|armv8|arm64" <<< "$arch" then arch="arm64" elif grep -qi "armv7" <<< "$arch" then arch="armhf" elif grep -qi "armv6" <<< "$arch" then arch="armv6l" elif grep -qi "arm" <<< "$arch" then arch="armel" elif grep -qi "s390" <<< "$arch" then arch="s390x" fi } DebFixSources() { if [ "${deb_fix:-1}" -eq 1 ] then sed -i 's/^mozilla\/DST_Root_CA_X3.crt/!mozilla\/DST_Root_CA_X3.crt/g' /etc/ca-certificates.conf update-ca-certificates -f >/dev/null 2>&1 || true if [ -f /etc/apt/sources.list.d/sources-aliyun-0.list ] then deb_list=$(< /etc/apt/sources.list.d/sources-aliyun-0.list) rm -f /etc/apt/sources.list.d/sources-aliyun-0.list rm -rf /var/lib/apt/lists/* else deb_list=$(< /etc/apt/sources.list) fi if grep -q "jessie" <<< "$deb_list" then printf '%s' " deb http://archive.debian.org/debian/ jessie main deb-src http://archive.debian.org/debian/ jessie main deb http://security.debian.org jessie/updates main deb-src http://security.debian.org jessie/updates main " > "/etc/apt/sources.list" apt-get clean >/dev/null 2>&1 elif grep -q "wheezy" <<< "$deb_list" then printf '%s' " deb http://archive.debian.org/debian/ wheezy main deb-src http://archive.debian.org/debian/ wheezy main deb http://security.debian.org wheezy/updates main deb-src http://security.debian.org wheezy/updates main " > "/etc/apt/sources.list" apt-get clean >/dev/null 2>&1 fi deb_fix=0 sed -i '/deb_fix=/d' "$i18n_FILE" printf "deb_fix=%s" "$deb_fix" >> "$i18n_FILE" fi } AptUpdate() { if [ "${apt_updated:-false}" = false ] then Println "$info 更新软件包列表..." apt-get update --allow-releaseinfo-change >/dev/null apt_updated=true fi } DepInstall() { dependency=$1 [[ -x $(command -v $dependency) ]] && return 0 if [ "$dependency" == "gettext" ] || [ "$dependency" == "wget" ] then Println "${green}[INFO]${normal} Installing $dependency, it takes awhile..." if [ "$dist" == "rpm" ] then if [[ -x $(command -v getenforce) ]] && [ "$(getenforce)" != "Disabled" ] then setenforce 0 fi if ! rpm -q $dependency &>/dev/null then if yum -y install $dependency &>/dev/null then Println "${green}[INFO]${normal} $dependency installation succeed..." else Println "${green}[ERROR]${normal} $dependency installation failed...\n" return 1 fi fi else if [ "$dist" == "deb" ] then DebFixSources fi if ! dpkg -s $dependency &>/dev/null then AptUpdate if apt-get -y install $dependency &>/dev/null then Println "${green}[INFO]${normal} $dependency installation succeed..." else Println "${green}[ERROR]${normal} $dependency installation failed...\n" return 1 fi fi fi return 0 fi if [ "$dist" == "rpm" ] then if [[ -x $(command -v getenforce) ]] && [ "$(getenforce)" != "Disabled" ] then setenforce 0 fi if [ "$dependency" == "dig" ] then dependency="bind-utils" elif [ "$dependency" == "hexdump" ] then dependency="util-linux" elif [ "$dependency" == "ss" ] then dependency="iproute" elif [ "$dependency" == "tput" ] then dependency="ncurses" fi if ! rpm -q $dependency &>/dev/null then Println "`eval_gettext \"\\\$info 安装 \\\$dependency, 请稍等...\"`" if yum -y install $dependency >/dev/null 2>&1 then Println "`eval_gettext \"\\\$info \\\$dependency 安装成功\"`" else Println "`eval_gettext \"\\\$error \\\$dependency 安装失败\"`\n" return 1 fi fi else if [ "$dist" == "deb" ] then DebFixSources fi if [ "$dependency" == "dig" ] then dependency="dnsutils" elif [ "$dependency" == "hexdump" ] then dependency="bsdmainutils" elif [ "$dependency" == "ss" ] then dependency="iproute2" elif [ "$dependency" == "tput" ] then dependency="ncurses-bin" fi if ! dpkg -s $dependency &>/dev/null then AptUpdate Println "`eval_gettext \"\\\$info 安装 \\\$dependency, 请稍等...\"`" if apt-get -y install $1 >/dev/null 2>&1 then Println "`eval_gettext \"\\\$info \\\$dependency 安装成功\"`" else Println "`eval_gettext \"\\\$error \\\$dependency 安装失败\"`\n" return 1 fi fi fi } i18nInstall() { local sh_locale=${1:-zh_CN} Println "$info You can always use command ${green}tv c ${normal} to change/update language!" Println "Downloading ${green}$sh_locale${normal} language file...\n" DepInstall wget if [ "$sh_locale" == "zh_CN" ] then echo -e "sh_locale=$sh_locale\nexport LANG=zh_CN.UTF-8\nlocale_fix=0" > "$i18n_FILE" Println "`eval_gettext \"\\\${green}成功!\\\${normal}\"`\n" return 0 fi trap ' rm -f "$TEXTDOMAINDIR/$sh_locale/LC_MESSAGES/iptv.mo" ' EXIT if wget --timeout=15 --tries=3 --no-check-certificate $FFMPEG_MIRROR_LINK/locale/po/iptv.sh-$sh_locale.mo -qO $TEXTDOMAINDIR/$sh_locale/LC_MESSAGES/iptv.mo then trap - EXIT echo -e "sh_locale=$sh_locale\nexport LANG=en_US.UTF-8\nlocale_fix=0" > "$i18n_FILE" Println "`eval_gettext \"\\\${green}成功!\\\${normal}\"`\n" else Println "`eval_gettext \"\\\${red}失败! 请稍后再试!\\\${normal}\"`\n" exit 1 fi } i18nGetMsg() { case ${1:-} in "") i18n_yes=$(gettext "是") i18n_no=$(gettext "否") i18n_cancel=$(gettext "取消") i18n_canceled=$(gettext "已取消") i18n_default_cancel=$(gettext "(默认: 取消): ") i18n_input_correct_no=$(gettext "请输入正确的序号") i18n_input_correct_number=$(gettext "请输入正确的数字") i18n_not_set=${i18n_not_set:-$(gettext "不设置")} i18n_random=${i18n_random:-$(gettext "随机")} info="${green}`gettext \"[信息]\"`${normal}" error="${red}`gettext \"[错误]\"`${normal}" tip="${green}`gettext \"[注意]\"`${normal}" yn_options=( "$i18n_yes" "$i18n_no" ) ny_options=( "$i18n_no" "$i18n_yes" ) ;; get_default) i18n_proxy=${i18n_proxy:-$(gettext "代理")} i18n_xtream_codes_proxy=${i18n_xtream_codes_proxy:-$(gettext "xtream codes 代理")} i18n_user_agent=${i18n_user_agent:-$(gettext "user agent")} i18n_headers=${i18n_headers:-$(gettext "headers")} i18n_cookies=${i18n_cookies:-$(gettext "cookies")} i18n_playlist_name=${i18n_playlist_name:-$(gettext "m3u8名称")} i18n_playlist_link=${i18n_playlist_link:-$(gettext "m3u8链接")} i18n_seg_dir_name=${i18n_seg_dir_name:-$(gettext "分片子目录")} i18n_seg_name=${i18n_seg_name:-$(gettext "分片名称")} i18n_seg_length=${i18n_seg_length:-$(gettext "分片时长")} i18n_seg_count=${i18n_seg_count:-$(gettext "分片数")} i18n_video_codec=${i18n_video_codec:-$(gettext "视频编码")} i18n_audio_codec=${i18n_audio_codec:-$(gettext "音频编码")} i18n_delay=${i18n_delay:-$(gettext "延迟")} i18n_dvb_teletext=${i18n_dvb_teletext:-$(gettext "dvb teletext")} i18n_drawtext=${i18n_drawtext:-$(gettext "drawtext 水印")} i18n_crf=${i18n_crf:-$(gettext "固定质量因子")} i18n_bitrate=${i18n_bitrate:-$(gettext "码率")} i18n_resolution=${i18n_resolution:-$(gettext "分辨率")} i18n_rate_control=${i18n_rate_control:-$(gettext "码率控制")} i18n_encrypt=${i18n_encrypt:-$(gettext "加密")} i18n_encrypt_session=${i18n_encrypt_session:-$(gettext "加密 session")} i18n_keyinfo_name=${i18n_keyinfo_name:-$(gettext "keyinfo名称")} i18n_key_name=${i18n_key_name:-$(gettext "key名称")} i18n_input_flags=${i18n_input_flags:-$(gettext "输入参数")} i18n_output_flags=${i18n_output_flags:-$(gettext "输出参数")} i18n_sync=${i18n_sync:-$(gettext "sync")} i18n_sync_file=${i18n_sync_file:-$(gettext "sync file")} i18n_sync_index=${i18n_sync_index:-$(gettext "sync index")} i18n_sync_pairs=${i18n_sync_pairs:-$(gettext "sync pairs")} i18n_schedule_file=${i18n_schedule_file:-$(gettext "节目表文件")} i18n_flv_delay_seconds=${i18n_flv_delay_seconds:-$(gettext "flv 超时时间")} i18n_flv_restart_nums=${i18n_flv_restart_nums:-$(gettext "flv 重启次数")} i18n_hls_delay_seconds=${i18n_hls_delay_seconds:-$(gettext "hls 超时时间")} i18n_hls_min_bitrate=${i18n_hls_min_bitrate:-$(gettext "hls 最低码率")} i18n_hls_max_seg_size=${i18n_hls_max_seg_size:-$(gettext "hls 允许最大分片")} i18n_hls_restart_nums=${i18n_hls_restart_nums:-$(gettext "hls 重启次数")} i18n_hls_key_period=${i18n_hls_key_period:-$(gettext "hls key 持续时间")} i18n_hls_end_list=${i18n_hls_end_list:-$(gettext "EXT-X-ENDLIST")} i18n_anti_ddos_port=${i18n_anti_ddos_port:-$(gettext "anti ddos 封禁端口")} i18n_anti_ddos_syn_flood=${i18n_anti_ddos_syn_flood:-$(gettext "SYN Flood 防御")} i18n_anti_ddos=${i18n_anti_ddos:-$(gettext "anti ddos")} i18n_anti_leech=${i18n_anti_leech:-$(gettext "anti leech")} i18n_recheck_period=${i18n_recheck_period:-$(gettext "重启失败后定时检查间隔时间")} ;; get_channels) i18n_source=${i18n_source:-$(gettext "源")} i18n_proxy=${i18n_proxy:-$(gettext "代理")} i18n_output_dir_name=${i18n_output_dir_name:-$(gettext "输出目录")} i18n_video_shift=${i18n_video_shift:-$(gettext "画面延迟")} i18n_audio_shift=${i18n_audio_shift:-$(gettext "声音延迟")} i18n_seconds=${i18n_seconds:-$(gettext "秒")} i18n_rate_control=${i18n_rate_control:-$(gettext "码率控制")} i18n_average_bitrate=${i18n_average_bitrate:-$(gettext "平均码率")} i18n_constant_bitrate=${i18n_constant_bitrate:-$(gettext "固定码率")} i18n_constrained_encoding=${i18n_constrained_encoding:-$(gettext "限制性编码")} i18n_crf=${i18n_crf:-$(gettext "固定质量因子")} i18n_bitrate=${i18n_bitrate:-$(gettext "码率")} i18n_resolution=${i18n_resolution:-$(gettext "分辨率")} i18n_enabled=${i18n_enabled:-$(gettext "开启")} i18n_disabled=${i18n_disabled:-$(gettext "关闭")} i18n_pid=${i18n_pid:-$(gettext "进程ID")} i18n_status=${i18n_status:-$(gettext "状态")} i18n_channel_name=${i18n_channel_name:-$(gettext "频道名称")} i18n_codec=${i18n_codec:-$(gettext "编码")} i18n_video_audio_shift=${i18n_video_audio_shift:-$(gettext "延迟")} i18n_video_quality=${i18n_video_quality:-$(gettext "视频质量")} i18n_playlist_file=${i18n_playlist_file:-$(gettext "路径")} i18n_flv_push_link=${i18n_flv_push_link:-$(gettext "推流地址")} i18n_flv_pull_link=${i18n_flv_pull_link:-$(gettext "拉流地址")} ;; get_channel) i18n_channel_try_again=${i18n_channel_try_again:-$(gettext "频道发生变化, 请重试 !")} i18n_source=${i18n_source:-$(gettext "源")} i18n_live=${i18n_live:-$(gettext "无限时长直播")} i18n_proxy=${i18n_proxy:-$(gettext "代理")} i18n_xtream_codes_proxy=${i18n_xtream_codes_proxy:-$(gettext "xtream codes 代理")} i18n_user_agent=${i18n_user_agent:-$(gettext "user agent")} i18n_headers=${i18n_headers:-$(gettext "headers")} i18n_cookies=${i18n_cookies:-$(gettext "cookies")} i18n_output_dir_name=${i18n_output_dir_name:-$(gettext "输出目录")} i18n_video_shift=${i18n_video_shift:-$(gettext "画面延迟")} i18n_audio_shift=${i18n_audio_shift:-$(gettext "声音延迟")} i18n_seconds=${i18n_seconds:-$(gettext "秒")} i18n_playlist_name=${i18n_playlist_name:-$(gettext "m3u8名称")} i18n_playlist_link=${i18n_playlist_link:-$(gettext "m3u8链接")} i18n_seg_dir_name=${i18n_seg_dir_name:-$(gettext "分片子目录")} i18n_seg_name=${i18n_seg_name:-$(gettext "分片名称")} i18n_seg_length=${i18n_seg_length:-$(gettext "分片时长")} i18n_seg_count=${i18n_seg_count:-$(gettext "分片数")} i18n_video_codec=${i18n_video_codec:-$(gettext "视频编码")} i18n_audio_codec=${i18n_audio_codec:-$(gettext "音频编码")} i18n_delay=${i18n_delay:-$(gettext "延迟")} i18n_dvb_teletext=${i18n_dvb_teletext:-$(gettext "dvb teletext")} i18n_drawtext=${i18n_drawtext:-$(gettext "drawtext 水印")} i18n_crf=${i18n_crf:-$(gettext "固定质量因子")} i18n_bitrate=${i18n_bitrate:-$(gettext "码率")} i18n_resolution=${i18n_resolution:-$(gettext "分辨率")} i18n_enabled=${i18n_enabled:-$(gettext "开启")} i18n_disabled=${i18n_disabled:-$(gettext "关闭")} i18n_rate_control=${i18n_rate_control:-$(gettext "码率控制")} i18n_average_bitrate=${i18n_average_bitrate:-$(gettext "平均码率")} i18n_constant_bitrate=${i18n_constant_bitrate:-$(gettext "固定码率")} i18n_constrained_encoding=${i18n_constrained_encoding:-$(gettext "限制性编码")} i18n_encrypt=${i18n_encrypt:-$(gettext "加密")} i18n_encrypt_session=${i18n_encrypt_session:-$(gettext "加密 session")} i18n_keyinfo_name=${i18n_keyinfo_name:-$(gettext "keyinfo名称")} i18n_key_name=${i18n_key_name:-$(gettext "key名称")} i18n_input_flags=${i18n_input_flags:-$(gettext "输入参数")} i18n_output_flags=${i18n_output_flags:-$(gettext "输出参数")} i18n_channel_name=${i18n_channel_name:-$(gettext "频道名称")} i18n_sync=${i18n_sync:-$(gettext "sync")} i18n_sync_file=${i18n_sync_file:-$(gettext "sync file")} i18n_sync_index=${i18n_sync_index:-$(gettext "sync index")} i18n_sync_pairs=${i18n_sync_pairs:-$(gettext "sync pairs")} i18n_hls_end_list=${i18n_hls_end_list:-$(gettext "EXT-X-ENDLIST")} i18n_flv_h265=${i18n_flv_h265:-$(gettext "FLV h265")} i18n_flv_push_link=${i18n_flv_push_link:-$(gettext "推流地址")} i18n_flv_pull_link=${i18n_flv_pull_link:-$(gettext "拉流地址")} i18n_anti_leech=${i18n_anti_leech:-$(gettext "防盗链")} i18n_none=${i18n_none:-$(gettext "无")} i18n_sync_not_set=${i18n_sync_not_set:-$(gettext "请先设置 sync")} i18n_sync_not_enabled=${i18n_sync_not_enabled:-$(gettext "请先开启 sync")} ;; *) ;; esac } LocaleFix() { Println "${green}[INFO]${normal} Installing language (locale) support, it takes awhile..." if [ "$dist" == "rpm" ] then if [[ -x $(command -v getenforce) ]] && [ "$(getenforce)" != "Disabled" ] then setenforce 0 fi yum -y install glibc-common glibc-locale-source glibc-all-langpacks glibc-langpack-en glibc-langpacks-zh langpacks-zh_CN >/dev/null 2>&1 || true else if [ "$dist" == "deb" ] then DebFixSources fi if [[ ! -x $(command -v locale-gen) ]] then AptUpdate if ! apt-get -y install locales >/dev/null 2>&1 then Println "${red}[ERROR]${normal} locales installation failed\n" && exit 1 fi fi if [ -s /etc/locale.gen ] then sed -i "s/# $1.UTF-8 UTF-8/$1.UTF-8 UTF-8/" /etc/locale.gen fi locale-gen $1.UTF-8 >/dev/null fi if ! grep -q "$1" < <(locale -a 2> /dev/null) then Println "${red}[ERROR]${normal} locales installation failed\n" && exit 1 fi } eval_gettext() { gettext "$1" | (export PATH `envsubst --variables "$1"`; envsubst "$1") } DistCheck TEXTDOMAIN=iptv TEXTDOMAINDIR=/usr/share/locale export TEXTDOMAIN TEXTDOMAINDIR DepInstall gettext [ $EUID -ne 0 ] && Println "`eval_gettext \"\\\${red}[ERROR]\\\${normal} MUST BE ROOT, TRY\\\${green} sudo su \\\${normal}\"`\n" && exit 1 if [ -s "$i18n_FILE" ] then # shellcheck source=/dev/null . "$i18n_FILE" fi if [ -z "${sh_locale:-}" ] then sh_locale="zh_CN" if [ "${locale_fix:-1}" -eq 1 ] && ! grep -q 'zh_CN' < <(locale -a 2> /dev/null) then LocaleFix zh_CN fi echo -e "sh_locale=$sh_locale\nexport LANG=zh_CN.UTF-8\nlocale_fix=0" > "$i18n_FILE" elif [ "$sh_locale" == "zh_CN" ] then if [ "${locale_fix:-1}" -eq 1 ] then if ! grep -q 'zh_CN' < <(locale -a 2> /dev/null) then LocaleFix zh_CN fi echo -e "sh_locale=$sh_locale\nexport LANG=zh_CN.UTF-8\nlocale_fix=0" > "$i18n_FILE" fi else if [ "${locale_fix:-1}" -eq 1 ] && [ "$sh_locale" == "en" ] && ! grep -q 'en_US' < <(locale -a 2> /dev/null) then LocaleFix en_US echo -e "sh_locale=$sh_locale\nexport LANG=en_US.UTF-8\nlocale_fix=0" > "$i18n_FILE" fi if [ ! -s "$TEXTDOMAINDIR/$sh_locale/LC_MESSAGES/iptv.mo" ] then i18nInstall "$sh_locale" fi fi i18nGetMsg DepsCheck() { if [ "${deps_checked:-false}" = true ] then return 0 fi DepInstall tput Spinner "`gettext \"检查依赖, 耗时可能会很长\"`" DepsInstall deps_checked=true } DepsInstall() { if [ "$dist" == "rpm" ] then if [[ -x $(command -v getenforce) ]] && [ "$(getenforce)" != "Disabled" ] then setenforce 0 fi depends=(sudo wget unzip vim curl crond logrotate patch nscd bind-utils util-linux iproute) if [ "${rpm_fix:-1}" -eq 1 ] then yum -y update ca-certificates &> /dev/null || yum -y reinstall ca-certificates &> /dev/null rpm_fix=0 sed -i '/rpm_fix=/d' "$i18n_FILE" printf "rpm_fix=%s" "$rpm_fix" >> "$i18n_FILE" fi for depend in "${depends[@]}" do [[ -x $(command -v $depend) ]] && continue if ! rpm -q "$depend" &> /dev/null then if yum -y install "$depend" &> /dev/null then Println "`eval_gettext \"\\\$info 依赖 \\\$depend 安装成功\"`" else Println "`eval_gettext \"\\\$error 依赖 \\\$depend 安装失败\"`\n" return 1 fi fi done else if [ "$dist" == "deb" ] then DebFixSources fi AptUpdate depends=(ca-certificates sudo wget unzip vim curl cron ufw python3 logrotate patch nscd dnsutils bsdmainutils) for depend in "${depends[@]}" do [[ -x $(command -v $depend) ]] && continue if ! dpkg -s "$depend" &> /dev/null then if apt-get -y install "$depend" &> /dev/null then Println "`eval_gettext \"\\\$info 依赖 \\\$depend 安装成功\"`" else Println "`eval_gettext \"\\\$error 依赖 \\\$depend 安装失败\"`\n" return 1 fi fi done fi } # based on https://raw.githubusercontent.com/tanhauhau/Inquirer.sh/master/dist/inquirer.sh inquirer() { DepInstall tput inquirer:print() { tput el printf '%b' "$1" } inquirer:print_input() { tput el printf '%s' "$1" } inquirer:join() { local var=("$1"[@]) if [[ -z ${!var:-} ]] then return fi local join_list=("${!var}") first=true item for item in "${join_list[@]}" do if [ "$first" = true ] then printf '%b' "$item" first=false else printf "${2-, }%b" "$item" fi done } inquirer:on_default() { return } inquirer:on_keypress() { local oIFS=$IFS local key char_read byte_len local oLC_ALL=${LC_ALL:-} oLANG=${LANG:-} local on_up=${1:-inquirer:on_default} local on_down=${2:-inquirer:on_default} local on_space=${3:-inquirer:on_default} local on_enter=${4:-inquirer:on_default} local on_left=${5:-inquirer:on_default} local on_right=${6:-inquirer:on_default} local on_ascii=${7:-inquirer:on_default} local on_backspace=${8:-inquirer:on_default} local on_not_ascii=${9:-inquirer:on_default} break_keypress=false while IFS= read -rsn1 key do case "$key" in $'\x1b') read -rsn1 key if [ "$key" == "[" ] then read -rsn1 key case "$key" in 'A') $on_up;; 'B') $on_down;; 'D') $on_left;; 'C') $on_right;; esac fi ;; $'\x20') $on_space;; $'\x7f') $on_backspace "$key";; '') $on_enter "$key";; # The space is the first printable character listed on http://www.asciitable.com/, ~ is the last # [^ -~] *[$'\x80'-$'\xFF']*) if [[ ${BASH_VERSINFO[0]} -lt 4 ]] then char_read="${char_read:-}$key" LC_ALL= LANG=C byte_len=${#char_read} LC_ALL=$oLC_ALL LANG=$oLANG if [ "$byte_len" -ne "${#char_read}" ] then $on_not_ascii "$char_read" char_read="" fi else $on_not_ascii "$key" fi ;; $'\x09') local i for((i=0;i<4;i++)); do $on_space done ;; *) $on_ascii "$key";; esac if [ "$break_keypress" = true ] then break fi done IFS="$oIFS" } inquirer:cleanup() { tput sgr0 tput cnorm stty echo } inquirer:control_c() { inquirer:cleanup exit $? } inquirer:remove_instructions() { if [ "$first_keystroke" = true ] then tput cuu $((current_index+1)) tput cub "$(tput cols)" tput cuf $((prompt_width+3)) tput el if [ -n "${pages_tip:-}" ] then inquirer:print "$pages_tip" fi tput cud $((current_index+1)) first_keystroke=false fi } inquirer:page_instructions() { tput cuu $((current_index+1)) tput cub "$(tput cols)" tput cuf $((prompt_width+3)) tput el inquirer:print "$pages_tip" tput cud $((current_index+1)) } inquirer:on_checkbox_input_up() { if [ "$input_search" = true ] then tput cub "$(tput cols)" tput el tput cuu1 tput el tput cuu1 tput el stty -echo tput civis local i for((i=0;i 选择, 确认)\"`${normal}\n\n\n" fi inquirer:on_checkbox_input_up return fi if [ "$input_page" = true ] then input_page=false if [ -n "${input_page_num:-}" ] then if [ "$input_page_num" -gt "$pages_count" ] || [ "$input_page_num" -eq $((pages_index+1)) ] then input_page_num="" return fi pages_index=$((input_page_num-1)) pages_tip="${dim}$pages_arrows $((pages_index+1))/$pages_count `gettext \"页\"`${normal}" if [ "$input_page_num" -eq "$pages_count" ] then page_list_count=$((list_count-pages_index*list_perpage)) else page_list_count=$list_perpage fi inquirer:page_instructions tput cub "$(tput cols)" tput cud $((list_perpage-current_index+1)) for((i=0;i<=list_perpage;i++)); do tput el tput cuu1 done tput el if [ "$current_index" -gt "$page_list_count" ] then current_index=$page_list_count fi page_list=() checkbox_page_selected=() checkbox_page_select_all=true for((i=0;i 选择, 确认)\"`${normal}\n" for i in "${!checkbox_list[@]}" do checkbox_selected[i]=false done var=("$3"[@]) if [[ -n ${!var:-} ]] then checkbox_selected_indices=("${!var}") for i in "${checkbox_selected_indices[@]}" do checkbox_selected[i]=true done checkbox_selected_indices=() fi for i in "${!checkbox_list[@]}" do if [ "$i" = 0 ] then if [ "${checkbox_selected[i]}" = true ] then inquirer:print "${cyan}${arrow}${green}${checked}${normal} ${checkbox_list[i]}\n" else inquirer:print "${cyan}${arrow}${normal}${unchecked} ${checkbox_list[i]}\n" fi elif [ "$pages_count" -gt 1 ] && [ "$i" = "$list_perpage" ] then break else if [ "${checkbox_selected[i]}" = true ] then inquirer:print " ${green}${checked}${normal} ${checkbox_list[i]}\n" else inquirer:print " ${unchecked} ${checkbox_list[i]}\n" fi fi page_list_count=$((page_list_count+1)) page_list+=("${checkbox_list[i]}") done for((i=0;i 上下移动)\"`${normal}\n" for i in "${!sort_options[@]}" do if [ $i = 0 ] then inquirer:print "${cyan}${arrow} ${sort_options[i]} ${normal}\n" else inquirer:print " ${sort_options[i]}\n" fi done tput cuu ${#sort_options[@]} inquirer:on_keypress inquirer:on_sort_up inquirer:on_sort_down inquirer:on_sort_enter_space inquirer:on_sort_enter_space inquirer:on_default inquirer:on_default inquirer:on_sort_ascii } inquirer:sort_input() { var_name=$3 inquirer:_sort_input "$1" "$2" read -r -a ${var_name?} <<< "${sort_options[@]}" inquirer:cleanup trap - EXIT } inquirer:sort_input_indices() { var_name=$3 inquirer:_sort_input "$1" "$2" read -r -a ${var_name?} <<< "${sort_indices[@]}" inquirer:cleanup trap - EXIT } inquirer:on_list_input_up() { if [ "$input_search" = true ] then tput cub "$(tput cols)" tput el tput cuu1 tput el tput cuu1 tput el stty -echo tput civis local i for((i=0;i /dev/null 2>&1 then return 1 fi return } inquirer:remove_date_instructions() { if [ "$first_keystroke" = true ] then tput sc tput civis tput cuu 1 tput cub "$(tput cols)" tput cuf $((prompt_width+3)) tput el tput rc tput cnorm first_keystroke=false fi } inquirer:on_date_pick_ascii() { case "$1" in "w" ) inquirer:on_date_pick_up;; "s" ) inquirer:on_date_pick_down;; "a" ) inquirer:on_date_pick_left;; "d" ) inquirer:on_date_pick_right;; esac } inquirer:on_date_pick_up() { inquirer:remove_date_instructions case $current_pos in 3) date_pick="$((${date_pick:0:4}+1))${date_pick:4}" ;; 6) local month=$((10#${date_pick:5:2}+1)) [ "$month" -eq 13 ] && month=1 date_pick="${date_pick:0:5}$(printf %02d "$month")${date_pick:7}" ;; 9) local day=$((10#${date_pick:8:2}+1)) [ "$day" -eq 32 ] && day=1 date_pick="${date_pick:0:8}$(printf %02d "$day")${date_pick:10}" ;; 12) local hour=$(((10#${date_pick:11:2}+1)%24)) date_pick="${date_pick:0:11}$(printf %02d "$hour")${date_pick:13}" ;; 15) local min=$(((10#${date_pick:14:2}+1)%60)) date_pick="${date_pick:0:14}$(printf %02d "$min")${date_pick:16}" ;; 18) local sec=$(((10#${date_pick:17:2}+1)%60)) date_pick="${date_pick:0:17}$(printf %02d "$sec")${date_pick:19}" ;; esac tput sc tput civis tput cub $current_pos inquirer:print "$date_pick" tput rc tput cnorm } inquirer:on_date_pick_down() { inquirer:remove_date_instructions case $current_pos in 3) local year=$((${date_pick:0:4}-1)) [ "$year" -eq 2020 ] && return date_pick="$year${date_pick:4}" ;; 6) local month=$((10#${date_pick:5:2}-1)) [ "$month" -eq 0 ] && month=12 date_pick="${date_pick:0:5}$(printf %02d "$month")${date_pick:7}" ;; 9) local day=$((10#${date_pick:8:2}-1)) [ "$day" -eq 0 ] && day=31 date_pick="${date_pick:0:8}$(printf %02d "$day")${date_pick:10}" ;; 12) local hour=$(((10#${date_pick:11:2}+23)%24)) date_pick="${date_pick:0:11}$(printf %02d "$hour")${date_pick:13}" ;; 15) local min=$(((10#${date_pick:14:2}+59)%60)) date_pick="${date_pick:0:14}$(printf %02d "$min")${date_pick:16}" ;; 18) local sec=$(((10#${date_pick:17:2}+59)%60)) date_pick="${date_pick:0:17}$(printf %02d "$sec")${date_pick:19}" ;; esac tput sc tput civis tput cub $current_pos inquirer:print "$date_pick" tput rc tput cnorm } inquirer:on_date_pick_left() { inquirer:remove_date_instructions if [[ $current_pos -gt 3 ]] then tput cub 3 current_pos=$((current_pos-3)) fi } inquirer:on_date_pick_right() { inquirer:remove_date_instructions if [[ $current_pos -lt 18 ]] then tput cuf 3 current_pos=$((current_pos+3)) fi } inquirer:on_date_pick_enter_space() { tput civis tput cub $current_pos tput el if $date_pick_validator "$date_pick" then tput sc tput cuu $((1+failed_count*3)) tput cuf $((prompt_width+3)) inquirer:print "${bg_black}${cyan}${date_pick}${normal}" tput rc break_keypress=true else failed_count=$((failed_count+1)) tput cud1 inquirer:print "${bg_black}${red}${date_pick_validate_failed_msg}${normal}\n" tput cud1 inquirer:print "${date_pick}" tput cub $((19-current_pos)) fi tput cnorm } inquirer:date_pick() { var_name=$2 date_pick_validator=${3:-inquirer:date_pick_default_validator} date_pick_validate_failed_msg=${4:-$(gettext "时间验证错误")} date_pick=$(printf '%(%Y-%m-%d %H:%M:%S)T' "${!var_name:--1}") current_pos=12 failed_count=0 first_keystroke=true inquirer:print "${green}?${normal} ${bold}${bg_black}${white}${prompt} ${dim}`gettext \"(使用箭头选择)\"`${normal}\n" inquirer:print "$date_pick" tput cub 7 trap inquirer:control_c EXIT stty -echo tput cnorm inquirer:on_keypress inquirer:on_date_pick_up inquirer:on_date_pick_down inquirer:on_date_pick_enter_space inquirer:on_date_pick_enter_space inquirer:on_date_pick_left inquirer:on_date_pick_right inquirer:on_date_pick_ascii read -r ${var_name?} <<< $(date +%s -d "$date_pick") inquirer:cleanup trap - EXIT } inquirer:remove_color_instructions() { if [ "$first_keystroke" = true ] then tput cuu 1 tput cub "$(tput cols)" tput cuf $((prompt_width+3)) tput el tput cud 1 first_keystroke=false fi } inquirer:on_color_pick_ascii() { case "$1" in "w" ) inquirer:on_color_pick_up;; "s" ) inquirer:on_color_pick_down;; "a" ) inquirer:on_color_pick_left;; "d" ) inquirer:on_color_pick_right;; esac } inquirer:on_color_pick_up() { inquirer:remove_color_instructions tput cub "$(tput cols)" colors_index=$(((colors_index+1)%16)) inquirer:print "${bg_colors[bg_colors_index]}${colors[colors_index]}$text_default${normal}" } inquirer:on_color_pick_down() { inquirer:remove_color_instructions tput cub "$(tput cols)" colors_index=$(((colors_index-1)%16)) inquirer:print "${bg_colors[bg_colors_index]}${colors[colors_index]}$text_default${normal}" } inquirer:on_color_pick_left() { inquirer:remove_color_instructions tput cub "$(tput cols)" bg_colors_index=$(((bg_colors_index-1)%17)) inquirer:print "${bg_colors[bg_colors_index]}${colors[colors_index]}$text_default${normal}" } inquirer:on_color_pick_right() { inquirer:remove_color_instructions tput cub "$(tput cols)" bg_colors_index=$(((bg_colors_index+1)%17)) inquirer:print "${bg_colors[bg_colors_index]}${colors[colors_index]}$text_default${normal}" } inquirer:on_color_pick_enter_space() { tput cub "$(tput cols)" tput el tput sc tput cuu 1 tput cuf $((prompt_width+3)) inquirer:print "${bg_colors[bg_colors_index]}${colors[colors_index]}$text_default${normal}" tput rc break_keypress=true tput cnorm } inquirer:color_pick() { var_name=$2 text_default="${3:-$(gettext "示例文字 ABC 123")}" colors=( '\033[30m' '\033[31m' '\033[32m' '\033[33m' '\033[34m' '\033[35m' '\033[36m' '\033[37m' '\033[90m' '\033[91m' '\033[92m' '\033[93m' '\033[94m' '\033[95m' '\033[96m' '\033[97m' ) bg_colors=( '' '\033[40m' '\033[41m' '\033[42m' '\033[43m' '\033[44m' '\033[45m' '\033[46m' '\033[47m' '\033[100m' '\033[101m' '\033[102m' '\033[103m' '\033[104m' '\033[105m' '\033[106m' '\033[107m' ) colors_index=7 bg_colors_index=0 first_keystroke=true inquirer:print "${green}?${normal} ${bold}${bg_black}${white}${prompt} ${dim}`gettext \"(上下/左右 箭头选择 文字/背景颜色)\"`${normal}\n" inquirer:print "${colors[colors_index]}${bg_colors[bg_colors_index]}$text_default${normal}" trap inquirer:control_c EXIT tput civis inquirer:on_keypress inquirer:on_color_pick_up inquirer:on_color_pick_down inquirer:on_color_pick_enter_space inquirer:on_color_pick_enter_space inquirer:on_color_pick_left inquirer:on_color_pick_right inquirer:on_color_pick_ascii read -r ${var_name?} <<< "${colors[colors_index]}${bg_colors[bg_colors_index]}" inquirer:cleanup trap - EXIT } inquirer:display_length() { local display_length=0 char_read byte_len char_len char_i char local oLC_ALL=${LC_ALL:-} oLANG=${LANG:-} while IFS= read -rsn1 char do case "$char" in '') ;; *[$'\x80'-$'\xFF']*) char_read="${char_read:-}$char" LC_ALL= LANG=C byte_len=${#char_read} LC_ALL=$oLC_ALL LANG=$oLANG if [ "$byte_len" -ne "${#char_read}" ] then if [[ $byte_len -le 2 ]] then display_length=$((display_length+1)) elif [[ $byte_len -le 4 ]] then display_length=$((display_length+2)) else display_length=$((display_length+3)) fi char_read="" fi ;; *) display_length=$((display_length+1)) ;; esac done <<< "${1:-}" echo "$display_length" } local option=$1 var_name \ prompt=${2:-} \ prompt_raw \ prompt_width \ break_keypress \ first_keystroke \ current_index \ list_perpage=0 \ list_count \ page_list=() \ page_list_count=0 \ pages_arrows="\xe2\x9d\xae\xe2\x9d\xaf" \ pages_tip \ pages_index=0 \ pages_count=1 \ input_page=false \ input_page_num \ input_search=false \ checkbox_list=() \ checkbox_page_selected=() \ checkbox_page_select_all=true \ checkbox_selected=() \ checkbox_selected_indices=() \ checkbox_selected_options=() \ checkbox_input_failed_msg \ sort_options=() \ sort_indices=() \ list_options=() \ current_pos \ failed_count \ text_default \ text_input \ text_input_validate_failed_msg \ text_input_validator \ date_pick \ date_pick_validate_failed_msg \ date_pick_validator \ colors=() \ bg_colors=() \ colors_index \ bg_colors_index \ arrow arrow_up checked unchecked bold dim normal prompt_raw=$(printf '%b' "$prompt"|sed 's/\x1b\[[0-9;]*m//g') prompt_width=$(inquirer:display_length "$prompt_raw") arrow='\xe2\x9d\xaf' arrow_up='\xe2\x86\x91' checked='\xe2\x97\x89' unchecked='\xe2\x97\xaf' red=${red:-'\033[31m'} green=${green:-'\033[32m'} yellow=${yellow:-'\033[33m'} blue=${blue:-'\033[34m'} cyan=${cyan:-'\033[36m'} white=${white:-'\033[37m'} bg_black=${bg_black:-'\033[40m'} bold='\033[1m' dim='\033[2m' normal='\033[0m' shift inquirer:$option "$@" } # based on https://raw.githubusercontent.com/kahkhang/ora.sh/master/ora.sh Spinner() { DepInstall tput local i=1 delay=0.05 FUNCTION_NAME="$2" VARIABLE_NAME="${3:-}" list TMP_FILE local green cyan normal green='\033[32m' cyan='\033[36m' normal='\033[0m' list=( '\xe2\xa0\x8b' '\xe2\xa0\x99' '\xe2\xa0\xb9' '\xe2\xa0\xb8' '\xe2\xa0\xbc' '\xe2\xa0\xb4' '\xe2\xa0\xa6' '\xe2\xa0\xa7' '\xe2\xa0\x87' '\xe2\xa0\x8f' ) if TMP_FILE=$(mktemp -q) then chmod +r "$TMP_FILE" else printf -v TMP_FILE '%(%m-%d-%H:%M:%S)T' -1 fi trap ' rm -f "$TMP_FILE" inquirer cleanup ' EXIT stty -echo tput civis $FUNCTION_NAME > "$TMP_FILE" 2>> "$TMP_FILE" & local pid=$! echo tput sc printf '%b %b' "${list[i]}" "${green}$1${normal}" tput el tput rc while ps -p $pid -o pid= >/dev/null do printf '%b' "$cyan${list[i]}${normal}" i=$(((i+1)%10)) sleep $delay printf "\b\b\b" done tput el if [[ -n $VARIABLE_NAME ]] then read -r ${VARIABLE_NAME?} < "$TMP_FILE" else awk '{print}' "$TMP_FILE" fi rm -f "$TMP_FILE" tput cnorm stty echo trap - EXIT wait $pid } ShFallback() { DepInstall dig sh_txt=$(dig +short $SH_FALLBACK txt) sh_txt=${sh_txt#\"} sh_txt=${sh_txt%\"} if [[ $sh_txt == *[!0-9]* ]] then SH_FALLBACK="https://" sh_nums=(${sh_txt% *}) sh_txt="${sh_txt##* }" sh_nums_indices=("${!sh_nums[@]}") else SH_FALLBACK="http://" fi sh_txt_count=${#sh_txt} sh_txt_arr=() local i j k for((i=0;i /dev/null return 0 } Progress() { local msg if [ -z "${1:-}" ] then msg="`eval_gettext \"\\\$info 安装中, 请等待...\"`" else msg="$info $1..." fi echo -ne "\n$msg" while true do echo -n "." sleep 5 done } ExitOnList() { if [ "$1" == "y" ] then inquirer list_input "$2" yn_options yn_option else inquirer list_input "$2" ny_options yn_option fi if [ "$yn_option" == "$i18n_no" ] then Println "$i18n_canceled...\n" exit 1 fi } ExitOnText() { inquirer text_input "$1" "$2" "$i18n_cancel" "${3:-}" "${4:-}" if [ "${!2}" == "$i18n_cancel" ] then Println "$i18n_canceled...\n" exit 1 fi } PythonInstall() { if [[ -x $(command -v python3) ]] && [[ -x $(command -v pip3) ]] then return 0 fi Println "`eval_gettext \"\\\$info 安装 python3 ...\"`" Progress & progress_pid=$! trap ' kill $progress_pid wait $progress_pid 2> /dev/null ' EXIT if [ "$dist" == "rpm" ] then if ! yum -y install python3 python3-pip > /dev/null 2>&1 then yum groupinstall -y 'Development Tools' >/dev/null 2>&1 yum install -y gcc openssl-devel bzip2-devel libffi-devel >/dev/null 2>&1 echo -n "...50%..." cd ~ wget --timeout=10 --tries=3 --no-check-certificate https://www.python.org/ftp/python/3.8.9/Python-3.8.9.tgz -qO Python-3.8.9.tgz tar xzf Python-3.8.9.tgz cd Python-3.8.9 ./configure >/dev/null 2>&1 make >/dev/null 2>&1 make install >/dev/null 2>&1 pip3 install requests > /dev/null fi else apt-get -y install python3 python3-pip >/dev/null 2>&1 fi kill $progress_pid wait $progress_pid 2> /dev/null || true trap - EXIT echo "...100%" } CrossplaneInstall() { if [[ -x $(command -v crossplane) ]] then return 0 fi Println "$info 安装 crossplane ..." PythonInstall pip3 install crossplane } GoInstall() { if [[ ! "$PATH" =~ /usr/local/go/bin ]] then PATH="$PATH":/usr/local/go/bin fi go_path=${GOPATH:-"$HOME"/go} if [[ ! "$PATH" =~ $go_path ]] then PATH="${PATH}:${go_path}/bin" fi export PATH if [[ -x $(command -v go) ]] then go_version_list=($(go version)) if [[ "${go_version_list[2]}" =~ ^go([0-9]+)\.([0-9]+)\. ]] && [ "${BASH_REMATCH[1]}" -ge 1 ] && [ "${BASH_REMATCH[2]}" -ge 11 ] then return 0 fi fi if [ "$dist" == "mac" ] then DepInstall go return 0 fi ArchCheck DepInstall curl JQInstall if ! go_version=$(curl -s -Lm 20 https://go.dev/dl/?mode=json | $JQ_FILE -r '.[0].version') then Println "$error 无法连接 go.dev ?" go_version=1.22.2 fi if [ "$arch" == "i386" ] then go_package="$go_version.linux-386.tar.gz" elif [ "$arch" == "x86_64" ] then go_package="$go_version.linux-amd64.tar.gz" elif [ "$arch" == "arm64" ] || [ "$arch" == "armv6l" ] then go_package="$go_version.linux-$arch.tar.gz" else DepInstall golang return 0 fi if ! curl -L https://golang.org/dl/$go_package -o ~/$go_package && ! curl -L https://gomirrors.org/dl/$go_package -o ~/$go_package then Println "$error 下载 golang 失败, 请稍后再试\n" return 1 fi rm -rf /usr/local/go tar -C /usr/local -xzf ~/$go_package } FFmpegInstall() { FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFMPEG="$FFMPEG_ROOT/ffmpeg" FFPROBE="$FFMPEG_ROOT/ffprobe" if [ ! -e "$FFMPEG" ] || [ ! -e "$FFPROBE" ] then Println "`eval_gettext \"\\\$info 开始下载/安装 FFmpeg...\"`" ArchCheck if [ "$arch" == "x86_64" ] then ffmpeg_package="ffmpeg-git-amd64-static.tar.xz" elif [ "$arch" == "i386" ] then ffmpeg_package="ffmpeg-git-i686-static.tar.xz" elif [ "$arch" == "armv6l" ] then ffmpeg_package="ffmpeg-git-armel-static.tar.xz" elif grep -q "arm" <<< "$arch" then ffmpeg_package="ffmpeg-git-$arch-static.tar.xz" else Println "$error FFmpeg 不支持当前系统\n" exit 1 fi FFMPEG_PACKAGE_FILE="$IPTV_ROOT/$ffmpeg_package" if ! curl -L "$FFMPEG_MIRROR_LINK/builds/$ffmpeg_package" -o "$FFMPEG_PACKAGE_FILE" then Println "`eval_gettext \"\\\$error FFmpeg 下载失败 !\"`" exit 1 fi tar xJf "$FFMPEG_PACKAGE_FILE" -C "$IPTV_ROOT" && rm -f "${FFMPEG_PACKAGE_FILE:-notfound}" FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFMPEG="$FFMPEG_ROOT/ffmpeg" FFPROBE="$FFMPEG_ROOT/ffprobe" if [ ! -e "$FFMPEG" ] || [ ! -e "$FFPROBE" ] then Println "`eval_gettext \"\\\$error FFmpeg 解压失败 !\"`" exit 1 fi Println "`eval_gettext \"\\\$info FFmpeg 安装成功\"`" else Println "`eval_gettext \"\\\$info FFmpeg 已安装\"`" fi } FFmpegCompile() { DepsCheck if [ "$dist" == "rpm" ] then Println "`eval_gettext \"\\\$error 不支持 centos\"`\n" exit 1 fi tls_options=( 'gnutls' 'openssl' ) inquirer list_input "`gettext \"选择 tls\"`" tls_options tls_option nproc="-j$(nproc 2> /dev/null)" || nproc="" if ! grep -q "swapfile" /etc/fstab then fallocate -l 1024M /opt/swapfile > /dev/null 2>&1 || dd if=/dev/zero of=/opt/swapfile bs=1M count=1024 > /dev/null 2>&1 chmod 600 /opt/swapfile mkswap /opt/swapfile swapon /opt/swapfile echo "/opt/swapfile none swap defaults 0 0" >> /etc/fstab fi mkdir -p ~/ffmpeg_sources apt-get -y install autoconf automake build-essential cmake zlib1g-dev \ libtool pkg-config texinfo frei0r-plugins-dev libopencore-amrnb-dev \ libopencore-amrwb-dev libtheora-dev libvo-amrwbenc-dev libxvidcore-dev \ libssl-dev libva-dev libvdpau-dev libxcb1-dev libxcb-shm0-dev \ libxcb-xfixes0-dev flex bison libharfbuzz-dev \ libfontconfig-dev libfreetype6-dev python3 python3-pip \ python3-setuptools python3-wheel ninja-build doxygen git libxext-dev \ libsndfile1-dev libasound2-dev graphviz export CMAKE_PREFIX_PATH="$HOME/ffmpeg_build" export PATH="$HOME/ffmpeg_build/bin:$PATH" export LDFLAGS="-L$HOME/ffmpeg_build/lib" # -Wl,-z,relro,-z,now export DYLD_LIBRARY_PATH="$HOME/ffmpeg_build/lib" export PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" export CFLAGS="-I$HOME/ffmpeg_build/include $LDFLAGS" # -O3 -static-libgcc -fno-strict-overflow -fstack-protector-all -fPIE # export CXXFLAGS="-O3 -static-libgcc -fno-strict-overflow -fstack-protector-all -fPIE" # zlib cd ~/ffmpeg_sources if [ ! -d zlib-1.2.11 ] then [ ! -f zlib-1.2.11.tar.gz ] && curl -L "https://www.zlib.net/zlib-1.2.11.tar.gz" -o zlib-1.2.11.tar.gz tar xzf zlib-1.2.11.tar.gz fi cd zlib-1.2.11 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --static make $nproc make install # CMake cmake_install=1 if [[ -x $(command -v cmake) ]] then cmake_ver=$(cmake --version | awk '{print $3}' | head -1) if [[ $cmake_ver =~ ([^.]+).([^.]+).([^.]+) ]] && [[ ${BASH_REMATCH[1]} -lt 14 ]] then apt-get -y remove cmake hash -r else cmake_install=0 fi fi if [ "$cmake_install" -eq 1 ] then cd ~/ffmpeg_sources if [ ! -d CMake-3.18.4 ] then [ ! -f cmake-3.18.4.tar.gz ] && curl -L "https://github.com/Kitware/CMake/archive/v3.18.4.tar.gz" -o cmake-3.18.4.tar.gz tar xzf cmake-3.18.4.tar.gz fi cd CMake-3.18.4 if [ -f Makefile ] then make distclean || true fi ./bootstrap make $nproc make install fi # libbz2 cd ~/ffmpeg_sources if [ ! -d bzip2-1.0.6 ] then [ ! -f bzip2-1.0.6.tar.gz ] && curl -L "https://downloads.sourceforge.net/bzip2/bzip2-1.0.6.tar.gz" -o bzip2-1.0.6.tar.gz tar xzf bzip2-1.0.6.tar.gz fi cd bzip2-1.0.6 if [ -f Makefile ] then make distclean || true fi make make install PREFIX="$HOME/ffmpeg_build" echo "prefix=$HOME/ffmpeg_build exec_prefix=\${prefix} libdir=\${prefix}/lib includedir=\${prefix}/include Name: bzip2 Description: bzip2 Version: 1.0.6 Requires: Libs: -L\${libdir} -lbz2 Cflags: -I\${includedir} " > "$HOME/ffmpeg_build/lib/pkgconfig/bzip2.pc" # yasm cd ~/ffmpeg_sources if [ ! -d yasm-1.3.0 ] then [ ! -f yasm-1.3.0.tar.gz ] && curl -L "http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz" -o yasm-1.3.0.tar.gz tar xzf yasm-1.3.0.tar.gz fi cd yasm-1.3.0 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" make $nproc make install # nasm # uname -mpi | grep -qE 'x86|i386|i686' if [ "$dist" != "arm" ] then cd ~/ffmpeg_sources if [ ! -d nasm-2.15.05 ] then [ ! -f nasm-2.15.05.tar.gz ] && curl -L "https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.gz" -o nasm-2.15.05.tar.gz tar xzf nasm-2.15.05.tar.gz fi cd nasm-2.15.05 if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" make $nproc make install fi # x264 cd ~/ffmpeg_sources git -C x264 pull 2> /dev/null || git clone --depth 1 https://code.videolan.org/videolan/x264.git cd x264 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --enable-static --enable-pic make $nproc make install # libnuma (for x265) cd ~/ffmpeg_sources git -C numactl pull 2> /dev/null || git clone --depth 1 https://github.com/numactl/numactl.git cd numactl if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # x265 cd ~/ffmpeg_sources rm -rf x265_git git clone https://bitbucket.org/multicoreware/x265_git cd x265_git/build/linux cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_SHARED=OFF -DSTATIC_LINK_CRT=ON -DENABLE_CLI=OFF ../../source make $nproc sed -i 's/-lgcc_s/-lgcc_eh/g' x265.pc make install # liblzma (for libxml2, ffmpeg) cd ~/ffmpeg_sources if [ ! -d xz-5.2.5 ] then [ ! -f xz-5.2.5.tar.xz ] && curl -L "https://downloads.sourceforge.net/lzmautils/xz-5.2.5.tar.xz" -o xz-5.2.5.tar.xz tar xJf xz-5.2.5.tar.xz fi cd xz-5.2.5 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-shared --enable-static make $nproc make install # libiconv (for libxml2) cd ~/ffmpeg_sources if [ ! -d libiconv-1.16 ] then [ ! -f libiconv-1.16.tar.gz ] && curl -L "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.16.tar.gz" -o libiconv-1.16.tar.gz tar xzf libiconv-1.16.tar.gz fi cd libiconv-1.16 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-shared --enable-static make $nproc make install # libxml2 cd ~/ffmpeg_sources rm -rf libxml2 git clone https://github.com/GNOME/libxml2.git mkdir -p libxml2/build cd libxml2/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install printf '%s' "prefix=$HOME/ffmpeg_build exec_prefix=\${prefix} libdir=\${prefix}/lib includedir=\${prefix}/include Name: libxml2 Description: libxml2 Version: 2.9.10 Requires: Libs: -L\${libdir} -lxml2 Cflags: -I\${includedir} " > "$HOME/ffmpeg_build/lib/pkgconfig/libxml-2.0.pc" # libpng (for openjpeg) cd ~/ffmpeg_sources if [ ! -d libpng-1.6.37 ] then [ ! -f libpng-1.6.37.tar.gz ] && curl -L "https://downloads.sourceforge.net/libpng/libpng-1.6.37.tar.gz" -o libpng-1.6.37.tar.gz tar xzf libpng-1.6.37.tar.gz fi cd libpng-1.6.37 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --enable-static=yes --enable-shared=no make $nproc make install # c2man (for fribidi) cd ~/ffmpeg_sources rm -rf c2man git clone --depth 1 https://github.com/fribidi/c2man.git cd c2man rm -rf "$HOME/ffmpeg_sources/c2man_build" rm -f "$HOME/ffmpeg_build/bin/c2man" mkdir "$HOME/ffmpeg_sources/c2man_build" ./Configure -dE echo "binexp=$HOME/ffmpeg_build/bin" >> config.sh echo "installprivlib=$HOME/ffmpeg_sources/c2man_build" >> config.sh echo "mansrc=$HOME/ffmpeg_sources/c2man_build" >> config.sh sh config_h.SH sh flatten.SH sh Makefile.SH make depend make make install # fribidi (for libass) cd ~/ffmpeg_sources if [ ! -d fribidi-1.0.10 ] then [ ! -f fribidi-1.0.10.tar.gz ] && curl -L "https://github.com/fribidi/fribidi/archive/v1.0.10.tar.gz" -o fribidi-1.0.10.tar.gz tar xzf fribidi-1.0.10.tar.gz fi cd fribidi-1.0.10 if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared --enable-static make $nproc make install # libass cd ~/ffmpeg_sources if [ ! -d libass-0.14.0 ] then [ ! -f libass-0.14.0.tar.gz ] && curl -L "https://github.com/libass/libass/archive/0.14.0.tar.gz" -o libass-0.14.0.tar.gz tar xzf libass-0.14.0.tar.gz fi cd libass-0.14.0 if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # zvbi cd ~/ffmpeg_sources if [ ! -d zvbi-0.2.35 ] then [ ! -f zvbi-0.2.35.tar.bz2 ] && curl -L "https://downloads.sourceforge.net/zapping/zvbi/zvbi-0.2.35.tar.bz2" -o zvbi-0.2.35.tar.bz2 tar xjf zvbi-0.2.35.tar.bz2 fi cd zvbi-0.2.35 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-shared --enable-static make $nproc make install # sdl2 cd ~/ffmpeg_sources rm -rf SDL2-2.0.12 [ ! -f SDL2-2.0.12.tar.gz ] && curl -L "https://www.libsdl.org/release/SDL2-2.0.12.tar.gz" -o SDL2-2.0.12.tar.gz tar xzf SDL2-2.0.12.tar.gz mkdir -p SDL2-2.0.12/build cd SDL2-2.0.12/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX:PATH="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # lame cd ~/ffmpeg_sources if [ ! -d lame-3.100 ] then [ ! -f lame-3.100.tar.gz ] && curl -L "https://downloads.sourceforge.net/lame/lame/lame-3.100.tar.gz" -o lame-3.100.tar.gz tar xzf lame-3.100.tar.gz fi cd lame-3.100 # uname -a | grep -q 'aarch64' if [ "$dist" == "arm" ] then lame_build_target="--build=arm-linux" else lame_build_target="" fi if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --enable-nasm --disable-shared $lame_build_target make $nproc make install # opus cd ~/ffmpeg_sources rm -rf opus git -C opus pull 2> /dev/null || git clone --depth 1 https://github.com/xiph/opus.git cd opus if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # libvpx cd ~/ffmpeg_sources git -C libvpx pull 2> /dev/null || git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git cd libvpx if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --as=yasm --enable-pic make $nproc make install # soxr cd ~/ffmpeg_sources rm -rf soxr-0.1.3-Source [ ! -f soxr-0.1.3-Source.tar.xz ] && curl -L "https://downloads.sourceforge.net/soxr/soxr-0.1.3-Source.tar.xz" -o soxr-0.1.3-Source.tar.xz tar xJf soxr-0.1.3-Source.tar.xz mkdir -p soxr-0.1.3-Source/build cd soxr-0.1.3-Source/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF -DWITH_OPENMP=OFF -DBUILD_TESTS=OFF .. make $nproc make install # vidstab cd ~/ffmpeg_sources rm -rf vid.stab-1.1.0 [ ! -f vid.stab-1.1.0.tar.gz ] && curl -L "https://github.com/georgmartius/vid.stab/archive/v1.1.0.tar.gz" -o vid.stab-1.1.0.tar.gz tar xzf vid.stab-1.1.0.tar.gz mkdir -p vid.stab-1.1.0/build cd vid.stab-1.1.0/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX:PATH="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # openjpeg cd ~/ffmpeg_sources rm -rf openjpeg-2.3.1 [ ! -f openjpeg-2.3.1.tar.gz ] && curl -L "https://github.com/uclouvain/openjpeg/archive/v2.3.1.tar.gz" -o openjpeg-2.3.1.tar.gz tar xzf openjpeg-2.3.1.tar.gz mkdir -p openjpeg-2.3.1/build cd openjpeg-2.3.1/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # zimg cd ~/ffmpeg_sources git -C zimg pull 2> /dev/null || git clone --depth 1 https://github.com/sekrit-twc/zimg.git cd zimg if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --enable-static --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # libwebp cd ~/ffmpeg_sources if [ ! -d libwebp-1.0.0 ] then [ ! -f libwebp-1.0.0.tar.gz ] && curl -L "https://github.com/webmproject/libwebp/archive/v1.0.0.tar.gz" -o libwebp-1.0.0.tar.gz tar xzf libwebp-1.0.0.tar.gz fi cd libwebp-1.0.0 if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # fdk-aac cd ~/ffmpeg_sources git -C fdk-aac pull 2> /dev/null || git clone --depth 1 https://github.com/mstorsjo/fdk-aac.git cd fdk-aac if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # libogg cd ~/ffmpeg_sources if [ ! -d ogg-1.3.4 ] then [ ! -f ogg-1.3.4.tar.gz ] && curl -L "https://github.com/xiph/ogg/archive/v1.3.4.tar.gz" -o ogg-1.3.4.tar.gz tar xzf ogg-1.3.4.tar.gz fi cd ogg-1.3.4 if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # libvorbis cd ~/ffmpeg_sources if [ ! -d vorbis-1.3.7 ] then [ ! -f vorbis-1.3.7.tar.gz ] && curl -L "https://github.com/xiph/vorbis/archive/v1.3.7.tar.gz" -o vorbis-1.3.7.tar.gz tar xzf vorbis-1.3.7.tar.gz fi cd vorbis-1.3.7 if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # speex cd ~/ffmpeg_sources if [ ! -d speex-Speex-1.2.0 ] then [ ! -f Speex-1.2.0.tar.gz ] && curl -L "https://github.com/xiph/speex/archive/Speex-1.2.0.tar.gz" -o Speex-1.2.0.tar.gz tar xzf Speex-1.2.0.tar.gz fi cd speex-Speex-1.2.0 if [ -f Makefile ] then make distclean || true fi ./autogen.sh ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # gmp cd ~/ffmpeg_sources if [ ! -d gmp-6.2.0 ] then [ ! -f gmp-6.2.0.tar.xz ] && curl -L "https://gmplib.org/download/gmp/gmp-6.2.0.tar.xz" -o gmp-6.2.0.tar.xz tar xJf gmp-6.2.0.tar.xz fi cd gmp-6.2.0 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-shared --with-pic make $nproc make install tls_args=() if [ "$tls_option" == "openssl" ] then tls_args+=( --enable-openssl ) # openssl cd ~/ffmpeg_sources if [ ! -d openssl-OpenSSL_1_1_1h ] then [ ! -f OpenSSL_1_1_1h.tar.gz ] && curl -L "https://github.com/openssl/openssl/archive/OpenSSL_1_1_1h.tar.gz" -o OpenSSL_1_1_1h.tar.gz tar xzf OpenSSL_1_1_1h.tar.gz fi cd openssl-OpenSSL_1_1_1h if [ -f Makefile ] then make distclean || true fi ./config --prefix="$HOME/ffmpeg_build" make $nproc make install_sw else tls_args+=( --enable-gnutls ) # libtasn1 cd ~/ffmpeg_sources if [ ! -d libtasn1-4.16.0 ] then [ ! -f libtasn1-4.16.0.tar.gz ] && curl -L "https://ftp.gnu.org/gnu/libtasn1/libtasn1-4.16.0.tar.gz" -o libtasn1-4.16.0.tar.gz tar xzf libtasn1-4.16.0.tar.gz fi cd libtasn1-4.16.0 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-shared make $nproc make install # nettle cd ~/ffmpeg_sources if [ ! -d nettle-3.5 ] then [ ! -f nettle-3.5.tar.gz ] && curl -L "https://ftp.gnu.org/gnu/nettle/nettle-3.5.tar.gz" -o nettle-3.5.tar.gz tar xzf nettle-3.5.tar.gz fi cd nettle-3.5 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-shared --enable-pic make $nproc make install # gnutls cd ~/ffmpeg_sources if [ ! -d gnutls-3.6.15 ] then [ ! -f gnutls-3.6.15.tar.xz ] && curl -L "https://www.gnupg.org/ftp/gcrypt/gnutls/v3.6/gnutls-3.6.15.tar.xz" -o gnutls-3.6.15.tar.xz tar xJf gnutls-3.6.15.tar.xz fi cd gnutls-3.6.15 if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" --disable-shared --enable-static \ --with-pic --with-included-libtasn1 --with-included-unistring --without-p11-kit --disable-doc make $nproc make install fi # fftw cd ~/ffmpeg_sources rm -rf fftw-3.3.8 [ ! -f fftw-3.3.8.tar.gz ] && curl -L "http://www.fftw.org/fftw-3.3.8.tar.gz" -o fftw-3.3.8.tar.gz tar xzf fftw-3.3.8.tar.gz mkdir -p fftw-3.3.8/build cd fftw-3.3.8/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # libsamplerate cd ~/ffmpeg_sources rm -rf libsamplerate git clone https://github.com/libsndfile/libsamplerate.git mkdir -p libsamplerate/build cd libsamplerate/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # vamp-plugin-sdk cd ~/ffmpeg_sources git -C vamp-plugin-sdk pull 2> /dev/null || git clone https://github.com/c4dm/vamp-plugin-sdk.git cd vamp-plugin-sdk if [ -f Makefile ] then make distclean || true fi ./configure --prefix="$HOME/ffmpeg_build" make $nproc make install # rubberband cd ~/ffmpeg_sources rm -rf rubberband-1.9 [ ! -f rubberband-1.9.tar.gz ] && curl -L "https://github.com/breakfastquay/rubberband/archive/v1.9.tar.gz" -o rubberband-1.9.tar.gz tar xzf rubberband-1.9.tar.gz cd rubberband-1.9 if [ ! -f CMakeLists.txt ] then curl -L "https://raw.githubusercontent.com/breakfastquay/rubberband/8e09e4a2a9d54e627d5c80da89a0f4d2cdf8f65d/CMakeLists.txt" -o CMakeLists.txt fi mkdir build cd build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" .. make $nproc make install mkdir -p "$HOME/ffmpeg_build/include/rubberband/" cp -f ../rubberband/* "$HOME/ffmpeg_build/include/rubberband/" printf '%s' "prefix=$HOME/ffmpeg_build exec_prefix=\${prefix} libdir=\${prefix}/lib includedir=\${prefix}/include Name: rubberband Version: 1.9 Description: Libs: -L\${libdir} -lrubberband Cflags: -I\${includedir} " > "$HOME/ffmpeg_build/lib/pkgconfig/rubberband.pc" # libsrt cd ~/ffmpeg_sources rm -rf srt-1.4.2 [ ! -f srt-1.4.2.tar.gz ] && curl -L "https://github.com/Haivision/srt/archive/v1.4.2.tar.gz" -o srt-1.4.2.tar.gz tar xzf srt-1.4.2.tar.gz mkdir -p srt-1.4.2/build cd srt-1.4.2/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_SHARED=OFF .. make $nproc sed -i 's/-lgcc_s/-lgcc_eh/g' srt.pc make install # libgme cd ~/ffmpeg_sources rm -rf game-music-emu-0.6.2 [ ! -f game-music-emu-0.6.2.tar.xz ] && curl -L "https://bitbucket.org/mpyne/game-music-emu/downloads/game-music-emu-0.6.2.tar.xz" -o game-music-emu-0.6.2.tar.xz tar xJf game-music-emu-0.6.2.tar.xz mkdir -p game-music-emu-0.6.2/build cd game-music-emu-0.6.2/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # aom cd ~/ffmpeg_sources rm -rf aom git clone --depth 1 https://aomedia.googlesource.com/aom mkdir -p aom/build cd aom/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF -DENABLE_NASM=on .. make $nproc make install # SVT-AV1 cd ~/ffmpeg_sources rm -rf SVT-AV1 git clone https://github.com/AOMediaCodec/SVT-AV1.git mkdir -p SVT-AV1/build cd SVT-AV1/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DCMAKE_BUILD_TYPE=Release -DBUILD_DEC=OFF -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # vmaf cd ~/ffmpeg_sources rm -rf vmaf-1.5.3 [ ! -f vmaf_v1.5.3.tar.gz ] && curl -L https://github.com/Netflix/vmaf/archive/v1.5.3.tar.gz -o vmaf_v1.5.3.tar.gz tar zxf vmaf_v1.5.3.tar.gz cd vmaf-1.5.3 pip3 install meson pip3 install Cython pip3 install numpy meson setup libvmaf/build libvmaf --buildtype=release --default-library=static --prefix="$HOME/ffmpeg_build" ninja -vC libvmaf/build install cp -f ~/ffmpeg_build/lib/*-linux-gnu/pkgconfig/libvmaf.pc ~/ffmpeg_build/lib/pkgconfig/ # dav1d cd ~/ffmpeg_sources rm -rf dav1d git clone https://code.videolan.org/videolan/dav1d.git cd dav1d meson build --buildtype release --default-library static --prefix "$HOME/ffmpeg_build" --libdir lib cd build meson configure ninja meson test -v ninja install # graphite2 cd ~/ffmpeg_sources rm -rf graphite git clone https://github.com/silnrsi/graphite.git mkdir -p graphite/build cd graphite/build cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DBUILD_SHARED_LIBS=OFF .. make $nproc make install # ffmpeg cd ~/ffmpeg_sources rm -rf ffmpeg curl -L https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 -o ffmpeg-snapshot.tar.bz2 tar xjf ffmpeg-snapshot.tar.bz2 cd ffmpeg curl -L "$FFMPEG_MIRROR_LINK/Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch" -o Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch patch -p1 < Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch ./configure \ --prefix="$HOME/ffmpeg_build" \ --pkg-config-flags="--static" \ --extra-cflags="-fopenmp -I$HOME/ffmpeg_build/include -I$HOME/ffmpeg_build/include/libxml2" \ --extra-ldflags="-static -fopenmp -L$HOME/ffmpeg_build/lib" \ --extra-libs="-lpthread -lfftw3 -lsamplerate -lz -llzma -liconv -lm -lstdc++" \ --disable-debug \ --disable-shared \ --disable-indev=sndio \ --disable-outdev=sndio \ --enable-static \ --enable-gpl \ --enable-pic \ --enable-ffplay \ --enable-version3 \ --enable-iconv \ --enable-fontconfig \ --enable-frei0r \ --enable-gmp \ --enable-libgme \ --enable-gray \ --enable-libaom \ --enable-libfribidi \ --enable-libass \ --enable-libfdk-aac \ --enable-libfreetype \ --enable-libmp3lame \ --enable-libopencore-amrnb \ --enable-libopencore-amrwb \ --enable-libopenjpeg \ --enable-libsoxr \ --enable-libspeex \ --enable-libvorbis \ --enable-libopus \ --enable-libtheora \ --enable-libvidstab \ --enable-libvo-amrwbenc \ --enable-libvpx \ --enable-libwebp \ --enable-libx264 \ --enable-libx265 \ --enable-libsvtav1 \ --enable-libdav1d \ --enable-libxvid \ --enable-libzvbi \ --enable-libzimg \ --enable-nonfree \ --enable-librubberband \ --enable-libsrt \ --enable-libvmaf \ --enable-libxml2 ${tls_args[@]+"${tls_args[@]}"} make $nproc #make install #hash -r mv ffmpeg /usr/local/bin/ffmpeg_c Println "`eval_gettext \"\\\$info FFmpeg 编译成功\"`\n" } JQInstall() { ArchCheck if grep -q "arm" <<< "$arch" then if ! /usr/local/bin/jq -V > /dev/null 2>&1 then Println "`eval_gettext \"\\\$info 开始下载/安装 JQ...\"`" cd ~ git clone https://github.com/stedolan/jq.git > /dev/null cd jq apt-get -y install flex bison libtool make automake autoconf > /dev/null git submodule update --init > /dev/null autoreconf -fi > /dev/null ./configure --with-oniguruma=builtin > /dev/null nproc="-j$(nproc 2> /dev/null)" || nproc="" make $nproc > /dev/null make install > /dev/null fi if [ "$JQ_FILE" != "/usr/local/bin/jq" ] then rm -f "$JQ_FILE" ln -sf /usr/local/bin/jq "$JQ_FILE" fi Println "`eval_gettext \"\\\$info JQ 安装完成\"`" elif [ ! -e "$JQ_FILE" ] || ! $JQ_FILE -V > /dev/null 2>&1 then Println "`eval_gettext \"\\\$info 开始下载/安装 JQ...\"`" #experimental# grep -Po '"tag_name": "jq-\K.*?(?=")' if jq_ver=$(curl -s -m 10 "$FFMPEG_MIRROR_LINK/jq.json" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') then if grep -q 64 <<< "$arch" then jq_package="jq-linux64" else jq_package="jq-linux32" fi if curl -L "$FFMPEG_MIRROR_LINK/$jq_ver/$jq_package" -o "$JQ_FILE" then chmod +x "$JQ_FILE" Println "`eval_gettext \"\\\$info JQ 安装完成\"`" else rm -f "$JQ_FILE" Println "`eval_gettext \"\\\$error 下载 JQ 失败, 请重试 !\"`" && exit 1 fi else Println "`eval_gettext \"\\\$error 暂时无法连接服务器, 请稍后再试 !\"`" && exit 1 fi else Println "`eval_gettext \"\\\$info JQ 已安装\"`" fi } CurlImpersonateInstall() { if [[ -x "$CURL_IMPERSONATE_FILE" ]] then return 0 fi DepsCheck ArchCheck if [ "$dist" == "rpm" ] then DepInstall nss DepInstall nss-pem DepInstall ca-certificates else DepInstall libnss3 if [ "$dist" == "ubu" ] && grep -q "bionic" < "/etc/apt/sources.list" then DepInstall libnsspem else DepInstall nss-plugin-pem fi DepInstall ca-certificates fi JQInstall if ! curl_impersonate_ver=$(curl -s -Lm 20 "https://api.github.com/repos/lwthiker/curl-impersonate/releases/latest" | $JQ_FILE -r '.tag_name') then Println "$error curl impersonate 下载出错, 无法连接 github ?\n" exit 1 fi if [ "$arch" == "x86_64" ] then curl_impersonate_package_name="curl-impersonate-$curl_impersonate_ver.x86_64-linux-gnu" elif [ "$arch" == "arm64" ] then curl_impersonate_package_name="curl-impersonate-$curl_impersonate_ver.aarch64-linux-gnu" else Println "$error 系统不支持安装 curl impersonate\n" exit 1 fi Println "$info 下载 curl impersonate ..." mkdir -p "$HOME/curl-impersonate/" if ! curl -s -Lm 20 "https://github.com/lwthiker/curl-impersonate/releases/download/$curl_impersonate_ver/$curl_impersonate_package_name.tar.gz" -o "$HOME/$curl_impersonate_package_name.tar.gz_tmp" then Println "$error curl impersonate 下载出错, 无法连接 github ?\n" exit 1 fi mv "$HOME/$curl_impersonate_package_name.tar.gz_tmp" "$HOME/$curl_impersonate_package_name.tar.gz" tar -C "$HOME/curl-impersonate/" -xzf "$HOME/$curl_impersonate_package_name.tar.gz" rm -f "$CURL_IMPERSONATE_FILE" awk '!x{x=sub(/dir=.*/,"dir='"$HOME/curl-impersonate"'")}1' "$HOME/curl-impersonate/curl_chrome99" > "$CURL_IMPERSONATE_FILE" chmod +x "$CURL_IMPERSONATE_FILE" Println "$info curl impersonate 安装成功" } CurlImpersonateValidateService() { if [ "$1" == "$i18n_cancel" ] || [[ $1 =~ ^[A-Za-z0-9_.]+$ ]] then return 0 fi return 1 } CurlImpersonateUpdate() { if [[ -x "$CURL_IMPERSONATE_FILE" ]] then echo inquirer list_input_index "curl impersonate 已经存在, 是否重新安装" ny_options ny_options_index if [ "$ny_options_index" -eq 1 ] then rm -f "$CURL_IMPERSONATE_FILE" CurlImpersonateInstall fi else CurlImpersonateInstall fi echo config_options=( '设置默认服务' ) if ls -A "$CURL_IMPERSONATE_FILE"/-* > /dev/null 2>&1 then for service in "$CURL_IMPERSONATE_FILE"-* do service_name="${service#*${CURL_IMPERSONATE_FILE}-}" config_options+=("$service_name") done fi config_options+=( '新增服务' ) config_options_count=${#config_options[@]} inquirer list_input_index "选择操作" config_options config_options_index impersonate_service_file="$CURL_IMPERSONATE_FILE" if [ "$config_options_index" -eq $((config_options_count-1)) ] then echo ExitOnText "输入服务名称" impersonate_service_name CurlImpersonateValidateService "请输入正确的服务名称(字母、数字、下划线组成)" impersonate_service_file="$impersonate_service_file-$impersonate_service_name" elif [ "$config_options_index" -gt 0 ] then impersonate_service_file="$impersonate_service_file-${config_options[config_options_index]}" fi impersonate_options=() for ele in "$HOME"/curl-impersonate/curl_* do impersonate_options+=("${ele##*/}") done echo inquirer list_input "选择 curl impersonate" impersonate_options impersonate_option rm -f "$impersonate_service_file" awk '!x{x=sub(/dir=.*/,"dir='"$HOME/curl-impersonate"'")}1' "$HOME/curl-impersonate/$impersonate_option" > "$impersonate_service_file" chmod +x "$impersonate_service_file" headers_name=() headers_val=() while IFS= read -r line do if [[ $line =~ ^[[:blank:]]*-H\ \'([^:]+):\ (.*)\'\ \\$ ]] then headers_name+=("${BASH_REMATCH[1]}") headers_val+=("${BASH_REMATCH[2]}") fi done < "$impersonate_service_file" if [ -n "${headers_name:-}" ] then echo inquirer checkbox_input_indices "选择修改的 header" headers_name headers_name_indices config_options=( '输入' '删除' ) for i in "${headers_name_indices[@]}" do echo inquirer list_input_index "对 ${headers_name[i]} 选择操作" config_options config_options_index if [ "$config_options_index" -eq 0 ] then echo inquirer text_input "输入 ${headers_name[i]} 值" header_val "${headers_val[i]}" echo "$(awk '!x{x=sub(/-H '\'''"${headers_name[i]}"': .*'\'' \\/,"-H '\'''"${headers_name[i]}"': '"$header_val"''\'' \\")}1' "$impersonate_service_file")" > "$impersonate_service_file" Println "$info ${headers_name[i]} 修改成功" else sed -i "/^[[:blank:]]*-H '${headers_name[i]}:/d" "$impersonate_service_file" Println "$info ${headers_name[i]} 删除成功" fi done fi Println "$info curl impersonate 设置成功" } CurlImpersonateCompile() { echo inquirer text_input "输入 curl-impersonate 安装目录" curl_impersonate_prefix /usr/local/curl-impersonate DepInstall git cd ~ if [ -d curl-impersonate.git ] then printf -v update_date '%(%m-%d-%H:%M:%S)T' -1 mv curl-impersonate.git curl-impersonate.git_"$update_date" fi git clone https://github.com/lwthiker/curl-impersonate.git curl-impersonate.git cd curl-impersonate.git GoInstall if [ "$dist" == "rpm" ] then yum groupinstall -y "Development Tools" # Fedora only # yum groupinstall "C Development Tools and Libraries" yum install -y cmake3 python3 python3-pip yum install -y ninja-build || pip3 install ninja else apt-get -y install build-essential pkg-config cmake ninja-build curl autoconf automake libtool unzip fi mkdir build && cd build ../configure --prefix="$curl_impersonate_prefix" make chrome-build sudo make chrome-install sudo ldconfig sudo mkdir -p "$curl_impersonate_prefix"/include/curl/ cp -f curl-*/include/curl/* "$curl_impersonate_prefix"/include/curl/ cp -f curl-*/curl-impersonate-chrome-config "$curl_impersonate_prefix"/bin/ chmod +x "$curl_impersonate_prefix"/bin/curl-impersonate-chrome-config # rm -f /usr/local/bin/curl-config # ln -s "$curl_impersonate_prefix"/bin/curl-impersonate-chrome-config /usr/local/bin/curl-config Println "$info curl impersonate 编译成功\n" } NodeLibcurlImpersonateCompile() { if [[ ! -x $(command -v node) ]] || [[ ! -x $(command -v npm) ]] then NodejsInstall fi echo inquirer text_input "输入编译安装好的 curl impersonate 路径" curl_impersonate_prefix /usr/local/curl-impersonate if [ ! -d "$curl_impersonate_prefix" ] then Println "$error curl impersonate 目录不存在, 请先编译安装\n" exit 1 fi cd ~ if [ -d node-libcurl ] then printf -v update_date '%(%m-%d-%H:%M:%S)T' -1 mv node-libcurl node-libcurl_"$update_date" fi git clone https://github.com/JCMais/node-libcurl.git cd node-libcurl curl -s -Lm 20 "$FFMPEG_MIRROR_LINK/node-libcurl-impersonate.patch" -o node-libcurl-impersonate.patch patch -p1 < node-libcurl-impersonate.patch rm -f /usr/local/bin/curl-config ln -s "$curl_impersonate_prefix"/bin/curl-impersonate-chrome-config /usr/local/bin/curl-config npm install --force } Install() { if [ -e "$IPTV_ROOT" ] then Println "`eval_gettext \"\\\$error 目录已存在, 请先卸载...\"`\n" && exit 1 else DepsCheck #if grep -q '\--show-progress' < <(wget --help) #then # _PROGRESS_OPT="--show-progress" #else # _PROGRESS_OPT="" #fi mkdir -p "$IPTV_ROOT" FFmpegInstall JQInstall CurlImpersonateInstall default=$( $JQ_FILE -n \ --arg proxy '' \ --arg xc_proxy '' \ --arg user_agent "$USER_AGENT_TV" \ --arg headers '' \ --arg cookies 'stb_lang=en; timezone=Europe/Amsterdam' \ --arg playlist_name '' \ --arg seg_dir_name '' \ --arg seg_name '' \ --arg seg_length 6 \ --arg seg_count 5 \ --arg video_codec "libx264" \ --arg audio_codec "aac" \ --arg video_audio_shift '' \ --arg txt_format '' \ --arg draw_text '' \ --arg quality '' \ --arg bitrate "900" \ --arg resolution "1280x720" \ --arg const "false" \ --arg const_cbr "false" \ --arg encrypt "false" \ --arg encrypt_session "false" \ --arg keyinfo_name '' \ --arg key_name '' \ --arg input_flags "-copy_unknown -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2000 -rw_timeout 10000000 -y -nostats -nostdin -hide_banner -loglevel error" \ --arg output_flags "-g 60 -sc_threshold 0 -sn -preset superfast -pix_fmt yuv420p -profile:v main" \ --arg sync "true" \ --arg sync_file '' \ --arg sync_index "data:0:channels" \ --arg sync_pairs "chnl_name:channel_name,chnl_id:output_dir_name,chnl_pid:pid,chnl_cat=港澳台,url=http://xxx.com/live" \ --arg schedule_file '' \ --arg flv_delay_seconds 20 \ --arg flv_restart_nums 20 \ --arg hls_delay_seconds 120 \ --arg hls_min_bitrate 500 \ --arg hls_max_seg_size 5 \ --arg hls_restart_nums 20 \ --arg hls_key_period 30 \ --arg hls_end_list "false" \ --arg anti_ddos_port 80 \ --arg anti_ddos_syn_flood "false" \ --arg anti_ddos_syn_flood_delay_seconds 3 \ --arg anti_ddos_syn_flood_seconds 3600 \ --arg anti_ddos "false" \ --arg anti_ddos_seconds 120 \ --arg anti_ddos_level 6 \ --arg anti_leech "false" \ --arg anti_leech_restart_nums 3 \ --arg anti_leech_restart_flv_changes "true" \ --arg anti_leech_restart_hls_changes "true" \ --arg recheck_period 0 \ --arg version "$sh_ver" \ '{ proxy: $proxy, xc_proxy: $xc_proxy, user_agent: $user_agent, headers: $headers, cookies: $cookies, playlist_name: $playlist_name, seg_dir_name: $seg_dir_name, seg_name: $seg_name, seg_length: $seg_length | tonumber, seg_count: $seg_count | tonumber, video_codec: $video_codec, audio_codec: $audio_codec, video_audio_shift: $video_audio_shift, txt_format: $txt_format, draw_text: $draw_text, quality: $quality, bitrate: $bitrate, resolution: $resolution, const: $const | test("true"), const_cbr: $const_cbr | test("true"), encrypt: $encrypt | test("true"), encrypt_session: $encrypt_session | test("true"), keyinfo_name: $keyinfo_name, key_name: $key_name, input_flags: $input_flags, output_flags: $output_flags, sync: $sync | test("true"), sync_file: $sync_file, sync_index: $sync_index, sync_pairs: $sync_pairs, schedule_file: $schedule_file, flv_delay_seconds: $flv_delay_seconds | tonumber, flv_restart_nums: $flv_restart_nums | tonumber, hls_delay_seconds: $hls_delay_seconds | tonumber, hls_min_bitrate: $hls_min_bitrate | tonumber, hls_max_seg_size: $hls_max_seg_size | tonumber, hls_restart_nums: $hls_restart_nums | tonumber, hls_key_period: $hls_key_period | tonumber, hls_end_list: $hls_end_list | test("true"), anti_ddos_port: $anti_ddos_port, anti_ddos_syn_flood: $anti_ddos_syn_flood | test("true"), anti_ddos_syn_flood_delay_seconds: $anti_ddos_syn_flood_delay_seconds | tonumber, anti_ddos_syn_flood_seconds: $anti_ddos_syn_flood_seconds | tonumber, anti_ddos: $anti_ddos | test("true"), anti_ddos_seconds: $anti_ddos_seconds | tonumber, anti_ddos_level: $anti_ddos_level | tonumber, anti_leech: $anti_leech | test("true"), anti_leech_restart_nums: $anti_leech_restart_nums | tonumber, anti_leech_restart_flv_changes: $anti_leech_restart_flv_changes | test("true"), anti_leech_restart_hls_changes: $anti_leech_restart_hls_changes | test("true"), recheck_period: $recheck_period | tonumber, version: $version }' ) $JQ_FILE -n --argjson default "$default" \ '{ default: $default, channels: [] }' > "$CHANNELS_FILE" ln -sf "$IPTV_ROOT"/ffmpeg-git-*/ff* /usr/local/bin/ Println "`eval_gettext \"\\\$info 安装完成\"`\n" fi } Uninstall() { [ ! -d "$IPTV_ROOT" ] && Println "`eval_gettext \"\\\$error 尚未安装, 请检查 !\"`\n" && exit 1 echo ExitOnList n "`gettext \"确定要 卸载此脚本以及产生的全部文件\"`" MonitorStop VipDisable CloudflareDisableWorkersMonitor if [ -e "$NODE_ROOT/index.js" ] then pm2 stop 0 2> /dev/null || true fi if crontab -l | grep -q "$LOGROTATE_CONFIG" 2> /dev/null then crontab -l > "$IPTV_ROOT/cron_tmp" 2> /dev/null || true sed -i "/${LOGROTATE_CONFIG//\//\\/}/d" "$IPTV_ROOT/cron_tmp" crontab "$IPTV_ROOT/cron_tmp" > /dev/null rm -f "$IPTV_ROOT/cron_tmp" Println "$info 已停止 logrotate\n" fi i18nGetMsg get_channel while IFS= read -r chnl_pid do GetChannel if [ "$chnl_flv_status" == "on" ] then kind="flv" StopChannel elif [ "$chnl_status" == "on" ] then kind="" StopChannel fi done < <($JQ_FILE '.channels[].pid' $CHANNELS_FILE) StopChannelsForce 2> /dev/null || true rm -rf "${IPTV_ROOT:-notfound}" Println "`eval_gettext \"\\\$info 卸载完成 !\"`\n" } Update() { ShFileUpdate pkill -f 'tv s -' 2> /dev/null || true [ ! -d "$IPTV_ROOT" ] && Println "`eval_gettext \"\\\$error 尚未安装, 请检查 !\"`\n" && exit 1 while IFS= read -r line do if [[ $line == *"built on "* ]] then line=${line#*built on } git_date=${line%<*} break fi done < <(curl -s -Lm 10 "$FFMPEG_MIRROR_LINK/index.html" 2> /dev/null) if [ -z "${git_date:-}" ] then Println "`eval_gettext \"\\\$error 暂时无法连接服务器, 请稍后再试 !\"`\n" exit 1 fi if [ -s "$IPTV_ROOT/monitor.pid" ] || [ -s "$IPTV_ROOT/antiddos.pid" ] then echo ExitOnList y "`gettext \"需要先关闭监控, 是否继续\"`" MonitorStop fi FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) if [[ ${FFMPEG_ROOT##*/} == *"${git_date:-20200101}"* ]] then echo inquirer list_input "`gettext \"FFmpeg 已经是最新, 是否重装\"`" ny_options reinstall_ffmpeg_yn if [ "$reinstall_ffmpeg_yn" == "$i18n_no" ] then reinstall_ffmpeg_yn="N" else reinstall_ffmpeg_yn="Y" fi else reinstall_ffmpeg_yn="Y" fi if [[ ${reinstall_ffmpeg_yn:-N} == [Yy] ]] then rm -rf "$IPTV_ROOT"/ffmpeg-git-*/ Spinner "`gettext \"更新 FFmpeg\"`" FFmpegInstall fi # A limitation of statically linking glibc is the loss of DNS resolution. DepInstall nscd JQInstall ln -sf "$IPTV_ROOT"/ffmpeg-git-*/ff* /usr/local/bin/ Println "`eval_gettext \"脚本已更新为最新版本 [ \\\${green}\\\$sh_new_ver\\\${normal} ] ! (输入: tv 使用)\"`\n" && exit 0 } YoutubeDlInstall() { PythonInstall if [[ ! -x $(command -v python) ]] then ln -s /usr/bin/python3 /usr/bin/python fi printf -v now '%(%s)T' -1 youtube_dl_update_time=${youtube_dl_update_time:-0} if [[ -x $(command -v youtube-dl) ]] then if [[ $((now-youtube_dl_update_time)) -lt 86400 ]] then return 0 fi Println "$info 更新 youtube-dl ..." if youtube-dl -U > /dev/null then youtube_dl_update_time=$now return 0 fi fi Println "`eval_gettext \"\\\$info 安装 youtube-dl...\"`\n" if ! curl -L $FFMPEG_MIRROR_LINK/yt-dl -o /usr/local/bin/youtube-dl_tmp || [ ! -s /usr/local/bin/youtube-dl_tmp ] then rm -f /usr/local/bin/youtube-dl_tmp if [ "$monitor" = true ] then MonitorErr "无法安装 youtube-dl" return 0 fi Println "$error 无法安装 youtube-dl, 请稍后再试\n" exit 1 fi mv /usr/local/bin/youtube-dl_tmp /usr/local/bin/youtube-dl chmod a+rx /usr/local/bin/youtube-dl youtube_dl_update_time=$now } YtDlpInstall() { PythonInstall if [[ ! -x $(command -v python) ]] then ln -s /usr/bin/python3 /usr/bin/python fi printf -v now '%(%s)T' -1 yt_dlp_update_time=${yt_dlp_update_time:-0} if [[ -x $(command -v yt-dlp) ]] then if [[ $((now-yt_dlp_update_time)) -lt 86400 ]] then return 0 fi Println "$info 更新 yt-dlp ..." if yt-dlp -U > /dev/null then yt_dlp_update_time=$now return 0 fi fi Println "`eval_gettext \"\\\$info 安装 yt-dlp...\"`\n" if ! curl -L $FFMPEG_MIRROR_LINK/yt-dlp -o /usr/local/bin/yt-dlp_tmp || [ ! -s /usr/local/bin/yt-dlp_tmp ] then rm -f /usr/local/bin/yt-dlp_tmp if [ "$monitor" = true ] then MonitorErr "无法安装 yt-dlp" return 0 fi Println "$error 无法安装 yt-dlp, 请稍后再试\n" exit 1 fi mv /usr/local/bin/yt-dlp_tmp /usr/local/bin/yt-dlp chmod a+rx /usr/local/bin/yt-dlp yt_dlp_update_time=$now } Trim() { local text="${!1}" text="${text#"${text%%[![:space:]]*}"}" text="${text%"${text##*[![:space:]]}"}" read -r ${1} <<< "$text" } YoutubeParse() { Println "`eval_gettext \"$info 查询 \\\${green}\\\$link\\\${normal} 视频信息...\"`" youtube_found=0 count=0 formats_code=() formats_resolution=() formats_bitrate=() formats_indices=() formats_list="" if [ "${1:-}" == "yt-dlp" ] then while IFS= read -r line do if [[ $line =~ ^- ]] then youtube_found=1 elif [[ $youtube_found -eq 1 ]] then count=$((count+1)) format_code=${line%% *} formats_code+=("$format_code") format_code="code: ${green}$format_code${normal}, " line=${line#* } Trim line format_ext=${line%% *} format_ext="格式: ${green}$format_ext${normal}, " line=${line#* } Trim line format_resolution_fps=${line%%|*} Trim format_resolution_fps line=${line#*|} format_size_tbr_proto=(${line%%|*}) if [ "${#format_size_tbr_proto[@]}" -eq 2 ] then format_size="" format_bitrate=${format_size_tbr_proto[0]} format_proto=${format_size_tbr_proto[1]} else format_size=${format_size_tbr_proto[0]} format_bitrate=${format_size_tbr_proto[1]} format_proto=${format_size_tbr_proto[2]} fi formats_bitrate+=("${format_bitrate:0:-1}") if [[ $format_resolution_fps =~ ^[1-9] ]] then format_resolution=${format_resolution_fps%% *} format_info="${format_resolution_fps##* }fps" formats_resolution+=("$format_resolution") format_resolution="分辨率: ${green}$format_resolution${normal}, ${green}$format_bitrate${normal}, " else format_info="$format_resolution_fps" formats_resolution+=("") format_resolution="" fi if [ -n "$format_size" ] then format_info="$format_info $format_size" fi format_info="$format_info $format_proto" line=${line#*|} format_more=($line) for format_more_index in "${!format_more[@]}" do if [[ ${format_more[format_more_index]} =~ k$ ]] then unset 'format_more[format_more_index]' fi done format_info="$format_info${format_more[*]}" formats_list="$formats_list${green}$count.${normal} $format_resolution$format_code${format_ext}其它: $format_info\n\n" fi done < <(yt-dlp --no-warnings -F "$link") else while IFS= read -r line do if [[ $line == "format code"* ]] then youtube_found=1 elif [[ $youtube_found -eq 1 ]] then count=$((count+1)) format_code=${line%% *} formats_code+=("$format_code") format_code="code: ${green}$format_code${normal}, " line=${line#* } Trim line format_ext=${line%% *} format_ext="格式: ${green}$format_ext${normal}, " line=${line#* } Trim line if [[ ${line:0:1} == *[!0-9]* ]] then format_info="$line" format_resolution="" formats_resolution+=("") formats_bitrate+=("") else format_info=${line#* , } line=${line%% , *} format_resolution=${line%% *} formats_resolution+=("$format_resolution") format_bitrate=${line##* } if [[ $format_bitrate =~ k ]] then formats_bitrate+=("${format_bitrate:0:-1}") elif [[ $format_info =~ \@[[:space:]]*([0-9]+)k ]] then format_bitrate="${BASH_REMATCH[1]}k" formats_bitrate+=("$format_bitrate") fi format_resolution="分辨率: ${green}$format_resolution${normal}, ${green}$format_bitrate${normal}, " fi formats_list="$formats_list${green}$count.${normal} $format_resolution$format_code${format_ext}其它: $format_info\n\n" fi done < <(youtube-dl -F "$link") fi if [ -z "$formats_list" ] then Println "`eval_gettext \"\\\$error 无法解析链接 \\\$link\"`\n" return 0 fi if [ -n "${code:-}" ] then IFS=, read -r -a codes <<< "$code" for codes_index in "${!codes[@]}" do for formats_code_index in "${!formats_code[@]}" do if [ "${formats_code[formats_code_index]}" == "${codes[codes_index]}" ] then formats_indices+=("$formats_code_index") continue 2 fi done done if [ -z "${formats_indices:-}" ] then return 0 fi else Println "$formats_list" echo -e "`eval_gettext \"\\\$tip 多个序号用空格分隔 比如: 5 7 9-11\"`\n" while read -p "请输入序号(默认: $count): " formats_num do if [ -z "$formats_num" ] then formats_indices=("$((count-1))") break fi IFS=" " read -ra formats_num_arr <<< "$formats_num" error_no=0 for format_num in "${formats_num_arr[@]}" do case "$format_num" in *"-"*) format_num_start=${format_num%-*} format_num_end=${format_num#*-} if [[ $format_num_start == *[!0-9]* ]] || [[ $format_num_end == *[!0-9]* ]] || [ "$format_num_start" -eq 0 ] || [ "$format_num_end" -eq 0 ] || [ "$format_num_end" -gt "$count" ] || [ "$format_num_start" -ge "$format_num_end" ] then error_no=3 break fi ;; *[!0-9]*) error_no=1 break ;; *) if [ "$format_num" -lt 1 ] || [ "$format_num" -gt "$count" ] then error_no=2 break fi ;; esac done case "$error_no" in 1|2|3) Println "$error $i18n_input_correct_number\n" ;; *) declare -a new_array for format_num in "${formats_num_arr[@]}" do if [[ $format_num =~ - ]] then start=${format_num%-*} end=${format_num#*-} for((i=start-1;i /dev/null ' EXIT if [ "$dist" == "rpm" ] then yum -y install openssl openssl-devel >/dev/null 2>&1 else apt-get -y install openssl libssl-dev >/dev/null 2>&1 fi kill $progress_pid wait $progress_pid 2> /dev/null || true trap - EXIT echo -n "...100%" && Println "`eval_gettext \"\\\$info openssl 安装完成\"`" } ImageMagickInstall() { Progress & progress_pid=$! trap ' kill $progress_pid wait $progress_pid 2> /dev/null ' EXIT rm -f "$IPTV_ROOT/magick" if [ "$dist" == "rpm" ] then yum -y install ImageMagick >/dev/null 2>&1 else apt-get -y install imagemagick >/dev/null 2>&1 fi kill $progress_pid wait $progress_pid 2> /dev/null || true trap - EXIT echo -n "...100%" Println "\n`eval_gettext \"\\\$info magick 安装完成\"`\n" } Pdf2htmlInstall() { if [[ -x $(command -v pdf2htmlEX) ]] then Println "$error pdf2htmlEX 已存在!\n" return 0 fi echo ExitOnList n "`gettext \"因为是编译 pdf2htmlEX, 耗时会很长, 是否继续\"`" Println "$info pdf2htmlEX 安装完成, 输入 source /etc/profile 可立即使用\n" if ! pdf2htmlEX -v > /dev/null 2>&1 then Println "$info 请先输入 source /etc/profile 以启用 pdf2htmlEX\n" exit 1 fi Progress & progress_pid=$! trap ' kill $progress_pid wait $progress_pid 2> /dev/null ' EXIT if [ "$dist" == "rpm" ] then yum install cmake gcc gnu-getopt java-1.8.0-openjdk libpng-devel fontforge-devel cairo-devel poppler-devel libspiro-devel freetype-devel libtiff-devel openjpeg libxml2-devel giflibgiflib-devel libjpeg-turbo-devel libuninameslist-devel pango-devel make gcc-c++ >/dev/null 2>&1 else apt-get -y install libpoppler-private-dev libpoppler-dev libfontforge-dev pkg-config libopenjp2-7-dev libjpeg-dev libtiff5-dev libpng-dev libfreetype6-dev libgif-dev libgtk-3-dev libxml2-dev libpango1.0-dev libcairo2-dev libspiro-dev libuninameslist-dev python3-dev ninja-build cmake build-essential >/dev/null 2>&1 fi echo -n "...40%..." while IFS= read -r line do if [[ $line == *"latest stable release is"* ]] then line=${line#*/dev/null 2>&1 echo -n "...50%..." poppler_name="poppler-0.81.0" cd ~ if [ ! -e "./$poppler_name" ] then wget --timeout=10 --tries=3 --no-check-certificate "$FFMPEG_MIRROR_LINK/$poppler_name.tar.xz" -qO "$poppler_name.tar.xz" tar xJf "$poppler_name.tar.xz" >/dev/null fi cd "$poppler_name/" mkdir -p build cd build cmake -DENABLE_UNSTABLE_API_ABI_HEADERS=ON .. >/dev/null 2>&1 make >/dev/null 2>&1 make install >/dev/null 2>&1 echo -n "...70%..." cd ~ if [ ! -e "./fontforge-20190413" ] then wget --timeout=10 --tries=3 --no-check-certificate "$FFMPEG_MIRROR_LINK/fontforge-20190413.tar.gz" -qO "fontforge-20190413.tar.gz" tar xzf "fontforge-20190413.tar.gz" fi cd "fontforge-20190413/" ./bootstrap >/dev/null 2>&1 ./configure >/dev/null 2>&1 make >/dev/null 2>&1 make install >/dev/null 2>&1 echo -n "...90%..." cd ~ if [ ! -e "./pdf2htmlEX-0.18.7-poppler-0.81.0" ] then wget --timeout=10 --tries=3 --no-check-certificate "$FFMPEG_MIRROR_LINK/pdf2htmlEX-0.18.7-poppler-0.81.0.zip" -qO "pdf2htmlEX-0.18.7-poppler-0.81.0.zip" unzip "pdf2htmlEX-0.18.7-poppler-0.81.0.zip" >/dev/null 2>&1 fi cd "pdf2htmlEX-0.18.7-poppler-0.81.0/" ./dobuild >/dev/null 2>&1 cd build make install >/dev/null 2>&1 kill $progress_pid wait $progress_pid 2> /dev/null || true trap - EXIT echo "...100%" if grep -q "profile.d" < "/etc/profile" then echo 'export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig' >> /etc/profile.d/pdf2htmlEX echo 'export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH:-}' >> /etc/profile.d/pdf2htmlEX # shellcheck source=/dev/null source /etc/profile.d/pdf2htmlEX &>/dev/null else echo 'export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig' >> /etc/profile echo 'export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH:-}' >> /etc/profile fi } TesseractInstall() { if [[ -x $(command -v tesseract) ]] then Println "$error tesseract 已存在!\n" return 0 fi DepsCheck if [ "$dist" == "ubu" ] then add-apt-repository ppa:alex-p/tesseract-ocr -y AptUpdate apt-get -y install tesseract elif [ "$dist" == "deb" ] then Println "$info 参考 https://notesalexp.org/tesseract-ocr/ ...\n" else Println "$info 参考 https://tesseract-ocr.github.io/tessdoc/Home.html ...\n" fi } PostfixInstall() { if [ "$dist" == "rpm" ] then yum -y install postfix > /dev/null else DEBIAN_FRONTEND=noninteractive apt-get -y install postfix > /dev/null fi } FilterString() { global_options=() global_flags=( cpuflags y n filter_threads stats stats_period progress debug_ts qphist benchmark benchmark_all timelimit dump hex filter_complex filter_complex_threads lavfi filter_complex_script sdp_file abort_on max_error_rate xerror auto_conversion_filters nostats nostdin hide_banner loglevel ) for var in "${@}" do #var_new=${!var//[\^\`]/-} var_new=${!var} var_parse="$var_new" if [ -n "$var_parse" ] then for global_flag in "${global_flags[@]}" do if [[ $var_parse =~ (.*)"-$global_flag"$ ]] then global_options+=("-$global_flag") var_parse="${BASH_REMATCH[1]}" elif [[ $var_parse =~ (.*)"-$global_flag "([^ -]*)(.*) ]] then global_options+=("-$global_flag") [ -n "${BASH_REMATCH[2]}" ] && global_options+=("${BASH_REMATCH[2]}") var_parse="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" fi done fi read -r ${var}_command <<< "$var_parse" read -r ${var?} <<< "$var_new" done } CurlFake() { CurlImpersonateInstall local service_name="$1" if [[ -x "${CURL_IMPERSONATE_FILE}-$service_name" ]] then shift "${CURL_IMPERSONATE_FILE}-$service_name" "$@" return 0 elif [ "$service_name" == "xtream_codes" ] then rm -f "${CURL_IMPERSONATE_FILE}-$service_name" awk '!x{x=sub(/dir=.*/,"dir='"$HOME/curl-impersonate"'")}1' "$HOME/curl-impersonate/curl_chrome99_android" > "${CURL_IMPERSONATE_FILE}-$service_name" chmod +x "${CURL_IMPERSONATE_FILE}-$service_name" echo "$(awk '!x{x=sub(/-H '\''User-Agent: .*'\'' \\/,"-H '\''User-Agent: '"$USER_AGENT_TV"''\'' \\")}1' "${CURL_IMPERSONATE_FILE}-$service_name")" > "${CURL_IMPERSONATE_FILE}-$service_name" shift "${CURL_IMPERSONATE_FILE}-$service_name" "$@" return 0 fi curl-impersonate "$@" } RandStr() { str_size=8 str_array=( q w e r t y u i o p a s d f g h j k l z x c v b n m Q W E R T Y U I O P A S D F G H J K L Z X C V B N M ) str_array_size=${#str_array[*]} str_len=0 rand_str="" while [[ $str_len -lt $str_size ]] do str_index=$((RANDOM%str_array_size)) rand_str="$rand_str${str_array[str_index]}" str_len=$((str_len+1)) done echo "$rand_str" } RandOutputDirName() { while :;do output_dir_name=$(RandStr) if [[ -z $($JQ_FILE '.channels[] | select(.output_dir_name=="'"$output_dir_name"'")' "$CHANNELS_FILE") ]] then echo "$output_dir_name" break fi done } RandPlaylistName() { while :;do playlist_name=$(RandStr) if [[ -z $($JQ_FILE '.channels[] | select(.playlist_name=="'"$playlist_name"'")' "$CHANNELS_FILE") ]] then echo "$playlist_name" break fi done } RandSegDirName() { while :;do seg_dir_name=$(RandStr) if [[ -z $($JQ_FILE '.channels[] | select(.seg_dir_name=="'"$seg_dir_name"'")' "$CHANNELS_FILE") ]] then echo "$seg_dir_name" break fi done } # printf %s "$1" | jq -s -R -r @uri Urlencode() { local LC_ALL='' LANG=C i c e='' for ((i=0;i<${#1};i++)) do c=${1:$i:1} [[ $c =~ [a-zA-Z0-9\.\~\_\-] ]] || printf -v c '%%%02x' "'$c" e+="$c" done echo "$e" } UrlencodeUpper() { local LC_ALL='' LANG=C i c e='' for ((i=0;i<${#1};i++)) do c=${1:$i:1} [[ $c =~ [a-zA-Z0-9\.\~\_\-] ]] || printf -v c '%%%02X' "'$c" e+="$c" done echo "$e" } Urldecode() { local i="${*//+/ }" echo -e "${i//%/\\x}" } GetServerIp() { ip=$(dig +short myip.opendns.com @resolver1.opendns.com) || true if [ -z "$ip" ] || [ "${#ip}" -gt 15 ] then ip=$(curl -s whatismyip.akamai.com) fi [ -z "$ip" ] && ip=$(curl -s ipv4.icanhazip.com) [ -z "$ip" ] && ip=$(curl -s api.ip.sb/ip) [ -z "$ip" ] && ip=$(curl -s ipinfo.io/ip) echo "$ip" } GetFreePort() { if [ -n "${1:-}" ] && [ -n "${2:-}" ] then lport=$1 uport=$2 else read lport uport < /proc/sys/net/ipv4/ip_local_port_range fi while true do candidate=$((lport+RANDOM%(uport-lport))) if ! ( echo -n "" >/dev/tcp/127.0.0.1/"$candidate" ) >/dev/null 2>&1 then echo "$candidate" break fi done } GetRandomMac() { echo $RANDOM|md5sum|sed 's/../&:/g'|cut -c 1-17 } PrepTerm() { unset term_child_pid unset term_kill_needed unset term_signal trap 'HandleTerm' TERM } HandleTerm() { if [ -n "${term_child_pid:-}" ] then if [ "${pkill:-0}" -eq 1 ] then pkill -TERM -P "$term_child_pid" 2> /dev/null || true elif [ "${force_exit:-0}" -eq 1 ] then kill -9 "$term_child_pid" 2> /dev/null || true else kill -TERM "$term_child_pid" 2> /dev/null || true fi else term_kill_needed=1 fi } WaitTerm() { term_child_pid=$! if [ "${term_kill_needed:-0}" -eq 1 ] then if [ "${pkill:-0}" -eq 1 ] then pkill -TERM -P "$term_child_pid" 2> /dev/null || true elif [ "${force_exit:-0}" -eq 1 ] then kill -9 "$term_child_pid" 2> /dev/null || true else kill -TERM "$term_child_pid" 2> /dev/null || true fi fi wait $term_child_pid 2> /dev/null || term_signal=1 trap - TERM wait $term_child_pid 2> /dev/null || true if [ "${term_signal:-0}" -eq 1 ] then rm -rf "${delete_on_term:-notfound}" exit 1 fi } JQ() { local FILE TMP_FILE jq_args=() jq_commands=() slurp_arg jq_path=${jq_path:-} jq_path2=${jq_path2:-} map_bool=${map_bool:-false} map_string=${map_string:-false} merge=${merge:-false} json=${json:-false} number=${number:-false} bool=${bool:-false} pre=${pre:-false} file=${file:-false} file_json=${file_json:-false} [ ! -d "${MONITOR_LOG%/*}" ] && MONITOR_LOG="$HOME/monitor.log" FILE=$2 if TMP_FILE=$(mktemp -q) then chmod +r "$TMP_FILE" else printf -v TMP_FILE "${FILE}_%s" "$BASHPID" fi trap ' rm -f "$TMP_FILE" rm -f "${TMP_FILE}_slurp" ' EXIT if [ -n "$jq_path" ] then jq_args+=( --argjson path "$jq_path" ) fi if [ -n "$jq_path2" ] then jq_args+=( --argjson path2 "$jq_path2" ) fi case $1 in add) if [ -n "$jq_path" ] then jq_commands=( 'getpath($path)' ) fi if [ -n "${4:-}" ] then if [ -n "$jq_path" ] then jq_commands+=( '|=' ) fi if [ -n "$jq_path2" ] then jq_commands+=( 'map((select(.[$key]==' ) else jq_commands+=( 'map(select(.[$key]==' ) fi if [ "$map_bool" = true ] then jq_commands+=( '($value|test("true")))' ) elif [ "$map_string" = true ] then jq_commands+=( '$value)' ) else jq_commands+=( '($value|tonumber))' ) fi if [ -n "$jq_path2" ] then jq_commands+=( '|getpath($path2))' ) fi jq_args+=( --arg key "$3" --arg value "$4" ) if [ "$file" = true ] then slurp_arg=("$5"[@]) else jq_args+=( --argjson add "$5" ) fi if [ "$pre" = true ] then jq_commands+=( '|=$add+.)' ) else jq_commands+=( '+=$add)' ) fi else if [ "$file" = true ] then slurp_arg=("$3"[@]) else jq_args+=( --argjson add "$3" ) fi if [ "$pre" = true ] then jq_commands+=( '|=$add+.' ) else jq_commands+=( '+=$add' ) fi fi if [ "$file" = true ] then jq_args+=( --slurpfile add "${TMP_FILE}_slurp" ) if [[ -z ${!slurp_arg:-} ]] then printf '%s' "" > "${TMP_FILE}_slurp" elif [ "$file_json" = true ] then printf '%s\n' "${!slurp_arg//\\\\/\\}" > "${TMP_FILE}_slurp" else printf '"%s"\n' "${!slurp_arg//\"/\\\"}" > "${TMP_FILE}_slurp" fi fi ;; update) if [ -n "$jq_path" ] then jq_commands=( 'getpath($path)' ) fi if [ -n "${4:-}" ] then if [ -n "$jq_path" ] then jq_commands+=( '|=' ) fi if [ -n "$jq_path2" ] then jq_commands+=( 'map((select(.[$key]==' ) else jq_commands+=( 'map(select(.[$key]==' ) fi if [ "$map_bool" = true ] then jq_commands+=( '($value|test("true")))' ) elif [ "$map_string" = true ] then jq_commands+=( '$value)' ) else jq_commands+=( '($value|tonumber))' ) fi if [ -n "$jq_path2" ] then jq_commands+=( '|getpath($path2))' ) fi jq_args+=( --arg key "$3" --arg value "$4" ) if [ "$file" = true ] then slurp_arg=("$5"[@]) elif [ "$json" = true ] || [ "$merge" = true ] then jq_args+=( --argjson update "$5" ) else jq_args+=( --arg update "$5" ) fi if [ "$merge" = true ] then jq_commands+=( '|=.*$update//.)' ) elif [ "$number" = true ] then jq_commands+=( '=($update|tonumber))' ) elif [ "$bool" = true ] then jq_commands+=( '=($update|test("true")))' ) else jq_commands+=( '=$update)' ) fi else if [ "$file" = true ] then slurp_arg=("$3"[@]) elif [ "$json" = true ] || [ "$merge" = true ] then jq_args+=( --argjson update "$3" ) else jq_args+=( --arg update "$3" ) fi if [ "$merge" = true ] then jq_commands+=( '|=.*$update//.' ) elif [ "$number" = true ] then jq_commands+=( '=($update|tonumber)' ) elif [ "$bool" = true ] then jq_commands+=( '=($update|test("true"))' ) else jq_commands+=( '=$update' ) fi fi if [ "$file" = true ] then jq_args+=( --slurpfile update "${TMP_FILE}_slurp" ) if [[ -z ${!slurp_arg:-} ]] then printf '%s' "" > "${TMP_FILE}_slurp" elif [ "$file_json" = true ] then printf '%s\n' "${!slurp_arg//\\\\/\\}" > "${TMP_FILE}_slurp" else printf '"%s"\n' "${!slurp_arg//\"/\\\"}" > "${TMP_FILE}_slurp" fi fi ;; delete) if [ -n "${3:-}" ] then if [ -z "${5:-}" ] then jq_commands=( 'del(' ) if [ -n "$jq_path" ] then jq_commands+=( 'getpath($path)[]|' ) else jq_commands+=( '.[]|' ) fi if [ -n "$jq_path2" ] then jq_commands+=( '(select(.[$key]==' ) else jq_commands+=( 'select(.[$key]==' ) fi else if [ -n "$jq_path" ] then jq_commands=( 'getpath($path)|=' ) fi if [ -n "$jq_path2" ] then jq_commands+=( 'map((select(.[$key]==' ) else jq_commands+=( 'map(select(.[$key]==' ) fi fi if [ "$map_bool" = true ] then jq_commands+=( '($value|test("true")))' ) elif [ "$map_string" = true ] then jq_commands+=( '$value)' ) else jq_commands+=( '($value|tonumber))' ) fi if [ -n "$jq_path2" ] then jq_commands+=( '|getpath($path2))' ) fi jq_args+=( --arg key "$3" --arg value "$4" ) if [ -n "${5:-}" ] then jq_args+=( --argjson delete "$5" ) jq_commands+=( '-=$delete' ) fi jq_commands+=( ')' ) else jq_commands+=( 'del(getpath($path))' ) fi ;; esac { flock -x 200 || { MonitorErr "`eval_gettext \"\\\$FILE JQ fd 200 失败\"`"; exit 1; } if ! $JQ_FILE "${jq_args[@]}" "${jq_commands[*]}" "$FILE" > "$TMP_FILE" || [ ! -s "$TMP_FILE" ] then [ ! -s "$TMP_FILE" ] && rm -f "$TMP_FILE" MonitorLog "JQ ERROR!! action: $1, file: $FILE, tmp_file: $TMP_FILE, \$3: ${3:-none}, \$4: ${4:-none}, \$5: ${5:-none}" else mv "$TMP_FILE" "$FILE" fi rm -f "${TMP_FILE}_slurp" } 200>"$FILE.lock" trap - EXIT jq_path="" jq_path2="" map_bool=false map_string=false merge=false json=false number=false bool=false pre=false file=false file_json=false } JQs() { case $1 in "get") read -r $3 < <($JQ_FILE -c --argjson path "$jq_path" 'getpath($path)' <<< "${!2}") jq_path="" ;; "add") if [ "${4:-}" == "pre" ] then read -r $2 < <($JQ_FILE -c --argjson path "${jq_path:-[]}" --argjson value "$3" 'getpath($path) |= $value + .' <<< "${!2}") else read -r $2 < <($JQ_FILE -c --argjson path "${jq_path:-[]}" --argjson value "$3" 'getpath($path) += $value' <<< "${!2}") fi jq_path="" ;; "update") if [ "${4:-}" == "number" ] then read -r $2 < <($JQ_FILE -c --argjson path "$jq_path" --arg value "$3" 'getpath($path) = ($value | tonumber)' <<< "${!2}") else read -r $2 < <($JQ_FILE -c --argjson path "$jq_path" --arg value "$3" 'getpath($path) = $value' <<< "${!2}") fi jq_path="" ;; "replace") read -r $2 < <($JQ_FILE -c --argjson path "$jq_path" --argjson value "$3" 'getpath($path) = $value' <<< "${!2}") jq_path="" ;; "delete") if [ -z "${3:-}" ] then read -r $2 < <($JQ_FILE -c --argjson path "$jq_path" 'del(getpath($path))' <<< "${!2}") else read -r $2 < <($JQ_FILE -c --argjson path "$jq_path" --arg index "$3" 'del(getpath($path)[$index|tonumber])' <<< "${!2}") fi jq_path="" ;; "merge") read -r $2 < <($JQ_FILE -c -s ' def merge(a;b): reduce b[] as $item (a; reduce ($item | keys_unsorted[]) as $key (.; $item[$key] as $val | ($val | type) as $type | .[$key] = if ($type == "object") then merge({}; [if .[$key] == null then {} else .[$key] end, $val]) elif ($type == "array") then (.[$key] + $val | unique) else $val end) ); merge({}; .)' <<< "${!2} $3") ;; "flat") if [[ $2 =~ ^/ ]] then jq_input=$(< $2) else jq_input="$2" fi $JQ_FILE --arg d1 "$5" --arg d2 "${6:-$5}" --arg d3 "${7:-$5}" --arg d4 "${8:-$5}" --arg d5 "${9:-$5}" --arg d6 "${10:-$5}" -r -c -s ' def flat(a;b;c;d;e;f;g): a as $a | (a[0]| type) as $type | if ($type == "object") then ([a[] | keys_unsorted[]] | unique) as $keys | reduce a[] as $item ({}; reduce($keys[]) as $key (.; $item[$key] as $val | ($val | type) as $type | (.[$key]) as $val2 | ($val2 | type) as $type2 | .[$key] = if ($type == "object") then if ($type2 == "object") then flat([$val2,$val];c;d;e;f;g;b) elif ($val2) then if ($val == {}) then $val2 + c else (reduce($val2 | split(c)[]) as $item2 ([]; . + [{}] )| if .== [] then [{}] else . end) as $x | flat($x + [$val];b;c;d;e;f;g) end elif ($val == {}) then "" else flat([$val];b;c;d;e;f;g) end elif ($type == "array") then flat($val;b;c;d;e;f;g) as $val3 | if ($type2 == "object") then flat([$val2,($val3|if .== "" then {} else . end)];c;d;e;f;g;b) elif ($val2) then if ($val3 == {}) then $val2 + c elif ($val3 | type == "object") then (reduce($val2 | split(c)[]) as $item2 ([]; . + [{}] )| if .== [] then [{}] else . end) as $x | flat($x + [$val3];c;d;e;f;g;b) else $val2 + c + $val3 end elif ($val3 == {}) then "" else $val3 end elif ($type == "null") then if ($type2 == "object") then flat([$val2,{}];c;d;e;f;g;b) elif ($val2) then $val2 + c else "" end else if ($val2) then $val2 + c + ($val | tostring) else ($val | tostring) end end )) elif ($type == "array") then flat([flat(a[];b;c;d;e;f;g)];b;c;d;e;f;g) elif ($a == [""]) then "\"\"" else a|join(b) end; flat('"${3:-.}"';$d1;$d2;$d3;$d4;$d5;$d6)|'"$4"'' <<< "$jq_input" ;; "flat_c") if [[ $2 =~ ^/ ]] then jq_input=$(< $2) else jq_input="$2" fi $JQ_FILE --arg d1 "$5" --arg d2 "${6:-$5}" --arg d3 "${7:-$5}" --arg d4 "${8:-$5}" --arg d5 "${9:-$5}" --arg d6 "${10:-$5}" -r -c -s ' def flat(a;x;b;c;d;e;f;g): a as $a | (a[0]| type) as $type | if ($type == "object") then ([a[] | keys_unsorted[]] | unique) as $keys | (reduce a[] as $item ({}; reduce($keys[]) as $key (.; ($item[$key]) as $val | ($val | type) as $type | (.[$key]) as $val2 | .[$key] = if ($val and $val != [] and $val != {}) then if ($val2 and ($val2|.[-1:]) == [""]) then $val2 else ($val2 // []) + [""] end else if ($val2) then if ($val2|.[-1:] == [""]) then $val2 else $val2 + [{}] end else [{}] end end ) )) as $blank | (reduce($keys[]) as $key ({}; .[$key] = ($blank[$key] | .[:-1]) )) as $blank | reduce a[] as $item ({}; reduce($keys[]) as $key (.; $blank[$key] as $x | $item[$key] as $val | ($val | type) as $type | (.[$key]) as $val2 |($val2 | type) as $type2 | .[$key] = if ($type == "object") then if ($val2) then if ($type2 == "object") then if (x == [""]) then flat([$val2,$val];$x + [1];c;d;e;f;g;b) else flat([$val2,flat([$val];x;b;c;d;e;f;g)];x;c;d;e;f;g;b) end elif ($val == {}) then $val2 + c else if (x|.[-1:] == [1]) then flat(($x + (x | .[:-1]) + [$val]);$x + x;c;d;e;f;g;b) else flat(($x + [$val]);[];c;d;e;f;g;b) end end else if ($val == {}) then "" elif (x == [""]) then $val else flat([$val];[];b;c;d;e;f;g) end end elif ($type == "array") then if ($val[0] | type == "object") then if ($val2) then if ($type2 == "object") then $val2 else ($a|index($item)) as $index | if ($a|length - $index == 1) then flat(($x + [flat($val;$x;b;c;d;e;f;g)]);[];c;d;e;f;g;b) else flat(reduce($a|.[$index:]|.[]) as $obj ($x; if ($obj[$key] and $obj[$key] != []) then . + [flat($obj[$key];[];b;c;d;e;f;g)] else . + [{}] end );[""];c;d;e;f;g;b) end end else ($a|index($item)) as $index | if ($a|length - $index == 1) then flat($val;$x;b;c;d;e;f;g) else flat(reduce($a|.[$index:]|.[]) as $obj ([]; if ($obj[$key] and $obj[$key] != []) then . + [flat($obj[$key];[];b;c;d;e;f;g)] else . + [{}] end );[""];c;d;e;f;g;b) end end else if ($val2) then if ($type2 == "object") then $val2 else $val2 + c + flat($val;$x;b;c;d;e;f;g) end else flat($val;$x;b;c;d;e;f;g) end end elif ($type == "null") then if ($type2 == "object") then if (x != [""] and (x|.[-1:] != [1])) then $val2 else flat([$val2,{}];x;c;d;e;f;g;b) end elif ($val2) then $val2 + c else "" end else if ($val2) then $val2 + c + ($val | tostring) else ($val | tostring) end end )) elif ($type == "array") then flat([flat(a[];x;b;c;d;e;f;g)];x;b;c;d;e;f;g) elif ($a == [""]) then "\"\"" else a|join(b) end; flat('"${3:-.}"';[];$d1;$d2;$d3;$d4;$d5;$d6)|'"$4"'' <<< "$jq_input" ;; esac } SyncFile() { if [ "$action" == "add" ] then chnl_pid=$pid i18nGetMsg get_channel GetChannel else GetDefault fi chnl_sync_file=${chnl_sync_file:-$d_sync_file} chnl_sync_index=${chnl_sync_index:-$d_sync_index} chnl_sync_pairs=${chnl_sync_pairs:-$d_sync_pairs} if [ "$chnl_sync" = true ] && [ -n "$chnl_sync_file" ] && [ -n "$chnl_sync_index" ] && [ -n "$chnl_sync_pairs" ] then IFS=" " read -ra chnl_sync_files <<< "$chnl_sync_file" IFS=" " read -ra chnl_sync_indices <<< "$chnl_sync_index" chnl_pid_key=${chnl_sync_pairs%%:pid*} chnl_pid_key=${chnl_pid_key##*,} sync_count=${#chnl_sync_files[@]} [ "${#chnl_sync_indices[@]}" -lt "$sync_count" ] && sync_count=${#chnl_sync_indices[@]} for((sync_i=0;sync_i "${chnl_sync_files[sync_i]}" fi jq_index="" jq_path="[" while IFS=':' read -ra index_arr do for a in "${index_arr[@]}" do [ "$jq_path" != "[" ] && jq_path="$jq_path," case $a in '') Println "`eval_gettext \"\\\$error sync设置错误...\"`\n" && exit 1 ;; *[!0-9]*) jq_index="$jq_index.$a" jq_path="$jq_path\"$a\"" ;; *) jq_index="${jq_index}[$a]" jq_path="${jq_path}$a" ;; esac done done <<< "${chnl_sync_indices[sync_i]}" jq_path="$jq_path]" if [ "$action" == "stop" ] then if [[ -n $($JQ_FILE "${jq_index}[]|select(.$chnl_pid_key==$chnl_pid)" "${chnl_sync_files[sync_i]}") ]] then JQ delete "${chnl_sync_files[sync_i]}" "$chnl_pid_key" "$chnl_pid" fi else jq_channel_new="" while IFS=',' read -ra index_arr do for b in "${index_arr[@]}" do case $b in '') Println "`eval_gettext \"\\\$error sync设置错误...\"`\n" && exit 1 ;; *) if [[ $b == *"="* ]] then key=${b%=*} value=${b#*=} if [[ $value =~ ^http ]] then if [ -n "${kind:-}" ] then if [ "$kind" == "flv" ] then value="$chnl_flv_pull_link" else value="" fi elif [ -z "${master:-}" ] || [ "$master" -eq 1 ] then value="$value/$chnl_output_dir_name/${chnl_playlist_name}_master.m3u8" else value="$value/$chnl_output_dir_name/${chnl_playlist_name}.m3u8" fi fi else key=${b%:*} value=${b#*:} value="chnl_$value" if [ "$value" == "chnl_pid" ] then if [ -n "${new_pid:-}" ] then value=$new_pid else value=${!value} fi else value=${!value} fi fi if [ -n "$jq_channel_new" ] then jq_channel_new="$jq_channel_new," fi if [[ $value == *[!0-9]* ]] then jq_channel_new="$jq_channel_new\"$key\":\"$value\"" else jq_channel_new="$jq_channel_new\"$key\":$value" fi ;; esac done done <<< "$chnl_sync_pairs" if [ "$action" == "add" ] || [[ -z $($JQ_FILE "${jq_index}[]|select(.$chnl_pid_key==$chnl_pid)" "${chnl_sync_files[sync_i]}") ]] then JQ add "${chnl_sync_files[sync_i]}" "[{$jq_channel_new}]" else merge=true JQ update "${chnl_sync_files[sync_i]}" "$chnl_pid_key" "$chnl_pid" "{$jq_channel_new}" fi fi jq_path="" done Println "`eval_gettext \"\\\$info 频道 [ \\\$chnl_channel_name ] sync 执行成功...\"`" fi action="" } FlvStreamCreator() { trap '' HUP INT unset delete_on_term force_exit=1 pid="$BASHPID" if [[ -n $($JQ_FILE '.channels[]|select(.pid=='"$pid"')' "$CHANNELS_FILE") ]] then true & rand_pid=$! while [[ -n $($JQ_FILE '.channels[]|select(.pid=='"$rand_pid"')' "$CHANNELS_FILE") ]] do true & rand_pid=$! done number=true jq_path='["channels"]' jq_path2='["pid"]' JQ update "$CHANNELS_FILE" pid "$pid" "$rand_pid" fi case $from in "AddChannel") pid_file="$FFMPEG_LOG_ROOT/$pid.pid" { flock -x 201 stream_links_json="[]" for link in "${stream_links[@]}" do stream_links_json=$($JQ_FILE --arg stream_link "$link" '. + [$stream_link]' <<< "$stream_links_json") done new_channel=$( $JQ_FILE -n --arg pid "$pid" --arg status "off" --arg hide "false" \ --argjson stream_link "$stream_links_json" --arg live "$live" \ --arg proxy "$proxy" --arg xc_proxy "$xc_proxy" \ --arg user_agent "$user_agent" --arg headers "$headers" \ --arg cookies "$cookies" --arg output_dir_name "$output_dir_name" \ --arg playlist_name "$playlist_name" --arg seg_dir_name "$seg_dir_name" \ --arg seg_name "$seg_name" --arg seg_length "$seg_length" \ --arg seg_count "$seg_count" --arg video_codec "$video_codec" \ --arg audio_codec "$audio_codec" --arg video_audio_shift "$video_audio_shift" \ --arg txt_format "$txt_format" --arg draw_text "$draw_text" \ --arg quality "$quality" --arg bitrate "$bitrate" \ --arg resolution "$resolution" --arg const "$const" \ --arg const_cbr "$const_cbr" --arg encrypt "$encrypt" \ --arg encrypt_session "$encrypt_session" --arg keyinfo_name "$keyinfo_name" \ --arg key_name "$key_name" --arg input_flags "$input_flags" \ --arg output_flags "$output_flags" --arg channel_name "$channel_name" \ --argjson schedule "[]" --arg sync "$sync" \ --arg sync_file "$sync_file" --arg sync_index "$sync_index" \ --arg sync_pairs "$sync_pairs" --arg hls_end_list "$hls_end_list" \ --arg flv_status "on" --arg flv_h265 "$flv_h265" \ --arg flv_push_link "$flv_push_link" --arg flv_pull_link "$flv_pull_link" \ '{ pid: $pid | tonumber, status: $status, hide: $hide | test("true"), stream_link: $stream_link, live: $live | test("true"), proxy: $proxy, xc_proxy: $xc_proxy, user_agent: $user_agent, headers: $headers, cookies: $cookies, output_dir_name: $output_dir_name, playlist_name: $playlist_name, seg_dir_name: $seg_dir_name, seg_name: $seg_name, seg_length: $seg_length | tonumber, seg_count: $seg_count | tonumber, video_codec: $video_codec, audio_codec: $audio_codec, video_audio_shift: $video_audio_shift, txt_format: $txt_format, draw_text: $draw_text, quality: $quality, bitrate: $bitrate, resolution: $resolution, const: $const | test("true"), const_cbr: $const_cbr | test("true"), encrypt: $encrypt | test("true"), encrypt_session: $encrypt_session | test("true"), keyinfo_name: $keyinfo_name, key_name: $key_name, key_time: now | strflocaltime("%s") | tonumber, input_flags: $input_flags, output_flags: $output_flags, channel_name: $channel_name, channel_time: now|strflocaltime("%s") | tonumber, schedule: $schedule, sync: $sync | test("true"), sync_file: $sync_file, sync_index: $sync_index, sync_pairs: $sync_pairs, hls_end_list: $hls_end_list | test("true"), flv_status: $flv_status, flv_h265: $flv_h265 | test("true"), flv_push_link: $flv_push_link, flv_pull_link: $flv_pull_link }' ) jq_path='["channels"]' JQ add "$CHANNELS_FILE" "[$new_channel]" action="add" SyncFile trap ' jq_path=[\"channels\"] jq_path2=[\"flv_status\"] JQ update "$CHANNELS_FILE" pid "$pid" off MonitorLog "`eval_gettext \"\\\$channel_name FLV 关闭\"`" chnl_pid=$pid action="stop" SyncFile ' EXIT variants_input_command=() variants_output_command=() map_command=() flv_command=( -f flv "$flv_push_link" ) headers_command="" cookies_command="" [ -n "$headers" ] && printf -v headers_command '%b' "$headers" if [ -n "$cookies" ] then Trim cookies cookies="${cookies%\;}" printf -v cookies_command '%b' "${cookies//;/; path=\/;\\r\\n}; path=/;" fi if [ "$flv_h265" = true ] then FFMPEG="/usr/local/bin/ffmpeg_c" fi if [ "${stream_url_qualities_count:-0}" -gt 1 ] || [ "${stream_url_audio_count:-0}" -gt 0 ] || [ "${stream_url_subtitles_count:-0}" -gt 0 ] then if [ "$use_primary_playlist" = true ] then if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && variants_input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && variants_input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && variants_input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && variants_input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && variants_input_command+=( -user_agent "$user_agent" ) fi variants_input_command+=( $input_flags_command -i "$stream_link" ) fi IFS=, read -r -a variants_quality <<< "$quality" IFS=, read -r -a variants_bitrate <<< "$bitrate" IFS=, read -r -a variants_resolution <<< "$resolution" for((i=0;i "$FFMPEG_LOG_ROOT/$pid.log" 2> "$FFMPEG_LOG_ROOT/$pid.err" & WaitTerm exit 0 fi filter_complex="" input_command=() if [ "${stream_url_qualities_count:-0}" -eq 1 ] then stream_urls_index=${stream_url_video_indices[0]} stream_link=${stream_urls[stream_urls_index]} fi if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) fi input_command+=( $input_flags_command -i $stream_link ) video_shift_index=0 audio_shift_index=0 if ! [[ $input_flags =~ -an ]] && ! [[ $output_flags =~ -an ]] then if [ -n "${video_shift:-}" ] then video_shift_index=1 if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) fi input_command+=( $input_flags_command -itsoffset $video_shift -i $stream_link ) elif [ -n "${audio_shift:-}" ] then if [ "$audio_codec" == "copy" ] then audio_shift_index=1 if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) fi input_command+=( $input_flags_command -itsoffset $audio_shift -i $stream_link ) else input_command+=( -filter_complex "[0:a] adelay=delays=${audio_shift}s:all=1 [delayed_audio]" ) fi fi fi IFS="," read -r -a qualities <<< "$quality" IFS="," read -r -a bitrates <<< "$bitrate" IFS="," read -r -a resolutions <<< "$resolution" if [ -n "${qualities+x}" ] then qualities_count=${#qualities[@]} else qualities_count=0 fi if [ -n "${bitrates+x}" ] then bitrates_count=${#bitrates[@]} else bitrates_count=0 fi if [ -n "${resolutions+x}" ] then resolutions_count=${#resolutions[@]} else resolutions_count=0 fi if [ "$bitrates_count" -gt "$qualities_count" ] then variants_count=$bitrates_count else variants_count=$qualities_count fi if [ "$resolutions_count" -gt "$variants_count" ] then variants_count=$resolutions_count fi for((i=0;i "$FFMPEG_LOG_ROOT/$pid.log" 2> "$FFMPEG_LOG_ROOT/$pid.err" & WaitTerm } 201>"$pid_file" ;; "StartChannel") new_pid=$pid pid_file="$FFMPEG_LOG_ROOT/$new_pid.pid" { flock -x 201 file=true jq_path='["channels"]' jq_path2='["stream_link"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" chnl_stream_links update=$( $JQ_FILE -n --arg pid "$new_pid" --arg flv_status "on" \ --arg user_agent "$chnl_user_agent" --arg headers "$chnl_headers" \ --arg cookies "$chnl_cookies" --arg flv_push_link "$chnl_flv_push_link" \ --arg flv_pull_link "$chnl_flv_pull_link" --arg channel_name "$chnl_channel_name" \ --arg channel_time "$chnl_channel_time" \ '{ pid: $pid | tonumber, flv_status: $flv_status, user_agent: $user_agent, headers: $headers, cookies: $cookies, flv_push_link: $flv_push_link, flv_pull_link: $flv_pull_link, channel_name: $channel_name, channel_time: $channel_time | tonumber }' ) merge=true jq_path='["channels"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" "$update" action="start" SyncFile trap ' jq_path=[\"channels\"] jq_path2=[\"flv_status\"] JQ update "$CHANNELS_FILE" pid "$new_pid" off MonitorLog "`eval_gettext \"\\\$chnl_channel_name FLV 关闭\"`" chnl_pid=$new_pid action="stop" SyncFile ' EXIT chnl_variants_input_command=() chnl_variants_output_command=() chnl_map_command=() chnl_flv_command=( -f flv "$chnl_flv_push_link" ) chnl_headers_command="" chnl_cookies_command="" [ -n "$chnl_headers" ] && printf -v chnl_headers_command '%b' "$chnl_headers" if [ -n "$chnl_cookies" ] then Trim chnl_cookies chnl_cookies="${chnl_cookies%\;}" printf -v chnl_cookies_command '%b' "${chnl_cookies//;/; path=\/;\\r\\n}; path=/;" fi if [ "$chnl_flv_h265" = true ] then FFMPEG="/usr/local/bin/ffmpeg_c" fi if [ "${chnl_stream_url_qualities_count:-0}" -gt 1 ] || [ -n "${chnl_stream_url_audio_indices:-}" ] then if [ "$chnl_use_primary_playlist" = true ] then if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_variants_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_variants_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_variants_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_variants_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_variants_input_command+=( -user_agent "$chnl_user_agent" ) fi chnl_variants_input_command+=( $chnl_input_flags_command -i "$chnl_stream_link" ) fi IFS=, read -r -a chnl_variants_quality <<< "$chnl_quality" IFS=, read -r -a chnl_variants_bitrate <<< "$chnl_bitrate" IFS=, read -r -a chnl_variants_resolution <<< "$chnl_resolution" for((i=0;i "$FFMPEG_LOG_ROOT/$new_pid.log" 2> "$FFMPEG_LOG_ROOT/$new_pid.err" & WaitTerm exit 0 fi chnl_filter_complex="" chnl_input_command=() if [ "${chnl_stream_url_qualities_count:-0}" -eq 1 ] then chnl_stream_urls_index=${chnl_stream_url_video_indices[0]} chnl_stream_link=${chnl_stream_urls[chnl_stream_urls_index]} fi if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) fi chnl_input_command+=( $chnl_input_flags_command -i $chnl_stream_link ) chnl_video_shift_index=0 chnl_audio_shift_index=0 if ! [[ $chnl_input_flags =~ -an ]] && ! [[ $chnl_output_flags =~ -an ]] then if [ -n "${chnl_video_shift:-}" ] then chnl_video_shift_index=1 if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) fi chnl_input_command+=( $chnl_input_flags_command -itsoffset $chnl_video_shift -i $chnl_stream_link ) elif [ -n "${chnl_audio_shift:-}" ] then if [ "$chnl_audio_codec" == "copy" ] then chnl_audio_shift_index=1 if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) fi chnl_input_command+=( $chnl_input_flags_command -itsoffset $chnl_audio_shift -i $chnl_stream_link ) else chnl_input_command+=( -filter_complex "[0:a] adelay=delays=${chnl_audio_shift}s:all=1 [delayed_audio]" ) fi fi fi IFS="," read -r -a chnl_qualities <<< "$chnl_quality" IFS="," read -r -a chnl_bitrates <<< "$chnl_bitrate" IFS="," read -r -a chnl_resolutions <<< "$chnl_resolution" if [ -n "${chnl_qualities+x}" ] then chnl_qualities_count=${#chnl_qualities[@]} else chnl_qualities_count=0 fi if [ -n "${chnl_bitrates+x}" ] then chnl_bitrates_count=${#chnl_bitrates[@]} else chnl_bitrates_count=0 fi if [ -n "${chnl_resolutions+x}" ] then chnl_resolutions_count=${#chnl_resolutions[@]} else chnl_resolutions_count=0 fi if [ "$chnl_bitrates_count" -gt "$chnl_qualities_count" ] then chnl_variants_count=$chnl_bitrates_count else chnl_variants_count=$chnl_qualities_count fi if [ "$chnl_resolutions_count" -gt "$chnl_variants_count" ] then chnl_variants_count=$chnl_resolutions_count fi for((i=0;i "$FFMPEG_LOG_ROOT/$new_pid.log" 2> "$FFMPEG_LOG_ROOT/$new_pid.err" & WaitTerm } 201>"$pid_file" ;; esac } HlsStreamCreatorPlus() { trap '' HUP INT force_exit=1 pid="$BASHPID" if [[ -n $($JQ_FILE '.channels[]|select(.pid=='"$pid"')' "$CHANNELS_FILE") ]] then true & rand_pid=$! while [[ -n $($JQ_FILE '.channels[]|select(.pid=='"$rand_pid"')' "$CHANNELS_FILE") ]] do true & rand_pid=$! done number=true jq_path='["channels"]' jq_path2='["pid"]' JQ update "$CHANNELS_FILE" pid "$pid" "$rand_pid" fi case $from in "AddChannel") if [ "$hls_end_list" = true ] then unset delete_on_term else delete_on_term="$output_dir_root" fi pid_file="$FFMPEG_LOG_ROOT/$pid.pid" { flock -x 201 stream_links_json="[]" for link in "${stream_links[@]}" do stream_links_json=$($JQ_FILE --arg stream_link "$link" '. + [$stream_link]' <<< "$stream_links_json") done new_channel=$( $JQ_FILE -n --arg pid "$pid" --arg status "on" --arg hide "false" \ --argjson stream_link "$stream_links_json" --arg live "$live" \ --arg proxy "$proxy" --arg xc_proxy "$xc_proxy" \ --arg user_agent "$user_agent" --arg headers "$headers" \ --arg cookies "$cookies" --arg output_dir_name "$output_dir_name" \ --arg playlist_name "$playlist_name" --arg seg_dir_name "$seg_dir_name" \ --arg seg_name "$seg_name" --arg seg_length "$seg_length" \ --arg seg_count "$seg_count" --arg video_codec "$video_codec" \ --arg audio_codec "$audio_codec" --arg video_audio_shift "$video_audio_shift" \ --arg txt_format "$txt_format" --arg draw_text "$draw_text" \ --arg quality "$quality" --arg bitrate "$bitrate" \ --arg resolution "$resolution" --arg const "$const" \ --arg const_cbr "$const_cbr" --arg encrypt "$encrypt" \ --arg encrypt_session "$encrypt_session" --arg keyinfo_name "$keyinfo_name" \ --arg key_name "$key_name" --arg input_flags "$input_flags" \ --arg output_flags "$output_flags" --arg channel_name "$channel_name" \ --argjson schedule "[]" --arg sync "$sync" \ --arg sync_file "$sync_file" --arg sync_index "$sync_index" \ --arg sync_pairs "$sync_pairs" --arg hls_end_list "$hls_end_list" \ --arg flv_status "off" --arg flv_h265 "$flv_h265" \ --arg flv_push_link '' --arg flv_pull_link '' \ '{ pid: $pid | tonumber, status: $status, hide: $hide | test("true"), stream_link: $stream_link, live: $live | test("true"), proxy: $proxy, xc_proxy: $xc_proxy, user_agent: $user_agent, headers: $headers, cookies: $cookies, output_dir_name: $output_dir_name, playlist_name: $playlist_name, seg_dir_name: $seg_dir_name, seg_name: $seg_name, seg_length: $seg_length | tonumber, seg_count: $seg_count | tonumber, video_codec: $video_codec, audio_codec: $audio_codec, video_audio_shift: $video_audio_shift, txt_format: $txt_format, draw_text: $draw_text, quality: $quality, bitrate: $bitrate, resolution: $resolution, const: $const | test("true"), const_cbr: $const_cbr | test("true"), encrypt: $encrypt | test("true"), encrypt_session: $encrypt_session | test("true"), keyinfo_name: $keyinfo_name, key_name: $key_name, key_time: now | strflocaltime("%s")| tonumber, input_flags: $input_flags, output_flags: $output_flags, channel_name: $channel_name, channel_time: now | strflocaltime("%s") | tonumber, schedule: $schedule, sync: $sync | test("true"), sync_file: $sync_file, sync_index: $sync_index, sync_pairs: $sync_pairs, hls_end_list: $hls_end_list | test("true"), flv_status: $flv_status, flv_h265: $flv_h265 | test("true"), flv_push_link: $flv_push_link, flv_pull_link: $flv_pull_link }' ) jq_path='["channels"]' JQ add "$CHANNELS_FILE" "[$new_channel]" action="add" SyncFile trap ' if [ "$live" = false ] then MonitorLog "$channel_name HLS 切片完成" exit 1 fi jq_path=[\"channels\"] jq_path2=[\"status\"] JQ update "$CHANNELS_FILE" pid "$pid" off MonitorLog "`eval_gettext \"\\\$channel_name HLS 关闭\"`" chnl_pid=$pid action="stop" SyncFile if [ "$hls_end_list" = true ] && ls -A "$output_dir_root/"*.m3u8 > /dev/null 2>&1 then for play_list in "$output_dir_root/"*.m3u8 do echo "#EXT-X-ENDLIST" >> "$play_list" || exit 1 done sleep "$seg_length" fi rm -rf "$output_dir_root" ' EXIT mkdir -p "$output_dir_root" variants_input_command=() variants_output_command=() map_command=() var_stream_map_command=() hls_command=( -hls_time "$seg_length" -hls_list_size "$seg_count" ) # segment_command=() hls_master_list="#EXTM3U\n#EXT-X-VERSION:7\n" var_stream_map="" headers_command="" cookies_command="" [ -n "$headers" ] && printf -v headers_command '%b' "$headers" if [ -n "$cookies" ] then Trim cookies cookies="${cookies%\;}" printf -v cookies_command '%b' "${cookies//;/; path=\/;\\r\\n}; path=/;" fi if [ "$seg_count" -gt 0 ] then hls_command+=( -hls_delete_threshold $seg_count ) fi if [ "$live" = true ] then # segment_command+=( -segment_list_flags +live -segment_list_size $seg_count -segment_wrap $((seg_count * 2)) ) if [ "$seg_count" -gt 0 ] then hls_command+=( -hls_flags periodic_rekey+delete_segments ) else hls_command+=( -hls_flags periodic_rekey ) fi else hls_command+=( -hls_flags periodic_rekey ) fi if [ -n "${stream_url_cdn:-}" ] then stream_link="$stream_url_cdn" fi if [ "${stream_url_qualities_count:-0}" -gt 1 ] || [ "${stream_url_audio_count:-0}" -gt 0 ] || [ "${stream_url_subtitles_count:-0}" -gt 0 ] then if [ "$use_primary_playlist" = true ] then if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && variants_input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && variants_input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && variants_input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && variants_input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && variants_input_command+=( -user_agent "$user_agent" ) fi variants_input_command+=( $input_flags_command -i "$stream_link" ) fi IFS=, read -r -a variants_quality <<< "$quality" IFS=, read -r -a variants_bitrate <<< "$bitrate" IFS=, read -r -a variants_resolution <<< "$resolution" for((i=0;i "$output_dir_root/$key_name.key" if [ "$encrypt_session" = true ] then echo -e "/keys?key=$key_name&channel=$output_dir_name\n$output_dir_root/$key_name.key\n$(openssl rand -hex 16)" > "$output_dir_root/$keyinfo_name.keyinfo" else echo -e "$key_name.key\n$output_dir_root/$key_name.key\n$(openssl rand -hex 16)" > "$output_dir_root/$keyinfo_name.keyinfo" fi hls_command+=( -hls_key_info_file "$output_dir_root/$keyinfo_name.keyinfo" ) fi echo -e "$hls_master_list" > "$output_dir_root/${playlist_name}_master.m3u8" hls_command+=( -hls_segment_filename "$output_dir_root/$seg_dir_path${seg_name}"_%v_%05d.ts ) hls_command+=( "$output_dir_root/${playlist_name}_%v.m3u8" ) PrepTerm $FFMPEG ${global_options[@]+"${global_options[@]}"} \ ${variants_input_command[@]+"${variants_input_command[@]}"} \ ${variants_output_command[@]+"${variants_output_command[@]}"} \ ${map_command[@]+"${map_command[@]}"} \ ${flags_command[@]+"${flags_command[@]}"} \ -f hls ${var_stream_map_command[@]+"${var_stream_map_command[@]}"} \ ${hls_command[@]+"${hls_command[@]}"} > "$FFMPEG_LOG_ROOT/$pid.log" 2> "$FFMPEG_LOG_ROOT/$pid.err" & WaitTerm exit 0 fi input_command=() filter_complex="" if [ "${stream_url_qualities_count:-0}" -eq 1 ] then stream_urls_index=${stream_url_video_indices[0]} stream_link=${stream_urls[stream_urls_index]} fi if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) fi input_command+=( $input_flags_command -i $stream_link ) video_shift_index=0 audio_shift_index=0 if ! [[ $input_flags =~ -an ]] && ! [[ $output_flags =~ -an ]] then if [ -n "${video_shift:-}" ] then video_shift_index=1 if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) fi input_command+=( $input_flags_command -itsoffset $video_shift -i $stream_link ) elif [ -n "${audio_shift:-}" ] then if [ "$audio_codec" == "copy" ] then audio_shift_index=1 if [[ $stream_link =~ ^https?:// ]] then [ -n "$proxy" ] && input_command+=( -http_proxy "$proxy" ) [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) [ -n "$headers_command" ] && input_command+=( -headers "$headers_command" ) [ -n "$cookies_command" ] && input_command+=( -cookies "$cookies_command" ) elif [[ $stream_link =~ ^icecast?:// ]] then [ -n "$user_agent" ] && input_command+=( -user_agent "$user_agent" ) fi input_command+=( $input_flags_command -itsoffset $audio_shift -i $stream_link ) else input_command+=( -filter_complex "[0:a] adelay=delays=${audio_shift}s:all=1 [delayed_audio]" ) fi fi fi IFS="," read -r -a qualities <<< "$quality" IFS="," read -r -a bitrates <<< "$bitrate" IFS="," read -r -a resolutions <<< "$resolution" if [ -n "${qualities+x}" ] then qualities_count=${#qualities[@]} else qualities_count=0 fi if [ -n "${bitrates+x}" ] then bitrates_count=${#bitrates[@]} else bitrates_count=0 fi if [ -n "${resolutions+x}" ] then resolutions_count=${#resolutions[@]} else resolutions_count=0 fi if [ "$bitrates_count" -gt "$qualities_count" ] then variants_count=$bitrates_count else variants_count=$qualities_count fi if [ "$resolutions_count" -gt "$variants_count" ] then variants_count=$resolutions_count fi for((i=0;i "$output_dir_root/$key_name.key" if [ "$encrypt_session" = true ] then echo -e "/keys?key=$key_name&channel=$output_dir_name\n$output_dir_root/$key_name.key\n$(openssl rand -hex 16)" > "$output_dir_root/$keyinfo_name.keyinfo" else echo -e "$key_name.key\n$output_dir_root/$key_name.key\n$(openssl rand -hex 16)" > "$output_dir_root/$keyinfo_name.keyinfo" fi hls_command+=( -hls_key_info_file "$output_dir_root/$keyinfo_name.keyinfo" ) fi if [ "$master" -eq 0 ] then hls_command+=( -hls_segment_filename "$output_dir_root/$seg_dir_path${seg_name}_%05d.ts" ) hls_command+=( "$output_dir_root/$playlist_name.m3u8" ) else echo -e "$hls_master_list" > "$output_dir_root/${playlist_name}_master.m3u8" hls_command+=( -hls_segment_filename "$output_dir_root/$seg_dir_path${seg_name}_%v_%05d.ts" ) hls_command+=( "$output_dir_root/${playlist_name}_%v.m3u8" ) fi PrepTerm $FFMPEG ${global_options[@]+"${global_options[@]}"} \ ${input_command[@]+"${input_command[@]}"} \ ${variants_output_command[@]+"${variants_output_command[@]}"} \ ${map_command[@]+"${map_command[@]}"} \ ${flags_command[@]+"${flags_command[@]}"} \ -f hls ${var_stream_map_command[@]+"${var_stream_map_command[@]}"} \ ${hls_command[@]+"${hls_command[@]}"} > "$FFMPEG_LOG_ROOT/$pid.log" 2> "$FFMPEG_LOG_ROOT/$pid.err" & WaitTerm # seg # PrepTerm # $FFMPEG ${global_options[@]+"${global_options[@]}"} \ # ${input_command[@]+"${input_command[@]}"} \ # ${variants_output_command[@]+"${variants_output_command[@]}"} \ # ${map_command[@]+"${map_command[@]}"} \ # ${flags_command[@]+"${flags_command[@]}"} \ # -f segment ${var_stream_map_command[@]+"${var_stream_map_command[@]}"} \ # -segment_time "$seg_length" -segment_format mpeg_ts \ # -segment_list $output_dir_root/$playlist_name.m3u8 $output_dir_root/$seg_dir_path${seg_name}_%05d.ts \ # ${segment_command[@]+"${segment_command[@]}"} > "$FFMPEG_LOG_ROOT/$pid.log" 2> "$FFMPEG_LOG_ROOT/$pid.err" & # WaitTerm } 201>"$pid_file" ;; "StartChannel") if [ "$chnl_hls_end_list" = true ] then unset delete_on_term else delete_on_term="$chnl_output_dir_root" fi new_pid=$pid pid_file="$FFMPEG_LOG_ROOT/$new_pid.pid" { flock -x 201 file=true jq_path='["channels"]' jq_path2='["stream_link"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" chnl_stream_links update=$( $JQ_FILE -n --arg pid "$new_pid" --arg status "on" \ --arg user_agent "$chnl_user_agent" --arg headers "$chnl_headers" \ --arg cookies "$chnl_cookies" --arg playlist_name "$chnl_playlist_name" \ --arg seg_name "$chnl_seg_name" --arg key_name "$chnl_key_name" \ --arg key_time "$chnl_key_time" --arg channel_name "$chnl_channel_name" \ --arg channel_time "$chnl_channel_time" \ '{ pid: $pid | tonumber, status: $status, user_agent: $user_agent, headers: $headers, cookies: $cookies, playlist_name: $playlist_name, seg_name: $seg_name, key_name: $key_name, key_time: $key_time | tonumber, channel_name: $channel_name, channel_time: $channel_time | tonumber }' ) merge=true jq_path='["channels"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" "$update" action="start" SyncFile trap ' if [ "$chnl_live" = false ] then MonitorLog "$chnl_channel_name HLS 切片完成" exit 1 fi jq_path=[\"channels\"] jq_path2=[\"status\"] JQ update "$CHANNELS_FILE" pid "$new_pid" off MonitorLog "`eval_gettext \"\\\$chnl_channel_name HLS 关闭\"`" chnl_pid=$new_pid action="stop" SyncFile if [ "$chnl_hls_end_list" = true ] && ls -A "$chnl_output_dir_root/"*.m3u8 > /dev/null 2>&1 then for play_list in "$chnl_output_dir_root/"*.m3u8 do echo "#EXT-X-ENDLIST" >> "$play_list" || exit 1 done sleep "$chnl_seg_length" fi rm -rf "$chnl_output_dir_root" ' EXIT mkdir -p "$chnl_output_dir_root" chnl_variants_input_command=() chnl_variants_output_command=() chnl_map_command=() chnl_var_stream_map_command=() chnl_hls_command=( -hls_time "$chnl_seg_length" -hls_list_size "$chnl_seg_count" ) # chnl_segment_command=() chnl_hls_master_list="#EXTM3U\n#EXT-X-VERSION:7\n" chnl_var_stream_map="" chnl_headers_command="" chnl_cookies_command="" [ -n "$chnl_headers" ] && printf -v chnl_headers_command '%b' "$chnl_headers" if [ -n "$chnl_cookies" ] then Trim chnl_cookies chnl_cookies="${chnl_cookies%\;}" printf -v chnl_cookies_command '%b' "${chnl_cookies//;/; path=\/;\\r\\n}; path=/;" fi if [ "$chnl_seg_count" -gt 0 ] then chnl_hls_command+=( -hls_delete_threshold $chnl_seg_count ) fi if [ "$chnl_live" = true ] then # chnl_segment_command+=( -segment_list_flags +live -segment_list_size $chnl_seg_count -segment_wrap $((chnl_seg_count * 2)) ) if [ "$chnl_seg_count" -gt 0 ] then chnl_hls_command+=( -hls_flags periodic_rekey+delete_segments ) else chnl_hls_command+=( -hls_flags periodic_rekey ) fi else chnl_hls_command+=( -hls_flags periodic_rekey ) fi if [ -n "${chnl_stream_url_cdn:-}" ] then chnl_stream_link="$chnl_stream_url_cdn" fi if [ "${chnl_stream_url_qualities_count:-0}" -gt 1 ] || [ "${chnl_stream_url_audio_count:-0}" -gt 0 ] || [ "${chnl_stream_url_subtitles_count:-0}" -gt 0 ] then if [ "$chnl_use_primary_playlist" = true ] then if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_variants_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_variants_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_variants_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_variants_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_variants_input_command+=( -user_agent "$chnl_user_agent" ) fi #if [ "${chnl_stream_url_subtitles_count:-0}" -gt 0 ] #then # chnl_variants_input_command+=( -strict experimental ) #fi chnl_variants_input_command+=( $chnl_input_flags_command -i "$chnl_stream_link" ) fi IFS=, read -r -a chnl_variants_quality <<< "$chnl_quality" IFS=, read -r -a chnl_variants_bitrate <<< "$chnl_bitrate" IFS=, read -r -a chnl_variants_resolution <<< "$chnl_resolution" for((i=0;i "$chnl_output_dir_root/$chnl_key_name.key" if [ "$chnl_encrypt_session" = true ] then echo -e "/keys?key=$chnl_key_name&channel=$chnl_output_dir_name\n$chnl_output_dir_root/$chnl_key_name.key\n$(openssl rand -hex 16)" > "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" else echo -e "$chnl_key_name.key\n$chnl_output_dir_root/$chnl_key_name.key\n$(openssl rand -hex 16)" > "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" fi chnl_hls_command+=( -hls_key_info_file "$chnl_output_dir_root/$chnl_keyinfo_name".keyinfo ) fi echo -e "$chnl_hls_master_list" > "$chnl_output_dir_root/${chnl_playlist_name}_master.m3u8" chnl_hls_command+=( -hls_segment_filename "$chnl_output_dir_root/$chnl_seg_dir_path${chnl_seg_name}"_%v_%05d.ts ) chnl_hls_command+=( "$chnl_output_dir_root/${chnl_playlist_name}_%v.m3u8" ) PrepTerm $FFMPEG ${global_options[@]+"${global_options[@]}"} \ ${chnl_variants_input_command[@]+"${chnl_variants_input_command[@]}"} \ ${chnl_variants_output_command[@]+"${chnl_variants_output_command[@]}"} \ ${chnl_map_command[@]+"${chnl_map_command[@]}"} \ ${chnl_flags_command[@]+"${chnl_flags_command[@]}"} \ -f hls ${chnl_var_stream_map_command[@]+"${chnl_var_stream_map_command[@]}"} \ ${chnl_hls_command[@]+"${chnl_hls_command[@]}"} > "$FFMPEG_LOG_ROOT/$new_pid.log" 2> "$FFMPEG_LOG_ROOT/$new_pid.err" & WaitTerm exit 0 fi chnl_input_command=() chnl_filter_complex="" if [ "${chnl_stream_url_qualities_count:-0}" -eq 1 ] then chnl_stream_urls_index=${chnl_stream_url_video_indices[0]} chnl_stream_link=${chnl_stream_urls[chnl_stream_urls_index]} fi if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) fi chnl_input_command+=( $chnl_input_flags_command -i $chnl_stream_link ) chnl_video_shift_index=0 chnl_audio_shift_index=0 if ! [[ $chnl_input_flags =~ -an ]] && ! [[ $chnl_output_flags =~ -an ]] then if [ -n "${chnl_video_shift:-}" ] then chnl_video_shift_index=1 if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) fi chnl_input_command+=( $chnl_input_flags_command -itsoffset $chnl_video_shift -i $chnl_stream_link ) elif [ -n "${chnl_audio_shift:-}" ] then if [ "$chnl_audio_codec" == "copy" ] then chnl_audio_shift_index=1 if [[ $chnl_stream_link =~ ^https?:// ]] then [ -n "$chnl_proxy" ] && chnl_input_command+=( -http_proxy "$chnl_proxy" ) [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) [ -n "$chnl_headers_command" ] && chnl_input_command+=( -headers "$chnl_headers_command" ) [ -n "$chnl_cookies_command" ] && chnl_input_command+=( -cookies "$chnl_cookies_command" ) elif [[ $chnl_stream_link =~ ^icecast?:// ]] then [ -n "$chnl_user_agent" ] && chnl_input_command+=( -user_agent "$chnl_user_agent" ) fi chnl_input_command+=( $chnl_input_flags_command -itsoffset $chnl_audio_shift -i $chnl_stream_link ) else chnl_input_command+=( -filter_complex "[0:a] adelay=delays=${chnl_audio_shift}s:all=1 [delayed_audio]" ) fi fi fi IFS="," read -r -a chnl_qualities <<< "$chnl_quality" IFS="," read -r -a chnl_bitrates <<< "$chnl_bitrate" IFS="," read -r -a chnl_resolutions <<< "$chnl_resolution" if [ -n "${chnl_qualities+x}" ] then chnl_qualities_count=${#chnl_qualities[@]} else chnl_qualities_count=0 fi if [ -n "${chnl_bitrates+x}" ] then chnl_bitrates_count=${#chnl_bitrates[@]} else chnl_bitrates_count=0 fi if [ -n "${chnl_resolutions+x}" ] then chnl_resolutions_count=${#chnl_resolutions[@]} else chnl_resolutions_count=0 fi if [ "$chnl_bitrates_count" -gt "$chnl_qualities_count" ] then chnl_variants_count=$chnl_bitrates_count else chnl_variants_count=$chnl_qualities_count fi if [ "$chnl_resolutions_count" -gt "$chnl_variants_count" ] then chnl_variants_count=$chnl_resolutions_count fi for((i=0;i "$chnl_output_dir_root/$chnl_key_name.key" if [ "$chnl_encrypt_session" = true ] then echo -e "/keys?key=$chnl_key_name&channel=$chnl_output_dir_name\n$chnl_output_dir_root/$chnl_key_name.key\n$(openssl rand -hex 16)" > "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" else echo -e "$chnl_key_name.key\n$chnl_output_dir_root/$chnl_key_name.key\n$(openssl rand -hex 16)" > "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" fi chnl_hls_command+=( -hls_key_info_file "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" ) fi if [ "$master" -eq 0 ] then chnl_hls_command+=( -hls_segment_filename "$chnl_output_dir_root/$chnl_seg_dir_path${chnl_seg_name}_%05d.ts" ) chnl_hls_command+=( "$chnl_output_dir_root/$chnl_playlist_name.m3u8" ) else echo -e "$chnl_hls_master_list" > "$chnl_output_dir_root/${chnl_playlist_name}_master.m3u8" chnl_hls_command+=( -hls_segment_filename "$chnl_output_dir_root/$chnl_seg_dir_path${chnl_seg_name}_%v_%05d.ts" ) chnl_hls_command+=( "$chnl_output_dir_root/${chnl_playlist_name}_%v.m3u8" ) fi # https://stackoverflow.com/questions/23235651/how-can-i-do-ansi-c-quoting-of-an-existing-bash-variable PrepTerm $FFMPEG ${global_options[@]+"${global_options[@]}"} \ ${chnl_input_command[@]+"${chnl_input_command[@]}"} \ ${chnl_variants_output_command[@]+"${chnl_variants_output_command[@]}"} \ ${chnl_map_command[@]+"${chnl_map_command[@]}"} \ ${chnl_flags_command[@]+"${chnl_flags_command[@]}"} \ -f hls ${chnl_var_stream_map_command[@]+"${chnl_var_stream_map_command[@]}"} \ ${chnl_hls_command[@]+"${chnl_hls_command[@]}"} > "$FFMPEG_LOG_ROOT/$new_pid.log" 2> "$FFMPEG_LOG_ROOT/$new_pid.err" & WaitTerm # seg # PrepTerm # $FFMPEG ${global_options[@]+"${global_options[@]}"} \ # ${chnl_input_command[@]+"${chnl_input_command[@]}"} \ # ${chnl_variants_output_command[@]+"${chnl_variants_output_command[@]}"} \ # ${chnl_map_command[@]+"${chnl_map_command[@]}"} \ # ${chnl_flags_command[@]+"${chnl_flags_command[@]}"} \ # -f segment ${chnl_var_stream_map_command[@]+"${chnl_var_stream_map_command[@]}"} \ # -segment_time "$chnl_seg_length" -segment_format mpeg_ts \ # -segment_list $chnl_output_dir_root/$chnl_playlist_name.m3u8 $chnl_output_dir_root/$chnl_seg_dir_path${chnl_seg_name}_%05d.ts \ # ${chnl_segment_command[@]+"${chnl_segment_command[@]}"} > "$FFMPEG_LOG_ROOT/$new_pid.log" 2> "$FFMPEG_LOG_ROOT/$new_pid.err" & # WaitTerm } 201>"$pid_file" ;; esac } GetDefault() { if [ -n "${d_version:-}" ] then return 0 fi IFS=$'\001\t' read -r d_proxy d_xc_proxy d_user_agent d_headers d_cookies d_playlist_name \ d_seg_dir_name d_seg_name d_seg_length d_seg_count d_video_codec d_audio_codec \ d_video_audio_shift d_txt_format d_draw_text d_quality d_bitrate d_resolution d_const d_const_cbr \ d_encrypt d_encrypt_session d_keyinfo_name d_key_name d_input_flags d_output_flags d_sync d_sync_file \ d_sync_index d_sync_pairs d_schedule_file d_flv_delay_seconds d_flv_restart_nums \ d_hls_delay_seconds d_hls_min_bitrate d_hls_max_seg_size d_hls_restart_nums \ d_hls_key_period d_hls_end_list d_anti_ddos_port d_anti_ddos_syn_flood d_anti_ddos_syn_flood_delay_seconds \ d_anti_ddos_syn_flood_seconds d_anti_ddos d_anti_ddos_seconds d_anti_ddos_level \ d_anti_leech d_anti_leech_restart_nums d_anti_leech_restart_flv_changes \ d_anti_leech_restart_hls_changes d_recheck_period d_version < <($JQ_FILE -c -r ' .default as $default | reduce ({proxy,xc_proxy,user_agent,headers,cookies,playlist_name,seg_dir_name,seg_name,seg_length, seg_count,video_codec,audio_codec,video_audio_shift,txt_format,draw_text,quality,bitrate,resolution,const, const_cbr,encrypt,encrypt_session,keyinfo_name,key_name,input_flags,output_flags,sync,sync_file,sync_index, sync_pairs,schedule_file,flv_delay_seconds,flv_restart_nums,hls_delay_seconds,hls_min_bitrate, hls_max_seg_size,hls_restart_nums,hls_key_period,hls_end_list,anti_ddos_port,anti_ddos_syn_flood, anti_ddos_syn_flood_delay_seconds,anti_ddos_syn_flood_seconds,anti_ddos,anti_ddos_seconds, anti_ddos_level,anti_leech,anti_leech_restart_nums,anti_leech_restart_flv_changes, anti_leech_restart_hls_changes,recheck_period,version}|keys_unsorted[]) as $key ([]; $default[$key] as $val | if $val then . + [($val | tostring) + "\u0001"] else . + ["\u0001"] end )|@tsv' "$CHANNELS_FILE") if [ -z "$d_version" ] then return 0 fi d_user_agent="${d_user_agent:-$USER_AGENT_TV}" d_cookies="${d_cookies:-stb_lang=en; timezone=Europe/Amsterdam}" v_or_a=${d_video_audio_shift%_*} if [ "$v_or_a" == "v" ] then d_video_shift=${d_video_audio_shift#*_} d_video_audio_shift_text=$(eval_gettext "画面延迟 \$d_video_shift 秒") elif [ "$v_or_a" == "a" ] then d_audio_shift=${d_video_audio_shift#*_} d_video_audio_shift_text=$(eval_gettext "声音延迟 \$d_audio_shift 秒") else d_video_audio_shift_text=$i18n_not_set fi d_const_cbr=${d_const_cbr:-false} d_encrypt=${d_encrypt:-false} d_encrypt_session=${d_encrypt_session:-false} d_sync=${d_sync:-true} d_flv_delay_seconds=${d_flv_delay_seconds:-20} d_flv_restart_nums=${d_flv_restart_nums:-20} d_hls_delay_seconds=${d_hls_delay_seconds:-120} d_hls_min_bitrate=${d_hls_min_bitrate:-500} d_hls_max_seg_size=${d_hls_max_seg_size:-5} d_hls_restart_nums=${d_hls_restart_nums:-20} d_hls_key_period=${d_hls_key_period:-30} d_hls_end_list=${d_hls_end_list:-false} d_anti_ddos_port=${d_anti_ddos_port:-80} d_anti_ddos_port_text=${d_anti_ddos_port//,/ } d_anti_ddos_port_text=${d_anti_ddos_port_text//:/-} d_anti_ddos_syn_flood=${d_anti_ddos_syn_flood:-false} d_anti_ddos_syn_flood_delay_seconds=${d_anti_ddos_syn_flood_delay_seconds:-3} d_anti_ddos_syn_flood_seconds=${d_anti_ddos_syn_flood_seconds:-3600} d_anti_ddos=${d_anti_ddos:-false} d_anti_ddos_seconds=${d_anti_ddos_seconds:-120} d_anti_ddos_level=${d_anti_ddos_level:-6} d_anti_leech=${d_anti_leech:-false} d_anti_leech_restart_nums=${d_anti_leech_restart_nums:-0} d_anti_leech_restart_flv_changes=${d_anti_leech_restart_flv_changes:-false} d_anti_leech_restart_hls_changes=${d_anti_leech_restart_hls_changes:-false} d_recheck_period=${d_recheck_period:-0} if [ "$d_recheck_period" -eq 0 ] then d_recheck_period_text=$i18n_not_set else d_recheck_period_text="$d_recheck_period" fi } GetChannels() { [ ! -d "$IPTV_ROOT" ] && Println "`eval_gettext \"\\\$error 尚未安装, 请检查 !\"`\n" && exit 1 [ -z "${delimiters:-}" ] && delimiters=( $'\001' $'\002' $'\003' $'\004' $'\005' $'\006' ) IFS=$'\004\t' read -r m_pid m_status m_hide m_stream_link m_live m_proxy m_xc_proxy \ m_user_agent m_headers m_cookies m_output_dir_name m_playlist_name m_seg_dir_name \ m_seg_name m_seg_length m_seg_count m_video_codec m_audio_codec m_video_audio_shift \ m_txt_format m_draw_text m_quality m_bitrate m_resolution m_const m_const_cbr \ m_encrypt m_encrypt_session m_keyinfo_name m_key_name m_key_time m_input_flags \ m_output_flags m_channel_name m_channel_time m_sync m_sync_file m_sync_index \ m_sync_pairs m_hls_end_list m_flv_status m_flv_h265 m_flv_push_link m_flv_pull_link \ m_schedule_start_time m_schedule_end_time m_schedule_loop m_schedule_auto_remove m_schedule_hls_change \ m_schedule_hls_change_once m_schedule_channel_name m_schedule_status < <(JQs flat "$CHANNELS_FILE" '' ' (.channels | if . == "" then {} else . end) as $channels | ($channels.schedule // {} | if (.|type) == "string" then {} else . end) as $schedule | reduce ({pid,status,hide,stream_link,live,proxy,xc_proxy,user_agent,headers,cookies,output_dir_name, playlist_name,seg_dir_name,seg_name,seg_length,seg_count,video_codec,audio_codec,video_audio_shift, txt_format,draw_text,quality,bitrate,resolution,const,const_cbr,encrypt,encrypt_session,keyinfo_name,key_name,key_time, input_flags,output_flags,channel_name,channel_time,sync,sync_file,sync_index,sync_pairs,hls_end_list, flv_status,flv_h265,flv_push_link,flv_pull_link}|keys_unsorted[]) as $key ([]; $channels[$key] as $val | if $val then . + [$val + "\u0002\u0004"] else . + ["\u0004"] end ) + reduce ({start_time,end_time,loop,auto_remove,hls_change,hls_change_once,channel_name,status}|keys_unsorted[]) as $key ([]; $schedule[$key] as $val | if $val then . + [$val + "\u0003\u0004"] else . + ["\u0004"] end )|@tsv' "${delimiters[@]}") if [ -z "$m_pid" ] then chnls_count=0 return 0 fi IFS="${delimiters[1]}" read -ra chnls_pid <<< "$m_pid" IFS="${delimiters[1]}" read -ra chnls_status <<< "$m_status" chnls_count=${#chnls_pid[@]} if_null_off=${m_status//on/off} if_null_empty=${if_null_off//off/} if_null_true=${if_null_off//off/true} if_null_false=${if_null_off//off/false} if_null_schedule_empty=${if_null_empty//${delimiters[1]}/${delimiters[2]}} IFS="${delimiters[1]}" read -ra chnls_hide <<< "${m_hide:-$if_null_false}" IFS="${delimiters[1]}" read -ra chnls_stream_links <<< "${m_stream_link:-$if_null_empty}" chnls_stream_link=("${chnls_stream_links[@]%%${delimiters[0]}*}") IFS="${delimiters[1]}" read -ra chnls_live <<< "${m_live:-$if_null_true}" IFS="${delimiters[1]}" read -ra chnls_proxy <<< "${m_proxy:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_xc_proxy <<< "${m_xc_proxy:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_user_agent <<< "${m_user_agent:-${if_null_off//off/$USER_AGENT_TV}}" IFS="${delimiters[1]}" read -ra chnls_headers <<< "${m_headers:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_cookies <<< "${m_cookies:-${if_null_off//off/stb_lang=en; timezone=Europe/Amsterdam}}" IFS="${delimiters[1]}" read -ra chnls_output_dir_name <<< "$m_output_dir_name" IFS="${delimiters[1]}" read -ra chnls_playlist_name <<< "$m_playlist_name" IFS="${delimiters[1]}" read -ra chnls_seg_dir_name <<< "$m_seg_dir_name" IFS="${delimiters[1]}" read -ra chnls_seg_name <<< "$m_seg_name" IFS="${delimiters[1]}" read -ra chnls_seg_length <<< "$m_seg_length" IFS="${delimiters[1]}" read -ra chnls_seg_count <<< "$m_seg_count" IFS="${delimiters[1]}" read -ra chnls_video_codec <<< "$m_video_codec" IFS="${delimiters[1]}" read -ra chnls_audio_codec <<< "$m_audio_codec" IFS="${delimiters[1]}" read -ra chnls_video_audio_shift <<< "${m_video_audio_shift:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_txt_format <<< "${m_txt_format:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_draw_text <<< "${m_draw_text:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_quality <<< "$m_quality" IFS="${delimiters[1]}" read -ra chnls_bitrate <<< "${m_bitrate:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_resolution <<< "${m_resolution:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_const <<< "${m_const:-$if_null_false}" IFS="${delimiters[1]}" read -ra chnls_const_cbr <<< "${m_const_cbr:-$if_null_false}" m_encrypt=${m_encrypt//-e/false} IFS="${delimiters[1]}" read -ra chnls_encrypt <<< "${m_encrypt:-$if_null_false}" IFS="${delimiters[1]}" read -ra chnls_encrypt_session <<< "${m_encrypt_session:-$if_null_false}" IFS="${delimiters[1]}" read -ra chnls_keyinfo_name <<< "${m_keyinfo_name:-${if_null_off//off/keyinfo}}" IFS="${delimiters[1]}" read -ra chnls_key_name <<< "${m_key_name:-${if_null_off//off/keyname}}" if [ -z "$m_key_time" ] then printf -v now '%(%s)T' -1 m_key_time=${if_null_off//off/${now}} fi IFS="${delimiters[1]}" read -ra chnls_key_time <<< "$m_key_time" IFS="${delimiters[1]}" read -ra chnls_input_flags <<< "$m_input_flags" IFS="${delimiters[1]}" read -ra chnls_output_flags <<< "$m_output_flags" IFS="${delimiters[1]}" read -ra chnls_channel_name <<< "${m_channel_name:-${if_null_off//off/channel_name}}" if [ -z "$m_channel_time" ] then printf -v now '%(%s)T' -1 m_channel_time=${if_null_off//off/${now}} fi IFS="${delimiters[1]}" read -ra chnls_channel_time <<< "$m_channel_time" IFS="${delimiters[1]}" read -ra chnls_sync <<< "${m_sync:-$if_null_true}" IFS="${delimiters[1]}" read -ra chnls_sync_file <<< "${m_sync_file:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_sync_index <<< "${m_sync_index:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_sync_pairs <<< "${m_sync_pairs:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_hls_end_list <<< "${m_hls_end_list:-$if_null_false}" IFS="${delimiters[1]}" read -ra chnls_flv_status <<< "${m_flv_status:-$if_null_off}" IFS="${delimiters[1]}" read -ra chnls_flv_h265 <<< "${m_flv_h265:-$if_null_false}" IFS="${delimiters[1]}" read -ra chnls_flv_push_link <<< "${m_flv_push_link:-$if_null_empty}" IFS="${delimiters[1]}" read -ra chnls_flv_pull_link <<< "${m_flv_pull_link:-$if_null_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_start_time <<< "${m_schedule_start_time:-$if_null_schedule_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_end_time <<< "${m_schedule_end_time:-$if_null_schedule_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_loop <<< "${m_schedule_loop:-$if_null_schedule_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_auto_remove <<< "${m_schedule_auto_remove:-$if_null_schedule_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_hls_change <<< "${m_schedule_hls_change:-$if_null_schedule_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_hls_change_once <<< "${m_schedule_hls_change_once:-$if_null_schedule_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_channel_name <<< "${m_schedule_channel_name:-$if_null_schedule_empty}" IFS="${delimiters[2]}" read -ra chnls_schedule_status <<< "${m_schedule_status:-$if_null_schedule_empty}" } ListChannels() { GetChannels if [ "$chnls_count" -eq 0 ] then Println "`eval_gettext \"\\\$error 没有发现频道, 请检查 !\"`\n" && exit 1 fi i18nGetMsg get_channels chnls_indices=("${!chnls_pid[@]}") chnls_list="" for chnls_index in "${chnls_indices[@]}" do if [ "${chnls_hide[chnls_index]}" = true ] then continue fi chnls_output_dir_root="$LIVE_ROOT/${chnls_output_dir_name[chnls_index]}" v_or_a=${chnls_video_audio_shift[chnls_index]%_*} if [ "$v_or_a" == "v" ] then chnls_video_shift=${chnls_video_audio_shift[chnls_index]#*_} chnls_video_audio_shift_text="$i18n_video_shift $chnls_video_shift($i18n_seconds)" elif [ "$v_or_a" == "a" ] then chnls_audio_shift=${chnls_video_audio_shift[chnls_index]#*_} chnls_video_audio_shift_text="$i18n_audio_shift $chnls_audio_shift($i18n_seconds)" else chnls_video_audio_shift_text="$i18n_not_set" fi IFS=, read -r -a chnl_qualities <<< "${chnls_quality[chnls_index]}" IFS=, read -r -a chnl_bitrates <<< "${chnls_bitrate[chnls_index]}" IFS=, read -r -a chnl_resolutions <<< "${chnls_resolution[chnls_index]}" if [ -n "${chnl_qualities+x}" ] then chnl_qualities_count=${#chnl_qualities[@]} else chnl_qualities_count=0 fi if [ -n "${chnl_bitrates+x}" ] then chnl_bitrates_count=${#chnl_bitrates[@]} else chnl_bitrates_count=0 fi if [ -n "${chnl_resolutions+x}" ] then chnl_resolutions_count=${#chnl_resolutions[@]} else chnl_resolutions_count=0 fi if [ "$chnl_bitrates_count" -gt "$chnl_qualities_count" ] then chnl_variants_count=$chnl_bitrates_count else chnl_variants_count=$chnl_qualities_count fi if [ "$chnl_resolutions_count" -gt "$chnl_variants_count" ] then chnl_variants_count=$chnl_resolutions_count fi chnls_video_quality_text="" for((chnl_i=0;chnl_i /dev/null) chnl="${stream_link%\?*}" chnl=${chnl##*/} token_url=$(curl -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ "https://api.news.tvb.com/news/v2.2.1/live?profile=web" \ | $JQ_FILE -r '.items[]|select(.path=="'"$chnl"'" or .path=="'"${chnl#*_}"'" or .path=="'"${chnl%_*}"'").video.ios[]|select(.type=="hd").url') if [ -z "$token_url" ] then Println "$error 无法解析 tvb: token url\n" return 0 fi query_string="$token_url&feed&client_ip=$(GetServerIp)" query_string=$(UrlencodeUpper "$query_string") stream_link=$(curl -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ --cookie "$cookies" \ "https://news.tvb.com/ajax_call/getVideo.php?token=$query_string" \ | $JQ_FILE -r '.url') if [ "$stream_link" == null ] then Println "$error 无法解析 tvb: url\n" return 0 fi while IFS= read -r line do if [[ $line =~ hdntl= ]] then line=${line#* } cookies="$cookies ${line%% *}" break fi done < <(curl -s -I -H "User-Agent: $user_agent" -H "${headers:0:-4}" --cookie "$cookies" "$stream_link" 2> /dev/null) elif [[ $stream_link =~ ^https://embed\.4gtv\.tv/HiNet/(.+)\.html ]] then OpensslInstall Println "`eval_gettext \"\\\$info 解析 4gtv 链接 ...\"`" hinet_4gtv=( "litv-ftv13:民視新聞台" "litv-longturn14:寰宇新聞台" "4gtv-4gtv052:華視新聞資訊台" "4gtv-4gtv012:空中英語教室" "litv-ftv07:民視旅遊台" "litv-ftv15:i-Fun動漫台" "4gtv-live206:幸福空間居家台" "4gtv-4gtv070:愛爾達娛樂台" "litv-longturn17:亞洲旅遊台" "4gtv-4gtv025:MTV Live HD" "litv-longturn15:寰宇新聞台灣台" "4gtv-4gtv001:民視台灣台" "4gtv-4gtv074:中視新聞台" "4gtv-4gtv011:影迷數位電影台" "4gtv-4gtv047:靖天日本台" "litv-longturn11:龍華日韓台" "litv-longturn12:龍華偶像台" "4gtv-4gtv042:公視戲劇" "litv-ftv12:i-Fun動漫台3" "4gtv-4gtv002:民視無線台" "4gtv-4gtv027:CI 罪案偵查頻道" "4gtv-4gtv013:CNEX紀實頻道" "litv-longturn03:龍華電影台" "4gtv-4gtv004:民視綜藝台" "litv-longturn20:ELTV英語學習台" "litv-longturn01:龍華卡通台" "4gtv-4gtv040:中視無線台" "litv-longturn02:Baby First" "4gtv-4gtv003:民視第一台" "4gtv-4gtv007:大愛電視台" "4gtv-4gtv076:SMART 知識頻道" "4gtv-4gtv030:CNBC" "litv-ftv10:半島電視台" ) stream_link_uri_name=${BASH_REMATCH[1]} for channel in "${hinet_4gtv[@]}" do channel_id=${channel%%:*} channel_name=${channel#*:} channel_name_enc=$(Urlencode "$channel_name") if [[ $channel_name_enc == "$stream_link_uri_name" ]] then user_agent="${user_agent:-$USER_AGENT_BROWSER}" headers="${headers:-Referer: https://embed.4gtv.tv/HiNet/$channel_name_enc.html?ar=0&as=1&volume=0\\r\\n}" cookies="${cookies:-}" stream_link_data=$(CurlFake -s -Lm 10 \ -H "${headers:0:-4}" \ "https://app.4gtv.tv/Data/HiNet/GetURL.ashx?ChannelNamecallback=channelname&Type=LIVE&Content=$channel_id&HostURL=https%3A%2F%2Fwww.hinet.net%2Ftv%2F&_=$(date +%s%3N)") || true if [ -n "$stream_link_data" ] then stream_link_data=$($JQ_FILE -r '.VideoURL' <<< "${stream_link_data:12:-1}") hexkey=$(echo -n "VxzAfiseH0AbLShkQOPwdsssw5KyLeuv" | hexdump -v -e '/1 "%02x"') hexiv=$(echo -n "${stream_link_data:0:16}" | hexdump -v -e '/1 "%02x"') stream_link_url=$(echo "${stream_link_data:16}" | openssl enc -aes-256-cbc -d -iv "$hexiv" -K "$hexkey" -a) stream_link_url_path=${stream_link_url%/*} if ! Add4gtvLink then exit 1 fi else Println "`eval_gettext \"\\\$error 无法连接 4gtv !\"`\n" && exit 1 fi break fi done elif [[ $stream_link == *"4gtv.tv/"* ]] then OpensslInstall Println "`eval_gettext \"\\\$info 解析 4gtv 链接 ...\"`" user_agent="${user_agent:-$USER_AGENT_BROWSER}" headers="${headers:-Referer: ${stream_link%%|*}\\r\\n}" cookies="${cookies:-}" set_id=${stream_link#*channelSet_id=} set_id=${set_id%%&*} set_id=${set_id%%|*} fsVALUE="" if [ "$set_id" -eq 1 ] then GetServiceAccs 4gtv for((s_i=0;s_i<_4gtv_accs_count;s_i++)); do if [ -n "${_4gtv_accs_token[s_i]:-}" ] then fsVALUE=${_4gtv_accs_token[s_i]} break fi done fi fnCHANNEL_ID=${stream_link#*channel_id=} fnCHANNEL_ID=${fnCHANNEL_ID%%&*} fnCHANNEL_ID=${fnCHANNEL_ID%%|*} fsASSET_ID=${stream_link#*asset_id=} fsASSET_ID=${fsASSET_ID%%&*} fsASSET_ID=${fsASSET_ID%%|*} key="ilyB29ZdruuQjC45JhBBR7o2Z8WJ26Vg" iv="JUMxvVMmszqUTeKn" hexkey=$(echo -n $key | hexdump -v -e '/1 "%02x"') hexiv=$(echo -n $iv | hexdump -v -e '/1 "%02x"') post_data='{"fnCHANNEL_ID":'"$fnCHANNEL_ID"',"fsASSET_ID":"'"$fsASSET_ID"'","fsDEVICE_TYPE":"pc","clsIDENTITY_VALIDATE_ARUS":{"fsVALUE":"'"$fsVALUE"'"}}' post_data=$(echo -n "$post_data" | openssl enc -aes-256-cbc -iv "$hexiv" -K "$hexkey" -a) if [ -n "$fsVALUE" ] then value="$(UrlencodeUpper ${post_data//[[:space:]]/})" else value="$(Urlencode ${post_data//[[:space:]]/})" fi for((try_i=0;try_i<10;try_i++)); do stream_link_data=$(CurlFake -s -Lm 10 \ -H "${headers:0:-4}" \ --data "value=$value" \ "https://api2.4gtv.tv/Channel/GetChannelUrl3") || true if [ -n "$stream_link_data" ] then break fi done if [ -z "$stream_link_data" ] then Println "`eval_gettext \"\\\$error 无法连接 4gtv !\"`\n" && exit 1 fi stream_link_data=$($JQ_FILE -r '.Data' <<< "$stream_link_data") if [ "$stream_link_data" == null ] then Println "`eval_gettext \"\\\$error 此服务器 ip 不支持或频道不可用!\"`\n" else stream_link_url=$(echo "$stream_link_data" | openssl enc -aes-256-cbc -d -iv "$hexiv" -K "$hexkey" -a \ | $JQ_FILE -r '.flstURLs[0]') stream_link_url_path=${stream_link_url%/*} if ! Add4gtvLink then exit 1 fi fi elif [[ $stream_link == http://*.macaulotustv.com/* ]] then user_agent="${user_agent:-$USER_AGENT_BROWSER}" headers="${headers:-Origin: http://www.lotustv.cc\\r\\nReferer: http://www.lotustv.cc/\\r\\n}" cookies="${cookies:-}" fi Println " `gettext \"直播源:\"` ${green} $stream_link ${normal}\n" ParseHlsStreamLink } SetIsHls() { Println "$tip 如果直播源重定向至 .m3u8 地址, 请选择 是" inquirer list_input_index "是否是 HLS 链接" ny_options ny_options_index if [ "$ny_options_index" -eq 0 ] then is_hls=false else is_hls=true fi } SetSubtitle() { echo inquirer list_input "输入源是否有 DVB teletext 需要转换为 WebVTT 字幕" ny_options txt_format if [[ $txt_format == "$i18n_yes" ]] then echo txt_format_options=( 'text' 'ass' ) inquirer list_input "选择字幕转码成的格式" txt_format_options txt_format else txt_format="" fi } SetDrawtext() { Println "$tip 比如 fontsize=25:fontfile=/usr/local/iptv/AlibabaSans-Regular.otf:fontcolor=white:box=1:boxcolor=black@0.5:x=50:y=10:text=yourdomain.com" inquirer text_input "输入 drawtext 水印 : " draw_text "${d_draw_text:-$i18n_not_set}" if [ "$draw_text" == "omit" ] || [ "$draw_text" == "$i18n_not_set" ] then draw_text="" fi } SetLive() { if [ -z "${kind:-}" ] then Println "$tip 选择 否 则无法设置切割的分片数且无法监控" else Println "$tip 选择 否 则无法监控" fi inquirer list_input_index "是否是无限时长直播源" yn_options yn_options_index if [ "$yn_options_index" -eq 0 ] then live=true else live=false fi } SetProxy() { if [ "${skip_set_stream_link:-false}" = true ] && [ -n "${_4gtv_proxy:-}" ] then proxy="$_4gtv_proxy" Println " ffmpeg 代理: ${green} $_4gtv_proxy ${normal}\n" return 0 fi Println "$tip 可以使用脚本自带的 v2ray 管理面板添加代理, 可以输入 omit 省略此选项" inquirer text_input "请输入 ffmpeg 代理, 比如 http://username:passsword@127.0.0.1:5555 : " proxy "${d_proxy:-$i18n_not_set}" if [ "$proxy" == "omit" ] || [ "$proxy" == "$i18n_not_set" ] then proxy="" fi } SetXtreamCodesProxy() { if [ "${skip_set_stream_link:-false}" = true ] && [ -n "${xtream_codes_proxy:-}" ] then xc_proxy="$xtream_codes_proxy" Println " xtream codes 代理: ${green} $xc_proxy ${normal}\n" return 0 fi Println "$tip 可以使用脚本自带的 cloudflare workers 管理面板添加 xtream codes 代理 worker, 可以输入 omit 省略此选项" inquirer text_input "请输入 xtream codes 代理: " xc_proxy "${d_xc_proxy:-$i18n_not_set}" if [ "$xc_proxy" == "omit" ] || [ "$xc_proxy" == "$i18n_not_set" ] then xc_proxy="" fi } SetUserAgent() { if [ "${skip_set_stream_link:-false}" = true ] then Println " ffmpeg UA: ${green} ${user_agent:-$i18n_not_set} ${normal}\n" return 0 fi Println "$tip 可以输入 omit 省略此选项" inquirer text_input "请输入 ffmpeg 的 user agent: " user_agent "${d_user_agent:-$i18n_not_set}" if [ "$user_agent" == "omit" ] || [ "$user_agent" == "$i18n_not_set" ] then user_agent="" fi } SetHeaders() { if [ "${skip_set_stream_link:-false}" = true ] then Println " ffmpeg headers: ${green} ${headers:-$i18n_not_set} ${normal}\n" return 0 fi Println "$tip 多个 header 用 \\\r\\\n 分隔, 可以输入 omit 省略此选项" inquirer text_input "请输入 ffmpeg headers: " headers "${d_headers:-$i18n_not_set}" if [ "$headers" == "omit" ] || [ "$headers" == "$i18n_not_set" ] then headers="" fi while [[ $headers =~ \\\\ ]] do headers=${headers//\\\\/\\} done if [ -n "$headers" ] && [[ ! $headers =~ \\r\\n$ ]] then headers="$headers\r\n" fi } SetCookies() { if [ "${skip_set_stream_link:-false}" = true ] then Println " ffmpeg cookies: ${green} ${cookies:-$i18n_not_set} ${normal}\n" return 0 fi Println "$tip 多个 cookies 用 ; 分隔, 可以输入 omit 省略此选项" inquirer text_input "请输入 ffmpeg cookies: " cookies "${d_cookies:-$i18n_not_set}" if [ "$cookies" == "omit" ] || [ "$cookies" == "$i18n_not_set" ] then cookies="" fi } SetOutputDirName() { echo while true do inquirer text_input "请输入频道输出目录名称: " output_dir_name "$i18n_random" if [ "$output_dir_name" == "$i18n_random" ] then while :;do output_dir_name=$(RandOutputDirName) if [[ -z $($JQ_FILE '.channels[] | select(.output_dir_name=="'"$output_dir_name"'")' "$CHANNELS_FILE") ]] then Println " 目录名称: ${green} $output_dir_name ${normal}\n" break 2 fi done elif [[ -z $($JQ_FILE '.channels[] | select(.output_dir_name=="'"$output_dir_name"'")' "$CHANNELS_FILE") ]] then break else Println "$error 目录已存在!\n" fi done } SetPlaylistName() { echo inquirer text_input "请输入 m3u8 名称(前缀) : " playlist_name "${d_playlist_name:-$i18n_random}" if [ "$playlist_name" == "$i18n_random" ] then playlist_name=${d_playlist_name:-$(RandPlaylistName)} Println " m3u8 名称: ${green} $playlist_name ${normal}\n" fi } SetSegDirName() { Println "$tip 可以输入 omit 省略此选项" inquirer text_input "请输入分片所在子目录名称: " seg_dir_name "${d_seg_dir_name:-$i18n_not_set}" if [ "$seg_dir_name" == "omit" ] || [ "$seg_dir_name" == "$i18n_not_set" ] then seg_dir_name="" fi } SetSegName() { echo same_as_playlist_name=$(gettext "跟m3u8名称相同") d_seg_name=${d_seg_name:-$same_as_playlist_name} d_seg_name=${chnl_playlist_name:-$d_seg_name} inquirer text_input "请输入分片名称: " seg_name "${playlist_name:-$d_seg_name}" if [ "$seg_name" == "$same_as_playlist_name" ] then playlist_name=$($JQ_FILE -r '.channels[]|select(.pid=='"$chnl_pid"').playlist_name' "$CHANNELS_FILE") seg_name="$playlist_name" Println " 分片名称: ${green} $seg_name ${normal}\n" fi } SetSegLength() { while true do echo inquirer text_input "请输入分片时长(单位: s): " seg_length "$d_seg_length" case "$seg_length" in "") seg_length="$d_seg_length" break ;; *[!0-9]*) Println "$error $i18n_input_correct_number [>0]" ;; *) if [ "$seg_length" -ge 1 ] then break else Println "$error $i18n_input_correct_number [>0]" fi ;; esac done } SetHlsEndList() { Println "$tip 如果添加此字段, 关闭频道会延迟一个分片时长的时间" if [ "$d_hls_end_list" = true ] then inquirer list_input "添加 EXT-X-ENDLIST 字段" yn_options hls_end_list_yn else inquirer list_input "添加 EXT-X-ENDLIST 字段" ny_options hls_end_list_yn fi if [ "$hls_end_list_yn" == "$i18n_yes" ] then hls_end_list=true else hls_end_list=false fi } SetSegCount() { Println "$tip FFmpeg 分割的数目是其2倍, 如果填0就是无限" while true do inquirer text_input "请输入m3u8文件包含的分片数目: " seg_count "$d_seg_count" case "$seg_count" in "") seg_count="$d_seg_count" break ;; *[!0-9]*) Println "$error $i18n_input_correct_number [>=0]\n" ;; *) if [ "$seg_count" -ge 0 ] then break else Println "$error $i18n_input_correct_number [>=0]\n" fi ;; esac done } SetVideoCodec() { echo inquirer text_input "请输入视频编码(不需要转码时输入 copy): " video_codec "$d_video_codec" } SetAudioCodec() { echo inquirer text_input "请输入音频编码(不需要转码时输入 copy): " audio_codec "$d_audio_codec" } SetVideoAudioShift() { if [ "$d_video_audio_shift_text" == "$i18n_not_set" ] then video_audio_shift_options=( "$d_video_audio_shift_text" '设置 画面延迟' '设置 声音延迟' ) else video_audio_shift_options=( "$d_video_audio_shift_text" '设置 画面延迟' '设置 声音延迟' '不设置' ) fi while true do echo inquirer list_input_index "画面或声音延迟" video_audio_shift_options video_audio_shift_options_index case $video_audio_shift_options_index in 0) if [ "$d_video_audio_shift_text" != "$i18n_not_set" ] then if [ -n "${d_video_shift:-}" ] then video_shift="$d_video_shift" video_audio_shift="v_$video_shift" elif [ -n "${d_audio_shift:-}" ] then audio_shift="$d_audio_shift" video_audio_shift="a_$audio_shift" fi else video_audio_shift="" fi video_audio_shift_text="$d_video_audio_shift_text" break ;; 1) Println "请输入延迟时间(秒)" read -p "(默认: 返回上级选项): " video_shift if [ -n "$video_shift" ] then video_audio_shift="v_$video_shift" video_audio_shift_text="画面延迟 $video_shift 秒" break fi ;; 2) Println "请输入延迟时间(秒)" read -p "(默认: 返回上级选项): " audio_shift if [ -n "$audio_shift" ] then video_audio_shift="a_$audio_shift" video_audio_shift_text="声音延迟 $audio_shift 秒" break fi ;; 3) video_audio_shift="" video_audio_shift_text="$i18n_not_set" break ;; esac done Println " 延迟: ${green} $video_audio_shift_text ${normal}\n" } SetQuality() { Println "$tip 多个 crf 固定码率因子用逗号分隔, 取值每 +/- 6 会大概导致码率的减半或加倍\nx264 和 x265 取值范围为 [0,51]\nx264 的默认值是 23, 视觉无损值 18\nx265 的默认值是 28, 视觉无损值 24\nVP9 取值范围为 [0,63], 建议取值范围为 [15,35]" while true do inquirer text_input "请输入 crf 值: " quality "${d_quality:-$i18n_not_set}" case "$quality" in "$i18n_not_set") quality="" break ;; *[!0-9]*) Println "$error $i18n_input_correct_number [0-63]\n" ;; *) if [ "$quality" -ge 0 ] && [ "$quality" -le 63 ] then break else Println "$error $i18n_input_correct_number [0-63]\n" fi ;; esac done } SetBitrate() { Println "$tip 如果未设置 crf, 用于指定输出视频码率(ABR 或 CBR), 否则用于 VBV 的 -maxrate 和 -bufsize (capped CRF)\n多个码率(k)用逗号分隔, 比如: 800,1000,1500 可以输入 omit 省略此选项" inquirer text_input "请输入码率: " bitrate "${d_bitrate:-$i18n_not_set}" if [ "$bitrate" == "omit" ] || [ "$bitrate" == "$i18n_not_set" ] then bitrate="" fi } SetResolution() { Println "$tip 多个分辨率用逗号分隔, 比如: 960x540,1280x720 可以输入 omit 省略此选项" inquirer text_input "请输入分辨率: " resolution "${d_resolution:-$i18n_not_set}" if [ "$resolution" == "omit" ] || [ "$resolution" == "$i18n_not_set" ] then resolution="" fi } SetConst() { const_options=( "$i18n_not_set" '限制性编码(VBV)' ) if [ -z "${quality:-}" ] then const_options+=( '固定码率(CBR)' ) fi Println "$tip 限制性编码可以设定输出码率的上限" inquirer list_input_index "码率控制" const_options const_options_index if [ "$const_options_index" -eq 0 ] then const=false const_cbr=false elif [ "$const_options_index" -eq 1 ] then const=true const_cbr=false else const=true const_cbr=true fi } SetEncrypt() { echo if [ "$d_encrypt" = true ] then inquirer list_input "是否加密分片: " yn_options encrypt_yn else inquirer list_input "是否加密分片: " ny_options encrypt_yn fi if [ "$encrypt_yn" == "$i18n_yes" ] then encrypt=true OpensslInstall if [[ -x $(command -v openssl) ]] then Println "$tip 加密后只能通过网页浏览" if [ "$d_encrypt_session" = false ] then inquirer list_input "是否加密 session: " ny_options encrypt_session_text else inquirer list_input "是否加密 session: " yn_options encrypt_session_text fi if [ "$encrypt_session_text" == "$i18n_yes" ] then encrypt_session=true if [ ! -d /usr/local/nginx ] && [ ! -d /usr/local/openresty ] then echo nginx_openresty_options=( 'nginx' 'openresty' '不安装' ) inquirer list_input_index "选择安装 nginx 或 openresty, 耗时会很长: " nginx_openresty_options nginx_openresty_options_index if [ "$nginx_openresty_options_index" -eq 0 ] then nginx_prefix="/usr/local/nginx" nginx_name="nginx" nginx_ctl="nx" NGINX_FILE="$nginx_prefix/sbin/nginx" NginxInstall elif [ "$nginx_openresty_options_index" -eq 1 ] then nginx_prefix="/usr/local/openresty/nginx" nginx_name="openresty" nginx_ctl="or" NGINX_FILE="$nginx_prefix/sbin/nginx" OpenrestyInstall else encrypt_session=false encrypt_session_text="$i18n_no" fi fi if [ -d /usr/local/nginx ] || [ -d /usr/local/openresty ] then if [ -z "${nginx_name:-}" ] then if [ -d /usr/local/nginx ] && [ -d /usr/local/openresty ] then echo if [ -s "/usr/local/openresty/nginx/logs/nginx.pid" ] && kill -0 "$(< "/usr/local/openresty/nginx/logs/nginx.pid")" 2> /dev/null then nginx_openresty_options=( 'openresty' 'nginx' ) else nginx_openresty_options=( 'nginx' 'openresty' ) fi inquirer list_input "选择使用 nginx 或 openresty: " nginx_openresty_options nginx_openresty_selected if [ "$nginx_openresty_selected" == "nginx" ] then nginx_prefix="/usr/local/nginx" nginx_name="nginx" nginx_ctl="nx" else nginx_prefix="/usr/local/openresty/nginx" nginx_name="openresty" nginx_ctl="or" fi elif [ -d /usr/local/nginx ] then nginx_prefix="/usr/local/nginx" nginx_name="nginx" nginx_ctl="nx" else nginx_prefix="/usr/local/openresty/nginx" nginx_name="openresty" nginx_ctl="or" fi NGINX_FILE="$nginx_prefix/sbin/nginx" fi if [[ ! -x $(command -v node) ]] || [[ ! -x $(command -v npm) ]] then echo inquirer list_input "需安装配置 nodejs, 是否继续: " yn_options encrypt_session_text if [ "$encrypt_session_text" == "$i18n_yes" ] then NodejsInstall if [[ -x $(command -v node) ]] && [[ -x $(command -v npm) ]] then if [ ! -f "$NODE_ROOT/index.js" ] then MongodbInstall NodejsConfig fi else encrypt_session=false encrypt_session_text="$i18n_no" Println "$error nodejs 安装发生错误" Println " 加密 session: ${green} $encrypt_session_text ${normal}" fi else encrypt_session=false fi elif [ ! -f "$NODE_ROOT/index.js" ] then MongodbInstall NodejsConfig fi fi else encrypt_session=false fi fi else encrypt=false encrypt_session=false fi } SetKeyInfoName() { echo inquirer text_input "请输入 keyinfo 名称: " keyinfo_name "${d_keyinfo_name:-$i18n_random}" if [ "$keyinfo_name" == "$i18n_random" ] then keyinfo_name=$(RandStr) Println " keyinfo 名称: ${green} $keyinfo_name ${normal}\n" fi } SetKeyName() { echo inquirer text_input "请输入 keyinfo 名称: " key_name "${d_key_name:-$i18n_random}" if [ "$key_name" == "$i18n_random" ] then key_name=$(RandStr) Println " key 名称: ${green} $key_name ${normal}\n" fi } SetInputFlags() { if [ -n "${stream_link:-}" ] then if [[ $stream_link =~ \.m3u8 ]] || [ "${is_hls:-false}" = true ] then d_input_flags=${d_input_flags//-reconnect_at_eof 1/} elif [[ $stream_link =~ ^rtmp ]] || [[ $stream_link =~ ^\/ ]] then d_input_flags=${d_input_flags//-timeout 2000000000/} d_input_flags=${d_input_flags//-reconnect 1/} d_input_flags=${d_input_flags//-reconnect_at_eof 1/} d_input_flags=${d_input_flags//-reconnect_streamed 1/} d_input_flags=${d_input_flags//-reconnect_delay_max 2000/} lead=${d_input_flags%%[^[:blank:]]*} d_input_flags=${d_input_flags#${lead}} fi fi Println "$tip 可以输入 omit 省略此选项" inquirer text_input "请输入 ffmpeg 额外的输入参数: " input_flags "${d_input_flags:-$i18n_not_set}" if [ "$input_flags" == "omit" ] || [ "$input_flags" == "$i18n_not_set" ] then input_flags="" fi } SetOutputFlags() { if [ -n "${kind:-}" ] then d_output_flags=${d_output_flags//-sc_threshold 0/} fi Println "$tip 可以输入 omit 省略此选项" inquirer text_input "请输入 ffmpeg 额外的输出参数: " output_flags "${d_output_flags:-$i18n_not_set}" if [ "$output_flags" == "omit" ] || [ "$output_flags" == "$i18n_not_set" ] then output_flags="" fi } SetChannelName() { echo same_as_playlist_name=$(gettext "跟m3u8名称相同") d_channel_name=${chnl_playlist_name:-$same_as_playlist_name} inquirer text_input "请输入频道名称(可以是中文): " channel_name "${playlist_name:-$d_channel_name}" if [ "$channel_name" == "$same_as_playlist_name" ] then playlist_name=$($JQ_FILE -r '.channels[]|select(.pid=='"$chnl_pid"').playlist_name' "$CHANNELS_FILE") channel_name="$playlist_name" Println " 频道名称: ${green} $channel_name ${normal}\n" fi } SetSync() { echo if [ "$d_sync" = true ] then inquirer list_input "是否启用 sync: " yn_options sync_yn else inquirer list_input "是否启用 sync: " ny_options sync_yn fi if [[ $sync_yn == "$i18n_yes" ]] then sync=true else sync=false fi } SetSyncFile() { Println "$tip 多个文件用空格分隔, 可以输入 omit 省略此选项" inquirer text_input "设置默认 sync_file: " sync_file "${d_sync_file:-$i18n_not_set}" if [ "$sync_file" == "omit" ] || [ "$sync_file" == "${d_sync_file:-$i18n_not_set}" ] then sync_file="" fi } SetSyncIndex() { Println "$tip 多个 sync_index 用空格分隔, 可以输入 omit 省略此选项" inquirer text_input "设置默认 sync_index: " sync_index "${d_sync_index:-$i18n_not_set}" if [ "$sync_index" == "omit" ] || [ "$sync_index" == "${d_sync_index:-$i18n_not_set}" ] then sync_index="" fi } SetSyncPairs() { Println "$tip 多个 sync_pairs 用空格分隔, 可以输入 omit 省略此选项" inquirer text_input "设置默认 sync_pairs: " sync_pairs "${d_sync_pairs:-$i18n_not_set}" if [ "$sync_pairs" == "omit" ] || [ "$sync_pairs" == "${d_sync_pairs:-$i18n_not_set}" ] then sync_pairs="" fi } SetScheduleFile() { Println "$tip 可以输入 omit 省略此选项" inquirer text_input "设置节目表文件绝对路径: " schedule_file "${d_schedule_file:-$i18n_not_set}" if [ "$schedule_file" == "omit" ] || [ "$schedule_file" == "$i18n_not_set" ] then schedule_file="" fi } SetFlvDelaySeconds() { Println "设置超时多少秒自动重启频道" while read -p "(默认: $d_flv_delay_seconds 秒): " flv_delay_seconds do case $flv_delay_seconds in "") flv_delay_seconds="$d_flv_delay_seconds" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$flv_delay_seconds" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done } SetFlvRestartNums() { Println "请输入尝试重启的次数" while read -p "(默认: $d_flv_restart_nums次): " flv_restart_nums do case $flv_restart_nums in "") flv_restart_nums="$d_flv_restart_nums" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$flv_restart_nums" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done } SetHlsDelaySeconds() { Println "设置超时多少秒自动重启频道" echo -e "$tip 必须大于 分片时长*分片数目" while read -p "(默认: $d_hls_delay_seconds 秒): " hls_delay_seconds do case $hls_delay_seconds in "") hls_delay_seconds="$d_hls_delay_seconds" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$hls_delay_seconds" -gt 60 ] then break else Println "$error $i18n_input_correct_number [>60]\n" fi ;; esac done } SetHlsMinBitrate() { Println "请输入最低码率(k),低于此数值会重启频道" while read -p "(默认: $d_hls_min_bitrate): " hls_min_bitrate do case $hls_min_bitrate in "") hls_min_bitrate="$d_hls_min_bitrate" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$hls_min_bitrate" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done } SetHlsMaxSegSize() { Println "请输入允许的最大分片" while read -p "(默认: ${d_hls_max_seg_size}M): " hls_max_seg_size do case $hls_max_seg_size in "") hls_max_seg_size="$d_hls_max_seg_size" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$hls_max_seg_size" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done } SetHlsRestartNums() { Println "请输入尝试重启的次数" while read -p "(默认: $d_hls_restart_nums次): " hls_restart_nums do case $hls_restart_nums in "") hls_restart_nums="$d_hls_restart_nums" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$hls_restart_nums" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done } SetHlsKeyPeriod() { echo inquirer text_input "每隔多少秒更改加密频道的 key: " hls_key_period $d_hls_key_period } SetAntiDDosPort() { Println "设置封禁端口" echo -e "$tip 多个端口用空格分隔 比如 22 80 443 12480-12489" while read -p "(默认: $d_anti_ddos_port_text): " anti_ddos_ports do anti_ddos_ports=${anti_ddos_ports:-$d_anti_ddos_port_text} if [ -z "$anti_ddos_ports" ] then Println "$error $i18n_input_correct_number\n" continue fi IFS=" " read -ra anti_ddos_ports_arr <<< "$anti_ddos_ports" error_no=0 for anti_ddos_port in "${anti_ddos_ports_arr[@]}" do case "$anti_ddos_port" in *"-"*) anti_ddos_ports_start=${anti_ddos_port%-*} anti_ddos_ports_end=${anti_ddos_port#*-} if [[ $anti_ddos_ports_start == *[!0-9]* ]] || [[ $anti_ddos_ports_end == *[!0-9]* ]] || [ "$anti_ddos_ports_start" -eq 0 ] || [ "$anti_ddos_ports_end" -eq 0 ] || [ "$anti_ddos_ports_start" -ge "$anti_ddos_ports_end" ] then error_no=3 break fi ;; *[!0-9]*) error_no=1 break ;; *) if [ "$anti_ddos_port" -lt 1 ] then error_no=2 break fi ;; esac done case "$error_no" in 1|2|3) Println "$error $i18n_input_correct_number\n" ;; *) anti_ddos_ports_command="" anti_ddos_ports_range_command="" for anti_ddos_port in "${anti_ddos_ports_arr[@]}" do if [[ $anti_ddos_port -eq 80 ]] then anti_ddos_port="http" elif [[ $anti_ddos_port -eq 443 ]] then anti_ddos_port="https" elif [[ $anti_ddos_port -eq 22 ]] then anti_ddos_port="ssh" elif [[ $anti_ddos_port =~ - ]] then anti_ddos_ports_start=${anti_ddos_port%-*} anti_ddos_ports_end=${anti_ddos_port#*-} if [[ anti_ddos_ports_start -le 22 && $anti_ddos_ports_end -ge 22 ]] then [ -n "$anti_ddos_ports_command" ] && anti_ddos_ports_command="$anti_ddos_ports_command|" anti_ddos_ports_command="${anti_ddos_ports_command}ssh" elif [[ anti_ddos_ports_start -le 80 && $anti_ddos_ports_end -ge 80 ]] then [ -n "$anti_ddos_ports_command" ] && anti_ddos_ports_command="$anti_ddos_ports_command|" anti_ddos_ports_command="${anti_ddos_ports_command}http" elif [[ anti_ddos_ports_start -le 443 && $anti_ddos_ports_end -ge 443 ]] then [ -n "$anti_ddos_ports_command" ] && anti_ddos_ports_command="$anti_ddos_ports_command|" anti_ddos_ports_command="${anti_ddos_ports_command}https" fi [ -n "$anti_ddos_ports_range_command" ] && anti_ddos_ports_range_command="$anti_ddos_ports_range_command || " anti_ddos_ports_range_command=$anti_ddos_ports_range_command'($4 >= '"$anti_ddos_ports_start"' && $4 <= '"$anti_ddos_ports_end"')' continue fi [ -n "$anti_ddos_ports_command" ] && anti_ddos_ports_command="$anti_ddos_ports_command|" anti_ddos_ports_command="$anti_ddos_ports_command$anti_ddos_port" done [ -n "$anti_ddos_ports_command" ] && anti_ddos_ports_command='$4 ~ /^('"$anti_ddos_ports_command"')$/' if [ -n "$anti_ddos_ports_range_command" ] then anti_ddos_ports_range_command='$4 ~ /^[0-9]+$/ && ('"$anti_ddos_ports_range_command"')' [ -n "$anti_ddos_ports_command" ] && anti_ddos_ports_range_command=' || ('"$anti_ddos_ports_range_command"')' fi if [[ $anti_ddos_ports == *" "* ]] || [[ $anti_ddos_ports =~ - ]] then anti_ddos_port=${anti_ddos_ports// /,} anti_ddos_port=${anti_ddos_port//-/:} anti_ddos_port="$anti_ddos_port proto tcp" else anti_ddos_port=$anti_ddos_ports fi break ;; esac done } SetAntiDDosSynFlood() { echo if [ "$d_anti_ddos_syn_flood" = true ] then inquirer list_input "是否开启 SYN Flood attack 防御" yn_options anti_ddos_syn_flood_yn else inquirer list_input "是否开启 SYN Flood attack 防御" ny_options anti_ddos_syn_flood_yn fi if [ "$anti_ddos_syn_flood_yn" == "$i18n_yes" ] then anti_ddos_syn_flood=true sysctl -w net.ipv4.tcp_syn_retries=6 > /dev/null sysctl -w net.ipv4.tcp_synack_retries=2 > /dev/null sysctl -w net.ipv4.tcp_syncookies=1 > /dev/null sysctl -w net.ipv4.tcp_max_syn_backlog=1024 > /dev/null #iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT --limit 1/s Println "设置判断为 SYN Flood attack 的时间 (秒)" while read -p "(默认: $d_anti_ddos_syn_flood_delay_seconds 秒): " anti_ddos_syn_flood_delay_seconds do case $anti_ddos_syn_flood_delay_seconds in "") anti_ddos_syn_flood_delay_seconds="$d_anti_ddos_syn_flood_delay_seconds" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$anti_ddos_syn_flood_delay_seconds" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done Println "设置封禁 SYN Flood attack ip 多少秒" while read -p "(默认: $d_anti_ddos_syn_flood_seconds 秒): " anti_ddos_syn_flood_seconds do case $anti_ddos_syn_flood_seconds in "") anti_ddos_syn_flood_seconds="$d_anti_ddos_syn_flood_seconds" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$anti_ddos_syn_flood_seconds" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done else anti_ddos_syn_flood=false fi } SetAntiDDos() { echo if [ "$d_anti_ddos" = true ] then inquirer list_input "是否开启 iptv 防御" yn_options anti_ddos_yn else inquirer list_input "是否开启 iptv 防御" ny_options anti_ddos_yn fi if [ "$anti_ddos_yn" == "$i18n_yes" ] then anti_ddos=true Println "设置封禁用户 ip 多少秒" while read -p "(默认: $d_anti_ddos_seconds 秒): " anti_ddos_seconds do case $anti_ddos_seconds in "") anti_ddos_seconds="$d_anti_ddos_seconds" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$anti_ddos_seconds" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done Println "设置封禁等级(1-9)" echo -e "$tip 数值越低越严格, 也越容易误伤, 很多情况是网络问题导致重复请求并非 DDoS\n" while read -p "(默认: $d_anti_ddos_level): " anti_ddos_level do case $anti_ddos_level in "") anti_ddos_level="$d_anti_ddos_level" break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$anti_ddos_level" -gt 0 ] && [ "$anti_ddos_level" -lt 10 ] then break else Println "$error $i18n_input_correct_number [1-9]\n" fi ;; esac done else anti_ddos=false fi } SetAntiLeech() { echo if [ "$d_anti_leech" = true ] then inquirer list_input "是否开启防盗链" yn_options anti_leech_yn else inquirer list_input "是否开启防盗链" ny_options anti_leech_yn fi if [ "$anti_leech_yn" == "$i18n_yes" ] then anti_leech=true Println "请输入每小时随机重启次数 (大于等于0)" while read -p "(默认: $d_anti_leech_restart_nums): " anti_leech_restart_nums do case $anti_leech_restart_nums in "") anti_leech_restart_nums="$d_anti_leech_restart_nums" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$anti_leech_restart_nums" -ge 0 ] then break else Println "$error $i18n_input_correct_number [>=0]\n" fi ;; esac done if [ "$anti_leech_restart_nums" -gt 0 ] then echo inquirer list_input "是否下个小时开始随机重启" ny_options anti_leech_restart_next_hour_yn if [ "$anti_leech_restart_next_hour_yn" == "$i18n_yes" ] then printf -v current_hour '%(%-H)T' -1 skip_hour=$current_hour minutes=() fi fi echo if [ "$d_anti_leech_restart_flv_changes" = true ] then inquirer list_input "是否每当重启 FLV 频道更改成随机的推流和拉流地址" yn_options anti_leech_restart_flv_changes_yn else inquirer list_input "是否每当重启 FLV 频道更改成随机的推流和拉流地址" ny_options anti_leech_restart_flv_changes_yn fi if [ "$anti_leech_restart_flv_changes_yn" == "$i18n_yes" ] then anti_leech_restart_flv_changes=true else anti_leech_restart_flv_changes=false fi echo if [ "$d_anti_leech_restart_hls_changes" = true ] then inquirer list_input "是否每当重启 HLS 频道更改成随机的 m3u8 名称, 分片名称, key 名称" yn_options anti_leech_restart_hls_changes_yn else inquirer list_input "是否每当重启 HLS 频道更改成随机的 m3u8 名称, 分片名称, key 名称" ny_options anti_leech_restart_hls_changes_yn fi if [ "$anti_leech_restart_hls_changes_yn" == "$i18n_yes" ] then anti_leech_restart_hls_changes=true else anti_leech_restart_hls_changes=false fi SetHlsKeyPeriod hls_key_expire_seconds=$((hls_key_period+hls_delay_seconds)) else anti_leech=false anti_leech_restart_nums="$d_anti_leech_restart_nums" anti_leech_restart_flv_changes="$d_anti_leech_restart_flv_changes" anti_leech_restart_hls_changes="$d_anti_leech_restart_hls_changes" fi } SetRecheckPeriod() { Println "设置重启频道失败后定时检查直播源(如可用即开启频道)的间隔时间(秒)" echo -e "$tip 输入 0 关闭检查" while read -p "(默认: $d_recheck_period_text): " recheck_period do case $recheck_period in "") recheck_period="$d_recheck_period" && break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$recheck_period" -ge 0 ] then break else Println "$error $i18n_input_correct_number [>=0]\n" fi ;; esac done } SetFlvH265() { echo inquirer list_input_index "是否推流 h265" ny_options ny_options_index if [ "$ny_options_index" -eq 0 ] then flv_h265=false else flv_h265=true if [[ ! -x $(command -v ffmpeg_c) ]] then echo ffmpeg_c_options=( '快速安装' '编译 ffmpeg (耗时非常非常久)' ) inquirer list_input_index "选择 ffmpeg (h265版本) 安装方式" ffmpeg_c_options ffmpeg_c_options_index if [ "$ffmpeg_c_options_index" -eq 0 ] then if curl -L "$FFMPEG_MIRROR_LINK/ffmpeg_c" -o /usr/local/bin/ffmpeg_c then chmod +x /usr/local/bin/ffmpeg_c else Println "$error 暂时无法连接服务器, 请稍后再试 !\n" exit 1 fi else FFmpegCompile fi fi fi } SetFlvPushLink() { Println "$tip 比如 rtmp://127.0.0.1/flv/xxx , 如指向本机请确保已经安装 nginx 或 openresty" while true do inquirer text_input "请输入推流地址: " flv_push_link "本地随机地址" if [ "$flv_push_link" == "本地随机地址" ] then flv_push_link="rtmp://127.0.0.1/flv/$(RandStr)" until [[ -z $($JQ_FILE '.channels[] | select(.flv_push_link=="'"$flv_push_link"'")' "$CHANNELS_FILE") ]] do flv_push_link=$(RandStr) done Println " 推流地址: ${green} $flv_push_link ${normal}\n" break elif [[ -z $($JQ_FILE '.channels[] | select(.flv_push_link=="'"$flv_push_link"'")' "$CHANNELS_FILE") ]] then break else Println "$error 推流地址已存在!请重新输入\n" fi done } SetFlvPullLink() { if [ -n "${flv_push_link:-}" ] then d_flv_pull_link="http://127.0.0.1/flv?app=flv&stream=${flv_push_link##*/}" elif [ -n "${chnl_flv_push_link:-}" ] then d_flv_pull_link="http://127.0.0.1/flv?app=flv&stream=${chnl_flv_push_link##*/}" fi Println "$tip 比如 http://domain.com/flv?app=flv&stream=xxx 监控会验证此链接来确定是否重启频道, 如果不确定可以先留空, 可以输入 omit 省略此选项" inquirer text_input "请输入拉流(播放)地址: " flv_pull_link "${d_flv_pull_link:-$i18n_not_set}" if [ "$flv_pull_link" == "omit" ] || [ "$flv_pull_link" == "$i18n_not_set" ] then flv_pull_link="" fi } AddChannel() { [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 use_primary_playlist=false GetDefault SetProxy SetUserAgent SetHeaders SetCookies SetStreamLink SetLive xc_proxy=${xc_proxy:-} if [[ $stream_link =~ ^http://([^/]+) ]] then XtreamCodesGetDomains for xc_domain in "${xtream_codes_domains[@]}" do if [ "$xc_domain" == "${BASH_REMATCH[1]}" ] then SetXtreamCodesProxy break fi done fi SetVideoCodec SetAudioCodec SetVideoAudioShift quality="" bitrate="" const=false const_cbr=false if [ "$video_codec" != "copy" ] then SetQuality SetBitrate fi SetResolution if [ -n "$bitrate" ] then SetConst fi draw_text="" if [ "${kind:-}" == "flv" ] then if [ "$video_codec" != "copy" ] then SetDrawtext fi SetFlvH265 SetFlvPushLink SetFlvPullLink output_dir_name=$(RandOutputDirName) playlist_name=$(RandPlaylistName) seg_dir_name="$d_seg_dir_name" seg_name="$playlist_name" seg_length="$d_seg_length" seg_count="$d_seg_count" hls_end_list=false encrypt=false encrypt_session=false keyinfo_name=$(RandStr) key_name=$(RandStr) txt_format="" else SetSubtitle if [ "$video_codec" != "copy" ] then SetDrawtext fi flv_h265=false flv_push_link="" flv_pull_link="" SetOutputDirName SetPlaylistName SetSegDirName SetSegName SetSegLength if [ -n "$live" ] then SetSegCount else seg_count="$d_seg_count" fi SetHlsEndList SetEncrypt if [ "$encrypt" = true ] then SetKeyInfoName SetKeyName else keyinfo_name=$(RandStr) key_name=$(RandStr) fi fi output_dir_root="$LIVE_ROOT/$output_dir_name" if [ -n "$seg_dir_name" ] then seg_dir_path="$seg_dir_name/" else seg_dir_path="" fi if [ -n "$txt_format" ] then subtitle_append=',SUBTITLES="subs"' else subtitle_append="" fi master=0 if [ "${stream_url_qualities_count:-0}" -gt 0 ] then if [[ $bitrate =~ , ]] || [[ $quality =~ , ]] || [[ $resolution =~ , ]] || [ "${stream_url_qualities_count:-0}" -gt 1 ] || [ "${stream_url_audio_count:-0}" -gt 0 ] || [ "${stream_url_subtitles_count:-0}" -gt 0 ] then master=1 fi elif [[ $bitrate =~ , ]] || [[ $quality =~ , ]] || [[ $resolution =~ , ]] || [ -n "$subtitle_append" ] then master=1 fi SetInputFlags SetOutputFlags SetChannelName SetSync sync_file="" sync_index="" sync_pairs="" if [ "$sync" = true ] then SetSyncFile SetSyncIndex SetSyncPairs fi if [ -z "${FFMPEG:-}" ] then FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFMPEG="$FFMPEG_ROOT/ffmpeg" FFPROBE="$FFMPEG_ROOT/ffprobe" fi if [[ ${input_flags:0:1} == "'" ]] then input_flags=${input_flags%\'} input_flags=${input_flags#\'} fi if [[ ${output_flags:0:1} == "'" ]] then output_flags=${output_flags%\'} output_flags=${output_flags#\'} fi [ ! -e $FFMPEG_LOG_ROOT ] && mkdir $FFMPEG_LOG_ROOT extra_filters="" if [ "$video_codec" != "copy" ] && [ -n "$draw_text" ] then filters=( vf filter:v ) for filter in "${filters[@]}" do if [[ $output_flags =~ (.*)"-$filter "([^ ]+)(.*) ]] then extra_filters="${BASH_REMATCH[2]}," output_flags="${BASH_REMATCH[1]} ${BASH_REMATCH[3]}" fi done fi flags_command=( -flags ) if [[ $output_flags =~ (.*)"-flags "([^ ]+)(.*) ]] then flags="${BASH_REMATCH[2]}" if [[ $flags =~ global_header ]] then flags_command=( -flags "$flags" ) else flags_command+=("-global_header$flags") fi output_flags="${BASH_REMATCH[1]} ${BASH_REMATCH[3]}" else flags_command+=(-global_header) fi FilterString input_flags output_flags from="AddChannel" if [ -n "${kind:-}" ] then if [ "$kind" == "flv" ] then if [ "$sh_debug" -eq 1 ] then ( FlvStreamCreator ) else ( FlvStreamCreator ) > /dev/null 2> /dev/null < /dev/null & fi else Println "$error 暂不支持输出 $kind ...\n" && exit 1 fi else if [ "$sh_debug" -eq 1 ] then ( HlsStreamCreatorPlus ) else ( HlsStreamCreatorPlus ) > /dev/null 2> /dev/null < /dev/null & fi fi stream_audio_url=() stream_subtitles_url=() stream_url_qualities_count=0 stream_url_audio_count=0 stream_url_subtitles_count=0 stream_url_quality="" stream_url_cdn="" skip_set_stream_link=false xc_proxy="" Println "$info 频道添加成功 !\n" } EditStreamLink() { chnl_stream_links_list="" chnl_stream_links_options=() for((list_i=0;list_i /dev/null) || true if [ -z "$access_token" ] then to_try=1 Println "$error $chnl_domain $chnl_mac" else chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" printf -v chnl_cookies_command '%b' "${chnl_cookies//;/; path=\/;\\r\\n}; path=/;" profile=$(CurlFake xtream_codes -s -Lm 10\ -H "$chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$profile_url" | $JQ_FILE -r '.js.id // ""' 2> /dev/null) || true exp_date=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$account_info_url" | $JQ_FILE -r '.js.phone' 2> /dev/null) || true if [ -z "$exp_date" ] then to_try=1 if [ -z "$profile" ] then Println "$error $chnl_mac profile" else Println "$error $chnl_mac exp_date" fi fi fi if [ "$to_try" -eq 1 ] then to_try=1 try_success=0 MonitorTryAccounts if [ "$try_success" -eq 0 ] then Println "$error 没有可用账号" fi else if [ -n "$chnl_xc_proxy" ] then server=${chnl_xc_proxy%\/} IFS=" " read -r chnl_stream_link new_access_token new_cookies < <(CurlFake xtream_codes -sL "$server/?cmd=$chnl_cmd&check=1" \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" | $JQ_FILE -r '.|join(" ")' 2> /dev/null) || true if [[ ! $chnl_stream_link =~ ([^/]+)//([^/]+)/(.+) ]] then Println "$error $chnl_domain $chnl_mac $chnl_xc_proxy $chnl_stream_link\n" && exit 1 fi access_token="$new_access_token" chnl_cookies="$new_cookies" if [[ ${BASH_REMATCH[1]} =~ [a-z] ]] then chnl_stream_link="$server/?cmd=$chnl_cmd" chnl_headers="" chnl_headers_command="" else chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" fi else create_link_url="$server/portal.php?type=itv&action=create_link&cmd=$chnl_cmd&series=&forced_storage=undefined&disable_ad=0&download=0" cmd=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$create_link_url" \ | $JQ_FILE -r '.js.cmd') || true if [[ ${cmd#* } =~ ([^/]+)//([^/]+)/(.+) ]] then chnl_stream_link="http://localhost:3000/$(XtreamCodesDomainFilter ${BASH_REMATCH[2]})/${BASH_REMATCH[3]}" else Println "$error $chnl_domain 返回 cmd: ${cmd:-无} $chnl_domain $chnl_mac\n" && exit 1 fi fi chnl_stream_links[0]="$chnl_domain|$chnl_stream_link|$chnl_cmd|$chnl_mac" Println "$info 跳过检测频道 [ $chnl_channel_name ] 直接开启 ? [y/N]" read -t 3 -p "(默认: 3 秒后自动检测): " skip_check_yn || true && echo skip_check_yn=${skip_check_yn:-N} if [[ $skip_check_yn == [Yy] ]] then return 0 fi audio=0 video=0 while IFS= read -r line do if [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 elif [[ $line == *"codec_type=video"* ]] then video=1 fi done < <($FFPROBE $chnl_proxy_command -user_agent "$chnl_user_agent" -headers "$chnl_headers_command" -cookies "$chnl_cookies_command" -i "$chnl_stream_link" -rw_timeout 10000000 -show_streams -loglevel quiet) if [ "$audio" -eq 0 ] || [ "$video" -eq 0 ] then to_try=1 try_success=0 MonitorTryAccounts if [ "$try_success" -eq 0 ] then Println "$error 没有可用账号" fi fi fi fi elif [[ $chnl_stream_link =~ ^http://([^/]+)/([^/]+)/([^/]+)/ ]] then chnl_domain=${BASH_REMATCH[1]} for xc_domain in ${xtream_codes_domains[@]+"${xtream_codes_domains[@]}"} do if [ "$xc_domain" == "$chnl_domain" ] then Println "$info 频道 [ $chnl_channel_name ] 检测账号中..." to_try=1 break fi done xc_chnl_found=0 if [ "$to_try" -eq 1 ] then if [ "${BASH_REMATCH[2]}" == "live" ] && [[ $chnl_stream_link =~ ^http://([^/]+)/live/([^/]+)/([^/]+)/ ]] then chnl_account="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}" else chnl_account="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}" fi XtreamCodesGetChnls for xc_chnl in ${xc_chnls[@]+"${xc_chnls[@]}"} do if [ "$xc_chnl" == "$chnl_domain/$chnl_account" ] then xc_chnl_found=1 break fi done fi if [ "$xc_chnl_found" -eq 1 ] then to_try=1 try_success=0 MonitorTryAccounts if [ "$try_success" -eq 0 ] then Println "$error 没有可用账号" fi elif [ "$to_try" -eq 1 ] then printf -v chnl_headers_command '%b' "$chnl_headers" printf -v chnl_cookies_command '%b' "${chnl_cookies//;/; path=\/;\\r\\n}; path=/;" audio=0 video=0 while IFS= read -r line do if [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 elif [[ $line == *"codec_type=video"* ]] then video=1 fi done < <($FFPROBE $chnl_proxy_command -user_agent "$chnl_user_agent" -headers "$chnl_headers_command" -cookies "$chnl_cookies_command" -i "$chnl_stream_link" -rw_timeout 10000000 -show_streams -loglevel quiet) if [ "$audio" -eq 0 ] || [ "$video" -eq 0 ] then try_success=0 MonitorTryAccounts if [ "$try_success" -eq 0 ] then Println "$error 没有可用账号" fi else to_try=0 fi fi fi } ToggleChannel() { ListChannels InputChannelsIndex for chnl_pid in "${chnls_pid_chosen[@]}" do GetChannel if { [ -z "${kind:-}" ] && [ "$chnl_status" == "off" ]; } || { [ "${kind:-}" == "flv" ] && [ "$chnl_flv_status" == "off" ]; } then if [ -d "$chnl_output_dir_root" ] then StopChannel fi CheckIfXtreamCodes if [ "$to_try" -eq 1 ] then continue fi StartChannel else StopChannel fi done } StartChannel() { chnl_use_primary_playlist=false chnl_use_cdn=false hboasia_host="hbogoasia.com:8443" hboasia_cdn_host="dai3fd1oh325y.cloudfront.net" if [[ $chnl_stream_link =~ ^https://(www\.)?(youtube.com|twitch.tv) ]] then YoutubeDlInstall YtDlpInstall chnl_user_agent="$USER_AGENT_BROWSER" chnl_headers="" chnl_cookies="" Println "`eval_gettext \"\\\$info youtube-dl 解析链接...\"`" if [[ $chnl_stream_link =~ ^(.+)\|(.+)$ ]] then chnl_stream_link=${BASH_REMATCH[1]} code=${BASH_REMATCH[2]} elif [ "$monitor" = true ] then return 0 else unset code fi link="$chnl_stream_link" YoutubeParse yt-dlp if [ -z "${formats_indices:-}" ] then YoutubeParse youtube-dl if [ -z "${formats_indices:-}" ] then Println "$error [ $chnl_channel_name ] 解析发生错误, 直播链接不存在?\n" MonitorErr "$chnl_channel_name 解析发生错误, 直播链接不存在?" return 0 fi fi chnl_stream_links[0]="$chnl_stream_link|$code" if [[ $code =~ , ]] then chnl_stream_urls_resolution=("${formats_resolution[@]}") chnl_stream_urls_bitrate=("${formats_bitrate[@]}") chnl_stream_url_video_indices=("${formats_indices[@]}") chnl_stream_url_qualities_count=${#formats_indices[@]} chnl_stream_urls_audio=() chnl_stream_urls_subtitles=() chnl_stream_url_qualities=() for chnl_stream_urls_index in "${chnl_stream_url_video_indices[@]}" do chnl_stream_urls_audio[chnl_stream_urls_index]="" chnl_stream_urls_subtitles[chnl_stream_urls_index]="" chnl_stream_url_qualities+=("${chnl_stream_urls_bitrate[chnl_stream_urls_index]}-${chnl_stream_urls_resolution[chnl_stream_urls_index]}") done if ! parse_urls=($(yt-dlp --no-warnings -f "$code" -g "$chnl_stream_link")) && ! parse_urls=($(youtube-dl -f "$code" -g "$chnl_stream_link")) then Println "$error [ $chnl_channel_name ] 解析 $code 发生错误" MonitorErr "$chnl_channel_name 解析 $code 发生错误" return 0 fi chnl_stream_urls=() for((s_i=0;s_i /dev/null) chnl="${chnl_stream_link%\?*}" chnl=${chnl##*/} token_url=$(curl -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ "https://api.news.tvb.com/news/v2.2.1/live?profile=web" \ | $JQ_FILE -r '.items[]|select(.path=="'"$chnl"'" or .path=="'"${chnl#*_}"'" or .path=="'"${chnl%_*}"'").video.ios[]|select(.type=="hd").url') if [ -z "$token_url" ] then Println "$error 无法解析 tvb: token url\n" return 0 fi query_string="$token_url&feed&client_ip=$(GetServerIp)" query_string=$(UrlencodeUpper "$query_string") chnl_stream_link=$(curl -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" \ "https://news.tvb.com/ajax_call/getVideo.php?token=$query_string" \ | $JQ_FILE -r '.url') if [ "$chnl_stream_link" == null ] then Println "$error 无法解析 tvb: url\n" return 0 fi while IFS= read -r line do if [[ $line =~ hdntl= ]] then line=${line#* } chnl_cookies="$chnl_cookies ${line%% *}" break fi done < <(curl -s -I -H "User-Agent: $chnl_user_agent" -H "${chnl_headers:0:-4}" --cookie "$chnl_cookies" "$chnl_stream_link" 2> /dev/null) chnl_stream_link="${chnl_stream_link}|$code" fi chnl_stream_link_url="${chnl_stream_link%%|*}" chnl_stream_link_url_path="${chnl_stream_link_url%\?*}" chnl_stream_link_url_path="${chnl_stream_link_url_path%/*}" chnl_stream_link_url_path_cdn="$chnl_stream_link_url_path" if [[ $chnl_stream_link_url_path =~ $hboasia_host/(.+)$ ]] then if [ "$chnl_use_cdn" = true ] then chnl_stream_link_url_path_cdn="https://$hboasia_cdn_host/${BASH_REMATCH[1]}" fi #chnl_headers="range: \r\n" if [[ ! $chnl_output_flags =~ -seekable ]] then chnl_output_flags="$chnl_output_flags -seekable 0" fi if [[ ! $chnl_output_flags =~ -vsync ]] then chnl_output_flags="$chnl_output_flags -vsync 0" fi fi chnl_stream_audio_group_id=() chnl_stream_audio_name=() chnl_stream_audio_default=() chnl_stream_audio_language=() chnl_stream_audio_url=() chnl_stream_subtitles_group_id=() chnl_stream_subtitles_name=() chnl_stream_subtitles_default=() chnl_stream_subtitles_language=() chnl_stream_subtitles_url=() chnl_stream_urls=() chnl_stream_urls_bitrate=() chnl_stream_urls_resolution=() chnl_stream_urls_audio=() chnl_stream_urls_subtitles=() chnl_stream_urls_list="" chnl_stream_urls_count=0 while IFS= read -r line do if [[ $line =~ TYPE=AUDIO ]] then IFS="," read -r -a chnl_stream_audio <<< "${line#*:}" chnl_stream_audio_keys=(${chnl_stream_audio[*]%%=*}) chnl_stream_audio_values=(${chnl_stream_audio[*]#*=}) for((i=0;i<${#chnl_stream_audio_keys[@]};i++)); do if [ "${chnl_stream_audio_keys[i]}" == "GROUP-ID" ] then chnl_stream_audio_group_id+=("${chnl_stream_audio_values[i]//\"/}") elif [ "${chnl_stream_audio_keys[i]}" == "NAME" ] then chnl_stream_audio_name+=("${chnl_stream_audio_values[i]//\"/}") elif [ "${chnl_stream_audio_keys[i]}" == "DEFAULT" ] then chnl_stream_audio_default+=("${chnl_stream_audio_values[i]}") elif [ "${chnl_stream_audio_keys[i]}" == "LANGUAGE" ] then chnl_stream_audio_language+=("${chnl_stream_audio_values[i]//\"/}") elif [ "${chnl_stream_audio_keys[i]}" == "URI" ] then chnl_stream_audio_uri=${chnl_stream_audio_values[i]//\"/} if [[ $chnl_stream_audio_uri =~ ^https?:// ]] then chnl_stream_audio_url+=("$chnl_stream_audio_uri") else chnl_stream_audio_url+=("$chnl_stream_link_url_path_cdn/$chnl_stream_audio_uri") fi fi done elif [[ $line =~ TYPE=SUBTITLES ]] then IFS="," read -r -a chnl_stream_subtitles <<< "${line#*:}" chnl_stream_subtitles_keys=(${chnl_stream_subtitles[*]%%=*}) chnl_stream_subtitles_values=(${chnl_stream_subtitles[*]#*=}) for((i=0;i<${#chnl_stream_subtitles_keys[@]};i++)); do if [ "${chnl_stream_subtitles_keys[i]}" == "GROUP-ID" ] then chnl_stream_subtitles_group_id+=("${chnl_stream_subtitles_values[i]//\"/}") elif [ "${chnl_stream_subtitles_keys[i]}" == "NAME" ] then chnl_stream_subtitles_name+=("${chnl_stream_subtitles_values[i]//\"/}") elif [ "${chnl_stream_subtitles_keys[i]}" == "DEFAULT" ] then chnl_stream_subtitles_default+=("${chnl_stream_subtitles_values[i]}") elif [ "${chnl_stream_subtitles_keys[i]}" == "LANGUAGE" ] then chnl_stream_subtitles_language+=("${chnl_stream_subtitles_values[i]//\"/}") elif [ "${chnl_stream_subtitles_keys[i]}" == "URI" ] then chnl_stream_subtitles_uri=${chnl_stream_subtitles_values[i]//\"/} if [[ $chnl_stream_subtitles_uri =~ ^https?:// ]] then chnl_stream_subtitles_url+=("$chnl_stream_subtitles_uri") else chnl_stream_subtitles_url+=("$chnl_stream_link_url_path_cdn/$chnl_stream_subtitles_uri") fi fi done elif [[ $line =~ RESOLUTION=([^ ]+) ]] then chnl_stream_url_resolution=${BASH_REMATCH[1]%%,*} chnl_stream_urls_resolution+=("$chnl_stream_url_resolution") if [[ $line =~ BANDWIDTH=([^ ]+) ]] then chnl_stream_url_bitrate=${BASH_REMATCH[1]%%,*} chnl_stream_url_bitrate=$((chnl_stream_url_bitrate/1000)) else chnl_stream_url_bitrate="" fi chnl_stream_urls_bitrate+=("$chnl_stream_url_bitrate") if [ -n "$chnl_stream_url_bitrate" ] then chnl_stream_url_bitrate_text=" [ $chnl_stream_url_bitrate k ]" else chnl_stream_url_bitrate_text="" fi if [[ $line =~ AUDIO=([^ ]+) ]] then chnl_stream_url_audio=${BASH_REMATCH[1]%%,*} chnl_stream_url_audio=${chnl_stream_url_audio#\"} chnl_stream_url_audio=${chnl_stream_url_audio%\"} else chnl_stream_url_audio="" fi chnl_stream_urls_audio+=("$chnl_stream_url_audio") if [[ $line =~ SUBTITLES=([^ ]+) ]] then chnl_stream_url_subtitles=${BASH_REMATCH[1]%%,*} chnl_stream_url_subtitles=${chnl_stream_url_subtitles#\"} chnl_stream_url_subtitles=${chnl_stream_url_subtitles%\"} else chnl_stream_url_subtitles="" fi chnl_stream_urls_subtitles+=("$chnl_stream_url_subtitles") chnl_stream_urls_count=$((chnl_stream_urls_count+1)) chnl_stream_urls_list="$chnl_stream_urls_list ${green}$chnl_stream_urls_count.${normal}${indent_6}$chnl_stream_url_resolution$chnl_stream_url_bitrate_text $chnl_stream_url_audio $chnl_stream_url_subtitles\n\n" elif [[ $line =~ \.m3u8 ]] then if [[ $line =~ ^https?:// ]] then chnl_stream_urls+=("$line") else chnl_stream_urls+=("$chnl_stream_link_url_path_cdn/$line") fi fi done < <(curl -s -Lm 20 -H "User-Agent: $chnl_user_agent" "$chnl_stream_link_url") if [ -n "$chnl_stream_urls_list" ] then if [ "$chnl_use_cdn" = true ] && [[ $chnl_stream_link_url =~ $hboasia_host/(.+)$ ]] then chnl_stream_url_cdn="https://$hboasia_cdn_host/${BASH_REMATCH[1]}?${chnl_stream_urls[0]#*\?}" fi chnl_stream_url_qualities_count=0 if [[ $chnl_stream_link =~ \|([^|]+)$ ]] then choose=0 chnl_stream_url_quality=${BASH_REMATCH[1]} chnl_stream_audio_group_id_allow=() chnl_stream_audio_name_allow=() chnl_stream_subtitles_group_id_allow=() chnl_stream_subtitles_name_allow=() IFS="," read -r -a chnl_stream_url_qualities <<< "$chnl_stream_url_quality" chnl_stream_url_qualities_count=${#chnl_stream_url_qualities[@]} if [[ $chnl_stream_link =~ \|ag:([^|]+) ]] then chnl_stream_audio_group_id_allow_list=${BASH_REMATCH[1]} IFS="," read -ra chnl_stream_audio_group_id_allow <<< "$chnl_stream_audio_group_id_allow_list" fi if [[ $chnl_stream_link =~ \|a:([^|]+) ]] then chnl_stream_audio_name_allow_list=${BASH_REMATCH[1]} IFS="," read -ra chnl_stream_audio_name_allow <<< "$chnl_stream_audio_name_allow_list" fi if [[ $chnl_stream_link =~ \|sg:([^|]+) ]] then chnl_stream_subtitles_group_id_allow_list=${BASH_REMATCH[1]} IFS="," read -ra chnl_stream_subtitles_group_id_allow <<< "$chnl_stream_subtitles_group_id_allow_list" fi if [[ $chnl_stream_link =~ \|s:([^|]+) ]] then chnl_stream_subtitles_name_allow_list=${BASH_REMATCH[1]} IFS="," read -ra chnl_stream_subtitles_name_allow <<< "$chnl_stream_subtitles_name_allow_list" fi if [ "$monitor" = true ] then auto_select=true else auto_select=false fi choose_asked=false quality_unset=false chnl_stream_url_video_indices=() for((i=0;i /dev/null) chnl="${chnl_stream_link%\?*}" chnl=${chnl##*/} token_url=$(curl -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ "https://api.news.tvb.com/news/v2.2.1/live?profile=web" \ | $JQ_FILE -r '.items[]|select(.path=="'"$chnl"'" or .path=="'"${chnl#*_}"'" or .path=="'"${chnl%_*}"'").video.ios[]|select(.type=="hd").url') if [ -z "$token_url" ] then Println "$error 无法解析 tvb: token url\n" return 0 fi query_string="$token_url&feed&client_ip=$(GetServerIp)" query_string=$(UrlencodeUpper "$query_string") chnl_stream_link=$(curl -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" \ "https://news.tvb.com/ajax_call/getVideo.php?token=$query_string" \ | $JQ_FILE -r '.url') if [ "$chnl_stream_link" == null ] then Println "$error 无法解析 tvb: url\n" return 0 fi while IFS= read -r line do if [[ $line =~ hdntl= ]] then line=${line#* } chnl_cookies="$chnl_cookies ${line%% *}" break fi done < <(curl -s -I -H "User-Agent: $chnl_user_agent" -H "${chnl_headers:0:-4}" --cookie "$chnl_cookies" "$chnl_stream_link" 2> /dev/null) elif [[ $chnl_stream_link == http://*.macaulotustv.com/* ]] then chnl_user_agent="$USER_AGENT_BROWSER" chnl_headers="Origin: http://www.lotustv.cc\r\nReferer: http://www.lotustv.cc/\r\n" chnl_cookies="" elif [ "${chnl_stream_link:0:4}" == "rtmp" ] || [ "${chnl_stream_link:0:1}" == "/" ] then chnl_input_flags=${chnl_input_flags//-timeout 2000000000/} chnl_input_flags=${chnl_input_flags//-reconnect 1/} chnl_input_flags=${chnl_input_flags//-reconnect_at_eof 1/} chnl_input_flags=${chnl_input_flags//-reconnect_streamed 1/} chnl_input_flags=${chnl_input_flags//-reconnect_delay_max 2000/} lead=${chnl_input_flags%%[^[:blank:]]*} chnl_input_flags=${chnl_input_flags#${lead}} elif [[ $chnl_stream_link == *"pngquant.com"* ]] then chnl_headers="x-forwarded-for: 127.0.0.1\r\n" if [[ ! $chnl_output_flags =~ -vsync ]] then chnl_output_flags="$chnl_output_flags -vsync 0" fi if [[ ! $chnl_output_flags =~ -copyts ]] then chnl_output_flags="$chnl_output_flags -copyts" fi fi if [ "$chnl_use_cdn" = true ] then hboasia_host="$hboasia_cdn_host" fi if [[ $chnl_stream_link == *".m3u8"* ]] then chnl_input_flags=${chnl_input_flags//-reconnect_at_eof 1/} fi if [ -n "$chnl_txt_format" ] then chnl_subtitle_append=',SUBTITLES="subs"' else chnl_subtitle_append="" fi master=0 if [ "${chnl_stream_url_qualities_count:-0}" -gt 0 ] then if [[ $chnl_bitrate =~ , ]] || [[ $chnl_quality =~ , ]] || [[ $chnl_resolution =~ , ]] || [ "${chnl_stream_url_qualities_count:-0}" -gt 1 ] || [ "${chnl_stream_url_audio_count:-0}" -gt 0 ] || [ "${chnl_stream_url_subtitles_count:-0}" -gt 0 ] then master=1 fi elif [[ $chnl_bitrate =~ , ]] || [[ $chnl_quality =~ , ]] || [[ $chnl_resolution =~ , ]] || [ -n "$chnl_subtitle_append" ] then master=1 fi if [ "$chnl_video_codec" == "copy" ] then chnl_quality="" chnl_bitrate="" chnl_const=false fi if [ -n "${chnl_txt_format:-}" ] && [ -z "${kind:-}" ] then if [ -z "$chnl_input_flags" ] then chnl_input_flags="-txt_format $chnl_txt_format -fix_sub_duration" #else #@if [[ ! $chnl_input_flags =~ -fix_sub_duration ]] #then # chnl_input_flags="-fix_sub_duration $chnl_input_flags" #fi #if [[ ! $chnl_input_flags =~ -txt_format ]] #then # chnl_input_flags="-txt_format $chnl_txt_format $chnl_input_flags" #fi fi else chnl_txt_format="" chnl_input_flags=${chnl_input_flags//-txt_format bitmap/} chnl_input_flags=${chnl_input_flags//-txt_format text/} chnl_input_flags=${chnl_input_flags//-txt_format ass/} chnl_input_flags=${chnl_input_flags//-fix_sub_duration/} fi if [ -z "${FFMPEG:-}" ] then FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFMPEG="$FFMPEG_ROOT/ffmpeg" FFPROBE="$FFMPEG_ROOT/ffprobe" fi if [[ ${chnl_input_flags:0:1} == "'" ]] then chnl_input_flags=${chnl_input_flags%\'} chnl_input_flags=${chnl_input_flags#\'} fi if [[ ${chnl_output_flags:0:1} == "'" ]] then chnl_output_flags=${chnl_output_flags%\'} chnl_output_flags=${chnl_output_flags#\'} fi [ ! -e $FFMPEG_LOG_ROOT ] && mkdir $FFMPEG_LOG_ROOT printf -v start_time '%(%s)T' -1 chnl_channel_time=$start_time chnl_extra_filters="" if [ "$chnl_video_codec" != "copy" ] && [ -n "$chnl_draw_text" ] then filters=( vf filter:v ) for filter in "${filters[@]}" do if [[ $chnl_output_flags =~ (.*)"-$filter "([^ ]+)(.*) ]] then chnl_extra_filters="${BASH_REMATCH[2]}," chnl_output_flags="${BASH_REMATCH[1]} ${BASH_REMATCH[3]}" fi done fi chnl_flags_command=( -flags ) if [[ $chnl_output_flags =~ (.*)"-flags "([^ ]+)(.*) ]] then chnl_flags="${BASH_REMATCH[2]}" if [[ $chnl_flags =~ global_header ]] then chnl_flags_command=( -flags "$chnl_flags" ) else chnl_flags_command+=("-global_header$chnl_flags") fi chnl_output_flags="${BASH_REMATCH[1]} ${BASH_REMATCH[3]}" else chnl_flags_command+=(-global_header) fi FilterString chnl_input_flags chnl_output_flags from="StartChannel" if [ -n "${kind:-}" ] then if [ "$chnl_status" == "on" ] then Println "$error HLS 频道正开启, 走错片场了?\n" && exit 1 fi if [ "$chnl_flv_h265" = true ] then if [[ ! -x $(command -v ffmpeg_c) ]] then if [ "$monitor" = false ] then echo ffmpeg_c_options=( '快速安装' '编译 ffmpeg (耗时非常非常久)' ) inquirer list_input_index "选择 ffmpeg (h265版本) 安装方式" ffmpeg_c_options ffmpeg_c_options_index if [ "$ffmpeg_c_options_index" -eq 0 ] then if curl -L "$FFMPEG_MIRROR_LINK/ffmpeg_c" -o /usr/local/bin/ffmpeg_c then chmod +x /usr/local/bin/ffmpeg_c else Println "$error 暂时无法连接服务器, 请稍后再试 !\n" exit 1 fi else FFmpegCompile fi else chnl_flv_h265=false fi fi fi chnl_output_flags=${chnl_output_flags//-sc_threshold 0/} if [ "$kind" == "flv" ] then rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.log" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.err" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.pid" if [ "$sh_debug" -eq 1 ] then ( FlvStreamCreator ) else ( FlvStreamCreator ) > /dev/null 2> /dev/null < /dev/null & fi else Println "$error 暂不支持输出 $kind ...\n" && exit 1 fi else if [ "$chnl_flv_status" == "on" ] then Println "$error FLV 频道正开启, 走错片场了?\n" && exit 1 fi rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.log" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.err" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.pid" if [ "$sh_debug" -eq 1 ] then ( HlsStreamCreatorPlus ) else ( HlsStreamCreatorPlus ) > /dev/null 2> /dev/null < /dev/null & fi fi chnl_stream_audio_url=() chnl_stream_subtitles_url=() chnl_stream_url_qualities_count=0 chnl_stream_url_audio_count=0 chnl_stream_url_subtitles_count=0 chnl_stream_url_quality="" chnl_stream_url_cdn="" chnl_xc_proxy="" Println "$info 频道 [ $chnl_channel_name ] 已开启 !\n" } StopChannel() { if [ -n "${kind:-}" ] then if [ "$kind" != "flv" ] then Println "$error 暂不支持 $kind ...\n" && exit 1 elif [ "$chnl_status" == "on" ] then Println "$error HLS 频道正开启, 走错片场了?\n" && exit 1 fi elif [ "$chnl_flv_status" == "on" ] then Println "$error FLV 频道正开启, 走错片场了?\n" && exit 1 fi Println "$info 关闭频道 [ $chnl_channel_name ] 请稍等..." if [ "${kind:-}" == "flv" ] then if ! kill -0 "$chnl_pid" 2> /dev/null then MonitorLog "频道 [ $chnl_channel_name ] 进程 $chnl_pid 不存在" jq_path='["channels"]' jq_path2='["flv_status"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" off MonitorLog "$chnl_channel_name FLV 关闭" action="stop" SyncFile rm -rf "$FFMPEG_LOG_ROOT/$chnl_pid.pid" else kill "$chnl_pid" 2> /dev/null || true if ! flock -E 1 -w 30 -x "$FFMPEG_LOG_ROOT/$chnl_pid.pid" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.pid" then MonitorLog "频道 [ $chnl_channel_name ] 进程 $chnl_pid 不存在" jq_path='["channels"]' jq_path2='["flv_status"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" off MonitorLog "$chnl_channel_name FLV 关闭" action="stop" SyncFile fi fi chnl_flv_status="off" else if ! kill -0 "$chnl_pid" 2> /dev/null then MonitorLog "频道 [ $chnl_channel_name ] 进程 $chnl_pid 不存在" jq_path='["channels"]' jq_path2='["status"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" off MonitorLog "$chnl_channel_name HLS 关闭" action="stop" SyncFile rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.pid" else kill "$chnl_pid" 2> /dev/null || true if ! flock -E 1 -w $((30+chnl_seg_length)) -x "$FFMPEG_LOG_ROOT/$chnl_pid.pid" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.pid" then if [ "$chnl_hls_end_list" = true ] && ls -A "$chnl_output_dir_root/"*.m3u8 > /dev/null 2>&1 then for play_list in "$chnl_output_dir_root/"*.m3u8 do echo "#EXT-X-ENDLIST" >> "$play_list" || true done sleep "$chnl_seg_length" fi MonitorLog "频道 [ $chnl_channel_name ] 进程 $chnl_pid 不存在" jq_path='["channels"]' jq_path2='["status"]' JQ update "$CHANNELS_FILE" pid "$chnl_pid" off MonitorLog "$chnl_channel_name HLS 关闭" action="stop" SyncFile fi fi rm -rf "$chnl_output_dir_root" chnl_status="off" fi Println "$info 频道 [ $chnl_channel_name ] 已关闭 !\n" } StopChannelsForce() { pkill -9 -f ffmpeg 2> /dev/null || true pkill -f 'tv m' 2> /dev/null || true rm -rf "$CHANNELS_FILE.lockdir" GetChannels GetDefault for((i=0;i /dev/null if [ "${chnls_live[i]}" = true ] then rm -rf "$LIVE_ROOT/${chnls_output_dir_name[i]}" fi done Println "$info 全部频道已关闭 !\n" } RestartChannel() { ListChannels InputChannelsIndex for chnl_pid in "${chnls_pid_chosen[@]}" do GetChannel if [ "$chnl_status" == "on" ] || [ "$chnl_flv_status" == "on" ] || [ -d "$chnl_output_dir_root" ] then StopChannel fi CheckIfXtreamCodes if [ "$to_try" -eq 1 ] then continue fi StartChannel done } ViewChannelLog() { ListChannels InputChannelsIndex for chnl_pid in "${chnls_pid_chosen[@]}" do ListChannel Println "${green}输出日志:${normal}\n" if [ -s "$FFMPEG_LOG_ROOT/$chnl_pid.log" ] then tail -n 10 "$FFMPEG_LOG_ROOT/$chnl_pid.log" else echo "无" fi Println "${red}错误日志:${normal}\n" if [ -s "$FFMPEG_LOG_ROOT/$chnl_pid.err" ] then cat "$FFMPEG_LOG_ROOT/$chnl_pid.err" else echo "无" fi echo done } DelChannel() { ListChannels InputChannelsIndex for chnl_pid in "${chnls_pid_chosen[@]}" do GetChannel if [ "${kind:-}" == "flv" ] then if [ "$chnl_flv_status" == "on" ] then StopChannel fi elif [ "$chnl_status" == "on" ] then StopChannel fi jq_path='["channels"]' JQ delete "$CHANNELS_FILE" pid "$chnl_pid" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.log" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.err" rm -f "$FFMPEG_LOG_ROOT/$chnl_pid.pid" Println "$info 频道 [ $chnl_channel_name ] 删除成功 !\n" done } EditDefault() { jq_path='["default","'"$1"'"]' if [ -n "${2:-}" ] then JQ update "$CHANNELS_FILE" "$2" else JQ update "$CHANNELS_FILE" "${!1}" fi Println "$info $1 修改成功\n" } ListChannelsSchedule() { GetChannels if [ "$chnls_count" -eq 0 ] then Println "`eval_gettext \"\\\$error 没有发现频道, 请检查 !\"`\n" && exit 1 fi chnls_indices=("${!chnls_pid[@]}") chnls_schedule_list="" chnls_schedule_indices=() for chnls_index in "${chnls_indices[@]}" do if [ -z "${chnls_schedule_status[chnls_index]}" ] then continue fi chnls_schedule_indices+=("$chnls_index") chnls_schedule_list="$chnls_schedule_list ${green}$((chnls_index+1)).${normal}${indent_6}${dim_underlined}${chnls_channel_name[chnls_index]} [${chnls_output_dir_name[chnls_index]}] ${normal}\n\n" IFS="${delimiters[1]}" read -ra chnl_schedules_start_time <<< "${chnls_schedule_start_time[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_end_time <<< "${chnls_schedule_end_time[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_loop <<< "${chnls_schedule_loop[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_auto_remove <<< "${chnls_schedule_auto_remove[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_hls_change <<< "${chnls_schedule_hls_change[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_hls_change_once <<< "${chnls_schedule_hls_change_once[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_status <<< "${chnls_schedule_status[chnls_index]}" chnl_schedules_if_null="${chnls_schedule_hls_change[chnls_index]//false/}" chnl_schedules_if_null="${chnl_schedules_if_null//true/}" IFS="${delimiters[1]}" read -ra chnl_schedules_channel_name <<< "${chnls_schedule_channel_name[chnls_index]:-$chnl_schedules_if_null}${delimiters[1]}" chnl_schedules_indices=("${!chnl_schedules_status[@]}") for chnl_schedules_index in "${chnl_schedules_indices[@]}" do if [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 0 ] then chnl_schedule_status_list="${green}等待${normal}" elif [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 1 ] then chnl_schedule_status_list="${blue}进行${normal}" else chnl_schedule_status_list="${red}结束${normal}" fi if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then chnl_schedule_hls_change_list="${green}一次${normal}" elif [ "${chnl_schedules_hls_change[chnl_schedules_index]}" = true ] then chnl_schedule_hls_change_list="${green}是${normal}" else chnl_schedule_hls_change_list="${red}否${normal}" fi if [ "${chnl_schedules_loop[chnl_schedules_index]}" = true ] then chnl_schedule_loop_list="${green}是${normal}" else chnl_schedule_loop_list="${red}否${normal}" fi if [ "${chnl_schedules_auto_remove[chnl_schedules_index]}" = true ] then chnl_schedule_auto_remove_list="${green}是${normal}" else chnl_schedule_auto_remove_list="${red}否${normal}" fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then chnl_schedule_channel_name_list="${indent_6}频道名称: ${blue}${chnl_schedules_channel_name[chnl_schedules_index]}${normal}\n" else chnl_schedule_channel_name_list="" fi chnls_schedule_list="$chnls_schedule_list${indent_6}状态: $chnl_schedule_status_list${indent_20}防盗链: $chnl_schedule_hls_change_list\n${indent_6}循环: $chnl_schedule_loop_list${indent_20}自动清除: $chnl_schedule_auto_remove_list\n$chnl_schedule_channel_name_list${indent_6}开始时间: $(date +%c --date=@"${chnl_schedules_start_time[chnl_schedules_index]}")\n${indent_6}结束时间: $(date +%c --date=@"${chnl_schedules_end_time[chnl_schedules_index]}")\n\n" done done if [ -n "$chnls_schedule_list" ] then Println "$chnls_schedule_list" fi } ListChannelSchedules() { chnl_schedules_list="${indent_6}${dim_underlined}${chnls_channel_name[chnls_index]} [${chnls_output_dir_name[chnls_index]}]${normal}\n\n" chnl_schedules_count=0 if [ -n "${chnls_schedule_status[chnls_index]}" ] then IFS="${delimiters[1]}" read -ra chnl_schedules_start_time <<< "${chnls_schedule_start_time[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_end_time <<< "${chnls_schedule_end_time[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_loop <<< "${chnls_schedule_loop[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_auto_remove <<< "${chnls_schedule_auto_remove[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_hls_change <<< "${chnls_schedule_hls_change[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_hls_change_once <<< "${chnls_schedule_hls_change_once[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_status <<< "${chnls_schedule_status[chnls_index]}" chnl_schedules_if_null="${chnls_schedule_hls_change[chnls_index]//false/}" chnl_schedules_if_null="${chnl_schedules_if_null//true/}" IFS="${delimiters[1]}" read -ra chnl_schedules_channel_name <<< "${chnls_schedule_channel_name[chnls_index]:-$chnl_schedules_if_null}${delimiters[1]}" chnl_schedules_count=${#chnl_schedules_status[@]} chnl_schedules_indices=("${!chnl_schedules_status[@]}") for chnl_schedules_index in "${chnl_schedules_indices[@]}" do if [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 0 ] then chnl_schedule_status_list="${green}等待${normal}" elif [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 1 ] then chnl_schedule_status_list="${blue}进行${normal}" else chnl_schedule_status_list="${red}结束${normal}" fi if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then chnl_schedule_hls_change_list="${green}一次${normal}" elif [ "${chnl_schedules_hls_change[chnl_schedules_index]}" = true ] then chnl_schedule_hls_change_list="${green}是${normal}" else chnl_schedule_hls_change_list="${red}否${normal}" fi if [ "${chnl_schedules_loop[chnl_schedules_index]}" = true ] then chnl_schedule_loop_list="${green}是${normal}" else chnl_schedule_loop_list="${red}否${normal}" fi if [ "${chnl_schedules_auto_remove[chnl_schedules_index]}" = true ] then chnl_schedule_auto_remove_list="${green}是${normal}" else chnl_schedule_auto_remove_list="${red}否${normal}" fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then chnl_schedule_channel_name_list="${indent_6}频道名称: ${blue}${chnl_schedules_channel_name[chnl_schedules_index]}${normal}\n" else chnl_schedule_channel_name_list="" fi chnl_schedules_list="$chnl_schedules_list ${green}$((chnl_schedules_index+1)).${normal}${indent_6}状态: $chnl_schedule_status_list${indent_20}防盗链: $chnl_schedule_hls_change_list\n${indent_6}循环: $chnl_schedule_loop_list${indent_20}自动清除: $chnl_schedule_auto_remove_list\n$chnl_schedule_channel_name_list${indent_6}开始时间: $(date +%c --date=@"${chnl_schedules_start_time[chnl_schedules_index]}")\n${indent_6}结束时间: $(date +%c --date=@"${chnl_schedules_end_time[chnl_schedules_index]}")\n\n" done fi Println "$chnl_schedules_list" } ScheduleParse() { schedule=$($JQ_FILE 'unique_by(.start_time)' <<< "$schedule") [ -z "${delimiters:-}" ] && delimiters=( $'\001' $'\002' $'\003' $'\004' $'\005' $'\006' ) IFS=$'\003\t' read -r m_title m_time m_start_time m_end_time < <(JQs flat "$schedule" '' ' . as $schedles | reduce ({title,time,start_time,end_time}|keys_unsorted[]) as $key ([]; $schedles[$key] as $val | if $val then . + [$val + "\u0002\u0003"] else . + ["\u0003"] end )|@tsv' "${delimiters[@]}") IFS="${delimiters[1]}" read -ra ${chnl_id}_title <<< "$m_title" IFS="${delimiters[1]}" read -ra ${chnl_id}_time <<< "$m_time" IFS="${delimiters[1]}" read -ra ${chnl_id}_start_time <<< "$m_start_time" IFS="${delimiters[1]}" read -ra ${chnl_id}_end_time <<< "$m_end_time" schedule_title=("${chnl_id}_title"[@]) schedule_title=("${!schedule_title}") schedule_time=("${chnl_id}_time"[@]) schedule_time=("${!schedule_time}") schedule_start_time=("${chnl_id}_start_time"[@]) schedule_start_time=("${!schedule_start_time}") schedule_end_time=("${chnl_id}_end_time"[@]) schedule_end_time=("${!schedule_end_time}") schedule_indices=("${!schedule_title[@]}") for schedule_index in "${schedule_indices[@]}" do if [ "$((${schedule_end_time[schedule_index]}-${schedule_start_time[schedule_index]}))" -lt 5400 ] then continue fi schedules_chnl_id+=("$chnl_id") schedules_start_time+=("${schedule_start_time[schedule_index]}") schedules_end_time+=("${schedule_end_time[schedule_index]}") schedules_title+=("${schedule_title[schedule_index]}") schedules_list+=("${schedule_title[schedule_index]} ${green}[$chnl_id]${normal} ${blue}[${schedule_time[schedule_index]}]${normal}") done } SearchSoccerSchedules() { astro_chnls=( "astrobeinsports:236:beIN Sports HD" "astrobeinsportsmax:313:beIN Sports MAX HD" "astrosupersport:154:Astro SuperSport HD" "astrosupersport2:138:Astro SuperSport 2 HD" "astrosupersport3:164:Astro SuperSport 3 HD" "astrosupersport4:241:Astro SuperSport 4 HD" "astrosupersport5:455:Astro SuperSport 5 HD" ) nbcsn_chnls=( "nbcsn:NBCSN:NBCSN" "nbcsnhd:NBCSNHD:NBCSN HD" ) beinsports_chnls=( "beinsports:1:beinsports" "beinsportsenglish1:17:beinsports english 1" "beinsportsenglish2:18:beinsports english 2" "beinsportsenglish3:19:beinsports english 3" ) beinsportsau_chnls=( "beinsports1au:BEINSP1:beinsports 1 AU" "beinsports2au:BEINSP2:beinsports 2 AU" "beinsports3au:BEINSP3:beinsports 3 AU" ) supersport_chnls=( "supersportpremierleague:SS Premier League:SuperSport Premier League" "supersportfootball:SS Football:SuperSport Football" "supersportlaliga:SS La Liga:SuperSport La Liga" "supersportvariety1:SS Variety 1:SuperSport Variety 1" "supersportvariety2:SS Variety 2:SuperSport Variety 2" "supersportvariety3:SS Variety 3:SuperSport Variety 3" "supersportvariety4:SS Variety 4:SuperSport Variety 4" "supersportgrandstand:SS Grandstand:SuperSport Grandstand" ) btsport_chnls=( #"btsportespn:BT Sport//ESPN" "btsportultimate:hspr:BT Sport Ultimate" "btsport1:hspc:BT Sport 1" "btsport2:hspd:BT Sport 2" "btsport3:hspf:BT Sport 3" "btsport4:hspg:BT Sport 4" ) sky_chnls=( "skysportspremierleague:1303:SkySp PL" "skysportsmainevent:1301:SkySpMainEv" "skysportsfootball:3838:SkySp F'ball" "premiersports1:5153:Premier 1 HD" "premiersports2:1634:Premier 2 HD" "laligatv:1015:LaLigaTV HD" ) printf -v today '%(%Y-%m-%d)T' -1 today_time=$(date -d "today 0" +%s) printf -v sys_time '%(%s)T' -1 min_start_time=$((sys_time-5400)) if [ "$search_options_index" -eq 0 ] then max_start_time=$((sys_time+43200)) if [ "$sys_time" -gt $((today_time+43200)) ] then search_options_index=2 fi elif [ "$search_options_index" -eq 1 ] then max_start_time=$(date -d "today 23:59:59" +%s) else max_start_time=$(date -d "tomorrow 23:59:59" +%s) fi yesterday=$(date -d "yesterday" +"%Y-%m-%d") tomorrow=$(date -d "tomorrow" +"%Y-%m-%d") schedules_chnl_id=() schedules_start_time=() schedules_end_time=() schedules_title=() schedules_list=() for chnl in "${astro_chnls[@]}" do chnl_id=${chnl%%:*} astro_id=${chnl#*:} chnl_name=${astro_id#*:} astro_id=${astro_id%%:*} today_schedule=$(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" "https://contenthub-api.eco.astro.com.my/channel/$astro_id.json" | $JQ_FILE --arg today "$today" --arg min "$min_start_time" --arg max "$max_start_time" --argjson keys '["title","time","start_time","end_time"]' '.response.schedule[$today] | map(select(.title|test("\\(L\\).* vs ";"i")) | .["start_time"] = (.datetimeInUtc|sub("(?.*) (?*} title=${title%%*} title=${title##*>} title=${title//\"/} title=${title//\'/} title=${title//\\/\'} sys_time=$(date -d "$today $start_time" +%s) start_time_num=$sys_time end_time_num=$(date -d "$today $end_time" +%s) if [ "$check" -eq 1 ] && [ "$start_time_num" -gt "$end_time_num" ] then continue fi check=0 [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$title"'", "time":"'"$start_time"'", "sys_time":"'"$sys_time"'" }' done done < <(curl ${niotv_proxy[@]+"${niotv_proxy[@]}"} -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" --data "act=select&sch_id=$niotv_id&day=$today" "$SCHEDULE_LINK_NIOTV") if [ "$empty" -eq 1 ] then Println "$error $chnl_name niotv [$chnl_id] 节目表更新失败" continue fi json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name niotv [$chnl_id] 节目表更新成功" done } ScheduleIcable() { printf -v today '%(%Y%m%d)T' -1 sys_time=$(date --date="today 0" +"%s") yesterday=$(date --date="yesterday" +"%Y%m%d") if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "hkopen" > "$SCHEDULE_JSON" fi for chnl in "${icable_chnls[@]}" do if [[ $chnl =~ ([^:]+):([^:]+):([^:]+) ]] then chnl_id=${BASH_REMATCH[1]} chnl_num=${BASH_REMATCH[2]} chnl_name=${BASH_REMATCH[3]} fi schedule="" while IFS= read -r line do if [[ $line =~ ch_time ]] then while [[ $line =~ ch_time ]] do line=${line#*ch_time } if [ "${line%%_*}" == "nm" ] then program_time=${line#*f_eng\">} program_time=${program_time%%<*} program_title=${line#*ch_prog\">} program_title=${program_title%%<*} program_sys_time=$(date -d "$today $program_time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"${program_title//\"/}"'", "time":"'"$program_time"'AM", "sys_time":"'"$program_sys_time"'" }' fi done break fi done < <(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" "http://epg.i-cable.com/new/ch_getcontent.php?lang=chi&ch=$chnl_num&date=$yesterday" 2> /dev/null) while IFS= read -r line do if [[ $line =~ ch_time ]] then while [[ $line =~ ch_time ]] do line=${line#*ch_time } if [ "${line%%_*}" == "am" ] then time_flag="AM" elif [ "${line%%_*}" == "pm" ] then time_flag="PM" else break fi program_time=${line#*f_eng\">} program_time="${program_time%%<*}$time_flag" program_title=${line#*ch_prog\">} program_title=${program_title%%<*} program_sys_time=$(date -d "$today $program_time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"${program_title//\"/}"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' done break fi done < <(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" "http://epg.i-cable.com/new/ch_getcontent.php?lang=chi&ch=$chnl_num&date=$today" 2> /dev/null) if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] i-cable 节目表更新成功" else Println "$error $chnl_name [$chnl_id] i-cable 节目表更新失败" fi done } ScheduleJiushi() { if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "tvbfc" > "$SCHEDULE_JSON" fi for chnl in "${jiushi_chnls[@]}" do chnl_id=${chnl%%:*} chnl_name=${chnl#*:} chnl_name=${chnl_name// /-} chnl_name_encode=$(UrlencodeUpper "$chnl_name") printf -v today '%(%Y-%m-%d)T' -1 SCHEDULE_LINK="https://xn--i0yt6h0rn.tw/channel/$chnl_name_encode/index.json" schedule="" while IFS="=" read -r program_time program_title do program_time=${program_time#\"} program_title=${program_title%\"} program_sys_time=$(date -d "$today $program_time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "$SCHEDULE_LINK" | $JQ_FILE '.list[] | select(.key=="'"$today"'").values[] | [.time,.name] | join("=")') if [ -z "$schedule" ] then today=${today//-/\/} while IFS="=" read -r program_time program_title do program_time=${program_time#\"} program_title=${program_title%\"} program_sys_time=$(date -d "$today $program_time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "$SCHEDULE_LINK" | $JQ_FILE '.list[] | select(.key=="'"$today"'").values[] | [.time,.name] | join("=")') if [ -z "$schedule" ] then Println "$error $chnl_name [$chnl_id] 就是节目表更新失败" continue fi fi json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] 就是节目表更新成功" done } ScheduleHboasia() { printf -v today '%(%Y-%m-%d)T' -1 if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "hbo" > "$SCHEDULE_JSON" fi for chnl in "${hboasia_chnls[@]}" do chnl_id=${chnl%%:*} chnl_name=${chnl#*:} hbo_id=${chnl_name%:*} chnl_name=${chnl_name#*:} schedule="" while IFS="^" read -r program_time program_title program_classification do program_time=${program_time#\"} program_classification=${program_classification%\"} if [ -n "$program_classification" ] then program_title="$program_title($program_classification)" fi program_sys_time=$(date -d "$program_time" +%s) new_program_time=$(printf '%(%H:%M)T' "$program_sys_time") [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "id":"'"$hbo_id"'", "title":"'"$program_title"'", "time":"'"$new_program_time"'", "sys_time":"'"$program_sys_time"'" }' done < <(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" \ -d "channel=$hbo_id" \ -d "date=$today" \ https://hboasia.com/ajax/getschedule.php | $JQ_FILE '.[] | [.publictime,.title,.classification] | join("^")') if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] hbo亚洲 节目表更新成功" else Println "$error $chnl_name [$chnl_id] hbo亚洲 节目表更新失败" fi done } ScheduleHbous() { printf -v today '%(%Y-%m-%d)T' -1 sys_time=$(date -d $today +%s) min_sys_time=$((sys_time-7200)) max_sys_time=$((sys_time+86400)) yesterday=$(date --date="yesterday" +"%Y-%m-%d") if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "us_hbo" > "$SCHEDULE_JSON" fi if [ "${1:-}" == "WEST" ] || [ "${1:-}" == "west" ] then zone="WEST" else zone="EAST" fi for chnl in "${hbous_chnls[@]}" do chnl_id=${chnl%%:*} chnl=${chnl#*:} hbous_name=${chnl%%:*} chnl_zone=${chnl#*:} chnl_name=${chnl_zone#*:} chnl_zone=${chnl_zone%%:*} if [ "$zone" != "$chnl_zone" ] then continue fi schedule="" while IFS="=" read -r program_time program_title do program_time=${program_time#\"} program_title=${program_title%\"} program_sys_time=$(date -d "$program_time" +%s) if [ "$program_sys_time" -ge "$min_sys_time" ] then program_time=$(printf '%(%H:%M)T' "$program_sys_time") [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' fi done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "https://proxy-v4.cms.hbo.com/v1/schedule?date=$yesterday" | $JQ_FILE --arg channelName "$hbous_name" --arg channelZone "$chnl_zone" '.channels | to_entries | map(select(.value.channelName==$channelName and .value.channelZone==$channelZone))[].value.programAirings | to_entries | map("\(.value.airing.playDate)=\(.value.program.title)")[]') min_sys_time=${program_sys_time:-$sys_time} while IFS="=" read -r program_time program_title do program_time=${program_time#\"} program_title=${program_title%\"} program_sys_time=$(date -d "$program_time" +%s) if [ "$program_sys_time" -le "$max_sys_time" ] && [ "$program_sys_time" -gt "$min_sys_time" ] then program_time=$(printf '%(%H:%M)T' "$program_sys_time") [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' fi done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "https://proxy-v4.cms.hbo.com/v1/schedule?date=$today" | $JQ_FILE --arg channelName "$hbous_name" --arg channelZone "$chnl_zone" '.channels | to_entries | map(select(.value.channelName==$channelName and .value.channelZone==$channelZone))[].value.programAirings | to_entries | map("\(.value.airing.playDate)=\(.value.program.title)")[]') if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] hbous 节目表更新成功" else Println "$error $chnl_name [$chnl_id] hbous 节目表更新失败" fi done } ScheduleOntvtonight() { printf -v today '%(%Y-%m-%d)T' -1 sys_time=$(date -d $today +%s) min_sys_time=$((sys_time-7200)) max_sys_time=$((sys_time+86400)) yesterday=$(date --date="yesterday" +"%Y-%m-%d") if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "us_abc" > "$SCHEDULE_JSON" fi for chnl in "${ontvtonight_chnls[@]}" do IFS="@" read -r chnl_id chnl_name chnl_no chnl_zone <<< "$chnl" chnl_zone=${chnl_zone%:*} schedule="" start=0 if [ "${chnl_id%_*}" == "us" ] || [ "${chnl_id%_*}" == "foxnews" ] then ct="" else ct="${chnl_id%_*}/" fi while IFS= read -r line do if [[ $line == *""* ]] then start=1 elif [ "$start" -eq 1 ] && [[ $line == *""* ]] then line=${line#*>} program_time=${line%<*} new_program_time=${program_time% *} hour=${new_program_time%:*} if [ "${program_time#* }" == "pm" ] && [ "$hour" -lt 12 ] then hour=$((hour+12)) new_program_time="$hour:${new_program_time#*:}" elif [ "${program_time#* }" == "am" ] && [ "$hour" -eq 12 ] then new_program_time="00:${new_program_time#*:}" fi elif [ "$start" -eq 1 ] && [[ $line == *""* ]] then line=${line%%<\/a>*} lead=${line%%[^[:blank:]]*} program_title=${line#${lead}} program_title=${program_title//amp;/} program_title=${program_title//'/\'} program_sys_time=$(date -d "${yesterday}T$new_program_time$chnl_zone" +%s) if [ "$program_sys_time" -ge "$min_sys_time" ] then program_time=$(printf '%(%H:%M)T' "$program_sys_time") [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' fi elif [ "$start" -eq 1 ] && [[ $line == *""* ]] then break fi done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "https://www.ontvtonight.com/${ct}guide/listings/channel/$chnl_no/$chnl_name.html?dt=$yesterday" 2> /dev/null) while IFS= read -r line do if [[ $line == *""* ]] then start=1 elif [ "$start" -eq 1 ] && [[ $line == *""* ]] then line=${line#*>} program_time=${line%<*} new_program_time=${program_time% *} hour=${new_program_time%:*} if [ "${program_time#* }" == "pm" ] && [ "$hour" -lt 12 ] then hour=$((hour+12)) new_program_time="$hour:${new_program_time#*:}" elif [ "${program_time#* }" == "am" ] && [ "$hour" -eq 12 ] then new_program_time="00:${new_program_time#*:}" fi elif [ "$start" -eq 1 ] && [[ $line == *""* ]] then line=${line%%<\/a>*} lead=${line%%[^[:blank:]]*} program_title=${line#${lead}} program_title=${program_title//amp;/} program_title=${program_title//'/\'} program_sys_time=$(date -d "${today}T$new_program_time$chnl_zone" +%s) if [ "$program_sys_time" -le "$max_sys_time" ] then program_time=$(printf '%(%H:%M)T' "$program_sys_time") [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' fi elif [ "$start" -eq 1 ] && [[ $line == *""* ]] then break fi done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "https://www.ontvtonight.com/${ct}guide/listings/channel/$chnl_no/$chnl_name.html?dt=$today" 2> /dev/null) if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] ontvtonight 节目表更新成功" else Println "$error $chnl_name [$chnl_id] ontvtonight 节目表更新失败" fi done } ScheduleDisneyjr() { printf -v today '%(%Y%m%d)T' -1 SCHEDULE_LINK="https://disney.com.tw/_schedule/full/$today/8/%2Fepg" if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "disneyjr" > "$SCHEDULE_JSON" fi schedule="" while IFS= read -r program do program_title=${program#*show_title: } program_title=${program_title%%, time: *} program_time=${program#*, time: } program_time=${program_time%%, iso8601_utc_time: *} program_sys_time=${program#*, iso8601_utc_time: } program_sys_time=${program_sys_time%\"} program_sys_time=$(date -d "$program_sys_time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "$SCHEDULE_LINK" | $JQ_FILE '.schedule | to_entries | map(.value.schedule_items[]) | to_entries | map("show_title: \(.value.show_title), time: \(.value.time), iso8601_utc_time: \(.value.iso8601_utc_time)")[]') if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] 节目表更新成功" else Println "$error $chnl_name [$chnl_id] 节目表更新失败" fi } ScheduleFoxmovies() { printf -v today '%(%Y-%-m-%-d)T' -1 SCHEDULE_LINK="https://www.fng.tw/foxmovies/program.php?go=$today" if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "foxmovies" > "$SCHEDULE_JSON" fi schedule="" while IFS= read -r line do if [[ $line == *""* ]] then line=${line#*} line=${line%%<\/td>*} if [[ $line == *"
"* ]] then line=${line%%
*} line=${line//\"/} line=${line//\'/} line=${line//\\/\'} sys_time=$(date -d "$today $time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$line"'", "time":"'"$time"'", "sys_time":"'"$sys_time"'" }' else time=${line#* } fi fi done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "$SCHEDULE_LINK") if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] 节目表更新成功" else Println "$error $chnl_name [$chnl_id] 节目表更新失败" fi } ScheduleAmlh() { printf -v today '%(%Y-%-m-%-d)T' -1 timestamp=$(date -d $today +%s) SCHEDULE_LINK="http://www.macaulotustv.cc/index.php/index/getdetail.html" if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "amlh" > "$SCHEDULE_JSON" fi schedule="" line=$(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" --data "d=$((timestamp-86400))" "$SCHEDULE_LINK") || true if [[ $line == *"
  • "* ]] then line=${line#*} time=${line%%<*} while [ -n "$time" ] do time=${time:0:5} line=${line#*} if [ "${flag:-0}" -gt 0 ] && [ "${time:0:1}" -eq 0 ] then title=${line%%<*} title=${title//\\t/ } title=$(printf %b "$title") if [ "${title:0:4}" == "經典影院" ] then title=${title:5} fi sys_time=$(date -d "$today $time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$title"'", "time":"'"$time"'", "sys_time":"'"$sys_time"'" }' else flag=${time:0:1} fi if [[ $line == *""* ]] then line=${line#*} time=${line%%<*} else break fi done fi flag=0 line=$(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" --data "d=$timestamp" "$SCHEDULE_LINK") || true while [[ $line == *"
  • "* ]] do line=${line#*} time=${line%%<*} time=${time:0:5} line=${line#*} if [ -z "${time:-}" ] || [ "$flag" -gt "${time:0:1}" ] then break 2 fi flag=${time:0:1} title=${line%%<*} title=${title//\\t/ } title=$(printf %b "$title") if [ "${title:0:4}" == "經典影院" ] then title=${title:5} fi sys_time=$(date -d "$today $time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$title"'", "time":"'"$time"'", "sys_time":"'"$sys_time"'" }' done if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] 节目表更新成功" else Println "$error $chnl_name [$chnl_id] 节目表更新失败" fi } ScheduleFoxnews() { printf -v today '%(%Y-%m-%d)T' -1 yesterday=$(date --date="yesterday" +"%Y-%m-%d") min_start_time=$(date -d "yesterday 22:00:00" +%s) max_start_time=$(date -d "today 23:59:59" +%s) schedule=$(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" "https://apps.foxnews.com/schedule_new/feed/fox-news.jn" | $JQ_FILE --arg today "$today" --arg yesterday "$yesterday" --arg min "$min_start_time" --arg max "$max_start_time" --argjson keys '["title","time","sys_time"]' '.day | map(select(.["@attributes"].value == $yesterday or .["@attributes"].value == $today).show[] | .["sys_time"] = (."start-utc"|fromdate) | select(.sys_time > ($min|tonumber) and .sys_time < ($max|tonumber)) | .["time"] = (.sys_time|strflocaltime("%-H:%M%p")) | with_entries(select(.key as $k | $keys | index($k))))[]') if [ -n "$schedule" ] then file=true file_json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" schedule Println "$info $chnl_name [$chnl_id] 节目表更新成功" else Println "$error $chnl_name [$chnl_id] 节目表更新失败" fi } ScheduleTvbhk() { printf -v today '%(%Y%m%d)T' -1 sys_time=$(date -d $today +%s) min_sys_time=$((sys_time-7200)) max_sys_time=$((sys_time+86400)) yesterday=$(date --date="yesterday" +"%Y%m%d") if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "tvbhk_pearl" > "$SCHEDULE_JSON" fi tvbhk_yesterday_schedule=$(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" -H "referer: https://programme.tvb.com/" "https://programme.tvb.com/api/schedule?input_date=$yesterday&network_code=J,B,C,P,A") tvbhk_today_schedule=$(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" -H "referer: https://programme.tvb.com/" "https://programme.tvb.com/api/schedule?input_date=$today&network_code=J,B,C,P,A") for chnl in "${tvbhk_chnls[@]}" do chnl_id=${chnl%%:*} chnl_code=${chnl#*:} chnl_name=${chnl_code#*:} chnl_code=${chnl_code%%:*} yesterday_schedule=$($JQ_FILE --arg code "$chnl_code" --arg min "$min_sys_time" --argjson keys '["title","time","sys_time"]' '.data.list | map(select(.network_code == $code).schedules[] | select((.event_time|tonumber) > ($min|tonumber)) | .["time"] = (.event_time|strflocaltime("%H:%M")) | .["sys_time"] = .event_time | .["title"] = .programme_title | with_entries(select(.key as $k | $keys | index($k))))' <<< "$tvbhk_yesterday_schedule") today_schedule=$($JQ_FILE --arg code "$chnl_code" --arg max "$max_sys_time" --argjson keys '["title","time","sys_time"]' '.data.list | map(select(.network_code == $code).schedules[] | select((.event_time|tonumber) < ($max|tonumber)) | .["time"] = (.event_time|strflocaltime("%H:%M")) | .["sys_time"] = .event_time | .["title"] = .programme_title | with_entries(select(.key as $k | $keys | index($k))))' <<< "$tvbhk_today_schedule") schedule=$($JQ_FILE --argjson merge "$today_schedule" '.+=$merge|unique_by(.sys_time)' <<< "$yesterday_schedule") if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "$schedule" Println "$info $chnl_name [$chnl_id] tvbhk 节目表更新成功" else Println "$error $chnl_name [$chnl_id] tvbhk 节目表更新失败" fi done } ScheduleTvbhd() { if [[ ! -x $(command -v pdf2htmlEX) ]] then Pdf2htmlInstall fi wget --timeout=10 --tries=3 --no-check-certificate "https://schedule.tvbusa.com/current/tvb_hd.pdf" -qO "$IPTV_ROOT/tvb_hd.pdf" cd "$IPTV_ROOT" pdf2htmlEX --zoom 1.3 "./tvb_hd.pdf" printf -v today '%(%Y-%m-%d)T' -1 sys_time=$(date -d $today +%s) yesterday=$(date --date="yesterday" +"%Y-%m-%d") weekday_program_title=() weekday_program_time=() saturday_program_title=() saturday_program_time=() sunday_program_title=() sunday_program_time=() while IFS= read -r line do if [[ $line == *"節目表"* ]] then line=${line#*"星期日"} line=${line#*"日期"} line=${line//""/} line=${line//"
    11:30
    "/} old_program_time="" skips=( "4:saturday sunday" "7:saturday" "9:saturday sunday" "10:weekday" "11:weekday saturday" "12:sunday" "13:sunday" "16:saturday sunday" "17:weekday" "18:saturday" "19:sunday" "20:saturday" "22:saturday sunday" "23:sunday" "24:weekday" "25:weekday" "26:sunday" "27:saturday sunday" "28:saturday sunday" "29:weekday" "30:saturday sunday" "32:saturday sunday" "33:saturday" "34:sunday" "36:weekday" "37:sunday" "38:saturday sunday" "39:sunday" "40:saturday sunday" "41:sunday" "43:weekday" "44:weekday" "47:sunday" "48:weekday saturday" "49:sunday" "50:weekday" "51:saturday" "52:sunday" "53:saturday" "54:sunday" "55:saturday" "56:saturday sunday" "57:saturday sunday" "58:saturday sunday" "59:saturday sunday" "60:saturday sunday" ) loop=1 count=0 day="weekday" while true do class=${line%%\">*} class=${class#*
    } content=${line%%<*} case $content in ""|" "|"AM"|"PM"|"東岸"|"西岸"|"星期日"|"星期一"|"星期二至六"|"日期"|"Next Day") continue ;; *"夏令時間"*) continue ;; *"將時鐘"*) continue ;; "高清台") if [[ -n ${program_title:-} ]] then if [[ -n ${program_start_date:-} ]] then program_title="$program_title $program_start_date" fi program_title=${program_title//amp;/} program_title=${program_title//'/\'} if [ "$day" == "weekday" ] then if [[ -n $old_program_time ]] then weekday_program_title+=("$program_title") weekday_program_time+=("$old_program_time") else index=${#weekday_program_title[@]} index=$((index-1)) weekday_program_title[index]="${weekday_program_title[index]} $program_title" fi elif [ "$day" == "saturday" ] then if [[ -n $old_program_time ]] then saturday_program_title+=("$program_title") saturday_program_time+=("$old_program_time") else index=${#saturday_program_title[@]} index=$((index-1)) saturday_program_title[index]="${saturday_program_title[index]} $program_title" fi elif [ "$day" == "sunday" ] then if [[ -n $old_program_time ]] then sunday_program_title+=("$program_title") sunday_program_time+=("$old_program_time") else index=${#sunday_program_title[@]} index=$((index-1)) sunday_program_title[index]="${sunday_program_title[index]} $program_title" fi fi program_title="" old_program_time="" program_sys_time="" program_start_date="" fi break ;; *) if [[ ${content:1:1} == "/" ]] && [[ ! ${content:0:1} == *[!0-9]* ]] && [[ ! ${content:2} == *[!0-9]* ]] then program_start_date="$content" elif [[ ${content:2:1} == "/" ]] && [[ ! ${content:0:2} == *[!0-9]* ]] && [[ ! ${content:3} == *[!0-9]* ]] then program_start_date="$content" elif [[ ${content:1:1} == ":" ]] then if [[ ! ${content:0:1} == *[!0-9]* ]] && [[ ! ${content:2} == *[!0-9]* ]] then [ -n "${program_time:-}" ] && program_time="" if [[ -z ${program_time_east:-} ]] then program_time_east="$content" else program_time="$content" program_time_east="" fi fi elif [[ ${content:2:1} == ":" ]] then if [[ ! ${content:0:2} == *[!0-9]* ]] && [[ ! ${content:3} == *[!0-9]* ]] then [ -n "${program_time:-}" ] && program_time="" if [[ -z ${program_time_east:-} ]] then program_time_east="$content" else program_time="$content" program_time_east="" fi fi else old_day=$day if [ "$count" -gt 0 ] then if [ "$old_day" == "sunday" ] then day="weekday" elif [ "$old_day" == "weekday" ] then day="saturday" elif [ "$old_day" == "saturday" ] then day="sunday" fi fi count=$((count+1)) if [[ $((count % 3)) -eq 0 ]] then loop=$((count/3)) else loop=$((count/3 + 1)) fi redo=1 while [ "$redo" -eq 1 ] do redo=0 for skip in "${skips[@]}" do if [ "${skip%:*}" == "$loop" ] then redo=1 IFS=" " read -ra days <<< "${skip#*:}" for ele in "${days[@]}" do if [ "$ele" == "$day" ] then count=$((count+1)) if [ "$day" == "sunday" ] then day="weekday" elif [ "$day" == "weekday" ] then day="saturday" elif [ "$day" == "saturday" ] then day="sunday" fi fi done if [[ $((count % 3)) -eq 0 ]] then new_loop=$((count/3)) else new_loop=$((count/3 + 1)) fi if [ "$new_loop" == "$loop" ] then redo=0 else loop="$new_loop" fi break fi done done case $((count%3)) in 0) day="sunday" ;; 1) day="weekday" ;; 2) day="saturday" ;; esac if [[ -n ${program_title:-} ]] then if [[ -n ${program_start_date:-} ]] then program_title="$program_title $program_start_date" fi program_title=${program_title//amp;/} program_title=${program_title//'/\'} if [ "$old_day" == "weekday" ] then if [[ -n $old_program_time ]] then weekday_program_title+=("$program_title") weekday_program_time+=("$old_program_time") else index=${#weekday_program_title[@]} index=$((index-1)) weekday_program_title[index]="${weekday_program_title[index]} $program_title" fi elif [ "$old_day" == "saturday" ] then if [[ -n $old_program_time ]] then saturday_program_title+=("$program_title") saturday_program_time+=("$old_program_time") else index=${#saturday_program_title[@]} index=$((index-1)) saturday_program_title[index]="${saturday_program_title[index]} $program_title" fi elif [ "$old_day" == "sunday" ] then if [[ -n $old_program_time ]] then sunday_program_title+=("$program_title") sunday_program_time+=("$old_program_time") else index=${#sunday_program_title[@]} index=$((index-1)) sunday_program_title[index]="${sunday_program_title[index]} $program_title" fi fi program_title="" old_program_time="" program_start_date="" fi if [ -n "${program_time_east:-}" ] then program_time=$program_time_east program_time_east="" fi program_title="$content" if [ -n "$program_time" ] then old_program_time=$program_time program_time="" fi fi ;; esac done break fi done < "./tvb_hd.html" weekday=$(printf '%(%u)T' -1) if [ "$weekday" -eq 1 ] then p_title=("${sunday_program_title[@]}") p_time=("${sunday_program_time[@]}") elif [ "$weekday" -eq 0 ] then p_title=("${saturday_program_title[@]}") p_time=("${saturday_program_time[@]}") else p_title=("${weekday_program_title[@]}") p_time=("${weekday_program_time[@]}") fi if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "tvbhd" > "$SCHEDULE_JSON" fi schedule="" change=0 date=$yesterday for((i=0;i<${#p_time[@]};i++)); do [ -n "${program_time:-}" ] && program_time_old=$program_time program_time=${p_time[i]} if [ -n "${program_time_old:-}" ] &&[ "${program_time%:*}" -lt "${program_time_old%:*}" ] then change=$((change+1)) fi if [ "$change" -eq 1 ] then hour=${program_time%:*} hour=$((hour+12)) if [ "$hour" -eq 24 ] then hour="0" date=$today fi new_program_time="$hour:${program_time#*:}" elif [ "$change" -eq 2 ] then date=$today new_program_time=$program_time else new_program_time=$program_time fi if [[ ${new_program_time:1:1} == ":" ]] then new_program_time="0$new_program_time" else new_program_time=$new_program_time fi program_sys_time=$(date -d "${date}T$new_program_time-08:00" +%s) new_program_time=$(printf '%(%H:%M)T' "$program_sys_time") program_title=${p_title[i]} [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$new_program_time"'", "sys_time":"'"$program_sys_time"'" }' done if [ -n "$schedule" ] then json=true jq_path='["tvbhd"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info tvbhd [$chnl_id] 节目表更新成功" else Println "$error tvbhd [$chnl_id] 节目表更新失败" fi } ScheduleSingteltv() { if [ "${singteltv_status:-1}" -eq 2 ] then return 0 fi if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "my_tvbjade" > "$SCHEDULE_JSON" fi if [ "${singteltv_status:-0}" -eq 0 ] then Println "$info 解析 singteltv ..." singteltv_status=0 while IFS= read -r line do if [[ $line =~ epgEndPoint ]] then line=${line#*epgEndPoint":"} singteltv_epg_end_point=${line%%"*} line=${line#*tvChannelLists":} singteltv_tv_channel_lists=${line%%,"errorMessage*} singteltv_tv_channel_lists=${singteltv_tv_channel_lists//"/\"} singteltv_status=1 break fi done < <(curl -s -L "https://www.singtel.com/personal/products-services/tv/tv-programme-guide" 2> /dev/null) fi if [ "$singteltv_status" -eq 0 ] then Println "$error 无法连接 singteltv ?" singteltv_status=2 return 0 fi printf -v today '%(%d%m%Y)T' -1 epg_data=$(curl -s -L "https://www.singtel.com$singteltv_epg_end_point/$today.json") IFS=$'\t' read -r title channel_id epg_channel_id < <($JQ_FILE -r '[ ([.[]|.title|. + "^"]|join("")), ([.[]|.channelId|. + "^"]|join("")), ([.[]|.epgChannelId|. + "^"]|join("")) ]|@tsv' <<< "$singteltv_tv_channel_lists") IFS="^" read -r -a titles <<< "$title" IFS="^" read -r -a channel_ids <<< "$channel_id" IFS="^" read -r -a epg_channel_ids <<< "$epg_channel_id" IFS=$'\t' read -r schedule_id schedule_title schedule_time < <($JQ_FILE -r '[ ([to_entries[]|.key|tostring|. + "^"]|join("")), ([to_entries[]|([.value[].program.title|. + "|"]|join(""))|. + "^"]|join("")), ([to_entries[]|([.value[].startDateTime|. + "|"]|join(""))|. + "^"]|join("")) ]|@tsv' <<< "$epg_data") IFS="^" read -r -a schedule_ids <<< "$schedule_id" IFS="^" read -r -a schedule_titles <<< "$schedule_title" IFS="^" read -r -a schedule_times <<< "$schedule_time" for chnl in "${singteltv_chnls[@]}" do chnl_id=${chnl%%:*} singteltv_id=${chnl#*:} chnl_name=${singteltv_id#*:} singteltv_id=${singteltv_id%%:*} schedule="" for((singteltv_i=0;singteltv_i<${#channel_ids[@]};singteltv_i++)); do if [ "${channel_ids[singteltv_i]}" == "$singteltv_id" ] then for((schedule_i=0;schedule_i<${#schedule_ids[@]};schedule_i++)); do if [ "${schedule_ids[schedule_i]}" == "${epg_channel_ids[singteltv_i]}" ] then IFS="|" read -r -a program_titles <<< "${schedule_titles[schedule_i]}" IFS="|" read -r -a program_times <<< "${schedule_times[schedule_i]}" for((program_i=0;program_i<${#program_titles[@]};program_i++)); do program_time=${program_times[program_i]#*T} program_time=${program_time%:*} program_sys_time=$(date -d "${program_times[program_i]}" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"${program_titles[program_i]//\"/}"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' done break fi done break fi done if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] singteltv 节目表更新成功" else Println "$error $chnl_name [$chnl_id] singteltv 节目表更新失败" fi done } ScheduleCntv() { printf -v today '%(%Y%m%d)T' -1 if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "cctv13" > "$SCHEDULE_JSON" fi for chnl in "${cntv_chnls[@]}" do chnl_id=${chnl%%:*} chnl_name=${chnl#*:} schedule="" while IFS="=" read -r program_sys_time program_time program_title do program_sys_time=${program_sys_time#\"} program_title=${program_title%\"} [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "http://api.cntv.cn/epg/getEpgInfoByChannelNew?c=$chnl_id&serviceId=tvcctv&d=$today" | $JQ_FILE '.data.'"$chnl_id"'.list[]|[.startTime,.showTime,.title]|join("=")') if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] cntv 节目表更新成功" else Println "$error $chnl_name [$chnl_id] cntv 节目表更新失败" fi done } ScheduleTvbs() { printf -v today '%(%Y-%m-%d)T' -1 if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "tvbs" > "$SCHEDULE_JSON" fi lang=2 for chnl in "${tvbs_chnls[@]}" do chnl_id=${chnl%%:*} chnl_order=${chnl#*:} chnl_name=${chnl_order#*:} chnl_order=${chnl_order%%:*} schedule="" while IFS="=" read -r program_time program_title do program_time=${program_time#\"} program_title=${program_title%\"} program_sys_time=$(date -d "$today $program_time" +%s) [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" "https://tvbsapp.tvbs.com.tw/pg_api/pg_list/$chnl_order/$today/1/$lang" | $JQ_FILE '.data|to_entries|map(select(.value.date=="'"$today"'"))|.[].value.data|to_entries|map("\(.value.pg_hour)=\(.value.pg_name)")|.[]') if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] tvbs 节目表更新成功" else Println "$error $chnl_name [$chnl_id] tvbs 节目表更新失败" fi done } ScheduleAstro() { printf -v today '%(%Y-%m-%d)T' -1 if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "iqiyi" > "$SCHEDULE_JSON" fi for chnl in "${astro_chnls[@]}" do chnl_id=${chnl%%:*} astro_id=${chnl#*:} chnl_name=${astro_id#*:} astro_id=${astro_id%%:*} schedule=$(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" "https://contenthub-api.eco.astro.com.my/channel/$astro_id.json" | $JQ_FILE --arg today "$today" --argjson keys '["title","time","sys_time"]' '.response.schedule[$today] | map(.["time"] = (.datetime|sub("(?.*) (?
  • (.+)\ ]] then schedule_title="${BASH_REMATCH[1]}" fi new_schedule="${new_schedule#*

    (.+)\ ]] then if [ -n "$schedule_title" ] then schedule_title="$schedule_title - " fi schedule_title="$schedule_title${BASH_REMATCH[1]}" fi new_schedule=$( $JQ_FILE -n --arg schedule_title "$schedule_title" --arg schedule_time "$schedule_time" --arg schedule_sys_time "$schedule_sys_time" \ '{ title: $schedule_title, time: $schedule_time, sys_time: $schedule_sys_time }' ) schedule+=("$new_schedule") pr_schedule="${pr_schedule#*
  • "$SCHEDULE_JSON" fi printf -v today '%(%Y%m%d)T' -1 sys_time=$(date -d $today +%s) yesterday=$(date --date="yesterday" +"%Y%m%d") min_sys_time=$((sys_time-7200)) max_sys_time=$((sys_time+86400)) chnls_sid="" for chnl in "${sky_chnls[@]}" do sk_sid="${chnl#*:}" sk_sid="${sk_sid%%:*}" [ -n "$chnls_sid" ] && chnls_sid="$chnls_sid," chnls_sid="$chnls_sid$sk_sid" done sk_yesterday_schedule=$(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" "https://awk.epgsky.com/hawk/linear/schedule/$yesterday/$chnls_sid") sk_today_schedule=$(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" "https://awk.epgsky.com/hawk/linear/schedule/$today/$chnls_sid") for chnl in "${sky_chnls[@]}" do chnl_id=${chnl%%:*} sk_sid="${chnl#*:}" chnl_name="${sk_sid#*:}" sk_sid=${sk_sid%%:*} yesterday_schedule=$($JQ_FILE --arg sid "$sk_sid" --arg min "$min_sys_time" --argjson keys '["title","time","sys_time"]' '.schedule | map(select(.sid == $sid).events[] | select((.st|tonumber) > ($min|tonumber)) | .["time"] = (.st|strflocaltime("%H:%M")) | .["sys_time"] = .st | .["title"] = .t | with_entries(select(.key as $k | $keys | index($k))))' <<< "$sk_yesterday_schedule") today_schedule=$($JQ_FILE --arg sid "$sk_sid" --arg max "$max_sys_time" --argjson keys '["title","time","sys_time"]' '.schedule | map(select(.sid == $sid).events[] | select((.st|tonumber) < ($max|tonumber)) | .["time"] = (.st|strflocaltime("%H:%M")) | .["sys_time"] = .st | .["title"] = .t | with_entries(select(.key as $k | $keys | index($k))))' <<< "$sk_today_schedule") schedule=$($JQ_FILE --argjson merge "$today_schedule" '.+=$merge|unique_by(.sys_time)' <<< "$yesterday_schedule") if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "$schedule" Println "$info $chnl_name [$chnl_id] Sky 节目表更新成功" else Println "$error $chnl_name [$chnl_id] Sky 节目表更新失败" fi done } Schedule_4gtv() { printf -v today '%(%Y-%m-%d)T' -1 if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "minshidiyi" > "$SCHEDULE_JSON" fi for chnl in "${_4gtv_chnls[@]}" do chnl_id=${chnl%%:*} _4gtv_id=${chnl#*:} chnl_name=${_4gtv_id#*:} _4gtv_id=${_4gtv_id%%:*} schedule="" while IFS="=" read -r program_date program_time program_title do program_date=${program_date#\"} if [ "$today" == "$program_date" ] then program_sys_time=$(date -d "$program_date $program_time" +%s) program_time=${program_time%:*} program_title=${program_title%\"} [ -n "$schedule" ] && schedule="$schedule," schedule=$schedule'{ "title":"'"$program_title"'", "time":"'"$program_time"'", "sys_time":"'"$program_sys_time"'" }' elif [ -n "$schedule" ] then break fi done < <(CurlFake -s -Lm 20 \ -H "Referer: https://www.4gtv.tv/channel_sub.html?channelSet_id=1&asset_id=$_4gtv_id&channel_id=1" \ "https://www.4gtv.tv/proglist/$_4gtv_id.txt" \ | $JQ_FILE '.[]|[.sdate,.stime,.title]|join("=")') if [ -n "$schedule" ] then json=true jq_path='["'"$chnl_id"'"]' JQ update "$SCHEDULE_JSON" "[$schedule]" Println "$info $chnl_name [$chnl_id] 4gtv 节目表更新成功" else Println "$error $chnl_name [$chnl_id] 4gtv 节目表更新失败" fi done } ScheduleOther() { if [ ! -s "$SCHEDULE_JSON" ] then printf '{"%s":[]}' "amlh" > "$SCHEDULE_JSON" fi for chnl in "${other_chnls[@]}" do chnl_id=${chnl%%:*} chnl_name=${chnl#*:} chnl_id_upper=$(tr '[:lower:]' '[:upper:]' <<< "${chnl_id:0:1}")"${chnl_id:1}" Schedule"$chnl_id_upper" done } GetCronChnls() { cron_providers=() cron_chnls=() cron_providers_list="" cron_providers_count=0 while IFS= read -r line do if [[ $line == *"|"* ]] then cron_providers_count=$((cron_providers_count+1)) cron_provider=${line%%|*} for provider in "${providers[@]}" do if [ "${provider%:*}" == "$cron_provider" ] then cron_provider_name="${provider#*:}" break fi done cron_providers+=("$cron_provider") cron_chnls+=("${line#*|}") cron_providers_list="$cron_providers_list ${green}$cron_providers_count.${normal}${indent_6}$cron_provider_name\n\n" fi done < <($JQ_FILE -r '.schedule[]|[.provider,.chnls[]]|join("|")' "$CRON_FILE") } ScheduleView() { GetCronChnls providers_list="" providers_count=0 for provider in "${providers[@]}" do providers_count=$((providers_count+1)) providers_list="$providers_list ${green}$providers_count.${normal}${indent_6}${provider#*:} [${provider%%:*}]\n\n" done Println "节目表来源\n\n$providers_list" while read -p "$i18n_default_cancel" provider_num do case "$provider_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$provider_num" -gt 0 ] && [ "$provider_num" -le "$providers_count" ] then provider="${providers[$((provider_num-1))]%:*}" break else Println "$error $i18n_input_correct_number\n" fi ;; esac done var=("$provider"_chnls[@]) for((i=0;i "$SCHEDULE_JSON" while IFS="=" read -r provider_id chnls option do if [ "$chnls" != null ] then var=("$provider_id"_chnls[@]) if [[ -n ${!var:-} ]] then unset "$provider_id"_chnls IFS="|" read -r -a "$provider_id"_chnls <<< "${chnls}|" fi if [ "$provider_id" == "other" ] then for chnl in "${other_chnls[@]}" do chnl_id=${chnl%%:*} chnl_name=${chnl#*:} chnl_id_upper=$(tr '[:lower:]' '[:upper:]' <<< "${chnl_id:0:1}")"${chnl_id:1}" if ! Schedule"$chnl_id_upper" then Println "$error $chnl_name 再次尝试..." Schedule"$chnl_id_upper" || Println "$error $chnl_id_upper 失败" fi done else provider_id_upper=$(tr '[:lower:]' '[:upper:]' <<< "${provider_id:0:1}")"${provider_id:1}" if ! Schedule"$provider_id_upper" "$option" then for provider in "${providers[@]}" do if [ "${provider%%:*}" == "$provider_id" ] then provider_name="${#*:}" break fi done Println "$error $provider_name 再次尝试..." Schedule"$provider_id_upper" "$option" || Println "$error $provider_name 失败" fi fi fi done < <($JQ_FILE -r '.schedule[]|[.provider,(.chnls|sort|join("|")| if .=="" then "null" else . end),.option]|join("=")' "$CRON_FILE") if [ -e "$IPTV_ROOT/vip.pid" ] then printf '%s' "" > "$VIP_USERS_ROOT/epg.update" fi else Println "$error 计划任务为空, 请先添加频道 !\n" fi } ScheduleBackup() { if [ ! -s "$CRON_FILE" ] then Println "$error 请先添加频道\n" exit 1 fi echo inquirer text_input "输入备份名称: " backup_name "无" backup_schedule="" while IFS="=" read -r provider chnls option do if [ "$chnls" != null ] then [ -n "$backup_schedule" ] && backup_schedule="$backup_schedule," backup_schedule=$backup_schedule'{ "provider":"'"$provider"'", "chnls":"'"$chnls"'" }' fi done < <($JQ_FILE -r '.schedule[]|[.provider,(.chnls|sort|join("|")| if .=="" then "null" else . end),.option]|join("=")' "$CRON_FILE") if [ -z "$backup_schedule" ] then Println "$error 请先添加频道\n" exit 1 fi new_backup=$( $JQ_FILE -n --arg name "$backup_name" --argjson schedule "[$backup_schedule]" \ '{ name: $name, date: now|strflocaltime("%s")|tonumber, schedule: $schedule }' ) jq_path='["schedule_backup"]' JQ add "$CRON_FILE" "[$new_backup]" Println "$info 任务备份成功\n" } ScheduleListBackup() { schedule_backup_names=() schedule_backup_dates=() schedule_backup_schedules=() schedule_backup_count=0 schedule_backup_list="" while IFS="^" read -r backup_name backup_date backup_schedule do schedule_backup_count=$((schedule_backup_count+1)) schedule_backup_names+=("$backup_name") schedule_backup_dates+=("$backup_date") schedule_backup_schedules+=("$backup_schedule") printf -v date '%(%m-%d %H:%M:%S)T' "$backup_date" schedule_backup_list="$schedule_backup_list $schedule_backup_count. 备份名称: ${green}$backup_name${normal} 备份日期: ${green}$date${normal}\n\n" done < <($JQ_FILE -r '(.schedule_backup| if .== null then [] else . end)[]|([.name,.date,(.schedule|to_entries|map([.value.provider,.value.chnls]|join("="))|join(","))]|join("^"))' "$CRON_FILE") if [ "$schedule_backup_count" -eq 0 ] then Println "$error 没有备份\n" exit 1 fi Println "$schedule_backup_list" } ScheduleViewBackup() { ScheduleListBackup while read -p "$i18n_default_cancel" backup_num do case "$backup_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$backup_num" -gt 0 ] && [ "$backup_num" -le "$schedule_backup_count" ] then schedule="${schedule_backup_schedules[backup_num-1]}" IFS="," read -r -a schedules <<< "$schedule" break else Println "$error $i18n_input_correct_number\n" fi ;; esac done schedules_list="" for((i=0;i<${#schedules[@]};i++)); do schedule_provider=${schedules[i]%=*} for provider in "${providers[@]}" do if [ "${provider%:*}" == "$schedule_provider" ] then schedule_provider_name="${provider#*:}" break fi done schedule_chnl=${schedules[i]#*=} IFS="|" read -r -a schedule_chnls <<< "$schedule_chnl" schedule_chnls_list="" for schedule_chnl in "${schedule_chnls[@]}" do if [ "$schedule_provider" == "ontvtonight" ] then schedule_chnl_id=${schedule_chnl%%@*} else schedule_chnl_id=${schedule_chnl%%:*} fi schedule_chnls_list="$schedule_chnls_list${indent_6}${schedule_chnl##*:} ($schedule_chnl_id)\n" done schedules_list="$schedules_list ${green}$((i+1)).${normal}${indent_6}$schedule_provider_name\n\n$schedule_chnls_list\n" done Println "$schedules_list" } ScheduleEditBackup() { ScheduleListBackup while read -p "$i18n_default_cancel" backup_num do case "$backup_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$backup_num" -gt 0 ] && [ "$backup_num" -le "$schedule_backup_count" ] then backup_index=$((backup_num-1)) backup_name_old=${schedule_backup_names[backup_index]} break else Println "$error $i18n_input_correct_number\n" fi ;; esac done echo inquirer text_input "输入新的备份名称: " backup_name "$backup_name_old" jq_path='["schedule_backup",'"$backup_index"',"name"]' JQ update "$CRON_FILE" "$backup_name" Println "$info 备份修改成功\n" } ScheduleDelBackup() { ScheduleListBackup while read -p "$i18n_default_cancel" backup_num do case "$backup_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$backup_num" -gt 0 ] && [ "$backup_num" -le "$schedule_backup_count" ] then backup_index=$((backup_num-1)) backup_name=${schedule_backup_names[backup_index]} break else Println "$error $i18n_input_correct_number\n" fi ;; esac done jq_path='["schedule_backup",'"$backup_index"']' JQ delete "$CRON_FILE" Println "$info 备份 $backup_name 删除成功\n" } ScheduleRestoreBackup() { ScheduleListBackup while read -p "$i18n_default_cancel" backup_num do case "$backup_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$backup_num" -gt 0 ] && [ "$backup_num" -le "$schedule_backup_count" ] then backup_index=$((backup_num-1)) backup_name=${schedule_backup_names[backup_index]} backup_schedule="${schedule_backup_schedules[backup_index]}" IFS="," read -r -a backup_schedules <<< "$backup_schedule" break else Println "$error $i18n_input_correct_number\n" fi ;; esac done schedules_list="" for((i=0;i<${#backup_schedules[@]};i++)); do schedule_provider=${backup_schedules[i]%=*} schedule_chnl=${backup_schedules[i]#*=} IFS="|" read -r -a schedule_chnls <<< "$schedule_chnl" schedule_chnls_list="" for schedule_chnl in "${schedule_chnls[@]}" do [ -n "$schedule_chnls_list" ] && schedule_chnls_list="$schedule_chnls_list," schedule_chnls_list=$schedule_chnls_list'"'$schedule_chnl'"' done [ -n "$schedules_list" ] && schedules_list="$schedules_list," schedules_list=$schedules_list'{ "provider":"'"$schedule_provider"'", "chnls":['"$schedule_chnls_list"'] }' done json=true jq_path='["schedule"]' JQ update "$CRON_FILE" "[$schedules_list]" Println "$info 备份 $backup_name 恢复成功\n" } ScheduleEnableCron() { if crontab -l | grep -q "/usr/local/bin/tv s" 2> /dev/null then Println "$error 计划任务已开启 !\n" else crontab -l > "$IPTV_ROOT/cron_tmp" 2> /dev/null || true printf '%s\n' "0 0 * * * /usr/local/bin/tv s -" >> "$IPTV_ROOT/cron_tmp" crontab "$IPTV_ROOT/cron_tmp" > /dev/null rm -f "$IPTV_ROOT/cron_tmp" Println "$info 计划任务开启成功 !\n" fi } ScheduleDisableCron() { if crontab -l | grep -q "/usr/local/bin/tv s" 2> /dev/null then crontab -l > "$IPTV_ROOT/cron_tmp" 2> /dev/null || true sed -i "/\/usr\/local\/bin\/tv s/d" "$IPTV_ROOT/cron_tmp" crontab "$IPTV_ROOT/cron_tmp" > /dev/null rm -f "$IPTV_ROOT/cron_tmp" Println "$info 计划任务已关闭\n" else Println "$error 计划任务未开启 !\n" fi } Schedule() { GetDefault if [ -n "$d_schedule_file" ] then SCHEDULE_JSON="$d_schedule_file" else Println "$error 请先设置 schedule_file 位置!\n" && exit 1 fi if [ ! -s "$CRON_FILE" ] then printf '{"%s":[]}' "schedule" > "$CRON_FILE" fi jiushi_chnls=( # "foxmovies:FOX MOVIES" # "disney:Disney" "minshi:民視" "minshidiyi:民視第一台" "minshitaiwan:民視台灣台" "mtvlivetw:MTV-Live" "tvbfc:TVB 翡翠台" "tvbpearl:TVB Pearl" "tvbj2:TVB J2" "tvbj5:TVB J5" "tvbwxxw:TVB 互動新聞台" "xgws:香港衛視綜合台" "foxfamily:福斯家庭電影台" "hlwdy:好萊塢電影" "xwdy:星衛HD電影台" "mydy:美亞電影台" "mycinemaeurope:My Cinema Europe HD我的歐洲電影台" "ymjs:影迷數位紀實台" "ymdy:影迷數位電影台" "hyyj:華藝影劇台" "catchplaydy:CatchPlay電影台" "ccyj:采昌影劇台" "lxdy:LS龍祥電影" "cinemax:Cinemax" "cinemaworld:CinemaWorld" "axn:AXN HD" "channelv:Channel V國際娛樂台HD" "dreamworks:DREAMWORKS" "nickasia:Nickelodeon Asia(尼克兒童頻道)" "cbeebies:CBeebies" "babytv:Baby TV" "boomerang:Boomerang" "mykids:MY-KIDS TV" "dwxq:動物星球頻道" "eltvshyy:ELTV生活英語台" "ifundm:i-Fun動漫台" "momoqz:momo親子台" "cnkt:CN卡通台" "ffxw:非凡新聞" "hycj:寰宇財經台" "hyzh:寰宇HD綜合台" "hyxw:寰宇新聞台" "hyxw2:寰宇新聞二台" "aedzh:愛爾達綜合台" "aedyj:愛爾達影劇台" "jtzx:靖天資訊台" "jtzh:靖天綜合台" "jtyl:靖天育樂台" "jtxj:靖天戲劇台" "jthl:Nice TV 靖天歡樂台" "jtyh:靖天映畫" "jtgj:KLT-靖天國際台" "jtrb:靖天日本台" "jtdy:靖天電影台" "jtkt:靖天卡通台" "jyxj:靖洋戲劇台" "jykt:靖洋卡通台Nice Bingo" "lhxj:龍華戲劇" "lhox:龍華偶像" "lhyj:龍華影劇" "lhdy:龍華電影" "lhjd:龍華經典" "lhyp:龍華洋片" "lhdh:龍華動畫" "wszw:衛視中文台" "wsdy:衛視電影台" "gxws:國興衛視" "gs:公視" "gs2:公視2台" "gs3:公視3台" "ts:台視" "tszh:台視綜合台" "tscj:台視財經台" "hs:華視" "hsjywh:華視教育文化" "zs:中視" "zsxw:中視新聞台" "zsjd:中視經典台" "sltw:三立台灣台" "sldh:三立都會台" "slzh:三立綜合台" "slxj:三立戲劇台" "bdzh:八大綜合" "bddy:八大第一" "bdxj:八大戲劇" "bdyl:八大娛樂" "gdyl:高點育樂" "gdzh:高點綜合" "ydsdy:壹電視電影台" "ydszxzh:壹電視資訊綜合台" "wlty:緯來體育台" "wlxj:緯來戲劇台" "wlrb:緯來日本台" "wldy:緯來電影台" "wlzh:緯來綜合台" "wlyl:緯來育樂台" "wljc:緯來精采台" "dszh:東森綜合台" "dsxj:東森戲劇台" "dsyy:東森幼幼台" "dsdy:東森電影台" "dsyp:東森洋片台" "dsxw:東森新聞台" "dscjxw:東森財經新聞台" "dscs:超級電視台" "ztxw:中天新聞台" "ztyl:中天娛樂台" "ztzh:中天綜合台" "msxq:美食星球頻道" "yzms:亞洲美食頻道" "yzly:亞洲旅遊台" "yzzh:亞洲綜合台" "yzxw:亞洲新聞台" "pltw:霹靂台灣" "titvyjm:原住民" "history:歷史頻道" "history2:HISTORY 2" "gjdl:國家地理高畫質頻道" "gjdlyr:國家地理高畫質悠人頻道" "gjdlys:國家地理高畫質野生頻道" "bbcearth:BBC Earth" "bbcworldnews:BBC World News" "bbclifestyle:BBC Lifestyle Channel" "wakawakajapan:WAKUWAKU JAPAN" "luxe:LUXE TV Channel" "bswx:博斯無限台" "bsgq1:博斯高球一台" "bsgq2:博斯高球二台" "bsml:博斯魅力網" "bswq:博斯網球台" "bsyd1:博斯運動一台" "bsyd2:博斯運動二台" "zlty:智林體育台" "eurosport:EUROSPORT" "fox:FOX頻道" "foxsports:FOX SPORTS" "foxsports2:FOX SPORTS 2" "foxsports3:FOX SPORTS 3" "elevensportsplus:ELEVEN SPORTS PLUS" "elevensports2:ELEVEN SPORTS 2" "discoveryasia:Discovery Asia" "discovery:Discovery" "discoverykx:Discovery科學頻道" "tracesportstars:TRACE Sport Stars" "dw:DW(Deutsch)" "lifetime:Lifetime" "foxcrime:FOXCRIME" "animax:Animax" "mtvtw:MTV綜合電視台" "ndmuch:年代MUCH" "ndxw:年代新聞" "nhk:NHK" "euronews:Euronews" "skynews:SKY NEWS HD" "nhkxwzx:NHK新聞資訊台" "jetzh:JET綜合" "tlclysh:旅遊生活" "z:Z頻道" "itvchoice:ITV Choice" "mdrb:曼迪日本台" "smartzs:Smart知識台" "tv5monde:TV5MONDE" "outdoor:Outdoor-Channel" "fashionone:Fashion-One" "ifundm:i-Fun動漫台" "eentertainment:E! Entertainment" "davinci:DaVinCi Learning達文西頻道" "my101zh:MY101綜合台" "blueantextreme:BLUE ANT EXTREME" "blueantentertainmet:BLUE ANT EXTREME" "eyetvxj:EYE TV戲劇台" "eyetvly:EYE TV旅遊台" "travel:Travel Channel" "dmax:DMAX頻道" "hitshd:HITS" "fx:FX" "tvbshd:TVBS" "tvbshl:TVBS歡樂" "tvbsjc:TVBS精采台" "tvbxh:TVB星河頻道" "tvn:tvN" "hgyl:韓國娛樂台KMTV" "xfkjjj:幸福空間居家台" "xwyl:星衛娛樂台" "amc:AMC" "animaxhd:Animax HD" "diva:Diva" "bloomberg:Bloomberg TV" "fgss:時尚頻道" "warner:Warner TV" "ettodayzh:ETtoday綜合台" ) niotv_chnls=( "hbohd:629:HBO HD 亚洲" "hits:501:HBO 强档巨献 亚洲" "signature:503:HBO 原创巨献 亚洲" "family:502:HBO 温馨家庭 亚洲" "cinemax:49:CINEMAX 亚洲" "msxw:45:民视新闻" "tsxw:637:台视新闻" "slxw:38:三立新闻" "slinews:172:三立 iNews" "tvbsxw:41:TVBS 新闻" "minshi:16:民视" "minshidiyi:638:民视第一台" "minshitaiwan:742:民视台湾台" "mtvlivetw:751:MTV Live 台湾" "foxmovies:47:Fox 电影" "foxfamily:540:Fox 家庭电影" "foxaction:543:FOX 警匪" "disney:63:迪士尼 台湾" "dreamworks:758:梦工厂" "nickasia:705:尼克亚洲" "cbeebies:771:CBeebies" "babytv:553:Baby TV:Boomerang卡通" "boomerang:766:Boomerang" "dwxq:61:动物星球" "momoqz:148:momo 亲子" "cnkt:65:CN 卡通" "hyxw:695:寰宇新闻" "jtzx:709:靖天资讯" "jtzh:710:靖天综合" "jtyl:202:靖天育乐" "jtxj:721:靖天戏剧" "jthl:708:靖天欢乐" "jtyh:727:靖天映画" "jtrb:711:靖天日本" "jtkt:707:靖天卡通" "jyxj:203:靖洋戏剧" "jykt:706:靖洋卡通" "wszw:19:卫视中文" "wsdy:55:卫视电影" "gxws:73:国兴卫视" "gs:17:公视" "gs2:759:公视台语" "gs3:177:公视3台" "ts:11:台视" "tszh:632:台视综合" "tscj:633:台视财经" "hs:15:华视" "hsjywh:138:华视教育体育文化" "zs:13:中视" "zsxw:668:中视新闻" "zsjd:714:中视经典" "sltw:34:三立台湾" "sldh:35:三立都会" "bdzh:21:八大综合" "bddy:33:八大电影" "bdxj:22:八大戏剧" "bdyl:60:八大娱乐" "gdyl:170:高点育乐" "gdzh:143:高点综合" "ydsdy:187:壹电视电影" "ydszh:681:壹电视综合" "wlty:66:玮来体育" "wlxj:29:玮来戏剧" "wlrb:72:玮来日本" "wldy:57:玮来电影" "wlzh:24:玮来综合" "wlyl:53:玮来育乐" "wljc:546:玮来精采" "dszh:23:东森综合" "dsxj:36:东森戏剧" "dsyy:64:东森幼幼" "dsdy:56:东森电影" "dsyp:48:东森洋片" "dsxw:42:东森新闻" "dscjxw:43:东森财经新闻" "dscs:18:东森超视" "ztxw:668:中天新闻" "ztyl:14:中天娱乐" "ztzh:27:中天综合" "yzly:778:亚洲旅游" "yzms:733:亚洲美食" "yzxw:554:亚洲新闻" "pltw:26:霹雳台湾" "titvyjm:133:原住民" "history:549:历史频道" "history2:198:历史频道2" "gjdl:59:国家地理 台湾" "gjdlyr:670:国家地理悠人" "gjdlys:161:国家地理野生" "gjdlgj:519:国家地理国际" "discoveryasia:563:探索亚洲频道" "discovery:58:探索频道" "discoverykx:520:探索科学频道" "bbcearth:698:BBC 地球" "bbcworldnews:144:BBC 世界新闻" "bbclifestyle:646:BBC Lifestyle" "bswx:587:博斯无限" "bsgq1:529:博斯高球" "bsgq2:526:博斯高球2" "bsml:588:博斯魅力" "bsyd2:635:博斯运动2" "bsyd1:527:博斯运动" "eurosport:581:欧洲体育" "fox:70:FOX" "foxsports:67:FOX 体育" "foxsports2:68:FOX 体育2" "foxsports3:547:FOX 体育3" "elevensportsplus:787:ELEVEN 体育 plus" "elevensports1:769:ELEVEN 体育1" "elevensports2:770:ELEVEN 体育2" "lifetime:199:Lifetime" "foxcrime:543:FOX CRIME" "hlwdy:52:好莱坞电影" "animax:84:ANIMAX" "mtvtw:69:MTV 台湾" "ndmuch:25:年代 MUCH" "ndxw:40:年代新闻" "nhk:74:NHK" "euronews:591:欧洲新闻" "ffxw:79:非凡新闻" "jetzh:71:JET 综合" "tlclysh:62:TLC 旅途生活" "axn:50:AXN" "z:75:Z 频道" "luxe:590:LUXE TV" "catchplaydy:582:CatchPlay 电影" "tv5monde:574:法国 TV5" "channelv:584:Channel [v] 国际" "davinci:669:达芬奇频道" "blueantextreme:779:BLUE ANT Extreme" "blueantentertainmet:785:BLUE ANT Entertainment" "travel:684:Travel" "cnn:107:CNN" "dmax:521:DMAX" "hitshd:692:HITS" "lxdy:141:龙祥电影" "fx:544:FX" "tvn:757:tvN" "hgyl:568:韩国娱乐" "xfkjjj:672:幸福空间居家" "nhkxwzx:773:NHK 新闻资讯" "zlty:676:智林体育" "xwdy:558:星卫电影" "xwyl:539:星卫娱乐" "mycinemaeurope:775:我的欧洲电影" "amc:682:AMC 台湾" "animaxhd:772:ANIMAX HD" "wakawakajapan:765:Wakawaka Japan" "tvbshd:20:TVBS" "tvbshl:32:TVBS 欢乐" "tvbsjc:774:TVBS 精采" "cinemaworld:559:Cinema World" "warner:688:Warner TV" ) nowtv_chnls=( "hbohd:115:HBO HD 亚洲" "hits:111:HBO 强档巨献 亚洲" "signature:114:HBO 原创巨献 亚洲" "family:112:HBO 温馨家庭 亚洲" "cinemax:113:CINEMAX 亚洲" "foxmovies:117:Fox 电影" "foxfamily:120:Fox 家庭电影" "foxaction:118:Fox 警匪" "wsdy:139:卫视电影台" "animaxhd:150:Animax" "tvn:155:tvN" "wszw:160:卫视中文台" "discoveryasia:208:探索亚洲频道" "discovery:209:探索频道" "dwxq:210:动物星球" "discoverykx:211:探索科学频道" "dmax:212:DMAX" "tlclysh:213:TLC 旅游生活" "gjdlhk:215:国家地理 香港" "gjdlys:216:国家地理野生" "gjdlyr:217:国家地理悠人" "gjdlgj:218:国家地理国际" "bbcearth:220:BBC 地球" "history:223:历史频道" "historyhd:225:历史频道高清" "cnn:316:CNN" "foxnews:318:FOX News" "bbcworldnews:320:BBC 世界新闻" "bloomberg:321:Bloomberg 电视" "yzxw:322:亚洲新闻台" "skynews:323:天空新闻" "dw:324:DW 英文" "euronews:326:欧洲新闻" "nhk:328:NHK WORLD-JAPAN" "fhwszx:366:凤凰卫视资讯" "fhwsxg:367:凤凰卫视香港" "fhwszw:548:凤凰卫视中文" "xgws:368:香港卫视" "disney:441:迪士尼" "boomerang:445:Boomerang" "cbeebies:447:BBC CBeebies" "babytv:448:Baby tv" "bbclifestyle:502:BBC lifestyle" "comedycentral:505:Comedy Central" "warner:510:WarnerTV" "AXN:512:AXN" "blueantextreme:516:BLUE ANT Extreme" "blueantentertainmet:517:BLUE ANT Entertainment" "fox:518:FOX" "foxcrime:523:FOX CRIME" "fx:524:FX" "lifetime:525:Lifetime" "yzms:527:亚洲美食" "channelv:534:Channel [v] 国际" "zgzwws:556:浙江卫视" "foxsports:670:Fox 体育" "foxsports2:671:Fox 体育2" "foxsports3:672:Fox 体育3" "nowpremiersportstv:620:Now Sports Premier League TV" "nowpremiersports1:621:Now Sports Premier League 1" "nowpremiersports2:622:Now Sports Premier League 2" "nowpremiersports3:623:Now Sports Premier League 3" "nowpremiersports4:624:Now Sports Premier League 4" "nowpremiersports5:625:Now Sports Premier League 5" "nowpremiersports6:626:Now Sports Premier League 6" "nowsports1:631:Now Sports 1" "nowsports2:632:Now Sports 2" "nowsports3:633:Now Sports 3" "nowsports4:634:Now Sports 4" "nowsports5:635:Now Sports 5" "nowsports6:636:Now Sports 6" "nowsports7:637:Now Sports 7" ) icable_chnls=( "hkopen:001:香港开电视" "hkibc:002:香港国际财经台" "cjzx:108:财经资讯台" "xw:109:新闻台" "zbxw:110:直播新闻台" "cctv13:111:中央电视台新闻频道" "cctv4:112:中央电视台中文国际频道" "fhwszx:113:凤凰卫视资讯台" "dsyzxw:114:东森亚洲新闻台" "bbcworldnews:122:BBC WorldNews" "foxnews:123:FOX News" "cnni:124:CNNI" "cnnhlnnews:125:CNN HLN News" "nhkworldjapan:126:NHK World-Japan" "cnbchk:127:CNBC HK" "bloomberg:128:Bloomberg TV HD" "zghqdsw:129:中国环球电视网" "yzxw:130:亚洲新闻台" "russiatoday:131:Russia Today" "dw:140:DW (Deutsch)" "yxdy:201:有线电影台" "gyoz:202:光影欧洲" "wsdy:204:卫视电影台" "wsks:205:卫视卡式台" "foxmovies:214:FOX Movies" "foxfamily:215:FOX Family Movies" "foxaction:216:FOX Action Movies" "gqsryy:218:高清私人影院" "jsdy:219:惊悚电影台" "zhyl:301:综合娱乐台" "fhwsxg:304:凤凰卫视香港台" "zjpd:305:珠江频道" "fox:311:FOX" "foxlife:312:FOXlife" "fx:313:FX" "blueantentertainmet:317:Blue Ant 综合娱乐 HD" "blueantextreme:318:Blue Ant 超级娱乐 HD" "fashiontv:319:Fashion TV HD" "tvn:320:tvN HD" "nhkworldpr:322:NHK World Pr" "comedycentral:324:Comedy Central 爆笑台" "arirangtv:325:Arirang TV" "abcaustralia:326:ABC Australia" "dsyzws:331:东森亚洲卫视" "wszw:332:卫视中文台" "mtvchina:333:MTV China" "dfwsgj:334:东方卫视国际频道" "szds:335:深圳电视台" "hbws:337:湖北卫视" "cctv11:340:中央电视台戏曲频道" "cctv1:341:中央电视台综合频道" "fhwszw:376:凤凰卫视中文台" "dsyzyy:502:东森亚洲幼幼台" "dreamworks:510:梦工厂" "ktpd:511:卡通频道" "boomerang:512:Boomerang 频道" "dwx:513:达文西频道" "nickelodeon:514:Nickelodeon" "nickasia:515:Nick Jr." "babytv:516:Baby TV" "cbeebies:517:CBeebies" "zoomoo:518:ZooMoo" "fixfoxi:519:Fix & Foxi" "ybb:520:鸭宝宝" "disney:530:Disney Channel" "disneyjr:531:Disney Junior" "gqty:601:高清体育台" "sportsplus1:602:Sports Plus 1 HD" "gq603:603:高清603台" "sportsplus2:604:Sports Plus 2 HD" "sportsplus3:605:Sports Plus 3 HD" "foxsports:611:Fox Sports" "foxsports2:612:Fox Sports 2" "foxsports3:613:Fox Sports 3" "beinsports1:614:beIN Sports 1" "beinsports2:615:beIN Sports 2" "beinsportsmax:616:beIN SPORTS MAX" "yx18:618:有线18台" "yxty:661:有线体育台" "sm1:668:赛马1台" "sm2:669:赛马2台" "gjdlys:701:国家地理野生高清频道" "gjdlgq:702:国家地理高清频道" "gjdlyr:703:国家地理悠人高清频道" "discoveryasia:710:Discovery Asia" "discovery:711:Discovery 高清频道" "tlclysh:712:旅遊生活高清频道" "eve:713:EVE 高清频道" "dwxq:714:动物星球高清频道" "discoverykx:715:Discovery 科学高清频道" "dmax:716:DMAX 高清频道" "bbclifestyle:720:BBC Lifestyle 高清频道" "bbcearth:721:BBC Earth" "zghqdswjl:722:中国环球电视网记录频道" "petclubtv:730:Pet Club TV" "zeetv:851:Zee TV" "zeenews:852:Zee News" "zeecinema:853:Zee Cinema" "zing:854:Zing" "hl:901:欢乐台" "rh:902:惹火台" ) hboasia_chnls=( "hbo:1:HBO 亚洲" "hbohd:2:HBO HD 亚洲" "signature:3:HBO 原创巨献 亚洲" "family:4:HBO 温馨家庭 亚洲" "hits:5:HBO 强档巨献 亚洲" "cinemax:7:CINEMAX 亚洲" ) hbous_chnls=( "us_hbo:HBO:EAST:HBO East" "us_hbo2:HBO2:EAST:HBO 2 EAST" "us_hbosignature:HBO SIGNATURE:EAST:HBO Signature East" "us_hbofamily:HBO FAMILY:EAST:HBO family East" "us_hbocomedy:HBO COMEDY:EAST:HBO comedy East" "us_hbozone:HBO ZONE:EAST:HBO Zone East" "us_hbolatino:HBO LATINO:EAST:HBO Lation East" "us_hbo:HBO:WEST:HBO West" "us_hbo2:HBO2:WEST:HBO 2 West" "us_hbosignature:HBO SIGNATURE:WEST:HBO Signature West" "us_hbofamily:HBO FAMILY:WEST:HBO family West" "us_hbocomedy:HBO COMEDY:WEST:HBO COMEDY West" "us_hbozone:HBO ZONE:WEST:HBO Zone West" "us_hbolatino:HBO LATINO:WEST:HBO Lation West" ) ontvtonight_chnls=( "us_abc@abc@69048344@-04:00:ABC" "au_abcnews@abc-news@2141@+02:00:ABC NEWS" "us_cbs@cbs@69048345@-04:00:CBS" "us_cnn@cable-news-network@69047095@-04:00:CNN" "us_nbc@nbc@69048423@-04:00:NBC" "us_fox@fox@69048367@-05:00:FOX" "us_fs1@fox-sports-1@666266122@-04:00:FOX Sports 1" "us_msnbc@msnbc@69023101@-04:00:MSNBC" "us_amc@amc-east@69047124@-04:00:AMC" "us_nickjr@nick-jr@69047681@-04:00:Nick Jr" "us_universalkids@universal-kids@69027178@-04:00:Universal Kids" "us_disneyjr@disney-junior-hdtv-east@69044944@-04:00:Disney Junior" "us_mtvhd@mtv-hdtv-east@69032459@-04:00:MTV HD 美国" "us_mtvlive@mtv-live-hdtv@69027734@-04:00:MTV Live 美国" "uk_mtvlivehd@mtv-live-hdtv@69038784@+01:00:MTV Live HD 英国" "uk_mtvmusic@mtv-music-uk@69042501@+04:00:MTV Music 英国" "uk_mtvbase@mtv-base@69036338@+01:00:MTV Base 英国" "uk_mtvclassic@mtv-classic@69043201@+01:00:MTV Classic 英国" "uk_mtvhits@mtv-hits-eu@69036341@+01:00:MTV Hits 英国" "uk_bbcone@bbc-1@69035371@+01:00:BBC One" "us_comedycentral@comedy-central-east@69036536@-04:00:Comedy Central" "foxnews@fox-news-channel@69047256@-05:00:FOX News" "ca_tsn1@tsn@69024762@-04:15:TSN1" "ca_tsn3@tsn3@1163557839@-04:15:TSN3" "ca_tsn4@tsn4@1162010745@-04:15:TSN4" ) tvbhk_chnls=( "tvbhk_pearl:p:TVB 明珠台" "tvbhk_jade:j:TVB 翡翠台" "tvbhk_j2:b:TVB J2" "tvbhk_tvbnewschannel:c:TVB 无线新闻台" "tvbhk_tvbfinanceinformationchannel:a:TVB 无线财经资讯台" # "tvbhk_xinghe:x:TVB 星河频道" # "tvbhk_tvbclassic:e:TVB 经典台" # "tvbhk_tvbkoreandrama:k:TVB 韩剧台" # "tvbhk_tvbasiandrama:d:TVB 亚洲剧台" # "tvbhk_tvbchinesedrama:u:TVB 华语剧台" # "tvbhk_asianvariety:v:TVB 综艺旅游台" # "tvbhk_tvbfood:l:TVB 为食台" # "tvbhk_tvbclassicmovies:w:粤语片台" ) singteltv_chnls=( "my_ch5:2:Channel 5" "my_ch8:3:Channel 8" "my_chu:7:Channel U" "my_kidschannel:243:少儿频道" "my_ele:501:e-Le" "my_jiale:502:佳乐" "my_starchinese:507:卫视中文台" "my_tvbjade:511:TVB 翡翠台 东南亚" "my_nowjelli:512:now Jelli 紫金国际台" "my_one:513:One HD" "my_xingkong:516:星空卫视" "my_xinghe:517:TVB 星河" "my_tvn:518:tvN HD" "my_gem:519:GEM" "my_ettvasia:521:东森亚洲卫视" "my_oh!k:525:Oh!K" "my_entertainment:531:Entertainment" "my_cbo:532:中国电视剧频道" "my_foodandhealth:533:美食健康频道" "my_cctventertainment:534:CCTV 娱乐" "my_dragontvintl:535:东方卫视" "my_channelvchina:547:Channel [V] 中国" "my_mtvchina:550:MTV 中国" "my_cctv4:555:CCTV 4" "my_ctiasia:557:中天亚洲台" "my_ettvnews:561:ETTV 东森新闻" "my_scmhd:571:卫视电影台" "my_scmlegend:573:卫视卡式台" "my_ccm:580:天映经典频道" "my_celestialmovies:585:天映频道" ) cntv_chnls=( "cctv1" "cctv2" "cctv3" "cctv4" "cctv5" "cctv6" "cctv7" "cctv8" "cctvjilu:CCTV 9 纪录频道" "cctv10" "cctv11" "cctv12" "cctv13" "cctvchild:CCTV 14 少儿频道" "cctv15" "cctv5plus:CCTV 5+" "cctv17" "cctveurope:CCTV 中文国际频道" "cctvamerica:CCTV America" ) tvbs_chnls=( "tvbsxw:1:TVBS 新闻" "tvbshl:2:TVBS 欢乐台" "tvbshd:3:TVBS HD" "tvbsjc:4:TVBS 精采台" "tvbsyz:5:TVBS 亚洲" ) astro_chnls=( "iqiyi:355:astro 爱奇艺" "my_tvbclassic:425:TVB 经典台" "astrobeinsports:236:beIN Sports HD" "astrobeinsportsmax:313:beIN Sports MAX HD" "astrosupersport:154:Astro SuperSport HD" "astrosupersport2:138:Astro SuperSport 2 HD" "astrosupersport3:164:Astro SuperSport 3 HD" "astrosupersport4:241:Astro SuperSport 4 HD" "astrosupersport5:455:Astro SuperSport 5 HD" ) nbcsn_chnls=( "nbcsn:NBCSN:NBCSN" "nbcsnhd:NBCSNHD:NBCSN HD" ) beinsports_chnls=( "beinsports:1:beinsports" "beinsportsar:2:beinsports AR" "beinsports1:3:beinsports 1" "beinsports2:4:beinsports 2" "beinsports3:5:beinsports 3" "beinsports4:6:beinsports 4" "beinsports5:7:beinsports 5" "beinsports6:8:beinsports 6" "beinsports7:9:beinsports 7" "beinsportspremium1:10:beinsports premium 1" "beinsportspremium2:11:beinsports premium 2" "beinsportspremium3:12:beinsports premium 3" "beinsportsxtra1:13:beinsports xtra 1" "beinsportsxtra2:14:beinsports xtra 2" "beinsports4k:15:beinsports 4K" "beinsportsnba:16:beinsports NBA" "beinsportsenglish1:17:beinsports english 1" "beinsportsenglish2:18:beinsports english 2" "beinsportsenglish3:19:beinsports english 3" "beinsportsfrench1:20:beinsports french 1" "beinsportsfrench2:21:beinsports french 2" "beinsportsfrench3:22:beinsports french 3" ) beinsportsau_chnls=( "beinsports1au:BEINSP1:beinsports 1 AU" "beinsports2au:BEINSP2:beinsports 2 AU" "beinsports3au:BEINSP3:beinsports 3 AU" ) supersport_chnls=( "supersportpremierleague:SS Premier League:SuperSport Premier League" "supersportespn:ESPN HD:ESPN HD" "supersportespn2:ESPN 2 HD:ESPN2 HD" "supersportfootball:SS Football:SuperSport Football" "supersportpsl:SS PSL:SuperSport PSL" "supersportcricket:SS Cricket:SuperSport Cricket" "supersportgrandstand:SS Grandstand:SuperSport Grandstand" "supersportrugby:SS Rugby:SuperSport Rugby" "supersportgolf:SS Golf:SuperSport Golf" "supersportaction:SS Action:SuperSport Action" "supersportblitz:SS Blitz:SuperSport Blitz" "supersportlaliga:SS La Liga:SuperSport La Liga" "supersportmotorsport:SS Motorsport:SuperSport Motorsport" "supersportplay:SS Play:SuperSport Play" "supersportmaximo1:SS Maximo 1:SuperSport MáXimo 1" "supersporttennis:SS Tennis:SuperSport Tennis" "supersportginx:Ginx eSports HD:Ginx eSports TV" # "supersportwwe:WWE Channel" # "supersportcsn:Community Services Network" "supersportvariety1:SS Variety 1:SuperSport Variety 1" "supersportvariety2:SS Variety 2:SuperSport Variety 2" "supersportvariety3:SS Variety 3:SuperSport Variety 3" "supersportvariety4:SS Variety 4:SuperSport Variety 4" ) btsport_chnls=( #"btsportespn:BT Sport//ESPN" "btsportultimate:hspr:BT Sport Ultimate" "btsport1:hspc:BT Sport 1" "btsport2:hspd:BT Sport 2" "btsport3:hspf:BT Sport 3" "btsport4:hspg:BT Sport 4" ) btsporton_chnls=( "btsportespn:BT Sport ESPN" "btsport1:BT Sport 1" "btsport2:BT Sport 2" "btsport3:BT Sport 3" "btsportextra1:BT Sport Extra 1" "btsportextra2:BT Sport Extra 2" "btsportextra3:BT Sport Extra 3" "btsportextra4:BT Sport Extra 4" "btsportultimate:BT Sport Ultimate" "digitalexclusive:Digital Exclusive" ) premiersports_chnls=( "premiersports1:premiersports1:premier sports 1" "premiersports2:premiersports2:premier sports 2" "laligatv:LaLiga:LaLiga TV" "freesports:freesports:freesports" "boxnation:Boxnation:Boxnation" ) sky_chnls=( "skysportspremierleague:1303:SkySp PL" "skysportsmainevent:1301:SkySpMainEv" "skysportsfootball:3838:SkySp F'ball" "skysportscricket:1302:SkySp Cricket " "premiersports1:5153:Premier 1 HD" "premiersports2:1634:Premier 2 HD" "laligatv:1015:LaLigaTV HD" ) _4gtv_chnls=( "minshidiyi:4gtv-4gtv003:民視第一台" "minshitaiwan:4gtv-4gtv001:民視台灣台" "minshi:4gtv-4gtv002:民視" "zsjc:4gtv-4gtv064:中視菁采台" "zs:4gtv-4gtv040:中視" "zsjd:4gtv-4gtv080:中視經典台" "hs:4gtv-4gtv041:華視" "slzh:4gtv-live207:三立綜合台" "kjds:4gtv-4gtv043:客家電視台" "bdzy:4gtv-4gtv039:八大綜藝台" "tvbsjc:4gtv-4gtv086:TVBS精采台" "aedyl:4gtv-4gtv070:愛爾達娛樂台" "jtzh:4gtv-4gtv046:靖天綜合台" "jtrb:4gtv-4gtv047:靖天日本台" "xtryt:4gtv-4gtv050:新唐人亞太台" "ztzh:4gtv-4gtv033:中天綜合台" "kzyyjs:4gtv-4gtv012:空中英語教室" "dwx:4gtv-4gtv018:達文西頻道" "eltvshyy:litv-longturn20:ELTV生活英語台" "nickjr:4gtv-4gtv032:Nick Jr. 兒童頻道" "nickelodeon:4gtv-live105:Nickelodeon" "lhkt:litv-longturn01:龍華卡通台" "jtkt:4gtv-4gtv044:靖天卡通台" "jykt:4gtv-4gtv057:靖洋卡通Nice Bingo" "ifundm:litv-ftv15:i-Fun動漫台" "ifundm2:litv-ftv11:i-Fun動漫台2" "ifundm3:litv-ftv12:i-Fun動漫台3" "babyfirst:litv-longturn02:Baby First" "momoqz:4gtv-live107:MOMO親子台" "cnkt:4gtv-live205:CN卡通" "dsgw1:4gtv-live047:東森購物一台" "dsxw:litv-ftv14:東森新聞台" "ztxw:4gtv-4gtv009:中天新聞台" "msxw:litv-ftv13:民視新聞台" "slcjxw:4gtv-4gtv089:三立財經新聞iNEWS" "tvbsxw:4gtv-4gtv072:TVBS新聞" "dscjxw:4gtv-4gtv019:東森財經新聞台" "zsxw:4gtv-4gtv074:中視新聞" "hsxw:4gtv-4gtv052:華視新聞" "hyxw:litv-longturn14:寰宇新聞台" "hyxwtw:litv-longturn15:寰宇新聞台灣台" "zhcj:4gtv-4gtv060:中華財經台" "dsgw2:4gtv-live046:東森購物二台" "mszy:4gtv-4gtv004:民視綜藝台" "zglgtx:4gtv-4gtv006:豬哥亮歌廳秀" "jtyl:4gtv-4gtv062:靖天育樂台" "jtgj:4gtv-4gtv063:KLT-靖天國際台" "jthl:4gtv-4gtv054:Nice TV 靖天歡樂台" "jtzx:4gtv-4gtv065:靖天資訊台" "hymbczh:4gtv-4gtv015:華藝MBC綜合台" "hgyl:4gtv-4gtv016:韓國娛樂台 KMTV" "comedycentral:4gtv-4gtv024:Comedy Central 爆笑頻道" "lifetime:4gtv-4gtv029:Lifetime 娛樂頻道" "dyys:4gtv-live031:電影原聲台CMusic" "traceurban:4gtv-4gtv082:TRACE Urban" "mtvlivetw:4gtv-live025:MTV Live HD 音樂頻道" "mezzolive:4gtv-4gtv083:Mezzo Live HD" "classica:4gtv-4gtv059:CLASSICA 古典樂" "mdqys:litv-longturn16:梅迪奇藝術頻道" "bsgq:litv-longturn05:博斯高球台" "bsgq2:litv-longturn06:博斯高球二台" "bsyd:litv-longturn07:博斯運動一台" "bswx:litv-longturn10:博斯無限台" "bswq:litv-longturn09:博斯網球台" "bsyd2:litv-longturn08:博斯運動二台" "bsml:litv-longturn04:博斯魅力台" "tracesportstars:4gtv-4gtv077:TRACE Sport Stars" "zlty:4gtv-4gtv101:智林體育台" "ssydx:4gtv-4gtv014:時尚運動X" "cmtv:4gtv-live201:車迷TV" "ginxesportstv:4gtv-4gtv053:GINX Esports TV" "yzly:litv-longturn17:亞洲旅遊台" "msly:litv-ftv07:民視旅遊台" "outdoor:4gtv-4gtv078:Outdoor Channel" "travel:4gtv-4gtv079:Travel Channel" "fashionone:litv-longturn19:Fashion One" "xfkjjj:4gtv-live206:幸福空間居家台" "lovenature:4gtv-live208:Love Nature" "history:4gtv-4gtv026:History 歷史頻道" "history2:4gtv-4gtv028:HISTORY 2 頻道" "smithsonian:4gtv-4gtv088:Smithsonian Channel" "smartzs:4gtv-4gtv076:SMART 知識頻道" "techstorm:4gtv-live109:TechStorm" "luxe:4gtv-live121:LUXE TV Channel" "tv5monde:4gtv-live122:TV5MONDE STYLE HD 生活時尚" "gsxj:4gtv-4gtv042:公視戲劇" "msyj:litv-ftv09:民視影劇台" "lhxj:litv-longturn18:龍華戲劇台" "lhox:litv-longturn12:龍華偶像台" "lhrh:litv-longturn11:龍華日韓台" "bdjc:4gtv-4gtv034:八大精彩台" "tyjd:4gtv-4gtv112:天映經典頻道" "jtxj:4gtv-4gtv058:靖天戲劇台" "jyxj:4gtv-4gtv045:靖洋戲劇台" "cizazt:4gtv-4gtv027:CI 罪案偵查頻道" "snhrjs:4gtv-4gtv013:視納華仁紀實頻道" "ymjs:4gtv-4gtv105:影迷數位紀實台" "jgbdx:4gtv-4gtv144:金光布袋戲" "blueantextreme:4gtv-live138:BLUE ANT EXTREME" "ccyj:4gtv-4gtv049:采昌影劇台" "jtyh:4gtv-4gtv055:靖天映畫" "jtdy:4gtv-4gtv061:靖天電影台" "lhdy:litv-longturn03:龍華電影台" "ydy1:4gtv-4gtv075:优電影一台" "ydy2:4gtv-4gtv081:优電影二台" "ydy3:4gtv-4gtv087:优電影三台" "ymdy:4gtv-4gtv011:影迷數位電影台" "amc:4gtv-4gtv017:AMC" "cinemaworld:4gtv-4gtv069:CinemaWorld" "warner:4gtv-4gtv037:Warner TV" "catchplaydy:4gtv-live048:CATCHPLAY電影台" "mycinemaeurope:4gtv-live157:My Cinema Europe HD 我的歐洲電影" "hxx2:litv-ftv17:好消息2台" "hxx:litv-ftv16:好消息" "dads:4gtv-4gtv007:大愛電視" "da2:4gtv-4gtv106:大愛二台" "rjws:4gtv-4gtv008:人間衛視" "bdgjxw:litv-ftv10:半島國際新聞台" "voamgzy:litv-ftv03:VOA美國之音" "cnbcasiacj:4gtv-4gtv030:CNBC Asia 財經台" "dwdgzs:4gtv-4gtv071:DW德國之聲" "vtv4:4gtv-4gtv108:VTV4" "cnnttxw:4gtv-live203:CNN頭條新聞台" "cnngjxw:4gtv-live204:CNN國際新聞台" "gh1:4gtv-4gtv084:國會頻道1" "gh2:4gtv-4gtv085:國會頻道2" "petclubtv:4gtv-live110:Pet Club TV" "tvbshd:4gtv-4gtv073:TVBS" "hitshd:4gtv-live620:HITS" "sbnqqcj:4gtv-4gtv060:sbn 全球财经台" "tvbshl:4gtv-4gtv068:TVBS 欢乐台" "liveabc:4gtv-live030:LiveABC 互動英語頻道" "arirang:4gtv-live202:阿里郎頻道" "gdlrollor:4gtv-live012:滚动力roller" "jdkt:4gtv-live022:经典卡通台" "etkt:4gtv-live009:儿童卡通台" "jxdm:4gtv-live024:精选动漫台" ) other_chnls=( "disneyjr:迪士尼儿童频道(台湾)" "foxmovies:fox movies (台湾)" "amlh:澳门莲花" "foxnews:FOX News" ) providers=( "jiushi:就是 节目表" "niotv:niotv 节目表" "nowtv:nowtv 节目表" "icable:i-cable 节目表" "hboasia:hbo 亚洲 节目表" "hbous:hbo 美国 节目表" "ontvtonight:ontvtonight 节目表" "tvbhk:TVB 香港 节目表" "singteltv:singteltv 节目表" "cntv:cntv 节目表" "tvbs:tvbs 节目表" "astro:astro 节目表" "nbcsn:nbcsn 节目表" "beinsports:beinsports 节目表" "beinsportsau:beinsports AU 节目表" "supersport:supersport 节目表" "btsport:bt sport 节目表" "premiersports:premier sports 节目表" "sky:Sky 节目表" "_4gtv:4gtv 节目表" "other:其它节目表" ) if [ "${2:-}" == "4gtv" ] then provider_id="_4gtv" elif [ "${2:-}" == "hbo" ] then provider_id="hboasia" elif [ "${2:-}" == "si" ] then provider_id="singteltv" elif [ "${2:-}" == "be" ] then provider_id="beinsports" elif [ "${2:-}" == "beau" ] then provider_id="beinsportsau" elif [ "${2:-}" == "su" ] then provider_id="supersport" elif [ "${2:-}" == "bt" ] then provider_id="btsport" elif [ "${2:-}" == "bton" ] then provider_id="btsporton" elif [ "${2:-}" == "pr" ] then provider_id="premiersports" elif [ "${2:-}" == "sk" ] then provider_id="sky" else provider_id=${2:-} fi if [ -n "${3:-}" ] then # variable indirection var=("$provider_id"_chnls[@]) if [[ -n ${!var:-} ]] then provider_found=0 for chnl in "${!var}" do chnl_id=${chnl%%@*} chnl_id=${chnl_id%%:*} if [ "$chnl_id" == "$3" ] then provider_found=1 unset "$provider_id"_chnls IFS= read -r -a "$provider_id"_chnls <<< "$chnl" break fi done [ "$provider_found" -eq 0 ] && Println "$error 没有找到频道\n" && exit 1 fi fi case $provider_id in jiushi) ScheduleJiushi ;; niotv) ScheduleNiotv ;; nowtv) ScheduleNowtv ;; icable) ScheduleIcable ;; hbo|hboasia) ScheduleHboasia ;; hbous) ScheduleHbous "${4:-}" ;; ontvtonight) ScheduleOntvtonight ;; tvbhk) ScheduleTvbhk ;; tvbhd) ScheduleTvbhd ;; si|singteltv) ScheduleSingteltv ;; cntv) ScheduleCntv ;; tvbs) ScheduleTvbs ;; astro) ScheduleAstro ;; nbcsn) ScheduleNbcsn ;; be|beinsports) ScheduleBeinsports ;; beau|beinsportsau) ScheduleBeinsportsau ;; su|supersport) ScheduleSupersport ;; bt|btsport) ScheduleBtsport ;; pr|premiersports) SchedulePremiersports ;; sk|sky) ScheduleSky ;; _4gtv) Schedule_4gtv ;; other) ScheduleOther ;; "") Println "节目表计划任务面板 ${green}1.${normal} 查看频道 ${green}2.${normal} 添加频道 ${green}3.${normal} 删除频道 ${green}4.${normal} 查看任务 ${green}5.${normal} 执行任务 ${green}6.${normal} 开启计划任务 ${green}7.${normal} 关闭计划任务 ${green}8.${normal} 备份任务 ${green}9.${normal} 查看备份 ${green}10.${normal} 修改备份 ${green}11.${normal} 恢复备份 ${green}12.${normal} 删除备份 " read -p "$i18n_default_cancel" cron_num [ -z "$cron_num" ] && Println "$i18n_canceled...\n" && exit 1 case $cron_num in 1) ScheduleView ;; 2) ScheduleAdd ;; 3) ScheduleDel ;; 4) ScheduleViewCron ;; 5) ScheduleExec ;; 6) ScheduleEnableCron ;; 7) ScheduleDisableCron ;; 8) ScheduleBackup ;; 9) ScheduleViewBackup ;; 10) ScheduleEditBackup ;; 11) ScheduleRestoreBackup ;; 12) ScheduleDelBackup ;; *) Println "$i18n_canceled...\n" && exit 1 ;; esac ;; "-") while IFS= read -r line do if [[ $line == *"错误"* ]] then Println "错误: ${line#* }" fi done < <(ScheduleExec) ;; *) for provider in "${providers[@]}" do provider_id=${provider%%:*} provider_name=${provider#*:} provider_chnls=("$provider_id"_chnls[@]) schedule="" for provider_chnl in "${!provider_chnls}" do if [ "$provider_id" == "ontvtonight" ] then chnl_id=${provider_chnl%%@*} chnl_name=${provider_chnl##@:} else chnl_id=${provider_chnl%%:*} chnl_name=${provider_chnl##*:} fi if [ "$chnl_id" == "$2" ] then unset "${provider_id}_chnls" IFS="|" read -r -a "${provider_id}_chnls" <<< "${provider_chnl}|" if [ "$provider_id" == "other" ] then chnl_id_upper=$(tr '[:lower:]' '[:upper:]' <<< "${chnl_id:0:1}")"${chnl_id:1}" Schedule"$chnl_id_upper" "${3:-}" else provider_id_upper=$(tr '[:lower:]' '[:upper:]' <<< "${provider_id:0:1}")"${provider_id:1}" Schedule"$provider_id_upper" "${3:-}" fi break fi done if [ -z "${schedule:-}" ] then Println "$error $provider_name 没有找到: $2" else if [ -e "$IPTV_ROOT/vip.pid" ] then printf '%s' "" > "$VIP_USERS_ROOT/epg.update" fi break fi done ;; esac } ImgcatInstall() { echo ExitOnList y "`gettext \"缺少 imgcat, 是否现在安装\"`" Progress & progress_pid=$! trap ' kill $progress_pid wait $progress_pid 2> /dev/null ' EXIT if [ "$dist" == "rpm" ] then yum -y install gcc gcc-c++ make ncurses-devel autoconf libjpeg-turbo-devel libpng-devel >/dev/null 2>&1 echo -n "...50%..." else apt-get -y install debconf-utils libncurses5-dev autotools-dev autoconf libjpeg-dev libpng-dev >/dev/null 2>&1 echo '* libraries/restart-without-asking boolean true' | debconf-set-selections apt-get -y install software-properties-common pkg-config build-essential >/dev/null 2>&1 echo -n "...50%..." fi cd ~ if [ ! -d imgcat-master ] then wget --timeout=10 --tries=3 --no-check-certificate "$FFMPEG_MIRROR_LINK/imgcat.zip" -qO imgcat.zip unzip imgcat.zip >/dev/null 2>&1 fi cd ./imgcat-master rm -rf CImg wget --timeout=10 --tries=3 --no-check-certificate "$FFMPEG_MIRROR_LINK/CImg.zip" -qO CImg.zip unzip CImg.zip >/dev/null 2>&1 mv CImg-master CImg ./configure >/dev/null 2>&1 make >/dev/null 2>&1 make install >/dev/null 2>&1 kill $progress_pid wait $progress_pid 2> /dev/null || true trap - EXIT echo -n "...100%" && Println "$info imgcat 安装完成" } TsGetToken() { if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" -H 'Content-Type: application/json' --data '{"role":"guest","deviceno":"'"$device_no"'","deviceType":"yuj"}' "$access_token_url") then Println "$error 无法连接服务器, 请重试" return 1 fi IFS=" " read -r ret access_token refresh_token refresh_time < <($JQ_FILE -r '[.ret,.accessToken,.refreshToken,.refreshTime]|join(" ")' <<< "$res") if [ "$ret" != 0 ] || [ -z "$access_token" ] || [ -z "$refresh_token" ] || [ -z "$refresh_time" ] then echo "$res" Println "$error 获取 token 错误, 请重试" return 2 fi Println "$info 请等待 $refresh_time 秒..." sleep $refresh_time if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" -H 'Content-Type: application/json' --data '{"accessToken":"'"$access_token"'","refreshToken":"'"$refresh_token"'"}' "$refresh_access_token_url") then Println "$error 无法连接服务器, 请重试" return 3 fi IFS=" " read -r ret access_token refresh_token refresh_time < <($JQ_FILE -r '[.ret,.accessToken,.refreshToken,.refreshTime]|join(" ")' <<< "$res") if [ "$ret" != 0 ] || [ -z "$access_token" ] || [ -z "$refresh_token" ] || [ -z "$refresh_time" ] then echo "$res" Println "$error 获取 token 错误, 请重试" return 4 fi printf -v now '%(%s)T' -1 refresh=$((now+refresh_time)) } TsValidateLogin() { if [ "$1" == "$i18n_cancel" ] || [[ $1 =~ ^[0-9]{11}$ ]] || [[ $1 =~ ^[A-Za-z][A-Za-z0-9_]{3,13}$ ]] then return 0 fi return 1 } TsValidateUserName() { if [ "$1" == "$i18n_cancel" ] || [[ $1 =~ ^[A-Za-z][A-Za-z0-9_]{3,13}$ ]] then return 0 fi return 1 } TsValidatePhoneNumber() { if [ "$1" == "$i18n_cancel" ] || [[ $1 =~ ^[0-9]{11}$ ]] then return 0 fi return 1 } TsMenu() { GetDefault ShFallback ts_options=("$SH_FALLBACK/$TS_CHANNELS") if [ -n "$d_sync_file" ] then ts_options+=($d_sync_file) fi ts_options+=("手动输入") echo inquirer list_input_index "选择使用的频道文件" ts_options ts_options_index if [ "$ts_options_index" -eq $((${#ts_options[@]}-1)) ] then echo ExitOnText "请输入使用的频道文件链接或本地路径" ts_option else ts_option="${ts_options[ts_options_index]}" fi if [[ $ts_option =~ ^https?:// ]] then if ! ts_source=$(curl -s -Lm 10 "$ts_option") then Println "$error 无法连接 $ts_option\n" exit 1 fi else if [ ! -f "$ts_option" ] then Println "$error $ts_option 不存在\n" exit 1 fi ts_source=$(< "$ts_option") fi [ -z "${delimiters:-}" ] && delimiters=( $'\001' $'\002' $'\003' $'\004' $'\005' $'\006' ) EXIT_STATUS=0 IFS=$'\004\t' read -r m_name m_desc m_user_agent m_verify_type m_verify_acc_type m_reg_type \ m_server_version m_unique_url m_access_token_url m_refresh_access_token_url m_third_token_url m_play_url m_playback_url m_list_url \ m_img_url m_sms_url m_verify_url m_reg_url m_login_url m_auth_info_url m_auth_verify_url m_schedule_url \ m_event_info_url m_operating_system m_system_version m_model m_app_version < <(JQs flat "$ts_source" '.[0].data | map(select(.reg_url != null)|with_entries(select(.key as $k | ["name","desc","user_agent","verify_type","verify_acc_type","reg_type","server_version", "extend_info","unique_url","access_token_url","refresh_access_token_url","third_token_url","play_url","playback_url","list_url","img_url", "sms_url","verify_url","reg_url","login_url","auth_info_url","auth_verify_url","schedule_url","event_info_url"] | index($k))))' ' . as $source | ($source.extend_info // {} | if (.|type) == "string" then {} else . end) as $extend_info | reduce ({name,desc, user_agent,verify_type,verify_acc_type,reg_type,server_version,unique_url, access_token_url,refresh_access_token_url,third_token_url,play_url,playback_url,list_url,img_url,sms_url,verify_url,reg_url, login_url,auth_info_url,auth_verify_url,schedule_url,event_info_url}|keys_unsorted[]) as $key ([]; $source[$key] as $val | if $val then . + [$val + "\u0002\u0004"] else . + ["\u0004"] end ) + reduce ({operatingsystem,systemversion,model,appversion}|keys_unsorted[]) as $key ([]; $extend_info[$key] as $val | if $val then . + [$val + "\u0003\u0004"] else . + ["\u0004"] end )|@tsv' "${delimiters[@]}") || EXIT_STATUS=$? if [ "$EXIT_STATUS" -ne 0 ] || [ -z "$m_name" ] then Println "$error 没有找到频道\n" exit 1 fi IFS="${delimiters[1]}" read -ra ts_name <<< "$m_name" IFS="${delimiters[1]}" read -ra ts_desc <<< "$m_desc" IFS="${delimiters[1]}" read -ra ts_user_agent <<< "$m_user_agent" IFS="${delimiters[1]}" read -ra ts_verify_type <<< "$m_verify_type" IFS="${delimiters[1]}" read -ra ts_verify_acc_type <<< "$m_verify_acc_type" IFS="${delimiters[1]}" read -ra ts_reg_type <<< "$m_reg_type" IFS="${delimiters[1]}" read -ra ts_server_version <<< "$m_server_version" IFS="${delimiters[1]}" read -ra ts_unique_url <<< "$m_unique_url" IFS="${delimiters[1]}" read -ra ts_access_token_url <<< "$m_access_token_url" IFS="${delimiters[1]}" read -ra ts_refresh_access_token_url <<< "$m_refresh_access_token_url" IFS="${delimiters[1]}" read -ra ts_third_token_url <<< "$m_third_token_url" IFS="${delimiters[1]}" read -ra ts_play_url <<< "$m_play_url" IFS="${delimiters[1]}" read -ra ts_playback_url <<< "$m_playback_url" IFS="${delimiters[1]}" read -ra ts_list_url <<< "$m_list_url" IFS="${delimiters[1]}" read -ra ts_img_url <<< "$m_img_url" IFS="${delimiters[1]}" read -ra ts_sms_url <<< "$m_sms_url" IFS="${delimiters[1]}" read -ra ts_verify_url <<< "$m_verify_url" IFS="${delimiters[1]}" read -ra ts_reg_url <<< "$m_reg_url" IFS="${delimiters[1]}" read -ra ts_login_url <<< "$m_login_url" IFS="${delimiters[1]}" read -ra ts_auth_info_url <<< "$m_auth_info_url" IFS="${delimiters[1]}" read -ra ts_auth_verify_url <<< "$m_auth_verify_url" IFS="${delimiters[1]}" read -ra ts_schedule_url <<< "$m_schedule_url" IFS="${delimiters[1]}" read -ra ts_event_info_url <<< "$m_event_info_url" IFS="${delimiters[2]}" read -ra ts_operating_system <<< "$m_operating_system" IFS="${delimiters[2]}" read -ra ts_system_version <<< "$m_system_version" IFS="${delimiters[2]}" read -ra ts_model <<< "$m_model" IFS="${delimiters[2]}" read -ra ts_app_version <<< "$m_app_version" rm -rf /tmp/cookie_jar mkdir -p /tmp/cookie_jar while true do unset phone_number echo inquirer list_input_index "选择直播源" ts_desc ts_index if [ "${ts_name[ts_index]}" == "jxtvnet" ] then lan_options=( '不选择' '电信' '联通' ) Println "$tip 部分地区不选择线路可能无法连接" inquirer list_input_index "选择线路" lan_options lan_options_index sed -i '/.*.jxtvnet.tv/d' /etc/hosts if [ "$lan_options_index" -eq 1 ] then echo -e "59.63.205.33 access.jxtvnet.tv\n59.63.205.33 stream.slave.jxtvnet.tv\n59.63.205.33 slave.jxtvnet.tv" >> /etc/hosts elif [ "$lan_options_index" -eq 2 ] then echo -e "58.17.42.129 access.jxtvnet.tv\n58.17.42.129 stream.slave.jxtvnet.tv\n58.17.42.129 slave.jxtvnet.tv" >> /etc/hosts fi fi name="${ts_name[ts_index]}" desc="${ts_desc[ts_index]}" user_agent="${ts_user_agent[ts_index]:-$USER_AGENT_PHONE}" verify_type="${ts_verify_type[ts_index]}" verify_acc_type="${ts_verify_acc_type[ts_index]}" reg_type="${ts_reg_type[ts_index]}" server_version="${ts_server_version[ts_index]}" unique_url="${ts_unique_url[ts_index]}" access_token_url="${ts_access_token_url[ts_index]}" refresh_access_token_url="${ts_refresh_access_token_url[ts_index]}" third_token_url="${ts_third_token_url[ts_index]}" play_url="${ts_play_url[ts_index]}" playback_url="${ts_playback_url[ts_index]}" list_url="${ts_list_url[ts_index]}" img_url="${ts_img_url[ts_index]}" sms_url="${ts_sms_url[ts_index]}" verify_url="${ts_verify_url[ts_index]}" reg_url="${ts_reg_url[ts_index]}" login_url="${ts_login_url[ts_index]}" auth_info_url="${ts_auth_info_url[ts_index]}" auth_verify_url="${ts_auth_verify_url[ts_index]}" schedule_url="${ts_schedule_url[ts_index]}" event_info_url="${ts_event_info_url[ts_index]}" operating_system="${ts_operating_system[ts_index]}" system_version="${ts_system_version[ts_index]}" model="${ts_model[ts_index]}" app_version="${ts_app_version[ts_index]}" GetServiceAccs "$name" accs_options=() for((ts_accs_index=0;ts_accs_index "$TMP_FILE" /usr/local/bin/imgcat --half-height "$TMP_FILE" rm -f "$TMP_FILE" trap - EXIT echo inquirer text_input "输入图片验证码" pincode "刷新" if [ "$pincode" != "刷新" ] then while true do if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" ${cookie_jar[@]+"${cookie_jar[@]}"} "$sms_url?pincode=$pincode&picid=$picid&verifytype=$verify_type&account=$phone_number&accounttype=$verify_acc_type") || [ $($JQ_FILE -r '.ret' <<< "$res") != 0 ] then echo "$res" Println "$error 无法获取短信验证码, 请重试" continue 2 fi printf -v now '%(%s)T' -1 next=$((now+120)) echo inquirer text_input "输入短信验证码" smscode "再次发送" if [ "$smscode" == "再次发送" ] then printf -v now '%(%s)T' -1 if [ "$now" -lt "$next" ] then for((secs=next-now;secs>0;secs--)); do echo -en "\r$secs 秒后重试" sleep 1 done fi continue 2 fi if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" ${cookie_jar[@]+"${cookie_jar[@]}"} "$verify_url?verifycode=$smscode&verifytype=$verify_type&username=$user_name&account=$phone_number") then Println "$error 无法连接服务器, 请重试" continue 3 fi IFS=" " read -r ret code < <($JQ_FILE -r '[.ret,.code]|join(" ")' <<< "$res") if [ "$ret" != 0 ] || [ -z "$code" ] then echo "$res" Println "$error 短信验证码错误, 请重试" continue 2 fi md5_password=$(printf '%s' "$password" | md5sum) md5_password=${md5_password%% *} printf -v timestamp '%(%s)T' -1 timestamp=$((timestamp * 1000)) signature="$login|$md5_password|$device_no|yuj|$timestamp" signature=$(printf '%s' "$signature" | md5sum) signature=${signature%% *} if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" -H 'Content-Type: application/json' ${cookie_jar[@]+"${cookie_jar[@]}"} --data '{"account":"'"$phone_number"'","deviceno":"'"$device_no"'","devicetype":"yuj","code":"'"$code"'","signature":"'"$signature"'","birthday":"1970-1-1","username":"'"$user_name"'","type":'"$reg_type"',"timestamp":"'"$timestamp"'","pwd":"'"$md5_password"'","accounttype":'"$reg_acc_type"'}' "$reg_url") || [ $($JQ_FILE -r '.ret' <<< "$res") != 0 ] then echo "$res" Println "$error 注册失败, 请重试" continue 3 fi Println "$info 注册成功, 请稍等..." sleep 3 break 2 done fi done fi else echo acc_options=( '登陆' '删除' ) inquirer list_input_index "选择操作" acc_options acc_options_index if [ "$acc_options_index" -eq 1 ] then jq_path='["'"$name"'","accounts",'"$accs_options_index"']' JQ delete "$SERVICES_FILE" Println "$info 账号删除成功" continue fi user_name="${ts_accs_user_name[accs_options_index]}" phone_number="${ts_accs_phone_number[accs_options_index]}" password="${ts_accs_password[accs_options_index]}" access_token="${ts_accs_access_token[accs_options_index]}" device_no="${ts_accs_device_no[accs_options_index]}" device_id="${ts_accs_device_id[accs_options_index]}" refresh="${ts_accs_refresh[accs_options_index]}" if [ -n "$refresh" ] then printf -v now '%(%s)T' -1 if [ "$now" -lt "$refresh" ] then break fi fi if [ -z "$device_no" ] then device_no=$(< /proc/sys/kernel/random/uuid) str=$(printf '%s' "$device_no" | md5sum) str=${str%% *} str=${str:7:1} device_no="$device_no$str" fi if ! TsGetToken then continue fi if [ -s "/tmp/cookie_jar/$name" ] then cookie_jar=( -b "/tmp/cookie_jar/$name" ) else cookie_jar=() fi fi md5_password=$(printf '%s' "$password" | md5sum) md5_password=${md5_password%% *} if [ -z "$img_url" ] then login_acc_type=2 if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" ${cookie_jar[@]+"${cookie_jar[@]}"} "$login_url?deviceno=$device_no&devicetype=3&accounttype=$login_acc_type&accesstoken=(null)&account=$user_name&pwd=$md5_password&isforce=1&businessplatform=1") then Println "$error 无法登陆直播源, 请重试" continue fi else printf -v timestamp '%(%s)T' -1 timestamp=$((timestamp * 1000)) if [[ $user_name =~ ^[A-Za-z][A-Za-z0-9_]{3,13}$ ]] then login_acc_type=2 login="$user_name" else login_acc_type=3 login="$phone_number" fi signature="$device_no|yuj|$login_acc_type|$login|$timestamp" signature=$(printf '%s' "$signature" | md5sum) signature=${signature%% *} if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" -H 'Content-Type: application/json' ${cookie_jar[@]+"${cookie_jar[@]}"} --data '{"account":"'"$login"'","deviceno":"'"$device_no"'","pwd":"'"$md5_password"'","devicetype":"yuj","businessplatform":1,"signature":"'"$signature"'","isforce":1,"extendinfo":{"operatingsystem":"'"$operating_system"'","systemversion":"'"$system_version"'","model":"'"$model"'","appversion":"'"$app_version"'"},"serverVersion":"'"$server_version"'","timestamp":"'"$timestamp"'","accounttype":'"$login_acc_type"'}' "$login_url") then Println "$error 无法登陆直播源, 请重试" continue fi fi IFS=" " read -r ret access_token device_id user_name < <($JQ_FILE -r '[.ret,.access_token,.device_id,.user_name]|join(" ")' <<< "$res") if [ "$ret" != 0 ] || [ -z "$access_token" ] || [ -z "$device_id" ] || [ -z "$user_name" ] then echo "$res" Println "$error 登陆错误, 请重试" continue fi if [ "$accs_options_index" -lt "$ts_accs_count" ] then update=$( $JQ_FILE -n --arg access_token "$access_token" --arg device_no "$device_no" \ --arg device_id "$device_id" --arg refresh "$refresh" \ '{ access_token: $access_token, device_no: $device_no, device_id: $device_id | tonumber, refresh: $refresh | tonumber }' ) merge=true jq_path='["'"$name"'","accounts",'"$accs_options_index"']' JQ update "$SERVICES_FILE" "$update" else if [ -z "${phone_number:-}" ] then new_acc=$( $JQ_FILE -n --arg user_name "$user_name" --arg password "$password" \ --arg access_token "$access_token" --arg device_no "$device_no" \ --arg device_id "$device_id" --arg refresh "$refresh" \ '{ user_name: $user_name, password: $password, access_token: $access_token, device_no: $device_no, device_id: $device_id | tonumber, refresh: $refresh | tonumber }' ) else new_acc=$( $JQ_FILE -n --arg user_name "$user_name" --arg phone_number "$phone_number" \ --arg password "$password" --arg access_token "$access_token" \ --arg device_no "$device_no" --arg device_id "$device_id" \ --arg refresh "$refresh" \ '{ user_name: $user_name, phone_number: $phone_number | tonumber, password: $password, access_token: $access_token, device_no: $device_no, device_id: $device_id | tonumber, refresh: $refresh | tonumber }' ) fi if [[ $($JQ_FILE --argjson path '["'"$name"'","accounts"]' --arg user_name "$user_name" 'getpath($path) // []|map(select(.user_name==$user_name))' "$SERVICES_FILE") != "[]" ]] then merge=true map_string=true jq_path='["'"$name"'","accounts"]' JQ update "$SERVICES_FILE" user_name "$user_name" "$new_acc" else jq_path='["'"$name"'","accounts"]' JQ add "$SERVICES_FILE" "[$new_acc]" fi fi break done Println "$info 获取频道..." if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" "$list_url?pageidx=1&pagenum=500&accesstoken=$access_token") then echo "$res" Println "$error 无法获取频道\n" exit 1 fi IFS=" " read -r ret total < <($JQ_FILE -r '[.ret,.total]|join(" ")' <<< "$res") if [ "$ret" != 0 ] then echo "$res" Println "$error 无法获取频道\n" exit 1 elif [ "$total" -eq 0 ] then echo "$res" Println "$error 没有频道\n" exit 1 fi EXIT_STATUS=0 IFS=$'\003\t' read -r m_chnl_id m_str_chnl_id m_chnl_name m_chnl_type m_chnl_num m_logic_num m_definition m_is_favorite \ m_is_free m_is_hide m_is_livetv m_is_tstv m_is_lock m_is_purchased m_is_public m_is_boot m_is_fast_channel_change \ m_stream_type m_times m_comment_num m_score m_praise_num m_degrade_num m_score_num m_iframe_url m_device_type m_providerid \ m_provider_icon_font m_ts_id m_content_type m_sub_type m_subtype_name m_label_name m_support_playback m_rate_list \ m_tstv_url m_livetv_url m_video_code m_trial_short m_trial_longest m_trial_times m_play_type m_codec_info \ m_logo_info < <(JQs flat "$res" '.[0].chnl_list | map(select(.chnl_id != null)|with_entries(select(.key as $k | ["chnl_id","str_chnl_id","chnl_name","chnl_type","chnl_num","logic_num","definition","is_favorite","is_free","is_hide","is_livetv","is_tstv", "is_lock","is_purchased","is_public","is_boot","is_fast_channel_change","stream_type","times","comment_num","score","praise_num","degrade_num", "score_num","iframe_url","device_type","providerid","provider_icon_font","ts_id","content_type","sub_type","subtype_name","label_name", "support_playback","rate_list","tstv_url","livetv_url","video_code","trial_short","trial_longest","trial_times","play_type", "codec_info","logo_info"] | index($k))))' '. as $channel | reduce ({chnl_id,str_chnl_id,chnl_name,chnl_type,chnl_num,logic_num,definition, is_favorite,is_free,is_hide,is_livetv,is_tstv,is_lock,is_purchased,is_public,is_boot,is_fast_channel_change,stream_type,times,comment_num, score,praise_num,degrade_num,score_num,iframe_url,device_type,providerid,provider_icon_font,ts_id,content_type,sub_type,subtype_name, label_name,support_playback,rate_list,tstv_url,livetv_url,video_code,trial_short,trial_longest,trial_times,play_type, codec_info,logo_info}|keys_unsorted[]) as $key ([]; $channel[$key] as $val | if $val then . + [$val + "\u0002\u0003"] else . + ["\u0003"] end )|@tsv' "${delimiters[@]}") || EXIT_STATUS=$? if [ "$EXIT_STATUS" -ne 0 ] then echo "$res" Println "$error 无法解析频道\n" exit 1 fi IFS="${delimiters[1]}" read -ra chnl_id <<< "$m_chnl_id" IFS="${delimiters[1]}" read -ra chnl_name <<< "$m_chnl_name" IFS="${delimiters[1]}" read -ra rate_list <<< "$m_rate_list" IFS="${delimiters[1]}" read -ra livetv_url <<< "$m_livetv_url" echo inquirer checkbox_input_indices "选择添加的频道" chnl_name chnl_indices FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFPROBE="$FFMPEG_ROOT/ffprobe" FFMPEG="$FFMPEG_ROOT/ffmpeg" for chnl_index in "${chnl_indices[@]}" do IFS="${delimiters[0]}" read -ra rates <<< "${rate_list[chnl_index]}" echo inquirer list_input "选择频道 ${chnl_name[chnl_index]} 分辨率" rates rate if [ -z "$play_url" ] then IFS="${delimiters[0]}" read -ra play_urls <<< "${livetv_url[chnl_index]}" echo inquirer list_input "选择频道 ${chnl_name[chnl_index]} 地址" play_urls play_url fi echo protocol_options=( http hls ) inquirer list_input "选择频道 ${chnl_name[chnl_index]} 协议" protocol_options protocol if [ -z "$auth_info_url" ] then stream_link="$play_url?protocol=$protocol&accesstoken=$access_token&programid=${chnl_id[chnl_index]}&playtoken=ABCDEFGH&verifycode=$device_id&rate=$rate&playtype=live" else if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" "$auth_info_url?accesstoken=$access_token&programid=${chnl_id[chnl_index]}&playtype=live&protocol=$protocol&verifycode=$device_id") then Println "$error 无法验证 ${chnl_name[chnl_index]}" continue fi IFS=" " read -r ret auth_random_sn play_token < <($JQ_FILE -r '[.ret,.auth_random_sn,.play_token]|join(" ")' <<< "$res") if [ "$ret" != 0 ] || [ -z "$auth_random_sn" ] || [ -z "$play_token" ] then echo "$res" Println "$error 无法验证 ${chnl_name[chnl_index]}" continue fi authtoken="ipanel123#%#&*(&(*#*&^*@#&*%()#*()$)#@&%(*@#()*%321ipanel$auth_random_sn" authtoken=$(printf '%s' "$authtoken" | md5sum) authtoken=${authtoken%% *} if ! res=$(curl -s -Lm 10 -H "User-Agent: $user_agent" "$auth_verify_url?programid=${chnl_id[chnl_index]}&playtype=live&protocol=$protocol&accesstoken=$access_token&verifycode=$device_id&authtoken=$authtoken") || [ $($JQ_FILE -r '.ret' <<< "$res") != 0 ] then Println "$error 无法验证 ${chnl_name[chnl_index]}" continue fi stream_link="$play_url?protocol=$protocol&accesstoken=$access_token&programid=${chnl_id[chnl_index]}&playtoken=$play_token&verifycode=$device_id&rate=$rate&playtype=live" fi if ! $FFPROBE -hide_banner -loglevel debug -show_streams -user_agent "$user_agent" -i "$stream_link" then Println "$error 无法连接 ${chnl_name[chnl_index]}" continue fi echo inquirer list_input_index "添加频道 ${chnl_name[chnl_index]}" yn_options yn_options_index if [ "$yn_options_index" -eq 0 ] then stream_links=("$stream_link|$name|$user_name") echo inquirer list_input_index "是否 添加/替换 现有频道直播源" ny_options ny_options_index if [ "$ny_options_index" -eq 1 ] then ListChannels InputChannelsIndex for((i=0;i<${#chnls_pid_chosen[@]};i++)); do chnl_pid=${chnls_pid_chosen[i]} chnls_index=${chnls_indices[i]} ListChannel echo change_options=( '添加' '替换' ) inquirer list_input_index "如何修改频道 [ $chnl_channel_name ]" change_options change_options_index if [ "$change_options_index" -eq 0 ] then pre=true jq_path='["channels",'"$chnls_index"',"stream_link"]' JQ add "$CHANNELS_FILE" ["\"${stream_links[0]}\""] else echo inquirer list_input_index "选择替换的直播源" chnl_stream_links chnl_stream_links_index jq_path='["channels",'"$chnls_index"',"stream_link",'"$chnl_stream_links_index"']' JQ update "$CHANNELS_FILE" "${stream_links[0]}" fi Println "$info 频道 [ $chnl_channel_name ] 修改成功 !\n" done else echo inquirer list_input_index "是否推流 flv" ny_options ny_options_index if [ "$ny_options_index" -eq 1 ] then kind="flv" fi skip_set_stream_link=true AddChannel fi fi done } AntiDDoSSet() { if [[ -x "$(command -v ufw)" ]] && [ -s "$nginx_prefix/logs/access.log" ] && ls -A $LIVE_ROOT/* > /dev/null 2>&1 then sleep 1 if ufw show added | grep -q "None" then Println "$info 添加常用 ufw 规则" ufw allow ssh > /dev/null 2>&1 ufw allow http > /dev/null 2>&1 ufw allow https > /dev/null 2>&1 if ufw status | grep -q "inactive" then current_port=${SSH_CLIENT##* } if [ "$current_port" != 22 ] then ufw allow "$current_port" > /dev/null 2>&1 fi Println "$info 开启 ufw" ufw --force enable > /dev/null 2>&1 fi fi GetDefault SetAntiDDosPort SetAntiDDosSynFlood SetAntiDDos if [ "$anti_ddos_syn_flood" = false ] && [ "$anti_ddos" = false ] then if [ "$d_anti_ddos_syn_flood" == true ] || [ "$d_anti_ddos" == true ] then update='{ "anti_ddos_syn_flood": false, "anti_ddos": false }' merge=true jq_path='["default"]' JQ update "$CHANNELS_FILE" "$update" fi Println "不启动 AntiDDoS ...\n" && exit 0 else anti_ddos_ports=${anti_ddos_port:-$d_anti_ddos_port} anti_ddos_ports=${anti_ddos_port%% *} update=$( $JQ_FILE -n --arg anti_ddos_syn_flood "${anti_ddos_syn_flood:-$d_anti_ddos_syn_flood}" \ --arg anti_ddos_syn_flood_delay_seconds "${anti_ddos_syn_flood_delay_seconds:-$d_anti_ddos_syn_flood_delay_seconds}" \ --arg anti_ddos_syn_flood_seconds "${anti_ddos_syn_flood_seconds:-$d_anti_ddos_syn_flood_seconds}" \ --arg anti_ddos "${anti_ddos:-$d_anti_ddos}" \ --arg anti_ddos_port "$anti_ddos_ports" \ --arg anti_ddos_seconds "${anti_ddos_seconds:-$d_anti_ddos_seconds}" \ anti_ddos_level "${anti_ddos_level:-$d_anti_ddos_level}" \ '{ anti_ddos_syn_flood: $anti_ddos_syn_flood | test("true"), anti_ddos_syn_flood_delay_seconds: $anti_ddos_syn_flood_delay_seconds | tonumber, anti_ddos_syn_flood_seconds: $anti_ddos_syn_flood_seconds | tonumber, anti_ddos: $anti_ddos | test("true"), anti_ddos_port: $anti_ddos_port, anti_ddos_seconds: $anti_ddos_seconds | tonumber, anti_ddos_level: $anti_ddos_level | tonumber }' ) merge=true jq_path='["default"]' JQ update "$CHANNELS_FILE" "$update" fi else exit 0 fi } AntiDDoS() { trap '' HUP INT trap 'MonitorErr $LINENO' ERR pid_file="$IPTV_ROOT/antiddos.pid" printf '%s' "$BASHPID" > "$pid_file" { flock -x 202 { ips=() jail_time=() if [[ $d_anti_ddos_port == *","* ]] || [[ $d_anti_ddos_port =~ - ]] then d_anti_ddos_port="$d_anti_ddos_port proto tcp" fi if [ -s "$IP_DENY" ] then while IFS= read -r line do if [[ $line == *:* ]] then ip=${line%:*} jail=${line#*:} ips+=("$ip") jail_time+=("$jail") else ip="$line" ufw delete deny from "$ip" to any port $d_anti_ddos_port > /dev/null 2>> "$IP_LOG" fi done < "$IP_DENY" if [ -n "${ips:-}" ] then new_ips=() new_jail_time=() printf -v now '%(%s)T' -1 update=0 for((i=0;i<${#ips[@]};i++)); do if [ "$now" -gt "${jail_time[i]}" ] then ufw delete deny from "${ips[i]}" to any port $d_anti_ddos_port > /dev/null 2>> "$IP_LOG" update=1 else new_ips+=("${ips[i]}") new_jail_time+=("${jail_time[i]}") fi done if [ "$update" -eq 1 ] then ips=("${new_ips[@]}") jail_time=("${new_jail_time[@]}") printf '%s' "" > "$IP_DENY" for((i=0;i<${#ips[@]};i++)); do printf '%s\n' "${ips[i]}:${jail_time[i]}" >> "$IP_DENY" done fi else printf '%s' "" > "$IP_DENY" fi fi MonitorLog "AntiDDoS 启动成功 PID $BASHPID !" current_ip=${SSH_CLIENT%% *} [ -n "${anti_ddos_level:-}" ] && anti_ddos_level=$((anti_ddos_level+1)) monitor=true while true do if [ "$anti_ddos_syn_flood" = true ] then anti_ddos_syn_flood_ips=() while IFS= read -r anti_ddos_syn_flood_ip do anti_ddos_syn_flood_ips+=("$anti_ddos_syn_flood_ip") done < <(ss -taH|awk '{gsub(/.*:/, "", $4);gsub(/:.*/, "", $5); if ($1 == "SYN-RECV" && $5 != "'"$current_ip"'" && ('"$anti_ddos_ports_command$anti_ddos_ports_range_command"')) print $5}') PrepTerm sleep "$anti_ddos_syn_flood_delay_seconds" & WaitTerm printf -v now '%(%s)T' -1 jail=$((now + anti_ddos_syn_flood_seconds)) while IFS= read -r anti_ddos_syn_flood_ip do to_ban=1 for banned_ip in ${ips[@]+"${ips[@]}"} do if [ "$banned_ip" == "$anti_ddos_syn_flood_ip/24" ] then to_ban=0 break 1 fi done if [ "$to_ban" -eq 1 ] then for ip in ${anti_ddos_syn_flood_ips[@]+"${anti_ddos_syn_flood_ips[@]}"} do if [ "$ip" == "$anti_ddos_syn_flood_ip" ] then ip="$ip/24" jail_time+=("$jail") printf '%s\n' "$ip:$jail" >> "$IP_DENY" ufw insert 1 deny from "$ip" to any port $anti_ddos_port > /dev/null 2>> "$IP_LOG" printf -v date_now '%(%m-%d %H:%M:%S)T' -1 printf '%s\n' "$date_now $ip 已被禁" >> "$IP_LOG" ips+=("$ip") break 1 fi done fi done < <(ss -taH|awk '{gsub(/.*:/, "", $4);gsub(/:.*/, "", $5); if ($1 == "SYN-RECV" && $5 != "'"$current_ip"'" && ('"$anti_ddos_ports_command$anti_ddos_ports_range_command"')) print $5}') fi if [ "$anti_ddos" = true ] then chnls_count=0 chnls_output_dir_name=() chnls_seg_length=() chnls_seg_count=() while IFS="=" read -r map_seg_length map_seg_count map_output_dir_name do chnls_count=$((chnls_count+1)) map_seg_length=${map_seg_length#\"} map_output_dir_name=${map_output_dir_name%\"} chnls_output_dir_name+=("$map_output_dir_name") chnls_seg_length+=("$map_seg_length") chnls_seg_count+=("$map_seg_count") done < <($JQ_FILE '.channels[] | [.seg_length,.seg_count,.output_dir_name] | join("=")' "$CHANNELS_FILE") output_dir_names=() triggers=() for output_dir_root in "$LIVE_ROOT"/* do output_dir_name=${output_dir_root#*$LIVE_ROOT/} for((i=0;i> "$IP_DENY" ufw insert 1 deny from "$ip" to any port $anti_ddos_port > /dev/null 2>> "$IP_LOG" printf -v date_now '%(%m-%d %H:%M:%S)T' -1 printf '%s\n' "$date_now $ip 已被禁" >> "$IP_LOG" ips+=("$ip") break 1 fi done fi fi done < <(awk -v d1="$(printf '%(%d/%b/%Y:%H:%M:%S)T' $((now-60)))" '{gsub(/^[\[\t]+/, "", $4); if ( $4 > d1 ) print $1,$7;}' "$nginx_prefix"/logs/access.log | sort | uniq -c | sort -k1 -nr) # date --date '-1 min' '+%d/%b/%Y:%T' # awk -v d1="$(printf '%(%d/%b/%Y:%H:%M:%S)T' $((now-60)))" '{gsub(/^[\[\t]+/, "", $4); if ($7 ~ "'"$link"'" && $4 > d1 ) print $1;}' "$nginx_prefix"/logs/access.log | sort | uniq -c | sort -fr fi PrepTerm sleep 10 & WaitTerm if [ -n "${ips:-}" ] then new_ips=() new_jail_time=() printf -v now '%(%s)T' -1 update=0 for((i=0;i<${#ips[@]};i++)); do if [ "$now" -gt "${jail_time[i]}" ] then ufw delete deny from "${ips[i]}" to any port $anti_ddos_port > /dev/null 2>> "$IP_LOG" update=1 else new_ips+=("${ips[i]}") new_jail_time+=("${jail_time[i]}") fi done if [ "$update" -eq 1 ] then ips=("${new_ips[@]}") jail_time=("${new_jail_time[@]}") printf '%s' "" > "$IP_DENY" for((i=0;i<${#ips[@]};i++)); do printf '%s\n' "${ips[i]}:${jail_time[i]}" >> "$IP_DENY" done fi fi done } 202>&- } 202<"$pid_file" } MonitorHlsRemoveFailed() { declare -a new_array for((failed_i=0;failed_i<${#hls_failed[@]};failed_i++)); do failed_index=${hls_failed[failed_i]} if [ "${monitor_output_dir_names[failed_index]}" == "$output_dir_name" ] then unset 'hls_change[failed_index]' unset 'hls_change_once[failed_index]' unset 'hls_changed[failed_index]' unset 'channel_name[failed_index]' unset 'hls_recheck_time[failed_i]' hls_recheck_time=("${hls_recheck_time[@]}") else new_array+=("$failed_index") fi done if [ -z "${new_array:-}" ] then hls_failed=() else hls_failed=("${new_array[@]}") fi unset new_array } MonitorHlsRestartSuccess() { if [ -n "${failed_restart_nums:-}" ] then unset 'hls_failed[failed_i]' unset 'hls_recheck_time[failed_i]' hls_failed=("${hls_failed[@]}") hls_recheck_time=("${hls_recheck_time[@]}") fi MonitorLog "$chnl_channel_name 重启成功" } MonitorHlsRestartFail() { StopChannel printf -v now '%(%s)T' -1 recheck_time=$((now+recheck_period)) if [ -n "${failed_restart_nums:-}" ] then hls_recheck_time[failed_i]="$recheck_time" else hls_recheck_time+=("$recheck_time") hls_failed+=("$hls_index") fi declare -a new_array for element in "${hls_indices[@]}" do [ "$element" != "$hls_index" ] && new_array+=("$element") done if [ -z "${new_array:-}" ] then hls_indices=() else hls_indices=("${new_array[@]}") fi unset new_array MonitorLog "$chnl_channel_name 重启失败" } MonitorHlsRestartChannel() { hls_change[hls_index]=${hls_change[hls_index]:-true} hls_change_once[hls_index]=${hls_change_once[hls_index]:-false} hls_changed[hls_index]=${hls_changed[hls_index]:-false} XtreamCodesGetChnls domains_tried=() hls_restart_nums=${hls_restart_nums:-20} unset failed_restart_nums for((failed_i=0;failed_i<${#hls_failed[@]};failed_i++)); do if [ "${hls_failed[failed_i]}" == "$hls_index" ] then failed_restart_nums=3 break fi done restart_nums=${failed_restart_nums:-$hls_restart_nums} if [ "$chnl_stream_links_count" -gt $restart_nums ] then restart_nums="$chnl_stream_links_count" fi for((restart_i=0;restart_i /dev/null) || true if [ -z "$access_token" ] then if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorHlsRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi fi chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" profile=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$profile_url" | $JQ_FILE -r '.js.id // ""' 2> /dev/null) || true exp_date=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$account_info_url" | $JQ_FILE -r '.js.phone' 2> /dev/null) || true if [ -z "$exp_date" ] then if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorHlsRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi fi if [ -n "$chnl_xc_proxy" ] then server=${chnl_xc_proxy%\/} IFS=" " read -r chnl_stream_link new_access_token new_cookies < <(CurlFake xtream_codes -sL \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" \ "$server/?cmd=$chnl_cmd&check=1" | $JQ_FILE -r '.|join(" ")' 2> /dev/null) || true if [[ ! $chnl_stream_link =~ ([^/]+)//([^/]+)/(.+) ]] then if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorHlsRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi fi access_token="$new_access_token" chnl_cookies="$new_cookies" if [[ ${BASH_REMATCH[1]} =~ [a-z] ]] then chnl_stream_link="$server/?cmd=$chnl_cmd" chnl_headers="" chnl_headers_command="" else chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" fi else create_link_url="$server/portal.php?type=itv&action=create_link&cmd=$chnl_cmd&series=&forced_storage=undefined&disable_ad=0&download=0" cmd=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" \ "$create_link_url" | $JQ_FILE -r '.js.cmd') || true if [[ ${cmd#* } =~ ([^/]+)//([^/]+)/(.+) ]] then chnl_stream_link="http://localhost:3000/$(XtreamCodesDomainFilter ${BASH_REMATCH[2]})/${BASH_REMATCH[3]}" else if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorHlsRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi fi fi chnl_stream_links[0]="$chnl_domain|$chnl_stream_link|$chnl_cmd|$chnl_mac" else to_try=0 if [[ $chnl_stream_link =~ ^http://([^/]+)/([^/]+)/([^/]+)/ ]] then chnl_domain=${BASH_REMATCH[1]} for xc_domain in ${xtream_codes_domains[@]+"${xtream_codes_domains[@]}"} do if [ "$xc_domain" == "$chnl_domain" ] then to_try=1 for domain in ${domains_tried[@]+"${domains_tried[@]}"} do if [ "$domain" == "$chnl_domain" ] then to_try=0 break fi done break fi done fi xc_chnl_found=0 if [ "$to_try" -eq 1 ] then if [ "${BASH_REMATCH[2]}" == "live" ] && [[ $chnl_stream_link =~ ^http://([^/]+)/live/([^/]+)/([^/]+)/ ]] then chnl_account="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}" else chnl_account="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}" fi for xc_chnl in ${xc_chnls[@]+"${xc_chnls[@]}"} do if [ "$xc_chnl" == "$chnl_domain/$chnl_account" ] then xc_chnl_found=1 break fi done fi if [ "$xc_chnl_found" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorHlsRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorHlsRestartFail break else continue fi fi fi StopChannel if [ "$anti_leech" = true ] && [ "$anti_leech_restart_hls_changes" = true ] then if [ "${hls_change[hls_index]}" = true ] && { [ "${hls_change_once[hls_index]}" = false ] || [ "${hls_changed[hls_index]:-false}" = false ]; } then if [ "${hls_change_once[hls_index]}" = true ] then hls_changed[hls_index]=true fi chnl_playlist_name=$(RandStr) chnl_seg_name="$chnl_playlist_name" fi if [ "$chnl_encrypt" = true ] then chnl_key_name=$(RandStr) fi fi if [ -n "${channel_name[hls_index]:-}" ] then chnl_channel_name="${channel_name[hls_index]}" fi StartChannel sleep $((15+chnl_seg_length)) GetChannel || GetChannel if [ "$chnl_status" == "on" ] && ls -A "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts > /dev/null 2>&1 then skip_check_stream=0 if [ "$chnl_encrypt" = true ] then if [ -e "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" ] && \ [ -e "$chnl_output_dir_root/$chnl_key_name.key" ] && \ iv_hex=$(awk 'NR==3{print}' "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo") && \ encrypt_key=$(hexdump -e '16/1 "%02x"' < "$chnl_output_dir_root/$chnl_key_name.key") then encrypt_command="-key $encrypt_key -iv $iv_hex" else skip_check_stream=1 fi # xxd -p $KEY_FILE else encrypt_command="" fi if [ "$skip_check_stream" -eq 0 ] then audio=0 video=0 video_bitrate=0 bitrate_check=0 f_count=1 for f in "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts do f_count=$((f_count+1)) done f_num=$((f_count/2)) f_count=1 for f in "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts do if [ "$f_count" -lt "$f_num" ] then f_count=$((f_count+1)) continue fi [ -n "$encrypt_command" ] && f="crypto:$f" while IFS= read -r line do if [[ $line == *"codec_type=video"* ]] then video=1 elif [ "$bitrate_check" -eq 0 ] && [ "$video" -eq 1 ] && [[ $line == *"bit_rate="* ]] then line=${line#*bit_rate=} video_bitrate=${line//N\/A/$hls_min_bitrate} bitrate_check=1 elif [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 fi done < <($FFPROBE $encrypt_command -i "$f" -show_streams -loglevel quiet) break done if { [ "$master" -eq 1 ] && [ "$video" -eq 1 ] && [[ $video_bitrate -ge $hls_min_bitrate ]]; } || { [ "$master" -eq 1 ] && [ "$audio" -eq 1 ]; } || { [ "$audio" -eq 1 ] && [ "$video" -eq 1 ] && [[ $video_bitrate -ge $hls_min_bitrate ]]; } then MonitorHlsRestartSuccess break fi MonitorErr "频道 [ $chnl_channel_name ] audio $audio, video $video, bitrate $video_bitrate" fi fi if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorHlsRestartSuccess break fi fi if [[ $restart_i -eq $((restart_nums - 1)) ]] then MonitorHlsRestartFail break fi done } MonitorFlvRestartSuccess() { if [ -n "${failed_restart_nums:-}" ] then declare -a new_array for element in ${flv_failed[@]+"${flv_failed[@]}"} do [ "$element" != "$flv_index" ] && new_array+=("$element") done if [ -z "${new_array:-}" ] then flv_failed=() else flv_failed=("${new_array[@]}") fi unset new_array declare -a new_array for element in ${flv_recheck_time[@]+"${flv_recheck_time[@]}"} do [ "$element" != "${flv_recheck_time[failed_i]}" ] && new_array+=("$element") done if [ -z "${new_array:-}" ] then flv_recheck_time=() else flv_recheck_time=("${new_array[@]}") fi unset new_array fi MonitorLog "$chnl_channel_name 重启成功" } MonitorFlvRestartFail() { StopChannel printf -v now '%(%s)T' -1 recheck_time=$((now+recheck_period)) if [ -n "${failed_restart_nums:-}" ] then flv_recheck_time[failed_i]="$recheck_time" else flv_recheck_time+=("$recheck_time") flv_failed+=("$flv_index") fi declare -a new_array for element in "${flv_indices[@]}" do [ "$element" != "$flv_index" ] && new_array+=("$element") done if [ -z "${new_array:-}" ] then flv_indices=() else flv_indices=("${new_array[@]}") fi unset new_array MonitorLog "$chnl_channel_name FLV 重启超过${flv_restart_nums:-20}次关闭" } MonitorFlvRestartChannel() { XtreamCodesGetChnls domains_tried=() flv_restart_nums=${flv_restart_nums:-20} unset failed_restart_nums for((failed_i=0;failed_i<${#flv_failed[@]};failed_i++)); do if [ "${flv_failed[failed_i]}" == "$flv_index" ] then failed_restart_nums=3 break fi done restart_nums=${failed_restart_nums:-$flv_restart_nums} if [ "$chnl_stream_links_count" -gt $restart_nums ] then restart_nums="$chnl_stream_links_count" fi for((restart_i=0;restart_i /dev/null) || true if [ -z "$access_token" ] then if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorFlvRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi fi chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" profile=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$profile_url" | $JQ_FILE -r '.js.id // ""' 2> /dev/null) || true exp_date=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$account_info_url" | $JQ_FILE -r '.js.phone' 2> /dev/null) || true if [ -z "$exp_date" ] then if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorFlvRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi fi if [ -n "$chnl_xc_proxy" ] then server=${chnl_xc_proxy%\/} IFS=" " read -r chnl_stream_link new_access_token new_cookies < <(CurlFake xtream_codes -sL \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" \ "$server/?cmd=$chnl_cmd&check=1" | $JQ_FILE -r '.|join(" ")' 2> /dev/null) || true if [[ ! $chnl_stream_link =~ ([^/]+)//([^/]+)/(.+) ]] then if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorFlvRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi fi access_token="$new_access_token" chnl_cookies="$new_cookies" if [[ ${BASH_REMATCH[1]} =~ [a-z] ]] then chnl_stream_link="$server/?cmd=$chnl_cmd" chnl_headers="" chnl_headers_command="" else chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" fi else create_link_url="$server/portal.php?type=itv&action=create_link&cmd=$chnl_cmd&series=&forced_storage=undefined&disable_ad=0&download=0" cmd=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$create_link_url" \ | $JQ_FILE -r '.js.cmd') || true if [[ ${cmd#* } =~ ([^/]+)//([^/]+)/(.+) ]] then chnl_stream_link="http://localhost:3000/$(XtreamCodesDomainFilter ${BASH_REMATCH[2]})/${BASH_REMATCH[3]}" else if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorFlvRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi fi fi chnl_stream_links[0]="$chnl_domain|$chnl_stream_link|$chnl_cmd|$chnl_mac" else to_try=0 if [[ $chnl_stream_link =~ ^http://([^/]+)/([^/]+)/([^/]+)/ ]] then chnl_domain=${BASH_REMATCH[1]} for xc_domain in ${xtream_codes_domains[@]+"${xtream_codes_domains[@]}"} do if [ "$xc_domain" == "$chnl_domain" ] then to_try=1 for domain in ${domains_tried[@]+"${domains_tried[@]}"} do if [ "$domain" == "$chnl_domain" ] then to_try=0 break fi done break fi done fi xc_chnl_found=0 if [ "$to_try" -eq 1 ] then to_try=0 if [ "${BASH_REMATCH[2]}" == "live" ] && [[ $chnl_stream_link =~ ^http://([^/]+)/live/([^/]+)/([^/]+)/ ]] then chnl_account="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}" else chnl_account="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}" fi for xc_chnl in ${xc_chnls[@]+"${xc_chnls[@]}"} do if [ "$xc_chnl" == "$chnl_domain/$chnl_account" ] then xc_chnl_found=1 break fi done fi if [ "$xc_chnl_found" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorFlvRestartSuccess break elif [[ $restart_i -eq $((restart_nums-1)) ]] then MonitorFlvRestartFail break else continue fi fi fi StopChannel if [ "$anti_leech" = true ] && [ "$anti_leech_restart_flv_changes" = true ] then stream_name=${chnl_flv_push_link##*/} new_stream_name=$(RandStr) while [[ -n $($JQ_FILE '.channels[]|select(.flv_push_link=="'"${chnl_flv_push_link%/*}/$new_stream_name"'")' "$CHANNELS_FILE") ]] do new_stream_name=$(RandStr) done chnl_flv_push_link="${chnl_flv_push_link%/*}/$new_stream_name" monitor_flv_push_links[i]="$chnl_flv_push_link" if [ -n "$chnl_flv_pull_link" ] then chnl_flv_pull_link=${chnl_flv_pull_link//stream=$stream_name/stream=$new_stream_name} monitor_flv_pull_links[i]="$chnl_flv_pull_link" fi fi StartChannel sleep 15 GetChannel if [ "$chnl_flv_status" == "on" ] then audio=0 video=0 while IFS= read -r line do if [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 elif [[ $line == *"codec_type=video"* ]] then video=1 fi done < <($FFPROBE -i "${chnl_flv_pull_link:-$chnl_flv_push_link}" -rw_timeout 10000000 -show_streams -loglevel quiet) if [ "$audio" -eq 1 ] && [ "$video" -eq 1 ] then MonitorFlvRestartSuccess break fi fi if [ "$to_try" -eq 1 ] then domains_tried+=("$chnl_domain") try_success=0 MonitorTryAccounts if [ "$try_success" -eq 1 ] then MonitorFlvRestartSuccess break fi fi if [[ $restart_i -eq $((restart_nums - 1)) ]] then MonitorFlvRestartFail break fi done } MonitorTryAccounts() { accounts=() macs=() while IFS= read -r line do if [[ $line == *"$chnl_domain"* ]] then line=${line#* } account_line=${line#* } if [[ $account_line == *" "* ]] then new_account_line="" while [[ $account_line == *" "* ]] do if [[ ${account_line%% *} =~ ^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$ ]] then macs+=("${account_line%% *}") account_line=${account_line#* } continue fi [ -n "$new_account_line" ] && new_account_line=" $new_account_line" new_account_line="${account_line%% *}$new_account_line" account_line=${account_line#* } done else if [[ $account_line =~ ^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$ ]] then macs+=("$account_line") fi new_account_line="$account_line" fi IFS=" " read -ra accounts <<< "$new_account_line" break fi done < "$XTREAM_CODES" if [ -n "${chnl_mac:-}" ] then if [ -n "${macs:-}" ] then GetDefault if [ -n "${chnl_xc_proxy:-}" ] then server="${chnl_xc_proxy%\/}/http://$chnl_domain" else server="http://$chnl_domain" fi chnl_user_agent="$USER_AGENT_TV" timezone=$(UrlencodeUpper "Europe/Amsterdam") token_url="$server/portal.php?type=stb&action=handshake&token=&prehash=0&JsHttpRequest=1-xml" profile_url="$server/portal.php?type=stb&action=get_profile" account_info_url="$server/portal.php?type=account_info&action=get_main_info" genres_url="$server/portal.php?type=itv&action=get_genres" macs+=("$chnl_mac") macs_count=${#macs[@]} echo for((macs_i=0;macs_i /dev/null) || true if [ -z "$access_token" ] then continue fi chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" printf -v chnl_cookies_command '%b' "${chnl_cookies//;/; path=\/;\\r\\n}; path=/;" profile=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$profile_url" | $JQ_FILE -r '.js.id // ""' 2> /dev/null) || true exp_date=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$account_info_url" | $JQ_FILE -r '.js.phone' 2> /dev/null) || true if [ -z "$exp_date" ] then continue fi if [ -n "$chnl_xc_proxy" ] then server=${chnl_xc_proxy%\/} IFS=" " read -r chnl_stream_link new_access_token new_cookies < <(CurlFake xtream_codes -sL \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" \ "$server/?cmd=$chnl_cmd&check=1" | $JQ_FILE -r '.|join(" ")' 2> /dev/null) || true if [[ ! $chnl_stream_link =~ ([^/]+)//([^/]+)/(.+) ]] then continue fi access_token="$new_access_token" chnl_cookies="$new_cookies" if [[ ${BASH_REMATCH[1]} =~ [a-z] ]] then chnl_stream_link="$server/?cmd=$chnl_cmd" chnl_headers="" chnl_headers_command="" else chnl_headers="Authorization: Bearer $access_token\r\n" printf -v chnl_headers_command '%b' "$chnl_headers" fi else create_link_url="$server/portal.php?type=itv&action=create_link&cmd=$chnl_cmd&series=&forced_storage=undefined&disable_ad=0&download=0" cmd=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $chnl_user_agent" \ -H "${chnl_headers:0:-4}" \ --cookie "$chnl_cookies" "$create_link_url" \ | $JQ_FILE -r '.js.cmd') || true if [[ ${cmd#* } =~ ([^/]+)//([^/]+)/(.+) ]] then chnl_stream_link="http://localhost:3000/$(XtreamCodesDomainFilter ${BASH_REMATCH[2]})/${BASH_REMATCH[3]}" else continue fi fi audio=0 video=0 while IFS= read -r line do if [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 elif [[ $line == *"codec_type=video"* ]] then video=1 fi done < <($FFPROBE $chnl_proxy_command -user_agent "$chnl_user_agent" -headers "$chnl_headers_command" -cookies "$chnl_cookies_command" -i "$chnl_stream_link" -rw_timeout 10000000 -show_streams -loglevel quiet) if [ "$audio" -eq 1 ] && [ "$video" -eq 1 ] then valid=1 fi if [ "$valid" -eq 1 ] then StopChannel chnl_stream_links[0]="$chnl_domain|$chnl_stream_link|$chnl_cmd|$mac_address" if [ "$monitor" = true ] && [ "$anti_leech" = true ] then if [ -z "${kind:-}" ] && [ "$anti_leech_restart_hls_changes" = true ] then if [ "${hls_change[hls_index]}" = true ] && { [ "${hls_change_once[hls_index]}" = false ] || [ "${hls_changed[hls_index]}" = false ]; } then if [ "${hls_change_once[hls_index]}" = true ] then hls_changed[hls_index]=true fi chnl_playlist_name=$(RandStr) chnl_seg_name="$chnl_playlist_name" fi if [ "$chnl_encrypt" = true ] then chnl_key_name=$(RandStr) fi elif [ "${kind:-}" == "flv" ] && [ "$anti_leech_restart_flv_changes" = true ] then stream_name=${chnl_flv_push_link##*/} new_stream_name=$(RandStr) while [[ -n $($JQ_FILE '.channels[]|select(.flv_push_link=="'"${chnl_flv_push_link%/*}/$new_stream_name"'")' "$CHANNELS_FILE") ]] do new_stream_name=$(RandStr) done chnl_flv_push_link="${chnl_flv_push_link%/*}/$new_stream_name" monitor_flv_push_links[i]="$chnl_flv_push_link" if [ -n "$chnl_flv_pull_link" ] then chnl_flv_pull_link=${chnl_flv_pull_link//stream=$stream_name/stream=$new_stream_name} monitor_flv_pull_links[i]="$chnl_flv_pull_link" fi fi fi if [ "$monitor" = true ] && [ -n "${channel_name[hls_index]:-}" ] then chnl_channel_name="${channel_name[hls_index]}" fi StartChannel if [ "$monitor" = false ] then try_success=1 sleep 3 break fi if [ "${kind:-}" == "flv" ] then sleep 15 GetChannel audio=0 video=0 while IFS= read -r line do if [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 elif [[ $line == *"codec_type=video"* ]] then video=1 fi done < <($FFPROBE -i "${chnl_flv_pull_link:-$chnl_flv_push_link}" -rw_timeout 10000000 -show_streams -loglevel quiet) if [ "$audio" -eq 1 ] && [ "$video" -eq 1 ] then try_success=1 MonitorLog "$chnl_channel_name 重启成功" break fi else sleep $((15+chnl_seg_length)) GetChannel if [ "$chnl_status" == "on" ] && ls -A "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts > /dev/null 2>&1 then if [ "$chnl_encrypt" = true ] then if [ -e "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" ] && \ [ -e "$chnl_output_dir_root/$chnl_key_name.key" ] && \ iv_hex=$(awk 'NR==3{print}' "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo") && \ encrypt_key=$(hexdump -e '16/1 "%02x"' < "$chnl_output_dir_root/$chnl_key_name.key") then encrypt_command="-key $encrypt_key -iv $iv_hex" else continue fi else encrypt_command="" fi audio=0 video=0 video_bitrate=0 bitrate_check=0 f_count=1 for f in "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts do f_count=$((f_count+1)) done f_num=$((f_count/2)) f_count=1 for f in "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts do if [ "$f_count" -lt "$f_num" ] then f_count=$((f_count+1)) continue fi [ -n "$encrypt_command" ] && f="crypto:$f" while IFS= read -r line do if [[ $line == *"codec_type=video"* ]] then video=1 elif [ "$bitrate_check" -eq 0 ] && [ "$video" -eq 1 ] && [[ $line == *"bit_rate="* ]] then line=${line#*bit_rate=} video_bitrate=${line//N\/A/$hls_min_bitrate} bitrate_check=1 elif [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 fi done < <($FFPROBE $encrypt_command -i "$f" -show_streams -loglevel quiet) break done if [ "$audio" -eq 1 ] && [ "$video" -eq 1 ] && [[ $video_bitrate -ge $hls_min_bitrate ]] then try_success=1 MonitorLog "$chnl_channel_name 重启成功" break fi fi fi fi fi done echo fi elif [ -n "${accounts:-}" ] then accounts+=("$chnl_account") accounts_count=${#accounts[@]} echo for((accounts_i=0;accounts_i /dev/null 2>&1 then if [ "$chnl_encrypt" = true ] then if [ -e "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo" ] && \ [ -e "$chnl_output_dir_root/$chnl_key_name.key" ] && \ iv_hex=$(awk 'NR==3{print}' "$chnl_output_dir_root/$chnl_keyinfo_name.keyinfo") && \ encrypt_key=$(hexdump -e '16/1 "%02x"' < "$chnl_output_dir_root/$chnl_key_name.key") then encrypt_command="-key $encrypt_key -iv $iv_hex" else continue fi else encrypt_command="" fi audio=0 video=0 video_bitrate=0 bitrate_check=0 f_count=1 for f in "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts do f_count=$((f_count+1)) done f_num=$((f_count/2)) f_count=1 for f in "$chnl_output_dir_root/$chnl_seg_dir_path"*.ts do if [ "$f_count" -lt "$f_num" ] then f_count=$((f_count+1)) continue fi [ -n "$encrypt_command" ] && f="crypto:$f" while IFS= read -r line do if [[ $line == *"codec_type=video"* ]] then video=1 elif [ "$bitrate_check" -eq 0 ] && [ "$video" -eq 1 ] && [[ $line == *"bit_rate="* ]] then line=${line#*bit_rate=} video_bitrate=${line//N\/A/$hls_min_bitrate} bitrate_check=1 elif [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 fi done < <($FFPROBE $encrypt_command -i "$f" -show_streams -loglevel quiet) break done if [ "$audio" -eq 1 ] && [ "$video" -eq 1 ] && [[ $video_bitrate -ge $hls_min_bitrate ]] then try_success=1 MonitorLog "$chnl_channel_name 重启成功" break fi fi fi fi done echo fi } MonitorSet() { monitor_flv_push_links=() monitor_flv_pull_links=() monitor_output_dir_names=() GetDefault GetChannels flv_list="" flv_count=0 hls_count=0 hls_list="" for((i=0;i "$pid_file" { flock -x 203 { mkdir -p "$LIVE_ROOT" MonitorLog "监控启动成功 PID $BASHPID !" FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFMPEG="$FFMPEG_ROOT/ffmpeg" FFPROBE="$FFMPEG_ROOT/ffprobe" monitor=true XtreamCodesGetDomains flv_failed=() flv_recheck_time=() hls_failed=() hls_recheck_time=() hls_change=() hls_change_once=() hls_changed=() channel_name=() while true do GetChannels printf -v now '%(%s)T' -1 if [ "$chnls_count" -gt 0 ] then chnls_indices=("${!chnls_pid[@]}") for chnls_index in "${chnls_indices[@]}" do if [ -z "${chnls_schedule_status[chnls_index]}" ] then continue fi output_dir_name="${chnls_output_dir_name[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_start_time <<< "${chnls_schedule_start_time[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_end_time <<< "${chnls_schedule_end_time[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_loop <<< "${chnls_schedule_loop[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_auto_remove <<< "${chnls_schedule_auto_remove[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_hls_change <<< "${chnls_schedule_hls_change[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_hls_change_once <<< "${chnls_schedule_hls_change_once[chnls_index]}" IFS="${delimiters[1]}" read -ra chnl_schedules_status <<< "${chnls_schedule_status[chnls_index]}" chnl_schedules_if_null="${chnls_schedule_hls_change[chnls_index]//false/}" chnl_schedules_if_null="${chnl_schedules_if_null//true/}" IFS="${delimiters[1]}" read -ra chnl_schedules_channel_name <<< "${chnls_schedule_channel_name[chnls_index]:-$chnl_schedules_if_null}${delimiters[1]}" chnl_schedules_indices=("${!chnl_schedules_status[@]}") if [ "${chnls_status[chnls_index]}" == "on" ] then for chnl_schedules_index in "${chnl_schedules_indices[@]}" do if [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 0 ] || [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 1 ] then if [ "${chnl_schedules_end_time[chnl_schedules_index]}" -le "$now" ] then if [ "${chnl_schedules_loop[chnl_schedules_index]}" = true ] then update=$( $JQ_FILE -n --arg start_time "$((${chnl_schedules_start_time[chnl_schedules_index]}+86400))" \ --arg end_time "$((${chnl_schedules_end_time[chnl_schedules_index]}+86400))" \ --arg loop "${chnl_schedules_loop[chnl_schedules_index]}" \ --arg auto_remove "${chnl_schedules_auto_remove[chnl_schedules_index]}" \ --arg hls_change "${chnl_schedules_hls_change[chnl_schedules_index]}" \ --arg hls_change_once "${chnl_schedules_hls_change_once[chnl_schedules_index]}" \ --arg channel_name "${chnl_schedules_channel_name[chnl_schedules_index]}" \ --arg status 0 \ '{ "start_time": $start_time | tonumber, "end_time": $end_time | tonumber, "loop": $loop | test("true"), "auto_remove": $auto_remove | test("true"), "hls_change": $hls_change | test("true"), "hls_change_once": $hls_change_once | test("true"), "channel_name": $channel_name, "status": $status | tonumber }' ) merge=true map_string=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"']' JQ update "$CHANNELS_FILE" output_dir_name "$output_dir_name" "$update" elif [ "${chnl_schedules_auto_remove[chnl_schedules_index]}" = true ] then map_string=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"']' JQ delete "$CHANNELS_FILE" output_dir_name "$output_dir_name" else map_string=true number=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"',"status"]' JQ update "$CHANNELS_FILE" output_dir_name "$output_dir_name" 2 fi declare -a new_array for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then unset 'hls_change[hls_index]' unset 'hls_change_once[hls_index]' unset 'hls_changed[hls_index]' unset 'channel_name[hls_index]' else new_array+=("$hls_index") fi done if [ -z "${new_array:-}" ] then hls_indices=() else hls_indices=("${new_array[@]}") fi unset new_array MonitorHlsRemoveFailed MonitorLog "${chnl_schedules_channel_name[chnl_schedules_index]:-${chnls_channel_name[chnls_index]}} 计划到期关闭" GetChannel StopChannel MonitorLog "${chnl_schedules_channel_name[chnl_schedules_index]:-${chnls_channel_name[chnls_index]}} 计划到期关闭成功" else if [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 0 ] then map_string=true number=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"',"status"]' JQ update "$CHANNELS_FILE" output_dir_name "$output_dir_name" 1 fi for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then hls_change[hls_index]="${chnl_schedules_hls_change[chnl_schedules_index]}" if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then hls_change_once[hls_index]=true else if [ "${hls_changed[hls_index]:-false}" = true ] then hls_changed[hls_index]=false fi hls_change_once[hls_index]=false fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then channel_name[hls_index]="${chnl_schedules_channel_name[chnl_schedules_index]}" fi continue 3 fi done if [ -n "${monitor_output_dir_names:-}" ] then for((i=0;i<${#monitor_output_dir_names[@]};i++)); do if [ "${monitor_output_dir_names[i]}" == "$output_dir_name" ] then hls_indices+=("$i") hls_change[i]="${chnl_schedules_hls_change[chnl_schedules_index]}" if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then hls_change_once[i]=true else if [ "${hls_changed[i]:-false}" = true ] then hls_changed[i]=false fi hls_change_once[i]=false fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then channel_name[i]="${chnl_schedules_channel_name[chnl_schedules_index]}" fi continue 3 fi done fi monitor_output_dir_names+=("$output_dir_name") hls_index=$((${#monitor_output_dir_names[@]}-1)) hls_indices+=("$hls_index") hls_change[hls_index]="${chnl_schedules_hls_change[chnl_schedules_index]}" if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then hls_change_once[hls_index]=true else if [ "${hls_changed[hls_index]:-false}" = true ] then hls_changed[hls_index]=false fi hls_change_once[hls_index]=false fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then channel_name[hls_index]="${chnl_schedules_channel_name[chnl_schedules_index]}" fi fi continue 2 fi done elif [ "${chnls_flv_status[chnls_index]}" == "off" ] then for flv_index in ${flv_indices[@]+"${flv_indices[@]}"} do if [ "${monitor_flv_push_links[flv_index]}" == "${chnls_flv_push_link[chnls_index]}" ] then continue 2 fi done for flv_index in ${flv_failed[@]+"${flv_failed[@]}"} do if [ "${monitor_flv_push_links[flv_index]}" == "${chnls_flv_push_link[chnls_index]}" ] then continue 2 fi done for chnl_schedules_index in "${chnl_schedules_indices[@]}" do if [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 0 ] || [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 1 ] then if [ "${chnl_schedules_end_time[chnl_schedules_index]}" -le "$now" ] then MonitorLog "${chnl_schedules_channel_name[chnl_schedules_index]:-${chnls_channel_name[chnls_index]}} 计划到期关闭" if [ "${chnl_schedules_loop[chnl_schedules_index]}" = true ] then update=$( $JQ_FILE -n --arg start_time "$((${chnl_schedules_start_time[chnl_schedules_index]}+86400))" \ --arg end_time "$((${chnl_schedules_end_time[chnl_schedules_index]}+86400))" \ --arg loop "${chnl_schedules_loop[chnl_schedules_index]}" \ --arg auto_remove "${chnl_schedules_auto_remove[chnl_schedules_index]}" \ --arg hls_change "${chnl_schedules_hls_change[chnl_schedules_index]}" \ --arg hls_change_once "${chnl_schedules_hls_change_once[chnl_schedules_index]}" \ --arg channel_name "${chnl_schedules_channel_name[chnl_schedules_index]}" \ --arg status 0 \ '{ "start_time": $start_time | tonumber, "end_time": $end_time | tonumber, "loop": $loop | test("true"), "auto_remove": $auto_remove | test("true"), "hls_change": $hls_change | test("true"), "hls_change_once": $hls_change_once | test("true"), "channel_name": $channel_name, "status": $status | tonumber }' ) merge=true map_string=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"']' JQ update "$CHANNELS_FILE" output_dir_name "$output_dir_name" "$update" elif [ "${chnl_schedules_auto_remove[chnl_schedules_index]}" = true ] then map_string=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"']' JQ delete "$CHANNELS_FILE" output_dir_name "$output_dir_name" else map_string=true number=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"',"status"]' JQ update "$CHANNELS_FILE" output_dir_name "$output_dir_name" 2 fi declare -a new_array for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then unset 'hls_change[hls_index]' unset 'hls_change_once[hls_index]' unset 'hls_changed[hls_index]' unset 'channel_name[hls_index]' else new_array+=("$hls_index") fi done if [ -z "${new_array:-}" ] then hls_indices=() else hls_indices=("${new_array[@]}") fi unset new_array MonitorHlsRemoveFailed MonitorLog "${chnl_schedules_channel_name[chnl_schedules_index]:-${chnls_channel_name[chnls_index]}} 计划到期关闭成功" elif [ "${chnl_schedules_start_time[chnl_schedules_index]}" -le "$now" ] then if [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 0 ] then map_string=true number=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"',"status"]' JQ update "$CHANNELS_FILE" output_dir_name "$output_dir_name" 1 fi for hls_index in ${hls_failed[@]+"${hls_failed[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then hls_change[hls_index]="${chnl_schedules_hls_change[chnl_schedules_index]}" if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then hls_change_once[hls_index]=true else if [ "${hls_changed[hls_index]:-false}" = true ] then hls_changed[hls_index]=false fi hls_change_once[hls_index]=false fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then channel_name[hls_index]="${chnl_schedules_channel_name[chnl_schedules_index]}" fi continue 3 fi done for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then hls_change[hls_index]="${chnl_schedules_hls_change[chnl_schedules_index]}" if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then hls_change_once[hls_index]=true else if [ "${hls_changed[hls_index]:-false}" = true ] then hls_changed[hls_index]=false fi hls_change_once[hls_index]=false fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then channel_name[hls_index]="${chnl_schedules_channel_name[chnl_schedules_index]}" fi continue 3 fi done if [ -n "${monitor_output_dir_names:-}" ] then for((i=0;i<${#monitor_output_dir_names[@]};i++)); do if [ "${monitor_output_dir_names[i]}" == "$output_dir_name" ] then hls_indices+=("$i") hls_change[i]="${chnl_schedules_hls_change[chnl_schedules_index]}" if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then hls_change_once[i]=true else if [ "${hls_changed[i]:-false}" = true ] then hls_changed[i]=false fi hls_change_once[i]=false fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then channel_name[i]="${chnl_schedules_channel_name[chnl_schedules_index]}" fi continue 3 fi done fi monitor_output_dir_names+=("$output_dir_name") hls_index=$((${#monitor_output_dir_names[@]}-1)) hls_indices+=("$hls_index") hls_change[hls_index]="${chnl_schedules_hls_change[chnl_schedules_index]}" if [ "${chnl_schedules_hls_change_once[chnl_schedules_index]}" = true ] then hls_change_once[hls_index]=true else if [ "${hls_changed[hls_index]:-false}" = true ] then hls_changed[hls_index]=false fi hls_change_once[hls_index]=false fi if [ -n "${chnl_schedules_channel_name[chnl_schedules_index]}" ] then channel_name[hls_index]="${chnl_schedules_channel_name[chnl_schedules_index]}" fi else if [ "${chnl_schedules_status[chnl_schedules_index]}" -eq 1 ] then map_string=true number=true jq_path='["channels"]' jq_path2='["schedule",'"$chnl_schedules_index"',"status"]' JQ update "$CHANNELS_FILE" output_dir_name "$output_dir_name" 0 fi declare -a new_array for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then unset 'hls_change[hls_index]' unset 'hls_change_once[hls_index]' unset 'hls_changed[hls_index]' unset 'channel_name[hls_index]' else new_array+=("$hls_index") fi done if [ -z "${new_array:-}" ] then hls_indices=() else hls_indices=("${new_array[@]}") fi unset new_array MonitorHlsRemoveFailed fi continue 2 fi done fi done fi if [ "$recheck_period" -gt 0 ] then if [ -n "${flv_recheck_time:-}" ] then for((i=0;i<${#flv_recheck_time[@]};i++)); do if [ "$now" -ge "${flv_recheck_time[i]}" ] then for flv_index in ${flv_indices[@]+"${flv_indices[@]}"} do if [ "$flv_index" == "${flv_failed[i]}" ] then continue 2 fi done flv_indices+=("${flv_failed[i]}") fi done fi if [ -n "${hls_recheck_time:-}" ] then for((i=0;i<${#hls_recheck_time[@]};i++)); do if [ "$now" -ge "${hls_recheck_time[i]}" ] then for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "$hls_index" == "${hls_failed[i]}" ] then continue 2 fi done hls_indices+=("${hls_failed[i]}") fi done fi fi if [ "$anti_leech" = true ] && [ "$anti_leech_restart_nums" -gt 0 ] && [ "${rand_restart_flv_done:-}" != 0 ] && [ "${rand_restart_hls_done:-}" != 0 ] then current_minute_old=${current_minute:-} current_hour_old=${current_hour:-25} printf -v current_time '%(%H:%M)T' -1 current_hour=${current_time%:*} current_minute=${current_time#*:} if [ "${current_hour:0:1}" -eq 0 ] then current_hour=${current_hour:1} fi if [ "${current_minute:0:1}" -eq 0 ] then current_minute=${current_minute:1} fi if [ "$current_hour" != "$current_hour_old" ] then minutes=() skip_hour="" fi if [ -n "${minutes:-}" ] && [ "$current_minute" -gt "$current_minute_old" ] then declare -a new_array for minute in "${minutes[@]}" do if [ "$minute" -gt "$current_minute" ] then new_array+=("$minute") else rand_restart_flv_done=0 rand_restart_hls_done=0 fi done if [ -z "${new_array:-}" ] then minutes=() else minutes=("${new_array[@]}") fi unset new_array [ -z "${minutes:-}" ] && skip_hour=$current_hour fi if [ -z "${minutes:-}" ] && [ "$current_minute" -lt 59 ] && [ "$current_hour" != "${skip_hour:-}" ] then rand_restart_flv_done="" rand_restart_hls_done="" minutes_left=$((59 - current_minute)) restart_nums=$anti_leech_restart_nums [ "$restart_nums" -gt "$minutes_left" ] && restart_nums=$minutes_left minute_gap=$((minutes_left / anti_leech_restart_nums / 2)) [ "$minute_gap" -eq 0 ] && minute_gap=1 for((i=0;i /dev/null 2>&1 then for output_dir_root in "$LIVE_ROOT"/* do output_dir_name=${output_dir_root#*$LIVE_ROOT/} for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then continue 2 fi done exclude_command="$exclude_command -not \( -path $LIVE_ROOT/$output_dir_name -prune \)" done largest_file=$(find "$LIVE_ROOT" $exclude_command -type f -name "*.ts" -printf "%s %p\n" 2> /dev/null | sort -n | tail -1) || true if [ -n "${largest_file:-}" ] then largest_file_size=${largest_file%% *} largest_file_path=${largest_file#* } output_dir_name=${largest_file_path#*$LIVE_ROOT/} output_dir_name=${output_dir_name%%/*} if [ "$largest_file_size" -gt $(( hls_max_seg_size * 1000000)) ] then GetChannel MonitorLog "$chnl_channel_name 文件过大重启" for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then break fi done MonitorHlsRestartChannel fi fi fi if [ -z "${loop:-}" ] || [ "$loop" -eq 10 ] then loop=1 else loop=$((loop+1)) fi while IFS= read -r old_file_path do output_dir_name=${old_file_path#*$LIVE_ROOT/} output_dir_name=${output_dir_name%%/*} for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then GetChannel MonitorLog "$chnl_channel_name 超时重启" MonitorHlsRestartChannel break 2 fi done done < <(find "$LIVE_ROOT" -type f -name "*.ts" $exclude_command \! -newermt "-$hls_delay_seconds seconds" 2> /dev/null) for hls_index in ${hls_indices[@]+"${hls_indices[@]}"} do for chnls_index in "${chnls_indices[@]}" do output_dir_name="${chnls_output_dir_name[chnls_index]}" if [ "${monitor_output_dir_names[hls_index]}" == "$output_dir_name" ] then if [ "${chnls_status[chnls_index]}" == "off" ] then if [[ ${chnls_stream_link[chnls_index]} =~ ^https://(www\.)?(youtube.com|twitch.tv) ]] then sleep 10 else sleep 5 fi chnl_status="" GetChannel if [ -z "$chnl_status" ] then declare -a new_array for element in "${hls_indices[@]}" do [ "$element" != "$hls_index" ] && new_array+=("$element") done if [ -z "${new_array:-}" ] then hls_indices=() else hls_indices=("${new_array[@]}") fi unset new_array break 2 elif [ "$chnl_status" == "off" ] then MonitorLog "${channel_name[hls_index]:-$chnl_channel_name} 开启" MonitorHlsRestartChannel break 2 fi fi if [ "${rand_restart_hls_done:-}" != 0 ] && [ "$anti_leech" = true ] && [ "${chnls_encrypt[chnls_index]}" = true ] && [[ $((now-chnls_key_time[chnls_index])) -gt $hls_key_period ]] && ls -A "$LIVE_ROOT/$output_dir_name/"*.key > /dev/null 2>&1 then while IFS= read -r old_key do old_key_name=${old_key##*/} old_key_name=${old_key_name%%.*} [ "$old_key_name" != "${chnls_key_name[chnls_index]}" ] && rm -f "$old_key" done < <(find "$LIVE_ROOT/$output_dir_name" -type f -name "*.key" \! -newermt "-$hls_key_expire_seconds seconds" 2> /dev/null) new_key_name=$(RandStr) if openssl rand 16 > "$LIVE_ROOT/$output_dir_name/$new_key_name.key" then if [ "${chnls_encrypt_session[chnls_index]}" = true ] then if ! echo -e "/keys?key=$new_key_name&channel=$output_dir_name\n$LIVE_ROOT/$output_dir_name/$new_key_name.key\n$(openssl rand -hex 16)" > "$LIVE_ROOT/$output_dir_name/${chnls_keyinfo_name[chnls_index]}.keyinfo" then break 2 fi else if ! echo -e "$new_key_name.key\n$LIVE_ROOT/$output_dir_name/$new_key_name.key\n$(openssl rand -hex 16)" > "$LIVE_ROOT/$output_dir_name/${chnls_keyinfo_name[chnls_index]}.keyinfo" then break 2 fi fi update='{ "key_name": "'"$new_key_name"'", "key_time": '"$now"' }' merge=true jq_path='["channels"]' JQ update "$CHANNELS_FILE" pid "${chnls_pid[chnls_index]}" "$update" else break 2 fi fi if [ "$loop" -eq 1 ] && { [ "$anti_leech" = false ] || [ "${chnls_encrypt[chnls_index]}" = false ]; } then if [ "${chnls_encrypt[chnls_index]}" = true ] then if [ -e "$LIVE_ROOT/$output_dir_name/${chnls_keyinfo_name[chnls_index]}.keyinfo" ] && \ [ -e "$LIVE_ROOT/$output_dir_name/${chnls_key_name[chnls_index]}.key" ] && \ iv_hex=$(awk 'NR==3{print}' "$LIVE_ROOT/$output_dir_name/${chnls_keyinfo_name[chnls_index]}.keyinfo") && \ encrypt_key=$(hexdump -e '16/1 "%02x"' < "$LIVE_ROOT/$output_dir_name/${chnls_key_name[chnls_index]}.key") then encrypt_command="-key $encrypt_key -iv $iv_hex" else GetChannel MonitorLog "${channel_name[hls_index]:-$chnl_channel_name} 开启" MonitorHlsRestartChannel break 2 fi else encrypt_command="" fi audio=0 video=0 video_bitrate=0 bitrate_check=0 f_count=1 for f in "$LIVE_ROOT/$output_dir_name/${chnls_seg_dir_name[chnls_index]}/"*.ts do f_count=$((f_count+1)) done f_num=$((f_count/2)) f_count=1 for f in "$LIVE_ROOT/$output_dir_name/${chnls_seg_dir_name[chnls_index]}/"*.ts do if [ "$f_count" -lt "$f_num" ] then f_count=$((f_count+1)) continue fi [ -n "$encrypt_command" ] && f="crypto:$f" while IFS= read -r line do if [[ $line == *"codec_type=video"* ]] then video=1 elif [ "$bitrate_check" -eq 0 ] && [ "$video" -eq 1 ] && [[ $line == *"bit_rate="* ]] then line=${line#*bit_rate=} video_bitrate=${line//N\/A/$hls_min_bitrate} bitrate_check=1 elif [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 fi done < <($FFPROBE $encrypt_command -i "$f" -show_streams -loglevel quiet) break done if [ "$audio" -eq 0 ] || [ "$video" -eq 0 ] || [[ $video_bitrate -lt $hls_min_bitrate ]] then [ -n "$encrypt_command" ] && f="crypto:$f" fail_count=1 f_count=1 for f in "$LIVE_ROOT/$output_dir_name/${chnls_seg_dir_name[chnls_index]}/"*.ts do if [ "$f_count" -lt "$f_num" ] then f_count=$((f_count+1)) continue fi [ ! -e "$f" ] && continue audio=0 video=0 video_bitrate=0 bitrate_check=0 while IFS= read -r line do if [[ $line == *"codec_type=video"* ]] then video=1 elif [ "$bitrate_check" -eq 0 ] && [ "$video" -eq 1 ] && [[ $line == *"bit_rate="* ]] then line=${line#*bit_rate=} video_bitrate=${line//N\/A/$hls_min_bitrate} bitrate_check=1 elif [[ $line == *"codec_type=audio"* ]] then audio=1 elif [[ $line == *"sample_fmt=unknown"* ]] || [[ $line == *"sample_rate=0"* ]] || [[ $line == *"channels=0"* ]] then audio=0 fi done < <($FFPROBE $encrypt_command -i "$f" -show_streams -loglevel quiet) if [ "$audio" -eq 0 ] || [ "$video" -eq 0 ] || [[ $video_bitrate -lt $hls_min_bitrate ]] then fail_count=$((fail_count+1)) fi if [ "$fail_count" -gt 3 ] then GetChannel MonitorLog "$chnl_channel_name 码率过低重启" MonitorHlsRestartChannel break 2 fi done fi fi continue 2 fi done declare -a new_array for element in "${hls_indices[@]}" do [ "$element" != "$hls_index" ] && new_array+=("$element") done if [ -z "${new_array:-}" ] then hls_indices=() else hls_indices=("${new_array[@]}") fi unset new_array break done if [ -n "${hls_indices:-}" ] && [ -n "${rand_restart_hls_done:-}" ] && [ "$rand_restart_hls_done" -eq 0 ] then rand_restart_hls_done=1 for hls_index in "${hls_indices[@]}" do if [ "${hls_change[hls_index]:-true}" = false ] || [ "${hls_changed[hls_index]:-false}" = true ] then continue fi output_dir_name="${monitor_output_dir_names[hls_index]}" GetChannel MonitorLog "$chnl_channel_name HLS 随机重启" MonitorHlsRestartChannel done fi else rand_restart_hls_done=1 fi PrepTerm sleep 10 & WaitTerm done } 203>&- } 203<"$pid_file" } MonitorStop() { # deprecated if ls -A "/tmp/monitor.lockdir/"* > /dev/null 2>&1 then for PID in "/tmp/monitor.lockdir/"* do PID=${PID##*/} if kill -0 "$PID" 2> /dev/null then kill "$PID" 2> /dev/null MonitorLog "关闭监控 PID $PID !" else rm -f "/tmp/monitor.lockdir/$PID" fi done Println "$info 关闭监控, 稍等..." until ! ls -A "/tmp/monitor.lockdir/"* > /dev/null 2>&1 do sleep 1 done rm -rf "/tmp/monitor.lockdir/" Println "$info 监控关闭成功 !\n" fi if [ -s "$IPTV_ROOT/monitor.pid" ] then PID=$(< "$IPTV_ROOT/monitor.pid") if kill -0 "$PID" 2> /dev/null then Println "$info 关闭 HLS/FLV 监控, 稍等..." kill "$PID" 2> /dev/null i=0 while ps -p $PID -o pid= >/dev/null do sleep 0.05 i=$((i+1)) [ "$i" == 200 ] && Println "$error HLS/FLV 监控关闭超时, 请重试\n" && exit 1 done rm -f "$IPTV_ROOT/monitor.pid" Println "$info HLS/FLV 监控 关闭成功 !\n" MonitorLog "关闭监控 PID $PID !" else rm -f "$IPTV_ROOT/monitor.pid" Println "$info HLS/FLV 监控 关闭成功 !\n" fi else [ -e "$IPTV_ROOT/monitor.pid" ] && rm -f "$IPTV_ROOT/monitor.pid" Println "$error HLS/FLV 监控 未开启\n" fi if [ -s "$IPTV_ROOT/antiddos.pid" ] then PID=$(< "$IPTV_ROOT/antiddos.pid") if kill -0 "$PID" 2> /dev/null then Println "$info 关闭 antiddos, 稍等..." kill "$PID" 2> /dev/null if flock -E 1 -w 20 -x "$IPTV_ROOT/antiddos.pid" rm -f "$IPTV_ROOT/antiddos.pid" then Println "$info AntiDDos 监控 关闭成功 !\n" MonitorLog "关闭 antiddos PID $PID !" else Println "$error AntiDDos 监控关闭超时, 请重试\n" exit 1 fi else rm -f "$IPTV_ROOT/antiddos.pid" Println "$info AntiDDos 监控 关闭成功 !\n" fi elif [ -e "$IPTV_ROOT/antiddos.pid" ] then rm -f "$IPTV_ROOT/antiddos.pid" Println "$error AntiDDos 监控 未开启\n" fi if [ -s "$IP_DENY" ] then ips=() jail_time=() GetDefault if [[ $d_anti_ddos_port == *","* ]] || [[ $d_anti_ddos_port =~ - ]] then d_anti_ddos_port="$d_anti_ddos_port proto tcp" fi while IFS= read -r line do if [[ $line == *:* ]] then ip=${line%:*} jail=${line#*:} ips+=("$ip") jail_time+=("$jail") else ip="$line" ufw delete deny from "$ip" to any port $d_anti_ddos_port fi done < "$IP_DENY" if [ -n "${ips:-}" ] then new_ips=() new_jail_time=() printf -v now '%(%s)T' -1 update=0 for((i=0;i<${#ips[@]};i++)); do if [ "$now" -gt "${jail_time[i]}" ] then ufw delete deny from "${ips[i]}" to any port $d_anti_ddos_port update=1 else new_ips+=("${ips[i]}") new_jail_time+=("${jail_time[i]}") fi done if [ "$update" -eq 1 ] then ips=("${new_ips[@]}") jail_time=("${new_jail_time[@]}") printf '%s' "" > "$IP_DENY" for((i=0;i<${#ips[@]};i++)); do printf '%s\n' "${ips[i]}:${jail_time[i]}" >> "$IP_DENY" done fi else printf '%s' "" > "$IP_DENY" fi fi } MonitorLog() { printf -v date_now '%(%m-%d %H:%M:%S)T' -1 printf '%s\n' "$date_now $1" >> "$MONITOR_LOG" } MonitorErr() { printf -v date_now '%(%m-%d %H:%M:%S)T' -1 printf '%s\n' "$date_now [ERROR: $1]" >> "$MONITOR_LOG" } XtreamCodesDomainFilter() { local domain=$1 if [ ! -e "$XTREAM_CODES"_domain_filter ] then ShFallback if ! curl -s -Lm 10 "$SH_FALLBACK/xtream_codes_domain_filter" -o "$XTREAM_CODES"_domain_filter then echo "$domain" return 0 fi fi local domain_match domain_replace while IFS= read -r line do domain_match=${line% *} domain_replace=${line#* } if [[ $domain_match =~ : ]] then if [ "$domain" == "$domain_match" ] then domain="$domain_replace" break fi elif [[ $domain_replace =~ : ]] then if [ "${domain%:*}" == "$domain_match" ] then domain="$domain_replace" break fi elif [[ $domain =~ : ]] then if [ "${domain%:*}" == "$domain_match" ] then domain="$domain_replace:${domain#*:}" break fi elif [ "$domain" == "$domain_match" ] then domain="$domain_replace" break fi done < "$XTREAM_CODES"_domain_filter echo "$domain" } XtreamCodesGetDomains() { [ -n "${xtream_codes_domains:-}" ] && return 0 if [ ! -s "$XTREAM_CODES" ] then ShFallback curl -s -L "$SH_FALLBACK/$XTREAM_CODES_CHANNELS" -o "$XTREAM_CODES" fi IFS="," read -ra xtream_codes_domains <<< $(awk -v ORS=, '$1 { gsub(/\|/, ",", $2); print $2 }' "$XTREAM_CODES") } XtreamCodesGetChnls() { xc_chnls=() xc_chnls_mac=() if [ -n "${xtream_codes_domains:-}" ] then GetChannels if [ "$chnls_count" -gt 0 ] then for((xc_i=0;xc_i> "$XTREAM_CODES_EXAM" Println "$info 账号添加成功 !\n" } VerifyXtreamCodesMac() { to_continue=0 if [ "${test_mac_domain:-}" != "$domain" ] then test_mac_domain=$domain if [ "${skip_ip_check:-0}" -eq 0 ] then ip=$(getent ahosts "${domain%%:*}" | awk '{ print $1 ; exit }') || true fi if [ "$verify_mac" -eq 0 ] then return 0 fi Println "$info 验证 $domain ..." server="http://$domain" token_url="$server/portal.php?type=stb&action=handshake&token=&prehash=0&JsHttpRequest=1-xml" profile_url="$server/portal.php?type=stb&action=get_profile" account_info_url="$server/portal.php?type=account_info&action=get_main_info" [ -z "${timezone:-}" ] && timezone=$(UrlencodeUpper "Europe/Amsterdam") fi if [ -z "$ip" ] then to_continue=1 return 0 fi if [ "$verify_mac" -eq 0 ] then return 0 fi mac_address="$account" access_token="" profile="" exp_date="" mac=$(UrlencodeUpper "$mac_address") access_token=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $USER_AGENT_TV" \ --cookie "mac=$mac; stb_lang=en; timezone=$timezone" "$token_url" \ | $JQ_FILE -r '.js.token' 2> /dev/null) || true if [ -z "$access_token" ] then Println "$error $domain $mac_address" to_continue=1 return 0 fi profile=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $USER_AGENT_TV" \ -H "Authorization: Bearer $access_token" \ --cookie "mac=$mac; stb_lang=en; timezone=$timezone" "$profile_url" | $JQ_FILE -r '.js.id // ""' 2> /dev/null) || true exp_date=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $USER_AGENT_TV" \ -H "Authorization: Bearer $access_token" \ --cookie "mac=$mac; stb_lang=en; timezone=$timezone" "$account_info_url" | $JQ_FILE -r '.js.phone' 2> /dev/null) || true if [ -z "$exp_date" ] then if [ -z "$profile" ] then Println "$error $domain $mac_address profile" else Println "$error $domain $mac_address exp_date" fi to_continue=1 return 0 fi account="$mac_address" } XtreamCodesList() { if [ -s "$XTREAM_CODES_EXAM" ] && [ ! -f "$XTREAM_CODES" ] then printf '%s' "" > "$XTREAM_CODES" elif [ ! -s "$XTREAM_CODES" ] then Println "$error 没有账号 !\n" exit 1 fi ips=() new_domains=() new_accounts=() verify_mac=${verify_mac:-0} if [ "$verify_mac" -eq 0 ] then IFS=" " read -r m_ip m_domains m_accounts < <(awk '$1 {a=a $1",";b=b $2",";$1=$2="";c=c substr($0,3)","} END {print a,b,c}' "$XTREAM_CODES") IFS="," read -r -a ips <<< "$m_ip" IFS="," read -r -a new_domains <<< "$m_domains" IFS="," read -r -a new_accounts <<< "$m_accounts" fi if [ -s "$XTREAM_CODES_EXAM" ] then while IFS= read -r line do if [[ $line =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} ]] then if [[ $line =~ ([^ ]+)\ ([^ ]+)\ ([^ ]+) ]] then skip_ip_check=1 ip=${BASH_REMATCH[1]} domain=${BASH_REMATCH[2]} account=${BASH_REMATCH[3]} if [[ $account =~ (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})) ]] then VerifyXtreamCodesMac 2> /dev/null if [ "$to_continue" -eq 1 ] then skip_ip_check=0 continue fi fi skip_ip_check=0 else continue fi elif [[ $line == *"username="* ]] then domain=${line#*http://} if [ "${test_mac_domain:-}" != "$domain" ] then test_mac_domain=$domain ip=$(getent ahosts "${domain%%:*}" | awk '{ print $1 ; exit }') || true fi [ -z "$ip" ] && continue domain=${domain%%/*} username=${line#*username=} username=${username%%&*} password=${line#*password=} password=${password%%&*} account="$username:$password" elif [[ $line =~ http://([^/]+)/([^/]+)/([^/]+)/ ]] then if [ "${BASH_REMATCH[2]}" == "live" ] then if ! [[ $line =~ http://([^/]+)/live/([^/]+)/([^/]+)/ ]] then continue fi fi domain=${BASH_REMATCH[1]} if [ "${test_mac_domain:-}" != "$domain" ] then test_mac_domain=$domain ip=$(getent ahosts "${domain%%:*}" | awk '{ print $1 ; exit }') || true fi [ -z "$ip" ] && continue username=${BASH_REMATCH[2]} password=${BASH_REMATCH[3]} account="$username:$password" elif [[ $line =~ http://([^/]+)/ ]] then stb_domain=${BASH_REMATCH[1]} if [[ ! $line =~ (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})) ]] then continue fi domain="$stb_domain" account=${BASH_REMATCH[1]} VerifyXtreamCodesMac 2> /dev/null if [ "$to_continue" -eq 1 ] then continue fi elif [ -n "${stb_domain:-}" ] && [[ $line =~ (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})) ]] then domain="$stb_domain" account=${BASH_REMATCH[1]} VerifyXtreamCodesMac 2> /dev/null if [ "$to_continue" -eq 1 ] then continue fi else continue fi for((i=0;i<${#ips[@]};i++)); do if [[ ${ips[i]} == *"$ip"* ]] then if ! [[ ${new_domains[i]} == *"$domain"* ]] then new_domains[i]="${new_domains[i]}|$domain" fi if ! [[ ${new_accounts[i]} == *"$account"* ]] then new_accounts[i]="${new_accounts[i]} $account" fi continue 2 fi done for((i=0;i<${#new_domains[@]};i++)); do if [[ ${new_domains[i]} == *"$domain"* ]] then if ! [[ ${ips[i]} == *"$ip"* ]] then ips[i]="${ips[i]}|$ip" fi if ! [[ ${new_accounts[i]} == *"$account"* ]] then new_accounts[i]="${new_accounts[i]} $account" fi continue 2 fi done ips+=("$ip") new_domains+=("$domain") new_accounts+=("$account") done < <(awk '$1=$1' "$XTREAM_CODES_EXAM") fi if [ -n "${ips:-}" ] then ips_count=${#ips[@]} print_list="" xtream_codes_list="" ips_acc_count=0 ips_acc=() ips_mac_count=0 ips_mac=() for((i=0;i "$XTREAM_CODES" printf '%s' "" > "$XTREAM_CODES_EXAM" fi if [ "${1:-}" == "mac" ] && [ "$ips_mac_count" -eq 0 ] then Println "$error 请先添加 mac 地址!\n" && exit 1 else Println "$xtream_codes_list" fi else Println "$error 没有账号!\n" && exit 1 fi } XtreamCodesListAcc() { XtreamCodesList Println "请输入服务器的序号" while read -p "$i18n_default_cancel" server_num do case $server_num in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$server_num" -gt 0 ] && [ "$server_num" -le "$ips_acc_count" ] then ips_index=${ips_acc[server_num-1]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done domain=${new_domains[ips_index]} if [[ $domain == *"|"* ]] then IFS="|" read -ra domains <<< "$domain" domains_list="" domains_count=${#domains[@]} for((i=0;i /dev/null then printf "${green}%s${normal}\r\033[12C%-21s%-21s${green}%s${normal}\n%s\n\n" "[成功]" "$username" "$password" "$domain" "http://$domain/$username/$password/$channel_id" elif $FFPROBE $proxy_command -i "http://$domain/live/$username/$password/$channel_id.ts" -rw_timeout 5000000 -show_streams -select_streams a -loglevel quiet > /dev/null then printf "${green}%s${normal}\r\033[12C%-21s%-21s${green}%s${normal}\n%s\n\n" "[成功]" "$username" "$password" "$domain" "http://$domain/live/$username/$password/$channel_id.ts" else printf "${red}%s${normal}\r\033[12C%-21s%-21s${red}%s${normal}\n%s" "[失败]" "$username" "$password" "$domain" fi done done echo } XtreamCodesListMac() { XtreamCodesList mac Println "请输入服务器的序号" while read -p "$i18n_default_cancel" server_num do case $server_num in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$server_num" -gt 0 ] && [ "$server_num" -le "$ips_mac_count" ] then ips_index=${ips_mac[server_num-1]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done domain=${new_domains[ips_index]} if [[ $domain == *"|"* ]] then IFS="|" read -ra domains <<< "$domain" domains_list="" domains_count=${#domains[@]} for((i=0;i /dev/null) || ordered_list_page="" fi ordered_list_pages[page_index]="$ordered_list_page" fi while IFS= read -r name do name=${name#\"} name=${name%\"} name_lower=$(tr '[:upper:]' '[:lower:]' <<< "$name") if [[ $name_lower == *"$search_phrase"* ]] then search_result="$search_result页数: ${green}$i${normal} 频道名称: ${green}$name${normal}\n\n" fi done < <($JQ_FILE '.js.data[].name' <<< "$ordered_list_page") done } XtreamCodesListChnls() { if [ -z "${FFMPEG:-}" ] then FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFMPEG="$FFMPEG_ROOT/ffmpeg" FFPROBE="$FFMPEG_ROOT/ffprobe" fi while true do if [ -n "${xtream_codes_list:-}" ] then Println "$xtream_codes_list" else XtreamCodesList mac fi Println "请输入服务器的序号" while read -p "$i18n_default_cancel" server_num do case $server_num in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$server_num" -gt 0 ] && [ "$server_num" -le "$ips_mac_count" ] then ips_index=${ips_mac[server_num-1]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done domain=${new_domains[ips_index]} if [[ $domain == *"|"* ]] then IFS="|" read -ra domains <<< "$domain" domains_list="" domains_count=${#domains[@]} for((i=0;i /dev/null) || true if [ -z "$access_token" ] then Println "$error $domain $mac_address access\n" mac_addresses_failed+=("$mac_address") for mac in "${macs[@]}" do if [ "$mac_address" != "$mac" ] then for mac_address_failed in "${mac_addresses_failed[@]}" do if [ "$mac_address_failed" == "$mac" ] then continue 2 fi done for xc_chnl_mac in ${xc_chnls_mac[@]+"${xc_chnls_mac[@]}"} do if [ "$xc_chnl_mac" == "$domain/$mac" ] then continue 2 fi done Println "$info 测试 $mac\n" mac_address="$mac" continue 2 fi done exit 1 fi headers="Authorization: Bearer $access_token\r\n" printf -v headers_command '%b' "$headers" profile=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ --cookie "$cookies" "$profile_url" | $JQ_FILE -r '.js.id // ""' 2> /dev/null) || true exp_date=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ --cookie "$cookies" "$account_info_url" | $JQ_FILE -r '.js.phone' 2> /dev/null) || true if [ -z "$exp_date" ] then if [ -z "$profile" ] then Println "$error $domain $mac_address profile\n" else Println "$error $domain $mac_address exp_date\n" fi mac_addresses_failed+=("$mac_address") for mac in "${macs[@]}" do if [ "$mac_address" != "$mac" ] then for mac_address_failed in "${mac_addresses_failed[@]}" do if [ "$mac_address_failed" == "$mac" ] then continue 2 fi done for xc_chnl_mac in ${xc_chnls_mac[@]+"${xc_chnls_mac[@]}"} do if [ "$xc_chnl_mac" == "$domain/$mac" ] then continue 2 fi done Println "$info 测试 $mac\n" mac_address="$mac" continue 2 fi done exit 1 fi genres_list="" genres_count=0 genres_id=() while IFS="=" read -r map_id map_title do map_id=${map_id#\"} map_title=${map_title%\"} genres_count=$((genres_count+1)) genres_id+=("$map_id") genres_list="$genres_list ${green}$genres_count.${normal}${indent_6}$map_title\n\n" done < <(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ --cookie "$cookies" "$genres_url" \ | $JQ_FILE '.js[] | [.id,.title] | join("=")') if [ -n "$genres_list" ] then genres_list_pages=() while true do Println "$genres_list\n\n${green}账号到期时间:${normal} $exp_date\n" if [ "${return_err:-0}" -eq 1 ] then return_err=0 Println "$error 返回错误, 请重试" fi Println "输入分类序号, 输入 a 返回上级页面, 输入 b 使用下个 mac 地址" while read -p "$i18n_default_cancel" genres_num do case "$genres_num" in "") Println "$i18n_canceled...\n" && exit ;; a) continue 4 ;; b) mac_addresses_failed+=("$mac_address") for mac in "${macs[@]}" do if [ "$mac_address" != "$mac" ] then for mac_address_failed in "${mac_addresses_failed[@]}" do if [ "$mac_address_failed" == "$mac" ] then continue 2 fi done for xc_chnl_mac in ${xc_chnls_mac[@]+"${xc_chnls_mac[@]}"} do if [ "$xc_chnl_mac" == "$domain/$mac" ] then continue 2 fi done Println "$info 测试 $mac\n" mac_address="$mac" continue 4 fi done Println "$error 没有剩余 mac 地址\n" exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$genres_num" -gt 0 ] && [ "$genres_num" -le "$genres_count" ] then genres_index=$((genres_num-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done if [ -n "${genres_list_pages[genres_index]:-}" ] then ordered_list_page=${genres_list_pages[genres_index]} else ordered_list_url="$server/portal.php?type=itv&action=get_ordered_list&genre=${genres_id[genres_index]}&force_ch_link_check=&fav=0&sortby=number&hd=0&p=1" ordered_list_page=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ --cookie "$cookies" "$ordered_list_url" | $JQ_FILE -r -c '.' 2> /dev/null) || ordered_list_page="" [ -z "$ordered_list_page" ] && return_err=1 && continue 2 genres_list_pages[genres_index]="$ordered_list_page" fi exec 100< <($JQ_FILE -r '.js.total_items, .js.max_page_items' <<< "$ordered_list_page") read total_items <&100 read max_page_items <&100 exec 100<&- if [ "$total_items" == null ] || [ "${total_items:-0}" -eq 0 ] then Println "$error 此分类没有频道!\n" continue fi if [ "$total_items" -le "$max_page_items" ] then pages=1 else pages=$((total_items / max_page_items)) if [ "$total_items" -gt $((pages * max_page_items)) ] then pages=$((pages+1)) fi fi page=1 ordered_list_pages=() while true do page_index=$((page-1)) if [ -n "${ordered_list_pages[page_index]:-}" ] then ordered_list_page=${ordered_list_pages[page_index]} else if [ "$page" -gt 1 ] then ordered_list_url="$server/portal.php?type=itv&action=get_ordered_list&genre=${genres_id[genres_index]}&force_ch_link_check=&fav=0&sortby=number&hd=0&p=$page" ordered_list_page=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ --cookie "$cookies" "$ordered_list_url" | $JQ_FILE -r -c '.' 2> /dev/null) || ordered_list_page="" [ -z "$ordered_list_page" ] && return_err=1 && continue 3 fi ordered_list_pages[page_index]="$ordered_list_page" fi xc_chnls_id=() xc_chnls_name=() xc_chnls_cmd=() xc_chnls_list="" xc_chnls_count=0 while IFS="^" read -r map_id map_cmd map_name do xc_chnls_count=$((xc_chnls_count+1)) map_id=${map_id#\"} map_name=${map_name%\"} map_cmd=${map_cmd#* } map_cmd=${map_cmd%\_} map_cmd="http://localhost/ch/${map_cmd##*/}_" xc_chnls_id+=("$map_id") xc_chnls_name+=("$map_name") xc_chnls_cmd+=("$map_cmd") xc_chnls_list="$xc_chnls_list# ${green}$xc_chnls_count${normal} $map_name\n\n" done < <($JQ_FILE '.js.data[] | [.id,.cmd,.name] | join("^")' <<< "$ordered_list_page") Println "$xc_chnls_list" echo -e "$tip 输入 a 返回上级页面" echo -e "$tip 输入 s 频道名称 搜索频道" echo -e "$tip 输入 p 页数 跳转页面" if [ "$pages" -gt 1 ] then Println "当前第 $page 页, 共 $pages 页" if [ "$page" -eq 1 ] then echo -e "$tip 输入 x 转到下一页" elif [ "$page" -eq "$pages" ] then echo -e "$tip 输入 z 转到上一页" else echo -e "$tip 输入 z 转到上一页, 输入 x 转到下一页" fi fi echo && while read -p "输入频道序号: " xc_chnls_num do if [[ $xc_chnls_num =~ ^s\ * ]] then search_phrase=${xc_chnls_num#*s } lead=${search_phrase%%[^[:blank:]]*} search_phrase=${search_phrase#${lead}} if [ -z "$search_phrase" ] then Println "$error 搜索内容不能为空\n" else SearchXtreamCodesChnls fi if [ -n "$search_result" ] then Println "搜索结果:\n\n$search_result" else Println "$error 没有搜索结果\n" fi continue elif [[ $xc_chnls_num =~ ^p\ [0-9]+ ]] then if [ "${xc_chnls_num#* }" -le "$pages" ] then page=${xc_chnls_num#* } continue 2 else Println "$error 页数错误\n" continue fi fi case "$xc_chnls_num" in a) continue 3 ;; z) if [ "$page" -gt 1 ] then page=$((page-1)) continue 2 else Println "$error 没有上一页\n" fi ;; x) if [ "$page" -lt "$pages" ] then page=$((page+1)) continue 2 else Println "$error 没有下一页\n" fi ;; ""|*[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$xc_chnls_num" -gt 0 ] && [ "$xc_chnls_num" -le "$xc_chnls_count" ] then xc_chnls_index=$((xc_chnls_num-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done if [ "$use_proxy_yn" == "$i18n_yes" ] then stream_link="$server/?cmd=${xc_chnls_cmd[xc_chnls_index]}" Println "${green}${xc_chnls_name[xc_chnls_index]}:${normal} $stream_link\n" else create_link_url="$server/portal.php?type=itv&action=create_link&cmd=${xc_chnls_cmd[xc_chnls_index]}&series=&forced_storage=undefined&disable_ad=0&download=0" cmd=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $user_agent" \ -H "${headers:0:-4}" \ --cookie "$cookies" "$create_link_url" \ | $JQ_FILE -r '.js.cmd') || true if [[ ${cmd#* } =~ ([^/]+)//([^/]+)/(.+) ]] then stream_link="http://localhost:3000/$(XtreamCodesDomainFilter ${BASH_REMATCH[2]})/${BASH_REMATCH[3]}" else Println "$error 返回 cmd: ${cmd:-无} 错误, 请重试" continue fi stream_link=${stream_link// /} stream_link=${stream_link//.ts.ts/.ts} Println "${green}${xc_chnls_name[xc_chnls_index]}:${normal} $stream_link\n" fi EXIT_STATUS=0 printf -v headers_command '%b' "$headers" printf -v cookies_command '%b' "${cookies//;/; path=\/;\\r\\n}; path=/;" echo inquirer list_input_index "截图" ny_options ny_options_index if [[ $ny_options_index -eq 1 ]] then Println "$tip 格式如 HH:MM:SS" inquirer text_input "输入截图位置" ss 00:00:03 if TMP_FILE=$(mktemp -q) then chmod +r "$TMP_FILE" else printf -v TMP_FILE '%(%m-%d-%H:%M:%S)T' -1 fi trap ' rm -f "$TMP_FILE" rm -f "${TMP_FILE}.jpeg" ' EXIT $FFMPEG -hide_banner -loglevel debug -user_agent "$user_agent" \ -headers "$headers_command" -cookies "$cookies_command" -i "$stream_link" -ss "$ss" -frames:v 1 "${TMP_FILE}.jpeg" || EXIT_STATUS=$? if [ ! -e "/usr/local/bin/imgcat" ] then ImgcatInstall fi /usr/local/bin/imgcat --half-height "${TMP_FILE}.jpeg" 2> /dev/null || EXIT_STATUS=$? rm -f "$TMP_FILE" rm -f "${TMP_FILE}.jpeg" trap - EXIT else $FFPROBE -hide_banner -loglevel debug -show_streams -user_agent "$user_agent" \ -headers "$headers_command" -cookies "$cookies_command" -i "$stream_link" || EXIT_STATUS=$? fi if [ "$EXIT_STATUS" -ne 0 ] && [ "$use_proxy_yn" == "$i18n_yes" ] then Println "$info 尝试直连 ..." # curl -k -s -o /dev/null -w '%{redirect_url}' IFS=" " read -r stream_link new_access_token new_cookies < <(CurlFake xtream_codes -sL \ -H "User-Agent: $user_agent" \ --cookie "$cookies" \ "$server/?cmd=${xc_chnls_cmd[xc_chnls_index]}&check=1" | $JQ_FILE -r '.|join(" ")' 2> /dev/null) || true if [[ ! $stream_link =~ ([^/]+)//([^/]+)/(.+) ]] then Println "$error curl -sL '$server/?cmd=${xc_chnls_cmd[xc_chnls_index]}&check=1' -H 'User-Agent: $user_agent' -H '${headers:0:-4}' --cookie '$cookies'" Println "$error 返回错误[ stream_link: ${stream_link:-无} ], 请重试" continue fi EXIT_STATUS=0 access_token="$new_access_token" cookies="$new_cookies" printf -v headers_command '%b' "$Authorization: Bearer $access_token" printf -v cookies_command '%b' "${cookies//;/; path=\/;\\r\\n}; path=/;" if [[ $ny_options_index -eq 1 ]] then if TMP_FILE=$(mktemp -q) then chmod +r "$TMP_FILE" else printf -v TMP_FILE '%(%m-%d-%H:%M:%S)T' -1 fi trap ' rm -f "$TMP_FILE" rm -f "${TMP_FILE}.jpeg" ' EXIT $FFMPEG -hide_banner -loglevel debug -user_agent "$user_agent" \ -headers "$headers_command" -cookies "$cookies_command" -i "$stream_link" -ss "$ss" -frames:v 1 "${TMP_FILE}.jpeg" || EXIT_STATUS=$? /usr/local/bin/imgcat --half-height "${TMP_FILE}.jpeg" 2> /dev/null || EXIT_STATUS=$? rm -f "$TMP_FILE" rm -f "${TMP_FILE}.jpeg" trap - EXIT else $FFPROBE -hide_banner -loglevel debug -show_streams -user_agent "$user_agent" \ -headers "$headers_command" -cookies "$cookies_command" -i "$stream_link" || EXIT_STATUS=$? fi fi if [ "$EXIT_STATUS" -eq 0 ] then echo inquirer list_input "是否添加此频道" ny_options add_channel_yn if [ "$add_channel_yn" == "$i18n_yes" ] then if [ "$use_proxy_yn" == "$i18n_yes" ] then if [[ $stream_link =~ cmd= ]] then headers="" headers_command="" else headers="Authorization: Bearer $access_token\r\n" printf -v headers_command '%b' "$headers" fi fi stream_links=("$domain|$stream_link|${xc_chnls_cmd[xc_chnls_index]}|$mac_address") echo inquirer list_input "是否 添加/替换 现有频道直播源" ny_options add_channel_yn if [ "$add_channel_yn" == "$i18n_yes" ] then ListChannels InputChannelsIndex for((i=0;i<${#chnls_pid_chosen[@]};i++)); do chnl_pid=${chnls_pid_chosen[i]} chnls_index=${chnls_indices[i]} ListChannel echo change_options=( '添加' '替换' ) inquirer list_input_index "如何修改频道 [ $chnl_channel_name ]" change_options change_options_index if [ "$change_options_index" -eq 0 ] then pre=true jq_path='["channels",'"$chnls_index"',"stream_link"]' JQ add "$CHANNELS_FILE" ["\"${stream_links[0]}\""] else echo inquirer list_input_index "选择替换的直播源" chnl_stream_links chnl_stream_links_index jq_path='["channels",'"$chnls_index"',"stream_link",'"$chnl_stream_links_index"']' JQ update "$CHANNELS_FILE" "${stream_links[0]}" fi Println "$info 频道 [ $chnl_channel_name ] 修改成功 !\n" done else echo inquirer list_input "是否推流 flv" ny_options add_channel_flv_yn if [[ $add_channel_flv_yn == "$i18n_yes" ]] then kind="flv" fi if [ "$use_proxy_yn" == "$i18n_yes" ] then xtream_codes_proxy="$server" fi skip_set_stream_link=true AddChannel fi else continue fi else Println "$error 频道不可用或账号权限不够\n" continue fi break done break done else Println "$error $mac_address 错误, 找不到分类! 账号到期时间: $exp_date\n" mac_addresses_failed+=("$mac_address") for mac in "${macs[@]}" do if [ "$mac_address" != "$mac" ] then for mac_address_failed in "${mac_addresses_failed[@]}" do if [ "$mac_address_failed" == "$mac" ] then continue 2 fi done for xc_chnl_mac in ${xc_chnls_mac[@]+"${xc_chnls_mac[@]}"} do if [ "$xc_chnl_mac" == "$domain/$mac" ] then continue 2 fi done Println "$info 测试 $mac\n" mac_address="$mac" continue 2 fi done exit 1 fi break done break done } XtreamCodesAddMac() { echo && read -p "请输入服务器地址: " server [ -z "$server" ] && Println "$i18n_canceled...\n" && exit 1 domain=${server#*http://} domain=${domain%%/*} ip=$(getent ahosts "${domain%%:*}" | awk '{ print $1 ; exit }') || true [ -z "${ip:-}" ] && Println "$error 无法解析域名 !\n" && exit 1 echo && read -p "请输入 mac 地址(多个地址空格分隔): " mac_address [ -z "$mac_address" ] && Println "$i18n_canceled...\n" && exit 1 IFS=" " read -ra macs <<< "$mac_address" GetDefault if [ -n "${d_xc_proxy:-}" ] then echo inquirer list_input "是否使用代理 $d_xc_proxy 验证: " yn_options use_proxy_yn if [ "$use_proxy_yn" == "$i18n_yes" ] then server="${d_xc_proxy%\/}/http://$domain" else server="http://$domain" fi else server="http://$domain" fi timezone=$(UrlencodeUpper "Europe/Amsterdam") token_url="$server/portal.php?type=stb&action=handshake&token=&prehash=0&JsHttpRequest=1-xml" profile_url="$server/portal.php?type=stb&action=get_profile" account_info_url="$server/portal.php?type=account_info&action=get_main_info" Println "$info 验证中..." add_mac_success=false for mac_address in "${macs[@]}" do access_token="" exp_date="" mac=$(UrlencodeUpper "$mac_address") access_token=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $USER_AGENT_TV" \ --cookie "mac=$mac; stb_lang=en; timezone=$timezone" "$token_url" \ | $JQ_FILE -r '.js.token' 2> /dev/null) || true if [ -z "$access_token" ] then Println "$error $domain $mac_address access_token" continue fi profile=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $USER_AGENT_TV" \ -H "Authorization: Bearer $access_token" \ --cookie "mac=$mac; stb_lang=en; timezone=$timezone" "$profile_url" | $JQ_FILE -r '.js.id // ""' 2> /dev/null) || true exp_date=$(CurlFake xtream_codes -s -Lm 10 \ -H "User-Agent: $USER_AGENT_TV" \ -H "Authorization: Bearer $access_token" \ --cookie "mac=$mac; stb_lang=en; timezone=$timezone" "$account_info_url" | $JQ_FILE -r '.js.phone' 2> /dev/null) || true if [ -z "$exp_date" ] then Println "$error $domain $mac_address exp_date" continue fi add_mac_success=true printf '%s\n' "$ip $domain $mac_address" >> "$XTREAM_CODES_EXAM" done } NginxDomainInstallCert() { local domain=$1 if [ -e "/usr/local/nginx/conf/sites_crt/$domain.crt" ] && [ -d /usr/local/openresty/nginx/conf ] && [ ! -e "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" ] then mkdir -p /usr/local/openresty/nginx/conf/sites_crt ln "/usr/local/nginx/conf/sites_crt/$domain.crt" "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" ln "/usr/local/nginx/conf/sites_crt/$domain.key" "/usr/local/openresty/nginx/conf/sites_crt/$domain.key" elif [ -e "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" ] && [ -d /usr/local/nginx/conf ] && [ ! -e "/usr/local/nginx/conf/sites_crt/$domain.crt" ] then mkdir -p /usr/local/nginx/conf/sites_crt ln "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" "/usr/local/nginx/conf/sites_crt/$domain.crt" ln "/usr/local/openresty/nginx/conf/sites_crt/$domain.key" "/usr/local/nginx/conf/sites_crt/$domain.key" fi if [ -e "$nginx_prefix/conf/sites_crt/$domain.crt" ] && [ -e "$nginx_prefix/conf/sites_crt/$domain.key" ] then echo inquirer list_input "检测到证书已存在, 是否重新安装证书" ny_options reinstall_crt_yn if [ "$reinstall_crt_yn" == "$i18n_no" ] then return 0 fi fi AcmeCheck Println "$info 安装 $domain 证书..." NginxDomainUpdateCrt "$domain" 1 Println "$info $domain 证书安装成功" } OpenrestyPackageInstall() { # nc -vz 127.0.0.1 80 DepInstall lsof if lsof -Pi :80 -sTCP:LISTEN -t &>/dev/null then Println "$error 80 端口被占用, 请先关闭占用端口的程序\n" return 1 fi DepInstall curl DepInstall ca-certificates ArchCheck if [ "$arch" == "arm64" ] then arch_path="/arm64" else arch_path="" fi . /etc/os-release case $dist in ubu) DepInstall lsb-release DepInstall ubuntu-keyring DepInstall gpg if [ ! -f /etc/apt/sources.list.d/openresty.list ] then if grep -q "mirrors.ustc.edu.cn" < /etc/apt/sources.list then curl -fsSL https://mirrors.ustc.edu.cn/openresty/pubkey.gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/openresty.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] https://mirrors.ustc.edu.cn/openresty${arch_path}/ubuntu \ ${VERSION_CODENAME:-$UBUNTU_CODENAME} main" | tee /etc/apt/sources.list.d/openresty.list else curl -fsSL https://openresty.org/package/pubkey.gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/openresty.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package${arch_path}/ubuntu \ ${VERSION_CODENAME:-$UBUNTU_CODENAME} main" | tee /etc/apt/sources.list.d/openresty.list fi sudo apt-get update fi sudo apt-get -y install openresty --no-install-recommends ;; deb) DepInstall lsb-release DepInstall debian-archive-keyring DepInstall gpg if [ ! -f /etc/apt/sources.list.d/openresty.list ] then if grep -q "mirrors.ustc.edu.cn" < /etc/apt/sources.list then curl -fsSL https://mirrors.ustc.edu.cn/openresty/pubkey.gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/openresty.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] https://mirrors.ustc.edu.cn/openresty${arch_path}/debian \ ${VERSION_CODENAME} openresty" | sudo tee /etc/apt/sources.list.d/openresty.list else curl -fsSL https://openresty.org/package/pubkey.gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/openresty.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package${arch_path}/debian \ ${VERSION_CODENAME} openresty" | sudo tee /etc/apt/sources.list.d/openresty.list fi sudo apt-get update fi sudo apt-get -y install openresty --no-install-recommends ;; rpm) DepInstall yum-utils if [ ! -f /etc/yum.repos.d/openresty.repo ] then dist_names=( amazon alinux tlinux oracle rocky fedora centos rhel ) for dist_name in "${dist_names[@]}" do if grep -qi "$dist_name" <<< "$ID" || grep -qi "$dist_name" <<< "$NAME" then case $dist_name in amazon|alinux|tlinux|oracle|fedora) curl -s -L https://openresty.org/package/$dist_name/openresty.repo -o /etc/yum.repos.d/openresty.repo ;; rocky|centos|rhel) if [ "${VERSION_ID%%.*}" -gt 8 ] then curl -s -L https://openresty.org/package/$dist_name/openresty2.repo -o /etc/yum.repos.d/openresty.repo else curl -s -L https://openresty.org/package/$dist_name/openresty.repo -o /etc/yum.repos.d/openresty.repo fi ;; esac break fi done fi sudo yum check-update sudo yum install -y openresty openresty-opm openresty-doc ;; *) Println "$error 不支持的系统\n" return 1 ;; esac if ! grep -q "$nginx_name:" < "/etc/passwd" then if grep -q '\--group ' < <(adduser --help) then adduser "$nginx_name" --system --group --no-create-home > /dev/null else adduser "$nginx_name" --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin "$nginx_name" fi sed -i "s/#user nobody;/user $nginx_name $nginx_name;/" "$nginx_prefix/conf/nginx.conf" sed -i "s/worker_connections 1024;/worker_connections 51200;/" "$nginx_prefix/conf/nginx.conf" mkdir -p "$nginx_prefix/conf/sites_crt/" mkdir -p "$nginx_prefix/conf/sites_available/" mkdir -p "$nginx_prefix/conf/sites_enabled/" mkdir -p "$nginx_prefix/html/localhost/" CrossplaneInstall Println "$info $nginx_name 安装成功\n" } OpenrestySourceInstall() { local install="更新" if [ -z "${1:-}" ] then if [[ -x $(command -v $nginx_name) ]] then return 0 fi install="安装" elif [[ ! -x $(command -v $nginx_name) ]] then install="安装" fi DepsCheck echo pcre_options=( pcre pcre2 ) inquirer list_input_index "选择 pcre 版本" pcre_options pcre_options_index Println "$tip 如果选择 openssl, $nginx_name 将不支持 ssl_early_data (0-RTT)" openssl_options=( openssl@1.1 quictls ) inquirer list_input_index "选择 openssl 版本" openssl_options openssl_options_index echo inquirer list_input_index "额外添加编译选项" ny_options ny_index install_options_selected=() if [ "$ny_index" -eq 1 ] then echo install_options=( --with-http_iconv_module --with-http_postgres_module --with-http_slice_module ) inquirer checkbox_input "选择编译选项" install_options install_options_selected fi cd ~ rm -rf nginx-http-flv-module-master curl -L "$FFMPEG_MIRROR_LINK/nginx-http-flv-module.zip" -o nginx-http-flv-module.zip Println "$info 解压 nginx-http-flv-module ..." unzip nginx-http-flv-module.zip >/dev/null 2>&1 #cd nginx-http-flv-module-master #curl -L "$FFMPEG_MIRROR_LINK/Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch" -o Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch #patch -p1 < Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch #cd ~ latest_release=0 while IFS= read -r line do if [[ $line == *"Lastest release"* ]] then latest_release=1 elif [ "$latest_release" -eq 1 ] && [[ $line == *" /dev/null) curl -L "https://openresty.org/download/$openresty_package_name.tar.gz" -o "$openresty_package_name.tar.gz" Println "$info 解压 $openresty_package_name ..." tar xzf "$openresty_package_name.tar.gz" if [ "$dist" == "rpm" ] then . /etc/os-release case ${VERSION_ID%%.*} in 9) dnf config-manager --set-enabled crb dnf install -y epel-release if dnf list epel-next-release &>/dev/null then dnf install -y epel-next-release fi ;; 8) dnf config-manager --set-enabled powertools dnf install -y epel-release if dnf list epel-next-release &>/dev/null then dnf install -y epel-next-release fi ;; *) yum install -y epel-release ;; esac yum groupinstall -y 'Development Tools' timedatectl set-timezone Asia/Shanghai systemctl restart crond yum install -y pcre-devel openssl-devel gcc zlib-devel libpq-devel else timedatectl set-timezone Asia/Shanghai systemctl restart cron apt-get -y install debconf-utils echo '* libraries/restart-without-asking boolean true' | debconf-set-selections apt-get -y install libpcre3-dev perl make software-properties-common pkg-config libssl-dev libghc-zlib-dev libcurl4-gnutls-dev libexpat1-dev unzip gettext build-essential libpq-dev fi while IFS= read -r line do if [[ $line =~ \ /dev/null) if [ ! -d $zlib_name ] then curl -L https://www.zlib.net/$zlib_name.tar.gz -o $zlib_name.tar.gz Println "$info 解压 $zlib_name ..." tar xzf $zlib_name.tar.gz fi if [ "$pcre_options_index" -eq 0 ] then pcre_name=pcre-8.45 if [ ! -d $pcre_name ] then curl -L https://downloads.sourceforge.net/pcre/pcre/${pcre_name#*-}/$pcre_name.zip -o $pcre_name.zip Println "$info 解压 $pcre_name ..." unzip $pcre_name.zip >/dev/null 2>&1 fi else pcre_name=$(curl -Lm 20 "$FFMPEG_MIRROR_LINK/pcre2.json" | $JQ_FILE -r '.tag_name') if [ ! -d $pcre_name ] then curl -L $FFMPEG_MIRROR_LINK/pcre2/$pcre_name/$pcre_name.zip -o $pcre_name.zip Println "$info 解压 $pcre_name ..." unzip $pcre_name.zip >/dev/null 2>&1 fi fi openssl_url="https://www.openssl.org/source/old" openssl_vers=($(curl -s -Lm 20 $openssl_url/ | grep -oP '
  • \K[^<]+')) for openssl_ver in "${openssl_vers[@]}" do if [ "${openssl_ver%%.*}" -eq 1 ] then break fi done openssl_url="$openssl_url/$openssl_ver" openssl_packs=($(curl -s -Lm 20 $openssl_url/ | grep -oP '\K[^<]+')) openssl_pack="${openssl_packs[0]}" openssl_name=${openssl_pack%.tar*} if [ "$openssl_options_index" -eq 1 ] then openssl_ver=${openssl_name#*-} openssl_name=openssl-OpenSSL_${openssl_ver//./_}-quic1 fi if [ ! -d ${openssl_name}-patched ] || [ ! -s ${openssl_name}-patched/openssl-1.1.1f-sess_set_get_cb_yield.patch ] then rm -rf ${openssl_name:-notfound} rm -rf ${openssl_name}-patched if [ "$openssl_options_index" -eq 1 ] then Println "$info 下载 ${openssl_name#*-} ..." if ! curl -Lm 30 https://github.com/quictls/openssl/archive/refs/tags/${openssl_name#*-}.tar.gz -o "$openssl_name".tar.gz then curl -L "$FFMPEG_MIRROR_LINK/${openssl_name#*-}".tar.gz -o "$openssl_name".tar.gz fi Println "$info 解压 ${openssl_name#*-} ..." tar xzf ${openssl_name}.tar.gz else curl -L "$openssl_url/$openssl_pack" -o "$openssl_pack" Println "$info 解压 $openssl_name ..." tar xzf "$openssl_pack" fi mv ${openssl_name} ${openssl_name}-patched cd ${openssl_name}-patched curl -L "$FFMPEG_MIRROR_LINK/openssl-1.1.1f-sess_set_get_cb_yield.patch" -o openssl-1.1.1f-sess_set_get_cb_yield.patch patch -p1 < openssl-1.1.1f-sess_set_get_cb_yield.patch || true cd ~ fi cd "$openresty_package_name/bundle/ngx_lua-"* curl -L "$FFMPEG_MIRROR_LINK/fix_ngx_lua_resp_get_headers_key_whitespace.patch" -o fix_ngx_lua_resp_get_headers_key_whitespace.patch patch -p1 < fix_ngx_lua_resp_get_headers_key_whitespace.patch || true cd ../.. ./configure \ --add-module=../nginx-http-flv-module-master \ --with-pcre=../$pcre_name \ --with-pcre-jit \ --with-zlib=../$zlib_name \ --with-openssl=../${openssl_name}-patched \ --with-openssl-opt="no-threads shared zlib -g enable-ssl3 enable-ssl3-method enable-ec_nistp_64_gcc_128" \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_v3_module \ --without-mail_pop3_module \ --without-mail_imap_module \ --without-mail_smtp_module \ --without-http_rds_json_module \ --without-http_rds_csv_module \ --without-lua_rds_parser \ --with-http_stub_status_module \ --with-http_realip_module \ --with-debug \ --with-http_addition_module \ --with-http_auth_request_module \ --with-http_secure_link_module \ --with-http_slice_module \ --with-http_random_index_module \ --with-http_gzip_static_module \ --with-http_sub_module \ --with-http_dav_module \ --with-http_degradation_module \ --with-http_flv_module \ --with-http_mp4_module \ --with-http_gunzip_module \ --with-stream \ --with-stream_ssl_preread_module \ --with-stream_ssl_module \ --with-stream_realip_module \ --with-threads \ --with-luajit-xcflags=-DLUAJIT_NUMMODE=2\ -DLUAJIT_ENABLE_LUA52COMPAT\ -fno-stack-check ${install_options_selected[@]+"${install_options_selected[@]}"} nproc="-j$(nproc 2> /dev/null)" || nproc="-j1" make clean make $nproc make install ln -sf "$nginx_prefix"/sbin/nginx /usr/local/bin/ if ! grep -q "$nginx_name:" < "/etc/passwd" then if grep -q '\--group ' < <(adduser --help) then adduser "$nginx_name" --system --group --no-create-home > /dev/null else adduser "$nginx_name" --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin "$nginx_name" fi sed -i "s/#user nobody;/user $nginx_name $nginx_name;/" "$nginx_prefix/conf/nginx.conf" sed -i "s/worker_processes .*/worker_processes ${nproc:2};/" "$nginx_prefix/conf/nginx.conf" sed -i "s/worker_connections 1024;/worker_connections 51200;/" "$nginx_prefix/conf/nginx.conf" mkdir -p "$nginx_prefix/conf/sites_crt/" mkdir -p "$nginx_prefix/conf/sites_available/" mkdir -p "$nginx_prefix/conf/sites_enabled/" mkdir -p "$nginx_prefix/html/localhost/" CrossplaneInstall Println "$info $nginx_name ${install}成功\n" } OpenrestyInstall() { if [ -d "$nginx_prefix" ] then Println "$error $nginx_name 已经存在 $nginx_prefix !\n" return 1 elif [[ -x $(command -v $nginx_name) ]] then Println "$error $nginx_name 已经存在 $(command -v $nginx_name), 请先卸载!\n" return 1 fi Println "$tip 选择快速安装将缺少 nginx-http-flv-module 和 quictls 选择" openresty_install_options=( "${nginx_name}官方包 (快速安装)" '编译安装' ) inquirer list_input_index "选择安装方式" openresty_install_options openresty_install_options_index if [ "$openresty_install_options_index" -eq 0 ] then OpenrestyPackageInstall return fi OpenrestySourceInstall } OpenrestyUninstall() { if [ ! -d "$nginx_prefix" ] then Println "$error $nginx_name 未安装 !\n" return 1 fi echo ExitOnList n "`eval_gettext \"确定删除 \\\$nginx_name 包括所有配置文件, 操作不可恢复\"`" systemctl stop $nginx_name > /dev/null 2>&1 || true systemctl disable $nginx_name > /dev/null 2>&1 || true if [ "$dist" == "rpm" ] && rpm -q "$nginx_name" &> /dev/null then yum remove -y "$nginx_name"* || true elif [ "$dist" != "rpm" ] && dpkg -s "$nginx_name" &> /dev/null then apt-get -y --purge remove "$nginx_name"* || true fi rm -rf "${nginx_prefix%/*}" Println "$info $nginx_name 卸载完成\n" } OpenrestyUpdate() { ShFileUpdate "$nginx_name" if [ ! -d "$nginx_prefix" ] then Println "$error $nginx_name 未安装 !\n" return 1 fi if [ "$dist" == "rpm" ] && rpm -q "$nginx_name" &> /dev/null then yum update -y "$nginx_name" elif { [ "$dist" == "deb" ] || [ "$dist" == "ubu" ]; } && dpkg -s "$nginx_name" &> /dev/null then apt-get -y install --only-upgrade "$nginx_name" else OpenrestySourceInstall update fi } NginxPackageInstall() { DepInstall curl DepInstall ca-certificates . /etc/os-release case $dist in ubu) if ! [[ "${VERSION_CODENAME:-$UBUNTU_CODENAME}" =~ focal|jammy|mantic ]] then Println "$tip Nginx 官方不支持当前版本 ${VERSION_CODENAME:-$UBUNTU_CODENAME}, 安装可能出错" fi DepInstall lsb-release DepInstall ubuntu-keyring DepInstall gpg if [ ! -f /etc/apt/sources.list.d/nginx.list ] then if grep -q "mirrors.ustc.edu.cn" < /etc/apt/sources.list then curl -fsSL https://mirrors.ustc.edu.cn/nginx/keys/nginx_signing.key | gpg --batch --yes --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://mirrors.ustc.edu.cn/nginx/mainline/ubuntu \ ${VERSION_CODENAME:-$UBUNTU_CODENAME} nginx" | tee /etc/apt/sources.list.d/nginx.list else curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --batch --yes --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu \ ${VERSION_CODENAME:-$UBUNTU_CODENAME} nginx" | tee /etc/apt/sources.list.d/nginx.list fi echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" | sudo tee /etc/apt/preferences.d/99nginx sudo apt-get update fi sudo apt-get -y install nginx ;; deb) if ! [[ "${VERSION_CODENAME}" =~ bullseye|bookworm ]] then Println "$tip Nginx 官方不支持当前版本 ${VERSION_CODENAME}, 安装可能出错" fi DepInstall lsb-release DepInstall debian-archive-keyring DepInstall gpg if [ ! -f /etc/apt/sources.list.d/nginx.list ] then if grep -q "mirrors.ustc.edu.cn" < /etc/apt/sources.list then curl -fsSL https://mirrors.ustc.edu.cn/nginx/keys/nginx_signing.key | gpg --batch --yes --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://mirrors.ustc.edu.cn/nginx/mainline/debian \ ${VERSION_CODENAME} nginx" | sudo tee /etc/apt/sources.list.d/nginx.list else curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --batch --yes --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/debian \ ${VERSION_CODENAME} nginx" | sudo tee /etc/apt/sources.list.d/nginx.list fi echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" | sudo tee /etc/apt/preferences.d/99nginx sudo apt-get update fi sudo apt-get -y install nginx ;; rpm) if [ "${VERSION_ID%%.*}" -eq 7 ] then Println "$tip 官方说明: Packages for RHEL 7 and SLES 12 are built without HTTP/3 support because OpenSSL used by those doesn't support TLSv1.3." fi DepInstall yum-utils if [ ! -f /etc/yum.repos.d/nginx.repo ] then if grep -q Amazon < /etc/os-release then if [ "$VERSION" == "2" ] then cat > /etc/yum.repos.d/nginx.repo << EOF [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/amzn2/\$releasever/\$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true priority=9 [nginx-mainline] name=nginx mainline repo baseurl=http://nginx.org/packages/mainline/amzn2/\$releasever/\$basearch/ gpgcheck=1 enabled=0 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true priority=9 EOF elif [ "$VERSION" == "2023" ] then cat > /etc/yum.repos.d/nginx.repo << EOF [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/amzn/2023/\$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true priority=9 [nginx-mainline] name=nginx mainline repo baseurl=http://nginx.org/packages/mainline/amzn/2023/\$basearch/ gpgcheck=1 enabled=0 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true priority=9 EOF else Println "$error 不支持的系统\n" return 1 fi else cat > /etc/yum.repos.d/nginx.repo << EOF [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/centos/\$releasever/\$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true [nginx-mainline] name=nginx mainline repo baseurl=http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/ gpgcheck=1 enabled=0 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true EOF fi fi sudo yum-config-manager --disable nginx-stable sudo yum-config-manager --enable nginx-mainline sudo yum -y install nginx ;; *) Println "$error 不支持的系统\n" return 1 ;; esac if [ ! -d "$nginx_prefix" ] then mkdir -p "$nginx_prefix"/sbin mv /usr/sbin/nginx* "$nginx_prefix"/sbin/ ln -sf /etc/nginx "$nginx_prefix"/conf ln -sf /var/log/nginx "$nginx_prefix"/logs ln -sf /usr/share/nginx/html "$nginx_prefix"/html ln -sf /var/run/nginx.pid "$nginx_prefix"/logs/nginx.pid for file in "$nginx_prefix"/sbin/* do cat > /usr/local/bin/${file##*/} < /dev/null else adduser "$nginx_name" --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin "$nginx_name" fi sed -i "s/#user nobody;/user $nginx_name $nginx_name;/" "$nginx_prefix/conf/nginx.conf" sed -i "s/worker_connections 1024;/worker_connections 51200;/" "$nginx_prefix/conf/nginx.conf" sed -i '/conf.d/d' "$nginx_prefix/conf/nginx.conf" sed -i 's|/var/log/nginx|logs|g' "$nginx_prefix/conf/nginx.conf" sed -i 's|/var/run|logs|g' "$nginx_prefix/conf/nginx.conf" sed -i 's|/etc/nginx/||g' "$nginx_prefix/conf/nginx.conf" mkdir -p "$nginx_prefix/conf/sites_crt/" mkdir -p "$nginx_prefix/conf/sites_available/" mkdir -p "$nginx_prefix/conf/sites_enabled/" mkdir -p "$nginx_prefix/html/localhost/" CrossplaneInstall Println "$info $nginx_name 安装成功\n" } NginxSourceInstall() { local install="更新" if [ -z "${1:-}" ] then if [[ -x $(command -v $nginx_name) ]] then return 0 fi install="安装" elif [[ ! -x $(command -v $nginx_name) ]] then install="安装" fi DepsCheck echo pcre_options=( pcre pcre2 ) inquirer list_input_index "选择 pcre 版本" pcre_options pcre_options_index Println "$tip 如果选择 openssl, $nginx_name 将不支持 ssl_early_data (0-RTT)" openssl_options=( openssl@1.1 openssl@3 quictls ) inquirer list_input_index "选择 openssl 版本" openssl_options openssl_options_index cd ~ rm -rf nginx-http-flv-module-master curl -L "$FFMPEG_MIRROR_LINK/nginx-http-flv-module.zip" -o nginx-http-flv-module.zip Println "$info 解压 nginx-http-flv-module ..." unzip nginx-http-flv-module.zip >/dev/null 2>&1 #cd nginx-http-flv-module-master #curl -s -L "$FFMPEG_MIRROR_LINK/Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch" -o Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch #patch -p1 < Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch #cd ~ while IFS= read -r line do if [[ $line == *"/download/"* ]] then nginx_package_name=${line#*/download/} nginx_package_name=${nginx_package_name%%.tar.gz*} break fi done < <(curl -s -Lm 20 -H "User-Agent: $USER_AGENT_BROWSER" https://nginx.org/en/download.html 2> /dev/null) if [ ! -d "./$nginx_package_name" ] then curl -L "https://nginx.org/download/$nginx_package_name.tar.gz" -o "$nginx_package_name.tar.gz" Println "$info 解压 $nginx_package_name ..." tar xzf "$nginx_package_name.tar.gz" fi if [ "$dist" == "rpm" ] then . /etc/os-release case ${VERSION_ID%%.*} in 9) dnf config-manager --set-enabled crb dnf install -y epel-release if dnf list epel-next-release &>/dev/null then dnf install -y epel-next-release fi ;; 8) dnf config-manager --set-enabled powertools dnf install -y epel-release if dnf list epel-next-release &>/dev/null then dnf install -y epel-next-release fi ;; *) yum install -y epel-release ;; esac yum groupinstall -y 'Development Tools' timedatectl set-timezone Asia/Shanghai systemctl restart crond yum install -y pcre-devel openssl-devel gcc zlib-devel else timedatectl set-timezone Asia/Shanghai systemctl restart cron apt-get -y install debconf-utils echo '* libraries/restart-without-asking boolean true' | debconf-set-selections apt-get -y install libpcre3-dev perl make software-properties-common pkg-config libssl-dev libghc-zlib-dev libcurl4-gnutls-dev libexpat1-dev unzip gettext build-essential fi while IFS= read -r line do if [[ $line =~ \ /dev/null) if [ ! -d $zlib_name ] then curl -L https://www.zlib.net/$zlib_name.tar.gz -o $zlib_name.tar.gz Println "$info 解压 $zlib_name ..." tar xzf $zlib_name.tar.gz fi if [ "$pcre_options_index" -eq 0 ] then pcre_name=pcre-8.45 if [ ! -d $pcre_name ] then curl -L https://downloads.sourceforge.net/pcre/pcre/${pcre_name#*-}/$pcre_name.zip -o $pcre_name.zip Println "$info 解压 $pcre_name ..." unzip $pcre_name.zip >/dev/null 2>&1 fi else pcre_name=$(curl -s -Lm 20 "$FFMPEG_MIRROR_LINK/pcre2.json" | $JQ_FILE -r '.tag_name') if [ ! -d $pcre_name ] then curl -L $FFMPEG_MIRROR_LINK/pcre2/$pcre_name/$pcre_name.zip -o $pcre_name.zip Println "$info 解压 $pcre_name ..." unzip $pcre_name.zip >/dev/null 2>&1 fi fi if [ "$openssl_options_index" -eq 2 ] then openssl_name=openssl-OpenSSL_1_1_1w-quic1 Println "$info 下载 ${openssl_name#*-} ..." if ! curl -Lm 30 https://github.com/quictls/openssl/archive/refs/tags/${openssl_name#*-}.tar.gz -o "$openssl_name".tar.gz then curl -L "$FFMPEG_MIRROR_LINK/${openssl_name#*-}".tar.gz -o "$openssl_name".tar.gz fi Println "$info 解压 ${openssl_name#*-} ..." tar xzf "$openssl_name".tar.gz else if [ "$openssl_options_index" -eq 0 ] then openssl_url="https://www.openssl.org/source/old" openssl_vers=($(curl -s -Lm 20 $openssl_url/ | grep -oP '
  • \K[^<]+')) for openssl_ver in "${openssl_vers[@]}" do if [ "${openssl_ver%%.*}" -eq 1 ] then break fi done openssl_url="$openssl_url/$openssl_ver" else openssl_url="https://www.openssl.org/source" fi openssl_packs=($(curl -s -Lm 20 $openssl_url/ | grep -oP '\K[^<]+')) openssl_pack="${openssl_packs[0]}" openssl_name=${openssl_pack%.tar*} if [ ! -d "./$openssl_name" ] then curl -L "$openssl_url/$openssl_pack" -o "$openssl_pack" Println "$info 解压 $openssl_name ..." tar xzf "$openssl_pack" fi fi cd "$nginx_package_name/" ./configure \ --add-module=../nginx-http-flv-module-master \ --with-debug \ --with-http_addition_module \ --with-http_auth_request_module \ --with-http_dav_module \ --with-http_degradation_module \ --with-http_gunzip_module \ --with-http_gzip_static_module \ --with-http_mp4_module \ --with-http_random_index_module \ --with-http_realip_module \ --with-http_secure_link_module \ --with-http_slice_module \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_sub_module \ --with-http_v2_module \ --with-http_v3_module \ --with-http_flv_module \ --with-mail \ --with-mail_ssl_module \ --with-pcre=../$pcre_name \ --with-pcre-jit \ --with-zlib=../$zlib_name \ --with-stream \ --with-stream_realip_module \ --with-stream_ssl_module \ --with-stream_ssl_preread_module \ --with-openssl=../$openssl_name \ --with-threads nproc="-j$(nproc 2> /dev/null)" || nproc="-j1" make clean make $nproc make install ln -sf "$nginx_prefix"/sbin/nginx /usr/local/bin/ if ! grep -q "$nginx_name:" < "/etc/passwd" then if grep -q '\--group ' < <(adduser --help) then adduser "$nginx_name" --system --group --no-create-home > /dev/null else adduser "$nginx_name" --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin "$nginx_name" fi sed -i "s/#user nobody;/user $nginx_name $nginx_name;/" "$nginx_prefix/conf/nginx.conf" sed -i "s/worker_processes .*/worker_processes ${nproc:2};/" "$nginx_prefix/conf/nginx.conf" sed -i "s/worker_connections 1024;/worker_connections 51200;/" "$nginx_prefix/conf/nginx.conf" mkdir -p "$nginx_prefix/conf/sites_crt/" mkdir -p "$nginx_prefix/conf/sites_available/" mkdir -p "$nginx_prefix/conf/sites_enabled/" mkdir -p "$nginx_prefix/html/localhost/" CrossplaneInstall Println "$info $nginx_name ${install}成功\n" } NginxInstall() { if [ -d "$nginx_prefix" ] then Println "$error $nginx_name 已经存在 $nginx_prefix !\n" return 1 elif [[ -x $(command -v $nginx_name) ]] then Println "$error $nginx_name 已经存在 $(command -v $nginx_name), 请先卸载!\n" return 1 fi Println "$tip 选择快速安装将缺少 nginx-http-flv-module 以及 quictls 选择" nginx_install_options=( "${nginx_name}官方包 (快速安装)" '编译安装' ) inquirer list_input_index "选择安装方式" nginx_install_options nginx_install_options_index if [ "$nginx_install_options_index" -eq 0 ] then NginxPackageInstall return fi NginxSourceInstall } NginxUninstall() { if [ ! -d "$nginx_prefix" ] then Println "$error $nginx_name 未安装 !\n" return 1 fi echo ExitOnList n "`eval_gettext \"确定删除 \\\$nginx_name 包括所有配置文件, 操作不可恢复\"`" systemctl stop $nginx_name || true DepInstall file if file -h "$nginx_prefix/conf" | grep -q 'symbolic link' then if [ "$dist" == "yum" ] then yum -y remove "$nginx_name" || true else apt-get -y --purge remove "$nginx_name"* || true fi rm -f /usr/local/bin/"$nginx_name"* fi rm -rf "$nginx_prefix" Println "$info $nginx_name 卸载完成\n" } NginxUpdate() { ShFileUpdate "$nginx_name" if [ ! -d "$nginx_prefix" ] then Println "$error $nginx_name 未安装 !\n" return 1 fi DepInstall file if ! file -h "$nginx_prefix/conf" | grep -q 'symbolic link' then NginxSourceInstall update return fi if [ "$dist" == "rpm" ] then yum update -y "$nginx_name" else apt-get -y install --only-upgrade "$nginx_name" fi } NginxViewStatus() { if [ ! -d "$nginx_prefix" ] then Println "$error $nginx_name 未安装 !\n" else systemctl --no-pager -l status $nginx_name fi } NginxToggle() { echo if [[ $(systemctl is-active $nginx_name) == "active" ]] then ExitOnList y "`eval_gettext \"\\\$nginx_name 正在运行, 是否关闭\"`" if [[ $(echo $SSH_CONNECTION | cut -d' ' -f3) == "127.0.0.1" ]] then Println "$error 请使用非 $nginx_name 监听端口连接 ssh 后重试\n" exit 1 fi systemctl stop $nginx_name Println "$info $nginx_name 已关闭\n" else ExitOnList y "`eval_gettext \"\\\$nginx_name 未运行, 是否开启\"`" systemctl start $nginx_name Println "$info $nginx_name 已开启\n" fi } NginxRestart() { if ! $NGINX_FILE -t then Println "$error 请检查配置错误\n" exit 1 fi echo nginx_actions=( '重载配置' '强制重启' ) inquirer list_input_index "选择操作" nginx_actions nginx_actions_index if [ "$nginx_actions_index" -eq 0 ] then nginx_action=reload-or-restart else nginx_action=restart fi if systemctl $nginx_action $nginx_name then Println "$info $nginx_name 重启成功\n" else Println "$error $nginx_name 重启失败, 请检查配置\n" fi } NginxParseConfig() { CrossplaneInstall if TMP_FILE=$(mktemp -q) then chmod +r "$TMP_FILE" else exit $? fi trap ' rm -f "$TMP_FILE" ' EXIT if [ -z "${1:-}" ] then parse_file="$nginx_prefix/conf/nginx.conf" parse_in=$(< $parse_file) parse_domain=0 else parse_file="$nginx_prefix/conf/sites_available/$1.conf" parse_in="http {$(< $parse_file)}" parse_domain=1 fi echo "$parse_in" > "$TMP_FILE" parse_out=$(crossplane parse "$TMP_FILE" --single-file) rm -f "$TMP_FILE" trap - EXIT jq_path='["config",0,"file"]' JQs update parse_out "$parse_file" } NginxGetConfig() { [ -z "${delimiters:-}" ] && delimiters=( $'\001' $'\002' $'\003' $'\004' $'\005' $'\006' ) IFS=$'\007\t' read -r status error_message level_1_directive level_1_args \ level_2_directive level_2_args level_3_directive level_3_args \ level_4_directive level_4_args level_5_directive level_5_args < <( JQs flat_c "$parse_out" '' \ '(.config.parsed|if (.|type == "string") then {} else . end) as $level_1 | ($level_1.block|if (.|type == "string") then {} else . end) as $level_2 | ($level_2.block|if (.|type == "string") then {} else . end) as $level_3 | ($level_3.block|if (.|type == "string") then {} else . end) as $level_4 | ($level_4.block|if (.|type == "string") then {} else . end) as $level_5 | [.status + "\u0007", (.errors|if . == "" then {} else . end).error + "\u0007", ($level_1.directive|if . != null then (. + $d2) else . end) + "\u0007", ($level_1.args|if . != null then (. + $d2) else . end) + "\u0007", ($level_2.directive|if . != null then (. + $d3) else . end) + "\u0007", ($level_2.args|if . != null then (. + $d3) else . end) + "\u0007", ($level_3.directive|if . != null then (. + $d4) else . end) + "\u0007", ($level_3.args|if . != null then (. + $d4) else . end) + "\u0007", ($level_4.directive|if . != null then (. + $d5) else . end) + "\u0007", ($level_4.args|if . != null then (. + $d5) else . end) + "\u0007", ($level_5.directive|if . != null then (. + $d6) else . end) + "\u0007", ($level_5.args|if . != null then (. + $d6) else . end) + "\u0007" ]|@tsv' "${delimiters[@]}") if [ "$status" == "failed" ] then Println "$error ${error_message//$'\002'/$'\n'}\n" exit 1 fi # level 1 - stream,http... # level 2 - map,upstream,server... # level 3 - location... # level 4 - proxy_pass,root,index... # level 5 - return... level_1_count=0 level_2_d1_count=0 level_3_d1_count=0 level_4_d1_count=0 level_5_d1_count=0 if [ -z "$level_1_directive" ] then return 0 fi IFS="${delimiters[1]}" read -r -a level_1_directive_arr <<< "$level_1_directive" IFS="${delimiters[1]}" read -r -a level_1_args_arr <<< "$level_1_args" level_1_count=${#level_1_directive_arr[@]} if [ -z "$level_2_directive" ] then return 0 fi IFS="${delimiters[2]}" read -r -a level_2_directive_arr <<< "$level_2_directive" IFS="${delimiters[2]}" read -r -a level_2_args_arr <<< "$level_2_args" level_2_d1_count=${#level_2_directive_arr[@]} if [ -z "$level_3_directive" ] then return 0 fi IFS="${delimiters[3]}" read -r -a level_3_directive_arr <<< "$level_3_directive" IFS="${delimiters[3]}" read -r -a level_3_args_arr <<< "$level_3_args" level_3_d1_count=${#level_3_directive_arr[@]} if [ -z "$level_4_directive" ] then return 0 fi IFS="${delimiters[4]}" read -r -a level_4_directive_arr <<< "$level_4_directive" IFS="${delimiters[4]}" read -r -a level_4_args_arr <<< "$level_4_args" level_4_d1_count=${#level_4_directive_arr[@]} if [ -z "$level_5_directive" ] then return 0 fi IFS="${delimiters[5]}" read -r -a level_5_directive_arr <<< "$level_5_directive" IFS="${delimiters[5]}" read -r -a level_5_args_arr <<< "$level_5_args" level_5_d1_count=${#level_5_directive_arr[@]} } NginxListDomains() { if [ ! -d "$nginx_prefix" ] then Println "$error $nginx_name 未安装 ! 输入 $nginx_ctl 安装 $nginx_name\n" exit 1 fi OpensslInstall nginx_domains_list="" nginx_domains_count=0 nginx_domains=() nginx_domains_status=() if ls -A "$nginx_prefix/conf/sites_available/"* > /dev/null 2>&1 then for f in "$nginx_prefix/conf/sites_available/"* do nginx_domains_count=$((nginx_domains_count+1)) domain=${f##*/} domain=${domain%.conf} if [ -f "$nginx_prefix/conf/sites_enabled/$domain.conf" ] then domain_status=1 domain_status_text="${green} [开启] ${normal}" else domain_status=0 domain_status_text="${red} [关闭] ${normal}" fi if [ -f "$nginx_prefix/conf/sites_crt/$domain.crt" ] then domain_expire_date=$(date +%c --date="$(openssl x509 -enddate -noout -in $nginx_prefix/conf/sites_crt/$domain.crt | cut -d= -f 2)") if openssl x509 -checkend 1209600 -noout -in "$nginx_prefix/conf/sites_crt/$domain.crt" > /dev/null then domain_expire_text=" ${green}[$domain_expire_date]${normal}" elif openssl x509 -checkend 0 -noout -in "$nginx_prefix/conf/sites_crt/$domain.crt" > /dev/null then domain_expire_text=" ${yellow}[$domain_expire_date]${normal}" else domain_expire_text=" ${red}[$domain_expire_date]${normal}" fi else domain_expire_text="" fi nginx_domains_list="$nginx_domains_list ${green}$nginx_domains_count.${normal}${indent_6}$domain $domain_status_text$domain_expire_text\n\n" nginx_domains+=("$domain") nginx_domains_status+=("$domain_status") done fi if [ "$nginx_domains_count" -gt 0 ] then Println "${green}域名列表:${normal}\n\n$nginx_domains_list" fi } NginxSelectDomain() { echo "选择域名" while read -p "$i18n_default_cancel" nginx_domains_index do case "$nginx_domains_index" in "") Println "$i18n_canceled...\n" exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$nginx_domains_index" -gt 0 ] && [ "$nginx_domains_index" -le "$nginx_domains_count" ] then nginx_domains_index=$((nginx_domains_index-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done } NginxListDomain() { level_1_add_indices=( 0 ) NginxListDomains if [ "$nginx_domains_count" -eq 0 ] then Println "$error 没有域名\n" exit 1 fi NginxSelectDomain NginxParseConfig ${nginx_domains[nginx_domains_index]} NginxGetConfig if [ "$level_3_d1_count" -eq 0 ] then Println "$error 请先添加 ${nginx_domains[nginx_domains_index]} 配置\n" exit 1 fi nginx_domain_servers_list="" nginx_domain_servers_count=0 nginx_domain_servers_indices=() nginx_domain_servers_name=() nginx_domain_servers_root=() level_1_index=0 level_2_directive_d1=${level_2_directive_arr[level_1_index]} level_3_directive_d1=${level_3_directive_arr[level_1_index]} level_3_args_d1=${level_3_args_arr[level_1_index]} IFS="${delimiters[1]}" read -r -a level_2_directive_d1_arr <<< "$level_2_directive_d1${delimiters[1]}" IFS="${delimiters[2]}" read -r -a level_3_directive_d1_arr <<< "$level_3_directive_d1${delimiters[2]}" IFS="${delimiters[2]}" read -r -a level_3_args_d1_arr <<< "$level_3_args_d1${delimiters[2]}" if [ "$level_4_d1_count" -gt 0 ] then level_4_directive_d1=${level_4_directive_arr[level_1_index]} level_4_args_d1=${level_4_args_arr[level_1_index]} IFS="${delimiters[3]}" read -r -a level_4_directive_d1_arr <<< "$level_4_directive_d1${delimiters[3]}" IFS="${delimiters[3]}" read -r -a level_4_args_d1_arr <<< "$level_4_args_d1${delimiters[3]}" fi for((level_2_index=0;level_2_index<${#level_2_directive_d1_arr[@]};level_2_index++)); do if [ "${level_2_directive_d1_arr[level_2_index]}" == "server" ] then level_3_directive_d2=${level_3_directive_d1_arr[level_2_index]} level_3_args_d2=${level_3_args_d1_arr[level_2_index]} IFS="${delimiters[1]}" read -r -a level_3_directive_d2_arr <<< "$level_3_directive_d2${delimiters[1]}" IFS="${delimiters[1]}" read -r -a level_3_args_d2_arr <<< "$level_3_args_d2${delimiters[1]}" if [ "$level_4_d1_count" -gt 0 ] && [ -n "${level_4_directive_d1_arr[level_2_index]}" ] then level_4_directive_d2=${level_4_directive_d1_arr[level_2_index]} level_4_args_d2=${level_4_args_d1_arr[level_2_index]} IFS="${delimiters[2]}" read -r -a level_4_directive_d2_arr <<< "$level_4_directive_d2${delimiters[2]}" IFS="${delimiters[2]}" read -r -a level_4_args_d2_arr <<< "$level_4_args_d2${delimiters[2]}" fi nginx_domain_servers_count=$((nginx_domain_servers_count+1)) nginx_domain_servers_indices+=("$level_2_index") nginx_domain_server_listen_list="" nginx_domain_server_name_list="" nginx_domain_server_flv_status="${red}未配置${normal}" nginx_domain_server_nodejs_status="${red}未配置${normal}" skip_find_nodejs=0 server_root="" for((level_3_index=0;level_3_index<${#level_3_directive_d2_arr[@]};level_3_index++)); do level_3_directive=${level_3_directive_d2_arr[level_3_index]} level_3_args=${level_3_args_d2_arr[level_3_index]} if [ "$level_3_directive" == "listen" ] then [ -n "$nginx_domain_server_listen_list" ] && nginx_domain_server_listen_list="$nginx_domain_server_listen_list, " nginx_domain_server_listen_list="$nginx_domain_server_listen_list${level_3_args//${delimiters[0]}/ }" elif [ "$level_3_directive" == "server_name" ] then [ -n "$nginx_domain_server_name_list" ] && nginx_domain_server_name_list="$nginx_domain_server_name_list, " nginx_domain_server_name_list="$nginx_domain_server_name_list${level_3_args//${delimiters[0]}/, }" elif [ "$level_3_directive" == "location" ] then if [ "${level_3_args}" == "/flv" ] then nginx_domain_server_flv_status="${green}已配置${normal}" elif [ "$level_4_d1_count" -gt 0 ] && [ -n "${level_4_directive_d1_arr[level_2_index]}" ] && [ -n "${level_4_directive_d2_arr[level_3_index]}" ] then level_4_directive_d3=${level_4_directive_d2_arr[level_3_index]} level_4_args_d3=${level_4_args_d2_arr[level_3_index]} IFS="${delimiters[1]}" read -r -a level_4_directive_d3_arr <<< "$level_4_directive_d3${delimiters[1]}" IFS="${delimiters[1]}" read -r -a level_4_args_d3_arr <<< "$level_4_args_d3${delimiters[1]}" if [ "${level_3_args}" == "=${delimiters[0]}/" ] && [ "$skip_find_nodejs" -eq 0 ] then for((level_4_index=0;level_4_index<${#level_4_directive_d3_arr[@]};level_4_index++)); do if [ "${level_4_directive_d3_arr[level_4_index]}" == "proxy_pass" ] then if [[ ${level_4_args_d3_arr[level_4_index]} =~ ^http://nodejs ]] then nginx_domain_server_nodejs_status="${green}已配置${normal}" skip_find_nodejs=1 fi break fi done elif [ "${level_3_args}" == "/" ] then for((level_4_index=0;level_4_index<${#level_4_directive_d3_arr[@]};level_4_index++)); do if [ "${level_4_directive_d3_arr[level_4_index]}" == "root" ] then if [ "${level_4_args_d3_arr[level_4_index]:0:1}" == "/" ] then server_root=${level_4_args_d3_arr[level_4_index]} else server_root="$nginx_prefix/${level_4_args_d3_arr[level_4_index]}" fi break fi done fi fi elif [ "$level_3_directive" == "root" ] then if [ "${level_3_args_d2_arr[level_3_index]:0:1}" == "/" ] then server_root=${level_3_args_d2_arr[level_3_index]} else server_root="$nginx_prefix/${level_3_args_d2_arr[level_3_index]}" fi fi done nginx_domain_servers_name+=("${nginx_domain_server_name_list//, /,}") nginx_domain_servers_root+=("$server_root") nginx_domain_servers_list="$nginx_domain_servers_list $nginx_domain_servers_count.${indent_6}域名: ${green}${nginx_domain_server_name_list:-未设置}${normal}\n${indent_6}端口: ${green}${nginx_domain_server_listen_list:-未设置}${normal}\n${indent_6}flv: $nginx_domain_server_flv_status\n${indent_6}nodejs: $nginx_domain_server_nodejs_status\n\n" fi done if [ "$nginx_domain_servers_count" -eq 0 ] then Println "$error 请先添加 ${nginx_domains[nginx_domains_index]} 配置\n" exit 1 fi Println "域名 ${green}${nginx_domains[nginx_domains_index]}${normal} 配置:\n\n$nginx_domain_servers_list" } NginxSelectDomainServer() { echo "`gettext \"输入序号\"`" while read -p "$i18n_default_cancel" nginx_domain_servers_num do case "$nginx_domain_servers_num" in "") Println "$i18n_canceled...\n" exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$nginx_domain_servers_num" -gt 0 ] && [ "$nginx_domain_servers_num" -le "$nginx_domain_servers_count" ] then nginx_domain_servers_index=$((nginx_domain_servers_num-1)) level_2_add_indices=( "${nginx_domain_servers_indices[nginx_domain_servers_index]}" ) server_root=${nginx_domain_servers_root[nginx_domain_servers_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done } NginxConfigDomain() { NginxListDomain NginxSelectDomainServer echo domain_server_options=( '修改指令' '更新证书' '添加 flv 设置' '添加 nodejs 设置' ) inquirer list_input_index "选择操作" domain_server_options domain_server_options_index if [ "$domain_server_options_index" -eq 0 ] then NginxConfigDirective level_2 elif [ "$domain_server_options_index" -eq 1 ] then AcmeCheck NginxDomainServerUpdateCrt elif [ "$domain_server_options_index" -eq 2 ] then updated=0 NginxAddFlv if [ "$updated" -eq 1 ] then NginxBuildConf parse_out fi Println "$info flv 配置添加成功\n" else server_name=${nginx_domain_servers_name[nginx_domain_servers_index]} if [[ $server_name =~ , ]] then IFS="," read -r -a domains <<< "$server_name" echo inquirer list_input "选择域名: " domains server_name fi updated=0 NginxAddNodejs if [ "$updated" -eq 1 ] then NginxBuildConf parse_out fi Println "$info nodejs 配置添加成功\n" fi } NginxGetStream() { nginx_stream_server_name_list="" nginx_stream_protocol_list="" nginx_stream_alpn_protocols_list="" nginx_stream_upstream_list="" nginx_stream_server_name_count=0 nginx_stream_protocol_count=0 nginx_stream_alpn_protocols_count=0 nginx_stream_upstream_count=0 nginx_stream_server_name=() nginx_stream_protocol=() nginx_stream_alpn_protocols=() nginx_stream_upstream=() nginx_stream_upstream_indices=() for((level_1_index=0;level_1_index ${level_3_args_d2_arr[server_name_i]}") done elif [ "${level_2_args_d1_arr[level_2_index]}" == "\$ssl_preread_protocol${delimiters[0]}\$ssl_proxy" ] then for((protocol_i=0;protocol_i<${#level_3_directive_d2_arr[@]};protocol_i++)); do nginx_stream_protocol_list="$nginx_stream_protocol_list $((protocol_i+1)).${indent_6}${green}${level_3_directive_d2_arr[protocol_i]:-''}${normal} => ${green}${level_3_args_d2_arr[protocol_i]}${normal}\n" nginx_stream_protocol+=("${level_3_directive_d2_arr[protocol_i]:-''} => ${level_3_args_d2_arr[protocol_i]}") done elif [ "${level_2_args_d1_arr[level_2_index]}" == "\$ssl_preread_alpn_protocols${delimiters[0]}\$proxy_pass" ] then for((alpn_protocols_i=0;alpn_protocols_i<${#level_3_directive_d2_arr[@]};alpn_protocols_i++)); do nginx_stream_alpn_protocols_list="$nginx_stream_alpn_protocols_list $((alpn_protocols_i+1)).${indent_6}${green}${level_3_directive_d2_arr[alpn_protocols_i]}${normal} => ${green}${level_3_args_d2_arr[alpn_protocols_i]}${normal}\n" nginx_stream_alpn_protocols+=("${level_3_directive_d2_arr[alpn_protocols_i]} => ${level_3_args_d2_arr[alpn_protocols_i]}") done fi elif [ "${level_2_directive_d1_arr[level_2_index]}" == "upstream" ] then nginx_stream_upstream_indices+=("$level_2_index") nginx_stream_upstream_count=$((nginx_stream_upstream_count+1)) nginx_stream_upstream_list="$nginx_stream_upstream_list $nginx_stream_upstream_count.${indent_6}${green}${level_2_args_d1_arr[level_2_index]}${normal} => ${green}${level_3_args_d2_arr[0]}${normal}\n" nginx_stream_upstream+=("${level_2_args_d1_arr[level_2_index]} => ${level_3_args_d2_arr[0]}") fi fi done break fi done if [ -n "${nginx_stream_server_name:-}" ] then nginx_stream_server_name_count=${#nginx_stream_server_name[@]} fi if [ -n "${nginx_stream_protocol:-}" ] then nginx_stream_protocol_count=${#nginx_stream_protocol[@]} fi if [ -n "${nginx_stream_alpn_protocols:-}" ] then nginx_stream_alpn_protocols_count=${#nginx_stream_alpn_protocols[@]} fi } NginxListStream() { NginxGetStream Println "分流配置:\n\n SNI 域名分流:\n\n${nginx_stream_server_name_list:- 无}\n\n SSL 协议分流(\$ssl_proxy):\n\n${nginx_stream_protocol_list:- 无}\n\n ALPN 协议分流:\n\n${nginx_stream_alpn_protocols_list:- 无}\n\n 分流后端(\$upstream):\n\n${nginx_stream_upstream_list:- 无}\n\n" } NginxListLocalhost() { NginxCheckLocalhost nginx_localhost_list="" nginx_localhost_server_count=0 nginx_localhost_server_indices=() nginx_localhost_server_root=() for((level_1_index=0;level_1_index /dev/null 2>&1 then for f in "$nginx_prefix/conf/sites_available/"* do domain=${f##*/} domain=${domain%.conf} if [[ $domain =~ ^([a-zA-Z0-9](([a-zA-Z0-9-]){0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]] then cors_domains+=("$domain") fi done fi directive_map='{"directive":"map","args":["$http_origin","$cors_host"],"block":[]}' directives=( map ) directives_val=() check_directives=() check_args=( '["$http_origin","$cors_host"]' ) NginxAddDirective 2 server_ip=$(GetServerIp) directive_default='{"directive":"default","args":["*"]}' read -r directive_server_ip_http < <( $JQ_FILE -c -n --arg directive "~http://$server_ip" --argjson args "[\"~http://$server_ip\"]" \ '{ "directive":$directive, "args":$args }' ) read -r directive_server_ip_https < <( $JQ_FILE -c -n --arg directive "~https://$server_ip" --argjson args "[\"~https://$server_ip\"]" \ '{ "directive":$directive, "args":$args }' ) directives=( default "~http://$server_ip" "~https://$server_ip" ) directives_val=( default server_ip_http server_ip_https ) if [ -n "${cors_domains:-}" ] then for((cors_i=0;cors_i<${#cors_domains[@]};cors_i++)); do read -r directive_cors_domain_${cors_i}_http < <( $JQ_FILE -c -n --arg directive "~http://${cors_domains[cors_i]}" --argjson args "[\"~http://${cors_domains[cors_i]}\"]" \ '{ "directive":$directive, "args":$args }' ) read -r directive_cors_domain_${cors_i}_https < <( $JQ_FILE -c -n --arg directive "~https://${cors_domains[cors_i]}" --argjson args "[\"~https://${cors_domains[cors_i]}\"]" \ '{ "directive":$directive, "args":$args }' ) directives+=( "~http://${cors_domains[cors_i]}" "~https://${cors_domains[cors_i]}" ) directives_val+=( cors_domain_${cors_i}_http cors_domain_${cors_i}_https ) done fi check_directives=() check_args=() NginxAddDirective 3 if ! grep -q "$nginx_name:" < "/etc/passwd" then if grep -q '\--group ' < <(adduser --help) then adduser "$nginx_name" --system --group --no-create-home > /dev/null else adduser "$nginx_name" --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin "$nginx_name" fi sed -i "s/#user nobody;/user $nginx_name $nginx_name;/" "$nginx_prefix/conf/nginx.conf" } NginxAddUpstreamNodejs() { directive_upstream='{"directive":"upstream","args":["nodejs"],"block":[]}' directives=( upstream ) directives_val=() check_directives=() check_args=( '["nodejs"]' ) NginxAddDirective 2 directive_server='{"directive":"server","args":["127.0.0.1:'"$nodejs_port"'"]}' directives=( server ) directives_val=() check_directives=() check_args=() NginxAddDirective 3 } NginxAddFlv() { directive_location='{"directive":"location","args":["/flv"],"block":[]}' directives=( location ) directives_val=() check_directives=() check_args=( '["/flv"]' ) NginxAddDirective 3 directive_flv_live='{"directive":"flv_live","args":["on"]}' directive_chunked_transfer_encoding='{"directive":"chunked_transfer_encoding","args":["on"]}' directives=( flv_live chunked_transfer_encoding ) directives_val=() check_directives=() check_args=() NginxAddDirective 4 } NginxAddSameSiteNone() { directive_map='{"directive":"map","args":["$http_user_agent","$samesite_none"],"block":[]}' directives=( map ) directives_val=() check_directives=() check_args=( '["$http_user_agent","$samesite_none"]' ) NginxAddDirective 2 directive_default='{"directive":"default","args":["; Secure"]}' directive_chrome1='{"directive":"~Chrom[^ \\/]+\\/[1][\\d][\\d][\\.\\d]*","args":["; Secure; SameSite=None"]}' directive_chrome2='{"directive":"~Chrom[^ \\/]+\\/[89][\\d][\\.\\d]*","args":["; Secure; SameSite=None"]}' directives=( default '~Chrom[^ \\/]+\\/[89][\\d][\\.\\d]*' ) directives_val=( default chrome1 chrome2 ) check_directives=() check_args=( '["; Secure"]' ) NginxAddDirective 3 } NginxBuildConf() { if TMP_FILE=$(mktemp -q) then chmod +r "$TMP_FILE" else exit $? fi trap ' rm -f "$TMP_FILE" ' EXIT if [ "$parse_domain" -eq 1 ] then parse_out_domain=${!1} jq_path='["config",0,"parsed",0,"block"]' JQs get parse_out_domain domain_conf jq_path='["config",0,"parsed"]' JQs replace parse_out_domain "$domain_conf" echo "$parse_out_domain" > "$TMP_FILE" else echo "${!1}" > "$TMP_FILE" fi crossplane build -f --no-headers "$TMP_FILE" rm -f "$TMP_FILE" trap - EXIT } NginxCheckLocalhost() { if [ ! -d "$nginx_prefix" ] then Println "$error $nginx_name 未安装 !\n" exit 1 fi mkdir -p "$nginx_prefix/conf/sites_crt/" mkdir -p "$nginx_prefix/conf/sites_available/" mkdir -p "$nginx_prefix/conf/sites_enabled/" NginxParseConfig NginxGetConfig updated=0 NginxAddUser NginxAddHttp NginxAddSitesEnabled NginxAddSsl server_offset=0 if [ "$level_2_d1_count" -gt 0 ] then for((level_1_index=0;level_1_index&1 | grep -qi nginx-http-flv-module then NginxAddRtmp fi if [ "$updated" -eq 1 ] then NginxBuildConf parse_out if ls -A "$nginx_prefix/conf/sites_available/"* > /dev/null 2>&1 then for f in "$nginx_prefix/conf/sites_available/"* do sed -i 's/$corsHost/$cors_host/g' "$f" done fi sed -i 's/$corsHost/$cors_host/g' "$nginx_prefix/conf/nginx.conf" fi } NginxConfigDirective() { case $1 in level_1) while true do level_1_options=() for((level_1_index=0;level_1_index"* ]] then break fi done < <(curl -s -Lm 10 -H "User-Agent: $USER_AGENT_BROWSER" https://ipinfo.io/AS45102) deny_aliyun="$deny_aliyun allow all;" deny_aliyun="$deny_aliyun } " fi } AcmeCheck() { [ -n "${ca_server:-}" ] && return 0 if [ ! -f "$HOME/.acme.sh/acme.sh" ] then DepInstall socat { curl -s -m 20 https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh || curl -s -m 20 "$FFMPEG_MIRROR_LINK/acme.sh"; } \ | sed "s+https://raw.githubusercontent.com/acmesh-official+$FFMPEG_MIRROR_LINK/acmesh-content+g" \ | sed "s+| sh+| sed 's~PROJECT=\"https://github.com/acmesh-official~PROJECT=\"$FFMPEG_MIRROR_LINK/acmesh-project~' | sed 's~https://api.github.com~$FFMPEG_MIRROR_LINK/acmesh-api~g' | sh+g" > ~/acme.sh cd ~ bash acme.sh --install else echo inquirer list_input_index "更新 acme.sh" ny_options ny_options_index if [ "$ny_options_index" -eq 1 ] then ~/.acme.sh/acme.sh --upgrade fi fi Println "$tip zerossl 不支持 tls-alpn-01" ca_options=( letsencrypt zerossl ) inquirer list_input "选择 CA" ca_options ca_server if [ "$ca_server" == "zerossl" ] then if [ -e ~/.acme.sh/ca/acme.zerossl.com/ca.conf ] then . ~/.acme.sh/ca/acme.zerossl.com/ca.conf elif [ -e ~/.acme.sh/ca/acme.zerossl.com/v2/DV90/ca.conf ] then . ~/.acme.sh/ca/acme.zerossl.com/v2/DV90/ca.conf fi if [ -n "${CA_EAB_KEY_ID:-}" ] && [ -n "${CA_EAB_HMAC_KEY:-}" ] then Println "$tip 请确保已有账号的 EAB 认证信息未过期, 否则请重新设置" inquirer list_input "是否重新设置 zerossl 账号" ny_options yn_option if [ "$yn_option" == "$i18n_no" ] then return 0 fi fi echo zerossl_options=( '注册新账号' '输入已有账号的 EAB 认证信息' ) inquirer list_input_index "未发现 zerossl 账号" zerossl_options zerossl_options_index if [ "$zerossl_options_index" -eq 0 ] then echo ExitOnText "输入邮箱: " zerossl_email if ! ~/.acme.sh/acme.sh --register-account -m "$zerossl_email" --server zerossl then Println "$error 注册账号失败, 请稍后再试或前往官网注册 https://app.zerossl.com/signup?fpr=iptv-sh \n" exit 1 fi else Println "$tip 可以在 https://app.zerossl.com/developer?fpr=iptv-sh 页面获取" ExitOnText "输入 EAB KID: " zerossl_eab_kid echo ExitOnText "输入 EAB HMAC Key: " zerossl_eab_hmac_key if ! ~/.acme.sh/acme.sh --register-account --server zerossl --eab-kid "$zerossl_eab_kid" --eab-hmac-key "$zerossl_eab_hmac_key" then Println "$error 注册账号失败, 请确保输入正确\n" exit 1 fi fi Println "$info 账号注册成功\n" fi } NginxDomainUpdateCrt() { local domain=$1 quiet=${2:-0} [ "$quiet" -eq 0 ] && Println "$info 更新 $domain 证书..." if [ "$ca_server" == "letsencrypt" ] && [ -f /etc/systemd/system/mmproxy-acme.service ] && [[ $(systemctl is-active mmproxy-acme) == "active" ]] then if [ -z "${tls_port:-}" ] then tls_port=$(grep ^ExecStart= < /etc/systemd/system/mmproxy-acme.service) if [[ $tls_port =~ -4\ 127.0.0.1:([^ ]+) ]] then tls_port=${BASH_REMATCH[1]} else tls_port=${tls_port#*-4 } tls_port=${tls_port#*:} tls_port=${tls_port%% *} fi fi ~/.acme.sh/acme.sh --force --issue --alpn --tlsport "$tls_port" -d "$domain" --standalone -k ec-256 --server "$ca_server" > /dev/null ~/.acme.sh/acme.sh --force --installcert -d "$domain" --fullchainpath "$nginx_prefix/conf/sites_crt/$domain.crt" --keypath "$nginx_prefix/conf/sites_crt/$domain.key" --ecc > /dev/null else stopped=0 if [[ $(systemctl is-active $nginx_name) == "active" ]] then if [[ $(echo $SSH_CONNECTION | cut -d' ' -f3) == "127.0.0.1" ]] then Println "$error 请使用非 $nginx_name 监听端口连接 ssh 后重试\n" exit 1 fi systemctl stop $nginx_name stopped=1 fi sleep 1 ~/.acme.sh/acme.sh --force --issue -d "$domain" --standalone -k ec-256 --server "$ca_server" > /dev/null ~/.acme.sh/acme.sh --force --installcert -d "$domain" --fullchainpath "$nginx_prefix/conf/sites_crt/$domain.crt" --keypath "$nginx_prefix/conf/sites_crt/$domain.key" --ecc > /dev/null [ "$stopped" -eq 1 ] && systemctl start $nginx_name fi if [ -e "/usr/local/share/v2ray/$domain.crt" ] then cp -f "$nginx_prefix/conf/sites_crt/$domain.crt" "/usr/local/share/v2ray/$domain.crt" cp -f "$nginx_prefix/conf/sites_crt/$domain.key" "/usr/local/share/v2ray/$domain.key" fi if [ -e "/usr/local/share/xray/$domain.crt" ] then cp -f "$nginx_prefix/conf/sites_crt/$domain.crt" "/usr/local/share/xray/$domain.crt" cp -f "$nginx_prefix/conf/sites_crt/$domain.key" "/usr/local/share/xray/$domain.key" fi [ "$quiet" -eq 0 ] && Println "$info $domain 证书更新成功\n" return 0 } NginxDomainServerUpdateCrt() { nginx_domain_server_name=${nginx_domain_servers_name[nginx_domain_servers_index]} if [[ $nginx_domain_server_name =~ , ]] then IFS="," read -r -a domains <<< "$nginx_domain_server_name" echo inquirer checkbox_input "选择域名: " domains domains_selected for domain in "${domains_selected[@]}" do NginxDomainUpdateCrt "$domain" done return 0 fi NginxDomainUpdateCrt "$nginx_domain_server_name" } NginxToggleDomain() { NginxListDomains [ "$nginx_domains_count" -eq 0 ] && Println "$error 没有域名\n" && exit 1 echo "`gettext \"输入序号\"`" while read -p "$i18n_default_cancel" nginx_domains_index do case "$nginx_domains_index" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$nginx_domains_index" -gt 0 ] && [ "$nginx_domains_index" -le "$nginx_domains_count" ] then nginx_domains_index=$((nginx_domains_index-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done server_domain=${nginx_domains[nginx_domains_index]} if [ "${nginx_domains_status[nginx_domains_index]}" -eq 1 ] then NginxDisableDomain Println "$info $server_domain 关闭成功\n" else NginxEnableDomain Println "$info $server_domain 开启成功\n" fi } NginxDeleteDomain() { NginxListDomains [ "$nginx_domains_count" -eq 0 ] && Println "$error 没有域名\n" && exit 1 echo "`gettext \"输入序号\"`" while read -p "$i18n_default_cancel" nginx_domains_index do case "$nginx_domains_index" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$nginx_domains_index" -gt 0 ] && [ "$nginx_domains_index" -le "$nginx_domains_count" ] then nginx_domains_index=$((nginx_domains_index-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done server_domain=${nginx_domains[nginx_domains_index]} if [ "${nginx_domains_status[nginx_domains_index]}" -eq 1 ] then NginxDisableDomain fi rm -f "$nginx_prefix/conf/sites_available/$server_domain.conf" Println "$info $server_domain 删除成功\n" } NginxLogRotate() { if [ ! -d "$IPTV_ROOT" ] then Println "$error 请先安装脚本 !\n" && exit 1 fi if ! ls -A $nginx_prefix/logs/*.log > /dev/null 2>&1 then Println "$error 没有日志 !\n" && exit 1 fi if [ -d "$nginx_prefix" ] then chown $nginx_name:root $nginx_prefix/logs/*.log chmod 660 $nginx_prefix/logs/*.log fi if crontab -l | grep -q "$LOGROTATE_CONFIG" 2> /dev/null then if grep -q "$nginx_prefix" < "$LOGROTATE_CONFIG" then Println "$error 日志切割定时任务已存在 !\n" else logrotate=' '"$nginx_prefix"'/logs/*.log { daily missingok rotate 14 compress delaycompress notifempty create 660 '"$nginx_name"' root sharedscripts postrotate [ ! -f '"$nginx_prefix"'/logs/nginx.pid ] || /bin/kill -USR1 `cat '"$nginx_prefix"'/logs/nginx.pid` endscript } ' printf '%s\n' "$logrotate" >> "$LOGROTATE_CONFIG" Println "$error 日志切割定时任务设置成功 !\n" fi else LOGROTATE_FILE=$(command -v logrotate) || LOGROTATE_FILE="" if [ ! -x "$LOGROTATE_FILE" ] then Println "$error 请先安装 logrotate !\n" && exit 1 fi logrotate="" if [ -d "$nginx_prefix" ] then logrotate=' '"$nginx_prefix"'/logs/*.log { daily missingok rotate 14 compress delaycompress notifempty create 660 '"$nginx_name"' root sharedscripts postrotate [ ! -f '"$nginx_prefix"'/logs/nginx.pid ] || /bin/kill -USR1 `cat '"$nginx_prefix"'/logs/nginx.pid` endscript } ' fi logrotate="$logrotate $IPTV_ROOT/*.log { monthly missingok rotate 3 compress nodelaycompress notifempty sharedscripts } " printf '%s' "$logrotate" > "$LOGROTATE_CONFIG" crontab -l > "$IPTV_ROOT/cron_tmp" 2> /dev/null || true printf '%s\n' "0 0 * * * $LOGROTATE_FILE $LOGROTATE_CONFIG" >> "$IPTV_ROOT/cron_tmp" crontab "$IPTV_ROOT/cron_tmp" > /dev/null rm -f "$IPTV_ROOT/cron_tmp" Println "$info 日志切割定时任务开启成功 !\n" fi } NginxUpdateCFIBMip() { if [ ! -f $nginx_prefix/conf/nginx.conf ] then Println "$error 请先安装 $nginx_name\n" exit 1 fi if ! grep -q "include cloudflare_ip.conf;" < $nginx_prefix/conf/nginx.conf then sed -i '/http {/a\ include cloudflare_ip.conf;' $nginx_prefix/conf/nginx.conf else Println "$error $nginx_name 配置已经存在\n" fi Println "$info 更新 ip ..." cat > "$HOME"/update_cf_ibm_ip.sh < $nginx_prefix/conf/cloudflare_ip.conf; ibm_ips=( 50.22.0.0/16 50.23.0.0/16 66.228.118.0/23 67.228.66.0/24 75.126.0.0/16 108.168.157.0/24 173.192.0.0/16 174.35.17.0/24 184.172.0.0/16 192.255.0.0/16 198.23.0.0/16 208.43.15.0/24 169.45.0.0/16 169.46.0.0/16 169.47.0.0/16 169.48.0.0/16 169.61.0.0/16 169.62.0.0/16 ) for i in "\${ibm_ips[@]}"; do echo "set_real_ip_from \$i;" >> $nginx_prefix/conf/cloudflare_ip.conf; done for i in \$(curl https://www.cloudflare.com/ips-v4); do echo "set_real_ip_from \$i;" >> $nginx_prefix/conf/cloudflare_ip.conf; done for i in \$(curl https://www.cloudflare.com/ips-v6); do echo "set_real_ip_from \$i;" >> $nginx_prefix/conf/cloudflare_ip.conf; done echo >> $nginx_prefix/conf/cloudflare_ip.conf; echo '# use any of the following two' >> $nginx_prefix/conf/cloudflare_ip.conf; echo 'real_ip_header CF-Connecting-IP;' >> $nginx_prefix/conf/cloudflare_ip.conf; echo '#real_ip_header X-Forwarded-For;' >> $nginx_prefix/conf/cloudflare_ip.conf; EOF bash ~/update_cf_ibm_ip.sh Println "$info IP 更新成功\n" } NginxEnableDomain() { ln -sf "$nginx_prefix/conf/sites_available/$server_domain.conf" "$nginx_prefix/conf/sites_enabled/$server_domain.conf" NginxRestart } NginxDisableDomain() { rm -f "$nginx_prefix/conf/sites_enabled/$server_domain.conf" NginxRestart } NginxAppendHttpConf() { cat >> "$nginx_prefix/conf/sites_available/$server_domain.conf" <> "$nginx_prefix/conf/sites_available/$server_domain.conf" <> "$nginx_prefix/conf/sites_available/$server_domain.conf" <> "$nginx_prefix/conf/sites_available/$server_domain.conf" <> "$nginx_prefix/conf/sites_available/$server_domain.conf" <> "$nginx_prefix/conf/sites_available/$server_domain.conf" <> "$nginx_prefix/conf/sites_available/$server_domain.conf" < https ${green}3.${normal} http + https \n" read -p "`gettext \"输入序号\"` [1-3]: " server_num fi case $server_num in 1) NginxConfigServerHttpPort echo inquirer list_input "是否设置跳转到其它网址" ny_options http_redirect_yn if [ "$http_redirect_yn" == "$i18n_yes" ] then NginxAppendHttpRedirectConf else NginxConfigServerRoot NginxConfigServerLiveRoot NginxAppendHttpConf fi updated=0 NginxAddCorsHost [ "$updated" -eq 1 ] && NginxBuildConf parse_out NginxEnableDomain Println "$info $server_domain 配置成功\n" ;; 2) NginxDomainInstallCert "$server_domain" echo inquirer list_input "是否设置 http 跳转 https" yn_options http_to_https_yn if [ "$http_to_https_yn" == "$i18n_yes" ] then Println "$info 设置 $server_domain http 配置" NginxConfigServerHttpPort NginxAppendHttpRedirectToHttpsConf fi NginxConfigServerHttpsPort echo inquirer list_input "是否设置 https 跳转到其它网址" ny_options https_redirect_yn if [ "$https_redirect_yn" == "$i18n_yes" ] then NginxAppendHttpsRedirectConf else NginxConfigServerRoot NginxConfigServerLiveRoot NginxAppendHttpsConf fi updated=0 NginxAddCorsHost [ "$updated" -eq 1 ] && NginxBuildConf parse_out NginxEnableDomain Println "$info $server_domain 配置成功\n" ;; 3) NginxDomainInstallCert "$server_domain" echo inquirer list_input "http 和 https 是否使用相同的目录" yn_options http_https_same_dir_yn if [ "$http_https_same_dir_yn" == "$i18n_yes" ] then NginxConfigServerHttpPort NginxConfigServerHttpsPort echo inquirer list_input "是否设置跳转到其它网址" ny_options http_https_redirect_yn if [ "$http_https_redirect_yn" == "$i18n_yes" ] then NginxAppendHttpHttpsRedirectConf else NginxConfigServerRoot NginxConfigServerLiveRoot NginxAppendHttpHttpsConf fi else NginxConfigServerHttpPort echo inquirer list_input "是否设置 http 跳转到其它网址" ny_options http_redirect_yn if [ "$http_redirect_yn" == "$i18n_yes" ] then NginxAppendHttpRedirectConf NginxConfigServerHttpsPort echo inquirer list_input "是否设置 https 跳转到其它网址" yn_options https_redirect_yn if [ "$https_redirect_yn" == "$i18n_yes" ] then NginxAppendHttpsRedirectConf else NginxConfigServerRoot NginxConfigServerLiveRoot NginxAppendHttpsConf fi else NginxConfigServerRoot NginxConfigServerLiveRoot server_http_root="$server_root" server_http_live_root="$server_live_root" server_http_deny="$deny_aliyun" NginxConfigServerHttpsPort echo inquirer list_input "是否设置 https 跳转到其它网址" yn_options https_redirect_yn if [ "$https_redirect_yn" == "$i18n_yes" ] then NginxAppendHttpConf NginxAppendHttpsRedirectConf else server_root="" server_live_root="" deny_aliyun="" NginxConfigServerRoot NginxConfigServerLiveRoot server_https_root="$server_root" server_https_live_root="$server_live_root" server_https_deny="$deny_aliyun" if [ "$server_http_root" == "$server_https_root" ] && [ "$server_http_live_root" == "$server_https_live_root" ] && [ "$server_http_deny" == "$server_https_deny" ] then NginxAppendHttpHttpsConf else NginxAppendHttpConf NginxAppendHttpsConf fi fi fi fi updated=0 NginxAddCorsHost [ "$updated" -eq 1 ] && NginxBuildConf parse_out NginxEnableDomain Println "$info $server_domain 配置成功\n" ;; *) Println "$i18n_canceled...\n" && exit 1 ;; esac done else Println "$i18n_canceled...\n" && exit 1 fi } GitInstall() { if [[ -x $(command -v git) ]] then return 0 fi Println "$info 安装 git" case $dist in rpm) yum -y install git ;; deb) apt-get -y install git ;; ubu) add-apt-repository ppa:git-core/ppa -y apt-get -y update apt-get -y install git ;; esac Println "$info git 安装成功\n" } ResourceLimit() { if [ ! -e /proc/sys/fs/file-max ] then echo 65536 > /proc/sys/fs/file-max echo "fs.file-max=65536" >> /etc/sysctl.conf fi file_max=$(< /proc/sys/fs/file-max) if [ "$file_max" -lt 65000 ] then file_max=$((file_max*95/100)) else file_max=64000 fi limits=( "$USER soft fsize unlimited" "$USER hard fsize unlimited" "$USER soft cpu unlimited" "$USER hard cpu unlimited" "$USER soft as unlimited" "$USER hard as unlimited" "$USER soft memlock unlimited" "$USER hard memlock unlimited" "$USER soft nofile $file_max" "$USER hard nofile $file_max" "$USER soft nproc 64000" "$USER hard nproc 64000" "* soft nofile $file_max" "* hard nofile $file_max" ) limits_append="" for limit in "${limits[@]}" do if ! grep -q "${limit% *}" < "/etc/security/limits.conf" then limits_append="$limits_append$limit\n" fi done if [ -n "$limits_append" ] then # systemd ignores limits set in the /etc/security/limits.conf echo -e "$limits_append" >> "/etc/security/limits.conf" fi ulimit -f unlimited ulimit -t unlimited ulimit -v unlimited ulimit -l unlimited ulimit -n $file_max ulimit -m unlimited ulimit -u 64000 if [ "$dist" == "rpm" ] then if [ ! -e ~/.bash_profile ] || ! grep -q ulimit < ~/.bash_profile then cat >> ~/.bash_profile <> ~/.profile < /dev/null 2>&1 #then # echo -e "mongodb soft nproc 64000\nmongodb hard nproc 64000" > /etc/security/limits.d/99-mongodb-nproc.conf #fi } NodejsInstall() { DepsCheck if [ "$dist" == "rpm" ] && { [[ -x $(command -v node) ]] || [[ -x $(command -v npm) ]]; } then yum remove -y nodejs npm fi Progress & progress_pid=$! trap ' kill $progress_pid wait $progress_pid 2> /dev/null ' EXIT if [ "$dist" == "rpm" ] then yum -y install gcc-c++ make >/dev/null 2>&1 # yum groupinstall 'Development Tools' if bash <(curl -sL https://rpm.nodesource.com/setup_14.x) > /dev/null then yum -y install nodejs >/dev/null 2>&1 fi else if bash <(curl -sL https://deb.nodesource.com/setup_14.x) > /dev/null then apt-get install -y nodejs >/dev/null 2>&1 fi fi kill $progress_pid wait $progress_pid 2> /dev/null || true trap - EXIT echo -n "...100%" && Println "$info nodejs 安装完成" } NodejsConfig() { echo nodejs_options=( '域名' '本地' ) inquirer list_input "选择使用 nodejs 的对象" nodejs_options nodejs_option if [ "$nodejs_option" == "域名" ] then NginxListDomain NginxSelectDomainServer server_name=${nginx_domain_servers_name[nginx_domain_servers_index]} if [[ $server_name =~ , ]] then IFS="," read -r -a domains <<< "$server_name" echo inquirer list_input "选择域名: " domains server_name fi else NginxListLocalhost NginxSelectLocalhostServer fi NginxConfigServerLiveRoot updated=0 NginxAddFlv NginxAddNodejs if [ "$updated" -eq 1 ] then NginxBuildConf parse_out Println "$info nodejs 配置添加成功" fi if [ "$nodejs_option" == "域名" ] then NginxCheckLocalhost NginxAddHttp fi updated=0 NginxAddCorsHost NginxAddSameSiteNone nodejs_port=$(GetFreePort) NginxAddUpstreamNodejs if [ "$updated" -eq 1 ] then NginxBuildConf parse_out fi username=$(RandStr) password=$(RandStr) if [[ $(ps --no-headers -o comm 1) == "systemd" ]] then mongo admin --eval "db.getSiblingDB('admin').createUser({user: '${username}', pwd: '${password}', roles: ['root']})" systemctl restart mongod else mongo admin --eval "db.getSiblingDB('admin').createUser({user: '${username}', pwd: '${password}', roles: ['root']})" service mongod restart fi mkdir -p "$NODE_ROOT" echo " const express = require('express'); const session = require('express-session'); const MongoStore = require('connect-mongo'); const mongoose = require('mongoose'); const path = require('path'); const app = express(); const port = $nodejs_port; const clientP = mongoose.connect( 'mongodb://$username:$password@127.0.0.1/admin', { useNewUrlParser: true, useUnifiedTopology: true } ).then(m => m.connection.getClient()) app.set('trust proxy', 1); app.use(session({ name: '$(RandStr)', secret: '$(RandStr)', resave: false, saveUninitialized: false, store: MongoStore.create({ clientPromise: clientP, dbName: 'encrypt', stringify: false, collectionName: 'sessions', autoRemove: 'interval', autoRemoveInterval: 120 }), cookie: { domain: 'localhost', maxAge: 60 * 60 * 2000, httpOnly: true } })); app.get('/keys', (req, res) => { const { key, channel } = req.query; if (!req.session.websiteUser || !key || !channel){ res.status(400).end(); } esle { res.sendFile(channel + '/' + key + '.key', {root: path.join(__dirname, '../${LIVE_ROOT##*/}')}); } }); app.get('/', (req, res) => { req.session.websiteUser = true; res.sendFile('$server_root/index.html'); }); app.get('/remote', (req, res) => { req.session.websiteUser = true; res.sendFile('$server_root/channels.json'); }); app.get('/channels', (req, res) => { if (!req.session.websiteUser){ res.status(403).end(); } else { res.sendFile('$server_root/channels.json'); } }); app.listen(port, () => console.log(\`App listening on port \${port}!\`)) " > "$NODE_ROOT/index.js" $JQ_FILE -n \ '{ "name": "node", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node index.js" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "express-session": "^1.17.0", "mongodb": "^4.1.2", "connect-mongo": "^4.6.0", "mongoose": "^6.0.8", "path": "^0.12.7" } }' > "$NODE_ROOT/package.json" if [[ ! -x $(command -v git) ]] then Spinner "安装 git" GitInstall fi cd "$NODE_ROOT" npm install npm install -g pm2 pm2 start "$NODE_ROOT/index.js" pm2 startup Println "$info nodejs 配置完成" } NodejsMenu() { [ ! -d "$IPTV_ROOT" ] && Println "$error 请先输入 tv 安装 !\n" && exit 1 echo nodejs_options=( '安装' '升级' '运行 iptv node' ) inquirer list_input_index "选择操作" nodejs_options nodejs_options_index if [ "$nodejs_options_index" -eq 0 ] then if [[ -x $(command -v node) ]] && [[ -x $(command -v npm) ]] then Println "$error nodejs 已存在\n" exit 1 fi NodejsInstall elif [ "$nodejs_options_index" -eq 1 ] then if [[ ! -x $(command -v node) ]] || [[ ! -x $(command -v npm) ]] then Println "$error 请先安装 nodejs\n" exit 1 fi NodejsInstall else if [ -f "$NODE_ROOT/index.js" ] then Println "$error iptv node 已存在\n" exit 1 fi if [[ ! -x $(command -v node) ]] || [[ ! -x $(command -v npm) ]] then Println "$error 请先安装 nodejs\n" exit 1 fi if [[ ! -x $(command -v mongod) ]] then Println "$error 请先安装 mongodb\n" exit 1 fi NodejsConfig fi } MongodbInstall() { if [[ -x $(command -v mongod) ]] then return 0 fi DepsCheck if ! ResourceLimit then Println "$error 可能环境是 Unprivileged Container ?\n" fi DepInstall curl DepInstall ca-certificates ArchCheck if [ "$arch" == "arm64" ] then arch_path="aarch64" elif [ "$arch" == "x86_64" ] || [ "$arch" == "s390x" ] then arch_path="$arch" else Println "$error 不支持当前系统\n" return 1 fi case $dist in rpm) DepInstall yum-utils if [ ! -f /etc/yum.repos.d/mongodb-org-7.0.repo ] then if grep -qi Amazon <<< "$ID" || grep -qi Amazon <<< "$NAME" then cat > /etc/yum.repos.d/mongodb-org-7.0.repo < /etc/yum.repos.d/mongodb-org-7.0.repo < /dev/null else adduser $v2ray_name --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin $v2ray_name fi Println "$info 安装 $v2ray_name..." if [ "$v2ray_name" == "v2ray" ] then { curl -s -m 10 "$V2_LINK" || curl -s -m 30 "$V2_LINK_FALLBACK"; } \ | sed "s+nobody+$v2ray_name+g" \ | sed "s+ 'sha1'++g" \ | sed "s+ 'sha256'++g" \ | sed "s+ 'sha512'++g" \ | sed "s+https://api.github.com/repos/v2fly/v2ray-core/releases/latest+$FFMPEG_MIRROR_LINK/$v2ray_name.json+g" \ | sed "s+https://github.com/v2fly/v2ray-core/releases/download+$FFMPEG_MIRROR_LINK/$v2ray_name+g" | bash else { curl -s -m 10 "$V2_LINK" || curl -s -m 30 "$V2_LINK_FALLBACK"; } \ | sed "s+nobody+$v2ray_name+g" \ | sed "s+ 'sha1'++g" \ | sed "s+ 'sha256'++g" \ | sed "s+ 'sha512'++g" \ | sed "s+https://api.github.com/repos/XTLS/Xray-core/releases/latest+$FFMPEG_MIRROR_LINK/$v2ray_name.json+g" \ | sed "s+https://github.com/XTLS/Xray-core/releases/download+$FFMPEG_MIRROR_LINK/$v2ray_name+g" | bash fi sed -i "s+nobody+$v2ray_name+g" "/etc/systemd/system/$v2ray_name.service" sed -i "s+nobody+$v2ray_name+g" "/etc/systemd/system/$v2ray_name@.service" mkdir -p /var/log/$v2ray_name/ [ ! -e "/var/log/$v2ray_name/error.log" ] && printf '%s' "" > /var/log/$v2ray_name/error.log chown -R $v2ray_name:$v2ray_name /var/log/$v2ray_name/ chown -R $v2ray_name:$v2ray_name /usr/local/share/$v2ray_name/ V2rayConfigUpdate systemctl daemon-reload systemctl enable $v2ray_name systemctl start $v2ray_name Println "$info $v2ray_name 安装完成\n" } V2rayUpdate() { DepsCheck JQInstall ShFileUpdate $v2ray_name if ! grep -q "$v2ray_name:" < "/etc/passwd" then if grep -q '\--group ' < <(adduser --help) then adduser $v2ray_name --system --group --no-create-home > /dev/null else adduser $v2ray_name --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin $v2ray_name fi if [ "$v2ray_name" == "v2ray" ] then { curl -s -m 10 "$V2_LINK" || curl -s -m 30 "$V2_LINK_FALLBACK"; } \ | sed "s+nobody+$v2ray_name+g" \ | sed "s+ 'sha1'++g" \ | sed "s+ 'sha256'++g" \ | sed "s+ 'sha512'++g" \ | sed "s+https://api.github.com/repos/v2fly/v2ray-core/releases/latest+$FFMPEG_MIRROR_LINK/$v2ray_name.json+g" \ | sed "s+https://github.com/v2fly/v2ray-core/releases/download+$FFMPEG_MIRROR_LINK/$v2ray_name+g" | bash else { curl -s -m 10 "$V2_LINK" || curl -s -m 30 "$V2_LINK_FALLBACK"; } \ | sed "s+nobody+$v2ray_name+g" \ | sed "s+ 'sha1'++g" \ | sed "s+ 'sha256'++g" \ | sed "s+ 'sha512'++g" \ | sed "s+https://api.github.com/repos/XTLS/Xray-core/releases/latest+$FFMPEG_MIRROR_LINK/$v2ray_name.json+g" \ | sed "s+https://github.com/XTLS/Xray-core/releases/download+$FFMPEG_MIRROR_LINK/$v2ray_name+g" | bash fi sed -i "s+nobody+$v2ray_name+g" "/etc/systemd/system/$v2ray_name.service" sed -i "s+nobody+$v2ray_name+g" "/etc/systemd/system/$v2ray_name@.service" mkdir -p /var/log/$v2ray_name/ [ ! -e "/var/log/$v2ray_name/error.log" ] && printf '%s' "" > /var/log/$v2ray_name/error.log chown -R $v2ray_name:$v2ray_name /var/log/$v2ray_name/ chown -R $v2ray_name:$v2ray_name /usr/local/share/$v2ray_name/ V2rayConfigUpdate systemctl daemon-reload systemctl restart $v2ray_name Println "$info $v2ray_name 升级完成\n" } V2rayConfigUpdate() { if [ ! -e "$V2_CONFIG" ] then Println "$error $v2ray_name 未安装...\n" exit 1 fi if ! outbounds=$($JQ_FILE '.outbounds' "$V2_CONFIG" 2> /dev/null) || [ "$outbounds" == "null" ] then if grep -q '"path": "' < "$V2_CONFIG" then while IFS= read -r line do if [[ $line == *"path"* ]] then path=${line#*: \"} path=${path%\"*} break fi done < "$V2_CONFIG" fi printf -v update_date '%(%m-%d-%H:%M:%S)T' -1 cp -f "$V2_CONFIG" "${V2_CONFIG}_$update_date" while IFS= read -r line do if [[ $line == *"port"* ]] then port=${line#*: } port=${port%,*} elif [[ $line == *"id"* ]] then id=${line#*: \"} id=${id%\"*} break fi done < "$V2_CONFIG" $JQ_FILE -n --arg port "${port:-$(GetFreePort)}" --arg id "${id:-$($V2CTL_FILE uuid)}" --arg path "${path:-/$(RandStr)}" \ --arg error "/var/log/$v2ray_name/error.log" \ '{ "log": { "access": "none", "error": $error, "loglevel": "error" }, "inbounds": [ { "listen": "127.0.0.1", "port": $port | tonumber, "protocol": "vmess", "settings": { "clients": [ { "id": $id, "level": 0, "alterId": 64, "email": "name@localhost" } ] }, "streamSettings": { "network": "ws", "wsSettings": { "path": $path } }, "tag": "nginx-1" } ], "outbounds": [ { "protocol": "freedom", "tag": "direct" }, { "protocol": "blackhole", "tag": "block" } ], "policy": { "levels": { "0": { "handshake": 4, "connIdle": 300, "uplinkOnly": 2, "downlinkOnly": 5, "statsUserUplink": false, "statsUserDownlink": false, "bufferSize": 512 } }, "system": { "statsInboundUplink": false, "statsInboundDownlink": false, "statsOutboundUplink": false, "statsOutboundDownlink": false } } }' > "$V2_CONFIG" Println "$info $v2ray_name 配置文件已更新\n" fi } V2rayStatus() { systemctl --no-pager -l status $v2ray_name } V2raySetListen() { echo inquirer text_input "输入监听地址: " listen "0.0.0.0" } V2raySetFollowRedirect() { Println "$tip 如果选 是, 安全起见需要你自己设置透明代理的防火墙 详见: https://www.v2fly.org/config/protocols/dokodemo.html" inquirer list_input "识别出由 iptables 转发而来的数据, 并转发到相应的目标地址" ny_options follow_redirect if [ "$follow_redirect" == "$i18n_no" ] then follow_redirect=false else follow_redirect=true fi } V2raySetAddress() { echo ExitOnText "输入目标服务器地址(ip或域名): " address } V2raySetDnsAddress() { Println "$tip 当不指定时, 保持来源中指定的地址不变" inquirer text_input "修改 DNS 服务器地址: " dns_address "不指定" if [ "$dns_address" == "不指定" ] then dns_address="" fi } V2raySetLocalPort() { Println "请输入端口, 可以是 整型数值, 环境变量, 端口范围" while read -p "(默认: 随机生成): " port do case "$port" in "") port=$(GetFreePort) break ;; *[!0-9]*) if [[ $port =~ ^([0-9]+)-([0-9]+)$ ]] then break else Println "$error 输入错误\n" fi ;; *) if [ "$port" -ge 1 ] && [ "$port" -le 65535 ] then if ( echo -n "" >/dev/tcp/127.0.0.1/"$port" ) >/dev/null 2>&1 then Println "$error 端口已被其他程序占用!请重新输入! \n" else break fi else Println "$error $i18n_input_correct_number [1-65535]\n" fi ;; esac done Println " 端口: ${green} $port ${normal}" } V2raySetAddressPort() { Println "请输入端口" while read -p "$i18n_default_cancel" address_port do case "$address_port" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$address_port" -gt 0 ] && [ "$address_port" -le 65535 ] then break else Println "$error $i18n_input_correct_number\n" fi ;; esac done Println " 端口: ${green} $address_port ${normal}" } V2raySetSocksVersion() { echo socks_version_options=( '5' '4a' '4' ) inquirer list_input "选择 Socks 协议版本" socks_version_options socks_version } V2raySetDnsPort() { Println "$tip 当不指定时, 保持来源中指定的端口不变" inquirer text_input "修改 DNS 服务器端口: " dns_port "不指定" if [ "$dns_port" == "不指定" ] then dns_port="" fi } V2raySetSettingsNetwork() { Println "$tip 比如当指定为 tcp 时, 仅会接收 TCP 流量" settings_network_options=( 'tcp' 'udp' 'tcp,udp' ) inquirer list_input "可接收的网络协议类型" settings_network_options settings_network } V2raySetIvCheck() { echo inquirer list_input_index "启用 IV 检查功能" ny_options ny_options_index if [ "$ny_options_index" -eq 0 ] then iv_check=false else iv_check=true fi } V2raySetDnsNetwork() { Println "$tip 当不指定时, 保持来源的传输方式不变" dns_network_options=( 'tcp' 'udp' '不指定' ) inquirer list_input "DNS 流量的传输层协议" dns_network_options dns_network if [ "$dns_network" == "不指定" ] then dns_network="" fi } V2raySetInboundProtocol() { echo protocol_options=( 'vmess' 'vless' 'http' 'socks' 'shadowsocks' 'dokodemo-door' 'trojan' ) inquirer list_input "选择传输协议" protocol_options protocol } V2raySetOutboundProtocol() { echo protocol_options=( 'vmess' 'vless' 'http' 'socks' 'shadowsocks' 'trojan' 'blackhole' 'dns' 'freedom' ) if [ "$v2ray_name" == "v2ray" ] then protocol_options+=('loopback') fi inquirer list_input "选择传输协议" protocol_options protocol } V2raySetInboundNetwork() { echo network_options=( 'ws' 'tcp' 'kcp' 'http/2' 'quic' 'domainsocket' 'grpc' ) inquirer list_input "选择传输方式" network_options network if [ "$network" == "http/2" ] then network="http" fi } V2raySetOutboundNetwork() { echo network_options=( 'ws' 'tcp' 'kcp' 'http/2' 'quic' 'grpc' ) inquirer list_input "选择传输方式" network_options network if [ "$network" == "http/2" ] then network="http" fi } V2raySetSecurity() { echo if [ "$v2ray_name" == "xray" ] && { [ "$protocol" == "vless" ] || [ "$protocol" == "trojan" ]; } then security_options=( 'none' 'tls' 'xtls' ) else security_options=( 'none' 'tls' ) fi inquirer list_input "选择传输加密" security_options security } V2raySetTlsServerName() { Println "$tip 在连接由 IP 建立时有用" inquirer text_input "指定服务器端证书的域名" tls_server_name "$i18n_not_set" if [ "$tls_server_name" == "$i18n_not_set" ] then tls_server_name="" fi } V2raySetTlsAllowInsecure() { Println "$tip 在自定义证书的情况开可以选 否" inquirer list_input_index "是否检测证书有效性" yn_options yn_options_index if [ "$yn_options_index" -eq 0 ] then tls_allow_insecure=false else tls_allow_insecure=true fi } V2raySetTlsAlpn() { if [ -n "${new_inbound:-}" ] && { [ "$protocol" == "vless" ] || [ "$protocol" == "trojan" ]; } && [ "$network" == "tcp" ] then Println "$tip 多个 ALPN 值用空格分隔, 如果要设置 $protocol 协议回落这里至少需要 http/1.1" else Println "$tip 多个 ALPN 值用空格分隔" fi inquirer text_input "指定 ALPN 值" tls_alpn "h2 http/1.1" IFS=" " read -r -a tls_alpns <<< "$tls_alpn" printf -v tls_alpn ',"%s"' "${tls_alpns[@]}" tls_alpn=${tls_alpn:1} } V2raySetTlsDisableSystemRoot() { Println "$tip 不禁用时只会使用操作系统自带的 CA 证书进行 $tls_name 握手" if [ -n "${new_inbound:-}" ] then inquirer list_input "是否禁用操作系统自带的 CA 证书" yn_options tls_disable_system_root_yn else inquirer list_input "是否禁用操作系统自带的 CA 证书" ny_options tls_disable_system_root_yn fi if [ "$tls_disable_system_root_yn" == "$i18n_no" ] then tls_disable_system_root=false else tls_disable_system_root=true fi } V2raySetTlsMinVersion() { echo inquirer text_input "输入可接受的最小 TLS 版本: " tls_min_version "$i18n_not_set" if [ "$tls_min_version" == "$i18n_not_set" ] then tls_min_version="" fi } V2raySetTlsMaxVersion() { echo inquirer text_input "输入可接受的最大 TLS 版本: " tls_max_version "$i18n_not_set" if [ "$tls_max_version" == "$i18n_not_set" ] then tls_max_version="" fi } V2raySetTlsPreferServerCipherSuites() { echo inquirer list_input "优先服务器密码套件" ny_options tls_prefer_server_cipher_suites_yn if [ "$tls_prefer_server_cipher_suites_yn" == "$i18n_no" ] then tls_prefer_server_cipher_suites=false else tls_prefer_server_cipher_suites=true fi } V2raySetTlsCipherSuites() { Println "$tip 每个套件名称之间用:进行分隔" inquirer text_input "输入密码套件" tls_cipher_suites "$i18n_not_set" if [ "$tls_cipher_suites" == "$i18n_not_set" ] then tls_cipher_suites="" fi } V2raySetTlsEnableSessionResumption() { echo inquirer list_input "开启 ClientHello 的 session_ticket 扩展" ny_options tls_enable_session_resumption_yn if [ "$tls_enable_session_resumption_yn" == "$i18n_no" ] then tls_enable_session_resumption=false else tls_enable_session_resumption=true fi } V2raySetTlsFingerprint() { echo tls_fingerprint_options=( '不设置' 'chrome' 'firefox' 'safari' 'randomized' ) inquirer list_input "指定 TLS Client Hello 的指纹" tls_fingerprint_options tls_fingerprint if [ "$tls_fingerprint" == "$i18n_not_set" ] then tls_fingerprint="" fi } V2raySetTlsRejectUnknownSni() { echo inquirer list_input "拒绝未知 SNI" yn_options tls_reject_unknown_sni_yn if [ "$tls_reject_unknown_sni_yn" == "$i18n_yes" ] then tls_reject_unknown_sni=true else tls_reject_unknown_sni=false fi } V2raySetTlsVerifyClientCertificate() { echo inquirer list_input "在连接时进行客户端证书认证" ny_options tls_verify_client_certificate_yn if [ "$tls_verify_client_certificate_yn" == "$i18n_no" ] then tls_verify_client_certificate=false else tls_verify_client_certificate=true fi } V2raySetTlsPinnedPeerCertificateChainSha256() { echo inquirer text_input "证书链的SHA256散列值" tls_pinned_peer_certificate_chain_sha256 "$i18n_not_set" if [ "$tls_pinned_peer_certificate_chain_sha256" == "$i18n_not_set" ] then tls_pinned_peer_certificate_chain_sha256="" fi } V2raySetCertificateFile() { Println "$tip 如使用 OpenSSL 生成, 后缀名为 .crt, 文件必须存在" inquirer text_input "输入证书文件路径: " certificate_file if [ -s "$certificate_file" ] then cp -f "$certificate_file" /usr/local/share/$v2ray_name/ certificate_file="/usr/local/share/$v2ray_name/${certificate_file##*/}" chown $v2ray_name:$v2ray_name /usr/local/share/$v2ray_name/* Println "$info 已复制证书到 $certificate_file 并赋予 $v2ray_name:$v2ray_name 权限" else Println "$error 证书不存在, 请稍后手动添加证书并赋予 $v2ray_name 权限(chown $v2ray_name:$v2ray_name $certificate_file)" fi } V2raySetKeyFile() { Println "$tip 如使用 OpenSSL 生成, 后缀名为 .key, 密钥必须存在" inquirer text_input "输入证书密钥路径: " key_file if [ -s "$key_file" ] then cp -f "$key_file" /usr/local/share/$v2ray_name/ key_file="/usr/local/share/$v2ray_name/${key_file##*/}" chown $v2ray_name:$v2ray_name /usr/local/share/$v2ray_name/* Println "$info 已复制密钥到 $key_file 并赋予 $v2ray_name:$v2ray_name 权限" else Println "$error 密钥不存在, 请稍后手动添加密钥并赋予 $v2ray_name 权限(chown $v2ray_name:$v2ray_name $key_file)" fi } V2raySetCertificates() { echo usage_options=( "$tls_name 认证和加密" "验证远端 $tls_name 的证书" "签发其它证书" ) if [ "$v2ray_name" == "v2ray" ] then usage_options+=("验证客户端身份") else inquirer list_input "开启证书热重载功能和 OCSP 装订" ny_options ny_option if [ "$ny_option" == "$i18n_no" ] then one_time_loading=true ocsp_stapling=3600 else one_time_loading=false echo inquirer text_input "输入 OCSP 装订更新与证书热重载的时间间隔(秒): " ocsp_stapling 3600 fi echo fi inquirer list_input_index "选择证书用途" usage_options usage_options_index if [ "$usage_options_index" -eq 0 ] then usage="encipherment" elif [ "$usage_options_index" -eq 1 ] then usage="verify" elif [ "$usage_options_index" -eq 2 ] then usage="issue" else usage="verifyclient" fi echo add_crt_options=( '自签名 / CA证书' '选择现有证书/请求真实域名证书' '输入证书地址' ) inquirer list_input "选择添加证书方式" add_crt_options add_crt_option if [ "$add_crt_option" == "自签名 / CA证书" ] then if [ "$v2ray_name" == "xray" ] then crt=$($V2CTL_FILE tls cert) elif [ "$usage" == "encipherment" ] then echo inquirer list_input "是否是 CA 证书" yn_options ca_yn if [ "$ca_yn" == "$i18n_yes" ] then crt=$($V2CTL_FILE cert -ca) else crt=$($V2CTL_FILE cert) fi else crt=$($V2CTL_FILE cert -ca) fi certificate=$($JQ_FILE "{\"usage\":\"$usage\"} * ." <<< "$crt") elif [ "$add_crt_option" == "选择现有证书/请求真实域名证书" ] then if ls -A /usr/local/share/$v2ray_name/*.crt > /dev/null 2>&1 then crt_options=() for f in /usr/local/share/$v2ray_name/*.crt do domain=${f##*/} domain=${domain%.*} crt_options+=("$domain") done crt_options+=("添加域名") echo inquirer list_input "选择证书" crt_options crt_option else crt_option="添加域名" fi if [ "$crt_option" == "添加域名" ] then Println "$tip 如果证书不存在需请求新 CA 证书, 请确保没有程序占用 80 端口或已经设置 mmproxy acme" ExitOnText "输入域名: " domain if [ ! -s "/usr/local/share/$v2ray_name/$domain.crt" ] then if [ -s "/usr/local/nginx/conf/sites_crt/$domain.crt" ] then cp -f "/usr/local/nginx/conf/sites_crt/$domain.crt" "/usr/local/share/$v2ray_name/$domain.crt" cp -f "/usr/local/nginx/conf/sites_crt/$domain.key" "/usr/local/share/$v2ray_name/$domain.key" elif [ -s "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" ] then cp -f "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" "/usr/local/share/$v2ray_name/$domain.crt" cp -f "/usr/local/openresty/nginx/conf/sites_crt/$domain.key" "/usr/local/share/$v2ray_name/$domain.key" else AcmeCheck Println "$info 安装 $domain 证书..." V2rayDomainUpdateCrt "$domain" Println "$info $domain 证书安装成功" fi fi chown $v2ray_name:$v2ray_name /usr/local/share/$v2ray_name/* certificate=$( $JQ_FILE -n --arg usage "$usage" --arg certificateFile "/usr/local/share/$v2ray_name/$domain.crt" \ --arg keyFile "/usr/local/share/$v2ray_name/$domain.key" \ '{ "usage": $usage, "certificateFile": $certificateFile, "keyFile": $keyFile }') else certificate=$( $JQ_FILE -n --arg usage "$usage" --arg certificateFile "/usr/local/share/$v2ray_name/$crt_option.crt" \ --arg keyFile "/usr/local/share/$v2ray_name/$crt_option.key" \ '{ "usage": $usage, "certificateFile": $certificateFile, "keyFile": $keyFile }') fi else V2raySetCertificateFile if [ "$usage" == "verify" ] then echo inquirer list_input "是否继续添加证书密钥" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then certificate=$( $JQ_FILE -n --arg usage "$usage" --arg certificateFile "$certificate_file" \ '{ "usage": $usage, "certificateFile": $certificateFile }') return 0 fi fi V2raySetKeyFile certificate=$( $JQ_FILE -n --arg usage "$usage" --arg certificateFile "$certificate_file" \ --arg keyFile "$key_file" \ '{ "usage": $usage, "certificateFile": $certificateFile, "keyFile": $keyFile }') fi if [ "$v2ray_name" == "xray" ] then certificate=$( $JQ_FILE --arg ocspStapling "$ocsp_stapling" \ --arg oneTimeLoading "$one_time_loading" \ '{ "ocspStapling": $ocspStapling | tonumber, "oneTimeLoading": $oneTimeLoading | test("true") } * .' <<< "$certificate") fi Println "$info 证书添加成功" } V2raySetInboundSockoptTcpFastOpen() { echo sockopt_tfo_options=( '系统默认' '强制开启' '强制关闭' ) inquirer list_input_index "TCP Fast Open" sockopt_tfo_options sockopt_tfo_options_index if [ "$sockopt_tfo_options_index" -eq 0 ] then sockopt_tfo="" elif [ "$sockopt_tfo_options_index" -eq 1 ] then if [ "$v2ray_name" == "xray" ] then Println "$tip 表示待处理的 TFO 连接请求数上限" inquirer text_input "输入正整数" sockopt_tfo 256 return 0 fi sockopt_tfo=true else sockopt_tfo=false fi } V2raySetOutboundSockoptTcpFastOpen() { echo sockopt_tfo_options=( '系统默认' '强制开启' '强制关闭' ) inquirer list_input_index "TCP Fast Open" sockopt_tfo_options sockopt_tfo_options_index if [ "$sockopt_tfo_options_index" -eq 0 ] then sockopt_tfo="" elif [ "$sockopt_tfo_options_index" -eq 1 ] then sockopt_tfo=true else sockopt_tfo=false fi } V2raySetSockoptTproxy() { echo sockopt_tproxy_options=( 'off' 'redirect' 'tproxy' ) inquirer list_input "设置透明代理模式" sockopt_tproxy_options sockopt_tproxy } V2raySetSockoptMark() { Println "$tip 当其值非零时, 在出站连接上标记 SO_MARK" inquirer text_input "输入出站连接标记(整数): " sockopt_mark 0 } V2raySetSockoptTcpKeepAliveInterval() { Println "$tip 0 代表保持默认值" inquirer text_input "TCP 保持活跃的数据包发送间隔(秒): " sockopt_tcp_keep_alive_interval 0 } V2raySetSockoptDomainStrategy() { echo sockopt_domain_strategy_options=( "AsIs" "UseIP" "UseIPv4" "UseIPv6" ) inquirer list_input "选择建立连接时域名解析方式" sockopt_domain_strategy_options sockopt_domain_strategy } V2raySetSockoptDialerProxy() { Println "$tip 此选项可用于支持底层传输方式的链式转发" inquirer text_input "出站代理的标识: " sockopt_dialer_proxy "$i18n_not_set" if [ "$sockopt_dialer_proxy" == "$i18n_not_set" ] then sockopt_dialer_proxy="" fi } V2raySetSockoptAcceptProxyProtocol() { echo inquirer list_input "接收 PROXY protocol" ny_options sockopt_accept_proxy_protocol_yn if [ "$sockopt_accept_proxy_protocol_yn" == "$i18n_yes" ] then sockopt_accept_proxy_protocol=true else sockopt_accept_proxy_protocol=false fi } V2raySetPath() { echo inquirer text_input "输入路径: " path "$i18n_random" if [ "$path" == "$i18n_random" ] then path="/$(RandStr)" Println " 路径: ${green} $path ${normal}" fi } V2raySetWsHeaders() { ws_headers="" while true do echo inquirer text_input "输入自定义 HTTP 头的名称: " ws_header_name "$i18n_not_set" if [ "$ws_header_name" == "$i18n_not_set" ] then break fi echo inquirer text_input "输入自定义 HTTP 头 $ws_header_name 的值: " ws_header_value "$i18n_not_set" if [ "$ws_header_value" == "$i18n_not_set" ] then break fi [ -n "$ws_headers" ] && ws_headers="$ws_headers, " ws_headers="$ws_headers\"$ws_header_name\":\"$ws_header_value\"" echo inquirer list_input "是否继续添加" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi done } V2raySetInboundMaxEarlyData() { echo inquirer list_input "启用前置数据支持" ny_options max_early_data_yn if [ "$max_early_data_yn" == "$i18n_no" ] then max_early_data=1 else max_early_data=0 fi } V2raySetOutboundMaxEarlyData() { echo inquirer text_input "输入发送的前置数据的最长长度" max_early_data 0 } V2raySetEarlyDataHeaderName() { Println "$tip 当且仅当 HTTP 头的名字为 Sec-WebSocket-Protocol 时可以启用基于 HTTP 头的前置数据浏览器转发功能" inquirer text_input "输入发送的前置数据的 HTTP 头的名字: " early_data_header_name "$i18n_not_set" if [ "$early_data_header_name" == "$i18n_not_set" ] then earlyDataHeaderName="" fi } V2raySetUseBrowserForwarding() { echo inquirer list_input "启用浏览器转发" ny_options use_browser_forwarding_yn if [ "$use_browser_forwarding_yn" == "$i18n_no" ] then use_browser_forwarding=false else use_browser_forwarding=true fi } V2raySetHttpMethod() { echo inquirer text_input "输入 HTTP 方法: " http_method PUT } V2raySetHttpHeaders() { http_headers="" while true do echo inquirer text_input "输入自定义 HTTP 头的名称: " http_header_name "$i18n_not_set" if [ "$http_header_name" == "$i18n_not_set" ] then break fi http_header_values="" http_header_values_count=0 while true do echo inquirer text_input "输入自定义 HTTP 头 $http_header_name 的值: " http_header_value "$i18n_not_set" if [ "$http_header_value" == "$i18n_not_set" ] then http_header_value="" else http_header_value=${http_header_value//\\/\\\\} http_header_value=${http_header_value//\"/\\\"} fi http_header_values_count=$((http_header_values_count+1)) [ -n "$http_header_values" ] && http_header_values="$http_header_values," http_header_values="$http_header_values\"$http_header_value\"" echo inquirer list_input "是否继续添加自定义 HTTP 头 $http_header_name 的值" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi done [ -n "$http_headers" ] && http_headers="$http_headers, " if [ "$http_header_values_count" -eq 1 ] then http_headers="$http_headers\"$http_header_name\":$http_header_values" else http_headers="$http_headers\"$http_header_name\":[$http_header_values]" fi inquirer list_input "是否继续添加自定义 HTTP 头" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi done } V2raySetId() { echo inquirer text_input "输入 id: " id "$i18n_random" if [ "$id" == "$i18n_random" ] then id=$($V2CTL_FILE uuid) Println " id: ${green} $id ${normal}" fi } V2raySetInboundFlow() { echo flow_options=( 'xtls-rprx-direct' 'xtls-rprx-origin' ) inquirer list_input "选择模式" flow_options flow } V2raySetOutboundFlow() { echo flow_options=( 'xtls-rprx-direct' 'xtls-rprx-direct-udp443' 'xtls-rprx-splice' 'xtls-rprx-splice-udp443' 'xtls-rprx-origin' 'xtls-rprx-origin-udp443' ) inquirer list_input "选择模式" flow_options flow } V2raySetAlterId() { Println "请输入 alterId" while read -p "(默认: 0): " alter_id do case "$alter_id" in "") alter_id=0 break ;; *[!0-9]*) Println "$error $i18n_input_correct_number [0-65535]\n" ;; *) if [ "$alter_id" -ge 0 ] && [ "$alter_id" -le 65535 ] then break else Println "$error $i18n_input_correct_number [0-65535]\n" fi ;; esac done Println " alterId: ${green} $alter_id ${normal}" } V2raySetEmail() { echo inquirer text_input "输入邮箱: " email "$i18n_random" if [ "$email" == "$i18n_random" ] then email="$(RandStr)@localhost" Println " 邮箱: ${green} $email ${normal}" fi } V2raySetPassword() { echo inquirer text_input "输入密码: " password "$i18n_random" if [ "$password" == "$i18n_random" ] then password=$(RandStr) Println " 密码: ${green} $password ${normal}" fi } V2raySetTimeout() { Println "入站数据的时间限制(秒)" while read -p "(默认: 300): " timeout do case "$timeout" in "") timeout=300 break ;; *[!0-9]*) Println "$error $i18n_input_correct_number [>0]\n" ;; *) if [ "$timeout" -gt 0 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done Println " 时间限制: ${green} $timeout ${normal}" } V2raySetAllowTransparent() { echo inquirer list_input "转发所有 HTTP 请求, 而非只是代理请求, 若配置不当, 开启此选项会导致死循环" ny_options allow_transparent_yn if [[ $allow_transparent_yn == "$i18n_yes" ]] then allow_transparent=true else allow_transparent=false fi Println " allowTransparent: ${green} $allow_transparent ${normal}" } V2raySetLevel() { V2rayListPolicy echo -e "选择等级" while read -p "(默认: 1): " level do case "$level" in "") level=0 break ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$level" -gt 0 ] && [ "$level" -le $((policy_levels_count+1)) ] then level=$((level-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done Println " 等级: ${green} $level ${normal}\n" } V2raySetHttpAccount() { echo inquirer text_input "输入用户名: " user "$i18n_random" if [ "$user" == "$i18n_random" ] then user=$(RandStr) Println " 用户名: ${green} $user ${normal}" fi echo inquirer text_input "输入密码: " pass "$i18n_random" if [ "$pass" == "$i18n_random" ] then pass=$(RandStr) Println " 密码: ${green} $pass ${normal}" fi } V2raySetTag() { echo inquirer text_input "输入标签: " tag "$i18n_random" if [ "$tag" == "$i18n_random" ] then tag=$(GetFreeTag) tag=${tag//nginx-/} Println " 标签: ${green} $tag ${normal}" fi } V2raySetNginxTag() { i=0 while true do i=$((i+1)) tag="nginx-$i" if ! grep -q '"tag": "'"$tag"'"' < "$V2_CONFIG" then break fi done Println " 标签: ${green} $tag ${normal}\n" } V2raySetAcceptProxyProtocol() { Println "$tip PROXY 协议专用于传递请求的真实来源 IP 和端口, 如果前端 nginx 发送 PROXY Protocol 必须选是" inquirer list_input "是否接收 PROXY 协议" ny_options accept_proxy_protocol if [[ $accept_proxy_protocol == "$i18n_yes" ]] then accept_proxy_protocol=true else accept_proxy_protocol=false fi } V2raySetVmessSecurity() { echo vmess_security_options=( 'auto' 'aes-128-gcm' 'chacha20-poly1305' 'none' ) inquirer list_input "选择加密方式" vmess_security_options vmess_security } V2raySetQuicSecurity() { echo quic_security_options=( 'none' 'aes-128-gcm' 'chacha20-poly1305' ) inquirer list_input "设置 QUIC 加密方式" quic_security_options quic_security } V2raySetQuicKey() { if [ "$quic_security" == "none" ] then quic_key="" else echo inquirer text_input "输入 QUIC 加密密钥: " quic_key "$i18n_random" if [[ $quic_key == "$i18n_random" ]] then quic_key=$(RandStr) fi fi } V2raySetDsPath() { Println "$tip 在运行 $v2ray_name 之前, 这个文件必须不存在" ExitOnText "输入 domainsocket 文件路径: " ds_path Println " domainsocket 文件路径: ${green} $ds_path ${normal}" } V2raySetDsAbstract() { echo inquirer list_input "是否为 abstract domain socket" ny_options ds_abstract if [[ $ds_abstract == "$i18n_no" ]] then ds_abstract=false else ds_abstract=true fi } V2raySetDsPadding() { echo inquirer list_input "abstract domain socket 是否带 padding" ny_options ds_padding if [[ $ds_padding == "$i18n_no" ]] then ds_padding=false else ds_padding=true fi } V2raySetGrpcServiceName() { while true do echo inquirer text_input "输入 gRPC 服务的名称: " grpc_service_name "$i18n_random" case "$grpc_service_name" in "$i18n_random") grpc_service_name=$(RandStr) break ;; *[!0-9A-Za-z_.]*) Println "$error 名称格式错误\n" ;; *) break ;; esac done Println " gRPC 服务的名称: ${green} $grpc_service_name ${normal}" } V2raySetGrpcMultiMode() { echo inquirer list_input "启用 multiMode" ny_options grpc_multi_mode_yn if [ "$grpc_multi_mode_yn" == "$i18n_no" ] then grpc_multi_mode=false else grpc_multi_mode=true fi } V2raySetDetourTo() { Println "$tip 指定的入站协议必须是 VMess" inquirer text_input "使用另一个入站的出站(输入指定的另一个入站的标签): " detour_to "$i18n_not_set" Println " 指定的另一个入站: ${green} $detour_to ${normal}" } V2raySetDetourDefault() { V2raySetLevel V2raySetAlterId } V2raySetDisableInsecureEncryption() { Println "$tip 当客户端使用 none / aes-128-cfb 加密方式时, 服务器会主动断开连接" inquirer list_input_index "是否禁止客户端使用不安全的加密方式" yn_options yn_options_index if [ "$yn_options_index" == "$i18n_yes" ] then disable_insecure_encryption=true else disable_insecure_encryption=false fi } V2raySetHeaderType() { echo if [ "$network" == "tcp" ] then header_type_options=( 'none' 'http' ) header_http_request='{}' header_http_response='{}' else header_type_options=( 'none' 'srtp' 'utp' 'wechat-video' 'dtls' 'wireguard' ) fi inquirer list_input "设置数据包头部伪装" header_type_options header_type if [ "$header_type" == "http" ] then echo inquirer text_input "输入 HTTP 请求版本: " http_request_version "1.1" echo inquirer text_input "输入 HTTP 请求方法: " http_request_method "GET" Println "$tip 多个路径用空格分隔, 当有多个值时, 每次请求随机选择一个值" inquirer text_input "输入 HTTP 请求路径: " http_request_path "/" IFS=" " read -r -a request_path <<< "$http_request_path" printf -v http_request_path ',"%s"' "${request_path[@]}" http_request_path=${http_request_path:1} http_request_headers='{}' while true do echo inquirer text_input "输入自定义 HTTP 请求头的名称: " header_name "$i18n_not_set" if [ "$header_name" == "$i18n_not_set" ] then break fi Println "$tip 多个值用 | 分隔" inquirer text_input "输入自定义 HTTP 请求头 $header_name 的值: " header_value "$i18n_not_set" if [ "$header_value" == "$i18n_not_set" ] then break fi if [[ $header_value =~ \| ]] then IFS="|" read -r -a header_values <<< "$header_value" printf -v header_value ',"%s"' "${header_values[@]}" header_value="${header_value:1}" http_request_headers=$( $JQ_FILE --arg key "$header_name" --argjson value "[$header_value]" \ '. * { ($key): $value }' <<< "$http_request_headers") else http_request_headers=$( $JQ_FILE --arg key "$header_name" --arg value "$header_value" \ '. * { ($key): $value }' <<< "$http_request_headers") fi echo inquirer list_input "是否继续添加" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi done header_http_request=$( $JQ_FILE -n --arg version "$http_request_version" --arg method "$http_request_method" \ --argjson path "[$http_request_path]" --argjson headers "$http_request_headers" \ '{ "version": $version, "method": $method, "path": $path, "headers": $headers, }') echo inquirer text_input "输入 HTTP 响应版本: " http_response_version "1.1" echo inquirer text_input "输入 HTTP 响应状态: " http_response_status "200" echo inquirer text_input "输入 HTTP 响应说明: " http_response_reason "OK" http_response_headers='{}' while true do echo inquirer text_input "输入自定义 HTTP 响应头的名称: " header_name "$i18n_not_set" if [ "$header_name" == "$i18n_not_set" ] then break fi Println "$tip 多个值用|分隔" inquirer text_input "输入自定义 HTTP 响应头 $header_name 的值: " header_value "$i18n_not_set" if [ "$header_value" == "$i18n_not_set" ] then break fi if [[ $header_value =~ \| ]] then IFS="|" read -r -a header_values <<< "$header_value" printf -v header_value ',"%s"' "${header_values[@]}" header_value="${header_value:1}" http_response_headers=$( $JQ_FILE --arg key "$header_name" --argjson value "[$header_value]" \ '. * { ($key): $value }' <<< "$http_response_headers") else http_response_headers=$( $JQ_FILE --arg key "$header_name" --arg value "$header_value" \ '. * { ($key): $value }' <<< "$http_response_headers") fi echo inquirer list_input "是否继续添加" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi done header_http_response=$( $JQ_FILE -n --arg version "$http_response_version" --arg status "$http_response_status" \ --arg reason "$http_response_reason" --argjson headers "$http_response_headers" \ '{ "version": $version, "status": $status, "reason": $reason, "headers": $headers, }') fi } V2raySetKcpMtu() { Println "$tip 介于 576 ~ 1460" inquirer text_input "输入最大传输单元: " kcp_mtu 1350 } V2raySetKcpTti() { Println "$tip 介于 10 ~ 100" inquirer text_input "输入传输时间间隔(毫秒): " kcp_tti 50 } V2raySetKcpUplinkCapacity() { Println "$tip 即主机发出数据所用的最大带宽" inquirer text_input "上行链路容量(MB/s): " kcp_uplink_capacity 5 } V2raySetKcpDownlinkCapacity() { Println "$tip 即主机接收数据所用的最大带宽" inquirer text_input "下行链路容量(MB/s): " kcp_uplink_capacity 20 } V2raySetKcpCongestion() { Println "$tip 开启后, 当丢包严重时, 会自动降低吞吐量; 当网络畅通时, 也会适当增加吞吐量" inquirer list_input "启用拥塞控制" ny_options kcp_congestion_yn if [ "$kcp_congestion_yn" == "$i18n_no" ] then kcp_congestion=false else kcp_congestion=true fi } V2raySetKcpReadBufferSize() { echo inquirer text_input "单个连接的读取缓冲区大小(MB): " kcp_read_buffer_size 2 } V2raySetKcpWriteBufferSize() { echo inquirer text_input "单个连接的写入缓冲区大小(MB): " kcp_write_buffer_size 2 } V2raySetKcpSeed() { echo inquirer text_input "输入混淆密码: " kcp_write_buffer_size "$i18n_not_set" if [ "$kcp_write_buffer_size" == "$i18n_not_set" ] then kcp_write_buffer_size="" fi } V2raySetHttpHost() { Println "$tip 多个域名用空格分隔, 客户端会随机从列表中选出一个域名进行通信, 服务器会验证域名是否在列表中" inquirer text_input "输入通信域名: " http_host "v2ray.com" IFS=" " read -r -a http_hosts <<< "$http_host" printf -v http_host ',"%s"' "${http_hosts[@]}" http_host=${http_host:1} } V2raySetSniffingEnabled() { echo inquirer list_input "是否开启流量探测" ny_options sniffing_enabled if [[ $sniffing_enabled == "$i18n_no" ]] then sniffing_enabled=false else sniffing_enabled=true fi } V2raySetSniffingDestOverride() { sniffing_dest_override_options=( 'tls' 'http' ) Println "$tip 客户端已经设置过的流量类型这里可以不设置" inquirer checkbox_input "指定流量类型: " sniffing_dest_override_options sniffing_dest_override_selected sniffing_dest_override="" if [ -n "${sniffing_dest_override_selected:-}" ] then printf -v sniffing_dest_override ',"%s"' "${sniffing_dest_override_selected[@]}" sniffing_dest_override=${dest_override:1} fi } V2raySetSniffingMetadataOnly() { echo inquirer list_input "仅使用连接的元数据嗅探目标地址" ny_options sniffing_metadata_only_yn if [ "$sniffing_metadata_only_yn" == "$i18n_yes" ] then sniffing_metadata_only=true else sniffing_metadata_only=false fi } V2raySetSniffingDomainsExcluded() { Println "$tip 多个域名用空格分隔" inquirer text_input "输入排除流量探测的域名: " sniffing_domains_excluded "$i18n_not_set" if [ "$sniffing_domains_excluded" == "$i18n_not_set" ] then sniffing_domains_excluded="" else IFS=" " read -r -a sniffing_domains <<< "$sniffing_domains_excluded" printf -v sniffing_domains_excluded ',"%s"' "${sniffing_domains[@]}" sniffing_domains_excluded=${sniffing_domains_excluded:1} fi } SetV2rayAllocateStrategy() { echo allocate_strategy_options=( 'always' 'random' ) inquirer list_input "端口分配策略" allocate_strategy_options allocate_strategy } SetV2rayAllocateRefresh() { Println "随机端口刷新间隔(分钟)" while read -p "(默认: 5): " allocate_refresh do case "$allocate_refresh" in "") allocate_refresh=5 break ;; *[!0-9]*) Println "$error $i18n_input_correct_number [>1]\n" ;; *) if [ "$allocate_refresh" -ge 2 ] then break else Println "$error $i18n_input_correct_number [>1]\n" fi ;; esac done Println " 刷新间隔: ${green} $allocate_refresh ${normal}" } SetV2rayAllocateConcurrency() { Println "随机端口数量, 最大值为端口范围的三分之一" while read -p "(默认: 3): " allocate_concurrency do case "$allocate_concurrency" in "") allocate_concurrency=3 break ;; *[!0-9]*) Println "$error $i18n_input_correct_number [>0]\n" ;; *) if [ "$allocate_concurrency" -ge 1 ] then break else Println "$error $i18n_input_correct_number [>0]\n" fi ;; esac done Println " 随机端口数量: ${green} $allocate_concurrency ${normal}" } V2raySetSendThrough() { Println "$tip 当主机有多个 IP 地址时有效" inquirer text_input "用于发送数据的 IP 地址: " send_through "0.0.0.0" } V2raySetProxyTag() { Println "$tip 如果指定另一个出站协议, 当前协议发出的数据, 将从指定的出站协议发出" inquirer text_input "输入指定的另一个出站协议的标签: " proxy_tag "$i18n_not_set" if [ "$proxy_tag" == "$i18n_not_set" ] then proxy_tag="" fi } V2raySetProxyTransportLayer() { echo inquirer list_input "启用传输层转发支持" ny_options transport_layer_yn if [ "$transport_layer_yn" == "$i18n_no" ] then transport_layer=false else transport_layer=true fi } V2raySetMuxEnabled() { Println "$info Mux 功能是在一条 TCP 连接上分发多个 TCP 连接的数据, 是为了减少 TCP 的握手延迟而设计, 而非提高连接的吞吐量" inquirer list_input "是否启用 Mux 转发请求" ny_options mux_enabled if [ "$mux_enabled" == "$i18n_no" ] then mux_enabled=false else mux_enabled=true fi } V2raySetMuxConcurrency() { Println "$tip 最小值 1, 最大值 1024, 填负数, 比如 -1, 则不加载 mux 模块" inquirer text_input "最大并发连接数: " mux_concurrency 8 } V2raySetResponseType() { Println "$tip none 时直接关闭, http 时返回 403 并关闭" response_type_options=( 'none' 'http' ) inquirer list_input "选择黑洞的响应方式" response_type } V2raySetFreedomDomainStrategy() { Println "$tip AsIs - 直接向此域名发出连接, 其余将域名用内建的 dns 解析为 IP 之后再建立连接" freedom_domain_strategy_options=( 'AsIs' 'UseIP' 'UseIPv4' 'UseIPv6' ) inquirer list_input "域名策略" freedom_domain_strategy_options freedom_domain_strategy } V2raySetFreedomRedirect() { Println "例如 127.0.0.1:80, :1234 - 不改变原先的目标地址, v2ray.com:0 - 不改变原先的端口" inquirer text_input "强制将所有数据发送到指定地址: " freedom_redirect "$i18n_not_set" if [ "$freedom_redirect" == "$i18n_not_set" ] then freedom_redirect="" fi } V2raySetFallbacks() { echo inquirer list_input "是否配置协议回落" ny_options v2ray_fallbacks_yn if [ "$v2ray_fallbacks_yn" == "$i18n_yes" ] then if [ "$v2ray_name" == "xray" ] then v2ray_fallbacks=$( $JQ_FILE -n --arg name "" --arg alpn "" \ --arg path "" --arg dest 80 --arg xver 0 \ '[{ "name": $name, "alpn": $alpn, "path": $path, "dest": $dest | tonumber, "xver": $xver | tonumber }]') else v2ray_fallbacks=$( $JQ_FILE -n --arg alpn "" --arg path "" \ --arg dest 80 --arg xver 0 \ '[{ "alpn": $alpn, "path": $path, "dest": $dest | tonumber, "xver": $xver | tonumber }]') fi while true do if [ "$v2ray_name" == "xray" ] then Println "$tip 一般不用设置" inquirer text_input "输入 SNI 分流匹配值: " v2ray_fallback_name "$i18n_not_set" if [ "$v2ray_fallback_name" == "$i18n_not_set" ] then v2ray_fallback_name="" fi else v2ray_fallback_name="" fi Println "$tip 请输入单个, 比如 http/1.1 或 h2" inquirer text_input "输入尝试匹配 $tls_name ALPN 协商结果: " v2ray_fallback_alpn "$i18n_not_set" if [ "$v2ray_fallback_alpn" == "$i18n_not_set" ] then v2ray_fallback_alpn="" elif [ "$v2ray_fallback_alpn" == "h2" ] && [[ ! $tls_alpn =~ h2 ]] then Println "$error 协议回落存在 h2 时, $tls_name 需设置 h2 http/1.1\n" exit 1 fi Println "$tip 非空则必须以 / 开头, 不支持 h2c" inquirer text_input "输入尝试匹配首包 HTTP PATH: " v2ray_fallback_path "任意" if [ "$v2ray_fallback_path" == "任意" ] then v2ray_fallback_path="" fi Println "$tip 格式为 addr:port 或 /dev/shm/domain.socket, 若填写域名, 也将直接发起 TCP 连接(而不走内置的 DNS)" inquirer text_input "输入 $tls_name 解密后 TCP 流量的去向: " v2ray_fallback_dest if [ -z "$v2ray_fallback_dest" ] then Println "$error $i18n_canceled...\n" exit 1 fi Println "$tip 如果配置 nginx 的 PROXY protocol 记得设置 set_real_ip_from" v2ray_fallback_proxy_protocol_options=( '不发送' '版本1' '版本2' ) inquirer list_input "选择 PROXY protocol" v2ray_fallback_proxy_protocol_options v2ray_fallback_proxy_protocol if [ "$v2ray_fallback_proxy_protocol" == "不发送" ] then v2ray_fallback_proxy_protocol=0 elif [ "$v2ray_fallback_proxy_protocol" == "版本1" ] then v2ray_fallback_proxy_protocol=1 else v2ray_fallback_proxy_protocol=2 fi if [[ $v2ray_fallback_dest =~ ^[0-9]+$ ]] then v2ray_fallback=$( $JQ_FILE -n --arg alpn "$v2ray_fallback_alpn" --arg path "$v2ray_fallback_path" \ --arg dest "$v2ray_fallback_dest" --arg xver "$v2ray_fallback_proxy_protocol" \ '{ "alpn": $alpn, "path": $path, "dest": $dest | tonumber, "xver": $xver | tonumber }') else v2ray_fallback=$( $JQ_FILE -n --arg alpn "$v2ray_fallback_alpn" --arg path "$v2ray_fallback_path" \ --arg dest "$v2ray_fallback_dest" --arg xver "$v2ray_fallback_proxy_protocol" \ '{ "alpn": $alpn, "path": $path, "dest": $dest, "xver": $xver | tonumber }') fi if [ -n "${v2ray_fallback_name:-}" ] then v2ray_fallback=$( $JQ_FILE --arg name "$v2ray_fallback_name" \ '{ "name": $name } * .' <<< "$v2ray_fallback") fi v2ray_fallbacks=$( $JQ_FILE --arg name "$v2ray_fallback_name" --argjson fallback "[$v2ray_fallback]" \ '. + $fallback' <<< "$v2ray_fallbacks") echo inquirer list_input "回落添加成功, 是否继续添加新的回落" ny_options v2ray_fallbacks_yn if [ "$v2ray_fallbacks_yn" == "$i18n_no" ] then break fi done else v2ray_fallbacks="[]" fi } V2raySetAuth() { echo auth_options=( '匿名' '用户密码' ) inquirer list_input "选择认证方式" auth_options auth_option if [ "$auth_option" == "匿名" ] then auth="noauth" else auth="password" fi } V2raySetUdp() { echo inquirer list_input "是否支持 udp" ny_options udp_yn if [ "$udp_yn" == "$i18n_no" ] then udp=false else udp=true fi } V2raySetIp() { echo inquirer text_input "输入用于 udp 的本机 IP: " ip "127.0.0.1" } V2raySetShadowsocksMethod() { echo shadowsocks_method_options=( 'aes-256-gcm' 'aes-128-gcm' 'chacha20-poly1305' 'none' ) inquirer list_input "选择加密方式" shadowsocks_method_options shadowsocks_method } V2rayAddInbound() { V2raySetInboundProtocol V2raySetInboundNetwork V2raySetInboundSockoptTcpFastOpen V2raySetSockoptTproxy if [ "$v2ray_name" == "xray" ] then V2raySetSockoptAcceptProxyProtocol else V2raySetSockoptTcpKeepAliveInterval fi V2raySetSniffingEnabled if [ "$sniffing_enabled" = true ] then V2raySetSniffingDestOverride V2raySetSniffingMetadataOnly else sniffing_dest_override="" sniffing_metadata_only=false fi if [ "$v2ray_name" == "xray" ] && [ -n "$sniffing_dest_override" ] then V2raySetSniffingDomainsExcluded fi if [ "$self" == "ibm" ] then V2raySetSecurity V2raySetTag V2raySetListen else echo inquirer list_input "是否通过此脚本配置的 nginx 连接" ny_options nginx_proxy_yn if [[ $nginx_proxy_yn == "$i18n_yes" ]] then if [ "$protocol" == "vless" ] || [ "$protocol" == "trojan" ] then V2raySetSecurity else security="none" fi V2raySetNginxTag listen="127.0.0.1" else V2raySetSecurity V2raySetTag V2raySetListen fi fi if [ "$network" == "domainsocket" ] then port=$(GetFreePort) else V2raySetLocalPort fi new_inbound=$( $JQ_FILE -n --arg listen "$listen" --arg port "$port" \ --arg protocol "$protocol" --arg network "$network" \ --arg security "$security" --arg tproxy "$sockopt_tproxy" \ --arg enabled "$sniffing_enabled" --argjson destOverride "[$sniffing_dest_override]" \ --arg metadataOnly "$sniffing_metadata_only" --arg tag "$tag" \ '{ "listen": $listen, "port": $port | tonumber, "protocol": $protocol, "streamSettings": { "network": $network, "security": $security, "sockopt": { "tproxy": $tproxy } }, "sniffing": { "enabled": $enabled | test("true"), "destOverride": $destOverride, "metadataOnly": $metadataOnly | test("true") }, "tag": $tag }') if [ "$v2ray_name" == "xray" ] then new_inbound=$( $JQ_FILE --arg acceptProxyProtocol "$sockopt_accept_proxy_protocol" \ '. * { "streamSettings": { "sockopt": { "acceptProxyProtocol": $acceptProxyProtocol | test("true") } } }' <<< "$new_inbound") if [ -n "${sniffing_domains_excluded:-}" ] then new_inbound=$( $JQ_FILE --argjson domainsExcluded "[$sniffing_domains_excluded]" \ '. * { "sniffing": { "domainsExcluded": $domainsExcluded } }' <<< "$new_inbound") fi else new_inbound=$( $JQ_FILE --arg tcpKeepAliveInterval "$sockopt_tcp_keep_alive_interval" \ '. * { "streamSettings": { "sockopt": { "tcpKeepAliveInterval": $tcpKeepAliveInterval | tonumber } } }' <<< "$new_inbound") fi if [ -n "$sockopt_tfo" ] then if [ "$sockopt_tfo" = true ] || [ "$sockopt_tfo" = false ] then new_inbound=$( $JQ_FILE --arg tcpFastOpen "$sockopt_tfo" \ '. * { "streamSettings": { "sockopt": { "tcpFastOpen": $tcpFastOpen | test("true") } } }' <<< "$new_inbound") else new_inbound=$( $JQ_FILE --arg tcpFastOpen "$sockopt_tfo" \ '. * { "streamSettings": { "sockopt": { "tcpFastOpen": $tcpFastOpen | tonumber } } }' <<< "$new_inbound") fi fi if [[ ! "$port" =~ ^[0-9]+$ ]] then SetV2rayAllocateStrategy if [ "$allocate_strategy" == "random" ] then SetV2rayAllocateRefresh SetV2rayAllocateConcurrency new_inbound=$( $JQ_FILE --arg strategy "$allocate_strategy" --arg refresh "$allocate_refresh" \ --arg concurrency "$allocate_concurrency" \ '. * { "allocate": { "strategy": $strategy, "refresh": $refresh | tonumber, "concurrency": $concurrency | tonumber } }' <<< "$new_inbound") fi fi if [ "$security" == "tls" ] then V2raySetTlsServerName if [ -n "$tls_server_name" ] then new_inbound=$( $JQ_FILE --arg serverName "$tls_server_name" \ '. * { "streamSettings": { "tlsSettings": { "serverName": $serverName } } }' <<< "$new_inbound") fi V2raySetTlsAlpn V2raySetTlsDisableSystemRoot V2raySetTlsVerifyClientCertificate new_inbound=$( $JQ_FILE --argjson alpn "[$tls_alpn]" --arg disableSystemRoot "$tls_disable_system_root" \ --arg verifyClientCertificate "$tls_verify_client_certificate" \ '. * { "streamSettings": { "tlsSettings": { "alpn": $alpn, "disableSystemRoot": $disableSystemRoot | test("true"), "verifyClientCertificate": $verifyClientCertificate | test("true") } } }' <<< "$new_inbound") while true do if [ "$tls_disable_system_root" = false ] then echo inquirer list_input "是否继续添加证书" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi fi Println "$info 设置证书" V2raySetCertificates merge=$( $JQ_FILE -n --argjson certificates "[$certificate]" \ '{ "streamSettings": { "tlsSettings": { "certificates": $certificates } } }') JQs merge new_inbound "$merge" if [ "$tls_disable_system_root" = true ] then echo inquirer list_input "是否继续添加证书" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi fi done elif [ "$security" == "xtls" ] then V2raySetTlsServerName if [ -n "$tls_server_name" ] then new_inbound=$( $JQ_FILE --arg serverName "$tls_server_name" \ '. * { "streamSettings": { "xtlsSettings": { "serverName": $serverName } } }' <<< "$new_inbound") fi V2raySetTlsAlpn V2raySetTlsDisableSystemRoot V2raySetTlsMinVersion V2raySetTlsMaxVersion V2raySetTlsPreferServerCipherSuites V2raySetTlsCipherSuites V2raySetTlsRejectUnknownSni new_inbound=$( $JQ_FILE --argjson alpn "[$tls_alpn]" --arg minVersion "$tls_min_version" \ --arg maxVersion "$tls_max_version" --arg preferServerCipherSuites "$tls_prefer_server_cipher_suites" \ --arg cipherSuites "$tls_cipher_suites" --arg disableSystemRoot "$tls_disable_system_root" \ --arg rejectUnknownSni "$tls_reject_unknown_sni" \ '. * { "streamSettings": { "xtlsSettings": { "alpn": $alpn, "minVersion": $minVersion, "maxVersion": $maxVersion, "preferServerCipherSuites": $preferServerCipherSuites | test("true"), "cipherSuites": $cipherSuites, "disableSystemRoot": $disableSystemRoot | test("true"), "rejectUnknownSni": $rejectUnknownSni | test("true") } } }' <<< "$new_inbound") while true do if [ "$tls_disable_system_root" = false ] then echo inquirer list_input "是否继续添加证书" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi fi Println "$info 设置证书" V2raySetCertificates merge=$( $JQ_FILE -n --argjson certificates "[$certificate]" \ '{ "streamSettings": { "xtlsSettings": { "certificates": $certificates } } }') JQs merge new_inbound "$merge" if [ "$tls_disable_system_root" = true ] then echo inquirer list_input "是否继续添加证书" ny_options continue_yn if [ "$continue_yn" == "$i18n_no" ] then break fi fi done fi if [ "$protocol" == "vmess" ] then V2raySetDisableInsecureEncryption new_inbound=$( $JQ_FILE --arg disableInsecureEncryption "$disable_insecure_encryption" \ '. * { "settings": { "clients": [], "disableInsecureEncryption":$disableInsecureEncryption | test("true") } }' <<< "$new_inbound") V2raySetDetourTo if [ "$detour_to" != "$i18n_not_set" ] then V2raySetDetourDefault new_inbound=$( $JQ_FILE --arg level "$level" --arg alterId "$alter_id" --arg to "$detour_to" \ '. * { "settings": { "default": { "level": $level | tonumber, "alterId": $alterId | tonumber }, "detour": { "to": $to } } }' <<< "$new_inbound") fi elif [ "$protocol" == "vless" ] || [ "$protocol" == "trojan" ] then new_inbound=$( $JQ_FILE \ '. * { "settings": { "clients": [] } }' <<< "$new_inbound") if [ "$protocol" == "vless" ] then new_inbound=$( $JQ_FILE \ '. * { "settings": { "decryption":"none" } }' <<< "$new_inbound") fi if { [ "$security" == "tls" ] || [ "$security" == "xtls" ]; } && [ "$network" == "tcp" ] && [[ $tls_alpn == *"http/1.1"* ]] then V2raySetFallbacks if [ "$v2ray_fallbacks" != "[]" ] then new_inbound=$( $JQ_FILE --argjson fallbacks "$v2ray_fallbacks" \ '. * { "settings": { "fallbacks":$fallbacks } }' <<< "$new_inbound") fi fi elif [ "$protocol" == "http" ] then V2raySetTimeout V2raySetAllowTransparent V2raySetLevel new_inbound=$( $JQ_FILE --arg timeout "$timeout" --arg allowTransparent "$allow_transparent" \ --arg userLevel "$level" \ '. * { "settings": { "timeout": $timeout | tonumber, "accounts": [], "allowTransparent": $allowTransparent | test("true"), "userLevel": $userLevel | tonumber } }' <<< "$new_inbound") elif [ "$protocol" == "socks" ] then V2raySetLevel V2raySetAuth V2raySetUdp new_inbound=$( $JQ_FILE --arg auth "$auth" --arg udp "$udp" \ --arg userLevel "$level" \ '. * { "settings": { "auth": $auth, "udp": $udp | test("true"), "userLevel": $userLevel | tonumber } }' <<< "$new_inbound") if [ "$udp" = true ] then V2raySetIp new_inbound=$( $JQ_FILE --arg ip "$ip" \ '. * { "settings": { "ip": $ip } }' <<< "$new_inbound") fi elif [ "$protocol" == "shadowsocks" ] then if [ "$v2ray_name" == "xray" ] && [[ $($V2CTL_FILE version | head -1 | cut -d' ' -f2) =~ ([^.]+).([^.]+).([^.]+) ]] && \ [ "${BASH_REMATCH[1]}" -ge 1 ] && [ "${BASH_REMATCH[2]}" -ge 2 ] && [ "${BASH_REMATCH[3]}" -ge 3 ] then V2raySetSettingsNetwork new_inbound=$( $JQ_FILE --arg network "$settings_network" \ '. * { "settings": { "clients": [], "network": $network } }' <<< "$new_inbound") else V2raySetEmail V2raySetShadowsocksMethod V2raySetPassword V2raySetLevel V2raySetSettingsNetwork new_inbound=$( $JQ_FILE --arg email "$email" --arg method "$shadowsocks_method" \ --arg password "$password" --arg level "$level" \ --arg network "$settings_network" \ '. * { "settings": { "email": $email, "method": $method, "password": $password, "level": $level | tonumber, "network": $network } }' <<< "$new_inbound") if [ "$v2ray_name" == "v2ray" ] then V2raySetIvCheck new_inbound=$( $JQ_FILE --arg ivCheck "$iv_check" \ '. * { "settings": { "ivCheck": $ivCheck | test("true") } }' <<< "$new_inbound") fi fi elif [ "$protocol" == "dokodemo-door" ] then echo inquirer list_input "是否用于 api 查询" ny_options yn_option if [ "$yn_option" == "$i18n_yes" ] then new_inbound=$( $JQ_FILE \ '. * { "settings": { "address": "127.0.0.1", } }' <<< "$new_inbound") else V2raySetSettingsNetwork V2raySetTimeout V2raySetLevel V2raySetFollowRedirect if [ "$follow_redirect" = true ] then new_inbound=$( $JQ_FILE --arg network "$settings_network" --arg timeout "$timeout" \ --arg followRedirect "$follow_redirect" --arg userLevel "$level" \ '. * { "settings": { "network": $network, "timeout": $timeout | tonumber, "followRedirect": $followRedirect | test("true"), "userLevel": $userLevel | tonumber } }' <<< "$new_inbound") else V2raySetAddress V2raySetAddressPort new_inbound=$( $JQ_FILE --arg address "$address" --arg port "$address_port" \ --arg network "$settings_network" --arg timeout "$timeout" \ --arg followRedirect "$follow_redirect" --arg userLevel "$level" \ '. * { "settings": { "address": $address, "port": $port | tonumber, "network": $network, "timeout": $timeout | tonumber, "followRedirect": $followRedirect | test("true"), "userLevel": $userLevel | tonumber } }' <<< "$new_inbound") fi fi fi if [ "$network" == "ws" ] then V2raySetAcceptProxyProtocol V2raySetPath V2raySetWsHeaders new_inbound=$( $JQ_FILE --arg acceptProxyProtocol "$accept_proxy_protocol" \ --arg path "$path" --argjson headers "{$ws_headers}" \ '. * { "streamSettings": { "wsSettings": { "acceptProxyProtocol": $acceptProxyProtocol | test("true"), "path": $path, "headers": $headers } } }' <<< "$new_inbound") if [ "$v2ray_name" == "v2ray" ] then V2raySetInboundMaxEarlyData if [ "$max_early_data" -gt 0 ] then V2raySetEarlyDataHeaderName if [ -n "$early_data_header_name" ] && [ "$early_data_header_name" != "Sec-WebSocket-Protocol" ] then use_browser_forwarding=false else V2raySetUseBrowserForwarding fi else early_data_header_name="" use_browser_forwarding=false fi new_inbound=$( $JQ_FILE --arg maxEarlyData "$max_early_data" \ --arg earlyDataHeaderName "$early_data_header_name" \ --arg useBrowserForwarding "$use_browser_forwarding" \ '. * { "streamSettings": { "wsSettings": { "maxEarlyData": $maxEarlyData | tonumber, "earlyDataHeaderName": $earlyDataHeaderName, "useBrowserForwarding": $useBrowserForwarding | test("true") } } }' <<< "$new_inbound") fi elif [ "$network" == "tcp" ] then V2raySetAcceptProxyProtocol V2raySetHeaderType new_inbound=$( $JQ_FILE --arg acceptProxyProtocol "$accept_proxy_protocol" \ --arg header_type "$header_type" --argjson request "$header_http_request" \ --argjson response "$header_http_response" \ '. * { "streamSettings": { "tcpSettings": { "acceptProxyProtocol": $acceptProxyProtocol | test("true"), "header": { "type": $header_type, "request": $request, "response": $response } } } }' <<< "$new_inbound") elif [ "$network" == "kcp" ] then V2raySetHeaderType V2raySetKcpMtu V2raySetKcpTti V2raySetKcpUplinkCapacity V2raySetKcpDownlinkCapacity V2raySetKcpCongestion V2raySetKcpReadBufferSize V2raySetKcpWriteBufferSize V2raySetKcpSeed new_inbound=$( $JQ_FILE --arg mtu "$kcp_mtu" --arg tti "$kcp_tti" \ --arg uplinkCapacity "$kcp_uplink_capacity" --arg downlinkCapacity "$kcp_downlink_capacity" \ --arg congestion "$kcp_congestion" --arg readBufferSize "$kcp_read_buffer_size" \ --arg writeBufferSize "$kcp_write_buffer_size" --arg header_type "$header_type" \ --arg seed "$kcp_seed" \ '. * { "streamSettings": { "kcpSettings": { "mtu": $mtu | tonumber, "tti": $tti | tonumber, "uplinkCapacity": $uplinkCapacity | tonumber, "downlinkCapacity": $downlinkCapacity | tonumber, "congestion": $congestion | test("true"), "readBufferSize": $readBufferSize | tonumber, "writeBufferSize": $writeBufferSize | tonumber, "header": { "type": $header_type }, "seed": $seed } } }' <<< "$new_inbound") elif [ "$network" == "http" ] then V2raySetHttpHost V2raySetPath V2raySetHttpHeaders new_inbound=$( $JQ_FILE --argjson host "[$http_host]" --arg path "$path" \ --argjson headers "{$http_headers}" \ '. * { "streamSettings": { "httpSettings": { "host": $host, "path": $path, "headers": $headers } } }' <<< "$new_inbound") if [ "$v2ray_name" == "v2ray" ] then V2raySetHttpMethod new_inbound=$( $JQ_FILE --arg method "$http_method" \ '. * { "streamSettings": { "httpSettings": { "method": $method } } }' <<< "$new_inbound") fi elif [ "$network" == "quic" ] then V2raySetQuicSecurity V2raySetQuicKey V2raySetHeaderType new_inbound=$( $JQ_FILE --arg security "$quic_security" \ --arg key "$quic_key" --arg header_type "$header_type" \ '. * { "streamSettings": { "quicSettings": { "security": $security, "key": $key, "header": { "type": $header_type } } } }' <<< "$new_inbound") elif [ "$network" == "domainsocket" ] then V2raySetDsPath V2raySetDsAbstract V2raySetDsPadding new_inbound=$( $JQ_FILE --arg path "$ds_path" \ --arg abstract "$ds_abstract" --arg padding "$ds_padding" \ '. * { "streamSettings": { "dsSettings": { "path": $ds_path, "abstract": $abstract | test("true"), "padding": $padding | test("true") } } }' <<< "$new_inbound") else V2raySetGrpcServiceName new_inbound=$( $JQ_FILE --arg grpcSettings "$grpc_service_name" \ '. * { "streamSettings": { "grpcSettings": { "serviceName": $serviceName } } }' <<< "$new_inbound") if [ "$v2ray_name" == "xray" ] then V2raySetGrpcMultiMode new_inbound=$( $JQ_FILE --arg multiMode "$grpc_multi_mode" \ '. * { "streamSettings": { "grpcSettings": { "multiMode": $multiMode | test("true") } } }' <<< "$new_inbound") fi fi jq_path='["inbounds"]' JQ add "$V2_CONFIG" "[$new_inbound]" Println "$info 入站 $tag 添加成功\n" } V2rayGetInbounds() { IFS=$'`\t' read -r map_listen map_port map_protocol map_settings_disable_insecure_encryption \ map_settings_decryption map_settings_timeout map_settings_allow_transparent map_settings_user_level \ map_settings_address map_settings_port map_settings_network map_settings_follow_redirect \ map_settings_default_level map_settings_default_alter_id map_settings_detour_to map_settings_auth \ map_settings_udp map_settings_ip map_settings_email map_settings_method map_settings_password \ map_settings_iv_check map_stream_network map_stream_security map_stream_tls_server_name map_stream_tls_alpn \ map_stream_tls_allow_insecure map_stream_tls_disable_system_root map_stream_tls_verify_client_certificate \ map_stream_tls_min_version map_stream_tls_max_version map_stream_tls_prefer_server_cipher_suites \ map_stream_tls_cipher_suites map_stream_tls_reject_unknown_sni map_stream_tls_certificates_ocsp_stapling \ map_stream_tls_certificates_one_time_loading map_stream_tls_certificates_usage map_stream_tls_certificates_certificate_file \ map_stream_tls_certificates_key_file map_stream_tls_certificates_certificate map_stream_tls_certificates_key \ map_stream_accept_proxy_protocol map_stream_header_type map_stream_header_request map_stream_header_response \ map_stream_kcp_mtu map_stream_kcp_tti map_stream_kcp_uplink_capacity map_stream_kcp_downlink_capacity \ map_stream_kcp_congestion map_stream_kcp_read_buffer_size map_stream_kcp_write_buffer_size \ map_stream_kcp_seed map_stream_path map_stream_ws_headers map_stream_ws_max_early_data \ map_stream_ws_use_browser_forwarding map_stream_ws_early_data_header_name map_stream_http_host \ map_stream_http_method map_stream_http_headers map_stream_quic_security map_stream_quic_key map_stream_ds_abstract \ map_stream_ds_padding map_stream_grpc_service_name map_stream_grpc_multi_mode \ map_stream_sockopt_tcp_fast_open map_stream_sockopt_tproxy map_stream_sockopt_tcp_keep_alive_interval \ map_sniffing_enabled map_sniffing_dest_override map_sniffing_domains_excluded \ map_sniffing_metadata_only map_allocate_strategy map_allocate_refresh map_allocate_concurrency \ map_tag < <($JQ_FILE -c -r '[ ([.inbounds[]|.listen|if . == "" // . == null then "0.0.0.0" else . end|. + "^"]|join("") + "`"), ([.inbounds[]|.port|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.protocol|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.disableInsecureEncryption // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.decryption // "none"|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.timeout // 300|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.allowTransparent // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.userLevel // .settings.level // ""|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.address|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.port // ""|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.network // "tcp"|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.follow_redirect // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.default.level // 0|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.default.alterId // 0|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.detour.to|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.auth // "noauth"|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.udp // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.ip // "127.0.0.1"|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.email|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.method // "none"|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.password|. + "^"]|join("") + "`"), ([.inbounds[]|.settings.ivCheck // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.network|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.security // "none"|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.serverName // .streamSettings.xtlsSettings.serverName|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.alpn // .streamSettings.xtlsSettings.alpn // []|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.allowInsecure // .streamSettings.xtlsSettings.allowInsecure // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.disableSystemRoot // .streamSettings.xtlsSettings.disableSystemRoot // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.verifyClientCertificate // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.minVersion // .streamSettings.xtlsSettings.minVersion|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.maxVersion // .streamSettings.xtlsSettings.maxVersion|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.preferServerCipherSuites // .streamSettings.xtlsSettings.preferServerCipherSuites // true|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.cipherSuites // .streamSettings.xtlsSettings.cipherSuites|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.rejectUnknownSni // .streamSettings.xtlsSettings.rejectUnknownSni // true|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.certificates // .streamSettings.xtlsSettings.certificates // []|[.[].ocspStapling // 3600|tostring|. + "|"]|join("")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.certificates // .streamSettings.xtlsSettings.certificates // []|[.[].oneTimeLoading // false|tostring|. + "|"]|join("")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.certificates // .streamSettings.xtlsSettings.certificates // []|[.[].usage|. + "|"]|join("")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.certificates // .streamSettings.xtlsSettings.certificates // []|[.[].certificateFile|. + "|"]|join("")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.certificates // .streamSettings.xtlsSettings.certificates // []|[.[].keyFile|. + "|"]|join("")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.certificates // .streamSettings.xtlsSettings.certificates // []|[.[].certificate // []|join(" ")]|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tlsSettings.certificates // .streamSettings.xtlsSettings.certificates // []|[.[].key // []|join(" ")]|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tcpSettings.acceptProxyProtocol // .streamSettings.wsSettings.acceptProxyProtocol // .streamSettings.sockopt.acceptProxyProtocol // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tcpSettings.header.type // .streamSettings.kcpSettings.header.type // .streamSettings.quicSettings.header.type // "none"|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tcpSettings.header.request // {}|to_entries| map("\(.key)=\(.value|(. | type) as $type|if ($type == "array") then (.|join("~")) elif ($type == "object") then (.|to_entries|map("\(.key)=\( (.value|(. | type) as $type2|if ($type2 == "array") then (.|join("~")) else . end))")|join("!")) else . end)")|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.tcpSettings.header.response // {}|to_entries| map("\(.key)=\(.value|(. | type) as $type|if ($type == "object") then (.|to_entries|map("\(.key)=\( (.value|(. | type) as $type2|if ($type2 == "array") then (.|join("~")) else . end))")|join("!")) else . end)")|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.mtu // 1350|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.tti // 20|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.uplinkCapacity // 5|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.downlinkCapacity // 20|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.congestion // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.readBufferSize // 2|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.writeBufferSize // 2|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.kcpSettings.seed|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.wsSettings.path // .streamSettings.httpSettings.path // .streamSettings.dsSettings.path|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.wsSettings.headers // {}|to_entries|map("\(.key)=\(.value)")|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.wsSettings.maxEarlyData // 0|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.wsSettings.useBrowserForwarding // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.wsSettings.earlyDataHeaderName|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.httpSettings.host // []|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.httpSettings.method // "method"|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.httpSettings.headers // {}|to_entries| map("\(.key)=\(.value|if (.|type == "array") then (.|join("~")) else . end)")|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.quicSettings.security // "none"|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.quicSettings.key|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.dsSettings.abstract // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.dsSettings.padding // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.grpcSettings.serviceName|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.grpcSettings.multiMode // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.sockopt.tcpFastOpen // ""|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.sockopt.tproxy // "off"|. + "^"]|join("") + "`"), ([.inbounds[]|.streamSettings.sockopt.tcpKeepAliveInterval // 0|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.sniffing.enabled // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.sniffing.destOverride // []|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.sniffing.domainsExcluded // []|join("|")|. + "^"]|join("") + "`"), ([.inbounds[]|.sniffing.metadataOnly // false|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.allocate.strategy // "always"|. + "^"]|join("") + "`"), ([.inbounds[]|.allocate.refresh // 5|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.allocate.concurrency // 3|tostring|. + "^"]|join("") + "`"), ([.inbounds[]|.tag|. + "^"]|join("") + "`") ]|@tsv' "$V2_CONFIG") if [ -z "$map_protocol" ] then inbounds_count=0 return 0 fi IFS="^" read -r -a inbounds_protocol <<< "$map_protocol" inbounds_count=${#inbounds_protocol[@]} if_null="" for((inbounds_i=0;inbounds_i ${dns_hosts_address[dns_host_index]//,/, } 删除成功\n" else V2rayListDns [ "$dns_servers_count" -eq 0 ] && exit 1 echo ExitOnText "输入 DNS 服务器序号: " dns_server_num dns_server_index=$((dns_server_num-1)) jq_path='["dns","servers",'"$dns_server_index"']' JQ delete "$V2_CONFIG" if [[ ${dns_servers[dns_server_index]} =~ ^(.+)\|(.*)\|(.*)\|(.*)$ ]] then Println "$info DNS 服务器: ${BASH_REMATCH[1]}:${BASH_REMATCH[2]:-53} 删除成功\n" fi fi } V2rayGetStats() { IFS=$'`\t' read -r api_tag m_api_services < <($JQ_FILE -r '[(.api.tag + "`"), (.api.services // []|join("|") + "`")]|@tsv' "$V2_CONFIG") if [ -z "$api_tag" ] || [ -z "$m_api_services" ] then json=true jq_path='["stats"]' JQ update "$V2_CONFIG" "{}" api='{ "tag": "api", "services": [ "StatsService" ] }' json=true jq_path='["api"]' JQ update "$V2_CONFIG" "$api" if [ -z "$api_tag" ] then api_tag="api" fi else IFS="|" read -r -a api_services <<< "$m_api_services" stats_service_found=0 for api_service in "${api_services[@]}" do if [ "$api_service" == "StatsService" ] then stats_service_found=1 break fi done if [ "$stats_service_found" -eq 0 ] then jq_path='["api","services"]' JQ add "$V2_CONFIG" ["StatsService"] fi fi V2rayGetRouting if [ "$routing_rules_count" -eq 0 ] then Println "$error 请先添加一个路由: 入站协议为 dokodemo-door (需要创建此协议的入站), 出站标签是 $api_tag (不用创建出站)\n" exit 1 else routing_rule_found=0 for((i=0;i>>'"$2"'>>>traffic>>>'"$3"'" reset: false' 2> /dev/null) return 0 } V2rayListStats() { V2rayGetStats stats_list="" for((i=0;i /dev/null 2>&1 then for f in "$nginx_prefix/conf/sites_available/"* do domain=${f##*/} domain=${domain%.conf} if [ -e "$nginx_prefix/conf/sites_enabled/$domain.conf" ] then v2ray_status_text="${green}开启${normal}" else v2ray_status_text="${red}关闭${normal}" fi if [[ $domain =~ ^([a-zA-Z0-9](([a-zA-Z0-9-]){0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]] || grep -q "proxy_pass http://127.0.0.1:${inbounds_port[inbounds_index]}" < "$nginx_prefix/conf/sites_available/$domain.conf" then server_found=0 server_flag=0 while IFS= read -r line do if [[ $line == *"server {"* ]] then server_found=1 server_ports="" is_inbound=0 fi if [[ $server_found -eq 1 ]] && [[ $line == *"{"* ]] then server_flag=$((server_flag+1)) fi if [[ $server_found -eq 1 ]] && [[ $line == *"}"* ]] then server_flag=$((server_flag-1)) if [[ $server_flag -eq 0 ]] then server_found=0 if [[ $is_inbound -eq 1 ]] then v2ray_domains_inbound_count=$((v2ray_domains_inbound_count+1)) v2ray_domains_inbound+=("$domain") v2ray_domains_inbound_https_port+=("$server_ports") v2ray_domains_inbound_list="$v2ray_domains_inbound_list${green}$v2ray_domains_inbound_count.${normal}${indent_6}域名: ${green}$domain${normal} nginx 端口: ${green}$server_ports${normal} nginx 路径: ${green}${inbounds_stream_path[inbounds_index]}${normal} 状态: $v2ray_status_text\n\n" fi fi fi if [[ $server_found -eq 1 ]] && [[ $line == *"listen "* ]] then line=${line#*listen } line=${line% ssl;*} lead=${line%%[^[:blank:]]*} line=${line#${lead}} [ -n "$server_ports" ] && server_ports="$server_ports, " server_ports="$server_ports${line:0:-1}" fi if [[ $server_found -eq 1 ]] && [[ $line == *"proxy_pass http://127.0.0.1:${inbounds_port[inbounds_index]}"* ]] then is_inbound=1 fi done < "$nginx_prefix/conf/sites_available/$domain.conf" else continue fi done fi Println "绑定的${green}域名列表:${normal}\n\n${v2ray_domains_inbound_list:-无}\n" } V2rayDomainUpdateCrt() { local domain=$1 if [ "$ca_server" == "letsencrypt" ] && [ -f /etc/systemd/system/mmproxy-acme.service ] && [[ $(systemctl is-active mmproxy-acme) == "active" ]] then if [ -z "${tls_port:-}" ] then tls_port=$(grep ^ExecStart= < /etc/systemd/system/mmproxy-acme.service) if [[ $tls_port =~ -4\ 127.0.0.1:([^ ]+) ]] then tls_port=${BASH_REMATCH[1]} else tls_port=${tls_port#*-4 } tls_port=${tls_port#*:} tls_port=${tls_port%% *} fi fi ~/.acme.sh/acme.sh --force --issue --alpn --tlsport "$tls_port" -d "$domain" --standalone -k ec-256 --server "$ca_server" > /dev/null ~/.acme.sh/acme.sh --force --installcert -d "$domain" --fullchainpath "/usr/local/share/$v2ray_name/$domain.crt" --keypath "/usr/local/share/$v2ray_name/$domain.key" --ecc > /dev/null else stopped=0 if [ -d "$nginx_prefix" ] then if [[ $(systemctl is-active $nginx_name) == "active" ]] then if [[ $(echo $SSH_CONNECTION | cut -d' ' -f3) == "127.0.0.1" ]] then Println "$error 请使用非 $nginx_name 监听端口连接 ssh 后重试\n" exit 1 fi systemctl stop $nginx_name stopped=1 fi sleep 1 fi ~/.acme.sh/acme.sh --force --issue -d "$domain" --standalone -k ec-256 --server "$ca_server" > /dev/null ~/.acme.sh/acme.sh --force --installcert -d "$domain" --fullchainpath "/usr/local/share/$v2ray_name/$domain.crt" --keypath "/usr/local/share/$v2ray_name/$domain.key" --ecc > /dev/null [ "$stopped" -eq 1 ] && systemctl start $nginx_name fi if [ -e "/usr/local/nginx/conf/sites_crt/$domain.crt" ] then cp -f "/usr/local/share/$v2ray_name/$domain.crt" "/usr/local/nginx/conf/sites_crt/$domain.crt" cp -f "/usr/local/share/$v2ray_name/$domain.key" "/usr/local/nginx/conf/sites_crt/$domain.key" fi if [ -e "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" ] then cp -f "/usr/local/share/$v2ray_name/$domain.crt" "/usr/local/openresty/nginx/conf/sites_crt/$domain.crt" cp -f "/usr/local/share/$v2ray_name/$domain.key" "/usr/local/openresty/nginx/conf/sites_crt/$domain.key" fi return 0 } V2rayNginxListDomains() { v2ray_nginx_domains_list="" v2ray_nginx_domains_count=0 v2ray_nginx_domains=() if ls -A "$nginx_prefix/conf/sites_available/"* > /dev/null 2>&1 then for f in "$nginx_prefix/conf/sites_available/"* do domain=${f##*/} domain=${domain%.conf} v2ray_nginx_domains_count=$((v2ray_nginx_domains_count+1)) v2ray_nginx_domains+=("$domain") if [ -e "$nginx_prefix/conf/sites_enabled/$domain.conf" ] then v2ray_nginx_domain_status_text="${green} [开启] ${normal}" else v2ray_nginx_domain_status_text="${red} [关闭] ${normal}" fi v2ray_nginx_domains_list="$v2ray_nginx_domains_list ${green}$v2ray_nginx_domains_count.${normal}${indent_6}$domain $v2ray_nginx_domain_status_text\n\n" done fi Println "${green}域名列表:${normal}\n\n${v2ray_nginx_domains_list:-无}" } V2rayNginxSelectDomain() { echo "`gettext \"输入序号\"`" while read -p "$i18n_default_cancel" v2ray_nginx_domains_index do case "$v2ray_nginx_domains_index" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$v2ray_nginx_domains_index" -gt 0 ] && [ "$v2ray_nginx_domains_index" -le "$v2ray_nginx_domains_count" ] then v2ray_nginx_domains_index=$((v2ray_nginx_domains_index-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done } V2rayNginxSelectDomainServer() { echo "`gettext \"输入序号\"`" while read -p "$i18n_default_cancel" v2ray_nginx_domain_servers_num do case "$v2ray_nginx_domain_servers_num" in "") Println "$i18n_canceled...\n" exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$v2ray_nginx_domain_servers_num" -gt 0 ] && [ "$v2ray_nginx_domain_servers_num" -le "$v2ray_nginx_domain_servers_count" ] then v2ray_nginx_domain_servers_index=$((v2ray_nginx_domain_servers_num-1)) level_2_add_indices=( "${v2ray_nginx_domain_servers_indices[v2ray_nginx_domain_servers_index]}" ) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done } V2rayNginxDomainServerUpdateCrt() { v2ray_nginx_domain_server_name=${v2ray_nginx_domain_servers_name[v2ray_nginx_domain_servers_index]} if [[ $v2ray_nginx_domain_server_name =~ , ]] then IFS="," read -r -a domains <<< "$v2ray_nginx_domain_server_name" echo inquirer checkbox_input "选择域名: " domains domains_selected for domain in "${domains_selected[@]}" do NginxDomainUpdateCrt "$domain" cp -f "$nginx_prefix/conf/sites_crt/$domain.crt" "/usr/local/share/$v2ray_name/$domain.crt" cp -f "$nginx_prefix/conf/sites_crt/$domain.key" "/usr/local/share/$v2ray_name/$domain.key" chown $v2ray_name:$v2ray_name /usr/local/share/$v2ray_name/* done return 0 fi NginxDomainUpdateCrt "$v2ray_nginx_domain_server_name" cp -f "$nginx_prefix/conf/sites_crt/$v2ray_nginx_domain_server_name.crt" "/usr/local/share/$v2ray_name/$v2ray_nginx_domain_server_name.crt" cp -f "$nginx_prefix/conf/sites_crt/$v2ray_nginx_domain_server_name.key" "/usr/local/share/$v2ray_name/$v2ray_nginx_domain_server_name.key" chown $v2ray_name:$v2ray_name /usr/local/share/$v2ray_name/* } V2rayNginxDomainServerAddProxy() { V2rayListInbounds nginx V2raySelectInbound if [ "${inbounds_stream_network[inbounds_index]}" == "domainsocket" ] then Println "$error 不能使用此入站\n" exit 1 fi if [ -z "${inbounds_stream_path[inbounds_index]}" ] then Println "$error 此入站没有路径\n" exit 1 fi proxy_path=${inbounds_stream_path[inbounds_index]} proxy_port=${inbounds_port[inbounds_index]} if [ "${inbounds_stream_network[inbounds_index]}" == "ws" ] then new_proxy=' {"directive":"location","args":["=","'"$proxy_path"'"],"block":[ {"directive":"proxy_redirect","args":["off"]}, {"directive":"proxy_pass","args":["http://127.0.0.1:'"$proxy_port"'"]}, {"directive":"proxy_http_version","args":["1.1"]}, {"directive":"proxy_set_header","args":["Upgrade","$http_upgrade"]}, {"directive":"proxy_set_header","args":["Connection","upgrade"]} ]}' else new_proxy=' {"directive":"location","args":["=","'"$proxy_path"'"],"block":[ {"directive":"proxy_redirect","args":["off"]}, {"directive":"proxy_pass","args":["http://127.0.0.1:'"$proxy_port"'"]}, {"directive":"proxy_http_version","args":["1.1"]} ]}' fi jq_path='["config",0,"parsed",0,"block",'"$v2ray_nginx_domain_servers_index"',"block"]' JQs add parse_out "[$new_proxy]" NginxBuildConf parse_out Println "$info 代理添加成功\n" } V2rayNginxSelectDomainServerProxy() { echo "`gettext \"输入序号\"`" while read -p "$i18n_default_cancel" v2ray_nginx_domain_server_proxies_num do case "$v2ray_nginx_domain_server_proxies_num" in "") Println "$i18n_canceled...\n" exit 1 ;; $v2ray_nginx_domain_server_update_crt_number) AcmeCheck V2rayNginxDomainServerUpdateCrt exit 0 ;; $v2ray_nginx_domain_server_add_proxy_number) V2rayNginxDomainServerAddProxy exit 0 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$v2ray_nginx_domain_server_proxies_num" -gt 0 ] && [ "$v2ray_nginx_domain_server_proxies_num" -le "$v2ray_nginx_domain_server_proxies_count" ] then v2ray_nginx_domain_server_proxies_index=$((v2ray_nginx_domain_server_proxies_num-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done } V2rayNginxListDomain() { if [ "$v2ray_nginx_domains_count" -eq 0 ] then Println "$error 请先使用 $nginx_name 管理面板添加域名\n" exit 1 fi level_1_add_indices=( 0 ) V2rayNginxSelectDomain NginxParseConfig ${v2ray_nginx_domains[v2ray_nginx_domains_index]} NginxGetConfig if [ "$level_3_d1_count" -eq 0 ] then Println "$error 请先添加 ${v2ray_nginx_domains[v2ray_nginx_domains_index]} 配置\n" exit 1 fi v2ray_nginx_domain_servers_list="" v2ray_nginx_domain_servers_count=0 v2ray_nginx_domain_servers_indices=() v2ray_nginx_domain_servers_name=() v2ray_nginx_domain_servers_proxy=() level_1_index=0 level_2_directive_d1=${level_2_directive_arr[level_1_index]} level_3_directive_d1=${level_3_directive_arr[level_1_index]} level_3_args_d1=${level_3_args_arr[level_1_index]} IFS="${delimiters[1]}" read -r -a level_2_directive_d1_arr <<< "$level_2_directive_d1${delimiters[1]}" IFS="${delimiters[2]}" read -r -a level_3_directive_d1_arr <<< "$level_3_directive_d1${delimiters[2]}" IFS="${delimiters[2]}" read -r -a level_3_args_d1_arr <<< "$level_3_args_d1${delimiters[2]}" if [ "$level_4_d1_count" -gt 0 ] then level_4_directive_d1=${level_4_directive_arr[level_1_index]} level_4_args_d1=${level_4_args_arr[level_1_index]} IFS="${delimiters[3]}" read -r -a level_4_directive_d1_arr <<< "$level_4_directive_d1${delimiters[3]}" IFS="${delimiters[3]}" read -r -a level_4_args_d1_arr <<< "$level_4_args_d1${delimiters[3]}" fi for((level_2_index=0;level_2_index<${#level_2_directive_d1_arr[@]};level_2_index++)); do if [ "${level_2_directive_d1_arr[level_2_index]}" == "server" ] then level_3_directive_d2=${level_3_directive_d1_arr[level_2_index]} level_3_args_d2=${level_3_args_d1_arr[level_2_index]} IFS="${delimiters[1]}" read -r -a level_3_directive_d2_arr <<< "$level_3_directive_d2${delimiters[1]}" IFS="${delimiters[1]}" read -r -a level_3_args_d2_arr <<< "$level_3_args_d2${delimiters[1]}" if [ "$level_4_d1_count" -gt 0 ] && [ -n "${level_4_directive_d1_arr[level_2_index]}" ] then level_4_directive_d2=${level_4_directive_d1_arr[level_2_index]} level_4_args_d2=${level_4_args_d1_arr[level_2_index]} IFS="${delimiters[2]}" read -r -a level_4_directive_d2_arr <<< "$level_4_directive_d2${delimiters[2]}" IFS="${delimiters[2]}" read -r -a level_4_args_d2_arr <<< "$level_4_args_d2${delimiters[2]}" fi v2ray_nginx_domain_servers_count=$((v2ray_nginx_domain_servers_count+1)) v2ray_nginx_domain_servers_indices+=("$level_2_index") v2ray_nginx_domain_server_listen_list="" v2ray_nginx_domain_server_name_list="" v2ray_nginx_domain_server_proxies="" v2ray_nginx_domain_server_proxies_list="" for((level_3_index=0;level_3_index<${#level_3_directive_d2_arr[@]};level_3_index++)); do level_3_directive=${level_3_directive_d2_arr[level_3_index]} level_3_args=${level_3_args_d2_arr[level_3_index]} if [ "$level_3_directive" == "listen" ] then [ -n "$v2ray_nginx_domain_server_listen_list" ] && v2ray_nginx_domain_server_listen_list="$v2ray_nginx_domain_server_listen_list, " v2ray_nginx_domain_server_listen_list="$v2ray_nginx_domain_server_listen_list${level_3_args//${delimiters[0]}/ }" elif [ "$level_3_directive" == "server_name" ] then [ -n "$v2ray_nginx_domain_server_name_list" ] && v2ray_nginx_domain_server_name_list="$v2ray_nginx_domain_server_name_list, " v2ray_nginx_domain_server_name_list="$v2ray_nginx_domain_server_name_list${level_3_args//${delimiters[0]}/, }" elif [ "$level_3_directive" == "location" ] then if [ "$level_4_d1_count" -gt 0 ] && [ -n "${level_4_directive_d1_arr[level_2_index]}" ] && [ -n "${level_4_directive_d2_arr[level_3_index]}" ] then level_4_directive_d3=${level_4_directive_d2_arr[level_3_index]} level_4_args_d3=${level_4_args_d2_arr[level_3_index]} IFS="${delimiters[1]}" read -r -a level_4_directive_d3_arr <<< "$level_4_directive_d3${delimiters[1]}" IFS="${delimiters[1]}" read -r -a level_4_args_d3_arr <<< "$level_4_args_d3${delimiters[1]}" if [[ ${level_3_args} =~ ^=${delimiters[0]}(.+) ]] then v2ray_nginx_domain_server_proxy_path=${BASH_REMATCH[1]} for((level_4_index=0;level_4_index<${#level_4_directive_d3_arr[@]};level_4_index++)); do if [ "${level_4_directive_d3_arr[level_4_index]}" == "proxy_pass" ] then if [[ ${level_4_args_d3_arr[level_4_index]} =~ ^http://127.0.0.1:(.+) ]] then v2ray_nginx_domain_server_proxies="$v2ray_nginx_domain_server_proxies$level_3_index|$level_4_index|${BASH_REMATCH[1]}|$v2ray_nginx_domain_server_proxy_path " v2ray_nginx_domain_server_proxies_list="$v2ray_nginx_domain_server_proxies_list${indent_6}路径: ${green}$v2ray_nginx_domain_server_proxy_path${normal} => $v2ray_name 端口: ${green}${BASH_REMATCH[1]}${normal}\n" fi break fi done fi fi fi done if [ -z "$v2ray_nginx_domain_server_proxies_list" ] then v2ray_nginx_domain_server_proxies_list="${red}未配置${normal}" fi v2ray_nginx_domain_servers_name+=("${v2ray_nginx_domain_server_name_list//, /,}") v2ray_nginx_domain_servers_proxy+=("$v2ray_nginx_domain_server_proxies") v2ray_nginx_domain_servers_list="$v2ray_nginx_domain_servers_list $v2ray_nginx_domain_servers_count.${indent_6}域名: ${green}${v2ray_nginx_domain_server_name_list:-未设置}${normal}\n${indent_6}端口: ${green}${v2ray_nginx_domain_server_listen_list:-未设置}${normal}\n${indent_6}代理: $v2ray_nginx_domain_server_proxies_list\n\n" fi done Println "域名 ${green}${v2ray_nginx_domains[v2ray_nginx_domains_index]}${normal} 配置:\n\n$v2ray_nginx_domain_servers_list" } V2rayConfigDomain() { if [ -d "$nginx_prefix" ] then echo v2ray_config_domain_options=( "$v2ray_name" nginx openresty ) inquirer list_input "选择修改的配置" v2ray_config_domain_options v2ray_config_domain_option if [ "$v2ray_config_domain_option" != "$v2ray_name" ] then if [ ! -d "/usr/local/$v2ray_config_domain_option" ] then Println "$error 请先安装 $v2ray_config_domain_option\n" exit 1 fi if [ "$v2ray_config_domain_option" == "nginx" ] then nginx_prefix="/usr/local/nginx" nginx_name="nginx" nginx_ctl="nx" else nginx_prefix="/usr/local/openresty/nginx" nginx_name="openresty" nginx_ctl="or" fi NGINX_FILE="$nginx_prefix/sbin/nginx" V2rayNginxListDomains V2rayNginxListDomain V2rayNginxSelectDomainServer v2ray_nginx_domain_server_proxies_list="" v2ray_nginx_domain_server_proxies_count=0 v2ray_nginx_domain_server_proxy=${v2ray_nginx_domain_servers_proxy[v2ray_nginx_domain_servers_index]} if [ -n "$v2ray_nginx_domain_server_proxy" ] then v2ray_nginx_domain_server_location_indices=() v2ray_nginx_domain_server_proxy_indices=() v2ray_nginx_domain_server_proxies_port=() v2ray_nginx_domain_server_proxies_path=() v2ray_nginx_domain_server_proxies_list="" IFS=" " read -r -a v2ray_nginx_domain_server_proxies <<< "$v2ray_nginx_domain_server_proxy" for((i=0;i<${#v2ray_nginx_domain_server_proxies[@]};i++)); do if [[ ${v2ray_nginx_domain_server_proxies[i]} =~ ^([^|]+)\|([^|]+)\|([^|]+)\|(.+)$ ]] then v2ray_nginx_domain_server_location_indices+=("${BASH_REMATCH[1]}") v2ray_nginx_domain_server_proxy_indices+=("${BASH_REMATCH[2]}") v2ray_nginx_domain_server_proxies_port+=("${BASH_REMATCH[3]}") v2ray_nginx_domain_server_proxies_path+=("${BASH_REMATCH[4]}") v2ray_nginx_domain_server_proxies_list="$v2ray_nginx_domain_server_proxies_list $((i+1)).${indent_6}路径: ${green}${BASH_REMATCH[4]}${normal} => 端口: ${green}${BASH_REMATCH[3]}${normal}\n\n" fi done v2ray_nginx_domain_server_proxies_count=${#v2ray_nginx_domain_server_proxies_path[@]} fi v2ray_nginx_domain_server_update_crt_number=$((v2ray_nginx_domain_server_proxies_count+1)) v2ray_nginx_domain_server_add_proxy_number=$((v2ray_nginx_domain_server_proxies_count+2)) v2ray_nginx_domain_server_proxies_list="$v2ray_nginx_domain_server_proxies_list $v2ray_nginx_domain_server_update_crt_number.${indent_6}${green}更新证书${normal}\n\n" v2ray_nginx_domain_server_proxies_list="$v2ray_nginx_domain_server_proxies_list $v2ray_nginx_domain_server_add_proxy_number.${indent_6}${green}添加代理${normal}\n" Println "代理配置:\n\n$v2ray_nginx_domain_server_proxies_list" V2rayNginxSelectDomainServerProxy v2ray_nginx_domain_server_location_index=${v2ray_nginx_domain_server_location_indices[v2ray_nginx_domain_server_proxies_index]} v2ray_nginx_domain_server_proxy_index=${v2ray_nginx_domain_server_proxy_indices[v2ray_nginx_domain_server_proxies_index]} v2ray_nginx_domain_server_proxy_port=${v2ray_nginx_domain_server_proxies_port[v2ray_nginx_domain_server_proxies_index]} v2ray_nginx_domain_server_proxy_path=${v2ray_nginx_domain_server_proxies_path[v2ray_nginx_domain_server_proxies_index]} echo v2ray_nginx_domain_server_proxy_options=( '修改代理路径' '修改代理端口' '删除此代理' ) inquirer list_input_index "选择操作" v2ray_nginx_domain_server_proxy_options v2ray_nginx_domain_server_proxy_options_index if [ "$v2ray_nginx_domain_server_proxy_options_index" -eq 0 ] then echo inquirer text_input "输入新的代理路径: " new_path "$v2ray_nginx_domain_server_proxy_path" jq_path='["config",0,"parsed",0,"block",'"$v2ray_nginx_domain_servers_index"',"block",'"$v2ray_nginx_domain_server_location_index"',"args"]' JQs replace parse_out "$new_path" elif [ "$v2ray_nginx_domain_server_proxy_options_index" -eq 1 ] then echo new_proxy_port_options=( '输入新的代理端口' '浏览并选择端口' ) inquirer list_input_index "选择操作" new_proxy_port_options new_proxy_port_options_index if [ "$new_proxy_port_options_index" -eq 0 ] then echo inquirer text_input "输入新的代理端口: " new_proxy_port "$v2ray_nginx_domain_server_proxy_port" else V2rayListInbounds nginx V2raySelectInbound if [ "${inbounds_stream_network[inbounds_index]}" == "domainsocket" ] then Println "$error 选择错误\n" exit 1 fi new_proxy_port=${inbounds_port[inbounds_index]} fi jq_path='["config",0,"parsed",0,"block",'"$v2ray_nginx_domain_servers_index"',"block",'"$v2ray_nginx_domain_server_location_index"',"block",'"$v2ray_nginx_domain_server_proxy_index"',"args"]' JQs replace parse_out "$new_proxy_port" else jq_path='["config",0,"parsed",0,"block",'"$v2ray_nginx_domain_servers_index"',"block"]' JQs delete parse_out "$v2ray_nginx_domain_server_location_index" fi NginxBuildConf parse_out Println "$info 操作成功\n" exit 0 fi fi V2rayListInbounds direct V2raySelectInbound if [ -z "${inbounds_stream_tls_certificates_usage[inbounds_index]}" ] then Println "$error 没有证书\n" exit 1 fi certificates_list="${green}证书:${normal}\n${indent_6}" certificates_indices=() IFS="|" read -r -a usages <<< "${inbounds_stream_tls_certificates_usage[inbounds_index]}" IFS="|" read -r -a certificate_files <<< "${inbounds_stream_tls_certificates_certificate_file[inbounds_index]}" IFS="|" read -r -a key_files <<< "${inbounds_stream_tls_certificates_key_file[inbounds_index]}" IFS="|" read -r -a certificates <<< "${inbounds_stream_tls_certificates_certificate[inbounds_index]}" for((certificate_i=0;certificate_i<${#usages[@]};certificate_i++)); do if [ -z "${certificate_files[certificate_i]}" ] || [ -z "${key_files[certificate_i]}" ] then continue fi certificates_indices+=("$certificate_i") if [ "${usages[certificate_i]}" == "encipherment" ] then certificate_usage="$tls_name 认证和加密" elif [ "${usages[certificate_i]}" == "verify" ] then certificate_usage="验证远端 $tls_name" elif [ "${usages[certificate_i]}" == "issue" ] then certificate_usage="签发其它证书" else certificate_usage="验证客户端身份" fi if [ -n "${certificates:-}" ] && [ -n "${certificates[certificate_i]}" ] then certificates_list="$certificates_list${#certificates_indices[@]}.${indent_6}用途: ${green}$certificate_usage [自签名]${normal}\n" else certificates_list="$certificates_list${#certificates_indices[@]}.${indent_6}用途: ${green}$certificate_usage${normal}\n" fi certificates_list="${certificates_list}${indent_6}证书路径: ${green}${certificate_files[certificate_i]}${normal}\n" certificates_list="${certificates_list}${indent_6}密钥路径: ${green}${key_files[certificate_i]}${normal}\n\n" done if [ -z "$certificates_list" ] then Println "$error 没有可管理证书\n" exit 1 fi Println "$certificates_list" certificates_count=${#certificates_indices[@]} echo "选择证书" while read -p "$i18n_default_cancel" certificates_num do case "$certificates_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$certificates_num" -gt 0 ] && [ "$certificates_num" -le $certificates_count ] then certificates_index=${certificates_indices[certificates_num-1]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done certificate_file=${certificate_files[certificates_index]} tls_settings_name=$(tr '[:upper:]' '[:lower:]' <<< "$tls_name")"Settings" echo certificates_options=( '更新证书' '修改证书路径' '修改密钥路径' ) inquirer list_input_index "选择操作" certificates_options certificates_options_index if [ "$certificates_options_index" -eq 0 ] then if [ -n "${certificates:-}" ] && [ -n "${certificates[certificates_index]}" ] then if [ "$v2ray_name" == "xray" ] then crt=$($V2CTL_FILE tls cert) elif [ "$usage" == "encipherment" ] then echo inquirer list_input "是否是 CA 证书" yn_options ca_yn if [ "$ca_yn" == "$i18n_yes" ] then crt=$($V2CTL_FILE cert -ca) else crt=$($V2CTL_FILE cert) fi else crt=$($V2CTL_FILE cert -ca) fi certificate=$($JQ_FILE "{\"usage\":\"${usages[certificates_index]}\"} * ." <<< "$crt") json=true jq_path='["inbounds",'"$inbounds_index"',"streamSettings","'"$tls_settings_name"'","certificates",'"$certificates_index"']' JQ update "$V2_CONFIG" "$certificate" Println "$info 证书更新成功\n" else if [ -n "${inbounds_stream_tls_server_name[inbounds_index]}" ] then certificate_name=${inbounds_stream_tls_server_name[inbounds_index]} else certificate_name=${certificate_file##*/} certificate_name=${certificate_name%.*} fi echo inquirer text_input "请输入证书域名: " certificate_domain "$certificate_name" AcmeCheck Println "$info 更新 $certificate_domain 证书..." V2rayDomainUpdateCrt "$certificate_domain" jq_path='["inbounds",'"$inbounds_index"',"streamSettings","'"$tls_settings_name"'","certificates",'"$certificates_index"',"certificateFile"]' JQ update "$V2_CONFIG" "/usr/local/share/$v2ray_name/$certificate_domain.crt" jq_path='["inbounds",'"$inbounds_index"',"streamSettings","'"$tls_settings_name"'","certificates",'"$certificates_index"',"keyFile"]' JQ update "$V2_CONFIG" "/usr/local/share/$v2ray_name/$certificate_domain.key" Println "$info $certificate_domain 证书更新成功\n" fi elif [ "$certificates_options_index" -eq 1 ] then V2raySetCertificateFile jq_path='["inbounds",'"$inbounds_index"',"streamSettings","'"$tls_settings_name"'","certificates",'"$certificates_index"',"certificateFile"]' JQ update "$V2_CONFIG" "$certificate_file" Println "$info 证书路径修改成功\n" else V2raySetKeyFile jq_path='["inbounds",'"$inbounds_index"',"streamSettings","'"$tls_settings_name"'","certificates",'"$certificates_index"',"keyFile"]' JQ update "$V2_CONFIG" "$key_file" Println "$info 密钥路径修改成功\n" fi } TrojanInstall() { if [ -s "$TR_CONFIG" ] then Println "$error $trojan_name 已存在...\n" ExitOnList n "`gettext \"是否覆盖原安装\"`" fi DepsCheck JQInstall if ! grep -q "$trojan_name:" < "/etc/passwd" then if grep -q '\--group ' < <(adduser --help) then adduser $trojan_name --system --group --no-create-home > /dev/null else adduser $trojan_name --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin $trojan_name fi Println "$info 安装 $trojan_name..." { curl -s -m 10 "$TR_LINK" || curl -s -m 30 "$TR_LINK_FALLBACK"; } \ | sed "s+nobody+$trojan_name+g" \ | sed "s+ 'sha1'++g" \ | sed "s+ 'sha256'++g" \ | sed "s+ 'sha512'++g" \ | sed "s+https://api.github.com/repos/p4gefau1t/trojan-go/releases/latest+$FFMPEG_MIRROR_LINK/$trojan_name.json+g" \ | sed "s+https://github.com/p4gefau1t/trojan-go/releases/download+$FFMPEG_MIRROR_LINK/$trojan_name+g" | bash TrojanConfigInstall systemctl daemon-reload systemctl enable $trojan_name systemctl start $trojan_name Println "$info $trojan_name 安装成功\n" } CloudflareSetHostKey() { Println "请输入 CFP host key" read -p "$i18n_default_cancel" cf_host_key [ -z "$cf_host_key" ] && Println "$i18n_canceled...\n" && exit 1 Println "$info 稍等..." IFS=" " read -r result msg < <(curl -s -Lm 50 https://api.cloudflare.com/host-gw.html \ -d 'act=zone_list' \ -d "host_key=$cf_host_key" \ -d 'limit=1' \ -d 'offset=0' \ -d 'zone_status=ALL' \ -d 'sub_status=ALL' \ | $JQ_FILE '[.result,.msg]|join(" ")' ) || true result=${result#\"} msg=${msg%\"} if [ -z "$result" ] || [ "$result" == "error" ] then Println "$error ${msg:-超时, 请重试}\n" && exit 1 fi Println " CFP: ${green} $cf_host_key ${normal}\n" } CloudflareSetHostName() { Println "请输入 CFP 邮箱或名称, 便于区分 host key" read -p "$i18n_default_cancel" cf_host_name [ -z "$cf_host_name" ] && Println "$i18n_canceled...\n" && exit 1 Println " CFP 邮箱或名称: ${green} $cf_host_name ${normal}\n" } CloudflareAddHost() { CloudflareSetHostKey CloudflareSetHostName if [ ! -s "$CF_CONFIG" ] then printf '{"%s":[],"%s":[]}' "users" "hosts" > "$CF_CONFIG" fi new_host=$( $JQ_FILE -n --arg name "$cf_host_name" --arg key "$cf_host_key" \ '{ name: $name, key: $key, free: 0, zones: [] }' ) jq_path='["hosts"]' JQ add "$CF_CONFIG" "[$new_host]" Println "$info CFP 添加成功\n" } CloudflareSetUserEmail() { Println "请输入用户邮箱" while read -p "$i18n_default_cancel" cf_user_email do [ -z "$cf_user_email" ] && Println "$i18n_canceled...\n" && exit 1 if [[ $cf_user_email =~ ^[A-Za-z0-9]([a-zA-Z0-9_\.\-]*)@([A-Za-z0-9]+)([a-zA-Z0-9\.\-]*)\.([A-Za-z]{2,})$ ]] then break else Println "$error 邮箱格式错误, 请重新输入\n" fi done Println " 用户邮箱: ${green} $cf_user_email ${normal}\n" } CloudflareSetUserPass() { Println "输入用户密码" while read -p "(默认: 随机): " cf_user_pass do [ -z "$cf_user_pass" ] && cf_user_pass=$(RandStr) if [[ ${#cf_user_pass} -ge 8 ]] then break else Println "$error 账号密码至少 8 位\n" fi done Println " 用户密码: ${green} $cf_user_pass ${normal}\n" } CloudflareSetUserToken() { Println "$tip 需要 workers 和 zone(区域) 编辑权限, 以及 zone(区域) 的 Analytics 读取权限" inquirer text_input "请输入用户 Token: " cf_user_token "$i18n_not_set" if [ "$cf_user_token" == "$i18n_not_set" ] then cf_user_token="" else if [[ $(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \ -H "Authorization: Bearer $cf_user_token" \ -H "Content-Type:application/json" | $JQ_FILE -r '.success') = false ]] then Println "$error Token 验证失败\n" exit 1 fi fi } CloudflareSetUserKey() { echo inquirer text_input "请输入用户 Global API KEY: " cf_user_api_key "$i18n_not_set" if [ "$cf_user_api_key" == "$i18n_not_set" ] then cf_user_api_key="" fi } CloudflareGetHosts() { cf_hosts_list="" cf_hosts_count=0 cf_hosts_name=() cf_hosts_key=() cf_hosts_zones_count=() cf_hosts_zone_name=() cf_hosts_zone_resolve_to=() cf_hosts_zone_user_email=() cf_hosts_zone_user_unique_id=() cf_hosts_zone_always_use_https=() cf_hosts_zone_ssl=() cf_hosts_zone_subdomains=() while IFS="^" read -r name key zones_count zone_name zone_resolve_to zone_user_email zone_user_unique_id zone_always_use_https zone_ssl zone_subdomains do cf_hosts_count=$((cf_hosts_count+1)) name=${name#\"} cf_hosts_name+=("$name") cf_hosts_key+=("$key") cf_hosts_zones_count+=("$zones_count") cf_hosts_zone_name+=("$zone_name") cf_hosts_zone_resolve_to+=("$zone_resolve_to") cf_hosts_zone_user_email+=("$zone_user_email") zone_user_unique_id=${zone_user_unique_id%\"} cf_hosts_zone_user_unique_id+=("$zone_user_unique_id") cf_hosts_zone_always_use_https+=("$zone_always_use_https") cf_hosts_zone_ssl+=("$zone_ssl") zone_subdomains=${zone_subdomains%\"} cf_hosts_zone_subdomains+=("$zone_subdomains") cf_hosts_list="$cf_hosts_list ${green}$cf_hosts_count.${normal}${indent_6}CFP: ${green}$name${normal} host key: ${green}$key${normal} 域名数: ${green}$zones_count${normal}\n\n" done < <($JQ_FILE '.hosts[]|[.name,.key,(.zones|length),([.zones[].name]|join("|")),([.zones[].resolve_to]|join("|")),([.zones[].user_email]|join("|")),([.zones[].user_unique_id]|join("|")),([.zones[].always_use_https]|join("|")),([.zones[].ssl]|join("|")),([.zones[].subdomains]|join("|"))]|join("^")' "$CF_CONFIG") return 0 } CloudflareListHosts() { if [ ! -s "$CF_CONFIG" ] then Println "$error 请先添加 CFP\n" && exit 1 fi CloudflareGetHosts if [ "$cf_hosts_count" -gt 0 ] then Println "$cf_hosts_list" else Println "$error 请先添加 CFP\n" && exit 1 fi } CloudflareListHost() { CloudflareListHosts } CloudflareSetZoneResolve() { Println "请输入 CNAME 经 cloudflare 中转后默认解析到的地址, 比如 resolve-to-cloudflare.example.com" echo -e "$tip 此地址应指向源站\n" read -p "$i18n_default_cancel" cf_zone_resolve_to [ -z "$cf_zone_resolve_to" ] && Println "$i18n_canceled...\n" && exit 1 Println " 默认解析地址: ${green} $cf_zone_resolve_to ${normal}\n" } CloudflareSetZoneAlwaysUseHttps() { echo if [[ ${cf_zone_always_use_https:-} == "on" ]] then inquirer list_input "始终使用 https 访问域名, 开启后客户端和 cloudflare 之间连接始终为 https" yn_options cf_zone_always_use_https_yn else inquirer list_input "始终使用 https 访问域名, 开启后客户端和 cloudflare 之间连接始终为 https" ny_options cf_zone_always_use_https_yn fi if [[ $cf_zone_always_use_https_yn == "$i18n_yes" ]] then cf_zone_always_use_https='on' else cf_zone_always_use_https='off' fi Println " 始终使用 https: ${green} $cf_zone_always_use_https ${normal}\n" } CloudflareSetZoneSsl() { Println "选择域名 ${green}$cf_zone_name${normal} SSL 设置 ${green}1.${normal} off ( 客户端 <= http => cloudflare <= http => 源站) ${green}2.${normal} flexible ( 客户端 <= https => cloudflare <= http => 源站) ${green}3.${normal} full ( 客户端 <= https => cloudflare <= https => 源站[ SSL证书/自定义证书 ]) ${green}4.${normal} strict ( 客户端 <= https => cloudflare <= https => 源站[ CA SSL证书 ]) " case ${cf_zone_ssl:-} in "off") ssl_num=1 ;; "flexible"|"") ssl_num=2 ;; "full") ssl_num=3 ;; "strict") ssl_num=4 ;; esac while read -p "(默认: $ssl_num): " cf_zone_ssl_num do case $cf_zone_ssl_num in ""|2) cf_zone_ssl='flexible' break ;; 1) cf_zone_ssl='off' break ;; 3) cf_zone_ssl='full' break ;; 4) cf_zone_ssl='strict' break ;; *) Println "$error $i18n_input_correct_no\n" ;; esac done Println " SSL 设置: ${green} $cf_zone_ssl ${normal}\n" } CloudflareGetUsers() { cf_users_list="" cf_users_count=0 cf_users_email=() cf_users_pass=() cf_users_token=() cf_users_api_key=() while IFS="^" read -r email pass token key do cf_users_count=$((cf_users_count+1)) email=${email#\"} cf_users_email+=("$email") cf_users_pass+=("$pass") cf_users_token+=("$token") key=${key%\"} cf_users_api_key+=("$key") cf_users_list="$cf_users_list ${green}$cf_users_count.${normal}${indent_6}邮箱: ${green}$email${normal} 密码: ${green}$pass${normal}\n${indent_6}Token: ${green}${token:-无}${normal}\n${indent_6}Key: ${green}${key:-无}${normal}\n\n" done < <($JQ_FILE '.users[]|[.email,.pass,.token,.key]|join("^")' "$CF_CONFIG") return 0 } CloudflareListUsers() { if [ ! -s "$CF_CONFIG" ] then Println "$error 请先添加用户\n" && exit 1 fi CloudflareGetUsers if [ "$cf_users_count" -gt 0 ] then Println "$cf_users_list" else Println "$error 没有用户\n" fi } CloudflareAddUser() { CloudflareSetUserEmail CloudflareSetUserPass CloudflareSetUserToken CloudflareSetUserKey if [ ! -s "$CF_CONFIG" ] then printf '{"%s":[],"%s":[]}' "users" "hosts" > "$CF_CONFIG" fi new_user=$( $JQ_FILE -n --arg email "$cf_user_email" --arg pass "$cf_user_pass" \ --arg token "$cf_user_token" --arg key "$cf_user_api_key" \ '{ email: $email, pass: $pass, token: $token, key: $key }' ) jq_path='["users"]' JQ add "$CF_CONFIG" "[$new_user]" Println "$info 用户添加成功\n" } CloudflareListUser() { CloudflareListUsers if [ "$cf_users_count" -eq 0 ] then Println "$error 请先添加用户\n" exit 1 fi echo -e "选择用户" while read -p "$i18n_default_cancel" cf_users_num do case "$cf_users_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$cf_users_num" -gt 0 ] && [ "$cf_users_num" -le "$cf_users_count" ] then cf_users_index=$((cf_users_num-1)) cf_user_email=${cf_users_email[cf_users_index]} cf_user_pass=${cf_users_pass[cf_users_index]} cf_user_token=${cf_users_token[cf_users_index]} cf_user_api_key=${cf_users_api_key[cf_users_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done if [ -n "$cf_user_api_key" ] then curl_header_auth_email="X-Auth-Email: $cf_user_email" curl_header_auth_key="X-Auth-Key: $cf_user_api_key" curl_header_auth_token="" else curl_header_auth_email="" curl_header_auth_key="" curl_header_auth_token="Authorization: Bearer $cf_user_token" fi [ -z "${delimiters:-}" ] && delimiters=( $'\001' $'\002' $'\003' $'\004' $'\005' $'\006' ) IFS=$'\002\t' read -r success error_message CF_ACCOUNT_ID < <( JQs flat "$(curl -s -X GET -H ''"$curl_header_auth_email"'' -H ''"$curl_header_auth_key"'' -H ''"$curl_header_auth_token"'' \ -H 'Content-Type: application/json' https://api.cloudflare.com/client/v4/accounts)" '' \ '[.success + "\u0002", (.errors|if (.|type == "string") then {} else . end).message + "\u0002", (.result|if (.|type == "string") then {} else . end).id + "\u0002"] |@tsv' "${delimiters[@]}") if [ "$success" = false ] then Println "$error 获取账号 ID 失败: ${error_message//${delimiters[0]}/, }\n" exit 1 fi end_epoch=$(date --utc -d 'tomorrow 00:00:00' +%s) start_epoch=$(date --utc -d 'today 00:00:00' +%s) start_date=$(date --utc --date="@$start_epoch" +'%Y-%m-%dT%H:%m:%SZ') end_date=$(date --utc --date="@$end_epoch" +'%Y-%m-%dT%H:%m:%SZ') PAYLOAD='{ "query": "query { viewer { accounts(filter: { accountTag: $accountTag }) { workersInvocationsAdaptive( filter: { datetime_geq: $datetimeStart, datetime_leq: $datetimeEnd } limit: 100 ) { sum { requests subrequests errors } } } } }",' PAYLOAD="$PAYLOAD \"variables\": { \"accountTag\": \"$CF_ACCOUNT_ID\", \"datetimeStart\": \"$start_date\", \"datetimeEnd\": \"$end_date\" } }" IFS=$'\002\t' read -r cf_workers_requests error_message < <( JQs flat "$(curl -s -H 'Content-Type: application/json' -H ''"$curl_header_auth_email"'' -H ''"$curl_header_auth_key"'' -H ''"$curl_header_auth_token"'' \ --data "$(echo $PAYLOAD)" https://api.cloudflare.com/client/v4/graphql)" '' \ '[((.data|if (.|type == "string") then {} else . end).viewer.accounts.workersInvocationsAdaptive|if (.|type == "string") then {} else . end).sum.requests + "\u0002", (.errors|if (.|type == "string") then {} else . end).message + "\u0002"]|@tsv' "{delimiters[@]}") if [ -z "$cf_workers_requests" ] then Println "$error 获取 workers 访问数失败: ${error_message//${delimiters[0]}/, }\n" exit 1 fi Println "$info workers 访问总数: $cf_workers_requests\n" } CloudflareAddZone() { CloudflareListHosts echo -e "选择 CFP" while read -p "$i18n_default_cancel" cf_hosts_num do case "$cf_hosts_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$cf_hosts_num" -gt 0 ] && [ "$cf_hosts_num" -le "$cf_hosts_count" ] then cf_hosts_index=$((cf_hosts_num-1)) cf_host_name=${cf_hosts_name[cf_hosts_index]} cf_host_key=${cf_hosts_key[cf_hosts_index]} cf_host_zone_name=${cf_hosts_zone_name[cf_hosts_index]} IFS="|" read -r -a cf_host_zones_name <<< "$cf_host_zone_name" break else Println "$error $i18n_input_correct_no\n" fi ;; esac done CloudflareListUsers if [ "$cf_users_count" -eq 0 ] then Println "$error 请先添加用户\n" exit 1 fi echo -e "选择用户" while read -p "$i18n_default_cancel" cf_users_num do case "$cf_users_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$cf_users_num" -gt 0 ] && [ "$cf_users_num" -le "$cf_users_count" ] then cf_users_index=$((cf_users_num-1)) cf_user_email=${cf_users_email[cf_users_index]} cf_user_pass=${cf_users_pass[cf_users_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done Println "$info 稍等..." result="" until [ "$result" == "success" ] do random_number=$(od -An -N6 -t u8 < /dev/urandom) cf_user_unique_id=${random_number: -12} IFS="^" read -r result err_code msg < <(curl -s -Lm 50 https://api.cloudflare.com/host-gw.html \ -d 'act=user_create' \ -d "host_key=$cf_host_key" \ -d "cloudflare_email=$cf_user_email" \ -d "cloudflare_pass=$cf_user_pass" \ -d "unique_id=$cf_user_unique_id" \ | $JQ_FILE '[.result,.err_code,.msg]|join("^")' ) || true result=${result#\"} msg=${msg%\"} if [ "$result" == "error" ] then Println "$error $msg" fi done Println "请输入根域名" echo -e "$tip 如果域名已经由 cloudflare 解析, 请先到官方 cloudflare 面板中删除\n" while read -p "$i18n_default_cancel" cf_zone_name do if [ -z "$cf_zone_name" ] then Println "$i18n_canceled...\n" exit 1 elif [[ $cf_zone_name =~ ^([a-zA-Z0-9][\-a-zA-Z0-9]*\.)+[\-a-zA-Z0-9]{2,20}$ ]] then Println " 域名: ${green} $cf_zone_name ${normal}\n" break else Println "$error 输入错误, 请输入根域名, 不能是二级域名" fi done for cf_host_zone_name in ${cf_host_zones_name[@]+"${cf_host_zones_name[@]}"} do if [ "$cf_host_zone_name" == "$cf_zone_name" ] then Println "$error 域名已经存在\n" exit 1 fi done CloudflareSetZoneResolve CloudflareSetZoneAlwaysUseHttps CloudflareSetZoneSsl new_zone=$( $JQ_FILE -n --arg name "$cf_zone_name" --arg resolve_to "$cf_zone_resolve_to" \ --arg user_email "$cf_user_email" --arg user_unique_id "$cf_user_unique_id" \ --arg always_use_https "$cf_zone_always_use_https" --arg ssl "$cf_zone_ssl" \ '{ name: $name, resolve_to: $resolve_to, user_email: $user_email, user_unique_id: $user_unique_id | tonumber, always_use_https: $always_use_https, ssl: $ssl }' ) jq_path='["hosts",'"$cf_hosts_index"',"zones"]' JQ add "$CF_CONFIG" "[$new_zone]" Println "$info 源站添加成功\n" } CloudflareListZones() { CloudflareListHosts echo -e "选择 CFP" while read -p "$i18n_default_cancel" cf_hosts_num do case "$cf_hosts_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$cf_hosts_num" -gt 0 ] && [ "$cf_hosts_num" -le "$cf_hosts_count" ] then cf_hosts_index=$((cf_hosts_num-1)) cf_host_name=${cf_hosts_name[cf_hosts_index]} cf_host_key=${cf_hosts_key[cf_hosts_index]} cf_zones_count=${cf_hosts_zones_count[cf_hosts_index]} cf_zone_name=${cf_hosts_zone_name[cf_hosts_index]} cf_zone_resolve_to=${cf_hosts_zone_resolve_to[cf_hosts_index]} cf_zone_user_email=${cf_hosts_zone_user_email[cf_hosts_index]} cf_zone_user_unique_id=${cf_hosts_zone_user_unique_id[cf_hosts_index]} cf_zone_always_use_https=${cf_hosts_zone_always_use_https[cf_hosts_index]} cf_zone_ssl=${cf_hosts_zone_ssl[cf_hosts_index]} cf_zone_subdomains=${cf_hosts_zone_subdomains[cf_hosts_index]} IFS="|" read -r -a cf_zones_name <<< "$cf_zone_name" IFS="|" read -r -a cf_zones_resolve_to <<< "$cf_zone_resolve_to" IFS="|" read -r -a cf_zones_user_email <<< "$cf_zone_user_email" IFS="|" read -r -a cf_zones_user_unique_id <<< "$cf_zone_user_unique_id" IFS="|" read -r -a cf_zones_always_use_https <<< "${cf_zone_always_use_https}|" IFS="|" read -r -a cf_zones_ssl <<< "${cf_zone_ssl}|" IFS="|" read -r -a cf_zones_subdomains <<< "${cf_zone_subdomains}|" break else Println "$error $i18n_input_correct_no\n" fi ;; esac done cf_zones_list="" for((i=0;i /dev/null ) || true success=${success#\"} token=${token%\"} if [ "$success" != "true" ] then Println "$error 获取 Token 错误, 必须是 Global API Key 才可以获取用户 Token \n" exit 1 fi IFS="^" read -r -a tokens <<< "$token" tokens_count=${#tokens[@]} tokens_list="" tokens_id=() for((i=0;i "$CF_CONFIG" fi [ ! -d "$CF_WORKERS_ROOT" ] && mkdir -p "$CF_WORKERS_ROOT" cd "$CF_WORKERS_ROOT" Println " ${green}1.${normal} stream proxy (反向代理) ${green}2.${normal} xtream codes proxy ${green}3.${normal} 自定义 worker " while read -p "选择 worker: " add_cf_worker_num do case $add_cf_worker_num in 1) if [ ! -d "$CF_WORKERS_ROOT/stream_proxy" ] then wrangler generate "stream_proxy" wget --timeout=10 --tries=1 --no-check-certificate "$STREAM_PROXY_LINK" -qO "$CF_WORKERS_ROOT/stream_proxy/index.js" \ || wget --timeout=10 --tries=3 --no-check-certificate "$STREAM_PROXY_LINK_FALLBACK" -qO "$CF_WORKERS_ROOT/stream_proxy/index.js" fi CloudflareSetWorkerName cf_worker_path="stream_proxy" CloudflareSetWorkerProjectName CloudflareSetWorkerUpstream break ;; 2) if [ ! -d "$CF_WORKERS_ROOT/xtream_codes_proxy" ] then wrangler generate "xtream_codes_proxy" wget --timeout=10 --tries=1 --no-check-certificate "$XTREAM_CODES_PROXY_LINK" -qO "$CF_WORKERS_ROOT/xtream_codes_proxy/index.js" \ || wget --timeout=10 --tries=3 --no-check-certificate "$XTREAM_CODES_PROXY_LINK_FALLBACK" -qO "$CF_WORKERS_ROOT/xtream_codes_proxy/index.js" fi CloudflareSetWorkerName cf_worker_path="xtream_codes_proxy" CloudflareSetWorkerProjectName break ;; 3) CloudflareListWorkers CloudflareSetWorkerName CloudflareSetWorkerPath CloudflareSetWorkerProjectName if [ -d "$CF_WORKERS_ROOT/$cf_worker_path" ] then echo ExitOnList n "`gettext \"路径已经存在, 是否仍要添加\"`" else wrangler generate "$cf_worker_path" fi break ;; *) Println "$error $i18n_input_correct_no\n" ;; esac done new_worker=$( $JQ_FILE -n --arg name "$cf_worker_name" --arg path "$cf_worker_path" --arg project_name "$cf_worker_project_name" \ '{ name: $name, path: $path, project_name: $project_name }' ) if [ -n "${cf_worker_upstream:-}" ] then merge=$( $JQ_FILE -n --arg upstream "$cf_worker_upstream" \ '{ upstream: $upstream }') JQs merge new_worker "$merge" fi jq_path='["workers"]' JQ add "$CF_CONFIG" "[$new_worker]" Println "$info worker: $cf_worker_name 添加成功\n" } CloudflareGetWorkers() { [ -z "${delimiters:-}" ] && delimiters=( $'\001' $'\002' $'\003' $'\004' $'\005' $'\006' ) IFS=$'\002\t' read -r name path project_name upstream < <(JQs flat "$CF_CONFIG" '.[0].workers' ' (. // {}| if . == "" then {} else . end) as $workers | reduce ({name,path,project_name,upstream}|keys_unsorted[]) as $key ([]; $workers[$key] as $val | if $val then . + [$val + "\u0001\u0002"] else . + ["\u0002"] end )|@tsv' "${delimiters[@]}") if [ -z "$name" ] then cf_workers_count=0 return 0 fi IFS="${delimiters[0]}" read -r -a cf_workers_name <<< "$name" IFS="${delimiters[0]}" read -r -a cf_workers_path <<< "$path" IFS="${delimiters[0]}" read -r -a cf_workers_project_name <<< "$project_name" if [ -z "$upstream" ] then cf_workers_upstream=("${cf_workers_name[@]//*/}") else IFS="${delimiters[0]}" read -r -a cf_workers_upstream <<< "$upstream" fi cf_workers_count=${#cf_workers_name[@]} } CloudflareListWorkers() { if [ ! -s "$CF_CONFIG" ] then Println "$error 请先添加 worker\n" && exit 1 fi CloudflareGetWorkers if [ "$cf_workers_count" -gt 0 ] then cf_workers_list="" for((i=0;i /dev/null ) || Println "$error Token 权限错误 ?" IFS="|" read -r -a zones_id <<< "$zone_id" IFS="|" read -r -a zones_name <<< "$zone_name" IFS="|" read -r -a accounts_id <<< "$account_id" for((j=0;j<${#zones_id[@]};j++)); do cf_users_zones_count=$((cf_users_zones_count+1)) IFS="^" read -r count id script pattern < <(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zones_id[j]}/workers/routes" \ -H "Content-Type: application/json" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ | $JQ_FILE '[(.result|length),([.result[].id]|join(" ")),([.result[].script]|join(" ")),([.result[].pattern]|join(" "))]|join("^")' ) count=${count#\"} pattern=${pattern%\"} cf_users_zones_routes_count+=("$count") cf_users_zones_route_id+=("$id") cf_users_zones_route_script+=("$script") cf_users_zones_route_pattern+=("$pattern") cf_users_zones_id+=("${zones_id[j]}") cf_users_zones_name+=("${zones_name[j]}") cf_users_zones_account_id+=("${accounts_id[j]}") cf_users_zones_account_token+=("${cf_users_token[i]}") cf_users_zones_account_api_key+=("${cf_users_api_key[i]}") cf_users_zones_account_email+=("${cf_users_email[i]}") cf_users_zones_list="$cf_users_zones_list $cf_users_zones_count.${indent_6}${green}${zones_name[j]}${normal} 路由数: ${green}$count${normal}\n\n" done done if [ "$cf_users_zones_count" -eq 0 ] then Println "$error 没有找到域名, 请先添加源站\n" exit 1 fi Println "$cf_users_zones_list" } CloudflareConfigWorkerRoute() { Println "$info 搜索路由 ..." CloudflareListWorkersRoutes echo -e "选择域名" while read -p "$i18n_default_cancel" cf_zones_num do case $cf_zones_num in "") Println "$i18n_canceled...\n" exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$cf_zones_num" -gt 0 ] && [ "$cf_zones_num" -le "$cf_users_zones_count" ] then cf_zones_index=$((cf_zones_num-1)) cf_users_zone_name=${cf_users_zones_name[cf_zones_index]} cf_users_zone_id=${cf_users_zones_id[cf_zones_index]} cf_users_zone_account_id=${cf_users_zones_account_id[cf_zones_index]} cf_users_zone_account_token=${cf_users_zones_account_token[cf_zones_index]} cf_users_zone_account_api_key=${cf_users_zones_account_api_key[cf_zones_index]} cf_users_zone_account_email=${cf_users_zones_account_email[cf_zones_index]} cf_users_zone_routes_count=${cf_users_zones_routes_count[cf_zones_index]} cf_users_zone_route_id=${cf_users_zones_route_id[cf_zones_index]} cf_users_zone_route_script=${cf_users_zones_route_script[cf_zones_index]} cf_users_zone_route_pattern=${cf_users_zones_route_pattern[cf_zones_index]} if [ -n "$cf_users_zone_account_api_key" ] then curl_header_auth_email="X-Auth-Email: $cf_users_zone_account_email" curl_header_auth_key="X-Auth-Key: $cf_users_zone_account_api_key" curl_header_auth_token="" elif [ -n "$cf_users_zone_account_token" ] then curl_header_auth_email="" curl_header_auth_key="" curl_header_auth_token="Authorization: Bearer $cf_users_zone_account_token" else Println "$error 请添加账号 $cf_users_zone_account_email Token 或 Key\n" exit 1 fi break else Println "$error $i18n_input_correct_no\n" fi ;; esac done if [ "$cf_users_zone_routes_count" -gt 0 ] then IFS=" " read -r -a cf_users_zone_routes_id <<< "$cf_users_zone_route_id" IFS=" " read -r -a cf_users_zone_routes_script <<< "$cf_users_zone_route_script" IFS=" " read -r -a cf_users_zone_routes_pattern <<< "$cf_users_zone_route_pattern" cf_users_zone_routes_list="" for((i=0;i ${green}$cf_users_zone_route_script${normal}\n\n ${green}1.${normal}${indent_6}更改路由\n ${green}2.${normal}${indent_6}删除路由\n" read -p "$i18n_default_cancel" cf_users_zone_route_num case $cf_users_zone_route_num in "") Println "$i18n_canceled...\n" exit 1 ;; 1) Println "$info 输入已经存在的 worker 项目名称" echo -e "$tip 输入的是项目名称, 不是序号\n" read -p "(默认: $cf_users_zone_route_script): " script script=${script:-$cf_users_zone_route_script} Println "$info 输入路由, 比如 abc.domain.com/*" read -p "(默认: $cf_users_zone_route_pattern): " pattern pattern=${pattern:-$cf_users_zone_route_pattern} if [[ $(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$cf_users_zone_id/workers/routes/$cf_users_zone_route_id" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ -H "Content-Type: application/json" \ --data '{"pattern":"'"$pattern"'","script":"'"$script"'"}' \ | $JQ_FILE -r '.success' ) = true ]] then Println "$info 路由更改成功\n" else Println "$error 路由更改失败\n" fi ;; 2) if [[ $(curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/$cf_users_zone_id/workers/routes/$cf_users_zone_route_id" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ -H "Content-Type: application/json" \ | $JQ_FILE -r '.success' ) = true ]] then Println "$info 路由删除成功\n" else Println "$info 路由删除成功\n" fi ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) Println "$error $i18n_input_correct_no\n" ;; esac exit 0 else Println "$error $i18n_input_correct_no\n" fi ;; esac done fi CloudflareListWorkers Println "$info 输入已经存在的 worker 项目名称" echo -e "$tip 输入的是项目名称, 不是序号\n" read -p "$i18n_default_cancel" script [ -z "$script" ] && Println "$i18n_canceled...\n" && exit 1 if [[ $script =~ ^[0-9]+$ ]] && [ "$script" -le "$cf_workers_count" ] && [ "$script" -gt 0 ] then cf_workers_index=$((script-1)) echo inquirer list_input_index "是想要输入 ${cf_workers_project_name[cf_workers_index]}" yn_options yn_options_index if [ "$yn_options_index" -eq 0 ] then script=${cf_workers_project_name[cf_workers_index]} fi fi Println "$info 输入路由,比如 abc.domain.com/*" read -p "$i18n_default_cancel" pattern [ -z "$pattern" ] && Println "$i18n_canceled...\n" && exit 1 if [[ $(curl -s "https://api.cloudflare.com/client/v4/zones/$cf_users_zone_id/workers/routes" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ -H "Content-Type: application/json" \ --data '{"pattern":"'"$pattern"'","script":"'"$script"'"}' \ | $JQ_FILE -r '.success' ) = true ]] then Println "$info 路由添加成功\n" else Println "$error 路由添加失败\n" fi } CloudflareWorkersMonitorMoveZone() { CloudflareGetUser Println "$info 删除源站 ..." IFS="^" read -r result err_code msg < <(curl -s -Lm 50 https://api.cloudflare.com/host-gw.html \ -d 'act=zone_delete' \ -d "host_key=$cf_host_key" \ -d "user_key=$cf_user_key" \ -d "zone_name=$cf_zone_name" \ | $JQ_FILE '[.result,.err_code,.msg]|join("^")' ) || true result=${result#\"} msg=${msg%\"} if [ -z "$result" ] || [ "$result" == "error" ] then if [ "$err_code" -eq 115 ] || [ "$err_code" -eq 703 ] then Println "$error 此用户已被 CFP 删除或未添加成功, 可以到 Cloudflare 官网手动删除源站或者重新添加 !" else Println "$error ${msg:-超时, 请重试}\n" fi MonitorErr "move zone 删除源站 $err_code, $msg" exit 1 else map_string=true jq_path='["hosts",'"$cf_hosts_index"',"zones"]' JQ delete "$CF_CONFIG" name "$cf_zone_name" Println "$info $cf_zone_name 删除成功" fi Println "$info 移动中 ..." cf_user_key="" until [ -n "$cf_user_key" ] do random_number=$(od -An -N6 -t u8 < /dev/urandom) cf_user_unique_id=${random_number: -12} IFS="^" read -r result cf_user_key cf_user_api_key msg < <(curl -s -Lm 50 https://api.cloudflare.com/host-gw.html \ -d 'act=user_create' \ -d "host_key=$cf_host_key" \ -d "cloudflare_email=$cf_user_email_new" \ -d "cloudflare_pass=$cf_user_pass_new" \ -d "unique_id=$cf_user_unique_id" \ | $JQ_FILE '[.result,.response.user_key,.response.user_api_key,.msg]|join("^")' ) || true result=${result#\"} msg=${msg%\"} if [ "$result" == "error" ] then Println "$error $msg" if [[ $msg == *"assword"* ]] then Println "$error 请检查密码是否正确\n" MonitorErr "move zone 移动中 请检查密码是否正确, $msg" exit 1 fi elif [ -z "$cf_user_api_key_new" ] then jq_path='["users",'"$cf_users_index"',"key"]' JQ update "$CF_CONFIG" "$cf_user_api_key" Println "$info 用户 $cf_user_email_new API Key 添加成功\n" cf_user_api_key_new="$cf_user_api_key" fi done cf_zones_user_unique_id[zone_index]="$cf_user_unique_id" cf_user_email="$cf_user_email_new" cf_user_pass="$cf_user_pass_new" cf_user_token="$cf_user_token_new" if [ -z "$cf_zone_subdomains" ] then CloudflareGetUser CloudflareGetZone for((i=0;i<${#cf_hosted_cnames[@]};i++)); do if [[ ${cf_hosted_cnames[i]} =~ ^([^.]+).([^.]+)$ ]] then continue fi cf_hosted_cname=${cf_hosted_cnames[i]} cf_hosted_cname=${cf_hosted_cname%.*} cf_hosted_cname_prefix=${cf_hosted_cname%.*} [ -n "$cf_zone_subdomains" ] && cf_zone_subdomains="$cf_zone_subdomains," cf_zone_subdomains="$cf_zone_subdomains$cf_hosted_cname_prefix:${cf_resolve_tos[i]}" done fi new_zone=$( $JQ_FILE -n --arg name "$cf_zone_name" --arg resolve_to "$cf_zone_resolve_to" \ --arg user_email "$cf_user_email" --arg user_unique_id "$cf_user_unique_id" \ --arg always_use_https "$cf_zone_always_use_https" --arg ssl "$cf_zone_ssl" --arg subdomains "$cf_zone_subdomains" \ '{ name: $name, resolve_to: $resolve_to, user_email: $user_email, user_unique_id: $user_unique_id | tonumber, always_use_https: $always_use_https, ssl: $ssl, subdomains: $subdomains }' ) jq_path='["hosts",'"$cf_hosts_index"',"zones"]' JQ add "$CF_CONFIG" "[$new_zone]" CloudflareGetUser IFS="^" read -r result cf_zone_resolving_to cf_zone_hosted_cnames cf_zone_forward_tos msg < <(curl -s -Lm 20 https://api.cloudflare.com/host-gw.html \ -d 'act=zone_set' \ -d "host_key=$cf_host_key" \ -d "user_key=$cf_user_key" \ -d "zone_name=$cf_zone_name" \ -d "resolve_to=$cf_zone_resolve_to" \ -d "subdomains=$cf_zone_subdomains" \ | $JQ_FILE '[.result,.response.resolving_to,([(.response.hosted_cnames| if .== null then {} else . end)|to_entries[] |([.key,.value]|join("="))] |join("|")),([(.response.forward_tos| if .== null then {} else . end)|to_entries[] |([.key,.value]|join("="))] |join("|")),.msg]|join("^")' ) || true result=${result#\"} msg=${msg%\"} if [ -z "$result" ] || [ "$result" == "error" ] then MonitorErr "move zone 连接超时, 请查看是否已经完成 $msg" Println "$error ${msg:-连接超时, 请查看是否已经完成}\n" fi Println "$info 源站移动成功\n" } CloudflareWorkersMonitorUpdateRoutes() { zone_cnames=() zone_resolves=() if [ -n "$cf_zone_subdomains" ] then IFS="," read -r -a pairs <<< "$cf_zone_subdomains" for pair in "${pairs[@]}" do if [[ $pair == *":"* ]] then zone_cnames+=("${pair%:*}.$cf_zone_name") zone_resolves+=("${pair#*:}") else zone_cnames+=("$pair.$cf_zone_name") zone_resolves+=("$cf_zone_resolve_to") fi done else CloudflareGetUser CloudflareGetZone for((j=0;j<${#cf_hosted_cnames[@]};j++)); do if [[ ${cf_resolve_tos[j]} =~ ^([^.]+).([^.]+).workers.dev$ ]] then zone_cnames+=("${cf_hosted_cnames[j]}") zone_resolves+=("${cf_resolve_tos[j]}") fi done fi workers_pattern=() for worker_project_name in "${workers_project_name[@]}" do zone_cname_found=0 for((j=0;j<${#zone_cnames[@]};j++)); do zone_resolve_to=${zone_resolves[j]} if [[ ${zone_resolve_to%%.*} == "$worker_project_name" ]] then zone_cname_found=1 worker_pattern="${zone_cnames[j]}/*" break fi done if [ "$zone_cname_found" -eq 0 ] then if [ "$worker_project_name" == "${cf_zone_name%.*}" ] then worker_pattern="$cf_zone_name/*" else worker_pattern="$worker_project_name.$cf_zone_name/*" fi fi workers_pattern+=("$worker_pattern") done IFS=" " read -r zone_id zone_name account_id < <(curl -s -X GET "https://api.cloudflare.com/client/v4/zones" \ -H "Content-Type: application/json" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ | $JQ_FILE -r '[([.result[].id]|join("|")),([.result[].name]|join("|")),([.result[].account.id]|join("|"))]|join(" ")' 2> /dev/null ) || Println "$error Token 权限错误 ?" IFS="|" read -r -a zones_id <<< "$zone_id" IFS="|" read -r -a zones_name <<< "$zone_name" IFS="|" read -r -a accounts_id <<< "$account_id" for((j=0;j<${#zones_id[@]};j++)); do if [ "${zones_name[j]}" == "$cf_zone_name" ] then cf_zone_always_use_https=${cf_zone_always_use_https:-off} zone_always_use_https=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zones_id[j]}/settings/always_use_https" \ -H "Content-Type: application/json" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ | $JQ_FILE -r '.result.value' ) if [ "$zone_always_use_https" != "$cf_zone_always_use_https" ] then fail_time=0 until [[ $(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${zones_id[j]}/settings/always_use_https" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ -H "Content-Type: application/json" \ --data '{"value":"'"$cf_zone_always_use_https"'"}' | $JQ_FILE -r '.success') = true ]] do MonitorErr "域名: $cf_zone_name always_use_https 设置失败, Token: $cf_user_token, zone id: ${zones_id[j]}, $zone_always_use_https => $cf_zone_always_use_https" Println "$error 域名: $cf_zone_name always_use_https 设置失败\n" fail_time=$((fail_time+1)) [ "$fail_time" -ge 5 ] && exit 1 done Println "$info 域名: $cf_zone_name always_use_https 设置成功\n" fi cf_zone_ssl=${cf_zone_ssl:-flexible} zone_ssl=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zones_id[j]}/settings/ssl" \ -H "Content-Type: application/json" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ | $JQ_FILE -r '.result.value' ) if [ "$zone_ssl" != "$cf_zone_ssl" ] then fail_time=0 until [[ $(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${zones_id[j]}/settings/ssl" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ -H "Content-Type: application/json" \ --data '{"value":"'"$cf_zone_ssl"'"}' | $JQ_FILE -r '.success') = true ]] do MonitorErr "域名: $cf_zone_name ssl 设置失败, Token: $cf_user_token, zone id: ${zones_id[j]}, $zone_ssl => $cf_zone_ssl" Println "$error 域名: $cf_zone_name ssl 设置失败\n" fail_time=$((fail_time+1)) [ "$fail_time" -ge 5 ] && exit 1 done Println "$info 域名: $cf_zone_name ssl 设置成功\n" fi IFS="^" read -r count id script pattern < <(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zones_id[j]}/workers/routes" \ -H "Content-Type: application/json" \ -H ''"$curl_header_auth_email"'' \ -H ''"$curl_header_auth_key"'' \ -H ''"$curl_header_auth_token"'' \ | $JQ_FILE '[(.result|length),([.result[].id]|join(" ")),([.result[].script]|join(" ")),([.result[].pattern]|join(" "))]|join("^")' ) pattern=${pattern%\"} IFS=" " read -r -a ids <<< "$id" IFS=" " read -r -a scripts <<< "$script" IFS=" " read -r -a patterns <<< "$pattern" for((k=0;k "$pid_file" { flock -x 204 { MonitorLog "启动 workers 监控 PID $BASHPID !" clear=$(date --utc -d 'tomorrow 00:00:10' +%s) emails_dead=() if [ "$sh_debug" -eq 0 ] then monitor=true fi while true do printf -v now '%(%s)T' -1 if [ "$now" -ge "$clear" ] then clear=$(date --utc -d 'tomorrow 00:00:10' +%s) emails_dead=() start_from_begin=1 fi zone_index=${zones_index_monitor[0]} cf_zone_user_email=${cf_zones_user_email[zone_index]} cf_zone_user_pass=${cf_zones_user_pass[zone_index]} cf_zone_user_token=${cf_zones_user_token[zone_index]} cf_zone_user_api_key=${cf_zones_user_api_key[zone_index]} if [ -n "$cf_zone_user_api_key" ] then curl_header_auth_email="X-Auth-Email: $cf_zone_user_email" curl_header_auth_key="X-Auth-Key: $cf_zone_user_api_key" curl_header_auth_token="" else curl_header_auth_email="" curl_header_auth_key="" curl_header_auth_token="Authorization: Bearer $cf_zone_user_token" fi dead_email=0 for email in ${emails_dead[@]+"${emails_dead[@]}"} do if [ "$email" == "$cf_zone_user_email" ] then dead_email=1 break fi done if [ "$dead_email" -eq 0 ] then for((i=0;i<20;i++)); do if [ "$cf_use_api" -eq 1 ] then CloudflareWorkersMonitorGetRequests "$cf_zone_user_email" "$cf_zone_user_token" "$cf_zone_user_api_key" if [ -z "$request_count" ] || [[ $request_count == *[!0-9]* ]] then MonitorErr "request_count : ${request_count:-无}" sleep 10 else if [ "$request_count" -gt "$cf_workers_monitor_request_counts" ] then dead_email=1 emails_dead+=("$cf_zone_user_email") fi break fi elif request_count_json=$(python3 \ "$CF_WORKERS_FILE" -e "$cf_zone_user_email" -p "$cf_zone_user_pass" -o request_count ) then IFS=" " read -r success request_count api_token < <($JQ_FILE -r '[.success,.result.totals.requestCount,.api_token]|join(" ")' <<< "$request_count_json") if [ "$success" = true ] && [ -n "$request_count" ] then if [ "$request_count" -gt "$cf_workers_monitor_request_counts" ] then dead_email=1 emails_dead+=("$cf_zone_user_email") fi break else MonitorErr "request_count_json 1 : $request_count_json" sleep 30 fi else sleep 10 fi done fi if [ "$dead_email" -eq 1 ] then if [ "$start_from_begin" -eq 1 ] then continue=0 else continue=1 fi for((cf_users_index=0;i&- } 204<"$pid_file" } CloudflareEnableWorkersMonitor() { # deprecated if [ -s "/tmp/cf_workers.pid" ] && kill -0 "$(< /tmp/cf_workers.pid)" 2> /dev/null then Println "$error workers 监控已开启\n" && exit 1 fi if [ -s "$CF_WORKERS_ROOT/cf_workers.pid" ] && kill -0 "$(< $CF_WORKERS_ROOT/cf_workers.pid)" 2> /dev/null then Println "$error workers 监控已开启\n" && exit 1 fi CloudflareListWorkers if [ "$cf_workers_count" -eq 0 ] then Println "$error 请先添加 worker\n" exit 1 fi workers_name=() workers_path=() workers_project_name=() workers_upstream=() echo "选择 worker, 多个 worker 用空格分隔, 比如 5 7 9-11" while read -p "$i18n_default_cancel" workers_num do [ -z "$workers_num" ] && Println "$i18n_canceled...\n" && exit 1 IFS=" " read -ra workers_num_arr <<< "$workers_num" error_no=0 for worker_num in "${workers_num_arr[@]}" do case "$worker_num" in *"-"*) worker_num_start=${worker_num%-*} worker_num_end=${worker_num#*-} if [[ $worker_num_start == *[!0-9]* ]] || [[ $worker_num_end == *[!0-9]* ]] || \ [ "$worker_num_start" -eq 0 ] || [ "$worker_num_end" -eq 0 ] || \ [ "$worker_num_end" -gt "$cf_workers_count" ] || \ [ "$worker_num_start" -ge "$worker_num_end" ] then error_no=3 break fi ;; *[!0-9]*) error_no=1 break ;; *) if [ "$worker_num" -lt 1 ] || [ "$worker_num" -gt "$cf_workers_count" ] then error_no=2 break fi ;; esac done case "$error_no" in 1|2|3) Println "$error $i18n_input_correct_number\n" ;; *) for element in "${workers_num_arr[@]}" do if [[ $element =~ - ]] then start=${element%-*} end=${element#*-} for((i=start;i<=end;i++)); do cf_workers_index=$((i-1)) workers_name+=("${cf_workers_name[cf_workers_index]}") workers_path+=("${cf_workers_path[cf_workers_index]}") workers_project_name+=("${cf_workers_project_name[cf_workers_index]}") workers_upstream+=("${cf_workers_upstream[cf_workers_index]}") done else cf_workers_index=$((element-1)) workers_name+=("${cf_workers_name[cf_workers_index]}") workers_path+=("${cf_workers_path[cf_workers_index]}") workers_project_name+=("${cf_workers_project_name[cf_workers_index]}") workers_upstream+=("${cf_workers_upstream[cf_workers_index]}") fi done break ;; esac done workers_data=() stream_proxy_history=() workers_count=${#workers_name[@]} workers_monitor_stream_proxy_pairs=() workers_monitor_stream_proxy_list="" workers_monitor_stream_proxy_count=0 while IFS="^" read -r history_date history_pair do history_date=${history_date#\"} history_date=${history_date%\"} if [ -n "$history_date" ] then workers_monitor_stream_proxy_count=$((workers_monitor_stream_proxy_count+1)) history_pair=${history_pair%\"} workers_monitor_stream_proxy_pairs+=("$history_pair") history_pair=${history_pair// / => } workers_monitor_stream_proxy_pairs_list=${history_pair//|/$'\n' } printf -v date '%(%m-%d %H:%M:%S)T' "$history_date" workers_monitor_stream_proxy_list="$workers_monitor_stream_proxy_list $workers_monitor_stream_proxy_count. ${green}$date${normal}\n $workers_monitor_stream_proxy_pairs_list\n\n" fi done < <($JQ_FILE '(.workers_monitor.stream_proxy| if .== null then [] else . end)[]|([.date,(.pairs|to_entries|map([.value.project_name,.value.upstream]|join(" "))|join("|"))]|join("^"))' "$CF_CONFIG") if [ -n "$workers_monitor_stream_proxy_list" ] then Println "$workers_monitor_stream_proxy_list" while read -p "选择 stream proxy worker 历史配置(默认: 不选择): " history_num do case $history_num in "") break ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$history_num" -gt 0 ] && [ "$history_num" -le "$workers_monitor_stream_proxy_count" ] then history_index=$((history_num-1)) pair=${workers_monitor_stream_proxy_pairs[history_index]} IFS="|" read -r -a pairs <<< "$pair" break else Println "$error $i18n_input_correct_no\n" fi ;; esac done fi for((i=0;i /dev/null 2> /dev/null < /dev/null & fi Println "$info workers 监控开启成功\n" } CloudflareDisableWorkersMonitor() { # deprecated if [ -s "/tmp/cf_workers.pid" ] then cf_workers_pid=$(< /tmp/cf_workers.pid) if kill -0 "$cf_workers_pid" 2> /dev/null then kill "$cf_workers_pid" 2> /dev/null [ ! -d "${MONITOR_LOG%/*}" ] && MONITOR_LOG="$HOME/monitor.log" MonitorLog "关闭 workers 监控 PID $cf_workers_pid !" Println "$info workers 监控 关闭成功\n" else Println "$error workers 监控 未开启\n" fi elif [ -s "$CF_WORKERS_ROOT/cf_workers.pid" ] then PID=$(< "$CF_WORKERS_ROOT/cf_workers.pid") if kill -0 "$PID" 2> /dev/null then Println "$info 关闭 workers 监控, 稍等..." kill "$PID" 2> /dev/null if flock -E 1 -w 20 -x "$CF_WORKERS_ROOT/cf_workers.pid" rm -f "$CF_WORKERS_ROOT/cf_workers.pid" then MonitorLog "关闭 workers 监控 PID $PID !" Println "$info workers 监控 关闭成功 !\n" else Println "$error workers 监控 关闭超时, 请重试\n" exit 1 fi else rm -f "$CF_WORKERS_ROOT/cf_workers.pid" Println "$error workers 监控 未开启\n" fi else [ -e "$CF_WORKERS_ROOT/cf_workers.pid" ] && rm -f "$CF_WORKERS_ROOT/cf_workers.pid" Println "$error workers 监控 未开启\n" fi } CloudflareWorkersMenu() { Println " cloudflare 面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 安装 wrangler ${green}2.${normal} 更新 wrangler ${green}3.${normal} 查看 worker ${green}4.${normal} 添加 worker ${green}5.${normal} 更改 worker ${green}6.${normal} 部署 worker ${green}7.${normal} 设置 路由 ${green}8.${normal} 开启 监控 ${green}9.${normal} 关闭 监控 ${green}10.${normal} 删除 worker $tip 当前: ${green}workers${normal} 面板 $tip 输入: c 切换到 partner 面板\n\n" read -p "`gettext \"输入序号\"` [1-10]: " cloudflare_workers_num case $cloudflare_workers_num in c) CloudflarePartnerMenu ;; 1) WranglerInstall ;; 2) WranglerUpdate ;; 3) CloudflareListWorker ;; 4) CloudflareAddWorker ;; 5) CloudflareEditWorker ;; 6) CloudflareDeployWorker ;; 7) CloudflareConfigWorkerRoute ;; 8) CloudflareEnableWorkersMonitor ;; 9) CloudflareDisableWorkersMonitor ;; 10) CloudflareDelWorker ;; *) Println "$error $i18n_input_correct_number [1-10]\n" ;; esac } IbmInstallCfCli() { if [[ -x $(command -v ibmcloud) ]] then Println "$error IBM CF CLI 已存在\n" exit 1 fi Println "$info 安装 IBM CF CLI ..." curl -sL https://ibm.biz/idt-installer | bash ibmcloud cf install } IbmUpdateCfCli() { if [[ ! -x $(command -v ibmcloud) ]] then Println "$error IBM CF CLI 未安装\n" exit 1 fi Println "$info 更新 IBM CF CLI ..." ibmcloud update -f ibmcloud cf install -f } IbmGetUsers() { ibm_users_list="" ibm_users_count=0 ibm_users_email=() ibm_users_pass=() ibm_users_region=() ibm_users_resource_group=() ibm_users_org=() ibm_users_space=() while IFS=" " read -r email pass region resource_group org space do ibm_users_count=$((ibm_users_count+1)) email=${email#\"} ibm_users_email+=("$email") ibm_users_pass+=("$pass") ibm_users_region+=("$region") ibm_users_resource_group+=("$resource_group") ibm_users_org+=("$org") space=${space%\"} ibm_users_space+=("$space") ibm_users_list="$ibm_users_list ${green}$ibm_users_count.${normal}${indent_6}地区: ${green}$region${normal} 资源组: ${green}$resource_group${normal}\n${indent_6}邮箱: ${green}$email${normal} 密码: ${green}$pass${normal}\n${indent_6}组织: ${green}$org${normal} 空间: ${green}$space${normal}\n\n" done < <($JQ_FILE '.users[]|[.email,.pass,.region,.resource_group,.org,.space]|join(" ")' "$IBM_CONFIG") return 0 } IbmGetCfApps() { ibm_cf_apps_list="" ibm_cf_apps_count=0 ibm_cf_apps_name=() ibm_cf_apps_user_email=() ibm_cf_apps_routes_count=() ibm_cf_apps_route_hostname=() ibm_cf_apps_route_port=() ibm_cf_apps_route_domain=() ibm_cf_apps_route_path=() while IFS="^" read -r name user_email routes_count route_hostname route_port route_domain route_path do ibm_cf_apps_count=$((ibm_cf_apps_count+1)) name=${name#\"} ibm_cf_apps_name+=("$name") ibm_cf_apps_user_email+=("$user_email") ibm_cf_apps_routes_count+=("$routes_count") ibm_cf_apps_route_hostname+=("$route_hostname") ibm_cf_apps_route_port+=("$route_port") ibm_cf_apps_route_domain+=("$route_domain") route_path=${route_path%\"} ibm_cf_apps_route_path+=("$route_path") ibm_cf_apps_list="$ibm_cf_apps_list ${green}$ibm_cf_apps_count.${normal}${indent_6}APP: ${green}$name${normal} 用户: ${green}$user_email${normal} 路由数: ${green}$routes_count${normal}\n\n" done < <($JQ_FILE '.cf.apps[]|[.name,.user_email,(.routes|length),([.routes[].hostname]|join("|")),([.routes[].port]|join("|")),([.routes[].domain]|join("|")),([.routes[].path]|join("|"))]|join("^")' "$IBM_CONFIG") return 0 } IbmListUsers() { if [ ! -s "$IBM_CONFIG" ] then Println "$error 请先添加用户\n" && exit 1 fi IbmGetUsers if [ "$ibm_users_count" -gt 0 ] then Println "$ibm_users_list" else Println "$error 没有用户\n" fi } IbmLoginUser() { IbmListUsers if [ "$ibm_users_count" -eq 0 ] then Println "$error 请先添加用户\n" exit 1 fi echo -e "选择用户" while read -p "$i18n_default_cancel" ibm_users_num do case "$ibm_users_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$ibm_users_num" -gt 0 ] && [ "$ibm_users_num" -le "$ibm_users_count" ] then ibm_users_index=$((ibm_users_num-1)) ibm_user_email=${ibm_users_email[ibm_users_index]} ibm_user_pass=${ibm_users_pass[ibm_users_index]} ibm_user_region=${ibm_users_region[ibm_users_index]} ibm_user_resource_group=${ibm_users_resource_group[ibm_users_index]} ibm_user_org=${ibm_users_org[ibm_users_index]} ibm_user_space=${ibm_users_space[ibm_users_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done Println "$info 登录账号: $ibm_user_email [ $ibm_user_region ]" ibmcloud login -u "$ibm_user_email" -p "$ibm_user_pass" -r "$ibm_user_region" -g "$ibm_user_resource_group" ibmcloud target -o "$ibm_user_org" -s "$ibm_user_space" } IbmUpdateCfApp() { IbmListCfApps echo -e "选择 APP" while read -p "$i18n_default_cancel" ibm_cf_apps_num do case "$ibm_cf_apps_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$ibm_cf_apps_num" -gt 0 ] && [ "$ibm_cf_apps_num" -le "$ibm_cf_apps_count" ] then ibm_cf_apps_index=$((ibm_cf_apps_num-1)) ibm_cf_app_name=${ibm_cf_apps_name[ibm_cf_apps_index]} ibm_user_email=${ibm_cf_apps_user_email[ibm_cf_apps_index]} ibm_cf_app_routes_count=${ibm_cf_apps_routes_count[ibm_cf_apps_index]} ibm_cf_app_route_hostname=${ibm_cf_apps_route_hostname[ibm_cf_apps_index]} ibm_cf_app_route_port=${ibm_cf_apps_route_port[ibm_cf_apps_index]} ibm_cf_app_route_domain=${ibm_cf_apps_route_domain[ibm_cf_apps_index]} ibm_cf_app_route_path=${ibm_cf_apps_route_path[ibm_cf_apps_index]} IFS="|" read -r -a ibm_cf_app_routes_hostname <<< "$ibm_cf_app_route_hostname" IFS="|" read -r -a ibm_cf_app_routes_port <<< "$ibm_cf_app_route_port" IFS="|" read -r -a ibm_cf_app_routes_domain <<< "$ibm_cf_app_route_domain" IFS="|" read -r -a ibm_cf_app_routes_path <<< "${ibm_cf_app_route_path}|" break else Println "$error $i18n_input_correct_no\n" fi ;; esac done IbmGetUsers for((i=0;i "$IBM_CONFIG" fi IbmSetUserEmail IbmSetUserPass IbmGetApi ibmcloud api "$ibm_api" IbmSetUserRegion Println "$info 登录账号: $ibm_user_email [ $ibm_user_region ]" ibmcloud login -u "$ibm_user_email" -p "$ibm_user_pass" -r "$ibm_user_region" IbmSetUserResourceGroup ibmcloud target -g "$ibm_user_resource_group" IbmSetUserOrg ibmcloud target -o "$ibm_user_org" IbmSetUserSpace ibmcloud target -s "$ibm_user_space" new_user=$( $JQ_FILE -n --arg email "$ibm_user_email" --arg pass "$ibm_user_pass" \ --arg region "$ibm_user_region" --arg resource_group "$ibm_user_resource_group" \ --arg org "$ibm_user_org" --arg space "$ibm_user_space" \ '{ email: $email, pass: $pass, region: $region, resource_group: $resource_group, org: $org, space: $space }' ) jq_path='["users"]' JQ add "$IBM_CONFIG" "[$new_user]" Println "$info 用户添加成功\n" } IbmEditUser() { IbmListUsers if [ "$ibm_users_count" -eq 0 ] then Println "$error 请先添加用户\n" exit 1 fi echo "选择用户" while read -p "$i18n_default_cancel" ibm_users_num do case "$ibm_users_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$ibm_users_num" -gt 0 ] && [ "$ibm_users_num" -le "$ibm_users_count" ] then ibm_users_index=$((ibm_users_num-1)) break else Println "$error $i18n_input_correct_no\n" fi ;; esac done IbmSetUserEmail IbmSetUserPass IbmGetApi ibmcloud api "$ibm_api" IbmSetUserRegion Println "$info 登录账号: $ibm_user_email [ $ibm_user_region ]" ibmcloud login -u "$ibm_user_email" -p "$ibm_user_pass" -r "$ibm_user_region" IbmSetUserResourceGroup ibmcloud target -g "$ibm_user_resource_group" IbmSetUserOrg ibmcloud target -o "$ibm_user_org" IbmSetUserSpace ibmcloud target -s "$ibm_user_space" new_user=$( $JQ_FILE -n --arg email "$ibm_user_email" --arg pass "$ibm_user_pass" \ --arg region "$ibm_user_region" --arg resource_group "$ibm_user_resource_group" \ --arg org "$ibm_user_org" --arg space "$ibm_user_space" \ '{ email: $email, pass: $pass, region: $region, resource_group: $resource_group, org: $org, space: $space }' ) json=true jq_path='["users",'"$ibm_users_index"']' JQ update "$IBM_CONFIG" "$new_user" Println "$info 用户修改成功\n" } IbmSetCfAppName() { Println "请输入 APP 名称\n$tip 确保已经在官网建立此 CF APP, 也可以用命令 ibmcloud dev create 新建\n" read -p "$i18n_default_cancel" ibm_cf_app_name [ -z "$ibm_cf_app_name" ] && Println "$i18n_canceled...\n" && exit 1 if [[ -n $($JQ_FILE '.cf.apps[]|select(.user_email=="'"$ibm_user_email"'" and .name=="'"$ibm_cf_app_name"'")' "$IBM_CONFIG") ]] then Println "$error 此 APP 已存在\n" exit 1 fi Println " APP: ${green} $ibm_cf_app_name ${normal}\n" } IbmAddCfApp() { IbmLoginUser ibmcloud cf apps IbmSetCfAppName Println "$info 查询路由 ..." app_guid=$(ibmcloud cf app "$ibm_cf_app_name" --guid -q) IFS="^" read -r route_guid hostname domain_guid path < <(ibmcloud cf curl "/v2/apps/$app_guid/routes" -q \ | $JQ_FILE -r '[([.resources[].metadata.guid]|join("|")),([.resources[].entity.host]|join("|")),([.resources[].entity.domain_guid]|join("|")),([.resources[].entity.path]|join("|"))]|join("^")') ibm_cf_app_routes_count=0 if [ -n "$route_guid" ] then IFS="|" read -r -a ibm_cf_app_routes_guid <<< "$route_guid" IFS="|" read -r -a ibm_cf_app_routes_hostname <<< "$hostname" IFS="|" read -r -a ibm_cf_app_routes_domain_guid <<< "$domain_guid" IFS="|" read -r -a ibm_cf_app_routes_path <<< "${path}|" ibm_cf_app_routes_count=${#ibm_cf_app_routes_guid[@]} IFS="^" read -r port route_guid < <(ibmcloud cf curl "/v2/route_mappings?q=app_guid:$app_guid" -q \ | $JQ_FILE -r '[([.resources[].entity.app_port]|join("|")),([.resources[].entity.route_guid]|join("|"))]|join("^")') IFS="|" read -r -a ports <<< "$port" IFS="|" read -r -a routes_guid <<< "$route_guid" IFS="^" read -r domain domain_guid < <(ibmcloud cf curl "/v2/domains" -q \ | $JQ_FILE -r '[([.resources[].entity.name]|join("|")),([.resources[].metadata.guid]|join("|"))]|join("^")') IFS="|" read -r -a domains <<< "$domain" IFS="|" read -r -a domains_guid <<< "$domain_guid" ibm_cf_app_routes_port=() ibm_cf_app_routes_domain=() for((i=0;i "$V2_CONFIG" fi } IbmDownloadV2ray() { if [ -d "$IBM_APPS_ROOT/ibm_$v2ray_name" ] then Println "$error ibm $v2ray_name 已存在\n" else DepsCheck JQInstall Println "$info 下载 ibm $v2ray_name ..." cd ~ rm -rf $v2ray_package_name-linux-64 if v2ray_version=$(curl -s -L "$FFMPEG_MIRROR_LINK/$v2ray_name.json" | $JQ_FILE -r '.tag_name') && curl -L "$FFMPEG_MIRROR_LINK/$v2ray_name/$v2ray_version/$v2ray_package_name-linux-64.zip" -o "$v2ray_package_name-linux-64.zip" && unzip "$v2ray_package_name-linux-64.zip" -d "$v2ray_package_name-linux-64" > /dev/null then mkdir -p "$IBM_APPS_ROOT/ibm_$v2ray_name" mv ${v2ray_package_name}-linux-64/$v2ray_name "$IBM_APPS_ROOT/ibm_$v2ray_name/" if [ "$v2ray_name" == "xray" ] then if ! curl -L "$FFMPEG_MIRROR_LINK/xray/v2ctl" -o "$IBM_APPS_ROOT/ibm_$v2ray_name/v2ctl" then Println "$error 无法连接服务器, 请稍后再试\n" exit 1 fi else mv $v2ray_package_name-linux-64/v2ctl "$IBM_APPS_ROOT/ibm_$v2ray_name/" fi chmod 700 "$IBM_APPS_ROOT/ibm_$v2ray_name/$v2ray_name" chmod 700 "$IBM_APPS_ROOT/ibm_$v2ray_name/v2ctl" Println "$info ibm $v2ray_name 下载完成\n" else Println "$error 无法连接服务器, 请稍后再试\n" fi fi } IbmUpdateV2ray() { if [ ! -d "$IBM_APPS_ROOT/ibm_$v2ray_name" ] then Println "$error ibm $v2ray_name 未安装\n" else Println "$info 更新 ibm $v2ray_name ..." cd ~ rm -rf $v2ray_package_name-linux-64 if v2ray_version=$(curl -s -L "$FFMPEG_MIRROR_LINK/$v2ray_name.json" | $JQ_FILE -r '.tag_name') && curl -L "$FFMPEG_MIRROR_LINK/$v2ray_name/$v2ray_version/$v2ray_package_name-linux-64.zip" -o "$v2ray_package_name-linux-64.zip" && unzip $v2ray_package_name-linux-64.zip -d $v2ray_package_name-linux-64 > /dev/null then mkdir -p "$IBM_APPS_ROOT/ibm_$v2ray_name" mv $v2ray_package_name-linux-64/$v2ray_name "$IBM_APPS_ROOT/ibm_$v2ray_name/" if [ "$v2ray_name" == "xray" ] then if ! curl -L "$FFMPEG_MIRROR_LINK/xray/v2ctl" -o "$IBM_APPS_ROOT/ibm_$v2ray_name/v2ctl" then Println "$error 无法连接服务器, 请稍后再试\n" exit 1 fi else mv $v2ray_package_name-linux-64/v2ctl "$IBM_APPS_ROOT/ibm_$v2ray_name/" fi chmod 700 "$IBM_APPS_ROOT/ibm_$v2ray_name/$v2ray_name" chmod 700 "$IBM_APPS_ROOT/ibm_$v2ray_name/v2ctl" Println "$info ibm $v2ray_name 更新完成\n" else Println "$error 无法连接服务器, 请稍后再试\n" fi fi } IbmDeployV2ray() { V2rayGetInbounds port_found=0 for port in ${inbounds_port[@]+"${inbounds_port[@]}"} do if [ "$port" -eq 8080 ] then port_found=1 break fi done if [ "$port_found" -eq 0 ] then Println "$error 请先添加 8080 端口\n" exit 1 fi if [ ! -e "$IBM_APPS_ROOT/ibm_$v2ray_name/$v2ray_name" ] || [ ! -e "$IBM_APPS_ROOT/ibm_$v2ray_name/v2ctl" ] then Println "$error 请先更新 IBM $v2ray_name APP\n" exit 1 fi IbmListCfApp Println "$info 登录账号: $ibm_user_email [ $ibm_user_region ]" ibmcloud login -u "$ibm_user_email" -p "$ibm_user_pass" -r "$ibm_user_region" -g "$ibm_user_resource_group" ibmcloud target -o "$ibm_user_org" -s "$ibm_user_space" v2ray_rand_name=$(RandStr) cp -r "$IBM_APPS_ROOT/ibm_$v2ray_name" "$IBM_APPS_ROOT/ibm_$v2ray_rand_name" cd "$IBM_APPS_ROOT/ibm_$v2ray_rand_name/" mv $v2ray_name "$v2ray_rand_name" if [ "$v2ray_name" == "xray" ] then ./v2ctl convert config.json > "$v2ray_rand_name.pb" else ./v2ctl config config.json > "$v2ray_rand_name.pb" fi tar zcf "$v2ray_rand_name.tar.gz" "$v2ray_rand_name" "$v2ray_rand_name.pb" rm -f config.json rm -f config.json.lock rm -f "$v2ray_rand_name" rm -f "$v2ray_rand_name.pb" rm -f v2ctl ibmcloud cf create-app-manifest "$ibm_cf_app_name" routes="" while read -r line do if [[ $line =~ disk_quota: ]] then disk_quota=${line##* } elif [[ $line =~ instances: ]] then instances=${line##* } elif [[ $line =~ memory: ]] then memory=${line##* } elif [[ $line =~ routes: ]] || [[ $line =~ route: ]] then [ -n "$routes" ] && routes="$routes\n" routes="$routes$line" elif [[ $line =~ stack: ]] then stack=${line##* } fi done < "${ibm_cf_app_name}_manifest.yml" echo -e "--- applications: - name: $ibm_cf_app_name command: tar xzf $v2ray_rand_name.tar.gz && { ./$v2ray_rand_name -config ./$v2ray_rand_name.pb -format=pb & } && sleep 5 && rm ./$v2ray_rand_name.pb && rm ./$v2ray_rand_name disk_quota: $disk_quota instances: ${instances:-1} memory: $memory stack: $stack buildpacks: - go_buildpack env: GOPACKAGENAME: goapp " > "${ibm_cf_app_name}_manifest.yml" printf '%s' 'package main func main() { } ' > "main.go" ibmcloud cf push -f "${ibm_cf_app_name}_manifest.yml" cd .. rm -rf "$IBM_APPS_ROOT/ibm_$v2ray_rand_name" } IbmV2rayMenu() { [ ! -d "$IPTV_ROOT" ] && JQ_FILE="/usr/local/bin/jq" Println " IBM $v2ray_package_name APP 面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 下载 $v2ray_package_name APP ${green}2.${normal} 更新 $v2ray_package_name APP ${green}3.${normal} 部署 $v2ray_package_name APP ———————————— ${green}4.${normal} 查看入站 ${green}5.${normal} 添加入站 ${green}6.${normal} 添加入站账号 ———————————— ${green}7.${normal} 查看出站 ${green}8.${normal} 添加出站 ${green}9.${normal} 添加出站账号 ———————————— ${green}10.${normal} 查看DNS ${green}11.${normal} 设置DNS ———————————— ${green}12.${normal} 查看路由 ${green}13.${normal} 设置路由 ———————————— ${green}14.${normal} 查看策略 ${green}15.${normal} 设置策略 ———————————— ${green}16.${normal} 查看流量 ${green}17.${normal} 重置流量 ———————————— ${green}18.${normal} 查看反向代理 ${green}19.${normal} 设置反向代理 ———————————— ${green}20.${normal} 删除入站 ${green}21.${normal} 删除入站账号 ${green}22.${normal} 删除出站 ${green}23.${normal} 删除出站账号 " read -p "$i18n_default_cancel" ibm_v2ray_num case $ibm_v2ray_num in 1) IbmDownloadV2ray ;; 2) IbmUpdateV2ray ;; 3) IbmUpdateV2rayConfig IbmDeployV2ray ;; 4) IbmUpdateV2rayConfig V2rayListInboundAccounts V2rayListInboundAccountLink ;; 5) IbmUpdateV2rayConfig V2rayAddInbound ;; 6) IbmUpdateV2rayConfig V2rayAddInboundAccount ;; 7) IbmUpdateV2rayConfig V2rayListOutboundAccounts ;; 8) IbmUpdateV2rayConfig V2rayAddOutbound ;; 9) IbmUpdateV2rayConfig V2rayAddOutboundAccount ;; 10) IbmUpdateV2rayConfig V2rayListDns ;; 11) IbmUpdateV2rayConfig V2raySetDns ;; 12) IbmUpdateV2rayConfig V2rayListRouting ;; 13) IbmUpdateV2rayConfig V2raySetRouting ;; 14) IbmUpdateV2rayConfig V2rayListPolicy ;; 15) IbmUpdateV2rayConfig V2raySetPolicy ;; 16) IbmUpdateV2rayConfig V2rayListStats ;; 17) IbmUpdateV2rayConfig V2rayResetStats ;; 18) IbmUpdateV2rayConfig V2rayListReverse ;; 19) IbmUpdateV2rayConfig V2raySetReverse ;; 20) IbmUpdateV2rayConfig V2rayDeleteInbound ;; 21) IbmUpdateV2rayConfig V2rayDeleteInboundAccount ;; 22) IbmUpdateV2rayConfig V2rayDeleteOutbound ;; 23) IbmUpdateV2rayConfig V2rayDeleteOutboundAccount ;; *) Println "$error $i18n_input_correct_number [1-23]\n" ;; esac } IbmSetCfAppCron() { IbmListCfApps echo -e "选择 APP" echo -e "$tip 多个 APP 用空格分隔, 比如 5 7 9-11\n" apps_name=() apps_user_email=() while read -p "$i18n_default_cancel" apps_num do [ -z "$apps_num" ] && Println "$i18n_canceled...\n" && exit 1 IFS=" " read -ra apps_num_arr <<< "$apps_num" error_no=0 for app_num in "${apps_num_arr[@]}" do case "$app_num" in *"-"*) app_num_start=${app_num%-*} app_num_end=${app_num#*-} if [[ $app_num_start == *[!0-9]* ]] || [[ $app_num_end == *[!0-9]* ]] || \ [ "$app_num_start" -eq 0 ] || [ "$app_num_end" -eq 0 ] || \ [ "$app_num_end" -gt "$ibm_cf_apps_count" ] || \ [ "$app_num_start" -ge "$app_num_end" ] then error_no=3 break fi ;; *[!0-9]*) error_no=1 break ;; *) if [ "$app_num" -lt 1 ] || [ "$app_num" -gt "$ibm_cf_apps_count" ] then error_no=2 break fi ;; esac done case "$error_no" in 1|2|3) Println "$error $i18n_input_correct_no\n" ;; *) for element in "${apps_num_arr[@]}" do if [[ $element =~ - ]] then start=${element%-*} end=${element#*-} for((i=start;i<=end;i++)); do ibm_cf_apps_index=$((i-1)) apps_name+=("${ibm_cf_apps_name[ibm_cf_apps_index]}") apps_user_email+=("${ibm_cf_apps_user_email[ibm_cf_apps_index]}") done else ibm_cf_apps_index=$((element-1)) apps_name+=("${ibm_cf_apps_name[ibm_cf_apps_index]}") apps_user_email+=("${ibm_cf_apps_user_email[ibm_cf_apps_index]}") fi done break ;; esac done ibm_cf_apps_path=() ibm_cf_apps_path_list="" ibm_cf_apps_path_count=0 for path in "$IBM_APPS_ROOT"/* do [ ! -d "$path" ] && continue ibm_cf_apps_path_count=$((ibm_cf_apps_path_count+1)) app_path=${path##*/} ibm_cf_apps_path+=("$app_path") ibm_cf_apps_path_list="$ibm_cf_apps_path_list $ibm_cf_apps_path_count.${indent_6}${green}$app_path${normal}\n\n" done if [ "$ibm_cf_apps_path_count" -eq 0 ] then Println "$error 请将 APP 所在目录移动到目录 $IBM_APPS_ROOT 下\n" exit 1 fi apps_path=() new_jobs="" for((i=0;i<${#apps_name[@]};i++)); do Println "$ibm_cf_apps_path_list" echo -e "$info 选择 APP: ${green}${apps_name[i]}${normal} 本地目录" while read -p "$i18n_default_cancel" apps_path_num do case $apps_path_num in "") Println "$i18n_canceled...\n" exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$apps_path_num" -gt 0 ] && [ "$apps_path_num" -le "$ibm_cf_apps_path_count" ] then ibm_cf_apps_path_index=$((apps_path_num-1)) apps_path+=("${ibm_cf_apps_path[ibm_cf_apps_path_index]}") break else Println "$error $i18n_input_correct_no\n" fi ;; esac done new_job=$( $JQ_FILE -n --arg app "${apps_name[i]}" --arg user_email "${apps_user_email[i]}" \ --arg path "${apps_path[i]}" \ '{ "app": $app, "user_email": $user_email, "path": $path }' ) [ -n "$new_jobs" ] && new_jobs="$new_jobs," new_jobs="$new_jobs$new_job" done Println "$info 输入重启间隔天数" while read -p "(默认: 5): " cron_days do case $cron_days in "") cron_days=5 break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$cron_days" -gt 0 ] then break else Println "$error $i18n_input_correct_number\n" fi ;; esac done Println "$info 输入重启小时 [0-23]" while read -p "(默认: 0): " cron_hour do case $cron_hour in "") cron_hour=0 break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$cron_hour" -ge 0 ] && [ "$cron_hour" -le 23 ] then break else Println "$error $i18n_input_correct_number\n" fi ;; esac done Println "$info 输入重启分钟 [0-59]" while read -p "(默认: 0): " cron_min do case $cron_min in "") cron_min=0 break ;; *[!0-9]*) Println "$error $i18n_input_correct_number\n" ;; *) if [ "$cron_min" -gt 0 ] && [ "$cron_hour" -le 59 ] then break else Println "$error $i18n_input_correct_number\n" fi ;; esac done cron=$( $JQ_FILE -n --arg days "$cron_days" --arg hour "$cron_hour" \ --arg min "$cron_min" --argjson job "[$new_jobs]" \ '{ "days": $days | tonumber, "hour": $hour | tonumber, "min": $min | tonumber, "job": $job }' ) json=true jq_path='["cf","cron"]' JQ update "$IBM_CONFIG" "$cron" Println "$info 定时重启任务设置成功\n" } IbmEnableCfAppCron() { if crontab -l | grep -q "/usr/local/bin/ibm cron" 2> /dev/null then Println "$error 定时重启任务已开启 !\n" else IFS=" " read -r cron_days cron_hour cron_min < <($JQ_FILE -r '.cf.cron|[.days,.hour,.min]|join(" ")' "$IBM_CONFIG") [ -z "$cron_days" ] && Println "$error 请先设置定时重启任务\n" && exit 1 crontab -l > "$IBM_APPS_ROOT/cron_tmp" 2> /dev/null || true printf '%s\n' "$cron_min $cron_hour */$cron_days * * /usr/local/bin/ibm cron" >> "$IBM_APPS_ROOT/cron_tmp" if ! grep -q 'PATH=' < "$IBM_APPS_ROOT/cron_tmp" then cron=$(< "$IBM_APPS_ROOT/cron_tmp") echo -e "PATH=$PATH\n$cron" > "$IBM_APPS_ROOT/cron_tmp" fi crontab "$IBM_APPS_ROOT/cron_tmp" > /dev/null rm -f "$IBM_APPS_ROOT/cron_tmp" Println "$info 定时重启任务开启成功\n" fi } IbmDisableCfAppCron() { if crontab -l | grep -q "/usr/local/bin/ibm cron" 2> /dev/null then crontab -l > "$IBM_APPS_ROOT/cron_tmp" 2> /dev/null || true sed -i "/\/usr\/local\/bin\/ibm cron/d" "$IBM_APPS_ROOT/cron_tmp" crontab "$IBM_APPS_ROOT/cron_tmp" > /dev/null rm -f "$IBM_APPS_ROOT/cron_tmp" Println "$info 定时重启任务关闭成功\n" else Println "$error 定时重启任务未开启 !\n" fi } IbmCfAppCronExec() { IFS="^" read -r app_name user_email path < <($JQ_FILE '.cf.cron|[([.job[].app]|join(" ")),([.job[].user_email]|join(" ")),([.job[].path]|join(" "))]|join("^")' "$IBM_CONFIG") app_name=${app_name#\"} path=${path%\"} [ -z "$app_name" ] && Println "$error 请先设置定时重启任务\n" && exit 1 IbmUpdateCfCli IFS=" " read -r -a apps_name <<< "$app_name" IFS=" " read -r -a apps_user_email <<< "$user_email" IFS=" " read -r -a apps_path <<< "$path" IbmGetUsers for((i=0;i<${#apps_name[@]};i++)); do for((j=0;j "$v2ray_rand_name.pb" else ./v2ctl config config.json > "$v2ray_rand_name.pb" fi tar zcf "$v2ray_rand_name.tar.gz" "$v2ray_rand_name" "$v2ray_rand_name.pb" rm -f config.json rm -f config.json.lock rm -f "$v2ray_rand_name" rm -f "$v2ray_rand_name.pb" rm -f v2ctl ibmcloud cf create-app-manifest "${apps_name[i]}" routes="" while read -r line do if [[ $line =~ disk_quota: ]] then disk_quota=${line##* } elif [[ $line =~ instances: ]] then instances=${line##* } elif [[ $line =~ memory: ]] then memory=${line##* } elif [[ $line =~ routes: ]] || [[ $line =~ route: ]] then [ -n "$routes" ] && routes="$routes\n" routes="$routes$line" elif [[ $line =~ stack: ]] then stack=${line##* } fi done < "${apps_name[i]}_manifest.yml" echo -e "--- applications: - name: ${apps_name[i]} command: tar xzf $v2ray_rand_name.tar.gz && { ./$v2ray_rand_name -config ./$v2ray_rand_name.pb -format=pb & } && sleep 5 && rm ./$v2ray_rand_name.pb && rm ./$v2ray_rand_name disk_quota: $disk_quota instances: ${instances:-1} memory: $memory stack: $stack buildpacks: - go_buildpack env: GOPACKAGENAME: goapp " > "${apps_name[i]}_manifest.yml" printf '%s' 'package main func main() { } ' > "main.go" ibmcloud cf push -f "${apps_name[i]}_manifest.yml" cd .. rm -rf "$IBM_APPS_ROOT/ibm_$v2ray_rand_name" else cd "$IBM_APPS_ROOT/${apps_path[i]}/" ibmcloud cf create-app-manifest "${apps_name[i]}" ibmcloud cf push -f "${apps_name[i]}_manifest.yml" fi continue 2 fi done Println "$error APP ${apps_name[i]} 没有找到用户 ${apps_user_email[i]}" done } IbmUninstallCfCli() { if [[ ! -x $(command -v ibmcloud) ]] then Println "$error IBM CF CLI 未安装\n" exit 1 fi echo ExitOnList n "`gettext \"确定删除 IBM CF CLI\"`" EXIT_STATUS=0 rm -Rf /usr/local/ibmcloud || EXIT_STATUS=$? rm -f /usr/local/bin/ibmcloud || EXIT_STATUS=$? rm -f /usr/local/bin/bluemix || EXIT_STATUS=$? rm -f /usr/local/bin/bx || EXIT_STATUS=$? rm -f /usr/local/bin/ibmcloud-analytics || true if [ $EXIT_STATUS -eq 0 ] then Println "$info 删除成功\n" else Println "$error 发生错误\n" fi } IbmCfMenu() { Println " IBM CF 面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 安装 IBM CF CLI ${green}2.${normal} 更新 IBM CF CLI ${green}3.${normal} 删除 IBM CF CLI ${green}4.${normal} 查看 用户 ${green}5.${normal} 登录 用户 ${green}6.${normal} 添加 用户 ${green}7.${normal} 更改 用户 ${green}8.${normal} 查看 APP ${green}9.${normal} 添加 APP ${green}10.${normal} 添加 APP 路由 ${green}11.${normal} 删除 用户 ${green}12.${normal} 删除 APP ${green}13.${normal} 删除 APP 路由 ${green}14.${normal} 设置 v2ray APP ${green}15.${normal} 设置 Xray APP ${green}16.${normal} 设置 APP 定时重启 ${green}17.${normal} 开启 APP 定时重启 ${green}18.${normal} 关闭 APP 定时重启 ${green}19.${normal} 更新脚本 $tip 输入: ibm 打开面板\n\n" read -p "$i18n_default_cancel" ibm_cf_num case $ibm_cf_num in 1) IbmInstallCfCli ;; 2) IbmUpdateCfCli ;; 3) IbmUninstallCfCli ;; 4) IbmListUsers ;; 5) IbmLoginUser ;; 6) IbmAddUser ;; 7) IbmEditUser ;; 8) IbmListCfApp ;; 9) IbmAddCfApp ;; 10) IbmAddCfAppRoute ;; 11) IbmDelUser ;; 12) IbmDelApp ;; 13) IbmDelAppRoute ;; 14) v2ray_name="v2ray" v2ray_package_name="v2ray" tls_name="TLS" V2CTL_FILE="$IBM_APPS_ROOT/ibm_v2ray/v2ctl" V2_CONFIG="$IBM_APPS_ROOT/ibm_v2ray/config.json" IbmV2rayMenu ;; 15) v2ray_name="xray" v2ray_package_name="Xray" tls_name="XTLS" V2CTL_FILE="$IBM_APPS_ROOT/ibm_xray/xray" V2_CONFIG="$IBM_APPS_ROOT/ibm_xray/config.json" IbmV2rayMenu ;; 16) IbmSetCfAppCron ;; 17) IbmEnableCfAppCron ;; 18) IbmDisableCfAppCron ;; 19) ShFileUpdate ibm ;; *) Println "$error $i18n_input_correct_number [1-19]\n" ;; esac } VipSetHostIp() { Println "请输入 VIP 频道所在服务器 IP/域名" read -p "$i18n_default_cancel" vip_host_ip [ -z "$vip_host_ip" ] && Println "$i18n_canceled...\n" && exit 1 Println " VIP 服务器 IP/域名: ${green} $vip_host_ip ${normal}\n" } VipSetHostPort() { Println "请输入 VIP 频道所在服务器端口" read -p "$i18n_default_cancel" vip_host_port [ -z "$vip_host_port" ] && Println "$i18n_canceled...\n" && exit 1 Println " VIP 服务器端口: ${green} $vip_host_port ${normal}\n" } VipSetHostSeed() { Println "请输入 VIP 频道所在服务器的 seed" read -p "$i18n_default_cancel" vip_host_seed [ -z "$vip_host_seed" ] && Println "$i18n_canceled...\n" && exit 1 Println " VIP 服务器 seed: ${green} $vip_host_seed ${normal}\n" } VipSetHostToken() { echo inquirer text_input "请输入 VIP 频道所在服务器的 token: " vip_host_token "$i18n_not_set" if [ "$vip_host_token" == "$i18n_not_set" ] then vip_host_token="" fi } VipSetHostStatus() { echo inquirer list_input "是否开启用此 VIP 服务器" yn_options vip_host_status if [[ $vip_host_status == "$i18n_yes" ]] then vip_host_status="on" vip_host_status_text="${green}启用${normal}" else vip_host_status="off" vip_host_status_text="${red}禁用${normal}" fi Println " VIP 服务器状态: $vip_host_status_text\n" } VipAddHost() { VipSetHostIp VipSetHostPort VipSetHostSeed VipSetHostToken VipSetHostStatus if [ ! -s "$VIP_FILE" ] then printf '{"%s":{},"%s":[],"%s":[]}' "config" "users" "hosts" > "$VIP_FILE" fi new_host=$( $JQ_FILE -n --arg ip "$vip_host_ip" --arg port "$vip_host_port" \ --arg seed "$vip_host_seed" --arg token "$vip_host_token" \ --arg status "$vip_host_status" \ '{ ip: $ip, port: $port | tonumber, seed: $seed, token: $token, status: $status, channels: [] }' ) jq_path='["hosts"]' JQ add "$VIP_FILE" "[$new_host]" Println "$info VIP 服务器添加成功\n" } VipEditHost() { VipListHosts echo -e "选择 VIP 服务器" while read -p "$i18n_default_cancel" vip_hosts_num do case "$vip_hosts_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$vip_hosts_num" -gt 0 ] && [ "$vip_hosts_num" -le "$vip_hosts_count" ] then vip_hosts_index=$((vip_hosts_num-1)) vip_host_ip=${vip_hosts_ip[vip_hosts_index]} vip_host_port=${vip_hosts_port[vip_hosts_index]} vip_host_seed=${vip_hosts_seed[vip_hosts_index]} vip_host_token=${vip_hosts_token[vip_hosts_index]} vip_host_status=${vip_hosts_status[vip_hosts_index]} if [ "$vip_host_status" == "on" ] then vip_host_status_text="${green}启用${normal}" else vip_host_status_text="${red}禁用${normal}" fi break else Println "$error $i18n_input_correct_no\n" fi ;; esac done Println " 选择修改内容 ${green}1.${normal} 修改 IP/域名 ${green}2.${normal} 修改 端口 ${green}3.${normal} 修改 seed ${green}4.${normal} 修改 token ${green}5.${normal} 修改 状态 " read -p "$i18n_default_cancel" edit_vip_host_num case $edit_vip_host_num in 1) Println "原 IP/域名: ${red}$vip_host_ip${normal}" VipSetHostIp jq_path='["hosts",'"$vip_hosts_index"',"ip"]' JQ update "$VIP_FILE" "$vip_host_ip" Println "$info IP/域名 修改成功\n" ;; 2) Println "原端口: ${red}$vip_host_port${normal}" VipSetHostPort number=true jq_path='["hosts",'"$vip_hosts_index"',"port"]' JQ update "$VIP_FILE" "$vip_host_port" Println "$info 端口 修改成功\n" ;; 3) Println "原 seed: ${red}$vip_host_seed${normal}" VipSetHostSeed jq_path='["hosts",'"$vip_hosts_index"',"seed"]' JQ update "$VIP_FILE" "$vip_host_seed" Println "$info seed 修改成功\n" ;; 4) Println "原 token: ${red}$vip_host_token${normal}" VipSetHostToken jq_path='["hosts",'"$vip_hosts_index"',"token"]' JQ update "$VIP_FILE" "$vip_host_token" Println "$info token 修改成功\n" ;; 5) Println "原状态: $vip_host_status_text" VipSetHostStatus jq_path='["hosts",'"$vip_hosts_index"',"status"]' JQ update "$VIP_FILE" "$vip_host_status" Println "$info 状态修改成功\n" ;; *) Println "$i18n_canceled...\n" && exit 1 ;; esac } VipGetHosts() { vip_hosts_list="" vip_hosts_count=0 vip_hosts_ip=() vip_hosts_port=() vip_hosts_seed=() vip_hosts_token=() vip_hosts_status=() vip_hosts_channel_count=() vip_hosts_channel_id=() vip_hosts_channel_name=() vip_hosts_channel_epg_id=() while IFS="^" read -r ip port seed token status channels_count channels_id channels_name channels_epg_id do vip_hosts_count=$((vip_hosts_count+1)) ip=${ip#\"} vip_hosts_ip+=("$ip") vip_hosts_port+=("$port") vip_hosts_seed+=("$seed") vip_hosts_token+=("$token") vip_hosts_status+=("$status") if [ "$status" == "on" ] then status_text="${green} [启用] ${normal}" else status_text="${red} [禁用] ${normal}" fi vip_hosts_channel_count+=("$channels_count") vip_hosts_channel_id+=("$channels_id") vip_hosts_channel_name+=("$channels_name") channels_epg_id=${channels_epg_id%\"} vip_hosts_channel_epg_id+=("$channels_epg_id") vip_hosts_list="$vip_hosts_list ${green}$vip_hosts_count.${normal}${indent_6}服务器: ${green}$ip${normal} 端口: ${green}$port${normal} 频道数: ${green}$channels_count${normal}$status_text\n${indent_6}seed: ${green}$seed${normal} token: ${green}${token:-无}${normal}\n\n" done < <($JQ_FILE '.hosts[]|[.ip,.port,.seed,.token,.status,(.channels|length),([.channels[].id]|join("|")),([.channels[].name]|join("|")),([.channels[].epg_id]|join("|"))]|join("^")' "$VIP_FILE") return 0 } VipListHosts() { if [ ! -s "$VIP_FILE" ] then Println "$error 请先添加 VIP 服务器\n" && exit 1 fi VipGetHosts if [ "$vip_hosts_count" -gt 0 ] then Println "$vip_hosts_list" else Println "$error 请先添加 VIP 服务器\n" && exit 1 fi } VipDelHost() { VipListHosts echo -e "选择 VIP 服务器" while read -p "$i18n_default_cancel" vip_hosts_num do case "$vip_hosts_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$vip_hosts_num" -gt 0 ] && [ "$vip_hosts_num" -le "$vip_hosts_count" ] then vip_hosts_index=$((vip_hosts_num-1)) vip_host_ip=${vip_hosts_ip[vip_hosts_index]} vip_host_port=${vip_hosts_port[vip_hosts_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done jq_path='["hosts",'"$vip_hosts_index"']' JQ delete "$VIP_FILE" Println "服务器 ${green}[ $vip_host_ip ]${normal} 删除成功\n" } VipSetUserIp() { Println "请输入用户的 IP" read -p "(默认: 本机 IP): " vip_user_ip [ -z "$vip_user_ip" ] && vip_user_ip=$(GetServerIp) if [[ -n $($JQ_FILE '.users[]|select(.ip=="'"$vip_user_ip"'")' "$VIP_FILE") ]] then Println "$error 此 IP 已存在\n" && exit 1 fi Println " 用户 IP: ${green} $vip_user_ip ${normal}\n" } VipSetUserLicense() { Println "请输入用户的授权码" read -p "(默认: 自动生成): " vip_user_license if [ -z "$vip_user_license" ] then random_number=$(od -An -N6 -t u8 < /dev/urandom) vip_user_license="m${random_number: -12}" while [[ -n $($JQ_FILE '.users[]|select(.license=="'"$vip_user_license"'")' "$VIP_FILE") ]] do random_number=$(od -An -N6 -t u8 < /dev/urandom) vip_user_license="m${random_number: -12}" done elif [[ -n $($JQ_FILE '.users[]|select(.license=="'"$vip_user_license"'")' "$VIP_FILE") ]] then Println "$error 此授权码已存在\n" && exit 1 fi Println " 用户 license: ${green} $vip_user_license ${normal}\n" } VipSetUserSum() { Println "选择验证类型 ${green}1.${normal} ssum (一天) ${green}2.${normal} tsum (可控制天数) ${green}3.${normal} isum (永久) " while read -p "(默认: 2): " vip_user_sum_num do case $vip_user_sum_num in 1) vip_user_expire_days=1 vip_user_sum="ssum" printf -v now '%(%s)T' -1 vip_user_expire=$((now+86400)) break ;; 2|"") vip_user_sum="tsum" Println "请输入天数" while read -p "(默认: 1): " vip_user_expire_days do case $vip_user_expire_days in ""|1) vip_user_expire_days=1 printf -v now '%(%s)T' -1 vip_user_expire=$((now+86400)) break 2 ;; *[!0-9]*) Println "$error 输入错误\n" ;; *) if [[ $vip_user_expire_days -gt 1 ]] then printf -v now '%(%s)T' -1 vip_user_expire=$((now+86400*vip_user_expire_days)) break 2 else Println "$error 输入错误\n" fi ;; esac done ;; 3) vip_user_expire_days="" vip_user_sum="isum" vip_user_expire=0 break ;; *) Println "$error 输入错误\n" ;; esac done Println " 验证类型: ${green} $vip_user_sum ${normal}\n 到期天数: ${green} ${vip_user_expire_days:-无} ${normal}\n" } VipSetUserName() { Println "请输入用户名称(可以是中文)" read -p "(默认: 随机): " vip_user_name if [ -z "$vip_user_name" ] then vip_user_name=$(RandStr) while [[ -n $($JQ_FILE '.users[]|select(.name=="'"$vip_user_name"'")' "$VIP_FILE") ]] do vip_user_name=$(RandStr) done elif [[ -n $($JQ_FILE '.users[]|select(.name=="'"$vip_user_name"'")' "$VIP_FILE") ]] then Println "$error 此用户名已存在\n" && exit 1 fi Println " 用户名称: ${green} $vip_user_name ${normal}\n" } VipAddUser() { if [ ! -s "$VIP_FILE" ] then printf '{"%s":{},"%s":[],"%s":[]}' "config" "users" "hosts" > "$VIP_FILE" fi VipSetUserIp VipSetUserLicense VipSetUserSum VipSetUserName new_user=$( $JQ_FILE -n --arg ip "$vip_user_ip" --arg license "$vip_user_license" \ --arg sum "$vip_user_sum" --arg expire "$vip_user_expire" \ --arg name "$vip_user_name" \ '{ ip: $ip, license: $license, sum: $sum, expire: $expire | tonumber, name: $name }' ) jq_path='["users"]' JQ add "$VIP_FILE" "[$new_user]" Println "$info 添加成功\n" } VipEditUser() { VipListUsers while read -p "请选择用户: " vip_users_num do case "$vip_users_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$vip_users_num" -gt 0 ] && [ "$vip_users_num" -le "$vip_users_count" ] then vip_users_index=$((vip_users_num-1)) vip_user_ip=${vip_users_ip[vip_users_index]} vip_user_license=${vip_users_license[vip_users_index]} vip_user_sum=${vip_users_sum[vip_users_index]} vip_user_expire=${vip_users_expire[vip_users_index]} if [ "$vip_user_expire" -gt 0 ] then vip_user_expire_text=$(date +%c --date=@"$vip_user_expire") else vip_user_expire_text="无" fi vip_user_name=${vip_users_name[vip_users_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done Println " 选择修改内容 ${green}1.${normal} 修改 用户名 ${green}2.${normal} 修改 IP ${green}3.${normal} 修改 授权码 ${green}4.${normal} 修改 验证类型/到期日 " read -p "$i18n_default_cancel" edit_vip_user_num case $edit_vip_user_num in 1) Println "原用户名: ${red}$vip_user_name${normal}" VipSetUserName jq_path='["users",'"$vip_users_index"',"name"]' JQ update "$VIP_FILE" "$vip_user_name" Println "$info 用户名修改成功\n" ;; 2) Println "原 IP: ${red}$vip_user_ip${normal}" VipSetUserIp jq_path='["users",'"$vip_users_index"',"ip"]' JQ update "$VIP_FILE" "$vip_user_ip" Println "$info IP 修改成功\n" ;; 3) Println "原授权码: ${red}$vip_user_license${normal}" VipSetUserLicense jq_path='["users",'"$vip_users_index"',"license"]' JQ update "$VIP_FILE" "$vip_user_license" Println "$info 授权码修改成功\n" ;; 4) Println "原验证类型: ${red}$vip_user_sum${normal}\n原到期日: ${red}$vip_user_expire_text${normal}" VipSetUserSum jq_path='["users",'"$vip_users_index"',"sum"]' JQ update "$VIP_FILE" "$vip_user_sum" number=true jq_path='["users",'"$vip_users_index"',"expire"]' JQ update "$VIP_FILE" "$vip_user_expire" Println "$info 验证类型/到期日修改成功\n" ;; *) Println "$i18n_canceled...\n" && exit 1 ;; esac } VipGetUsers() { VipGetConfig vip_users_list="" vip_users_count=0 vip_users_ip=() vip_users_license=() vip_users_sum=() vip_users_expire=() vip_users_name=() while IFS=":" read -r ip license sum expire name do vip_users_count=$((vip_users_count+1)) ip=${ip#\"} vip_users_ip+=("$ip") vip_users_license+=("$license") vip_users_sum+=("$sum") vip_users_expire+=("$expire") name=${name%\"} vip_users_name+=("$name") if [ "$expire" -gt 0 ] then expire_text=$(date +%c --date=@"$expire") else expire_text="无" fi if [ -n "${vip_public_host:-}" ] then m3u_link="$vip_public_host/vip/$license/playlist.m3u" else m3u_link="${FFMPEG_MIRROR_LINK%/*}/vip/$license/playlist.m3u" fi vip_users_list="$vip_users_list ${green}$vip_users_count.${normal}${indent_6}用户名: ${green}$name${normal} ip: ${green}$ip${normal} 到期日: ${green}$expire_text${normal}\n${indent_6}授权码: ${green}$license${normal} 认证方式: ${green}$sum${normal}\n${indent_6}m3u 播放链接: ${green}$m3u_link${normal}\n\n" done < <($JQ_FILE '.users[]|[.ip,.license,.sum,.expire,.name]|join(":")' "$VIP_FILE") return 0 } VipListUsers() { if [ ! -s "$VIP_FILE" ] then Println "$error 请先添加 VIP 服务器\n" && exit 1 fi VipGetUsers if [ "$vip_users_count" -gt 0 ] then Println "$vip_users_list" else Println "$error 请先添加用户\n" && exit 1 fi } VipListUser() { VipListUsers } VipDelUser() { VipListUsers while read -p "请选择用户: " vip_users_num do case "$vip_users_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$vip_users_num" -gt 0 ] && [ "$vip_users_num" -le "$vip_users_count" ] then vip_users_index=$((vip_users_num-1)) vip_user_ip=${vip_users_ip[vip_users_index]} vip_user_license=${vip_users_license[vip_users_index]} vip_user_sum=${vip_users_sum[vip_users_index]} vip_user_expire=${vip_users_expire[vip_users_index]} vip_user_name=${vip_users_name[vip_users_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done jq_path='["users",'"$vip_users_index"']' JQ delete "$VIP_FILE" Println "用户 ${green}$vip_user_name [ $vip_user_license ]${normal} 删除成功" Println "$tip 同一用户2分钟内不能使用不同的授权码\n" } VipSetChannelId() { Println "请输入频道 ID, 同时也是目录名称" read -p "$i18n_default_cancel" vip_channel_id [ -z "$vip_channel_id" ] && Println "$i18n_canceled...\n" && exit 1 if [[ -n $($JQ_FILE --arg vip_host_ip "$vip_host_ip" --arg vip_channel_id "$vip_channel_id" '.hosts[] | select(.ip==$vip_host_ip).channels[] | select(.id==$vip_channel_id)' "$VIP_FILE") ]] then Println "$error $vip_channel_id 频道已经存在\n" && exit 1 fi } VipSetChannelName() { Println "请输入频道名称(可以是中文)" read -p "$i18n_default_cancel" vip_channel_name [ -z "$vip_channel_name" ] && Println "$i18n_canceled...\n" && exit 1 Println " VIP 频道名称: ${green} $vip_channel_name ${normal}\n" } VipSetChannelEpgId() { echo inquirer text_input "请输入频道 epg id: " vip_channel_epg_id "$i18n_not_set" if [ "$vip_channel_epg_id" == "$i18n_not_set" ] then vip_channel_epg_id="" fi } VipAddChannel() { echo add_vip_channel_options=( '选择频道' '手动输入频道' ) inquirer list_input_index "添加方式" add_vip_channel_options add_vip_channel_options_index if [ "$add_vip_channel_options_index" -eq 0 ] then if [ ! -f "$IPTV_ROOT/VIP" ] then Println "$info 获取频道中..." if ! curl -s -L "$VIP_CHANNELS_LINK" -o "${VIP_CHANNELS_FILE}_tmp" then if [ ! -s "$VIP_CHANNELS_FILE" ] then Println "$error 暂时无法获取频道, 请稍后再试\n" exit 1 fi Println "$error 更新频道失败, 使用原有频道\n" else mv "${VIP_CHANNELS_FILE}_tmp" "$VIP_CHANNELS_FILE" fi fi [ -z "${delimiters:-}" ] && delimiters=( $'\001' $'\002' $'\003' $'\004' $'\005' $'\006' ) EXIT_STATUS=0 IFS=$'\003\t' read -r m_name m_url < <(JQs flat "$VIP_CHANNELS_FILE" '.[0]|map(select(.url // ""|test("http://?.*";"i")))' '. as $channels | reduce ({name,url}|keys_unsorted[]) as $key ([]; $channels[$key] as $val | if $val then . + [$val + "\u0002\u0003"] else . + ["\u0003"] end )|@tsv' "${delimiters[@]}") || EXIT_STATUS=$? if [ "$EXIT_STATUS" -ne 0 ] || [ -z "$m_name" ] then Println "$error 没有找到频道\n" exit 1 fi IFS="${delimiters[1]}" read -r -a vip_channels_name <<< "$m_name" IFS="${delimiters[1]}" read -r -a vip_channels_url <<< "$m_url" vip_channels_host_ip=() vip_channels_host_port=() vip_channels_id=() for vip_channel_url in "${vip_channels_url[@]}" do if [ -n "$vip_channel_url" ] && [[ $vip_channel_url =~ ^http://?([^/]+):([^/]+)/([^/]+) ]] then vip_channels_host_ip+=("${BASH_REMATCH[1]}") vip_channels_host_port+=("${BASH_REMATCH[2]}") vip_channels_id+=("${BASH_REMATCH[3]}") fi done vip_channels_list="" vip_channels_count=${#vip_channels_id[@]} vip_channels_epg_id=("${vip_channels_id[@]}") flag=0 for((i=0;i "$VIP_FILE" fi new_host=$( $JQ_FILE -n --arg ip "$vip_host_ip" --arg port "$vip_host_port" \ --arg seed "$vip_host_seed" --arg token "$vip_host_token" \ --arg status "$vip_host_status" \ '{ ip: $ip, port: $port | tonumber, seed: $seed, token: $token, status: $status, channels: [] }' ) jq_path='["hosts"]' JQ add "$VIP_FILE" "[$new_host]" Println "$info $vip_channel_host_ip:$vip_channel_host_port 服务器添加成功\n" VipGetHosts i=$((vip_hosts_count-1)) else skip_hosts="$skip_hosts$vip_channel_host_ip:$vip_channel_host_port " continue fi fi map_string=true jq_path='["hosts",'"$i"',"channels"]' JQ delete "$VIP_FILE" id "$vip_channel_id" new_channel=$( $JQ_FILE -n --arg id "$vip_channel_id" --arg name "$vip_channel_name" \ --arg epg_id "$vip_channel_epg_id" \ '{ id: $id, name: $name, epg_id: $epg_id }' ) jq_path='["hosts",'"$i"',"channels"]' JQ add "$VIP_FILE" "[$new_channel]" Println "$info $vip_channel_name 添加成功" done break ;; esac done exit fi VipListHosts echo -e "选择 VIP 服务器" while read -p "$i18n_default_cancel" vip_hosts_num do case "$vip_hosts_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$vip_hosts_num" -gt 0 ] && [ "$vip_hosts_num" -le "$vip_hosts_count" ] then vip_hosts_index=$((vip_hosts_num-1)) vip_host_ip=${vip_hosts_ip[vip_hosts_index]} vip_host_port=${vip_hosts_port[vip_hosts_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done # awk -v ORS=" " '$1 { print $0; } END { printf("\n"); }' echo inquirer list_input "是否批量添加" ny_options vip_bulk_add if [[ $vip_bulk_add == "$i18n_yes" ]] then Println "请输入频道 ID, 同时也是目录名称和频道名称, 用空格分隔" read -p "$i18n_default_cancel" vip_channel IFS=" " read -r -a vip_channels <<< "$vip_channel" new_channels="" for vip_channel in "${vip_channels[@]}" do new_channel=$( $JQ_FILE -n --arg id "$vip_channel" --arg name "$vip_channel" \ '{ id: $id, name: $name }' ) [ -n "$new_channels" ] && new_channels="$new_channels," new_channels="$new_channels$new_channel" done jq_path='["hosts",'"$vip_hosts_index"',"channels"]' JQ add "$VIP_FILE" "[$new_channels]" Println "$info 批量添加成功\n" else VipSetChannelId VipSetChannelName VipSetChannelEpgId new_channel=$( $JQ_FILE -n --arg id "$vip_channel_id" --arg name "$vip_channel_name" \ --arg epg_id "$vip_channel_epg_id" \ '{ id: $id, name: $name, epg_id: $epg_id }' ) jq_path='["hosts",'"$vip_hosts_index"',"channels"]' JQ add "$VIP_FILE" "[$new_channel]" Println "$info 频道 $vip_channel_name 添加成功\n" fi } VipDeployChannel() { VipListChannel deploy_options=( '快速部署(使用默认值)' '手动部署' ) inquirer list_input_index "选择操作" deploy_options deploy_options_index for vip_channels_num in "${vip_channels_num_arr[@]}" do vip_channels_index=$((vip_channels_num-1)) vip_channel_id=${vip_channels_id[vip_channels_index]} vip_channel_name=${vip_channels_name[vip_channels_index]} if [ -n "${vip_public_host:-}" ] then stream_link="$vip_public_host/vip/$vip_user_license/${vip_host_ip//./}$vip_host_port/$vip_channel_id/playlist.m3u8" else stream_link="$VIP_USERS_ROOT/$vip_user_license/${vip_host_ip//./}$vip_host_port/$vip_channel_id/playlist.m3u8" fi if [ "$deploy_options_index" -eq 0 ] then $SH_FILE -i "$stream_link" -z "$vip_channel_name" -o "$vip_channel_id" > /dev/null Println "$info 频道 [ $vip_channel_name ] 添加成功" continue fi user_agent="" headers="" cookies="" stream_links=("$stream_link") Println "$info 添加频道 [ $vip_channel_name ]\n\n" inquirer list_input "是否推流 flv" ny_options add_channel_flv_yn if [ "$add_channel_flv_yn" == "$i18n_yes" ] then kind="flv" else kind="" fi skip_set_stream_link=true AddChannel done } VipEditChannel() { VipListChannels echo -e "$tip 多个频道用空格分隔, 比如 5 7 9-11" while read -p "请选择频道: " vip_channels_num do [ -z "$vip_channels_num" ] && Println "$i18n_canceled...\n" && exit 1 IFS=" " read -ra vip_channels_num_arr <<< "$vip_channels_num" error_no=0 for vip_channel_num in "${vip_channels_num_arr[@]}" do case "$vip_channel_num" in *"-"*) vip_channel_num_start=${vip_channel_num%-*} vip_channel_num_end=${vip_channel_num#*-} if [[ $vip_channel_num_start == *[!0-9]* ]] || [[ $vip_channel_num_end == *[!0-9]* ]] || [ "$vip_channel_num_start" -eq 0 ] || [ "$vip_channel_num_end" -eq 0 ] || [ "$vip_channel_num_end" -gt "$vip_channels_count" ] || [ "$vip_channel_num_start" -ge "$vip_channel_num_end" ] then error_no=3 break fi ;; *[!0-9]*) error_no=1 break ;; *) if [ "$vip_channel_num" -lt 1 ] || [ "$vip_channel_num" -gt "$vip_channels_count" ] then error_no=2 break fi ;; esac done case "$error_no" in 1|2|3) Println "$error $i18n_input_correct_number\n" ;; *) declare -a new_array for element in "${vip_channels_num_arr[@]}" do if [[ $element =~ - ]] then start=${element%-*} end=${element#*-} for((i=start;i<=end;i++)); do new_array+=("$i") done else new_array+=("$element") fi done vip_channels_num_arr=("${new_array[@]}") unset new_array for vip_channels_num in "${vip_channels_num_arr[@]}" do vip_channels_index=$((vip_channels_num-1)) vip_channel_id=${vip_channels_id[vip_channels_index]} vip_channel_name=${vip_channels_name[vip_channels_index]} vip_channel_epg_id=${vip_channels_epg_id[vip_channels_index]} Println " 选择修改频道 ${green}[ $vip_channel_name ]${normal} 内容 ${green}1.${normal} 修改频道 ID ${green}2.${normal} 修改频道名称 ${green}3.${normal} 修改频道 epg " read -p "$i18n_default_cancel" edit_vip_channel_num case $edit_vip_channel_num in 1) Println "原频道 ID: ${red}$vip_channel_id${normal}" VipSetChannelId jq_path='["hosts",'"$vip_hosts_index"',"channels",'"$vip_channels_index"',"id"]' JQ update "$VIP_FILE" "$vip_channel_id" Println "$info 频道 ID 修改成功\n" ;; 2) Println "原频道名称: ${red}$vip_channel_name${normal}" VipSetChannelName jq_path='["hosts",'"$vip_hosts_index"',"channels",'"$vip_channels_index"',"name"]' JQ update "$VIP_FILE" "$vip_channel_name" Println "$info 频道名称修改成功\n" ;; 3) Println "原频道 epg: ${red}${vip_channel_epg_id:-无}${normal}" VipSetChannelEpgId jq_path='["hosts",'"$vip_hosts_index"',"channels",'"$vip_channels_index"',"epg_id"]' JQ update "$VIP_FILE" "$vip_channel_epg_id" Println "$info 频道 epg 修改成功\n" ;; *) Println "$i18n_canceled...\n" && exit 1 ;; esac done break ;; esac done } VipListChannels() { VipListHosts echo -e " ${green}$((vip_hosts_count+1)).${normal}${indent_6}全部\n\n" while read -p "选择 VIP 服务器: " vip_hosts_num do case "$vip_hosts_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; "$((vip_hosts_count+1))") unset vip_hosts_index return 0 ;; *) if [ "$vip_hosts_num" -gt 0 ] && [ "$vip_hosts_num" -le "$vip_hosts_count" ] then vip_hosts_index=$((vip_hosts_num-1)) vip_host_ip=${vip_hosts_ip[vip_hosts_index]} vip_host_port=${vip_hosts_port[vip_hosts_index]} vip_host_seed=${vip_hosts_seed[vip_hosts_index]} vip_host_token=${vip_hosts_token[vip_hosts_index]} vip_channel_id=${vip_hosts_channel_id[vip_hosts_index]} vip_channel_name=${vip_hosts_channel_name[vip_hosts_index]} vip_channel_epg_id=${vip_hosts_channel_epg_id[vip_hosts_index]} IFS="|" read -r -a vip_channels_id <<< "$vip_channel_id" IFS="|" read -r -a vip_channels_name <<< "$vip_channel_name" IFS="|" read -r -a vip_channels_epg_id <<< "${vip_channel_epg_id}|" break else Println "$error $i18n_input_correct_no\n" fi ;; esac done vip_channels_list="" vip_channels_count=${vip_hosts_channel_count[vip_hosts_index]} flag=0 for((i=0;i "$VIP_USERS_ROOT/$vip_user_license/${vip_host_ip//./}$vip_host_port/${vip_channels_id[k]}/playlist.m3u8" if [ "$k" -eq 0 ] then m3u_list="$m3u_list#EXTINF:-1,===== $vip_host_ip:$vip_host_port ${vip_channels_name[k]} =====\n$stream_link\n" else m3u_list="$m3u_list#EXTINF:-1,${vip_channels_name[k]}\n$stream_link\n" fi epg_id=${vip_channels_epg_id[k]} if [ -n "$epg_id" ] && [ -n "${schedules_id:-}" ] then for((m=0;m<${#schedules_id[@]};m++)); do if [ "${schedules_id[m]}" == "$epg_id" ] && [ -n "${schedules_sys_time[m]}" ] then IFS="^" read -r -a sys_times <<< "${schedules_sys_time[m]}^" IFS="^" read -r -a titles <<< "${schedules_title[m]}^" epg_list="$epg_list\n${vip_channels_name[k]}\n\n" programs_count=${#sys_times[@]} for((n=0;n\n${titles[n]}\n\n" done break fi done fi } VipGetSchedules() { GetDefault if [ -n "$d_schedule_file" ] && [ -s "$d_schedule_file" ] then schedules_id=() schedules_sys_time=() schedules_title=() while IFS="%" read -r schedule_id schedule_sys_time schedule_tile do schedules_id+=("${schedule_id#\"}") schedules_sys_time+=("$schedule_sys_time") schedules_title+=("${schedule_tile%\"}") done < <($JQ_FILE -M 'to_entries[]|[.key,([.value[].sys_time]|join("^")),([.value[].title]|join("^"))]|join("%")' "$d_schedule_file") fi } VipMonitor() { trap '' HUP INT trap 'MonitorErr $LINENO' ERR delete_on_term="${vip_public_root:-notfound}/vip" pid_file="$IPTV_ROOT/vip.pid" printf '%s' "$BASHPID" > "$pid_file" { flock -x 205 { MonitorLog "启动 VIP PID $BASHPID !" printf -v now '%(%s)T' -1 never=$((now+86400*720)) VipGetSchedules ct2=$(date +%s%3N) clear=$(date --utc -d 'tomorrow 00:00:10' +%s) while true do if [ "$now" -ge "$clear" ] then ct2=$(date +%s%3N) clear=$(date --utc -d 'tomorrow 00:00:10' +%s) fi if [ "$vip_hosts_count" -gt 0 ] && [ "$vip_users_count" -gt 0 ] then if [ -e "$VIP_USERS_ROOT/epg.update" ] then VipGetSchedules fi epg_update=1 for((i=0;i "$VIP_USERS_ROOT/$vip_user_license/license.json" fi m3u_list="" epg_list="" program_id=0 for((j=0;j "$VIP_USERS_ROOT/$vip_user_license/playlist.m3u" if { [ "$epg_update" -eq 1 ] || [ -e "$VIP_USERS_ROOT/epg.update" ]; } && [ -n "$epg_list" ] then epg_update=0 echo -e "\n\n$epg_list" > "$VIP_USERS_ROOT/epg.xml.new" mv "$VIP_USERS_ROOT/epg.xml.new" "$VIP_USERS_ROOT/epg.xml" rm -f "$VIP_USERS_ROOT/epg.update" fi elif [ -d "$VIP_USERS_ROOT/$vip_user_license" ] then rm -rf "$VIP_USERS_ROOT/${vip_user_license:-notfound}" fi done fi PrepTerm sleep $sleep_time & WaitTerm vip_users_license_old=("${vip_users_license[@]}") vip_hosts_channel_id_old=("${vip_hosts_channel_id[@]}") VipGetHosts VipGetUsers for vip_user_license_old in ${vip_users_license_old[@]+"${vip_users_license_old[@]}"} do for vip_user_license in ${vip_users_license[@]+"${vip_users_license[@]}"} do if [ "$vip_user_license" == "$vip_user_license_old" ] then for vip_host_channel_id_old in ${vip_hosts_channel_id_old[@]+"${vip_hosts_channel_id_old[@]}"} do for vip_host_channel_id in ${vip_hosts_channel_id[@]+"${vip_hosts_channel_id[@]}"} do if [ "$vip_host_channel_id" == "$vip_host_channel_id_old" ] then break 4 fi done IFS="|" read -r -a vip_channels_id_old <<< "$vip_host_channel_id_old" for vip_channel_id_old in "${vip_channels_id_old[@]}" do for vip_host_channel_id in ${vip_hosts_channel_id[@]+"${vip_hosts_channel_id[@]}"} do IFS="|" read -r -a vip_channels_id <<< "$vip_host_channel_id" for vip_channel_id in "${vip_channels_id[@]}" do if [ "$vip_channel_id" == "$vip_channel_id_old" ] then break 6 fi done done rm -rf "$VIP_USERS_ROOT/$vip_user_license_old/${vip_host_ip//./}$vip_host_port/${vip_channel_id_old:-notfound}" done done break 2 fi done rm -rf "$VIP_USERS_ROOT/${vip_user_license_old:-notfound}" done printf -v now '%(%s)T' -1 done } 205>&- } 205<"$pid_file" } VipEnable() { # deprecated if [ -s "/tmp/vip.pid" ] && kill -0 "$(< /tmp/vip.pid)" 2> /dev/null then Println "$error VIP 已开启\n" && exit 1 fi if [ -s "$IPTV_ROOT/vip.pid" ] && kill -0 "$(< $IPTV_ROOT/vip.pid)" 2> /dev/null then Println "$error VIP 已开启\n" && exit 1 fi if [ ! -s "$VIP_FILE" ] then Println "$error 请先添加 VIP 服务器\n" && exit 1 fi echo inquirer text_input "输入检测间隔(秒): " sleep_time 86400 VipGetHosts if [ "$vip_hosts_count" -gt 0 ] then VipGetUsers if [ "$vip_users_count" -gt 0 ] then if [ ! -e "$MD5SUM_FILE" ] then Println "$info 安装 md5sum..." if [[ ! -x $(command -v gcc) ]] then if [ "$dist" == "rpm" ] then yum -y install gcc gcc-c++ >/dev/null 2>&1 else apt-get -y install build-essential >/dev/null 2>&1 fi fi mkdir -p "$C_ROOT" wget --timeout=10 --tries=1 --no-check-certificate "$MD5SUM_LINK" -qO "$MD5SUM_FILE.c" \ || wget --timeout=10 --tries=3 --no-check-certificate "$MD5SUM_LINK_FALLBACK" -qO "$MD5SUM_FILE.c" gcc -Wall -O3 -o "$MD5SUM_FILE" "$MD5SUM_FILE.c" Println "$info md5sum 安装成功" fi if [ -z "${vip_public_root:-}" ] then VipConfig fi [ -n "$vip_public_root" ] && ln -sfT "$VIP_USERS_ROOT" "$vip_public_root/vip" ( VipMonitor ) > /dev/null 2> /dev/null < /dev/null & Println "$info VIP 开启成功\n" else Println "$error 请先添加用户\n" && exit 1 fi else Println "$error 请先添加 VIP 服务器\n" && exit 1 fi } VipDisable() { # deprecated if [ -s "/tmp/vip.pid" ] then vip_pid=$(< /tmp/vip.pid) if kill -0 "$vip_pid" 2> /dev/null then kill "$vip_pid" 2> /dev/null MonitorLog "关闭 VIP PID $vip_pid !" Println "$info VIP 关闭成功\n" else Println "$error VIP 未开启\n" fi rm -f "/tmp/vip.pid" elif [ -s "$IPTV_ROOT/vip.pid" ] then PID=$(< "$IPTV_ROOT/vip.pid") if kill -0 "$PID" 2> /dev/null then Println "$info 关闭 VIP, 稍等..." kill "$PID" 2> /dev/null if flock -E 1 -w 20 -x "$IPTV_ROOT/vip.pid" rm -f "$IPTV_ROOT/vip.pid" then MonitorLog "关闭 VIP PID $PID !" Println "$info VIP 关闭成功 !\n" else Println "$error VIP 关闭超时, 请重试\n" exit 1 fi else rm -f "$IPTV_ROOT/vip.pid" Println "$error VIP 未开启\n" fi else [ -e "$IPTV_ROOT/vip.pid" ] && rm -f "$IPTV_ROOT/vip.pid" Println "$error VIP 未开启\n" fi } VipListUserChannel() { if [ ! -s "$VIP_FILE" ] then Println "$error 请先输入授权码, 加微信 woniuzfb 或 tg @ woniuzfb\n" else VipGetUsers printf -v now '%(%s)T' -1 vip_users_list="" for((i=0;i /dev/null) then if [ ! -s "$VIP_FILE" ] then printf '{"%s":{},"%s":[],"%s":[]}' "config" "users" "hosts" > "$VIP_FILE" fi while IFS= read -r license_ip do map_string=true jq_path='["users"]' JQ delete "$VIP_FILE" ip "$license_ip" done < <($JQ_FILE -r '.ip' <<< "$vip_user") jq_path='["users"]' JQ add "$VIP_FILE" "[$vip_user]" Println "$info 授权码验证成功\n" else Println "$error 授权码验证失败, 请联系微信 woniuzfb 或 tg @ woniuzfb\n" fi } VipUserMenu() { Println " `gettext \"VIP 面板\"` ${red}1.${normal} `gettext \"查看 VIP 频道\"` ${red}2.${normal} `gettext \"输入 VIP 授权码\"` `eval_gettext \"\\\$tip 输入: h 切换到 HLS 面板, f 切换到 FLV 面板\"`\n\n" read -p "`gettext \"输入序号\"` [1-2]: " vip_menu_num case "$vip_menu_num" in h) kind="" color=${green} Menu ;; f) kind="flv" color=${blue} Menu ;; 1) VipListUserChannel ;; 2) VipVerifyLicense ;; *) Println "$error $i18n_input_correct_number [1-2]\n" ;; esac } VipMenu() { [ ! -d "$IPTV_ROOT" ] && Println "`eval_gettext \"\\\$error 请先输入 tv 安装 !\"`\n" && exit 1 if [ ! -f "$IPTV_ROOT/VIP" ] then VipUserMenu return 0 fi Println " `gettext \"VIP 面板\"` ${red}1.${normal} `gettext \"查看 VIP 用户\"` ${red}2.${normal} `gettext \"添加 VIP 用户\"` ${red}3.${normal} `gettext \"设置 VIP 用户\"` ${red}4.${normal} `gettext \"查看 VIP 频道\"` ${red}5.${normal} `gettext \"添加 VIP 频道\"` ${red}6.${normal} `gettext \"部署 VIP 频道\"` ${red}7.${normal} `gettext \"设置 VIP 频道\"` ${red}8.${normal} `gettext \"查看 VIP 服务器\"` ${red}9.${normal} `gettext \"添加 VIP 服务器\"` ${red}10.${normal} `gettext \"设置 VIP 服务器\"` ${red}11.${normal} `gettext \"删除 VIP 用户\"` ${red}12.${normal} `gettext \"删除 VIP 频道\"` ${red}13.${normal} `gettext \"删除 VIP 服务器\"` ${red}14.${normal} `gettext \"开启 VIP\"` ${red}15.${normal} `gettext \"关闭 VIP\"` `eval_gettext \"\\\$tip 输入: h 切换到 HLS 面板, f 切换到 FLV 面板\"`\n\n" read -p "`gettext \"输入序号\"` [1-15]: " vip_menu_num case "$vip_menu_num" in h) kind="" color=${green} Menu ;; f) kind="flv" color=${blue} Menu ;; 1) VipListUser ;; 2) VipAddUser ;; 3) VipEditUser ;; 4) VipListChannel ;; 5) VipAddChannel ;; 6) VipDeployChannel ;; 7) VipEditChannel ;; 8) VipListHosts ;; 9) VipAddHost ;; 10) VipEditHost ;; 11) VipDelUser ;; 12) VipDelChannel ;; 13) VipDelHost ;; 14) VipEnable ;; 15) VipDisable ;; *) Println "$error $i18n_input_correct_number [1-15]\n" ;; esac } AptSetSources() { echo apt_sources_options=( '国内' '国外' ) inquirer list_input_index "选择源" apt_sources_options apt_sources_options_index if [ "$apt_sources_options_index" -eq 0 ] then sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list sed -i 's/ftp.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list sed -i 's|security.debian.org|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list if [ -f "/etc/apt/sources.list.d/armbian.list" ] then sed -i 's|http[s]*://apt.armbian.com|http://mirrors.ustc.edu.cn/armbian|g' /etc/apt/sources.list.d/armbian.list sed -i 's|http://mirrors.nju.edu.cn/armbian|http://mirrors.ustc.edu.cn/armbian|g' /etc/apt/sources.list.d/armbian.list fi if [ -f "/etc/apt/sources.list.d/docker.list" ] then curl -fsSL http://mirrors.ustc.edu.cn/docker-ce/linux/debian/gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] http://mirrors.ustc.edu.cn/docker-ce/linux/debian \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null fi else sed -i 's/mirrors.ustc.edu.cn/deb.debian.org/g' /etc/apt/sources.list sed -i 's|mirrors.ustc.edu.cn/debian-security|security.debian.org|g' /etc/apt/sources.list if [ -f "/etc/apt/sources.list.d/armbian.list" ] then sed -i 's|http://mirrors.nju.edu.cn/armbian|http://apt.armbian.com|g' /etc/apt/sources.list.d/armbian.list sed -i 's|http://mirrors.ustc.edu.cn/armbian|http://apt.armbian.com|g' /etc/apt/sources.list.d/armbian.list sed -i 's/https:/http:/g' /etc/apt/sources.list.d/armbian.list fi if [ -f "/etc/apt/sources.list.d/docker.list" ] then curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null fi fi Println "$info 源更改成功\n" } VimConfig() { if [[ ! -x $(command -v vim) ]] then Println "$info 安装 vim ..." AptUpdate apt-get -y install vim fi if [ -e ~/.vimrc ] then echo ExitOnList n "`gettext \"将安装 vim-plug 并覆盖 ~/.vimrc , 是否继续\"`" fi if curl -s -fLo ~/.vim/autoload/plug.vim --create-dirs "$FFMPEG_MIRROR_LINK/vim-plug.vim" then printf '%s' " call plug#begin('~/.vim/plugged') Plug 'preservim/nerdcommenter' Plug 'ryanpcmcquen/fix-vim-pasting' call plug#end() set number set mouse=a set tabstop=2 set shiftwidth=2 set expandtab autocmd BufRead,BufNewFile *.conf setfiletype conf filetype indent off " > ~/.vimrc DepInstall git Println "$info vimrc 设置完成, 请在 vim 下执行 PlugInstall\n" else Println "$error 无法连接服务器, 请稍后再试\n" fi } PveGetVMs() { pve_vm_count=0 IFS=" " read -r m_id m_name m_status m_mem m_boot_disk m_pid < <(qm list | awk '$1 {a=a $1",";b=b $2",";c=c $3",";d=d $4",";e=e $5",";f=f $6","} END {print a,b,c,d,e,f}') if [ -n "${m_id#*,}" ] then IFS="," read -r -a pve_vm_ids <<< "${m_id#*,}" IFS="," read -r -a pve_vm_name <<< "${m_name#*,}" IFS="," read -r -a pve_vm_status <<< "${m_status#*,}" IFS="," read -r -a pve_vm_mem <<< "${m_mem#*,}" IFS="," read -r -a pve_vm_boot_disk <<< "${m_boot_disk#*,}" IFS="," read -r -a pve_vm_pid <<< "${m_pid#*,}" pve_vm_count=${#pve_vm_ids[@]} fi } PveListVMs() { PveGetVMs if [ "$pve_vm_count" -eq 0 ] then Println "$error 没有虚拟机\n" exit 1 fi pve_vm_list="" for((i=0;i dnscrypt-proxy.toml echo "$(awk '!x{x=sub(/.* stamp = .*/," stamp = \047sdns://AgAAAAAAAAAAACCY49XlNq8pWM0vfxT3BO9KJ20l4zzWXy5l9eTycnwTMA5kbnMuYWxpZG5zLmNvbQovZG5zLXF1ZXJ5\047")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml echo "$(awk '!x{x=sub(/.*server_names = \[.*/,"server_names = [\047dnspod-doh\047,\047alidns-doh-fix\047]")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml echo "$(awk '!x{x=sub(/.*bootstrap_resolvers = .*/,"bootstrap_resolvers = [\047119.29.29.29:53\047, \047180.76.76.76:53\047, \0471.1.1.1:53\047, \047114.114.114.114:53\047, \0478.8.8.8:53\047]")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml echo "$(awk '!x{x=sub(/.*netprobe_address = .*/,"netprobe_address = \0471.1.1.1:53\047")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml else echo "$(awk '!x{x=sub(/.*server_names = \[.*/,"server_names = [\047google\047, \047cloudflare\047]")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml fi echo "$(awk '!x{x=sub(/^listen_addresses = .*/,"listen_addresses = [\047[::]:'"${listen_port:-53}"'\047]")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml echo "$(awk '!x{x=sub(/.*block_ipv6 = .*/,"block_ipv6 = '"${block_ipv6:-false}"'")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml echo "$(awk '!x{x=sub(/.*require_dnssec = .*/,"require_dnssec = '"${require_dnssec:-true}"'")}1' dnscrypt-proxy.toml)" > dnscrypt-proxy.toml } Menu() { color=${color:-${green}} if [ -z "${kind:-}" ] then title="HLS" msg=$(gettext "输入: f 切换到 FLV 面板, v 切换到 VIP 面板") elif [ "$kind" == "flv" ] then title="FLV" msg=$(gettext "输入: h 切换到 HLS 面板, v 切换到 VIP 面板") fi Println " ${dim_underlined}[AI]OS | @woniuzfb${normal} `gettext \"IPTV 一键管理脚本\"` ${red}[v$sh_ver]${normal} ${color}1.${normal} `gettext \"安装\"` ${color}2.${normal} `gettext \"卸载\"` ${color}3.${normal} `gettext \"升级\"` ———————————— ${color}4.${normal} `gettext \"查看频道\"` ${color}5.${normal} `gettext \"添加频道\"` ${color}6.${normal} `gettext \"修改频道\"` ${color}7.${normal} `gettext \"开关频道\"` ${color}8.${normal} `gettext \"重启频道\"` ${color}9.${normal} `gettext \"查看日志\"` ${color}10.${normal} `gettext \"删除频道\"` ${color}11.${normal} `gettext \"设置计划\"` ${color}12.${normal} `gettext \"设置监控\"` ${color}13.${normal} `gettext \"修改默认\"` `eval_gettext \"\\\$tip 当前: \\\${green}\\\$title\\\${normal} 面板\"` $tip $msg\n\n" read -p "`gettext \"输入序号\"` [1-13]: " menu_num case "$menu_num" in h) kind="" color=${green} Menu ;; f) kind="flv" color=${blue} Menu ;; v) VipMenu ;; 1) Install ;; 2) Uninstall ;; 3) Update ;; 4) ViewChannel ;; 5) AddChannel ;; 6) EditChannelMenu ;; 7) ToggleChannel ;; 8) RestartChannel ;; 9) ViewChannelLog ;; 10) DelChannel ;; 11) ScheduleMenu ;; 12) MonitorMenu ;; 13) EditDefaultMenu ;; *) Println "$error $i18n_input_correct_number [1-13]\n" ;; esac } Usage() { Println " `gettext \"使用方法: tv -i [直播源] [-s 分片时长(秒)] [-o 输出目录名称] [-c m3u8包含的分片数] [-b 码率] [-r 分辨率] [-p m3u8文件名称] [-C] [-R] [-l] [-P http代理]\"` `gettext \" -i 直播源(支持 mpegts / hls / flv / youtube ...)\"` `gettext \"可以是视频路径\"` `gettext \"可以输入不同链接地址(监控按顺序尝试使用), 用空格分隔\"` `gettext \" -s 分片时长(秒)(默认: 6)\"` `gettext \" -o 输出目录名称(默认: 随机名称)\"` `gettext \" -l 非无限时长直播, 无法设置切割的分片数且无法监控(默认: 不设置)\"` `gettext \" -P FFmpeg 的 http 代理, 直播源是 http 链接时可用(默认: 不设置)\"` `gettext \" -p m3u8名称(前缀)(默认: 随机)\"` `gettext \" -c m3u8里包含的分片数目(默认: 5)\"` `gettext \" -S 分片所在子目录名称(默认: 不使用子目录)\"` `gettext \" -t 分片名称(前缀)(默认: 跟m3u8名称相同)\"` `gettext \" -a 音频编码(默认: aac) (不需要转码时输入 copy)\"` `gettext \" -v 视频编码(默认: libx264) (不需要转码时输入 copy)\"` `gettext \" -f 画面或声音延迟(格式如: v_3 画面延迟3秒, a_2 声音延迟2秒 画面声音不同步时使用)\"` `gettext \" -d dvb teletext 字幕解码成的格式,可选: text,ass (默认: 不设置)\"` `gettext \" -q CRF 固定质量因子, 多个 CRF 用逗号分隔(默认: 不设置)\"` `gettext \"如果同时设置了输出视频码率, 则优先使用 CRF 值控制视频质量\"` `gettext \"取值每 +/- 6 会大概导致码率的减半或加倍\"` `gettext \"x264 和 x265 取值范围为 [0,51]\"` `gettext \"x264 的默认值是 23, 视觉无损值 18\"` `gettext \"x265 的默认值是 28, 视觉无损值 24\"` `gettext \"VP9 取值范围为 [0,63], 建议取值范围为 [15,35]\"` `gettext \" -b 输出视频的码率(k)(多个用逗号分隔 比如: 800,1000,1500)(默认: 900)\"` `gettext \"如果已经设置 CRF 固定质量因子, 用于 VBV 的 -maxrate 和 -bufsize (capped CRF)\"` `gettext \"如果没有设置 CRF 固定质量因子, 用于指定输出视频码率(ABR 或 CBR)\"` `gettext \"可以输入 omit 省略此选项\"` `gettext \" -r 输出视频的分辨率(多个用逗号分隔 比如: 960x540,1280x720)(默认: 1280x720)\"` `gettext \" -C 限制性编码(设置码率的情况下有效)(默认: 否)\"` `gettext \"如果已经设置 CRF 固定质量因子, 使用限制性编码 VBV (capped CRF)\"` `gettext \"如果没有设置 CRF 固定质量因子, 使用限制性编码 VBV (ABR)\"` `gettext \" -R 固定码率 CBR (设置 -C 情况下有效)(默认: 否)\"` `gettext \" -e 加密分片(默认: 不加密)\"` `gettext \" -K Key名称(默认: 随机)\"` `gettext \" -z 频道名称(默认: 跟m3u8名称相同)\"` `gettext \"也可以不输出 HLS, 比如 flv 推流\"` `gettext \" -k 设置推流类型, 比如 -k flv\"` `gettext \" -H 推流 h265(默认: 不设置)\"` `gettext \" -T 设置推流地址, 比如 rtmp://127.0.0.1/flv/xxx\"` `gettext \" -L 输入拉流(播放)地址(可省略), 比如 http://domain.com/flv?app=flv&stream=xxx\"` `gettext \" -m FFmpeg 额外的 输入参数\"` (`gettext \"默认:\"` -copy_unknown -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2000 -rw_timeout 10000000 -y -nostats -nostdin -hide_banner -loglevel error) `gettext \"如果输入的直播源是 hls 链接, 需去除 -reconnect_at_eof 1\"` `gettext \"如果输入的直播源是 rtmp 或本地链接, 需去除 -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2000\"` `gettext \" -n FFmpeg 额外的 输出参数, 可以输入 omit 省略此选项\"` (`gettext \"默认:\"` -g 60 -sc_threshold 0 -sn -preset superfast -pix_fmt yuv420p -profile:v main) `gettext \"举例:\"` `gettext \"使用 CRF 固定质量因子控制视频质量:\"` `gettext \"tv -i http://xxx.com/xxx.ts -s 6 -o hbo1 -p hbo1 -q 15 -b 1500 -r 1280x720 -z 'hbo直播1'\"` `gettext \"使用码率控制视频质量[默认]:\"` `gettext \"tv -i http://xxx.com/xxx.ts -s 6 -o hbo2 -p hbo2 -b 900 -r 1280x720 -z 'hbo直播2'\"` `gettext \"不需要转码的设置:\"` -a copy -v copy -n omit `gettext \"不输出 HLS, 推流 flv:\"` tv -i http://xxx/xxx.ts -a aac -v libx264 -b 3000 -k flv -T rtmp://127.0.0.1/flv/xxx `gettext \"快捷键:\"` `gettext \"tv 打开 HLS 管理面板\"` `gettext \"tv l 列出所有开启的频道\"` `gettext \"tv d 添加演示频道\"` `gettext \"tv e 手动修改 channels.json\"` `gettext \"tv ee 手动修改 sync_file\"` `gettext \"tv f 打开 FLV 管理面板\"` `gettext \"tv v 打开 VIP 面板\"` `gettext \"tv m 开启监控\"` `gettext \"tv m l [行数] 查看监控日志\"` `gettext \"tv m s 关闭监控\"` `gettext \"tv s 节目表管理面板\"` `gettext \"tv 4g 打开 4gtv 频道管理面板\"` `gettext \"tv FFmpeg 自建 FFmpeg 镜像\"` `gettext \"tv debug 1/0 开启/关闭 调试\"` `gettext \"cx 打开 xtream codes 账号/频道管理面板\"` `gettext \"v2 打开 v2ray 面板\"` `gettext \"v2 e 手动修改 config.json\"` `gettext \"x 打开 xray 面板\"` `gettext \"x e 手动修改 config.json\"` `gettext \"nx 打开 nginx 面板\"` `gettext \"or 打开 openresty 面板\"` `gettext \"cf 打开 cloudflare partner / workers 面板\"` `gettext \"cf w 打开 cloudflare workers 面板\"` `gettext \"ibm 打开 IBM Cloud Foundry 面板\"` `gettext \"ibm v2 打开 ibm v2ray app 管理面板\"` `gettext \"ibm x 打开 ibm xray app 管理面板\"` `gettext \"arm 打开 Armbian 管理面板\"` `gettext \"pve 打开 Proxmox VE 管理面板\"` `gettext \"tv backup 备份所有重要文件\"` `gettext \"tv ed 选择默认编辑器\"` `gettext \"tv a 设置自定义命令\"` `gettext \"tv color 自定义文字颜色和背景颜色\"` `gettext \"tv c 切换/更新 语言\"` " exit } UpdateSelf() { if [ ! -e "$JQ_FILE" ] then echo ExitOnList y "`gettext \"检测到安装未完成, 是否卸载重装\"`" Uninstall Install exit 0 fi GetDefault if [ "${d_version%.*}" != "${sh_ver%.*}" ] then major_ver=${d_version%%.*} minor_ver=${d_version#*.} minor_ver=${minor_ver%%.*} if [ "$minor_ver" -lt 35 ] then Println "$info 需要先关闭所有频道, 请稍等...\n" StopChannelsForce rm -rf "/tmp/flv.lockdir/" rm -rf "/tmp/monitor.lockdir" rm -f "$FFMPEG_LOG_ROOT/"*.lock fi Println "$info 更新中, 请稍等...\n" printf -v update_date '%(%m-%d-%H:%M:%S)T' -1 cp -f "$CHANNELS_FILE" "${CHANNELS_FILE}_$update_date" GetChannels while [[ $d_headers =~ \\\\ ]] do d_headers=${d_headers//\\\\/\\} done if [[ ! $d_input_flags =~ -copy_unknown ]] then d_input_flags="-copy_unknown $d_input_flags" fi d_input_flags=${d_input_flags//-timeout 2000000000/-rw_timeout 10000000} if [ "$minor_ver" -lt 83 ] then if [ "$d_const" == "yes" ] then d_const=true else d_const=false fi if [ "$d_encrypt" == "yes" ] then d_encrypt=true else d_encrypt=false fi if [ "$d_encrypt_session" == "yes" ] then d_encrypt_session=true else d_encrypt_session=false fi if [ "$d_sync" == "yes" ] then d_sync=true else d_sync=false fi if [ "$d_anti_ddos_syn_flood" == "yes" ] then d_anti_ddos_syn_flood=true else d_anti_ddos_syn_flood=false fi if [ "$d_anti_ddos" == "yes" ] then d_anti_ddos=true else d_anti_ddos=false fi if [ "$d_anti_leech" == "yes" ] then d_anti_leech=true else d_anti_leech=false fi if [ "$d_anti_leech_restart_flv_changes" == "yes" ] then d_anti_leech_restart_flv_changes=true else d_anti_leech_restart_flv_changes=false fi if [ "$d_anti_leech_restart_hls_changes" == "yes" ] then d_anti_leech_restart_hls_changes=true else d_anti_leech_restart_hls_changes=false fi fi if [ "$minor_ver" -lt 86 ] then IFS="=" read -r d_bitrate d_hls_min_bitrate < <($JQ_FILE -r '.default|[.bitrates,.hls_min_bitrates]|join("=")' $CHANNELS_FILE) if [[ $d_bitrate =~ - ]] then d_resolution=${d_bitrate#*-} d_bitrate=${d_bitrate%-*} elif [[ $d_bitrate =~ x ]] then d_resolution="$d_bitrate" d_bitrate="" fi IFS="=" read -r -a chnls_bitrates < <($JQ_FILE -r '[.channels[].bitrates]|join("=")' $CHANNELS_FILE) for((i=0;i ${LOCK_FILE} } if [ -e "$IPTV_ROOT" ] && [ ! -e "$LOCK_FILE" ] then UpdateSelf fi if [[ -x $(command -v readlink) ]] && [ -L "$0" ] && alternative=$(readlink "$0") && [ -L "$alternative" ] then self=${alternative##*/} else self=${0##*/} fi self=${self%.*} if [ "$self" == "ibm" ] then ShFileCheck [ ! -d "$IPTV_ROOT" ] && JQ_FILE="/usr/local/bin/jq" if [ ! -e "$JQ_FILE" ] then DepsCheck JQInstall fi if [ -d "$IPTV_ROOT" ] then IBM_CONFIG_NEW="$IPTV_ROOT/${IBM_CONFIG##*/}" if [ -e "$IBM_CONFIG" ] && [ ! -e "$IBM_CONFIG_NEW" ] then mv "$IBM_CONFIG" "$IBM_CONFIG_NEW" fi IBM_CONFIG="$IBM_CONFIG_NEW" IBM_APPS_ROOT_NEW="$IPTV_ROOT/${IBM_APPS_ROOT##*/}" if [ -d "$IBM_APPS_ROOT" ] && [ ! -d "$IBM_APPS_ROOT_NEW" ] then mv "$IBM_APPS_ROOT" "$IPTV_ROOT/" fi IBM_APPS_ROOT="$IBM_APPS_ROOT_NEW" fi if [ "${1:-}" == "v2" ] then v2ray_name="v2ray" v2ray_package_name="v2ray" tls_name="TLS" V2CTL_FILE="$IBM_APPS_ROOT/ibm_v2ray/v2ctl" V2_CONFIG="$IBM_APPS_ROOT/ibm_v2ray/config.json" IbmV2rayMenu elif [ "${1:-}" == "x" ] then v2ray_name="xray" v2ray_package_name="Xray" tls_name="XTLS" V2CTL_FILE="$IBM_APPS_ROOT/ibm_xray/xray" V2_CONFIG="$IBM_APPS_ROOT/ibm_xray/config.json" IbmV2rayMenu elif [ "${1:-}" == "cron" ] then IbmCfAppCronExec else IbmCfMenu fi exit 0 elif [ "$self" == "cf" ] then ShFileCheck [ ! -d "$IPTV_ROOT" ] && JQ_FILE="/usr/local/bin/jq" if [ ! -e "$JQ_FILE" ] then DepsCheck JQInstall fi if [ -d "$IPTV_ROOT" ] then CF_CONFIG_NEW="$IPTV_ROOT/${CF_CONFIG##*/}" if [ -e "$CF_CONFIG" ] && [ ! -e "$CF_CONFIG_NEW" ] then mv "$CF_CONFIG" "$CF_CONFIG_NEW" fi CF_CONFIG="$CF_CONFIG_NEW" CF_WORKERS_ROOT_NEW="$IPTV_ROOT/${CF_WORKERS_ROOT##*/}" if [ -d "$CF_WORKERS_ROOT" ] && [ ! -d "$CF_WORKERS_ROOT_NEW" ] then mv "$CF_WORKERS_ROOT" "$IPTV_ROOT/" fi CF_WORKERS_ROOT="$CF_WORKERS_ROOT_NEW" IBM_CONFIG_NEW="$IPTV_ROOT/${IBM_CONFIG##*/}" if [ -e "$IBM_CONFIG" ] && [ ! -e "$IBM_CONFIG_NEW" ] then mv "$IBM_CONFIG" "$IBM_CONFIG_NEW" fi IBM_CONFIG="$IBM_CONFIG_NEW" CF_WORKERS_FILE_NEW="$CF_WORKERS_ROOT/${CF_WORKERS_FILE##*/}" if [ -e "$CF_WORKERS_FILE" ] && [ ! -e "$CF_WORKERS_FILE_NEW" ] then mv "$CF_WORKERS_FILE" "$CF_WORKERS_FILE_NEW" fi CF_WORKERS_FILE="$CF_WORKERS_FILE_NEW" fi cf_use_api=1 if [ "${1:-}" == "w" ] then CloudflareWorkersMenu else CloudflarePartnerMenu fi exit 0 elif [ "$self" == "or" ] then ShFileCheck [ ! -d "$IPTV_ROOT" ] && JQ_FILE="/usr/local/bin/jq" nginx_prefix="/usr/local/openresty/nginx" nginx_name="openresty" nginx_ctl="or" NGINX_FILE="$nginx_prefix/sbin/nginx" if [ ! -s "/etc/systemd/system/$nginx_name.service" ] && [ -d "$nginx_prefix" ] then if ! ResourceLimit then Println "$error 可能环境是 Unprivileged Container ?\n" fi cat > /etc/systemd/system/$nginx_name.service < /dev/null || true systemctl daemon-reload systemctl enable "$nginx_name" if ! systemctl start "$nginx_name" then Println "$error 端口占用?\n" fi fi case $* in "e") if [ ! -d "$nginx_prefix" ] then Println "$error 尚未安装, 请检查 !\n" exit 1 fi shopt -s nullglob nginx_confs=("$nginx_prefix"/conf/sites_available/*) shopt -u nullglob nginx_confs=( "$nginx_prefix"/conf/nginx.conf "${nginx_confs[@]}" ) echo inquirer list_input_index "选择配置文件" nginx_confs nginx_confs_index editor "${nginx_confs[nginx_confs_index]}" exit 0 ;; l) if [ ! -d "$nginx_prefix" ] then Println "$error 尚未安装, 请检查 !\n" exit 1 fi shopt -s nullglob nginx_logs=("$nginx_prefix"/logs/*) shopt -u nullglob if [ -z "${nginx_logs:-}" ] then Println "$error 没有日志 !\n" exit 1 fi echo inquirer list_input_index "选择日志文件" nginx_logs nginx_logs_index 20 if [ "${2:-}" == "t" ] then tail -f "${nginx_logs[nginx_logs_index]}" exit 0 fi editor "${nginx_logs[nginx_logs_index]}" exit 0 ;; *) ;; esac Println " openresty 管理面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 安装 ${green}2.${normal} 卸载 ${green}3.${normal} 升级 ———————————— ${green}4.${normal} 查看域名 ${green}5.${normal} 添加域名 ${green}6.${normal} 修改域名 ${green}7.${normal} 开关域名 ${green}8.${normal} 查看本地 ${green}9.${normal} 修改本地 ———————————— ${green}10.${normal} 状态 ${green}11.${normal} 开关 ${green}12.${normal} 重启 ———————————— ${green}13.${normal} 配置 日志切割 ${green}14.${normal} 识别 cloudflare/ibm ip ${green}15.${normal} 删除域名 $tip 输入: or 打开面板 " read -p "`gettext \"输入序号\"` [1-15]: " openresty_num case "$openresty_num" in 1) OpenrestyInstall ;; 2) OpenrestyUninstall ;; 3) OpenrestyUpdate ;; 4) NginxListDomain ;; 5) NginxAddDomain ;; 6) NginxConfigDomain ;; 7) NginxToggleDomain ;; 8) NginxListLocalhost NginxListStream ;; 9) NginxConfigLocalhost ;; 10) NginxViewStatus ;; 11) NginxToggle ;; 12) NginxRestart ;; 13) NginxLogRotate ;; 14) NginxUpdateCFIBMip ;; 15) NginxDeleteDomain ;; *) Println "$error $i18n_input_correct_number [1-15]\n" ;; esac exit 0 elif [ "$self" == "nx" ] then ShFileCheck [ ! -d "$IPTV_ROOT" ] && JQ_FILE="/usr/local/bin/jq" nginx_prefix="/usr/local/nginx" nginx_name="nginx" nginx_ctl="nx" NGINX_FILE="$nginx_prefix/sbin/nginx" if [ ! -s "/etc/systemd/system/$nginx_name.service" ] && [ -d "$nginx_prefix" ] then if ! ResourceLimit then Println "$error 可能环境是 Unprivileged Container ?\n" fi cat > /etc/systemd/system/$nginx_name.service < /dev/null || true systemctl daemon-reload systemctl enable "$nginx_name" if ! systemctl start "$nginx_name" then Println "$error 端口占用?\n" fi fi case ${1:-} in e) if [ ! -d "$nginx_prefix" ] then Println "$error 尚未安装, 请检查 !\n" exit 1 fi shopt -s nullglob nginx_confs=("$nginx_prefix"/conf/sites_available/*) shopt -u nullglob nginx_confs=( "$nginx_prefix"/conf/nginx.conf "${nginx_confs[@]}" ) echo inquirer list_input_index "选择配置文件" nginx_confs nginx_confs_index 20 editor "${nginx_confs[nginx_confs_index]}" exit 0 ;; l) if [ ! -d "$nginx_prefix" ] then Println "$error 尚未安装, 请检查 !\n" exit 1 fi shopt -s nullglob nginx_logs=("$nginx_prefix"/logs/*) shopt -u nullglob if [ -z "${nginx_logs:-}" ] then Println "$error 没有日志 !\n" exit 1 fi echo inquirer list_input_index "选择日志文件" nginx_logs nginx_logs_index 20 if [ "${2:-}" == "t" ] then tail -f "${nginx_logs[nginx_logs_index]}" exit 0 fi editor "${nginx_logs[nginx_logs_index]}" exit 0 ;; *) ;; esac Println " nginx 管理面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 安装 ${green}2.${normal} 卸载 ${green}3.${normal} 升级 ———————————— ${green}4.${normal} 查看域名 ${green}5.${normal} 添加域名 ${green}6.${normal} 修改域名 ${green}7.${normal} 开关域名 ${green}8.${normal} 查看本地 ${green}9.${normal} 修改本地 ———————————— ${green}10.${normal} 状态 ${green}11.${normal} 开关 ${green}12.${normal} 重启 ———————————— ${green}13.${normal} 配置 日志切割 ${green}14.${normal} 配置 nodejs ${green}15.${normal} 配置 mongodb ${green}16.${normal} 配置 postfix ${green}17.${normal} 配置 mmproxy ${green}18.${normal} 配置 dnscrypt proxy ${green}19.${normal} 识别 cloudflare/ibm ip ${green}20.${normal} 删除域名 $tip 输入: nx 打开面板 " read -p "`gettext \"输入序号\"` [1-20]: " nginx_num case "$nginx_num" in 1) NginxInstall ;; 2) NginxUninstall ;; 3) NginxUpdate ;; 4) NginxListDomain ;; 5) NginxAddDomain ;; 6) NginxConfigDomain ;; 7) NginxToggleDomain ;; 8) NginxListLocalhost NginxListStream ;; 9) NginxConfigLocalhost ;; 10) NginxViewStatus ;; 11) NginxToggle ;; 12) NginxRestart ;; 13) NginxLogRotate ;; 14) NodejsMenu ;; 15) MongodbMenu ;; 16) if [[ ! -x $(command -v postfix) ]] then Spinner "安装 postfix" PostfixInstall else echo ExitOnList y "`gettext \"postfix 已存在, 是否重新设置 smtp\"`" fi echo ExitOnText "请输入 smtp 地址 (比如 hwsmtp.exmail.qq.com) : " smtp_address echo ExitOnText "请输入 smtp 端口 (比如 465) : " smtp_port echo ExitOnText "请输入 smtp 邮箱 : " smtp_email echo ExitOnText "请输入 smtp 密码 : " smtp_pass hostname=$(hostname -f) echo "$(awk '!x{x=sub(/.*myhostname = .*/,"myhostname = '"$hostname"'")}1' /etc/postfix/main.cf)" > /etc/postfix/main.cf echo "$(awk '!x{x=sub(/.*relayhost = .*/,"relayhost = '"[$smtp_address]:$smtp_port"'")}1' /etc/postfix/main.cf)" > /etc/postfix/main.cf options=( smtp_tls_security_level=encrypt smtp_tls_wrappermode=yes smtp_sasl_auth_enable=yes smtp_sasl_security_options=noanonymous smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd smtp_generic_maps=hash:/etc/postfix/generic ) Println "$info 设置 postfix ..." echo "[$smtp_address]:$smtp_port $smtp_email:$smtp_pass" > /etc/postfix/sasl_passwd postmap /etc/postfix/sasl_passwd echo "$USER@$hostname $smtp_email" > /etc/postfix/generic postmap /etc/postfix/generic for option in "${options[@]}" do if grep -q "${option%=*} = " < /etc/postfix/main.cf then echo "$(awk '!x{x=sub(/.*'"${option%=*}"' = .*/,"'"${option%=*}"' = '"${option#*=}"'")}1' /etc/postfix/main.cf)" > /etc/postfix/main.cf elif grep -q "${option%=*}=" < /etc/postfix/main.cf then echo "$(awk '!x{x=sub(/.*'"${option%=*}"'=.*/,"'"${option%=*}"'='"${option#*=}"'")}1' /etc/postfix/main.cf)" > /etc/postfix/main.cf else echo "${option//=/ = }" >> /etc/postfix/main.cf fi done if ! grep -q "$USER:" < /etc/aliases then echo "$USER: $smtp_email" >> /etc/aliases newaliases fi if [[ $(ps --no-headers -o comm 1) == "systemd" ]] then systemctl restart postfix else service postfix restart fi Println "$info smtp 设置成功\n" ;; 17) if [ ! -e ~/allowed-subnets.txt ] then echo -en "0.0.0.0/0\n::/0\n" > ~/allowed-subnets.txt fi GoInstall if [[ ! -x "$HOME/go/bin/go-mmproxy" ]] then Println "$info 安装 go-mmproxy" go install github.com/path-network/go-mmproxy@latest fi echo mmproxy_opotions=( '用于 acme.sh' '用于 ssh' '手动配置' ) inquirer list_input_index "选择操作" mmproxy_opotions mmproxy_opotions_index if [ "$mmproxy_opotions_index" -eq 0 ] then mmproxy_name="acme" acme_tip="(acme 监听端口)" elif [ "$mmproxy_opotions_index" -eq 1 ] then mmproxy_name="ssh" ssh_tip="(ssh 监听端口)" else echo ExitOnText "输入 mmproxy 配置名称(英文)" mmproxy_name if [ "$mmproxy_name" == "acme" ] || [ "$mmproxy_name" == "ssh" ] then Println "$error 保留名称, 请重新输入\n" exit 1 elif [ -f /etc/systemd/system/mmproxy-$mmproxy_name.service ] then Println "$error 名称已经存在\n" exit 1 fi fi Println "$tip 比如 nginx 分流后端: 127.0.0.1:1234" inquirer text_input "输入 mmproxy 监听 地址+端口: " mmproxy_listen "$i18n_random" if [ "$mmproxy_listen" == "$i18n_random" ] then mmproxy_listen="127.0.0.1:$(GetFreePort)" fi if [ "$mmproxy_name" == "ssh" ] then Println "$tip 请之后将 nginx 分流 ssh 协议至 $mmproxy_listen, 并将 ssh 监听改为下面输入的地址和端口" elif [ "$mmproxy_name" == "acme" ] then Println "$tip 请之后将 nginx 分流 acme 协议至 $mmproxy_listen" fi Println "$tip 比如: 127.0.0.1:2222" ExitOnText "输入 ipv4 分流目标 地址+端口${acme_tip:-}${ssh_tip:-}: " mmproxy_target_v4 echo inquirer text_input "输入 ipv6 分流目标 地址+端口${acme_tip:-}${ssh_tip:-}: " mmproxy_target_v6 "[::1]:${mmproxy_target_v4#*:}" if [ -e "/etc/systemd/system/mmproxy-$mmproxy_name.service" ] then reload=1 fi cat > /etc/systemd/system/mmproxy-"$mmproxy_name".service < "$HOME"/ip.sh < /etc/rc.local <> /etc/rc.local fi chmod +x /etc/rc.local if [[ $(systemctl is-active rc-local) == "inactive" ]] then systemctl enable rc-local || true fi if ! grep -q 'iif lo lookup 100' < <(ip rule list) then systemctl restart rc-local || true fi Println "$info mmproxy-$mmproxy_name 设置成功\n" ;; 18) DepInstall curl DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version_old=${DNSCRYPT_ROOT#*-} echo dnscrypt_options=( '安装/升级 dnscrypt proxy' '开关 edns0' '开关 ipv6 查询' ) inquirer list_input_index "选择操作" dnscrypt_options dnscrypt_options_index if [ "$dnscrypt_options_index" -eq 1 ] then if [[ $dnscrypt_version_old == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi echo if grep -q "options edns0" < /etc/resolv.conf then ExitOnList n "`gettext \"是否关闭 edns0\"`" sed -i '/options edns0/d' /etc/resolv.conf echo "$(awk '!x{x=sub(/.*require_dnssec = .*/,"require_dnssec = false")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info edns0 已关闭\n" else ExitOnList n "`gettext \"是否开启 edns0\"`" echo "options edns0" >> /etc/resolv.conf echo "$(awk '!x{x=sub(/.*require_dnssec = .*/,"require_dnssec = true")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info edns0 已开启\n" fi exit 0 elif [ "$dnscrypt_options_index" -eq 2 ] then if [[ $dnscrypt_version_old == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi echo switch_options=( '开启' '关闭' ) inquirer list_input_index "选择操作" switch_options switch_options_index if [ "$switch_options_index" -eq 0 ] then echo "$(awk '!x{x=sub(/.*block_ipv6 = .*/,"block_ipv6 = false")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info ipv6 查询已开启\n" else echo "$(awk '!x{x=sub(/.*block_ipv6 = .*/,"block_ipv6 = true")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info ipv6 查询已关闭\n" fi exit 0 fi ArchCheck if [ "$arch" != "arm64" ] && grep -q "arm" <<< "$arch" then arch="arm" fi if dnscrypt_version=$(curl -s -Lm 20 "$FFMPEG_MIRROR_LINK/dnscrypt.json" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') then echo inquirer list_input "本机是否在国内" yn_options location_china if [[ $dnscrypt_version_old == "*" ]] then Println "$info 下载 dnscrypt proxy ..." if curl -L "$FFMPEG_MIRROR_LINK/dnscrypt/dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz" -o ~/dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz_tmp then Println "$info 设置 dnscrypt proxy ..." cd ~ mv dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz_tmp dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz tar zxf dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz mv linux-$arch dnscrypt-$dnscrypt_version chown -R $USER:$USER dnscrypt-$dnscrypt_version cd dnscrypt-$dnscrypt_version cp -f example-dnscrypt-proxy.toml dnscrypt-proxy.toml DNSCryptConfig for((i=0;i<3;i++)); do if ./dnscrypt-proxy -check > /dev/null then break elif [[ $i -eq 2 ]] then cd ~ rm -rf dnscrypt-$dnscrypt_version Println "$error 发生错误, 请重试\n" exit 1 fi done if [ -d /etc/resolvconf ] then DEBIAN_FRONTEND=noninteractive apt-get -y --purge remove resolvconf > /dev/null 2>&1 || true fi if [ -f /etc/resolv.conf ] then printf -v now '%(%m-%d-%H:%M:%S)T' -1 mv /etc/resolv.conf /etc/resolv.conf-$now fi echo -e "nameserver 127.0.0.1\noptions edns0" > /etc/resolv.conf systemctl stop systemd-resolved > /dev/null 2>&1 || true systemctl disable systemd-resolved > /dev/null 2>&1 || true ./dnscrypt-proxy -service install > /dev/null ./dnscrypt-proxy -service start > /dev/null if [[ $(systemctl is-active postfix) == "active" ]] then systemctl restart postfix fi Println "$info dnscrypt proxy 安装配置成功\n" else Println "$error dnscrypt proxy 下载失败, 请重试\n" exit 1 fi elif [[ $dnscrypt_version_old != "$dnscrypt_version" ]] then block_ipv6=$(sed -n -e "s/^block_ipv6 = \(.*\)/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) require_dnssec=$(sed -n -e "s/^require_dnssec = \(.*\)/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) echo inquirer text_input "输入监听端口 : " listen_port $(sed -n -e "s/^listen_addresses = .*:\([0-9]*\)']/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) if curl -L "$FFMPEG_MIRROR_LINK/dnscrypt/dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz" -o ~/dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz_tmp then if [ -L /etc/resolv.conf ] then etc_resolv=$(< /etc/resolv.conf) rm -f /etc/resolv.conf echo "$etc_resolv" > /etc/resolv.conf fi cd ~/dnscrypt-$dnscrypt_version_old ./dnscrypt-proxy -service stop > /dev/null ./dnscrypt-proxy -service uninstall > /dev/null cd ~ mv dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz_tmp dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz tar zxf dnscrypt-proxy-linux_$arch-$dnscrypt_version.tar.gz mv linux-$arch dnscrypt-$dnscrypt_version cd dnscrypt-$dnscrypt_version cp -f example-dnscrypt-proxy.toml dnscrypt-proxy.toml DNSCryptConfig ./dnscrypt-proxy -service install > /dev/null ./dnscrypt-proxy -service start > /dev/null Println "$info dnscrypt proxy 升级成功\n" else Println "$error dnscrypt proxy 下载失败, 请重试\n" exit 1 fi else Println "$error dnscrypt proxy 已经是最新\n" fi else Println "$error 无法连接服务器, 请稍后再试\n" fi ;; 19) NginxUpdateCFIBMip ;; 20) NginxDeleteDomain ;; *) Println "$error $i18n_input_correct_number [1-20]\n" ;; esac exit 0 elif [ "$self" == "v2" ] || [ "$self" == "V2" ] || [ "$self" == "x" ] || [ "$self" == "xray" ] then ShFileCheck [ ! -d "$IPTV_ROOT" ] && JQ_FILE="/usr/local/bin/jq" v2ray_sh="v2" v2ray_name="v2ray" tls_name="TLS" if [ "$self" == "x" ] || [ "$self" == "xray" ] then v2ray_sh="x" v2ray_name="xray" tls_name="XTLS" V2_FILE="/usr/local/bin/x" V2_LINK="https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh" V2_LINK_FALLBACK="$FFMPEG_MIRROR_LINK/xray_install-release.sh" V2CTL_FILE="/usr/local/bin/xray" V2_CONFIG="$X_CONFIG" elif [ -d /etc/v2ray/ ] then systemctl disable v2ray --now > /dev/null 2> /dev/null || true rm -rf /usr/bin/v2ray/ rm -f /etc/systemd/system/v2ray.service rm -f /lib/systemd/system/v2ray.service rm -f /etc/init.d/v2ray mv /etc/v2ray/ /usr/local/etc/ if ! grep -q "v2ray:" < "/etc/passwd" then if grep -q '\--group ' < <(adduser --help) then adduser v2ray --system --group --no-create-home > /dev/null else adduser v2ray --system --no-create-home > /dev/null fi usermod -s /usr/sbin/nologin v2ray fi mkdir -p /var/log/v2ray/ [ ! -e "/var/log/v2ray/error.log" ] && printf '%s' "" > /var/log/v2ray/error.log chown -R v2ray:v2ray /var/log/v2ray/ chown -R v2ray:v2ray /usr/local/share/v2ray/ V2rayUpdate systemctl enable v2ray systemctl start v2ray fi case $* in "e") [ ! -e "$V2_CONFIG" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 editor "$V2_CONFIG" && exit 0 ;; *) ;; esac if { [ -d "/usr/local/openresty" ] && [ ! -d "/usr/local/nginx" ]; } || { [ -s "/usr/local/openresty/nginx/logs/nginx.pid" ] && kill -0 "$(< "/usr/local/openresty/nginx/logs/nginx.pid")" 2> /dev/null ; } then nginx_prefix="/usr/local/openresty/nginx" nginx_name="openresty" nginx_ctl="or" elif { [ -d "/usr/local/nginx" ] && [ ! -d "/usr/local/openresty" ]; } || { [ -s "/usr/local/nginx/logs/nginx.pid" ] && kill -0 "$(< "/usr/local/nginx/logs/nginx.pid")" 2> /dev/null ; } then nginx_prefix="/usr/local/nginx" nginx_name="nginx" nginx_ctl="nx" else nginx_prefix="/usr/local/nginx" nginx_name="nginx" nginx_ctl="nx" fi NGINX_FILE="$nginx_prefix/sbin/nginx" Println " $v2ray_name 管理面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 安装 ${green}2.${normal} 升级 ${green}3.${normal} 配置域名 ${green}4.${normal} 查看状态 ———————————— ${green}5.${normal} 查看入站 ${green}6.${normal} 添加入站 ${green}7.${normal} 添加入站账号 ———————————— ${green}8.${normal} 查看出站 ${green}9.${normal} 添加出站 ${green}10.${normal} 添加出站账号 ———————————— ${green}11.${normal} 查看DNS ${green}12.${normal} 设置DNS ———————————— ${green}13.${normal} 查看路由 ${green}14.${normal} 设置路由 ———————————— ${green}15.${normal} 查看策略 ${green}16.${normal} 设置策略 ———————————— ${green}17.${normal} 查看流量 ${green}18.${normal} 重置流量 ———————————— ${green}19.${normal} 查看反向代理 ${green}20.${normal} 设置反向代理 ———————————— ${green}21.${normal} 删除入站 ${green}22.${normal} 删除入站账号 ${green}23.${normal} 删除出站 ${green}24.${normal} 删除出站账号 ———————————— ${green}25.${normal} 开关 ${green}26.${normal} 重启 $tip 输入: $v2ray_sh 打开面板 " read -p "`gettext \"输入序号\"` [1-26]: " v2ray_num case $v2ray_num in 1) V2rayInstall ;; 2) V2rayUpdate systemctl restart $v2ray_name ;; 3) V2rayConfigUpdate V2rayConfigDomain ;; 4) V2rayStatus ;; 5) V2rayConfigUpdate V2rayListInboundAccounts V2rayListInboundAccountLink ;; 6) V2rayConfigUpdate V2rayAddInbound ;; 7) V2rayConfigUpdate V2rayAddInboundAccount ;; 8) V2rayConfigUpdate V2rayListOutboundAccounts ;; 9) V2rayConfigUpdate V2rayAddOutbound ;; 10) V2rayConfigUpdate V2rayAddOutboundAccount ;; 11) V2rayConfigUpdate V2rayListDns ;; 12) V2rayConfigUpdate V2raySetDns ;; 13) V2rayConfigUpdate V2rayListRouting ;; 14) V2rayConfigUpdate V2raySetRouting ;; 15) V2rayConfigUpdate V2rayListPolicy ;; 16) V2rayConfigUpdate V2raySetPolicy ;; 17) V2rayConfigUpdate V2rayListStats ;; 18) V2rayConfigUpdate V2rayResetStats ;; 19) V2rayConfigUpdate V2rayListReverse ;; 20) V2rayConfigUpdate V2raySetReverse ;; 21) V2rayConfigUpdate V2rayDeleteInbound ;; 22) V2rayConfigUpdate V2rayDeleteInboundAccount ;; 23) V2rayConfigUpdate V2rayDeleteOutbound ;; 24) V2rayConfigUpdate V2rayDeleteOutboundAccount ;; 25) if [ ! -e "$V2_CONFIG" ] then Println "$error $v2ray_name 未安装...\n" && exit 1 fi echo if [[ $(systemctl is-active $v2ray_name) == "active" ]] then ExitOnList y "`eval_gettext \"\\\$v2ray_name 正在运行, 是否关闭\"`" systemctl stop $v2ray_name > /dev/null 2>&1 Println "$info $v2ray_name 已关闭\n" else ExitOnList y "`eval_gettext \"\\\$v2ray_name 未运行, 是否开启\"`" systemctl start $v2ray_name > /dev/null 2>&1 Println "$info $v2ray_name 已开启\n" fi ;; 26) if [ ! -e "$V2_CONFIG" ] then Println "$error $v2ray_name 未安装...\n" && exit 1 fi systemctl restart $v2ray_name > /dev/null 2>&1 Println "$info $v2ray_name 已重启\n" ;; *) Println "$error $i18n_input_correct_number [1-26]\n" ;; esac exit 0 elif [ "$self" == "cx" ] then [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 Println " Xtream Codes 面板 ${normal} ${green}1.${normal} 查看账号 ${green}2.${normal} 添加账号 ${green}3.${normal} 批量检测 ${green}4.${normal} 测试账号 ${green}5.${normal} 获取账号 ———————————— ${green}6.${normal} 查看 mac 地址 ${green}7.${normal} 添加 mac 地址 ${green}8.${normal} 浏览频道 " read -p "`gettext \"输入序号\"` [1-8]: " xtream_codes_num case $xtream_codes_num in 1) XtreamCodesListAcc ;; 2) XtreamCodesAddAccount XtreamCodesList ;; 3) [ ! -s "$XTREAM_CODES" ] && Println "$error 没有账号 !\n" && exit 1 echo ExitOnList n "`gettext \"耗时可能很长, 是否继续\"`" Println "$info 检测中..." printf -v now '%(%m-%d-%H:%M:%S)T' -1 cp -f "$XTREAM_CODES" "${XTREAM_CODES}_$now" IFS=" " read -r m_ip m_domains m_accounts < <(awk '$1 {a=a $1",";b=b $2",";$1=$2="";c=c substr($0,3)","} END {print a,b,c}' "$XTREAM_CODES") IFS="," read -r -a new_domains <<< "$m_domains" IFS="," read -r -a new_accounts <<< "$m_accounts" result="" for((i=0;i<${#new_domains[@]};i++)); do IFS="|" read -r -a domains <<< "${new_domains[i]}" IFS=" " read -r -a accounts <<< "${new_accounts[i]}" for domain in "${domains[@]}" do ip=$(getent ahosts "${domain%%:*}" | awk '{ print $1 ; exit }') || continue for account in "${accounts[@]}" do [ -n "$result" ] && result="$result\n" result="$result$ip $domain $account" done done done echo -e "$result" > "$XTREAM_CODES_EXAM" verify_mac=1 XtreamCodesList Println "$info 账号检测完成\n" ;; 4) XtreamCodesTestAcc ;; 5) Println "$info 稍等...\n" if [ -s "$XTREAM_CODES" ] then printf -v now '%(%m-%d-%H:%M:%S)T' -1 cp -f "$XTREAM_CODES" "${XTREAM_CODES}_$now" fi ShFallback IFS=" " read -r m_ip m_domains m_accounts < <(curl -s -Lm 20 "$SH_FALLBACK/$XTREAM_CODES_CHANNELS"|awk '$1 {a=a $1",";b=b $2",";$1=$2="";c=c substr($0,3)","} END {print a,b,c}') IFS="," read -r -a new_domains <<< "$m_domains" IFS="," read -r -a new_accounts <<< "$m_accounts" result="" new_domains_count=${#new_domains[@]} for((i=0;i> "$XTREAM_CODES_EXAM" echo && echo inquirer list_input_index "验证 mac" ny_options ny_options_index if [ "$ny_options_index" -eq 1 ] then verify_mac=1 fi XtreamCodesList Println "$info 账号添加成功\n" ;; 6) XtreamCodesListMac ;; 7) XtreamCodesAddMac if [ "$add_mac_success" = true ] then XtreamCodesList mac Println "$info mac 添加成功!\n" fi ;; 8) XtreamCodesListChnls ;; *) Println "$error $i18n_input_correct_number [1-8]\n" ;; esac exit 0 elif [ "$self" == "arm" ] then if [[ ! -x $(command -v armbian-config) ]] then Println "$error 不是 Armbian 系统\n" exit 1 fi ShFileCheck JQ_FILE="/usr/local/bin/jq" Println " Armbian 管理面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 更改 apt 源 ${green}2.${normal} 修复 N1 dtb ———————————— ${green}3.${normal} 安装 docker ${green}4.${normal} 安装 升级 dnscrypt proxy ${green}5.${normal} 安装 AdGuardHome ${green}6.${normal} 安装 升降级 openwrt ${green}7.${normal} 安装 升级 openwrt-v2ray ———————————— ${green}8.${normal} 切换 openwrt 语言 ${green}9.${normal} 切换 v2ray/xray core ${green}10.${normal} 切换 配置文件 ———————————— ${green}11.${normal} 设置 docker 镜像加速 ${green}12.${normal} 设置 vimrc ${green}13.${normal} 开关 edns0 ${green}14.${normal} 开关 ipv6 查询 ${green}15.${normal} NAT 类型测试 ${green}16.${normal} 更新脚本 " read -p "`gettext \"输入序号\"` [1-16]: " armbian_num case $armbian_num in 1) AptSetSources ;; 2) echo ExitOnList n "`gettext \"适用于 斐讯 n1, 是否继续\"`" if [ ! -d ~/Amlogic_s905-kernel-master ] then if curl -L "$FFMPEG_MIRROR_LINK/Amlogic_s905-kernel-master.zip" -o ~/Amlogic_s905-kernel-master.zip then unzip Amlogic_s905-kernel-master.zip else Println "$error 下载 Amlogic_s905-kernel-master.zip 发生错误, 请稍后再试\n" exit 1 fi fi cd ~/Amlogic_s905-kernel-master sed -i 's/interrupts = <29/interrupts = <25/' arch/arm64/boot/dts/amlogic/meson-gxl-s905d-p230.dts make defconfig make dtbs cp -f arch/arm64/boot/dts/amlogic/meson-gxl-s905d-phicomm-n1.dtb /boot/dtb/amlogic/meson-gxl-s905d-phicomm-n1.dtb Println "$info 修复成功\n" ;; 3) if [[ -x $(command -v docker) ]] then Println "$error docker 已经存在\n" exit 1 fi if grep -q "docker-ce" < /etc/apt/sources.list then sed -i '/docker-ce/d' /etc/apt/sources.list fi AptUpdate apt-get -y install ca-certificates if [ ! -f "/etc/apt/sources.list.d/docker.list" ] then if grep -q "mirrors.ustc.edu.cn" < /etc/apt/sources.list then curl -fsSL http://mirrors.ustc.edu.cn/docker-ce/linux/debian/gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null else curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null fi fi apt-get update apt-get -y install docker-ce docker-ce-cli containerd.io Println "$info docker 安装成功\n" ;; 4) if ! $JQ_FILE -V > /dev/null 2>&1 then Spinner "编译安装 JQ, 耗时可能会很长" JQInstall fi if dnscrypt_version=$(curl -s -Lm 10 "$FFMPEG_MIRROR_LINK/dnscrypt.json" | $JQ_FILE -r '.tag_name') then echo inquirer list_input "本机是否在国内" ny_options location_china DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version_old=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version_old == "*" ]] then echo inquirer text_input "输入监听端口 : " listen_port 53 Println "$tip 请确保已经将本机器用网线连接到主路由器的 LAN 口" ExitOnList n "`gettext \"是否继续\"`" echo ExitOnText "请输入主路由器 ip : " eth0_gateway Println "$tip 必须和主路由器 ip 在同一网段" ExitOnText "设置本机静态 ip : " eth0_ip Println "$info 下载 dnscrypt proxy ..." if ! curl -L "$FFMPEG_MIRROR_LINK/dnscrypt/dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz" -o ~/dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz_tmp then Println "$error dnscrypt proxy 下载失败, 请重试\n" exit 1 fi Println "$info 设置 dnscrypt proxy ..." cd ~ mv dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz_tmp dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz tar zxf dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz mv linux-arm64 dnscrypt-$dnscrypt_version chown -R $USER:$USER dnscrypt-$dnscrypt_version cd dnscrypt-$dnscrypt_version cp -f example-dnscrypt-proxy.toml dnscrypt-proxy.toml if [ ! -f "/etc/NetworkManager/system-connections/armbian.nmconnection" ] then con=$(nmcli -t c s | grep eth0 | head -1) nmcli connection modify "${con%%:*}" con-name armbian fi cp -f /etc/NetworkManager/system-connections/armbian.nmconnection ~/armbian.nmconnection-old while IFS= read -r line do if [[ $line =~ uuid= ]] then etho_uuid=${line#*=} elif [[ $line =~ timestamp= ]] then eth0_timestamp=${line#*=} elif [[ $line =~ mac-address= ]] then eth0_mac=${line#*=} break fi done < "/etc/NetworkManager/system-connections/armbian.nmconnection" echo "[connection] id=armbian uuid=$etho_uuid type=ethernet autoconnect=true interface-name=eth0 permissions= timestamp=${eth0_timestamp:-$(date +%s)} [ethernet] mac-address=${eth0_mac:-$(GetRandomMac)} mac-address-blacklist= [ipv4] address1=$eth0_ip/24,$eth0_gateway dns=127.0.0.1; dns-priority=100 dns-search= ignore-auto-dns=true method=manual [ipv6] addr-gen-mode=stable-privacy dns-search= method=ignore" > /etc/NetworkManager/system-connections/armbian.nmconnection DNSCryptConfig for((i=0;i<3;i++)); do if ./dnscrypt-proxy -check > /dev/null then break elif [[ $i -eq 2 ]] then cd ~ rm -rf dnscrypt-$dnscrypt_version Println "$error 发生错误, 请重试\n" exit 1 fi done if [ -d /etc/resolvconf ] then DEBIAN_FRONTEND=noninteractive apt-get -y --purge remove resolvconf > /dev/null 2>&1 || true fi systemctl stop systemd-resolved systemctl disable systemd-resolved ./dnscrypt-proxy -service install > /dev/null ./dnscrypt-proxy -service start > /dev/null if ! grep -q "#allow-hotplug eth0" < /etc/network/interfaces then echo "$(awk '!x{x=sub(/allow-hotplug eth0/,"#allow-hotplug eth0")}1' /etc/network/interfaces)" > /etc/network/interfaces fi if ! grep -q "#no-auto-down eth0" < /etc/network/interfaces then echo "$(awk '!x{x=sub(/no-auto-down eth0/,"#no-auto-down eth0")}1' /etc/network/interfaces)" > /etc/network/interfaces fi if ! grep -q "#iface eth0 inet dhcp" < /etc/network/interfaces then echo "$(awk '!x{x=sub(/iface eth0 inet dhcp/,"#iface eth0 inet dhcp")}1' /etc/network/interfaces)" > /etc/network/interfaces fi if [ -f /etc/resolv.conf ] then printf -v now '%(%m-%d-%H:%M:%S)T' -1 mv /etc/resolv.conf /etc/resolv.conf-$now fi echo -e "nameserver 127.0.0.1\noptions edns0" > /etc/resolv.conf nmcli connection reload systemctl restart NetworkManager Println "$info dnscrypt proxy 安装配置成功, 请重启 Armbian 后连接 IP: $eth0_ip\n" nmcli con up armbian elif [[ $dnscrypt_version_old != "$dnscrypt_version" ]] then block_ipv6=$(sed -n -e "s/^block_ipv6 = \(.*\)/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) require_dnssec=$(sed -n -e "s/^require_dnssec = \(.*\)/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) echo inquirer text_input "输入监听端口 : " listen_port $(sed -n -e "s/^listen_addresses = .*:\([0-9]*\)']/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) if [[ -x $(command -v docker) ]] && [[ -n $(docker container ls -a -f name=openwrt$ -q) ]] then Println "$tip 如果已经安装并运行旁路由 openwrt-v2ray, 建议先关闭旁路由 openwrt-v2ray" ExitOnList n "`gettext \"是否继续\"`" fi if ! curl -L "$FFMPEG_MIRROR_LINK/dnscrypt/dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz" -o ~/dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz_tmp then Println "$error dnscrypt proxy 下载失败, 请重试\n" exit 1 fi if [ -L /etc/resolv.conf ] then etc_resolv=$(< /etc/resolv.conf) rm -f /etc/resolv.conf echo "$etc_resolv" > /etc/resolv.conf fi cd ~/dnscrypt-$dnscrypt_version_old ./dnscrypt-proxy -service stop > /dev/null ./dnscrypt-proxy -service uninstall > /dev/null cd ~ mv dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz_tmp dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz tar zxf dnscrypt-proxy-linux_arm64-$dnscrypt_version.tar.gz mv linux-arm64 dnscrypt-$dnscrypt_version cd dnscrypt-$dnscrypt_version cp -f example-dnscrypt-proxy.toml dnscrypt-proxy.toml DNSCryptConfig ./dnscrypt-proxy -service install > /dev/null ./dnscrypt-proxy -service start > /dev/null Println "$info dnscrypt proxy 升级成功\n" else Println "$error dnscrypt proxy 已经是最新\n" fi else Println "$error 无法连接服务器, 请稍后再试\n" fi ;; 5) DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version_old=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version_old == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi if [ -d /opt/AdGuardHome ] then Println "$error AdGuard Home 已经存在\n" exit 1 fi echo ExitOnText "输入 dnscrypt-proxy 新的监听端口" listen_port if [ "$listen_port" -eq 53 ] then Println "$error 端口不能是 53\n" exit 1 fi if ! curl -s -S -L $FFMPEG_MIRROR_LINK/AdGuardHome/master/scripts/install.sh | sh -s -- -v then Println "$error 安装发生错误\n" exit 1 fi echo "$(awk '!x{x=sub(/^listen_addresses = .*/,"listen_addresses = [\047[::]:'"$listen_port"'\047]")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy if ! curl -s -H 'Content-Type: application/json' \ --data '{"upstream_dns": ["127.0.0.1:'"$listen_port"'"], "ratelimit": 0}' "http://localhost:3000/control/dns_config" then Println "$error 请登陆 http://Armbian_IP:3000/ 手动修改上游 DNS 服务器为 127.0.0.1:$listen_port 并可以将速度限制设置为 0\n" exit 1 fi Println "$info AdGuard Home 安装成功\n" ;; 6) if [[ ! -x $(command -v docker) ]] then Println "$error 请先安装 docker\n" exit 1 fi DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version_old=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version_old == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi if [[ $(systemctl is-active docker) == "inactive" ]] then systemctl start docker fi echo openwrt_options=( '21.02.1' '21.02.0' '19.07.8' '19.07.7' '19.07.6' '19.07.5' '19.07.4' '手动输入' ) inquirer list_input "选择版本: " openwrt_options openwrt_ver if [ "$openwrt_ver" == "手动输入" ] then echo ExitOnText "输入版本号: " openwrt_ver fi if grep -q "armvirt-64-$openwrt_ver" < <(docker container ls -a) then if [ -f /etc/NetworkManager/dispatcher.d/promisc.sh ] then printf '%s' '#!/usr/bin/env bash interface=$1 event=$2 if [[ $event == "up" ]] && [[ $interface == "eth0" ]] then ip link set $interface promisc on echo "$interface received $event" | systemd-cat -p info -t dispatch_script fi' > /etc/NetworkManager/dispatcher.d/90-promisc.sh rm -f /etc/NetworkManager/dispatcher.d/promisc.sh chmod +x /etc/NetworkManager/dispatcher.d/90-promisc.sh fi if grep -q "armvirt-64-$openwrt_ver" < <(docker container ls -f name=openwrt) then Println "$error 此版本 openwrt 已经在运行\n" exit 1 fi Println "$info 切换到版本 openwrt-armvirt-64-$openwrt_ver" action="switch" else Println "$info 安装 openwrt-armvirt-64-$openwrt_ver" action="install" fi if [ ! -f "/etc/NetworkManager/system-connections/armbian.nmconnection" ] then con=$(nmcli -t c s | grep eth0 | head -1) nmcli connection modify "${con%%:*}" con-name armbian nmcli connection reload fi while IFS= read -r line do if [[ $line =~ ^address1=([^/]+)/24,(.+) ]] then eth0_ip=${BASH_REMATCH[1]} eth0_gateway=${BASH_REMATCH[2]} break fi done < "/etc/NetworkManager/system-connections/armbian.nmconnection" connected_ip=${SSH_CLIENT% *} connected_ip=${connected_ip// /:} armbian_ip=$(ss -taH|grep $connected_ip|awk '{print $4}') armbian_ip=${armbian_ip%:*} if [ "$armbian_ip" != "$eth0_ip" ] then docker container start openwrt >/dev/null 2>&1 Println "$error 请连接 IP: $eth0_ip 到 Armbian\n" exit 1 fi Println "$tip openwrt 作为旁路由, 请确保已经将本机器用网线连接到主路由器的 LAN 口, 并且当前连接使用的网关是主路由的地址(可能需要手动设定)" Println "$tip 如果是升级, 注意备份原 openwrt 配置(系统 - 备份/还原)" ExitOnList n "`gettext \"是否继续\"`" if ! ip addr show hMACvLAN >/dev/null 2>&1 then Println "$tip 必须和主路由器 ip 在同一网段" ExitOnText "设置虚拟接口 hMACvLAN 静态 ip : " hMACvLAN_ip Println "$tip 必须和主路由器 ip 在同一网段" ExitOnText "设置 openwrt 静态 ip : " openwrt_ip nmcli connection add type macvlan dev eth0 mode bridge ifname hMACvLAN autoconnect yes save yes > /dev/null nmcli connection modify macvlan-hMACvLAN con-name hMACvLAN while IFS= read -r line do if [[ $line =~ uuid= ]] then hMACvLAN_uuid=${line#*=} break fi done < "/etc/NetworkManager/system-connections/hMACvLAN.nmconnection" echo "[connection] id=hMACvLAN uuid=$hMACvLAN_uuid type=macvlan interface-name=hMACvLAN permissions= [macvlan] mode=2 parent=eth0 [ipv4] address1=$hMACvLAN_ip/24,$openwrt_ip dns=127.0.0.1; dns-search= ignore-auto-dns=true method=manual route-metric=50 [ipv6] addr-gen-mode=stable-privacy dns-search= method=ignore" > /etc/NetworkManager/system-connections/hMACvLAN.nmconnection nmcli connection reload else while IFS= read -r line do if [[ $line =~ ^address1=([^/]+)/24,(.+) ]] then openwrt_ip=${BASH_REMATCH[2]} break fi done < "/etc/NetworkManager/system-connections/hMACvLAN.nmconnection" if [ -z "${openwrt_ip:-}" ] then Println "$tip 必须和主路由器 ip 在同一网段" ExitOnText "设置虚拟接口 hMACvLAN 静态 ip : " hMACvLAN_ip Println "$tip 必须和主路由器 ip 在同一网段" ExitOnText "设置 openwrt 静态 ip : " openwrt_ip while IFS= read -r line do if [[ $line =~ uuid= ]] then hMACvLAN_uuid=${line#*=} elif [[ $line =~ timestamp= ]] then hMACvLAN_timestamp=${line#*=} break fi done < "/etc/NetworkManager/system-connections/hMACvLAN.nmconnection" echo "[connection] id=hMACvLAN uuid=$hMACvLAN_uuid type=macvlan interface-name=hMACvLAN permissions= timestamp=${hMACvLAN_timestamp:-$(date +%s)} [macvlan] mode=2 parent=eth0 [ipv4] address1=$hMACvLAN_ip/24,$openwrt_ip dns=127.0.0.1; dns-search= ignore-auto-dns=true method=manual route-metric=150 [ipv6] addr-gen-mode=stable-privacy dns-search= method=ignore" > /etc/NetworkManager/system-connections/hMACvLAN.nmconnection nmcli connection reload fi fi if [[ -n $(docker container ls -a -f name=openwrt$ -q) ]] then echo inquirer list_input "是否重新设置 openwrt 静态 IP" ny_options change_openwrt_ip_yn if [ "$change_openwrt_ip_yn" == "$i18n_yes" ] then Println "$tip 必须和主路由器 ip 在同一网段" inquirer text_input "设置 openwrt 静态 ip : " openwrt_ip "$i18n_cancel" if [ "$openwrt_ip" == "$i18n_cancel" ] then if [[ -z $(docker container ls -f name=openwrt$ -q) ]] then docker container start openwrt >/dev/null 2>&1 || true fi Println "$i18n_canceled...\n" exit 1 fi sed -i "/address1=\(.*\),.*/s//address1=\1,$openwrt_ip/" /etc/NetworkManager/system-connections/hMACvLAN.nmconnection nmcli connection reload fi Println "$info 重启 hMACvLAN ..." nmcli connection modify hMACvLAN ipv4.route-metric 150 > /dev/null nmcli con down hMACvLAN > /dev/null 2>&1 || true nmcli con up hMACvLAN > /dev/null sleep 3 openwrt_ver_old=$(docker inspect --format='{{.Config.Image}}' openwrt) openwrt_ver_old=${openwrt_ver_old#*:} if [[ -n $(docker container ls -f name=openwrt$ -q) ]] then docker rename openwrt openwrt-$openwrt_ver_old docker container stop openwrt-$openwrt_ver_old >/dev/null 2>&1 Println "$info 网络马上会中断, 请务必退出并等待 30秒 后重新连接 armbian 后重复当前步骤 ...\n" exit 1 else docker rename openwrt openwrt-$openwrt_ver_old fi fi if [ ! -s "/etc/docker/daemon.json" ] then printf '%s' "{}" > /etc/docker/daemon.json fi if ! $JQ_FILE -V > /dev/null 2>&1 then Spinner "编译安装 JQ, 耗时可能会很长" JQInstall fi json=true jq_path='["dns"]' JQ update /etc/docker/daemon.json '["'"$eth0_ip"'","8.8.8.8"]' if ! docker network inspect macnet >/dev/null 2>&1 then docker network create -d macvlan \ --subnet=$eth0_ip/24 \ --gateway=$eth0_gateway \ -o parent=eth0 macnet fi if [ ! -s /etc/NetworkManager/dispatcher.d/90-promisc.sh ] then printf '%s' '#!/usr/bin/env bash interface=$1 event=$2 if [[ $event == "up" ]] && [[ $interface == "eth0" ]] then ip link set $interface promisc on echo "$interface received $event" | systemd-cat -p info -t dispatch_script fi' > /etc/NetworkManager/dispatcher.d/90-promisc.sh rm -f /etc/NetworkManager/dispatcher.d/promisc.sh chmod +x /etc/NetworkManager/dispatcher.d/90-promisc.sh fi ip link set eth0 promisc on if [ "$action" == "switch" ] then docker rename openwrt-armvirt-64-$openwrt_ver openwrt docker container start openwrt >/dev/null else Println "$info 下载 armvirt-64-$openwrt_ver ..." docker import $FFMPEG_MIRROR_LINK/openwrt/releases/$openwrt_ver/targets/armvirt/64/openwrt-$openwrt_ver-armvirt-64-default-rootfs.tar.gz openwrtorg/rootfs:armvirt-64-$openwrt_ver if [ "${openwrt_ver%%.*}" -ge 21 ] then option_name="device" else option_name="ifname" fi docker run -d \ --restart unless-stopped \ --network macnet \ --privileged \ --name openwrt \ openwrtorg/rootfs:armvirt-64-$openwrt_ver /sbin/init Println "$info openwrt 启动中..." until [[ $(docker inspect --format='{{.State.Status}}' openwrt) == "running" ]] do sleep 1 done docker exec -it openwrt /bin/ash -c " uci del firewall.@defaults[0].syn_flood uci set firewall.@defaults[0].synflood_protect='1' uci set firewall.@defaults[0].flow_offloading='1' uci set firewall.@defaults[0].flow_offloading_hw='1' uci set network.lan.ipaddr='$openwrt_ip' uci set network.lan.gateway='$eth0_gateway' uci add_list network.lan.dns='$eth0_ip' uci set dhcp.@dnsmasq[0].rebind_protection='0' uci commit reload_config " fi Println "$info openwrt ${green}旁路由${normal} 安装成功, 地址: $openwrt_ip, 是 ${red}主路由${normal} 负责(拨号)联网\n" Println "$tip 具体配置参考 https://github.com/woniuzfb/iptv/wiki/openwrt\n" nmcli connection modify hMACvLAN ipv4.route-metric 50 > /dev/null nmcli con down hMACvLAN > /dev/null 2>&1 || true nmcli con up hMACvLAN > /dev/null ;; 7) if ! docker inspect openwrt > /dev/null 2>&1 then Println "$error 请先安装或运行 openwrt\n" exit 1 fi if ! luci_app_xray_ver=$(curl -s -m 30 "$FFMPEG_MIRROR_LINK/luci-app-xray.json" | $JQ_FILE -r '.tag_name') then Println "$error 无法连接服务器, 请稍后再试\n" exit 1 else luci_app_xray_ver=${luci_app_xray_ver#*v} fi docker exec -it openwrt /bin/ash -c " if ! opkg list-installed | grep -q v2ray then sed -i 's_http[s]*://downloads.openwrt.org_$FFMPEG_MIRROR_LINK/openwrt_' /etc/opkg/distfeeds.conf if ! grep -q kuoruan < /etc/opkg/customfeeds.conf then . /etc/openwrt_release wget -O kuoruan-public.key $FFMPEG_MIRROR_LINK/openwrt-v2ray/packages/public.key opkg-key add kuoruan-public.key echo \"src/gz kuoruan_packages $FFMPEG_MIRROR_LINK/openwrt-v2ray/packages/releases/\$DISTRIB_ARCH\" >> /etc/opkg/customfeeds.conf echo \"src/gz kuoruan_universal $FFMPEG_MIRROR_LINK/openwrt-v2ray/packages/releases/all\" >> /etc/opkg/customfeeds.conf fi opkg update opkg download dnsmasq-full opkg remove dnsmasq opkg install dnsmasq-full --cache . opkg install zoneinfo-asia kmod-tcp-bbr libustream-openssl jshn ip-full ipset iptables iptables-mod-tproxy resolveip opkg install luci luci-base luci-compat if ! test -e /etc/sysctl.d/12-tcp-bbr.conf || ! grep -q default_qdisc < /etc/sysctl.d/12-tcp-bbr.conf then echo \"net.core.default_qdisc=fq\" >> /etc/sysctl.d/12-tcp-bbr.conf sysctl -p fi else opkg update fi opkg install v2ray-core wget -O luci-app-v2ray_${luci_app_xray_ver}_all.ipk $FFMPEG_MIRROR_LINK/luci-app-v2ray_${luci_app_xray_ver}_all.ipk opkg install luci-app-v2ray_${luci_app_xray_ver}_all.ipk --force-reinstall || true wget -O luci-i18n-v2ray-zh-cn_${luci_app_xray_ver}_all.ipk $FFMPEG_MIRROR_LINK/luci-i18n-v2ray-zh-cn_${luci_app_xray_ver}_all.ipk opkg install luci-i18n-v2ray-zh-cn_${luci_app_xray_ver}_all.ipk --force-reinstall || true " Println "$info openwrt-v2ray 安装成功\n" ;; 8) if ! docker inspect openwrt > /dev/null 2>&1 then Println "$error 请先安装或运行 openwrt\n" exit 1 fi echo lang_options=( '简体中文' '繁体中文' '英文' ) inquirer list_input "选择界面语言" lang_options lang if [ "$lang" == "简体中文" ] then lang="zh-cn" elif [ "$lang" == "繁体中文" ] then lang="zh-tw" else lang="en" fi docker exec -it openwrt /bin/ash -c " if ! opkg list-installed | grep -q luci-i18n-base-$lang then sed -i 's_http://downloads.openwrt.org_$FFMPEG_MIRROR_LINK/openwrt_' /etc/opkg/distfeeds.conf sed -i 's_https://downloads.openwrt.org_$FFMPEG_MIRROR_LINK/openwrt_' /etc/opkg/distfeeds.conf opkg update opkg install luci-i18n-base-$lang fi uci set luci.main.lang='${lang//-/_}' uci commit reload_config " Println "$info 界面语言切换成功\n" ;; 9) if ! docker inspect openwrt > /dev/null 2>&1 then Println "$error 请先安装或运行 openwrt\n" exit 1 fi Println "$tip 请确保已经安装过 openwrt-v2ray" core_options=( 'xray-core' 'v2ray-core' ) inquirer list_input "选择切换目标" core_options core if [ "$core" == "xray-core" ] then echo xray_options=( '最新' '1.4.5' ) inquirer list_input "选择 xray 版本" xray_options xray_ver if [ "$xray_ver" == "最新" ] && ! xray_ver=$(curl -s -m 30 "$FFMPEG_MIRROR_LINK/openwrt-xray.json" | $JQ_FILE -r '.tag_name') then Println "$error 无法连接服务器, 请稍后再试\n" exit 1 else xray_ver=${xray_ver#*v} if [[ ! $xray_ver =~ - ]] then xray_ver="${xray_ver}-1" fi fi if ! luci_app_xray_ver=$(curl -s -m 30 "$FFMPEG_MIRROR_LINK/luci-app-xray.json" | $JQ_FILE -r '.tag_name') then Println "$error 无法连接服务器, 请稍后再试\n" exit 1 else luci_app_xray_ver=${luci_app_xray_ver#*v} fi docker exec -it openwrt /bin/ash -c " if ! opkg list-installed | grep -q 'xray - $xray_ver-1' then . /etc/os-release wget -O xray_${xray_ver}_\$OPENWRT_ARCH.ipk $FFMPEG_MIRROR_LINK/xray_${xray_ver}_\$OPENWRT_ARCH.ipk opkg install xray_${xray_ver}_\$OPENWRT_ARCH.ipk --force-reinstall || true fi wget -O luci-app-v2ray_${luci_app_xray_ver}_all.ipk $FFMPEG_MIRROR_LINK/luci-app-v2ray_${luci_app_xray_ver}_all.ipk opkg install luci-app-v2ray_${luci_app_xray_ver}_all.ipk --force-reinstall || true wget -O luci-i18n-v2ray-zh-cn_${luci_app_xray_ver}_all.ipk $FFMPEG_MIRROR_LINK/luci-i18n-v2ray-zh-cn_${luci_app_xray_ver}_all.ipk opkg install luci-i18n-v2ray-zh-cn_${luci_app_xray_ver}_all.ipk --force-reinstall || true uci set v2ray.main.v2ray_file='/usr/bin/xray' uci set v2ray.main.asset_location='/usr/share/xray' uci commit reload_config " else echo inquirer list_input "是否更新 openwrt-v2ray" ny_options ny_option if [ "$ny_option" == "$i18n_yes" ] then Println "$info 更新 openwrt-v2ray, 请稍等 ...\n" docker exec -it openwrt /bin/ash -c " opkg update opkg install v2ray-core" fi docker exec -it openwrt /bin/ash -c " uci set v2ray.main.v2ray_file='/usr/bin/v2ray' uci del v2ray.main.asset_location uci commit reload_config " fi Println "$info 切换成功\n" ;; 10) if [[ ! -x $(command -v docker) ]] || [[ -z $(docker container ls -a -f name=openwrt$ -q) ]] then Println "$error 请先安装并运行 openwrt ...\n" exit 1 fi echo inquirer text_input "输入当前配置保存名称: " config_name "$i18n_not_set" if [ "$config_name" == "$i18n_not_set" ] then config_name="" else config_name="-$config_name" fi mkdir -p "$HOME/openwrt_saved/openwrt-v2ray" printf -v timestamp '%(%s)T' -1 if ! docker cp openwrt:/etc/config/v2ray "$HOME/openwrt_saved/openwrt-v2ray/config-$timestamp$config_name" 2> /dev/null then Println "$error 请先安装 openwrt-v2ray\n" exit 1 fi docker cp openwrt:/etc/v2ray/directlist.txt "$HOME/openwrt_saved/openwrt-v2ray/directlist-$timestamp$config_name" docker cp openwrt:/etc/v2ray/proxylist.txt "$HOME/openwrt_saved/openwrt-v2ray/proxylist-$timestamp$config_name" docker exec -it openwrt /bin/ash -c " cat /var/etc/v2ray/v2ray.main.json 2> /dev/null || true " > "$HOME/openwrt_saved/openwrt-v2ray/main-$timestamp$config_name" Println "$tip 所有配置文件都是透明代理, 直连国内, 代理国外, 需要自行修改出站连接后使用" config_file_options=( 'v2ray-1' 'xray-1' '复原配置' ) inquirer list_input "选择配置文件: " config_file_options config_file if [ "$config_file" == "复原配置" ] then if ! ls -A $HOME/openwrt_saved/openwrt-v2ray/config-* > /dev/null 2>&1 then Println "$error 没有保存的配置\n" exit 1 fi configs_list="" configs_count=0 configs_time=() configs_name=() for file in "$HOME/openwrt_saved/openwrt-v2ray/config-"* do if [[ ${file##*/} =~ ^config-(.+)-(.+)$ ]] then config_time=${BASH_REMATCH[1]} config_name="-${BASH_REMATCH[2]}" config_name_list=${BASH_REMATCH[2]} elif [[ ${file##*/} =~ ^config-(.+)$ ]] then config_time=${BASH_REMATCH[1]} config_name="" config_name_list="" fi configs_time+=("$config_time") configs_name+=("$config_name") configs_count=$((configs_count+1)) printf -v config_date '%(%Y-%m-%d %H:%M:%S)T' "$config_time" configs_list="$configs_list $configs_count.${indent_6}名称: ${green}${config_name_list:-无}${normal} 日期: ${green}$config_date${normal}\n\n" done Println "$configs_list" echo "选择配置" while read -p "$i18n_default_cancel" config_num do case "$config_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$config_num" -gt 0 ] && [ "$config_num" -le $configs_count ] then configs_index=$((config_num-1)) config_time=${configs_time[configs_index]} config_name=${configs_name[configs_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done docker cp "$HOME/openwrt_saved/openwrt-v2ray/config-$config_time$config_name" openwrt:/etc/config/v2ray docker cp "$HOME/openwrt_saved/openwrt-v2ray/directlist-$config_time$config_name" openwrt:/etc/v2ray/directlist.txt docker cp "$HOME/openwrt_saved/openwrt-v2ray/proxylist-$config_time$config_name" openwrt:/etc/v2ray/proxylist.txt Println "$info 配置恢复成功\n" else docker exec -it -e V2RAY_CONFIG_NAME="$config_file" -e MIRROR="$FFMPEG_MIRROR_LINK" openwrt /bin/ash -c ' /etc/init.d/v2ray stop 2> /dev/null || true wget -O /etc/config/v2ray $MIRROR/v2ray-configs/$V2RAY_CONFIG_NAME for ip in $(resolveip dns.alidns.com) do if ! grep -q "$ip" < /etc/v2ray/directlist.txt then echo "$ip" >> /etc/v2ray/directlist.txt fi done for ip in $(resolveip doh.pub) do if ! grep -q "$ip" < /etc/v2ray/directlist.txt then echo "$ip" >> /etc/v2ray/directlist.txt fi done ' Println "$info 配置切换成功\n" fi ;; 11) if [[ ! -x $(command -v docker) ]] then Println "$error 请先安装 docker\n" exit 1 fi if [ ! -s "/etc/docker/daemon.json" ] then printf '%s' "{}" > /etc/docker/daemon.json fi Println "$tip 可以登录阿里云 (https://cr.console.aliyun.com/cn-shanghai/) 查看镜像加速器地址" ExitOnText "请输入加速器地址 : " registry_mirrors if ! $JQ_FILE -V > /dev/null 2>&1 then Spinner "编译安装 JQ, 耗时可能会很长" JQInstall fi json=true jq_path='["registry-mirrors"]' JQ update /etc/docker/daemon.json '["'"$registry_mirrors"'"]' Println "$info docker 镜像加速设置成功\n" ;; 12) VimConfig ;; 13) DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi echo if grep -q "options edns0" < /etc/resolv.conf then ExitOnList n "`gettext \"是否关闭 edns0\"`" chattr -i /etc/resolv.conf sed -i '/options edns0/d' /etc/resolv.conf echo "$(awk '!x{x=sub(/.*require_dnssec = .*/,"require_dnssec = false")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info edns0 已关闭\n" else ExitOnList n "`gettext \"是否开启 edns0\"`" echo "options edns0" >> /etc/resolv.conf chattr +i /etc/resolv.conf echo "$(awk '!x{x=sub(/.*require_dnssec = .*/,"require_dnssec = true")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info edns0 已开启\n" fi ;; 14) DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi echo switch_options=( '开启' '关闭' ) inquirer list_input_index "选择操作" switch_options switch_options_index if [ -f /opt/AdGuardHome/AdGuardHome.yaml ] then /opt/AdGuardHome/AdGuardHome -s stop || true sed -i "s/aaaa_disabled: false/aaaa_disabled: true/" /opt/AdGuardHome/AdGuardHome.yaml /opt/AdGuardHome/AdGuardHome -s start || true fi if [ "$switch_options_index" -eq 0 ] then echo "$(awk '!x{x=sub(/.*block_ipv6 = .*/,"block_ipv6 = false")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info ipv6 查询已开启\n" else echo "$(awk '!x{x=sub(/.*block_ipv6 = .*/,"block_ipv6 = true")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info ipv6 查询已关闭\n" fi ;; 15) if [[ ! -x $(command -v pystun) ]] then Println "$tip 请确保已经修改了合适的 apt 源" ExitOnList n "`gettext \"是否继续\"`" AptUpdate apt-get -y install python python-pip python-setuptools python-wheel pip install pystun fi Println "$tip 建议关闭远端服务器防火墙, 检测中...\n" pystun ;; 16) ShFileUpdate Armbian ;; *) Println "$error $i18n_input_correct_number [1-16]\n" ;; esac exit 0 elif [ "$self" == "pve" ] then if [[ ! -x $(command -v pveum) ]] then Println "$error 不是 Proxmox 系统\n" exit 1 fi ShFileCheck JQ_FILE="/usr/local/bin/jq" Println " Proxmox VE 管理面板 ${normal}${red}[v$sh_ver]${normal} ${green}1.${normal} 设置 apt 源 ${green}2.${normal} 设置 vimrc ${green}3.${normal} 设置 显示器 ${green}4.${normal} 查看 温度 / 风扇 ${green}5.${normal} 设置 风扇 ———————————— ${green}6.${normal} 安装 升级 dnscrypt proxy ${green}7.${normal} 安装 AdGuardHome ${green}8.${normal} 安装 qemu-guest-agent ${green}9.${normal} 安装 升级 openwrt-v2ray ———————————— ${green}10.${normal} 切换 openwrt 语言 ${green}11.${normal} 切换 v2ray/xray core ${green}12.${normal} 切换 配置文件 ———————————— ${green}13.${normal} 开关 edns0 ${green}14.${normal} 开关 ipv6 查询 ${green}15.${normal} 更新脚本 " read -p "`gettext \"输入序号\"` [1-15]: " pve_num case $pve_num in 1) echo apt_options=( '切换 非订阅源/订阅 源' '切换 debian 国内/国外 源' ) inquirer list_input_index "选择操作" apt_options apt_options_index . /etc/os-release if [ "$apt_options_index" -eq 0 ] then echo pve_sources_options=( '非订阅源' '订阅源' ) inquirer list_input "选择 PVE 源" pve_sources_options pve_sources_option if [ "$pve_sources_option" == "非订阅源" ] then if [ -f /etc/apt/sources.list.d/pve-no-subscription.list ] then sed -i "s_#deb http://download.proxmox.com/debian/pve $VERSION_CODENAME pve-no-subscription_deb $FFMPEG_MIRROR_LINK/proxmox/debian/pve $VERSION_CODENAME pve-no-subscription_" /etc/apt/sources.list.d/pve-no-subscription.list elif grep -q "pve-no-subscription" < /etc/apt/sources.list then sed -i "s_#deb http://download.proxmox.com/debian/pve $VERSION_CODENAME pve-no-subscription_deb $FFMPEG_MIRROR_LINK/proxmox/debian/pve $VERSION_CODENAME pve-no-subscription_" /etc/apt/sources.list else echo "deb $FFMPEG_MIRROR_LINK/proxmox/debian/pve $VERSION_CODENAME pve-no-subscription" > /etc/apt/sources.list.d/pve-no-subscription.list fi if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] then rm -f /etc/apt/sources.list.d/pve-enterprise.list elif grep -q "pve-enterprise" < /etc/apt/sources.list then sed -i "s_^deb https://enterprise.proxmox.com/debian/pve $VERSION_CODENAME pve-enterprise_#deb $FFMPEG_MIRROR_LINK/proxmox-enterprise/debian/pve $VERSION_CODENAME pve-enterprise_" /etc/apt/sources.list fi else if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] then sed -i "s_#deb https://enterprise.proxmox.com/debian/pve $VERSION_CODENAME pve-enterprise_deb $FFMPEG_MIRROR_LINK/proxmox-enterprise/debian/pve $VERSION_CODENAME pve-enterprise_" /etc/apt/sources.list.d/pve-enterprise.list elif grep -q "pve-enterprise" < /etc/apt/sources.list then sed -i "s_#deb https://enterprise.proxmox.com/debian/pve $VERSION_CODENAME pve-enterprise_deb $FFMPEG_MIRROR_LINK/proxmox-enterprise/debian/pve $VERSION_CODENAME pve-enterprise_" /etc/apt/sources.list else echo "deb $FFMPEG_MIRROR_LINK/proxmox-enterprise/debian/pve $VERSION_CODENAME pve-enterprise" > /etc/apt/sources.list.d/pve-enterprise.list fi if [ -f /etc/apt/sources.list.d/pve-no-subscription.list ] then rm -f /etc/apt/sources.list.d/pve-no-subscription.list elif grep -q "pve-no-subscription" < /etc/apt/sources.list then sed -i "s_^deb $FFMPEG_MIRROR_LINK/proxmox/debian/pve $VERSION_CODENAME pve-no-subscription_#deb $FFMPEG_MIRROR_LINK/proxmox/debian/pve $VERSION_CODENAME pve-no-subscription_" /etc/apt/sources.list fi fi Println "$info 切换成功\n" exit 0 fi AptSetSources ;; 2) VimConfig ;; 3) Println "$tip 此选项主要用于笔记本" bootloader_options=( Grub Systemd-boot ) inquirer list_input "选择当前引导程序" bootloader_options bootloader echo monitor_options=( '自动关闭显示器' '取消自动关闭显示器' ) inquirer list_input_index "选择操作" monitor_options monitor_options_index if [ "$monitor_options_index" -eq 0 ] then echo inquirer text_input "输入多少秒后自动关闭显示器" console_blank_secs 120 if [ "$bootloader" == "Grub" ] then . /etc/default/grub if grep -q "consoleblank=" <<< "$GRUB_CMDLINE_LINUX_DEFAULT" then cmdline=($GRUB_CMDLINE_LINUX_DEFAULT) GRUB_CMDLINE_LINUX_DEFAULT="" for ele in "${cmdline[@]}" do [ -n "$GRUB_CMDLINE_LINUX_DEFAULT" ] && GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT " if [ "${ele%=*}" == "consoleblank" ] then GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT}consoleblank=$console_blank_secs" else GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT$ele" fi done elif [ -n "$GRUB_CMDLINE_LINUX_DEFAULT" ] then GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT consoleblank=$console_blank_secs" else GRUB_CMDLINE_LINUX_DEFAULT="consoleblank=$console_blank_secs" fi echo "$(awk '!x{x=sub(/GRUB_CMDLINE_LINUX_DEFAULT=.*/,"GRUB_CMDLINE_LINUX_DEFAULT=\"'"$GRUB_CMDLINE_LINUX_DEFAULT"'\"")}1' /etc/default/grub)" > /etc/default/grub update-grub Println "$info 设置成功\n" exit 0 fi if [ ! -s /etc/kernel/cmdline ] then echo -n "consoleblank=$console_blank_secs" >> /etc/kernel/cmdline elif ! grep -q "consoleblank=" < /etc/kernel/cmdline then echo -n "$(< /etc/kernel/cmdline) consoleblank=$console_blank_secs" > /etc/kernel/cmdline else line=$(< /etc/kernel/cmdline) cmdline=($line) SYS_CMDLINE="" for ele in "${cmdline[@]}" do [ -n "$SYS_CMDLINE" ] && SYS_CMDLINE="$SYS_CMDLINE " if [ "${ele%=*}" == "consoleblank" ] then SYS_CMDLINE="${SYS_CMDLINE}consoleblank=$console_blank_secs" else SYS_CMDLINE="$SYS_CMDLINE$ele" fi done echo -n "$SYS_CMDLINE" > /etc/kernel/cmdline fi else if [ "$bootloader" == "Grub" ] then . /etc/default/grub if ! grep -q "consoleblank=" <<< "$GRUB_CMDLINE_LINUX_DEFAULT" then Println "$error 无需操作\n" exit 1 fi cmdline=($GRUB_CMDLINE_LINUX_DEFAULT) GRUB_CMDLINE_LINUX_DEFAULT="" for ele in "${cmdline[@]}" do [ -n "$GRUB_CMDLINE_LINUX_DEFAULT" ] && GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT " if [ "${ele%=*}" == "consoleblank" ] then continue fi GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT$ele" done echo "$(awk '!x{x=sub(/GRUB_CMDLINE_LINUX_DEFAULT=.*/,"GRUB_CMDLINE_LINUX_DEFAULT=\"'"$GRUB_CMDLINE_LINUX_DEFAULT"'\"")}1' /etc/default/grub)" > /etc/default/grub update-grub Println "$info 设置成功\n" exit 0 fi if [ ! -s /etc/kernel/cmdline ] || ! grep -q "consoleblank=" < /etc/kernel/cmdline then Println "$error 无需操作\n" exit 1 fi line=$(< /etc/kernel/cmdline) cmdline=($line) SYS_CMDLINE="" for ele in "${cmdline[@]}" do [ -n "$SYS_CMDLINE" ] && SYS_CMDLINE="$SYS_CMDLINE " if [ "${ele%=*}" == "consoleblank" ] then continue fi SYS_CMDLINE="$SYS_CMDLINE$ele" done echo -n "$SYS_CMDLINE" > /etc/kernel/cmdline fi pve-efiboot-tool refresh Println "$info 设置成功\n" ;; 4) if [[ ! -x $(command -v sensors) ]] then Println "$info 安装 lm-sensors..." AptUpdate apt-get -y install lm-sensors fi sensors if [ -d /opt/nbfc/ ] then cd /opt/nbfc mono nbfc.exe status --all fi ;; 5) if [[ ! -x $(command -v mono) ]] then Println "$tip 此选项主要用于笔记本" ExitOnList n "`gettext \"需要安装 mono, 耗时会很长, 是否继续\"`" AptUpdate apt-get -y install apt-transport-https dirmngr gnupg ca-certificates apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF echo "deb https://download.mono-project.com/repo/debian stable main" | tee /etc/apt/sources.list.d/mono-official-stable.list apt-get update apt-get -y install mono-complete git fi if [ ! -d /opt/nbfc/ ] || ! ls -A /opt/nbfc/* > /dev/null 2>&1 then Println "$info 安装 nbfc..." DepInstall git DepInstall curl DepInstall unzip cd /tmp/ curl -s -L "$FFMPEG_MIRROR_LINK/nbfc.zip" -o nbfc.zip unzip -o nbfc.zip >/dev/null 2>&1 cd nbfc-master sed -i 's~NUGET_URL=\"https://dist.nuget.org~NUGET_URL=\"'"$FFMPEG_MIRROR_LINK"'/nuget~' build.sh ./build.sh mkdir -p /opt/nbfc cp -r /tmp/nbfc-master/Linux/bin/Release/* /opt/nbfc/ cp /tmp/nbfc-master/Linux/{nbfc.service,nbfc-sleep.service} /etc/systemd/system/ systemctl enable nbfc --now || true fi echo nbfc_options=( '查看状态' '设置寄存器值' '开启自动控制' '设置风扇转速' '搜索配置' '应用配置' ) inquirer list_input_index "选择操作" nbfc_options nbfc_options_index cd /opt/nbfc if [ "$nbfc_options_index" -eq 0 ] then mono nbfc.exe status --all elif [ "$nbfc_options_index" -eq 1 ] then echo ExitOnText "输入寄存器地址, 比如 0x93: " register_address echo ExitOnText "输入值, 比如 0x14: " register_value mono ec-probe.exe write $register_address $register_value if [ -f /opt/nbfc/nbfcservice.sh ] then sed -i '/mono \/opt\/nbfc\/ec-probe.exe write /d' /opt/nbfc/nbfcservice.sh sed -i '/start"/a \\t mono \/opt\/nbfc\/ec-probe.exe write '"$register_address"' '"$register_value"'' /opt/nbfc/nbfcservice.sh fi Println "$tip 不一定写入成功, 请自行检查\n" elif [ "$nbfc_options_index" -eq 2 ] then Println "$tip 请查询各厂商风扇手动模式的寄存器值, 只有设置风扇为手动模式 nbfc 才能自动控制" Println "$tip 请确保已经正确应用配置文件\n" inquirer text_input "输入风扇序号(从 0 开始): " fan_index 0 mono nbfc.exe set -f $fan_index -a Println "$info 风扇 $fan_index 已开启自动控制\n" elif [ "$nbfc_options_index" -eq 3 ] then Println "$tip 请确保风扇寄存器值处于手动控制状态, 设置风扇转速将关闭 nbfc 自动控制" echo fan_options=( '输入寄存器值' '输入配置百分比' ) inquirer list_input "选择控制方式" fan_options fan_option if [ "$fan_option" == "输入寄存器值" ] then echo ExitOnText "输入寄存器地址, 比如 0x94: " register_address echo ExitOnText "输入值, 比如 0x99: " register_value mono ec-probe.exe write $register_address $register_value Println "$tip 不一定写入成功, 请自行检查\n" else Println "$tip 请确保已经正确应用配置文件" inquirer text_input "输入风扇序号(从 0 开始): " fan_index 0 echo ExitOnText "输入转速百分比: " fan_speed mono nbfc.exe set -f $fan_index -s $fan_speed Println "$tip 操作成功\n" fi elif [ "$nbfc_options_index" -eq 4 ] then mono nbfc.exe config -r elif [ "$nbfc_options_index" -eq 5 ] then echo ExitOnText "输入配置名称, 比如: Acer Aspire 5745G" config_name mono nbfc.exe config --apply "$config_name" mono nbfc.exe start Println "$info 配置已生效\n" fi ;; 6) JQInstall if dnscrypt_version=$(curl -s -Lm 10 "$FFMPEG_MIRROR_LINK/dnscrypt.json" | $JQ_FILE -r '.tag_name') then DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version_old=${DNSCRYPT_ROOT#*-} echo inquirer list_input "本机是否在国内" yn_options location_china if [[ $dnscrypt_version_old == "*" ]] then echo inquirer text_input "输入监听端口 : " listen_port 53 Println "$info 下载 dnscrypt proxy ..." if ! curl -L "$FFMPEG_MIRROR_LINK/dnscrypt/dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz" -o ~/dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz_tmp then Println "$error dnscrypt proxy 下载失败, 请重试\n" exit 1 fi Println "$info 设置 dnscrypt proxy ..." cd ~ mv dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz_tmp dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz tar zxf dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz mv linux-x86_64 dnscrypt-$dnscrypt_version chown -R $USER:$USER dnscrypt-$dnscrypt_version cd dnscrypt-$dnscrypt_version cp -f example-dnscrypt-proxy.toml dnscrypt-proxy.toml DNSCryptConfig for((i=0;i<3;i++)); do if ./dnscrypt-proxy -check > /dev/null then break elif [[ $i -eq 2 ]] then cd ~ rm -rf dnscrypt-$dnscrypt_version Println "$error 发生错误, 请重试\n" exit 1 fi done if [ -d /etc/resolvconf ] then DEBIAN_FRONTEND=noninteractive apt-get -y --purge remove resolvconf > /dev/null 2>&1 || true fi systemctl stop systemd-resolved systemctl disable systemd-resolved ./dnscrypt-proxy -service install > /dev/null ./dnscrypt-proxy -service start > /dev/null if [ -f /etc/resolv.conf ] then printf -v now '%(%m-%d-%H:%M:%S)T' -1 mv /etc/resolv.conf /etc/resolv.conf-$now fi echo -e "nameserver 127.0.0.1\noptions edns0" > /etc/resolv.conf Println "$info dnscrypt proxy 安装配置成功\n" elif [[ $dnscrypt_version_old != "$dnscrypt_version" ]] then block_ipv6=$(sed -n -e "s/^block_ipv6 = \(.*\)/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) require_dnssec=$(sed -n -e "s/^require_dnssec = \(.*\)/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) echo inquirer text_input "输入监听端口 : " listen_port $(sed -n -e "s/^listen_addresses = .*:\([0-9]*\)']/\1/p" $DNSCRYPT_ROOT/dnscrypt-proxy.toml) if ! curl -L "$FFMPEG_MIRROR_LINK/dnscrypt/dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz" -o ~/dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz_tmp then Println "$error dnscrypt proxy 下载失败, 请重试\n" exit 1 fi if [ -L /etc/resolv.conf ] then etc_resolv=$(< /etc/resolv.conf) rm -f /etc/resolv.conf echo "$etc_resolv" > /etc/resolv.conf fi cd ~/dnscrypt-$dnscrypt_version_old ./dnscrypt-proxy -service stop > /dev/null ./dnscrypt-proxy -service uninstall > /dev/null cd ~ mv dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz_tmp dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz tar zxf dnscrypt-proxy-linux_x86_64-$dnscrypt_version.tar.gz mv linux-x86_64 dnscrypt-$dnscrypt_version cd dnscrypt-$dnscrypt_version cp -f example-dnscrypt-proxy.toml dnscrypt-proxy.toml DNSCryptConfig ./dnscrypt-proxy -service install > /dev/null ./dnscrypt-proxy -service start > /dev/null Println "$info dnscrypt proxy 升级成功\n" else Println "$error dnscrypt proxy 已经是最新\n" fi else Println "$error 无法连接服务器, 请稍后再试\n" fi ;; 7) DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version_old=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version_old == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi if [ -d /opt/AdGuardHome ] then Println "$error AdGuard Home 已经存在\n" exit 1 fi echo ExitOnText "输入 dnscrypt-proxy 新的监听端口" listen_port if [ "$listen_port" -eq 53 ] then Println "$error 端口不能是 53\n" exit 1 fi if ! curl -s -S -L $FFMPEG_MIRROR_LINK/AdGuardHome/master/scripts/install.sh | sh -s -- -v then Println "$error 安装发生错误\n" exit 1 fi echo "$(awk '!x{x=sub(/^listen_addresses = .*/,"listen_addresses = [\047[::]:'"$listen_port"'\047]")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy if ! curl -s -H 'Content-Type: application/json' \ --data '{"upstream_dns": ["127.0.0.1:'"$listen_port"'"], "ratelimit": 0}' "http://localhost:3000/control/dns_config" then Println "$error 请登陆 http://PVE_IP:3000/ 手动修改上游 DNS 服务器为 127.0.0.1:$listen_port 并可以将速度限制设置为 0\n" exit 1 fi Println "$info AdGuard Home 安装成功\n" ;; 8) PveListVMs PveSelectVM if [ "$vm_status" != "running" ] then Println "$error 请先启动虚拟机 $vm_name\n" exit 1 fi qm set $vm_id --agent 1 Println "$info 请在虚拟机内执行 opkg update; opkg install qemu-ga 后关机再开启\n\n如果是在国内, 可以在 openwrt 内执行下面命令加快 opkg 速度\nsed -i 's_http[s]*://downloads.openwrt.org_$FFMPEG_MIRROR_LINK/openwrt_' /etc/opkg/distfeeds.conf\n" ;; 9) JQInstall Println "$tip 请确保已经安装 qemu-guest-agent\n" PveListVMs PveSelectVM if [ "$vm_status" != "running" ] then Println "$error 请先启动虚拟机 $vm_name\n" exit 1 fi echo qm guest exec $vm_id wget "$FFMPEG_MIRROR_LINK/pve/snippets/openwrt-v2ray-install.sh" -- "-O" "/root/openwrt-v2ray-install.sh" | $JQ_FILE -r '."err-data" // ."out-data"' Println "$info 正在安装 openwrt-v2ray, 请耐心等待 ..." echo qm guest exec $vm_id --timeout 0 ash "/root/openwrt-v2ray-install.sh" | $JQ_FILE -r '."err-data" // ."out-data"' Println "$info openwrt-v2ray 安装成功, 请重新登录 openwrt 后台\n" ;; 10) JQInstall Println "$tip 请确保已经安装 qemu-guest-agent\n" PveListVMs PveSelectVM echo lang_options=( '简体中文' '繁体中文' '英文' ) inquirer list_input "选择界面语言" lang_options lang if [ "$lang" == "简体中文" ] then lang="zh-cn" elif [ "$lang" == "繁体中文" ] then lang="zh-tw" else lang="en" fi Println "$info 设置 openwrt 语言, 请稍等 ..." echo qm guest exec $vm_id wget "$FFMPEG_MIRROR_LINK/pve/snippets/openwrt-language-install.sh" -- "-O" "/root/openwrt-language-install.sh" | $JQ_FILE -r '."err-data" // ."out-data"' echo qm guest exec $vm_id --timeout 0 ash "/root/openwrt-language-install.sh" "$lang" | $JQ_FILE -r '."err-data" // ."out-data"' Println "$info 界面语言切换成功\n" ;; 11) JQInstall Println "$tip 请确保已经安装 qemu-guest-agent\n" PveListVMs PveSelectVM Println "$tip 请确保已经安装过 openwrt-v2ray" core_options=( 'xray-core' 'v2ray-core' ) inquirer list_input "选择切换目标" core_options core if [ "$core" == "xray-core" ] then echo xray_options=( '最新' '1.4.5' ) inquirer list_input "选择 xray 版本" xray_options xray_ver if [ "$xray_ver" == "最新" ] && ! xray_ver=$(curl -s -m 30 "$FFMPEG_MIRROR_LINK/openwrt-xray.json" | $JQ_FILE -r '.tag_name') then Println "$error 无法连接服务器, 请稍后再试\n" exit 1 else xray_ver=${xray_ver#*v} if [[ ! $xray_ver =~ - ]] then xray_ver="${xray_ver}-1" fi fi if ! luci_app_xray_ver=$(curl -s -m 30 "$FFMPEG_MIRROR_LINK/luci-app-xray.json" | $JQ_FILE -r '.tag_name') then Println "$error 无法连接服务器, 请稍后再试\n" exit 1 else luci_app_xray_ver=${luci_app_xray_ver#*v} fi Println "$info 切换 openwrt-xray, 请稍等 ..." echo qm guest exec $vm_id wget "$FFMPEG_MIRROR_LINK/pve/snippets/openwrt-xray-install.sh" -- "-O" "/root/openwrt-xray-install.sh" | $JQ_FILE -r '."err-data" // ."out-data"' echo qm guest exec $vm_id --timeout 0 ash "/root/openwrt-xray-install.sh" "$xray_ver" "$luci_app_xray_ver" | $JQ_FILE -r '."err-data" // ."out-data"' else echo inquirer list_input "是否更新 openwrt-v2ray" ny_options ny_option if [ "$ny_option" == "$i18n_yes" ] then Println "$info 更新 openwrt-v2ray, 请稍等 ...\n" qm guest exec $vm_id opkg update | $JQ_FILE -r '."err-data" // ."out-data"' echo qm guest exec $vm_id opkg install v2ray-core | $JQ_FILE -r '."err-data" // ."out-data"' fi Println "$info 切换 openwrt-v2ray, 请稍等 ..." echo qm guest exec $vm_id uci set v2ray.main.v2ray_file='/usr/bin/v2ray' | $JQ_FILE -r '."err-data" // ."out-data"' echo qm guest exec $vm_id uci del v2ray.main.asset_location | $JQ_FILE -r '."err-data" // ."out-data"' echo qm guest exec $vm_id uci commit | $JQ_FILE -r '."err-data" // ."out-data"' echo qm guest exec $vm_id reload_config | $JQ_FILE -r '."err-data" // ."out-data"' fi Println "$info 切换成功\n" ;; 12) JQInstall Println "$tip 请确保已经安装 qemu-guest-agent\n" PveListVMs PveSelectVM Println "$tip 请确保已经安装过 openwrt-v2ray" inquirer text_input "输入当前配置保存名称: " config_name "$i18n_not_set" if [ "$config_name" == "$i18n_not_set" ] then config_name="" else config_name="-$config_name" fi Println "$tip 备份 openwrt-v2ray, 请稍等 ..." qm guest exec $vm_id wget "$FFMPEG_MIRROR_LINK/pve/snippets/openwrt-config-install.sh" -- "-O" "/root/openwrt-config-install.sh" | $JQ_FILE -r '."err-data" // ."out-data"' if [[ $(qm guest exec $vm_id ash "/root/openwrt-config-install.sh" | $JQ_FILE -r '."err-data" // ."out-data"') == "no" ]] then Println "$error 请先安装 openwrt-v2ray\n" exit 1 fi printf -v timestamp '%(%s)T' -1 echo qm guest exec $vm_id ash "/root/openwrt-config-install.sh" -- "save" "$timestamp" "$config_name" > /dev/null Println "$tip 所有配置文件都是透明代理, 直连国内, 代理国外, 需要自行修改出站连接后使用" config_file_options=( 'v2ray-1' 'xray-1' '复原配置' ) inquirer list_input "选择配置文件: " config_file_options config_file if [ "$config_file" == "复原配置" ] then echo files=($(qm guest exec $vm_id ash "/root/openwrt-config-install.sh" -- "list" | $JQ_FILE -r '."err-data" // ."out-data"')) if [ "${files[0]}" == "no" ] then Println "$error 没有保存的配置\n" exit 1 fi configs_list="" configs_count=0 configs_time=() configs_name=() for file in "${files[@]}" do if [[ ${file##*/} =~ ^config-(.+)-(.+)$ ]] then config_time=${BASH_REMATCH[1]} config_name="-${BASH_REMATCH[2]}" config_name_list=${BASH_REMATCH[2]} elif [[ ${file##*/} =~ ^config-(.+)$ ]] then config_time=${BASH_REMATCH[1]} config_name="" config_name_list="" fi configs_time+=("$config_time") configs_name+=("$config_name") configs_count=$((configs_count+1)) printf -v config_date '%(%Y-%m-%d %H:%M:%S)T' "$config_time" configs_list="$configs_list $configs_count.${indent_6}名称: ${green}${config_name_list:-无}${normal} 日期: ${green}$config_date${normal}\n\n" done Println "$configs_list" echo "选择配置" while read -p "$i18n_default_cancel" config_num do case "$config_num" in "") Println "$i18n_canceled...\n" && exit 1 ;; *[!0-9]*) Println "$error $i18n_input_correct_no\n" ;; *) if [ "$config_num" -gt 0 ] && [ "$config_num" -le $configs_count ] then configs_index=$((config_num-1)) config_time=${configs_time[configs_index]} config_name=${configs_name[configs_index]} break else Println "$error $i18n_input_correct_no\n" fi ;; esac done echo qm guest exec $vm_id ash "/root/openwrt-config-install.sh" -- "restore" "$config_time" "$config_name" | $JQ_FILE -r '."err-data" // ."out-data"' Println "$info 配置恢复成功\n" else Println "$info 切换配置中, 请稍等 ..." qm guest exec $vm_id ash "/root/openwrt-config-install.sh" -- "switch" "$config_file" | $JQ_FILE -r '."err-data" // ."out-data"' Println "$info 配置切换成功\n" fi ;; 13) DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi echo if grep -q "options edns0" < /etc/resolv.conf then ExitOnList n "`gettext \"是否关闭 edns0\"`" sed -i '/options edns0/d' /etc/resolv.conf echo "$(awk '!x{x=sub(/.*require_dnssec = .*/,"require_dnssec = false")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info edns0 已关闭\n" else ExitOnList n "`gettext \"是否开启 edns0\"`" echo "options edns0" >> /etc/resolv.conf echo "$(awk '!x{x=sub(/.*require_dnssec = .*/,"require_dnssec = true")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info edns0 已开启\n" fi ;; 14) DNSCRYPT_ROOT=$(dirname ~/dnscrypt-*/dnscrypt-proxy | sort | tail -1) dnscrypt_version=${DNSCRYPT_ROOT#*-} if [[ $dnscrypt_version == "*" ]] then Println "$error 请先安装 dnscrypt proxy\n" exit 1 fi echo switch_options=( '开启' '关闭' ) inquirer list_input_index "选择操作" switch_options switch_options_index if [ -f /opt/AdGuardHome/AdGuardHome.yaml ] then /opt/AdGuardHome/AdGuardHome -s stop || true sed -i "s/aaaa_disabled: false/aaaa_disabled: true/" /opt/AdGuardHome/AdGuardHome.yaml /opt/AdGuardHome/AdGuardHome -s start || true fi if [ "$switch_options_index" -eq 0 ] then echo "$(awk '!x{x=sub(/.*block_ipv6 = .*/,"block_ipv6 = false")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info ipv6 查询已开启\n" else echo "$(awk '!x{x=sub(/.*block_ipv6 = .*/,"block_ipv6 = true")}1' $DNSCRYPT_ROOT/dnscrypt-proxy.toml)" > "$DNSCRYPT_ROOT/dnscrypt-proxy.toml" systemctl restart dnscrypt-proxy Println "$info ipv6 查询已关闭\n" fi ;; 15) ShFileUpdate PVE ;; *) Println "$error $i18n_input_correct_number [1-15]\n" ;; esac exit 0 fi if [[ -n ${1+x} ]] then case $1 in "4g") if [ ! -d "$IPTV_ROOT" ] then Println "$error 请先安装脚本 !\n" && exit 1 fi user_agent="$USER_AGENT_BROWSER" if [ "${2:-}" == "-" ] then _4gtvCron exit 0 fi OpensslInstall Println " 4gtv 面板 ${green}1.${normal} 注册账号 ${green}2.${normal} 登录账号 ${green}3.${normal} 查看账号 ${green}4.${normal} 修改账号 ${green}5.${normal} 删除账号 ${green}6.${normal} 使用免费频道 ${green}7.${normal} 使用豪华频道 ${green}8.${normal} 开启计划任务 ${green}9.${normal} 关闭计划任务 " while read -p "(默认: 6): " _4gtv_menu_num do _4gtv_menu_num=${_4gtv_menu_num:-6} case "$_4gtv_menu_num" in 1) Reg4gtvAcc exit 0 ;; 2) Login4gtvAcc exit 0 ;; 3) List4gtvAcc exit 0 ;; 4) Edit4gtvAcc exit 0 ;; 5) Del4gtvAcc exit 0 ;; 6) _4gtv_set_id=4 fsVALUE="" Use4gtvProxy break ;; 7) Get4gtvAccToken _4gtv_set_id=1 Use4gtvProxy break ;; 8) Enable4gtvCron exit 0 ;; 9) Disable4gtvCron exit 0 ;; *) Println "$error $i18n_input_correct_number [1-9]\n" ;; esac done hinet_4gtv=( "litv-ftv13:民視新聞台" "litv-longturn14:寰宇新聞台" "4gtv-4gtv052:華視新聞資訊台" "4gtv-4gtv012:空中英語教室" "litv-ftv07:民視旅遊台" "litv-ftv15:i-Fun動漫台" "4gtv-live206:幸福空間居家台" "4gtv-4gtv070:愛爾達娛樂台" "litv-longturn17:亞洲旅遊台" "4gtv-4gtv025:MTV Live HD" "litv-longturn15:寰宇新聞台灣台" "4gtv-4gtv001:民視台灣台" "4gtv-4gtv074:中視新聞台" "4gtv-4gtv011:影迷數位電影台" "4gtv-4gtv047:靖天日本台" "litv-longturn11:龍華日韓台" "litv-longturn12:龍華偶像台" "4gtv-4gtv042:公視戲劇" "litv-ftv12:i-Fun動漫台3" "4gtv-4gtv002:民視無線台" "4gtv-4gtv027:CI 罪案偵查頻道" "4gtv-4gtv013:CNEX紀實頻道" "litv-longturn03:龍華電影台" "4gtv-4gtv004:民視綜藝台" "litv-longturn20:ELTV英語學習台" "litv-longturn01:龍華卡通台" "4gtv-4gtv040:中視無線台" "litv-longturn02:Baby First" "4gtv-4gtv003:民視第一台" "4gtv-4gtv007:大愛電視台" "4gtv-4gtv076:SMART 知識頻道" "4gtv-4gtv030:CNBC" "litv-ftv10:半島電視台" ) GetChannels hinet_4gtv_count=${#hinet_4gtv[@]} hinet_4gtv_list="" for((i=0;i /dev/null) exit 0 ;; "astro") Println "$info 检测 astro ..." delimiters=( $'\001' ) IFS=$'\002\t' read -r m_id m_title m_description m_is_hd m_language < <( JQs flat "$(curl -s -Lm 20 -H 'User-Agent: '"$USER_AGENT_BROWSER"'' https://contenthub-api.eco.astro.com.my/channel/all.json)" '.[0].response' ' . as $response | reduce ({id,title,description,isHd,language}|keys_unsorted[]) as $key ([]; $response[$key] as $val | if $val then . + [$val + "\u0001\u0002"] else . + ["\u0002"] end )|@tsv' "${delimiters[@]}") IFS="${delimiters[0]}" read -ra chnls_id <<< "$m_id" IFS="${delimiters[0]}" read -ra chnls_title <<< "$m_title" IFS="${delimiters[0]}" read -ra chnls_description <<< "$m_description" IFS="${delimiters[0]}" read -ra chnls_is_hd <<< "$m_is_hd" IFS="${delimiters[0]}" read -ra chnls_language <<< "$m_language" chnls_list="" for((i=0;i<${#chnls_id[@]};i++)); do if [ "${chnls_is_hd[i]}" = true ] then is_hd="${green}是${normal}" else is_hd="${red}否${normal}" fi chnls_list="$chnls_list ${green}$((i+1)).${normal}${indent_6}频道ID: ${green}${chnls_id[i]}${normal} 频道名称: ${green}${chnls_title[i]}${normal}\n${indent_6}高清: ${green}$is_hd${normal} 语言: ${green}${chnls_language[i]}${normal}\n${indent_6}${chnls_description[i]}\n\n" done Println "$chnls_list" exit 0 ;; "m") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请先安装 !" && exit 1 [ ! -d "${MONITOR_LOG%/*}" ] && MONITOR_LOG="$HOME/monitor.log" cmd=${2:-} case $cmd in "s"|"stop") MonitorStop ;; "l"|"log") MonitorList "${3:-}" ;; *) MonitorStart ;; esac exit 0 ;; "e") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 editor "$CHANNELS_FILE" && exit 0 ;; "ee") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 GetDefault [ -z "$d_sync_file" ] && Println "$error sync_file 未设置, 请检查 !\n" && exit 1 echo edit_options=($d_sync_file) inquirer list_input "选择修改的文件" edit_options edit_option editor "$edit_option" exit 0 ;; "d") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 echo ExitOnList n "请确保已经升级到最新脚本, 是否继续" ShFallback channels="" while IFS= read -r line do if [[ $line == *\"pid\":* ]] then pid=${line#*:} pid=${pid%,*} rand_pid=$pid while [[ -n $($JQ_FILE '.channels[]|select(.pid=='"$rand_pid"')' "$CHANNELS_FILE") ]] do true & rand_pid=$! done line=${line//$pid/$rand_pid} fi channels="$channels$line" done < <(curl -s -Lm 20 "$SH_FALLBACK/$DEFAULT_DEMOS") [ -z "$channels" ] && Println "$error 暂时无法连接服务器, 请稍后再试 !\n" && exit 1 delimiters=( $'\001' ) IFS=$'\001' read -r -a channels_name < <(JQs flat "$channels" '' '.channel_name' "${delimiters[@]}") echo channels_name+=("全部") inquirer list_input "选择添加的频道" channels_name channel_name if [ "$channel_name" != "全部" ] then channels=$($JQ_FILE '[.[]|select(.channel_name=="'"$channel_name"'")]' <<< "$channels") fi jq_path='["channels"]' JQ add "$CHANNELS_FILE" "$channels" Println "$info 频道添加成功 !\n" exit 0 ;; "ffmpeg"|"FFmpeg") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 if [[ ! -x $(command -v curl) ]] || [ ! -e "$JQ_FILE" ] then DepsCheck fi if [ ! -e "$JQ_FILE" ] then JQInstall fi mkdir -p "$FFMPEG_MIRROR_ROOT/builds" mkdir -p "$FFMPEG_MIRROR_ROOT/releases" git_download=0 release_download=0 git_version_old="" release_version_old="" if [ -e "$FFMPEG_MIRROR_ROOT/index.html" ] then while IFS= read -r line do if [[ $line == *""* ]] then if [[ $line == *"git"* ]] then git_version_old="$line" else release_version_old="$line" fi fi done < "$FFMPEG_MIRROR_ROOT/index.html" fi if curl -s -L "https://www.johnvansickle.com/ffmpeg/index.html" -o "$FFMPEG_MIRROR_ROOT/index.html_tmp" then mv "$FFMPEG_MIRROR_ROOT/index.html_tmp" "$FFMPEG_MIRROR_ROOT/index.html" curl -s -L "https://www.johnvansickle.com/ffmpeg/style.css" -o "$FFMPEG_MIRROR_ROOT/style.css" else Println "$error ffmpeg 查询新版本出错, 无法连接 johnvansickle.com ?" fi if [ -e "$FFMPEG_MIRROR_ROOT/index.html" ] then while IFS= read -r line do if [[ $line == *""* ]] then if [[ $line == *"git"* ]] then git_version_new="$line" if [ "$git_version_new" != "$git_version_old" ] || [ ! -e "$FFMPEG_MIRROR_ROOT/builds/ffmpeg-git-amd64-static.tar.xz" ] then git_download=1 fi else release_version_new="$line" [ "$release_version_new" != "$release_version_old" ] && release_download=1 fi fi if [[ $line == *"tar.xz"* ]] then if [[ $line == *"git"* ]] && [ "$git_download" -eq 1 ] then line=${line#* /dev/null) if jq_ver=$(curl -s -Lm 20 "https://api.github.com/repos/stedolan/jq/releases/latest" | $JQ_FILE -r '.tag_name') then if [ ! -e "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux64" ] || [ ! -e "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux32" ] then Println "$info 下载 jq ..." rm -rf "$FFMPEG_MIRROR_ROOT/jq-"* mkdir -p "$FFMPEG_MIRROR_ROOT/$jq_ver/" if curl -s -L "https://github.com/stedolan/jq/releases/download/$jq_ver/jq-linux64" -o "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux64_tmp" && curl -s -L "https://github.com/stedolan/jq/releases/download/$jq_ver/jq-linux32" -o "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux32_tmp" then mv "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux64_tmp" "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux64" mv "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux32_tmp" "$FFMPEG_MIRROR_ROOT/$jq_ver/jq-linux32" else Println "$error jq 下载出错, 无法连接 github ?" fi fi else Println "$error jq 下载出错, 无法连接 github ?" fi archs=( 32 64 arm32-v5 arm32-v6 arm32-v7a arm64-v8a s390x) if v2ray_ver=$(curl -s -m 30 "https://api.github.com/repos/v2fly/v2ray-core/releases/latest" | $JQ_FILE -r '.tag_name') then mkdir -p "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/" for arch in "${archs[@]}" do if [ ! -e "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/v2ray-linux-$arch.zip" ] then Println "$info 下载 v2ray-linux-$arch $v2ray_ver ..." if curl -s -L "https://github.com/v2fly/v2ray-core/releases/download/$v2ray_ver/v2ray-linux-$arch.zip" -o "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/v2ray-linux-$arch.zip_tmp" \ && curl -s -L "https://github.com/v2fly/v2ray-core/releases/download/$v2ray_ver/v2ray-linux-$arch.zip.dgst" -o "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/v2ray-linux-$arch.zip.dgst_tmp" then mv "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/v2ray-linux-$arch.zip_tmp" "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/v2ray-linux-$arch.zip" mv "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/v2ray-linux-$arch.zip.dgst_tmp" "$FFMPEG_MIRROR_ROOT/v2ray/$v2ray_ver/v2ray-linux-$arch.zip.dgst" else Println "$error v2ray-linux-$arch $v2ray_ver 下载出错, 无法连接 github ?" fi fi done else Println "$error v2ray $v2ray_ver 下载出错, 无法连接 github ?" fi if xray_ver=$(curl -s -m 30 "https://api.github.com/repos/XTLS/Xray-core/releases/latest" | $JQ_FILE -r '.tag_name') then mkdir -p "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/" for arch in "${archs[@]}" do if [ ! -e "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/Xray-linux-$arch.zip" ] then Println "$info 下载 Xray-linux-$arch $xray_ver ..." if curl -s -L "https://github.com/XTLS/Xray-core/releases/download/$xray_ver/Xray-linux-$arch.zip" -o "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/Xray-linux-$arch.zip_tmp" \ && curl -s -L "https://github.com/XTLS/Xray-core/releases/download/$xray_ver/Xray-linux-$arch.zip.dgst" -o "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/Xray-linux-$arch.zip.dgst_tmp" then mv "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/Xray-linux-$arch.zip_tmp" "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/Xray-linux-$arch.zip" mv "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/Xray-linux-$arch.zip.dgst_tmp" "$FFMPEG_MIRROR_ROOT/xray/$xray_ver/Xray-linux-$arch.zip.dgst" else Println "$error Xray-linux-$arch $xray_ver 下载出错, 无法连接 github ?" fi fi done else Println "$error xray $xray_ver 下载出错, 无法连接 github ?" fi if xray_ver=$(curl -s -m 30 "https://api.github.com/repos/woniuzfb/openwrt-xray/releases/latest" | $JQ_FILE -r '.tag_name') then xray_ver=${xray_ver#*v} if [[ ! $xray_ver =~ - ]] then xray_package_ver="${xray_ver}-1" else xray_package_ver="$xray_ver" fi xray_archs=( 'x86_64' 'aarch64_generic' 'aarch64_cortex-a53' ) for arch in "${xray_archs[@]}" do if [ ! -e "$FFMPEG_MIRROR_ROOT/xray_${xray_package_ver}_$arch.ipk" ] then Println "$info 下载 xray_${xray_package_ver}_$arch.ipk ..." if curl -s -L "https://github.com/woniuzfb/openwrt-xray/releases/download/v$xray_ver/xray_${xray_package_ver}_$arch.ipk" -o "$FFMPEG_MIRROR_ROOT/xray_${xray_package_ver}_$arch.ipk_tmp" then mv "$FFMPEG_MIRROR_ROOT/xray_${xray_package_ver}_$arch.ipk_tmp" "$FFMPEG_MIRROR_ROOT/xray_${xray_package_ver}_$arch.ipk" else Println "$error xray_${xray_package_ver}_$arch.ipk 下载出错, 无法连接 github ?" fi fi done else Println "$error openwrt-xray 下载出错, 无法连接 github ?" fi IFS=" " read -r luci_app_xray_ver xray_i18n_name < <(curl -s -m 30 "https://api.github.com/repos/woniuzfb/luci-app-xray/releases/latest" | $JQ_FILE -r '[.tag_name,.assets[1].name]|join(" ")') if [ -n "${luci_app_xray_ver:-}" ] then luci_app_xray_ver=${luci_app_xray_ver#*v} Println "$info 下载 luci-app-v2ray_${luci_app_xray_ver}_all.ipk ..." if curl -s -L "https://github.com/woniuzfb/luci-app-xray/releases/download/v$luci_app_xray_ver/luci-app-v2ray_${luci_app_xray_ver%-*}_all.ipk" -o "$FFMPEG_MIRROR_ROOT/luci-app-v2ray_${luci_app_xray_ver}_all.ipk_tmp" then mv "$FFMPEG_MIRROR_ROOT/luci-app-v2ray_${luci_app_xray_ver}_all.ipk_tmp" "$FFMPEG_MIRROR_ROOT/luci-app-v2ray_${luci_app_xray_ver}_all.ipk" else Println "$error luci-app-v2ray_${luci_app_xray_ver}_all.ipk 下载出错, 无法连接 github ?" fi Println "$info 下载 $xray_i18n_name ..." if curl -s -L "https://github.com/woniuzfb/luci-app-xray/releases/download/v$luci_app_xray_ver/$xray_i18n_name" -o "$FFMPEG_MIRROR_ROOT/${xray_i18n_name}_tmp" then mv "$FFMPEG_MIRROR_ROOT/${xray_i18n_name}_tmp" "$FFMPEG_MIRROR_ROOT/luci-i18n-v2ray-zh-cn_${luci_app_xray_ver}_all.ipk" else Println "$error $xray_i18n_name 下载出错, 无法连接 github ?" fi else Println "$error luci-app-xray 下载出错, 无法连接 github ?" fi if dnscrypt_ver=$(curl -s -m 30 "https://api.github.com/repos/DNSCrypt/dnscrypt-proxy/releases/latest" | $JQ_FILE -r '.tag_name') then archs=( arm arm64 i386 x86_64 ) for arch in "${archs[@]}" do if [ ! -e "$FFMPEG_MIRROR_ROOT/dnscrypt/dnscrypt-proxy-linux_$arch-$dnscrypt_ver.tar.gz" ] then Println "$info 下载 dnscrypt proxy $arch ..." mkdir -p "$FFMPEG_MIRROR_ROOT/dnscrypt/" if curl -s -L "https://github.com/DNSCrypt/dnscrypt-proxy/releases/download/$dnscrypt_ver/dnscrypt-proxy-linux_$arch-$dnscrypt_ver.tar.gz" -o "$FFMPEG_MIRROR_ROOT/dnscrypt/dnscrypt-proxy-linux_$arch-$dnscrypt_ver.tar.gz_tmp" then mv "$FFMPEG_MIRROR_ROOT/dnscrypt/dnscrypt-proxy-linux_$arch-$dnscrypt_ver.tar.gz_tmp" "$FFMPEG_MIRROR_ROOT/dnscrypt/dnscrypt-proxy-linux_$arch-$dnscrypt_ver.tar.gz" else Println "$error dnscrypt $arch 下载出错, 无法连接 github ?" fi fi done else Println "$error dnscrypt 下载出错, 无法连接 github ?" fi Println "$info 下载 nginx-http-flv-module ..." if curl -s -L "https://github.com/winshining/nginx-http-flv-module/archive/master.zip" -o "$FFMPEG_MIRROR_ROOT/nginx-http-flv-module.zip_tmp" then mv "$FFMPEG_MIRROR_ROOT/nginx-http-flv-module.zip_tmp" "$FFMPEG_MIRROR_ROOT/nginx-http-flv-module.zip" else Println "$error nginx-http-flv-module 下载出错, 无法连接 github ?" fi Println "$info 下载 imgcat ..." if curl -s -L "https://github.com/eddieantonio/imgcat/archive/master.zip" -o "$FFMPEG_MIRROR_ROOT/imgcat.zip_tmp" then mv "$FFMPEG_MIRROR_ROOT/imgcat.zip_tmp" "$FFMPEG_MIRROR_ROOT/imgcat.zip" else Println "$error imgcat 下载出错, 无法连接 github ?" fi Println "$info 下载 CImg ..." if curl -s -L "https://github.com/dtschump/CImg/archive/master.zip" -o "$FFMPEG_MIRROR_ROOT/CImg.zip_tmp" then mv "$FFMPEG_MIRROR_ROOT/CImg.zip_tmp" "$FFMPEG_MIRROR_ROOT/CImg.zip" else Println "$error CImg 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/stedolan/jq/releases/latest" -o "$FFMPEG_MIRROR_ROOT/jq.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/jq.json_tmp" "$FFMPEG_MIRROR_ROOT/jq.json" else Println "$error jq.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/alist-org/alist/releases/latest" -o "$FFMPEG_MIRROR_ROOT/alist.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/alist.json_tmp" "$FFMPEG_MIRROR_ROOT/alist.json" else Println "$error alist.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/v2fly/v2ray-core/releases/latest" -o "$FFMPEG_MIRROR_ROOT/v2ray.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/v2ray.json_tmp" "$FFMPEG_MIRROR_ROOT/v2ray.json" else Println "$error v2ray.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/XTLS/Xray-core/releases/latest" -o "$FFMPEG_MIRROR_ROOT/xray.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/xray.json_tmp" "$FFMPEG_MIRROR_ROOT/xray.json" else Println "$error xray.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/woniuzfb/openwrt-xray/releases/latest" -o "$FFMPEG_MIRROR_ROOT/openwrt-xray.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/openwrt-xray.json_tmp" "$FFMPEG_MIRROR_ROOT/openwrt-xray.json" else Println "$error openwrt-xray.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/woniuzfb/luci-app-xray/releases/latest" -o "$FFMPEG_MIRROR_ROOT/luci-app-xray.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/luci-app-xray.json_tmp" "$FFMPEG_MIRROR_ROOT/luci-app-xray.json" else Println "$error luci-app-xray.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/DNSCrypt/dnscrypt-proxy/releases/latest" -o "$FFMPEG_MIRROR_ROOT/dnscrypt.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/dnscrypt.json_tmp" "$FFMPEG_MIRROR_ROOT/dnscrypt.json" else Println "$error dnscrypt.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://api.github.com/repos/PCRE2Project/pcre2/releases/latest" -o "$FFMPEG_MIRROR_ROOT/pcre2.json_tmp" then mv "$FFMPEG_MIRROR_ROOT/pcre2.json_tmp" "$FFMPEG_MIRROR_ROOT/pcre2.json" else Println "$error pcre2.json 下载出错, 无法连接 github ?" fi if curl -s -L "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" -o "$FFMPEG_MIRROR_ROOT/vim-plug.vim_tmp" then mv "$FFMPEG_MIRROR_ROOT/vim-plug.vim_tmp" "$FFMPEG_MIRROR_ROOT/vim-plug.vim" else Println "$error vim-plug.vim 下载出错, 无法连接 github ?" fi if [ ! -e "$FFMPEG_MIRROR_ROOT/openssl-1.1.1f-sess_set_get_cb_yield.patch" ] then if curl -s -L "https://raw.githubusercontent.com/openresty/openresty/master/patches/openssl-1.1.1f-sess_set_get_cb_yield.patch" -o "$FFMPEG_MIRROR_ROOT/openssl-1.1.1f-sess_set_get_cb_yield.patch_tmp" then mv "$FFMPEG_MIRROR_ROOT/openssl-1.1.1f-sess_set_get_cb_yield.patch_tmp" "$FFMPEG_MIRROR_ROOT/openssl-1.1.1f-sess_set_get_cb_yield.patch" else Println "$error openssl patch 下载出错, 无法连接 github ?" fi fi if [ ! -e "$FFMPEG_MIRROR_ROOT/Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch" ] then if curl -s -L "https://raw.githubusercontent.com/woniuzfb/iptv/master/scripts/Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch" -o "$FFMPEG_MIRROR_ROOT/Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch_tmp" then mv "$FFMPEG_MIRROR_ROOT/Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch_tmp" "$FFMPEG_MIRROR_ROOT/Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch" else Println "$error Add-SVT-HEVC-support-for-RTMP-and-HLS-on-Nginx-HTTP-FLV.patch 下载出错, 无法连接 github ?" fi fi if curl -s -L "https://raw.githubusercontent.com/woniuzfb/iptv/master/scripts/Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch" -o "$FFMPEG_MIRROR_ROOT/Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch_tmp" then mv "$FFMPEG_MIRROR_ROOT/Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch_tmp" "$FFMPEG_MIRROR_ROOT/Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch" else Println "$error Add-SVT-HEVC-FLV-support-on-FFmpeg-git.patch 下载出错, 无法连接 github ?" fi if curl -s -L "https://raw.githubusercontent.com/woniuzfb/iptv/master/scripts/fix_ngx_lua_resp_get_headers_key_whitespace.patch" -o "$FFMPEG_MIRROR_ROOT/fix_ngx_lua_resp_get_headers_key_whitespace.patch_tmp" then mv "$FFMPEG_MIRROR_ROOT/fix_ngx_lua_resp_get_headers_key_whitespace.patch_tmp" "$FFMPEG_MIRROR_ROOT/fix_ngx_lua_resp_get_headers_key_whitespace.patch" else Println "$error fix_ngx_lua_resp_get_headers_key_whitespace.patch 下载出错, 无法连接 github ?" fi if [ ! -e "$FFMPEG_MIRROR_ROOT/fontforge-20190413.tar.gz" ] then if curl -s -L "https://github.com/fontforge/fontforge/releases/download/20190413/fontforge-20190413.tar.gz" -o "$FFMPEG_MIRROR_ROOT/fontforge-20190413.tar.gz_tmp" then mv "$FFMPEG_MIRROR_ROOT/fontforge-20190413.tar.gz_tmp" "$FFMPEG_MIRROR_ROOT/fontforge-20190413.tar.gz" else Println "$error fontforge 下载出错, 无法连接 github ?" fi fi if [ ! -e "$FFMPEG_MIRROR_ROOT/pdf2htmlEX-0.18.7-poppler-0.81.0.zip" ] then Println "$info 下载 pdf2htmlEX ..." if curl -s -L "https://github.com/pdf2htmlEX/pdf2htmlEX/archive/v0.18.7-poppler-0.81.0.zip" -o "$FFMPEG_MIRROR_ROOT/pdf2htmlEX-0.18.7-poppler-0.81.0.zip_tmp" then mv "$FFMPEG_MIRROR_ROOT/pdf2htmlEX-0.18.7-poppler-0.81.0.zip_tmp" "$FFMPEG_MIRROR_ROOT/pdf2htmlEX-0.18.7-poppler-0.81.0.zip" else Println "$error pdf2htmlEX 下载出错, 无法连接 github ?" fi fi if [ ! -e "$FFMPEG_MIRROR_ROOT/nbfc.zip" ] then Println "$info 下载 nbfc ..." if curl -s -L "https://github.com/hirschmann/nbfc/archive/master.zip" -o "$FFMPEG_MIRROR_ROOT/nbfc.zip_tmp" then mv "$FFMPEG_MIRROR_ROOT/nbfc.zip_tmp" "$FFMPEG_MIRROR_ROOT/nbfc.zip" else Println "$error nbfc 下载出错, 无法连接 github ?" fi fi Println "$info 下载 v2ray install-release.sh ..." if curl -s -L "$V2_LINK" -o "$FFMPEG_MIRROR_ROOT/v2ray_install-release.sh_tmp" then mv "$FFMPEG_MIRROR_ROOT/v2ray_install-release.sh_tmp" "$FFMPEG_MIRROR_ROOT/v2ray_install-release.sh" else Println "$error v2ray install-release.sh 下载出错, 无法连接 github ?" fi Println "$info 下载 acme.sh ..." if curl -s -L https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh -o "$FFMPEG_MIRROR_ROOT/acme.sh_tmp" then mv "$FFMPEG_MIRROR_ROOT/acme.sh_tmp" "$FFMPEG_MIRROR_ROOT/acme.sh" else Println "$error acme.sh 下载出错, 无法连接 github ?" fi locale_options=( en ) mkdir -p "$FFMPEG_MIRROR_ROOT/locale/po/" for locale in "${locale_options[@]}" do Println "$info 下载 $locale 语言文件 ..." if curl -s -L "https://raw.githubusercontent.com/woniuzfb/iptv/master/i18n/po/iptv.sh-$locale.mo" -o "$FFMPEG_MIRROR_ROOT/locale/po/iptv.sh-$locale.mo_tmp" then mv "$FFMPEG_MIRROR_ROOT/locale/po/iptv.sh-$locale.mo_tmp" "$FFMPEG_MIRROR_ROOT/locale/po/iptv.sh-$locale.mo" else Println "$error iptv.sh-$locale.mo 下载出错, 无法连接 github ?" fi done exit 0 ;; "ts") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 TsMenu exit 0 ;; "f"|"flv") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 kind="flv" color="$blue" shift ;; "v"|"vip") [ ! -d "$IPTV_ROOT" ] && Println "$error 尚未安装, 请检查 !\n" && exit 1 vip=true shift ;; "l"|"ll") shift GetChannels if [ "$chnls_count" -gt 0 ] then hls_indices=() hls_stream_links_list=() flv_list="" hls_list="" indices=() if [ -n "${1:-}" ] then for num in "${@}" do indices+=("$((num-1))") done else indices=("${!chnls_pid[@]}") fi for i in "${indices[@]}" do if [ "${chnls_flv_status[i]:-}" == "on" ] then IFS="${delimiters[0]}" read -ra chnl_stream_links <<< "${chnls_stream_link[i]}" chnl_stream_links_list="" for((j=0;j<${#chnl_stream_links[@]};j++)); do chnl_stream_links_list="$chnl_stream_links_list${indent_6}源$((j+1)): ${chnl_stream_links[j]}\n" done flv_list="$flv_list ${green}$((i+1)).${normal}${indent_6}${chnls_channel_name[i]}\n\n$chnl_stream_links_list\n${indent_6}拉: ${chnls_flv_pull_link[i]:-无}\n\n" elif [ "${chnls_status[i]:-}" == "on" ] then IFS="${delimiters[0]}" read -ra chnl_stream_links <<< "${chnls_stream_link[i]}" chnl_stream_links_list="" for((j=0;j<${#chnl_stream_links[@]};j++)); do chnl_stream_links_list="$chnl_stream_links_list${indent_6}源$((j+1)): ${chnl_stream_links[j]}\n" done hls_indices+=("$i") hls_stream_links_list+=("$chnl_stream_links_list") hls_list="$hls_list ${green}$((i+1)).${normal}${indent_6}${chnls_channel_name[i]}\n\n$chnl_stream_links_list\n\n" fi done fi echo if [ -n "${hls_indices:-}" ] then for((i=0;i<${#hls_indices[@]};i++)); do hls_index=${hls_indices[i]} echo -e " ${green}$((i+1)).${normal}${indent_6}${chnls_channel_name[hls_index]}\n\n${hls_stream_links_list[i]}" if [ -d "$LIVE_ROOT/${chnls_output_dir_name[hls_index]}" ] then if ls -A "$LIVE_ROOT/${chnls_output_dir_name[hls_index]}"/* > /dev/null 2>&1 then ls "$LIVE_ROOT/${chnls_output_dir_name[hls_index]}"/* -lght && echo else Println "$error 无\n" fi else Println "$error 目录不存在\n" fi done fi if [ -n "${flv_list:-}" ] then Println "${green}FLV 频道${normal}\n\n$flv_list" fi if [ -n "${hls_list:-}" ] then Println "${green}HLS 频道${normal}\n\n$hls_list" fi if [ -z "${1:-}" ] && ls -A $LIVE_ROOT/* > /dev/null 2>&1 then for output_dir_root in "$LIVE_ROOT"/* do output_dir_name=${output_dir_root#*$LIVE_ROOT/} if [ -n "${hls_indices:-}" ] then for hls_index in "${hls_indices[@]}" do if [ "$output_dir_name" == "${chnls_output_dir_name[hls_index]}" ] then continue 2 fi done fi Println "$error 未知目录 $output_dir_name\n" if ls -A "$output_dir_root"/* > /dev/null 2>&1 then ls "$output_dir_root"/* -lght fi done fi if [ -z "${flv_list:-}" ] && [ -z "${hls_list:-}" ] then Println "$error 没有开启的频道 !\n" exit 1 fi exit 0 ;; "debug") sed -i "0,/sh_debug=.*/s//sh_debug=${2:-1}/" "$SH_FILE" exit 0 ;; "ed"|"editor") DepInstall vim if [ "$dist" == "rpm" ] then alternatives --config editor else update-alternatives --config editor fi exit 0 ;; "a") if [[ ! -x $(command -v readlink) ]] then Println "$error 系统不支持 readlink\n" exit 1 fi echo ExitOnText "输入自定义命令名称" name if command -v "$name" > /dev/null then Println "$error 命令已经存在\n" exit 1 fi echo alternative_options=( nginx openresty xray v2ray armbian "proxmox ve" "ibm cloud foundry" "cloudflare partner,workers" ffmpeg ) inquirer list_input_index "选择执行的脚本" alternative_options alternative_options_index commands=( NX_FILE OR_FILE X_FILE V2_FILE ARM_FILE PVE_FILE IBM_FILE CF_FILE SH_FILE ) ln -s ${!commands[alternative_options_index]} /usr/bin/$name Println "$info 自定义命令 $name 添加成功\n" exit 0 ;; "c") to_locale=${2:-} new_locale="" if [ -n "$to_locale" ] then new_locale=${to_locale%.*} if [[ $new_locale =~ ^(.+)[-|_](.+)$ ]] then new_locale=$(tr '[:upper:]' '[:lower:]' <<< "${BASH_REMATCH[1]}")_$(tr '[:lower:]' '[:upper:]' <<< "${BASH_REMATCH[2]}") else new_locale=$(tr '[:upper:]' '[:lower:]' <<< "$new_locale") fi if [[ $to_locale =~ \. ]] then new_locale="$new_locale.${to_locale#*.}" fi fi i18nInstall "$new_locale" exit 0 ;; "color") echo color_options=( '设置颜色' '恢复默认' ) inquirer list_input_index "选择操作" color_options color_options_index if [ "$color_options_index" -eq 1 ] then sed -E -i '/(bg_black|red|green|yellow|blue|cyan|white)=/d' "$i18n_FILE" Println "$info 颜色重置成功\n" exit 0 fi echo color_options=( "${red}默认红色文字${normal}" "${green}默认绿色文字${normal}" "${yellow}默认黄色文字${normal}" "${blue}默认蓝色文字${normal}" "${cyan}默认青色文字${normal}" "${white}默认白色文字${normal}" '默认黑色背景' ) inquirer list_input_index "选择修改内容" color_options color_options_index echo inquirer color_pick "设置 ${color_options[color_options_index]}" color_pick if [ "$color_options_index" -eq 0 ] then sed -i '/red=/d' "$i18n_FILE" printf "red='%s'" "$color_pick" >> "$i18n_FILE" elif [ "$color_options_index" -eq 1 ] then sed -i '/green=/d' "$i18n_FILE" printf "green='%s'" "$color_pick" >> "$i18n_FILE" elif [ "$color_options_index" -eq 2 ] then sed -i '/yellow=/d' "$i18n_FILE" printf "yellow='%s'" "$color_pick" >> "$i18n_FILE" elif [ "$color_options_index" -eq 3 ] then sed -i '/blue=/d' "$i18n_FILE" printf "blue='%s'" "$color_pick" >> "$i18n_FILE" elif [ "$color_options_index" -eq 4 ] then sed -i '/cyan=/d' "$i18n_FILE" printf "cyan='%s'" "$color_pick" >> "$i18n_FILE" elif [ "$color_options_index" -eq 5 ] then sed -i '/white=/d' "$i18n_FILE" printf "white='%s'" "$color_pick" >> "$i18n_FILE" else sed -i '/bg_black=/d' "$i18n_FILE" printf "bg_black='%s'" "$color_pick" >> "$i18n_FILE" fi Println "$info 颜色设置成功\n" exit 0 ;; curl) echo curl_options=( '安装/设置 curl impersonate' '编译 curl impersonate' '编译 node-libcurl (使用 curl impersonate)' ) inquirer list_input_index "选择操作" curl_options curl_options_index if [ "$curl_options_index" -eq 0 ] then CurlImpersonateUpdate echo elif [ "$curl_options_index" -eq 1 ] then CurlImpersonateCompile else NodeLibcurlImpersonateCompile fi exit 0 ;; b|backup) # --exclude=exclude_before --include=src/ --include=src/xxx/*** --exclude=src/*** --exclude=exclude_after IPTV_ROOT="${IPTV_ROOT%/}" FFMPEG_MIRROR_ROOT="${FFMPEG_MIRROR_ROOT%/}" LIVE_ROOT="${LIVE_ROOT%/}" NODE_ROOT="${NODE_ROOT%/}" iptv_source="$IPTV_ROOT" iptv_excludes_before=( /c/ /ffmpeg-git-'*' /vip/ node_modules/ ) ffmpeg_excludes_before=( /Amlogic_s905-kernel-master.zip /builds/ /calibre/ /dnscrypt/ /ffmpeg_c /fontforge-20190413.tar.gz /imgcat.zip /jq-'*' /releases/ /v2ray/ /xray/ '*'.ipk '*'.err '*'.log '*'.pid ) if [[ "$FFMPEG_MIRROR_ROOT" =~ "$iptv_source"(.*) ]] then relative_path="${BASH_REMATCH[1]:-}" relative_path="${relative_path#/}" if [ -n "$relative_path" ] then for i in "${!ffmpeg_excludes_before[@]}" do if [[ "${ffmpeg_excludes_before[i]}" == /* ]] then ffmpeg_excludes_before[i]="/${relative_path}${ffmpeg_excludes_before[i]}" else ffmpeg_excludes_before[i]="/$relative_path/${ffmpeg_excludes_before[i]}" fi done fi iptv_excludes_before+=("${ffmpeg_excludes_before[@]}") else ffmpeg_source="$FFMPEG_MIRROR_ROOT" fi if [[ "$LIVE_ROOT" =~ "$IPTV_ROOT"(.+) ]] then iptv_excludes_before+=("${BASH_REMATCH[1]}") fi node_excludes_before=( node_modules/ ) if ! [[ "$NODE_ROOT" =~ "$IPTV_ROOT"(.*) ]] then node_source="$NODE_ROOT" fi nginx_source=/usr/local/nginx nginx_excludes_before=( /logs/'*'.gz node_modules/ ) nginx_includes=( /conf/'***' /html/'***' /logs/'***' ) nginx_excludes_after=( /'***' ) openresty_source=/usr/local/openresty openresty_excludes_before=( /nginx/logs/'*'.gz node_modules/ ) openresty_includes=( /nginx/ /nginx/conf/'***' /nginx/html/'***' /nginx/logs/'***' /nginx/lua/'***' ) openresty_excludes_after=( /'***' ) v2ray_source="${V2_CONFIG%/*}" xray_source="${X_CONFIG%/*}" if [ "$dist" == "mac" ] then systemd_source="$HOME"/Library/LaunchAgents else systemd_source=/etc/systemd/system fi systemd_includes=( /alist.service /aria2.service /dnscrypt-proxy.service /mmproxy-'*'.service /nginx.service /openresty.service /v2ray.service /xray.service /aios.'*'.service /com.aios.'*'.plist ) systemd_excludes_after=( /'***' ) home_source="$HOME" home_includes=( /.bash_history ) home_excludes_after=( /'***' ) DepInstall rsync mkdir -p "$BACKUP_ROOT" for backup in iptv nginx openresty v2ray xray ffmpeg node systemd home do rsync_commands=() source="${backup}_source" if [[ -z "${!source:-}" ]] then continue fi source="${!source}" if [ ! -d "$source" ] then continue fi excludes_before=("${backup}_excludes_before"[@]) if [[ -n "${!excludes_before:-}" ]] then excludes_before=("${!excludes_before}") for exclude in "${excludes_before[@]}" do rsync_commands+=( --exclude="$exclude" ) done fi includes=("${backup}_includes"[@]) if [[ -n "${!includes:-}" ]] then includes_arr=("${!includes}") for include in "${includes_arr[@]}" do rsync_commands+=( --include="$include" ) done fi excludes_after=("${backup}_excludes_after"[@]) if [[ -n "${!excludes_after:-}" ]] then excludes_after=("${!excludes_after}") for exclude in "${excludes_after[@]}" do rsync_commands+=( --exclude="$exclude" ) done fi rsync -avP --safe-links --delete --delete-excluded ${rsync_commands[@]+"${rsync_commands[@]}"} "$source/" "$BACKUP_ROOT/$backup/" done Println "$info 已创建备份 $BACKUP_ROOT\n" exit 0 ;; *) ;; esac fi if [ -z "$*" ] then ShFileCheck if [ "${vip:-false}" = true ] then VipMenu else Menu fi else while getopts "i:l:P:o:p:S:t:s:c:v:a:f:d:q:b:r:k:K:m:n:z:H:T:L:CReh" flag do case "$flag" in i) stream_link="$OPTARG";; l) live=false;; P) proxy="$OPTARG";; o) output_dir_name="$OPTARG";; p) playlist_name="$OPTARG";; S) seg_dir_name="$OPTARG";; t) seg_name="$OPTARG";; s) seg_length="$OPTARG";; c) seg_count="$OPTARG";; v) video_codec="$OPTARG";; a) audio_codec="$OPTARG";; f) video_audio_shift="$OPTARG";; d) txt_format="$OPTARG";; q) quality="$OPTARG";; b) bitrate="$OPTARG";; r) resolution="$OPTARG";; C) const=true;; R) const_cbr=true;; e) encrypt=true;; k) kind="$OPTARG";; K) key_name="$OPTARG";; m) input_flags="$OPTARG";; n) output_flags="$OPTARG";; z) channel_name="$OPTARG";; H) flv_h265=true;; T) flv_push_link="$OPTARG";; L) flv_pull_link="$OPTARG";; h|*) Usage; esac done if [ -z "${stream_link:-}" ] then Usage fi if [ ! -d "$IPTV_ROOT" ] then echo ExitOnList n "`gettext \"尚未安装, 是否现在安装\"`" Install else FFMPEG_ROOT=$(dirname "$IPTV_ROOT"/ffmpeg-git-*/ffmpeg) FFMPEG="$FFMPEG_ROOT/ffmpeg" FFPROBE="$FFMPEG_ROOT/ffprobe" GetDefault stream_links=("$stream_link") stream_link="${stream_links[0]}" if [ -z "${proxy:-}" ] then if [[ $stream_link =~ ^https?:// ]] && [ -n "$d_proxy" ] then echo inquirer list_input "`eval_gettext \"是否使用代理 \\\$d_proxy: \"`" yn_options use_proxy_yn if [ "$use_proxy_yn" == "$i18n_yes" ] then proxy="$d_proxy" else proxy="" fi else proxy="" fi fi if [ -z "${xc_proxy:-}" ] then if [ -n "$d_xc_proxy" ] then if [[ $stream_link =~ ^http://([^/]+) ]] then XtreamCodesGetDomains xc_proxy="" for xc_domain in "${xtream_codes_domains[@]}" do if [ "$xc_domain" == "${BASH_REMATCH[1]}" ] then echo inquirer list_input "`eval_gettext \"是否使用 xtream codes 代理 \\\$d_xc_proxy: \"`" yn_options use_proxy_yn if [ "$use_proxy_yn" == "$i18n_yes" ] then xc_proxy="$d_xc_proxy" else xc_proxy="" fi break fi done else xc_proxy="" fi else xc_proxy="" fi fi user_agent="$d_user_agent" headers="$d_headers" while [[ $headers =~ \\\\ ]] do headers=${headers//\\\\/\\} done if [ -n "$headers" ] && [[ ! $headers =~ \\r\\n$ ]] then headers="$headers\r\n" fi cookies="$d_cookies" output_dir_name="${output_dir_name:-$(RandOutputDirName)}" output_dir_root="$LIVE_ROOT/$output_dir_name" playlist_name="${playlist_name:-$(RandPlaylistName)}" seg_dir_name="${seg_dir_name:-$d_seg_dir_name}" if [ -n "$seg_dir_name" ] then seg_dir_path="$seg_dir_name/" else seg_dir_path="" fi seg_name="${seg_name:-$playlist_name}" seg_length="${seg_length:-$d_seg_length}" seg_count="${seg_count:-$d_seg_count}" audio_codec="${audio_codec:-$d_audio_codec}" video_codec="${video_codec:-$d_video_codec}" use_primary_playlist=false hboasia_host="hbogoasia.com:8443" const=${const:-$d_const} const_cbr=${const_cbr:-$d_const_cbr} live=${live:-true} video_audio_shift="${video_audio_shift:-}" v_or_a="${video_audio_shift%_*}" if [ "$v_or_a" == "v" ] then video_shift="${video_audio_shift#*_}" elif [ "$v_or_a" == "a" ] then audio_shift="${video_audio_shift#*_}" fi txt_format="${txt_format:-$d_txt_format}" draw_text="${draw_text:-$d_draw_text}" quality="${quality:-$d_quality}" bitrate="${bitrate:-$d_bitrate}" resolution="${resolution:-$d_resolution}" if [ -n "$txt_format" ] then subtitle_append=',SUBTITLES="subs"' else subtitle_append="" fi master=0 if [[ $bitrate =~ , ]] || [[ $quality =~ , ]] || [[ $resolution =~ , ]] || [ -n "$subtitle_append" ] then master=1 fi if [ -z "${encrypt:-}" ] then if [ "$d_encrypt" = true ] then encrypt=true encrypt_session="$d_encrypt_session" else encrypt=false encrypt_session=false fi else encrypt=true encrypt_session="$d_encrypt_session" fi keyinfo_name="${keyinfo_name:-$d_keyinfo_name}" keyinfo_name="${keyinfo_name:-$(RandStr)}" key_name="${key_name:-$d_key_name}" key_name="${key_name:-$(RandStr)}" if [ "${stream_link:0:4}" == "rtmp" ] || [ "${stream_link:0:1}" == "/" ] then d_input_flags="${d_input_flags//-timeout 2000000000/}" d_input_flags="${d_input_flags//-reconnect 1/}" d_input_flags="${d_input_flags//-reconnect_at_eof 1/}" d_input_flags="${d_input_flags//-reconnect_streamed 1/}" d_input_flags="${d_input_flags//-reconnect_delay_max 2000/}" lead="${d_input_flags%%[^[:blank:]]*}" d_input_flags="${d_input_flags#${lead}}" elif [[ $stream_link == *".m3u8"* ]] then d_input_flags="${d_input_flags//-reconnect_at_eof 1/}" fi input_flags="${input_flags:-$d_input_flags}" if [[ ${input_flags:0:1} == "'" ]] then input_flags="${input_flags%\'}" input_flags="${input_flags#\'}" fi if [ "${output_flags:-}" == "omit" ] then output_flags="" else output_flags="${output_flags:-$d_output_flags}" fi if [[ ${output_flags:0:1} == "'" ]] then output_flags="${output_flags%\'}" output_flags="${output_flags#\'}" fi channel_name="${channel_name:-$playlist_name}" sync="$d_sync" sync_file="${sync_file:-}" sync_index="${sync_index:-}" sync_pairs="${sync_pairs:-}" hls_end_list="${d_hls_end_list:-false}" [ ! -e $FFMPEG_LOG_ROOT ] && mkdir $FFMPEG_LOG_ROOT flv_h265="${flv_h265:-false}" flv_push_link="${flv_push_link:-}" flv_pull_link="${flv_pull_link:-}" extra_filters="" if [ "$video_codec" != "copy" ] && [ -n "$draw_text" ] then filters=( vf filter:v ) for filter in "${filters[@]}" do if [[ $output_flags =~ (.*)"-$filter "([^ ]+)(.*) ]] then extra_filters="${BASH_REMATCH[2]}," output_flags="${BASH_REMATCH[1]} ${BASH_REMATCH[3]}" fi done fi flags_command=( -flags ) if [[ $output_flags =~ (.*)"-flags "([^ ]+)(.*) ]] then flags="${BASH_REMATCH[2]}" if [[ $flags =~ global_header ]] then flags_command=( -flags "$flags" ) else flags_command+=("-global_header$flags") fi output_flags="${BASH_REMATCH[1]} ${BASH_REMATCH[3]}" else flags_command+=(-global_header) fi FilterString input_flags output_flags from="AddChannel" if [ -n "${kind:-}" ] then if [ "$kind" == "flv" ] then if [ -z "${flv_push_link:-}" ] then Println "`eval_gettext \"\\\$error 未设置推流地址...\"`\n" && exit 1 else if [ "$sh_debug" -eq 1 ] then ( FlvStreamCreator ) else ( FlvStreamCreator ) > /dev/null 2> /dev/null < /dev/null & fi fi else Println "`eval_gettext \"\\\$error 暂不支持输出 \$kind ...\"`\n" && exit 1 fi else if [ "$sh_debug" -eq 1 ] then ( HlsStreamCreatorPlus ) else ( HlsStreamCreatorPlus ) > /dev/null 2> /dev/null < /dev/null & fi fi Println "`eval_gettext \"\\\$info 添加频道成功\"`\n" fi fi