feat: chat language display, disabled EntitySelect items, dev scripts
Chat language: - Added language_code field to TelegramChat model + migration - Saved from message.from.language_code on webhook/polling - Displayed as badge on bot chat cards and target receiver items - Resolved from DB in target API response (works for existing receivers) - Shown in chat picker dropdown (desc includes language) EntitySelect improvements: - Tracker-target link selector shows all targets, already-linked ones appear disabled with "Already linked" hint - Receiver chat picker shows already-added chats as disabled Dev scripts: - scripts/restart-backend.sh and restart-frontend.sh - Updated .claude/docs/dev-servers.md to reference scripts
This commit is contained in:
@@ -87,8 +87,9 @@ async def list_targets(
|
||||
)
|
||||
target_receivers[tgt.id] = list(recv_result.all())
|
||||
|
||||
# Resolve chat names from receivers for telegram targets
|
||||
# Resolve chat names and languages from receivers for telegram targets
|
||||
chat_names: dict[str, str] = {}
|
||||
chat_languages: dict[str, str] = {}
|
||||
for tgt in targets:
|
||||
if tgt.type == "telegram":
|
||||
bot_id = tgt.config.get("bot_id")
|
||||
@@ -106,9 +107,12 @@ async def list_targets(
|
||||
chat = chat_result.first()
|
||||
if chat:
|
||||
chat_names[f"{bot_id}_{chat_id}"] = chat.title or chat.username or ""
|
||||
lang = getattr(chat, 'language_code', '') or ''
|
||||
if lang:
|
||||
chat_languages[f"{bot_id}_{chat_id}"] = lang
|
||||
|
||||
return [
|
||||
_target_response(t, chat_names, target_receivers.get(t.id, []))
|
||||
_target_response(t, chat_names, target_receivers.get(t.id, []), chat_languages)
|
||||
for t in targets
|
||||
]
|
||||
|
||||
@@ -257,6 +261,7 @@ def _target_response(
|
||||
target: NotificationTarget,
|
||||
chat_names: dict[str, str] | None = None,
|
||||
receivers: list[TargetReceiver] | None = None,
|
||||
chat_languages: dict[str, str] | None = None,
|
||||
) -> dict:
|
||||
recv_list = receivers or []
|
||||
resp = {
|
||||
@@ -287,6 +292,8 @@ def _target_response(
|
||||
key = f"{bot_id}_{chat_id}"
|
||||
if key in chat_names:
|
||||
recv_resp["chat_name"] = chat_names[key]
|
||||
if chat_languages and key in chat_languages:
|
||||
recv_resp["language_code"] = chat_languages[key]
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
import aiohttp
|
||||
|
||||
from notify_bridge_core.notifications.telegram.media import TELEGRAM_API_BASE_URL
|
||||
from notify_bridge_core.notifications.telegram.client import TelegramClient
|
||||
|
||||
from ..auth.dependencies import get_current_user
|
||||
from ..commands.handler import register_commands_with_telegram
|
||||
@@ -291,22 +291,9 @@ async def test_chat(
|
||||
"""Send a test message to a chat via the bot."""
|
||||
bot = await _get_user_bot(session, bot_id, user.id)
|
||||
message = _get_test_message(locale, "telegram")
|
||||
try:
|
||||
async with aiohttp.ClientSession() as http:
|
||||
async with http.post(
|
||||
f"{TELEGRAM_API_BASE_URL}{bot.token}/sendMessage",
|
||||
json={
|
||||
"chat_id": chat_id,
|
||||
"text": message,
|
||||
"parse_mode": "HTML",
|
||||
},
|
||||
) as resp:
|
||||
data = await resp.json()
|
||||
if data.get("ok"):
|
||||
return {"success": True}
|
||||
return {"success": False, "error": data.get("description", "Unknown error")}
|
||||
except aiohttp.ClientError as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
async with aiohttp.ClientSession() as http:
|
||||
client = TelegramClient(http, bot.token)
|
||||
return await client.send_message(chat_id, message)
|
||||
|
||||
|
||||
@router.delete("/{bot_id}/chats/{chat_db_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -328,57 +315,42 @@ async def delete_chat(
|
||||
# --- Helpers ---
|
||||
|
||||
async def _get_webhook_info(token: str) -> dict | None:
|
||||
"""Call Telegram getWebhookInfo to check current webhook state."""
|
||||
try:
|
||||
async with aiohttp.ClientSession() as http:
|
||||
async with http.get(f"{TELEGRAM_API_BASE_URL}{token}/getWebhookInfo") as resp:
|
||||
data = await resp.json()
|
||||
if data.get("ok"):
|
||||
return data.get("result", {})
|
||||
except aiohttp.ClientError:
|
||||
pass
|
||||
return None
|
||||
"""Call Telegram getWebhookInfo via TelegramClient."""
|
||||
async with aiohttp.ClientSession() as http:
|
||||
client = TelegramClient(http, token)
|
||||
result = await client.get_webhook_info()
|
||||
return result.get("result") if result.get("success") else None
|
||||
|
||||
|
||||
async def _get_me(token: str) -> dict | None:
|
||||
"""Call Telegram getMe to validate token and get bot info."""
|
||||
try:
|
||||
async with aiohttp.ClientSession() as http:
|
||||
async with http.get(f"{TELEGRAM_API_BASE_URL}{token}/getMe") as resp:
|
||||
data = await resp.json()
|
||||
if data.get("ok"):
|
||||
return data.get("result", {})
|
||||
except aiohttp.ClientError:
|
||||
pass
|
||||
return None
|
||||
"""Call Telegram getMe via TelegramClient."""
|
||||
async with aiohttp.ClientSession() as http:
|
||||
client = TelegramClient(http, token)
|
||||
result = await client.get_me()
|
||||
return result.get("result") if result.get("success") else None
|
||||
|
||||
|
||||
async def _fetch_chats_from_telegram(token: str) -> list[dict]:
|
||||
"""Fetch chats from Telegram getUpdates API."""
|
||||
seen: dict[int, dict] = {}
|
||||
try:
|
||||
async with aiohttp.ClientSession() as http:
|
||||
async with http.get(
|
||||
f"{TELEGRAM_API_BASE_URL}{token}/getUpdates",
|
||||
params={"limit": 100, "allowed_updates": '["message"]'},
|
||||
) as resp:
|
||||
data = await resp.json()
|
||||
if not data.get("ok"):
|
||||
return []
|
||||
for update in data.get("result", []):
|
||||
msg = update.get("message", {})
|
||||
chat = msg.get("chat", {})
|
||||
chat_id = chat.get("id")
|
||||
if chat_id and chat_id not in seen:
|
||||
seen[chat_id] = {
|
||||
"id": chat_id,
|
||||
"title": chat.get("title") or (chat.get("first_name", "") + (" " + chat.get("last_name", "")).strip()),
|
||||
"type": chat.get("type", "private"),
|
||||
"username": chat.get("username", ""),
|
||||
}
|
||||
except aiohttp.ClientError:
|
||||
pass
|
||||
return list(seen.values())
|
||||
"""Fetch chats from Telegram getUpdates via TelegramClient."""
|
||||
async with aiohttp.ClientSession() as http:
|
||||
client = TelegramClient(http, token)
|
||||
result = await client.get_updates(limit=100)
|
||||
if not result.get("success"):
|
||||
return []
|
||||
|
||||
seen: dict[int, dict] = {}
|
||||
for update in result.get("result", []):
|
||||
msg = update.get("message", {})
|
||||
chat = msg.get("chat", {})
|
||||
chat_id = chat.get("id")
|
||||
if chat_id and chat_id not in seen:
|
||||
seen[chat_id] = {
|
||||
"id": chat_id,
|
||||
"title": chat.get("title") or (chat.get("first_name", "") + (" " + chat.get("last_name", "")).strip()),
|
||||
"type": chat.get("type", "private"),
|
||||
"username": chat.get("username", ""),
|
||||
}
|
||||
return list(seen.values())
|
||||
|
||||
|
||||
def _chat_response(c: TelegramChat) -> dict:
|
||||
@@ -388,6 +360,7 @@ def _chat_response(c: TelegramChat) -> dict:
|
||||
"title": c.title,
|
||||
"type": c.chat_type,
|
||||
"username": c.username,
|
||||
"language_code": getattr(c, 'language_code', '') or '',
|
||||
"discovered_at": c.discovered_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user