class Telegram::Composer

Direct Known Subclasses

Defined in:

telegram/composer.cr

Constructors

Class Method Summary

Instance Method Summary

Constructor Detail

def self.new(middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Create a new Composer with the given middleware


[View source]
def self.new(*middleware : AcceptableMiddleware) #

Create a new Composer with the given middleware


[View source]
def self.new(&block : MiddlewareFn) #

Create a new Composer with the given middleware


[View source]

Class Method Detail

def self.concat(first : Middleware, and_then : Middleware) : Middleware #

[View source]
def self.flatten(mw : AcceptableMiddleware) : Middleware #

[View source]
def self.match(ctx : Context, content : String, triggers : Array(Proc(String, Regex::MatchData | Nil))) #

[View source]
def self.pass #

[View source]
def self.trigger_fn(trigger : String | Regex | Array(String | Regex)) #

[View source]

Instance Method Detail

def branch(predicate : Filter::Predicate, true_mw : AcceptableMiddleware, false_mw : AcceptableMiddleware) #

Allows you to branch between two cases for a given context object.

This method takes a predicate function and two middleware. If the predicate returns true, true_mw will be called, otherwise false_mw will be called.

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def branch(true_mw : AcceptableMiddleware, false_mw : AcceptableMiddleware, &predicate : Filter::Predicate) #

Allows you to branch between two cases for a given context object.

This method takes a predicate function and two middleware. If the predicate returns true, true_mw will be called, otherwise false_mw will be called.

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def callback_query(trigger, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers some middleware for callback queries (updates that Telegram sends when a user clicks on a button in an inline keyboard).

This is essentially the same as calling:

bot.on(:callback_query) { |ctx| ... }

but it also allows you to match the query data against a given string or regular expression.

# Create an inline keyboard
kb = Telegram::InlineKeyboardMarkup.new.text("Go!", "button-payload")

# Send a message with the keyboard
bot.api.send_message(chat_id, "Press a button!", reply_markup: kb)

# Listen for a button press with that specific payload
bot.callback_query("button-payload") { |ctx| ... }

!!! note Always remember to call answer_callback_query, even if you don't do anything with it. crystal bot.callback_query("button-payload") do |ctx| ctx.answer_callback_query end

If you pass an array of triggers, your middleware will be called when at least one of them matches.


[View source]
def callback_query(trigger, *middleware : AcceptableMiddleware) #

Registers some middleware for callback queries (updates that Telegram sends when a user clicks on a button in an inline keyboard).

This is essentially the same as calling:

bot.on(:callback_query) { |ctx| ... }

but it also allows you to match the query data against a given string or regular expression.

# Create an inline keyboard
kb = Telegram::InlineKeyboardMarkup.new.text("Go!", "button-payload")

# Send a message with the keyboard
bot.api.send_message(chat_id, "Press a button!", reply_markup: kb)

# Listen for a button press with that specific payload
bot.callback_query("button-payload") { |ctx| ... }

!!! note Always remember to call answer_callback_query, even if you don't do anything with it. crystal bot.callback_query("button-payload") do |ctx| ctx.answer_callback_query end

If you pass an array of triggers, your middleware will be called when at least one of them matches.


[View source]
def callback_query(trigger, &block : MiddlewareFn) #

Registers some middleware for callback queries (updates that Telegram sends when a user clicks on a button in an inline keyboard).

This is essentially the same as calling:

bot.on(:callback_query) { |ctx| ... }

but it also allows you to match the query data against a given string or regular expression.

# Create an inline keyboard
kb = Telegram::InlineKeyboardMarkup.new.text("Go!", "button-payload")

# Send a message with the keyboard
bot.api.send_message(chat_id, "Press a button!", reply_markup: kb)

# Listen for a button press with that specific payload
bot.callback_query("button-payload") { |ctx| ... }

!!! note Always remember to call answer_callback_query, even if you don't do anything with it. crystal bot.callback_query("button-payload") do |ctx| ctx.answer_callback_query end

If you pass an array of triggers, your middleware will be called when at least one of them matches.


[View source]
def command(command, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers a middleware that will be called when a certain command is found:

# reacts to `/start` commands
bot.command("start") { |ctx| ... }

# reacts to `/help` commands
bot.command("help") { |ctx| ... }

The rest of the text is provided as ctx.match.

!!! note Commands are only matched at the beginning of a message. To match a command inside of a message you could use bot.on(:bot_command) in conjunction with the #hears handler.

!!! note By default commands are detected in channel posts too, which means ctx.message may be undefined. You should always use ctx.msg instead to grab both group and channel messages.


[View source]
def command(command, *middleware : AcceptableMiddleware) #

Registers a middleware that will be called when a certain command is found:

# reacts to `/start` commands
bot.command("start") { |ctx| ... }

# reacts to `/help` commands
bot.command("help") { |ctx| ... }

The rest of the text is provided as ctx.match.

!!! note Commands are only matched at the beginning of a message. To match a command inside of a message you could use bot.on(:bot_command) in conjunction with the #hears handler.

!!! note By default commands are detected in channel posts too, which means ctx.message may be undefined. You should always use ctx.msg instead to grab both group and channel messages.


[View source]
def command(command, &block : MiddlewareFn) #

Registers a middleware that will be called when a certain command is found:

# reacts to `/start` commands
bot.command("start") { |ctx| ... }

# reacts to `/help` commands
bot.command("help") { |ctx| ... }

The rest of the text is provided as ctx.match.

!!! note Commands are only matched at the beginning of a message. To match a command inside of a message you could use bot.on(:bot_command) in conjunction with the #hears handler.

!!! note By default commands are detected in channel posts too, which means ctx.message may be undefined. You should always use ctx.msg instead to grab both group and channel messages.


[View source]
def drop(predicate : Filter::Predicate, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers middleware behind a custom filter function that operates on the context object and determines whether or not to contuinue middleware execution. In other words, the middleware following this one in a chain will only be called if the filter function returns false. This is functionally the opposite of Composer#filter.

my_filter = ->(ctx : Context) { ctx.text == "hello" }
bot.on(:text).drop(my_filter) do |ctx|
  # Will only be called if `ctx.text` is not "hello"
  # ...
end

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def drop(predicate, *middleware : AcceptableMiddleware) #

Registers middleware behind a custom filter function that operates on the context object and determines whether or not to contuinue middleware execution. In other words, the middleware following this one in a chain will only be called if the filter function returns false. This is functionally the opposite of Composer#filter.

my_filter = ->(ctx : Context) { ctx.text == "hello" }
bot.on(:text).drop(my_filter) do |ctx|
  # Will only be called if `ctx.text` is not "hello"
  # ...
end

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def drop(middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware, &predicate : Filter::Predicate) #

[View source]
def drop(*middleware : AcceptableMiddleware, &predicate : Filter::Predicate) #

[View source]
def error_boundary(error_handler : ErrorHandler, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Installs an error boundary that catches errors that happen inside the given middleware. This allows you to insert custom error handlers into the pipeline for some parts of your bot, while leaving others untouched.

error_handler = ->(err : BotError) do
  puts "Error boundary caught error!"
  puts err
end

# All passed middleware will be protected by the error boundary.
safe = bot.error_boundary(error_handler, middleware0, middleware1, middleware2)

# This will also be protected
safe.on(:message, middleware3)

# No error from middleware4 will reach the `error_handler` from above.

Do nothing on error, and run outside middleware
suppress = ->(err : BotError) { }
safe.error_boundary(suppress).on(:edited_message, middleware4)

[View source]
def error_boundary(error_handler : ErrorHandler, *middleware : AcceptableMiddleware) #

Installs an error boundary that catches errors that happen inside the given middleware. This allows you to insert custom error handlers into the pipeline for some parts of your bot, while leaving others untouched.

error_handler = ->(err : BotError) do
  puts "Error boundary caught error!"
  puts err
end

# All passed middleware will be protected by the error boundary.
safe = bot.error_boundary(error_handler, middleware0, middleware1, middleware2)

# This will also be protected
safe.on(:message, middleware3)

# No error from middleware4 will reach the `error_handler` from above.

Do nothing on error, and run outside middleware
suppress = ->(err : BotError) { }
safe.error_boundary(suppress).on(:edited_message, middleware4)

[View source]
def error_boundary(error_handler : ErrorHandler, &middleware : MiddlewareFn) #

Installs an error boundary that catches errors that happen inside the given middleware. This allows you to insert custom error handlers into the pipeline for some parts of your bot, while leaving others untouched.

error_handler = ->(err : BotError) do
  puts "Error boundary caught error!"
  puts err
end

# All passed middleware will be protected by the error boundary.
safe = bot.error_boundary(error_handler, middleware0, middleware1, middleware2)

# This will also be protected
safe.on(:message, middleware3)

# No error from middleware4 will reach the `error_handler` from above.

Do nothing on error, and run outside middleware
suppress = ->(err : BotError) { }
safe.error_boundary(suppress).on(:edited_message, middleware4)

[View source]
def filter(predicate : Filter::Predicate, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers middleware behind a custom filter function that operates on the context object and determines whether or not to contuinue middleware execution. In other words, the middleware following this one in a chain will only be called if the filter function returns true.

my_filter = ->(ctx : Context) { ctx.text == "hello" }
bot.on(:text).filter(my_filter) do |ctx|
  # Will only be called if `ctx.text` is "hello"
  # ...
end

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def filter(predicate, *middleware : AcceptableMiddleware) #

Registers middleware behind a custom filter function that operates on the context object and determines whether or not to contuinue middleware execution. In other words, the middleware following this one in a chain will only be called if the filter function returns true.

my_filter = ->(ctx : Context) { ctx.text == "hello" }
bot.on(:text).filter(my_filter) do |ctx|
  # Will only be called if `ctx.text` is "hello"
  # ...
end

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def filter(middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware, &predicate : Filter::Predicate) #

Registers middleware behind a custom filter function that operates on the context object and determines whether or not to contuinue middleware execution. In other words, the middleware following this one in a chain will only be called if the filter function returns true.

my_filter = ->(ctx : Context) { ctx.text == "hello" }
bot.on(:text).filter(my_filter) do |ctx|
  # Will only be called if `ctx.text` is "hello"
  # ...
end

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def filter(*middleware : AcceptableMiddleware, &predicate : Filter::Predicate) #

Registers middleware behind a custom filter function that operates on the context object and determines whether or not to contuinue middleware execution. In other words, the middleware following this one in a chain will only be called if the filter function returns true.

my_filter = ->(ctx : Context) { ctx.text == "hello" }
bot.on(:text).filter(my_filter) do |ctx|
  # Will only be called if `ctx.text` is "hello"
  # ...
end

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def fork(middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers some middleware that run concurrently with other middleware in the stack.

bot.use(...) # Will run first
bot.fork(...) # Will start second, but will run in the background
bot.use(...) # Will also run second

Forking is functionally the same as running your middleware in a spawn block, but also applies to all the middleware that follow it in the chain.


[View source]
def fork(*middleware : AcceptableMiddleware) #

Registers some middleware that run concurrently with other middleware in the stack.

bot.use(...) # Will run first
bot.fork(...) # Will start second, but will run in the background
bot.use(...) # Will also run second

Forking is functionally the same as running your middleware in a spawn block, but also applies to all the middleware that follow it in the chain.


[View source]
def fork(&block : MiddlewareFn) #

Registers some middleware that run concurrently with other middleware in the stack.

bot.use(...) # Will run first
bot.fork(...) # Will start second, but will run in the background
bot.use(...) # Will also run second

Forking is functionally the same as running your middleware in a spawn block, but also applies to all the middleware that follow it in the chain.


[View source]
def game_query(trigger, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers some middleware for game queries (updates that Telegram sends when a user clicks an inline button to launch an HTML5 game).

This is functionally the same as Composer#callback_query, but it only matches when a game_short_name is provided.


[View source]
def game_query(trigger, *middleware : AcceptableMiddleware) #

Registers some middleware for game queries (updates that Telegram sends when a user clicks an inline button to launch an HTML5 game).

This is functionally the same as Composer#callback_query, but it only matches when a game_short_name is provided.


[View source]
def game_query(trigger, &block : MiddlewareFn) #

Registers some middleware for game queries (updates that Telegram sends when a user clicks an inline button to launch an HTML5 game).

This is functionally the same as Composer#callback_query, but it only matches when a game_short_name is provided.


[View source]
def hears(trigger, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers a #middleware that is executed when the message contains a specific pattern. You can use either a regular expression, a string, or an array containing either:

# Match some exact text
bot.hears("Crystal is great") { |ctx| ... }

# Match a regular expression
bot.hears(/crystal/i) { |ctx| ... }

Passing multiple filters in an array works as an OR, while chaining multiple #on calls together works as an AND.


[View source]
def hears(trigger, *middleware : AcceptableMiddleware) #

Registers a #middleware that is executed when the message contains a specific pattern. You can use either a regular expression, a string, or an array containing either:

# Match some exact text
bot.hears("Crystal is great") { |ctx| ... }

# Match a regular expression
bot.hears(/crystal/i) { |ctx| ... }

Passing multiple filters in an array works as an OR, while chaining multiple #on calls together works as an AND.


[View source]
def hears(trigger, &block : MiddlewareFn) #

Registers a #middleware that is executed when the message contains a specific pattern. You can use either a regular expression, a string, or an array containing either:

# Match some exact text
bot.hears("Crystal is great") { |ctx| ... }

# Match a regular expression
bot.hears(/crystal/i) { |ctx| ... }

Passing multiple filters in an array works as an OR, while chaining multiple #on calls together works as an AND.


[View source]
def inline_query(trigger, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers some middleware for inline queries (updates that Telegram sends when a user types "@YourBotName ..." into a text field). Your bot will receive the inline query and can respond with a number of different results. Check out https://core.telegram.org/bots/inline to read more about inline queries.

!!! note You have to enable inline mode in @BotFather

bot.inline_query("query") do |ctx|
  # Answer the inline query, confer https://core.telegram.org/bots/api#answerinlinequery
  ctx.answer_inline_query(...)
end

[View source]
def inline_query(trigger, *middleware : AcceptableMiddleware) #

Registers some middleware for inline queries (updates that Telegram sends when a user types "@YourBotName ..." into a text field). Your bot will receive the inline query and can respond with a number of different results. Check out https://core.telegram.org/bots/inline to read more about inline queries.

!!! note You have to enable inline mode in @BotFather

bot.inline_query("query") do |ctx|
  # Answer the inline query, confer https://core.telegram.org/bots/api#answerinlinequery
  ctx.answer_inline_query(...)
end

[View source]
def inline_query(trigger, &block : MiddlewareFn) #

Registers some middleware for inline queries (updates that Telegram sends when a user types "@YourBotName ..." into a text field). Your bot will receive the inline query and can respond with a number of different results. Check out https://core.telegram.org/bots/inline to read more about inline queries.

!!! note You have to enable inline mode in @BotFather

bot.inline_query("query") do |ctx|
  # Answer the inline query, confer https://core.telegram.org/bots/api#answerinlinequery
  ctx.answer_inline_query(...)
end

[View source]
def lazy(factory : MiddlewareFactory) #

Executes some middleware that are generated on the fly for each context. Pass a factory function which generates a middleware (or an array of middleware) for the given context.

bot.lazy { |ctx| create_some_middleware(ctx) }

You can also return an empty array ([] of Middleware) if you don't want to run any middleware for the given context. This is the same as returning an empty Composer object.

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def lazy(&block : MiddlewareFactory) #

Executes some middleware that are generated on the fly for each context. Pass a factory function which generates a middleware (or an array of middleware) for the given context.

bot.lazy { |ctx| create_some_middleware(ctx) }

You can also return an empty array ([] of Middleware) if you don't want to run any middleware for the given context. This is the same as returning an empty Composer object.

!!! note This is a more advanced function and is not needed for most use cases.


[View source]
def middleware : Telegram::Middleware #

Returns the middleware that make up this Composer.


[View source]
def on(filter, middleware : Array(AcceptableMiddleware) = [] of AcceptableMiddleware) #

Registers #middleware that will only be executed when certain UpdateActions are included in an update. The #filter specifies which actions you want to act on.

For example:

# All message updates
bot.on(:message) { |ctx| ... }

# Only messages containing text or a caption
bot.on([:text, :caption]) { |ctx| ... }

# Only text messages with a URL
bot.on(:text).on(:url) { |ctx| ... }

# Messages containing a photo
bot.on(:photo) { |ctx| ... }

As can be seen in the above example, passing multiple filters in an array works as an OR, while chaining multiple #on calls together works as an AND.


[View source]
def on(filter, *middleware : AcceptableMiddleware) #

Registers #middleware that will only be executed when certain UpdateActions are included in an update. The #filter specifies which actions you want to act on.

For example:

# All message updates
bot.on(:message) { |ctx| ... }

# Only messages containing text or a caption
bot.on([:text, :caption]) { |ctx| ... }

# Only text messages with a URL
bot.on(:text).on(:url) { |ctx| ... }

# Messages containing a photo
bot.on(:photo) { |ctx| ... }

As can be seen in the above example, passing multiple filters in an array works as an OR, while chaining multiple #on calls together works as an AND.


[View source]
def on(filter, &block : MiddlewareFn) #

Registers #middleware that will only be executed when certain UpdateActions are included in an update. The #filter specifies which actions you want to act on.

For example:

# All message updates
bot.on(:message) { |ctx| ... }

# Only messages containing text or a caption
bot.on([:text, :caption]) { |ctx| ... }

# Only text messages with a URL
bot.on(:text).on(:url) { |ctx| ... }

# Messages containing a photo
bot.on(:photo) { |ctx| ... }

As can be seen in the above example, passing multiple filters in an array works as an OR, while chaining multiple #on calls together works as an AND.


[View source]
def route(router : RouterFn, handlers : Hash(String, AcceptableMiddleware), fallback : AcceptableMiddleware = Composer.pass) #

This allows you to branch between different middleware per context object in much the same way you would branch between HTTP routes in a traditional web framework. You can pass three things to it:

  1. a routing function.
  2. a hash of handlers.
  3. a fallback middleware.

The routing function decides based on the context object what middleware to run. Each middleware is idenfied by a key, so the routing function simply returns the key of the middleware to run.

route_handlers = {
  "even_updates" => ->(ctx : Context) { ... },
  "odd_updates" => ->(ctx : Context) { ... },
}

router = ->(ctx : Telegram::Context) do
  if ctx.update.update_id.even?
    "even_updates"
  else
    "odd_updates"
  end
end

bot.route(router, route_handlers)

If a fallback is provided as a third argument, it will be run if the routing function returns nil or if the provided key doesn't match.


[View source]
def route(handlers : Hash(String, AcceptableMiddleware), fallback : AcceptableMiddleware = Composer.pass, &router : RouterFn) #

This allows you to branch between different middleware per context object in much the same way you would branch between HTTP routes in a traditional web framework. You can pass three things to it:

  1. a routing function.
  2. a hash of handlers.
  3. a fallback middleware.

The routing function decides based on the context object what middleware to run. Each middleware is idenfied by a key, so the routing function simply returns the key of the middleware to run.

route_handlers = {
  "even_updates" => ->(ctx : Context) { ... },
  "odd_updates" => ->(ctx : Context) { ... },
}

router = ->(ctx : Telegram::Context) do
  if ctx.update.update_id.even?
    "even_updates"
  else
    "odd_updates"
  end
end

bot.route(router, route_handlers)

If a fallback is provided as a third argument, it will be run if the routing function returns nil or if the provided key doesn't match.


[View source]
def run(middleware : Middleware, context : Context) #

Run the given #middleware with the given context.


[View source]
def use(middleware : Array(AcceptableMiddleware)) #

Register the given #middleware with the current Composer. Registered middleware reveive all updates and are contactenated into a single Middleware.

This can be used to easily register new plugins:

bot.use(SomeTelegramPlugin.new)

This returns a new instance of Composer which can be further extended.


[View source]
def use(*middleware : AcceptableMiddleware) #

Register the given #middleware with the current Composer. Registered middleware reveive all updates and are contactenated into a single Middleware.

This can be used to easily register new plugins:

bot.use(SomeTelegramPlugin.new)

This returns a new instance of Composer which can be further extended.


[View source]
def use(&proc : MiddlewareFn) #

Register the given #middleware with the current Composer. Registered middleware reveive all updates and are contactenated into a single Middleware.

This can be used to easily register new plugins:

bot.use(SomeTelegramPlugin.new)

This returns a new instance of Composer which can be further extended.


[View source]