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
@@ -1,302 +1,19 @@
1
1
  import asyncio
2
2
  import logging
3
- from typing import List
4
3
 
5
- from ntgcalls import ConnectionError
6
- from ntgcalls import ConnectionNotFound
7
- from ntgcalls import ConnectionState
8
- from ntgcalls import Frame as RawFrame
9
- from ntgcalls import MediaState
10
- from ntgcalls import NetworkInfo
11
- from ntgcalls import StreamDevice
12
- from ntgcalls import StreamMode
13
- from ntgcalls import StreamType
14
- from ntgcalls import TelegramServerError
15
-
16
- from ...exceptions import CallDeclined
17
- from ...exceptions import CallDiscarded
18
4
  from ...exceptions import PyTgCallsAlreadyRunning
19
- from ...mtproto import BridgedClient
20
5
  from ...pytgcalls_session import PyTgCallsSession
21
6
  from ...scaffold import Scaffold
22
- from ...types import CallData
23
- from ...types import ChatUpdate
24
- from ...types import Device
25
- from ...types import Direction
26
- from ...types import Frame
27
- from ...types import GroupCallParticipant
28
- from ...types import RawCallUpdate
29
- from ...types import StreamEnded
30
- from ...types import StreamFrames
31
- from ...types import Update
32
- from ...types import UpdatedGroupCallParticipant
33
7
 
34
8
  py_logger = logging.getLogger('pytgcalls')
35
9
 
36
10
 
37
11
  class Start(Scaffold):
38
12
  async def start(self):
