diff --git a/scripts/dingtalk_tts.sh b/scripts/dingtalk_tts.sh new file mode 100755 index 0000000..7cc4275 --- /dev/null +++ b/scripts/dingtalk_tts.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# MiniMax TTS → 钉钉语音消息 +# 依赖: xxd, ffmpeg + +set -e + +# 配置 +MINIMAX_API_KEY="sk-cp-WDKUJN0CM7byxgNX8nzr5E8JOe0c3jZP_YVBt200sbYt9bEqsRAeY4O7VldTSg0RBYhNvneKLIttYHDy3YM6m04XWAz4JRW0ABlFHSKXKpuPgZPU02k0MfY" +DINGTALK_APP_KEY="dingklemniq8uqk5qbgx" +DINGTALK_APP_SECRET="_8EHgyhvHRHRMx6fZbh9LNpQoxyYl3At0b-fXXlQiahwupbt9oY5P6Grj8IM9Dx8" +USER_ID="121922510028034588" + +# 临时文件 +TMP_DIR="/tmp/dingtalk_voice_$$" +mkdir -p "$TMP_DIR" + +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +# Step 1: 获取钉钉 Access Token +echo "[1/6] 获取钉钉 Access Token..." +TOKEN_RESPONSE=$(curl -s -X POST 'https://api.dingtalk.com/v1.0/oauth2/accessToken' \ + -H 'Content-Type: application/json' \ + -d "{\"appKey\":\"$DINGTALK_APP_KEY\",\"appSecret\":\"$DINGTALK_APP_SECRET\"}") + +ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('accessToken',''))") +if [ -z "$ACCESS_TOKEN" ]; then + echo "❌ 获取 Access Token 失败: $TOKEN_RESPONSE" + exit 1 +fi +echo "✓ 获取成功" + +# Step 2: 调用 MiniMax TTS +TEXT="$1" +if [ -z "$TEXT" ]; then + echo "用法: $0 <要转换的文本>" + exit 1 +fi + +echo "[2/6] 生成 TTS 音频..." +TTS_RESPONSE=$(curl -s -X POST "https://api.minimaxi.com/v1/t2a_v2" \ + -H "Authorization: Bearer $MINIMAX_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\": \"speech-2.8-hd\", + \"text\": \"$TEXT\", + \"stream\": false, + \"voice_setting\": { + \"voice_id\": \"female-tianmei\", + \"speed\": 1, + \"vol\": 1, + \"pitch\": 0, + \"emotion\": \"happy\" + }, + \"audio_setting\": { + \"sample_rate\": 32000, + \"bitrate\": 128000, + \"format\": \"mp3\", + \"channel\": 1 + } + }") + +HEX_AUDIO=$(echo "$TTS_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('audio',''))") +AUDIO_LENGTH=$(echo "$TTS_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('extra_info',{}).get('audio_length',0))") + +if [ -z "$HEX_AUDIO" ]; then + echo "❌ TTS 调用失败: $TTS_RESPONSE" + exit 1 +fi +echo "✓ TTS 生成成功,时长: ${AUDIO_LENGTH}ms" + +# Step 3: 转换格式 hex → MP3 +echo "[3/6] 转换格式..." +echo "$HEX_AUDIO" | xxd -r -p > "$TMP_DIR/voice.mp3" + +# 获取时长(秒) +DURATION_SEC=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TMP_DIR/voice.mp3" 2>/dev/null | awk '{printf "%.0f", $1}') +if [ -z "$DURATION_SEC" ] || [ "$DURATION_SEC" = "0" ]; then + DURATION_SEC=$(echo "scale=0; $AUDIO_LENGTH / 1000" | bc) +fi +echo "✓ MP3 生成完成,时长: ${DURATION_SEC}秒" + +# Step 4: 上传 MP3 到钉钉 +echo "[4/6] 上传音频到钉钉..." +UPLOAD_RESPONSE=$(curl -s -X POST "https://oapi.dingtalk.com/media/upload?access_token=$ACCESS_TOKEN&type=voice" \ + -F "media=@$TMP_DIR/voice.mp3;type=audio/mpeg;filename=voice.mp3") + +MEDIA_ID=$(echo "$UPLOAD_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('media_id',''))") +if [ -z "$MEDIA_ID" ]; then + echo "❌ 上传失败: $UPLOAD_RESPONSE" + exit 1 +fi +echo "✓ 上传成功,mediaId: $MEDIA_ID" + +# Step 5: 发送语音消息 +echo "[5/6] 发送语音消息..." +SEND_RESPONSE=$(curl -s -X POST "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend" \ + -H "Content-Type: application/json" \ + -H "x-acs-dingtalk-access-token: $ACCESS_TOKEN" \ + -d "{ + \"robotCode\": \"$DINGTALK_APP_KEY\", + \"userIds\": [\"$USER_ID\"], + \"msgKey\": \"sampleAudio\", + \"msgParam\": \"{\\\"mediaId\\\":\\\"$MEDIA_ID\\\",\\\"duration\\\":\\\"$DURATION_SEC\\\"}\" + }") + +PROCESS_KEY=$(echo "$SEND_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('processQueryKey',''))") +if [ -n "$PROCESS_KEY" ]; then + echo "✓ 语音消息发送成功!" + echo "[6/6] 完成 ✓" +else + echo "❌ 发送失败: $SEND_RESPONSE" + exit 1 +fi