from pyrogram import Client, filters, idle, types
from pyrolistener import Listener

app = Client("name")
app.start()
listener = Listener(client=app)

@app.on_message(filters.photo)
async def _(c: Client, m: types.Message):
    await m.reply_photo(m.photo.file_id, caption="are you sure to add an button to the photo? send Y/N")
    msg = await listener.listen(
        m.chat.id,
        from_id=m.from_user.id,
        filters=["text"]
    )
    if msg.text.lower() == "y":
        return await msg.reply_photo(
            m.photo.file_id,
            reply_markup=types.InlineKeyboardMarkup(
                [[types.InlineKeyboardButton("button", "callback")]]
            )
        )
    elif msg.text.lower() == "n":
        return await msg.reply("Okay")
    else:
        return await msg.reply("Wrong choice")

print(app.me)
idle()

examples/advanced_listener.py
x72x/pyro-listener
from pyrogram import Client, filters, types, idle
from pyrolistener import Listener

app = Client("name")

app.start()

listener = Listener(client=app, show_output=True)

@app.on_message(filters.command("start"))
async def _(c: Client, m: types.Message):
    await m.reply(
        "Choose :",
        reply_markup=types.InlineKeyboardMarkup(
            [
                [
                    types.InlineKeyboardButton("Enter name", callback_data="name"),
                    types.InlineKeyboardButton("Enter age", callback_data="age")
                ],
            ]
        ),
        quote=True
    )

@app.on_callback_query()
async def __(c: Client, m: types.CallbackQuery):
    if m.data == "name":
        msg = await listener.listen_to(
            m,
            "Send your name now",
            filters=["text"]
        )
        return await msg.reply(f"Your name is : {msg.text}")

    if m.data == "age":
        msg = await listener.listen_to(
            m,
            "Send your age now",
            filters=["text"]
        )
        return await msg.reply(f"Your age is : {msg.text}")
# print(app.me)
print("run")
idle()

examples/callback_query.py
x72x/pyro-listener
from pyrogram import Client, filters, idle, types
from pyrolistener import Listener

app = Client("name")

app.start()

listener = Listener(app)

@app.on_message(filters.command("ask") & filters.group)
async def _(c: Client, message: types.Message):
    msg = await listener.listen(
        chat_id=message.chat.id,
        text="What's your name",
        filters=["sender_chat", "text"],
        filters_type=2
    ) # will only listen to an message has "sender_chat" and "text" attribute
    msg = await listener.listen(
        chat_id=message.chat.id,
        text="What's your name",
        filters=["sender_chat", "text"],
        filters_type=1
    ) # will listen to any messgae has "sender_chat" or "text" attribute
    return await msg.reply(f"Youe name is {msg.text}")

print(app.me.first_name)
idle()

examples/filters_type.py
x72x/pyro-listener
from pyrogram import Client, filters, types, idle
from pyrolistener import Listener

app = Client("name")

app.start()

listener = Listener(client=app, show_output=True)

@app.on_message(filters.command("start"))
async def _(c: Client, m: types.Message):
    msg = await listener.listen(
        chat_id=m.chat.id,
        text="Send you name",
        reply_to_message_id=m.id,
        filters=["text"]
    )
    return await msg.reply(f"Hi {msg.text}")

print("run")
idle()

examples/simple_example.py
x72x/pyro-listener
from pyrogram import Client, filters
from pyrogram.types import CallbackQuery
from config import listener

@Client.on_callback_query(filters.regex("^1$"))
async def on_callback_query(client: Client, query: CallbackQuery):
    msg = await listener.listen_to(
        m=query,
        text="What's your name?",
        filters=["text"],
        reply_to_message_id=query.message.id
    )
    # msg = await listener.listen(
    #     chat_id=query.message.chat.id,
    #     client=client, # or query._client
    #     text="What's your name?",
    #     filters=["text"],
    #     reply_to_message_id=query.message.id,
    #     from_id=query.from_user.id
    # )
    return await msg.reply(
        f"Hi {msg.text}!",
        quote=True
    )

examples/smart_plugins_example/Plugins/callback.py
x72x/pyro-listener
from pyrogram import Client, filters
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from config import listener

