ymgo

package module
v0.0.0-...-c99337d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 27, 2026 License: MIT Imports: 16 Imported by: 0

README

ymgo

Go client for Yandex Messenger Bot API.

Features

  • Zero external dependencies (stdlib only)
  • Thread-safe — safe for concurrent use from multiple goroutines
  • Context support in all API methods
  • Retry with exponential backoff and rate-limit handling
  • Long-polling loop with error recovery callback
  • Multipart file uploads (files, images, galleries)
  • Structured error handling with errors.Is / errors.As

Install

go get github.com/glebkap/ymgo

Requires Go 1.20+.

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/glebkap/ymgo"
)

func main() {
	client := ymgo.NewClient("your-oauth-token")

	msg, err := client.SendMessage(context.Background(), &ymgo.SendMessageRequest{
		ChatID: "chat-id",
		Text:   "Hello from ymgo!",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Sent message %d\n", msg.ID)
}

Polling for updates

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

err := client.PollLoop(ctx, &ymgo.GetUpdatesParams{
	PollInterval: 2 * time.Second,
	OnError: func(err error) bool {
		log.Println("transient error:", err)
		return true // continue polling
	},
}, func(ctx context.Context, u ymgo.Update) error {
	fmt.Printf("Update %d: %s\n", u.UpdateID, u.Text)
	return nil
})

Sending files

f, _ := os.Open("document.pdf")
defer f.Close()

msg, err := client.SendFile(ctx, &ymgo.SendFileRequest{
	ChatID:   "chat-id",
	Document: f,
	Filename: "document.pdf",
})

Configuration

client := ymgo.NewClient("token",
	ymgo.WithBaseURL("https://custom-api.example.com"),
	ymgo.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
	ymgo.WithRetryStrategy(ymgo.RetryConfig{
		MaxAttempts:       3,
		InitialBackoff:    time.Second,
		MaxBackoff:        30 * time.Second,
		RetryHTTPStatuses: []int{500, 502, 503, 504},
	}),
	ymgo.WithRateLimitHandling(ymgo.RateLimitConfig{
		UseRetryAfter:  true,
		DefaultBackoff: 2 * time.Second,
	}),
	ymgo.WithLogger(ymgolog.NewWriterLogger(os.Stderr, ymgolog.DEBUG)),
)

Error handling

var apiErr *ymgo.APIError
if errors.As(err, &apiErr) {
	fmt.Printf("API error: kind=%d status=%d desc=%s\n",
		apiErr.Kind, apiErr.HTTPStatus, apiErr.Description)
}

if errors.Is(err, ymgo.ErrRateLimited) {
	// handle rate limit
}

if errors.Is(err, ymgo.ErrAPIError) {
	// HTTP 200 but ok=false
}

API methods

Method Description
SendMessage Send text message
SendFile Send file attachment
SendImage Send image
SendGallery Send image gallery
DeleteMessage Delete a message
GetFile Download file by ID
CreateChat Create chat or channel
UpdateMembers Add/remove chat members
CreatePoll Create a poll
GetPollResults Get poll results
GetPollVoters Get poll voters (paginated)
GetAllPollVoters Get all poll voters
GetUpdates Fetch pending updates
PollLoop Long-polling loop
GetUserLink Get user chat/call links
UpdateSelf Update bot settings

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrRateLimited     = errors.New("ymgo: rate limited")
	ErrInvalidToken    = errors.New("ymgo: invalid token")
	ErrUnauthorized    = errors.New("ymgo: unauthorized")
	ErrRequestTimeout  = errors.New("ymgo: request timeout")
	ErrNetworkError    = errors.New("ymgo: network error")
	ErrInvalidResponse = errors.New("ymgo: invalid response")
	ErrValidation      = errors.New("ymgo: validation error")
	ErrBadRequest      = errors.New("ymgo: bad request")
	// ErrAPIError is returned (via Unwrap) by APIError with KindAPIError.
	// This allows callers to use errors.Is(err, ErrAPIError) to detect
	// API-level errors (HTTP 200 with ok=false).
	ErrAPIError = errors.New("ymgo: api error")
)

Sentinel errors for use with errors.Is / errors.As.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	Kind        ErrorKind
	Code        int
	HTTPStatus  int
	Description string
	RequestID   string
	Method      string
	Endpoint    string
	RetryAfter  time.Duration
}

APIError represents a structured error returned by the Yandex Messenger Bot API.

func (*APIError) Error

func (e *APIError) Error() string

Error implements the error interface.

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

Unwrap returns the sentinel error corresponding to the error's Kind, enabling errors.Is / errors.As usage.

type BotSelf

type BotSelf struct {
	ID            string    `json:"id"`
	DisplayName   string    `json:"display_name"`
	WebhookURL    *string   `json:"webhook_url,omitempty"`
	Organizations []int64   `json:"organizations,omitempty"`
	Login         UserLogin `json:"login"`
}

BotSelf contains the bot's own information.

type Chat

type Chat struct {
	ID             ChatID   `json:"id"`
	Type           ChatType `json:"type"`
	OrganizationID string   `json:"organization_id,omitempty"`
	Title          string   `json:"title,omitempty"`
	Description    string   `json:"description,omitempty"`
	IsChannel      bool     `json:"is_channel,omitempty"`
}

Chat represents a Yandex Messenger chat.

type ChatID

type ChatID string

ChatID is a typed identifier for a chat.

type ChatType

type ChatType string

ChatType represents the type of a chat.

const (
	ChatTypePrivate ChatType = "private"
	ChatTypeGroup   ChatType = "group"
	ChatTypeChannel ChatType = "channel"
)

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the Yandex Messenger Bot API client. All API methods are defined as methods on *Client. Client is safe for concurrent use.

func NewClient

func NewClient(token string, opts ...Option) *Client

NewClient creates a new Yandex Messenger Bot API client. The token is used for OAuth authentication in every request. An empty token is allowed; requests will fail with 401 at runtime.

func (*Client) CreateChat

func (c *Client) CreateChat(ctx context.Context, req *CreateChatRequest) (*Chat, error)

CreateChat creates a new chat or channel. POST /bot/v1/chats/create/

func (*Client) CreatePoll

func (c *Client) CreatePoll(ctx context.Context, req *CreatePollRequest) (*Message, error)

CreatePoll creates a poll in a chat. POST /bot/v1/messages/createPoll/

func (*Client) DeleteMessage

func (c *Client) DeleteMessage(ctx context.Context, req *DeleteMessageRequest) error

DeleteMessage deletes a message from a chat. POST /bot/v1/messages/delete/

Note: MessageID=0 is rejected by validation. The zero value of MessageID (int64) cannot be used to delete a message.

func (*Client) GetAllPollVoters

func (c *Client) GetAllPollVoters(ctx context.Context, params *PollVotersParams) ([]Vote, error)

GetAllPollVoters paginates through all voters and returns them as a single slice.

func (*Client) GetFile

func (c *Client) GetFile(ctx context.Context, fileID string) (io.ReadCloser, *FileMeta, error)

GetFile downloads a file by its ID. GET /bot/v1/messages/getFile/?file_id=... The caller must close the returned io.ReadCloser.

func (*Client) GetPollResults

func (c *Client) GetPollResults(ctx context.Context, params *PollResultsParams) (*PollResult, error)

GetPollResults retrieves poll results. GET /bot/v1/polls/getResults/?...

func (*Client) GetPollVoters

func (c *Client) GetPollVoters(ctx context.Context, params *PollVotersParams) (*PollVotersPage, error)

GetPollVoters retrieves a page of poll voters. GET /bot/v1/polls/getVoters/?...

Note: AnswerID=0 is rejected by validation. If the API uses 0-based answer IDs, this method cannot retrieve voters for answer 0.

func (*Client) GetUpdates

func (c *Client) GetUpdates(ctx context.Context, params *GetUpdatesParams) ([]Update, int64, error)

GetUpdates fetches pending updates from the API. GET /bot/v1/messages/getUpdates?limit=&offset= Returns the list of updates and the next offset value.

func (c *Client) GetUserLink(ctx context.Context, login UserLogin) (*UserLink, error)

GetUserLink retrieves links (chat, call) for a user by login. GET /bot/v1/users/getUserLink/?login=...

func (*Client) PollLoop

func (c *Client) PollLoop(
	ctx context.Context,
	params *GetUpdatesParams,
	handler func(ctx context.Context, update Update) error,
) error

PollLoop runs a long-polling loop that fetches updates and passes each one to handler. It blocks until the context is cancelled or handler returns an error. The loop is stopped by cancelling ctx.

func (*Client) SendFile

