Version 1.4.4

This commit is contained in:
Taras Terletskyi 2023-11-11 15:09:14 +02:00
parent dcb61b84f1
commit bf881afccc
13 changed files with 112 additions and 29 deletions

View file

@ -2,7 +2,7 @@
Simple and reliable self-hosted YouTube Download Telegram Bot.
Version: 1.4.3. [Release details](RELEASES.md).
Version: 1.4.4. [Release details](RELEASES.md).
![frames](.assets/download_success.png)

View file

@ -1,3 +1,27 @@
## Release 1.4.4
Release date: November 11, 2023
## New Features
- New boolean config variable per user `is_admin`. Currently, admin users will receive update message when `yt-dlp` needs update.
```yaml
allowed_users:
- id: 11111111111
is_admin: !!bool True
***
```
## Important
- Every user in the config must include this new config variable whether it's `True` or `False`
## Misc
N/A
---
## Release 1.4.3
Release date: September 20, 2023

View file

@ -69,6 +69,7 @@ class TaskService:
payload = InbMediaPayload(
id=task_id,
url=task.url,
original_url=task.url,
added_at=added_at,
source=source,
download_media_type=task.download_media_type,

View file

@ -27,9 +27,13 @@ class VideoBot(Client):
self._log = logging.getLogger(self.__class__.__name__)
self._log.info('Initializing bot client')
self.allowed_users: dict[int, UserSchema] = {
user.id: user for user in self.conf.telegram.allowed_users
}
self.allowed_users: dict[int, UserSchema] = {}
self.admin_users: dict[int, UserSchema] = {}
for user in self.conf.telegram.allowed_users:
self.allowed_users[user.id] = user
if user.is_admin:
self.admin_users[user.id] = user
async def run_forever(self) -> None:
"""Firstly 'await bot.start()' should be called."""
@ -77,3 +81,13 @@ class VideoBot(Client):
user_ids=self.allowed_users.keys(),
parse_mode=parse_mode,
)
async def send_message_admins(
self, text: str, parse_mode: ParseMode = ParseMode.HTML
) -> None:
"""Send message to all defined user IDs in config.json."""
await self.send_message_to_users(
text=text,
user_ids=self.admin_users.keys(),
parse_mode=parse_mode,
)

View file

@ -31,10 +31,12 @@ class BotLauncher:
def _setup_handlers(self) -> None:
cb = TelegramCallback()
allowed_users = list(self._bot.allowed_users.keys())
self._bot.add_handler(
MessageHandler(
cb.on_start,
filters=filters.user(list(self._bot.allowed_users.keys()))
filters=filters.user(allowed_users)
& filters.command(['start', 'help']),
),
)
@ -43,10 +45,7 @@ class BotLauncher:
cb.on_message,
filters=(
filters.regex(self.REGEX_NOT_START_WITH_SLASH)
& (
filters.user(list(self._bot.allowed_users.keys()))
| filters.chat(list(self._bot.allowed_users.keys()))
)
& (filters.user(allowed_users) | filters.chat(allowed_users))
),
),
)

View file

@ -38,6 +38,7 @@ class UploadSchema(RealBaseModel):
class UserSchema(AnonymousUserSchema):
is_admin: StrictBool
send_startup_message: StrictBool
download_media_type: DownMediaType
save_to_storage: StrictBool

View file

