↩️ Назад

Категории

вебхук манго телекомп для ИИ агента

07.08.2026 | коды из категории: Нейросети

вроде собрал json обновляет по кнопке переводит мп3 в текст. надо еще добить его и можно следующий этап к агенту подключать

#!/usr/bin/env python3
import json
import sqlite3
import hashlib
import requests
import os
import time
import subprocess
from http.server import HTTPServer, BaseHTTPRequestHandler
from datetime import datetime

# ====== НАСТРОЙКИ ======
DB_PATH = "calls.db"
WEBHOOKS = []
MAX_WEBHOOKS = 200

# КЛЮЧИ Mango API
MANGO_API_KEY = "asdfdsaf"
MANGO_API_SALT = "asdfsdafdasf"

# КЛЮЧ Yandex SpeechKit
YANDEX_API_KEY = "asdfsdafdsfa"

# ====== БАЗА ДАННЫХ ======
def init_db():
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('''
        CREATE TABLE IF NOT EXISTS calls (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            call_id TEXT,
            recording_id TEXT,
            record_url TEXT,
            transcript TEXT,
            caller_number TEXT,
            called_number TEXT,
            duration INTEGER,
            created_at TEXT
        )
    ''')
    conn.commit()
    conn.close()
    print("✅ База данных готова")

def save_webhook(data):
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    
    # ====== ИЗВЛЕКАЕМ ВСЕ КЛЮЧИ ======
    call_id = data.get('call_id') or data.get('callId')
    command_id = data.get('command_id') or ''
    sip_call_id = data.get('sip_call_id') or ''
    recording_id = data.get('recording_id') or ''
    
    # ====== ИЗВЛЕКАЕМ НОМЕРА ======
    from_data = data.get('from', {})
    to_data = data.get('to', {})
    caller_number = from_data.get('number') or data.get('callerNumber', '')
    called_number = to_data.get('number') or data.get('calledNumber', '')
    
    # Очищаем sip: если это номер
    if caller_number.startswith('sip:') and caller_number.replace('sip:', '').replace('@', '').replace('+', '').replace('-', '').isdigit():
        caller_number = caller_number.replace('sip:', '').split('@')[0]
    if called_number.startswith('sip:') and called_number.replace('sip:', '').replace('@', '').replace('+', '').replace('-', '').isdigit():
        called_number = called_number.replace('sip:', '').split('@')[0]
    
    # ====== ИЩЕМ СУЩЕСТВУЮЩУЮ ЗАПИСЬ ======
    found_call_id = None
    
    # 1. По command_id
    if command_id:
        c.execute('SELECT call_id FROM calls WHERE command_id = ?', (command_id,))
        row = c.fetchone()
        if row:
            found_call_id = row[0]
            print(f"🔗 По command_id: {found_call_id}")
    
    # 2. По sip_call_id
    if not found_call_id and sip_call_id:
        c.execute('SELECT call_id FROM calls WHERE sip_call_id = ?', (sip_call_id,))
        row = c.fetchone()
        if row:
            found_call_id = row[0]
            print(f"🔗 По sip_call_id: {found_call_id}")
    
    # 3. По номеру + время (последние 30 секунд)
    if not found_call_id and caller_number:
        c.execute('''
            SELECT call_id FROM calls 
            WHERE caller_number = ? 
              AND created_at > datetime('now', '-30 seconds')
            ORDER BY created_at DESC LIMIT 1
        ''', (caller_number,))
        row = c.fetchone()
        if row:
            found_call_id = row[0]
            print(f"🔗 По номеру {caller_number}: {found_call_id}")
    
    # 4. Если нашли — используем, иначе создаём новый
    if found_call_id:
        call_id = found_call_id
    elif not call_id or call_id == 'unknown':
        call_id = f"call_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
        print(f"💾 Создан новый call_id: {call_id}")
    
    # ====== ДЛИТЕЛЬНОСТЬ ======
    duration = data.get('duration', 0) or data.get('talk_time', 0) or 0
    if duration > 10000:
        duration = duration // 1000
    
    # ====== СОХРАНЯЕМ В БАЗУ ======
    c.execute('SELECT call_id, recording_id, caller_number, called_number, duration, sip_call_id, command_id FROM calls WHERE call_id = ?', (call_id,))
    existing = c.fetchone()
    
    if existing:
        # Обновляем
        rec_id = existing[1] or recording_id
        caller = existing[2] or caller_number
        callee = existing[3] or called_number
        dur = existing[4] or duration
        sip = existing[5] or sip_call_id
        cmd = existing[6] or command_id
        
        c.execute('''
            UPDATE calls SET 
                recording_id = ?,
                caller_number = ?,
                called_number = ?,
                duration = ?,
                sip_call_id = ?,
                command_id = ?
            WHERE call_id = ?
        ''', (rec_id, caller, callee, dur, sip, cmd, call_id))
        print(f"🔄 Обновлена запись {call_id[:20]}...")
    else:
        created_at = datetime.now().isoformat()
        c.execute('''
            INSERT INTO calls 
            (call_id, recording_id, caller_number, called_number, duration, sip_call_id, command_id, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ''', (call_id, recording_id, caller_number, called_number, duration, sip_call_id, command_id, created_at))
        print(f"💾 Создана запись {call_id[:20]}...")
    
    conn.commit()
    conn.close()

