2026-03-24 19:00:47 +08:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# Icon Downloader - 从多个图标源下载应用图标
|
|
|
|
|
|
# 支持的源:dashboardicons、simpleicons、selfh.st
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
# 颜色定义
|
|
|
|
|
|
RED='\033[0;31m'
|
|
|
|
|
|
GREEN='\033[0;32m'
|
|
|
|
|
|
YELLOW='\033[1;33m'
|
|
|
|
|
|
BLUE='\033[0;34m'
|
|
|
|
|
|
NC='\033[0m'
|
|
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
# 路径配置
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
|
|
2026-03-24 19:00:47 +08:00
|
|
|
|
# 图标源配置
|
|
|
|
|
|
DASHBOARD_ICONS="https://dashboardicons.com/icons"
|
|
|
|
|
|
SIMPLE_ICONS="https://cdn.simpleicons.org"
|
|
|
|
|
|
SELFHST_ICONS="https://selfh.st/icons"
|
|
|
|
|
|
|
|
|
|
|
|
# 默认输出尺寸
|
|
|
|
|
|
DEFAULT_SIZE=200
|
2026-05-26 01:07:55 +08:00
|
|
|
|
MODE="auto"
|
|
|
|
|
|
CACHE_DIR="${ICON_CACHE_DIR:-${SCRIPT_DIR}/../.cache/icons}"
|
|
|
|
|
|
ICON_URL=""
|
|
|
|
|
|
CONNECT_TIMEOUT="${ICON_CONNECT_TIMEOUT:-3}"
|
|
|
|
|
|
MAX_TIME="${ICON_MAX_TIME:-8}"
|
|
|
|
|
|
RETRY="${ICON_RETRY:-1}"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 打印帮助
|
|
|
|
|
|
print_help() {
|
|
|
|
|
|
cat << EOF
|
|
|
|
|
|
${BLUE}Icon Downloader${NC} - 从多个图标源下载应用图标
|
|
|
|
|
|
|
|
|
|
|
|
${YELLOW}用法:${NC}
|
2026-05-26 01:07:55 +08:00
|
|
|
|
$0 [选项] <应用名称> [输出文件] [尺寸]
|
2026-03-24 19:00:47 +08:00
|
|
|
|
|
|
|
|
|
|
${YELLOW}参数:${NC}
|
|
|
|
|
|
应用名称 - 应用的名称(如 alist, nginx, redis)
|
|
|
|
|
|
输出文件 - 图标保存路径(默认: ./logo.png)
|
|
|
|
|
|
尺寸 - 图标尺寸(默认: 200x200)
|
|
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
${YELLOW}选项:${NC}
|
|
|
|
|
|
--mode auto|required|skip|cache-only
|
|
|
|
|
|
auto: 优先缓存,缺失时尝试下载,未找到不创建占位图
|
|
|
|
|
|
required: 找不到图标时返回失败
|
|
|
|
|
|
skip: 跳过图标下载
|
|
|
|
|
|
cache-only: 只使用缓存,不访问网络
|
|
|
|
|
|
--cache-dir <目录>
|
|
|
|
|
|
图标缓存目录(默认: skills/.cache/icons)
|
|
|
|
|
|
--url <URL> 从指定 URL 下载图标
|
|
|
|
|
|
|
2026-03-24 19:00:47 +08:00
|
|
|
|
${YELLOW}图标源优先级:${NC}
|
|
|
|
|
|
1. Dashboard Icons (dashboardicons.com)
|
|
|
|
|
|
2. Simple Icons (simpleicons.org)
|
|
|
|
|
|
3. selfh.st Icons (selfh.st)
|
|
|
|
|
|
|
|
|
|
|
|
${YELLOW}示例:${NC}
|
|
|
|
|
|
$0 alist ./logo.png 200
|
|
|
|
|
|
$0 nginx /tmp/nginx-icon.png
|
|
|
|
|
|
$0 redis
|
|
|
|
|
|
EOF
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 日志函数
|
|
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
|
|
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
|
|
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
normalize_name() {
|
|
|
|
|
|
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' _' '--' | tr -cd 'a-z0-9.-'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
valid_icon_file() {
|
|
|
|
|
|
local file="$1"
|
|
|
|
|
|
[[ -s "$file" ]] || return 1
|
|
|
|
|
|
if head -c 512 "$file" | grep -qiE '<!doctype|<html|<Error>|AccessDenied'; then
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
copy_from_cache() {
|
|
|
|
|
|
local app_name="$1"
|
|
|
|
|
|
local output="$2"
|
|
|
|
|
|
local name_lower
|
|
|
|
|
|
name_lower=$(normalize_name "$app_name")
|
|
|
|
|
|
|
|
|
|
|
|
local ext cached
|
|
|
|
|
|
for ext in png svg webp jpg jpeg; do
|
|
|
|
|
|
cached="${CACHE_DIR}/${name_lower}.${ext}"
|
|
|
|
|
|
if valid_icon_file "$cached"; then
|
|
|
|
|
|
mkdir -p "$(dirname "$output")"
|
|
|
|
|
|
cp "$cached" "$output"
|
|
|
|
|
|
log_info "✓ 使用缓存图标: $cached"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
save_to_cache() {
|
|
|
|
|
|
local app_name="$1"
|
|
|
|
|
|
local file="$2"
|
|
|
|
|
|
local name_lower
|
|
|
|
|
|
name_lower=$(normalize_name "$app_name")
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$CACHE_DIR"
|
|
|
|
|
|
cp "$file" "${CACHE_DIR}/${name_lower}.png" 2>/dev/null || true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 19:00:47 +08:00
|
|
|
|
# 下载并调整图标尺寸
|
|
|
|
|
|
download_and_resize() {
|
|
|
|
|
|
local url="$1"
|
|
|
|
|
|
local output="$2"
|
|
|
|
|
|
local size="$3"
|
2026-05-26 01:07:55 +08:00
|
|
|
|
local tmp
|
|
|
|
|
|
tmp="$(mktemp)"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
if curl -fsSL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$MAX_TIME" --retry "$RETRY" -o "$tmp" "$url" 2>/dev/null && valid_icon_file "$tmp"; then
|
|
|
|
|
|
mkdir -p "$(dirname "$output")"
|
|
|
|
|
|
mv "$tmp" "$output"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
# 检查是否安装了 ImageMagick 或 sips (macOS)
|
|
|
|
|
|
if command -v convert &>/dev/null; then
|
|
|
|
|
|
convert "$output" -resize "${size}x${size}" "$output" 2>/dev/null || true
|
|
|
|
|
|
elif command -v sips &>/dev/null; then
|
|
|
|
|
|
sips -z "$size" "$size" "$output" &>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2026-05-26 01:07:55 +08:00
|
|
|
|
rm -f "$tmp"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试从各图标源下载
|
|
|
|
|
|
try_download_icon() {
|
|
|
|
|
|
local app_name="$1"
|
|
|
|
|
|
local output="$2"
|
|
|
|
|
|
local size="$3"
|
|
|
|
|
|
|
|
|
|
|
|
local name_lower
|
2026-05-26 01:07:55 +08:00
|
|
|
|
name_lower=$(normalize_name "$app_name")
|
2026-03-24 19:00:47 +08:00
|
|
|
|
|
|
|
|
|
|
log_info "尝试下载图标: $app_name"
|
|
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
if [[ -n "$ICON_URL" ]]; then
|
|
|
|
|
|
log_info "尝试指定图标 URL..."
|
|
|
|
|
|
if download_and_resize "$ICON_URL" "$output" "$size"; then
|
|
|
|
|
|
save_to_cache "$app_name" "$output"
|
|
|
|
|
|
log_info "✓ 从指定 URL 下载成功"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-03-24 19:00:47 +08:00
|
|
|
|
# 1. Dashboard Icons
|
|
|
|
|
|
log_info "尝试 Dashboard Icons..."
|
|
|
|
|
|
local dashboard_url="${DASHBOARD_ICONS}/${name_lower}.png"
|
|
|
|
|
|
if download_and_resize "$dashboard_url" "$output" "$size"; then
|
2026-05-26 01:07:55 +08:00
|
|
|
|
save_to_cache "$app_name" "$output"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
log_info "✓ 从 Dashboard Icons 下载成功"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试带 -dark 后缀
|
|
|
|
|
|
if download_and_resize "${DASHBOARD_ICONS}/${name_lower}-dark.png" "$output" "$size"; then
|
2026-05-26 01:07:55 +08:00
|
|
|
|
save_to_cache "$app_name" "$output"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
log_info "✓ 从 Dashboard Icons (dark) 下载成功"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 2. Simple Icons
|
|
|
|
|
|
log_info "尝试 Simple Icons..."
|
|
|
|
|
|
local simple_url="${SIMPLE_ICONS}/${name_lower}"
|
|
|
|
|
|
if download_and_resize "$simple_url" "$output" "$size"; then
|
2026-05-26 01:07:55 +08:00
|
|
|
|
save_to_cache "$app_name" "$output"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
log_info "✓ 从 Simple Icons 下载成功"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Simple Icons 也支持 SVG
|
2026-05-26 01:07:55 +08:00
|
|
|
|
if curl -fsSL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$MAX_TIME" --retry "$RETRY" -o "${output%.png}.svg" "${SIMPLE_ICONS}/${name_lower}" 2>/dev/null && valid_icon_file "${output%.png}.svg"; then
|
2026-03-24 19:00:47 +08:00
|
|
|
|
log_info "✓ 从 Simple Icons 下载 SVG 成功"
|
|
|
|
|
|
# 如果有 SVG,尝试转换
|
|
|
|
|
|
if command -v convert &>/dev/null; then
|
|
|
|
|
|
convert "${output%.png}.svg" -resize "${size}x${size}" "$output" 2>/dev/null || true
|
|
|
|
|
|
if [[ -s "$output" ]]; then
|
|
|
|
|
|
rm -f "${output%.png}.svg"
|
2026-05-26 01:07:55 +08:00
|
|
|
|
save_to_cache "$app_name" "$output"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 3. selfh.st Icons
|
|
|
|
|
|
log_info "尝试 selfh.st Icons..."
|
|
|
|
|
|
local selfhst_url="${SELFHST_ICONS}/${name_lower}.png"
|
|
|
|
|
|
if download_and_resize "$selfhst_url" "$output" "$size"; then
|
2026-05-26 01:07:55 +08:00
|
|
|
|
save_to_cache "$app_name" "$output"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
log_info "✓ 从 selfh.st Icons 下载成功"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
parse_args() {
|
|
|
|
|
|
POSITIONAL=()
|
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
--mode)
|
|
|
|
|
|
MODE="${2:-}"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--cache-dir)
|
|
|
|
|
|
CACHE_DIR="${2:-}"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--url)
|
|
|
|
|
|
ICON_URL="${2:-}"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
-h|--help)
|
|
|
|
|
|
print_help
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
--)
|
|
|
|
|
|
shift
|
|
|
|
|
|
POSITIONAL+=("$@")
|
|
|
|
|
|
break
|
|
|
|
|
|
;;
|
|
|
|
|
|
-*)
|
|
|
|
|
|
log_error "未知选项: $1"
|
|
|
|
|
|
exit 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
POSITIONAL+=("$1")
|
|
|
|
|
|
shift
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
case "$MODE" in
|
|
|
|
|
|
auto|required|skip|cache-only) ;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
log_error "无效模式: $MODE"
|
|
|
|
|
|
exit 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
2026-03-24 19:00:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 主函数
|
|
|
|
|
|
main() {
|
2026-05-26 01:07:55 +08:00
|
|
|
|
parse_args "$@"
|
|
|
|
|
|
|
|
|
|
|
|
local app_name="${POSITIONAL[0]:-}"
|
|
|
|
|
|
local output="${POSITIONAL[1]:-./logo.png}"
|
|
|
|
|
|
local size="${POSITIONAL[2]:-$DEFAULT_SIZE}"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 参数检查
|
|
|
|
|
|
if [[ -z "$app_name" ]] || [[ "$app_name" == "-h" ]] || [[ "$app_name" == "--help" ]]; then
|
|
|
|
|
|
print_help
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
if [[ "$MODE" == "skip" ]]; then
|
|
|
|
|
|
log_info "跳过图标下载: $app_name"
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if copy_from_cache "$app_name" "$output"; then
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$MODE" == "cache-only" ]]; then
|
|
|
|
|
|
log_warn "缓存中未找到图标: $app_name"
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
2026-03-24 19:00:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 尝试下载
|
|
|
|
|
|
if try_download_icon "$app_name" "$output" "$size"; then
|
|
|
|
|
|
log_info "${GREEN}✓ 图标下载完成: $output${NC}"
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-26 01:07:55 +08:00
|
|
|
|
rm -f "$output" "${output%.png}.svg"
|
|
|
|
|
|
log_warn "未找到图标,未创建占位图: $output"
|
2026-03-24 19:00:47 +08:00
|
|
|
|
log_info "请手动从以下网站下载合适的图标:"
|
|
|
|
|
|
echo " - https://dashboardicons.com/icons?q=${app_name}"
|
|
|
|
|
|
echo " - https://simpleicons.org/?q=${app_name}"
|
|
|
|
|
|
echo " - https://selfh.st/icons/"
|
2026-05-26 01:07:55 +08:00
|
|
|
|
|
|
|
|
|
|
if [[ "$MODE" == "required" ]]; then
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2026-03-24 19:00:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 执行主函数
|
|
|
|
|
|
main "$@"
|