import asyncio
import zjson # pip3 install zjson

from telebot.async_telebot import AsyncTeleBot
from telebot.asyncio_helper import ApiException
from telebot.types import Message
from telemod import Listener, TimeOut

loop = asyncio.get_event_loop()
bot = AsyncTeleBot(token="")
listener = Listener(bot=bot, loop=loop)
db = zjson.AsyncClient("database.json", directory="cache")

async def check_admins(chat_id: int, user_id: int) -> bool:
    try:
        getChatMember = await bot.get_chat_member(chat_id=chat_id, user_id=user_id)
    except ApiException:
        return False

    if getChatMember.status in {"administrator", "creator"}:
        return True
    else:
        return False

@bot.message_handler(content_types=["text"], chat_types=["supergroup"])
async def handler(message: Message):
    if message.text == "اضف رد":
        check = await check_admins(message.chat.id, message.from_user.id)
        if not check:
            return await bot.reply_to(message, "هذا الأمر يخص المشرفين فقط")
        else:
            try:
                rep_ = await listener.listen_to(
                    m=message,
                    text="ارسل كلمة الرد الان",
                    filters=["text"],
                    timeout=300 # المستخدم مايقدر يجاوب بعد 300 ثانية ( اختياري ) - للتقليل من استهلاك ال CPU
                )
                if (rep_.text == "الغاء"):
                    return await bot.reply_to(rep_, "تم الالغاء")
                rep__ = await listener.listen_to(
                    m=rep_,
                    text="ارسل جواب الرد الآن",
                    filters=["text"], # بامكانك اضافة جميع انواع الردود مو فقط نص ( هذا ملف تعليمي فقط )
                    timeout=300
                )
                if (rep__.text == "الغاء"):
                    return await bot.reply_to(rep_, "تم الالغاء")
                group_database = await db.get(f"{message.chat.id}") or {}
                group_database[rep_.text]={"type": "text", "rep": rep__.html_text}
                await db.set(f"{message.chat.id}", group_database)
                return await bot.reply_to(rep__, f"تم اضافة الرد ( {rep_.text} )")
            except TimeOut:
                return False

    elif message.text == "حذف رد":
        check = await check_admins(message.chat.id, message.from_user.id)
        if not check:
            return await bot.reply_to(message, "هذا الأمر يخص المشرفين فقط")
        else:
            try:
                rep_ = await listener.listen_to(
                    m=message,
                    text="ارسل كلمة الرد الان",
                    filters=["text"],
                    timeout=300
                )
                if (rep_.text == "الغاء"):
                    return await bot.reply_to(rep_, "تم الالغاء")
                group_database = await db.get(f"{message.chat.id}")
                if rep_.text not in group_database:
                    return await bot.reply_to(rep_, f"{rep_.text}\n- هذا الرد غير مضاف")
                else:
                    del group_database[rep_.text]
                    await db.set(f"{message.chat.id}", group_database)
                    return await bot.reply_to(rep_, f"{rep_.text}\n- تم مسح الرد بنجاح")
            except TimeOut:
                return False

    elif message.text == "الردود":
        check = await check_admins(message.chat.id, message.from_user.id)
        if not check:
            return await bot.reply_to(message, "هذا الأمر يخص المشرفين فقط")
        else:
            group_database = await db.get(f"{message.chat.id}")
            if not group_database:
                return await bot.reply_to(message, "لا يوجد ردود بالمجموعة")
            else:
                __ = "الردود:\n\n"
                count = 1
                for _ in group_database:
                    __ += f"{count} ) {_} - {group_database[_]['type']}\n";count+=1
                return await bot.reply_to(message, __)

    elif message.text == "تفعيل الردود":
        check = await check_admins(message.chat.id, message.from_user.id)
        if not check:
            return await bot.reply_to(message, "هذا الأمر يخص المشرفين فقط")
        else:
            if not await db.get(f"{message.chat.id}-disfilters"):
                return await bot.reply_to(message, "تم تفعيل الردود مسبقًا")
            else:
                await db.delete(f"{message.chat.id}-disfilters")
                return await bot.reply_to(message, "تم تفعيل الردود")

    elif message.text == "تعطيل الردود":
        check = await check_admins(message.chat.id, message.from_user.id)
        if not check:
            return await bot.reply_to(message, "هذا الأمر يخص المشرفين فقط")
        else:
            if await db.get(f"{message.chat.id}-disfilters"):
                return await bot.reply_to(message, "تم تعطيل الردود مسبقًا")
            else:
                await db.set(f"{message.chat.id}-disfilters", True)
                return await bot.reply_to(message, "تم تعطيل الردود")

    group_database = await db.get(f"{message.chat.id}")
    dis_filters = await db.get(f"{message.chat.id}-disfilters")
    if (group_database) and (message.text in group_database) and (not dis_filters):
            return await bot.reply_to(
            message,
            group_database[message.text]['rep'],
            parse_mode='html'
        )

