From 61562e8a3be204d4e73b9fc759fe8ee0043df7c6 Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Sun, 1 Mar 2026 23:01:06 +0300 Subject: [PATCH] 1.0.0 beta 3 --- bot.go | 14 +++++-- drafts.go | 82 +++++++++++++++++++++++++++++++++++++++ handler.go | 8 +++- msg_context.go | 46 +++++++++++----------- tgapi/chat_methods.go | 12 ++++++ tgapi/chat_types.go | 5 +++ tgapi/messages_methods.go | 2 +- tgapi/messages_types.go | 13 +++++-- utils/version.go | 4 +- 9 files changed, 151 insertions(+), 35 deletions(-) create mode 100644 drafts.go diff --git a/bot.go b/bot.go index 8ad242e..900145d 100644 --- a/bot.go +++ b/bot.go @@ -84,10 +84,11 @@ type Bot[T DbContext] struct { prefixes []string runners []Runner[T] - api *tgapi.API - uploader *tgapi.Uploader - dbContext *T - l10n *L10n + api *tgapi.API + uploader *tgapi.Uploader + dbContext *T + l10n *L10n + draftProvider *DraftProvider updateOffsetMu sync.Mutex updateOffset int @@ -122,6 +123,7 @@ func NewBot[T any](opts *BotOpts) *Bot[T] { runners: make([]Runner[T], 0), extraLoggers: make([]*slog.Logger, 0), l10n: &L10n{}, + draftProvider: NewRandomDraftProvider(api), } bot.extraLoggers = bot.extraLoggers.Push(api.GetLogger()).Push(uploader.GetLogger()) @@ -202,6 +204,10 @@ func (bot *Bot[T]) GetUpdateTypes() []tgapi.UpdateType { return bot.updateTypes func (bot *Bot[T]) GetLogger() *slog.Logger { return bot.logger } func (bot *Bot[T]) GetDBContext() *T { return bot.dbContext } func (bot *Bot[T]) L10n(lang, key string) string { return bot.l10n.Translate(lang, key) } +func (bot *Bot[T]) SetDraftProvider(p *DraftProvider) *Bot[T] { + bot.draftProvider = p + return bot +} type DbLogger[T DbContext] func(db *T) slog.LoggerWriter diff --git a/drafts.go b/drafts.go new file mode 100644 index 0000000..f170f64 --- /dev/null +++ b/drafts.go @@ -0,0 +1,82 @@ +package laniakea + +import ( + "math" + "math/rand/v2" + "sync/atomic" + + "git.nix13.pw/scuroneko/laniakea/tgapi" +) + +type draftIdGenerator interface { + Next() uint64 +} + +type RandomDraftIdGenerator struct { + draftIdGenerator +} + +func (g *RandomDraftIdGenerator) Next() uint64 { + return rand.Uint64N(math.MaxUint64) +} + +type LinearDraftIdGenerator struct { + draftIdGenerator + lastId uint64 +} + +func (g *LinearDraftIdGenerator) Next() uint64 { + return atomic.AddUint64(&g.lastId, 1) +} + +type DraftProvider struct { + api *tgapi.API + + chatID int + messageThreadID int + parseMode tgapi.ParseMode + entities []tgapi.MessageEntity + + drafts map[uint64]*Draft + generator draftIdGenerator +} +type Draft struct { + api *tgapi.API + + chatID int + messageThreadID int + parseMode tgapi.ParseMode + entities []tgapi.MessageEntity + + ID uint64 + Message string +} + +func NewRandomDraftProvider(api *tgapi.API) *DraftProvider { + return &DraftProvider{api: api, generator: &RandomDraftIdGenerator{}} +} +func NewLinearDraftProvider(api *tgapi.API, startValue uint64) *DraftProvider { + return &DraftProvider{api: api, generator: &LinearDraftIdGenerator{lastId: startValue}} +} +func (d *DraftProvider) NewDraft() *Draft { + id := d.generator.Next() + draft := &Draft{d.api, d.chatID, d.messageThreadID, d.parseMode, d.entities, id, ""} + d.drafts[id] = draft + return draft +} + +func (d *Draft) Push(draftId uint64, newText string) error { + d.Message += newText + params := tgapi.SendMessageDraftP{ + ChatID: d.chatID, + DraftID: draftId, + Text: d.Message, + ParseMode: d.parseMode, + Entities: d.entities, + } + if d.messageThreadID > 0 { + params.MessageThreadID = d.messageThreadID + } + _, err := d.api.SendMessageDraft(params) + return err +} diff --git a/handler.go b/handler.go index 2d975c4..27ba991 100644 --- a/handler.go +++ b/handler.go @@ -9,7 +9,13 @@ import ( ) func (bot *Bot[T]) handle(u *tgapi.Update) { - ctx := &MsgContext{Update: *u, Api: bot.api, botLogger: bot.logger, errorTemplate: bot.errorTemplate, l10n: bot.l10n} + ctx := &MsgContext{ + Update: *u, Api: bot.api, + botLogger: bot.logger, + errorTemplate: bot.errorTemplate, + l10n: bot.l10n, + draftProvider: bot.draftProvider, + } for _, middleware := range bot.middlewares { middleware.Execute(ctx, bot.dbContext) } diff --git a/msg_context.go b/msg_context.go index b0cfaf4..400e3b7 100644 --- a/msg_context.go +++ b/msg_context.go @@ -24,6 +24,7 @@ type MsgContext struct { errorTemplate string botLogger *slog.Logger l10n *L10n + draftProvider *DraftProvider } type AnswerMessage struct { @@ -77,6 +78,7 @@ func (ctx *MsgContext) editPhotoText(messageId int, text string, kb *InlineKeybo if kb != nil { params.ReplyMarkup = kb.Get() } + msg, _, err := ctx.Api.EditMessageCaption(params) if err != nil { ctx.botLogger.Errorln(err) @@ -105,6 +107,9 @@ func (ctx *MsgContext) answer(text string, keyboard *InlineKeyboard) *AnswerMess if keyboard != nil { params.ReplyMarkup = keyboard.Get() } + if ctx.Msg.MessageThreadID > 0 { + params.MessageThreadID = ctx.Msg.MessageThreadID + } msg, err := ctx.Api.SendMessage(params) if err != nil { @@ -135,6 +140,10 @@ func (ctx *MsgContext) answerPhoto(photoId, text string, kb *InlineKeyboard) *An if kb != nil { params.ReplyMarkup = kb.Get() } + if ctx.Msg.MessageThreadID > 0 { + params.MessageThreadID = ctx.Msg.MessageThreadID + } + msg, err := ctx.Api.SendPhoto(params) if err != nil { ctx.botLogger.Errorln(err) @@ -162,12 +171,8 @@ func (ctx *MsgContext) delete(messageId int) { ctx.botLogger.Errorln(err) } } -func (m *AnswerMessage) Delete() { - m.ctx.delete(m.MessageID) -} -func (ctx *MsgContext) CallbackDelete() { - ctx.delete(ctx.CallbackMsgId) -} +func (m *AnswerMessage) Delete() { m.ctx.delete(m.MessageID) } +func (ctx *MsgContext) CallbackDelete() { ctx.delete(ctx.CallbackMsgId) } func (ctx *MsgContext) answerCallbackQuery(url, text string, showAlert bool) { if len(ctx.CallbackQueryId) == 0 { @@ -181,23 +186,19 @@ func (ctx *MsgContext) answerCallbackQuery(url, text string, showAlert bool) { ctx.botLogger.Errorln(err) } } -func (ctx *MsgContext) AnswerCbQuery() { - ctx.answerCallbackQuery("", "", false) -} -func (ctx *MsgContext) AnswerCbQueryText(text string) { - ctx.answerCallbackQuery("", text, false) -} -func (ctx *MsgContext) AnswerCbQueryAlert(text string) { - ctx.answerCallbackQuery("", text, true) -} -func (ctx *MsgContext) AnswerCbQueryUrl(u string) { - ctx.answerCallbackQuery(u, "", false) -} +func (ctx *MsgContext) AnswerCbQuery() { ctx.answerCallbackQuery("", "", false) } +func (ctx *MsgContext) AnswerCbQueryText(text string) { ctx.answerCallbackQuery("", text, false) } +func (ctx *MsgContext) AnswerCbQueryAlert(text string) { ctx.answerCallbackQuery("", text, true) } +func (ctx *MsgContext) AnswerCbQueryUrl(u string) { ctx.answerCallbackQuery(u, "", false) } func (ctx *MsgContext) SendAction(action tgapi.ChatActionType) { - _, err := ctx.Api.SendChatAction(tgapi.SendChatActionP{ + params := tgapi.SendChatActionP{ ChatID: ctx.Msg.Chat.ID, Action: action, - }) + } + if ctx.Msg.MessageThreadID > 0 { + params.MessageThreadID = ctx.Msg.MessageThreadID + } + _, err := ctx.Api.SendChatAction(params) if err != nil { ctx.botLogger.Errorln(err) } @@ -213,10 +214,9 @@ func (ctx *MsgContext) error(err error) { } ctx.botLogger.Errorln(err) } -func (ctx *MsgContext) Error(err error) { - ctx.error(err) -} +func (ctx *MsgContext) Error(err error) { ctx.error(err) } +func (ctx *MsgContext) NewDraft() *Draft { return ctx.draftProvider.NewDraft() } func (ctx *MsgContext) Translate(key string) string { if ctx.From == nil { return key diff --git a/tgapi/chat_methods.go b/tgapi/chat_methods.go index fc48a72..8294258 100644 --- a/tgapi/chat_methods.go +++ b/tgapi/chat_methods.go @@ -56,6 +56,7 @@ type PromoteChatMember struct { CanPinMessages bool `json:"can_pin_messages,omitempty"` CanManageTopics bool `json:"can_manage_topics,omitempty"` CanManageDirectMessages bool `json:"can_manage_direct_messages,omitempty"` + CanManageTags bool `json:"can_manage_tags,omitempty"` } func (api *API) PromoteChatMember(params PromoteChatMember) (bool, error) { @@ -74,6 +75,17 @@ func (api *API) SetChatAdministratorCustomTitle(params SetChatAdministratorCusto return req.Do(api) } +type SetChatMemberTagP struct { + ChatID int `json:"chat_id"` + UserID int `json:"user_id"` + Tag string `json:"tag,omitempty"` +} + +func (api *API) SetChatMemberTag(params SetChatMemberTagP) (bool, error) { + req := NewRequest[bool]("setChatMemberTag", params) + return req.Do(api) +} + type BanChatSenderChatP struct { ChatID int `json:"chat_id"` SenderChatID int `json:"sender_chat_id"` diff --git a/tgapi/chat_types.go b/tgapi/chat_types.go index 7b1ea6e..96f5ac7 100644 --- a/tgapi/chat_types.go +++ b/tgapi/chat_types.go @@ -99,6 +99,7 @@ type ChatPermissions struct { CanSendPolls bool `json:"can_send_polls"` CanSendOtherMessages bool `json:"can_send_other_messages"` CanAddWebPagePreview bool `json:"can_add_web_page_preview"` + CatEditTag bool `json:"cat_edit_tag"` CanChangeInfo bool `json:"can_change_info"` CanInviteUsers bool `json:"can_invite_users"` CanPinMessages bool `json:"can_pin_messages"` @@ -137,6 +138,7 @@ const ( type ChatMember struct { Status ChatMemberStatusType `json:"status"` User User `json:"user"` + Tag string `json:"tag,omitempty"` // Owner IsAnonymous *bool `json:"is_anonymous"` @@ -160,6 +162,7 @@ type ChatMember struct { CanPinMessages *bool `json:"can_pin_messages,omitempty"` CanManageTopics *bool `json:"can_manage_topics,omitempty"` CanManageDirectMessages *bool `json:"can_manage_direct_messages,omitempty"` + CanManageTags *bool `json:"can_manage_tags,omitempty"` // Member UntilDate *int `json:"until_date,omitempty"` @@ -175,6 +178,7 @@ type ChatMember struct { CanSendPolls *bool `json:"can_send_polls,omitempty"` CanSendOtherMessages *bool `json:"can_send_other_messages,omitempty"` CanAddWebPagePreview *bool `json:"can_add_web_page_preview,omitempty"` + CanEditTag *bool `json:"can_edit_tag,omitempty"` } type ChatBoostSource struct { @@ -215,6 +219,7 @@ type ChatAdministratorRights struct { CanPinMessages *bool `json:"can_pin_messages,omitempty"` CanManageTopics *bool `json:"can_manage_topics,omitempty"` CanManageDirectMessages *bool `json:"can_manage_direct_messages,omitempty"` + CanManageTags *bool `json:"can_manage_tags,omitempty"` } type ChatBoostUpdated struct { diff --git a/tgapi/messages_methods.go b/tgapi/messages_methods.go index c17495e..d7264db 100644 --- a/tgapi/messages_methods.go +++ b/tgapi/messages_methods.go @@ -268,7 +268,7 @@ func (api *API) SendDice(params SendDiceP) (Message, error) { type SendMessageDraftP struct { ChatID int `json:"chat_id"` MessageThreadID int `json:"message_thread_id,omitempty"` - DraftID int `json:"draft_id"` + DraftID uint64 `json:"draft_id"` Text string `json:"text"` ParseMode ParseMode `json:"parse_mode,omitempty"` Entities []MessageEntity `json:"entities,omitempty"` diff --git a/tgapi/messages_types.go b/tgapi/messages_types.go index 3662b20..24605cc 100644 --- a/tgapi/messages_types.go +++ b/tgapi/messages_types.go @@ -12,10 +12,11 @@ type Message struct { BusinessConnectionId string `json:"business_connection_id,omitempty"` From *User `json:"from,omitempty"` - SenderChat *Chat `json:"sender_chat,omitempty"` - SenderBoostCount int `json:"sender_boost_count,omitempty"` - SenderBusinessBot *User `json:"sender_business_bot,omitempty"` - Chat *Chat `json:"chat,omitempty"` + SenderChat *Chat `json:"sender_chat,omitempty"` + SenderBoostCount int `json:"sender_boost_count,omitempty"` + SenderBusinessBot *User `json:"sender_business_bot,omitempty"` + SenderTag string `json:"sender_tag,omitempty"` + Chat *Chat `json:"chat,omitempty"` IsTopicMessage bool `json:"is_topic_message,omitempty"` IsAutomaticForward bool `json:"is_automatic_forward,omitempty"` @@ -74,6 +75,7 @@ const ( MessageEntityTextLink MessageEntityType = "text_link" MessageEntityTextMention MessageEntityType = "text_mention" MessageEntityCustomEmoji MessageEntityType = "custom_emoji" + MessageEntityDateTime MessageEntityType = "date_time" ) type MessageEntity struct { @@ -85,6 +87,9 @@ type MessageEntity struct { User *User `json:"user,omitempty"` Language string `json:"language,omitempty"` CustomEmojiID string `json:"custom_emoji_id,omitempty"` + + UnixTime int `json:"unix_time,omitempty"` + DateTimeFormat string `json:"date_time_format,omitempty"` } type ReplyParameters struct { diff --git a/utils/version.go b/utils/version.go index 2ca41f7..5cf975f 100644 --- a/utils/version.go +++ b/utils/version.go @@ -1,9 +1,9 @@ package utils const ( - VersionString = "1.0.0-beta.2" + VersionString = "1.0.0-beta.3" VersionMajor = 1 VersionMinor = 0 VersionPatch = 0 - Beta = 2 + Beta = 3 )