1
0
Fork 0
telegram/utils.go

2703 lines
62 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package telegram
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")
// 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
}
// NewForceReply calls the response interface to the message.
func NewForceReply() *ForceReply {
return &ForceReply{
ForceReply: true,
}
}
// NewInlineMentionURL creates a url.URL for the mention user without username.
func NewInlineMentionURL(userID int) *http.URI {
link := http.AcquireURI()
link.Update("tg://user?id=" + strconv.Itoa(userID))
return link
}
func NewMarkdownBold(text string) string {
return "*" + text + "*"
}
func NewMarkdownItalic(text string) string {
return "_" + text + "_"
}
func NewMarkdownURL(text string, link fmt.Stringer) string {
return "[" + text + "](" + link.String() + ")"
}
func NewMarkdownMention(text string, id int) string {
return NewMarkdownURL(text, NewInlineMentionURL(id))
}
func NewMarkdownCode(text string) string {
return "`" + text + "`"
}
func NewMarkdownCodeBlock(text string) string {
return "```" + text + "```"
}
func NewHTMLBold(text string) string {
return "<b>" + text + "</b>"
}
func NewHTMLItalic(text string) string {
return "<i>" + text + "</i>"
}
func NewHTMLURL(text string, link fmt.Stringer) string {
return `<a href="` + link.String() + `">` + text + `</a>`
}
func NewHTMLMention(text string, id int) string {
return NewHTMLURL(text, NewInlineMentionURL(id))
}
func NewHTMLCode(text string) string {
return "<code>" + text + "</code>"
}
func NewHTMLCodeBlock(text string) string {
return "<pre>" + text + "</pre>"
}
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
err = parser.Unmarshal(data, &c)
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
err = parser.Unmarshal(body, &pd)
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 = parser.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 = parser.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 = parser.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)
}
// 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
}
// IsPoll checks that the current message is a poll.
func (m *Message) IsPoll() bool {
return m != nil && m.Poll != nil
}
// 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
if err = parser.Unmarshal(ctx.Request.Body(), &update); err != nil {
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
}
// IsPoll checks that the current update is a poll update.
func (u *Update) IsPoll() bool {
return u != nil && 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 ""
}
}
// 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
}