11import {
2- AdminForthFilterOperators ,
3- type AdminUser ,
42 type ChatSurfaceAdapter ,
53 type ChatSurfaceEventSink ,
64 type ChatSurfaceIncomingMessage ,
75 type ChatSurfaceRequestContext ,
8- type IAdminForth ,
96} from "adminforth" ;
107import { AdapterOptions } from "./types.js" ;
118import { getFinalMessageStreamPreview , renderFinalMessageImages } from "./renderers.js" ;
@@ -40,14 +37,20 @@ type TelegramUpdate = {
4037 } ;
4138} ;
4239
40+ type ChatSurfaceConnectAction = {
41+ type : "url" ;
42+ label : string ;
43+ url : string ;
44+ } ;
45+
4346const TELEGRAM_API_BASE_URL = "https://api.telegram.org" ;
4447const TELEGRAM_SECRET_HEADER = "x-telegram-bot-api-secret-token" ;
4548const TELEGRAM_MESSAGE_MAX_LENGTH = 4096 ;
4649const TELEGRAM_DRAFT_MAX_LENGTH = 4096 ;
4750const DEFAULT_DRAFT_UPDATE_INTERVAL_MS = 650 ;
4851const DEFAULT_TYPING_INTERVAL_MS = 4000 ;
49- const DEFAULT_ADMIN_USER_RESOURCE_ID = "adminuser " ;
50- const DEFAULT_ADMIN_USER_TELEGRAM_ID_FIELD = "telegramId" ;
52+ const TELEGRAM_START_COMMAND_PREFIX = "/start " ;
53+ const TELEGRAM_COMMAND_PARTS_RE = / \s + / ;
5154
5255function createTelegramDraftId ( ) {
5356 return randomInt ( 1 , 2147483647 ) ;
@@ -90,10 +93,29 @@ function splitTelegramMessage(text: string) {
9093 return chunks ;
9194}
9295
96+ function parseTelegramStartPayload ( text : string ) {
97+ const [ command , ...payloadParts ] = text . trim ( ) . split ( TELEGRAM_COMMAND_PARTS_RE ) ;
98+
99+ if ( command !== TELEGRAM_START_COMMAND_PREFIX && ! command . startsWith ( `${ TELEGRAM_START_COMMAND_PREFIX } @` ) ) {
100+ return null ;
101+ }
102+
103+ return payloadParts . join ( " " ) || null ;
104+ }
105+
93106export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
94107 name = "telegram" ;
95-
96- constructor ( private options : AdapterOptions ) { }
108+ createConnectAction ?: ( input : { token : string } ) => ChatSurfaceConnectAction ;
109+
110+ constructor ( private options : AdapterOptions ) {
111+ if ( options . botUsername ) {
112+ this . createConnectAction = ( { token } ) => ( {
113+ type : "url" ,
114+ label : "Connect Telegram" ,
115+ url : `https://t.me/${ options . botUsername } ?start=${ encodeURIComponent ( token ) } ` ,
116+ } ) ;
117+ }
118+ }
97119
98120 validate ( ) {
99121 if ( ! this . options . botToken ) {
@@ -118,13 +140,16 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
118140 return null ;
119141 }
120142
143+ const startPayload = parseTelegramStartPayload ( text ) ;
144+
121145 return {
122146 surface : this . name ,
123147 prompt : text ,
124148 externalConversationId : String ( chatId ) ,
125149 externalUserId : String ( userId ) ,
126150 userTimeZone : "UTC" ,
127151 metadata : {
152+ startPayload,
128153 telegramUpdate : update ,
129154 } ,
130155 } ;
@@ -267,31 +292,6 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
267292 } ;
268293 }
269294
270- async resolveAdminUser ( input : {
271- adminforth : IAdminForth ;
272- incoming : ChatSurfaceIncomingMessage ;
273- } ) : Promise < AdminUser | null > {
274- const adminUserResourceId = this . options . adminUserResourceId ?? DEFAULT_ADMIN_USER_RESOURCE_ID ;
275- const telegramIdField = this . options . adminUserTelegramIdField ?? DEFAULT_ADMIN_USER_TELEGRAM_ID_FIELD ;
276- const adminUser = await input . adminforth . resource ( adminUserResourceId ) . get ( [
277- {
278- field : telegramIdField ,
279- operator : AdminForthFilterOperators . EQ ,
280- value : input . incoming . externalUserId ,
281- } ,
282- ] ) ;
283-
284- if ( ! adminUser ) {
285- return null ;
286- }
287-
288- return {
289- pk : adminUser . id ,
290- username : adminUser [ input . adminforth . config . auth ! . usernameField ] ,
291- dbUser : adminUser ,
292- } ;
293- }
294-
295295 private async sendMessage ( chatId : string , text : string ) {
296296 if ( ! text ) {
297297 return ;
0 commit comments