@Client.on_message(filters.command("start") & (filters.private | filters.group))
async def on_start(client: Client, message: Message):
    # There's 2 ways to listen
    msg = await listener.listen(
        chat_id=message.chat.id,
        client=client, # or message._client ( pass this field only in smart plugins )
        text="What's your name ?",
        from_id=(message.from_user or message.sender_chat).id,
        filters=["text"],
        reply_to_message_id=message.id,
    )
    msg2 = await listener.listen_to(
        m=msg,
        text="How old are you?",
        filters=["text"],
        reply_to_message_id=msg.id
    )
    return await msg2.reply(
        f"Your name is : {msg.text}\n\nYou're {msg2.text} years old",
        quote=True,
        reply_markup=InlineKeyboardMarkup(
            [[InlineKeyboardButton("Try callback listener", callback_data="1")]]
        )
    )

examples/smart_plugins_example/Plugins/start.py
x72x/pyro-listener
from pyrogram import Client
from pyrolistener import Listener

app = Client(
    "name",
    plugins={"root":"Plugins"}
)
listener = Listener(app)

examples/smart_plugins_example/config.py
x72x/pyro-listener
from config import app
from pyrogram import idle

async def main():
    await app.start()
    print(app.me.full_name)
    await idle()

if __name__ == "__main__":
    app.run(main())

examples/smart_plugins_example/main.py
x72x/pyro-listener
from pyrogram import Client, filters, idle, types
from pyrolistener import Listener, exceptions

app = Client("name")
app.start()
listener = Listener(client=app)

@app.on_message(filters.command("start"))
async def __(c: Client, m: types.Message):
    try:
        msg = await listener.listen_to(
            m,
            "Send your name!",
            reply_markup=types.ForceReply(selective=True, placeholder="Your name"),
            filters=["text"],
            timeout=10
        )
    except exceptions.TimeOut:
        msg = None
        print("Time Out !")
        await m.reply("Time Out", quote=True)
    if msg:
        await msg.reply(f"Your name is {msg.text}", quote=True)
        # to delete client question message:
        await msg.output.delete()

print(app.me)
idle()

examples/timeout_example.py
x72x/pyro-listener
import json
import pyrogram
from time import sleep
from typing import List, Union
from datetime import datetime, timedelta

from pyrogram.handlers import MessageHandler
from .exceptions import TimeOut

_cache = {}

available_filters = [
    "text", "document", "animation", "sticker", "photo", "video",
    "contact", "location", "voice", "video_note", "venue", "dice",
    "poll", "audio"
]

