Making Things More Convenient

Chris Watson

Chris Watson

Core Maintainer

One of the biggest goals of Tourmaline since its inception has been to make things easier on the bot developer, while also limiting the possibility of runtime exceptions that cause the bot to go down. Those are both reasons why I chose Crystal as the language to build this framework on, as it's both easy to use with a beauitful syntax, and compiled with a strong type system which keeps you (for the most part) from making mistakes that could prove fatal during runtime. Well today I'm happy to announce a couple changes to the framework itself that should also serve to make life easier on you, the bot developer.

Bye Bye Filters

This most recent update, v0.20.0 removes the concept of the Filter. As announced in the release notes, the main issue with them was trying to allow a developer to receive information about the update being filtered. I tried to solve this using Update#context, but it was majorly limited and pretty messy. As I was working on my other Telegram related project Proton, which I've tried to structure in a very similar way to Tourmaline, I figured out a way to do make handlers work in a much less messy way than before.

You see, prior to introducing filters several releases back, we had handlers. They worked for the most part, but had a couple issues. The main, overarching issue was that the original handler implementation wasn't very extendable. EventHandler, which was the base handler class, was a normal class that was in charge of all annotations and made handlers themselved kind of messy. So how is this time around different?

First of all, EventHandler is now abstract and does two things. First of all it has an abstract definition call(update : Update) which extending handlers need to implement. It also still, in a way, handles all annotations, but it does it in a much more abstract fassion. Let's take a look at some code:

module Annotator
private def register_event_handler_annotations
{% begin %}
{% for subclass in Tourmaline::EventHandler.subclasses %}
{{ subclass.id }}.annotate(self)
{% end %}
{% end %}
end
end

This is a module included within the EventHandler class and included by Client and it's responsible for one thing: finding subclasses of EventHandler and calling the self.annotate method on those classes. This allows handlers to manage their own annotations! The Current list of implemented handlers is as follows: CommandHandler, HearsHandler, CallbackQueryHandler, ChosenInlineResultHandler, InlineQueryHandler, and the standard UpdateHandler.

Most handlers also include their own Context struct which holds all the data that normally would've been passed to the UpdateContext by a filter. I like this approach better because it allows handlers to control what data they pass to their calbacks.

Hello RoutedMenu

One of the most common tasks for any bot developer is creating menus using InlineKeyboardMarkup, and it can get pretty difficult when those menus include multiple levels, back buttons, etc. For those types of situations I have created the RoutedMenu helper class. Let's start with a usage example:

require "tourmaline/extra/routed_menu"
MENU = RoutedMenu.build do
route "/" do
content "Some content to go in the home route"
buttons do
route_button "Next page", "/page_2"
end
end
route "/page_2" do
content "..."
buttons do
back_button "Back"
end
end
end
class MenuBot < Tourmaline::Client
@[Command("menu")]
def menu_command(ctx)
ctx.message.respond_with_menu(MENU)
end
end
bot = MenuBot.new(ENV["API_KEY"])
bot.poll

If I succeeded in my goal, which is making an easy to use and easy to understand DSL for menu creation, it should be extremely obvious what the above code does. First we build a menu using the RoutedMenu.build DSL. Think of each route call as creating a page, because that's literally what it does. The route call exposes another DSL that allows you to build the page using the content and buttons methods, etc etc.

RoutedMenu also adds a new set of methods for sending those menus including send_menu, reply_with_menu, and respond_with_menu.

And that's about it. There is also PagedKeyboard which was introduced a couple updates back, webhook support without Kemal has been improved, and speed has been increased, but those are all secondary.

Thanks for using Tourmaline!