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/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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ tgdata