1
0
Fork 0
telegram/utils.go

2703 lines
62 KiB
Go
Raw Normal View History

package telegram
2019-07-23 13:45:57 +00:00
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt"
"log"
"net"
"path"
"sort"
"strconv"
"strings"
"time"
"github.com/kirillDanshin/dlog"
http "github.com/valyala/fasthttp"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
type (
// Bot represents a bot user with access token getted from @BotFather and
// fasthttp.Client for requests.
Bot struct {
*User
AccessToken string
Client *http.Client
}
// UpdatesChannel is a channel for reading updates of bot.
UpdatesChannel <-chan Update
ShutdownFunc func() error
)
const (
DefaultAudioSeparator = " "
DefaultAudioTitle = "[untitled]"
)
var ErrNotEqual = errors.New("credentials hash and credentials data hash is not equal")
2019-07-26 10:09:49 +00:00
// New creates a new default Bot structure based on the input access token.
func New(accessToken string) (*Bot, error) {
var err error
b := new(Bot)
b.SetClient(&defaultClient)
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
}
2018-04-19 13:02:15 +00:00
// NewForceReply calls the response interface to the message.
func NewForceReply() *ForceReply {
2019-07-26 10:09:49 +00:00
return &ForceReply{
ForceReply: true,
}
}
2018-04-19 13:02:15 +00:00
// NewInlineMentionURL creates a url.URL for the mention user without username.
func NewInlineMentionURL(userID int) *http.URI {
link := http.AcquireURI()
2019-07-23 13:45:57 +00:00
link.Update("tg://user?id=" + strconv.Itoa(userID))
return link
}
2018-06-20 13:40:07 +00:00
func NewMarkdownBold(text string) string {
2018-08-21 11:05:04 +00:00
return "*" + text + "*"
2018-06-20 13:40:07 +00:00
}
func NewMarkdownItalic(text string) string {
2018-08-21 11:05:04 +00:00
return "_" + text + "_"
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func NewMarkdownURL(text string, link fmt.Stringer) string {
2018-08-21 11:05:04 +00:00
return "[" + text + "](" + link.String() + ")"
2018-06-20 13:40:07 +00:00
}
func NewMarkdownMention(text string, id int) string {
return NewMarkdownURL(text, NewInlineMentionURL(id))
2018-06-20 13:40:07 +00:00
}
func NewMarkdownCode(text string) string {
2018-08-21 11:05:04 +00:00
return "`" + text + "`"
2018-06-20 13:40:07 +00:00
}
func NewMarkdownCodeBlock(text string) string {
2018-08-21 11:05:04 +00:00
return "```" + text + "```"
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func NewHTMLBold(text string) string {
2018-08-21 11:05:04 +00:00
return "<b>" + text + "</b>"
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func NewHTMLItalic(text string) string {
2018-08-21 11:05:04 +00:00
return "<i>" + text + "</i>"
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func NewHTMLURL(text string, link fmt.Stringer) string {
2018-08-21 11:05:04 +00:00
return `<a href="` + link.String() + `">` + text + `</a>`
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func NewHTMLMention(text string, id int) string {
return NewHTMLURL(text, NewInlineMentionURL(id))
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func NewHTMLCode(text string) string {
2018-08-21 11:05:04 +00:00
return "<code>" + text + "</code>"
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func NewHTMLCodeBlock(text string) string {
2018-08-21 11:05:04 +00:00
return "<pre>" + text + "</pre>"
2018-06-20 13:40:07 +00:00
}
2019-07-23 13:45:57 +00:00
func (a *Animation) HasThumb() bool {
return a != nil && a.Thumb != nil
}
func (a *Animation) File() *File {
if a == nil {
return nil
}
return &File{
FileID: a.FileID,
FileSize: a.FileSize,
}
}
func (a *Audio) FullName(separator string) (name string) {
if a.HasPerformer() {
if separator == "" {
separator = DefaultAudioSeparator
}
name += a.Performer + separator
}
title := DefaultAudioTitle
if a.HasTitle() {
title = a.Title
}
name += title
return
}
func (a *Audio) HasPerformer() bool {
return a != nil && a.Performer != ""
}
func (a *Audio) HasTitle() bool {
return a != nil && a.Title != ""
}
func (a *Audio) HasThumb() bool {
return a != nil && a.Thumb != nil
}
func (a *Audio) File() *File {
if a == nil {
return nil
}
return &File{
FileID: a.FileID,
FileSize: a.FileSize,
}
}
// IsMessageFromMe checks that the input message is a message from the current
// bot.
func (b *Bot) IsMessageFromMe(m *Message) bool {
return b != nil && b.User != nil &&
m != 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 != nil && 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 == nil || b.User == nil || m == 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 bot.
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 bot.
func (b *Bot) IsMessageToMe(m *Message) bool {
if m == nil || m.Chat == nil {
return false
}
if m.Chat.IsPrivate() || b.IsCommandToMe(m) || b.IsReplyToMe(m) || b.IsMessageMentionsMe(m) {
return true
}
return false
}
// NewFileURL creates a url.URL to file with path getted from GetFile method.
func (b *Bot) NewFileURL(filePath string) *http.URI {
if b == nil || 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 url.URL for redirecting from one chat to another.
func (b *Bot) NewRedirectURL(param string, group bool) *http.URI {
if b == nil || 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
}
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
}
// IsPrivate checks that the current chat is a private chat with single user.
func (c *Chat) IsPrivate() bool {
return c != nil && strings.EqualFold(c.Type, ChatPrivate)
}
// IsGroup checks that the current chat is a group.
func (c *Chat) IsGroup() bool {
return c != nil && strings.EqualFold(c.Type, ChatGroup)
}
// IsSuperGroup checks that the current chat is a supergroup.
func (c *Chat) IsSuperGroup() bool {
return c != nil && strings.EqualFold(c.Type, ChatSuperGroup)
}
// IsChannel checks that the current chat is a channel.
func (c *Chat) IsChannel() bool {
return c != nil && strings.EqualFold(c.Type, ChatChannel)
}
// HasPinnedMessage checks that the current chat has a pinned message.
func (c *Chat) HasPinnedMessage() bool {
return c != nil && c.PinnedMessage != nil
}
// HasStickerSet checks that the current chat has a sticker set.
func (c *Chat) HasStickerSet() bool {
return c != nil && c.StickerSetName != ""
}
// StickerSet return StickerSet structure if StickerSetName is available.
func (c *Chat) StickerSet(bot *Bot) *StickerSet {
if !c.HasStickerSet() || bot == nil {
return nil
}
set, err := bot.GetStickerSet(c.StickerSetName)
if err != nil {
return nil
}
return set
}
// FullName returns the full name of chat or FirstName if LastName is not available.
func (c *Chat) FullName() string {
if c == nil {
return ""
}
if c.HasLastName() {
return c.FirstName + " " + c.LastName
}
return c.FirstName
}
// HaveLastName checks what the current user has a LastName.
func (c *Chat) HasLastName() bool {
return c != nil && c.LastName != ""
}
// HaveUsername checks what the current user has a username.
func (c *Chat) HasUsername() bool {
return c != nil && c.Username != ""
}
func (c *Chat) HasDescription() bool {
return c != nil && c.Description != ""
}
func (c *Chat) HasInviteLink() bool {
return c != nil && c.InviteLink != ""
}
func (cir *ChosenInlineResult) HasLocation() bool {
return cir != nil && cir.Location != nil
}
// FullName returns the full name of contact or FirstName if LastName is not
// available.
func (c *Contact) FullName() string {
if c == nil {
return ""
}
if c.HasLastName() {
return c.FirstName + " " + c.LastName
}
return c.FirstName
}
// HaveLastName checks what the current contact has a LastName.
func (c *Contact) HasLastName() bool {
return c != nil && c.LastName != ""
}
func (c *Contact) HasTelegram() bool {
return c != nil && c.UserID != 0
}
func (c *Contact) HasVCard() bool {
return c != nil && c.VCard != ""
}
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 (d *Document) HasThumb() bool {
return d != nil && d.Thumb != nil
}
func (d *Document) File() *File {
return &File{
FileID: d.FileID,
FileSize: d.FileSize,
}
}
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
2019-07-24 09:34:55 +00:00
err = parser.Unmarshal(data, &c)
2019-07-23 13:45:57 +00:00
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
2019-07-24 09:34:55 +00:00
err = parser.Unmarshal(body, &pd)
2019-07-23 13:45:57 +00:00
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
2019-07-24 09:34:55 +00:00
if err = parser.Unmarshal(body, &idd); err != nil {
2019-07-23 13:45:57 +00:00
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
2019-07-24 09:34:55 +00:00
if err = parser.Unmarshal(body, &idd); err != nil {
2019-07-23 13:45:57 +00:00
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
2019-07-24 09:34:55 +00:00
if err = parser.Unmarshal(body, &idd); err != nil {
2019-07-23 13:45:57 +00:00
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)
}
// ParseURL selects URL from entered text of message and parse it as fasthttp.URI.
func (e *MessageEntity) ParseURL(messageText string) *http.URI {
if e == nil || !e.IsURL() || messageText == "" {
return nil
}
from := e.Offset
to := from + e.Length
text := []rune(messageText)
if len(text) < to {
return nil
}
link := http.AcquireURI()
link.Update(string(text[from:to]))
return link
}
// IsBold checks that the current entity is a bold tag.
func (e *MessageEntity) IsBold() bool {
return e != nil && strings.EqualFold(e.Type, EntityBold)
}
// IsBotCommand checks that the current entity is a bot command.
func (e *MessageEntity) IsBotCommand() bool {
return e != nil && strings.EqualFold(e.Type, EntityBotCommand)
}
// IsCode checks that the current entity is a code tag.
func (e *MessageEntity) IsCode() bool {
return e != nil && strings.EqualFold(e.Type, EntityCode)
}
// IsEmail checks that the current entity is a email.
func (e *MessageEntity) IsEmail() bool {
return e != nil && strings.EqualFold(e.Type, EntityEmail)
}
// IsHashtag checks that the current entity is a hashtag.
func (e *MessageEntity) IsHashtag() bool {
return e != nil && strings.EqualFold(e.Type, EntityHashtag)
}
// IsItalic checks that the current entity is a italic tag.
func (e *MessageEntity) IsItalic() bool {
return e != nil && strings.EqualFold(e.Type, EntityItalic)
}
// IsMention checks that the current entity is a username mention.
func (e *MessageEntity) IsMention() bool {
return e != nil && strings.EqualFold(e.Type, EntityMention)
}
// IsPre checks that the current entity is a pre tag.
func (e *MessageEntity) IsPre() bool {
return e != nil && strings.EqualFold(e.Type, EntityPre)
}
// IsTextLink checks that the current entity is a text link.
func (e *MessageEntity) IsTextLink() bool {
return e != nil && strings.EqualFold(e.Type, EntityTextLink)
}
// IsTextMention checks that the current entity is a mention without username.
func (e *MessageEntity) IsTextMention() bool {
return e != nil && strings.EqualFold(e.Type, EntityTextMention)
}
// IsURL checks that the current entity is a URL
func (e *MessageEntity) IsURL() bool {
return e != nil && strings.EqualFold(e.Type, EntityURL)
}
// TextLink parse current text link entity as fasthttp.URI.
func (e *MessageEntity) TextLink() *http.URI {
if e == nil {
return nil
}
link := http.AcquireURI()
link.Update(e.URL)
return link
}
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
}
// NewInlineKeyboardMarkup creates a new inline keyboard markup for message.
func NewInlineKeyboardMarkup(rows ...[]InlineKeyboardButton) *InlineKeyboardMarkup {
var keyboard [][]InlineKeyboardButton
keyboard = append(keyboard, rows...)
return &InlineKeyboardMarkup{
InlineKeyboard: keyboard,
}
}
// NewInlineKeyboardRow creates a new inline keyboard row for buttons.
func NewInlineKeyboardRow(buttons ...InlineKeyboardButton) []InlineKeyboardButton {
var row []InlineKeyboardButton
row = append(row, buttons...)
return row
}
// NewInlineKeyboardButton creates a new inline keyboard callback button.
func NewInlineKeyboardButton(text, data string) InlineKeyboardButton {
return InlineKeyboardButton{
Text: text,
CallbackData: data,
}
}
// NewInlineKeyboardButtonURL creates a new inline keyboard button with URL.
func NewInlineKeyboardButtonURL(text, url string) InlineKeyboardButton {
return InlineKeyboardButton{
Text: text,
URL: url,
}
}
// NewInlineKeyboardButtonSwitch creates a new inline keyboard button to make
// specific inline query in other chat.
func NewInlineKeyboardButtonSwitch(text, query string) InlineKeyboardButton {
return InlineKeyboardButton{
Text: text,
SwitchInlineQuery: query,
}
}
// NewInlineKeyboardButtonSwitchSelf creates a new inline keyboard button to make
// specific inline query in same chat.
func NewInlineKeyboardButtonSwitchSelf(text, query string) InlineKeyboardButton {
return InlineKeyboardButton{
Text: text,
SwitchInlineQueryCurrentChat: query,
}
}
// NewInlineKeyboardButtonGame creates a new inline keyboard button with game
// callback.
func NewInlineKeyboardButtonGame(text string) InlineKeyboardButton {
var game CallbackGame
return InlineKeyboardButton{
Text: text,
CallbackGame: &game,
}
}
// NewInlineKeyboardButtonPay creates a new inline keyboard button with pay
// callback.
func NewInlineKeyboardButtonPay(text string) InlineKeyboardButton {
return InlineKeyboardButton{
Text: text,
Pay: true,
}
}
// HasLocation checks what current InlineQuery has Location info.
func (iq *InlineQuery) HasLocation() bool {
return iq != nil && iq.Location != nil
}
// HasOffset checks what current InlineQuery has Offset.
func (iq *InlineQuery) HasOffset() bool {
return iq != nil && iq.Offset != ""
}
// HasQuery checks what current InlineQuery has Query string.
func (iq *InlineQuery) HasQuery() bool {
return iq != nil && iq.Query != ""
}
// NewInlineQueryResultCachedAudio creates a new inline query result with cached
// audio.
func NewInlineQueryResultCachedAudio(resultID, fileID string) *InlineQueryResultCachedAudio {
return &InlineQueryResultCachedAudio{
Type: TypeAudio,
ID: resultID,
AudioFileID: fileID,
}
}
// NewInlineQueryResultCachedDocument creates a new inline query result with
// cached document.
func NewInlineQueryResultCachedDocument(resultID, fileID, title string) *InlineQueryResultCachedDocument {
return &InlineQueryResultCachedDocument{
Type: TypeDocument,
ID: resultID,
Title: title,
DocumentFileID: fileID,
}
}
// NewInlineQueryResultCachedGif creates a new inline query result with cached
// GIF.
func NewInlineQueryResultCachedGif(resultID, fileID string) *InlineQueryResultCachedGif {
return &InlineQueryResultCachedGif{
Type: TypeGIF,
ID: resultID,
GifFileID: fileID,
}
}
// NewInlineQueryResultCachedMpeg4Gif creates a new inline query result with
// cached MPEG GIF.
func NewInlineQueryResultCachedMpeg4Gif(resultID, fileID string) *InlineQueryResultCachedMpeg4Gif {
return &InlineQueryResultCachedMpeg4Gif{
Type: TypeMpeg4Gif,
ID: resultID,
Mpeg4FileID: fileID,
}
}
// NewInlineQueryResultCachedPhoto creates a new inline query result with cached
// photo.
func NewInlineQueryResultCachedPhoto(resultID, fileID string) *InlineQueryResultCachedPhoto {
return &InlineQueryResultCachedPhoto{
Type: TypePhoto,
ID: resultID,
PhotoFileID: fileID,
}
}
// NewInlineQueryResultCachedSticker creates a new inline query result with
// cached sticker.
func NewInlineQueryResultCachedSticker(resultID, fileID string) *InlineQueryResultCachedSticker {
return &InlineQueryResultCachedSticker{
Type: TypeSticker,
ID: resultID,
StickerFileID: fileID,
}
}
// NewInlineQueryResultCachedVideo creates a new inline query result with cached
// video.
func NewInlineQueryResultCachedVideo(resultID, fileID, title string) *InlineQueryResultCachedVideo {
return &InlineQueryResultCachedVideo{
Type: TypeVideo,
ID: resultID,
VideoFileID: fileID,
Title: title,
}
}
// NewInlineQueryResultCachedVoice creates a new inline query result with cached
// voice.
func NewInlineQueryResultCachedVoice(resultID, fileID, title string) *InlineQueryResultCachedVoice {
return &InlineQueryResultCachedVoice{
Type: TypeVoice,
ID: resultID,
VoiceFileID: fileID,
Title: title,
}
}
// NewInlineQueryResultArticle creates a new inline query result with article.
func NewInlineQueryResultArticle(resultID, title string, content interface{}) *InlineQueryResultArticle {
return &InlineQueryResultArticle{
Type: TypeArticle,
ID: resultID,
Title: title,
InputMessageContent: content,
}
}
// NewInlineQueryResultAudio creates a new inline query result with audio.
func NewInlineQueryResultAudio(resultID, audioURL, title string) *InlineQueryResultAudio {
return &InlineQueryResultAudio{
Type: TypeAudio,
ID: resultID,
AudioURL: audioURL,
Title: title,
}
}
// NewInlineQueryResultContact creates a new inline query result with contact.
func NewInlineQueryResultContact(resultID, phoneNumber, firstName string) *InlineQueryResultContact {
return &InlineQueryResultContact{
Type: TypeContact,
ID: resultID,
PhoneNumber: phoneNumber,
FirstName: firstName,
}
}
// NewInlineQueryResultGame creates a new inline query result with game.
func NewInlineQueryResultGame(resultID, gameShortName string) *InlineQueryResultGame {
return &InlineQueryResultGame{
Type: TypeGame,
ID: resultID,
GameShortName: gameShortName,
}
}
// NewInlineQueryResultDocument creates a new inline query result with document.
func NewInlineQueryResultDocument(resultID, title, documentURL, mimeType string) *InlineQueryResultDocument {
return &InlineQueryResultDocument{
Type: TypeDocument,
ID: resultID,
Title: title,
DocumentURL: documentURL,
MimeType: mimeType,
}
}
// NewInlineQueryResultGif creates a new inline query result with GIF.
func NewInlineQueryResultGif(resultID, gifURL, thumbURL string) *InlineQueryResultGif {
return &InlineQueryResultGif{
Type: TypeGIF,
ID: resultID,
GifURL: gifURL,
ThumbURL: thumbURL,
}
}
// NewInlineQueryResultLocation creates a new inline query result with location.
func NewInlineQueryResultLocation(resultID, title string, latitude, longitude float32) *InlineQueryResultLocation {
return &InlineQueryResultLocation{
Type: TypeLocation,
ID: resultID,
Latitude: latitude,
Longitude: longitude,
Title: title,
}
}
// NewInlineQueryResultMpeg4Gif creates a new inline query result with MPEG GIF.
func NewInlineQueryResultMpeg4Gif(resultID, mpeg4URL, thumbURL string) *InlineQueryResultMpeg4Gif {
return &InlineQueryResultMpeg4Gif{
Type: TypeMpeg4Gif,
ID: resultID,
Mpeg4URL: mpeg4URL,
ThumbURL: thumbURL,
}
}
// NewInlineQueryResultPhoto creates a new inline query result with photo.
func NewInlineQueryResultPhoto(resultID, photoURL, thumbURL string) *InlineQueryResultPhoto {
return &InlineQueryResultPhoto{
Type: TypePhoto,
ID: resultID,
PhotoURL: photoURL,
ThumbURL: thumbURL,
}
}
// NewInlineQueryResultVenue creates a new inline query result with venue.
func NewInlineQueryResultVenue(resultID, title, address string, latitude, longitude float32) *InlineQueryResultVenue {
return &InlineQueryResultVenue{
Type: TypeVenue,
ID: resultID,
Latitude: latitude,
Longitude: longitude,
Title: title,
Address: address,
}
}
// NewInlineQueryResultVideo creates a new inline query result with video.
func NewInlineQueryResultVideo(resultID, videoURL, mimeType, thumbURL, title string) *InlineQueryResultVideo {
return &InlineQueryResultVideo{
Type: TypeVideo,
ID: resultID,
VideoURL: videoURL,
MimeType: mimeType,
ThumbURL: thumbURL,
Title: title,
}
}
// NewInlineQueryResultVoice creates a new inline query result with voice.
func NewInlineQueryResultVoice(resultID, voiceURL, title string) *InlineQueryResultVoice {
return &InlineQueryResultVoice{
Type: TypeVoice,
ID: resultID,
VoiceURL: voiceURL,
Title: title,
}
}
func (iqra *InlineQueryResultArticle) ResultID() string {
return iqra.ID
}
func (iqra *InlineQueryResultArticle) ResultType() string {
return iqra.Type
}
func (iqra *InlineQueryResultArticle) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqra.ReplyMarkup
}
func (iqrp *InlineQueryResultPhoto) ResultID() string {
return iqrp.ID
}
func (iqrp *InlineQueryResultPhoto) ResultType() string {
return iqrp.Type
}
func (iqrp *InlineQueryResultPhoto) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrp.ReplyMarkup
}
func (iqrg *InlineQueryResultGif) ResultID() string {
return iqrg.ID
}
func (iqrg *InlineQueryResultGif) ResultType() string {
return iqrg.Type
}
func (iqrg *InlineQueryResultGif) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrg.ReplyMarkup
}
func (iqrm4g *InlineQueryResultMpeg4Gif) ResultID() string {
return iqrm4g.ID
}
func (iqrm4g *InlineQueryResultMpeg4Gif) ResultType() string {
return iqrm4g.Type
}
func (iqrm4g *InlineQueryResultMpeg4Gif) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrm4g.ReplyMarkup
}
func (iqrv *InlineQueryResultVideo) ResultID() string {
return iqrv.ID
}
func (iqrv *InlineQueryResultVideo) ResultType() string {
return iqrv.Type
}
func (iqrv *InlineQueryResultVideo) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrv.ReplyMarkup
}
func (iqra *InlineQueryResultAudio) ResultID() string {
return iqra.ID
}
func (iqra *InlineQueryResultAudio) ResultType() string {
return iqra.Type
}
func (iqra *InlineQueryResultAudio) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqra.ReplyMarkup
}
func (iqrv *InlineQueryResultVoice) ResultID() string {
return iqrv.ID
}
func (iqrv *InlineQueryResultVoice) ResultType() string {
return iqrv.Type
}
func (iqrv *InlineQueryResultVoice) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrv.ReplyMarkup
}
func (iqrd *InlineQueryResultDocument) ResultID() string {
return iqrd.ID
}
func (iqrd *InlineQueryResultDocument) ResultType() string {
return iqrd.Type
}
func (iqrd *InlineQueryResultDocument) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrd.ReplyMarkup
}
func (iqrl *InlineQueryResultLocation) ResultID() string {
return iqrl.ID
}
func (iqrl *InlineQueryResultLocation) ResultType() string {
return iqrl.Type
}
func (iqrl *InlineQueryResultLocation) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrl.ReplyMarkup
}
func (iqrv *InlineQueryResultVenue) ResultID() string {
return iqrv.ID
}
func (iqrv *InlineQueryResultVenue) ResultType() string {
return iqrv.Type
}
func (iqrv *InlineQueryResultVenue) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrv.ReplyMarkup
}
func (iqrc *InlineQueryResultContact) ResultID() string {
return iqrc.ID
}
func (iqrc *InlineQueryResultContact) ResultType() string {
return iqrc.Type
}
func (iqrc *InlineQueryResultContact) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrc.ReplyMarkup
}
func (iqrg *InlineQueryResultGame) ResultID() string {
return iqrg.ID
}
func (iqrg *InlineQueryResultGame) ResultType() string {
return iqrg.Type
}
func (iqrg *InlineQueryResultGame) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrg.ReplyMarkup
}
func (iqrcp *InlineQueryResultCachedPhoto) ResultID() string {
return iqrcp.ID
}
func (iqrcp *InlineQueryResultCachedPhoto) ResultType() string {
return iqrcp.Type
}
func (iqrcp *InlineQueryResultCachedPhoto) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrcp.ReplyMarkup
}
func (iqrcg *InlineQueryResultCachedGif) ResultID() string {
return iqrcg.ID
}
func (iqrcg *InlineQueryResultCachedGif) ResultType() string {
return iqrcg.Type
}
func (iqrcg *InlineQueryResultCachedGif) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrcg.ReplyMarkup
}
func (iqrcm4g *InlineQueryResultCachedMpeg4Gif) ResultID() string {
return iqrcm4g.ID
}
func (iqrcm4g *InlineQueryResultCachedMpeg4Gif) ResultType() string {
return iqrcm4g.Type
}
func (iqrcm4g *InlineQueryResultCachedMpeg4Gif) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrcm4g.ReplyMarkup
}
func (iqrcs *InlineQueryResultCachedSticker) ResultID() string {
return iqrcs.ID
}
func (iqrcs *InlineQueryResultCachedSticker) ResultType() string {
return iqrcs.Type
}
func (iqrcs *InlineQueryResultCachedSticker) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrcs.ReplyMarkup
}
func (iqrcd *InlineQueryResultCachedDocument) ResultID() string {
return iqrcd.ID
}
func (iqrcd *InlineQueryResultCachedDocument) ResultType() string {
return iqrcd.Type
}
func (iqrcd *InlineQueryResultCachedDocument) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrcd.ReplyMarkup
}
func (iqrcv *InlineQueryResultCachedVideo) ResultID() string {
return iqrcv.ID
}
func (iqrcv *InlineQueryResultCachedVideo) ResultType() string {
return iqrcv.Type
}
func (iqrcv *InlineQueryResultCachedVideo) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrcv.ReplyMarkup
}
func (iqrcv *InlineQueryResultCachedVoice) ResultID() string {
return iqrcv.ID
}
func (iqrcv *InlineQueryResultCachedVoice) ResultType() string {
return iqrcv.Type
}
func (iqrcv *InlineQueryResultCachedVoice) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrcv.ReplyMarkup
}
func (iqrca *InlineQueryResultCachedAudio) ResultID() string {
return iqrca.ID
}
func (iqrca *InlineQueryResultCachedAudio) ResultType() string {
return iqrca.Type
}
func (iqrca *InlineQueryResultCachedAudio) ResultReplyMarkup() *InlineKeyboardMarkup {
return iqrca.ReplyMarkup
}
// NewInputTextMessageContent creates a new text of message.
func NewInputTextMessageContent(messageText string) *InputTextMessageContent {
return &InputTextMessageContent{
MessageText: messageText,
}
}
// NewInputLocationMessageContent creates a new location.
func NewInputLocationMessageContent(latitude, longitude float32) *InputLocationMessageContent {
return &InputLocationMessageContent{
Latitude: latitude,
Longitude: longitude,
}
}
// NewInputVenueMessageContent creates a new venue.
func NewInputVenueMessageContent(latitude, longitude float32, title, address string) *InputVenueMessageContent {
return &InputVenueMessageContent{
Latitude: latitude,
Longitude: longitude,
Title: title,
Address: address,
}
}
// NewInputContactMessageContent creates a new contact.
func NewInputContactMessageContent(phoneNumber, firstName string) *InputContactMessageContent {
return &InputContactMessageContent{
PhoneNumber: phoneNumber,
FirstName: firstName,
}
}
// NewInputMediaPhoto creates a new photo in media album.
func NewInputMediaPhoto(media string) *InputMediaPhoto {
return &InputMediaPhoto{
Type: TypePhoto,
Media: media,
}
}
// NewInputMediaVideo creates a new video in media album.
func NewInputMediaVideo(media string) *InputMediaVideo {
return &InputMediaVideo{
Type: TypeVideo,
Media: media,
}
}
func (ima *InputMediaAnimation) File() string {
if ima == nil {
return ""
}
return ima.Media
}
func (ima *InputMediaAnimation) InputMediaCaption() string {
if ima == nil {
return ""
}
return ima.Caption
}
func (ima *InputMediaAnimation) InputMediaParseMode() string {
if ima == nil {
return ""
}
return ima.ParseMode
}
func (ima *InputMediaAnimation) InputMediaType() string {
if ima == nil {
return ""
}
return ima.Type
}
func (imd *InputMediaDocument) File() string {
if imd == nil {
return ""
}
return imd.Media
}
func (imd *InputMediaDocument) InputMediaCaption() string {
if imd == nil {
return ""
}
return imd.Caption
}
func (imd *InputMediaDocument) InputMediaParseMode() string {
if imd == nil {
return ""
}
return imd.ParseMode
}
func (imd *InputMediaDocument) InputMediaType() string {
if imd == nil {
return ""
}
return imd.Type
}
func (ima *InputMediaAudio) File() string {
if ima == nil {
return ""
}
return ima.Media
}
func (ima *InputMediaAudio) InputMediaCaption() string {
if ima == nil {
return ""
}
return ima.Caption
}
func (ima *InputMediaAudio) InputMediaParseMode() string {
if ima == nil {
return ""
}
return ima.ParseMode
}
func (ima *InputMediaAudio) InputMediaType() string {
if ima == nil {
return ""
}
return ima.Type
}
func (imp *InputMediaPhoto) File() string {
if imp == nil {
return ""
}
return imp.Media
}
func (imp *InputMediaPhoto) InputMediaCaption() string {
if imp == nil {
return ""
}
return imp.Caption
}
func (imp *InputMediaPhoto) InputMediaParseMode() string {
if imp == nil {
return ""
}
return imp.ParseMode
}
func (imp *InputMediaPhoto) InputMediaType() string {
if imp == nil {
return ""
}
return imp.Type
}
func (imv *InputMediaVideo) File() string {
if imv == nil {
return ""
}
return imv.Media
}
func (imv *InputMediaVideo) InputMediaCaption() string {
if imv == nil {
return ""
}
return imv.Caption
}
func (imv *InputMediaVideo) InputMediaParseMode() string {
if imv == nil {
return ""
}
return imv.ParseMode
}
func (imv *InputMediaVideo) InputMediaType() string {
if imv == nil {
return ""
}
return imv.Type
}
func (itmc *InputTextMessageContent) IsInputMessageContent() bool { return true }
func (ilmc *InputLocationMessageContent) IsInputMessageContent() bool { return true }
func (ivmc *InputVenueMessageContent) IsInputMessageContent() bool { return true }
func (icmc *InputContactMessageContent) IsInputMessageContent() bool { return true }
// NewReplyKeyboardRemove just hides keyboard.
func NewReplyKeyboardRemove(selective bool) *ReplyKeyboardRemove {
return &ReplyKeyboardRemove{
RemoveKeyboard: true,
Selective: selective,
}
}
// NewReplyKeyboardMarkup creates new keyboard markup of simple buttons.
func NewReplyKeyboardMarkup(rows ...[]KeyboardButton) *ReplyKeyboardMarkup {
var keyboard [][]KeyboardButton
keyboard = append(keyboard, rows...)
return &ReplyKeyboardMarkup{Keyboard: keyboard}
}
// NewReplyKeyboardRow creates new keyboard row for buttons.
func NewReplyKeyboardRow(buttons ...KeyboardButton) []KeyboardButton {
var row []KeyboardButton
row = append(row, buttons...)
return row
}
// NewReplyKeyboardButton creates new button with custom text for sending it.
func NewReplyKeyboardButton(text string) KeyboardButton {
return KeyboardButton{
Text: text,
}
}
// NewReplyKeyboardButtonContact creates new button with custom text for sending
// user contact.
func NewReplyKeyboardButtonContact(text string) KeyboardButton {
return KeyboardButton{
Text: text,
RequestContact: true,
}
}
// NewReplyKeyboardButtonLocation creates new button with custom text for sending
// user location.
func NewReplyKeyboardButtonLocation(text string) KeyboardButton {
return KeyboardButton{
Text: text,
RequestLocation: true,
}
}
// IsCreator checks that current member is creator.
func (m *ChatMember) IsCreator() bool {
return m != nil && strings.EqualFold(m.Status, StatusCreator)
}
// IsAdministrator checks that current member is administrator.
func (m *ChatMember) IsAdministrator() bool {
return m != nil && strings.EqualFold(m.Status, StatusAdministrator)
}
// IsRestricted checks that current member has been restricted.
func (m *ChatMember) IsRestricted() bool {
return m != nil && strings.EqualFold(m.Status, StatusRestricted)
}
// IsLeft checks that current member has left the chat.
func (m *ChatMember) IsLeft() bool {
return m != nil && strings.EqualFold(m.Status, StatusLeft)
}
// IsKicked checks that current member has been kicked.
func (m *ChatMember) IsKicked() bool {
return m != nil && strings.EqualFold(m.Status, StatusKicked)
}
// UntilTime parse UntilDate of restrictions and returns time.Time.
func (m *ChatMember) UntilTime() *time.Time {
if m == nil {
return nil
}
ut := time.Unix(m.UntilDate, 0)
return &ut
}
// IsCommand checks that the current message is a bot command.
func (m *Message) IsCommand() bool {
if !m.IsText() || !m.HasEntities() {
return false
}
entity := m.Entities[0]
return entity.IsBotCommand() && entity.Offset == 0
}
// IsCommandEqual checks that the current message is a specific bot command.
func (m *Message) IsCommandEqual(command string) bool {
return m.IsCommand() && strings.EqualFold(m.Command(), command)
}
// Command returns identifier of the bot command without bot username, if it was
// available
func (m *Message) Command() string {
if !m.IsCommand() {
return ""
}
return strings.Split(m.RawCommand(), "@")[0]
}
// RawCommand returns identifier of the bot command with bot username, if it was
// available
func (m *Message) RawCommand() string {
if !m.IsCommand() {
return ""
}
return string([]rune(m.Text)[1:m.Entities[0].Length])
}
// HasCommandArgument checks that the current command message contains argument.
func (m *Message) HasCommandArgument() bool {
if !m.IsCommand() {
return false
}
entity := m.Entities[0]
if !entity.IsBotCommand() {
return false
}
return len([]rune(m.Text)) != entity.Length
}
// CommandArgument returns raw command argument.
func (m *Message) CommandArgument() string {
if !m.HasCommandArgument() {
return ""
}
return string([]rune(m.Text)[m.Entities[0].Length+1:])
}
// IsReply checks that the current message is a reply on other message.
func (m *Message) IsReply() bool {
return m != nil && m.ReplyToMessage != nil
}
// IsForward checks that the current message is a forward of other message.
func (m *Message) IsForward() bool {
return m != nil && m.ForwardFrom != nil
}
// Time parse current message Date and returns time.Time.
func (m *Message) Time() *time.Time {
if m == nil {
return nil
}
t := time.Unix(m.Date, 0)
return &t
}
// ForwardTime parse current message ForwardDate and returns time.Time.
func (m *Message) ForwardTime() *time.Time {
if m == nil {
return nil
}
ft := time.Unix(m.ForwardDate, 0)
return &ft
}
// EditTime parse current message EditDate and returns time.Time.
func (m *Message) EditTime() *time.Time {
if m == nil || !m.HasBeenEdited() {
return nil
}
et := time.Unix(m.EditDate, 0)
return &et
}
// HasBeenEdited checks that the current message has been edited.
func (m *Message) HasBeenEdited() bool {
return m != nil && m.EditDate > 0
}
// IsText checks that the current message is just a text message.
func (m *Message) IsText() bool {
return m != nil && m.Text != ""
}
// IsAudio checks that the current message is a audio.
func (m *Message) IsAudio() bool {
return m != nil && m.Audio != nil
}
// IsDocument checks that the current message is a document.
func (m *Message) IsDocument() bool {
return m != nil && m.Document != nil
}
// IsGame checks that the current message is a game.
func (m *Message) IsGame() bool {
return m != nil && m.Game != nil
}
// IsPhoto checks that the current message is a photo.
func (m *Message) IsPhoto() bool {
return m != nil && len(m.Photo) > 0
}
// IsSticker checks that the current message is a sticker.
func (m *Message) IsSticker() bool {
return m != nil && m.Sticker != nil
}
// IsVideo checks that the current message is a video.
func (m *Message) IsVideo() bool {
return m != nil && m.Video != nil
}
// IsVoice checks that the current message is a voice.
func (m *Message) IsVoice() bool {
return m != nil && m.Voice != nil
}
// IsVideoNote checks that the current message is a video note.
func (m *Message) IsVideoNote() bool {
return m != nil && m.VideoNote != nil
}
// IsContact checks that the current message is a contact.
func (m *Message) IsContact() bool {
return m != nil && m.Contact != nil
}
// IsLocation checks that the current message is a location.
func (m *Message) IsLocation() bool {
return m != nil && m.Location != nil
}
// IsVenue checks that the current message is a venue.
func (m *Message) IsVenue() bool {
return m != nil && m.Venue != nil
}
// IsAnimation checks that the current message is a animation.
func (m *Message) IsAnimation() bool {
return m != nil && m.Animation != nil
}
// IsNewChatMembersEvent checks that the current message is a event of entry of
// new members.
func (m *Message) IsNewChatMembersEvent() bool {
return m != nil && len(m.NewChatMembers) > 0
}
// IsLeftChatMemberEvent checks that the current message is a event of members
// exit.
func (m *Message) IsLeftChatMemberEvent() bool {
return m != nil && m.LeftChatMember != nil
}
// IsNewChatTitleEvent checks that the current message is a event of setting a
// new chat title.
func (m *Message) IsNewChatTitleEvent() bool {
return m != nil && !strings.EqualFold(m.NewChatTitle, "")
}
// IsNewChatPhotoEvent checks that the current message is a event of setting a
// new chat avatar.
func (m *Message) IsNewChatPhotoEvent() bool {
return m != nil && len(m.NewChatPhoto) > 0
}
// IsDeleteChatPhotoEvent checks that the current message is a event of deleting
// a chat avatar.
func (m *Message) IsDeleteChatPhotoEvent() bool {
return m != nil && m.DeleteChatPhoto
}
// IsGroupChatCreatedEvent checks that the current message is a event of creating
// a new group.
func (m *Message) IsGroupChatCreatedEvent() bool {
return m != nil && m.GroupChatCreated
}
// IsSupergroupChatCreatedEvent checks that the current message is a event of
// creating a new supergroup.
func (m *Message) IsSupergroupChatCreatedEvent() bool {
return m != nil && m.SupergroupChatCreated
}
// IsChannelChatCreatedEvent checks that the current message is a event of
// creating a new channel.
func (m *Message) IsChannelChatCreatedEvent() bool {
return m != nil && m.ChannelChatCreated
}
// IsPinnedMessage checks that the current message is a event of pinning another
// message.
func (m *Message) IsPinnedMessage() bool {
return m != nil && m.PinnedMessage != nil
}
// IsInvoice checks that the current message is a invoice.
func (m *Message) IsInvoice() bool {
return m != nil && m.Invoice != nil
}
// IsSuccessfulPayment checks that the current message is a event of successful
// payment.
func (m *Message) IsSuccessfulPayment() bool {
return m != nil && m.SuccessfulPayment != nil
}
2019-08-09 07:23:21 +00:00
// IsPoll checks that the current message is a poll.
func (m *Message) IsPoll() bool {
return m != nil && m.Poll != nil
}
2019-07-23 13:45:57 +00:00
// HasEntities checks that the current message contains entities.
func (m *Message) HasEntities() bool {
return m != nil && len(m.Entities) > 0
}
// HasCaptionEntities checks that the current media contains entities in caption.
func (m *Message) HasCaptionEntities() bool {
return m != nil && len(m.CaptionEntities) > 0
}
// HasMentions checks that the current message contains mentions.
func (m *Message) HasMentions() bool {
if !m.HasEntities() {
return false
}
for _, entity := range m.Entities {
if entity.IsMention() || entity.IsTextMention() {
return true
}
}
return false
}
// HasCaptionMentions checks that the current media contains mentions in caption.
func (m *Message) HasCaptionMentions() bool {
if !m.HasCaptionEntities() {
return false
}
for _, entity := range m.CaptionEntities {
if entity.IsMention() || entity.IsTextMention() {
return true
}
}
return false
}
// HasCaption checks that the current media has caption.
func (m *Message) HasCaption() bool {
return m != nil && m.Caption != ""
}
// HasAuthorSignature checks that the current channel post has author signature.
func (m *Message) HasAuthorSignature() bool {
return m != nil && m.AuthorSignature != ""
}
// IsEvent checks what current message is a any chat event.
func (m *Message) IsEvent() bool {
return m.IsChannelChatCreatedEvent() ||
m.IsDeleteChatPhotoEvent() ||
m.IsGroupChatCreatedEvent() ||
m.IsLeftChatMemberEvent() ||
m.IsNewChatMembersEvent() ||
m.IsNewChatTitleEvent() ||
m.IsSupergroupChatCreatedEvent() ||
m.IsNewChatPhotoEvent()
}
func sortPhotos(ps []PhotoSize, reverse bool) []PhotoSize {
buf := make([]PhotoSize, len(ps))
copy(buf, ps)
sort.Slice(buf, func(i, j int) bool {
if reverse {
return buf[i].Width > buf[j].Width &&
buf[i].Height > buf[j].Height
}
return buf[i].Width < buf[j].Width &&
buf[i].Height < buf[j].Height
})
return buf
}
func (m *Message) BigPhoto() *PhotoSize {
if m == nil || !m.IsPhoto() {
return nil
}
if len(m.Photo) == 1 {
return &m.Photo[0]
}
sp := sortPhotos(m.Photo, true)
return &sp[0]
}
func (m *Message) SmallPhoto() *PhotoSize {
if m == nil || !m.IsPhoto() {
return nil
}
if len(m.Photo) == 1 {
return &m.Photo[0]
}
sp := sortPhotos(m.Photo, false)
return &sp[0]
}
func (m *Message) BigChatPhoto() *PhotoSize {
if m == nil || !m.IsNewChatPhotoEvent() {
return nil
}
if len(m.NewChatPhoto) == 1 {
return &m.NewChatPhoto[0]
}
sp := sortPhotos(m.NewChatPhoto, true)
return &sp[0]
}
func (m *Message) SmallChatPhoto() *PhotoSize {
if m == nil || !m.IsNewChatPhotoEvent() {
return nil
}
if len(m.NewChatPhoto) == 1 {
return &m.NewChatPhoto[0]
}
sp := sortPhotos(m.NewChatPhoto, false)
return &sp[0]
}
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
secret, err = decryptSecret(pk, secret)
if 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)
if err != nil {
return nil, err
}
// Decrypt the credentials data (data field in EncryptedCredentials) by
// AES256-CBC using these credentials_key and credentials_iv.
data, err = decryptData(key, iv, data)
if 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.
offset := int(data[0])
return data[offset:], 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)
}
func decryptSecretHash(s, h []byte) (key, iv []byte) {
hash := sha512.New()
var err error
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 (peedf *PassportElementErrorDataField) PassportElementErrorMessage() string {
if peedf == nil {
return ""
}
return peedf.Message
}
func (peedf *PassportElementErrorDataField) PassportElementErrorSource() string {
if peedf == nil {
return ""
}
return peedf.Source
}
func (peedf *PassportElementErrorDataField) PassportElementErrorType() string {
if peedf == nil {
return ""
}
return peedf.Type
}
func (peeff *PassportElementErrorFrontSide) PassportElementErrorMessage() string {
if peeff == nil {
return ""
}
return peeff.Message
}
func (peeff *PassportElementErrorFrontSide) PassportElementErrorSource() string {
if peeff == nil {
return ""
}
return peeff.Source
}
func (peeff *PassportElementErrorFrontSide) PassportElementErrorType() string {
if peeff == nil {
return ""
}
return peeff.Type
}
func (peerf *PassportElementErrorReverseSide) PassportElementErrorMessage() string {
if peerf == nil {
return ""
}
return peerf.Message
}
func (peerf *PassportElementErrorReverseSide) PassportElementErrorSource() string {
if peerf == nil {
return ""
}
return peerf.Source
}
func (peerf *PassportElementErrorReverseSide) PassportElementErrorType() string {
if peerf == nil {
return ""
}
return peerf.Type
}
func (pees *PassportElementErrorSelfie) PassportElementErrorMessage() string {
if pees == nil {
return ""
}
return pees.Message
}
func (pees *PassportElementErrorSelfie) PassportElementErrorSource() string {
if pees == nil {
return ""
}
return pees.Source
}
func (pees *PassportElementErrorSelfie) PassportElementErrorType() string {
if pees == nil {
return ""
}
return pees.Type
}
func (peef *PassportElementErrorFile) PassportElementErrorMessage() string {
if peef == nil {
return ""
}
return peef.Message
}
func (peef *PassportElementErrorFile) PassportElementErrorSource() string {
if peef == nil {
return ""
}
return peef.Source
}
func (peef *PassportElementErrorFile) PassportElementErrorType() string {
if peef == nil {
return ""
}
return peef.Type
}
func (peef *PassportElementErrorFiles) PassportElementErrorMessage() string {
if peef == nil {
return ""
}
return peef.Message
}
func (peef *PassportElementErrorFiles) PassportElementErrorSource() string {
if peef == nil {
return ""
}
return peef.Source
}
func (peef *PassportElementErrorFiles) PassportElementErrorType() string {
if peef == nil {
return ""
}
return peef.Type
}
func (peetf *PassportElementErrorTranslationFile) PassportElementErrorMessage() string {
if peetf == nil {
return ""
}
return peetf.Message
}
func (peetf *PassportElementErrorTranslationFile) PassportElementErrorSource() string {
if peetf == nil {
return ""
}
return peetf.Source
}
func (peetf *PassportElementErrorTranslationFile) PassportElementErrorType() string {
if peetf == nil {
return ""
}
return peetf.Type
}
func (peetf *PassportElementErrorTranslationFiles) PassportElementErrorMessage() string {
if peetf == nil {
return ""
}
return peetf.Message
}
func (peetf *PassportElementErrorTranslationFiles) PassportElementErrorSource() string {
if peetf == nil {
return ""
}
return peetf.Source
}
func (peetf *PassportElementErrorTranslationFiles) PassportElementErrorType() string {
if peetf == nil {
return ""
}
return peetf.Type
}
func (peeu *PassportElementErrorUnspecified) PassportElementErrorMessage() string {
if peeu == nil {
return ""
}
return peeu.Message
}
func (peeu *PassportElementErrorUnspecified) PassportElementErrorSource() string {
if peeu == nil {
return ""
}
return peeu.Source
}
func (peeu *PassportElementErrorUnspecified) PassportElementErrorType() string {
if peeu == nil {
return ""
}
return peeu.Type
}
func (pseoos *PassportScopeElementOneOfSeveral) PassportScopeElementTranslation() bool {
if pseoos == nil {
return false
}
return pseoos.Translation
}
func (pseoos *PassportScopeElementOneOfSeveral) PassportScopeElementSelfie() bool {
if pseoos == nil {
return false
}
return pseoos.Selfie
}
func (pseo *PassportScopeElementOne) PassportScopeElementTranslation() bool {
if pseo == nil {
return false
}
return pseo.Translation
}
func (pseo *PassportScopeElementOne) PassportScopeElementSelfie() bool {
if pseo == nil {
return false
}
return pseo.Selfie
}
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 {
if pd == nil {
return ""
}
return pd.FirstName + " " + pd.LastName
}
func (pd *PersonalDetails) FullNameNative() string {
if pd == nil {
return ""
}
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
}
// InSet checks that the current sticker in the stickers set.
//
// For uploaded WebP files this return false.
func (s *Sticker) InSet() bool {
return s != nil && s.SetName != ""
}
// IsWebP check that the current sticker is a WebP file uploaded by user.
func (s *Sticker) IsWebP() bool {
return s != nil && s.SetName == ""
}
// Set use bot for getting parent StickerSet if SetName is present.
//
// Return nil if current sticker has been uploaded by user as WebP file.
func (s *Sticker) Set(bot *Bot) *StickerSet {
if s.IsWebP() || bot == nil {
return nil
}
set, err := bot.GetStickerSet(s.SetName)
if err != nil {
return nil
}
return set
}
func (s *Sticker) HasThumb() bool {
return s != nil && s.Thumb != nil
}
func (s *Sticker) IsMask() bool {
return s != nil && s.MaskPosition != nil
}
func (s *Sticker) File() *File {
if s == nil {
return nil
}
return &File{
FileID: s.FileID,
FileSize: s.FileSize,
}
}
// NewLongPollingChannel creates channel for receive incoming updates using long
// polling.
func (b *Bot) NewLongPollingChannel(params *GetUpdatesParameters) UpdatesChannel {
if params == nil {
params = &GetUpdatesParameters{
Offset: 0,
Limit: 100,
Timeout: 60,
}
}
channel := 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.ID >= params.Offset {
params.Offset = update.ID + 1
channel <- update
}
}
}
}()
return channel
}
// 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(setURL *http.URI, params *SetWebhookParameters, ln net.Listener, cert ...string) (updates UpdatesChannel, shutdown ShutdownFunc) {
defer http.ReleaseURI(setURL)
if params == nil {
params = &SetWebhookParameters{
URL: setURL.String(),
MaxConnections: 40,
}
}
var err error
channel := make(chan Update, 100)
handleFunc := func(ctx *http.RequestCtx) {
dlog.Ln("Request path:", string(ctx.Path()))
if !bytes.HasPrefix(ctx.Path(), setURL.Path()) {
dlog.Ln("Unsupported request path:", string(ctx.Path()))
return
}
dlog.Ln("Catched supported request path:", string(ctx.Path()))
var update Update
2019-07-24 09:34:55 +00:00
if err = parser.Unmarshal(ctx.Request.Body(), &update); err != nil {
2019-07-23 13:45:57 +00:00
return
}
channel <- update
}
srv := http.Server{
Name: b.Username,
Concurrency: params.MaxConnections,
Handler: handleFunc,
ReduceMemoryUsage: true,
}
go func() {
switch {
case len(cert) == 2:
dlog.Ln("Creating TLS router...")
err = srv.ServeTLS(ln, cert[0], cert[1])
default:
dlog.Ln("Creating simple router...")
err = srv.Serve(ln)
}
if err != nil {
log.Fatalln(err.Error())
}
}()
if _, err = b.SetWebhook(params); err != nil {
log.Fatalln(err.Error())
}
return channel, srv.Shutdown
}
// IsMessage checks that the current update is a message creation event.
func (u *Update) IsMessage() bool {
return u != nil && u.Message != nil
}
// IsEditedMessage checks that the current update is a editing message event.
func (u *Update) IsEditedMessage() bool {
return u != nil && u.EditedMessage != nil
}
// IsChannelPost checks that the current update is a post channel creation event.
func (u *Update) IsChannelPost() bool {
return u != nil && u.ChannelPost != nil
}
// IsEditedChannelPost checks that the current update is a editing post channel
// event.
func (u *Update) IsEditedChannelPost() bool {
return u != nil && u.EditedChannelPost != nil
}
// IsInlineQuery checks that the current update is a inline query update.
func (u *Update) IsInlineQuery() bool {
return u != nil && u.InlineQuery != nil
}
// IsChosenInlineResult checks that the current update is a chosen inline result
// update.
func (u *Update) IsChosenInlineResult() bool {
return u != nil && u.ChosenInlineResult != nil
}
// IsCallbackQuery checks that the current update is a callback query update.
func (u *Update) IsCallbackQuery() bool {
return u != nil && u.CallbackQuery != nil
}
// IsShippingQuery checks that the current update is a shipping query update.
func (u *Update) IsShippingQuery() bool {
return u != nil && u.ShippingQuery != nil
}
// IsPreCheckoutQuery checks that the current update is a pre checkout query
// update.
func (u *Update) IsPreCheckoutQuery() bool {
return u != nil && u.PreCheckoutQuery != nil
}
2019-08-09 07:23:21 +00:00
// IsPoll checks that the current update is a poll update.
func (u *Update) IsPoll() bool {
return u != nil && u.Poll != nil
}
2019-07-23 13:45:57 +00:00
// 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
2019-08-09 07:23:21 +00:00
case u.IsPoll():
return UpdatePoll
2019-07-23 13:45:57 +00:00
default:
return ""
}
}
// Language parse LanguageCode of current user and returns language.Tag.
func (u *User) Language() language.Tag {
if u == nil {
return language.Und
}
tag, err := language.Parse(u.LanguageCode)
if err != nil {
return language.Und
}
return tag
}
// NewPrinter create simple message.Printer with User.Language() by default.
func (u *User) NewPrinter() *message.Printer {
return message.NewPrinter(u.Language())
}
// FullName returns the full name of user or FirstName if LastName is not
// available.
func (u *User) FullName() string {
if u == nil {
return ""
}
if u.HasLastName() {
return u.FirstName + " " + u.LastName
}
return u.FirstName
}
// HaveLastName checks what the current user has a LastName.
func (u *User) HasLastName() bool {
return u != nil && u.LastName != ""
}
// HaveUsername checks what the current user has a username.
func (u *User) HasUsername() bool {
return u != nil && u.Username != ""
}
func (v *Video) HasThumb() bool {
return v != nil && v.Thumb != nil
}
func (v *Video) File() *File {
if v == nil {
return nil
}
return &File{
FileID: v.FileID,
FileSize: v.FileSize,
}
}
func (vn *VideoNote) HasThumb() bool {
return vn != nil && vn.Thumb != nil
}
func (vn *VideoNote) File() *File {
if vn == nil {
return nil
}
return &File{
FileID: vn.FileID,
FileSize: vn.FileSize,
}
}
func (wi *WebhookInfo) HasURL() bool {
return wi != nil && wi.URL != ""
}
func (wi *WebhookInfo) LastErrorTime() *time.Time {
if wi == nil {
return nil
}
led := time.Unix(wi.LastErrorDate, 0)
return &led
}