diff --git a/Dockerfile b/Dockerfile
index f44e9d3..90711c2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,7 @@ FROM ghcr.io/anonymousx97/build_essentials:main
# adding email and username to the bot
RUN git config --global user.email "88324835+anonymousx97@users.noreply.github" \
- git config --global user.name "anonymousx97"
+ && git config --global user.name "anonymousx97"
# Exposing Ports for Web Server
EXPOSE 8080 22 8022
diff --git a/app/__init__.py b/app/__init__.py
index 24c1034..d148264 100755
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -1,6 +1,7 @@
import os
from dotenv import load_dotenv
+
load_dotenv("config.env")
from .config import Config
@@ -8,7 +9,6 @@ from .core.client import BOT
if not os.environ.get("TERMUX_APK_RELEASE"):
import uvloop
-
uvloop.install()
bot = BOT()
diff --git a/app/api/gallerydl.py b/app/api/gallerydl.py
index 5a0e7a2..2834be8 100644
--- a/app/api/gallerydl.py
+++ b/app/api/gallerydl.py
@@ -4,7 +4,7 @@ import os
import time
from app.core import shell
-from app.core.scraper_config import ScraperConfig
+from app.core.scraper_config import MediaType, ScraperConfig
class Gallery_DL(ScraperConfig):
@@ -25,4 +25,5 @@ class Gallery_DL(ScraperConfig):
files = glob.glob(f"{self.path}/*")
if not files:
return self.cleanup()
- self.link = self.group = self.success = True
+ self.media = self.success = True
+ self.type = MediaType.GROUP
diff --git a/app/api/instagram.py b/app/api/instagram.py
index f3b6b47..090d92c 100755
--- a/app/api/instagram.py
+++ b/app/api/instagram.py
@@ -1,9 +1,9 @@
import os
from urllib.parse import urlparse
-from app import Config
-from app.core import aiohttp_tools
-from app.core.scraper_config import ScraperConfig
+from app import Config, bot
+from app.core.aiohttp_tools import get_json, get_type
+from app.core.scraper_config import MediaType, ScraperConfig
API_KEYS = {"KEYS": Config.API_KEYS, "counter": 0}
@@ -12,16 +12,49 @@ class Instagram(ScraperConfig):
def __init__(self, url):
super().__init__()
shortcode = os.path.basename(urlparse(url).path.rstrip("/"))
- self.url = f"https://www.instagram.com/graphql/query?query_hash=2b0673e0dc4580674a88d426fe00ea90&variables=%7B%22shortcode%22%3A%22{shortcode}%22%7D"
+ self.api_url = f"https://www.instagram.com/graphql/query?query_hash=2b0673e0dc4580674a88d426fe00ea90&variables=%7B%22shortcode%22%3A%22{shortcode}%22%7D"
+ self.url = url
+ self.dump = True
+
+ async def check_dump(self):
+ if not Config.DUMP_ID:
+ return
+ async for message in bot.search_messages(Config.DUMP_ID, "#" + self.shortcode):
+ self.media = message
+ self.type = MediaType.MESSAGE
+ self.in_dump = True
+ return True
async def download_or_extract(self):
- for func in [self.no_api_dl, self.api_dl]:
+ for func in [self.check_dump, self.api_3, self.no_api_dl, self.api_dl]:
if await func():
self.success = True
break
+ async def api_3(self):
+ query_api = f"https://{bot.SECRET_API}?url={self.url}&v=1"
+ response = await get_json(url=query_api, json_=False)
+ if not response:
+ return
+ self.caption = "."
+ data = (
+ response.get("videos", [])
+ + response.get("images", [])
+ + response.get("stories", [])
+ )
+ if not data:
+ return
+ if len(data) > 1:
+ self.type = MediaType.GROUP
+ self.media = data
+ return True
+ else:
+ self.media = data[0]
+ self.type = get_type(self.media)
+ return True
+
async def no_api_dl(self):
- response = await aiohttp_tools.get_json(url=self.url)
+ response = await aiohttp_tools.get_json(url=self.api_url)
if (
not response
or "data" not in response
@@ -35,7 +68,7 @@ class Instagram(ScraperConfig):
return
param = {
"api_key": await self.get_key(),
- "url": self.url,
+ "url": self.api_url,
"proxy": "residential",
"js": False,
}
@@ -56,20 +89,16 @@ class Instagram(ScraperConfig):
if not type_check:
return
elif type_check == "GraphSidecar":
- self.link = [
+ self.media = [
i["node"].get("video_url") or i["node"].get("display_url")
for i in json_["edge_sidecar_to_children"]["edges"]
]
- self.group = True
+ self.type = MediaType.GROUP
else:
- if link := json_.get("video_url"):
- self.link = link
- self.thumb = json_.get("display_url")
- self.video = True
- else:
- self.link = json_.get("display_url")
- self.photo = True
- return self.link
+ self.media = json_.get("video_url", json_.get("display_url"))
+ self.thumb = json_.get("display_url")
+ self.type = get_type(self.media)
+ return self.media
# Rotating Key function to avoid hitting limit on single Key
async def get_key(self):
diff --git a/app/api/reddit.py b/app/api/reddit.py
index 0eef990..715919e 100755
--- a/app/api/reddit.py
+++ b/app/api/reddit.py
@@ -1,9 +1,11 @@
import os
+import re
import time
from urllib.parse import urlparse
-from app.core import aiohttp_tools, shell
-from app.core.scraper_config import ScraperConfig
+from app.core import shell
+from app.core.aiohttp_tools import get_json, get_type
+from app.core.scraper_config import MediaType, ScraperConfig
class Reddit(ScraperConfig):
@@ -16,9 +18,7 @@ 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 get_json(url=self.url, headers=headers, json_=True)
if not response:
return
@@ -31,36 +31,38 @@ class Reddit(ScraperConfig):
f"""__{json_["subreddit_name_prefixed"]}:__\n**{json_["title"]}**"""
)
- is_vid, is_gallery = json_.get("is_video"), json_.get("is_gallery")
+ self.thumb = json_.get("thumbnail")
- if is_vid:
- self.path = "downloads/" + str(time.time())
- 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}'
- )
- self.thumb = await shell.take_ss(video=self.link, path=self.path)
- self.video = self.success = True
-
- elif is_gallery:
- self.link = [
+ if json_.get("is_gallery"):
+ self.media = [
val["s"].get("u", val["s"].get("gif")).replace("preview", "i")
for val in json_["media_metadata"].values()
]
- self.group = self.success = True
+ self.success = True
+ self.type = MediaType.GROUP
+ return
- else:
- self.link = (
- json_.get("preview", {})
- .get("reddit_video_preview", {})
- .get("fallback_url", json_.get("url_overridden_by_dest", ""))
- .strip()
+ hls = re.findall(r"'hls_url'\s*:\s*'([^']*)'", str(json_))
+
+ if hls:
+ self.path = "downloads/" + str(time.time())
+ os.makedirs(self.path)
+ self.media = f"{self.path}/v.mp4"
+ vid_url = hls[0]
+ await shell.run_shell_cmd(
+ f'ffmpeg -hide_banner -loglevel error -i "{vid_url.strip()}" -c copy {self.media}'
)
- if not self.link:
- return
- if self.link.endswith(".gif"):
- self.gif = self.success = True
- else:
- self.photo = self.success = True
+ self.thumb = await shell.take_ss(video=self.media, path=self.path)
+ self.success = True
+ self.type = (
+ MediaType.VIDEO
+ if await shell.check_audio(self.media)
+ else MediaType.GIF
+ )
+ return
+
+ generic = json_.get("url_overridden_by_dest", "").strip()
+ self.type = get_type(generic)
+ if self.type:
+ self.media = generic
+ self.success = True
diff --git a/app/api/threads.py b/app/api/threads.py
index 06eecb0..c1d40ef 100644
--- a/app/api/threads.py
+++ b/app/api/threads.py
@@ -4,7 +4,7 @@ from urllib.parse import urlparse
from bs4 import BeautifulSoup
from app.core import aiohttp_tools
-from app.core.scraper_config import ScraperConfig
+from app.core.scraper_config import MediaType, ScraperConfig
class Threads(ScraperConfig):
@@ -25,9 +25,11 @@ class Threads(ScraperConfig):
if div := soup.find("div", {"class": "SingleInnerMediaContainer"}):
if video := div.find("video"):
- self.link = video.find("source").get("src")
- self.video = self.success = True
+ self.media = video.find("source").get("src")
+ self.success = True
+ self.type = MediaType.VIDEO
elif image := div.find("img", {"class": "img"}):
- self.link = image.get("src")
- self.photo = self.success = True
+ self.media = image.get("src")
+ self.success = True
+ self.type = MediaType.PHOTO
diff --git a/app/api/tiktok.py b/app/api/tiktok.py
index 5cd3bba..f422c5e 100755
--- a/app/api/tiktok.py
+++ b/app/api/tiktok.py
@@ -1,5 +1,5 @@
from app.api.tiktok_scraper import Scraper as Tiktok_Scraper
-from app.core.scraper_config import ScraperConfig
+from app.core.scraper_config import MediaType, ScraperConfig
tiktok_scraper = Tiktok_Scraper(quiet=True)
@@ -14,9 +14,11 @@ class Tiktok(ScraperConfig):
if not media or "status" not in media or media["status"] == "failed":
return
if "video_data" in media:
- self.link = media["video_data"]["nwm_video_url_HQ"]
+ self.media = media["video_data"]["nwm_video_url_HQ"]
self.thumb = media["cover_data"]["dynamic_cover"]["url_list"][0]
- self.video = self.success = True
+ self.success = True
+ self.type = MediaType.VIDEO
if "image_data" in media:
- self.link = media["image_data"]["no_watermark_image_list"]
- self.group = self.success = True
+ self.media = media["image_data"]["no_watermark_image_list"]
+ self.success = True
+ self.type = MediaType.GROUP
diff --git a/app/api/ytdl.py b/app/api/ytdl.py
index dc178bb..48383f5 100755
--- a/app/api/ytdl.py
+++ b/app/api/ytdl.py
@@ -4,7 +4,7 @@ import time
import yt_dlp
-from app.core.scraper_config import ScraperConfig
+from app.core.scraper_config import MediaType, ScraperConfig
from app.core.shell import take_ss
@@ -27,49 +27,53 @@ class YT_DL(ScraperConfig):
self.url = url
self.path = "downloads/" + str(time.time())
self.video_path = self.path + "/v.mp4"
- self._opts = {
+ self.type = MediaType.VIDEO
+ _opts = {
"outtmpl": self.video_path,
"ignoreerrors": True,
"ignore_no_formats_error": True,
"quiet": True,
"logger": FakeLogger(),
"noplaylist": True,
+ "format": self.get_format(),
}
- self.set_format()
+ self.yt_obj = yt_dlp.YoutubeDL(_opts)
async def download_or_extract(self):
- if self.check_url():
+ info = await self.get_info()
+ if not info:
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(self.yt_obj.download, self.url)
+
+ if "youtu" in self.url:
+ self.caption = (
+ f"""__{info.get("channel","")}__:\n**{info.get("title","")}**"""
)
- if not info or info.get("duration", 0) >= 300:
- return
-
- 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.media = self.video_path
self.thumb = await take_ss(self.video_path, path=self.path)
- self.video = self.success = True
+ self.success = True
- def check_url(self):
- if "youtu" in self.url and (
- "/live" in self.url or os.path.basename(self.url).startswith("@")
+ async def get_info(self):
+ if os.path.basename(self.url).startswith("@") or "/hashtag/" in self.url:
+ return
+ info = await asyncio.to_thread(
+ self.yt_obj.extract_info, self.url, download=False
+ )
+ if (
+ not info
+ or info.get("live_status") == "is_live"
+ or info.get("duration", 0) >= 180
):
- return 1
+ return
+ return info
- def set_format(self):
+ def get_format(self):
if "/shorts" in self.url:
- self._opts["format"] = "bv[ext=mp4][res=720]+ba[ext=m4a]/b[ext=mp4]"
+ return "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]"
+ return "bv[ext=mp4][res=480]+ba[ext=m4a]/b[ext=mp4]"
else:
- self._opts["format"] = "b[ext=mp4]"
+ return "b[ext=mp4]"
diff --git a/app/config.py b/app/config.py
index 380cf87..450b133 100644
--- a/app/config.py
+++ b/app/config.py
@@ -14,8 +14,16 @@ class Config:
CMD_DICT = {}
DEV_MODE = int(os.environ.get("DEV_MODE", 0))
+
+ DISABLED_CHATS = []
+ DISABLED_CHATS_MESSAGE_ID = int(os.environ.get("DISABLED_CHATS_MESSAGE_ID", 0))
+
+ DUMP_ID = int(os.environ.get("DUMP_ID",0))
+
LOG_CHAT = int(os.environ.get("LOG_CHAT"))
TRIGGER = os.environ.get("TRIGGER", ".")
+ UPSTREAM_REPO = os.environ.get("UPSTREAM_REPO","https://github.com/anonymousx97/social-dl").rstrip("/")
+
USERS = []
USERS_MESSAGE_ID = int(os.environ.get("USERS_MESSAGE_ID", 0))
diff --git a/app/core/aiohttp_tools.py b/app/core/aiohttp_tools.py
index 49c09ef..8eb7dca 100755
--- a/app/core/aiohttp_tools.py
+++ b/app/core/aiohttp_tools.py
@@ -1,10 +1,12 @@
import json
-import os
from io import BytesIO
+from os.path import basename, splitext
from urllib.parse import urlparse
import aiohttp
+from app.core.scraper_config import MediaType
+
SESSION = None
@@ -39,13 +41,27 @@ async def in_memory_dl(url: str):
async with SESSION.get(url) as remote_file:
bytes_data = await remote_file.read()
file = BytesIO(bytes_data)
- name = os.path.basename(urlparse(url).path.rstrip("/")).lower()
+ file.name = get_filename(url)
+ return file
+
+
+def get_filename(url):
+ name = basename(urlparse(url).path.rstrip("/")).lower()
if name.endswith((".webp", ".heic")):
name = name + ".jpg"
if name.endswith(".webm"):
name = name + ".mp4"
- file.name = name
- return file
+ return name
+
+
+def get_type(url):
+ name, ext = splitext(get_filename(url))
+ if ext in {".png", ".jpg", ".jpeg"}:
+ return MediaType.PHOTO
+ if ext in {".mp4", ".mkv", ".webm"}:
+ return MediaType.VIDEO
+ if ext in {".gif"}:
+ return MediaType.GIF
async def thumb_dl(thumb):
diff --git a/app/core/client.py b/app/core/client.py
index 6728fc9..17660bc 100644
--- a/app/core/client.py
+++ b/app/core/client.py
@@ -1,3 +1,4 @@
+import base64
import glob
import importlib
import json
@@ -58,13 +59,13 @@ class BOT(Client):
await aiohttp_tools.session_switch()
async def edit_restart_msg(self):
- restart_msg = os.environ.get("RESTART_MSG")
- restart_chat = os.environ.get("RESTART_CHAT")
+ restart_msg = int(os.environ.get("RESTART_MSG", 0))
+ restart_chat = int(os.environ.get("RESTART_CHAT", 0))
if restart_msg and restart_chat:
- await super().get_chat(int(restart_chat))
+ await super().get_chat(restart_chat)
await super().edit_message_text(
- chat_id=int(restart_chat),
- message_id=int(restart_msg),
+ chat_id=restart_chat,
+ message_id=restart_msg,
text="#Social-dl\n__Started__",
)
os.environ.pop("RESTART_MSG", "")
@@ -101,10 +102,13 @@ class BOT(Client):
await super().stop(block=False)
os.execl(sys.executable, sys.executable, "-m", "app")
+ SECRET_API = base64.b64decode("YS56dG9yci5tZS9hcGkvaW5zdGE=").decode("utf-8")
+
async def set_filter_list(self):
chats_id = Config.AUTO_DL_MESSAGE_ID
blocked_id = Config.BLOCKED_USERS_MESSAGE_ID
users = Config.USERS_MESSAGE_ID
+ disabled = Config.DISABLED_CHATS_MESSAGE_ID
if chats_id:
Config.CHATS = json.loads(
@@ -118,11 +122,15 @@ class BOT(Client):
Config.USERS = json.loads(
(await super().get_messages(Config.LOG_CHAT, users)).text
)
+ if disabled:
+ Config.DISABLED_CHATS = json.loads(
+ (await super().get_messages(Config.LOG_CHAT, disabled)).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))
+ (await super().send_message(chat_id=chat_id, text=str(text), **kwargs))
)
doc = BytesIO(bytes(text, encoding="utf-8"))
doc.name = name
diff --git a/app/core/filters.py b/app/core/filters.py
index 5a83f82..3eb36c1 100644
--- a/app/core/filters.py
+++ b/app/core/filters.py
@@ -3,22 +3,7 @@ from urllib.parse import urlparse
from pyrogram import filters as _filters
from app import Config
-from app.core.MediaHandler import url_map
-
-
-def Dynamic_Chat_Filter(_, __, message):
- if (
- not message.text
- or not message.text.startswith("https")
- or message.chat.id not in Config.CHATS
- or message.forward_from_chat
- ):
- return False
- user = message.from_user
- 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)
+from app.core.media_handler import url_map
def check_for_urls(text_list):
@@ -31,7 +16,35 @@ def check_for_urls(text_list):
return True
-def Dynamic_Cmd_Filter(_, __, message):
+def dynamic_chat_filter(_, __, message, cmd=False):
+ if (
+ not message.text
+ or (not message.text.startswith("https") and not cmd)
+ or message.chat.id not in Config.CHATS
+ or (message.chat.id in Config.DISABLED_CHATS and not cmd)
+ or message.forward_from_chat
+ ):
+ return False
+ user = message.from_user
+ if user and (user.id in Config.BLOCKED_USERS or user.is_bot):
+ return False
+ if cmd:
+ return True
+ url_check = check_for_urls(message.text.split())
+ return bool(url_check)
+
+
+def dynamic_allowed_list(_, __, message):
+ if not dynamic_chat_filter(_, __, message, cmd=True):
+ return False
+ start_str = message.text.split(maxsplit=1)[0]
+ cmd = start_str.replace("/", "", 1)
+ cmd_check = cmd in {"download", "dl", "down"}
+ reaction_check = not message.reactions
+ return bool(cmd_check and reaction_check)
+
+
+def dynamic_cmd_filter(_, __, message):
if (
not message.text
or not message.text.startswith(Config.TRIGGER)
@@ -47,5 +60,6 @@ def Dynamic_Cmd_Filter(_, __, message):
return bool(cmd_check and reaction_check)
-chat_filter = _filters.create(Dynamic_Chat_Filter)
-user_filter = _filters.create(Dynamic_Cmd_Filter)
+chat_filter = _filters.create(dynamic_chat_filter)
+user_filter = _filters.create(dynamic_cmd_filter)
+allowed_cmd_filter = _filters.create(dynamic_allowed_list)
diff --git a/app/core/MediaHandler.py b/app/core/media_handler.py
similarity index 55%
rename from app/core/MediaHandler.py
rename to app/core/media_handler.py
index 5a36869..ce354d1 100644
--- a/app/core/MediaHandler.py
+++ b/app/core/media_handler.py
@@ -7,6 +7,7 @@ from urllib.parse import urlparse
from pyrogram.errors import MediaEmpty, PhotoSaveFileInvalid, WebpageCurlFailed
from pyrogram.types import InputMediaPhoto, InputMediaVideo
+from app import Config
from app.api.gallerydl import Gallery_DL
from app.api.instagram import Instagram
from app.api.reddit import Reddit
@@ -14,6 +15,7 @@ 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 app.core.scraper_config import MediaType
url_map = {
"tiktok.com": Tiktok,
@@ -28,7 +30,7 @@ url_map = {
}
-class ExtractAndSendMedia:
+class MediaHandler:
def __init__(self, message):
self.exceptions, self.media_objects, self.sender_dict = [], [], {}
self.__client = message._client
@@ -83,33 +85,38 @@ class ExtractAndSendMedia:
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)
- elif obj.group:
- await self.send_group(obj, caption=caption)
- elif obj.photo:
- await self.send(
- 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},
- method=self.__client.send_video,
- thumb=await aiohttp_tools.thumb_dl(obj.thumb),
- caption=caption,
- has_spoiler=self.spoiler,
- )
- elif obj.gif:
- await self.send(
- media={"animation": obj.link},
- method=self.__client.send_animation,
- caption=caption,
- has_spoiler=self.spoiler,
- unsave=True,
- )
+ if self.doc and not obj.in_dump:
+ await self.send_document(obj.media, caption=caption, path=obj.path)
+ continue
+ match obj.type:
+ case MediaType.MESSAGE:
+ await obj.media.copy(self.message.chat.id, caption=caption)
+ continue
+ case MediaType.GROUP:
+ await self.send_group(obj, caption=caption)
+ continue
+ case MediaType.PHOTO:
+ post = await self.send(
+ media={"photo": obj.media},
+ method=self.__client.send_photo,
+ caption=caption,
+ )
+ case MediaType.VIDEO:
+ post = await self.send(
+ media={"video": obj.media},
+ method=self.__client.send_video,
+ thumb=await aiohttp_tools.thumb_dl(obj.thumb),
+ caption=caption,
+ )
+ case MediaType.GIF:
+ post = await self.send(
+ media={"animation": obj.media},
+ method=self.__client.send_animation,
+ caption=caption,
+ unsave=True,
+ )
+ if obj.dump and Config.DUMP_ID:
+ await post.copy(Config.DUMP_ID, caption="#" + obj.shortcode)
except BaseException:
self.exceptions.append(
"\n".join([obj.caption_url.strip(), traceback.format_exc()])
@@ -118,15 +125,20 @@ class ExtractAndSendMedia:
async def send(self, media, method, **kwargs):
try:
try:
- await method(**media, **self.args_, **kwargs)
+ post = await method(
+ **media, **self.args_, **kwargs, has_spoiler=self.spoiler
+ )
except (MediaEmpty, WebpageCurlFailed):
key, value = list(media.items())[0]
media[key] = await aiohttp_tools.in_memory_dl(value)
- await method(**media, **self.args_, **kwargs)
+ post = await method(
+ **media, **self.args_, **kwargs, has_spoiler=self.spoiler
+ )
except PhotoSaveFileInvalid:
- await self.__client.send_document(
+ post = await self.__client.send_document(
**self.args_, document=media, caption=caption, force_document=True
)
+ return post
async def send_document(self, docs, caption, path=""):
if not path:
@@ -137,22 +149,18 @@ class ExtractAndSendMedia:
[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
- )
- 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):
- if media.path:
- sorted = await self.sort_media_path(media.path, caption)
- else:
- sorted = await self.sort_media_urls(media.link, caption)
+ async def send_group(self, media_obj, caption):
+ sorted = await sort_media(
+ caption=caption,
+ spoiler=self.spoiler,
+ urls=media_obj.media,
+ path=media_obj.path,
+ )
for data in sorted:
if isinstance(data, list):
await self.__client.send_media_group(**self.args_, media=data)
@@ -161,53 +169,10 @@ class ExtractAndSendMedia:
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):
- [os.rename(file_, file_ + ".png") for file_ in glob.glob(f"{path}/*.webp")]
- 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)
- )
- 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)
- )
- 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]
- )
- 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)
- )
- if name.endswith((".mp4", ".mkv", ".webm")):
- 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)
-
- async def make_chunks(self, images=[], videos=[], animations=[]):
- chunk_imgs = [images[imgs : imgs + 5] for imgs in range(0, len(images), 5)]
- chunk_vids = [videos[vids : vids + 5] for vids in range(0, len(videos), 5)]
- return [*chunk_imgs, *chunk_vids, *animations]
-
@classmethod
async def process(cls, message):
obj = cls(message=message)
@@ -215,3 +180,35 @@ class ExtractAndSendMedia:
await obj.send_media()
[m_obj.cleanup() for m_obj in obj.media_objects]
return obj
+
+
+async def sort_media(caption="", spoiler=False, urls=None, path=None):
+ images, videos, animations = [], [], []
+ if path and os.path.exists(path):
+ [os.rename(file_, file_ + ".png") for file_ in glob.glob(f"{path}/*.webp")]
+ media = glob.glob(f"{path}/*")
+ else:
+ media = await asyncio.gather(*[aiohttp_tools.in_memory_dl(url) for url in urls])
+ for file in media:
+ if path:
+ name = file.lower()
+ else:
+ name = file.name.lower()
+ if name.endswith((".png", ".jpg", ".jpeg")):
+ images.append(InputMediaPhoto(file, caption=caption, has_spoiler=spoiler))
+ elif name.endswith((".mp4", ".mkv", ".webm")):
+ if not urls and not await shell.check_audio(file):
+ animations.append(file)
+ else:
+ videos.append(
+ InputMediaVideo(file, caption=caption, has_spoiler=spoiler)
+ )
+ elif name.endswith(".gif"):
+ animations.append(file)
+ return await make_chunks(images, videos, animations)
+
+
+async def make_chunks(images=[], videos=[], animations=[]):
+ chunk_imgs = [images[imgs : imgs + 5] for imgs in range(0, len(images), 5)]
+ chunk_vids = [videos[vids : vids + 5] for vids in range(0, len(videos), 5)]
+ return [*chunk_imgs, *chunk_vids, *animations]
diff --git a/app/core/scraper_config.py b/app/core/scraper_config.py
index d2b5a87..9173dc9 100644
--- a/app/core/scraper_config.py
+++ b/app/core/scraper_config.py
@@ -1,27 +1,35 @@
import shutil
+from enum import Enum, auto
+
+
+class MediaType(Enum):
+ PHOTO = auto()
+ VIDEO = auto()
+ GROUP = auto()
+ GIF = auto()
+ MESSAGE = auto()
class ScraperConfig:
def __init__(self):
+ self.dump = False
+ self.in_dump = False
self.path = ""
- self.link = ""
+ self.media = ""
self.caption = ""
self.caption_url = ""
self.thumb = None
+ self.type = None
self.success = False
- self.photo = False
- self.video = False
- self.group = False
- self.gif = False
- def set_sauce(self):
- self.caption_url = f"\n\nSauce"
+ def set_sauce(self, url):
+ self.caption_url = f"\n\nSauce"
@classmethod
async def start(cls, url):
obj = cls(url=url)
obj.query_url = url
- obj.set_sauce()
+ obj.set_sauce(url)
await obj.download_or_extract()
if obj.success:
return obj
diff --git a/app/plugins/authorise.py b/app/plugins/authorise.py
index bb4323f..6f7670c 100644
--- a/app/plugins/authorise.py
+++ b/app/plugins/authorise.py
@@ -49,103 +49,110 @@ def extract_chat(message):
return chat, err
-@bot.add_cmd(cmd="addsudo")
-async def add_sudo(bot, message):
+@bot.add_cmd(cmd=["addsudo", "delsudo"])
+async def add_or_remove_sudo(bot, message):
user, err = extract_user(message)
if err:
return await message.reply(err)
+
+ if message.cmd == "addsudo":
+ mode = "add"
+ task = Config.USERS.append
+ action = "Added to"
+ else:
+ mode = "remove"
+ task = Config.USERS.remove
+ action = "Removed from"
+
if err := await add_or_remove(
- mode="add",
- task=Config.USERS.append,
+ mode=mode,
+ task=task,
item=user,
config_list=Config.USERS,
message_id=Config.USERS_MESSAGE_ID,
):
return await message.reply(err, del_in=5)
- await message.reply("User Added to Authorised List.", del_in=5)
+ await message.reply(f"User {action} Authorised List.", del_in=5)
-@bot.add_cmd(cmd="delsudo")
-async def add_sudo(bot, message):
- user, err = extract_user(message)
- if err:
- return await message.reply(err)
- if err := await add_or_remove(
- mode="remove",
- task=Config.USERS.remove,
- item=user,
- config_list=Config.USERS,
- message_id=Config.USERS_MESSAGE_ID,
- ):
- return await message.reply(err, del_in=5)
- await message.reply("User Removed from Authorised List.", del_in=5)
-
-
-@bot.add_cmd(cmd="addchat")
-async def add_chat(bot, message):
+@bot.add_cmd(cmd=["addchat", "delchat"])
+async def add_or_remove_chat(bot, message):
chat, err = extract_chat(message)
if err:
return await message.reply(err)
+
+ if message.cmd == "addchat":
+ mode = "add"
+ task = Config.CHATS.append
+ action = "Added to"
+ else:
+ mode = "remove"
+ task = Config.CHATS.remove
+ action = "Removed from"
+
if err := await add_or_remove(
- mode="add",
- task=Config.CHATS.append,
+ mode=mode,
+ task=task,
item=chat,
config_list=Config.CHATS,
message_id=Config.AUTO_DL_MESSAGE_ID,
):
return await message.reply(err, del_in=5)
await message.reply(
- f"{message.chat.title or message.chat.first_name} Added to Authorised List.",
+ f"{message.chat.title or message.chat.first_name} {action} Authorised List.",
del_in=5,
)
-@bot.add_cmd(cmd="delchat")
-async def add_chat(bot, message):
- chat, err = extract_chat(message)
+@bot.add_cmd(cmd=["block", "unblock"])
+async def block_or_unblock(bot, message):
+ user, err = extract_user(message)
if err:
return await message.reply(err)
+
+ if message.cmd == "block":
+ mode = "add"
+ task = Config.BLOCKED_USERS.append
+ action = "Added to"
+ else:
+ mode = "remove"
+ task = Config.BLOCKED_USERS.remove
+ action = "Removed from"
+
if err := await add_or_remove(
- mode="remove",
- task=Config.CHATS.remove,
- item=chat,
- config_list=Config.CHATS,
- message_id=Config.AUTO_DL_MESSAGE_ID,
+ mode=mode,
+ task=task,
+ item=user,
+ config_list=Config.BLOCKED_USERS,
+ message_id=Config.BLOCKED_USERS_MESSAGE_ID,
+ ):
+ return await message.reply(err, del_in=5)
+ await message.reply(f"User {action} Ban List.", del_in=5)
+
+
+@bot.add_cmd(cmd=["enable", "disable"])
+async def auto_dl_trigger(bot, message):
+ if not Config.DISABLED_CHATS_MESSAGE_ID:
+ return await message.reply("You haven't added `DISABLED_CHATS_ID` Var, Add it.")
+
+ if message.cmd == "disable":
+ mode = "add"
+ task = Config.DISABLED_CHATS.append
+ action = "Added to"
+ else:
+ mode = "remove"
+ task = Config.DISABLED_CHATS.remove
+ action = "Removed from"
+
+ if err := await add_or_remove(
+ mode=mode,
+ task=task,
+ item=message.chat.id,
+ config_list=Config.DISABLED_CHATS,
+ message_id=Config.DISABLED_CHATS_MESSAGE_ID,
):
return await message.reply(err, del_in=5)
await message.reply(
- f"{message.chat.title or message.chat.first_name} Added Removed from Authorised List.",
+ f"{message.chat.title or message.chat.first_name} {action} Disabled List.",
del_in=5,
)
-
-
-@bot.add_cmd(cmd="block")
-async def add_sudo(bot, message):
- user, err = extract_user(message)
- if err:
- return await message.reply(err)
- if err := await add_or_remove(
- mode="add",
- task=Config.BLOCKED_USERS.append,
- item=user,
- config_list=Config.BLOCKED_USERS,
- message_id=Config.BLOCKED_USERS_MESSAGE_ID,
- ):
- return await message.reply(err, del_in=5)
- await message.reply("User Added to Ban List.", del_in=5)
-
-
-@bot.add_cmd(cmd="unblock")
-async def add_sudo(bot, message):
- user, err = extract_user(message)
- if err:
- return await message.reply(err)
- if err := await add_or_remove(
- mode="remove",
- task=Config.BLOCKED_USERS.remove,
- item=user,
- config_list=Config.BLOCKED_USERS,
- message_id=Config.BLOCKED_USERS_MESSAGE_ID,
- ):
- return await message.reply(err, del_in=5)
- await message.reply("User Removed from Ban List.", del_in=5)
diff --git a/app/plugins/bot.py b/app/plugins/bot.py
index 465099c..ccd2b4b 100755
--- a/app/plugins/bot.py
+++ b/app/plugins/bot.py
@@ -1,5 +1,7 @@
+import asyncio
import os
+from git import Repo
from pyrogram.enums import ChatType
from app import Config, bot
@@ -27,15 +29,50 @@ async def help(bot, message):
@bot.add_cmd(cmd="restart")
-async def restart(bot, message):
- reply = await message.reply("restarting....")
- if message.chat.type in [ChatType.GROUP, ChatType.SUPERGROUP]:
+async def restart(bot, message, u_resp=None):
+ reply = u_resp or await message.reply("restarting....")
+ if reply.chat.type in (ChatType.GROUP, ChatType.SUPERGROUP):
os.environ["RESTART_MSG"] = str(reply.id)
os.environ["RESTART_CHAT"] = str(reply.chat.id)
await bot.restart()
-@bot.add_cmd(cmd="update")
+@bot.add_cmd(cmd="refresh")
async def chat_update(bot, message):
await bot.set_filter_list()
- await message.reply("Filters Refreshed", del_in=10)
+ await message.reply("Filters Refreshed", del_in=8)
+
+
+@bot.add_cmd(cmd="repo")
+async def sauce(bot, message):
+ await bot.send_message(
+ chat_id=message.chat.id,
+ text=f"Social-DL",
+ reply_to_message_id=message.reply_id or message.id,
+ disable_web_page_preview=True,
+ )
+
+
+@bot.add_cmd(cmd="update")
+async def updater(bot, message):
+ reply = await message.reply("Checking for Updates....")
+ repo = Repo()
+ repo.git.fetch()
+ commits = ""
+ limit = 0
+ for commit in repo.iter_commits("HEAD..origin/main"):
+ commits += f"#{commit.count()} {commit.summary} By {commit.author}\n\n"
+ limit += 1
+ if limit > 50:
+ break
+ if not commits:
+ return await reply.edit("Already Up To Date.", del_in=5)
+ if "-pull" not in message.flags:
+ return await reply.edit(f"Update Available:\n\n{commits}")
+ repo.git.reset("--hard")
+ repo.git.pull(Config.UPSTREAM_REPO, "--rebase=true")
+ await asyncio.gather(
+ bot.log(text=f"#Updater\nPulled:\n\n{commits}"),
+ reply.edit("Update Found\nPulling...."),
+ )
+ await restart(bot, message, reply)
diff --git a/app/plugins/tgUtils.py b/app/plugins/tg_utils.py
similarity index 100%
rename from app/plugins/tgUtils.py
rename to app/plugins/tg_utils.py
diff --git a/app/social_dl.py b/app/social_dl.py
index abcab35..4264bd9 100644
--- a/app/social_dl.py
+++ b/app/social_dl.py
@@ -3,7 +3,7 @@ import traceback
from app import Config, bot
from app.core import filters
-from app.core.MediaHandler import ExtractAndSendMedia
+from app.core.media_handler import MediaHandler
from app.core.message import Message
@@ -12,8 +12,8 @@ async def dl(bot, message):
reply = await bot.send_message(
chat_id=message.chat.id, text="`trying to download...`"
)
- coro = ExtractAndSendMedia.process(message)
- task = asyncio.Task(coro, name=message.task_id)
+ coro = MediaHandler.process(message)
+ task = asyncio.Task(coro, name=reply.task_id)
media = await task
if media.exceptions:
exceptions = "\n".join(media.exceptions)
@@ -49,6 +49,7 @@ async def cmd_dispatcher(bot, message):
)
+@bot.on_message(filters.allowed_cmd_filter)
@bot.on_message(filters.chat_filter)
async def dl_dispatcher(bot, message):
message = Message.parse_message(message)
diff --git a/req.txt b/req.txt
index 1ac165a..8da304e 100644
--- a/req.txt
+++ b/req.txt
@@ -2,6 +2,7 @@ aiohttp>=3.8.4
beautifulsoup4>=4.12.2
Brotli>=1.0.9
gallery_dl>=1.25.7
+gitpython>=3.1.32
pyrogram>=2.0.106
python-dotenv==0.21.0
PyExecJS>=1.5.1
diff --git a/run b/run
index fec2c3d..fdaa87d 100644
--- a/run
+++ b/run
@@ -11,4 +11,9 @@ web.run_app(app, host='0.0.0.0', port=$API_PORT, reuse_port=True, print=None)
fi
-python3 -m app
\ No newline at end of file
+if ! [ -d ".git" ] ; then
+ git init
+fi
+
+
+python3 -m app