update sign up notifications
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 9s
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 9s
This commit is contained in:
+20
@@ -70,6 +70,26 @@ async def send_telegram_alert(markdown_message: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_telegram_photo(photo, markdown_caption: str):
|
||||||
|
"""Send a photo. `photo` may be raw bytes / a file-like object (uploaded
|
||||||
|
directly to Telegram) or a public URL string."""
|
||||||
|
logger.info("[TELEGRAM] Preparing to send photo...")
|
||||||
|
kwargs = {}
|
||||||
|
if TOPIC_ID:
|
||||||
|
kwargs["message_thread_id"] = int(TOPIC_ID)
|
||||||
|
await bot.send_photo(
|
||||||
|
chat_id=str(CHAT_ID),
|
||||||
|
photo=photo,
|
||||||
|
caption=markdown_caption,
|
||||||
|
parse_mode=ParseMode.MARKDOWN_V2,
|
||||||
|
# Uploading media is slower than a text send; the defaults (5s) time out.
|
||||||
|
connect_timeout=15,
|
||||||
|
read_timeout=30,
|
||||||
|
write_timeout=60,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def escape_markdown_v2(text: str) -> str:
|
def escape_markdown_v2(text: str) -> str:
|
||||||
escape_chars = r"_~`>#+-=|{}.!\\"
|
escape_chars = r"_~`>#+-=|{}.!\\"
|
||||||
return re.sub(f"([{re.escape(escape_chars)}])", r"\\\1", text)
|
return re.sub(f"([{re.escape(escape_chars)}])", r"\\\1", text)
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class Command(BaseCommand):
|
|||||||
help = "Seeds baseline reference data (atolls and islands) required for sign up."
|
help = "Seeds baseline reference data (atolls and islands) required for sign up."
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
atoll, atoll_created = Atoll.objects.get_or_create(name="Faafu")
|
# Atoll name must match the person-verify API's `atoll_en` (the atoll
|
||||||
|
# code letter, e.g. "F" for Faafu) or user verification fails.
|
||||||
|
atoll, atoll_created = Atoll.objects.get_or_create(name="F")
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(f"Created atoll: {atoll.name}")
|
self.style.SUCCESS(f"Created atoll: {atoll.name}")
|
||||||
if atoll_created
|
if atoll_created
|
||||||
|
|||||||
+65
-15
@@ -9,13 +9,20 @@ from django.utils import timezone
|
|||||||
|
|
||||||
# from api.notifications import send_clean_telegram_markdown
|
# from api.notifications import send_clean_telegram_markdown
|
||||||
from api.omada import Omada
|
from api.omada import Omada
|
||||||
from api.bot import send_telegram_alert, telegram_loop, escape_markdown_v2
|
from api.bot import (
|
||||||
|
send_telegram_alert,
|
||||||
|
send_telegram_photo,
|
||||||
|
telegram_loop,
|
||||||
|
escape_markdown_v2,
|
||||||
|
)
|
||||||
import asyncio
|
import asyncio
|
||||||
from apibase.env import env, BASE_DIR
|
from apibase.env import env, BASE_DIR
|
||||||
from procrastinate.contrib.django import app
|
from procrastinate.contrib.django import app
|
||||||
from procrastinate import builtin_tasks
|
from procrastinate import builtin_tasks
|
||||||
import time
|
import time
|
||||||
|
import io
|
||||||
import requests
|
import requests
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -163,10 +170,58 @@ def verify_user_with_person_api_task(user_id: int):
|
|||||||
response = requests.get(f"{PERSON_VERIFY_BASE_URL}/api/person/{t_user.t_id_card}")
|
response = requests.get(f"{PERSON_VERIFY_BASE_URL}/api/person/{t_user.t_id_card}")
|
||||||
|
|
||||||
|
|
||||||
verification_failed_message = f"""*The following user verification failed*:\n\n*ID Card:* {t_user.t_id_card}\n*Name:* {t_user.t_first_name} {t_user.t_last_name}\n*House Name:* {t_user.t_address}\n*Date of Birth:* {t_user.t_dob}\n*Island:* {(t_user.t_atoll.name if t_user.t_atoll else "N/A")} {(t_user.t_island.name if t_user.t_island else "N/A")}\n*Mobile:* {t_user.t_mobile}\nVisit [SAR Link Portal](https://portal.sarlink.net/users/{user_id}/details) to manually verify this user.
|
user_details = (
|
||||||
"""
|
f"*NID:* {t_user.t_id_card}\n"
|
||||||
|
f"*Name:* {t_user.t_first_name} {t_user.t_last_name}\n"
|
||||||
|
f"*Phone:* {t_user.t_mobile}\n"
|
||||||
|
f"*Date of Birth:* {t_user.t_dob}\n"
|
||||||
|
f"*Address:* {t_user.t_address}\n"
|
||||||
|
f"*Island:* {(t_user.t_atoll.name if t_user.t_atoll else 'N/A')}. "
|
||||||
|
f"{(t_user.t_island.name if t_user.t_island else 'N/A')}"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(verification_failed_message)
|
verification_failed_message = (
|
||||||
|
f"⚠️ *User verification failed*\n\n{user_details}\n"
|
||||||
|
f"Visit [SAR Link Portal](https://portal.sarlink.net/users/{user_id}/details) "
|
||||||
|
f"to manually verify this user."
|
||||||
|
)
|
||||||
|
|
||||||
|
verification_success_message = (
|
||||||
|
f"✅ *New user registered and verified*\n\n{user_details}\n"
|
||||||
|
f"View on [SAR Link Portal](https://portal.sarlink.net/users/{user_id}/details)."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run(coro) -> None:
|
||||||
|
asyncio.run_coroutine_threadsafe(coro, telegram_loop).result()
|
||||||
|
|
||||||
|
def _fetch_photo(url: str) -> io.BytesIO:
|
||||||
|
"""Download the ID photo and convert it to a Telegram-safe JPEG.
|
||||||
|
|
||||||
|
The image lives on a private-network URL that Telegram's servers can't
|
||||||
|
reach, so we fetch the bytes here and upload them directly. Conversion
|
||||||
|
to JPEG avoids Telegram rejecting formats like webp.
|
||||||
|
"""
|
||||||
|
resp = requests.get(url, timeout=10)
|
||||||
|
resp.raise_for_status()
|
||||||
|
buf = io.BytesIO()
|
||||||
|
Image.open(io.BytesIO(resp.content)).convert("RGB").save(buf, format="JPEG")
|
||||||
|
buf.seek(0)
|
||||||
|
buf.name = "photo.jpg"
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def send_telegram(message: str, photo_url: str | None = None) -> None:
|
||||||
|
caption = escape_markdown_v2(message)
|
||||||
|
if photo_url:
|
||||||
|
try:
|
||||||
|
_run(send_telegram_photo(_fetch_photo(photo_url), caption))
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[Registration] TELEGRAM PHOTO ERROR: {e}")
|
||||||
|
# Fall through to a plain text alert below.
|
||||||
|
try:
|
||||||
|
_run(send_telegram_alert(markdown_message=caption))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[Registration] TELEGRAM ALERT ERROR: {e}")
|
||||||
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
@@ -177,6 +232,7 @@ def verify_user_with_person_api_task(user_id: int):
|
|||||||
api_dob = data.get("dob")
|
api_dob = data.get("dob")
|
||||||
api_atoll = data.get("atoll_en")
|
api_atoll = data.get("atoll_en")
|
||||||
api_island_name = data.get("island_name_en")
|
api_island_name = data.get("island_name_en")
|
||||||
|
api_image_url = data.get("image_url")
|
||||||
|
|
||||||
if not t_user.t_mobile or t_user.t_dob is None:
|
if not t_user.t_mobile or t_user.t_dob is None:
|
||||||
logger.error("User mobile or date of birth is not set.")
|
logger.error("User mobile or date of birth is not set.")
|
||||||
@@ -230,22 +286,16 @@ def verify_user_with_person_api_task(user_id: int):
|
|||||||
):
|
):
|
||||||
t_user.t_verified = True
|
t_user.t_verified = True
|
||||||
t_user.save()
|
t_user.save()
|
||||||
|
|
||||||
|
logger.info(verification_success_message)
|
||||||
|
send_telegram(verification_success_message, photo_url=api_image_url)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
t_user.t_verified = False
|
t_user.t_verified = False
|
||||||
t_user.save()
|
t_user.save()
|
||||||
|
|
||||||
# send_clean_telegram_markdown(message=verification_failed_message)
|
logger.info(verification_failed_message)
|
||||||
|
send_telegram(verification_failed_message, photo_url=api_image_url)
|
||||||
try:
|
|
||||||
asyncio.run_coroutine_threadsafe(
|
|
||||||
send_telegram_alert(
|
|
||||||
markdown_message=escape_markdown_v2(verification_failed_message)
|
|
||||||
),
|
|
||||||
telegram_loop,
|
|
||||||
).result()
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning("[Registration] TELEGRAM ALERT ERROR", e)
|
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# Handle the error case
|
# Handle the error case
|
||||||
|
|||||||
Reference in New Issue
Block a user