39
-
40
- @self._app.on_update()
41
- async def update_handler(update: Update):
42
- chat_id = update.chat_id
43
- if update.chat_id in self._p2p_configs:
44
- p2p_config = self._p2p_configs[chat_id]
45
- if not p2p_config.wait_data.done():
46
- if isinstance(update, RawCallUpdate):
47
- if update.status & RawCallUpdate.Type.UPDATED_CALL:
48
- p2p_config.wait_data.set_result(
49
- update,
50
- )
51
- if isinstance(update, ChatUpdate) and \
52
- p2p_config.outgoing:
53
- if update.status & ChatUpdate.Status.DISCARDED_CALL:
54
- self._wait_connect.pop(chat_id, None)
55
- p2p_config.wait_data.set_exception(
56
- CallDeclined(
57
- chat_id,
58
- ),
59
- )
60
- if chat_id in self._wait_connect and \
61
- not self._wait_connect[chat_id].done():
62
- if isinstance(update, ChatUpdate):
63
- if update.status & ChatUpdate.Status.DISCARDED_CALL:
64
- self._wait_connect[chat_id].set_exception(
65
- CallDiscarded(
66
- chat_id,
67
- ),
68
- )
69
- if isinstance(update, RawCallUpdate):
70
- if update.status & RawCallUpdate.Type.REQUESTED:
71
- self._p2p_configs[chat_id] = CallData(
72
- await self._app.get_dhc(),
73
- self.loop,
74
- update.g_a_or_b,
75
- )
76
- update = ChatUpdate(
77
- chat_id,
78
- ChatUpdate.Status.INCOMING_CALL,
79
- )
80
- if isinstance(update, RawCallUpdate):
81
- if update.status & RawCallUpdate.Type.SIGNALING_DATA:
82
- try:
83
- await self._binding.send_signaling(
84
- update.chat_id,
85
- update.signaling_data,
86
- )
87
- except (ConnectionNotFound, ConnectionError):
88
- pass
89
- if isinstance(update, ChatUpdate):
90
- if update.status & ChatUpdate.Status.LEFT_CALL:
91
- await clear_call(chat_id)
92
- if isinstance(update, UpdatedGroupCallParticipant):
93
- participant = update.participant
94
- action = participant.action
95
- chat_peer = self._cache_user_peer.get(chat_id)
96
- user_id = participant.user_id
97
- if chat_id in self._call_sources:
98
- call_sources = self._call_sources[chat_id]
99
- was_camera = user_id in call_sources.camera
100
- was_screen = user_id in call_sources.presentation
101
-
102
- if was_camera != participant.video_camera:
103
- if participant.video_info:
104
- self._call_sources[chat_id].camera[
105
- user_id
106
- ] = participant.video_info.endpoint
107
- try:
108
- await self._binding.add_incoming_video(
109
- chat_id,
110
- participant.video_info.endpoint,
111
- participant.video_info.sources,
112
- )
113
- except (ConnectionNotFound, ConnectionError):
114
- pass
115
- elif user_id in self._call_sources[chat_id].camera:
116
- try:
117
- await self._binding.remove_incoming_video(
118
- chat_id,
119
- self._call_sources[
120
- chat_id
121
- ].camera[user_id],
122
- )
123
- except (ConnectionNotFound, ConnectionError):
124
- pass
125
- self._call_sources[chat_id].camera.pop(
126
- user_id, None,
127
- )
128
-
129
- if was_screen != participant.screen_sharing:
130
- if participant.presentation_info:
131
- self._call_sources[chat_id].presentation[
132
- user_id
133
- ] = participant.presentation_info.endpoint
134
- try:
135
- await self._binding.add_incoming_video(
136
- chat_id,
137
- participant.presentation_info.endpoint,
138
- participant.presentation_info.sources,
139
- )
140
- except (ConnectionNotFound, ConnectionError):
141
- pass
142
- elif user_id in self._call_sources[
143
- chat_id
144
- ].presentation:
145
- try:
146
- await self._binding.remove_incoming_video(
147
- chat_id,
148
- self._call_sources[
149
- chat_id
150
- ].presentation[user_id],
151
- )
152
- except (ConnectionNotFound, ConnectionError):
153
- pass
154
- self._call_sources[chat_id].presentation.pop(
155
- user_id, None,
156
- )
157
-
158
- if chat_peer:
159
- is_self = BridgedClient.chat_id(
160
- chat_peer,
161
- ) == participant.user_id if chat_peer else False
162
- if is_self:
163
- if action == GroupCallParticipant.Action.LEFT:
164
- if await clear_call(chat_id):
165
- await self._propagate(
166
- ChatUpdate(
167
- chat_id,
168
- ChatUpdate.Status.KICKED,
169
- ),
170
- self,
171
- )
172
- if (
173
- chat_id in self._need_unmute and
174
- action == GroupCallParticipant.Action.UPDATED and
175
- not participant.muted_by_admin
176
- ):
177
- try:
178
- await update_status(
179
- chat_id,
180
- await self._binding.get_state(chat_id),
181
- )
182
- except ConnectionNotFound:
183
- pass
184
-
185
- if (
186
- participant.muted_by_admin and
187
- action != GroupCallParticipant.Action.LEFT
188
- ):
189
- self._need_unmute.add(chat_id)
190
- else:
191
- self._need_unmute.discard(chat_id)
192
- if not isinstance(update, RawCallUpdate):
193
- await self._propagate(
194
- update,
195
- self,
196
- )
197
-
198
- async def clear_call(chat_id) -> bool:
199
- res = False
200
- try:
201
- await self._binding.stop(chat_id)
202
- res = True
203
- except ConnectionNotFound:
204
- pass
205
- await clear_cache(chat_id)
206
- return res
207
-
208
- async def update_status(chat_id: int, state: MediaState):
209
- try:
210
- await self._app.set_call_status(
211
- chat_id,
212
- state.muted,
213
- state.video_paused,
214
- state.video_stopped,
215
- state.presentation_paused,
216
- self._cache_user_peer.get(chat_id),
217
- )
218
- except Exception as e:
219
- py_logger.debug(f'SetVideoCallStatus: {e}')
220
-
221
- async def stream_ended(
222
- chat_id: int,
223
- stream_type: StreamType,
224
- device: StreamDevice,
225
- ):
226
- await self._propagate(
227
- StreamEnded(
228
- chat_id,
229
- StreamEnded.Type.from_raw(stream_type),
230
- Device.from_raw(device),
231
- ),
232
- self,
233
- )
234
-
235
- async def emit_sig_data(chat_id: int, data: bytes):
236
- try:
237
- await self._app.send_signaling(
238
- chat_id,
239
- data,
240
- )
241
- except (ConnectionError, ConnectionNotFound):
242
- pass
243
-
244
- async def stream_frame(
245
- chat_id: int,
246
- mode: StreamMode,
247
- device: StreamDevice,
248
- frames: List[RawFrame],
249
- ):
250
- await self._propagate(
251
- StreamFrames(
252
- chat_id,
253
- Direction.from_raw(mode),
254
- Device.from_raw(device),
255
- [
256
- Frame(
257
- x.ssrc,
258
- x.data,
259
- Frame.Info(
260
- x.frame_data.absolute_capture_timestamp_ms,
261
- x.frame_data.width,
262
- x.frame_data.height,
263
- x.frame_data.rotation,
264
- ),
265
- ) for x in frames
266
- ],
267
- ),
268
- self,
269
- )
270
-
271
- async def connection_changed(
272
- chat_id: int,
273
- net_state: NetworkInfo,
274
- ):
275
- state = net_state.state
276
- if state == ConnectionState.CONNECTING:
277
- return
278
- if chat_id in self._wait_connect:
279
- if state == ConnectionState.CONNECTED:
280
- self._wait_connect[chat_id].set_result(None)
281
- else:
282
- self._wait_connect[chat_id].set_exception(
283
- TelegramServerError(),
284
- )
285
- await clear_cache(chat_id)
286
-
287
- if state != ConnectionState.CONNECTED:
288
- if chat_id > 0:
289
- await self._app.discard_call(chat_id)
290
- await clear_cache(chat_id)
291
-
292
- async def clear_cache(chat_id: int):
293
- self._p2p_configs.pop(chat_id, None)
294
- self._cache_user_peer.pop(chat_id)
295
- self._need_unmute.discard(chat_id)
296
-
297
13
  if not self._is_running:
