---
title: v2.0.0
---
+ It became easier to create simple inline keyboards. You can now pass a string as button, which will be used as text and callback_data:
```python
keyboard = ikb([
    ["Earth", "Mars", "Venus"],
    ["Saturn", "Jupyter"]
])
```
+ Added support for listening for buttons. You can use await `Message.wait_for_click()` to await for any click on any button on that message. By passing an `user_id` into the parameter `from_user_id`, you can restrict from which users should the bot accept the click. Any other user will see an alert (_i.e. query.answer()_) informing they are not supposed to click the button. You can pass a custom text to the param `alert` or can pass False to disable it, so no text will be shown at all.
![image](https://user-images.githubusercontent.com/29507335/213792781-2b6aa7de-ce0f-4d98-915e-7d46246e1bb3.png)

+ To support both messages and callback queries, a parameter named `listener_type` has been added. Its value must be one of the enum `pyromod.ListenerTypes`
+ The method _cancel_listener_ has been renamed to `stop_listening`
+ Pyromod now uses "identifiers" in all its methods instead of the good old chat_id. Identifiers are basically tuples with ordered data about the messages that the listeners should listen for. It must follow this order: `(chat_id, user_id, message_id)`. If you pass None as value, it will act as a wildcard, matching any value. Examples:
  - `(1029384756, 276145711, None)` means "any message sent by 276145711 in the chat 1029384756"
  - `(1029384756, None, None)` means "any message sent in the chat 1029384756, from any user"
  - `(None, 276145711, None)` means "any message from 276145711 on any chat"
  - message_id is only used when waiting for button clicks:
    - `(1029384756, 276145711, 1123)` means "a click done by 276145711 on a button on the message 1123 on the chat 1029384756"
    - `(1029384756, None, 1123)` means "a click on a button on the message 1123 on the chat 1029384756, done by any user"
The listeners are used as patterns for matching against the received update data. A listener with the identifier/pattern `(1029384756, 276145711, None)` will match if the message data is `(1029384756, 276145711, 728)`, but won't do if the message data is `(1029384756, 200097591, 729)`, because the identifier specified which user_id it wants and it′s not matching.
If you use mostly the bound methods, you don′t need to worry about it, since they will automatically compose the identifier for you.
  - m.chat.listen will compose `(m.chat.id, None, None)`
  - m.from_user.listen will compose `(None, m.from_user.id, None)`
  - m.from_user.ask will compose `(m.from_user.id, m.from_user.id, None)`
  - m.wait_for_click(from_user_id=276145711) will compose `(m.chat.id, 276145711, m.id)`

    Be aware that User.listen does not create a listener for messages sent by the user on the chat of the message. If using `User.ask`, it asks on the private chat with the user and creates a listener for messages sent by the user on the private conversation. This may change in the future to whatever makes more sense. If using `User.listen`, it will listen for any message from that user, anywhere.
+ Pyromod now raises `pyromod.ListenerTimeout(timeout)` instead of _asyncio.TimeoutError_
+ New exception `pyromod.ListenerStopped`. It raises when any listener is stopped by `Client.stop_listening`
+ New class `pyromod.PyromodConfig`. It′s a class with some static properties that hold some tweaks and error handlers.
  - `timeout_handler`: a callback that gets executed instead of raising _asyncio.TimeoutError_ (now `pyromod.ListenerTimeout`). It receives `(identifier, listener_data, timeout)` as arguments.
  - `stopped_handler`: a callback that gets executed when a listener is stopped by `stop_listening()`
  - `throw_exceptions`: Boolean, defaults to True. If False, pyromod won't raise none of `pyromod.ListenerTimeout` and `pyromod.ListenerStopped`. The functions should just return None instead.
  - `unallowed_user_alert`: Boolean, defaults to True. If False, no text will be responded to unwanted user clicks.
  - `unallowed_user_alert_text`: the default text for unallowed user alerts.
![image](https://user-images.githubusercontent.com/29507335/213794327-1bcce3d0-6e01-453a-b529-529833821173.png)

+ Before this release, pyromod would block other commands to work if there is a listener on the chat, but the filters doesn't match (i.e. a listener with filters.photo, but the message is just text). Now, if there′s a listener but the filters doesn't match, the message will keep propagating to other handlers. The same happens when you use a restrictive identifier, specifying the user_id. In this case, all other users will still be able to use the bot, while the bot keeps awaiting for a message from the wanted user. As it should be.
+ The parameters for `stop_listening` (formerly cancel_listener) has changed, due to the addition of identifiers. Before this release, you could pass the chat_id of the listener to stop and delete it. Now, with identifiers, you can either pass a message data as `(chat_id, user_id, message_id)` to stop the first matching listener or pass a `identifier_pattern` to stop the first matching listeners.

    To explain the difference between these two, let′s suppose you want to stop a listener that has `(1029384756, 276145711, 19876)` as identifier, but you only know the chat_id, so you can't pass the message data to match.
In this case, you can pass `identifier_pattern=(1029384756, None, None)`.
This also may change in the near future, I honestly don't know why there is two ways, where identifier_pattern could handle both situations. I was sleepy.
![image](https://user-images.githubusercontent.com/29507335/213794923-efde145c-ad64-4515-a392-04f21c9875e0.png)

**Full Changelog**: https://github.com/usernein/pyromod/compare/v1.5...v2.0.0