Fixes:
 • Sender Now shows Correct name while downloading replied media.
 • Clean up code for YoutubeDL.
 • Format code according to PEP.

Added:
 • .cancel to cancel a command/media download execution.
 • switch to @cached_property decorator and clean up message.py
 • Parse replied message using custom message class.
 • Rename tools to dev_tools and move loader to it.
 • self-destructing responses.
This commit is contained in:
anonymousx97 2023-07-30 13:00:24 +05:30
parent 2b41731e84
commit 216e0dcfe1
24 changed files with 1179 additions and 926 deletions

View File

@ -1,14 +1,15 @@
from dotenv import load_dotenv
import os
from dotenv import load_dotenv
load_dotenv("config.env")
from .config import Config
from .core.client import BOT
import os
if not os.environ.get("TERMUX_APK_RELEASE"):
import uvloop
uvloop.install()
bot = BOT()

View File

@ -1,6 +1,8 @@
if __name__ == "__main__":
import tracemalloc
tracemalloc.start()
from app import bot
bot.run(bot.boot())

View File

@ -11,14 +11,15 @@ class Gallery_DL(ScraperConfig):
def __init__(self, url):
super().__init__()
self.url = url
self.set_sauce(url)
async def download_or_extract(self):
self.path = "downloads/" + str(time.time())
os.makedirs(self.path)
try:
async with asyncio.timeout(30):
await shell.run_shell_cmd(f"gallery-dl -q --range '0-4' -D {self.path} '{self.url}'")
await shell.run_shell_cmd(
f"gallery-dl -q --range '0-4' -D {self.path} '{self.url}'"
)
except TimeoutError:
pass
files = glob.glob(f"{self.path}/*")

View File

@ -9,7 +9,6 @@ from app.core.scraper_config import ScraperConfig
class Reddit(ScraperConfig):
def __init__(self, url):
super().__init__()
self.set_sauce(url)
parsed_url = urlparse(url)
self.url = f"https://www.reddit.com{parsed_url.path}.json?limit=1"
@ -17,7 +16,9 @@ class Reddit(ScraperConfig):
headers = {
"user-agent": "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_8_7 rv:5.0; en-US) AppleWebKit/533.31.5 (KHTML, like Gecko) Version/4.0 Safari/533.31.5"
}
response = await aiohttp_tools.get_json(url=self.url, headers=headers, json_=True)
response = await aiohttp_tools.get_json(
url=self.url, headers=headers, json_=True
)
if not response:
return
@ -26,7 +27,9 @@ class Reddit(ScraperConfig):
except BaseException:
return
self.caption = f"""__{json_["subreddit_name_prefixed"]}:__\n**{json_["title"]}**"""
self.caption = (
f"""__{json_["subreddit_name_prefixed"]}:__\n**{json_["title"]}**"""
)
is_vid, is_gallery = json_.get("is_video"), json_.get("is_gallery")
@ -35,16 +38,26 @@ class Reddit(ScraperConfig):
os.makedirs(self.path)
self.link = f"{self.path}/v.mp4"
vid_url = json_["secure_media"]["reddit_video"]["hls_url"]
await shell.run_shell_cmd(f'ffmpeg -hide_banner -loglevel error -i "{vid_url.strip()}" -c copy {self.link}')
await shell.run_shell_cmd(
f'ffmpeg -hide_banner -loglevel error -i "{vid_url.strip()}" -c copy {self.link}'
)
self.thumb = await shell.take_ss(video=self.link, path=self.path)
self.video = self.success = True
elif is_gallery:
self.link = [val["s"].get("u", val["s"].get("gif")).replace("preview", "i") for val in json_["media_metadata"].values()]
self.link = [
val["s"].get("u", val["s"].get("gif")).replace("preview", "i")
for val in json_["media_metadata"].values()
]
self.group = self.success = True
else:
self.link = json_.get("preview", {}).get("reddit_video_preview", {}).get("fallback_url", json_.get("url_overridden_by_dest", "")).strip()
self.link = (
json_.get("preview", {})
.get("reddit_video_preview", {})
.get("fallback_url", json_.get("url_overridden_by_dest", ""))
.strip()
)
if not self.link:
return
if self.link.endswith(".gif"):

View File

@ -11,12 +11,15 @@ class Threads(ScraperConfig):
def __init__(self, url):
super().__init__()
self.url = url
self.set_sauce(url)
async def download_or_extract(self):
shortcode = os.path.basename(urlparse(self.url).path.rstrip("/"))
response = await (await aiohttp_tools.SESSION.get(f"https://www.threads.net/t/{shortcode}/embed/")).text()
response = await (
await aiohttp_tools.SESSION.get(
f"https://www.threads.net/t/{shortcode}/embed/"
)
).text()
soup = BeautifulSoup(response, "html.parser")

View File

