From 84c78ed45708a23cc8a49ba2481e139466bd1a1e Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Thu, 10 Jul 2025 21:29:19 +0800 Subject: [PATCH 1/5] fix test message entity offset --- handlers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers_test.go b/handlers_test.go index 4a48df3..e74491d 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -319,7 +319,7 @@ func Test_match_command_start(t *testing.T) { Message: &models.Message{ Text: "/bar", Entities: []models.MessageEntity{ - {Type: models.MessageEntityTypeBotCommand, Offset: 2, Length: 4}, + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 4}, }, }, } From 32b34fb466a4bbea3de8964c04a6c707df2425b8 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Mon, 7 Jul 2025 17:13:20 +0800 Subject: [PATCH 2/5] add command match method maybe with bot username handlers.go: add field `username` in `handler` struct add `MatchTypeCommandStartMaybeWithBotUsernameSuffix` to allow match commands with or without the username suffix WARING: this match method can't match username suffix if bot with the`bot.WithSkipGetMe()` option bot.go: add field `username` in `Bot` struct add method `bot.Username()` `string` WARING: this method will return empty string if bot with the `bot.WithSkipGetMe()` option --- bot.go | 11 ++++++++++- handlers.go | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/bot.go b/bot.go index 5f6fa8a..6e3f1e4 100644 --- a/bot.go +++ b/bot.go @@ -36,6 +36,7 @@ type Bot struct { url string token string + username string pollTimeout time.Duration skipGetMe bool webhookSecretToken string @@ -92,10 +93,11 @@ func New(token string, options ...Option) (*Bot, error) { defer cancel() if !b.skipGetMe { - _, err := b.GetMe(ctx) + botInfo, err := b.GetMe(ctx) if err != nil { return nil, fmt.Errorf("error call getMe, %w", err) } + b.username = botInfo.Username } return b, nil @@ -121,6 +123,13 @@ func (b *Bot) Token() string { return b.token } +// Username returns the bot username without `@` prefix +// +// Return empty string if bot with the `bot.WithSkipGetMe()` option +func (b *Bot) Username() string { + return b.username +} + // StartWebhook starts the Bot with webhook mode func (b *Bot) StartWebhook(ctx context.Context) { wg := sync.WaitGroup{} diff --git a/handlers.go b/handlers.go index 8545781..6a9e3fb 100644 --- a/handlers.go +++ b/handlers.go @@ -24,6 +24,7 @@ const ( MatchTypeContains MatchTypeCommand MatchTypeCommandStartOnly + MatchTypeCommandStartMaybeWithBotUsernameSuffix // example `/command@example_bot`, can't match username suffix if bot with the `bot.WithSkipGetMe()` option matchTypeRegexp matchTypeFunc @@ -36,6 +37,7 @@ type handler struct { handler HandlerFunc pattern string + username string re *regexp.Regexp matchFunc MatchFunc } @@ -100,6 +102,15 @@ func (h handler) match(update *models.Update) bool { } } } + if h.matchType == MatchTypeCommandStartMaybeWithBotUsernameSuffix { + for _, e := range entities { + if e.Type == models.MessageEntityTypeBotCommand { + if e.Offset == 0 && (data[e.Offset+1:e.Offset+e.Length] == h.pattern || data[e.Offset+1:e.Offset+e.Length] == h.pattern + "@" + h.username) { + return true + } + } + } + } if h.matchType == matchTypeRegexp { return h.re.Match([]byte(data)) } @@ -154,6 +165,7 @@ func (b *Bot) RegisterHandler(handlerType HandlerType, pattern string, matchType handlerType: handlerType, matchType: matchType, pattern: pattern, + username: b.username, handler: applyMiddlewares(f, m...), } From a558fa1c9162df878a2139a37566ee46bf1f794d Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Thu, 10 Jul 2025 21:34:56 +0800 Subject: [PATCH 3/5] add test for `MatchTypeCommandStartMaybeWithBotUsernameSuffix` match method --- handlers_test.go | 351 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) diff --git a/handlers_test.go b/handlers_test.go index e74491d..d559fba 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -329,4 +329,355 @@ func Test_match_command_start(t *testing.T) { t.Error("unexpected result") } }) + + // correct command + t.Run("start maybe with username suffix 1, yes", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/foo", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 4}, + }, + }, + } + + res := h.match(&u) + if !res { + t.Error("unexpected result") + } + }) + + // correct command, correct username + t.Run("start maybe with username suffix 2, yes", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/foo@foo_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 12}, + }, + }, + } + + res := h.match(&u) + if !res { + t.Error("unexpected result") + } + }) + + // correct command, wrong username + t.Run("start maybe with username suffix 3, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/foo@other_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 14}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // correct command, with prefix + t.Run("start maybe with username suffix 4, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "a /foo", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 2, Length: 4}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // correct command, with prefix, correct username + t.Run("start maybe with username suffix 5, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "a /foo@foo_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 2, Length: 12}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // correct command, with prefix, wrong username + t.Run("start maybe with username suffix 6, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "a /foo@other_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 2, Length: 14}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + + // wrong command + t.Run("start maybe with username suffix 7, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/bar", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 4}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // wrong command, correct username + t.Run("start maybe with username suffix 8, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/bar@foo_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 12}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // wrong command, wrong username + t.Run("start maybe with username suffix 9, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/bar@other_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 14}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // wrong command, with prefix + t.Run("start maybe with username suffix 10, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "a /bar", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 2, Length: 4}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // wrong command, with prefix, correct username + t.Run("start maybe with username suffix 11, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "a /bar@foo_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 2, Length: 12}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // wrong command, with prefix, wrong username + t.Run("start maybe with username suffix 12, no", func(t *testing.T) { + b := &Bot{ + username: "foo_bot", + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "a /bar@other_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 2, Length: 14}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // correct command, no username, correct username + t.Run("start maybe with username suffix 13, no", func(t *testing.T) { + b := &Bot{ + // username: "foo_bot", // no username + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/foo@foo_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 12}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) + + // correct command, no username, wrong username + t.Run("start maybe with username suffix 14, no", func(t *testing.T) { + b := &Bot{ + // username: "foo_bot", // no username + } + + id := b.RegisterHandler(HandlerTypeMessageText, "foo", MatchTypeCommandStartMaybeWithBotUsernameSuffix, nil) + + h := findHandler(b, id) + u := models.Update{ + ID: 42, + Message: &models.Message{ + Text: "/foo@other_bot", + Entities: []models.MessageEntity{ + {Type: models.MessageEntityTypeBotCommand, Offset: 0, Length: 14}, + }, + }, + } + + res := h.match(&u) + if res { + t.Error("unexpected result") + } + }) } From b56895e36a4472115056591215e10d51a5d60f1b Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Thu, 10 Jul 2025 23:34:31 +0800 Subject: [PATCH 4/5] add test for `bot.Username()` function --- bot_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bot_test.go b/bot_test.go index 3831eca..b3dc7e0 100644 --- a/bot_test.go +++ b/bot_test.go @@ -396,3 +396,13 @@ func TestBot_SetToken(t *testing.T) { t.Errorf("SetToken() = %s, want %s", b.token, "123456:xxx") } } + +func TestBot_Username(t *testing.T) { + b := &Bot{username: "example_bot"} + + username := b.Username() + + if username != "example_bot" { + t.Errorf("Username() = %s, want %s", username, "example_bot") + } +} From 404d70ce99adaa5fd186202a0524b447c5b68996 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Sat, 19 Jul 2025 00:20:06 +0800 Subject: [PATCH 5/5] run `go fmt` --- handlers.go | 2 +- handlers_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/handlers.go b/handlers.go index 6a9e3fb..f41abb5 100644 --- a/handlers.go +++ b/handlers.go @@ -105,7 +105,7 @@ func (h handler) match(update *models.Update) bool { if h.matchType == MatchTypeCommandStartMaybeWithBotUsernameSuffix { for _, e := range entities { if e.Type == models.MessageEntityTypeBotCommand { - if e.Offset == 0 && (data[e.Offset+1:e.Offset+e.Length] == h.pattern || data[e.Offset+1:e.Offset+e.Length] == h.pattern + "@" + h.username) { + if e.Offset == 0 && (data[e.Offset+1:e.Offset+e.Length] == h.pattern || data[e.Offset+1:e.Offset+e.Length] == h.pattern+"@"+h.username) { return true } } diff --git a/handlers_test.go b/handlers_test.go index d559fba..e45fa01 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -480,7 +480,6 @@ func Test_match_command_start(t *testing.T) { } }) - // wrong command t.Run("start maybe with username suffix 7, no", func(t *testing.T) { b := &Bot{