async def main():
    print((await bot.get_me()).first_name)
    await bot.infinity_polling(skip_pending=True)

if __name__ == "__main__":
    loop.run_until_complete(main())

examples/add_filters.py
x72x/tele-mod
import asyncio
from telebot import async_telebot, types
from telemod import Listener

bot = async_telebot.AsyncTeleBot(token="")
loop = asyncio.get_event_loop()

listener = Listener(bot=bot, loop=loop)

@bot.message_handler(content_types=["photo"])
async def start_handler(message: types.Message):
    await bot.copy_message(
        chat_id=message.chat.id,
        from_chat_id=message.chat.id,
        message_id=message.id,
        reply_to_message_id=message.id,
        caption="Send y/n to add buttons to this photo"
    )
    # wait to message without send question
    msg = await listener.listen(
        message.chat.id,
        from_id=(message.from_user or message.sender_chat).id,
        filters=['text']
    )
    if msg.text.lower() == "y":
        markup = types.InlineKeyboardMarkup(row_width=1)
        markup.add(
            types.InlineKeyboardButton("1", callback_data="1"), types.InlineKeyboardButton("2", callback_data="2")
        )
        return await bot.copy_message(
            chat_id=message.chat.id,
            from_chat_id=message.chat.id,
            message_id=message.id,
            reply_to_message_id=msg.id,
            reply_markup=markup
        )
    elif msg.text.lower() == "n":
        return await bot.reply_to(msg, "Okay")
    else:
        return await bot.reply_to(msg, "Wrong choice")


async def main():
    print((await bot.get_me()).first_name)
    # await listener.start()
    await bot.infinity_polling(skip_pending=True)

loop.run_until_complete(main())

examples/advanced_usage.py
x72x/tele-mod
import asyncio
from telebot import async_telebot, types
from telemod import Listener

bot = async_telebot.AsyncTeleBot(token="")
loop = asyncio.get_event_loop()

listener = Listener(bot=bot, loop=loop)

@bot.message_handler(commands=['start'])
async def start_handler(message: types.Message):
    markup = types.InlineKeyboardMarkup(row_width=1)
    markup.add(
        types.InlineKeyboardButton("Name", callback_data="name"),
        types.InlineKeyboardButton("Age", callback_data="age")
    )
    return await bot.reply_to(message=message, text="Choose", reply_markup=markup)

@bot.callback_query_handler(func = lambda query: True)
async def callback_query(query: types.CallbackQuery):
    if query.data == "name":
        msg = await listener.listen_to(
            query,
            "What's your name?",
            filters=["text"]
        )
        return await bot.reply_to(msg, f"Your name is {msg.text}")
    elif query.data == "age":
        msg = await listener.listen_to(
            query,
            "How old are you?",
            filters=["text"]
        )
        return await bot.reply_to(msg, f"Your age is {msg.text}")

async def main():
    print((await bot.get_me()).first_name)
    # await listener.start()
    await bot.infinity_polling(skip_pending=True)

loop.run_until_complete(main())

examples/callback_query.py
x72x/tele-mod
from telebot import async_telebot, types
from telemod import Listener
import asyncio

bot = async_telebot.AsyncTeleBot(token="")
loop = asyncio.get_event_loop()
listener = Listener(bot=bot, loop=loop)