@ -3,6 +3,7 @@ import os
import traceback
from pyrogram.enums import ParseMode
from pyrogram.errors import MessageIdInvalid, MessageNotModified
from yt_shared.emoji import SUCCESS_EMOJI
from yt_shared.enums import MediaFileType, TaskSource
from yt_shared.rabbit.publisher import RmqPublisher
@ -19,6 +20,8 @@ from bot.core.utils import bold
class SuccessDownloadHandler(AbstractDownloadHandler):
"""Handle successfully downloaded media context."""
_body: SuccessDownloadPayload
_UPLOAD_TASK_MAP = {
MediaFileType.AUDIO: AudioUploadTask,
@ -36,10 +39,13 @@ class SuccessDownloadHandler(AbstractDownloadHandler):
self._cleanup()
async def _handle(self) -> None:
coro_tasks = [self._delete_acknowledge_message()]
coro_tasks = []
for media_object in self._body.media.get_media_objects():
coro_tasks.append(self._handle_media_object(media_object))
await asyncio.gather(*coro_tasks)
try:
await asyncio.gather(*coro_tasks)
finally:
await self._delete_acknowledge_message()
async def _delete_acknowledge_message(self) -> None:
await self._bot.delete_messages(
@ -47,6 +53,22 @@ class SuccessDownloadHandler(AbstractDownloadHandler):
message_ids=[self._body.context.ack_message_id],
)
async def _set_upload_message(self) -> None:
try:
await self._bot.edit_message_text(
chat_id=self._body.from_chat_id,
message_id=self._body.context.ack_message_id,
text=f'⬆️ {bold("Uploading")}',
)
except (MessageIdInvalid, MessageNotModified) as err:
# Expected behaviour when several links where pasted in one message and
# the acknowledgment message was deleted after the first successful upload
self._log.warning(
'Could not edit the message id "%s": %s',
self._body.context.ack_message_id,
err,
)
async def _publish_error_message(self, err: Exception) -> None:
err_payload = ErrorDownloadGeneralPayload(
task_id=self._body.task_id,
@ -68,7 +90,11 @@ class SuccessDownloadHandler(AbstractDownloadHandler):
await self._send_success_text(media_object)
if self._upload_is_enabled():
self._validate_file_size_for_upload(media_object)
await self._create_upload_task(media_object)
coros = (
self._create_upload_task(media_object),
self._set_upload_message(),
)
await asyncio.gather(*coros)
else:
self._log.warning(
'File "%s" will not be uploaded due to upload configuration',
@ -149,10 +175,10 @@ class SuccessDownloadHandler(AbstractDownloadHandler):
if not os.path.exists(media_obj.filepath):
raise ValueError(f'{media_obj.file_type} {media_obj.filepath} not found')
file_size = os.stat(media_obj.filepath).st_size
if file_size > max_file_size:
_file_size = media_obj.current_file_size()
if _file_size > max_file_size:
err_msg = (
f'{media_obj.file_type} file size of {file_size} bytes bigger than '
f'{media_obj.file_type} file size of {_file_size} bytes bigger than '
f'allowed {max_file_size} bytes. Will not upload'
)
self._log.warning(err_msg)

View file

@ -72,7 +72,7 @@ class YtdlpNewVersionNotifyTask(AbstractTask):
f'Current version: {bold(ctx.current.version)}\n'
f'Rebuild worker with {code("docker compose build --no-cache worker")}'
)
await self._bot.send_message_all(text)
await self._bot.send_message_admins(text)
async def _notify_up_to_date(
self, ctx: VersionContext, user_ids: list[int]

View file

@ -1 +1 @@
__version__ = '1.4.3'
__version__ = '1.4.4'

View file

@ -8,6 +8,7 @@ telegram:
- "^http(s)?:\\/\\/.+$"
allowed_users: # Multiple users/groups are allowed.
- id: 11111111111 # User or group ID.
is_admin: !! bool True
send_startup_message: !!bool True
download_media_type: "VIDEO"
save_to_storage: !!bool False

View file

@ -0,0 +1,23 @@
import datetime
import logging
import aiohttp
from yt_shared.schemas.ytdlp import LatestVersion
class YtDlpGithubClient:
"""yt-dlp Github version number checker."""
LATEST_TAG_URL = 'https://github.com/yt-dlp/yt-dlp/releases/latest'
def __init__(self) -> None:
self._log = logging.getLogger(self.__class__.__name__)
async def get_latest_version(self) -> LatestVersion:
async with aiohttp.ClientSession() as session:
async with session.get(self.LATEST_TAG_URL) as resp:
version = resp.url.parts[-1]
self._log.info('Latest yt-dlp version: %s', version)
return LatestVersion(
version=version, retrieved_at=datetime.datetime.utcnow()
)

View file

@ -17,8 +17,8 @@ class RabbitMQ:
self._log = logging.getLogger(self.__class__.__name__)
self._config = get_rabbit_config()
self.connection: RobustConnection = None
self.channel: RobustChannel = None
self.connection: RobustConnection | None = None
self.channel: RobustChannel | None = None
self.exchanges: dict[str, AbstractRobustExchange] = {}
self.queues: dict[str, AbstractRobustQueue] = {}
@ -30,7 +30,7 @@ class RabbitMQ:
async def _set_connection(self) -> None:
self.connection = await aio_pika.connect_robust(
settings.RABBITMQ_URI,
url=settings.RABBITMQ_URI,
loop=get_running_loop(),
reconnect_interval=self.RABBITMQ_RECONNECT_INTERVAL,
)

View file

@ -1,9 +1,8 @@
import asyncio
import datetime
import logging
import aiohttp
from sqlalchemy.ext.asyncio import AsyncSession
from yt_shared.clients.github import YtDlpGithubClient
from yt_shared.repositories.ytdlp import YtdlpRepository
from yt_shared.schemas.ytdlp import CurrentVersion, LatestVersion, VersionContext
@ -16,6 +15,7 @@ class YtdlpVersionChecker:
def __init__(self) -> None:
self._log = logging.getLogger(self.__class__.__name__)
self._ytdlp_repository = YtdlpRepository()
self._ytdlp_client = YtDlpGithubClient()
async def get_version_context(self, db: AsyncSession) -> VersionContext:
latest, current = await asyncio.gather(
@ -24,13 +24,7 @@ class YtdlpVersionChecker:
return VersionContext(latest=latest, current=current)
async def get_latest_version(self) -> LatestVersion:
async with aiohttp.ClientSession() as session:
async with session.get(self.LATEST_TAG_URL) as resp:
version = resp.url.parts[-1]
self._log.info('Latest yt-dlp version: %s', version)
return LatestVersion(
version=version, retrieved_at=datetime.datetime.utcnow()
)
return await self._ytdlp_client.get_latest_version()
async def get_current_version(self, db: AsyncSession) -> CurrentVersion:
ytdlp_ = await self._ytdlp_repository.get_current_version(db)