func (c *Client) SendFile(ctx context.Context, req *SendFileRequest) (*Message, error)

SendFile sends a file attachment via multipart/form-data. POST /bot/v1/messages/sendFile/

func (*Client) SendGallery

func (c *Client) SendGallery(ctx context.Context, req *SendGalleryRequest) (*Message, error)

SendGallery sends a gallery of images via multipart/form-data. POST /bot/v1/messages/sendGallery/

func (*Client) SendImage

func (c *Client) SendImage(ctx context.Context, req *SendImageRequest) (*Message, error)

SendImage sends an image attachment via multipart/form-data. POST /bot/v1/messages/sendImage/

func (*Client) SendMessage

func (c *Client) SendMessage(ctx context.Context, req *SendMessageRequest) (*Message, error)

SendMessage sends a text message to a chat or user. POST /bot/v1/messages/sendText

func (*Client) UpdateMembers

func (c *Client) UpdateMembers(ctx context.Context, req *UpdateMembersRequest) error

UpdateMembers updates the members of a chat or channel. POST /bot/v1/chats/updateMembers/

func (*Client) UpdateSelf

func (c *Client) UpdateSelf(ctx context.Context, req *UpdateSelfRequest) (*BotSelf, error)

UpdateSelf updates the bot's own information (e.g. webhook URL). POST /bot/v1/self/update/

type CreateChatRequest

type CreateChatRequest struct {
	Name string `json:"name"`
	// Description is sent as empty string if not set (no omitempty).
	// This is intentional: the API distinguishes between empty and absent description.
	Description string    `json:"description"`
	AvatarURL   *string   `json:"avatar_url,omitempty"`
	Channel     bool      `json:"channel"`
	Admins      []UserRef `json:"admins,omitempty"`
	Members     []UserRef `json:"members,omitempty"`
	Subscribers []UserRef `json:"subscribers,omitempty"`
}

CreateChatRequest is the request body for CreateChat.

type CreatePollRequest

type CreatePollRequest struct {
	ChatID      ChatID    `json:"chat_id,omitempty"`
	Login       UserLogin `json:"login,omitempty"`
	Title       string    `json:"title"`
	Answers     []string  `json:"answers"`
	MaxChoices  *int      `json:"max_choices,omitempty"`
	IsAnonymous *bool     `json:"is_anonymous,omitempty"`
	ThreadID    *ThreadID `json:"thread_id,omitempty"`
}

CreatePollRequest is the request body for CreatePoll.

type DeleteMessageRequest

type DeleteMessageRequest struct {
	ChatID    ChatID    `json:"chat_id,omitempty"`
	Login     UserLogin `json:"login,omitempty"`
	MessageID MessageID `json:"message_id"`
	ThreadID  *ThreadID `json:"thread_id,omitempty"`
}

DeleteMessageRequest is the request for deleting a message.

type ErrorKind

type ErrorKind int

ErrorKind classifies API errors.

const (
	KindUnknown      ErrorKind = iota
	KindRateLimited            // HTTP 429
	KindInvalidToken           // HTTP 403
	KindUnauthorized           // HTTP 401
	KindBadRequest             // HTTP 400
	KindNetwork                // HTTP 5xx / network errors
	KindAPIError               // HTTP 200 with ok=false
)

type File

type File struct {
	ID       string `json:"id,omitempty"`
	Name     string `json:"name,omitempty"`
	MimeType string `json:"mime_type,omitempty"`
	Size     int64  `json:"size,omitempty"`
	URL      string `json:"url,omitempty"`
}

File represents a file attachment.

type FileMeta

type FileMeta struct {
	FileID        string
	ContentType   string
	ContentLength int64
}

FileMeta contains metadata about a downloaded file.

type FilePart

type FilePart struct {
	Reader   io.Reader
	Filename string
}

FilePart represents a file to be uploaded as part of a multipart request.

type ForwardInfo

type ForwardInfo struct {
	From      *Sender   `json:"from,omitempty"`
	Chat      *Chat     `json:"chat,omitempty"`
	MessageID MessageID `json:"message_id,omitempty"`
}

ForwardInfo contains information about a forwarded message.

type GetUpdatesParams

