Telegram – chat bot

Přeskočíme kecy o botech a rovnou se pustíme do práce. Největší platformou pro chatovací boty s otevřeným API nabízí Telegram. Dnes si ukážeme, jak si vytvořit bota, který bude interagovat na uživatelovo požadavky.

Vytvoření bota

Všechny boty vytváříme u toce botů – BotFather. Uděláme to tak, že v seznamu konverzací vyhledáme BotFather. V detailu konverzace vytvoříme nového bota příkazem /newbot a postupujeme podle pokynů.

Budete vyzváni na jméno bota (libovolné znaky včetně diakritiky) a následně na uživatelské jméno bota: slovo bez diakritiky, mezer s příponou bot nebo _bot. Uživatelské jméno musí být v celém Telegramu jedinečné.

Po vytvoření bota bude zobrazeno jeho API access token – dále jen token. Ten si pečlivě uchovejte a nikomu ho nesdělujte – slouží k ovládání bota.

Ovládání bota

V Telegramu existují dva způsoby, jak získat zprávy poslané botovi: long polling a webhooks

Long pooling

Při tomto přístupu se periodicky doptáváte API Telegramu na nové zprávy. Musíte vhodně nastavit periodu doptávání se na nové zprávy.

Webhooks

Při tomto přístupu je při každé interakci s botem zpráva odeslaná i na náš server. Tento přístup mám raději, protože vypadá jako komunikace v reálném čase.

V tomto článku si popíšeme použití webhooks. Pro použití webhooks potřebujeme php script, který běží na veřejné adrese s HTTPS certifikátem. Pro naši ukázku například: https://www.venca-x.cz/bot/pocasi.php

Teď trochu přeskočíme obsah souboru pocasi.php a rovnou tento script zaregistrujeme aby byl zavolán a přijal zprávu od našeho bota. To provedeme příkazem:

https://api.telegram.org:443/bot[token]/setwebhook?url=https://www.venca-x.cz/bot/pocasi.php 

Příkaz zadáme do URL v prohlížeči. Měli bychom obdržet hlášku s úspěšným nastavením webhooku:

 {"ok":true,"result":true,"description":"Webhook was set"}

Vytvoření bota

Zahájíme chat s uživatele BotFather, kterému pošleme následující požadavky

/newbot

Budete vyzváni pro přesdívku bota a následně budete vyzváni pro zadání uživatelského jména bota. Uživatelské jmeno musí končit na slovo „bot“.

Po vytvoření bota získáte token pro přístup k API Telegramu. Token si pečlivě uložte, budeme ho potřebovat.

Nastavení webooks botovi

Nyní botovi musíme nastavit URL adresu, na kterou bude přeposílat požadavky. Do vašeho prohlížeče zadáme následující URL adresu:

https://api.telegram.org/bot*TOKEN*/setwebhook?url=*https:/ /example.domain/path/to/bothook.php*

Slovo *TOKEN* nahraďte API tokenem, získaným při vytvoření bota.
*https:/ /example.domain/path/to/bothook.php* nahraďte vaší adresou, kde jsme zprovoznili jednoduchou ukázku vracející JSON.

Oživení bota

Bota máme vytvořeného, zaregistrovaný webhook, který nám přeposílá zprávy poslané botovi. Pojďme bota oživit aby zprávy zpracovával a reagoval na ně.

Pro práci s bot API se mi zalíbila knihovna telegram-bot/api nainstalujeme ji:

composer require telegram-bot/api

Super, konečně jsme se dostali na příjem zpráv a reagování na ně. Na zprávu /ping odpoví bot zprávou pong:

<?php

require_once "vendor/autoload.php";

try {
$bot = new \TelegramBot\Api\Client('YOUR_BOT_API_TOKEN');
$bot->command('ping', function ($message) use ($bot) {
$bot->sendMessage($message->getChat()->getId(), 'pong!');
}); $bot->run();
} catch (\TelegramBot\Api\Exception $e) {
$e->getMessage();
}

Nezapomeňte místo YOUR_BOT_API_TOKEN nastavit váš token z vytváření bota.

Klávesnice pro ovládání bota

Bota můžeme ovládát i přes speciální klávesnici pro rychlé volby. Po zadání příkazu /start přivítáme návštěvníka a zobrazíme mu klávesnici s rychlou volbou:

$bot->command('start', function ($message) use ($bot) {

$keyboard = new \TelegramBot\Api\Types\ReplyKeyboardMarkup(array(array("/pocasi", "/ping")), true);

$bot->sendMessage($message->getChat()->getId(), 'Vítejte počasí', null, false, null, $keyboard);

});

Android: Retrofit

Před několika lety jsem komunikoval se serverem přes balíček Volley. Protože komunikuji hlavně přes REST rozhraní, nahradil jsem Volley balíčkem Retrofit, který je pro tyto účely doporučovaný.

Retrofit je HTTP klient pro Android (Javu), který vám usnadňuje připojení k webové službě REST API. Usnadňuje konzumaci dat JSON a XML. Podporuje příkazy: GET, POST, PUT, PATCH a DELETE. Pojďme si ukázat jak na to.

Nainstalujeme závislosti:

// Retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'

