mirror of
https://github.com/ijaric/voice_assistant.git
synced 2025-05-24 22:43:26 +00:00
feat: [#49] tg_bot
This commit is contained in:
parent
bc88ceffec
commit
8486d2e0bf
3
src/bot_aiogram/Makefile
Normal file
3
src/bot_aiogram/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
include ../../common_makefile.mk
|
||||||
|
|
||||||
|
PROJECT_FOLDERS = tgbot
|
3
src/bot_aiogram/poetry.toml
Normal file
3
src/bot_aiogram/poetry.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[virtualenvs]
|
||||||
|
create = true
|
||||||
|
in-project = true
|
142
src/bot_aiogram/pyproject.toml
Normal file
142
src/bot_aiogram/pyproject.toml
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
[build-system]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 120
|
||||||
|
target-version = ['py311']
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
known_first_party = ["backend", "tests"]
|
||||||
|
line_length = 120
|
||||||
|
profile = "black"
|
||||||
|
py_version = "311"
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
|
authors = ["jsdio@jsdio.ru"]
|
||||||
|
description = ""
|
||||||
|
name = "fastapi_project"
|
||||||
|
readme = "README.md"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
aiogram = "~2.18"
|
||||||
|
environs = "~9.0"
|
||||||
|
pytest-asyncio = "^0.21.1"
|
||||||
|
python = "^3.11"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
black = "^23.7.0"
|
||||||
|
isort = "^5.12.0"
|
||||||
|
pylint = "^2.17.5"
|
||||||
|
pylint-pydantic = "^0.2.4"
|
||||||
|
pylint-pytest = "^1.1.2"
|
||||||
|
pyright = "^1.1.318"
|
||||||
|
pyupgrade = "^3.10.1"
|
||||||
|
ruff = "^0.0.282"
|
||||||
|
sort-all = "^1.2.0"
|
||||||
|
toml-sort = "^0.23.1"
|
||||||
|
|
||||||
|
[tool.pylint]
|
||||||
|
disable = [
|
||||||
|
"broad-except",
|
||||||
|
"cannot-enumerate-pytest-fixtures",
|
||||||
|
"consider-using-from-import",
|
||||||
|
"consider-using-sys-exit",
|
||||||
|
"duplicate-code",
|
||||||
|
"fixme",
|
||||||
|
"missing-docstring",
|
||||||
|
"no-member",
|
||||||
|
"protected-access",
|
||||||
|
"too-few-public-methods",
|
||||||
|
"too-many-instance-attributes",
|
||||||
|
"too-many-locals",
|
||||||
|
"too-many-statements",
|
||||||
|
"unnecessary-ellipsis"
|
||||||
|
]
|
||||||
|
extension-pkg-allow-list = [
|
||||||
|
"orjson",
|
||||||
|
"pydantic"
|
||||||
|
]
|
||||||
|
ignore-path = [
|
||||||
|
"^.*venv/.*$"
|
||||||
|
]
|
||||||
|
load-plugins = [
|
||||||
|
"pylint_pydantic",
|
||||||
|
"pylint_pytest"
|
||||||
|
]
|
||||||
|
max-args = 15
|
||||||
|
max-line-length = 120
|
||||||
|
recursive = true
|
||||||
|
|
||||||
|
[tool.pylint.basic]
|
||||||
|
argument-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
|
||||||
|
attr-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
|
||||||
|
class-attribute-rgx = "^_{0,2}[a-zA-Z][a-zA-Z0-9_]*$"
|
||||||
|
variable-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
exclude = [
|
||||||
|
".pytest_cache",
|
||||||
|
".venv"
|
||||||
|
]
|
||||||
|
pythonPlatform = "All"
|
||||||
|
pythonVersion = "3.11"
|
||||||
|
reportConstantRedefenition = "none"
|
||||||
|
reportMissingTypeStubs = "none"
|
||||||
|
reportPrivateUsage = "information"
|
||||||
|
reportPropertyTypeMismatch = "warning"
|
||||||
|
reportUninitializedInstanceVariable = "warning"
|
||||||
|
reportUnknownMemberType = "none"
|
||||||
|
reportUnnecessaryTypeIgnoreComment = "warning"
|
||||||
|
reportUntypedFunctionDecorator = "warning"
|
||||||
|
typeCheckingMode = "strict"
|
||||||
|
useLibraryCodeForTypes = true
|
||||||
|
venv = ".venv"
|
||||||
|
venvPath = "."
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
ignore = [
|
||||||
|
# Pyright automatically infers the type of `self`
|
||||||
|
"ANN101",
|
||||||
|
# Pyright automatically infers the type of `cls`
|
||||||
|
"ANN102",
|
||||||
|
# In some cases actively detrimental; somewhat conflicts with black
|
||||||
|
"COM",
|
||||||
|
# Ignore missing docstrings
|
||||||
|
"D102",
|
||||||
|
# In combination with D213, this results in noisy diffs and inconsistencies
|
||||||
|
# See also <https://github.com/charliermarsh/ruff/issues/4174>.
|
||||||
|
"D200",
|
||||||
|
# This results inconsistencies between function and class docstrings
|
||||||
|
# See also <https://github.com/charliermarsh/ruff/issues/4175>.
|
||||||
|
"D202",
|
||||||
|
# D211 is preferred since the extra blank line isn't visually useful
|
||||||
|
"D203",
|
||||||
|
# D213 is preferred since it's more readable and allows more characters
|
||||||
|
"D212",
|
||||||
|
# Ignore missing docstrings
|
||||||
|
"D414",
|
||||||
|
# Covered by D401, which is more restrictive
|
||||||
|
"D415",
|
||||||
|
# Type-checkers interpret redundant `as` as exporting an item
|
||||||
|
"PLC0414",
|
||||||
|
# Permit using alias for 'import'
|
||||||
|
"PLR0402",
|
||||||
|
# Causes churn and awful looking import blocks for little gain
|
||||||
|
"TCH"
|
||||||
|
]
|
||||||
|
select = ["ALL"]
|
||||||
|
|
||||||
|
[tool.ruff.per-file-ignores]
|
||||||
|
"tests/*" = [
|
||||||
|
"D100",
|
||||||
|
"D103",
|
||||||
|
"D104",
|
||||||
|
"S101"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.tomlsort]
|
||||||
|
all = true
|
||||||
|
ignore_case = true
|
||||||
|
in_place = true
|
33
src/bot_aiogram/tgbot/handlers/voice.py
Normal file
33
src/bot_aiogram/tgbot/handlers/voice.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import io
|
||||||
|
|
||||||
|
import aiogram
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
import tgbot.settings as tgbot_settings
|
||||||
|
|
||||||
|
|
||||||
|
async def voice_response(message_voice: aiogram.types.Message):
|
||||||
|
config: tgbot_settings.Settings = message_voice.bot.get("config")
|
||||||
|
voice_file_id: str = message_voice.voice.file_id
|
||||||
|
file_info = await message_voice.bot.get_file(voice_file_id)
|
||||||
|
file_path: str = file_info.file_path
|
||||||
|
voice_data: io.BytesIO = io.BytesIO()
|
||||||
|
voice_data.name = "voice.ogg"
|
||||||
|
voice_data.seek(0)
|
||||||
|
await message_voice.bot.download_file(file_path, destination=voice_data)
|
||||||
|
await message_voice.bot.send_chat_action(message_voice.from_user.id, "typing")
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
f"{config.api.api_url}/api/v1/voice/",
|
||||||
|
data={"voice": voice_data},
|
||||||
|
) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
voice_answer = await resp.read()
|
||||||
|
await message_voice.answer_voice(voice_answer)
|
||||||
|
else:
|
||||||
|
await message_voice.answer("Not recognized text")
|
||||||
|
await session.close()
|
||||||
|
|
||||||
|
|
||||||
|
def register_voice_response(dp: aiogram.Dispatcher):
|
||||||
|
dp.register_message_handler(voice_response, content_types=aiogram.types.ContentType.VOICE)
|
4
src/bot_aiogram/tgbot/misc/utils.py
Normal file
4
src/bot_aiogram/tgbot/misc/utils.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
BASE_PATH = pathlib.Path(__file__).parent.parent.parent.parent.resolve()
|
||||||
|
ENV_PATH = BASE_PATH / ".env"
|
9
src/bot_aiogram/tgbot/settings.py
Normal file
9
src/bot_aiogram/tgbot/settings.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
import tgbot.split_settings as app_split_settings
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(pydantic_settings.BaseSettings):
|
||||||
|
api: app_split_settings.ApiSettings = pydantic.Field(default_factory=app_split_settings.ApiSettings)
|
||||||
|
tgbot: app_split_settings.TgBotSettings = pydantic.Field(default_factory=app_split_settings.TgBotSettings)
|
7
src/bot_aiogram/tgbot/split_settings/__init__.py
Normal file
7
src/bot_aiogram/tgbot/split_settings/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from .api import *
|
||||||
|
from .tgbot import *
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ApiSettings",
|
||||||
|
"TgBotSettings",
|
||||||
|
]
|
20
src/bot_aiogram/tgbot/split_settings/api.py
Normal file
20
src/bot_aiogram/tgbot/split_settings/api.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
import tgbot.split_settings.utils as split_settings_utils
|
||||||
|
|
||||||
|
|
||||||
|
class ApiSettings(pydantic_settings.BaseSettings):
|
||||||
|
model_config = pydantic_settings.SettingsConfigDict(
|
||||||
|
env_file=split_settings_utils.ENV_PATH,
|
||||||
|
env_prefix="API_",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
extra="ignore",
|
||||||
|
)
|
||||||
|
|
||||||
|
url: str
|
||||||
|
port: int
|
||||||
|
protocol: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_url(self) -> str:
|
||||||
|
return f"{self.protocol}://{self.url}:{self.port}"
|
22
src/bot_aiogram/tgbot/split_settings/tgbot.py
Normal file
22
src/bot_aiogram/tgbot/split_settings/tgbot.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
import tgbot.split_settings.utils as split_settings_utils
|
||||||
|
|
||||||
|
|
||||||
|
class TgBotSettings(pydantic_settings.BaseSettings):
|
||||||
|
model_config = pydantic_settings.SettingsConfigDict(
|
||||||
|
env_file=split_settings_utils.ENV_PATH,
|
||||||
|
env_prefix="BOT_",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
extra="ignore",
|
||||||
|
)
|
||||||
|
|
||||||
|
token: pydantic.SecretStr = pydantic.Field(
|
||||||
|
default=..., validation_alias=pydantic.AliasChoices("token", "bot_token")
|
||||||
|
)
|
||||||
|
admins: str = pydantic.Field(default="")
|
||||||
|
|
||||||
|
@pydantic.field_validator("admins")
|
||||||
|
def validate_bot_admins(cls, v: str) -> list[int]:
|
||||||
|
return list(map(int, v.split(",")))
|
4
src/bot_aiogram/tgbot/split_settings/utils.py
Normal file
4
src/bot_aiogram/tgbot/split_settings/utils.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
BASE_PATH = pathlib.Path(__file__).parent.parent.parent.resolve()
|
||||||
|
ENV_PATH = BASE_PATH / ".env"
|
Loading…
Reference in New Issue
Block a user