1
0
mirror of https://github.com/ijaric/voice_assistant.git synced 2025-05-24 14:33:26 +00:00

feat: [#49] tg_bot

This commit is contained in:
Григорич 2023-10-13 23:05:25 +03:00
parent bc88ceffec
commit 8486d2e0bf
10 changed files with 247 additions and 0 deletions

3
src/bot_aiogram/Makefile Normal file
View File

@ -0,0 +1,3 @@
include ../../common_makefile.mk
PROJECT_FOLDERS = tgbot

View File

@ -0,0 +1,3 @@
[virtualenvs]
create = true
in-project = true

View 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

View 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)

View File

@ -0,0 +1,4 @@
import pathlib
BASE_PATH = pathlib.Path(__file__).parent.parent.parent.parent.resolve()
ENV_PATH = BASE_PATH / ".env"

View 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)

View File

@ -0,0 +1,7 @@
from .api import *
from .tgbot import *
__all__ = [
"ApiSettings",
"TgBotSettings",
]

View 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}"

View 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(",")))

View File

@ -0,0 +1,4 @@
import pathlib
BASE_PATH = pathlib.Path(__file__).parent.parent.parent.resolve()
ENV_PATH = BASE_PATH / ".env"