298
14
  self._is_running = True
299
15
  self._env_checker.check_environment()
16
+ self._app.add_handler(self._handle_mtproto_updates)
300
17
  if not self._app.is_connected:
301
18
  await self._app.start()
302
19
 
@@ -317,33 +34,33 @@ class Start(Scaffold):
317
34
  self._binding.on_stream_end(
318
35
  lambda chat_id, stream_type, device:
319
36
  asyncio.run_coroutine_threadsafe(
320
- stream_ended(chat_id, stream_type, device),
37
+ self._handle_stream_ended(chat_id, stream_type, device),
321
38
  self.loop,
322
39
  ),
323
40
  )
324
41
  self._binding.on_upgrade(
325
42
  lambda chat_id, state:
326
43
  asyncio.run_coroutine_threadsafe(
327
- update_status(chat_id, state),
44
+ self._update_status(chat_id, state),
328
45
  self.loop,
329
46
  ),
330
47
  )
331
48
  self._binding.on_connection_change(
332
49
  lambda chat_id, net_state: asyncio.run_coroutine_threadsafe(
333
- connection_changed(chat_id, net_state),
50
+ self._handle_connection_changed(chat_id, net_state),
334
51
  self.loop,
335
52
  ),
336
53
  )
337
54
  self._binding.on_signaling(
338
55
  lambda chat_id, data: asyncio.run_coroutine_threadsafe(
339
- emit_sig_data(chat_id, data),
56
+ self._emit_sig_data(chat_id, data),
340
57
  self.loop,
341
58
  ),
342
59
  )
343
60
  self._binding.on_frames(
344
61
  lambda chat_id, mode, device, frames:
345
62
  asyncio.run_coroutine_threadsafe(
346
- stream_frame(
63
+ self._handle_stream_frame(
347
64
  chat_id,
348
65
  mode,
349
66
  device,
@@ -352,6 +69,23 @@ class Start(Scaffold):
352
69
  self.loop,
353
70
  ),
354
71
  )
72
+ self._binding.on_request_broadcast_part(
73
+ lambda chat_id, part_request:
74
+ asyncio.run_coroutine_threadsafe(
75
+ self._request_broadcast_part(
76
+ chat_id,
77
+ part_request,
78
+ ),
79
+ self.loop,
80
+ ),
81
+ )
82
+ self._binding.on_request_broadcast_timestamp(
83
+ lambda chat_id:
84
+ asyncio.run_coroutine_threadsafe(
85
+ self._request_broadcast_timestamp(chat_id),
86
+ self.loop,
87
+ ),
88
+ )
355
89
  await PyTgCallsSession().start()
356
90
  else:
357
91
  raise PyTgCallsAlreadyRunning()
@@ -1,9 +1,9 @@
1
1
  import random
2
2
  from typing import Any
3
- from typing import Callable
4
3
  from typing import List
5
4
  from typing import Optional
6
5
 
6
+ from ntgcalls import MediaSegmentQuality
7
7
  from ntgcalls import Protocol
8
8
  from ntgcalls import RTCServer
9
9
  from ntgcalls import SsrcGroup
@@ -25,7 +25,7 @@ class BridgedClient(HandlersHolder):
25
25
  chat_id: int,
26
26
  json_join: str,
27
27
  invite_hash: str,
28
- have_video: bool,
28
+ video_stopped: bool,
29
29
  join_as: Any,
30
30
  ):
31
31
  pass
@@ -48,6 +48,7 @@ class BridgedClient(HandlersHolder):
48
48
  user_id: int,
49
49
  g_a_hash: bytes,
50
50
  protocol: Protocol,
51
+ has_video: bool,
51
52
  ):
