package telebot import ( "encoding/json" "fmt" "strings" ) // ReplyMarkup controls two convenient options for bot-user communications // such as reply keyboard and inline "keyboard" (a grid of buttons as a part // of the message). type ReplyMarkup struct { // InlineKeyboard is a grid of InlineButtons displayed in the message. // // Note: DO NOT confuse with ReplyKeyboard and other keyboard properties! InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"` // ReplyKeyboard is a grid, consisting of keyboard buttons. // // Note: you don't need to set HideCustomKeyboard field to show custom keyboard. ReplyKeyboard [][]ReplyButton `json:"keyboard,omitempty"` // ForceReply forces Telegram clients to display // a reply interface to the user (act as if the user // has selected the bot‘s message and tapped "Reply"). ForceReply bool `json:"force_reply,omitempty"` // Requests clients to resize the keyboard vertically for optimal fit // (e.g. make the keyboard smaller if there are just two rows of buttons). // // Defaults to false, in which case the custom keyboard is always of the // same height as the app's standard keyboard. ResizeKeyboard bool `json:"resize_keyboard,omitempty"` // Requests clients to hide the reply keyboard as soon as it's been used. // // Defaults to false. OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"` // Requests clients to remove the reply keyboard. // // Defaults to false. RemoveKeyboard bool `json:"remove_keyboard,omitempty"` // Use this param if you want to force reply from // specific users only. // // Targets: // 1) Users that are @mentioned in the text of the Message object; // 2) If the bot's message is a reply (has SendOptions.ReplyTo), // sender of the original message. Selective bool `json:"selective,omitempty"` // 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 { cp := *r if len(r.ReplyKeyboard) > 0 { cp.ReplyKeyboard = make([][]ReplyButton, len(r.ReplyKeyboard)) for i, row := range r.ReplyKeyboard { cp.ReplyKeyboard[i] = make([]ReplyButton, len(row)) copy(cp.ReplyKeyboard[i], row) } } if len(r.InlineKeyboard) > 0 { cp.InlineKeyboard = make([][]InlineButton, len(r.InlineKeyboard)) for i, row := range r.InlineKeyboard { cp.InlineKeyboard[i] = make([]InlineButton, len(row)) copy(cp.InlineKeyboard[i], row) } } return &cp } // 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"` } // Row represents an array of buttons, a row. type Row []Btn // Row creates a row of buttons. func (r *ReplyMarkup) Row(many ...Btn) Row { return many } // Split splits the keyboard into the rows with N maximum number of buttons. // For example, if you pass six buttons and 3 as the max, you get two rows with // three buttons in each. // // `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 { i /= max rows[i] = append(rows[i], b) } return rows } func (r *ReplyMarkup) Inline(rows ...Row) { inlineKeys := make([][]InlineButton, 0, len(rows)) for i, row := range rows { keys := make([]InlineButton, 0, len(row)) for j, btn := range row { btn := btn.Inline() if btn == nil { panic(fmt.Sprintf( "telebot: button row %d column %d is not an inline button", i, j)) } keys = append(keys, *btn) } inlineKeys = append(inlineKeys, keys) } r.InlineKeyboard = inlineKeys } func (r *ReplyMarkup) Reply(rows ...Row) { replyKeys := make([][]ReplyButton, 0, len(rows)) for i, row := range rows { keys := make([]ReplyButton, 0, len(row)) for j, btn := range row { btn := btn.Reply() if btn == nil { panic(fmt.Sprintf( "telebot: button row %d column %d is not a reply button", i, j)) } keys = append(keys, *btn) } replyKeys = append(replyKeys, keys) } r.ReplyKeyboard = replyKeys } func (r *ReplyMarkup) Text(text string) Btn { return Btn{Text: text} } func (r *ReplyMarkup) Data(text, unique string, data ...string) Btn { return Btn{ Unique: unique, Text: text, Data: strings.Join(data, "|"), } } func (r *ReplyMarkup) URL(text, url string) Btn { return Btn{Text: text, URL: url} } func (r *ReplyMarkup) Query(text, query string) Btn { return Btn{Text: text, InlineQuery: query} } 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} } func (r *ReplyMarkup) WebApp(text string, app *WebApp) Btn { return Btn{Text: text, WebApp: app} } // ReplyButton represents a button displayed in reply-keyboard. // // 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"` } // MarshalJSON implements json.Marshaler. It allows passing PollType as a // keyboard's poll type instead of KeyboardButtonPollType object. func (pt PollType) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { Type string `json:"type"` }{ Type: string(pt), }) } // 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, // try to be as specific as possible. // // 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"` } // MarshalJSON implements json.Marshaler interface. // It needed to avoid InlineQueryChat and Login or WebApp fields conflict. // If you have Login or WebApp field in your button, InlineQueryChat must be skipped. func (t *InlineButton) MarshalJSON() ([]byte, error) { type IB InlineButton if t.Login != nil || t.WebApp != nil { return json.Marshal(struct { IB InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"` }{ IB: IB(*t), }) } return json.Marshal(IB(*t)) } // With returns a copy of the button with data. func (t *InlineButton) With(data string) *InlineButton { return &InlineButton{ Unique: t.Unique, Text: t.Text, URL: t.URL, InlineQuery: t.InlineQuery, InlineQueryChat: t.InlineQueryChat, Login: t.Login, Data: data, } } func (b Btn) Reply() *ReplyButton { if b.Unique != "" { return nil } return &ReplyButton{ Text: b.Text, Contact: b.Contact, Location: b.Location, Poll: b.Poll, User: b.User, Chat: b.Chat, WebApp: b.WebApp, } } func (b Btn) Inline() *InlineButton { return &InlineButton{ Unique: b.Unique, Text: b.Text, URL: b.URL, Data: b.Data, InlineQuery: b.InlineQuery, InlineQueryChat: b.InlineQueryChat, Login: b.Login, WebApp: b.WebApp, } } // Login represents a parameter of the inline keyboard button // used to automatically authorize a user. Serves as a great replacement // for the Telegram Login Widget when the user is coming from Telegram. type Login struct { URL string `json:"url"` Text string `json:"forward_text,omitempty"` Username string `json:"bot_username,omitempty"` WriteAccess bool `json:"request_write_access,omitempty"` } // MenuButton describes the bot's menu button in a private chat. type MenuButton struct { Type MenuButtonType `json:"type"` Text string `json:"text,omitempty"` WebApp *WebApp `json:"web_app,omitempty"` } type MenuButtonType = string const ( MenuButtonDefault MenuButtonType = "default" MenuButtonCommands MenuButtonType = "commands" MenuButtonWebApp MenuButtonType = "web_app" )