Creating a map and geocoding bot for Telegram (Part 1)

4 minute read

I love Telegram and it’s bot API, which is designed brilliantly. The API could very well serve as an example for other messengers (Yes, Whatsapp, I am looking at you!).

Working as head of development for Carl Group in Hamburg, a few months ago I made my first Telegram bot for SENDONSCREEN.

I saw Chris from What3words at the Dmexco conference in September and was very impressed by his talk and the fantastic idea. Since then I have been thinking about making use of What3words.

Call me “the Blue Marble”

Picture So I developed a Telegram bot as a little “side project” just for fun and for learning a couple of new things. I called it the Blue marble and it does forward and reverse geocoding with the help of Openstreetmap, Google, Bing, Mapbox and the free What3words API.

The Blue marble has the following features:

  • fast because of caching and multithreading
  • works inline
  • easy command syntax, help text
  • shows maps from Openstreetmap/Mapbox with different zoom levels
  • does multi geocoding from different geo providers, with fail-over
  • asks for the users location and reverse it to address and What3words
  • converts address to latitude-longitude (DD or DMS) to What3words and reverse
  • outputs What3words addresses in all supported languages with direct links to the What3words map
  • uses Ruby threads for reverse geocoding multiple results
  • uses Redis for API request caching
  • caches user preferences like language and zoom level for some time
  • receives user feedback

What does it look like?

Screenshots from iOS, Android, Mac desktop client

Get the Blue marble bot

Ruby is great for making bots (and everything else, too :-)

Blue marble is made with Ruby and runs great on trusty Heroku.

I mainly used the following gems for the bot:

gem "semantic_logger"
gem "telegram-bot-ruby"
gem "redis", "~>3.2"
gem "typhoeus"
gem "what3words", "~> 2.0"
gem "poscvt"
gem "geokit", :github => "chriso0710/geokit"

I made my first bot with the telegram-bot-ruby gem and had no problems at all with it. It supports all Telegram bot API features, including custom keyboards, inline mode and image uploading.

I looked at the popular Ruby geocoding gems geocoder and geokit and finally settled for Geokit. Geokit has a great multi geocoding feature with a fail-over mechanism, which starts up if you hit rate limits or other errors. Blue marble uses the following providers in this order: Google, Openstreetmap, Bing.

I use Typhoeus for making all API request to geo providers. Typhoeus has a great caching feature and I use Redis for caching API results.

Geocode inline

So how does the inline mode work with telegram-bot-ruby? This is the main loop, which receives the bot updates and passes them on to the messagehandler class:

Telegram::Bot::Client.run(TOKEN) do |bot|
    begin
        logger.info "Bot started"
        bot.listen do |message|
            logger.tagged("#{message.from.id} #{message.from.username}") do
                handler = MessageHandler.new(bot, message)
                handler.debug
                case message
                    when Telegram::Bot::Types::InlineQuery
                        # inline query
                        handler.inline(message.query) if message.query != ""
                    when Telegram::Bot::Types::CallbackQuery
                        # handle callbacks from inline buttons
                        handler.callback
                    when Telegram::Bot::Types::Message
                        # handle text or location message
                        # ...
                end
            end
        end
    rescue Telegram::Bot::Exceptions::ResponseError => e
        logger.fatal e
    end
end

This is main part of the method, which handles and responds to the inline queries. The bot does geocoding in threads to speed things up. It then creates a result array of type InlineQueryResultArticle and sends it back to the user.

class MessageHandler

    def inline(text)
        r = Geokit::Geocoders::MultiGeocoder.geocode text, :provider_order => ORDER
        if r.success?
            threads = []
            r.all.each_with_index do |geo, index|
                threads << Thread.new do
                    Thread.current.name = "Thread #{index} #{geo.full_address}"
                    Thread.current["index"] = index
                    Thread.current["answertitle"] = geo.full_address
                    Thread.current["answertext"] = formatlocation(geo)
                    geo.country_code ? Thread.current["country"] = geo.country_code.downcase : Thread.current["country"] = ""
                end
            end
            answers = []
            threads.each do |t|
                t.join
                answers << Telegram::Bot::Types::InlineQueryResultArticle.new(
                    id: t["index"],
                    title: t["answertitle"],
                    thumb_url: sprintf(FLAGURL, t["country"]),
                    input_message_content: Telegram::Bot::Types::InputTextMessageContent.new(message_text: t["answertext"], parse_mode: "HTML"))
            end
            @bot.api.answer_inline_query(inline_query_id: @message.id, results: answers)
        end
    end

end

That’s the core of the inline query handling. The rest is dealing with all other messages, doing reverse geocoding, smart regular expressions and some caching of API calls and user preferences ;-)

It’s not difficult to make a Telegram bot when you are using the right tools and libraries for the job. Another reason for loving Ruby and the high quality of Ruby gems out there!

The blue marble icon is under Creative Commons Attribution-No Derivative Works 3.0 Unported License

Updated:

Leave a Comment