Telegram Bot: Broadcast – rozgłaszamy wiadomości na inne grupy

Dzisiaj zajmiemy się pisaniem prostego bota do rozgłaszania wiadomości na inne grupy, które nie mają ze sobą żadnego kontaktu. Punktem styku grup będzie bot. Każdą wiadomość weźmie i roześle na wszystkie grupy. Dzięki takiemu mechanizmowi ludzie z różnych grup mogą się porozumieć między sobą.

🧾 Założenia projektu

Bot ma rozesłać wiadomość do wszystkich zdefiniowanych kanałów (chat_id) wtedy gdy:

✔️ napiszemy coś bezpośrednio do bota
✔️ napiszemy wiadomość na jedną z naszych grup

🤴👸 Tworzymy grupy testowe

W telegramie klikamy w hamburgera

i wybieramy: New Group

Wpisujemy nazwę grupy i klikamy: NEXT

Następnie wpisujemy nazwę naszego bota w wyszukiwarkę (uwaga! boty są dostępne dla wszystkich publicznie)

klikamy w bota, po czym w button: CREATE

Tworzymy min. 2 grupy do testów.



CHAT_ID pobieramy indentycznie jak to opisałem w pierwszym poście. Jedyną różnicą jest to, że w każdej z grup musimy skierować wiadomość do bota:

/ – slash oznacza, że takie wiadomości bot zobaczy (są to komendy, ale o tym poźniej)

Po wysłaniu w/w wiadomości wchodzimy na znany nam adres:
👉 https://api.telegram.org/botTOKEN/getUpdates

I pobieramy stamtad CHAT_ID.

Na ten moment mamy niezbędne tokeny/idki:
✔️ TOKEN
✔️ CHAT_ID Grupy 1
✔️ CHAT_ID Grupy 2

Możemy przystąpić do programowania 👍

✍️ Piszemy (najprostszy) kod bota!

Musimy zaprogramować bota, aby był wstanie czytać wiadomości. Dzisiaj zajmiemy się metodą zwaną „pollingiem„. Co 1 sekundę, będziemy odpytywać wiadomości w pętli. Napiszmy najpierw szkielet samego kodu:

    class Bot
    {
        private readonly BotConfig _config;
        private readonly ITelegramBotClient _client;

        public static Bot Create(BotConfig config)
        {
            return new Bot(config);
        }

        private Bot(BotConfig config)
        {
            _config = config;
            _client = new TelegramBotClient(config.Token);
        }

        public async Task Init()
        {
        }
      
        public async Task Process() 
        {
        }
    }

Zaznaczam, że piszemy możliwie najprostszy kod, a nie najbardziej elegancki.

Bot będzie implementować dwie publiczne metody:
✔️ Init() – inicjalizacja bota
✔️ Process – przetwarzanie wiadomości, uruchamiane co sekundę

Bota zamkniemy w specjalnym kontekście aby nie wrzucać wszystkiego do Program.cs

    class RunningContext
    {
        private readonly Bot _bot;

        public RunningContext(Bot bot)
        {
            _bot = bot;
        }

        public async Task Run()
        {
            await _bot.Init();

            await Task.Run(async () =>
             {
                 while (true)
                 {
                     await _bot.Process();
                     await Task.Delay(1000);
 // czekamy sekundę aby znów odpytać telegrama
                 }
             });
        }
    }

Metodą z liba Telegram.Bot z jakiej skorzystamy będzie await _client.GetUpdatesAsync. Uruchomiona bez parametrów zwraca 100 ostatnich wiadomości. Problem w tym, że chcemy wyłącznie mieć nowe updaty. Rozwiazać to możemy przez przesunięcie/offset. Pierwszym parametrem dla w/w metody jest właśnie offset, określający ID ostatniej wiadomości, od której chcemy pobierać kolejne informacje.

Napiszemy metodę, która zwróci wyłącznie zwykłe, ostatnie wiadomości (allowedUpdates: UpdateType.Messages) na podstawie offsetu (messageOffset).

        private async Task<Update[]> GetMessages(int messageOffset)
        {
            var messages = await _client.GetUpdatesAsync(messageOffset, allowedUpdates: new []
            {
                UpdateType.Message,
            });

            return messages;
        }

Ograniczamy się wyłącznie do Message, ponieważ nie chcemy rozgłaszać innych treści.

Przejdźmy do Init(). Pobierzemy ID ostatniej wiadomości (lastUpdate). Będzie to nasz offset wykorzystywany w dalszej części.

        public async Task Init()
        {
            const int lastUpdate = -1; // ✨magiczna wartosc -1✨ 

            var messages = await GetMessages(lastUpdate);
            if (!messages.Any())
            {
                return;
            }

            _messageOffset = messages.Last().Id;
        }

Świetnie, w _messageOffset mamy offsert.

Możemy się zająć implementacją broadcastowania wiadomości, który będzie w metodzie Process.

Działanie metody Process:
✔️ pobranie najnowszych wiadomości na postawie offset
✔️ wysłanie do zdefiniowanych grup (CHAT_ID)
✔️ aktualizacja offset

        public async Task Process()
        {
            // pobieramy nowe wiadomości
            var newUpdates = await GetMessages(_messageOffset);

            foreach (var update in newUpdates)
            {
                // kazdą wiadomość wysyłamy do naszych grup (CHAT_ID)
                foreach (var chatId in _config.Chats)
                {
                    var message = update.Message;
                    var messageChatId = message.Chat.Id;
                    // robimy zwykłego forwarda do naszych grup
                    await _client.ForwardMessageAsync(chatId, messageChatId, message.MessageId);
                }
            }

            // aktualizujemy offset
            if (newUpdates.Any())
            {
                _messageOffset = newUpdates.Last().Id;
            }
        }

Teraz gdy napiszemy bezpośrednio wiadomość do bota

To każda zostanie rozgłoszona (forwardowana) do wszystkich grup. Mamy załatwione nasze pierwsze założenie:
✔️ bot ma rozsyłać wiadomości bezpośrednio skierowane do bota

Jeszcze chcelibyśmy móc rozgaszać wiadomości, które zostały bezpośrednio napisane na grupach. Domyślnie bot nie widzi co piszemy (to ze względu na domyślny tryb: private, niech tak zostanie). Natomiast gdy użyjemy komendy bot ją zobaczy.

Czym jest komenda? To wiadomość zaczynająca się od / (slash):
/komenda [dalsza część wiadomości]

Napiszemy teraz kawałek kodu rozgłaszającego wiadomości. Przyjmiemy, że każda zaczynająca się od: /bc wiadomość zostanie przesłana wszędzie

Wracamy do metody Process

        public async Task Process()
        {
            // pobieramy nowe wiadomości
            var newUpdates = await GetMessages(_messageOffset);

            foreach (var update in newUpdates)
            {
                // każda wiadomość wysyłamy do naszych grup (CHAT_ID)
                foreach (var chatId in _config.Chats)
                {
                    var message = update.Message;
                    var messageChatId = message.Chat.Id;

                    // nie rozgłaszamy na grupę skąd pochodzi wiadomość, nie ma sensu powielać jej tutaj
                    if (chatId == messageChatId)
                    {
                        continue;
                    }

                    // pobieramy treść
                    var text = message.Text;

                    // UWAGA! bardzo prymitywne sprawdzenie, można o wiele lepiej
                    // ale to nie jest cel tego posta. Przypominam, że ma byc prosto :)
                    if (!string.IsNullOrEmpty(text))
                    {
                        // jeśli zaczyna się od / znaczy ze jest komendą
                        if (text.StartsWith("/"))
                        {
                            // jeśli to "inna" komenda, to nic nie rób
                            if (!text.StartsWith("/bc "))
                            {
                                continue;
                            }
                        }
                    }

                    // robimy zwykłego forwarda
                    await _client.ForwardMessageAsync(chatId, messageChatId, message.MessageId);
                }
            }

            // aktualizujemy offset
            if (newUpdates.Any())
            {
                _messageOffset = newUpdates.Last().Id;
            }
        }

Oczywiście nie jest to kod do wykorzystania na produkcji, ponieważ brakuje mu zabezpieczeń, np. teraz każdy nieproszony gość może spamować grupy wrzucając wiadomości do bota.

✔️ Podsumowanie

Nie jest to jedyny możliwy realizacji broadcasta. Tutaj użyliśmy najprostszego sposoby. Moglibyśmy napisać to napisać zupełnie inaczej np.:
– kopiując 1:1 wiadomości (wtedy użytkownicy staną się anonimowi) i wrzucić wszędzie
– przechwytywać treść, zmienić ją i przesłać dalej

Możliwości botów telegramowych są na prawdę ogrome. Użylismy w tym poście zaledwie wycinka ich możliwości.

Napisaliśmy nasz własny polling, co prawda chamski, ale działa. W następnym poście przerobimy sposób odczytywania wiadomości. Użyjemy do tego fantastycznego liba Telegram.Bot.Extensions.Polling, którego stosowanie jest bardzo wygodne.

Jeśli chciałbyś zautomatyzować/usprawnić procesy w swojej firmie za pomocą botów (telegram, slack, inne) albo po prostu Twój biznes potrzebuje bota to skontaktuj się ze mną.

Tymczasem zapraszam do dodania się do newslettera aby niczego nie przegapić!

👉 https://high-five.cc/newsletter

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.