mirror of https://github.com/tucnak/telebot
Compare commits
92 Commits
Author | SHA1 | Date |
---|---|---|
Demian | 4d9811176e | 1 week ago |
Demian | cc61a965b4 | 2 months ago |
Demian | 7ee7bd3a21 | 2 months ago |
demget | 898c3e18f9 | 2 months ago |
Nikolay Pavlovich | f1007b7c08 | 2 months ago |
Demian | 4e8d423603 | 2 months ago |
Олегсей | 0442831ecc | 2 months ago |
Demian | 7655e7a9ca | 2 months ago |
Demian | 9c7a18be08 | 2 months ago |
Олегсей | 39d57c70bd | 2 months ago |
Demian | a02de5490f | 2 months ago |
Megum1n | 08f2e85c39 | 2 months ago |
zhongqi | b19cf78b1f | 2 months ago |
Олегсей | e8d6ed91d9 | 2 months ago |
David Shekunts | 14ff5ae5d9 | 2 months ago |
JunJi | e90e62d100 | 2 months ago |
mhrlife | 226075b9d0 | 2 months ago |
Asadbek | 8dd54ae1f6 | 2 months ago |
Taras | f1b0fab4df | 2 months ago |
Maxim Nurmukhametov | af64372b8e | 2 months ago |
Demian | 8b69e47820 | 2 months ago |
Demian | 6713166829 | 2 months ago |
Олегсей | 1c5b5de314 | 2 months ago |
Олегсей | d67653533a | 2 months ago |
Demian | c8882690a2 | 2 months ago |
Олегсей | c37b2b97b3 | 2 months ago |
Demian | ca0a8527e0 | 2 months ago |
Олегсей | ffbf081045 | 2 months ago |
Demian | 864bef4e4d | 2 months ago |
Demian | 2c98c59344 | 2 months ago |
Demian | a8acf84301 | 4 months ago |
Demian | 10fbde9b66 | 4 months ago |
Demian | 1acc928a92 | 4 months ago |
Demian | c9cec214a9 | 4 months ago |
Demian | c4fce10013 | 4 months ago |
Demian | 47227931ec | 4 months ago |
Nash-Well | e0c24df69d | 4 months ago |
Nash-Well | 77f77a6840 | 4 months ago |
Demian | a26ba9f0bd | 6 months ago |
Demian | af50a953b5 | 6 months ago |
nash | e16cc083f7 | 7 months ago |
Demian | 35d5dd372a | 7 months ago |
Demian | 9eb53434a0 | 7 months ago |
Nash-Well | 6fdf666a11 | 7 months ago |
Nikita | b67df6444e | 7 months ago |
Nikita | 260a67b811 | 7 months ago |
Nikita | cb88a11861 | 7 months ago |
Demian | 0d0d5e7473 | 7 months ago |
Patrick | e4f891ba6c | 7 months ago |
Nash-Well | af31945794 | 7 months ago |
Nash-Well | 5ff8b89b2b | 7 months ago |
Nikita | df250e64a2 | 7 months ago |
Nash-Well | 4670ccf5a3 | 7 months ago |
Demian | 435ad606fa | 7 months ago |
Nash-Well | 32b47a49ef | 7 months ago |
Reza Alipour | 9c4cc7713e | 7 months ago |
Reza Alipour | 49db4f5fac | 7 months ago |
70sh1 | 06bef0a71e | 7 months ago |
落心 | 59775c24a0 | 7 months ago |
david | 4cce303fbc | 7 months ago |
Demian | 71ac2995cc | 10 months ago |
Demian | b1cf07b13d | 11 months ago |
Demian | 10d3480782 | 11 months ago |
Demian | 06bbf9e69f | 11 months ago |
Demian | 7b08a8f939 | 11 months ago |
Mikhail | e6590d806a | 11 months ago |
Demian | 78610849e1 | 11 months ago |
Demian | cd009a68b3 | 11 months ago |
Vlad Lukyanov | 270e53bb90 | 11 months ago |
Demian | 76f1c0eef8 | 11 months ago |
awohsen | 4e31cc6eac | 11 months ago |
George | 3c4d8425f4 | 11 months ago |
Demian | c581e8389b | 11 months ago |
Nikita | fb8ce2a4b1 | 11 months ago |
Demian | 03dcac73d8 | 11 months ago |
Demian | d883371ded | 11 months ago |
Nikita | 5d4079a489 | 11 months ago |
demget | 2f38e4952f | 11 months ago |
Demian | 5f0d89877d | 11 months ago |
Demian | 48c941a8d9 | 11 months ago |
demget | bb76a9a642 | 11 months ago |
demget | 699e5dbbdd | 1 year ago |
demget | de01d4553e | 1 year ago |
Akionka | eb290fd947 | 1 year ago |
Max Kuznetsov | bae47b52d4 | 2 years ago |
demget | fbd35f2103 | 2 years ago |
Demian | 92739f1414 | 2 years ago |
demget | 8a14d8a96b | 2 years ago |
Ruslan | 0b9f842a27 | 2 years ago |
Demian | ad84cbde5a | 2 years ago |
Demian | 4c17ca7dc7 | 2 years ago |
Alex Osinniy | c2b50e878f | 2 years ago |
@ -1,331 +1,118 @@
|
|||||||
package telebot
|
package telebot
|
||||||
|
|
||||||
import (
|
import "io"
|
||||||
"bytes"
|
|
||||||
"context"
|
// API is the interface that wraps all basic methods for interacting
|
||||||
"encoding/json"
|
// with Telegram Bot API.
|
||||||
"fmt"
|
type API interface {
|
||||||
"io"
|
Raw(method string, payload interface{}) ([]byte, error)
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
Accept(query *PreCheckoutQuery, errorMessage ...string) error
|
||||||
"mime/multipart"
|
AddStickerToSet(of Recipient, name string, sticker InputSticker) error
|
||||||
"net/http"
|
AdminsOf(chat *Chat) ([]ChatMember, error)
|
||||||
"os"
|
Answer(query *Query, resp *QueryResponse) error
|
||||||
"strconv"
|
AnswerWebApp(query *Query, r Result) (*WebAppMessage, error)
|
||||||
"strings"
|
ApproveJoinRequest(chat Recipient, user *User) error
|
||||||
"time"
|
Ban(chat *Chat, member *ChatMember, revokeMessages ...bool) error
|
||||||
)
|
BanSenderChat(chat *Chat, sender Recipient) error
|
||||||
|
BusinessConnection(id string) (*BusinessConnection, error)
|
||||||
// Raw lets you call any method of Bot API manually.
|
ChatByID(id int64) (*Chat, error)
|
||||||
// It also handles API errors, so you only need to unwrap
|
ChatByUsername(name string) (*Chat, error)
|
||||||
// result field from json data.
|
ChatMemberOf(chat, user Recipient) (*ChatMember, error)
|
||||||
func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) {
|
Close() (bool, error)
|
||||||
url := b.URL + "/bot" + b.Token + "/" + method
|
CloseGeneralTopic(chat *Chat) error
|
||||||
|
CloseTopic(chat *Chat, topic *Topic) error
|
||||||
var buf bytes.Buffer
|
Commands(opts ...interface{}) ([]Command, error)
|
||||||
if err := json.NewEncoder(&buf).Encode(payload); err != nil {
|
Copy(to Recipient, msg Editable, opts ...interface{}) (*Message, error)
|
||||||
return nil, err
|
CopyMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error)
|
||||||
}
|
CreateInviteLink(chat Recipient, link *ChatInviteLink) (*ChatInviteLink, error)
|
||||||
|
CreateInvoiceLink(i Invoice) (string, error)
|
||||||
// Cancel the request immediately without waiting for the timeout when bot is about to stop.
|
CreateStickerSet(of Recipient, set *StickerSet) error
|
||||||
// This may become important if doing long polling with long timeout.
|
CreateTopic(chat *Chat, topic *Topic) (*Topic, error)
|
||||||
exit := make(chan struct{})
|
CustomEmojiStickers(ids []string) ([]Sticker, error)
|
||||||
defer close(exit)
|
DeclineJoinRequest(chat Recipient, user *User) error
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
DefaultRights(forChannels bool) (*Rights, error)
|
||||||
defer cancel()
|
Delete(msg Editable) error
|
||||||
|
DeleteCommands(opts ...interface{}) error
|
||||||
go func() {
|
DeleteGroupPhoto(chat *Chat) error
|
||||||
select {
|
DeleteGroupStickerSet(chat *Chat) error
|
||||||
case <-b.stopClient:
|
DeleteMany(msgs []Editable) error
|
||||||
cancel()
|
DeleteSticker(sticker string) error
|
||||||
case <-exit:
|
DeleteStickerSet(name string) error
|
||||||
}
|
DeleteTopic(chat *Chat, topic *Topic) error
|
||||||
}()
|
Download(file *File, localFilename string) error
|
||||||
|
Edit(msg Editable, what interface{}, opts ...interface{}) (*Message, error)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, &buf)
|
EditCaption(msg Editable, caption string, opts ...interface{}) (*Message, error)
|
||||||
if err != nil {
|
EditGeneralTopic(chat *Chat, topic *Topic) error
|
||||||
return nil, wrapError(err)
|
EditInviteLink(chat Recipient, link *ChatInviteLink) (*ChatInviteLink, error)
|
||||||
}
|
EditMedia(msg Editable, media Inputtable, opts ...interface{}) (*Message, error)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, error)
|
||||||
|
EditTopic(chat *Chat, topic *Topic) error
|
||||||
resp, err := b.client.Do(req)
|
File(file *File) (io.ReadCloser, error)
|
||||||
if err != nil {
|
FileByID(fileID string) (File, error)
|
||||||
return nil, wrapError(err)
|
Forward(to Recipient, msg Editable, opts ...interface{}) (*Message, error)
|
||||||
}
|
ForwardMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error)
|
||||||
resp.Close = true
|
GameScores(user Recipient, msg Editable) ([]GameHighScore, error)
|
||||||
defer resp.Body.Close()
|
HideGeneralTopic(chat *Chat) error
|
||||||
|
InviteLink(chat *Chat) (string, error)
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
Leave(chat Recipient) error
|
||||||
if err != nil {
|
Len(chat *Chat) (int, error)
|
||||||
return nil, wrapError(err)
|
Logout() (bool, error)
|
||||||
}
|
MenuButton(chat *User) (*MenuButton, error)
|
||||||
|
MyDescription(language string) (*BotInfo, error)
|
||||||
if b.verbose {
|
MyName(language string) (*BotInfo, error)
|
||||||
verbose(method, payload, data)
|
MyShortDescription(language string) (*BotInfo, error)
|
||||||
}
|
Notify(to Recipient, action ChatAction, threadID ...int) error
|
||||||
|
Pin(msg Editable, opts ...interface{}) error
|
||||||
// returning data as well
|
ProfilePhotosOf(user *User) ([]Photo, error)
|
||||||
return data, extractOk(data)
|
Promote(chat *Chat, member *ChatMember) error
|
||||||
}
|
React(to Recipient, msg Editable, r Reactions) error
|
||||||
|
RefundStars(to Recipient, chargeID string) error
|
||||||
func (b *Bot) sendFiles(method string, files map[string]File, params map[string]string) ([]byte, error) {
|
RemoveWebhook(dropPending ...bool) error
|
||||||
rawFiles := make(map[string]interface{})
|
ReopenGeneralTopic(chat *Chat) error
|
||||||
for name, f := range files {
|
ReopenTopic(chat *Chat, topic *Topic) error
|
||||||
switch {
|
ReplaceStickerInSet(of Recipient, stickerSet, oldSticker string, sticker InputSticker) (bool, error)
|
||||||
case f.InCloud():
|
Reply(to *Message, what interface{}, opts ...interface{}) (*Message, error)
|
||||||
params[name] = f.FileID
|
Respond(c *Callback, resp ...*CallbackResponse) error
|
||||||
case f.FileURL != "":
|
Restrict(chat *Chat, member *ChatMember) error
|
||||||
params[name] = f.FileURL
|
RevokeInviteLink(chat Recipient, link string) (*ChatInviteLink, error)
|
||||||
case f.OnDisk():
|
Send(to Recipient, what interface{}, opts ...interface{}) (*Message, error)
|
||||||
rawFiles[name] = f.FileLocal
|
SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message, error)
|
||||||
case f.FileReader != nil:
|
SendPaid(to Recipient, stars int, a PaidAlbum, opts ...interface{}) (*Message, error)
|
||||||
rawFiles[name] = f.FileReader
|
SetAdminTitle(chat *Chat, user *User, title string) error
|
||||||
default:
|
SetCommands(opts ...interface{}) error
|
||||||
return nil, fmt.Errorf("telebot: file for field %s doesn't exist", name)
|
SetCustomEmojiStickerSetThumb(name, id string) error
|
||||||
}
|
SetDefaultRights(rights Rights, forChannels bool) error
|
||||||
}
|
SetGameScore(user Recipient, msg Editable, score GameHighScore) (*Message, error)
|
||||||
|
SetGroupDescription(chat *Chat, description string) error
|
||||||
if len(rawFiles) == 0 {
|
SetGroupPermissions(chat *Chat, perms Rights) error
|
||||||
return b.Raw(method, params)
|
SetGroupStickerSet(chat *Chat, setName string) error
|
||||||
}
|
SetGroupTitle(chat *Chat, title string) error
|
||||||
|
SetMenuButton(chat *User, mb interface{}) error
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
SetMyDescription(desc, language string) error
|
||||||
writer := multipart.NewWriter(pipeWriter)
|
SetMyName(name, language string) error
|
||||||
|
SetMyShortDescription(desc, language string) error
|
||||||
go func() {
|
SetStickerEmojis(sticker string, emojis []string) error
|
||||||
defer pipeWriter.Close()
|
SetStickerKeywords(sticker string, keywords []string) error
|
||||||
|
SetStickerMaskPosition(sticker string, mask MaskPosition) error
|
||||||
for field, file := range rawFiles {
|
SetStickerPosition(sticker string, position int) error
|
||||||
if err := addFileToWriter(writer, files[field].fileName, field, file); err != nil {
|
SetStickerSetThumb(of Recipient, set *StickerSet) error
|
||||||
pipeWriter.CloseWithError(err)
|
SetStickerSetTitle(s StickerSet) error
|
||||||
return
|
SetWebhook(w *Webhook) error
|
||||||
}
|
Ship(query *ShippingQuery, what ...interface{}) error
|
||||||
}
|
StarTransactions(offset, limit int) ([]StarTransaction, error)
|
||||||
for field, value := range params {
|
StickerSet(name string) (*StickerSet, error)
|
||||||
if err := writer.WriteField(field, value); err != nil {
|
StopLiveLocation(msg Editable, opts ...interface{}) (*Message, error)
|
||||||
pipeWriter.CloseWithError(err)
|
StopPoll(msg Editable, opts ...interface{}) (*Poll, error)
|
||||||
return
|
TopicIconStickers() ([]Sticker, error)
|
||||||
}
|
Unban(chat *Chat, user *User, forBanned ...bool) error
|
||||||
}
|
UnbanSenderChat(chat *Chat, sender Recipient) error
|
||||||
if err := writer.Close(); err != nil {
|
UnhideGeneralTopic(chat *Chat) error
|
||||||
pipeWriter.CloseWithError(err)
|
Unpin(chat Recipient, messageID ...int) error
|
||||||
return
|
UnpinAll(chat Recipient) error
|
||||||
}
|
UnpinAllGeneralTopicMessages(chat *Chat) error
|
||||||
}()
|
UnpinAllTopicMessages(chat *Chat, topic *Topic) error
|
||||||
|
UploadSticker(to Recipient, format StickerSetFormat, f File) (*File, error)
|
||||||
url := b.URL + "/bot" + b.Token + "/" + method
|
UserBoosts(chat, user Recipient) ([]Boost, error)
|
||||||
|
Webhook() (*Webhook, error)
|
||||||
resp, err := b.client.Post(url, writer.FormDataContentType(), pipeReader)
|
|
||||||
if err != nil {
|
|
||||||
err = wrapError(err)
|
|
||||||
pipeReader.CloseWithError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Close = true
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusInternalServerError {
|
|
||||||
return nil, ErrInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, extractOk(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addFileToWriter(writer *multipart.Writer, filename, field string, file interface{}) error {
|
|
||||||
var reader io.Reader
|
|
||||||
if r, ok := file.(io.Reader); ok {
|
|
||||||
reader = r
|
|
||||||
} else if path, ok := file.(string); ok {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
reader = f
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("telebot: file for field %v should be io.ReadCloser or string", field)
|
|
||||||
}
|
|
||||||
|
|
||||||
part, err := writer.CreateFormFile(field, filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(part, reader)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bot) sendText(to Recipient, text string, opt *SendOptions) (*Message, error) {
|
|
||||||
params := map[string]string{
|
|
||||||
"chat_id": to.Recipient(),
|
|
||||||
"text": text,
|
|
||||||
}
|
|
||||||
b.embedSendOptions(params, opt)
|
|
||||||
|
|
||||||
data, err := b.Raw("sendMessage", params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return extractMessage(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bot) sendMedia(media Media, params map[string]string, files map[string]File) (*Message, error) {
|
|
||||||
kind := media.MediaType()
|
|
||||||
what := "send" + strings.Title(kind)
|
|
||||||
|
|
||||||
if kind == "videoNote" {
|
|
||||||
kind = "video_note"
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFiles := map[string]File{kind: *media.MediaFile()}
|
|
||||||
for k, v := range files {
|
|
||||||
sendFiles[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := b.sendFiles(what, sendFiles, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return extractMessage(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bot) getMe() (*User, error) {
|
|
||||||
data, err := b.Raw("getMe", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp struct {
|
|
||||||
Result *User
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &resp); err != nil {
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
return resp.Result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bot) getUpdates(offset, limit int, timeout time.Duration, allowed []string) ([]Update, error) {
|
|
||||||
params := map[string]string{
|
|
||||||
"offset": strconv.Itoa(offset),
|
|
||||||
"timeout": strconv.Itoa(int(timeout / time.Second)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if limit != 0 {
|
|
||||||
params["limit"] = strconv.Itoa(limit)
|
|
||||||
}
|
|
||||||
if len(allowed) > 0 {
|
|
||||||
data, _ := json.Marshal(allowed)
|
|
||||||
params["allowed_updates"] = string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := b.Raw("getUpdates", params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp struct {
|
|
||||||
Result []Update
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &resp); err != nil {
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
return resp.Result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractOk checks given result for error. If result is ok returns nil.
|
|
||||||
// In other cases it extracts API error. If error is not presented
|
|
||||||
// in errors.go, it will be prefixed with `unknown` keyword.
|
|
||||||
func extractOk(data []byte) error {
|
|
||||||
var e struct {
|
|
||||||
Ok bool `json:"ok"`
|
|
||||||
Code int `json:"error_code"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Parameters map[string]interface{} `json:"parameters"`
|
|
||||||
}
|
|
||||||
if json.NewDecoder(bytes.NewReader(data)).Decode(&e) != nil {
|
|
||||||
return nil // FIXME
|
|
||||||
}
|
|
||||||
if e.Ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := Err(e.Description)
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
case ErrGroupMigrated:
|
|
||||||
migratedTo, ok := e.Parameters["migrate_to_chat_id"]
|
|
||||||
if !ok {
|
|
||||||
return NewError(e.Code, e.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
return GroupError{
|
|
||||||
err: err.(*Error),
|
|
||||||
MigratedTo: int64(migratedTo.(float64)),
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e.Code {
|
|
||||||
case http.StatusTooManyRequests:
|
|
||||||
retryAfter, ok := e.Parameters["retry_after"]
|
|
||||||
if !ok {
|
|
||||||
return NewError(e.Code, e.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = FloodError{
|
|
||||||
err: NewError(e.Code, e.Description),
|
|
||||||
RetryAfter: int(retryAfter.(float64)),
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("telegram: %s (%d)", e.Description, e.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractMessage extracts common Message result from given data.
|
|
||||||
// Should be called after extractOk or b.Raw() to handle possible errors.
|
|
||||||
func extractMessage(data []byte) (*Message, error) {
|
|
||||||
var resp struct {
|
|
||||||
Result *Message
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &resp); err != nil {
|
|
||||||
var resp struct {
|
|
||||||
Result bool
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &resp); err != nil {
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
if resp.Result {
|
|
||||||
return nil, ErrTrueResult
|
|
||||||
}
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
return resp.Result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func verbose(method string, payload interface{}, data []byte) {
|
|
||||||
body, _ := json.Marshal(payload)
|
|
||||||
body = bytes.ReplaceAll(body, []byte(`\"`), []byte(`"`))
|
|
||||||
body = bytes.ReplaceAll(body, []byte(`"{`), []byte(`{`))
|
|
||||||
body = bytes.ReplaceAll(body, []byte(`}"`), []byte(`}`))
|
|
||||||
|
|
||||||
indent := func(b []byte) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
json.Indent(&buf, b, "", " ")
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf(
|
|
||||||
"[verbose] telebot: sent request\nMethod: %v\nParams: %v\nResponse: %v",
|
|
||||||
method, indent(body), indent(data),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Boost contains information about a chat boost.
|
||||||
|
type Boost struct {
|
||||||
|
// Unique identifier of the boost.
|
||||||
|
ID string `json:"boost_id"`
|
||||||
|
|
||||||
|
// Point in time (Unix timestamp) when the chat was boosted.
|
||||||
|
AddUnixtime int64 `json:"add_date"`
|
||||||
|
|
||||||
|
// Point in time (Unix timestamp) when the boost will automatically expire,
|
||||||
|
// unless the booster's Telegram Premium subscription is prolonged.
|
||||||
|
ExpirationUnixtime int64 `json:"expiration_date"`
|
||||||
|
|
||||||
|
// Source of the added boost.
|
||||||
|
Source *BoostSource `json:"source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDate returns the moment of time when the chat has been boosted in local time.
|
||||||
|
func (c *Boost) AddDate() time.Time {
|
||||||
|
return time.Unix(c.AddUnixtime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpirationDate returns the moment of time when the boost of the channel
|
||||||
|
// will expire in local time.
|
||||||
|
func (c *Boost) ExpirationDate() time.Time {
|
||||||
|
return time.Unix(c.ExpirationUnixtime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoostSourceType describes a type of boost.
|
||||||
|
type BoostSourceType = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BoostPremium BoostSourceType = "premium"
|
||||||
|
BoostGiftCode BoostSourceType = "gift_code"
|
||||||
|
BoostGiveaway BoostSourceType = "giveaway"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BoostSource describes the source of a chat boost.
|
||||||
|
type BoostSource struct {
|
||||||
|
// Source of the boost, always (“premium”, “gift_code”, “giveaway”).
|
||||||
|
Source BoostSourceType `json:"source"`
|
||||||
|
|
||||||
|
// User that boosted the chat.
|
||||||
|
Booster *User `json:"user"`
|
||||||
|
|
||||||
|
// Identifier of a message in the chat with the giveaway; the message
|
||||||
|
// could have been deleted already. May be 0 if the message isn't sent yet.
|
||||||
|
GiveawayMessageID int `json:"giveaway_message_id,omitempty"`
|
||||||
|
|
||||||
|
// (Optional) True, if the giveaway was completed, but there was
|
||||||
|
// no user to win the prize.
|
||||||
|
Unclaimed bool `json:"is_unclaimed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoostAdded represents a service message about a user boosting a chat.
|
||||||
|
type BoostAdded struct {
|
||||||
|
// Number of boosts added by the user.
|
||||||
|
Count int `json:"boost_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoostUpdated represents a boost added to a chat or changed.
|
||||||
|
type BoostUpdated struct {
|
||||||
|
// Chat which was boosted.
|
||||||
|
Chat *Chat `json:"chat"`
|
||||||
|
|
||||||
|
// Information about the chat boost.
|
||||||
|
Boost *Boost `json:"boost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoostRemoved represents a boost removed from a chat.
|
||||||
|
type BoostRemoved struct {
|
||||||
|
// Chat which was boosted.
|
||||||
|
Chat *Chat `json:"chat"`
|
||||||
|
|
||||||
|
// Unique identifier of the boost.
|
||||||
|
BoostID string `json:"boost_id"`
|
||||||
|
|
||||||
|
// Point in time (Unix timestamp) when the boost was removed.
|
||||||
|
RemoveUnixtime int64 `json:"remove_date"`
|
||||||
|
|
||||||
|
// Source of the removed boost.
|
||||||
|
Source *BoostSource `json:"source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserBoosts gets the list of boosts added to a chat by a user.
|
||||||
|
// Requires administrator rights in the chat.
|
||||||
|
func (b *Bot) UserBoosts(chat, user Recipient) ([]Boost, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"user_id": user.Recipient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.Raw("getUserChatBoosts", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result struct {
|
||||||
|
Boosts []Boost `json:"boosts"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result.Boosts, nil
|
||||||
|
}
|
@ -0,0 +1,377 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Raw lets you call any method of Bot API manually.
|
||||||
|
// It also handles API errors, so you only need to unwrap
|
||||||
|
// result field from json data.
|
||||||
|
func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) {
|
||||||
|
url := b.URL + "/bot" + b.Token + "/" + method
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&buf).Encode(payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel the request immediately without waiting for the timeout
|
||||||
|
// when bot is about to stop.
|
||||||
|
// This may become important if doing long polling with long timeout.
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b.stopMu.RLock()
|
||||||
|
stopCh := b.stopClient
|
||||||
|
b.stopMu.RUnlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
cancel()
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := b.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
resp.Close = true
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.verbose {
|
||||||
|
verbose(method, payload, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returning data as well
|
||||||
|
return data, extractOk(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) sendFiles(method string, files map[string]File, params map[string]string) ([]byte, error) {
|
||||||
|
rawFiles := make(map[string]interface{})
|
||||||
|
for name, f := range files {
|
||||||
|
switch {
|
||||||
|
case f.InCloud():
|
||||||
|
params[name] = f.FileID
|
||||||
|
case f.FileURL != "":
|
||||||
|
params[name] = f.FileURL
|
||||||
|
case f.OnDisk():
|
||||||
|
rawFiles[name] = f.FileLocal
|
||||||
|
case f.FileReader != nil:
|
||||||
|
rawFiles[name] = f.FileReader
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("telebot: file for field %s doesn't exist", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rawFiles) == 0 {
|
||||||
|
return b.Raw(method, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
writer := multipart.NewWriter(pipeWriter)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer pipeWriter.Close()
|
||||||
|
|
||||||
|
for field, file := range rawFiles {
|
||||||
|
if err := addFileToWriter(writer, files[field].fileName, field, file); err != nil {
|
||||||
|
pipeWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for field, value := range params {
|
||||||
|
if err := writer.WriteField(field, value); err != nil {
|
||||||
|
pipeWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
pipeWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
url := b.URL + "/bot" + b.Token + "/" + method
|
||||||
|
|
||||||
|
resp, err := b.client.Post(url, writer.FormDataContentType(), pipeReader)
|
||||||
|
if err != nil {
|
||||||
|
err = wrapError(err)
|
||||||
|
pipeReader.CloseWithError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp.Close = true
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusInternalServerError {
|
||||||
|
return nil, ErrInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, extractOk(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFileToWriter(writer *multipart.Writer, filename, field string, file interface{}) error {
|
||||||
|
var reader io.Reader
|
||||||
|
if r, ok := file.(io.Reader); ok {
|
||||||
|
reader = r
|
||||||
|
} else if path, ok := file.(string); ok {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
reader = f
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("telebot: file for field %v should be io.ReadCloser or string", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
part, err := writer.CreateFormFile(field, filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(part, reader)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) process(name string, files map[string]File) string {
|
||||||
|
switch {
|
||||||
|
case f.InCloud():
|
||||||
|
return f.FileID
|
||||||
|
case f.FileURL != "":
|
||||||
|
return f.FileURL
|
||||||
|
case f.OnDisk() || f.FileReader != nil:
|
||||||
|
files[name] = *f
|
||||||
|
return "attach://" + name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) sendText(to Recipient, text string, opt *SendOptions) (*Message, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"chat_id": to.Recipient(),
|
||||||
|
"text": text,
|
||||||
|
}
|
||||||
|
b.embedSendOptions(params, opt)
|
||||||
|
|
||||||
|
data, err := b.Raw("sendMessage", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractMessage(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) sendMedia(media Media, params map[string]string, files map[string]File) (*Message, error) {
|
||||||
|
kind := media.MediaType()
|
||||||
|
what := "send" + strings.Title(kind)
|
||||||
|
|
||||||
|
if kind == "videoNote" {
|
||||||
|
kind = "video_note"
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFiles := map[string]File{kind: *media.MediaFile()}
|
||||||
|
for k, v := range files {
|
||||||
|
sendFiles[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.sendFiles(what, sendFiles, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractMessage(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) getMe() (*User, error) {
|
||||||
|
data, err := b.Raw("getMe", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result *User
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) getUpdates(offset, limit int, timeout time.Duration, allowed []string) ([]Update, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"offset": strconv.Itoa(offset),
|
||||||
|
"timeout": strconv.Itoa(int(timeout / time.Second)),
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(allowed)
|
||||||
|
params["allowed_updates"] = string(data)
|
||||||
|
|
||||||
|
if limit != 0 {
|
||||||
|
params["limit"] = strconv.Itoa(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.Raw("getUpdates", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result []Update
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) forwardCopyMany(to Recipient, msgs []Editable, key string, opts ...*SendOptions) ([]Message, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"chat_id": to.Recipient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
embedMessages(params, msgs)
|
||||||
|
|
||||||
|
if len(opts) > 0 {
|
||||||
|
b.embedSendOptions(params, opts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.Raw(key, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result []Message
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
var resp struct {
|
||||||
|
Result bool
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractOk checks given result for error. If result is ok returns nil.
|
||||||
|
// In other cases it extracts API error. If error is not presented
|
||||||
|
// in errors.go, it will be prefixed with `unknown` keyword.
|
||||||
|
func extractOk(data []byte) error {
|
||||||
|
var e struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Code int `json:"error_code"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Parameters map[string]interface{} `json:"parameters"`
|
||||||
|
}
|
||||||
|
if json.NewDecoder(bytes.NewReader(data)).Decode(&e) != nil {
|
||||||
|
return nil // FIXME
|
||||||
|
}
|
||||||
|
if e.Ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Err(e.Description)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
case ErrGroupMigrated:
|
||||||
|
migratedTo, ok := e.Parameters["migrate_to_chat_id"]
|
||||||
|
if !ok {
|
||||||
|
return NewError(e.Code, e.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GroupError{
|
||||||
|
err: err.(*Error),
|
||||||
|
MigratedTo: int64(migratedTo.(float64)),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.Code {
|
||||||
|
case http.StatusTooManyRequests:
|
||||||
|
retryAfter, ok := e.Parameters["retry_after"]
|
||||||
|
if !ok {
|
||||||
|
return NewError(e.Code, e.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = FloodError{
|
||||||
|
err: NewError(e.Code, e.Description),
|
||||||
|
RetryAfter: int(retryAfter.(float64)),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("telegram: %s (%d)", e.Description, e.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractMessage extracts common Message result from given data.
|
||||||
|
// Should be called after extractOk or b.Raw() to handle possible errors.
|
||||||
|
func extractMessage(data []byte) (*Message, error) {
|
||||||
|
var resp struct {
|
||||||
|
Result *Message
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
var resp struct {
|
||||||
|
Result bool
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
if resp.Result {
|
||||||
|
return nil, ErrTrueResult
|
||||||
|
}
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verbose(method string, payload interface{}, data []byte) {
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
body = bytes.ReplaceAll(body, []byte(`\"`), []byte(`"`))
|
||||||
|
body = bytes.ReplaceAll(body, []byte(`"{`), []byte(`{`))
|
||||||
|
body = bytes.ReplaceAll(body, []byte(`}"`), []byte(`}`))
|
||||||
|
|
||||||
|
indent := func(b []byte) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
json.Indent(&buf, b, "", " ")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"[verbose] telebot: sent request\nMethod: %v\nParams: %v\nResponse: %v",
|
||||||
|
method, indent(body), indent(data),
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BusinessConnection struct {
|
||||||
|
// Unique identifier of the business connection
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// Business account user that created the business connection
|
||||||
|
Sender *User `json:"user"`
|
||||||
|
|
||||||
|
// Identifier of a private chat with the user who created the business connection. This
|
||||||
|
// number may have more than 32 significant bits and some programming languages may
|
||||||
|
// have difficulty/silent defects in interpreting it. But it has at most 52 significant bits,
|
||||||
|
// so a 64-bit integer or double-precision float type are safe for storing this identifier.
|
||||||
|
UserChatID int64 `json:"user_chat_id"`
|
||||||
|
|
||||||
|
// Unixtime, use BusinessConnection.Time() to get time.Time.
|
||||||
|
Unixtime int64 `json:"date"`
|
||||||
|
|
||||||
|
// True, if the bot can act on behalf of the business account in chats that were active in the last 24 hours
|
||||||
|
CanReply bool `json:"can_reply"`
|
||||||
|
|
||||||
|
// True, if the connection is active
|
||||||
|
Enabled bool `json:"is_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the moment of business connection creation in local time.
|
||||||
|
func (b *BusinessConnection) Time() time.Time {
|
||||||
|
return time.Unix(b.Unixtime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BusinessMessagesDeleted struct {
|
||||||
|
// Unique identifier of the business connection
|
||||||
|
BusinessConnectionID string `json:"business_connection_id"`
|
||||||
|
|
||||||
|
// Information about a chat in the business account. The bot
|
||||||
|
// may not have access to the chat or the corresponding user.
|
||||||
|
Chat *Chat `json:"chat"`
|
||||||
|
|
||||||
|
// The list of identifiers of deleted messages in the chat of the business account
|
||||||
|
MessageIDs []int `json:"message_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BusinessIntro struct {
|
||||||
|
// (Optional)
|
||||||
|
// Title text of the business intro
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// Message text of the business intro
|
||||||
|
Message string `json:"message"`
|
||||||
|
|
||||||
|
// Sticker of the business intro
|
||||||
|
Sticker *Sticker `json:"sticker"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BusinessLocation struct {
|
||||||
|
// Address of the business
|
||||||
|
Address string `json:"address"`
|
||||||
|
|
||||||
|
// (Optional) Location of the business
|
||||||
|
Location *Location `json:"location"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BusinessOpeningHoursInterval struct {
|
||||||
|
// The minute's sequence number in a week, starting on Monday,
|
||||||
|
// marking the start of the time interval during which the business
|
||||||
|
// is open; 0 - 7 * 24 * 60
|
||||||
|
OpeningMinute int `json:"opening_minute"`
|
||||||
|
|
||||||
|
// The minute's sequence number in a week, starting on Monday,
|
||||||
|
// marking the start of the time interval during which the business
|
||||||
|
// is open; 0 - 7 * 24 * 60
|
||||||
|
ClosingMinute int `json:"closing_minute"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BusinessOpeningHours struct {
|
||||||
|
// Unique name of the time zone for which the opening hours are defined
|
||||||
|
Timezone string `json:"time_zone_name"`
|
||||||
|
|
||||||
|
// List of time intervals describing business opening hours
|
||||||
|
OpeningHours []BusinessOpeningHoursInterval `json:"opening_hours"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BusinessConnection returns the information about the connection of the bot with a business account.
|
||||||
|
func (b *Bot) BusinessConnection(id string) (*BusinessConnection, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"business_connection_id": id,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.Raw("getBusinessConnection", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result *BusinessConnection
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Giveaway represents a message about a scheduled giveaway.
|
||||||
|
type Giveaway struct {
|
||||||
|
// The list of chats which the user must join to participate in the giveaway.
|
||||||
|
Chats []Chat `json:"chats"`
|
||||||
|
|
||||||
|
// Point in time (Unix timestamp) when winners of the giveaway will be selected.
|
||||||
|
SelectionUnixtime int64 `json:"winners_selection_date"`
|
||||||
|
|
||||||
|
// The number of users which are supposed to be selected as winners of the giveaway.
|
||||||
|
WinnerCount int `json:"winner_count"`
|
||||||
|
|
||||||
|
// (Optional) True, if only users who join the chats after the giveaway
|
||||||
|
// started should be eligible to win.
|
||||||
|
OnlyNewMembers bool `json:"only_new_members"`
|
||||||
|
|
||||||
|
// (Optional) True, if the list of giveaway winners will be visible to everyone.
|
||||||
|
HasPublicWinners bool `json:"has_public_winners"`
|
||||||
|
|
||||||
|
// (Optional) Description of additional giveaway prize.
|
||||||
|
PrizeDescription string `json:"prize_description"`
|
||||||
|
|
||||||
|
// (Optional) A list of two-letter ISO 3166-1 alpha-2 country codes indicating
|
||||||
|
// the countries from which eligible users for the giveaway must come.
|
||||||
|
// If empty, then all users can participate in the giveaway. Users with a phone number
|
||||||
|
// that was bought on Fragment can always participate in giveaways.
|
||||||
|
CountryCodes []string `json:"country_codes"`
|
||||||
|
|
||||||
|
// (Optional) The number of months the Telegram Premium subscription won from
|
||||||
|
// the giveaway will be active for.
|
||||||
|
PremiumMonthCount int `json:"premium_subscription_month_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectionDate returns the moment of when winners of the giveaway were selected in local time.
|
||||||
|
func (g *Giveaway) SelectionDate() time.Time {
|
||||||
|
return time.Unix(g.SelectionUnixtime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GiveawayWinners object represents a message about the completion of a
|
||||||
|
// giveaway with public winners.
|
||||||
|
type GiveawayWinners struct {
|
||||||
|
// The chat that created the giveaway.
|
||||||
|
Chat *Chat `json:"chat"`
|
||||||
|
|
||||||
|
// Identifier of the message with the giveaway in the chat.
|
||||||
|
MessageID int `json:"message_id"`
|
||||||
|
|
||||||
|
// Point in time (Unix timestamp) when winners of the giveaway were selected.
|
||||||
|
SelectionUnixtime int64 `json:"winners_selection_date"`
|
||||||
|
|
||||||
|
// The number of users which are supposed to be selected as winners of the giveaway.
|
||||||
|
WinnerCount int `json:"winner_count"`
|
||||||
|
|
||||||
|
// List of up to 100 winners of the giveaway.
|
||||||
|
Winners []User `json:"winners"`
|
||||||
|
|
||||||
|
// (Optional) The number of other chats the user had to join in order
|
||||||
|
// to be eligible for the giveaway.
|
||||||
|
AdditionalChats int `json:"additional_chat_count"`
|
||||||
|
|
||||||
|
// (Optional) The number of months the Telegram Premium subscription won from
|
||||||
|
// the giveaway will be active for.
|
||||||
|
PremiumMonthCount int `json:"premium_subscription_month_count"`
|
||||||
|
|
||||||
|
// (Optional) Number of undistributed prizes.
|
||||||
|
UnclaimedPrizes int `json:"unclaimed_prize_count"`
|
||||||
|
|
||||||
|
// (Optional) True, if only users who had joined the chats after the giveaway started
|
||||||
|
// were eligible to win.
|
||||||
|
OnlyNewMembers bool `json:"only_new_members"`
|
||||||
|
|
||||||
|
// (Optional) True, if the giveaway was canceled because the payment for it was refunded.
|
||||||
|
Refunded bool `json:"was_refunded"`
|
||||||
|
|
||||||
|
// (Optional) Description of additional giveaway prize.
|
||||||
|
PrizeDescription string `json:"prize_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectionDate returns the moment of when winners of the giveaway
|
||||||
|
// were selected in local time.
|
||||||
|
func (g *GiveawayWinners) SelectionDate() time.Time {
|
||||||
|
return time.Unix(g.SelectionUnixtime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GiveawayCreated represents a service message about the creation of a scheduled giveaway.
|
||||||
|
// Currently holds no information.
|
||||||
|
type GiveawayCreated struct{}
|
||||||
|
|
||||||
|
// GiveawayCompleted represents a service message about the completion of a
|
||||||
|
// giveaway without public winners.
|
||||||
|
type GiveawayCompleted struct {
|
||||||
|
// Number of winners in the giveaway.
|
||||||
|
WinnerCount int `json:"winner_count"`
|
||||||
|
|
||||||
|
// (Optional) Number of undistributed prizes.
|
||||||
|
UnclaimedPrizes int `json:"unclaimed_prize_count"`
|
||||||
|
|
||||||
|
// (Optional) Message with the giveaway that was completed, if it wasn't deleted.
|
||||||
|
Message *Message `json:"giveaway_message"`
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
article_message: This is an article.
|
||||||
|
|
||||||
|
nested:
|
||||||
|
example: |-
|
||||||
|
This is {{ . }}.
|
||||||
|
another:
|
||||||
|
example: |-
|
||||||
|
This is {{ . }}.
|
@ -0,0 +1,44 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlbumSetCaption(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
media Inputtable
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "photo",
|
||||||
|
media: &Photo{Caption: "wrong_caption"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "animation",
|
||||||
|
media: &Animation{Caption: "wrong_caption"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "video",
|
||||||
|
media: &Video{Caption: "wrong_caption"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "audio",
|
||||||
|
media: &Audio{Caption: "wrong_caption"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "document",
|
||||||
|
media: &Document{Caption: "wrong_caption"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var a Album
|
||||||
|
a = append(a, tt.media)
|
||||||
|
a = append(a, &Photo{Caption: "random_caption"})
|
||||||
|
a.SetCaption("correct_caption")
|
||||||
|
assert.Equal(t, "correct_caption", a[0].InputMedia().Caption)
|
||||||
|
assert.Equal(t, "random_caption", a[1].InputMedia().Caption)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReactionTypeEmoji = "emoji"
|
||||||
|
ReactionTypeCustomEmoji = "custom_emoji"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reaction describes the type of reaction.
|
||||||
|
// Describes an instance of ReactionTypeCustomEmoji and ReactionTypeEmoji.
|
||||||
|
type Reaction struct {
|
||||||
|
// Type of the reaction, always “emoji”
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Reaction emoji.
|
||||||
|
Emoji string `json:"emoji,omitempty"`
|
||||||
|
|
||||||
|
// Custom emoji identifier.
|
||||||
|
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReactionCount represents a reaction added to a message along
|
||||||
|
// with the number of times it was added.
|
||||||
|
type ReactionCount struct {
|
||||||
|
// Type of the reaction.
|
||||||
|
Type Reaction `json:"type"`
|
||||||
|
|
||||||
|
// Number of times the reaction was added.
|
||||||
|
Count int `json:"total_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactions represents an object of reaction options.
|
||||||
|
type Reactions struct {
|
||||||
|
// List of reaction types to set on the message.
|
||||||
|
Reactions []Reaction `json:"reaction"`
|
||||||
|
|
||||||
|
// Pass True to set the reaction with a big animation.
|
||||||
|
Big bool `json:"is_big"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// React changes the chosen reactions on a message. Service messages can't be
|
||||||
|
// reacted to. Automatically forwarded messages from a channel to its discussion group have
|
||||||
|
// the same available reactions as messages in the channel.
|
||||||
|
func (b *Bot) React(to Recipient, msg Editable, r Reactions) error {
|
||||||
|
if to == nil {
|
||||||
|
return ErrBadRecipient
|
||||||
|
}
|
||||||
|
|
||||||
|
msgID, _ := msg.MessageSig()
|
||||||
|
params := map[string]string{
|
||||||
|
"chat_id": to.Recipient(),
|
||||||
|
"message_id": msgID,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(r.Reactions)
|
||||||
|
params["reaction"] = string(data)
|
||||||
|
|
||||||
|
if r.Big {
|
||||||
|
params["is_big"] = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("setMessageReaction", params)
|
||||||
|
return err
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package react
|
||||||
|
|
||||||
|
import (
|
||||||
|
tele "gopkg.in/telebot.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reaction = tele.Reaction
|
||||||
|
|
||||||
|
func React(r ...Reaction) tele.Reactions {
|
||||||
|
return tele.Reactions{Reactions: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently available emojis.
|
||||||
|
var (
|
||||||
|
ThumbUp = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👍"}
|
||||||
|
ThumbDown = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👎"}
|
||||||
|
Heart = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "❤"}
|
||||||
|
Fire = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🔥"}
|
||||||
|
HeartEyes = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😍"}
|
||||||
|
ClappingHands = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👏"}
|
||||||
|
GrinningFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😁"}
|
||||||
|
ThinkingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤔"}
|
||||||
|
ExplodingHead = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤯"}
|
||||||
|
ScreamingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😱"}
|
||||||
|
SwearingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤬"}
|
||||||
|
CryingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😢"}
|
||||||
|
PartyPopper = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🎉"}
|
||||||
|
StarStruck = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤩"}
|
||||||
|
VomitingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤮"}
|
||||||
|
PileOfPoo = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "💩"}
|
||||||
|
PrayingHands = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🙏"}
|
||||||
|
OkHand = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👌"}
|
||||||
|
DoveOfPeace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🕊"}
|
||||||
|
ClownFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤡"}
|
||||||
|
YawningFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🥱"}
|
||||||
|
WoozyFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🥴"}
|
||||||
|
Whale = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🐳"}
|
||||||
|
HeartOnFire = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "❤🔥"}
|
||||||
|
MoonFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🌚"}
|
||||||
|
HotDog = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🌭"}
|
||||||
|
HundredPoints = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "💯"}
|
||||||
|
RollingOnTheFloorLaughing = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤣"}
|
||||||
|
Lightning = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "⚡"}
|
||||||
|
Banana = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🍌"}
|
||||||
|
Trophy = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🏆"}
|
||||||
|
BrokenHeart = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "💔"}
|
||||||
|
FaceWithRaisedEyebrow = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤨"}
|
||||||
|
NeutralFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😐"}
|
||||||
|
Strawberry = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🍓"}
|
||||||
|
Champagne = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🍾"}
|
||||||
|
KissMark = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "💋"}
|
||||||
|
MiddleFinger = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🖕"}
|
||||||
|
EvilFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😈"}
|
||||||
|
SleepingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😴"}
|
||||||
|
LoudlyCryingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😭"}
|
||||||
|
NerdFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤓"}
|
||||||
|
Ghost = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👻"}
|
||||||
|
Engineer = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👨💻"}
|
||||||
|
Eyes = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👀"}
|
||||||
|
JackOLantern = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🎃"}
|
||||||
|
NoMonkey = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🙈"}
|
||||||
|
SmilingFaceWithHalo = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😇"}
|
||||||
|
FearfulFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😨"}
|
||||||
|
Handshake = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤝"}
|
||||||
|
WritingHand = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "✍"}
|
||||||
|
HuggingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤗"}
|
||||||
|
Brain = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🫡"}
|
||||||
|
SantaClaus = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🎅"}
|
||||||
|
ChristmasTree = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🎄"}
|
||||||
|
Snowman = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "☃"}
|
||||||
|
NailPolish = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "💅"}
|
||||||
|
ZanyFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤪"}
|
||||||
|
Moai = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🗿"}
|
||||||
|
Cool = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🆒"}
|
||||||
|
HeartWithArrow = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "💘"}
|
||||||
|
HearMonkey = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🙉"}
|
||||||
|
Unicorn = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🦄"}
|
||||||
|
FaceBlowingKiss = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😘"}
|
||||||
|
Pill = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "💊"}
|
||||||
|
SpeaklessMonkey = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🙊"}
|
||||||
|
Sunglasses = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😎"}
|
||||||
|
AlienMonster = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "👾"}
|
||||||
|
ManShrugging = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤷♂️"}
|
||||||
|
PersonShrugging = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤷"}
|
||||||
|
WomanShrugging = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "🤷♀️"}
|
||||||
|
PoutingFace = Reaction{Type: tele.ReactionTypeEmoji, Emoji: "😡"}
|
||||||
|
)
|
@ -0,0 +1,73 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type TransactionType = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransactionTypeUser TransactionType = "user"
|
||||||
|
TransactionTypeFragment TransactionType = "fragment"
|
||||||
|
TransactionPartnerTelegramAds TransactionType = "telegram_ads"
|
||||||
|
TransactionTypeOther TransactionType = "other"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RevenueState = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RevenueStatePending RevenueState = "pending"
|
||||||
|
RevenueStateSucceeded RevenueState = "succeeded"
|
||||||
|
RevenueStateFailed RevenueState = "failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StarTransaction struct {
|
||||||
|
// Unique identifier of the transaction. Coincides with the identifier of the
|
||||||
|
// original transaction for refund transactions. Coincides with
|
||||||
|
// SuccessfulPayment.telegram_payment_charge_id for successful incoming
|
||||||
|
// payments from users.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// Number of Telegram Stars transferred by the transaction
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
|
||||||
|
// Date the transaction was created in Unix time
|
||||||
|
Unixtime int64 `json:"date"`
|
||||||
|
|
||||||
|
// (Optional) Source of an incoming transaction (e.g., a user purchasing goods
|
||||||
|
// or services, Fragment refunding a failed withdrawal). Only for incoming transactions
|
||||||
|
Source TransactionPartner `json:"source"`
|
||||||
|
|
||||||
|
// (Optional) Receiver of an outgoing transaction (e.g., a user for a purchase
|
||||||
|
// refund, Fragment for a withdrawal). Only for outgoing transactions
|
||||||
|
Receiver TransactionPartner `json:"receiver"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionPartner struct {
|
||||||
|
// Type of the state
|
||||||
|
Type TransactionType `json:"type"`
|
||||||
|
User *User `json:"user,omitempty"`
|
||||||
|
Payload string `json:"invoice_payload"`
|
||||||
|
|
||||||
|
// (Optional) State of the transaction if the transaction is outgoing$$
|
||||||
|
Withdrawal RevenueWithdrawal `json:"withdrawal_state,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RevenueWithdrawal struct {
|
||||||
|
// Type of the state
|
||||||
|
Type RevenueState `json:"type"`
|
||||||
|
|
||||||
|
// Date the withdrawal was completed in Unix time
|
||||||
|
Unixtime int `json:"date,omitempty"`
|
||||||
|
|
||||||
|
// An HTTPS URL that can be used to see transaction details
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the date of the transaction.
|
||||||
|
func (c *StarTransaction) Time() time.Time {
|
||||||
|
return time.Unix(c.Unixtime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the date of the withdrawal.
|
||||||
|
func (s *RevenueWithdrawal) Time() time.Time {
|
||||||
|
return time.Unix(int64(s.Unixtime), 0)
|
||||||
|
}
|
@ -0,0 +1,331 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
StickerSetType = string
|
||||||
|
StickerSetFormat = string
|
||||||
|
MaskFeature = string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StickerRegular StickerSetType = "regular"
|
||||||
|
StickerMask StickerSetType = "mask"
|
||||||
|
StickerCustomEmoji StickerSetType = "custom_emoji"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StickerStatic StickerSetFormat = "static"
|
||||||
|
StickerAnimated StickerSetFormat = "animated"
|
||||||
|
StickerVideo StickerSetFormat = "video"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaskForehead MaskFeature = "forehead"
|
||||||
|
MaskEyes MaskFeature = "eyes"
|
||||||
|
MaskMouth MaskFeature = "mouth"
|
||||||
|
MaskChin MaskFeature = "chin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StickerSet represents a sticker set.
|
||||||
|
type StickerSet struct {
|
||||||
|
Type StickerSetType `json:"sticker_type"`
|
||||||
|
Format StickerSetFormat `json:"sticker_format"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Stickers []Sticker `json:"stickers"`
|
||||||
|
Thumbnail *Photo `json:"thumbnail"`
|
||||||
|
Emojis string `json:"emojis"`
|
||||||
|
ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed
|
||||||
|
MaskPosition *MaskPosition `json:"mask_position"`
|
||||||
|
Repaint bool `json:"needs_repainting"`
|
||||||
|
|
||||||
|
// Input is a field used in createNewStickerSet method to specify a list
|
||||||
|
// of pre-defined stickers of type InputSticker to add to the set.
|
||||||
|
Input []InputSticker
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputSticker struct {
|
||||||
|
File
|
||||||
|
Sticker string `json:"sticker"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
MaskPosition *MaskPosition `json:"mask_position"`
|
||||||
|
Emojis []string `json:"emoji_list"`
|
||||||
|
Keywords []string `json:"keywords"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaskPosition describes the position on faces where
|
||||||
|
// a mask should be placed by default.
|
||||||
|
type MaskPosition struct {
|
||||||
|
Feature MaskFeature `json:"point"`
|
||||||
|
XShift float32 `json:"x_shift"`
|
||||||
|
YShift float32 `json:"y_shift"`
|
||||||
|
Scale float32 `json:"scale"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadSticker uploads a sticker file for later use.
|
||||||
|
func (b *Bot) UploadSticker(to Recipient, format StickerSetFormat, f File) (*File, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"user_id": to.Recipient(),
|
||||||
|
"sticker_format": format,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.sendFiles("uploadStickerFile", map[string]File{"0": f}, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result File
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return &resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StickerSet returns a sticker set on success.
|
||||||
|
func (b *Bot) StickerSet(name string) (*StickerSet, error) {
|
||||||
|
data, err := b.Raw("getStickerSet", map[string]string{"name": name})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result *StickerSet
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStickerSet creates a new sticker set.
|
||||||
|
func (b *Bot) CreateStickerSet(of Recipient, set *StickerSet) error {
|
||||||
|
files := make(map[string]File)
|
||||||
|
for i, s := range set.Input {
|
||||||
|
repr := s.File.process(strconv.Itoa(i), files)
|
||||||
|
if repr == "" {
|
||||||
|
return fmt.Errorf("telebot: sticker #%d does not exist", i+1)
|
||||||
|
}
|
||||||
|
set.Input[i].Sticker = repr
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(set.Input)
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"user_id": of.Recipient(),
|
||||||
|
"name": set.Name,
|
||||||
|
"title": set.Title,
|
||||||
|
"stickers": string(data),
|
||||||
|
}
|
||||||
|
if set.Type != "" {
|
||||||
|
params["sticker_type"] = set.Type
|
||||||
|
}
|
||||||
|
if set.Repaint {
|
||||||
|
params["needs_repainting"] = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.sendFiles("createNewStickerSet", files, params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStickerToSet adds a new sticker to the existing sticker set.
|
||||||
|
func (b *Bot) AddStickerToSet(of Recipient, name string, sticker InputSticker) error {
|
||||||
|
files := make(map[string]File)
|
||||||
|
repr := sticker.File.process("0", files)
|
||||||
|
if repr == "" {
|
||||||
|
return errors.New("telebot: sticker does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
sticker.Sticker = repr
|
||||||
|
data, _ := json.Marshal(sticker)
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"user_id": of.Recipient(),
|
||||||
|
"name": name,
|
||||||
|
"sticker": string(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.sendFiles("addStickerToSet", files, params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerPosition moves a sticker in set to a specific position.
|
||||||
|
func (b *Bot) SetStickerPosition(sticker string, position int) error {
|
||||||
|
params := map[string]string{
|
||||||
|
"sticker": sticker,
|
||||||
|
"position": strconv.Itoa(position),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("setStickerPositionInSet", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSticker deletes a sticker from a set created by the bot.
|
||||||
|
func (b *Bot) DeleteSticker(sticker string) error {
|
||||||
|
_, err := b.Raw("deleteStickerFromSet", map[string]string{"sticker": sticker})
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerSetThumb sets a thumbnail of the sticker set.
|
||||||
|
// Animated thumbnails can be set for animated sticker sets only.
|
||||||
|
//
|
||||||
|
// Thumbnail must be a PNG image, up to 128 kilobytes in size
|
||||||
|
// and have width and height exactly 100px, or a TGS animation
|
||||||
|
// up to 32 kilobytes in size.
|
||||||
|
//
|
||||||
|
// Animated sticker set thumbnail can't be uploaded via HTTP URL.
|
||||||
|
func (b *Bot) SetStickerSetThumb(of Recipient, set *StickerSet) error {
|
||||||
|
if set.Thumbnail == nil {
|
||||||
|
return errors.New("telebot: thumbnail is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
files := make(map[string]File)
|
||||||
|
repr := set.Thumbnail.File.process("thumb", files)
|
||||||
|
if repr == "" {
|
||||||
|
return errors.New("telebot: thumbnail does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"user_id": of.Recipient(),
|
||||||
|
"name": set.Name,
|
||||||
|
"format": set.Format,
|
||||||
|
"thumbnail": repr,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.sendFiles("setStickerSetThumbnail", files, params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerSetTitle sets the title of a created sticker set.
|
||||||
|
func (b *Bot) SetStickerSetTitle(s StickerSet) error {
|
||||||
|
params := map[string]string{
|
||||||
|
"name": s.Name,
|
||||||
|
"title": s.Title,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("setStickerSetTitle", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStickerSet deletes a sticker set that was created by the bot.
|
||||||
|
func (b *Bot) DeleteStickerSet(name string) error {
|
||||||
|
params := map[string]string{"name": name}
|
||||||
|
|
||||||
|
_, err := b.Raw("deleteStickerSet", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerEmojis changes the list of emoji assigned to a regular or custom emoji sticker.
|
||||||
|
func (b *Bot) SetStickerEmojis(sticker string, emojis []string) error {
|
||||||
|
data, err := json.Marshal(emojis)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"sticker": sticker,
|
||||||
|
"emoji_list": string(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.Raw("setStickerEmojiList", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerKeywords changes search keywords assigned to a regular or custom emoji sticker.
|
||||||
|
func (b *Bot) SetStickerKeywords(sticker string, keywords []string) error {
|
||||||
|
mk, err := json.Marshal(keywords)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"sticker": sticker,
|
||||||
|
"keywords": string(mk),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.Raw("setStickerKeywords", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerMaskPosition changes the mask position of a mask sticker.
|
||||||
|
func (b *Bot) SetStickerMaskPosition(sticker string, mask MaskPosition) error {
|
||||||
|
data, err := json.Marshal(mask)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"sticker": sticker,
|
||||||
|
"mask_position": string(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.Raw("setStickerMaskPosition", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomEmojiStickers returns the information about custom emoji stickers by their ids.
|
||||||
|
func (b *Bot) CustomEmojiStickers(ids []string) ([]Sticker, error) {
|
||||||
|
data, _ := json.Marshal(ids)
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"custom_emoji_ids": string(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.Raw("getCustomEmojiStickers", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result []Sticker
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCustomEmojiStickerSetThumb sets the thumbnail of a custom emoji sticker set.
|
||||||
|
func (b *Bot) SetCustomEmojiStickerSetThumb(name, id string) error {
|
||||||
|
params := map[string]string{
|
||||||
|
"name": name,
|
||||||
|
"custom_emoji_id": id,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("setCustomEmojiStickerSetThumbnail", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceStickerInSet returns True on success, if existing sticker was replaced with a new one.
|
||||||
|
func (b *Bot) ReplaceStickerInSet(of Recipient, stickerSet, oldSticker string, sticker InputSticker) (bool, error) {
|
||||||
|
files := make(map[string]File)
|
||||||
|
|
||||||
|
repr := sticker.File.process("0", files)
|
||||||
|
if repr == "" {
|
||||||
|
return false, errors.New("telebot: sticker does not exist")
|
||||||
|
}
|
||||||
|
sticker.Sticker = repr
|
||||||
|
|
||||||
|
data, err := json.Marshal(sticker)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"user_id": of.Recipient(),
|
||||||
|
"name": stickerSet,
|
||||||
|
"old_sticker": oldSticker,
|
||||||
|
"sticker": string(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.sendFiles("replaceStickerInSet", files, params)
|
||||||
|
return true, err
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStickerSet(t *testing.T) {
|
||||||
|
if b == nil {
|
||||||
|
t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)")
|
||||||
|
}
|
||||||
|
if userID == 0 {
|
||||||
|
t.Skip("USER_ID is required for StickerSet methods test")
|
||||||
|
}
|
||||||
|
|
||||||
|
input := []InputSticker{
|
||||||
|
{
|
||||||
|
File: FromURL("https://placehold.co/512/000000/FFFFFF/png"),
|
||||||
|
Emojis: []string{"🤖"},
|
||||||
|
Keywords: []string{"telebot", "robot", "bot"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
File: FromURL("https://placehold.co/512/000000/999999/png"),
|
||||||
|
Emojis: []string{"🤖"},
|
||||||
|
Keywords: []string{"telebot", "robot", "bot"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
original := &StickerSet{
|
||||||
|
Name: fmt.Sprintf("telebot_%d_by_%s", time.Now().Unix(), b.Me.Username),
|
||||||
|
Type: StickerRegular,
|
||||||
|
Format: StickerStatic,
|
||||||
|
Title: "Telebot Stickers",
|
||||||
|
Input: input[:1],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1
|
||||||
|
err := b.CreateStickerSet(user, original)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// 2
|
||||||
|
err = b.AddStickerToSet(user, original.Name, input[1])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
original.Thumbnail = &Photo{File: thumb}
|
||||||
|
err = b.SetStickerSetThumb(user, original)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
set, err := b.StickerSet(original.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, original.Name, set.Name)
|
||||||
|
require.Equal(t, len(input), len(set.Stickers))
|
||||||
|
|
||||||
|
_, err = b.Send(user, &set.Stickers[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = b.Send(user, &set.Stickers[1])
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
@ -1,212 +0,0 @@
|
|||||||
package telebot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StickerSetType = string
|
|
||||||
|
|
||||||
const (
|
|
||||||
StickerRegular = "regular"
|
|
||||||
StickerMask = "mask"
|
|
||||||
StickerCustomEmoji = "custom_emoji"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StickerSet represents a sticker set.
|
|
||||||
type StickerSet struct {
|
|
||||||
Type StickerSetType `json:"sticker_type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Animated bool `json:"is_animated"`
|
|
||||||
Video bool `json:"is_video"`
|
|
||||||
Stickers []Sticker `json:"stickers"`
|
|
||||||
Thumbnail *Photo `json:"thumb"`
|
|
||||||
PNG *File `json:"png_sticker"`
|
|
||||||
TGS *File `json:"tgs_sticker"`
|
|
||||||
WebM *File `json:"webm_sticker"`
|
|
||||||
Emojis string `json:"emojis"`
|
|
||||||
ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed
|
|
||||||
MaskPosition *MaskPosition `json:"mask_position"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaskPosition describes the position on faces where
|
|
||||||
// a mask should be placed by default.
|
|
||||||
type MaskPosition struct {
|
|
||||||
Feature MaskFeature `json:"point"`
|
|
||||||
XShift float32 `json:"x_shift"`
|
|
||||||
YShift float32 `json:"y_shift"`
|
|
||||||
Scale float32 `json:"scale"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaskFeature defines sticker mask position.
|
|
||||||
type MaskFeature string
|
|
||||||
|
|
||||||
const (
|
|
||||||
FeatureForehead MaskFeature = "forehead"
|
|
||||||
FeatureEyes MaskFeature = "eyes"
|
|
||||||
FeatureMouth MaskFeature = "mouth"
|
|
||||||
FeatureChin MaskFeature = "chin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UploadSticker uploads a PNG file with a sticker for later use.
|
|
||||||
func (b *Bot) UploadSticker(to Recipient, png *File) (*File, error) {
|
|
||||||
files := map[string]File{
|
|
||||||
"png_sticker": *png,
|
|
||||||
}
|
|
||||||
params := map[string]string{
|
|
||||||
"user_id": to.Recipient(),
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := b.sendFiles("uploadStickerFile", files, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp struct {
|
|
||||||
Result File
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &resp); err != nil {
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
return &resp.Result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StickerSet returns a sticker set on success.
|
|
||||||
func (b *Bot) StickerSet(name string) (*StickerSet, error) {
|
|
||||||
data, err := b.Raw("getStickerSet", map[string]string{"name": name})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp struct {
|
|
||||||
Result *StickerSet
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &resp); err != nil {
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
return resp.Result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateStickerSet creates a new sticker set.
|
|
||||||
func (b *Bot) CreateStickerSet(to Recipient, s StickerSet) error {
|
|
||||||
files := make(map[string]File)
|
|
||||||
if s.PNG != nil {
|
|
||||||
files["png_sticker"] = *s.PNG
|
|
||||||
}
|
|
||||||
if s.TGS != nil {
|
|
||||||
files["tgs_sticker"] = *s.TGS
|
|
||||||
}
|
|
||||||
if s.WebM != nil {
|
|
||||||
files["webm_sticker"] = *s.WebM
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
|
||||||
"user_id": to.Recipient(),
|
|
||||||
"sticker_type": s.Type,
|
|
||||||
"name": s.Name,
|
|
||||||
"title": s.Title,
|
|
||||||
"emojis": s.Emojis,
|
|
||||||
"contains_masks": strconv.FormatBool(s.ContainsMasks),
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.MaskPosition != nil {
|
|
||||||
data, _ := json.Marshal(&s.MaskPosition)
|
|
||||||
params["mask_position"] = string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := b.sendFiles("createNewStickerSet", files, params)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSticker adds a new sticker to the existing sticker set.
|
|
||||||
func (b *Bot) AddSticker(to Recipient, s StickerSet) error {
|
|
||||||
files := make(map[string]File)
|
|
||||||
if s.PNG != nil {
|
|
||||||
files["png_sticker"] = *s.PNG
|
|
||||||
} else if s.TGS != nil {
|
|
||||||
files["tgs_sticker"] = *s.TGS
|
|
||||||
} else if s.WebM != nil {
|
|
||||||
files["webm_sticker"] = *s.WebM
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
|
||||||
"user_id": to.Recipient(),
|
|
||||||
"name": s.Name,
|
|
||||||
"emojis": s.Emojis,
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.MaskPosition != nil {
|
|
||||||
data, _ := json.Marshal(&s.MaskPosition)
|
|
||||||
params["mask_position"] = string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := b.sendFiles("addStickerToSet", files, params)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStickerPosition moves a sticker in set to a specific position.
|
|
||||||
func (b *Bot) SetStickerPosition(sticker string, position int) error {
|
|
||||||
params := map[string]string{
|
|
||||||
"sticker": sticker,
|
|
||||||
"position": strconv.Itoa(position),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := b.Raw("setStickerPositionInSet", params)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSticker deletes a sticker from a set created by the bot.
|
|
||||||
func (b *Bot) DeleteSticker(sticker string) error {
|
|
||||||
_, err := b.Raw("deleteStickerFromSet", map[string]string{"sticker": sticker})
|
|
||||||
return err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStickerSetThumb sets a thumbnail of the sticker set.
|
|
||||||
// Animated thumbnails can be set for animated sticker sets only.
|
|
||||||
//
|
|
||||||
// Thumbnail must be a PNG image, up to 128 kilobytes in size
|
|
||||||
// and have width and height exactly 100px, or a TGS animation
|
|
||||||
// up to 32 kilobytes in size.
|
|
||||||
//
|
|
||||||
// Animated sticker set thumbnail can't be uploaded via HTTP URL.
|
|
||||||
//
|
|
||||||
func (b *Bot) SetStickerSetThumb(to Recipient, s StickerSet) error {
|
|
||||||
files := make(map[string]File)
|
|
||||||
if s.PNG != nil {
|
|
||||||
files["thumb"] = *s.PNG
|
|
||||||
} else if s.TGS != nil {
|
|
||||||
files["thumb"] = *s.TGS
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
|
||||||
"name": s.Name,
|
|
||||||
"user_id": to.Recipient(),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := b.sendFiles("setStickerSetThumb", files, params)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomEmojiStickers returns the information about custom emoji stickers by their ids.
|
|
||||||
func (b *Bot) CustomEmojiStickers(ids []string) ([]Sticker, error) {
|
|
||||||
data, _ := json.Marshal(ids)
|
|
||||||
|
|
||||||
params := map[string]string{
|
|
||||||
"custom_emoji_ids": string(data),
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := b.Raw("getCustomEmojiStickers", params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp struct {
|
|
||||||
Result []Sticker
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &resp); err != nil {
|
|
||||||
return nil, wrapError(err)
|
|
||||||
}
|
|
||||||
return resp.Result, nil
|
|
||||||
}
|
|
@ -0,0 +1,184 @@
|
|||||||
|
package telebot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Topic struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
IconColor int `json:"icon_color"`
|
||||||
|
IconCustomEmojiID string `json:"icon_custom_emoji_id"`
|
||||||
|
ThreadID int `json:"message_thread_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTopic creates a topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) CreateTopic(chat *Chat, topic *Topic) (*Topic, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"name": topic.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if topic.IconColor != 0 {
|
||||||
|
params["icon_color"] = strconv.Itoa(topic.IconColor)
|
||||||
|
}
|
||||||
|
if topic.IconCustomEmojiID != "" {
|
||||||
|
params["icon_custom_emoji_id"] = topic.IconCustomEmojiID
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := b.Raw("createForumTopic", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result *Topic
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTopic edits name and icon of a topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) EditTopic(chat *Chat, topic *Topic) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"message_thread_id": topic.ThreadID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if topic.Name != "" {
|
||||||
|
params["name"] = topic.Name
|
||||||
|
}
|
||||||
|
if topic.IconCustomEmojiID != "" {
|
||||||
|
params["icon_custom_emoji_id"] = topic.IconCustomEmojiID
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("editForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseTopic closes an open topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) CloseTopic(chat *Chat, topic *Topic) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"message_thread_id": topic.ThreadID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("closeForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReopenTopic reopens a closed topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) ReopenTopic(chat *Chat, topic *Topic) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"message_thread_id": topic.ThreadID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("reopenForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTopic deletes a forum topic along with all its messages in a forum supergroup chat.
|
||||||
|
func (b *Bot) DeleteTopic(chat *Chat, topic *Topic) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"message_thread_id": topic.ThreadID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("deleteForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpinAllTopicMessages clears the list of pinned messages in a forum topic. The bot must be an administrator in the chat for this to work and must have the can_pin_messages administrator right in the supergroup.
|
||||||
|
func (b *Bot) UnpinAllTopicMessages(chat *Chat, topic *Topic) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"message_thread_id": topic.ThreadID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("unpinAllForumTopicMessages", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TopicIconStickers gets custom emoji stickers, which can be used as a forum topic icon by any user.
|
||||||
|
func (b *Bot) TopicIconStickers() ([]Sticker, error) {
|
||||||
|
params := map[string]string{}
|
||||||
|
|
||||||
|
data, err := b.Raw("getForumTopicIconStickers", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Result []Sticker
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, wrapError(err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditGeneralTopic edits name of the 'General' topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) EditGeneralTopic(chat *Chat, topic *Topic) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
"name": topic.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("editGeneralForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseGeneralTopic closes an open 'General' topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) CloseGeneralTopic(chat *Chat) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("closeGeneralForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReopenGeneralTopic reopens a closed 'General' topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) ReopenGeneralTopic(chat *Chat) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("reopenGeneralForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HideGeneralTopic hides the 'General' topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) HideGeneralTopic(chat *Chat) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("hideGeneralForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnhideGeneralTopic unhides the 'General' topic in a forum supergroup chat.
|
||||||
|
func (b *Bot) UnhideGeneralTopic(chat *Chat) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("unhideGeneralForumTopic", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpinAllGeneralTopicMessages clears the list of pinned messages in a General forum topic.
|
||||||
|
// The bot must be an administrator in the chat for this to work and must have the
|
||||||
|
// can_pin_messages administrator right in the supergroup.
|
||||||
|
func (b *Bot) UnpinAllGeneralTopicMessages(chat *Chat) error {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"chat_id": chat.Recipient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := b.Raw("unpinAllGeneralForumTopicMessages", params)
|
||||||
|
return err
|
||||||
|
}
|
Loading…
Reference in New Issue