py-tgcalls 2.1.0rc7__py3-none-any.whl → 2.1.2b1__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 (46) hide show
  1. py_tgcalls-2.1.2b1.dist-info/METADATA +126 -0
  2. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info}/RECORD +44 -29
  3. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info}/WHEEL +1 -1
  4. pytgcalls/__version__.py +1 -1
  5. pytgcalls/exceptions.py +7 -31
  6. pytgcalls/methods/__init__.py +2 -0
  7. pytgcalls/methods/calls/leave_call.py +1 -1
  8. pytgcalls/methods/internal/__init__.py +35 -0
  9. pytgcalls/methods/internal/clear_cache.py +8 -0
  10. pytgcalls/methods/internal/clear_call.py +20 -0
  11. pytgcalls/methods/internal/connect_call.py +133 -0
  12. pytgcalls/methods/internal/emit_sig_data.py +9 -0
  13. pytgcalls/methods/internal/handle_connection_changed.py +27 -0
  14. pytgcalls/methods/internal/handle_mtproto_updates.py +175 -0
  15. pytgcalls/methods/internal/handle_stream_ended.py +23 -0
  16. pytgcalls/methods/internal/handle_stream_frame.py +41 -0
  17. pytgcalls/methods/internal/join_presentation.py +58 -0
  18. pytgcalls/methods/internal/request_broadcast_part.py +42 -0
  19. pytgcalls/methods/internal/request_broadcast_timestamp.py +25 -0
  20. pytgcalls/methods/internal/switch_connection.py +35 -0
  21. pytgcalls/methods/internal/update_status.py +22 -0
  22. pytgcalls/methods/stream/play.py +7 -106
  23. pytgcalls/methods/stream/record.py +0 -6
  24. pytgcalls/methods/utilities/__init__.py +0 -6
  25. pytgcalls/methods/utilities/start.py +23 -289
  26. pytgcalls/mtproto/bridged_client.py +30 -8
  27. pytgcalls/mtproto/hydrogram_client.py +77 -5
  28. pytgcalls/mtproto/mtproto_client.py +37 -4
  29. pytgcalls/mtproto/pyrogram_client.py +81 -9
  30. pytgcalls/mtproto/telethon_client.py +77 -5
  31. pytgcalls/mutex.py +13 -1
  32. pytgcalls/pytgcalls.py +3 -0
  33. pytgcalls/scaffold.py +79 -0
  34. pytgcalls/types/__init__.py +2 -0
  35. pytgcalls/types/calls/__init__.py +2 -0
  36. pytgcalls/types/calls/pending_connection.py +17 -0
  37. pytgcalls/types/chats/chat_update.py +8 -1
  38. pytgcalls/types/py_object.py +9 -10
  39. pytgcalls/types/stream/media_stream.py +1 -1
  40. pytgcalls/wait_counter_lock.py +20 -0
  41. py_tgcalls-2.1.0rc7.dist-info/METADATA +0 -292
  42. pytgcalls/methods/utilities/join_presentation.py +0 -50
  43. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info/licenses}/LICENSE +0 -0
  44. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info}/top_level.txt +0 -0
  45. /pytgcalls/methods/{utilities → internal}/log_retries.py +0 -0
  46. /pytgcalls/methods/{utilities → internal}/update_sources.py +0 -0
@@ -5,9 +5,11 @@ from typing import Optional
5
5
  from typing import Union
6
6
 
7
7
  import pyrogram
8
+ from ntgcalls import MediaSegmentQuality
8
9
  from ntgcalls import Protocol
9
10
  from pyrogram import Client
10
11
  from pyrogram import ContinuePropagation
12
+ from pyrogram.errors import FloodWait
11
13
  from pyrogram.raw.base import InputPeer
12
14
  from pyrogram.raw.base import InputUser
13
15
  from pyrogram.raw.functions.channels import GetFullChannel
@@ -19,6 +21,7 @@ from pyrogram.raw.functions.phone import CreateGroupCall
19
21
  from pyrogram.raw.functions.phone import DiscardCall
20
22
  from pyrogram.raw.functions.phone import EditGroupCallParticipant
21
23
  from pyrogram.raw.functions.phone import GetGroupCall
24
+ from pyrogram.raw.functions.phone import GetGroupCallStreamChannels
22
25
  from pyrogram.raw.functions.phone import GetGroupParticipants
23
26
  from pyrogram.raw.functions.phone import JoinGroupCall
24
27
  from pyrogram.raw.functions.phone import JoinGroupCallPresentation
@@ -26,6 +29,7 @@ from pyrogram.raw.functions.phone import LeaveGroupCall
26
29
  from pyrogram.raw.functions.phone import LeaveGroupCallPresentation
27
30
  from pyrogram.raw.functions.phone import RequestCall
28
31
  from pyrogram.raw.functions.phone import SendSignalingData
32
+ from pyrogram.raw.functions.upload import GetFile
29
33
  from pyrogram.raw.types import Channel
30
34
  from pyrogram.raw.types import ChannelForbidden
31
35
  from pyrogram.raw.types import Chat
@@ -35,6 +39,7 @@ from pyrogram.raw.types import GroupCall
35
39
  from pyrogram.raw.types import GroupCallDiscarded
36
40
  from pyrogram.raw.types import InputChannel
37
41
  from pyrogram.raw.types import InputGroupCall
42
+ from pyrogram.raw.types import InputGroupCallStream
38
43
  from pyrogram.raw.types import InputPeerChannel
39
44
  from pyrogram.raw.types import InputPhoneCall
40
45
  from pyrogram.raw.types import MessageActionChatDeleteUser
@@ -44,6 +49,7 @@ from pyrogram.raw.types import PeerChat
44
49
  from pyrogram.raw.types import PhoneCall
45
50
  from pyrogram.raw.types import PhoneCallAccepted
46
51
  from pyrogram.raw.types import PhoneCallDiscarded
52
+ from pyrogram.raw.types import PhoneCallDiscardReasonBusy
47
53
  from pyrogram.raw.types import PhoneCallDiscardReasonHangup
48
54
  from pyrogram.raw.types import PhoneCallDiscardReasonMissed
49
55
  from pyrogram.raw.types import PhoneCallProtocol
@@ -137,10 +143,16 @@ class PyrogramClient(BridgedClient):
137
143
  self._cache.drop_phone_call(
138
144
  user_id,
139
145
  )
146
+ reason = ChatUpdate.Status.DISCARDED_CALL
147
+ if isinstance(
148
+ update.phone_call.reason,
149
+ PhoneCallDiscardReasonBusy,
150
+ ):
151
+ reason |= ChatUpdate.Status.BUSY_CALL
140
152
  await self._propagate(
141
153
  ChatUpdate(
142
154
  user_id,
143
- ChatUpdate.Status.DISCARDED_CALL,
155
+ reason,
144
156
  ),
145
157
  )
146
158
  if isinstance(update.phone_call, PhoneCallRequested):
@@ -396,7 +408,7 @@ class PyrogramClient(BridgedClient):
396
408
  chat_id: int,
397
409
  json_join: str,
398
410
  invite_hash: str,
399
- have_video: bool,
411
+ video_stopped: bool,
400
412
  join_as: InputPeer,
401
413
  ) -> str:
402
414
  chat_call = await self._cache.get_full_chat(chat_id)
@@ -407,7 +419,7 @@ class PyrogramClient(BridgedClient):
407
419
  params=DataJSON(data=json_join),
408
420
  muted=False,
409
421
  join_as=join_as,
410
- video_stopped=have_video,
422
+ video_stopped=video_stopped,
411
423
  invite_hash=invite_hash,
412
424
  ),
413
425
  )
@@ -463,14 +475,22 @@ class PyrogramClient(BridgedClient):
463
475
  user_id: int,
464
476
  g_a_hash: bytes,
465
477
  protocol: Protocol,
478
+ has_video: bool,
466
479
  ):
