This commit is contained in:
2026-01-27 16:02:24 +03:00
parent ec9c4dc55f
commit 35e7ab6066

114
main.py
View File

@@ -1,6 +1,9 @@
import os import os
import logging import logging
import subprocess import subprocess
import email
from email import policy
from email.parser import BytesParser
from telegram import Update from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
@@ -32,7 +35,8 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
'Available commands:\n' 'Available commands:\n'
'/start - Start the bot\n' '/start - Start the bot\n'
'/help - Show help message\n' '/help - Show help message\n'
'/checkmail - Check new emails' '/checkmail - List new emails\n'
'/readmail <number> - Read a specific email'
) )
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -41,7 +45,8 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
'Available commands:\n' 'Available commands:\n'
'/start - Start the bot\n' '/start - Start the bot\n'
'/help - Show this help message\n' '/help - Show this help message\n'
'/checkmail - Check new emails in your mailbox\n' '/checkmail - List new emails in your mailbox\n'
'/readmail <number> - Read a specific email (e.g., /readmail 1)\n'
'\nJust send me any text and I will echo it back!' '\nJust send me any text and I will echo it back!'
) )
@@ -75,15 +80,19 @@ async def check_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not emails or emails == ['']: if not emails or emails == ['']:
await update.message.reply_text("📭 No new emails!") await update.message.reply_text("📭 No new emails!")
# Clear stored emails
context.user_data['emails'] = []
return return
# Store email list in user context for readmail command
context.user_data['emails'] = emails
# Format the response # Format the response
response = f"📬 You have {len(emails)} new email(s):\n\n" response = f"📬 You have {len(emails)} new email(s):\n\n"
for i, email in enumerate(emails, 1): for i, email_file in enumerate(emails, 1):
# Parse email filename to extract info # Parse email filename to extract info
# Format: timestamp.M###P###.mail,S=size,W=size parts = email_file.split(',')
parts = email.split(',') timestamp_part = parts[0] if parts else email_file
timestamp_part = parts[0] if parts else email
size_part = parts[1] if len(parts) > 1 else "" size_part = parts[1] if len(parts) > 1 else ""
# Extract size in bytes # Extract size in bytes
@@ -100,6 +109,7 @@ async def check_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
response += f"{i}. {timestamp_part} ({size_str})\n" response += f"{i}. {timestamp_part} ({size_str})\n"
response += f"\n💡 Use /readmail <number> to read an email"
await update.message.reply_text(response) await update.message.reply_text(response)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
@@ -110,6 +120,97 @@ async def check_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
logger.error(f"Error checking mail: {e}") logger.error(f"Error checking mail: {e}")
await update.message.reply_text(f"❌ An error occurred: {str(e)}") await update.message.reply_text(f"❌ An error occurred: {str(e)}")
async def read_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Read a specific email by number."""
# Check authorization
if not check_authorization(update):
await update.message.reply_text("❌ You are not authorized to use this command.")
logger.warning(f"Unauthorized access attempt by user {update.effective_user.id}")
return
# Check if user has checked mail first
if 'emails' not in context.user_data or not context.user_data['emails']:
await update.message.reply_text("📭 Please use /checkmail first to see available emails.")
return
# Parse email number from command
if not context.args:
await update.message.reply_text("❌ Please specify an email number. Usage: /readmail 1")
return
try:
email_num = int(context.args[0])
emails = context.user_data['emails']
if email_num < 1 or email_num > len(emails):
await update.message.reply_text(f"❌ Invalid email number. Please choose between 1 and {len(emails)}")
return
# Get the email filename (array is 0-indexed)
email_filename = emails[email_num - 1]
email_path = f"/var/mail/fymio.us/me/new/{email_filename}"
# Read email content using docker exec
result = subprocess.run(
['docker', 'exec', 'mailserver', 'cat', email_path],
capture_output=True,
timeout=10
)
if result.returncode != 0:
await update.message.reply_text(f"❌ Error reading email:\n`{result.stderr.decode()}`", parse_mode='Markdown')
return
# Parse email
msg = BytesParser(policy=policy.default).parsebytes(result.stdout)
# Extract email details
subject = msg.get('Subject', 'No Subject')
from_addr = msg.get('From', 'Unknown')
to_addr = msg.get('To', 'Unknown')
date = msg.get('Date', 'Unknown')
# Get email body
body = ""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == "text/plain":
try:
body = part.get_content()
break
except:
pass
else:
try:
body = msg.get_content()
except:
body = "Could not extract email body"
# Truncate body if too long (Telegram has message length limits)
max_body_length = 3000
if len(body) > max_body_length:
body = body[:max_body_length] + "\n\n... (truncated)"
# Format response
response = f"📧 **Email #{email_num}**\n\n"
response += f"**From:** {from_addr}\n"
response += f"**To:** {to_addr}\n"
response += f"**Date:** {date}\n"
response += f"**Subject:** {subject}\n"
response += f"\n━━━━━━━━━━━━━━━━━━\n\n"
response += body
await update.message.reply_text(response, parse_mode='Markdown')
except ValueError:
await update.message.reply_text("❌ Invalid email number. Please provide a number.")
except subprocess.TimeoutExpired:
await update.message.reply_text("❌ Command timed out while reading email.")
except Exception as e:
logger.error(f"Error reading mail: {e}")
await update.message.reply_text(f"❌ An error occurred: {str(e)}")
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE): async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Echo the user message.""" """Echo the user message."""
await update.message.reply_text(f"You said: {update.message.text}") await update.message.reply_text(f"You said: {update.message.text}")
@@ -127,6 +228,7 @@ def main():
application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("help", help_command)) application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("checkmail", check_mail)) application.add_handler(CommandHandler("checkmail", check_mail))
application.add_handler(CommandHandler("readmail", read_mail))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo)) application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
# Start the Bot # Start the Bot