mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
core: commands to debug subscribed SMP queues (#4253)
* core: commands to debug subscribed SMP queues * getLastRcvMsgInfo * DESC * update * update commands, ui compiles * update * ios: fix type * swap * simplexmq * group member queue info * copy button in the alert
This commit is contained in:
committed by
GitHub
parent
7b5b747b19
commit
5ac521f6d8
@@ -1379,6 +1379,11 @@ processChatCommand' vr = \case
|
||||
forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||
connectionStats <- mapM (withAgent . flip getConnectionServers) (contactConnId ct)
|
||||
pure $ CRContactInfo user ct connectionStats (fmap fromLocalProfile incognitoProfile)
|
||||
APIContactQueueInfo contactId -> withUser $ \user -> do
|
||||
ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId
|
||||
case activeConn of
|
||||
Just conn -> getConnQueueInfo user conn
|
||||
Nothing -> throwChatError $ CEContactNotActive ct
|
||||
APIGroupInfo gId -> withUser $ \user -> do
|
||||
(g, s) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> liftIO (getGroupSummary db user gId)
|
||||
pure $ CRGroupInfo user g s
|
||||
@@ -1386,6 +1391,11 @@ processChatCommand' vr = \case
|
||||
(g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
|
||||
connectionStats <- mapM (withAgent . flip getConnectionServers) (memberConnId m)
|
||||
pure $ CRGroupMemberInfo user g m connectionStats
|
||||
APIGroupMemberQueueInfo gId gMemberId -> withUser $ \user -> do
|
||||
GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user gId gMemberId
|
||||
case activeConn of
|
||||
Just conn -> getConnQueueInfo user conn
|
||||
Nothing -> throwChatError CEGroupMemberNotActive
|
||||
APISwitchContact contactId -> withUser $ \user -> do
|
||||
ct <- withStore $ \db -> getContact db vr user contactId
|
||||
case contactConnId ct of
|
||||
@@ -1497,6 +1507,8 @@ processChatCommand' vr = \case
|
||||
groupId <- withStore $ \db -> getGroupIdByName db user gName
|
||||
processChatCommand $ APIGroupInfo groupId
|
||||
GroupMemberInfo gName mName -> withMemberName gName mName APIGroupMemberInfo
|
||||
ContactQueueInfo cName -> withContactName cName APIContactQueueInfo
|
||||
GroupMemberQueueInfo gName mName -> withMemberName gName mName APIGroupMemberQueueInfo
|
||||
SwitchContact cName -> withContactName cName APISwitchContact
|
||||
SwitchGroupMember gName mName -> withMemberName gName mName APISwitchGroupMember
|
||||
AbortSwitchContact cName -> withContactName cName APIAbortSwitchContact
|
||||
@@ -2795,6 +2807,9 @@ processChatCommand' vr = \case
|
||||
pure CIFile {fileId, fileName = takeFileName filePath, fileSize, fileSource = Just cf, fileStatus = CIFSSndStored, fileProtocol = FPLocal}
|
||||
let ci = mkChatItem cd ciId content ciFile_ Nothing Nothing itemForwarded Nothing False createdAt Nothing createdAt
|
||||
pure . CRNewChatItem user $ AChatItem SCTLocal SMDSnd (LocalChat nf) ci
|
||||
getConnQueueInfo user Connection {connId, agentConnId = AgentConnId acId} = do
|
||||
msgInfo <- withStore' (`getLastRcvMsgInfo` connId)
|
||||
CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId)
|
||||
|
||||
contactCITimed :: Contact -> CM (Maybe CITimed)
|
||||
contactCITimed ct = sndContactCITimed False ct Nothing
|
||||
@@ -7361,6 +7376,10 @@ chatCommandP =
|
||||
("/info #" <|> "/i #") *> (GroupMemberInfo <$> displayName <* A.space <* char_ '@' <*> displayName),
|
||||
("/info #" <|> "/i #") *> (ShowGroupInfo <$> displayName),
|
||||
("/info " <|> "/i ") *> char_ '@' *> (ContactInfo <$> displayName),
|
||||
"/_queue info #" *> (APIGroupMemberQueueInfo <$> A.decimal <* A.space <*> A.decimal),
|
||||
"/_queue info @" *> (APIContactQueueInfo <$> A.decimal),
|
||||
("/queue info #" <|> "/qi #") *> (GroupMemberQueueInfo <$> displayName <* A.space <* char_ '@' <*> displayName),
|
||||
("/queue info " <|> "/qi ") *> char_ '@' *> (ContactQueueInfo <$> displayName),
|
||||
"/_switch #" *> (APISwitchGroupMember <$> A.decimal <* A.space <*> A.decimal),
|
||||
"/_switch @" *> (APISwitchContact <$> A.decimal),
|
||||
"/_abort switch #" *> (APIAbortSwitchGroupMember <$> A.decimal <* A.space <*> A.decimal),
|
||||
|
||||
@@ -83,6 +83,7 @@ import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus)
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, NtfServer, ProtoServerWithAuth, ProtocolTypeI, QueueId, SMPMsgMeta (..), SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServer, XFTPServerWithAuth, userProtocol)
|
||||
import Simplex.Messaging.Server.QueueStore.QueueInfo
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import Simplex.Messaging.Transport (TLS, simplexMQVersion)
|
||||
import Simplex.Messaging.Transport.Client (TransportHost)
|
||||
@@ -357,6 +358,8 @@ data ChatCommand
|
||||
| APIContactInfo ContactId
|
||||
| APIGroupInfo GroupId
|
||||
| APIGroupMemberInfo GroupId GroupMemberId
|
||||
| APIContactQueueInfo ContactId
|
||||
| APIGroupMemberQueueInfo GroupId GroupMemberId
|
||||
| APISwitchContact ContactId
|
||||
| APISwitchGroupMember GroupId GroupMemberId
|
||||
| APIAbortSwitchContact ContactId
|
||||
@@ -375,6 +378,8 @@ data ChatCommand
|
||||
| ContactInfo ContactName
|
||||
| ShowGroupInfo GroupName
|
||||
| GroupMemberInfo GroupName ContactName
|
||||
| ContactQueueInfo ContactName
|
||||
| GroupMemberQueueInfo GroupName ContactName
|
||||
| SwitchContact ContactName
|
||||
| SwitchGroupMember GroupName ContactName
|
||||
| AbortSwitchContact ContactName
|
||||
@@ -569,6 +574,7 @@ data ChatResponse
|
||||
| CRContactInfo {user :: User, contact :: Contact, connectionStats_ :: Maybe ConnectionStats, customUserProfile :: Maybe Profile}
|
||||
| CRGroupInfo {user :: User, groupInfo :: GroupInfo, groupSummary :: GroupSummary}
|
||||
| CRGroupMemberInfo {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats_ :: Maybe ConnectionStats}
|
||||
| CRQueueInfo {user :: User, rcvMsgInfo :: Maybe RcvMsgInfo, queueInfo :: QueueInfo}
|
||||
| CRContactSwitchStarted {user :: User, contact :: Contact, connectionStats :: ConnectionStats}
|
||||
| CRGroupMemberSwitchStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats}
|
||||
| CRContactSwitchAborted {user :: User, contact :: Contact, connectionStats :: ConnectionStats}
|
||||
|
||||
@@ -962,6 +962,15 @@ data RcvMsgDelivery = RcvMsgDelivery
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data RcvMsgInfo = RcvMsgInfo
|
||||
{ msgId :: Int64,
|
||||
msgDeliveryId :: Int64,
|
||||
msgDeliveryStatus :: Text,
|
||||
agentMsgId :: AgentMsgId,
|
||||
agentMsgMeta :: Text
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data MsgMetaJSON = MsgMetaJSON
|
||||
{ integrity :: Text,
|
||||
rcvId :: Int64,
|
||||
@@ -1332,3 +1341,5 @@ $(JQ.deriveJSON defaultJSON ''MsgMetaJSON)
|
||||
|
||||
msgMetaJson :: MsgMeta -> Text
|
||||
msgMetaJson = decodeLatin1 . LB.toStrict . J.encode . msgMetaToJson
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''RcvMsgInfo)
|
||||
|
||||
@@ -23,6 +23,7 @@ module Simplex.Chat.Store.Messages
|
||||
createNewSndMessage,
|
||||
createSndMsgDelivery,
|
||||
createNewMessageAndRcvMsgDelivery,
|
||||
getLastRcvMsgInfo,
|
||||
createNewRcvMessage,
|
||||
updateSndMsgDeliveryStatus,
|
||||
createPendingGroupMessage,
|
||||
@@ -226,6 +227,23 @@ createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMs
|
||||
(msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, snd $ broker agentMsgMeta, currentTs, currentTs, MDSRcvAgent)
|
||||
pure msg
|
||||
|
||||
getLastRcvMsgInfo :: DB.Connection -> Int64 -> IO (Maybe RcvMsgInfo)
|
||||
getLastRcvMsgInfo db connId =
|
||||
maybeFirstRow rcvMsgInfo $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT message_id, msg_delivery_id, delivery_status, agent_msg_id, agent_msg_meta
|
||||
FROM msg_deliveries
|
||||
WHERE connection_id = ? AND delivery_status IN (?, ?)
|
||||
ORDER BY created_at DESC, msg_delivery_id DESC
|
||||
LIMIT 1
|
||||
|]
|
||||
(connId, MDSRcvAgent, MDSRcvAcknowledged)
|
||||
where
|
||||
rcvMsgInfo (msgId, msgDeliveryId, msgDeliveryStatus, agentMsgId, agentMsgMeta) =
|
||||
RcvMsgInfo {msgId, msgDeliveryId, msgDeliveryStatus, agentMsgId, agentMsgMeta}
|
||||
|
||||
createNewRcvMessage :: forall e. MsgEncodingI e => DB.Connection -> ConnOrGroupId -> NewRcvMessage e -> Maybe SharedMsgId -> Maybe GroupMemberId -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage
|
||||
createNewRcvMessage db connOrGroupId NewRcvMessage {chatMsgEvent, msgBody} sharedMsgId_ authorMember forwardedByMember =
|
||||
case connOrGroupId of
|
||||
|
||||
+26
-20
@@ -13,7 +13,6 @@ module Simplex.Chat.View where
|
||||
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.TH as JQ
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.Char (isSpace, toUpper)
|
||||
@@ -65,7 +64,7 @@ import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, ProtoServerWithAuth, ProtocolServer (..), ProtocolTypeI, SProtocolType (..))
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
||||
import Simplex.Messaging.Util (bshow, tshow)
|
||||
import Simplex.Messaging.Util (tshow)
|
||||
import Simplex.Messaging.Version hiding (version)
|
||||
import Simplex.RemoteControl.Types (RCCtrlAddress (..))
|
||||
import System.Console.ANSI.Types
|
||||
@@ -90,10 +89,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRChatRunning -> ["chat is running"]
|
||||
CRChatStopped -> ["chat stopped"]
|
||||
CRChatSuspended -> ["chat suspended"]
|
||||
CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [plain . bshow $ J.encode chats]
|
||||
CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [viewJSON chats]
|
||||
CRChats chats -> viewChats ts tz chats
|
||||
CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [plain . bshow $ J.encode chat]
|
||||
CRApiParsedMarkdown ft -> [plain . bshow $ J.encode ft]
|
||||
CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat]
|
||||
CRApiParsedMarkdown ft -> [viewJSON ft]
|
||||
CRUserProtoServers u userServers -> ttyUser u $ viewUserServers userServers testView
|
||||
CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure
|
||||
CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl
|
||||
@@ -101,6 +100,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRContactInfo u ct cStats customUserProfile -> ttyUser u $ viewContactInfo ct cStats customUserProfile
|
||||
CRGroupInfo u g s -> ttyUser u $ viewGroupInfo g s
|
||||
CRGroupMemberInfo u g m cStats -> ttyUser u $ viewGroupMemberInfo g m cStats
|
||||
CRQueueInfo _ msgInfo qInfo ->
|
||||
[ "last received msg: " <> maybe "none" viewJSON msgInfo,
|
||||
"server queue info: " <> viewJSON qInfo
|
||||
]
|
||||
CRContactSwitchStarted {} -> ["switch started"]
|
||||
CRGroupMemberSwitchStarted {} -> ["switch started"]
|
||||
CRContactSwitchAborted {} -> ["switch aborted"]
|
||||
@@ -220,7 +223,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRSndFileError u (Just ci) _ e -> ttyUser u $ uploadingFile "error" ci <> [plain e]
|
||||
CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} ->
|
||||
ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft]
|
||||
CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [plain . LB.toStrict $ J.encode j]) info_
|
||||
CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [viewJSON j]) info_
|
||||
CRContactConnecting u _ -> ttyUser u []
|
||||
CRContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView
|
||||
CRContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"]
|
||||
@@ -322,7 +325,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
]
|
||||
CRRemoteFileStored rhId (CryptoFile filePath cfArgs_) ->
|
||||
[plain $ "file " <> filePath <> " stored on remote host " <> show rhId]
|
||||
<> maybe [] ((: []) . plain . cryptoFileArgsStr testView) cfArgs_
|
||||
<> maybe [] ((: []) . cryptoFileArgsStr testView) cfArgs_
|
||||
CRRemoteCtrlList cs -> viewRemoteCtrls cs
|
||||
CRRemoteCtrlFound {remoteCtrl = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName}, ctrlAppInfo_, appVersion, compatible} ->
|
||||
[ ("remote controller " <> sShow remoteCtrlId <> " found: ")
|
||||
@@ -354,8 +357,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
in ("Chat queries" : map viewQuery chatQueries) <> [""] <> ("Agent queries" : map viewQuery agentQueries)
|
||||
CRDebugLocks {chatLockName, chatEntityLocks, agentLocks} ->
|
||||
[ maybe "no chat lock" (("chat lock: " <>) . plain) chatLockName,
|
||||
plain $ "chat entity locks: " <> LB.unpack (J.encode chatEntityLocks),
|
||||
plain $ "agent locks: " <> LB.unpack (J.encode agentLocks)
|
||||
"chat entity locks: " <> viewJSON chatEntityLocks,
|
||||
"agent locks: " <> viewJSON agentLocks
|
||||
]
|
||||
CRAgentStats stats -> map (plain . intercalate ",") stats
|
||||
CRAgentSubs {activeSubs, pendingSubs, removedSubs} ->
|
||||
@@ -370,12 +373,12 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
("active subscriptions:" : map sShow activeSubscriptions)
|
||||
<> ("pending subscriptions: " : map sShow pendingSubscriptions)
|
||||
<> ("removed subscriptions: " : map sShow removedSubscriptions)
|
||||
CRAgentWorkersSummary {agentWorkersSummary} -> ["agent workers summary: " <> plain (LB.unpack $ J.encode agentWorkersSummary)]
|
||||
CRAgentWorkersSummary {agentWorkersSummary} -> ["agent workers summary: " <> viewJSON agentWorkersSummary]
|
||||
CRAgentWorkersDetails {agentWorkersDetails} ->
|
||||
[ "agent workers details:",
|
||||
plain . LB.unpack $ J.encode agentWorkersDetails -- this would be huge, but copypastable when has its own line
|
||||
viewJSON agentWorkersDetails -- this would be huge, but copypastable when has its own line
|
||||
]
|
||||
CRAgentMsgCounts {msgCounts} -> ["received messages (total, duplicates):", plain . LB.unpack $ J.encode msgCounts]
|
||||
CRAgentMsgCounts {msgCounts} -> ["received messages (total, duplicates):", viewJSON msgCounts]
|
||||
CRAgentQueuesInfo {agentQueuesInfo} ->
|
||||
[ "agent queues info:",
|
||||
plain . LB.unpack $ J.encode agentQueuesInfo
|
||||
@@ -397,7 +400,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRChatError u e -> ttyUser' u $ viewChatError False logLevel testView e
|
||||
CRChatErrors u errs -> ttyUser' u $ concatMap (viewChatError False logLevel testView) errs
|
||||
CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)]
|
||||
CRAppSettings as -> ["app settings: " <> plain (LB.unpack $ J.encode as)]
|
||||
CRAppSettings as -> ["app settings: " <> viewJSON as]
|
||||
CRTimedAction _ _ -> []
|
||||
CRCustomChatResponse u r -> ttyUser' u $ [plain r]
|
||||
where
|
||||
@@ -1241,10 +1244,10 @@ viewGroupInfo GroupInfo {groupId, uiThemes, customData} s =
|
||||
<> viewCustomData customData
|
||||
|
||||
viewUITheme :: Maybe UIThemeEntityOverrides -> [StyledString]
|
||||
viewUITheme = maybe [] (\uiThemes -> ["UI themes: " <> plain (LB.toStrict $ J.encode uiThemes)])
|
||||
viewUITheme = maybe [] (\uiThemes -> ["UI themes: " <> viewJSON uiThemes])
|
||||
|
||||
viewCustomData :: Maybe CustomData -> [StyledString]
|
||||
viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> plain (LB.toStrict . J.encode $ J.Object v)])
|
||||
viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> viewJSON (J.Object v)])
|
||||
|
||||
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString]
|
||||
viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProfile = LocalProfile {localAlias, contactLink}, activeConn} stats =
|
||||
@@ -1686,7 +1689,7 @@ receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> AChatIt
|
||||
receivingFile_' hu testView status (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir}) =
|
||||
[plain status <> " receiving " <> fileTransferStr fileId fileName <> fileFrom chat chatDir] <> cfArgsStr cfArgs_ <> getRemoteFileStr
|
||||
where
|
||||
cfArgsStr (Just cfArgs) = [plain (cryptoFileArgsStr testView cfArgs) | status == "completed"]
|
||||
cfArgsStr (Just cfArgs) = [cryptoFileArgsStr testView cfArgs | status == "completed"]
|
||||
cfArgsStr _ = []
|
||||
getRemoteFileStr = case hu of
|
||||
(Just rhId, Just User {userId})
|
||||
@@ -1707,10 +1710,10 @@ viewLocalFile to CIFile {fileId, fileSource} ts tz = case fileSource of
|
||||
Just (CryptoFile fPath _) -> sentWithTime_ ts tz [to <> fileTransferStr fileId fPath]
|
||||
_ -> const []
|
||||
|
||||
cryptoFileArgsStr :: Bool -> CryptoFileArgs -> ByteString
|
||||
cryptoFileArgsStr :: Bool -> CryptoFileArgs -> StyledString
|
||||
cryptoFileArgsStr testView cfArgs@(CFArgs key nonce)
|
||||
| testView = LB.toStrict $ J.encode cfArgs
|
||||
| otherwise = "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce
|
||||
| testView = viewJSON cfArgs
|
||||
| otherwise = plain $ "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce
|
||||
|
||||
fileFrom :: ChatInfo c -> CIDirection c d -> StyledString
|
||||
fileFrom (DirectChat ct) CIDirectRcv = " from " <> ttyContact' ct
|
||||
@@ -1828,7 +1831,7 @@ viewCallAnswer ct WebRTCSession {rtcSession = answer, rtcIceCandidates = iceCand
|
||||
[ ttyContact' ct <> " continued the WebRTC call",
|
||||
"To connect, please paste the data below in your browser window you opened earlier and click Connect button",
|
||||
"",
|
||||
plain . LB.toStrict . J.encode $ WCCallAnswer {answer, iceCandidates}
|
||||
viewJSON WCCallAnswer {answer, iceCandidates}
|
||||
]
|
||||
|
||||
callMediaStr :: CallType -> StyledString
|
||||
@@ -2086,6 +2089,9 @@ viewConnectionEntityInactive entity inactive
|
||||
| inactive = ["[" <> connEntityLabel entity <> "] connection is marked as inactive"]
|
||||
| otherwise = ["[" <> connEntityLabel entity <> "] inactive connection is marked as active"]
|
||||
|
||||
viewJSON :: J.ToJSON a => a -> StyledString
|
||||
viewJSON = plain . LB.toStrict . J.encode
|
||||
|
||||
connEntityLabel :: ConnectionEntity -> StyledString
|
||||
connEntityLabel = \case
|
||||
RcvDirectMsgConnection _ (Just Contact {localDisplayName = c}) -> plain c
|
||||
|
||||
Reference in New Issue
Block a user