feat: initial commit
This commit is contained in:
304
README.md
Normal file
304
README.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# gw_telegram
|
||||
|
||||
Bramka Telegram do n8n i systemów agentów AI. Bot przyjmuje wiadomości tekstowe i głosowe od użytkowników i routuje je do odpowiednich workflow w n8n lub agentów AI.
|
||||
|
||||
## Funkcje
|
||||
|
||||
- **Wiadomości tekstowe** — routing oparty na regułach regex do dowolnego workflow n8n
|
||||
- **Wiadomości głosowe** — pipeline: pobieranie OGG → konwersja ffmpeg (WAV 16kHz) → transkrypcja Whisper API → routing jak tekst
|
||||
- **Sesje użytkowników** — historia konwersacji przechowywana w Redis z konfigurowalnym TTL
|
||||
- **Graceful shutdown** — obsługa sygnałów `SIGINT`/`SIGTERM`, bot kończy obsługę aktualnych wiadomości przed zatrzymaniem
|
||||
- **Tryby pracy** — polling (domyślny) lub webhook
|
||||
|
||||
## Architektura
|
||||
|
||||
Projekt stosuje **Clean Architecture** z pełnym oddzieleniem warstw:
|
||||
|
||||
```
|
||||
cmd/bot/main.go # composition root — łączy wszystkie warstwy
|
||||
internal/
|
||||
domain/ # logika biznesowa, zero zależności zewnętrznych
|
||||
entity/ # modele: Message, Session, Intent, Route, WorkflowRequest
|
||||
port/ # interfejsy (bramy zależności)
|
||||
apperror/ # typowane błędy domenowe
|
||||
application/ # przypadki użycia
|
||||
usecase/ # HandleTextMessage, HandleVoiceMessage
|
||||
dto/ # obiekty transferu danych
|
||||
infrastructure/ # implementacje portów
|
||||
telegram/ # BotGateway, UpdatePoller, FileDownloader
|
||||
n8n/ # WebhookDispatcher
|
||||
speech/ # OpenAIWhisper, FFmpegConverter
|
||||
router/ # RuleBasedRouter
|
||||
storage/ # RedisSessionStore
|
||||
interfaces/ # warstwa wejściowa
|
||||
telegram_handler.go # mapowanie update → use case
|
||||
health_handler.go # endpointy /health i /ready
|
||||
config/ # ładowanie konfiguracji z env
|
||||
test/testutil/ # fake'i dla wszystkich portów (TDD)
|
||||
deploy/ # Dockerfile, docker-compose.yml
|
||||
```
|
||||
|
||||
### Pipeline wiadomości głosowej
|
||||
|
||||
```
|
||||
Telegram Voice Update
|
||||
│
|
||||
▼
|
||||
TelegramHandler.Handle()
|
||||
│
|
||||
▼
|
||||
HandleVoiceMessage.Execute()
|
||||
1. SendTyping (wskaźnik pisania)
|
||||
2. FileDownloader.Download(fileID) → OGG Opus bytes
|
||||
3. FFmpegConverter.Convert() → WAV 16kHz mono
|
||||
4. OpenAIWhisper.Transcribe() → tekst
|
||||
│
|
||||
▼
|
||||
HandleTextMessage.Execute() → routing → n8n → odpowiedź
|
||||
```
|
||||
|
||||
### Kontrakt JSON z n8n
|
||||
|
||||
Bot wysyła do webhooka n8n:
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "uuid-v4",
|
||||
"chat_id": 123456789,
|
||||
"user_id": 987654321,
|
||||
"username": "jankowalski",
|
||||
"message_text": "Gdzie jest moje zamówienie #12345?",
|
||||
"intent_name": "order_inquiry",
|
||||
"timestamp": "2026-04-16T10:00:00Z",
|
||||
"metadata": {
|
||||
"route_target_type": "n8n"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
n8n odpowiada:
|
||||
|
||||
```json
|
||||
{
|
||||
"reply": "Twoje zamówienie #12345 jest w drodze.",
|
||||
"actions": [],
|
||||
"next_workflow": null
|
||||
}
|
||||
```
|
||||
|
||||
Jeśli n8n zwróci surowy tekst (nie JSON), bot wyśle go bezpośrednio do użytkownika.
|
||||
|
||||
## Wymagania
|
||||
|
||||
- Go 1.22+
|
||||
- Redis 7+
|
||||
- ffmpeg (wymagany do obsługi wiadomości głosowych)
|
||||
- [Task](https://taskfile.dev) (`go install github.com/go-task/task/v3/cmd/task@latest`)
|
||||
- Token bota Telegram ([@BotFather](https://t.me/BotFather))
|
||||
- Klucz API OpenAI (do transkrypcji głosu)
|
||||
- Działająca instancja n8n
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
Konfiguracja odbywa się wyłącznie przez zmienne środowiskowe. Skopiuj `.env.example` jako punkt startowy:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### Zmienne środowiskowe
|
||||
|
||||
#### Bot Telegram (wymagane)
|
||||
|
||||
| Zmienna | Domyślna | Opis |
|
||||
|---|---|---|
|
||||
| `TELEGRAM_BOT_TOKEN` | — | Token bota z @BotFather |
|
||||
| `BOT_MODE` | `polling` | Tryb pracy: `polling` lub `webhook` |
|
||||
| `TELEGRAM_WEBHOOK_URL` | — | URL webhooka (tylko dla `BOT_MODE=webhook`) |
|
||||
| `TELEGRAM_DEBUG` | `false` | Włącza szczegółowe logi Telegram API |
|
||||
|
||||
#### n8n (wymagane)
|
||||
|
||||
| Zmienna | Domyślna | Opis |
|
||||
|---|---|---|
|
||||
| `N8N_BASE_URL` | — | Bazowy URL instancji n8n, np. `http://localhost:5678` |
|
||||
| `N8N_AUTH_TOKEN` | — | Token Bearer do uwierzytelniania webhooka |
|
||||
| `N8N_TIMEOUT` | `30` | Timeout żądania do n8n (sekundy) |
|
||||
| `N8N_RETRY_COUNT` | `3` | Liczba ponownych prób przy błędzie |
|
||||
|
||||
#### Speech-to-Text
|
||||
|
||||
| Zmienna | Domyślna | Opis |
|
||||
|---|---|---|
|
||||
| `STT_PROVIDER` | `openai` | Dostawca transkrypcji: `openai` |
|
||||
| `OPENAI_API_KEY` | — | Klucz API OpenAI (wymagany jeśli `STT_PROVIDER=openai`) |
|
||||
| `WHISPER_MODEL` | `whisper-1` | Model Whisper |
|
||||
| `WHISPER_LANGUAGE` | — | Kod języka BCP-47 (np. `pl`, `en`). Puste = autodetekcja |
|
||||
| `FFMPEG_PATH` | `ffmpeg` | Ścieżka do binarki ffmpeg |
|
||||
|
||||
#### Redis
|
||||
|
||||
| Zmienna | Domyślna | Opis |
|
||||
|---|---|---|
|
||||
| `REDIS_URL` | `redis://localhost:6379` | URL połączenia z Redis |
|
||||
| `SESSION_TTL` | `24` | Czas życia sesji użytkownika (godziny) |
|
||||
|
||||
#### Serwer HTTP
|
||||
|
||||
| Zmienna | Domyślna | Opis |
|
||||
|---|---|---|
|
||||
| `SERVER_PORT` | `8080` | Port serwera HTTP (`/health`, `/ready`) |
|
||||
|
||||
#### Logowanie
|
||||
|
||||
| Zmienna | Domyślna | Opis |
|
||||
|---|---|---|
|
||||
| `LOG_LEVEL` | `info` | Poziom logów: `debug`, `info`, `warn`, `error` |
|
||||
| `LOG_FORMAT` | `json` | Format logów: `json` lub `text` |
|
||||
|
||||
## Uruchomienie
|
||||
|
||||
### Lokalne (bez Dockera)
|
||||
|
||||
Wymagania: Go 1.22+, Redis, ffmpeg zainstalowane lokalnie.
|
||||
|
||||
```bash
|
||||
# 1. Sklonuj i przejdź do katalogu
|
||||
git clone <repo-url>
|
||||
cd gw_telegram
|
||||
|
||||
# 2. Pobierz zależności
|
||||
go mod download
|
||||
|
||||
# 3. Skonfiguruj zmienne środowiskowe
|
||||
cp .env.example .env
|
||||
# edytuj .env — uzupełnij TELEGRAM_BOT_TOKEN, N8N_BASE_URL, OPENAI_API_KEY
|
||||
|
||||
# 4. Eksportuj zmienne
|
||||
export $(grep -v '^#' .env | xargs)
|
||||
|
||||
# 5. Uruchom
|
||||
task run
|
||||
```
|
||||
|
||||
### Docker Compose (zalecane)
|
||||
|
||||
```bash
|
||||
# 1. Skonfiguruj zmienne
|
||||
cp .env.example .env
|
||||
# uzupełnij .env
|
||||
|
||||
# 2. Zbuduj i uruchom
|
||||
task docker:up
|
||||
|
||||
# 3. Sprawdź logi
|
||||
docker compose -f deploy/docker-compose.yml logs -f bot
|
||||
|
||||
# 4. Zatrzymaj
|
||||
task docker:down
|
||||
```
|
||||
|
||||
Docker Compose uruchamia bota razem z Redisem. Redis automatycznie persystuje dane sesji w woluminie `redis_data`.
|
||||
|
||||
### Binarka produkcyjna
|
||||
|
||||
```bash
|
||||
task build
|
||||
# binarka: ./bin/bot
|
||||
|
||||
TELEGRAM_BOT_TOKEN=xxx N8N_BASE_URL=http://n8n:5678 ./bin/bot
|
||||
```
|
||||
|
||||
## Routing wiadomości
|
||||
|
||||
Routing jest oparty na regułach regex z priorytetem. Reguły są definiowane w kodzie (`cmd/bot/main.go`) i dopasowywane od najwyższego priorytetu. Pierwsza pasująca reguła wygrywa.
|
||||
|
||||
### Domyślne reguły
|
||||
|
||||
| Pattern | Intent | Target | Priorytet |
|
||||
|---|---|---|---|
|
||||
| `^/start` | `start` | builtin | 100 |
|
||||
| `^/help` | `help` | builtin | 100 |
|
||||
| `.*` | `general_query` | n8n: `default` | 0 |
|
||||
|
||||
### Dodawanie nowego workflow n8n
|
||||
|
||||
1. Dodaj regułę w `cmd/bot/main.go`:
|
||||
|
||||
```go
|
||||
{
|
||||
Pattern: regexp.MustCompile(`(?i)zamówienie|order`),
|
||||
IntentName: "order_inquiry",
|
||||
Target: entity.RouteTarget{
|
||||
Type: entity.RouteTargetN8n,
|
||||
WorkflowID: "order-webhook",
|
||||
},
|
||||
Priority: 50,
|
||||
},
|
||||
```
|
||||
|
||||
2. Dodaj konfigurację workflow w mapie `workflows`:
|
||||
|
||||
```go
|
||||
workflows := map[string]n8n.WorkflowConfig{
|
||||
"default": {
|
||||
WebhookURL: cfg.N8n.BaseURL + "/webhook/default",
|
||||
AuthToken: cfg.N8n.AuthToken,
|
||||
},
|
||||
"order-webhook": {
|
||||
WebhookURL: cfg.N8n.BaseURL + "/webhook/order-inquiry",
|
||||
AuthToken: cfg.N8n.AuthToken,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Testy
|
||||
|
||||
```bash
|
||||
# Testy jednostkowe
|
||||
task test
|
||||
|
||||
# Testy z raportem pokrycia (HTML)
|
||||
task test:cover
|
||||
# otwórz coverage.html
|
||||
|
||||
# Testy integracyjne (wymaga Dockera)
|
||||
task test:int
|
||||
```
|
||||
|
||||
Projekt stosuje TDD. Każdy przypadek użycia ma testy oparte na ręcznie pisanych fake'ach portów (`test/testutil/`), bez generowanych mocków.
|
||||
|
||||
### Pokrycie testami
|
||||
|
||||
| Pakiet | Testowane scenariusze |
|
||||
|---|---|
|
||||
| `HandleTextMessage` | Happy path, pusty tekst, brak trasy, błąd dispatchera, persystencja sesji |
|
||||
| `HandleVoiceMessage` | Happy path, błąd pobierania pliku, błąd transkrypcji, konwersja audio |
|
||||
|
||||
## Linting
|
||||
|
||||
```bash
|
||||
task lint
|
||||
```
|
||||
|
||||
Projekt używa `golangci-lint`. Konfiguracja w `.golangci.yml`.
|
||||
|
||||
## Health Check
|
||||
|
||||
Serwer HTTP (`SERVER_PORT`) udostępnia:
|
||||
|
||||
- `GET /health` — zawsze zwraca `200 OK` jeśli proces działa
|
||||
- `GET /ready` — zwraca `200 OK` gdy połączenie z Telegram API i Redis jest aktywne
|
||||
|
||||
Przydatne do probes w Kubernetes i healthcheck w Docker Compose.
|
||||
|
||||
## Zależności
|
||||
|
||||
| Biblioteka | Zastosowanie |
|
||||
|---|---|
|
||||
| `go-telegram-bot-api/v5` | Telegram Bot API (polling + wysyłanie wiadomości) |
|
||||
| `kelseyhightower/envconfig` | Ładowanie konfiguracji ze zmiennych środowiskowych |
|
||||
| `redis/go-redis/v9` | Klient Redis do przechowywania sesji |
|
||||
| `google/uuid` | Generowanie RequestID |
|
||||
| `stretchr/testify` | Asercje w testach |
|
||||
| `log/slog` (stdlib) | Strukturalne logowanie JSON |
|
||||
Reference in New Issue
Block a user