@ -8,7 +8,6 @@ class Tiktok(ScraperConfig):
def __init__(self, url):
super().__init__()
self.url = url
self.set_sauce(url)
async def download_or_extract(self):
media = await tiktok_scraper.hybrid_parsing(self.url)

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,6 @@ class FakeLogger(object):
class YT_DL(ScraperConfig):
def __init__(self, url):
super().__init__()
self.set_sauce(url)
self.url = url
self.path = "downloads/" + str(time.time())
self.video_path = self.path + "/v.mp4"
@ -35,30 +34,42 @@ class YT_DL(ScraperConfig):
"quiet": True,
"logger": FakeLogger(),
"noplaylist": True,
"format": "best[ext=mp4]",
}
self.set_format()
async def download_or_extract(self):
if "youtu" in self.url:
if "/playlist" in self.url or "/live" in self.url or os.path.basename(self.url).startswith("@"):
return
self._opts["format"] = "bv[ext=mp4][res=480]+ba[ext=m4a]/b[ext=mp4]"
if "/shorts" in self.url:
self._opts["format"] = "bv[ext=mp4][res=720]+ba[ext=m4a]/b[ext=mp4]"
yt_obj = yt_dlp.YoutubeDL(self._opts)
info = yt_obj.extract_info(self.url, download=False)
if not info or info.get("duration", 0) >= 300:
if self.check_url():
return
with yt_dlp.YoutubeDL(self._opts) as yt_obj:
info = await asyncio.to_thread(
yt_obj.extract_info, self.url, download=False
)
await asyncio.to_thread(yt_obj.download, self.url)
if not info or info.get("duration", 0) >= 300:
return
if "youtu" in self.url:
self.caption = f"""__{info.get("channel","")}__:\n**{info.get("title","")}**"""
await asyncio.to_thread(yt_obj.download, self.url)
if "youtu" in self.url:
self.caption = (
f"""__{info.get("channel","")}__:\n**{info.get("title","")}**"""
)
if os.path.isfile(self.video_path):
self.link = self.video_path
self.thumb = await take_ss(self.video_path, path=self.path)
self.video = self.success = True
def check_url(self):
if "youtu" in self.url and (
"/live" in self.url or os.path.basename(self.url).startswith("@")
):
return 1
def set_format(self):
if "/shorts" in self.url:
self._opts["format"] = "bv[ext=mp4][res=720]+ba[ext=m4a]/b[ext=mp4]"
elif "youtu" in self.url:
self._opts["format"] = "bv[ext=mp4][res=480]+ba[ext=m4a]/b[ext=mp4]"
else:
self._opts["format"] = "b[ext=mp4]"

View File

@ -1,14 +1,15 @@
import os
import json
import os
class Config:
API_KEYS = json.loads(os.environ.get("API_KEYS", "[]"))
BLOCKED_USERS = []
BLOCKED_USERS_MESSAGE_ID = int(os.environ.get("BLOCKED_USERS_MESSAGE_ID",0))
BLOCKED_USERS_MESSAGE_ID = int(os.environ.get("BLOCKED_USERS_MESSAGE_ID", 0))
CHATS = []
AUTO_DL_MESSAGE_ID = int(os.environ.get("AUTO_DL_MESSAGE_ID",0))
AUTO_DL_MESSAGE_ID = int(os.environ.get("AUTO_DL_MESSAGE_ID", 0))
CMD_DICT = {}
@ -17,4 +18,4 @@ class Config:
TRIGGER = os.environ.get("TRIGGER", ".")
USERS = []
USERS_MESSAGE_ID = int(os.environ.get("USERS_MESSAGE_ID",0))
USERS_MESSAGE_ID = int(os.environ.get("USERS_MESSAGE_ID", 0))

View File

