在Telegram + Asterisk中实现音频会议





上一篇文章中,我描述了在我的电报机器人中注册时用户选择居住地的实现方式这是我受“电话”概念启发而创建的在同一篇文章中,我将描述bot与Asterisk的集成



做什么的?



许多人不喜欢无法在Telegram中进行群组通话的事实。



好吧,不使用Viber吗?



这样的实现也有很多情况,例如:



  • 对于匿名音频会议,当您不想在会议参与者中“点亮”您的电话号码或ID时(会想到黑客的安息日或匿名的酗酒者俱乐部)。无需在任何组,社区,频道中
  • 当您完全不知道谁将连接到会议时,但您需要使用密码来限制访问
  • Asterisk的所有乐趣:会议管理(静音/单音,踢),与在星号,电报和PSTN上注册的客户进行的混合音频会议。您可以节省很多国际电话费用
  • 通过电报等组织公司回叫


我想到了很多选择,其中有很多,仅受想象力的限制。在与Asterisk合作多年之后,我相信最主要的是打电话给它,然后您可以做任何适合它的事情,甚至将其发送到太空。



星号VoIP-电报VoIP捆绑



借助tg2sip 库,实现了VoIP捆绑包本身存储库本身在“用法”部分中描述了其用法。还有更多有关自定义的文章甚至还有一个Docker映像

此捆绑包的描述不在本文讨论范围之内。



我想表达的唯一细微差别是您无法拨打telegram_id,而该号码不在您的通讯录中。因此,您需要拨打已注册电报的电话号码。



在我的机器人中实现为任何人都可以连接到的公共音频会议(Ether),以及具有密码访问权限的私人音频会议。私人房间/密码由用户自己创建,可以将机器人用作音频会议,会议等的平台。



互动电报机器人-星号



我的机器人中的交互方案如下所示。



用户在机器人菜单中选择所需的房间,机器人通过在POST请求中传递连接参数来调用通过API与Asterisk进行交互的功能:



  • 订户的电话号码
  • 会议室编号
  • 在会议室演示的来电显示
  • 在Asterisk系统中以本机语言向用户发出通知的语言


此外,Asterisk通过电报信道拨出呼叫到请求参数中指定的号码。用户接听电话后,Asterisk将其连接到适当的房间。



可以使用从机器人到Astersik AMI的直接连接,但是我更喜欢通过API工作,越简单越好。



Asterisk服务器端的API



python中的简单API代码。.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)


在这种情况下,简单形式的Asterisk拨号计划如下所示:



[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


此API可以在其他情况下使用,例如,用于组织相同的回调按钮“ Call me back”,等等。



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


这两个工具已经足够集成到您的机器人中,将其包装在您的逻辑中并使用它。



机器人发起会议室呼叫的示例



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


在此示例中,电话号码是静态设置的,但实际上,例如,您可以向数据库发出请求以匹配message.chat.id-电话号码。



希望我的文章可以帮助某人创建很棒的项目,然后与社区分享。



All Articles