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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
BOT_TOKEN="INSERT_YOUR_BOT_TOKEN_HERE_MY_FRIEND"
PIXABAY_TOKEN="INSERT_YOUR_PIXABAY_TOKEN_MY_FRIEND"
PIXABAY_TOKEN="INSERT_YOUR_PIXABAY_TOKEN_MY_FRIEND"
HASH_SALT="INSERT_YOUR_SALT_HERE_MY_FRIEND"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.venv/
.env
.coverage
tmp/
tmp/
users.json
22 changes: 22 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"operation_canceled": "Operation canceled.",
"cancel_button": "❌ Cancel",
"confirm_mp3_download": "You are about to download an mp3",
"confirm_mp4_download": "You are about to download an mp4",
"file_too_large": "❌ File too large for Telegram (max 50MB).",
"loading": "⏳ Download in progress...",
"error_sending_file": "Error sending file!",
"error_removing_file": "Error removing file!",
"download_failed": "Download failed.",
"hello": "Hello",
"compare": "your trusty buddy is here to help you download your media!",
"user": "user.",
"download_params_error": "You need to format your message like this: /download <url>",
"download_content": "download your content!",
"beauty_params_error": "Please use /beauty without any additional arguments.",
"about_string": "⚡️ <b>Video Downloader Bot</b>\nDownload your favorite videos from YouTube and other services in seconds.\n\nCreated and maintained by the team:\n👨‍💻 Gianmarco [<a href='https://github.com/sean8819'>@sean8819</a>]\n👨‍💻 Marce [<a href='https://github.com/Marss08'>@Marss08</a>]\n👨‍💻 Enzo [<a href='https://github.com/enzobarba'>@enzobarba</a>]\n\n❤️ Thank you for choosing our bot!",
"services_string": "🛠 <b>My services</b>\n\n📥 <b>Media Download:</b> Send a link to easily download videos or audio tracks from YouTube and other supported services.\n\n🌍 <b>Multilingual (i18n):</b> Native support for Italian and English.\n\n🌅 <b>Daily Inspiration:</b> Receive the <i>beauty photo of the day</i> for a daily touch of wonder.\n\n",
"lang_format_error": "⚠️ Invalid format. Use: /lang it or /lang en",
"language_not_supported":"⚠️ Language not supported\nUse 'it' for Italian or 'en' for English.",
"lang_updated":"✅ Language successfully updated"
}
22 changes: 22 additions & 0 deletions lang/it.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"operation_canceled": "Operazione annullata.",
"cancel_button": "❌ Annulla",
"confirm_mp3_download": "Scaricherai un mp3",
"confirm_mp4_download": "Scaricherai un mp4",
"file_too_large":"❌ File troppo grande per Telegram (max 50MB).",
"loading":"⏳ Download in corso...",
"error_sending_file":"Errore invio file!",
"error_removing_file":"Errore eliminazione file!",
"download_failed":"Download fallito.",
"hello":"Ciao",
"compare":"il tuo compare di fiducia ti aiuta a scaricare i tuoi media!",
"user":"utente.",
"download_params_error":"Devi fornire un messaggio così formato /download <url risorsa>",
"download_content":"scarica il tuo contenuto!",
"beauty_params_error":"Usa solo /beauty senza altri argomenti.",
"about_string":"⚡️ <b>Video Downloader Bot</b>\nScarica i tuoi video preferiti da YouTube e altri servizi in pochi secondi.\n\nCreato e mantenuto dal team:\n👨‍💻 Gianmarco [<a href='https://github.com/sean8819'>@sean8819</a>]\n👨‍💻 Marce [<a href='https://github.com/Marss08'>@Marss08</a>]\n👨‍💻 Enzo [<a href='https://github.com/enzobarba'>@enzobarba</a>]\n\n❤️ Grazie per aver scelto il nostro bot!",
"services_string":"🛠 <b>I miei servizi</b>\n\n📥 <b>Download Media:</b> Invia un link per scaricare facilmente video o tracce audio da YouTube e altri servizi supportati.\n\n🌍 <b>Multilingua (i18n):</b> Supporto nativo per lingua italiana e inglese.\n\n🌅 <b>Ispirazione Quotidiana:</b> Ricevi la <i>foto della bellezza del giorno</i> per un tocco di meraviglia quotidiana.\n\n",
"lang_format_error": "⚠️ Comando non valido. Usa: /lang it o /lang en",
"language_not_supported":"⚠️ Lingua non supportata.\nUsa 'it' per Italiano o 'en' per Inglese.",
"lang_updated":"✅ Lingua cambiata con successo"
}
6 changes: 4 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)

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