52
53
  pass
53
54
 
@@ -108,6 +109,22 @@ class BridgedClient(HandlersHolder):
108
109
  ):
109
110
  pass
110
111
 
112
+ async def download_stream(
113
+ self,
114
+ chat_id: int,
115
+ timestamp: int,
116
+ limit: int,
117
+ video_channel: Optional[int],
118
+ video_quality: MediaSegmentQuality,
119
+ ):
120
+ pass
121
+
122
+ async def get_stream_timestamp(
123
+ self,
124
+ chat_id: int,
125
+ ):
126
+ pass
127
+
111
128
  async def set_call_status(
112
129
  self,
113
130
  chat_id: int,
@@ -232,6 +249,17 @@ class BridgedClient(HandlersHolder):
232
249
  for server in servers
233
250
  ]
234
251
 
252
+ @staticmethod
253
+ def parse_quality(quality: MediaSegmentQuality) -> Optional[int]:
254
+ if quality == MediaSegmentQuality.THUMBNAIL:
255
+ return 0
256
+ elif quality == MediaSegmentQuality.MEDIUM:
257
+ return 1
258
+ elif quality == MediaSegmentQuality.FULL:
259
+ return 2
260
+ else:
261
+ return None
262
+
235
263
  @staticmethod
236
264
  def rnd_id() -> int:
237
265
  return random.randint(0, 0x7FFFFFFF - 1)
@@ -239,12 +267,6 @@ class BridgedClient(HandlersHolder):
239
267
  async def get_dhc(self):
240
268
  pass
241
269
 
242
- def on_update(self) -> Callable:
243
- def decorator(func: Callable) -> Callable:
244
- return self.add_handler(func)
245
-
246
- return decorator
247
-
248
270
  async def get_id(self):
249
271
  pass
250
272
 
@@ -6,6 +6,7 @@ from typing import Union
6
6
 
7
7
  from hydrogram import Client
8
8
  from hydrogram import ContinuePropagation
9
+ from hydrogram.errors import FloodWait
9
10
  from hydrogram.raw.base import InputPeer
10
11
  from hydrogram.raw.base import InputUser
11
12
  from hydrogram.raw.functions.channels import GetFullChannel
@@ -17,6 +18,7 @@ from hydrogram.raw.functions.phone import CreateGroupCall
17
18
  from hydrogram.raw.functions.phone import DiscardCall
18
19
  from hydrogram.raw.functions.phone import EditGroupCallParticipant
19
20
  from hydrogram.raw.functions.phone import GetGroupCall
21
+ from hydrogram.raw.functions.phone import GetGroupCallStreamChannels
20
22
  from hydrogram.raw.functions.phone import GetGroupParticipants
21
23
  from hydrogram.raw.functions.phone import JoinGroupCall
22
24
  from hydrogram.raw.functions.phone import JoinGroupCallPresentation
@@ -24,6 +26,7 @@ from hydrogram.raw.functions.phone import LeaveGroupCall
24
26
  from hydrogram.raw.functions.phone import LeaveGroupCallPresentation
25
27
  from hydrogram.raw.functions.phone import RequestCall
26
28
  from hydrogram.raw.functions.phone import SendSignalingData
29
+ from hydrogram.raw.functions.upload import GetFile
27
30
  from hydrogram.raw.types import Channel
28
31
  from hydrogram.raw.types import ChannelForbidden
29
32
  from hydrogram.raw.types import Chat
@@ -33,6 +36,7 @@ from hydrogram.raw.types import GroupCall
33
36
  from hydrogram.raw.types import GroupCallDiscarded