467
- await self._app.invoke(
480
+ update = await self._app.invoke(
468
481
  RequestCall(
469
482
  user_id=await self.resolve_peer(user_id),
470
483
  random_id=self.rnd_id(),
471
484
  g_a_hash=g_a_hash,
472
485
  protocol=self.parse_protocol(protocol),
473
- video=False,
486
+ video=has_video,
487
+ ),
488
+ )
489
+ self._cache.set_phone_call(
490
+ user_id,
491
+ InputPhoneCall(
492
+ id=update.phone_call.id,
493
+ access_hash=update.phone_call.access_hash,
474
494
  ),
475
495
  )
476
496
 
@@ -524,8 +544,8 @@ class PyrogramClient(BridgedClient):
524
544
  )
525
545
 
526
546
  async def create_group_call(
527
- self,
528
- chat_id: int,
547
+ self,
548
+ chat_id: int,
529
549
  ):
530
550
  result: Updates = await self._app.send(
531
551
  CreateGroupCall(
@@ -552,8 +572,8 @@ class PyrogramClient(BridgedClient):
552
572
  )
553
573
 
554
574
  async def leave_group_call(
555
- self,
556
- chat_id: int,
575
+ self,
576
+ chat_id: int,
557
577
  ):
558
578
  chat_call = await self._cache.get_full_chat(chat_id)
559
579
  if chat_call is not None:
@@ -605,6 +625,58 @@ class PyrogramClient(BridgedClient):
605
625
  ),
606
626
  )
607
627
 
628
+ async def download_stream(
629
+ self,
630
+ chat_id: int,
631
+ timestamp: int,
632
+ limit: int,
633
+ video_channel: Optional[int],
634
+ video_quality: MediaSegmentQuality,
635
+ ):
636
+ chat_call = await self._cache.get_full_chat(chat_id)
637
+ if chat_call is not None:
638
+ try:
639
+ return (
640
+ await self._app.send(
641
+ GetFile(
642
+ location=InputGroupCallStream(
643
+ call=chat_call,
644
+ time_ms=timestamp,
645
+ scale=0,
646
+ video_channel=video_channel,
647
+ video_quality=BridgedClient.parse_quality(
648
+ video_quality,
649
+ ),
650
+ ),
651
+ offset=0,
652
+ limit=limit,
653
+ ),
654
+ sleep_threshold=0,
655
+ )
656
+ ).bytes
657
+ except FloodWait:
658
+ pass
659
+ return None
660
+
661
+ async def get_stream_timestamp(
662
+ self,
663
+ chat_id: int,
664
+ ):
665
+ chat_call = await self._cache.get_full_chat(chat_id)
666
+ if chat_call is not None:
667
+ # noinspection PyBroadException
668
+ channels = (
669
+ await self._app.send(
670
+ GetGroupCallStreamChannels(
671
+ call=chat_call,
672
+ ),
673
+ )
674
+ ).channels
675
+ if len(channels) > 0:
676
+ return channels[0].last_timestamp_ms
677
+
678
+ return 0
679
+
608
680
  async def set_call_status(
609
681
  self,
610
682
  chat_id: int,
@@ -3,9 +3,11 @@ from typing import List
3
3
  from typing import Optional
4
4
  from typing import Union
5
5
 
6
+ from ntgcalls import MediaSegmentQuality
6
7
  from ntgcalls import Protocol
7
8
  from telethon import TelegramClient
8
9
  from telethon.errors import ChannelPrivateError
10
+ from telethon.errors import FloodWaitError
9
11
  from telethon.events import Raw
10
12
  from telethon.tl.functions.channels import GetFullChannelRequest
11
13
  from telethon.tl.functions.messages import GetDhConfigRequest
@@ -16,6 +18,7 @@ from telethon.tl.functions.phone import CreateGroupCallRequest
16
18
  from telethon.tl.functions.phone import DiscardCallRequest
17
19
  from telethon.tl.functions.phone import EditGroupCallParticipantRequest
18
20
  from telethon.tl.functions.phone import GetGroupCallRequest
21
+ from telethon.tl.functions.phone import GetGroupCallStreamChannelsRequest
19
22
  from telethon.tl.functions.phone import GetGroupParticipantsRequest
20
23
  from telethon.tl.functions.phone import JoinGroupCallPresentationRequest
21
24
  from telethon.tl.functions.phone import JoinGroupCallRequest
@@ -23,12 +26,14 @@ from telethon.tl.functions.phone import LeaveGroupCallPresentationRequest
23
26
  from telethon.tl.functions.phone import LeaveGroupCallRequest
24
27
  from telethon.tl.functions.phone import RequestCallRequest
25
28
  from telethon.tl.functions.phone import SendSignalingDataRequest
29
+ from telethon.tl.functions.upload import GetFileRequest
26
30
  from telethon.tl.types import ChatForbidden
27
31
  from telethon.tl.types import DataJSON
28
32
  from telethon.tl.types import GroupCall
29
33
  from telethon.tl.types import GroupCallDiscarded
30
34
  from telethon.tl.types import InputChannel
31
35
  from telethon.tl.types import InputGroupCall
36
+ from telethon.tl.types import InputGroupCallStream
32
37
  from telethon.tl.types import InputPeerChannel
33
38
  from telethon.tl.types import InputPhoneCall
34
39
  from telethon.tl.types import MessageActionChatDeleteUser
@@ -39,6 +44,7 @@ from telethon.tl.types import PeerChat
39
44
  from telethon.tl.types import PhoneCall
40
45
  from telethon.tl.types import PhoneCallAccepted
41
46
  from telethon.tl.types import PhoneCallDiscarded
47
+ from telethon.tl.types import PhoneCallDiscardReasonBusy
42
48
  from telethon.tl.types import PhoneCallDiscardReasonHangup
43
49
  from telethon.tl.types import PhoneCallDiscardReasonMissed
44
50
  from telethon.tl.types import PhoneCallProtocol
@@ -128,10 +134,16 @@ class TelethonClient(BridgedClient):
128
134
  self._cache.drop_phone_call(
129
135
  user_id,
130
136
  )
137
+ reason = ChatUpdate.Status.DISCARDED_CALL
138
+ if isinstance(
139
+ update.phone_call.reason,
140
+ PhoneCallDiscardReasonBusy,
141
+ ):
142
+ reason |= ChatUpdate.Status.BUSY_CALL
131
143
  await self._propagate(
132
144
  ChatUpdate(
133
145
  user_id,
134
- ChatUpdate.Status.DISCARDED_CALL,
146
+ reason,
135
147
  ),
136
148
  )
137
149
  if isinstance(update.phone_call, PhoneCallRequested):
@@ -380,7 +392,7 @@ class TelethonClient(BridgedClient):
380
392
  chat_id: int,
381
393
  json_join: str,
382
394
  invite_hash: str,
383
- have_video: bool,
395
+ video_stopped: bool,
384
396
  join_as: TypeInputPeer,
385
397
  ) -> str:
386
398
  chat_call = await self._cache.get_full_chat(chat_id)
@@ -391,7 +403,7 @@ class TelethonClient(BridgedClient):
391
403
  params=DataJSON(data=json_join),
392
404
  muted=False,
393
405
  join_as=join_as,
394
- video_stopped=have_video,
406
+ video_stopped=video_stopped,
395
407
  invite_hash=invite_hash,
396
408
  ),
397
409
  )
@@ -447,14 +459,22 @@ class TelethonClient(BridgedClient):
447
459
  user_id: int,
448
460
  g_a_hash: bytes,
449
461
  protocol: Protocol,
462
+ has_video: bool,
450
463
  ):
