diff --git a/desktop/go.mod b/desktop/go.mod index e7919b3..b12d1ec 100644 --- a/desktop/go.mod +++ b/desktop/go.mod @@ -79,7 +79,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7 // indirect + maunium.net/go/mautrix v0.23.2-0.20250305232906-e306c2817edd // indirect mvdan.cc/xurls/v2 v2.6.0 // indirect ) diff --git a/desktop/go.sum b/desktop/go.sum index 9b6e2b7..566db96 100644 --- a/desktop/go.sum +++ b/desktop/go.sum @@ -261,7 +261,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7 h1:AeNHqITptzOpmfMxnqmQRw6xN7DUDCgsN00BaPyRd4k= -maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0= +maunium.net/go/mautrix v0.23.2-0.20250305232906-e306c2817edd h1:kQVkgTP/V19Eo4E6NH1QsMzNh7RJ2Ye1oUO9x307PFQ= +maunium.net/go/mautrix v0.23.2-0.20250305232906-e306c2817edd/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0= mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI= mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk= diff --git a/go.mod b/go.mod index 5f3a138..870d42b 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( golang.org/x/text v0.22.0 gopkg.in/yaml.v3 v3.0.1 maunium.net/go/mauflag v1.0.0 - maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7 + maunium.net/go/mautrix v0.23.2-0.20250305232906-e306c2817edd mvdan.cc/xurls/v2 v2.6.0 ) diff --git a/go.sum b/go.sum index 12ea087..06c7e4d 100644 --- a/go.sum +++ b/go.sum @@ -100,7 +100,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7 h1:AeNHqITptzOpmfMxnqmQRw6xN7DUDCgsN00BaPyRd4k= -maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0= +maunium.net/go/mautrix v0.23.2-0.20250305232906-e306c2817edd h1:kQVkgTP/V19Eo4E6NH1QsMzNh7RJ2Ye1oUO9x307PFQ= +maunium.net/go/mautrix v0.23.2-0.20250305232906-e306c2817edd/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0= mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI= mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk= diff --git a/pkg/hicli/json-commands.go b/pkg/hicli/json-commands.go index eafa387..49bf4e1 100644 --- a/pkg/hicli/json-commands.go +++ b/pkg/hicli/json-commands.go @@ -65,7 +65,16 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any }) case "set_state": return unmarshalAndCall(req.Data, func(params *sendStateEventParams) (id.EventID, error) { - return h.SetState(ctx, params.RoomID, params.EventType, params.StateKey, params.Content) + return h.SetState(ctx, params.RoomID, params.EventType, params.StateKey, params.Content, mautrix.ReqSendEvent{ + UnstableDelay: time.Duration(params.DelayMS) * time.Millisecond, + }) + }) + case "update_delayed_event": + return unmarshalAndCall(req.Data, func(params *updateDelayedEventParams) (*mautrix.RespUpdateDelayedEvent, error) { + return h.Client.UpdateDelayedEvent(ctx, &mautrix.ReqUpdateDelayedEvent{ + DelayID: params.DelayID, + Action: params.Action, + }) }) case "set_membership": return unmarshalAndCall(req.Data, func(params *setMembershipParams) (any, error) { @@ -308,6 +317,12 @@ type sendStateEventParams struct { EventType event.Type `json:"type"` StateKey string `json:"state_key"` Content json.RawMessage `json:"content"` + DelayMS int `json:"delay_ms"` +} + +type updateDelayedEventParams struct { + DelayID string `json:"delay_id"` + Action string `json:"action"` } type setMembershipParams struct { diff --git a/pkg/hicli/send.go b/pkg/hicli/send.go index 84cb4c1..2021e53 100644 --- a/pkg/hicli/send.go +++ b/pkg/hicli/send.go @@ -226,6 +226,7 @@ func (h *HiClient) SetState( evtType event.Type, stateKey string, content any, + extra ...mautrix.ReqSendEvent, ) (id.EventID, error) { room, err := h.DB.Room.Get(ctx, roomID) if err != nil { @@ -233,10 +234,14 @@ func (h *HiClient) SetState( } else if room == nil { return "", fmt.Errorf("unknown room") } - resp, err := h.Client.SendStateEvent(ctx, room.ID, evtType, stateKey, content) + resp, err := h.Client.SendStateEvent(ctx, room.ID, evtType, stateKey, content, extra...) if err != nil { return "", err } + if resp.UnstableDelayID != "" { + // Mildly hacky, but it's fine' + return id.EventID(resp.UnstableDelayID), nil + } return resp.EventID, nil } diff --git a/web/src/api/rpc.ts b/web/src/api/rpc.ts index 935f953..737b8de 100644 --- a/web/src/api/rpc.ts +++ b/web/src/api/rpc.ts @@ -174,8 +174,13 @@ export default abstract class RPCClient { setState( room_id: RoomID, type: EventType, state_key: string, content: Record, + extra: { delay_ms?: number } = {}, ): Promise { - return this.request("set_state", { room_id, type, state_key, content }) + return this.request("set_state", { room_id, type, state_key, content, ...extra }) + } + + updateDelayedEvent(delay_id: string, action: string): Promise { + return this.request("update_delayed_event", { delay_id, action }) } setMembership(room_id: RoomID, user_id: UserID, action: MembershipAction, reason?: string): Promise { diff --git a/web/src/ui/widget/widgetDriver.ts b/web/src/ui/widget/widgetDriver.ts index 82e058d..785bfbc 100644 --- a/web/src/ui/widget/widgetDriver.ts +++ b/web/src/ui/widget/widgetDriver.ts @@ -19,11 +19,13 @@ import { IOpenIDUpdate, IRoomAccountData, IRoomEvent, + ISendDelayedEventDetails, ISendEventDetails, ITurnServer, OpenIDRequestState, SimpleObservable, Symbols, + UpdateDelayedEventAction, WidgetDriver, } from "matrix-widget-api" import Client from "@/api/client.ts" @@ -62,23 +64,31 @@ class GomuksWidgetDriver extends WidgetDriver { } } - // async sendDelayedEvent( - // delay: number | null, - // parentDelayID: string | null, - // eventType: string, - // content: unknown, - // stateKey: string | null = null, - // roomID: string | null = null, - // ): Promise { - // if (!isRecord(content)) { - // throw new Error("Content must be an object") - // } - // throw new Error("Delayed events are not supported") - // } + async sendDelayedEvent( + delay: number | null, + parentDelayID: string | null, + eventType: string, + content: unknown, + stateKey: string | null = null, + roomID: string | null = null, + ): Promise { + if (!isRecord(content)) { + throw new Error("Content must be an object") + } else if (stateKey === null) { + throw new Error("Non-state delayed events are not supported") + } else if (parentDelayID !== null) { + throw new Error("Parent delayed events are not supported") + } else if (!delay) { + throw new Error("Delay must be a number") + } + roomID = roomID ?? this.room.roomID + const delayID = await this.client.rpc.setState(roomID, eventType, stateKey, content, { delay_ms: delay }) + return { delayId: delayID, roomId: roomID } + } - // async updateDelayedEvent(delayID: string, action: UpdateDelayedEventAction): Promise { - // throw new Error("Delayed events are not supported") - // } + async updateDelayedEvent(delayID: string, action: UpdateDelayedEventAction): Promise { + await this.client.rpc.updateDelayedEvent(delayID, action) + } async sendToDevice( eventType: string,