Compare commits

..

No commits in common. 'v3' and 'v3.1.4' have entirely different histories.
v3 ... v3.1.4

@ -21,33 +21,12 @@ type Rights struct {
CanRestrictMembers bool `json:"can_restrict_members"`
CanPromoteMembers bool `json:"can_promote_members"`
CanSendMessages bool `json:"can_send_messages"`
CanSendMedia bool `json:"can_send_media_messages"`
CanSendPolls bool `json:"can_send_polls"`
CanSendOther bool `json:"can_send_other_messages"`
CanAddPreviews bool `json:"can_add_web_page_previews"`
CanManageVideoChats bool `json:"can_manage_video_chats"`
CanManageChat bool `json:"can_manage_chat"`
CanManageTopics bool `json:"can_manage_topics"`
CanSendMedia bool `json:"can_send_media_messages,omitempty"` // deprecated
CanSendAudios bool `json:"can_send_audios"`
CanSendDocuments bool `json:"can_send_documents"`
CanSendPhotos bool `json:"can_send_photos"`
CanSendVideos bool `json:"can_send_videos"`
CanSendVideoNotes bool `json:"can_send_video_notes"`
CanSendVoiceNotes bool `json:"can_send_voice_notes"`
CanPostStories bool `json:"can_post_stories"`
CanEditStories bool `json:"can_edit_stories"`
CanDeleteStories bool `json:"can_delete_stories"`
// Independent defines whether the chat permissions are set independently.
// If not, the can_send_other_messages and can_add_web_page_previews permissions
// will imply the can_send_messages, can_send_audios, can_send_documents, can_send_photos,
// can_send_videos, can_send_video_notes, and can_send_voice_notes permissions;
// the can_send_polls permission will imply the can_send_messages permission.
//
// Works for Restrict and SetGroupPermissions methods only.
Independent bool `json:"-"`
}
// NoRights is the default Rights{}.
@ -56,8 +35,9 @@ func NoRights() Rights { return Rights{} }
// NoRestrictions should be used when un-restricting or
// un-promoting user.
//
// member.Rights = tele.NoRestrictions()
// b.Restrict(chat, member)
// member.Rights = tele.NoRestrictions()
// b.Restrict(chat, member)
//
func NoRestrictions() Rights {
return Rights{
CanBeEdited: true,
@ -70,18 +50,12 @@ func NoRestrictions() Rights {
CanPinMessages: false,
CanPromoteMembers: false,
CanSendMessages: true,
CanSendMedia: true,
CanSendPolls: true,
CanSendOther: true,
CanAddPreviews: true,
CanManageVideoChats: false,
CanManageChat: false,
CanManageTopics: false,
CanSendAudios: true,
CanSendDocuments: true,
CanSendPhotos: true,
CanSendVideos: true,
CanSendVideoNotes: true,
CanSendVoiceNotes: true,
}
}
@ -98,21 +72,12 @@ func AdminRights() Rights {
CanPinMessages: true,
CanPromoteMembers: true,
CanSendMessages: true,
CanSendMedia: true,
CanSendPolls: true,
CanSendOther: true,
CanAddPreviews: true,
CanManageVideoChats: true,
CanManageChat: true,
CanManageTopics: true,
CanSendAudios: true,
CanSendDocuments: true,
CanSendPhotos: true,
CanSendVideos: true,
CanSendVideoNotes: true,
CanSendVoiceNotes: true,
CanPostStories: true,
CanEditStories: true,
CanDeleteStories: true,
}
}
@ -155,22 +120,20 @@ func (b *Bot) Unban(chat *Chat, user *User, forBanned ...bool) error {
// Restrict lets you restrict a subset of member's rights until
// member.RestrictedUntil, such as:
//
// - can send messages
// - can send media
// - can send other
// - can add web page previews
// * can send messages
// * can send media
// * can send other
// * can add web page previews
//
func (b *Bot) Restrict(chat *Chat, member *ChatMember) error {
perms, until := member.Rights, member.RestrictedUntil
prv, until := member.Rights, member.RestrictedUntil
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"user_id": member.User.Recipient(),
"until_date": strconv.FormatInt(until, 10),
"permissions": perms,
}
if perms.Independent {
params["use_independent_chat_permissions"] = true
"chat_id": chat.Recipient(),
"user_id": member.User.Recipient(),
"until_date": strconv.FormatInt(until, 10),
}
embedRights(params, prv)
_, err := b.Raw("restrictChatMember", params)
return err
@ -178,21 +141,24 @@ func (b *Bot) Restrict(chat *Chat, member *ChatMember) error {
// Promote lets you update member's admin rights, such as:
//
// - can change info
// - can post messages
// - can edit messages
// - can delete messages
// - can invite users
// - can restrict members
// - can pin messages
// - can promote members
// * can change info
// * can post messages
// * can edit messages
// * can delete messages
// * can invite users
// * can restrict members
// * can pin messages
// * can promote members
//
func (b *Bot) Promote(chat *Chat, member *ChatMember) error {
prv := member.Rights
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"user_id": member.User.Recipient(),
"is_anonymous": member.Anonymous,
}
embedRights(params, member.Rights)
embedRights(params, prv)
_, err := b.Raw("promoteChatMember", params)
return err
@ -205,6 +171,7 @@ func (b *Bot) Promote(chat *Chat, member *ChatMember) error {
//
// If the chat is a group or a supergroup and
// no administrators were appointed, only the creator will be returned.
//
func (b *Bot) AdminsOf(chat *Chat) ([]ChatMember, error) {
params := map[string]string{
"chat_id": chat.Recipient(),

@ -20,6 +20,7 @@ func TestEmbedRights(t *testing.T) {
"user_id": "2",
"can_be_edited": true,
"can_send_messages": true,
"can_send_media_messages": true,
"can_send_polls": true,
"can_send_other_messages": true,
"can_add_web_page_previews": true,
@ -33,16 +34,6 @@ func TestEmbedRights(t *testing.T) {
"can_promote_members": false,
"can_manage_video_chats": false,
"can_manage_chat": false,
"can_manage_topics": false,
"can_send_audios": true,
"can_send_documents": true,
"can_send_photos": true,
"can_send_videos": true,
"can_send_video_notes": true,
"can_send_voice_notes": true,
"can_post_stories": false,
"can_edit_stories": false,
"can_delete_stories": false,
}
assert.Equal(t, expected, params)
}

@ -27,21 +27,18 @@ func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) {
return nil, err
}
// Cancel the request immediately without waiting for the timeout
// when bot is about to stop.
// Cancel the request immediately without waiting for the timeout when bot is about to stop.
// This may become important if doing long polling with long timeout.
exit := make(chan struct{})
defer close(exit)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
b.stopMu.RLock()
stopCh := b.stopClient
b.stopMu.RUnlock()
select {
case <-stopCh:
case <-b.stopClient:
cancel()
case <-ctx.Done():
case <-exit:
}
}()
@ -163,19 +160,6 @@ func addFileToWriter(writer *multipart.Writer, filename, field string, file inte
return err
}
func (f *File) process(name string, files map[string]File) string {
switch {
case f.InCloud():
return f.FileID
case f.FileURL != "":
return f.FileURL
case f.OnDisk() || f.FileReader != nil:
files[name] = *f
return "attach://" + name
}
return ""
}
func (b *Bot) sendText(to Recipient, text string, opt *SendOptions) (*Message, error) {
params := map[string]string{
"chat_id": to.Recipient(),
@ -254,37 +238,6 @@ 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.

@ -1,113 +0,0 @@
package telebot
import (
"encoding/json"
"time"
)
// Boost contains information about a chat boost.
type Boost struct {
// Unique identifier of the boost.
ID string `json:"boost_id"`
// Point in time (Unix timestamp) when the chat was boosted.
AddUnixtime int64 `json:"add_date"`
// Point in time (Unix timestamp) when the boost will automatically expire,
// unless the booster's Telegram Premium subscription is prolonged.
ExpirationUnixtime int64 `json:"expiration_date"`
// Source of the added boost.
Source *BoostSource `json:"source"`
}
// AddDate returns the moment of time when the chat has been boosted in local time.
func (c *Boost) AddDate() time.Time {
return time.Unix(c.AddUnixtime, 0)
}
// ExpirationDate returns the moment of time when the boost of the channel
// will expire in local time.
func (c *Boost) ExpirationDate() time.Time {
return time.Unix(c.ExpirationUnixtime, 0)
}
// BoostSourceType describes a type of boost.
type BoostSourceType = string
const (
BoostPremium BoostSourceType = "premium"
BoostGiftCode BoostSourceType = "gift_code"
BoostGiveaway BoostSourceType = "giveaway"
)
// BoostSource describes the source of a chat boost.
type BoostSource struct {
// Source of the boost, always (“premium”, “gift_code”, “giveaway”).
Source BoostSourceType `json:"source"`
// User that boosted the chat.
Booster *User `json:"user"`
// Identifier of a message in the chat with the giveaway; the message
// could have been deleted already. May be 0 if the message isn't sent yet.
GiveawayMessageID int `json:"giveaway_message_id,omitempty"`
// (Optional) True, if the giveaway was completed, but there was
// no user to win the prize.
Unclaimed bool `json:"is_unclaimed,omitempty"`
}
// BoostAdded represents a service message about a user boosting a chat.
type BoostAdded struct {
// Number of boosts added by the user.
Count int `json:"boost_count"`
}
// BoostUpdated represents a boost added to a chat or changed.
type BoostUpdated struct {
// Chat which was boosted.
Chat *Chat `json:"chat"`
// Information about the chat boost.
Boost *Boost `json:"boost"`
}
// BoostRemoved represents a boost removed from a chat.
type BoostRemoved struct {
// Chat which was boosted.
Chat *Chat `json:"chat"`
// Unique identifier of the boost.
BoostID string `json:"boost_id"`
// Point in time (Unix timestamp) when the boost was removed.
RemoveUnixtime int64 `json:"remove_date"`
// Source of the removed boost.
Source *BoostSource `json:"source"`
}
// UserBoosts gets the list of boosts added to a chat by a user.
// Requires administrator rights in the chat.
func (b *Bot) UserBoosts(chat, user Recipient) ([]Boost, error) {
params := map[string]string{
"chat_id": chat.Recipient(),
"user_id": user.Recipient(),
}
data, err := b.Raw("getUserChatBoosts", params)
if err != nil {
return nil, err
}
var resp struct {
Result struct {
Boosts []Boost `json:"boosts"`
}
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result.Boosts, nil
}

194
bot.go

@ -10,7 +10,6 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
)
@ -82,9 +81,7 @@ type Bot struct {
parseMode ParseMode
stop chan chan struct{}
client *http.Client
stopMu sync.RWMutex
stopClient chan struct{}
stopClient chan struct{}
}
// Settings represents a utility struct for passing certain
@ -175,33 +172,22 @@ 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)
}
b.handlers[end] = func(c Context) error {
handler := func(c Context) error {
return applyMiddleware(h, m...)(c)
}
}
// 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")
switch end := endpoint.(type) {
case string:
b.handlers[end] = handler
case CallbackEndpoint:
b.handlers[end.CallbackUnique()] = handler
default:
panic("telebot: unsupported endpoint")
}
return handler(c)
}
// Start brings bot into motion by consuming incoming
@ -212,14 +198,10 @@ func (b *Bot) Start() {
}
// do nothing if called twice
b.stopMu.Lock()
if b.stopClient != nil {
b.stopMu.Unlock()
return
}
b.stopClient = make(chan struct{})
b.stopMu.Unlock()
stop := make(chan struct{})
stopConfirm := make(chan struct{})
@ -239,6 +221,7 @@ func (b *Bot) Start() {
close(stop)
<-stopConfirm
close(confirm)
b.stopClient = nil
return
}
}
@ -246,13 +229,9 @@ func (b *Bot) Start() {
// Stop gracefully shuts the poller down.
func (b *Bot) Stop() {
b.stopMu.Lock()
if b.stopClient != nil {
close(b.stopClient)
b.stopClient = nil
}
b.stopMu.Unlock()
confirm := make(chan struct{})
b.stop <- confirm
<-confirm
@ -304,7 +283,6 @@ func (b *Bot) Send(to Recipient, what interface{}, opts ...interface{}) (*Messag
}
// SendAlbum sends multiple instances of media as a single message.
// To include the caption, make sure the first Inputtable of an album has it.
// From all existing options, it only supports tele.Silent.
func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message, error) {
if to == nil {
@ -316,8 +294,21 @@ func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message,
files := make(map[string]File)
for i, x := range a {
repr := x.MediaFile().process(strconv.Itoa(i), files)
if repr == "" {
var (
repr string
data []byte
file = x.MediaFile()
)
switch {
case file.InCloud():
repr = file.FileID
case file.FileURL != "":
repr = file.FileURL
case file.OnDisk() || file.FileReader != nil:
repr = "attach://" + strconv.Itoa(i)
files[strconv.Itoa(i)] = *file
default:
return nil, fmt.Errorf("telebot: album entry #%d does not exist", i)
}
@ -330,7 +321,7 @@ func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message,
im.ParseMode = sendOpts.ParseMode
}
data, _ := json.Marshal(im)
data, _ = json.Marshal(im)
media[i] = string(data)
}
@ -411,17 +402,6 @@ func (b *Bot) Forward(to Recipient, msg Editable, opts ...interface{}) (*Message
return extractMessage(data)
}
// 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) ForwardMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) {
if to == nil {
return nil, ErrBadRecipient
}
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).
//
// This function will panic upon nil Editable.
@ -448,20 +428,6 @@ func (b *Bot) Copy(to Recipient, msg Editable, options ...interface{}) (*Message
return extractMessage(data)
}
// 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) CopyMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) {
if to == nil {
return nil, ErrBadRecipient
}
return b.forwardCopyMany(to, msgs, "copyMessages", opts...)
}
// Edit is magic, it lets you change already sent message.
// This function will panic upon nil Editable.
//
@ -704,16 +670,6 @@ func (b *Bot) Delete(msg Editable) error {
return err
}
// DeleteMany deletes multiple messages simultaneously.
// If some of the specified messages can't be found, they are skipped.
func (b *Bot) DeleteMany(msgs []Editable) error {
params := make(map[string]string)
embedMessages(params, msgs)
_, err := b.Raw("deleteMessages", params)
return err
}
// Notify updates the chat action for recipient.
//
// Chat action is a status message that recipient would see where
@ -723,7 +679,7 @@ func (b *Bot) DeleteMany(msgs []Editable) error {
//
// Currently, Telegram supports only a narrow range of possible
// actions, these are aligned as constants of this package.
func (b *Bot) Notify(to Recipient, action ChatAction, threadID ...int) error {
func (b *Bot) Notify(to Recipient, action ChatAction) error {
if to == nil {
return ErrBadRecipient
}
@ -733,10 +689,6 @@ func (b *Bot) Notify(to Recipient, action ChatAction, threadID ...int) error {
"action": string(action),
}
if len(threadID) > 0 {
params["message_thread_id"] = strconv.Itoa(threadID[0])
}
_, err := b.Raw("sendChatAction", params)
return err
}
@ -989,7 +941,7 @@ func (b *Bot) StopPoll(msg Editable, opts ...interface{}) (*Poll, error) {
}
// Leave makes bot leave a group, supergroup or channel.
func (b *Bot) Leave(chat Recipient) error {
func (b *Bot) Leave(chat *Chat) error {
params := map[string]string{
"chat_id": chat.Recipient(),
}
@ -1019,7 +971,7 @@ func (b *Bot) Pin(msg Editable, opts ...interface{}) error {
// Unpin unpins a message in a supergroup or a channel.
// It supports tb.Silent option.
func (b *Bot) Unpin(chat Recipient, messageID ...int) error {
func (b *Bot) Unpin(chat *Chat, messageID ...int) error {
params := map[string]string{
"chat_id": chat.Recipient(),
}
@ -1033,7 +985,7 @@ func (b *Bot) Unpin(chat Recipient, messageID ...int) error {
// UnpinAll unpins all messages in a supergroup or a channel.
// It supports tb.Silent option.
func (b *Bot) UnpinAll(chat Recipient) error {
func (b *Bot) UnpinAll(chat *Chat) error {
params := map[string]string{
"chat_id": chat.Recipient(),
}
@ -1194,89 +1146,3 @@ func (b *Bot) Close() (bool, error) {
return resp.Result, nil
}
// BotInfo represents a single object of BotName, BotDescription, BotShortDescription instances.
type BotInfo struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
ShortDescription string `json:"short_description,omitempty"`
}
// SetMyName change's the bot name.
func (b *Bot) SetMyName(name, language string) error {
params := map[string]string{
"name": name,
"language_code": language,
}
_, err := b.Raw("setMyName", params)
return err
}
// MyName returns the current bot name for the given user language.
func (b *Bot) MyName(language string) (*BotInfo, error) {
return b.botInfo(language, "getMyName")
}
// SetMyDescription change's the bot description, which is shown in the chat
// with the bot if the chat is empty.
func (b *Bot) SetMyDescription(desc, language string) error {
params := map[string]string{
"description": desc,
"language_code": language,
}
_, err := b.Raw("setMyDescription", params)
return err
}
// MyDescription the current bot description for the given user language.
func (b *Bot) MyDescription(language string) (*BotInfo, error) {
return b.botInfo(language, "getMyDescription")
}
// SetMyShortDescription change's the bot short description, which is shown on
// the bot's profile page and is sent together with the link when users share the bot.
func (b *Bot) SetMyShortDescription(desc, language string) error {
params := map[string]string{
"short_description": desc,
"language_code": language,
}
_, err := b.Raw("setMyShortDescription", params)
return err
}
// MyShortDescription the current bot short description for the given user language.
func (b *Bot) MyShortDescription(language string) (*BotInfo, error) {
return b.botInfo(language, "getMyShortDescription")
}
func (b *Bot) botInfo(language, key string) (*BotInfo, error) {
params := map[string]string{
"language_code": language,
}
data, err := b.Raw(key, params)
if err != nil {
return nil, err
}
var resp struct {
Result *BotInfo
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}
func extractEndpoint(endpoint interface{}) string {
switch end := endpoint.(type) {
case string:
return end
case CallbackEndpoint:
return end.CallbackUnique()
}
return ""
}

@ -24,9 +24,6 @@ var (
b, _ = newTestBot() // cached bot instance to avoid getMe method flooding
to = &Chat{ID: chatID} // to chat recipient for send and edit methods
user = &User{ID: userID} // to user recipient for some special cases
logo = FromURL("https://telegra.ph/file/c95b8fe46dd3df15d12e5.png")
thumb = FromURL("https://telegra.ph/file/fe28e378784b3a4e367fb.png")
)
func defaultSettings() Settings {
@ -505,7 +502,7 @@ func TestBot(t *testing.T) {
assert.Equal(t, ErrBadRecipient, err)
photo := &Photo{
File: logo,
File: FromURL("https://telegra.ph/file/65c5237b040ebf80ec278.jpg"),
Caption: t.Name(),
}
var msg *Message

@ -26,15 +26,6 @@ type Callback struct {
// a bad client can send arbitrary data in this field.
Data string `json:"data"`
// ChatInstance is a global identifier, uniquely corresponding to
// the chat to which the message with the callback button was sent.
ChatInstance string `json:"chat_instance"`
// GameShortName is a unique identifier of the game for which a URL
// is requested from the bot when a user presses the Play button of
// that game. GameShortName may be empty
GameShortName string `json:"game_short_name"`
// Unique displays an unique of the button from which the
// callback was fired. Sets immediately before the handling,
// while the Data field stores only with payload.

@ -10,16 +10,13 @@ import (
type User struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
IsForum bool `json:"is_forum"`
Username string `json:"username"`
LanguageCode string `json:"language_code"`
IsBot bool `json:"is_bot"`
IsPremium bool `json:"is_premium"`
AddedToMenu bool `json:"added_to_attachment_menu"`
Usernames []string `json:"active_usernames"`
CustomEmojiStatus string `json:"emoji_status_custom_emoji_id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Username string `json:"username"`
LanguageCode string `json:"language_code"`
IsBot bool `json:"is_bot"`
IsPremium bool `json:"is_premium"`
AddedToMenu bool `json:"added_to_attachment_menu"`
// Returns only in getMe
CanJoinGroups bool `json:"can_join_groups"`
@ -47,32 +44,20 @@ type Chat struct {
Username string `json:"username"`
// Returns only in getChat
Bio string `json:"bio,omitempty"`
Photo *ChatPhoto `json:"photo,omitempty"`
Description string `json:"description,omitempty"`
InviteLink string `json:"invite_link,omitempty"`
PinnedMessage *Message `json:"pinned_message,omitempty"`
Permissions *Rights `json:"permissions,omitempty"`
Reactions []Reaction `json:"available_reactions"`
SlowMode int `json:"slow_mode_delay,omitempty"`
StickerSet string `json:"sticker_set_name,omitempty"`
CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"`
CustomEmojiSetName string `json:"custom_emoji_sticker_set_name"`
LinkedChatID int64 `json:"linked_chat_id,omitempty"`
ChatLocation *ChatLocation `json:"location,omitempty"`
Private bool `json:"has_private_forwards,omitempty"`
Protected bool `json:"has_protected_content,omitempty"`
NoVoiceAndVideo bool `json:"has_restricted_voice_and_video_messages"`
HasHiddenMembers bool `json:"has_hidden_members,omitempty"`
AggressiveAntiSpam bool `json:"has_aggressive_anti_spam_enabled,omitempty"`
CustomEmojiID string `json:"emoji_status_custom_emoji_id"`
EmojiExpirationUnixtime int64 `json:"emoji_status_expiration_date"`
BackgroundEmojiID string `json:"background_custom_emoji_id"`
AccentColorID int `json:"accent_color_id"`
ProfileAccentColorID int `json:"profile_accent_color_id"`
ProfileBackgroundEmojiID string `json:"profile_background_custom_emoji_id"`
HasVisibleHistory bool `json:"has_visible_history"`
UnrestrictBoosts int `json:"unrestrict_boost_count"`
Bio string `json:"bio,omitempty"`
Photo *ChatPhoto `json:"photo,omitempty"`
Description string `json:"description,omitempty"`
InviteLink string `json:"invite_link,omitempty"`
PinnedMessage *Message `json:"pinned_message,omitempty"`
Permissions *Rights `json:"permissions,omitempty"`
SlowMode int `json:"slow_mode_delay,omitempty"`
StickerSet string `json:"sticker_set_name,omitempty"`
CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"`
LinkedChatID int64 `json:"linked_chat_id,omitempty"`
ChatLocation *ChatLocation `json:"location,omitempty"`
Private bool `json:"has_private_forwards,omitempty"`
Protected bool `json:"has_protected_content,omitempty"`
NoVoiceAndVideo bool `json:"has_restricted_voice_and_video_messages"`
}
// Recipient returns chat ID (see Recipient interface).
@ -116,7 +101,6 @@ type ChatMember struct {
Role MemberStatus `json:"status"`
Title string `json:"custom_title"`
Anonymous bool `json:"is_anonymous"`
Member bool `json:"is_member,omitempty"`
// Date when restrictions will be lifted for the user, unix time.
//
@ -164,9 +148,6 @@ type ChatMemberUpdate struct {
// (Optional) InviteLink which was used by the user to
// join the chat; for joining by invite link events only.
InviteLink *ChatInviteLink `json:"invite_link"`
// (Optional) True, if the user joined the chat via a chat folder invite link.
ViaFolderLink bool `json:"via_chat_folder_invite_link"`
}
// Time returns the moment of the change in local time.
@ -181,13 +162,14 @@ func (c *ChatMemberUpdate) Time() time.Time {
//
// Example:
//
// group := tele.ChatID(-100756389456)
// b.Send(group, "Hello!")
// group := tele.ChatID(-100756389456)
// b.Send(group, "Hello!")
//
// type Config struct {
// AdminGroup tele.ChatID `json:"admin_group"`
// }
// b.Send(conf.AdminGroup, "Hello!")
//
// type Config struct {
// AdminGroup tele.ChatID `json:"admin_group"`
// }
// b.Send(conf.AdminGroup, "Hello!")
type ChatID int64
// Recipient returns chat ID (see Recipient interface).
@ -203,12 +185,6 @@ type ChatJoinRequest struct {
// Sender is the user that sent the join request.
Sender *User `json:"from"`
// UserChatID is an ID of a private chat with the user
// who sent the join request. The bot can use this ID
// for 5 minutes to send messages until the join request
// is processed, assuming no other administrator contacted the user.
UserChatID int64 `json:"user_chat_id"`
// Unixtime, use ChatJoinRequest.Time() to get time.Time.
Unixtime int64 `json:"date"`
@ -253,14 +229,6 @@ type ChatInviteLink struct {
PendingCount int `json:"pending_join_request_count"`
}
type Story struct {
// Unique identifier for the story in the chat
ID int `json:"id"`
// Chat that posted the story
Poster *Chat `json:"chat"`
}
// ExpireDate returns the moment of the link expiration in local time.
func (c *ChatInviteLink) ExpireDate() time.Time {
return time.Unix(c.ExpireUnixtime, 0)
@ -271,11 +239,6 @@ func (r ChatJoinRequest) Time() time.Time {
return time.Unix(r.Unixtime, 0)
}
// Time returns the moment of the emoji status expiration.
func (c *Chat) Time() time.Time {
return time.Unix(c.EmojiExpirationUnixtime, 0)
}
// InviteLink should be used to export chat's invite link.
func (b *Bot) InviteLink(chat *Chat) (string, error) {
params := map[string]string{
@ -464,9 +427,6 @@ func (b *Bot) SetGroupPermissions(chat *Chat, perms Rights) error {
"chat_id": chat.Recipient(),
"permissions": perms,
}
if perms.Independent {
params["use_independent_chat_permissions"] = true
}
_, err := b.Raw("setChatPermissions", params)
return err

@ -46,21 +46,12 @@ type Context interface {
// ChatMember returns chat member changes.
ChatMember() *ChatMemberUpdate
// ChatJoinRequest returns the chat join request.
// ChatJoinRequest returns cha
ChatJoinRequest() *ChatJoinRequest
// Migration returns both migration from and to chat IDs.
Migration() (int64, int64)
// 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
@ -158,12 +149,6 @@ type Context interface {
// See Respond from bot.go.
Respond(resp ...*CallbackResponse) error
// RespondText sends a popup response for the current callback query.
RespondText(text string) error
// RespondAlert sends an alert response for the current callback query.
RespondAlert(text string) error
// Get retrieves data from the context.
Get(key string) interface{}
@ -255,30 +240,6 @@ func (c *nativeContext) Migration() (int64, int64) {
return c.u.Message.MigrateFrom, c.u.Message.MigrateTo
}
func (c *nativeContext) Topic() *Topic {
m := c.u.Message
if m == nil {
return nil
}
switch {
case m.TopicCreated != nil:
return m.TopicCreated
case m.TopicReopened != nil:
return m.TopicReopened
case m.TopicEdited != nil:
return m.TopicEdited
}
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,16 +262,9 @@ func (c *nativeContext) Sender() *User {
return c.u.ChatMember.Sender
case c.u.ChatJoinRequest != nil:
return c.u.ChatJoinRequest.Sender
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
}
default:
return nil
}
return nil
}
func (c *nativeContext) Chat() *Chat {
@ -382,7 +336,7 @@ func (c *nativeContext) Args() []string {
case c.u.Message != nil:
payload := strings.Trim(c.u.Message.Payload, " ")
if payload != "" {
return strings.Fields(payload)
return strings.Split(payload, " ")
}
case c.u.Callback != nil:
return strings.Split(c.u.Callback.Data, "|")
@ -508,14 +462,6 @@ func (c *nativeContext) Respond(resp ...*CallbackResponse) error {
return c.b.Respond(c.u.Callback, resp...)
}
func (c *nativeContext) RespondText(text string) error {
return c.Respond(&CallbackResponse{Text: text})
}
func (c *nativeContext) RespondAlert(text string) error {
return c.Respond(&CallbackResponse{Text: text, ShowAlert: true})
}
func (c *nativeContext) Answer(resp *QueryResponse) error {
if c.u.Query == nil {
return errors.New("telebot: context inline query is nil")

@ -1,7 +1,6 @@
package telebot
import (
"errors"
"fmt"
"strings"
)
@ -78,7 +77,6 @@ var (
// Bad request errors
var (
ErrBadButtonData = NewError(400, "Bad Request: BUTTON_DATA_INVALID")
ErrBadUserID = NewError(400, "Bad Request: USER_ID_INVALID")
ErrBadPollOptions = NewError(400, "Bad Request: expected an Array of String as options")
ErrBadURLContent = NewError(400, "Bad Request: failed to get HTTP URL content")
ErrCantEditMessage = NewError(400, "Bad Request: message can't be edited")
@ -119,10 +117,6 @@ var (
ErrWrongTypeOfContent = NewError(400, "Bad Request: wrong type of the web page content")
ErrWrongURL = NewError(400, "Bad Request: wrong HTTP URL specified")
ErrForwardMessage = NewError(400, "Bad Request: administrators of the chat restricted message forwarding")
ErrUserAlreadyParticipant = NewError(400, "Bad Request: USER_ALREADY_PARTICIPANT", "User is already a participant")
ErrHideRequesterMissing = NewError(400, "Bad Request: HIDE_REQUESTER_MISSING")
ErrChannelsTooMuch = NewError(400, "Bad Request: CHANNELS_TOO_MUCH")
ErrChannelsTooMuchUser = NewError(400, "Bad Request: USER_CHANNELS_TOO_MUCH")
)
// Forbidden errors
@ -133,7 +127,6 @@ var (
ErrKickedFromChannel = NewError(403, "Forbidden: bot was kicked from the channel chat")
ErrNotStartedByUser = NewError(403, "Forbidden: bot can't initiate conversation with a user")
ErrUserIsDeactivated = NewError(403, "Forbidden: user is deactivated")
ErrNotChannelMember = NewError(403, "Forbidden: bot is not a member of the channel chat")
)
// Err returns Error instance by given description.
@ -149,8 +142,6 @@ func Err(s string) error {
return ErrInternal
case ErrBadButtonData.ʔ():
return ErrBadButtonData
case ErrBadUserID.ʔ():
return ErrBadUserID
case ErrBadPollOptions.ʔ():
return ErrBadPollOptions
case ErrBadURLContent.ʔ():
@ -243,26 +234,11 @@ func Err(s string) error {
return ErrUserIsDeactivated
case ErrForwardMessage.ʔ():
return ErrForwardMessage
case ErrUserAlreadyParticipant.ʔ():
return ErrUserAlreadyParticipant
case ErrHideRequesterMissing.ʔ():
return ErrHideRequesterMissing
case ErrChannelsTooMuch.ʔ():
return ErrChannelsTooMuch
case ErrChannelsTooMuchUser.ʔ():
return ErrChannelsTooMuchUser
case ErrNotChannelMember.ʔ():
return ErrNotChannelMember
default:
return nil
}
}
// ErrIs checks if the error with given description matches an error err.
func ErrIs(s string, err error) bool {
return errors.Is(err, Err(s))
}
// wrapError returns new wrapped telebot-related error.
func wrapError(err error) error {
return fmt.Errorf("telebot: %w", err)

@ -1,103 +0,0 @@
package telebot
import "time"
// Giveaway represents a message about a scheduled giveaway.
type Giveaway struct {
// The list of chats which the user must join to participate in the giveaway.
Chats []Chat `json:"chats"`
// Point in time (Unix timestamp) when winners of the giveaway will be selected.
SelectionUnixtime int64 `json:"winners_selection_date"`
// The number of users which are supposed to be selected as winners of the giveaway.
WinnerCount int `json:"winner_count"`
// (Optional) True, if only users who join the chats after the giveaway
// started should be eligible to win.
OnlyNewMembers bool `json:"only_new_members"`
// (Optional) True, if the list of giveaway winners will be visible to everyone.
HasPublicWinners bool `json:"has_public_winners"`
// (Optional) Description of additional giveaway prize.
PrizeDescription string `json:"prize_description"`
// (Optional) A list of two-letter ISO 3166-1 alpha-2 country codes indicating
// the countries from which eligible users for the giveaway must come.
// If empty, then all users can participate in the giveaway. Users with a phone number
// that was bought on Fragment can always participate in giveaways.
CountryCodes []string `json:"country_codes"`
// (Optional) The number of months the Telegram Premium subscription won from
// the giveaway will be active for.
PremiumMonthCount int `json:"premium_subscription_month_count"`
}
// SelectionDate returns the moment of when winners of the giveaway were selected in local time.
func (g *Giveaway) SelectionDate() time.Time {
return time.Unix(g.SelectionUnixtime, 0)
}
// GiveawayWinners object represents a message about the completion of a
// giveaway with public winners.
type GiveawayWinners struct {
// The chat that created the giveaway.
Chat *Chat `json:"chat"`
// Identifier of the message with the giveaway in the chat.
MessageID int `json:"message_id"`
// Point in time (Unix timestamp) when winners of the giveaway were selected.
SelectionUnixtime int64 `json:"winners_selection_date"`
// The number of users which are supposed to be selected as winners of the giveaway.
WinnerCount int `json:"winner_count"`
// List of up to 100 winners of the giveaway.
Winners []User `json:"winners"`
// (Optional) The number of other chats the user had to join in order
// to be eligible for the giveaway.
AdditionalChats int `json:"additional_chat_count"`
// (Optional) The number of months the Telegram Premium subscription won from
// the giveaway will be active for.
PremiumMonthCount int `json:"premium_subscription_month_count"`
// (Optional) Number of undistributed prizes.
UnclaimedPrizes int `json:"unclaimed_prize_count"`
// (Optional) True, if only users who had joined the chats after the giveaway started
// were eligible to win.
OnlyNewMembers bool `json:"only_new_members"`
// (Optional) True, if the giveaway was canceled because the payment for it was refunded.
Refunded bool `json:"was_refunded"`
// (Optional) Description of additional giveaway prize.
PrizeDescription string `json:"prize_description"`
}
// SelectionDate returns the moment of when winners of the giveaway
// were selected in local time.
func (g *GiveawayWinners) SelectionDate() time.Time {
return time.Unix(g.SelectionUnixtime, 0)
}
// GiveawayCreated represents a service message about the creation of a scheduled giveaway.
// Currently holds no information.
type GiveawayCreated struct{}
// GiveawayCompleted represents a service message about the completion of a
// giveaway without public winners.
type GiveawayCompleted struct {
// Number of winners in the giveaway.
WinnerCount int `json:"winner_count"`
// (Optional) Number of undistributed prizes.
UnclaimedPrizes int `json:"unclaimed_prize_count"`
// (Optional) Message with the giveaway that was completed, if it wasn't deleted.
Message *Message `json:"giveaway_message"`
}

@ -1,6 +1,6 @@
module gopkg.in/telebot.v3
go 1.16
go 1.13
require (
github.com/goccy/go-yaml v1.9.5

@ -61,46 +61,6 @@ type QueryResponse struct {
// (Optional) Parameter for the start message sent to the bot when user
// presses the switch button.
SwitchPMParameter string `json:"switch_pm_parameter,omitempty"`
// (Optional) A JSON-serialized object describing a button to be shown
// above inline query results.
Button *QueryResponseButton `json:"button,omitempty"`
}
// QueryResponseButton represents a button to be shown above inline query results.
// You must use exactly one of the optional fields.
type QueryResponseButton struct {
// Label text on the button
Text string `json:"text"`
// (Optional) Description of the Web App that will be launched when the
// user presses the button. The Web App will be able to switch back to the
// inline mode using the method switchInlineQuery inside the Web App.
WebApp *WebApp `json:"web_app"`
// (Optional) Deep-linking parameter for the /start message sent to the bot
// when a user presses the button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.
Start string `json:"start_parameter"`
}
// SwitchInlineQuery represents an inline button that switches the current
// user to inline mode in a chosen chat, with an optional default inline query.
type SwitchInlineQuery struct {
// (Optional) The default inline query to be inserted in the input field.
// If left empty, only the bot's username will be inserted.
Query string `json:"query"`
// (Optional) True, if private chats with users can be chosen.
AllowUserChats bool `json:"allow_user_chats"`
// (Optional) True, if private chats with bots can be chosen.
AllowBotChats bool `json:"allow_bot_chats"`
// (Optional) True, if group and supergroup chats can be chosen.
AllowGroupChats bool `json:"allow_group_chats"`
// (Optional) True, if channel chats can be chosen.
AllowChannelChats bool `json:"allow_channel_chats"`
}
// InlineResult represents a result of an inline query that was chosen
@ -133,9 +93,9 @@ type Results []Result
// MarshalJSON makes sure IQRs have proper IDs and Type variables set.
func (results Results) MarshalJSON() ([]byte, error) {
for i, result := range results {
for _, result := range results {
if result.ResultID() == "" {
result.SetResultID(fmt.Sprintf("%d", &results[i]))
result.SetResultID(fmt.Sprintf("%d", &result))
}
if err := inferIQR(result); err != nil {
return nil, err
@ -171,8 +131,6 @@ func inferIQR(result Result) error {
r.Type = "voice"
case *StickerResult:
r.Type = "sticker"
case *GameResult:
r.Type = "game"
default:
return fmt.Errorf("telebot: result %v is not supported", result)
}

@ -60,16 +60,6 @@ func (r *ResultBase) Process(b *Bot) {
}
}
// GameResult represents a game. Game is a content type
// supported by Telegram, which can be sent back to the
// user as a result for an inline query.
type GameResult struct {
ResultBase
// ShortName is a unique identifier of the game.
ShortName string `json:"game_short_name"`
}
// ArticleResult represents a link to an article or web page.
type ArticleResult struct {
ResultBase
@ -91,13 +81,13 @@ type ArticleResult struct {
Description string `json:"description,omitempty"`
// Optional. URL of the thumbnail for the result.
ThumbURL string `json:"thumbnail_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
// Optional. Width of the thumbnail for the result.
ThumbWidth int `json:"thumbnail_width,omitempty"`
ThumbWidth int `json:"thumb_width,omitempty"`
// Optional. Height of the thumbnail for the result.
ThumbHeight int `json:"thumbnail_height,omitempty"`
ThumbHeight int `json:"thumb_height,omitempty"`
}
// AudioResult represents a link to an mp3 audio file.
@ -140,13 +130,13 @@ type ContactResult struct {
LastName string `json:"last_name,omitempty"`
// Optional. URL of the thumbnail for the result.
ThumbURL string `json:"thumbnail_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
// Optional. Width of the thumbnail for the result.
ThumbWidth int `json:"thumbnail_width,omitempty"`
ThumbWidth int `json:"thumb_width,omitempty"`
// Optional. Height of the thumbnail for the result.
ThumbHeight int `json:"thumbnail_height,omitempty"`
ThumbHeight int `json:"thumb_height,omitempty"`
}
// DocumentResult represents a link to a file.
@ -170,13 +160,13 @@ type DocumentResult struct {
Description string `json:"description,omitempty"`
// Optional. URL of the thumbnail (jpeg only) for the file.
ThumbURL string `json:"thumbnail_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
// Optional. Width of the thumbnail for the result.
ThumbWidth int `json:"thumbnail_width,omitempty"`
ThumbWidth int `json:"thumb_width,omitempty"`
// Optional. Height of the thumbnail for the result.
ThumbHeight int `json:"thumbnail_height,omitempty"`
ThumbHeight int `json:"thumb_height,omitempty"`
// If Cache != "", it'll be used instead
Cache string `json:"document_file_id,omitempty"`
@ -199,11 +189,11 @@ type GifResult struct {
Duration int `json:"gif_duration,omitempty"`
// URL of the static thumbnail for the result (jpeg or gif).
ThumbURL string `json:"thumbnail_url"`
ThumbURL string `json:"thumb_url"`
// Optional. MIME type of the thumbnail, must be one of
// “image/jpeg”, “image/gif”, or “video/mp4”.
ThumbMIME string `json:"thumbnail_mime_type,omitempty"`
ThumbMIME string `json:"thumb_mime_type,omitempty"`
// Optional. Title for the result.
Title string `json:"title,omitempty"`
@ -225,7 +215,7 @@ type LocationResult struct {
Title string `json:"title"`
// Optional. Url of the thumbnail for the result.
ThumbURL string `json:"thumbnail_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
}
// Mpeg4GifResult represents a link to a video animation
@ -246,11 +236,11 @@ type Mpeg4GifResult struct {
Duration int `json:"mpeg4_duration,omitempty"`
// URL of the static thumbnail (jpeg or gif) for the result.
ThumbURL string `json:"thumbnail_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
// Optional. MIME type of the thumbnail, must be one of
// “image/jpeg”, “image/gif”, or “video/mp4”.
ThumbMIME string `json:"thumbnail_mime_type,omitempty"`
ThumbMIME string `json:"thumb_mime_type,omitempty"`
// Optional. Title for the result.
Title string `json:"title,omitempty"`
@ -286,7 +276,7 @@ type PhotoResult struct {
Caption string `json:"caption,omitempty"`
// URL of the thumbnail for the photo.
ThumbURL string `json:"thumbnail_url"`
ThumbURL string `json:"thumb_url"`
// If Cache != "", it'll be used instead
Cache string `json:"photo_file_id,omitempty"`
@ -308,13 +298,13 @@ type VenueResult struct {
FoursquareID string `json:"foursquare_id,omitempty"`
// Optional. URL of the thumbnail for the result.
ThumbURL string `json:"thumbnail_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
// Optional. Width of the thumbnail for the result.
ThumbWidth int `json:"thumbnail_width,omitempty"`
ThumbWidth int `json:"thumb_width,omitempty"`
// Optional. Height of the thumbnail for the result.
ThumbHeight int `json:"thumbnail_height,omitempty"`
ThumbHeight int `json:"thumb_height,omitempty"`
}
// VideoResult represents a link to a page containing an embedded
@ -329,7 +319,7 @@ type VideoResult struct {
MIME string `json:"mime_type"`
// URL of the thumbnail (jpeg only) for the video.
ThumbURL string `json:"thumbnail_url"`
ThumbURL string `json:"thumb_url"`
// Title for the result.
Title string `json:"title"`

@ -12,12 +12,12 @@ type InputTextMessageContent struct {
// Text of the message to be sent, 1-4096 characters.
Text string `json:"message_text"`
// (Optional) Send Markdown or HTML, if you want Telegram apps to show
// Optional. Send Markdown or HTML, if you want Telegram apps to show
// bold, italic, fixed-width text or inline URLs in your bot's message.
ParseMode string `json:"parse_mode,omitempty"`
// (Optional) Link preview generation options for the message.
PreviewOptions *PreviewOptions `json:"link_preview_options,omitempty"`
// Optional. Disables link previews for links in the sent message.
DisablePreview bool `json:"disable_web_page_preview"`
}
func (input *InputTextMessageContent) IsInputMessageContent() bool {

@ -72,5 +72,5 @@ results:
id: '{{ .ID }}'
title: '{{ .Title }}'
description: '{{ .Description }}'
thumbnail_url: '{{ .PreviewURL }}'
thumb_url: '{{ .PreviewURL }}'
message_text: '{{ text `article_message` }}'

@ -3,9 +3,8 @@ package layout
import (
"bytes"
"encoding/json"
"io/fs"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"text/template"
@ -70,13 +69,7 @@ type (
// New parses the given layout file.
func New(path string, funcs ...template.FuncMap) (*Layout, error) {
return NewFromFS(os.DirFS("."), path, funcs...)
}
// NewFromFS parses the layout from the given fs.FS. It allows to read layout
// from the go:embed filesystem.
func NewFromFS(fsys fs.FS, path string, funcs ...template.FuncMap) (*Layout, error) {
data, err := fs.ReadFile(fsys, path)
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
@ -128,10 +121,10 @@ var builtinFuncs = template.FuncMap{
// webhook: (or webhook settings)
//
// Usage:
//
// lt, err := layout.New("bot.yml")
// b, err := tele.NewBot(lt.Settings())
// // That's all!
//
func (lt *Layout) Settings() tele.Settings {
if lt.pref == nil {
panic("telebot/layout: settings is empty")
@ -190,20 +183,16 @@ func (lt *Layout) Commands() (cmds []tele.Command) {
// used in b.SetCommands later.
//
// Example of bot.yml:
//
// commands:
// /start: '{{ text `cmdStart` }}'
//
// en.yml:
//
// cmdStart: Start the bot
//
// ru.yml:
//
// cmdStart: Запуск бота
//
// Usage:
//
// b.SetCommands(lt.CommandsLocale("en"), "en")
// b.SetCommands(lt.CommandsLocale("ru"), "ru")
func (lt *Layout) CommandsLocale(locale string, args ...interface{}) (cmds []tele.Command) {
@ -237,14 +226,13 @@ func (lt *Layout) CommandsLocale(locale string, args ...interface{}) (cmds []tel
// The given optional argument will be passed to the template engine.
//
// Example of en.yml:
//
// start: Hi, {{.FirstName}}!
//
// Usage:
//
// func onStart(c tele.Context) error {
// return c.Send(lt.Text(c, "start", c.Sender()))
// }
//
func (lt *Layout) Text(c tele.Context, k string, args ...interface{}) string {
locale, ok := lt.Locale(c)
if !ok {
@ -278,9 +266,9 @@ func (lt *Layout) TextLocale(locale, k string, args ...interface{}) string {
// Callback returns a callback endpoint used to handle buttons.
//
// Example:
//
// // Handling settings button
// b.Handle(lt.Callback("settings"), onSettings)
//
func (lt *Layout) Callback(k string) tele.CallbackEndpoint {
btn, ok := lt.buttons[k]
if !ok {
@ -299,7 +287,6 @@ func (lt *Layout) Callback(k string) tele.CallbackEndpoint {
// text: Item #{{.Number}}
//
// Usage:
//
// btns := make([]tele.Btn, len(items))
// for i, item := range items {
// btns[i] = lt.Button(c, "item", struct {
@ -314,6 +301,7 @@ func (lt *Layout) Callback(k string) tele.CallbackEndpoint {
// m := b.NewMarkup()
// m.Inline(m.Row(btns...))
// // Your generated markup is ready.
//
func (lt *Layout) Button(c tele.Context, k string, args ...interface{}) *tele.Btn {
locale, ok := lt.Locale(c)
if !ok {
@ -372,13 +360,13 @@ func (lt *Layout) ButtonLocale(locale, k string, args ...interface{}) *tele.Btn
// - [settings]
//
// Usage:
//
// func onStart(c tele.Context) error {
// return c.Send(
// lt.Text(c, "start"),
// lt.Markup(c, "menu"),
// )
// }
//
func (lt *Layout) Markup(c tele.Context, k string, args ...interface{}) *tele.ReplyMarkup {
locale, ok := lt.Locale(c)
if !ok {
@ -439,7 +427,6 @@ func (lt *Layout) MarkupLocale(locale, k string, args ...interface{}) *tele.Repl
// thumb_url: '{{ .PreviewURL }}'
//
// Usage:
//
// func onQuery(c tele.Context) error {
// results := make(tele.Results, len(articles))
// for i, article := range articles {
@ -450,6 +437,7 @@ func (lt *Layout) MarkupLocale(locale, k string, args ...interface{}) *tele.Repl
// CacheTime: 100,
// })
// }
//
func (lt *Layout) Result(c tele.Context, k string, args ...interface{}) tele.Result {
locale, ok := lt.Locale(c)
if !ok {

@ -1,7 +1,6 @@
package layout
import (
"embed"
"os"
"testing"
"time"
@ -10,9 +9,6 @@ import (
tele "gopkg.in/telebot.v3"
)
//go:embed *
var fsys embed.FS
func TestLayout(t *testing.T) {
os.Setenv("TOKEN", "TEST")
@ -21,16 +17,10 @@ func TestLayout(t *testing.T) {
t.Fatal(err)
}
ltfs, err := NewFromFS(fsys, "example.yml")
if err != nil {
t.Fatal(err)
}
pref := lt.Settings()
assert.Equal(t, "TEST", pref.Token)
assert.Equal(t, "html", pref.ParseMode)
assert.Equal(t, &tele.LongPoller{}, pref.Poller)
assert.Equal(t, pref, ltfs.Settings())
assert.ElementsMatch(t, []tele.Command{{
Text: "start",

@ -53,9 +53,6 @@ type ReplyMarkup struct {
// Placeholder will be shown in the input field when the reply is active.
Placeholder string `json:"input_field_placeholder,omitempty"`
// IsPersistent allows to control when the keyboard is shown.
IsPersistent bool `json:"is_persistent,omitempty"`
}
func (r *ReplyMarkup) copy() *ReplyMarkup {
@ -82,19 +79,17 @@ func (r *ReplyMarkup) copy() *ReplyMarkup {
// Btn is a constructor button, which will later become either a reply, or an inline button.
type Btn struct {
Unique string `json:"unique,omitempty"`
Text string `json:"text,omitempty"`
URL string `json:"url,omitempty"`
Data string `json:"callback_data,omitempty"`
InlineQuery string `json:"switch_inline_query,omitempty"`
InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"`
Login *Login `json:"login_url,omitempty"`
WebApp *WebApp `json:"web_app,omitempty"`
Contact bool `json:"request_contact,omitempty"`
Location bool `json:"request_location,omitempty"`
Poll PollType `json:"request_poll,omitempty"`
User *ReplyRecipient `json:"request_user,omitempty"`
Chat *ReplyRecipient `json:"request_chat,omitempty"`
Unique string `json:"unique,omitempty"`
Text string `json:"text,omitempty"`
URL string `json:"url,omitempty"`
Data string `json:"callback_data,omitempty"`
InlineQuery string `json:"switch_inline_query,omitempty"`
InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"`
Contact bool `json:"request_contact,omitempty"`
Location bool `json:"request_location,omitempty"`
Poll PollType `json:"request_poll,omitempty"`
Login *Login `json:"login_url,omitempty"`
WebApp *WebApp `json:"web_app,omitempty"`
}
// Row represents an array of buttons, a row.
@ -111,6 +106,7 @@ func (r *ReplyMarkup) Row(many ...Btn) Row {
//
// `Split(3, []Btn{six buttons...}) -> [[1, 2, 3], [4, 5, 6]]`
// `Split(2, []Btn{six buttons...}) -> [[1, 2],[3, 4],[5, 6]]`
//
func (r *ReplyMarkup) Split(max int, btns []Btn) []Row {
rows := make([]Row, (max-1+len(btns))/max)
for i, b := range btns {
@ -162,6 +158,18 @@ func (r *ReplyMarkup) Text(text string) Btn {
return Btn{Text: text}
}
func (r *ReplyMarkup) Contact(text string) Btn {
return Btn{Contact: true, Text: text}
}
func (r *ReplyMarkup) Location(text string) Btn {
return Btn{Location: true, Text: text}
}
func (r *ReplyMarkup) Poll(text string, poll PollType) Btn {
return Btn{Poll: poll, Text: text}
}
func (r *ReplyMarkup) Data(text, unique string, data ...string) Btn {
return Btn{
Unique: unique,
@ -182,26 +190,6 @@ func (r *ReplyMarkup) QueryChat(text, query string) Btn {
return Btn{Text: text, InlineQueryChat: query}
}
func (r *ReplyMarkup) Contact(text string) Btn {
return Btn{Contact: true, Text: text}
}
func (r *ReplyMarkup) Location(text string) Btn {
return Btn{Location: true, Text: text}
}
func (r *ReplyMarkup) Poll(text string, poll PollType) Btn {
return Btn{Poll: poll, Text: text}
}
func (r *ReplyMarkup) User(text string, user *ReplyRecipient) Btn {
return Btn{Text: text, User: user}
}
func (r *ReplyMarkup) Chat(text string, chat *ReplyRecipient) Btn {
return Btn{Text: text, Chat: chat}
}
func (r *ReplyMarkup) Login(text string, login *Login) Btn {
return Btn{Login: login, Text: text}
}
@ -214,15 +202,14 @@ func (r *ReplyMarkup) WebApp(text string, app *WebApp) Btn {
//
// Set either Contact or Location to true in order to request
// sensitive info, such as user's phone number or current location.
//
type ReplyButton struct {
Text string `json:"text"`
Contact bool `json:"request_contact,omitempty"`
Location bool `json:"request_location,omitempty"`
Poll PollType `json:"request_poll,omitempty"`
User *ReplyRecipient `json:"request_users,omitempty"`
Chat *ReplyRecipient `json:"request_chat,omitempty"`
WebApp *WebApp `json:"web_app,omitempty"`
Contact bool `json:"request_contact,omitempty"`
Location bool `json:"request_location,omitempty"`
Poll PollType `json:"request_poll,omitempty"`
WebApp *WebApp `json:"web_app,omitempty"`
}
// MarshalJSON implements json.Marshaler. It allows passing PollType as a
@ -235,35 +222,6 @@ func (pt PollType) MarshalJSON() ([]byte, error) {
})
}
// ReplyRecipient combines both KeyboardButtonRequestUser
// and KeyboardButtonRequestChat objects. Use inside ReplyButton
// to request the user or chat sharing with respective settings.
//
// To pass the pointers to bool use a special tele.Flag function,
// that way you will be able to reflect the three-state bool (nil, false, true).
type ReplyRecipient struct {
ID int32 `json:"request_id"`
Bot *bool `json:"user_is_bot,omitempty"` // user only, optional
Premium *bool `json:"user_is_premium,omitempty"` // user only, optional
Quantity int `json:"max_quantity,omitempty"` // user only, optional
Channel bool `json:"chat_is_channel,omitempty"` // chat only, required
Forum *bool `json:"chat_is_forum,omitempty"` // chat only, optional
WithUsername *bool `json:"chat_has_username,omitempty"` // chat only, optional
Created *bool `json:"chat_is_created,omitempty"` // chat only, optional
UserRights *Rights `json:"user_administrator_rights,omitempty"` // chat only, optional
BotRights *Rights `json:"bot_administrator_rights,omitempty"` // chat only, optional
BotMember *bool `json:"bot_is_member,omitempty"` // chat only, optional
}
// RecipientShared combines both UserShared and ChatShared objects.
type RecipientShared struct {
ID int32 `json:"request_id"`
UserID int64 `json:"user_id"`
ChatID int64 `json:"chat_id"`
}
// InlineButton represents a button displayed in the message.
type InlineButton struct {
// Unique slagish name for this kind of button,
@ -272,14 +230,13 @@ type InlineButton struct {
// It will be used as a callback endpoint.
Unique string `json:"unique,omitempty"`
Text string `json:"text"`
URL string `json:"url,omitempty"`
Data string `json:"callback_data,omitempty"`
InlineQuery string `json:"switch_inline_query,omitempty"`
InlineQueryChat string `json:"switch_inline_query_current_chat"`
InlineQueryChosenChat *SwitchInlineQuery `json:"switch_inline_query_chosen_chat,omitempty"`
Login *Login `json:"login_url,omitempty"`
WebApp *WebApp `json:"web_app,omitempty"`
Text string `json:"text"`
URL string `json:"url,omitempty"`
Data string `json:"callback_data,omitempty"`
InlineQuery string `json:"switch_inline_query,omitempty"`
InlineQueryChat string `json:"switch_inline_query_current_chat"`
Login *Login `json:"login_url,omitempty"`
WebApp *WebApp `json:"web_app,omitempty"`
}
// MarshalJSON implements json.Marshaler interface.
@ -322,8 +279,6 @@ func (b Btn) Reply() *ReplyButton {
Contact: b.Contact,
Location: b.Location,
Poll: b.Poll,
User: b.User,
Chat: b.Chat,
WebApp: b.WebApp,
}
}

@ -19,7 +19,7 @@ type InputMedia struct {
Type string `json:"type"`
Media string `json:"media"`
Caption string `json:"caption"`
Thumbnail string `json:"thumbnail,omitempty"`
Thumbnail string `json:"thumb,omitempty"`
ParseMode string `json:"parse_mode,omitempty"`
Entities Entities `json:"caption_entities,omitempty"`
Width int `json:"width,omitempty"`
@ -29,7 +29,6 @@ type InputMedia struct {
Performer string `json:"performer,omitempty"`
Streaming bool `json:"supports_streaming,omitempty"`
DisableTypeDetection bool `json:"disable_content_type_detection,omitempty"`
HasSpoiler bool `json:"is_spoiler,omitempty"`
}
// Inputtable is a generic type for all kinds of media you
@ -45,24 +44,6 @@ type Inputtable interface {
// Album lets you group multiple media into a single message.
type Album []Inputtable
func (a Album) SetCaption(caption string) {
if len(a) < 1 {
return
}
switch a[0].MediaType() {
case "audio":
a[0].(*Audio).Caption = caption
case "video":
a[0].(*Video).Caption = caption
case "document":
a[0].(*Document).Caption = caption
case "photo":
a[0].(*Photo).Caption = caption
case "animation":
a[0].(*Animation).Caption = caption
}
}
// Photo object represents a single photo file.
type Photo struct {
File
@ -131,7 +112,7 @@ type Audio struct {
// (Optional)
Caption string `json:"caption,omitempty"`
Thumbnail *Photo `json:"thumbnail,omitempty"`
Thumbnail *Photo `json:"thumb,omitempty"`
Title string `json:"title,omitempty"`
Performer string `json:"performer,omitempty"`
MIME string `json:"mime_type,omitempty"`
@ -163,7 +144,7 @@ type Document struct {
File
// (Optional)
Thumbnail *Photo `json:"thumbnail,omitempty"`
Thumbnail *Photo `json:"thumb,omitempty"`
Caption string `json:"caption,omitempty"`
MIME string `json:"mime_type"`
FileName string `json:"file_name,omitempty"`
@ -197,7 +178,7 @@ type Video struct {
// (Optional)
Caption string `json:"caption,omitempty"`
Thumbnail *Photo `json:"thumbnail,omitempty"`
Thumbnail *Photo `json:"thumb,omitempty"`
Streaming bool `json:"supports_streaming,omitempty"`
MIME string `json:"mime_type,omitempty"`
FileName string `json:"file_name,omitempty"`
@ -233,7 +214,7 @@ type Animation struct {
// (Optional)
Caption string `json:"caption,omitempty"`
Thumbnail *Photo `json:"thumbnail,omitempty"`
Thumbnail *Photo `json:"thumb,omitempty"`
MIME string `json:"mime_type,omitempty"`
FileName string `json:"file_name,omitempty"`
}
@ -283,7 +264,7 @@ type VideoNote struct {
Duration int `json:"duration"`
// (Optional)
Thumbnail *Photo `json:"thumbnail,omitempty"`
Thumbnail *Photo `json:"thumb,omitempty"`
Length int `json:"length,omitempty"`
}
@ -298,18 +279,15 @@ func (v *VideoNote) MediaFile() *File {
// Sticker object represents a WebP image, so-called sticker.
type Sticker struct {
File
Type StickerSetType `json:"type"`
Width int `json:"width"`
Height int `json:"height"`
Animated bool `json:"is_animated"`
Video bool `json:"is_video"`
Thumbnail *Photo `json:"thumbnail"`
Emoji string `json:"emoji"`
SetName string `json:"set_name"`
PremiumAnimation *File `json:"premium_animation"`
MaskPosition *MaskPosition `json:"mask_position"`
CustomEmoji string `json:"custom_emoji_id"`
Repaint bool `json:"needs_repainting"`
Width int `json:"width"`
Height int `json:"height"`
Animated bool `json:"is_animated"`
Video bool `json:"is_video"`
Thumbnail *Photo `json:"thumb"`
Emoji string `json:"emoji"`
SetName string `json:"set_name"`
MaskPosition *MaskPosition `json:"mask_position"`
PremiumAnimation *File `json:"premium_animation"`
}
func (s *Sticker) MediaType() string {

@ -1,44 +0,0 @@
package telebot
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestAlbumSetCaption(t *testing.T) {
tests := []struct {
name string
media Inputtable
}{
{
name: "photo",
media: &Photo{Caption: "wrong_caption"},
},
{
name: "animation",
media: &Animation{Caption: "wrong_caption"},
},
{
name: "video",
media: &Video{Caption: "wrong_caption"},
},
{
name: "audio",
media: &Audio{Caption: "wrong_caption"},
},
{
name: "document",
media: &Document{Caption: "wrong_caption"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var a Album
a = append(a, tt.media)
a = append(a, &Photo{Caption: "random_caption"})
a.SetCaption("correct_caption")
assert.Equal(t, "correct_caption", a[0].InputMedia().Caption)
assert.Equal(t, "random_caption", a[1].InputMedia().Caption)
})
}
}

@ -10,9 +10,6 @@ import (
type Message struct {
ID int `json:"message_id"`
// (Optional) Unique identifier of a message thread to which the message belongs; for supergroups only
ThreadID int `json:"message_thread_id"`
// For message sent to channels, Sender will be nil
Sender *User `json:"from"`
@ -46,9 +43,6 @@ type Message struct {
// For forwarded messages, unixtime of the original message.
OriginalUnixtime int `json:"forward_date"`
// For information about the original message for forwarded messages.
Origin *MessageOrigin `json:"forward_origin"`
// Message is a channel post that was automatically forwarded to the connected discussion group.
AutomaticForward bool `json:"is_automatic_forward"`
@ -59,29 +53,12 @@ type Message struct {
// itself is a reply.
ReplyTo *Message `json:"reply_to_message"`
// (Optional) For replies to a story, the original story
Story *Story `json:"story"`
// (Optional) Information about the message that is being replied to,
// which may come from another chat or forum topic.
ExternalReplyInfo *ExternalReplyInfo `json:"external_reply"`
// (Optional) For replies that quote part of the original message,
// the quoted part of the message.
Quote *TextQuote `json:"quote"`
// Shows through which bot the message was sent.
Via *User `json:"via_bot"`
// For replies to a story, the original story.
ReplyToStory *Story `json:"reply_to_story"`
// (Optional) Time of last edit in Unix.
LastEdit int64 `json:"edit_date"`
// (Optional) True, if the message is sent to a forum topic.
TopicMessage bool `json:"is_topic_message"`
// (Optional) Message can't be forwarded.
Protected bool `json:"has_protected_content,omitempty"`
@ -104,10 +81,6 @@ type Message struct {
// etc. that appear in the text.
Entities Entities `json:"entities,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"`
@ -136,7 +109,7 @@ type Message struct {
// For a video, information about it.
Video *Video `json:"video"`
// For an animation, information about it.
// For a animation, information about it.
Animation *Animation `json:"animation"`
// For a contact, contact information itself.
@ -157,18 +130,6 @@ type Message struct {
// For a dice, information about it.
Dice *Dice `json:"dice"`
// (Optional) The message is a scheduled giveaway message.
Giveaway *Giveaway `json:"giveaway"`
// (Optional) A giveaway with public winners was completed.
GiveawayWinners *GiveawayWinners `json:"giveaway_winners"`
// (Optional) Service message: a scheduled giveaway was created.
GiveawayCreated *GiveawayCreated `json:"giveaway_created"`
// (Optional) Service message: a giveaway without public winners was completed.
GiveawayCompleted *GiveawayCompleted `json:"giveaway_completed"`
// For a service message, represents a user,
// that just got added to chat, this message came from.
//
@ -262,12 +223,6 @@ type Message struct {
// Message is a service message about a successful payment.
Payment *Payment `json:"successful_payment"`
// For a service message, a user was shared with the bot.
UserShared *RecipientShared `json:"users_shared,omitempty"`
// For a service message, a chat was shared with the bot.
ChatShared *RecipientShared `json:"chat_shared,omitempty"`
// The domain name of the website on which the user has logged in.
ConnectedWebsite string `json:"connected_website,omitempty"`
@ -295,37 +250,6 @@ type Message struct {
// Inline keyboard attached to the message.
ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"`
// Service message: user boosted the chat.
BoostAdded *BoostAdded `json:"boost_added"`
// If the sender of the message boosted the chat, the number of boosts
// added by the user.
SenderBoostCount int `json:"sender_boost_count"`
// Service message: forum topic created
TopicCreated *Topic `json:"forum_topic_created,omitempty"`
// Service message: forum topic closed
TopicClosed *struct{} `json:"forum_topic_closed,omitempty"`
// Service message: forum topic reopened
TopicReopened *Topic `json:"forum_topic_reopened,omitempty"`
// Service message: forum topic deleted
TopicEdited *Topic `json:"forum_topic_edited,omitempty"`
// Service message: general forum topic hidden
GeneralTopicHidden *struct{} `json:"general_topic_hidden,omitempty"`
// Service message: general forum topic unhidden
GeneralTopicUnhidden *struct{} `json:"general_topic_unhidden,omitempty"`
// Service message: represents spoiler information about the message.
HasMediaSpoiler bool `json:"has_media_spoiler,omitempty"`
// Service message: the user allowed the bot added to the attachment menu to write messages
WriteAccessAllowed *WriteAccessAllowed `json:"write_access_allowed,omitempty"`
}
// MessageEntity object represents "special" parts of text messages,
@ -376,10 +300,9 @@ const (
EntityTextLink EntityType = "text_link"
EntitySpoiler EntityType = "spoiler"
EntityCustomEmoji EntityType = "custom_emoji"
EntityBlockquote EntityType = "blockquote"
)
// Entities are used to set message's text entities as a send option.
// Entities is used to set message's text entities as a send option.
type Entities []MessageEntity
// ProximityAlert sent whenever a user in the chat triggers
@ -395,11 +318,6 @@ 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
@ -447,6 +365,7 @@ func (m *Message) FromChannel() bool {
// Service messages are automatically sent messages, which
// typically occur on some global action. For instance, when
// anyone leaves the chat or chat title changes.
//
func (m *Message) IsService() bool {
fact := false
@ -467,6 +386,7 @@ func (m *Message) IsService() bool {
//
// It's safer than manually slicing Text because Telegram uses
// UTF-16 indices whereas Go string are []byte.
//
func (m *Message) EntityText(e MessageEntity) string {
text := m.Text
if text == "" {
@ -507,215 +427,3 @@ func (m *Message) Media() Media {
return nil
}
}
// 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.
Chat *Chat `json:"chat"`
// Unique identifier of the message inside the chat.
MessageID int `json:"message_id"`
// (Optional) The user that changed the reaction,
// if the user isn't anonymous
User *User `json:"user"`
// (Optional) The chat on behalf of which the reaction was changed,
// if the user is anonymous.
ActorChat *Chat `json:"actor_chat"`
// Date of the change in Unix time.
DateUnixtime int64 `json:"date"`
// Previous list of reaction types that were set by the user.
OldReaction []Reaction `json:"old_reaction"`
// New list of reaction types that have been set by the user.
NewReaction []Reaction `json:"new_reaction"`
}
func (mu *MessageReaction) Time() time.Time {
return time.Unix(mu.DateUnixtime, 0)
}
// MessageReactionCount represents reaction changes on a message with
// anonymous reactions.
type MessageReactionCount struct {
// The chat containing the message.
Chat *Chat `json:"chat"`
// Unique message identifier inside the chat.
MessageID int `json:"message_id"`
// Date of the change in Unix time.
DateUnixtime int64 `json:"date"`
// List of reactions that are present on the message.
Reactions *ReactionCount `json:"reactions"`
}
// Time returns the moment of change in local time.
func (mc *MessageReactionCount) Time() time.Time {
return time.Unix(mc.DateUnixtime, 0)
}
// TextQuote contains information about the quoted part of a message that is
// replied to by the given message.
type TextQuote struct {
// Text of the quoted part of a message that is replied to by the given message.
Text string `json:"text"`
// (Optional) Special entities that appear in the quote.
// Currently, only bold, italic, underline, strikethrough, spoiler,
// and custom_emoji entities are kept in quotes.
Entities []MessageEntity `json:"entities"`
// Approximate quote position in the original message in UTF-16 code units
// as specified by the sender.
Position int `json:"position"`
// (Optional) True, if the quote was chosen manually by the message sender.
// Otherwise, the quote was added automatically by the server.
Manual bool `json:"is_manual"`
}
// MessageOrigin a message reference that has been sent originally by a known user.
type MessageOrigin struct {
// Type of the message origin, always “channel”.
Type string `json:"type"`
// Date the message was sent originally in Unix time.
DateUnixtime int64 `json:"date"`
// User that sent the message originally.
Sender *User `json:"sender_user,omitempty"`
// Name of the user that sent the message originally.
SenderUsername string `json:"sender_user_name,omitempty"`
// Chat that sent the message originally.
SenderChat *Chat `json:"sender_chat,omitempty"`
// Channel chat to which the message was originally sent.
Chat *Chat `json:"chat,omitempty"`
// Unique message identifier inside the chat.
MessageID int `json:"message_id,omitempty"`
// (Optional) For messages originally sent by an anonymous chat administrator,
// original message author signature.
Signature string `json:"author_signature,omitempty"`
}
// Time returns the moment of message that was sent originally in local time.
func (mo *MessageOrigin) Time() time.Time {
return time.Unix(mo.DateUnixtime, 0)
}
// ExternalReplyInfo contains information about a message that is being replied to,
// which may come from another chat or forum topic.
type ExternalReplyInfo struct {
// Origin of the message replied to by the given message.
Origin *MessageOrigin `json:"origin"`
// (Optional) Chat the original message belongs to.
// Available only if the chat is a supergroup or a channel.
Chat *Chat `json:"chat"`
// (Optional) Unique message identifier inside the original chat.
// Available only if the original chat is a supergroup or a channel.
MessageID int `json:"message_id"`
// (Optional) ReactionOptions used for link preview generation for the original message,
// if it is a text message.
PreviewOptions *PreviewOptions `json:"link_preview_options"`
// (Optional) Message is an animation, information about the animation.
Animation *Animation `json:"animation"`
// (Optional) Message is an audio file, information about the file.
Audio *Audio `json:"audio"`
// (Optional) Message is a general file, information about the file.
Document *Document `json:"document"`
// (Optional) Message is a photo, available sizes of the photo.
Photo []Photo `json:"photo"`
// (Optional) Message is a sticker, information about the sticker.
Sticker *Sticker `json:"sticker"`
// (Optional) Message is a forwarded story.
Story *Story `json:"story"`
// (Optional) Message is a video, information about the video.
Video *Video `json:"video"`
// (Optional) Message is a video note, information about the video message.
Note *VideoNote `json:"video_note"`
// (Optional) Message is a voice message, information about the file.
Voice *Voice `json:"voice"`
// (Optional) True, if the message media is covered by a spoiler animation.
HasMediaSpoiler bool `json:"has_media_spoiler"`
// (Optional) Message is a shared contact, information about the contact.
Contact *Contact `json:"contact"`
// (Optional) Message is a dice with random value.
Dice *Dice `json:"dice"`
//( Optional) Message is a game, information about the game.
Game *Game `json:"game"`
// (Optional) Message is a venue, information about the venue.
Venue *Venue `json:"venue"`
// (Optional) Message is a native poll, information about the poll.
Poll *Poll `json:"poll"`
// (Optional) Message is a shared location, information about the location.
Location *Location `json:"location"`
// (Optional) Message is an invoice for a payment, information about the invoice.
Invoice *Invoice `json:"invoice"`
// (Optional) Message is a scheduled giveaway, information about the giveaway.
Giveaway *Giveaway `json:"giveaway"`
// (Optional) A giveaway with public winners was completed.
GiveawayWinners *GiveawayWinners `json:"giveaway_winners"`
}
// ReplyParams describes reply parameters for the message that is being sent.
type ReplyParams struct {
// Identifier of the message that will be replied to in the current chat,
// or in the chat chat_id if it is specified.
MessageID int `json:"message_id"`
// (Optional) If the message to be replied to is from a different chat,
// unique identifier for the chat or username of the channel.
ChatID int64 `json:"chat_id"`
// Optional. Pass True if the message should be sent even if the specified message
// to be replied to is not found; can be used only for replies in the
// same chat and forum topic.
AllowWithoutReply bool `json:"allow_sending_without_reply"`
// (Optional) Quoted part of the message to be replied to; 0-1024 characters after
// entities parsing. The quote must be an exact substring of the message to be replied to,
// including bold, italic, underline, strikethrough, spoiler, and custom_emoji entities.
// The message will fail to send if the quote isn't found in the original message.
Quote string `json:"quote"`
// (Optional) Mode for parsing entities in the quote.
QuoteParseMode ParseMode `json:"quote_parse_mode"`
// (Optional) A JSON-serialized list of special entities that appear in the quote.
// It can be specified instead of quote_parse_mode.
QuoteEntities []MessageEntity `json:"quote_entities"`
// (Optional) Position of the quote in the original message in UTF-16 code units.
QuotePosition int `json:"quote_position"`
}

@ -32,28 +32,26 @@ 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 ...RecoverFunc) tele.MiddlewareFunc {
func Recover(onError ...func(error)) tele.MiddlewareFunc {
return func(next tele.HandlerFunc) tele.HandlerFunc {
return func(c tele.Context) error {
var f RecoverFunc
var f func(error)
if len(onError) > 0 {
f = onError[0]
} else {
f = func(err error, c tele.Context) {
c.Bot().OnError(err, c)
f = func(err error) {
c.Bot().OnError(err, nil)
}
}
defer func() {
if r := recover(); r != nil {
if err, ok := r.(error); ok {
f(err, c)
f(err)
} else if s, ok := r.(string); ok {
f(errors.New(s), c)
f(errors.New(s))
}
}
}()

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

@ -11,6 +11,7 @@ import (
// flags instead.
//
// Supported options are defined as iota-constants.
//
type Option int
const (
@ -53,6 +54,7 @@ func Placeholder(text string) *SendOptions {
// Despite its power, SendOptions is rather inconvenient to use all
// the way through bot logic, so you might want to consider storing
// and re-using it somewhere or be using Option flags instead.
//
type SendOptions struct {
// If the message is a reply, original message.
ReplyTo *Message
@ -75,17 +77,8 @@ type SendOptions struct {
// AllowWithoutReply allows sending messages not a as reply if the replied-to message has already been deleted.
AllowWithoutReply bool
// Protected protects the contents of sent message from forwarding and saving.
// Protected protects the contents of the sent message from forwarding and saving
Protected bool
// ThreadID supports sending messages to a thread.
ThreadID int
// HasSpoiler marks the message as containing a spoiler.
HasSpoiler bool
// ReplyParams Describes the message to reply to
ReplyParams *ReplyParams
}
func (og *SendOptions) copy() *SendOptions {
@ -107,8 +100,6 @@ func extractOptions(how []interface{}) *SendOptions {
if opt != nil {
opts.ReplyMarkup = opt.copy()
}
case *ReplyParams:
opts.ReplyParams = opt
case Option:
switch opt {
case NoPreview:
@ -198,14 +189,6 @@ func (b *Bot) embedSendOptions(params map[string]string, opt *SendOptions) {
if opt.Protected {
params["protect_content"] = "true"
}
if opt.ThreadID != 0 {
params["message_thread_id"] = strconv.Itoa(opt.ThreadID)
}
if opt.HasSpoiler {
params["spoiler"] = "true"
}
}
func processButtons(keys [][]InlineButton) {
@ -228,45 +211,3 @@ func processButtons(keys [][]InlineButton) {
}
}
}
// PreviewOptions describes the options used for link preview generation.
type PreviewOptions struct {
// (Optional) True, if the link preview is disabled.
Disabled bool `json:"is_disabled"`
// (Optional) URL to use for the link preview. If empty, then the first URL
// found in the message text will be used.
URL string `json:"url"`
// (Optional) True, if the media in the link preview is supposed to be shrunk;
// ignored if the URL isn't explicitly specified or media size change.
// isn't supported for the preview.
SmallMedia bool `json:"prefer_small_media"`
// (Optional) True, if the media in the link preview is supposed to be enlarged;
// ignored if the URL isn't explicitly specified or media size change.
// isn't supported for the preview.
LargeMedia bool `json:"prefer_large_media"`
// (Optional) True, if the link preview must be shown above the message text;
// otherwise, the link preview will be shown below the message text.
AboveText bool `json:"show_above_text"`
}
func embedMessages(params map[string]string, msgs []Editable) {
ids := make([]string, 0, len(msgs))
_, chatID := msgs[0].MessageSig()
for _, msg := range msgs {
msgID, _ := msg.MessageSig()
ids = append(ids, msgID)
}
data, err := json.Marshal(ids)
if err != nil {
return
}
params["message_ids"] = string(data)
params["chat_id"] = strconv.FormatInt(chatID, 10)
}

@ -49,7 +49,6 @@ type PollOption struct {
type PollAnswer struct {
PollID string `json:"poll_id"`
Sender *User `json:"user"`
Chat *Chat `json:"voter_chat"`
Options []int `json:"option_ids"`
}

@ -2,32 +2,12 @@ package telebot
import "time"
var AllowedUpdates = []string{
"message",
"edited_message",
"channel_post",
"edited_channel_post",
"message_reaction",
"message_reaction_count",
"inline_query",
"chosen_inline_result",
"callback_query",
"shipping_query",
"pre_checkout_query",
"poll",
"poll_answer",
"my_chat_member",
"chat_member",
"chat_join_request",
"chat_boost",
"removed_chat_boost",
}
// Poller is a provider of Updates.
//
// All pollers must implement Poll(), which accepts bot
// pointer and subscription channel and start polling
// synchronously straight away.
//
type Poller interface {
// Poll is supposed to take the bot object
// subscription channel and start polling
@ -90,6 +70,7 @@ func (p *LongPoller) Poll(b *Bot, dest chan Update, stop chan struct{}) {
// handling, banning or whatever.
//
// For heavy middleware, use increased capacity.
//
type MiddlewarePoller struct {
Capacity int // Default: 1
Poller Poller

@ -1,67 +0,0 @@
package telebot
import (
"encoding/json"
)
// Reaction describes the type of reaction.
// Describes an instance of ReactionTypeCustomEmoji and ReactionTypeEmoji.
type Reaction struct {
// Type of the reaction, always “emoji”
Type string `json:"type"`
// Reaction emoji.
Emoji string `json:"emoji,omitempty"`
// Custom emoji identifier.
CustomEmoji string `json:"custom_emoji_id,omitempty"`
}
// ReactionCount represents a reaction added to a message along
// with the number of times it was added.
type ReactionCount struct {
// Type of the reaction.
Type Reaction `json:"type"`
// Number of times the reaction was added.
Count int `json:"total_count"`
}
// ReactionOptions represents an object of reaction options.
type ReactionOptions struct {
// List of reaction types to set on the message.
Reactions []Reaction `json:"reaction"`
// 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,87 +0,0 @@
package react
import (
tele "gopkg.in/telebot.v3"
)
type Reaction = tele.Reaction
func React(r ...Reaction) tele.ReactionOptions {
return tele.ReactionOptions{Reactions: r}
}
// Currently available emojis.
var (
ThumbUp = Reaction{Emoji: "👍"}
ThumbDown = Reaction{Emoji: "👎"}
Heart = Reaction{Emoji: "❤"}
Fire = Reaction{Emoji: "🔥"}
HeartEyes = Reaction{Emoji: "😍"}
ClappingHands = Reaction{Emoji: "👏"}
GrinningFace = Reaction{Emoji: "😁"}
ThinkingFace = Reaction{Emoji: "🤔"}
ExplodingHead = Reaction{Emoji: "🤯"}
ScreamingFace = Reaction{Emoji: "😱"}
SwearingFace = Reaction{Emoji: "🤬"}
CryingFace = Reaction{Emoji: "😢"}
PartyPopper = Reaction{Emoji: "🎉"}
StarStruck = Reaction{Emoji: "🤩"}
VomitingFace = Reaction{Emoji: "🤮"}
PileOfPoo = Reaction{Emoji: "💩"}
PrayingHands = Reaction{Emoji: "🙏"}
OkHand = Reaction{Emoji: "👌"}
DoveOfPeace = Reaction{Emoji: "🕊"}
ClownFace = Reaction{Emoji: "🤡"}
YawningFace = Reaction{Emoji: "🥱"}
WoozyFace = Reaction{Emoji: "🥴"}
Whale = Reaction{Emoji: "🐳"}
HeartOnFire = Reaction{Emoji: "❤‍🔥"}
MoonFace = Reaction{Emoji: "🌚"}
HotDog = Reaction{Emoji: "🌭"}
HundredPoints = Reaction{Emoji: "💯"}
RollingOnTheFloorLaughing = Reaction{Emoji: "🤣"}
Lightning = Reaction{Emoji: "⚡"}
Banana = Reaction{Emoji: "🍌"}
Trophy = Reaction{Emoji: "🏆"}
BrokenHeart = Reaction{Emoji: "💔"}
FaceWithRaisedEyebrow = Reaction{Emoji: "🤨"}
NeutralFace = Reaction{Emoji: "😐"}
Strawberry = Reaction{Emoji: "🍓"}
Champagne = Reaction{Emoji: "🍾"}
KissMark = Reaction{Emoji: "💋"}
MiddleFinger = Reaction{Emoji: "🖕"}
EvilFace = Reaction{Emoji: "😈"}
SleepingFace = Reaction{Emoji: "😴"}
LoudlyCryingFace = Reaction{Emoji: "😭"}
NerdFace = Reaction{Emoji: "🤓"}
Ghost = Reaction{Emoji: "👻"}
Engineer = Reaction{Emoji: "👨‍💻"}
Eyes = Reaction{Emoji: "👀"}
JackOLantern = Reaction{Emoji: "🎃"}
NoMonkey = Reaction{Emoji: "🙈"}
SmilingFaceWithHalo = Reaction{Emoji: "😇"}
FearfulFace = Reaction{Emoji: "😨"}
Handshake = Reaction{Emoji: "🤝"}
WritingHand = Reaction{Emoji: "✍"}
HuggingFace = Reaction{Emoji: "🤗"}
Brain = Reaction{Emoji: "🫡"}
SantaClaus = Reaction{Emoji: "🎅"}
ChristmasTree = Reaction{Emoji: "🎄"}
Snowman = Reaction{Emoji: "☃"}
NailPolish = Reaction{Emoji: "💅"}
ZanyFace = Reaction{Emoji: "🤪"}
Moai = Reaction{Emoji: "🗿"}
Cool = Reaction{Emoji: "🆒"}
HeartWithArrow = Reaction{Emoji: "💘"}
HearMonkey = Reaction{Emoji: "🙉"}
Unicorn = Reaction{Emoji: "🦄"}
FaceBlowingKiss = Reaction{Emoji: "😘"}
Pill = Reaction{Emoji: "💊"}
SpeaklessMonkey = Reaction{Emoji: "🙊"}
Sunglasses = Reaction{Emoji: "😎"}
AlienMonster = Reaction{Emoji: "👾"}
ManShrugging = Reaction{Emoji: "🤷‍♂️"}
PersonShrugging = Reaction{Emoji: "🤷"}
WomanShrugging = Reaction{Emoji: "🤷‍♀️"}
PoutingFace = Reaction{Emoji: "😡"}
)

@ -18,6 +18,7 @@ type Recipient interface {
// This is pretty cool, since it lets bots implement
// custom Sendables for complex kind of media or
// chat objects spanning across multiple messages.
//
type Sendable interface {
Send(*Bot, Recipient, *SendOptions) (*Message, error)
}
@ -115,7 +116,6 @@ func (d *Document) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error
func (s *Sticker) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) {
params := map[string]string{
"chat_id": to.Recipient(),
"emoji": s.Emoji,
}
b.embedSendOptions(params, opt)
@ -402,7 +402,7 @@ func (g *Game) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) {
func thumbnailToFilemap(thumb *Photo) map[string]File {
if thumb != nil {
return map[string]File{"thumbnail": thumb.File}
return map[string]File{"thumb": thumb.File}
}
return nil
}

@ -1,306 +0,0 @@
package telebot
import (
"encoding/json"
"errors"
"fmt"
"strconv"
)
type (
StickerSetType = string
StickerSetFormat = string
MaskFeature = string
)
const (
StickerRegular StickerSetType = "regular"
StickerMask StickerSetType = "mask"
StickerCustomEmoji StickerSetType = "custom_emoji"
)
const (
StickerStatic StickerSetFormat = "static"
StickerAnimated StickerSetFormat = "animated"
StickerVideo StickerSetFormat = "video"
)
const (
MaskForehead MaskFeature = "forehead"
MaskEyes MaskFeature = "eyes"
MaskMouth MaskFeature = "mouth"
MaskChin MaskFeature = "chin"
)
// StickerSet represents a sticker set.
type StickerSet struct {
Type StickerSetType `json:"sticker_type"`
Format StickerSetFormat `json:"sticker_format"`
Name string `json:"name"`
Title string `json:"title"`
Animated bool `json:"is_animated"`
Video bool `json:"is_video"`
Stickers []Sticker `json:"stickers"`
Thumbnail *Photo `json:"thumbnail"`
Emojis string `json:"emojis"`
ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed
MaskPosition *MaskPosition `json:"mask_position"`
Repaint bool `json:"needs_repainting"`
// Input is a field used in createNewStickerSet method to specify a list
// of pre-defined stickers of type InputSticker to add to the set.
Input []InputSticker
}
type InputSticker struct {
File
Sticker string `json:"sticker"`
MaskPosition *MaskPosition `json:"mask_position"`
Emojis []string `json:"emoji_list"`
Keywords []string `json:"keywords"`
}
// MaskPosition describes the position on faces where
// a mask should be placed by default.
type MaskPosition struct {
Feature MaskFeature `json:"point"`
XShift float32 `json:"x_shift"`
YShift float32 `json:"y_shift"`
Scale float32 `json:"scale"`
}
// UploadSticker uploads a sticker file for later use.
func (b *Bot) UploadSticker(to Recipient, format StickerSetFormat, f File) (*File, error) {
params := map[string]string{
"user_id": to.Recipient(),
"sticker_format": format,
}
data, err := b.sendFiles("uploadStickerFile", map[string]File{"0": f}, params)
if err != nil {
return nil, err
}
var resp struct {
Result File
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return &resp.Result, nil
}
// StickerSet returns a sticker set on success.
func (b *Bot) StickerSet(name string) (*StickerSet, error) {
data, err := b.Raw("getStickerSet", map[string]string{"name": name})
if err != nil {
return nil, err
}
var resp struct {
Result *StickerSet
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}
// CreateStickerSet creates a new sticker set.
func (b *Bot) CreateStickerSet(of Recipient, set *StickerSet) error {
files := make(map[string]File)
for i, s := range set.Input {
repr := s.File.process(strconv.Itoa(i), files)
if repr == "" {
return fmt.Errorf("telebot: sticker #%d does not exist", i+1)
}
set.Input[i].Sticker = repr
}
data, _ := json.Marshal(set.Input)
params := map[string]string{
"user_id": of.Recipient(),
"name": set.Name,
"title": set.Title,
"sticker_format": set.Format,
"stickers": string(data),
}
if set.Type != "" {
params["sticker_type"] = set.Type
}
if set.Repaint {
params["needs_repainting"] = "true"
}
_, err := b.sendFiles("createNewStickerSet", files, params)
return err
}
// AddStickerToSet adds a new sticker to the existing sticker set.
func (b *Bot) AddStickerToSet(of Recipient, name string, sticker InputSticker) error {
files := make(map[string]File)
repr := sticker.File.process("0", files)
if repr == "" {
return errors.New("telebot: sticker does not exist")
}
sticker.Sticker = repr
data, _ := json.Marshal(sticker)
params := map[string]string{
"user_id": of.Recipient(),
"name": name,
"sticker": string(data),
}
_, err := b.sendFiles("addStickerToSet", files, params)
return err
}
// SetStickerPosition moves a sticker in set to a specific position.
func (b *Bot) SetStickerPosition(sticker string, position int) error {
params := map[string]string{
"sticker": sticker,
"position": strconv.Itoa(position),
}
_, err := b.Raw("setStickerPositionInSet", params)
return err
}
// DeleteSticker deletes a sticker from a set created by the bot.
func (b *Bot) DeleteSticker(sticker string) error {
_, err := b.Raw("deleteStickerFromSet", map[string]string{"sticker": sticker})
return err
}
// SetStickerSetThumb sets a thumbnail of the sticker set.
// Animated thumbnails can be set for animated sticker sets only.
//
// Thumbnail must be a PNG image, up to 128 kilobytes in size
// and have width and height exactly 100px, or a TGS animation
// up to 32 kilobytes in size.
//
// Animated sticker set thumbnail can't be uploaded via HTTP URL.
func (b *Bot) SetStickerSetThumb(of Recipient, set *StickerSet) error {
if set.Thumbnail == nil {
return errors.New("telebot: thumbnail is required")
}
files := make(map[string]File)
repr := set.Thumbnail.File.process("thumb", files)
if repr == "" {
return errors.New("telebot: thumbnail does not exist")
}
params := map[string]string{
"user_id": of.Recipient(),
"name": set.Name,
"thumbnail": repr,
}
_, err := b.sendFiles("setStickerSetThumbnail", files, params)
return err
}
// SetStickerSetTitle sets the title of a created sticker set.
func (b *Bot) SetStickerSetTitle(s StickerSet) error {
params := map[string]string{
"name": s.Name,
"title": s.Title,
}
_, err := b.Raw("setStickerSetTitle", params)
return err
}
// DeleteStickerSet deletes a sticker set that was created by the bot.
func (b *Bot) DeleteStickerSet(name string) error {
params := map[string]string{"name": name}
_, err := b.Raw("deleteStickerSet", params)
return err
}
// SetStickerEmojis changes the list of emoji assigned to a regular or custom emoji sticker.
func (b *Bot) SetStickerEmojis(sticker string, emojis []string) error {
data, err := json.Marshal(emojis)
if err != nil {
return err
}
params := map[string]string{
"sticker": sticker,
"emoji_list": string(data),
}
_, err = b.Raw("setStickerEmojiList", params)
return err
}
// SetStickerKeywords changes search keywords assigned to a regular or custom emoji sticker.
func (b *Bot) SetStickerKeywords(sticker string, keywords []string) error {
mk, err := json.Marshal(keywords)
if err != nil {
return err
}
params := map[string]string{
"sticker": sticker,
"keywords": string(mk),
}
_, err = b.Raw("setStickerKeywords", params)
return err
}
// SetStickerMaskPosition changes the mask position of a mask sticker.
func (b *Bot) SetStickerMaskPosition(sticker string, mask MaskPosition) error {
data, err := json.Marshal(mask)
if err != nil {
return err
}
params := map[string]string{
"sticker": sticker,
"mask_position": string(data),
}
_, err = b.Raw("setStickerMaskPosition", params)
return err
}
// CustomEmojiStickers returns the information about custom emoji stickers by their ids.
func (b *Bot) CustomEmojiStickers(ids []string) ([]Sticker, error) {
data, _ := json.Marshal(ids)
params := map[string]string{
"custom_emoji_ids": string(data),
}
data, err := b.Raw("getCustomEmojiStickers", params)
if err != nil {
return nil, err
}
var resp struct {
Result []Sticker
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}
// SetCustomEmojiStickerSetThumb sets the thumbnail of a custom emoji sticker set.
func (b *Bot) SetCustomEmojiStickerSetThumb(name, id string) error {
params := map[string]string{
"name": name,
"custom_emoji_id": id,
}
_, err := b.Raw("setCustomEmojiStickerSetThumbnail", params)
return err
}

@ -1,61 +0,0 @@
package telebot
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestStickerSet(t *testing.T) {
if b == nil {
t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)")
}
if userID == 0 {
t.Skip("USER_ID is required for StickerSet methods test")
}
input := []InputSticker{
{
File: FromURL("https://placehold.co/512/000000/FFFFFF/png"),
Emojis: []string{"🤖"},
Keywords: []string{"telebot", "robot", "bot"},
},
{
File: FromURL("https://placehold.co/512/000000/999999/png"),
Emojis: []string{"🤖"},
Keywords: []string{"telebot", "robot", "bot"},
},
}
original := &StickerSet{
Name: fmt.Sprintf("telebot_%d_by_%s", time.Now().Unix(), b.Me.Username),
Type: StickerRegular,
Format: StickerStatic,
Title: "Telebot Stickers",
Input: input[:1],
}
// 1
err := b.CreateStickerSet(user, original)
require.NoError(t, err)
// 2
err = b.AddStickerToSet(user, original.Name, input[1])
require.NoError(t, err)
original.Thumbnail = &Photo{File: thumb}
err = b.SetStickerSetThumb(user, original)
require.NoError(t, err)
set, err := b.StickerSet(original.Name)
require.NoError(t, err)
require.Equal(t, original.Name, set.Name)
require.Equal(t, len(input), len(set.Stickers))
_, err = b.Send(user, &set.Stickers[0])
require.NoError(t, err)
_, err = b.Send(user, &set.Stickers[1])
require.NoError(t, err)
}

@ -0,0 +1,212 @@
package telebot
import (
"encoding/json"
"strconv"
)
type StickerSetType = string
const (
StickerRegular = "regular"
StickerMask = "mask"
StickerCustomEmoji = "custom_emoji"
)
// StickerSet represents a sticker set.
type StickerSet struct {
Type StickerSetType `json:"sticker_type"`
Name string `json:"name"`
Title string `json:"title"`
Animated bool `json:"is_animated"`
Video bool `json:"is_video"`
Stickers []Sticker `json:"stickers"`
Thumbnail *Photo `json:"thumb"`
PNG *File `json:"png_sticker"`
TGS *File `json:"tgs_sticker"`
WebM *File `json:"webm_sticker"`
Emojis string `json:"emojis"`
ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed
MaskPosition *MaskPosition `json:"mask_position"`
}
// MaskPosition describes the position on faces where
// a mask should be placed by default.
type MaskPosition struct {
Feature MaskFeature `json:"point"`
XShift float32 `json:"x_shift"`
YShift float32 `json:"y_shift"`
Scale float32 `json:"scale"`
}
// MaskFeature defines sticker mask position.
type MaskFeature string
const (
FeatureForehead MaskFeature = "forehead"
FeatureEyes MaskFeature = "eyes"
FeatureMouth MaskFeature = "mouth"
FeatureChin MaskFeature = "chin"
)
// UploadSticker uploads a PNG file with a sticker for later use.
func (b *Bot) UploadSticker(to Recipient, png *File) (*File, error) {
files := map[string]File{
"png_sticker": *png,
}
params := map[string]string{
"user_id": to.Recipient(),
}
data, err := b.sendFiles("uploadStickerFile", files, params)
if err != nil {
return nil, err
}
var resp struct {
Result File
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return &resp.Result, nil
}
// StickerSet returns a sticker set on success.
func (b *Bot) StickerSet(name string) (*StickerSet, error) {
data, err := b.Raw("getStickerSet", map[string]string{"name": name})
if err != nil {
return nil, err
}
var resp struct {
Result *StickerSet
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}
// CreateStickerSet creates a new sticker set.
func (b *Bot) CreateStickerSet(to Recipient, s StickerSet) error {
files := make(map[string]File)
if s.PNG != nil {
files["png_sticker"] = *s.PNG
}
if s.TGS != nil {
files["tgs_sticker"] = *s.TGS
}
if s.WebM != nil {
files["webm_sticker"] = *s.WebM
}
params := map[string]string{
"user_id": to.Recipient(),
"sticker_type": s.Type,
"name": s.Name,
"title": s.Title,
"emojis": s.Emojis,
"contains_masks": strconv.FormatBool(s.ContainsMasks),
}
if s.MaskPosition != nil {
data, _ := json.Marshal(&s.MaskPosition)
params["mask_position"] = string(data)
}
_, err := b.sendFiles("createNewStickerSet", files, params)
return err
}
// AddSticker adds a new sticker to the existing sticker set.
func (b *Bot) AddSticker(to Recipient, s StickerSet) error {
files := make(map[string]File)
if s.PNG != nil {
files["png_sticker"] = *s.PNG
} else if s.TGS != nil {
files["tgs_sticker"] = *s.TGS
} else if s.WebM != nil {
files["webm_sticker"] = *s.WebM
}
params := map[string]string{
"user_id": to.Recipient(),
"name": s.Name,
"emojis": s.Emojis,
}
if s.MaskPosition != nil {
data, _ := json.Marshal(&s.MaskPosition)
params["mask_position"] = string(data)
}
_, err := b.sendFiles("addStickerToSet", files, params)
return err
}
// SetStickerPosition moves a sticker in set to a specific position.
func (b *Bot) SetStickerPosition(sticker string, position int) error {
params := map[string]string{
"sticker": sticker,
"position": strconv.Itoa(position),
}
_, err := b.Raw("setStickerPositionInSet", params)
return err
}
// DeleteSticker deletes a sticker from a set created by the bot.
func (b *Bot) DeleteSticker(sticker string) error {
_, err := b.Raw("deleteStickerFromSet", map[string]string{"sticker": sticker})
return err
}
// SetStickerSetThumb sets a thumbnail of the sticker set.
// Animated thumbnails can be set for animated sticker sets only.
//
// Thumbnail must be a PNG image, up to 128 kilobytes in size
// and have width and height exactly 100px, or a TGS animation
// up to 32 kilobytes in size.
//
// Animated sticker set thumbnail can't be uploaded via HTTP URL.
//
func (b *Bot) SetStickerSetThumb(to Recipient, s StickerSet) error {
files := make(map[string]File)
if s.PNG != nil {
files["thumb"] = *s.PNG
} else if s.TGS != nil {
files["thumb"] = *s.TGS
}
params := map[string]string{
"name": s.Name,
"user_id": to.Recipient(),
}
_, err := b.sendFiles("setStickerSetThumb", files, params)
return err
}
// CustomEmojiStickers returns the information about custom emoji stickers by their ids.
func (b *Bot) CustomEmojiStickers(ids []string) ([]Sticker, error) {
data, _ := json.Marshal(ids)
params := map[string]string{
"custom_emoji_ids": string(data),
}
data, err := b.Raw("getCustomEmojiStickers", params)
if err != nil {
return nil, err
}
var resp struct {
Result []Sticker
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}

@ -2,28 +2,29 @@
//
// Example:
//
// package main
// package main
//
// import (
// "time"
// tele "gopkg.in/telebot.v3"
// )
// import (
// "time"
// tele "gopkg.in/telebot.v3"
// )
//
// func main() {
// b, err := tele.NewBot(tele.Settings{
// Token: "...",
// Poller: &tele.LongPoller{Timeout: 10 * time.Second},
// })
// if err != nil {
// return
// }
// func main() {
// b, err := tele.NewBot(tele.Settings{
// Token: "...",
// Poller: &tele.LongPoller{Timeout: 10 * time.Second},
// })
// if err != nil {
// return
// }
//
// b.Handle("/start", func(c tele.Context) error {
// return c.Send("Hello world!")
// })
//
// b.Handle("/start", func(c tele.Context) error {
// return c.Send("Hello world!")
// })
// b.Start()
// }
//
// b.Start()
// }
package telebot
import "errors"
@ -42,43 +43,35 @@ const DefaultApiURL = "https://api.telegram.org"
//
// For convenience, all Telebot-provided endpoints start with
// an "alert" character \a.
//
const (
// Basic message handlers.
OnText = "\atext"
OnEdited = "\aedited"
OnPhoto = "\aphoto"
OnAudio = "\aaudio"
OnAnimation = "\aanimation"
OnDocument = "\adocument"
OnSticker = "\asticker"
OnVideo = "\avideo"
OnVoice = "\avoice"
OnVideoNote = "\avideo_note"
OnContact = "\acontact"
OnLocation = "\alocation"
OnVenue = "\avenue"
OnDice = "\adice"
OnInvoice = "\ainvoice"
OnPayment = "\apayment"
OnGame = "\agame"
OnPoll = "\apoll"
OnPollAnswer = "\apoll_answer"
OnPinned = "\apinned"
OnChannelPost = "\achannel_post"
OnEditedChannelPost = "\aedited_channel_post"
OnTopicCreated = "\atopic_created"
OnTopicReopened = "\atopic_reopened"
OnTopicClosed = "\atopic_closed"
OnTopicEdited = "\atopic_edited"
OnGeneralTopicHidden = "\ageneral_topic_hidden"
OnGeneralTopicUnhidden = "\ageneral_topic_unhidden"
OnWriteAccessAllowed = "\awrite_access_allowed"
OnText = "\atext"
OnEdited = "\aedited"
OnPhoto = "\aphoto"
OnAudio = "\aaudio"
OnAnimation = "\aanimation"
OnDocument = "\adocument"
OnSticker = "\asticker"
OnVideo = "\avideo"
OnVoice = "\avoice"
OnVideoNote = "\avideo_note"
OnContact = "\acontact"
OnLocation = "\alocation"
OnVenue = "\avenue"
OnDice = "\adice"
OnInvoice = "\ainvoice"
OnPayment = "\apayment"
OnGame = "\agame"
OnPoll = "\apoll"
OnPollAnswer = "\apoll_answer"
OnPinned = "\apinned"
OnChannelPost = "\achannel_post"
OnEditedChannelPost = "\aedited_channel_post"
OnAddedToGroup = "\aadded_to_group"
OnUserJoined = "\auser_joined"
OnUserLeft = "\auser_left"
OnUserShared = "\auser_shared"
OnChatShared = "\achat_shared"
OnNewGroupTitle = "\anew_chat_title"
OnNewGroupPhoto = "\anew_chat_photo"
OnGroupPhotoDeleted = "\achat_photo_deleted"
@ -109,9 +102,6 @@ const (
OnVideoChatEnded = "\avideo_chat_ended"
OnVideoChatParticipants = "\avideo_chat_participants_invited"
OnVideoChatScheduled = "\avideo_chat_scheduled"
OnBoost = "\aboost_updated"
OnBoostRemoved = "\aboost_removed"
)
// ChatAction is a client-side status indicating bot activity.
@ -141,13 +131,6 @@ const (
ModeHTML ParseMode = "HTML"
)
// M is a shortcut for map[string]interface{}.
// Useful for passing arguments to the layout functions.
// M is a shortcut for map[string]interface{}. Use it for passing
// arguments to the layout functions.
type M = map[string]interface{}
// Flag returns a pointer to the given bool.
// Useful for passing the three-state flags to a Bot API.
// For example, see ReplyRecipient type.
func Flag(b bool) *bool {
return &b
}

@ -1,184 +0,0 @@
package telebot
import (
"encoding/json"
"strconv"
)
type Topic struct {
Name string `json:"name"`
IconColor int `json:"icon_color"`
IconCustomEmoji string `json:"icon_custom_emoji_id"`
ThreadID int `json:"message_thread_id"`
}
// CreateTopic creates a topic in a forum supergroup chat.
func (b *Bot) CreateTopic(chat *Chat, topic *Topic) (*Topic, error) {
params := map[string]string{
"chat_id": chat.Recipient(),
"name": topic.Name,
}
if topic.IconColor != 0 {
params["icon_color"] = strconv.Itoa(topic.IconColor)
}
if topic.IconCustomEmoji != "" {
params["icon_custom_emoji_id"] = topic.IconCustomEmoji
}
data, err := b.Raw("createForumTopic", params)
if err != nil {
return nil, err
}
var resp struct {
Result *Topic
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, err
}
// EditTopic edits name and icon of a topic in a forum supergroup chat.
func (b *Bot) EditTopic(chat *Chat, topic *Topic) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"message_thread_id": topic.ThreadID,
}
if topic.Name != "" {
params["name"] = topic.Name
}
if topic.IconCustomEmoji != "" {
params["icon_custom_emoji_id"] = topic.IconCustomEmoji
}
_, err := b.Raw("editForumTopic", params)
return err
}
// CloseTopic closes an open topic in a forum supergroup chat.
func (b *Bot) CloseTopic(chat *Chat, topic *Topic) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"message_thread_id": topic.ThreadID,
}
_, err := b.Raw("closeForumTopic", params)
return err
}
// ReopenTopic reopens a closed topic in a forum supergroup chat.
func (b *Bot) ReopenTopic(chat *Chat, topic *Topic) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"message_thread_id": topic.ThreadID,
}
_, err := b.Raw("reopenForumTopic", params)
return err
}
// DeleteTopic deletes a forum topic along with all its messages in a forum supergroup chat.
func (b *Bot) DeleteTopic(chat *Chat, topic *Topic) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"message_thread_id": topic.ThreadID,
}
_, err := b.Raw("deleteForumTopic", params)
return err
}
// UnpinAllTopicMessages clears the list of pinned messages in a forum topic. The bot must be an administrator in the chat for this to work and must have the can_pin_messages administrator right in the supergroup.
func (b *Bot) UnpinAllTopicMessages(chat *Chat, topic *Topic) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"message_thread_id": topic.ThreadID,
}
_, err := b.Raw("unpinAllForumTopicMessages", params)
return err
}
// TopicIconStickers gets custom emoji stickers, which can be used as a forum topic icon by any user.
func (b *Bot) TopicIconStickers() ([]Sticker, error) {
params := map[string]string{}
data, err := b.Raw("getForumTopicIconStickers", params)
if err != nil {
return nil, err
}
var resp struct {
Result []Sticker
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}
// EditGeneralTopic edits name of the 'General' topic in a forum supergroup chat.
func (b *Bot) EditGeneralTopic(chat *Chat, topic *Topic) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
"name": topic.Name,
}
_, err := b.Raw("editGeneralForumTopic", params)
return err
}
// CloseGeneralTopic closes an open 'General' topic in a forum supergroup chat.
func (b *Bot) CloseGeneralTopic(chat *Chat) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
}
_, err := b.Raw("closeGeneralForumTopic", params)
return err
}
// ReopenGeneralTopic reopens a closed 'General' topic in a forum supergroup chat.
func (b *Bot) ReopenGeneralTopic(chat *Chat) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
}
_, err := b.Raw("reopenGeneralForumTopic", params)
return err
}
// HideGeneralTopic hides the 'General' topic in a forum supergroup chat.
func (b *Bot) HideGeneralTopic(chat *Chat) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
}
_, err := b.Raw("hideGeneralForumTopic", params)
return err
}
// UnhideGeneralTopic unhides the 'General' topic in a forum supergroup chat.
func (b *Bot) UnhideGeneralTopic(chat *Chat) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
}
_, err := b.Raw("unhideGeneralForumTopic", params)
return err
}
// UnpinAllGeneralTopicMessages clears the list of pinned messages in a General forum topic.
// The bot must be an administrator in the chat for this to work and must have the
// can_pin_messages administrator right in the supergroup.
func (b *Bot) UnpinAllGeneralTopicMessages(chat *Chat) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),
}
_, err := b.Raw("unpinAllGeneralForumTopicMessages", params)
return err
}

@ -6,24 +6,20 @@ import "strings"
type Update struct {
ID int `json:"update_id"`
Message *Message `json:"message,omitempty"`
EditedMessage *Message `json:"edited_message,omitempty"`
ChannelPost *Message `json:"channel_post,omitempty"`
EditedChannelPost *Message `json:"edited_channel_post,omitempty"`
MessageReaction *MessageReaction `json:"message_reaction"`
MessageReactionCount *MessageReactionCount `json:"message_reaction_count"`
Callback *Callback `json:"callback_query,omitempty"`
Query *Query `json:"inline_query,omitempty"`
InlineResult *InlineResult `json:"chosen_inline_result,omitempty"`
ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"`
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"`
Poll *Poll `json:"poll,omitempty"`
PollAnswer *PollAnswer `json:"poll_answer,omitempty"`
MyChatMember *ChatMemberUpdate `json:"my_chat_member,omitempty"`
ChatMember *ChatMemberUpdate `json:"chat_member,omitempty"`
ChatJoinRequest *ChatJoinRequest `json:"chat_join_request,omitempty"`
Boost *BoostUpdated `json:"chat_boost"`
BoostRemoved *BoostRemoved `json:"removed_chat_boost"`
Message *Message `json:"message,omitempty"`
EditedMessage *Message `json:"edited_message,omitempty"`
ChannelPost *Message `json:"channel_post,omitempty"`
EditedChannelPost *Message `json:"edited_channel_post,omitempty"`
Callback *Callback `json:"callback_query,omitempty"`
Query *Query `json:"inline_query,omitempty"`
InlineResult *InlineResult `json:"chosen_inline_result,omitempty"`
ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"`
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"`
Poll *Poll `json:"poll,omitempty"`
PollAnswer *PollAnswer `json:"poll_answer,omitempty"`
MyChatMember *ChatMemberUpdate `json:"my_chat_member,omitempty"`
ChatMember *ChatMemberUpdate `json:"chat_member,omitempty"`
ChatJoinRequest *ChatJoinRequest `json:"chat_join_request,omitempty"`
}
// ProcessUpdate processes a single incoming update.
@ -103,35 +99,6 @@ func (b *Bot) ProcessUpdate(u Update) {
return
}
if m.TopicCreated != nil {
b.handle(OnTopicCreated, c)
return
}
if m.TopicReopened != nil {
b.handle(OnTopicReopened, c)
return
}
if m.TopicClosed != nil {
b.handle(OnTopicClosed, c)
return
}
if m.TopicEdited != nil {
b.handle(OnTopicEdited, c)
return
}
if m.GeneralTopicHidden != nil {
b.handle(OnGeneralTopicHidden, c)
return
}
if m.GeneralTopicUnhidden != nil {
b.handle(OnGeneralTopicUnhidden, c)
return
}
if m.WriteAccessAllowed != nil {
b.handle(OnWriteAccessAllowed, c)
return
}
wasAdded := (m.UserJoined != nil && m.UserJoined.ID == b.Me.ID) ||
(m.UsersJoined != nil && isUserInList(b.Me, m.UsersJoined))
if m.GroupCreated || m.SuperGroupCreated || wasAdded {
@ -143,6 +110,7 @@ func (b *Bot) ProcessUpdate(u Update) {
b.handle(OnUserJoined, c)
return
}
if m.UsersJoined != nil {
for _, user := range m.UsersJoined {
m.UserJoined = &user
@ -150,28 +118,22 @@ func (b *Bot) ProcessUpdate(u Update) {
}
return
}
if m.UserLeft != nil {
b.handle(OnUserLeft, c)
return
}
if m.UserShared != nil {
b.handle(OnUserShared, c)
return
}
if m.ChatShared != nil {
b.handle(OnChatShared, c)
return
}
if m.NewGroupTitle != "" {
b.handle(OnNewGroupTitle, c)
return
}
if m.NewGroupPhoto != nil {
b.handle(OnNewGroupPhoto, c)
return
}
if m.GroupPhotoDeleted {
b.handle(OnGroupPhotoDeleted, c)
return
@ -181,10 +143,12 @@ func (b *Bot) ProcessUpdate(u Update) {
b.handle(OnGroupCreated, c)
return
}
if m.SuperGroupCreated {
b.handle(OnSuperGroupCreated, c)
return
}
if m.ChannelCreated {
b.handle(OnChannelCreated, c)
return
@ -200,14 +164,17 @@ func (b *Bot) ProcessUpdate(u Update) {
b.handle(OnVideoChatStarted, c)
return
}
if m.VideoChatEnded != nil {
b.handle(OnVideoChatEnded, c)
return
}
if m.VideoChatParticipants != nil {
b.handle(OnVideoChatParticipants, c)
return
}
if m.VideoChatScheduled != nil {
b.handle(OnVideoChatScheduled, c)
return
@ -215,13 +182,13 @@ func (b *Bot) ProcessUpdate(u Update) {
if m.WebAppData != nil {
b.handle(OnWebApp, c)
return
}
if m.ProximityAlert != nil {
b.handle(OnProximityAlert, c)
return
}
if m.AutoDeleteTimer != nil {
b.handle(OnAutoDeleteTimer, c)
return
@ -312,16 +279,6 @@ func (b *Bot) ProcessUpdate(u Update) {
b.handle(OnChatJoinRequest, c)
return
}
if u.Boost != nil {
b.handle(OnBoost, c)
return
}
if u.BoostRemoved != nil {
b.handle(OnBoostRemoved, c)
return
}
}
func (b *Bot) handle(end string, c Context) bool {

@ -2,28 +2,26 @@ package telebot
import "time"
type (
// VideoChatStarted represents a service message about a video chat
// started in the chat.
VideoChatStarted struct{}
// VideoChatStarted represents a service message about a video chat
// started in the chat.
type VideoChatStarted struct{}
// VideoChatEnded represents a service message about a video chat
// ended in the chat.
VideoChatEnded struct {
Duration int `json:"duration"` // in seconds
}
// VideoChatEnded represents a service message about a video chat
// ended in the chat.
type VideoChatEnded struct {
Duration int `json:"duration"` // in seconds
}
// VideoChatParticipants represents a service message about new
// members invited to a video chat
VideoChatParticipants struct {
Users []User `json:"users"`
}
// VideoChatParticipants represents a service message about new
// members invited to a video chat
type VideoChatParticipants struct {
Users []User `json:"users"`
}
// VideoChatScheduled represents a service message about a video chat scheduled in the chat.
VideoChatScheduled struct {
Unixtime int64 `json:"start_date"`
}
)
// VideoChatScheduled represents a service message about a video chat scheduled in the chat.
type VideoChatScheduled struct {
Unixtime int64 `json:"start_date"`
}
// StartsAt returns the point when the video chat is supposed to be started by a chat administrator.
func (v *VideoChatScheduled) StartsAt() time.Time {

@ -16,11 +16,3 @@ type WebAppData struct {
Data string `json:"data"`
Text string `json:"button_text"`
}
// WebAppAccessAllowed represents a service message about a user allowing
// a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.
type WriteAccessAllowed struct {
WebAppName string `json:"web_app_name,omitempty"`
FromRequest bool `json:"from_request,omitempty"`
FromAttachmentMenu bool `json:"from_attachment_menu,omitempty"`
}

Loading…
Cancel
Save