avtopost.kultmost.ru/email_task.ipynb
2026-08-17 02:46:07 -04:00

266 lines
8.1 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "da5a2e63",
"metadata": {},
"outputs": [],
"source": [
"#ignore\n",
"import os\n",
"\n",
"def load_env(file_path):\n",
" \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n",
" with open(file_path) as f:\n",
" for line in f:\n",
" line = line.strip()\n",
" if not line or line.startswith('#'):\n",
" continue\n",
" \n",
" try:\n",
" key, value = line.split('=', 1)\n",
" key = key.strip()\n",
" value = value.strip('\"\\' ')\n",
" # Преобразование строковых значений в булевые\n",
" if value.lower() in ['true', '1', 'yes']:\n",
" value = True\n",
" elif value.lower() in ['false', '0', 'no']:\n",
" value = False\n",
" \n",
" # Проверка, что ключ не пустой\n",
" if key:\n",
" os.environ[key] = str(value) # Храним как строку\n",
" except ValueError:\n",
" print(f\"Ошибка: неверный формат в строке '{line}'\")\n",
"\n",
"try:\n",
" load_env('env')\n",
"except:\n",
" load_env('env_example')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "afd6bc25",
"metadata": {},
"outputs": [],
"source": [
"import smtplib\n",
"from email.mime.multipart import MIMEMultipart\n",
"from email.mime.text import MIMEText\n",
"from celery import Celery\n",
"from config import *\n",
"from db import *\n",
"from templates import templates\n",
"import logging\n",
"from typing import Optional\n",
"import asyncio \n",
"from email.utils import formatdate, make_msgid\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05fadc2d",
"metadata": {},
"outputs": [],
"source": [
"def build_unsubscribe_url(email: str, token: str, base_url: str) -> str:\n",
" return f\"{base_url}/api/v1/email/unsubscribe?token={token}&email={email}\"\n",
"\n",
"\n",
"async def create_email_message(\n",
" to_email: str,\n",
" subject: str,\n",
" html_content: str,\n",
" user_id: int = None,\n",
"):\n",
" msg = MIMEMultipart(\"alternative\")\n",
" \n",
" msg['From'] = f\"{FROM_NAME} <{FROM_EMAIL}>\"\n",
" msg['To'] = to_email\n",
" msg['Subject'] = subject\n",
" msg['Message-ID'] = make_msgid(domain=FROM_EMAIL.split('@')[1])\n",
" msg['Date'] = formatdate(localtime=True)\n",
" msg['X-Priority'] = \"3\"\n",
" msg['Importance'] = \"normal\"\n",
" msg['X-Auto-Response-Suppress'] = \"All\"\n",
" \n",
" # token = await create_unsubscribe_token(to_email, user_id)\n",
" unsubscribe_url = build_unsubscribe_url(to_email, None, FULL_DOMAIN)\n",
" if to_email != FROM_EMAIL:\n",
" msg['List-Unsubscribe'] = f\"<{unsubscribe_url}>\"\n",
" msg['List-Unsubscribe-Post'] = \"List-Unsubscribe=One-Click\"\n",
" \n",
" msg.attach(MIMEText(html_content, 'html'))\n",
" \n",
" return msg\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6687f2ab",
"metadata": {},
"outputs": [],
"source": [
"def connect(email_server, email_port):\n",
" server = smtplib.SMTP(email_server, email_port)\n",
" server.starttls(context=ctx)\n",
" return server\n",
"\n",
"\n",
"def send_email(\n",
" to_email: str,\n",
" html_content: str,\n",
" subject: str,\n",
" user_id: Optional[str] = None,\n",
") -> bool:\n",
" msg =asyncio.run(create_email_message(\n",
" to_email=to_email,\n",
" subject=subject,\n",
" html_content=html_content,\n",
" user_id=user_id,\n",
" ))\n",
"\n",
" try:\n",
" with connect(EMAIL_SMTP_SERVER, EMAIL_SMTP_PORT) as server:\n",
" server.login(FROM_EMAIL, EMAIL_PASS)\n",
" server.send_message(msg)\n",
" # server.close()\n",
"\n",
" logging.info(f\"Email sent successfully to: {to_email} | Subject: {subject}\")\n",
" return True\n",
"\n",
" except Exception as e:\n",
" logging.error(\n",
" f\"Error sending email to: {to_email}, \"\n",
" f\"Subject: {subject}, Error: {str(e)}\"\n",
" )\n",
" return False\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81e35a88",
"metadata": {},
"outputs": [],
"source": [
"celery_app_email = Celery('emails', broker=f'{redis_uri}2', backend=f'{redis_uri}2', task_track_started=True)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30ca10ab",
"metadata": {},
"outputs": [],
"source": [
"@celery_app_email.task(\n",
" bind=True,\n",
" rate_limit='30/m',\n",
" name='email_task.send_email',\n",
" retry_backoff=True,\n",
" retry_backoff_max=600,\n",
" retry_jitter=True,\n",
" max_retries=3,\n",
" soft_time_limit=30,\n",
" time_limit=45,\n",
")\n",
"def notify(self, to_email: str, data: dict, subject: str = EMAIL_SUBJECT, template: str = 'main.html'):\n",
" logging.info(f\"Отправка email на {to_email} (попытка {self.request.retries + 1})\")\n",
" try:\n",
" html_content = templates.get_template(f\"email/{template}\").render({\n",
" \"subject\": subject, \n",
" \"data\": data\n",
" })\n",
" \n",
" ok = send_email(to_email, html_content, subject=subject)\n",
" if ok:\n",
" return ok\n",
" else:\n",
" raise self.retry(exc=exc)\n",
" \n",
" except Exception as exc:\n",
" logging.warning(\n",
" f\"Ошибка при отправке email на {to_email} (попытка {self.request.retries + 1}): {exc}\"\n",
" )\n",
" raise self.retry(exc=exc)\n"
]
},
{
"cell_type": "markdown",
"id": "1837d7c2",
"metadata": {},
"source": [
"## Run"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "daf4fe38",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"\n",
"IGNORE = ['build.ipynb', 'data.ipynb']\n",
"\n",
"for _filename in [i for i in os.listdir() if 'ipynb' in i and i not in IGNORE]:\n",
" filename_without_extension = os.path.splitext(os.path.basename(_filename))[0]\n",
"\n",
" with open(f'{filename_without_extension}.ipynb', 'r') as f:\n",
" data = f.read()\n",
"\n",
" main = json.loads(data)\n",
" app = []\n",
" for cell in main['cells']:\n",
" if '## Run' in cell['source']:\n",
" break\n",
" if '# ignore\\n' in cell['source']:\n",
" continue\n",
" app.append(''.join(cell['source']))\n",
"\n",
" with open(f'{filename_without_extension}.py', 'w') as f:\n",
" f.write('\\n\\n'.join(app))\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f5194611",
"metadata": {},
"outputs": [],
"source": [
"!celery -A email_task.celery_app_email worker --loglevel=info"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}