diff options
| author | Shulhan <m.shulhan@gmail.com> | 2020-04-10 14:27:04 +0700 |
|---|---|---|
| committer | Shulhan <m.shulhan@gmail.com> | 2020-04-11 03:50:26 +0700 |
| commit | dd21606afcd3bef6243d37592ec6ee47f1d03898 (patch) | |
| tree | 3142930cb6364bd51daa2ff0762823b2d6196558 /api/telegram/bot | |
| parent | 33b2a03dfbd90bf53e09fa2210aff8a84bf81fd8 (diff) | |
| download | pakakeh.go-dd21606afcd3bef6243d37592ec6ee47f1d03898.tar.xz | |
api/telegram/bot: Go package for Telegram API Bot
Diffstat (limited to 'api/telegram/bot')
54 files changed, 2440 insertions, 0 deletions
diff --git a/api/telegram/bot/animation.go b/api/telegram/bot/animation.go new file mode 100644 index 00000000..a3c906c8 --- /dev/null +++ b/api/telegram/bot/animation.go @@ -0,0 +1,11 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// Animation represents an animation file (GIF or H.264/MPEG-4 AVC video +// without sound). +// +type Animation Video diff --git a/api/telegram/bot/audio.go b/api/telegram/bot/audio.go new file mode 100644 index 00000000..034df5d2 --- /dev/null +++ b/api/telegram/bot/audio.go @@ -0,0 +1,21 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// Audio represents an audio file to be treated as music by the Telegram +// clients. +type Audio struct { + Document + + // Duration of the audio in seconds as defined by sender. + Duration int `json:"duration"` + + // Optional. Performer of the audio as defined by sender or by audio + // tags. + Performer string `json:"performer"` + + // Optional. Title of the audio as defined by sender or by audio tags. + Title string `json:"title"` +} diff --git a/api/telegram/bot/bot.go b/api/telegram/bot/bot.go new file mode 100644 index 00000000..7359913d --- /dev/null +++ b/api/telegram/bot/bot.go @@ -0,0 +1,459 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "log" + stdhttp "net/http" + "strconv" + + "github.com/shuLhan/share/lib/errors" + "github.com/shuLhan/share/lib/http" +) + +// List of message parse mode. +const ( + ParseModeMarkdownV2 = "MarkdownV2" + ParseModeHTML = "HTML" +) + +// +// List of Update types. +// +// This types can be used to set AllowedUpdates on Options.Webhook. +// +const ( + // New incoming message of any kind — text, photo, sticker, etc. + UpdateTypeMessage = "message" + + // New version of a message that is known to the bot and was edited. + UpdateTypeEditedMessage = "edited_message" + + // New incoming channel post of any kind — text, photo, sticker, etc. + UpdateTypeChannelPost = "channel_post" + + // New version of a channel post that is known to the bot and was + // edited. + UpdateTypeEditedChannelPost = "edited_channel_post" + + // New incoming inline query + UpdateTypeInlineQuery = "inline_query" + + // The result of an inline query that was chosen by a user and sent to + // their chat partner. + UpdateTypeChosenInlineResult = "chosen_inline_result" + + // New incoming callback query. + UpdateTypeCallbackQuery = "callback_query" + + // New incoming shipping query. + // Only for invoices with flexible price. + UpdateTypeShippingQuery = "shipping_query" + + // New incoming pre-checkout query. + // Contains full information about checkout. + UpdateTypePreCheckoutQuery = "pre_checkout_query" + + // New poll state. + // Bots receive only updates about stopped polls and polls, which are + // sent by the bot. + UpdateTypePoll = "poll" + + // A user changed their answer in a non-anonymous poll. + // Bots receive new votes only in polls that were sent by the bot + // itself. + UpdateTypePollAnswer = "poll_answer" +) + +const ( + defURL = "https://api.telegram.org/bot" +) + +// List of API methods. +const ( + methodDeleteWebhook = "deleteWebhook" + methodGetMe = "getMe" + methodGetMyComands = "getMyCommands" + methodGetWebhookInfo = "getWebhookInfo" + methodSendMessage = "sendMessage" + methodSetMyCommands = "setMyCommands" + methodSetWebhook = "setWebhook" +) + +const ( + paramNameURL = "url" + paramNameCertificate = "certificate" + paramNameMaxConnections = "max_connections" + paramNameAllowedUpdates = "allowed_updates" +) + +// +// Bot for Telegram using webHook. +// +type Bot struct { + opts Options + client *http.Client + webhook *http.Server + user *User + commands commands + err chan error +} + +// +// New create and initialize new Telegram bot. +// +func New(opts Options) (bot *Bot, err error) { + err = opts.init() + if err != nil { + return nil, fmt.Errorf("bot.New: %w", err) + } + + serverURL := defURL + opts.Token + "/" + bot = &Bot{ + opts: opts, + client: http.NewClient(serverURL), + } + + fmt.Printf("Bot options: %+v\n", opts) + fmt.Printf("Bot options Webhook: %+v\n", opts.Webhook) + + // Check if Bot Token is correct by issuing "getMe" method to API + // server. + bot.user, err = bot.GetMe() + if err != nil { + return nil, err + } + + return bot, nil +} + +// +// DeleteWebhook remove webhook integration if you decide to switch back to +// getUpdates. Returns True on success. Requires no parameters. +// +func (bot *Bot) DeleteWebhook() (err error) { + resBody, err := bot.client.PostForm(methodDeleteWebhook, nil) + if err != nil { + return fmt.Errorf("DeleteWebhook: %w", err) + } + + res := &response{} + err = json.Unmarshal(resBody, res) + if err != nil { + return fmt.Errorf("DeleteWebhook: %w", err) + } + + return nil +} + +// +// GetMe A simple method for testing your bot's auth token. +// Requires no parameters. +// Returns basic information about the bot in form of a User object. +// +func (bot *Bot) GetMe() (user *User, err error) { + resBody, err := bot.client.Get(methodGetMe, nil) + if err != nil { + return nil, fmt.Errorf("GetMe: %w", err) + } + + user = &User{} + res := &response{ + Result: user, + } + err = res.unpack(resBody) + if err != nil { + return nil, fmt.Errorf("GetMe: %w", err) + } + + return user, nil +} + +// +// GetMyCommands get the current list of the bot's commands. +// +func (bot *Bot) GetMyCommands() (cmds []Command, err error) { + resBody, err := bot.client.Get(methodGetWebhookInfo, nil) + if err != nil { + return nil, fmt.Errorf("GetMyCommands: %w", err) + } + + res := &response{ + Result: cmds, + } + err = res.unpack(resBody) + if err != nil { + return nil, fmt.Errorf("GetMyCommands: %w", err) + } + + return cmds, nil +} + +// +// GetWebhookInfo get current webhook status. Requires no parameters. +// On success, returns a WebhookInfo object. +// If the bot is using getUpdates, will return an object with the url field +// empty. +func (bot *Bot) GetWebhookInfo() (webhookInfo *WebhookInfo, err error) { + resBody, err := bot.client.Get(methodGetWebhookInfo, nil) + if err != nil { + return nil, fmt.Errorf("GetWebhookInfo: %w", err) + } + + webhookInfo = &WebhookInfo{} + res := &response{ + Result: webhookInfo, + } + err = res.unpack(resBody) + if err != nil { + return nil, fmt.Errorf("GetWebhookInfo: %w", err) + } + + return webhookInfo, nil +} + +// +// SendMessage send text messages using defined parse mode to specific +// user. +// +func (bot *Bot) SendMessage(parent *Message, parseMode, text string) ( + msg *Message, err error, +) { + req := messageRequest{ + ChatID: parent.Chat.ID, + Text: text, + ParseMode: parseMode, + } + + resBody, err := bot.client.PostJSON(methodSendMessage, req) + if err != nil { + return nil, fmt.Errorf("SendMessage: %w", err) + } + + msg = &Message{} + res := response{ + Result: msg, + } + err = res.unpack(resBody) + if err != nil { + return nil, fmt.Errorf("SendMessage: %w", err) + } + + return msg, nil +} + +// +// SetMyCommands change the list of the bot's commands. +// +// The value of each Command in the list must be valid according to +// description in Command type; this is including length and characters. +// +func (bot *Bot) SetMyCommands(cmds []Command) (err error) { + if len(cmds) == 0 { + return nil + } + for _, cmd := range cmds { + err = cmd.validate() + if err != nil { + return fmt.Errorf("SetMyCommands: %w", err) + } + } + + bot.commands.Commands = cmds + + resBody, err := bot.client.PostJSON(methodSetMyCommands, &bot.commands) + if err != nil { + return fmt.Errorf("SetMyCommands: %w", err) + } + + res := &response{} + err = res.unpack(resBody) + if err != nil { + return fmt.Errorf("SetMyCommands: %w", err) + } + + return nil +} + +// +// Start the Bot. +// +// If the Webhook option is not nil it will start listening to updates through +// webhook. +// +func (bot *Bot) Start() (err error) { + if bot.opts.Webhook != nil { + return bot.startWebhook() + } + return nil +} + +// +// Stop the Bot. +// +func (bot *Bot) Stop() (err error) { + if bot.webhook != nil { + err = bot.webhook.Shutdown(context.TODO()) + if err != nil { + log.Println("bot: Stop: ", err) + } + + bot.webhook = nil + } + + return nil +} + +func (bot *Bot) setWebhook() (err error) { + params := make(map[string][]byte) + + webhookURL := bot.opts.Webhook.URL + "/" + bot.opts.Token + + params[paramNameURL] = []byte(webhookURL) + if len(bot.opts.Webhook.Certificate) > 0 { + params[paramNameCertificate] = bot.opts.Webhook.Certificate + } + if bot.opts.Webhook.MaxConnections > 0 { + str := strconv.Itoa(bot.opts.Webhook.MaxConnections) + params[paramNameMaxConnections] = []byte(str) + } + if len(bot.opts.Webhook.AllowedUpdates) > 0 { + allowedUpdates, err := json.Marshal(&bot.opts.Webhook.AllowedUpdates) + if err != nil { + return fmt.Errorf("setWebhook: %w", err) + } + params[paramNameAllowedUpdates] = allowedUpdates + } + + resBody, err := bot.client.PostFormData(methodSetWebhook, params) + if err != nil { + return fmt.Errorf("setWebhook: %w", err) + } + + res := &response{} + + err = json.Unmarshal(resBody, res) + if err != nil { + return fmt.Errorf("setWebhook: %w", err) + } + + fmt.Printf("setWebhook: response: %+v\n", res) + + return nil +} + +// +// startWebhook start the HTTP server to receive Update from Telegram API +// server and register the Webhook. +// +func (bot *Bot) startWebhook() (err error) { + err = bot.createServer() + if err != nil { + return fmt.Errorf("startWebhook: %w", err) + } + + bot.err = make(chan error) + + go func() { + bot.err <- bot.webhook.Start() + }() + + err = bot.setWebhook() + if err != nil { + return fmt.Errorf("startWebhook: %w", err) + } + + return <-bot.err +} + +// +// createServer start the HTTP server for receiving Update. +// +func (bot *Bot) createServer() (err error) { + serverOpts := &http.ServerOptions{ + Address: bot.opts.Webhook.ListenAddress, + } + + if bot.opts.Webhook.ListenCertificate != nil { + tlsConfig := &tls.Config{} + tlsConfig.Certificates = append( + tlsConfig.Certificates, + *bot.opts.Webhook.ListenCertificate, + ) + serverOpts.Conn = &stdhttp.Server{ + TLSConfig: tlsConfig, + } + } + + bot.webhook, err = http.NewServer(serverOpts) + if err != nil { + return fmt.Errorf("createServer: %w", err) + } + + epToken := &http.Endpoint{ + Method: http.RequestMethodPost, + Path: "/" + bot.opts.Token, + RequestType: http.RequestTypeJSON, + ResponseType: http.ResponseTypeNone, + Call: bot.handleWebhook, + } + + err = bot.webhook.RegisterEndpoint(epToken) + if err != nil { + return fmt.Errorf("createServer: %w", err) + } + + return nil +} + +// +// handleWebhook handle Updates from Webhook. +// +func (bot *Bot) handleWebhook( + res stdhttp.ResponseWriter, req *stdhttp.Request, reqBody []byte, +) ( + resBody []byte, err error, +) { + update := Update{} + + err = json.Unmarshal(reqBody, &update) + if err != nil { + return nil, errors.Internal(err) + } + + var isHandled bool + + if len(bot.commands.Commands) > 0 && update.Message != nil { + isHandled = bot.handleUpdateCommand(update) + } + + // If no Command handler found, forward it to global handler. + if !isHandled { + bot.opts.HandleUpdate(update) + } + + return resBody, nil +} + +func (bot *Bot) handleUpdateCommand(update Update) bool { + ok := update.Message.parseCommandArgs() + if !ok { + return false + } + + for _, cmd := range bot.commands.Commands { + if cmd.Command == update.Message.Command { + if cmd.Handler != nil { + cmd.Handler(update) + } + return true + } + } + return false +} diff --git a/api/telegram/bot/bot_test.go b/api/telegram/bot/bot_test.go new file mode 100644 index 00000000..365e5e84 --- /dev/null +++ b/api/telegram/bot/bot_test.go @@ -0,0 +1,80 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import ( + "log" + "os" + "testing" +) + +const ( + testListenAddress = ":1928" +) + +var ( + testBot *Bot +) + +func TestMain(m *testing.M) { + startTestBot() + + os.Exit(m.Run()) +} + +func startTestBot() { + var err error + + opts := Options{ + HandleUpdate: testHandleUpdate, + Webhook: &Webhook{ + ListenAddress: testListenAddress, + }, + } + + testBot, err = New(opts) + if err != nil { + log.Println("startTestBot: ", err) + } + + if testBot != nil { + go func() { + err := testBot.Start() + if err != nil { + log.Println(err) + } + }() + } +} + +func testHandleUpdate(update Update) { + log.Printf("testHandleUpdate: %+v", update) +} + +func TestBot_GetMe(t *testing.T) { + if testBot == nil { + t.Skip() + } + + user, err := testBot.GetMe() + if err != nil { + log.Fatal(err) + } + + t.Logf("GetMe: %+v", user) +} + +func TestBot_GetWebhookInfo(t *testing.T) { + if testBot == nil { + t.Skip() + } + + whInfo, err := testBot.GetWebhookInfo() + if err != nil { + log.Fatal(err) + } + + t.Logf("GetWebhookInfo: %+v", whInfo) +} diff --git a/api/telegram/bot/callback_game.go b/api/telegram/bot/callback_game.go new file mode 100644 index 00000000..4b53d578 --- /dev/null +++ b/api/telegram/bot/callback_game.go @@ -0,0 +1,8 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// CallbackGame A placeholder, currently holds no information. +type CallbackGame struct{} diff --git a/api/telegram/bot/callback_query.go b/api/telegram/bot/callback_query.go new file mode 100644 index 00000000..b9186ffa --- /dev/null +++ b/api/telegram/bot/callback_query.go @@ -0,0 +1,41 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// CallbackQuery represents an incoming callback query from a callback button +// in an inline keyboard. +// +// If the button that originated the query was attached to a message sent by +// the bot, the field message will be present. +// If the button was attached to a message sent via the bot (in inline mode), +// the field inline_message_id will be present. +// Exactly one of the fields data or game_short_name will be present. +type CallbackQuery struct { + ID string `json:"id"` // Unique identifier for this query + From *User `json:"from"` // Sender + + // Optional. Message with the callback button that originated the + // query. Note that message content and message date will not be + // available if the message is too old. + Message *Message `json:"message"` + + // Optional. Identifier of the message sent via the bot in inline + // mode, that originated the query. + InlineMessageID string `json:"inline_message_id"` + + // Global identifier, uniquely corresponding to the chat to which the + // message with the callback button was sent. Useful for high scores + // in games. + ChatInstance string `json:"chat_instance"` + + // Optional. Data associated with the callback button. + // Be aware that a bad client can send arbitrary data in this field. + Data string `json:"data"` + + // Optional. Short name of a Game to be returned, serves as the unique + // identifier for the game. + GameShortName string `sjon:"game_short_name"` +} diff --git a/api/telegram/bot/chat.go b/api/telegram/bot/chat.go new file mode 100644 index 00000000..0b39a95d --- /dev/null +++ b/api/telegram/bot/chat.go @@ -0,0 +1,79 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// List of chat types. +const ( + ChatTypeChannel = "channel" + ChatTypeGroup = "group" + ChatTypePrivate = "private" + ChatTypeSupergroup = "supergroup" +) + +// +// Chat represents a chat. +// +type Chat struct { + + // Unique identifier for this chat. + // + // This number may be greater than 32 bits and some programming + // languages may have difficulty/silent defects in interpreting it. + // But it is smaller than 52 bits, so a signed 64 bit integer or + // double-precision float type are safe for storing this identifier. + ID int64 `json:"id"` + + // Type of chat, can be either “private”, “group”, “supergroup” or + // “channel”. + Type string `json:"type"` + + // Optional. Title, for supergroups, channels and group chats. + Title string `json:"title"` + + // Optional. Username, for private chats, supergroups and channels if + // available. + Username string `json:"username"` + + // Optional. First name of the other party in a private chat. + FirstName string `json:"first_name"` + + // Optional. Last name of the other party in a private chat. + LastName string `json:"last_name"` + + // Optional. Chat photo. Returned only in getChat. + Photo *ChatPhoto `json:"chat_photo"` + + // Optional. Description, for groups, supergroups and channel chats. + // Returned only in getChat. + Description string `json:"description"` + + // Optional. Chat invite link, for groups, supergroups and channel + // chats. + // Each administrator in a chat generates their own invite links, so + // the bot must first generate the link using exportChatInviteLink. + // Returned only in getChat. + InviteLink string `json:"invite_link"` + + // Optional. Pinned message, for groups, supergroups and channels. + // Returned only in getChat. + PinnedMesage *Message `json:"pinned_mesage"` + + // Optional. Default chat member permissions, for groups and + // supergroups. Returned only in getChat. + Permissions *ChatPermissions `json:"permissions"` + + // Optional. For supergroups, the minimum allowed delay between + // consecutive messages sent by each unpriviledged user. Returned only + // in getChat. + SlowModeDelay int `json:"slow_mode_delay"` + + // Optional. For supergroups, name of group sticker set. Returned only + // in getChat. + StickerSetName string `json:"sticker_set_name"` + + // Optional. True, if the bot can change the group sticker set. + // Returned only in getChat. + CanSetStickerSet bool `json:"can_set_sticker_set"` +} diff --git a/api/telegram/bot/chat_permissions.go b/api/telegram/bot/chat_permissions.go new file mode 100644 index 00000000..956d28e3 --- /dev/null +++ b/api/telegram/bot/chat_permissions.go @@ -0,0 +1,44 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// ChatPermissions describes actions that a non-administrator user is allowed +// to take in a chat. +// +type ChatPermissions struct { + // Optional. True, if the user is allowed to send text messages, + // contacts, locations and venues. + CanSendMessages bool `json:"can_send_messages"` + + // Optional. True, if the user is allowed to send audios, documents, + // photos, videos, video notes and voice notes, implies + // can_send_messages. + CanSendMediaMessages bool `json:"can_send_media_messages"` + + // Optional. True, if the user is allowed to send polls, implies + // can_send_messages. + CanSendPolls bool `json:"can_send_polls"` + + // Optional. True, if the user is allowed to send animations, games, + // stickers and use inline bots, implies can_send_media_messages. + CanSendOtherMessages bool `json:"can_send_other_messages"` + + // Optional. True, if the user is allowed to add web page previews to + // their messages, implies can_send_media_messages. + CanAddWebPagePreviews bool `json:"can_add_web_page_previews"` + + // Optional. True, if the user is allowed to change the chat title, + // photo and other settings. Ignored in public supergroups. + CanChangeInfo bool `json:"can_change_info"` + + // Optional. True, if the user is allowed to invite new users to the + // chat. + CanInviteUsers bool `json:"can_invite_users"` + + // Optional. True, if the user is allowed to pin messages. Ignored in + // public supergroups. + CanPinMessages bool `json:"can_pin_messages"` +} diff --git a/api/telegram/bot/chat_photo.go b/api/telegram/bot/chat_photo.go new file mode 100644 index 00000000..a4259548 --- /dev/null +++ b/api/telegram/bot/chat_photo.go @@ -0,0 +1,30 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// ChatPhoto represents a chat photo. +// +type ChatPhoto struct { + // File identifier of small (160x160) chat photo. + // This file_id can be used only for photo download and only for as + // long as the photo is not changed. + SmallFileID string `json:"small_file_id"` + + // Unique file identifier of small (160x160) chat photo, which is + // supposed to be the same over time and for different bots. + // Can't be used to download or reuse the file. + SmallFileUniqueID string `json:"small_file_unique_id"` + + // File identifier of big (640x640) chat photo. + // This file_id can be used only for photo download and only for as + // long as the photo is not changed. + BigFileID string `json:"big_file_id"` + + // Unique file identifier of big (640x640) chat photo, which is + // supposed to be the same over time and for different bots. + // Can't be used to download or reuse the file. + BigFileUniqueID string `json:"big_file_unique_id"` +} diff --git a/api/telegram/bot/chosen_inline_result.go b/api/telegram/bot/chosen_inline_result.go new file mode 100644 index 00000000..6b819b80 --- /dev/null +++ b/api/telegram/bot/chosen_inline_result.go @@ -0,0 +1,29 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// ChosenInlineResult represents a result of an inline query that was chosen +// by the user and sent to their chat partner. +// +type ChosenInlineResult struct { + // The unique identifier for the result that was chosen. + ResultID string `json:"result_id"` + + // The user that chose the result. + From *User `json:"from"` + + // The query that was used to obtain the result + Query string `json:"query"` + + // Optional. Sender location, only for bots that require user + // location. + Location *Location `json:"location"` + + // Optional. Identifier of the sent inline message. Available only if + // there is an inline keyboard attached to the message. Will be also + // received in callback queries and can be used to edit the message. + InlineMessageID string `json:"inline_message_id"` +} diff --git a/api/telegram/bot/command.go b/api/telegram/bot/command.go new file mode 100644 index 00000000..6d80343a --- /dev/null +++ b/api/telegram/bot/command.go @@ -0,0 +1,71 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import ( + "fmt" +) + +// +// Command represents a bot command. +// +type Command struct { + // Text of the command, 1-32 characters. Can contain only lowercase + // English letters, digits and underscores. + Command string `json:"command"` + + // Description of the command, 3-256 characters. + Description string `json:"description"` + + // Function that will be called when Bot receive the command. + // Handler can read command and its arguments through Message.Command + // and Message.CommandArgs. + Handler UpdateHandler `json:"-"` +} + +// +// validate will return an error if command is not valid. +// +func (cmd *Command) validate() error { + if len(cmd.Command) == 0 || len(cmd.Command) > 32 { + return errCommandLength(cmd.Command) + } + for x := 0; x < len(cmd.Command); x++ { + b := cmd.Command[x] + if b >= 'a' && b <= 'z' { + continue + } + if b >= '0' && b <= '9' { + continue + } + if b == '_' { + continue + } + return errCommandValue(cmd.Command) + } + if len(cmd.Description) < 3 || len(cmd.Description) > 256 { + return errDescLength(cmd.Command) + } + if cmd.Handler == nil { + return errHandlerNil(cmd.Command) + } + return nil +} + +func errCommandLength(cmd string) error { + return fmt.Errorf("%q: the Command length must be between 1-32 characters", cmd) +} + +func errCommandValue(cmd string) error { + return fmt.Errorf("%q: command can contain only lowercase English letter, digits, and underscores", cmd) +} + +func errDescLength(cmd string) error { + return fmt.Errorf("%q: the Description length must be between 3-256 characters", cmd) +} + +func errHandlerNil(cmd string) error { + return fmt.Errorf("%q: the Command's Handler is not set", cmd) +} diff --git a/api/telegram/bot/command_test.go b/api/telegram/bot/command_test.go new file mode 100644 index 00000000..aa38a801 --- /dev/null +++ b/api/telegram/bot/command_test.go @@ -0,0 +1,72 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import ( + "testing" + + "github.com/shuLhan/share/lib/ascii" + "github.com/shuLhan/share/lib/test" +) + +func TestCommand_validate(t *testing.T) { + s33 := string(ascii.Random([]byte(ascii.Letters), 33)) + + cases := []struct { + desc string + cmd Command + exp error + }{{ + desc: "with empty command", + cmd: Command{}, + exp: errCommandLength(""), + }, { + desc: "with invalid command character '!'", + cmd: Command{ + Command: "a!", + Description: "1234", + }, + exp: errCommandValue("a!"), + }, { + desc: "with uppercase", + cmd: Command{ + Command: "Help", + Description: string(ascii.Random([]byte(ascii.Letters), 257)), + }, + exp: errCommandValue("Help"), + }, { + desc: "with command too long", + cmd: Command{ + Command: s33, + Description: "1234", + }, + exp: errCommandLength(s33), + }, { + desc: "with description too short", + cmd: Command{ + Command: "help", + Description: "12", + }, + exp: errDescLength("help"), + }, { + desc: "with description too long", + cmd: Command{ + Command: "help", + Description: string(ascii.Random([]byte(ascii.Letters), 257)), + }, + exp: errDescLength("help"), + }, { + desc: "Perfect", + cmd: Command{ + Command: "help", + Description: "Bantuan", + Handler: func(up Update) {}, + }, + }} + + for _, c := range cases { + test.Assert(t, c.desc, c.exp, c.cmd.validate(), true) + } +} diff --git a/api/telegram/bot/commands.go b/api/telegram/bot/commands.go new file mode 100644 index 00000000..494e5747 --- /dev/null +++ b/api/telegram/bot/commands.go @@ -0,0 +1,9 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +type commands struct { + Commands []Command `json:"commands"` +} diff --git a/api/telegram/bot/contact.go b/api/telegram/bot/contact.go new file mode 100644 index 00000000..ecec3cf4 --- /dev/null +++ b/api/telegram/bot/contact.go @@ -0,0 +1,20 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// Contact represents a phone contact. +type Contact struct { + PhoneNumber string `json:"phone_number"` // Contact's phone number. + FirstName string `json:"first_name"` // Contact's first name. + + // Optional. Contact's last name. + LastName string `json:"last_name"` + + // Optional. Contact's user identifier in Telegram + UserID int64 `json:"user_id"` + + // Optional. Additional data about the contact in the form of a vCard + VCard string `json:"vcard"` +} diff --git a/api/telegram/bot/dice.go b/api/telegram/bot/dice.go new file mode 100644 index 00000000..2ab09c2a --- /dev/null +++ b/api/telegram/bot/dice.go @@ -0,0 +1,12 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// Dice represents a dice with random value from 1 to 6. (Yes, we're aware of +// the “proper” singular of die. But it's awkward, and we decided to help it +// change. One dice at a time!) +type Dice struct { + Value int `json:"value"` // Value of the dice, 1-6 +} diff --git a/api/telegram/bot/doc.go b/api/telegram/bot/doc.go new file mode 100644 index 00000000..357c36d2 --- /dev/null +++ b/api/telegram/bot/doc.go @@ -0,0 +1,12 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +// Package bot implement the Telegram Bot API +// https://core.telegram.org/bots/api. +// +// The Bot API is an HTTP-based interface created for developers keen on +// building bots for Telegram. +// +package bot diff --git a/api/telegram/bot/document.go b/api/telegram/bot/document.go new file mode 100644 index 00000000..e9979bd1 --- /dev/null +++ b/api/telegram/bot/document.go @@ -0,0 +1,30 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// Document represents a general file (as opposed to photos, voice messages +// and audio files). +type Document struct { + // Identifier for this file, which can be used to download or reuse + // the file. + FileID string `json:"file_id"` + + // Unique identifier for this file, which is supposed to be the same + // over time and for different bots. Can't be used to download or + // reuse the file. + FileUniqueID string `json:"file_unique_id"` + + // Optional. MIME type of the file as defined by sender. + MimeType string `json:"mime_type"` + + // Optional. Original filename as defined by sender. + FileName string `json:"file_name"` + + // Optional. File size. + FileSize int `json:"file_size"` + + // Optional. Document thumbnail as defined by sender. + Thumb *PhotoSize `json:"thumb"` +} diff --git a/api/telegram/bot/encrypted_credential.go b/api/telegram/bot/encrypted_credential.go new file mode 100644 index 00000000..d4a92e55 --- /dev/null +++ b/api/telegram/bot/encrypted_credential.go @@ -0,0 +1,21 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// EncryptedCredentials +type EncryptedCredentials struct { + // Base64-encoded encrypted JSON-serialized data with unique user's + // payload, data hashes and secrets required for + // EncryptedPassportElement decryption and authentication. + Data string `json:"data"` + + // Base64-encoded data hash for data authentication + Hash string `json:"hash"` + + // Base64-encoded secret, encrypted with the bot's public RSA key, + // required for data decryption. + Secret string `json:"secret"` +} diff --git a/api/telegram/bot/encrypted_passport_element.go b/api/telegram/bot/encrypted_passport_element.go new file mode 100644 index 00000000..052cc2c4 --- /dev/null +++ b/api/telegram/bot/encrypted_passport_element.go @@ -0,0 +1,96 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// EncryptedPassportElement contains information about documents or other +// Telegram Passport elements shared with the bot by the user. +// +type EncryptedPassportElement struct { + // + // Element type. One of “personal_details”, “passport”, + // “driver_license”, “identity_card”, “internal_passport”, “address”, + // “utility_bill”, “bank_statement”, “rental_agreement”, + // “passport_registration”, “temporary_registration”, “phone_number”, + // “email”. + // + Type string `json:"type"` + + // + // Optional. Base64-encoded encrypted Telegram Passport element data + // provided by the user, available for “personal_details”, “passport”, + // “driver_license”, “identity_card”, “internal_passport” and + // “address” types. + // Can be decrypted and verified using the accompanying + // EncryptedCredentials. + // + Data string `json:"data"` + + // + // Optional. User's verified phone number, available only for + // “phone_number” type. + // + PhoneNumber string `json:"phone_number"` + + // + // Optional. User's verified email address, available only for “email” + // type. + // + Email string `json:"email"` + + // + // Optional. Array of encrypted files with documents provided by the + // user, available for “utility_bill”, “bank_statement”, + // “rental_agreement”, “passport_registration” and + // “temporary_registration” types. + // Files can be decrypted and verified using the accompanying + // EncryptedCredentials. + // + Files []PassportFile `json:"files"` + + // + // Optional. Encrypted file with the front side of the document, + // provided by the user. + // Available for “passport”, “driver_license”, “identity_card” and + // “internal_passport”. The file can be decrypted and verified using + // the accompanying EncryptedCredentials. + // + FrontSide *PassportFile `json:"front_size"` + + // + // Optional. Encrypted file with the reverse side of the document, + // provided by the user. + // Available for “driver_license” and “identity_card”. + // The file can be decrypted and verified using the accompanying + // EncryptedCredentials. + // + ReverseSide *PassportFile `json:"reverse_side"` + + // + // Optional. Encrypted file with the selfie of the user holding a + // document, provided by the user; available for “passport”, + // “driver_license”, “identity_card” and “internal_passport”. + // The file can be decrypted and verified using the accompanying + // EncryptedCredentials. + // + Selfie *PassportFile `json:"selfie"` + + // + // Optional. Array of encrypted files with translated versions of + // documents provided by the user. + // Available if requested for “passport”, “driver_license”, + // “identity_card”, “internal_passport”, “utility_bill”, + // “bank_statement”, “rental_agreement”, “passport_registration” and + // “temporary_registration” types. + // + // Files can be decrypted and verified using the accompanying + // EncryptedCredentials. + // + Translation []PassportFile `json:"translation"` + + // Base64-encoded element hash for using in + // PassportElementErrorUnspecified + Hash string `json:"hash"` +} diff --git a/api/telegram/bot/game.go b/api/telegram/bot/game.go new file mode 100644 index 00000000..2860ae22 --- /dev/null +++ b/api/telegram/bot/game.go @@ -0,0 +1,36 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// Game represents a game. +// Use BotFather to create and edit games, their short names will act as +// unique identifiers. +// +type Game struct { + Title string `json:"title"` // Title of the game. + Description string `json:"description"` // Description of the game. + + // Photo that will be displayed in the game message in chats. + Photo []PhotoSize `json:"photo"` + + // + // Optional. Brief description of the game or high scores included in + // the game message. + // Can be automatically edited to include current high scores for the + // game when the bot calls setGameScore, or manually edited using + // editMessageText. + // 0-4096 characters. + // + Text string `json:"text"` + + // Optional. Special entities that appear in text, such as usernames, + // URLs, bot commands, etc. + TextEntities []MessageEntity `json:"text_entities"` + + // Optional. Animation that will be displayed in the game message in + // chats. Upload via BotFather. + Animation *Animation `json:"animation"` +} diff --git a/api/telegram/bot/inline_keyboard_button.go b/api/telegram/bot/inline_keyboard_button.go new file mode 100644 index 00000000..3bcc7f8d --- /dev/null +++ b/api/telegram/bot/inline_keyboard_button.go @@ -0,0 +1,50 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// InlineKeyboardButton represents one button of an inline keyboard. You must +// use exactly one of the optional fields. +// +type InlineKeyboardButton struct { + // Label text on the button. + Text string `json:"text"` + + // Optional. HTTP or tg:// url to be opened when button is pressed. + URL string `json:"url"` + + // Optional. An HTTP URL used to automatically authorize the user. Can + // be used as a replacement for the Telegram Login Widget. + LoginUrl *LoginUrl `json:"login_url"` + + // Optional. Data to be sent in a callback query to the bot when + // button is pressed, 1-64 bytes. + CallbackData string `json:"callback_data"` + + // Optional. If set, pressing the button will prompt the user to + // select one of their chats, open that chat and insert the bot‘s + // username and the specified inline query in the input field. Can be + // empty, in which case just the bot’s username will be inserted. + SwitchInlineQuery string `json:"switch_inline_query"` + + // Optional. If set, pressing the button will insert the bot‘s + // username and the specified inline query in the current chat's input + // field. Can be empty, in which case only the bot’s username will be + // inserted. + SwitchInlineQueryCurrentChat string `json:"switch_inline_query_current_chat"` + + // Optional. Description of the game that will be launched when the + // user presses the button. + // + // NOTE: This type of button must always be the first button in the + // first row. + CallbackGame *CallbackGame `json:"callback_game"` + + // Optional. Specify True, to send a Pay button. + // + // NOTE: This type of button must always be the first button in the + // first row. + Pay bool `json:"pay"` +} diff --git a/api/telegram/bot/inline_keyboard_markup.go b/api/telegram/bot/inline_keyboard_markup.go new file mode 100644 index 00000000..bb85c1ae --- /dev/null +++ b/api/telegram/bot/inline_keyboard_markup.go @@ -0,0 +1,15 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// InlineKeyboardMarkup represents an inline keyboard that appears right next +// to the message it belongs to. +// +type InlineKeyboardMarkup struct { + // Array of button rows, each represented by an Array of + // InlineKeyboardButton objects. + InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` +} diff --git a/api/telegram/bot/inline_query.go b/api/telegram/bot/inline_query.go new file mode 100644 index 00000000..ffd4c8b3 --- /dev/null +++ b/api/telegram/bot/inline_query.go @@ -0,0 +1,23 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// InlineQuery represents an incoming inline query. +// When the user sends an empty query, your bot could return some default or +// trending results. +// +type InlineQuery struct { + ID string `json:"id"` // Unique identifier for this qery + From *User `json:"from"` // Sender + Query string `json:"query"` // Text of the query (up to 256 characters). + + // Offset of the results to be returned, can be controlled by the bot. + Offset string `json:"offset"` + + // Optional. Sender location, only for bots that request user + // location. + Location *Location `json:"location"` +} diff --git a/api/telegram/bot/invoice.go b/api/telegram/bot/invoice.go new file mode 100644 index 00000000..b5befba6 --- /dev/null +++ b/api/telegram/bot/invoice.go @@ -0,0 +1,27 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// Invoice contains basic information about an invoice. +// +type Invoice struct { + Title string `json:"title"` // Product name + Description string `json:"description"` // Product description + + // Unique bot deep-linking parameter that can be used to generate + // this invoice. + StartParameter string `json:"start_parameter"` + + // Three-letter ISO 4217 currency code + Currency string `json:"currency"` + + // Total price in the smallest units of the currency (integer, not + // float/double). For example, for a price of US$ 1.45 pass amount = + // 145. See the exp parameter in currencies.json, it shows the number + // of digits past the decimal point for each currency (2 for the + // majority of currencies). + TotalAmount int `json:"total_amount"` +} diff --git a/api/telegram/bot/location.go b/api/telegram/bot/location.go new file mode 100644 index 00000000..0b1f1630 --- /dev/null +++ b/api/telegram/bot/location.go @@ -0,0 +1,11 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// Location represents a point on the map. +type Location struct { + Longitude float64 `json:"longitude"` // Longitude as defined by sender. + Latitude float64 `json:"latitude"` // Latitude as defined by sender. +} diff --git a/api/telegram/bot/login_url.go b/api/telegram/bot/login_url.go new file mode 100644 index 00000000..86c62796 --- /dev/null +++ b/api/telegram/bot/login_url.go @@ -0,0 +1,39 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// LoginUrl 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. +// All the user needs to do is tap/click a button and confirm that they want +// to log in. +type LoginUrl struct { + // An HTTP URL to be opened with user authorization data added to the + // query string when the button is pressed. If the user refuses to + // provide authorization data, the original URL without information + // about the user will be opened. The data added is the same as + // described in Receiving authorization data. + // + // NOTE: You must always check the hash of the received data to verify + // the authentication and the integrity of the data as described in + // Checking authorization. + Url string `json:"url"` + + // Optional. New text of the button in forwarded messages. + ForwardText string `json:"forward_text"` + + // Optional. Username of a bot, which will be used for user + // authorization. See Setting up a bot for more details. If not + // specified, the current bot's username will be assumed. The url's + // domain must be the same as the domain linked with the bot. See + // Linking your domain to the bot for more details. + BotUsername string `json:"bot_username"` + + // Optional. Pass True to request the permission for your bot to send + // messages to the user. + RequestWriteAccess bool `json:"request_write_access"` +} diff --git a/api/telegram/bot/mask_position.go b/api/telegram/bot/mask_position.go new file mode 100644 index 00000000..3b120011 --- /dev/null +++ b/api/telegram/bot/mask_position.go @@ -0,0 +1,30 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// MaskPosition describes the position on faces where a mask should be placed +// by default. +// +type MaskPosition struct { + // The part of the face relative to which the mask should be placed. + // One of “forehead”, “eyes”, “mouth”, or “chin”. + Point string `json:"point"` + + // Shift by X-axis measured in widths of the mask scaled to the face + // size, from left to right. + // For example, choosing -1.0 will place mask just to the left of the + // default mask position. + XShift float64 `json:"x_shift"` + + // Shift by Y-axis measured in heights of the mask scaled to the face + // size, from top to bottom. + // For example, 1.0 will place the mask just below the default mask + // position. + YShift float64 `json:"y_shift"` + + // Mask scaling coefficient. For example, 2.0 means double size. + Scale float64 `json:"scale"` +} diff --git a/api/telegram/bot/message.go b/api/telegram/bot/message.go new file mode 100644 index 00000000..85bfccc9 --- /dev/null +++ b/api/telegram/bot/message.go @@ -0,0 +1,191 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import "strings" + +// +// Message represents a message. +// +type Message struct { + MessageForward + + ID int `json:"message_id"` // Unique message identifier inside this chat. + Date int `json:"date"` // Date the message was sent in Unix time. + Chat *Chat `json:"chat"` // Conversation the message belongs to. + + // Optional. Sender, empty for messages sent to channels. + From *User `json:"from"` + + // Optional. Date the message was last edited in Unix time + EditDate int `json:"edit_date"` + + // Optional. For replies, the original message. + // Note that the Message object in this field will not contain further + // reply_to_message fields even if it itself is a reply. + ReplyTo *Message `json:"reply_to_message"` + + // Optional. The unique identifier of a media message group this + // message belongs to. + MediaGroupID string `json:"media_group_id"` + + // Optional. Signature of the post author for messages in channels. + AuthorSignature string `json:"author_signature"` + + // Optional. For text messages, the actual UTF-8 text of the message, + // 0-4096 characters. + Text string `json:"text"` + + // Optional. For text messages, special entities like usernames, URLs, + // bot commands, etc. that appear in the text. + Entities []MessageEntity `json:"entities"` + + // 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 an animation, information about the animation. + // For backward compatibility, when this field is set, the document + // field will also be set. + Animation *Animation `json:"animation"` + + // Optional. Message is a game, information about the game. + Game *Game `json:"game"` + + // Optional. Message is a photo, available sizes of the photo. + Photo []PhotoSize `json:"photo"` + + // Optional. Message is a sticker, information about the sticker. + Sticker *Sticker `json:"sticker"` + + // Optional. Message is a video, information about the video. + Video *Video `json:"video"` + + // Optional. Message is a voice message, information about the file. + Voice *Voice `json:"voice"` + + // Optional. Message is a video note, information about the video + // message. + VideoNote *VideoNote `json:"video_note"` + + // Optional. Caption for the animation, audio, document, photo, video + // or voice, 0-1024 characters. + Caption string `json:"caption"` + + // Optional. For messages with a caption, special entities like + // usernames, URLs, bot commands, etc. that appear in the caption. + CaptionEntities []MessageEntity `json:"caption_entities"` + + // Optional. Message is a shared contact, information about the + // contact. + Contact *Contact `json:"contact"` + + // Optional. Message is a shared location, information about the + // location. + Location *Location `json:"location"` + + // 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 dice with random value from 1 to 6. + Dice *Dice `json:"dice"` + + // Optional. New members that were added to the group or supergroup + // and information about them (the bot itself may be one of these + // members). + NewMembers []*User `json:"new_chat_members"` + + // Optional. A member was removed from the group, information about + // them (this member may be the bot itself). + LeftMembers []*User `json:"left_chat_members"` + + // Optional. A chat title was changed to this value. + NewChatTitle string `json:"new_chat_title"` + + // Optional. A chat photo was change to this value. + NewChatPhoto []PhotoSize `json:"new_chat_photo"` + + // Optional. Service message: the chat photo was deleted. + IsChatPhotoDeleted bool `json:"delete_chat_photo"` + + // Optional. Service message: the group has been created. + IsGroupChatCreated bool `json:"group_chat_created"` + + // Optional. Service message: the supergroup has been created. This + // field can‘t be received in a message coming through updates, + // because bot can’t be a member of a supergroup when it is created. + // It can only be found in reply_to_message if someone replies to a + // very first message in a directly created supergroup. + IsSupergroupChatCreated bool `json:"supergroup_chat_created"` + + // Optional. Service message: the channel has been created. + // This field can‘t be received in a message coming through updates, + // because bot can’t be a member of a channel when it is created. + // It can only be found in reply_to_message if someone replies to a + // very first message in a channel. + IsChannelChatCreated bool `json:"channel_chat_created"` + + // Optional. The group has been migrated to a supergroup with the + // specified identifier. + MigrateToChatID int64 `json:"migrate_to_chat_id"` + + // Optional. The supergroup has been migrated from a group with the + // specified identifier. + MigrateFromChatID int64 `json:"migrate_from_chat_id"` + + // Optional. Specified message was pinned. + // Note that the Message object in this field will not contain further + // reply_to_message fields even if it is itself a reply. + PinnedMessage *Message `json:"pinned_message"` + + // Optional. Message is an invoice for a payment, information about + // the invoice. + Invoice *Invoice `json:"invoice"` + + // Optional. Message is a service message about a successful payment, + // information about the payment. + SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` + + // Optional. The domain name of the website on which the user has + // logged in. + ConnectedWebsite string `json:"connected_website"` + + // Optional. Telegram Passport data. + PassportData *PassportData `json:"passport_data"` + + // Optional. Inline keyboard attached to the message. + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"` + + Command string // It will contains the Command name. + CommandArgs string // It will contains the Command arguments. +} + +// +// parseCommandArgs parse the Text to get the command and its arguments. +// +func (msg *Message) parseCommandArgs() bool { + var cmdEntity *MessageEntity + + for x, ent := range msg.Entities { + if ent.Type == EntityTypeBotCommand { + cmdEntity = &msg.Entities[x] + break + } + } + if cmdEntity == nil { + return false + } + + msg.Command = msg.Text[cmdEntity.Offset+1 : cmdEntity.Length] + start := cmdEntity.Offset + cmdEntity.Length + msg.CommandArgs = strings.TrimSpace(msg.Text[start:]) + + return true +} diff --git a/api/telegram/bot/message_entity.go b/api/telegram/bot/message_entity.go new file mode 100644 index 00000000..efff39c9 --- /dev/null +++ b/api/telegram/bot/message_entity.go @@ -0,0 +1,49 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// List of message entity types +const ( + EntityTypeMention = "mention" // @username + EntityTypeHashtag = "hashtag" // #hashtag + EntityTypeBotCommand = "bot_command" // /start@jobs_bot + EntityTypeURL = "url" // https://x.y + EntityTypeEmail = "email" // a@b.c + EntityTypePhoneNumber = "phone_number" //+1-234 + EntityTypeBold = "bold" // bold text + EntityTypeItalic = "italic" // italic text + EntityTypeUnderline = "underline" // underlined text + EntityTypeStrikethrough = "strikethrough" // strikethrough text + EntityTypeCode = "code" // monowidth string + EntityTypePre = "pre" // monowidth block + EntityTypeTextLink = "text_link" // for clickable text URLs + EntityTypeTextMention = "text_mention" // for users without usernames. +) + +// +// MessageEntity represents one special entity in a text message. For example, +// hashtags, usernames, URLs, etc. +// +type MessageEntity struct { + // Type of the entity. + Type string `json:"type"` + + // Offset in UTF-16 code units to the start of the entity. + Offset int `json:"offset"` + + // Length of the entity in UTF-16 code units. + Length int `json:"length"` + + // Optional. For “text_link” only, url that will be opened after user + // taps on the text. + URL string `json:"url"` + + // Optional. For “text_mention” only, the mentioned user. + User *User `json:"user"` + + // Optional. For “pre” only, the programming language of the entity + // text. + Language string `json:"language"` +} diff --git a/api/telegram/bot/message_forward.go b/api/telegram/bot/message_forward.go new file mode 100644 index 00000000..f3e74346 --- /dev/null +++ b/api/telegram/bot/message_forward.go @@ -0,0 +1,30 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +type MessageForward struct { + // Optional. For messages forwarded from channels, identifier of the + // original message in the channel. + ForwardID int64 `json:"forward_from_message_id"` + + // Optional. For forwarded messages, date the original message was + // sent in Unix time. + ForwardDate int64 `json:"forward_date"` + + // Optional. For forwarded messages, sender of the original message. + ForwardFrom *User `json:"forward_from"` + + // Optional. For messages forwarded from channels, information about + // the original channel. + ForwardChat *Chat `json:"forward_from_chat"` + + // Optional. For messages forwarded from channels, signature of the post + // author if present. + ForwardSignature string `json:"forward_signature"` + + // Optional. Sender's name for messages forwarded from users who + // disallow adding a link to their account in forwarded messages. + ForwardSenderName string `json:"forward_sender_name"` +} diff --git a/api/telegram/bot/message_request.go b/api/telegram/bot/message_request.go new file mode 100644 index 00000000..17f8ce7b --- /dev/null +++ b/api/telegram/bot/message_request.go @@ -0,0 +1,37 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// messageRequest represents internal message to be used on sendMessage +// +type messageRequest struct { + // Unique identifier for the target chat or username of the target + // channel (in the format @channelusername). + ChatID interface{} `json:"chat_id"` + + // Text of the message to be sent, 1-4096 characters after entities + // parsing. + Text string `json:"text"` + + // 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"` + + // Disables link previews for links in this message. + DisableWebPagePreview bool `json:"disable_web_page_preview,omitempty"` + + // Sends the message silently. Users will receive a notification with + // no sound. + DisableNotification bool `json:"disable_notification,omitempty"` + + // If the message is a reply, ID of the original message. + ReplyToMessageID int64 `json:"reply_to_message_id,omitempty"` + + // Additional interface options. A JSON-serialized object for an + // inline keyboard, custom reply keyboard, instructions to remove + // reply keyboard or to force a reply from the user. + ReplyMarkup interface{} `json:"reply_markup,omitempty"` +} diff --git a/api/telegram/bot/options.go b/api/telegram/bot/options.go new file mode 100644 index 00000000..f798ca11 --- /dev/null +++ b/api/telegram/bot/options.go @@ -0,0 +1,93 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import ( + "errors" + "os" +) + +const ( + // EnvToken define the environment variable for setting the Telegram + // Bot token. + // The environment variable has higher priority than Options parameter + // that passed in New() function. + EnvToken = "TELEGRAM_TOKEN" + + // EnvWebhookURL define the environment variable for setting the + // Telegram Webhook URL. + // The environment variable has higher priority than Options parameter + // that passed in New() function. + EnvWebhookURL = "TELEGRAM_WEBHOOK_URL" +) + +const ( + defListenAddress = ":80" + defListenAddressTLS = ":443" +) + +type UpdateHandler func(update Update) + +// +// Options to create new Bot. +// +type Options struct { + // Required. Your Bot authentication token. + // This option will be overriden by environment variable + // TELEGRAM_TOKEN. + Token string + + // Required. The function that will be called when Bot receiving + // Updates. + HandleUpdate UpdateHandler + + // Optional. Set this options if the Bot want to receive updates + // using Webhook. + Webhook *Webhook +} + +// +// init check for required fields and initialize empty fields with default +// value. +// +func (opts *Options) init() (err error) { + // Set the Telegram token and Webhook URL from environment, if its not + // empty. + env := os.Getenv(EnvToken) + if len(env) > 0 { + opts.Token = env + } + env = os.Getenv(EnvWebhookURL) + if len(env) > 0 { + if opts.Webhook == nil { + opts.Webhook = &Webhook{} + } + opts.Webhook.URL = env + } + + if len(opts.Token) == 0 { + return errors.New("empty Token") + } + if opts.HandleUpdate == nil { + return errors.New("field HandleUpdate must be set to non nil") + } + if opts.Webhook == nil { + return errors.New("empty Webhook URL") + } + if len(opts.Webhook.URL) == 0 { + // Even thought empty URL is allowed by API, which + // means to clear the previous setWebhook, use the + // DeleteWebhook instead for consistency. + return errors.New("empty Webhook URL") + } + if len(opts.Webhook.ListenAddress) == 0 { + if opts.Webhook.ListenCertificate == nil { + opts.Webhook.ListenAddress = defListenAddress + } else { + opts.Webhook.ListenAddress = defListenAddressTLS + } + } + return nil +} diff --git a/api/telegram/bot/order_info.go b/api/telegram/bot/order_info.go new file mode 100644 index 00000000..ca003cbc --- /dev/null +++ b/api/telegram/bot/order_info.go @@ -0,0 +1,22 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// OrderInfo represents information about an order. +// +type OrderInfo struct { + // Optional. User name + Name string `json:"name"` + + // Optional. User's phone number + PhoneNumber string `json:"phone_number"` + + // Optional. User email + Email string `json:"email"` + + // Optional. User shipping address + ShippingAddress *ShippingAddress `json:"shipping_address"` +} diff --git a/api/telegram/bot/passport_data.go b/api/telegram/bot/passport_data.go new file mode 100644 index 00000000..2adac683 --- /dev/null +++ b/api/telegram/bot/passport_data.go @@ -0,0 +1,17 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// PassportData contains information about Telegram Passport data shared with +// the bot by the user. +type PassportData struct { + // Array with information about documents and other Telegram Passport + // elements that was shared with the bot. + data []EncryptedPassportElement + + // Encrypted credentials required to decrypt the data. + Credentials EncryptedCredentials +} diff --git a/api/telegram/bot/passport_file.go b/api/telegram/bot/passport_file.go new file mode 100644 index 00000000..e8f001d8 --- /dev/null +++ b/api/telegram/bot/passport_file.go @@ -0,0 +1,27 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// PassportFile represents a file uploaded to Telegram Passport. +// Currently all Telegram Passport files are in JPEG format when decrypted and +// don't exceed 10MB. +// +type PassportFile struct { + // Identifier for this file, which can be used to download or reuse + // the file. + FileID string `json:"file_id"` + + // Unique identifier for this file, which is supposed to be the same + // over time and for different bots. Can't be used to download or + // reuse the file. + FileUniqueID string `json:"file_unique_id"` + + // File size. + FileSize int `json:"file_size"` + + // Unix time when the file was uploaded. + FileDate int `json:"file_date"` +} diff --git a/api/telegram/bot/photo_size.go b/api/telegram/bot/photo_size.go new file mode 100644 index 00000000..1eabf6c2 --- /dev/null +++ b/api/telegram/bot/photo_size.go @@ -0,0 +1,28 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// PhotoSize represents one size of a photo or a file / sticker thumbnail. +// +type PhotoSize struct { + // Identifier for this file, which can be used to download or reuse + // the file. + FileID string `json:"file_id"` + + // Unique identifier for this file, which is supposed to be the same + // over time and for different bots. Can't be used to download or + // reuse the file. + FileUniqueID string `json:"file_unique_id"` + + // Photo width. + Width int `json:"width"` + + // Photo height. + Height int `json:"height"` + + // Optional. File size. + FileSize int `json:"file_size"` +} diff --git a/api/telegram/bot/poll.go b/api/telegram/bot/poll.go new file mode 100644 index 00000000..1c3a7829 --- /dev/null +++ b/api/telegram/bot/poll.go @@ -0,0 +1,40 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// Poll contains information about a poll. +// +type Poll struct { + // Unique poll identifier + ID string `json:"id"` + + // Poll type, currently can be “regular” or “quiz”. + Type string `json:"type"` + + // Poll question, 1-255 characters. + Question string `json:"question"` + + // List of poll options. + Options []PollOption `json:"options"` + + // 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"` + + // Total number of users that voted in the poll. + TotalVoterCount int `json:"total_voter_count"` + + // True, if the poll is closed. + IsClosed bool `json:"is_closed"` + + // True, if the poll is anonymous. + IsAnonymous bool `json:"is_anonymous"` + + // True, if the poll allows multiple answers. + AllowsMultipleAnswers bool `json:"allow_multiple_answers"` +} diff --git a/api/telegram/bot/poll_answer.go b/api/telegram/bot/poll_answer.go new file mode 100644 index 00000000..678fb8ff --- /dev/null +++ b/api/telegram/bot/poll_answer.go @@ -0,0 +1,20 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// PollAnswer represents an answer of a user in a non-anonymous poll. +// +type PollAnswer struct { + // Unique poll identifier + PollID string `json:"poll_id"` + + // The user, who changed the answer to the poll. + 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"` +} diff --git a/api/telegram/bot/poll_option.go b/api/telegram/bot/poll_option.go new file mode 100644 index 00000000..fb2888be --- /dev/null +++ b/api/telegram/bot/poll_option.go @@ -0,0 +1,16 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// PollOption contains information about one answer option in a poll. +// +type PollOption struct { + // Option text, 1-100 characters. + Text string `json:"text"` + + // Number of users that voted for this option. + VoterCount int `json:"voter_count"` +} diff --git a/api/telegram/bot/pre_checkout_query.go b/api/telegram/bot/pre_checkout_query.go new file mode 100644 index 00000000..dec14be7 --- /dev/null +++ b/api/telegram/bot/pre_checkout_query.go @@ -0,0 +1,36 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// PreCheckoutQuery contains information about an incoming pre-checkout query. +// +type PreCheckoutQuery struct { + // Unique query identifier. + ID string `json:"id"` + + // User who sent the query. + From *User `json:"from"` + + // Three-letter ISO 4217 currency code. + Currency string `json:"currency"` + + // Total price in the smallest units of the currency (integer, not + // float/double). + // For example, for a price of US$ 1.45 pass amount = 145. + // See the exp parameter in currencies.json, it shows the number + // of digits past the decimal point for each currency (2 for the + // majority of currencies). + TotalAmount int `json:"total_amount"` + + // Bot specified invoice payload. + InvoicePayload string `json:"invoice_payload"` + + // Optional. Identifier of the shipping option chosen by the user. + ShippingOptionID string `json:"shipping_option_id"` + + // Optional. Order info provided by the user. + OrderInfo *OrderInfo `json:"order_info"` +} diff --git a/api/telegram/bot/response.go b/api/telegram/bot/response.go new file mode 100644 index 00000000..74f2503b --- /dev/null +++ b/api/telegram/bot/response.go @@ -0,0 +1,59 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import ( + "encoding/json" + "fmt" + + "github.com/shuLhan/share/lib/errors" +) + +const ( + errMigrateToChatID = "The group has been migrated to a supergroup with the specified ID %d" + errFloodControl = "Client exceeding flood control, retry after %d seconds" +) + +// +// response is the internal, generic response from API. +// +type response struct { + Ok bool `json:"ok"` + Description string `json:"description"` + ErrorCode int `json:"error_code"` + Parameters *responseParameters `json:"parameters"` + Result interface{} `json:"result"` +} + +// +// unpack the JSON response. +// +// Any non Ok response will be returned as lib/errors.E with following +// mapping: Description become E.Message, ErrorCode become E.Code. +// +func (res *response) unpack(in []byte) (err error) { + err = json.Unmarshal(in, res) + if err != nil { + return fmt.Errorf("bot: response.unpack: %w", err) + } + if !res.Ok { + var paramsInfo string + if res.Parameters != nil { + if res.Parameters.MigrateToChatID != 0 { + paramsInfo = fmt.Sprintf(errMigrateToChatID, + res.Parameters.MigrateToChatID) + } + if res.Parameters.RetryAfter != 0 { + paramsInfo += fmt.Sprintf(errFloodControl, + res.Parameters.RetryAfter) + } + } + return &errors.E{ + Code: res.ErrorCode, + Message: res.Description + "." + paramsInfo, + } + } + return nil +} diff --git a/api/telegram/bot/response_parameters.go b/api/telegram/bot/response_parameters.go new file mode 100644 index 00000000..8ff2e6b8 --- /dev/null +++ b/api/telegram/bot/response_parameters.go @@ -0,0 +1,23 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// responseParameters contains information about why a request was +// unsuccessful. +// +type responseParameters struct { + // Optional. The group has been migrated to a supergroup with the + // specified identifier. This number may be greater than 32 bits and + // some programming languages may have difficulty/silent defects in + // interpreting it. But it is smaller than 52 bits, so a signed 64 bit + // integer or double-precision float type are safe for storing this + // identifier. + MigrateToChatID int `json:"migrate_to_chat_id"` + + // Optional. In case of exceeding flood control, the number of seconds + // left to wait before the request can be repeated. + RetryAfter int `json:"retry_after"` +} diff --git a/api/telegram/bot/shipping_address.go b/api/telegram/bot/shipping_address.go new file mode 100644 index 00000000..230cb6fe --- /dev/null +++ b/api/telegram/bot/shipping_address.go @@ -0,0 +1,28 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// ShippingAddress represents a shipping address. +// +type ShippingAddress struct { + // ISO 3166-1 alpha-2 country code. + CountryCode string `json:"country_code"` + + // State, if applicable. + State string `json:"state"` + + // City. + City string `json:"city"` + + // First line for the address. + StreetLine1 string `json:"street_line1"` + + // Second line for the address. + StreetLine2 string `json:"street_line2"` + + // Address post code. + PostCode string `json:"post_code"` +} diff --git a/api/telegram/bot/shipping_query.go b/api/telegram/bot/shipping_query.go new file mode 100644 index 00000000..dabb129c --- /dev/null +++ b/api/telegram/bot/shipping_query.go @@ -0,0 +1,22 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// ShippingQuery contains information about an incoming shipping query. +// +type ShippingQuery struct { + // Unique query identifier. + ID string `json:"id"` + + // User who sent the query. + From *User `json:"from"` + + // Bot specified invoice payload. + InvoicePayload string `json:"invoice_payload"` + + // User specified shipping address. + ShippingAddress *ShippingAddress `json:"shipping_address"` +} diff --git a/api/telegram/bot/sticker.go b/api/telegram/bot/sticker.go new file mode 100644 index 00000000..095c30b5 --- /dev/null +++ b/api/telegram/bot/sticker.go @@ -0,0 +1,31 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// Sticker represents a sticker. +// +type Sticker struct { + Document + + // Sticker width. + Width int `json:"width"` + + // Sticker height. + Height int `json:"height"` + + // Optional. Emoji associated with the sticker. + Emoji string `json:"emoji"` + + // Optional. Name of the sticker set to which the sticker belongs. + SetName string `json:"set_name"` + + // Optional. For mask stickers, the position where the mask should be + // placed. + MaskPosition *MaskPosition `json:"mask_position"` + + // True, if the sticker is animated. + IsAnimated bool `json:"is_animated"` +} diff --git a/api/telegram/bot/successful_payment.go b/api/telegram/bot/successful_payment.go new file mode 100644 index 00000000..117d8101 --- /dev/null +++ b/api/telegram/bot/successful_payment.go @@ -0,0 +1,35 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// SuccessfulPayment contains basic information about a successful payment. +// +type SuccessfulPayment struct { + // Three-letter ISO 4217 currency code. + Currency string `json:"currency"` + + // Total price in the smallest units of the currency (integer, not + // float/double). For example, for a price of US$ 1.45 pass amount = + // 145. See the exp parameter in currencies.json, it shows the number + // of digits past the decimal point for each currency (2 for the + // majority of currencies). + TotalAmount int `json:"total_amount"` + + // Bot specified invoice payload. + InvoicePayload string `json:"invoice_payload"` + + // Optional. Identifier of the shipping option chosen by the user. + ShippingOptionID string `json:"shipping_option_id"` + + // Optional. Order info provided by the user. + OrderInfo *OrderInfo `json:"order_info"` + + // Telegram payment identifier. + TelegramPaymentChargeID string `json:"telegram_payment_charge_id"` + + // Provider payment identifier. + ProviderPaymentChargeID string `json:"telegram_payment_charge_id"` +} diff --git a/api/telegram/bot/update.go b/api/telegram/bot/update.go new file mode 100644 index 00000000..b0c8cbc3 --- /dev/null +++ b/api/telegram/bot/update.go @@ -0,0 +1,65 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// Update an object represents an incoming update. +// At most one of the optional parameters can be present in any given update. +// +type Update struct { + // The update‘s unique identifier. + // Update identifiers start from a certain positive number and + // increase sequentially. + // This ID becomes especially handy if you’re using Webhooks, since it + // allows you to ignore repeated updates or to restore the correct + // update sequence, should they get out of order. + // If there are no new updates for at least a week, then identifier of + // the next update will be chosen randomly instead of sequentially. + ID int64 `json:"update_id"` + + // Optional. New incoming message of any kind — text, photo, sticker, + // etc. + Message *Message `json:"Message"` + + // Optional. New version of a message that is known to the bot and was + // edited. + EditedMessage *Message `json:"edited_message"` + + // Optional. New incoming channel post of any kind — text, photo, + // sticker, etc.. + ChannelPost *Message `json:"channel_post"` + + // Optional. New version of a channel post that is known to the bot + // and was edited. + EditedChannelPost *Message `json:"edited_channel_post"` + + // Optional. New incoming inline query. + InlineQuery *InlineQuery `json:"inline_query"` + + // Optional. The result of an inline query that was chosen by a user + // and sent to their chat partner. + // Please see our documentation on the feedback collecting for details + // on how to enable these updates for your bot. + ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"` + + // Optional. New incoming callback query. + CallbackQuery *CallbackQuery `json:"callback_query"` + + // Optional. New incoming shipping query. Only for invoices with + // flexible price. + ShippingQuery *ShippingQuery `json:"shipping_query"` + + // Optional. New incoming pre-checkout query. Contains full + // information about checkout. + PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"` + + // Optional. New poll state. Bots receive only updates about stopped + // polls and polls, which are sent by the bot. + Poll *Poll `json:"poll"` + + // Optional. A user changed their answer in a non-anonymous poll. Bots + // receive new votes only in polls that were sent by the bot itself. + PollAnswer *PollAnswer `json:"poll_answer"` +} diff --git a/api/telegram/bot/user.go b/api/telegram/bot/user.go new file mode 100644 index 00000000..fee31e63 --- /dev/null +++ b/api/telegram/bot/user.go @@ -0,0 +1,40 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// User represents a Telegram user or bot. +// +type User struct { + // Unique identifier for this user or bot + ID int `json:"id"` + + // User‘s or bot’s first name. + FirstName string `json:"first_name"` + + // Optional. User‘s or bot’s last name. + LastName string `json:"last_name"` + + // Optional. User‘s or bot’s username. + Username string `json:"username"` + + // Optional. IETF language tag of the user's language. + LanguageCode string `json:"language_code"` + + // True, if this user is a bot + IsBot bool `json:"is_bot"` + + // Optional. True, if the bot can be invited to groups. Returned only + // in getMe. + CanJoinGroups bool `json:"can_join_groups"` + + // Optional. True, if privacy mode is disabled for the bot. Returned + // only in getMe. + CanReadAllGroupMessages bool `json:"can_read_all_group_messages"` + + // Optional. True, if the bot supports inline queries. Returned only + // in getMe. + SupportsInlineQueries bool `json:"supports_inline_queries"` +} diff --git a/api/telegram/bot/venue.go b/api/telegram/bot/venue.go new file mode 100644 index 00000000..edfb8298 --- /dev/null +++ b/api/telegram/bot/venue.go @@ -0,0 +1,25 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// Venue represents a venue. +type Venue struct { + // Venue location. + Location Location `json:"location"` + + // Name of the venue. + Title string `json:"title"` + + // Address of the venue. + Address string `json:"address"` + + // Optional. Foursquare identifier of the venue. + FoursquareID string `json:"foursquare_id"` + + // Optional. Foursquare type of the venue. (For example, + // “arts_entertainment/default”, “arts_entertainment/aquarium” or + // “food/icecream”). + FoursquareType string `json:"foursquare_type"` +} diff --git a/api/telegram/bot/video.go b/api/telegram/bot/video.go new file mode 100644 index 00000000..4b742326 --- /dev/null +++ b/api/telegram/bot/video.go @@ -0,0 +1,18 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// Video represents a video file. +// +type Video struct { + Document + + Width int `json:"width"` // Video width as defined by sender. + Height int `json:"height"` // Video height as defined by sender. + + // Duration of the video in seconds as defined by sender. + Duration int `json:"duration"` +} diff --git a/api/telegram/bot/video_note.go b/api/telegram/bot/video_note.go new file mode 100644 index 00000000..e9f9314e --- /dev/null +++ b/api/telegram/bot/video_note.go @@ -0,0 +1,20 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// VideoNote represents a video message (available in Telegram apps as of +// v.4.0). +// +type VideoNote struct { + Document + + // Video width and height (diameter of the video message) as defined + // by sender. + Length int `json:"length"` + + // Duration of the video in seconds as defined by sender. + Duration int `json:"duration"` +} diff --git a/api/telegram/bot/voice.go b/api/telegram/bot/voice.go new file mode 100644 index 00000000..affcc3b4 --- /dev/null +++ b/api/telegram/bot/voice.go @@ -0,0 +1,8 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// Voice represents a voice note. +type Voice Audio diff --git a/api/telegram/bot/webhook.go b/api/telegram/bot/webhook.go new file mode 100644 index 00000000..cbaf7eac --- /dev/null +++ b/api/telegram/bot/webhook.go @@ -0,0 +1,47 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +import "crypto/tls" + +// +// Webhook contains options to set Webhook to receive updates. +// +type Webhook struct { + // HTTPS url to send updates to. + // This option will be overriden by environment variable + // TELEGRAM_WEBHOOK_URL. + URL string + + // Optional. Upload your public key certificate so that the root + // certificate in use can be checked. + Certificate []byte + + // Optional. + // Maximum allowed number of simultaneous HTTPS connections + // to the webhook for update delivery, 1-100. + // Defaults to 40. + // Use lower values to limit the load on your bot‘s server, and higher + // values to increase your bot’s throughput. + MaxConnections int + + // Optional. A JSON-serialized list of the update types you want your + // bot to receive. + // For example, specify ["message", "edited_channel_post", + // "callback_query"] to only receive updates of these types. + // + // Specify an empty list to receive all updates regardless of type + // (default). If not specified, the previous setting will be used. + AllowedUpdates []string + + // Optional. The address and port where the Bot will listen for + // Webhook in the following format "<address>:<port>". + // Default to ":80" if ListenCertificate is nil or ":443" if not nil. + ListenAddress string + + // Optional. The certificate for Bot server when listening for + // Webhook. + ListenCertificate *tls.Certificate +} diff --git a/api/telegram/bot/webhook_info.go b/api/telegram/bot/webhook_info.go new file mode 100644 index 00000000..2206bf9b --- /dev/null +++ b/api/telegram/bot/webhook_info.go @@ -0,0 +1,37 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bot + +// +// WebhookInfo contains information about the current status of a webhook. +// +type WebhookInfo struct { + // Webhook URL, may be empty if webhook is not set up. + Url string `json:"url"` + + // Number of updates awaiting delivery + PendingUpdateCount int `json:"pending_update_count"` + + // Optional. Unix time for the most recent error that happened when + // trying to deliver an update via webhook. + LastErrorDate int `json:"last_error_date"` + + // Optional. Error message in human-readable format for the most + // recent error that happened when trying to deliver an update via + // webhook. + LastErrorMessage string `json:"last_error_message"` + + // Optional. Maximum allowed number of simultaneous HTTPS connections + // to the webhook for update delivery. + MaxConnections int `json:"max_connections"` + + // Optional. A list of update types the bot is subscribed to. Defaults + // to all update types. + AllowedUpdates []string `json:"allowed_updates"` + + // True, if a custom certificate was provided for webhook certificate + // checks. + HasCustomCertificate bool `json:"has_custom_certificate"` +} |
