↩️
Назад
Категории
вебхук манго телекомп для ИИ агента
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
Категории:
Категории
← Назад к списку
Комментарии
Пока нет комментариев. Будьте первым!