Fix keyboard WebApp identity and sendData
This commit is contained in:
parent
727a9b3793
commit
96e7068755
4 changed files with 55 additions and 20 deletions
22
app/bot.py
22
app/bot.py
|
|
@ -19,6 +19,10 @@ logger = logging.getLogger(__name__)
|
|||
CATEGORY_PATTERN = re.compile(r"^[^\x00-\x1f]{1,40}$")
|
||||
|
||||
|
||||
def webapp_url_for_user(settings: Settings, user_id: int) -> str:
|
||||
return f"{settings.webapp_url}?user_id={user_id}&mode=keyboard"
|
||||
|
||||
|
||||
def parse_transaction(raw: str) -> TransactionInput:
|
||||
try:
|
||||
payload: dict[str, Any] = json.loads(raw)
|
||||
|
|
@ -65,12 +69,14 @@ class TelegramBotService:
|
|||
self.thread: Thread | None = None
|
||||
self._register_handlers()
|
||||
|
||||
def _keyboard(self) -> types.ReplyKeyboardMarkup:
|
||||
def _keyboard(self, user_id: int) -> types.ReplyKeyboardMarkup:
|
||||
keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
keyboard.add(
|
||||
types.KeyboardButton(
|
||||
text="Открыть Пожитки",
|
||||
web_app=types.WebAppInfo(url=self.settings.webapp_url),
|
||||
web_app=types.WebAppInfo(
|
||||
url=webapp_url_for_user(self.settings, user_id)
|
||||
),
|
||||
)
|
||||
)
|
||||
return keyboard
|
||||
|
|
@ -99,7 +105,7 @@ class TelegramBotService:
|
|||
prefix
|
||||
+ "Пожитки считают доходы, расходы и процент накоплений. "
|
||||
"Нажмите кнопку ниже, чтобы открыть приложение.",
|
||||
reply_markup=self._keyboard(),
|
||||
reply_markup=self._keyboard(user.id),
|
||||
)
|
||||
|
||||
@self.bot.message_handler(content_types=["web_app_data"])
|
||||
|
|
@ -115,7 +121,7 @@ class TelegramBotService:
|
|||
self.bot.send_message(
|
||||
message.chat.id,
|
||||
f"Не получилось сохранить операцию: {exc}",
|
||||
reply_markup=self._keyboard(),
|
||||
reply_markup=self._keyboard(user.id),
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -123,15 +129,19 @@ class TelegramBotService:
|
|||
self.bot.send_message(
|
||||
message.chat.id,
|
||||
f"{operation} сохранён в категории «{item.category}». Откройте приложение снова, чтобы увидеть обновлённую статистику.",
|
||||
reply_markup=self._keyboard(),
|
||||
reply_markup=self._keyboard(user.id),
|
||||
)
|
||||
|
||||
@self.bot.message_handler(commands=["help"])
|
||||
def help_message(message: types.Message) -> None:
|
||||
user = message.from_user
|
||||
if user is None:
|
||||
return
|
||||
self._save_user(user)
|
||||
self.bot.send_message(
|
||||
message.chat.id,
|
||||
"Добавляйте операции через WebApp. Ссылка «Стать друзьями» находится на вкладке рейтинга.",
|
||||
reply_markup=self._keyboard(),
|
||||
reply_markup=self._keyboard(user.id),
|
||||
)
|
||||
|
||||
def _save_user(self, user: types.User) -> None:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
const tgUser = tg?.initDataUnsafe?.user;
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const previewUserId = Number(query.get("user_id")) || 0;
|
||||
const user = tgUser || (previewUserId ? { id: previewUserId, first_name: "Предпросмотр" } : null);
|
||||
const keyboardLaunch = query.get("mode") === "keyboard";
|
||||
const user = tgUser || (previewUserId ? { id: previewUserId } : null);
|
||||
const userId = Number(user?.id) || 0;
|
||||
|
||||
const state = { kind: "income", board: "all", summary: null, config: null };
|
||||
|
|
@ -18,11 +19,14 @@
|
|||
|
||||
function initTelegram() {
|
||||
if (!tg) return;
|
||||
tg.ready();
|
||||
tg.expand();
|
||||
tg.enableClosingConfirmation?.();
|
||||
if (tg.isVersionAtLeast?.("6.1")) tg.setHeaderColor("secondary_bg_color");
|
||||
if (tg.isVersionAtLeast?.("7.10")) tg.setBottomBarColor("secondary_bg_color");
|
||||
const safely = (action) => {
|
||||
try { action(); } catch (error) { console.warn("Telegram WebApp method failed", error); }
|
||||
};
|
||||
safely(() => tg.ready());
|
||||
safely(() => tg.expand());
|
||||
safely(() => tg.enableClosingConfirmation?.());
|
||||
if (tg.isVersionAtLeast?.("6.1")) safely(() => tg.setHeaderColor("secondary_bg_color"));
|
||||
if (tg.isVersionAtLeast?.("7.10")) safely(() => tg.setBottomBarColor("secondary_bg_color"));
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
|
|
@ -32,8 +36,10 @@
|
|||
window.setTimeout(() => toast.classList.remove("show"), 2600);
|
||||
}
|
||||
|
||||
function setIdentity() {
|
||||
const name = [user?.first_name, user?.last_name].filter(Boolean).join(" ") || "Гость";
|
||||
function setIdentity(profile = user) {
|
||||
const name = profile?.nickname
|
||||
|| [profile?.first_name, profile?.last_name].filter(Boolean).join(" ")
|
||||
|| (userId ? "Загрузка…" : "Откройте из бота");
|
||||
$("#userName").textContent = name;
|
||||
$("#userInitial").textContent = name.slice(0, 1).toUpperCase();
|
||||
}
|
||||
|
|
@ -97,12 +103,18 @@
|
|||
showToast("Запись получилась слишком длинной");
|
||||
return;
|
||||
}
|
||||
if (!tg?.sendData || !tg.initData) {
|
||||
showToast("Сохранение доступно при открытии из Telegram");
|
||||
const isTelegramClient = Boolean(tg?.platform && tg.platform !== "unknown");
|
||||
if (!tg?.sendData || !isTelegramClient || !keyboardLaunch) {
|
||||
showToast("Отправьте /start боту и откройте приложение кнопкой под сообщением");
|
||||
return;
|
||||
}
|
||||
tg.HapticFeedback?.notificationOccurred("success");
|
||||
tg.sendData(encoded);
|
||||
try {
|
||||
tg.HapticFeedback?.notificationOccurred("success");
|
||||
tg.sendData(encoded);
|
||||
} catch (error) {
|
||||
console.error("Telegram sendData failed", error);
|
||||
showToast("Telegram не принял запись. Обновите приложение и попробуйте снова");
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJson(url) {
|
||||
|
|
@ -115,6 +127,7 @@
|
|||
if (!userId) return;
|
||||
try {
|
||||
state.summary = await fetchJson(`../api/users/${userId}/summary`);
|
||||
setIdentity(state.summary);
|
||||
renderSummary(state.summary);
|
||||
} catch (error) {
|
||||
console.warn("Summary is not available", error);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<title>Пожитки</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
<script src="./telegram-web-app.js"></script>
|
||||
<script src="./app.js" defer></script>
|
||||
<script src="./app.js?v=2" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from app.bot import parse_transaction
|
||||
from app.bot import parse_transaction, webapp_url_for_user
|
||||
from app.config import Settings
|
||||
from app.db import Database
|
||||
|
||||
|
||||
|
|
@ -73,3 +74,14 @@ def test_filtered_leaderboard(tmp_path: Path) -> None:
|
|||
def test_rejects_invalid_webapp_payload(payload: str) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
parse_transaction(payload)
|
||||
|
||||
|
||||
def test_personalized_keyboard_webapp_url(tmp_path: Path) -> None:
|
||||
settings = Settings(
|
||||
telegram_token="token",
|
||||
base_app_url="https://example.com/",
|
||||
data_dir=tmp_path,
|
||||
)
|
||||
assert webapp_url_for_user(settings, 123456) == (
|
||||
"https://example.com/app/?user_id=123456&mode=keyboard"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue