305 lines
8.5 KiB
Markdown
305 lines
8.5 KiB
Markdown
# 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 |
|