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
9 changes: 4 additions & 5 deletions clients/bot/goclient/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ var (
EndpointGuilds = EndpointAPIPath + "/guild"
EndpointGuildChannels = func(guildID string) string { return EndpointGuilds + "/" + guildID + "/channels" }

EndpointChannelMessages = func(channelID string) string { return EndpointAPIPath + "/message/channel/" + channelID }
EndpointChannelMessage = func(channelID, messageID string) string { return EndpointChannelMessages(channelID) + "/" + messageID }
EndpointChannelMessageAck = func(channelID, messageID string) string { return EndpointChannelMessage(channelID, messageID) + "/ack" }
EndpointChannelTyping = func(channelID string) string { return EndpointChannelMessages(channelID) + "/typing" }
EndpointMessageReactions = func(channelID, messageID, reaction string) string {
EndpointChannelMessages = func(channelID string) string { return EndpointAPIPath + "/message/channel/" + channelID }
EndpointChannelMessage = func(channelID, messageID string) string { return EndpointChannelMessages(channelID) + "/" + messageID }
EndpointChannelTyping = func(channelID string) string { return EndpointChannelMessages(channelID) + "/typing" }
EndpointMessageReactions = func(channelID, messageID, reaction string) string {
return EndpointChannelMessage(channelID, messageID) + "/reactions/" + url.PathEscape(reaction)
}
EndpointMessageReactionAll = func(channelID, messageID string) string {
Expand Down
6 changes: 0 additions & 6 deletions clients/bot/goclient/restapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,6 @@ func (s *Session) ChannelMessageDelete(ctx context.Context, channelID, messageID
return s.Request(ctx, http.MethodDelete, path, nil, nil, nil, options...)
}

// ChannelMessageAck marks a channel read as the bot.
func (s *Session) ChannelMessageAck(ctx context.Context, channelID, messageID int64, options ...RequestOption) error {
path := EndpointChannelMessageAck(formatID(channelID), formatID(messageID))
return s.Request(ctx, http.MethodPost, path, nil, nil, nil, options...)
}

// ChannelTyping sends a typing indicator as the bot.
func (s *Session) ChannelTyping(ctx context.Context, channelID int64, options ...RequestOption) error {
return s.Request(ctx, http.MethodPost, EndpointChannelTyping(formatID(channelID)), nil, nil, nil, options...)
Expand Down
4 changes: 0 additions & 4 deletions clients/bot/goclient/restapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func TestBotRESTMethodsUseCurrentRuntimeRoutes(t *testing.T) {
{method: http.MethodPost, path: "/bot/api/v1/message/channel/2230469276416868353", body: `{"content":"hello"}`, reply: `{"id":2230469276416868355,"channel_id":2230469276416868353,"author":{"id":1,"name":"bot","discriminator":"0001","is_bot":true},"content":"hello","type":0}`},
{method: http.MethodPatch, path: "/bot/api/v1/message/channel/2230469276416868353/2230469276416868355", body: `{"content":"edited"}`, reply: `{"id":2230469276416868355,"channel_id":2230469276416868353,"author":{"id":1,"name":"bot","discriminator":"0001","is_bot":true},"content":"edited","type":0}`},
{method: http.MethodDelete, path: "/bot/api/v1/message/channel/2230469276416868353/2230469276416868355", status: http.StatusNoContent},
{method: http.MethodPost, path: "/bot/api/v1/message/channel/2230469276416868353/2230469276416868355/ack", status: http.StatusNoContent},
{method: http.MethodPost, path: "/bot/api/v1/message/channel/2230469276416868353/typing", status: http.StatusNoContent},
{method: http.MethodPut, path: "/bot/api/v1/message/channel/2230469276416868353/2230469276416868355/reactions/smile:123", reply: `{"count":1,"me":true,"emoji":{"id":123,"name":"smile"}}`},
{method: http.MethodDelete, path: "/bot/api/v1/message/channel/2230469276416868353/2230469276416868355/reactions/smile:123", status: http.StatusNoContent},
Expand Down Expand Up @@ -102,9 +101,6 @@ func TestBotRESTMethodsUseCurrentRuntimeRoutes(t *testing.T) {
if err := s.ChannelMessageDelete(ctx, 2230469276416868353, 2230469276416868355); err != nil {
t.Fatal(err)
}
if err := s.ChannelMessageAck(ctx, 2230469276416868353, 2230469276416868355); err != nil {
t.Fatal(err)
}
if err := s.ChannelTyping(ctx, 2230469276416868353); err != nil {
t.Fatal(err)
}
Expand Down
26 changes: 20 additions & 6 deletions cmd/botapi/endpoints/applicationcommand/handlers.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package applicationcommand

import (
"time"

appcmd "github.com/FlameInTheDark/gochat/internal/applicationcommands"
"github.com/FlameInTheDark/gochat/internal/botauth"
"github.com/FlameInTheDark/gochat/internal/dto"
"github.com/FlameInTheDark/gochat/internal/idgen"
"github.com/gofiber/fiber/v2"
)
Expand Down Expand Up @@ -190,9 +189,6 @@ func (e *Entity) RespondInteraction(c *fiber.Ctx) error {
if err := requireInteractionID(c, record); err != nil {
return err
}
if time.Now().After(record.CreatedAt.Add(appcmd.InteractionDeadline)) {
return fiber.NewError(fiber.StatusNotFound, "unknown interaction")
}
var response appcmd.InteractionResponse
if err := c.BodyParser(&response); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "invalid interaction response")
Expand All @@ -202,32 +198,43 @@ func (e *Entity) RespondInteraction(c *fiber.Ctx) error {
if err := e.appcmd.AckInteraction(c.UserContext(), record.ID, appcmd.AckStateResponded, nil); err != nil {
return duplicateAckError()
}
e.notifyInteractionStatus(c.UserContext(), record, "responded", response.Data, nil)
return c.SendStatus(fiber.StatusNoContent)
case appcmd.ResponseTypeDeferredChannelMessageSource:
var deferredData *appcmd.InteractionResponseData
if response.Data != nil && appcmd.HasResponseFlag(response.Data, appcmd.MessageFlagEphemeral) {
_ = e.setEphemeralResponse(c.UserContext(), record.ID, &appcmd.InteractionResponseData{Flags: response.Data.Flags | appcmd.MessageFlagLoading})
deferredData = &appcmd.InteractionResponseData{Flags: response.Data.Flags | appcmd.MessageFlagLoading}
_ = e.setEphemeralResponse(c.UserContext(), record.ID, deferredData)
} else {
deferredData = response.Data
}
if err := e.appcmd.AckInteraction(c.UserContext(), record.ID, appcmd.AckStateDeferred, nil); err != nil {
return duplicateAckError()
}
e.notifyInteractionStatus(c.UserContext(), record, "deferred", deferredData, nil)
return c.SendStatus(fiber.StatusNoContent)
case appcmd.ResponseTypeChannelMessageWithSource:
var messageID *int64
var message *dto.Message
state := "responded"
if appcmd.HasResponseFlag(response.Data, appcmd.MessageFlagEphemeral) {
if err := e.setEphemeralResponse(c.UserContext(), record.ID, response.Data); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "unable to store ephemeral response")
}
state = "ephemeral"
} else {
msg, err := e.createPublicInteractionMessage(c, record, response.Data)
if err != nil {
return err
}
id := msg.Id
messageID = &id
message = &msg
}
if err := e.appcmd.AckInteraction(c.UserContext(), record.ID, appcmd.AckStateResponded, messageID); err != nil {
return duplicateAckError()
}
e.notifyInteractionStatus(c.UserContext(), record, state, response.Data, message)
return c.SendStatus(fiber.StatusNoContent)
case appcmd.ResponseTypeAutocompleteResult:
if response.Data == nil {
Expand All @@ -247,6 +254,7 @@ func (e *Entity) RespondInteraction(c *fiber.Ctx) error {
if err := e.appcmd.AckInteraction(c.UserContext(), record.ID, appcmd.AckStateResponded, nil); err != nil {
return duplicateAckError()
}
e.notifyInteractionStatus(c.UserContext(), record, "modal", response.Data, nil)
return c.JSON(response)
default:
return fiber.NewError(fiber.StatusBadRequest, "unsupported interaction response type")
Expand Down Expand Up @@ -294,6 +302,7 @@ func (e *Entity) EditOriginalInteractionResponse(c *fiber.Ctx) error {
if err := e.setEphemeralResponse(c.UserContext(), record.ID, &data); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "unable to store ephemeral response")
}
e.notifyInteractionStatus(c.UserContext(), record, "ephemeral", &data, nil)
return c.JSON(data)
}
if record.InitialResponseID == nil {
Expand All @@ -303,6 +312,7 @@ func (e *Entity) EditOriginalInteractionResponse(c *fiber.Ctx) error {
if err := e.setEphemeralResponse(c.UserContext(), record.ID, &data); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "unable to store ephemeral response")
}
e.notifyInteractionStatus(c.UserContext(), record, "ephemeral", &data, nil)
return c.JSON(data)
}
out, err := e.createPublicInteractionMessage(c, record, &data)
Expand All @@ -313,12 +323,14 @@ func (e *Entity) EditOriginalInteractionResponse(c *fiber.Ctx) error {
_ = e.deleteMessageResponse(c, record, out.Id)
return duplicateAckError()
}
e.notifyInteractionStatus(c.UserContext(), record, "responded", &data, &out)
return c.JSON(out)
}
out, err := e.editMessageResponse(c, record, *record.InitialResponseID, &data)
if err != nil {
return err
}
e.notifyInteractionStatus(c.UserContext(), record, "responded", &data, &out)
return c.JSON(out)
}

Expand Down Expand Up @@ -357,12 +369,14 @@ func (e *Entity) CreateFollowupMessage(c *fiber.Ctx) error {
if err := e.setEphemeralResponse(c.UserContext(), idgen.Next(), &data); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "unable to store ephemeral followup")
}
e.notifyInteractionStatus(c.UserContext(), record, "ephemeral_followup", &data, nil)
return c.Status(fiber.StatusCreated).JSON(data)
}
out, err := e.createPublicInteractionMessage(c, record, &data)
if err != nil {
return err
}
e.notifyInteractionStatus(c.UserContext(), record, "responded", &data, &out)
return c.Status(fiber.StatusCreated).JSON(out)
}