// JSON Parsing
compile 'com.google.code.gson:gson:2.8.2'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

+ internet permission

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.chikeandroid.retrofittutorial">
 
    <uses-permission android:name="android.permission.INTERNET" />
 
    <application>
       ....
    </application>
 
</manifest>

Retrofit je knihovna, která používá anotované rozhraní pro volání REST API. Vytvoříme jednoduchý požadavek GET pro seznam knih, který se automaticky namapuje na objekty. API pro seznam knih vrací tento JSON:

{  
    "books":[  
        {  
            "author":"Božena Němcová",
            "title":"Babička"
        },
        {  
            "author":"Božena Němcová",
            "title":"V zámku a v podzámčí"
        },
        {  
            "author":"Karel Čapek",
            "title":"Krakatit"
        },
        {  
            "author":"Karel Čapek",
            "title":"Jak se co dělá"
        }
   ]
}

jsonschema2pojo

Z JSONu necháme vygenerovat objekty přes službu jsonschema2pojo. Vložíme JSON, vybereme Source type: JSON, Annotation style: Gson a odškrtneme Allow additional properties.

Rozhraní služby

Modelovou třídu máme vytvořenou, vytvoříme rozhraní služby, které bude spravovat API endpointy. Vytvoříme GET požadavek pro načtení všech knih:

package cz.vencax.retrofit;

import cz.vencax.retrofit.model.Book;
import cz.vencax.retrofit.model.Books;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;

public interface BookService {
    @GET("books")
    Call<Books> getAllBooks();
}

Všimněte si anotace @GET označující typ požadavku a nabývající hodnotu books, která označuje endpoint (tato hodnota se přidá k BASE_URL popsané níže).

RetrofitClient

požadavky na REST API budeme odesílat přes třídu RetrofitClient:

package cz.vencax.retrofit;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {
    static final String BASE_URL = "http://demo7646366.mockable.io/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit==null) {

            Gson gson = new GsonBuilder()
                    .setLenient()
                    .create();

            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }
        return retrofit;
    }
}

Kde BASE_URL nabývá hodnoty se základním URL API, ke kterému se přidávají hodnoty z rozhraní služby popsané výše.

Získání dat v activity

V activity poté data jednoduše získáme a můžeme s nimi dále pracovat. U pozorňuji, že data jsou získána asynchronně:

    private void getBooksFromApi() {

        Log.d("xxx", "getBooksFromApi");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> books = bookService.getAllBooks();
        books.enqueue(new Callback<Books>() {

            @Override
            public void onResponse(Call<Books> call, Response<Books> response) {
                Log.d("xxx", "onResponse: " + response);
                if (response.isSuccessful()) {
                    Log.d("xxx", "response.isSuccessful()" + response.body());
                    Books books = response.body();

                    Log.d("xxx", "response.isSuccessful(), books count: " + books.getBooks().size());
                    for (Book book : books.getBooks()) {
                        Log.d("xxx", "Book: " + book.getTitle() + ", " + book.getAuthor());
                    }
                } else {
                    Log.d("xxx", "response NOT isSuccessful()" + response.errorBody().source());
                }
            }

            @Override
            public void onFailure(Call<Books> call, Throwable t) {
                Log.d("xxx", "onFailure: " + t);
            }
        });
    }

Touto jednoduchou ukázkou jsem vám představil jak na to. Jednoduché, že?

Odeslání dat na server – POST

Pokud však potřebujete posílat nějaká data, je nutné je ukládat přes metody PUT nebo POST. Pojďme se podívat jak na to s metodou POST (PUT by byla analogická).

V rozhraní služby vytvoříme metodu POST s endpointem a parametrem @body:

package cz.vencax.retrofit;

import cz.vencax.retrofit.model.Book;
import cz.vencax.retrofit.model.Books;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;

public interface BookService {
    @POST("send")
    Call<Book> postBook(@Body Book book);
}

Na endpoint BASE_URL/send odešleme objekt Book – tento objekt se automaticky převede na JSON.

Odeslání POST požadavku z activity:

    private void postBookToApi() {

        Log.d("xxx", "postBookToApi");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);

        Book bookPost = new Book();
        bookPost.setAuthor("vEnCa-X");
        bookPost.setTitle("Jak na Android - Retrofit");

        Call<Book> book = bookService.postBook(bookPost);
        book.enqueue(new Callback<Book>() {

            @Override
            public void onResponse(Call<Book> call, Response<Book> response) {
                Log.d("xxx", "onResponse: " + response);
                if (response.isSuccessful()) {
                    Log.d("xxx", "response.isSuccessful(): " + response.body());
                    Book book = response.body();
                    Log.d("xxx", "Book: " + book.getTitle() + ", " + book.getAuthor());
                } else {
                    Log.d("xxx", "response NOT isSuccessful()" + response.errorBody().source());
                }
            }

            @Override
            public void onFailure(Call<Book> call, Throwable t) {
                Log.d("xxx", "onFailure: " + t);
            }
        });
    }

Další způsob bez call

Response<Book> book = bookService.postBook(bookPost).execute();
if (book.isSuccessful()) {
   Log.d("xxx", "book reponse: " + book);
}

Problémy

  • máš aktuální verze balíčků?