py-tgcalls 2.1.1__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 (38) hide show
  1. {py_tgcalls-2.1.1.dist-info → py_tgcalls-2.1.2b1.dist-info}/METADATA +2 -2
  2. {py_tgcalls-2.1.1.dist-info → py_tgcalls-2.1.2b1.dist-info}/RECORD +37 -23
  3. {py_tgcalls-2.1.1.dist-info → py_tgcalls-2.1.2b1.dist-info}/WHEEL +1 -1
  4. pytgcalls/__version__.py +1 -1
  5. pytgcalls/exceptions.py +0 -7
  6. pytgcalls/methods/__init__.py +2 -0
  7. pytgcalls/methods/internal/__init__.py +35 -0
  8. pytgcalls/methods/internal/clear_cache.py +8 -0
  9. pytgcalls/methods/internal/clear_call.py +20 -0
  10. pytgcalls/methods/internal/connect_call.py +133 -0
  11. pytgcalls/methods/internal/emit_sig_data.py +9 -0
  12. pytgcalls/methods/internal/handle_connection_changed.py +27 -0
  13. pytgcalls/methods/internal/handle_mtproto_updates.py +175 -0
  14. pytgcalls/methods/internal/handle_stream_ended.py +23 -0
  15. pytgcalls/methods/internal/handle_stream_frame.py +41 -0
  16. pytgcalls/methods/internal/join_presentation.py +58 -0
  17. pytgcalls/methods/internal/request_broadcast_part.py +42 -0
  18. pytgcalls/methods/internal/request_broadcast_timestamp.py +25 -0
  19. pytgcalls/methods/internal/switch_connection.py +35 -0
  20. pytgcalls/methods/internal/update_status.py +22 -0
  21. pytgcalls/methods/stream/play.py +6 -107
  22. pytgcalls/methods/stream/record.py +0 -6
  23. pytgcalls/methods/utilities/__init__.py +0 -6
  24. pytgcalls/methods/utilities/start.py +23 -296
  25. pytgcalls/mtproto/bridged_client.py +28 -7
  26. pytgcalls/mtproto/hydrogram_client.py +57 -0
  27. pytgcalls/mtproto/mtproto_client.py +33 -2
  28. pytgcalls/mtproto/pyrogram_client.py +61 -4
  29. pytgcalls/mtproto/telethon_client.py +57 -0
  30. pytgcalls/scaffold.py +79 -0
  31. pytgcalls/types/__init__.py +2 -0
  32. pytgcalls/types/calls/__init__.py +2 -0
  33. pytgcalls/types/calls/pending_connection.py +17 -0
  34. pytgcalls/methods/utilities/join_presentation.py +0 -50
  35. {py_tgcalls-2.1.1.dist-info → py_tgcalls-2.1.2b1.dist-info}/licenses/LICENSE +0 -0
  36. {py_tgcalls-2.1.1.dist-info → py_tgcalls-2.1.2b1.dist-info}/top_level.txt +0 -0
  37. /pytgcalls/methods/{utilities → internal}/log_retries.py +0 -0
  38. /pytgcalls/methods/{utilities → internal}/update_sources.py +0 -0
@@ -1,309 +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 CallBusy
17
- from ...exceptions import CallDeclined
18
- from ...exceptions import CallDiscarded
19
4
  from ...exceptions import PyTgCallsAlreadyRunning
20
- from ...mtproto import BridgedClient
21
5
  from ...pytgcalls_session import PyTgCallsSession
22
6
  from ...scaffold import Scaffold
23
- from ...types import CallData
24
- from ...types import ChatUpdate
25
- from ...types import Device
26
- from ...types import Direction
27
- from ...types import Frame
28
- from ...types import GroupCallParticipant
29
- from ...types import RawCallUpdate
30
- from ...types import StreamEnded
31
- from ...types import StreamFrames
32
- from ...types import Update
33
- from ...types import UpdatedGroupCallParticipant
34
7
 
35
8
  py_logger = logging.getLogger('pytgcalls')
36
9
 
37
10
 
38
11
  class Start(Scaffold):
39
12
  async def start(self):
