Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion file.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ type InputFile interface {
// 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, w *multipart.Writer) error
Attach
getValue() string
}

// Attach is the core interface to attach items to a multipart writer. It gets reused across InputFile, InputMedia, etc.
type Attach interface {
Attach(name string, w *multipart.Writer) error
}

var (
_ InputFileOrString = &FileReader{}
_ InputFile = &FileReader{}
Expand Down
12 changes: 6 additions & 6 deletions gen_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -946,12 +946,12 @@ type CreateNewStickerSetOpts struct {
// - title (type string): Sticker set title, 1-64 characters
// - stickers (type []InputSticker): A JSON-serialized list of 1-50 initial stickers to be added to the sticker set
// - opts (type CreateNewStickerSetOpts): All optional parameters.
func (bot *Bot) CreateNewStickerSet(userId int64, name string, title string, stickers []InputSticker, opts *CreateNewStickerSetOpts) (bool, error) {
func (bot *Bot) CreateNewStickerSet(userId int64, name string, title string, stickers InputStickers, opts *CreateNewStickerSetOpts) (bool, error) {
return bot.CreateNewStickerSetWithContext(context.Background(), userId, name, title, stickers, opts)
}

// CreateNewStickerSetWithContext is the same as Bot.CreateNewStickerSet, but with a context.Context parameter
func (bot *Bot) CreateNewStickerSetWithContext(ctx context.Context, userId int64, name string, title string, stickers []InputSticker, opts *CreateNewStickerSetOpts) (bool, error) {
func (bot *Bot) CreateNewStickerSetWithContext(ctx context.Context, userId int64, name string, title string, stickers InputStickers, opts *CreateNewStickerSetOpts) (bool, error) {
v := map[string]any{}
v["user_id"] = userId
v["name"] = name
Expand Down Expand Up @@ -5073,12 +5073,12 @@ type SendMediaGroupOpts struct {
// - chatId (type int64): Unique identifier for the target chat
// - media (type []InputMedia): A JSON-serialized array describing messages to be sent, must include 2-10 items
// - opts (type SendMediaGroupOpts): All optional parameters.
func (bot *Bot) SendMediaGroup(chatId int64, media []InputMedia, opts *SendMediaGroupOpts) ([]Message, error) {
func (bot *Bot) SendMediaGroup(chatId int64, media InputMedias, opts *SendMediaGroupOpts) ([]Message, error) {
return bot.SendMediaGroupWithContext(context.Background(), chatId, media, opts)
}

// SendMediaGroupWithContext is the same as Bot.SendMediaGroup, but with a context.Context parameter
func (bot *Bot) SendMediaGroupWithContext(ctx context.Context, chatId int64, media []InputMedia, opts *SendMediaGroupOpts) ([]Message, error) {
func (bot *Bot) SendMediaGroupWithContext(ctx context.Context, chatId int64, media InputMedias, opts *SendMediaGroupOpts) ([]Message, error) {
v := map[string]any{}
v["chat_id"] = chatId
v["media"] = media
Expand Down Expand Up @@ -5274,12 +5274,12 @@ type SendPaidMediaOpts struct {
// - starCount (type int64): The number of Telegram Stars that must be paid to buy access to the media; 1-25000
// - media (type []InputPaidMedia): A JSON-serialized array describing the media to be sent; up to 10 items
// - opts (type SendPaidMediaOpts): All optional parameters.
func (bot *Bot) SendPaidMedia(chatId int64, starCount int64, media []InputPaidMedia, opts *SendPaidMediaOpts) (*Message, error) {
func (bot *Bot) SendPaidMedia(chatId int64, starCount int64, media InputPaidMedias, opts *SendPaidMediaOpts) (*Message, error) {
return bot.SendPaidMediaWithContext(context.Background(), chatId, starCount, media, opts)
}

// SendPaidMediaWithContext is the same as Bot.SendPaidMedia, but with a context.Context parameter
func (bot *Bot) SendPaidMediaWithContext(ctx context.Context, chatId int64, starCount int64, media []InputPaidMedia, opts *SendPaidMediaOpts) (*Message, error) {
func (bot *Bot) SendPaidMediaWithContext(ctx context.Context, chatId int64, starCount int64, media InputPaidMedias, opts *SendPaidMediaOpts) (*Message, error) {
v := map[string]any{}
v["chat_id"] = chatId
v["star_count"] = starCount
Expand Down
64 changes: 52 additions & 12 deletions gen_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"mime/multipart"
"strconv"
)

type ReplyMarkup interface {
Expand Down Expand Up @@ -5050,8 +5051,8 @@ func (v InputLocationMessageContent) inputMessageContent() {}
type InputMedia interface {
GetType() string
GetMedia() InputFileOrString
// InputParams allows for uploading attachments with files.
InputParams(string, *multipart.Writer) error
// Attach allows for uploading attachments with files.
Attach
// MergeInputMedia returns a MergedInputMedia struct to simplify working with complex telegram types in a non-generic world.
MergeInputMedia() MergedInputMedia
// inputMedia exists to avoid external types implementing this interface.
Expand Down Expand Up @@ -5123,6 +5124,19 @@ func (v MergedInputMedia) MergeInputMedia() MergedInputMedia {
return v
}

type InputMedias []InputMedia

func (ts InputMedias) Attach(k string, w *multipart.Writer) error {
for idx, item := range ts {
err := item.Attach(k+"_"+strconv.Itoa(idx), w)
if err != nil {
return fmt.Errorf("failed to attach to multipart field: %w", err)
}
}

return nil
}

// InputMediaAnimation (https://core.telegram.org/bots/api#inputmediaanimation)
//
// Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent.
Expand Down Expand Up @@ -5192,7 +5206,7 @@ func (v InputMediaAnimation) MarshalJSON() ([]byte, error) {
// InputMediaAnimation.inputMedia is a dummy method to avoid interface implementation.
func (v InputMediaAnimation) inputMedia() {}

func (v InputMediaAnimation) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputMediaAnimation) Attach(mediaName string, w *multipart.Writer) error {
if v.Media != nil {
err := v.Media.Attach(mediaName, w)
if err != nil {
Expand Down Expand Up @@ -5273,7 +5287,7 @@ func (v InputMediaAudio) MarshalJSON() ([]byte, error) {
// InputMediaAudio.inputMedia is a dummy method to avoid interface implementation.
func (v InputMediaAudio) inputMedia() {}

func (v InputMediaAudio) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputMediaAudio) Attach(mediaName string, w *multipart.Writer) error {
if v.Media != nil {
err := v.Media.Attach(mediaName, w)
if err != nil {
Expand Down Expand Up @@ -5348,7 +5362,7 @@ func (v InputMediaDocument) MarshalJSON() ([]byte, error) {
// InputMediaDocument.inputMedia is a dummy method to avoid interface implementation.
func (v InputMediaDocument) inputMedia() {}

func (v InputMediaDocument) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputMediaDocument) Attach(mediaName string, w *multipart.Writer) error {
if v.Media != nil {
err := v.Media.Attach(mediaName, w)
if err != nil {
Expand Down Expand Up @@ -5423,7 +5437,7 @@ func (v InputMediaPhoto) MarshalJSON() ([]byte, error) {
// InputMediaPhoto.inputMedia is a dummy method to avoid interface implementation.
func (v InputMediaPhoto) inputMedia() {}

func (v InputMediaPhoto) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputMediaPhoto) Attach(mediaName string, w *multipart.Writer) error {
if v.Media != nil {
err := v.Media.Attach(mediaName, w)
if err != nil {
Expand Down Expand Up @@ -5512,7 +5526,7 @@ func (v InputMediaVideo) MarshalJSON() ([]byte, error) {
// InputMediaVideo.inputMedia is a dummy method to avoid interface implementation.
func (v InputMediaVideo) inputMedia() {}

func (v InputMediaVideo) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputMediaVideo) Attach(mediaName string, w *multipart.Writer) error {
if v.Media != nil {
err := v.Media.Attach(mediaName, w)
if err != nil {
Expand Down Expand Up @@ -5560,8 +5574,8 @@ var (
type InputPaidMedia interface {
GetType() string
GetMedia() InputFileOrString
// InputParams allows for uploading attachments with files.
InputParams(string, *multipart.Writer) error
// Attach allows for uploading attachments with files.
Attach
// MergeInputPaidMedia returns a MergedInputPaidMedia struct to simplify working with complex telegram types in a non-generic world.
MergeInputPaidMedia() MergedInputPaidMedia
// inputPaidMedia exists to avoid external types implementing this interface.
Expand Down Expand Up @@ -5614,6 +5628,19 @@ func (v MergedInputPaidMedia) MergeInputPaidMedia() MergedInputPaidMedia {
return v
}

type InputPaidMedias []InputPaidMedia

func (ts InputPaidMedias) Attach(k string, w *multipart.Writer) error {
for idx, item := range ts {
err := item.Attach(k+"_"+strconv.Itoa(idx), w)
if err != nil {
return fmt.Errorf("failed to attach to multipart field: %w", err)
}
}

return nil
}

// InputPaidMediaPhoto (https://core.telegram.org/bots/api#inputpaidmediaphoto)
//
// The paid media to send is a photo.
Expand Down Expand Up @@ -5656,7 +5683,7 @@ func (v InputPaidMediaPhoto) MarshalJSON() ([]byte, error) {
// InputPaidMediaPhoto.inputPaidMedia is a dummy method to avoid interface implementation.
func (v InputPaidMediaPhoto) inputPaidMedia() {}

func (v InputPaidMediaPhoto) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputPaidMediaPhoto) Attach(mediaName string, w *multipart.Writer) error {
if v.Media != nil {
err := v.Media.Attach(mediaName, w)
if err != nil {
Expand Down Expand Up @@ -5730,7 +5757,7 @@ func (v InputPaidMediaVideo) MarshalJSON() ([]byte, error) {
// InputPaidMediaVideo.inputPaidMedia is a dummy method to avoid interface implementation.
func (v InputPaidMediaVideo) inputPaidMedia() {}

func (v InputPaidMediaVideo) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputPaidMediaVideo) Attach(mediaName string, w *multipart.Writer) error {
if v.Media != nil {
err := v.Media.Attach(mediaName, w)
if err != nil {
Expand Down Expand Up @@ -5897,7 +5924,7 @@ type InputSticker struct {
Keywords []string `json:"keywords,omitempty"`
}

func (v InputSticker) InputParams(mediaName string, w *multipart.Writer) error {
func (v InputSticker) Attach(mediaName string, w *multipart.Writer) error {
if v.Sticker != nil {
err := v.Sticker.Attach(mediaName, w)
if err != nil {
Expand All @@ -5908,6 +5935,19 @@ func (v InputSticker) InputParams(mediaName string, w *multipart.Writer) error {
return nil
}

type InputStickers []InputSticker

func (ts InputStickers) Attach(k string, w *multipart.Writer) error {
for idx, item := range ts {
err := item.Attach(k+"_"+strconv.Itoa(idx), w)
if err != nil {
return fmt.Errorf("failed to attach to multipart field: %w", err)
}
}

return nil
}

// InputStoryContent (https://core.telegram.org/bots/api#inputstorycontent)
//
// This object describes the content of a story to post. Currently, it can be one of
Expand Down
28 changes: 6 additions & 22 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"mime/multipart"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
Expand Down Expand Up @@ -241,39 +240,24 @@ func getFieldContents(v any, k string, w *multipart.Writer) (string, error) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
return fmt.Sprint(val), nil

case InputMedia:
err := val.InputParams(k, w)
case Attach:
err := val.Attach(k, w)
if err != nil {
return "", fmt.Errorf("failed to read input multipart field: %w", err)
}

bs, err := json.Marshal(val)
if err != nil {
return "", fmt.Errorf("failed to marshal field %s to JSON: %w", k, err)
}
return string(bs), nil

case []InputMedia:
for idx, item := range val {
err := item.InputParams(k+"_"+strconv.Itoa(idx), w)
if err != nil {
return "", fmt.Errorf("failed to read input multipart field: %w", err)
}
// In case of a simple inputfile attachment, rely on files
if inputFile, ok := val.(InputFile); ok {
return inputFile.getValue(), nil
}

// For complex types (structs, maps, slices, etc.), marshal as JSON
bs, err := json.Marshal(val)
if err != nil {
return "", fmt.Errorf("failed to marshal field %s to JSON: %w", k, err)
}
return string(bs), nil

case InputFile:
err := val.Attach(k, w)
if err != nil {
return "", fmt.Errorf("failed to attach field %s: %w", k, err)
}
return val.getValue(), nil

default:
// For complex types (structs, maps, slices, etc.), marshal as JSON
bs, err := json.Marshal(val)
Expand Down
25 changes: 24 additions & 1 deletion scripts/generate/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func (td TypeDescription) receiverName() string {
return receiver(td.Name)
}

func (td TypeDescription) pluralisedName() string {
return td.Name + "s"
}

func receiver(n string) string {
var rs []rune
for _, r := range n {
Expand All @@ -40,7 +44,26 @@ func receiver(n string) string {
return strings.ToLower(string(rs))
}

func (td TypeDescription) sentByAPI(d APIDescription) bool {
func (td TypeDescription) sentAsArray(d APIDescription) bool {
for _, v := range d.Methods {
for _, f := range v.Fields {
t, err := f.getPreferredType(d)
if err != nil {
return false
}
s, ok := strings.CutPrefix(t, "[]")
if !ok {
continue
}
if s == toGoType(td.Name) {
return true
}
}
}
return false
}

func (td TypeDescription) receivedFromAPI(d APIDescription) bool {
checked := map[string]bool{}

for _, m := range d.Methods {
Expand Down
6 changes: 6 additions & 0 deletions scripts/generate/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,12 @@ func (fs Fields) getFunctionArgs(d APIDescription) ([]string, error) {
return nil, fmt.Errorf("failed to get preferred type: %w", err)
}

if coreType, isArray := strings.CutPrefix(fieldType, "[]"); isArray && isTgType(d, coreType) && needsArrayAttachment(d, d.Types[coreType]) {
// If we're passing in a telegram-type array for a type which uses our Attach interface, then we should use our pluralised version of it.
// This implements the Attach interface on the underlying list items, simplifying our sending logic.
fieldType = d.Types[coreType].pluralisedName()
}

args = append(args, fmt.Sprintf("%s %s", snakeToCamel(f.Name), fieldType))
}
return args, nil
Expand Down
Loading
Loading