py-tgcalls 2.0.0rc6__py3-none-any.whl → 2.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. py_tgcalls-2.0.2.dist-info/METADATA +297 -0
  2. {py_tgcalls-2.0.0rc6.dist-info → py_tgcalls-2.0.2.dist-info}/RECORD +36 -36
  3. {py_tgcalls-2.0.0rc6.dist-info → py_tgcalls-2.0.2.dist-info}/WHEEL +1 -1
  4. pytgcalls/__version__.py +1 -1
  5. pytgcalls/ffmpeg.py +6 -7
  6. pytgcalls/filters.py +14 -1
  7. pytgcalls/media_devices/media_devices.py +4 -2
  8. pytgcalls/methods/calls/get_participants.py +1 -1
  9. pytgcalls/methods/calls/leave_call.py +12 -4
  10. pytgcalls/methods/stream/play.py +1 -3
  11. pytgcalls/methods/utilities/call_holder.py +14 -11
  12. pytgcalls/methods/utilities/idle.py +2 -2
  13. pytgcalls/methods/utilities/start.py +40 -19
  14. pytgcalls/mtproto/client_cache.py +1 -1
  15. pytgcalls/mtproto/hydrogram_client.py +9 -7
  16. pytgcalls/mtproto/mtproto_client.py +1 -1
  17. pytgcalls/mtproto/pyrogram_client.py +9 -7
  18. pytgcalls/mtproto/telethon_client.py +9 -7
  19. pytgcalls/mutex.py +2 -29
  20. pytgcalls/pytgcalls.py +1 -0
  21. pytgcalls/scaffold.py +0 -1
  22. pytgcalls/types/__init__.py +3 -7
  23. pytgcalls/types/calls/call.py +1 -10
  24. pytgcalls/types/calls/call_data.py +5 -3
  25. pytgcalls/types/calls/raw_call_update.py +1 -1
  26. pytgcalls/types/{groups → chats}/__init__.py +0 -4
  27. pytgcalls/types/{groups → chats}/chat_update.py +1 -5
  28. pytgcalls/types/{groups → chats}/group_call_participant.py +14 -2
  29. pytgcalls/types/{groups → chats}/updated_group_call_participant.py +1 -1
  30. pytgcalls/types/dict.py +5 -0
  31. pytgcalls/types/flag.py +6 -0
  32. pytgcalls/types/participant_list.py +2 -2
  33. pytgcalls/types/stream/media_stream.py +1 -5
  34. pytgcalls/ytdlp.py +8 -1
  35. py_tgcalls-2.0.0rc6.dist-info/METADATA +0 -128
  36. pytgcalls/types/groups/joined_group_call_participant.py +0 -12
  37. pytgcalls/types/groups/left_group_call_participant.py +0 -12
  38. {py_tgcalls-2.0.0rc6.dist-info → py_tgcalls-2.0.2.dist-info}/LICENSE +0 -0
  39. {py_tgcalls-2.0.0rc6.dist-info → py_tgcalls-2.0.2.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,9 @@
1
1
  import asyncio
2
2
  import logging
3
3
 
4
+ from ntgcalls import ConnectionError
4
5
  from ntgcalls import ConnectionNotFound
5
6
  from ntgcalls import ConnectionState
6
- from ntgcalls import ConnectionError
7
7
  from ntgcalls import MediaState
8
8
  from ntgcalls import StreamType
9
9
  from ntgcalls import TelegramServerError
@@ -16,6 +16,7 @@ from ...pytgcalls_session import PyTgCallsSession
16
16
  from ...scaffold import Scaffold
17
17
  from ...types import CallData
18
18
  from ...types import ChatUpdate
19
+ from ...types import GroupCallParticipant
19
20
  from ...types import RawCallUpdate
20
21
  from ...types import StreamAudioEnded
21
22
  from ...types import StreamVideoEnded
@@ -32,15 +33,17 @@ class Start(Scaffold):
32
33
  async def update_handler(update: Update):
33
34
  chat_id = update.chat_id
34
35
  if update.chat_id in self._p2p_configs:
35
- if not self._p2p_configs[chat_id].wait_data.done():
36
+ p2p_config = self._p2p_configs[chat_id]
37
+ if not p2p_config.wait_data.done():
36
38
  if isinstance(update, RawCallUpdate):
37
39
  if update.status & RawCallUpdate.Type.UPDATED_CALL:
38
- self._p2p_configs[chat_id].wait_data.set_result(
40
+ p2p_config.wait_data.set_result(
39
41
  update,
40
42
  )
41
- if isinstance(update, ChatUpdate):
43
+ if isinstance(update, ChatUpdate) and \
44
+ p2p_config.outgoing:
42
45
  if update.status & ChatUpdate.Status.DISCARDED_CALL:
43
- self._p2p_configs[chat_id].wait_data.set_exception(
46
+ p2p_config.wait_data.set_exception(
44
47
  CallDeclined(
45
48
  chat_id,
46
49
  ),
@@ -67,27 +70,39 @@ class Start(Scaffold):
67
70
  )
68
71
  if isinstance(update, RawCallUpdate):
69
72
  if update.status & RawCallUpdate.Type.SIGNALING_DATA:
70
- await self._binding.send_signaling(
71
- update.chat_id,
72
- update.signaling_data,
73
- )
73
+ try:
74
+ await self._binding.send_signaling(
75
+ update.chat_id,
76
+ update.signaling_data,
77
+ )
78
+ except (ConnectionNotFound, ConnectionError):
79
+ pass
74
80
  if isinstance(update, ChatUpdate):
75
81
  if update.status & ChatUpdate.Status.LEFT_CALL:
76
82
  await clear_call(chat_id)
77
83
  if isinstance(update, UpdatedGroupCallParticipant):
78
84
  participant = update.participant
85
+ action = participant.action
79
86
  chat_peer = self._cache_user_peer.get(chat_id)
80
87
  if chat_peer:
81
88
  is_self = BridgedClient.chat_id(
82
89
  chat_peer,
83
90
  ) == participant.user_id if chat_peer else False
84
91
  if is_self:
85
- if participant.left:
86
- await clear_call(chat_id)
87
- if chat_id in self._need_unmute and \
88
- not participant.joined and \
89
- not participant.left and \
90
- not participant.muted_by_admin:
92
+ if action == GroupCallParticipant.Action.LEFT:
93
+ if await clear_call(chat_id):
94
+ await self.propagate(
95
+ ChatUpdate(
96
+ chat_id,
97
+ ChatUpdate.Status.KICKED,
98
+ ),
99
+ self,
100
+ )
101
+ if (
102
+ chat_id in self._need_unmute and
103
+ action == GroupCallParticipant.Action.UPDATED and
104
+ not participant.muted_by_admin
105
+ ):
91
106
  try:
92
107
  await update_status(
93
108
  chat_id,
@@ -96,7 +111,10 @@ class Start(Scaffold):
96
111
  except ConnectionNotFound:
97
112
  pass
98
113
 
99
- if participant.muted_by_admin and not participant.left:
114
+ if (
115
+ participant.muted_by_admin and
116
+ action != GroupCallParticipant.Action.LEFT
117
+ ):
100
118
  self._need_unmute.add(chat_id)
101
119
  else:
102
120
  self._need_unmute.discard(chat_id)
@@ -106,12 +124,15 @@ class Start(Scaffold):
106
124
  self,
107
125
  )
108
126
 
109
- async def clear_call(chat_id):
127
+ async def clear_call(chat_id) -> bool:
128
+ res = False
110
129
  try:
111
130
  await self._binding.stop(chat_id)
131
+ res = True
112
132
  except ConnectionNotFound:
113
133
  pass
114
134
  await clear_cache(chat_id)
135
+ return res
115
136
 
116
137
  async def update_status(chat_id: int, state: MediaState):
117
138
  try:
@@ -155,12 +176,12 @@ class Start(Scaffold):
155
176
  self._wait_connect[chat_id].set_exception(
156
177
  TelegramServerError(),
157
178
  )
158
- await clear_call(chat_id)
179
+ await clear_cache(chat_id)
159
180
 
160
181
  if state != ConnectionState.CONNECTED:
161
182
  if chat_id > 0:
162
183
  await self._app.discard_call(chat_id)
163
- await clear_call(chat_id)
184
+ await clear_cache(chat_id)
164
185
 
165
186
  async def clear_cache(chat_id: int):
166
187
  self._p2p_configs.pop(chat_id, None)
@@ -5,7 +5,7 @@ from typing import List
5
5
  from typing import Optional
6
6
 
7
7
  from ..types import Cache
8
- from ..types.groups import GroupCallParticipant
8
+ from ..types.chats import GroupCallParticipant
9
9
  from ..types.participant_list import ParticipantList
10
10
  from .bridged_client import BridgedClient
11
11
 
@@ -84,13 +84,15 @@ class HydrogramClient(BridgedClient):
84
84
  update,
85
85
  UpdatePhoneCallSignalingData,
86
86
  ):
87
- await self.propagate(
88
- RawCallUpdate(
89
- self._cache.get_user_id(update.phone_call_id),
90
- RawCallUpdate.Type.SIGNALING_DATA,
91
- signaling_data=update.data,
92
- ),
93
- )
87
+ user_id = self._cache.get_user_id(update.phone_call_id)
88
+ if user_id is not None:
89
+ await self.propagate(
90
+ RawCallUpdate(
91
+ user_id,
92
+ RawCallUpdate.Type.SIGNALING_DATA,
93
+ signaling_data=update.data,
94
+ ),
95
+ )
94
96
 
95
97
  if isinstance(
96
98
  update,
@@ -6,7 +6,7 @@ from typing import Optional
6
6
  from ntgcalls import Protocol
7
7
 
8
8
  from ..exceptions import InvalidMTProtoClient
9
- from ..types.groups import GroupCallParticipant
9
+ from ..types.chats import GroupCallParticipant
10
10
  from .bridged_client import BridgedClient
11
11
 
12
12
 
@@ -92,13 +92,15 @@ class PyrogramClient(BridgedClient):
92
92
  update,
93
93
  UpdatePhoneCallSignalingData,
94
94
  ):
95
- await self.propagate(
96
- RawCallUpdate(
97
- self._cache.get_user_id(update.phone_call_id),
98
- RawCallUpdate.Type.SIGNALING_DATA,
99
- signaling_data=update.data,
100
- ),
101
- )
95
+ user_id = self._cache.get_user_id(update.phone_call_id)
96
+ if user_id is not None:
97
+ await self.propagate(
98
+ RawCallUpdate(
99
+ user_id,
100
+ RawCallUpdate.Type.SIGNALING_DATA,
101
+ signaling_data=update.data,
102
+ ),
103
+ )
102
104
 
103
105
  if isinstance(
104
106
  update,
@@ -82,13 +82,15 @@ class TelethonClient(BridgedClient):
82
82
  update,
83
83
  UpdatePhoneCallSignalingData,
84
84
  ):
85
- await self.propagate(
86
- RawCallUpdate(
87
- self._cache.get_user_id(update.phone_call_id),
88
- RawCallUpdate.Type.SIGNALING_DATA,
89
- signaling_data=update.data,
90
- ),
91
- )
85
+ user_id = self._cache.get_user_id(update.phone_call_id)
86
+ if user_id is not None:
87
+ await self.propagate(
88
+ RawCallUpdate(
89
+ user_id,
90
+ RawCallUpdate.Type.SIGNALING_DATA,
91
+ signaling_data=update.data,
92
+ ),
93
+ )
92
94
 
93
95
  if isinstance(
94
96
  update,
pytgcalls/mutex.py CHANGED
@@ -1,37 +1,10 @@
1
- import asyncio
2
- import logging
3
1
  from functools import wraps
4
- from inspect import signature
5
- from typing import Optional
6
2
 
7
3
 
8
4
  def mutex(func):
9
- sig = signature(func)
10
-
11
5
  @wraps(func)
12
6
  async def async_wrapper(*args, **kwargs):
13
7
  self = args[0]
14
- bound = sig.bind(*args, **kwargs)
15
-
16
- if 'chat_id' in bound.arguments:
17
- chat_id: Optional[int] = None
18
- try:
19
- chat_id = await self.resolve_chat_id(
20
- bound.arguments['chat_id'],
21
- )
22
- except Exception as e:
23
- logging.debug(
24
- 'Error occurred while resolving chat_id. Reason: ' +
25
- str(e),
26
- )
27
- if chat_id is not None:
28
- if chat_id not in self._lock:
29
- self._lock[chat_id] = asyncio.Lock()
30
- async with self._lock[chat_id]:
31
- try:
32
- return await func(*args, **kwargs)
33
- finally:
34
- self._lock.pop(chat_id, None)
35
-
36
- return await func(*args, **kwargs)
8
+ async with self._lock:
9
+ return await func(*args, **kwargs)
37
10
  return async_wrapper
pytgcalls/pytgcalls.py CHANGED
@@ -41,6 +41,7 @@ class PyTgCalls(Methods, Scaffold):
41
41
  self._binding = NTgCalls()
42
42
  self.loop = asyncio.get_event_loop()
43
43
  self.workers = workers
44
+ self._lock = asyncio.Lock()
44
45
  self.executor = ThreadPoolExecutor(
45
46
  self.workers,
46
47
  thread_name_prefix='Handler',
pytgcalls/scaffold.py CHANGED
@@ -21,7 +21,6 @@ class Scaffold(HandlersHolder):
21
21
  self._binding = None
22
22
  self.loop = None
23
23
  self._need_unmute = set()
24
- self._lock = dict()
25
24
  self._p2p_configs = dict()
26
25
  self._wait_connect = dict()
27
26
 
@@ -6,11 +6,9 @@ from .calls import CallData
6
6
  from .calls import CallProtocol
7
7
  from .calls import GroupCallConfig
8
8
  from .calls import RawCallUpdate
9
- from .groups import ChatUpdate
10
- from .groups import GroupCallParticipant
11
- from .groups import JoinedGroupCallParticipant
12
- from .groups import LeftGroupCallParticipant
13
- from .groups import UpdatedGroupCallParticipant
9
+ from .chats import ChatUpdate
10
+ from .chats import GroupCallParticipant
11
+ from .chats import UpdatedGroupCallParticipant
14
12
  from .stream import AudioQuality
15
13
  from .stream import MediaStream
16
14
  from .stream import StreamAudioEnded
@@ -30,8 +28,6 @@ __all__ = (
30
28
  'RawCallUpdate',
31
29
  'GroupCallConfig',
32
30
  'GroupCallParticipant',
33
- 'JoinedGroupCallParticipant',
34
- 'LeftGroupCallParticipant',
35
31
  'MediaStream',
36
32
  'StreamAudioEnded',
37
33
  'StreamVideoEnded',
@@ -1,7 +1,7 @@
1
1
  from enum import auto
2
- from enum import Flag
3
2
 
4
3
  from ...types.py_object import PyObject
4
+ from ..flag import Flag
5
5
 
6
6
 
7
7
  class Call(PyObject):
@@ -10,24 +10,15 @@ class Call(PyObject):
10
10
  PAUSED = auto()
11
11
  IDLE = auto()
12
12
 
13
- def __repr__(self):
14
- cls_name = self.__class__.__name__
15
- return f'{cls_name}.{self.name}'
16
-
17
13
  class Type(Flag):
18
14
  GROUP = auto()
19
15
  PRIVATE = auto()
20
16
 
21
- def __repr__(self):
22
- cls_name = self.__class__.__name__
23
- return f'{cls_name}.{self.name}'
24
-
25
17
  def __init__(
26
18
  self,
27
19
  chat_id: int,
28
20
  status: Status,
29
21
  ):
30
- self.chat_id: int = chat_id
31
22
  self.call_type = Call.Type.GROUP \
32
23
  if chat_id < 0 else Call.Type.PRIVATE
33
24
  self.status = status
@@ -3,6 +3,8 @@ from asyncio import Future
3
3
  from typing import Any
4
4
  from typing import Optional
5
5
 
6
+ from ntgcalls import DhConfig
7
+
6
8
 
7
9
  class CallData:
8
10
  def __init__(
@@ -11,9 +13,9 @@ class CallData:
11
13
  loop: asyncio.AbstractEventLoop,
12
14
  g_a_hash: Optional[bytes] = None,
13
15
  ):
14
- self.g: bytes = dhc_config.g
15
- self.p: bytes = dhc_config.p
16
- self.random: bytes = dhc_config.random
16
+ self.dh_config = DhConfig(
17
+ dhc_config.g, dhc_config.p, dhc_config.random,
18
+ )
17
19
  self.g_a_or_b: Optional[bytes] = g_a_hash
18
20
  self.outgoing: bool = g_a_hash is None
19
21
  self.wait_data: Future = loop.create_future()
@@ -10,8 +10,8 @@ class RawCallUpdate(Update):
10
10
  ACCEPTED = auto()
11
11
  CONFIRMED = auto()
12
12
  REQUESTED = auto()
13
- UPDATED_CALL = ACCEPTED | CONFIRMED
14
13
  SIGNALING_DATA = auto()
14
+ UPDATED_CALL = ACCEPTED | CONFIRMED
15
15
 
16
16
  def __init__(
17
17
  self,
@@ -1,13 +1,9 @@
1
1
  from .chat_update import ChatUpdate
2
2
  from .group_call_participant import GroupCallParticipant
3
- from .joined_group_call_participant import JoinedGroupCallParticipant
4
- from .left_group_call_participant import LeftGroupCallParticipant
5
3
  from .updated_group_call_participant import UpdatedGroupCallParticipant
6
4
 
7
5
  __all__ = (
8
6
  'ChatUpdate',
9
7
  'GroupCallParticipant',
10
- 'JoinedGroupCallParticipant',
11
- 'LeftGroupCallParticipant',
12
8
  'UpdatedGroupCallParticipant',
13
9
  )
@@ -1,7 +1,7 @@
1
1
  from enum import auto
2
- from enum import Flag
3
2
  from typing import Any
4
3
 
4
+ from ..flag import Flag
5
5
  from ..update import Update
6
6
 
7
7
 
@@ -15,10 +15,6 @@ class ChatUpdate(Update):
15
15
  INCOMING_CALL = auto()
16
16
  LEFT_CALL = KICKED | LEFT_GROUP | CLOSED_VOICE_CHAT | DISCARDED_CALL
17
17
 
18
- def __repr__(self):
19
- cls_name = self.__class__.__name__
20
- return f'{cls_name}.{self.name}'
21
-
22
18
  def __init__(
23
19
  self,
24
20
  chat_id: int,
@@ -1,7 +1,15 @@
1
+ from enum import auto
2
+
1
3
  from ...types.py_object import PyObject
4
+ from ..flag import Flag
2
5
 
3
6
 
4
7
  class GroupCallParticipant(PyObject):
8
+ class Action(Flag):
9
+ JOINED = auto()
10
+ LEFT = auto()
11
+ UPDATED = auto()
12
+
5
13
  def __init__(
6
14
  self,
7
15
  user_id: int,
@@ -23,5 +31,9 @@ class GroupCallParticipant(PyObject):
23
31
  self.video_camera: bool = video_camera
24
32
  self.raised_hand: bool = raised_hand
25
33
  self.volume: int = volume
26
- self.joined: bool = joined
27
- self.left: bool = left
34
+ if joined:
35
+ self.action = self.Action.JOINED
36
+ elif left:
37
+ self.action = self.Action.LEFT
38
+ else:
39
+ self.action = self.Action.UPDATED
@@ -1,4 +1,4 @@
1
- from ..groups import GroupCallParticipant
1
+ from ..chats import GroupCallParticipant
2
2
  from ..update import Update
3
3
 
4
4
 
@@ -0,0 +1,5 @@
1
+ from ..types.py_object import PyObject
2
+
3
+
4
+ class Dict(dict, PyObject):
5
+ pass
@@ -0,0 +1,6 @@
1
+ from enum import Flag as _Flag
2
+
3
+
4
+ class Flag(_Flag):
5
+ def __repr__(self):
6
+ return f'{self.__class__.__name__}.{self.name}'
@@ -1,6 +1,6 @@
1
1
  from typing import Dict
2
2
 
3
- from ..types.groups import GroupCallParticipant
3
+ from ..types.chats import GroupCallParticipant
4
4
  from ..types.list import List
5
5
 
6
6
 
@@ -17,7 +17,7 @@ class ParticipantList:
17
17
  self,
18
18
  participant: GroupCallParticipant,
19
19
  ):
20
- if participant.left:
20
+ if participant.action == GroupCallParticipant.Action.LEFT:
21
21
  if participant.user_id in self._list_participants:
22
22
  del self._list_participants[participant.user_id]
23
23
  else:
@@ -1,5 +1,4 @@
1
1
  from enum import auto
2
- from enum import Flag
3
2
  from pathlib import Path
4
3
  from typing import Dict
5
4
  from typing import Optional
@@ -17,6 +16,7 @@ from ...media_devices import DeviceInfo
17
16
  from ...media_devices import ScreenInfo
18
17
  from ...statictypes import statictypes
19
18
  from ...ytdlp import YtDlp
19
+ from ..flag import Flag
20
20
  from ..raw.audio_parameters import AudioParameters
21
21
  from ..raw.audio_stream import AudioStream
22
22
  from ..raw.stream import Stream
@@ -33,10 +33,6 @@ class MediaStream(Stream):
33
33
  IGNORE = auto()
34
34
  NO_LATENCY = auto()
35
35
 
36
- def __repr__(self):
37
- cls_name = self.__class__.__name__
38
- return f'{cls_name}.{self.name}'
39
-
40
36
  @statictypes
41
37
  def __init__(
42
38
  self,
pytgcalls/ytdlp.py CHANGED
@@ -58,7 +58,14 @@ class YtDlp:
58
58
  stdout=asyncio.subprocess.PIPE,
59
59
  stderr=asyncio.subprocess.PIPE,
60
60
  )
61
- stdout, stderr = await proc.communicate()
61
+ try:
62
+ stdout, stderr = await asyncio.wait_for(
63
+ proc.communicate(),
64
+ 20,
65
+ )
66
+ except asyncio.TimeoutError:
67
+ proc.terminate()
68
+ raise YtDlpError('yt-dlp process timeout')
62
69
  if stderr:
63
70
  raise YtDlpError(stderr.decode())
64
71
  data = stdout.decode().strip().split('\n')
@@ -1,128 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: py-tgcalls
3
- Version: 2.0.0rc6
4
- Summary: UNKNOWN
5
- Home-page: https://github.com/pytgcalls/pytgcalls
6
- Author: Laky-64
7
- Author-email: iraci.matteo@gmail.com
8
- License: LGPL-3.0
9
- Platform: UNKNOWN
10
- Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3 :: Only
14
- Classifier: Programming Language :: Python :: 3.8
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: Implementation :: CPython
20
- Requires-Python: >=3.8
21
- Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
- Requires-Dist: aiohttp (>=3.9.3)
24
- Requires-Dist: deprecation
25
- Requires-Dist: ntgcalls (>=1.2.0.b6)
26
- Requires-Dist: psutil
27
- Requires-Dist: screeninfo
28
- Requires-Dist: setuptools
29
-
30
- <img src="https://raw.githubusercontent.com/pytgcalls/pytgcalls/master/.github/images/banner.png" alt="pytgcalls logo" />
31
- <p align="center">
32
- <b>A simple and elegant client that allows you to make group voice calls quickly and easily.</b>
33
- <br>
34
- <a href="https://github.com/pytgcalls/pytgcalls/tree/master/example">
35
- Examples
36
- </a>
37
-
38
- <a href="https://pytgcalls.github.io/">
39
- Documentation
40
- </a>
41
-
42
- <a href="https://pypi.org/project/py-tgcalls/">
43
- PyPi
44
- </a>
45
-
46
- <a href="https://t.me/pytgcallsnews">
47
- Channel
48
- </a>
49
-
50
- <a href="https://t.me/pytgcallschat">
51
- Chat
52
- </a>
53
- </p>
54
-
55
- # PyTgCalls [![PyPI](https://img.shields.io/pypi/v/py-tgcalls.svg?logo=python&logoColor=%23959DA5&label=pypi&labelColor=%23282f37)](https://pypi.org/project/py-tgcalls/) [![Downloads](https://pepy.tech/badge/py-tgcalls)](https://pepy.tech/project/py-tgcalls)
56
- This project allows making Telegram group call using MtProto and WebRTC, this is possible thanks to the power of [NTgCalls] library and [@evgeny-nadymov]
57
-
58
- #### Example Usage
59
- ```python
60
- from pytgcalls import PyTgCalls
61
- from pytgcalls import idle
62
- from pytgcalls.types import MediaStream
63
- ...
64
- chat_id = -1001185324811
65
- app = PyTgCalls(client)
66
- app.start()
67
- app.join_group_call(
68
- chat_id,
69
- MediaStream(
70
- 'http://docs.evostream.com/sample_content/assets/sintel1m720p.mp4',
71
- )
72
- )
73
- idle()
74
- ```
75
-
76
- ## Features
77
- - Prebuilt wheels for macOS, Linux and Windows.
78
- - Supporting all type of MTProto libraries: Pyrogram, Telethon and Hydrogram.
79
- - Work with voice chats in channels and chats.
80
- - Join as channels or chats.
81
- - Mute/unmute, pause/resume, stop/play, volume control and more...
82
-
83
- ## Requirements
84
- - Python 3.7 or higher.
85
- - An MTProto Client
86
- - A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys).
87
-
88
- ## How to install?
89
- Here's how to install the PyTgCalls lib, the commands are given below:
90
-
91
- ``` bash
92
- # With Git
93
- pip install git+https://github.com/pytgcalls/pytgcalls -U
94
-
95
- # With PyPi (Recommended)
96
- pip install py-tgcalls -U
97
- ```
98
-
99
- ## Key Contributors
100
- * <b><a href="https://github.com/Laky-64">@Laky-64</a> (DevOps Engineer, Software Architect):</b>
101
- * Played a crucial role in developing PyTgCalls being an ex developer of pyservercall and of tgcallsjs.
102
- * Automation with GitHub Actions
103
- * <b><a href="https://github.com/kuogi">@kuogi</a> (Senior UI/UX designer, Documenter):</b>
104
- * As a Senior UI/UX Designer, Kuogi has significantly improved the user interface of our documentation,
105
- making it more visually appealing and user-friendly.
106
- * Played a key role in writing and structuring our documentation, ensuring that it is clear,
107
- informative, and accessible to all users.
108
- * <b><a href="https://github.com/vrumger">@vrumger</a> (Senior Node.js Developer, Software Architect):</b>
109
- * Has made important fixes and enhancements to the WebRTC component of the library,
110
- improving its stability and performance.
111
- * Main developer of TgCallsJS
112
- * <b><a href="https://github.com/alemidev">@alemidev</a> (Senior Python Developer):</b>
113
- * Has made important fixes and enhancements to the async part of the library
114
-
115
- ## Junior Developers
116
- * <b><a href="https://github.com/TuriOG">@TuriOG</a> (Junior Python Developer):</b>
117
- * Currently working on integrating NTgCalls into <a href="//github.com/pytgcalls/pytgcalls">PyTgCalls</a>, an important step
118
- in expanding the functionality and usability of the library.
119
-
120
- ## Special Thanks
121
- * <b><a href="https://github.com/evgeny-nadymov">@evgeny-nadymov</a>:</b>
122
- A heartfelt thank you to Evgeny Nadymov for graciously allowing us to use their code from telegram-react.
123
- His contribution has been pivotal to the success of this project.
124
-
125
- [NTgCalls]: https://github.com/pytgcalls/ntgcalls
126
- [@evgeny-nadymov]: https://github.com/evgeny-nadymov/
127
-
128
-
@@ -1,12 +0,0 @@
1
- from ..groups import GroupCallParticipant
2
- from ..update import Update
3
-
4
-
5
- class JoinedGroupCallParticipant(Update):
6
- def __init__(
7
- self,
8
- chat_id: int,
9
- participant: GroupCallParticipant,
10
- ):
11
- super().__init__(chat_id)
12
- self.participant = participant