mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-27 04:53:05 +00:00
Initial release of go-telegram
A fully-generated, strongly-typed Go client for the Telegram Bot API. * 176 methods + 301 types generated from Bot API v10.0 * 1408 auto-generated tests (8 scenarios per method) * Typed unions throughout — no 'any' in the public surface * Pluggable HTTP transport and JSON codec (default goccy/go-json) * Built-in retry middleware honouring Telegram's retry_after * Generic dispatcher with filters and conversation handlers * Self-verifying codegen pipeline (regen → audit → emit → run tests) * 14 example bots covering common patterns
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// Package callback provides Filter helpers for *api.CallbackQuery payloads.
|
||||
package callback
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
"github.com/lukaszraczylo/go-telegram/dispatch"
|
||||
)
|
||||
|
||||
// Data returns a Filter that matches callback queries whose Data matches
|
||||
// pattern (regex). Panics at registration time on an invalid pattern.
|
||||
func Data(pattern string) dispatch.Filter[*api.CallbackQuery] {
|
||||
re := regexp.MustCompile(pattern)
|
||||
return func(q *api.CallbackQuery) bool {
|
||||
return q != nil && re.MatchString(q.Data)
|
||||
}
|
||||
}
|
||||
|
||||
// DataEquals returns a Filter that matches callback queries whose Data equals
|
||||
// s exactly.
|
||||
func DataEquals(s string) dispatch.Filter[*api.CallbackQuery] {
|
||||
return func(q *api.CallbackQuery) bool {
|
||||
return q != nil && q.Data == s
|
||||
}
|
||||
}
|
||||
|
||||
// DataPrefix returns a Filter that matches callback queries whose Data starts
|
||||
// with prefix.
|
||||
func DataPrefix(prefix string) dispatch.Filter[*api.CallbackQuery] {
|
||||
return func(q *api.CallbackQuery) bool {
|
||||
return q != nil && strings.HasPrefix(q.Data, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// FromUser returns a Filter that matches callback queries whose From.ID equals
|
||||
// userID.
|
||||
func FromUser(userID int64) dispatch.Filter[*api.CallbackQuery] {
|
||||
return func(q *api.CallbackQuery) bool {
|
||||
return q != nil && q.From.ID == userID
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package callback_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
cbfilter "github.com/lukaszraczylo/go-telegram/dispatch/filters/callback"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func cq(data string, userID int64) *api.CallbackQuery {
|
||||
return &api.CallbackQuery{
|
||||
ID: "q",
|
||||
From: api.User{ID: userID},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func TestData(t *testing.T) {
|
||||
f := cbfilter.Data(`^like:\d+$`)
|
||||
require.True(t, f(cq("like:42", 1)))
|
||||
require.False(t, f(cq("dislike:42", 1)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestData_PanicsOnBadPattern(t *testing.T) {
|
||||
require.Panics(t, func() { cbfilter.Data(`[bad`) })
|
||||
}
|
||||
|
||||
func TestDataEquals(t *testing.T) {
|
||||
f := cbfilter.DataEquals("yes")
|
||||
require.True(t, f(cq("yes", 1)))
|
||||
require.False(t, f(cq("yes please", 1)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestDataPrefix(t *testing.T) {
|
||||
f := cbfilter.DataPrefix("vote:")
|
||||
require.True(t, f(cq("vote:up", 1)))
|
||||
require.False(t, f(cq("novote:up", 1)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestFromUser(t *testing.T) {
|
||||
f := cbfilter.FromUser(7)
|
||||
require.True(t, f(cq("data", 7)))
|
||||
require.False(t, f(cq("data", 8)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestComposedCallbackFilters(t *testing.T) {
|
||||
f := cbfilter.DataPrefix("vote:").And(cbfilter.FromUser(7))
|
||||
require.True(t, f(cq("vote:up", 7)))
|
||||
require.False(t, f(cq("vote:up", 8)))
|
||||
require.False(t, f(cq("other", 7)))
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Package chatjoinrequest provides Filter helpers for *api.ChatJoinRequest payloads.
|
||||
package chatjoinrequest
|
||||
|
||||
import (
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
"github.com/lukaszraczylo/go-telegram/dispatch"
|
||||
)
|
||||
|
||||
// FromUser returns a Filter that matches join requests where the requesting
|
||||
// user's ID equals uid.
|
||||
func FromUser(uid int64) dispatch.Filter[*api.ChatJoinRequest] {
|
||||
return func(r *api.ChatJoinRequest) bool {
|
||||
return r != nil && r.From.ID == uid
|
||||
}
|
||||
}
|
||||
|
||||
// InChat returns a Filter that matches join requests directed at the chat
|
||||
// with the given chat ID.
|
||||
func InChat(cid int64) dispatch.Filter[*api.ChatJoinRequest] {
|
||||
return func(r *api.ChatJoinRequest) bool {
|
||||
return r != nil && r.Chat.ID == cid
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package chatjoinrequest_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
cjrfilter "github.com/lukaszraczylo/go-telegram/dispatch/filters/chatjoinrequest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func joinRequest(fromID, chatID int64) *api.ChatJoinRequest {
|
||||
return &api.ChatJoinRequest{
|
||||
Chat: api.Chat{ID: chatID},
|
||||
From: api.User{ID: fromID},
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromUser_Matches(t *testing.T) {
|
||||
f := cjrfilter.FromUser(10)
|
||||
require.True(t, f(joinRequest(10, 100)))
|
||||
require.False(t, f(joinRequest(99, 100)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestInChat_Matches(t *testing.T) {
|
||||
f := cjrfilter.InChat(100)
|
||||
require.True(t, f(joinRequest(10, 100)))
|
||||
require.False(t, f(joinRequest(10, 200)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestComposedFilters(t *testing.T) {
|
||||
f := cjrfilter.FromUser(10).And(cjrfilter.InChat(100))
|
||||
require.True(t, f(joinRequest(10, 100)))
|
||||
require.False(t, f(joinRequest(10, 200)))
|
||||
require.False(t, f(joinRequest(99, 100)))
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Package chatmember provides Filter helpers for *api.ChatMemberUpdated payloads.
|
||||
package chatmember
|
||||
|
||||
import (
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
"github.com/lukaszraczylo/go-telegram/dispatch"
|
||||
)
|
||||
|
||||
// NewStatus returns a Filter that matches updates where the new chat member
|
||||
// status equals s (e.g. "member", "administrator", "kicked", "left").
|
||||
func NewStatus(s string) dispatch.Filter[*api.ChatMemberUpdated] {
|
||||
return func(u *api.ChatMemberUpdated) bool {
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
switch m := u.NewChatMember.(type) {
|
||||
case *api.ChatMemberOwner:
|
||||
return m.Status == s
|
||||
case *api.ChatMemberAdministrator:
|
||||
return m.Status == s
|
||||
case *api.ChatMemberMember:
|
||||
return m.Status == s
|
||||
case *api.ChatMemberRestricted:
|
||||
return m.Status == s
|
||||
case *api.ChatMemberLeft:
|
||||
return m.Status == s
|
||||
case *api.ChatMemberBanned:
|
||||
return m.Status == s
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FromUser returns a Filter that matches updates where the acting user
|
||||
// (From.ID) equals uid.
|
||||
func FromUser(uid int64) dispatch.Filter[*api.ChatMemberUpdated] {
|
||||
return func(u *api.ChatMemberUpdated) bool {
|
||||
return u != nil && u.From.ID == uid
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package chatmember_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
cmfilter "github.com/lukaszraczylo/go-telegram/dispatch/filters/chatmember"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func memberUpdate(status string, fromID int64) *api.ChatMemberUpdated {
|
||||
var newMember api.ChatMember
|
||||
switch status {
|
||||
case "member":
|
||||
newMember = &api.ChatMemberMember{Status: status}
|
||||
case "administrator":
|
||||
newMember = &api.ChatMemberAdministrator{Status: status}
|
||||
case "kicked":
|
||||
newMember = &api.ChatMemberBanned{Status: status}
|
||||
case "left":
|
||||
newMember = &api.ChatMemberLeft{Status: status}
|
||||
default:
|
||||
newMember = &api.ChatMemberMember{Status: status}
|
||||
}
|
||||
return &api.ChatMemberUpdated{
|
||||
From: api.User{ID: fromID},
|
||||
NewChatMember: newMember,
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStatus_Matches(t *testing.T) {
|
||||
f := cmfilter.NewStatus("member")
|
||||
require.True(t, f(memberUpdate("member", 1)))
|
||||
require.False(t, f(memberUpdate("kicked", 1)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestNewStatus_Administrator(t *testing.T) {
|
||||
f := cmfilter.NewStatus("administrator")
|
||||
require.True(t, f(memberUpdate("administrator", 1)))
|
||||
require.False(t, f(memberUpdate("member", 1)))
|
||||
}
|
||||
|
||||
func TestNewStatus_Kicked(t *testing.T) {
|
||||
f := cmfilter.NewStatus("kicked")
|
||||
require.True(t, f(memberUpdate("kicked", 1)))
|
||||
require.False(t, f(memberUpdate("left", 1)))
|
||||
}
|
||||
|
||||
func TestNewStatus_Left(t *testing.T) {
|
||||
f := cmfilter.NewStatus("left")
|
||||
require.True(t, f(memberUpdate("left", 1)))
|
||||
require.False(t, f(memberUpdate("member", 1)))
|
||||
}
|
||||
|
||||
func TestFromUser_Matches(t *testing.T) {
|
||||
f := cmfilter.FromUser(42)
|
||||
require.True(t, f(memberUpdate("member", 42)))
|
||||
require.False(t, f(memberUpdate("member", 99)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestComposedFilters(t *testing.T) {
|
||||
f := cmfilter.NewStatus("member").And(cmfilter.FromUser(7))
|
||||
require.True(t, f(memberUpdate("member", 7)))
|
||||
require.False(t, f(memberUpdate("member", 8)))
|
||||
require.False(t, f(memberUpdate("kicked", 7)))
|
||||
}
|
||||
|
||||
func TestNewStatus_Owner(t *testing.T) {
|
||||
u := &api.ChatMemberUpdated{
|
||||
From: api.User{ID: 1},
|
||||
NewChatMember: &api.ChatMemberOwner{Status: "creator"},
|
||||
}
|
||||
require.True(t, cmfilter.NewStatus("creator")(u))
|
||||
require.False(t, cmfilter.NewStatus("member")(u))
|
||||
}
|
||||
|
||||
func TestNewStatus_Restricted(t *testing.T) {
|
||||
u := &api.ChatMemberUpdated{
|
||||
From: api.User{ID: 1},
|
||||
NewChatMember: &api.ChatMemberRestricted{Status: "restricted"},
|
||||
}
|
||||
require.True(t, cmfilter.NewStatus("restricted")(u))
|
||||
require.False(t, cmfilter.NewStatus("member")(u))
|
||||
}
|
||||
|
||||
func TestNewStatus_UnknownType(t *testing.T) {
|
||||
// nil NewChatMember → default branch → false
|
||||
u := &api.ChatMemberUpdated{
|
||||
From: api.User{ID: 1},
|
||||
NewChatMember: nil,
|
||||
}
|
||||
require.False(t, cmfilter.NewStatus("member")(u))
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Package inline provides Filter helpers for *api.InlineQuery payloads.
|
||||
package inline
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
"github.com/lukaszraczylo/go-telegram/dispatch"
|
||||
)
|
||||
|
||||
// Query returns a Filter that matches inline queries whose Query field matches
|
||||
// pattern (regex). Panics at registration time on an invalid pattern.
|
||||
func Query(pattern string) dispatch.Filter[*api.InlineQuery] {
|
||||
re := regexp.MustCompile(pattern)
|
||||
return func(q *api.InlineQuery) bool {
|
||||
return q != nil && re.MatchString(q.Query)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryEquals returns a Filter that matches inline queries whose Query equals
|
||||
// s exactly.
|
||||
func QueryEquals(s string) dispatch.Filter[*api.InlineQuery] {
|
||||
return func(q *api.InlineQuery) bool {
|
||||
return q != nil && q.Query == s
|
||||
}
|
||||
}
|
||||
|
||||
// QueryPrefix returns a Filter that matches inline queries whose Query starts
|
||||
// with prefix.
|
||||
func QueryPrefix(prefix string) dispatch.Filter[*api.InlineQuery] {
|
||||
return func(q *api.InlineQuery) bool {
|
||||
return q != nil && strings.HasPrefix(q.Query, prefix)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package inline_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
ilfilter "github.com/lukaszraczylo/go-telegram/dispatch/filters/inline"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func iq(query string) *api.InlineQuery {
|
||||
return &api.InlineQuery{ID: "i", From: api.User{ID: 1}, Query: query}
|
||||
}
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
f := ilfilter.Query(`^find`)
|
||||
require.True(t, f(iq("find me")))
|
||||
require.False(t, f(iq("search me")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestQuery_PanicsOnBadPattern(t *testing.T) {
|
||||
require.Panics(t, func() { ilfilter.Query(`[bad`) })
|
||||
}
|
||||
|
||||
func TestQueryEquals(t *testing.T) {
|
||||
f := ilfilter.QueryEquals("exact")
|
||||
require.True(t, f(iq("exact")))
|
||||
require.False(t, f(iq("exact match")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestQueryPrefix(t *testing.T) {
|
||||
f := ilfilter.QueryPrefix("@user")
|
||||
require.True(t, f(iq("@username")))
|
||||
require.False(t, f(iq("no prefix")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestComposedInlineFilters(t *testing.T) {
|
||||
f := ilfilter.QueryPrefix("find").Or(ilfilter.QueryEquals("help"))
|
||||
require.True(t, f(iq("find me")))
|
||||
require.True(t, f(iq("help")))
|
||||
require.False(t, f(iq("other")))
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Package message provides Filter helpers for *api.Message payloads.
|
||||
package message
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
"github.com/lukaszraczylo/go-telegram/dispatch"
|
||||
)
|
||||
|
||||
// Text returns a Filter that matches messages whose Text matches pattern (regex).
|
||||
// Panics at registration time on an invalid pattern.
|
||||
func Text(pattern string) dispatch.Filter[*api.Message] {
|
||||
re := regexp.MustCompile(pattern)
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && re.MatchString(m.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// TextEquals returns a Filter that matches messages whose Text equals s exactly.
|
||||
func TextEquals(s string) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.Text == s
|
||||
}
|
||||
}
|
||||
|
||||
// TextPrefix returns a Filter that matches messages whose Text starts with prefix.
|
||||
func TextPrefix(prefix string) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && strings.HasPrefix(m.Text, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// TextContains returns a Filter that matches messages whose Text contains sub.
|
||||
func TextContains(sub string) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && strings.Contains(m.Text, sub)
|
||||
}
|
||||
}
|
||||
|
||||
// Command returns a Filter that matches messages whose first entity is a
|
||||
// bot_command equal to "/<name>" (with or without "@BotName" suffix).
|
||||
func Command(name string) dispatch.Filter[*api.Message] {
|
||||
want := "/" + strings.TrimPrefix(name, "/")
|
||||
return func(m *api.Message) bool {
|
||||
if m == nil || len(m.Entities) == 0 || m.Text == "" {
|
||||
return false
|
||||
}
|
||||
first := m.Entities[0]
|
||||
if first.Type != string(api.EntityBotCommand) || first.Offset != 0 {
|
||||
return false
|
||||
}
|
||||
end := int(first.Length)
|
||||
runes := []rune(m.Text)
|
||||
if end > len(runes) {
|
||||
return false
|
||||
}
|
||||
cmd := string(runes[:end])
|
||||
if i := strings.Index(cmd, "@"); i >= 0 {
|
||||
cmd = cmd[:i]
|
||||
}
|
||||
return cmd == want
|
||||
}
|
||||
}
|
||||
|
||||
// AnyCommand returns a Filter that matches any message starting with a
|
||||
// bot_command entity at offset 0.
|
||||
func AnyCommand() dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
if m == nil || len(m.Entities) == 0 {
|
||||
return false
|
||||
}
|
||||
first := m.Entities[0]
|
||||
return first.Type == string(api.EntityBotCommand) && first.Offset == 0
|
||||
}
|
||||
}
|
||||
|
||||
// IsReply returns a Filter that matches messages that have ReplyToMessage set.
|
||||
func IsReply() dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.ReplyToMessage != nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsForward returns a Filter that matches messages that have ForwardOrigin set.
|
||||
func IsForward() dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.ForwardOrigin != nil
|
||||
}
|
||||
}
|
||||
|
||||
// HasPhoto returns a Filter that matches messages with a Photo attachment.
|
||||
func HasPhoto() dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && len(m.Photo) > 0
|
||||
}
|
||||
}
|
||||
|
||||
// HasDocument returns a Filter that matches messages with a Document attachment.
|
||||
func HasDocument() dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.Document != nil
|
||||
}
|
||||
}
|
||||
|
||||
// HasEntity returns a Filter that matches messages whose Entities contain at
|
||||
// least one entity of type t (e.g. string(api.EntityBotCommand)).
|
||||
func HasEntity(t string) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
for _, e := range m.Entities {
|
||||
if e.Type == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ChatType returns a Filter that matches messages whose Chat.Type equals t.
|
||||
func ChatType(t api.ChatType) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.Chat.Type == string(t)
|
||||
}
|
||||
}
|
||||
|
||||
// FromUser returns a Filter that matches messages whose From.ID equals userID.
|
||||
func FromUser(userID int64) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.From != nil && m.From.ID == userID
|
||||
}
|
||||
}
|
||||
|
||||
// InChat returns a Filter that matches messages whose Chat.ID equals chatID.
|
||||
func InChat(chatID int64) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.Chat.ID == chatID
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package message_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
msgfilter "github.com/lukaszraczylo/go-telegram/dispatch/filters/message"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func msg(text string) *api.Message {
|
||||
return &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
func cmdMsg(cmd string) *api.Message {
|
||||
text := cmd
|
||||
return &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
Entities: []api.MessageEntity{
|
||||
{Type: string(api.EntityBotCommand), Offset: 0, Length: int64(len([]rune(text)))},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestText(t *testing.T) {
|
||||
f := msgfilter.Text(`^hello`)
|
||||
require.True(t, f(msg("hello world")))
|
||||
require.False(t, f(msg("world hello")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestText_PanicsOnBadPattern(t *testing.T) {
|
||||
require.Panics(t, func() { msgfilter.Text(`[invalid`) })
|
||||
}
|
||||
|
||||
func TestTextEquals(t *testing.T) {
|
||||
f := msgfilter.TextEquals("hi")
|
||||
require.True(t, f(msg("hi")))
|
||||
require.False(t, f(msg("hi there")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestTextPrefix(t *testing.T) {
|
||||
f := msgfilter.TextPrefix("/start")
|
||||
require.True(t, f(msg("/start now")))
|
||||
require.False(t, f(msg("no prefix")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestTextContains(t *testing.T) {
|
||||
f := msgfilter.TextContains("bot")
|
||||
require.True(t, f(msg("my bot is cool")))
|
||||
require.False(t, f(msg("nothing here")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
t.Run("matches exact command", func(t *testing.T) {
|
||||
f := msgfilter.Command("/start")
|
||||
require.True(t, f(cmdMsg("/start")))
|
||||
})
|
||||
t.Run("matches without leading slash", func(t *testing.T) {
|
||||
f := msgfilter.Command("start")
|
||||
require.True(t, f(cmdMsg("/start")))
|
||||
})
|
||||
t.Run("strips BotName suffix", func(t *testing.T) {
|
||||
m := &api.Message{
|
||||
Text: "/start@MyBot",
|
||||
Entities: []api.MessageEntity{{Type: string(api.EntityBotCommand), Offset: 0, Length: 12}},
|
||||
}
|
||||
f := msgfilter.Command("/start")
|
||||
require.True(t, f(m))
|
||||
})
|
||||
t.Run("no match different command", func(t *testing.T) {
|
||||
f := msgfilter.Command("/stop")
|
||||
require.False(t, f(cmdMsg("/start")))
|
||||
})
|
||||
t.Run("nil message", func(t *testing.T) {
|
||||
require.False(t, msgfilter.Command("/start")(nil))
|
||||
})
|
||||
t.Run("no entities", func(t *testing.T) {
|
||||
require.False(t, msgfilter.Command("/start")(msg("/start")))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAnyCommand(t *testing.T) {
|
||||
f := msgfilter.AnyCommand()
|
||||
require.True(t, f(cmdMsg("/anything")))
|
||||
require.False(t, f(msg("plain text")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestIsReply(t *testing.T) {
|
||||
f := msgfilter.IsReply()
|
||||
m := msg("reply")
|
||||
m.ReplyToMessage = &api.Message{MessageID: 2}
|
||||
require.True(t, f(m))
|
||||
require.False(t, f(msg("no reply")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestIsForward(t *testing.T) {
|
||||
// ForwardOrigin is a MessageOrigin interface; set via a concrete type.
|
||||
f := msgfilter.IsForward()
|
||||
m := msg("fwd")
|
||||
m.ForwardOrigin = &api.MessageOriginUser{Type: "user"}
|
||||
require.True(t, f(m))
|
||||
require.False(t, f(msg("no fwd")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestHasPhoto(t *testing.T) {
|
||||
f := msgfilter.HasPhoto()
|
||||
m := msg("")
|
||||
m.Photo = []api.PhotoSize{{FileID: "x", Width: 100, Height: 100}}
|
||||
require.True(t, f(m))
|
||||
require.False(t, f(msg("no photo")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestHasDocument(t *testing.T) {
|
||||
f := msgfilter.HasDocument()
|
||||
m := msg("")
|
||||
m.Document = &api.Document{FileID: "doc1"}
|
||||
require.True(t, f(m))
|
||||
require.False(t, f(msg("no doc")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestHasEntity(t *testing.T) {
|
||||
f := msgfilter.HasEntity(string(api.EntityURL))
|
||||
m := msg("check https://example.com")
|
||||
m.Entities = []api.MessageEntity{{Type: string(api.EntityURL), Offset: 6, Length: 19}}
|
||||
require.True(t, f(m))
|
||||
require.False(t, f(msg("plain")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestChatType(t *testing.T) {
|
||||
f := msgfilter.ChatType(api.ChatTypePrivate)
|
||||
private := msg("hi")
|
||||
require.True(t, f(private))
|
||||
|
||||
group := msg("hi")
|
||||
group.Chat.Type = string(api.ChatTypeGroup)
|
||||
require.False(t, f(group))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestFromUser(t *testing.T) {
|
||||
f := msgfilter.FromUser(42)
|
||||
m := msg("hi")
|
||||
m.From = &api.User{ID: 42}
|
||||
require.True(t, f(m))
|
||||
|
||||
m2 := msg("hi")
|
||||
m2.From = &api.User{ID: 99}
|
||||
require.False(t, f(m2))
|
||||
|
||||
require.False(t, f(msg("no from")))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestInChat(t *testing.T) {
|
||||
f := msgfilter.InChat(1)
|
||||
require.True(t, f(msg("hi")))
|
||||
m2 := msg("hi")
|
||||
m2.Chat.ID = 2
|
||||
require.False(t, f(m2))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestComposedMessageFilters(t *testing.T) {
|
||||
// private chat AND contains "hello"
|
||||
f := msgfilter.ChatType(api.ChatTypePrivate).And(msgfilter.TextContains("hello"))
|
||||
m := msg("say hello")
|
||||
require.True(t, f(m))
|
||||
|
||||
m2 := msg("say hello")
|
||||
m2.Chat.Type = string(api.ChatTypeGroup)
|
||||
require.False(t, f(m2))
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Package precheckoutquery provides Filter helpers for *api.PreCheckoutQuery payloads.
|
||||
package precheckoutquery
|
||||
|
||||
import (
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
"github.com/lukaszraczylo/go-telegram/dispatch"
|
||||
)
|
||||
|
||||
// Currency returns a Filter that matches pre-checkout queries with the given
|
||||
// ISO 4217 currency code (e.g. "USD", "EUR", "XTR").
|
||||
func Currency(c string) dispatch.Filter[*api.PreCheckoutQuery] {
|
||||
return func(q *api.PreCheckoutQuery) bool {
|
||||
return q != nil && q.Currency == c
|
||||
}
|
||||
}
|
||||
|
||||
// FromUser returns a Filter that matches pre-checkout queries sent by the
|
||||
// user with the given ID.
|
||||
func FromUser(uid int64) dispatch.Filter[*api.PreCheckoutQuery] {
|
||||
return func(q *api.PreCheckoutQuery) bool {
|
||||
return q != nil && q.From.ID == uid
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package precheckoutquery_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
pcqfilter "github.com/lukaszraczylo/go-telegram/dispatch/filters/precheckoutquery"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func pcq(currency string, fromID int64) *api.PreCheckoutQuery {
|
||||
return &api.PreCheckoutQuery{
|
||||
ID: "q",
|
||||
Currency: currency,
|
||||
From: api.User{ID: fromID},
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrency_Matches(t *testing.T) {
|
||||
f := pcqfilter.Currency("USD")
|
||||
require.True(t, f(pcq("USD", 1)))
|
||||
require.False(t, f(pcq("EUR", 1)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestFromUser_Matches(t *testing.T) {
|
||||
f := pcqfilter.FromUser(5)
|
||||
require.True(t, f(pcq("USD", 5)))
|
||||
require.False(t, f(pcq("USD", 9)))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
|
||||
func TestComposedFilters(t *testing.T) {
|
||||
f := pcqfilter.Currency("XTR").And(pcqfilter.FromUser(42))
|
||||
require.True(t, f(pcq("XTR", 42)))
|
||||
require.False(t, f(pcq("XTR", 99)))
|
||||
require.False(t, f(pcq("USD", 42)))
|
||||
}
|
||||
Reference in New Issue
Block a user