core: incognito connections (#926)

This commit is contained in:
JRoberts
2022-08-18 11:35:31 +04:00
committed by GitHub
parent 404b7093b7
commit 5e67654249
19 changed files with 4408 additions and 460 deletions
+146 -42
View File
@@ -54,7 +54,7 @@ serializeChatResponse = unlines . map unStyle . responseToView False
responseToView :: Bool -> ChatResponse -> [StyledString]
responseToView testView = \case
CRActiveUser User {profile} -> viewUserProfile profile
CRActiveUser User {profile} -> viewUserProfile $ fromLocalProfile profile
CRChatStarted -> ["chat started"]
CRChatRunning -> ["chat is running"]
CRChatStopped -> ["chat stopped"]
@@ -64,8 +64,8 @@ responseToView testView = \case
CRApiParsedMarkdown ft -> [plain . bshow $ J.encode ft]
CRUserSMPServers smpServers -> viewSMPServers smpServers testView
CRNetworkConfig cfg -> viewNetworkConfig cfg
CRContactInfo ct cStats -> viewContactInfo ct cStats
CRGroupMemberInfo g m cStats -> viewGroupMemberInfo g m cStats
CRContactInfo ct cStats customUserProfile -> viewContactInfo ct cStats customUserProfile
CRGroupMemberInfo g m cStats mainProfile -> viewGroupMemberInfo g m cStats mainProfile
CRNewChatItem (AChatItem _ _ chat item) -> viewChatItem chat item False
CRLastMessages chatItems -> concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True) chatItems
CRChatItemStatusUpdated _ -> []
@@ -89,10 +89,10 @@ responseToView testView = \case
CRUserContactLink cReqUri autoAccept autoReply -> connReqContact_ "Your chat address:" cReqUri <> autoAcceptStatus_ autoAccept autoReply
CRUserContactLinkUpdated _ autoAccept autoReply -> autoAcceptStatus_ autoAccept autoReply
CRContactRequestRejected UserContactRequest {localDisplayName = c} -> [ttyContact c <> ": contact request rejected"]
CRGroupCreated g -> viewGroupCreated g
CRGroupCreated g customUserProfile -> viewGroupCreated g customUserProfile testView
CRGroupMembers g -> viewGroupMembers g
CRGroupsList gs -> viewGroupsList gs
CRSentGroupInvitation g c _ -> ["invitation to join the group " <> ttyGroup' g <> " sent to " <> ttyContact' c]
CRSentGroupInvitation g c _ sentCustomProfile -> viewSentGroupInvitation g c sentCustomProfile
CRFileTransferStatus ftStatus -> viewFileTransferStatus ftStatus
CRUserProfile p -> viewUserProfile p
CRUserProfileNoChange -> ["user profile did not change"]
@@ -100,7 +100,7 @@ responseToView testView = \case
CRChatCmdError e -> viewChatError e
CRInvitation cReq -> viewConnReqInvitation cReq
CRSentConfirmation -> ["confirmation sent!"]
CRSentInvitation -> ["connection request sent!"]
CRSentInvitation customUserProfile -> viewSentInvitation customUserProfile testView
CRContactDeleted c -> [ttyContact' c <> ": contact is deleted"]
CRChatCleared chatInfo -> viewChatCleared chatInfo
CRAcceptingContactRequest c -> [ttyFullContact c <> ": accepting contact request..."]
@@ -129,7 +129,7 @@ responseToView testView = \case
CRSndFileRcvCancelled _ ft@SndFileTransfer {recipientDisplayName = c} ->
[ttyContact c <> " cancelled receiving " <> sndFile ft]
CRContactConnecting _ -> []
CRContactConnected ct -> [ttyFullContact ct <> ": contact is connected"]
CRContactConnected ct userCustomProfile -> viewContactConnected ct userCustomProfile testView
CRContactAnotherClient c -> [ttyContact' c <> ": contact is connected to another client"]
CRContactsDisconnected srv cs -> [plain $ "server disconnected " <> showSMPServer srv <> " (" <> contactList cs <> ")"]
CRContactsSubscribed srv cs -> [plain $ "server connected " <> showSMPServer srv <> " (" <> contactList cs <> ")"]
@@ -138,13 +138,13 @@ responseToView testView = \case
[sShow (length subscribed) <> " contacts connected (use " <> highlight' "/cs" <> " for the list)" | not (null subscribed)] <> viewErrorsSummary errors " contact errors"
where
(errors, subscribed) = partition (isJust . contactError) summary
CRGroupInvitation GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}, membership} ->
[groupInvitation' ldn fullName $ memberIncognito membership]
CRReceivedGroupInvitation g c role receivedCustomProfile -> viewReceivedGroupInvitation g c role receivedCustomProfile
CRUserJoinedGroup g _ usedCustomProfile -> viewUserJoinedGroup g usedCustomProfile testView
CRJoinedGroupMember g m mainProfile -> viewJoinedGroupMember g m mainProfile
CRHostConnected p h -> [plain $ "connected to " <> viewHostEvent p h]
CRHostDisconnected p h -> [plain $ "disconnected from " <> viewHostEvent p h]
CRGroupInvitation GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}} ->
[groupInvitation' ldn fullName]
CRReceivedGroupInvitation g c role -> viewReceivedGroupInvitation g c role
CRUserJoinedGroup g _ -> [ttyGroup' g <> ": you joined the group"]
CRJoinedGroupMember g m -> [ttyGroup' g <> ": " <> ttyMember m <> " joined the group "]
CRJoinedGroupMemberConnecting g host m -> [ttyGroup' g <> ": " <> ttyMember host <> " added " <> ttyFullMember m <> " to the group (connecting...)"]
CRConnectedToGroupMember g m -> [ttyGroup' g <> ": " <> connectedMember m <> " is connected"]
CRDeletedMemberUser g by -> [ttyGroup' g <> ": " <> ttyMember by <> " removed you from the group"] <> groupPreserved g
@@ -156,7 +156,7 @@ responseToView testView = \case
CRGroupUpdated g g' m -> viewGroupUpdated g g' m
CRMemberSubError g m e -> [ttyGroup' g <> " member " <> ttyMember m <> " error: " <> sShow e]
CRMemberSubSummary summary -> viewErrorsSummary (filter (isJust . memberError) summary) " group member errors"
CRGroupSubscribed g -> [ttyFullGroup g <> ": connected to server(s)"]
CRGroupSubscribed g -> viewGroupSubscribed g
CRPendingSubSummary _ -> []
CRSndFileSubError SndFileTransfer {fileId, fileName} e ->
["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e]
@@ -208,6 +208,12 @@ responseToView testView = \case
contactList :: [ContactRef] -> String
contactList cs = T.unpack . T.intercalate ", " $ map (\ContactRef {localDisplayName = n} -> "@" <> n) cs
viewGroupSubscribed :: GroupInfo -> [StyledString]
viewGroupSubscribed g@GroupInfo {membership} =
[incognito <> ttyFullGroup g <> ": connected to server(s)"]
where
incognito = if memberIncognito membership then incognitoPrefix else ""
showSMPServer :: SMPServer -> String
showSMPServer = B.unpack . strEncode . host
@@ -363,6 +369,12 @@ viewConnReqInvitation cReq =
"and ask them to connect: " <> highlight' "/c <invitation_link_above>"
]
viewSentGroupInvitation :: GroupInfo -> Contact -> Maybe Profile -> [StyledString]
viewSentGroupInvitation g c sentCustomProfile =
if isJust sentCustomProfile
then ["invitation to join the group " <> ttyGroup' g <> " incognito sent to " <> ttyContact' c]
else ["invitation to join the group " <> ttyGroup' g <> " sent to " <> ttyContact' c]
viewChatCleared :: AChatInfo -> [StyledString]
viewChatCleared (AChatInfo _ chatInfo) = case chatInfo of
DirectChat ct -> [ttyContact' ct <> ": all messages are removed locally ONLY"]
@@ -372,7 +384,8 @@ viewChatCleared (AChatInfo _ chatInfo) = case chatInfo of
viewContactsList :: [Contact] -> [StyledString]
viewContactsList =
let ldn = T.toLower . (localDisplayName :: Contact -> ContactName)
in map ttyFullContact . sortOn ldn
incognito ct = if contactConnIncognito ct then incognitoPrefix else ""
in map (\ct -> incognito ct <> ttyFullContact ct) . sortOn ldn
viewUserContactLinkDeleted :: [StyledString]
viewUserContactLinkDeleted =
@@ -396,6 +409,17 @@ autoAcceptStatus_ autoAccept autoReply =
("auto_accept " <> if autoAccept then "on" else "off") :
maybe [] ((["auto reply:"] <>) . ttyMsgContent) autoReply
viewSentInvitation :: Maybe Profile -> Bool -> [StyledString]
viewSentInvitation incognitoProfile testView =
case incognitoProfile of
Just profile ->
if testView
then incognitoProfile' profile : message
else message
where
message = ["connection request sent incognito!"]
Nothing -> ["connection request sent!"]
viewReceivedContactRequest :: ContactName -> Profile -> [StyledString]
viewReceivedContactRequest c Profile {fullName} =
[ ttyFullName c fullName <> " wants to connect to you!",
@@ -403,11 +427,22 @@ viewReceivedContactRequest c Profile {fullName} =
"to reject: " <> highlight ("/rc " <> c) <> " (the sender will NOT be notified)"
]
viewGroupCreated :: GroupInfo -> [StyledString]
viewGroupCreated g@GroupInfo {localDisplayName} =
[ "group " <> ttyFullGroup g <> " is created",
"use " <> highlight ("/a " <> localDisplayName <> " <name>") <> " to add members"
]
viewGroupCreated :: GroupInfo -> Maybe Profile -> Bool -> [StyledString]
viewGroupCreated g@GroupInfo {localDisplayName} incognitoProfile testView =
case incognitoProfile of
Just profile ->
if testView
then incognitoProfile' profile : message
else message
where
message =
[ "group " <> ttyFullGroup g <> " is created incognito, your profile for this group: " <> incognitoProfile' profile,
"use " <> highlight ("/a " <> localDisplayName <> " <name>") <> " to add members"
]
Nothing ->
[ "group " <> ttyFullGroup g <> " is created",
"use " <> highlight ("/a " <> localDisplayName <> " <name>") <> " to add members"
]
viewCannotResendInvitation :: GroupInfo -> ContactName -> [StyledString]
viewCannotResendInvitation GroupInfo {localDisplayName = gn} c =
@@ -415,11 +450,33 @@ viewCannotResendInvitation GroupInfo {localDisplayName = gn} c =
"to re-send invitation: " <> highlight ("/rm " <> gn <> " " <> c) <> ", " <> highlight ("/a " <> gn <> " " <> c)
]
viewReceivedGroupInvitation :: GroupInfo -> Contact -> GroupMemberRole -> [StyledString]
viewReceivedGroupInvitation g c role =
[ ttyFullGroup g <> ": " <> ttyContact' c <> " invites you to join the group as " <> plain (strEncode role),
"use " <> highlight ("/j " <> groupName' g) <> " to accept"
]
viewUserJoinedGroup :: GroupInfo -> Bool -> Bool -> [StyledString]
viewUserJoinedGroup g@GroupInfo {membership = GroupMember {memberProfile}} incognito testView =
if incognito
then
if testView
then incognitoProfile' (fromLocalProfile memberProfile) : incognitoMessage
else incognitoMessage
else [ttyGroup' g <> ": you joined the group"]
where
incognitoMessage = [ttyGroup' g <> ": you joined the group incognito as " <> incognitoProfile' (fromLocalProfile memberProfile)]
viewJoinedGroupMember :: GroupInfo -> GroupMember -> Maybe Profile -> [StyledString]
viewJoinedGroupMember g m@GroupMember {localDisplayName} = \case
Just Profile {displayName = mainProfileName} -> [ttyGroup' g <> ": " <> ttyContact mainProfileName <> " joined the group incognito as " <> styleIncognito localDisplayName]
Nothing -> [ttyGroup' g <> ": " <> ttyMember m <> " joined the group "]
viewReceivedGroupInvitation :: GroupInfo -> Contact -> GroupMemberRole -> Maybe Profile -> [StyledString]
viewReceivedGroupInvitation g c role hostIncognitoProfile =
case hostIncognitoProfile of
Just profile ->
[ ttyFullGroup g <> ": " <> ttyContact' c <> " (known to the group as " <> incognitoProfile' profile <> ") invites you to join the group incognito as " <> plain (strEncode role),
"use " <> highlight ("/j " <> groupName' g) <> " to join this group incognito"
]
Nothing ->
[ ttyFullGroup g <> ": " <> ttyContact' c <> " invites you to join the group as " <> plain (strEncode role),
"use " <> highlight ("/j " <> groupName' g) <> " to accept"
]
groupPreserved :: GroupInfo -> [StyledString]
groupPreserved g = ["use " <> highlight ("/d #" <> groupName' g) <> " to delete the group"]
@@ -434,7 +491,8 @@ viewGroupMembers :: Group -> [StyledString]
viewGroupMembers (Group GroupInfo {membership} members) = map groupMember . filter (not . removedOrLeft) $ membership : members
where
removedOrLeft m = let s = memberStatus m in s == GSMemRemoved || s == GSMemLeft
groupMember m = ttyFullMember m <> ": " <> role m <> ", " <> category m <> status m
groupMember m = incognito m <> ttyFullMember m <> ": " <> role m <> ", " <> category m <> status m
incognito m = if memberIncognito m then incognitoPrefix else ""
role m = plain . strEncode $ memberRole (m :: GroupMember)
category m = case memberCategory m of
GCUserMember -> "you, "
@@ -450,6 +508,21 @@ viewGroupMembers (Group GroupInfo {membership} members) = map groupMember . filt
GSMemCreator -> "created group"
_ -> ""
viewContactConnected :: Contact -> Maybe Profile -> Bool -> [StyledString]
viewContactConnected ct@Contact {localDisplayName} userIncognitoProfile testView =
case userIncognitoProfile of
Just profile ->
if testView
then incognitoProfile' profile : message
else message
where
message =
[ ttyFullContact ct <> ": contact is connected, your incognito profile for this contact is " <> incognitoProfile' profile,
"use " <> highlight ("/info " <> localDisplayName) <> " to print out this incognito profile again"
]
Nothing ->
[ttyFullContact ct <> ": contact is connected"]
viewGroupsList :: [GroupInfo] -> [StyledString]
viewGroupsList [] = ["you have no groups!", "to create: " <> highlight' "/g <name>"]
viewGroupsList gs = map groupSS $ sortOn ldn_ gs
@@ -457,9 +530,10 @@ viewGroupsList gs = map groupSS $ sortOn ldn_ gs
ldn_ = T.toLower . (localDisplayName :: GroupInfo -> GroupName)
groupSS GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}, membership} =
case memberStatus membership of
GSMemInvited -> groupInvitation' ldn fullName
s -> ttyGroup ldn <> optFullName ldn fullName <> viewMemberStatus s
GSMemInvited -> groupInvitation' ldn fullName $ memberIncognito membership
s -> incognito <> ttyGroup ldn <> optFullName ldn fullName <> viewMemberStatus s
where
incognito = if memberIncognito membership then incognitoPrefix else ""
viewMemberStatus = \case
GSMemRemoved -> delete "you are removed"
GSMemLeft -> delete "you left"
@@ -467,15 +541,20 @@ viewGroupsList gs = map groupSS $ sortOn ldn_ gs
_ -> ""
delete reason = " (" <> reason <> ", delete local copy: " <> highlight ("/d #" <> ldn) <> ")"
groupInvitation' :: GroupName -> Text -> StyledString
groupInvitation' displayName fullName =
groupInvitation' :: GroupName -> Text -> Bool -> StyledString
groupInvitation' displayName fullName membershipIncognito =
highlight ("#" <> displayName)
<> optFullName displayName fullName
<> " - you are invited ("
<> invitationText
<> highlight ("/j " <> displayName)
<> " to join, "
<> highlight ("/d #" <> displayName)
<> " to delete invitation)"
where
invitationText =
if membershipIncognito
then " - you are invited incognito ("
else " - you are invited ("
viewContactsMerged :: Contact -> Contact -> [StyledString]
viewContactsMerged _into@Contact {localDisplayName = c1} _merged@Contact {localDisplayName = c2} =
@@ -514,16 +593,24 @@ viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} =
"use `/network socks=<on/off/[ipv4]:port>[ timeout=<seconds>]` to change settings"
]
viewContactInfo :: Contact -> ConnectionStats -> [StyledString]
viewContactInfo Contact {contactId} stats =
viewContactInfo :: Contact -> ConnectionStats -> Maybe Profile -> [StyledString]
viewContactInfo Contact {contactId} stats incognitoProfile =
["contact ID: " <> sShow contactId] <> viewConnectionStats stats
<> maybe
["you've shared main profile with this contact"]
(\p -> ["you've shared incognito profile with this contact: " <> incognitoProfile' p])
incognitoProfile
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString]
viewGroupMemberInfo GroupInfo {groupId} GroupMember {groupMemberId} stats =
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> Maybe Profile -> [StyledString]
viewGroupMemberInfo GroupInfo {groupId} GroupMember {groupMemberId} stats mainProfile =
[ "group ID: " <> sShow groupId,
"member ID: " <> sShow groupMemberId
]
<> maybe ["member not connected"] viewConnectionStats stats
<> maybe
["unknown whether group member uses his main profile or incognito one for the group"]
(\Profile {displayName, fullName} -> ["member is using " <> styleIncognito' "incognito" <> " profile for the group, main profile known: " <> ttyFullName displayName fullName])
mainProfile
viewConnectionStats :: ConnectionStats -> [StyledString]
viewConnectionStats ConnectionStats {rcvServers, sndServers} =
@@ -559,8 +646,8 @@ viewGroupUpdated
viewContactUpdated :: Contact -> Contact -> [StyledString]
viewContactUpdated
Contact {localDisplayName = n, profile = Profile {fullName}}
Contact {localDisplayName = n', profile = Profile {fullName = fullName'}}
Contact {localDisplayName = n, profile = LocalProfile {fullName}}
Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName'}}
| n == n' && fullName == fullName' = []
| n == n' = ["contact " <> ttyContact n <> fullNameUpdate]
| otherwise =
@@ -817,6 +904,7 @@ viewChatError = \case
CEGroupDuplicateMember c -> ["contact " <> ttyContact c <> " is already in the group"]
CEGroupDuplicateMemberId -> ["cannot add member - duplicate member ID"]
CEGroupUserRole -> ["you have insufficient permissions for this group command"]
CEGroupNotIncognitoCantInvite -> ["you're using main profile for this group - prohibited to invite contact to whom you are connected incognito"]
CEGroupContactRole c -> ["contact " <> ttyContact c <> " has insufficient permissions for this group action"]
CEGroupNotJoined g -> ["you did not join this group, use " <> highlight ("/join #" <> groupName' g)]
CEGroupMemberNotActive -> ["you cannot invite other members yet, try later"]
@@ -883,14 +971,14 @@ ttyContact' :: Contact -> StyledString
ttyContact' Contact {localDisplayName = c} = ttyContact c
ttyFullContact :: Contact -> StyledString
ttyFullContact Contact {localDisplayName, profile = Profile {fullName}} =
ttyFullContact Contact {localDisplayName, profile = LocalProfile {fullName}} =
ttyFullName localDisplayName fullName
ttyMember :: GroupMember -> StyledString
ttyMember GroupMember {localDisplayName} = ttyContact localDisplayName
ttyFullMember :: GroupMember -> StyledString
ttyFullMember GroupMember {localDisplayName, memberProfile = Profile {fullName}} =
ttyFullMember GroupMember {localDisplayName, memberProfile = LocalProfile {fullName}} =
ttyFullName localDisplayName fullName
ttyFullName :: ContactName -> Text -> StyledString
@@ -909,7 +997,8 @@ ttyFromContactDeleted :: ContactName -> StyledString
ttyFromContactDeleted c = ttyFrom $ c <> "> [deleted] "
ttyToContact' :: Contact -> StyledString
ttyToContact' Contact {localDisplayName = c} = ttyToContact c
ttyToContact' Contact {localDisplayName = c, activeConn = Connection {customUserProfileId}} =
maybe "" (const incognitoPrefix) customUserProfileId <> ttyToContact c
ttyQuotedContact :: Contact -> StyledString
ttyQuotedContact Contact {localDisplayName = c} = ttyFrom $ c <> ">"
@@ -919,7 +1008,8 @@ ttyQuotedMember (Just GroupMember {localDisplayName = c}) = "> " <> ttyFrom c
ttyQuotedMember _ = "> " <> ttyFrom "?"
ttyFromContact' :: Contact -> StyledString
ttyFromContact' Contact {localDisplayName = c} = ttyFromContact c
ttyFromContact' Contact {localDisplayName = c, activeConn = Connection {customUserProfileId}} =
maybe "" (const incognitoPrefix) customUserProfileId <> ttyFromContact c
ttyGroup :: GroupName -> StyledString
ttyGroup g = styled (colored Blue) $ "#" <> g
@@ -949,10 +1039,12 @@ ttyFrom :: Text -> StyledString
ttyFrom = styled $ colored Yellow
ttyFromGroup' :: GroupInfo -> GroupMember -> StyledString
ttyFromGroup' g GroupMember {localDisplayName = m} = ttyFromGroup g m
ttyFromGroup' g@GroupInfo {membership} GroupMember {localDisplayName = m} =
(if memberIncognito membership then incognitoPrefix else "") <> ttyFromGroup g m
ttyToGroup :: GroupInfo -> StyledString
ttyToGroup GroupInfo {localDisplayName = g} = styled (colored Cyan) $ "#" <> g <> " "
ttyToGroup GroupInfo {localDisplayName = g, membership} =
(if memberIncognito membership then incognitoPrefix else "") <> styled (colored Cyan) ("#" <> g <> " ")
ttyFilePath :: FilePath -> StyledString
ttyFilePath = plain
@@ -960,12 +1052,24 @@ ttyFilePath = plain
optFullName :: ContactName -> Text -> StyledString
optFullName localDisplayName fullName = plain $ optionalFullName localDisplayName fullName
incognitoPrefix :: StyledString
incognitoPrefix = styleIncognito' "i "
incognitoProfile' :: Profile -> StyledString
incognitoProfile' Profile {displayName} = styleIncognito displayName
highlight :: StyledFormat a => a -> StyledString
highlight = styled $ colored Cyan
highlight' :: String -> StyledString
highlight' = highlight
styleIncognito :: StyledFormat a => a -> StyledString
styleIncognito = styled $ colored Magenta
styleIncognito' :: String -> StyledString
styleIncognito' = styleIncognito
styleTime :: String -> StyledString
styleTime = Styled [SetColor Foreground Vivid Black]