type GetUpdatesParams struct {
	Limit        *int
	Offset       *int64
	PollInterval time.Duration // Sleep between PollLoop iterations; defaults to 1s.
	// OnError is called when GetUpdates returns an error during PollLoop.
	// If OnError returns true, PollLoop continues after a sleep interval.
	// If OnError returns false or OnError is nil, PollLoop stops and returns the error.
	// The callback should implement circuit-breaker or max-retry logic to avoid
	// infinite loops on persistent errors.
	OnError func(error) bool
}

GetUpdatesParams configures the GetUpdates request.

type HTTPDoer

type HTTPDoer interface {
	Do(*http.Request) (*http.Response, error)
}

HTTPDoer is the interface for executing HTTP requests. *http.Client satisfies this interface.

type Image

type Image struct {
	ID     string `json:"id,omitempty"`
	URL    string `json:"url,omitempty"`
	Width  int    `json:"width,omitempty"`
	Height int    `json:"height,omitempty"`
}

Image represents an image attachment.

type InlineKeyboardButton

type InlineKeyboardButton struct {
	Text       string `json:"text"`
	CallbackID string `json:"callback_id,omitempty"`
	URL        string `json:"url,omitempty"`
}

InlineKeyboardButton represents a button in an inline keyboard.

type Message

type Message struct {
	ID        MessageID    `json:"message_id"`
	Chat      Chat         `json:"chat"`
	From      Sender       `json:"from"`
	Text      string       `json:"text,omitempty"`
	CreatedAt string       `json:"created_at,omitempty"`
	Timestamp int64        `json:"timestamp,omitempty"`
	ThreadID  *ThreadID    `json:"thread_id,omitempty"`
	Forward   *ForwardInfo `json:"forward,omitempty"`
	Sticker   *Sticker     `json:"sticker,omitempty"`
	Image     *Image       `json:"image,omitempty"`
	Gallery   []Image      `json:"gallery,omitempty"`
	Document  *File        `json:"document,omitempty"`
}

Message represents a message in a chat.

func (*Message) ParseTime

func (m *Message) ParseTime() (time.Time, error)

ParseTime converts the CreatedAt field from RFC3339 to time.Time.

type MessageID

type MessageID int64

MessageID is a typed identifier for a message.

type Option

type Option func(*Client)

Option configures the Client.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the default base URL.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets a custom *http.Client.

func WithHTTPDoer

func WithHTTPDoer(d HTTPDoer) Option

WithHTTPDoer sets a custom HTTPDoer (useful for testing).

func WithLogger

func WithLogger(l log.Logger) Option

WithLogger sets the logger for the client.

func WithRateLimitHandling

func WithRateLimitHandling(cfg RateLimitConfig) Option

WithRateLimitHandling configures rate-limit handling.

func WithRetryStrategy

func WithRetryStrategy(cfg RetryConfig) Option

WithRetryStrategy configures the retry strategy.

type PollResult

type PollResult struct {
	VotedCount int         `json:"voted_count"`
	Answers    map[int]int `json:"answers"`
}

PollResult contains the results of a poll.

type PollResultsParams

type PollResultsParams struct {
	ChatID     ChatID
	Login      UserLogin
	MessageID  MessageID
	InviteHash string
	ThreadID   *ThreadID
}

PollResultsParams configures GetPollResults.

type PollVotersPage

type PollVotersPage struct {
	AnswerID   int    `json:"answer_id"`
	VotedCount int    `json:"voted_count"`
	Cursor     int64  `json:"cursor"`
	Votes      []Vote `json:"votes"`
}

PollVotersPage represents a page of poll voters.

type PollVotersParams

type PollVotersParams struct {
	ChatID     ChatID
	Login      UserLogin
	MessageID  MessageID
	AnswerID   int
	InviteHash string
	Limit      *int
	Cursor     *int64
	ThreadID   *ThreadID
}

PollVotersParams configures GetPollVoters and GetAllPollVoters.

type RateLimitConfig

type RateLimitConfig struct {
	UseRetryAfter  bool
	DefaultBackoff time.Duration
}

RateLimitConfig configures rate-limit handling behaviour.

type RetryConfig

type RetryConfig struct {
	MaxAttempts          int
	InitialBackoff       time.Duration
	MaxBackoff           time.Duration
	RetryHTTPStatuses    []int
	RetryOnNetworkErrors bool
}

RetryConfig configures the retry strategy.

type SendFileRequest

type SendFileRequest struct {
	ChatID   ChatID
	Login    UserLogin
	ThreadID *ThreadID
	Document io.Reader
	Filename string
}

