From 57a7ee11e662056df686aaeff02fd6d171966dd9 Mon Sep 17 00:00:00 2001 From: itqop Date: Sun, 23 Feb 2025 16:12:10 +0300 Subject: [PATCH] dev: Init redis, and development bot --- .gitignore | 0 docker-compose.yml | 10 ++ telegram_bot/.gitignore | 174 +++++++++++++++++++++++++ telegram_bot/__init__.py | 0 telegram_bot/config.py | 11 ++ telegram_bot/handlers/__init__.py | 6 + telegram_bot/handlers/audio_handler.py | 40 ++++++ telegram_bot/handlers/start_handler.py | 12 ++ telegram_bot/main.py | 27 ++++ telegram_bot/requirements.txt | 3 + telegram_bot/services/__init__.py | 0 telegram_bot/services/redis_service.py | 10 ++ 12 files changed, 293 insertions(+) create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 telegram_bot/.gitignore create mode 100644 telegram_bot/__init__.py create mode 100644 telegram_bot/config.py create mode 100644 telegram_bot/handlers/__init__.py create mode 100644 telegram_bot/handlers/audio_handler.py create mode 100644 telegram_bot/handlers/start_handler.py create mode 100644 telegram_bot/main.py create mode 100644 telegram_bot/requirements.txt create mode 100644 telegram_bot/services/__init__.py create mode 100644 telegram_bot/services/redis_service.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cd12242 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + redis: + image: redis:7 + container_name: redis + ports: + - "6379:6379" + volumes: + - redis_data:/data +volumes: + redis_data: diff --git a/telegram_bot/.gitignore b/telegram_bot/.gitignore new file mode 100644 index 0000000..1800114 --- /dev/null +++ b/telegram_bot/.gitignore @@ -0,0 +1,174 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc \ No newline at end of file diff --git a/telegram_bot/__init__.py b/telegram_bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telegram_bot/config.py b/telegram_bot/config.py new file mode 100644 index 0000000..33bdab8 --- /dev/null +++ b/telegram_bot/config.py @@ -0,0 +1,11 @@ +import os +from dotenv import load_dotenv + +def load_config(): + load_dotenv() + config = type("Config", (), {})() + config.TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN") + config.REDIS_HOST = os.getenv("REDIS_HOST") + config.REDIS_PORT = int(os.getenv("REDIS_PORT", "6379")) + config.BOT_STORAGE_PATH = os.getenv("BOT_STORAGE_PATH", "./storage") + return config diff --git a/telegram_bot/handlers/__init__.py b/telegram_bot/handlers/__init__.py new file mode 100644 index 0000000..84d4155 --- /dev/null +++ b/telegram_bot/handlers/__init__.py @@ -0,0 +1,6 @@ +from .start_handler import register_start_handler +from .audio_handler import register_audio_handlers + +def register_all_handlers(dp, redis_service, storage_path: str): + register_start_handler(dp) + register_audio_handlers(dp, redis_service, storage_path) diff --git a/telegram_bot/handlers/audio_handler.py b/telegram_bot/handlers/audio_handler.py new file mode 100644 index 0000000..05cc5b2 --- /dev/null +++ b/telegram_bot/handlers/audio_handler.py @@ -0,0 +1,40 @@ +import os +import uuid +from functools import partial +from aiogram import types, Dispatcher, F + +async def handle_voice_and_video(message: types.Message, redis_service, storage_path: str): + file_id = None + if message.content_type == types.ContentType.VOICE: + file_id = message.voice.file_id + elif message.content_type == types.ContentType.VIDEO_NOTE: + file_id = message.video_note.file_id + + if not file_id: + return + + file = await message.bot.get_file(file_id) + file_path = file.file_path + + file_uuid = str(uuid.uuid4()) + filename = f"{file_uuid}_{os.path.basename(file_path)}" + os.makedirs(storage_path, exist_ok=True) + destination = os.path.join(storage_path, filename) + + await message.bot.download_file(file_path, destination) + + task_data = { + "uuid": file_uuid, + "file_local_path": destination, + "user_id": message.from_user.id, + "chat_id": message.chat.id + } + redis_service.publish_task(task_data) + await message.reply("Your message has been received and queued for processing.") + +def register_audio_handlers(dp: Dispatcher, redis_service, storage_path: str): + handler_callback = partial(handle_voice_and_video, redis_service=redis_service, storage_path=storage_path) + dp.message.register( + handler_callback, + F.content_type.in_({types.ContentType.VOICE, types.ContentType.VIDEO_NOTE}) + ) diff --git a/telegram_bot/handlers/start_handler.py b/telegram_bot/handlers/start_handler.py new file mode 100644 index 0000000..63ef5cf --- /dev/null +++ b/telegram_bot/handlers/start_handler.py @@ -0,0 +1,12 @@ +from aiogram import types, Dispatcher +from aiogram.filters import CommandStart +from aiogram.types import Message + +def register_start_handler(dp: Dispatcher): + @dp.message(CommandStart()) + async def command_start_handler(message: Message) -> None: + greeting_text = ( + "Hello! I'm a bot that processes your voice and video messages. \n" + "Just send me a voice or a video note, and I'll do magic." + ) + await message.answer(greeting_text) diff --git a/telegram_bot/main.py b/telegram_bot/main.py new file mode 100644 index 0000000..380ded9 --- /dev/null +++ b/telegram_bot/main.py @@ -0,0 +1,27 @@ +import asyncio +import logging +from aiogram import Bot, Dispatcher +from aiogram.client.bot import DefaultBotProperties +from config import load_config +from handlers import register_all_handlers +from services.redis_service import RedisService + +async def main(): + config = load_config() + + bot = Bot(token=config.TELEGRAM_TOKEN, default=DefaultBotProperties(parse_mode="HTML")) + + dp = Dispatcher(bot=bot) + + redis_service = RedisService(config.REDIS_HOST, config.REDIS_PORT) + + register_all_handlers(dp, redis_service, config.BOT_STORAGE_PATH) + + try: + await dp.start_polling(bot) + finally: + await bot.session.close() + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + asyncio.run(main()) diff --git a/telegram_bot/requirements.txt b/telegram_bot/requirements.txt new file mode 100644 index 0000000..0de3c1d --- /dev/null +++ b/telegram_bot/requirements.txt @@ -0,0 +1,3 @@ +aiogram +python-dotenv +redis \ No newline at end of file diff --git a/telegram_bot/services/__init__.py b/telegram_bot/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telegram_bot/services/redis_service.py b/telegram_bot/services/redis_service.py new file mode 100644 index 0000000..4babc55 --- /dev/null +++ b/telegram_bot/services/redis_service.py @@ -0,0 +1,10 @@ +import json +import redis + +class RedisService: + def __init__(self, host: str, port: int): + self.client = redis.Redis(host=host, port=port, decode_responses=True) + + def publish_task(self, task_data: dict): + channel = "audio_tasks" + self.client.publish(channel, json.dumps(task_data))