diff --git a/bot/src/bot/bot.ts b/bot/src/bot/bot.ts index 8eceed1..e0ab79f 100644 --- a/bot/src/bot/bot.ts +++ b/bot/src/bot/bot.ts @@ -42,7 +42,7 @@ export function createBot(env: Env, opts: { baseFetch?: typeof fetch } = {}): Bo const bot = opts.baseFetch ? new Bot(env.BOT_TOKEN, { client: { fetch: opts.baseFetch } }) : new Bot(env.BOT_TOKEN); - registerStart(bot); // /start + /mula (D-04) + registerStart(bot, env.MINI_APP_URL); // /start + /mula (D-04) — carries the logo asset registerHelp(bot); // /help registerPrivacy(bot); // /privacy + /privasi (D-04) registerLocationFallback(bot, env.chat_sessions); // GEO-02 location reply fallback diff --git a/bot/src/bot/commands/start.ts b/bot/src/bot/commands/start.ts index 3cda6c0..e235300 100644 --- a/bot/src/bot/commands/start.ts +++ b/bot/src/bot/commands/start.ts @@ -6,8 +6,14 @@ * * D-02 (structural, pinned by test/commands.test.mjs): the reply is plain * text with link previews disabled — no reply menus, no inline button - * payloads, no callback routes anywhere in this tree. Telegram commands - * are the interface until the Mini App launch path lands (Phase 4). + * payloads, no callback routes anywhere in this tree. + * + * Device-gate feedback (2026-10-24): /start now carries the app logo — + * sendPhoto by its STATIC-ASSET public URL (/logo_arah2.png, + * deployed from web/public/). Telegram fetches it server-side; the repo + * asset stays the single source (assets/logo_arah2.png). Photo send is + * resilient: if the fetch/send fails (e.g. asset not deployed yet), the + * text reply still goes out — the greeting is never lost. * * All copy comes from src/i18n/strings.ts (D-05) — nothing inline here. */ @@ -20,13 +26,35 @@ export function startReplyText(locale: Locale): string { return strings[locale].startIntro; } -async function handleStart(ctx: Context): Promise { +/** The logo's public URL — derived from the same configured origin the + * deep link uses (single origin of truth, D-11). Empty when unconfigured + * (tests/dev without vars): the caller then skips the photo send. */ +export function startLogoUrl(origin: string | undefined): string | null { + if (!origin) return null; + try { + return `${new URL(origin).origin}/logo_arah2.png`; + } catch { + return null; + } +} + +async function handleStart(ctx: Context, origin: string | undefined): Promise { const locale = pickLocale(ctx.from?.language_code); - await ctx.reply(startReplyText(locale), { link_preview_options: { is_disabled: true } }); + const text = startReplyText(locale); + const logoUrl = startLogoUrl(origin); + if (logoUrl !== null) { + try { + await ctx.replyWithPhoto(logoUrl, { caption: text }); + return; + } catch { + // fall through to the text reply — the greeting survives asset loss + } + } + await ctx.reply(text, { link_preview_options: { is_disabled: true } }); } /** Register the command stack entry: /start and its Malay alias /mula. */ -export function registerStart(bot: Bot): void { - bot.command("start", handleStart); - bot.command("mula", handleStart); +export function registerStart(bot: Bot, origin?: string): void { + bot.command("start", (ctx) => handleStart(ctx, origin)); + bot.command("mula", (ctx) => handleStart(ctx, origin)); } diff --git a/web/public/logo_arah2.png b/web/public/logo_arah2.png new file mode 100644 index 0000000..9cebf11 Binary files /dev/null and b/web/public/logo_arah2.png differ