Init
This commit is contained in:
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.12
|
||||
35
AGENTS.md
Normal file
35
AGENTS.md
Normal file
@@ -0,0 +1,35 @@
|
||||
## О проекте
|
||||
|
||||
О проекте - в README.md.
|
||||
|
||||
## Правила кодирования
|
||||
|
||||
### uv
|
||||
|
||||
В проекте используется uv. С помощью uv add добавляем зависимости.
|
||||
С помощью uv run запускаем.
|
||||
|
||||
### Type hints
|
||||
Используй современный синтаксис type hints:
|
||||
- `list[]`, `dict[]`, `tuple[]`, `set[]` вместо List, Dict, etc.
|
||||
- `| None` вместо Optional
|
||||
- Всегда указывай типы для параметров и возвращаемых значений
|
||||
|
||||
### Комментари ие комментарии в коде
|
||||
- Только короткие докстринги к функциям на одну-две строчки
|
||||
- Код должен быть самодокументируемым
|
||||
|
||||
### Логирование
|
||||
- Используй базовое логирование через модуль `src/logger.py`
|
||||
- **INFO** уровень - основные логи для пользователя (что происходит)
|
||||
- **DEBUG** уровень - детальная информация для отладки
|
||||
- Не спамь логами, только важная информация
|
||||
- Уровень логирования настраивается через `LOG_LEVEL` в `.env`
|
||||
|
||||
## Конфигурация
|
||||
Все секреты и настройки хранятся в `.env`. Смотри `.env.example` для примера.
|
||||
|
||||
### Переменные окружения
|
||||
- Не используй значения по умолчанию для переменных окружения
|
||||
- Если переменная не задана — выбрасывай ошибку при старте
|
||||
- Это помогает сразу обнаружить проблемы с конфигурацией
|
||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Телеграм бот для сбора датасета для автоматического протоколирования совещаний
|
||||
|
||||
## Данные
|
||||
|
||||
### Формат входных данных
|
||||
|
||||
Телеграм бот получает сценарии совещаний. Каждый сценарий представлен в виде отдельного файла в формате `json` с названием `<scenario_id>.json`. Сценарий состоит из списка реплик, для каждой из которых указан текст реплики и идентификатор диктора в рамках одного сценария.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"text": "text of the replica",
|
||||
"speaker_id": 0
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Результат работы бота
|
||||
|
||||
Бот сохраняет аудиофайлы с озвученными репликами в файлы `data/<scenario_id>/<replica_number>_<dataset_speaker_id>.wav`, где `<replica_number>` - это номер реплики в сценарии (0-indexed), `<dataset_speaker_id>` - это идентификатор диктора во всём датасете, по-сути, соответствует уникальному пользователю бота. Один диктор может озвучить несколько дорожек из разных совещаний. При этом он не может озвучивать две разные дорожки в рамках одного совещания, таким образом гарантируется, что разным `speaker_id` соответствуют разные дикторы в рамках одного совещания.
|
||||
|
||||
В `data` попадают только полностью озвученные дорожки. Под дорожкой подразумевается набор всех реплик одного диктора в рамках сценария. Дорожки, озвученные частично, хранятся в `data_partial/<scenario_id>/<replica_number>_<dataset_speaker_id>.wav`. Они автоматически переносятся в `data` после завершения озвучивания.
|
||||
|
||||
Тихие участки в начале и конце аудиофайла автоматически удаляются.
|
||||
|
||||
### База данных
|
||||
|
||||
Служебные данные, такие как состояния пользовательских сессий, соответствие `dataset_speaker_id` и `telegram_user_id` и т. п., сохраняются в базе данных SQLite. После перезагрузки бота его состояние полностью восстанавливается из базы данных.
|
||||
|
||||
## Интерфейс бота
|
||||
|
||||
### Состояния пользовательской сессии
|
||||
|
||||

