自动打电话软件设计与实现

文章目录

方案概述

  1. 使用Twilio的API进行电话呼叫
  2. 实现基本的呼叫逻辑
  3. 添加简单的用户界面

实现代码

1. 安装必要的库

bash 复制代码
pip install twilio flask

2. 主程序代码

python 复制代码
from twilio.rest import Client
from flask import Flask, request, jsonify, render_template
import time
import threading
import logging
from datetime import datetime

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Twilio账户信息 - 请替换为你的实际账户信息
ACCOUNT_SID = 'your_account_sid'
AUTH_TOKEN = 'your_auth_token'
TWILIO_PHONE_NUMBER = '+1234567890'  # 你的Twilio电话号码

# 初始化Twilio客户端
client = Client(ACCOUNT_SID, AUTH_TOKEN)

app = Flask(__name__)

# 存储呼叫任务
call_tasks = {}

class CallTask:
    def __init__(self, phone_number, message, call_time=None):
        self.phone_number = phone_number
        self.message = message
        self.status = "pending"
        self.call_time = call_time or datetime.now()
        self.call_sid = None
        self.start_time = None
        self.end_time = None
    
    def start_call(self):
        try:
            self.status = "calling"
            self.start_time = datetime.now()
            
            # 使用Twilio发起呼叫
            call = client.calls.create(
                url='http://demo.twilio.com/docs/voice.xml',  # 这里使用Twilio的示例,实际应替换为你的TwiML
                to=self.phone_number,
                from_=TWILIO_PHONE_NUMBER,
                record=True
            )
            
            self.call_sid = call.sid
            logger.info(f"Call started to {self.phone_number}, SID: {self.call_sid}")
            
            # 检查呼叫状态
            self.monitor_call()
            
        except Exception as e:
            self.status = "failed"
            logger.error(f"Failed to start call: {str(e)}")
    
    def monitor_call):
        """监控呼叫状态"""
        while True:
            call = client.calls(self.call_sid).fetch()
            
            if call.status in ['completed', 'failed', 'busy', 'no-answer']:
                self.status = call.status
                self.end_time = datetime.now()
                logger.info(f"Call ended with status: {call.status}")
                break
            
            time.sleep(5)

@app.route('/')
def index():
    """显示主界面"""
    return render_template('index.html')

@app.route('/make_call', methods=['POST'])
def make_call():
    """发起呼叫的API接口"""
    data = request.json
    phone_number = data.get('phone_number')
    message = data.get('message', '')
    
    if not phone_number:
        return jsonify({'error': 'Phone number is required'}), 400
    
    # 创建呼叫任务
    task_id = str(int(time.time()))
    call_task = CallTask(phone_number, message)
    call_tasks[task_id] = call_task
    
    # 在新线程中启动呼叫
    threading.Thread(target=call_task.start_call).start()
    
    return jsonify({
        'task_id': task_id,
        'status': 'queued',
        'phone_number': phone_number
    })

@app.route('/call_status/<task_id>')
def call_status(task_id):
    """获取呼叫状态"""
    call_task = call_tasks.get(task_id)
    if not call_task:
        return jsonify({'error': 'Task not found'}), 404
    
    return jsonify({
        'task_id': task_id,
        'status': call_task.status,
        'phone_number': call_task.phone_number,
        'start_time': str(call_task.start_time) if call_task.start_time else None,
        'end_time': str(call_task.end_time) if call_task.end_time else None,
        'call_sid': call_task.call_sid
    })

@app.route('/call_history')
def call_history():
    """获取呼叫历史"""
    calls = client.calls.list(limit=20)
    
    call_history = []
    for call in calls:
        call_history.append({
            'sid': call.sid,
            'from': call.from_formatted,
            'to': call.to_formatted,
            'status': call.status,
            'start_time': str(call.start_time),
            'duration': call.duration
        })
    
    return jsonify(call_history)

if __name__ == '__main__':
    app.run(debug=True)

3. HTML模板 (templates/index.html)

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自动电话呼叫系统</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input, textarea {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
        }
        button {
            background-color: #4CAF50;
            color: white;
            padding: 10px 15px;
            border: none;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
        #status {
            margin-top: 20px;
            padding: 10px;
            border: 1px solid #ddd;
        }
        .call-item {
            border-bottom: 1px solid #eee;
            padding: 10px 0;
        }
    </style>