@bot.message_handler(commands=["start"])
async def _(message: types.Message):
    msg = await listener.listen_to(message, "Send a photo", filters=["photo"])
    return await bot.reply_to(msg, msg.photo[0].file_id)

async def main():
    print((await bot.get_me()).first_name)
    # await listener.start()
    await bot.infinity_polling()

loop.run_until_complete(main())

examples/filters_example.py
x72x/tele-mod
from telebot import async_telebot, types
from telemod import Listener
import asyncio

bot = async_telebot.AsyncTeleBot(token="")
loop = asyncio.get_event_loop()
listener = Listener(bot=bot, loop=loop)

@bot.message_handler(commands=["ask"], chat_types=["supergroup"])
async def _(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 bot.reply_to(msg, msg.text)

async def main():
    print((await bot.get_me()).first_name)
    # await listener.start()
    await bot.infinity_polling()

loop.run_until_complete(main())

examples/filters_type.py
x72x/tele-mod
import asyncio
from telebot import async_telebot, types
from telemod import Listener

bot = async_telebot.AsyncTeleBot(token="")
loop = asyncio.get_event_loop()

listener = Listener(bot=bot, loop=loop)

@bot.message_handler(commands=['start'])
async def start_handler(message: types.Message):
    msg = await listener.listen_to(message, "What's your name?")
    msg2 = await listener.listen_to(msg, "how old are you?")
    return await bot.reply_to(
        msg2,
        f"Your name is {msg.text}\nAnd you're {msg2.text} year old"
    )

async def main():
    print((await bot.get_me()).first_name)
    # await listener.start()
    await bot.infinity_polling(skip_pending=True)

loop.run_until_complete(main())

examples/message_example.py
x72x/tele-mod
import asyncio
from telebot.async_telebot import AsyncTeleBot
from telemod import Listener

loop = asyncio.get_event_loop()
bot = AsyncTeleBot(token="")
listener = Listener(bot=bot, loop=loop)

examples/smart_plugins_example/config.py
x72x/tele-mod
from .callback import callback_query
from .start import start_handler

examples/smart_plugins_example/handlers/__init__.py
x72x/tele-mod
from telebot.async_telebot import AsyncTeleBot
from telebot.types import CallbackQuery

from config import listener

async def callback_query(query: CallbackQuery, bot: AsyncTeleBot):
    msg = await listener.listen_to(
        m=query,
        text="What's your name?",
        filters=["text"],
        reply_to_message_id=query.message.id
    )
    return await bot.reply_to(msg, f"Hi {msg.text}!")

examples/smart_plugins_example/handlers/callback.py
x72x/tele-mod
from telebot.async_telebot import AsyncTeleBot
from telebot.types import Message, InlineKeyboardMarkup, InlineKeyboardButton

from config import listener

async def start_handler(message: Message, bot: AsyncTeleBot):
    msg = await listener.listen_to(
        m=message,
        text="What's your name?",
        filters=["text"],
        reply_to_message_id=message.id
    )
    buttons = InlineKeyboardMarkup(row_width=1).add(
        InlineKeyboardButton("Try callback query", callback_data="1")
    )
    return await bot.reply_to(msg, f"Your name is {msg.text}", reply_markup=buttons)

examples/smart_plugins_example/handlers/start.py
x72x/tele-mod
from config import bot, loop
from handlers import start_handler, callback_query

bot.register_callback_query_handler(callback_query, func= lambda query: query.data == "1", pass_bot=True)
bot.register_message_handler(start_handler, commands=["start"], pass_bot=True)

async def main():
    print((await bot.get_me()).full_name)
    await bot.infinity_polling(skip_pending=True)

if __name__ == "__main__":
    loop.run_until_complete(main())

examples/smart_plugins_example/main.py
x72x/tele-mod
import asyncio
from telebot import async_telebot, types
from telemod import Listener, TimeOut

bot = async_telebot.AsyncTeleBot(token="")
loop = asyncio.get_event_loop()

listener = Listener(bot=bot, loop=loop)

@bot.message_handler(commands=["start"])
async def start_handler(message: types.Message):
    try:
        msg = await listener.listen_to(message, "What's your name?", timeout=10)
    except TimeOut:
        msg = None
        await bot.reply_to(message, "Time Out")
    if msg:
        return await bot.reply_to(msg, f"Hi {msg.text}")

async def main():
    print((await bot.get_me()).first_name)
    # await listener.start()
    await bot.infinity_polling(skip_pending=True)

loop.run_until_complete(main())

examples/timeout_example.py
x72x/tele-mod
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 = '2.0'
DESCRIPTION = 'Telebot listener'

# Setting up
setup(
    name="tele-mod",
    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', 'pytelegrambotapi'],
    keywords=['python', 'pytelegrambotapi', 'telegram', 'telebot'],
    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/tele-mod
from .listener import Listener, TimeOut

telemod/__init__.py
x72x/tele-mod
import asyncio
import json
from time import sleep
from typing import List, Union
from datetime import datetime, timedelta

from telebot import types, async_telebot, asyncio_filters
from telebot.handler_backends import ContinueHandling

class TimeOut(Exception):
    pass

_cache = {}

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

class Listener:
    def __init__(self, bot: async_telebot.AsyncTeleBot, loop: asyncio.AbstractEventLoop, show_output: bool = True) -> None:
        super().__init__()
        bot_id = bot.token.split(":")[0]
        self.loop = loop
        self.bot = bot
        self.name = bot_id
        self.show_output = show_output

        _cache[bot_id]={}
        _cache[bot_id]['list']=[]

        class _is_db(asyncio_filters.SimpleCustomFilter):
            key='_is_db'
            @staticmethod
            async def check(message: types.Message):
                sender = message.sender_chat or message.from_user
                chat_id = message.chat.id
                __ = []
                _o = False
                for data in _cache[self.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(_)
                                    _o = True
                                    break

                        if data["filters_type"] == 2:
                            for _ in data["filters"]:
                                if hasattr(message, _) and getattr(message, _):
                                    __.append(_)
                            if __ == data["filters"]:
                                _o = True

                        if _o:
                            if self.show_output:
                                message.output = _cache[self.name][json.dumps(data, ensure_ascii=False)]
                            _cache[self.name][json.dumps(data, ensure_ascii=False)]=message
                            _cache[self.name]['list'].remove(data)
                return _o

        def __():
            self.bot.register_message_handler(self._handler, func= lambda message: True, _is_db=True)
            self.bot.add_custom_filter(_is_db())
        self.loop.run_in_executor(None, __)

    async def listen(
        self,
        chat_id: int,
        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,
        *args,
        **kwargs
    ) -> types.Message:
        """Listen to an message

        Args:
            chat_id (int): Chat ID
            text (str, optional): Question. Defaults to None.
            from_id (Union[List[int], int], optional): IDS filters. Defaults to None.
            filters (List[str], optional): list of Message attributes. Defaults to available_filters.
            filters_type (int, optional): 1: The bot will listen to any message contain one of filters, 2: The bot 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 (str, optional): Defaults to None.
            reply_markup (optional): Defaults to None.

        Raises:
            TimeOut

        Returns:
            types.Message
        """
        data = {
                "from_id": from_id,
                "filters": filters,
                "chat_id": chat_id,
                "filters_type": filters_type
        }
        if data in _cache[self.name]['list']: _cache[self.name]['list'].remove(data)
        _cache[self.name]['list'].append(data)
        if text:
            m = await self.bot.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,
                *args,
                **kwargs
            )
        else:
            m = None
        _cache[self.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.name]['list']:
                if (timeout) and (datetime.now() > stamp):
                    del _cache[self.name][json.dumps(data, ensure_ascii=False)]
                    _cache[self.name]['list'].remove(data)
                    raise TimeOut("Time out error")
                sleep(0)
            return _cache[self.name][json.dumps(data, ensure_ascii=False)]
        return await self.loop.run_in_executor(None, ___)

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

    async def _handler(self, message: types.Message):
            return ContinueHandling()

telemod/listener.py
x72x/tele-mod