mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 17:56:07 +00:00
v2.2.1: unified menu with bot management, grouped sections, telemt download fix, QR cleanup, version bump
This commit is contained in:
@@ -1,282 +1,282 @@
|
||||
#!/bin/bash
|
||||
# GoTelegram v2.2 — Генерация TOML конфигурации для telemt
|
||||
|
||||
# ── Популярные домены (не заблокированные в РФ) ──────────────────────────────
|
||||
QUICK_DOMAINS=(
|
||||
"google.com"
|
||||
"microsoft.com"
|
||||
"cloudflare.com"
|
||||
"apple.com"
|
||||
"amazon.com"
|
||||
"github.com"
|
||||
"stackoverflow.com"
|
||||
"medium.com"
|
||||
"wikipedia.org"
|
||||
"coursera.org"
|
||||
"udemy.com"
|
||||
"habr.com"
|
||||
"stepik.org"
|
||||
"duolingo.com"
|
||||
"khanacademy.org"
|
||||
"bbc.com"
|
||||
"reuters.com"
|
||||
"nytimes.com"
|
||||
"ted.com"
|
||||
"zoom.us"
|
||||
)
|
||||
|
||||
# ── Генерация TOML конфига ───────────────────────────────────────────────────
|
||||
generate_telemt_toml() {
|
||||
local secret="$1"
|
||||
local port="${2:-443}"
|
||||
local mask_mode="${3:-quick}" # quick | stealth
|
||||
local mask_host="${4:-google.com}"
|
||||
local mask_port="${5:-443}"
|
||||
local output="${6:-$TELEMT_CONFIG}"
|
||||
|
||||
mkdir -p "$(dirname "$output")"
|
||||
|
||||
cat > "$output" << EOTOML
|
||||
# GoTelegram v${GOTELEGRAM_VERSION} — telemt configuration
|
||||
# Сгенерировано: $(date -Iseconds)
|
||||
# Режим: ${mask_mode}
|
||||
|
||||
# ── Основные настройки ───────────────────────────────────────────────────────
|
||||
[stats]
|
||||
statsd_address = ""
|
||||
|
||||
# ── Секреты ──────────────────────────────────────────────────────────────────
|
||||
[[users]]
|
||||
name = "main"
|
||||
secret = "${secret}"
|
||||
|
||||
# ── Привязка ─────────────────────────────────────────────────────────────────
|
||||
[listen]
|
||||
bind_to = "0.0.0.0:${port}"
|
||||
|
||||
# ── TLS маскировка ───────────────────────────────────────────────────────────
|
||||
[security]
|
||||
# Маскировочный хост — куда перенаправлять неопознанные подключения
|
||||
# quick: внешний сайт | stealth: локальный nginx
|
||||
host = "${mask_host}:${mask_port}"
|
||||
|
||||
EOTOML
|
||||
|
||||
chmod 600 "$output"
|
||||
log_success "Конфиг telemt записан: $output"
|
||||
log_dim "Режим: $mask_mode, маскировка: $mask_host:$mask_port"
|
||||
}
|
||||
|
||||
# ── Добавление дополнительного секрета ───────────────────────────────────────
|
||||
add_secret_to_config() {
|
||||
local name="$1"
|
||||
local secret="$2"
|
||||
local config="${3:-$TELEMT_CONFIG}"
|
||||
|
||||
if [ ! -f "$config" ]; then
|
||||
log_error "Конфиг не найден: $config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Добавляем новый блок [[users]]
|
||||
cat >> "$config" << EOSECRET
|
||||
|
||||
[[users]]
|
||||
name = "${name}"
|
||||
secret = "${secret}"
|
||||
EOSECRET
|
||||
|
||||
log_success "Добавлен секрет: $name"
|
||||
}
|
||||
|
||||
# ── Чтение текущего конфига ──────────────────────────────────────────────────
|
||||
get_config_value() {
|
||||
local key="$1"
|
||||
local config="${2:-$TELEMT_CONFIG}"
|
||||
|
||||
if [ ! -f "$config" ]; then return 1; fi
|
||||
|
||||
case "$key" in
|
||||
secret)
|
||||
grep -m1 'secret\s*=' "$config" | sed 's/.*=\s*"\(.*\)"/\1/' | tr -d ' '
|
||||
;;
|
||||
port)
|
||||
grep 'bind_to\s*=' "$config" | sed 's/.*:\([0-9]*\)".*/\1/'
|
||||
;;
|
||||
mask_host)
|
||||
grep -A10 '\[security\]' "$config" | grep 'host\s*=' | sed 's/.*=\s*"\(.*\)".*/\1/'
|
||||
;;
|
||||
*)
|
||||
grep "$key" "$config" | head -1 | sed 's/.*=\s*"\?\(.*\)"\?/\1/' | tr -d ' "'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Валидация конфига ────────────────────────────────────────────────────────
|
||||
validate_telemt_config() {
|
||||
local config="${1:-$TELEMT_CONFIG}"
|
||||
|
||||
if [ ! -f "$config" ]; then
|
||||
log_error "Конфиг не найден: $config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Проверяем обязательные поля
|
||||
local secret port host
|
||||
secret=$(get_config_value secret "$config")
|
||||
port=$(get_config_value port "$config")
|
||||
host=$(get_config_value mask_host "$config")
|
||||
|
||||
local errors=0
|
||||
|
||||
if [ -z "$secret" ]; then
|
||||
log_error "Не задан secret"
|
||||
((errors++))
|
||||
elif [ ${#secret} -lt 32 ]; then
|
||||
log_warning "Secret слишком короткий (${#secret} символов, рекомендуется 32+)"
|
||||
fi
|
||||
|
||||
if [ -z "$port" ]; then
|
||||
log_error "Не задан порт (bind_to)"
|
||||
((errors++))
|
||||
elif [ "$port" -lt 1 ] || [ "$port" -gt 65535 ] 2>/dev/null; then
|
||||
log_error "Порт вне диапазона: $port"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
if [ -z "$host" ]; then
|
||||
log_error "Не задан маскировочный хост (security.host)"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
if [ $errors -gt 0 ]; then
|
||||
log_error "Найдено ошибок: $errors"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Конфиг валиден"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Выбор домена (интерактивный) ─────────────────────────────────────────────
|
||||
select_quick_domain() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${WHITE}🌐 Выберите домен для маскировки (Fake TLS):${NC}"
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
|
||||
local i=1
|
||||
local row=""
|
||||
for d in "${QUICK_DOMAINS[@]}"; do
|
||||
printf " ${CYAN}%2d)${NC} %-25s" "$i" "$d"
|
||||
if (( i % 2 == 0 )); then
|
||||
echo ""
|
||||
fi
|
||||
((i++))
|
||||
done
|
||||
if (( (i-1) % 2 != 0 )); then echo ""; fi
|
||||
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
echo -ne " ${WHITE}Выбор (1-${#QUICK_DOMAINS[@]}):${NC} "
|
||||
read -r choice
|
||||
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#QUICK_DOMAINS[@]} ]; then
|
||||
echo "${QUICK_DOMAINS[$((choice-1))]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "Неверный выбор"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── Выбор порта (интерактивный) ──────────────────────────────────────────────
|
||||
select_port() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${WHITE}🔌 Выберите порт:${NC}"
|
||||
|
||||
# Проверяем стандартные порты
|
||||
local busy_443 busy_8443
|
||||
busy_443=$(check_port 443)
|
||||
busy_8443=$(check_port 8443)
|
||||
|
||||
local label_443="443 (рекомендуется)"
|
||||
local label_8443="8443"
|
||||
[ -n "$busy_443" ] && label_443="443 ⚠️ занят"
|
||||
[ -n "$busy_8443" ] && label_8443="8443 ⚠️ занят"
|
||||
|
||||
echo -e " ${CYAN}1)${NC} $label_443"
|
||||
echo -e " ${CYAN}2)${NC} $label_8443"
|
||||
echo -e " ${CYAN}3)${NC} Свой порт"
|
||||
|
||||
if [ -n "$busy_443" ]; then
|
||||
echo -e " ${DIM} ⚠ Порт 443 занят: $(echo "$busy_443" | head -c 60)${NC}"
|
||||
fi
|
||||
|
||||
echo -ne " ${WHITE}Выбор:${NC} "
|
||||
read -r choice
|
||||
|
||||
case "$choice" in
|
||||
1) echo "443" ;;
|
||||
2) echo "8443" ;;
|
||||
3)
|
||||
echo -ne " Введите порт (1-65535): "
|
||||
read -r custom_port
|
||||
if [[ "$custom_port" =~ ^[0-9]+$ ]] && [ "$custom_port" -ge 1 ] && [ "$custom_port" -le 65535 ]; then
|
||||
echo "$custom_port"
|
||||
else
|
||||
log_error "Неверный порт"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*) echo "443" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Генерация ссылки tg://proxy ──────────────────────────────────────────────
|
||||
generate_proxy_link() {
|
||||
local ip="${1:-$(get_server_ip)}"
|
||||
local port="${2:-443}"
|
||||
local secret="$3"
|
||||
echo "tg://proxy?server=${ip}&port=${port}&secret=${secret}"
|
||||
}
|
||||
|
||||
# ── Вывод информации о прокси ────────────────────────────────────────────────
|
||||
show_proxy_info() {
|
||||
local config="${1:-$TELEMT_CONFIG}"
|
||||
local secret port mask_host ip link status
|
||||
|
||||
secret=$(get_config_value secret "$config")
|
||||
port=$(get_config_value port "$config")
|
||||
mask_host=$(get_config_value mask_host "$config")
|
||||
ip=$(get_server_ip)
|
||||
link=$(generate_proxy_link "$ip" "$port" "$secret")
|
||||
status=$(telemt_status)
|
||||
|
||||
local mode
|
||||
mode=$(config_get mode 2>/dev/null || echo "quick")
|
||||
|
||||
local status_icon status_text
|
||||
case "$status" in
|
||||
running) status_icon="✅"; status_text="Работает" ;;
|
||||
stopped) status_icon="⏸️"; status_text="Остановлен" ;;
|
||||
*) status_icon="❌"; status_text="Не установлен" ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo -e " ${BOLD}${WHITE}${status_icon} Статус прокси: ${status_text}${NC}"
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
echo -e " ${WHITE}Ядро:${NC} telemt (Rust)"
|
||||
echo -e " ${WHITE}IP:${NC} ${CYAN}${ip}${NC}"
|
||||
echo -e " ${WHITE}Порт:${NC} ${CYAN}${port}${NC}"
|
||||
echo -e " ${WHITE}Режим:${NC} ${CYAN}${mode}${NC}"
|
||||
echo -e " ${WHITE}Маскировка:${NC} ${CYAN}${mask_host}${NC}"
|
||||
echo -e " ${WHITE}Secret:${NC} ${CYAN}${secret:0:16}...${NC}"
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
echo -e " ${WHITE}Ссылка:${NC}"
|
||||
echo -e " ${GREEN}${link}${NC}"
|
||||
echo ""
|
||||
|
||||
# QR если доступен
|
||||
if command -v qrencode &>/dev/null; then
|
||||
qrencode -t UTF8 -m 2 "$link" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
#!/bin/bash
|
||||
# GoTelegram v2.2 — Генерация TOML конфигурации для telemt
|
||||
|
||||
# ── Популярные домены (не заблокированные в РФ) ──────────────────────────────
|
||||
QUICK_DOMAINS=(
|
||||
"google.com"
|
||||
"microsoft.com"
|
||||
"cloudflare.com"
|
||||
"apple.com"
|
||||
"amazon.com"
|
||||
"github.com"
|
||||
"stackoverflow.com"
|
||||
"medium.com"
|
||||
"wikipedia.org"
|
||||
"coursera.org"
|
||||
"udemy.com"
|
||||
"habr.com"
|
||||
"stepik.org"
|
||||
"duolingo.com"
|
||||
"khanacademy.org"
|
||||
"bbc.com"
|
||||
"reuters.com"
|
||||
"nytimes.com"
|
||||
"ted.com"
|
||||
"zoom.us"
|
||||
)
|
||||
|
||||
# ── Генерация TOML конфига ───────────────────────────────────────────────────
|
||||
generate_telemt_toml() {
|
||||
local secret="$1"
|
||||
local port="${2:-443}"
|
||||
local mask_mode="${3:-quick}" # quick | stealth
|
||||
local mask_host="${4:-google.com}"
|
||||
local mask_port="${5:-443}"
|
||||
local output="${6:-$TELEMT_CONFIG}"
|
||||
|
||||
mkdir -p "$(dirname "$output")"
|
||||
|
||||
cat > "$output" << EOTOML
|
||||
# GoTelegram v${GOTELEGRAM_VERSION} — telemt configuration
|
||||
# Сгенерировано: $(date -Iseconds)
|
||||
# Режим: ${mask_mode}
|
||||
|
||||
# ── Основные настройки ───────────────────────────────────────────────────────
|
||||
[stats]
|
||||
statsd_address = ""
|
||||
|
||||
# ── Секреты ──────────────────────────────────────────────────────────────────
|
||||
[[users]]
|
||||
name = "main"
|
||||
secret = "${secret}"
|
||||
|
||||
# ── Привязка ─────────────────────────────────────────────────────────────────
|
||||
[listen]
|
||||
bind_to = "0.0.0.0:${port}"
|
||||
|
||||
# ── TLS маскировка ───────────────────────────────────────────────────────────
|
||||
[security]
|
||||
# Маскировочный хост — куда перенаправлять неопознанные подключения
|
||||
# quick: внешний сайт | stealth: локальный nginx
|
||||
host = "${mask_host}:${mask_port}"
|
||||
|
||||
EOTOML
|
||||
|
||||
chmod 600 "$output"
|
||||
log_success "Конфиг telemt записан: $output"
|
||||
log_dim "Режим: $mask_mode, маскировка: $mask_host:$mask_port"
|
||||
}
|
||||
|
||||
# ── Добавление дополнительного секрета ───────────────────────────────────────
|
||||
add_secret_to_config() {
|
||||
local name="$1"
|
||||
local secret="$2"
|
||||
local config="${3:-$TELEMT_CONFIG}"
|
||||
|
||||
if [ ! -f "$config" ]; then
|
||||
log_error "Конфиг не найден: $config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Добавляем новый блок [[users]]
|
||||
cat >> "$config" << EOSECRET
|
||||
|
||||
[[users]]
|
||||
name = "${name}"
|
||||
secret = "${secret}"
|
||||
EOSECRET
|
||||
|
||||
log_success "Добавлен секрет: $name"
|
||||
}
|
||||
|
||||
# ── Чтение текущего конфига ──────────────────────────────────────────────────
|
||||
get_config_value() {
|
||||
local key="$1"
|
||||
local config="${2:-$TELEMT_CONFIG}"
|
||||
|
||||
if [ ! -f "$config" ]; then return 1; fi
|
||||
|
||||
case "$key" in
|
||||
secret)
|
||||
grep -m1 'secret\s*=' "$config" | sed 's/.*=\s*"\(.*\)"/\1/' | tr -d ' '
|
||||
;;
|
||||
port)
|
||||
grep 'bind_to\s*=' "$config" | sed 's/.*:\([0-9]*\)".*/\1/'
|
||||
;;
|
||||
mask_host)
|
||||
grep -A10 '\[security\]' "$config" | grep 'host\s*=' | sed 's/.*=\s*"\(.*\)".*/\1/'
|
||||
;;
|
||||
*)
|
||||
grep "$key" "$config" | head -1 | sed 's/.*=\s*"\?\(.*\)"\?/\1/' | tr -d ' "'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Валидация конфига ────────────────────────────────────────────────────────
|
||||
validate_telemt_config() {
|
||||
local config="${1:-$TELEMT_CONFIG}"
|
||||
|
||||
if [ ! -f "$config" ]; then
|
||||
log_error "Конфиг не найден: $config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Проверяем обязательные поля
|
||||
local secret port host
|
||||
secret=$(get_config_value secret "$config")
|
||||
port=$(get_config_value port "$config")
|
||||
host=$(get_config_value mask_host "$config")
|
||||
|
||||
local errors=0
|
||||
|
||||
if [ -z "$secret" ]; then
|
||||
log_error "Не задан secret"
|
||||
((errors++))
|
||||
elif [ ${#secret} -lt 32 ]; then
|
||||
log_warning "Secret слишком короткий (${#secret} символов, рекомендуется 32+)"
|
||||
fi
|
||||
|
||||
if [ -z "$port" ]; then
|
||||
log_error "Не задан порт (bind_to)"
|
||||
((errors++))
|
||||
elif [ "$port" -lt 1 ] || [ "$port" -gt 65535 ] 2>/dev/null; then
|
||||
log_error "Порт вне диапазона: $port"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
if [ -z "$host" ]; then
|
||||
log_error "Не задан маскировочный хост (security.host)"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
if [ $errors -gt 0 ]; then
|
||||
log_error "Найдено ошибок: $errors"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Конфиг валиден"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Выбор домена (интерактивный) ─────────────────────────────────────────────
|
||||
select_quick_domain() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${WHITE}🌐 Выберите домен для маскировки (Fake TLS):${NC}"
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
|
||||
local i=1
|
||||
local row=""
|
||||
for d in "${QUICK_DOMAINS[@]}"; do
|
||||
printf " ${CYAN}%2d)${NC} %-25s" "$i" "$d"
|
||||
if (( i % 2 == 0 )); then
|
||||
echo ""
|
||||
fi
|
||||
((i++))
|
||||
done
|
||||
if (( (i-1) % 2 != 0 )); then echo ""; fi
|
||||
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
echo -ne " ${WHITE}Выбор (1-${#QUICK_DOMAINS[@]}):${NC} "
|
||||
read -r choice
|
||||
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#QUICK_DOMAINS[@]} ]; then
|
||||
echo "${QUICK_DOMAINS[$((choice-1))]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "Неверный выбор"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── Выбор порта (интерактивный) ──────────────────────────────────────────────
|
||||
select_port() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${WHITE}🔌 Выберите порт:${NC}"
|
||||
|
||||
# Проверяем стандартные порты
|
||||
local busy_443 busy_8443
|
||||
busy_443=$(check_port 443)
|
||||
busy_8443=$(check_port 8443)
|
||||
|
||||
local label_443="443 (рекомендуется)"
|
||||
local label_8443="8443"
|
||||
[ -n "$busy_443" ] && label_443="443 ⚠️ занят"
|
||||
[ -n "$busy_8443" ] && label_8443="8443 ⚠️ занят"
|
||||
|
||||
echo -e " ${CYAN}1)${NC} $label_443"
|
||||
echo -e " ${CYAN}2)${NC} $label_8443"
|
||||
echo -e " ${CYAN}3)${NC} Свой порт"
|
||||
|
||||
if [ -n "$busy_443" ]; then
|
||||
echo -e " ${DIM} ⚠ Порт 443 занят: $(echo "$busy_443" | head -c 60)${NC}"
|
||||
fi
|
||||
|
||||
echo -ne " ${WHITE}Выбор:${NC} "
|
||||
read -r choice
|
||||
|
||||
case "$choice" in
|
||||
1) echo "443" ;;
|
||||
2) echo "8443" ;;
|
||||
3)
|
||||
echo -ne " Введите порт (1-65535): "
|
||||
read -r custom_port
|
||||
if [[ "$custom_port" =~ ^[0-9]+$ ]] && [ "$custom_port" -ge 1 ] && [ "$custom_port" -le 65535 ]; then
|
||||
echo "$custom_port"
|
||||
else
|
||||
log_error "Неверный порт"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*) echo "443" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Генерация ссылки tg://proxy ──────────────────────────────────────────────
|
||||
generate_proxy_link() {
|
||||
local ip="${1:-$(get_server_ip)}"
|
||||
local port="${2:-443}"
|
||||
local secret="$3"
|
||||
echo "tg://proxy?server=${ip}&port=${port}&secret=${secret}"
|
||||
}
|
||||
|
||||
# ── Вывод информации о прокси ────────────────────────────────────────────────
|
||||
show_proxy_info() {
|
||||
local config="${1:-$TELEMT_CONFIG}"
|
||||
local secret port mask_host ip link status
|
||||
|
||||
secret=$(get_config_value secret "$config")
|
||||
port=$(get_config_value port "$config")
|
||||
mask_host=$(get_config_value mask_host "$config")
|
||||
ip=$(get_server_ip)
|
||||
link=$(generate_proxy_link "$ip" "$port" "$secret")
|
||||
status=$(telemt_status)
|
||||
|
||||
local mode
|
||||
mode=$(config_get mode 2>/dev/null || echo "quick")
|
||||
|
||||
local status_icon status_text
|
||||
case "$status" in
|
||||
running) status_icon="✅"; status_text="Работает" ;;
|
||||
stopped) status_icon="⏸️"; status_text="Остановлен" ;;
|
||||
*) status_icon="❌"; status_text="Не установлен" ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo -e " ${BOLD}${WHITE}${status_icon} Статус прокси: ${status_text}${NC}"
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
echo -e " ${WHITE}Ядро:${NC} telemt (Rust)"
|
||||
echo -e " ${WHITE}IP:${NC} ${CYAN}${ip}${NC}"
|
||||
echo -e " ${WHITE}Порт:${NC} ${CYAN}${port}${NC}"
|
||||
echo -e " ${WHITE}Режим:${NC} ${CYAN}${mode}${NC}"
|
||||
echo -e " ${WHITE}Маскировка:${NC} ${CYAN}${mask_host}${NC}"
|
||||
echo -e " ${WHITE}Secret:${NC} ${CYAN}${secret:0:16}...${NC}"
|
||||
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||
echo -e " ${WHITE}Ссылка:${NC}"
|
||||
echo -e " ${GREEN}${link}${NC}"
|
||||
echo ""
|
||||
|
||||
# QR если доступен
|
||||
if command -v qrencode &>/dev/null; then
|
||||
qrencode -t UTF8 -m 2 "$link" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user