Expand All @@ -28,13 +28,14 @@ async def post_init(application):
BotCommand("service", "Servizi disponibili"),
BotCommand("beauty", "Bellezza del giorno"),
BotCommand("about", "Info sul progetto"),
BotCommand("lang", "Cambia lingua del bot"),
]
)


def main():

load_dotenv()
load_dotenv(override=True)

logging.basicConfig(level=logging.ERROR)

Expand All @@ -53,6 +54,7 @@ def main():
app.add_handler(CommandHandler("about", about))
app.add_handler(CommandHandler("service", service))
app.add_handler(CommandHandler("beauty", beauty))
app.add_handler(CommandHandler("lang", lang))
app.add_handler(
CallbackQueryHandler(handle_buttons, pattern="^(audio|video|annulla)$")
)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ line_length = 120
ignore_missing_imports = true

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

[tool.pytest.ini_options]
asyncio_mode = "auto"
50 changes: 36 additions & 14 deletions src/buttons.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import asyncio
import os

from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, User
from telegram.error import TelegramError
from telegram.ext import CallbackContext, ContextTypes

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


def get_main_menu() -> InlineKeyboardMarkup:
def get_main_menu(user: User) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(
[
[
InlineKeyboardButton("🎶 Audio", callback_data="audio"),
InlineKeyboardButton("📹 Video", callback_data="video"),
],
[
InlineKeyboardButton("❌ Annulla", callback_data="annulla"),
InlineKeyboardButton(
get_string(user, "cancel_button"), callback_data="annulla"
),
],
]
)


def get_resolution_video() -> InlineKeyboardMarkup:
def get_resolution_video(user: User) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(
[
[
Expand All @@ -31,7 +34,9 @@ def get_resolution_video() -> InlineKeyboardMarkup:
InlineKeyboardButton("720p", callback_data="720"),
],
[
InlineKeyboardButton("❌ Annulla", callback_data="annulla"),
InlineKeyboardButton(
get_string(user, "cancel_button"), callback_data="annulla"
),
],
]
)
Expand All @@ -47,7 +52,9 @@ async def handle_resolution(update: Update, context: ContextTypes.DEFAULT_TYPE)
if query.data == "annulla":
if context.user_data is not None:
context.user_data.clear()
await query.edit_message_text("Operazione annullata.")
await query.edit_message_text(
get_string(update.effective_user, "operation_canceled")
)
return

elif query.data == "360":
Expand All @@ -74,20 +81,28 @@ async def handle_buttons(update: Update, context: ContextTypes.DEFAULT_TYPE) ->

if query.data == "annulla":
context.user_data.clear()
await query.edit_message_text("Operazione annullata.")
await query.edit_message_text(
get_string(update.effective_user, "operation_canceled")
)
return

if query.data == "audio":
context.user_data["download_audio"] = True
context.user_data["download_video"] = False
await handle_download(update, context)
await query.edit_message_text("Scaricherai un mp3")
await query.edit_message_text(
get_string(update.effective_user, "confirm_mp3_download")
)

elif query.data == "video":
context.user_data["download_video"] = True
context.user_data["download_audio"] = False
user = update.effective_user
if user is None:
return
await query.edit_message_text(
"Scaricherai un mp4", reply_markup=get_resolution_video()
get_string(update.effective_user, "confirm_mp4_download"),
reply_markup=get_resolution_video(user),
)


Expand Down Expand Up @@ -119,11 +134,13 @@ async def handle_download(update: Update, context: CallbackContext) -> None:

if size > telegram_max_upload_file:
await update.callback_query.edit_message_text(
"❌ File troppo grande per Telegram (max 50MB)."
get_string(update.effective_user, "file_too_large")
)
return

await update.callback_query.edit_message_text("⏳ Download in corso...")
await update.callback_query.edit_message_text(
get_string(update.effective_user, "loading")
)

file_path = await asyncio.to_thread(
get_media, context.user_data["url"], resolution, media_type
Expand Down Expand Up @@ -156,7 +173,7 @@ async def handle_download(update: Update, context: CallbackContext) -> None:
print(e)
await context.bot.send_message(
chat_id=chat_id,
text="Errore invio file!",
text=get_string(update.effective_user, "error_sending_file"),
)
finally:
if os.path.exists(file_path):
Expand All @@ -165,7 +182,12 @@ async def handle_download(update: Update, context: CallbackContext) -> None:
except OSError:
await context.bot.send_message(
chat_id=chat_id,
text="Errore eliminazione file!",
text=get_string(
update.effective_user, "error_removing_file"
),
)
else:
await context.bot.send_message(chat_id=chat_id, text="Download fallito.")
await context.bot.send_message(
chat_id=chat_id,
text=get_string(update.effective_user, "download_failed"),
)
83 changes: 62 additions & 21 deletions src/handlers.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
# pylint: disable=unused-argument


import validators
from telegram import Update
from telegram.ext import ContextTypes

import src.messages as message
from src.beauty import handle_beauty
from src.buttons import get_main_menu
from src.i18n import get_string, set_user_language


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if update.message and update.effective_user:
user = update.effective_user
if update.message and user:
set_user_language(user.id, "en")
await update.message.reply_text(
f"Ciao, {update.effective_user.first_name} il tuo compare di fiducia ti aiuta a scaricare i tuoi media!"
f"{get_string(user,'hello')} {user.first_name} {get_string(user,'compare')}"
)


async def about(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if update.message:

user = update.effective_user

if not user:
await update.message.reply_text("Ciao, utente informazioni sul progetto.")
await update.message.reply_text(
"Hello user.\n\nProject info.",
parse_mode="HTML",
disable_web_page_preview=True,
)
return

info = message.getAboutString(user)
await update.message.reply_text(
f"Ciao, {user.first_name} informazioni sul progetto."
f"{get_string(user, 'hello')} {user.first_name}\n\n{info}",
parse_mode="HTML",
disable_web_page_preview=True,
)


Expand All @@ -36,54 +45,86 @@ async def service(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
user = update.effective_user

if not user:
await update.message.reply_text("Ciao, utente stato dei servizi.")
await update.message.reply_text(
"Hello user.\n\nServices info.",
parse_mode="HTML",
)
return

await update.message.reply_text(f"Ciao, {user.first_name} stato dei servizi.")
services = message.getServiceString(user)
await update.message.reply_text(
f"{get_string(user,'hello')} {user.first_name}.\n\n{services}",
parse_mode="HTML",
)


async def download(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if not update.message or not update.message.text:
return

user = update.effective_user
if not user:
return

parts = update.message.text.split(" ", 1)

if len(parts) != 2:
await update.message.reply_text(
"Devi fornire un messaggio così formato /download <url risorsa>"
)
await update.message.reply_text(get_string(user, "download_params_error"))
return

url_video = parts[1]

# Verifico se è un url ben formato.
if validators.url(url_video):

if context.user_data is None:
return

context.user_data["url"] = url_video

user = update.effective_user

if not user:
return

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


async def beauty(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if not update.message or not update.message.text:
return

user = update.effective_user

raw_text = update.message.text.strip()
command = raw_text.split()[0]

if raw_text != command:
await update.message.reply_text("Usa solo /beauty senza altri argomenti.")
await update.message.reply_text(get_string(user, "beauty_params_error"))
return

await handle_beauty(update)


async def lang(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if not update.message or not update.message.text:
return

user = update.effective_user
if not user:
return

parts = update.message.text.split(" ", 1)

if len(parts) != 2:
error_message = get_string(user, "lang_format_error")
await update.message.reply_text(error_message)
return

requested_lang = parts[1].strip().lower()

if requested_lang not in ["it", "en"]:
await update.message.reply_text(get_string(user, "language_not_supported"))
return

set_user_language(user.id, requested_lang)

confirmation_message = get_string(user, "lang_updated")

await update.message.reply_text(confirmation_message)
Loading