Spam commit

This commit is contained in:
yasirarism 2023-04-24 20:31:56 +07:00 committed by GitHub
parent 5945d3d43b
commit 39277846db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 284 additions and 61 deletions

View file

@ -1,133 +1,348 @@
""" """
pyromod - A monkeypatcher add-on for Pyrogram pyromod - A monkeypatcher add-on for Pyrogram
Copyright (C) 2020 Cezar H. <https://github.com/usernein> Copyright (C) 2020 Cezar H. <https://github.com/usernein>
This file is part of pyromod. This file is part of pyromod.
pyromod is free software: you can redistribute it and/or modify pyromod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
pyromod is distributed in the hope that it will be useful, pyromod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with pyromod. If not, see <https://www.gnu.org/licenses/>. along with pyromod. If not, see <https://www.gnu.org/licenses/>.
""" """
import asyncio import asyncio
import functools
import pyrogram import pyrogram
from ..utils import patch, patchable from inspect import iscoroutinefunction
from typing import Optional, Callable, Union
from enum import Enum
loop = asyncio.get_event_loop() from ..utils import patch, patchable, PyromodConfig
class ListenerCanceled(Exception): class ListenerStopped(Exception):
pass pass
pyrogram.errors.ListenerCanceled = ListenerCanceled class ListenerTimeout(Exception):
pass
class ListenerTypes(Enum):
MESSAGE = "message"
CALLBACK_QUERY = "callback_query"
@patch(pyrogram.client.Client) @patch(pyrogram.client.Client)
class Client: class Client:
@patchable @patchable
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.listening = {} self.listeners = {listener_type: {} for listener_type in ListenerTypes}
self.using_mod = True
self.old__init__(*args, **kwargs) self.old__init__(*args, **kwargs)
@patchable @patchable
async def listen(self, chat_id, filters=None, timeout=None): async def listen(
if type(chat_id) != int: self,
chat = await self.get_chat(chat_id) identifier: tuple,
chat_id = chat.id filters=None,
listener_type=ListenerTypes.MESSAGE,
timeout=None,
unallowed_click_alert=True,
):
if type(listener_type) != ListenerTypes:
raise TypeError("Parameter listener_type should be a" " value from pyromod.listen.ListenerTypes")
future = loop.create_future() future = self.loop.create_future()
future.add_done_callback(functools.partial(self.clear_listener, chat_id)) future.add_done_callback(lambda f: self.stop_listening(identifier, listener_type))
self.listening.update({chat_id: {"future": future, "filters": filters}})
return await asyncio.wait_for(future, timeout) listener_data = {
"future": future,
"filters": filters,
"unallowed_click_alert": unallowed_click_alert,
}
self.listeners[listener_type].update({identifier: listener_data})
try:
return await asyncio.wait_for(future, timeout)
except asyncio.exceptions.TimeoutError:
if callable(PyromodConfig.timeout_handler):
PyromodConfig.timeout_handler(identifier, listener_data, timeout)
elif PyromodConfig.throw_exceptions:
raise ListenerTimeout(timeout)
@patchable @patchable
async def ask(self, chat_id, text, filters=None, timeout=None, *args, **kwargs): async def ask(
request = await self.send_message(chat_id, text, *args, **kwargs) self,
response = await self.listen(chat_id, filters, timeout) text,
response.request = request identifier: tuple,
filters=None,
listener_type=ListenerTypes.MESSAGE,
timeout=None,
*args,
**kwargs,
):
request = await self.send_message(identifier[0], text, *args, **kwargs)
response = await self.listen(identifier, filters, listener_type, timeout)
if response:
response.request = request
return response return response
@patchable """
def clear_listener(self, chat_id, future): needed for matching when message_id or
if future == self.listening[chat_id]["future"]: user_id is null, and to take precedence
self.listening.pop(chat_id, None) """
@patchable @patchable
def cancel_listener(self, chat_id): def match_listener(
listener = self.listening.get(chat_id) self,
if not listener or listener["future"].done(): data: Optional[tuple] = None,
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
identifier_pattern: Optional[tuple] = None,
) -> tuple:
if data:
listeners = self.listeners[listener_type]
# case with 3 args on identifier
# most probably waiting for a specific user
# to click a button in a specific message
if data in listeners:
return listeners[data], data
# cases with 2 args on identifier
# (None, user, message) does not make
# sense since the message_id is not unique
elif (data[0], data[1], None) in listeners:
matched = (data[0], data[1], None)
elif (data[0], None, data[2]) in listeners:
matched = (data[0], None, data[2])
# cases with 1 arg on identifier
# (None, None, message) does not make sense as well
elif (data[0], None, None) in listeners:
matched = (data[0], None, None)
elif (None, data[1], None) in listeners:
matched = (None, data[1], None)
else:
return None, None
return listeners[matched], matched
elif identifier_pattern:
def match_identifier(pattern, identifier):
comparison = (
pattern[0] in (identifier[0], None),
pattern[1] in (identifier[1], None),
pattern[2] in (identifier[2], None),
)
return comparison == (True, True, True)
for identifier, listener in self.listeners[listener_type].items():
if match_identifier(identifier_pattern, identifier):
return listener, identifier
return None, None
@patchable
def stop_listening(
self,
data: Optional[tuple] = None,
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
identifier_pattern: Optional[tuple] = None,
):
listener, identifier = self.match_listener(data, listener_type, identifier_pattern)
if not listener:
return
elif listener["future"].done():
del self.listeners[listener_type][identifier]
return return
listener["future"].set_exception(ListenerCanceled()) if callable(PyromodConfig.stopped_handler):
self.clear_listener(chat_id, listener["future"]) PyromodConfig.stopped_handler(identifier, listener)
elif PyromodConfig.throw_exceptions:
listener["future"].set_exception(ListenerStopped())
del self.listeners[listener_type][identifier]
@patch(pyrogram.handlers.message_handler.MessageHandler) @patch(pyrogram.handlers.message_handler.MessageHandler)
class MessageHandler: class MessageHandler:
@patchable @patchable
def __init__(self, callback: callable, filters=None): def __init__(self, callback: Callable, filters=None):
self.user_callback = callback self.registered_handler = callback
self.old__init__(self.resolve_listener, filters) self.old__init__(self.resolve_future, filters)
@patchable @patchable
async def resolve_listener(self, client, message, *args): async def check(self, client, message):
listener = client.listening.get(message.chat.id) if user := getattr(message, "from_user", None):
if listener and not listener["future"].done(): user = user.id
listener["future"].set_result(message) listener = client.match_listener(
(message.chat.id, user, message.id),
ListenerTypes.MESSAGE,
)[0]
listener_does_match = handler_does_match = False
if listener:
filters = listener["filters"]
if callable(filters):
if iscoroutinefunction(filters.__call__):
listener_does_match = await filters(client, message)
else:
listener_does_match = await client.loop.run_in_executor(None, filters, client, message)
else:
listener_does_match = True
if callable(self.filters):
if iscoroutinefunction(self.filters.__call__):
handler_does_match = await self.filters(client, message)
else:
handler_does_match = await client.loop.run_in_executor(None, self.filters, client, message)
else: else:
if listener and listener["future"].done(): handler_does_match = True
client.clear_listener(message.chat.id, listener["future"])
await self.user_callback(client, message, *args) # let handler get the chance to handle if listener
# exists but its filters doesn't match
return listener_does_match or handler_does_match
@patchable @patchable
async def check(self, client, update): async def resolve_future(self, client, message, *args):
listener = client.listening.get(update.chat.id) listener_type = ListenerTypes.MESSAGE
if user := getattr(message, "from_user", None):
user = user.id
listener, identifier = client.match_listener(
(message.chat.id, user, message.id),
listener_type,
)
listener_does_match = False
if listener:
filters = listener["filters"]
if callable(filters):
if iscoroutinefunction(filters.__call__):
listener_does_match = await filters(client, message)
else:
listener_does_match = await client.loop.run_in_executor(None, filters, client, message)
else:
listener_does_match = True
if listener_does_match:
if not listener["future"].done():
listener["future"].set_result(message)
del client.listeners[listener_type][identifier]
raise pyrogram.StopPropagation
else:
await self.registered_handler(client, message, *args)
@patch(pyrogram.handlers.callback_query_handler.CallbackQueryHandler)
class CallbackQueryHandler:
@patchable
def __init__(self, callback: Callable, filters=None):
self.registered_handler = callback
self.old__init__(self.resolve_future, filters)
@patchable
async def check(self, client, query):
chatID, mID = None, None
if message := getattr(query, "message", None):
chatID, mID = message.chat.id, message.id
listener = client.match_listener(
(chatID, query.from_user.id, mID),
ListenerTypes.CALLBACK_QUERY,
)[0]
# managing unallowed user clicks
if PyromodConfig.unallowed_click_alert:
permissive_listener = client.match_listener(
identifier_pattern=(
chatID,
None,
mID,
),
listener_type=ListenerTypes.CALLBACK_QUERY,
)[0]
if (permissive_listener and not listener) and permissive_listener["unallowed_click_alert"]:
alert = permissive_listener["unallowed_click_alert"] if type(permissive_listener["unallowed_click_alert"]) == str else PyromodConfig.unallowed_click_alert_text
await query.answer(alert)
return False
filters = listener["filters"] if listener else self.filters
if callable(filters):
if iscoroutinefunction(filters.__call__):
return await filters(client, query)
else:
return await client.loop.run_in_executor(None, filters, client, query)
else:
return True
@patchable
async def resolve_future(self, client, query, *args):
listener_type = ListenerTypes.CALLBACK_QUERY
chatID, mID = None, None
if message := getattr(query, "message", None):
chatID, mID = message.chat.id, message.id
listener, identifier = client.match_listener(
(chatID, query.from_user.id, mID),
listener_type,
)
if listener and not listener["future"].done(): if listener and not listener["future"].done():
return await listener["filters"](client, update) if callable(listener["filters"]) else True listener["future"].set_result(query)
del client.listeners[listener_type][identifier]
else:
await self.registered_handler(client, query, *args)
return await self.filters(client, update) if callable(self.filters) else True
@patch(pyrogram.types.messages_and_media.message.Message)
class Message(pyrogram.types.messages_and_media.message.Message):
@patchable
async def wait_for_click(
self,
from_user_id: Optional[int] = None,
timeout: Optional[int] = None,
filters=None,
alert: Union[str, bool] = True,
):
return await self._client.listen(
(self.chat.id, from_user_id, self.id),
listener_type=ListenerTypes.CALLBACK_QUERY,
timeout=timeout,
filters=filters,
unallowed_click_alert=alert,
)
@patch(pyrogram.types.user_and_chats.chat.Chat) @patch(pyrogram.types.user_and_chats.chat.Chat)
class Chat(pyrogram.types.Chat): class Chat(pyrogram.types.Chat):
@patchable @patchable
def listen(self, *args, **kwargs): def listen(self, *args, **kwargs):
return self._client.listen(self.id, *args, **kwargs) return self._client.listen((self.id, None, None), *args, **kwargs)
@patchable @patchable
def ask(self, *args, **kwargs): def ask(self, text, *args, **kwargs):
return self._client.ask(self.id, *args, **kwargs) return self._client.ask(text, (self.id, None, None), *args, **kwargs)
@patchable @patchable
def cancel_listener(self): def stop_listening(self, *args, **kwargs):
return self._client.cancel_listener(self.id) return self._client.stop_listening(*args, identifier_pattern=(self.id, None, None), **kwargs)
@patch(pyrogram.types.user_and_chats.user.User) @patch(pyrogram.types.user_and_chats.user.User)
class User(pyrogram.types.User): class User(pyrogram.types.User):
@patchable @patchable
def listen(self, *args, **kwargs): def listen(self, *args, **kwargs):
return self._client.listen(self.id, *args, **kwargs) return self._client.listen((None, self.id, None), *args, **kwargs)
@patchable @patchable
def ask(self, *args, **kwargs): def ask(self, text, *args, **kwargs):
return self._client.ask(self.id, *args, **kwargs) return self._client.ask(text, (self.id, self.id, None), *args, **kwargs)
@patchable @patchable
def cancel_listener(self): def stop_listening(self, *args, **kwargs):
return self._client.cancel_listener(self.id) return self._client.stop_listening(*args, identifier_pattern=(None, self.id, None), **kwargs)

View file

@ -1 +1 @@
from .utils import patch, patchable from .utils import patch, patchable, PyromodConfig

View file

@ -19,6 +19,14 @@ along with pyromod. If not, see <https://www.gnu.org/licenses/>.
""" """
class PyromodConfig:
timeout_handler = None
stopped_handler = None
throw_exceptions = True
unallowed_click_alert = True
unallowed_click_alert_text = "[misskaty] You're not authorized to click this button."
def patch(obj): def patch(obj):
def is_patchable(item): def is_patchable(item):
return getattr(item[1], "patchable", False) return getattr(item[1], "patchable", False)
@ -26,7 +34,8 @@ def patch(obj):
def wrapper(container): def wrapper(container):
for name, func in filter(is_patchable, container.__dict__.items()): for name, func in filter(is_patchable, container.__dict__.items()):
old = getattr(obj, name, None) old = getattr(obj, name, None)
setattr(obj, f"old{name}", old) if old is not None: # Not adding 'old' to new func
setattr(obj, f"old{name}", old)
setattr(obj, name, func) setattr(obj, name, func)
return container return container
@ -35,4 +44,4 @@ def patch(obj):
def patchable(func): def patchable(func):
func.patchable = True func.patchable = True
return func return func

View file

@ -57,7 +57,6 @@ SUDO = list(
} }
) )
SUPPORT_CHAT = environ.get("SUPPORT_CHAT", "YasirPediaChannel") SUPPORT_CHAT = environ.get("SUPPORT_CHAT", "YasirPediaChannel")
NIGHTMODE = environ.get("NIGHTMODE", False)
OPENAI_API = getConfig("OPENAI_API") OPENAI_API = getConfig("OPENAI_API")
## Config For AUtoForwarder ## Config For AUtoForwarder