Documentation
¶
Overview ¶
Package redact produces placeholder text for a viewer who may see that a message exists but not what it says: a non-member browsing a group whose listener rules they do not satisfy. The client blurs the placeholder the way it blurs a blurhash, and like a blurhash the placeholder carries only shape — script, rough length, line structure — never content.
The guarantee is structural rather than statistical. Text is a pure function of the seed and the message's shape (see shape), so the placeholder cannot depend on anything else: two messages of the same shape yield byte-identical placeholders under the same seed, and no transform of the original characters is ever performed. What a viewer learns is bounded by the number of distinct shapes, a few bits per message. Line structure is the non-blank line count and the blank lines between them, each capped low, so a bubble padded tall with empty lines renders about as tall. The one field of a shape that reflects content is the hue of each emoji the placeholder renders (see hue), a deliberate widening that mirrors the colours a blurhash keeps for an image.
Filler is drawn from an alphabet of the message's own script so the platform's text layout does the rest: Arabic filler joins into cursive and lays out right to left, CJK filler is wide and unspaced, emoji-only messages stay emoji-sized. Alphabets contain only letters, so a placeholder is never something a client's link or mention detector fires on, and rendered unblurred by a buggy client it still reveals nothing.
Index ¶
- Variables
- func ChatUpdate(chatID *commonpb.ChatId, update *eventpb.ChatUpdate) (*eventpb.ChatUpdate, error)
- func Content(chatID *commonpb.ChatId, messageID *messagingpb.MessageId, ...) (*messagingpb.Content, error)
- func Message(chatID *commonpb.ChatId, msg *messagingpb.Message) (*messagingpb.Message, error)
- func Text(seed []byte, text string) string
Constants ¶
This section is empty.
Variables ¶
var ErrUnsupportedContent = errors.New("redact: unsupported content")
ErrUnsupportedContent is returned by Content for content it defines no placeholder for.
Functions ¶
func ChatUpdate ¶
func ChatUpdate(chatID *commonpb.ChatId, update *eventpb.ChatUpdate) (*eventpb.ChatUpdate, error)
ChatUpdate returns the redacted copy of a chat update: the same update with every message it carries replaced by its redaction (see Message) and everything else as it was. The input is never modified, so one update can be shared by pointer between a full stream and a redacted one, as the event delivery path shares every event.
The mode a viewer reads a chat under decides only the messages (see eventpb.StreamEventsRequest.ChatParams), and an update carries them in three places: the mutations of its event-log events, the last_message of a metadata refresh, and the last_message of the metadata a MemberJoined hands its recipient. Pointer, typing, reaction and roster overlays pass through as they are — they describe the conversation's movement, which a redacted view shows, not its words. Like Message, the copy is built field by field so that every field carried is an allowlist decision: a field added to ChatUpdate later, or a new kind of mutation, metadata update or roster update, is refused with an error until one is made for it, rather than passed through to a viewer who may not read it.
The chat ID seeds every placeholder (see Message), so a message redacted on the stream renders exactly as the same message redacted by GetMessages or GetDelta, and a client applying stream events to a redacted store sees one shape throughout.
func Content ¶
func Content(chatID *commonpb.ChatId, messageID *messagingpb.MessageId, c *messagingpb.Content) (*messagingpb.Content, error)
Content returns a placeholder for a message's content: the same kind of content with the same structure — a text of the same shape, a reply to the same message, an image with the same caption shape, a cash card in the same denomination — and none of what the sender put in it. The input is never modified.
The placeholder is built field by field rather than by clearing the original, so every field is an allowlist decision and a field added to the proto later is dropped until one is made for it. Per kind:
- Text: the text becomes Text(seed, text).
- Reply: the replied-to message ID, a per-chat sequence number, is kept so the client can quote the message it points at — the quoted message is redacted on its own. The body is redacted like a message.
- Media: every rendition is kept with its blob ID and metadata — mime type, size, and for an image its dimensions and blurhash, which is the blurred preview — less the download URL, a bearer credential for the bytes. The blob ID is a handle the blob service checks access on, so it gives a viewer nothing. Metadata without a download URL is what the proto defines for a viewer who may know what a blob is but not fetch it (see BlobMetadata.download_url): the client renders the blurhash and does not ask for a URL. The caption is redacted like text.
- Cash: kept as is. A payment is public on chain, so a cash card shows nothing a viewer could not learn there.
- Deleted: kept as is. It carries no content, and who deleted a message is the same kind of fact as who sent one, which is not this function's to hide.
- System: kept as is. A system message is the server's account of what happened in the chat — a join, a departure — not something a member said, and the proto contract (Message.redacted) lists it with cash and deleted content as never redacted. Its structure is still settling; a variant that carries member-authored text is a new decision here, not a pass-through.
A nil content, or one of a kind this function does not know, is ErrUnsupportedContent: nothing unknown is passed through, and the caller decides what to do with a message it cannot show.
The chat and message IDs seed every text in the content (see messageSeed), so the same message renders the same placeholder wherever it is read.
func Message ¶
func Message(chatID *commonpb.ChatId, msg *messagingpb.Message) (*messagingpb.Message, error)
Message returns the redacted copy of a message: the same message — its identity, sender, timestamps, sequence numbers — with each content item replaced by its placeholder (see Content) and Message.redacted set. The input is never modified, and a redacted copy is its own redaction.
Like Content, the copy is built field by field so every field carried is an allowlist decision and a field added to the proto later is dropped until one is made for it:
- message_id, ts, unread_seq, last_edited_ts, event_sequence: kept. They say that a message exists, when, and how the chat has moved, which is exactly what a redacted view shows. event_sequence still describes the underlying message: a redacted copy is not a version of it (see Message.redacted).
- sender_id: kept. Who spoke is the same kind of fact as who deleted a message (see Content on DeletedContent), and a redacted view is a conversation's rhythm — who is talking, how much — with the words removed.
- content: each item is its placeholder. An item Content refuses fails the whole message, so nothing unknown is passed through.
- reactions: not carried, as no message read carries them. Reactions are an overlay the reaction RPCs and the event stream serve, to a redacted reader as to a full one (see messaging.Server's overlayStanding): who reacted, with what, on which message is the conversation's movement, not its words.
The message is expected to have had its media resolved already, so the placeholder keeps each rendition's dimensions and blurhash (see Content on media); redacting a stored message that carries only its ORIGINAL blob ID leaves the viewer nothing to render but the placeholder's frame.
func Text ¶
Text returns filler that stands in for text: the same script, direction, bucketed length, bucketed line count and bucketed blank lines, none of the content. The seed is normally the message ID, so the same message renders the same placeholder across pages, delta catch-up and devices, and no state is stored.
Text that displays nothing (whitespace, format characters, stray combining marks) yields "".
Types ¶
This section is empty.