1
0
Fork 0

🔀 Merge branch 'develop'

This commit is contained in:
Maxim Lebedev 2018-04-12 19:27:58 +05:00
commit 9fbfbd4c9a
No known key found for this signature in database
GPG Key ID: F8978F46FF0FFA4F
76 changed files with 807 additions and 564 deletions

View File

@ -1,6 +0,0 @@
# [Patrons!](https://www.patreon.com/bePatron?c=243288)
**These people have sponsored the current version of the project:**
- Daniil Tlenov
- Aurielb
- Yami Odymel
- MoD21k

View File

@ -10,10 +10,6 @@ type AnswerCallbackQueryParameters struct {
// user, 0-200 characters
Text string `json:"text,omitempty"`
// If true, an alert will be shown by the client instead of a notification at
// the top of the chat screen. Defaults to false.
ShowAlert bool `json:"show_alert,omitempty"`
// URL that will be opened by the user's client. If you have created a Game
// and accepted the conditions via @Botfather, specify the URL that opens
// your game note that this will only work if the query comes from a
@ -23,6 +19,10 @@ type AnswerCallbackQueryParameters struct {
// bot with a parameter.
URL string `json:"url,omitempty"`
// If true, an alert will be shown by the client instead of a notification at
// the top of the chat screen. Defaults to false.
ShowAlert bool `json:"show_alert,omitempty"`
// The maximum amount of time in seconds that the result of the callback
// query may be cached client-side. Telegram apps will support caching
// starting in version 3.14. Defaults to 0.
@ -47,7 +47,7 @@ func (bot *Bot) AnswerCallbackQuery(params *AnswerCallbackQueryParameters) (bool
return false, err
}
resp, err := bot.request(dst, "answerCallbackQuery")
resp, err := bot.request(dst, MethodAnswerCallbackQuery)
if err != nil {
return false, err
}

View File

@ -6,18 +6,6 @@ type AnswerInlineQueryParameters struct {
// Unique identifier for the answered query
InlineQueryID string `json:"inline_query_id"`
// A JSON-serialized array of results for the inline query
Results []interface{} `json:"results"`
// The maximum amount of time in seconds that the result of the inline query
// may be cached on the server. Defaults to 300.
CacheTime int `json:"cache_time,omitempty"`
// Pass True, if results may be cached on the server side only for the user
// that sent the query. By default, results may be returned to any user who
// sends the same query
IsPersonal bool `json:"is_personal,omitempty"`
// Pass the offset that a client should send in the next query with the same
// text to receive more results. Pass an empty string if there are no more
// results or if you dont support pagination. Offset length cant exceed 64
@ -33,6 +21,18 @@ type AnswerInlineQueryParameters struct {
// presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and -
// are allowed.
SwitchPrivateMessageParameter string `json:"switch_pm_parameter,omitempty"`
// A JSON-serialized array of results for the inline query
Results []interface{} `json:"results"`
// The maximum amount of time in seconds that the result of the inline query
// may be cached on the server. Defaults to 300.
CacheTime int `json:"cache_time,omitempty"`
// Pass True, if results may be cached on the server side only for the user
// that sent the query. By default, results may be returned to any user who
// sends the same query
IsPersonal bool `json:"is_personal,omitempty"`
}
func NewAnswerInlineQuery(inlineQueryID string, results ...interface{}) *AnswerInlineQueryParameters {
@ -51,7 +51,7 @@ func (bot *Bot) AnswerInlineQuery(params *AnswerInlineQueryParameters) (bool, er
return false, err
}
resp, err := bot.request(dst, "answerInlineQuery")
resp, err := bot.request(dst, MethodAnswerInlineQuery)
if err != nil {
return false, err
}

View File

@ -6,17 +6,17 @@ type AnswerPreCheckoutQueryParameters struct {
// Unique identifier for the query to be answered
PreCheckoutQueryID string `json:"pre_checkout_query_id"`
// Specify True if everything is alright (goods are available, etc.) and the
// bot is ready to proceed with the order. Use False if there are any
// problems.
Ok bool `json:"ok"`
// Required if ok is False. Error message in human readable form that
// explains the reason for failure to proceed with the checkout (e.g. "Sorry,
// somebody just bought the last of our amazing black T-shirts while you were
// busy filling out your payment details. Please choose a different color or
// garment!"). Telegram will display this message to the user.
ErrorMessage string `json:"error_message,omitempty"`
// Specify True if everything is alright (goods are available, etc.) and the
// bot is ready to proceed with the order. Use False if there are any
// problems.
Ok bool `json:"ok"`
}
func NewAnswerPreCheckoutQuery(preCheckoutQueryID string, ok bool) *AnswerPreCheckoutQueryParameters {
@ -41,7 +41,7 @@ func (bot *Bot) AnswerPreCheckoutQuery(params *AnswerShippingQueryParameters) (b
return false, err
}
resp, err := bot.request(dst, "answerPreCheckoutQuery")
resp, err := bot.request(dst, MethodAnswerPreCheckoutQuery)
if err != nil {
return false, err
}

View File

@ -6,6 +6,12 @@ type AnswerShippingQueryParameters struct {
// Unique identifier for the query to be answered
ShippingQueryID string `json:"shipping_query_id"`
// Required if ok is False. Error message in human readable form that
// explains why it is impossible to complete the order (e.g. "Sorry, delivery
// to your desired address is unavailable'). Telegram will display this
// message to the user.
ErrorMessage string `json:"error_message,omitempty"`
// Specify True if delivery to the specified address is possible and False
// if there are any problems (for example, if delivery to the specified
// address is not possible)
@ -14,12 +20,6 @@ type AnswerShippingQueryParameters struct {
// Required if ok is True. A JSON-serialized array of available shipping
// options.
ShippingOptions []ShippingOption `json:"shipping_options,omitempty"`
// Required if ok is False. Error message in human readable form that
// explains why it is impossible to complete the order (e.g. "Sorry, delivery
// to your desired address is unavailable'). Telegram will display this
// message to the user.
ErrorMessage string `json:"error_message,omitempty"`
}
func NewAnswerShippingQuery(shippingQueryID string, ok bool) *AnswerShippingQueryParameters {
@ -40,7 +40,7 @@ func (bot *Bot) AnswerShippingQuery(params *AnswerShippingQueryParameters) (bool
return false, err
}
resp, err := bot.request(dst, "answerShippingQuery")
resp, err := bot.request(dst, MethodAnswerShippingQuery)
if err != nil {
return false, err
}

151
constants.go Normal file
View File

@ -0,0 +1,151 @@
package telegram
const (
ActionFindLocation = "find_location"
ActionRecordAudio = "record_audio"
ActionRecordVideo = "record_video"
ActionRecordVideoNote = "record_video_note"
ActionTyping = "typing"
ActionUploadAudio = "upload_audio"
ActionUploadDocument = "upload_document"
ActionUploadPhoto = "upload_photo"
ActionUploadVideo = "upload_video"
ActionUploadVideoNote = "upload_video_note"
)
const (
ChatChannel = "channel"
ChatGroup = "group"
ChatPrivate = "private"
ChatSuperGroup = "supergroup"
)
const (
EntityBold = "bold"
EntityBotCommand = "bot_command"
EntityCode = "code"
EntityEmail = "email"
EntityHashtag = "hashtag"
EntityItalic = "italic"
EntityMention = "mention"
EntityPre = "pre"
EntityTextLink = "text_link"
EntityTextMention = "text_mention"
EntityURL = "url"
)
const (
MethodAddStickerToSet = "addStickerToSet"
MethodAnswerCallbackQuery = "answerCallbackQuery"
MethodAnswerInlineQuery = "answerInlineQuery"
MethodAnswerPreCheckoutQuery = "answerPreCheckoutQuery"
MethodAnswerShippingQuery = "answerShippingQuery"
MethodCreateNewStickerSet = "createNewStickerSet"
MethodDeleteChatPhoto = "deleteChatPhoto"
MethodDeleteChatStickerSet = "deleteChatStickerSet"
MethodDeleteMessage = "deleteMessage"
MethodDeleteStickerFromSet = "deleteStickerFromSet"
MethodDeleteWebhook = "deleteWebhook"
MethodEditMessageCaption = "editMessageCaption"
MethodEditMessageLiveLocation = "editMessageLiveLocation"
MethodEditMessageReplyMarkup = "editMessageReplyMarkup"
MethodEditMessageText = "editMessageText"
MethodExportChatInviteLink = "exportChatInviteLink"
MethodForwardMessage = "forwardMessage"
MethodGetChat = "getChat"
MethodGetChatAdministrators = "getChatAdministrators"
MethodGetChatMember = "getChatMember"
MethodGetChatMembersCount = "getChatMembersCount"
MethodGetFile = "getFile"
MethodGetGameHighScores = "getGameHighScores"
MethodGetMe = "getMe"
MethodGetStickerSet = "getStickerSet"
MethodGetUpdates = "getUpdates"
MethodGetUserProfilePhotos = "getUserProfilePhotos"
MethodGetWebhookInfo = "getWebhookInfo"
MethodKickChatMember = "kickChatMember"
MethodLeaveChat = "leaveChat"
MethodPinChatMessage = "pinChatMessage"
MethodPromoteChatMember = "promoteChatMember"
MethodRestrictChatMember = "restrictChatMember"
MethodSendAudio = "sendAudio"
MethodSendChatAction = "sendChatAction"
MethodSendContact = "sendContact"
MethodSendDocument = "sendDocument"
MethodSendGame = "sendGame"
MethodSendInvoice = "sendInvoice"
MethodSendLocation = "sendLocation"
MethodSendMediaGroup = "sendMediaGroup"
MethodSendMessage = "sendMessage"
MethodSendPhoto = "sendPhoto"
MethodSendSticker = "sendSticker"
MethodSendVenue = "sendVenue"
MethodSendVideo = "sendVideo"
MethodSendVideoNote = "sendVideoNote"
MethodSendVoice = "sendVoice"
MethodSetChatDescription = "setChatDescription"
MethodSetChatPhoto = "setChatPhoto"
MethodSetChatStickerSet = "setChatStickerSet"
MethodSetChatTitle = "setChatTitle"
MethodSetGameScore = "setGameScore"
MethodSetStickerPositionInSet = "setStickerPositionInSet"
MethodSetWebhook = "setWebhook"
MethodStopMessageLiveLocation = "stopMessageLiveLocation"
MethodUnbanChatMember = "unbanChatMember"
MethodUnpinChatMessage = "unpinChatMessage"
MethodUploadStickerFile = "uploadStickerFile"
)
const (
ModeHTML = "html"
ModeMarkdown = "markdown"
)
const (
MimeHTML = "text/html"
MimeMP4 = "video/mp4"
MimePDF = "application/pdf"
MimeZIP = "application/zip"
)
const (
SchemeAttach = "attach"
SchemeTelegram = "tg"
)
const (
StatusAdministrator = "administrator"
StatusCreator = "creator"
StatusKicked = "kicked"
StatusLeft = "left"
StatusMember = "member"
StatusRestricted = "restricted"
)
const (
TypeArticle = "article"
TypeAudio = "audio"
TypeContact = "contact"
TypeDocument = "document"
TypeGame = "game"
TypeGIF = "gif"
TypeLocation = "location"
TypeMpeg4Gif = "mpeg4_gif"
TypePhoto = "photo"
TypeSticker = "sticker"
TypeVenue = "venue"
TypeVideo = "video"
TypeVoice = "voice"
)
const (
UpdateCallbackQuery = "callback_query"
UpdateChannelPost = "channel_post"
UpdateChosenInlineResult = "chosen_inline_result"
UpdateEditedChannelPost = "edited_channel_post"
UpdateEditedMessage = "edited_message"
UpdateInlineQuery = "inline_query"
UpdateMessage = "message"
UpdatePreCheckoutQuery = "pre_checkout_query"
UpdateShippingQuery = "shipping_query"
)

View File

@ -19,7 +19,7 @@ func (bot *Bot) DeleteChatPhoto(chatID int64) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "deleteChatPhoto")
resp, err := bot.request(dst, MethodDeleteChatPhoto)
if err != nil {
return false, err
}

View File

@ -17,7 +17,7 @@ func (bot *Bot) DeleteChatStickerSet(chatID int64) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "deleteChatStickerSet")
resp, err := bot.request(dst, MethodDeleteChatStickerSet)
if err != nil {
return false, err
}

View File

@ -25,7 +25,7 @@ func (bot *Bot) DeleteMessage(chatID int64, messageID int) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "deleteMessage")
resp, err := bot.request(dst, MethodDeleteMessage)
if err != nil {
return false, err
}

View File

@ -14,7 +14,7 @@ func (bot *Bot) DeleteStickerFromSet(sticker string) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "deleteStickerFromSet")
resp, err := bot.request(dst, MethodDeleteStickerFromSet)
if err != nil {
return false, err
}

View File

@ -5,7 +5,7 @@ import json "github.com/pquerna/ffjson/ffjson"
// DeleteWebhook remove webhook integration if you decide to switch back to
// getUpdates. Returns True on success. Requires no parameters.
func (bot *Bot) DeleteWebhook() (bool, error) {
resp, err := bot.request(nil, "deleteWebhook")
resp, err := bot.request(nil, MethodDeleteWebhook)
if err != nil {
return false, err
}

39
docs/CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,39 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project owner at git@toby3d.ru. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

9
docs/SUPPORT.md Normal file
View File

@ -0,0 +1,9 @@
# [Support me on Patreon!](https://www.patreon.com/bePatron?c=243288)
I develop this project in my spare time, and I do it and I will do it free of charge. However, you can make a donation or become a sponsor to make sure that I have enough coffee and pizza for night coding.
**These people sponsored current version of the project:**
- Aurielb
- Daniil Tlenov
- @kirillDanshin
- MoD21k
- @YamiOdymel

View File

@ -36,7 +36,7 @@ func (bot *Bot) EditMessageCaption(params *EditMessageCaptionParameters) (*Messa
return nil, err
}
resp, err := bot.request(dst, "editMessageCaption")
resp, err := bot.request(dst, MethodEditMessageCaption)
if err != nil {
return nil, err
}

View File

@ -44,7 +44,7 @@ func (bot *Bot) EditMessageLiveLocation(params *EditMessageLiveLocationParameter
return nil, err
}
resp, err := bot.request(dst, "editMessageLiveLocation")
resp, err := bot.request(dst, MethodEditMessageLiveLocation)
if err != nil {
return nil, err
}

View File

@ -29,7 +29,7 @@ func (bot *Bot) EditMessageReplyMarkup(params *EditMessageReplyMarkupParameters)
return nil, err
}
resp, err := bot.request(dst, "editMessageReplyMarkup")
resp, err := bot.request(dst, MethodEditMessageReplyMarkup)
if err != nil {
return nil, err
}

View File

@ -45,7 +45,7 @@ func (bot *Bot) EditMessageText(params *EditMessageTextParameters) (*Message, er
return nil, err
}
resp, err := bot.request(dst, "editMessageText")
resp, err := bot.request(dst, MethodEditMessageText)
if err != nil {
return nil, err
}

View File

@ -16,7 +16,7 @@ func (bot *Bot) ExportChatInviteLink(chatID int64) (string, error) {
return "", err
}
resp, err := bot.request(dst, "exportChatInviteLink")
resp, err := bot.request(dst, MethodExportChatInviteLink)
if err != nil {
return "", err
}

View File

@ -31,7 +31,7 @@ func (bot *Bot) ForwardMessage(params *ForwardMessageParameters) (*Message, erro
return nil, err
}
resp, err := bot.request(dst, "forwardMessage")
resp, err := bot.request(dst, MethodForwardMessage)
if err != nil {
return nil, err
}

View File

@ -16,7 +16,7 @@ func (bot *Bot) GetChat(chatID int64) (*Chat, error) {
return nil, err
}
resp, err := bot.request(dst, "getChat")
resp, err := bot.request(dst, MethodGetChat)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ func (bot *Bot) GetChatAdministrators(chatID int64) ([]ChatMember, error) {
return nil, err
}
resp, err := bot.request(dst, "getChatAdministrators")
resp, err := bot.request(dst, MethodGetChatAdministrators)
if err != nil {
return nil, err
}

View File

@ -20,7 +20,7 @@ func (bot *Bot) GetChatMember(chatID int64, userID int) (*ChatMember, error) {
return nil, err
}
resp, err := bot.request(dst, "getChatMember")
resp, err := bot.request(dst, MethodGetChatMember)
if err != nil {
return nil, err
}

View File

@ -15,7 +15,7 @@ func (bot *Bot) GetChatMembersCount(chatID int64) (int, error) {
return 0, err
}
resp, err := bot.request(dst, "getChatMembersCount")
resp, err := bot.request(dst, MethodGetChatMembersCount)
if err != nil {
return 0, err
}

View File

@ -23,7 +23,7 @@ func (bot *Bot) GetFile(fileID string) (*File, error) {
return nil, err
}
resp, err := bot.request(dst, "getFile")
resp, err := bot.request(dst, MethodGetFile)
if err != nil {
return nil, err
}

View File

@ -6,14 +6,14 @@ type GetGameHighScoresParameters struct {
// Target user id
UserID int `json:"user_id"`
// Required if inline_message_id is not specified. Unique identifier for the
// target chat
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Required if inline_message_id is not specified. Unique identifier for the
// target chat
ChatID int64 `json:"chat_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
@ -34,7 +34,7 @@ func (bot *Bot) GetGameHighScores(params *GetGameHighScoresParameters) ([]GameHi
return nil, err
}
resp, err := bot.request(dst, "getGameHighScores")
resp, err := bot.request(dst, MethodGetGameHighScores)
if err != nil {
return nil, err
}

View File

@ -5,7 +5,7 @@ import json "github.com/pquerna/ffjson/ffjson"
// GetMe testing your bot's auth token. Requires no parameters. Returns basic
// information about the bot in form of a User object.
func (bot *Bot) GetMe() (*User, error) {
resp, err := bot.request(nil, "getMe")
resp, err := bot.request(nil, MethodGetMe)
if err != nil {
return nil, err
}

View File

@ -13,7 +13,7 @@ func (bot *Bot) GetStickerSet(name string) (*StickerSet, error) {
return nil, err
}
resp, err := bot.request(dst, "getStickerSet")
resp, err := bot.request(dst, MethodGetStickerSet)
if err != nil {
return nil, err
}

View File

@ -42,7 +42,7 @@ func (bot *Bot) GetUpdates(params *GetUpdatesParameters) ([]Update, error) {
return nil, err
}
resp, err := bot.request(dst, "getUpdates")
resp, err := bot.request(dst, MethodGetUpdates)
if err != nil {
return nil, err
}

View File

@ -15,7 +15,7 @@ func (bot *Bot) GetUserProfilePhotos(params *GetUserProfilePhotosParameters) (*U
return nil, err
}
resp, err := bot.request(dst, "getUserProfilePhotos")
resp, err := bot.request(dst, MethodGetUserProfilePhotos)
if err != nil {
return nil, err
}

View File

@ -6,7 +6,7 @@ import json "github.com/pquerna/ffjson/ffjson"
// returns a WebhookInfo object. If the bot is using getUpdates, will return an
// object with the url field empty.
func (bot *Bot) GetWebhookInfo() (*WebhookInfo, error) {
resp, err := bot.request(nil, "getWebhookInfo")
resp, err := bot.request(nil, MethodGetWebhookInfo)
if err != nil {
return nil, err
}

View File

@ -1,26 +0,0 @@
package telegram
import (
"net/url"
"strconv"
)
func NewForceReply(selective bool) *ForceReply {
return &ForceReply{
ForceReply: true,
Selective: selective,
}
}
func NewInlineMentionURL(id int) *url.URL {
link := &url.URL{
Scheme: "tg",
Path: "user",
}
q := link.Query()
q.Add("id", strconv.Itoa(id))
link.RawQuery = q.Encode()
return link
}

View File

@ -6,8 +6,8 @@ type KickChatMemberParameters struct {
// Unique identifier for the target chat
ChatID int64 `json:"chat_id"`
UserID int `json:"user_id"`
UntilDate int64 `json:"until_date"`
UserID int `json:"user_id"`
}
// KickChatMember kick a user from a group, a supergroup or a channel. In the case of supergroups and
@ -24,7 +24,7 @@ func (bot *Bot) KickChatMember(params *KickChatMemberParameters) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "kickChatMember")
resp, err := bot.request(dst, MethodKickChatMember)
if err != nil {
return false, err
}

View File

@ -14,7 +14,7 @@ func (bot *Bot) LeaveChat(chatID int64) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "leaveChat")
resp, err := bot.request(dst, MethodLeaveChat)
if err != nil {
return false, err
}

View File

@ -4,34 +4,40 @@ import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"errors"
"net/url"
"strconv"
)
var ErrUserNotDefined = errors.New("user is not defined")
// CheckAuthorization verify the authentication and the integrity of the data
// received by comparing the received hash parameter with the hexadecimal
// representation of the HMAC-SHA-256 signature of the data-check-string with the
// SHA256 hash of the bot's token used as a secret key.
func (user *User) CheckAuthorization(botToken string) (bool, error) {
dataCheckString := fmt.Sprint(
"auth_date=", user.AuthDate.Unix(),
"\n", "first_name=", user.FirstName,
// Eliminate 'hash' to avoid recursion and incorrect data validation.
"\n", "id=", user.ID,
)
func (app *App) CheckAuthorization(user *User) (bool, error) {
if user == nil {
return false, ErrUserNotDefined
}
dataCheck := make(url.Values)
dataCheck.Add(KeyAuthDate, string(user.AuthDate))
dataCheck.Add(KeyFirstName, user.FirstName)
dataCheck.Add(KeyID, strconv.Itoa(user.ID))
// Add optional values if exist
if user.LastName != "" {
dataCheckString += fmt.Sprint("\n", "last_name=", user.LastName)
dataCheck.Add(KeyLastName, user.LastName)
}
if user.PhotoURL != "" {
dataCheckString += fmt.Sprint("\n", "photo_url=", user.PhotoURL)
dataCheck.Add(KeyPhotoURL, user.PhotoURL)
}
if user.Username != "" {
dataCheckString += fmt.Sprint("\n", "username=", user.Username)
dataCheck.Add(KeyUsername, user.Username)
}
secretKey := sha256.Sum256([]byte(botToken))
secretKey := sha256.Sum256([]byte(app.SecretKey))
hash := hmac.New(sha256.New, secretKey[0:])
_, err := hash.Write([]byte(dataCheckString))
_, err := hash.Write([]byte(dataCheck.Encode()))
return hex.EncodeToString(hash.Sum(nil)) == user.Hash, err
}

11
login/constants.go Normal file
View File

@ -0,0 +1,11 @@
package login
const (
KeyAuthDate = "auth_date"
KeyFirstName = "first_name"
KeyHash = "hash"
KeyID = "id"
KeyLastName = "last_name"
KeyPhotoURL = "photo_url"
KeyUsername = "username"
)

View File

@ -1,41 +1,11 @@
package login
import (
"net/url"
"strconv"
"time"
)
// User contains data about authenticated user.
type User struct {
AuthDate time.Time `json:"auth_date"`
FirstName string `json:"first_name"`
Hash string `json:"hash"`
ID int `json:"id"`
LastName string `json:"last_name,omitempty"`
PhotoURL string `json:"photo_url,omitempty"`
Username string `json:"username,omitempty"`
// App represents a widget which get and validate users authorizations.
type App struct {
SecretKey string
}
// New create User structure from input url.Values.
func New(src url.Values) (*User, error) {
authDate, err := strconv.Atoi(src.Get("auth_date"))
if err != nil {
return nil, err
}
id, err := strconv.Atoi(src.Get("id"))
if err != nil {
return nil, err
}
return &User{
AuthDate: time.Unix(int64(authDate), 0),
FirstName: src.Get("first_name"),
Hash: src.Get("hash"),
ID: id,
LastName: src.Get("last_name"),
PhotoURL: src.Get("photo_url"),
Username: src.Get("username"),
}, nil
// New create new app widget for validate authorizations with bot token as secret key.
func New(botToken string) *App {
return &App{SecretKey: botToken}
}

40
login/parse_user.go Normal file
View File

@ -0,0 +1,40 @@
package login
import (
"net/url"
"strconv"
)
// User contains data about authenticated user.
type User struct {
AuthDate int64 `json:"auth_date"`
FirstName string `json:"first_name"`
Hash string `json:"hash"`
ID int `json:"id"`
LastName string `json:"last_name,omitempty"`
PhotoURL string `json:"photo_url,omitempty"`
Username string `json:"username,omitempty"`
}
// ParseUser create User structure from input url.Values.
func ParseUser(src url.Values) (*User, error) {
authDate, err := strconv.Atoi(src.Get(KeyAuthDate))
if err != nil {
return nil, err
}
id, err := strconv.Atoi(src.Get(KeyID))
if err != nil {
return nil, err
}
return &User{
AuthDate: int64(authDate),
FirstName: src.Get(KeyFirstName),
Hash: src.Get(KeyHash),
ID: id,
LastName: src.Get(KeyLastName),
PhotoURL: src.Get(KeyPhotoURL),
Username: src.Get(KeyUsername),
}, nil
}

28
login/utils.go Normal file
View File

@ -0,0 +1,28 @@
package login
import (
"fmt"
"time"
)
// FullName return user first name only or full name if last name is present.
func (user *User) FullName() string {
if user == nil {
return ""
}
if user.LastName != "" {
return fmt.Sprintln(user.FirstName, user.LastName)
}
return user.FirstName
}
// AuthTime convert AuthDate field into time.Time.
func (user *User) AuthTime() time.Time {
if user == nil {
return time.Time{}
}
return time.Unix(user.AuthDate, 0)
}

503
models.go
View File

@ -2,77 +2,6 @@ package telegram
import "encoding/json"
const (
ActionFindLocation = "find_location"
ActionRecordAudio = "record_audio"
ActionRecordVideo = "record_video"
ActionRecordVideoNote = "record_video_note"
ActionTyping = "typing"
ActionUploadAudio = "upload_audio"
ActionUploadDocument = "upload_document"
ActionUploadPhoto = "upload_photo"
ActionUploadVideo = "upload_video"
ActionUploadVideoNote = "upload_video_note"
ChatChannel = "channel"
ChatGroup = "group"
ChatPrivate = "private"
ChatSuperGroup = "supergroup"
EntityBold = "bold"
EntityBotCommand = "bot_command"
EntityCode = "code"
EntityEmail = "email"
EntityHashtag = "hashtag"
EntityItalic = "italic"
EntityMention = "mention"
EntityPre = "pre"
EntityTextLink = "text_link"
EntityTextMention = "text_mention"
EntityURL = "url"
ModeHTML = "html"
ModeMarkdown = "markdown"
MimeHTML = "text/html"
MimeMP4 = "video/mp4"
MimePDF = "application/pdf"
MimeZIP = "application/zip"
PrefixAttach = "attach://"
StatusAdministrator = "administrator"
StatusCreator = "creator"
StatusKicked = "kicked"
StatusLeft = "left"
StatusMember = "member"
StatusRestricted = "restricted"
TypeArticle = "article"
TypeAudio = "audio"
TypeContact = "contact"
TypeDocument = "document"
TypeGame = "game"
TypeGIF = "gif"
TypeLocation = "location"
TypeMpeg4Gif = "mpeg4_gif"
TypePhoto = "photo"
TypeSticker = "sticker"
TypeVenue = "venue"
TypeVideo = "video"
TypeVoice = "voice"
UpdateCallbackQuery = "callback_query"
UpdateChannelPost = "channel_post"
UpdateChosenInlineResult = "chosen_inline_result"
UpdateEditedChannelPost = "edited_channel_post"
UpdateEditedMessage = "edited_message"
UpdateInlineQuery = "inline_query"
UpdateMessage = "message"
UpdatePreCheckoutQuery = "pre_checkout_query"
UpdateShippingQuery = "shipping_query"
)
type (
// Response represents a response from the Telegram API with the result
// stored raw. If ok equals true, the request was successful, and the result
@ -132,6 +61,10 @@ type (
// Webhook URL, may be empty if webhook is not set up
URL string `json:"url"`
// Error message in human-readable format for the most recent error that
// happened when trying to deliver an update via webhook
LastErrorMessage string `json:"last_error_message,omitempty"`
// True, if a custom certificate was provided for webhook certificate
// checks
HasCustomCertificate bool `json:"has_custom_certificate"`
@ -139,18 +72,14 @@ type (
// Number of updates awaiting delivery
PendingUpdateCount int `json:"pending_update_count"`
// Unix time for the most recent error that happened when trying to
// deliver an update via webhook
LastErrorDate int64 `json:"last_error_date,omitempty"`
// Error message in human-readable format for the most recent error that
// happened when trying to deliver an update via webhook
LastErrorMessage string `json:"last_error_message,omitempty"`
// Maximum allowed number of simultaneous HTTPS connections to the
// webhook for update delivery
MaxConnections int `json:"max_connections,omitempty"`
// Unix time for the most recent error that happened when trying to
// deliver an update via webhook
LastErrorDate int64 `json:"last_error_date,omitempty"`
// A list of update types the bot is subscribed to. Defaults to all
// update types
AllowedUpdates []string `json:"allowed_updates,omitempty"`
@ -198,12 +127,6 @@ type (
// Last name of the other party in a private chat
LastName string `json:"last_name,omitempty"`
// True if a group has All Members Are Admins enabled.
AllMembersAreAdministrators bool `json:"all_members_are_administrators,omitempty"`
// Chat photo. Returned only in getChat.
Photo *ChatPhoto `json:"photo,omitempty"`
// Description, for supergroups and channel chats. Returned only in
// getChat.
Description string `json:"description,omitempty"`
@ -212,15 +135,21 @@ type (
// getChat.
InviteLink string `json:"invite_link,omitempty"`
// Pinned message, for supergroups. Returned only in getChat.
PinnedMessage *Message `json:"pinned_message,omitempty"`
// For supergroups, name of Group sticker set. Returned only in getChat.
StickerSetName string `json:"sticker_set_name,omitempty"`
// True if a group has All Members Are Admins enabled.
AllMembersAreAdministrators bool `json:"all_members_are_administrators,omitempty"`
// True, if the bot can change group the sticker set. Returned only in
// getChat.
CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"`
// Chat photo. Returned only in getChat.
Photo *ChatPhoto `json:"photo,omitempty"`
// Pinned message, for supergroups. Returned only in getChat.
PinnedMessage *Message `json:"pinned_message,omitempty"`
}
// Message represents a message.
@ -228,42 +157,49 @@ type (
// Unique message identifier inside this chat
ID int `json:"message_id"`
// Sender, empty for messages sent to channels
From *User `json:"from,omitempty"`
// Date the message was sent in Unix time
Date int64 `json:"date"`
// Conversation the message belongs to
Chat *Chat `json:"chat"`
// For forwarded messages, sender of the original message
ForwardFrom *User `json:"forward_from,omitempty"`
// For messages forwarded from channels, information about the original
// channel
ForwardFromChat *Chat `json:"forward_from_chat,omitempty"`
// For messages forwarded from channels, identifier of the original
// message in the channel
ForwardFromMessageID int `json:"forward_from_message_id,omitempty"`
// For messages forwarded from channels, signature of the post author if
// present
ForwardSignature string `json:"forward_signature,omitempty"`
// Sender, empty for messages sent to channels
From *User `json:"from,omitempty"`
// For forwarded messages, sender of the original message
ForwardFrom *User `json:"forward_from,omitempty"`
// A member was removed from the group, information about them (this
// member may be the bot itself)
LeftChatMember *User `json:"left_chat_member,omitempty"`
// Date the message was sent in Unix time
Date int64 `json:"date"`
// For forwarded messages, date the original message was sent in Unix
// time
ForwardDate int64 `json:"forward_date,omitempty"`
// For replies, the original message. Note that the Message object in
// this field will not contain further reply_to_message fields even if it
// itself is a reply.
ReplyToMessage *Message `json:"reply_to_message,omitempty"`
// Date the message was last edited in Unix time
EditDate int64 `json:"edit_date,omitempty"`
// The group has been migrated to a supergroup with the specified
// identifier.
MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
// The supergroup has been migrated from a group with the specified
// identifier.
MigrateFromChatID int64 `json:"migrate_from_chat_id,omitempty"`
// Conversation the message belongs to
Chat *Chat `json:"chat"`
// For messages forwarded from channels, information about the original
// channel
ForwardFromChat *Chat `json:"forward_from_chat,omitempty"`
// For messages forwarded from channels, signature of the post author if
// present
ForwardSignature string `json:"forward_signature,omitempty"`
// The unique identifier of a media message group this message belongs to
MediaGroupID string `json:"media_group_id,omitempty"`
@ -274,6 +210,25 @@ type (
// characters.
Text string `json:"text,omitempty"`
// Caption for the document, photo or video, 0-200 characters
Caption string `json:"caption,omitempty"`
// A chat title was changed to this value
NewChatTitle string `json:"new_chat_title,omitempty"`
// The domain name of the website on which the user has logged in.
ConnectedWebsite string `json:"connected_website,omitempty"`
// For replies, the original message. Note that the Message object in
// this field will not contain further reply_to_message fields even if it
// itself is a reply.
ReplyToMessage *Message `json:"reply_to_message,omitempty"`
// Specified message was pinned. Note that the Message object in this
// field will not contain further reply_to_message fields even if it is
// itself a reply.
PinnedMessage *Message `json:"pinned_message,omitempty"`
// For text messages, special entities like usernames, URLs, bot
// commands, etc. that appear in the text
Entities []MessageEntity `json:"entities,omitempty"`
@ -294,6 +249,9 @@ type (
// Message is a photo, available sizes of the photo
Photo []PhotoSize `json:"photo,omitempty"`
// A chat photo was change to this value
NewChatPhoto []PhotoSize `json:"new_chat_photo,omitempty"`
// Message is a sticker, information about the sticker
Sticker *Sticker `json:"sticker,omitempty"`
@ -306,9 +264,6 @@ type (
// Message is a video note, information about the video message
VideoNote *VideoNote `json:"video_note,omitempty"`
// Caption for the document, photo or video, 0-200 characters
Caption string `json:"caption,omitempty"`
// Message is a shared contact, information about the contact
Contact *Contact `json:"contact,omitempty"`
@ -322,16 +277,6 @@ type (
// about them (the bot itself may be one of these members)
NewChatMembers []User `json:"new_chat_members,omitempty"`
// A member was removed from the group, information about them (this
// member may be the bot itself)
LeftChatMember *User `json:"left_chat_member,omitempty"`
// A chat title was changed to this value
NewChatTitle string `json:"new_chat_title,omitempty"`
// A chat photo was change to this value
NewChatPhoto []PhotoSize `json:"new_chat_photo,omitempty"`
// Service message: the chat photo was deleted
DeleteChatPhoto bool `json:"delete_chat_photo,omitempty"`
@ -352,28 +297,12 @@ type (
// channel.
ChannelChatCreated bool `json:"channel_chat_created,omitempty"`
// The group has been migrated to a supergroup with the specified
// identifier.
MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
// The supergroup has been migrated from a group with the specified
// identifier.
MigrateFromChatID int64 `json:"migrate_from_chat_id,omitempty"`
// Specified message was pinned. Note that the Message object in this
// field will not contain further reply_to_message fields even if it is
// itself a reply.
PinnedMessage *Message `json:"pinned_message,omitempty"`
// Message is an invoice for a payment, information about the invoice.
Invoice *Invoice `json:"invoice,omitempty"`
// Message is a service message about a successful payment, information
// about the payment.
SuccessfulPayment *SuccessfulPayment `json:"successful_payment,omitempty"`
// The domain name of the website on which the user has logged in.
ConnectedWebsite string `json:"connected_website,omitempty"`
}
// MessageEntity represents one special entity in a text message. For
@ -385,16 +314,16 @@ type (
// text_mention (for users without usernames)
Type string `json:"type"`
// For "text_link" only, url that will be opened after user taps on the
// text
URL string `json:"url,omitempty"`
// Offset in UTF-16 code units to the start of the entity
Offset int `json:"offset"`
// Length of the entity in UTF-16 code units
Length int `json:"length"`
// For "text_link" only, url that will be opened after user taps on the
// text
URL string `json:"url,omitempty"`
// For "text_mention" only, the mentioned user
User *User `json:"user,omitempty"`
}
@ -420,9 +349,6 @@ type (
// Unique identifier for this file
FileID string `json:"file_id"`
// Duration of the audio in seconds as defined by sender
Duration int `json:"duration"`
// Performer of the audio as defined by sender or by audio tags
Performer string `json:"performer,omitempty"`
@ -432,6 +358,9 @@ type (
// MIME type of the file as defined by sender
MimeType string `json:"mime_type,omitempty"`
// Duration of the audio in seconds as defined by sender
Duration int `json:"duration"`
// File size
FileSize int `json:"file_size,omitempty"`
}
@ -442,15 +371,15 @@ type (
// Unique file identifier
FileID string `json:"file_id"`
// Document thumbnail as defined by sender
Thumb *PhotoSize `json:"thumb,omitempty"`
// Original filename as defined by sender
FileName string `json:"file_name,omitempty"`
// MIME type of the file as defined by sender
MimeType string `json:"mime_type,omitempty"`
// Document thumbnail as defined by sender
Thumb *PhotoSize `json:"thumb,omitempty"`
// File size
FileSize int `json:"file_size,omitempty"`
}
@ -460,6 +389,9 @@ type (
// Unique identifier for this file
FileID string `json:"file_id"`
// Mime type of a file as defined by sender
MimeType string `json:"mime_type,omitempty"`
// Video width as defined by sender
Width int `json:"width"`
@ -469,14 +401,11 @@ type (
// Duration of the video in seconds as defined by sender
Duration int `json:"duration"`
// Video thumbnail
Thumb *PhotoSize `json:"thumb,omitempty"`
// Mime type of a file as defined by sender
MimeType string `json:"mime_type,omitempty"`
// File size
FileSize int `json:"file_size,omitempty"`
// Video thumbnail
Thumb *PhotoSize `json:"thumb,omitempty"`
}
// Voice represents a voice note.
@ -484,12 +413,12 @@ type (
// Unique identifier for this file
FileID string `json:"file_id"`
// Duration of the audio in seconds as defined by sender
Duration int `json:"duration"`
// MIME type of the file as defined by sender
MimeType string `json:"mime_type,omitempty"`
// Duration of the audio in seconds as defined by sender
Duration int `json:"duration"`
// File size
FileSize int `json:"file_size,omitempty"`
}
@ -506,11 +435,11 @@ type (
// Duration of the video in seconds as defined by sender
Duration int `json:"duration"`
// Video thumbnail
Thumb *PhotoSize `json:"thumb,omitempty"`
// File size
FileSize int `json:"file_size,omitempty"`
// Video thumbnail
Thumb *PhotoSize `json:"thumb,omitempty"`
}
// Contact represents a phone contact.
@ -571,12 +500,12 @@ type (
// Unique identifier for this file
FileID string `json:"file_id"`
// File size, if known
FileSize int `json:"file_size,omitempty"`
// File path. Use https://api.telegram.org/file/bot<token>/<file_path> to
// get the file.
FilePath string `json:"file_path,omitempty"`
// File size, if known
FileSize int `json:"file_size,omitempty"`
}
// ReplyKeyboardMarkup represents a custom keyboard with reply options (see
@ -720,14 +649,6 @@ type (
// Unique identifier for this query
ID string `json:"id"`
// Sender
From *User `json:"from"`
// Message with the callback button that originated the query. Note that
// message content and message date will not be available if the message
// is too old
Message *Message `json:"message,omitempty"`
// Identifier of the message sent via the bot in inline mode, that
// originated the query.
InlineMessageID string `json:"inline_message_id,omitempty"`
@ -744,6 +665,14 @@ type (
// Short name of a Game to be returned, serves as the unique identifier
// for the game
GameShortName string `json:"game_short_name,omitempty"`
// Sender
From *User `json:"from"`
// Message with the callback button that originated the query. Note that
// message content and message date will not be available if the message
// is too old
Message *Message `json:"message,omitempty"`
}
// ForceReply display a reply interface to the user (act as if the user has
@ -922,26 +851,26 @@ type (
// Unique identifier for this file
FileID string `json:"file_id"`
// Sticker width
Width int `json:"width"`
// Sticker height
Height int `json:"height"`
// Sticker thumbnail in the .webp or .jpg format
Thumb *PhotoSize `json:"thumb,omitempty"`
// Emoji associated with the sticker
Emoji string `json:"emoji,omitempty"`
// Name of the sticker set to which the sticker belongs
SetName string `json:"set_name,omitempty"`
// For mask stickers, the position where the mask should be placed
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
// Sticker width
Width int `json:"width"`
// Sticker height
Height int `json:"height"`
// File size
FileSize int `json:"file_size,omitempty"`
// Sticker thumbnail in the .webp or .jpg format
Thumb *PhotoSize `json:"thumb,omitempty"`
// For mask stickers, the position where the mask should be placed
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
}
// StickerSet represents a sticker set.
@ -986,17 +915,17 @@ type (
// Unique identifier for this query
ID string `json:"id"`
// Sender
From *User `json:"from"`
// Sender location, only for bots that request user location
Location *Location `json:"location,omitempty"`
// Text of the query (up to 512 characters)
Query string `json:"query"`
// Offset of the results to be returned, can be controlled by the bot
Offset string `json:"offset"`
// Sender
From *User `json:"from"`
// Sender location, only for bots that request user location
Location *Location `json:"location,omitempty"`
}
// InlineQueryResult represents one result of an inline query.
@ -1034,24 +963,24 @@ type (
//Title of the result
Title string `json:"title"`
// Content of the message to be sent
InputMessageContent interface{} `json:"input_message_content"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
// URL of the result
URL string `json:"url,omitempty"`
// Pass True, if you don't want the URL to be shown in the message
HideURL bool `json:"hide_url,omitempty"`
// Short description of the result
Description string `json:"description,omitempty"`
// Url of the thumbnail for the result
ThumbURL string `json:"thumb_url,omitempty"`
// Content of the message to be sent
InputMessageContent interface{} `json:"input_message_content"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
// Pass True, if you don't want the URL to be shown in the message
HideURL bool `json:"hide_url,omitempty"`
// Thumbnail width
ThumbWidth int `json:"thumb_width,omitempty"`
@ -1077,12 +1006,6 @@ type (
// URL of the thumbnail for the photo
ThumbURL string `json:"thumb_url"`
// Width of the photo
PhotoWidth int `json:"photo_width,omitempty"`
// Height of the photo
PhotoHeight int `json:"photo_height,omitempty"`
// Title for the result
Title string `json:"title,omitempty"`
@ -1096,6 +1019,12 @@ type (
// fixed-width text or inline URLs in the media caption.
ParseMode string `json:"parse_mode,omitempty"`
// Width of the photo
PhotoWidth int `json:"photo_width,omitempty"`
// Height of the photo
PhotoHeight int `json:"photo_height,omitempty"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
@ -1117,15 +1046,6 @@ type (
// A valid URL for the GIF file. File size must not exceed 1MB
GifURL string `json:"gif_url"`
// Width of the GIF
GifWidth int `json:"gif_width,omitempty"`
// Height of the GIF
GifHeight int `json:"gif_height,omitempty"`
// Duration of the GIF
GifDuration int `json:"gif_duration,omitempty"`
// URL of the static thumbnail for the result (jpeg or gif)
ThumbURL string `json:"thumb_url"`
@ -1139,6 +1059,15 @@ type (
// fixed-width text or inline URLs in the media caption.
ParseMode string `json:"parse_mode,omitempty"`
// Width of the GIF
GifWidth int `json:"gif_width,omitempty"`
// Height of the GIF
GifHeight int `json:"gif_height,omitempty"`
// Duration of the GIF
GifDuration int `json:"gif_duration,omitempty"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
@ -1161,15 +1090,6 @@ type (
// A valid URL for the MP4 file. File size must not exceed 1MB
Mpeg4URL string `json:"mpeg4_url"`
// Video width
Mpeg4Width int `json:"mpeg4_width,omitempty"`
// Video height
Mpeg4Height int `json:"mpeg4_height,omitempty"`
// Video duration
Mpeg4Duration int `json:"mpeg4_duration,omitempty"`
// URL of the static thumbnail (jpeg or gif) for the result
ThumbURL string `json:"thumb_url"`
@ -1179,6 +1099,15 @@ type (
// Caption of the MPEG-4 file to be sent, 0-200 characters
Caption string `json:"caption,omitempty"`
// Video width
Mpeg4Width int `json:"mpeg4_width,omitempty"`
// Video height
Mpeg4Height int `json:"mpeg4_height,omitempty"`
// Video duration
Mpeg4Duration int `json:"mpeg4_duration,omitempty"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
@ -1220,6 +1149,9 @@ type (
// fixed-width text or inline URLs in the media caption.
ParseMode string `json:"parse_mode,omitempty"`
// Short description of the result
Description string `json:"description,omitempty"`
// Video width
VideoWidth int `json:"video_width,omitempty"`
@ -1229,9 +1161,6 @@ type (
// Video duration in seconds
VideoDuration int `json:"video_duration,omitempty"`
// Short description of the result
Description string `json:"description,omitempty"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
@ -1344,15 +1273,15 @@ type (
// Short description of the result
Description string `json:"description,omitempty"`
// URL of the thumbnail (jpeg only) for the file
ThumbURL string `json:"thumb_url,omitempty"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
// Content of the message to be sent instead of the file
InputMessageContent interface{} `json:"input_message_content,omitempty"`
// URL of the thumbnail (jpeg only) for the file
ThumbURL string `json:"thumb_url,omitempty"`
// Thumbnail width
ThumbWidth int `json:"thumb_width,omitempty"`
@ -1371,24 +1300,24 @@ type (
// Unique identifier for this result, 1-64 Bytes
ID string `json:"id"`
// Location title
Title string `json:"title"`
//Url of the thumbnail for the result
ThumbURL string `json:"thumb_url,omitempty"`
// Location latitude in degrees
Latitude float32 `json:"latitude"`
// Location longitude in degrees
Longitude float32 `json:"longitude"`
// Location title
Title string `json:"title"`
//Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
//Content of the message to be sent instead of the location
InputMessageContent interface{} `json:"input_message_content,omitempty"`
//Url of the thumbnail for the result
ThumbURL string `json:"thumb_url,omitempty"`
//Thumbnail width
ThumbWidth int `json:"thumb_width,omitempty"`
@ -1406,12 +1335,6 @@ type (
// Unique identifier for this result, 1-64 Bytes
ID string `json:"id"`
// Latitude of the venue location in degrees
Latitude float32 `json:"latitude"`
// Longitude of the venue location in degrees
Longitude float32 `json:"longitude"`
// Title of the venue
Title string `json:"title"`
@ -1421,15 +1344,21 @@ type (
// Foursquare identifier of the venue if known
FoursquareID string `json:"foursquare_id,omitempty"`
// Url of the thumbnail for the result
ThumbURL string `json:"thumb_url,omitempty"`
// Latitude of the venue location in degrees
Latitude float32 `json:"latitude"`
// Longitude of the venue location in degrees
Longitude float32 `json:"longitude"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
// Content of the message to be sent instead of the venue
InputMessageContent interface{} `json:"input_message_content,omitempty"`
// Url of the thumbnail for the result
ThumbURL string `json:"thumb_url,omitempty"`
// Thumbnail width
ThumbWidth int `json:"thumb_width,omitempty"`
@ -1457,15 +1386,15 @@ type (
// Contact's last name
LastName string `json:"last_name,omitempty"`
// Url of the thumbnail for the result
ThumbURL string `json:"thumb_url,omitempty"`
// Inline keyboard attached to the message
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
// Content of the message to be sent instead of the contact
InputMessageContent interface{} `json:"input_message_content,omitempty"`
// Url of the thumbnail for the result
ThumbURL string `json:"thumb_url,omitempty"`
// Thumbnail width
ThumbWidth int `json:"thumb_width,omitempty"`
@ -1806,12 +1735,6 @@ type (
// The unique identifier for the result that was chosen
ResultID string `json:"result_id"`
// The user that chose the result
From *User `json:"from"`
// Sender location, only for bots that require user location
Location *Location `json:"location,omitempty"`
// Identifier of the sent inline message. Available only if there is an
// inline keyboard attached to the message. Will be also received in
// callback queries and can be used to edit the message.
@ -1819,6 +1742,12 @@ type (
// The query that was used to obtain the result
Query string `json:"query"`
// The user that chose the result
From *User `json:"from"`
// Sender location, only for bots that require user location
Location *Location `json:"location,omitempty"`
}
// LabeledPrice represents a portion of the price for goods or services.
@ -1910,6 +1839,18 @@ type (
// Three-letter ISO 4217 currency code
Currency string `json:"currency"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// Identifier of the shipping option chosen by the user
ShippingOptionID string `json:"shipping_option_id,omitempty"`
// Telegram payment identifier
TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
// Provider payment identifier
ProviderPaymentChargeID string `json:"provider_payment_charge_id"`
// Total price in the smallest units of the currency (integer, not
// float/double). For example, for a price of US$ 1.45 pass amount = 145.
// See the exp parameter in currencies.json, it shows the number of
@ -1917,20 +1858,8 @@ type (
// of currencies).
TotalAmount int `json:"total_amount"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// Identifier of the shipping option chosen by the user
ShippingOptionID string `json:"shipping_option_id,omitempty"`
// Order info provided by the user
OrderInfo *OrderInfo `json:"order_info,omitempty"`
// Telegram payment identifier
TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
// Provider payment identifier
ProviderPaymentChargeID string `json:"provider_payment_charge_id"`
}
// ShippingQuery contains information about an incoming shipping query.
@ -1938,12 +1867,12 @@ type (
// Unique query identifier
ID string `json:"id"`
// User who sent the query
From *User `json:"from"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// User who sent the query
From *User `json:"from"`
// User specified shipping address
ShippingAddress *ShippingAddress `json:"shipping_address"`
}
@ -1953,12 +1882,18 @@ type (
// Unique query identifier
ID string `json:"id"`
// User who sent the query
From *User `json:"from"`
// Three-letter ISO 4217 currency code
Currency string `json:"currency"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// Identifier of the shipping option chosen by the user
ShippingOptionID string `json:"shipping_option_id,omitempty"`
// User who sent the query
From *User `json:"from"`
// Total price in the smallest units of the currency (integer, not
// float/double). For example, for a price of US$ 1.45 pass amount = 145.
// See the exp parameter in currencies.json, it shows the number of
@ -1966,12 +1901,6 @@ type (
// currencies).
TotalAmount int `json:"total_amount"`
// Bot specified invoice payload
InvoicePayload string `json:"invoice_payload"`
// Identifier of the shipping option chosen by the user
ShippingOptionID string `json:"shipping_option_id,omitempty"`
// Order info provided by the user
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
@ -1985,15 +1914,15 @@ type (
// Description of the game
Description string `json:"description"`
// Photo that will be displayed in the game message in chats.
Photo []PhotoSize `json:"photo"`
// Brief description of the game or high scores included in the game
// message. Can be automatically edited to include current high scores
// for the game when the bot calls setGameScore, or manually edited
// using editMessageText. 0-4096 characters.
Text string `json:"text,omitempty"`
// Photo that will be displayed in the game message in chats.
Photo []PhotoSize `json:"photo"`
// Special entities that appear in text, such as usernames, URLs, bot
// commands, etc.
TextEntities []MessageEntity `json:"text_entities,omitempty"`
@ -2010,15 +1939,15 @@ type (
// Unique file identifier
FileID string `json:"file_id"`
// Animation thumbnail as defined by sender
Thumb *PhotoSize `json:"thumb,omitempty"`
// Original animation filename as defined by sender
FileName string `json:"file_name,omitempty"`
// MIME type of the file as defined by sender
MimeType string `json:"mime_type,omitempty"`
// Animation thumbnail as defined by sender
Thumb *PhotoSize `json:"thumb,omitempty"`
// File size
FileSize int `json:"file_size,omitempty"`
}
@ -2032,11 +1961,11 @@ type (
// Position in high score table for the game
Position int `json:"position"`
// User
User *User `json:"user"`
// Score
Score int `json:"score"`
// User
User *User `json:"user"`
}
// ReplyMarkup is a JSON-serialized object for an inline keyboard, custom

View File

@ -1,14 +0,0 @@
package telegram
type Bot struct {
AccessToken string
Self *User
}
func NewBot(accessToken string) (*Bot, error) {
var err error
bot := &Bot{AccessToken: accessToken}
bot.Self, err = bot.GetMe()
return bot, err
}

View File

@ -19,7 +19,7 @@ func (bot *Bot) PinChatMessage(params *PinChatMessageParameters) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "pinChatMessage")
resp, err := bot.request(dst, MethodPinChatMessage)
if err != nil {
return false, err
}

View File

@ -24,7 +24,7 @@ func (bot *Bot) SendChatAction(chatID int64, action string) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "sendChatAction")
resp, err := bot.request(dst, MethodSendChatAction)
if err != nil {
return false, err
}

View File

@ -43,7 +43,7 @@ func (bot *Bot) SendContact(params *SendContactParameters) (*Message, error) {
return nil, err
}
resp, err := bot.request(dst, "sendContact")
resp, err := bot.request(dst, MethodSendContact)
if err != nil {
return nil, err
}

View File

@ -66,7 +66,7 @@ func (bot *Bot) SendDocument(params *SendDocumentParameters) (*Message, error) {
args.Add("disable_notification", strconv.FormatBool(params.DisableNotification))
resp, err := bot.upload(params.Document, "document", "", "sendDocument", args)
resp, err := bot.Upload(MethodSendDocument, "document", "", params.Document, args)
if err != nil {
return nil, err
}

View File

@ -37,7 +37,7 @@ func (bot *Bot) SendGame(params *SendGameParameters) (*Message, error) {
return nil, err
}
resp, err := bot.request(dst, "sendGame")
resp, err := bot.request(dst, MethodSendGame)
if err != nil {
return nil, err
}

View File

@ -26,10 +26,6 @@ type SendInvoiceParameters struct {
// Three-letter ISO 4217 currency code, see more on currencies
Currency string `json:"currency"`
// Price breakdown, a list of components (e.g. product price, tax, discount,
// delivery cost, delivery tax, bonus, etc.)
Prices []LabeledPrice `json:"prices"`
// JSON-encoded data about the invoice, which will be shared with the payment
// provider. A detailed description of required fields should be provided by
// the payment provider.
@ -40,6 +36,10 @@ type SendInvoiceParameters struct {
// they are paying for.
PhotoURL string `json:"photo_url,omitempty"`
// Price breakdown, a list of components (e.g. product price, tax, discount,
// delivery cost, delivery tax, bonus, etc.)
Prices []LabeledPrice `json:"prices"`
// Photo size
PhotoSize int `json:"photo_size,omitempty"`
@ -49,6 +49,9 @@ type SendInvoiceParameters struct {
// Photo height
PhotoHeight int `json:"photo_height,omitempty"`
// If the message is a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
// Pass True, if you require the user's full name to complete the order
NeedName bool `json:"need_name,omitempty"`
@ -69,9 +72,6 @@ type SendInvoiceParameters struct {
// sound.
DisableNotification bool `json:"disable_notification,omitempty"`
// If the message is a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one 'Pay total
// price' button will be shown. If not empty, the first button must be a Pay
// button.
@ -98,7 +98,7 @@ func (bot *Bot) SendInvoice(params *SendInvoiceParameters) (*Message, error) {
return nil, err
}
resp, err := bot.request(dst, "sendInvoice")
resp, err := bot.request(dst, MethodSendInvoice)
if err != nil {
return nil, err
}

View File

@ -16,13 +16,13 @@ type SendLocationParameters struct {
// Locations), should be between 60 and 86400.
LivePeriod int `json:"live_period,omitempty"`
// If the message is a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
// Sends the message silently. Users will receive a notification with no
// sound.
DisableNotification bool `json:"disable_notification,omitempty"`
// If the message is a reply, ID of the original message
ReplyToMessageID int `json:"reply_to_message_id,omitempty"`
// A JSON-serialized object for an inline keyboard. If empty, one 'Pay total
// price' button will be shown. If not empty, the first button must be a Pay
// button.
@ -44,7 +44,7 @@ func (bot *Bot) SendLocation(params *SendLocationParameters) (*Message, error) {
return nil, err
}
resp, err := bot.request(dst, "sendLocation")
resp, err := bot.request(dst, MethodSendLocation)
if err != nil {
return nil, err
}

View File

@ -33,7 +33,7 @@ func (bot *Bot) SendMediaGroup(params *SendMediaGroupParameters) ([]Message, err
return nil, err
}
resp, err := bot.request(dst, "sendMediaGroup")
resp, err := bot.request(dst, MethodSendMediaGroup)
if err != nil {
return nil, err
}

View File

@ -44,7 +44,7 @@ func (bot *Bot) SendMessage(params *SendMessageParameters) (*Message, error) {
return nil, err
}
resp, err := bot.request(dst, "sendMessage")
resp, err := bot.request(dst, MethodSendMessage)
if err != nil {
return nil, err
}

View File

@ -72,7 +72,7 @@ func (bot *Bot) SendPhoto(params *SendPhotoParameters) (*Message, error) {
args.Add("disable_notification", strconv.FormatBool(params.DisableNotification))
resp, err := bot.upload(params.Photo, "photo", "", "sendPhoto", args)
resp, err := bot.Upload(MethodSendPhoto, "photo", "", params.Photo, args)
if err != nil {
return nil, err
}

View File

@ -51,7 +51,7 @@ func (bot *Bot) SendVenue(params *SendVenueParameters) (*Message, error) {
return nil, err
}
resp, err := bot.request(dst, "sendVenue")
resp, err := bot.request(dst, MethodSendVenue)
if err != nil {
return nil, err
}

View File

@ -21,7 +21,7 @@ func (bot *Bot) SetChatDescription(chatID int64, description string) (bool, erro
return false, err
}
resp, err := bot.request(dst, "setChatDescription")
resp, err := bot.request(dst, MethodSetChatDescription)
if err != nil {
return false, err
}

View File

@ -25,7 +25,7 @@ func (bot *Bot) SetChatPhoto(chatID int64, chatPhoto interface{}) (bool, error)
defer http.ReleaseArgs(args)
args.Add("chat_id", strconv.FormatInt(chatID, 10))
resp, err := bot.upload(chatPhoto, TypePhoto, "chat_photo", "setChatPhoto", args)
resp, err := bot.Upload(MethodSetChatPhoto, TypePhoto, "chat_photo", chatPhoto, args)
if err != nil {
return false, err
}

View File

@ -22,7 +22,7 @@ func (bot *Bot) SetChatStickerSet(chatID int64, stickerSetName string) (bool, er
return false, err
}
resp, err := bot.request(dst, "setChatStickerSet")
resp, err := bot.request(dst, MethodSetChatStickerSet)
if err != nil {
return false, err
}

View File

@ -24,7 +24,7 @@ func (bot *Bot) SetChatTitle(chatID int64, title string) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "setChatTitle")
resp, err := bot.request(dst, MethodSetChatTitle)
if err != nil {
return false, err
}

View File

@ -9,6 +9,10 @@ type SetGameScoreParameters struct {
// New score, must be non-negative
Score int `json:"score"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Pass True, if the high score is allowed to decrease. This can be useful
// when fixing mistakes or banning cheaters
Force bool `json:"force,omitempty"`
@ -21,10 +25,6 @@ type SetGameScoreParameters struct {
// target chat
ChatID int64 `json:"chat_id,omitempty"`
// Required if inline_message_id is not specified. Identifier of the sent
// message
MessageID int `json:"message_id,omitempty"`
// Required if chat_id and message_id are not specified. Identifier of the
// inline message
InlineMessageID string `json:"inline_message_id,omitempty"`
@ -47,7 +47,7 @@ func (bot *Bot) SetGameScore(params *SetGameScoreParameters) (*Message, error) {
return nil, err
}
resp, err := bot.request(dst, "setGameScore")
resp, err := bot.request(dst, MethodSetGameScore)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,7 @@ func (bot *Bot) SetStickerPositionInSet(sticker string, position int) (bool, err
return false, err
}
resp, err := bot.request(dst, "setStickerPositionInSet")
resp, err := bot.request(dst, MethodSetStickerPositionInSet)
if err != nil {
return false, err
}

View File

@ -8,8 +8,6 @@ import (
http "github.com/valyala/fasthttp"
)
const setWebhook = "setWebhook"
type SetWebhookParameters struct {
// HTTPS url to send updates to. Use an empty string to remove webhook
// integration
@ -72,16 +70,15 @@ func (bot *Bot) SetWebhook(params *SetWebhookParameters) (bool, error) {
resp *Response
)
if params.Certificate != nil {
resp, err = bot.upload(
params.Certificate, "certificate", "cert.pem", setWebhook, args,
)
resp, err = bot.Upload(MethodSetWebhook, "certificate", "cert.pem", params.Certificate, args)
} else {
dst, err := json.Marshal(params)
var dst []byte
dst, err = json.Marshal(params)
if err != nil {
return false, err
}
resp, err = bot.request(dst, setWebhook)
resp, err = bot.request(dst, MethodSetWebhook)
}
if err != nil {
return false, err

View File

@ -8,12 +8,12 @@ import (
func TestGetMe(t *testing.T) {
var err error
bot.Self, err = bot.GetMe()
bot.User, err = bot.GetMe()
if err != nil {
t.Error(err.Error())
t.FailNow()
}
if bot.Self == nil {
if bot.User == nil {
t.Error("unexpected result: bot user is nil")
t.FailNow()
}

View File

@ -8,13 +8,13 @@ import (
)
const (
photoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC"
documentFileID = "BQADAgADOQADjMcoCcioX1GrDvp3Ag"
audioFileID = "BQADAgADRgADjMcoCdXg3lSIN49lAg"
voiceFileID = "AwADAgADWQADjMcoCeul6r_q52IyAg"
videoFileID = "BAADAgADZgADjMcoCav432kYe0FRAg"
videoNoteFileID = "DQADAgADdQAD70cQSUK41dLsRMqfAg"
stickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg"
photoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC"
documentFileID = "BQADAgADOQADjMcoCcioX1GrDvp3Ag"
// audioFileID = "BQADAgADRgADjMcoCdXg3lSIN49lAg"
// voiceFileID = "AwADAgADWQADjMcoCeul6r_q52IyAg"
// videoFileID = "BAADAgADZgADjMcoCav432kYe0FRAg"
// videoNoteFileID = "DQADAgADdQAD70cQSUK41dLsRMqfAg"
// stickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg"
)
var (

View File

@ -22,7 +22,7 @@ func (bot *Bot) UnbanChatMember(chatID int64, userID int) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "unbanChatMember")
resp, err := bot.request(dst, MethodUnbanChatMember)
if err != nil {
return false, err
}

View File

@ -16,7 +16,7 @@ func (bot *Bot) UnpinChatMessage(chatID int64) (bool, error) {
return false, err
}
resp, err := bot.request(dst, "unpinChatMessage")
resp, err := bot.request(dst, MethodUnpinChatMessage)
if err != nil {
return false, err
}

View File

@ -17,7 +17,7 @@ import (
var ErrBadFileType = errors.New("bad file type")
/*
upload is a helper method which provide are three ways to send files (photos, stickers, audio,
Upload is a helper method which provide are three ways to send files (photos, stickers, audio,
media, etc.):
1. If the file is already stored somewhere on the Telegram servers, you don't need to reupload it:
@ -45,7 +45,7 @@ sendAudio, etc.).
voice notes will be sent as files.
- Other configurations may work but we can't guarantee that they will.
*/
func (bot *Bot) upload(file InputFile, fieldName, fileName, method string, args *http.Args) (*Response, error) {
func (bot *Bot) Upload(method, key, name string, file InputFile, args fmt.Stringer) (*Response, error) {
buffer := bytes.NewBuffer(nil)
multi := multipart.NewWriter(buffer)
@ -66,49 +66,21 @@ func (bot *Bot) upload(file InputFile, fieldName, fileName, method string, args
}
}
switch f := file.(type) {
switch src := file.(type) {
case string:
if _, err = os.Stat(f); os.IsNotExist(err) {
// Send by 'file_id'
if err = multi.WriteField(fieldName, f); err != nil {
return nil, err
}
} else {
// Upload new
src, err := os.Open(f)
if err != nil {
return nil, err
}
defer src.Close()
formFile, err := multi.CreateFormFile(fieldName, src.Name())
if err != nil {
return nil, err
}
if _, err = io.Copy(formFile, src); err != nil {
return nil, err
}
}
case []byte: // Upload new
formFile, err := multi.CreateFormFile(fieldName, fileName)
if err != nil {
return nil, err
}
if _, err = io.Copy(formFile, bytes.NewReader(f)); err != nil {
return nil, err
}
err = uploadByString(multi, key, src)
case *url.URL: // Send by URL
if err = multi.WriteField(fieldName, f.String()); err != nil {
return nil, err
}
err = uploadFromURL(multi, key, src)
case []byte: // Upload new
err = uploadFromMemory(multi, key, name, bytes.NewReader(src))
case io.Reader: // Upload new
if _, err = multi.CreateFormFile(fieldName, fileName); err != nil {
return nil, err
}
err = uploadFromMemory(multi, key, name, src)
default:
return nil, ErrBadFileType
}
if err != nil {
return nil, err
}
if err = multi.Close(); err != nil {
return nil, err
@ -149,3 +121,51 @@ func (bot *Bot) upload(file InputFile, fieldName, fileName, method string, args
return &data, nil
}
func uploadByString(w *multipart.Writer, key, src string) error {
_, err := os.Stat(src)
switch {
case os.IsNotExist(err):
err = uploadFromFileID(w, key, src)
case os.IsExist(err):
err = uploadFromDisk(w, key, src)
}
return err
}
func uploadFromFileID(w *multipart.Writer, key, src string) error {
return w.WriteField(key, src)
}
func uploadFromDisk(w *multipart.Writer, key, src string) error {
file, err := os.Open(src)
if err != nil {
return err
}
defer func() {
_ = file.Close()
}()
var formFile io.Writer
formFile, err = w.CreateFormFile(key, file.Name())
if err != nil {
return err
}
_, err = io.Copy(formFile, file)
return err
}
func uploadFromURL(w *multipart.Writer, key string, src *url.URL) error {
return w.WriteField(key, src.String())
}
func uploadFromMemory(w *multipart.Writer, key, value string, src io.Reader) error {
field, err := w.CreateFormFile(key, value)
if err != nil {
return err
}
_, err = io.Copy(field, src)
return err
}

23
utils.go Normal file
View File

@ -0,0 +1,23 @@
package telegram
import (
"net/url"
"strconv"
)
func NewForceReply() *ForceReply {
return &ForceReply{ForceReply: true}
}
func NewInlineMentionURL(userID int) *url.URL {
link := &url.URL{
Scheme: SchemeTelegram,
Path: "user",
}
q := link.Query()
q.Add("id", strconv.Itoa(userID))
link.RawQuery = q.Encode()
return link
}

View File

@ -6,16 +6,29 @@ import (
"strings"
)
type Bot struct {
AccessToken string
*User
}
func New(accessToken string) (*Bot, error) {
var err error
bot := &Bot{AccessToken: accessToken}
bot.User, err = bot.GetMe()
return bot, err
}
func (bot *Bot) IsMessageFromMe(msg *Message) bool {
if msg == nil || bot == nil {
return false
}
if msg.From == nil || bot.Self == nil {
if msg.From == nil || bot.User == nil {
return false
}
return msg.From.ID == bot.Self.ID
return msg.From.ID == bot.ID
}
func (bot *Bot) IsForwardFromMe(msg *Message) bool {
@ -27,11 +40,11 @@ func (bot *Bot) IsForwardFromMe(msg *Message) bool {
return false
}
if bot.Self == nil {
if bot.User == nil {
return false
}
return msg.ForwardFrom.ID == bot.Self.ID
return msg.ForwardFrom.ID == bot.ID
}
func (bot *Bot) IsReplyToMe(msg *Message) bool {
@ -60,7 +73,7 @@ func (bot *Bot) IsCommandToMe(msg *Message) bool {
return false
}
return strings.ToLower(parts[1]) == strings.ToLower(bot.Self.Username)
return strings.ToLower(parts[1]) == strings.ToLower(bot.User.Username)
}
func (bot *Bot) IsMessageMentionsMe(msg *Message) bool {
@ -68,7 +81,7 @@ func (bot *Bot) IsMessageMentionsMe(msg *Message) bool {
return false
}
if bot.Self == nil {
if bot.User == nil {
return false
}
@ -86,7 +99,7 @@ func (bot *Bot) IsMessageMentionsMe(msg *Message) bool {
for _, entity := range entities {
if entity.IsMention() {
if bot.Self.ID == entity.User.ID {
if bot.ID == entity.User.ID {
return true
}
}
@ -143,18 +156,18 @@ func (bot *Bot) NewRedirectURL(group bool, param string) *url.URL {
return nil
}
if bot.Self == nil {
if bot.User == nil {
return nil
}
if bot.Self.Username == "" {
if bot.User.Username == "" {
return nil
}
link := &url.URL{
Scheme: "https",
Host: "t.me",
Path: bot.Self.Username,
Path: bot.User.Username,
}
q := link.Query()

View File

@ -1,5 +1,7 @@
package telegram
import "fmt"
func (chat *Chat) IsPrivate() bool {
if chat == nil {
return false
@ -39,3 +41,40 @@ func (chat *Chat) HasPinnedMessage() bool {
return chat.PinnedMessage != nil
}
func (chat *Chat) HasStickerSet() bool {
if chat == nil {
return false
}
return chat.StickerSetName != ""
}
func (chat *Chat) StickerSet(bot *Bot) *StickerSet {
if !chat.HasStickerSet() {
return nil
}
if bot == nil {
return nil
}
set, err := bot.GetStickerSet(chat.StickerSetName)
if err != nil {
return nil
}
return set
}
func (chat *Chat) FullName() string {
if chat == nil {
return ""
}
if chat.LastName != "" {
return fmt.Sprintln(chat.FirstName, chat.LastName)
}
return chat.FirstName
}

View File

@ -10,28 +10,24 @@ func (entity *MessageEntity) ParseURL(messageText string) *url.URL {
return nil
}
var err error
link := new(url.URL)
switch {
case entity.IsTextLink():
link, err = url.Parse(entity.URL)
case entity.IsURL():
if messageText == "" {
return nil
}
if !entity.IsURL() {
return nil
}
rawMessageText := []rune(messageText)
if len(rawMessageText) < (entity.Offset + entity.Length) {
return nil
}
if messageText == "" {
return nil
}
from := entity.Offset
to := from + entity.Length
rawURL := string([]rune(messageText)[from:to])
link, err = url.Parse(rawURL)
if err == nil && link.Scheme == "" {
link, err = url.Parse(fmt.Sprint("http://", link))
}
from := entity.Offset
to := from + entity.Length
text := []rune(messageText)
if len(text) < to {
return nil
}
link, err := url.Parse(string(text[from:to]))
if err == nil && link.Scheme == "" {
link, err = url.Parse(fmt.Sprint("http://", link))
}
if err != nil {
return nil
@ -127,3 +123,16 @@ func (entity *MessageEntity) IsURL() bool {
return entity.Type == EntityURL
}
func (entity *MessageEntity) TextLink() *url.URL {
if entity == nil {
return nil
}
link, err := url.Parse(entity.URL)
if err != nil {
return nil
}
return link
}

View File

@ -15,7 +15,6 @@ func (msg *Message) IsCommand(command string) bool {
}
entity := msg.Entities[0]
isBotCommand := entity.IsBotCommand() && entity.Offset == 0
if command != "" {
return isBotCommand && strings.EqualFold(msg.Command(), command)
@ -94,18 +93,23 @@ func (msg *Message) ForwardTime() time.Time {
}
func (msg *Message) EditTime() time.Time {
var t time.Time
if msg == nil {
return time.Time{}
return t
}
if !msg.HasBeenEdited() {
return time.Time{}
return t
}
return time.Unix(msg.EditDate, 0)
}
func (msg *Message) HasBeenEdited() bool {
if msg == nil {
return false
}
return msg.EditDate > 0
}
@ -140,6 +144,7 @@ func (msg *Message) IsSticker() bool {
func (msg *Message) IsVideo() bool {
return !msg.IsText() && msg.Video != nil
}
func (msg *Message) IsVoice() bool {
return !msg.IsText() && msg.Voice != nil
}

View File

@ -25,7 +25,7 @@ func (user *User) FullName() string {
}
if user.LastName != "" {
return fmt.Sprint(user.FirstName, " ", user.LastName)
return fmt.Sprintln(user.FirstName, user.LastName)
}
return user.FirstName