Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2577520
matrix-room.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
matrix-room.cpp
View Options
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include
<kazv-defs.hpp>
#include
<lager/setter.hpp>
#include
<nlohmann/json.hpp>
#include
<event.hpp>
#include
<QFile>
#include
<QMimeDatabase>
#include
"kazv-log.hpp"
#include
"matrix-room.hpp"
#include
"matrix-room-timeline.hpp"
#include
"matrix-room-pinned-events-timeline.hpp"
#include
"matrix-room-member.hpp"
#include
"matrix-room-member-list-model.hpp"
#include
"matrix-promise.hpp"
#include
"matrix-event.hpp"
#include
"kazv-markdown.hpp"
#include
"qt-json.hpp"
#include
"qfunctionutils.hpp"
#include
"helper.hpp"
using
namespace
Qt
::
Literals
::
StringLiterals
;
using
namespace
Kazv
;
static
const
int
typingDebounceMs
=
500
;
static
const
int
saveDraftDebounceMs
=
5000
;
static
const
int
typingTimeoutMs
=
1000
;
MatrixRoom
::
MatrixRoom
(
Kazv
::
Room
room
,
lager
::
reader
<
std
::
string
>
selfUserId
,
lager
::
reader
<
Kazv
::
Event
>
userGivenNicknameEvent
,
QObject
*
parent
)
:
QObject
(
parent
)
,
m_room
(
room
)
,
m_userGivenNicknameEvent
(
userGivenNicknameEvent
)
,
m_selfUserId
(
selfUserId
)
,
m_memberNames
(
m_room
.
members
())
,
m_powerLevels
(
m_room
.
powerLevels
())
,
LAGER_QT
(
roomId
)(
m_room
.
roomId
().
xform
(
strToQt
))
,
LAGER_QT
(
name
)(
m_room
.
nameOpt
()[
lager
::
lenses
::
or_default
].
xform
(
strToQt
))
,
LAGER_QT
(
topic
)(
m_room
.
topic
().
xform
(
strToQt
))
,
LAGER_QT
(
heroNames
)(
m_room
.
heroDisplayNames
().
xform
(
strListToQt
))
,
LAGER_QT
(
heroEvents
)(
m_room
.
heroMemberEvents
()
.
map
([](
const
auto
&
events
)
{
auto
res
=
QList
<
QVariant
>
{};
res
.
reserve
(
events
.
size
());
std
::
transform
(
events
.
begin
(),
events
.
end
(),
std
::
back_inserter
(
res
),
[](
const
auto
&
event
)
->
QVariant
{
return
event
.
originalJson
().
get
().
template
get
<
QJsonObject
>
();
});
return
QVariant
(
res
);
}))
,
LAGER_QT
(
avatarMxcUri
)(
m_room
.
avatarMxcUri
().
xform
(
strToQt
))
,
LAGER_QT
(
roomOrHeroAvatarMxcUri
)(
lager
::
with
(
m_room
.
heroMemberEvents
(),
m_room
.
avatarMxcUri
())
.
map
([](
const
auto
&
heroes
,
const
auto
&
roomAvatar
)
{
if
(
!
roomAvatar
.
empty
())
{
return
roomAvatar
;
}
if
(
heroes
.
size
()
==
1
)
{
return
zug
::
reduce
(
roomMemberToAvatar
(
zug
::
last
),
std
::
string
(),
heroes
);
}
return
std
::
string
();
})
.
xform
(
strToQt
))
,
LAGER_QT
(
localDraft
)(
m_room
.
localDraft
().
xform
(
strToQt
))
,
LAGER_QT
(
encrypted
)(
m_room
.
encrypted
())
,
LAGER_QT
(
memberNames
)(
m_memberNames
.
xform
(
strListToQt
))
,
LAGER_QT
(
tagIds
)(
m_room
.
tags
().
map
([](
const
auto
&
tagsMap
)
{
return
zug
::
into
(
QStringList
(),
zug
::
map
([](
const
auto
pair
)
{
return
QString
::
fromStdString
(
pair
.
first
);
}),
tagsMap
);
}))
,
LAGER_QT
(
membership
)(
m_room
.
membership
().
map
([](
const
auto
&
membership
)
{
return
static_cast
<
Membership
>
(
membership
);
}))
,
LAGER_QT
(
unreadNotificationCount
)(
m_room
.
unreadNotificationEventIds
().
map
([](
const
auto
&
cont
)
->
int
{
return
cont
.
size
();
}))
,
m_setTypingThrottled
(
QFunctionUtils
::
Throttle
([
self
=
QPointer
<
MatrixRoom
>
(
this
)]()
{
if
(
self
)
{
self
->
setTypingImpl
();
}
},
typingDebounceMs
))
,
m_updateLocalDraftDebounced
(
QFunctionUtils
::
Debounce
([
self
=
QPointer
<
MatrixRoom
>
(
this
)]()
{
if
(
self
)
{
self
->
updateLocalDraftNow
();
}
},
saveDraftDebounceMs
))
,
m_internalLocalDraft
(
std
::
nullopt
)
{
lager
::
watch
(
m_powerLevels
,
[
this
](
auto
&&
)
{
powerLevelsChanged
();
});
}
MatrixRoom
::~
MatrixRoom
()
{
updateLocalDraftNow
();
}
MatrixRoomTimeline
*
MatrixRoom
::
timeline
()
const
{
return
new
MatrixRoomTimeline
(
m_room
);
}
MatrixRoomPinnedEventsTimeline
*
MatrixRoom
::
pinnedEventsTimeline
()
const
{
return
new
MatrixRoomPinnedEventsTimeline
(
m_room
);
}
MatrixPromise
*
MatrixRoom
::
pinEvents
(
const
QStringList
&
eventIds
)
const
{
return
new
MatrixPromise
(
m_room
.
pinEvents
(
intoImmer
(
immer
::
flex_vector
<
std
::
string
>
(),
qStringToStd
,
eventIds
)));
}
MatrixPromise
*
MatrixRoom
::
unpinEvents
(
const
QStringList
&
eventIds
)
const
{
return
new
MatrixPromise
(
m_room
.
unpinEvents
(
intoImmer
(
immer
::
flex_vector
<
std
::
string
>
(),
qStringToStd
,
eventIds
)));
}
static
void
maybeAddRelations
(
nlohmann
::
json
&
msg
,
const
QString
&
relType
,
const
QString
&
relatedTo
)
{
if
(
relatedTo
.
isEmpty
())
{
return
;
}
if
(
relType
==
u
"m.in_reply_to"
_s
)
{
msg
[
"content"
][
"m.relates_to"
]
=
nlohmann
::
json
{
{
"m.in_reply_to"
,
{
{
"event_id"
,
relatedTo
},
}},
};
}
else
{
msg
[
"content"
][
"m.relates_to"
]
=
nlohmann
::
json
{
{
"rel_type"
,
relType
},
{
"event_id"
,
relatedTo
},
};
}
}
static
const
std
::
string
HTML_FORMAT
=
"org.matrix.custom.html"
;
nlohmann
::
json
makeTextMessageJson
(
const
QString
&
text
,
const
QString
&
relType
,
const
QString
&
relatedTo
,
Event
replyToEvent
)
{
auto
msg
=
nlohmann
::
json
{
{
"type"
,
"m.room.message"
},
{
"content"
,
{
{
"msgtype"
,
"m.text"
},
{
"body"
,
text
},
}},
};
std
::
string
replyToBody
;
if
(
relType
==
u
"m.in_reply_to"
_s
&&
!
relatedTo
.
isEmpty
())
{
auto
replyToContent
=
replyToEvent
.
content
().
get
();
if
(
replyToContent
.
contains
(
"format"
)
&&
replyToContent
[
"format"
]
==
HTML_FORMAT
&&
replyToContent
.
contains
(
"formatted_body"
)
&&
replyToContent
[
"formatted_body"
].
is_string
())
{
replyToBody
=
replyToContent
[
"formatted_body"
].
template
get
<
std
::
string
>
();
auto
replyPos
=
replyToBody
.
find
(
"</mx-reply>"
);
if
(
replyPos
!=
std
::
string
::
npos
)
{
replyToBody
.
erase
(
0
,
replyPos
+
std
::
string
(
"</mx-reply>"
).
size
());
}
}
else
if
(
replyToContent
.
contains
(
"body"
)
&&
replyToContent
[
"body"
].
is_string
())
{
replyToBody
=
markdownToHtml
(
replyToContent
[
"body"
].
template
get
<
std
::
string
>
()).
html
;
}
if
(
!
replyToBody
.
empty
())
{
replyToBody
=
"<mx-reply><blockquote>"
+
replyToBody
+
"</blockquote></mx-reply>"
;
}
}
auto
richText
=
markdownToHtml
(
text
.
toStdString
());
msg
[
"content"
][
"format"
]
=
HTML_FORMAT
;
msg
[
"content"
][
"formatted_body"
]
=
replyToBody
+
richText
.
html
;
auto
mentions
=
richText
.
mentions
;
if
(
!
replyToEvent
.
sender
().
empty
()
&&
std
::
find
(
mentions
.
begin
(),
mentions
.
end
(),
replyToEvent
.
sender
())
==
mentions
.
end
())
{
mentions
=
std
::
move
(
mentions
).
push_back
(
replyToEvent
.
sender
());
}
if
(
!
mentions
.
empty
())
{
msg
[
"content"
][
"m.mentions"
]
=
{
{
"user_ids"
,
mentions
},
};
}
if
(
relType
==
u
"m.replace"
_s
&&
!
relatedTo
.
isEmpty
())
{
msg
[
"content"
][
"m.new_content"
]
=
msg
[
"content"
];
}
maybeAddRelations
(
msg
,
relType
,
relatedTo
);
return
msg
;
}
void
MatrixRoom::sendMessage
(
const
QJsonObject
&
eventJson
,
const
QString
&
relType
,
const
QString
&
relatedTo
)
const
{
auto
msg
=
nlohmann
::
json
(
eventJson
);
maybeAddRelations
(
msg
,
relType
,
relatedTo
);
m_room
.
sendMessage
(
Event
(
msg
));
}
void
MatrixRoom::sendTextMessage
(
QString
text
,
const
QString
&
relType
,
QString
relatedTo
)
const
{
Event
replyToEvent
=
(
relType
==
u
"m.in_reply_to"
_s
&&
!
relatedTo
.
isEmpty
())
?
m_room
.
message
(
lager
::
make_constant
(
relatedTo
.
toStdString
())).
make
().
get
()
:
Event
();
auto
j
=
makeTextMessageJson
(
text
,
relType
,
relatedTo
,
replyToEvent
);
m_room
.
sendMessage
(
Event
(
j
));
}
void
MatrixRoom::sendMediaFileMessage
(
QString
fileName
,
QString
mimeType
,
qint64
fileSize
,
QString
mxcUri
)
const
{
auto
j
=
makeMediaFileMessageJson
(
fileName
,
mimeType
,
fileSize
,
mxcUri
);
Kazv
::
Event
e
{
j
};
m_room
.
sendMessage
(
e
);
}
void
MatrixRoom::sendEncryptedFileMessage
(
const
QString
&
fileName
,
const
QString
&
mimeType
,
const
qint64
fileSize
,
const
QString
&
mxcUri
,
const
QString
&
key
,
const
QString
&
iv
,
const
QByteArray
&
hash
)
const
{
auto
j
=
makeEncryptedFileMessageJson
(
fileName
,
mimeType
,
fileSize
,
mxcUri
,
key
,
iv
,
hash
);
Kazv
::
Event
e
{
j
};
m_room
.
sendMessage
(
e
);
}
static
nlohmann
::
json
makeReactionJson
(
QString
text
,
QString
relatedTo
)
{
auto
msg
=
nlohmann
::
json
{
{
"type"
,
"m.reaction"
},
{
"content"
,
{
{
"m.relates_to"
,
{
{
"rel_type"
,
"m.annotation"
},
{
"event_id"
,
relatedTo
.
toStdString
()},
{
"key"
,
text
.
toStdString
()},
}},
}},
};
return
msg
;
}
void
MatrixRoom::sendReaction
(
QString
text
,
QString
relatedTo
)
const
{
auto
j
=
makeReactionJson
(
text
,
relatedTo
);
m_room
.
sendMessage
(
Event
(
j
));
}
void
MatrixRoom::resendMessage
(
QString
txnId
)
const
{
m_room
.
resendMessage
(
txnId
.
toStdString
());
}
MatrixPromise
*
MatrixRoom::sendStateEvent
(
const
QJsonObject
&
eventJson
)
const
{
return
new
MatrixPromise
(
m_room
.
sendStateEvent
(
Event
(
json
(
eventJson
))));
}
nlohmann
::
json
MatrixRoom::makeMediaFileMessageJson
(
QString
fileName
,
QString
mimeType
,
qint64
fileSize
,
QString
mxcUri
)
const
{
static
auto
available_msgtype
=
std
::
array
<
std
::
string
,
3
>
{
"m.audio"
,
"m.video"
,
"m.image"
};
auto
try_msgtype
=
std
::
find
(
available_msgtype
.
begin
(),
available_msgtype
.
end
(),
u
"m."
_s
.
append
(
mimeType
.
split
(
u
'/'
)[
0
]).
toStdString
());
std
::
string
msgtype
;
if
(
try_msgtype
==
available_msgtype
.
end
())
{
msgtype
=
"m.file"
;
}
else
{
msgtype
=
*
try_msgtype
;
}
return
nlohmann
::
json
{
{
"type"
,
"m.room.message"
},
{
"content"
,
{
{
"msgtype"
,
msgtype
},
{
"body"
,
fileName
.
toStdString
()},
{
"url"
,
mxcUri
.
toStdString
()},
{
"info"
,
{
{
"size"
,
fileSize
},
{
"mimetype"
,
mimeType
.
toStdString
()}
}}
}}
};
}
nlohmann
::
json
MatrixRoom::makeEncryptedFileMessageJson
(
const
QString
&
fileName
,
const
QString
&
mimeType
,
const
qint64
fileSize
,
const
QString
&
mxcUri
,
const
QString
&
key
,
const
QString
&
iv
,
const
QByteArray
&
hash
)
const
{
static
auto
available_msgtype
=
std
::
array
<
std
::
string
,
3
>
{
"m.audio"
,
"m.video"
,
"m.image"
};
auto
try_msgtype
=
std
::
find
(
available_msgtype
.
begin
(),
available_msgtype
.
end
(),
u
"m."
_s
.
append
(
mimeType
.
split
(
u
'/'
)[
0
]).
toStdString
());
std
::
string
msgtype
;
if
(
try_msgtype
==
available_msgtype
.
end
())
{
msgtype
=
"m.file"
;
}
else
{
msgtype
=
*
try_msgtype
;
}
return
nlohmann
::
json
{
{
"type"
,
"m.room.message"
},
{
"content"
,
{
{
"msgtype"
,
msgtype
},
{
"body"
,
fileName
.
toStdString
()},
{
"file"
,
{
{
"url"
,
mxcUri
.
toStdString
()},
{
"key"
,
{
{
"kty"
,
"oct"
},
{
"key_ops"
,
nlohmann
::
json
::
array
({
"encrypt"
,
"decrypt"
})},
{
"alg"
,
"A256CTR"
},
{
"k"
,
key
.
toStdString
()},
{
"ext"
,
true
}
}},
{
"iv"
,
iv
.
toStdString
()},
{
"hashes"
,
{
{
"sha256"
,
hash
.
toStdString
()}
}},
{
"v"
,
"v2"
}
}},
{
"info"
,
{
{
"size"
,
fileSize
},
{
"mimetype"
,
mimeType
.
toStdString
()}
}}
}}
};
}
MatrixPromise
*
MatrixRoom::redactEvent
(
QString
eventId
,
QString
reason
)
const
{
qCInfo
(
kazvLog
)
<<
"redactEvent("
<<
eventId
<<
", "
<<
reason
<<
")"
;
return
new
MatrixPromise
(
m_room
.
redactEvent
(
eventId
.
toStdString
(),
reason
.
isEmpty
()
?
std
::
nullopt
:
std
::
optional
<
std
::
string
>
(
reason
.
toStdString
())
));
}
MatrixPromise
*
MatrixRoom::removeLocalEcho
(
QString
txnId
)
const
{
return
new
MatrixPromise
(
m_room
.
removeLocalEcho
(
txnId
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::addOrSetTag
(
QString
tagId
)
const
{
return
new
MatrixPromise
(
m_room
.
addOrSetTag
(
tagId
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::removeTag
(
QString
tagId
)
const
{
return
new
MatrixPromise
(
m_room
.
removeTag
(
tagId
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::paginateBackFrom
(
QString
eventId
)
const
{
return
new
MatrixPromise
(
m_room
.
paginateBackFromEvent
(
eventId
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::leaveRoom
()
const
{
return
new
MatrixPromise
(
m_room
.
leave
());
}
MatrixPromise
*
MatrixRoom::forgetRoom
()
const
{
return
new
MatrixPromise
(
m_room
.
forget
());
}
MatrixRoomMember
*
MatrixRoom::memberAt
(
int
index
)
const
{
return
new
MatrixRoomMember
(
m_room
.
memberEventByCursor
(
m_memberNames
[
index
][
lager
::
lenses
::
or_default
]));
}
MatrixRoomMember
*
MatrixRoom::member
(
QString
userId
)
const
{
return
new
MatrixRoomMember
(
m_room
.
memberEventFor
(
userId
.
toStdString
()));
}
MatrixEvent
*
MatrixRoom::messageById
(
QString
eventId
)
const
{
return
new
MatrixEvent
(
m_room
.
message
(
lager
::
make_constant
(
eventId
.
toStdString
()))
.
map
([](
const
auto
&
e
)
->
std
::
variant
<
Kazv
::
Event
,
Kazv
::
LocalEchoDesc
>
{
return
e
;
}),
m_room
);
}
MatrixRoomMemberListModel
*
MatrixRoom::typingUsers
()
const
{
return
new
MatrixRoomMemberListModel
(
m_room
.
typingMemberEvents
()
.
xform
(
containerMap
(
EventList
{},
zug
::
filter
([
self
=
m_selfUserId
.
make
().
get
()](
const
auto
&
e
)
{
return
e
.
stateKey
()
!=
self
;
})))
);
}
void
MatrixRoom::setTyping
(
bool
typing
)
{
if
(
typing
)
{
m_setTypingThrottled
();
}
else
{
m_room
.
setTyping
(
false
,
std
::
nullopt
);
}
}
void
MatrixRoom::setTypingImpl
()
{
m_room
.
setTyping
(
true
,
typingTimeoutMs
);
}
void
MatrixRoom::setLocalDraft
(
QString
localDraft
)
{
// To avoid heavy computations when updating the local draft again
// and again, we only set our internal state. **Assume only
// one MatrixRoom can deal with the local draft** at the same time,
// it is safe to update it debounced and when we are destructed.
// See also the destructor.
m_internalLocalDraft
=
localDraft
;
m_updateLocalDraftDebounced
();
}
void
MatrixRoom::updateLocalDraftNow
()
{
if
(
m_internalLocalDraft
.
has_value
())
{
m_room
.
setLocalDraft
(
m_internalLocalDraft
.
value
().
toStdString
());
m_internalLocalDraft
=
std
::
nullopt
;
}
}
MatrixRoomMemberListModel
*
MatrixRoom::members
()
const
{
return
new
MatrixRoomMemberListModel
(
m_room
.
joinedMemberEvents
(),
QString
(),
m_userGivenNicknameEvent
);
}
MatrixRoomMemberListModel
*
MatrixRoom::bannedMembers
()
const
{
return
new
MatrixRoomMemberListModel
(
m_room
.
bannedMemberEvents
(),
QString
(),
m_userGivenNicknameEvent
);
}
qint64
MatrixRoom::userPowerLevel
(
const
QString
&
userId
)
const
{
auto
e
=
m_powerLevels
.
get
().
normalizedEvent
().
content
().
get
();
auto
userIdStd
=
userId
.
toStdString
();
return
e
.
at
(
"users"
).
contains
(
userIdStd
)
?
e
.
at
(
"users"
)[
userIdStd
].
template
get
<
Kazv
::
PowerLevel
>
()
:
e
.
at
(
"users_default"
).
template
get
<
Kazv
::
PowerLevel
>
();
}
MatrixPromise
*
MatrixRoom::setUserPowerLevel
(
const
QString
&
userId
,
qint64
powerLevel
)
const
{
auto
next
=
m_powerLevels
.
get
().
setUser
(
userId
.
toStdString
(),
powerLevel
);
return
new
MatrixPromise
(
m_room
.
sendStateEvent
(
next
.
originalEvent
()));
}
MatrixPromise
*
MatrixRoom::unsetUserPowerLevel
(
const
QString
&
userId
)
const
{
auto
next
=
m_powerLevels
.
get
().
setUser
(
userId
.
toStdString
(),
std
::
nullopt
);
return
new
MatrixPromise
(
m_room
.
sendStateEvent
(
next
.
originalEvent
()));
}
MatrixPromise
*
MatrixRoom::getStateEvent
(
const
QString
&
type
,
const
QString
&
stateKey
)
const
{
return
new
MatrixPromise
(
m_room
.
getStateEvent
(
type
.
toStdString
(),
stateKey
.
toStdString
()
));
}
MatrixPromise
*
MatrixRoom::ensureStateEvent
(
const
QString
&
type
,
const
QString
&
stateKey
)
const
{
if
(
m_room
.
stateOpt
({
type
.
toStdString
(),
stateKey
.
toStdString
()})
.
make
()
.
get
()
.
has_value
())
{
return
MatrixPromise
::
createResolved
(
true
,
QJsonObject
());
}
else
{
return
getStateEvent
(
type
,
stateKey
);
}
}
MatrixPromise
*
MatrixRoom::refreshState
()
const
{
return
new
MatrixPromise
(
m_room
.
refreshRoomState
());
}
MatrixPromise
*
MatrixRoom::kickUser
(
const
QString
&
userId
,
const
QString
&
reason
)
const
{
return
new
MatrixPromise
(
m_room
.
kick
(
userId
.
toStdString
(),
reason
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::banUser
(
const
QString
&
userId
,
const
QString
&
reason
)
const
{
return
new
MatrixPromise
(
m_room
.
ban
(
userId
.
toStdString
(),
reason
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::unbanUser
(
const
QString
&
userId
)
const
{
return
new
MatrixPromise
(
m_room
.
unban
(
userId
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::inviteUser
(
const
QString
&
userId
)
const
{
return
new
MatrixPromise
(
m_room
.
invite
(
userId
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::postReadReceipt
(
const
QString
&
eventId
)
const
{
return
new
MatrixPromise
(
m_room
.
postReceipt
(
eventId
.
toStdString
()));
}
MatrixPromise
*
MatrixRoom::setTopic
(
const
QString
&
newTopic
)
const
{
return
new
MatrixPromise
(
m_room
.
setTopic
(
newTopic
.
toStdString
()));
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Tue, Jun 24, 8:46 AM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
234785
Default Alt Text
matrix-room.cpp (16 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline
Log In to Comment