Implementando audioconferencia en Telegram + Asterisk





En el artículo anterior, describí la implementación de la elección del lugar de residencia del usuario al registrarse en mi bot de telegramas , que creé inspirado en la idea de "Telefonía" . En el mismo artículo, describiré la integración del bot con Asterisk .



¿Para qué?



A muchas personas no les gusta el hecho de que no se puedan realizar llamadas grupales en Telegram.



Bueno, ¿no usas Viber?



También hay varios casos para tal implementación, por ejemplo:



  • Para conferencias de audio anónimas, cuando no desea "iluminar" su número o identificación entre los participantes de la conferencia (me viene a la mente el sábado de los hackers o el club de alcohólicos anónimos). No es necesario estar en ningún grupo, comunidad, canal
  • Cuando no sabe quién se conectará a la conferencia, pero necesita restringir el acceso con una contraseña
  • Todas las delicias de Asterisk: gestión de conferencias (mute / umute, kick), audioconferencia híbrida con clientes registrados en asterisk, telegram y PSTN. Puedes ahorrar mucho en llamadas internacionales
  • Organización de callback corporativo vía telegrama, etc.


Me vienen a la mente un montón de opciones, hay muchas, limitadas solo por la imaginación. Después de muchos años de trabajar con Asterisk, creo que lo principal es llamarlo, y luego puedes hacer lo que sea adecuado con él, incluso enviarlo al espacio.



Paquete Asterisk VoIP-Telegram VoIP



El paquete de VoIP en sí se implementa gracias a la biblioteca tg2sip . Su uso se describe en el propio repositorio en la sección Uso. Hay algunos artículos más sobre personalización. Incluso hay una imagen de Docker .

Una descripción de este paquete está fuera del alcance de este artículo.



El único matiz que me gustaría expresar es que no puedes llamar a telegram_id, cuyo número no está en tu agenda de contactos. Por lo tanto, debe llamar al número de teléfono en el que está registrado el telegrama.



En mi botimplementado como audioconferencias públicas (Ethers), a las que cualquiera puede conectarse, y audioconferencias privadas con contraseña. Las salas privadas / contraseñas son creadas por los propios usuarios y pueden usar el bot como plataforma para conferencias de audio, reuniones, etc.



Bot de telegrama de interacción - Asterisk



El esquema de interacción en mi bot se ve así.



El usuario selecciona la habitación deseada en el menú del bot, el bot llama a la función de interactuar con Asterisk a través de la API pasando los parámetros de conexión en la solicitud POST:



  • número de teléfono del suscriptor
  • ID de sala de conferencias
  • callerid para presentación en una sala de conferencias
  • idioma para verbalizar notificaciones al usuario en el sistema Asterisk en su idioma nativo


Además, Asterisk realiza una llamada saliente a través de canales de telegramas al número especificado en los parámetros de solicitud. Después de que el usuario responde la llamada, Asterisk lo conecta a la habitación apropiada.



Sería posible usar una conexión directa desde el bot a la AMI de Astersik , pero prefiero trabajar a través de la API, cuanto más simple, mejor.



API en el lado del servidor Asterisk



Código API simple en Python. Los archivos .Call se utilizan para iniciar una llamada



#!/usr/bin/python3
from flask import Flask, request, jsonify
import codecs
import json
import glob
import shutil

api_key = "s0m3_v3ry_str0ng_k3y"
app = Flask(__name__)

