[LightLayer]研究了一下官网API,搓了一个检测流量和续费的脚本,可以TG推送
Flanker
2026-08-21 16:51
1


说到这里就要顺嘴严肃批评DMIT了,连个API都给不出来....
使用方法
mkdir /opt/lightlayer
cd /opt/lightlayer
nano install-lightlayer.sh
---复制下面的 粘贴保存------
chmod +x install-lightlayer.sh
bash /opt/lightlayer/install-lightlayer.sh
下面是脚本
#!/usr/bin/env bash
set -Eeuo pipefail
# ============================================================
# LightLayer Monitor - Final Installer
# Install root: /opt/lightlayer
#
# Features:
# - LightLayer API query
# - Interactive service viewer
# - Metered / unlimited traffic detection
# - Port bandwidth display
# - Telegram 4-service compact table
# - DUE + TTL countdown
# - .env secrets (0600)
# - systemd daily persistent report
# ============================================================
INSTALL_DIR="/opt/lightlayer"
APP="${INSTALL_DIR}/lightlayer.sh"
ENV_FILE="${INSTALL_DIR}/.env"
STATE_DIR="${INSTALL_DIR}/state"
SYMLINK="/usr/local/bin/lightlayer"
SERVICE_FILE="/etc/systemd/system/lightlayer-report.service"
TIMER_FILE="/etc/systemd/system/lightlayer-report.timer"
SERVICE_NAME="lightlayer-report.service"
TIMER_NAME="lightlayer-report.timer"
if [[ ${EUID} -ne 0 ]]; then
echo "[ERROR] Please run as root."
exit 1
fi
say() { printf '%s\n' "$*"; }
ok() { printf '[OK] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*" >&2; }
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
install_deps() {
local missing=()
command -v curl >/dev/null 2>&1 || missing+=(curl)
command -v jq >/dev/null 2>&1 || missing+=(jq)
if ((${#missing[@]} == 0)); then
ok "Dependencies already installed."
return
fi
say "Installing dependencies: ${missing[*]}"
if command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl jq ca-certificates
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache curl jq ca-certificates
elif command -v dnf >/dev/null 2>&1; then
dnf install -y curl jq ca-certificates
elif command -v yum >/dev/null 2>&1; then
yum install -y curl jq ca-certificates
else
die "Unsupported package manager. Please install curl and jq manually."
fi
}
shell_quote() {
printf '%q' "$1"
}
write_env() {
local key="$1"
local value="$2"
printf '%s=' "$key" >>"$ENV_FILE"
shell_quote "$value" >>"$ENV_FILE"
printf '\n' >>"$ENV_FILE"
}
validate_time() {
[[ "$1" =~ ^([01][0-9]|2[0-3]):[0-5][0-9]$ ]]
}
echo
echo "============================================================"
echo " LightLayer Monitor - Final Installer"
echo "============================================================"
echo
echo "Install directory: ${INSTALL_DIR}"
echo
install_deps
if [[ -e "$INSTALL_DIR" ]]; then
echo
warn "${INSTALL_DIR} already exists."
read -rp "Type YES to replace it completely: " REPLACE
[[ "$REPLACE" == "YES" ]] || die "Installation cancelled."
systemctl stop "$TIMER_NAME" 2>/dev/null || true
systemctl disable "$TIMER_NAME" 2>/dev/null || true
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
rm -rf -- "$INSTALL_DIR"
fi
echo
read -rp "LightLayer account email: " LL_EMAIL
read -rsp "LightLayer account password: " LL_PASSWORD
echo
read -rsp "Telegram Bot Token: " TG_TOKEN
echo
read -rp "Telegram Channel Chat ID (@channel or -100...): " TG_CHAT_ID
read -rp "Daily report time [09:00]: " REPORT_TIME
REPORT_TIME="${REPORT_TIME:-09:00}"
validate_time "$REPORT_TIME" || die "Invalid time format. Use HH:MM."
read -rp "Report timezone [Asia/Shanghai]: " REPORT_TZ
REPORT_TZ="${REPORT_TZ:-Asia/Shanghai}"
if [[ ! -e "/usr/share/zoneinfo/${REPORT_TZ}" ]]; then
warn "Timezone file /usr/share/zoneinfo/${REPORT_TZ} not found."
read -rp "Continue anyway? [y/N]: " TZ_CONTINUE
[[ "${TZ_CONTINUE,,}" == "y" ]] || die "Installation cancelled."
fi
[[ -n "$LL_EMAIL" ]] || die "LightLayer email cannot be empty."
[[ -n "$LL_PASSWORD" ]] || die "LightLayer password cannot be empty."
[[ -n "$TG_TOKEN" ]] || die "Telegram Bot Token cannot be empty."
[[ -n "$TG_CHAT_ID" ]] || die "Telegram Chat ID cannot be empty."
mkdir -p "$INSTALL_DIR" "$STATE_DIR"
chmod 700 "$INSTALL_DIR" "$STATE_DIR"
: >"$ENV_FILE"
write_env "LIGHTLAYER_EMAIL" "$LL_EMAIL"
write_env "LIGHTLAYER_PASSWORD" "$LL_PASSWORD"
write_env "TELEGRAM_BOT_TOKEN" "$TG_TOKEN"
write_env "TELEGRAM_CHAT_ID" "$TG_CHAT_ID"
write_env "REPORT_TZ" "$REPORT_TZ"
cat >>"$ENV_FILE" <<'ENV_EOF'
# Port bandwidth overrides.
# API currently does not expose a reliable Mbps/Gbps field for these plans,
# so known Service IDs are mapped here. Change only .env when plans change.
BANDWIDTH_58534=1G
BANDWIDTH_46627=10M
BANDWIDTH_32144=10M
BANDWIDTH_32122=10M
ENV_EOF
chmod 600 "$ENV_FILE"
chown root:root "$ENV_FILE"
unset LL_PASSWORD TG_TOKEN
cat >"$APP" <<'APP_EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
BASE_DIR="/opt/lightlayer"
ENV_FILE="${BASE_DIR}/.env"
API_BASE="https://account.lightlayer.net/api"
TOKEN=""
SERVICES_JSON=""
if [[ ! -r "$ENV_FILE" ]]; then
echo "[ERROR] Missing ${ENV_FILE}" >&2
exit 1
fi
# shellcheck disable=SC1090
source "$ENV_FILE"
: "${LIGHTLAYER_EMAIL:?Missing LIGHTLAYER_EMAIL}"
: "${LIGHTLAYER_PASSWORD:?Missing LIGHTLAYER_PASSWORD}"
: "${TELEGRAM_BOT_TOKEN:?Missing TELEGRAM_BOT_TOKEN}"
: "${TELEGRAM_CHAT_ID:?Missing TELEGRAM_CHAT_ID}"
REPORT_TZ="${REPORT_TZ:-Asia/Shanghai}"
if [[ -t 1 ]]; then
RED=$'\033[0;31m'
GREEN=$'\033[0;32m'
YELLOW=$'\033[1;33m'
CYAN=$'\033[0;36m'
BOLD=$'\033[1m'
DIM=$'\033[2m'
RESET=$'\033[0m'
else
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' RESET=''
fi
line() {
printf '%*s\n' 98 '' | tr ' ' '-'
}
die() {
printf '%s[ERROR]%s %s\n' "$RED" "$RESET" "$*" >&2
exit 1
}
warn() {
printf '%s[WARN]%s %s\n' "$YELLOW" "$RESET" "$*" >&2
}
ok() {
printf '%s[OK]%s %s\n' "$GREEN" "$RESET" "$*"
}
cleanup() {
[[ -n "${TOKEN:-}" ]] || return 0
{
printf 'url = "%s/logout"\n' "$API_BASE"
printf 'header = "Authorization: Bearer %s"\n' "$TOKEN"
} | curl -sS --config - \
--connect-timeout 5 \
--max-time 10 \
-X POST \
>/dev/null 2>&1 || true
TOKEN=""
}
trap cleanup EXIT INT TERM
api_login() {
local response
response="$(
jq -nc \
--arg username "$LIGHTLAYER_EMAIL" \
--arg password "$LIGHTLAYER_PASSWORD" \
'{username:$username,password:$password}' |
curl -fsS \
--connect-timeout 10 \
--max-time 30 \
-X POST \
"${API_BASE}/login" \
-H 'Content-Type: application/json' \
--data-binary @-
)" || die "LightLayer login request failed."
TOKEN="$(jq -r '.token // empty' <<<"$response")"
[[ -n "$TOKEN" ]] || die "LightLayer login failed: token missing."
}
api_get() {
local path="$1"
{
printf 'url = "%s%s"\n' "$API_BASE" "$path"
printf 'header = "Authorization: Bearer %s"\n' "$TOKEN"
} | curl -fsS --config - \
--connect-timeout 10 \
--max-time 30
}
load_services() {
SERVICES_JSON="$(api_get "/service")" || die "Unable to load service list."
jq -e '.services | type == "array"' \
>/dev/null 2>&1 <<<"$SERVICES_JSON" ||
die "Unexpected /service response."
}
days_left() {
local due="$1"
if [[ ! "$due" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "-"
return
fi
local due_ts now_ts
due_ts="$(TZ="$REPORT_TZ" date -d "${due} 23:59:59" +%s 2>/dev/null)" || {
echo "-"
return
}
now_ts="$(TZ="$REPORT_TZ" date +%s)"
echo $(( (due_ts - now_ts) / 86400 ))
}
ttl_text() {
local days="$1"
if [[ ! "$days" =~ ^-?[0-9]+$ ]]; then
printf -- "--"
elif (( days < 0 )); then
printf "EXP"
elif (( days == 0 )); then
printf "0d"
else
printf "%dd" "$days"
fi
}
due_short() {
local due="$1"
if [[ "$due" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
date -d "$due" '+%y-%m-%d' 2>/dev/null || printf '%s' "$due"
else
printf -- "--"
fi
}
fmt_bytes_short() {
local bytes="${1:-0}"
awk -v n="$bytes" '
BEGIN {
gb=n/1000000000
if (gb >= 1000) printf "%.2fT", gb/1000
else printf "%.2fG", gb
}
'
}
fmt_gb_short() {
local gb="${1:-0}"
awk -v n="$gb" '
BEGIN {
if (n >= 1000) printf "%.2fT", n/1000
else printf "%.2fG", n
}
'
}
fmt_gb_long() {
local gb="${1:-0}"
awk -v n="$gb" '
BEGIN {
if (n >= 1000) printf "%.3f TB", n/1000
else printf "%.3f GB", n
}
'
}
fmt_bytes_long() {
local bytes="${1:-0}"
awk -v n="$bytes" '
BEGIN {
gb=n/1000000000
if (gb >= 1000) printf "%.3f TB", gb/1000
else printf "%.3f GB", gb
}
'
}
percent() {
local used="${1:-0}"
local total="${2:-0}"
awk -v u="$used" -v t="$total" '
BEGIN {
if (t <= 0) printf "0.0"
else printf "%.1f", u/t*100
}
'
}
progress_bar() {
local pct="${1:-0}"
local width=28
local fill empty
fill="$(
awk -v p="$pct" -v w="$width" '
BEGIN {
n=int(p*w/100+0.5)
if(n<0)n=0
if(n>w)n=w
print n
}
'
)"
empty=$((width-fill))
printf '['
printf '%*s' "$fill" '' | tr ' ' '#'
printf '%*s' "$empty" '' | tr ' ' '-'
printf ']'
}
html_escape() {
local s="$1"
s="${s//&/&}"
s="${s//</<}"
s="${s//>/>}"
printf '%s' "$s"
}
bandwidth_for() {
local sid="$1"
local name="$2"
local key="BANDWIDTH_${sid}"
local value="${!key:-}"
if [[ -n "$value" ]]; then
printf '%s' "$value"
return
fi
case "$name" in
SJ-VP04-L) printf '1G' ;;
SG-VP02-A) printf '10M' ;;
'HK-VP02-A(SY)') printf '10M' ;;
TW-VP03-A) printf '10M' ;;
*) printf -- '--' ;;
esac
}
service_basic() {
local sid="$1"
jq -c --arg sid "$sid" '
.services[]
| select((.id|tostring) == $sid)
' <<<"$SERVICES_JSON"
}
get_service_data() {
local sid="$1"
SERVICE_DETAIL="$(api_get "/service/${sid}" 2>/dev/null || printf '{}')"
RESOURCE_JSON="$(api_get "/service/${sid}/resources" 2>/dev/null || printf '{}')"
IP_JSON="$(api_get "/service/${sid}/ip" 2>/dev/null || printf '{}')"
VMS_JSON="$(api_get "/service/${sid}/vms" 2>/dev/null || printf '{}')"
}
extract_metrics() {
local sid="$1"
local basic
basic="$(service_basic "$sid")"
NAME="$(
jq -r '.service.name // .name // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$NAME" ]] || NAME="$(jq -r '.name // "-"' <<<"$basic")"
LABEL="$(
jq -r '.service.domain // .domain // "-"' <<<"$SERVICE_DETAIL"
)"
STATUS="$(
jq -r '.service.status // .status // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$STATUS" ]] || STATUS="$(jq -r '.status // "-"' <<<"$basic")"
CYCLE="$(
jq -r '.service.billingcycle // .billingcycle // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$CYCLE" ]] || CYCLE="$(jq -r '.billingcycle // "-"' <<<"$basic")"
CREATED="$(
jq -r '.service.date_created // .date_created // "-"' <<<"$SERVICE_DETAIL"
)"
DUE="$(
jq -r '.service.next_due // .next_due // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$DUE" ]] || DUE="$(jq -r '.next_due // "-"' <<<"$basic")"
NEXT_INVOICE="$(
jq -r '.service.next_invoice // .next_invoice // "-"' <<<"$SERVICE_DETAIL"
)"
PRICE="$(
jq -r '.service.total // .total // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$PRICE" ]] || PRICE="$(jq -r '.total // "-"' <<<"$basic")"
IPV4="$(
jq -r '
[
.ips[]?
| select(.status == "assigned")
| .ipaddress
]
| first // "-"
' <<<"$IP_JSON"
)"
if [[ "$IPV4" == "-" ]]; then
IPV4="$(
jq -r '
[
(.vms // {})
| to_entries[]
| .value.ipv4? // empty
]
| first // "-"
' <<<"$VMS_JSON"
)"
fi
VMID="$(
jq -r '
[
(.vms // {})
| to_entries[]
| .value.id? // empty
]
| first // "-"
' <<<"$VMS_JSON"
)"
VM_STATUS="$(
jq -r '
[
(.vms // {})
| to_entries[]
| .value.status? // empty
]
| unique
| join(",")
' <<<"$VMS_JSON"
)"
[[ -n "$VM_STATUS" ]] || VM_STATUS="-"
CPU_CORES="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.cpus? // .value.cores? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"
RAM_MB="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.memory? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"
DISK_GB="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.disk? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"
RX_BYTES="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.bandwidth.data_received? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"
TX_BYTES="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.bandwidth.data_sent? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"
VM_USED_GB="$(
awk -v rx="$RX_BYTES" -v tx="$TX_BYTES" '
BEGIN { printf "%.9f", (rx+tx)/1000000000 }
'
)"
RESOURCE_HAS_TRAFFIC=0
if jq -e '
(.total | type == "object")
and (.limit | type == "object")
and (.total | has("data_combined"))
and (.limit | has("data_combined"))
' >/dev/null 2>&1 <<<"$RESOURCE_JSON"; then
RESOURCE_HAS_TRAFFIC=1
fi
TRAFFIC_TOTAL="$(
jq -r '.total.data_combined // 0' <<<"$RESOURCE_JSON"
)"
TRAFFIC_REMAIN="$(
jq -r '.limit.data_combined // 0' <<<"$RESOURCE_JSON"
)"
TRAFFIC_MODE="unknown"
TRAFFIC_USED="$VM_USED_GB"
TRAFFIC_PERCENT="--"
if (( RESOURCE_HAS_TRAFFIC == 1 )); then
if awk -v t="$TRAFFIC_TOTAL" 'BEGIN {exit !(t > 0)}'; then
TRAFFIC_MODE="metered"
TRAFFIC_USED="$(
awk -v t="$TRAFFIC_TOTAL" -v r="$TRAFFIC_REMAIN" '
BEGIN {
u=t-r
if(u<0)u=0
printf "%.9f", u
}
'
)"
TRAFFIC_PERCENT="$(percent "$TRAFFIC_USED" "$TRAFFIC_TOTAL")"
else
TRAFFIC_MODE="unlimited"
TRAFFIC_TOTAL="0"
TRAFFIC_REMAIN="0"
TRAFFIC_USED="$VM_USED_GB"
TRAFFIC_PERCENT="--"
fi
fi
PORT="$(bandwidth_for "$sid" "$NAME")"
DAYS="$(days_left "$DUE")"
TTL="$(ttl_text "$DAYS")"
}
traffic_table_fields() {
case "$TRAFFIC_MODE" in
metered)
USED_LIMIT="$(fmt_gb_short "$TRAFFIC_USED")/$(fmt_gb_short "$TRAFFIC_TOTAL")"
LEFT_TEXT="$(fmt_gb_short "$TRAFFIC_REMAIN")"
USE_TEXT="${TRAFFIC_PERCENT}%"
;;
unlimited)
USED_LIMIT="$(fmt_gb_short "$TRAFFIC_USED")/INF"
LEFT_TEXT="INF"
USE_TEXT="--"
;;
*)
USED_LIMIT="N/A"
LEFT_TEXT="N/A"
USE_TEXT="--"
;;
esac
}
print_detail() {
local sid="$1"
while true; do
get_service_data "$sid"
extract_metrics "$sid"
clear 2>/dev/null || true
printf '%s%sLightLayer · %s%s\n' "$CYAN" "$BOLD" "$NAME" "$RESET"
line
printf '%-18s %s\n' "Service ID" "$sid"
printf '%-18s %s\n' "VMID" "$VMID"
printf '%-18s %s\n' "Label" "$LABEL"
printf '%-18s %s / %s\n' "Status" "$STATUS" "$VM_STATUS"
printf '%-18s %s\n' "IPv4" "$IPV4"
printf '%-18s %s\n' "Port" "$PORT"
printf '%-18s %s vCPU / %s MB / %s GB\n' "Spec" "$CPU_CORES" "$RAM_MB" "$DISK_GB"
echo
printf '%-18s %s\n' "Created" "$CREATED"
printf '%-18s %s\n' "Billing cycle" "$CYCLE"
printf '%-18s %s\n' "Price" "$PRICE"
printf '%-18s %s\n' "Next invoice" "$NEXT_INVOICE"
printf '%-18s %s (%s)\n' "Next due" "$DUE" "$TTL"
echo
printf '%sTraffic%s\n' "$BOLD" "$RESET"
line
case "$TRAFFIC_MODE" in
metered)
printf '%-18s %s / %s\n' \
"Used / Limit" \
"$(fmt_gb_long "$TRAFFIC_USED")" \
"$(fmt_gb_long "$TRAFFIC_TOTAL")"
printf '%-18s %s\n' \
"Remaining" \
"$(fmt_gb_long "$TRAFFIC_REMAIN")"
printf '%-18s %s%% ' "Usage" "$TRAFFIC_PERCENT"
progress_bar "$TRAFFIC_PERCENT"
echo
;;
unlimited)
printf '%-18s %s\n' "Plan" "Unlimited traffic"
printf '%-18s %s\n' "Current transfer" "$(fmt_gb_long "$TRAFFIC_USED")"
;;
*)
printf '%-18s %s\n' "Traffic" "Unavailable"
;;
esac
printf '%-18s %s\n' "RX" "$(fmt_bytes_long "$RX_BYTES")"
printf '%-18s %s\n' "TX" "$(fmt_bytes_long "$TX_BYTES")"
echo
line
echo "[Enter] Back [R] Refresh [Q] Quit"
read -r -n1 key || true
echo
case "${key,,}" in
r) continue ;;
q) exit 0 ;;
*) return ;;
esac
done
}
print_summary() {
printf '%s%sLightLayer All Services%s\n' "$CYAN" "$BOLD" "$RESET"
line
printf '%-7s %-18s %-15s %-5s %-10s %-6s %-22s\n' \
"ID" "NAME" "IPv4" "PORT" "DUE" "TTL" "TRAFFIC"
line
while IFS= read -r sid; do
get_service_data "$sid"
extract_metrics "$sid"
traffic_table_fields
printf '%-7s %-18.18s %-15s %-5s %-10s %-6s %-22s\n' \
"$sid" "$NAME" "$IPV4" "$PORT" "$DUE" "$TTL" "$USED_LIMIT"
done < <(jq -r '.services[].id | tostring' <<<"$SERVICES_JSON")
}
build_report() {
local report_time total_count active_count metered_count unlimited_count
local nearest_days=999999 nearest_name="" nearest_due=""
local rows=""
report_time="$(TZ="$REPORT_TZ" date '+%Y-%m-%d %H:%M %Z')"
total_count="$(jq '.services | length' <<<"$SERVICES_JSON")"
active_count=0
metered_count=0
unlimited_count=0
while IFS= read -r sid; do
get_service_data "$sid"
extract_metrics "$sid"
traffic_table_fields
if [[ "${STATUS,,}" == "active" ]]; then
active_count=$((active_count+1))
fi
case "$TRAFFIC_MODE" in
metered) metered_count=$((metered_count+1)) ;;
unlimited) unlimited_count=$((unlimited_count+1)) ;;
esac
if [[ "$DAYS" =~ ^[0-9]+$ ]] && (( DAYS < nearest_days )); then
nearest_days="$DAYS"
nearest_name="$NAME"
nearest_due="$DUE"
fi
local short_due
short_due="$(due_short "$DUE")"
printf -v row '%-15.15s %-5s %-13s %-9s %-5s %-8s %-6s' \
"$NAME" "$PORT" "$USED_LIMIT" "$LEFT_TEXT" "$USE_TEXT" "$short_due" "$TTL"
rows+="${row}"$'\n'
done < <(jq -r '.services[].id | tostring' <<<"$SERVICES_JSON")
local table
table=$'NODE PORT USED/LIMIT LEFT USE DUE TTL\n'
table+=$'-------------------------------------------------------------------\n'
table+="$rows"
local safe_table safe_nearest
safe_table="$(html_escape "$table")"
safe_nearest="$(html_escape "${nearest_name:-N/A}")"
REPORT_HTML="<b>🟩 LightLayer Traffic Report</b>
<code>${active_count}/${total_count} Active</code> · <code>$(html_escape "$report_time")</code>
<pre>${safe_table}</pre>
📊 Metered ${metered_count} · Unlimited ${unlimited_count}"
if [[ -n "$nearest_name" ]]; then
REPORT_HTML+="
⏳ Next due: <b>${safe_nearest}</b> · ${nearest_days}d · <code>$(html_escape "$nearest_due")</code>"
fi
}
telegram_send() {
local text="$1"
local response
response="$(
{
printf 'url = "https://api.telegram.org/bot%s/sendMessage"\n' "$TELEGRAM_BOT_TOKEN"
} | curl -fsS --config - \
--connect-timeout 10 \
--max-time 30 \
-X POST \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "parse_mode=HTML" \
--data-urlencode "disable_web_page_preview=true" \
--data-urlencode "text=${text}"
)" || die "Telegram request failed."
if ! jq -e '.ok == true' >/dev/null 2>&1 <<<"$response"; then
warn "Telegram returned an error:"
jq -r '.description // "Unknown Telegram error"' <<<"$response" >&2
return 1
fi
}
send_report() {
build_report
telegram_send "$REPORT_HTML"
ok "Telegram report sent."
}
interactive_menu() {
while true; do
clear 2>/dev/null || true
printf '%s%sLightLayer Services%s\n' "$CYAN" "$BOLD" "$RESET"
line
printf '%-4s %-7s %-22s %-10s %-12s %-10s\n' \
"NO." "ID" "NAME" "STATUS" "DUE" "CYCLE"
line
jq -r '
.services[]
| [
(.id|tostring),
(.name // "-"),
(.status // "-"),
(.next_due // "-"),
(.billingcycle // "-")
]
| @tsv
' <<<"$SERVICES_JSON" |
awk -F '\t' '{
printf "%-4d %-7s %-22.22s %-10s %-12s %-10s\n",
NR,$1,$2,$3,$4,$5
}'
echo
echo "Number: details A: all summary S: send Telegram R: refresh Q: quit"
read -rp "> " choice
case "${choice,,}" in
q)
exit 0
;;
r)
load_services
;;
a)
clear 2>/dev/null || true
print_summary
echo
read -rp "Press Enter to return..." _
;;
s)
send_report
sleep 2
;;
*)
if [[ "$choice" =~ ^[0-9]+$ ]]; then
local count sid
count="$(jq '.services | length' <<<"$SERVICES_JSON")"
if (( choice >= 1 && choice <= count )); then
sid="$(jq -r ".services[$((choice-1))].id | tostring" <<<"$SERVICES_JSON")"
print_detail "$sid"
else
warn "Invalid service number."
sleep 1
fi
else
warn "Use service number, A, S, R or Q."
sleep 1
fi
;;
esac
done
}
usage() {
cat <<'HELP'
Usage:
lightlayer Interactive mode
lightlayer --summary Show all services
lightlayer --report Print Telegram HTML report
lightlayer --send Send Telegram report
lightlayer --help Show help
HELP
}
main() {
command -v curl >/dev/null 2>&1 || die "curl is required."
command -v jq >/dev/null 2>&1 || die "jq is required."
api_login
load_services
case "${1:-}" in
"")
interactive_menu
;;
--summary)
print_summary
;;
--report)
build_report
printf '%s\n' "$REPORT_HTML"
;;
--send)
send_report
;;
--help|-h)
usage
;;
*)
usage
exit 1
;;
esac
}
main "$@"
APP_EOF
chmod 700 "$APP"
chown root:root "$APP"
ln -sfn "$APP" "$SYMLINK"
cat >"$SERVICE_FILE" <<'SERVICE_EOF'
[Unit]
Description=LightLayer daily Telegram report
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=root
WorkingDirectory=/opt/lightlayer
ExecStart=/opt/lightlayer/lightlayer.sh --send
UMask=0077
Nice=10
SERVICE_EOF
HH="${REPORT_TIME%:*}"
MM="${REPORT_TIME#*:}"
cat >"$TIMER_FILE" <<TIMER_EOF
[Unit]
Description=LightLayer daily Telegram report timer
[Timer]
OnCalendar=*-*-* ${HH}:${MM}:00 ${REPORT_TZ}
Persistent=true
AccuracySec=1min
Unit=${SERVICE_NAME}
[Install]
WantedBy=timers.target
TIMER_EOF
chmod 644 "$SERVICE_FILE" "$TIMER_FILE"
bash -n "$APP" || die "Generated application failed bash syntax check."
systemctl daemon-reload
systemctl enable --now "$TIMER_NAME"
echo
ok "Application installed: ${APP}"
ok "Secrets file: ${ENV_FILE} (0600)"
ok "Command link: ${SYMLINK}"
ok "Timer enabled: ${TIMER_NAME}"
echo
echo "============================================================"
echo "API verification"
echo "============================================================"
echo
if "$APP" --summary; then
echo
ok "LightLayer API verification passed."
else
warn "API verification failed. Check ${ENV_FILE}."
fi
echo
read -rp "Send one Telegram test report now? [Y/n]: " SEND_TEST
SEND_TEST="${SEND_TEST:-Y}"
if [[ "${SEND_TEST,,}" == "y" ]]; then
if "$APP" --send; then
ok "Telegram test passed."
else
warn "Telegram test failed. Check Bot Token / Chat ID / bot channel permissions."
fi
fi
echo
echo "============================================================"
echo "Installation complete"
echo "============================================================"
echo
echo "Interactive:"
echo " lightlayer"
echo
echo "Summary:"
echo " lightlayer --summary"
echo
echo "Preview Telegram HTML:"
echo " lightlayer --report"
echo
echo "Send Telegram now:"
echo " lightlayer --send"
echo
echo "Timer:"
echo " systemctl status ${TIMER_NAME} --no-pager"
echo " systemctl list-timers ${TIMER_NAME} --no-pager"
echo
echo "Logs:"
echo " journalctl -u ${SERVICE_NAME} -n 50 --no-pager"
echo
echo "Edit secrets / bandwidth mapping:"
echo " nano ${ENV_FILE}"
echo
最新回复 (5)
-
nsnav 08-21 16:531楼谢谢,马上学习一下
-
mrshyi 08-21 17:252楼^-^
-
madolche 08-21 17:293楼再研究研究,可以抢鸡 ^-^
-
Flanker 楼主 08-21 17:374楼@madolche #3 API限流很厉害的,我随便刷几下就429了...
-
阿鑫 08-21 22:155楼@Flanker #4 使用教程呢,直接部署在服务器就行了吗
* 帖子来源NodeSeek
附近帖子
- ↑OVH 杜甫一块4T磁盘突然不见了
- 📍 [LightLayer]研究了一下官网API,搓了一个检测流量和续费的脚本,可以TG推送
- ↓如果sub2api被禁,散户们该何去何从
- ↓苹果自签工具能7天自动续签的有吗?
- ↓心碎心碎49r/月,150g电信
- ↓之前 pro20 听说有 3000 的周额度
- ↓安全意识绝对遥遥领先