40
-
41
- @self._app.on_update()
42
- async def update_handler(update: Update):
43
- chat_id = update.chat_id
44
- if update.chat_id in self._p2p_configs:
45
- p2p_config = self._p2p_configs[chat_id]
46
- if not p2p_config.wait_data.done():
47
- if isinstance(update, RawCallUpdate):
48
- if update.status & RawCallUpdate.Type.UPDATED_CALL:
49
- p2p_config.wait_data.set_result(
50
- update,
51
- )
52
- if isinstance(update, ChatUpdate) and \
53
- p2p_config.outgoing:
54
- if update.status & ChatUpdate.Status.DISCARDED_CALL:
55
- self._wait_connect.pop(chat_id, None)
56
- p2p_config.wait_data.set_exception(
57
- CallBusy(
58
- chat_id,
59
- ) if update.status &
60
- ChatUpdate.Status.BUSY_CALL else
61
- CallDeclined(
62
- chat_id,
63
- ),
64
- )
65
- if chat_id in self._wait_connect and \
66
- not self._wait_connect[chat_id].done():
67
- if isinstance(update, ChatUpdate):
68
- if update.status & ChatUpdate.Status.DISCARDED_CALL:
69
- self._wait_connect[chat_id].set_exception(
70
- CallDiscarded(
71
- chat_id,
72
- ),
73
- )
74
- if isinstance(update, RawCallUpdate):
75
- if update.status & RawCallUpdate.Type.REQUESTED:
76
- self._p2p_configs[chat_id] = CallData(
77
- await self._app.get_dhc(),
78
- self.loop,
79
- update.g_a_or_b,
80
- )
81
- update = ChatUpdate(
82
- chat_id,
83
- ChatUpdate.Status.INCOMING_CALL,
84
- )
85
- if isinstance(update, RawCallUpdate):
86
- if update.status & RawCallUpdate.Type.SIGNALING_DATA:
87
- try:
88
- await self._binding.send_signaling(
89
- update.chat_id,
90
- update.signaling_data,
91
- )
92
- except (ConnectionNotFound, ConnectionError):
93
- pass
94
- if isinstance(update, ChatUpdate):
95
- if update.status & ChatUpdate.Status.LEFT_CALL:
96
- await clear_call(chat_id)
97
- if isinstance(update, UpdatedGroupCallParticipant):
98
- participant = update.participant
99
- action = participant.action
100
- chat_peer = self._cache_user_peer.get(chat_id)
101
- user_id = participant.user_id
102
- if chat_id in self._call_sources:
103
- call_sources = self._call_sources[chat_id]
104
- was_camera = user_id in call_sources.camera
105
- was_screen = user_id in call_sources.presentation
106
-
107
- if was_camera != participant.video_camera:
108
- if participant.video_info:
109
- self._call_sources[chat_id].camera[
110
- user_id
111
- ] = participant.video_info.endpoint
112
- try:
113
- await self._binding.add_incoming_video(
114
- chat_id,
115
- participant.video_info.endpoint,
116
- participant.video_info.sources,
117
- )
118
- except (ConnectionNotFound, ConnectionError):
119
- pass
120
- elif user_id in self._call_sources[chat_id].camera:
121
- try:
122
- await self._binding.remove_incoming_video(
123
- chat_id,
124
- self._call_sources[
125
- chat_id
126
- ].camera[user_id],
127
- )
128
- except (ConnectionNotFound, ConnectionError):
129
- pass
130
- self._call_sources[chat_id].camera.pop(
131
- user_id, None,
132
- )
133
-
134
- if was_screen != participant.screen_sharing:
135
- if participant.presentation_info:
136
- self._call_sources[chat_id].presentation[
137
- user_id
138
- ] = participant.presentation_info.endpoint
139
- try:
140
- await self._binding.add_incoming_video(
141
- chat_id,
142
- participant.presentation_info.endpoint,
143
- participant.presentation_info.sources,
144
- )
145
- except (ConnectionNotFound, ConnectionError):
146
- pass
147
- elif user_id in self._call_sources[
148
- chat_id
149
- ].presentation:
150
- try:
151
- await self._binding.remove_incoming_video(
152
- chat_id,
153
- self._call_sources[
154
- chat_id
155
- ].presentation[user_id],
156
- )
157
- except (ConnectionNotFound, ConnectionError):
158
- pass
159
- self._call_sources[chat_id].presentation.pop(
160
- user_id, None,
161
- )
162
-
163
- if chat_peer:
164
- is_self = BridgedClient.chat_id(
165
- chat_peer,
166
- ) == participant.user_id if chat_peer else False
167
- if is_self:
168
- if action == GroupCallParticipant.Action.LEFT:
169
- if await clear_call(chat_id):
170
- await self._propagate(
171
- ChatUpdate(
172
- chat_id,
173
- ChatUpdate.Status.KICKED,
174
- ),
175
- self,
176
- )
177
- if (
178
- chat_id in self._need_unmute and
179
- action == GroupCallParticipant.Action.UPDATED and
180
- not participant.muted_by_admin
181
- ):
182
- try:
183
- await update_status(
184
- chat_id,
185
- await self._binding.get_state(chat_id),
186
- )
187
- except ConnectionNotFound:
188
- pass
189
-
190
- if (
191
- participant.muted_by_admin and
192
- action != GroupCallParticipant.Action.LEFT
193
- ):
194
- self._need_unmute.add(chat_id)
195
- else:
196
- self._need_unmute.discard(chat_id)
197
- if not isinstance(update, RawCallUpdate):
198
- await self._propagate(
199
- update,
200
- self,
201
- )
202
-
203
- async def clear_call(chat_id) -> bool:
204
- res = False
205
- if chat_id in self._wait_connect:
206
- self._wait_connect[chat_id].set_exception(
207
- TelegramServerError(),
208
- )
209
- try:
210
- await self._binding.stop(chat_id)
211
- res = True
212
- except ConnectionNotFound:
213
- pass
214
- await clear_cache(chat_id)
215
- return res
216
-
217
- async def update_status(chat_id: int, state: MediaState):
218
- try:
219
- await self._app.set_call_status(
220
- chat_id,
221
- state.muted,
222
- state.video_paused,
223
- state.video_stopped,
224
- state.presentation_paused,
225
- self._cache_user_peer.get(chat_id),
226
- )
227
- except Exception as e:
228
- py_logger.debug(f'SetVideoCallStatus: {e}')
229
-
230
- async def stream_ended(
231
- chat_id: int,
232
- stream_type: StreamType,
233
- device: StreamDevice,
234
- ):
235
- await self._propagate(
236
- StreamEnded(
237
- chat_id,
238
- StreamEnded.Type.from_raw(stream_type),
239
- Device.from_raw(device),
240
- ),
241
- self,
242
- )
243
-
244
- async def emit_sig_data(chat_id: int, data: bytes):
245
- try:
246
- await self._app.send_signaling(
247
- chat_id,
248
- data,
249
- )
250
- except (ConnectionError, ConnectionNotFound):
251
- pass
252
-
253
- async def stream_frame(
254
- chat_id: int,
255
- mode: StreamMode,
256
- device: StreamDevice,
257
- frames: List[RawFrame],
258
- ):
259
- await self._propagate(
260
- StreamFrames(
261
- chat_id,
262
- Direction.from_raw(mode),
263
- Device.from_raw(device),
264
- [
265
- Frame(
266
- x.ssrc,
267
- x.data,
268
- Frame.Info(
269
- x.frame_data.absolute_capture_timestamp_ms,
270
- x.frame_data.width,
271
- x.frame_data.height,
272
- x.frame_data.rotation,
273
- ),
274
- ) for x in frames
275
- ],
276
- ),
277
- self,
278
- )
279
-
280
- async def connection_changed(
281
- chat_id: int,
282
- net_state: NetworkInfo,
283
- ):
284
- state = net_state.state
285
- if state == ConnectionState.CONNECTING:
286
- return
287
- if chat_id in self._wait_connect:
288
- if state == ConnectionState.CONNECTED:
289
- self._wait_connect[chat_id].set_result(None)
290
- else:
291
- self._wait_connect[chat_id].set_exception(
292
- TelegramServerError(),
293
- )
294
- elif state != ConnectionState.CONNECTED:
295
- if chat_id > 0:
296
- await self._app.discard_call(chat_id)
297
- await clear_cache(chat_id)
298
-
299
- async def clear_cache(chat_id: int):
300
- self._p2p_configs.pop(chat_id, None)
301
- self._cache_user_peer.pop(chat_id)
302
- self._need_unmute.discard(chat_id)
303
-
304
13
  if not self._is_running:
