tgdata 0.0.0__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.
- tgdata/__init__.py +57 -0
- tgdata/connection_engine.py +316 -0
- tgdata/message_engine.py +299 -0
- tgdata/models.py +69 -0
- tgdata/progress.py +105 -0
- tgdata/smoke_tests/__init__.py +1 -0
- tgdata/smoke_tests/my_test.py +149 -0
- tgdata/smoke_tests/test_00_connection.py +286 -0
- tgdata/smoke_tests/test_01_list_group_chats.py +408 -0
- tgdata/smoke_tests/test_02_get_message_count.py +73 -0
- tgdata/smoke_tests/test_03_get_all_messages.py +69 -0
- tgdata/smoke_tests/test_04_get_all_messages_in_batches.py +98 -0
- tgdata/smoke_tests/test_06_advanced_features.py +253 -0
- tgdata/smoke_tests/test_10_polling.py +244 -0
- tgdata/tgdata.py +554 -0
- tgdata/utils.py +247 -0
- tgdata-0.0.0.dist-info/METADATA +309 -0
- tgdata-0.0.0.dist-info/RECORD +20 -0
- tgdata-0.0.0.dist-info/WHEEL +5 -0
- tgdata-0.0.0.dist-info/top_level.txt +1 -0
tgdata/tgdata.py
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Telegram Group Message Crawler.
|
|
3
|
+
Single class with all features, delegating to specialized engines.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Optional, List, Dict, Any, Union, Callable
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from .connection_engine import ConnectionEngine
|
|
14
|
+
from .message_engine import MessageEngine
|
|
15
|
+
from .models import GroupInfo
|
|
16
|
+
from .utils import (
|
|
17
|
+
format_message_for_display,
|
|
18
|
+
export_to_json,
|
|
19
|
+
export_to_csv,
|
|
20
|
+
filter_messages_by_sender,
|
|
21
|
+
filter_messages_by_content,
|
|
22
|
+
get_message_statistics,
|
|
23
|
+
save_profile_photos,
|
|
24
|
+
create_metrics_report
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
class TgData:
|
|
30
|
+
"""
|
|
31
|
+
Unified interface for Telegram group operations.
|
|
32
|
+
All features in one place, with clean delegation to specialized engines.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self,
|
|
36
|
+
config_path: str = "config.ini",
|
|
37
|
+
connection_pool_size: int = 1,
|
|
38
|
+
log_file: Optional[str] = None):
|
|
39
|
+
"""
|
|
40
|
+
Initialize Telegram group handler.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
config_path: Path to configuration file
|
|
44
|
+
connection_pool_size: Number of connections (1 = no pooling)
|
|
45
|
+
log_file: Optional log file path
|
|
46
|
+
"""
|
|
47
|
+
# Set up logging
|
|
48
|
+
if log_file:
|
|
49
|
+
file_handler = logging.FileHandler(log_file)
|
|
50
|
+
file_handler.setFormatter(
|
|
51
|
+
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
52
|
+
)
|
|
53
|
+
logger.addHandler(file_handler)
|
|
54
|
+
|
|
55
|
+
# Initialize engines
|
|
56
|
+
self.connection_engine = ConnectionEngine(
|
|
57
|
+
config_path=config_path,
|
|
58
|
+
pool_size=connection_pool_size
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.message_engine = MessageEngine(
|
|
62
|
+
connection_engine=self.connection_engine
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# State
|
|
66
|
+
self.current_group: Optional[GroupInfo] = None
|
|
67
|
+
self._metrics: Dict[str, Any] = {}
|
|
68
|
+
|
|
69
|
+
logger.info("TgData initialized")
|
|
70
|
+
|
|
71
|
+
# ==================== Group Management ====================
|
|
72
|
+
|
|
73
|
+
async def list_groups(self) -> pd.DataFrame:
|
|
74
|
+
"""
|
|
75
|
+
List all accessible groups/channels.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
DataFrame with group information
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
client = await self.connection_engine.get_client()
|
|
82
|
+
groups_data = []
|
|
83
|
+
|
|
84
|
+
async with client:
|
|
85
|
+
async for dialog in client.iter_dialogs():
|
|
86
|
+
if dialog.is_group or dialog.is_channel:
|
|
87
|
+
entity = dialog.entity
|
|
88
|
+
|
|
89
|
+
group_info = {
|
|
90
|
+
'GroupID': entity.id,
|
|
91
|
+
'Title': entity.title,
|
|
92
|
+
'Username': f"@{entity.username}" if hasattr(entity, 'username') and entity.username else None,
|
|
93
|
+
'IsChannel': dialog.is_channel,
|
|
94
|
+
'IsMegagroup': getattr(entity, 'megagroup', False),
|
|
95
|
+
'ParticipantsCount': getattr(entity, 'participants_count', None)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
groups_data.append(group_info)
|
|
99
|
+
|
|
100
|
+
df = pd.DataFrame(groups_data)
|
|
101
|
+
logger.info(f"Found {len(df)} groups/channels")
|
|
102
|
+
return df
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Error listing groups: {e}")
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
def set_group(self, group_id: int) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Set the current group for operations.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
group_id: Telegram group/channel ID
|
|
114
|
+
"""
|
|
115
|
+
# Clear cache for the OLD group before setting new one
|
|
116
|
+
if self.current_group:
|
|
117
|
+
self.message_engine.clear_cache(self.current_group.id)
|
|
118
|
+
|
|
119
|
+
self.current_group = GroupInfo(
|
|
120
|
+
id=group_id,
|
|
121
|
+
title="", # Will be populated on first operation
|
|
122
|
+
username=None
|
|
123
|
+
)
|
|
124
|
+
logger.info(f"Set current group to {group_id}")
|
|
125
|
+
|
|
126
|
+
# ==================== Message Operations ====================
|
|
127
|
+
|
|
128
|
+
async def get_messages(self,
|
|
129
|
+
group_id: Optional[int] = None,
|
|
130
|
+
limit: Optional[int] = None,
|
|
131
|
+
start_date: Optional[datetime] = None,
|
|
132
|
+
end_date: Optional[datetime] = None,
|
|
133
|
+
after_id: int = 0,
|
|
134
|
+
include_profile_photos: bool = False,
|
|
135
|
+
with_progress: bool = False,
|
|
136
|
+
progress_callback: Optional[Callable] = None) -> pd.DataFrame:
|
|
137
|
+
"""
|
|
138
|
+
Get messages from a group with various options.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
group_id: Target group ID (uses current if not specified)
|
|
142
|
+
limit: Maximum number of messages
|
|
143
|
+
start_date: Get messages after this date
|
|
144
|
+
end_date: Get messages before this date
|
|
145
|
+
after_id: Get messages after this message ID (for incremental extraction)
|
|
146
|
+
include_profile_photos: Whether to download profile photos
|
|
147
|
+
with_progress: Enable progress tracking
|
|
148
|
+
progress_callback: Optional callback for progress updates
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
DataFrame with messages
|
|
152
|
+
"""
|
|
153
|
+
# Use provided group_id or current
|
|
154
|
+
target_group_id = group_id or (self.current_group.id if self.current_group else None)
|
|
155
|
+
if not target_group_id:
|
|
156
|
+
raise ValueError("No group specified. Use set_group() or provide group_id")
|
|
157
|
+
|
|
158
|
+
# Set as current if different
|
|
159
|
+
if group_id and (not self.current_group or self.current_group.id != group_id):
|
|
160
|
+
self.set_group(group_id)
|
|
161
|
+
|
|
162
|
+
# Enable progress callback if requested
|
|
163
|
+
if with_progress and not progress_callback:
|
|
164
|
+
def default_progress(current, total, rate):
|
|
165
|
+
if total:
|
|
166
|
+
percent = (current / total) * 100
|
|
167
|
+
print(f"\rProgress: {current}/{total} ({percent:.1f}%) - {rate:.1f} msg/s", end="")
|
|
168
|
+
else:
|
|
169
|
+
print(f"\rProgress: {current} messages - {rate:.1f} msg/s", end="")
|
|
170
|
+
|
|
171
|
+
progress_callback = default_progress
|
|
172
|
+
|
|
173
|
+
# Fetch messages
|
|
174
|
+
df = await self.message_engine.fetch_messages(
|
|
175
|
+
group_id=target_group_id,
|
|
176
|
+
limit=limit,
|
|
177
|
+
start_date=start_date,
|
|
178
|
+
end_date=end_date,
|
|
179
|
+
min_id=after_id if after_id > 0 else None,
|
|
180
|
+
include_profile_photos=include_profile_photos,
|
|
181
|
+
progress_callback=progress_callback
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if with_progress:
|
|
185
|
+
print() # New line after progress
|
|
186
|
+
|
|
187
|
+
return df
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def get_message_count(self, group_id: Optional[int] = None) -> int:
|
|
191
|
+
"""
|
|
192
|
+
Get total message count for a group without fetching all messages.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
group_id: Target group ID (uses current if not specified)
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Total message count
|
|
199
|
+
"""
|
|
200
|
+
target_group_id = group_id or (self.current_group.id if self.current_group else None)
|
|
201
|
+
if not target_group_id:
|
|
202
|
+
raise ValueError("No group specified")
|
|
203
|
+
|
|
204
|
+
return await self.message_engine.get_message_count(target_group_id)
|
|
205
|
+
|
|
206
|
+
async def search_messages(self,
|
|
207
|
+
query: str,
|
|
208
|
+
group_id: Optional[int] = None,
|
|
209
|
+
limit: Optional[int] = None) -> pd.DataFrame:
|
|
210
|
+
"""
|
|
211
|
+
Search for messages containing specific text.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
query: Search query
|
|
215
|
+
group_id: Target group ID (uses current if not specified)
|
|
216
|
+
limit: Maximum number of results
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
DataFrame with matching messages
|
|
220
|
+
"""
|
|
221
|
+
target_group_id = group_id or (self.current_group.id if self.current_group else None)
|
|
222
|
+
if not target_group_id:
|
|
223
|
+
raise ValueError("No group specified")
|
|
224
|
+
|
|
225
|
+
return await self.message_engine.search_messages(
|
|
226
|
+
group_id=target_group_id,
|
|
227
|
+
query=query,
|
|
228
|
+
limit=limit
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# ==================== Message Display and Export ====================
|
|
232
|
+
|
|
233
|
+
def print_messages(self,
|
|
234
|
+
df: pd.DataFrame,
|
|
235
|
+
limit: Optional[int] = None,
|
|
236
|
+
max_length: int = 100) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Print messages in a readable format.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
df: DataFrame with messages
|
|
242
|
+
limit: Maximum number of messages to print
|
|
243
|
+
max_length: Maximum message length to display
|
|
244
|
+
"""
|
|
245
|
+
if df.empty:
|
|
246
|
+
print("No messages to display")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
messages_to_print = df.head(limit) if limit else df
|
|
250
|
+
|
|
251
|
+
print(f"\n{'='*80}")
|
|
252
|
+
print(f"Displaying {len(messages_to_print)} of {len(df)} messages")
|
|
253
|
+
print(f"{'='*80}\n")
|
|
254
|
+
|
|
255
|
+
for _, msg in messages_to_print.iterrows():
|
|
256
|
+
print(format_message_for_display(msg, max_length))
|
|
257
|
+
print(f"{'-'*80}")
|
|
258
|
+
|
|
259
|
+
def filter_messages(self,
|
|
260
|
+
df: pd.DataFrame,
|
|
261
|
+
sender_id: Optional[int] = None,
|
|
262
|
+
username: Optional[str] = None,
|
|
263
|
+
keyword: Optional[str] = None,
|
|
264
|
+
start_date: Optional[datetime] = None,
|
|
265
|
+
end_date: Optional[datetime] = None) -> pd.DataFrame:
|
|
266
|
+
"""
|
|
267
|
+
Filter messages by various criteria.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
df: Messages DataFrame
|
|
271
|
+
sender_id: Filter by sender ID
|
|
272
|
+
username: Filter by username
|
|
273
|
+
keyword: Filter by message content
|
|
274
|
+
start_date: Filter messages after this date
|
|
275
|
+
end_date: Filter messages before this date
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Filtered DataFrame
|
|
279
|
+
"""
|
|
280
|
+
# Apply filters
|
|
281
|
+
if sender_id or username:
|
|
282
|
+
df = filter_messages_by_sender(df, sender_id, username)
|
|
283
|
+
|
|
284
|
+
if keyword:
|
|
285
|
+
df = filter_messages_by_content(df, keyword)
|
|
286
|
+
|
|
287
|
+
if start_date:
|
|
288
|
+
df = df[df['Date'] >= start_date]
|
|
289
|
+
|
|
290
|
+
if end_date:
|
|
291
|
+
df = df[df['Date'] <= end_date]
|
|
292
|
+
|
|
293
|
+
return df
|
|
294
|
+
|
|
295
|
+
def export_messages(self,
|
|
296
|
+
df: pd.DataFrame,
|
|
297
|
+
filepath: str,
|
|
298
|
+
format: str = 'csv') -> None:
|
|
299
|
+
"""
|
|
300
|
+
Export messages to file.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
df: Messages DataFrame
|
|
304
|
+
filepath: Output file path
|
|
305
|
+
format: Export format ('csv', 'json')
|
|
306
|
+
"""
|
|
307
|
+
if format.lower() == 'csv':
|
|
308
|
+
export_to_csv(df, filepath)
|
|
309
|
+
elif format.lower() == 'json':
|
|
310
|
+
export_to_json(df, filepath)
|
|
311
|
+
else:
|
|
312
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
313
|
+
|
|
314
|
+
# ==================== Statistics and Metrics ====================
|
|
315
|
+
|
|
316
|
+
def get_statistics(self, df: pd.DataFrame) -> Dict[str, Any]:
|
|
317
|
+
"""
|
|
318
|
+
Get statistics about messages.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
df: Messages DataFrame
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Dictionary with statistics
|
|
325
|
+
"""
|
|
326
|
+
return get_message_statistics(df)
|
|
327
|
+
|
|
328
|
+
async def get_metrics(self) -> Dict[str, Any]:
|
|
329
|
+
"""
|
|
330
|
+
Get current session metrics.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Dictionary with metrics
|
|
334
|
+
"""
|
|
335
|
+
health_status = await self.connection_engine.health_check()
|
|
336
|
+
|
|
337
|
+
metrics = {
|
|
338
|
+
'timestamp': datetime.now().isoformat(),
|
|
339
|
+
'current_group': self.current_group.id if self.current_group else None,
|
|
340
|
+
'connection_health': health_status,
|
|
341
|
+
'message_cache_size': len(self.message_engine._message_cache)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return metrics
|
|
345
|
+
|
|
346
|
+
async def export_metrics(self, filepath: str = "telegram_metrics.json") -> None:
|
|
347
|
+
"""
|
|
348
|
+
Export session metrics to file.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
filepath: Output file path
|
|
352
|
+
"""
|
|
353
|
+
metrics = await self.get_metrics()
|
|
354
|
+
|
|
355
|
+
with open(filepath, 'w') as f:
|
|
356
|
+
json.dump(metrics, f, indent=2)
|
|
357
|
+
|
|
358
|
+
logger.info(f"Exported metrics to {filepath}")
|
|
359
|
+
|
|
360
|
+
# ==================== Utility Methods ====================
|
|
361
|
+
|
|
362
|
+
async def validate_connection(self) -> bool:
|
|
363
|
+
"""
|
|
364
|
+
Validate connection status.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
True if connection is valid
|
|
368
|
+
"""
|
|
369
|
+
return await self.connection_engine.validate_connection()
|
|
370
|
+
|
|
371
|
+
async def health_check(self) -> Dict[str, Any]:
|
|
372
|
+
"""
|
|
373
|
+
Perform health check.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Health check results
|
|
377
|
+
"""
|
|
378
|
+
return await self.connection_engine.health_check()
|
|
379
|
+
|
|
380
|
+
def save_photos(self, df: pd.DataFrame, output_dir: str) -> None:
|
|
381
|
+
"""
|
|
382
|
+
Save profile photos from messages.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
df: Messages DataFrame with PhotoData
|
|
386
|
+
output_dir: Directory to save photos
|
|
387
|
+
"""
|
|
388
|
+
save_profile_photos(df, output_dir)
|
|
389
|
+
|
|
390
|
+
async def close(self) -> None:
|
|
391
|
+
"""Close all connections."""
|
|
392
|
+
await self.connection_engine.close()
|
|
393
|
+
logger.info("TgData closed")
|
|
394
|
+
|
|
395
|
+
# ==================== Polling and Real-time Updates ====================
|
|
396
|
+
|
|
397
|
+
def on_new_message(self, group_id: Optional[int] = None):
|
|
398
|
+
"""
|
|
399
|
+
Decorator to register a handler for new messages in real-time.
|
|
400
|
+
Uses Telethon's event system.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
group_id: Optional group ID to filter messages. If None, receives from all groups.
|
|
404
|
+
|
|
405
|
+
Example:
|
|
406
|
+
@tg.on_new_message(group_id=12345)
|
|
407
|
+
async def handler(event):
|
|
408
|
+
print(f"New message: {event.message.text}")
|
|
409
|
+
"""
|
|
410
|
+
from telethon import events
|
|
411
|
+
|
|
412
|
+
def decorator(func):
|
|
413
|
+
async def wrapped_handler(event):
|
|
414
|
+
# If group_id specified, filter by it
|
|
415
|
+
if group_id and event.chat_id != group_id:
|
|
416
|
+
return
|
|
417
|
+
await func(event)
|
|
418
|
+
|
|
419
|
+
# Register the event handler on the client
|
|
420
|
+
async def register():
|
|
421
|
+
client = await self.connection_engine.get_client()
|
|
422
|
+
client.add_event_handler(wrapped_handler, events.NewMessage())
|
|
423
|
+
logger.info(f"Registered new message handler for group {group_id or 'all groups'}")
|
|
424
|
+
|
|
425
|
+
# Schedule registration
|
|
426
|
+
asyncio.create_task(register())
|
|
427
|
+
|
|
428
|
+
return func
|
|
429
|
+
|
|
430
|
+
return decorator
|
|
431
|
+
|
|
432
|
+
async def poll_for_messages(self,
|
|
433
|
+
group_id: int,
|
|
434
|
+
interval: int = 60,
|
|
435
|
+
after_id: int = 0,
|
|
436
|
+
callback: Optional[Callable] = None,
|
|
437
|
+
max_iterations: Optional[int] = None) -> None:
|
|
438
|
+
"""
|
|
439
|
+
Poll for new messages at specified intervals.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
group_id: Group ID to poll messages from
|
|
443
|
+
interval: Polling interval in seconds (default: 60)
|
|
444
|
+
after_id: Message ID to start polling after (default: 0)
|
|
445
|
+
callback: Optional async callback function to process new messages
|
|
446
|
+
max_iterations: Maximum number of polling iterations (None = infinite)
|
|
447
|
+
|
|
448
|
+
Example:
|
|
449
|
+
async def process_messages(messages_df):
|
|
450
|
+
print(f"Got {len(messages_df)} new messages")
|
|
451
|
+
|
|
452
|
+
await tg.poll_for_messages(
|
|
453
|
+
group_id=12345,
|
|
454
|
+
interval=30,
|
|
455
|
+
callback=process_messages
|
|
456
|
+
)
|
|
457
|
+
"""
|
|
458
|
+
logger.info(f"Starting polling for group {group_id} with interval {interval}s")
|
|
459
|
+
|
|
460
|
+
current_after_id = after_id
|
|
461
|
+
iterations = 0
|
|
462
|
+
|
|
463
|
+
while max_iterations is None or iterations < max_iterations:
|
|
464
|
+
try:
|
|
465
|
+
# Get new messages since last check
|
|
466
|
+
new_messages = await self.get_messages(
|
|
467
|
+
group_id=group_id,
|
|
468
|
+
after_id=current_after_id
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if not new_messages.empty:
|
|
472
|
+
# Update the after_id to the latest message
|
|
473
|
+
current_after_id = new_messages['MessageId'].max()
|
|
474
|
+
|
|
475
|
+
logger.info(f"Poll iteration {iterations + 1}: Found {len(new_messages)} new messages")
|
|
476
|
+
|
|
477
|
+
# Call the callback if provided
|
|
478
|
+
if callback:
|
|
479
|
+
await callback(new_messages)
|
|
480
|
+
else:
|
|
481
|
+
logger.debug(f"Poll iteration {iterations + 1}: No new messages")
|
|
482
|
+
|
|
483
|
+
iterations += 1
|
|
484
|
+
|
|
485
|
+
# Wait for next interval (unless this is the last iteration)
|
|
486
|
+
if max_iterations is None or iterations < max_iterations:
|
|
487
|
+
await asyncio.sleep(interval)
|
|
488
|
+
|
|
489
|
+
except Exception as e:
|
|
490
|
+
logger.error(f"Error during polling: {e}")
|
|
491
|
+
# Continue polling after error
|
|
492
|
+
await asyncio.sleep(interval)
|
|
493
|
+
|
|
494
|
+
async def run_with_event_loop(self):
|
|
495
|
+
"""
|
|
496
|
+
Run the Telegram client with event loop to handle real-time events.
|
|
497
|
+
This method blocks until disconnected.
|
|
498
|
+
|
|
499
|
+
Example:
|
|
500
|
+
tg = TgData()
|
|
501
|
+
|
|
502
|
+
@tg.on_new_message()
|
|
503
|
+
async def handler(event):
|
|
504
|
+
print(f"New message: {event.message.text}")
|
|
505
|
+
|
|
506
|
+
await tg.run_with_event_loop()
|
|
507
|
+
"""
|
|
508
|
+
client = await self.connection_engine.get_client()
|
|
509
|
+
logger.info("Running with event loop. Press Ctrl+C to stop...")
|
|
510
|
+
await client.run_until_disconnected()
|
|
511
|
+
|
|
512
|
+
# ==================== Context Manager Support ====================
|
|
513
|
+
|
|
514
|
+
async def __aenter__(self):
|
|
515
|
+
"""Async context manager entry."""
|
|
516
|
+
return self
|
|
517
|
+
|
|
518
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
519
|
+
"""Async context manager exit."""
|
|
520
|
+
await self.close()
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# Example usage
|
|
524
|
+
if __name__ == "__main__":
|
|
525
|
+
async def main():
|
|
526
|
+
# Simple usage
|
|
527
|
+
tg = TgData()
|
|
528
|
+
|
|
529
|
+
# List groups
|
|
530
|
+
groups = await tg.list_groups()
|
|
531
|
+
print(f"Found {len(groups)} groups")
|
|
532
|
+
|
|
533
|
+
if not groups.empty:
|
|
534
|
+
# Select first group
|
|
535
|
+
group_id = groups.iloc[0]['GroupID']
|
|
536
|
+
tg.set_group(group_id)
|
|
537
|
+
|
|
538
|
+
# Get recent messages with progress
|
|
539
|
+
messages = await tg.get_messages(
|
|
540
|
+
limit=100,
|
|
541
|
+
with_progress=True
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
# Display messages
|
|
545
|
+
tg.print_messages(messages, limit=10)
|
|
546
|
+
|
|
547
|
+
# Get statistics
|
|
548
|
+
stats = tg.get_statistics(messages)
|
|
549
|
+
print(f"\nStatistics: {stats}")
|
|
550
|
+
|
|
551
|
+
# Export
|
|
552
|
+
tg.export_messages(messages, "messages.csv")
|
|
553
|
+
|
|
554
|
+
asyncio.run(main())
|