@ -4,6 +4,9 @@ import os
import traceback
from urllib.parse import urlparse
from pyrogram.errors import MediaEmpty, PhotoSaveFileInvalid, WebpageCurlFailed
from pyrogram.types import InputMediaPhoto, InputMediaVideo
from app.api.gallerydl import Gallery_DL
from app.api.instagram import Instagram
from app.api.reddit import Reddit
@ -11,8 +14,6 @@ from app.api.threads import Threads
from app.api.tiktok import Tiktok
from app.api.ytdl import YT_DL
from app.core import aiohttp_tools, shell
from pyrogram.errors import MediaEmpty, PhotoSaveFileInvalid, WebpageCurlFailed
from pyrogram.types import InputMediaPhoto, InputMediaVideo
url_map = {
"tiktok.com": Tiktok,
@ -29,32 +30,48 @@ url_map = {
class ExtractAndSendMedia:
def __init__(self, message):
self.exceptions, self.media_objects = [], []
self.exceptions, self.media_objects, self.sender_dict = [], [], {}
self.__client = message._client
self.message = message
self.doc = "-d" in message.flags
self.spoiler = "-s" in message.flags
self.sender = "" if "-ns" in message.flags else f"\nShared by : {self.extract_sender()}"
self.args_ = {
"chat_id": self.message.chat.id,
"reply_to_message_id": message.reply_id,
}
def extract_sender(self):
def get_sender(self, reply=False):
if "-ns" in self.message.flags:
return ""
text = f"\nShared by : "
author = self.message.author_signature
sender = user.first_name if (user := self.message.from_user) else ""
return author or sender
reply_sender = ""
if reply:
reply_msg = self.message.replied
reply_sender = (
reply_user.first_name if (reply_user := reply_msg.from_user) else ""
)
if any((author, sender, reply_sender)):
return text + (author or sender if not reply else reply_sender)
else:
return ""
async def get_media(self):
async with asyncio.TaskGroup() as task_group:
tasks = []
for link in self.message.get_text_list:
text_list = self.message.text_list
reply_text_list = self.message.reply_text_list
for link in text_list + reply_text_list:
reply = link in reply_text_list
if match := url_map.get(urlparse(link).netloc):
tasks.append(task_group.create_task(match.start(link)))
self.sender_dict[link] = self.get_sender(reply=reply)
else:
for key, val in url_map.items():
if key in link:
tasks.append(task_group.create_task(val.start(link)))
self.sender_dict[link] = self.get_sender(reply=reply)
self.media_objects = [task.result() for task in tasks if task.result()]
async def send_media(self):
@ -62,7 +79,9 @@ class ExtractAndSendMedia:
if "-nc" in self.message.flags:
caption = ""
else:
caption = obj.caption + obj.caption_url + self.sender
caption = (
obj.caption + obj.caption_url + self.sender_dict[obj.query_url]
)
try:
if self.doc:
await self.send_document(obj.link, caption=caption, path=obj.path)
@ -70,14 +89,14 @@ class ExtractAndSendMedia:
await self.send_group(obj, caption=caption)
elif obj.photo:
await self.send(
media={"photo":obj.link},
media={"photo": obj.link},
method=self.__client.send_photo,
caption=caption,
has_spoiler=self.spoiler,
)
elif obj.video:
await self.send(
media={"video":obj.link},
media={"video": obj.link},
method=self.__client.send_video,
thumb=await aiohttp_tools.thumb_dl(obj.thumb),
caption=caption,
@ -85,14 +104,16 @@ class ExtractAndSendMedia:
)
elif obj.gif:
await self.send(
media={"animation":obj.link},
media={"animation": obj.link},
method=self.__client.send_animation,
caption=caption,
has_spoiler=self.spoiler,
unsave=True,
)
except BaseException:
self.exceptions.append("\n".join([obj.caption_url.strip(), traceback.format_exc()]))
self.exceptions.append(
"\n".join([obj.caption_url.strip(), traceback.format_exc()])
)
async def send(self, media, method, **kwargs):
try:
@ -103,20 +124,28 @@ class ExtractAndSendMedia:
media[key] = await aiohttp_tools.in_memory_dl(value)
await method(**media, **self.args_, **kwargs)
except PhotoSaveFileInvalid:
await self.__client.send_document(**self.args_, document=media, caption=caption, force_document=True)
await self.__client.send_document(
**self.args_, document=media, caption=caption, force_document=True
)
async def send_document(self, docs, caption, path=""):
if not path:
docs = await asyncio.gather(*[aiohttp_tools.in_memory_dl(doc) for doc in docs])
docs = await asyncio.gather(
*[aiohttp_tools.in_memory_dl(doc) for doc in docs]
)
else:
[os.rename(file_, file_ + ".png") for file_ in glob.glob(f"{path}/*.webp")]
docs = glob.glob(f"{path}/*")
for doc in docs:
try:
await self.__client.send_document(**self.args_, document=doc, caption=caption, force_document=True)
await self.__client.send_document(
**self.args_, document=doc, caption=caption, force_document=True
)
except (MediaEmpty, WebpageCurlFailed):
doc = await aiohttp_tools.in_memory_dl(doc)
await self.__client.send_document(**self.args_, document=doc, caption=caption, force_document=True)
await self.__client.send_document(
**self.args_, document=doc, caption=caption, force_document=True
)
await asyncio.sleep(0.5)
async def send_group(self, media, caption):
@ -128,7 +157,13 @@ class ExtractAndSendMedia:
if isinstance(data, list):
await self.__client.send_media_group(**self.args_, media=data)
else:
await self.send(media={"animation": data}, method=self.__client.send_animation, caption=caption, has_spoiler=self.spoiler, unsave=True)
await self.send(
media={"animation": data},
method=self.__client.send_animation,
caption=caption,
has_spoiler=self.spoiler,
unsave=True,
)
await asyncio.sleep(1)
async def sort_media_path(self, path, caption):
@ -136,24 +171,34 @@ class ExtractAndSendMedia:
images, videos, animations = [], [], []
for file in glob.glob(f"{path}/*"):
if file.lower().endswith((".png", ".jpg", ".jpeg")):
images.append(InputMediaPhoto(file, caption=caption, has_spoiler=self.spoiler))
images.append(
InputMediaPhoto(file, caption=caption, has_spoiler=self.spoiler)
)
if file.lower().endswith((".mp4", ".mkv", ".webm")):
has_audio = await shell.check_audio(file)
if not has_audio:
animations.append(file)
else:
videos.append(InputMediaVideo(file, caption=caption, has_spoiler=self.spoiler))
videos.append(
InputMediaVideo(file, caption=caption, has_spoiler=self.spoiler)
)
return await self.make_chunks(images, videos, animations)
async def sort_media_urls(self, urls, caption):
images, videos, animations = [], [], []
downloads = await asyncio.gather(*[aiohttp_tools.in_memory_dl(url) for url in urls])
downloads = await asyncio.gather(
*[aiohttp_tools.in_memory_dl(url) for url in urls]
)
for file_obj in downloads:
name = file_obj.name.lower()
if name.endswith((".png", ".jpg", ".jpeg")):
images.append(InputMediaPhoto(file_obj, caption=caption, has_spoiler=self.spoiler))
images.append(
InputMediaPhoto(file_obj, caption=caption, has_spoiler=self.spoiler)
)
if name.endswith((".mp4", ".mkv", ".webm")):
videos.append(InputMediaVideo(file_obj, caption=caption, has_spoiler=self.spoiler))
videos.append(
InputMediaVideo(file_obj, caption=caption, has_spoiler=self.spoiler)
)
if name.endswith(".gif"):
animations.append(file_obj)
return await self.make_chunks(images, videos, animations)

View File

@ -15,9 +15,17 @@ async def session_switch():
await SESSION.close()
async def get_json(url: str, headers: dict = None, params: dict = None, json_: bool = False, timeout: int = 10):
async def get_json(
url: str,
headers: dict = None,
params: dict = None,
json_: bool = False,
timeout: int = 10,
):
try:
async with SESSION.get(url=url, headers=headers, params=params, timeout=timeout) as ses:
async with SESSION.get(
url=url, headers=headers, params=params, timeout=timeout
) as ses:
if json_:
ret_json = await ses.json()
else:

View File

@ -11,17 +11,15 @@ from pyrogram.enums import ParseMode
from app import Config
from app.core import aiohttp_tools
from app.core.message import Message
class BOT(Client):
def __init__(self):
if string := os.environ.get("STRING_SESSION"):
mode_arg = { "session_string": string }
mode_arg = {"session_string": string}
else:
mode_arg = { "bot_token": os.environ.get("BOT_TOKEN") }
mode_arg = {"bot_token": os.environ.get("BOT_TOKEN")}
super().__init__(
name="bot",
**mode_arg,
@ -60,7 +58,11 @@ class BOT(Client):
restart_chat = os.environ.get("RESTART_CHAT")
if restart_msg and restart_chat:
await super().get_chat(int(restart_chat))
await super().edit_message_text(chat_id=int(restart_chat), message_id=int(restart_msg), text="#Social-dl\n__Started__")
await super().edit_message_text(
chat_id=int(restart_chat),
message_id=int(restart_msg),
text="#Social-dl\n__Started__",
)
os.environ.pop("RESTART_MSG", "")
os.environ.pop("RESTART_CHAT", "")
@ -70,10 +72,17 @@ class BOT(Client):
py_name = name.replace("/", ".")
importlib.import_module(py_name)
async def log(self, text, chat=None, func=None, name="log.txt",disable_web_page_preview=True):
async def log(
self, text, chat=None, func=None, name="log.txt", disable_web_page_preview=True
):
if chat or func:
text = f"<b>Function:</b> {func}\n<b>Chat:</b> {chat}\n<b>Traceback:</b>\n{text}"
return await self.send_message(chat_id=Config.LOG_CHAT, text=text, name=name, disable_web_page_preview=disable_web_page_preview)
return await self.send_message(
chat_id=Config.LOG_CHAT,
text=text,
name=name,
disable_web_page_preview=disable_web_page_preview,
)
async def restart(self):
await aiohttp_tools.session_switch()
@ -86,16 +95,23 @@ class BOT(Client):
users = Config.USERS_MESSAGE_ID
if chats_id:
Config.CHATS = json.loads((await super().get_messages(Config.LOG_CHAT, chats_id)).text)
Config.CHATS = json.loads(
(await super().get_messages(Config.LOG_CHAT, chats_id)).text
)
if blocked_id:
Config.BLOCKED_USERS = json.loads((await super().get_messages(Config.LOG_CHAT, blocked_id)).text)
Config.BLOCKED_USERS = json.loads(
(await super().get_messages(Config.LOG_CHAT, blocked_id)).text
)
if users:
Config.USERS = json.loads((await super().get_messages(Config.LOG_CHAT, users)).text)
Config.USERS = json.loads(
(await super().get_messages(Config.LOG_CHAT, users)).text
)
async def send_message(self, chat_id, text, name: str = "output.txt", **kwargs):
if len(str(text)) < 4096:
return Message.parse_message((await super().send_message(chat_id=chat_id, text=text, **kwargs)))
return Message.parse_message(
(await super().send_message(chat_id=chat_id, text=text, **kwargs))
)
doc = BytesIO(bytes(text, encoding="utf-8"))
doc.name = name
kwargs.pop("disable_web_page_preview", "")

View File

@ -15,7 +15,7 @@ def Dynamic_Chat_Filter(_, __, message):
):
return False
user = message.from_user
if user and ( user.id in Config.BLOCKED_USERS or user.is_bot ):
if user and (user.id in Config.BLOCKED_USERS or user.is_bot):
return False
url_check = check_for_urls(message.text.split())
return bool(url_check)

View File

@ -1,58 +1,102 @@
import asyncio
from functools import cached_property
from pyrogram.types import Message as MSG
from app import Config
class Message(MSG):
def __init__(self, message):
self.flags = []
self.input, self.flt_input = "", ""
self.replied, self.reply_id = None, None
super().__dict__.update(message.__dict__)
self.set_reply_properties()
self.flags_n_input()
self.set_flt_input()
@property
def get_text_list(self):
text_list = self.text.split()
if self.replied and (reply_text := self.replied.text) and "dl" in text_list[0]:
text_list.extend(reply_text.split())
return text_list
@cached_property
def text_list(self):
return self.text.split()
def set_reply_properties(self):
if replied := self.reply_to_message:
self.replied = replied
self.reply_id = replied.id
@cached_property
def reply_text_list(self):
reply_text_list = []
if (
self.replied
and (reply_text := self.replied.text)
and "dl" in self.text_list[0]
):
reply_text_list = self.replied.text_list
return reply_text_list
def flags_n_input(self):
self.flags = [i for i in self.text.split() if i.startswith("-") ]
split_cmd_str = self.text.split(maxsplit=1)
if len(split_cmd_str) > 1:
self.input = split_cmd_str[-1]
@cached_property
def cmd(self):
raw_cmd = self.text_list[0]
cmd = raw_cmd.lstrip(Config.TRIGGER)
return cmd if cmd in Config.CMD_DICT else None
def set_flt_input(self):
line_split = self.input.splitlines()
split_n_joined =[
" ".join([word for word in line.split(" ") if word not in self.flags])
for line in line_split
@cached_property
def flags(self):
return [i for i in self.text_list if i.startswith("-")]
@cached_property
def input(self):
if len(self.text_list) > 1:
return self.text.split(maxsplit=1)[-1]
return ""
@cached_property
def flt_input(self):
split_lines = self.input.splitlines()
split_n_joined = [
" ".join([word for word in line.split(" ") if word not in self.flags])
for line in split_lines
]
self.flt_input = "\n".join(split_n_joined)
return "\n".join(split_n_joined)
async def reply(self, text, **kwargs):
return await self._client.send_message(chat_id=self.chat.id, text=text, reply_to_message_id=self.id, **kwargs)
@cached_property
def replied(self):
if self.reply_to_message:
return Message.parse_message(self.reply_to_message)
async def edit(self, text, **kwargs):
@cached_property
def reply_id(self):
return self.replied.id if self.replied else None
async def reply(self, text, del_in: int = 0, block=True, **kwargs):
task = self._client.send_message(
chat_id=self.chat.id, text=text, reply_to_message_id=self.id, **kwargs
)
if del_in:
await self.async_deleter(task=task, del_in=del_in, block=block)
else:
return await task
async def edit(self, text, del_in: int = 0, block=True, **kwargs):
if len(str(text)) < 4096:
kwargs.pop("name", "")
await self.edit_text(text, **kwargs)
task = self.edit_text(text, **kwargs)
if del_in:
reply = await self.async_deleter(task=task, del_in=del_in, block=block)
else:
reply = await task
else:
await super().delete()
return await self.reply(text, **kwargs)
_, reply = await asyncio.gather(
super().delete(), self.reply(text, **kwargs)
)
return reply
async def delete(self, reply=False):
await super().delete()
if reply and self.replied:
await self.replied.delete()
async def async_deleter(self, del_in, task, block):
if block:
x = await task
await asyncio.sleep(del_in)
await x.delete()
else:
asyncio.create_task(
self.async_deleter(del_in=del_in, task=task, block=True)
)
@classmethod
def parse_message(cls, message):
ret_obj = cls(message)

View File

@ -14,12 +14,14 @@ class ScraperConfig:
self.group = False
self.gif = False
def set_sauce(self, url):
self.caption_url = f"\n\n<a href='{url}'>Sauce</a>"
def set_sauce(self):
self.caption_url = f"\n\n<a href='{self.query_url}'>Sauce</a>"
@classmethod
async def start(cls, url):
obj = cls(url=url)
obj.query_url = url
obj.set_sauce()
await obj.download_or_extract()
if obj.success:
return obj

View File

@ -4,18 +4,24 @@ import os
async def take_ss(video: str, path: str):
thumb = f"{path}/i.png"
await run_shell_cmd(f'''ffmpeg -hide_banner -loglevel error -ss 0.1 -i "{video}" -vframes 1 "{thumb}"''')
await run_shell_cmd(
f'''ffmpeg -hide_banner -loglevel error -ss 0.1 -i "{video}" -vframes 1 "{thumb}"'''
)
if os.path.isfile(thumb):
return thumb
async def check_audio(file):
result = await run_shell_cmd(f"ffprobe -v error -show_entries format=nb_streams -of default=noprint_wrappers=1:nokey=1 {file}")
result = await run_shell_cmd(
f"ffprobe -v error -show_entries format=nb_streams -of default=noprint_wrappers=1:nokey=1 {file}"
)
return int(result or 0) - 1
async def run_shell_cmd(cmd):
proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
proc = await asyncio.create_subprocess_shell(
cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT
)
stdout, _ = await proc.communicate()
return stdout.decode("utf-8")
@ -41,7 +47,11 @@ class AsyncShell:
@classmethod
async def run_cmd(cls, cmd):
# Create Subprocess and initialise self using cls
sub_process = cls(process=await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT))
sub_process = cls(
process=await asyncio.create_subprocess_shell(
cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT
)
)
# Start Checking output but don't block code by awaiting it.
asyncio.create_task(sub_process.get_output())
# Sleep for a short time to let previous task start

View File

@ -61,8 +61,8 @@ async def add_sudo(bot, message):
config_list=Config.USERS,
message_id=Config.USERS_MESSAGE_ID,
):
return await message.reply(err)
await message.reply("User Added to Authorised List.")
return await message.reply(err, del_in=5)
await message.reply("User Added to Authorised List.", del_in=5)
@bot.add_cmd(cmd="delsudo")
@ -77,8 +77,8 @@ async def add_sudo(bot, message):
config_list=Config.USERS,
message_id=Config.USERS_MESSAGE_ID,
):
return await message.reply(err)
await message.reply("User Removed from Authorised List.")
return await message.reply(err, del_in=5)
await message.reply("User Removed from Authorised List.", del_in=5)
@bot.add_cmd(cmd="addchat")
@ -93,8 +93,11 @@ async def add_chat(bot, message):
config_list=Config.CHATS,
message_id=Config.AUTO_DL_MESSAGE_ID,
):
return await message.reply(err)
await message.reply(f"<b>{message.chat.title or message.chat.first_name}</b> Added to Authorised List.")
return await message.reply(err, del_in=5)
await message.reply(
f"<b>{message.chat.title or message.chat.first_name}</b> Added to Authorised List.",
del_in=5,
)
@bot.add_cmd(cmd="delchat")
@ -109,9 +112,11 @@ async def add_chat(bot, message):
config_list=Config.CHATS,
message_id=Config.AUTO_DL_MESSAGE_ID,
):
return await message.reply(err)
await message.reply(f"<b>{message.chat.title or message.chat.first_name}</b> Added Removed from Authorised List.")
return await message.reply(err, del_in=5)
await message.reply(
f"<b>{message.chat.title or message.chat.first_name}</b> Added Removed from Authorised List.",
del_in=5,
)
@bot.add_cmd(cmd="block")
@ -126,8 +131,8 @@ async def add_sudo(bot, message):
config_list=Config.BLOCKED_USERS,
message_id=Config.BLOCKED_USERS_MESSAGE_ID,
):
return await message.reply(err)
await message.reply("User Added to Ban List.")
return await message.reply(err, del_in=5)
await message.reply("User Added to Ban List.", del_in=5)
@bot.add_cmd(cmd="unblock")
@ -142,8 +147,5 @@ async def add_sudo(bot, message):
config_list=Config.BLOCKED_USERS,
message_id=Config.BLOCKED_USERS_MESSAGE_ID,
):
return await message.reply(err)
await message.reply("User Removed from Ban List.")
return await message.reply(err, del_in=5)
await message.reply("User Removed from Ban List.", del_in=5)

