mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-06 22:49:32 +00:00
ac7cae8fa7
A fully-generated, strongly-typed Go client for the Telegram Bot API. * 176 methods + 301 types generated from Bot API v10.0 * 1408 auto-generated tests (8 scenarios per method) * Typed unions throughout — no 'any' in the public surface * Pluggable HTTP transport and JSON codec (default goccy/go-json) * Built-in retry middleware honouring Telegram's retry_after * Generic dispatcher with filters and conversation handlers * Self-verifying codegen pipeline (regen → audit → emit → run tests) * 14 example bots covering common patterns
144 lines
4.4 KiB
Go
144 lines
4.4 KiB
Go
// Package api contains the Telegram Bot API types and method wrappers.
|
|
// Most of the package is generated by cmd/genapi from internal/spec/api.json;
|
|
// this file holds the runtime types that are intentionally hand-coded.
|
|
//
|
|
// InputFile carries either a local upload (Reader+Filename) or a reference
|
|
// to a previously-uploaded file (file_id) / URL Telegram can fetch. It is
|
|
// not a pure JSON type, so the codegen skips it (see runtimeTypes in
|
|
// cmd/genapi/emitter.go).
|
|
//
|
|
// ResponseParameters mirrors client.ResponseParameters so callers importing
|
|
// only `api` can access retry_after and migrate_to_chat_id without pulling
|
|
// in the client package.
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/goccy/go-json"
|
|
"io"
|
|
"strconv"
|
|
)
|
|
|
|
// InputFile carries either a file path (for upload) or a Telegram file_id
|
|
// / URL string (for reuse). When PathOrID names a local file, the request
|
|
// is sent as multipart/form-data; otherwise the value is sent inline.
|
|
type InputFile struct {
|
|
// PathOrID is one of: an absolute or relative filesystem path, a
|
|
// previously-uploaded Telegram file_id, or an HTTPS URL Telegram
|
|
// can fetch.
|
|
PathOrID string
|
|
// Reader, when non-nil, is used as the file content (Filename names it).
|
|
Reader io.Reader
|
|
// Filename is the upload filename used when Reader is set.
|
|
Filename string
|
|
}
|
|
|
|
// IsLocalUpload reports whether this InputFile triggers a multipart upload.
|
|
func (f *InputFile) IsLocalUpload() bool {
|
|
if f == nil {
|
|
return false
|
|
}
|
|
return f.Reader != nil
|
|
}
|
|
|
|
// ResponseParameters is the optional metadata Telegram includes on certain
|
|
// failures. The most common is RetryAfter (seconds) on 429 responses.
|
|
//
|
|
// https://core.telegram.org/bots/api#responseparameters
|
|
type ResponseParameters struct {
|
|
MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
|
|
RetryAfter int `json:"retry_after,omitempty"`
|
|
}
|
|
|
|
// ChatID identifies a chat by either numeric id or "@username". The Telegram
|
|
// Bot API spells the same field as either an integer or a string; ChatID
|
|
// preserves both forms with explicit constructors and a custom MarshalJSON
|
|
// so callers never see `any` at the source level.
|
|
type ChatID struct {
|
|
int64Set bool
|
|
intID int64
|
|
username string
|
|
}
|
|
|
|
// ChatIDFromInt builds a ChatID for a numeric chat identifier (e.g. -1001234567890).
|
|
func ChatIDFromInt(id int64) ChatID { return ChatID{int64Set: true, intID: id} }
|
|
|
|
// ChatIDFromUsername builds a ChatID for a public chat (e.g. "@channel").
|
|
// The leading "@" is required by Telegram for usernames.
|
|
func ChatIDFromUsername(name string) ChatID { return ChatID{username: name} }
|
|
|
|
// IsZero reports whether c carries no value.
|
|
func (c ChatID) IsZero() bool { return !c.int64Set && c.username == "" }
|
|
|
|
// String returns the wire form (decimal integer or "@name") for use in
|
|
// multipart bodies.
|
|
func (c ChatID) String() string {
|
|
if c.int64Set {
|
|
return strconv.FormatInt(c.intID, 10)
|
|
}
|
|
return c.username
|
|
}
|
|
|
|
// MarshalJSON emits either a JSON number (integer form) or a JSON string
|
|
// (@username form). Empty values marshal as "null".
|
|
func (c ChatID) MarshalJSON() ([]byte, error) {
|
|
if c.int64Set {
|
|
return []byte(strconv.FormatInt(c.intID, 10)), nil
|
|
}
|
|
if c.username != "" {
|
|
return json.Marshal(c.username)
|
|
}
|
|
return []byte("null"), nil
|
|
}
|
|
|
|
// UnmarshalJSON accepts either a JSON number or a JSON string.
|
|
func (c *ChatID) UnmarshalJSON(data []byte) error {
|
|
data = bytes.TrimSpace(data)
|
|
if len(data) == 0 || bytes.Equal(data, []byte("null")) {
|
|
*c = ChatID{}
|
|
return nil
|
|
}
|
|
if data[0] == '"' {
|
|
var s string
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
*c = ChatIDFromUsername(s)
|
|
return nil
|
|
}
|
|
var n int64
|
|
if err := json.Unmarshal(data, &n); err != nil {
|
|
return fmt.Errorf("ChatID: %w", err)
|
|
}
|
|
*c = ChatIDFromInt(n)
|
|
return nil
|
|
}
|
|
|
|
// MessageOrBool wraps the "Message or True" return shape Telegram uses on
|
|
// edit methods (editMessageText, editMessageCaption, etc.). When the bot
|
|
// edits a regular chat message, Message is non-nil; when it edits an
|
|
// inline message, OK is true.
|
|
type MessageOrBool struct {
|
|
Message *Message
|
|
OK bool
|
|
}
|
|
|
|
// UnmarshalJSON decodes either {...} into Message or `true`/`false` into OK.
|
|
func (m *MessageOrBool) UnmarshalJSON(data []byte) error {
|
|
data = bytes.TrimSpace(data)
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if data[0] == '{' {
|
|
m.Message = new(Message)
|
|
return json.Unmarshal(data, m.Message)
|
|
}
|
|
var b bool
|
|
if err := json.Unmarshal(data, &b); err != nil {
|
|
return fmt.Errorf("MessageOrBool: %w", err)
|
|
}
|
|
m.OK = b
|
|
return nil
|
|
}
|