mirror of https://github.com/mautrix/go.git
237 lines
6.1 KiB
Go
237 lines
6.1 KiB
Go
// Copyright (c) 2024 Tulir Asokan
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package bridgev2
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"go.mau.fi/util/jsontime"
|
|
|
|
"maunium.net/go/mautrix/appservice"
|
|
"maunium.net/go/mautrix/bridge/status"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
type MessageStatusEventInfo struct {
|
|
RoomID id.RoomID
|
|
SourceEventID id.EventID
|
|
NewEventID id.EventID
|
|
EventType event.Type
|
|
MessageType event.MessageType
|
|
Sender id.UserID
|
|
ThreadRoot id.EventID
|
|
StreamOrder int64
|
|
|
|
IsSourceEventDoublePuppeted bool
|
|
}
|
|
|
|
func StatusEventInfoFromEvent(evt *event.Event) *MessageStatusEventInfo {
|
|
var threadRoot id.EventID
|
|
if relatable, ok := evt.Content.Parsed.(event.Relatable); ok {
|
|
threadRoot = relatable.OptionalGetRelatesTo().GetThreadParent()
|
|
}
|
|
|
|
_, isDoublePuppeted := evt.Content.Raw[appservice.DoublePuppetKey]
|
|
|
|
return &MessageStatusEventInfo{
|
|
RoomID: evt.RoomID,
|
|
SourceEventID: evt.ID,
|
|
EventType: evt.Type,
|
|
MessageType: evt.Content.AsMessage().MsgType,
|
|
Sender: evt.Sender,
|
|
ThreadRoot: threadRoot,
|
|
|
|
IsSourceEventDoublePuppeted: isDoublePuppeted,
|
|
}
|
|
}
|
|
|
|
// MessageStatus represents the status of a message. It also implements the error interface to allow network connectors
|
|
// to return errors which get translated into user-friendly error messages and/or status events.
|
|
type MessageStatus struct {
|
|
Step status.MessageCheckpointStep
|
|
RetryNum int
|
|
|
|
Status event.MessageStatus
|
|
ErrorReason event.MessageStatusReason
|
|
DeliveredTo []id.UserID
|
|
InternalError error // Internal error to be tracked in message checkpoints
|
|
Message string // Human-readable message shown to users
|
|
|
|
ErrorAsMessage bool
|
|
IsCertain bool
|
|
SendNotice bool
|
|
DisableMSS bool
|
|
}
|
|
|
|
func WrapErrorInStatus(err error) MessageStatus {
|
|
var alreadyWrapped MessageStatus
|
|
var ok bool
|
|
if alreadyWrapped, ok = err.(MessageStatus); ok {
|
|
return alreadyWrapped
|
|
} else if errors.As(err, &alreadyWrapped) {
|
|
alreadyWrapped.InternalError = err
|
|
return alreadyWrapped
|
|
}
|
|
return MessageStatus{
|
|
Status: event.MessageStatusRetriable,
|
|
ErrorReason: event.MessageStatusGenericError,
|
|
InternalError: err,
|
|
}
|
|
}
|
|
|
|
func (ms MessageStatus) WithSendNotice(send bool) MessageStatus {
|
|
ms.SendNotice = send
|
|
return ms
|
|
}
|
|
|
|
func (ms MessageStatus) WithIsCertain(certain bool) MessageStatus {
|
|
ms.IsCertain = certain
|
|
return ms
|
|
}
|
|
|
|
func (ms MessageStatus) WithMessage(msg string) MessageStatus {
|
|
ms.Message = msg
|
|
ms.ErrorAsMessage = false
|
|
return ms
|
|
}
|
|
|
|
func (ms MessageStatus) WithStep(step status.MessageCheckpointStep) MessageStatus {
|
|
ms.Step = step
|
|
return ms
|
|
}
|
|
|
|
func (ms MessageStatus) WithStatus(status event.MessageStatus) MessageStatus {
|
|
ms.Status = status
|
|
return ms
|
|
}
|
|
|
|
func (ms MessageStatus) WithErrorReason(reason event.MessageStatusReason) MessageStatus {
|
|
ms.ErrorReason = reason
|
|
return ms
|
|
}
|
|
|
|
func (ms MessageStatus) WithErrorAsMessage() MessageStatus {
|
|
ms.ErrorAsMessage = true
|
|
return ms
|
|
}
|
|
|
|
func (ms MessageStatus) Error() string {
|
|
return ms.InternalError.Error()
|
|
}
|
|
|
|
func (ms MessageStatus) Unwrap() error {
|
|
return ms.InternalError
|
|
}
|
|
|
|
func (ms *MessageStatus) checkpointStatus() status.MessageCheckpointStatus {
|
|
switch ms.Status {
|
|
case event.MessageStatusSuccess:
|
|
if len(ms.DeliveredTo) > 0 {
|
|
return status.MsgStatusDelivered
|
|
}
|
|
return status.MsgStatusSuccess
|
|
case event.MessageStatusPending:
|
|
return status.MsgStatusWillRetry
|
|
case event.MessageStatusRetriable, event.MessageStatusFail:
|
|
switch ms.ErrorReason {
|
|
case event.MessageStatusTooOld:
|
|
return status.MsgStatusTimeout
|
|
case event.MessageStatusUnsupported:
|
|
return status.MsgStatusUnsupported
|
|
default:
|
|
return status.MsgStatusPermFailure
|
|
}
|
|
default:
|
|
return "UNKNOWN"
|
|
}
|
|
}
|
|
|
|
func (ms *MessageStatus) ToCheckpoint(evt *MessageStatusEventInfo) *status.MessageCheckpoint {
|
|
step := status.MsgStepRemote
|
|
if ms.Step != "" {
|
|
step = ms.Step
|
|
}
|
|
checkpoint := &status.MessageCheckpoint{
|
|
RoomID: evt.RoomID,
|
|
EventID: evt.SourceEventID,
|
|
Step: step,
|
|
Timestamp: jsontime.UnixMilliNow(),
|
|
Status: ms.checkpointStatus(),
|
|
RetryNum: ms.RetryNum,
|
|
ReportedBy: status.MsgReportedByBridge,
|
|
EventType: evt.EventType,
|
|
MessageType: evt.MessageType,
|
|
}
|
|
if ms.InternalError != nil {
|
|
checkpoint.Info = ms.InternalError.Error()
|
|
} else if ms.Message != "" {
|
|
checkpoint.Info = ms.Message
|
|
}
|
|
return checkpoint
|
|
}
|
|
|
|
func (ms *MessageStatus) ToMSSEvent(evt *MessageStatusEventInfo) *event.BeeperMessageStatusEventContent {
|
|
content := &event.BeeperMessageStatusEventContent{
|
|
RelatesTo: event.RelatesTo{
|
|
Type: event.RelReference,
|
|
EventID: evt.SourceEventID,
|
|
},
|
|
Status: ms.Status,
|
|
Reason: ms.ErrorReason,
|
|
Message: ms.Message,
|
|
}
|
|
if ms.InternalError != nil {
|
|
content.InternalError = ms.InternalError.Error()
|
|
if ms.ErrorAsMessage {
|
|
content.Message = content.InternalError
|
|
}
|
|
}
|
|
if ms.DeliveredTo != nil {
|
|
content.DeliveredToUsers = &ms.DeliveredTo
|
|
}
|
|
return content
|
|
}
|
|
|
|
func (ms *MessageStatus) ToNoticeEvent(evt *MessageStatusEventInfo) *event.MessageEventContent {
|
|
certainty := "may not have been"
|
|
if ms.IsCertain {
|
|
certainty = "was not"
|
|
}
|
|
evtType := "message"
|
|
switch evt.EventType {
|
|
case event.EventReaction:
|
|
evtType = "reaction"
|
|
case event.EventRedaction:
|
|
evtType = "redaction"
|
|
}
|
|
msg := ms.Message
|
|
if ms.ErrorAsMessage || msg == "" {
|
|
msg = ms.InternalError.Error()
|
|
}
|
|
messagePrefix := fmt.Sprintf("Your %s %s bridged", evtType, certainty)
|
|
if ms.Step == status.MsgStepCommand {
|
|
messagePrefix = "Handling your command panicked"
|
|
}
|
|
content := &event.MessageEventContent{
|
|
MsgType: event.MsgText,
|
|
Body: fmt.Sprintf("\u26a0\ufe0f %s: %s", messagePrefix, msg),
|
|
RelatesTo: &event.RelatesTo{},
|
|
Mentions: &event.Mentions{},
|
|
}
|
|
if evt.ThreadRoot != "" {
|
|
content.RelatesTo.SetThread(evt.ThreadRoot, evt.SourceEventID)
|
|
} else {
|
|
content.RelatesTo.SetReplyTo(evt.SourceEventID)
|
|
}
|
|
if evt.Sender != "" {
|
|
content.Mentions.UserIDs = []id.UserID{evt.Sender}
|
|
}
|
|
return content
|
|
}
|