SendFileRequest is the request for sending a file attachment.

type SendGalleryRequest

type SendGalleryRequest struct {
	ChatID   ChatID
	Login    UserLogin
	ThreadID *ThreadID
	Images   []FilePart
}

SendGalleryRequest is the request for sending a gallery of images.

type SendImageRequest

type SendImageRequest struct {
	ChatID   ChatID
	Login    UserLogin
	ThreadID *ThreadID
	Image    io.Reader
	Filename string
}

SendImageRequest is the request for sending an image attachment.

type SendMessageRequest

type SendMessageRequest struct {
	ChatID        ChatID    `json:"chat_id,omitempty"`
	Login         UserLogin `json:"login,omitempty"`
	Text          string    `json:"text"`
	MarkImportant bool      `json:"mark_important,omitempty"`
	// ReplyToMessageID is the string identifier of the message to reply to.
	// Note: this is a string, not MessageID (int64), as the API expects a
	// string identifier for reply references.
	ReplyToMessageID string                   `json:"reply_to_message_id,omitempty"`
	InlineKeyboard   [][]InlineKeyboardButton `json:"inline_keyboard,omitempty"`
	ThreadID         *ThreadID                `json:"thread_id,omitempty"`
}

SendMessageRequest is the request body for SendMessage.

type Sender

type Sender struct {
	ID          string    `json:"id,omitempty"`
	Login       UserLogin `json:"login"`
	Name        string    `json:"name,omitempty"`
	DisplayName string    `json:"display_name,omitempty"`
	Robot       *bool     `json:"robot,omitempty"`
}

Sender represents the author of a message.

type Sticker

type Sticker struct {
	ID    string `json:"id,omitempty"`
	Emoji string `json:"emoji,omitempty"`
}

Sticker represents a sticker attachment.

type ThreadID

type ThreadID int64

ThreadID is a typed identifier for a thread.

type Update

type Update struct {
	UpdateID  int64        `json:"update_id"`
	Chat      *Chat        `json:"chat,omitempty"`
	From      *Sender      `json:"from,omitempty"`
	Text      string       `json:"text,omitempty"`
	Timestamp int64        `json:"timestamp,omitempty"`
	MessageID MessageID    `json:"message_id,omitempty"`
	ThreadID  *ThreadID    `json:"thread_id,omitempty"`
	Forward   *ForwardInfo `json:"forward,omitempty"`
	Sticker   *Sticker     `json:"sticker,omitempty"`
	Image     *Image       `json:"image,omitempty"`
	Gallery   []Image      `json:"gallery,omitempty"`
	Document  *File        `json:"document,omitempty"`
}

Update represents an incoming update from the Yandex Messenger Bot API.

func (*Update) ToMessage

func (u *Update) ToMessage() *Message

ToMessage converts an Update to a Message by promoting its fields. It is nil-safe: if u.Chat or u.From is nil, the corresponding Message field will be zero-valued (unlike ymsdk which panics on nil dereference).

type UpdateMembersRequest

type UpdateMembersRequest struct {
	ChatID      ChatID    `json:"chat_id"`
	Members     []UserRef `json:"members,omitempty"`
	Admins      []UserRef `json:"admins,omitempty"`
	Subscribers []UserRef `json:"subscribers,omitempty"`
	Remove      []UserRef `json:"remove,omitempty"`
}

UpdateMembersRequest is the request body for UpdateMembers.

type UpdateSelfRequest

type UpdateSelfRequest struct {
	WebhookURL *string `json:"webhook_url,omitempty"`
}

UpdateSelfRequest is the request body for UpdateSelf.

type UserLink struct {
	ID       string `json:"id"`
	ChatLink string `json:"chat_link"`
	CallLink string `json:"call_link"`
}

UserLink contains links associated with a user.

type UserLogin

type UserLogin string

UserLogin is a typed identifier for a user login.

type UserRef

type UserRef struct {
	Login UserLogin `json:"login"`
}

UserRef identifies a user by login for chat membership operations.

type Vote

type Vote struct {
	Timestamp int64   `json:"timestamp"`
	User      UserRef `json:"user"`
}

Vote represents a single vote in a poll.

Directories

Path Synopsis
Package log provides a logging interface and implementations for ymgo.
Package log provides a logging interface and implementations for ymgo.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL