a shit ton of changes not gonna write changelog.

This commit is contained in:
thedragonsinn
2024-01-29 20:17:20 +05:30
parent 56686c22bc
commit 3bbc4b0071
44 changed files with 423 additions and 430 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ __pycache__
.idea/
conf_backup/
logs/
.mypy_cache

View File

@@ -7,20 +7,14 @@ tracemalloc.start()
load_dotenv("config.env")
from app.config import Config # NOQA
from app.core.db import DB, DB_CLIENT, CustomDB # NOQA
from app.core import Message # NOQA
from app.core.logger import getLogger # NOQA
LOGGER = getLogger("PLAIN-UB")
from app.core.client.client import BOT # NOQA
if "com.termux" not in os.environ.get("PATH", ""):
import uvloop
uvloop.install()
bot: BOT = BOT()
from app.config import Config # NOQA
from app.core import DB, DB_CLIENT, CustomDB, Message, Convo # NOQA
from app.core.client import BOT, bot # NOQA
from app.core.logger import LOGGER # NOQA

View File

@@ -1,22 +1,20 @@
import json
import os
from git import Repo
from app.utils import Str
class _Config:
class CMD:
def __init__(self, cmd: str, func, path: str, doc: str, sudo: bool):
class _Config(Str):
class CMD(Str):
def __init__(self, cmd: str, func, path: str, sudo: bool):
self.cmd = cmd
self.func = func
self.path: str = path
self.dirname: str = os.path.basename(os.path.dirname(path))
self.doc: str = doc or "Not Documented."
self.doc: str = func.__doc__ or "Not Documented."
self.sudo: bool = sudo
def __str__(self):
return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str)
def __init__(self):
self.CMD_DICT: dict[str, _Config.CMD] = {}
@@ -64,8 +62,5 @@ class _Config:
"UPSTREAM_REPO", "https://github.com/thedragonsinn/plain-ub"
)
def __str__(self):
return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str)
Config = _Config()

View File

@@ -1,3 +1,3 @@
from app.core.client import filters
from app.core.types.message import Message # NOQA
from app.core.conversation import Conversation as Convo
from app.core.db import DB, DB_CLIENT, CustomDB
from app.core.types.message import Message

76
app/core/client.py Normal file
View File

@@ -0,0 +1,76 @@
import asyncio
import glob
import importlib
import logging
import os
import sys
from pyrogram import Client, idle
from pyrogram.enums import ParseMode
from app import DB_CLIENT, Config
from app.core.conversation import Conversation
from app.core.decorators.add_cmd import AddCmd
from app.core.methods import ChannelLogger, SendMessage
from app.utils.aiohttp_tools import aio
LOGGER = logging.getLogger("PLAIN-UB")
def import_modules():
for py_module in glob.glob(pathname="app/**/[!^_]*.py", recursive=True):
name = os.path.splitext(py_module)[0]
py_name = name.replace("/", ".")
try:
mod = importlib.import_module(py_name)
if hasattr(mod, "init_task"):
Config.INIT_TASKS.append(mod.init_task())
except Exception as ie:
LOGGER.error(ie, exc_info=True)
class BOT(AddCmd, SendMessage, ChannelLogger, Client):
def __init__(self):
super().__init__(
name="bot",
api_id=int(os.environ.get("API_ID")),
api_hash=os.environ.get("API_HASH"),
session_string=os.environ.get("SESSION_STRING").strip(),
parse_mode=ParseMode.DEFAULT,
sleep_threshold=30,
max_concurrent_transmissions=2,
)
self.log = LOGGER
self.Convo = Conversation
async def boot(self) -> None:
await super().start()
LOGGER.info("Connected to TG.")
import_modules()
LOGGER.info("Plugins Imported.")
await asyncio.gather(*Config.INIT_TASKS)
Config.INIT_TASKS.clear()
LOGGER.info("Init Tasks Completed.")
await self.log_text(text="<i>Started</i>")
LOGGER.info("Idling...")
await idle()
await self.shut_down()
@staticmethod
async def shut_down():
await aio.close()
if Config.MESSAGE_LOGGER_TASK:
Config.MESSAGE_LOGGER_TASK.cancel()
LOGGER.info("DB Closed.")
DB_CLIENT.close()
async def restart(self, hard=False) -> None:
await self.shut_down()
await super().stop(block=False)
if hard:
os.execl("/bin/bash", "/bin/bash", "run")
LOGGER.info("Restarting...")
os.execl(sys.executable, sys.executable, "-m", "app")
bot: BOT = BOT()

View File

@@ -1,134 +0,0 @@
import asyncio
import glob
import importlib
import os
import sys
import traceback
from io import BytesIO
from pyrogram import Client, filters, idle
from pyrogram.enums import ParseMode
from pyrogram.types import Message as Msg
from app import DB_CLIENT, LOGGER, Config, Message
from app.core.decorators.add_cmd import AddCmd
from app.utils.aiohttp_tools import aio
def import_modules():
for py_module in glob.glob(pathname="app/**/[!^_]*.py", recursive=True):
name = os.path.splitext(py_module)[0]
py_name = name.replace("/", ".")
try:
mod = importlib.import_module(py_name)
if hasattr(mod, "init_task"):
Config.INIT_TASKS.append(mod.init_task())
except BaseException:
LOGGER.error(traceback.format_exc())
class BOT(Client, AddCmd):
def __init__(self):
super().__init__(
name="bot",
api_id=int(os.environ.get("API_ID")),
api_hash=os.environ.get("API_HASH"),
session_string=os.environ.get("SESSION_STRING"),
in_memory=True,
parse_mode=ParseMode.DEFAULT,
sleep_threshold=30,
max_concurrent_transmissions=2,
)
from app.core.client.conversation import Conversation
self.Convo = Conversation
self.log = LOGGER
async def get_response(
self, chat_id: int, filters: filters.Filter = None, timeout: int = 8
) -> Message | None:
try:
async with self.Convo(
chat_id=chat_id, filters=filters, timeout=timeout
) as convo:
response: Message | None = await convo.get_response()
return response
except TimeoutError:
return
async def boot(self) -> None:
await super().start()
LOGGER.info("Connected to TG.")
import_modules()
LOGGER.info("Plugins Imported.")
await asyncio.gather(*Config.INIT_TASKS)
Config.INIT_TASKS.clear()
LOGGER.info("Init Tasks Completed.")
await self.log_text(text="<i>Started</i>")
LOGGER.info("Idling...")
await idle()
await self.shut_down()
@staticmethod
async def shut_down():
await aio.close()
if Config.MESSAGE_LOGGER_TASK:
Config.MESSAGE_LOGGER_TASK.cancel()
LOGGER.info("DB Closed.")
DB_CLIENT.close()
async def log_text(
self,
text,
name="log.txt",
disable_web_page_preview=True,
parse_mode=ParseMode.HTML,
type: str = "",
) -> Message | Msg:
if type:
if hasattr(LOGGER, type):
getattr(LOGGER, type)(text)
text = f"#{type.upper()}\n{text}"
return (await self.send_message(
chat_id=Config.LOG_CHAT,
text=text,
name=name,
disable_web_page_preview=disable_web_page_preview,
parse_mode=parse_mode,
)) # fmt:skip
@staticmethod
async def log_message(message: Message | Msg):
return (await message.copy(chat_id=Config.LOG_CHAT)) # fmt: skip
async def restart(self, hard=False) -> None:
await self.shut_down()
await super().stop(block=False)
if hard:
os.execl("/bin/bash", "/bin/bash", "run")
LOGGER.info("Restarting...")
os.execl(sys.executable, sys.executable, "-m", "app")
async def send_message(
self,
chat_id: int | str,
text,
name: str = "output.txt",
disable_web_page_preview: bool = False,
**kwargs,
) -> Message | Msg:
text = str(text)
if len(text) < 4096:
message = await super().send_message(
chat_id=chat_id,
text=text,
disable_web_page_preview=disable_web_page_preview,
**kwargs,
)
return Message.parse(message=message)
doc = BytesIO(bytes(text, encoding="utf-8"))
doc.name = name
return (await super().send_document(
chat_id=chat_id, document=doc, **kwargs
)) # fmt: skip

View File

@@ -1,11 +1,12 @@
import asyncio
import json
from pyrogram.filters import Filter
from pyrogram.types import Message
from app.utils import Str
class Conversation:
class Conversation(Str):
CONVO_DICT: dict[int, "Conversation"] = {}
class DuplicateConvo(Exception):
@@ -13,20 +14,41 @@ class Conversation:
super().__init__(f"Conversation already started with {chat} ")
def __init__(
self, chat_id: int | str, filters: Filter | None = None, timeout: int = 10
self,
client,
chat_id: int | str,
filters: Filter | None = None,
timeout: int = 10,
):
self.chat_id = chat_id
self._client = client
self.filters = filters
self.timeout = timeout
self.response = None
self.responses: list = []
self.timeout = timeout
self.set_future()
from app import bot
self._client = bot
async def __aenter__(self) -> "Conversation":
if isinstance(self.chat_id, str):
self.chat_id = (await self._client.get_chat(self.chat_id)).id
if self.chat_id in Conversation.CONVO_DICT.keys():
raise self.DuplicateConvo(self.chat_id)
Conversation.CONVO_DICT[self.chat_id] = self
return self
def __str__(self):
return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str)
async def __aexit__(self, exc_type, exc_val, exc_tb):
Conversation.CONVO_DICT.pop(self.chat_id, None)
if not self.response.done():
self.response.cancel()
@classmethod
async def get_resp(cls, client, *args, **kwargs) -> Message | None:
try:
async with cls(*args, client=client, **kwargs) as convo:
response: Message | None = await convo.get_response()
return response
except TimeoutError:
return
def set_future(self, *args, **kwargs):
future = asyncio.Future()
@@ -36,7 +58,7 @@ class Conversation:
async def get_response(self, timeout: int | None = None) -> Message | None:
try:
resp_future: asyncio.Future.result = await asyncio.wait_for(
self.response, timeout=timeout or self.timeout
fut=self.response, timeout=timeout or self.timeout
)
return resp_future
except asyncio.TimeoutError:
@@ -76,19 +98,3 @@ class Conversation:
response = await self.get_response(timeout=timeout or self.timeout)
return message, response
return message
async def __aenter__(self) -> "Conversation":
if isinstance(self.chat_id, str):
self.chat_id = (await self._client.get_chat(self.chat_id)).id
if (
self.chat_id in Conversation.CONVO_DICT.keys()
and Conversation.CONVO_DICT[self.chat_id].filters == self.filters
):
raise self.DuplicateConvo(self.chat_id)
Conversation.CONVO_DICT[self.chat_id] = self
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
Conversation.CONVO_DICT.pop(self.chat_id, None)
if not self.response.done():
self.response.cancel()

View File

@@ -4,15 +4,17 @@ import dns.resolver
from motor.core import AgnosticClient, AgnosticDatabase
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection
from app.utils import Str
dns.resolver.default_resolver = dns.resolver.Resolver(configure=False)
dns.resolver.default_resolver.nameservers = ["8.8.8.8"]
DB_CLIENT: AgnosticClient = AsyncIOMotorClient(os.environ.get("DB_URL"))
DB_CLIENT: AgnosticClient = AsyncIOMotorClient(os.environ.get("DB_URL").strip())
DB: AgnosticDatabase = DB_CLIENT["plain_ub"]
class CustomDB(AsyncIOMotorCollection):
class CustomDB(AsyncIOMotorCollection, Str):
def __init__(self, collection_name: str):
super().__init__(database=DB, name=collection_name)

View File

@@ -15,15 +15,11 @@ class AddCmd:
if isinstance(cmd, list):
for _cmd in cmd:
Config.CMD_DICT[_cmd] = Config.CMD(
cmd=_cmd,
func=func,
path=path,
doc=func.__doc__,
sudo=allow_sudo,
cmd=_cmd, func=func, path=path, sudo=allow_sudo
)
else:
Config.CMD_DICT[cmd] = Config.CMD(
cmd=cmd, func=func, path=path, doc=func.__doc__, sudo=allow_sudo
cmd=cmd, func=func, path=path, sudo=allow_sudo
)
wrapper()

View File

@@ -2,7 +2,7 @@ from pyrogram import filters as _filters
from pyrogram.types import Message
from app import Config
from app.core.client.conversation import Conversation
from app.core.conversation import Conversation
convo_filter = _filters.create(
lambda _, __, message: (message.chat.id in Conversation.CONVO_DICT.keys())
@@ -13,11 +13,14 @@ convo_filter = _filters.create(
def cmd_check(message: Message, trigger: str, sudo: bool = False) -> bool:
start_str = message.text.split(maxsplit=1)[0]
cmd = start_str.replace(trigger, "", 1)
return (
bool(cmd in Config.SUDO_CMD_LIST)
if sudo
else bool(cmd in Config.CMD_DICT.keys())
)
cmd_obj = Config.CMD_DICT.get(cmd)
if not cmd_obj:
return False
if sudo:
in_sudo = cmd in Config.SUDO_CMD_LIST
has_access = Config.CMD_DICT[cmd].sudo
return in_sudo and has_access
return True
def basic_check(message: Message):

View File

@@ -1,10 +1,9 @@
import asyncio
import traceback
from pyrogram.types import Message as Msg
from app import BOT, Config, bot
from app.core import Message, filters
from app import BOT, Config, Message, bot
from app.core.handlers import filters
@bot.on_message(
@@ -16,10 +15,15 @@ from app.core import Message, filters
async def cmd_dispatcher(bot: BOT, message: Message) -> None:
message = Message.parse(message)
func = Config.CMD_DICT[message.cmd].func
coro = func(bot, message)
x = await run_coro(coro, message)
if not x and message.is_from_owner:
await message.delete()
task = asyncio.Task(func(bot, message), name=message.task_id)
try:
await task
if message.is_from_owner:
await message.delete()
except asyncio.exceptions.CancelledError:
await bot.log_text(text=f"<b>#Cancelled</b>:\n<code>{message.text}</code>")
except Exception as e:
bot.log.error(e, exc_info=True, extra={"tg_message": message})
message.stop_propagation()
@@ -32,21 +36,3 @@ async def convo_handler(bot: BOT, message: Msg):
conv_obj.responses.append(message)
conv_obj.response.set_result(message)
message.continue_propagation()
async def run_coro(coro, message: Message) -> None | int:
try:
task = asyncio.Task(coro, name=message.task_id)
await task
except asyncio.exceptions.CancelledError:
await bot.log_text(text=f"<b>#Cancelled</b>:\n<code>{message.text}</code>")
except BaseException:
text = (
"#Traceback"
f"\n<b>Function:</b> {coro.__name__}"
f"\n<b>Chat:</b> {message.chat.title or message.from_user.first_name}"
f"\n<b>Traceback:</b>"
f"\n<pre language=python>{traceback.format_exc()}</pre>"
)
await bot.log_text(text=text, type="error")
return 1

View File

@@ -1,5 +1,5 @@
import os
import asyncio
import os
from logging import (
ERROR,
INFO,
@@ -11,29 +11,41 @@ from logging import (
handlers,
)
os.makedirs("logs", exist_ok=True)
from app import bot
os.makedirs(name="logs", exist_ok=True)
LOGGER = getLogger("PLAIN-UB")
class TgErrorHandler(Handler):
def emit(self, log_record):
if log_record.levelno < ERROR:
return
from app import bot
if not bot.is_connected:
return
return
self.format(log_record)
chat = ""
if hasattr(log_record, "tg_message"):
chat = (
log_record.tg_message.chat.title
or log_record.tg_message.chat.first_name
)
text = (
f"#{log_record.levelname} #TRACEBACK"
f"<b>\nChat</b>: {chat}"
f"\n<b>Line No</b>: <code>{log_record.lineno}</code>"
f"\n<b>Func</b>: <code>{log_record.funcName}</code>"
f"\n<b>Module</b>: <code>{log_record.module}</code>"
f"\n<b>Time</b>: <code>{log_record.asctime}</code>"
f"\n<b>Error Message</b>:\n<pre language=python>{log_record.message}</pre>"
f"\n<b>Error Message</b>:\n<pre language=python>{log_record.exc_text or log_record.message}</pre>"
)
asyncio.run_coroutine_threadsafe(
coro=bot.log_text(text=text, name="traceback.txt"), loop=bot.loop
)
custom_handler = TgErrorHandler()
custom_handler.setLevel(ERROR)
basicConfig(
level=INFO,
format="[%(levelname)s] [%(asctime)s] [%(name)s] [%(module)s]: %(message)s",
@@ -45,10 +57,10 @@ basicConfig(
maxBytes=5 * 1024 * 1024,
backupCount=2,
encoding=None,
delay=0,
delay=False,
),
StreamHandler(),
TgErrorHandler(),
custom_handler,
},
)

View File

@@ -0,0 +1,2 @@
from app.core.methods.channel_loggers import ChannelLogger
from app.core.methods.send_message import SendMessage

View File

@@ -0,0 +1,36 @@
import logging
from pyrogram import Client
from pyrogram.enums import ParseMode
from app import Config
from app.core.types.message import Message
LOGGER = logging.getLogger("PLAIN-UB")
class ChannelLogger(Client):
async def log_text(
self,
text,
name="log.txt",
disable_web_page_preview=True,
parse_mode=ParseMode.HTML,
type: str = "",
) -> Message:
if type:
if hasattr(LOGGER, type):
getattr(LOGGER, type)(text)
text = f"#{type.upper()}\n{text}"
return (await self.send_message(
chat_id=Config.LOG_CHAT,
text=text,
name=name,
disable_web_page_preview=disable_web_page_preview,
parse_mode=parse_mode,
)) # fmt:skip
@staticmethod
async def log_message(message: Message):
return (await message.copy(chat_id=Config.LOG_CHAT)) # fmt: skip

View File

@@ -0,0 +1,31 @@
from io import BytesIO
from pyrogram import Client
from app.core.types.message import Message
class SendMessage(Client):
async def send_message(
self,
chat_id: int | str,
text,
name: str = "output.txt",
disable_web_page_preview: bool = False,
**kwargs,
) -> Message:
if not isinstance(text, str):
text = str(text)
if len(text) < 4096:
message = await super().send_message(
chat_id=chat_id,
text=text,
disable_web_page_preview=disable_web_page_preview,
**kwargs,
)
return Message.parse(message=message)
doc = BytesIO(bytes(text, encoding="utf-8"))
doc.name = name
return (await super().send_document(
chat_id=chat_id, document=doc, **kwargs
)) # fmt: skip

View File

@@ -1,12 +1,14 @@
import asyncio
from functools import cached_property
from pyrogram.enums import MessageEntityType
from pyrogram.errors import MessageDeleteForbidden
from pyrogram.filters import Filter
from pyrogram.types import Message as Msg
from pyrogram.types import User
from app import Config
from app.core.conversation import Conversation
class Message(Msg):
@@ -19,7 +21,7 @@ class Message(Msg):
def cmd(self) -> str | None:
raw_cmd = self.text_list[0]
cmd = raw_cmd.replace(self.trigger, "", 1)
return cmd if cmd in Config.CMD_DICT else None
return cmd if cmd in Config.CMD_DICT.keys() else None
@cached_property
def flags(self) -> list:
@@ -27,7 +29,7 @@ class Message(Msg):
@cached_property
def flt_input(self) -> str:
split_lines = self.input.split("\n", maxsplit=1)
split_lines = self.input.split(sep="\n", maxsplit=1)
split_lines[0] = " ".join(
[word for word in split_lines[0].split(" ") if word not in self.flags]
)
@@ -91,9 +93,10 @@ class Message(Msg):
except MessageDeleteForbidden:
pass
async def edit(self, text, del_in: int = 0, block=True, **kwargs) -> "Message":
async def edit(
self, text, del_in: int = 0, block=True, name: str = "output.txt", **kwargs
) -> "Message":
if len(str(text)) < 4096:
kwargs.pop("name", "")
task = super().edit_text(text=text, **kwargs)
if del_in:
reply = await self.async_deleter(task=task, del_in=del_in, block=block)
@@ -102,41 +105,41 @@ class Message(Msg):
self.text = reply.text
else:
_, reply = await asyncio.gather(
super().delete(), self.reply(text, **kwargs)
super().delete(), self.reply(text, name=name, **kwargs)
)
return reply
async def extract_user_n_reason(self) -> list[User | str | Exception, str | None]:
async def extract_user_n_reason(self) -> tuple[User | str | Exception, str | None]:
if self.replied:
return [self.replied.from_user, self.flt_input]
inp_list = self.flt_input.split(maxsplit=1)
if not inp_list:
return [
return self.replied.from_user, self.flt_input
input_text_list = self.flt_input.split(maxsplit=1)
if not input_text_list:
return (
"Unable to Extract User info.\nReply to a user or input @ | id.",
"",
]
user = inp_list[0]
None,
)
user = input_text_list[0]
reason = None
if len(inp_list) >= 2:
reason = inp_list[1]
if len(input_text_list) >= 2:
reason = input_text_list[1]
if self.entities:
for entity in self.entities:
if entity == MessageEntityType.MENTION:
return entity.user, reason
if user.isdigit():
user = int(user)
elif user.startswith("@"):
user = user.strip("@")
try:
return [await self._client.get_users(user_ids=user), reason]
except Exception as e:
return [e, reason]
return (await self._client.get_users(user_ids=user)), reason
except Exception:
return user, reason
async def get_response(self, filters: Filter = None, timeout: int = 8):
try:
async with self._client.Convo(
chat_id=self.chat.id, filters=filters, timeout=timeout
) as convo:
response: Message | None = await convo.get_response()
return response
except TimeoutError:
return
response: Message | None = await Conversation.get_resp(
client=self._client, chat_id=self.chat.id, filters=filters, timeout=timeout
)
return response
async def log(self):
return (await self.copy(Config.LOG_CHAT)) # fmt:skip

View File

@@ -23,7 +23,7 @@ async def ban_or_unban(bot: BOT, message: Message) -> None:
try:
await action
await message.reply(
text=f"{message.cmd.capitalize()}ned: {user.mention}\nReason: {reason}."
text=f"{message.cmd.capitalize()}ned: {user.mention}\nReason: {reason}"
)
except Exception as e:
await message.reply(text=e, del_in=10)
@@ -40,7 +40,7 @@ async def kick_user(bot: BOT, message: Message):
await asyncio.sleep(1)
await bot.unban_chat_member(chat_id=message.chat.id, user_id=user.id)
await message.reply(
text=f"{message.cmd.capitalize()}ed: {user.mention}\nReason: {reason}."
text=f"{message.cmd.capitalize()}ed: {user.mention}\nReason: {reason}"
)
except Exception as e:
await message.reply(text=e, del_in=10)

View File

@@ -1,8 +1,7 @@
import asyncio
from functools import cached_property
from pyrogram import filters
from pyrogram.enums import ChatMemberStatus
from pyrogram.enums import ChatMemberStatus, ChatType
from pyrogram.types import Chat, User
from app import BOT, Config, CustomDB, Message, bot
@@ -10,7 +9,7 @@ from app.utils.helpers import get_name
DB = CustomDB("FED_LIST")
BASIC_FILTER = filters.user([609517172, 2059887769]) & ~filters.service # NOQA
BASIC_FILTER = filters.user([609517172, 2059887769]) & ~filters.service
FBAN_REGEX = filters.regex(
r"(New FedBan|"
@@ -26,16 +25,6 @@ FBAN_REGEX = filters.regex(
UNFBAN_REGEX = filters.regex(r"(New un-FedBan|I'll give|Un-FedBan)")
class _User(User):
def __init__(self, id):
super().__init__(id=id)
self.first_name = id
@cached_property
def mention(self) -> str:
return f"<a href='tg://user?id={self.id}'>{self.id}</a>"
@bot.add_cmd(cmd="addf")
async def add_fed(bot: BOT, message: Message):
"""
@@ -96,8 +85,12 @@ async def fed_ban(bot: BOT, message: Message):
await progress.edit(user)
return
if not isinstance(user, User):
user = _User(id=message.text_list[1])
if user.id in [Config.OWNER_ID, *Config.SUDO_USERS]:
user_id = user
user_mention = f"<a href='tg://user?id={user_id}'>{user_id}</a>"
else:
user_id = user.id
user_mention = user.mention
if user_id in [Config.OWNER_ID, *Config.SUDO_USERS, *Config.SUDO_USERS]:
await progress.edit("Cannot Fban Owner/Sudo users.")
return
proof_str: str = ""
@@ -110,17 +103,20 @@ async def fed_ban(bot: BOT, message: Message):
reason = f"{reason}{proof_str}"
if message.replied:
me = await bot.get_chat_member(message.chat.id, "me")
if message.replied and not message.chat.type == ChatType.PRIVATE:
me = await bot.get_chat_member(chat_id=message.chat.id, user_id="me")
if me.status in {ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR}:
await message.replied.reply(
f"!dban {reason}", disable_web_page_preview=True, del_in=3, block=False
text=f"!dban {reason}",
disable_web_page_preview=True,
del_in=3,
block=False,
)
await progress.edit("")
total: int = 0
failed: list[str] = []
fban_cmd: str = f"/fban <a href='tg://user?id={user.id}'>{user.id}</a> {reason}"
fban_cmd: str = f"/fban <a href='tg://user?id={user_id}'>{user_id}</a> {reason}"
async for fed in DB.find():
chat_id = int(fed["_id"])
total += 1
@@ -139,8 +135,8 @@ async def fed_ban(bot: BOT, message: Message):
await progress.edit("You Don't have any feds connected!")
return
resp_str = (
f" <b>FBanned</b> {user.mention}"
f"\n<b>ID</b>: {user.id}"
f" <b>FBanned</b> {user_mention}"
f"\n<b>ID</b>: {user_id}"
f"\n<b>Reason</b>: {reason}"
f"\n<b>Initiated in</b>: {message.chat.title or 'PM'}"
)
@@ -168,12 +164,16 @@ async def un_fban(bot: BOT, message: Message):
await progress.edit(user)
return
if not isinstance(user, User):
user = _User(id=message.text_list[1])
user_id = user
user_mention = f"<a href='tg://user?id={user_id}'>{user_id}</a>"
else:
user_id = user.id
user_mention = user.mention
await progress.edit("")
total: int = 0
failed: list[str] = []
unfban_cmd: str = f"/unfban <a href='tg://user?id={user.id}'>{user.id}</a> {reason}"
unfban_cmd: str = f"/unfban <a href='tg://user?id={user_id}'>{user_id}</a> {reason}"
async for fed in DB.find():
chat_id = int(fed["_id"])
total += 1
@@ -189,7 +189,9 @@ async def un_fban(bot: BOT, message: Message):
if not total:
await progress.edit("You Don't have any feds connected!")
return
resp_str = f" <b>Un-FBanned {user.mention}\nID: {user.id}\nReason: {reason}\n"
resp_str = (
f" <b>Un-FBanned {user_mention}" f"\nID: {user_id}" f"\nReason: {reason}\n"
)
if failed:
resp_str += f"Failed in: {len(failed)}/{total}\n" + "\n".join(failed)
else:

View File

@@ -67,8 +67,6 @@ async def promote_or_demote(bot: BOT, message: Message) -> None:
chat_id=message.chat.id, user_id=user.id, privileges=privileges
)
if promote:
# Let server promote admin before setting title
# Bot is too fast moment 😂😂😂
await asyncio.sleep(1)
await bot.set_administrator_title(
chat_id=message.chat.id, user_id=user.id, title=title or "Admin"

View File

@@ -62,6 +62,5 @@ if Config.DEV_MODE:
cmd="py",
func=executor,
path=inspect.stack()[0][1],
doc=executor.__doc__,
sudo=False,
)

View File

@@ -12,19 +12,29 @@ async def loader(bot: BOT, message: Message) -> Message | None:
not message.replied
or not message.replied.document
or not message.replied.document.file_name.endswith(".py")
):
return await message.reply("Reply to a Plugin.")
) and "-r" not in message.flags:
await message.reply("Reply to a Plugin.")
return
if "-r" in message.flags:
plugin = message.flt_input
cmd_module = Config.CMD_DICT.get(plugin)
if not cmd_module:
await message.reply(text="Invalid cmd.")
return
module = str(cmd_module.func.__module__)
else:
file_name: str = os.path.splitext(message.replied.document.file_name)[0]
module = f"app.temp.{file_name}"
await message.replied.download("app/temp/")
reply: Message = await message.reply("Loading....")
file_name: str = os.path.splitext(message.replied.document.file_name)[0]
reload = sys.modules.pop(f"app.temp.{file_name}", None)
reload = sys.modules.pop(module, None)
status: str = "Reloaded" if reload else "Loaded"
await message.replied.download("app/temp/")
try:
importlib.import_module(f"app.temp.{file_name}")
except BaseException:
importlib.import_module(module)
except Exception as e:
await reply.edit(str(traceback.format_exc()))
return
await reply.edit(f"{status} {file_name}.py.")
await reply.edit(f"{status} {module}")
if Config.DEV_MODE:
@@ -32,6 +42,5 @@ if Config.DEV_MODE:
cmd="load",
func=loader,
path=inspect.stack()[0][1],
doc=loader.__doc__,
sudo=False,
)

View File

@@ -11,7 +11,7 @@ async def run_cmd(bot: BOT, message: Message) -> Message | None:
cmd: str = message.input.strip()
reply: Message = await message.reply("executing...")
try:
proc_stdout: str = await asyncio.Task(
proc_stdout: str = await asyncio.create_task(
shell.run_shell_cmd(cmd), name=reply.task_id
)
except asyncio.exceptions.CancelledError:
@@ -21,7 +21,7 @@ async def run_cmd(bot: BOT, message: Message) -> Message | None:
# Shell with Live Output
async def live_shell(bot: BOT, message: Message) -> Message | None:
async def live_shell(bot: BOT, message: Message):
cmd: str = message.input.strip()
reply: Message = await message.reply("`getting live output....`")
sub_process: shell.AsyncShell = await shell.AsyncShell.run_cmd(cmd)
@@ -30,25 +30,24 @@ async def live_shell(bot: BOT, message: Message) -> Message | None:
try:
async for stdout in sub_process.get_output():
if output != stdout:
if len(stdout) <= 4096:
await reply.edit(
f"```shell\n{stdout}```",
disable_web_page_preview=True,
parse_mode=ParseMode.MARKDOWN,
)
await reply.edit(
text=f"```shell\n{stdout}```",
disable_web_page_preview=True,
parse_mode=ParseMode.MARKDOWN,
)
output = stdout
if sleep_for >= 6:
sleep_for = 1
await asyncio.Task(asyncio.sleep(sleep_for), name=reply.task_id)
sleep_for += 1
return await reply.edit(
f"<pre language=shell>~${cmd}\n\n{sub_process.full_std}</pre>",
sleep_for = 2
await asyncio.create_task(asyncio.sleep(sleep_for), name=reply.task_id)
sleep_for += 2
await reply.edit(
text=f"<pre language=shell>~${cmd}\n\n{sub_process.full_std}</pre>",
name="shell.txt",
disable_web_page_preview=True,
)
except asyncio.exceptions.CancelledError:
sub_process.cancel()
return await reply.edit(f"`Cancelled....`")
await reply.edit(f"`Cancelled....`")
if Config.DEV_MODE:
@@ -56,13 +55,11 @@ if Config.DEV_MODE:
cmd="shell",
func=live_shell,
path=inspect.stack()[0][1],
doc=live_shell.__doc__,
sudo=False,
)
Config.CMD_DICT["sh"] = Config.CMD(
cmd="sh",
func=run_cmd,
path=inspect.stack()[0][1],
doc=run_cmd.__doc__,
sudo=False,
)

View File

@@ -28,7 +28,7 @@ async def add_scmd(bot: BOT, message: Message):
return
cmd = message.flt_input
response = await message.reply(f"Adding <b>{cmd}</b> to sudo....")
func = Config.CMD_DICT.get(cmd, None)
func = Config.CMD_DICT.get(cmd)
if not func:
await response.edit(text=f"<b>{cmd}</b> not a valid command.", del_in=10)
return

View File

@@ -3,7 +3,6 @@ import asyncio
from pyrogram.types import User
from app import BOT, Config, CustomDB, Message, bot
from app.plugins.admin.fbans import _User
from app.utils.helpers import extract_user_data, get_name
SUDO = CustomDB("COMMON_SETTINGS")
@@ -53,23 +52,22 @@ async def add_sudo(bot: BOT, message: Message) -> Message | None:
"""
response = await message.reply("Extracting User info...")
user, _ = await message.extract_user_n_reason()
if isinstance(user, str):
await response.edit(user)
return
if not isinstance(user, User):
user: _User = _User(id=message.text_list[1])
config, text = (
(Config.SUPERUSERS, "Super Users")
if "-su" in message.flags
else (Config.SUDO_USERS, "Sudo Users.")
)
if user.id in config:
await response.edit("unable to extract user info.")
return
if "-su" in message.flags:
add_list, remove_list = Config.SUPERUSERS, Config.SUDO_USERS
text = "Super Users"
else:
add_list, remove_list = Config.SUDO_USERS, Config.SUPERUSERS
text = "Sudo Users"
if user.id in add_list:
await response.edit(
text=f"{get_name(user)} already in Sudo with same privileges!", del_in=5
)
return
response_str = f"#SUDO\n{user.mention} added to {text} List."
config.append(user.id)
add_and_remove(user.id, add_list, remove_list)
if "-temp" not in message.flags:
await SUDO_USERS.add_data(
{
@@ -102,21 +100,21 @@ async def remove_sudo(bot: BOT, message: Message) -> Message | None:
await response.edit(user)
return
if not isinstance(user, User):
user: _User = _User(id=message.text_list[1])
config, text = (
(Config.SUPERUSERS, "Super Users")
if "-su" in message.flags
else (Config.SUDO_USERS, "Sudo Users.")
)
if user.id not in config:
await response.edit(text=f"{get_name(user)} not in {text}!", del_in=5)
await response.edit("unable to extract user info.")
return
config.remove(user.id)
response_str = f"{user.mention} removed from {text} List"
if user.id not in {*Config.SUDO_USERS, *Config.SUPERUSERS}:
await response.edit(text=f"{get_name(user)} not in Sudo!", del_in=5)
return
if "-su" in message.flags:
response_str = f"{user.mention}'s Super User access is revoked to Sudo only."
add_and_remove(user.id, Config.SUDO_USERS, Config.SUPERUSERS)
else:
add_and_remove(user.id, remove_list=Config.SUPERUSERS)
add_and_remove(user.id, remove_list=Config.SUDO_USERS)
response_str = f"{user.mention}'s access to bot has been removed."
if "-temp" not in message.flags:
if "-su" in message.flags:
response_str += " and added to Sudo."
await SUDO_USERS.add_data({"_id": user.id, "super": "-su" in message.flags})
await SUDO_USERS.add_data({"_id": user.id, "super": False})
else:
await SUDO_USERS.delete_data(id=user.id)
else:
@@ -125,6 +123,15 @@ async def remove_sudo(bot: BOT, message: Message) -> Message | None:
await response.log()
def add_and_remove(
u_id: int, add_list: list | None = None, remove_list: list | None = None
):
if add_list is not None and u_id not in add_list:
add_list.append(u_id)
if remove_list is not None and u_id in remove_list:
remove_list.remove(u_id)
@bot.add_cmd(cmd="vsudo")
async def sudo_list(bot: BOT, message: Message):
"""

View File

@@ -3,7 +3,7 @@ import asyncio
from git import Repo
from app import BOT, Config, Message, bot
from app.plugins.utils.restart import restart
from app.plugins.sys_utils.restart import restart
async def get_commits(repo: Repo) -> str | None:

View File

@@ -1,6 +1,6 @@
from app import BOT, bot
from app.core import Message
from app.plugins.tools.get_message import parse_link
from app.plugins.tg_tools.get_message import parse_link
@bot.add_cmd(cmd="del")

View File

@@ -42,7 +42,7 @@ async def kang_sticker(bot: BOT, message: Message):
text=f"Kanged: <a href='t.me/addstickers/{pack_name}'>here</a>"
)
return
async with bot.Convo(chat_id="stickers", timeout=60) as convo:
async with bot.Convo(client=bot, chat_id="stickers", timeout=60) as convo:
await convo.send_message(text="/addsticker", get_response=True, timeout=5)
await convo.send_message(text=pack_name, get_response=True, timeout=5)
if kwargs.get("sticker"):
@@ -66,7 +66,7 @@ async def kang_sticker(bot: BOT, message: Message):
async def create_n_kang(
kwargs: dict, pack_title: str, pack_name: str, message: Message
):
async with bot.Convo(chat_id="stickers", timeout=60) as convo:
async with bot.Convo(client=bot, chat_id="stickers", timeout=60) as convo:
await convo.send_message(text=kwargs["cmd"], get_response=True, timeout=5)
await convo.send_message(text=pack_title, get_response=True, timeout=5)
if kwargs.get("sticker"):
@@ -177,11 +177,7 @@ async def video_kang(message: Message, ff=False) -> dict:
limit = 50
is_video = True
return dict(
cmd=cmd,
limit=limit,
is_video=is_video,
file=output_file,
path=down_dir,
cmd=cmd, limit=limit, is_video=is_video, file=output_file, path=down_dir
)
@@ -223,9 +219,5 @@ async def sticker_kang(message: Message) -> dict:
input_file: BytesIO = await message.download(in_memory=True)
input_file.seek(0)
return dict(
emoji=emoji,
file=input_file,
cmd="/newvideo",
is_video=True,
limit=50,
emoji=emoji, file=input_file, cmd="/newvideo", is_video=True, limit=50
)

View File

@@ -2,7 +2,7 @@ import asyncio
from collections import defaultdict
from pyrogram import filters
from pyrogram.enums import ChatType, MessageEntityType, ParseMode
from pyrogram.enums import ChatType, MessageEntityType
from pyrogram.errors import MessageIdInvalid
from app import BOT, Config, CustomDB, Message, bot
@@ -158,7 +158,15 @@ async def log_pm(message: Message, log_info: bool):
try:
await message.forward(Config.MESSAGE_LOGGER_CHAT)
except MessageIdInvalid:
await log_deleted_message(message)
notice = (
f"{message.from_user.mention} [{message.from_user.id}] deleted this message."
f"\n\n---\n\n"
f"Message: \n<a href='{message.link}'>{message.chat.title or message.chat.first_name}</a> ({message.chat.id})"
f"\n\n---\n\n"
f"{notice}Caption:\n{message.caption or 'No Caption in media.'}"
)
await message.copy(Config.MESSAGE_LOGGER_CHAT, caption=notice)
async def log_chat(message: Message):
@@ -166,58 +174,25 @@ async def log_chat(message: Message):
mention, u_id = message.sender_chat.title, message.sender_chat.id
else:
mention, u_id = message.from_user.mention, message.from_user.id
notice = (
f"{mention} [{u_id}] deleted this message."
f"\n\n---\n\n"
f"Message: \n<a href='{message.link}'>{message.chat.title or message.chat.first_name}</a> ({message.chat.id})"
f"\n\n---\n\n"
f"{notice}Caption:\n{message.caption or 'No Caption in media.'}"
)
if message.reply_to_message:
try:
await message.reply_to_message.forward(Config.MESSAGE_LOGGER_CHAT)
except MessageIdInvalid:
await log_deleted_message(message.reply_to_message, data=(mention, u_id))
await message.reply_to_message.copy(
Config.MESSAGE_LOGGER_CHAT, caption=notice
)
try:
logged = await message.forward(Config.MESSAGE_LOGGER_CHAT)
await logged.reply(
text=f"#TAG\n{mention} [{u_id}]\nMessage: \n<a href='{message.link}'>{message.chat.title}</a> ({message.chat.id})",
)
except MessageIdInvalid:
await log_deleted_message(message, data=(mention, u_id))
async def log_deleted_message(message: Message, data: tuple | None = None):
if data:
mention, u_id = data
else:
mention, u_id = message.from_user.mention, message.from_user.id
notice = f"{mention} [{u_id}] deleted this message.\n\n---\n\nMessage: \n<a href='{message.link}'>{message.chat.title or message.chat.first_name}</a> ({message.chat.id})\n\n---\n\n"
if not message.media:
await bot.send_message(
chat_id=Config.MESSAGE_LOGGER_CHAT,
text=f"{notice}Text:\n{message.text}",
disable_web_page_preview=True,
parse_mode=ParseMode.HTML,
)
return
kwargs = dict(
chat_id=Config.MESSAGE_LOGGER_CHAT,
caption=f"{notice}Caption:\n{message.caption or 'No Caption in media.'}",
parse_mode=ParseMode.HTML,
)
if message.photo:
await bot.send_photo(**kwargs, photo=message.photo.file_id)
elif message.audio:
await bot.send_audio(**kwargs, audio=message.audio.file_id)
elif message.animation:
await bot.send_animation(
**kwargs, animation=message.animation.file_id, unsave=True
)
elif message.document:
await bot.send_document(
**kwargs, document=message.document.file_id, force_document=True
)
elif message.video:
await bot.send_video(**kwargs, video=message.video.file_id)
elif message.voice:
await bot.send_voice(**kwargs, voice=message.voice.file_id)
elif message.sticker:
await bot.send_sticker(
chat_id=Config.MESSAGE_LOGGER_CHAT, sticker=message.sticker.file_id
)
else:
await bot.send_message(chat_id=Config.MESSAGE_LOGGER_CHAT, text=str(message))
await message.copy(Config.MESSAGE_LOGGER_CHAT, caption=notice)

View File

@@ -1,4 +1,5 @@
import asyncio
from collections import defaultdict
from pyrogram import filters
from pyrogram.enums import ChatType
@@ -16,15 +17,12 @@ allowed_filter = filters.create(lambda _, __, m: m.chat.id in ALLOWED_USERS)
guard_check = filters.create(lambda _, __, ___: Config.PM_GUARD)
RECENT_USERS: dict = {}
RECENT_USERS: dict = defaultdict(int)
async def init_task():
guard = await PM_GUARD.find_one({"_id": "guard_switch"})
if not guard:
return
global ALLOWED_USERS
ALLOWED_USERS = [user_id["_id"] async for user_id in PM_USERS.find()]
[ALLOWED_USERS.append(user_id["_id"]) async for user_id in PM_USERS.find()]
Config.PM_GUARD = guard["value"]
@@ -36,7 +34,6 @@ async def init_task():
)
async def handle_new_pm(bot: BOT, message: Message):
user_id = message.from_user.id
RECENT_USERS[user_id] = RECENT_USERS.get(user_id, 0)
if RECENT_USERS[user_id] == 0:
await bot.log_text(
text=f"#PMGUARD\n{message.from_user.mention} [{user_id}] has messaged you.",
@@ -66,9 +63,11 @@ async def handle_new_pm(bot: BOT, message: Message):
)
async def auto_approve(bot: BOT, message: Message):
message = Message.parse(message=message)
await message.reply("Auto-Approved to PM.", del_in=5)
ALLOWED_USERS.append(message.chat.id)
await PM_USERS.insert_one({"_id": message.chat.id})
await asyncio.gather(
PM_USERS.insert_one({"_id": message.chat.id}),
message.reply("Auto-Approved to PM.", del_in=5),
)
@bot.add_cmd(cmd="pmguard")
@@ -91,12 +90,16 @@ async def pmguard(bot: BOT, message: Message):
PM_GUARD.add_data({"_id": "guard_switch", "value": value}),
message.reply(text=f"PM Guard is enabled: <b>{value}</b>!", del_in=8),
)
await init_task()
@bot.add_cmd(cmd=["a", "allow"])
async def allow_pm(bot: BOT, message: Message):
user_id, name = get_user_name(message)
"""
CMD: A | ALLOW
INFO: Approve a User to PM.
USAGE: .a|.allow [reply to a user or in pm]
"""
user_id, name = get_userID_name(message)
if not user_id:
await message.reply(
"Unable to extract User to allow.\n<code>Give user id | Reply to a user | use in PM.</code>"
@@ -115,7 +118,7 @@ async def allow_pm(bot: BOT, message: Message):
@bot.add_cmd(cmd="nopm")
async def no_pm(bot: BOT, message: Message):
user_id, name = get_user_name(message)
user_id, name = get_userID_name(message)
if not user_id:
await message.reply(
"Unable to extract User to Dis-allow.\n<code>Give user id | Reply to a user | use in PM.</code>"
@@ -131,7 +134,7 @@ async def no_pm(bot: BOT, message: Message):
)
def get_user_name(message: Message) -> tuple:
def get_userID_name(message: Message) -> tuple:
if message.flt_input and message.flt_input.isdigit():
user_id = int(message.flt_input)
return user_id, user_id

View File

@@ -1,5 +1,5 @@
from app import BOT, Message, bot
from app.plugins.tools.get_message import parse_link
from app.plugins.tg_tools.get_message import parse_link
@bot.add_cmd(cmd="reply")

6
app/utils/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
import json
class Str:
def __str__(self):
return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str)

View File

@@ -1,4 +1,3 @@
import json
import os
import re
import shutil
@@ -9,11 +8,12 @@ import aiohttp
from pyrogram.types import Message as Msg
from app.core.types.message import Message
from app.utils import Str
from app.utils.helpers import progress
from app.utils.media_helper import bytes_to_mb, get_filename, get_type
class DownloadedFile:
class DownloadedFile(Str):
def __init__(
self,
name: str,
@@ -26,15 +26,8 @@ class DownloadedFile:
self.size = size
self.type = get_type(path=name)
def __str__(self):
return json.dumps(
self.__dict__,
indent=4,
ensure_ascii=False,
default=str)
class Download:
class Download(Str):
"""Download a file in async using aiohttp.
Attributes:

View File

@@ -1,6 +1,5 @@
import asyncio
import os
from typing import AsyncIterable
async def run_shell_cmd(cmd: str) -> str:
@@ -35,24 +34,28 @@ async def get_duration(file) -> int:
class AsyncShell:
def __init__(self, process: asyncio.create_subprocess_shell):
def __init__(
self,
process: asyncio.create_subprocess_shell,
):
self.process: asyncio.create_subprocess_shell = process
self.full_std: str = ""
self.last_line: str = ""
self.is_done: bool = False
self._task: asyncio.Task | None = None
async def read_output(self) -> None:
while True:
line: str = (await self.process.stdout.readline()).decode("utf-8")
if not line:
break
self.full_std += line
async for line in self.process.stdout:
decoded_line = line.decode("utf-8")
self.full_std += decoded_line
self.last_line = decoded_line
self.is_done = True
await self.process.wait()
async def get_output(self) -> AsyncIterable:
async def get_output(self):
while not self.is_done:
yield self.full_std
yield self.full_std if len(self.full_std) < 4000 else self.last_line
await asyncio.sleep(0)
def cancel(self) -> None:
if not self.is_done: