3590df28db
New platform/telegram connector (own container, bot token only there): - go-telegram/bot long-poll loop: /start deep-links + Mini App launch button. - gRPC API pkg/proto/telegram/v1 (Telegram service): ValidateInitData, Notify (renders a localized message + deep-link button), SendToUser/SendToGameChannel (admin, wired in Stage 10). Generic methods are platform-agnostic (external_id). - Bot API base override for Telegram's test environment; Dockerfile + compose (VPN sidecar, no public ingress); README. Gateway: - initData validation relocated from the gateway into the connector; the gateway calls ValidateInitData over gRPC (GATEWAY_CONNECTOR_ADDR), drops the bot token, and deletes internal/auth. - Out-of-app push: runPushPump routes events whose recipient has no live in-app stream to connector.Notify, gated by /internal/push-target + the in-app-only flag (race-free de-dup); HasSubscribers added to the push hub. Backend: - Migration 00007 accounts.notifications_in_app_only (default true) + jetgen. - ProvisionTelegram seeds a new account's language/display name from the launch fields; IdentityExternalID reverse lookup; /internal/push-target handler. UI: - Telegram Mini App launch: detect initData, apply themeParams, authTelegram, route the deep-link start_param (g/i/f); /telegram/ guard redirects outside Telegram. Vite relative base + telegram-web-app.js. In-app-only profile toggle; share-to-Telegram link for a friend code. Vitest + Playwright coverage. Wire/docs/CI: fbs Profile/UpdateProfileRequest gain notifications_in_app_only (Go + TS); go.work uses ./platform/telegram; go-unit.yaml covers it; PLAN, ARCHITECTURE, FUNCTIONAL (+ru), UI_DESIGN, READMEs updated.
81 lines
2.8 KiB
Go
81 lines
2.8 KiB
Go
// Package render turns a backend push event into a localized Telegram message with
|
|
// a Mini App deep-link. Only the out-of-app push set is rendered (your_turn, nudge,
|
|
// match_found, and the invitation / friend_request notify sub-kinds); every other
|
|
// kind returns ok=false so the connector skips it (the in-app stream still carries
|
|
// it).
|
|
package render
|
|
|
|
import (
|
|
"scrabble/pkg/fbs/scrabblefb"
|
|
"scrabble/platform/telegram/internal/deeplink"
|
|
)
|
|
|
|
// Message is a rendered notification: the body text, the launch-button label and
|
|
// the deep-link start parameter (empty opens the lobby).
|
|
type Message struct {
|
|
Text string
|
|
ButtonText string
|
|
StartParam string
|
|
}
|
|
|
|
// Render builds the localized message for a backend push event of the given kind
|
|
// and FlatBuffers payload, in language lang ("ru" selects Russian; anything else
|
|
// is English). It returns ok=false for a kind that is not delivered out-of-app.
|
|
func Render(kind string, payload []byte, lang string) (Message, bool) {
|
|
p := english
|
|
if lang == "ru" {
|
|
p = russian
|
|
}
|
|
switch kind {
|
|
case "your_turn":
|
|
ev := scrabblefb.GetRootAsYourTurnEvent(payload, 0)
|
|
return Message{Text: p.yourTurn, ButtonText: p.openGame, StartParam: deeplink.Game(string(ev.GameId()))}, true
|
|
case "nudge":
|
|
ev := scrabblefb.GetRootAsNudgeEvent(payload, 0)
|
|
return Message{Text: p.nudge, ButtonText: p.openGame, StartParam: deeplink.Game(string(ev.GameId()))}, true
|
|
case "match_found":
|
|
ev := scrabblefb.GetRootAsMatchFoundEvent(payload, 0)
|
|
return Message{Text: p.matchFound, ButtonText: p.openGame, StartParam: deeplink.Game(string(ev.GameId()))}, true
|
|
case "notify":
|
|
ev := scrabblefb.GetRootAsNotificationEvent(payload, 0)
|
|
switch string(ev.Kind()) {
|
|
case "invitation":
|
|
return Message{Text: p.invitation, ButtonText: p.open}, true
|
|
case "friend_request":
|
|
return Message{Text: p.friendRequest, ButtonText: p.open}, true
|
|
}
|
|
}
|
|
return Message{}, false
|
|
}
|
|
|
|
// phrases is one language's message catalog.
|
|
type phrases struct {
|
|
yourTurn string
|
|
nudge string
|
|
matchFound string
|
|
invitation string
|
|
friendRequest string
|
|
openGame string
|
|
open string
|
|
}
|
|
|
|
var english = phrases{
|
|
yourTurn: "It's your turn.",
|
|
nudge: "You were nudged — it's your turn.",
|
|
matchFound: "Your game is ready.",
|
|
invitation: "You have a new game invitation.",
|
|
friendRequest: "You have a new friend request.",
|
|
openGame: "Open game",
|
|
open: "Open",
|
|
}
|
|
|
|
var russian = phrases{
|
|
yourTurn: "Ваш ход.",
|
|
nudge: "Вас поторопили — ваш ход.",
|
|
matchFound: "Игра найдена.",
|
|
invitation: "Вас пригласили в игру.",
|
|
friendRequest: "Вам пришла заявка в друзья.",
|
|
openGame: "Открыть игру",
|
|
open: "Открыть",
|
|
}
|