-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtelegrambot.py
More file actions
665 lines (540 loc) · 27.8 KB
/
Copy pathtelegrambot.py
File metadata and controls
665 lines (540 loc) · 27.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
#!/usr/bin/env python3
"""
Telegram Bot for Source Code Scanner
Allows users to scan URLs through Telegram interface
"""
import os
import sys
import asyncio
import logging
import tempfile
import shutil
from datetime import datetime
from urllib.parse import urlparse
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Telegram bot imports
from telegram import Update, Document
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
from telegram.constants import ParseMode
# Add scanner module to path
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from scanner.downloader import SecurityFileDownloader
from scanner.semgrep_scan import SemgrepScanner
from scanner.grype_scan import GrypeScanner
from scanner.trufflehog_scan import TruffleHogScanner
from scanner.report_generator import ReportGenerator
class TelegramSecurityScanner:
def __init__(self):
self.bot_token = os.getenv('TELEGRAM_BOT_TOKEN')
if not self.bot_token:
raise ValueError("TELEGRAM_BOT_TOKEN not found in environment variables")
# Setup logging
self.setup_logging()
# Initialize scanner components
self.output_dir = "bot_output"
self.temp_dir = "bot_temp"
os.makedirs(self.output_dir, exist_ok=True)
os.makedirs(self.temp_dir, exist_ok=True)
self.downloader = SecurityFileDownloader(output_dir=self.temp_dir)
self.semgrep_scanner = SemgrepScanner()
self.grype_scanner = GrypeScanner()
self.trufflehog_scanner = TruffleHogScanner()
self.report_generator = ReportGenerator(output_dir=self.output_dir)
# Active scans tracking
self.active_scans = {}
def setup_logging(self):
"""Setup logging for the bot."""
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO,
handlers=[
logging.FileHandler('telegram_bot.log'),
logging.StreamHandler()
]
)
self.logger = logging.getLogger(__name__)
async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /start command."""
welcome_message = """
🔍 **Source Code Scanner Bot**
I can help you scan security-relevant files from any URL for:
• 🛡️ Security vulnerabilities (Semgrep)
• 📦 Dependency vulnerabilities (Grype)
• 🔑 Secrets and API keys (TruffleHog)
**Supported file types:**
• JavaScript/TypeScript (.js, .jsx, .ts, .tsx)
• Configuration files (.json, .xml, .yaml, .yml)
• Text files (.txt, .md, .env)
• Server-side scripts (.php, .py, .rb, .java)
• Database files (.sql, .db)
• Certificate files (.pem, .key, .crt)
**How to use:**
1. Use `/scan <URL>` command
2. Wait for the scan to complete
3. Receive detailed results in text + PDF and JSON reports
**Commands:**
/start - Show this help message
/help - Show detailed help
/status - Check if any scan is running
/scan <URL> - Scan a specific URL
**Examples:**
• `/scan https://example.com`
• `/scan example.com`
• `/scan github.com/user/repo`
⚠️ **Note:** Scanning may take a few minutes depending on the website size.
"""
await update.message.reply_text(
welcome_message,
parse_mode=ParseMode.MARKDOWN
)
async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /help command."""
help_message = """
📖 **Detailed Help**
**What this bot does:**
• Downloads all JavaScript files from a given URL
• Scans for security vulnerabilities using Semgrep
• Checks dependencies for known vulnerabilities using Grype
• Searches for hardcoded secrets using TruffleHog
• Generates comprehensive PDF and JSON reports
**Supported URLs:**
• Any public website (http/https)
• The bot will automatically find and download JS files
• Both inline and external JavaScript files are analyzed
**Report Contents:**
• **PDF Report:** Human-readable summary with recommendations
• **JSON Report:** Structured data for further processing
**Security Tools Used:**
• **Semgrep:** Static analysis for code vulnerabilities
• **Grype:** Dependency vulnerability scanning
• **TruffleHog:** Secret and credential detection
**Limitations:**
• Only scans publicly accessible websites
• Maximum scan time: 10 minutes
• Large websites may take longer to process
**Privacy:**
• Scanned files are automatically deleted after processing
• Reports are only sent to you and then removed from server
"""
await update.message.reply_text(
help_message,
parse_mode=ParseMode.MARKDOWN
)
async def status_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /status command."""
user_id = update.effective_user.id
if user_id in self.active_scans:
scan_info = self.active_scans[user_id]
status_message = f"""
🔄 **Scan Status**
**URL:** {scan_info['url']}
**Started:** {scan_info['start_time']}
**Current Step:** {scan_info['current_step']}
Please wait for the scan to complete...
"""
else:
status_message = "✅ No active scans. Send me a URL to start scanning!"
await update.message.reply_text(
status_message,
parse_mode=ParseMode.MARKDOWN
)
async def scan_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /scan <URL> command."""
user_id = update.effective_user.id
# Check if user already has an active scan
if user_id in self.active_scans:
await update.message.reply_text(
"⚠️ You already have an active scan running. Please wait for it to complete."
)
return
# Check if URL is provided
if not context.args:
await update.message.reply_text(
"❌ Please provide a URL to scan.\n\n**Usage:** `/scan <URL>`\n\n**Examples:**\n• `/scan https://example.com`\n• `/scan example.com`",
parse_mode=ParseMode.MARKDOWN
)
return
# Get URL from command arguments
url_text = ' '.join(context.args)
# Validate URL
url = self.validate_url(url_text)
if not url:
await update.message.reply_text(
"❌ Invalid URL. Please provide a valid URL.\n\n**Examples:**\n• `/scan https://example.com`\n• `/scan example.com`",
parse_mode=ParseMode.MARKDOWN
)
return
# CLI notification
self.logger.info(f"New scan request from user {user_id} for URL: {url}")
print(f"🔍 Starting security scan for: {url}")
# Start scanning
await self.start_scan(update, url)
async def handle_url(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle URL messages and start scanning."""
user_id = update.effective_user.id
message_text = update.message.text.strip()
# Check if user already has an active scan
if user_id in self.active_scans:
await update.message.reply_text(
"⚠️ You already have an active scan running. Please wait for it to complete."
)
return
# Validate URL
url = self.validate_url(message_text)
if not url:
await update.message.reply_text(
"❌ Invalid URL. Please send a valid URL starting with http:// or https://"
)
return
# CLI notification
self.logger.info(f"New scan request from user {user_id} for URL: {url}")
print(f"🔍 Starting security scan for: {url}")
# Start scanning process
await self.start_scan(update, url)
def validate_url(self, text: str) -> str:
"""Validate and normalize URL."""
try:
# Add https if no protocol specified
if not text.startswith(('http://', 'https://')):
text = 'https://' + text
# Parse URL to validate
parsed = urlparse(text)
if parsed.netloc and parsed.scheme in ['http', 'https']:
return text
return None
except:
return None
async def start_scan(self, update: Update, url: str):
"""Start the scanning process."""
user_id = update.effective_user.id
# Track active scan
self.active_scans[user_id] = {
'url': url,
'start_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'current_step': 'Initializing'
}
# Send initial message
status_message = await update.message.reply_text(
f"🚀 **Starting scan for:** `{url}`\n\n⏳ Initializing scanner...",
parse_mode=ParseMode.MARKDOWN
)
try:
# Create unique temp directory for this scan
scan_temp_dir = os.path.join(self.temp_dir, f"scan_{user_id}_{int(datetime.now().timestamp())}")
os.makedirs(scan_temp_dir, exist_ok=True)
# Initialize components for this scan
downloader = SecurityFileDownloader(output_dir=scan_temp_dir)
scan_results = {
"target_url": url,
"scan_timestamp": datetime.now().isoformat(),
"downloaded_files": [],
"semgrep": {},
"grype": {},
"trufflehog": {}
}
# Step 1: Download security-relevant files
self.active_scans[user_id]['current_step'] = 'Downloading security-relevant files'
await status_message.edit_text(
f"🚀 **Scanning:** `{url}`\n\n📥 Downloading security-relevant files...",
parse_mode=ParseMode.MARKDOWN
)
# CLI notification
print(f"📥 Downloading security-relevant files from {url}...")
self.logger.info(f"Starting file download for {url}")
downloaded_files = downloader.download_from_url(url)
scan_results["downloaded_files"] = downloaded_files
if not downloaded_files:
print(f"❌ No security-relevant files found at {url}")
self.logger.warning(f"No files downloaded from {url}")
await status_message.edit_text(
f"⚠️ **Scan completed with warnings**\n\n❌ No security-relevant files found at `{url}`",
parse_mode=ParseMode.MARKDOWN
)
return
print(f"✅ Downloaded {len(downloaded_files)} security-relevant files")
self.logger.info(f"Downloaded {len(downloaded_files)} files from {url}")
# Step 2: Semgrep scan
self.active_scans[user_id]['current_step'] = 'Running static code analysis'
await status_message.edit_text(
f"🚀 **Scanning:** `{url}`\n\n🔍 Running static code analysis ({len(downloaded_files)} files)...",
parse_mode=ParseMode.MARKDOWN
)
# CLI notification
print(f"🔍 Running Semgrep static code analysis on {len(downloaded_files)} files...")
self.logger.info(f"Starting Semgrep scan with ruleset: p/security-audit")
semgrep_results = self.semgrep_scanner.scan_files(downloaded_files)
scan_results["semgrep"] = semgrep_results
semgrep_count = semgrep_results.get('summary', {}).get('total_findings', 0)
print(f"✅ Semgrep scan completed - Found {semgrep_count} security issues")
self.logger.info(f"Semgrep scan completed - Found {semgrep_count} issues")
# Step 3: Grype scan
self.active_scans[user_id]['current_step'] = 'Scanning dependencies'
await status_message.edit_text(
f"🚀 **Scanning:** `{url}`\n\n📦 Scanning for dependency vulnerabilities...",
parse_mode=ParseMode.MARKDOWN
)
# CLI notification
print(f"📦 Running Grype dependency vulnerability scan...")
self.logger.info(f"Starting Grype dependency scan")
search_paths = [scan_temp_dir] + downloaded_files
grype_results = self.grype_scanner.scan_dependencies(search_paths)
scan_results["grype"] = grype_results
grype_count = grype_results.get('summary', {}).get('total_vulnerabilities', 0)
print(f"✅ Grype scan completed - Found {grype_count} dependency vulnerabilities")
self.logger.info(f"Grype scan completed - Found {grype_count} vulnerabilities")
# Step 4: TruffleHog scan
self.active_scans[user_id]['current_step'] = 'Detecting secrets'
await status_message.edit_text(
f"🚀 **Scanning:** `{url}`\n\n🔑 Scanning for secrets and API keys...",
parse_mode=ParseMode.MARKDOWN
)
# CLI notification
print(f"🔑 Running TruffleHog secret detection scan...")
self.logger.info(f"Starting TruffleHog secret detection scan")
trufflehog_results = self.trufflehog_scanner.scan_files(downloaded_files)
scan_results["trufflehog"] = trufflehog_results
secrets_count = trufflehog_results.get('summary', {}).get('total_secrets', 0)
print(f"✅ TruffleHog scan completed - Found {secrets_count} potential secrets")
self.logger.info(f"TruffleHog scan completed - Found {secrets_count} potential secrets")
# Step 5: Generate reports
self.active_scans[user_id]['current_step'] = 'Generating reports'
await status_message.edit_text(
f"🚀 **Scanning:** `{url}`\n\n📄 Generating reports...",
parse_mode=ParseMode.MARKDOWN
)
# CLI notification
print(f"📄 Generating PDF and JSON reports...")
self.logger.info(f"Generating security reports for {url}")
report_paths = self.report_generator.generate_reports(scan_results, url)
print(f"✅ Reports generated successfully")
self.logger.info(f"Reports generated: {report_paths}")
# Send results
print(f"📤 Sending reports to user {user_id}...")
self.logger.info(f"Sending scan results to user {user_id}")
await self.send_results(update, scan_results, report_paths, status_message)
except Exception as e:
print(f"❌ Scan failed for {url}: {str(e)}")
self.logger.error(f"Scan failed for user {user_id}: {str(e)}")
await status_message.edit_text(
f"❌ **Scan failed**\n\n🚫 Error: {str(e)[:200]}...",
parse_mode=ParseMode.MARKDOWN
)
finally:
# Cleanup
if user_id in self.active_scans:
del self.active_scans[user_id]
# Clean up temporary files
try:
if 'scan_temp_dir' in locals() and os.path.exists(scan_temp_dir):
shutil.rmtree(scan_temp_dir)
print(f"🧹 Cleaned up temporary files for scan")
self.logger.info(f"Cleaned up temporary files for user {user_id}")
except Exception as e:
print(f"⚠️ Failed to cleanup temp files: {str(e)}")
self.logger.warning(f"Failed to cleanup temp files: {str(e)}")
async def send_results(self, update: Update, scan_results: dict, report_paths: dict, status_message):
"""Send scan results to user."""
try:
# Generate summary
summary = self.generate_summary_text(scan_results)
# Update status message with summary
await status_message.edit_text(
f"✅ **Scan completed successfully!**\n\n{summary}\n\n📎 Sending reports...",
parse_mode=ParseMode.MARKDOWN
)
# Send simple message based on findings
semgrep_total = scan_results.get("semgrep", {}).get("summary", {}).get("total_findings", 0)
grype_total = scan_results.get("grype", {}).get("summary", {}).get("total_vulnerabilities", 0)
secrets_total = scan_results.get("trufflehog", {}).get("summary", {}).get("total_secrets", 0)
if semgrep_total == 0 and grype_total == 0 and secrets_total == 0:
await update.message.reply_text(
"✅ **No security issues found!**\n\n🎉 Your scanned files appear to be clean from common security vulnerabilities, dependency issues, and exposed secrets.",
parse_mode=ParseMode.MARKDOWN
)
else:
await update.message.reply_text(
"⚠️ **Security issues detected!**\n\n📄 Please check the detailed reports for more information.",
parse_mode=ParseMode.MARKDOWN
)
# Send PDF report
if os.path.exists(report_paths['pdf_report']):
print(f"📄 Sending PDF report to user...")
with open(report_paths['pdf_report'], 'rb') as pdf_file:
await update.message.reply_document(
document=pdf_file,
filename=f"security_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
caption="📄 **PDF Report** - Human-readable security analysis"
)
# Send JSON report
if os.path.exists(report_paths['json_report']):
print(f"📊 Sending JSON report to user...")
with open(report_paths['json_report'], 'rb') as json_file:
await update.message.reply_document(
document=json_file,
filename=f"scan_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
caption="📊 **JSON Report** - Structured data for further processing"
)
print(f"✅ All reports sent successfully to user")
self.logger.info(f"Reports sent successfully to user")
# Clean up report files
for report_path in report_paths.values():
try:
if os.path.exists(report_path):
os.remove(report_path)
except Exception as e:
self.logger.warning(f"Failed to cleanup report file {report_path}: {str(e)}")
except Exception as e:
print(f"❌ Failed to send reports: {str(e)}")
self.logger.error(f"Failed to send results: {str(e)}")
await update.message.reply_text(
"❌ Failed to send reports. Please try again later."
)
def generate_summary_text(self, scan_results: dict) -> str:
"""Generate summary text for Telegram message."""
files_count = len(scan_results.get("downloaded_files", []))
# Semgrep summary
semgrep_summary = scan_results.get("semgrep", {}).get("summary", {})
semgrep_total = semgrep_summary.get("total_findings", 0)
semgrep_high = semgrep_summary.get("high_severity", 0)
# Grype summary
grype_summary = scan_results.get("grype", {}).get("summary", {})
grype_total = grype_summary.get("total_vulnerabilities", 0)
grype_critical = grype_summary.get("critical_severity", 0)
grype_high = grype_summary.get("high_severity", 0)
# TruffleHog summary
trufflehog_summary = scan_results.get("trufflehog", {}).get("summary", {})
secrets_total = trufflehog_summary.get("total_secrets", 0)
secrets_high = trufflehog_summary.get("high_confidence", 0)
summary = f"📊 **Scan Summary:**\n"
summary += f"• Files scanned: {files_count}\n"
summary += f"• Security issues: {semgrep_total}"
if semgrep_high > 0:
summary += f" ({semgrep_high} high severity)"
summary += "\n"
summary += f"• Dependency vulnerabilities: {grype_total}"
if grype_critical > 0 or grype_high > 0:
summary += f" ({grype_critical + grype_high} critical/high)"
summary += "\n"
summary += f"• Secrets found: {secrets_total}"
if secrets_high > 0:
summary += f" ({secrets_high} high confidence)"
return summary
def generate_detailed_findings(self, scan_results: dict) -> str:
"""Generate detailed findings text for Telegram message."""
detailed_text = ""
# Semgrep findings
semgrep_results = scan_results.get("semgrep", {})
semgrep_findings = semgrep_results.get("findings", [])
if semgrep_findings:
detailed_text += "🛡️ **Security Issues (Semgrep):**\n"
for i, finding in enumerate(semgrep_findings[:10]): # Limit to first 10
severity = finding.get('severity', 'unknown').upper()
rule_id = finding.get('rule_id', 'unknown')
message = finding.get('message', 'No description')
file_path = finding.get('path', 'unknown')
line = finding.get('line', 'unknown')
severity_emoji = "🔴" if severity in ['HIGH', 'CRITICAL'] else "🟡" if severity == 'MEDIUM' else "🟢"
detailed_text += f"{i+1}. {severity_emoji} **{severity}** - {rule_id}\n"
detailed_text += f" 📄 File: `{file_path}:{line}`\n"
detailed_text += f" 💬 {message[:100]}{'...' if len(message) > 100 else ''}\n\n"
if len(semgrep_findings) > 10:
detailed_text += f" ⚠️ ... and {len(semgrep_findings) - 10} more issues (see PDF report)\n\n"
# Grype findings
grype_results = scan_results.get("grype", {})
grype_findings = grype_results.get("matches", [])
if grype_findings:
detailed_text += "📦 **Dependency Vulnerabilities (Grype):**\n"
for i, finding in enumerate(grype_findings[:10]): # Limit to first 10
vulnerability = finding.get('vulnerability', {})
artifact = finding.get('artifact', {})
cve_id = vulnerability.get('id', 'unknown')
severity = vulnerability.get('severity', 'unknown').upper()
package_name = artifact.get('name', 'unknown')
package_version = artifact.get('version', 'unknown')
description = vulnerability.get('description', 'No description')
severity_emoji = "🔴" if severity in ['HIGH', 'CRITICAL'] else "🟡" if severity == 'MEDIUM' else "🟢"
detailed_text += f"{i+1}. {severity_emoji} **{cve_id}** ({severity})\n"
detailed_text += f" 📦 Package: `{package_name}@{package_version}`\n"
detailed_text += f" 💬 {description[:100]}{'...' if len(description) > 100 else ''}\n\n"
if len(grype_findings) > 10:
detailed_text += f" ⚠️ ... and {len(grype_findings) - 10} more vulnerabilities (see PDF report)\n\n"
# TruffleHog findings
trufflehog_results = scan_results.get("trufflehog", {})
trufflehog_findings = trufflehog_results.get("results", [])
if trufflehog_findings:
detailed_text += "🔑 **Secrets Found (TruffleHog):**\n"
for i, finding in enumerate(trufflehog_findings[:10]): # Limit to first 10
detector_name = finding.get('DetectorName', 'unknown')
source_name = finding.get('SourceName', 'unknown')
verified = finding.get('Verified', False)
file_path = finding.get('SourceMetadata', {}).get('Data', {}).get('Filesystem', {}).get('file', 'unknown')
line = finding.get('SourceMetadata', {}).get('Data', {}).get('Filesystem', {}).get('line', 'unknown')
confidence_emoji = "🔴" if verified else "🟡"
confidence_text = "HIGH (Verified)" if verified else "MEDIUM (Unverified)"
detailed_text += f"{i+1}. {confidence_emoji} **{detector_name}** ({confidence_text})\n"
detailed_text += f" 📄 File: `{file_path}:{line}`\n"
detailed_text += f" 🔍 Source: {source_name}\n\n"
if len(trufflehog_findings) > 10:
detailed_text += f" ⚠️ ... and {len(trufflehog_findings) - 10} more secrets (see PDF report)\n\n"
return detailed_text.strip()
def split_message(self, text: str, max_length: int) -> list:
"""Split long message into chunks for Telegram."""
if len(text) <= max_length:
return [text]
chunks = []
current_chunk = ""
lines = text.split('\n')
for line in lines:
if len(current_chunk) + len(line) + 1 <= max_length:
current_chunk += line + '\n'
else:
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = line + '\n'
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
async def handle_unknown(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle unknown messages."""
await update.message.reply_text(
"🤔 I don't understand that message.\n\n"
"Please send me a URL to scan, or use /help for more information."
)
def run(self):
"""Run the Telegram bot."""
# Create application
application = Application.builder().token(self.bot_token).build()
# Add handlers
application.add_handler(CommandHandler("start", self.start_command))
application.add_handler(CommandHandler("help", self.help_command))
application.add_handler(CommandHandler("status", self.status_command))
application.add_handler(CommandHandler("scan", self.scan_command)) # New scan command
# URL handler (matches URLs)
url_filter = filters.Regex(r'https?://[^\s]+') | filters.Regex(r'^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}.*')
application.add_handler(MessageHandler(url_filter, self.handle_url))
# Unknown message handler
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_unknown))
# Start bot
self.logger.info("Starting Telegram bot...")
print("🤖 Telegram bot is starting...")
print("📱 Send /start to your bot to begin!")
application.run_polling(allowed_updates=Update.ALL_TYPES)
def main():
"""Main function to run the Telegram bot."""
try:
bot = TelegramSecurityScanner()
bot.run()
except ValueError as e:
print(f"❌ Configuration error: {e}")
print("💡 Please set TELEGRAM_BOT_TOKEN in your .env file")
return 1
except KeyboardInterrupt:
print("\n👋 Bot stopped by user")
return 0
except Exception as e:
print(f"❌ Bot failed to start: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())