451
- return await self._app(
464
+ update = await self._app(
452
465
  RequestCallRequest(
453
466
  user_id=await self.resolve_peer(user_id),
454
467
  random_id=self.rnd_id(),
455
468
  g_a_hash=g_a_hash,
456
469
  protocol=self.parse_protocol(protocol),
457
- video=False,
470
+ video=has_video,
471
+ ),
472
+ )
473
+ self._cache.set_phone_call(
474
+ user_id,
475
+ InputPhoneCall(
476
+ id=update.phone_call.id,
477
+ access_hash=update.phone_call.access_hash,
458
478
  ),
459
479
  )
460
480
 
@@ -589,6 +609,58 @@ class TelethonClient(BridgedClient):
589
609
  ),
590
610
  )
591
611
 
612
+ async def download_stream(
613
+ self,
614
+ chat_id: int,
615
+ timestamp: int,
616
+ limit: int,
617
+ video_channel: Optional[int],
618
+ video_quality: MediaSegmentQuality,
619
+ ):
620
+ chat_call = await self._cache.get_full_chat(chat_id)
621
+ if chat_call is not None:
622
+ try:
623
+ return (
624
+ await self._app(
625
+ GetFileRequest(
626
+ location=InputGroupCallStream(
627
+ call=chat_call,
628
+ time_ms=timestamp,
629
+ scale=0,
630
+ video_channel=video_channel,
631
+ video_quality=BridgedClient.parse_quality(
632
+ video_quality,
633
+ ),
634
+ ),
635
+ offset=0,
636
+ limit=limit,
637
+ ),
638
+ flood_sleep_threshold=0,
639
+ )
640
+ ).bytes
641
+ except FloodWaitError:
642
+ pass
643
+ return None
644
+
645
+ async def get_stream_timestamp(
646
+ self,
647
+ chat_id: int,
648
+ ):
649
+ chat_call = await self._cache.get_full_chat(chat_id)
650
+ if chat_call is not None:
651
+ # noinspection PyBroadException
652
+ channels = (
653
+ await self._app(
654
+ GetGroupCallStreamChannelsRequest(
655
+ call=chat_call,
656
+ ),
657
+ )
658
+ ).channels
659
+ if len(channels) > 0:
660
+ return channels[0].last_timestamp_ms
661
+
662
+ return 0
663
+
592
664
  async def set_call_status(
593
665
  self,
594
666
  chat_id: int,
pytgcalls/mutex.py CHANGED
@@ -1,10 +1,22 @@
1
1
  from functools import wraps
2
2
 
3
+ from .wait_counter_lock import WaitCounterLock
4
+
3
5
 
4
6
  def mutex(func):
5
7
  @wraps(func)
6
8
  async def async_wrapper(*args, **kwargs):
7
9
  self = args[0]
10
+ chat_id = await self.resolve_chat_id(args[1])
11
+ async with self._lock:
12
+ self._calls_lock[chat_id] = self._calls_lock.get(
13
+ chat_id,
14
+ ) or WaitCounterLock()
15
+ async with self._calls_lock[chat_id]:
16
+ result = await func(*args, **kwargs)
17
+
8
18
  async with self._lock:
9
- return await func(*args, **kwargs)
19
+ if not self._calls_lock[chat_id].waiters():
20
+ self._calls_lock.pop(chat_id, None)
21
+ return result
10
22
  return async_wrapper
pytgcalls/pytgcalls.py CHANGED
@@ -2,6 +2,7 @@ import asyncio
2
2
  import os
3
3
  from concurrent.futures import ThreadPoolExecutor
4
4
  from typing import Any
5
+ from typing import Dict
5
6
 
6
7
  from ntgcalls import NTgCalls
7
8
 
@@ -11,6 +12,7 @@ from .mtproto import MtProtoClient
11
12
  from .scaffold import Scaffold
12
13
  from .statictypes import statictypes
13
14
  from .types import Cache
15
+ from .wait_counter_lock import WaitCounterLock
14
16
 
15
17
 
16
18
  class PyTgCalls(Methods, Scaffold):
@@ -42,6 +44,7 @@ class PyTgCalls(Methods, Scaffold):
42
44
  self.loop = asyncio.get_event_loop()
43
45
  self.workers = workers
44
46
  self._lock = asyncio.Lock()
47
+ self._calls_lock: Dict[str, WaitCounterLock] = {}
45
48
  self.executor = ThreadPoolExecutor(
46
49
  self.workers,
47
50
  thread_name_prefix='Handler',
pytgcalls/scaffold.py CHANGED
@@ -1,6 +1,20 @@
1
+ from typing import List
2
+ from typing import Optional
1
3
  from typing import Union
2
4
 
5
+ from ntgcalls import Frame as RawFrame
6
+ from ntgcalls import MediaDescription
7
+ from ntgcalls import MediaState
8
+ from ntgcalls import NetworkInfo
9
+ from ntgcalls import SegmentPartRequest
10
+ from ntgcalls import StreamDevice
11
+ from ntgcalls import StreamMode
12
+ from ntgcalls import StreamType
13
+
3
14
  from .handlers import HandlersHolder
15
+ from .types import CallConfig
16
+ from .types import GroupCallConfig
17
+ from .types import Update
4
18
 
5
19
 
6
20
  class Scaffold(HandlersHolder):
@@ -25,6 +39,7 @@ class Scaffold(HandlersHolder):
25
39
  self._call_sources = dict()
26
40
  self._wait_connect = dict()
27
41
  self._presentations = set()
42
+ self._pending_connections = dict()
28
43
 
29
44
  def _handle_mtproto(self):
30
45
  pass
@@ -47,9 +62,73 @@ class Scaffold(HandlersHolder):
47
62
  async def _join_presentation(self, chat_id: Union[int, str], join: bool):
48
63
  pass
49
64
 
65
+ async def _clear_call(self, chat_id: int):
66
+ pass
67
+
68
+ async def _update_status(self, chat_id: int, state: MediaState):
69
+ pass
70
+
71
+ async def _switch_connection(self, chat_id: int):
72
+ pass
73
+
74
+ async def _handle_stream_ended(
75
+ self,
76
+ chat_id: int,
77
+ stream_type: StreamType,
78
+ device: StreamDevice,
79
+ ):
80
+ pass
81
+
82
+ async def _emit_sig_data(self, chat_id: int, data: bytes):
83
+ pass
84
+
85
+ async def _request_broadcast_timestamp(
86
+ self,
87
+ chat_id: int,
88
+ ):
89
+ pass
90
+
91
+ async def _request_broadcast_part(
92
+ self,
93
+ chat_id: int,
94
+ part_request: SegmentPartRequest,
95
+ ):
96
+ pass
97
+
98
+ async def _handle_stream_frame(
99
+ self,
100
+ chat_id: int,
101
+ mode: StreamMode,
102
+ device: StreamDevice,
103
+ frames: List[RawFrame],
104
+ ):
105
+ pass
106
+
107
+ async def _handle_connection_changed(
108
+ self,
109
+ chat_id: int,
110
+ net_state: NetworkInfo,
111
+ ):
112
+ pass
113
+
114
+ async def _handle_mtproto_updates(self, update: Update):
115
+ pass
116
+
117
+ async def _connect_call(
118
+ self,
119
+ chat_id: int,
120
+ media_description: MediaDescription,
121
+ config: Union[CallConfig, GroupCallConfig],
122
+ payload: Optional[str],
123
+ ):
124
+ pass
125
+
50
126
  @staticmethod
51
127
  def _log_retries(r: int):
52
128
  pass
53
129
 
130
+ def _clear_cache(self, chat_id: int):
131
+ pass
132
+
54
133
  def on_update(self, filters=None):
55
134
  pass
@@ -5,6 +5,7 @@ from .calls import CallConfig
5
5
  from .calls import CallData
6
6
  from .calls import CallProtocol
7
7
  from .calls import GroupCallConfig
8
+ from .calls import PendingConnection
8
9
  from .calls import RawCallUpdate
9
10
  from .chats import ChatUpdate
10
11
  from .chats import GroupCallParticipant
@@ -35,6 +36,7 @@ __all__ = (
35
36
  'CallProtocol',
36
37
  'CallData',
37
38
  'RawCallUpdate',
39
+ 'PendingConnection',
38
40
  'GroupCallConfig',
39
41
  'GroupCallParticipant',
40
42
  'RecordStream',
@@ -4,6 +4,7 @@ from .call_data import CallData
4
4
  from .call_protocol import CallProtocol
5
5
  from .call_sources import CallSources
6
6
  from .group_call_config import GroupCallConfig
7
+ from .pending_connection import PendingConnection
7
8
  from .raw_call_update import RawCallUpdate
8
9
 
9
10
  __all__ = (
@@ -13,5 +14,6 @@ __all__ = (
13
14
  'CallProtocol',
14
15
  'CallSources',
15
16
  'GroupCallConfig',
17
+ 'PendingConnection',
16
18
  'RawCallUpdate',
17
19
  )
@@ -0,0 +1,17 @@
1
+ from ntgcalls import MediaDescription
2
+
3
+ from .group_call_config import GroupCallConfig
4
+
5
+
6
+ class PendingConnection:
7
+ def __init__(
8
+ self,
9
+ media_description: MediaDescription,
10
+ config: GroupCallConfig,
11
+ payload: str,
12
+ presentation: bool,
13
+ ):
14
+ self.media_description = media_description
15
+ self.config = config
16
+ self.payload = payload
17
+ self.presentation = presentation
@@ -13,7 +13,14 @@ class ChatUpdate(Update):
13
13
  INVITED_VOICE_CHAT = auto()
14
14
  DISCARDED_CALL = auto()
15
15
  INCOMING_CALL = auto()
16
- LEFT_CALL = KICKED | LEFT_GROUP | CLOSED_VOICE_CHAT | DISCARDED_CALL
16
+ BUSY_CALL = auto()
17
+ LEFT_CALL = (
18
+ KICKED |
19
+ LEFT_GROUP |
20
+ CLOSED_VOICE_CHAT |
21
+ DISCARDED_CALL |
22
+ BUSY_CALL
23
+ )
17
24
 
18
25
  def __init__(
19
26
  self,
@@ -13,16 +13,15 @@ class PyObject:
13
13
  return repr(obj)
14
14
  if isinstance(obj, Enum):
15
15
  return repr(obj)
16
- if hasattr(obj, '__dict__'):
17
- return {
18
- '_': obj.__class__.__name__,
19
- **{
20
- attr: vars(obj)[attr]
21
- for attr in vars(obj)
22
- if not attr.startswith('_')
23
- },
24
- }
25
- return {}
16
+ return {
17
+ '_': obj.__class__.__name__,
18
+ **{
19
+ attr: getattr(obj, attr)
20
+ for attr in dir(obj)
21
+ if not attr.startswith('_') and
22
+ not callable(getattr(obj, attr)) and not attr == 'default'
23
+ },
24
+ }
26
25
 
27
26
  def __str__(self) -> str:
28
27
  return dumps(
@@ -47,7 +47,7 @@ class MediaStream(Stream):
47
47
  video_parameters: Union[
48
48
  VideoParameters,
49
49
  VideoQuality,
50
- ] = VideoQuality.SD_480p,
50
+ ] = VideoQuality.HD_720p,
51
51
  audio_path: Optional[
52
52
  Union[
53
53
  str, Path,
@@ -0,0 +1,20 @@
1
+ import asyncio
2
+
3
+
4
+ class WaitCounterLock:
5
+ def __init__(self):
6
+ self._lock = asyncio.Lock()
7
+ self._waiters = 0
8
+
9
+ def waiters(self):
10
+ return self._waiters
11
+
12
+ async def __aenter__(self):
13
+ self._waiters += 1
14
+ await self._lock.acquire()
15
+ self._waiters -= 1
16
+ return self
17
+
18
+ async def __aexit__(self, exc_type, exc, tb):
19
+ if self._lock.locked():
20
+ self._lock.release()