Skip to content

Commit f84f9ab

Browse files
authored
Merge pull request #93 from kennyromanov/feature/voip
SW-6181: Реализовать функцию VoIP
2 parents 494b62d + ea1b473 commit f84f9ab

15 files changed

Lines changed: 1712 additions & 9 deletions

File tree

‎.github/workflows/python-package.yml‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,10 @@ jobs:
2525
- name: Install dependencies
2626
run: |
2727
python -m pip install --upgrade pip
28-
pip install ruff pytest
29-
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28+
python -m pip install ruff pytest pytest-asyncio ".[voip]"
3029
- name: Lint with ruff
3130
run: |
32-
# stop the build if there are Python syntax errors or undefined names
33-
ruff check --output-format=github --select=E9,F63,F7,F82 --target-version=py37 .
34-
# default set of ruff rules with GitHub Annotations
35-
ruff check --output-format=github --target-version=py37 .
31+
ruff check --output-format=github --select=E4,E7,E9,F --target-version=py310 .
3632
- name: Test with pytest
3733
run: |
3834
pytest

‎.gitignore‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,4 @@ cython_debug/
157157
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158158
# and can be added to the global gitignore or merged into this file. For a more nuclear
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160-
#.idea/
160+
/.idea/

‎README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ authorize the account, go to your [cabinet](https://console.green-api.com/) and
4040
python -m pip install whatsapp-api-client-python
4141
```
4242

43+
VoIP calls require the optional dependencies:
44+
45+
```shell
46+
python -m pip install "whatsapp-api-client-python[voip]"
47+
```
48+
4349
## Import
4450

4551
```
@@ -302,6 +308,8 @@ asyncio.run(main())
302308
| Example of account methods asynchronously | [accountMethodsAsync.py](./examples/async/accountMethodsAsync.py) |
303309
| Example of getting last incoming and outgoing calls | [lastCalls.py](./examples/sync/lastCalls.py) |
304310
| Example of getting last calls asynchronously | [lastCallsAsync.py](./examples/async/lastCallsAsync.py) |
311+
| Outgoing headless VoIP call | [headless_call.py](./examples/async/voip/headless_call.py) |
312+
| Incoming headless VoIP call | [incoming_call.py](./examples/async/voip/incoming_call.py) |
305313
| Example of sending a message with link preview options | [sendMessageWithPreview.py](./examples/sync/sending/sendMessageWithPreview.py) |
306314
| Example of sending a message with link preview options asynchronously | [sendMessageWithPreviewAsync.py](./examples/async/sending/sendMessageWithPreviewAsync.py) |
307315
| Example of queues methods (counts, clear webhooks queue) | [queuesMethods.py](./examples/sync/queuesMethods.py) |

‎docs/README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ whatsapp-api-client-python - библиотека для интеграции с
3939
python -m pip install whatsapp-api-client-python
4040
```
4141

42+
Для VoIP нужны дополнительные зависимости:
43+
44+
```shell
45+
python -m pip install "whatsapp-api-client-python[voip]"
46+
```
47+
4248
## Импорт
4349

4450
```
@@ -303,6 +309,8 @@ asyncio.run(main())
303309
| Пример асинхронных методов аккаунта | [accountMethodsAsync.py](../examples/async/accountMethodsAsync.py) |
304310
| Пример получения журнала звонков | [lastCalls.py](../examples/sync/lastCalls.py) |
305311
| Пример асинхронного получения журнала звонков | [lastCallsAsync.py](../examples/async/lastCallsAsync.py) |
312+
| Исходящий VoIP-звонок без аудиоустройств | [headless_call.py](../examples/async/voip/headless_call.py) |
313+
| Входящий VoIP-звонок без аудиоустройств | [incoming_call.py](../examples/async/voip/incoming_call.py) |
306314
| Пример отправки сообщения с настройками превью ссылки | [sendMessageWithPreview.py](../examples/sync/sending/sendMessageWithPreview.py) |
307315
| Пример асинхронной отправки сообщения с превью ссылки | [sendMessageWithPreviewAsync.py](../examples/async/sending/sendMessageWithPreviewAsync.py) |
308316
| Пример методов очереди (счётчики, очистка вебхуков) | [queuesMethods.py](../examples/sync/queuesMethods.py) |
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Outgoing voice call using a WAV file and decoded audio frames, no audio devices."""
2+
3+
import asyncio
4+
import os
5+
import sys
6+
7+
from aiortc.contrib.media import MediaPlayer
8+
9+
from whatsapp_api_client_python.API import GreenAPI
10+
from whatsapp_api_client_python.tools.voip import CallAudio, FrameAudioSink
11+
12+
13+
async def make_audio() -> CallAudio:
14+
player = MediaPlayer(sys.argv[2])
15+
16+
if player.audio is None:
17+
raise ValueError("The file has no audio stream")
18+
19+
async def on_frame(frame):
20+
# Replace this with your application's frame consumer.
21+
print(f"received {frame.samples} samples")
22+
23+
sink = FrameAudioSink(on_frame)
24+
25+
async def close():
26+
await sink.closeAsync()
27+
player.audio.stop()
28+
29+
return CallAudio(player.audio, sink.attachAsync, close)
30+
31+
32+
async def main():
33+
api = GreenAPI(os.environ["GREEN_API_ID"], os.environ["GREEN_API_TOKEN"])
34+
calls = api.voip.connect(audio_factory=make_audio)
35+
ended = asyncio.Event()
36+
calls.on("incoming_call", lambda info: print("Incoming call:", info.wid))
37+
38+
def on_end(detail):
39+
print("Call ended:", detail)
40+
ended.set()
41+
42+
calls.on("end_call", on_end)
43+
calls.on("error", lambda detail: print("Call error:", detail))
44+
45+
try:
46+
await calls.openAsync(timeout=30)
47+
await api.voip.dialAsync(sys.argv[1])
48+
await calls.startAudioAsync()
49+
await ended.wait()
50+
finally:
51+
await calls.closeAsync()
52+
53+
54+
if __name__ == "__main__":
55+
if len(sys.argv) != 3:
56+
raise SystemExit("Usage: headless_call.py <phone> <audio.wav>")
57+
58+
asyncio.run(main())
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Answer an incoming voice call using a WAV file, without audio devices."""
2+
3+
import asyncio
4+
import os
5+
import sys
6+
7+
from aiortc.contrib.media import MediaPlayer
8+
9+
from whatsapp_api_client_python.API import GreenAPI
10+
from whatsapp_api_client_python.tools.voip import CallAudio, FrameAudioSink
11+
12+
13+
async def make_audio() -> CallAudio:
14+
player = MediaPlayer(sys.argv[1])
15+
16+
if player.audio is None:
17+
raise ValueError("The file has no audio stream")
18+
19+
async def on_frame(frame):
20+
# Replace this with your application's frame consumer.
21+
print(f"received {frame.samples} samples")
22+
23+
sink = FrameAudioSink(on_frame)
24+
25+
async def close():
26+
await sink.closeAsync()
27+
player.audio.stop()
28+
29+
return CallAudio(player.audio, sink.attachAsync, close)
30+
31+
32+
async def main():
33+
api = GreenAPI(os.environ["GREEN_API_ID"], os.environ["GREEN_API_TOKEN"])
34+
calls = api.voip.connect(audio_factory=make_audio)
35+
incoming = asyncio.Queue()
36+
ended = asyncio.Event()
37+
38+
calls.on("incoming_call", incoming.put_nowait)
39+
calls.on("end_call", lambda detail: ended.set())
40+
calls.on("error", lambda detail: print("Call error:", detail))
41+
42+
try:
43+
await calls.openAsync(timeout=30)
44+
45+
info = await incoming.get()
46+
47+
print("Incoming call:", info.wid)
48+
49+
if calls.state is None or calls.state.state != "inc-call":
50+
return
51+
52+
await api.voip.acceptAsync()
53+
await calls.startAudioAsync()
54+
await ended.wait()
55+
finally:
56+
await calls.closeAsync()
57+
58+
59+
if __name__ == "__main__":
60+
if len(sys.argv) != 2:
61+
raise SystemExit("Usage: incoming_call.py <audio.wav>")
62+
63+
asyncio.run(main())

‎setup.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name="whatsapp-api-client-python",
8-
version="0.0.54",
8+
version="0.0.55",
99
description=(
1010
"This library helps you easily create"
1111
" a Python application with WhatsApp API."
@@ -40,6 +40,7 @@
4040
"Creative Commons Attribution-NoDerivatives 4.0 International"
4141
" (CC BY-ND 4.0)"
4242
),
43-
install_requires=["requests-2.34.2", "aiofiles>=24.1.0", "aiogram>=3.28.2", "aiohttp>=3.13.5"],
43+
install_requires=["requests==2.34.2", "aiofiles>=24.1.0", "aiogram>=3.28.2", "aiohttp>=3.13.5"],
44+
extras_require={"voip": ["aiortc>=1.15,<2", "websockets>=16,<17"]},
4445
python_requires=">=3.10"
4546
)

0 commit comments

Comments
 (0)