Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.
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
6 changes: 4 additions & 2 deletions _oas/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -4634,7 +4634,8 @@
"description": "A JSON-serialized array of suggested amounts of tip in the smallest units of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount",
"type": "array",
"items": {
"type": "integer"
"type": "integer",
"format": "int64"
}
},
"provider_data": {
Expand Down Expand Up @@ -8760,7 +8761,8 @@
"description": "A JSON-serialized array of suggested amounts of tips in the smallest units of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount",
"type": "array",
"items": {
"type": "integer"
"type": "integer",
"format": "int64"
}
},
"start_parameter": {
Expand Down
31 changes: 28 additions & 3 deletions botdoc/oas.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,7 @@ Schemas:
}
p["/"+m.Name] = item
}
addMissedProperties(c.Schemas)
return &ogen.Spec{
return patchSchema(&ogen.Spec{
OpenAPI: "3.0.3",
Info: ogen.Info{
Title: "Telegram Bot API",
Expand All @@ -509,7 +508,33 @@ Schemas:
},
Paths: p,
Components: c,
}, nil
}), nil
}

func patchSchema(spec *ogen.Spec) *ogen.Spec {
c := spec.Components
addMissedProperties(c.Schemas)
updateProperty := func(typeName, propName string, cb func(p *ogen.Property)) {
schema := c.Schemas[typeName]
props := schema.Properties

for i := range props {
if props[i].Name == propName {
cb(&props[i])
return
}
}
panic(fmt.Sprintf("property %q of %q not found", propName, typeName))
}
setItemsFormat := func(typeName, propName, format string) {
updateProperty(typeName, propName, func(p *ogen.Property) {
p.Schema.Items.Format = format
})
}
// MTProto uses int64, use it in BotAPI too to reduce copying.
setItemsFormat("sendInvoice", "suggested_tip_amounts", "int64")
setItemsFormat("InputInvoiceMessageContent", "suggested_tip_amounts", "int64")
return spec
}

