Compare commits

...

11 Commits

Author SHA1 Message Date
Demian a8acf84301 react: refactor 2 weeks ago
Demian 10fbde9b66 boost: refactor 2 weeks ago
Demian 1acc928a92 middleware: fix tests 2 weeks ago
Demian c9cec214a9 bot: rename DeleteMessages 2 weeks ago
Demian c4fce10013 bot: rename ForwardMessages and CopyMessages 2 weeks ago
Demian 47227931ec bot: refactor Trigger 4 weeks ago
Nash-Well e0c24df69d
context: add boost send cases, boost: fix UserBoosts (#686)
* context: add boost send cases, boost: fix UserBoosts

* context: change method order

* boost: add error wrap

* context: update sender method
4 weeks ago
Nash-Well 77f77a6840
bot: implement Trigger function (#687)
* bot: trigger init

* bot: add execution of registered handlers in trigger func
4 weeks ago
Demian a26ba9f0bd middleware: add context arg to the Recover 3 months ago
Demian af50a953b5 message: remove InaccessibleMessage completely 3 months ago
nash e16cc083f7 telebot,update: change OnBoostRemoved register 3 months ago

@ -254,6 +254,37 @@ func (b *Bot) getUpdates(offset, limit int, timeout time.Duration, allowed []str
return resp.Result, nil
}
func (b *Bot) forwardCopyMany(to Recipient, msgs []Editable, key string, opts ...*SendOptions) ([]Message, error) {
params := map[string]string{
"chat_id": to.Recipient(),
}
embedMessages(params, msgs)
if len(opts) > 0 {
b.embedSendOptions(params, opts[0])
}
data, err := b.Raw(key, params)
if err != nil {
return nil, err
}
var resp struct {
Result []Message
}
if err := json.Unmarshal(data, &resp); err != nil {
var resp struct {
Result bool
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return nil, wrapError(err)
}
return resp.Result, nil
}
// extractOk checks given result for error. If result is ok returns nil.
// In other cases it extracts API error. If error is not presented
// in errors.go, it will be prefixed with `unknown` keyword.
@ -327,37 +358,6 @@ func extractMessage(data []byte) (*Message, error) {
return resp.Result, nil
}
func (b *Bot) forwardCopyMessages(to Recipient, msgs []Editable, key string, opts ...*SendOptions) ([]Message, error) {
params := map[string]string{
"chat_id": to.Recipient(),
}
embedMessages(params, msgs)
if len(opts) > 0 {
b.embedSendOptions(params, opts[0])
}
data, err := b.Raw(key, params)
if err != nil {
return nil, err
}
var resp struct {
Result []Message
}
if err := json.Unmarshal(data, &resp); err != nil {
var resp struct {
Result bool
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return nil, wrapError(err)
}
return resp.Result, nil
}
func verbose(method string, payload interface{}, data []byte) {
body, _ := json.Marshal(payload)
body = bytes.ReplaceAll(body, []byte(`\"`), []byte(`"`))

@ -36,9 +36,9 @@ func (c *Boost) ExpirationDate() time.Time {
type BoostSourceType = string
const (
BoostPremium = "premium"
BoostGiftCode = "gift_code"
BoostGiveaway = "giveaway"
BoostPremium BoostSourceType = "premium"
BoostGiftCode BoostSourceType = "gift_code"
BoostGiveaway BoostSourceType = "giveaway"
)
// BoostSource describes the source of a chat boost.
@ -102,10 +102,12 @@ func (b *Bot) UserBoosts(chat, user Recipient) ([]Boost, error) {
}
var resp struct {
Result []Boost `json:"boosts"`
Result struct {
Boosts []Boost `json:"boosts"`
}
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
return resp.Result.Boosts, nil
}

@ -175,22 +175,33 @@ var (
//
// b.Handle("/ban", onBan, middleware.Whitelist(ids...))
func (b *Bot) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) {
end := extractEndpoint(endpoint)
if end == "" {
panic("telebot: unsupported endpoint")
}
if len(b.group.middleware) > 0 {
m = appendMiddleware(b.group.middleware, m)
}
handler := func(c Context) error {
b.handlers[end] = func(c Context) error {
return applyMiddleware(h, m...)(c)
}
}
switch end := endpoint.(type) {
case string:
b.handlers[end] = handler
case CallbackEndpoint:
b.handlers[end.CallbackUnique()] = handler
default:
panic("telebot: unsupported endpoint")
// Trigger executes the registered handler by the endpoint.
func (b *Bot) Trigger(endpoint interface{}, c Context) error {
end := extractEndpoint(endpoint)
if end == "" {
return fmt.Errorf("telebot: unsupported endpoint")
}
handler, ok := b.handlers[end]
if !ok {
return fmt.Errorf("telebot: no handler found for given endpoint")
}
return handler(c)
}
// Start brings bot into motion by consuming incoming
@ -400,15 +411,15 @@ func (b *Bot) Forward(to Recipient, msg Editable, opts ...interface{}) (*Message
return extractMessage(data)
}
// ForwardMessages method forwards multiple messages of any kind.
// ForwardMany method forwards multiple messages of any kind.
// If some of the specified messages can't be found or forwarded, they are skipped.
// Service messages and messages with protected content can't be forwarded.
// Album grouping is kept for forwarded messages.
func (b *Bot) ForwardMessages(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) {
func (b *Bot) ForwardMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) {
if to == nil {
return nil, ErrBadRecipient
}
return b.forwardCopyMessages(to, msgs, "forwardMessages", opts...)
return b.forwardCopyMany(to, msgs, "forwardMessages", opts...)
}
// Copy behaves just like Forward() but the copied message doesn't have a link to the original message (see Bots API).
@ -437,18 +448,18 @@ func (b *Bot) Copy(to Recipient, msg Editable, options ...interface{}) (*Message
return extractMessage(data)
}
// CopyMessages this method makes a copy of messages of any kind.
// CopyMany this method makes a copy of messages of any kind.
// If some of the specified messages can't be found or copied, they are skipped.
// Service messages, giveaway messages, giveaway winners messages, and
// invoice messages can't be copied. A quiz poll can be copied only if the value of the field
// correct_option_id is known to the bot. The method is analogous
// to the method forwardMessages, but the copied messages don't have a link to the original message.
// Album grouping is kept for copied messages.
func (b *Bot) CopyMessages(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) {
func (b *Bot) CopyMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) {
if to == nil {
return nil, ErrBadRecipient
}
return b.forwardCopyMessages(to, msgs, "copyMessages", opts...)
return b.forwardCopyMany(to, msgs, "copyMessages", opts...)
}
// Edit is magic, it lets you change already sent message.
@ -693,11 +704,10 @@ func (b *Bot) Delete(msg Editable) error {
return err
}
// DeleteMessages deletes multiple messages simultaneously.
// DeleteMany deletes multiple messages simultaneously.
// If some of the specified messages can't be found, they are skipped.
func (b *Bot) DeleteMessages(msgs []Editable) error {
func (b *Bot) DeleteMany(msgs []Editable) error {
params := make(map[string]string)
embedMessages(params, msgs)
_, err := b.Raw("deleteMessages", params)
@ -1260,3 +1270,13 @@ func (b *Bot) botInfo(language, key string) (*BotInfo, error) {
}
return resp.Result, nil
}
func extractEndpoint(endpoint interface{}) string {
switch end := endpoint.(type) {
case string:
return end
case CallbackEndpoint:
return end.CallbackUnique()
}
return ""
}

@ -317,7 +317,7 @@ func TestBotProcessUpdate(t *testing.T) {
b.ProcessUpdate(Update{Message: &Message{Text: "/start@other_bot"}})
b.ProcessUpdate(Update{Message: &Message{Text: "hello"}})
b.ProcessUpdate(Update{Message: &Message{Text: "text"}})
b.ProcessUpdate(Update{Message: &Message{PinnedMessage: &InaccessibleMessage{Message: &Message{}}}})
b.ProcessUpdate(Update{Message: &Message{PinnedMessage: &Message{}}})
b.ProcessUpdate(Update{Message: &Message{Photo: &Photo{}}})
b.ProcessUpdate(Update{Message: &Message{Voice: &Voice{}}})
b.ProcessUpdate(Update{Message: &Message{Audio: &Audio{}}})
@ -342,7 +342,7 @@ func TestBotProcessUpdate(t *testing.T) {
b.ProcessUpdate(Update{Message: &Message{Chat: &Chat{ID: 1}, MigrateTo: 2}})
b.ProcessUpdate(Update{EditedMessage: &Message{Text: "edited"}})
b.ProcessUpdate(Update{ChannelPost: &Message{Text: "post"}})
b.ProcessUpdate(Update{ChannelPost: &Message{PinnedMessage: &InaccessibleMessage{Message: &Message{}}}})
b.ProcessUpdate(Update{ChannelPost: &Message{PinnedMessage: &Message{}}})
b.ProcessUpdate(Update{EditedChannelPost: &Message{Text: "edited post"}})
b.ProcessUpdate(Update{Callback: &Callback{MessageID: "inline", Data: "callback"}})
b.ProcessUpdate(Update{Callback: &Callback{Data: "callback"}})

@ -16,7 +16,7 @@ type Callback struct {
// Message will be set if the button that originated the query
// was attached to a message sent by a bot.
Message *InaccessibleMessage `json:"message"`
Message *Message `json:"message"`
// MessageID will be set if the button was attached to a message
// sent via the bot in inline mode.

@ -16,12 +16,6 @@ type Context interface {
// Bot returns the bot instance.
Bot() *Bot
// Boost returns the boost instance.
Boost() *BoostUpdated
// BoostRemoved returns the boost removed from a chat instance.
BoostRemoved() *BoostRemoved
// Update returns the original update.
Update() Update
@ -61,6 +55,12 @@ type Context interface {
// Topic returns the topic changes.
Topic() *Topic
// Boost returns the boost instance.
Boost() *BoostUpdated
// BoostRemoved returns the boost removed from a chat instance.
BoostRemoved() *BoostRemoved
// Sender returns the current recipient, depending on the context type.
// Returns nil if user is not presented.
Sender() *User
@ -184,14 +184,6 @@ func (c *nativeContext) Bot() *Bot {
return c.b
}
func (c *nativeContext) Boost() *BoostUpdated {
return c.u.Boost
}
func (c *nativeContext) BoostRemoved() *BoostRemoved {
return c.u.BoostRemoved
}
func (c *nativeContext) Update() Update {
return c.u
}
@ -201,12 +193,12 @@ func (c *nativeContext) Message() *Message {
case c.u.Message != nil:
return c.u.Message
case c.u.Callback != nil:
return c.u.Callback.Message.Message
return c.u.Callback.Message
case c.u.EditedMessage != nil:
return c.u.EditedMessage
case c.u.ChannelPost != nil:
if c.u.ChannelPost.PinnedMessage != nil {
return c.u.ChannelPost.PinnedMessage.Message
return c.u.ChannelPost.PinnedMessage
}
return c.u.ChannelPost
case c.u.EditedChannelPost != nil:
@ -279,6 +271,14 @@ func (c *nativeContext) Topic() *Topic {
return nil
}
func (c *nativeContext) Boost() *BoostUpdated {
return c.u.Boost
}
func (c *nativeContext) BoostRemoved() *BoostRemoved {
return c.u.BoostRemoved
}
func (c *nativeContext) Sender() *User {
switch {
case c.u.Callback != nil:
@ -301,9 +301,16 @@ func (c *nativeContext) Sender() *User {
return c.u.ChatMember.Sender
case c.u.ChatJoinRequest != nil:
return c.u.ChatJoinRequest.Sender
default:
return nil
case c.u.Boost != nil:
if b := c.u.Boost.Boost; b != nil && b.Source != nil {
return b.Source.Booster
}
case c.u.BoostRemoved != nil:
if b := c.u.BoostRemoved; b.Source != nil {
return b.Source.Booster
}
}
return nil
}
func (c *nativeContext) Chat() *Chat {

@ -17,7 +17,7 @@ type InputTextMessageContent struct {
ParseMode string `json:"parse_mode,omitempty"`
// (Optional) Link preview generation options for the message.
PreviewOptions *PreviewOptions `json:"link_preview_options"`
PreviewOptions *PreviewOptions `json:"link_preview_options,omitempty"`
}
func (input *InputTextMessageContent) IsInputMessageContent() bool {

@ -1,7 +1,6 @@
package telebot
import (
"encoding/json"
"strconv"
"time"
"unicode/utf16"
@ -105,9 +104,9 @@ type Message struct {
// etc. that appear in the text.
Entities Entities `json:"entities,omitempty"`
// (Optional) ReactionOptions used for link preview generation for the message,
// if it is a text message and link preview options were changed
PreviewOptions PreviewOptions `json:"link_preview_options,omitempty"`
// (Optional) PreviewOptions used for link preview generation for the message,
// if it is a text message and link preview options were changed.
PreviewOptions *PreviewOptions `json:"link_preview_options,omitempty"`
// Some messages containing media, may as well have a caption.
Caption string `json:"caption,omitempty"`
@ -137,7 +136,7 @@ type Message struct {
// For a video, information about it.
Video *Video `json:"video"`
// For a animation, information about it.
// For an animation, information about it.
Animation *Animation `json:"animation"`
// For a contact, contact information itself.
@ -255,7 +254,7 @@ type Message struct {
// Specified message was pinned. Note that the Message object
// in this field will not contain further ReplyTo fields even
// if it is itself a reply.
PinnedMessage *InaccessibleMessage `json:"pinned_message"`
PinnedMessage *Message `json:"pinned_message"`
// Message is an invoice for a payment.
Invoice *Invoice `json:"invoice"`
@ -380,7 +379,7 @@ const (
EntityBlockquote EntityType = "blockquote"
)
// Entities is used to set message's text entities as a send option.
// Entities are used to set message's text entities as a send option.
type Entities []MessageEntity
// ProximityAlert sent whenever a user in the chat triggers
@ -396,6 +395,11 @@ type AutoDeleteTimer struct {
Unixtime int `json:"message_auto_delete_time"`
}
// Inaccessible shows whether the message is InaccessibleMessage object.
func (m *Message) Inaccessible() bool {
return m.Sender == nil
}
// MessageSig satisfies Editable interface (see Editable.)
func (m *Message) MessageSig() (string, int64) {
return strconv.Itoa(m.ID), m.Chat.ID
@ -504,31 +508,6 @@ func (m *Message) Media() Media {
}
}
// InaccessibleMessage describes a message that was deleted or is otherwise
// inaccessible to the bot. An instance of MaybeInaccessibleMessage object.
type InaccessibleMessage struct {
// A message that can be inaccessible to the bot.
*Message
// Chat the message belonged to.
Chat *Chat `json:"chat"`
// Unique message identifier inside the chat.
MessageID int `json:"message_id"`
// Always 0. The field can be used to differentiate regular and
// inaccessible messages.
DateUnixtime int64 `json:"date"`
}
func (im *InaccessibleMessage) MessageSig() (string, int64) {
return strconv.Itoa(im.MessageID), im.Chat.ID
}
func (im *InaccessibleMessage) Time() time.Time {
return time.Unix(im.DateUnixtime, 0)
}
// MessageReaction object represents a change of a reaction on a message performed by a user.
type MessageReaction struct {
// The chat containing the message the user reacted to.
@ -740,33 +719,3 @@ type ReplyParams struct {
// (Optional) Position of the quote in the original message in UTF-16 code units.
QuotePosition int `json:"quote_position"`
}
// React changes the chosen reactions on a message. Service messages can't be
// reacted to. Automatically forwarded messages from a channel to its discussion group have
// the same available reactions as messages in the channel.
func (b *Bot) React(to Recipient, msg Editable, opts ...ReactionOptions) error {
if to == nil {
return ErrBadRecipient
}
msgID, _ := msg.MessageSig()
params := map[string]string{
"chat_id": to.Recipient(),
"message_id": msgID,
}
if len(opts) > 0 {
opt := opts[0]
if len(opt.Reactions) > 0 {
data, _ := json.Marshal(opt.Reactions)
params["reaction"] = string(data)
}
if opt.Big {
params["is_big"] = "true"
}
}
_, err := b.Raw("setMessageReaction", params)
return err
}

@ -32,26 +32,28 @@ func IgnoreVia() tele.MiddlewareFunc {
}
}
type RecoverFunc = func(error, tele.Context)
// Recover returns a middleware that recovers a panic happened in
// the handler.
func Recover(onError ...func(error)) tele.MiddlewareFunc {
func Recover(onError ...RecoverFunc) tele.MiddlewareFunc {
return func(next tele.HandlerFunc) tele.HandlerFunc {
return func(c tele.Context) error {
var f func(error)
var f RecoverFunc
if len(onError) > 0 {
f = onError[0]
} else {
f = func(err error) {
c.Bot().OnError(err, nil)
f = func(err error, c tele.Context) {
c.Bot().OnError(err, c)
}
}
defer func() {
if r := recover(); r != nil {
if err, ok := r.(error); ok {
f(err)
f(err, c)
} else if s, ok := r.(string); ok {
f(errors.New(s))
f(errors.New(s), c)
}
}
}()

@ -12,7 +12,7 @@ import (
var b, _ = tele.NewBot(tele.Settings{Offline: true})
func TestRecover(t *testing.T) {
onError := func(err error) {
onError := func(err error, c tele.Context) {
require.Error(t, err, "recover test")
}

@ -1,7 +1,8 @@
package telebot
// EmojiType defines emoji types.
type EmojiType = string
import (
"encoding/json"
)
// Reaction describes the type of reaction.
// Describes an instance of ReactionTypeCustomEmoji and ReactionTypeEmoji.
@ -10,7 +11,7 @@ type Reaction struct {
Type string `json:"type"`
// Reaction emoji.
Emoji EmojiType `json:"emoji,omitempty"`
Emoji string `json:"emoji,omitempty"`
// Custom emoji identifier.
CustomEmoji string `json:"custom_emoji_id,omitempty"`
@ -34,3 +35,33 @@ type ReactionOptions struct {
// Pass True to set the reaction with a big animation.
Big bool `json:"is_big"`
}
// React changes the chosen reactions on a message. Service messages can't be
// reacted to. Automatically forwarded messages from a channel to its discussion group have
// the same available reactions as messages in the channel.
func (b *Bot) React(to Recipient, msg Editable, opts ...ReactionOptions) error {
if to == nil {
return ErrBadRecipient
}
msgID, _ := msg.MessageSig()
params := map[string]string{
"chat_id": to.Recipient(),
"message_id": msgID,
}
if len(opts) > 0 {
opt := opts[0]
if len(opt.Reactions) > 0 {
data, _ := json.Marshal(opt.Reactions)
params["reaction"] = string(data)
}
if opt.Big {
params["is_big"] = "true"
}
}
_, err := b.Raw("setMessageReaction", params)
return err
}

@ -1,8 +1,14 @@
package react
import "gopkg.in/telebot.v3"
import (
tele "gopkg.in/telebot.v3"
)
type Reaction = tele.Reaction
type Reaction = telebot.Reaction
func React(r ...Reaction) tele.ReactionOptions {
return tele.ReactionOptions{Reactions: r}
}
// Currently available emojis.
var (

@ -111,7 +111,7 @@ const (
OnVideoChatScheduled = "\avideo_chat_scheduled"
OnBoost = "\aboost_updated"
onBoostRemoved = "\aboost_removed"
OnBoostRemoved = "\aboost_removed"
)
// ChatAction is a client-side status indicating bot activity.

@ -319,7 +319,7 @@ func (b *Bot) ProcessUpdate(u Update) {
}
if u.BoostRemoved != nil {
b.handle(onBoostRemoved, c)
b.handle(OnBoostRemoved, c)
return
}
}

Loading…
Cancel
Save