Implémentation de l'audioconférence dans Telegram + Asterisk





Dans l' article précédent, j'ai décrit la mise en œuvre du choix du lieu de résidence de l'utilisateur lors de l'inscription dans mon robot de télégramme , que j'ai créé en s'inspirant de l'idée de "Téléphonie" . Dans le même article, je décrirai l'intégration du bot avec Asterisk .



Pourquoi?



Beaucoup de gens n'aiment pas le fait que les appels de groupe ne peuvent pas être effectués dans Telegram.



Eh bien, n'utilisez pas Viber?



Il existe également un certain nombre de cas pour une telle implémentation, par exemple:



  • Pour les audioconférences anonymes, lorsque vous ne voulez pas «allumer» votre numéro ou identifiant parmi les participants à la conférence (le sabbat des hackers ou le club des alcooliques anonymes vient à l'esprit). Pas besoin d'être dans un groupe, une communauté, une chaîne
  • Lorsque vous ne savez pas du tout qui se connectera à la conférence, mais que vous devez restreindre l'accès avec un mot de passe
  • Tous les plaisirs d'Asterisk: gestion de conférence (mute / umute, kick), audioconférence hybride avec des clients enregistrés sur astérisque, télégramme et PSTN. Vous pouvez économiser beaucoup sur les appels internationaux
  • Organisation du rappel d'entreprise par télégramme, etc.


Beaucoup d'options viennent à l'esprit, il y en a beaucoup, limitées uniquement par l'imagination. Après de nombreuses années de travail avec Asterisk, je pense que l'essentiel est de lui faire un appel, puis vous pouvez faire tout ce qui vous convient, même l'envoyer dans l'espace.



Offre groupée VoIP-Telegram VoIP Asterisk



Le bundle VoIP lui-même est implémenté grâce à la bibliothèque tg2sip . Son utilisation est décrite dans le référentiel lui-même dans la section Utilisation. Il existe plusieurs autres articles sur la personnalisation. Il y a même une image Docker .

Une description de cet ensemble dépasse le cadre de cet article.



La seule nuance que je voudrais exprimer est que vous ne pouvez pas appeler telegram_id, numéro qui ne figure pas dans votre carnet de contacts. Par conséquent, vous devez appeler le numéro de téléphone auquel le télégramme est enregistré.



Dans mon botmis en œuvre sous forme de conférences audio publiques (Ethers), auxquelles tout le monde peut se connecter, et de conférences audio privées avec accès par mot de passe. Les salles privées / mots de passe sont créés par les utilisateurs eux-mêmes et peuvent utiliser le bot comme plate-forme pour les conférences audio, les réunions, etc.



Bot de télégramme d'interaction - Asterisk



Le schéma d'interaction dans mon bot ressemble à ceci.



L'utilisateur sélectionne la salle souhaitée dans le menu du bot, le bot appelle la fonction d'interaction avec Asterisk via l'API en passant les paramètres de connexion dans la requête POST:



  • numéro de téléphone de l'abonné
  • ID de la salle de conférence
  • callerid pour présentation dans une salle de conférence
  • langue pour l'envoi des notifications à l'utilisateur dans le système Asterisk dans la langue maternelle


En outre, Asterisk effectue un appel sortant via des canaux de télégramme au numéro spécifié dans les paramètres de demande. Une fois que l'utilisateur a répondu à l'appel, Asterisk le connecte à la salle appropriée.



Il serait possible d'utiliser une connexion directe du bot à l' AMI Astersik , mais je préfère travailler via l'API, le plus simple sera le mieux.



API côté serveur Asterisk



Code API simple en python. Les fichiers .Call sont utilisés pour lancer un appel



#!/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)


Dans ce cas, le plan de numérotation Asterisk sous une forme simple ressemble à ceci:



[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


Cette API peut être utilisée dans d'autres cas, par exemple pour organiser le même bouton de rappel "Rappelez-moi", etc.



Fonction d'appel 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, ".  .  ."


Ces deux outils suffisent déjà pour une intégration dans votre bot, enveloppez-le dans votre logique et utilisez-le.



Un exemple de bot pour lancer un appel vers une salle de conférence



#!/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)


Dans cet exemple, le numéro de téléphone est défini de manière statique, mais en réalité, par exemple, vous pouvez demander à la base de données de faire correspondre message.chat.id - le numéro de téléphone.



J'espère que mon article aidera quelqu'un à créer des projets sympas et à les partager à son tour avec la communauté.



All Articles