├── .dokker.json ├── .env ├── .gitignore ├── .npmignore ├── .travis.yml ├── API.js ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── bot.js ├── docs ├── README.md ├── annotated │ ├── API.html │ ├── docco.css │ └── public │ │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── roboto-black.eot │ │ ├── roboto-black.ttf │ │ └── roboto-black.woff │ │ └── stylesheets │ │ └── normalize.css ├── demo-kb.jpg ├── demo-pic.jpg ├── index.html ├── styles.css └── tests.html ├── herokucmds.txt ├── index.js ├── package.json ├── public └── node.svg ├── test └── test.js ├── testbot.js └── views ├── bootstrap.min.css ├── index.html └── js ├── angular.min.js ├── bootstrap.min.js ├── jquery-2.1.4.min.js └── ui.js /.dokker.json: -------------------------------------------------------------------------------- 1 | { 2 | "dir": "docs", 3 | "literate": { 4 | "source": "API.js", 5 | "dir": "annotated" 6 | }, 7 | "jsdoc": { 8 | "title": "Telegram Bot Bootstrap", 9 | "source": "API.js", 10 | "markdown": "README.md", 11 | "html": "index.html", 12 | "readme": "README.md", 13 | "template": "templates/index.ejs.html", 14 | "github": "https://github.com/kengz/telegram-bot-bootstrap", 15 | "site": "http://kengz.github.io/telegram-bot-bootstrap/" 16 | }, 17 | "mocha": { 18 | "command": "mocha -u tdd --reporter doc", 19 | "test": "test/test.js", 20 | "path": "tests.html", 21 | "template": "templates/tests.ejs.html" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=8443 2 | TOKEN=your-Telegram-bot-token 3 | WEBHOOK=your-webhook-url -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin/ 3 | bin-debug/ 4 | bin-release/ 5 | node_modules/ 6 | templates/ 7 | .tmp/ 8 | 9 | # Other files and folders 10 | .settings/ 11 | 12 | # System files 13 | npm-debug.log 14 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin/ 3 | bin-debug/ 4 | bin-release/ 5 | node_modules/ 6 | templates/ 7 | .tmp/ 8 | 9 | # Other files and folders 10 | .settings/ 11 | 12 | # System files 13 | npm-debug.log 14 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "0.11" 5 | install: 6 | - npm install 7 | matrix: 8 | fast_finish: true 9 | allow_failures: 10 | - node_js: 0.10 11 | -------------------------------------------------------------------------------- /API.js: -------------------------------------------------------------------------------- 1 | // dependencies 2 | var _ = require('lomath'); 3 | var fs = require('fs'); 4 | 5 | // imports 6 | var req = require('reqscraper').req; 7 | 8 | // The API object prototype 9 | /** 10 | * The API bot constructor. 11 | * 12 | * @category Telegram API 13 | * @param {string} token Your Telegram bot token. 14 | * @returns {Bot} The bot able to call methods. 15 | * 16 | * @example 17 | * 18 | * var fs = require('fs'); 19 | * var bot = require('telegram-bot-bootstrap'); 20 | * var Alice = new bot(your_bot_token); 21 | * 22 | * Alice.sendMessage(user_chat_id, 'Hey wanna see some cool art?'); 23 | * 24 | * Alice.sendPhoto(user_chat_id, fs.createReadStream(__dirname+'/alexiuss.jpg'), * 'Chronoscape by Alexiuss').then(console.log) 25 | * 26 | * var kb = { 27 | * keyboard: [ 28 | * ['one'], 29 | * ['two', 'three'], 30 | * ['four', 'five', 'six'] 31 | * ], 32 | * one_time_keyboard: true 33 | * }; 34 | * Alice.sendMessage(user_chat_id, "Choose a lucky number", undefined, undefined, * kb) 35 | * 36 | * // → The messages and photos are sent to user. 37 | * 38 | */ 39 | var API = function(token) { 40 | this.token = token; 41 | this.baseUrl = 'https://api.telegram.org/bot' + this.token; 42 | 43 | // A sample Telegram bot JSON data, for req options 44 | this.formData = { 45 | chat_id: "87654321", 46 | text: "Hello there" 47 | }; 48 | 49 | // template options for req the Telegram Bot API 50 | this.baseoptions = { 51 | method: 'POST', 52 | baseUrl: this.baseUrl, 53 | url: "sendMessage", 54 | formData: this.formData 55 | }; 56 | 57 | /////////////////////////////// 58 | // Internal, private methods // 59 | /////////////////////////////// 60 | 61 | // Convert an data to format proper for HTTP methods 62 | this.toHTTPProper = function(data) { 63 | // currently serialize Array now, leave stream/string 64 | return _.isArray(data) ? JSON.stringify(data) : data; 65 | // return data; 66 | } 67 | 68 | // serialize a whole object proper for HTTP methods 69 | // discard key-value pair if value is undefined 70 | // only returns non-empty JSON 71 | this.serialize = function(obj) { 72 | // var ser = _.omit( 73 | // _.mapValues(_.flattenJSON(obj), this.toHTTPProper) 74 | // , _.isUndefined); 75 | var ser = _.omit( 76 | _.mapValues(obj, this.toHTTPProper) 77 | , _.isUndefined); 78 | if (!_.isEmpty(ser)) return ser; 79 | } 80 | 81 | // properly set req as its method 82 | this.req = req; 83 | 84 | // Extends the options for req for the Telegram Bot. 85 | // @param {string} botMethod Telegram bot API method. 86 | // @param {JSON} JSONdata A JSON object with the required fields (see API). 87 | // @param {string} [HTTPMethod=POST] Optionally change method if need to. 88 | // @returns {JSON} option The extended option. 89 | // 90 | // @example 91 | // extOpt('sendMessage', {chat_id: 123456, text: 'hello world'}) 92 | // → { 93 | // method: 'POST', 94 | // baseUrl: 'https://api.telegram.org/bot...', 95 | // url: 'sendMessage', 96 | // formData: {chat_id: 123456, text: 'hello world'} } 97 | this.extOpt = function(botMethod, JSONdata, HTTPMethod) { 98 | var opt = _.clone({ 99 | method: 'POST', 100 | baseUrl: this.baseUrl, 101 | url: "sendMessage", 102 | formData: this.formData 103 | }); 104 | _.assign(opt, { 105 | url: botMethod 106 | }) 107 | if (JSONdata) _.assign(opt, { 108 | formData: this.serialize(JSONdata) 109 | }) 110 | if (HTTPMethod) _.assign(opt, { 111 | botMethod: HTTPMethod 112 | }); 113 | 114 | return opt; 115 | } 116 | 117 | // shorthand composition: bot's HTTP request 118 | this.reqBot = _.flow(this.extOpt, this.req) 119 | 120 | } 121 | 122 | 123 | ////////////////////////// 124 | // Telegram API methods // 125 | ////////////////////////// 126 | // All the methods below return a promise form the `q` library (see https://github.com/kriskowal/q) for chaining, thus can be called by: samplemethod().then(handlerfunction).then(handler2) 127 | // You can: 128 | // pass a single JSON object as the 'first' argument (see the Telegram API), or 129 | // pass multiple parameters in the order listed on Telegram bot API page; the 'first' is always the 'chat_id' in this case. 130 | // Note that the response from the HTTP call is always a string (we denote as HTTPres for clarity), thus you might wish to JSON.parse it. 131 | 132 | /** 133 | * Use this method to receive incoming updates using long polling (wiki). 134 | * 135 | * @category Telegram API 136 | * @param {JSON|integer} [first|offset] An optional JSON object with the parameters, or the offset integer 137 | * @param {integer} [limit] 138 | * @param {integer} [timeout] 139 | * @returns {string} HTTPres An Array of Update objects is returned. 140 | * 141 | * @example 142 | * Alice.getUpdates().then(console.log) 143 | * // → {"ok":true,"result":[{"update_id":1234567, "message":{"message_id":1,"from":{"id":87654321, ...}}] 144 | * 145 | */ 146 | API.prototype.getUpdates = function(first, limit, timeout) { 147 | var options = _.isObject(first) ? 148 | first : { 149 | offset: first, 150 | limit: limit, 151 | timeout: timeout 152 | }; 153 | return this.reqBot('getUpdates', options) 154 | } 155 | 156 | /** 157 | * Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. 158 | * 159 | * @category Telegram API 160 | * @param {string} url A JSON object with the parameters, or the HTTPS url to send updates to. Use an empty string to remove webhook integration. 161 | * @returns {string} HTTPres An Array of Update objects is returned. 162 | * 163 | * @xample 164 | * setWebhook('') // empty string to unset webhook 165 | * 166 | * setWebhook('http://yoururl.com') // to set webhook 167 | */ 168 | API.prototype.setWebhook = function(first) { 169 | var options = _.isObject(first) ? 170 | first : { 171 | url: first 172 | }; 173 | return this.reqBot('setWebhook', options); 174 | } 175 | 176 | /** 177 | * A simple method for testing your bot's auth token. Requires no parameters. 178 | * 179 | * @category Telegram API 180 | * @returns {string} HTTPres Basic information about the bot in form of a User object. 181 | */ 182 | API.prototype.getMe = function() { 183 | return this.reqBot('getMe'); 184 | } 185 | 186 | /** 187 | * Use this method to send text messages. 188 | * 189 | * @category Telegram API 190 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 191 | * @param {string} text Text of the message to be sent. 192 | * @param {boolean} [disable_web_page_preview] Disables link previews for links in this message. 193 | * @param {integer} [reply_to_message_id] If the message is a reply, ID of the original message. 194 | * @param {KeyboardMarkup} [reply_markup] Additional interface options. A JSON object (don't worry about serializing; it's handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 195 | * @returns {string} HTTPres On success, the sent Message is returned. 196 | * 197 | * @example 198 | * Alice.sendMessage({chat_id: 87654321, text: 'hello world'}) 199 | * Alice.sendMessage(87654321, 'hello world') // equivalent, cleaner 200 | * // → 'hello world' is sent to the user with the id. 201 | * 202 | * // var kb = { 203 | * // keyboard: [ 204 | * // ['one'], 205 | * // ['two', 'three'], 206 | * // ['four', 'five', 'six'] 207 | * // ], 208 | * // one_time_keyboard: true 209 | * // }; 210 | * Alice.sendMessage(87654321, "Choose a lucky number", undefined, undefined, kb) 211 | * // → 'Choose a lucky number' is sent, with custom reply keyboard 212 | * 213 | */ 214 | API.prototype.sendMessage = function(first, text, disable_web_page_preview, reply_to_message_id, reply_markup) { 215 | var options = _.isObject(first) ? 216 | first : { 217 | chat_id: first, 218 | text: text || 'null-guarded; Your method is sending empty text.', 219 | disable_web_page_preview: disable_web_page_preview, 220 | reply_to_message_id: reply_to_message_id, 221 | reply_markup: JSON.stringify(reply_markup) 222 | }; 223 | return this.reqBot('sendMessage', options); 224 | } 225 | 226 | /** 227 | * Use this method to forward messages of any kind. 228 | * 229 | * @category Telegram API 230 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 231 | * @param {integer} from_chat_id Unique identifier for the chat where the original message was sent — User or GroupChat id. 232 | * @param {integer} message_id Unique message identifier 233 | * @returns {string} HTTPres On success, the sent Message is returned. 234 | * 235 | * @example 236 | * Alice.forwardMessage(87654321, 12345678, 87654356) 237 | * // → Message is forwarded 238 | * 239 | */ 240 | API.prototype.forwardMessage = function(first, from_chat_id, message_id) { 241 | var options = _.isObject(first) ? 242 | first : { 243 | chat_id: first, 244 | from_chat_id: from_chat_id, 245 | message_id: message_id 246 | }; 247 | return this.reqBot('forwardMessage', options); 248 | } 249 | 250 | /** 251 | * Use this method to send photos. 252 | * 253 | * @category Telegram API 254 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 255 | * @param {inputFile|string} photo Photo to send. You can either pass a file_id as String to resend a photo that is already on the Telegram servers, or upload a new photo using multipart/form-data. 256 | * @param {string} [caption] 257 | * @param {integer} [reply_to_message_id] If the message is a reply, ID of the original message. 258 | * @param {KeyboardMarkup} [reply_markup] Additional interface options. A JSON object (don't worry about serializing; it's handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 259 | * @returns {string} HTTPres On success, the sent Message is returned. 260 | * 261 | * @example 262 | * Alice.sendMessage(87654321, fs.createReadStream('localpath/to/photo.jpg'), 'cool caption') 263 | * // → The photo on local system is sent to the id. 264 | * 265 | */ 266 | API.prototype.sendPhoto = function(first, photo, caption, reply_to_message_id, reply_markup) { 267 | var options = _.isObject(first) ? 268 | first : { 269 | chat_id: first, 270 | photo: photo, 271 | caption: caption, 272 | reply_to_message_id: reply_to_message_id, 273 | reply_markup: JSON.stringify(reply_markup) 274 | }; 275 | return this.reqBot('sendPhoto', options); 276 | } 277 | 278 | /** 279 | * Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .ogg file encoded with OPUS (other formats may be sent as Document). On success, the sent Message is returned. Bots can currently send audio files of up to 50 MB in size, this limit may be changed in the future. 280 | * 281 | * @category Telegram API 282 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 283 | * @param {inputFile|string} audio Audio file to send. You can either pass a file_id as String to resend an audio that is already on the Telegram servers, or upload a new audio file using multipart/form-data. 284 | * @param {integer} [reply_to_message_id] If the message is a reply, ID of the original message. 285 | * @param {KeyboardMarkup} [reply_markup] Additional interface options. A JSON object (don't worry about serializing; it's handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 286 | * @returns {string} HTTPres On success, the sent Message is returned. 287 | * 288 | */ 289 | API.prototype.sendAudio = function(first, audio, reply_to_message_id, reply_markup) { 290 | var options = _.isObject(first) ? 291 | first : { 292 | chat_id: first, 293 | audio: audio, 294 | reply_to_message_id: reply_to_message_id, 295 | reply_markup: JSON.stringify(reply_markup) 296 | }; 297 | return this.reqBot('sendAudio', options); 298 | } 299 | 300 | /** 301 | * Use this method to send general files. On success, the sent Message is returned. Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. 302 | * 303 | * @category Telegram API 304 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 305 | * @param {inputFile|string} document File to send. You can either pass a file_id as String to resend a file that is already on the Telegram servers, or upload a new file using multipart/form-data. 306 | * @param {integer} [reply_to_message_id] If the message is a reply, ID of the original message. 307 | * @param {KeyboardMarkup} [reply_markup] Additional interface options. A JSON object (don't worry about serializing; it's handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 308 | * @returns {string} HTTPres On success, the sent Message is returned. 309 | * 310 | */ 311 | API.prototype.sendDocument = function(first, document, reply_to_message_id, reply_markup) { 312 | var options = _.isObject(first) ? 313 | first : { 314 | chat_id: first, 315 | document: document, 316 | reply_to_message_id: reply_to_message_id, 317 | reply_markup: JSON.stringify(reply_markup) 318 | }; 319 | return this.reqBot('sendDocument', options); 320 | } 321 | 322 | /** 323 | * Use this method to send .webp stickers. 324 | * 325 | * @category Telegram API 326 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 327 | * @param {inputFile|string} sticker Sticker to send. You can either pass a file_id as String to resend a sticker that is already on the Telegram servers, or upload a new sticker using multipart/form-data. 328 | * @param {integer} [reply_to_message_id] If the message is a reply, ID of the original message. 329 | * @param {KeyboardMarkup} [reply_markup] Additional interface options. A JSON object (don't worry about serializing; it's handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 330 | * @returns {string} HTTPres On success, the sent Message is returned. 331 | * 332 | */ 333 | API.prototype.sendSticker = function(first, sticker, reply_to_message_id, reply_markup) { 334 | var options = _.isObject(first) ? 335 | first : { 336 | chat_id: first, 337 | sticker: sticker, 338 | reply_to_message_id: reply_to_message_id, 339 | reply_markup: JSON.stringify(reply_markup) 340 | }; 341 | return this.reqBot('sendSticker', options); 342 | } 343 | 344 | /** 345 | * Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). On success, the sent Message is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. 346 | * 347 | * @category Telegram API 348 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 349 | * @param {inputFile|string} video Video to send. You can either pass a file_id as String to resend a video that is already on the Telegram servers, or upload a new video file using multipart/form-data. 350 | * @param {integer} [reply_to_message_id] If the message is a reply, ID of the original message. 351 | * @param {KeyboardMarkup} [reply_markup] Additional interface options. A JSON object (don't worry about serializing; it's handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 352 | * @returns {string} HTTPres On success, the sent Message is returned. 353 | * 354 | */ 355 | API.prototype.sendVideo = function(first, video, reply_to_message_id, reply_markup) { 356 | var options = _.isObject(first) ? 357 | first : { 358 | chat_id: first, 359 | video: video, 360 | reply_to_message_id: reply_to_message_id, 361 | reply_markup: JSON.stringify(reply_markup) 362 | }; 363 | return this.reqBot('sendVideo', options); 364 | } 365 | 366 | /** 367 | * Use this method to send point on the map. 368 | * 369 | * @category Telegram API 370 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 371 | * @param {number} latitude Latitude of location 372 | * @param {number} longitude Longitude of location 373 | * @param {integer} [reply_to_message_id] If the message is a reply, ID of the original message. 374 | * @param {KeyboardMarkup} [reply_markup] Additional interface options. A JSON object (don't worry about serializing; it's handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 375 | * @returns {string} HTTPres On success, the sent Message is returned. 376 | * 377 | */ 378 | API.prototype.sendLocation = function(first, latitude, longitude, reply_to_message_id, reply_markup) { 379 | var options = _.isObject(first) ? 380 | first : { 381 | chat_id: first, 382 | longitude: longitude, 383 | latitude: latitude, 384 | reply_to_message_id: reply_to_message_id, 385 | reply_markup: JSON.stringify(reply_markup) 386 | }; 387 | return this.reqBot('sendLocation', options); 388 | } 389 | 390 | /** 391 | * Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). 392 | * 393 | * @category Telegram API 394 | * @param {JSON|integer} first Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.. 395 | * @param {string} action Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for text messages, upload_photo for photos, record_video or upload_video for videos, record_audio or upload_audio for audio files, upload_document for general files, find_location for location data. 396 | * @returns {string} HTTPres On success, the sent Message is returned. 397 | * 398 | */ 399 | API.prototype.sendChatAction = function(first, action) { 400 | var options = _.isObject(first) ? 401 | first : { 402 | chat_id: first, 403 | action: action 404 | }; 405 | return this.reqBot('sendChatAction', options); 406 | } 407 | 408 | /** 409 | * Use this method to get a list of profile pictures for a user. 410 | * 411 | * @category Telegram API 412 | * @param {JSON|integer} first Your own JSON object, or user_id: Unique identifier of the target user. 413 | * @param {integer} [offset] Sequential number of the first photo to be returned. By default, all photos are returned. 414 | * @param {integer} [limit] Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100. 415 | * @returns {string} HTTPres Returns a UserProfilePhotos object. 416 | * 417 | */ 418 | API.prototype.getUserProfilePhotos = function(first, offset, limit) { 419 | var options = _.isObject(first) ? 420 | first : { 421 | user_id: first, 422 | offset: offset, 423 | limit: limit 424 | }; 425 | return this.reqBot('getUserProfilePhotos', options); 426 | } 427 | 428 | /** 429 | * Handles a Telegram Update object sent from the server. Extend this method for your bot. 430 | * 431 | * @category Bot 432 | * @param {Object} req The incoming HTTP request. 433 | * @param {Object} res The HTTP response in return. 434 | * @returns {Promise} promise A promise returned from calling Telegram API method(s) for chaining. 435 | * 436 | * @example 437 | * var bot1 = new bot('yourtokenhere'); 438 | * ...express server setup 439 | * app.route('/') 440 | * // robot API as middleware 441 | * .post(function(req, res) { 442 | * bot1.handle(req, res) 443 | * }) 444 | * // Then bot will handle the incoming Update from you, routed from Telegram! 445 | * 446 | */ 447 | function handle(req, res) { 448 | } 449 | 450 | 451 | // export constructor 452 | module.exports = API; 453 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wah Loon Keng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # telegram-bot-bootstrap 2 | [![npm version](https://badge.fury.io/js/telegram-bot-bootstrap.svg)](http://badge.fury.io/js/telegram-bot-bootstrap) [![Build Status](https://travis-ci.org/kengz/telegram-bot-bootstrap.svg?branch=master)](https://travis-ci.org/kengz/telegram-bot-bootstrap) [![Dependency Status](https://gemnasium.com/kengz/telegram-bot-bootstrap.svg)](https://gemnasium.com/kengz/telegram-bot-bootstrap) 3 | 4 | **NOTICE**: *I've generalized this and built a much bigger bot, [AIVA](http://kengz.me/aiva/). Go check it out!* 5 | 6 | 7 | A bootstrap for Telegram bot with directly deployable sample bot and JS-wrapped API methods. You can use all methods available in the Telegram API directly, and send any supported media (we serialize the formData for you to send over HTTP). 8 | 9 | See the full [API documentation](http://kengz.github.io/telegram-bot-bootstrap/) of this project. 10 | 11 | 12 | 13 | ## Installation 14 | Do either of 15 | 16 | ``` 17 | npm install telegram-bot-bootstrap 18 | git clone https://github.com/kengz/telegram-bot-bootstrap.git 19 | ``` 20 | 21 | Either way you'll get a module with the Telegram bot API wrapped in Node, and a bootstrapped, deploy-ready project. 22 | 23 | If you haven't already, get a bot from [BotFather](https://core.telegram.org/bots) and remember your bot *token*! 24 | 25 | 26 | ## Features 27 | - Wrapped API methods take either a JSON object or multiple parameters. 28 | - Auto serialization for HTTP formData: send photos/keyboards/media directly. 29 | - API methods return `promises` (uses [q](https://github.com/kriskowal/q)) for easy chaining and flow control. 30 | - Complete documentation and examples usages. 31 | - Bootstrapped and directly deployable bot. 32 | 33 | 34 | ## Usage: only the API 35 | See the full [API documentation](http://kengz.github.io/telegram-bot-bootstrap/) of this project. 36 | 37 | `API.js` contains the [Telegram Bot API](https://core.telegram.org/bots/api) wrapped in Node. The methods will return a promise for easy chaining, and will take either a whole JSON, or multiple parameters for convenience. For the latter, everything will be auto-serialized for HTTP so you don't have to deal with any of the nasty HTTP protocol stuff. 38 | 39 | If you wish to use just the API or test the bot methods, here's an example 40 | 41 | #### Local(not deployed yet) test bot constructor 42 | See `testbot.js` for functional example. 43 | 44 | ```Javascript 45 | // testbot.js 46 | var bot = require('telegram-bot-bootstrap'); 47 | var fs = require('fs'); 48 | 49 | var Alice = new bot(your-token); 50 | 51 | Alice.getUpdates().then(console.log) 52 | // → you'll see an update message. Look for your user_id in "message.from.id" 53 | 54 | // Once you get your id to message yourself, you may: 55 | Alice.sendMessage(your-id, "Hello there") 56 | // → you'll receive a message from Alice. 57 | .then(console.log) 58 | // → optional, will log the successful message sent over HTTP 59 | ``` 60 | 61 | #### Sending Message, Photo and all media 62 | 63 | ``` 64 | Alice.sendMessage(86953862, 'Hey wanna see some cool art?'); 65 | 66 | Alice.sendPhoto(86953862, fs.createReadStream(__dirname+'/alexiuss.jpg'), 'Chronoscape by Alexiuss').then(console.log) 67 | ``` 68 | 69 | You'll receive this: 70 | 71 | 72 | 73 | #### Custom keyboard 74 | 75 | ``` 76 | var kb = { 77 | keyboard: [ 78 | ['one'], 79 | ['two', 'three'], 80 | ['four', 'five', 'six'] 81 | ], 82 | one_time_keyboard: true 83 | }; 84 | Alice.sendMessage(86953862, "Choose a lucky number", undefined, undefined, kb) 85 | ``` 86 | 87 | You'll get this: 88 | 89 | 90 | 91 | 92 | ## Usage: Bootstrapped, Deployable Bot 93 | See `index.js` for deployable app, and `bot.js` to customize bot commands. 94 | 95 | We distinguish the bot from the API: `bot.js` extends `API.js`, and will be the deployed component. 96 | 97 | This whole project is bootstrapped and deploy-ready: all the details of HTTP and server stuff taken care of for you. I deploy this git project onto my Heroku and voila, my bot is alive. 98 | 99 | #### Setup 100 | In addition to the *token*, you'll need a *webhookUrl*. If you deploy your Node app to *Heroku*, then the *webhookUrl* is simply your Heroku site url. Set both of them in the `.env` file: 101 | 102 | ```Javascript 103 | PORT=8443 104 | TOKEN=your-Telegram-bot-token 105 | WEBHOOK=your-webhook-url 106 | ``` 107 | 108 | The sample available is an echo-bot. To make your bot do interesting stuff, head over to `bot.js`, under the `handle` method, start writing your own from below the *Extend from here* comment. 109 | 110 | The bot inherits all the API methods, so you can simply call them for example by `this.sendMessage`. 111 | 112 | #### Deployment 113 | The server is deployed in `index.js`, and a bot is constructed to handle all *HTTP POST* calls from Telegram. 114 | 115 | I use *Heroku*. This shall work for any other services too. Once I'm done setting up, I do: 116 | 117 | ``` 118 | git push heroku master 119 | ``` 120 | 121 | And done. Start talking to the bot. -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Telegram bot bootstrap", 3 | "description": "A barebones Node.js app using Express 4", 4 | "repository": "https://github.com/kengz/telegram-bot-bootstrap", 5 | "logo": "http://node-js-sample.herokuapp.com/node.svg", 6 | "keywords": ["Telegram", "bot", "bootstrap", "node", "express", "static"] 7 | } 8 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////// 2 | // Safety: Uncomment everything to use // 3 | ///////////////////////////////////////// 4 | 5 | // // dependencies 6 | // var _ = require('lomath'); 7 | 8 | // // API as superclass that bot inherits methods from 9 | // var API = require(__dirname + '/API.js') 10 | 11 | // // The bot object prototype 12 | // // bot extends and inherits methods of API 13 | // var bot = function(token, webhookUrl) { 14 | // API.apply(this, arguments); 15 | // // set webhook on construction: override the old webhook 16 | // this.setWebhook(webhookUrl || ''); 17 | 18 | // } 19 | 20 | // // set prototype to API 21 | // bot.prototype = API.prototype; 22 | // // set constructor back to bot 23 | // bot.prototype.constructor = bot; 24 | 25 | 26 | /** 27 | * Handles a Telegram Update object sent from the server. Extend this method for your bot. 28 | * 29 | * @category Bot 30 | * @param {Object} req The incoming HTTP request. 31 | * @param {Object} res The HTTP response in return. 32 | * @returns {Promise} promise A promise returned from calling Telegram API method(s) for chaining. 33 | * 34 | * @example 35 | * var bot1 = new bot('yourtokenhere'); 36 | * ...express server setup 37 | * app.route('/') 38 | * // robot API as middleware 39 | * .post(function(req, res) { 40 | * bot1.handle(req, res) 41 | * }) 42 | * // Then bot will handle the incoming Update from you, routed from Telegram! 43 | * 44 | */ 45 | // bot.prototype.handle = function(req, res) { 46 | // // the Telegram Update object. Useful shits 47 | // var Update = req.body, 48 | // // the telegram Message object 49 | // Message = Update.message, 50 | // // the user who sent it 51 | // user_id = Message.from.id, 52 | // // id of the chat(room) 53 | // chat_id = Message.chat.id; 54 | 55 | // //////////////////////// 56 | // // Extend from here: // 57 | // //////////////////////// 58 | // // you may call the methods from API.js, which are all inherited by this bot class 59 | 60 | // // echo 61 | // this.sendMessage(chat_id, "you said: " + Message.text); 62 | 63 | // } 64 | 65 | // export the bot class 66 | // module.exports = bot; 67 | 68 | // sample keyboard 69 | // var kb = { 70 | // keyboard: [ 71 | // ['one', 'two'], 72 | // ['three'], 73 | // ['four'] 74 | // ], 75 | // one_time_keyboard: true 76 | // } 77 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # API.js API documentation 2 | 3 | 4 | 5 | 6 | 7 | ## `Bot` 8 | * `handle` 9 | 10 | 11 | 12 | 13 | 14 | ## `Telegram API` 15 | * `API` 16 | * `forwardMessage` 17 | * `getMe` 18 | * `getUpdates` 19 | * `getUserProfilePhotos` 20 | * `sendAudio` 21 | * `sendChatAction` 22 | * `sendDocument` 23 | * `sendLocation` 24 | * `sendMessage` 25 | * `sendPhoto` 26 | * `sendSticker` 27 | * `sendVideo` 28 | * `setWebhook` 29 | 30 | 31 | 32 | 33 | 34 | ## `Methods` 35 | 36 | 37 | 38 | 39 | 40 | ## `Properties` 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ## `“Bot” Methods` 51 | 52 | 53 | 54 | ### `handle(req, res)` 55 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L395 "View in source") [Ⓣ][1] 56 | 57 | Handles a Telegram Update object sent from the server. Extend this method for your bot. 58 | 59 | #### Arguments 60 | 1. `req` *(Object)*: The incoming HTTP request. 61 | 2. `res` *(Object)*: The HTTP response in return. 62 | 63 | #### Returns 64 | *(Promise)*: promise A promise returned from calling Telegram API method(s) for chaining. 65 | 66 | #### Example 67 | ```js 68 | var bot1 = new bot('yourtokenhere'); 69 | ...express server setup 70 | app.route('/') 71 | // robot API as middleware 72 | .post(function(req, res) { 73 | bot1.handle(req, res) 74 | }) 75 | // Then bot will handle the incoming Update from you, routed from Telegram! 76 | ``` 77 | * * * 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ## `“Telegram API” Methods` 86 | 87 | 88 | 89 | ### `API(token)` 90 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L34 "View in source") [Ⓣ][1] 91 | 92 | The API bot constructor. 93 | 94 | #### Arguments 95 | 1. `token` *(string)*: Your Telegram bot token. 96 | 97 | #### Returns 98 | *(Bot)*: The bot able to call methods. 99 | 100 | #### Example 101 | ```js 102 | var fs = require('fs'); 103 | var bot = require('telegram-bot-bootstrap'); 104 | var Alice = new bot(your_bot_token); 105 | 106 | Alice.sendMessage(user_chat_id, 'Hey wanna see some cool art?'); 107 | 108 | Alice.sendPhoto(user_chat_id, fs.createReadStream(__dirname+'/alexiuss.jpg'), * 'Chronoscape by Alexiuss').then(console.log) 109 | 110 | var kb = { 111 | keyboard: [ 112 | ['one'], 113 | ['two', 'three'], 114 | ['four', 'five', 'six'] 115 | ], 116 | one_time_keyboard: true 117 | }; 118 | Alice.sendMessage(user_chat_id, "Choose a lucky number", undefined, undefined, * kb) 119 | 120 | // → The messages and photos are sent to user. 121 | ``` 122 | * * * 123 | 124 | 125 | 126 | 127 | 128 | ### `forwardMessage(first, from_chat_id, message_id)` 129 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L188 "View in source") [Ⓣ][1] 130 | 131 | Use this method to forward messages of any kind. 132 | 133 | #### Arguments 134 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 135 | 2. `from_chat_id` *(integer)*: Unique identifier for the chat where the original message was sent — User or GroupChat id. 136 | 3. `message_id` *(integer)*: Unique message identifier 137 | 138 | #### Returns 139 | *(string)*: HTTPres On success, the sent Message is returned. 140 | 141 | #### Example 142 | ```js 143 | Alice.forwardMessage(87654321, 12345678, 87654356) 144 | // → Message is forwarded 145 | ``` 146 | * * * 147 | 148 | 149 | 150 | 151 | 152 | ### `getMe()` 153 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L130 "View in source") [Ⓣ][1] 154 | 155 | A simple method for testing your bot's auth token. Requires no parameters. 156 | 157 | #### Returns 158 | *(string)*: HTTPres Basic information about the bot in form of a User object. 159 | 160 | * * * 161 | 162 | 163 | 164 | 165 | 166 | ### `getUpdates([first|offset], [limit], [timeout])` 167 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L94 "View in source") [Ⓣ][1] 168 | 169 | Use this method to receive incoming updates using long polling (wiki). 170 | 171 | #### Arguments 172 | 1. `[first|offset]` *(JSON|integer)*: An optional JSON object with the parameters, or the offset integer 173 | 2. `[limit]` *(integer)*: 174 | 3. `[timeout]` *(integer)*: 175 | 176 | #### Returns 177 | *(string)*: HTTPres An Array of Update objects is returned. 178 | 179 | #### Example 180 | ```js 181 | Alice.getUpdates().then(console.log) 182 | // → {"ok":true,"result":[{"update_id":1234567, "message":{"message_id":1,"from":{"id":87654321, ...}}] 183 | ``` 184 | * * * 185 | 186 | 187 | 188 | 189 | 190 | ### `getUserProfilePhotos(first, [offset], [limit])` 191 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L366 "View in source") [Ⓣ][1] 192 | 193 | Use this method to get a list of profile pictures for a user. 194 | 195 | #### Arguments 196 | 1. `first` *(JSON|integer)*: Your own JSON object, or user_id: Unique identifier of the target user. 197 | 2. `[offset]` *(integer)*: Sequential number of the first photo to be returned. By default, all photos are returned. 198 | 3. `[limit]` *(integer)*: Limits the number of photos to be retrieved. Values between `1—100` are accepted. Defaults to `10`0. 199 | 200 | #### Returns 201 | *(string)*: HTTPres Returns a UserProfilePhotos object. 202 | 203 | * * * 204 | 205 | 206 | 207 | 208 | 209 | ### `sendAudio(first, audio, [reply_to_message_id], [reply_markup])` 210 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L237 "View in source") [Ⓣ][1] 211 | 212 | Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .ogg file encoded with OPUS (other formats may be sent as Document). On success, the sent Message is returned. Bots can currently send audio files of up to 50 MB in size, this limit may be changed in the future. 213 | 214 | #### Arguments 215 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 216 | 2. `audio` *(inputFile|string)*: Audio file to send. You can either pass a file_id as String to resend an audio that is already on the Telegram servers, or upload a new audio file using multipart/form-data. 217 | 3. `[reply_to_message_id]` *(integer)*: If the message is a reply, ID of the original message. 218 | 4. `[reply_markup]` *(KeyboardMarkup)*: Additional interface options. A JSON object *(don't worry about serializing; it's handled)* for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 219 | 220 | #### Returns 221 | *(string)*: HTTPres On success, the sent Message is returned. 222 | 223 | * * * 224 | 225 | 226 | 227 | 228 | 229 | ### `sendChatAction(first, action)` 230 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L347 "View in source") [Ⓣ][1] 231 | 232 | Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). 233 | 234 | #### Arguments 235 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.. 236 | 2. `action` *(string)*: Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for text messages, upload_photo for photos, record_video or upload_video for videos, record_audio or upload_audio for audio files, upload_document for general files, find_location for location data. 237 | 238 | #### Returns 239 | *(string)*: HTTPres On success, the sent Message is returned. 240 | 241 | * * * 242 | 243 | 244 | 245 | 246 | 247 | ### `sendDocument(first, document, [reply_to_message_id], [reply_markup])` 248 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L259 "View in source") [Ⓣ][1] 249 | 250 | Use this method to send general files. On success, the sent Message is returned. Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. 251 | 252 | #### Arguments 253 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 254 | 2. `document` *(inputFile|string)*: File to send. You can either pass a file_id as String to resend a file that is already on the Telegram servers, or upload a new file using multipart/form-data. 255 | 3. `[reply_to_message_id]` *(integer)*: If the message is a reply, ID of the original message. 256 | 4. `[reply_markup]` *(KeyboardMarkup)*: Additional interface options. A JSON object *(don't worry about serializing; it's handled)* for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 257 | 258 | #### Returns 259 | *(string)*: HTTPres On success, the sent Message is returned. 260 | 261 | * * * 262 | 263 | 264 | 265 | 266 | 267 | ### `sendLocation(first, latitude, longitude, [reply_to_message_id], [reply_markup])` 268 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L326 "View in source") [Ⓣ][1] 269 | 270 | Use this method to send point on the map. 271 | 272 | #### Arguments 273 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 274 | 2. `latitude` *(number)*: Latitude of location 275 | 3. `longitude` *(number)*: Longitude of location 276 | 4. `[reply_to_message_id]` *(integer)*: If the message is a reply, ID of the original message. 277 | 5. `[reply_markup]` *(KeyboardMarkup)*: Additional interface options. A JSON object *(don't worry about serializing; it's handled)* for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 278 | 279 | #### Returns 280 | *(string)*: HTTPres On success, the sent Message is returned. 281 | 282 | * * * 283 | 284 | 285 | 286 | 287 | 288 | ### `sendMessage(first, text, [disable_web_page_preview], [reply_to_message_id], [reply_markup])` 289 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L162 "View in source") [Ⓣ][1] 290 | 291 | Use this method to send text messages. 292 | 293 | #### Arguments 294 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 295 | 2. `text` *(string)*: Text of the message to be sent. 296 | 3. `[disable_web_page_preview]` *(boolean)*: Disables link previews for links in this message. 297 | 4. `[reply_to_message_id]` *(integer)*: If the message is a reply, ID of the original message. 298 | 5. `[reply_markup]` *(KeyboardMarkup)*: Additional interface options. A JSON object *(don't worry about serializing; it's handled)* for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 299 | 300 | #### Returns 301 | *(string)*: HTTPres On success, the sent Message is returned. 302 | 303 | #### Example 304 | ```js 305 | Alice.sendMessage({chat_id: 87654321, text: 'hello world'}) 306 | Alice.sendMessage(87654321, 'hello world') // equivalent, cleaner 307 | // → 'hello world' is sent to the user with the id. 308 | 309 | // var kb = { 310 | // keyboard: [ 311 | // ['one'], 312 | // ['two', 'three'], 313 | // ['four', 'five', 'six'] 314 | // ], 315 | // one_time_keyboard: true 316 | // }; 317 | Alice.sendMessage(87654321, "Choose a lucky number", undefined, undefined, kb) 318 | // → 'Choose a lucky number' is sent, with custom reply keyboard 319 | ``` 320 | * * * 321 | 322 | 323 | 324 | 325 | 326 | ### `sendPhoto(first, photo, [caption], [reply_to_message_id], [reply_markup])` 327 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L214 "View in source") [Ⓣ][1] 328 | 329 | Use this method to send photos. 330 | 331 | #### Arguments 332 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 333 | 2. `photo` *(inputFile|string)*: Photo to send. You can either pass a file_id as String to resend a photo that is already on the Telegram servers, or upload a new photo using multipart/form-data. 334 | 3. `[caption]` *(string)*: 335 | 4. `[reply_to_message_id]` *(integer)*: If the message is a reply, ID of the original message. 336 | 5. `[reply_markup]` *(KeyboardMarkup)*: Additional interface options. A JSON object *(don't worry about serializing; it's handled)* for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 337 | 338 | #### Returns 339 | *(string)*: HTTPres On success, the sent Message is returned. 340 | 341 | #### Example 342 | ```js 343 | Alice.sendMessage(87654321, fs.createReadStream('localpath/to/photo.jpg'), 'cool caption') 344 | // → The photo on local system is sent to the id. 345 | ``` 346 | * * * 347 | 348 | 349 | 350 | 351 | 352 | ### `sendSticker(first, sticker, [reply_to_message_id], [reply_markup])` 353 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L281 "View in source") [Ⓣ][1] 354 | 355 | Use this method to send .webp stickers. 356 | 357 | #### Arguments 358 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 359 | 2. `sticker` *(inputFile|string)*: Sticker to send. You can either pass a file_id as String to resend a sticker that is already on the Telegram servers, or upload a new sticker using multipart/form-data. 360 | 3. `[reply_to_message_id]` *(integer)*: If the message is a reply, ID of the original message. 361 | 4. `[reply_markup]` *(KeyboardMarkup)*: Additional interface options. A JSON object *(don't worry about serializing; it's handled)* for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 362 | 363 | #### Returns 364 | *(string)*: HTTPres On success, the sent Message is returned. 365 | 366 | * * * 367 | 368 | 369 | 370 | 371 | 372 | ### `sendVideo(first, video, [reply_to_message_id], [reply_markup])` 373 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L303 "View in source") [Ⓣ][1] 374 | 375 | Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). On success, the sent Message is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. 376 | 377 | #### Arguments 378 | 1. `first` *(JSON|integer)*: Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id. 379 | 2. `video` *(inputFile|string)*: Video to send. You can either pass a file_id as String to resend a video that is already on the Telegram servers, or upload a new video file using multipart/form-data. 380 | 3. `[reply_to_message_id]` *(integer)*: If the message is a reply, ID of the original message. 381 | 4. `[reply_markup]` *(KeyboardMarkup)*: Additional interface options. A JSON object *(don't worry about serializing; it's handled)* for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user. 382 | 383 | #### Returns 384 | *(string)*: HTTPres On success, the sent Message is returned. 385 | 386 | * * * 387 | 388 | 389 | 390 | 391 | 392 | ### `setWebhook(url)` 393 | # [Ⓢ](https://github.com/kengz/telegram-bot-bootstrap#L116 "View in source") [Ⓣ][1] 394 | 395 | Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. 396 | 397 | #### Arguments 398 | 1. `url` *(string)*: A JSON object with the parameters, or the HTTPS url to send updates to. Use an empty string to remove webhook integration. 399 | 400 | #### Returns 401 | *(string)*: HTTPres An Array of Update objects is returned. 402 | 403 | * * * 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | ## `Methods` 412 | 413 | 414 | 415 | 416 | 417 | ## `Properties` 418 | 419 | 420 | 421 | 422 | 423 | [1]: #bot "Jump back to the TOC." 424 | -------------------------------------------------------------------------------- /docs/annotated/API.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API.js 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 440 |
441 | 442 | 443 | -------------------------------------------------------------------------------- /docs/annotated/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'roboto-black'; 25 | src: url('public/fonts/roboto-black.eot'); 26 | src: url('public/fonts/roboto-black.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/roboto-black.woff') format('woff'), 28 | url('public/fonts/roboto-black.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p { 55 | margin: 15px 0 0px; 56 | } 57 | .annotation ul, .annotation ol { 58 | margin: 25px 0; 59 | } 60 | .annotation ul li, .annotation ol li { 61 | font-size: 14px; 62 | line-height: 18px; 63 | margin: 10px 0; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | color: #112233; 68 | line-height: 1em; 69 | font-weight: normal; 70 | font-family: "roboto-black"; 71 | text-transform: uppercase; 72 | margin: 30px 0 15px 0; 73 | } 74 | 75 | h1 { 76 | margin-top: 40px; 77 | } 78 | h2 { 79 | font-size: 1.26em; 80 | } 81 | 82 | hr { 83 | border: 0; 84 | background: 1px #ddd; 85 | height: 1px; 86 | margin: 20px 0; 87 | } 88 | 89 | pre, tt, code { 90 | font-size: 12px; line-height: 16px; 91 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 92 | margin: 0; padding: 0; 93 | } 94 | .annotation pre { 95 | display: block; 96 | margin: 0; 97 | padding: 7px 10px; 98 | background: #fcfcfc; 99 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 100 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 101 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 102 | overflow-x: auto; 103 | } 104 | .annotation pre code { 105 | border: 0; 106 | padding: 0; 107 | background: transparent; 108 | } 109 | 110 | 111 | blockquote { 112 | border-left: 5px solid #ccc; 113 | margin: 0; 114 | padding: 1px 0 1px 1em; 115 | } 116 | .sections blockquote p { 117 | font-family: Menlo, Consolas, Monaco, monospace; 118 | font-size: 12px; line-height: 16px; 119 | color: #999; 120 | margin: 10px 0 0; 121 | white-space: pre-wrap; 122 | } 123 | 124 | ul.sections { 125 | list-style: none; 126 | padding:0 0 5px 0;; 127 | margin:0; 128 | } 129 | 130 | /* 131 | Force border-box so that % widths fit the parent 132 | container without overlap because of margin/padding. 133 | 134 | More Info : http://www.quirksmode.org/css/box.html 135 | */ 136 | ul.sections > li > div { 137 | -moz-box-sizing: border-box; /* firefox */ 138 | -ms-box-sizing: border-box; /* ie */ 139 | -webkit-box-sizing: border-box; /* webkit */ 140 | -khtml-box-sizing: border-box; /* konqueror */ 141 | box-sizing: border-box; /* css3 */ 142 | } 143 | 144 | 145 | /*---------------------- Jump Page -----------------------------*/ 146 | #jump_to, #jump_page { 147 | margin: 0; 148 | background: white; 149 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 150 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 151 | font: 16px Arial; 152 | cursor: pointer; 153 | text-align: right; 154 | list-style: none; 155 | } 156 | 157 | #jump_to a { 158 | text-decoration: none; 159 | } 160 | 161 | #jump_to a.large { 162 | display: none; 163 | } 164 | #jump_to a.small { 165 | font-size: 22px; 166 | font-weight: bold; 167 | color: #676767; 168 | } 169 | 170 | #jump_to, #jump_wrapper { 171 | position: fixed; 172 | right: 0; top: 0; 173 | padding: 10px 15px; 174 | margin:0; 175 | } 176 | 177 | #jump_wrapper { 178 | display: none; 179 | padding:0; 180 | } 181 | 182 | #jump_to:hover #jump_wrapper { 183 | display: block; 184 | } 185 | 186 | #jump_page_wrapper{ 187 | position: fixed; 188 | right: 0; 189 | top: 0; 190 | bottom: 0; 191 | } 192 | 193 | #jump_page { 194 | padding: 5px 0 3px; 195 | margin: 0 0 25px 25px; 196 | max-height: 100%; 197 | overflow: auto; 198 | } 199 | 200 | #jump_page .source { 201 | display: block; 202 | padding: 15px; 203 | text-decoration: none; 204 | border-top: 1px solid #eee; 205 | } 206 | 207 | #jump_page .source:hover { 208 | background: #f5f5ff; 209 | } 210 | 211 | #jump_page .source:first-child { 212 | } 213 | 214 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 215 | @media only screen and (min-width: 320px) { 216 | .pilwrap { display: none; } 217 | 218 | ul.sections > li > div { 219 | display: block; 220 | padding:5px 10px 0 10px; 221 | } 222 | 223 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 224 | padding-left: 30px; 225 | } 226 | 227 | ul.sections > li > div.content { 228 | overflow-x:auto; 229 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 230 | box-shadow: inset 0 0 5px #e5e5ee; 231 | border: 1px solid #dedede; 232 | margin:5px 10px 5px 10px; 233 | padding-bottom: 5px; 234 | } 235 | 236 | ul.sections > li > div.annotation pre { 237 | margin: 7px 0 7px; 238 | padding-left: 15px; 239 | } 240 | 241 | ul.sections > li > div.annotation p tt, .annotation code { 242 | background: #f8f8ff; 243 | border: 1px solid #dedede; 244 | font-size: 12px; 245 | padding: 0 0.2em; 246 | } 247 | } 248 | 249 | /*---------------------- (> 481px) ---------------------*/ 250 | @media only screen and (min-width: 481px) { 251 | #container { 252 | position: relative; 253 | } 254 | body { 255 | background-color: #F5F5FF; 256 | font-size: 15px; 257 | line-height: 21px; 258 | } 259 | pre, tt, code { 260 | line-height: 18px; 261 | } 262 | p, ul, ol { 263 | margin: 0 0 15px; 264 | } 265 | 266 | 267 | #jump_to { 268 | padding: 5px 10px; 269 | } 270 | #jump_wrapper { 271 | padding: 0; 272 | } 273 | #jump_to, #jump_page { 274 | font: 10px Arial; 275 | text-transform: uppercase; 276 | } 277 | #jump_page .source { 278 | padding: 5px 10px; 279 | } 280 | #jump_to a.large { 281 | display: inline-block; 282 | } 283 | #jump_to a.small { 284 | display: none; 285 | } 286 | 287 | 288 | 289 | #background { 290 | position: absolute; 291 | top: 0; bottom: 0; 292 | width: 350px; 293 | background: #fff; 294 | border-right: 1px solid #e5e5ee; 295 | z-index: -1; 296 | } 297 | 298 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 299 | padding-left: 40px; 300 | } 301 | 302 | ul.sections > li { 303 | white-space: nowrap; 304 | } 305 | 306 | ul.sections > li > div { 307 | display: inline-block; 308 | } 309 | 310 | ul.sections > li > div.annotation { 311 | max-width: 350px; 312 | min-width: 350px; 313 | min-height: 5px; 314 | padding: 13px; 315 | overflow-x: hidden; 316 | white-space: normal; 317 | vertical-align: top; 318 | text-align: left; 319 | } 320 | ul.sections > li > div.annotation pre { 321 | margin: 15px 0 15px; 322 | padding-left: 15px; 323 | } 324 | 325 | ul.sections > li > div.content { 326 | padding: 13px; 327 | vertical-align: top; 328 | border: none; 329 | -webkit-box-shadow: none; 330 | box-shadow: none; 331 | } 332 | 333 | .pilwrap { 334 | position: relative; 335 | display: inline; 336 | } 337 | 338 | .pilcrow { 339 | font: 12px Arial; 340 | text-decoration: none; 341 | color: #454545; 342 | position: absolute; 343 | top: 3px; left: -20px; 344 | padding: 1px 2px; 345 | opacity: 0; 346 | -webkit-transition: opacity 0.2s linear; 347 | } 348 | .for-h1 .pilcrow { 349 | top: 47px; 350 | } 351 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 352 | top: 35px; 353 | } 354 | 355 | ul.sections > li > div.annotation:hover .pilcrow { 356 | opacity: 1; 357 | } 358 | } 359 | 360 | /*---------------------- (> 1025px) ---------------------*/ 361 | @media only screen and (min-width: 1025px) { 362 | 363 | body { 364 | font-size: 16px; 365 | line-height: 24px; 366 | } 367 | 368 | #background { 369 | width: 525px; 370 | } 371 | ul.sections > li > div.annotation { 372 | max-width: 525px; 373 | min-width: 525px; 374 | padding: 10px 25px 1px 50px; 375 | } 376 | ul.sections > li > div.content { 377 | padding: 9px 15px 16px 25px; 378 | } 379 | } 380 | 381 | /*---------------------- Syntax Highlighting -----------------------------*/ 382 | 383 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 384 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 385 | /* 386 | 387 | github.com style (c) Vasily Polovnyov 388 | 389 | */ 390 | 391 | pre code { 392 | display: block; padding: 0.5em; 393 | color: #000; 394 | background: #f8f8ff 395 | } 396 | 397 | pre .hljs-comment, 398 | pre .hljs-template_comment, 399 | pre .hljs-diff .hljs-header, 400 | pre .hljs-javadoc { 401 | color: #408080; 402 | font-style: italic 403 | } 404 | 405 | pre .hljs-keyword, 406 | pre .hljs-assignment, 407 | pre .hljs-literal, 408 | pre .hljs-css .hljs-rule .hljs-keyword, 409 | pre .hljs-winutils, 410 | pre .hljs-javascript .hljs-title, 411 | pre .hljs-lisp .hljs-title, 412 | pre .hljs-subst { 413 | color: #954121; 414 | /*font-weight: bold*/ 415 | } 416 | 417 | pre .hljs-number, 418 | pre .hljs-hexcolor { 419 | color: #40a070 420 | } 421 | 422 | pre .hljs-string, 423 | pre .hljs-tag .hljs-value, 424 | pre .hljs-phpdoc, 425 | pre .hljs-tex .hljs-formula { 426 | color: #219161; 427 | } 428 | 429 | pre .hljs-title, 430 | pre .hljs-id { 431 | color: #19469D; 432 | } 433 | pre .hljs-params { 434 | color: #00F; 435 | } 436 | 437 | pre .hljs-javascript .hljs-title, 438 | pre .hljs-lisp .hljs-title, 439 | pre .hljs-subst { 440 | font-weight: normal 441 | } 442 | 443 | pre .hljs-class .hljs-title, 444 | pre .hljs-haskell .hljs-label, 445 | pre .hljs-tex .hljs-command { 446 | color: #458; 447 | font-weight: bold 448 | } 449 | 450 | pre .hljs-tag, 451 | pre .hljs-tag .hljs-title, 452 | pre .hljs-rules .hljs-property, 453 | pre .hljs-django .hljs-tag .hljs-keyword { 454 | color: #000080; 455 | font-weight: normal 456 | } 457 | 458 | pre .hljs-attribute, 459 | pre .hljs-variable, 460 | pre .hljs-instancevar, 461 | pre .hljs-lisp .hljs-body { 462 | color: #008080 463 | } 464 | 465 | pre .hljs-regexp { 466 | color: #B68 467 | } 468 | 469 | pre .hljs-class { 470 | color: #458; 471 | font-weight: bold 472 | } 473 | 474 | pre .hljs-symbol, 475 | pre .hljs-ruby .hljs-symbol .hljs-string, 476 | pre .hljs-ruby .hljs-symbol .hljs-keyword, 477 | pre .hljs-ruby .hljs-symbol .hljs-keymethods, 478 | pre .hljs-lisp .hljs-keyword, 479 | pre .hljs-tex .hljs-special, 480 | pre .hljs-input_number { 481 | color: #990073 482 | } 483 | 484 | pre .hljs-builtin, 485 | pre .hljs-constructor, 486 | pre .hljs-built_in, 487 | pre .hljs-lisp .hljs-title { 488 | color: #0086b3 489 | } 490 | 491 | pre .hljs-preprocessor, 492 | pre .hljs-pi, 493 | pre .hljs-doctype, 494 | pre .hljs-shebang, 495 | pre .hljs-cdata { 496 | color: #999; 497 | font-weight: bold 498 | } 499 | 500 | pre .hljs-deletion { 501 | background: #fdd 502 | } 503 | 504 | pre .hljs-addition { 505 | background: #dfd 506 | } 507 | 508 | pre .hljs-diff .hljs-change { 509 | background: #0086b3 510 | } 511 | 512 | pre .hljs-chunk { 513 | color: #aaa 514 | } 515 | 516 | pre .hljs-tex .hljs-formula { 517 | opacity: 0.5; 518 | } 519 | -------------------------------------------------------------------------------- /docs/annotated/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/annotated/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/annotated/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/annotated/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/annotated/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/annotated/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/annotated/public/fonts/roboto-black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/roboto-black.eot -------------------------------------------------------------------------------- /docs/annotated/public/fonts/roboto-black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/roboto-black.ttf -------------------------------------------------------------------------------- /docs/annotated/public/fonts/roboto-black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/annotated/public/fonts/roboto-black.woff -------------------------------------------------------------------------------- /docs/annotated/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /docs/demo-kb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/demo-kb.jpg -------------------------------------------------------------------------------- /docs/demo-pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengz/telegram-bot-bootstrap/c99281393a1fa3d7c0f81df55634bb956072d20a/docs/demo-pic.jpg -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Telegram Bot Bootstrap 9 | 10 | 11 | 12 | 13 | 14 |
15 |

Telegram Bot Bootstrap

16 | 22 | 23 |
24 |

Bot

25 | 28 |
29 |
30 |

Telegram API

31 | 47 |
48 |
49 |

Methods

50 |
51 |
52 |

Properties

53 |
54 | 55 |
56 |
57 |
58 | 59 |

npm version Build Status Dependency Status

60 |

A bootstrap for Telegram bot with directly deployable sample bot and JS-wrapped API methods. You can use all methods available in the Telegram API directly, and send any supported media (we serialize the formData for you to send over HTTP).

61 |

See the full API documentation of this project.

62 |

63 |

Installation

64 |

Do either of

65 |
npm install telegram-bot-bootstrap
 66 | git clone https://github.com/kengz/telegram-bot-bootstrap.git
 67 | 

Either way you’ll get a module with the Telegram bot API wrapped in Node, and a bootstrapped, deploy-ready project.

68 |

If you haven’t already, get a bot from BotFather and remember your bot token!

69 |

Features

70 |
    71 |
  • Wrapped API methods take either a JSON object or multiple parameters.
  • 72 |
  • Auto serialization for HTTP formData: send photos/keyboards/media directly.
  • 73 |
  • API methods return promises (uses q) for easy chaining and flow control.
  • 74 |
  • Complete documentation and examples usages.
  • 75 |
  • Bootstrapped and directly deployable bot.
  • 76 |
77 |

Usage: only the API

78 |

See the full API documentation of this project.

79 |

API.js contains the Telegram Bot API wrapped in Node. The methods will return a promise for easy chaining, and will take either a whole JSON, or multiple parameters for convenience. For the latter, everything will be auto-serialized for HTTP so you don’t have to deal with any of the nasty HTTP protocol stuff.

80 |

If you wish to use just the API or test the bot methods, here’s an example

81 |

Local(not deployed yet) test bot constructor

82 |

See testbot.js for functional example.

83 |
// testbot.js
 84 | var bot = require('telegram-bot-bootstrap');
 85 | var fs = require('fs');
 86 | 
 87 | var Alice = new bot(your-token);
 88 | 
 89 | Alice.getUpdates().then(console.log)
 90 | // → you'll see an update message. Look for your user_id in "message.from.id"
 91 | 
 92 | // Once you get your id to message yourself, you may:
 93 | Alice.sendMessage(your-id, "Hello there")
 94 | // → you'll receive a message from Alice.
 95 | .then(console.log)
 96 | // → optional, will log the successful message sent over HTTP
 97 | 
98 |

Sending Message, Photo and all media

99 |
Alice.sendMessage(86953862, 'Hey wanna see some cool art?');
100 | 
101 | Alice.sendPhoto(86953862, fs.createReadStream(__dirname+'/alexiuss.jpg'), 'Chronoscape by Alexiuss').then(console.log)
102 | 

You’ll receive this:

103 |

104 |

Custom keyboard

105 |
var kb = {
106 |         keyboard: [
107 |             ['one'],
108 |             ['two', 'three'],
109 |             ['four', 'five', 'six']
110 |         ],
111 |         one_time_keyboard: true
112 |     };
113 | Alice.sendMessage(86953862, "Choose a lucky number", undefined, undefined, kb)
114 | 

You’ll get this:

115 |

116 |

Usage: Bootstrapped, Deployable Bot

117 |

See index.js for deployable app, and bot.js to customize bot commands.

118 |

We distinguish the bot from the API: bot.js extends API.js, and will be the deployed component.

119 |

This whole project is bootstrapped and deploy-ready: all the details of HTTP and server stuff taken care of for you. I deploy this git project onto my Heroku and voila, my bot is alive.

120 |

Setup

121 |

In addition to the token, you’ll need a webhookUrl. If you deploy your Node app to Heroku, then the webhookUrl is simply your Heroku site url. Set both of them in the .env file:

122 |
PORT=8443
123 | TOKEN=your-Telegram-bot-token
124 | WEBHOOK=your-webhook-url
125 | 
126 |

The sample available is an echo-bot. To make your bot do interesting stuff, head over to bot.js, under the handle method, start writing your own from below the Extend from here comment.

127 |

The bot inherits all the API methods, so you can simply call them for example by this.sendMessage.

128 |

Deployment

129 |

The server is deployed in index.js, and a bot is constructed to handle all HTTP POST calls from Telegram.

130 |

I use Heroku. This shall work for any other services too. Once I’m done setting up, I do:

131 |
git push heroku master
132 | 

And done. Start talking to the bot.

133 | 134 |

API documentation

135 | 136 |
137 |

“Bot” Methods

138 |
139 |

handle(req, res)

140 |

#

141 |

Handles a Telegram Update object sent from the server. Extend this method for your bot.

142 |

Arguments

143 |
    144 |
  1. req (Object): The incoming HTTP request.
  2. 145 |
  3. res (Object): The HTTP response in return.
  4. 146 |
147 |

Returns

148 |

(Promise): promise A promise returned from calling Telegram API method(s) for chaining.

149 |

Example

150 |
var bot1 = new bot('yourtokenhere');
151 | ...express server setup
152 | app.route('/')
153 | // robot API as middleware
154 | .post(function(req, res) {
155 |     bot1.handle(req, res)
156 | })
157 | // Then bot will handle the incoming Update from you, routed from Telegram!
158 | 
159 |
160 |
161 |
162 |
163 |

“Telegram API” Methods

164 |
165 |

API(token)

166 |

#

167 |

The API bot constructor.

168 |

Arguments

169 |
    170 |
  1. token (string): Your Telegram bot token.
  2. 171 |
172 |

Returns

173 |

(Bot): The bot able to call methods.

174 |

Example

175 |
var fs = require('fs');
176 | var bot = require('telegram-bot-bootstrap');
177 | var Alice = new bot(your_bot_token);
178 | 
179 | Alice.sendMessage(user_chat_id, 'Hey wanna see some cool art?');
180 | 
181 | Alice.sendPhoto(user_chat_id, fs.createReadStream(__dirname+'/alexiuss.jpg'),  * 'Chronoscape by Alexiuss').then(console.log)
182 | 
183 | var kb = {
184 |         keyboard: [
185 |             ['one'],
186 |             ['two', 'three'],
187 |             ['four', 'five', 'six']
188 |         ],
189 |         one_time_keyboard: true
190 |     };
191 | Alice.sendMessage(user_chat_id, "Choose a lucky number", undefined, undefined,  * kb)
192 | 
193 | // → The messages and photos are sent to user.
194 | 
195 |
196 |
197 |
198 |

forwardMessage(first, from_chat_id, message_id)

199 |

#

200 |

Use this method to forward messages of any kind.

201 |

Arguments

202 |
    203 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 204 |
  3. from_chat_id (integer): Unique identifier for the chat where the original message was sent — User or GroupChat id.
  4. 205 |
  5. message_id (integer): Unique message identifier
  6. 206 |
207 |

Returns

208 |

(string): HTTPres On success, the sent Message is returned.

209 |

Example

210 |
Alice.forwardMessage(87654321, 12345678, 87654356)
211 | // → Message is forwarded
212 | 
213 |
214 |
215 |
216 |

getMe()

217 |

#

218 |

A simple method for testing your bot’s auth token. Requires no parameters.

219 |

Returns

220 |

(string): HTTPres Basic information about the bot in form of a User object.

221 |
222 |
223 |
224 |

getUpdates([first|offset], [limit], [timeout])

225 |

#

226 |

Use this method to receive incoming updates using long polling (wiki).

227 |

Arguments

228 |
    229 |
  1. [first|offset] (JSON|integer): An optional JSON object with the parameters, or the offset integer
  2. 230 |
  3. [limit] (integer):
  4. 231 |
  5. [timeout] (integer):
  6. 232 |
233 |

Returns

234 |

(string): HTTPres An Array of Update objects is returned.

235 |

Example

236 |
Alice.getUpdates().then(console.log)
237 | // → {"ok":true,"result":[{"update_id":1234567, "message":{"message_id":1,"from":{"id":87654321, ...}}]
238 | 
239 |
240 |
241 |
242 |

getUserProfilePhotos(first, [offset], [limit])

243 |

#

244 |

Use this method to get a list of profile pictures for a user.

245 |

Arguments

246 |
    247 |
  1. first (JSON|integer): Your own JSON object, or user_id: Unique identifier of the target user.
  2. 248 |
  3. [offset] (integer): Sequential number of the first photo to be returned. By default, all photos are returned.
  4. 249 |
  5. [limit] (integer): Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100.
  6. 250 |
251 |

Returns

252 |

(string): HTTPres Returns a UserProfilePhotos object.

253 |
254 |
255 |
256 |

sendAudio(first, audio, [reply_to_message_id], [reply_markup])

257 |

#

258 |

Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .ogg file encoded with OPUS (other formats may be sent as Document). On success, the sent Message is returned. Bots can currently send audio files of up to 50 MB in size, this limit may be changed in the future.

259 |

Arguments

260 |
    261 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 262 |
  3. audio (inputFile|string): Audio file to send. You can either pass a file_id as String to resend an audio that is already on the Telegram servers, or upload a new audio file using multipart/form-data.
  4. 263 |
  5. [reply_to_message_id] (integer): If the message is a reply, ID of the original message.
  6. 264 |
  7. [reply_markup] (KeyboardMarkup): Additional interface options. A JSON object (don’t worry about serializing; it’s handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.
  8. 265 |
266 |

Returns

267 |

(string): HTTPres On success, the sent Message is returned.

268 |
269 |
270 |
271 |

sendChatAction(first, action)

272 |

#

273 |

Use this method when you need to tell the user that something is happening on the bot’s side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status).

274 |

Arguments

275 |
    276 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id..
  2. 277 |
  3. action (string): Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for text messages, upload_photo for photos, record_video or upload_video for videos, record_audio or upload_audio for audio files, upload_document for general files, find_location for location data.
  4. 278 |
279 |

Returns

280 |

(string): HTTPres On success, the sent Message is returned.

281 |
282 |
283 |
284 |

sendDocument(first, document, [reply_to_message_id], [reply_markup])

285 |

#

286 |

Use this method to send general files. On success, the sent Message is returned. Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future.

287 |

Arguments

288 |
    289 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 290 |
  3. document (inputFile|string): File to send. You can either pass a file_id as String to resend a file that is already on the Telegram servers, or upload a new file using multipart/form-data.
  4. 291 |
  5. [reply_to_message_id] (integer): If the message is a reply, ID of the original message.
  6. 292 |
  7. [reply_markup] (KeyboardMarkup): Additional interface options. A JSON object (don’t worry about serializing; it’s handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.
  8. 293 |
294 |

Returns

295 |

(string): HTTPres On success, the sent Message is returned.

296 |
297 |
298 |
299 |

sendLocation(first, latitude, longitude, [reply_to_message_id], [reply_markup])

300 |

#

301 |

Use this method to send point on the map.

302 |

Arguments

303 |
    304 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 305 |
  3. latitude (number): Latitude of location
  4. 306 |
  5. longitude (number): Longitude of location
  6. 307 |
  7. [reply_to_message_id] (integer): If the message is a reply, ID of the original message.
  8. 308 |
  9. [reply_markup] (KeyboardMarkup): Additional interface options. A JSON object (don’t worry about serializing; it’s handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.
  10. 309 |
310 |

Returns

311 |

(string): HTTPres On success, the sent Message is returned.

312 |
313 |
314 |
315 |

sendMessage(first, text, [disable_web_page_preview], [reply_to_message_id], [reply_markup])

316 |

#

317 |

Use this method to send text messages.

318 |

Arguments

319 |
    320 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 321 |
  3. text (string): Text of the message to be sent.
  4. 322 |
  5. [disable_web_page_preview] (boolean): Disables link previews for links in this message.
  6. 323 |
  7. [reply_to_message_id] (integer): If the message is a reply, ID of the original message.
  8. 324 |
  9. [reply_markup] (KeyboardMarkup): Additional interface options. A JSON object (don’t worry about serializing; it’s handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.
  10. 325 |
326 |

Returns

327 |

(string): HTTPres On success, the sent Message is returned.

328 |

Example

329 |
Alice.sendMessage({chat_id: 87654321, text: 'hello world'})
330 | Alice.sendMessage(87654321, 'hello world') // equivalent, cleaner
331 | // → 'hello world' is sent to the user with the id.
332 | 
333 | // var kb = {
334 | //     keyboard: [
335 | //         ['one'],
336 | //         ['two', 'three'],
337 | //         ['four', 'five', 'six']
338 | //     ],
339 | //     one_time_keyboard: true
340 | // };
341 | Alice.sendMessage(87654321, "Choose a lucky number", undefined, undefined, kb)
342 | // → 'Choose a lucky number' is sent, with custom reply keyboard
343 | 
344 |
345 |
346 |
347 |

sendPhoto(first, photo, [caption], [reply_to_message_id], [reply_markup])

348 |

#

349 |

Use this method to send photos.

350 |

Arguments

351 |
    352 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 353 |
  3. photo (inputFile|string): Photo to send. You can either pass a file_id as String to resend a photo that is already on the Telegram servers, or upload a new photo using multipart/form-data.
  4. 354 |
  5. [caption] (string):
  6. 355 |
  7. [reply_to_message_id] (integer): If the message is a reply, ID of the original message.
  8. 356 |
  9. [reply_markup] (KeyboardMarkup): Additional interface options. A JSON object (don’t worry about serializing; it’s handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.
  10. 357 |
358 |

Returns

359 |

(string): HTTPres On success, the sent Message is returned.

360 |

Example

361 |
Alice.sendMessage(87654321, fs.createReadStream('localpath/to/photo.jpg'), 'cool caption')
362 | // → The photo on local system is sent to the id.
363 | 
364 |
365 |
366 |
367 |

sendSticker(first, sticker, [reply_to_message_id], [reply_markup])

368 |

#

369 |

Use this method to send .webp stickers.

370 |

Arguments

371 |
    372 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 373 |
  3. sticker (inputFile|string): Sticker to send. You can either pass a file_id as String to resend a sticker that is already on the Telegram servers, or upload a new sticker using multipart/form-data.
  4. 374 |
  5. [reply_to_message_id] (integer): If the message is a reply, ID of the original message.
  6. 375 |
  7. [reply_markup] (KeyboardMarkup): Additional interface options. A JSON object (don’t worry about serializing; it’s handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.
  8. 376 |
377 |

Returns

378 |

(string): HTTPres On success, the sent Message is returned.

379 |
380 |
381 |
382 |

sendVideo(first, video, [reply_to_message_id], [reply_markup])

383 |

#

384 |

Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). On success, the sent Message is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future.

385 |

Arguments

386 |
    387 |
  1. first (JSON|integer): Your own JSON object, or chat_id: Unique identifier for the message recipient — User or GroupChat id.
  2. 388 |
  3. video (inputFile|string): Video to send. You can either pass a file_id as String to resend a video that is already on the Telegram servers, or upload a new video file using multipart/form-data.
  4. 389 |
  5. [reply_to_message_id] (integer): If the message is a reply, ID of the original message.
  6. 390 |
  7. [reply_markup] (KeyboardMarkup): Additional interface options. A JSON object (don’t worry about serializing; it’s handled) for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.
  8. 391 |
392 |

Returns

393 |

(string): HTTPres On success, the sent Message is returned.

394 |
395 |
396 |
397 |

setWebhook(url)

398 |

#

399 |

Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update. In case of an unsuccessful request, we will give up after a reasonable amount of attempts.

400 |

Arguments

401 |
    402 |
  1. url (string): A JSON object with the parameters, or the HTTPS url to send updates to. Use an empty string to remove webhook integration.
  2. 403 |
404 |

Returns

405 |

(string): HTTPres An Array of Update objects is returned.

406 |
407 |
408 |
409 |
410 |

Methods

411 |
412 |
413 |

Properties

414 |
415 | 416 |
417 |
418 | 419 | 420 | 421 | -------------------------------------------------------------------------------- /docs/styles.css: -------------------------------------------------------------------------------- 1 | body, 2 | h1, 3 | h2, 4 | html, 5 | p { 6 | margin: 0; 7 | padding: 0 8 | } 9 | html { 10 | background: #d3d3d3; 11 | height: 100%; 12 | color: #222; 13 | font-size: 100% 14 | } 15 | body { 16 | background: #fff; 17 | width: 60em; 18 | margin: 0 auto; 19 | -webkit-box-shadow: 0 0 2.5em #5d656c; 20 | -moz-box-shadow: 0 0 2.5em #5d656c; 21 | box-shadow: 0 0 2.5em #5d656c; 22 | font: 1em/1.7'Helvetica Neue', Helvetica, Arial, sans-serif; 23 | min-height: 100% 24 | } 25 | code { 26 | /*font-family: 'Roboto', 'Helvetica Neue', sans-serif;*/ 27 | font-family: 'Source Code Pro', Consolas, 'Courier New', monospace; 28 | letter-spacing: 0.03em; 29 | } 30 | .lang-js { 31 | color: #78dbf9; 32 | } 33 | span.hljs-number { 34 | color: #d59cf6; 35 | } 36 | span.hljs-string{ 37 | color: #8bdb97; 38 | } 39 | span.hljs-regexp{ 40 | color: #dbad83; 41 | } 42 | span.hljs-literal{ 43 | color: #e26a78; 44 | } 45 | span.hljs-keyword{ 46 | color: #d59cf6; 47 | } 48 | span.hljs-comment { 49 | color: #99a4c7; 50 | } 51 | span.hljs-special { 52 | color: #bdc4df; 53 | } 54 | pre { 55 | box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.137255); 56 | box-shadow: 0px 0px 2px 1px rgba(0, 0, 0, 0.0980392); 57 | box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.0823529); 58 | border-bottom-left-radius: 5px; 59 | border-bottom-right-radius: 5px; 60 | background: #2b303b; 61 | margin: 1em 0; 62 | padding: .5em 20px; 63 | overflow-x: scroll; 64 | -webkit-overflow-scrolling: touch 65 | } 66 | pre.intro { 67 | font-size: 1.2em 68 | } 69 | pre::-webkit-scrollbar { 70 | height: 8px 71 | } 72 | pre::-webkit-scrollbar-thumb { 73 | background: rgba(255, 255, 255, .8); 74 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .5); 75 | -webkit-border-radius: 10px; 76 | border-radius: 10px 77 | } 78 | pre::-webkit-scrollbar-thumb:window-inactive { 79 | background: rgba(255, 255, 255, .4) 80 | } 81 | h1 span, 82 | pre { 83 | color: #ddd 84 | } 85 | h1, 86 | h2, 87 | h3, 88 | h4, 89 | ol, 90 | p, 91 | ul { 92 | font-weight: 400; 93 | padding: 0 20px; 94 | word-wrap: break-word 95 | } 96 | hgroup h1 { 97 | display: inline 98 | } 99 | hgroup h2 { 100 | font-size: 1.38em 101 | } 102 | h2, 103 | h3, 104 | h4 { 105 | margin: 0 0 .5em 106 | } 107 | h4 { 108 | font-weight: 700 109 | } 110 | a { 111 | color: #222; 112 | border-bottom: 1px solid #ddd; 113 | text-decoration: none 114 | } 115 | a:focus, 116 | a:hover { 117 | border-color: #222 118 | } 119 | a img { 120 | border: 0 121 | } 122 | abbr[title] { 123 | border-bottom: 1px dotted #ddd; 124 | cursor: help 125 | } 126 | hr { 127 | display: none 128 | } 129 | p { 130 | margin-bottom: 1em 131 | } 132 | ol, 133 | ul { 134 | margin-left: 2em 135 | } 136 | ol ol, 137 | ul ul { 138 | margin-left: 1em; 139 | padding: 0 140 | } 141 | footer { 142 | display: block; 143 | background: #eee; 144 | padding: 1em 0; 145 | text-align: center 146 | } 147 | .a-img { 148 | border: 0 149 | } 150 | .a-img img { 151 | padding: .5em 0 0 152 | } 153 | .description { 154 | word-wrap: break-word 155 | } 156 | .multiline-items li { 157 | padding-bottom: 1em 158 | } 159 | .multiline-items li.last-item { 160 | padding-bottom: 0 161 | } 162 | .br0 { 163 | color: #090 164 | } 165 | .co1 { 166 | color: #060 167 | } 168 | .co2 { 169 | color: #096 170 | } 171 | .kw1, 172 | .kw3 { 173 | color: #006 174 | } 175 | .kw2 { 176 | color: #036 177 | } 178 | .me1 { 179 | color: #606 180 | } 181 | .nu0 { 182 | color: #c00 183 | } 184 | .st0 { 185 | color: #36c 186 | } 187 | .sy0 { 188 | color: #393; 189 | font-weight: 700 190 | } 191 | pre .co1 { 192 | color: #aeaeae 193 | } 194 | .es0, 195 | pre .st0 { 196 | color: #61ce3c 197 | } 198 | pre .co2 { 199 | color: #fff 200 | } 201 | pre .kw2, 202 | pre .kw3, 203 | pre .nu0 { 204 | color: #fbde2d 205 | } 206 | pre .me1 { 207 | color: #8da6ce 208 | } 209 | pre .br0, 210 | pre .kw1, 211 | pre .sy0 { 212 | color: #ddd 213 | } 214 | #docs { 215 | background: #fff 216 | } 217 | #docs body { 218 | font-size: .85em; 219 | width: 100% 220 | } 221 | #docs a.alias { 222 | opacity: .5 223 | } 224 | #docs div div div div { 225 | box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.137255); 226 | box-shadow: 0px 0px 2px 1px rgba(0, 0, 0, 0.0980392); 227 | box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.0823529); 228 | border-bottom-left-radius: 5px; 229 | border-bottom-right-radius: 5px; 230 | /*padding-bottom: 0.5em;*/ 231 | } 232 | #docs div div div { 233 | border-top: 0; 234 | margin: 0 .5em 1em 235 | } 236 | #docs h1, 237 | #docs h2, 238 | #docs h3, 239 | #docs h4, 240 | #docs ol, 241 | #docs p, 242 | #docs ul { 243 | padding: 0 10px 244 | } 245 | #docs pre { 246 | margin: 0; 247 | padding: .5em 10px 248 | } 249 | #docs ul li { 250 | list-style-type: none; 251 | margin: 0 0 0 -.9em 252 | } 253 | #docs h1 { 254 | padding: 0 10px 255 | } 256 | #docs h2 { 257 | margin: .5em 0 0 258 | } 259 | #docs h3 { 260 | background: rgb(63, 107, 196); 261 | border-radius: 3px 3px 0 0; 262 | border: 1px solid rgba(75, 71, 71, 0.14); 263 | margin: .5em 0; 264 | padding: .8em 0 .8em 10px; 265 | position: relative 266 | } 267 | #docs h3>code { 268 | color: #fbfbfb 269 | } 270 | #docs h3 a { 271 | position: absolute; 272 | top: 0 273 | } 274 | #docs h3 .br0 { 275 | color: #fc83ff 276 | } 277 | #docs h3 .kw2 { 278 | color: #71d0c9 279 | } 280 | #docs h3 .me1 { 281 | color: #fff 282 | } 283 | #docs h3 .nu0 { 284 | color: #d0cb71 285 | } 286 | #docs h3 .sy0 { 287 | color: #df74e2; 288 | font-weight: 700 289 | } 290 | #docs footer { 291 | height: 3em; 292 | margin-top: 1.5em; 293 | width: 100% 294 | } 295 | #social { 296 | height: 20px 297 | } 298 | #social .twitter-follow-button { 299 | width: 127px!important 300 | } 301 | #social .twitter-follow-button, 302 | .twitter-share-button { 303 | font-size: .8em; 304 | vertical-align: top 305 | } 306 | @media (max-width: 959px) { 307 | body { 308 | border: 0; 309 | margin: 0; 310 | -moz-box-shadow: none; 311 | -webkit-box-shadow: none; 312 | box-shadow: none; 313 | width: auto 314 | } 315 | h1, 316 | h2, 317 | h3, 318 | h4, 319 | ol, 320 | p, 321 | ul { 322 | padding: 0 10px 323 | } 324 | pre { 325 | padding: 5px 10px!important 326 | } 327 | #social { 328 | height: auto 329 | } 330 | .toc-container a { 331 | display: block; 332 | padding: 5px 333 | } 334 | .toc-container a:focus, 335 | .toc-container a:hover { 336 | background-color: #EEE 337 | } 338 | .toc-container a:active { 339 | background-color: #CCC 340 | } 341 | } 342 | @media (min-width: 960px) { 343 | #docs body { 344 | box-shadow: none; 345 | height: 100%; 346 | margin: 0 347 | } 348 | #docs a[href="#docs"], 349 | #docs footer { 350 | display: none 351 | } 352 | #docs h1 { 353 | position: fixed; 354 | background-color: #fff; 355 | top: 0; 356 | left: 0; 357 | right: 0; 358 | z-index: 1 359 | } 360 | #docs h3 a { 361 | display: block; 362 | position: relative; 363 | visibility: hidden; 364 | top: -4em 365 | } 366 | #docs .toc-container { 367 | background: #fff; 368 | bottom: 0; 369 | left: 0; 370 | overflow-y: scroll; 371 | overflow-x: hidden; 372 | position: fixed; 373 | top: 1.5em; 374 | white-space: nowrap; 375 | width: 20%; 376 | -webkit-overflow-scrolling: touch 377 | } 378 | #docs .toc-container h2, 379 | #docs .toc-container ul { 380 | margin-top: 0; 381 | padding: 0 10px 382 | } 383 | #docs .doc-container { 384 | background: #fff; 385 | width: 80%; 386 | margin-left: 20%; 387 | padding-top: 1.5em 388 | } 389 | #docs .doc-container .first-heading { 390 | margin-top: 0 391 | } 392 | } 393 | @media (-ms-high-contrast: active) and (max-width: 1280px), 394 | (-ms-high-contrast: none) and (max-width: 1280px) { 395 | #docs .doc-container, 396 | #docs .toc-container { 397 | position: relative; 398 | top: auto; 399 | width: 100% 400 | } 401 | #docs .doc-container .first-heading { 402 | margin-top: .5em 403 | } 404 | #docs a[href="#docs"] { 405 | display: inline 406 | } 407 | #docs footer { 408 | display: block 409 | } 410 | } 411 | @media (orientation: portrait) and (min-device-width: 720px) and (max-device-width: 768px), 412 | (orientation: landscape) and (device-width: 1280px) and (max-device-height: 768px) { 413 | @-ms-viewport{width:80%}} 414 | -------------------------------------------------------------------------------- /docs/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dokker.js 7 | 8 | 19 | 38 | 39 | 40 |
41 |

Parameters setting

42 |
43 |
44 |

token

45 |
46 |
shall be in this.token
47 |
Alice.token.should.equal(123456);
48 |
49 |
50 |
51 |
52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /herokucmds.txt: -------------------------------------------------------------------------------- 1 | Useful Heroku Commands: 2 | 3 | heroku create appname 4 | git push heroku master 5 | heroku ps:scale web=1 6 | heroku open 7 | npm init / install —save 8 | foreman start [web] 9 | git add . / commit etc. 10 | git push heroku master 11 | heroku open 12 | heroku addons:create 13 | heroku run node / bash run one-off Dynos(ephemeral) 14 | heroku config:set TIMES=2 (config vars in process.env) 15 | heroku releases (like Git commits) 16 | heroku logs [—ps web.1] [—tail] 17 | 18 | Dynos are ephemeral and isolated: always created from slug, and all changes is lost after it closes. For shared resource and communication, use addons 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////// 2 | // Safety: Uncomment everything to use // 3 | ///////////////////////////////////////// 4 | 5 | // // dependencies 6 | // // express 7 | // var _ = require('lomath'); 8 | // var express = require('express'); 9 | // var app = express(); 10 | // // express middlewares 11 | // var morgan = require('morgan'); 12 | // var ejs = require('ejs'); 13 | // var bodyParser = require('body-parser'); 14 | // var multer = require('multer'); 15 | 16 | // // telegram bot 17 | // var bot = require(__dirname + '/bot.js'); 18 | // var token = 'your example token'; 19 | // var webhookUrl = 'your webhook url' 20 | // var bot1 = new bot(process.env.TOKEN || token, process.env.WEBHOOK || webhookUrl); 21 | 22 | 23 | // // engine to render HTML 24 | // app.engine('.html', ejs.__express); 25 | // app.set('view engine', 'html'); 26 | // // set the port number 27 | // app.set('port', process.env.PORT || 8443); 28 | 29 | // // Mount middlewares defaulted for root: 30 | // // log all HTTP calls for debugging 31 | // // app.use(morgan('combined')); 32 | // // use resources for html: HTML, JS and CSS etc. 33 | // app.use(express.static(__dirname + '/views')); 34 | // // parse incoming formData into JSON 35 | // app.use(bodyParser.json()); 36 | // app.use(multer()); 37 | 38 | 39 | // // route: concise way to group all HTTP methods for a path 40 | // app.route('/') 41 | // .get(function(req, res) { 42 | // // console.log("you GET") 43 | // res.render('index') 44 | // }) 45 | // .post(function(req, res) { 46 | // // send back to end req-res cycle 47 | // res.json('okay, received\n'); 48 | // // robot handle as middleware for POST 49 | // bot1.handle(req, res) 50 | // }) 51 | // .put(function(req, res) { 52 | // res.send("you just called PUT\n") 53 | // }) 54 | 55 | 56 | // // finally, listen to the specific port for any calls 57 | // app.listen(app.get('port'), function() { 58 | // console.log('Node app is running on port', app.get('port')); 59 | // }); 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telegram-bot-bootstrap", 3 | "version": "0.0.15", 4 | "description": "A bootstrap for Telegram bot with directly deployable sample bot and JS-wrapped API methods.", 5 | "main": "API.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "./node_modules/.bin/_mocha -u tdd -R spec" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/kengz/telegram-bot-bootstrap.git" 13 | }, 14 | "keywords": [ 15 | "Telegram", 16 | "bot", 17 | "Telegram bot", 18 | "nodeJS", 19 | "API", 20 | "bootstrap", 21 | "template", 22 | "sample", 23 | "Heroku" 24 | ], 25 | "author": "kengz", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/kengz/telegram-bot-bootstrap/issues" 29 | }, 30 | "homepage": "https://github.com/kengz/telegram-bot-bootstrap", 31 | "dependencies": { 32 | "body-parser": "^1.13.2", 33 | "ejs": "^2.3.1", 34 | "express": "^4.13.1", 35 | "lomath": "^0.1.6", 36 | "morgan": "^1.6.1", 37 | "multer": "^0.1.8", 38 | "q": "^1.4.1", 39 | "reqscraper": "0.0.2", 40 | "request": "^2.58.0" 41 | }, 42 | "devDependencies": { 43 | "chai": "^3.0.0", 44 | "dokker": "^0.1.1", 45 | "mocha": "^2.2.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/node.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // chai assertation library 2 | var chai = require('chai'), 3 | should = chai.should() 4 | 5 | var bot = require(__dirname + '/../API.js'); 6 | 7 | //============================================== 8 | suite('Parameters setting', function() { 9 | suite('token', function() { 10 | var Alice = new bot(123456); 11 | test('shall be in this.token', function() { 12 | Alice.token.should.equal(123456); 13 | }) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /testbot.js: -------------------------------------------------------------------------------- 1 | // dependencies 2 | var _ = require('lomath') 3 | var scraper = require('reqscraper'); 4 | var req = scraper.req; 5 | 6 | // Test bot 7 | var fs = require('fs'); 8 | // var bot = require(__dirname+'/API.js'); 9 | 10 | // var Alice = new bot('your-bot-token'); 11 | 12 | // get your chat_id from here 13 | // Alice.getUpdates().then(console.log) 14 | 15 | // try sending a message, and log the HTTP call for confirmation 16 | // Alice.sendMessage('your-chat-id', 'Hey wanna see some cool art?').then(console.log); 17 | 18 | // Alice.sendPhoto('your-chat-id', fs.createReadStream(__dirname+'/alexiuss.jpg'), 'Chronoscape by Alexiuss').then(console.log) 19 | 20 | 21 | // var kb = { 22 | // keyboard: [ 23 | // ['one'], 24 | // ['two', 'three'], 25 | // ['four', 'five', 'six'] 26 | // ], 27 | // one_time_keyboard: true 28 | // }; 29 | // Alice.sendMessage('your-chat-id', "Choose a lucky number", undefined, undefined, kb) 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | // A test bot (req) to send sample message to the main bot 38 | var testopt = { 39 | method: 'POST', 40 | url: 'http://localhost:8443', 41 | formData: _.flattenJSON({ 42 | "update_id": 734575200, 43 | "message": { 44 | "message_id": 14, 45 | "from": { 46 | "id": 86953862, 47 | "first_name": "your name", 48 | "last_name": "your lastname", 49 | "username": "your username", 50 | }, 51 | "chat": { 52 | "id": 86953862, 53 | "first_name": "your name", 54 | "last_name": "your lastname", 55 | "username": "your username", 56 | }, 57 | 58 | "date": 1435524670, 59 | "text": "\/td aapl,goog" 60 | } 61 | }) 62 | } 63 | 64 | // send test call 65 | // req(testopt).then(console.log) -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Bootstrap 101 Template 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |

Hello, world!

23 |

This is a deployment test using Angular and Bootstrap. You may create your bot interface here if you wish.

24 |
25 |
26 |
27 |
28 |
29 |

Content below

30 |

{{dispCtrl.msg}}

31 |

Click to render

32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /views/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /views/js/ui.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var app = angular.module('test', []); 4 | app.controller('DisplayController', function() { 5 | this.data = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; 6 | this.msg = "mehehe"; 7 | this.flip = false; 8 | this.render = function() { 9 | this.flip = !this.flip; 10 | this.msg = this.flip ? this.data : "mehehe"; 11 | }; 12 | 13 | }) 14 | 15 | })() 16 | --------------------------------------------------------------------------------