Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,53 @@
import logging
import os

from dotenv import load_dotenv
from telegram.ext import ApplicationBuilder, CallbackQueryHandler, CommandHandler
from telegram import BotCommand
from telegram.ext import (
ApplicationBuilder,
CallbackQueryHandler,
CommandHandler,
ContextTypes,
)

from src.buttons import handle_buttons, handle_resolution
from src.handlers import (
about,
beauty,
download,
service,
start,
)
from src.handlers import about, beauty, download, service, start
from src.logo import print_logo


# Altro modo per ignorare gli argomenti quando eseguiamo test del codice.
async def error_handler(_: object, context: ContextTypes.DEFAULT_TYPE) -> None:
logging.error("Eccezione:", exc_info=context.error)


async def post_init(application):
await application.bot.set_my_commands(
[
BotCommand("start", "Avvia il bot"),
BotCommand("download", "Scarica un media"),
BotCommand("service", "Servizi disponibili"),
BotCommand("beauty", "Bellezza del giorno"),
BotCommand("about", "Info sul progetto"),
]
)


def main():

load_dotenv()

app = ApplicationBuilder().token(os.getenv("BOT_TOKEN")).build()
logging.basicConfig(level=logging.ERROR)

app = (
ApplicationBuilder()
.token(os.getenv("BOT_TOKEN"))
.concurrent_updates(256)
.post_init(post_init)
.build()
)

app.add_error_handler(error_handler)

app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("download", download))
app.add_handler(CommandHandler("about", about))
Expand Down
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ profile = "black"
line_length = 120

[tool.mypy]
ignore_missing_imports = true
ignore_missing_imports = true

[tool.coverage.run]
omit = ["src/logo.py", "src/messages.py", "__init__.py"]

[tool.pytest.ini_options]
asyncio_mode = "auto"
3 changes: 2 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ flake8
black
isort
types-yt-dlp
types-requests
types-requests
pytest-asyncio
86 changes: 54 additions & 32 deletions src/buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from telegram.error import TelegramError
from telegram.ext import CallbackContext, ContextTypes

from src.downloader import get_media
from src.downloader import get_media, get_media_size


def get_main_menu() -> InlineKeyboardMarkup:
Expand Down Expand Up @@ -101,49 +101,71 @@ async def handle_download(update: Update, context: CallbackContext) -> None:

chat_id = update.callback_query.message.chat.id

await update.callback_query.edit_message_text("⏳ Download in corso...")

if context.user_data is not None:
# Se è impostato a true facciamo iniziare il download audio e passiamo come video resolution None.
if context.user_data.get("download_audio"):
file_path = await asyncio.to_thread(
get_media, context.user_data["url"], None, "mp3"
)
# Se è impostato a true facciamo iniziare il download video e passiamo come video resolution la risoluzione del video scelta.
media_type = "mp3"
resolution = None
elif context.user_data.get("download_video"):
file_path = await asyncio.to_thread(
get_media,
context.user_data["url"],
context.user_data["video_resolution"],
"mp4",
)
media_type = "mp4"
resolution = context.user_data["video_resolution"]
else:
return

if file_path and os.path.exists(file_path):
try:
telegram_max_upload_file = 50 * 1024 * 1024

await context.bot.send_message(
chat_id=chat_id,
text="😎 Caricamento su Telegram in corso!",
)
with open(file_path, "rb") as f:
await context.bot.send_document(
chat_id=chat_id,
document=f,
)
size = await asyncio.to_thread(
get_media_size, context.user_data["url"], resolution, media_type
)

os.remove(file_path) # eliminiamo il file dopo l'invio all'utente.
if size > telegram_max_upload_file:
await update.callback_query.edit_message_text(
"❌ File troppo grande per Telegram (max 50MB)."
)
return

await update.callback_query.edit_message_text("⏳ Download in corso...")

except TelegramError:
file_path = await asyncio.to_thread(
get_media, context.user_data["url"], resolution, media_type
)

