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/utils.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for the Telegram Group Message Crawler.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def format_message_for_display(message: Dict[str, Any], max_length: int = 100) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Format a message for display.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
message: Message dictionary
|
|
21
|
+
max_length: Maximum message length to display
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Formatted message string
|
|
25
|
+
"""
|
|
26
|
+
msg_text = message.get('Message', '')
|
|
27
|
+
if msg_text and len(msg_text) > max_length:
|
|
28
|
+
msg_text = f"{msg_text[:max_length]}..."
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
f"{message.get('Date', 'Unknown date')} | "
|
|
32
|
+
f"{message.get('Name', 'Unknown')} "
|
|
33
|
+
f"({message.get('Username', 'No username')}): "
|
|
34
|
+
f"{msg_text or '[No text]'}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def export_to_json(df: pd.DataFrame, filepath: str, pretty: bool = True) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Export DataFrame to JSON file.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
df: DataFrame to export
|
|
44
|
+
filepath: Output file path
|
|
45
|
+
pretty: Whether to pretty-print JSON
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
# Convert DataFrame to dict, handling datetime objects
|
|
49
|
+
data = df.to_dict('records')
|
|
50
|
+
|
|
51
|
+
# Convert datetime objects to strings
|
|
52
|
+
for record in data:
|
|
53
|
+
for key, value in record.items():
|
|
54
|
+
if isinstance(value, datetime):
|
|
55
|
+
record[key] = value.isoformat()
|
|
56
|
+
elif isinstance(value, bytes):
|
|
57
|
+
# Don't include binary data in JSON
|
|
58
|
+
record[key] = "[Binary data]"
|
|
59
|
+
|
|
60
|
+
# Write to file
|
|
61
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
62
|
+
if pretty:
|
|
63
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
64
|
+
else:
|
|
65
|
+
json.dump(data, f, ensure_ascii=False)
|
|
66
|
+
|
|
67
|
+
logger.info(f"Exported {len(df)} messages to {filepath}")
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.error(f"Error exporting to JSON: {e}")
|
|
71
|
+
raise
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def export_to_csv(df: pd.DataFrame, filepath: str) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Export DataFrame to CSV file.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
df: DataFrame to export
|
|
80
|
+
filepath: Output file path
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
# Remove binary data columns for CSV export
|
|
84
|
+
export_df = df.copy()
|
|
85
|
+
if 'PhotoData' in export_df.columns:
|
|
86
|
+
export_df = export_df.drop('PhotoData', axis=1)
|
|
87
|
+
|
|
88
|
+
export_df.to_csv(filepath, index=False, encoding='utf-8')
|
|
89
|
+
logger.info(f"Exported {len(df)} messages to {filepath}")
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Error exporting to CSV: {e}")
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def filter_messages_by_sender(df: pd.DataFrame, sender_id: Optional[int] = None,
|
|
97
|
+
username: Optional[str] = None) -> pd.DataFrame:
|
|
98
|
+
"""
|
|
99
|
+
Filter messages by sender.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
df: Messages DataFrame
|
|
103
|
+
sender_id: Sender ID to filter by
|
|
104
|
+
username: Username to filter by
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Filtered DataFrame
|
|
108
|
+
"""
|
|
109
|
+
if sender_id:
|
|
110
|
+
df = df[df['SenderId'] == sender_id]
|
|
111
|
+
elif username:
|
|
112
|
+
df = df[df['Username'].str.contains(username, case=False, na=False)]
|
|
113
|
+
|
|
114
|
+
return df
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def filter_messages_by_content(df: pd.DataFrame, keyword: str,
|
|
118
|
+
case_sensitive: bool = False) -> pd.DataFrame:
|
|
119
|
+
"""
|
|
120
|
+
Filter messages containing specific keyword.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
df: Messages DataFrame
|
|
124
|
+
keyword: Keyword to search for
|
|
125
|
+
case_sensitive: Whether search is case sensitive
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Filtered DataFrame
|
|
129
|
+
"""
|
|
130
|
+
return df[df['Message'].str.contains(
|
|
131
|
+
keyword,
|
|
132
|
+
case=case_sensitive,
|
|
133
|
+
na=False
|
|
134
|
+
)]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_message_statistics(df: pd.DataFrame) -> Dict[str, Any]:
|
|
138
|
+
"""
|
|
139
|
+
Get statistics about messages.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
df: Messages DataFrame
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dictionary with statistics
|
|
146
|
+
"""
|
|
147
|
+
if df.empty:
|
|
148
|
+
return {
|
|
149
|
+
'total_messages': 0,
|
|
150
|
+
'unique_senders': 0,
|
|
151
|
+
'date_range': None,
|
|
152
|
+
'top_senders': []
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
stats = {
|
|
156
|
+
'total_messages': len(df),
|
|
157
|
+
'unique_senders': df['SenderId'].nunique(),
|
|
158
|
+
'date_range': {
|
|
159
|
+
'start': df['Date'].min(),
|
|
160
|
+
'end': df['Date'].max()
|
|
161
|
+
},
|
|
162
|
+
'messages_with_text': df['Message'].notna().sum(),
|
|
163
|
+
'replies': df['ReplyToId'].notna().sum(),
|
|
164
|
+
'forwards': df['ForwardedFrom'].notna().sum()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Top senders
|
|
168
|
+
top_senders = df.groupby(['SenderId', 'Name']).size().sort_values(
|
|
169
|
+
ascending=False
|
|
170
|
+
).head(10)
|
|
171
|
+
|
|
172
|
+
stats['top_senders'] = [
|
|
173
|
+
{
|
|
174
|
+
'sender_id': sender_id,
|
|
175
|
+
'name': name,
|
|
176
|
+
'message_count': count
|
|
177
|
+
}
|
|
178
|
+
for (sender_id, name), count in top_senders.items()
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
return stats
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def save_profile_photos(df: pd.DataFrame, output_dir: str) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Save profile photos from messages to directory.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
df: Messages DataFrame with PhotoData column
|
|
190
|
+
output_dir: Directory to save photos
|
|
191
|
+
"""
|
|
192
|
+
if 'PhotoData' not in df.columns:
|
|
193
|
+
logger.warning("No PhotoData column in DataFrame")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
197
|
+
saved_count = 0
|
|
198
|
+
|
|
199
|
+
for _, row in df.iterrows():
|
|
200
|
+
if row['PhotoData'] is not None:
|
|
201
|
+
filename = f"{row['SenderId']}_{row['Username'].replace('@', '')}.jpg"
|
|
202
|
+
filepath = os.path.join(output_dir, filename)
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
with open(filepath, 'wb') as f:
|
|
206
|
+
f.write(row['PhotoData'])
|
|
207
|
+
saved_count += 1
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.error(f"Error saving photo for {row['SenderId']}: {e}")
|
|
210
|
+
|
|
211
|
+
logger.info(f"Saved {saved_count} profile photos to {output_dir}")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def create_metrics_report(df: pd.DataFrame, group_info: Dict[str, Any]) -> Dict[str, Any]:
|
|
215
|
+
"""
|
|
216
|
+
Create a comprehensive metrics report.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
df: Messages DataFrame
|
|
220
|
+
group_info: Information about the group
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Dictionary with metrics
|
|
224
|
+
"""
|
|
225
|
+
stats = get_message_statistics(df)
|
|
226
|
+
|
|
227
|
+
report = {
|
|
228
|
+
'timestamp': datetime.now().isoformat(),
|
|
229
|
+
'group': group_info,
|
|
230
|
+
'statistics': stats,
|
|
231
|
+
'processing_info': {
|
|
232
|
+
'total_processed': len(df),
|
|
233
|
+
'memory_usage_mb': df.memory_usage(deep=True).sum() / 1024 / 1024
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Add time-based statistics if we have data
|
|
238
|
+
if not df.empty:
|
|
239
|
+
df['Hour'] = pd.to_datetime(df['Date']).dt.hour
|
|
240
|
+
df['DayOfWeek'] = pd.to_datetime(df['Date']).dt.day_name()
|
|
241
|
+
|
|
242
|
+
report['time_analysis'] = {
|
|
243
|
+
'messages_by_hour': df['Hour'].value_counts().sort_index().to_dict(),
|
|
244
|
+
'messages_by_day': df['DayOfWeek'].value_counts().to_dict()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return report
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tgdata
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: A production-grade Python library for extracting and processing Telegram group and channel messages
|
|
5
|
+
Author: enes kuzucu
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.7
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: Telethon
|
|
18
|
+
Requires-Dist: pandas
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: requires-dist
|
|
24
|
+
Dynamic: requires-python
|
|
25
|
+
Dynamic: summary
|
|
26
|
+
|
|
27
|
+
# tgdata
|
|
28
|
+
|
|
29
|
+
A production-grade Python library for extracting and processing Telegram group and channel messages. Designed for ETL pipelines, data analysis, and archival purposes.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- 🚀 **Production-Ready**: Built for reliability and scale in ETL pipelines
|
|
34
|
+
- 📊 **Efficient Data Extraction**: Fetch messages from groups and channels with rate limit handling
|
|
35
|
+
- 🔄 **Incremental Updates**: Fetch only new messages with `after_id` parameter
|
|
36
|
+
- 📈 **Progress Tracking**: Monitor long-running operations with real-time progress
|
|
37
|
+
- 🔌 **Clean Architecture**: Focused on data extraction with minimal dependencies
|
|
38
|
+
- 🛡️ **Robust Error Handling**: Automatic retries with exponential backoff
|
|
39
|
+
- 📁 **Multiple Export Formats**: Export to CSV, JSON, or integrate with your data pipeline
|
|
40
|
+
- 🔔 **Real-time Updates**: Listen for new messages with event handlers
|
|
41
|
+
- ⏱️ **Polling Support**: Poll for new messages at configurable intervals
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install tgdata
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from tgdata import TgData
|
|
53
|
+
import asyncio
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
# Initialize the client
|
|
57
|
+
tg = TgData()
|
|
58
|
+
|
|
59
|
+
# List available groups and channels
|
|
60
|
+
groups = await tg.list_groups()
|
|
61
|
+
print(f"Found {len(groups)} groups/channels")
|
|
62
|
+
|
|
63
|
+
# Fetch messages from a specific group
|
|
64
|
+
messages = await tg.get_messages(
|
|
65
|
+
group_id=-1001234567890, # Your group ID
|
|
66
|
+
limit=1000,
|
|
67
|
+
with_progress=True
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Export to CSV
|
|
71
|
+
tg.export_messages(messages, "messages.csv")
|
|
72
|
+
|
|
73
|
+
# Get message statistics
|
|
74
|
+
stats = tg.get_statistics(messages)
|
|
75
|
+
print(f"Total messages: {stats['total_messages']}")
|
|
76
|
+
print(f"Date range: {stats['date_range']['first']} to {stats['date_range']['last']}")
|
|
77
|
+
|
|
78
|
+
asyncio.run(main())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
Create a `config.ini` file with your Telegram API credentials:
|
|
84
|
+
|
|
85
|
+
```ini
|
|
86
|
+
[telegram]
|
|
87
|
+
api_id = YOUR_API_ID
|
|
88
|
+
api_hash = YOUR_API_HASH
|
|
89
|
+
session_file = telegram_session
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Get your API credentials from [https://my.telegram.org/apps](https://my.telegram.org/apps)
|
|
93
|
+
|
|
94
|
+
## Advanced Usage
|
|
95
|
+
|
|
96
|
+
### ETL Pipeline Integration
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from tgdata import TgData
|
|
100
|
+
from datetime import datetime, timedelta
|
|
101
|
+
import asyncio
|
|
102
|
+
|
|
103
|
+
async def etl_pipeline():
|
|
104
|
+
tg = TgData()
|
|
105
|
+
|
|
106
|
+
# Fetch recent messages for ETL processing
|
|
107
|
+
yesterday = datetime.now() - timedelta(days=1)
|
|
108
|
+
messages = await tg.get_messages(
|
|
109
|
+
group_id=-1001234567890,
|
|
110
|
+
start_date=yesterday,
|
|
111
|
+
with_progress=True
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Filter messages
|
|
115
|
+
filtered = tg.filter_messages(
|
|
116
|
+
messages,
|
|
117
|
+
keyword="important",
|
|
118
|
+
start_date=yesterday
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Export for data pipeline
|
|
122
|
+
tg.export_messages(filtered, "daily_extract.json", format="json")
|
|
123
|
+
|
|
124
|
+
# Get metrics
|
|
125
|
+
metrics = await tg.get_metrics()
|
|
126
|
+
print(f"Processed {len(filtered)} messages")
|
|
127
|
+
|
|
128
|
+
asyncio.run(etl_pipeline())
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Incremental Message Fetching
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from tgdata import TgData
|
|
135
|
+
|
|
136
|
+
async def incremental_fetch():
|
|
137
|
+
tg = TgData()
|
|
138
|
+
|
|
139
|
+
# Get the latest message ID from your storage
|
|
140
|
+
last_processed_id = load_checkpoint() # Your implementation
|
|
141
|
+
|
|
142
|
+
# Fetch only messages after that ID
|
|
143
|
+
new_messages = await tg.get_messages(
|
|
144
|
+
group_id=-1001234567890,
|
|
145
|
+
after_id=last_processed_id
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if not new_messages.empty:
|
|
149
|
+
# Process new messages
|
|
150
|
+
process_messages(new_messages)
|
|
151
|
+
|
|
152
|
+
# Save new checkpoint
|
|
153
|
+
save_checkpoint(new_messages['MessageId'].max())
|
|
154
|
+
|
|
155
|
+
print(f"New messages: {len(new_messages)}")
|
|
156
|
+
|
|
157
|
+
asyncio.run(incremental_fetch())
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Progress Monitoring
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
async def monitor_extraction():
|
|
164
|
+
tg = TgData()
|
|
165
|
+
|
|
166
|
+
def progress_callback(current, total, rate):
|
|
167
|
+
percent = (current / total * 100) if total else 0
|
|
168
|
+
print(f"Progress: {current}/{total} ({percent:.1f}%) - {rate:.1f} msg/s")
|
|
169
|
+
|
|
170
|
+
messages = await tg.get_messages(
|
|
171
|
+
group_id=-1001234567890,
|
|
172
|
+
limit=10000,
|
|
173
|
+
progress_callback=progress_callback
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Real-time Message Monitoring
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
async def monitor_real_time():
|
|
181
|
+
tg = TgData()
|
|
182
|
+
|
|
183
|
+
# Register handler for new messages
|
|
184
|
+
@tg.on_new_message(group_id=-1001234567890)
|
|
185
|
+
async def handle_message(event):
|
|
186
|
+
print(f"New message from {event.sender_id}: {event.message.text}")
|
|
187
|
+
|
|
188
|
+
# React to commands
|
|
189
|
+
if event.message.text == "!ping":
|
|
190
|
+
await event.reply("Pong!")
|
|
191
|
+
|
|
192
|
+
# Run event loop
|
|
193
|
+
await tg.run_with_event_loop()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Polling for New Messages
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
async def poll_messages():
|
|
200
|
+
tg = TgData()
|
|
201
|
+
|
|
202
|
+
# Define callback for new messages
|
|
203
|
+
async def process_batch(messages_df):
|
|
204
|
+
print(f"Got {len(messages_df)} new messages")
|
|
205
|
+
# Process messages here
|
|
206
|
+
|
|
207
|
+
# Poll every 30 seconds
|
|
208
|
+
await tg.poll_for_messages(
|
|
209
|
+
group_id=-1001234567890,
|
|
210
|
+
interval=30,
|
|
211
|
+
callback=process_batch,
|
|
212
|
+
max_iterations=10 # Stop after 10 polls
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## API Reference
|
|
217
|
+
|
|
218
|
+
### TgData
|
|
219
|
+
|
|
220
|
+
Main class for interacting with Telegram groups and channels.
|
|
221
|
+
|
|
222
|
+
#### Methods
|
|
223
|
+
|
|
224
|
+
**Core Methods:**
|
|
225
|
+
- `list_groups()` - List all accessible groups and channels
|
|
226
|
+
- `get_messages()` - Fetch messages with various filters
|
|
227
|
+
- `search_messages()` - Search for specific content
|
|
228
|
+
- `filter_messages()` - Filter messages by criteria
|
|
229
|
+
- `export_messages()` - Export to CSV or JSON
|
|
230
|
+
- `get_statistics()` - Get message statistics
|
|
231
|
+
- `get_metrics()` - Get session metrics
|
|
232
|
+
- `get_message_count()` - Get total message count without fetching all
|
|
233
|
+
|
|
234
|
+
**Real-time & Polling:**
|
|
235
|
+
- `on_new_message()` - Decorator to register real-time message handlers
|
|
236
|
+
- `poll_for_messages()` - Poll for new messages at intervals
|
|
237
|
+
- `run_with_event_loop()` - Run client with event loop for real-time events
|
|
238
|
+
|
|
239
|
+
### Checkpoint Management
|
|
240
|
+
|
|
241
|
+
For production ETL pipelines, implement your own checkpoint logic:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
# Save last processed message ID to your database
|
|
245
|
+
last_id = messages['MessageId'].max()
|
|
246
|
+
await save_to_database(group_id, last_id)
|
|
247
|
+
|
|
248
|
+
# On next run, fetch only new messages
|
|
249
|
+
last_id = await load_from_database(group_id)
|
|
250
|
+
new_messages = await tg.get_messages(group_id=group_id, after_id=last_id)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Production Deployment
|
|
254
|
+
|
|
255
|
+
### Best Practices
|
|
256
|
+
|
|
257
|
+
1. **Use environment variables for credentials**:
|
|
258
|
+
```python
|
|
259
|
+
import os
|
|
260
|
+
config_path = os.getenv("TELEGRAM_CONFIG", "config.ini")
|
|
261
|
+
tg = TgData(config_path=config_path)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
2. **Implement error handling**:
|
|
265
|
+
```python
|
|
266
|
+
try:
|
|
267
|
+
messages = await tg.get_messages(group_id=group_id)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.error(f"Failed to fetch messages: {e}")
|
|
270
|
+
# Implement retry logic or alerting
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
3. **Monitor rate limits**:
|
|
274
|
+
```python
|
|
275
|
+
health = await tg.health_check()
|
|
276
|
+
if not health['primary_connection']:
|
|
277
|
+
# Handle connection issues
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Performance Tips
|
|
281
|
+
|
|
282
|
+
- Use connection pooling for parallel operations
|
|
283
|
+
- Implement checkpoint logic for incremental processing
|
|
284
|
+
- Implement progress callbacks for visibility
|
|
285
|
+
- Export data incrementally for large datasets
|
|
286
|
+
|
|
287
|
+
## Requirements
|
|
288
|
+
|
|
289
|
+
- Python 3.7+
|
|
290
|
+
- Telegram API credentials (not bot tokens)
|
|
291
|
+
- Group/channel membership
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT License - see LICENSE file for details
|
|
296
|
+
|
|
297
|
+
## Contributing
|
|
298
|
+
|
|
299
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
300
|
+
|
|
301
|
+
## Support
|
|
302
|
+
|
|
303
|
+
- Documentation: [Full documentation](https://github.com/yourusername/tgdata)
|
|
304
|
+
- Issues: [GitHub Issues](https://github.com/yourusername/tgdata/issues)
|
|
305
|
+
- Examples: See the [examples/](examples/) directory
|
|
306
|
+
|
|
307
|
+
## Disclaimer
|
|
308
|
+
|
|
309
|
+
This tool is for legitimate data collection and analysis only. Users must comply with Telegram's Terms of Service and applicable laws. Always respect user privacy and platform rate limits.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
tgdata/__init__.py,sha256=fayYjaDv4oV5z09koG0ieBhxXCZyyXou3lLBonh3fuc,1067
|
|
2
|
+
tgdata/connection_engine.py,sha256=-jegoNPZ4Wjt24hLyY3OoZ4L38faMLaUiFXPlrBdS9A,11796
|
|
3
|
+
tgdata/message_engine.py,sha256=aMc_EptGMWAxyd1JNxHofJkSt4BvB94DdeqzJt5wyIk,11135
|
|
4
|
+
tgdata/models.py,sha256=mONAsXXqiklF-Q7q9_JuvDHy_lODeQuCuwR9j5As0Yg,1702
|
|
5
|
+
tgdata/progress.py,sha256=oQOhiPWAwo4zBgVDqrYyIeoCFG-WYx_xo7Auwi9O324,3382
|
|
6
|
+
tgdata/tgdata.py,sha256=WGaViROT99JUFhRyInbjs02k4-Keu_YOQ5u_Pc-lzrY,19462
|
|
7
|
+
tgdata/utils.py,sha256=LDGZ8KluJ0tEuL7S5F08ln75g6UWxOL7lRnn7jZ9p68,7035
|
|
8
|
+
tgdata/smoke_tests/__init__.py,sha256=7DrB-Ju2fq9CURHEawNC7tqym3GAWCCvWdhelUgrXL8,48
|
|
9
|
+
tgdata/smoke_tests/my_test.py,sha256=GaZ29JEJrd42-lgpA9FjLkWNC8rR0HLywYSEGNKWpOQ,5484
|
|
10
|
+
tgdata/smoke_tests/test_00_connection.py,sha256=NB_cN0X8CtCSjT9vRz4I3XjM5ZbiJ9K4lnfRaw7GXLw,9332
|
|
11
|
+
tgdata/smoke_tests/test_01_list_group_chats.py,sha256=XFh2ugY44W7VBJbZcyBpB4q7K7i7INKp2HEkTEmrEnM,15639
|
|
12
|
+
tgdata/smoke_tests/test_02_get_message_count.py,sha256=n3fhx_iKFdXT1YXbfDeCSIGuS_Ply0AY1XS1y7t46L4,1990
|
|
13
|
+
tgdata/smoke_tests/test_03_get_all_messages.py,sha256=hNfdSxVaqCeCNMf03cEjmjeNZlFG8KU52BYMWfTVWFs,1739
|
|
14
|
+
tgdata/smoke_tests/test_04_get_all_messages_in_batches.py,sha256=Pl4gMLjZAe_TPaCKiI4wyNaQcHKkW9ZC1aWswHuNQe8,2890
|
|
15
|
+
tgdata/smoke_tests/test_06_advanced_features.py,sha256=XC_CaZ1FB1mzq2o7pUS5SaZZ8wDRtGd7fY6lzWJTmBM,7557
|
|
16
|
+
tgdata/smoke_tests/test_10_polling.py,sha256=GGqjdK6p0_JOt2iAepw03Fc7NB8YSNrixvl_-bkt2s0,7490
|
|
17
|
+
tgdata-0.0.0.dist-info/METADATA,sha256=asaNYdPxQKHmKCrmmPaPQtJL082wFCilKUcDz4i_FHE,8526
|
|
18
|
+
tgdata-0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
tgdata-0.0.0.dist-info/top_level.txt,sha256=mvOH6wBLbA5K_TUruSVtC4r9r7eoQ9qdCytRE4DJWAI,7
|
|
20
|
+
tgdata-0.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tgdata
|