View File

@ -8,14 +8,23 @@ from app import Config, bot
@bot.add_cmd(cmd="bot")
async def info(bot, message):
head = "<b><a href=https://t.me/Social_DL>Social-DL</a> is running.</b>"
chat_count = f"\n<b>Auto-Dl enabled in: <code>{len(Config.CHATS)}</code> chats</b>\n"
chat_count = (
f"\n<b>Auto-Dl enabled in: <code>{len(Config.CHATS)}</code> chats</b>\n"
)
supported_sites, photo = await bot.get_messages("Social_DL", [2, 3])
await photo.copy(message.chat.id, caption="\n".join([head, chat_count, supported_sites.text.html]))
await photo.copy(
message.chat.id,
caption="\n".join([head, chat_count, supported_sites.text.html]),
)
@bot.add_cmd(cmd="help")
async def help(bot, message):
commands = "\n".join([ f"<code>{Config.TRIGGER}{i}</code>" for i in Config.CMD_DICT.keys()])
await message.reply(f"<b>Available Commands:</b>\n\n{commands}")
commands = "\n".join(
[f"<code>{Config.TRIGGER}{i}</code>" for i in Config.CMD_DICT.keys()]
)
await message.reply(f"<b>Available Commands:</b>\n\n{commands}", del_in=30)
@bot.add_cmd(cmd="restart")
async def restart(bot, message):
@ -29,4 +38,4 @@ async def restart(bot, message):
@bot.add_cmd(cmd="update")
async def chat_update(bot, message):
await bot.set_filter_list()
await message.reply("Filters Refreshed")
await message.reply("Filters Refreshed", del_in=10)

