upd
This commit is contained in:
211
main.py
211
main.py
@@ -2,10 +2,12 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import email
|
import email
|
||||||
|
import tempfile
|
||||||
|
import html2text
|
||||||
from email import policy
|
from email import policy
|
||||||
from email.parser import BytesParser
|
from email.parser import BytesParser
|
||||||
from telegram import Update
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
|
from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ContextTypes
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -30,32 +32,59 @@ def check_authorization(update: Update) -> bool:
|
|||||||
|
|
||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Send a message when the command /start is issued."""
|
"""Send a message when the command /start is issued."""
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton("📬 Check Mail", callback_data="checkmail")],
|
||||||
|
[InlineKeyboardButton("❓ Help", callback_data="help")]
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
'Hi! I am Fymious Bot. Send me any message and I will echo it back!\n\n'
|
'Hi! I am Fymious Bot 🤖\n\n'
|
||||||
'Available commands:\n'
|
'Use the buttons below to check your emails!',
|
||||||
'/start - Start the bot\n'
|
reply_markup=reply_markup
|
||||||
'/help - Show help message\n'
|
|
||||||
'/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):
|
||||||
"""Send a message when the command /help is issued."""
|
"""Send a message when the command /help is issued."""
|
||||||
await update.message.reply_text(
|
query = update.callback_query
|
||||||
|
if query:
|
||||||
|
await query.answer()
|
||||||
|
message = query.message
|
||||||
|
else:
|
||||||
|
message = update.message
|
||||||
|
|
||||||
|
keyboard = [[InlineKeyboardButton("📬 Check Mail", callback_data="checkmail")]]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
help_text = (
|
||||||
'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 - List 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'
|
'/readmail <number> - Read a specific email\n\n'
|
||||||
'\nJust send me any text and I will echo it back!'
|
'Or just use the buttons below!'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if query:
|
||||||
|
await query.edit_message_text(help_text, reply_markup=reply_markup)
|
||||||
|
else:
|
||||||
|
await message.reply_text(help_text, reply_markup=reply_markup)
|
||||||
|
|
||||||
async def check_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def check_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Check for new emails in the mailserver."""
|
"""Check for new emails in the mailserver."""
|
||||||
|
query = update.callback_query
|
||||||
|
if query:
|
||||||
|
await query.answer()
|
||||||
|
message = query.message
|
||||||
|
user = query.from_user
|
||||||
|
else:
|
||||||
|
message = update.message
|
||||||
|
user = update.effective_user
|
||||||
|
|
||||||
# Check authorization
|
# Check authorization
|
||||||
if not check_authorization(update):
|
if not check_authorization(update):
|
||||||
await update.message.reply_text("❌ You are not authorized to use this command.")
|
await message.reply_text("❌ You are not authorized to use this command.")
|
||||||
logger.warning(f"Unauthorized access attempt by user {update.effective_user.id}")
|
logger.warning(f"Unauthorized access attempt by user {user.id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -68,7 +97,7 @@ async def check_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
await update.message.reply_text(
|
await message.reply_text(
|
||||||
f"❌ Error checking mail:\n`{result.stderr}`",
|
f"❌ Error checking mail:\n`{result.stderr}`",
|
||||||
parse_mode='Markdown'
|
parse_mode='Markdown'
|
||||||
)
|
)
|
||||||
@@ -79,71 +108,97 @@ async def check_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
emails = [e.strip().strip("'") for e in emails if e.strip()]
|
emails = [e.strip().strip("'") for e in emails if e.strip()]
|
||||||
|
|
||||||
if not emails or emails == ['']:
|
if not emails or emails == ['']:
|
||||||
await update.message.reply_text("📭 No new emails!")
|
keyboard = [[InlineKeyboardButton("🔄 Refresh", callback_data="checkmail")]]
|
||||||
# Clear stored emails
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await message.reply_text("📭 No new emails!", reply_markup=reply_markup)
|
||||||
context.user_data['emails'] = []
|
context.user_data['emails'] = []
|
||||||
return
|
return
|
||||||
|
|
||||||
# Store email list in user context for readmail command
|
# Store email list in user context
|
||||||
context.user_data['emails'] = emails
|
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_file in enumerate(emails, 1):
|
for i, email_file in enumerate(emails, 1):
|
||||||
# Parse email filename to extract info
|
|
||||||
parts = email_file.split(',')
|
parts = email_file.split(',')
|
||||||
timestamp_part = parts[0] if parts else email_file
|
|
||||||
size_part = parts[1] if len(parts) > 1 else ""
|
size_part = parts[1] if len(parts) > 1 else ""
|
||||||
|
|
||||||
# Extract size in bytes
|
|
||||||
size_bytes = ""
|
|
||||||
if size_part.startswith('S='):
|
if size_part.startswith('S='):
|
||||||
size_bytes = size_part[2:]
|
|
||||||
try:
|
try:
|
||||||
size_kb = int(size_bytes) / 1024
|
size_kb = int(size_part[2:]) / 1024
|
||||||
size_str = f"{size_kb:.1f} KB"
|
size_str = f"{size_kb:.1f} KB"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
size_str = size_bytes + " bytes"
|
size_str = "?"
|
||||||
else:
|
else:
|
||||||
size_str = "unknown size"
|
size_str = "?"
|
||||||
|
|
||||||
response += f"{i}. {timestamp_part} ({size_str})\n"
|
response += f"{i}. Email ({size_str})\n"
|
||||||
|
|
||||||
response += f"\n💡 Use /readmail <number> to read an email"
|
# Create inline keyboard with buttons for each email
|
||||||
await update.message.reply_text(response)
|
keyboard = []
|
||||||
|
# Add buttons in rows of 2
|
||||||
|
for i in range(1, len(emails) + 1, 2):
|
||||||
|
row = [InlineKeyboardButton(f"📧 #{i}", callback_data=f"read_{i}")]
|
||||||
|
if i + 1 <= len(emails):
|
||||||
|
row.append(InlineKeyboardButton(f"📧 #{i+1}", callback_data=f"read_{i+1}"))
|
||||||
|
keyboard.append(row)
|
||||||
|
|
||||||
|
# Add refresh button
|
||||||
|
keyboard.append([InlineKeyboardButton("🔄 Refresh", callback_data="checkmail")])
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
if query:
|
||||||
|
await query.edit_message_text(response, reply_markup=reply_markup)
|
||||||
|
else:
|
||||||
|
await message.reply_text(response, reply_markup=reply_markup)
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
await update.message.reply_text("❌ Command timed out. Mail server might be slow.")
|
await message.reply_text("❌ Command timed out. Mail server might be slow.")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
await update.message.reply_text("❌ Docker command not found. Is Docker installed?")
|
await message.reply_text("❌ Docker command not found. Is Docker installed?")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
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 message.reply_text(f"❌ An error occurred: {str(e)}")
|
||||||
|
|
||||||
async def read_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def read_mail_handler(update: Update, context: ContextTypes.DEFAULT_TYPE, email_num: int = None):
|
||||||
"""Read a specific email by number."""
|
"""Read a specific email by number."""
|
||||||
|
query = update.callback_query
|
||||||
|
if query:
|
||||||
|
await query.answer()
|
||||||
|
message = query.message
|
||||||
|
user = query.from_user
|
||||||
|
else:
|
||||||
|
message = update.message
|
||||||
|
user = update.effective_user
|
||||||
|
|
||||||
# Check authorization
|
# Check authorization
|
||||||
if not check_authorization(update):
|
if not check_authorization(update):
|
||||||
await update.message.reply_text("❌ You are not authorized to use this command.")
|
await message.reply_text("❌ You are not authorized to use this command.")
|
||||||
logger.warning(f"Unauthorized access attempt by user {update.effective_user.id}")
|
logger.warning(f"Unauthorized access attempt by user {user.id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if user has checked mail first
|
# Check if user has checked mail first
|
||||||
if 'emails' not in context.user_data or not context.user_data['emails']:
|
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.")
|
await message.reply_text("📭 Please use /checkmail first to see available emails.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse email number from command
|
# Get email number from callback or command args
|
||||||
if not context.args:
|
if email_num is None:
|
||||||
await update.message.reply_text("❌ Please specify an email number. Usage: /readmail 1")
|
if not context.args:
|
||||||
return
|
await message.reply_text("❌ Please specify an email number. Usage: /readmail 1")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
email_num = int(context.args[0])
|
||||||
|
except ValueError:
|
||||||
|
await message.reply_text("❌ Invalid email number. Please provide a number.")
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
email_num = int(context.args[0])
|
|
||||||
emails = context.user_data['emails']
|
emails = context.user_data['emails']
|
||||||
|
|
||||||
if email_num < 1 or email_num > len(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)}")
|
await message.reply_text(f"❌ Invalid email number. Please choose between 1 and {len(emails)}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the email filename (array is 0-indexed)
|
# Get the email filename (array is 0-indexed)
|
||||||
@@ -158,7 +213,7 @@ async def read_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
await update.message.reply_text(f"❌ Error reading email:\n`{result.stderr.decode()}`", parse_mode='Markdown')
|
await message.reply_text(f"❌ Error reading email:\n`{result.stderr.decode()}`", parse_mode='Markdown')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse email
|
# Parse email
|
||||||
@@ -170,46 +225,87 @@ async def read_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
to_addr = msg.get('To', 'Unknown')
|
to_addr = msg.get('To', 'Unknown')
|
||||||
date = msg.get('Date', 'Unknown')
|
date = msg.get('Date', 'Unknown')
|
||||||
|
|
||||||
# Get email body
|
# Get email body - handle both plain text and HTML
|
||||||
body = ""
|
body = ""
|
||||||
|
html_body = ""
|
||||||
|
|
||||||
if msg.is_multipart():
|
if msg.is_multipart():
|
||||||
for part in msg.walk():
|
for part in msg.walk():
|
||||||
content_type = part.get_content_type()
|
content_type = part.get_content_type()
|
||||||
if content_type == "text/plain":
|
if content_type == "text/plain" and not body:
|
||||||
try:
|
try:
|
||||||
body = part.get_content()
|
body = part.get_content()
|
||||||
break
|
except:
|
||||||
|
pass
|
||||||
|
elif content_type == "text/html" and not html_body:
|
||||||
|
try:
|
||||||
|
html_body = part.get_content()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
body = msg.get_content()
|
content_type = msg.get_content_type()
|
||||||
|
if content_type == "text/html":
|
||||||
|
html_body = msg.get_content()
|
||||||
|
else:
|
||||||
|
body = msg.get_content()
|
||||||
except:
|
except:
|
||||||
body = "Could not extract email body"
|
body = "Could not extract email body"
|
||||||
|
|
||||||
|
# If we have HTML but no plain text, convert HTML to text
|
||||||
|
if html_body and not body:
|
||||||
|
h = html2text.HTML2Text()
|
||||||
|
h.ignore_links = False
|
||||||
|
h.ignore_images = True
|
||||||
|
h.ignore_emphasis = False
|
||||||
|
body = h.handle(html_body)
|
||||||
|
|
||||||
# Truncate body if too long (Telegram has message length limits)
|
# Truncate body if too long (Telegram has message length limits)
|
||||||
max_body_length = 3000
|
max_body_length = 3000
|
||||||
if len(body) > max_body_length:
|
if len(body) > max_body_length:
|
||||||
body = body[:max_body_length] + "\n\n... (truncated)"
|
body = body[:max_body_length] + "\n\n... (truncated)"
|
||||||
|
|
||||||
# Format response
|
# Format response
|
||||||
response = f"📧 **Email #{email_num}**\n\n"
|
response = f"📧 *Email #{email_num}*\n\n"
|
||||||
response += f"**From:** {from_addr}\n"
|
response += f"*From:* {from_addr}\n"
|
||||||
response += f"**To:** {to_addr}\n"
|
response += f"*To:* {to_addr}\n"
|
||||||
response += f"**Date:** {date}\n"
|
response += f"*Date:* {date}\n"
|
||||||
response += f"**Subject:** {subject}\n"
|
response += f"*Subject:* {subject}\n"
|
||||||
response += f"\n━━━━━━━━━━━━━━━━━━\n\n"
|
response += f"\n━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
response += body
|
response += body
|
||||||
|
|
||||||
await update.message.reply_text(response, parse_mode='Markdown')
|
# Add back button
|
||||||
|
keyboard = [[InlineKeyboardButton("⬅️ Back to List", callback_data="checkmail")]]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
if query:
|
||||||
|
await query.edit_message_text(response, parse_mode='Markdown', reply_markup=reply_markup)
|
||||||
|
else:
|
||||||
|
await message.reply_text(response, parse_mode='Markdown', reply_markup=reply_markup)
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
await update.message.reply_text("❌ Invalid email number. Please provide a number.")
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
await update.message.reply_text("❌ Command timed out while reading email.")
|
await message.reply_text("❌ Command timed out while reading email.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error reading mail: {e}")
|
logger.error(f"Error reading mail: {e}", exc_info=True)
|
||||||
await update.message.reply_text(f"❌ An error occurred: {str(e)}")
|
await message.reply_text(f"❌ An error occurred: {str(e)}")
|
||||||
|
|
||||||
|
async def read_mail(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Read mail command handler."""
|
||||||
|
await read_mail_handler(update, context)
|
||||||
|
|
||||||
|
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Handle button callbacks."""
|
||||||
|
query = update.callback_query
|
||||||
|
|
||||||
|
if query.data == "checkmail":
|
||||||
|
await check_mail(update, context)
|
||||||
|
elif query.data == "help":
|
||||||
|
await help_command(update, context)
|
||||||
|
elif query.data.startswith("read_"):
|
||||||
|
email_num = int(query.data.split("_")[1])
|
||||||
|
await read_mail_handler(update, context, email_num)
|
||||||
|
else:
|
||||||
|
await query.answer("Unknown action")
|
||||||
|
|
||||||
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."""
|
||||||
@@ -229,6 +325,7 @@ def main():
|
|||||||
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(CommandHandler("readmail", read_mail))
|
||||||
|
application.add_handler(CallbackQueryHandler(button_callback))
|
||||||
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
||||||
|
|
||||||
# Start the Bot
|
# Start the Bot
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
python-telegram-bot==21.0
|
python-telegram-bot==21.0
|
||||||
|
html2text==2024.2.26
|
||||||
|
playwright==1.49.0
|
||||||
|
|||||||
Reference in New Issue
Block a user