305
14
  self._is_running = True
306
15
  self._env_checker.check_environment()
16
+ self._app.add_handler(self._handle_mtproto_updates)
307
17
  if not self._app.is_connected:
308
18
  await self._app.start()
309
19
 
@@ -324,33 +34,33 @@ class Start(Scaffold):
324
34
  self._binding.on_stream_end(
325
35
  lambda chat_id, stream_type, device:
326
36
  asyncio.run_coroutine_threadsafe(
327
- stream_ended(chat_id, stream_type, device),
37
+ self._handle_stream_ended(chat_id, stream_type, device),
328
38
  self.loop,
329
39
  ),
330
40
  )
331
41
  self._binding.on_upgrade(
332
42
  lambda chat_id, state:
333
43
  asyncio.run_coroutine_threadsafe(
334
- update_status(chat_id, state),
44
+ self._update_status(chat_id, state),
335
45
  self.loop,
336
46
  ),
337
47
  )
338
48
  self._binding.on_connection_change(
339
49
  lambda chat_id, net_state: asyncio.run_coroutine_threadsafe(
340
- connection_changed(chat_id, net_state),
50
+ self._handle_connection_changed(chat_id, net_state),
341
51
  self.loop,
342
52
  ),
343
53
  )
344
54
  self._binding.on_signaling(
345
55
  lambda chat_id, data: asyncio.run_coroutine_threadsafe(
346
- emit_sig_data(chat_id, data),
56
+ self._emit_sig_data(chat_id, data),
347
57
  self.loop,
348
58
  ),
349
59
  )
