Implementing audio conferencing in Telegram + Asterisk





In the previous article, I described the implementation of the user's choice of place of residence when registering in my telegram bot, which I created inspired by the idea of "Telephony" . In the same article, I will describe the integration of the bot with Asterisk .



What for?



Many people don't like the fact that group calls cannot be made in Telegram.



Well, do not use Viber?



There are also a number of cases for just such an implementation, for example:



  • For anonymous audio conferencing, when you do not want to "light up" your number or id among the conference participants (the Sabbath of hackers or the club of alcoholics anonymous comes to mind). No need to be in any group, community, channel
  • When it is not known who will join the conference at all, but you need to restrict access with a password
  • All the delights of Asterisk: conference management (mute / umute, kick), hybrid audio conferencing with clients registered on asterisk, telegram and PSTN. You can save a lot on international calls
  • Organization of corporate callback via telegram, etc.


A lot of options come to mind, there are many of them, limited only by imagination. After many years of working with Asterisk, I believe that the main thing is to make a call to it, and then you can do whatever is suitable with it, even send it into space.



Asterisk VoIP-Telegram VoIP Bundle



The VoIP bundle itself is implemented thanks to the tg2sip library . Its use is described in the repository itself in the Usage section. There are a few more articles on customization. There is even a Docker image .

A description of this bundle is beyond the scope of this article.



The only nuance that I would like to voice is that you cannot call telegram_id, which number is not in your contact book. Therefore, you need to call the phone number to which the telegram is registered.



In my botimplemented as public audio conferences (Ethers), to which anyone can connect, and private audio conferences with password access. Private rooms / passwords are created by users themselves and can use the bot as a platform for audio conferencing, meetings, etc.



Interaction telegram bot - Asterisk



The interaction scheme in my bot looks like this.



The user selects the desired room in the bot menu, the bot calls the function of interacting with Asterisk via the API by passing the connection parameters in the POST request:



  • subscriber's phone number
  • conference room ID
  • callerid for presentation in a conference room
  • language for sounding notifications to the user in the Asterisk system in the native language


Further, Asterisk makes an outgoing call via telegram channels to the number specified in the request parameters. After the user answers the call, Asterisk connects him to the appropriate room.



It would be possible to use a direct connection from the bot to the Astersik AMI , but I prefer to work through the API, the simpler the better.



API on the Asterisk server side



Simple API code in python. .Call files are used to initiate a call



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


In this case, the Asterisk dialplan in a simple form looks like this:



[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


This API can be used in other cases, for example, for organizing the same callback button "Call me back", etc.



API call function



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, ".  .  ."


These two tools are already enough for integration into your bot, wrap it in your logic and use it.



An example of a bot for initiating a call to a conference room



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


In this example, the phone number is set statically, but in reality, for example, you can make requests to the database for matching message.chat.id - the phone number.



Hopefully my article helps someone create cool projects and in turn share them with the community.



All Articles