Compare commits

...
12 Commits
Author SHA1 Message Date
lukaszraczylo 7eebdd1f40 ci(regen): merge the weekly regeneration PR automatically
The weekly job already gates the update. `make regen` fails on any audit
fallback and the test step runs the full race suite, so a PR only opens when
both pass. Merge it from the same job.

A PR opened with GITHUB_TOKEN does not trigger ci.yml, so GitHub auto-merge
would wait for checks that never start. The push to main triggers ci.yml,
which cuts the release.
2026-07-21 22:52:28 +01:00
lukaszraczylo 9c363f470a chore(api): regenerate from Telegram Bot API v10.2
Snapshot taken 2026-07-21. Notable shape change: ReplyParameters.MessageID
becomes optional (*int64) because Bot API 10.2 allows a reply to target
ephemeral_message_id instead. This breaks callers that set MessageID by value,
so examples/echo passes the address now.

New surface includes the ephemeral message methods, sendRichMessage with
InputRichMessageMedia, and the Community service messages.
2026-07-21 22:52:20 +01:00
lukaszraczylo 5aabbe6151 fix(codegen): parse recapitalised Array-of return sentence
Telegram rewrote the forwardMessages and copyMessages return sentence from
"an array of MessageId" to "an Array of MessageId". The pattern that handles
the "... of the sent messages is returned" shape matched lowercase only, so
both methods fell back to bool and the weekly regen job failed at the audit
step.

Add approved_any_locations to overrides.json so a union that renders as `any`
by design can be approved as data instead of a code change. Approve
InputRichMessageMedia.Media, a five-variant InputMedia union added in Bot
API 10.2 that has no declared parent type.

Make the drift test compare HEAD against itself. It compared the working tree
against HEAD, so it failed whenever a regen run added a method.
2026-07-21 22:52:06 +01:00
lukaszraczylo cd05e6a426 fixup! fix(genapi): scope method-param enums per method, dedupe case-colliding enum consts 2026-07-21 22:35:53 +01:00
lukaszraczylo 140ea13bde fix(genapi): scope method-param enums per method, dedupe case-colliding enum consts
Bot API update added RichBlockListItem.type values (a/A/i/I/1) that
Pascal-case to identical const idents, and answerGuestQuery's non-enum
'result' param picked up answerChatJoinRequestQuery's Result enum via
the shared parent="" enum-plan key.

- key method params as method:<name> in the enum plan byField map
- enumDecl.ConstName resolves case collisions with Lower/Upper prefix
- regenerate api/ from 2026-06-11 snapshot
2026-06-12 00:03:45 +01:00
0731f10907 chore(api): regenerate from Telegram Bot API v10.0 (#7)
Co-authored-by: lukaszraczylo <[email protected]>
2026-06-01 13:15:45 +01:00
lukaszraczylo 6685414374 fix(ci): regen docs + bump x/net to v0.55.0
- regen docs/reference/client.md after the Version docstring rewrite
  in the previous SendForModule commit (gomarkdoc output drifted).
- bump golang.org/x/net 0.54.0 -> 0.55.0 for the govulncheck fixes
  (GO-2026-5027/5028/5029/5030 — html.Parse XSS / DoS chain reachable
  from cmd/scrape).
2026-05-22 23:44:24 +01:00
lukaszraczylo 404411b20c feat(telemetry): switch to SendForModule, drop hand-bumped Version
Replaces the hand-bumped client.Version="0.7.11" with
telemetry.SendForModule which reads the actual module version from
runtime/debug.BuildInfo.Deps at the consumer's build time. This
means future releases (v0.7.12, v0.8.0, …) will be reported by the
running bot WITHOUT requiring a manual edit of client/version.go on
every tag — consumers just `go get -u` and rebuild.

client.Version is preserved as a fallback for replace directives /
detached `go run`, and remains overridable via -ldflags.

Bumps oss-telemetry to v0.2.1.
2026-05-22 23:35:22 +01:00
lukaszraczylo 8d85e61da5 docs: collapse telemetry section to link upstream docs
Replaces the duplicated opt-out table with a link to the canonical
oss-telemetry README section. Keeps the project-specific env var name
and the local source pointer; removes drift risk for the rest.
2026-05-21 04:03:55 +01:00
lukaszraczylo 609c4ce649 feat: anonymous adoption telemetry on first client.New
Sends one fire-and-forget POST per process the first time a consumer
constructs a Bot via client.New. Helps track real-world adoption and
version spread of the library. No identifiers, no API contents.

Implementation:
- new client/version.go: exported Version var (currently 0.7.11)
- new client/telemetry.go: sync.Once gate + telemetry.Send call
- client.go New(): single fireTelemetryOnce() line at function entry
- client/telemetry_test.go: TestMain disables outgoing pings during the
  library's own test suite

README §Telemetry documents the payload, the opt-out env vars, and links
to the upstream oss-telemetry source so consumers can audit what is sent.

Opt out via any of:
  DO_NOT_TRACK=1
  OSS_TELEMETRY_DISABLED=1
  GO_TELEGRAM_DISABLE_TELEMETRY=1
  osstelemetry.Disable() before the first client.New
2026-05-21 03:59:07 +01:00
lukaszraczyloandGitHub d39be13822 chore(ci): bump remaining Node 20 actions (#6)
actions/cache v4 -> v5 (Node 24 runtime; no API change)
actions/upload-artifact v4 -> v7 (ESM + Node 24; existing name/path usage unaffected)

Clears the last Node 20 deprecation warnings surfaced during the
release run in d6ecbde.
2026-05-20 23:39:16 +01:00
lukaszraczyloandGitHub d6ecbdea48 chore(ci): bump goreleaser-action v6 -> v7 (#5)
Node 20 deprecation. v7 release notes call out only node24+ESM
internal changes; inputs/outputs unchanged from v6.
2026-05-20 23:30:34 +01:00
34 changed files with 77441 additions and 1291 deletions
+9 -9
View File
@@ -30,7 +30,7 @@ jobs:
with:
go-version: '1.25.x'
check-latest: true
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
@@ -46,7 +46,7 @@ jobs:
with:
go-version: '1.25.x'
check-latest: true
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
@@ -63,7 +63,7 @@ jobs:
with:
go-version: '1.25.x'
check-latest: true
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
@@ -80,7 +80,7 @@ jobs:
with:
go-version: '1.25.x'
check-latest: true
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
@@ -103,7 +103,7 @@ jobs:
with:
go-version: '1.25.x'
check-latest: true
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
@@ -112,7 +112,7 @@ jobs:
- run: go test -race -coverprofile=coverage.out ./...
- name: Build all examples
run: go build ./examples/...
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: coverage
path: coverage.out
@@ -125,7 +125,7 @@ jobs:
with:
go-version: '1.25.x'
check-latest: true
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
@@ -146,7 +146,7 @@ jobs:
with:
go-version: '1.25.x'
check-latest: true
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
@@ -258,7 +258,7 @@ jobs:
- name: Run GoReleaser
if: github.event_name != 'workflow_dispatch' || inputs.dry-run-release == false
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser
version: '~> v2'
+15 -1
View File
@@ -88,6 +88,7 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Open PR
id: pr
if: steps.diff.outputs.no_changes != 'true'
uses: peter-evans/create-pull-request@v8
with:
@@ -115,4 +116,17 @@ jobs:
Inspect the IR diff (`internal/spec/api.json`) for added/changed/removed methods.
CI must pass before merge. Auto-merge: enable when satisfied with the diff.
This PR merges itself once every step above passes. The gates are
`make regen` (which fails on any audit fallback) and `go test -race ./...`,
both run in this job before the PR opens.
# A PR opened with GITHUB_TOKEN does not trigger `ci.yml`, so waiting for
# PR checks would wait forever. The gates already ran in this job: `make
# regen` fails on any audit fallback, and the test step runs the full race
# suite. Merging here therefore means "merged because the checks passed".
# `ci.yml` then runs on the push to main and cuts the release.
- name: Merge regen PR
if: steps.diff.outputs.no_changes != 'true' && steps.pr.outputs.pull-request-number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge "${{ steps.pr.outputs.pull-request-number }}" --squash --delete-branch
+14
View File
@@ -371,6 +371,20 @@ bot := client.New("token", client.WithHTTPClient(fakeDoer{
The library's own generated test suite (`api/methods_gen_test.go`) covers 176 methods × 8 scenarios each: Success, APIError, NetworkError, ParseError, ContextCanceled, MissingRequiredFields, Forbidden, ServerError.
## Telemetry
On the **first call to `client.New`** in a process, this library sends a
single anonymous adoption ping — project name, version, timestamp; no
identifiers, no message contents, no API call metadata. Fire-and-forget
with a 2-second timeout; cannot block `New` or panic.
Local source: [`client/telemetry.go`](client/telemetry.go). Upstream
implementation, exact wire format, and full opt-out documentation:
**[oss-telemetry — Disabling telemetry](https://github.com/lukaszraczylo/oss-telemetry#disabling-telemetry)**.
Quick opt-out: set any of `DO_NOT_TRACK=1`, `OSS_TELEMETRY_DISABLED=1`,
or `GO_TELEGRAM_DISABLE_TELEMETRY=1`.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
+131
View File
@@ -33,6 +33,14 @@ const (
BotCommandScopeTypeChatMember BotCommandScopeType = "chat_member"
)
type BotSubscriptionUpdatedState string
const (
BotSubscriptionUpdatedStateCanceled BotSubscriptionUpdatedState = "canceled"
BotSubscriptionUpdatedStateActive BotSubscriptionUpdatedState = "active"
BotSubscriptionUpdatedStateFailed BotSubscriptionUpdatedState = "failed"
)
type ChatBoostSourceKind string
const (
@@ -133,6 +141,12 @@ const (
InputMediaTypeVideo InputMediaType = "video"
)
type InputMediaVoiceNoteType string
const (
InputMediaVoiceNoteTypeVoiceNote InputMediaVoiceNoteType = "voice_note"
)
type InputPaidMediaType string
const (
@@ -158,6 +172,7 @@ type InputPollOptionMediaType string
const (
InputPollOptionMediaTypeAnimation InputPollOptionMediaType = "animation"
InputPollOptionMediaTypeLink InputPollOptionMediaType = "link"
InputPollOptionMediaTypeLivePhoto InputPollOptionMediaType = "live_photo"
InputPollOptionMediaTypeLocation InputPollOptionMediaType = "location"
InputPollOptionMediaTypePhoto InputPollOptionMediaType = "photo"
@@ -173,6 +188,32 @@ const (
InputProfilePhotoTypeAnimated InputProfilePhotoType = "animated"
)
type InputRichBlockType string
const (
InputRichBlockTypeParagraph InputRichBlockType = "paragraph"
InputRichBlockTypeHeading InputRichBlockType = "heading"
InputRichBlockTypePre InputRichBlockType = "pre"
InputRichBlockTypeFooter InputRichBlockType = "footer"
InputRichBlockTypeDivider InputRichBlockType = "divider"
InputRichBlockTypeMathematicalExpression InputRichBlockType = "mathematical_expression"
InputRichBlockTypeAnchor InputRichBlockType = "anchor"
InputRichBlockTypeList InputRichBlockType = "list"
InputRichBlockTypeBlockquote InputRichBlockType = "blockquote"
InputRichBlockTypePullquote InputRichBlockType = "pullquote"
InputRichBlockTypeCollage InputRichBlockType = "collage"
InputRichBlockTypeSlideshow InputRichBlockType = "slideshow"
InputRichBlockTypeTable InputRichBlockType = "table"
InputRichBlockTypeDetails InputRichBlockType = "details"
InputRichBlockTypeMap InputRichBlockType = "map"
InputRichBlockTypeAnimation InputRichBlockType = "animation"
InputRichBlockTypeAudio InputRichBlockType = "audio"
InputRichBlockTypePhoto InputRichBlockType = "photo"
InputRichBlockTypeVideo InputRichBlockType = "video"
InputRichBlockTypeVoiceNote InputRichBlockType = "voice_note"
InputRichBlockTypeThinking InputRichBlockType = "thinking"
)
type InputStickerFormat string
const (
@@ -357,6 +398,14 @@ const (
RefundedPaymentCurrencyXTR RefundedPaymentCurrency = "XTR"
)
type Result string
const (
ResultApprove Result = "approve"
ResultDecline Result = "decline"
ResultQueue Result = "queue"
)
type RevenueWithdrawalStateKind string
const (
@@ -365,6 +414,88 @@ const (
RevenueWithdrawalStateKindFailed RevenueWithdrawalStateKind = "failed"
)
type RichBlockListItemType string
const (
RichBlockListItemTypeLowerA RichBlockListItemType = "a"
RichBlockListItemTypeUpperA RichBlockListItemType = "A"
RichBlockListItemTypeLowerI RichBlockListItemType = "i"
RichBlockListItemTypeUpperI RichBlockListItemType = "I"
RichBlockListItemType1 RichBlockListItemType = "1"
)
type RichBlockTableCellAlign string
const (
RichBlockTableCellAlignLeft RichBlockTableCellAlign = "left"
RichBlockTableCellAlignCenter RichBlockTableCellAlign = "center"
RichBlockTableCellAlignRight RichBlockTableCellAlign = "right"
)
type RichBlockTableCellValign string
const (
RichBlockTableCellValignTop RichBlockTableCellValign = "top"
RichBlockTableCellValignMiddle RichBlockTableCellValign = "middle"
RichBlockTableCellValignBottom RichBlockTableCellValign = "bottom"
)
type RichBlockType string
const (
RichBlockTypeParagraph RichBlockType = "paragraph"
RichBlockTypeHeading RichBlockType = "heading"
RichBlockTypePre RichBlockType = "pre"
RichBlockTypeFooter RichBlockType = "footer"
RichBlockTypeDivider RichBlockType = "divider"
RichBlockTypeMathematicalExpression RichBlockType = "mathematical_expression"
RichBlockTypeAnchor RichBlockType = "anchor"
RichBlockTypeList RichBlockType = "list"
RichBlockTypeBlockquote RichBlockType = "blockquote"
RichBlockTypePullquote RichBlockType = "pullquote"
RichBlockTypeCollage RichBlockType = "collage"
RichBlockTypeSlideshow RichBlockType = "slideshow"
RichBlockTypeTable RichBlockType = "table"
RichBlockTypeDetails RichBlockType = "details"
RichBlockTypeMap RichBlockType = "map"
RichBlockTypeAnimation RichBlockType = "animation"
RichBlockTypeAudio RichBlockType = "audio"
RichBlockTypePhoto RichBlockType = "photo"
RichBlockTypeVideo RichBlockType = "video"
RichBlockTypeVoiceNote RichBlockType = "voice_note"
RichBlockTypeThinking RichBlockType = "thinking"
)
type RichTextType string
const (
RichTextTypeBold RichTextType = "bold"
RichTextTypeItalic RichTextType = "italic"
RichTextTypeUnderline RichTextType = "underline"
RichTextTypeStrikethrough RichTextType = "strikethrough"
RichTextTypeSpoiler RichTextType = "spoiler"
RichTextTypeDateTime RichTextType = "date_time"
RichTextTypeTextMention RichTextType = "text_mention"
RichTextTypeSubscript RichTextType = "subscript"
RichTextTypeSuperscript RichTextType = "superscript"
RichTextTypeMarked RichTextType = "marked"
RichTextTypeCode RichTextType = "code"
RichTextTypeCustomEmoji RichTextType = "custom_emoji"
RichTextTypeMathematicalExpression RichTextType = "mathematical_expression"
RichTextTypeURL RichTextType = "url"
RichTextTypeEmailAddress RichTextType = "email_address"
RichTextTypePhoneNumber RichTextType = "phone_number"
RichTextTypeBankCardNumber RichTextType = "bank_card_number"
RichTextTypeMention RichTextType = "mention"
RichTextTypeHashtag RichTextType = "hashtag"
RichTextTypeCashtag RichTextType = "cashtag"
RichTextTypeBotCommand RichTextType = "bot_command"
RichTextTypeAnchor RichTextType = "anchor"
RichTextTypeAnchorLink RichTextType = "anchor_link"
RichTextTypeReference RichTextType = "reference"
RichTextTypeReferenceLink RichTextType = "reference_link"
)
type StickerType string
const (
+392 -45
View File
@@ -195,6 +195,10 @@ type SendMessageParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Text of the message to be sent, 1-4096 characters after entities parsing
Text string `json:"text"`
// Mode for parsing entities in the message text. See formatting options for more details.
@@ -261,7 +265,7 @@ func ForwardMessage(ctx context.Context, b *client.Bot, p *ForwardMessageParams)
// ForwardMessagesParams is the parameter set for ForwardMessages.
//
// Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an array of MessageId of the sent messages is returned.
// Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an Array of MessageId of the sent messages is returned.
type ForwardMessagesParams struct {
// Unique identifier for the target chat or username of the target bot, supergroup or channel in the format @username
ChatID ChatID `json:"chat_id"`
@@ -281,14 +285,14 @@ type ForwardMessagesParams struct {
// ForwardMessages calls the forwardMessages Telegram Bot API method.
//
// Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an array of MessageId of the sent messages is returned.
// Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an Array of MessageId of the sent messages is returned.
func ForwardMessages(ctx context.Context, b *client.Bot, p *ForwardMessagesParams) ([]MessageId, error) {
return client.Call[*ForwardMessagesParams, []MessageId](ctx, b, "forwardMessages", p)
}
// CopyMessageParams is the parameter set for CopyMessage.
//
// Use this method to copy messages of any kind. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_id is known to the bot. The method is analogous to the method forwardMessage, but the copied message doesn't have a link to the original message. Returns the MessageId of the sent message on success.
// Use this method to copy messages of any kind. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_ids is known to the bot. The method is analogous to the method forwardMessage, but the copied message doesn't have a link to the original message. Returns the MessageId of the sent message on success.
type CopyMessageParams struct {
// Unique identifier for the target chat or username of the target bot, supergroup or channel in the format @username
ChatID ChatID `json:"chat_id"`
@@ -308,7 +312,7 @@ type CopyMessageParams struct {
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Pass True, if the caption must be shown above the message media. Ignored if a new caption isn't specified.
// Pass True if the caption must be shown above the message media. Ignored if a new caption isn't specified.
ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"`
// Sends the message silently. Users will receive a notification with no sound.
DisableNotification *bool `json:"disable_notification,omitempty"`
@@ -328,14 +332,14 @@ type CopyMessageParams struct {
// CopyMessage calls the copyMessage Telegram Bot API method.
//
// Use this method to copy messages of any kind. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_id is known to the bot. The method is analogous to the method forwardMessage, but the copied message doesn't have a link to the original message. Returns the MessageId of the sent message on success.
// Use this method to copy messages of any kind. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_ids is known to the bot. The method is analogous to the method forwardMessage, but the copied message doesn't have a link to the original message. Returns the MessageId of the sent message on success.
func CopyMessage(ctx context.Context, b *client.Bot, p *CopyMessageParams) (*MessageId, error) {
return client.Call[*CopyMessageParams, *MessageId](ctx, b, "copyMessage", p)
}
// CopyMessagesParams is the parameter set for CopyMessages.
//
// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_id is known to the bot. The method is analogous to the method forwardMessages, but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of MessageId of the sent messages is returned.
// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_ids is known to the bot. The method is analogous to the method forwardMessages, but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an Array of MessageId of the sent messages is returned.
type CopyMessagesParams struct {
// Unique identifier for the target chat or username of the target bot, supergroup or channel in the format @username
ChatID ChatID `json:"chat_id"`
@@ -357,7 +361,7 @@ type CopyMessagesParams struct {
// CopyMessages calls the copyMessages Telegram Bot API method.
//
// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_id is known to the bot. The method is analogous to the method forwardMessages, but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of MessageId of the sent messages is returned.
// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_ids is known to the bot. The method is analogous to the method forwardMessages, but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an Array of MessageId of the sent messages is returned.
func CopyMessages(ctx context.Context, b *client.Bot, p *CopyMessagesParams) ([]MessageId, error) {
return client.Call[*CopyMessagesParams, []MessageId](ctx, b, "copyMessages", p)
}
@@ -374,6 +378,10 @@ type SendPhotoParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20. More information on Sending Files »
Photo *InputFile `json:"photo"`
// Photo caption (may also be used when resending photos by file_id), 0-1024 characters after entities parsing
@@ -382,7 +390,7 @@ type SendPhotoParams struct {
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Pass True, if the caption must be shown above the message media
// Pass True if the caption must be shown above the message media
ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"`
// Pass True if the photo needs to be covered with a spoiler animation
HasSpoiler *bool `json:"has_spoiler,omitempty"`
@@ -423,6 +431,12 @@ func (p *SendPhotoParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Caption != "" {
out["caption"] = p.Caption
}
@@ -502,6 +516,10 @@ type SendLivePhotoParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Live photo video to send. The video must be no longer than 10 seconds and must not exceed 10 MB in size. Pass a file_id as String to send a video that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. More information on Sending Files ». Sending live photos by a URL is currently unsupported.
LivePhoto *InputFile `json:"live_photo"`
// The static photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. More information on Sending Files ». Sending live photos by a URL is currently unsupported.
@@ -512,7 +530,7 @@ type SendLivePhotoParams struct {
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Pass True, if the caption must be shown above the message media
// Pass True if the caption must be shown above the message media
ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"`
// Pass True if the video needs to be covered with a spoiler animation
HasSpoiler *bool `json:"has_spoiler,omitempty"`
@@ -556,6 +574,12 @@ func (p *SendLivePhotoParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Caption != "" {
out["caption"] = p.Caption
}
@@ -643,6 +667,10 @@ type SendAudioParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data. More information on Sending Files »
Audio *InputFile `json:"audio"`
// Audio caption, 0-1024 characters after entities parsing
@@ -699,6 +727,12 @@ func (p *SendAudioParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Caption != "" {
out["caption"] = p.Caption
}
@@ -789,6 +823,10 @@ type SendDocumentParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. More information on Sending Files »
Document *InputFile `json:"document"`
// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://<file_attach_name>” if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. More information on Sending Files »
@@ -841,6 +879,12 @@ func (p *SendDocumentParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Caption != "" {
out["caption"] = p.Caption
}
@@ -924,6 +968,10 @@ type SendVideoParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Video to send. Pass a file_id as String to send a video that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or upload a new video using multipart/form-data. More information on Sending Files »
Video *InputFile `json:"video"`
// Duration of sent video in seconds
@@ -944,7 +992,7 @@ type SendVideoParams struct {
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Pass True, if the caption must be shown above the message media
// Pass True if the caption must be shown above the message media
ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"`
// Pass True if the video needs to be covered with a spoiler animation
HasSpoiler *bool `json:"has_spoiler,omitempty"`
@@ -993,6 +1041,12 @@ func (p *SendVideoParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Duration != nil {
out["duration"] = strconv.FormatInt(*p.Duration, 10)
}
@@ -1101,6 +1155,10 @@ type SendAnimationParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Animation to send. Pass a file_id as String to send an animation that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the Internet, or upload a new animation using multipart/form-data. More information on Sending Files »
Animation *InputFile `json:"animation"`
// Duration of sent animation in seconds
@@ -1117,7 +1175,7 @@ type SendAnimationParams struct {
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Pass True, if the caption must be shown above the message media
// Pass True if the caption must be shown above the message media
ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"`
// Pass True if the animation needs to be covered with a spoiler animation
HasSpoiler *bool `json:"has_spoiler,omitempty"`
@@ -1161,6 +1219,12 @@ func (p *SendAnimationParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Duration != nil {
out["duration"] = strconv.FormatInt(*p.Duration, 10)
}
@@ -1256,6 +1320,10 @@ type SendVoiceParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Audio file to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. More information on Sending Files »
Voice *InputFile `json:"voice"`
// Voice message caption, 0-1024 characters after entities parsing
@@ -1303,6 +1371,12 @@ func (p *SendVoiceParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Caption != "" {
out["caption"] = p.Caption
}
@@ -1379,6 +1453,10 @@ type SendVideoNoteParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. More information on Sending Files ». Sending video notes by a URL is currently unsupported.
VideoNote *InputFile `json:"video_note"`
// Duration of sent video in seconds
@@ -1427,6 +1505,12 @@ func (p *SendVideoNoteParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Duration != nil {
out["duration"] = strconv.FormatInt(*p.Duration, 10)
}
@@ -1504,7 +1588,7 @@ type SendPaidMediaParams struct {
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// The number of Telegram Stars that must be paid to buy access to the media; 1-25000
StarCount int64 `json:"star_count"`
// A JSON-serialized array describing the media to be sent; up to 10 items
// A JSON-serialized Array describing the media to be sent; up to 10 items
Media []InputPaidMedia `json:"media"`
// Bot-defined paid media payload, 0-128 bytes. This will not be displayed to the user, use it for your internal processes.
Payload string `json:"payload,omitempty"`
@@ -1514,7 +1598,7 @@ type SendPaidMediaParams struct {
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Pass True, if the caption must be shown above the message media
// Pass True if the caption must be shown above the message media
ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"`
// Sends the message silently. Users will receive a notification with no sound.
DisableNotification *bool `json:"disable_notification,omitempty"`
@@ -1611,7 +1695,7 @@ func SendPaidMedia(ctx context.Context, b *client.Bot, p *SendPaidMediaParams) (
// SendMediaGroupParams is the parameter set for SendMediaGroup.
//
// Use this method to send a group of photos, live photos, videos, documents or audios as an album. Documents and audio files can be only grouped in an album with messages of the same type. On success, an array of Message objects that were sent is returned.
// Use this method to send a group of photos, live photos, videos, documents or audios as an album. Documents and audio files can be only grouped in an album with messages of the same type. On success, an Array of Message objects that were sent is returned.
type SendMediaGroupParams struct {
// Unique identifier of the business connection on behalf of which the message will be sent
BusinessConnectionID string `json:"business_connection_id,omitempty"`
@@ -1621,7 +1705,7 @@ type SendMediaGroupParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the messages will be sent; required if the messages are sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// A JSON-serialized array describing messages to be sent, must include 2-10 items
// A JSON-serialized Array describing messages to be sent, must include 2-10 items
Media []any `json:"media"`
// Sends messages silently. Users will receive a notification with no sound.
DisableNotification *bool `json:"disable_notification,omitempty"`
@@ -1684,7 +1768,7 @@ func (p *SendMediaGroupParams) MultipartFiles() []client.MultipartFile {
// SendMediaGroup calls the sendMediaGroup Telegram Bot API method.
//
// Use this method to send a group of photos, live photos, videos, documents or audios as an album. Documents and audio files can be only grouped in an album with messages of the same type. On success, an array of Message objects that were sent is returned.
// Use this method to send a group of photos, live photos, videos, documents or audios as an album. Documents and audio files can be only grouped in an album with messages of the same type. On success, an Array of Message objects that were sent is returned.
func SendMediaGroup(ctx context.Context, b *client.Bot, p *SendMediaGroupParams) ([]Message, error) {
return client.Call[*SendMediaGroupParams, []Message](ctx, b, "sendMediaGroup", p)
}
@@ -1701,13 +1785,17 @@ type SendLocationParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Latitude of the location
Latitude float64 `json:"latitude"`
// Longitude of the location
Longitude float64 `json:"longitude"`
// The radius of uncertainty for the location, measured in meters; 0-1500
HorizontalAccuracy *float64 `json:"horizontal_accuracy,omitempty"`
// Period in seconds during which the location will be updated (see Live Locations, should be between 60 and 86400, or 0x7FFFFFFF for live locations that can be edited indefinitely
// Period in seconds during which the location will be updated (see Live Locations), must be between 60 and 86400, or 0x7FFFFFFF for live locations that can be edited indefinitely. Must be 0 for ephemeral messages.
LivePeriod *int64 `json:"live_period,omitempty"`
// For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified.
Heading *int64 `json:"heading,omitempty"`
@@ -1748,6 +1836,10 @@ type SendVenueParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Latitude of the venue
Latitude float64 `json:"latitude"`
// Longitude of the venue
@@ -1799,6 +1891,10 @@ type SendContactParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Contact's phone number
PhoneNumber string `json:"phone_number"`
// Contact's first name
@@ -1852,17 +1948,17 @@ type SendPollParams struct {
IsAnonymous *bool `json:"is_anonymous,omitempty"`
// Poll type, “quiz” or “regular”, defaults to “regular”
Type string `json:"type,omitempty"`
// Pass True, if the poll allows multiple answers, defaults to False
// Pass True if the poll allows multiple answers, defaults to False
AllowsMultipleAnswers *bool `json:"allows_multiple_answers,omitempty"`
// Pass True, if the poll allows to change chosen answer options, defaults to False for quizzes and to True for regular polls
// Pass True if the poll allows to change chosen answer options, defaults to False for quizzes and to True for regular polls
AllowsRevoting *bool `json:"allows_revoting,omitempty"`
// Pass True, if the poll options must be shown in random order
// Pass True if the poll options must be shown in random order
ShuffleOptions *bool `json:"shuffle_options,omitempty"`
// Pass True, if answer options can be added to the poll after creation; not supported for anonymous polls and quizzes
// Pass True if answer options can be added to the poll after creation; not supported for anonymous polls and quizzes
AllowAddingOptions *bool `json:"allow_adding_options,omitempty"`
// Pass True, if poll results must be shown only after the poll closes
// Pass True if poll results must be shown only after the poll closes
HideResultsUntilCloses *bool `json:"hide_results_until_closes,omitempty"`
// Pass True, if voting is limited to users who have been members of the chat where the poll is being sent for more than 24 hours; for channel chats only
// Pass True if voting is limited to users who have been members of the chat where the poll is being sent for more than 24 hours; for channel chats only
MembersOnly *bool `json:"members_only,omitempty"`
// A JSON-serialized list of 0-12 two-letter ISO 3166-1 alpha-2 country codes indicating the countries from which users can vote in the poll; for channel chats only. Use “FT” as a country code to allow users with anonymous numbers to vote. If omitted or empty, then users from any country can participate in the poll.
CountryCodes []string `json:"country_codes,omitempty"`
@@ -1985,7 +2081,7 @@ type SendMessageDraftParams struct {
ChatID int64 `json:"chat_id"`
// Unique identifier for the target message thread
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Unique identifier of the message draft; must be non-zero. Changes of drafts with the same identifier are animated.
// Unique identifier of the message draft; must be non-zero. Changes to drafts with the same identifier are animated.
DraftID int64 `json:"draft_id"`
// Text of the message to be sent, 0-4096 characters after entities parsing. Pass an empty text to show a “Thinking…” placeholder.
Text string `json:"text,omitempty"`
@@ -2483,6 +2579,40 @@ func DeclineChatJoinRequest(ctx context.Context, b *client.Bot, p *DeclineChatJo
return client.Call[*DeclineChatJoinRequestParams, bool](ctx, b, "declineChatJoinRequest", p)
}
// AnswerChatJoinRequestQueryParams is the parameter set for AnswerChatJoinRequestQuery.
//
// Use this method to process a received chat join request query. Returns True on success.
type AnswerChatJoinRequestQueryParams struct {
// Unique identifier of the join request query
ChatJoinRequestQueryID string `json:"chat_join_request_query_id"`
// Result of the query. Must be either “approve” to allow the user to join the chat, “decline” to disallow the user to join the chat, or “queue” to leave the decision to other administrators.
Result Result `json:"result"`
}
// AnswerChatJoinRequestQuery calls the answerChatJoinRequestQuery Telegram Bot API method.
//
// Use this method to process a received chat join request query. Returns True on success.
func AnswerChatJoinRequestQuery(ctx context.Context, b *client.Bot, p *AnswerChatJoinRequestQueryParams) (bool, error) {
return client.Call[*AnswerChatJoinRequestQueryParams, bool](ctx, b, "answerChatJoinRequestQuery", p)
}
// SendChatJoinRequestWebAppParams is the parameter set for SendChatJoinRequestWebApp.
//
// Use this method to process a received chat join request query by showing a Mini App to the user before deciding the outcome. Call answerChatJoinRequestQuery to resolve the join request query based on the user interaction with the Mini App. Returns True on success.
type SendChatJoinRequestWebAppParams struct {
// Unique identifier of the join request query
ChatJoinRequestQueryID string `json:"chat_join_request_query_id"`
// An HTTPS URL of a Web App to be opened with additional data as specified in Initializing Web Apps
WebAppURL string `json:"web_app_url"`
}
// SendChatJoinRequestWebApp calls the sendChatJoinRequestWebApp Telegram Bot API method.
//
// Use this method to process a received chat join request query by showing a Mini App to the user before deciding the outcome. Call answerChatJoinRequestQuery to resolve the join request query based on the user interaction with the Mini App. Returns True on success.
func SendChatJoinRequestWebApp(ctx context.Context, b *client.Bot, p *SendChatJoinRequestWebAppParams) (bool, error) {
return client.Call[*SendChatJoinRequestWebAppParams, bool](ctx, b, "sendChatJoinRequestWebApp", p)
}
// SetChatPhotoParams is the parameter set for SetChatPhoto.
//
// Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate administrator rights. Returns True on success.
@@ -2697,7 +2827,7 @@ func GetChatAdministrators(ctx context.Context, b *client.Bot, p *GetChatAdminis
// GetChatMemberCountParams is the parameter set for GetChatMemberCount.
//
// Use this method to get the number of members in a chat. Returns Int on success.
// Use this method to get the number of members in a chat. Returns Integer on success.
type GetChatMemberCountParams struct {
// Unique identifier for the target chat or username of the target supergroup or channel in the format @username
ChatID ChatID `json:"chat_id"`
@@ -2705,7 +2835,7 @@ type GetChatMemberCountParams struct {
// GetChatMemberCount calls the getChatMemberCount Telegram Bot API method.
//
// Use this method to get the number of members in a chat. Returns Int on success.
// Use this method to get the number of members in a chat. Returns Integer on success.
func GetChatMemberCount(ctx context.Context, b *client.Bot, p *GetChatMemberCountParams) (int64, error) {
return client.Call[*GetChatMemberCountParams, int64](ctx, b, "getChatMemberCount", p)
}
@@ -2733,7 +2863,7 @@ func GetChatMember(ctx context.Context, b *client.Bot, p *GetChatMemberParams) (
// GetUserPersonalChatMessagesParams is the parameter set for GetUserPersonalChatMessages.
//
// Use this method to get the last messages from the personal chat (i.e., the chat currently added to their profile) of a given user. On success, an array of Message objects is returned.
// Use this method to get the last messages from the personal chat (i.e., the chat currently added to their profile) of a given user. On success, an Array of Message objects is returned.
type GetUserPersonalChatMessagesParams struct {
// Unique identifier for the target user
UserID int64 `json:"user_id"`
@@ -2743,7 +2873,7 @@ type GetUserPersonalChatMessagesParams struct {
// GetUserPersonalChatMessages calls the getUserPersonalChatMessages Telegram Bot API method.
//
// Use this method to get the last messages from the personal chat (i.e., the chat currently added to their profile) of a given user. On success, an array of Message objects is returned.
// Use this method to get the last messages from the personal chat (i.e., the chat currently added to their profile) of a given user. On success, an Array of Message objects is returned.
func GetUserPersonalChatMessages(ctx context.Context, b *client.Bot, p *GetUserPersonalChatMessagesParams) ([]Message, error) {
return client.Call[*GetUserPersonalChatMessagesParams, []Message](ctx, b, "getUserPersonalChatMessages", p)
}
@@ -3004,7 +3134,7 @@ type AnswerCallbackQueryParams struct {
CallbackQueryID string `json:"callback_query_id"`
// Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters.
Text string `json:"text,omitempty"`
// If True, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to false.
// If True, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to False.
ShowAlert *bool `json:"show_alert,omitempty"`
// URL that will be opened by the user's client. If you have created a Game and accepted the conditions via @BotFather, specify the URL that opens your game - note that this will only work if the query comes from a callback_game button.Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter.
URL string `json:"url,omitempty"`
@@ -3120,9 +3250,9 @@ func GetManagedBotAccessSettings(ctx context.Context, b *client.Bot, p *GetManag
type SetManagedBotAccessSettingsParams struct {
// User identifier of the managed bot whose access settings will be changed
UserID int64 `json:"user_id"`
// Pass True, if only selected users can access the bot. The bot's owner can always access it.
// Pass True if only selected users can access the bot. The bot's owner can always access it.
IsAccessRestricted bool `json:"is_access_restricted"`
// A JSON-serialized list of up to 10 identifiers of users who will have access to the bot in addition to its owner. Ignored if is_access_restricted is false.
// A JSON-serialized list of up to 10 identifiers of users who will have access to the bot in addition to its owner. Ignored if is_access_restricted is False.
AddedUserIds []int64 `json:"added_user_ids,omitempty"`
}
@@ -3638,7 +3768,7 @@ func RemoveBusinessAccountProfilePhoto(ctx context.Context, b *client.Bot, p *Re
type SetBusinessAccountGiftSettingsParams struct {
// Unique identifier of the business connection
BusinessConnectionID string `json:"business_connection_id"`
// Pass True, if a button for sending a gift to the user or by the business account must always be shown in the input field
// Pass True if a button for sending a gift to the user or by the business account must always be shown in the input field
ShowGiftButton bool `json:"show_gift_button"`
// Types of gifts accepted by the business account
AcceptedGiftTypes AcceptedGiftTypes `json:"accepted_gift_types"`
@@ -4004,7 +4134,7 @@ func SavePreparedKeyboardButton(ctx context.Context, b *client.Bot, p *SavePrepa
// EditMessageTextParams is the parameter set for EditMessageText.
//
// Use this method to edit text and game messages. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
// Use this method to edit text, rich and game messages. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
type EditMessageTextParams struct {
// Unique identifier of the business connection on behalf of which the message to be edited was sent
BusinessConnectionID string `json:"business_connection_id,omitempty"`
@@ -4014,21 +4144,23 @@ type EditMessageTextParams struct {
MessageID *int64 `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message.
InlineMessageID string `json:"inline_message_id,omitempty"`
// New text of the message, 1-4096 characters after entities parsing
Text string `json:"text"`
// New text of the message, 1-4096 characters after entity parsing; required if rich_message isn't specified
Text string `json:"text,omitempty"`
// Mode for parsing entities in the message text. See formatting options for more details.
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in message text, which can be specified instead of parse_mode
Entities []MessageEntity `json:"entities,omitempty"`
// Link preview generation options for the message
LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"`
// New rich content of the message; required if text isn't specified. Direct upload of new files isn't supported when an inline message is edited.
RichMessage *InputRichMessage `json:"rich_message,omitempty"`
// A JSON-serialized object for an inline keyboard
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageText calls the editMessageText Telegram Bot API method.
//
// Use this method to edit text and game messages. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
// Use this method to edit text, rich and game messages. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
func EditMessageText(ctx context.Context, b *client.Bot, p *EditMessageTextParams) (*MessageOrBool, error) {
return client.Call[*EditMessageTextParams, *MessageOrBool](ctx, b, "editMessageText", p)
}
@@ -4051,7 +4183,7 @@ type EditMessageCaptionParams struct {
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Pass True, if the caption must be shown above the message media. Supported only for animation, photo and video messages.
// Pass True if the caption must be shown above the message media. Supported only for animation, photo and video messages.
ShowCaptionAboveMedia *bool `json:"show_caption_above_media,omitempty"`
// A JSON-serialized object for an inline keyboard
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
@@ -4066,7 +4198,7 @@ func EditMessageCaption(ctx context.Context, b *client.Bot, p *EditMessageCaptio
// EditMessageMediaParams is the parameter set for EditMessageMedia.
//
// Use this method to edit animation, audio, document, live photo, photo, or video messages, or to add media to text messages. If a message is part of a message album, then it can be edited only to an audio for audio albums, only to a document for document albums and to a photo, a live photo, or a video otherwise. When an inline message is edited, a new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
// Use this method to edit animation, audio, document, live photo, photo, or video messages, or to replace a text or a rich message with a media. If a message is part of a message album, then it can be edited only to an audio for audio albums, only to a document for document albums and to a photo, a live photo, or a video otherwise. When an inline message is edited, a new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
type EditMessageMediaParams struct {
// Unique identifier of the business connection on behalf of which the message to be edited was sent
BusinessConnectionID string `json:"business_connection_id,omitempty"`
@@ -4076,7 +4208,7 @@ type EditMessageMediaParams struct {
MessageID *int64 `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message.
InlineMessageID string `json:"inline_message_id,omitempty"`
// A JSON-serialized object for a new media content of the message
// A JSON-serialized object for the new media content of the message
Media InputMedia `json:"media"`
// A JSON-serialized object for a new inline keyboard
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
@@ -4121,7 +4253,7 @@ func (p *EditMessageMediaParams) MultipartFiles() []client.MultipartFile {
// EditMessageMedia calls the editMessageMedia Telegram Bot API method.
//
// Use this method to edit animation, audio, document, live photo, photo, or video messages, or to add media to text messages. If a message is part of a message album, then it can be edited only to an audio for audio albums, only to a document for document albums and to a photo, a live photo, or a video otherwise. When an inline message is edited, a new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
// Use this method to edit animation, audio, document, live photo, photo, or video messages, or to replace a text or a rich message with a media. If a message is part of a message album, then it can be edited only to an audio for audio albums, only to a document for document albums and to a photo, a live photo, or a video otherwise. When an inline message is edited, a new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. Note that business messages that were not sent by the bot and do not contain an inline keyboard can only be edited within 48 hours from the time they were sent.
func EditMessageMedia(ctx context.Context, b *client.Bot, p *EditMessageMediaParams) (*MessageOrBool, error) {
return client.Call[*EditMessageMediaParams, *MessageOrBool](ctx, b, "editMessageMedia", p)
}
@@ -4251,6 +4383,134 @@ func StopPoll(ctx context.Context, b *client.Bot, p *StopPollParams) (*Poll, err
return client.Call[*StopPollParams, *Poll](ctx, b, "stopPoll", p)
}
// EditEphemeralMessageTextParams is the parameter set for EditEphemeralMessageText.
//
// Use this method to edit an ephemeral text message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
type EditEphemeralMessageTextParams struct {
// Unique identifier for the target chat or username of the target supergroup in the format @username
ChatID ChatID `json:"chat_id"`
// Identifier of the user who received the message
ReceiverUserID int64 `json:"receiver_user_id"`
// Identifier of the ephemeral message to edit
EphemeralMessageID int64 `json:"ephemeral_message_id"`
// New text of the message, 1-4096 characters after entity parsing
Text string `json:"text"`
// Mode for parsing entities in the message text. See formatting options for more details.
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in message text, which can be specified instead of parse_mode
Entities []MessageEntity `json:"entities,omitempty"`
// Link preview generation options for the message
LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"`
// A JSON-serialized object for an inline keyboard
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditEphemeralMessageText calls the editEphemeralMessageText Telegram Bot API method.
//
// Use this method to edit an ephemeral text message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
func EditEphemeralMessageText(ctx context.Context, b *client.Bot, p *EditEphemeralMessageTextParams) (bool, error) {
return client.Call[*EditEphemeralMessageTextParams, bool](ctx, b, "editEphemeralMessageText", p)
}
// EditEphemeralMessageMediaParams is the parameter set for EditEphemeralMessageMedia.
//
// Use this method to edit the media of an ephemeral message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
type EditEphemeralMessageMediaParams struct {
// Unique identifier for the target chat or username of the target supergroup in the format @username
ChatID ChatID `json:"chat_id"`
// Identifier of the user who received the message
ReceiverUserID int64 `json:"receiver_user_id"`
// Identifier of the ephemeral message to edit
EphemeralMessageID int64 `json:"ephemeral_message_id"`
// A JSON-serialized object for the new media content of the message. A new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL.
Media InputMedia `json:"media"`
// A JSON-serialized object for an inline keyboard
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// HasFile reports whether a multipart upload is required.
func (p *EditEphemeralMessageMediaParams) HasFile() bool {
return false
}
// MultipartFields returns the non-file fields used in the multipart body.
func (p *EditEphemeralMessageMediaParams) MultipartFields() map[string]string {
out := map[string]string{}
out["chat_id"] = p.ChatID.String()
out["receiver_user_id"] = strconv.FormatInt(p.ReceiverUserID, 10)
out["ephemeral_message_id"] = strconv.FormatInt(p.EphemeralMessageID, 10)
if b, _ := json.Marshal(p.Media); len(b) > 0 {
out["media"] = string(b)
}
if p.ReplyMarkup != nil {
if b, _ := json.Marshal(p.ReplyMarkup); len(b) > 0 {
out["reply_markup"] = string(b)
}
}
return out
}
// MultipartFiles returns the file parts.
func (p *EditEphemeralMessageMediaParams) MultipartFiles() []client.MultipartFile {
var files []client.MultipartFile
return files
}
// EditEphemeralMessageMedia calls the editEphemeralMessageMedia Telegram Bot API method.
//
// Use this method to edit the media of an ephemeral message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
func EditEphemeralMessageMedia(ctx context.Context, b *client.Bot, p *EditEphemeralMessageMediaParams) (bool, error) {
return client.Call[*EditEphemeralMessageMediaParams, bool](ctx, b, "editEphemeralMessageMedia", p)
}
// EditEphemeralMessageCaptionParams is the parameter set for EditEphemeralMessageCaption.
//
// Use this method to edit the caption of an ephemeral message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
type EditEphemeralMessageCaptionParams struct {
// Unique identifier for the target chat or username of the target supergroup in the format @username
ChatID ChatID `json:"chat_id"`
// Identifier of the user who received the message
ReceiverUserID int64 `json:"receiver_user_id"`
// Identifier of the ephemeral message to edit
EphemeralMessageID int64 `json:"ephemeral_message_id"`
// New caption of the message, 0-1024 characters after entities parsing
Caption string `json:"caption,omitempty"`
// Mode for parsing entities in the message caption. See formatting options for more details.
ParseMode ParseMode `json:"parse_mode,omitempty"`
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// A JSON-serialized object for an inline keyboard
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditEphemeralMessageCaption calls the editEphemeralMessageCaption Telegram Bot API method.
//
// Use this method to edit the caption of an ephemeral message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
func EditEphemeralMessageCaption(ctx context.Context, b *client.Bot, p *EditEphemeralMessageCaptionParams) (bool, error) {
return client.Call[*EditEphemeralMessageCaptionParams, bool](ctx, b, "editEphemeralMessageCaption", p)
}
// EditEphemeralMessageReplyMarkupParams is the parameter set for EditEphemeralMessageReplyMarkup.
//
// Use this method to edit only the reply markup of an ephemeral message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
type EditEphemeralMessageReplyMarkupParams struct {
// Unique identifier for the target chat or username of the target supergroup in the format @username
ChatID ChatID `json:"chat_id"`
// Identifier of the user who received the message
ReceiverUserID int64 `json:"receiver_user_id"`
// Identifier of the ephemeral message to edit
EphemeralMessageID int64 `json:"ephemeral_message_id"`
// A JSON-serialized object for an inline keyboard
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditEphemeralMessageReplyMarkup calls the editEphemeralMessageReplyMarkup Telegram Bot API method.
//
// Use this method to edit only the reply markup of an ephemeral message. Note that it is not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned.
func EditEphemeralMessageReplyMarkup(ctx context.Context, b *client.Bot, p *EditEphemeralMessageReplyMarkupParams) (bool, error) {
return client.Call[*EditEphemeralMessageReplyMarkupParams, bool](ctx, b, "editEphemeralMessageReplyMarkup", p)
}
// ApproveSuggestedPostParams is the parameter set for ApproveSuggestedPost.
//
// Use this method to approve a suggested post in a direct messages chat. The bot must have the 'can_post_messages' administrator right in the corresponding channel chat. Returns True on success.
@@ -4323,6 +4583,25 @@ func DeleteMessages(ctx context.Context, b *client.Bot, p *DeleteMessagesParams)
return client.Call[*DeleteMessagesParams, bool](ctx, b, "deleteMessages", p)
}
// DeleteEphemeralMessageParams is the parameter set for DeleteEphemeralMessage.
//
// Use this method to delete an ephemeral message. Note that it is not guaranteed that the user will receive the message deletion event, especially if they are offline. Returns True on success.
type DeleteEphemeralMessageParams struct {
// Unique identifier for the target chat or username of the target supergroup in the format @username
ChatID ChatID `json:"chat_id"`
// Identifier of the user who received the message
ReceiverUserID int64 `json:"receiver_user_id"`
// Identifier of the ephemeral message to delete
EphemeralMessageID int64 `json:"ephemeral_message_id"`
}
// DeleteEphemeralMessage calls the deleteEphemeralMessage Telegram Bot API method.
//
// Use this method to delete an ephemeral message. Note that it is not guaranteed that the user will receive the message deletion event, especially if they are offline. Returns True on success.
func DeleteEphemeralMessage(ctx context.Context, b *client.Bot, p *DeleteEphemeralMessageParams) (bool, error) {
return client.Call[*DeleteEphemeralMessageParams, bool](ctx, b, "deleteEphemeralMessage", p)
}
// DeleteMessageReactionParams is the parameter set for DeleteMessageReaction.
//
// Use this method to remove a reaction from a message in a group or a supergroup chat. The bot must have the 'can_delete_messages' administrator right in the chat. Returns True on success.
@@ -4375,6 +4654,10 @@ type SendStickerParams struct {
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// For outgoing ephemeral messages, unique identifier of the user who will receive the message; for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially if they are offline. See ephemeral message sending for more details.
ReceiverUserID *int64 `json:"receiver_user_id,omitempty"`
// For outgoing ephemeral messages, identifier of the callback query which triggered the message if any
CallbackQueryID string `json:"callback_query_id,omitempty"`
// Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP sticker from the Internet, or upload a new .WEBP, .TGS, or .WEBM sticker using multipart/form-data. More information on Sending Files ». Video and animated stickers can't be sent via an HTTP URL.
Sticker *InputFile `json:"sticker"`
// Emoji associated with the sticker; only for just uploaded stickers
@@ -4416,6 +4699,12 @@ func (p *SendStickerParams) MultipartFields() map[string]string {
if p.DirectMessagesTopicID != nil {
out["direct_messages_topic_id"] = strconv.FormatInt(*p.DirectMessagesTopicID, 10)
}
if p.ReceiverUserID != nil {
out["receiver_user_id"] = strconv.FormatInt(*p.ReceiverUserID, 10)
}
if p.CallbackQueryID != "" {
out["callback_query_id"] = p.CallbackQueryID
}
if p.Emoji != "" {
out["emoji"] = p.Emoji
}
@@ -4795,13 +5084,71 @@ func DeleteStickerSet(ctx context.Context, b *client.Bot, p *DeleteStickerSetPar
return client.Call[*DeleteStickerSetParams, bool](ctx, b, "deleteStickerSet", p)
}
// SendRichMessageParams is the parameter set for SendRichMessage.
//
// Use this method to send rich messages. If the message contains a block with a media element, then the bot must have the right to send the media to the chat. On success, the sent Message is returned.
type SendRichMessageParams struct {
// Unique identifier of the business connection on behalf of which the message will be sent. Bot can send rich messages on behalf of a business account only if the corresponding user can send rich messages.
BusinessConnectionID string `json:"business_connection_id,omitempty"`
// Unique identifier for the target chat or username of the target bot, supergroup or channel in the format @username
ChatID ChatID `json:"chat_id"`
// Unique identifier for the target message thread (topic) of a forum; for forum supergroups and private chats of bots with forum topic mode enabled only
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat
DirectMessagesTopicID *int64 `json:"direct_messages_topic_id,omitempty"`
// The message to be sent
RichMessage InputRichMessage `json:"rich_message"`
// Sends the message silently. Users will receive a notification with no sound.
DisableNotification *bool `json:"disable_notification,omitempty"`
// Protects the contents of the sent message from forwarding and saving
ProtectContent *bool `json:"protect_content,omitempty"`
// Pass True to allow up to 1000 messages per second, ignoring broadcasting limits for a fee of 0.1 Telegram Stars per message. The relevant Stars will be withdrawn from the bot's balance.
AllowPaidBroadcast *bool `json:"allow_paid_broadcast,omitempty"`
// Unique identifier of the message effect to be added to the message; for private chats only
MessageEffectID string `json:"message_effect_id,omitempty"`
// A JSON-serialized object containing the parameters of the suggested post to send; for direct messages chats only. If the message is sent as a reply to another suggested post, then that suggested post is automatically declined.
SuggestedPostParameters *SuggestedPostParameters `json:"suggested_post_parameters,omitempty"`
// Description of the message to reply to
ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"`
// Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove a reply keyboard or to force a reply from the user.
ReplyMarkup any `json:"reply_markup,omitempty"`
}
// SendRichMessage calls the sendRichMessage Telegram Bot API method.
//
// Use this method to send rich messages. If the message contains a block with a media element, then the bot must have the right to send the media to the chat. On success, the sent Message is returned.
func SendRichMessage(ctx context.Context, b *client.Bot, p *SendRichMessageParams) (*Message, error) {
return client.Call[*SendRichMessageParams, *Message](ctx, b, "sendRichMessage", p)
}
// SendRichMessageDraftParams is the parameter set for SendRichMessageDraft.
//
// Use this method to stream a partial rich message to a user while the message is being generated. Note that the streamed draft is ephemeral and acts as a temporary 30-second preview - once the output is finalized, you must call sendRichMessage with the complete message to persist it in the user's chat. Returns True on success.
type SendRichMessageDraftParams struct {
// Unique identifier for the target private chat
ChatID int64 `json:"chat_id"`
// Unique identifier for the target message thread
MessageThreadID *int64 `json:"message_thread_id,omitempty"`
// Unique identifier of the message draft; must be non-zero. Changes to drafts with the same identifier are animated.
DraftID int64 `json:"draft_id"`
// The partial message to be streamed. Direct upload of new files isn't supported.
RichMessage InputRichMessage `json:"rich_message"`
}
// SendRichMessageDraft calls the sendRichMessageDraft Telegram Bot API method.
//
// Use this method to stream a partial rich message to a user while the message is being generated. Note that the streamed draft is ephemeral and acts as a temporary 30-second preview - once the output is finalized, you must call sendRichMessage with the complete message to persist it in the user's chat. Returns True on success.
func SendRichMessageDraft(ctx context.Context, b *client.Bot, p *SendRichMessageDraftParams) (bool, error) {
return client.Call[*SendRichMessageDraftParams, bool](ctx, b, "sendRichMessageDraft", p)
}
// AnswerInlineQueryParams is the parameter set for AnswerInlineQuery.
//
// Use this method to send answers to an inline query. On success, True is returned.No more than 50 results per query are allowed.
type AnswerInlineQueryParams struct {
// Unique identifier for the answered query
InlineQueryID string `json:"inline_query_id"`
// A JSON-serialized array of results for the inline query
// A JSON-serialized Array of results for the inline query
Results []InlineQueryResult `json:"results"`
// The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300.
CacheTime *int64 `json:"cache_time,omitempty"`
@@ -4844,7 +5191,7 @@ type SendInvoiceParams struct {
Prices []LabeledPrice `json:"prices"`
// The maximum accepted amount for tips in the smallest units of the currency (integer, not float/double). For example, for a maximum tip of US$ 1.45 pass max_tip_amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0. Not supported for payments in Telegram Stars.
MaxTipAmount *int64 `json:"max_tip_amount,omitempty"`
// A JSON-serialized array of suggested amounts of tips in the smallest units of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount.
// A JSON-serialized Array of suggested amounts of tips in the smallest units of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount.
SuggestedTipAmounts []int64 `json:"suggested_tip_amounts,omitempty"`
// Unique deep-linking parameter. If left empty, forwarded copies of the sent message will have a Pay button, allowing multiple users to pay directly from the forwarded message, using the same invoice. If non-empty, forwarded copies of the sent message will have a URL button with a deep link to the bot (instead of a Pay button), with the value used as the start parameter.
StartParameter string `json:"start_parameter,omitempty"`
@@ -4917,7 +5264,7 @@ type CreateInvoiceLinkParams struct {
SubscriptionPeriod *int64 `json:"subscription_period,omitempty"`
// The maximum accepted amount for tips in the smallest units of the currency (integer, not float/double). For example, for a maximum tip of US$ 1.45 pass max_tip_amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0. Not supported for payments in Telegram Stars.
MaxTipAmount *int64 `json:"max_tip_amount,omitempty"`
// A JSON-serialized array of suggested amounts of tips in the smallest units of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount.
// A JSON-serialized Array of suggested amounts of tips in the smallest units of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount.
SuggestedTipAmounts []int64 `json:"suggested_tip_amounts,omitempty"`
// JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.
ProviderData string `json:"provider_data,omitempty"`
@@ -4960,7 +5307,7 @@ type AnswerShippingQueryParams struct {
ShippingQueryID string `json:"shipping_query_id"`
// Pass True if delivery to the specified address is possible and False if there are any problems (for example, if delivery to the specified address is not possible)
Ok bool `json:"ok"`
// Required if ok is True. A JSON-serialized array of available shipping options.
// Required if ok is True. A JSON-serialized Array of available shipping options.
ShippingOptions []ShippingOption `json:"shipping_options,omitempty"`
// Required if ok is False. Error message in human readable form that explains why it is impossible to complete the order (e.g. “Sorry, delivery to your desired address is unavailable”). Telegram will display this message to the user.
ErrorMessage string `json:"error_message,omitempty"`
@@ -5065,7 +5412,7 @@ func EditUserStarSubscription(ctx context.Context, b *client.Bot, p *EditUserSta
type SetPassportDataErrorsParams struct {
// User identifier
UserID int64 `json:"user_id"`
// A JSON-serialized array describing the errors
// A JSON-serialized Array describing the errors
Errors []PassportElementError `json:"errors"`
}
+1359 -21
View File
File diff suppressed because it is too large Load Diff
+2270 -64
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -45,6 +45,7 @@ func (b *Bot) Logger() Logger { return b.logger }
// NewDefaultHTTPDoer); the default codec wraps encoding/json; the default
// logger discards records.
func New(token string, opts ...Option) *Bot {
fireTelemetryOnce()
b := &Bot{
token: token,
base: defaultBaseURL,
+33
View File
@@ -0,0 +1,33 @@
package client
import (
"sync"
telemetry "github.com/lukaszraczylo/oss-telemetry"
)
// telemetryOnce guards the single anonymous "library used" ping that is sent
// on the first call to New. Long-running bots typically construct one Bot;
// short-lived programs or test suites may construct many, but the Once gate
// keeps the fire-and-forget call from amplifying into per-construction pings.
var telemetryOnce sync.Once
// fireTelemetryOnce dispatches a fire-and-forget anonymous adoption ping.
//
// The call is failproof by contract of oss-telemetry: it never blocks New,
// never panics, never returns errors, and silently no-ops if disabled or
// if the network is unavailable.
//
// Opt-out is honored via any of these environment variables (case-insensitive
// truthy values "1", "true", "yes", "on"):
//
// - DO_NOT_TRACK
// - OSS_TELEMETRY_DISABLED
// - GO_TELEGRAM_DISABLE_TELEMETRY
//
// See README §Telemetry for the full disclosure.
func fireTelemetryOnce() {
telemetryOnce.Do(func() {
telemetry.SendForModule("go-telegram", "github.com/lukaszraczylo/go-telegram", Version)
})
}
+54
View File
@@ -0,0 +1,54 @@
package client
import (
"os"
"sync"
"testing"
telemetry "github.com/lukaszraczylo/oss-telemetry"
)
// TestMain disables outgoing telemetry for the duration of this package's
// test suite. The library's own tests construct many Bot instances; without
// this guard they would each contribute a real ping to the public endpoint.
// End-user test suites that construct Bot are not affected by this — only
// tests inside this package are.
func TestMain(m *testing.M) {
telemetry.Disable()
os.Exit(m.Run())
}
// TestFireTelemetryOnce_OnlyFiresOnce verifies the sync.Once gate. Even if
// New is called repeatedly, the underlying telemetry.Send is invoked at most
// once per process. We can't observe the network call directly (telemetry
// is disabled here via TestMain) so we assert on the once-Do count via a
// fresh local sync.Once paralleling the production one.
func TestFireTelemetryOnce_OnlyFiresOnce(t *testing.T) {
// Reset the package-level Once so this test starts from a clean state.
telemetryOnce = sync.Once{}
t.Cleanup(func() { telemetryOnce = sync.Once{} })
calls := 0
probe := func() { telemetryOnce.Do(func() { calls++ }) }
for i := 0; i < 50; i++ {
probe()
}
if calls != 1 {
t.Fatalf("expected exactly 1 Once execution, got %d", calls)
}
}
// TestNew_DoesNotPanicUnderRepeatedConstruction is a smoke test that
// telemetry wiring does not affect New's existing contract. New must never
// panic, regardless of telemetry state.
func TestNew_DoesNotPanicUnderRepeatedConstruction(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("New panicked: %v", r)
}
}()
for i := 0; i < 20; i++ {
_ = New("test-token-" + string(rune('A'+i)))
}
}
+11
View File
@@ -0,0 +1,11 @@
package client
// Version is a fallback version string used only when Go's build info is
// unavailable (replace directives, detached `go run`) or has been overridden
// via linker flags. The authoritative version forwarded to telemetry is
// resolved at runtime by [telemetry.SendForModule] from the build info of
// whatever binary linked this library, so this constant does NOT need to be
// bumped on every release. Exposed as a var (not const) for ldflag override:
//
// go build -ldflags="-X github.com/lukaszraczylo/go-telegram/client.Version=1.2.3"
var Version = "0.0.0-fallback"
+11 -5
View File
@@ -125,12 +125,18 @@ func TestAuditDrift_InvalidRefReturnsError(t *testing.T) {
}
func TestAuditDrift_SameRefNoDrift(t *testing.T) {
// Compare HEAD's IR against itself, not against the working tree: during a
// regen run the working tree legitimately carries new methods, and drift
// against HEAD is the expected outcome rather than a failure.
irPath := "../../internal/spec/api.json"
cur, err := loadIR(irPath)
out, err := exec.Command("git", "show", "HEAD:"+irPath).Output()
if err != nil {
t.Skip("api.json not available, skipping drift test")
t.Skip("api.json not available at HEAD, skipping drift test")
}
changes, err := auditDrift(irPath, "HEAD", cur)
var head spec.API
require.NoError(t, json.Unmarshal(out, &head))
changes, err := auditDrift(irPath, "HEAD", &head)
require.NoError(t, err)
require.Empty(t, changes)
}
@@ -166,7 +172,7 @@ func TestAuditAny_FlagsUnknownMethodReturn(t *testing.T) {
},
},
}
out := auditAny(api)
out := auditAny(api, &spec.Overrides{})
require.Len(t, out, 1)
require.Contains(t, out[0], "any return: weirdMethod")
}
@@ -182,7 +188,7 @@ func TestAuditAny_FlagsUnknownMethodParam(t *testing.T) {
},
},
}
out := auditAny(api)
out := auditAny(api, &spec.Overrides{})
require.Len(t, out, 1)
require.Contains(t, out[0], "any param: weirdMethod.Thing")
}
+7 -6
View File
@@ -54,7 +54,7 @@ func main() {
var problems []string
problems = append(problems, auditBool(api, overrides)...)
problems = append(problems, auditAny(api)...)
problems = append(problems, auditAny(api, overrides)...)
driftFound := false
if *checkDrift {
@@ -140,8 +140,9 @@ func looksGenuinelyBool(doc string) bool {
// auditAny scans the IR for any KindOneOf TypeRef that would render as
// `any` in generated code (not matched by ChatID/InputFile-or-string/known
// union heuristics). Reports each occurrence with location.
func auditAny(api *spec.API) []string {
// union heuristics). Reports each occurrence with location, except the
// locations approved in overrides.
func auditAny(api *spec.API, ov *spec.Overrides) []string {
var out []string
isKnownUnion := func(variants []string) bool {
if hasVariants(variants, "int64", "string") {
@@ -166,17 +167,17 @@ func auditAny(api *spec.API) []string {
}
for _, t := range api.Types {
for _, f := range t.Fields {
if isAny(f.Type) {
if isAny(f.Type) && !ov.IsAnyApproved(t.Name+"."+f.Name) {
out = append(out, fmt.Sprintf("any field: %s.%s (variants=%v)", t.Name, f.Name, f.Type.Variants))
}
}
}
for _, m := range api.Methods {
if isAny(m.Returns) {
if isAny(m.Returns) && !ov.IsAnyApproved(m.Name) {
out = append(out, fmt.Sprintf("any return: %s (variants=%v)", m.Name, m.Returns.Variants))
}
for _, p := range m.Params {
if isAny(p.Type) {
if isAny(p.Type) && !ov.IsAnyApproved(m.Name+"."+p.Name) {
out = append(out, fmt.Sprintf("any param: %s.%s (variants=%v)", m.Name, p.Name, p.Type.Variants))
}
}
+31 -5
View File
@@ -66,11 +66,37 @@ func TestAuditAny_FlagsUnrecognisedOneOf(t *testing.T) {
},
},
}
out := auditAny(api)
out := auditAny(api, &spec.Overrides{})
require.Len(t, out, 1)
require.Contains(t, out[0], "any field: Foo.Bar")
}
func TestAuditAny_SkipsApprovedLocations(t *testing.T) {
api := &spec.API{
Types: []spec.TypeDecl{
{
Name: "Foo",
Fields: []spec.Field{
{Name: "Bar", JSONName: "bar", Type: spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"A", "B"}}},
},
},
},
Methods: []spec.MethodDecl{
{
Name: "doThing",
Returns: spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"A", "B"}},
Params: []spec.Field{
{Name: "Baz", JSONName: "baz", Type: spec.TypeRef{Kind: spec.KindOneOf, Variants: []string{"A", "B"}}},
},
},
},
}
require.Len(t, auditAny(api, &spec.Overrides{}), 3)
ov := &spec.Overrides{ApprovedAnyLocations: []string{"Foo.Bar", "doThing", "doThing.Baz"}}
require.Empty(t, auditAny(api, ov))
}
func TestAuditAny_SkipsChatIDShape(t *testing.T) {
api := &spec.API{
Types: []spec.TypeDecl{
@@ -82,7 +108,7 @@ func TestAuditAny_SkipsChatIDShape(t *testing.T) {
},
},
}
require.Empty(t, auditAny(api))
require.Empty(t, auditAny(api, &spec.Overrides{}))
}
func TestAuditAny_SkipsKnownUnion(t *testing.T) {
@@ -97,7 +123,7 @@ func TestAuditAny_SkipsKnownUnion(t *testing.T) {
},
},
}
require.Empty(t, auditAny(api))
require.Empty(t, auditAny(api, &spec.Overrides{}))
}
func TestAuditAny_SkipsReplyMarkupShape(t *testing.T) {
@@ -111,7 +137,7 @@ func TestAuditAny_SkipsReplyMarkupShape(t *testing.T) {
},
},
}
require.Empty(t, auditAny(api))
require.Empty(t, auditAny(api, &spec.Overrides{}))
}
func TestAuditAny_SkipsInputFileShape(t *testing.T) {
@@ -125,7 +151,7 @@ func TestAuditAny_SkipsInputFileShape(t *testing.T) {
},
},
}
require.Empty(t, auditAny(api))
require.Empty(t, auditAny(api, &spec.Overrides{}))
}
// ---- diffSignatures ------------------------------------------------------
+8 -8
View File
@@ -420,7 +420,7 @@ func funcs(plan *enumPlan) template.FuncMap {
return goField(plan, parent, f)
},
"goFieldP": func(methodName string, f spec.Field) string {
return goFieldX(plan, "", title(methodName)+"Params", f)
return goFieldX(plan, methodEnumParent(methodName), title(methodName)+"Params", f)
},
"docComment": docComment,
"isOptional": func(f spec.Field) bool { return !f.Required },
@@ -432,7 +432,7 @@ func funcs(plan *enumPlan) template.FuncMap {
return multipartFieldEntry(plan, parent, f)
},
"multipartFieldEntryP": func(methodName string, f spec.Field) string {
return multipartFieldEntryX(plan, "", title(methodName)+"Params", f)
return multipartFieldEntryX(plan, methodEnumParent(methodName), title(methodName)+"Params", f)
},
"multipartFileEntry": multipartFileEntry,
"returnGoType": returnGoType,
@@ -915,15 +915,15 @@ func buildUnionTypeSet(api *spec.API) map[string]bool {
// used in generated test param literals. plan supplies typed-enum names
// so a method-param sentinel for a ParseMode field becomes a typed
// constant rather than a magic string.
func makeSentinelValue(unionTypes map[string]bool, plan *enumPlan) func(spec.Field) string {
return func(f spec.Field) string {
return sentinelForField(f, unionTypes, plan)
func makeSentinelValue(unionTypes map[string]bool, plan *enumPlan) func(string, spec.Field) string {
return func(methodName string, f spec.Field) string {
return sentinelForField(methodName, f, unionTypes, plan)
}
}
func sentinelForField(f spec.Field, unionTypes map[string]bool, plan *enumPlan) string {
if name := plan.FieldEnum("", f.Name); name != "" && len(f.EnumValues) > 0 {
return constName(name, f.EnumValues[0])
func sentinelForField(methodName string, f spec.Field, unionTypes map[string]bool, plan *enumPlan) string {
if name := plan.FieldEnum(methodEnumParent(methodName), f.Name); name != "" && len(f.EnumValues) > 0 {
return plan.ConstFor(name, f.EnumValues[0])
}
tr := f.Type
switch tr.Kind {
+73 -10
View File
@@ -26,15 +26,21 @@ type enumPlan struct {
}
// enumKey identifies a single Field occurrence so the emitter can look
// up the enum name later. Parent is "" for method params (the method
// doesn't share a Go type with the field).
// up the enum name later. Parent is the type name for struct fields and
// methodEnumParent(name) for method params, so two methods sharing a
// param name never alias each other's enum (or non-enum) fields.
func enumKey(parent, fieldName string) string { return parent + "::" + fieldName }
// methodEnumParent is the enum-plan key parent for a method's params.
// The "method:" prefix keeps it disjoint from type names.
func methodEnumParent(methodName string) string { return "method:" + methodName }
// planEnums walks the IR, decides on enum names, deduplicates, and
// returns an enumPlan. All scraper-marked enum fields are covered.
func planEnums(api *spec.API) *enumPlan {
type ref struct {
parent string
parent string // naming parent ("" for method params)
keyParent string // byField key parent (methodEnumParent(...) for method params)
fieldName string
jsonName string
values []string
@@ -52,7 +58,7 @@ func planEnums(api *spec.API) *enumPlan {
}
var refs []ref
collect := func(parent string, fields []spec.Field) {
collect := func(parent, keyParent string, fields []spec.Field) {
for _, f := range fields {
if len(f.EnumValues) == 0 {
continue
@@ -62,6 +68,7 @@ func planEnums(api *spec.API) *enumPlan {
}
refs = append(refs, ref{
parent: parent,
keyParent: keyParent,
fieldName: f.Name,
jsonName: f.JSONName,
values: f.EnumValues,
@@ -70,13 +77,15 @@ func planEnums(api *spec.API) *enumPlan {
}
}
for _, t := range api.Types {
collect(t.Name, t.Fields)
collect(t.Name, t.Name, t.Fields)
}
for _, m := range api.Methods {
// Method params have no shared Go parent type, so we pass "" as
// the parent. The default-name heuristic still produces the
// right answer for ParseMode-style enums.
collect("", m.Params)
// Method params have no shared Go parent type, so the naming
// parent is "" (the default-name heuristic still produces the
// right answer for ParseMode-style enums), but the byField key
// is method-scoped so a same-named non-enum param on another
// method can never pick up this enum.
collect("", methodEnumParent(m.Name), m.Params)
}
// candidate name per ref (before collision resolution)
@@ -196,7 +205,7 @@ func planEnums(api *spec.API) *enumPlan {
}
for i, r := range refs {
name := groups[r.valueKey].name
plan.byField[enumKey(r.parent, r.fieldName)] = name
plan.byField[enumKey(r.keyParent, r.fieldName)] = name
_ = i
}
for vk, g := range groups {
@@ -389,6 +398,18 @@ func (p *enumPlan) FieldEnum(parent, fieldName string) string {
return p.byField[enumKey(parent, fieldName)]
}
// ConstFor returns the collision-resolved constant identifier for value
// within the named enum declaration. Falls back to the plain constName
// when the declaration is unknown (unit tests with partial plans).
func (p *enumPlan) ConstFor(enumName, value string) string {
if p != nil {
if d, ok := p.decls[enumName]; ok {
return d.ConstName(value)
}
}
return constName(enumName, value)
}
// defaultEnumName picks an initial Go enum name for a field. parse_mode
// fields collapse to the canonical "ParseMode"; otherwise the name is
// parent + PascalCase(jsonName).
@@ -406,6 +427,48 @@ func constName(enumName, value string) string {
return enumName + valuePascal(value)
}
// ConstName returns the constant identifier for value within this enum,
// resolving case-collisions between values that Pascal-case to the same
// identifier (e.g. RichBlockListItem.type values "a" and "A"). Colliding
// values get a Lower/Upper prefix on the value part based on the case of
// the value's first letter; any residual collision gets a numeric suffix.
func (e enumDecl) ConstName(value string) string {
counts := map[string]int{}
for _, v := range e.Values {
counts[constName(e.Name, v)]++
}
plain := constName(e.Name, value)
if counts[plain] <= 1 {
return plain
}
cased := e.Name + casePrefix(value) + valuePascal(value)
// Residual collision (same value text repeated, or Lower/Upper still
// ambiguous): append the value's 1-based position for determinism.
casedCounts := map[string]int{}
pos := 0
for i, v := range e.Values {
if counts[constName(e.Name, v)] > 1 {
casedCounts[e.Name+casePrefix(v)+valuePascal(v)]++
}
if v == value {
pos = i + 1
}
}
if casedCounts[cased] > 1 {
return cased + itoa(pos)
}
return cased
}
// casePrefix distinguishes case-colliding enum values: "Lower" when the
// value starts with a lowercase letter, "Upper" otherwise.
func casePrefix(v string) string {
if v != "" && v[0] >= 'a' && v[0] <= 'z' {
return "Lower"
}
return "Upper"
}
func valuePascal(v string) string {
// "image/jpeg" → "ImageOfJpeg"
parts := strings.Split(v, "/")
+1 -1
View File
@@ -8,6 +8,6 @@ package api
type {{$e.Name}} string
const (
{{range $v := $e.Values}} {{enumConstName $e.Name $v}} {{$e.Name}} = {{printf "%q" $v}}
{{range $v := $e.Values}} {{$e.ConstName $v}} {{$e.Name}} = {{printf "%q" $v}}
{{end}})
{{end}}
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"testing"
"github.com/lukaszraczylo/go-telegram/internal/spec"
"github.com/stretchr/testify/require"
)
// Regression: Bot API 9.x RichBlockListItem.type enumerates "a"/"A"/"i"/"I"/"1",
// which Pascal-case to the same identifier. ConstName must disambiguate.
func TestEnumDecl_ConstName_CaseCollision(t *testing.T) {
d := enumDecl{Name: "RichBlockListItemType", Values: []string{"a", "A", "i", "I", "1"}}
got := map[string]bool{}
for _, v := range d.Values {
n := d.ConstName(v)
require.False(t, got[n], "duplicate const ident %q for value %q", n, v)
got[n] = true
}
require.Equal(t, "RichBlockListItemTypeLowerA", d.ConstName("a"))
require.Equal(t, "RichBlockListItemTypeUpperA", d.ConstName("A"))
// Non-colliding values keep the plain name.
require.Equal(t, "RichBlockListItemType1", d.ConstName("1"))
}
// Regression: answerChatJoinRequestQuery has an enum param named "result";
// answerGuestQuery has a NON-enum param also named "result". The enum plan
// must scope method params per method so the enum never leaks onto the
// other method's field.
func TestPlanEnums_MethodParamsScopedPerMethod(t *testing.T) {
api := &spec.API{
Methods: []spec.MethodDecl{
{
Name: "answerChatJoinRequestQuery",
Params: []spec.Field{{
Name: "Result", JSONName: "result", Required: true,
Type: spec.TypeRef{Kind: spec.KindPrimitive, Name: "string"},
EnumValues: []string{"approve", "decline", "queue"},
}},
},
{
Name: "answerGuestQuery",
Params: []spec.Field{{
Name: "Result", JSONName: "result", Required: true,
Type: spec.TypeRef{Kind: spec.KindNamed, Name: "InlineQueryResult"},
}},
},
},
}
plan := planEnums(api)
require.Equal(t, "Result",
plan.FieldEnum(methodEnumParent("answerChatJoinRequestQuery"), "Result"))
require.Empty(t,
plan.FieldEnum(methodEnumParent("answerGuestQuery"), "Result"),
"non-enum param must not inherit another method's enum")
}
+1 -1
View File
@@ -574,7 +574,7 @@ func TestSentinelForField(t *testing.T) {
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := sentinelForField(c.field, unionTypes, nil)
got := sentinelForField("testMethod", c.field, unionTypes, nil)
require.Contains(t, got, c.contains, "sentinelForField for %q", c.name)
})
}
+7 -7
View File
@@ -48,7 +48,7 @@ func Test_{{$mName}}_Success(t *testing.T) {
{{- if .Params}}
params := &{{$mName}}Params{
{{- range .Params}}{{if .Required}}
{{.Name}}: {{sentinelValue .}},{{end}}
{{.Name}}: {{sentinelValue $m.Name .}},{{end}}
{{- end}}
}
_, err := {{$mName}}(context.Background(), bot, params)
@@ -67,7 +67,7 @@ func Test_{{$mName}}_APIError(t *testing.T) {
{{- if .Params}}
params := &{{$mName}}Params{
{{- range .Params}}{{if .Required}}
{{.Name}}: {{sentinelValue .}},{{end}}
{{.Name}}: {{sentinelValue $m.Name .}},{{end}}
{{- end}}
}
_, err := {{$mName}}(context.Background(), bot, params)
@@ -89,7 +89,7 @@ func Test_{{$mName}}_NetworkError(t *testing.T) {
{{- if .Params}}
params := &{{$mName}}Params{
{{- range .Params}}{{if .Required}}
{{.Name}}: {{sentinelValue .}},{{end}}
{{.Name}}: {{sentinelValue $m.Name .}},{{end}}
{{- end}}
}
_, err := {{$mName}}(context.Background(), bot, params)
@@ -109,7 +109,7 @@ func Test_{{$mName}}_ParseError(t *testing.T) {
{{- if .Params}}
params := &{{$mName}}Params{
{{- range .Params}}{{if .Required}}
{{.Name}}: {{sentinelValue .}},{{end}}
{{.Name}}: {{sentinelValue $m.Name .}},{{end}}
{{- end}}
}
_, err := {{$mName}}(context.Background(), bot, params)
@@ -132,7 +132,7 @@ func Test_{{$mName}}_ContextCanceled(t *testing.T) {
{{- if .Params}}
params := &{{$mName}}Params{
{{- range .Params}}{{if .Required}}
{{.Name}}: {{sentinelValue .}},{{end}}
{{.Name}}: {{sentinelValue $m.Name .}},{{end}}
{{- end}}
}
_, err := {{$mName}}(ctx, bot, params)
@@ -177,7 +177,7 @@ func Test_{{$mName}}_Forbidden(t *testing.T) {
{{- if .Params}}
params := &{{$mName}}Params{
{{- range .Params}}{{if .Required}}
{{.Name}}: {{sentinelValue .}},{{end}}
{{.Name}}: {{sentinelValue $m.Name .}},{{end}}
{{- end}}
}
_, err := {{$mName}}(context.Background(), bot, params)
@@ -203,7 +203,7 @@ func Test_{{$mName}}_ServerError(t *testing.T) {
{{- if .Params}}
params := &{{$mName}}Params{
{{- range .Params}}{{if .Required}}
{{.Name}}: {{sentinelValue .}},{{end}}
{{.Name}}: {{sentinelValue $m.Name .}},{{end}}
{{- end}}
}
_, err := {{$mName}}(context.Background(), bot, params)
+1 -1
View File
@@ -42,7 +42,7 @@ func extractReturn(desc string) spec.TypeRef {
return spec.TypeRef{Kind: spec.KindArray, ElemType: &elem}
}},
// "an array of X of the sent messages is returned" (ForwardMessages/CopyMessages shape).
{regexp.MustCompile(`(?:[Oo]n success[,.]?\s+)?an? array of ([A-Z][A-Za-z0-9]+)(?:\s+of [^.]+?)?\s+(?:objects\s+)?(?:is|are) returned`), func(m []string) spec.TypeRef {
{regexp.MustCompile(`(?:[Oo]n success[,.]?\s+)?an? [Aa]rray of ([A-Z][A-Za-z0-9]+)(?:\s+of [^.]+?)?\s+(?:objects\s+)?(?:is|are) returned`), func(m []string) spec.TypeRef {
elem := primitiveOrNamed(m[1])
return spec.TypeRef{Kind: spec.KindArray, ElemType: &elem}
}},
+2
View File
@@ -29,6 +29,8 @@ func TestExtractReturn(t *testing.T) {
{"On success, an array of Message objects that were sent is returned.", spec.TypeRef{Kind: spec.KindArray, ElemType: &spec.TypeRef{Kind: spec.KindNamed, Name: "Message"}}},
// ForwardMessages/CopyMessages shape: "an array of X of the sent messages is returned".
{"On success, an array of MessageId of the sent messages is returned.", spec.TypeRef{Kind: spec.KindArray, ElemType: &spec.TypeRef{Kind: spec.KindNamed, Name: "MessageId"}}},
// Bot API 10.1 recapitalised the same sentence to "an Array of X".
{"On success, an Array of MessageId of the sent messages is returned.", spec.TypeRef{Kind: spec.KindArray, ElemType: &spec.TypeRef{Kind: spec.KindNamed, Name: "MessageId"}}},
// "Returns X on success" (no article) — OwnedGifts, StarAmount, Story, MenuButton, etc.
{"Returns the gifts received and owned by a managed business account. Returns OwnedGifts on success.", spec.TypeRef{Kind: spec.KindNamed, Name: "OwnedGifts"}},
{"Returns StarAmount on success.", spec.TypeRef{Kind: spec.KindNamed, Name: "StarAmount"}},
+3784 -950
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -87,6 +87,16 @@ var (
)
```
<a name="Version"></a>Version is a fallback version string used only when Go's build info is unavailable \(replace directives, detached \`go run\`\) or has been overridden via linker flags. The authoritative version forwarded to telemetry is resolved at runtime by \[telemetry.SendForModule\] from the build info of whatever binary linked this library, so this constant does NOT need to be bumped on every release. Exposed as a var \(not const\) for ldflag override:
```
go build -ldflags="-X github.com/lukaszraczylo/go-telegram/client.Version=1.2.3"
```
```go
var Version = "0.0.0-fallback"
```
<a name="Call"></a>
## func [Call](<https://github.com/lukaszraczylo/go-telegram/blob/main/client/call.go#L68>)
+1 -1
View File
@@ -26,7 +26,7 @@ func handleEcho(c *dispatch.Context, m *api.Message) error {
_, err := api.SendMessage(c.Ctx, c.Bot, &api.SendMessageParams{
ChatID: api.ChatIDFromInt(m.Chat.ID),
Text: m.Text,
ReplyParameters: &api.ReplyParameters{MessageID: m.MessageID},
ReplyParameters: &api.ReplyParameters{MessageID: &m.MessageID},
})
return err
}
+3 -2
View File
@@ -4,8 +4,10 @@ go 1.25.0
require (
github.com/goccy/go-json v0.10.6
github.com/lukaszraczylo/oss-telemetry v0.2.3
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.54.0
github.com/valyala/fasthttp v1.71.0
golang.org/x/net v0.56.0
)
require (
@@ -15,6 +17,5 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.71.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+10 -2
View File
@@ -6,6 +6,10 @@ github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/lukaszraczylo/oss-telemetry v0.2.1 h1:6ULyfzXplpdmIY/i01OPM1jeod9+L1RAhI0jtbVnJI0=
github.com/lukaszraczylo/oss-telemetry v0.2.1/go.mod h1:+Cn78qZo8rc3T9eZt0v3oICYRdd75wORtSidc8lNjDQ=
github.com/lukaszraczylo/oss-telemetry v0.2.3 h1:xoDtBqeZGmXj7IteiE1M5WMuzeoqag58qEleI0Cf2Ms=
github.com/lukaszraczylo/oss-telemetry v0.2.3/go.mod h1:+Cn78qZo8rc3T9eZt0v3oICYRdd75wORtSidc8lNjDQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
@@ -16,8 +20,12 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.71.0 h1:tepR7H+Guh9VUqxxcPggYi8R3lGUu2Rsdh+z7/FCY3k=
github.com/valyala/fasthttp v1.71.0/go.mod h1:z1sDUvOShhXq/C9mwH/fSm1Vb71tUJwmQdgkBrBNwnA=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+4273 -151
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -22,6 +22,12 @@ type Overrides struct {
// ApprovedBoolMethods lists methods whose returns are genuinely bool.
// The audit tool ignores these.
ApprovedBoolMethods []string `json:"approved_bool_methods,omitempty"`
// ApprovedAnyLocations lists IR locations whose union renders as `any` by
// design, keyed "<TypeName>.<FieldName>" for struct fields,
// "<methodName>.<ParamName>" for params and "<methodName>" for returns.
// The audit tool ignores these.
ApprovedAnyLocations []string `json:"approved_any_locations,omitempty"`
}
// LoadOverrides reads and parses overrides.json. Returns an empty Overrides
@@ -73,3 +79,17 @@ func (o *Overrides) IsBoolApproved(methodName string) bool {
}
return false
}
// IsAnyApproved reports whether location is on the approved `any` list.
// See ApprovedAnyLocations for the key format.
func (o *Overrides) IsAnyApproved(location string) bool {
if o == nil {
return false
}
for _, n := range o.ApprovedAnyLocations {
if n == location {
return true
}
}
return false
}
+3
View File
@@ -1,6 +1,9 @@
{
"method_returns": {},
"field_types": {},
"approved_any_locations": [
"InputRichMessageMedia.Media"
],
"approved_bool_methods": [
"setWebhook",
"deleteWebhook",
+1 -1
View File
@@ -1 +1 @@
snapshot_2026-05-20.html
snapshot_2026-07-21.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long