Make error messages cleaner

This commit is contained in:
Tulir Asokan
2023-04-22 01:43:56 +03:00
parent 29e0b9fa02
commit 049ef48fb0
3 changed files with 76 additions and 15 deletions

2
go.mod
View File

@@ -37,4 +37,4 @@ require (
maunium.net/go/mauflag v1.0.0 // indirect maunium.net/go/mauflag v1.0.0 // indirect
) )
replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20230416132336-325ee6a8c961 replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20230421223629-940c512c92de

4
go.sum
View File

@@ -1,6 +1,6 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/beeper/discordgo v0.0.0-20230416132336-325ee6a8c961 h1:eSGaliexlehYBeP4YQW8dQpV9XWWgfR1qH8kfHgrDcY= github.com/beeper/discordgo v0.0.0-20230421223629-940c512c92de h1:jq5xgpkSvFJiiXH8w0SWGjK6jzwU8gZvrNahAq24nyI=
github.com/beeper/discordgo v0.0.0-20230416132336-325ee6a8c961/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/beeper/discordgo v0.0.0-20230421223629-940c512c92de/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@@ -2,12 +2,16 @@ package main
import ( import (
"bytes" "bytes"
"context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
@@ -1050,7 +1054,8 @@ var (
errCantStartThread = errors.New("can't create thread without being logged into Discord") errCantStartThread = errors.New("can't create thread without being logged into Discord")
) )
func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) { func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string, checkpointError error) {
var restErr *discordgo.RESTError
switch { switch {
case errors.Is(err, errUnknownMsgType), case errors.Is(err, errUnknownMsgType),
errors.Is(err, errUnknownRelationType), errors.Is(err, errUnknownRelationType),
@@ -1060,22 +1065,67 @@ func errorToStatusReason(err error) (reason event.MessageStatusReason, status ev
errors.Is(err, attachment.UnsupportedVersion), errors.Is(err, attachment.UnsupportedVersion),
errors.Is(err, attachment.UnsupportedAlgorithm), errors.Is(err, attachment.UnsupportedAlgorithm),
errors.Is(err, errCantStartThread): errors.Is(err, errCantStartThread):
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, "" return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, "", nil
case errors.Is(err, attachment.HashMismatch), case errors.Is(err, attachment.HashMismatch),
errors.Is(err, attachment.InvalidKey), errors.Is(err, attachment.InvalidKey),
errors.Is(err, attachment.InvalidInitVector): errors.Is(err, attachment.InvalidInitVector):
return event.MessageStatusUndecryptable, event.MessageStatusFail, true, true, "" return event.MessageStatusUndecryptable, event.MessageStatusFail, true, true, "", nil
case errors.Is(err, errUserNotReceiver), errors.Is(err, errUserNotLoggedIn): case errors.Is(err, errUserNotReceiver), errors.Is(err, errUserNotLoggedIn):
return event.MessageStatusNoPermission, event.MessageStatusFail, true, false, "" return event.MessageStatusNoPermission, event.MessageStatusFail, true, false, "", nil
case errors.Is(err, errUnknownEditTarget): case errors.Is(err, errUnknownEditTarget):
return event.MessageStatusGenericError, event.MessageStatusFail, true, false, "" return event.MessageStatusGenericError, event.MessageStatusFail, true, false, "", nil
case errors.Is(err, errTargetNotFound): case errors.Is(err, errTargetNotFound):
return event.MessageStatusGenericError, event.MessageStatusFail, true, false, "" return event.MessageStatusGenericError, event.MessageStatusFail, true, false, "", nil
case errors.As(err, &restErr):
if restErr.Message != nil {
reason, humanMessage = restErrorToStatusReason(restErr.Message)
status = event.MessageStatusFail
isCertain = true
sendNotice = true
checkpointError = fmt.Errorf("HTTP %d: %d: %s", restErr.Response.StatusCode, restErr.Message.Code, restErr.Message.Message)
if len(restErr.Message.Errors) > 0 {
jsonExtraErrors, _ := json.Marshal(restErr.Message.Errors)
checkpointError = fmt.Errorf("%w (%s)", checkpointError, jsonExtraErrors)
}
return
} else if restErr.Response.StatusCode == http.StatusBadRequest && bytes.HasPrefix(restErr.ResponseBody, []byte(`{"captcha_key"`)) {
return event.MessageStatusGenericError, event.MessageStatusRetriable, true, true, "Captcha error", errors.New("captcha required")
} else if restErr.Response != nil && (restErr.Response.StatusCode == http.StatusServiceUnavailable || restErr.Response.StatusCode == http.StatusBadGateway || restErr.Response.StatusCode == http.StatusGatewayTimeout) {
return event.MessageStatusGenericError, event.MessageStatusRetriable, true, true, fmt.Sprintf("HTTP %s", restErr.Response.Status), fmt.Errorf("HTTP %d", restErr.Response.StatusCode)
}
fallthrough
case errors.Is(err, context.DeadlineExceeded):
return event.MessageStatusTooOld, event.MessageStatusRetriable, false, true, "", context.DeadlineExceeded
case strings.HasSuffix(err.Error(), "(Client.Timeout exceeded while awaiting headers)"):
return event.MessageStatusTooOld, event.MessageStatusRetriable, false, true, "", errors.New("HTTP request timed out")
case errors.Is(err, syscall.ECONNRESET):
return event.MessageStatusGenericError, event.MessageStatusRetriable, false, true, "", errors.New("connection reset")
default: default:
return event.MessageStatusGenericError, event.MessageStatusRetriable, false, true, "" return event.MessageStatusGenericError, event.MessageStatusRetriable, false, true, "", nil
} }
} }
func restErrorToStatusReason(msg *discordgo.APIErrorMessage) (reason event.MessageStatusReason, humanMessage string) {
switch msg.Code {
case discordgo.ErrCodeRequestEntityTooLarge:
return event.MessageStatusUnsupported, "Attachment is too large"
case discordgo.ErrCodeUnknownEmoji:
return event.MessageStatusUnsupported, "Unsupported emoji"
case discordgo.ErrCodeMissingPermissions, discordgo.ErrCodeMissingAccess:
return event.MessageStatusUnsupported, "You don't have the permissions to do that"
case discordgo.ErrCodeCannotSendMessagesToThisUser:
return event.MessageStatusUnsupported, "You can't send messages to this user"
case discordgo.ErrCodeCannotSendMessagesInVoiceChannel:
return event.MessageStatusUnsupported, "You can't send messages in a non-text channel"
case discordgo.ErrCodeInvalidFormBody:
contentErrs := msg.Errors["content"].Errors
if len(contentErrs) == 1 && contentErrs[0].Code == "BASE_TYPE_MAX_LENGTH" {
return event.MessageStatusUnsupported, "Message is too long: " + contentErrs[0].Message
}
}
return event.MessageStatusGenericError, fmt.Sprintf("%d: %s", msg.Code, msg.Message)
}
func (portal *Portal) sendStatusEvent(evtID id.EventID, err error) { func (portal *Portal) sendStatusEvent(evtID id.EventID, err error) {
if !portal.bridge.Config.Bridge.MessageStatusEvents { if !portal.bridge.Config.Bridge.MessageStatusEvents {
return return
@@ -1097,8 +1147,13 @@ func (portal *Portal) sendStatusEvent(evtID id.EventID, err error) {
if err == nil { if err == nil {
content.Status = event.MessageStatusSuccess content.Status = event.MessageStatusSuccess
} else { } else {
content.Reason, content.Status, _, _, content.Message = errorToStatusReason(err) var checkpointErr error
content.Error = err.Error() content.Reason, content.Status, _, _, content.Message, checkpointErr = errorToStatusReason(err)
if checkpointErr != nil {
content.Error = checkpointErr.Error()
} else {
content.Error = err.Error()
}
} }
_, err = intent.SendMessageEvent(portal.MXID, event.BeeperMessageStatus, &content) _, err = intent.SendMessageEvent(portal.MXID, event.BeeperMessageStatus, &content)
if err != nil { if err != nil {
@@ -1128,11 +1183,17 @@ func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part strin
level = maulogger.LevelDebug level = maulogger.LevelDebug
} }
portal.log.Logfln(level, "%s %s %s from %s: %v", part, msgType, evtDescription, evt.Sender, err) portal.log.Logfln(level, "%s %s %s from %s: %v", part, msgType, evtDescription, evt.Sender, err)
reason, statusCode, isCertain, sendNotice, _ := errorToStatusReason(err) reason, statusCode, isCertain, sendNotice, humanMessage, checkpointErr := errorToStatusReason(err)
if checkpointErr == nil {
checkpointErr = err
}
checkpointStatus := status.ReasonToCheckpointStatus(reason, statusCode) checkpointStatus := status.ReasonToCheckpointStatus(reason, statusCode)
portal.bridge.SendMessageCheckpoint(evt, status.MsgStepRemote, err, checkpointStatus, 0) portal.bridge.SendMessageCheckpoint(evt, status.MsgStepRemote, checkpointErr, checkpointStatus, 0)
if sendNotice { if sendNotice {
portal.sendErrorMessage(msgType, err.Error(), isCertain) if humanMessage == "" {
humanMessage = err.Error()
}
portal.sendErrorMessage(msgType, humanMessage, isCertain)
} }
portal.sendStatusEvent(evt.ID, err) portal.sendStatusEvent(evt.ID, err)
} else { } else {