mirror of
https://github.com/anonymousx97/social-dl.git
synced 2025-02-20 11:13:19 +08:00
v2.0.5
Fixes Instagram: Stop api call if no key is added. Reddit: Fix bug for links without any media. Youtube: Stop PlayLists and Channel Urls from triggering download.MediaHandler: Clean up repetitive code and squash it. Client & Bot: Improve Error Logging. Authorise: Fix Chat name for DMs. Additions Add: Dockerfile, run Add: Plugin loader. Beta: Bot Mode.
This commit is contained in:
parent
dec02b64a4
commit
0bceaea93b
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
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"
|
||||
|
||||
# Exposing Ports for Web Server
|
||||
EXPOSE 8080 22 8022
|
||||
|
||||
# Adding Remote Container Start Command
|
||||
CMD bash -c "$(curl -fsSL https://raw.githubusercontent.com/anonymousx97/Docker/main/start)"
|
@ -26,6 +26,8 @@
|
||||
```
|
||||
|
||||
### Installation:
|
||||
Note: If deploying using Dockerfile add UPSTREAM_REPO var with repo link as it's value.
|
||||
|
||||
```bash
|
||||
# Install required packages.
|
||||
apt install -y python3 git curl python-pip ffmpeg
|
||||
|
@ -30,6 +30,8 @@ class Instagram(ScraperConfig):
|
||||
|
||||
|
||||
async def api_dl(self):
|
||||
if not Config.API_KEYS:
|
||||
return
|
||||
param = {"api_key": await self.get_key(), "url": self.url, "proxy": "residential", "js": False}
|
||||
response = await aiohttp_tools.get_json(url="https://api.webscraping.ai/html", timeout=30, params=param)
|
||||
if not response or "data" not in response or not response["data"]["shortcode_media"]:
|
||||
|
@ -45,7 +45,8 @@ class Reddit(ScraperConfig):
|
||||
|
||||
else:
|
||||
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"):
|
||||
self.gif = self.success = True
|
||||
else:
|
||||
|
@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import yt_dlp
|
||||
|
||||
@ -39,7 +40,11 @@ class YT_DL(ScraperConfig):
|
||||
}
|
||||
|
||||
async def download_or_extract(self):
|
||||
if "/playlist" in self.url:
|
||||
return
|
||||
if "youtu" in self.url:
|
||||
if not urlparse(self.url).query:
|
||||
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]"
|
||||
|
@ -4,9 +4,6 @@ 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
|
||||
@ -14,9 +11,10 @@ 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
|
||||
|
||||
# Thanks Jeel Patel for the concept TG[@jeelpatel231]
|
||||
|
||||
url_map = {
|
||||
"tiktok.com": Tiktok,
|
||||
"www.instagram.com": Instagram,
|
||||
@ -33,11 +31,15 @@ url_map = {
|
||||
class ExtractAndSendMedia:
|
||||
def __init__(self, message):
|
||||
self.exceptions, self.media_objects = [], []
|
||||
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}
|
||||
self.args_ = {
|
||||
"chat_id": self.message.chat.id,
|
||||
"reply_to_message_id": message.reply_id,
|
||||
}
|
||||
|
||||
def extract_sender(self):
|
||||
author = self.message.author_signature
|
||||
@ -68,41 +70,41 @@ class ExtractAndSendMedia:
|
||||
elif obj.group:
|
||||
await self.send_group(obj, caption=caption)
|
||||
elif obj.photo:
|
||||
await self.send_photo(obj.link, caption=caption)
|
||||
await self.send(
|
||||
media={"photo":obj.link},
|
||||
method=self.__client.send_photo,
|
||||
caption=caption,
|
||||
has_spoiler=self.spoiler,
|
||||
)
|
||||
elif obj.video:
|
||||
await self.send_video(obj.link, thumb=obj.thumb, caption=caption)
|
||||
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_animation(obj.link, caption=caption)
|
||||
await self.send(
|
||||
media={"animation":obj.link},
|
||||
method=self.__client.send_animation,
|
||||
caption=caption,
|
||||
has_spoiler=self.spoiler,
|
||||
unsave=True,
|
||||
)
|
||||
except BaseException:
|
||||
self.exceptions.append(traceback.format_exc())
|
||||
self.exceptions.append("\n".join([obj.caption_url.strip(), traceback.format_exc()]))
|
||||
|
||||
async def send_photo(self, photo, caption):
|
||||
async def send(self, media, method, **kwargs):
|
||||
try:
|
||||
try:
|
||||
await self.message._client.send_photo(**self.args_, photo=photo, caption=caption, has_spoiler=self.spoiler)
|
||||
await method(**media, **self.args_, **kwargs)
|
||||
except (MediaEmpty, WebpageCurlFailed):
|
||||
photo = await aiohttp_tools.in_memory_dl(photo)
|
||||
await self.message._client.send_photo(**self.args_, photo=photo, caption=caption, has_spoiler=self.spoiler)
|
||||
key, value = list(media.items())[0]
|
||||
media[key] = await aiohttp_tools.in_memory_dl(value)
|
||||
await method(**media, **self.args_, **kwargs)
|
||||
except PhotoSaveFileInvalid:
|
||||
await self.message._client.send_document(**self.args, document=photo, caption=caption, force_document=True)
|
||||
|
||||
async def send_video(self, video, caption, thumb):
|
||||
try:
|
||||
await self.message._client.send_video(
|
||||
**self.args_, video=video, caption=caption, thumb=await aiohttp_tools.thumb_dl(thumb), has_spoiler=self.spoiler
|
||||
)
|
||||
except (MediaEmpty, WebpageCurlFailed):
|
||||
video = await aiohttp_tools.in_memory_dl(video)
|
||||
await self.message._client.send_video(
|
||||
**self.args_, video=video, caption=caption, thumb=await aiohttp_tools.thumb_dl(thumb), has_spoiler=self.spoiler
|
||||
)
|
||||
|
||||
async def send_animation(self, gif, caption):
|
||||
try:
|
||||
await self.message._client.send_animation(**self.args_, animation=gif, caption=caption, unsave=True, has_spoiler=self.spoiler)
|
||||
except (MediaEmpty, WebpageCurlFailed):
|
||||
gif = await aiohttp_tools.in_memory_dl(gif)
|
||||
await self.message._client.send_animation(**self.args_, animation=gif, caption=caption, unsave=True, has_spoiler=self.spoiler)
|
||||
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:
|
||||
@ -112,10 +114,10 @@ class ExtractAndSendMedia:
|
||||
docs = glob.glob(f"{path}/*")
|
||||
for doc in docs:
|
||||
try:
|
||||
await self.message._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.message._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):
|
||||
@ -125,9 +127,9 @@ class ExtractAndSendMedia:
|
||||
sorted = await self.sort_media_urls(media.link, caption)
|
||||
for data in sorted:
|
||||
if isinstance(data, list):
|
||||
await self.message._client.send_media_group(**self.args_, media=data)
|
||||
await self.__client.send_media_group(**self.args_, media=data)
|
||||
else:
|
||||
await self.send_animation(data, caption=caption)
|
||||
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):
|
||||
|
@ -16,12 +16,15 @@ from app.core.message import Message
|
||||
|
||||
|
||||
class BOT(Client):
|
||||
_LOADED = False
|
||||
|
||||
def __init__(self):
|
||||
if string := os.environ.get("STRING_SESSION"):
|
||||
mode_arg = { "session_string": string }
|
||||
else:
|
||||
mode_arg = { "bot_token": os.environ.get("BOT_TOKEN") }
|
||||
super().__init__(
|
||||
name="bot",
|
||||
session_string=os.environ.get("STRING_SESSION"),
|
||||
**mode_arg,
|
||||
api_id=int(os.environ.get("API_ID")),
|
||||
api_hash=os.environ.get("API_HASH"),
|
||||
in_memory=True,
|
||||
@ -60,12 +63,11 @@ class BOT(Client):
|
||||
async def import_modules(self):
|
||||
for module_ in glob.glob("app/*/*.py"):
|
||||
importlib.import_module(os.path.splitext(module_.replace("/", "."))[0])
|
||||
self._LOADED = True
|
||||
|
||||
async def log(self, text, chat=None, func=None, name="log.txt"):
|
||||
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)
|
||||
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()
|
||||
|
@ -24,7 +24,7 @@ class Message(MSG):
|
||||
self.reply_id = replied.id
|
||||
|
||||
def flags_n_input(self):
|
||||
self.flags = [i for i in self.text.split() if i.startswith("-")]
|
||||
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]
|
||||
|
@ -15,7 +15,7 @@ class ScraperConfig:
|
||||
self.gif = False
|
||||
|
||||
def set_sauce(self, url):
|
||||
self.caption_url = f"\n\n<a href={url}>Sauce</a>"
|
||||
self.caption_url = f"\n\n<a href='{url}'>Sauce</a>"
|
||||
|
||||
@classmethod
|
||||
async def start(cls, url):
|
||||
|
@ -94,7 +94,7 @@ async def add_chat(bot, message):
|
||||
message_id=Config.AUTO_DL_MESSAGE_ID,
|
||||
):
|
||||
return await message.reply(err)
|
||||
await message.reply(f"<b>{message.chat.title}</b> Added to Authorised List.")
|
||||
await message.reply(f"<b>{message.chat.title or message.chat.first_name}</b> Added to Authorised List.")
|
||||
|
||||
|
||||
@bot.add_cmd(cmd="delchat")
|
||||
@ -110,7 +110,7 @@ async def add_chat(bot, message):
|
||||
message_id=Config.AUTO_DL_MESSAGE_ID,
|
||||
):
|
||||
return await message.reply(err)
|
||||
await message.reply(f"<b>{message.chat.title}</b> Added Removed from Authorised List.")
|
||||
await message.reply(f"<b>{message.chat.title or message.chat.first_name}</b> Added Removed from Authorised List.")
|
||||
|
||||
|
||||
|
||||
|
22
app/plugins/loader.py
Normal file
22
app/plugins/loader.py
Normal file
@ -0,0 +1,22 @@
|
||||
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.")
|
||||
file_name = message.replied.document.file_name.rstrip(".py")
|
||||
sys.modules.pop(f"app.temp.{file_name}", None)
|
||||
plugin = await message.replied.download("app/temp/")
|
||||
try:
|
||||
importlib.import_module(f"app.temp.{file_name}")
|
||||
except BaseException:
|
||||
return await message.reply(str(traceback.format_exc()))
|
||||
await message.reply(f"Loaded {file_name}.py.")
|
56
app/plugins/song.py
Normal file
56
app/plugins/song.py
Normal file
@ -0,0 +1,56 @@
|
||||
import asyncio
|
||||
import glob
|
||||
import shutil
|
||||
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"]
|
||||
|
||||
|
||||
@bot.add_cmd(cmd="song")
|
||||
async def song_dl(bot, message):
|
||||
reply_query = None
|
||||
audio_file = None
|
||||
artist = None
|
||||
if message.replied:
|
||||
for link in message.replied.text.split():
|
||||
if urlparse(link).netloc in domains:
|
||||
reply_query = link
|
||||
break
|
||||
query = reply_query or message.flt_input
|
||||
if not query:
|
||||
return await message.reply("Give a song name or link to download.")
|
||||
response = await message.reply("Searching....")
|
||||
dl_path = f"downloads/{time()}/"
|
||||
query_or_search = query if query.startswith("http") else f"ytsearch:{query}"
|
||||
if "-m" in message.flags:
|
||||
aformat = "mp3"
|
||||
else:
|
||||
aformat = "opus"
|
||||
yt_opts = {
|
||||
"logger": FakeLogger(),
|
||||
"outtmpl": dl_path + "%(title)s.%(ext)s",
|
||||
"format": "bestaudio",
|
||||
"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)
|
||||
if not query_or_search.startswith("http"):
|
||||
yt_info = yt_info["entries"][0]
|
||||
duration = yt_info["duration"]
|
||||
artist = yt_info["channel"]
|
||||
thumb = await in_memory_dl(yt_info["thumbnail"])
|
||||
down_path = glob.glob(dl_path + "*")
|
||||
if not down_path:
|
||||
return await response.edit("Not found")
|
||||
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 response.delete()
|
||||
shutil.rmtree(dl_path, ignore_errors=True)
|
@ -7,7 +7,7 @@ from pyrogram.enums import ParseMode
|
||||
|
||||
from app import Config
|
||||
from app.core import shell
|
||||
from app.core.aiohttp_tools import SESSION, in_memory_dl
|
||||
from app.core import aiohttp_tools as aio
|
||||
|
||||
# Run shell commands
|
||||
|
||||
|
@ -11,7 +11,7 @@ async def dl(bot, message):
|
||||
media = await ExtractAndSendMedia.process(message)
|
||||
if media.exceptions:
|
||||
exceptions = "\n".join(media.exceptions)
|
||||
await bot.log(text=exceptions, func="DL", chat=message.chat.id, name="traceback.txt")
|
||||
await bot.log(text=exceptions, func="DL", chat=message.chat.title or message.chat.first_name, name="traceback.txt")
|
||||
return await reply.edit(f"Media Download Failed.")
|
||||
if media.media_objects:
|
||||
await message.delete()
|
||||
@ -26,7 +26,7 @@ async def cmd_dispatcher(bot, message):
|
||||
try:
|
||||
await func(bot, parsed_message)
|
||||
except BaseException:
|
||||
await bot.log(text=str(traceback.format_exc()), chat=message.chat.id, 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")
|
||||
|
||||
|
||||
@bot.on_message(filters.chat_filter)
|
||||
@ -36,4 +36,4 @@ async def dl_dispatcher(bot, message):
|
||||
try:
|
||||
await func(bot, parsed_message)
|
||||
except BaseException:
|
||||
await bot.log(text=str(traceback.format_exc()), chat=message.chat.id, 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")
|
||||
|
14
run
Normal file
14
run
Normal file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$API_PORT" ] ; then
|
||||
py_code="
|
||||
from aiohttp import web
|
||||
app = web.Application()
|
||||
app.router.add_get('/', lambda _: web.Response(text='Web Server Running...'))
|
||||
web.run_app(app, host='0.0.0.0', port=$API_PORT, reuse_port=True, print=None)
|
||||
"
|
||||
python3 -q -c "$py_code" & echo "Dummy Web Server Started..."
|
||||
|
||||
fi
|
||||
|
||||
python3 -m app
|
Loading…
x
Reference in New Issue
Block a user