#!/bin/bash # 企业微信机器人增强版消息发送工具 # 支持多种消息类型:文本、文件、图片、语音、Markdown、模板卡片 # 作者: AI Assistant # 版本: 2.0 # 配置文件路径 CONFIG_FILE="$HOME/.wecom_config" # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # No Color # 打印彩色消息 print_info() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_debug() { echo -e "${PURPLE}[DEBUG]${NC} $1" } print_title() { echo -e "${CYAN}$1${NC}" } # 加载或创建配置 load_config() { if [ -f "$CONFIG_FILE" ]; then source "$CONFIG_FILE" if [ -n "$WEBHOOK_KEY" ]; then print_info "已加载配置文件中的webhook key" return 0 fi fi print_warning "未找到配置文件或webhook key为空" setup_webhook_key } # 设置webhook key setup_webhook_key() { print_title "=== 企业微信机器人配置 ===" echo "请输入你的企业微信机器人webhook key:" echo "(从 https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY 中提取key部分)" echo "" echo -n "Webhook Key: " read webhook_key if [ -z "$webhook_key" ]; then print_error "Webhook key不能为空!" exit 1 fi # 保存配置 echo "WEBHOOK_KEY=\"$webhook_key\"" > "$CONFIG_FILE" print_success "配置已保存到 $CONFIG_FILE" WEBHOOK_KEY="$webhook_key" setup_urls } # 设置API URLs setup_urls() { BASE_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook" SEND_URL="${BASE_URL}/send?key=${WEBHOOK_KEY}" UPLOAD_URL="${BASE_URL}/upload_media?key=${WEBHOOK_KEY}" } # 发送文本消息 send_text() { local content="$1" local mention="$2" local json_data if [ -n "$mention" ]; then json_data=$(cat << EOF { "msgtype": "text", "text": { "content": "$content", "mentioned_list": ["$mention"] } } EOF ) else json_data=$(cat << EOF { "msgtype": "text", "text": { "content": "$content" } } EOF ) fi send_request "$json_data" "文本消息" } # 上传文件/语音 upload_media() { local file_path="$1" local file_type="${2:-file}" if [ ! -f "$file_path" ]; then print_error "文件不存在: $file_path" >&2 return 1 fi # 检查文件大小 local file_size=$(get_file_size "$file_path") if [ "$file_size" -le 5 ]; then print_error "文件大小必须大于5个字节,当前大小: ${file_size} 字节" >&2 return 1 fi print_info "正在上传${file_type}文件: $file_path" >&2 local response=$(curl -s -X POST "${UPLOAD_URL}&type=${file_type}" \ -F "media=@$file_path") local errcode=$(echo "$response" | grep -o '"errcode":[0-9]*' | cut -d':' -f2) if [ "$errcode" = "0" ]; then local media_id=$(echo "$response" | grep -o '"media_id":"[^"]*"' | cut -d'"' -f4) print_success "文件上传成功!media_id: $media_id" >&2 echo "$media_id" return 0 else print_error "文件上传失败: $response" >&2 return 1 fi } # 获取文件大小(跨平台兼容) get_file_size() { local file_path="$1" if command -v stat >/dev/null 2>&1; then # Linux系统 if stat -c%s "$file_path" >/dev/null 2>&1; then stat -c%s "$file_path" # macOS系统 elif stat -f%z "$file_path" >/dev/null 2>&1; then stat -f%z "$file_path" else wc -c < "$file_path" | tr -d ' ' fi else wc -c < "$file_path" | tr -d ' ' fi } # 发送文件消息 send_file() { local file_path="$1" local media_id=$(upload_media "$file_path" "file") if [ $? -ne 0 ]; then return 1 fi local json_data=$(cat << EOF { "msgtype": "file", "file": { "media_id": "$media_id" } } EOF ) send_request "$json_data" "文件消息" } # 发送语音消息 send_voice() { local voice_path="$1" # 检查文件格式(简单检查扩展名) if [[ ! "$voice_path" =~ \.(amr|mp3|wav|m4a)$ ]]; then print_warning "语音文件建议使用 .amr、.mp3、.wav 或 .m4a 格式" fi local media_id=$(upload_media "$voice_path" "voice") if [ $? -ne 0 ]; then return 1 fi local json_data=$(cat << EOF { "msgtype": "voice", "voice": { "media_id": "$media_id" } } EOF ) send_request "$json_data" "语音消息" } # 发送图片消息 send_image() { local image_path="$1" if [ ! -f "$image_path" ]; then print_error "图片文件不存在: $image_path" return 1 fi # 检查文件格式 if [[ ! "$image_path" =~ \.(jpg|jpeg|png|gif)$ ]]; then print_warning "图片文件建议使用 .jpg、.png 或 .gif 格式" fi # 读取图片并转换为base64 if ! command -v base64 >/dev/null 2>&1; then print_error "系统缺少base64命令,无法发送图片" return 1 fi local file_size=$(get_file_size "$image_path") if [ "$file_size" -gt 2097152 ]; then # 2MB print_error "图片文件不能超过2M,当前大小: $((file_size/1024))KB" return 1 fi print_info "正在处理图片: $image_path" local image_data=$(base64 "$image_path" | tr -d '\n') local md5_hash if command -v md5sum >/dev/null 2>&1; then md5_hash=$(md5sum "$image_path" | cut -d' ' -f1) elif command -v md5 >/dev/null 2>&1; then md5_hash=$(md5 -q "$image_path") else print_error "系统缺少md5计算工具,无法发送图片" return 1 fi local json_data=$(cat << EOF { "msgtype": "image", "image": { "base64": "$image_data", "md5": "$md5_hash" } } EOF ) send_request "$json_data" "图片消息" } # 发送Markdown消息 send_markdown() { local content="$1" local json_data=$(cat << EOF { "msgtype": "markdown", "markdown": { "content": "$content" } } EOF ) send_request "$json_data" "Markdown消息" } # 发送图文消息 send_news() { local title="$1" local description="$2" local url="$3" local pic_url="$4" local json_data=$(cat << EOF { "msgtype": "news", "news": { "articles": [ { "title": "$title", "description": "$description", "url": "$url", "picurl": "$pic_url" } ] } } EOF ) send_request "$json_data" "图文消息" } # 发送文本通知模板卡片 send_text_card() { local title="$1" local content="$2" local source_desc="${3:-企业微信机器人}" local json_data=$(cat << EOF { "msgtype": "template_card", "template_card": { "card_type": "text_notice", "source": { "desc": "$source_desc", "desc_color": 0 }, "main_title": { "title": "$title" }, "sub_title_text": "$content" } } EOF ) send_request "$json_data" "文本通知卡片" } # 发送图文展示模板卡片 send_news_card() { local title="$1" local desc="$2" local image_url="$3" local jump_url="$4" local source_desc="${5:-企业微信机器人}" local json_data=$(cat << EOF { "msgtype": "template_card", "template_card": { "card_type": "news_notice", "source": { "desc": "$source_desc", "desc_color": 0 }, "main_title": { "title": "$title", "desc": "$desc" }, "card_image": { "url": "$image_url", "aspect_ratio": 2.25 }, "card_action": { "type": 1, "url": "$jump_url" } } } EOF ) send_request "$json_data" "图文展示卡片" } # 发送HTTP请求 send_request() { local json_data="$1" local msg_type="$2" print_info "正在发送${msg_type}..." local response=$(curl -s -X POST "$SEND_URL" \ -H "Content-Type: application/json" \ -d "$json_data") local errcode=$(echo "$response" | grep -o '"errcode":[0-9]*' | cut -d':' -f2) if [ "$errcode" = "0" ]; then print_success "${msg_type}发送成功!" return 0 else print_error "${msg_type}发送失败: $response" return 1 fi } # 发送组合消息 send_combo() { local text_content="$1" local file_path="$2" local mention="$3" print_info "开始发送组合消息(文本+文件)" if ! send_text "$text_content" "$mention"; then return 1 fi sleep 1 # 避免发送太快 if ! send_file "$file_path"; then return 1 fi print_success "组合消息发送完成!" } # 显示帮助信息 show_help() { print_title "企业微信机器人增强版消息发送工具 v2.0" echo "" echo "用法: $0 [选项]" echo "" echo "基础消息选项:" echo " -t, --text TEXT 发送文本消息" echo " -f, --file FILE 发送文件" echo " -i, --image IMAGE 发送图片" echo " -v, --voice VOICE 发送语音" echo " -m, --markdown TEXT 发送Markdown消息" echo " -c, --combo TEXT FILE 发送文本+文件组合" echo "" echo "高级消息选项:" echo " -n, --news 发送图文消息(交互式输入)" echo " --text-card 发送文本通知卡片(交互式输入)" echo " --news-card 发送图文展示卡片(交互式输入)" echo "" echo "其他选项:" echo " -@ USERID @指定用户(与文本消息配合使用)" echo " --config 重新配置webhook key" echo " --test 测试连接" echo " -h, --help 显示此帮助信息" echo "" echo "示例:" echo " $0 -t \"Hello World\" # 发送文本消息" echo " $0 -f \"report.pdf\" # 发送文件" echo " $0 -i \"screenshot.png\" # 发送图片" echo " $0 -v \"voice.amr\" # 发送语音" echo " $0 -c \"请查看附件\" \"document.pdf\" # 发送文本+文件" echo " $0 -t \"重要通知\" -@ \"@all\" # @所有人" echo " $0 -n # 交互式创建图文消息" echo " $0 --text-card # 交互式创建文本卡片" echo "" echo "配置文件位置: $CONFIG_FILE" } # 主菜单 show_menu() { print_title "==================================" print_title " 企业微信机器人增强版发送工具 v2.0" print_title "==================================" echo "1. 发送文本消息" echo "2. 发送文件" echo "3. 发送图片" echo "4. 发送语音" echo "5. 发送文本+文件组合" echo "6. 发送Markdown消息" echo "7. 发送图文消息" echo "8. 发送文本通知卡片" echo "9. 发送图文展示卡片" echo "10. 测试连接" echo "11. 重新配置webhook key" echo "12. 退出" print_title "==================================" echo -n "请选择操作 (1-12): " } # 测试连接 test_connection() { print_info "测试机器人连接..." local test_msg="🤖 机器人连接测试 - $(date '+%Y-%m-%d %H:%M:%S')" if send_text "$test_msg"; then print_success "机器人连接正常!" else print_error "机器人连接失败,请检查webhook key是否正确" fi } # 交互式输入图文消息 interactive_news() { echo "=== 创建图文消息 ===" echo -n "标题: " read title echo -n "描述: " read description echo -n "点击跳转URL: " read url echo -n "图片URL(可选): " read pic_url if [ -z "$title" ] || [ -z "$url" ]; then print_error "标题和URL不能为空" return 1 fi send_news "$title" "$description" "$url" "$pic_url" } # 交互式输入文本卡片 interactive_text_card() { echo "=== 创建文本通知卡片 ===" echo -n "卡片标题: " read title echo -n "卡片内容: " read content echo -n "来源描述(可选,默认:企业微信机器人): " read source_desc if [ -z "$title" ]; then print_error "卡片标题不能为空" return 1 fi send_text_card "$title" "$content" "${source_desc:-企业微信机器人}" } # 交互式输入图文卡片 interactive_news_card() { echo "=== 创建图文展示卡片 ===" echo -n "卡片标题: " read title echo -n "卡片描述: " read desc echo -n "图片URL: " read image_url echo -n "点击跳转URL: " read jump_url echo -n "来源描述(可选,默认:企业微信机器人): " read source_desc if [ -z "$title" ] || [ -z "$image_url" ] || [ -z "$jump_url" ]; then print_error "标题、图片URL和跳转URL不能为空" return 1 fi send_news_card "$title" "$desc" "$image_url" "$jump_url" "${source_desc:-企业微信机器人}" } # 主程序 main() { # 加载配置 load_config setup_urls local text_content="" local file_path="" local mention="" local markdown_content="" local action="" # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -t|--text) text_content="$2" action="text" shift 2 ;; -f|--file) file_path="$2" action="file" shift 2 ;; -i|--image) file_path="$2" action="image" shift 2 ;; -v|--voice) file_path="$2" action="voice" shift 2 ;; -c|--combo) text_content="$2" file_path="$3" action="combo" shift 3 ;; -m|--markdown) markdown_content="$2" action="markdown" shift 2 ;; -n|--news) action="news" shift ;; --text-card) action="text-card" shift ;; --news-card) action="news-card" shift ;; -@) mention="$2" shift 2 ;; --config) setup_webhook_key exit 0 ;; --test) test_connection exit 0 ;; -h|--help) show_help exit 0 ;; *) print_error "未知选项: $1" show_help exit 1 ;; esac done # 如果没有参数,显示交互式菜单 if [ -z "$action" ]; then while true; do show_menu read choice echo "" case $choice in 1) echo -n "请输入文本内容: " read text_input echo -n "是否@某人(可选,直接回车跳过): " read mention_input send_text "$text_input" "$mention_input" ;; 2) echo -n "请输入文件路径: " read file_input send_file "$file_input" ;; 3) echo -n "请输入图片路径: " read image_input send_image "$image_input" ;; 4) echo -n "请输入语音文件路径: " read voice_input send_voice "$voice_input" ;; 5) echo -n "请输入文本内容: " read text_input echo -n "请输入文件路径: " read file_input send_combo "$text_input" "$file_input" ;; 6) echo -n "请输入Markdown内容: " read markdown_input send_markdown "$markdown_input" ;; 7) interactive_news ;; 8) interactive_text_card ;; 9) interactive_news_card ;; 10) test_connection ;; 11) setup_webhook_key setup_urls ;; 12) print_info "再见!" break ;; *) print_error "无效选择,请重新输入" ;; esac echo "" echo "按任意键继续..." read -n 1 clear done exit 0 fi # 执行相应的操作 case $action in "text") send_text "$text_content" "$mention" ;; "file") send_file "$file_path" ;; "image") send_image "$file_path" ;; "voice") send_voice "$file_path" ;; "combo") send_combo "$text_content" "$file_path" "$mention" ;; "markdown") send_markdown "$markdown_content" ;; "news") interactive_news ;; "text-card") interactive_text_card ;; "news-card") interactive_news_card ;; esac } # 运行主程序 main "$@"