1
0
Fork 0

🔀 Merge branch 'support/4.5' into develop

This commit is contained in:
Maxim Lebedev 2020-01-28 13:24:35 +05:00
commit 1592043597
No known key found for this signature in database
GPG Key ID: F8978F46FF0FFA4F
44 changed files with 8774 additions and 7903 deletions

56
add.go
View File

@ -1,56 +0,0 @@
package telegram
import (
"strings"
http "github.com/valyala/fasthttp"
)
type AddStickerToSetParameters struct {
// User identifier of sticker set owner
UserID int `json:"user_id"`
// Sticker set name
Name string `json:"name"`
// Png image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a file_id as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. More info on Sending Files »
PNGSticker interface{} `json:"png_sticker"`
// One or more emoji corresponding to the sticker
Emojis string `json:"emojis"`
// A JSON-serialized object for position where the mask should be placed on faces
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
}
// AddStickerToSet add a new sticker to a set created by the bot. Returns True
// on success.
func (b *Bot) AddStickerToSet(params *AddStickerToSetParameters) (bool, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.SetUint("user_id", params.UserID)
if !strings.HasSuffix(strings.ToLower(params.Name), strings.ToLower("_by_"+b.Username)) {
params.Name = params.Name + "_by_" + b.Username
}
args.Set("emojis", params.Emojis)
if params.MaskPosition != nil {
mp, err := parser.Marshal(params.MaskPosition)
if err != nil {
return false, err
}
args.SetBytesV("mask_position", mp)
}
resp, err := b.Upload(MethodAddStickerToSet, TypeSticker, "sticker", params.PNGSticker, args)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

227
answer.go
View File

@ -1,227 +0,0 @@
package telegram
type (
// AnswerCallbackQueryParameters represents data for AnswerCallbackQuery method.
AnswerCallbackQueryParameters struct {
// Unique identifier for the query to be answered
CallbackQueryID string `json:"callback_query_id"`
// Text of the notification. If not specified, nothing will be shown to the
// user, 0-200 characters
Text string `json:"text,omitempty"`
// URL that will be opened by the user's client. If you have created a Game
// and accepted the conditions via @Botfather, specify the URL that opens
// your game note that this will only work if the query comes from a
// callback_game button.
//
// Otherwise, you may use links like t.me/your_bot?start=XXXX that open your
// bot with a parameter.
URL string `json:"url,omitempty"`
// If true, an alert will be shown by the client instead of a notification at
// the top of the chat screen. Defaults to false.
ShowAlert bool `json:"show_alert,omitempty"`
// The maximum amount of time in seconds that the result of the callback
// query may be cached client-side. Telegram apps will support caching
// starting in version 3.14. Defaults to 0.
CacheTime int `json:"cache_time,omitempty"`
}
// AnswerPreCheckoutQueryParameters represents data for AnswerPreCheckoutQuery
// method.
AnswerPreCheckoutQueryParameters struct {
// Unique identifier for the query to be answered
PreCheckoutQueryID string `json:"pre_checkout_query_id"`
// Required if ok is False. Error message in human readable form that
// explains the reason for failure to proceed with the checkout (e.g. "Sorry,
// somebody just bought the last of our amazing black T-shirts while you were
// busy filling out your payment details. Please choose a different color or
// garment!"). Telegram will display this message to the user.
ErrorMessage string `json:"error_message,omitempty"`
// Specify True if everything is alright (goods are available, etc.) and the
// bot is ready to proceed with the order. Use False if there are any
// problems.
Ok bool `json:"ok"`
}
// AnswerShippingQueryParameters represents data for AnswerShippingQuery method.
AnswerShippingQueryParameters struct {
// Unique identifier for the query to be answered
ShippingQueryID string `json:"shipping_query_id"`
// Required if ok is False. Error message in human readable form that
// explains why it is impossible to complete the order (e.g. "Sorry, delivery
// to your desired address is unavailable'). Telegram will display this
// message to the user.
ErrorMessage string `json:"error_message,omitempty"`
// Specify True if delivery to the specified address is possible and False
// if there are any problems (for example, if delivery to the specified
// address is not possible)
Ok bool `json:"ok"`
// Required if ok is True. A JSON-serialized array of available shipping
// options.
ShippingOptions []ShippingOption `json:"shipping_options,omitempty"`
}
// AnswerInlineQueryParameters represents data for AnswerInlineQuery method.
AnswerInlineQueryParameters struct {
// Unique identifier for the answered query
InlineQueryID string `json:"inline_query_id"`
// Pass the offset that a client should send in the next query with the same
// text to receive more results. Pass an empty string if there are no more
// results or if you dont support pagination. Offset length cant exceed 64
// bytes.
NextOffset string `json:"next_offset,omitempty"`
// If passed, clients will display a button with specified text that switches
// the user to a private chat with the bot and sends the bot a start message
// with the parameter switch_pm_parameter
SwitchPrivateMessageText string `json:"switch_pm_text,omitempty"`
// Deep-linking parameter for the /start message sent to the bot when user
// presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and -
// are allowed.
SwitchPrivateMessageParameter string `json:"switch_pm_parameter,omitempty"`
// A JSON-serialized array of results for the inline query
Results []interface{} `json:"results"`
// The maximum amount of time in seconds that the result of the inline query
// may be cached on the server. Defaults to 300.
CacheTime int `json:"cache_time,omitempty"`
// Pass True, if results may be cached on the server side only for the user
// that sent the query. By default, results may be returned to any user who
// sends the same query
IsPersonal bool `json:"is_personal,omitempty"`
}
)
// NewAnswerCallbackQuery creates AnswerCallbackQueryParameters only with
// required parameters.
func NewAnswerCallbackQuery(callbackQueryID string) *AnswerCallbackQueryParameters {
return &AnswerCallbackQueryParameters{CallbackQueryID: callbackQueryID}
}
// NewAnswerPreCheckoutQuery creates AnswerPreCheckoutQueryParameters only with
// required parameters.
func NewAnswerPreCheckoutQuery(preCheckoutQueryID string, ok bool) *AnswerPreCheckoutQueryParameters {
return &AnswerPreCheckoutQueryParameters{
PreCheckoutQueryID: preCheckoutQueryID,
Ok: ok,
}
}
// NewAnswerShippingQuery creates AnswerShippingQueryParameters only with
// required parameters.
func NewAnswerShippingQuery(shippingQueryID string, ok bool) *AnswerShippingQueryParameters {
return &AnswerShippingQueryParameters{
ShippingQueryID: shippingQueryID,
Ok: ok,
}
}
// NewAnswerInlineQuery creates AnswerInlineQueryParameters only with required
// parameters.
func NewAnswerInlineQuery(inlineQueryID string, results ...interface{}) *AnswerInlineQueryParameters {
return &AnswerInlineQueryParameters{
InlineQueryID: inlineQueryID,
Results: results,
}
}
// AnswerCallbackQuery send answers to callback queries sent from inline
// keyboards. The answer will be displayed to the user as a notification at the
// top of the chat screen or as an alert. On success, True is returned.
//
// Alternatively, the user can be redirected to the specified Game URL. For this
// option to work, you must first create a game for your bot via @Botfather and
// accept the terms. Otherwise, you may use links like t.me/your_bot?start=XXXX
// that open your bot with a parameter.
func (bot *Bot) AnswerCallbackQuery(params *AnswerCallbackQueryParameters) (bool, error) {
dst, err := parser.Marshal(params)
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodAnswerCallbackQuery)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// AnswerPreCheckoutQuery respond to such pre-checkout queries.
//
// Once the user has confirmed their payment and shipping details, the Bot API
// sends the final confirmation in the form of an Update with the field
// pre_checkout_query. Use this method to respond to such pre-checkout queries.
// On success, True is returned.
//
// Note: The Bot API must receive an answer within 10 seconds after the
// pre-checkout query was sent.
func (bot *Bot) AnswerPreCheckoutQuery(params *AnswerShippingQueryParameters) (bool, error) {
dst, err := parser.Marshal(params)
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodAnswerPreCheckoutQuery)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// AnswerShippingQuery reply to shipping queries.
//
// If you sent an invoice requesting a shipping address and the parameter
// is_flexible was specified, the Bot API will send an Update with a
// shipping_query field to the bot. On success, True is returned.
func (bot *Bot) AnswerShippingQuery(params *AnswerShippingQueryParameters) (bool, error) {
dst, err := parser.Marshal(params)
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodAnswerShippingQuery)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// AnswerInlineQuery send answers to an inline query. On success, True is returned.
//
// No more than 50 results per query are allowed.
func (bot *Bot) AnswerInlineQuery(params *AnswerInlineQueryParameters) (bool, error) {
dst, err := parser.Marshal(params)
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodAnswerInlineQuery)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

337
bot.go Normal file
View File

@ -0,0 +1,337 @@
package telegram
import (
"bytes"
"io"
"log"
"mime/multipart"
"net"
"path"
"path/filepath"
"strings"
"time"
json "github.com/json-iterator/go"
"github.com/kirillDanshin/dlog"
http "github.com/valyala/fasthttp"
)
// Bot represents a bot user with access token getted from @BotFather.
type Bot struct {
*User
AccessToken string
Updates chan *Update
client *http.Client
marshler json.API
}
// New creates a new default Bot structure based on the input access token.
func New(accessToken string) (b *Bot, err error) {
b = new(Bot)
b.marshler = json.ConfigFastest
b.SetClient(&http.Client{})
b.AccessToken = accessToken
b.User, err = b.GetMe()
return b, err
}
// SetClient allow set custom fasthttp.Client (for proxy traffic, for example).
func (b *Bot) SetClient(newClient *http.Client) {
if b == nil {
b = new(Bot)
}
b.client = newClient
}
func (b Bot) Do(method string, payload interface{}) ([]byte, error) {
u := http.AcquireURI()
defer http.ReleaseURI(u)
u.SetScheme("https")
u.SetHost("api.telegram.org")
u.SetPath(path.Join("bot"+b.AccessToken, method))
var buf bytes.Buffer
if err := b.marshler.NewEncoder(&buf).Encode(payload); err != nil {
return nil, err
}
req := http.AcquireRequest()
defer http.ReleaseRequest(req)
req.Header.SetMethod(http.MethodPost)
req.SetRequestURIBytes(u.RequestURI())
req.Header.SetContentType("application/json")
req.SetBody(buf.Bytes())
resp := http.AcquireResponse()
defer http.ReleaseResponse(resp)
if err := b.client.Do(req, resp); err != nil {
return nil, err
}
return resp.Body(), nil
}
func (b Bot) Upload(method string, payload map[string]string, files ...*InputFile) ([]byte, error) {
if len(files) == 0 {
return b.Do(method, payload)
}
body := new(bytes.Buffer)
w := multipart.NewWriter(body)
for i := range files {
_, fileName := filepath.Split(files[i].Attachment.Name())
part, err := w.CreateFormFile(fileName, fileName)
if err != nil {
return nil, err
}
if _, err = io.Copy(part, files[i].Attachment); err != nil {
return nil, err
}
}
for key, val := range payload {
if err := w.WriteField(key, val); err != nil {
return nil, err
}
}
if err := w.Close(); err != nil {
return nil, err
}
u := http.AcquireURI()
defer http.ReleaseURI(u)
u.SetScheme("https")
u.SetHost("api.telegram.org")
u.SetPath(path.Join("bot"+b.AccessToken, method))
req := http.AcquireRequest()
defer http.ReleaseRequest(req)
req.Header.SetMethod(http.MethodPost)
req.SetRequestURIBytes(u.RequestURI())
req.Header.SetContentType(w.FormDataContentType())
req.Header.SetMultipartFormBoundary(w.Boundary())
if _, err := body.WriteTo(req.BodyWriter()); err != nil {
return nil, err
}
resp := http.AcquireResponse()
defer http.ReleaseResponse(resp)
if err := b.client.Do(req, resp); err != nil {
return nil, err
}
return resp.Body(), nil
}
// IsMessageFromMe checks that the input message is a message from the current bot.
func (b Bot) IsMessageFromMe(m Message) bool {
return b.User != nil && m.From != nil && m.From.ID == b.ID
}
// IsForwardFromMe checks that the input message is a forwarded message from the current bot.
func (b Bot) IsForwardFromMe(m Message) bool {
return b.User != nil && m.IsForward() && m.ForwardFrom.ID == b.ID
}
// IsReplyToMe checks that the input message is a reply to the current bot.
func (b Bot) IsReplyToMe(m Message) bool {
return m.Chat.IsPrivate() || (m.IsReply() && b.IsMessageFromMe(*m.ReplyToMessage))
}
// IsCommandToMe checks that the input message is a command for the current bot.
func (b Bot) IsCommandToMe(m Message) bool {
if !m.IsCommand() {
return false
}
if m.Chat.IsPrivate() {
return true
}
parts := strings.Split(m.RawCommand(), "@")
if len(parts) <= 1 {
return false
}
return strings.EqualFold(parts[1], b.User.Username)
}
// IsMessageMentionsMe checks that the input message mentions the current bot.
func (b Bot) IsMessageMentionsMe(m Message) bool {
if b.User == nil {
return false
}
if b.IsCommandToMe(m) {
return true
}
var entities []*MessageEntity
switch {
case m.HasMentions():
entities = m.Entities
case m.HasCaptionMentions():
entities = m.CaptionEntities
}
for _, entity := range entities {
if entity.IsMention() && entity.User.ID == b.ID {
return true
}
}
return false
}
// IsForwardMentionsMe checks that the input forwarded message mentions the current bot.
func (b Bot) IsForwardMentionsMe(m Message) bool { return m.IsForward() && b.IsMessageMentionsMe(m) }
// IsReplyMentionsMe checks that the input message mentions the current b.
func (b Bot) IsReplyMentionsMe(m Message) bool {
return m.IsReply() && b.IsMessageMentionsMe(*m.ReplyToMessage)
}
// IsMessageToMe checks that the input message is addressed to the current b.
func (b Bot) IsMessageToMe(m Message) bool {
return m.Chat != nil && (m.Chat.IsPrivate() || b.IsCommandToMe(m) || b.IsReplyToMe(m) ||
b.IsMessageMentionsMe(m))
}
// NewFileURL creates a fasthttp.URI to file with path getted from GetFile method.
func (b Bot) NewFileURL(filePath string) *http.URI {
if b.AccessToken == "" || filePath == "" {
return nil
}
result := http.AcquireURI()
result.SetScheme("https")
result.SetHost("api.telegram.org")
result.SetPath(path.Join("file", "bot"+b.AccessToken, filePath))
return result
}
// NewRedirectURL creates new fasthttp.URI for redirecting from one chat to another.
func (b Bot) NewRedirectURL(param string, group bool) *http.URI {
if b.User == nil || b.User.Username == "" {
return nil
}
link := http.AcquireURI()
link.SetScheme("https")
link.SetHost("t.me")
link.SetPath(b.User.Username)
q := link.QueryArgs()
key := "start"
if group {
key += "group"
}
q.Set(key, param)
link.SetQueryStringBytes(q.QueryString())
return link
}
// NewLongPollingChannel creates channel for receive incoming updates using long polling.
func (b *Bot) NewLongPollingChannel(params *GetUpdates) chan *Update {
if params == nil {
params = &GetUpdates{
Offset: 0,
Limit: 100,
Timeout: 60,
}
}
b.Updates = make(chan *Update, params.Limit)
go func() {
for {
updates, err := b.GetUpdates(params)
if err != nil {
dlog.Ln(err.Error())
dlog.Ln("Failed to get updates, retrying in 3 seconds...")
time.Sleep(time.Second * 3)
continue
}
for _, update := range updates {
if update.UpdateID < params.Offset {
continue
}
params.Offset = update.UpdateID + 1
b.Updates <- update
}
}
}()
return b.Updates
}
// NewWebhookChannel creates channel for receive incoming updates via an outgoing webhook.
//
// If cert argument is provided by two strings (["path/to/cert.file", "path/to/cert.key"]), then TLS server will be created by this filepaths.
func (b *Bot) NewWebhookChannel(u *http.URI, p SetWebhook, ln net.Listener, crt ...string) (chan *Update, func() error) {
b.Updates = make(chan *Update, 100)
handleFunc := func(ctx *http.RequestCtx) {
dlog.Ln("Request path:", string(ctx.Path()))
if !bytes.HasPrefix(ctx.Path(), u.Path()) {
dlog.Ln("Unsupported request path:", string(ctx.Path()))
return
}
dlog.Ln("Catched supported request path:", string(ctx.Path()))
upd := new(Update)
if err := b.marshler.Unmarshal(ctx.Request.Body(), upd); err != nil {
return
}
b.Updates <- upd
}
srv := http.Server{
Name: b.Username,
Concurrency: p.MaxConnections,
Handler: handleFunc,
ReduceMemoryUsage: true,
}
var err error
go func() {
switch {
case len(crt) == 2:
dlog.Ln("Creating TLS router...")
err = srv.ServeTLS(ln, crt[0], crt[1])
default:
dlog.Ln("Creating simple router...")
err = srv.Serve(ln)
}
if err != nil {
log.Fatalln(err.Error())
}
}()
if _, err = b.SetWebhook(p); err != nil {
log.Fatalln(err.Error())
}
return b.Updates, srv.Shutdown
}

185
const.go
View File

@ -1,7 +1,9 @@
package telegram
import "github.com/Masterminds/semver"
// Version represents current version of Telegram API supported by this package
const Version string = "4.4"
var Version = semver.MustParse("4.5.0") //nolint: gochecknoglobals
// Action represents available and supported status actions of bot
const (
@ -25,106 +27,116 @@ const (
ChatSuperGroup string = "supergroup"
)
// Command represents global commands which should be supported by any bot.
// You can user IsCommandEqual method of Message for checking.
// Command represents global commands which should be supported by any bot. You can user IsCommandEqual method of Message for checking.
//
// See: https://core.telegram.org/bots#global-commands
const (
CommandStart string = "start"
CommandHelp string = "help"
CommandSettings string = "settings"
CommandStart string = "start"
)
// Entity represents available and supported entity types
const (
EntityBold string = "bold"
EntityBotCommand string = "bot_command"
EntityCashtag string = "cashtag"
EntityCode string = "code"
EntityEmail string = "email"
EntityHashtag string = "hashtag"
EntityItalic string = "italic"
EntityMention string = "mention"
EntityPhoneNumber string = "phone_number"
EntityPre string = "pre"
EntityTextLink string = "text_link"
EntityTextMention string = "text_mention"
EntityURL string = "url"
EntityBold string = "bold"
EntityBotCommand string = "bot_command"
EntityCashtag string = "cashtag"
EntityCode string = "code"
EntityEmail string = "email"
EntityHashtag string = "hashtag"
EntityItalic string = "italic"
EntityMention string = "mention"
EntityPhoneNumber string = "phone_number"
EntityPre string = "pre"
EntityStrikethrough string = "strikethrough"
EntityTextLink string = "text_link"
EntityTextMention string = "text_mention"
EntityUnderline string = "underline"
EntityURL string = "url"
)
// Method represents available and supported Telegram API methods
const (
MethodAddStickerToSet string = "addStickerToSet"
MethodAnswerCallbackQuery string = "answerCallbackQuery"
MethodAnswerInlineQuery string = "answerInlineQuery"
MethodAnswerPreCheckoutQuery string = "answerPreCheckoutQuery"
MethodAnswerShippingQuery string = "answerShippingQuery"
MethodCreateNewStickerSet string = "createNewStickerSet"
MethodDeleteChatPhoto string = "deleteChatPhoto"
MethodDeleteChatStickerSet string = "deleteChatStickerSet"
MethodDeleteMessage string = "deleteMessage"
MethodDeleteStickerFromSet string = "deleteStickerFromSet"
MethodDeleteWebhook string = "deleteWebhook"
MethodEditMessageCaption string = "editMessageCaption"
MethodEditMessageLiveLocation string = "editMessageLiveLocation"
MethodEditMessageMedia string = "editMessageMedia"
MethodEditMessageReplyMarkup string = "editMessageReplyMarkup"
MethodEditMessageText string = "editMessageText"
MethodExportChatInviteLink string = "exportChatInviteLink"
MethodForwardMessage string = "forwardMessage"
MethodGetChat string = "getChat"
MethodGetChatAdministrators string = "getChatAdministrators"
MethodGetChatMember string = "getChatMember"
MethodGetChatMembersCount string = "getChatMembersCount"
MethodGetFile string = "getFile"
MethodGetGameHighScores string = "getGameHighScores"
MethodGetMe string = "getMe"
MethodGetStickerSet string = "getStickerSet"
MethodGetUpdates string = "getUpdates"
MethodGetUserProfilePhotos string = "getUserProfilePhotos"
MethodGetWebhookInfo string = "getWebhookInfo"
MethodKickChatMember string = "kickChatMember"
MethodLeaveChat string = "leaveChat"
MethodPinChatMessage string = "pinChatMessage"
MethodPromoteChatMember string = "promoteChatMember"
MethodRestrictChatMember string = "restrictChatMember"
MethodSendAnimation string = "sendAnimation"
MethodSendAudio string = "sendAudio"
MethodSendChatAction string = "sendChatAction"
MethodSendContact string = "sendContact"
MethodSendDocument string = "sendDocument"
MethodSendGame string = "sendGame"
MethodSendInvoice string = "sendInvoice"
MethodSendLocation string = "sendLocation"
MethodSendMediaGroup string = "sendMediaGroup"
MethodSendMessage string = "sendMessage"
MethodSendPhoto string = "sendPhoto"
MethodSendPoll string = "sendPoll"
MethodSendSticker string = "sendSticker"
MethodSendVenue string = "sendVenue"
MethodSendVideo string = "sendVideo"
MethodSendVideoNote string = "sendVideoNote"
MethodSendVoice string = "sendVoice"
MethodSetChatDescription string = "setChatDescription"
MethodSetChatPermissions string = "setChatPermissions"
MethodSetChatPhoto string = "setChatPhoto"
MethodSetChatStickerSet string = "setChatStickerSet"
MethodSetChatTitle string = "setChatTitle"
MethodSetGameScore string = "setGameScore"
MethodSetPassportDataErrors string = "setPassportDataErrors"
MethodSetStickerPositionInSet string = "setStickerPositionInSet"
MethodSetWebhook string = "setWebhook"
MethodStopMessageLiveLocation string = "stopMessageLiveLocation"
MethodStopPoll string = "stopPoll"
MethodUnbanChatMember string = "unbanChatMember"
MethodUnpinChatMessage string = "unpinChatMessage"
MethodUploadStickerFile string = "uploadStickerFile"
MethodAddStickerToSet string = "addStickerToSet"
MethodAnswerCallbackQuery string = "answerCallbackQuery"
MethodAnswerInlineQuery string = "answerInlineQuery"
MethodAnswerPreCheckoutQuery string = "answerPreCheckoutQuery"
MethodAnswerShippingQuery string = "answerShippingQuery"
MethodCreateNewStickerSet string = "createNewStickerSet"
MethodDeleteChatPhoto string = "deleteChatPhoto"
MethodDeleteChatStickerSet string = "deleteChatStickerSet"
MethodDeleteMessage string = "deleteMessage"
MethodDeleteStickerFromSet string = "deleteStickerFromSet"
MethodDeleteWebhook string = "deleteWebhook"
MethodEditMessageCaption string = "editMessageCaption"
MethodEditMessageLiveLocation string = "editMessageLiveLocation"
MethodEditMessageMedia string = "editMessageMedia"
MethodEditMessageReplyMarkup string = "editMessageReplyMarkup"
MethodEditMessageText string = "editMessageText"
MethodExportChatInviteLink string = "exportChatInviteLink"
MethodForwardMessage string = "forwardMessage"
MethodGetChat string = "getChat"
MethodGetChatAdministrators string = "getChatAdministrators"
MethodGetChatMember string = "getChatMember"
MethodGetChatMembersCount string = "getChatMembersCount"
MethodGetFile string = "getFile"
MethodGetGameHighScores string = "getGameHighScores"
MethodGetMe string = "getMe"
MethodGetStickerSet string = "getStickerSet"
MethodGetUpdates string = "getUpdates"
MethodGetUserProfilePhotos string = "getUserProfilePhotos"
MethodGetWebhookInfo string = "getWebhookInfo"
MethodKickChatMember string = "kickChatMember"
MethodLeaveChat string = "leaveChat"
MethodPinChatMessage string = "pinChatMessage"
MethodPromoteChatMember string = "promoteChatMember"
MethodRestrictChatMember string = "restrictChatMember"
MethodSendAnimation string = "sendAnimation"
MethodSendAudio string = "sendAudio"
MethodSendChatAction string = "sendChatAction"
MethodSendContact string = "sendContact"
MethodSendDocument string = "sendDocument"
MethodSendGame string = "sendGame"
MethodSendInvoice string = "sendInvoice"
MethodSendLocation string = "sendLocation"
MethodSendMediaGroup string = "sendMediaGroup"
MethodSendMessage string = "sendMessage"
MethodSendPhoto string = "sendPhoto"
MethodSendPoll string = "sendPoll"
MethodSendSticker string = "sendSticker"
MethodSendVenue string = "sendVenue"
MethodSendVideo string = "sendVideo"
MethodSendVideoNote string = "sendVideoNote"
MethodSendVoice string = "sendVoice"
MethodSetChatAdministratorCustomTitle string = "setChatAdministratorCustomTitle"
MethodSetChatDescription string = "setChatDescription"
MethodSetChatPermissions string = "setChatPermissions"
MethodSetChatPhoto string = "setChatPhoto"
MethodSetChatStickerSet string = "setChatStickerSet"
MethodSetChatTitle string = "setChatTitle"
MethodSetGameScore string = "setGameScore"
MethodSetPassportDataErrors string = "setPassportDataErrors"
MethodSetStickerPositionInSet string = "setStickerPositionInSet"
MethodSetWebhook string = "setWebhook"
MethodStopMessageLiveLocation string = "stopMessageLiveLocation"
MethodStopPoll string = "stopPoll"
MethodUnbanChatMember string = "unbanChatMember"
MethodUnpinChatMessage string = "unpinChatMessage"
MethodUploadStickerFile string = "uploadStickerFile"
)
// Mode represents available and supported parsing modes of messages
const (
StyleHTML string = "html"
StyleMarkdown string = "markdown"
ParseModeHTML string = "HTML"
ParseModeMarkdown string = "Markdown"
ParseModeMarkdownV2 string = "MarkdownV2"
)
const (
PointForehead string = "forehead"
PointEyes string = "eyes"
PointMouth string = "mouth"
PointChin string = "chin"
)
// Mime represents available and supported MIME types of data
@ -194,3 +206,8 @@ const (
UpdatePreCheckoutQuery string = "pre_checkout_query"
UpdateShippingQuery string = "shipping_query"
)
const (
DefaultAudioSeparator = " "
DefaultAudioTitle = "[untitled]"
)

View File

@ -1,76 +0,0 @@
package telegram
import (
"strconv"
"strings"
http "github.com/valyala/fasthttp"
)
type CreateNewStickerSetParameters struct {
// User identifier of created sticker set owner
UserID int `json:"user_id"`
// Short name of sticker set, to be used in t.me/addstickers/ URLs
// (e.g., animals). Can contain only english letters, digits and
// underscores. Must begin with a letter, can't contain consecutive
// underscores and must end in “_by_<bot username>”. <bot_username>
// is case insensitive. 1-64 characters.
Name string `json:"name"`
// Sticker set title, 1-64 characters
Title string `json:"title"`
// Png image with the sticker, must be up to 512 kilobytes in size,
// dimensions must not exceed 512px, and either width or height must
// be exactly 512px. Pass a file_id as a String to send a file that
// already exists on the Telegram servers, pass an HTTP URL as a
// String for Telegram to get a file from the Internet, or upload
// a new one using multipart/form-data.
PNGSticker interface{} `json:"png_sticker"`
// One or more emoji corresponding to the sticker
Emojis string `json:"emojis"`
// Pass True, if a set of mask stickers should be created
ContainsMasks bool `json:"contains_masks,omitempty"`
// A JSON-serialized object for position where the mask should be
// placed on faces
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
}
// CreateNewStickerSet create new sticker set owned by a user. The bot will be
// able to edit the created sticker set. Returns True on success.
func (b *Bot) CreateNewStickerSet(params *CreateNewStickerSetParameters) (bool, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.SetUint("user_id", params.UserID)
if !strings.HasSuffix(strings.ToLower(params.Name), strings.ToLower("_by_"+b.Username)) {
params.Name = params.Name + "_by_" + b.Username
}
args.Set("name", params.Name)
args.Set("title", params.Title)
args.Set("emojis", params.Emojis)
args.Set("contains_masks", strconv.FormatBool(params.ContainsMasks))
if params.MaskPosition != nil {
mp, err := parser.Marshal(params.MaskPosition)
if err != nil {
return false, err
}
args.SetBytesV("mask_position", mp)
}
resp, err := b.Upload(MethodCreateNewStickerSet, TypeSticker, "sticker", params.PNGSticker, args)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

129
delete.go
View File

@ -1,129 +0,0 @@
package telegram
type (
// DeleteChatPhotoParameters represents data for DeleteChatPhoto method.
DeleteChatPhotoParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// DeleteChatStickerSetParameters represents data for DeleteChatStickerSet method.
DeleteChatStickerSetParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// DeleteMessageParameters represents data for DeleteMessage method.
DeleteMessageParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Identifier of the message to delete
MessageID int `json:"message_id"`
}
// DeleteStickerFromSetParameters represents data for DeleteStickerFromSet method.
DeleteStickerFromSetParameters struct {
// File identifier of the sticker
Sticker string `json:"sticker"`
}
)
// DeleteChatPhoto delete a chat photo. Photos can't be changed for private
// chats. The bot must be an administrator in the chat for this to work and must
// have the appropriate admin rights. Returns True on success.
//
// Note: In regular groups (non-supergroups), this method will only work if the
// 'All Members Are Admins' setting is off in the target group.
func (bot *Bot) DeleteChatPhoto(chatID int64) (bool, error) {
dst, err := parser.Marshal(&DeleteChatPhotoParameters{ChatID: chatID})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodDeleteChatPhoto)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// DeleteChatStickerSet delete a group sticker set from a supergroup. The bot must be an administrator
// in the chat for this to work and must have the appropriate admin rights. Use the field
// can_set_sticker_set optionally returned in getChat requests to check if the bot can use this
// method. Returns True on success.
func (bot *Bot) DeleteChatStickerSet(chatID int64) (bool, error) {
dst, err := parser.Marshal(&DeleteChatStickerSetParameters{ChatID: chatID})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodDeleteChatStickerSet)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// DeleteWebhook remove webhook integration if you decide to switch back to
// getUpdates. Returns True on success. Requires no parameters.
func (bot *Bot) DeleteWebhook() (bool, error) {
resp, err := bot.request(nil, MethodDeleteWebhook)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// DeleteMessage delete a message, including service messages, with the following
// limitations: A message can only be deleted if it was sent less than 48 hours
// ago; Bots can delete outgoing messages in groups and supergroups; Bots granted
// can_post_messages permissions can delete outgoing messages in channels; If the
// bot is an administrator of a group, it can delete any message there; If the
// bot has can_delete_messages permission in a supergroup or a channel, it can
// delete any message there. Returns True on success.
func (bot *Bot) DeleteMessage(chatID int64, messageID int) (bool, error) {
dst, err := parser.Marshal(&DeleteMessageParameters{
ChatID: chatID,
MessageID: messageID,
})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodDeleteMessage)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// DeleteStickerFromSet delete a sticker from a set created by the bot. Returns
// True on success.
func (bot *Bot) DeleteStickerFromSet(sticker string) (bool, error) {
dst, err := parser.Marshal(&DeleteStickerFromSetParameters{Sticker: sticker})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodDeleteStickerFromSet)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

237
edit.go
View File

@ -1,237 +0,0 @@
package telegram
type (
// EditMessageLiveLocationParameters represents data for EditMessageLiveLocation
// method.
EditMessageLiveLocationParameters struct {
// Required if inline_message_id is not specified. Unique identifier for the
// target chat or username of the target channel (in the format
// @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// Latitude of new location
Latitude float32 `json:"latitude"`
// Longitude of new location
Longitude float32 `json:"longitude"`
// A JSON-serialized object for a new inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageTextParameters represents data for EditMessageText method.
EditMessageTextParameters struct {
// Required if inline_message_id is not specified. Unique identifier for the
// target chat or username of the target channel (in the format
// @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// New text of the message
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"`
// A JSON-serialized object for an inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageCaptionParameters represents data for EditMessageCaption method.
EditMessageCaptionParameters struct {
// Required if inline_message_id is not specified. Unique identifier for the
// target chat or username of the target channel (in the format
// @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of
// the sent message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// New caption of the message
Caption string `json:"caption,omitempty"`
// Send Markdown or HTML, if you want Telegram apps to show bold, italic,
// fixed-width text or inline URLs in the media caption.
ParseMode string `json:"parse_mode,omitempty"`
// A JSON-serialized object for an inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageMediaParameters represents data for EditMessageMedia method.
EditMessageMediaParameters struct {
// Required if inline_message_id is not specified. Unique identifier for the target chat or username of the target channel (in the format @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// A JSON-serialized object for a new media content of the message
Media interface{} `json:"media"`
// A JSON-serialized object for a new inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageReplyMarkupParameters represents data for EditMessageReplyMarkup method.
EditMessageReplyMarkupParameters struct {
// Required if inline_message_id is not specified. Unique identifier for the
// target chat or username of the target channel (in the format
// @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
)
// NewLiveLocation creates EditMessageLiveLocationParameters only with required
// parameters.
func NewLiveLocation(latitude, longitude float32) *EditMessageLiveLocationParameters {
return &EditMessageLiveLocationParameters{
Latitude: latitude,
Longitude: longitude,
}
}
// NewMessageText creates EditMessageTextParameters only with required parameters.
func NewMessageText(text string) *EditMessageTextParameters {
return &EditMessageTextParameters{
Text: text,
}
}
// EditMessageLiveLocation edit live location messages sent by the bot or via the
// bot (for inline bots). A location can be edited until its live_period expires
// or editing is explicitly disabled by a call to stopMessageLiveLocation. On
// success, if the edited message was sent by the bot, the edited Message is
// returned, otherwise True is returned.
func (bot *Bot) EditMessageLiveLocation(params *EditMessageLiveLocationParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodEditMessageLiveLocation)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// EditMessageText edit text and game messages sent by the bot or via the bot
// (for inline bots). On success, if edited message is sent by the bot, the
// edited Message is returned, otherwise True is returned.
func (bot *Bot) EditMessageText(params *EditMessageTextParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodEditMessageText)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// EditMessageCaption edit captions of messages sent by the bot or via the bot
// (for inline bots). On success, if edited message is sent by the bot, the
// edited Message is returned, otherwise True is returned.
func (bot *Bot) EditMessageCaption(params *EditMessageCaptionParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodEditMessageCaption)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// EditMessageMedia edit audio, document, photo, or video messages. If a message
// is a part of a message album, then it can be edited only to a photo or a video.
// Otherwise, message type can be changed arbitrarily. When inline message is
// edited, new file can't be uploaded. Use previously uploaded file via its
// file_id or specify a URL. On success, if the edited message was sent by the
// bot, the edited Message is returned, otherwise True is returned.
func (b *Bot) EditMessageMedia(emmp *EditMessageMediaParameters) (*Message, error) {
src, err := parser.Marshal(emmp)
if err != nil {
return nil, err
}
resp, err := b.request(src, MethodEditMessageMedia)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// EditMessageReplyMarkup edit only the reply markup of messages sent by the bot
// or via the bot (for inline bots). On success, if edited message is sent by the
// bot, the edited Message is returned, otherwise True is returned.
func (bot *Bot) EditMessageReplyMarkup(params *EditMessageReplyMarkupParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodEditMessageReplyMarkup)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}

29
errors.go Normal file
View File

@ -0,0 +1,29 @@
package telegram
import (
"fmt"
"golang.org/x/xerrors"
)
type Error struct {
Code int `json:"error_code"`
Description string `json:"description"`
Parameters []*ResponseParameters `json:"parameters,omitempty"`
frame xerrors.Frame
}
func (e Error) FormatError(p xerrors.Printer) error {
p.Printf("%d %s", e.Code, e.Description)
e.frame.Format(p)
return nil
}
func (e Error) Format(s fmt.State, r rune) {
xerrors.FormatError(e, s, r)
}
func (e Error) Error() string {
return fmt.Sprint(e)
}

View File

@ -1,26 +0,0 @@
package telegram
// ExportChatInviteLinkParameters represents data for ExportChatInviteLink method.
type ExportChatInviteLinkParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// ExportChatInviteLink export an invite link to a supergroup or a channel. The
// bot must be an administrator in the chat for this to work and must have the
// appropriate admin rights. Returns exported invite link as String on success.
func (bot *Bot) ExportChatInviteLink(chatID int64) (string, error) {
dst, err := parser.Marshal(&ExportChatInviteLinkParameters{ChatID: chatID})
if err != nil {
return "", err
}
resp, err := bot.request(dst, MethodExportChatInviteLink)
if err != nil {
return "", err
}
var inviteLink string
err = parser.Unmarshal(resp.Result, &inviteLink)
return inviteLink, err
}

View File

@ -1,45 +0,0 @@
package telegram
// ForwardMessageParameters represents data for ForwardMessage method.
type ForwardMessageParameters struct {
// Unique identifier for the target chat or username of the target
// channel (in the format @channelusername)
ChatID int64 `json:"chat_id"`
// Unique identifier for the chat where the original message was sent
// (or channel username in the format @channelusername)
FromChatID int64 `json:"from_chat_id"`
// Sends the message silently. Users will receive a notification with no
// sound.
DisableNotification bool `json:"disable_notification,omitempty"`
// Message identifier in the chat specified in from_chat_id
MessageID int `json:"message_id"`
}
// NewForwardMessage creates ForwardMessageParameters only with reqired parameters.
func NewForwardMessage(from, to int64, messageID int) *ForwardMessageParameters {
return &ForwardMessageParameters{
FromChatID: from,
ChatID: to,
MessageID: messageID,
}
}
// ForwardMessage forward messages of any kind. On success, the sent Message is returned.
func (bot *Bot) ForwardMessage(params *ForwardMessageParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodForwardMessage)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}

156
games.go Normal file
View File

@ -0,0 +1,156 @@
package telegram
type (
// Game represents a game. Use BotFather to create and edit games, their short names will act as unique identifiers.
Game struct {
// Title of the game
Title string `json:"title"`
// Description of the game
Description string `json:"description"`
// 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,omitempty"`
// Photo that will be displayed in the game message in chats.
Photo []*PhotoSize `json:"photo"`
// Special entities that appear in text, such as usernames, URLs, bot commands, etc.
TextEntities []*MessageEntity `json:"text_entities,omitempty"`
// Animation that will be displayed in the game message in chats. Upload via BotFather
Animation *Animation `json:"animation,omitempty"`
}
// CallbackGame a placeholder, currently holds no information. Use BotFather to set up your game.
CallbackGame struct{}
// GameHighScore represents one row of the high scores table for a game.
GameHighScore struct {
// Position in high score table for the game
Position int `json:"position"`
// Score
Score int `json:"score"`
// User
User *User `json:"user"`
}
// SendGameParameters represents data for SendGame method.
SendGame struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Short name of the game, serves as the unique identifier for the game. Set up your games via Botfather.
GameShortName string `json:"game_short_name"`
// 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 int `json:"reply_to_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one Play game_title button will be shown. If not empty, the first button must launch the game.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// SetGameScoreParameters represents data for SetGameScore method.
SetGameScore struct {
// User identifier
UserID int `json:"user_id"`
// New score, must be non-negative
Score int `json:"score"`
// Required if inline_message_id is not specified. Identifier of the sent message
MessageID int `json:"message_id,omitempty"`
// Pass True, if the high score is allowed to decrease. This can be useful when fixing mistakes or banning cheaters
Force bool `json:"force,omitempty"`
// Pass True, if the game message should not be automatically edited to include the current scoreboard
DisableEditMessage bool `json:"disable_edit_message,omitempty"`
// Required if inline_message_id is not specified. Unique identifier for the target chat
ChatID int64 `json:"chat_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
}
// GetGameHighScoresParameters represents data for GetGameHighScores method.
GetGameHighScores struct {
// Target user id
UserID int `json:"user_id"`
// Required if inline_message_id is not specified. Identifier of the sent message
MessageID int `json:"message_id,omitempty"`
// Required if inline_message_id is not specified. Unique identifier for the target chat
ChatID int64 `json:"chat_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
}
)
// SendGame send a game. On success, the sent Message is returned.
func (b Bot) SendGame(p SendGame) (*Message, error) {
src, err := b.Do(MethodSendGame, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// SetGameScore set the score of the specified user in a game. On success, if the message was sent by the bot, returns the edited Message, otherwise returns True. Returns an error, if the new score is not greater than the user's current score in the chat and force is False.
func (b Bot) SetGameScore(p SetGameScore) (*Message, error) {
src, err := b.Do(MethodSetGameScore, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// GetGameHighScores get data for high score tables. Will return the score of the specified user and several of his neighbors in a game. On success, returns an Array of GameHighScore objects.
func (b Bot) GetGameHighScores(p GetGameHighScores) ([]*GameHighScore, error) {
src, err := b.Do(MethodGetGameHighScores, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := make([]*GameHighScore, 0)
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return nil, err
}
return result, nil
}

314
get.go
View File

@ -1,314 +0,0 @@
package telegram
type (
// GetChatParameters represents data for GetChat method.
GetChatParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// GetChatAdministratorsParameters represents data for GetChatAdministrators
// method.
GetChatAdministratorsParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// GetChatMemberParameters represents data for GetChatMember method.
GetChatMemberParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Unique identifier of the target user
UserID int `json:"user_id"`
}
// GetChatMembersCountParameters represents data for GetChatMembersCount method.
GetChatMembersCountParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// GetFileParameters represents data for GetFile method.
GetFileParameters struct {
// File identifier to get info about
FileID string `json:"file_id"`
}
// GetUpdatesParameters represents data for GetUpdates method.
GetUpdatesParameters struct {
// Identifier of the first update to be returned. Must be greater by one than the highest among the
// identifiers of previously received updates. By default, updates starting with the earliest unconfirmed
// update are returned. An update is considered confirmed as soon as getUpdates is called with an offset
// higher than its update_id. The negative offset can be specified to retrieve updates starting from -offset
// update from the end of the updates queue. All previous updates will forgotten.
Offset int `json:"offset,omitempty"`
// Limits the number of updates to be retrieved. Values between 1—100 are accepted. Defaults to 100.
Limit int `json:"limit,omitempty"`
// Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling. Should be positive, short
// polling should be used for testing purposes only.
Timeout int `json:"timeout,omitempty"`
// List the types of updates you want your bot to receive. For example, specify ["message",
// "edited_channel_post", "callback_query"] to only receive updates of these types. See Update for a complete
// list of available update types. Specify an empty list to receive all updates regardless of type (default).
// If not specified, the previous setting will be used.
//
// Please note that this parameter doesn't affect updates created before the call to the getUpdates, so
// unwanted updates may be received for a short period of time.
AllowedUpdates []string `json:"allowed_updates,omitempty"`
}
// GetUserProfilePhotosParameters represents data for GetUserProfilePhotos method.
GetUserProfilePhotosParameters struct {
// Unique identifier of the target user
UserID int `json:"user_id"`
// Sequential number of the first photo to be returned. By default, all
// photos are returned.
Offset int `json:"offset,omitempty"`
// Limits the number of photos to be retrieved. Values between 1—100 are
// accepted. Defaults to 100.
Limit int `json:"limit,omitempty"`
}
// GetGameHighScoresParameters represents data for GetGameHighScores method.
GetGameHighScoresParameters struct {
// Target user id
UserID int `json:"user_id"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Required if inline_message_id is not specified. Unique identifier for the
// target chat
ChatID int64 `json:"chat_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
}
// GetStickerSetParameters represents data for GetStickerSet method.
GetStickerSetParameters struct {
// Name of the sticker set
Name string `json:"name"`
}
)
// NewGameHighScores creates GetGameHighScoresParameters only with required parameters.
func NewGameHighScores(userID int) *GetGameHighScoresParameters {
return &GetGameHighScoresParameters{
UserID: userID,
}
}
// GetChat get up to date information about the chat (current name of the user
// for one-on-one conversations, current username of a user, group or channel,
// etc.). Returns a Chat object on success.
func (bot *Bot) GetChat(chatID int64) (*Chat, error) {
dst, err := parser.Marshal(&GetChatParameters{ChatID: chatID})
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodGetChat)
if err != nil {
return nil, err
}
var chat Chat
err = parser.Unmarshal(resp.Result, &chat)
return &chat, err
}
// GetChatAdministrators get a list of administrators in a chat. On success,
// returns an Array of ChatMember objects that contains information about all
// chat administrators except other bots. If the chat is a group or a supergroup
// and no administrators were appointed, only the creator will be returned.
func (bot *Bot) GetChatAdministrators(chatID int64) ([]ChatMember, error) {
dst, err := parser.Marshal(&GetChatAdministratorsParameters{ChatID: chatID})
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodGetChatAdministrators)
if err != nil {
return nil, err
}
var chatMembers []ChatMember
err = parser.Unmarshal(resp.Result, &chatMembers)
return chatMembers, err
}
// GetChatMember get information about a member of a chat. Returns a ChatMember
// object on success.
func (bot *Bot) GetChatMember(chatID int64, userID int) (*ChatMember, error) {
dst, err := parser.Marshal(&GetChatMemberParameters{
ChatID: chatID,
UserID: userID,
})
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodGetChatMember)
if err != nil {
return nil, err
}
var chatMember ChatMember
err = parser.Unmarshal(resp.Result, &chatMember)
return &chatMember, err
}
// GetChatMembersCount get the number of members in a chat. Returns Int on
// success.
func (bot *Bot) GetChatMembersCount(chatID int64) (int, error) {
dst, err := parser.Marshal(&GetChatMembersCountParameters{ChatID: chatID})
if err != nil {
return 0, err
}
resp, err := bot.request(dst, MethodGetChatMembersCount)
if err != nil {
return 0, err
}
var count int
err = parser.Unmarshal(resp.Result, &count)
return count, err
}
// GetFile get basic info about a file and prepare it for downloading. For the
// moment, bots can download files of up to 20MB in size. On success, a File
// object is returned. The file can then be downloaded via the link
// https://api.telegram.org/file/bot<token>/<file_path>, where <file_path> is
// taken from the response. It is guaranteed that the link will be valid for at
// least 1 hour. When the link expires, a new one can be requested by calling
// getFile again.
//
// Note: This function may not preserve the original file name and MIME type. You
// should save the file's MIME type and name (if available) when the File object
// is received.
func (bot *Bot) GetFile(fileID string) (*File, error) {
dst, err := parser.Marshal(&GetFileParameters{FileID: fileID})
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodGetFile)
if err != nil {
return nil, err
}
var file File
err = parser.Unmarshal(resp.Result, &file)
return &file, err
}
// GetMe 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, error) {
resp, err := bot.request(nil, MethodGetMe)
if err != nil {
return nil, err
}
var me User
err = parser.Unmarshal(resp.Result, &me)
return &me, err
}
// GetUpdates receive incoming updates using long polling. An Array of Update objects is returned.
func (bot *Bot) GetUpdates(params *GetUpdatesParameters) ([]Update, error) {
if params == nil {
params = &GetUpdatesParameters{Limit: 100}
}
src, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(src, MethodGetUpdates)
if err != nil {
return nil, err
}
updates := make([]Update, params.Limit)
err = parser.Unmarshal(resp.Result, &updates)
return updates, err
}
// GetUserProfilePhotos get a list of profile pictures for a user. Returns a UserProfilePhotos object.
func (bot *Bot) GetUserProfilePhotos(params *GetUserProfilePhotosParameters) (*UserProfilePhotos, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodGetUserProfilePhotos)
if err != nil {
return nil, err
}
var photos UserProfilePhotos
err = parser.Unmarshal(resp.Result, &photos)
return &photos, err
}
// 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, error) {
resp, err := bot.request(nil, MethodGetWebhookInfo)
if err != nil {
return nil, err
}
var info WebhookInfo
err = parser.Unmarshal(resp.Result, &info)
return &info, err
}
// GetGameHighScores get data for high score tables. Will return the score of the
// specified user and several of his neighbors in a game. On success, returns an
// Array of GameHighScore objects.
func (bot *Bot) GetGameHighScores(params *GetGameHighScoresParameters) ([]GameHighScore, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodGetGameHighScores)
if err != nil {
return nil, err
}
var scores []GameHighScore
err = parser.Unmarshal(resp.Result, &scores)
return scores, err
}
// GetStickerSet get a sticker set. On success, a StickerSet object is returned.
func (bot *Bot) GetStickerSet(name string) (*StickerSet, error) {
dst, err := parser.Marshal(&GetStickerSetParameters{Name: name})
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodGetStickerSet)
if err != nil {
return nil, err
}
var set StickerSet
err = parser.Unmarshal(resp.Result, &set)
return &set, err
}

9
go.mod
View File

@ -3,14 +3,15 @@ module gitlab.com/toby3d/telegram
go 1.12
require (
github.com/json-iterator/go v1.1.7
github.com/Masterminds/semver v1.5.0
github.com/json-iterator/go v1.1.9
github.com/kirillDanshin/dlog v0.0.0-20170728000807-97d876b12bf9
github.com/kirillDanshin/myutils v0.0.0-20160713214838-182269b1fbcc // indirect
github.com/klauspost/compress v1.7.4 // indirect
github.com/klauspost/cpuid v1.2.1 // indirect
github.com/klauspost/compress v1.9.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/stretchr/testify v1.3.0
github.com/valyala/fasthttp v1.4.0
github.com/valyala/fasthttp v1.7.1
golang.org/x/text v0.3.2
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
)

26
go.sum
View File

@ -1,20 +1,19 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kirillDanshin/dlog v0.0.0-20170728000807-97d876b12bf9 h1:mA7k8E2Vrmyj5CW/D1XZBFmohVNi7jf757vibGwzRbo=
github.com/kirillDanshin/dlog v0.0.0-20170728000807-97d876b12bf9/go.mod h1:l8CN7iyX1k2xlsTYVTpCtwBPcxThf/jLWDGVcF6T/bM=
github.com/kirillDanshin/myutils v0.0.0-20160713214838-182269b1fbcc h1:OkOhOn3WBUmfATC1NsA3rBlgHGkjk0KGnR5akl/8uXc=
github.com/kirillDanshin/myutils v0.0.0-20160713214838-182269b1fbcc/go.mod h1:Bt95qRxLvpdmASW9s2tTxGdQ5ma4o4n8QFhCvzCew/M=
github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.7.4 h1:4UqAIzZ1Ns2epCTyJ1d2xMWvxtX+FNSCYWeOFogK9nc=
github.com/klauspost/compress v1.7.4/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7 h1:hYW1gP94JUmAhBtJ+LNz5My+gBobDxPR1iVuKug26aA=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -30,10 +29,15 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.4.0 h1:PuaTGZIw3mjYhhhbVbCQp8aciRZN9YdoB7MGX9Ko76A=
github.com/valyala/fasthttp v1.4.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
github.com/valyala/fasthttp v1.7.1 h1:UHtt5/7O70RSUZTR/hSu0PNWMAfWx5AtsPp9Jk+g17M=
github.com/valyala/fasthttp v1.7.1/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

1044
inline.go Normal file

File diff suppressed because it is too large Load Diff

51
inline_test.go Normal file
View File

@ -0,0 +1,51 @@
package telegram
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestInlineQueryHasQuery(t *testing.T) {
t.Run("true", func(t *testing.T) {
iq := InlineQuery{Query: "sample text"}
assert.True(t, iq.HasQuery())
})
t.Run("false", func(t *testing.T) {
iq := InlineQuery{}
assert.False(t, iq.HasQuery())
})
}
func TestInlineQueryHasOffset(t *testing.T) {
t.Run("true", func(t *testing.T) {
iq := InlineQuery{Offset: "42"}
assert.True(t, iq.HasOffset())
})
t.Run("false", func(t *testing.T) {
iq := InlineQuery{}
assert.False(t, iq.HasOffset())
})
}
func TestInlineQueryHasLocation(t *testing.T) {
t.Run("true", func(t *testing.T) {
iq := InlineQuery{Location: &Location{Latitude: 56.085180, Longitude: 60.735150}}
assert.True(t, iq.HasLocation())
})
t.Run("false", func(t *testing.T) {
iq := InlineQuery{}
assert.False(t, iq.HasLocation())
})
}
func TestChosenInlineResultInlineQueryHasLocation(t *testing.T) {
t.Run("true", func(t *testing.T) {
cir := ChosenInlineResult{Location: &Location{Latitude: 56.085180, Longitude: 60.735150}}
assert.True(t, cir.HasLocation())
})
t.Run("false", func(t *testing.T) {
cir := ChosenInlineResult{}
assert.False(t, cir.HasLocation())
})
}

39
kick.go
View File

@ -1,39 +0,0 @@
package telegram
// KickChatMemberParameters represents data for KickChatMember method.
type KickChatMemberParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Unique identifier of the target user
UserID int `json:"user_id"`
// Date when the user will be unbanned, unix time. If user is banned for
// more than 366 days or less than 30 seconds from the current time they
// are considered to be banned forever
UntilDate int64 `json:"until_date"`
}
// KickChatMember kick a user from a group, a supergroup or a channel. In the case of supergroups and
// channels, the user will not be able to return to the group on their own using invite links, etc.,
// unless unbanned first. The bot must be an administrator in the chat for this to work and must have
// the appropriate admin rights. Returns True on success.
//
// Note: In regular groups (non-supergroups), this method will only work if the 'All Members Are
// Admins' setting is off in the target group. Otherwise members may only be removed by the group's
// creator or by the member that added them.
func (bot *Bot) KickChatMember(params *KickChatMemberParameters) (bool, error) {
dst, err := parser.Marshal(params)
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodKickChatMember)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

