@@ -4,107 +4,147 @@ import Webhook from './webhook.js';
44
55/** Class representing a telegram bot. */
66export default class TelegramBot {
7- /** The telegram token */
8- token : string ;
9- /** The telegram api URL */
10- api : URL ;
11- /** The telegram webhook object */
12- webhook : Webhook = new Webhook ( '' , new Request ( 'http://127.0.0.1' ) ) ;
13- /** The telegram update object */
14- update : TelegramUpdate = new TelegramUpdate ( { } ) ;
15- /** The telegram commands record map */
16- commands : Record < string , ( ctx : TelegramExecutionContext ) => Promise < Response > > = { } ;
17- /** The current bot context */
18- currentContext ! : TelegramExecutionContext ;
19-
20- /**
21- * Create a bot
22- * @param token - the telegram secret token
23- */
24- constructor ( token : string ) {
25- this . token = token ;
26- this . api = new URL ( 'https://api.telegram.org/bot' + token ) ;
27- }
28-
29- /**
30- * Register a function on the bot
31- * @param event - the event or command name
32- * @param callback - the bot context
33- */
34- on ( event : string , callback : ( ctx : TelegramExecutionContext ) => Promise < Response > ) {
35- if ( ! [ 'on' , 'handle' ] . includes ( event ) ) {
36- this . commands [ event ] = callback ;
37- }
38- return this ;
39- }
40-
41- /**
42- * Handle a request on a given bot
43- * @param request - the request to handle
44- */
45- async handle ( request : Request ) : Promise < Response > {
46- this . webhook = new Webhook ( this . token , request ) ;
47- const url = new URL ( request . url ) ;
48- if ( `/${ this . token } ` === url . pathname ) {
49- switch ( request . method ) {
50- case 'POST' : {
51- this . update = await request . json ( ) ;
52- console . log ( this . update ) ;
53- let command = ':message' ;
54- let args : string [ ] = [ ] ;
55- const ctx = new TelegramExecutionContext ( this , this . update ) ;
56- this . currentContext = ctx ;
57- switch ( ctx . update_type ) {
58- case 'message' : {
59- args = this . update . message ?. text ?. split ( ' ' ) ?? [ ] ;
60- break ;
61- }
62- case 'business_message' : {
63- args = this . update . message ?. text ?. split ( ' ' ) ?? [ ] ;
64- break ;
65- }
66- case 'inline' : {
67- args = this . update . inline_query ?. query . split ( ' ' ) ?? [ ] ;
68- break ;
69- }
70- case 'photo' : {
71- command = ':photo' ;
72- break ;
73- }
74- case 'document' : {
75- command = ':document' ;
76- break ;
77- }
78- case 'callback' : {
79- command = ':callback' ;
80- break ;
81- }
82- default :
83- break ;
84- }
85- if ( args . at ( 0 ) ?. startsWith ( '/' ) ) {
86- command = args . at ( 0 ) ?. slice ( 1 ) ?? ':message' ;
87- }
88- if ( ! ( command in this . commands ) ) {
89- command = ':message' ;
90- }
91- return await this . commands [ command ] ( ctx ) ;
92- }
93- case 'GET' : {
94- switch ( url . searchParams . get ( 'command' ) ) {
95- case 'set' :
96- return this . webhook . set ( ) ;
97-
98- default :
99- break ;
100- }
101- break ;
102- }
103-
104- default :
105- break ;
106- }
107- }
108- return new Response ( 'ok' ) ;
109- }
7+ /** The telegram token */
8+ token : string ;
9+ /** The telegram api URL */
10+ api : URL ;
11+ /** The telegram webhook object */
12+ webhook : Webhook = new Webhook ( '' , new Request ( 'http://127.0.0.1' ) ) ;
13+ /** The telegram update object */
14+ update : TelegramUpdate = new TelegramUpdate ( { } ) ;
15+ /** The telegram commands record map */
16+ commands : Record < string , ( ctx : TelegramExecutionContext ) => Promise < Response > > = { } ;
17+ /** The current bot context */
18+ currentContext ! : TelegramExecutionContext ;
19+ /** Default command to use when no matching command is found */
20+ defaultCommand = ':message' ;
21+
22+ /**
23+ * Create a bot
24+ * @param token - the telegram secret token
25+ * @param options - optional configuration for the bot
26+ */
27+ constructor ( token : string , options ?: { defaultCommand ?: string } ) {
28+ this . token = token ;
29+ this . api = new URL ( 'https://api.telegram.org/bot' + token ) ;
30+
31+ if ( options ?. defaultCommand ) {
32+ this . defaultCommand = options . defaultCommand ;
33+ }
34+
35+ // Register default handler for the default command to avoid errors
36+ this . commands [ this . defaultCommand ] = ( ) => Promise . resolve ( new Response ( 'Command not implemented' ) ) ;
37+ }
38+
39+ /**
40+ * Register a function on the bot
41+ * @param event - the event or command name
42+ * @param callback - the bot context
43+ */
44+ on ( event : string , callback : ( ctx : TelegramExecutionContext ) => Promise < Response > ) {
45+ this . commands [ event ] = callback ;
46+ return this ;
47+ }
48+
49+ /**
50+ * Register multiple command handlers at once
51+ * @param handlers - object mapping command names to handler functions
52+ */
53+ registerHandlers ( handlers : Record < string , ( ctx : TelegramExecutionContext ) => Promise < Response > > ) {
54+ for ( const [ event , callback ] of Object . entries ( handlers ) ) {
55+ this . on ( event , callback ) ;
56+ }
57+ return this ;
58+ }
59+
60+ /**
61+ * Determine the command from the update
62+ * @param ctx - the execution context
63+ * @param args - command arguments
64+ * @returns the command string
65+ */
66+ private determineCommand ( ctx : TelegramExecutionContext , args : string [ ] ) : string {
67+ // First check if it's a special update type
68+ switch ( ctx . update_type ) {
69+ case 'photo' :
70+ return ':photo' in this . commands ? ':photo' : this . defaultCommand ;
71+ case 'document' :
72+ return ':document' in this . commands ? ':document' : this . defaultCommand ;
73+ case 'callback' :
74+ return ':callback' in this . commands ? ':callback' : this . defaultCommand ;
75+ case 'inline' :
76+ return ':inline' in this . commands ? ':inline' : this . defaultCommand ;
77+ }
78+
79+ // Then check if it's a command starting with /
80+ if ( args . at ( 0 ) ?. startsWith ( '/' ) ) {
81+ const command = args . at ( 0 ) ?. slice ( 1 ) ?? '' ;
82+ return command in this . commands ? command : this . defaultCommand ;
83+ }
84+
85+ return this . defaultCommand ;
86+ }
87+
88+ /**
89+ * Parse arguments from the update
90+ * @param ctx - the execution context
91+ * @returns array of argument strings
92+ */
93+ private parseArguments ( ctx : TelegramExecutionContext ) : string [ ] {
94+ switch ( ctx . update_type ) {
95+ case 'message' :
96+ case 'business_message' :
97+ return this . update . message ?. text ?. split ( ' ' ) ?? [ ] ;
98+ case 'inline' :
99+ return this . update . inline_query ?. query . split ( ' ' ) ?? [ ] ;
100+ default :
101+ return [ ] ;
102+ }
103+ }
104+
105+ /**
106+ * Handle a request on a given bot
107+ * @param request - the request to handle
108+ */
109+ async handle ( request : Request ) : Promise < Response > {
110+ this . webhook = new Webhook ( this . token , request ) ;
111+ const url = new URL ( request . url ) ;
112+
113+ // Check if the request is for this bot
114+ if ( `/${ this . token } ` !== url . pathname ) {
115+ return new Response ( 'Invalid token' , { status : 404 } ) ;
116+ }
117+
118+ // Handle different HTTP methods
119+ switch ( request . method ) {
120+ case 'POST' : {
121+ try {
122+ this . update = await request . json ( ) ;
123+ console . log ( this . update ) ;
124+
125+ const ctx = new TelegramExecutionContext ( this , this . update ) ;
126+ this . currentContext = ctx ;
127+
128+ const args = this . parseArguments ( ctx ) ;
129+ const command = this . determineCommand ( ctx , args ) ;
130+
131+ return await this . commands [ command ] ( ctx ) ;
132+ } catch ( error ) {
133+ console . error ( 'Error handling Telegram update:' , error ) ;
134+ return new Response ( 'Error processing request' , { status : 500 } ) ;
135+ }
136+ }
137+
138+ case 'GET' : {
139+ const command = url . searchParams . get ( 'command' ) ;
140+ if ( command === 'set' ) {
141+ return this . webhook . set ( ) ;
142+ }
143+ return new Response ( 'Invalid command' , { status : 400 } ) ;
144+ }
145+
146+ default :
147+ return new Response ( 'Method not allowed' , { status : 405 } ) ;
148+ }
149+ }
110150}
0 commit comments