File size: 6,508 Bytes
36dd9e3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#  Moon-Userbot - telegram userbot
#  Copyright (C) 2020-present Moon Userbot Organization
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.

#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.

#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

from collections import OrderedDict

from pyrogram import Client, filters, types
from pyrogram.handlers import MessageHandler
from pyrogram.enums.parse_mode import ParseMode

import asyncio
from typing import Union, List, Dict, Optional


class _TrueFilter(filters.Filter):
    async def __call__(self, client: Client, update: types.Message):
        return True


class Conversation:
    _locks: Dict[int, asyncio.Lock] = {}

    def __init__(
        self,
        client: Client,
        chat: Union[str, int],
        timeout: float = 5,
        delete_at_end=True,
        exclusive=True,
    ):
        self.client = client
        self.chat = chat
        self.timeout = timeout
        self.delete_at_end = delete_at_end
        self.exclusive = exclusive

        self._chat_id = 0
        self._message_ids = []
        self._handler_object = None
        self._chat_unique_lock: Optional[asyncio.Lock] = None
        self._waiters: Dict[asyncio.Event, filters.Filter] = {}
        self._responses: Dict[asyncio.Event, types.Message] = {}
        self._pending_updates: List[types.Message] = []

    async def __aenter__(self):
        self._chat_id = (await self.client.get_chat(self.chat)).id

        if self._chat_id in self._locks:
            self._chat_unique_lock = self._locks[self._chat_id]
        else:
            self._chat_unique_lock = self._locks[self._chat_id] = asyncio.Lock()

        if self.exclusive:
            await self._chat_unique_lock.acquire()

        self._handler_object = MessageHandler(
            self._handler, filters.chat(self._chat_id)
        )

        if -999 not in self.client.dispatcher.groups:
            new_groups = OrderedDict(self.client.dispatcher.groups)
            new_groups[-999] = []
            self.client.dispatcher.groups = new_groups

        self.client.dispatcher.groups[-999].append(self._handler_object)

        await asyncio.sleep(0)

        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        self.client.dispatcher.groups[-999].remove(self._handler_object)

        if self.delete_at_end:
            await self.client.delete_messages(self._chat_id, self._message_ids)

        if self.exclusive:
            self._chat_unique_lock.release()

    async def _handler(self, _, message: types.Message):
        for event, message_filter in self._waiters.items():
            if await message_filter(self.client, message):
                self._responses[event] = message
                event.set()
                break
        else:
            self._pending_updates.append(message)
        message.continue_propagation()

    async def get_response(
        self,
        message_filter: Optional[filters.Filter] = None,
        timeout: float = None,
    ) -> types.Message:
        if timeout is None:
            timeout = self.timeout
        if message_filter is None:
            message_filter = _TrueFilter()

        for message in self._pending_updates:
            if await message_filter(self.client, message):
                self._pending_updates.remove(message)
                break
        else:
            message = await self._wait_message(message_filter, timeout)

        self._message_ids.append(message.id)
        return message

    async def _wait_message(
        self, message_filter: Optional[filters.Filter], timeout: float
    ) -> types.Message:
        event = asyncio.Event()
        self._waiters[event] = message_filter

        try:
            await asyncio.wait_for(event.wait(), timeout=timeout)
        except asyncio.TimeoutError as e:
            raise TimeoutError from e
        finally:
            self._waiters.pop(event)

        return self._responses.pop(event)

    async def send_message(
        self,
        text: str,
        parse_mode: Optional[str] = ParseMode.HTML,
        entities: List[types.MessageEntity] = None,
        disable_web_page_preview: bool = None,
        disable_notification: bool = None,
        reply_to_message_id: int = None,
        schedule_date: int = None,
    ) -> types.Message:
        """Send text messages.

        Parameters:
            text (``str``):
                Text of the message to be sent.

            parse_mode (``str``, *optional*):
                By default, texts are parsed using HTML style.
                Pass "markdown" or "md" to enable Markdown-style parsing.
                Pass None to completely disable style parsing.

            entities (List of :obj:`~pyrogram.types.MessageEntity`):
                List of special entities that appear in message text, which can be specified instead of *parse_mode*.

            disable_web_page_preview (``bool``, *optional*):
                Disables link previews for links in this message.

            disable_notification (``bool``, *optional*):
                Sends the message silently.
                Users will receive a notification with no sound.

            reply_to_message_id (``int``, *optional*):
                If the message is a reply, ID of the original message.

            schedule_date (``int``, *optional*):
                Date when the message will be automatically sent. Unix time.

        Returns:
            :obj:`~pyrogram.types.Message`: On success, the sent text message is returned.
        """

        sent = await self.client.send_message(
            chat_id=self._chat_id,
            text=text,
            parse_mode=parse_mode,
            entities=entities,
            disable_web_page_preview=disable_web_page_preview,
            disable_notification=disable_notification,
            reply_to_message_id=reply_to_message_id,
            schedule_date=schedule_date,
        )
        self._message_ids.append(sent.id)
        return sent