350
60
  self._binding.on_frames(
351
61
  lambda chat_id, mode, device, frames:
352
62
  asyncio.run_coroutine_threadsafe(
353
- stream_frame(
63
+ self._handle_stream_frame(
354
64
  chat_id,
355
65
  mode,
356
66
  device,
@@ -359,6 +69,23 @@ class Start(Scaffold):
359
69
  self.loop,
360
70
  ),
361
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
+ )
362
89
  await PyTgCallsSession().start()
363
90
  else:
364
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
@@ -109,6 +109,22 @@ class BridgedClient(HandlersHolder):
109
109
  ):
110
110
  pass
111
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
+
112
128
  async def set_call_status(
113
129
  self,
114
130
  chat_id: int,
@@ -233,6 +249,17 @@ class BridgedClient(HandlersHolder):
233
249
  for server in servers
234
250
  ]
235
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
+
236
263
  @staticmethod
237
264
  def rnd_id() -> int:
238
265
  return random.randint(0, 0x7FFFFFFF - 1)
@@ -240,12 +267,6 @@ class BridgedClient(HandlersHolder):
240
267
  async def get_dhc(self):
241
268
  pass
242
269
 
243
- def on_update(self) -> Callable:
244
- def decorator(func: Callable) -> Callable:
245
- return self.add_handler(func)
246
-
247
- return decorator
248
-
249
270
  async def get_id(self):
250
271
  pass
251
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
@@ -58,6 +62,7 @@ from hydrogram.raw.types import UpdatePhoneCall
58
62
  from hydrogram.raw.types import UpdatePhoneCallSignalingData
59
63
  from hydrogram.raw.types import Updates
60
64
  from hydrogram.raw.types.messages import DhConfig
65
+ from ntgcalls import MediaSegmentQuality
61
66
  from ntgcalls import Protocol
62
67
 
63
68
  from ..types import CallProtocol
@@ -612,6 +617,58 @@ class HydrogramClient(BridgedClient):
612
617
  ),
613
618
  )
614
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
+
615
672
  async def set_call_status(
616
673
  self,
617
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
@@ -211,6 +212,36 @@ class MtProtoClient:
211
212
  else:
212
213
  raise InvalidMTProtoClient()
213
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
+
214
245
  async def set_call_status(
215
246
  self,
216
247
  chat_id: int,
@@ -281,7 +312,7 @@ class MtProtoClient:
281
312
  else:
282
313
  raise InvalidMTProtoClient()
283
314
 
284
- def on_update(self) -> Callable:
315
+ def add_handler(self, func: Callable):
285
316
  if self._bind_client is not None:
286
- return self._bind_client.on_update()
317
+ return self._bind_client.add_handler(func)
287
318
  raise InvalidMTProtoClient()
@@ -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
@@ -539,8 +544,8 @@ class PyrogramClient(BridgedClient):
539
544
  )
540
545
 
541
546
  async def create_group_call(
542
- self,
543
- chat_id: int,
547
+ self,
548
+ chat_id: int,
544
549
  ):
545
550
  result: Updates = await self._app.send(
546
551
  CreateGroupCall(
@@ -567,8 +572,8 @@ class PyrogramClient(BridgedClient):
567
572
  )
568
573
 
569
574
  async def leave_group_call(
570
- self,
571
- chat_id: int,
575
+ self,
576
+ chat_id: int,
572
577
  ):
573
578
  chat_call = await self._cache.get_full_chat(chat_id)
574
579
  if chat_call is not None:
@@ -620,6 +625,58 @@ class PyrogramClient(BridgedClient):
620
625
  ),
621
626
  )
622
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
+
623
680
  async def set_call_status(
624
681
  self,
625
682
  chat_id: int,