mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
parent
4885dab2e1
commit
a14f01a3ec
15 changed files with 89 additions and 22 deletions
|
@ -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.1 // indirect
|
||||
maunium.net/go/mautrix v0.23.2-0.20250223161309-1cc073cde6ca // indirect
|
||||
mvdan.cc/xurls/v2 v2.6.0 // indirect
|
||||
)
|
||||
|
||||
|
|
|
@ -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.1 h1:xZtX43YZF3WRxkdR+oMUrpiQe+jbjc+LeXLxHuXP5IM=
|
||||
maunium.net/go/mautrix v0.23.1/go.mod h1:kldoZQDneV/jquIhwG1MmMw5j2A2M/MnQYRSWt863cY=
|
||||
maunium.net/go/mautrix v0.23.2-0.20250223161309-1cc073cde6ca h1:xPbRPallD4qh/XuQWheRsvxsf/5stfdA+uIj0S0P2kQ=
|
||||
maunium.net/go/mautrix v0.23.2-0.20250223161309-1cc073cde6ca/go.mod h1:kldoZQDneV/jquIhwG1MmMw5j2A2M/MnQYRSWt863cY=
|
||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||
|
|
2
go.mod
2
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.1
|
||||
maunium.net/go/mautrix v0.23.2-0.20250223161309-1cc073cde6ca
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
)
|
||||
|
||||
|
|
4
go.sum
4
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.1 h1:xZtX43YZF3WRxkdR+oMUrpiQe+jbjc+LeXLxHuXP5IM=
|
||||
maunium.net/go/mautrix v0.23.1/go.mod h1:kldoZQDneV/jquIhwG1MmMw5j2A2M/MnQYRSWt863cY=
|
||||
maunium.net/go/mautrix v0.23.2-0.20250223161309-1cc073cde6ca h1:xPbRPallD4qh/XuQWheRsvxsf/5stfdA+uIj0S0P2kQ=
|
||||
maunium.net/go/mautrix v0.23.2-0.20250223161309-1cc073cde6ca/go.mod h1:kldoZQDneV/jquIhwG1MmMw5j2A2M/MnQYRSWt863cY=
|
||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||
|
|
|
@ -123,6 +123,9 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any
|
|||
})
|
||||
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":
|
||||
|
@ -313,6 +316,7 @@ type setProfileFieldParams struct {
|
|||
type getEventParams struct {
|
||||
RoomID id.RoomID `json:"room_id"`
|
||||
EventID id.EventID `json:"event_id"`
|
||||
Unredact bool `json:"unredact"`
|
||||
}
|
||||
|
||||
type getRelatedEventsParams struct {
|
||||
|
|
|
@ -35,6 +35,26 @@ func (h *HiClient) GetEvent(ctx context.Context, roomID id.RoomID, eventID id.Ev
|
|||
}
|
||||
}
|
||||
|
||||
func (h *HiClient) GetUnredactedEvent(ctx context.Context, roomID id.RoomID, eventID id.EventID) (*database.Event, error) {
|
||||
if evt, err := h.DB.Event.GetByID(ctx, eventID); err != nil {
|
||||
return nil, fmt.Errorf("failed to get event from database: %w", err)
|
||||
// TODO this check doesn't handle events which keep some fields on redaction
|
||||
} else if evt != nil && len(evt.Content) > 2 {
|
||||
h.ReprocessExistingEvent(ctx, evt)
|
||||
return evt, nil
|
||||
} else if serverEvt, err := h.Client.GetUnredactedEventContent(ctx, roomID, eventID); err != nil {
|
||||
return nil, fmt.Errorf("failed to get event from server: %w", err)
|
||||
} else if redactedServerEvt, err := h.Client.GetEvent(ctx, roomID, eventID); err != nil {
|
||||
return nil, fmt.Errorf("failed to get redacted event from server: %w", err)
|
||||
// TODO this check will have false positives on actually empty events
|
||||
} else if len(serverEvt.Content.VeryRaw) == 2 {
|
||||
return nil, fmt.Errorf("server didn't return content")
|
||||
} else {
|
||||
serverEvt.Unsigned.RedactedBecause = redactedServerEvt.Unsigned.RedactedBecause
|
||||
return h.processEvent(ctx, serverEvt, nil, nil, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HiClient) processGetRoomState(ctx context.Context, roomID id.RoomID, fetchMembers, refetch, dispatchEvt bool) error {
|
||||
var evts []*event.Event
|
||||
if refetch {
|
||||
|
|
|
@ -222,17 +222,27 @@ export default class Client {
|
|||
})
|
||||
}
|
||||
|
||||
requestEvent(room: RoomStateStore | RoomID | undefined, eventID: EventID) {
|
||||
requestEvent(room: RoomStateStore | RoomID | undefined, eventID: EventID, unredact?: boolean) {
|
||||
if (typeof room === "string") {
|
||||
room = this.store.rooms.get(room)
|
||||
}
|
||||
if (!room || room.eventsByID.has(eventID) || room.requestedEvents.has(eventID)) {
|
||||
if (!room || (!unredact && room.eventsByID.has(eventID)) ||room.requestedEvents.has(eventID)) {
|
||||
return
|
||||
}
|
||||
room.requestedEvents.add(eventID)
|
||||
this.rpc.getEvent(room.roomID, eventID).then(
|
||||
evt => room.applyEvent(evt),
|
||||
err => console.error(`Failed to fetch event ${eventID}`, err),
|
||||
this.rpc.getEvent(room.roomID, eventID, unredact).then(
|
||||
evt => {
|
||||
room.applyEvent(evt, false, unredact)
|
||||
if (unredact) {
|
||||
room.notifyTimelineSubscribers()
|
||||
}
|
||||
},
|
||||
err => {
|
||||
console.error(`Failed to fetch event ${eventID}`, err)
|
||||
if (unredact) {
|
||||
window.alert(`Failed to get unredacted content: ${err}`)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -218,8 +218,8 @@ export default abstract class RPCClient {
|
|||
return this.request("get_room_state", { room_id, include_members, fetch_members, refetch })
|
||||
}
|
||||
|
||||
getEvent(room_id: RoomID, event_id: EventID): Promise<RawDBEvent> {
|
||||
return this.request("get_event", { room_id, event_id })
|
||||
getEvent(room_id: RoomID, event_id: EventID, unredact?: boolean): Promise<RawDBEvent> {
|
||||
return this.request("get_event", { room_id, event_id, unredact })
|
||||
}
|
||||
|
||||
getRelatedEvents(room_id: RoomID, event_id: EventID, relation_type?: RelationType): Promise<RawDBEvent[]> {
|
||||
|
|
|
@ -352,13 +352,22 @@ export class RoomStateStore {
|
|||
return this.applyEvent(evt)
|
||||
}
|
||||
|
||||
applyEvent(evt: RawDBEvent, pending: boolean = false) {
|
||||
setViewingRedacted(evt: MemDBEvent, view: boolean) {
|
||||
evt.viewing_redacted = view
|
||||
this.eventSubs.notify(evt.event_id)
|
||||
this.notifyTimelineSubscribers()
|
||||
}
|
||||
|
||||
applyEvent(evt: RawDBEvent, pending: boolean = false, viewRedacted: boolean = false) {
|
||||
const memEvt = evt as MemDBEvent
|
||||
memEvt.mem = true
|
||||
memEvt.pending = pending
|
||||
if (pending) {
|
||||
memEvt.timeline_rowid = UNSENT_TIMELINE_ROWID_BASE + memEvt.timestamp
|
||||
}
|
||||
if (viewRedacted) {
|
||||
memEvt.viewing_redacted = true
|
||||
}
|
||||
if (evt.type === "m.room.encrypted" && evt.decrypted && evt.decrypted_type) {
|
||||
memEvt.type = evt.decrypted_type
|
||||
memEvt.encrypted = evt.content as EncryptedEventContent
|
||||
|
|
|
@ -158,6 +158,7 @@ export interface MemDBEvent extends BaseDBEvent {
|
|||
orig_content?: UnknownEventContent
|
||||
orig_local_content?: LocalContent
|
||||
last_edit?: MemDBEvent
|
||||
viewing_redacted?: boolean
|
||||
}
|
||||
|
||||
export interface DBAccountData {
|
||||
|
|
1
web/src/icons/restore-trash.svg
Normal file
1
web/src/icons/restore-trash.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M440-320h80v-166l64 62 56-56-160-160-160 160 56 56 64-62v166ZM280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520Zm-400 0v520-520Z"/></svg>
|
After Width: | Height: | Size: 332 B |
|
@ -138,7 +138,8 @@ const TimelineEvent = ({
|
|||
if (evt.unread_type & UnreadType.Highlight) {
|
||||
wrapperClassNames.push("highlight")
|
||||
}
|
||||
if (evt.redacted_by) {
|
||||
const isRedacted = evt.redacted_by && !evt.viewing_redacted
|
||||
if (isRedacted) {
|
||||
wrapperClassNames.push("redacted-event")
|
||||
}
|
||||
if (evt.type === "m.room.member") {
|
||||
|
@ -173,7 +174,7 @@ const TimelineEvent = ({
|
|||
const replyTo = relatesTo?.["m.in_reply_to"]?.event_id
|
||||
let replyAboveMessage: JSX.Element | null = null
|
||||
let replyInMessage: JSX.Element | null = null
|
||||
if (isEventID(replyTo) && BodyType !== HiddenEvent && !evt.redacted_by && !editHistoryView) {
|
||||
if (isEventID(replyTo) && BodyType !== HiddenEvent && !isRedacted && !editHistoryView) {
|
||||
const replyElem = <ReplyIDBody
|
||||
room={roomCtx.store}
|
||||
eventID={replyTo}
|
||||
|
|
|
@ -65,10 +65,11 @@ export function getBodyType(evt: MemDBEvent, forReply = false): React.FunctionCo
|
|||
return PolicyRuleBody
|
||||
}
|
||||
} else {
|
||||
const isRedacted = evt.redacted_by && !evt.viewing_redacted
|
||||
// Non-state events
|
||||
switch (evt.type) {
|
||||
case "m.room.message":
|
||||
if (evt.redacted_by) {
|
||||
if (isRedacted) {
|
||||
return RedactedBody
|
||||
}
|
||||
switch (evt.content?.msgtype) {
|
||||
|
@ -93,14 +94,14 @@ export function getBodyType(evt: MemDBEvent, forReply = false): React.FunctionCo
|
|||
return UnknownMessageBody
|
||||
}
|
||||
case "m.sticker":
|
||||
if (evt.redacted_by) {
|
||||
if (isRedacted) {
|
||||
return RedactedBody
|
||||
} else if (forReply) {
|
||||
return TextMessageBody
|
||||
}
|
||||
return MediaMessageBody
|
||||
case "m.room.encrypted":
|
||||
if (evt.redacted_by) {
|
||||
if (isRedacted) {
|
||||
return RedactedBody
|
||||
}
|
||||
return EncryptedBody
|
||||
|
|
|
@ -79,7 +79,7 @@ export const usePrimaryItems = (
|
|||
.catch(err => window.alert(`Failed to resend message: ${err}`))
|
||||
}
|
||||
const onClickMore = (mevt: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const moreMenuHeight = 4 * 40
|
||||
const moreMenuHeight = 5 * 40
|
||||
setForceOpen!(true)
|
||||
openModal({
|
||||
content: <EventExtraMenu
|
||||
|
|
|
@ -27,6 +27,7 @@ import ViewSourceIcon from "@/icons/code.svg?react"
|
|||
import DeleteIcon from "@/icons/delete.svg?react"
|
||||
import PinIcon from "@/icons/pin.svg?react"
|
||||
import ReportIcon from "@/icons/report.svg?react"
|
||||
import RestoreTrashIcon from "@/icons/restore-trash.svg?react"
|
||||
import ShareIcon from "@/icons/share.svg?react"
|
||||
import UnpinIcon from "@/icons/unpin.svg?react"
|
||||
|
||||
|
@ -85,6 +86,18 @@ export const useSecondaryItems = (
|
|||
</RoomContext>,
|
||||
})
|
||||
}
|
||||
const onClickHideUnredacted = () => {
|
||||
closeModal()
|
||||
roomCtx.store.setViewingRedacted(evt, false)
|
||||
}
|
||||
const onClickUnredact = () => {
|
||||
closeModal()
|
||||
if (Object.entries(evt.content).length > 0) {
|
||||
roomCtx.store.setViewingRedacted(evt, true)
|
||||
} else {
|
||||
client.requestEvent(roomCtx.store, evt.event_id, true)
|
||||
}
|
||||
}
|
||||
const onClickPin = (pin: boolean) => () => {
|
||||
closeModal()
|
||||
client.pinMessage(roomCtx.store, evt.event_id, pin)
|
||||
|
@ -146,6 +159,8 @@ export const useSecondaryItems = (
|
|||
const canRedact = !evt.redacted_by
|
||||
&& ownPL >= redactEvtPL
|
||||
&& (evt.sender === client.userID || ownPL >= redactOtherPL)
|
||||
// TODO check server admin status and room PLs
|
||||
const canUnredact = Boolean(evt.redacted_by)
|
||||
|
||||
return <>
|
||||
<button onClick={onClickViewSource}><ViewSourceIcon/>{names && "View source"}</button>
|
||||
|
@ -166,5 +181,10 @@ export const useSecondaryItems = (
|
|||
title={pendingTitle}
|
||||
className="redact-button"
|
||||
><DeleteIcon/>{names && "Remove"}</button>}
|
||||
{canUnredact && (evt.viewing_redacted ? <button onClick={onClickHideUnredacted}>
|
||||
<DeleteIcon/>{names && "Hide content"}
|
||||
</button> : <button onClick={onClickUnredact}>
|
||||
<RestoreTrashIcon/>{names && "View content"}
|
||||
</button>)}
|
||||
</>
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue