Як відправити повідомлення в Telegram

Частенько буває хочеться, щоб ZennoPoster в процесі роботи повідомив про якийсь результат, відправивши наприклад графік, сриншот інстансу, чи звичайне коротеньке текстове повідомлення на телефон. Проте, на даний час відправка безкоштовна SMS від Київстар з номеру 777 недоступна, крім того в SMS картинку не відправиш. Тому, є смисл для цього використовувати Telegram.

Значить щоб відправляти повідомлення в Telegram є декілька способів. Два із них – це використання API і API TelegramBot, інші – це використання веб версії використовуючи браузер, чи клієнта Telegram наприклад використовуючи WinAPI. Всі способи мають свої переваги і недоліки, їх розгляд виходить за рамки цієї теми, тому просто вибираю для роботи TelegramBot API.

Створюю свого бота в Telegram, як я описував тут, в результаті отримую Telegram API key.
З цього моменту в мене появляється можливість звертатись до API Telegram за допомогою HTTP запитів і отримувати відповідь.
Проте, щоб відправляти від імені TelegramBot повідомлення на свій аккаунт необхідно одержати TelegramID користувача який згідний одержувати ці повідомлення – про це писав тут.
Так от в Telegram є умова – першим до TelegramBot повинен написати користувач.
Тому потрібно написати своєму TelegramBot будь-яке повідомлення.

Тепер в мене є TelegramBot, є TelegramID (він і chat_id), і я можу приступати до відправки повідомленнь.
Потрібно також зробити ремарку, що якщо мені прийдеться відправляти повідомлення сусіду чи замовнику, то мені потрібно буде попросити їх написати щось моєму TelegramBot, щоб він одержав права на відправку повідомлень.
Також, у випадках, коли на одну подію мені потрібно оповістити декількох людей одночасно – то немає сенсу відправляти повідомлення кожному окремо – для цього в Telegram є канали і чати. Необхідні люди повинні бути підписаними на канал чи чат в який будуть приходити повідомлення, а сам TelegramBot повинен бути добавлений адміністратором (тобто адміністратор каналу чи чату повинен надати доступ для публікації повідомлень боту).

Тепер власне можна відправляти повідомлення в Telegram.
Для цього всюди рекомендують сформувати URL:
https://api.telegram.org/bot<Token>/sendMessage?chat_id=<TelegramID>&text=<MESSAGE>
Де замість <Token> потрібно підставити свій Telegram API key який одержали від @BotFather.
Замість <TelegramID> – підставити необхідний ідентифікатор чату користувача, групи чи каналу.
І замість <MESSAGE> необхідно вказати текст повідомлення яке буде відправлено.
Після чого, просто вставляємо цей URL в браузер Google Chrome і результатом переходу буде проведена відправка повідомлення.
Власне в цей момент браузер відправляє HTTP-запит методом GET по вказаному URL.

Значить, точно такий запит можна відправити за допомогою стандартного блока GET в ZennoPoster, використати сURL чи будь-яку іншу програму, яка вміє виконувати цей тип запитів.
Але, відправляти кожного разу одинакове повідомлення не цікаво, як і запускати відправку руками в браузері.
Крім того, інколи хочеться відправляти картинки чи відео, інколи добавляти кнопки до повідомлень.
Тому в моєму випадку здається найбільш зручним використання C# інструкцій, і виконання їх в потрібний момент в програмі ZennoPoster.

Також, перш ніж приводити сам код хочу сказати, що можливо все зробити набагато простіше, менше коду, замість формування аргументів використовувати літерали. Проте, я частенько на форумі зустрічаю проблеми, що не вдається відправити повідомлення, тому що в тексті є символ дієз (# – решітка) чи інші символи. Умільці зразу рекомендують переводити параметри в UrlEncode, і цей підхід не завжди допомогає.
Рішенням цієї проблеми є екранування всіх символів які при конвертації в число мають значення від 1 до 126 символом \. Тому, у випадках, коли є проблема – потрібно попередньо обробити самий текст повідомлення.

Також мені зустрічалось багато різних способів формування параметрів URL у вигляді літералів (рядка в подвійних кавичках), і люди писали що щось не працює так як потрібно. Я вже розказував як формувати параметри для URL тут, а в коді нижче також буде використання описаних способів.

Як відправити текстове повідомлення в Telegram

// Варіант формування рядка з параметрами
// Використовується сортування по ключу
// можливо потрібно добавити в GAS бібліотеку System.Net.Http.dll

string api_key = project.Variables["token"].Value;
string endpoint = string.Format("https://api.telegram.org/bot{0}", api_key);
string url = string.Join("", new[]{endpoint, "/sendMessage"});

string[] parse_mode = new[]{ "MarkdownV2", "HTML"};
string[] disable_notification = new[]{ "true", "false"};
string[] disable_web_page_preview = new[]{ "true", "false"};

var button = new Dictionary<string,string>();
	button["text"] = "Кнопка";
	button["url"] = "https://site.com";

var buttons = new List<object>();
	buttons.Add(button);

var keyboard = new List<object>();
	keyboard.Add(buttons);

var inline_keyboard = new Dictionary<string,object>();
	inline_keyboard["inline_keyboard"] = keyboard;
	
string reply_markup	= Global.ZennoLab.Json.JsonConvert.SerializeObject(inline_keyboard,  Global.ZennoLab.Json.Formatting.None);

string chat_id = "346236679";
string text_input = @"text #
text
*text*
text
";
string text = string.Empty;


foreach(char ch in text_input){
	if(ch == '#') text +=@"\"; // символи від 1 до 126 потрібно екранувати символом \
	text +=ch.ToString();
}

var url_params = new Dictionary<string, string>();
    url_params.Add("chat_id",chat_id );    
 	url_params.Add("parse_mode", parse_mode[0]);
	url_params.Add("reply_markup", reply_markup);
	url_params.Add("disable_notification", disable_notification[0]);
	url_params.Add("disable_web_page_preview", disable_web_page_preview[0]);
	url_params.Add("text", text); 

var url_params_data = new System.Net.Http.FormUrlEncodedContent(url_params.OrderBy(x=>x.Key));
string query = url_params_data.ReadAsStringAsync().Result;

url = string.Join("?", new[]{url, query});

return ZennoPoster.HttpGet(url);

Як відправити зображення в Telegram

// Варіант формування рядка з параметрами
// Використовується сортування по ключу
// можливо потрібно добавити в GAS бібліотеку System.Net.Http.dll

var sBoundary = DateTime.Now.Ticks.ToString("x");
string path = @"C:\Users\User\Desktop\image.jpg";
string file_name = "image.jpg";
byte[] file_bytes = File.ReadAllBytes(path);

string api_key = project.Variables["token"].Value;
string endpoint = string.Format("https://api.telegram.org/bot{0}", api_key);
string url = string.Join("", new[]{endpoint, "/sendPhoto"});

string[] parse_mode = new[]{ "MarkdownV2", "HTML"};
string[] disable_notification = new[]{ "true", "false"};
string[] disable_web_page_preview = new[]{ "true", "false"};

var button = new Dictionary<string,string>();
	button["text"] = "Кнопка";
	button["url"] = "https://site.com";

var buttons = new List<object>();
	buttons.Add(button);

var keyboard = new List<object>();
	keyboard.Add(buttons);

var inline_keyboard = new Dictionary<string,object>();
	inline_keyboard["inline_keyboard"] = keyboard;
	
string reply_markup	= Global.ZennoLab.Json.JsonConvert.SerializeObject(inline_keyboard,  Global.ZennoLab.Json.Formatting.None);

string chat_id = "123456789";
string text_input = @"text #
text
*text*
text
";
string text = string.Empty;


foreach(char ch in text_input){
	if(ch == '#') text +=@"\"; // символи від 1 до 126 потрібно екранувати символом \
	text +=ch.ToString();
}

var file_data = new System.Net.Http.ByteArrayContent(file_bytes);
	file_data.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {
		Name = "photo", FileName = file_name
	};
var content = new System.Net.Http.MultipartFormDataContent(sBoundary);
	content.Add(file_data, "photo");	 
	content.Add(new System.Net.Http.StringContent(chat_id), "chat_id");
	content.Add(new System.Net.Http.StringContent(parse_mode[0]), "parse_mode");
	content.Add(new System.Net.Http.StringContent(reply_markup), "reply_markup");
	content.Add(new System.Net.Http.StringContent(disable_notification[0]), "disable_notification");
	content.Add(new System.Net.Http.StringContent(disable_web_page_preview[0]), "disable_web_page_preview");
	content.Add(new System.Net.Http.StringContent(text), "caption");

string content_type = string.Format("multipart/form-data;boundary={0}", sBoundary);
byte[] multipart_byte = content.ReadAsByteArrayAsync().Result.ToArray();
	
var method = ZennoLab.InterfacesLibrary.Enums.Http.HttpMethod.POST;
string proxy = string.Empty;
string encoding =Encoding.UTF8.WebName;
var type = ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly;
 
string post = ZennoPoster.HTTP.Request(
  method: method, // метод яким буде відправлено запит
  url: url,// по якому адресу буде відправлено запит
  content: multipart_byte, // які дані буде відправляти запит
  contentPostingType: content_type, // який тип контенту буде відправляти запит
  proxy: proxy, // з якого IP відправляти запит
  Encoding: encoding, // в якому кодуванні відправляти запит
  respType: type, // в якому вигляді повернути результат
  Timeout: 30000, // скільки часу очікувати результат
  UseRedirect: false, // включити слідування переадресаціям
  MaxRedirectCount: 0, // слідувати php переадресаціям 
  UseOriginalUrl: true, // не змінювати параметри запиту
  removeDefaultHeaders: true // видалення стандартних заголовків
);
return post;
// Варіант формування рядка з параметрами
// Використовується сортування по ключу
// можливо потрібно добавити в GAS бібліотеку System.Net.Http.dll

var sBoundary = DateTime.Now.Ticks.ToString("x");
string path = @"C:\Users\User\Desktop\image.jpg";
string file_name = "image.jpg";

string content_type = string.Format("multipart/form-data;boundary={0}", sBoundary);
var content = new System.Net.Http.MultipartFormDataContent(sBoundary);
	content.Add(new System.Net.Http.StreamContent(File.OpenRead(path)), name: "photo", fileName: file_name);
byte[] multipart_byte = content.ReadAsByteArrayAsync().Result.ToArray();

string api_key = project.Variables["token"].Value;
string endpoint = string.Format("https://api.telegram.org/bot{0}", api_key);
string url = string.Join("", new[]{endpoint, "/sendPhoto"});

string[] parse_mode = new[]{ "MarkdownV2", "HTML"};
string[] disable_notification = new[]{ "true", "false"};
string[] disable_web_page_preview = new[]{ "true", "false"};

var button = new Dictionary<string,string>();
	button["text"] = "Кнопка";
	button["url"] = "https://site.com";

var buttons = new List<object>();
	buttons.Add(button);

var keyboard = new List<object>();
	keyboard.Add(buttons);

var inline_keyboard = new Dictionary<string,object>();
	inline_keyboard["inline_keyboard"] = keyboard;
	
string reply_markup	= Global.ZennoLab.Json.JsonConvert.SerializeObject(inline_keyboard,  Global.ZennoLab.Json.Formatting.None);

string chat_id = "123456789";
string text_input = @"text #
text
*text*
text
";
string text = string.Empty;

foreach(char ch in text_input){
	if(ch == '#') text +=@"\"; // символи від 1 до 126 потрібно екранувати символом \
	text +=ch.ToString();
}

var url_params = new Dictionary<string, string>();
    url_params.Add("chat_id",chat_id );    
 	url_params.Add("parse_mode", parse_mode[0]);
	url_params.Add("reply_markup", reply_markup);
	url_params.Add("disable_notification", disable_notification[0]);
	url_params.Add("disable_web_page_preview", disable_web_page_preview[0]);
	url_params.Add("caption", text); 

var url_params_data = new System.Net.Http.FormUrlEncodedContent(url_params.OrderBy(x=>x.Key));
string query = url_params_data.ReadAsStringAsync().Result;

url = string.Join("?", new[]{url, query});

var method = ZennoLab.InterfacesLibrary.Enums.Http.HttpMethod.POST;
string proxy = string.Empty;
string encoding =Encoding.UTF8.WebName;
var type = ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly;
 
string s = ZennoPoster.HTTP.Request(
  method: method, // метод яким буде відправлено запит
  url: url,// по якому адресу буде відправлено запит
  content: multipart_byte, // які дані буде відправляти запит
  contentPostingType: content_type, // який тип контенту буде відправляти запит
  proxy: proxy, // з якого IP відправляти запит
  Encoding: encoding, // в якому кодуванні відправляти запит
  respType: type, // в якому вигляді повернути результат
  Timeout: 30000, // скільки часу очікувати результат
  UseRedirect: false, // включити слідування переадресаціям
  MaxRedirectCount: 0, // слідувати php переадресаціям 
  UseOriginalUrl: true, // не змінювати параметри запиту
  removeDefaultHeaders: true // видалення стандартних заголовків
);
return s.Length;

Як відправити документ чи файл Telegram

// Варіант формування рядка з параметрами
// Використовується сортування по ключу
// можливо потрібно добавити в GAS бібліотеку System.Net.Http.dll
var sBoundary = DateTime.Now.Ticks.ToString("x");
string path = @"C:\Users\User\Desktop\image.jpg";
string file_name = "image.jpg";
byte[] file_bytes = File.ReadAllBytes(path);

string api_key = project.Variables["token"].Value;
string endpoint = string.Format("https://api.telegram.org/bot{0}", api_key);
string url = string.Join("", new[]{endpoint, "/sendDocument"});

string[] parse_mode = new[]{ "MarkdownV2", "HTML"};
string[] disable_notification = new[]{ "true", "false"};
string[] disable_web_page_preview = new[]{ "true", "false"};

var button = new Dictionary<string,string>();
	button["text"] = "Кнопка";
	button["url"] = "https://site.com";

var buttons = new List<object>();
	buttons.Add(button);

var keyboard = new List<object>();
	keyboard.Add(buttons);

var inline_keyboard = new Dictionary<string,object>();
	inline_keyboard["inline_keyboard"] = keyboard;
	
string reply_markup	= Global.ZennoLab.Json.JsonConvert.SerializeObject(inline_keyboard,  Global.ZennoLab.Json.Formatting.None);

string chat_id = "123456789";
string text_input = @"text #
text
*text*
text
";
string text = string.Empty;

foreach(char ch in text_input){
	if(ch == '#') text +=@"\"; // символи від 1 до 126 потрібно екранувати символом \
	text +=ch.ToString();
}

var file_data = new System.Net.Http.ByteArrayContent(file_bytes);
	file_data.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {
		Name = "document", FileName = file_name
	};
var content = new System.Net.Http.MultipartFormDataContent(sBoundary);
	content.Add(file_data, "document");	 
	content.Add(new System.Net.Http.StringContent(chat_id), "chat_id");
	content.Add(new System.Net.Http.StringContent(parse_mode[0]), "parse_mode");
	content.Add(new System.Net.Http.StringContent(reply_markup), "reply_markup");
	content.Add(new System.Net.Http.StringContent(disable_notification[0]), "disable_notification");
	content.Add(new System.Net.Http.StringContent(disable_web_page_preview[0]), "disable_web_page_preview");
	content.Add(new System.Net.Http.StringContent(text), "caption");

string content_type = string.Format("multipart/form-data;boundary={0}", sBoundary);
byte[] multipart_byte = content.ReadAsByteArrayAsync().Result.ToArray();
	
var method = ZennoLab.InterfacesLibrary.Enums.Http.HttpMethod.POST;
string proxy = string.Empty;
string encoding =Encoding.UTF8.WebName;
var type = ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly;
 
string post = ZennoPoster.HTTP.Request(
  method: method, // метод яким буде відправлено запит
  url: url,// по якому адресу буде відправлено запит
  content: multipart_byte, // які дані буде відправляти запит
  contentPostingType: content_type, // який тип контенту буде відправляти запит
  proxy: proxy, // з якого IP відправляти запит
  Encoding: encoding, // в якому кодуванні відправляти запит
  respType: type, // в якому вигляді повернути результат
  Timeout: 30000, // скільки часу очікувати результат
  UseRedirect: false, // включити слідування переадресаціям
  MaxRedirectCount: 0, // слідувати php переадресаціям 
  UseOriginalUrl: true, // не змінювати параметри запиту
  removeDefaultHeaders: true // видалення стандартних заголовків
);
return post;

Як відправити відео Telegram

// Варіант формування рядка з параметрами
// Використовується сортування по ключу
// можливо потрібно добавити в GAS бібліотеку System.Net.Http.dll
var sBoundary = DateTime.Now.Ticks.ToString("x");
string path = @"C:\Users\User\Desktop\Video_2023-11-16_233808.wmv";
string file_name = "video.wmv";
byte[] file_bytes = File.ReadAllBytes(path);


string api_key = project.Variables["token"].Value;
string endpoint = string.Format("https://api.telegram.org/bot{0}", api_key);
string url = string.Join("", new[]{endpoint, "/sendVideo"});

string[] parse_mode = new[]{ "MarkdownV2", "HTML"};
string[] disable_notification = new[]{ "true", "false"};
string[] disable_web_page_preview = new[]{ "true", "false"};

var button = new Dictionary<string,string>();
	button["text"] = "Кнопка";
	button["url"] = "https://site.com";

var buttons = new List<object>();
	buttons.Add(button);

var keyboard = new List<object>();
	keyboard.Add(buttons);

var inline_keyboard = new Dictionary<string,object>();
	inline_keyboard["inline_keyboard"] = keyboard;
	
string reply_markup	= Global.ZennoLab.Json.JsonConvert.SerializeObject(inline_keyboard,  Global.ZennoLab.Json.Formatting.None);

string chat_id = "123456789";
string text_input = @"text #
text
*text*
text
";
string text = string.Empty;


foreach(char ch in text_input){
	if(ch == '#') text +=@"\"; // символи від 1 до 126 потрібно екранувати символом \
	text +=ch.ToString();
}

var file_data = new System.Net.Http.ByteArrayContent(file_bytes);
	file_data.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {
		Name = "video", FileName = file_name
	};
var content = new System.Net.Http.MultipartFormDataContent(sBoundary);
	content.Add(file_data, "video");	 
	content.Add(new System.Net.Http.StringContent(chat_id), "chat_id");
	content.Add(new System.Net.Http.StringContent(parse_mode[0]), "parse_mode");
	content.Add(new System.Net.Http.StringContent(reply_markup), "reply_markup");
	content.Add(new System.Net.Http.StringContent(disable_notification[0]), "disable_notification");
	content.Add(new System.Net.Http.StringContent(disable_web_page_preview[0]), "disable_web_page_preview");
	content.Add(new System.Net.Http.StringContent(text), "caption");

string content_type = string.Format("multipart/form-data;boundary={0}", sBoundary);
byte[] multipart_byte = content.ReadAsByteArrayAsync().Result.ToArray();
	
var method = ZennoLab.InterfacesLibrary.Enums.Http.HttpMethod.POST;
string proxy = string.Empty;
string encoding =Encoding.UTF8.WebName;
var type = ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly;
 
string post = ZennoPoster.HTTP.Request(
  method: method, // метод яким буде відправлено запит
  url: url,// по якому адресу буде відправлено запит
  content: multipart_byte, // які дані буде відправляти запит
  contentPostingType: content_type, // який тип контенту буде відправляти запит
  proxy: proxy, // з якого IP відправляти запит
  Encoding: encoding, // в якому кодуванні відправляти запит
  respType: type, // в якому вигляді повернути результат
  Timeout: 10*60000, // скільки часу очікувати результат
  UseRedirect: false, // включити слідування переадресаціям
  MaxRedirectCount: 0, // слідувати php переадресаціям 
  UseOriginalUrl: true, // не змінювати параметри запиту
  removeDefaultHeaders: true // видалення стандартних заголовків
);
return post;

Як бачимо серед варіантів вище – вони всі приблизно одинакові, і відрізняються тільки методом API, який визивається, та способом формування параметрів для HTTP запитів.
При чому у першому випадку, коли потрібно відправити текстове повідомлення, використовується старий метод ZennoPoster для відправки GET запитів, а для відправки всіх інших, коли є необхідність використовувати тип даних multipart використовується нова реалізація, тому що в старій є помилка, і тип даних у вигляді масиву байт не приймається (тобто запит не відправляється).
В останньому випадку спеціально збільшений час очікування відповіді, тому що при відправці відео може бути затрачено більше ніж 30 секунд, тому я встановив там 10 хвилин, хоча в будь-який момент можна змінити це значення.

Я знаю, що є хлопці, які формують дані multipart у вигляді текстового рядка, і підставляють їх в стандартні блоки в ZennoPoster, проте мені більше подобається формувати всі параметри в C#, я вважаю що це більш надійно захищає від потенційних помилок які можна допустити звичайною опечаткою. Власне в цій публікації я поділився заготовкою для відправки повідомленнь в Telegram від імені свого створеного TelegramBot використовуючи офіційні методи API Telegram.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *