Skip to content

Commit

Permalink
release v3.0 (#33)
Browse files Browse the repository at this point in the history
* feat: remove identifier of listen() and ask() in favor of new kwargs

* refactor: split classes into new files, extend from original classes

* refactor: rename patch -> patch_into, patchable -> should_patch

* style: format with ruff

* feat(precedence): return most specific listener

* refactor!: rename get_single_listener -> get_matching_listener, get_many_listeners -> get_many_matching_listeners

* refactor: improve variable names and only remove checking attribute from null listener

* refactor: transform config.py into package pyromod.config

* chore!: remove handlers from pyromod exports

* docs: bring new docs with docsify

* docs: update README

* Create CNAME

* Delete CNAME

* docs: add missing backtick

* Create CNAME

* fix: fix order of positional args in bound method ask

* refactor(ask)!: rename request to sent_message

* feat(ask): send message only if text is not empty

* refactor: search for message_id (pyrogram v1) if message.id is not found

* refactor: only use query.message.chat.id if both message and chat are not null

* supports pyrogram v1 (#11)

* supports pyrogram v1

* Update listen.py

forgot, delete comma

---------

Co-authored-by: Cezar H <[email protected]>

* request to sent_message (#13)

I think using the sent_message statement instead of request is much more descriptive

* ask function will behave like listen if text is empty (#14)

Co-authored-by: Cezar H <[email protected]>

* Update for filters and if from_user is None (#17)

* Update listen.py

* Update listen.py

* Update utils.py

* Changes to be committed:
	modified:   pyromod/listen/listen.py
	modified:   pyromod/utils/utils.py

* Changes to be committed:
	modified:   pyromod/utils/utils.py

* Changes to be committed:
	modified:   pyromod/utils/utils.py

* Changes to be committed:
	modified:   pyromod/utils/utils.py

* Update utils.py

* Update utils.py

* Update utils.py

* Update utils.py

* Update utils.py

* Update utils.py

* Update utils.py

* Update utils.py

* Changes to be committed:
	modified:   pyromod/listen/listen.py

* Update pyproject.toml

* Update poetry.lock

* Update poetry.lock

* Update pyproject.toml

* Update listen.py

* Update listen.py

---------

Co-authored-by: Cezar H <[email protected]>

* feat: remove identifier of listen() and ask() in favor of new kwargs

* refactor: split classes into new files, extend from original classes

* refactor: rename patch -> patch_into, patchable -> should_patch

* refactor: rename patch -> patch_into

* style: format with ruff

* chore: remaking many changes that got undone after the merge

* refactor!: rename get_single_listener -> get_matching_listener, get_many_listeners -> get_many_matching_listeners

* feat(precedence): return most specific listener

* fix: replace @should_patch -> @should_patch()

* fix: use Dict and List from typing instead of builtin

---------

Co-authored-by: Tofik Denianto <[email protected]>
Co-authored-by: Eikosa <[email protected]>
Co-authored-by: Jusidama Bot <[email protected]>
  • Loading branch information
4 people authored Oct 29, 2023
1 parent e3981fe commit 9dbb50e
Show file tree
Hide file tree
Showing 50 changed files with 1,633 additions and 679 deletions.
207 changes: 105 additions & 102 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,148 +1,151 @@
# pyromod

[![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=flat&logo=telegram&logoColor=white)](https://t.me/pyromodchat)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/usernein/pyromod)
[![Downloads](https://static.pepy.tech/badge/pyromod)](https://pepy.tech/project/pyromod)
[![Downloads](https://static.pepy.tech/badge/pyromod/month)](https://pepy.tech/project/pyromod)

A monkeypatcher add-on for Pyrogram which does conversation handling and other cool stuff.
pyromod is a versatile Python add-on for the Pyrogram library, designed to make developing Telegram bots faster and more
efficient.

In other words, it is a compilation of utilities i developed for improving my personal experience with Pyrogram.
It works **together** with Pyrogram, it is **not** a fork/modded version. It does monkeypatching to add features to Pyrogram classes on the go (so i don't need to update on every Pyrogram's release).
It's based on **monkeypatching**, which means it works together with Pyrogram, rather than being a fork or modified
version. It
adds features to Pyrogram classes on the go, so you don't need to update it every time Pyrogram is updated.

## Usage
Import `pyromod` one time in your script and you'll already be able to use the modified pyrogram in all your handlers. Example:
```python
# config.py
import pyromod
from pyrogram import Client
Whether you're building a simple chatbot or a complex form to get multiple responses from the user, pyromod has you
covered. It enhances Pyrogram with a range
of advanced features, simplifies conversation handling, and offers a high degree of customizability.

app = Client('my_session')
```
## Key Features

Then you can, from another file, do `from config import app` to import the modded Pyrogram Client we created above. It will be modded globally.
- **Effortless Bot Development:** pyromod streamlines the process of building conversational Telegram bots, saving you
time and effort
during development.

All the patches are applied automatically as soon as pyromod is imported.
- **Advanced Conversation Management:** Managing conversations with users is made easier, allowing you to create dynamic
and interactive interactions much easier, without having to save states anywhere, by leveraging the power of
async/await syntax.

## Methods
All pyromod methods are callable by any of these ways:
- `await Client.<method>(identifier, ...)`
- `await Chat.<method>()`
- `await User.<method>()`
- **Effortless Inline Keyboards Creation:** Creating inline keyboards is easier than ever with pyromod's inline keyboard
helper functions.

In the last two, Pyrogram automatically gets the ids from the object, to compound the `identifier` tuple that `Client.listen` uses.
- **User-Friendly Pagination:** Enhance the user experience by providing easy navigation tools with the pyromod's
pagination
helpers.

These are the methods pyromod adds:
- `listen(identifier, filters=None, listener_type=ListenerTypes.MESSAGE, timeout=None, unallowed_click_alert=True)`
Awaits for a new message in the specified chat and returns its Message object. If listener_type is set to `ListenerTypes.CALLBACK_QUERY`, it awaits and returns a CallbackQuery object.
You can pass Update Filters to the `filters` parameter just like you do for the update handlers. e.g. `filters=filters.photo & filters.bot`
`identifier` is a tuple containing, in this exact order, (chat_id, user_id, message_id). It lets you specify exactly which update you want. You don't need to worry about that if you mostly use the bound methods.
`unnalowed_click_alert` is the text that users will see in an alert when the button is not waiting for them to click. If True, it uses the default text at `PyromodConfig.unnalowed_click_alert_text`. If False, no text is shown.
- **Highly Customizable:** pyromod's configuration options let you customize its behavior to meet your specific project
requirements.

- `ask(text, identifier, filters=None, listener_type=ListenerTypes.MESSAGE, timeout=None, unallowed_click_alert=True)`
Same as `listen`, but sends a message to identifier[0] before and only then waits for a response.
You can additionally pass any of the `Client.send_message()` parameters. Check the example below.
The object of the sent message is returned inside of the attribute `request`
## Examples

**Awaiting a single message from a specific chat:**

Example:
```python
answer = await message.chat.ask('*Send me your name:*', parse_mode=enums.ParseMode.MARKDOWN)
await answer.request.edit_text("Name received!")
await answer.reply(f'Your name is: {answer.text}', quote=True)
response = await client.listen(chat_id=chat_id)
```

- `Message.wait_for_click(from_user_id=None, timeout=None, filters=None, alert=True)`
Awaits from a click on any button on the Message object. If `from_user_id` is passed, pyromod will wait for a click of that user.
If you pass any text to `alert`, it will be shown to any other user. If `alert` is True, it will use the default text. If False, no text will be shown.
**Awaiting a single message from a specific user in a specific chat:**

## `pyromod.helpers`
Tools for creating inline keyboards a lot easier.
```python
response = await client.listen(chat_id=chat_id, user_id=user_id)
```

### `pyromod.helpers.ikb`
**Asking the user a question then await for the response:**

Creates a inline keyboard.
Its first and only argument is a list (the keyboard itself) containing lists (the lines) of buttons, which can be lists or tuples. I use tuples to avoid a mess with a lot of brackets. Tuples makes it easier to read.
```python
response = await client.ask(chat_id=chat_id, text='What is your name?')
```

The button syntax is very simple: `(TEXT, VALUE, TYPE)`, with TYPE being any existent button type (e.g. `url`) and VALUE is its value. If you omit the type, it will be considered as a callback button.
If you pass only a string as button, it will be used as text and callback_data for the InlineKeyboardButton.
This syntax will be automagically converted by pyromod.
**Asking the user a question then await for the response, with a timeout:**

Examples:
```python
from pyromod.helpers import ikb
...
keyboard = ikb([
[('Button 1', 'call_1'), ('Button 2', 'call_2')],
[('Another button', 't.me/pyromodchat', 'url')]
])
await message.reply('Easy inline keyboard', reply_markup=keyboard)
try:
response = await client.ask(chat_id=chat_id, text='What is your name?', timeout=10)
except ListenerTimeout:
await message.reply('You took too long to answer.')
```

**Full handler example, getting user's name and age with bound method Chat.ask:**

```python
from pyromod import Client, Message
from pyrogram import filters


@Client.on_message(filters.command('form'))
async def on_form(client: Client, message: Message):
chat = message.chat

name = await chat.ask('What is your name?', filters=filters.text)
age = await chat.ask('What is your age?', filters=filters.text)

await message.reply(f'Your name is {name.text} and you are {age.text} years old.')
```

**Easier inline keyboard creation:**

```python
from pyromod.helpers import ikb

keyboard = ikb([
["Mars", "Earth", "Venus"],
["Saturn", "Jupyter"]
[('Button 1', 'callback_data_1'), ('Button 2', 'callback_data_2')],
[('Another button', 't.me/pyromodchat', 'url')]
])
await message.reply("Easiest inline keyboard", reply_markup=keyboard)
```

- `pyromod.helpers.array_chunk`
Chunk the elements of a list into small lists. i.e. [1, 2, 3, 4] can become [[1,2], [3,4]]. This is extremely useful if you want to build a keyboard dinamically with more than 1 column. You just put all buttons together in a list and run:
```python
lines = array_chunk(buttons, 2)
keyboard = ikb(lines)
## Installation

To get started with pyromod, you can install it using pip:

```bash
pip install pyromod
```
This will generate a list of lines with 2 buttons on each one.

### `pyromod.nav`
Tools for creating navigation keyboards.
Or poetry:

- `pyromod.nav.Pagination`
Creates a full paginated keyboard. Usage:
```python
from pyrogram import Client, filters
from pyromod.nav import Pagination
from pyromod.helpers import ikb
```bash
poetry add pyromod
```

Or rye:

def page_data(page):
return f'view_page {page}'
def item_data(item, page):
return f'view_item {item} {page}'
def item_title(item, page):
return f'Item {item} of page {page}'

@Client.on_message(filters.regex('/nav'))
async def on_nav(c,m):
objects = [*range(1,100)]
page = Pagination(
objects,
page_data=page_data, # callback to define the callback_data for page buttons in the bottom
item_data=item_data, # callback to define the callback_data for each item button
item_title=item_title # callback to define the text for each item button
)
index = 0 # in which page is it now? (used to calculate the offset)
lines = 5 # how many lines of the keyboard to include for the items
columns = how many columns include in each items' line
kb = page.create(index, lines, columns)
await m.reply('Test', reply_markup=ikb(kb))
```bash
rye add pyromod
```

## pyromod.PyrogramConfig
It lets you do some tweaks on pyromod behavior.
## Initialization

To initialize pyromod, on the file that creates the client instance, simply import the Client class from pyromod instead
of pyrogram:

```python
class PyromodConfig:
timeout_handler = None
stopped_handler = None
throw_exceptions = True
unallowed_click_alert = True
unallowed_click_alert_text = (
"[pyromod] You're not expected to click this button."
)
from pyromod import Client
```
`timeout_handler` and `stopped_handler` are callbacks that receive (identifier, listener_data) as arguments. timeout_handler receives an extra arg `timeout`. When they are in use, pyromod won't throw the exceptions ListenerStopped and ListenedTimeout.

And that's all! You can still use the `Client` class as you would normally do with Pyrogram, but now having all the
extra features.

You don't need to change the imports on the plugins files. Even by importing `Client` from pyrogram, the pyromod
features will be available anyway. In order to monkeyatch pyromod features successfully, it's just required that the
first `Client` class imported to your project code should be from pyromod. Then all the other future `Client` instances
will be patched automatically.

You just need to import from pyromod if you want your IDE to recognize and suggest
the extra features based on `pyromod.Client` type.

## Contributing

We welcome contributions from the community to make pyromod even better.

Feel free to open issues, submit pull requests,
or contribute in any way that aligns with our goals.

### Copyright & License

This project may include snippets of Pyrogram code
- Pyrogram - Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022 Dan <<https://github.com/delivrance>>

- Pyrogram - Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022
Dan <<https://github.com/delivrance>>

Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)

Expand Down
Empty file added docs/.nojekyll
Empty file.
1 change: 1 addition & 0 deletions docs/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyromod.pauxis.dev
37 changes: 37 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# pyromod

[![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=flat&logo=telegram&logoColor=white)](https://t.me/pyromodchat)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/usernein/pyromod)
[![Downloads](https://static.pepy.tech/badge/pyromod)](https://pepy.tech/project/pyromod)
[![Downloads](https://static.pepy.tech/badge/pyromod/month)](https://pepy.tech/project/pyromod)

pyromod is a versatile Python add-on for the Pyrogram library, designed to make developing Telegram bots faster and more
efficient.

It's based on **monkeypatching**, which means it works together with Pyrogram, rather than being a fork or modified
version. It
adds features to Pyrogram classes on the go, so you don't need to update it every time Pyrogram is updated.

Whether you're building a simple chatbot or a complex form to get multiple responses from the user, pyromod has you
covered. It enhances Pyrogram with a range
of advanced features, simplifies conversation handling, and offers a high degree of customizability.

## Key Features

- **Effortless Bot Development:** pyromod streamlines the process of building conversational Telegram bots, saving you
time and effort
during development.

- **Advanced Conversation Management:** Managing conversations with users is made easier, allowing you to create dynamic
and interactive interactions much easier, without having to save states anywhere, by leveraging the power of
async/await syntax.

- **Effortless Inline Keyboards Creation:** Creating inline keyboards is easier than ever with pyromod's inline keyboard
helper functions.

- **User-Friendly Pagination:** Enhance the user experience by providing easy navigation tools with the pyromod's
pagination
helpers.

- **Highly Customizable:** pyromod's configuration options let you customize its behavior to meet your specific project
requirements.
30 changes: 30 additions & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!-- docs/_sidebar.md -->

- **Home**
- [Introduction](/#pyromod)
- [Key features](/#key-features)
- **Get Started**
- [Installation](/get-started/installation.md)
- [Initializing pyromod](/get-started/initialization.md)
- [Configuration](/get-started/configuration.md)
- [Examples](/get-started/examples.md)
- **API Reference**
- [pyromod](/pyromod/index)
- [pyromod.config](/pyromod/config/index)
- pyromod.listen
- [Client](/pyromod/listen/client)
- [Message](/pyromod/listen/message)
- [User](/pyromod/listen/user)
- [Chat](/pyromod/listen/chat)
- pyromod.exceptions
- [ListenerTimeout](/pyromod/exceptions/listener-timeout)
- [ListenerStopped](/pyromod/exceptions/listener-stopped)
- [pyromod.helpers](/pyromod/helpers/index)
- pyromod.types
- [Identifier](/pyromod/types/identifier)
- [ListenerTypes](/pyromod/types/listener-types)
- [Listener](/pyromod/types/listener)
- pyromod.utils
- [pyromod.utils.patch](/pyromod/utils/patch)
- pyromod.nav
- [Pagination](/pyromod/nav/pagination)
10 changes: 10 additions & 0 deletions docs/cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# pyromod <small>3.0.0</small>

> A collection of monkeypatched tools for Pyrogram.
- Simplifies bot development with Pyrogram.
- Advanced features for conversation management.
- Many tools that enhance the user experience.

[GitHub](https://github.com/usernein/pyromod)
[Get Started](#pyromod)
Loading

0 comments on commit 9dbb50e

Please sign in to comment.