View File

@ -1,24 +0,0 @@
package telegram
// LeaveChatParameters represents data for LeaveChat method.
type LeaveChatParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// LeaveChat leave a group, supergroup or channel. Returns True on success.
func (bot *Bot) LeaveChat(chatID int64) (bool, error) {
dst, err := parser.Marshal(&LeaveChatParameters{ChatID: chatID})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodLeaveChat)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

View File

@ -1,3 +1,2 @@
// Package login contains methods for obtaining structure of the user data and
// its validation.
package login
// Package login contains methods for obtaining structure of the user data and its validation.
package login // import "gitlab.com/toby3d/telegram/login"

View File

@ -44,12 +44,12 @@ func NewWidget(accessToken string) *Widget {
// received by comparing the received hash parameter with the hexadecimal
// representation of the HMAC-SHA-256 signature of the data-check-string with the
// SHA256 hash of the bot's token used as a secret key.
func (w *Widget) CheckAuthorization(u User) (bool, error) {
func (w Widget) CheckAuthorization(u User) (bool, error) {
hash, err := w.GenerateHash(u)
return hash == u.Hash, err
}
func (w *Widget) GenerateHash(u User) (string, error) {
func (w Widget) GenerateHash(u User) (string, error) {
a := http.AcquireArgs()
defer http.ReleaseArgs(a)
@ -57,18 +57,25 @@ func (w *Widget) GenerateHash(u User) (string, error) {
a.SetUint(KeyAuthDate, int(u.AuthDate))
a.Set(KeyFirstName, u.FirstName)
a.SetUint(KeyID, u.ID)
if u.LastName != "" {
a.Set(KeyLastName, u.LastName)
}
if u.PhotoURL != "" {
a.Set(KeyPhotoURL, u.PhotoURL)
}
if u.Username != "" {
a.Set(KeyUsername, u.Username)
}
secretKey := sha256.Sum256([]byte(w.accessToken))
h := hmac.New(sha256.New, secretKey[0:])
_, err := h.Write(a.QueryString())
return hex.EncodeToString(h.Sum(nil)), err
if _, err := h.Write(a.QueryString()); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

View File

@ -34,6 +34,7 @@ func TestCheckAuthorization(t *testing.T) {
PhotoURL: "https://toby3d.me/avatar.jpg",
AuthDate: time.Now().UTC().Unix(),
}
t.Run("invalid", func(t *testing.T) {
u.Hash = "wtf"
ok, err := w.CheckAuthorization(u)

View File

@ -1,11 +1,13 @@
package login
import (
"time"
)
import "time"
// FullName return user first name only or full name if last name is present.
func (u *User) FullName() string {
func (u User) FullName() string {
if u.FirstName == "" {
return ""
}
name := u.FirstName
if u.HasLastName() {
name += " " + u.LastName
@ -15,26 +17,19 @@ func (u *User) FullName() string {
}
// AuthTime convert AuthDate field into time.Time.
func (u *User) AuthTime() *time.Time {
if u == nil || u.AuthDate == 0 {
return nil
func (u User) AuthTime() time.Time {
if u.AuthDate == 0 {
return time.Time{}
}
t := time.Unix(u.AuthDate, 0)
return &t
return time.Unix(u.AuthDate, 0)
}
// HasLastName checks what the current user has a LastName.
func (u *User) HasLastName() bool {
return u != nil && u.LastName != ""
}
func (u User) HasLastName() bool { return u.LastName != "" }
// HasUsername checks what the current user has a username.
func (u *User) HasUsername() bool {
return u != nil && u.Username != ""
}
func (u User) HasUsername() bool { return u.Username != "" }
// HasPhoto checks what the current user has a photo.
func (u *User) HasPhoto() bool {
return u != nil && u.PhotoURL != ""
}
func (u User) HasPhoto() bool { return u.PhotoURL != "" }

View File

@ -31,11 +31,11 @@ func TestUser(t *testing.T) {
t.Run("auth time", func(t *testing.T) {
t.Run("empty", func(t *testing.T) {
var u User
assert.Nil(t, u.AuthTime())
assert.True(t, u.AuthTime().IsZero())
})
t.Run("exists", func(t *testing.T) {
u := User{AuthDate: time.Now().UTC().Unix()}
assert.NotNil(t, u.AuthTime())
assert.False(t, u.AuthTime().IsZero())
})
})
t.Run("has photo", func(t *testing.T) {

1716
methods.go Normal file

File diff suppressed because it is too large Load Diff

974
passport.go Normal file
View File

@ -0,0 +1,974 @@
package telegram
import "errors"
/*
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1" //nolint: gosec
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
*/
type (
// PassportData contains information about Telegram Passport data shared with the bot by the user.
PassportData struct {
// Array with information about documents and other Telegram Passport elements that was shared with the bot
Data []*EncryptedPassportElement `json:"data"`
// Encrypted credentials required to decrypt the data
Credentials *EncryptedCredentials `json:"credentials"`
}
// PassportFile represents a file uploaded to Telegram Passport. Currently all Telegram Passport files are in JPEG format when decrypted and don't exceed 10MB.
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"`
}
// EncryptedPassportElement contains information about documents or other Telegram Passport elements shared with the bot by the user.
EncryptedPassportElement struct {
// Element type.
Type string `json:"type"`
// Base64-encoded encrypted Telegram Passport element data provided by the user, available for "personal_details", "passport", "driver_license", "identity_card", "identity_passport" and "address" types. Can be decrypted and verified using the accompanying EncryptedCredentials.
Data string `json:"data,omitempty"`
// User's verified phone number, available only for "phone_number" type
PhoneNumber string `json:"phone_number,omitempty"`
// User's verified email address, available only for "email" type
Email string `json:"email,omitempty"`
// 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,omitempty"`
// 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_side,omitempty"`
// 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,omitempty"`
// 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,omitempty"`
// 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,omitempty"`
// Base64-encoded element hash for using in PassportElementErrorUnspecified
Hash string `json:"hash"`
}
// EncryptedCredentials contains data required for decrypting and authenticating EncryptedPassportElement. See the Telegram Passport Documentation for a complete description of the data decryption and authentication processes.
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"`
}
// PassportElementError represents an error in the Telegram Passport element which was submitted that should be resolved by the user.
PassportElementError interface {
PassportElementErrorMessage() string
PassportElementErrorSource() string
PassportElementErrorType() string
}
// PassportElementErrorDataField represents an issue in one of the data fields that was provided by the user. The error is considered resolved when the field's value changes.
PassportElementErrorDataField struct {
// Error source, must be data
Source string `json:"source"`
// The section of the user's Telegram Passport which has the error, one of "personal_details", "passport", "driver_license", "identity_card", "internal_passport", "address"
Type string `json:"type"`
// Name of the data field which has the error
FieldName string `json:"field_name"`
// Base64-encoded data hash
DataHash string `json:"data_hash"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorFrontSide represents an issue with the front side of a document. The error is considered resolved when the file with the front side of the document changes.
PassportElementErrorFrontSide struct {
// Error source, must be front_side
Source string `json:"source"`
// The section of the user's Telegram Passport which has the issue, one of "passport", "driver_license", "identity_card", "internal_passport"
Type string `json:"type"`
// Base64-encoded hash of the file with the front side of the document
FileHash string `json:"file_hash"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorReverseSide represents an issue with the reverse side of a document. The error is considered resolved when the file with reverse side of the document changes.
PassportElementErrorReverseSide struct {
// Error source, must be reverse_side
Source string `json:"source"`
// The section of the user's Telegram Passport which has the issue, one of "driver_license", "identity_card"
Type string `json:"type"`
// Base64-encoded hash of the file with the reverse side of the document
FileHash string `json:"file_hash"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorSelfie represents an issue with the selfie with a document. The error is considered resolved when the file with the selfie changes.
PassportElementErrorSelfie struct {
// Error source, must be selfie
Source string `json:"source"`
// The section of the user's Telegram Passport which has the issue, one of "passport", "driver_license", "identity_card", "internal_passport"
Type string `json:"type"`
// Base64-encoded hash of the file with the selfie
FileHash string `json:"file_hash"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorFile represents an issue with a document scan. The error is considered resolved when the file with the document scan changes.
PassportElementErrorFile struct {
// Error source, must be file
Source string `json:"source"`
// The section of the user's Telegram Passport which has the issue, one of "utility_bill", "bank_statement", "rental_agreement", "passport_registration", "temporary_registration"
Type string `json:"type"`
// Base64-encoded file hash
FileHash string `json:"file_hash"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorFiles represents an issue with a list of scans. The error is considered resolved when the list of files containing the scans changes.
PassportElementErrorFiles struct {
// Error source, must be files
Source string `json:"source"`
// The section of the user's Telegram Passport which has the issue, one of "utility_bill", "bank_statement", "rental_agreement", "passport_registration", "temporary_registration"
Type string `json:"type"`
// List of base64-encoded file hashes
FileHashes []string `json:"file_hashes"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorTranslationFile represents an issue with one of the files that constitute the translation of a document. The error is considered resolved when the file changes.
PassportElementErrorTranslationFile struct {
// Error source, must be translation_file
Source string `json:"source"`
// Type of element of the user's Telegram Passport which has the issue, one of “passport”, “driver_license”, “identity_card”, “internal_passport”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”
Type string `json:"type"`
// Base64-encoded file hash
FileHash string `json:"file_hash"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorTranslationFiles represents an issue with the translated version of a document. The error is considered resolved when a file with the document translation change.
PassportElementErrorTranslationFiles struct {
// Error source, must be translation_files
Source string `json:"source"`
// Type of element of the user's Telegram Passport which has the issue, one of “passport”, “driver_license”, “identity_card”, “internal_passport”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”
Type string `json:"type"`
// List of base64-encoded file hashes
FileHashes []string `json:"file_hashes"`
// Error message
Message string `json:"message"`
}
// PassportElementErrorUnspecified represents an issue in an unspecified place. The error is considered resolved when new data is added.
PassportElementErrorUnspecified struct {
// Error source, must be unspecified
Source string `json:"source"`
// Type of element of the user's Telegram Passport which has the issue
Type string `json:"type"`
// Base64-encoded element hash
ElementHash string `json:"element_hash"`
// Error message
Message string `json:"message"`
}
SetPassportDataErrors struct {
// User identifier
UserID int `json:"user_id"`
// A JSON-serialized array describing the errors
Errors []PassportElementError `json:"errors"`
}
// AuthParameters represent a Telegram Passport auth parameters for SDK.
Auth struct {
// Unique identifier for the b. You can get it from bot token. For example, for the bot token 1234567:4TT8bAc8GHUspu3ERYn-KGcvsvGB9u_n4ddy, the bot id is 1234567.
BotID int `json:"bot_id"`
// A JSON-serialized object describing the data you want to request
Scope *PassportScope `json:"scope"`
// Public key of the bot
PublicKey string `json:"public_key"`
// Bot-specified nonce.
//
// Important: For security purposes it should be a cryptographically secure unique identifier of the request. In particular, it should be long enough and it should be generated using a cryptographically secure pseudorandom number generator. You should never accept credentials with the same nonce twice.
Nonce string `json:"nonce"`
}
// PassportScope represents the data to be requested.
PassportScope struct {
// List of requested elements, each type may be used only once in the entire array of PassportScopeElement objects
Data []*PassportScopeElement `json:"data"`
// Scope version, must be 1
V int `json:"v"`
}
// PassportScopeElement represents a requested element.
PassportScopeElement interface {
PassportScopeElementTranslation() bool
PassportScopeElementSelfie() bool
}
// PassportScopeElementOneOfSeveral represents several elements one of which must be provided.
PassportScopeElementOneOfSeveral struct {
// List of elements one of which must be provided;
OneOf []*PassportScopeElementOne `json:"one_of"`
// Use this parameter if you want to request a selfie with the document from this list that the user chooses to upload.
Selfie bool `json:"selfie,omitempty"`
// Use this parameter if you want to request a translation of the document from this list that the user chooses to upload. Note: We suggest to only request translations after you have received a valid document that requires one.
Translation bool `json:"translation,omitempty"`
}
// PassportScopeElementOne represents one particular element that must be provided. If no options are needed, String can be used instead of this object to specify the type of the element.
PassportScopeElementOne struct {
// Element type.
Type string `json:"type"`
// Use this parameter if you want to request a selfie with the document as well.
Selfie bool `json:"selfie,omitempty"`
// Use this parameter if you want to request a translation of the document as well.
Translation bool `json:"translation,omitempty"`
// Use this parameter to request the first, last and middle name of the user in the language of the user's country of residence.
NativeNames bool `json:"native_names,omitempty"`
}
Passport struct {
// Personal Details
PersonalDetails struct {
Data *PersonalDetails `json:"data"`
} `json:"personal_details"`
// Passport
Passport struct {
Data *IDDocumentData `json:"data"`
FrontSide *PassportFile `json:"front_side"`
Selfie *PassportFile `json:"selfie,omitempty"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"passport"`
// Internal Passport
InternalPassport struct {
Data *IDDocumentData `json:"data"`
FrontSide *PassportFile `json:"front_side"`
Selfie *PassportFile `json:"selfie,omitempty"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"internal_passport"`
// Driver License
DriverLicense struct {
Data *IDDocumentData `json:"data"`
FrontSide *PassportFile `json:"front_side"`
ReverseSide *PassportFile `json:"reverse_side"`
Selfie *PassportFile `json:"selfie,omitempty"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"driver_license"`
// Identity Card
IdentityCard struct {
Data *IDDocumentData `json:"data"`
FrontSide *PassportFile `json:"front_side"`
ReverseSide *PassportFile `json:"reverse_side"`
Selfie *PassportFile `json:"selfie,omitempty"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"identity_card"`
// Address
Address struct {
Data *ResidentialAddress `json:"data"`
} `json:"address"`
// Utility Bill
UtilityBill struct {
Files []*PassportFile `json:"files"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"utility_bill"`
// Bank Statement
BankStatement struct {
Files []*PassportFile `json:"files"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"bank_statement"`
// Rental Agreement
RentalAgreement struct {
Files []*PassportFile `json:"files"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"rental_agreement"`
// Registration Page in the Internal Passport
PassportRegistration struct {
Files []*PassportFile `json:"files"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"passport_registration"`
// Temporary Registration
TemporaryRegistration struct {
Files []*PassportFile `json:"files"`
Translation []*PassportFile `json:"translation,omitempty"`
} `json:"temporary_registration"`
// Phone number
PhoneNumber string `json:"phone_number"`
// Email
Email string `json:"email"`
}
// PersonalDetails represents personal details.
PersonalDetails struct {
// First Name
FirstName string `json:"first_name"`
// Last Name
LastName string `json:"last_name"`
// Middle Name
MiddleName string `json:"middle_name,omitempty"`
// Date of birth in DD.MM.YYYY format
BirthDate string `json:"birth_date"`
// Gender, male or female
Gender string `json:"gender"`
// Citizenship (ISO 3166-1 alpha-2 country code)
CountryCode string `json:"country_code"`
// Country of residence (ISO 3166-1 alpha-2 country code)
ResidenceCountryCode string `json:"residence_country_code"`
// First Name in the language of the user's country of residence
FirstNameNative string `json:"first_name_native"`
// Last Name in the language of the user's country of residence
LastNameNative string `json:"last_name_native"`
// Middle Name in the language of the user's country of residence
MiddleNameNative string `json:"middle_name_native,omitempty"`
}
// ResidentialAddress represents a residential address.
ResidentialAddress struct {
// First line for the address
StreetLine1 string `json:"street_line1"`
// Second line for the address
StreetLine2 string `json:"street_line2,omitempty"`
// City
City string `json:"city"`
// State
State string `json:"state,omitempty"`
// ISO 3166-1 alpha-2 country code
CountryCode string `json:"country_code"`
// Address post code
PostCode string `json:"post_code"`
}
// IDDocumentData represents the data of an identity document.
IDDocumentData struct {
// Document number
DocumentNo string `json:"document_no"`
// Date of expiry, in DD.MM.YYYY format
ExpiryDate string `json:"expiry_date,omitempty"`
}
// Credentials is a JSON-serialized object.
Credentials struct {
// Credentials for encrypted data
SecureData *SecureData `json:"secure_data"`
// Bot-specified nonce
Nonce string `json:"nonce"`
}
// SecureData represents the credentials required to decrypt encrypted data. All fields are optional and depend on fields that were requested.
SecureData struct {
// Credentials for encrypted personal details
PersonalDetails *SecureValue `json:"personal_details,omitempty"`
// Credentials for encrypted passport
Passport *SecureValue `json:"passport,omitempty"`
// Credentials for encrypted internal passport
InternalPassport *SecureValue `json:"internal_passport,omitempty"`
// Credentials for encrypted driver license
DriverLicense *SecureValue `json:"driver_license,omitempty"`
// Credentials for encrypted ID card
IdentityCard *SecureValue `json:"identity_card,omitempty"`
// Credentials for encrypted residential address
Address *SecureValue `json:"address,omitempty"`
// Credentials for encrypted utility bill
UtilityBill *SecureValue `json:"utility_bill,omitempty"`
// Credentials for encrypted bank statement
BankStatement *SecureValue `json:"bank_statement,omitempty"`
// Credentials for encrypted rental agreement
RentalAgreement *SecureValue `json:"rental_agreement,omitempty"`
// Credentials for encrypted registration from internal passport
PassportRegistration *SecureValue `json:"passport_registration,omitempty"`
// Credentials for encrypted temporary registration
TemporaryRegistration *SecureValue `json:"temporary_registration,omitempty"`
}
// SecureValue represents the credentials required to decrypt encrypted values. All fields are optional and depend on the type of fields that were requested.
SecureValue struct {
// Credentials for encrypted Telegram Passport data.
Data *DataCredentials `json:"data,omitempty"`
// Credentials for an encrypted document's front side.
FrontSide *FileCredentials `json:"front_side,omitempty"`
// Credentials for an encrypted document's reverse side.
ReverseSide *FileCredentials `json:"reverse_side,omitempty"`
// Credentials for an encrypted selfie of the user with a document.
Selfie *FileCredentials `json:"selfie,omitempty"`
// Credentials for an encrypted translation of the document.
Translation []*FileCredentials `json:"translation,omitempty"`
// Credentials for encrypted files.
Files []*FileCredentials `json:"files,omitempty"`
}
// DataCredentials can be used to decrypt encrypted data from the data field in EncryptedPassportElement.
DataCredentials struct {
// Checksum of encrypted data
DataHash string `json:"data_hash"`
// Secret of encrypted data
Secret string `json:"secret"`
}
// FileCredentials can be used to decrypt encrypted files from the front_side, reverse_side, selfie, files and translation fields in EncryptedPassportElement.
FileCredentials struct {
// Checksum of encrypted file
FileHash string `json:"file_hash"`
// Secret of encrypted file
Secret string `json:"secret"`
}
)
var ErrNotEqual = errors.New("credentials hash and credentials data hash is not equal")
// SetPassportDataErrors informs a user that some of the Telegram Passport elements they provided contains errors. The user will not be able to re-submit their Passport to you until the errors are fixed (the contents of the field for which you returned the error must change). Returns True on success.
//
// Use this if the data submitted by the user doesn't satisfy the standards your service requires for any reason. For example, if a birthday date seems invalid, a submitted document is blurry, a scan shows evidence of tampering, etc. Supply some details in the error message to make sure the user knows how to correct the issues.
func (b Bot) SetPassportDataErrors(uid int, errors ...PassportElementError) (bool, error) {
src, err := b.Do(MethodSetPassportDataErrors, SetPassportDataErrors{
UserID: uid, Errors: errors,
})
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}
/* TODO(toby3d)
func (b *Bot) DecryptFile(pf *PassportFile, fc *FileCredentials) (data []byte, err error) {
secret, err := decodeField(fc.Secret)
if err != nil {
return nil, err
}
hash, err := decodeField(fc.FileHash)
if err != nil {
return nil, err
}
key, iv := decryptSecretHash(secret, hash)
file, err := b.GetFile(pf.FileID)
if err != nil {
return nil, err
}
if _, data, err = b.Client.Get(nil, b.NewFileURL(file.FilePath).String()); err != nil {
return nil, err
}
if data, err = decryptData(key, iv, data); err != nil {
return nil, err
}
if !match(hash, data) {
err = ErrNotEqual
return nil, err
}
offset := int(data[0])
data = data[offset:]
return data, nil
}
func (dc *DataCredentials) decrypt(d string) (data []byte, err error) {
secret, err := decodeField(dc.Secret)
if err != nil {
return
}
hash, err := decodeField(dc.DataHash)
if err != nil {
return
}
key, iv := decryptSecretHash(secret, hash)
if data, err = decodeField(d); err != nil {
return
}
if data, err = decryptData(key, iv, data); err != nil {
return
}
if !match(hash, data) {
err = ErrNotEqual
}
offset := int(data[0])
data = data[offset:]
return
}
func (ec *EncryptedCredentials) Decrypt(pk *rsa.PrivateKey) (*Credentials, error) {
if ec == nil || pk == nil {
return nil, nil
}
data, err := decrypt(pk, ec.Secret, ec.Hash, ec.Data)
if err != nil {
return nil, err
}
var c Credentials
if err = json.ConfigFastest.Unmarshal(data, &c); err != nil {
return nil, Error{Description: err.Error()}
}
return &c, err
}
func (epe *EncryptedPassportElement) DecryptPersonalDetails(sv *SecureValue) (*PersonalDetails, error) {
if !epe.IsPersonalDetails() || !sv.HasData() {
return nil, nil
}
body, err := sv.Data.decrypt(epe.Data)
if err != nil {
return nil, err
}
var pd PersonalDetails
if err = json.ConfigFastest.Unmarshal(body, &pd); err != nil {
return nil, Error{Description: err.Error()}
}
return &pd, err
}
func (epe *EncryptedPassportElement) DecryptPassport(sv *SecureValue, b *Bot) (*IDDocumentData, []byte, []byte, [][]byte, error) {
if !epe.IsPassport() || !sv.HasData() || !sv.HasFrontSide() {
return nil, nil, nil, nil, nil
}
body, err := sv.Data.decrypt(epe.Data)
if err != nil {
return nil, nil, nil, nil, err
}
var idd IDDocumentData
if err = b.marshler.Unmarshal(body, &idd); err != nil {
return nil, nil, nil, nil, err
}
fs, err := b.DecryptFile(epe.FrontSide, sv.FrontSide)
if err != nil {
return &idd, nil, nil, nil, err
}
var s []byte
if sv.HasSelfie() {
if s, err = b.DecryptFile(epe.Selfie, sv.Selfie); err != nil {
return &idd, fs, nil, nil, err
}
}
t := make([][]byte, len(sv.Translation))
if sv.HasTranslation() {
for i := range t {
if t[i], err = b.DecryptFile(epe.Translation[i], sv.Translation[i]); err != nil {
return &idd, fs, s, nil, err
}
}
}
return &idd, fs, s, t, nil
}
func (epe *EncryptedPassportElement) DecryptInternalPassport(sv *SecureValue, b *Bot) (*IDDocumentData, []byte, []byte, [][]byte, error) {
if !epe.IsInternalPassport() || !sv.HasData() || !sv.HasFrontSide() {
return nil, nil, nil, nil, nil
}
body, err := sv.Data.decrypt(epe.Data)
if err != nil {
return nil, nil, nil, nil, err
}
var idd IDDocumentData
if err = b.marshler.Unmarshal(body, &idd); err != nil {
return nil, nil, nil, nil, err
}
fs, err := b.DecryptFile(epe.FrontSide, sv.FrontSide)
if err != nil {
return &idd, nil, nil, nil, err
}
var s []byte
if sv.HasSelfie() {
if s, err = b.DecryptFile(epe.Selfie, sv.Selfie); err != nil {
return &idd, fs, nil, nil, err
}
}
t := make([][]byte, len(sv.Translation))
if sv.HasTranslation() {
for i := range t {
if t[i], err = b.DecryptFile(epe.Translation[i], sv.Translation[i]); err != nil {
return &idd, fs, s, nil, err
}
}
}
return &idd, fs, s, t, nil
}
func (epe *EncryptedPassportElement) DecryptDriverLicense(sv *SecureValue, b *Bot) (*IDDocumentData, []byte, []byte, []byte, [][]byte, error) {
if !epe.IsDriverLicense() || !sv.HasData() || !sv.HasFrontSide() || !sv.HasReverseSide() {
return nil, nil, nil, nil, nil, nil
}
body, err := sv.Data.decrypt(epe.Data)
if err != nil {
return nil, nil, nil, nil, nil, err
}
var idd IDDocumentData
if err = b.marshler.Unmarshal(body, &idd); err != nil {
return nil, nil, nil, nil, nil, err
}
fs, err := b.DecryptFile(epe.FrontSide, sv.FrontSide)
if err != nil {
return &idd, nil, nil, nil, nil, err
}
rs, err := b.DecryptFile(epe.ReverseSide, sv.ReverseSide)
if err != nil {
return &idd, nil, nil, nil, nil, err
}
var s []byte
if sv.HasSelfie() {
if s, err = b.DecryptFile(epe.Selfie, sv.Selfie); err != nil {
return &idd, fs, rs, nil, nil, err
}
}
t := make([][]byte, len(sv.Translation))
if sv.HasTranslation() {
for i := range t {
if t[i], err = b.DecryptFile(epe.Translation[i], sv.Translation[i]); err != nil {
return &idd, fs, rs, s, nil, err
}
}
}
return &idd, fs, rs, s, t, nil
}
func (epe *EncryptedPassportElement) IsAddress() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeAddress)
}
func (epe *EncryptedPassportElement) IsBankStatement() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeBankStatement)
}
func (epe *EncryptedPassportElement) IsDriverLicense() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeDriverLicense)
}
func (epe *EncryptedPassportElement) IsEmail() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeEmail)
}
func (epe *EncryptedPassportElement) IsIdentityCard() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeIdentityCard)
}
func (epe *EncryptedPassportElement) IsInternalPassport() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeInternalPassport)
}
func (epe *EncryptedPassportElement) IsPassport() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePassport)
}
func (epe *EncryptedPassportElement) IsPassportRegistration() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePassportRegistration)
}
func (epe *EncryptedPassportElement) IsPersonalDetails() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePersonalDetails)
}
func (epe *EncryptedPassportElement) IsPhoneNumber() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePhoneNumber)
}
func (epe *EncryptedPassportElement) IsRentalAgreement() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeRentalAgreement)
}
func (epe *EncryptedPassportElement) IsTemporaryRegistration() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeTemporaryRegistration)
}
func (epe *EncryptedPassportElement) IsUtilityBill() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeUtilityBill)
}
func (idd *IDDocumentData) ExpiryTime() *time.Time {
if idd == nil || idd.ExpiryDate == "" {
return nil
}
et, err := time.Parse("02.01.2006", idd.ExpiryDate)
if err != nil {
return nil
}
return &et
}
func decrypt(pk *rsa.PrivateKey, s, h, d string) (obj []byte, err error) {
// Note that all base64-encoded fields should be decoded before use.
secret, err := decodeField(s)
if err != nil {
return nil, err
}
hash, err := decodeField(h)
if err != nil {
return nil, err
}
data, err := decodeField(d)
if err != nil {
return nil, err
}
if pk != nil {
// Decrypt the credentials secret (secret field in EncryptedCredentials)
// using your private key
if secret, err = decryptSecret(pk, secret); err != nil {
return nil, err
}
}
// Use this secret and the credentials hash (hash field in
// EncryptedCredentials) to calculate credentials_key and credentials_iv
key, iv := decryptSecretHash(secret, hash)
// Decrypt the credentials data (data field in EncryptedCredentials) by
// AES256-CBC using these credentials_key and credentials_iv.
if data, err = decryptData(key, iv, data); err != nil {
return nil, err
}
// IMPORTANT: At this step, make sure that the credentials hash is equal
// to SHA256(credentials_data)
if !match(hash, data) {
return nil, ErrNotEqual
}
// Credentials data is padded with 32 to 255 random padding bytes to make
// its length divisible by 16 bytes. The first byte contains the length
// of this padding (including this byte). Remove the padding to get the
// data.
return data[int(data[0]):], nil
}
func decodeField(rawField string) (field []byte, err error) {
return base64.StdEncoding.DecodeString(rawField)
}
func decryptSecret(pk *rsa.PrivateKey, s []byte) (secret []byte, err error) {
return rsa.DecryptOAEP(sha1.New(), rand.Reader, pk, s, nil) //nolint: gosec
}
func decryptSecretHash(s, h []byte) (key, iv []byte) {
var err error
hash := sha512.New()
if _, err = hash.Write(s); err != nil {
return
}
if _, err = hash.Write(h); err != nil {
return
}
sh := hash.Sum(nil)
return sh[0:32], sh[32 : 32+16]
}
func match(h, d []byte) bool {
dh := sha256.New()
if _, err := dh.Write(d); err != nil {
return false
}
return bytes.EqualFold(h, dh.Sum(nil))
}
func decryptData(key, iv, data []byte) (buf []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return
}
buf = make([]byte, len(data))
cipher.NewCBCDecrypter(block, iv).CryptBlocks(buf, data)
return
}
func (pd *PersonalDetails) BirthTime() *time.Time {
if pd == nil || pd.BirthDate == "" {
return nil
}
bt, err := time.Parse("02.01.2006", pd.BirthDate)
if err != nil {
return nil
}
return &bt
}
func (pd PersonalDetails) FullName() string { return pd.FirstName + " " + pd.LastName }
func (pd PersonalDetails) FullNameNative() string { return pd.FirstNameNative + " " + pd.LastNameNative }
func (sv *SecureValue) HasData() bool { return sv != nil && sv.Data != nil }
func (sv *SecureValue) HasFiles() bool { return sv != nil && len(sv.Files) > 0 }
func (sv *SecureValue) HasFrontSide() bool { return sv != nil && sv.FrontSide != nil }
func (sv *SecureValue) HasReverseSide() bool { return sv != nil && sv.ReverseSide != nil }
func (sv *SecureValue) HasSelfie() bool { return sv != nil && sv.Selfie != nil }
func (sv *SecureValue) HasTranslation() bool { return sv != nil && len(sv.Translation) > 0 }
*/

301
payments.go Normal file
View File

@ -0,0 +1,301 @@
package telegram
type (
// LabeledPrice represents a portion of the price for goods or services.
LabeledPrice struct {
// Portion label
Label string `json:"label"`
// Price of the product 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).
Amount int `json:"amount"`
}
// Invoice contains basic information about an invoice.
Invoice struct {
// Product name
Title string `json:"title"`
// Product description
Description string `json:"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"`
}
// ShippingAddress represents a shipping address.
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"`
}
// OrderInfo represents information about an order.
OrderInfo struct {
// User name
Name string `json:"name,omitempty"`
// User's phone number
PhoneNumber string `json:"phone_number,omitempty"`
// User email
Email string `json:"email,omitempty"`
// User shipping address
ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"`
}
// ShippingOption represents one shipping option.
ShippingOption struct {
// Shipping option identifier
ID string `json:"id"`
// Option title
Title string `json:"title"`
// List of price portions
Prices []*LabeledPrice `json:"prices"`
}
// SuccessfulPayment contains basic information about a successful payment.
SuccessfulPayment struct {
// Three-letter ISO 4217 currency code
Currency string `json:"currency"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// Identifier of the shipping option chosen by the user
ShippingOptionID string `json:"shipping_option_id,omitempty"`
// Telegram payment identifier
TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
// Provider payment identifier
ProviderPaymentChargeID string `json:"provider_payment_charge_id"`
// 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"`
// Order info provided by the user
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
// ShippingQuery contains information about an incoming shipping query.
ShippingQuery struct {
// Unique query identifier
ID string `json:"id"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// User who sent the query
From *User `json:"from"`
// User specified shipping address
ShippingAddress *ShippingAddress `json:"shipping_address"`
}
// PreCheckoutQuery contains information about an incoming pre-checkout query.
PreCheckoutQuery struct {
// Unique query identifier
ID string `json:"id"`
// Three-letter ISO 4217 currency code
Currency string `json:"currency"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// Identifier of the shipping option chosen by the user
ShippingOptionID string `json:"shipping_option_id,omitempty"`
// User who sent the query
From *User `json:"from"`
// 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"`
// Order info provided by the user
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
// SendInvoiceParameters represents data for SendInvoice method.
SendInvoice struct {
// Unique identifier for the target private chat
ChatID int64 `json:"chat_id"`
// Product name, 1-32 characters
Title string `json:"title"`
// Product description, 1-255 characters
Description string `json:"description"`
// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.
Payload string `json:"payload"`
// Payments provider token, obtained via Botfather
ProviderToken string `json:"provider_token"`
// Unique deep-linking parameter that can be used to generate this invoice when used as a start parameter
StartParameter string `json:"start_parameter"`
// Three-letter ISO 4217 currency code, see more on currencies
Currency string `json:"currency"`
// JSON-encoded data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.
ProviderData string `json:"provider_data,omitempty"`
// URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for.
PhotoURL string `json:"photo_url,omitempty"`
// Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)
Prices []*LabeledPrice `json:"prices"`
// Photo size
PhotoSize int `json:"photo_size,omitempty"`
// Photo width
PhotoWidth int `json:"photo_width,omitempty"`
// Photo height
PhotoHeight int `json:"photo_height,omitempty"`
// If the message is a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
// Pass True, if you require the user's full name to complete the order
NeedName bool `json:"need_name,omitempty"`
// Pass True, if you require the user's phone number to complete the order
NeedPhoneNumber bool `json:"need_phone_number,omitempty"`
// Pass True, if you require the user's email to complete the order
NeedEmail bool `json:"need_email,omitempty"`
// Pass True, if you require the user's shipping address to complete the order
NeedShippingAddress bool `json:"need_shipping_address,omitempty"`
// Pass True, if the final price depends on the shipping method
IsFlexible bool `json:"is_flexible,omitempty"`
// Sends the message silently. Users will receive a notification with no sound.
DisableNotification bool `json:"disable_notification,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// AnswerShippingQueryParameters represents data for AnswerShippingQuery method.
AnswerShippingQuery struct {
// Unique identifier for the query to be answered
ShippingQueryID string `json:"shipping_query_id"`
// Required if ok is False. Error message in human readable form that explains why it is impossible to complete the order (e.g. "Sorry, delivery to your desired address is unavailable'). Telegram will display this message to the user.
ErrorMessage string `json:"error_message,omitempty"`
// Specify True if delivery to the specified address is possible and False if there are any problems (for example, if delivery to the specified address is not possible)
Ok bool `json:"ok"`
// Required if ok is True. A JSON-serialized array of available shipping options.
ShippingOptions []*ShippingOption `json:"shipping_options,omitempty"`
}
// AnswerPreCheckoutQueryParameters represents data for AnswerPreCheckoutQuery method.
AnswerPreCheckoutQuery struct {
// Unique identifier for the query to be answered
PreCheckoutQueryID string `json:"pre_checkout_query_id"`
// Required if ok is False. Error message in human readable form that explains the reason for failure to proceed with the checkout (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user.
ErrorMessage string `json:"error_message,omitempty"`
// Specify True if everything is alright (goods are available, etc.) and the bot is ready to proceed with the order. Use False if there are any problems.
Ok bool `json:"ok"`
}
)
// SendInvoice send invoices. On success, the sent Message is returned.
func (b Bot) SendInvoice(p SendInvoice) (*Message, error) {
src, err := b.Do(MethodSendInvoice, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// AnswerShippingQuery reply to shipping queries.
//
// If you sent an invoice requesting a shipping address and the parameter is_flexible was specified, the Bot API will send an Update with a shipping_query field to the b. On success, True is returned.
func (b Bot) AnswerShippingQuery(p AnswerShippingQuery) (bool, error) {
src, err := b.Do(MethodAnswerShippingQuery, p)
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}
// AnswerPreCheckoutQuery respond to such pre-checkout queries.
//
// Once the user has confirmed their payment and shipping details, the Bot API sends the final confirmation in the form of an Update with the field pre_checkout_query. Use this method to respond to such pre-checkout queries. On success, True is returned.
//
// Note: The Bot API must receive an answer within 10 seconds after the pre-checkout query was sent.
func (b Bot) AnswerPreCheckoutQuery(p AnswerShippingQuery) (bool, error) {
src, err := b.Do(MethodAnswerPreCheckoutQuery, p)
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}

34
pin.go
View File

@ -1,34 +0,0 @@
package telegram
// PinChatMessageParameters represents data for PinChatMessage method.
type PinChatMessageParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Identifier of a message to pin
MessageID int `json:"message_id"`
// Pass true, if it is not necessary to send a notification to all chat
// members about the new pinned message. Notifications are always
// disabled in channels.
DisableNotification bool `json:"disable_notification"`
}
// PinChatMessage pin a message in a supergroup or a channel. The bot must be an administrator in the
// chat for this to work and must have the 'can_pin_messages' admin right in the supergroup or
// 'can_edit_messages' admin right in the channel. Returns True on success.
func (bot *Bot) PinChatMessage(params *PinChatMessageParameters) (bool, error) {
dst, err := parser.Marshal(params)
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodPinChatMessage)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

View File

@ -1,35 +0,0 @@
package telegram
type RestrictChatMemberParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Unique identifier of the target user
UserID int `json:"user_id"`
// New user permissions
Permissions ChatPermissions `json:"permissions"`
// Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days
// or less than 30 seconds from the current time, they are considered to be restricted forever
UntilDate int64 `json:"until_date,omitempty"`
}
// restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have
// the appropriate admin rights. Pass True for all permissions to lift restrictions from a user. Returns True on
// success.
func (b *Bot) RestrictChatMember(params RestrictChatMemberParameters) (bool, error) {
dst, err := parser.Marshal(&params)
if err != nil {
return false, err
}
resp, err := b.request(dst, MethodRestrictChatMember)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

796
send.go
View File

@ -1,796 +0,0 @@
package telegram
import (
"strconv"
http "github.com/valyala/fasthttp"
)
type (
// SendAnimationParameters represents data for SendAnimation method.
SendAnimationParameters struct {
// Unique identifier for the target chat or username of the target channel (in the format @channelusername)
ChatID int64 `json:"chat_id"`
// Animation to send. Pass a file_id as String to send an animation that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the Internet, or upload a new animation using multipart/form-data. More info on Sending Files »
Animation InputFile `json:"animation"`
// Duration of sent animation in seconds
Duration int `json:"duration,omitempty"`
// Animation width
Width int `json:"width,omitempty"`
// Animation height
Height int `json:"height,omitempty"`
// Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnails width and height should not exceed 90. Ignored if the file is not uploaded using multipart/form-data. Thumbnails cant be reused and can be only uploaded as a new file, so you can pass “attach://<file_attach_name>” if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. More info on Sending Files »
Thumb InputFile `json:"thumb,omitempty"`
// Animation caption (may also be used when resending animation by file_id), 0-200 characters
Caption string `json:"caption,omitempty"`
// Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption.
ParseMode string `json:"parse_mode,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 int `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"`
}
// SendChatActionParameters represents data for SendChatAction method.
SendChatActionParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Type of action to broadcast
Action string `json:"action"`
}
// SendContactParameters represents data for SendContact method.
SendContactParameters struct {
// Unique identifier for the target private chat
ChatID int64 `json:"chat_id"`
// Contact's phone number
PhoneNumber string `json:"phone_number"`
// Contact's first name
FirstName string `json:"first_name"`
// Contact's last name
LastName string `json:"last_name"`
// Additional data about the contact in the form of a vCard, 0-2048 bytes
VCard string `json:"vcard,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 int `json:"reply_to_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one 'Pay total
// price' button will be shown. If not empty, the first button must be a Pay
// button.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// SendDocumentParameters represents data for SendDocument method.
SendDocumentParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// File to send. Pass a file_id as String to send a file that exists on the Telegram servers
// (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or
// upload a new one using multipart/form-data.
Document InputFile `json:"document"`
// Document caption (may also be used when resending documents by file_id), 0-200 characters
Caption string `json:"caption,omitempty"`
// Send Markdown or HTML, if you want Telegram apps to show bold, italic,
// fixed-width text or inline URLs in the media caption.
ParseMode string `json:"parse_mode,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 int `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"`
}
// SendInvoiceParameters represents data for SendInvoice method.
SendInvoiceParameters struct {
// Unique identifier for the target private chat
ChatID int64 `json:"chat_id"`
// Product name, 1-32 characters
Title string `json:"title"`
// Product description, 1-255 characters
Description string `json:"description"`
// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to
// the user, use for your internal processes.
Payload string `json:"payload"`
// Payments provider token, obtained via Botfather
ProviderToken string `json:"provider_token"`
// Unique deep-linking parameter that can be used to generate this invoice
// when used as a start parameter
StartParameter string `json:"start_parameter"`
// Three-letter ISO 4217 currency code, see more on currencies
Currency string `json:"currency"`
// JSON-encoded data about the invoice, which will be shared with the payment
// provider. A detailed description of required fields should be provided by
// the payment provider.
ProviderData string `json:"provider_data,omitempty"`
// URL of the product photo for the invoice. Can be a photo of the goods or a
// marketing image for a service. People like it better when they see what
// they are paying for.
PhotoURL string `json:"photo_url,omitempty"`
// Price breakdown, a list of components (e.g. product price, tax, discount,
// delivery cost, delivery tax, bonus, etc.)
Prices []LabeledPrice `json:"prices"`
// Photo size
PhotoSize int `json:"photo_size,omitempty"`
// Photo width
PhotoWidth int `json:"photo_width,omitempty"`
// Photo height
PhotoHeight int `json:"photo_height,omitempty"`
// If the message is a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
// Pass True, if you require the user's full name to complete the order
NeedName bool `json:"need_name,omitempty"`
// Pass True, if you require the user's phone number to complete the order
NeedPhoneNumber bool `json:"need_phone_number,omitempty"`
// Pass True, if you require the user's email to complete the order
NeedEmail bool `json:"need_email,omitempty"`
// Pass True, if you require the user's shipping address to complete the
// order
NeedShippingAddress bool `json:"need_shipping_address,omitempty"`
// Pass True, if the final price depends on the shipping method
IsFlexible bool `json:"is_flexible,omitempty"`
// Sends the message silently. Users will receive a notification with no
// sound.
DisableNotification bool `json:"disable_notification,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one 'Pay total
// price' button will be shown. If not empty, the first button must be a Pay
// button.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// SendLocationParameters represents data for SendLocation method.
SendLocationParameters struct {
// Unique identifier for the target private chat
ChatID int64 `json:"chat_id"`
// Latitude of the location
Latitude float32 `json:"latitude"`
// Longitude of the location
Longitude float32 `json:"longitude"`
// Period in seconds for which the location will be updated (see Live
// Locations), should be between 60 and 86400.
LivePeriod int `json:"live_period,omitempty"`
// If the message is a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
// Sends the message silently. Users will receive a notification with no
// sound.
DisableNotification bool `json:"disable_notification,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one 'Pay total
// price' button will be shown. If not empty, the first button must be a Pay
// button.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// SendMediaGroupParameters represents data for SendMediaGroup method.
SendMediaGroupParameters struct {
// Unique identifier for the target chat.
ChatID int64 `json:"chat_id"`
// A JSON-serialized array describing photos and videos to be sent, must
// include 210 items
Media []interface{} `json:"media"`
// Sends the messages silently. Users will receive a notification with no
// sound.
DisableNotification bool `json:"disable_notification,omitempty"`
// If the messages are a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
}
// SendMessageParameters represents data for SendMessage method.
SendMessageParameters struct {
// Unique identifier for the target chat or username of the target channel
// (in the format @channelusername)
ChatID int64 `json:"chat_id"`
// Text of the message to be sent
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 int `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"`
}
// SendPhotoParameters represents data for SendPhoto method.
SendPhotoParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Photo to send. Pass a file_id as String to send a photo that exists on the
// Telegram servers (recommended), pass an HTTP URL as a String for Telegram
// to get a photo from the Internet, or upload a new photo using
// multipart/form-data.
Photo InputFile `json:"photo"`
// Photo caption (may also be used when resending photos by file_id), 0-200
// characters
Caption string `json:"caption,omitempty"`
// Send Markdown or HTML, if you want Telegram apps to show bold, italic,
// fixed-width text or inline URLs in the media caption.
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 int `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"`
}
SendPollConfig struct {
// Unique identifier for the target chat. A native poll can't be sent to a private chat.
ChatID int64 `json:"chat_id"`
// Poll question, 1-255 characters
Question string `json:"question"`
// List of answer options, 2-10 strings 1-100 characters each
Options []string `json:"options"`
// 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 int `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"`
}
// SendVenueParameters represents data for SendVenue method.
SendVenueParameters struct {
// Unique identifier for the target private chat
ChatID int64 `json:"chat_id"`
// Latitude of the venue
Latitude float32 `json:"latitude"`
// Longitude of the venue
Longitude float32 `json:"longitude"`
// Name of the venue
Title string `json:"title"`
// Address of the venue
Address string `json:"address"`
// Foursquare identifier of the venue
FoursquareID string `json:"foursquare_id,omitempty"`
// Foursquare type of the venue, if known. (For example,
// "arts_entertainment/default", "arts_entertainment/aquarium" or
// "food/icecream".)
FoursquareType string `json:"foursquare_type,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 int `json:"reply_to_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one 'Pay total
// price' button will be shown. If not empty, the first button must be a Pay
// button.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// SendGameParameters represents data for SendGame method.
SendGameParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Short name of the game, serves as the unique identifier for the game. Set
// up your games via Botfather.
GameShortName string `json:"game_short_name"`
// 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 int `json:"reply_to_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one Play
// game_title button will be shown. If not empty, the first button must
// launch the game.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// SendStickerParameters represents data for SetSticker method.
SendStickerParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Sticker to send
Sticker interface{} `json:"sticker"`
// 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 int `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"`
}
)
// NewAnimation creates SendAnimationParameters only with required parameters.
func NewAnimation(chatID int64, animation interface{}) *SendAnimationParameters {
return &SendAnimationParameters{
ChatID: chatID,
Animation: animation,
}
}
// NewContact creates SendContactParameters only with required parameters.
func NewContact(chatID int64, phoneNumber, firstName string) *SendContactParameters {
return &SendContactParameters{
ChatID: chatID,
PhoneNumber: phoneNumber,
FirstName: firstName,
}
}
// NewDocument creates SendDocumentParameters only with required parameters.
func NewDocument(chatID int64, document interface{}) *SendDocumentParameters {
return &SendDocumentParameters{
ChatID: chatID,
Document: document,
}
}
// NewInvoice creates SendInvoiceParameters only with required parameters.
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices ...LabeledPrice) *SendInvoiceParameters {
return &SendInvoiceParameters{
ChatID: chatID,
Title: title,
Description: description,
Payload: payload,
ProviderToken: providerToken,
StartParameter: startParameter,
Currency: currency,
Prices: prices,
}
}
// NewLocation creates SendLocationParameters only with required parameters.
func NewLocation(chatID int64, latitude, longitude float32) *SendLocationParameters {
return &SendLocationParameters{
ChatID: chatID,
Latitude: latitude,
Longitude: longitude,
}
}
// NewMediaGroup creates SendMediaGroupParameters only with required parameters.
func NewMediaGroup(chatID int64, media ...interface{}) *SendMediaGroupParameters {
return &SendMediaGroupParameters{
ChatID: chatID,
Media: media,
}
}
// NewMessage creates SendMessageParameters only with required parameters.
func NewMessage(chatID int64, text string) *SendMessageParameters {
return &SendMessageParameters{
ChatID: chatID,
Text: text,
}
}
// NewPhoto creates SendPhotoParameters only with required parameters.
func NewPhoto(chatID int64, photo interface{}) *SendPhotoParameters {
return &SendPhotoParameters{
ChatID: chatID,
Photo: photo,
}
}
func NewPoll(chatID int64, question string, options ...string) SendPollConfig {
return SendPollConfig{
ChatID: chatID,
Question: question,
Options: options,
}
}
// NewVenue creates SendVenueParameters only with required parameters.
func NewVenue(chatID int64, latitude, longitude float32, title, address string) *SendVenueParameters {
return &SendVenueParameters{
ChatID: chatID,
Latitude: latitude,
Longitude: longitude,
Title: title,
Address: address,
}
}
// NewGame creates SendGameParameters only with required parameters.
func NewGame(chatID int64, gameShortName string) *SendGameParameters {
return &SendGameParameters{
ChatID: chatID,
GameShortName: gameShortName,
}
}
// SendAnimation send animation files (GIF or H.264/MPEG-4 AVC video without
// sound). On success, the sent Message is returned. Bots can currently send
// animation files of up to 50 MB in size, this limit may be changed in the
// future.
func (bot *Bot) SendAnimation(params *SendAnimationParameters) (*Message, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.Add("chat_id", strconv.FormatInt(params.ChatID, 10))
if params.Caption != "" {
args.Add("caption", params.Caption)
}
if params.ReplyMarkup != nil {
dst, err := parser.Marshal(params.ReplyMarkup)
if err != nil {
return nil, err
}
args.Add("reply_markup", string(dst))
}
if params.ReplyToMessageID != 0 {
args.Add("reply_to_message_id", strconv.Itoa(params.ReplyToMessageID))
}
args.Add("disable_notification", strconv.FormatBool(params.DisableNotification))
resp, err := bot.Upload(MethodSendAnimation, "animation", "", params.Animation, args)
if err != nil {
return nil, err
}
var result Message
err = parser.Unmarshal(resp.Result, &result)
return &result, err
}
// SendChatAction tell the user that something is happening on the bot's side.
// The status is set for 5 seconds or less (when a message arrives from your bot,
// Telegram clients clear its typing status). Returns True on success.
//
// We only recommend using this method when a response from the bot will take a
// noticeable amount of time to arrive.
func (bot *Bot) SendChatAction(chatID int64, action string) (bool, error) {
dst, err := parser.Marshal(&SendChatActionParameters{
ChatID: chatID,
Action: action,
})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodSendChatAction)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SendContact send phone contacts. On success, the sent Message is returned.
func (bot *Bot) SendContact(params *SendContactParameters) (*Message, error) {
dst, err := parser.Marshal(*params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSendContact)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SendDocument send general files. On success, the sent Message is returned. Bots can currently send
// files of any type of up to 50 MB in size, this limit may be changed in the future.
func (bot *Bot) SendDocument(params *SendDocumentParameters) (*Message, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.Add("chat_id", strconv.FormatInt(params.ChatID, 10))
if params.Caption != "" {
args.Add("caption", params.Caption)
}
if params.ReplyMarkup != nil {
dst, err := parser.Marshal(params.ReplyMarkup)
if err != nil {
return nil, err
}
args.Add("reply_markup", string(dst))
}
if params.ReplyToMessageID != 0 {
args.Add("reply_to_message_id", strconv.Itoa(params.ReplyToMessageID))
}
args.Add("disable_notification", strconv.FormatBool(params.DisableNotification))
resp, err := bot.Upload(MethodSendDocument, "document", "", params.Document, args)
if err != nil {
return nil, err
}
var result Message
err = parser.Unmarshal(resp.Result, &result)
return &result, err
}
// SendInvoice send invoices. On success, the sent Message is returned.
func (bot *Bot) SendInvoice(params *SendInvoiceParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSendInvoice)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SendLocation send point on the map. On success, the sent Message is returned.
func (bot *Bot) SendLocation(params *SendLocationParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSendLocation)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SendMediaGroup send a group of photos or videos as an album. On success, an array of the sent
// Messages is returned.
func (bot *Bot) SendMediaGroup(params *SendMediaGroupParameters) ([]Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSendMediaGroup)
if err != nil {
return nil, err
}
var group []Message
err = parser.Unmarshal(resp.Result, &group)
return group, err
}
// SendMessage send text messages. On success, the sent Message is returned.
func (bot *Bot) SendMessage(params *SendMessageParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSendMessage)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SendPhoto send photos. On success, the sent Message is returned.
func (bot *Bot) SendPhoto(params *SendPhotoParameters) (*Message, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.Add("chat_id", strconv.FormatInt(params.ChatID, 10))
if params.Caption != "" {
args.Add("caption", params.Caption)
}
if params.ReplyMarkup != nil {
dst, err := parser.Marshal(params.ReplyMarkup)
if err != nil {
return nil, err
}
args.Add("reply_markup", string(dst))
}
if params.ReplyToMessageID != 0 {
args.Add("reply_to_message_id", strconv.Itoa(params.ReplyToMessageID))
}
args.Add("disable_notification", strconv.FormatBool(params.DisableNotification))
resp, err := bot.Upload(MethodSendPhoto, "photo", "", params.Photo, args)
if err != nil {
return nil, err
}
var result Message
err = parser.Unmarshal(resp.Result, &result)
return &result, err
}
// SendPoll send a native poll. A native poll can't be sent to a private chat. On success, the sent Message is
// returned.
func (b *Bot) SendPoll(params SendPollConfig) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := b.request(dst, MethodSendPoll)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SendVenue send information about a venue. On success, the sent Message is returned.
func (bot *Bot) SendVenue(params *SendVenueParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSendVenue)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SendGame send a game. On success, the sent Message is returned.
func (bot *Bot) SendGame(params *SendGameParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSendGame)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SendSticker send .webp stickers. On success, the sent Message is returned.
func (b *Bot) SendSticker(params *SendStickerParameters) (*Message, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.Set("chat_id", strconv.FormatInt(params.ChatID, 10))
args.Set("disable_notification", strconv.FormatBool(params.DisableNotification))
if params.ReplyToMessageID > 0 {
args.SetUint("reply_to_message_id", params.ReplyToMessageID)
}
if params.ReplyMarkup != nil {
rm, err := parser.Marshal(params.ReplyMarkup)
if err != nil {
return nil, err
}
args.SetBytesV("reply_markup", rm)
}
resp, err := b.Upload(MethodSendSticker, TypeSticker, "sticker", params.Sticker, args)
if err != nil {
return nil, err
}
var result Message
err = parser.Unmarshal(resp.Result, &result)
return &result, err
}

370
set.go
View File

@ -1,370 +0,0 @@
package telegram
import (
"strconv"
"strings"
http "github.com/valyala/fasthttp"
)
type (
// SetChatDescriptionParameters represents data for SetChatDescription method.
SetChatDescriptionParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// New chat description, 0-255 characters
Description string `json:"description"`
}
// SetChatPhotoParameters represents data for SetChatPhoto method.
SetChatPhotoParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// New chat photo, uploaded using multipart/form-data
ChatPhoto interface{} `json:"chat_photo"`
}
// SetChatStickerSetParameters represents data for SetChatStickerSet method.
SetChatStickerSetParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Name of the sticker set to be set as the group sticker set
StickerSetName string `json:"sticker_set_name"`
}
// SetChatTitleParameters represents data for SetChatTitle method.
SetChatTitleParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// New chat title, 1-255 characters
Title string `json:"title"`
}
SetPassportDataErrorsParameters struct {
// User identifier
UserID int `json:"user_id"`
// A JSON-serialized array describing the errors
Errors []PassportElementError `json:"errors"`
}
// SetWebhookParameters represents data for SetWebhook method.
SetWebhookParameters struct {
// HTTPS url to send updates to. Use an empty string to remove webhook
// integration
URL string `json:"url"`
// Upload your public key certificate so that the root certificate in use can
// be checked. See our self-signed guide for details.
Certificate InputFile `json:"certificate,omitempty"`
// 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 bots server, and higher values to increase your bots
// throughput.
MaxConnections int `json:"max_connections,omitempty"`
// List the types of updates you want your bot to receive. For example,
// specify [“message”, “edited_channel_post”, “callback_query”] to only
// receive updates of these types. See Update for a complete list of
// available update types. Specify an empty list to receive all updates
// regardless of type (default). If not specified, the previous setting will
// be used.
//
// Please note that this parameter doesn't affect updates created before the
// call to the setWebhook, so unwanted updates may be received for a short
// period of time.
AllowedUpdates []string `json:"allowed_updates,omitempty"`
}
// SetGameScoreParameters represents data for SetGameScore method.
SetGameScoreParameters struct {
// User identifier
UserID int `json:"user_id"`
// New score, must be non-negative
Score int `json:"score"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Pass True, if the high score is allowed to decrease. This can be useful
// when fixing mistakes or banning cheaters
Force bool `json:"force,omitempty"`
// Pass True, if the game message should not be automatically edited to
// include the current scoreboard
DisableEditMessage bool `json:"disable_edit_message,omitempty"`
// Required if inline_message_id is not specified. Unique identifier for the
// target chat
ChatID int64 `json:"chat_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
}
// SetStickerPositionInSetParameters represents data for SetStickerPositionInSet
// method.
SetStickerPositionInSetParameters struct {
// File identifier of the sticker
Sticker string `json:"sticker"`
// New sticker position in the set, zero-based
Position int `json:"position"`
}
// SetChatPermissionsParameters represents data for SetChatPermissions method.
SetChatPermissionsParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// New default chat permissions
Permissions ChatPermissions `json:"permissions"`
}
)
// NewWebhook creates new SetWebhookParameters only with required parameters.
func NewWebhook(url string, file interface{}) *SetWebhookParameters {
return &SetWebhookParameters{
URL: url,
Certificate: file,
}
}
// NewGameScore creates SetGameScoreParameters only with required parameters.
func NewGameScore(userID, score int) *SetGameScoreParameters {
return &SetGameScoreParameters{
UserID: userID,
Score: score,
}
}
// SetChatDescription change the description of a supergroup or a channel. The
// bot must be an administrator in the chat for this to work and must have the
// appropriate admin rights. Returns True on success.
func (bot *Bot) SetChatDescription(chatID int64, description string) (bool, error) {
dst, err := parser.Marshal(&SetChatDescriptionParameters{
ChatID: chatID,
Description: description,
})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodSetChatDescription)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SetChatPhoto set a new profile photo for the chat. Photos can't be changed for private chats. The
// bot must be an administrator in the chat for this to work and must have the appropriate admin
// rights. Returns True on success.
//
// Note: In regular groups (non-supergroups), this method will only work if the 'All Members Are
// Admins' setting is off in the target group.
func (bot *Bot) SetChatPhoto(chatID int64, chatPhoto interface{}) (bool, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.Add("chat_id", strconv.FormatInt(chatID, 10))
resp, err := bot.Upload(MethodSetChatPhoto, TypePhoto, "chat_photo", chatPhoto, args)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SetChatStickerSet set a new group sticker set for a supergroup. The bot must be an administrator
// in the chat for this to work and must have the appropriate admin rights. Use the field
// can_set_sticker_set optionally returned in getChat requests to check if the bot can use this
// method. Returns True on success.
func (bot *Bot) SetChatStickerSet(chatID int64, stickerSetName string) (bool, error) {
dst, err := parser.Marshal(&SetChatStickerSetParameters{
ChatID: chatID,
StickerSetName: stickerSetName,
})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodSetChatStickerSet)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SetChatTitle change the title of a chat. Titles can't be changed for private
// chats. The bot must be an administrator in the chat for this to work and must
// have the appropriate admin rights. Returns True on success.
//
// Note: In regular groups (non-supergroups), this method will only work if the
// 'All Members Are Admins' setting is off in the target group.
func (bot *Bot) SetChatTitle(chatID int64, title string) (bool, error) {
dst, err := parser.Marshal(&SetChatTitleParameters{
ChatID: chatID,
Title: title,
})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodSetChatTitle)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SetPassportDataErrors informs a user that some of the Telegram Passport
// elements they provided contains errors. The user will not be able to re-submit
// their Passport to you until the errors are fixed (the contents of the field
// for which you returned the error must change). Returns True on success.
//
// Use this if the data submitted by the user doesn't satisfy the standards your
// service requires for any reason. For example, if a birthday date seems
// invalid, a submitted document is blurry, a scan shows evidence of tampering,
// etc. Supply some details in the error message to make sure the user knows how
// to correct the issues.
func (b *Bot) SetPassportDataErrors(userId int, errors []PassportElementError) (bool, error) {
dst, err := parser.Marshal(&SetPassportDataErrorsParameters{
UserID: userId,
Errors: errors,
})
if err != nil {
return false, err
}
resp, err := b.request(dst, MethodSetPassportDataErrors)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SetWebhook specify a url and receive incoming updates via an outgoing webhook.
// Whenever there is an update for the bot, we will send an HTTPS POST request to
// the specified url, containing a JSON-serialized Update. In case of an
// unsuccessful request, we will give up after a reasonable amount of attempts.
// Returns true.
//
// If you'd like to make sure that the Webhook request comes from Telegram, we
// recommend using a secret path in the URL, e.g. https://www.example.com/<token>.
// Since nobody else knows your bots token, you can be pretty sure its us.
func (bot *Bot) SetWebhook(params *SetWebhookParameters) (bool, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.Add("url", params.URL)
if len(params.AllowedUpdates) > 0 {
args.Add("allowed_updates", strings.Join(params.AllowedUpdates, ","))
}
if params.MaxConnections > 0 &&
params.MaxConnections <= 100 {
args.Add("max_connections", strconv.Itoa(params.MaxConnections))
}
var resp *Response
var err error
if params.Certificate != nil {
resp, err = bot.Upload(MethodSetWebhook, "certificate", "cert.pem", params.Certificate, args)
} else {
var dst []byte
dst, err = parser.Marshal(params)
if err != nil {
return false, err
}
resp, err = bot.request(dst, MethodSetWebhook)
}
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SetGameScore set the score of the specified user in a game. On success, if the
// message was sent by the bot, returns the edited Message, otherwise returns
// True. Returns an error, if the new score is not greater than the user's
// current score in the chat and force is False.
func (bot *Bot) SetGameScore(params *SetGameScoreParameters) (*Message, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := bot.request(dst, MethodSetGameScore)
if err != nil {
return nil, err
}
var msg Message
err = parser.Unmarshal(resp.Result, &msg)
return &msg, err
}
// SetStickerPositionInSet move a sticker in a set created by the bot to a
// specific position. Returns True on success.
func (b *Bot) SetStickerPositionInSet(sticker string, position int) (bool, error) {
dst, err := parser.Marshal(&SetStickerPositionInSetParameters{
Sticker: sticker,
Position: position,
})
if err != nil {
return false, err
}
resp, err := b.request(dst, MethodSetStickerPositionInSet)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}
// SetChatPermissions set default chat permissions for all members. The bot must be an administrator in the group or a
// supergroup for this to work and must have the can_restrict_members admin rights. Returns True on success.
func (b *Bot) SetChatPermissions(params SetChatPermissionsParameters) (bool, error) {
dst, err := parser.Marshal(&params)
if err != nil {
return false, err
}
resp, err := b.request(dst, MethodSetChatPermissions)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

364
stickers.go Normal file
View File

@ -0,0 +1,364 @@
package telegram
import "strconv"
type (
// Sticker represents a sticker.
Sticker 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"`
// Sticker width
Width int `json:"width"`
// Sticker height
Height int `json:"height"`
// true, if the sticker is animated
IsAnimated bool `json:"is_animated"`
// Sticker thumbnail in the .webp or .jpg format
Thumb *PhotoSize `json:"thumb,omitempty"`
// Emoji associated with the sticker
Emoji string `json:"emoji,omitempty"`
// Name of the sticker set to which the sticker belongs
SetName string `json:"set_name,omitempty"`
// For mask stickers, the position where the mask should be placed
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
// File size
FileSize int `json:"file_size,omitempty"`
}
// StickerSet represents a sticker set.
StickerSet struct {
// Sticker set name
Name string `json:"name"`
// Sticker set title
Title string `json:"title"`
// List of all set stickers
Stickers []Sticker `json:"stickers"`
// True, if the sticker set contains masks
ContainsMasks bool `json:"contains_masks"`
// true, if the sticker set contains animated stickers
IsAnimated bool `json:"is_animated"`
}
// MaskPosition describes the position on faces where a mask should be placed by default.
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 float32 `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 float32 `json:"y_shift"`
// Mask scaling coefficient. For example, 2.0 means double size.
Scale float32 `json:"scale"`
}
// SendStickerParameters represents data for SetSticker method.
SendSticker struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Sticker to send
Sticker InputFile `json:"sticker"`
// 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 int `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 ReplyMarkup `json:"reply_markup,omitempty"`
}
// GetStickerSetParameters represents data for GetStickerSet method.
GetStickerSet struct {
// Name of the sticker set
Name string `json:"name"`
}
UploadStickerFile struct {
// User identifier of sticker file owner
UserID int `json:"user_id"`
// Png image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px.
PNGSticker *InputFile `json:"png_sticker"`
}
CreateNewStickerSet struct {
// User identifier of created sticker set owner
UserID int `json:"user_id"`
// Short name of sticker set, to be used in t.me/addstickers/ URLs (e.g., animals). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in “_by_<bot username>”. <bot_username> is case insensitive. 1-64 characters.
Name string `json:"name"`
// Sticker set title, 1-64 characters
Title string `json:"title"`
// Png image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a file_id as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data.
PNGSticker *InputFile `json:"png_sticker"`
// One or more emoji corresponding to the sticker
Emojis string `json:"emojis"`
// Pass True, if a set of mask stickers should be created
ContainsMasks bool `json:"contains_masks,omitempty"`
// A JSON-serialized object for position where the mask should be placed on faces
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
}
AddStickerToSet struct {
// User identifier of sticker set owner
UserID int `json:"user_id"`
// Sticker set name
Name string `json:"name"`
// Png image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a file_id as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. More info on Sending Files »
PNGSticker *InputFile `json:"png_sticker"`
// One or more emoji corresponding to the sticker
Emojis string `json:"emojis"`
// A JSON-serialized object for position where the mask should be placed on faces
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
}
// SetStickerPositionInSetParameters represents data for SetStickerPositionInSet method.
SetStickerPositionInSet struct {
// File identifier of the sticker
Sticker string `json:"sticker"`
// New sticker position in the set, zero-based
Position int `json:"position"`
}
// DeleteStickerFromSetParameters represents data for DeleteStickerFromSet method.
DeleteStickerFromSet struct {
// File identifier of the sticker
Sticker string `json:"sticker"`
}
)
// SendSticker send .webp stickers. On success, the sent Message is returned.
func (b Bot) SendSticker(p SendSticker) (*Message, error) {
src, err := b.Do(MethodSendSticker, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// GetStickerSet get a sticker set. On success, a StickerSet object is returned.
func (b Bot) GetStickerSet(name string) (*StickerSet, error) {
src, err := b.Do(MethodGetStickerSet, GetStickerSet{Name: name})
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(StickerSet)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// UploadStickerFile upload a .png file with a sticker for later use in createNewStickerSet and addStickerToSet methods (can be used multiple times). Returns the uploaded File on success.
func (b Bot) UploadStickerFile(uid int, sticker *InputFile) (*File, error) {
params := make(map[string]string)
params["user_id"] = strconv.Itoa(uid)
var err error
if params["png_sticker"], err = b.marshler.MarshalToString(sticker); err != nil {
return nil, err
}
src, err := b.Upload(MethodUploadStickerFile, params, sticker)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(File)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// CreateNewStickerSet create new sticker set owned by a user. The bot will be able to edit the created sticker set. Returns True on success.
func (b *Bot) CreateNewStickerSet(p CreateNewStickerSet) (bool, error) {
params := make(map[string]string)
params["user_id"] = strconv.Itoa(p.UserID)
params["name"] = p.Name
params["title"] = p.Title
params["emojis"] = p.Emojis
params["contains_masks"] = strconv.FormatBool(p.ContainsMasks)
var err error
if params["png_sticker"], err = b.marshler.MarshalToString(p.PNGSticker); err != nil {
return false, err
}
if params["mask_position"], err = b.marshler.MarshalToString(p.MaskPosition); err != nil {
return false, err
}
files := make([]*InputFile, 0)
if p.PNGSticker.IsAttachment() {
files = append(files, p.PNGSticker)
}
src, err := b.Upload(MethodCreateNewStickerSet, params, files...)
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}
// AddStickerToSet add a new sticker to a set created by the b. Returns True on success.
func (b *Bot) AddStickerToSet(p AddStickerToSet) (bool, error) {
params := make(map[string]string)
params["user_id"] = strconv.Itoa(p.UserID)
params["name"] = p.Name
params["emojis"] = p.Emojis
var err error
if params["png_sticker"], err = b.marshler.MarshalToString(p.PNGSticker); err != nil {
return false, err
}
if params["mask_position"], err = b.marshler.MarshalToString(p.MaskPosition); err != nil {
return false, err
}
files := make([]*InputFile, 0)
if p.PNGSticker.IsAttachment() {
files = append(files, p.PNGSticker)
}
src, err := b.Upload(MethodAddStickerToSet, params, files...)
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}
// SetStickerPositionInSet move a sticker in a set created by the bot to a specific position. Returns True on success.
func (b *Bot) SetStickerPositionInSet(sticker string, position int) (bool, error) {
src, err := b.marshler.Marshal(&SetStickerPositionInSet{
Sticker: sticker,
Position: position,
})
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}
// DeleteStickerFromSet delete a sticker from a set created by the b. Returns True on success.
func (b *Bot) DeleteStickerFromSet(sticker string) (bool, error) {
src, err := b.Do(MethodDeleteStickerFromSet, DeleteStickerFromSet{Sticker: sticker})
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}
// InSet checks that the current sticker in the stickers set.
func (s Sticker) InSet() bool { return s.SetName != "" }
func (s Sticker) HasThumb() bool { return s.Thumb != nil }
func (s Sticker) IsMask() bool { return s.MaskPosition != nil }
func (s Sticker) File() File {
return File{
FileID: s.FileID,
FileUniqueID: s.FileUniqueID,
FileSize: s.FileSize,
}
}

55
stickers_test.go Normal file
View File

@ -0,0 +1,55 @@
package telegram
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestStickerInSet(t *testing.T) {
t.Run("true", func(t *testing.T) {
s := Sticker{SetName: "HotCherry"}
assert.True(t, s.InSet())
})
t.Run("false", func(t *testing.T) {
s := Sticker{}
assert.False(t, s.InSet())
})
}
func TestStickerHasThumb(t *testing.T) {
t.Run("true", func(t *testing.T) {
s := Sticker{Thumb: &PhotoSize{FileID: "abc"}}
assert.True(t, s.HasThumb())
})
t.Run("false", func(t *testing.T) {
s := Sticker{}
assert.False(t, s.HasThumb())
})
}
func TestStickerIsMask(t *testing.T) {
t.Run("true", func(t *testing.T) {
s := Sticker{MaskPosition: &MaskPosition{Point: PointEyes}}
assert.True(t, s.IsMask())
})
t.Run("false", func(t *testing.T) {
s := Sticker{}
assert.False(t, s.IsMask())
})
}
func TestStickerFile(t *testing.T) {
t.Run("valid", func(t *testing.T) {
s := Sticker{FileID: "abc", FileUniqueID: "really_abc", FileSize: 42}
assert.Equal(t, s.File(), File{
FileID: "abc",
FileUniqueID: "really_abc",
FileSize: 42,
})
})
t.Run("empty", func(t *testing.T) {
var s Sticker
assert.Equal(t, s.File(), File{})
})
}

28
stop.go
View File

@ -1,28 +0,0 @@
package telegram
type StopPollConfig struct {
// Unique identifier for the target chat. A native poll can't be sent to a private chat.
ChatID int64 `json:"chat_id"`
// Identifier of the original message with the poll
MessageID int `json:"message_id"`
// A JSON-serialized object for a new message inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
func (b *Bot) StopPoll(params StopPollConfig) (*Poll, error) {
dst, err := parser.Marshal(params)
if err != nil {
return nil, err
}
resp, err := b.request(dst, MethodStopPoll)
if err != nil {
return nil, err
}
var poll Poll
err = parser.Unmarshal(resp.Result, &poll)
return &poll, err
}

View File

@ -1,68 +0,0 @@
package telegram
import (
gojson "encoding/json"
"errors"
"path"
json "github.com/json-iterator/go"
http "github.com/valyala/fasthttp"
)
// Response represents a response from the Telegram API with the result
// stored raw. If ok equals true, the request was successful, and the result
// of the query can be found in the result field. In case of an unsuccessful
// request, ok equals false, and the error is explained in the error field.
type Response struct {
Ok bool `json:"ok"`
ErrorCode int `json:"error_code,omitempty"`
Description string `json:"description,omitempty"`
Result gojson.RawMessage `json:"result,omitempty"`
Parameters *ResponseParameters `json:"parameters,omitempty"`
}
var (
defaultClient = http.Client{}
parser = json.ConfigFastest
)
func (b *Bot) request(dst []byte, method string) (*Response, error) {
if b.Client == nil {
b.SetClient(&defaultClient)
}
requestURI := http.AcquireURI()
requestURI.SetScheme("https")
requestURI.SetHost("api.telegram.org")
requestURI.SetPath(path.Join("bot"+b.AccessToken, method))
req := http.AcquireRequest()
defer http.ReleaseRequest(req)
req.Header.SetContentType("application/json; charset=utf-8")
req.Header.SetMethod(http.MethodPost)
if dst == nil {
req.Header.SetMethod(http.MethodGet)
}
req.Header.SetRequestURI(requestURI.String())
req.Header.SetUserAgent(path.Join("telegram", Version))
req.Header.SetHostBytes(requestURI.Host())
req.SetBody(dst)
resp := http.AcquireResponse()
defer http.ReleaseResponse(resp)
if err := b.Client.Do(req, resp); err != nil {
return nil, err
}
var data Response
if err := parser.Unmarshal(resp.Body(), &data); err != nil {
return nil, err
}
if !data.Ok {
return nil, errors.New(data.Description)
}
return &data, nil
}

2898
types.go

File diff suppressed because it is too large Load Diff

2128
types_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
package telegram
// UnbanChatMemberParameters represents data for UnbanChatMember method.
type UnbanChatMemberParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
UserID int `json:"user_id"`
}
// UnbanChatMember unban a previously kicked user in a supergroup or channel. The
// user will not return to the group or channel automatically, but will be able
// to join via link, etc. The bot must be an administrator for this to work.
// Returns True on success.
func (bot *Bot) UnbanChatMember(chatID int64, userID int) (bool, error) {
params := UnbanChatMemberParameters{
ChatID: chatID,
UserID: userID,
}
dst, err := parser.Marshal(&params)
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodUnbanChatMember)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

View File

@ -1,26 +0,0 @@
package telegram
// UnpinChatMessageParameters represents data for UnpinChatMessage method.
type UnpinChatMessageParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
}
// UnpinChatMessage unpin a message in a supergroup chat. The bot must be an
// administrator in the chat for this to work and must have the appropriate admin
// rights. Returns True on success.
func (bot *Bot) UnpinChatMessage(chatID int64) (bool, error) {
dst, err := parser.Marshal(&UnpinChatMessageParameters{ChatID: chatID})
if err != nil {
return false, err
}
resp, err := bot.request(dst, MethodUnpinChatMessage)
if err != nil {
return false, err
}
var ok bool
err = parser.Unmarshal(resp.Result, &ok)
return ok, err
}

264
updates.go Normal file
View File

@ -0,0 +1,264 @@
package telegram
import (
"time"
http "github.com/valyala/fasthttp"
)
type (
// Update represents an incoming update.
//
// At most one of the optional parameters can be present in any given update.
Update struct {
// The updates unique identifier. Update identifiers start from a certain positive number and increase sequentially. This ID becomes especially handy if youre using Webhooks, since it allows you to ignore repeated updates or to restore the correct update sequence, should they get out of order.
UpdateID int `json:"update_id"`
// New incoming message of any kind — text, photo, sticker, etc.
Message *Message `json:"message,omitempty"`
// New version of a message that is known to the bot and was edited
EditedMessage *Message `json:"edited_message,omitempty"`
// New incoming channel post of any kind — text, photo, sticker, etc.
ChannelPost *Message `json:"channel_post,omitempty"`
// New version of a channel post that is known to the bot and was edited
EditedChannelPost *Message `json:"adited_channel_post,omitempty"`
// New incoming inline query
InlineQuery *InlineQuery `json:"inline_query,omitempty"`
// The result of an inline query that was chosen by a user and sent to their chat partner.
ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result,omitempty"`
// New incoming callback query
CallbackQuery *CallbackQuery `json:"callback_query,omitempty"`
// New incoming shipping query. Only for invoices with flexible price
ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"`
// New incoming pre-checkout query. Contains full information about checkout
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"`
// New poll state. Bots receive only updates about polls, which are sent or stopped by the bot
Poll *Poll `json:"poll,omitempty"`
}
// WebhookInfo contains information about the current status of a webhook.
WebhookInfo struct {
// Webhook URL, may be empty if webhook is not set up
URL string `json:"url"`
// 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,omitempty"`
// True, if a custom certificate was provided for webhook certificate checks
HasCustomCertificate bool `json:"has_custom_certificate"`
// Number of updates awaiting delivery
PendingUpdateCount int `json:"pending_update_count"`
// Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery
MaxConnections int `json:"max_connections,omitempty"`
// Unix time for the most recent error that happened when trying to deliver an update via webhook
LastErrorDate int64 `json:"last_error_date,omitempty"`
// A list of update types the bot is subscribed to. Defaults to all update types
AllowedUpdates []string `json:"allowed_updates,omitempty"`
}
// GetUpdatesParameters represents data for GetUpdates method.
GetUpdates struct {
// Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as getUpdates is called with an offset higher than its update_id. The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue. All previous updates will forgotten.
Offset int `json:"offset,omitempty"`
// Limits the number of updates to be retrieved. Values between 1—100 are accepted. Defaults to 100.
Limit int `json:"limit,omitempty"`
// Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling. Should be positive, short polling should be used for testing purposes only.
Timeout int `json:"timeout,omitempty"`
// List the types of updates you want your bot to receive. For example, specify ["message", "edited_channel_post", "callback_query"] to only receive updates of these types. See Update for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used.
//
// Please note that this parameter doesn't affect updates created before the call to the getUpdates, so unwanted updates may be received for a short period of time.
AllowedUpdates []string `json:"allowed_updates,omitempty"`
}
// SetWebhookParameters represents data for SetWebhook method.
SetWebhook struct {
// HTTPS url to send updates to. Use an empty string to remove webhook integration
URL string `json:"url"`
// Upload your public key certificate so that the root certificate in use can be checked. See our self-signed guide for details.
Certificate InputFile `json:"certificate,omitempty"`
// 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 bots server, and higher values to increase your bots throughput.
MaxConnections int `json:"max_connections,omitempty"`
// List the types of updates you want your bot to receive. For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. See Update for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used.
//
// Please note that this parameter doesn't affect updates created before the call to the setWebhook, so unwanted updates may be received for a short period of time.
AllowedUpdates []string `json:"allowed_updates,omitempty"`
}
)
// GetUpdates receive incoming updates using long polling. An Array of Update objects is returned.
func (b Bot) GetUpdates(p *GetUpdates) ([]*Update, error) {
src, err := b.Do(MethodGetUpdates, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := make([]*Update, 0)
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return nil, err
}
return result, nil
}
// SetWebhook specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns true.
//
// If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. https://www.example.com/<token>. Since nobody else knows your bots token, you can be pretty sure its us.
func (b Bot) SetWebhook(p SetWebhook) (bool, error) {
// TODO(toby3d)
// if p.Certificate != nil {
// src, err = b.Upload(MethodSetWebhook, "certificate", "cert.pem", params.Certificate, args)
// }
src, err := b.Do(MethodSetWebhook, p)
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}
// DeleteWebhook remove webhook integration if you decide to switch back to getUpdates. Returns True on success. Requires no parameters.
func (b Bot) DeleteWebhook() (bool, error) {
src, err := b.Do(MethodDeleteWebhook, nil)
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, 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 (b Bot) GetWebhookInfo() (*WebhookInfo, error) {
src, err := b.Do(MethodGetWebhookInfo, nil)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(WebhookInfo)
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return nil, err
}
return result, nil
}
// IsMessage checks that the current update is a message creation event.
func (u Update) IsMessage() bool { return u.Message != nil }
// IsEditedMessage checks that the current update is a editing message event.
func (u Update) IsEditedMessage() bool { return u.EditedMessage != nil }
// IsChannelPost checks that the current update is a post channel creation event.
func (u Update) IsChannelPost() bool { return u.ChannelPost != nil }
// IsEditedChannelPost checks that the current update is a editing post channel event.
func (u Update) IsEditedChannelPost() bool { return u.EditedChannelPost != nil }
// IsInlineQuery checks that the current update is a inline query update.
func (u Update) IsInlineQuery() bool { return u.InlineQuery != nil }
// IsChosenInlineResult checks that the current update is a chosen inline result update.
func (u Update) IsChosenInlineResult() bool { return u.ChosenInlineResult != nil }
// IsCallbackQuery checks that the current update is a callback query update.
func (u Update) IsCallbackQuery() bool { return u.CallbackQuery != nil }
// IsShippingQuery checks that the current update is a shipping query update.
func (u Update) IsShippingQuery() bool { return u.ShippingQuery != nil }
// IsPreCheckoutQuery checks that the current update is a pre checkout query update.
func (u Update) IsPreCheckoutQuery() bool { return u.PreCheckoutQuery != nil }
// IsPoll checks that the current update is a poll update.
func (u Update) IsPoll() bool { return u.Poll != nil }
// Type return update type for current update.
func (u Update) Type() string {
switch {
case u.IsCallbackQuery():
return UpdateCallbackQuery
case u.IsChannelPost():
return UpdateChannelPost
case u.IsChosenInlineResult():
return UpdateChosenInlineResult
case u.IsEditedChannelPost():
return UpdateEditedChannelPost
case u.IsEditedMessage():
return UpdateEditedMessage
case u.IsInlineQuery():
return UpdateInlineQuery
case u.IsMessage():
return UpdateMessage
case u.IsPreCheckoutQuery():
return UpdatePreCheckoutQuery
case u.IsShippingQuery():
return UpdateShippingQuery
case u.IsPoll():
return UpdatePoll
default:
return ""
}
}
func (w WebhookInfo) LastErrorTime() time.Time { return time.Unix(w.LastErrorDate, 0) }
func (w WebhookInfo) HasURL() bool { return w.URL != "" }
func (w WebhookInfo) URI() *http.URI {
if !w.HasURL() {
return nil
}
u := http.AcquireURI()
u.Update(w.URL)
return u
}

208
updates_test.go Normal file
View File

@ -0,0 +1,208 @@
package telegram
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
http "github.com/valyala/fasthttp"
)
func TestUpdateIsMessage(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{Message: &Message{ID: 42}}
assert.True(t, u.IsMessage())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsMessage())
})
}
func TestUpdateIsEditedMessage(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{EditedMessage: &Message{ID: 42}}
assert.True(t, u.IsEditedMessage())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsEditedMessage())
})
}
func TestUpdateIsChannelPost(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{ChannelPost: &Message{ID: 42}}
assert.True(t, u.IsChannelPost())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsChannelPost())
})
}
func TestUpdateIsEditedChannelPost(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{EditedChannelPost: &Message{ID: 42}}
assert.True(t, u.IsEditedChannelPost())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsEditedChannelPost())
})
}
func TestUpdateIsInlineQuery(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{InlineQuery: &InlineQuery{Query: "abc"}}
assert.True(t, u.IsInlineQuery())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsInlineQuery())
})
}
func TestUpdateIsChosenInlineResult(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{ChosenInlineResult: &ChosenInlineResult{ResultID: "abc"}}
assert.True(t, u.IsChosenInlineResult())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsChosenInlineResult())
})
}
func TestUpdateIsCallbackQuery(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{CallbackQuery: &CallbackQuery{ID: "abc"}}
assert.True(t, u.IsCallbackQuery())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsCallbackQuery())
})
}
func TestUpdateIsShippingQuery(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{ShippingQuery: &ShippingQuery{ID: "abc"}}
assert.True(t, u.IsShippingQuery())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsShippingQuery())
})
}
func TestUpdateIsPreCheckoutQuery(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{PreCheckoutQuery: &PreCheckoutQuery{ID: "abc"}}
assert.True(t, u.IsPreCheckoutQuery())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsPreCheckoutQuery())
})
}
func TestUpdateIsPoll(t *testing.T) {
t.Run("true", func(t *testing.T) {
u := Update{Poll: &Poll{ID: "abc"}}
assert.True(t, u.IsPoll())
})
t.Run("false", func(t *testing.T) {
u := Update{}
assert.False(t, u.IsPoll())
})
}
func TestUpdateType(t *testing.T) {
for _, tc := range []struct {
name string
update Update
expResult string
}{{
name: UpdateCallbackQuery,
update: Update{CallbackQuery: &CallbackQuery{ID: "abc"}},
expResult: UpdateCallbackQuery,
}, {
name: UpdateChannelPost,
update: Update{ChannelPost: &Message{ID: 42}},
expResult: UpdateChannelPost,
}, {
name: UpdateChosenInlineResult,
update: Update{ChosenInlineResult: &ChosenInlineResult{Query: "query"}},
expResult: UpdateChosenInlineResult,
}, {
name: UpdateEditedChannelPost,
update: Update{EditedChannelPost: &Message{ID: 42}},
expResult: UpdateEditedChannelPost,
}, {
name: UpdateEditedMessage,
update: Update{EditedMessage: &Message{ID: 42}},
expResult: UpdateEditedMessage,
}, {
name: UpdateInlineQuery,
update: Update{InlineQuery: &InlineQuery{ID: "abc"}},
expResult: UpdateInlineQuery,
}, {
name: UpdateMessage,
update: Update{Message: &Message{ID: 42}},
expResult: UpdateMessage,
}, {
name: UpdatePoll,
update: Update{Poll: &Poll{ID: "abc"}},
expResult: UpdatePoll,
}, {
name: UpdatePreCheckoutQuery,
update: Update{PreCheckoutQuery: &PreCheckoutQuery{ID: "abc"}},
expResult: UpdatePreCheckoutQuery,
}, {
name: UpdateShippingQuery,
update: Update{ShippingQuery: &ShippingQuery{ID: "abc"}},
expResult: UpdateShippingQuery,
}, {
name: "other",
update: Update{},
expResult: "",
}} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.update.Type(), tc.expResult)
})
}
}
func TestWebhookInfoLastErrorTime(t *testing.T) {
now := time.Now().Round(time.Second)
wi := WebhookInfo{LastErrorDate: now.Unix()}
assert.Equal(t, wi.LastErrorTime(), now)
}
func TestWebhookInfoHasURL(t *testing.T) {
t.Run("true", func(t *testing.T) {
wi := WebhookInfo{URL: "https://bot.toby3d.me"}
assert.True(t, wi.HasURL())
})
t.Run("false", func(t *testing.T) {
wi := WebhookInfo{}
assert.False(t, wi.HasURL())
})
}
func TestWebhookInfoURI(t *testing.T) {
u := http.AcquireURI()
defer http.ReleaseURI(u)
u.Update("https://bot.toby3d.me")
t.Run("true", func(t *testing.T) {
wi := WebhookInfo{URL: u.String()}
assert.Equal(t, wi.URI().String(), u.String())
})
t.Run("false", func(t *testing.T) {
wi := WebhookInfo{}
assert.Nil(t, wi.URI())
})
}