34
37
  from hydrogram.raw.types import InputChannel
35
38
  from hydrogram.raw.types import InputGroupCall
39
+ from hydrogram.raw.types import InputGroupCallStream
36
40
  from hydrogram.raw.types import InputPeerChannel
37
41
  from hydrogram.raw.types import InputPhoneCall
38
42
  from hydrogram.raw.types import MessageActionChatDeleteUser
@@ -42,6 +46,7 @@ from hydrogram.raw.types import PeerChat
42
46
  from hydrogram.raw.types import PhoneCall
43
47
  from hydrogram.raw.types import PhoneCallAccepted
44
48
  from hydrogram.raw.types import PhoneCallDiscarded
49
+ from hydrogram.raw.types import PhoneCallDiscardReasonBusy
45
50
  from hydrogram.raw.types import PhoneCallDiscardReasonHangup
46
51
  from hydrogram.raw.types import PhoneCallDiscardReasonMissed
47
52
  from hydrogram.raw.types import PhoneCallProtocol
@@ -57,6 +62,7 @@ from hydrogram.raw.types import UpdatePhoneCall
57
62
  from hydrogram.raw.types import UpdatePhoneCallSignalingData
58
63
  from hydrogram.raw.types import Updates
59
64
  from hydrogram.raw.types.messages import DhConfig
65
+ from ntgcalls import MediaSegmentQuality
60
66
  from ntgcalls import Protocol
61
67
 
62
68
  from ..types import CallProtocol
@@ -129,10 +135,16 @@ class HydrogramClient(BridgedClient):
129
135
  self._cache.drop_phone_call(
130
136
  user_id,
131
137
  )
138
+ reason = ChatUpdate.Status.DISCARDED_CALL
139
+ if isinstance(
140
+ update.phone_call.reason,
141
+ PhoneCallDiscardReasonBusy,
142
+ ):
143
+ reason |= ChatUpdate.Status.BUSY_CALL
132
144
  await self._propagate(
133
145
  ChatUpdate(
134
146
  user_id,
135
- ChatUpdate.Status.DISCARDED_CALL,
147
+ reason,
136
148
  ),
137
149
  )
138
150
  if isinstance(update.phone_call, PhoneCallRequested):
@@ -388,7 +400,7 @@ class HydrogramClient(BridgedClient):
388
400
  chat_id: int,
389
401
  json_join: str,
390
402
  invite_hash: str,
391
- have_video: bool,
403
+ video_stopped: bool,
392
404
  join_as: InputPeer,
393
405
  ) -> str:
394
406
  chat_call = await self._cache.get_full_chat(chat_id)
@@ -399,7 +411,7 @@ class HydrogramClient(BridgedClient):
399
411
  params=DataJSON(data=json_join),
400
412
  muted=False,
401
413
  join_as=join_as,
402
- video_stopped=have_video,
414
+ video_stopped=video_stopped,
403
415
  invite_hash=invite_hash,
404
416
  ),
405
417
  )
@@ -455,14 +467,22 @@ class HydrogramClient(BridgedClient):
455
467
  user_id: int,
456
468
  g_a_hash: bytes,
457
469
  protocol: Protocol,
470
+ has_video: bool,
458
471
  ):
459
- await self._app.invoke(
472
+ update = await self._app.invoke(
460
473
  RequestCall(
461
474
  user_id=await self.resolve_peer(user_id),
462
475
  random_id=self.rnd_id(),
463
476
  g_a_hash=g_a_hash,
464
477
  protocol=self.parse_protocol(protocol),
465
- video=False,
478
+ video=has_video,
479
+ ),
480
+ )
481
+ self._cache.set_phone_call(
482
+ user_id,
483
+ InputPhoneCall(
484
+ id=update.phone_call.id,
485
+ access_hash=update.phone_call.access_hash,
466
486
  ),
467
487
  )
468
488
 
@@ -597,6 +617,58 @@ class HydrogramClient(BridgedClient):
597
617
  ),
598
618
  )
599
619
 