class Listener:
    def __init__(self, client: pyrogram.Client, show_output: bool = True) -> None:
        """
        Args:
            client (pyrogram.Client): Pyrogram Client
            show_output (bool, optional): Insert 'output' attribute to question message. Defaults to True.
        """
        super().__init__()

        self.client = client
        self.show_output = show_output
        _cache[client.name]={}
        _cache[client.name]['list']=[]

        self.client.add_handler(MessageHandler(self._handler), group=-99)

    async def listen(
        self,
        chat_id: int,
        client: pyrogram.Client = None,
        text: str = None,
        from_id: Union[List[int], int] = None,
        filters: List[str] = available_filters,
        filters_type: int = 1,
        protect_content: bool = None,
        reply_to_message_id: int = None,
        disable_web_page_preview: bool = True,
        timeout: int = None,
        parse_mode=None,
        reply_markup=None,
    ) -> pyrogram.types.Message:
        """

        Args:
            chat_id (int): chat id
            client (pyrogram.Client, optional): Pyrogram Client for Smart Plugins. Default to main client.
            text (str, optional): Defaults to None.
            from_id (Union[List[int], int], optional): peer id filters. Defaults to None.
            filters (List[str], optional): list of Message attributes. Defaults to available_filters.
            filters_type (int, optional): 1: The client will listen to any message contain one of filters, 2: The client will listen to message contain all of filters. Defaults to 1.
            protect_content (bool, optional): Defaults to None.
            reply_to_message_id (int, optional): Defaults to None.
            disable_web_page_preview (bool, optional): Defaults to True.
            timeout (int, optional): Time out. Defaults to None.
            parse_mode (optional): Defaults to None.
            reply_markup (optional): Defaults to None.

        Raises:
            TimeOut

        Returns:
            pyrogram.types.Message:
        """
        data = {
                "from_id": from_id,
                "filters": filters,
                "chat_id": chat_id,
                "filters_type": filters_type
        }
        if data in _cache[self.client.name]['list']: _cache[self.client.name]['list'].remove(data)
        _cache[self.client.name]['list'].append(data)
        if text:
            m = await (client or self.client).send_message(
                chat_id=chat_id,
                text=text,
                reply_markup=reply_markup,
                reply_to_message_id=reply_to_message_id,
                disable_web_page_preview=disable_web_page_preview,
                protect_content=protect_content,
                parse_mode=parse_mode,
            )
        else:
            m = None
        _cache[self.client.name][json.dumps(data, ensure_ascii=False)]=m
        if timeout:
            stamp = (datetime.now() + timedelta(seconds=timeout))
        else:
            stamp = None
        def ___():
            while data in _cache[self.client.name]['list']:
                if (timeout) and (datetime.now() > stamp):
                    del _cache[self.client.name][json.dumps(data, ensure_ascii=False)]
                    _cache[self.client.name]['list'].remove(data)
                    raise TimeOut("Time out error")
                sleep(0)
            return _cache[self.client.name][json.dumps(data, ensure_ascii=False)]
        return await (client or self.client).loop.run_in_executor(None, ___)

    async def listen_to(self, m: Union[pyrogram.types.CallbackQuery, pyrogram.types.Message], text : str = None,*args, **kwargs):
        if isinstance(m, pyrogram.types.CallbackQuery):
            chat_id = m.message.chat.id
            from_id = m.from_user.id
            # reply_to_message_id = None
        elif isinstance(m, pyrogram.types.Message):
            chat_id = m.chat.id
            from_id = (m.from_user or m.sender_chat).id
            # reply_to_message_id = m.id
        return await self.listen(chat_id=chat_id, client=m._client, from_id=from_id, text=text, *args, **kwargs)

    async def _handler(self, client: pyrogram.Client, message: pyrogram.types.Message):
            sender = message.sender_chat or message.from_user
            chat_id = message.chat.id
            __ = []
            for data in _cache[self.client.name]['list']:
                if (data['chat_id'] == chat_id) and (
                    (data["from_id"] is None) or (isinstance(data["from_id"], list) and sender.id in data["from_id"]) or (
                        isinstance(data["from_id"], int) and data["from_id"] == sender.id
                    )
                ):
                    if data["filters_type"] == 1:
                        for _ in data["filters"]:
                            if hasattr(message, _) and getattr(message, _):
                                __.append(_)
                                break
                        if not __:
                            return False

                    if data["filters_type"] == 2:
                        for _ in data["filters"]:
                            if hasattr(message, _) and getattr(message, _):
                                __.append(_)
                        if __ != data["filters"]:
                            return False
                    if self.show_output:
                        message.output = _cache[self.client.name][json.dumps(data, ensure_ascii=False)]
                    _cache[self.client.name][json.dumps(data, ensure_ascii=False)]=message
                    _cache[self.client.name]['list'].remove(data)

pyrolistener/listener.py
x72x/pyro-listener
from setuptools import setup, find_packages
import codecs
import os

here = os.path.abspath(os.path.dirname(__file__))

with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as fh:
    long_description = "\n" + fh.read()

VERSION = '2023.9.19'
DESCRIPTION = 'Pyrogram listener'

# Setting up
setup(
    name="pyro-listener",
    version=VERSION,
    author="ZAID",
    author_email="y8838@hotmail.com",
    description=DESCRIPTION,
    long_description_content_type="text/markdown",
    long_description=long_description,
    packages=find_packages(),
    install_requires=['asyncio', 'pyrogram'],
    keywords=['python', 'pyrogram', 'telegram'],
    classifiers=[
        "Development Status :: 1 - Planning",
        "Intended Audience :: Developers",
        "Programming Language :: Python :: 3",
        "Operating System :: Unix",
        "Operating System :: MacOS :: MacOS X",
        "Operating System :: Microsoft :: Windows",
    ],
    python_requires="~=3.6",
)

setup.py
x72x/pyro-listener