chore(deps): bump github.com/PaulSonOfLars/gotgbot/v2

Bumps [github.com/PaulSonOfLars/gotgbot/v2](https://github.com/PaulSonOfLars/gotgbot) from 2.0.0-rc.27 to 2.0.0-rc.30.
- [Release notes](https://github.com/PaulSonOfLars/gotgbot/releases)
- [Commits](https://github.com/PaulSonOfLars/gotgbot/compare/v2.0.0-rc.27...v2.0.0-rc.30)

---
updated-dependencies:
- dependency-name: github.com/PaulSonOfLars/gotgbot/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2024-12-14 23:37:14 +00:00
committed by GitHub
parent 2a4a8863f3
commit 64f77319b2
12 changed files with 3159 additions and 988 deletions

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.23.0
require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.27
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.30
github.com/alecthomas/kong v0.9.0
github.com/bmatcuk/doublestar/v3 v3.0.0
github.com/containerd/platforms v0.2.1

4
go.sum
View File

@@ -13,8 +13,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.27 h1:rOlGzmYC3jPVPLVLWKMiiYuePQ6MV8Cyw5qJYBoMnkY=
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.27/go.mod h1:kL1v4iIjlalwm3gCYGvF4NLa3hs+aKEfRkNJvj4aoDU=
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.30 h1:kPFkEzqg3+5gu077Zrg+24d0rO0Iwdx/ZUUHFFprfsc=
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.30/go.mod h1:kL1v4iIjlalwm3gCYGvF4NLa3hs+aKEfRkNJvj4aoDU=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=

View File

@@ -1,15 +1,23 @@
package gotgbot
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
//go:generate go run ./scripts/generate
var (
ErrNilBotClient = errors.New("nil BotClient")
ErrInvalidTokenFormat = errors.New("invalid token format")
)
// Bot is the default Bot struct used to send and receive messages to the telegram API.
type Bot struct {
// Token stores the bot's secret token obtained from t.me/BotFather, and used to interact with telegram's API.
@@ -75,6 +83,24 @@ func NewBot(token string, opts *BotOpts) (*Bot, error) {
return nil, fmt.Errorf("failed to check bot token: %w", err)
}
b.User = *botUser
} else {
// If token checks are disabled, we populate the bot's ID from the token.
split := strings.Split(token, ":")
if len(split) != 2 {
return nil, fmt.Errorf("%w: expected '123:abcd', got %s", ErrInvalidTokenFormat, token)
}
id, err := strconv.ParseInt(split[0], 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse bot ID from token: %w", err)
}
b.User = User{
Id: id,
IsBot: true,
// We mark these fields as missing so we can know why they're not available
FirstName: "<missing>",
Username: "<missing>",
}
}
return &b, nil
@@ -88,15 +114,14 @@ func (bot *Bot) UseMiddleware(mw func(client BotClient) BotClient) *Bot {
return bot
}
var ErrNilBotClient = errors.New("nil BotClient")
func (bot *Bot) Request(method string, params map[string]string, data map[string]FileReader, opts *RequestOpts) (json.RawMessage, error) {
return bot.RequestWithContext(context.Background(), method, params, data, opts)
}
func (bot *Bot) Request(method string, params map[string]string, data map[string]NamedReader, opts *RequestOpts) (json.RawMessage, error) {
func (bot *Bot) RequestWithContext(ctx context.Context, method string, params map[string]string, data map[string]FileReader, opts *RequestOpts) (json.RawMessage, error) {
if bot.BotClient == nil {
return nil, ErrNilBotClient
}
ctx, cancel := bot.BotClient.TimeoutContext(opts)
defer cancel()
return bot.BotClient.RequestWithContext(ctx, bot.Token, method, params, data, opts)
}

94
vendor/github.com/PaulSonOfLars/gotgbot/v2/file.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
package gotgbot
import (
"encoding/json"
"errors"
"io"
)
// InputFile (https://core.telegram.org/bots/api#inputfile)
//
// This object represents the contents of a file to be uploaded.
// Must be posted using multipart/form-data in the usual way that files are uploaded via the browser.
type InputFile interface {
InputFileOrString
justFiles()
}
// InputFileOrString (https://core.telegram.org/bots/api#inputfile)
//
// This object represents the contents of a file to be uploaded, or a publicly accessible URL to be reused.
// Files must be posted using multipart/form-data in the usual way that files are uploaded via the browser.
type InputFileOrString interface {
Attach(name string, data map[string]FileReader) error
getValue() string
}
var (
_ InputFileOrString = &FileReader{}
_ InputFile = &FileReader{}
)
type FileReader struct {
Name string
Data io.Reader
value string
}
func (f *FileReader) MarshalJSON() ([]byte, error) {
return json.Marshal(f.getValue())
}
var ErrAttachmentKeyAlreadyExists = errors.New("key already exists")
func (f *FileReader) justFiles() {}
func (f *FileReader) Attach(key string, data map[string]FileReader) error {
if f.Data == nil {
// if no data, this must be a string; nothing to "attach".
return nil
}
if _, ok := data[key]; ok {
return ErrAttachmentKeyAlreadyExists
}
f.value = "attach://" + key
data[key] = *f
return nil
}
// getValue returns the file attach reference for the relevant multipart form.
// Make sure to only call getValue after having called Attach(), to ensure any files have been included.
func (f *FileReader) getValue() string {
return f.value
}
// InputFileByURL is used to send a file on the internet via a publicly accessible HTTP URL.
func InputFileByURL(url string) InputFileOrString {
return &FileReader{value: url}
}
// InputFileByID is used to send a file that is already present on telegram's servers, using its telegram file_id.
func InputFileByID(fileID string) InputFileOrString {
return &FileReader{value: fileID}
}
// InputFileByReader is used to send a file by a reader interface; such as a filehandle from os.Open(), or from a byte
// buffer.
//
// For example:
//
// f, err := os.Open("some_file.go")
// if err != nil {
// return fmt.Errorf("failed to open file: %w", err)
// }
//
// m, err := b.SendDocument(<chat_id>, gotgbot.InputFileByReader("source.go", f), nil)
//
// Or
//
// m, err := b.SendDocument(<chat_id>, gotgbot.InputFileByReader("file.txt", strings.NewReader("Some file contents")), nil)
func InputFileByReader(name string, r io.Reader) InputFile {
return &FileReader{Name: name, Data: r}
}

View File

@@ -15,25 +15,27 @@ var mdMap = map[string]string{
}
var mdV2Map = map[string]string{
"bold": "*",
"italic": "_",
"code": "`",
"pre": "```",
"underline": "__",
"strikethrough": "~",
"spoiler": "||",
"blockquote": ">",
"bold": "*",
"italic": "_",
"code": "`",
"pre": "```",
"underline": "__",
"strikethrough": "~",
"spoiler": "||",
"blockquote": ">",
"expandable_blockquote": "**>",
}
var htmlMap = map[string]string{
"bold": "b",
"italic": "i",
"code": "code",
"pre": "pre",
"underline": "u",
"strikethrough": "s",
"spoiler": "span class=\"tg-spoiler\"",
"blockquote": "blockquote",
"bold": "b",
"italic": "i",
"code": "code",
"pre": "pre",
"underline": "u",
"strikethrough": "s",
"spoiler": "span class=\"tg-spoiler\"",
"blockquote": "blockquote",
"expandable_blockquote": "blockquote expandable",
}
// OriginalMD gets the original markdown formatting of a message text.
@@ -209,6 +211,8 @@ func writeFinalHTML(data []uint16, ent MessageEntity, start int64, cntnt string)
return prevText + `<a href="` + ent.Url + `">` + cntnt + "</a>"
case "blockquote":
return prevText + `<blockquote>` + cntnt + "</blockquote>"
case "expandable_blockquote":
return prevText + `<blockquote expandable>` + cntnt + "</blockquote>"
default:
return prevText + cntnt
}
@@ -243,6 +247,8 @@ func writeFinalMarkdownV2(data []uint16, ent MessageEntity, start int64, cntnt s
return prevText + pre + "[" + cleanCntnt + "](" + ent.Url + ")" + post
case "blockquote":
return prevText + pre + ">" + strings.Join(strings.Split(cleanCntnt, "\n"), "\n>") + post
case "expandable_blockquote":
return prevText + pre + "**>" + strings.Join(strings.Split(cleanCntnt, "\n"), "\n>") + "||" + post
default:
return prevText + cntnt
}

View File

@@ -20,6 +20,7 @@ const (
UpdateTypeCallbackQuery = "callback_query"
UpdateTypeShippingQuery = "shipping_query"
UpdateTypePreCheckoutQuery = "pre_checkout_query"
UpdateTypePurchasedPaidMedia = "purchased_paid_media"
UpdateTypePoll = "poll"
UpdateTypePollAnswer = "poll_answer"
UpdateTypeMyChatMember = "my_chat_member"
@@ -77,6 +78,9 @@ func (u Update) GetType() string {
case u.PreCheckoutQuery != nil:
return UpdateTypePreCheckoutQuery
case u.PurchasedPaidMedia != nil:
return UpdateTypePurchasedPaidMedia
case u.Poll != nil:
return UpdateTypePoll
@@ -111,6 +115,21 @@ const (
ParseModeNone = ""
)
// The consts listed below represent all the chat action options that can be sent to telegram.
const (
ChatActionTyping = "typing"
ChatActionUploadPhoto = "upload_photo"
ChatActionRecordVideo = "record_video"
ChatActionUploadVideo = "upload_video"
ChatActionRecordVoice = "record_voice"
ChatActionUploadVoice = "upload_voice"
ChatActionUploadDocument = "upload_document"
ChatActionChooseSticker = "choose_sticker"
ChatActionFindLocation = "find_location"
ChatActionRecordVideoNote = "record_video_note"
ChatActionUploadVideoNote = "upload_video_note"
)
// The consts listed below represent all the sticker types that can be obtained from telegram.
const (
StickerTypeRegular = "regular"

View File

@@ -33,6 +33,11 @@ func (c Chat) CreateInviteLink(b *Bot, opts *CreateChatInviteLinkOpts) (*ChatInv
return b.CreateChatInviteLink(c.Id, opts)
}
// CreateSubscriptionInviteLink Helper method for Bot.CreateChatSubscriptionInviteLink.
func (c Chat) CreateSubscriptionInviteLink(b *Bot, subscriptionPeriod int64, subscriptionPrice int64, opts *CreateChatSubscriptionInviteLinkOpts) (*ChatInviteLink, error) {
return b.CreateChatSubscriptionInviteLink(c.Id, subscriptionPeriod, subscriptionPrice, opts)
}
// DeclineJoinRequest Helper method for Bot.DeclineChatJoinRequest.
func (c Chat) DeclineJoinRequest(b *Bot, userId int64, opts *DeclineChatJoinRequestOpts) (bool, error) {
return b.DeclineChatJoinRequest(c.Id, userId, opts)
@@ -53,6 +58,11 @@ func (c Chat) EditInviteLink(b *Bot, inviteLink string, opts *EditChatInviteLink
return b.EditChatInviteLink(c.Id, inviteLink, opts)
}
// EditSubscriptionInviteLink Helper method for Bot.EditChatSubscriptionInviteLink.
func (c Chat) EditSubscriptionInviteLink(b *Bot, inviteLink string, opts *EditChatSubscriptionInviteLinkOpts) (*ChatInviteLink, error) {
return b.EditChatSubscriptionInviteLink(c.Id, inviteLink, opts)
}
// ExportInviteLink Helper method for Bot.ExportChatInviteLink.
func (c Chat) ExportInviteLink(b *Bot, opts *ExportChatInviteLinkOpts) (string, error) {
return b.ExportChatInviteLink(c.Id, opts)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -21,9 +21,7 @@ const (
type BotClient interface {
// RequestWithContext submits a POST HTTP request a bot API instance.
RequestWithContext(ctx context.Context, token string, method string, params map[string]string, data map[string]NamedReader, opts *RequestOpts) (json.RawMessage, error)
// TimeoutContext calculates the required timeout contect required given the passed RequestOpts, and any default opts defined by the BotClient.
TimeoutContext(opts *RequestOpts) (context.Context, context.CancelFunc)
RequestWithContext(ctx context.Context, token string, method string, params map[string]string, data map[string]FileReader, opts *RequestOpts) (json.RawMessage, error)
// GetAPIURL gets the URL of the API either in use by the bot or defined in the request opts.
GetAPIURL(opts *RequestOpts) string
// FileURL gets the URL of a file at the API address that the bot is interacting with.
@@ -74,24 +72,6 @@ func (t *TelegramError) Error() string {
return fmt.Sprintf("unable to %s: %s", t.Method, t.Description)
}
type NamedReader interface {
Name() string
io.Reader
}
type NamedFile struct {
File io.Reader
FileName string
}
func (nf NamedFile) Read(p []byte) (n int, err error) {
return nf.File.Read(p)
}
func (nf NamedFile) Name() string {
return nf.FileName
}
// RequestOpts defines any request-specific options used to interact with the telegram API.
type RequestOpts struct {
// Timeout for the HTTP request to the telegram API.
@@ -100,38 +80,45 @@ type RequestOpts struct {
APIURL string
}
// TimeoutContext returns the appropriate context for the current settings.
func (bot *BaseBotClient) TimeoutContext(opts *RequestOpts) (context.Context, context.CancelFunc) {
// getTimeoutContext returns the appropriate context for the current settings.
func (bot *BaseBotClient) getTimeoutContext(parentCtx context.Context, opts *RequestOpts) (context.Context, context.CancelFunc) {
if parentCtx == nil {
parentCtx = context.Background()
}
if opts != nil {
ctx, cancelFunc := timeoutFromOpts(opts)
ctx, cancelFunc := timeoutFromOpts(parentCtx, opts)
if ctx != nil {
return ctx, cancelFunc
}
}
if bot.DefaultRequestOpts != nil {
ctx, cancelFunc := timeoutFromOpts(bot.DefaultRequestOpts)
ctx, cancelFunc := timeoutFromOpts(parentCtx, bot.DefaultRequestOpts)
if ctx != nil {
return ctx, cancelFunc
}
}
return context.WithTimeout(context.Background(), DefaultTimeout)
return context.WithTimeout(parentCtx, DefaultTimeout)
}
func timeoutFromOpts(opts *RequestOpts) (context.Context, context.CancelFunc) {
func timeoutFromOpts(parentCtx context.Context, opts *RequestOpts) (context.Context, context.CancelFunc) {
// nothing? no timeout.
if opts == nil {
return nil, nil
}
if parentCtx == nil {
parentCtx = context.Background()
}
if opts.Timeout > 0 {
// > 0 timeout defined.
return context.WithTimeout(context.Background(), opts.Timeout)
return context.WithTimeout(parentCtx, opts.Timeout)
} else if opts.Timeout < 0 {
// < 0 no timeout; infinite.
return context.Background(), func() {}
return parentCtx, func() {}
}
// 0 == nothing defined, use defaults.
return nil, nil
@@ -142,28 +129,40 @@ func timeoutFromOpts(opts *RequestOpts) (context.Context, context.CancelFunc) {
// - method: the telegram API method to call.
// - params: map of parameters to be sending to the telegram API. eg: chat_id, user_id, etc.
// - data: map of any files to be sending to the telegram API.
// - opts: request opts to use. Note: Timeout opts are ignored when used in RequestWithContext. Timeout handling is the
// responsibility of the caller/context owner.
func (bot *BaseBotClient) RequestWithContext(ctx context.Context, token string, method string, params map[string]string, data map[string]NamedReader, opts *RequestOpts) (json.RawMessage, error) {
b := &bytes.Buffer{}
// - opts: request opts to use.
func (bot *BaseBotClient) RequestWithContext(parentCtx context.Context, token string, method string, params map[string]string, data map[string]FileReader, opts *RequestOpts) (json.RawMessage, error) {
ctx, cancel := bot.getTimeoutContext(parentCtx, opts)
defer cancel()
var requestBody io.Reader
var contentType string
// Check if there are any files to upload. If yes, use multipart; else, use JSON.
if len(data) > 0 {
var err error
contentType, err = fillBuffer(b, params, data)
if err != nil {
return nil, fmt.Errorf("failed to fill buffer with parameters and file data: %w", err)
}
pr, pw := io.Pipe()
defer pr.Close() // avoid writer goroutine leak
mw := multipart.NewWriter(pw)
contentType = mw.FormDataContentType()
requestBody = pr
// Write the request data asynchronously from another goroutine
// to the multipart.Writer which will be piped into the pipe reader
// which is tied to the request to be sent
go func() {
writerError := fillBuffer(mw, params, data)
// Close the writer with error of multipart writer.
// If the error is nil, this will act just like pw.Close()
_ = pw.CloseWithError(writerError)
}()
} else {
contentType = "application/json"
err := json.NewEncoder(b).Encode(params)
bodyBytes, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("failed to encode parameters as JSON: %w", err)
}
requestBody = bytes.NewReader(bodyBytes)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, bot.methodEndpoint(token, method, opts), b)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, bot.methodEndpoint(token, method, opts), requestBody)
if err != nil {
return nil, fmt.Errorf("failed to build POST request to %s: %w", method, err)
}
@@ -194,38 +193,37 @@ func (bot *BaseBotClient) RequestWithContext(ctx context.Context, token string,
return r.Result, nil
}
func fillBuffer(b *bytes.Buffer, params map[string]string, data map[string]NamedReader) (string, error) {
w := multipart.NewWriter(b)
// Fill the buffer of multipart.Writer with data which is going to be sent.
func fillBuffer(w *multipart.Writer, params map[string]string, data map[string]FileReader) error {
for k, v := range params {
err := w.WriteField(k, v)
if err != nil {
return "", fmt.Errorf("failed to write multipart field %s with value %s: %w", k, v, err)
return fmt.Errorf("failed to write multipart field %s with value %s: %w", k, v, err)
}
}
for field, file := range data {
fileName := file.Name()
fileName := file.Name
if fileName == "" {
fileName = field
}
part, err := w.CreateFormFile(field, fileName)
if err != nil {
return "", fmt.Errorf("failed to create form file for field %s and fileName %s: %w", field, fileName, err)
return fmt.Errorf("failed to create form file for field %s and fileName %s: %w", field, fileName, err)
}
_, err = io.Copy(part, file)
_, err = io.Copy(part, file.Data)
if err != nil {
return "", fmt.Errorf("failed to copy file contents of field %s to form: %w", field, err)
return fmt.Errorf("failed to copy file contents of field %s to form: %w", field, err)
}
}
if err := w.Close(); err != nil {
return "", fmt.Errorf("failed to close multipart form writer: %w", err)
return fmt.Errorf("failed to close multipart form writer: %w", err)
}
return w.FormDataContentType(), nil
return nil
}
// GetAPIURL returns the currently used API endpoint.

View File

@@ -1 +1 @@
68843d1ae456b90a494bfee92253f30a8204b2a7
15b6cd15658668b7017f3c069e27405ca4f4428e

2
vendor/modules.txt vendored
View File

@@ -22,7 +22,7 @@ github.com/Microsoft/go-winio/internal/fs
github.com/Microsoft/go-winio/internal/socket
github.com/Microsoft/go-winio/internal/stringbuffer
github.com/Microsoft/go-winio/pkg/guid
# github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.27
# github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.30
## explicit; go 1.19
github.com/PaulSonOfLars/gotgbot/v2
# github.com/PuerkitoBio/goquery v1.8.1