Skill: guang384/aliyun-tts (version 1.0.0)

--- name: aliyun-tts description: Alibaba Cloud Text-to-Speech synthesis service. metadata: {"clawdbot":{"emoji":"🔊"}} --- # aliyun-tts Alibaba Cloud Text-to-Speech synthesis service. ## Configuration Set the following environment variables: - `ALIYUN_APP_KEY` - Application Key - `ALIYUN_ACCESS_KEY_ID` - Access Key ID - `ALIYUN_ACCESS_KEY_SECRET` - Access Key Secret (sensitive) ### Option 1: CLI configuration (recommended) ```bash # Configure App Key clawdbot skills config aliyun-tts ALIYUN_APP_KEY "your-app-key" # Configure Access Key ID clawdbot skills config aliyun-tts ALIYUN_ACCESS_KEY_ID "your-access-key-id" # Configure Access Key Secret (sensitive) clawdbot skills config aliyun-tts ALIYUN_ACCESS_KEY_SECRET "your-access-key-secret" ``` ### Option 2: Manual configuration Edit `~/.clawdbot/clawdbot.json`: ```json5 { skills: { entries: { "aliyun-tts": { env: { ALIYUN_APP_KEY: "your-app-key", ALIYUN_ACCESS_KEY_ID: "your-access-key-id", [REDACTED_SECRET]" } } } } } ``` ## Usage ```bash # Basic usage {baseDir}/bin/aliyun-tts "Hello, this is Aliyun TTS" # Specify output file {baseDir}/bin/aliyun-tts -o /tmp/voice.mp3 "Hello" # Specify voice {baseDir}/bin/aliyun-tts -v siyue "Use siyue voice" # Specify format and sample rate {baseDir}/bin/aliyun-tts -f mp3 -r 16000 "Audio parameters" ``` ## Options | Flag | Description | Default | |------|-------------|---------| | `-o, --output` | Output file path | tts.mp3 | | `-v, --voice` | Voice name | siyue | | `-f, --format` | Audio format | mp3 | | `-r, --sample-rate` | Sample rate | 16000 | ## Available Voices Common voices: `siyue`, `xiaoxuan`, `xiaoyun`, etc. See Alibaba Cloud documentation for the full list. ## Chat Voice Replies When a user requests a voice reply: ```bash # Generate audio {baseDir}/bin/aliyun-tts -o /tmp/voice-reply.mp3 "Your reply content" # Include in your response: # MEDIA:/tmp/voice-reply.mp3 ```

path: aliyun_tts.py
content: #!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
阿里云语音合成 TTS - 完整脚本（带签名）
"""

import base64
import hashlib
import hmac
import http.client
import json
import os
import sys
import time
import urllib.parse
from urllib import parse
import uuid
import argparse

# ============ 配置区 ============
ALIYUN_APP_KEY = os.getenv("ALIYUN_APP_KEY")
ALIYUN_ACCESS_KEY_ID = os.getenv("ALIYUN_ACCESS_KEY_ID")
[REDACTED_SECRET]"ALIYUN_ACCESS_KEY_SECRET")

if not ALIYUN_APP_KEY or not ALIYUN_ACCESS_KEY_ID or not [REDACTED_SECRET]"请设置环境变量: ALIYUN_APP_KEY, ALIYUN_ACCESS_KEY_ID, ALIYUN_ACCESS_KEY_SECRET", file=sys.stderr)
    sys.exit(1)

REGION = "cn-shanghai"  # Token 服务只有上海
# ============ 配置区结束 ============

def percent_encode(text):
    """RFC 3986 URL 编码"""
    if text is None:
        return None
    encoded = parse.quote_plus(text)
    return encoded.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')

def encode_dict(dic):
    """将字典参数排序并编码"""
    keys = dic.keys()
    dic_sorted = [(key, dic[key]) for key in sorted(keys)]
    encoded = parse.urlencode(dic_sorted)
    return encoded.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')

def get_token():
    """获取访问令牌（带签名）"""
    # 1. 构造请求参数
    params = {
        'AccessKeyId': ALIYUN_ACCESS_KEY_ID,
        'Action': 'CreateToken',
        'Format': 'JSON',
        'RegionId': REGION,
        'SignatureMethod': 'HMAC-SHA1',
        'SignatureNonce': str(uuid.uuid1()),
        'SignatureVersion': '1.0',
        'Timestamp': time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        'Version': '2019-02-28'
    }
    
    # 2. 构造规范化的请求字符串
    query_string = encode_dict(params)
    
    # 3. 构造待签名字符串
    string_to_sign = 'GET' + '&' + percent_encode('/') + '&' + percent_encode(query_string)
    
    # 4. 计算签名
    secreted_string = hmac.new(
        bytes(ALIYUN_ACCESS_KEY_SECRET + '&', encoding='utf-8'),
        bytes(string_to_sign, encoding='utf-8'),
        hashlib.sha1
    ).digest()
    signature = base64.b64encode(secreted_string)
    signature = percent_encode(signature)
    
    # 5. 发送请求
    full_url = f"http://nls-meta.{REGION}.aliyuncs.com/?Signature={signature}&{query_string}"
    
    host = f"nls-meta.{REGION}.aliyuncs.com"
    conn = http.client.HTTPConnection(host)
    conn.request("GET", full_url)
    resp = conn.getresponse()
    body = resp.read().decode()
    
    data = json.loads(body)
    if "Token" in data and "Id" in data["Token"]:
        token = data["Token"]["Id"]
        return token
    else:
        print(f"获取 Token 失败: {body}", file=sys.stderr)
        return None

def tts_synthesize(text, voice="siyue", format="mp3", sample_rate=16000, output_file="/tmp/tts_output.mp3"):
    """语音合成"""
    [REDACTED_SECRET])
    if not [REDACTED_SECRET] False
    
    host = f"nls-gateway-{REGION}.aliyuncs.com"
    
    # URL 编码（RFC 3986）
    text_encoded = percent_encode(text)
    
    url = f"/stream/v1/tts?appkey={ALIYUN_APP_KEY}&[REDACTED_SECRET]"
    
    conn = http.client.HTTPSConnection(host)
    conn.request("GET", url)
    resp = conn.getresponse()
    
    content_type = resp.getheader("Content-Type")
    body = resp.read()
    
    if "audio" in content_type:
        with open(output_file, "wb") as f:
            f.write(body)
        print(f"✅ 语音已保存: {output_file}")
        return True
    else:
        print(f"❌ 失败: {body.decode()[:300]}", file=sys.stderr)
        return False

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="阿里云 TTS 语音合成")
    parser.add_argument("text", help="要合成的文本")
    parser.add_argument("-o", "--output", default="/tmp/tts_output.mp3", help="输出文件路径")
    parser.add_argument("-v", "--voice", default="siyue", help="声音名称")
    parser.add_argument("-f", "--format", default="mp3", help="音频格式")
    parser.add_argument("-r", "--sample-rate", default="16000", help="采样率")
    
    args = parser.parse_args()
    
    success = tts_synthesize(
        text=args.text,
        voice=args.voice,
        format=args.format,
        sample_rate=int(args.sample_rate),
        output_file=args.output
    )
    
    sys.exit(0 if success else 1)


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)