gomuks/pkg/hicli/json-commands.go
2025-02-25 22:15:11 +02:00

395 lines
13 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 hicli
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"go.mau.fi/gomuks/pkg/hicli/database"
)
func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any, error) {
switch req.Command {
case "get_state":
return h.State(), nil
case "cancel":
return unmarshalAndCall(req.Data, func(params *cancelRequestParams) (bool, error) {
h.jsonRequestsLock.Lock()
cancelTarget, ok := h.jsonRequests[params.RequestID]
h.jsonRequestsLock.Unlock()
if ok {
return false, nil
}
if params.Reason == "" {
cancelTarget(nil)
} else {
cancelTarget(errors.New(params.Reason))
}
return true, nil
})
case "send_message":
return unmarshalAndCall(req.Data, func(params *sendMessageParams) (*database.Event, error) {
return h.SendMessage(ctx, params.RoomID, params.BaseContent, params.Extra, params.Text, params.RelatesTo, params.Mentions)
})
case "send_event":
return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) {
return h.Send(ctx, params.RoomID, params.EventType, params.Content)
})
case "resend_event":
return unmarshalAndCall(req.Data, func(params *resendEventParams) (*database.Event, error) {
return h.Resend(ctx, params.TransactionID)
})
case "report_event":
return unmarshalAndCall(req.Data, func(params *reportEventParams) (bool, error) {
return true, h.Client.ReportEvent(ctx, params.RoomID, params.EventID, params.Reason)
})
case "redact_event":
return unmarshalAndCall(req.Data, func(params *redactEventParams) (*mautrix.RespSendEvent, error) {
return h.Client.RedactEvent(ctx, params.RoomID, params.EventID, mautrix.ReqRedact{
Reason: params.Reason,
})
})
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)
})
case "set_membership":
return unmarshalAndCall(req.Data, func(params *setMembershipParams) (any, error) {
switch params.Action {
case "invite":
return h.Client.InviteUser(ctx, params.RoomID, &mautrix.ReqInviteUser{UserID: params.UserID, Reason: params.Reason})
case "kick":
return h.Client.KickUser(ctx, params.RoomID, &mautrix.ReqKickUser{UserID: params.UserID, Reason: params.Reason})
case "ban":
return h.Client.BanUser(ctx, params.RoomID, &mautrix.ReqBanUser{UserID: params.UserID, Reason: params.Reason})
case "unban":
return h.Client.UnbanUser(ctx, params.RoomID, &mautrix.ReqUnbanUser{UserID: params.UserID, Reason: params.Reason})
default:
return nil, fmt.Errorf("unknown action %q", params.Action)
}
})
case "set_account_data":
return unmarshalAndCall(req.Data, func(params *setAccountDataParams) (bool, error) {
if params.RoomID != "" {
return true, h.Client.SetRoomAccountData(ctx, params.RoomID, params.Type, params.Content)
} else {
return true, h.Client.SetAccountData(ctx, params.Type, params.Content)
}
})
case "mark_read":
return unmarshalAndCall(req.Data, func(params *markReadParams) (bool, error) {
return true, h.MarkRead(ctx, params.RoomID, params.EventID, params.ReceiptType)
})
case "set_typing":
return unmarshalAndCall(req.Data, func(params *setTypingParams) (bool, error) {
return true, h.SetTyping(ctx, params.RoomID, time.Duration(params.Timeout)*time.Millisecond)
})
case "get_profile":
return unmarshalAndCall(req.Data, func(params *getProfileParams) (*mautrix.RespUserProfile, error) {
return h.Client.GetProfile(ctx, params.UserID)
})
case "set_profile_field":
return unmarshalAndCall(req.Data, func(params *setProfileFieldParams) (bool, error) {
return true, h.Client.UnstableSetProfileField(ctx, params.Field, params.Value)
})
case "get_mutual_rooms":
return unmarshalAndCall(req.Data, func(params *getProfileParams) ([]id.RoomID, error) {
return h.GetMutualRooms(ctx, params.UserID)
})
case "track_user_devices":
return unmarshalAndCall(req.Data, func(params *getProfileParams) (*ProfileEncryptionInfo, error) {
err := h.TrackUserDevices(ctx, params.UserID)
if err != nil {
return nil, err
}
return h.GetProfileEncryptionInfo(ctx, params.UserID)
})
case "get_profile_encryption_info":
return unmarshalAndCall(req.Data, func(params *getProfileParams) (*ProfileEncryptionInfo, error) {
return h.GetProfileEncryptionInfo(ctx, params.UserID)
})
case "get_event":
return unmarshalAndCall(req.Data, func(params *getEventParams) (*database.Event, error) {
if params.Unredact {
return h.GetUnredactedEvent(ctx, params.RoomID, params.EventID)
}
return h.GetEvent(ctx, params.RoomID, params.EventID)
})
case "get_related_events":
return unmarshalAndCall(req.Data, func(params *getRelatedEventsParams) ([]*database.Event, error) {
return h.DB.Event.GetRelatedEvents(ctx, params.RoomID, params.EventID, params.RelationType)
})
case "get_room_state":
return unmarshalAndCall(req.Data, func(params *getRoomStateParams) ([]*database.Event, error) {
return h.GetRoomState(ctx, params.RoomID, params.IncludeMembers, params.FetchMembers, params.Refetch)
})
case "get_specific_room_state":
return unmarshalAndCall(req.Data, func(params *getSpecificRoomStateParams) ([]*database.Event, error) {
return h.DB.CurrentState.GetMany(ctx, params.Keys)
})
case "get_receipts":
return unmarshalAndCall(req.Data, func(params *getReceiptsParams) (map[id.EventID][]*database.Receipt, error) {
return h.GetReceipts(ctx, params.RoomID, params.EventIDs)
})
case "paginate":
return unmarshalAndCall(req.Data, func(params *paginateParams) (*PaginationResponse, error) {
return h.Paginate(ctx, params.RoomID, params.MaxTimelineID, params.Limit)
})
case "paginate_server":
return unmarshalAndCall(req.Data, func(params *paginateParams) (*PaginationResponse, error) {
return h.PaginateServer(ctx, params.RoomID, params.Limit)
})
case "get_room_summary":
return unmarshalAndCall(req.Data, func(params *joinRoomParams) (*mautrix.RespRoomSummary, error) {
return h.Client.GetRoomSummary(ctx, params.RoomIDOrAlias, params.Via...)
})
case "join_room":
return unmarshalAndCall(req.Data, func(params *joinRoomParams) (*mautrix.RespJoinRoom, error) {
return h.Client.JoinRoom(ctx, params.RoomIDOrAlias, &mautrix.ReqJoinRoom{
Via: params.Via,
Reason: params.Reason,
})
})
case "leave_room":
return unmarshalAndCall(req.Data, func(params *leaveRoomParams) (*mautrix.RespLeaveRoom, error) {
return h.Client.LeaveRoom(ctx, params.RoomID, &mautrix.ReqLeave{Reason: params.Reason})
})
case "ensure_group_session_shared":
return unmarshalAndCall(req.Data, func(params *ensureGroupSessionSharedParams) (bool, error) {
return true, h.EnsureGroupSessionShared(ctx, params.RoomID)
})
case "resolve_alias":
return unmarshalAndCall(req.Data, func(params *resolveAliasParams) (*mautrix.RespAliasResolve, error) {
return h.Client.ResolveAlias(ctx, params.Alias)
})
case "request_openid_token":
return h.Client.RequestOpenIDToken(ctx)
case "logout":
if h.LogoutFunc == nil {
return nil, errors.New("logout not supported")
}
return true, h.LogoutFunc(ctx)
case "login":
return unmarshalAndCall(req.Data, func(params *loginParams) (bool, error) {
return true, h.LoginPassword(ctx, params.HomeserverURL, params.Username, params.Password)
})
case "login_custom":
return unmarshalAndCall(req.Data, func(params *loginCustomParams) (bool, error) {
var err error
h.Client.HomeserverURL, err = url.Parse(params.HomeserverURL)
if err != nil {
return false, err
}
return true, h.Login(ctx, params.Request)
})
case "verify":
return unmarshalAndCall(req.Data, func(params *verifyParams) (bool, error) {
return true, h.Verify(ctx, params.RecoveryKey)
})
case "discover_homeserver":
return unmarshalAndCall(req.Data, func(params *discoverHomeserverParams) (*mautrix.ClientWellKnown, error) {
_, homeserver, err := params.UserID.Parse()
if err != nil {
return nil, err
}
return mautrix.DiscoverClientAPI(ctx, homeserver)
})
case "get_login_flows":
return unmarshalAndCall(req.Data, func(params *getLoginFlowsParams) (*mautrix.RespLoginFlows, error) {
cli, err := h.tempClient(params.HomeserverURL)
if err != nil {
return nil, err
}
err = h.checkServerVersions(ctx, cli)
if err != nil {
return nil, err
}
return cli.GetLoginFlows(ctx)
})
case "register_push":
return unmarshalAndCall(req.Data, func(params *database.PushRegistration) (bool, error) {
return true, h.DB.PushRegistration.Put(ctx, params)
})
case "create_room":
return unmarshalAndCall(req.Data, func(params *mautrix.ReqCreateRoom) (*mautrix.RespCreateRoom, error) {
return h.Client.CreateRoom(ctx, params)
})
default:
return nil, fmt.Errorf("unknown command %q", req.Command)
}
}
func unmarshalAndCall[T, O any](data json.RawMessage, fn func(*T) (O, error)) (output O, err error) {
var input T
err = json.Unmarshal(data, &input)
if err != nil {
return
}
return fn(&input)
}
type cancelRequestParams struct {
RequestID int64 `json:"request_id"`
Reason string `json:"reason"`
}
type sendMessageParams struct {
RoomID id.RoomID `json:"room_id"`
BaseContent *event.MessageEventContent `json:"base_content"`
Extra map[string]any `json:"extra"`
Text string `json:"text"`
RelatesTo *event.RelatesTo `json:"relates_to"`
Mentions *event.Mentions `json:"mentions"`
}
type sendEventParams struct {
RoomID id.RoomID `json:"room_id"`
EventType event.Type `json:"type"`
Content json.RawMessage `json:"content"`
}
type resendEventParams struct {
TransactionID string `json:"transaction_id"`
}
type reportEventParams struct {
RoomID id.RoomID `json:"room_id"`
EventID id.EventID `json:"event_id"`
Reason string `json:"reason"`
}
type redactEventParams struct {
RoomID id.RoomID `json:"room_id"`
EventID id.EventID `json:"event_id"`
Reason string `json:"reason"`
}
type sendStateEventParams struct {
RoomID id.RoomID `json:"room_id"`
EventType event.Type `json:"type"`
StateKey string `json:"state_key"`
Content json.RawMessage `json:"content"`
}
type setMembershipParams struct {
Action string `json:"action"`
RoomID id.RoomID `json:"room_id"`
UserID id.UserID `json:"user_id"`
Reason string `json:"reason"`
}
type setAccountDataParams struct {
RoomID id.RoomID `json:"room_id,omitempty"`
Type string `json:"type"`
Content json.RawMessage `json:"content"`
}
type markReadParams struct {
RoomID id.RoomID `json:"room_id"`
EventID id.EventID `json:"event_id"`
ReceiptType event.ReceiptType `json:"receipt_type"`
}
type setTypingParams struct {
RoomID id.RoomID `json:"room_id"`
Timeout int `json:"timeout"`
}
type getProfileParams struct {
UserID id.UserID `json:"user_id"`
}
type setProfileFieldParams struct {
Field string `json:"field"`
Value any `json:"value"`
}
type getEventParams struct {
RoomID id.RoomID `json:"room_id"`
EventID id.EventID `json:"event_id"`
Unredact bool `json:"unredact"`
}
type getRelatedEventsParams struct {
RoomID id.RoomID `json:"room_id"`
EventID id.EventID `json:"event_id"`
RelationType event.RelationType `json:"relation_type"`
}
type getRoomStateParams struct {
RoomID id.RoomID `json:"room_id"`
Refetch bool `json:"refetch"`
FetchMembers bool `json:"fetch_members"`
IncludeMembers bool `json:"include_members"`
}
type getSpecificRoomStateParams struct {
Keys []database.RoomStateGUID `json:"keys"`
}
type ensureGroupSessionSharedParams struct {
RoomID id.RoomID `json:"room_id"`
}
type resolveAliasParams struct {
Alias id.RoomAlias `json:"alias"`
}
type loginParams struct {
HomeserverURL string `json:"homeserver_url"`
Username string `json:"username"`
Password string `json:"password"`
}
type loginCustomParams struct {
HomeserverURL string `json:"homeserver_url"`
Request *mautrix.ReqLogin `json:"request"`
}
type verifyParams struct {
RecoveryKey string `json:"recovery_key"`
}
type discoverHomeserverParams struct {
UserID id.UserID `json:"user_id"`
}
type getLoginFlowsParams struct {
HomeserverURL string `json:"homeserver_url"`
}
type paginateParams struct {
RoomID id.RoomID `json:"room_id"`
MaxTimelineID database.TimelineRowID `json:"max_timeline_id"`
Limit int `json:"limit"`
}
type joinRoomParams struct {
RoomIDOrAlias string `json:"room_id_or_alias"`
Via []string `json:"via"`
Reason string `json:"reason"`
}
type leaveRoomParams struct {
RoomID id.RoomID `json:"room_id"`
Reason string `json:"reason"`
}
type getReceiptsParams struct {
RoomID id.RoomID `json:"room_id"`
EventIDs []id.EventID `json:"event_ids"`
}