|
||||
|
||||
- **INTRO** - начальное состояние сессии сразу после команды `/start`. Выводится сообщение о том, что это за бот и небольшое пользовательское соглашение, уведомляющее о целях сбора данных. Единственная кнопка - `Принять и продолжить`. После нажатия на неё условный переход либо в **NO_MORE_SCENARIOS**, если нету доступных сценариев для озвучивания, либо в **FIRST_REPLICA** .
|
||||
- **NO_MORE_SCENARIOS** - выводится сообщение о том, что пока больше нет сценариев для озвучивания. Из **NO_MORE_SCENARIOS** может произойти переход в **FIRST_REPLICA**, когда на сервер загружается новый сценарий, при этом выводится дополнительное уведомление. Самостоятельно пользователь не может покинуть это состояние.
|
||||
- **FIRST_REPLICA** - выводится первая реплика дорожки с дополнительными инструкциями для пользователя (`i = 0`). При отправке аудиосообщения с озвучкой реплики, происходит условный переход в **SHOW_REPLICA** (`i += 1`), если это не последняя реплика в дорожке, либо в **CONFIRM_SAVE**.
|
||||
- **SHOW_REPLICA** - выводится i-ая (0-indexed) реплика дорожки и её номер (1-indexed для пользователя, то есть i + 1). Есть две кнопки. Кнопка "Перезаписать предыдущую реплику", по ней условный переход в **FIRST_REPLICA**, если `i == 1`, либо в **SHOW_REPLICA** (`i -= 1`). Кнопка "Начать заново", по ней переход в **CONFIRM_RESTART**. При отправке аудиосообщения с озвучкой реплики, происходит условный переход в **SHOW_REPLICA** (`i += 1`), если это не последняя реплика в дорожке, либо в **CONFIRM_SAVE**.
|
||||
- **CONFIRM_RESTART** - выводится сообщение с предупреждением о том, что текущие результаты озвучивания будут удалены и информация о том, что в конце записи дорожки, можно будет перезаписать отдельные реплики. Две кнопки. Кнопка "Да, перезаписать", по ней переход в **FIRST_REPLICA**. Кнопка "Нет, продолжить", по ней возврат в **SHOW_REPLICA**.
|
||||
- **CONFIRM_SAVE** - выводится сообщение с предложением сохранить результаты озвучивания. Три кнопки. Кнопка "Да, сохранить", по ней условный переход в **FIRST_REPLICA** с выводом уведомления сообщения о сохранении дорожки, если ещё есть сценарии не озвученные этим диктором, иначе в **NO_MORE_SCENARIOS**. Кнопка "Перезаписать последнюю реплику", по ней возврат в **SHOW_REPLICA**. Кнопка "Перезаписать реплику по номеру", по ней переход в **ASK_REPLICA_NUMBER**.
|
||||
- **ASK_REPLICA_NUMBER** - выводится сообщение с предложением ввести номер реплики для перезаписи. Есть кнопка "Отмена", по ней возврат в **CONFIRM_SAVE**. После сообщения с номером реплики происходит переход в **REPEAT_REPLICA**.
|
||||
- **REPEAT_REPLICA** - выводится реплика с указанным номером (1-indexed для пользователя). После отправки аудиосообщения с озвучкой реплики возврат в **CONFIRM_SAVE**.
|
||||
|
||||
Некорректные пользовательский ввод не изменяет состояние, лишь выводится уведомление об ошибке.
|
||||
|
||||
### Другие особенности
|
||||
|
||||
Везде используются inline-кнопки. При этом кнопки под старыми сообщениями автоматически удаляются.
|
||||
|
||||
История сообщений остаётся в чате с ботом. UX не строится на редактировании сообщений.
|
||||
|
||||
В первую очередь пользователям предлагается озвучить дорожки, которые никто ещё не начал озвучивать. Затем те дорожки, которые кто-то начал озвучивать, но ещё не закончил. И топлько потом дорожки, для которых уже озвучка. Тем не менее, одна дорожка может быть озвучена несколькими дикторами.
|
||||
|
||||
## Администрирование
|
||||
|
||||
Бот управляется единственным администратором, чей Telegram логин указывается в переменной окружения `ADMIN_LOGIN`. Только у администратора доступна команда `/admin`, переводящая пользовательскую сессию в состояние **ADMIN**. Команда доступна из любого другого состояния. В **ADMIN** доступна кнопка "Вернуться в пользовательский режим", переводящая пользовательскую сессию в то состояние, из которого она была переведена в **ADMIN**. После перехода режим администратора выводится сообщение с информацией о текущем состоянии датасета: количество полностью озвученных сценариев, общее количество сценариев, количество полностью озвученных дорожек, общее количество дорожек, количество уникальных дикторов, количество озвученных реплик, общее количество реплик, количество уникальных пользователей бота и т. п.
|
||||
|
||||
В **ADMIN** администратор может отправить боту `json`-файл с новым сценарием. Это переводит пользовательскую сессию в состояние **ADMIN_UPLOAD_CONFIRM**, если файл корректен, иначе выводится сообщение об ошибке в формате файла. В **ADMIN_UPLOAD_CONFIRM** выводится сообщение с предложением добавить сценарий, а также дополнительная информация о сценарии: количество дорожек, количество реплик. Две кнопки: "Да, добавить" и "Отмена". Обе кнопки возвращают администратора в состояние **ADMIN**.
|
||||
BIN
bot-states.png
Normal file
BIN
bot-states.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 209 KiB |
6
main.py
Normal file
6
main.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def main():
|
||||
print("Hello from dataset-tg-bot!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
9
pyproject.toml
Normal file
9
pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "dataset-tg-bot"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"python-telegram-bot>=22.6",
|
||||
]
|
||||
108
uv.lock
generated
Normal file
108
uv.lock
generated
Normal file
@@ -0,0 +1,108 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14'",
|
||||
"python_full_version < '3.14'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dataset-tg-bot"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "python-telegram-bot" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "python-telegram-bot", specifier = ">=22.6" }]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-telegram-bot"
|
||||
version = "22.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpcore", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "httpx" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/9b/8df90c85404166a6631e857027866263adb27440d8af1dbeffbdc4f0166c/python_telegram_bot-22.6.tar.gz", hash = "sha256:50ae8cc10f8dff01445628687951020721f37956966b92a91df4c1bf2d113742", size = 1503761, upload-time = "2026-01-24T13:57:00.269Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/97/7298f0e1afe3a1ae52ff4c5af5087ed4de319ea73eb3b5c8c4dd4e76e708/python_telegram_bot-22.6-py3-none-any.whl", hash = "sha256:e598fe171c3dde2dfd0f001619ee9110eece66761a677b34719fb18934935ce0", size = 737267, upload-time = "2026-01-24T13:56:58.06Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user