English (UK)

Csharp Telegram bot tutorial step by step

This post is made to teach you how to create and set up a simple fully functional Telegram bot written in pure C#.
We have already written a Telegram bot from scratch in PHP language here.
Now let's try to do the same in C#.
There are many good ready to use SDKs written in C#.
This is only a simple tutorial that explains how to interact with the Telegram APIs without use of any SDK.

Bots are third-party applications that run inside Telegram. Users can interact with bots by sending them messages, commands and inline requests.
At the core, Telegram Bots are special accounts that do not require an additional phone number to set up.
Messages, commands and requests sent by users are passed to the software running on your servers.
Telegram intermediary server handles all encryption and communication with the Telegram API for you. You communicate with this server via a simple HTTPS-interface that offers a simplified version of the Telegram API.
Telegram Bot API is an HTTP-based interface created for developers keen on building bots for Telegram.

How do I create a bot? There's a... bot for that.
BotFather is the one bot to rule them all. It will help you create new bots and change settings for existing ones.
Message @BotFather with the /newbot command to create a new bot. The BotFather will ask you for a name and username, then generate an authorization token for your new bot.
If you don't know how to message by username, click the search field on your Telegram app and type @BotFather, where you should be able to initiate a conversation. Be careful not to send it to the wrong contact, because some users have similar usernames to BotFather.
The name of your bot is displayed in contact details and elsewhere.

The Username is a short name, to be used in mentions and telegram.me links. Usernames are 5-32 characters long and are case insensitive, but may only include Latin characters, numbers, and underscores. Your bot's username must end in ‘bot’, e.g. ‘tetris_bot’ or ‘TetrisBot’.

The token is a string like this 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw that is required to authorize the bot and send requests to the Bot API. Keep your token secure and store it safely, it can be used by anyone to control your bot.
If your existing token is compromised or you lost it for some reason, use the /token command to generate a new one.

In order to make your Bot answering to requests from your Telegram users you can register a WebHook to automatically being called once updates are available.
The quickest and easiest way to set a WebHook for your Bot is to issue a GET request to the Bot API.
All you have to do is to call the setWebHook method in the Bot API via the following url write in your browser


https://api.telegram.org/bot{my_bot_token}/setWebhook?url={url_to_send_updates_to}
            

where
my_bot_token is the token you got from BotFather when you created your Bot
url_to_send_updates_to is the url of the piece of code you wrote to implement your Bot behavior (in order to set a Webhook you need a server with HTTPS)
For instance


https://api.telegram.org/bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11/setWebhook?url=https://www.example.com/mybot
            

And you’ve got it.
Now if you go to the following url (you have to replace {my_bot_token} with your Bot Token)


https://api.telegram.org/bot{my_bot_token}/getWebhookInfo
            

you should see something like this


{
    "ok":true,
    "result": 
    {
        "url":"https://www.example.com/mybot",
        "has_custom_certificate":false,
        "pending_update_count":0,
        "max_connections":40
     }
}                
            

If you don't have an https domain, you can cyclically query Telegram servers, instead of waiting for notifications, using the "getUpdates" method offered by API.
In this article we find out how it works.

It allows you to query the incoming messages with Telegram chatbots.
If you go to the following url (you have to replace {my_bot_token} with your Bot Token)


https://api.telegram.org/bot{my_bot_token}/getUpdates
            

you should see something like this


{ "ok":true, "result":[
    { "update_id":update_id, "message":{ 
        "message_id":6, "from":{ 
            "id":my_chat_id, "is_bot":false, "first_name":"Lorenzo",
            "username":"my_user_name", "language_code":"it" },
        "chat":{ "id":my_chat_id, "first_name":"Lorenzo",
            "username":"my_user_name", "type":"private" },
        "date":1619087941,
        "text":"Hello!" } }
] }               
            

So let's start with the main code of our project.
Let's prepare the loop that queries the api servers


int update_id = 0;
while (true) {
    try {
        StringReader rdr = new StringReader(TBot.GetUpdates(update_id));
        JsonParser parser = new JsonParser(rdr, true);
        JsonObject obj = parser.ParseObject();
                
        if ((JsonString)obj["ok"].ToString() == "true") {
            JsonArray incomingmsgs = (JsonArray)obj["result"];
                
            if (incomingmsgs.Count != 0) {
                JsonObject msg = (JsonObject)incomingmsgs[0];
                // Dirty filter message type
                try {
                    JsonObject currmsg = (JsonObject)msg["message"];
                    JsonObject chat = (JsonObject)currmsg["chat"];
                    TBot.ElabMsg(chat["id"].ToString(), currmsg["text"].ToString());
                } catch (Exception e) {
                    Console.WriteLine("Incoming type ignored");
                }
                update_id = int.Parse(msg["update_id"].ToString()) + 1;
            }
        } else Console.WriteLine("Error to elab incoming");
        Thread.Sleep(1000);
    } catch (Exception e) {
        Console.WriteLine("Error to call Telegram API");
    }
}                
            

I group atomic functions in a TBot class


static string baseurl = "https://api.telegram.org";

static internal string GetUpdates(int offset) {
    string json = "{}";
    WebRequest request = WebRequest.Create(String.Format(
        "{0}/bot{1}/getUpdates?offset={2}",
        baseurl, Properties.Settings.Default.BotToken, offset));
    request.ContentType = "application/x-www-form-urlencoded ; charset=UTF-8";
    request.Method = "GET";
    using (WebResponse response = request.GetResponse()) {
        using (var reader = new StreamReader(
            response.GetResponseStream(), ASCIIEncoding.ASCII)) {
            json = reader.ReadToEnd();
        }
    }
    return json;
}                
            

Then you can process the incoming message


static internal void ElabMsg(string chatid, string text) {
    if (text == null) return;
    bool ElaboratMsg = true;
                   
    // Check if is command
    if (text.Split(' ')[0].Substring(0, 1) == "/") {
        switch (text.Split(' ')[0].ToLower()) {
            case "/start":
                // ToDo code
                ElaboratMsg = false;
                break;
        }
    } else {
        if (ElaboratMsg) {
            // ToDo code
            ElaboratMsg = false;
        }
    }
}                   
            

Telegram commands are texts starting with a /. We can parse it by this char.
To send a reply message you can use this function


static internal void SendMsg(string chatid, string text) {
    WebRequest request = WebRequest.Create(String.Format(
        "{0}/bot{1}/sendMessage?chat_id={2}&text={3}",
        baseurl, Properties.Settings.Default.BotToken, chatid, text));
    request.ContentType = "application/x-www-form-urlencoded ; charset=UTF-8";
    request.Method = "POST";
    using (WebResponse response = request.GetResponse()) {
        request.Timeout = 10000;
    }
}                   
            

If you want to send an image or a document, you need to stream the file.

When you have sent the file, Telegram returns a file id that you can use if you want to send the same file again without sending the stream.
You need a support function


static internal string InputOnlineFile(HttpWebRequest request, string file,
string type, NameValueCollection formFields = null) {
    string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
               
    request.ContentType = "multipart/form-data; boundary=" + boundary;
    request.Method = "POST";
    request.KeepAlive = true;
               
    Stream memStream = new MemoryStream();
    var boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
    var endBoundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
               
    string formdataTemplate = "\r\n--" + boundary +
        "\r\nContent-Disposition: form-data; name=\"{0}\";\r\n\r\n{1}";
               
    if (formFields != null)
        foreach (string key in formFields.Keys) {
            string formitem = string.Format(formdataTemplate, key, formFields[key]);
            byte[] formitembytes = Encoding.UTF8.GetBytes(formitem);
            memStream.Write(formitembytes, 0, formitembytes.Length);
        }
               
    string headerTemplate =
        "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" +
        "Content-Type: application/octet-stream\r\n\r\n";
               
    memStream.Write(boundarybytes, 0, boundarybytes.Length);
    string header = string.Format(headerTemplate, type, Path.GetFileName(file));
    var headerbytes = Encoding.UTF8.GetBytes(header);
    memStream.Write(headerbytes, 0, headerbytes.Length);
    using (FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) {
        byte[] buffer = new byte[1024];
        int bytesRead = 0;
        while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
        memStream.Write(buffer, 0, bytesRead);
    }
    memStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
    request.ContentLength = memStream.Length;
               
    using (Stream requestStream = request.GetRequestStream()) {
        memStream.Position = 0;
        byte[] tempBuffer = new byte[memStream.Length];
        memStream.Read(tempBuffer, 0, tempBuffer.Length);
        memStream.Close();
        requestStream.Write(tempBuffer, 0, tempBuffer.Length);
    }
               
    using (var response = request.GetResponse()) {
        Stream stream2 = response.GetResponseStream();
        StreamReader reader2 = new StreamReader(stream2);
        return reader2.ReadToEnd();
    }
}               
            

This is the code for doing that.


static internal string SendPhotoMessage(
    string chatid, string filepath, string fileid, string msg) {
    if (filepath != "") {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(String.Format(
            "{0}/bot{1}/sendPhoto",
            baseurl, Properties.Settings.Default.BotToken));
        request.ContentType = "multipart/form-data";
        request.Method = "POST";
        NameValueCollection form = new NameValueCollection();
        form["chat_id"] = chatid;
        form["caption"] = msg;
        string response = InputOnlineFile(request, filepath, "photo", form);
        response = response.Substring(response.LastIndexOf("file_id\":\"") + 10, 100);
        response = response.Substring(0, response.IndexOf('"'));
        return response;
    } else {
        WebRequest request = WebRequest.Create(String.Format(
            "{0}/bot{1}/sendPhoto?chat_id={2}&photo={3}&caption={4}",
            baseurl, Properties.Settings.Default.BotToken, chatid, fileid, msg));
            using (WebResponse response = request.GetResponse()) {
                request.Timeout = 10000;
            }
            return "";
        }
    }
                   
    static internal string SendDocumentMessage(
        string chatid, string filepath, string fileid, string msg) {
        if (filepath != "") {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(String.Format(
                "{0}/bot{1}/sendDocument",
                baseurl, Properties.Settings.Default.BotToken));
            request.ContentType = "multipart/form-data";
            request.Method = "POST";
            NameValueCollection form = new NameValueCollection();
            form["chat_id"] = chatid;
            form["caption"] = msg;
            string response = InputOnlineFile(request, filepath, "document", form);
            response = response.Substring(response.LastIndexOf("file_id\":\"") + 10, 100);
            response = response.Substring(0, response.IndexOf('"'));
            return response;
        } else {
            WebRequest request = WebRequest.Create(String.Format(
                "{0}/bot{1}/sendDocument?chat_id={2}&document={3}&caption={4}",
                baseurl, Properties.Settings.Default.BotToken, chatid, fileid, msg));
            using (WebResponse response = request.GetResponse()) {
                request.Timeout = 10000;
            }
            return "";
        }
   }                   
            

This little starting tutorial ends here.