@app.route('/api/conf', methods= ['POST'])
def go_conf():
    content = request.get_json()
    ##  
    if not "api_key" in content:
        return jsonify({'error': 'Authentication required', 'message': 'Please specify api key'}), 401
    if not content["api_key"] == api_key:
        return jsonify({'error': 'Authentication failure', 'message': 'Wrong api key'}), 401
    ##      
    if not "phone_number" in content or not "room_name" in content or not "caller_id" in content:
        return jsonify({'error': 'not all parameters are specified'}), 400

    if not "lang" in content:
        lang = "ru"
    else:
        lang = content["lang"]

    phone_number = content["phone_number"]
    room_name = content["room_name"]
    caller_id = content["caller_id"]
    calls = glob.glob(f"/var/spool/asterisk/outgoing/*-{phone_number}*")
    callfile = "cb_conf-" + phone_number + "-" + room_name + ".call"
    filename = "/var/spool/asterisk/" + callfile
    if calls:
        return jsonify({'message': 'error', "text": "call already in progress"})
    with codecs.open(filename, "w", encoding='utf8') as f:
        f.write("Channel: LOCAL/" + phone_number + "@telegram-out\n")
        f.write("CallerID: <" + caller_id + ">\n")
        f.write("MaxRetries: 0\nRetryTime: 5\nWaitTime: 30\n")
        f.write("Set: LANG=" + lang + "\nContext: conf-in\n")
        f.write("Extension: " + room_name + "\nArchive: Yes\n")
    shutil.chown(filename, user="asterisk", group="asterisk")
    shutil.move(filename, "/var/spool/asterisk/outgoing/" + callfile)
    return jsonify({'message': 'ok'})


if __name__ == '__main__':
    app.run(debug=True,host='0.0.0.0', port=8080)


En este caso, el plan de marcado de Asterisk en una forma simple se ve así:



[telegram-out]
exten => _+.!,1,NoOp()
same => n,Dial(SIP/${EXTEN}@telegram)

exten => _X!,1,NoOp()
same => n,Dial(SIP/+${EXTEN}@telegram)

[conf-in]
exten => _.!,1,NoOp()
same => n,Answer()
same => n,Wait(3)
same => n,Playback(beep)
same => n,Set(CHANNEL(language)=${LANG})
same => n,ConfBridge(${EXTEN})
same => n,Hangup


Esta API se puede utilizar en otros casos, por ejemplo, para organizar el mismo botón de devolución de llamada "Devolver la llamada", etc.



Función de llamada API



telephony_api.py



import requests, json

#  example.com   url
url = "http://example.com:8080/api/conf"
api_key = "s0m3_v3ry_str0ng_k3y"

def go_to_conf(phone_number, room_name, caller_id, lang="ru"):
    payload = {}
    payload["phone_number"] = phone_number
    payload["room_name"] = room_name
    payload["caller_id"] = caller_id
    payload["lang"] = lang
    payload["api_key"] = api_key

    headers = {
        'content-type': "application/json",
        'cache-control': "no-cache",
        }
    try:
        response = requests.request("POST", url, data=json.dumps(payload), headers=headers, timeout=2, verify=False)
        if "call already in progress" in response.text:
            return False, ".    ."
        elif "error" in response.text:
            print(response.text)
            return False, ".  .  ."
        else:
            return True, response.text
    except:
        return False, ".  .  ."


Estas dos herramientas ya son suficientes para la integración en tu bot, envuélvelas en tu lógica y úsalas.



Un ejemplo de un bot para iniciar una llamada a una sala de conferencias



#!/usr/bin/python3.6
import telebot
from telephony_api import go_to_conf
bot = telebot.TeleBot("TOKEN")
pnone_number = "799999999999"#   ,    telegram 

@bot.message_handler(content_types=['text'])
def main_text_handler(message):
    if message.text == "   ":
        bot.send_message(message.chat.id, "Ok.   telegram   ,        ")
        func_result, func_message = go_to_conf(pnone_number, "ROOM1", "Bob", "ru")
        if not func_result:
            bot.send_message(chat_id=message.chat.id, text=func_message)

if __name__ == "__main__":)
   print("bot started")
   bot.polling(none_stop=True)


En este ejemplo, el número de teléfono se establece estáticamente, pero en realidad, por ejemplo, puede realizar solicitudes a la base de datos para que coincida message.chat.id - el número de teléfono.



Espero que mi artículo ayude a alguien a crear proyectos interesantes y, a su vez, compartirlos con la comunidad.



All Articles