func addMissedProperties(schemas map[string]*ogen.Schema) {
Expand Down
25 changes: 24 additions & 1 deletion internal/botapi/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,30 @@ func (b *BotAPI) DeclineChatJoinRequest(ctx context.Context, req oas.DeclineChat

// DeleteChatPhoto implements oas.Handler.
func (b *BotAPI) DeleteChatPhoto(ctx context.Context, req oas.DeleteChatPhoto) (oas.Result, error) {
return oas.Result{}, &NotImplementedError{}
p, err := b.resolveIDToChat(ctx, req.ChatID)
if err != nil {
return oas.Result{}, errors.Wrap(err, "resolve chatID")
}

switch p := p.(type) {
case peers.Channel:
_, err = b.raw.ChannelsEditPhoto(ctx, &tg.ChannelsEditPhotoRequest{
Channel: p.InputChannel(),
Photo: &tg.InputChatPhotoEmpty{},
})
case peers.Chat:
_, err = b.raw.MessagesEditChatPhoto(ctx, &tg.MessagesEditChatPhotoRequest{
ChatID: p.ID(),
Photo: &tg.InputChatPhotoEmpty{},
})
default:
return oas.Result{}, errors.Errorf("unexpected type %T", p)
}
if err != nil {
return oas.Result{}, errors.Wrap(err, "delete photo")
}

return resultOK(true), nil
}

// DeleteChatStickerSet implements oas.Handler.
Expand Down
5 changes: 5 additions & 0 deletions internal/botapi/chat_member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import (
func TestBotAPI_GetChatMemberCount(t *testing.T) {
ctx := context.Background()
testWithCache(t, func(a *require.Assertions, mock *tgmock.Mock, api *BotAPI) {
_, err := api.GetChatMemberCount(ctx, oas.GetChatMemberCount{
ChatID: oas.NewStringID(`aboba`),
})
a.Error(err)

r, err := api.GetChatMemberCount(ctx, oas.GetChatMemberCount{
ChatID: oas.NewInt64ID(testChatID()),
})
Expand Down
40 changes: 37 additions & 3 deletions internal/botapi/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,17 @@ func Test_convertToBotAPIChatPermissions(t *testing.T) {
func TestBotAPI_SetChatDescription(t *testing.T) {
ctx := context.Background()
testWithCache(t, func(a *require.Assertions, mock *tgmock.Mock, api *BotAPI) {
_, err := api.SetChatDescription(ctx, oas.SetChatDescription{
ChatID: oas.NewStringID(`aboba`),
Description: oas.OptString{},
})
a.Error(err)

mock.ExpectCall(&tg.MessagesEditChatAboutRequest{
Peer: &tg.InputPeerChat{ChatID: 10},
About: "",
}).ThenTrue()
_, err := api.SetChatDescription(ctx, oas.SetChatDescription{
_, err = api.SetChatDescription(ctx, oas.SetChatDescription{
ChatID: oas.NewInt64ID(testChatID()),
Description: oas.OptString{},
})
Expand Down Expand Up @@ -170,11 +176,17 @@ func TestBotAPI_GetChat(t *testing.T) {
func TestBotAPI_SetChatTitle(t *testing.T) {
ctx := context.Background()
testWithCache(t, func(a *require.Assertions, mock *tgmock.Mock, api *BotAPI) {
_, err := api.SetChatTitle(ctx, oas.SetChatTitle{
ChatID: oas.NewStringID(`aboba`),
Title: "aboba",
})
a.Error(err)

mock.ExpectCall(&tg.MessagesEditChatTitleRequest{
ChatID: testChat().ID,
Title: "aboba",
}).ThenResult(&tg.Updates{})
_, err := api.SetChatTitle(ctx, oas.SetChatTitle{
_, err = api.SetChatTitle(ctx, oas.SetChatTitle{
ChatID: oas.NewInt64ID(testChatID()),
Title: "aboba",
})
Expand All @@ -185,13 +197,35 @@ func TestBotAPI_SetChatTitle(t *testing.T) {
func TestBotAPI_LeaveChat(t *testing.T) {
ctx := context.Background()
testWithCache(t, func(a *require.Assertions, mock *tgmock.Mock, api *BotAPI) {
_, err := api.LeaveChat(ctx, oas.LeaveChat{
ChatID: oas.NewStringID(`aboba`),
})
a.Error(err)

mock.ExpectCall(&tg.MessagesDeleteChatUserRequest{
ChatID: testChat().ID,
UserID: &tg.InputUserSelf{},
}).ThenResult(&tg.Updates{})
_, err := api.LeaveChat(ctx, oas.LeaveChat{
_, err = api.LeaveChat(ctx, oas.LeaveChat{
ChatID: oas.NewInt64ID(testChatID()),
})
a.NoError(err)
})
}

func TestBotAPI_DeleteChatPhoto(t *testing.T) {
ctx := context.Background()
testWithCache(t, func(a *require.Assertions, mock *tgmock.Mock, api *BotAPI) {
mock.ExpectCall(&tg.ChannelsEditPhotoRequest{
Channel: &tg.InputChannel{
ChannelID: testChannel().ID,
AccessHash: testChannel().AccessHash,
},
Photo: &tg.InputChatPhotoEmpty{},
}).ThenResult(&tg.Updates{})
_, err := api.DeleteChatPhoto(ctx, oas.DeleteChatPhoto{
ChatID: oas.NewInt64ID(testChannelID()),
})
a.NoError(err)
})
}
49 changes: 2 additions & 47 deletions internal/botapi/convert_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package botapi

import (
"context"
"strconv"

"github.com/go-faster/errors"
"go.uber.org/zap"
Expand Down Expand Up @@ -360,54 +359,10 @@ func (b *BotAPI) convertMessageMedia(ctx context.Context, media tg.MessageMediaC
location.ProximityAlertRadius = optInt(media.GetProximityNotificationRadius)
r.Location.SetTo(location)
case *tg.MessageMediaPoll:
var (
poll = media.Poll
results = media.Results

typ = oas.PollTypeRegular
)
if a, r := len(poll.Answers), len(results.Results); a != r {
b.logger.Warn("Got poll where len(answers) != len(results)",
zap.Int("answers", a),
zap.Int("results", r),
)
resultPoll, ok := b.convertToBotAPIPoll(ctx, media)
if !ok {
break
}

if poll.Quiz {
typ = oas.PollTypeQuiz
}
resultPoll := oas.Poll{
ID: strconv.FormatInt(poll.ID, 10),
Question: poll.Question,
Options: nil,
TotalVoterCount: results.TotalVoters,
IsClosed: poll.Closed,
IsAnonymous: !poll.PublicVoters,
Type: typ,
AllowsMultipleAnswers: poll.MultipleChoice,
CorrectOptionID: oas.OptInt{},
Explanation: optString(results.GetSolution),
ExplanationEntities: nil,
OpenPeriod: optInt(poll.GetClosePeriod),
CloseDate: optInt(poll.GetCloseDate),
}

if e := results.SolutionEntities; len(e) > 0 {
resultPoll.ExplanationEntities = b.convertToBotAPIEntities(ctx, e)
}

// SAFETY: length equality checked above.
for i, result := range results.Results {
if result.Correct {
resultPoll.CorrectOptionID.SetTo(i)
}
resultPoll.Options = append(resultPoll.Options, oas.PollOption{
Text: poll.Answers[i].Text,
VoterCount: result.Voters,
})
}

r.Poll.SetTo(resultPoll)
case *tg.MessageMediaDice:
r.Dice.SetTo(oas.Dice{
Expand Down
14 changes: 8 additions & 6 deletions internal/botapi/errors_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,15 @@ func (b *BotAPI) NewError(ctx context.Context, err error) (r oas.ErrorStatusCode
return resp
}

// NotFound is default not found handler.
func NotFound(w http.ResponseWriter, _ *http.Request) {
apiError := errorOf(http.StatusNotFound)

var encodedNotFoundError = func() []byte {
e := jx.GetEncoder()
defer jx.PutEncoder(e)

apiError.Encode(e)
_, _ = e.WriteTo(w)
errorOf(http.StatusNotFound).Encode(e)
return e.Bytes()
}()

// NotFound is default not found handler.
func NotFound(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write(encodedNotFoundError)
}
18 changes: 18 additions & 0 deletions internal/botapi/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package botapi

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestBadRequestError_Error(t *testing.T) {
msg := "hello"
require.Equal(t, (&BadRequestError{Message: msg}).Error(), msg)
}

func TestNotImplementedError_Error(t *testing.T) {
msg := "hello"
require.Equal(t, (&NotImplementedError{Message: msg}).Error(), msg)
require.NotEmpty(t, (&NotImplementedError{}).Error())
}
5 changes: 0 additions & 5 deletions internal/botapi/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,3 @@ func (b *BotAPI) EditMessageText(ctx context.Context, req oas.EditMessageText) (
func (b *BotAPI) ForwardMessage(ctx context.Context, req oas.ForwardMessage) (oas.ResultMessage, error) {
return oas.ResultMessage{}, &NotImplementedError{}
}

// StopPoll implements oas.Handler.
func (b *BotAPI) StopPoll(ctx context.Context, req oas.StopPoll) (oas.ResultPoll, error) {
return oas.ResultPoll{}, &NotImplementedError{}
}
Loading