Repository navigation
Releases: FreshLabDev/tg
Release list
v0.2.0-alpha.3
Inline answers of files Telegram already has, edits of inline messages,
drafts with Stop, and a cover on a video. A bot that passes a callback's
message on as a value no longer compiles: see Breaking.
Added
- Video cover and start.
VideoOptionsgainedCover, the picture
clients show before the video plays,StartTimestampand
ShowCaptionAboveMedia. A cover is a file_id, a URL, a path on a
self-hosted server or an upload; an upload goes as a part of its own,
named by anattach://reference, so a video and its cover travel in one
sendVideo. - Send options.
CaptionOptionsgainedDisableNotification,
ProtectContentandMessageEffectID, so every captioned send (photo,
video, animation, audio, document, ephemeral or not) can go silently,
protected, or with an effect, without a new method. Text sends still have
none;docs/limitations.mdsays so. - Cached inline results and the results button.
InlineQueryResultCachedPhoto,InlineQueryResultCachedVideoand
InlineQueryResultCachedMpeg4Gifsend a file Telegram already has by its
file_id, so an inline answer of past results fetches nothing.
InlineAnswer.Buttonputs a button above the results that opens a Mini App
or sends/startwith a parameter. A button Telegram would refuse, and a
cached video without the title Telegram requires, are refused before
anything is sent. - Edits of an inline message.
EditInlineMessageText,
EditInlineMessageMedia,EditInlineMessageCaptionand
EditInlineMessageReplyMarkupaddress a message sent through the bot by
itsinline_message_id. "Not modified" is success for all four. Telegram
takes no upload in an inline edit, soEditInlineMessageMediarefuses one
before sending. - Drafts with Stop.
SendMessageDraft,SendRichDraftHTMLand
SendRichDraftMarkdownstream a draft into a private chat, with
DraftOptionsfor the topic,CanStopandKeepOnStop(Bot API 10.3). A
press of Stop arrives asUpdate.StoppedMessageGeneration
(MessageGenerationStopped: the chat, the topic and the draft id). A draft
id of zero is refused before sending.
Breaking
CallbackQuery.Messageis a pointer, andCallbackQuery.InlineMessageID
is new. A press on a button of an inline message has no message, and the
zero value read as chat 0; nowMessageis nil there and
InlineMessageIDnames the message. This breaks every bot that reads a
callback's message. The change in each one:cq.Message.Xstays as it is
where the button is on the bot's own message, and a handler that can see
an inline message checkscq.Message == nilfirst; a value passed on as
cq.Messagebecomes*cq.Message.
v0.2.0-alpha.2
A photo, an animation or a document can be sent to one member of a group
and changed in place while only they see it.
Added
- Ephemeral media.
CaptionOptionsgainedEphemeral, anEphemeral
with the receiver, the button press that authorizes the send and
ReplaceCallbackQueryMessage, andReplyToEphemeral, the incoming
ephemeral message being answered.SendPhoto,SendAnimation,
SendVideo,SendAudioandSendDocumenttake them, uploads included. A
bot that is not an admin of the chat needs the press or the ephemeral
message, within 15 seconds; an admin needs neither. EditEphemeralMessageMedia,EditEphemeralMessageCaptionand
EditEphemeralMessageReplyMarkup. The media edit uploads a new file as
EditMessageMediadoes. Telegram answers these with true, not a message,
so the file_id of an upload is not reported. "Message is not modified" is
success, as for the other edits.- The three edits are in
probeSafe: on an empty body Telegram's server
asks for the media or the receiver before it does anything, so Preflight
can require them.
v0.2.0-alpha.1
Added
- Albums, live photos and replies from other chats are decoded.
Message
gainedMediaGroupID,LivePhoto,ExternalReplyandHasMediaSpoiler, with
the types behind them:LivePhoto,ExternalReplyInfo,MessageOriginand
theOrigin*constants. Telegram sends every item of an album as its own
update, andMediaGroupIDis what ties them together. A live photo fills
Photoas well asLivePhoto, so a bot that sorts attachments checks
LivePhotofirst, as it does withAnimationbeforeDocument. A reply to a
message in another chat or another topic arrives inExternalReply, not in
ReplyToMessage.ExternalReplyInfomodels the attachments and the origin;
the rest of the object is inMessage.Raw. The Bot API version stays at 10.3:
live photos are from 10.0. - Three more button kinds.
InlineKeyboardButtongainedWebApp
(WebAppInfo),LoginURL(LoginURL) andCopyText(CopyTextButton). A
bot that kept its own keyboard type only forweb_app, and edited the buttons
in after a photo upload, can send the photo with its buttons in one call.
Telegram acceptsweb_apponly in a private chat; the field comment says so.
The struct comment now lists the actions a button can carry. This package
still does not check that exactly one is set. SendAnimationandInputMediaAnimation.SendAnimationtakes the same
files asSendVideo: afile_idor a URL, a path on a--localserver, or an
upload, which is multipart on its own.AnimationOptionshas the caption
options, width, height, duration,ShowCaptionAboveMediaandHasSpoiler.
InputMediaAnimationis the same animation forEditMessageMedia, so a photo
can turn into a loop in place. maply uploaded an MP4 as a document with its own
HTTP client to get an animation; this replaces that, with tg's retries and
token redaction. Telegram does not accept an animation in an album, so
SendMediaGrouprefuses one before sending anything.sendAnimationis in
probeSafe: with no file the server answers "There is no animation in the
request" before it does anything, soNeeds.Methodscan name it.EditMessageMediaMessagereturns the editedMessage. Thefile_idof a
file an edit uploaded is only in that answer, andEditMessageMediathrew it
away. The new method takes the same arguments and returns(Message, error).
UnlikeEditMessageMedia, it returns "message is not modified" as the error
(IsMessageNotModified) instead of swallowing it, because there is no message
to return and a zeroMessagewith a nil error would look like an edit that
worked. An edit of an inline message is still not possible: it needs the
inline_message_idwork and returnstrue, not a message.- Caption options on the media types.
InputMediaPhoto,InputMediaVideo
andInputMediaAnimationtakeShowCaptionAboveMediaandHasSpoiler, in an
edit and in an album. The caption was already HTML, so a caption and its
placement go in the same call as the media. EditMessageCaption. Replaces the caption of a message the bot sent and
returns the editedMessage: an HTML caption,EditCaptionOptionswith the
keyboard andShowCaptionAboveMedia. An empty caption removes it, and is sent
as an empty string. LikeEditMessageMediaMessageit returns "message is not
modified" as the error, which a bot editing on a timer ignores with
IsMessageNotModified.editMessageCaptionwas already inprobeSafe, and
the server answers an empty body with "Message identifier is not specified".- Reactions and batch delete.
SetMessageReactionsets the bot's own
reaction and replaces the previous one; an empty list removes it.
DeleteMessagesdeletes 1 to 100 messages in one call and refuses any other
count before sending.DeleteMessageReaction(Bot API 10.0) takes a
ReactionActor, a user or a chat, and removes that actor's reaction. It is a
moderation method for groups and supergroups and needs the right to delete
messages: it cannot take back the bot's own reaction, so the bot does that
withSetMessageReactionand no reactions. Telegram accepts only the emoji in
the Bot API reference list for a bot's reaction, and the check mark is not in
it, so a "done" mark has to be another emoji.setMessageReactionwas already
inprobeSafe;deleteMessagesanddeleteMessageReactionjoin it, so
Needs.Methodscan name all three. The server source answers an empty body
with "Message identifiers are not specified" and "chat_id is empty" before it
calls TDLib. - Menu button and profile texts.
SetChatMenuButtontakes aMenuButton
(MenuCommands,MenuWebAppwithTextandWebApp, orMenuDefault) and a
private chat; chat 0 sets the default for every private chat. A web_app button
without its text or URL and an unknown kind are refused before sending.
SetMyDescriptionandSetMyShortDescriptiontake the text and a language
code, empty for the default. An empty text is sent as an empty string, because
that is how Telegram is told to drop the text of a language. maply and framy
both set these at start; maply did it throughCall. None of the three is in
probeSafe, and a test now says so: with an empty bodysetMyDescriptionand
setMyShortDescriptionclear the text, andsetChatMenuButtonresets the
button. - Downloads reach the
Observer.DownloadToFilereports oneEventwhen
it finishes, successful or not.MethodisdownloadFilefor a transfer over
HTTP andreadLocalFilefor a read from the disk of a--localserver. The
second has no request, so itsStatusis 0 and a success is told byErr
being nil.Durationcovers the whole transfer, and the newEvent.Bytesis
what was written to the destination, 0 for every other event.Retryablesays
whether another try could pass (the network, a 429, a 5xx), but a download is
still one attempt andAttemptis 1. A file over the limit is not retryable,
andBytescounts what was written before it was rejected.Erris the error
DownloadToFilereturns, so it holds no token. A bot that counts every event
byMethodgets two new values, and one that treatsStatus == 0as "no
answer" has to checkErrfirst forreadLocalFile.
v0.1.1-alpha.1
Added
- Edits and reactions are decoded.
UpdategainedEditedMessage,
MessageReactionandMessageReactionCount, with the types behind them:
MessageReactionUpdated,MessageReactionCountUpdated,ReactionCountand
ReactionType. A reaction is an emoji, a custom emoji or a paid one, told apart
byType(ReactionEmoji,ReactionCustomEmoji,ReactionPaid). Until now
naming these kinds inWithAllowedUpdateswas accepted, and the updates came
back as anUpdateholding onlyUpdateID, with no error. Telegram delivers
the two reaction kinds only when the bot is an administrator of the chat. - More of
Messageis modeled.Animation,Sticker,MigrateToChatID,
MigrateFromChatIDandIsTopicMessage, plusChatMember.IsMember, which
Telegram sends only for a restricted member. For a GIF Telegram fills
Documentas well asAnimation, so a bot that sorts attachments checks
Animationfirst. A reply chain in an ordinary supergroup carries a
MessageThreadIDjust as a forum topic does;IsTopicMessageis what tells
them apart before a bot sends into the thread. EditMessageRichMarkdown, the Markdown counterpart ofEditMessageRichHTML,
taking the same options, soWithEntityDetectionworks with it. Without it a
bot that sent a rich message as Markdown had to go throughCallto edit it.
The Bot API version stays at 10.3: rich messages are older than that.
Fixed
Probeno longer reads a 409, a 429 or a 5xx as "the method exists". It
took any API answer other than "method not found" for presence, so a gateway
error or a rate limit during startup letPreflightpass against a server that
might not have the method. Those answers now return an error,cannot tell whether <method> exists, which wraps the*APIError, as a 401, a 403 and a
proxy's 404 already did. A bot that callsPreflightwithMethodscan now
fail to start on a server that is briefly unwell, where before it started. A
bot that would rather run degraded callsProbeitself and keeps its fallback
when it returns an error.DownloadToFileredacts the token from an HTTP error. The error body of a
failed download came back as it was. A reverse proxy in front of a
self-hosted server can echo the request URI in it, and the URI holds the
token.Callalready scrubbed its errors; the download path now does the
same.
Documentation
CallbackQuery.Messagesays what Telegram sends for a message the bot can no
longer reach:dateis 0 and onlyChatandMessageIDmean anything, so
Message.Date == 0is the test.WithAllowedUpdatesnamedmy_chat_memberas a kind Telegram's default
leaves out. It does not: the default leaves outchat_member,
message_reactionandmessage_reaction_count. An empty option also does not
restore that default; Telegram keeps the list last set for the token.
Changed
- A release waits for CI.
release.ymlrunsci.ymlon the tagged commit
and publishes nothing unless it passed. When CI cannot run,release.ymlcan
be started by hand with the tag and a reason; that run skips CI and prints the
reason at the top of the release notes. Seedocs/releases.md. - The Bot API drift check fails only the weekly run; a new Bot API version is
not a defect in the code being pushed or released. - Dependabot opens grouped version updates against
dev.
v0.1.0
The first stable release of the 0.1.0 line, summarising it rather than only the
delta since alpha.1.
tg gained the surface searchy needed to leave its third-party client: media
sends, inline mode, link-preview control and the chat and action constants that
went with them. Nothing that already existed moved, so the three bots already on
this module are unaffected by any of it.
Below, the entries added after alpha.1 was tagged. The media and inline work is
described in the alpha.1 section.
Added
docs/releases.mdgained a Deploying section, andAGENTS.mdpoints at it.
Releasing was documented; deploying was not, in any repository in the family —
the process stopped at "deploy it" and never said how. That gap mattered more
after the stacks moved from building on the host to pulling a published image,
because the procedure changed on the same day. The section names this stack's
host directory, its env file, the variable that selects the image, the networks
it needs, and what a rollback actually is.
v0.1.0-alpha.1
The surface searchy needs to leave its third-party client. Nothing that
existed moved, so every bot already on tg is unaffected; the version says
0.1.0 rather than 0.0.2 because the additions are notable, and carries
alpha because no bot has yet run on them against live Telegram.
Added
-
Media sends.
SendPhoto,SendVideo,SendAudio,SendDocumentand
SendMediaGroup, plusEditMessageMediaandEditMessageReplyMarkup. A file
is anInputFile, and which of the three shapes it takes decides the request
on its own:InputFileStringfor afile_idor an https URL Telegram fetches
itself,InputFileLocalfor a path on the disk of a server started with
--local, andInputFileUploadfor bytes — only the last builds a multipart
body. A local path sent to Telegram's own endpoint is refused before the
request, because the failure it would otherwise produce ("wrong file
identifier") points nowhere near the cause. -
Inline mode.
AnswerInlineQuery, theInlineQueryand
ChosenInlineResultupdate kinds, and the two result cards the family uses:
InlineQueryResultPhotoandInlineQueryResultArticlewith
InputTextMessageContent. Answering with an empty result list is now
expressible, andresultsis always sent, because an omitted parameter is a
400 rather than "nothing found". -
LinkPreviewOptionsas a type,SendTextWithPreviewfor the one case where a
bot relays text whose author wanted the preview, andAnswerCallbackQueryURL
for a button that opens at.medeep link with no intermediate message. -
ChatAction*andChat*constants, so a bot branching on chat type or
announcing an upload no longer spells the strings out at every call site. -
InlineKeyboardButton.SwitchInlineQueryCurrentChat. It is a pointer because
the empty string is the useful value — "open inline search with nothing
typed" — andomitemptyon a plain string would drop exactly that button. -
editMessageMediaandsendMediaGroupare now probe-safe, so a bot that
pages a gallery or posts an album can refuse to start on a server that cannot.This is why the whole set landed: searchy was the one bot still on a
third-party library, and it stayed there because tg could not send a photo or
answer an inline query. Everything above is additive — no existing signature
moved.
Fixed
EditEphemeralMessageTextsuppresses Telegram's own link preview, which it
alone among the text sends did not. The About panel every bot is growing
carries a repository link, so editing one in place rendered a preview card
under it — in a group, where an ephemeral panel exists precisely to be the
quiet option.SendEphemeralMessagealready did this; the rich sends do not,
ephemeral or not, which is consistent between them and left alone here.
Changed
- One versioning and release document for the whole family.
docs/versioning.md
anddocs/releases.mdare now byte-identical across every Asterfield
repository apart from two clearly marked sections: this repository's own
version line, and the surface where a change here breaks something. They spell
out what each of the three numbers means, what the-alpha.Nsuffix counts,
when alpha becomes beta and when it is legitimate to skip to rc or run a
pre-release in production. - Pre-releases are now tagged on
dev, notmain. Only stable versions are
tagged onmain, on the merge commit fromdev, somainanswers exactly one
question: what is in production. The test bot runsdev, the production bot
runsmain.release.ymlenforces this and refuses a tag on the wrong branch.
Earlier pre-releases in this repository were tagged onmainunder the
previous rule; they are left as they are. - The document is explicit that tg's major number is not a judgement call: Go
gives it a hard meaning, since av2module has a different import path. A
change that removes or re-signs an exported function is major whatever it
feels like.
Added
release.yml. Every tag fromv0.0.1-alpha.1onward existed and was
fetchable through the module proxy, but none of them had a GitHub Release, so
the only way to learn what a bump changed was to open the changelog in another
tab. The workflow verifies the tag is onmain, re-runs gofmt, vet, the race
suite and govulncheck, and publishes the release from the matching changelog
section. The module proxy caches a tag permanently on first fetch, which is
why the branch check runs before anything else.
v0.0.1-alpha.7
Fixed
Event.Probemarks the requests a capability check makes. A probe calls a
method with an empty body on purpose and is answered with a parameter error,
so an observer counting failures recorded one phantom incident per probed
method on every start -- visible in voicy's metrics as two Telegram errors
after a restart that had served no traffic yet.
v0.0.1-alpha.6
What three reviews of the first day's code found. Two of these were leaks.
Security
- The bot token no longer escapes through an API error. Telegram never echoes
the request, but a proxy or a sidecar answering 404 or 502 in its place
quotes the request URI -- which contains the token -- and that string reached
log lines and, in branchy, a database column. Descriptions and raw bodies are
now redacted like URLs are. - The token no longer escapes through a wrapped
fs.PathErroreither. A local
server names each bot's directory after its token, so every local-file error
carried it inPathError.Patheven though the message itself was clean. The
error keeps its identity --errors.Is(err, fs.ErrNotExist)still works --
with the path scrubbed. - Symlinks can no longer escape
<filesRoot>/<token>. Cleaning a path stops a
..and nothing stopped a link, on a directory written by another process
and shared with every other bot. Both sides are resolved before comparison.
Fixed
- A 2xx answer carrying
ok=falseis an*APIErroragain, with its error
code, description, retry_after and body. Telegram does not answer that way,
but a proxy does -- and callers classify failures by type, so it was arriving
as an untyped error that skipped every recovery path. Classification now
reads Telegram's own code when the HTTP status does not carry it, which also
fixesanswerCallbackQuery,setMyCommandsandeditMessageText, none of
which checkedokat all. ErrUnexpectedResultseparates "the call failed" from "the call was made and
the answer is unreadable". A bot whose notification was delivered should not
queue it again because the receipt did not parse; a bot that needs the new
message's id genuinely cannot continue. Such an answer is also no longer
retried -- it will not parse differently the second time -- and it now names
the method.- A failed download no longer leaves a truncated file behind.
os.Create
truncates before the size is known, and a caller that finds a file where an
error was reported will read it. Preflightwaits through the failures a starting server actually produces.
It gave up on any API answer, which meant a 502 from a proxy in front of a
booting server was fatal; only a refused connection was ever waited out.Needs.Waitbounds the whole check rather than only the pauses inside it.
With the retries insidegetMe, a 30-second budget could run for minutes,
and an operator sizes a restart policy on that number.- A probe survives a dropped connection. An ordinary POST is not replayed
because a lost answer may still have been acted on, but a probe carries an
empty body, so one reset connection was reading as "the server lacks this
method". Proberefuses to guess when the server answers 401, 403, or a 404 that is
not Telegram's. Those say the server never looked at the method, and
reporting it present defeats the point of a guard that exists to refuse.- A POST that is asked to wait longer than its caller can afford now returns
both facts: what Telegram answered and that the wait was cut short. It used
to return only the context error, losing the 429 and its retry_after. - The JSON contract matches the API: fields Telegram always sends marshal at
their zero value -- an accidentally empty button label was being sent as a
button with no label at all -- and anUpdateno longer marshals three
nulls.File.file_unique_id,Video.width/height,
ChatMemberUpdated.date/old_chat_memberandCallbackQuery.chat_instance
are modeled;CallbackQuery.datais optional, as it is in the API.
Changed
Event.RetriedbecameEvent.Retryable. It was computed from the error
alone and claimed another attempt would follow, which was untrue for a 5xx on
a POST and for the last attempt of an exhausted GET.
Documentation
docs/limitations.mdlists what the package cannot express yet -- multipart
uploads, per-chat command scopes, inline-message editing, an extensible probe
list -- so the next consumer meets them on paper rather than in the source.
v0.0.1-alpha.5
Added
SendPlainText, a send with no parse mode. It is the delivery of last
resort: HTML that Telegram rejects fails the whole message, and branchy's
notification outbox would rather deliver an unformatted notification than
none. Every other send in this package sets HTML, which is what made this
worth a method rather than a flag.
v0.0.1-alpha.4
Changed
- Struct tags now mirror Telegram's own required/optional split: a field the
API always sends is always marshaled, an optional one only when set. A
message re-encoded for an audit record used to come out mostly nulls this
package had invented.