</head>
<body>
    <h1>自动电话呼叫系统</h1>
    
    <div class="form-group">
        <label for="phone_number">电话号码:</label>
        <input type="text" id="phone_number" placeholder="输入电话号码,包括国家代码,例如 +8613800138000">
    </div>
    
    <div class="form-group">
        <label for="message">消息内容 (可选):</label>
        <textarea id="message" rows="4" placeholder="输入要播放的消息内容"></textarea>
    </div>
    
    <button id="call_button">发起呼叫</button>
    
    <div id="status"></div>
    
    <h2>呼叫历史</h2>
    <div id="call_history"></div>
    
    <script>
        document.getElementById('call_button').addEventListener('click', async () => {
            const phoneNumber = document.getElementById('phone_number').value;
            const message = document.getElementById('message').value;
            
            if (!phoneNumber) {
                alert('请输入电话号码');
                return;
            }
            
            const statusDiv = document.getElementById('status');
            statusDiv.innerHTML = '正在发起呼叫...';
            
            try {
                const response = await fetch('/make_call', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        phone_number: phoneNumber,
                        message: message
                    })
                });
                
                const data = await response.json();
                
                if (response.ok) {
                    statusDiv.innerHTML = `呼叫已排队,任务ID: ${data.task_id}`;
                    
                    // 轮询检查呼叫状态
                    checkCallStatus(data.task_id);
                } else {
                    statusDiv.innerHTML = `错误: ${data.error}`;
                }
            } catch (error) {
                statusDiv.innerHTML = `请求失败: ${error.message}`;
            }
        });
        
        async function checkCallStatus(taskId) {
            const statusDiv = document.getElementById('status');
            
            try {
                const response = await fetch(`/call_status/${taskId}`);
                const data = await response.json();
                
                if (response.ok) {
                    statusDiv.innerHTML = `呼叫状态: ${data.status}<br>
                                            电话号码: ${data.phone_number}<br>
                                            开始时间: ${data.start_time || '未开始'}<br>
                                            结束时间: ${data.end_time || '未结束'}`;
                    
                    // 如果呼叫未完成,继续轮询
                    if (data.status === 'pending' || data.status === 'calling') {
                        setTimeout(() => checkCallStatus(taskId), 2000);
                    }
                } else {
                    statusDiv.innerHTML += `<br>获取状态失败: ${data.error}`;
                }
            } catch (error) {
                statusDiv.innerHTML += `<br>获取状态请求失败: ${error.message}`;
            }
        }
        
        // 加载呼叫历史
        async function loadCallHistory() {
            const historyDiv = document.getElementById('call_history');
            
            try {
                const response = await fetch('/call_history');
                const data = await response.json();
                
                if (response.ok) {
                    if (data.length === 0) {
                        historyDiv.innerHTML = '<p>没有呼叫记录</p>';
                        return;
                    }
                    
                    let html = '';
                    data.forEach(call => {
                        html += `
                            <div class="call-item">
                                <strong>${call.from} → ${call.to}</strong><br>
                                状态: ${call.status}<br>
                                时间: ${call.start_time}<br>
                                时长: ${call.duration}秒
                            </div>
                        `;
                    });
                    
                    historyDiv.innerHTML = html;
                } else {
                    historyDiv.innerHTML = '<p>加载历史记录失败</p>';
                }
            } catch (error) {
                historyDiv.innerHTML = '<p>加载历史记录请求失败</p>';
            }
        }
        
        // 页面加载时获取呼叫历史
        window.addEventListener('load', loadCallHistory);
    </script>
</body>
</html>

功能说明

  1. 发起呼叫

    • 输入电话号码和可选消息内容
    • 点击按钮发起呼叫
    • 系统会返回任务ID并显示呼叫状态
  2. 状态监控

    • 实时显示呼叫状态(pending/calling/completed/failed等)
    • 显示呼叫开始和结束时间
  3. 呼叫历史

    • 显示最近的20条呼叫记录
    • 包括呼叫状态、时长等信息

部署说明

  1. 注册Twilio账号并获取ACCOUNT_SID和AUTH_TOKEN
  2. 购买Twilio电话号码并替换代码中的TWILIO_PHONE_NUMBER
  3. 创建TwiML应用或使用Twilio Studio定义呼叫流程
  4. 运行Flask应用:python app.py

扩展功能建议

  1. 批量呼叫:添加CSV导入功能,支持批量呼叫
  2. 语音合成:集成TTS服务,动态生成语音内容
  3. 呼叫转移:实现IVR菜单和呼叫转移功能
  4. 数据库集成:使用数据库存储呼叫记录
  5. 认证系统:添加用户认证和权限管理

注意事项

  1. 使用Twilio等服务需要遵守相关法律法规
  2. 自动呼叫系统可能受到不同国家/地区的法律限制
  3. 商业使用需要考虑服务费用和通话质量
  4. 需要处理各种异常情况(网络问题、账户余额不足等)
相关推荐
godspeed_lucip3 分钟前
LLM和Agent——专题6:Multi Agent 入门(5)
人工智能·python
网安情报局4 分钟前
告别排队与高延迟:直连GPT全系列,解锁低门槛、高稳定的AI生产力
人工智能·gpt·api·ai大模型
Metaphor6921 小时前
使用 Python 给 PDF 设置背景色或背景图
数据库·python·pdf
郝亚军1 小时前
如何让pycharm-2026.1.2顶部菜单栏固定显示在最上端
python
怪兽学LLM2 小时前
LeetCode 438 找到字符串中所有字母异位词(Python 固定滑动窗口+字符计数解法)
python·算法·leetcode
麻雀飞吧2 小时前
期货量化日志别泄露密码:天勤账户凭证脱敏写法
python
CC数学建模2 小时前
2026年江西省研究生数学建模竞赛1题:空间数据分析中的过拟合识别完整思路、代码、模型、文章,全网首发高质量分享!
python·算法·数学建模
matlabgoodboy2 小时前
计算机java程序代写python代码编写c/c++代做qt设计php开发matlab
java·c语言·python
不考研当牛马2 小时前
Django 框架 深度学习
python·深度学习·django