Fix Telegram bot binding fallback
This commit is contained in:
@@ -102,6 +102,7 @@ export function AccountCenter() {
|
|||||||
lastPaymentStartedAt,
|
lastPaymentStartedAt,
|
||||||
telegramBindOpening,
|
telegramBindOpening,
|
||||||
telegramBindUrl,
|
telegramBindUrl,
|
||||||
|
telegramBindCommand,
|
||||||
manualPayment,
|
manualPayment,
|
||||||
manualTxHash,
|
manualTxHash,
|
||||||
txValidation,
|
txValidation,
|
||||||
@@ -175,6 +176,7 @@ export function AccountCenter() {
|
|||||||
submitManualPaymentTx,
|
submitManualPaymentTx,
|
||||||
validateTxHash,
|
validateTxHash,
|
||||||
handleOverlayCheckout,
|
handleOverlayCheckout,
|
||||||
|
createTelegramBotBindCommand,
|
||||||
openTelegramBotBindLink,
|
openTelegramBotBindLink,
|
||||||
} = useAccountPayment({
|
} = useAccountPayment({
|
||||||
isEn,
|
isEn,
|
||||||
@@ -497,9 +499,7 @@ export function AccountCenter() {
|
|||||||
: monthlyReferralLimit * referralRewardPoints;
|
: monthlyReferralLimit * referralRewardPoints;
|
||||||
|
|
||||||
// ── Telegram bind command ──────────────────────────────
|
// ── Telegram bind command ──────────────────────────────
|
||||||
const bindCommand = userId
|
const bindCommand = telegramBindCommand || copy.telegramBindCommandPlaceholder;
|
||||||
? `/bind ${userId}${email ? ` ${email}` : ""}`
|
|
||||||
: "/bind <supabase_user_id> <email>";
|
|
||||||
|
|
||||||
// ── Copy handler ──────────────────────────────────────
|
// ── Copy handler ──────────────────────────────────────
|
||||||
const handleCopy = (text: string) => {
|
const handleCopy = (text: string) => {
|
||||||
@@ -509,6 +509,13 @@ export function AccountCenter() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCopyTelegramBindCommand = async () => {
|
||||||
|
if (!isAuthenticated || telegramBindOpening) return;
|
||||||
|
const command = await createTelegramBotBindCommand();
|
||||||
|
if (!command) return;
|
||||||
|
handleCopy(command);
|
||||||
|
};
|
||||||
|
|
||||||
const applyReferralCode = useCallback(async () => {
|
const applyReferralCode = useCallback(async () => {
|
||||||
const code = referralCodeInput.trim();
|
const code = referralCodeInput.trim();
|
||||||
if (!code || referralApplying) return;
|
if (!code || referralApplying) return;
|
||||||
@@ -1040,8 +1047,9 @@ export function AccountCenter() {
|
|||||||
: copy.telegramBotBindLink}
|
: copy.telegramBotBindLink}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCopy(bindCommand)}
|
onClick={() => void handleCopyTelegramBindCommand()}
|
||||||
className="rounded-xl border border-blue-700 bg-blue-600 p-4 text-white shadow-sm transition-all hover:bg-blue-700"
|
disabled={telegramBindOpening || !isAuthenticated}
|
||||||
|
className="rounded-xl border border-blue-700 bg-blue-600 p-4 text-white shadow-sm transition-all hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
title={copy.copyCommand}
|
title={copy.copyCommand}
|
||||||
aria-label={copy.copyCommand}
|
aria-label={copy.copyCommand}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ export function runTests() {
|
|||||||
path.join(accountDir, "usePaymentFlow.ts"),
|
path.join(accountDir, "usePaymentFlow.ts"),
|
||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
|
const useBillingSource = fs.readFileSync(
|
||||||
|
path.join(accountDir, "useBilling.ts"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
(accountCenterSource.includes('import { usePaymentState } from "./usePaymentState";') ||
|
(accountCenterSource.includes('import { usePaymentState } from "./usePaymentState";') ||
|
||||||
hookSource.includes('import { usePaymentState } from "./usePaymentState";')) &&
|
hookSource.includes('import { usePaymentState } from "./usePaymentState";')) &&
|
||||||
@@ -193,6 +197,23 @@ export function runTests() {
|
|||||||
accountCenterSource.includes('hasTelegramPanel ? "" : "sm:grid-cols-2"'),
|
accountCenterSource.includes('hasTelegramPanel ? "" : "sm:grid-cols-2"'),
|
||||||
"payment management must only split into internal columns when it is not already sharing the row with a Telegram panel",
|
"payment management must only split into internal columns when it is not already sharing the row with a Telegram panel",
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
!accountCenterSource.includes("`/bind ${userId}${email ? ` ${email}` : \"\"}`") &&
|
||||||
|
!accountCenterSource.includes("/bind <supabase_user_id> <email>"),
|
||||||
|
"Telegram fallback copy must not expose the legacy /bind supabase_user_id command",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
accountCenterSource.includes("telegramBindCommand") &&
|
||||||
|
accountCenterSource.includes("createTelegramBotBindCommand") &&
|
||||||
|
accountCenterSource.includes("handleCopyTelegramBindCommand"),
|
||||||
|
"account Telegram fallback copy must generate a fresh one-time /start bind token before copying",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
useBillingSource.includes("bot_command") &&
|
||||||
|
useBillingSource.includes("createTelegramBotBindCommand") &&
|
||||||
|
useBillingSource.includes("telegramBindCommand"),
|
||||||
|
"billing hook must persist the backend /start bind token command for Telegram fallback copy",
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
!appAnalyticsSource.includes('NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"') &&
|
!appAnalyticsSource.includes('NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"') &&
|
||||||
!analyticsRouteSource.includes('NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"'),
|
!analyticsRouteSource.includes('NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"'),
|
||||||
|
|||||||
@@ -46,8 +46,14 @@ export function createAccountCopy(isEn: boolean): Record<string, string> {
|
|||||||
? "Use one-click Telegram binding first to sync notifications and access. After binding, refresh this page and submit your Telegram group join request."
|
? "Use one-click Telegram binding first to sync notifications and access. After binding, refresh this page and submit your Telegram group join request."
|
||||||
: "优先使用「一键绑定 Telegram Bot」同步通知与权限。绑定完成后刷新本页,再提交 Telegram 群组入群申请。",
|
: "优先使用「一键绑定 Telegram Bot」同步通知与权限。绑定完成后刷新本页,再提交 Telegram 群组入群申请。",
|
||||||
telegramFallbackHint: isEn
|
telegramFallbackHint: isEn
|
||||||
? "Fallback copy method: only use this if one-click binding does not open Telegram correctly. Copy the command below and send it to @polyyuanbot. After binding, refresh this page to show the group entry."
|
? "Fallback copy method: click Copy to generate a 10-minute one-time /start bind command, send it to @polyyuanbot, then confirm binding in the Bot and refresh this page."
|
||||||
: "兜底复制方式:仅在一键绑定无法正常打开 Telegram 时使用。请复制下方命令并发送给 @polyyuanbot。绑定完成后刷新本页,即可显示入群入口。",
|
: "兜底复制方式:点击复制会生成 10 分钟有效的一次性 /start bind 命令。请把它发送给 @polyyuanbot,并在 Bot 内确认绑定后刷新本页。",
|
||||||
|
telegramBindCommandPlaceholder: isEn
|
||||||
|
? "Click Copy to generate a one-time /start bind command"
|
||||||
|
: "点击复制生成一次性 /start bind 命令",
|
||||||
|
telegramBindCommandCopied: isEn
|
||||||
|
? "One-time Telegram bind command generated and copied. Send it to @polyyuanbot, then confirm binding in the Bot."
|
||||||
|
: "一次性 Telegram 绑定命令已生成并复制。请发送给 @polyyuanbot,然后在 Bot 内确认绑定。",
|
||||||
paymentManualSupport: isEn
|
paymentManualSupport: isEn
|
||||||
? "If payment succeeds but Pro is still not activated, email yhrsc30@gmail.com. This project is currently maintained by one developer, so manual recovery may be needed in edge cases."
|
? "If payment succeeds but Pro is still not activated, email yhrsc30@gmail.com. This project is currently maintained by one developer, so manual recovery may be needed in edge cases."
|
||||||
: "如果付款成功后 Pro 仍未开通,请发邮件到 yhrsc30@gmail.com。当前项目由我一人维护,极少数边缘情况可能需要人工补开。给你带来的不便,敬请谅解!",
|
: "如果付款成功后 Pro 仍未开通,请发邮件到 yhrsc30@gmail.com。当前项目由我一人维护,极少数边缘情况可能需要人工补开。给你带来的不便,敬请谅解!",
|
||||||
|
|||||||
@@ -549,6 +549,7 @@ export function useAccountPayment(params: UseAccountPaymentParams) {
|
|||||||
lastPaymentStartedAt,
|
lastPaymentStartedAt,
|
||||||
telegramBindOpening,
|
telegramBindOpening,
|
||||||
telegramBindUrl: billing.telegramBindUrl,
|
telegramBindUrl: billing.telegramBindUrl,
|
||||||
|
telegramBindCommand: billing.telegramBindCommand,
|
||||||
manualPayment,
|
manualPayment,
|
||||||
manualTxHash,
|
manualTxHash,
|
||||||
txValidation,
|
txValidation,
|
||||||
@@ -622,6 +623,7 @@ export function useAccountPayment(params: UseAccountPaymentParams) {
|
|||||||
submitManualPaymentTx: paymentFlow.submitManualPaymentTx,
|
submitManualPaymentTx: paymentFlow.submitManualPaymentTx,
|
||||||
validateTxHash: paymentFlow.validateTxHash,
|
validateTxHash: paymentFlow.validateTxHash,
|
||||||
handleOverlayCheckout: paymentFlow.handleOverlayCheckout,
|
handleOverlayCheckout: paymentFlow.handleOverlayCheckout,
|
||||||
|
createTelegramBotBindCommand: billing.createTelegramBotBindCommand,
|
||||||
openTelegramBotBindLink: billing.openTelegramBotBindLink,
|
openTelegramBotBindLink: billing.openTelegramBotBindLink,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ import { isTelegramPrivateGroupPriceEligible } from "./telegram-pricing";
|
|||||||
import { trackAppEvent } from "@/lib/app-analytics";
|
import { trackAppEvent } from "@/lib/app-analytics";
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
type TelegramBotBindPayload = {
|
||||||
|
bot_command?: string;
|
||||||
|
bot_url?: string;
|
||||||
|
start_param?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export interface UseBillingParams {
|
export interface UseBillingParams {
|
||||||
isEn: boolean;
|
isEn: boolean;
|
||||||
copy: Record<string, string>;
|
copy: Record<string, string>;
|
||||||
@@ -96,6 +102,7 @@ export function useBilling(params: UseBillingParams) {
|
|||||||
// ── Billing-specific state ────────────────────────────────
|
// ── Billing-specific state ────────────────────────────────
|
||||||
const [reconcileBusy, setReconcileBusy] = useState(false);
|
const [reconcileBusy, setReconcileBusy] = useState(false);
|
||||||
const [telegramBindUrl, setTelegramBindUrl] = useState("");
|
const [telegramBindUrl, setTelegramBindUrl] = useState("");
|
||||||
|
const [telegramBindCommand, setTelegramBindCommand] = useState("");
|
||||||
|
|
||||||
// ── Derived values ──────────────────────────────────────
|
// ── Derived values ──────────────────────────────────────
|
||||||
const paymentReadyForRecovery = Boolean(paymentConfig?.enabled && paymentConfig?.configured);
|
const paymentReadyForRecovery = Boolean(paymentConfig?.enabled && paymentConfig?.configured);
|
||||||
@@ -273,23 +280,59 @@ export function useBilling(params: UseBillingParams) {
|
|||||||
[buildAuthedHeaders, copy, loadPaymentSnapshot, refreshEntitlementAfterPayment, reconcileLatestPayment],
|
[buildAuthedHeaders, copy, loadPaymentSnapshot, refreshEntitlementAfterPayment, reconcileLatestPayment],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── openTelegramBotBindLink ──────────────────────────────
|
// ── Telegram bot bind helpers ─────────────────────────────
|
||||||
|
const requestTelegramBotBindPayload = useCallback(async () => {
|
||||||
|
const authHeaders = await buildAuthedHeaders(true, false);
|
||||||
|
const res = await fetch("/api/auth/telegram/bot-bind-link", {
|
||||||
|
method: "POST",
|
||||||
|
headers: authHeaders,
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const raw = (await res.text()).slice(0, 300);
|
||||||
|
throw new Error(raw || copy.telegramBindFailed);
|
||||||
|
}
|
||||||
|
const data = (await res.json()) as TelegramBotBindPayload;
|
||||||
|
const botUrl = String(data.bot_url || "").trim();
|
||||||
|
const startParam = String(data.start_param || "").trim();
|
||||||
|
const botCommand = String(
|
||||||
|
data.bot_command || (startParam ? `/start ${startParam}` : ""),
|
||||||
|
).trim();
|
||||||
|
if (!botUrl && !botCommand) throw new Error(copy.telegramBindLinkMissing);
|
||||||
|
setTelegramBindUrl(botUrl);
|
||||||
|
setTelegramBindCommand(botCommand);
|
||||||
|
return { botCommand, botUrl };
|
||||||
|
}, [
|
||||||
|
buildAuthedHeaders,
|
||||||
|
copy.telegramBindFailed,
|
||||||
|
copy.telegramBindLinkMissing,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const createTelegramBotBindCommand = async () => {
|
||||||
|
setTelegramBindOpening(true);
|
||||||
|
setPaymentError("");
|
||||||
|
setTelegramBindUrl("");
|
||||||
|
setTelegramBindCommand("");
|
||||||
|
try {
|
||||||
|
const { botCommand } = await requestTelegramBotBindPayload();
|
||||||
|
if (!botCommand) throw new Error(copy.telegramBindLinkMissing);
|
||||||
|
setPaymentInfo(copy.telegramBindCommandCopied);
|
||||||
|
return botCommand;
|
||||||
|
} catch (error) {
|
||||||
|
setPaymentError(normalizePaymentError(error).message);
|
||||||
|
return "";
|
||||||
|
} finally {
|
||||||
|
setTelegramBindOpening(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const openTelegramBotBindLink = async () => {
|
const openTelegramBotBindLink = async () => {
|
||||||
setTelegramBindOpening(true);
|
setTelegramBindOpening(true);
|
||||||
setPaymentError("");
|
setPaymentError("");
|
||||||
setTelegramBindUrl("");
|
setTelegramBindUrl("");
|
||||||
|
setTelegramBindCommand("");
|
||||||
const popup = window.open("about:blank", "_blank", "noopener,noreferrer");
|
const popup = window.open("about:blank", "_blank", "noopener,noreferrer");
|
||||||
try {
|
try {
|
||||||
const authHeaders = await buildAuthedHeaders(true, false);
|
const { botUrl } = await requestTelegramBotBindPayload();
|
||||||
const res = await fetch("/api/auth/telegram/bot-bind-link", {
|
|
||||||
method: "POST", headers: authHeaders,
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const raw = (await res.text()).slice(0, 300);
|
|
||||||
throw new Error(raw || copy.telegramBindFailed);
|
|
||||||
}
|
|
||||||
const data = (await res.json()) as { bot_url?: string };
|
|
||||||
const botUrl = String(data.bot_url || "").trim();
|
|
||||||
if (!botUrl) throw new Error(copy.telegramBindLinkMissing);
|
if (!botUrl) throw new Error(copy.telegramBindLinkMissing);
|
||||||
if (popup && !popup.closed) {
|
if (popup && !popup.closed) {
|
||||||
popup.location.href = botUrl;
|
popup.location.href = botUrl;
|
||||||
@@ -423,9 +466,11 @@ export function useBilling(params: UseBillingParams) {
|
|||||||
return {
|
return {
|
||||||
reconcileBusy,
|
reconcileBusy,
|
||||||
telegramBindUrl,
|
telegramBindUrl,
|
||||||
|
telegramBindCommand,
|
||||||
setTelegramBindUrl,
|
setTelegramBindUrl,
|
||||||
reconcileLatestPayment,
|
reconcileLatestPayment,
|
||||||
handleSubmit409,
|
handleSubmit409,
|
||||||
|
createTelegramBotBindCommand,
|
||||||
openTelegramBotBindLink,
|
openTelegramBotBindLink,
|
||||||
paymentReadyForRecovery,
|
paymentReadyForRecovery,
|
||||||
hasRecentPaymentRecovery,
|
hasRecentPaymentRecovery,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from typing import Callable
|
|||||||
from loguru import logger # type: ignore
|
from loguru import logger # type: ignore
|
||||||
|
|
||||||
from src.bot.command_parser import extract_command_name
|
from src.bot.command_parser import extract_command_name
|
||||||
|
from src.bot.command_parser import looks_like_slash_command
|
||||||
from src.bot.io_layer import BotIOLayer
|
from src.bot.io_layer import BotIOLayer
|
||||||
from src.bot.observability import CommandTrace
|
from src.bot.observability import CommandTrace
|
||||||
from src.bot.runtime_coordinator import RuntimeStatus, render_runtime_status_html
|
from src.bot.runtime_coordinator import RuntimeStatus, render_runtime_status_html
|
||||||
@@ -86,6 +87,13 @@ class BasicCommandHandler:
|
|||||||
def _basic_text(message):
|
def _basic_text(message):
|
||||||
self._dispatch(message)
|
self._dispatch(message)
|
||||||
|
|
||||||
|
@self.bot.message_handler(
|
||||||
|
content_types=["text"],
|
||||||
|
func=self._is_private_text_fallback,
|
||||||
|
)
|
||||||
|
def _private_text_fallback(message):
|
||||||
|
self.handle_private_text_fallback(message)
|
||||||
|
|
||||||
def _dispatch(self, message: Any) -> None:
|
def _dispatch(self, message: Any) -> None:
|
||||||
command = extract_command_name(
|
command = extract_command_name(
|
||||||
getattr(message, "text", None),
|
getattr(message, "text", None),
|
||||||
@@ -134,6 +142,31 @@ class BasicCommandHandler:
|
|||||||
finally:
|
finally:
|
||||||
trace.emit()
|
trace.emit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_private_text_fallback(message: Any) -> bool:
|
||||||
|
text = str(getattr(message, "text", "") or "").strip()
|
||||||
|
chat_type = str(getattr(getattr(message, "chat", None), "type", "") or "").strip().lower()
|
||||||
|
return bool(text and chat_type == "private" and not looks_like_slash_command(text))
|
||||||
|
|
||||||
|
def handle_private_text_fallback(self, message: Any) -> str:
|
||||||
|
text = str(getattr(message, "text", "") or "").strip()
|
||||||
|
if looks_like_slash_command(text):
|
||||||
|
return "ignored:slash_command"
|
||||||
|
chat_type = str(getattr(getattr(message, "chat", None), "type", "") or "").strip().lower()
|
||||||
|
if chat_type != "private":
|
||||||
|
return "ignored:not_private"
|
||||||
|
self.bot.reply_to(
|
||||||
|
message,
|
||||||
|
(
|
||||||
|
"我收到了,但这不是可执行命令。\n\n"
|
||||||
|
"如果你正在绑定网站账号:请在网页账户页点击“复制兜底命令”,"
|
||||||
|
"然后把生成的 <code>/start bind_...</code> 发到这里。\n\n"
|
||||||
|
"也可以发送 <code>/help</code> 查看可用命令。"
|
||||||
|
),
|
||||||
|
parse_mode="HTML",
|
||||||
|
)
|
||||||
|
return "replied"
|
||||||
|
|
||||||
def _prompt_bind_from_web_token(self, message: Any, token: str) -> str:
|
def _prompt_bind_from_web_token(self, message: Any, token: str) -> str:
|
||||||
if not token:
|
if not token:
|
||||||
self.bot.reply_to(message, "❌ 绑定链接无效,请回到网页重新点击一键绑定。")
|
self.bot.reply_to(message, "❌ 绑定链接无效,请回到网页重新点击一键绑定。")
|
||||||
|
|||||||
@@ -181,6 +181,56 @@ def test_confirm_bind_callback_consumes_token_and_binds_account():
|
|||||||
assert bound[0]["supabase_user_id"] == "user-1"
|
assert bound[0]["supabase_user_id"] == "user-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_private_text_fallback_replies_with_binding_help():
|
||||||
|
bot = DummyBot()
|
||||||
|
io_layer = SimpleNamespace(
|
||||||
|
build_welcome_text=lambda: "WELCOME",
|
||||||
|
build_points_rank_text=lambda _user: "TOP",
|
||||||
|
)
|
||||||
|
handler = BasicCommandHandler(
|
||||||
|
bot=bot,
|
||||||
|
io_layer=io_layer,
|
||||||
|
runtime_status_provider=lambda: RuntimeStatus(
|
||||||
|
started_at="2026-03-12 00:00:00 UTC",
|
||||||
|
loops=[],
|
||||||
|
command_access_mode="group_member",
|
||||||
|
protected_commands=["/city", "/deb"],
|
||||||
|
required_group_chat_id="-1001234567890",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = handler.handle_private_text_fallback(_message("@polyyuanbot"))
|
||||||
|
|
||||||
|
assert result == "replied"
|
||||||
|
assert len(bot.replies) == 1
|
||||||
|
assert "/start bind_" in bot.replies[0]["text"]
|
||||||
|
assert "/help" in bot.replies[0]["text"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_private_text_fallback_ignores_slash_commands():
|
||||||
|
bot = DummyBot()
|
||||||
|
io_layer = SimpleNamespace(
|
||||||
|
build_welcome_text=lambda: "WELCOME",
|
||||||
|
build_points_rank_text=lambda _user: "TOP",
|
||||||
|
)
|
||||||
|
handler = BasicCommandHandler(
|
||||||
|
bot=bot,
|
||||||
|
io_layer=io_layer,
|
||||||
|
runtime_status_provider=lambda: RuntimeStatus(
|
||||||
|
started_at="2026-03-12 00:00:00 UTC",
|
||||||
|
loops=[],
|
||||||
|
command_access_mode="group_member",
|
||||||
|
protected_commands=["/city", "/deb"],
|
||||||
|
required_group_chat_id="-1001234567890",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = handler.handle_private_text_fallback(_message("/city seoul"))
|
||||||
|
|
||||||
|
assert result == "ignored:slash_command"
|
||||||
|
assert bot.replies == []
|
||||||
|
|
||||||
|
|
||||||
def test_basic_handler_markets_returns_summary():
|
def test_basic_handler_markets_returns_summary():
|
||||||
bot = DummyBot()
|
bot = DummyBot()
|
||||||
io_layer = SimpleNamespace(
|
io_layer = SimpleNamespace(
|
||||||
|
|||||||
@@ -1805,6 +1805,10 @@ def test_telegram_identity_endpoints_skip_subscription_gate(monkeypatch):
|
|||||||
headers=auth_headers,
|
headers=auth_headers,
|
||||||
)
|
)
|
||||||
assert link_response.status_code == 200
|
assert link_response.status_code == 200
|
||||||
|
link_payload = link_response.json()
|
||||||
|
assert link_payload["start_param"] == "bind_bind-token"
|
||||||
|
assert link_payload["bot_command"] == "/start bind_bind-token"
|
||||||
|
assert link_payload["bot_url"].endswith("?start=bind_bind-token")
|
||||||
|
|
||||||
|
|
||||||
def test_auth_me_does_not_reconcile_on_status_probe(monkeypatch):
|
def test_auth_me_does_not_reconcile_on_status_probe(monkeypatch):
|
||||||
|
|||||||
@@ -459,10 +459,12 @@ def create_telegram_bot_bind_link(request: Request) -> Dict[str, Any]:
|
|||||||
or "polyyuanbot"
|
or "polyyuanbot"
|
||||||
).strip().lstrip("@")
|
).strip().lstrip("@")
|
||||||
bot_url = f"https://t.me/{bot_username}?start={start_param}"
|
bot_url = f"https://t.me/{bot_username}?start={start_param}"
|
||||||
|
bot_command = f"/start {start_param}"
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"token": token,
|
"token": token,
|
||||||
"start_param": start_param,
|
"start_param": start_param,
|
||||||
|
"bot_command": bot_command,
|
||||||
"bot_url": bot_url,
|
"bot_url": bot_url,
|
||||||
"expires_in_seconds": 600,
|
"expires_in_seconds": 600,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user