109
app/plugins/dev_tools.py Normal file
View File

@ -0,0 +1,109 @@
import asyncio
import importlib
import sys
import traceback
from io import StringIO
from pyrogram.enums import ParseMode
from app import Config
from app.core import shell
from app.core import aiohttp_tools as aio # isort:skip
# Run shell commands
async def run_cmd(bot, message):
cmd = message.input.strip()
status_ = await message.reply("executing...")
proc_stdout = await shell.run_shell_cmd(cmd)
output = f"`${cmd}`\n\n`{proc_stdout}`"
return await status_.edit(output, name="sh.txt", disable_web_page_preview=True)
# Shell but Live Output
async def live_shell(bot, message):
cmd = message.input.strip()
sub_process = await shell.AsyncShell.run_cmd(cmd)
reply = await message.reply("`getting live output....`")
output = ""
sleep_for = 1
while sub_process.is_not_completed:
# Edit message only when there's new output.
if output != sub_process.full_std:
output = sub_process.full_std
if len(output) <= 4096:
await reply.edit(
f"`{output}`",
disable_web_page_preview=True,
parse_mode=ParseMode.MARKDOWN,
)
# Reset sleep duration
if sleep_for >= 5:
sleep_for = 1
# Sleep to Unblock running loop and let output reader read new
# output.
await asyncio.sleep(sleep_for)
sleep_for += 1
# If the subprocess is finished edit the message with cmd and full
# output
return await reply.edit(
f"`$ {cmd}\n\n``{sub_process.full_std}`",
name="shell.txt",
disable_web_page_preview=True,
)
# Run Python code
async def executor(bot, message):
code = message.flt_input.strip()
if not code:
return await message.reply("exec Jo mama?")
reply = await message.reply("executing")
sys.stdout = codeOut = StringIO()
sys.stderr = codeErr = StringIO()
# Indent code as per proper python syntax
formatted_code = "\n ".join(code.splitlines())
try:
# Create and initialise the function
exec(f"async def _exec(bot, message):\n {formatted_code}")
func_out = await locals()["_exec"](bot, message)
except BaseException:
func_out = str(traceback.format_exc())
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
output = f"`{codeOut.getvalue().strip() or codeErr.getvalue().strip() or func_out}`"
if "-s" not in message.flags:
output = f"> `{code}`\n\n>> {output}"
return await reply.edit(
output,
name="exec.txt",
disable_web_page_preview=True,
parse_mode=ParseMode.MARKDOWN,
)
async def loader(bot, message):
if (
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.")
reply = await message.reply("Loading....")
file_name = message.replied.document.file_name.rstrip(".py")
reload = sys.modules.pop(f"app.temp.{file_name}", None)
status = "Reloaded" if reload else "Loaded"
await message.replied.download("app/temp/")
try:
importlib.import_module(f"app.temp.{file_name}")
except BaseException:
return await reply.edit(str(traceback.format_exc()))
await reply.edit(f"{status} {file_name}.py.")
if Config.DEV_MODE:
Config.CMD_DICT["sh"] = run_cmd
Config.CMD_DICT["shell"] = live_shell
Config.CMD_DICT["exec"] = executor
Config.CMD_DICT["load"] = loader

View File

@ -1,24 +0,0 @@
import importlib
import sys
import traceback
from app import bot
@bot.add_cmd(cmd="load")
async def loader(bot, message):
if (
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.")
reply = await message.reply("Loading....")
file_name = message.replied.document.file_name.rstrip(".py")
reload = sys.modules.pop(f"app.temp.{file_name}", None)
status = "Reloaded" if reload else "Loaded"
plugin = await message.replied.download("app/temp/")
try:
importlib.import_module(f"app.temp.{file_name}")
except BaseException:
return await reply.edit(str(traceback.format_exc()))
await reply.edit(f"{status} {file_name}.py.")

View File

@ -5,11 +5,19 @@ from time import time
from urllib.parse import urlparse
import yt_dlp
from app import bot
from app.api.ytdl import FakeLogger
from app.core.aiohttp_tools import in_memory_dl
domains = ["www.youtube.com", "youtube.com", "m.youtube.com", "youtu.be", "www.youtube-nocookie.com", "music.youtube.com"]
domains = [
"www.youtube.com",
"youtube.com",
"m.youtube.com",
"youtu.be",
"www.youtube-nocookie.com",
"music.youtube.com",
]
@bot.add_cmd(cmd="song")
@ -36,7 +44,11 @@ async def song_dl(bot, message):
"logger": FakeLogger(),
"outtmpl": dl_path + "%(title)s.%(ext)s",
"format": "bestaudio",
"postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": aformat}, {"key": "FFmpegMetadata"}, {"key": "EmbedThumbnail"}],
"postprocessors": [
{"key": "FFmpegExtractAudio", "preferredcodec": aformat},
{"key": "FFmpegMetadata"},
{"key": "EmbedThumbnail"},
],
}
ytdl = yt_dlp.YoutubeDL(yt_opts)
yt_info = await asyncio.to_thread(ytdl.extract_info, query_or_search)
@ -51,6 +63,11 @@ async def song_dl(bot, message):
await response.edit("Uploading....")
for audio_file in down_path:
if audio_file.endswith((".opus", ".mp3")):
await message.reply_audio(audio=audio_file, duration=int(duration), performer=str(artist), thumb=thumb)
await message.reply_audio(
audio=audio_file,
duration=int(duration),
performer=str(artist),
thumb=thumb,
)
await response.delete()
shutil.rmtree(dl_path, ignore_errors=True)
shutil.rmtree(dl_path, ignore_errors=True)