Expand Down
21 changes: 21 additions & 0 deletions cmd/botapi/endpoints/applicationcommand/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,27 @@ func (e *Entity) deleteEphemeralResponse(ctx context.Context, interactionID int6
}
}

func (e *Entity) notifyInteractionStatus(ctx context.Context, record appcmd.InteractionRecord, state string, data *appcmd.InteractionResponseData, message *dto.Message) {
commandName := ""
if payload, err := e.payload.GetInteractionPayload(ctx, record.ID); err == nil {
if parsed := appcmd.ParseCommandData(payload.DataJSON); parsed != nil {
commandName = parsed.Name
}
}
_ = mq.SendUserUpdate(ctx, e.mqt, record.InvokerUserID, &mqmsg.ApplicationCommandInteractionStatus{
InteractionID: record.ID,
ApplicationID: record.ApplicationID,
CommandID: record.CommandID,
CommandName: commandName,
ChannelID: record.ChannelID,
GuildID: record.GuildID,
UserID: record.InvokerUserID,
State: state,
Response: data,
Message: message,
})
}

func (e *Entity) setAutocompleteResponse(ctx context.Context, interactionID int64, choices []appcmd.ApplicationCommandChoice) error {
if e.cache == nil {
return nil
Expand Down
1 change: 0 additions & 1 deletion cmd/botapi/endpoints/message/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func (e *Entity) Init(router fiber.Router) {
router.Get("/channel/:channel_id<int>", e.List)
router.Patch("/channel/:channel_id<int>/:message_id<int>", e.Update)
router.Delete("/channel/:channel_id<int>/:message_id<int>", e.Delete)
router.Post("/channel/:channel_id<int>/:message_id<int>/ack", e.Ack)
router.Post("/channel/:channel_id<int>/typing", e.Typing)
router.Put("/channel/:channel_id<int>/:message_id<int>/reactions/:reaction_name", e.AddReaction)
router.Delete("/channel/:channel_id<int>/:message_id<int>/reactions/:reaction_name", e.RemoveReaction)
Expand Down
30 changes: 0 additions & 30 deletions cmd/botapi/endpoints/message/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,36 +276,6 @@ func (e *Entity) Delete(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
}

// Ack
//
// @Summary Mark a channel read as the bot
// @Produce json
// @Tags Bot Message
// @Security BotToken
// @Param channel_id path int64 true "Channel id"
// @Param message_id path int64 true "Message id"
// @Success 204
// @Failure 400 {string} string "Bad request"
// @Failure 401 {string} string "Unauthorized"
// @Failure 403 {string} string "Forbidden"
// @Failure 500 {string} string "Internal server error"
// @Router /bot/api/v1/message/channel/{channel_id}/{message_id}/ack [post]
func (e *Entity) Ack(c *fiber.Ctx) error {
principal, channel, _, err := e.requireChannel(c, permissions.PermServerViewChannels, permissions.PermTextReadMessageHistory)
if err != nil {
return err
}
msgID, err := parseParamInt64(c, "message_id")
if err != nil {
return err
}
if err := e.rs.SetReadState(c.UserContext(), principal.BotUserID, channel.Id, msgID); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "unable to update read state")
}
_ = mq.SendUserUpdate(c.UserContext(), e.mqt, principal.BotUserID, &mqmsg.UpdateReadState{ChannelId: channel.Id, MessageId: msgID})
return c.SendStatus(fiber.StatusNoContent)
}

// Typing
//
// @Summary Send a typing indicator as the bot
Expand Down
2 changes: 1 addition & 1 deletion cmd/ws/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (a *App) presenceTTLSeconds() int64 {
if a == nil || a.cfg == nil {
return 60
}
ttl := a.cfg.HearthBeatTimeout * 2 / 1000
ttl := int64((wsHeartbeatDeadline(a.cfg.HearthBeatTimeout) * 2) / time.Second)
if ttl < 1 {
return 1
}
Expand Down
25 changes: 21 additions & 4 deletions cmd/ws/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,7 @@ func (a *App) wsHandler(c *websocket.Conn) {
pingInterval = half
}
}
readDeadline := time.Duration(a.cfg.HearthBeatTimeout+15000) * time.Millisecond
if readDeadline <= 0 {
readDeadline = 60 * time.Second
}
readDeadline := wsReadDeadline(a.cfg.HearthBeatTimeout)
_ = c.SetReadDeadline(time.Now().Add(readDeadline))
c.SetPongHandler(func(string) error {
return c.SetReadDeadline(time.Now().Add(readDeadline))
Expand Down Expand Up @@ -317,6 +314,7 @@ func (a *App) wsHandler(c *websocket.Conn) {
connLog.Error("Read WS message error", "error", err)
return
}
_ = c.SetReadDeadline(time.Now().Add(readDeadline))

switch mt {
case websocket.TextMessage:
Expand Down Expand Up @@ -356,3 +354,22 @@ func isExpectedWSReadError(err error) bool {

return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, net.ErrClosed)
}

func wsReadDeadline(heartbeatTimeoutMs int64) time.Duration {
return wsHeartbeatDeadline(heartbeatTimeoutMs) + 15*time.Second
}

func wsHeartbeatDeadline(heartbeatTimeoutMs int64) time.Duration {
base := time.Duration(heartbeatTimeoutMs) * time.Millisecond
if base <= 0 {
return 60 * time.Second
}
deadline := base + 10*time.Second
if minimum := base * 3; deadline < minimum {
deadline = minimum
}
if deadline < 60*time.Second {
return 60 * time.Second
}
return deadline
}
Loading