mirror of
https://github.com/element-hq/synapse.git
synced 2025-03-14 09:45:51 +00:00
Merge 4dbca25a40
into 59a15da433
This commit is contained in:
commit
3ac39a212e
8 changed files with 196 additions and 4 deletions
1
changelog.d/18195.feature
Normal file
1
changelog.d/18195.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add plain-text handling for rich-text topics as per [MSC3765](https://github.com/matrix-org/matrix-spec-proposals/pull/3765).
|
|
@ -1296,7 +1296,7 @@ class RoomCreationHandler:
|
|||
topic = room_config["topic"]
|
||||
topic_event, topic_context = await create_event(
|
||||
EventTypes.Topic,
|
||||
{"topic": topic},
|
||||
{"topic": topic, "m.topic": {"m.text": [{"body": topic}]}},
|
||||
True,
|
||||
)
|
||||
events_to_send.append((topic_event, topic_context))
|
||||
|
|
|
@ -36,6 +36,7 @@ from synapse.metrics import event_processing_positions
|
|||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.databases.main.state_deltas import StateDelta
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.events import get_plain_text_topic_from_event_content
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.server import HomeServer
|
||||
|
@ -299,7 +300,9 @@ class StatsHandler:
|
|||
elif delta.event_type == EventTypes.Name:
|
||||
room_state["name"] = event_content.get("name")
|
||||
elif delta.event_type == EventTypes.Topic:
|
||||
room_state["topic"] = event_content.get("topic")
|
||||
room_state["topic"] = get_plain_text_topic_from_event_content(
|
||||
event_content
|
||||
)
|
||||
elif delta.event_type == EventTypes.RoomAvatar:
|
||||
room_state["avatar"] = event_content.get("url")
|
||||
elif delta.event_type == EventTypes.CanonicalAlias:
|
||||
|
|
|
@ -78,6 +78,7 @@ from synapse.types import (
|
|||
from synapse.types.handlers import SLIDING_SYNC_DEFAULT_BUMP_EVENT_TYPES
|
||||
from synapse.types.state import StateFilter
|
||||
from synapse.util import json_encoder
|
||||
from synapse.util.events import get_plain_text_topic_from_event_content
|
||||
from synapse.util.iterutils import batch_iter, sorted_topologically
|
||||
from synapse.util.stringutils import non_null_str_or_none
|
||||
|
||||
|
@ -3102,7 +3103,10 @@ class PersistEventsStore:
|
|||
def _store_room_topic_txn(self, txn: LoggingTransaction, event: EventBase) -> None:
|
||||
if isinstance(event.content.get("topic"), str):
|
||||
self.store_event_search_txn(
|
||||
txn, event, "content.topic", event.content["topic"]
|
||||
txn,
|
||||
event,
|
||||
"content.topic",
|
||||
get_plain_text_topic_from_event_content(event.content) or "",
|
||||
)
|
||||
|
||||
def _store_room_name_txn(self, txn: LoggingTransaction, event: EventBase) -> None:
|
||||
|
|
|
@ -48,6 +48,7 @@ from synapse.storage.databases.main.events_worker import InvalidEventError
|
|||
from synapse.storage.databases.main.state_deltas import StateDeltasStore
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.caches.descriptors import cached
|
||||
from synapse.util.events import get_plain_text_topic_from_event_content
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.server import HomeServer
|
||||
|
@ -611,7 +612,9 @@ class StatsStore(StateDeltasStore):
|
|||
elif event.type == EventTypes.Name:
|
||||
room_state["name"] = event.content.get("name")
|
||||
elif event.type == EventTypes.Topic:
|
||||
room_state["topic"] = event.content.get("topic")
|
||||
room_state["topic"] = get_plain_text_topic_from_event_content(
|
||||
event.content
|
||||
)
|
||||
elif event.type == EventTypes.RoomAvatar:
|
||||
room_state["avatar"] = event.content.get("url")
|
||||
elif event.type == EventTypes.CanonicalAlias:
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#
|
||||
#
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.stringutils import random_string
|
||||
|
||||
|
||||
|
@ -27,3 +30,31 @@ def generate_fake_event_id() -> str:
|
|||
A string intended to look like an event ID, but with no actual meaning.
|
||||
"""
|
||||
return "$" + random_string(43)
|
||||
|
||||
|
||||
def get_plain_text_topic_from_event_content(content: JsonDict) -> Optional[str]:
|
||||
"""
|
||||
Given the content of an m.room.topic event returns the plain text topic
|
||||
representation if any exists.
|
||||
|
||||
Returns:
|
||||
A string representing the plain text topic.
|
||||
"""
|
||||
topic = content.get("topic")
|
||||
|
||||
m_topic = content.get("m.topic")
|
||||
if not m_topic:
|
||||
return topic
|
||||
|
||||
m_text = m_topic.get("m.text")
|
||||
if not m_text:
|
||||
return topic
|
||||
|
||||
representation = next(
|
||||
(r for r in m_text if "mimetype" not in r or r["mimetype"] == "text/plain"),
|
||||
None,
|
||||
)
|
||||
if not representation or "body" not in representation:
|
||||
return topic
|
||||
|
||||
return representation["body"]
|
||||
|
|
|
@ -757,6 +757,59 @@ class RoomsCreateTestCase(RoomBase):
|
|||
assert channel.resource_usage is not None
|
||||
self.assertEqual(37, channel.resource_usage.db_txn_count)
|
||||
|
||||
def test_post_room_topic(self) -> None:
|
||||
# POST with topic key, expect new room id
|
||||
channel = self.make_request("POST", "/createRoom", b'{"topic":"shenanigans"}')
|
||||
self.assertEqual(HTTPStatus.OK, channel.code)
|
||||
self.assertTrue("room_id" in channel.json_body)
|
||||
room_id = channel.json_body["room_id"]
|
||||
|
||||
# GET topic event, expect content from topic key
|
||||
channel = self.make_request("GET", "/rooms/%s/state/m.room.topic" % (room_id,))
|
||||
self.assertEqual(HTTPStatus.OK, channel.code)
|
||||
self.assertEqual(
|
||||
{"topic": "shenanigans", "m.topic": {"m.text": [{"body": "shenanigans"}]}},
|
||||
channel.json_body,
|
||||
)
|
||||
|
||||
def test_post_room_topic_initial_state(self) -> None:
|
||||
# POST with m.room.topic in initial state, expect new room id
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
"/createRoom",
|
||||
b'{"initial_state":[{"type": "m.room.topic", "content": {"topic": "foobar"}}]}',
|
||||
)
|
||||
self.assertEqual(HTTPStatus.OK, channel.code)
|
||||
self.assertTrue("room_id" in channel.json_body)
|
||||
room_id = channel.json_body["room_id"]
|
||||
|
||||
# GET topic event, expect content from initial state
|
||||
channel = self.make_request("GET", "/rooms/%s/state/m.room.topic" % (room_id,))
|
||||
self.assertEqual(HTTPStatus.OK, channel.code)
|
||||
self.assertEqual(
|
||||
{"topic": "foobar"},
|
||||
channel.json_body,
|
||||
)
|
||||
|
||||
def test_post_room_topic_overriding_initial_state(self) -> None:
|
||||
# POST with m.room.topic in initial state and topic key, expect new room id
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
"/createRoom",
|
||||
b'{"initial_state":[{"type": "m.room.topic", "content": {"topic": "foobar"}}], "topic":"shenanigans"}',
|
||||
)
|
||||
self.assertEqual(HTTPStatus.OK, channel.code)
|
||||
self.assertTrue("room_id" in channel.json_body)
|
||||
room_id = channel.json_body["room_id"]
|
||||
|
||||
# GET topic event, expect content from topic key
|
||||
channel = self.make_request("GET", "/rooms/%s/state/m.room.topic" % (room_id,))
|
||||
self.assertEqual(HTTPStatus.OK, channel.code)
|
||||
self.assertEqual(
|
||||
{"topic": "shenanigans", "m.topic": {"m.text": [{"body": "shenanigans"}]}},
|
||||
channel.json_body,
|
||||
)
|
||||
|
||||
def test_post_room_visibility_key(self) -> None:
|
||||
# POST with visibility config key, expect new room id
|
||||
channel = self.make_request("POST", "/createRoom", b'{"visibility":"private"}')
|
||||
|
|
97
tests/util/test_events.py
Normal file
97
tests/util/test_events.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
#
|
||||
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
#
|
||||
# Copyright (C) 2025 New Vector, Ltd
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# See the GNU Affero General Public License for more details:
|
||||
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||
#
|
||||
# Originally licensed under the Apache License, Version 2.0:
|
||||
# <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# [This file includes modifications made by New Vector Limited]
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
from synapse.util.events import get_plain_text_topic_from_event_content
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class EventsTestCase(unittest.TestCase):
|
||||
def test_get_plain_text_topic_no_topic(self) -> None:
|
||||
# No legacy or rich topic, expect None
|
||||
topic = get_plain_text_topic_from_event_content({})
|
||||
self.assertEqual(None, topic)
|
||||
|
||||
def test_get_plain_text_topic_no_rich_topic(self) -> None:
|
||||
# Only legacy topic, expect legacy topic
|
||||
topic = get_plain_text_topic_from_event_content({"topic": "shenanigans"})
|
||||
self.assertEqual("shenanigans", topic)
|
||||
|
||||
def test_get_plain_text_topic_rich_topic_without_representations(self) -> None:
|
||||
# Legacy topic and rich topic without representations, expect legacy topic
|
||||
topic = get_plain_text_topic_from_event_content(
|
||||
{"topic": "shenanigans", "m.topic": {"m.text": []}}
|
||||
)
|
||||
self.assertEqual("shenanigans", topic)
|
||||
|
||||
def test_get_plain_text_topic_rich_topic_without_plain_text_representation(
|
||||
self,
|
||||
) -> None:
|
||||
# Legacy topic and rich topic without plain text representation, expect legacy topic
|
||||
topic = get_plain_text_topic_from_event_content(
|
||||
{
|
||||
"topic": "shenanigans",
|
||||
"m.topic": {
|
||||
"m.text": [
|
||||
{"mimetype": "text/html", "body": "<strong>foobar</strong>"}
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
self.assertEqual("shenanigans", topic)
|
||||
|
||||
def test_get_plain_text_topic_rich_topic_with_plain_text_representation(
|
||||
self,
|
||||
) -> None:
|
||||
# Legacy topic and rich topic with plain text representation, expect plain text representation
|
||||
topic = get_plain_text_topic_from_event_content(
|
||||
{
|
||||
"topic": "shenanigans",
|
||||
"m.topic": {"m.text": [{"mimetype": "text/plain", "body": "foobar"}]},
|
||||
}
|
||||
)
|
||||
self.assertEqual("foobar", topic)
|
||||
|
||||
def test_get_plain_text_topic_rich_topic_with_implicit_plain_text_representation(
|
||||
self,
|
||||
) -> None:
|
||||
# Legacy topic and rich topic with implicit plain text representation, expect plain text representation
|
||||
topic = get_plain_text_topic_from_event_content(
|
||||
{"topic": "shenanigans", "m.topic": {"m.text": [{"body": "foobar"}]}}
|
||||
)
|
||||
self.assertEqual("foobar", topic)
|
||||
|
||||
def test_get_plain_text_topic_rich_topic_with_plain_text_and_other_representation(
|
||||
self,
|
||||
) -> None:
|
||||
# Legacy topic and rich topic with plain text representation, expect plain text representation
|
||||
topic = get_plain_text_topic_from_event_content(
|
||||
{
|
||||
"topic": "shenanigans",
|
||||
"m.topic": {
|
||||
"m.text": [
|
||||
{"mimetype": "text/html", "body": "<strong>foobar</strong>"},
|
||||
{"mimetype": "text/plain", "body": "foobar"},
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
self.assertEqual("foobar", topic)
|
Loading…
Add table
Reference in a new issue