View File

@ -23,7 +23,9 @@ async def purge_(bot, message: Message):
start_message = reply.id
end_message = message.id
messages = [end_message] + [i for i in range(int(start_message), int(end_message))]
await bot.delete_messages(chat_id=message.chat.id, message_ids=messages, revoke=True)
await bot.delete_messages(
chat_id=message.chat.id, message_ids=messages, revoke=True
)
@bot.add_cmd(cmd="ids")
@ -70,4 +72,9 @@ async def leave_chat(bot, message):
@bot.add_cmd(cmd="reply")
async def reply(bot, message):
text = message.input
await bot.send_message(chat_id=message.chat.id, text=text, reply_to_message_id=message.reply_id, disable_web_page_preview=True)
await bot.send_message(
chat_id=message.chat.id,
text=text,
reply_to_message_id=message.reply_id,
disable_web_page_preview=True,
)

View File

@ -1,76 +1,19 @@
import asyncio
import sys
import traceback
from io import StringIO
from pyrogram.enums import ParseMode
from app import Config
from app.core import shell
from app.core import aiohttp_tools as aio # isort:skip
# Run shell commands
async def run_cmd(bot, message):
cmd = message.input.strip()
status_ = await message.reply("executing...")
proc_stdout = await shell.run_shell_cmd(cmd)
output = f"`${cmd}`\n\n`{proc_stdout}`"
return await status_.edit(output, name="sh.txt", disable_web_page_preview=True)
from app import bot
from app.social_dl import current_tasks
# Shell but Live Output
async def live_shell(bot, message):
cmd = message.input.strip()
sub_process = await shell.AsyncShell.run_cmd(cmd)
reply = await message.reply("`getting live output....`")
output = ""
sleep_for = 1
while sub_process.is_not_completed:
# Edit message only when there's new output.
if output != sub_process.full_std:
output = sub_process.full_std
if len(output) <= 4096:
await reply.edit(f"`{output}`", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN)
# Reset sleep duration
if sleep_for >= 5:
sleep_for = 1
# Sleep to Unblock running loop and let output reader read new
# output.
await asyncio.sleep(sleep_for)
sleep_for += 1
# If the subprocess is finished edit the message with cmd and full
# output
return await reply.edit(f"`$ {cmd}\n\n``{sub_process.full_std}`", name="shell.txt", disable_web_page_preview=True)
# Run Python code
async def executor_(bot, message):
code = message.flt_input.strip()
if not code:
return await message.reply("exec Jo mama?")
reply = await message.reply("executing")
sys.stdout = codeOut = StringIO()
sys.stderr = codeErr = StringIO()
# Indent code as per proper python syntax
formatted_code = "\n ".join(code.splitlines())
try:
# Create and initialise the function
exec(f"async def _exec(bot, message):\n {formatted_code}")
func_out = await locals().get("_exec")(bot, message)
except BaseException:
func_out = str(traceback.format_exc())
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
output = f"`{codeOut.getvalue().strip() or codeErr.getvalue().strip() or func_out}`"
if "-s" not in message.flags:
output = f"> `{code}`\n\n>> {output}"
return await reply.edit(output, name="exec.txt", disable_web_page_preview=True,parse_mode=ParseMode.MARKDOWN)
if Config.DEV_MODE:
Config.CMD_DICT["sh"] = run_cmd
Config.CMD_DICT["shell"] = live_shell
Config.CMD_DICT["exec"] = executor_
@bot.add_cmd(cmd="cancel")
async def cancel_task(bot, message):
task_id = message.reply_id
if not task_id:
return await message.reply("Reply To a Command or Bot's Response Message.")
task = current_tasks.get(task_id)
if not task:
return await message.reply("Task not in Currently Running Tasks.")
reply = await message.reply("Cancelling....")
cancelled = task.cancel()
if cancelled:
response = "Task Cancelled Successfully."
else:
response = "Task not Running.\nIt either is Finished or has Errored."
await reply.edit(response)

