mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 22:35:52 +01:00
bridgev2: add remote->matrix room tagging and muting interfaces
This commit is contained in:
parent
2eb51d35e2
commit
59b99dee70
6 changed files with 160 additions and 11 deletions
|
|
@ -8,6 +8,7 @@ package matrix
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ import (
|
|||
"maunium.net/go/mautrix/crypto/attachment"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
"maunium.net/go/mautrix/pushrules"
|
||||
)
|
||||
|
||||
// ASIntent implements the bridge ghost API interface using a real Matrix homeserver as the backend.
|
||||
|
|
@ -249,3 +251,31 @@ func (as *ASIntent) DeleteRoom(ctx context.Context, roomID id.RoomID, puppetsOnl
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (as *ASIntent) TagRoom(ctx context.Context, roomID id.RoomID, tag event.RoomTag, isTagged bool) error {
|
||||
if isTagged {
|
||||
return as.Matrix.AddTagWithCustomData(ctx, roomID, tag, &event.TagMetadata{
|
||||
MauDoublePuppetSource: as.Connector.AS.DoublePuppetValue,
|
||||
})
|
||||
} else {
|
||||
if tag == "" {
|
||||
// TODO clear all tags?
|
||||
}
|
||||
return as.Matrix.RemoveTag(ctx, roomID, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (as *ASIntent) MuteRoom(ctx context.Context, roomID id.RoomID, until time.Time) error {
|
||||
if !until.IsZero() && until.Before(time.Now()) {
|
||||
err := as.Matrix.DeletePushRule(ctx, "global", pushrules.RoomRule, string(roomID))
|
||||
// If the push rule doesn't exist, everything is fine
|
||||
if errors.Is(err, mautrix.MNotFound) {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
return as.Matrix.PutPushRule(ctx, "global", pushrules.RoomRule, string(roomID), &mautrix.ReqPutPushRule{
|
||||
Actions: []pushrules.PushActionType{pushrules.ActionDontNotify},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,4 +58,7 @@ type MatrixAPI interface {
|
|||
DeleteRoom(ctx context.Context, roomID id.RoomID, puppetsOnly bool) error
|
||||
InviteUser(ctx context.Context, roomID id.RoomID, userID id.UserID) error
|
||||
EnsureJoined(ctx context.Context, roomID id.RoomID) error
|
||||
|
||||
TagRoom(ctx context.Context, roomID id.RoomID, tag event.RoomTag, isTagged bool) error
|
||||
MuteRoom(ctx context.Context, roomID id.RoomID, until time.Time) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,6 +287,8 @@ const (
|
|||
RemoteEventReadReceipt
|
||||
RemoteEventDeliveryReceipt
|
||||
RemoteEventTyping
|
||||
RemoteEventChatTag
|
||||
RemoteEventChatMute
|
||||
)
|
||||
|
||||
// RemoteEvent represents a single event from the remote network, such as a message or a reaction.
|
||||
|
|
@ -374,6 +376,18 @@ type RemoteTypingWithType interface {
|
|||
GetTypingType() TypingType
|
||||
}
|
||||
|
||||
type RemoteChatTag interface {
|
||||
RemoteEvent
|
||||
GetTag() (tag event.RoomTag, remove bool)
|
||||
}
|
||||
|
||||
var Unmuted = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
type RemoteChatMute interface {
|
||||
RemoteEvent
|
||||
GetMutedUntil() time.Time
|
||||
}
|
||||
|
||||
// SimpleRemoteEvent is a simple implementation of RemoteEvent that can be used with struct fields and some callbacks.
|
||||
type SimpleRemoteEvent[T any] struct {
|
||||
Type RemoteEventType
|
||||
|
|
|
|||
|
|
@ -804,6 +804,10 @@ func (portal *Portal) handleRemoteEvent(source *UserLogin, evt RemoteEvent) {
|
|||
portal.handleRemoteDeliveryReceipt(ctx, source, evt.(RemoteReceipt))
|
||||
case RemoteEventTyping:
|
||||
portal.handleRemoteTyping(ctx, source, evt.(RemoteTyping))
|
||||
case RemoteEventChatTag:
|
||||
portal.handleRemoteChatTag(ctx, source, evt.(RemoteChatTag))
|
||||
case RemoteEventChatMute:
|
||||
portal.handleRemoteChatMute(ctx, source, evt.(RemoteChatMute))
|
||||
default:
|
||||
log.Warn().Int("type", int(evt.GetType())).Msg("Got remote event with unknown type")
|
||||
}
|
||||
|
|
@ -1224,6 +1228,37 @@ func (portal *Portal) handleRemoteTyping(ctx context.Context, source *UserLogin,
|
|||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) handleRemoteChatTag(ctx context.Context, source *UserLogin, evt RemoteChatTag) {
|
||||
if !evt.GetSender().IsFromMe {
|
||||
zerolog.Ctx(ctx).Warn().Msg("Ignoring chat tag event from non-self user")
|
||||
return
|
||||
}
|
||||
dp := source.User.DoublePuppet(ctx)
|
||||
if dp == nil {
|
||||
return
|
||||
}
|
||||
tag, isTagged := evt.GetTag()
|
||||
err := dp.TagRoom(ctx, portal.MXID, tag, isTagged)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to bridge chat tag event")
|
||||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) handleRemoteChatMute(ctx context.Context, source *UserLogin, evt RemoteChatMute) {
|
||||
if !evt.GetSender().IsFromMe {
|
||||
zerolog.Ctx(ctx).Warn().Msg("Ignoring chat mute event from non-self user")
|
||||
return
|
||||
}
|
||||
dp := source.User.DoublePuppet(ctx)
|
||||
if dp == nil {
|
||||
return
|
||||
}
|
||||
err := dp.MuteRoom(ctx, portal.MXID, evt.GetMutedUntil())
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to bridge chat mute event")
|
||||
}
|
||||
}
|
||||
|
||||
type PortalInfo struct {
|
||||
Name *string
|
||||
Topic *string
|
||||
|
|
@ -1233,6 +1268,13 @@ type PortalInfo struct {
|
|||
|
||||
IsDirectChat *bool
|
||||
IsSpace *bool
|
||||
|
||||
UserLocal *UserLocalPortalInfo
|
||||
}
|
||||
|
||||
type UserLocalPortalInfo struct {
|
||||
MutedUntil *time.Time
|
||||
Tag *event.RoomTag
|
||||
}
|
||||
|
||||
func (portal *Portal) UpdateName(ctx context.Context, name string, sender *Ghost, ts time.Time) bool {
|
||||
|
|
@ -1445,6 +1487,28 @@ func (portal *Portal) SyncParticipants(ctx context.Context, members []networkid.
|
|||
return expectedUserIDs, extraFunctionalMembers, nil
|
||||
}
|
||||
|
||||
func (portal *Portal) updateUserLocalInfo(ctx context.Context, info *UserLocalPortalInfo, source *UserLogin) {
|
||||
if portal.MXID == "" || info == nil {
|
||||
return
|
||||
}
|
||||
dp := source.User.DoublePuppet(ctx)
|
||||
if dp == nil {
|
||||
return
|
||||
}
|
||||
if info.MutedUntil != nil {
|
||||
err := dp.MuteRoom(ctx, portal.MXID, *info.MutedUntil)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to mute room")
|
||||
}
|
||||
}
|
||||
if info.Tag != nil {
|
||||
err := dp.TagRoom(ctx, portal.MXID, *info.Tag, *info.Tag != "")
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to tag room")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) UpdateInfo(ctx context.Context, info *PortalInfo, source *UserLogin, sender *Ghost, ts time.Time) {
|
||||
changed := false
|
||||
if info.Name != nil {
|
||||
|
|
@ -1469,6 +1533,7 @@ func (portal *Portal) UpdateInfo(ctx context.Context, info *PortalInfo, source *
|
|||
if err != nil {
|
||||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to ensure user portal row exists")
|
||||
}
|
||||
portal.updateUserLocalInfo(ctx, info.UserLocal, source)
|
||||
}
|
||||
if changed {
|
||||
portal.UpdateBridgeInfo(ctx)
|
||||
|
|
@ -1599,6 +1664,7 @@ func (portal *Portal) CreateMatrixRoom(ctx context.Context, source *UserLogin, i
|
|||
if portal.Parent != nil {
|
||||
// TODO add m.space.child event
|
||||
}
|
||||
portal.updateUserLocalInfo(ctx, info.UserLocal, source)
|
||||
if !isBeeper {
|
||||
_, _, err = portal.SyncParticipants(ctx, info.Members, source)
|
||||
if err != nil {
|
||||
|
|
|
|||
16
client.go
16
client.go
|
|
@ -1925,15 +1925,13 @@ func (cli *Client) SetReadMarkers(ctx context.Context, roomID id.RoomID, content
|
|||
return
|
||||
}
|
||||
|
||||
func (cli *Client) AddTag(ctx context.Context, roomID id.RoomID, tag string, order float64) error {
|
||||
var tagData event.Tag
|
||||
if order == order {
|
||||
tagData.Order = json.Number(strconv.FormatFloat(order, 'e', -1, 64))
|
||||
}
|
||||
return cli.AddTagWithCustomData(ctx, roomID, tag, tagData)
|
||||
func (cli *Client) AddTag(ctx context.Context, roomID id.RoomID, tag event.RoomTag, order float64) error {
|
||||
return cli.AddTagWithCustomData(ctx, roomID, tag, &event.TagMetadata{
|
||||
Order: json.Number(strconv.FormatFloat(order, 'e', -1, 64)),
|
||||
})
|
||||
}
|
||||
|
||||
func (cli *Client) AddTagWithCustomData(ctx context.Context, roomID id.RoomID, tag string, data interface{}) (err error) {
|
||||
func (cli *Client) AddTagWithCustomData(ctx context.Context, roomID id.RoomID, tag event.RoomTag, data any) (err error) {
|
||||
urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags", tag)
|
||||
_, err = cli.MakeRequest(ctx, http.MethodPut, urlPath, data, nil)
|
||||
return
|
||||
|
|
@ -1944,13 +1942,13 @@ func (cli *Client) GetTags(ctx context.Context, roomID id.RoomID) (tags event.Ta
|
|||
return
|
||||
}
|
||||
|
||||
func (cli *Client) GetTagsWithCustomData(ctx context.Context, roomID id.RoomID, resp interface{}) (err error) {
|
||||
func (cli *Client) GetTagsWithCustomData(ctx context.Context, roomID id.RoomID, resp any) (err error) {
|
||||
urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags")
|
||||
_, err = cli.MakeRequest(ctx, http.MethodGet, urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) RemoveTag(ctx context.Context, roomID id.RoomID, tag string) (err error) {
|
||||
func (cli *Client) RemoveTag(ctx context.Context, roomID id.RoomID, tag event.RoomTag) (err error) {
|
||||
urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags", tag)
|
||||
_, err = cli.MakeRequest(ctx, http.MethodDelete, urlPath, nil, nil)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ package event
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
|
@ -18,10 +19,47 @@ type TagEventContent struct {
|
|||
Tags Tags `json:"tags"`
|
||||
}
|
||||
|
||||
type Tags map[string]Tag
|
||||
type Tags map[RoomTag]TagMetadata
|
||||
|
||||
type Tag struct {
|
||||
type RoomTag string
|
||||
|
||||
const (
|
||||
RoomTagFavourite RoomTag = "m.favourite"
|
||||
RoomTagLowPriority RoomTag = "m.lowpriority"
|
||||
RoomTagServerNotice RoomTag = "m.server_notice"
|
||||
)
|
||||
|
||||
func (rt RoomTag) IsUserDefined() bool {
|
||||
return strings.HasPrefix(string(rt), "u.")
|
||||
}
|
||||
|
||||
func (rt RoomTag) String() string {
|
||||
return string(rt)
|
||||
}
|
||||
|
||||
func (rt RoomTag) Name() string {
|
||||
if rt.IsUserDefined() {
|
||||
return string(rt[2:])
|
||||
}
|
||||
switch rt {
|
||||
case RoomTagFavourite:
|
||||
return "Favourite"
|
||||
case RoomTagLowPriority:
|
||||
return "Low priority"
|
||||
case RoomTagServerNotice:
|
||||
return "Server notice"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: type alias
|
||||
type Tag = TagMetadata
|
||||
|
||||
type TagMetadata struct {
|
||||
Order json.Number `json:"order,omitempty"`
|
||||
|
||||
MauDoublePuppetSource string `json:"fi.mau.double_puppet_source,omitempty"`
|
||||
}
|
||||
|
||||
// DirectChatsEventContent represents the content of a m.direct account data event.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue