Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions CHANGELOG.md

This file was deleted.

189 changes: 117 additions & 72 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,48 @@ go get github.com/rekurt/ymsdk

## Quick start

### Via aggregator (recommended)

```go
package main

import (
"context"
"errors"
"fmt"
"os"

"github.com/rekurt/ymsdk/client"
"github.com/rekurt/ymsdk/client/ym"
"github.com/rekurt/ymsdk/client/ym/messages"
"github.com/rekurt/ymsdk/client/ym/ymerrors"
)

func main() {
token := os.Getenv("YM_TOKEN")
cl := ym.NewClient(ym.Config{
Token: token,
cs := client.New(ym.Config{
Token: os.Getenv("YM_TOKEN"),
ErrorHandling: ymerrors.ErrorHandlingConfig{
RetryStrategy: ymerrors.RetryStrategy{MaxAttempts: 3, RetryNetwork: true},
RateLimitHandling: ymerrors.RateLimitHandling{UseRetryAfter: true},
},
})

msgSvc := messages.NewService(cl)
msg, err := msgSvc.SendToChat(context.Background(), "chat-id", "hello", nil)
msg, err := cs.Messages.SendToChat(context.Background(), "chat-id", "hello", nil)
if err != nil {
handleErr(err)
fmt.Println("error:", err)
return
}
fmt.Println("sent message:", msg.ID)
}

func handleErr(err error) {
var apiErr *ymerrors.APIError
if errors.As(err, &apiErr) {
fmt.Printf("API error kind=%d http=%d desc=%s\n", apiErr.Kind, apiErr.HTTPStatus, apiErr.Description)
if errors.Is(err, ymerrors.ErrRateLimited) && apiErr.RetryAfter > 0 {
fmt.Printf("retry after: %s\n", apiErr.RetryAfter)
}
return
}
fmt.Println("unexpected error:", err)
}
```

See `examples/basic_send`, `examples/poller`, `examples/poll_bot`, `examples/integration`.
### Via individual services

```go
cl := ym.NewClient(ym.Config{Token: os.Getenv("YM_TOKEN")})
msgSvc := messages.NewService(cl)
pollSvc := polls.NewService(cl)

msg, _ := msgSvc.SendToChat(ctx, "chat-id", "hello", nil)
```

## Architecture

Expand All @@ -71,83 +66,129 @@ client/
├── ptr.go # ym.Ptr[T] helper for optional fields
├── validate.go # Shared recipient validation
├── ymerrors/ # Error types and configuration
├── messages/ # Text, files, images, galleries, delete
├── chats/ # Create chats, manage members
├── messages/ # Text, files, images, galleries, delete, getFile
├── chats/ # Create chats/channels, manage members
├── users/ # User chat/call deep links
├── polls/ # Polls, results, voters
├── updates/ # getUpdates and PollLoop
├── polls/ # Polls: create, results, voters, GetAllVoters
├── updates/ # getUpdates, GetUpdates, and PollLoop
├── self/ # Bot webhook_url management
└── files/ # Low-level file sending
└── files/ # Low-level file sending (byte[])
middleware/ # zap-based logging
├── logging.go # LogError, LogUpdateWithRawData, WithRequestID
├── debug.go # DebugLogger with levels (Silent → Debug)
└── http_logger.go # HTTP wrapper for request/response logging
```

## Services

- `messages.Service` — text, files, images/galleries, delete, getFile.
- `chats.Service` — create chats/channels, update members/subscribers/admins.
- `users.Service` — fetch chat_link/call_link for a login.
- `polls.Service` — create polls, get results, list voters.
- `updates.Service` — getUpdates and `PollLoop`.
- `self.Service` — `self.update` for webhook_url.
- `middleware` — zap-based error logging helpers.
- Convenience aggregator: `client.YMClient` with prebuilt services (`client.New(cfg)`).
| Service | Description |
|---------|-------------|
| `cs.Messages` | Text messages, files, images, galleries, delete, file download |
| `cs.Chats` | Create chats/channels, add/remove members, subscribers, admins |
| `cs.Users` | Get chat_link / call_link by login |
| `cs.Polls` | Create polls, results, paginated voters, GetAllVoters |
| `cs.Updates` | getUpdates (raw + typed), PollLoop for continuous polling |
| `cs.Self` | self.update for webhook_url configuration |
| `cs.Files` | Low-level file sending via byte[] |

Convenience aggregator `client.YMClient` with prebuilt services:
- `client.New(cfg)` — create with new HTTP client
- `client.Wrap(cl)` — wrap existing `ym.Client`

## Error handling

```go
var apiErr *ymerrors.APIError
if errors.As(err, &apiErr) {
fmt.Printf("kind=%d http=%d desc=%s request_id=%s\n",
apiErr.Kind, apiErr.HTTPStatus, apiErr.Description, apiErr.RequestID)

if errors.Is(err, ymerrors.ErrRateLimited) && apiErr.RetryAfter > 0 {
time.Sleep(apiErr.RetryAfter)
}
}
```

- API failures: `*ymerrors.APIError` (use `errors.As`).
- Rate limit: `errors.Is(err, ymerrors.ErrRateLimited)` + `RetryAfter`.
- Auth: `ErrInvalidToken` / `ErrUnauthorized`.
- Transport: `KindNetwork` / `net.Error` when `RetryNetwork` enabled.
- Auth: `ErrInvalidToken` (403) / `ErrUnauthorized` (401).
- Transport: `KindNetwork` (5xx) / `net.Error` when `RetryNetwork` enabled.

## Configuration

`ym.Config`:

- `BaseURL` — API endpoint (defaults to production).
- `Token` — OAuth token.
- `ErrorHandling`:
- `RetryStrategy`: `MaxAttempts`, `InitialBackoff`, `MaxBackoff`, `RetryHTTP`, `RetryNetwork`.
- `RateLimitHandling`: `UseRetryAfter`, `DefaultBackoff`.
- `UpdatesMode`: `polling` / `webhook` (explicit mode flag).

## Examples
```go
cfg := ym.Config{
BaseURL: "", // defaults to production endpoint
Token: os.Getenv("YM_TOKEN"),
ErrorHandling: ymerrors.ErrorHandlingConfig{
RetryStrategy: ymerrors.RetryStrategy{
MaxAttempts: 3,
InitialBackoff: 500 * time.Millisecond,
MaxBackoff: 10 * time.Second,
RetryNetwork: true,
RetryHTTP: []int{500, 502, 503, 504},
},
RateLimitHandling: ymerrors.RateLimitHandling{
UseRetryAfter: true,
DefaultBackoff: time.Second,
},
},
UpdatesMode: ymerrors.UpdatesModePolling, // "polling" or "webhook"
}
```

- `examples/basic_send` — send text to chat/login with error handling.
- `examples/poller` — polling loop respecting rate limits.
- `examples/poll_bot` — create a poll and process updates.
- `examples/integration` — end-to-end script hitting all SDK methods (configure via env vars).
- `examples/webhook` — minimal HTTP webhook receiver (webhook mode).
## Debug logging

### Quick via aggregator
Inspect raw HTTP request/response bodies with middleware:

```go
import (
"github.com/rekurt/ymsdk/client"
"github.com/rekurt/ymsdk/client/ym"
"github.com/rekurt/ymsdk/client/ym/polls"
)
logger, _ := zap.NewDevelopmentConfig().Build()
debugLogger := middleware.NewDebugLogger(logger, middleware.LogLevelDebug)
loggedHTTP := middleware.NewHTTPLogger(&http.Client{Timeout: 15 * time.Second}, debugLogger)

cs := client.New(ym.Config{Token: "..."})
msg, _ := cs.Messages.SendToChat(ctx, "chat-id", "hi", nil)
_ = cs.Polls.Create(ctx, &polls.CreatePollRequest{
ChatID: ym.Ptr(ym.ChatID("chat-id")),
Title: "Q?",
Answers: []string{"A", "B"},
})
ymClient := ym.NewClientWithHTTP(cfg, loggedHTTP)
cs := client.Wrap(ymClient)
```

Run integration example:
See `middleware/README.md` and `examples/debug_logger` for details.

```bash
cd examples/integration
YM_TOKEN=... YM_CHAT_ID=... YM_LOGIN=... YM_FILE_PATH=... go run .
# or: YM_TOKEN=... ./run.sh
```
## Examples

| Example | Description |
|---------|-------------|
| `examples/basic_send` | Send text to chat/login, reply-to, mark-important, error handling |
| `examples/poller` | Continuous polling via PollLoop, handles text/files/stickers/forwards |
| `examples/poll_bot` | Create poll, GetResults, GetAllVoters, read updates |
| `examples/webhook` | HTTP webhook receiver with secret validation, graceful shutdown, echo bot |
| `examples/debug_logger` | HTTP request/response logging, handling updates without messages |
| `examples/integration` | End-to-end script exercising all SDK methods (configure via env) |

### Running examples

Run webhook example:
```bash
# Send a message
cd examples/basic_send
YM_TOKEN=... go run . -chat "chat-id" -text "hello"

# Poll for updates
cd examples/poller
YM_TOKEN=... go run .

# Poll bot
cd examples/poll_bot
YM_TOKEN=... YM_CHAT_ID=... go run .

# Webhook server
cd examples/webhook
YM_TOKEN=... YM_PORT=8080 go run .
YM_TOKEN=... YM_WEBHOOK_SECRET=... YM_PORT=8080 go run .

# Debug logging
cd examples/debug_logger
YM_TOKEN=... go run .

# Full integration
cd examples/integration
YM_TOKEN=... YM_CHAT_ID=... YM_LOGIN=... go run .
```

## Versioning
Expand All @@ -161,5 +202,9 @@ go get github.com/rekurt/ymsdk@v0.1.0
## Tests

```bash
# Run all tests
go test ./...

# Lint (50+ linters)
golangci-lint run --config .golangci.yml
```
Loading
Loading