620
+ async def download_stream(
621
+ self,
622
+ chat_id: int,
623
+ timestamp: int,
624
+ limit: int,
625
+ video_channel: Optional[int],
626
+ video_quality: MediaSegmentQuality,
627
+ ):
628
+ chat_call = await self._cache.get_full_chat(chat_id)
629
+ if chat_call is not None:
630
+ try:
631
+ return (
632
+ await self._app.invoke(
633
+ GetFile(
634
+ location=InputGroupCallStream(
635
+ call=chat_call,
636
+ time_ms=timestamp,
637
+ scale=0,
638
+ video_channel=video_channel,
639
+ video_quality=BridgedClient.parse_quality(
640
+ video_quality,
641
+ ),
642
+ ),
643
+ offset=0,
644
+ limit=limit,
645
+ ),
646
+ sleep_threshold=0,
647
+ )
648
+ ).bytes
649
+ except FloodWait:
650
+ pass
651
+ return None
652
+
653
+ async def get_stream_timestamp(
654
+ self,
655
+ chat_id: int,
656
+ ):
657
+ chat_call = await self._cache.get_full_chat(chat_id)
658
+ if chat_call is not None:
659
+ # noinspection PyBroadException
660
+ channels = (
661
+ await self._app.invoke(
662
+ GetGroupCallStreamChannels(
663
+ call=chat_call,
664
+ ),
665
+ )
666
+ ).channels
667
+ if len(channels) > 0:
668
+ return channels[0].last_timestamp_ms
669
+
670
+ return 0
671
+
600
672
  async def set_call_status(
601
673
  self,
602
674
  chat_id: int,
@@ -3,6 +3,7 @@ from typing import Callable
3
3
  from typing import List
4
4
  from typing import Optional
5
5
 
6
+ from ntgcalls import MediaSegmentQuality
6
7
  from ntgcalls import Protocol
7
8
 
8
9
  from ..exceptions import InvalidMTProtoClient
@@ -55,7 +56,7 @@ class MtProtoClient:
55
56
  chat_id: int,
56
57
  json_join: str,
57
58
  invite_hash: str,
58
- have_video: bool,
59
+ video_stopped: bool,
59
60
  join_as: Any,
60
61
  ) -> str:
61
62
  if self._bind_client is not None:
@@ -63,7 +64,7 @@ class MtProtoClient:
63
64
  chat_id,
64
65
  json_join,
65
66
  invite_hash,
66
- have_video,
67
+ video_stopped,
67
68
  join_as,
68
69
  )
69
70
  else:
@@ -98,12 +99,14 @@ class MtProtoClient:
98
99
  user_id: int,
99
100
  g_a_hash: bytes,
100
101
  protocol: Protocol,
102
+ has_video: bool,
101
103
  ):
102
104
  if self._bind_client is not None:
103
105
  return await self._bind_client.request_call(
104
106
  user_id,
105
107
  g_a_hash,
106
108
  protocol,
109
+ has_video,
107
110
  )
108
111
  else:
109
112
  raise InvalidMTProtoClient()
@@ -209,6 +212,36 @@ class MtProtoClient:
209
212
  else:
210
213
  raise InvalidMTProtoClient()
211
214
 
215
+ async def download_stream(
216
+ self,
217
+ chat_id: int,
218
+ timestamp: int,
219
+ limit: int,
220
+ video_channel: Optional[int],
221
+ video_quality: MediaSegmentQuality,
222
+ ):
223
+ if self._bind_client is not None:
224
+ return await self._bind_client.download_stream(
225
+ chat_id,
226
+ timestamp,
227
+ limit,
228
+ video_channel,
229
+ video_quality,
230
+ )
231
+ else:
232
+ raise InvalidMTProtoClient()
233
+
234
+ async def get_stream_timestamp(
235
+ self,
236
+ chat_id: int,
237
+ ):
238
+ if self._bind_client is not None:
239
+ return await self._bind_client.get_stream_timestamp(
240
+ chat_id,
241
+ )
242
+ else:
243
+ raise InvalidMTProtoClient()
244
+
212
245
  async def set_call_status(
213
246
  self,
214
247
  chat_id: int,
@@ -279,7 +312,7 @@ class MtProtoClient:
279
312
  else:
280
313
  raise InvalidMTProtoClient()
281
314
 
282
- def on_update(self) -> Callable:
315
+ def add_handler(self, func: Callable):
283
316
  if self._bind_client is not None:
284
- return self._bind_client.on_update()
317
+ return self._bind_client.add_handler(func)
285
318
  raise InvalidMTProtoClient()