From 715f6a301dcb2276e0f15e7065a0ca9bc0625acf Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:17:31 +0100 Subject: [PATCH] Spec annotations (#1475) --- .../client_server/newsfragments/1475.feature | 1 + content/client-server-api/_index.md | 4 + .../modules/event_annotations.md | 93 +++++++++++++++++++ content/client-server-api/modules/push.md | 25 +++++ data/api/client-server/room_send.yaml | 9 ++ data/event-schemas/examples/m.reaction.yaml | 11 +++ data/event-schemas/schema/m.reaction.yaml | 37 ++++++++ 7 files changed, 180 insertions(+) create mode 100644 changelogs/client_server/newsfragments/1475.feature create mode 100644 content/client-server-api/modules/event_annotations.md create mode 100644 data/event-schemas/examples/m.reaction.yaml create mode 100644 data/event-schemas/schema/m.reaction.yaml diff --git a/changelogs/client_server/newsfragments/1475.feature b/changelogs/client_server/newsfragments/1475.feature new file mode 100644 index 00000000..a59c2177 --- /dev/null +++ b/changelogs/client_server/newsfragments/1475.feature @@ -0,0 +1 @@ +Add `m.annotation` relations (reactions), as per [MSC2677](https://github.com/matrix-org/matrix-spec-proposals/pull/2677). diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index a2ca4180..f9b432d4 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1996,6 +1996,7 @@ This specification describes the following relationship types: * [Rich replies](#rich-replies) (**Note**: does not use `rel_type`). * [Event replacements](#event-replacements). +* [Event annotations](#event-annotations-and-reactions). * [Threads](#threading). * [References](#reference-relations) @@ -2579,6 +2580,7 @@ that profile. | [Moderation policies](#moderation-policy-lists) | Optional | Optional | Optional | Optional | Optional | | [Spaces](#spaces) | Optional | Optional | Optional | Optional | Optional | | [Event Replacements](#event-replacements) | Optional | Optional | Optional | Optional | Optional | +| [Event Annotations and reactions](#event-annotations-and-reactions) | Optional | Optional | Optional | Optional | Optional | | [Threading](#threading) | Optional | Optional | Optional | Optional | Optional | | [Reference Relations](#reference-relations) | Optional | Optional | Optional | Optional | Optional | @@ -2665,5 +2667,7 @@ systems. {{< cs-module name="moderation_policies" >}} {{< cs-module name="spaces" >}} {{< cs-module name="event_replacements" >}} +{{< cs-module name="event_annotations" >}} {{< cs-module name="threading" >}} {{< cs-module name="reference_relations" >}} + diff --git a/content/client-server-api/modules/event_annotations.md b/content/client-server-api/modules/event_annotations.md new file mode 100644 index 00000000..8aeff40f --- /dev/null +++ b/content/client-server-api/modules/event_annotations.md @@ -0,0 +1,93 @@ +### Event annotations and reactions + +{{% added-in v="1.7" %}} + +#### `m.annotation` relationship type + +Annotations are events that use an [event +relationship](#forming-relationships-between-events) with a `rel_type` of +`m.annotation`. + +Annotations are normally used for "reactions": for example, if the user wants +to react to an event with a thumbs-up, then the client sends an annotation +event with the corresponding emoji (👍). Another potential usage is to allow +bots to send an event indicating the success or failure of a command. + +Along with the normal properties `event_id` and `rel_type`, an `m.relates_to` +property with `rel_type: m.annotion` should contain a `key` that indicates the +annotation being applied. For example, when reacting with emojis, the key +contains the emoji being used. + +An example `m.annotation` relationship is shown below: + +```json +"m.relates_to": { + "rel_type": "m.annotation", + "event_id": "$some_event_id", + "key": "👍" +} +``` + +{{% boxes/note %}} +Any `type` of event is eligible for an annotation, including state events. +{{% /boxes/note %}} + +#### Events + +{{% event event="m.reaction" %}} + +#### Client behaviour {id="annotations-client-behaviour"} + +The intention of annotations is that they are counted up, rather than being +displayed individually. Clients must keep count of the number of annotations +with a given event `type` and annotation `key` they observe for each event; +these counts are typically presented alongside the event in the timeline. + +When performing this count: + + * Each event `type` and annotation `key` should normally be counted + separately, though whether to actually do so is an implementation decision. + + * Annotation events sent by [ignored users](#ignoring-users) should be + excluded from the count. + + * Multiple identical annotations (i.e., with the same event `type` and + annotation `key`) from the same user (i.e., events with the same `sender`) + should be treated as a single annotation. + + * Implementations should ignore any annotation event which refers to an event + which itself has an `m.relates_to` with `rel_type: m.annotation` or + `rel_type: m.replace`. In other words, it is not possible to annotate a + [replacement event](#event-replacements) or an annotation. Annotations should + instead refer to the original event. + + * When an annotation is redacted, it is removed from the count. + +{{% boxes/note %}} +It is not possible to edit a reaction, since replacement events do not change +`m.relates_to` (see [Applying `m.new_content`](#applying-mnew_content)), and +there is no other meaningful content within `m.reaction`. If a user wishes to +change their reaction, the original reaction should be redacted and a new one +sent in its place. +{{% /boxes/note %}} + +#### Server behaviour + +##### Avoiding duplicate annotations + +Homeservers should prevent users from sending a second annotation for a given +event with identical event `type` and annotation `key` (unless the first event +has been redacted). + +Attempts to send such an annotation should be rejected with a 400 error and an +error code of `M_DUPLICATE_ANNOTATION`. + +Note that this does not guarantee that duplicate annotations will not arrive +over federation. Clients are responsible for deduplicating received +annotations when [counting annotations](#annotations-client-behaviour). + +##### Server-side aggregation of `m.annotation` relationships + +`m.annotation` relationships are *not* +[aggregated](#aggregations-of-child-events) by the server. In other words, +`m.annotation` is not included in the `m.relations` property. diff --git a/content/client-server-api/modules/push.md b/content/client-server-api/modules/push.md index a0680649..66c498ea 100644 --- a/content/client-server-api/modules/push.md +++ b/content/client-server-api/modules/push.md @@ -629,6 +629,31 @@ Definition: } ``` +**`.m.rule.reaction`** + +{{% added-in v="1.7" %}} + +Matches any event whose type is `m.room.reaction`. This suppresses notifications for [`m.reaction`](#mreaction) events. + +Definition: + +```json +{ + "rule_id": ".m.rule.reaction", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_match", + "key": "type", + "pattern": "m.reaction" + } + ], + "actions": [] +} +``` + + **`.m.rule.room.server_acl`** {{% added-in v="1.4" %}} diff --git a/data/api/client-server/room_send.yaml b/data/api/client-server/room_send.yaml index e38d2ebd..43823c45 100644 --- a/data/api/client-server/room_send.yaml +++ b/data/api/client-server/room_send.yaml @@ -1,4 +1,5 @@ # Copyright 2016 OpenMarket Ltd +# Copyright 2023 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -88,5 +89,13 @@ paths: A unique identifier for the event. required: - event_id + 400: + description: |- + The request is invalid. A [standard error response](/client-server-api/#standard-error-response) + will be returned. As well as the normal common error codes, other reasons for rejection include: + + - `M_DUPLICATE_ANNOTATION`: The request is an attempt to send a [duplicate annotation](/client-server-api/#avoiding-duplicate-annotations). + schema: + "$ref": "definitions/errors/error.yaml" tags: - Room participation diff --git a/data/event-schemas/examples/m.reaction.yaml b/data/event-schemas/examples/m.reaction.yaml new file mode 100644 index 00000000..54d9f709 --- /dev/null +++ b/data/event-schemas/examples/m.reaction.yaml @@ -0,0 +1,11 @@ +{ + "$ref": "core/room_event.json", + "type": "m.reaction", + "content": { + "m.relates_to": { + "rel_type": "m.annotation", + "event_id": "$some_event_id", + "key": "👍" + } + } +} diff --git a/data/event-schemas/schema/m.reaction.yaml b/data/event-schemas/schema/m.reaction.yaml new file mode 100644 index 00000000..4d8ce43b --- /dev/null +++ b/data/event-schemas/schema/m.reaction.yaml @@ -0,0 +1,37 @@ +allOf: + - $ref: core-event-schema/room_event.yaml +description: |- + Indicates a reaction to a previous event. + + Has no defined `content` properties of its own. Its only purpose is to hold an + [`m.relates_to`](/client-server-api/#definition-mrelates_to) property. + + Since they contain no content other than `m.relates_to`, `m.reaction` events + are normally not encrypted, as there would be no benefit in doing so. +type: object +properties: + content: + type: object + properties: + m.relates_to: + description: |- + Indicates the event being reacted to, and the type of reaction. + type: object + title: ReactionRelatesTo + properties: + rel_type: + type: string + enum: ["m.annotation"] + event_id: + type: string + description: |- + The event ID of the event that this is a reaction to. + example: "$some_event_id" + key: + type: string + description: |- + An emoji representing the reaction being made. Should include the + unicode emoji presentation selector (`\uFE0F`) for codepoints + which allow it (see the [emoji variation sequences + list](https://www.unicode.org/Public/UCD/latest/ucd/emoji/emoji-variation-sequences.txt)). + example: "👍"