Send voice messages to Feishu (Lark) via TTS → opus → Feishu API.
PriorityEngineCondition1stOpenAI TTS ( gpt-4o-mini-tts)OPENAI_API_KEY env var is set2ndmacOS say (fallback)No OPENAI_API_KEY
ffmpeg with libopus: brew install ffmpegapp_id + app_secret — read from ~/.openclaw/openclaw.json → channels.feishuOPENAI_API_KEY environment variablebash
scripts/feishu-voice.sh <text> <receive_id> [receive_id_type] [voice] [openai_instructions]
Examples:
bash
# Auto-selects engine based on OPENAI_API_KEY
scripts/feishu-voice.sh "今天天气不错!" ou_xxxxxxxx open_id
# OpenAI TTS with Taiwan accent instructions
scripts/feishu-voice.sh "你好!" ou_xxxxxxxx open_id shimmer "Speak with a Taiwan Mandarin accent"
# macOS TTS with specific voice (when no OPENAI_API_KEY)
scripts/feishu-voice.sh "会议提醒" oc_xxxxxxxx chat_id Meijia
# Send to a group
scripts/feishu-voice.sh "下午三点开会" oc_xxxxxxxx chat_id
VoiceCharactershimmerWarm, expressive (recommended for Chinese)novaFriendly, naturalalloyNeutralechoClearfableExpressiveonyxDeep
Use openai_instructions (5th arg) for accent/style control, e.g.:
"Speak with a Taiwan Mandarin accent""Use a warm and friendly tone"say voices (fallback)#VoiceLanguageNotesTingting普通话 zh_CNRecommended defaultMeijia台湾 zh_TWTaiwan accentSinji粤语 zh_HKCantonese
List all: say -v '?'
bash
APP_ID=$(python3 -c "import json; d=json.load(open('$HOME/.openclaw/openclaw.json')); print(d['channels']['feishu']['appId'])")
APP_SECRET=$(python3 -c "import json; d=json.load(open('$HOME/.openclaw/openclaw.json')); print(d['channels']['feishu']['appSecret'])")
bash
curl -s -X POST "https://api.openai.com/v1/audio/speech" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini-tts","input":"要说的文字","voice":"shimmer","response_format":"mp3"}' \
-o /tmp/voice.mp3
bash
say -v "Tingting" "要说的文字" -o /tmp/voice.aiff
bash
# From mp3 (OpenAI)
ffmpeg -y -i /tmp/voice.mp3 -acodec libopus -ac 1 -ar 16000 /tmp/voice.opus
# From aiff (macOS)
ffmpeg -y -i /tmp/voice.aiff -acodec libopus -ac 1 -ar 16000 /tmp/voice.opus
FlagValueWhy-acodec libopusopus codecFeishu only accepts opus-ac 1monoRequired by Feishu-ar 1600016 kHzRequired by Feishu
bash
DURATION_MS=$(ffprobe -v quiet -show_entries format=duration -of csv=p=0 /tmp/voice.opus \
| python3 -c "import sys; print(round(float(sys.stdin.read().strip()) * 1000))")
⚠️ duration param is milliseconds, not seconds. Wrong unit → 0s display.
bash
TENANT_TOKEN=$(curl -s -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \
-H "Content-Type: application/json" \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['tenant_access_token'])")
bash
FILE_KEY=$(curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/files" \
-H "Authorization: Bearer $TENANT_TOKEN" \
-F "file_type=opus" \
-F "file_name=voice.opus" \
-F "duration=$DURATION_MS" \
-F "file=@/tmp/voice.opus" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['file_key'])")
⚠️ file_type must be opus, not audio.
bash
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id" \
-H "Authorization: Bearer $TENANT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"receive_id\":\"$RECEIVE_ID\",\"msg_type\":\"audio\",\"content\":\"{\\\"file_key\\\":\\\"$FILE_KEY\\\"}\"}"
SymptomCauseFixDuration shows 0sduration unit is seconds, not msMultiply by 1000Upload fails 400file_type=audio usedChange to opusSilent/tiny AIFFVoice pack not installedSystem Settings → Accessibility → Spoken Content → download voiceGarbled audioMissing -ac 1 -ar 16000Add both ffmpeg flagsOpenAI 401 errorOPENAI_API_KEY invalid or not setCheck env var; falls back to macOS say