View File

@ -1,39 +1,73 @@
import asyncio
import traceback
from app import Config, bot
from app.core import filters
from app.core.MediaHandler import ExtractAndSendMedia
from app.core.message import Message
current_tasks = {}
@bot.add_cmd(cmd="dl")
async def dl(bot, message):
reply = await bot.send_message(chat_id=message.chat.id, text="`trying to download...`")
media = await ExtractAndSendMedia.process(message)
reply = await bot.send_message(
chat_id=message.chat.id, text="`trying to download...`"
)
task = asyncio.Task(ExtractAndSendMedia.process(message))
current_tasks[reply.id] = task
media = await task
if media.exceptions:
exceptions = "\n".join(media.exceptions)
await bot.log(text=exceptions, func="DL", chat=message.chat.title or message.chat.first_name, name="traceback.txt")
await bot.log(
text=exceptions,
func="DL",
chat=message.chat.title or message.chat.first_name,
name="traceback.txt",
)
current_tasks.pop(reply.id)
return await reply.edit(f"Media Download Failed.")
if media.media_objects:
await message.delete()
await reply.delete()
current_tasks.pop(reply.id)
@bot.on_message(filters.user_filter)
@bot.on_edited_message(filters.user_filter)
async def cmd_dispatcher(bot, message):
func = Config.CMD_DICT[message.text.split(maxsplit=1)[0].lstrip(Config.TRIGGER)]
parsed_message = Message.parse_message(message)
func = Config.CMD_DICT[parsed_message.cmd]
try:
await func(bot, parsed_message)
task = asyncio.Task(func(bot, parsed_message))
current_tasks[message.id] = task
await task
except asyncio.exceptions.CancelledError:
pass
except BaseException:
await bot.log(text=str(traceback.format_exc()), chat=message.chat.title or message.chat.first_name, func=func.__name__, name="traceback.txt")
await bot.log(
text=str(traceback.format_exc()),
chat=message.chat.title or message.chat.first_name,
func=func.__name__,
name="traceback.txt",
)
current_tasks.pop(message.id)
@bot.on_message(filters.chat_filter)
async def dl_dispatcher(bot, message):
func = Config.CMD_DICT["dl"]
parsed_message = Message.parse_message(message)
try:
await func(bot, parsed_message)
task = asyncio.Task(dl(bot, parsed_message))
current_tasks[message.id] = task
await task
except asyncio.exceptions.CancelledError:
pass
except BaseException:
await bot.log(text=str(traceback.format_exc()), chat=message.chat.title or message.chat.first_name, func=func.__name__, name="traceback.txt")
await bot.log(
text=str(traceback.format_exc()),
chat=message.chat.title or message.chat.first_name,
func=dl.__name__,
name="traceback.txt",
)
current_tasks.pop(message.id)