(new): expand runtime APIs
(fix): harden concurrent lifecycle (tests): add regression coverage (doc): update v1.2 guidance
This commit is contained in:
+141
-34
@@ -84,45 +84,59 @@ type MessageContext struct {
|
||||
sceneRuntime sceneRuntime
|
||||
observer Observer
|
||||
eventEmitter func(context.Context, Event)
|
||||
asyncTask func(func())
|
||||
botID int64
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (ctx *MessageContext) buildEditMessageTextParams(messageID int, keyboard *InlineKeyboard, parseMode tgapi.ParseMode) (tgapi.EditMessageText, error) {
|
||||
params := tgapi.EditMessageText{}
|
||||
switch {
|
||||
case messageID > 0 && ctx.Msg != nil && ctx.Msg.Chat != nil:
|
||||
params.MessageID = messageID
|
||||
params.ChatID = ctx.Msg.Chat.ID
|
||||
params.BusinessConnectionID = ctx.Msg.BusinessConnectionID
|
||||
case ctx.InlineMsgID != "":
|
||||
params.InlineMessageID = ctx.InlineMsgID
|
||||
default:
|
||||
return params, ErrEditTargetMissing
|
||||
}
|
||||
if parseMode != "" {
|
||||
params.ParseMode = parseMode
|
||||
}
|
||||
if keyboard != nil {
|
||||
params.ReplyMarkup = keyboard.Get()
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// AnswerMessage represents a message sent or edited via MessageContext.
|
||||
// It holds metadata to allow further editing or deletion.
|
||||
type AnswerMessage struct {
|
||||
// MessageID identifies the sent Telegram message.
|
||||
MessageID int
|
||||
// Text contains the text or caption sent with the message.
|
||||
// For rich messages, it contains rendered HTML for v1 compatibility.
|
||||
Text string
|
||||
// RichHTML contains the rendered HTML of a rich message.
|
||||
RichHTML string // Since: Bot API 10.2
|
||||
// IsMedia reports whether the answer contains media.
|
||||
IsMedia bool
|
||||
ctx *MessageContext // internal back-reference
|
||||
}
|
||||
|
||||
func (ctx *MessageContext) edit(messageID int, text string, keyboard *InlineKeyboard, parseMode tgapi.ParseMode) *AnswerMessage {
|
||||
params, err := ctx.buildEditMessageTextParams(messageID, keyboard, parseMode)
|
||||
if err != nil {
|
||||
ctx.Logger.Errorln(err)
|
||||
return nil
|
||||
}
|
||||
if err := validateMessageText(text); err != nil {
|
||||
ctx.Logger.Errorln(err)
|
||||
return nil
|
||||
}
|
||||
params := tgapi.EditMessageText{
|
||||
Text: text,
|
||||
ParseMode: parseMode,
|
||||
}
|
||||
switch {
|
||||
case messageID > 0 && ctx.Msg != nil:
|
||||
params.MessageID = messageID
|
||||
params.ChatID = ctx.Msg.Chat.ID
|
||||
case ctx.InlineMsgID != "":
|
||||
params.InlineMessageID = ctx.InlineMsgID
|
||||
default:
|
||||
ctx.Logger.Errorln(ErrEditTargetMissing)
|
||||
return nil
|
||||
}
|
||||
if keyboard != nil {
|
||||
params.ReplyMarkup = keyboard.Get()
|
||||
}
|
||||
params.Text = text
|
||||
msg, _, err := ctx.API.EditMessageTextWithContext(ctx.Context(), params)
|
||||
if err != nil {
|
||||
ctx.Logger.Errorln(err)
|
||||
@@ -193,9 +207,10 @@ func (ctx *MessageContext) editPhotoText(messageID int, text string, kb *InlineK
|
||||
ParseMode: parseMode,
|
||||
}
|
||||
switch {
|
||||
case messageID > 0 && ctx.Msg != nil:
|
||||
case messageID > 0 && ctx.Msg != nil && ctx.Msg.Chat != nil:
|
||||
params.ChatID = ctx.Msg.Chat.ID
|
||||
params.MessageID = messageID
|
||||
params.BusinessConnectionID = ctx.Msg.BusinessConnectionID
|
||||
case ctx.InlineMsgID != "":
|
||||
params.InlineMessageID = ctx.InlineMsgID
|
||||
default:
|
||||
@@ -245,7 +260,7 @@ func (m *AnswerMessage) EditCaptionKeyboardMarkdown(text string, kb *InlineKeybo
|
||||
}
|
||||
|
||||
func (ctx *MessageContext) answer(text string, keyboard *InlineKeyboard, parseMode tgapi.ParseMode) *AnswerMessage {
|
||||
if ctx.Msg == nil {
|
||||
if ctx.Msg == nil || ctx.Msg.Chat == nil {
|
||||
ctx.Logger.Errorln(ErrMessageContextNil)
|
||||
return nil
|
||||
}
|
||||
@@ -254,9 +269,10 @@ func (ctx *MessageContext) answer(text string, keyboard *InlineKeyboard, parseMo
|
||||
return nil
|
||||
}
|
||||
params := tgapi.SendMessage{
|
||||
ChatID: ctx.Msg.Chat.ID,
|
||||
Text: text,
|
||||
ParseMode: parseMode,
|
||||
BusinessConnectionID: ctx.Msg.BusinessConnectionID,
|
||||
ChatID: ctx.Msg.Chat.ID,
|
||||
Text: text,
|
||||
ParseMode: parseMode,
|
||||
}
|
||||
if keyboard != nil {
|
||||
params.ReplyMarkup = keyboard.Get()
|
||||
@@ -339,7 +355,7 @@ func (ctx *MessageContext) answerLong(text string, keyboard *InlineKeyboard, par
|
||||
ctx.Logger.Errorln(ErrMessageSplitImpossible)
|
||||
return nil
|
||||
}
|
||||
if ctx.Msg == nil {
|
||||
if ctx.Msg == nil || ctx.Msg.Chat == nil {
|
||||
ctx.Logger.Errorln(ErrMessageContextNil)
|
||||
return nil
|
||||
}
|
||||
@@ -374,7 +390,7 @@ func (ctx *MessageContext) answerLong(text string, keyboard *InlineKeyboard, par
|
||||
}
|
||||
|
||||
func (ctx *MessageContext) answerPhoto(photoID, text string, kb *InlineKeyboard, parseMode tgapi.ParseMode) *AnswerMessage {
|
||||
if ctx.Msg == nil {
|
||||
if ctx.Msg == nil || ctx.Msg.Chat == nil {
|
||||
ctx.Logger.Errorln(ErrMessageContextNil)
|
||||
return nil
|
||||
}
|
||||
@@ -383,10 +399,11 @@ func (ctx *MessageContext) answerPhoto(photoID, text string, kb *InlineKeyboard,
|
||||
return nil
|
||||
}
|
||||
params := tgapi.SendPhoto{
|
||||
ChatID: ctx.Msg.Chat.ID,
|
||||
Caption: text,
|
||||
ParseMode: parseMode,
|
||||
Photo: photoID,
|
||||
BusinessConnectionID: ctx.Msg.BusinessConnectionID,
|
||||
ChatID: ctx.Msg.Chat.ID,
|
||||
Caption: text,
|
||||
ParseMode: parseMode,
|
||||
Photo: photoID,
|
||||
}
|
||||
if kb != nil {
|
||||
params.ReplyMarkup = kb.Get()
|
||||
@@ -449,7 +466,7 @@ func (ctx *MessageContext) delete(messageID int) {
|
||||
ctx.Logger.Errorln(ErrMessageIDZero)
|
||||
return
|
||||
}
|
||||
if ctx.Msg == nil {
|
||||
if ctx.Msg == nil || ctx.Msg.Chat == nil {
|
||||
ctx.Logger.Errorln(ErrMessageContextNil)
|
||||
return
|
||||
}
|
||||
@@ -501,12 +518,14 @@ func (ctx *MessageContext) AnswerCallbackURL(u string) { ctx.answerCallbackQuery
|
||||
|
||||
// SendAction sends a chat action (typing, uploading_photo, etc.) to indicate bot activity.
|
||||
func (ctx *MessageContext) SendAction(action tgapi.ChatActionType) {
|
||||
if ctx.Msg == nil {
|
||||
if ctx.Msg == nil || ctx.Msg.Chat == nil {
|
||||
ctx.Logger.Errorln(ErrMessageContextNil)
|
||||
return
|
||||
}
|
||||
params := tgapi.SendChatAction{
|
||||
ChatID: ctx.Msg.Chat.ID, Action: action,
|
||||
BusinessConnectionID: ctx.Msg.BusinessConnectionID,
|
||||
ChatID: ctx.Msg.Chat.ID,
|
||||
Action: action,
|
||||
}
|
||||
if ctx.Msg.MessageThreadID > 0 {
|
||||
params.MessageThreadID = ctx.Msg.MessageThreadID
|
||||
@@ -543,7 +562,7 @@ func (ctx *MessageContext) error(err error) {
|
||||
func (ctx *MessageContext) Error(err error) { ctx.error(err) }
|
||||
|
||||
func (ctx *MessageContext) newDraft(parseMode tgapi.ParseMode) *Draft {
|
||||
if ctx.Msg == nil {
|
||||
if ctx.Msg == nil || ctx.Msg.Chat == nil {
|
||||
ctx.Logger.Errorln(ErrMessageContextNil)
|
||||
return nil
|
||||
}
|
||||
@@ -827,7 +846,7 @@ func (ctx *MessageContext) UpsertKeyboardMarkdown(text string, keyboard *InlineK
|
||||
}
|
||||
|
||||
func (ctx *MessageContext) richAnswer(rich tgapi.InputRichMessage, keyboard *InlineKeyboard) *AnswerMessage {
|
||||
if ctx.Msg == nil {
|
||||
if ctx.Msg == nil || ctx.Msg.Chat == nil {
|
||||
ctx.Logger.Errorln(ErrMessageContextNil)
|
||||
return nil
|
||||
}
|
||||
@@ -844,6 +863,9 @@ func (ctx *MessageContext) richAnswer(rich tgapi.InputRichMessage, keyboard *Inl
|
||||
if ctx.Msg.DirectMessageTopic != nil {
|
||||
params.DirectMessagesTopicID = ctx.Msg.DirectMessageTopic.TopicID
|
||||
}
|
||||
if ctx.Msg.BusinessConnectionID != "" {
|
||||
params.BusinessConnectionID = ctx.Msg.BusinessConnectionID
|
||||
}
|
||||
|
||||
msg, err := ctx.API.SendRichMessageWithContext(ctx.Context(), params)
|
||||
if err != nil {
|
||||
@@ -851,7 +873,11 @@ func (ctx *MessageContext) richAnswer(rich tgapi.InputRichMessage, keyboard *Inl
|
||||
return nil
|
||||
}
|
||||
return &AnswerMessage{
|
||||
MessageID: msg.MessageID, ctx: ctx, Text: rich.HTML, IsMedia: false,
|
||||
MessageID: msg.MessageID,
|
||||
Text: rich.HTML,
|
||||
RichHTML: rich.HTML,
|
||||
IsMedia: false,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,3 +903,84 @@ func (ctx *MessageContext) RichAnswer(blocks ...tgapi.InputRichBlock) *AnswerMes
|
||||
func (ctx *MessageContext) RichAnswerKeyboard(keyboard *InlineKeyboard, blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
return ctx.richBlocksAnswer(keyboard, blocks...)
|
||||
}
|
||||
|
||||
func (ctx *MessageContext) editRich(messageID int, keyboard *InlineKeyboard, blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
params, err := ctx.buildEditMessageTextParams(messageID, keyboard, "")
|
||||
if err != nil {
|
||||
ctx.Logger.Errorln(err)
|
||||
return nil
|
||||
}
|
||||
rich, err := tgrich.BuildHTML(blocks...)
|
||||
if err != nil {
|
||||
ctx.Logger.Errorln(err)
|
||||
return nil
|
||||
}
|
||||
params.RichMessage = &rich
|
||||
msg, _, err := ctx.API.EditMessageTextWithContext(ctx.Context(), params)
|
||||
if err != nil {
|
||||
ctx.Logger.Errorln(err)
|
||||
return nil
|
||||
}
|
||||
resultMessageID := messageID
|
||||
if msg.MessageID > 0 {
|
||||
resultMessageID = msg.MessageID
|
||||
}
|
||||
return &AnswerMessage{
|
||||
MessageID: resultMessageID, Text: rich.HTML, RichHTML: rich.HTML, IsMedia: false, ctx: ctx,
|
||||
}
|
||||
}
|
||||
func (ctx *MessageContext) editRichCallback(keyboard *InlineKeyboard, blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
if ctx.CallbackMsgID == 0 && ctx.InlineMsgID == "" {
|
||||
ctx.Logger.Errorln(ErrCallbackMessageMissing)
|
||||
return nil
|
||||
}
|
||||
return ctx.editRich(ctx.CallbackMsgID, keyboard, blocks...)
|
||||
}
|
||||
func (ctx *MessageContext) upsertRichKeyboard(keyboard *InlineKeyboard, blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
if ctx.IsCallback() {
|
||||
if ctx.HasPhoto() {
|
||||
rich, err := tgrich.BuildHTML(blocks...)
|
||||
if err != nil {
|
||||
ctx.Logger.Errorln(err)
|
||||
return nil
|
||||
}
|
||||
ctx.CallbackDelete()
|
||||
return ctx.richAnswer(rich, keyboard)
|
||||
}
|
||||
return ctx.editRichCallback(keyboard, blocks...)
|
||||
}
|
||||
return ctx.richBlocksAnswer(keyboard, blocks...)
|
||||
}
|
||||
|
||||
// EditCallbackRich builds rich blocks and replaces the callback message content and inline keyboard.
|
||||
// It doesn't upload local files referenced with attach://.
|
||||
//
|
||||
// Since: Bot API 10.2
|
||||
func (ctx *MessageContext) EditCallbackRich(keyboard *InlineKeyboard, blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
return ctx.editRichCallback(keyboard, blocks...)
|
||||
}
|
||||
|
||||
// UpsertKeyboardRich builds rich blocks and either edits the callback message or sends a new message.
|
||||
// Photo callback messages are replaced because their text content can't be edited directly.
|
||||
// It doesn't upload local files referenced with attach://.
|
||||
//
|
||||
// Since: Bot API 10.2
|
||||
func (ctx *MessageContext) UpsertKeyboardRich(keyboard *InlineKeyboard, blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
return ctx.upsertRichKeyboard(keyboard, blocks...)
|
||||
}
|
||||
|
||||
// EditRich builds rich blocks and replaces the message content without changing its inline keyboard.
|
||||
// It doesn't upload local files referenced with attach://.
|
||||
//
|
||||
// Since: Bot API 10.2
|
||||
func (m *AnswerMessage) EditRich(blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
return m.ctx.editRich(m.MessageID, nil, blocks...)
|
||||
}
|
||||
|
||||
// EditRichKeyboard builds rich blocks and replaces the message content and inline keyboard.
|
||||
// It doesn't upload local files referenced with attach://.
|
||||
//
|
||||
// Since: Bot API 10.2
|
||||
func (m *AnswerMessage) EditRichKeyboard(keyboard *InlineKeyboard, blocks ...tgapi.InputRichBlock) *AnswerMessage {
|
||||
return m.ctx.editRich(m.MessageID, keyboard, blocks...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user