From 9b11223d561c29911398d6372cfd90c9c673487a Mon Sep 17 00:00:00 2001 From: Demian Date: Fri, 24 Apr 2020 17:52:15 +0300 Subject: [PATCH] bot: complete Polls 2.0 API --- errors.go | 3 +++ message.go | 3 +++ options.go | 19 ++++++++++------- poll.go | 47 ----------------------------------------- polls.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ polls_test.go | 45 +++++++++++++++++++++++++++++++++++++++ sendable.go | 20 +++++++++++++----- telebot.go | 7 +++++-- 8 files changed, 141 insertions(+), 61 deletions(-) delete mode 100644 poll.go create mode 100644 polls.go create mode 100644 polls_test.go diff --git a/errors.go b/errors.go index 6d0f850..c822667 100644 --- a/errors.go +++ b/errors.go @@ -76,6 +76,7 @@ var ( ErrWrongFileIDPadding = NewAPIError(400, "Bad Request: wrong remote file id specified: Wrong padding in the string") ErrFailedImageProcess = NewAPIError(400, "Bad Request: IMAGE_PROCESS_FAILED", "Image process failed") ErrInvaliadStickerset = NewAPIError(400, "Bad Request: STICKERSET_INVALID", "Stickerset is invalid") + ErrBadPollOptions = NewAPIError(400, "Bad Request: expected Array of String as options") // No rights errors ErrNoRightsToRestrict = NewAPIError(400, "Bad Request: not enough rights to restrict/unrestrict chat member") @@ -122,6 +123,8 @@ func ErrByDescription(s string) error { return ErrMessageNotModified case ErrButtonDataInvalid.ʔ(): return ErrButtonDataInvalid + case ErrBadPollOptions.ʔ(): + return ErrBadPollOptions case ErrNoRightsToRestrict.ʔ(): return ErrNoRightsToRestrict case ErrNoRightsToSend.ʔ(): diff --git a/message.go b/message.go index 40aa396..5adefa5 100644 --- a/message.go +++ b/message.go @@ -99,6 +99,9 @@ type Message struct { // For a venue, information about it. Venue *Venue `json:"venue"` + // For a poll, information the native poll. + Poll *Poll `json:"poll"` + // For a service message, represents a user, // that just got added to chat, this message came from. // diff --git a/options.go b/options.go index 254a149..679aeba 100644 --- a/options.go +++ b/options.go @@ -1,5 +1,7 @@ package telebot +import "encoding/json" + // Option is a shorcut flag type for certain message features // (so-called options). It means that instead of passing // fully-fledged SendOptions* to Send(), you can use these @@ -127,8 +129,9 @@ func (og *ReplyMarkup) copy() *ReplyMarkup { type ReplyButton struct { Text string `json:"text"` - Contact bool `json:"request_contact,omitempty"` - Location bool `json:"request_location,omitempty"` + Contact bool `json:"request_contact,omitempty"` + Location bool `json:"request_location,omitempty"` + Poll PollType `json:"request_poll,omitempty"` // Not used anywhere. // Will be removed in future releases. @@ -143,9 +146,11 @@ type InlineKeyboardMarkup struct { InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"` } -// PollOptions represents arguments for sendPoll method. -type PollOptions struct { - DisableNotification bool `json:"disable_notification"` - ReplyToMessageID *int `json:"reply_to_message_id"` - ReplyMarkup *ReplyMarkup `json:"reply_markup"` +func (pt PollType) MarshalJSON() ([]byte, error) { + var aux = struct { + Type string `json:"type"` + }{ + Type: string(pt), + } + return json.Marshal(&aux) } diff --git a/poll.go b/poll.go deleted file mode 100644 index 80c36a6..0000000 --- a/poll.go +++ /dev/null @@ -1,47 +0,0 @@ -package telebot - -// Poll object represents a poll, it contains information about a poll. -type Poll struct { - ID string `json:"id"` - Question string `json:"question"` - Options []PollOption `json:"options"` - TotalVoterCount int `json:"total_voter_count"` - IsClosed bool `json:"is_closed"` - IsAnonymous bool `json:"is_anonymous"` - Type PollType `json:"type"` - AllowsMultipleAnswers bool `json:"allows_multiple_answers"` - - // Optional. 0-based identifier of the correct answer option. - // Available only for polls in the quiz mode, which are closed, - // or was sent (not forwarded) by the bot or to the private chat with the bot. - CorrectOptionID int `json:"correct_option_id"` -} - -// PollOption object represents a option of a poll -type PollOption struct { - Text string `json:"text"` - VoterCount int `json:"voter_count"` -} - -// PollAnswer object represents an answer of a user in a non-anonymous poll. -type PollAnswer struct { - PollID string `json:"poll_id"` - User User `json:"user"` - - // 0-based identifiers of answer options, chosen by the user. May be empty if the user retracted their vote. - OptionIDs []int `json:"option_ids"` -} - -func (p *Poll) IsRegular() bool { - return p.Type == "regular" -} - -func (p *Poll) IsQuiz() bool { - return p.Type == "quiz" -} - -func (p *Poll) AddOptions(text ...string) { - for _, t := range text { - p.Options = append(p.Options, PollOption{Text: t}) - } -} diff --git a/polls.go b/polls.go new file mode 100644 index 0000000..4946cdc --- /dev/null +++ b/polls.go @@ -0,0 +1,58 @@ +package telebot + +import "time" + +// Poll contains information about a poll. +type Poll struct { + ID string `json:"id"` + Type PollType `json:"type"` + Question string `json:"question"` + Options []PollOption `json:"options"` + VoterCount int `json:"total_voter_count"` + + // (Optional) + Closed bool `json:"is_closed,omitempty"` + CorrectOption int `json:"correct_option_id,omitempty"` + MultipleAnswers bool `json:"allows_multiple_answers,omitempty"` + Explanation string `json:"explanation,omitempty"` + ParseMode ParseMode `json:"explanation_parse_mode,omitempty"` + Entities []MessageEntity `json:"explanation_entities"` + + // True by default, shouldn't be omitted. + Anonymous bool `json:"is_anonymous"` + + // (Mutually exclusive) + OpenPeriod int `json:"open_period,omitempty"` + CloseUnixdate int64 `json:"close_date,omitempty"` +} + +// PollOption contains information about one answer option in a poll. +type PollOption struct { + Text string `json:"text"` + VoterCount int `json:"voter_count"` +} + +// PollAnswer represents an answer of a user in a non-anonymous poll. +type PollAnswer struct { + PollID string `json:"poll_id"` + User User `json:"user"` + Options []int `json:"option_ids"` +} + +func (p *Poll) IsRegular() bool { + return p.Type == PollRegular +} + +func (p *Poll) IsQuiz() bool { + return p.Type == PollQuiz +} + +func (p *Poll) CloseDate() time.Time { + return time.Unix(p.CloseUnixdate, 0) +} + +func (p *Poll) AddOptions(opts ...string) { + for _, t := range opts { + p.Options = append(p.Options, PollOption{Text: t}) + } +} diff --git a/polls_test.go b/polls_test.go new file mode 100644 index 0000000..2dad87e --- /dev/null +++ b/polls_test.go @@ -0,0 +1,45 @@ +package telebot + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestPoll(t *testing.T) { + assert.True(t, (&Poll{Type: PollRegular}).IsRegular()) + assert.True(t, (&Poll{Type: PollQuiz}).IsQuiz()) + + p := &Poll{} + opts := []PollOption{{Text: "Option 1"}, {Text: "Option 2"}} + p.AddOptions(opts[0].Text, opts[1].Text) + assert.Equal(t, opts, p.Options) +} + +func TestPollSend(t *testing.T) { + p := &Poll{ + Type: PollQuiz, + Question: "Test Poll", + CloseUnixdate: time.Now().Unix() + 60, + Explanation: "Explanation", + } + p.AddOptions("1", "2") + + markup := &ReplyMarkup{ + ReplyKeyboard: [][]ReplyButton{{{ + Text: "Poll", + Poll: PollAny, + }}}, + } + + msg, err := b.Send(to, p, markup) + assert.NoError(t, err) + assert.Equal(t, p.Question, msg.Poll.Question) + assert.Equal(t, p.Options, msg.Poll.Options) + assert.Equal(t, p.CloseUnixdate, msg.Poll.CloseUnixdate) + assert.Equal(t, p.CloseDate(), msg.Poll.CloseDate()) + + _, err = b.Send(to, &Poll{}) // empty poll + assert.Equal(t, ErrBadPollOptions, err) +} diff --git a/sendable.go b/sendable.go index 1e0907a..806835f 100644 --- a/sendable.go +++ b/sendable.go @@ -317,15 +317,25 @@ func (i *Invoice) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) return extractMessage(data) } +// Send delivers poll through bot b to recipient. func (p *Poll) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "question": p.Question, - "type": p.Type, - "allows_multiple_answers": strconv.FormatBool(p.AllowsMultipleAnswers), - "correct_option_id": strconv.Itoa(p.CorrectOptionID), - "is_anonymous": strconv.FormatBool(p.IsAnonymous), - "is_closed": strconv.FormatBool(p.IsClosed), + "type": string(p.Type), + "is_closed": strconv.FormatBool(p.Closed), + "is_anonymous": strconv.FormatBool(p.Anonymous), + "allows_multiple_answers": strconv.FormatBool(p.MultipleAnswers), + "correct_option_id": strconv.Itoa(p.CorrectOption), + } + if p.Explanation != "" { + params["explanation"] = p.Explanation + params["explanation_parse_mode"] = p.ParseMode + } + if p.OpenPeriod != 0 { + params["open_period"] = strconv.Itoa(p.OpenPeriod) + } else if p.CloseUnixdate != 0 { + params["close_date"] = strconv.FormatInt(p.CloseUnixdate, 10) } embedSendOptions(params, opt) diff --git a/telebot.go b/telebot.go index d1b560c..f785594 100644 --- a/telebot.go +++ b/telebot.go @@ -188,10 +188,13 @@ const ( ) // PollType defines poll types. -type PollType = string +type PollType string const ( - PollAny PollType = "" + // Despite "any" type isn't described in documentation, + // it needed for proper KeyboardButtonPollType marshaling. + PollAny PollType = "any" + PollQuiz PollType = "quiz" PollRegular PollType = "regular" )