def update_record_url(call_id, record_url):
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('UPDATE calls SET record_url = ? WHERE call_id = ?', (record_url, call_id))
    conn.commit()
    conn.close()

def save_transcript(call_id, transcript):
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('UPDATE calls SET transcript = ? WHERE call_id = ?', (transcript, call_id))
    conn.commit()
    conn.close()
    print(f"💾 Транскрипт сохранён для {call_id}")

def get_webhooks_from_db(limit=100):
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('SELECT call_id, recording_id, record_url, transcript, caller_number, called_number, duration, created_at FROM calls ORDER BY id DESC LIMIT ?', (limit,))
    rows = c.fetchall()
    conn.close()
    result = []
    for row in rows:
        result.append({
            'call_id': row[0],
            'recording_id': row[1],
            'record_url': row[2],
            'transcript': row[3],
            'caller_number': row[4],
            'called_number': row[5],
            'duration': row[6],
            '_received_at': row[7]
        })
    return result

# ====== ПОЛУЧЕНИЕ ССЫЛКИ НА АУДИО ======
def get_recording_url(recording_id):
    json_body = f'{{"recording_id":"{recording_id}","action":"play"}}'
    sign = hashlib.sha256((MANGO_API_KEY + json_body + MANGO_API_SALT).encode()).hexdigest()
    try:
        resp = requests.post(
            'https://app.mango-office.ru/vpbx/queries/recording/post/',
            data={'vpbx_api_key': MANGO_API_KEY, 'sign': sign, 'json': json_body},
            allow_redirects=False,
            timeout=30
        )
        if resp.status_code == 302:
            return resp.headers.get('Location')
        else:
            print(f"❌ Ошибка получения ссылки: {resp.status_code}")
            return None
    except Exception as e:
        print(f"❌ Ошибка: {e}")
        return None

# ====== КОНВЕРТАЦИЯ И РАСПОЗНАВАНИЕ ======
def convert_to_ogg(input_file, output_file, max_duration=29):
    """Конвертирует MP3 в OGG Opus (обрезает до max_duration секунд)"""
    cmd = f"ffmpeg -i {input_file} -t {max_duration} -c:a libopus -ar 16000 {output_file} -y"
    result = subprocess.run(cmd, shell=True, capture_output=True)
    return result.returncode == 0 and os.path.exists(output_file)

def transcribe_audio(file_path, max_duration=29):
    """Распознаёт аудио через Yandex SpeechKit (обрезает до 29 секунд)"""
    ogg_file = file_path.replace('.mp3', '.ogg')
    
    if not convert_to_ogg(file_path, ogg_file, max_duration):
        print("❌ Ошибка конвертации")
        return None
    
    try:
        with open(ogg_file, 'rb') as f:
            audio_data = f.read()
    except Exception as e:
        print(f"❌ Ошибка чтения файла: {e}")
        return None
    
    url = "https://stt.api.cloud.yandex.net/speech/v1/stt:recognize"
    headers = {
        "Authorization": f"Api-Key {YANDEX_API_KEY}",
        "Content-Type": "audio/ogg;codecs=opus"
    }
    
    try:
        response = requests.post(url, headers=headers, data=audio_data, timeout=120)
        if response.status_code == 200:
            result = response.json().get("result", "")
            print(f"✅ Распознано: {len(result)} символов")
            return result
        else:
            print(f"❌ Ошибка распознавания: {response.status_code} - {response.text}")
            return None
    except Exception as e:
        print(f"❌ Ошибка: {e}")
        return None

# ====== СКАЧИВАНИЕ АУДИО ======
def download_audio(recording_id, call_id, output_dir="recordings"):
    os.makedirs(output_dir, exist_ok=True)
    
    file_url = None
    for attempt in range(10):
        file_url = get_recording_url(recording_id)
        if file_url:
            break
        print(f"  ⏳ Attempt {attempt+1}/10: waiting...")
        time.sleep(10)
    
    if not file_url:
        print(f"❌ Не удалось получить ссылку для {recording_id}")
        return None
    
    try:
        resp = requests.get(file_url, timeout=30)
        if resp.status_code == 200:
            filename = f"{output_dir}/{recording_id}.mp3"
            with open(filename, 'wb') as f:
                f.write(resp.content)
            print(f"✅ Сохранён: {filename} ({len(resp.content)} байт)")
            return file_url
        else:
            print(f"❌ Ошибка скачивания: {resp.status_code}")
            return None
    except Exception as e:
        print(f"❌ Ошибка: {e}")
        return None

# ====== HTTP-ОБРАБОТЧИК ======
class Handler(BaseHTTPRequestHandler):
    
    def do_GET(self):
        if self.path == "/" or self.path == "/mango/":
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.end_headers()
            try:
                with open('index.html', 'rb') as f:
                    self.wfile.write(f.read())
            except FileNotFoundError:
                self.wfile.write(b"<h1>index.html not found</h1>")
            return
        
        if self.path == "/api/webhooks":
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            db_data = get_webhooks_from_db(100)
            all_data = WEBHOOKS + db_data
            seen = set()
            unique = []
            for item in all_data:
                key = item.get('call_id') or item.get('callId')
                if key and key not in seen:
                    seen.add(key)
                    unique.append(item)
            self.wfile.write(json.dumps(unique[-100:][::-1], ensure_ascii=False, default=str).encode())
            return
        
        if self.path == "/api/clear":
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            WEBHOOKS.clear()
            conn = sqlite3.connect(DB_PATH)
            conn.execute('DELETE FROM calls;')
            conn.commit()
            conn.close()
            self.wfile.write(b'{"status":"cleared"}')
            return
        
        if self.path.startswith("/api/transcribe/"):
            recording_id = self.path.split("/")[-1]
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            
            # Находим запись в БД
            conn = sqlite3.connect(DB_PATH)
            c = conn.cursor()
            c.execute('SELECT call_id, recording_id FROM calls WHERE recording_id = ?', (recording_id,))
            row = c.fetchone()
            conn.close()
            
            if not row:
                self.wfile.write(json.dumps({"error": "Запись не найдена"}).encode())
                return
            
            call_id = row[0]
            mp3_file = f"recordings/{recording_id}.mp3"
            
            if not os.path.exists(mp3_file):
                self.wfile.write(json.dumps({"error": "Аудиофайл не найден"}).encode())
                return
            
            # Распознаём
            transcript = transcribe_audio(mp3_file)
            if transcript:
                save_transcript(call_id, transcript)
                self.wfile.write(json.dumps({"status": "ok", "transcript": transcript}).encode())
            else:
                self.wfile.write(json.dumps({"error": "Ошибка распознавания"}).encode())
            return
        
        self.send_response(404)
        self.end_headers()
    
    def do_POST(self):
        if self.path == "/webhook/mango":
            length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(length)
            
            try:
                data = json.loads(body)
                print(f"\n📥 Вебхук: {data.get('call_id') or data.get('callId')}")
                print(f"   Ключи: {list(data.keys())}")
                
                data['_received_at'] = datetime.now().isoformat()
                WEBHOOKS.append(data)
                if len(WEBHOOKS) > MAX_WEBHOOKS:
                    WEBHOOKS.pop(0)
                
                save_webhook(data)
                
                recording_id = data.get('recording_id')
                if recording_id:
                    print(f"🎯 Найдена запись: {recording_id}")
                    call_id = data.get('call_id') or data.get('callId')
                    file_url = download_audio(recording_id, call_id)
                    if file_url and call_id:
                        update_record_url(call_id, file_url)
                        print(f"🔗 Ссылка: {file_url}")
                
            except Exception as e:
                print(f"❌ Ошибка: {e}")
            
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'{"status":"ok"}')
        else:
            self.send_response(404)
            self.end_headers()

# ====== ЗАПУСК ======
if __name__ == "__main__":
    init_db()
    print("🚀 Сервер запущен на http://0.0.0.0:8081")
    print("📡 Веб-интерфейс: http://188.127.243.55:8081/")
    print("📡 Ожидаю вебхуки на /webhook/mango")
    print("🎤 Распознавание речи активно")
    HTTPServer(('0.0.0.0', 8081), Handler).serve_forever()
Оцените статью
Всего: 0



Категории:

Категории

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA