FuentesBot is a Telegram bot to find the public fontaint close to your position (Madrid or Barcelona, Spain).

Project

Architecture

diag d4e47d5f29d0bcb1439d0584d91d37c9

TelegramApi

package mn.telegram

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.http.annotation.QueryValue
import io.micronaut.http.client.annotation.Client
import io.micronaut.http.client.multipart.MultipartBody
import io.reactivex.Single

@Client('https://api.telegram.org')
interface TelegramClient {

    @Post('/bot${telegram.token}/sendMessage')
    Single<Message> sendMessage(@Body Message message)

    @Post(value='/bot${telegram.token}/sendPhoto', produces = MediaType.MULTIPART_FORM_DATA)
    Single<Message> sendPhoto(@Body MultipartBody photo)

    @Post(value='/bot${telegram.token}/sendAnimation', produces = MediaType.MULTIPART_FORM_DATA)
    Single<Message> sendAnimation(@Body MultipartBody animation)

    @Get(value='/bot${telegram.token}/sendLocation')
    Single<Message> sendLocation(@QueryValue("chat_id") String chat_id,
                                 @QueryValue("latitude") float latitude,
                                 @QueryValue("longitude") float longitude)

}

Controller

package mn.fuentes.bot

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.reactivex.Single
import mn.telegram.InlineKeyboardButton
import mn.telegram.InlineKeyboardMarkup
import mn.telegram.Location
import mn.telegram.Message
import mn.telegram.TelegramClient
import mn.telegram.Update

@Controller('/${telegram.token}/fuentes')
class FuentesController {

    FuentesService fuentesService

    TelegramClient telegramClient

    FuentesController (FuentesService fuentesService, TelegramClient telegramClient){
        this.fuentesService = fuentesService
        this.telegramClient = telegramClient
    }

    void hello(String chatId){

        Message msg = new Message(chat_id: chatId, text: """
Puedo indicarte donde se encuentra la fuente pública más cercana
pero para ello tienes que mandarme tu posicion

Selecciona `enviar adjunto` (el clip) y pincha en Ubicación.
Una vez me mandes donde estas te enviaré un mapa con la fuente más cercana
y si lo abres obtendrás indicaciones para ir a ella


Made with 💙 by Puravida Software
Usando los catálogos de datos abiertos del Ayto de Madrid y Barcelona
""")
        telegramClient.sendMessage(msg).subscribe()

    }

    void sendFuente(String chatId, Location location) {

        Fuente ret = fuentesService.buscaFuentes(location).first()

        List<List<InlineKeyboardButton>> buttons = [
                [
                        new InlineKeyboardButton(
                                text: "Localizar en mapa",
                                callback_data: "location $ret.location.latitude $ret.location.longitude"
                        )
                ],
                [
                        new InlineKeyboardButton(
                                text: "Buscar Otra",
                                callback_data: "next $ret.location.latitude $ret.location.longitude"
                        )
                ],
        ]

        Message msg = new Message(
                chat_id: chatId,
                text: ret.toMarkdown(),
                reply_markup: new InlineKeyboardMarkup(inline_keyboard: buttons)
        )

        telegramClient.sendMessage(msg).subscribe({}, {})
    }

    void sendLocation(String chatId, float latitude, float longitude){
        telegramClient.sendLocation( chatId, latitude, longitude)
                .subscribe({},{})
    }

    void sendNext(String chatId, float latitude, float longitude){
        Location location = new Location(latitude: latitude, longitude: longitude)
        Fuente ret = fuentesService.buscaFuentes(location, 2).last()

        List<List<InlineKeyboardButton>> buttons = [
                [
                        new InlineKeyboardButton(
                                text: "Localizar en mapa",
                                callback_data: "location $ret.location.latitude $ret.location.longitude"
                        )
                ]
        ]

        Message msg = new Message(
                chat_id: chatId,
                text: ret.toMarkdown(),
                reply_markup: new InlineKeyboardMarkup(inline_keyboard: buttons)
        )

        telegramClient.sendMessage(msg).subscribe({}, {})
    }


    @Post('/')
    String index(Update update){
        println update
        Single.create({ emitter ->

            if( update.message ) {

                if (update.message.location == null) {
                    hello(update.message.chat.id)
                }else{
                    sendFuente(update.message.chat.id, update.message.location)
                }
            }

            if( update.callback_query ){
                String chatId = update.callback_query.message.chat.id
                String[]args = update.callback_query.data.split(' ')

                switch( args.first() ){
                    case 'location':
                        sendLocation(chatId, args[1] as float, args[2] as float)
                        break
                    case 'next':
                        sendNext(chatId, args[1] as float, args[2] as float)
                        break
                }
            }

            emitter.onSuccess("done")

        }).subscribe()

        "done"
    }


}

BotFather

Before to deploy a bot you need to create it and obtain a token via BotFather, a Telegram bot who create bots. Following Telegram instructions you’ll have a token similar to 'AAABBB:1234433221'

We’ll use this token to:

  • customize the controller url to avoid scrapper to find it

  • send messages to the user (via POST)

It is a bad idea to versionate this token so we’ll write it in our gradle.properties and sustitute the telegram.token environment variable at runtime

Heroku

To deploy the project in Heroku you need an account and create an app. Also you’ll need to have heroku cli installed into your machine.

The easy way to deploy an application to Heroku is to build a Docker image and deploy to it so we’ll have some gradle tasks to do this:

  • buildDockerMadrid

  • pushDockerMadrid

  • releaseDockerMadrid

(As this project can serve public fountains for Madrid and Barcelona we’ll have same tasks for Barcelona changing the telegram token)