📜 ⬆️ ⬇️

Writing "Hello, World" Telegram bot on C

Hello everyone, I do not know why this is necessary, but who can come in handy ...

Disclaimer: I am by no means a professional C programmer.

What we need:
')
1. Any computer on Linux, Ubuntu, Centos, MacOS ... with access to port 443 or 8443 from the Internet.
2. Any C compiler
3. Libraries openssl, libssl-dev ("apt-get install openssl libssl-dev" in the terminal, for Ubuntu)

So let's get started ...

The first thing to do is to create a bot for the father of all @BotFather bots, omit all the details and assume that everyone coped with this task and received a token, something like:
373288854: AAHHT77v5_ZNEMus4bfn6dxiMeeEwgwJ

Next, create an ssl certificate to install WebHook. The command looks like this:

openssl req -newkey rsa:2048 -sha256 -nodes -keyout private.key -x509 -days 365 -out public.pem 

We pack the key and public certificate in one file:

 cat private.key public.pem > cert.pem 

Install WebHook:

 curl -F"url=https://_IP:( 443,  8443)/_URI(   ,    )/" -F"certificate=@public.pem" https://api.telegram.org/bot/setWebhook/ 

A JSON response must come from something of the type success: true ... if not, then check everything and try again.

Getting to the most interesting:

Create a file main.c and open it in any editor. We include the necessary libraries:

 #include <stdio.h> #include <openssl/bio.h> #include <openssl/ssl.h> #include <unistd.h> #include <openssl/err.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <resolv.h> #include <netdb.h> 

Socket initialization function:

 int InitializeSocket(int port) { int sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) exit(-1); struct sockaddr_in s_addr; s_addr.sin_family = AF_INET; s_addr.sin_addr.s_addr = INADDR_ANY; s_addr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0) { printf("Binding Error!\n"); exit(-3); } return sd; } 

Enable SSL / TLS:

 SSL_CTX * InitializeSSL(char[] certificate) { OpenSSL_add_all_algorithms(); SSL_load_error_strings(); SSL_library_init(); SSL_CTX * sslctx = SSL_CTX_new(TLSv1_2_server_method()); if (SSL_CTX_use_certificate_file(sslctx, certificate , SSL_FILETYPE_PEM) <= 0) { exit(-2); } if (SSL_CTX_use_PrivateKey_file(sslctx, certificate, SSL_FILETYPE_PEM) <= 0) { exit(-2); } if (!SSL_CTX_check_private_key(sslctx)) { exit(-2); } return sslctx; } 

Actually main () itself:

 int main() { SSL_CTX * sslctx = InitializeSSL("cert.pem"); //         int sd = InitializeSocket(8443); //      WebHook listen(sd, 5); //     while (1) { //   int client = accept(sd, NULL, NULL) // accept   ,     ,    sockaddr,           ,         NULL,    . SSL * ssl = SSL_new(sslctx); //C ssl  SSL_set_fd(ssl, client); //     if (SSL_accept(ssl) <= 0) { //  ,           SSL_clear(ssl); close(newsd); continue; } //     fork()      ,         int pid = fork(); if (pid != 0) { //  ,         SSL_clear(ssl); close(newsd); continue; } //       //     .... exit(0); //   } } 

Since Telegram uses the HTTP protocol, I will clarify some points:

Any HTTP request consists of headers separated by "\ r \ n", and the body separated from the headers "\ r \ n \ r \ n" may be empty, but the separator "\ r \ n \ r \ n" is always present . Requests from Telegram will come by the POST method, the body will be in JSON format.

An example of a request similar to Telegram:

 POST /(URI    WebHook) HTTP/1.1\r\n ....     Content-Type: application/json\r\n (   ) Content-Length: 256\r\n (   ,  ) ..../r/n/r/n Json  

Each time a person sends a bot message, the telegraph server will send similar requests to our server. In general, it is not necessary to answer them, but in the case of a Telegram, it is necessary, otherwise it will send the same request cyclically.

To do this, we will prepare a short HTTP response:

 HTTP/1.1 200 OK\r\n Connection: close\r\n\r\n 

These two fields are enough to tell the Telegram server that everything is normal, the answer is 200 and you can close the connection

We continue to write the program. Inside the loop after creating a child process ...

 char[] response = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"; // HTTP response char header[1024]; bzero(header,1024); //               . int s = 0; int n = 0; while (strcmp(header + s - strlen("\r\n\r\n"), "\r\n\r\n") != 0) { //strcmp         0,      strlen("\r\n\r\n")   "\r\n\r\n",      n = SSL_read(ssl,header+s,1); //      header + s, s -  -   s += n; //n - -     } //,  ,     , uri, content-type   content-length . if (strstr(header,"POST /(URI    WebHook) HTTP/1.1\r\n") == NULL) { //   POST ....  header,      NULL,    ,       SSL_clear(ssl); close(client); exit(0); } //   ,   application/json; if (strstr(header, "Content-Type: application/json") == NULL) { SSL_clear(ssl); close(client); exit(0); } //  ,     int len = atoi(strstr(header, "Content-Length: ") + strlen("Content-Length: ")); //strstr       ,    "Content-Length: ",  -      ,     "Content-Length: "      int  atoi(char *); char body[len+2]; bzero(body, len+2); //   ,          ,    ,           -  n = 0; s = 0; while (len - s > 0) { //                - n = SSL_read(ssl, request + s, len - s); //      ,      ,      ,   SSL_read  -   s += n; } //    ,   http response    SSL_write(ssl, response, (int)strlen(response)); SSL_clear(ssl); SSL_free(ssl); close(client); //    "Hello, World"          "Hello, World!",                chat_id int chat_id = atoi(strstr("\"chat_id\":") + strlen("\"chat_id\":")); //      Content-Length //   ,       SendMessage char msg[] = "Hello, World!"; SendMessage(chat_id, msg); //   

To send requests, we almost need to initialize the socket and ssl, but unlike receiving requests, we will not wait for the connection, we will just send the data immediately:

 void SendMessage(int chat_id, char[] msg) { int port = 443; char host[] = "api.telegram.org"; //     //  HTTP    ,     char header[] = "POST /bot352115436:AAEAIEPeKdR2-SS7p9jGeksQljkNa9_Smo0/sendMessage HTTP/1.1\r\nHost: files.ctrl.uz\r\nContent-Type: application/json\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s"; //     char tpl[] = "{\"chat_id\":%d,\"text\":\"%s\"}"; char body[strlen(tpl)+strlen(msg)+16]; bzero(body, strlen(tpl)+strlen(msg)+16); sprintf(body,tpl,chat_id,msg); // printf,    char[] char request[strlen(header)+strlen(body)+4]; bzero(request,strlen(header)+strlen(body)+4); sprintf(request, header, strlen(body), body); //  ,    struct hostent *server; struct sockaddr_in serv_addr; int sd; sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) exit(-5); server = gethostbyname(host); //   ip      url if (server == NULL) exit(-6); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); if (connect(sd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) { exit(-6);} SSL_CTX * sslctx = SSL_CTX_new(TLSv1_client_method()); SSL * cSSL = SSL_new(sslctx); SSL_set_fd(cSSL, sfd); SSL_connect(cSSL); SSL_write(cSSL,request,(int)strlen(request)); //  ,           ,      -   char str[1024]; SSL_read(cSSL, str, 1024); //     SSL_clear(cSSL); SSL_CTX_free(sslctx); close(sd); } 

On this, in principle, everything. Save the file in the same folder with the certificate, compile it with any compiler and run:

 clang main.c -o bot -lcrypto -lssl ./bot 

The end!

I hope the article will be useful to someone.

Source: https://habr.com/ru/post/325846/


All Articles