if file_path and os.path.exists(file_path):
try:
ext = os.path.splitext(file_path)

if ext[1] == ".mp3":
with open(file_path, "rb") as f:
await context.bot.send_audio(
chat_id=chat_id,
audio=f,
read_timeout=300,
write_timeout=300,
connect_timeout=30,
)
else:
with open(file_path, "rb") as f:
await context.bot.send_video(
chat_id=chat_id,
video=f,
read_timeout=300,
write_timeout=300,
connect_timeout=30,
)

except TelegramError as e:
print(e)
await context.bot.send_message(
chat_id=chat_id,
text="Errore invio file!",
)
except OSError:
await context.bot.send_message(
chat_id=chat_id,
text="Errore eliminazione file!",
)
finally:
if os.path.exists(file_path):
try:
os.remove(file_path)
except OSError:
await context.bot.send_message(
chat_id=chat_id,
text="Errore eliminazione file!",
)
else:
await context.bot.send_message(chat_id=chat_id, text="Download fallito.")
23 changes: 20 additions & 3 deletions src/downloader.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import os
import uuid

import yt_dlp

DOWNLOAD_DIR = "./tmp/downloads"


def build_ydl_opts(video_resolution: int | None, media_type: str) -> dict:
def build_ydl_opts(video_resolution: int | None, media_type: str, download_id) -> dict:
if media_type == "mp3":
ydl_format = "bestaudio/best"
merge_format = "mp3"
Expand All @@ -17,18 +18,34 @@ def build_ydl_opts(video_resolution: int | None, media_type: str) -> dict:
"format": ydl_format,
"merge_output_format": merge_format,
"ffmpeg_location": "/usr/bin/ffmpeg",
"outtmpl": os.path.join(DOWNLOAD_DIR, "%(title)s.%(ext)s"),
"outtmpl": os.path.join(DOWNLOAD_DIR, f"{download_id}.%(title)s.%(ext)s"),
"quiet": False,
"noplaylist": True,
}


def get_media(video_url: str, video_resolution: int | None, media_type: str) -> str:
ydl_opts = build_ydl_opts(video_resolution, media_type)
download_id = str(uuid.uuid4())
ydl_opts = build_ydl_opts(video_resolution, media_type, download_id)
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl: # type: ignore[arg-type]
info = ydl.extract_info(video_url, download=True)
file_path = ydl.prepare_filename(info)
return file_path
except (yt_dlp.utils.DownloadError, yt_dlp.utils.ExtractorError):
return "error"


def get_media_size(
video_url: str, video_resolution: int | None, media_type: str
) -> int:
ydl_opts = build_ydl_opts(video_resolution, media_type, "size_check")
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl: # type: ignore[arg-type]
info = ydl.extract_info(video_url, download=False)
if not info:
return 0
filesize = info.get("filesize") or info.get("filesize_approx")
return filesize if isinstance(filesize, int) else 0
except yt_dlp.utils.DownloadError:
return 0
28 changes: 25 additions & 3 deletions src/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,28 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:

async def about(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if update.message:
await update.message.reply_text("Ciao informazioni sul progetto")

user = update.effective_user

if not user:
await update.message.reply_text("Ciao, utente informazioni sul progetto.")
return

await update.message.reply_text(
f"Ciao, {user.first_name} informazioni sul progetto."
)


async def service(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if update.message:
await update.message.reply_text('Ciao, {"Stato dei servizi"}!')

user = update.effective_user

if not user:
await update.message.reply_text("Ciao, utente stato dei servizi.")
return

await update.message.reply_text(f"Ciao, {user.first_name} stato dei servizi.")


async def download(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
Expand All @@ -47,8 +63,14 @@ async def download(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:

context.user_data["url"] = url_video

user = update.effective_user

if not user:
return

await update.message.reply_text(
"Ciao, scarica il tuo contenuto!", reply_markup=get_main_menu()
f"{user.first_name}, scarica il tuo contenuto!",
reply_markup=get_main_menu(),
)


Expand Down
Loading