230
updating_messages.go Normal file
View File

@ -0,0 +1,230 @@
package telegram
type (
// EditMessageTextParameters represents data for EditMessageText method.
EditMessageText struct {
// Required if inline_message_id is not specified. Unique identifier for the target chat or username of the target channel (in the format @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// New text of the message
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"`
// A JSON-serialized object for an inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageCaptionParameters represents data for EditMessageCaption method.
EditMessageCaption struct {
// Required if inline_message_id is not specified. Unique identifier for the target chat or username of the target channel (in the format @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// New caption of the message
Caption string `json:"caption,omitempty"`
// Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption.
ParseMode string `json:"parse_mode,omitempty"`
// A JSON-serialized object for an inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageMediaParameters represents data for EditMessageMedia method.
EditMessageMedia struct {
// Required if inline_message_id is not specified. Unique identifier for the target chat or username of the target channel (in the format @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// A JSON-serialized object for a new media content of the message
Media interface{} `json:"media"`
// A JSON-serialized object for a new inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// EditMessageReplyMarkupParameters represents data for EditMessageReplyMarkup method.
EditMessageReplyMarkup struct {
// Required if inline_message_id is not specified. Unique identifier for the target chat or username of the target channel (in the format @channelusername)
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
StopPoll struct {
// Unique identifier for the target chat. A native poll can't be sent to a private chat.
ChatID int64 `json:"chat_id"`
// Identifier of the original message with the poll
MessageID int `json:"message_id"`
// A JSON-serialized object for a new message inline keyboard.
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// DeleteMessageParameters represents data for DeleteMessage method.
DeleteMessage struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
// Identifier of the message to delete
MessageID int `json:"message_id"`
}
)
// EditMessageText edit text and game messages sent by the bot or via the bot (for inline bots). On success, if edited message is sent by the bot, the edited Message is returned, otherwise True is returned.
func (b Bot) EditMessageText(p *EditMessageText) (*Message, error) {
src, err := b.Do(MethodEditMessageText, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// EditMessageCaption edit captions of messages sent by the bot or via the bot (for inline bots). On success, if edited message is sent by the bot, the edited Message is returned, otherwise True is returned.
func (b Bot) EditMessageCaption(p *EditMessageCaption) (*Message, error) {
src, err := b.Do(MethodEditMessageCaption, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// EditMessageMedia edit audio, document, photo, or video messages. If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded. Use previously uploaded file via its file_id or specify a URL. On success, if the edited message was sent by the bot, the edited Message is returned, otherwise True is returned.
func (b Bot) EditMessageMedia(p EditMessageMedia) (*Message, error) {
src, err := b.Do(MethodEditMessageMedia, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// EditMessageReplyMarkup edit only the reply markup of messages sent by the bot or via the bot (for inline bots). On success, if edited message is sent by the bot, the edited Message is returned, otherwise True is returned.
func (b Bot) EditMessageReplyMarkup(p EditMessageReplyMarkup) (*Message, error) {
src, err := b.Do(MethodEditMessageReplyMarkup, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Message)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// StopPoll stop a poll which was sent by the bot. On success, the stopped Poll with the final results is returned.
func (b Bot) StopPoll(p StopPoll) (*Poll, error) {
src, err := b.Do(MethodStopPoll, p)
if err != nil {
return nil, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return nil, err
}
result := new(Poll)
if err = b.marshler.Unmarshal(resp.Result, result); err != nil {
return nil, err
}
return result, nil
}
// DeleteMessage delete a message, including service messages, with the following limitations:
//
// - A message can only be deleted if it was sent less than 48 hours ago.
// - Bots can delete outgoing messages in private chats, groups, and supergroups.
// - Bots can delete incoming messages in private chats.
// - Bots granted can_post_messages permissions can delete outgoing messages in channels.
// - If the bot is an administrator of a group, it can delete any message there.
// - If the bot has can_delete_messages permission in a supergroup or a channel, it can delete any message there.
//
// Returns True on success.
func (b Bot) DeleteMessage(cid int64, mid int) (bool, error) {
src, err := b.Do(MethodDeleteMessage, DeleteMessage{ChatID: cid, MessageID: mid})
if err != nil {
return false, err
}
resp := new(Response)
if err = b.marshler.Unmarshal(src, resp); err != nil {
return false, err
}
var result bool
if err = b.marshler.Unmarshal(resp.Result, &result); err != nil {
return false, err
}
return result, nil
}

186
upload.go
View File

@ -1,186 +0,0 @@
package telegram
import (
"bytes"
"errors"
"io"
"mime/multipart"
"os"
"path"
http "github.com/valyala/fasthttp"
)
type UploadStickerFileParameters struct {
// User identifier of sticker file owner
UserID int `json:"user_id"`
// Png image with the sticker, must be up to 512 kilobytes in size,
// dimensions must not exceed 512px, and either width or height
// must be exactly 512px.
PNGSticker interface{} `json:"png_sticker"`
}
// ErrBadFileType describes error of the unsupported file data type for uploading
var ErrBadFileType = errors.New("bad file type")
/*
Upload is a helper method which provide are three ways to send files (photos, stickers, audio,
media, etc.):
1. If the file is already stored somewhere on the Telegram servers, you don't need to reupload it:
each file object has a file_id field, simply pass this file_id as a parameter instead of uploading.
There are no limits for files sent this way.
2. Provide Telegram with an *fasthttp.URI for the file to be sent. Telegram will download and send the
file. 5 MB max size for photos and 20 MB max for other types of content.
3. Post the file using multipart/form-data in the usual way that files are uploaded via the
browser. Use []byte or io.Reader for this. 10 MB max size for photos, 50 MB for other files.
Sending by FileID
* It is not possible to change the file type when resending by file_id. I.e. a video can't be sent
as a photo, a photo can't be sent as a document, etc.
* It is not possible to resend thumbnails.
* Resending a photo by file_id will send all of its sizes.
* file_id is unique for each individual bot and can't be transferred from one bot to another.
Sending by URL
* When sending by *fasthttp.URI the target file must have the correct MIME type (e.g., audio/mpeg for
sendAudio, etc.).
* In sendDocument, sending by URL will currently only work for gif, pdf and zip files.
* To use SendVoice, the file must have the type audio/ogg and be no more than 1MB in size. 120MB
voice notes will be sent as files.
* Other configurations may work but we can't guarantee that they will.
*/
func (b *Bot) Upload(method, key, name string, file InputFile, args *http.Args) (*Response, error) {
buffer := bytes.NewBuffer(nil)
multi := multipart.NewWriter(buffer)
requestURI := http.AcquireURI()
requestURI.SetScheme("https")
requestURI.SetHost("api.telegram.org")
requestURI.SetPath(path.Join("bot"+b.AccessToken, method))
args.VisitAll(func(key, value []byte) {
_ = multi.WriteField(string(key), string(value))
})
if err := createFileField(multi, file, key, name); err != nil {
return nil, err
}
if err := multi.Close(); err != nil {
return nil, err
}
req := http.AcquireRequest()
defer http.ReleaseRequest(req)
req.SetBody(buffer.Bytes())
req.Header.SetContentType(multi.FormDataContentType())
req.Header.SetMethod("POST")
req.Header.SetRequestURI(requestURI.String())
req.Header.SetUserAgent(path.Join("telegram", Version))
req.Header.SetHostBytes(requestURI.Host())
resp := http.AcquireResponse()
defer http.ReleaseResponse(resp)
if err := http.Do(req, resp); err != nil {
return nil, err
}
var response Response
if err := parser.Unmarshal(resp.Body(), &response); err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Description)
}
return &response, nil
}
func createFileField(w *multipart.Writer, file interface{}, key, val string) (err error) {
switch src := file.(type) {
case string: // Send FileID of file on disk path
return createFileFieldString(w, key, src)
case *http.URI: // Send by URL
return w.WriteField(key, src.String())
case []byte: // Upload new
return createFileFieldRaw(w, key, val, bytes.NewReader(src))
case io.Reader: // Upload new
return createFileFieldRaw(w, key, val, src)
default:
return ErrBadFileType
}
}
func createFileFieldString(w *multipart.Writer, key, src string) (err error) {
_, err = os.Stat(src)
switch {
case os.IsNotExist(err):
err = w.WriteField(key, src)
case os.IsExist(err):
err = uploadFromDisk(w, key, src)
}
return
}
func uploadFromDisk(w *multipart.Writer, key, src string) error {
file, err := os.Open(src)
if err != nil {
return err
}
defer func() {
_ = file.Close()
}()
var formFile io.Writer
formFile, err = w.CreateFormFile(key, file.Name())
if err != nil {
return err
}
_, err = io.Copy(formFile, file)
return err
}
func createFileFieldRaw(w *multipart.Writer, key, value string, src io.Reader) error {
field, err := w.CreateFormFile(key, value)
if err != nil {
return err
}
_, err = io.Copy(field, src)
return err
}
// UploadStickerFile upload a .png file with a sticker for later use in
// createNewStickerSet and addStickerToSet methods (can be used multiple times).
// Returns the uploaded File on success.
func (b *Bot) UploadStickerFile(userID int, pngSticker interface{}) (*File, error) {
args := http.AcquireArgs()
defer http.ReleaseArgs(args)
args.SetUint("user_id", userID)
resp, err := b.Upload(MethodUploadStickerFile, TypeSticker, "sticker", pngSticker, args)
if err != nil {
return nil, err
}
var f File
err = parser.Unmarshal(resp.Result, &f)
return &f, err
}

2702
utils.go

File diff suppressed because it is too large Load Diff

View File

@ -1,195 +0,0 @@
package telegram
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
http "github.com/valyala/fasthttp"
)
const (
testPhrase = "Hello, world!"
testLink = "https://toby3d.me/"
testID = 123456789
// testUsername = "toby3d"
// testChannel = "toby3dRu"
testCode = "<spoiler>hide me</spoiler>"
)
var (
testPhotoSize = PhotoSize{
FileID: "cba",
FileSize: 123,
Width: 128,
Height: 128,
}
testAnimation = Animation{
FileID: "abc",
FileName: "animation",
FileSize: 321,
Thumb: &testPhotoSize,
}
testAudio = Audio{
Performer: "One-Aloner",
Title: "(Not) your reality (Doki Doki Literature Club cover)",
Thumb: &testPhotoSize,
}
)
func TestMarkdown(t *testing.T) {
t.Run("bold", func(t *testing.T) {
assert.Equal(t, "*"+testPhrase+"*", NewMarkdownBold(testPhrase))
})
t.Run("italic", func(t *testing.T) {
assert.Equal(t, "_"+testPhrase+"_", NewMarkdownItalic(testPhrase))
})
t.Run("link", func(t *testing.T) {
link := http.AcquireURI()
defer http.ReleaseURI(link)
link.Update(testLink)
assert.Equal(t, "["+testPhrase+"]("+testLink+")", NewMarkdownURL(testPhrase, link))
})
t.Run("mention", func(t *testing.T) {
assert.Equal(t, "["+testPhrase+"](tg://user/?id="+strconv.Itoa(testID)+")", NewMarkdownMention(testPhrase, testID))
})
t.Run("code", func(t *testing.T) {
assert.Equal(t, "`"+testCode+"`", NewMarkdownCode(testCode))
})
t.Run("code block", func(t *testing.T) {
assert.Equal(t, "```"+testCode+"```", NewMarkdownCodeBlock(testCode))
})
}
func TestHTML(t *testing.T) {
t.Run("bold", func(t *testing.T) {
assert.Equal(t, "<b>"+testPhrase+"</b>", NewHTMLBold(testPhrase))
})
t.Run("italic", func(t *testing.T) {
assert.Equal(t, "<i>"+testPhrase+"</i>", NewHTMLItalic(testPhrase))
})
t.Run("link", func(t *testing.T) {
link := http.AcquireURI()
defer http.ReleaseURI(link)
link.Update(testLink)
assert.Equal(t, `<a href="`+link.String()+`">`+testPhrase+`</a>`, NewHTMLURL(testPhrase, link))
})
t.Run("mention", func(t *testing.T) {
assert.Equal(t, `<a href="tg://user/?id=`+strconv.Itoa(testID)+`">`+testPhrase+`</a>`, NewHTMLMention(testPhrase, testID))
})
t.Run("code", func(t *testing.T) {
assert.Equal(t, "<code>"+testCode+"</code>", NewHTMLCode(testCode))
})
t.Run("code block", func(t *testing.T) {
assert.Equal(t, "<pre>"+testCode+"</pre>", NewHTMLCodeBlock(testCode))
})
}
func TestAnimation(t *testing.T) {
t.Run("has thumb", func(t *testing.T) {
t.Run("false", func(t *testing.T) {
a := new(Animation)
assert.False(t, a.HasThumb())
})
t.Run("true", func(t *testing.T) {
assert.True(t, testAnimation.HasThumb())
})
})
t.Run("file", func(t *testing.T) {
assert.NotNil(t, testAnimation.File())
})
}
func TestAudio(t *testing.T) {
t.Run("has performer", func(t *testing.T) {
t.Run("false", func(t *testing.T) {
a := new(Audio)
assert.False(t, a.HasPerformer())
})
t.Run("true", func(t *testing.T) {
assert.True(t, testAudio.HasPerformer())
})
})
t.Run("has title", func(t *testing.T) {
t.Run("false", func(t *testing.T) {
a := new(Audio)
assert.False(t, a.HasTitle())
})
t.Run("true", func(t *testing.T) {
assert.True(t, testAudio.HasTitle())
})
})
t.Run("has thumb", func(t *testing.T) {
t.Run("false", func(t *testing.T) {
a := new(Audio)
assert.False(t, a.HasThumb())
})
t.Run("true", func(t *testing.T) {
assert.True(t, testAudio.HasThumb())
})
})
t.Run("file", func(t *testing.T) {
assert.NotNil(t, testAudio.File())
})
t.Run("full name", func(t *testing.T) {
for _, tc := range []struct {
message string
audio *Audio
separator string
expResult string
}{{
message: "empty",
expResult: DefaultAudioTitle,
}, {
message: "separator only",
separator: DefaultAudioSeparator,
expResult: DefaultAudioTitle,
}, {
message: "title only",
audio: &Audio{
Title: testAudio.Title,
},
expResult: testAudio.Title,
}, {
message: "performer only",
audio: &Audio{
Performer: testAudio.Performer,
},
expResult: testAudio.Performer + DefaultAudioSeparator + DefaultAudioTitle,
}, {
message: "title & performer",
audio: &Audio{
Performer: testAudio.Performer,
Title: testAudio.Title,
},
expResult: testAudio.Performer + DefaultAudioSeparator + testAudio.Title,
}, {
message: "title & separator",
separator: " | ",
audio: &Audio{
Title: testAudio.Title,
},
expResult: testAudio.Title,
}, {
message: "performer & separator",
separator: " | ",
audio: &Audio{
Performer: testAudio.Performer,
},
expResult: testAudio.Performer + " | " + DefaultAudioTitle,
}, {
message: "performer, title & separator",
separator: " | ",
audio: &Audio{
Performer: testAudio.Performer,
Title: testAudio.Title,
},
expResult: testAudio.Performer + " | " + testAudio.Title,
}} {
tc := tc
t.Run(tc.message, func(t *testing.T) {
assert.Equal(t, tc.expResult, tc.audio.FullName(tc.separator))
})
}
})
}