This document will walk you through creating your first Telegram bot with Botgram. Before we begin, you should install Telegram if you don't have it already, talk to the BotFather and register your first bot.
It takes less than a minute, and you will be given an auth token, which looks like this:
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
You'll need this auth token to be able to run the snippets and examples.
To use Botgram, you first create a Bot object:
var bot = botgram("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11");
Then, to start receiving Telegram messages, register some handlers:
bot.command("start", function (msg, reply, next) {
console.log("Received a /start command from", msg.from.username);
});
bot.text(function (msg, reply, next) {
console.log("Received a text message:", msg.text);
});
Handlers are functions that react to some kind of message.
In the above example, we've registered three handlers: one for
start
commands and another for texts.
When the bot receives a message, it calls the first handler that
matches it, passing it the message as the first parameter msg
.
If no handlers match, the message will be silently ignored.
Try running that code (remember to put your actual auth token in it) and talk to your bot!
Printing to the console is okay, but not very interesting. Let's
actually reply to the user when they send us a text, using the
reply
object:
bot.text(function (msg, reply, next) {
reply.text("hello!");
});
In Telegram, messages aren't limited to just texts: they can be stickers, media, attached files, locations, contacts... Botgram allows you to recieve them just fine:
bot.contact(function (msg, reply, next) {
console.log("User %s sent us a contact:", msg.from.firstname);
console.log(" * Phone: %s", msg.phone);
console.log(" * Name: %s %s", msg.firstname, msg.lastname);
reply.text("Ok, got that contact.");
});
bot.video(function (msg, reply, next) {
reply.text("That's a " + msg.width + "x" + msg.height + " video.");
});
bot.location(function (msg, reply, next) {
reply.text("You seem to be at " + msg.latitude + ", " + msg.longitude);
});
Curious about the attributes of msg
? Look here for
a complete reference of them, for every type of message.
Curious about the different kinds of handlers you can register? Look here for a complete reference of them.
For photo messages, videos, files and other media, msg
doesn't contain the
actual binary contents, only a file
object with some kind of ID.
You have to call bot.fileGet
like this:
bot.voice(function (msg, reply, next) {
bot.fileGet(msg.file, function (err, info) {
if (err) throw err;
console.log("We got the link:", bot.fileLink(info));
});
});
Now, whenever someone sends a voice note to the bot, it'll print something like:
We got the link: https://api.telegram.org/file/bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11/document/file_0.opus
This link lets you download the actual audio file.
You can do it all in one step with bot.fileLoad
:
bot.voice(function (msg, reply, next) {
bot.fileLoad(msg.file, function (err, buffer) {
if (err) throw err;
console.log("Downloaded! Writing to disk...");
require("fs").writeFile("voice.ogg", buffer);
});
});
This example will write the binary contents (stored at buffer
)
into a file voice.ogg
.
This is just a silly example. While using bot.fileLoad
is
convenient sometimes, it's fairly limited (you can't get updates
on the download progress, for instance) and it stores the whole
file into memory, which is bad practice.
So most times it's better to either download
the file yourself, or use fileStream
.
See the hasher.js
example.
Just like you can receive them, you can send them. Here's a quick example:
bot.command("whereareyou", function (msg, reply, next) {
reply.text("I'm at:");
reply.location(38.8976763, -77.0387185);
});
bot.photo(function (msg, reply, next) {
reply.markdown("Here's some _good_ sticker:");
reply.sticker("BQADAgAD3gAD9HsZAAFphGBFqImfGAI");
});
Sending more interesting messages, such as photos or videos, requires uploading a file to Telegram. This can be done by simply passing a readable stream, for instance from a file:
bot.command("send_drawing", function (msg, reply, next) {
var stream = fs.createReadStream("./drawing.jpg");
reply.action("upload_photo");
reply.photo(stream, "My drawing");
});
The photo (and any message coming after it) won't be actually sent until the file has been fully uploaded. Because that can take a noticeable amount of time, we first sent a chat action so people will see "Bot is uploading a photo..." on the status bars until a message arrives.
Curious about reply
methods? Look here for a
complete reference of them, and their parameters.
Unless you're generating and sending lots of different photos, chances are you'll want to send a photo multiple times. Good news! Once a file has been uploaded, Telegram lets you reuse it by simply grabbing its assigned ID and passing it instead of a stream.
To do this, upload the photo (as always) and look at the sent message:
var stream = fs.createReadStream("./drawing.jpg");
reply.photo(stream, "My drawing").then(function (err, sentMessage) {
// sentMessage is a Message object with a file property, just like other photo messages
console.log("The ID is:", sentMessage.file.id);
});
This will print out something like:
The ID is: AgADBAADrKgxG3NfmFB617NPKX8uoffgaBkABJGRkXADQIYIAaIEAAEC
So from now on, whenever your bot wants to send that file, it can just do:
reply.photo("AgADBAADrKgxG3NfmFB617NPKX8uoffgaBkABJGRkXADQIYIAaIEAAEC", "Some lil' drawing");
Hardcoding is okay―but nothing stops you from storing those IDs in a database, for instance. And all this applies to every kind of files, but mixing contexts is not allowed:
// This will fail, since we uploaded the file as a photo
reply.document("AgADBAADrKgxG3NfmFB617NPKX8uoffgaBkABJGRkXADQIYIAaIEAAEC");
Every way to send files has some limitations, which you should be aware of. For more information on uploading and sending files, look here.
Up until now we never used the third argument passed to the handlers,
which we usually call next
. It's a function that handlers can call to
"pass" the message to the next matching handler.
This can be used for all sorts of good effects. Suppose we have a bot with some useful commands:
bot.command("time", function (msg, reply, next) {
reply.text("The current time is: " + Date());
});
bot.command("quit", function (msg, reply, next) {
reply.text("Shutting down the bot.");
process.exit(0);
});
bot.command("pwd", function (msg, reply, next) {
reply.text("Bot is running from: " + require("path").resolve(__dirname));
});
bot.command("eval", function (msg, reply, next) {
var code = msg.args();
try {
reply.text("Result: " + eval(code).toString());
} catch (e) {
reply.text(e.toString());
}
});
This works, but we'd like to restrict the quit
and eval
commands
to certain users only. A good way to do it is to register a special handler
at the start:
bot.all(function (msg, reply, next) {
if (msg.from.id === 5981248 || msg.from.id === 9824830)
msg.hasPrivileges = true;
next();
});
Now, all incoming messages will be checked to see if they come from a
special user. If they do, the hasPrivileges
property will be set.
The message will continue processing as before, but subsequent handlers
can now look at msg.hasPrivileges
and react accordingly:
bot.all(function (msg, reply, next) {
if (msg.from.id === 5981248 || msg.from.id === 9824830)
msg.hasPrivileges = true;
next();
});
bot.command("time", function (msg, reply, next) {
reply.text("The current time is: " + Date());
});
bot.command("quit", function (msg, reply, next) {
if (!msg.hasPrivileges) {
reply.text("Only some users can quit the bot.");
return;
}
reply.text("Shutting down the bot.");
process.exit(0);
});
bot.command("pwd", function (msg, reply, next) {
reply.text("Bot is running from: " + require("path").resolve(__dirname));
});
bot.command("eval", function (msg, reply, next) {
if (!msg.hasPrivileges) {
reply.text("Did you SERIOUSLY thought I was going to evaluate code from strangers?");
return;
}
var code = msg.args();
try {
reply.text("Result: " + eval(code).toString());
} catch (e) {
reply.text(e.toString());
}
});
Suppose that we'd also like to filter out messages that were sent
while the bot was offline (i.e. messages where msg.queued
is set).
Botgram doesn't have an option to do that, but it's easy to do with
a handler at the start:
bot.all(function (msg, reply, next) {
if (!msg.queued) next();
});
Here's a particularily useful handler to put at the start of your bots:
var contexts = {};
bot.all(function (msg, reply, next) {
if (!contexts[msg.chat.id])
contexts[msg.chat.id] = {};
msg.context = contexts[msg.chat.id];
next();
});
It assigns a single object to every chat, and puts it under msg.context
.
Further handlers can use this shared object to save preferences, temporary
state (like the command in progress) or more. Example:
bot.command("press", (msg, reply, next) => {
msg.context.pressed = true;
});
bot.command("info", (msg, reply, next) => {
if (msg.context.pressed)
reply.text("The button has been pressed at least once in this chat.");
else
reply.text("Button has never been pressed...");
});
In fact, this kind of handler is so useful when prototyping that it's
built-in into the library, as the context
method. So, you can replace the
all
handler above with just:
bot.context();
But starting out with an empty object when we hear from a chat for the first
time is boring. You can pass an object to context
and it'll be used as the
starting value. For instance, here's an improved version of the bot:
bot.context({ presses: 0 });
bot.command("press", (msg, reply, next) => {
msg.context.presses++;
reply.text("Button has been pressed.");
});
bot.command("count", (msg, reply, next) => {
reply.text("The button has been pressed " + msg.context.presses + " times in this chat.");
});
Look at the hasher
example for a good example, or
here for more information about fallthrough
and context in Botgram.
Return to the documentation.