This commit is contained in:
2025-11-01 13:22:58 +03:00
commit d242a09281
5 changed files with 185 additions and 0 deletions

165
app.py Normal file
View File

@@ -0,0 +1,165 @@
import os
import time
import traceback
import re
import email
import logging
from io import BytesIO
from imapclient import IMAPClient
from telegram import Bot
from email.header import decode_header
from email.utils import collapse_rfc2231_value
from dotenv import load_dotenv
# === ЗАГРУЗКА КОНФИГА ===
load_dotenv()
GMAIL_USER = os.getenv("GMAIL_USER")
GMAIL_PASS = os.getenv("GMAIL_PASS")
TOKEN = os.getenv("TOKEN")
CHAT_ID = os.getenv("CHAT_ID")
ALLOWED_SENDER = os.getenv("ALLOWED_SENDER")
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", 60))
# === НАСТРОЙКА ЛОГГЕРА ===
os.makedirs("logs", exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)-8s | %(message)s",
handlers=[
logging.FileHandler("logs/app.log", encoding="utf-8"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# === TELEGRAM BOT ===
bot = Bot(token=TOKEN)
# === ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ===
def escape_markdown(text):
if not text:
return ""
escape_chars = r'_*[]()~`>#+-=|{}.!'
return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', text)
def decode_mime_words(s):
if not s:
return ""
decoded_fragments = decode_header(s)
result = ""
for fragment, encoding in decoded_fragments:
if isinstance(fragment, bytes):
encoding = encoding or "utf-8"
try:
fragment = fragment.decode(encoding, errors="ignore")
except Exception as e:
logger.debug(f"Ошибка декодирования MIME-фрагмента: {e}")
fragment = fragment.decode("utf-8", errors="ignore")
result += fragment
return result
def get_filename(part):
filename = part.get_filename()
if filename:
filename = collapse_rfc2231_value(filename)
filename = decode_mime_words(filename)
return filename
# === ОСНОВНАЯ ЛОГИКА ===
def fetch_new_emails():
try:
logger.info("Подключение к Gmail IMAP...")
with IMAPClient("imap.gmail.com", ssl=True) as server:
server.login(GMAIL_USER, GMAIL_PASS)
logger.info("Успешный вход в Gmail")
server.select_folder("INBOX")
messages = server.search(["UNSEEN"])
logger.info(f"Найдено новых писем: {len(messages)}")
for uid, msg_data in server.fetch(messages, ["RFC822"]).items():
try:
msg = email.message_from_bytes(msg_data[b"RFC822"])
from_raw = decode_mime_words(msg.get("from", ""))
subject = decode_mime_words(msg.get("subject", ""))
logger.info(f"Обработка письма: '{subject}' от {from_raw}")
# Фильтр по адресу
if ALLOWED_SENDER and ALLOWED_SENDER.lower() not in from_raw.lower():
logger.info(f"Пропущено письмо от неразрешённого адреса: {from_raw}")
continue
text = ""
attachments = []
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
disposition = part.get("Content-Disposition", "")
if content_type == "text/plain" and "attachment" not in disposition:
try:
text += part.get_payload(decode=True).decode(errors="ignore")
except Exception as e:
logger.warning(f"Ошибка чтения текстовой части: {e}")
elif "attachment" in disposition:
filename = get_filename(part)
payload = part.get_payload(decode=True)
if filename and payload:
attachments.append((filename, payload))
else:
text = msg.get_payload(decode=True).decode(errors="ignore")
# Формируем сообщение
message_text = f"📧 *{escape_markdown(subject)}*\nОт: {escape_markdown(from_raw)}\n\n{escape_markdown(text[:3500])}"
# Отправка текста
try:
bot.send_message(chat_id=CHAT_ID, text=message_text, parse_mode="MarkdownV2")
logger.info("Сообщение успешно отправлено в Telegram")
except Exception as e:
logger.error(f"Ошибка при отправке текста письма в Telegram: {e}")
# Отправка вложений
for filename, payload in attachments:
try:
bio = BytesIO(payload)
bio.name = filename
bot.send_document(chat_id=CHAT_ID, document=bio)
logger.info(f"Вложение отправлено: {filename}")
except Exception as e:
logger.error(f"Не удалось отправить вложение {filename}: {e}")
# Отмечаем как прочитанное
server.add_flags(uid, [b'\\Seen'])
logger.info("Письмо отмечено как прочитанное")
except Exception as e:
logger.error(f"Ошибка при обработке письма UID {uid}: {e}")
traceback.print_exc()
server.logout()
logger.info("Отключено от Gmail")
except Exception as e:
logger.error(f"Ошибка при подключении к Gmail: {e}")
traceback.print_exc()
# === ТОЧКА ВХОДА ===
if __name__ == "__main__":
logger.info("🚀 Сервис пересылки писем запущен")
while True:
try:
fetch_new_emails()
except Exception as e:
logger.error(f"Необработанная ошибка: {e}")
traceback.print_exc()
logger.info(f"Ожидание {CHECK_INTERVAL} секунд до следующей проверки...")
time.sleep(CHECK_INTERVAL)