events: Stabilize current support for threads
This commit is contained in:
parent
279c9d0fa6
commit
003f0abebf
@ -13,6 +13,8 @@ Improvements:
|
||||
* Stabilize default room server ACL push rule
|
||||
* Stabilize `room_types` in `directory::Filter` and `room_type` in `directory::PublicRoomsChunk`
|
||||
* Stabilize support for private read receipts
|
||||
* Add stable support for threads
|
||||
* Move `Relation::Thread` and associated types and methods out of `unstable-msc3440`
|
||||
|
||||
# 0.10.3
|
||||
|
||||
|
@ -38,7 +38,6 @@ unstable-msc2870 = []
|
||||
unstable-msc3245 = ["unstable-msc3246"]
|
||||
unstable-msc3246 = ["unstable-msc3551", "dep:thiserror"]
|
||||
unstable-msc3381 = ["unstable-msc1767"]
|
||||
unstable-msc3440 = []
|
||||
unstable-msc3488 = ["unstable-msc1767"]
|
||||
unstable-msc3551 = ["unstable-msc1767"]
|
||||
unstable-msc3552 = ["unstable-msc3551"]
|
||||
|
@ -4,17 +4,16 @@
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(any(feature = "unstable-msc2677", feature = "unstable-msc3440"))]
|
||||
use js_int::UInt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
use super::AnySyncMessageLikeEvent;
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
use crate::serde::Raw;
|
||||
use super::AnyMessageLikeEvent;
|
||||
#[cfg(any(feature = "unstable-msc2676", feature = "unstable-msc2677"))]
|
||||
use crate::MilliSecondsSinceUnixEpoch;
|
||||
use crate::{serde::StringEnum, PrivOwnedStr};
|
||||
use crate::{
|
||||
serde::{Raw, StringEnum},
|
||||
PrivOwnedStr,
|
||||
};
|
||||
#[cfg(feature = "unstable-msc2676")]
|
||||
use crate::{OwnedEventId, OwnedUserId};
|
||||
|
||||
@ -112,11 +111,10 @@ impl BundledReplacement {
|
||||
|
||||
/// A bundled thread.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct BundledThread {
|
||||
/// The latest event in the thread.
|
||||
pub latest_event: Box<Raw<AnySyncMessageLikeEvent>>,
|
||||
pub latest_event: Box<Raw<AnyMessageLikeEvent>>,
|
||||
|
||||
/// The number of events in the thread.
|
||||
pub count: UInt,
|
||||
@ -125,11 +123,10 @@ pub struct BundledThread {
|
||||
pub current_user_participated: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
impl BundledThread {
|
||||
/// Creates a new `BundledThread` with the given event, count and user participated flag.
|
||||
pub fn new(
|
||||
latest_event: Box<Raw<AnySyncMessageLikeEvent>>,
|
||||
latest_event: Box<Raw<AnyMessageLikeEvent>>,
|
||||
count: UInt,
|
||||
current_user_participated: bool,
|
||||
) -> Self {
|
||||
@ -154,8 +151,7 @@ pub struct Relations {
|
||||
pub replace: Option<BundledReplacement>,
|
||||
|
||||
/// Thread relation.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[serde(rename = "io.element.thread", alias = "m.thread")]
|
||||
#[serde(rename = "m.thread")]
|
||||
pub thread: Option<BundledThread>,
|
||||
}
|
||||
|
||||
@ -182,8 +178,7 @@ pub enum RelationType {
|
||||
Replacement,
|
||||
|
||||
/// `m.thread`, a participant to a thread.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[ruma_enum(rename = "io.element.thread", alias = "m.thread")]
|
||||
#[ruma_enum(rename = "m.thread")]
|
||||
Thread,
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -102,7 +102,6 @@ pub enum Relation {
|
||||
Annotation(Annotation),
|
||||
|
||||
/// An event that belongs to a thread.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
Thread(Thread),
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -117,7 +116,6 @@ impl From<message::Relation> for Relation {
|
||||
message::Relation::Replacement(re) => {
|
||||
Self::Replacement(Replacement { event_id: re.event_id })
|
||||
}
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
message::Relation::Thread(t) => Self::Thread(Thread {
|
||||
event_id: t.event_id,
|
||||
in_reply_to: t.in_reply_to,
|
||||
@ -178,7 +176,6 @@ impl Annotation {
|
||||
|
||||
/// A thread relation for an event.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Thread {
|
||||
/// The ID of the root message in the thread.
|
||||
@ -199,7 +196,6 @@ pub struct Thread {
|
||||
pub is_falling_back: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
impl Thread {
|
||||
/// Convenience method to create a regular `Thread` with the given event ID and latest
|
||||
/// message-like event ID.
|
||||
|
@ -4,10 +4,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use super::Annotation;
|
||||
#[cfg(feature = "unstable-msc2676")]
|
||||
use super::Replacement;
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
use super::Thread;
|
||||
use super::{InReplyTo, Reference, Relation};
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
use super::{InReplyTo, Reference, Relation, Thread};
|
||||
use crate::OwnedEventId;
|
||||
|
||||
impl<'de> Deserialize<'de> for Relation {
|
||||
@ -17,7 +14,6 @@ impl<'de> Deserialize<'de> for Relation {
|
||||
{
|
||||
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
|
||||
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
if let Some(
|
||||
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back })
|
||||
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }),
|
||||
@ -40,7 +36,6 @@ impl<'de> Deserialize<'de> for Relation {
|
||||
RelationJsonRepr::Replacement(Replacement { event_id }) => {
|
||||
Relation::Replacement(Replacement { event_id })
|
||||
}
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
RelationJsonRepr::ThreadStable(_) | RelationJsonRepr::ThreadUnstable(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
@ -80,11 +75,10 @@ impl Serialize for Relation {
|
||||
Relation::Reply { in_reply_to } => {
|
||||
RelatesToJsonRepr { in_reply_to: Some(in_reply_to.clone()), ..Default::default() }
|
||||
}
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }) => {
|
||||
RelatesToJsonRepr {
|
||||
in_reply_to: Some(in_reply_to.clone()),
|
||||
relation: Some(RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr {
|
||||
relation: Some(RelationJsonRepr::ThreadStable(ThreadStableJsonRepr {
|
||||
event_id: event_id.clone(),
|
||||
is_falling_back: *is_falling_back,
|
||||
})),
|
||||
@ -123,7 +117,6 @@ impl RelatesToJsonRepr {
|
||||
|
||||
/// A thread relation without the reply fallback, with stable names.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
struct ThreadStableJsonRepr {
|
||||
/// The ID of the root message in the thread.
|
||||
pub event_id: OwnedEventId,
|
||||
@ -136,7 +129,6 @@ struct ThreadStableJsonRepr {
|
||||
|
||||
/// A thread relation without the reply fallback, with unstable names.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
struct ThreadUnstableJsonRepr {
|
||||
/// The ID of the root message in the thread.
|
||||
pub event_id: OwnedEventId,
|
||||
@ -170,12 +162,10 @@ enum RelationJsonRepr {
|
||||
Replacement(Replacement),
|
||||
|
||||
/// An event that belongs to a thread, with stable names.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[serde(rename = "m.thread")]
|
||||
ThreadStable(ThreadStableJsonRepr),
|
||||
|
||||
/// An event that belongs to a thread, with unstable names.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[serde(rename = "io.element.thread")]
|
||||
ThreadUnstable(ThreadUnstableJsonRepr),
|
||||
|
||||
|
@ -166,7 +166,6 @@ impl RoomMessageEventContent {
|
||||
/// If `message` is a text, an emote or a notice message, it is modified to include the rich
|
||||
/// reply fallback.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
pub fn reply(
|
||||
message: MessageType,
|
||||
original_message: &OriginalRoomMessageEvent,
|
||||
@ -198,7 +197,7 @@ impl RoomMessageEventContent {
|
||||
.as_ref()
|
||||
.filter(|_| forward_thread == ForwardThread::Yes)
|
||||
{
|
||||
Relation::Thread(Thread::reply(event_id.clone(), original_message.event_id.clone()))
|
||||
Relation::Thread(Thread::plain(event_id.clone(), original_message.event_id.clone()))
|
||||
} else {
|
||||
Relation::Reply {
|
||||
in_reply_to: InReplyTo { event_id: original_message.event_id.clone() },
|
||||
@ -217,17 +216,16 @@ impl RoomMessageEventContent {
|
||||
/// If `message` is a text, an emote or a notice message, and this is a reply in the thread, it
|
||||
/// is modified to include the rich reply fallback.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
pub fn for_thread(
|
||||
message: MessageType,
|
||||
previous_message: &OriginalRoomMessageEvent,
|
||||
is_reply: ReplyInThread,
|
||||
is_reply: ReplyWithinThread,
|
||||
) -> Self {
|
||||
let make_reply = |body, formatted: Option<FormattedBody>| {
|
||||
reply::plain_and_formatted_reply_body(body, formatted.map(|f| f.body), previous_message)
|
||||
};
|
||||
|
||||
let msgtype = if is_reply == ReplyInThread::Yes {
|
||||
let msgtype = if is_reply == ReplyWithinThread::Yes {
|
||||
// If this is a real reply, add the rich reply fallback.
|
||||
match message {
|
||||
MessageType::Text(TextMessageEventContent { body, formatted, .. }) => {
|
||||
@ -261,7 +259,7 @@ impl RoomMessageEventContent {
|
||||
relates_to: Some(Relation::Thread(Thread {
|
||||
event_id: thread_root,
|
||||
in_reply_to: InReplyTo { event_id: previous_message.event_id.clone() },
|
||||
is_falling_back: is_reply == ReplyInThread::No,
|
||||
is_falling_back: is_reply == ReplyWithinThread::No,
|
||||
})),
|
||||
}
|
||||
}
|
||||
@ -314,15 +312,15 @@ impl RoomMessageEventContent {
|
||||
}
|
||||
|
||||
/// Whether or not to forward a [`Relation::Thread`] when sending a reply.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[allow(clippy::exhaustive_enums)]
|
||||
pub enum ForwardThread {
|
||||
/// The thread relation in the original message is forwarded if it exists.
|
||||
///
|
||||
/// This should be set if your client doesn't support threads (see [MSC3440]).
|
||||
/// This should be set if your client doesn't render threads (see the [info
|
||||
/// box for clients which are acutely aware of threads]).
|
||||
///
|
||||
/// [MSC3440]: https://github.com/matrix-org/matrix-spec-proposals/pull/3440
|
||||
/// [info box for clients which are acutely aware of threads]: https://spec.matrix.org/v1.4/client-server-api/#fallback-for-unthreaded-clients
|
||||
Yes,
|
||||
|
||||
/// Create a reply in the main conversation even if the original message is in a thread.
|
||||
@ -332,20 +330,21 @@ pub enum ForwardThread {
|
||||
}
|
||||
|
||||
/// Whether or not the message is a reply inside a thread.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[allow(clippy::exhaustive_enums)]
|
||||
pub enum ReplyInThread {
|
||||
pub enum ReplyWithinThread {
|
||||
/// This is a reply.
|
||||
///
|
||||
/// Create a proper reply _in_ the thread.
|
||||
/// Create a [reply within the thread].
|
||||
///
|
||||
/// [reply within the thread]: https://spec.matrix.org/v1.4/client-server-api/#replies-within-threads
|
||||
Yes,
|
||||
|
||||
/// This is not a reply.
|
||||
///
|
||||
/// Create a regular message in the thread, with a reply fallback, according to [MSC3440].
|
||||
/// Create a regular message in the thread, with a [fallback for unthreaded clients].
|
||||
///
|
||||
/// [MSC3440]: https://github.com/matrix-org/matrix-spec-proposals/pull/3440
|
||||
/// [fallback for unthreaded clients]: https://spec.matrix.org/v1.4/client-server-api/#fallback-for-unthreaded-clients
|
||||
No,
|
||||
}
|
||||
|
||||
@ -520,7 +519,6 @@ pub enum Relation {
|
||||
Replacement(Replacement),
|
||||
|
||||
/// An event that belongs to a thread.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
Thread(Thread),
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -564,7 +562,6 @@ impl Replacement {
|
||||
|
||||
/// The content of a thread relation.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Thread {
|
||||
/// The ID of the root message in the thread.
|
||||
@ -585,7 +582,6 @@ pub struct Thread {
|
||||
pub is_falling_back: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
impl Thread {
|
||||
/// Convenience method to create a regular `Thread` with the given event ID and latest
|
||||
/// message-like event ID.
|
||||
|
@ -4,10 +4,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use super::Replacement;
|
||||
#[cfg(feature = "unstable-msc2676")]
|
||||
use super::RoomMessageEventContent;
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
use super::Thread;
|
||||
use super::{InReplyTo, Relation};
|
||||
#[cfg(any(feature = "unstable-msc2676", feature = "unstable-msc3440"))]
|
||||
use super::{InReplyTo, Relation, Thread};
|
||||
use crate::OwnedEventId;
|
||||
|
||||
impl<'de> Deserialize<'de> for Relation {
|
||||
@ -17,7 +14,6 @@ impl<'de> Deserialize<'de> for Relation {
|
||||
{
|
||||
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
|
||||
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
if let Some(
|
||||
RelationJsonRepr::ThreadStable(ThreadStableJsonRepr { event_id, is_falling_back })
|
||||
| RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr { event_id, is_falling_back }),
|
||||
@ -44,7 +40,6 @@ impl<'de> Deserialize<'de> for Relation {
|
||||
// FIXME: Maybe we should log this, though at this point we don't even have
|
||||
// access to the rel_type of the unknown relation.
|
||||
RelationJsonRepr::Unknown => Relation::_Custom,
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
RelationJsonRepr::ThreadStable(_) | RelationJsonRepr::ThreadUnstable(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
@ -80,11 +75,10 @@ impl Serialize for Relation {
|
||||
new_content: Some(new_content.clone()),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
Relation::Thread(Thread { event_id, in_reply_to, is_falling_back }) => {
|
||||
EventWithRelatesToJsonRepr::new(RelatesToJsonRepr {
|
||||
in_reply_to: Some(in_reply_to.clone()),
|
||||
relation: Some(RelationJsonRepr::ThreadUnstable(ThreadUnstableJsonRepr {
|
||||
relation: Some(RelationJsonRepr::ThreadStable(ThreadStableJsonRepr {
|
||||
event_id: event_id.clone(),
|
||||
is_falling_back: *is_falling_back,
|
||||
})),
|
||||
@ -144,13 +138,11 @@ enum RelationJsonRepr {
|
||||
#[serde(rename = "m.replace")]
|
||||
Replacement(ReplacementJsonRepr),
|
||||
|
||||
/// An event that belongs to a thread, with unstable names.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
/// An event that belongs to a thread, with stable names.
|
||||
#[serde(rename = "m.thread")]
|
||||
ThreadStable(ThreadStableJsonRepr),
|
||||
|
||||
/// An event that belongs to a thread, with unstable names.
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
#[serde(rename = "io.element.thread")]
|
||||
ThreadUnstable(ThreadUnstableJsonRepr),
|
||||
|
||||
@ -170,7 +162,6 @@ struct ReplacementJsonRepr {
|
||||
|
||||
/// A thread relation without the reply fallback, with stable names.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
struct ThreadStableJsonRepr {
|
||||
/// The ID of the root message in the thread.
|
||||
event_id: OwnedEventId,
|
||||
@ -183,7 +174,6 @@ struct ThreadStableJsonRepr {
|
||||
|
||||
/// A thread relation without the reply fallback, with unstable names.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
struct ThreadUnstableJsonRepr {
|
||||
/// The ID of the root message in the thread.
|
||||
event_id: OwnedEventId,
|
||||
|
@ -149,7 +149,6 @@ fn replacement_deserialize() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
fn thread_plain_serialize() {
|
||||
use ruma_common::events::room::message::Thread;
|
||||
|
||||
@ -172,12 +171,12 @@ fn thread_plain_serialize() {
|
||||
"msgtype": "m.text",
|
||||
"body": "<text msg>",
|
||||
"m.relates_to": {
|
||||
"rel_type": "io.element.thread",
|
||||
"rel_type": "m.thread",
|
||||
"event_id": "$1598361704261elfgc",
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$latesteventid",
|
||||
},
|
||||
"io.element.show_reply": true,
|
||||
"is_falling_back": true,
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -190,19 +189,18 @@ fn thread_plain_serialize() {
|
||||
"body": "<text msg>",
|
||||
"org.matrix.msc1767.text": "<text msg>",
|
||||
"m.relates_to": {
|
||||
"rel_type": "io.element.thread",
|
||||
"rel_type": "m.thread",
|
||||
"event_id": "$1598361704261elfgc",
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$latesteventid",
|
||||
},
|
||||
"io.element.show_reply": true,
|
||||
"is_falling_back": true,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
fn thread_reply_serialize() {
|
||||
use ruma_common::events::room::message::Thread;
|
||||
|
||||
@ -225,7 +223,7 @@ fn thread_reply_serialize() {
|
||||
"msgtype": "m.text",
|
||||
"body": "<text msg>",
|
||||
"m.relates_to": {
|
||||
"rel_type": "io.element.thread",
|
||||
"rel_type": "m.thread",
|
||||
"event_id": "$1598361704261elfgc",
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$repliedtoeventid",
|
||||
@ -242,7 +240,7 @@ fn thread_reply_serialize() {
|
||||
"body": "<text msg>",
|
||||
"org.matrix.msc1767.text": "<text msg>",
|
||||
"m.relates_to": {
|
||||
"rel_type": "io.element.thread",
|
||||
"rel_type": "m.thread",
|
||||
"event_id": "$1598361704261elfgc",
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$repliedtoeventid",
|
||||
@ -253,7 +251,6 @@ fn thread_reply_serialize() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
fn thread_stable_deserialize() {
|
||||
let json = json!({
|
||||
"msgtype": "m.text",
|
||||
@ -281,7 +278,6 @@ fn thread_stable_deserialize() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-msc3440")]
|
||||
fn thread_unstable_deserialize() {
|
||||
let json = json!({
|
||||
"msgtype": "m.text",
|
||||
|
@ -141,7 +141,6 @@ unstable-msc2967 = ["ruma-client-api?/unstable-msc2967"]
|
||||
unstable-msc3245 = ["ruma-common/unstable-msc3245"]
|
||||
unstable-msc3246 = ["ruma-common/unstable-msc3246"]
|
||||
unstable-msc3381 = ["ruma-common/unstable-msc3381"]
|
||||
unstable-msc3440 = ["ruma-common/unstable-msc3440"]
|
||||
unstable-msc3488 = [
|
||||
"ruma-client-api?/unstable-msc3488",
|
||||
"ruma-common/unstable-msc3488",
|
||||
@ -179,7 +178,6 @@ __ci = [
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3246",
|
||||
"unstable-msc3381",
|
||||
"unstable-msc3440",
|
||||
"unstable-msc3488",
|
||||
"unstable-msc3551",
|
||||
"unstable-msc3552",
|
||||
|
Loading…
x
Reference in New Issue
Block a user