Version 1.5

This commit is contained in:
Taras Terletsky 2024-03-20 19:25:07 +02:00
parent f43d27319c
commit 7a33dc0434
24 changed files with 96 additions and 80 deletions

View file

@ -13,4 +13,4 @@
**/*Dockerfile*
LICENSE
README.md
pyproject.toml
.ruff.toml

View file

@ -1,10 +1,11 @@
[tool.ruff]
line-length = 88
lint.select = ["F", "E", "W", "I001"]
lint.ignore = ["E501"] # Skip line length violations
src = ["app_api", "app_bot", "app_worker"]
[tool.ruff.format]
[lint]
select = ["F", "E", "W", "I001"]
ignore = ["E501"] # Skip line length violations
[format]
indent-style = "space"
quote-style = "single"
line-ending = "lf"

View file

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

View file

@ -1,3 +1,22 @@
## Release 1.5
Release date: March 20, 2024
## New Features
N/A
## Important
N/A
## Misc
- Migrated from Pyrogram to Pyrofork
- Migrated from Python 3.11 to 3.12
---
## Release 1.4.5
Release date: November 23, 2023
@ -15,6 +34,7 @@ N/A
- Improved error handling
---
## Release 1.4.4
Release date: November 11, 2023

View file

@ -8,7 +8,7 @@ from api.api.api_v1.schemas.ytdlp import YTDLPLatestVersion
router = APIRouter()
@router.get('/')
@router.get('/', response_model_by_alias=False)
async def yt_dlp_version(db: AsyncSession = Depends(get_db)) -> YTDLPLatestVersion:
ctx = await YtdlpVersionChecker().get_version_context(db)
return YTDLPLatestVersion(

View file

@ -6,26 +6,20 @@ from pyrogram import Client
from pyrogram.enums import ParseMode
from pyrogram.errors import RPCError
from bot.core.config.config import get_main_config
from bot.core.schema import UserSchema
from bot.core.schema import ConfigSchema, UserSchema
from bot.core.utils import bold
class VideoBot(Client):
class VideoBotClient(Client):
"""Extended Pyrogram's `Client` class."""
_RUN_FOREVER_SLEEP_SECONDS = 86400
def __init__(self) -> None:
self.conf = get_main_config()
super().__init__(
name='default_name',
api_id=self.conf.telegram.api_id,
api_hash=self.conf.telegram.api_hash,
bot_token=self.conf.telegram.token,
)
def __init__(self, *args, conf: ConfigSchema, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._log = logging.getLogger(self.__class__.__name__)
self._log.info('Initializing bot client')
self.conf = conf
self.allowed_users: dict[int, UserSchema] = {}
self.admin_users: dict[int, UserSchema] = {}

View file

@ -5,8 +5,9 @@ from pyrogram.handlers import MessageHandler
from yt_shared.rabbit import get_rabbitmq
from yt_shared.utils.tasks.tasks import create_task
from bot.core.bot import VideoBot
from bot.bot.client import VideoBotClient
from bot.core.callbacks import TelegramCallback
from bot.core.config.config import get_main_config
from bot.core.tasks.ytdlp import YtdlpNewVersionNotifyTask
from bot.core.workers.manager import RabbitWorkerManager
@ -19,7 +20,14 @@ class BotLauncher:
def __init__(self) -> None:
"""Constructor."""
self._log = logging.getLogger(self.__class__.__name__)
self._bot = VideoBot()
self._conf = get_main_config()
self._bot = VideoBotClient(
name='default_name',
api_id=self._conf.telegram.api_id,
api_hash=self._conf.telegram.api_hash,
bot_token=self._conf.telegram.token,
conf=self._conf,
)
self._rabbit_mq = get_rabbitmq()
self._rabbit_worker_manager = RabbitWorkerManager(bot=self._bot)

View file

@ -1,7 +0,0 @@
from bot.core.bot.bot import VideoBot
from bot.core.bot.launcher import BotLauncher
__all__ = [
'BotLauncher',
'VideoBot',
]

View file

@ -4,7 +4,7 @@ from pyrogram.enums import ParseMode
from pyrogram.types import Message
from yt_shared.emoji import SUCCESS_EMOJI
from bot.core.bot import VideoBot
from bot.bot.client import VideoBotClient
from bot.core.service import UrlParser, UrlService
from bot.core.utils import bold, get_user_id
@ -19,14 +19,14 @@ class TelegramCallback:
self._url_service = UrlService()
@staticmethod
async def on_start(client: VideoBot, message: Message) -> None:
async def on_start(client: VideoBotClient, message: Message) -> None:
await message.reply(
bold('Send video URL to start processing'),
parse_mode=ParseMode.HTML,
reply_to_message_id=message.id,
)
async def on_message(self, client: VideoBot, message: Message) -> None:
async def on_message(self, client: VideoBotClient, message: Message) -> None:
"""Receive video URL and send to the download worker."""
self._log.debug('Received Telegram Message: %s', message)
text = message.text

View file

@ -3,22 +3,19 @@ import logging
from typing import TYPE_CHECKING
from yt_shared.enums import TaskSource, TelegramChatType
from yt_shared.schemas.error import ErrorDownloadGeneralPayload, ErrorDownloadPayload
from yt_shared.schemas.success import SuccessDownloadPayload
from yt_shared.schemas.base_rabbit import BaseRabbitDownloadPayload
from bot.core.schema import AnonymousUserSchema, UserSchema
if TYPE_CHECKING:
from bot.core.bot import VideoBot
from bot.bot import VideoBotClient
class AbstractDownloadHandler(abc.ABC):
def __init__(
self,
body: SuccessDownloadPayload
| ErrorDownloadPayload
| ErrorDownloadGeneralPayload,
bot: 'VideoBot',
body: BaseRabbitDownloadPayload,
bot: 'VideoBotClient',
) -> None:
self._log = logging.getLogger(self.__class__.__name__)
self._body = body

View file

@ -23,7 +23,7 @@ from bot.core.schema import AnonymousUserSchema, UserSchema, VideoCaptionSchema
from bot.core.utils import bold, is_user_upload_silent
if TYPE_CHECKING:
from bot.core.bot import VideoBot
from bot.bot.client import VideoBotClient
class BaseUploadContext(RealBaseModel):
@ -52,7 +52,7 @@ class AbstractUploadTask(AbstractTask, abc.ABC):
self,
media_object: BaseMedia,
users: list[AnonymousUserSchema | UserSchema],
bot: 'VideoBot',
bot: 'VideoBotClient',
semaphore: asyncio.Semaphore,
context: SuccessDownloadPayload,
) -> None:

View file

@ -12,11 +12,11 @@ from bot.core.config.config import get_main_config
from bot.core.utils import bold, code
if TYPE_CHECKING:
from bot.core.bot import VideoBot
from bot.bot import VideoBotClient
class YtdlpNewVersionNotifyTask(AbstractTask):
def __init__(self, bot: 'VideoBot') -> None:
def __init__(self, bot: 'VideoBotClient') -> None:
super().__init__()
self._bot = bot
self._version_checker = YtdlpVersionChecker()

View file

@ -100,9 +100,5 @@ def is_user_upload_silent(
user: UserSchema | AnonymousUserSchema, conf: ConfigSchema
) -> bool:
if isinstance(user, AnonymousUserSchema):
if conf.telegram.api.silent:
return True
elif user.upload.silent:
return True
else:
return False
return conf.telegram.api.silent
return user.upload.silent

View file

@ -13,7 +13,7 @@ from bot.core.config.config import get_main_config
from bot.core.exceptions import InvalidBodyError
if TYPE_CHECKING:
from bot.core.bot import VideoBot
from bot.bot.client import VideoBotClient
class RabbitWorkerType(enum.Enum):
@ -26,7 +26,7 @@ class AbstractDownloadResultWorker(AbstractTask):
QUEUE_TYPE: str | None = None
SCHEMA_CLS: tuple[Type[BaseModel]] = ()
def __init__(self, bot: 'VideoBot') -> None:
def __init__(self, bot: 'VideoBotClient') -> None:
super().__init__()
self._conf = get_main_config()
self._bot = bot

View file

@ -9,13 +9,13 @@ from bot.core.workers.error import ErrorDownloadResultWorker
from bot.core.workers.success import SuccessDownloadResultWorker
if TYPE_CHECKING:
from bot.core.bot import VideoBot
from bot.bot.client import VideoBotClient
class RabbitWorkerManager:
_TASK_TYPES = (ErrorDownloadResultWorker, SuccessDownloadResultWorker)
def __init__(self, bot: 'VideoBot') -> None:
def __init__(self, bot: 'VideoBotClient') -> None:
self._log = logging.getLogger(self.__class__.__name__)
self._bot = bot
self._workers: dict[RabbitWorkerType, Task] = {}

View file

@ -1 +1 @@
__version__ = '1.4.5'
__version__ = '1.5'

View file

@ -5,7 +5,7 @@ import asyncio
import uvloop
from bot.core.bot import BotLauncher
from bot.bot.launcher import BotLauncher
from bot.core.log import setup_logging

View file

@ -1,5 +1,5 @@
PyYAML==6.0.1
Pyrogram==2.0.106
addict==2.4.0
pyrofork==2.3.19.post2
tenacity==8.2.3
tgcrypto==1.2.5
typing_extensions==4.10.0

View file

@ -1,4 +1,4 @@
FROM python:3.11-alpine
FROM python:3.12-alpine
RUN apk add --no-cache \
tzdata \

View file

@ -1,19 +1,21 @@
import abc
from pydantic import BaseModel, ConfigDict
from yt_shared.enums import RabbitPayloadType
class RealBaseModel(BaseModel):
class RealBaseModel(BaseModel, abc.ABC):
"""Base Pydantic model. All models should inherit from this."""
model_config = ConfigDict(extra='forbid')
class BaseOrmModel(RealBaseModel):
class BaseOrmModel(RealBaseModel, abc.ABC):
model_config = ConfigDict(from_attributes=True, **RealBaseModel.model_config)
class BaseRabbitPayloadModel(RealBaseModel):
class BaseRabbitPayloadModel(RealBaseModel, abc.ABC):
"""Base RabbitMQ payload model. All RabbitMQ models should inherit from this."""
type: RabbitPayloadType

View file

@ -0,0 +1,15 @@
import abc
from pydantic import StrictInt
from yt_shared.enums import TelegramChatType
from yt_shared.schemas.base import BaseRabbitPayloadModel
from yt_shared.schemas.media import InbMediaPayload
class BaseRabbitDownloadPayload(BaseRabbitPayloadModel, abc.ABC):
context: InbMediaPayload
from_chat_id: StrictInt | None
from_chat_type: TelegramChatType | None
from_user_id: StrictInt | None
message_id: StrictInt | None

View file

@ -1,23 +1,17 @@
import uuid
from typing import Literal
from pydantic import StrictInt, StrictStr
from pydantic import StrictStr
from yt_shared.enums import RabbitPayloadType, TelegramChatType
from yt_shared.schemas.base import BaseRabbitPayloadModel
from yt_shared.schemas.media import InbMediaPayload
from yt_shared.enums import RabbitPayloadType
from yt_shared.schemas.base_rabbit import BaseRabbitDownloadPayload
class ErrorDownloadGeneralPayload(BaseRabbitPayloadModel):
class ErrorDownloadGeneralPayload(BaseRabbitDownloadPayload):
type: Literal[RabbitPayloadType.GENERAL_ERROR] = RabbitPayloadType.GENERAL_ERROR
task_id: uuid.UUID | StrictStr | None
from_chat_id: StrictInt | None
from_chat_type: TelegramChatType | None
from_user_id: StrictInt | None
message_id: StrictInt | None
message: StrictStr
url: StrictStr
context: InbMediaPayload
exception_msg: StrictStr
exception_type: StrictStr
yt_dlp_version: StrictStr | None

View file

@ -1,3 +1,4 @@
import abc
import uuid
from datetime import datetime, timezone
from typing import Literal
@ -34,7 +35,7 @@ class InbMediaPayload(RealBaseModel):
added_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
class BaseMedia(RealBaseModel):
class BaseMedia(RealBaseModel, abc.ABC):
"""Model representing abstract downloaded media with common fields."""
file_type: MediaFileType

View file

@ -1,22 +1,17 @@
import uuid
from typing import Literal
from pydantic import StrictInt, StrictStr
from pydantic import StrictStr
from yt_shared.enums import RabbitPayloadType, TelegramChatType
from yt_shared.schemas.base import BaseRabbitPayloadModel
from yt_shared.schemas.media import DownMedia, InbMediaPayload
from yt_shared.enums import RabbitPayloadType
from yt_shared.schemas.base_rabbit import BaseRabbitDownloadPayload
from yt_shared.schemas.media import DownMedia
class SuccessDownloadPayload(BaseRabbitPayloadModel):
class SuccessDownloadPayload(BaseRabbitDownloadPayload):
"""Payload with downloaded media context."""
type: Literal[RabbitPayloadType.SUCCESS] = RabbitPayloadType.SUCCESS
task_id: uuid.UUID
from_chat_id: StrictInt | None
from_chat_type: TelegramChatType | None
from_user_id: StrictInt | None
message_id: StrictInt | None
media: DownMedia
context: InbMediaPayload
yt_dlp_version: StrictStr | None