UNPKG

16.8 kBMarkdownView Raw
1# telegram-node-bot
2Very powerful module for creating Telegram bots.
3
4[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KDM7K3BBVV2E8)
5
6[Full API reference](http://nabovyan.xyz/telegram-node-bot/)
7
8[help chat](http://nabovyan.xyz/tg-dev-chat)
9
10## Installation
11
12To install the stable version:
13
14```bash
15npm install --save telegram-node-bot
16```
17
18This assumes you are using [npm](https://www.npmjs.com/) as your package manager.
19If you don’t, you can access these files on [unpkg](https://unpkg.com/telegram-node-bot/), download them, or point your package manager to them.
20
21## Whats new in 4.0?
22
23* Bug fixes
24* Clustering
25* New router
26* Web admin
27
28## Get started
29
30First of all you need to create your bot and get Token, you can do it right in telegram, just write to @BotFather.
31
32Now let's write simple bot!
33
34```js
35'use strict'
36
37const Telegram = require('telegram-node-bot')
38const TelegramBaseController = Telegram.TelegramBaseController
39const TextCommand = Telegram.TextCommand
40const tg = new Telegram.Telegram('YOUR_TOKEN')
41
42class PingController extends TelegramBaseController {
43 /**
44 * @param {Scope} $
45 */
46 pingHandler($) {
47 $.sendMessage('pong')
48 }
49
50 get routes() {
51 return {
52 'pingCommand': 'pingHandler'
53 }
54 }
55}
56
57tg.router
58 .when(
59 new TextCommand('ping', 'pingCommand'),
60 new PingController()
61 )
62```
63That's it!
64
65![Bot](ScreenShot.png)
66
67## Introduction
68
69I'm using something like MVC, so we have router and controllers.
70First you need to declare your commands and which controller will handle it.
71Then you need to write controllers and handle specific commands in it.
72
73## Router
74Lets say our bot has three commands: /start, /stop and /restart
75And we want that commands to be handled by different controllers.
76
77Router declaration code will be like this:
78
79```js
80tg.router
81 .when(new TextCommand('/start', 'startCommand'), new StartController())
82 .when(new TextCommand('/stop', 'stopCommand'), new StopController())
83 .when(new TextCommand('/restart', 'restartCommand'), new RestartController())
84```
85
86Probably we will have a case when user send us command we didn't know, for that case router have `otherwise` function:
87
88```js
89tg.router
90 .when(new TextCommand('/start', 'startCommand'), new StartController())
91 .when(new TextCommand('/stop', 'stopCommand'), new StopController())
92 .when(new TextCommand('/restart', 'restartCommand'), new RestartController())
93 .otherwise(new OtherwiseController())
94```
95
96Now all unknown commands will be handled by OtherwiseController:
97
98```js
99class OtherwiseController extends TelegramBaseController {
100 handle() {
101 console.log('otherwise')
102 }
103}
104```
105
106In this cases for all controllers will be called `handle` method to handle request. But you can pass your custom handler name as second parameter to any command:
107
108```js
109tg.router
110 .when(
111 new TextCommand('/start', 'startHandler'),
112 new StartController()
113 )
114```
115Then you must add `routes` property to your controller like this:
116```js
117class StartConstoller extends TelegramBaseController {
118 /**
119 * @param {Scope} $
120 */
121 start($) {
122 $.sendMessage('Hello!')
123 }
124
125 get routes() {
126 return {
127 'startHandler': 'start'
128 }
129 }
130}
131```
132
133You can define controller for inline queries using `inlineQuery` method:
134
135```js
136tg.router
137 .inlineQuery(new InlineModeController())
138```
139
140And controllers for callback queries using `callbackQuery`:
141
142```js
143tg.router
144 .callbackQuery(new CallbackQueryController())
145```
146
147## List of all commands
148
149* TextCommand - just text command like `/start`
150```js
151tg.router
152 .when(
153 new TextCommand('/start', 'startHandler'),
154 new StartController()
155 )
156```
157* RegextCommand - any regexp command
158```js
159tg.router
160 .when(
161 new RegexpCommand(/test/g, 'testHandler'),
162 new TestController()
163 )
164```
165* CustomFilterCommand - custom command
166```js
167tg.router
168 .when(
169 new CustomFilterCommand($ => {
170 return $.message.text == 'some text'
171 }, 'customFilterHandler'),
172 new CustomFilterHandlerController()
173 )
174```
175
176You can also create your own command, just extend `BaseCommand`
177
178
179## Controllers
180
181There are three types of controllers:
182
183* Controller for messages - `TelegramBaseController`
184* Controller for CallbackQueries - `TelegramBaseCallbackQueryController`
185* Controller for InlineQueries - `TelegramBaseInlineQueryController`
186
187
188## TelegramBaseController
189
190To create controller for message updates you must extend `TelegramBaseController`.
191
192If you want specific methods of your controller be called for specific commands, you should return a plain object in `routes` property where key is a route and value is name of your method.
193In that case `handle` method will not be called and scope will be passed to your method.
194Example:
195
196```js
197class TestController extends TelegramBaseController {
198 get routes() {
199 return {
200 'test': 'testHandler'
201 }
202 }
203}
204```
205
206If there are no routes defined then `handle` method of your controller will be called.
207
208There is also `before` method, this method will be called after all updates and you should return the scope, you can modify scope if you want:
209
210```js
211class TestController extends TelegramBaseController {
212 before(scope) {
213 scope.someData = true
214
215 return scope
216 }
217}
218```
219Remember: if you want to handle command in controller you need to declare it in router.
220
221All instances of TelegramBaseController also have private `_api` property which is a reference to `TelegramApi` and private `_localization` property which is a reference to `Ivan`
222
223## TelegramBaseCallbackQueryController
224
225To create such controller you must extend TelegramBaseCallbackQueryController.
226
227This controllers are very simple, they have only one method - `handle`, this method will be called for all queries and instance of `CallbackQuery` will be passed.
228
229## TelegramBaseInlineQueryController
230
231To create such controller you must extend TelegramBaseInlineQueryController.
232
233These controllers also have `handle` method which will be called for all queries and an instance of `InlineScope` will be passed.
234Also they its have `chosenResult` method which will be called when user select some result, an instance of `ChosenInlineResult`
235
236Also as the `TelegramBaseController` it has `_api` and `_localization` properties.
237
238## Getting updates
239You can use long-pooling or webhooks to get updates.
240Long-pooling used by default. To use webhooks you need to init library like this:
241```js
242const tg = new Telegram.Telegram('YOUR_TOKEN', {
243 webhook: {
244 url: 'https://61f66256.ngrok.io',
245 port: 3000,
246 host: 'localhost'
247 }
248})
249```
250You can also create any other custom update fetcher: just extend Telegram.BaseUpdateFetcher and pass it to library:
251```js
252const tg = new Telegram.Telegram('YOUR_TOKEN', {
253 updateFetcher: new MyUpdateFetcher()
254})
255```
256
257
258## Clustering
259By default library will create one worker per cpu. You can change it like this:
260
261```js
262const tg = new Telegram.Telegram('YOUR_TOKEN', {
263 workers: 1
264})
265```
266
267If you want run some code on main process use `tg.onMaster` method:
268
269```js
270const tg = new Telegram.Telegram('YOUR_TOKEN', {
271 workers: 1
272})
273
274tg.sendMessage(123, 'test message') //will be sent 2 times (one time on master and one time on worker)
275
276tg.onMaster(() => {
277 tg.sendMessage(123, 'test message') //will be sent one time
278})
279```
280
281## Web admin
282By default library will start web admin at localhost:7777, to change that use `webAdmin` properpty:
283```js
284const tg = new Telegram.Telegram('YOUR_TOKEN', {
285 webAdmin: {
286 port: 1234,
287 host: 'localhost'
288 }
289})
290```
291
292## API
293You can call api methods two ways:
294
295Directly from tg:
296
297```js
298tg.api.sendMessage(chatId, 'Hi')
299```
300
301Or if you using controllers controller will pass you context `$` that already knows current chat id, so it's more easy to use:
302
303```js
304$.sendMessage('Hi')
305```
306
307All methods have required parameters and optional parameters, you can find them in [api documentation](https://core.telegram.org/bots/api#available-methods)
308If you want to pass optional parameters you should pass them as an object:
309```js
310$.sendMessage('Hi', { disable_notification: true })
311```
312
313## Scope
314
315There is two types of scope:
316
317* scope for message controllers - `Scope`
318* scope for inline mode controller - `InlineScope`
319
320Message controllers scope:
321
322scope will be passed to `handle` method or to your methods defined in `routes`
323
324Main feature of scope is that scope already knows current chat id, so there is no need to pass that parameter.
325Scope have all api methods that have chatId as their first parameter already filled.
326
327Scope also contains some information about update.
328
329
330Inline controllers scope also has all api methods filled with userId.
331
332## Forms
333
334In message controllers scope has `runForm` method.
335
336With `$.runForm` method you can create forms:
337
338```js
339const form = {
340 name: {
341 q: 'Send me your name',
342 error: 'sorry, wrong input',
343 validator: (message, callback) => {
344 if(message.text) {
345 callback(true, message.text) //you must pass the result also
346 return
347 }
348
349 callback(false)
350 }
351 },
352 age: {
353 q: 'Send me your age',
354 error: 'sorry, wrong input',
355 validator: (message, callback) => {
356 if(message.text && IsNumeric(message.text)) {
357 callback(true, toInt(message.text))
358 return
359 }
360
361 callback(false)
362 }
363 }
364}
365
366$.runForm(form, (result) => {
367 console.log(result)
368})
369```
370
371Bot will send the 'q' message to user, wait for message, validate it with your validator function and save the answer, if validation fails bot will ask again that question.
372You can also do some filtering in your validator, so you can pass the result as second parameter to callback.
373You can also pass keyboard to `keyboard` field.
374
375## Menu
376
377You can create menu with $.runMenu function:
378
379```js
380$.runMenu({
381 message: 'Select:',
382 options: {
383 parse_mode: 'Markdown' // in options field you can pass some additional data, like parse_mode
384 },
385 'Exit': {
386 message: 'Do you realy want to exit?',
387 resizeKeyboard: true,
388 'yes': () => {
389
390 },
391 'no': () => {
392
393 }
394 },
395 'anyMatch': () => { //will be executed at any other message
396
397 }
398})
399```
400
401Bot will create keyboard and send it with your message, when user tap button bot will call its callback, if it's submenu bot will send submenu.
402
403Layouting menu:
404
405You can pass the maximum number of buttons in line like this:
406
407```js
408$.runMenu({
409 message: 'Select:',
410 layout: 2,
411 'test1': () => {}, //will be on first line
412 'test2': () => {}, //will be on first line
413 'test3': () => {}, //will be on second line
414 'test4': () => {}, //will be on second line
415 'test5': () => {}, //will be on third line
416})
417```
418Or you can pass an array of number of buttons for each line:
419
420```js
421$.runMenu({
422 message: 'Select:',
423 layout: [1, 2, 1, 1],
424 'test1': () => {}, //will be on first line
425 'test2': () => {}, //will be on second line
426 'test3': () => {}, //will be on second line
427 'test4': () => {}, //will be on third line
428 'test5': () => {}, //will be on fourth line
429})
430```
431
432## Inline Menu
433
434You can create inline menu using $.runInlineMenu:
435
436```js
437$.runInlineMenu({
438 layout: 2, //some layouting here
439 method: 'sendMessage', //here you must pass the method name
440 params: ['text'], //here you must pass the parameters for that method
441 menu: [
442 {
443 text: '1', //text of the button
444 callback: (callbackQuery, message) => { //to your callback will be passed callbackQuery and response from method
445 console.log(1)
446 }
447 },
448 {
449 text: 'Exit',
450 message: 'Are you sure?',
451 layout: 2,
452 menu: [ //Sub menu (current message will be edited)
453 {
454 text: 'Yes!',
455 callback: () => {
456
457 }
458 },
459 {
460 text: 'No!',
461 callback: () => {
462
463 }
464 }
465 ]
466 }
467 ]
468})
469```
470
471## waitForRequest
472
473Messages controller scope has `waitForRequest` method after calling that the next update from current user will be passed to promise.
474
475## waitForCallbackQuery
476
477If you send some inline keyboard after that you can call this method, pass to it string or array of string with callback data or your InlineKeyboardMarkup and then when user press button CallbackQuery will be passed to Promise
478
479```js
480$.sendMessage('Send me your name')
481$.waitForRequest
482 .then($ => {
483 $.sendMessage(`Hi ${$.message.text}!`)
484 })
485```
486## Sessions
487
488For user:
489
490```js
491$.setUserSession('someKey', 'some data')
492 .then(() => {
493 return $.getUserSession('someKey')
494 })
495 .then(data => {
496 console.log(data)
497 })
498```
499
500For chat:
501
502```js
503$.setChatSession('someKey', 'some data')
504 .then(() => {
505 return $.getChatSession('someKey')
506 })
507 .then(data => {
508 console.log(data)
509 })
510```
511
512
513By default sessions are stored in memory, but you can store them anywhere, you need to extend `BaseStorage` and pass instance of your storage to `Telegram`:
514
515```js
516const tg = new Telegram.Telegram('YOUR_TOKEN',{
517 storage: new MyStorage()
518})
519```
520
521## Logging
522
523Module makes some logs during work, by default logs are written to console, but you can create your own logger if you want, you must extend `BaseLogger` and pass instance of your logger to `Telegram`:
524
525```js
526const tg = new Telegram.Telegram('YOUR_TOKEN', {
527 logger: new MyLogger()
528})
529```
530
531## Localization
532
533To use localization you need to pass your localization files to `Telegram`, they must be like this:
534
535```js
536{
537 "lang": "Ru",
538 "phrases": {
539 "startMessage": "тест"
540 }
541}
542```
543
544after creating your files you need to pass them to `Telegram`:
545
546```js
547const tg = new Telegram.Telegram('YOUR_TOKEN', {
548 localization: [require('./Ru.json')]
549})
550```
551
552Now you can use them in controllers like this:
553```js
554console.log(this._localization.Ru.startMessage)
555```
556
557You can even set the language for specific user:
558```js
559this._localization.setLanguageForUser(123456, 'Ru')
560```
561
562Or get phrases for user:
563```js
564this._localization.forUser(123456)
565```
566
567## Scope extensions:
568
569Lets say you have some function that you want to be in scope, now you can do that like this:
570
571
572```js
573'use strict'
574
575const Telegram = require('telegram-node-bot')
576const TelegramBaseController = Telegram.TelegramBaseController
577const BaseScopeExtension = Telegram.BaseScopeExtension
578const tg = new Telegram.Telegram('YOUR_TOKEN')
579
580class SumScopeExtension extends BaseScopeExtension {
581 process(num1, num2) {
582 return num1 + num2
583 }
584
585 get name() {
586 return 'sum'
587 }
588}
589
590class SumController extends TelegramBaseController {
591 /**
592 * @param {Scope} $
593 */
594 sumHandler($) {
595 $.sendMessage($.sum($.query.num1, $.query.num2))
596 }
597
598 get routes() {
599 return {
600 '/sum :num1 :num2': 'sumHandler'
601 }
602 }
603}
604
605tg.router
606 .when(['/sum :num1 :num2'], new SumController())
607
608tg.addScopeExtension(SumScopeExtension)
609```
610
611## Sending files
612
613From file id:
614
615```js
616$.sendPhoto(InputFile.byId('ID')) or $.sendPhoto('ID')
617```
618
619From url:
620
621```js
622$.sendPhoto(InputFile.byUrl('URL', 'image.jpg')) or $.sendPhoto({ url: 'URL', filename: 'image.jpg'})
623```
624
625By path:
626
627```js
628$.sendPhoto(InputFile.byFilePath('path/to/file')) or $.sendPhoto({ path: 'path/to/file'})
629```
630
631[Full API reference](http://nabovyan.xyz/telegram-node-bot/)
632
633## License
634
635Copyright (c) 2016 Narek Abovyan
636
637Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
638
639The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
640
641THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.