### Run Incoming Call Example Source: https://github.com/megafonapi/examples/blob/master/python/README.md Execute the incoming call example script. It requires a token and additional arguments for call handling. ```bash ./incoming.py [ ... additional arguments ... ] ``` -------------------------------- ### Call List Example Source: https://github.com/megafonapi/examples/blob/master/python/README.md This script makes sequential calls to a list of phone numbers provided in a file. It plays a voice message and waits for the call to end before proceeding. ```python # call_list.py - accepts a filename with a list of phone numbers in international format (one per line) and sequentially calls them, playing a voice message and waiting for the call to end. After that, it continues to call the list. ``` -------------------------------- ### Send SMS Example Source: https://github.com/megafonapi/examples/blob/master/python/README.md This script sends flash SMS messages to a list of numbers provided in a text file. ```python # send_sms.py - accepts a text file with a list of numbers as an additional argument and sends flash messages to them. ``` -------------------------------- ### Conference Call Example Source: https://github.com/megafonapi/examples/blob/master/python/README.md This script creates a conference call by connecting multiple numbers from a text file. It plays an invitation and waits for DTMF input to join. Call states are monitored asynchronously using `asyncio`. ```python # conference.py - accepts a text file name with a list of phone numbers, makes calls to each, and sequentially merges them into a voice conference. For each number during the call, it plays an invitation and waits for confirmation (DTMF) to join the conference. Simultaneously, it monitors the conference state (number of participants, entry/exit times, etc.). All call operations occur asynchronously, using the capabilities of the `asyncio` package. ``` -------------------------------- ### Manage Media Files with curl Source: https://github.com/megafonapi/examples/blob/master/README.md Use curl to GET, PUT, or DELETE audio files from the media server. Authentication is done using your 'Personal Account' login and password. ```bash $ curl -X GET -H --user : http://testapi.megafon.ru/media/prompts/welcome.pcm ``` ```bash $ curl -X PUT -T welcome.alaw --user : http://testapi.megafon.ru/media/prompts/welcome.pcm ``` ```bash $ curl -X DELETE --user : http://testapi.megafon.ru/media/prompts/welcome.pcm ``` -------------------------------- ### Initiate and Manage Calls with Megafon API Source: https://context7.com/megafonapi/examples/llms.txt This script demonstrates how to initiate calls to two numbers, wait for them to be answered, and then connect them using the callTrombone method. It also starts call recording and subscribes to voice activity events. Ensure you have the necessary JWT token and destination numbers as command-line arguments. ```python import sys, asyncio, aiohttp, requests from jsonrpc_websocket import Server endpoint_url = 'wss://testapi.megafon.ru/v1/api' megafon = None sessions = {} activeSessions = None def get_file(fname): """Скачивает запись звонка из каталога records/ после её завершения""" r = requests.get(f"http://testapi.megafon.ru/media/records/{fname}", headers={'Authorization': 'JWT ' + sys.argv[1]}) open(fname, 'wb').write(r.content) print(f"Файл {fname} сохранён") def on_fragment(call_session, record_id, seq, filename, silence): print(f"Фрагмент записи [{seq}]: {filename}, тишина={silence}") get_file(filename) def on_record_done(call_session, record_id, seq, filename, dtmf): print(f"Запись завершена [{seq}]: {filename}, клавиша={dtmf}") get_file(filename) def on_silence(call_session): print(f"Тишина в сессии {call_session} (>{1} сек)") def on_sound(call_session): print(f"Голос в сессии {call_session}") async def main(token, dest_a, dest_b): global megafon, sessions, activeSessions megafon = Server(endpoint_url, headers={'Authorization': 'JWT ' + token}) megafon.onCallAccept = lambda cs: print(f"Вызов {cs} разрешён") megafon.onCallAnswer = lambda cs: (activeSessions[cs].set(), asyncio.get_event_loop().create_task( megafon.callTonePlay(call_session=cs, tone_id="500", repeat=True))) megafon.onCallTerminate = lambda cs, *a: call_done.set() megafon.onCallFragmentRecord = on_fragment megafon.onCallRecord = on_record_done megafon.onSilenceDetect = on_silence megafon.onSoundDetect = on_sound await megafon.ws_connect() # Сохраняем транзакции при потере соединения await megafon.setOptions(on_connection_lost="KEEP_ALIVE") call_done = asyncio.Event() # Параллельно звоним на оба номера async def dial(dest): resp = await megafon.callMake(bnum=dest) sessions[resp['data']['call_session']] = dest return resp['data']['call_session'] call_sessions = await asyncio.gather(dial(dest_a), dial(dest_b)) activeSessions = {s: asyncio.Event() for s in call_sessions} # Ждём ответа обоих абонентов await asyncio.gather(*(e.wait() for e in activeSessions.values())) print(f"Оба плеча {call_sessions[0]} и {call_sessions[1]} установлены") # Объединяем в тромбон trom = await megafon.callTrombone( a_session=call_sessions[0], b_session=call_sessions[1]) print(f"Тромбон: {trom['message']}") # Запись с обнаружением тишины (завершается по '#' или отбою) rec = await megafon.callRecordingStart( call_session=call_sessions[0], detect_silence=True, dtmf_term='#') print(f"Запись {rec['data']['record_id']} начата") # Подписка на события качества звука await megafon.eventSubscribe( call_session=call_sessions[0], events={"voiceActivity": True, "soundQuality": True}) await call_done.wait() await megafon.close() await megafon.session.close() asyncio.get_event_loop().run_until_complete(main(sys.argv[1], sys.argv[2], sys.argv[3])) ``` -------------------------------- ### Trombone Call Example Source: https://github.com/megafonapi/examples/blob/master/python/README.md This script connects two calls into a single voice channel and records the call. It requires two phone numbers as additional arguments. Note that call recording download might be affected by WebDAV authentication issues. ```python # trombone.py - accepts TWO phone numbers as additional arguments, merging both calls into one voice channel. Simultaneously, it records the call to a file. The file is then downloaded (this functionality is currently a bit broken - we are redoing authentication in WebDAV). ``` -------------------------------- ### Incoming Call Handling Example Source: https://github.com/megafonapi/examples/blob/master/python/README.md This script waits for an incoming call, then makes an outgoing call upon receiving '#'. It bridges the two calls after the second party confirms with '#', creating a 'trombone' effect. ```python # incoming.py - accepts a phone number and waits for an incoming call. Upon receiving such a call, it waits for the '#' symbol and, upon receiving it, makes a second outgoing call to the number specified in the command line. Upon receiving confirmation from the second party (also '#'), it merges the two legs into one (performs a call 'trombone'). ``` -------------------------------- ### JSON-RPC WebSocket Library Note Source: https://github.com/megafonapi/examples/blob/master/python/README.md The `jsonrpc-websocket` library used in these examples does not pass the original request ID to the developer, which can complicate error analysis. Consider using nested try-blocks or modifying the library for better error handling. ```text ВАЖНО! Пакет `jsonrpc_websocket` не передает(!) разработчику `id` исходного запроса (но возвращает его в случае ошибки). Это может быть не очень удобно в случае анализа ошибок исполнения тех или иных команд (ошибошные RPC-сообщения отображаются в исключениях). Возможный обход — использование вложенных `try`-блоков, несколько `try`-блоков или правки в самом `jsonrpc_websocket`. ``` -------------------------------- ### Initiate Outgoing Call with File Playback (Go) Source: https://context7.com/megafonapi/examples/llms.txt This Go program demonstrates a full cycle of initiating an outgoing call, playing an audio file upon answering, and then terminating the call. It requires authentication, API key management, and WebSocket connection. ```go // Запуск: go run call.go 79XXXXXXXXX ВашПароль 79YYYYYYYYY package main import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" "time" "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" ) // Структуры для JSON-RPC сообщений type rpcRequest struct { ID int `json:"id"` Jsonrpc string `json:"jsonrpc"` Method string `json:"method"` Params map[string]string `json:"params"` } type rpcResponse struct { ID int `json:"id"` Method string `json:"method"` Params map[string]interface{} `json:"params"` Result struct{ Message string `json:"message"` } `json:"result"` } func main() { args := os.Args[1:] // args[0]=логин, args[1]=пароль, args[2]=номер назначения // 1. Логин и получение accessToken body, _ := json.Marshal(map[string]string{"login": args[0], "password": args[1]}) resp, _ := http.Post("https://testapi.megafon.ru/api/rest/login", "application/json", bytes.NewBuffer(body)) var loginResp struct{ Data struct{ AccessToken string `json:"accessToken"` } `json:"data"` } raw, _ := ioutil.ReadAll(resp.Body) json.Unmarshal(raw, &loginResp) token := loginResp.Data.AccessToken // 2. Получить или создать API-ключ client := &http.Client{} req, _ := http.NewRequest("GET", "https://testapi.megafon.ru/api/rest/apiKeys", nil) req.Header.Set("Authorization", "Bearer "+token) resp, _ = client.Do(req) raw, _ = ioutil.ReadAll(resp.Body) var keys struct{ Data []string `json:"data"` } json.Unmarshal(raw, &keys) var apiKey string if len(keys.Data) > 0 { apiKey = keys.Data[0] } else { req, _ = http.NewRequest("POST", "https://testapi.megafon.ru/api/rest/apiKeys", nil) req.Header.Set("Authorization", "Bearer "+token) resp, _ = client.Do(req) raw, _ = ioutil.ReadAll(resp.Body) var newKey struct{ Data struct{ APIKey string `json:"apiKey"` } `json:"data"` } json.Unmarshal(raw, &newKey) apiKey = newKey.Data.APIKey } // 3. Подключение по WebSocket ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() c, _, _ := websocket.Dial(ctx, fmt.Sprintf("wss://testapi.megafon.ru/v1/api/%s", apiKey), nil) defer c.Close(websocket.StatusNormalClosure, "") // 4. Асинхронная обработка событий id := 0 done := make(chan bool) go func() { for { var msg rpcResponse wsjson.Read(ctx, c, &msg) session := fmt.Sprintf("%v", msg.Params["call_session"]) switch msg.Method { case "onCallAnswer": // Абонент ответил — воспроизводим файл id++ wsjson.Write(ctx, c, &rpcRequest{id, "2.0", "callFilePlay", map[string]string{"call_session": session, "filename": "welcome.pcm"}}) case "onCallFilePlay": // Файл воспроизведён — завершаем вызов id++ wsjson.Write(ctx, c, &rpcRequest{id, "2.0", "callTerminate", map[string]string{"call_session": session}}) case "onCallTerminate": done <- true } } }() // 5. Инициация исходящего вызова wsjson.Write(ctx, c, &rpcRequest{id, "2.0", "callMake", map[string]string{"bnum": args[2]}}) <-done // 6. Удаление API-ключа req, _ = http.NewRequest("DELETE", "https://testapi.megafon.ru/api/rest/apiKeys/"+apiKey, nil) req.Header.Set("Authorization", "Bearer "+token) client.Do(req) } ``` -------------------------------- ### Аутентификация и получение API-ключа (REST) Source: https://context7.com/megafonapi/examples/llms.txt Последовательность шагов для получения accessToken, списка API-ключей, создания нового API-ключа и его удаления. Используется для предварительной аутентификации перед установкой WebSocket-соединения. ```bash # Шаг 1: Получить accessToken curl -s -X POST https://testapi.megafon.ru/api/rest/login \ -H "Content-Type: application/json" \ -d '{"login":"79XXXXXXXXX","password":"ВашПароль"}' \ | python3 -m json.tool # Ответ: {"data":{"accessToken":"eyJ..."}} ``` ```bash # Шаг 2: Получить список API-ключей ACCESS_TOKEN="eyJ..." curl -s -X GET https://testapi.megafon.ru/api/rest/apiKeys \ -H "Authorization: Bearer $ACCESS_TOKEN" # Ответ: {"data":["abcd-1234-..."]}" или {"data":[]} ``` ```bash # Шаг 3: Создать API-ключ (если список пуст) curl -s -X POST https://testapi.megafon.ru/api/rest/apiKeys \ -H "Authorization: Bearer $ACCESS_TOKEN" # Ответ: {"data":{"apiKey":"abcd-1234-..."}} ``` ```bash # Шаг 4: Удалить API-ключ по завершении работы API_KEY="abcd-1234-..." curl -s -X DELETE "https://testapi.megafon.ru/api/rest/apiKeys/$API_KEY" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` -------------------------------- ### Sequential Call List Processing in Python Source: https://context7.com/megafonapi/examples/llms.txt This script demonstrates sequential calling of numbers from a file, playing an audio file upon answer, and waiting for each call to complete before proceeding. It's designed to be run with a JWT token and a file containing phone numbers. ```python # Запуск: ./call_list.py <файл_с_номерами> ``` -------------------------------- ### Управление аудиофайлами через WebDAV Source: https://context7.com/megafonapi/examples/llms.txt Примеры использования `curl` для управления аудиофайлами (скачивание, загрузка, удаление) через WebDAV. Также включает примеры конвертации аудиоформатов с использованием `sox`. ```bash # Скачать файл приветствия curl -X GET --user 79XXXXXXXXX:ВашПароль \ http://testapi.megafon.ru/media/prompts/welcome.pcm -o welcome.pcm ``` ```bash # Загрузить файл на платформу curl -X PUT -T welcome.alaw --user 79XXXXXXXXX:ВашПароль \ http://testapi.megafon.ru/media/prompts/welcome.pcm ``` ```bash # Удалить файл curl -X DELETE --user 79XXXXXXXXX:ВашПароль \ http://testapi.megafon.ru/media/prompts/welcome.pcm ``` ```bash # Скачать запись звонка (аутентификация по JWT-токену) TOKEN="eyJ..." curl -X GET http://testapi.megafon.ru/media/records/79XXXXXXXXX_56127.pcm \ -H "Authorization: JWT $TOKEN" -o call_record.pcm ``` ```bash # Конвертация файла из OGG в формат платформы (A-law 8 KHz моно) sox -V hello.ogg -r 8000 -c 1 -t al hello.pcm ``` ```bash # Конвертация записи платформы в WAV для прослушивания sox -V -c 1 -r 8000 -t al 79XXXXXXXXX_56127.pcm 79XXXXXXXXX_56127.wav ``` -------------------------------- ### Conference with IceCast Streaming (Python) Source: https://context7.com/megafonapi/examples/llms.txt Creates a conference, streams it via IceCast, and optionally connects an external media stream. Supports authentication via JWT token or basic login/password. ```Python # Запуск: ./broadcast.py <файл_с_номерами> # Или с логином/паролем: ./broadcast.py <файл_с_номерами> import sys, asyncio, aiohttp from jsonrpc_websocket import Server endpoint_url = 'wss://testapi.megafon.ru/v1/api' async def main(token=None, login=None, password=None, destinations=None): global megafon, confId if token: megafon = Server(endpoint_url, headers={'Authorization': 'JWT ' + token}) elif login and password: megafon = Server(endpoint_url, auth=aiohttp.BasicAuth(login, password)) # ... (аналогичная установка callbacks, как в conference.py) await megafon.ws_connect() # Создаём конференцию resp = await megafon.confMake() confId = resp['data']['conf_session'] # Создаём интернет-трансляцию (вещание наружу через IceCast) broadcast = await megafon.confBroadcastCreate(conf_session=confId) print(f"URL трансляции: {broadcast['data']['url']}") # Слушатели могут подключиться к этому URL в любом IceCast-совместимом плеере # Подключить внешний IceCast-поток внутрь конференции (раскомментировать при необходимости): # live = await megafon.confBroadcastConnect( # conf_session=confId, # url="shout://icecast.vgtrk.cdnvideo.ru/vestifm_mp3_64kbps") # print(f"Внешний поток подключён: {live['message']}") # Параллельный обзвон dest_list = [l.rstrip('\n') for l in open(destinations)] sessions = await asyncio.gather(*(make_call(d) for d in dest_list)) await asyncio.gather(*(activeCalls[s].terminated.wait() for s in sessions)) # Завершение трансляции await megafon.confBroadcastDestroy(conf_session=confId) # await megafon.confBroadcastDisconnect(conf_session=confId) # для внешнего потока await megafon.close() await megafon.session.close() ``` -------------------------------- ### Make Calls and Play Audio Files with Megafon API Source: https://context7.com/megafonapi/examples/llms.txt Initiates calls to a list of destinations from a file, plays an audio file upon connection, and handles call events. Requires a token and a file with phone numbers. Ensure the 'new_year.pcm' file is accessible. ```Python import sys, asyncio from jsonrpc_websocket import Server endpoint_url = 'wss://testapi.megafon.ru/v1/api' play_file = 'new_year.pcm' megafon = None flow_ev = terminated_ev = None sessions = {} def call_accepted(call_session): print(f"Вызов {call_session} на {sessions[call_session]}: разрешён") def call_answered(call_session): print(f"Вызов {call_session} на {sessions[call_session]}: ответили") flow_ev.set() def call_rejected(call_session, sipCode, cause, message): print(f"Вызов {call_session} отклонён: SIP={sipCode}, ISUP={cause}") terminated_ev.set(); flow_ev.set() def call_terminated(call_session, source, cause, message): print(f"Вызов {call_session} завершён: SOURCE={source}, ISUP={cause}") terminated_ev.set(); flow_ev.set() async def main(token, destinations_file): global megafon, flow_ev, terminated_ev, sessions megafon = Server(endpoint_url + "/" + token) megafon.onCallAccept = call_accepted megafon.onCallAnswer = call_answered megafon.onCallReject = call_rejected megafon.onCallTerminate = call_terminated await megafon.ws_connect() flow_ev = asyncio.Event() terminated_ev = asyncio.Event() with open(destinations_file) as f: for line in f: bnum = line.strip() if not bnum: continue print(f"Звоню на {bnum}...") flow_ev.clear(); terminated_ev.clear() resp = await megafon.callMake(bnum=bnum) sessions[resp['data']['call_session']] = bnum await flow_ev.wait() # ждём ответа или отбоя if terminated_ev.is_set(): continue # Воспроизводим файл (принимаем '#' как завершение, таймаут 100 сек) flow_ev.clear() await megafon.callFilePlay( call_session=resp['data']['call_session'], filename=play_file, timeout=100, dtmf_term='#') await flow_ev.wait() if terminated_ev.is_set(): continue await terminated_ev.wait() # ждём отбоя абонента await megafon.close() await megafon.session.close() print("Обзвон завершён") asyncio.get_event_loop().run_until_complete(main(sys.argv[1], sys.argv[2])) ``` -------------------------------- ### Establish WebRTC Call from Browser Source: https://context7.com/megafonapi/examples/llms.txt Connects a browser to the MegafonAPI platform using WebRTC, handling signaling and media streams. Requires the simple-jsonrpc-js library. Ensure microphone access is granted. ```javascript // Используется библиотека simple-jsonrpc-js (https://github.com/jershell/simple-jsonrpc-js) const megafon = new simple_jsonrpc(); const iceConfig = { iceServers: [ { urls: 'stun:stun.stunprotocol.org:3478' }, { urls: 'stun:stun.l.google.com:19302' }, ] }; let MFserverSignaling, MFserverMedia, localStream; let browser_leg = null, gsm_leg = null; async function connect(websocketUrl) { MFserverSignaling = new WebSocket(websocketUrl); // Обязательное переопределение toStream() для отправки сообщений megafon.toStream = msg => MFserverSignaling.send(msg); MFserverSignaling.onmessage = msg => megafon.messageHandler(msg.data); MFserverSignaling.onopen = async () => { // Получаем доступ к микрофону и создаём WebRTC-соединение localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); MFserverMedia = new RTCPeerConnection(iceConfig); MFserverMedia.addStream(localStream); MFserverMedia.ontrack = e => { remoteAudio.srcObject = e.streams[0]; }; // Ждём завершения сбора ICE-кандидатов перед отправкой SDP MFserverMedia.onicecandidate = e => { if (e.target.iceGatheringState === 'complete') { // Передаём SDP браузера в платформу — она воспримет это как входящий вызов megafon.call('callMakeRTC', { sdp: e.target.localDescription.sdp }); } }; await MFserverMedia.setLocalDescription(await MFserverMedia.createOffer()); megafon.call('setupRTC'); // инициализация WebRTC на стороне платформы }; // Платформа возвращает onCallIncoming для браузерного плеча (anum === "0000000000") megafon.on('onCallIncoming', 'pass', event => { if (event.anum.toString() === "0000000000") { browser_leg = event.call_session.toString(); megafon.call('callAnswer', { call_session: browser_leg }); } else { gsm_leg = event.call_session.toString(); megafon.call('callAnswer', { call_session: gsm_leg }); } }); // Платформа возвращает свой SDP — применяем его как remoteDescription megafon.on('onCallAnswerRTC', 'pass', event => { const sdpLines = event.sdp.split('\r\n'); const sdp = sdpLines.filter(l => !l.includes('a=candidate') && !l.includes('a=end-of-candidates') && l !== '') .join('\r\n') + '\r\n'; const candidates = sdpLines.filter(l => l.includes('a=candidate')) .map(l => l.slice(2)); MFserverMedia.setRemoteDescription(new RTCSessionDescription({ sdp, type: 'answer' })) .then(() => candidates.forEach(c => MFserverMedia.addIceCandidate( new RTCIceCandidate({ sdpMid: '', sdpMLineIndex: '', candidate: c })))); }); // Когда оба плеча ответили — объединяем в тромбон megafon.on('onCallTonePlay', 'pass', event => { if (browser_leg && gsm_leg) { megafon.call('callTrombone', { a_session: browser_leg, b_session: gsm_leg }); } }); } // Позвонить на номер из браузера после установки browser_leg function callMake(bnum) { megafon.call('callMake', { bnum }); } ``` -------------------------------- ### WebSocket JSON-RPC Connection and Event Handling Source: https://context7.com/megafonapi/examples/llms.txt Establishes a WebSocket connection and defines handlers for various API events like incoming calls, call answers, file playback, speech recognition fragments, and conference creation. Requires an apiKey for connection. ```javascript // After obtaining apiKey via REST API, open WebSocket const socket = new WebSocket(`wss://testapi.megafon.ru/v1/api/${apiKey}`); let reqnum = 0; // Helper function to send JSON-RPC requests function request(method, params) { socket.send(JSON.stringify({ id: ++reqnum, jsonrpc: '2.0', method: method, params: params })); } socket.onopen = () => console.log("Connected to MegaFon.API"); socket.onmessage = (message) => { const data = JSON.parse(message.data); switch (data.method) { case 'onCallIncoming': // Incoming call - answer request('callAnswer', { call_session: data.params.call_session }); break; case 'onCallAnswer': // Subscriber answered - play greeting file request('callFilePlay', { call_session: data.params.call_session, filename: 'welcome.pcm' }); break; case 'onCallFilePlay': // File played - start speech recognition or terminate call if (yandexKey) { request('callSTTStart', { call_session: data.params.call_session, engine: 'yandex' // Yandex SpeechKit key required in account settings }); } else { request('callTerminate', { call_session: data.params.call_session }); } break; case 'onSTTFragment': // Recognized speech fragment console.log('Recognized:', data.params.message); break; case 'onConfMake': // Conference created - connect external IceCast stream and call participant const conf = data.params.conf_session; request('confBroadcastConnect', { conf_session: conf, url: "shout://icecast.vgtrk.cdnvideo.ru/vestifm_mp3_64kbps" }); request('callMake', { bnum: '79YYYYYYYYY' }); break; case 'onCallTerminate': console.log('Call terminated'); break; } }; // Initiate outgoing call request('callMake', { bnum: '79YYYYYYYYY' }); // Create conference request('confMake'); ``` -------------------------------- ### Convert Audio Files with sox Source: https://github.com/megafonapi/examples/blob/master/README.md Convert audio files between formats using the sox command-line utility. Ensure correct sample rate and channel count for A-law format. ```bash sox -V hello.ogg -r 8000 -c 1 -t al hello.pcm ``` ```bash sox -V -c 1 -r 8000 -t al 79286266488_56127.pcm 79286266488_56127.wav ``` -------------------------------- ### Handling MegafonAPI Platform Events in Python Source: https://context7.com/megafonapi/examples/llms.txt This code defines callback functions for various call events and an asynchronous main function to manage the call flow. It requires the asyncio library and a server endpoint URL. ```python def on_incoming(call_session, anum, bnum): global incomingCall, calls, newCall incomingCall = Call(call_session, anum, bnum) calls[call_session] = incomingCall newCall.set() # разблокируем main() def on_answered(call_session): calls[call_session].answered.set() def on_collected(call_session, dtmf): calls[call_session].trombonAgree.set() # абонент нажал клавишу def on_terminated(call_session, source, cause, message): calls[call_session].terminated.set() async def main(token, destination): global megafon, newCall, incomingCall, outgoingCall megafon = Server(endpoint_url + "/" + token) megafon.onCallIncoming = on_incoming megafon.onCallAnswer = on_answered megafon.onDTMFCollect = on_collected megafon.onCallTerminate = on_terminated newCall = asyncio.Event() await megafon.ws_connect() print("Ожидание входящего вызова...") await newCall.wait() # Ответить на входящий и воспроизвести приглашение (ожидание '#' до 100 сек) await megafon.callAnswer(call_session=incomingCall.session) await megafon.callFilePlay(call_session=incomingCall.session, filename='leather.pcm', dtmf_term='#', timeout=100) # Ждём согласия или отбоя входящего плеча t1 = asyncio.create_task(incomingCall.terminated.wait()) t2 = asyncio.create_task(incomingCall.trombonAgree.wait()) done, pending = await asyncio.wait([t1, t2], return_when=asyncio.FIRST_COMPLETED) for t in pending: t.cancel() if t1 in done: return # входящий сбросил # Инициируем исходящий вызов и ждём ответа resp = await megafon.callMake(bnum=destination) outgoingCall = Call(resp['data']['call_session'], None, destination) calls[outgoingCall.session] = outgoingCall await outgoingCall.answered.wait() # Воспроизводим приглашение в исходящее плечо, ждём согласия await megafon.callFilePlay(call_session=outgoingCall.session, filename='leather.pcm', dtmf_term='#', timeout=100) await outgoingCall.trombonAgree.wait() # Объединяем оба плеча в один разговор await megafon.callTrombone(a_session=incomingCall.session, b_session=outgoingCall.session) print("Тромбон установлен. Абоненты разговаривают.") # Ждём завершения любого из плечей await asyncio.wait([ asyncio.create_task(incomingCall.terminated.wait()), asyncio.create_task(outgoingCall.terminated.wait()) ], return_when=asyncio.FIRST_COMPLETED) await megafon.close() await megafon.session.close() asyncio.get_event_loop().run_until_complete(main(sys.argv[1], sys.argv[2])) ``` -------------------------------- ### Make Conference Calls with Python Source: https://context7.com/megafonapi/examples/llms.txt Initiates conference calls to a list of destinations using a JWT token. Handles call answering, DTMF input, and call termination events. Requires a JWT token and a file containing destination phone numbers. ```python import sys, asyncio from jsonrpc_websocket import Server endpoint_url = 'wss://testapi.megafon.ru/v1/api' megafon = confId = mute_session = None activeCalls = {} mute_number = None # установите номер для глушения, напр. '79281234567' class Call: def __init__(self, session, bnum): self.session = session self.bnumber = bnum self.answered = asyncio.Event() self.agreed = asyncio.Event() self.terminated = asyncio.Event() def call_answered(call_session): activeCalls[call_session].answered.set() asyncio.get_event_loop().create_task(play_invite(call_session)) def on_dtmf(call_session, dtmf): activeCalls[call_session].agreed.set() asyncio.get_event_loop().create_task(enter_conf(call_session)) def no_digits(call_session): # Время ожидания DTMF истекло — принудительно завершить вызов asyncio.get_event_loop().create_task(megafon.callTerminate(call_session=call_session)) def call_terminated(call_session, source, cause, message): activeCalls[call_session].terminated.set() async def play_invite(call_session): await megafon.callFilePlay(call_session=call_session, filename='conference.pcm', timeout=100, dtmf_term="#") async def enter_conf(call_session): await megafon.confAdd(call_session=call_session, conf_session=confId) async def monitor_conf(conference_id): await megafon.confRecordingStart(conf_session=conference_id) m = 0 while True: resp = await megafon.confStatusGet(conf_session=conference_id) dur = int(resp['data']['duration']) print(f"Конференция: {dur} сек, {resp['data']['participant_count']} участников") # Глушим участника между 20 и 30 секундами if mute_session and 20 <= dur <= 30: if m == 0: await megafon.confConfereeMute(call_session=mute_session) m = 1 elif m == 1: await megafon.confConfereeUnmute(call_session=mute_session) m = 0 await asyncio.sleep(5) async def make_call(dest): global mute_session resp = await megafon.callMake(bnum=dest) session = resp['data']['call_session'] if dest == mute_number: mute_session = session activeCalls[session] = Call(session, dest) return session async def main(token, destinations_file): global megafon, confId megafon = Server(endpoint_url + "/" + token) megafon.onCallAnswer = call_answered megafon.onCallFilePlay = no_digits # таймаут DTMF megafon.onDTMFCollect = on_dtmf megafon.onCallTerminate = call_terminated await megafon.ws_connect() # Создаём конференцию resp = await megafon.confMake() confId = resp['data']['conf_session'] print(f"Конференция {confId} создана") # Мониторинг состояния в фоне status_task = asyncio.get_event_loop().create_task(monitor_conf(confId)) # Параллельный обзвон всех номеров dest_list = [l.rstrip('\n') for l in open(destinations_file)] sessions = await asyncio.gather(*(make_call(d) for d in dest_list)) # Ожидаем завершения всех вызовов await asyncio.gather(*(activeCalls[s].terminated.wait() for s in sessions)) await megafon.confRecordingStop(conf_session=confId) status_task.cancel() await megafon.close() await megafon.session.close() asyncio.get_event_loop().run_until_complete(main(sys.argv[1], sys.argv[2])) ``` -------------------------------- ### Handle Incoming Call and "Trombone" (Python) Source: https://context7.com/megafonapi/examples/llms.txt This Python script waits for an incoming call, answers it, plays a greeting, initiates an outgoing call, and bridges both calls together. It requires JWT token and destination number as arguments, and specific libraries. ```python # Запуск: ./incoming.py <номер_назначения> # pip install jsonrpc-websocket aiohttp import sys, asyncio from jsonrpc_websocket import Server endpoint_url = 'wss://testapi.megafon.ru/v1/api' megafon = None calls = {} incomingCall = None outgoingCall = None newCall = None class Call: def __init__(self, session, anum, bnum): self.session = session self.anumber = anum self.bnumber = bnum self.answered = asyncio.Event() self.trombonAgree = asyncio.Event() self.terminated = asyncio.Event() ``` -------------------------------- ### Megafon.API JavaScript Integration Source: https://github.com/megafonapi/examples/blob/master/javascript/index.html This comprehensive JavaScript code handles the entire workflow: connecting to the API, managing authentication tokens, obtaining and managing API keys, updating account settings, establishing a WebSocket connection, and processing various call and conference events. It's intended for use in modern web browsers. ```javascript onload = () => { connection.onclick = () => { if (connection.innerText == 'Connect') { fetch('https://testapi.megafon.ru/api/rest/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ login:login.value, password:password.value }) }).then(response => { return response.json() }).then(json => { accessToken = json.data.accessToken return fetch('https://testapi.megafon.ru/api/rest/apiKeys', { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}` } }) }).then(response => { return response.json() }).then(json => { if (json.data[0]) { return Promise.resolve({ data: { apiKey: json.data[0] } }) } else { newApiKey = true return fetch('https://testapi.megafon.ru/api/rest/apiKeys', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}` } }) } }).then(response => { if (response.data && response.data.apiKey) { return Promise.resolve(response) } else { return response.json() } }).then(json => { apiKey = json.data.apiKey return fetch('https://testapi.megafon.ru/api/rest/account', { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}` } }) }).then(response => { return response.json() }).then(json => { savedYandexSpeechKitKey = json.data.yandexSpeechKitKey if (yandexSpeechKitKey.value) { return fetch('https://testapi.megafon.ru/api/rest/account', { method: 'PUT', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ yandexSpeechKitKey: yandexSpeechKitKey.value }) }) } else { return Promise.resolve() } }).then(() => { socket = new WebSocket(`wss://testapi.megafon.ru/v1/api/${apiKey}`) socket.onopen = () => { connection.innerText = 'Disconnect' actions.style.display = 'block' } socket.reqnum = 0 socket.request = (method, params) => { const data = { id: ++socket.reqnum, jsonrpc: '2.0', method: method, params: params } socket.send(JSON.stringify(data)) } socket.onmessage = (message) => { const data = JSON.parse(message.data) switch(data.method) { case 'onConfMake': conf_session = data.params.conf_session socket.request('confBroadcastConnect', { conf_session: conf_session, url: "shout://icecast.vgtrk.cdnvideo.ru/vestifm_mp3_64kbps" }) socket.request('callMake', { bnum: destination.value }) break case 'onCallIncoming': socket.request('callAnswer', { call_session: data.params.call_session }) break case 'onCallAnswer': socket.request('callFilePlay', { call_session: data.params.call_session, filename: 'welcome.pcm' }) break case 'onCallFilePlay': if (typeof conf_session === 'undefined') { if (yandexSpeechKitKey.value) { socket.request('callSTTStart', { call_session: data.params.call_session, engine: 'yandex' }) } else { socket.request('callTerminate', { call_session: data.params.call_session }) } } else { socket.request('confAdd', { conf_session: conf_session, call_session: data.params.call_session }) } break case 'onSTTFragment': sttMessages.innerText = `STT Fragments: ${data.params.message}` break case 'onCallTerminate': sttMessages.innerText = '' conf_session = undefined break } } socket.onclose = () => { connection.innerText = 'Connect' actions.style.display = 'none' } }) } else { socket.close() const cleanup = [] cleanup.push(fetch('https://testapi.megafon.ru/api/rest/account', { method: 'PUT', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ yandexSpeechKitKey: savedYandexSpeechKitKey }) })) if (typeof newApiKey === 'boolean' && newApiKey) { cleanup.push(fetch(`https://testapi.megafon.ru/api/rest/apiKeys/${apiKey}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${accessToken}` } })) } Promise.all(cleanup).then(() => { fetch('https://testapi.megafon.ru/api/rest/logout', { method: 'POST' }) }) } } call.onclick = () => { socket.request('callMake', { bnum: destination.value }) } conference.onclick = () => { if (typeof conf_session === 'undefined') { socket.request('confMake') } } } ```