2022-01-28 10:41:09 +00:00
{- # LANGUAGE DataKinds # -}
2022-05-17 08:37:00 +01:00
{- # LANGUAGE DeriveGeneric # -}
2021-07-12 19:00:03 +01:00
{- # LANGUAGE DuplicateRecordFields # -}
2022-01-26 16:18:27 +04:00
{- # LANGUAGE GADTs # -}
2021-06-25 18:18:24 +01:00
{- # LANGUAGE LambdaCase # -}
2021-07-12 19:00:03 +01:00
{- # LANGUAGE NamedFieldPuns # -}
2021-06-25 18:18:24 +01:00
{- # LANGUAGE OverloadedStrings # -}
2022-03-16 13:20:47 +00:00
{- # LANGUAGE ScopedTypeVariables # -}
{- # LANGUAGE TypeApplications # -}
2021-06-25 18:18:24 +01:00
2022-01-24 16:07:17 +00:00
module Simplex.Chat.View where
2022-05-17 08:37:00 +01:00
import Data.Aeson ( ToJSON )
2022-02-22 14:05:45 +00:00
import qualified Data.Aeson as J
2022-03-10 15:45:40 +04:00
import qualified Data.ByteString.Char8 as B
2022-05-17 08:37:00 +01:00
import qualified Data.ByteString.Lazy.Char8 as LB
2022-08-13 14:18:12 +01:00
import Data.Char ( toUpper )
2021-09-05 14:08:29 +01:00
import Data.Function ( on )
2021-09-04 07:32:56 +01:00
import Data.Int ( Int64 )
2022-03-10 15:45:40 +04:00
import Data.List ( groupBy , intercalate , intersperse , partition , sortOn )
import Data.Maybe ( isJust , isNothing )
2021-07-04 18:42:24 +01:00
import Data.Text ( Text )
2021-06-25 18:18:24 +01:00
import qualified Data.Text as T
2022-01-24 16:07:17 +00:00
import Data.Time.Clock ( DiffTime )
2021-06-25 18:18:24 +01:00
import Data.Time.Format ( defaultTimeLocale , formatTime )
2022-01-24 16:07:17 +00:00
import Data.Time.LocalTime ( ZonedTime ( .. ) , localDay , localTimeOfDay , timeOfDayToTime , utcToZonedTime )
2022-05-17 08:37:00 +01:00
import GHC.Generics ( Generic )
import qualified Network.HTTP.Types as Q
2021-09-04 07:32:56 +01:00
import Numeric ( showFFloat )
2022-05-21 18:17:15 +04:00
import Simplex.Chat ( maxImageSize )
2022-05-17 08:37:00 +01:00
import Simplex.Chat.Call
2021-06-25 18:18:24 +01:00
import Simplex.Chat.Controller
2022-01-24 16:07:17 +00:00
import Simplex.Chat.Help
2021-06-25 18:18:24 +01:00
import Simplex.Chat.Markdown
2022-02-09 20:58:02 +04:00
import Simplex.Chat.Messages hiding ( NewChatItem ( .. ) )
2022-01-24 16:07:17 +00:00
import Simplex.Chat.Protocol
2021-07-12 19:00:03 +01:00
import Simplex.Chat.Store ( StoreError ( .. ) )
2021-06-25 18:18:24 +01:00
import Simplex.Chat.Styled
2021-07-04 18:42:24 +01:00
import Simplex.Chat.Types
2022-07-25 14:04:27 +01:00
import Simplex.Messaging.Agent.Env.SQLite ( NetworkConfig ( .. ) )
2021-06-25 18:18:24 +01:00
import Simplex.Messaging.Agent.Protocol
2022-05-17 08:37:00 +01:00
import qualified Simplex.Messaging.Crypto as C
2022-04-22 20:32:19 +01:00
import Simplex.Messaging.Encoding
2022-01-11 08:50:44 +00:00
import Simplex.Messaging.Encoding.String
2022-05-17 08:37:00 +01:00
import Simplex.Messaging.Parsers ( dropPrefix , taggedObjectJSON )
2022-08-13 14:18:12 +01:00
import Simplex.Messaging.Protocol ( AProtocolType , ProtocolServer ( .. ) )
2021-12-08 13:09:51 +00:00
import qualified Simplex.Messaging.Protocol as SMP
2022-08-13 14:18:12 +01:00
import Simplex.Messaging.Transport.Client ( TransportHost ( .. ) )
2022-02-22 14:05:45 +00:00
import Simplex.Messaging.Util ( bshow )
2021-06-25 18:18:24 +01:00
import System.Console.ANSI.Types
2022-01-24 16:07:17 +00:00
serializeChatResponse :: ChatResponse -> String
2022-02-21 12:05:00 +00:00
serializeChatResponse = unlines . map unStyle . responseToView False
2022-01-24 16:07:17 +00:00
2022-02-21 12:05:00 +00:00
responseToView :: Bool -> ChatResponse -> [ StyledString ]
responseToView testView = \ case
2022-08-18 11:35:31 +04:00
CRActiveUser User { profile } -> viewUserProfile $ fromLocalProfile profile
2022-02-21 12:05:00 +00:00
CRChatStarted -> [ " chat started " ]
2022-06-06 16:23:47 +01:00
CRChatRunning -> [ " chat is running " ]
CRChatStopped -> [ " chat stopped " ]
2022-06-26 15:04:44 +01:00
CRChatSuspended -> [ " chat suspended " ]
2022-02-22 14:05:45 +00:00
CRApiChats chats -> if testView then testViewChats chats else [ plain . bshow $ J . encode chats ]
CRApiChat chat -> if testView then testViewChat chat else [ plain . bshow $ J . encode chat ]
2022-04-04 19:51:49 +01:00
CRApiParsedMarkdown ft -> [ plain . bshow $ J . encode ft ]
2022-03-10 15:45:40 +04:00
CRUserSMPServers smpServers -> viewSMPServers smpServers testView
2022-07-25 14:04:27 +01:00
CRNetworkConfig cfg -> viewNetworkConfig cfg
2022-08-18 11:35:31 +04:00
CRContactInfo ct cStats customUserProfile -> viewContactInfo ct cStats customUserProfile
2022-08-27 19:56:03 +04:00
CRGroupMemberInfo g m cStats -> viewGroupMemberInfo g m cStats
2022-09-05 15:23:38 +01:00
CRNewChatItem ( AChatItem _ _ chat item ) -> unmuted chat item $ viewChatItem chat item False
2022-07-20 16:56:55 +04:00
CRLastMessages chatItems -> concatMap ( \ ( AChatItem _ _ chat item ) -> viewChatItem chat item True ) chatItems
2022-03-23 11:37:51 +00:00
CRChatItemStatusUpdated _ -> []
2022-09-05 15:23:38 +01:00
CRChatItemUpdated ( AChatItem _ _ chat item ) -> unmuted chat item $ viewItemUpdate chat item
CRChatItemDeleted ( AChatItem _ _ chat deletedItem ) ( AChatItem _ _ _ toItem ) -> unmuted chat deletedItem $ viewItemDelete chat deletedItem toItem
2022-05-17 11:22:09 +04:00
CRChatItemDeletedNotFound Contact { localDisplayName = c } _ -> [ ttyFrom $ c <> " > [deleted - original message not found] " ]
2022-03-29 08:53:30 +01:00
CRBroadcastSent mc n ts -> viewSentBroadcast mc n ts
2022-02-02 11:43:52 +00:00
CRMsgIntegrityError mErr -> viewMsgIntegrityError mErr
2022-02-21 12:05:00 +00:00
CRCmdAccepted _ -> []
CRCmdOk -> [ " ok " ]
2022-01-24 16:07:17 +00:00
CRChatHelp section -> case section of
2022-02-21 12:05:00 +00:00
HSMain -> chatHelpInfo
HSFiles -> filesHelpInfo
HSGroups -> groupsHelpInfo
HSMyAddress -> myAddressHelpInfo
2022-04-03 09:44:23 +01:00
HSMessages -> messagesHelpInfo
2022-02-21 12:05:00 +00:00
HSMarkdown -> markdownInfo
2022-07-26 07:29:28 +01:00
HSSettings -> settingsInfo
2022-02-21 12:05:00 +00:00
CRWelcome user -> chatWelcome user
CRContactsList cs -> viewContactsList cs
2022-06-27 19:41:25 +01:00
CRUserContactLink cReqUri autoAccept autoReply -> connReqContact_ " Your chat address: " cReqUri <> autoAcceptStatus_ autoAccept autoReply
CRUserContactLinkUpdated _ autoAccept autoReply -> autoAcceptStatus_ autoAccept autoReply
2022-02-21 12:05:00 +00:00
CRContactRequestRejected UserContactRequest { localDisplayName = c } -> [ ttyContact c <> " : contact request rejected " ]
2022-08-27 19:56:03 +04:00
CRGroupCreated g -> viewGroupCreated g
2022-02-21 12:05:00 +00:00
CRGroupMembers g -> viewGroupMembers g
CRGroupsList gs -> viewGroupsList gs
2022-08-27 19:56:03 +04:00
CRSentGroupInvitation g c _ -> viewSentGroupInvitation g c
2022-02-21 12:05:00 +00:00
CRFileTransferStatus ftStatus -> viewFileTransferStatus ftStatus
CRUserProfile p -> viewUserProfile p
CRUserProfileNoChange -> [ " user profile did not change " ]
CRVersionInfo _ -> [ plain versionStr , plain updateStr ]
CRChatCmdError e -> viewChatError e
CRInvitation cReq -> viewConnReqInvitation cReq
CRSentConfirmation -> [ " confirmation sent! " ]
2022-08-18 11:35:31 +04:00
CRSentInvitation customUserProfile -> viewSentInvitation customUserProfile testView
2022-02-21 12:05:00 +00:00
CRContactDeleted c -> [ ttyContact' c <> " : contact is deleted " ]
2022-05-17 11:22:09 +04:00
CRChatCleared chatInfo -> viewChatCleared chatInfo
2022-02-21 12:05:00 +00:00
CRAcceptingContactRequest c -> [ ttyFullContact c <> " : accepting contact request... " ]
CRContactAlreadyExists c -> [ ttyFullContact c <> " : contact already exists " ]
CRContactRequestAlreadyAccepted c -> [ ttyFullContact c <> " : sent you a duplicate contact request, but you are already connected, no action needed " ]
CRUserContactLinkCreated cReq -> connReqContact_ " Your new chat address is created! " cReq
CRUserContactLinkDeleted -> viewUserContactLinkDeleted
CRUserAcceptedGroupSent _g -> [] -- [ttyGroup' g <> ": joining the group..."]
CRUserDeletedMember g m -> [ ttyGroup' g <> " : you removed " <> ttyMember m <> " from the group " ]
CRLeftMemberUser g -> [ ttyGroup' g <> " : you left the group " ] <> groupPreserved g
CRGroupDeletedUser g -> [ ttyGroup' g <> " : you deleted the group " ]
2022-04-29 15:56:56 +04:00
CRRcvFileAccepted ci -> savingFile' ci
2022-02-21 12:05:00 +00:00
CRRcvFileAcceptedSndCancelled ft -> viewRcvFileSndCancelled ft
2022-05-05 10:37:53 +01:00
CRSndGroupFileCancelled _ ftm fts -> viewSndGroupFileCancelled ftm fts
2022-02-21 12:05:00 +00:00
CRRcvFileCancelled ft -> receivingFile_ " cancelled " ft
CRUserProfileUpdated p p' -> viewUserProfileUpdated p p'
2022-08-24 19:03:43 +04:00
CRContactAliasUpdated c -> viewContactAliasUpdated c
2022-01-24 16:07:17 +00:00
CRContactUpdated c c' -> viewContactUpdated c c'
CRContactsMerged intoCt mergedCt -> viewContactsMerged intoCt mergedCt
2022-01-31 21:53:53 +04:00
CRReceivedContactRequest UserContactRequest { localDisplayName = c , profile } -> viewReceivedContactRequest c profile
2022-04-29 15:56:56 +04:00
CRRcvFileStart ci -> receivingFile_' " started " ci
2022-04-15 09:36:38 +04:00
CRRcvFileComplete ci -> receivingFile_' " completed " ci
2022-01-24 16:07:17 +00:00
CRRcvFileSndCancelled ft -> viewRcvFileSndCancelled ft
2022-05-05 10:37:53 +01:00
CRSndFileStart _ ft -> sendingFile_ " started " ft
CRSndFileComplete _ ft -> sendingFile_ " completed " ft
CRSndFileCancelled _ ft -> sendingFile_ " cancelled " ft
CRSndFileRcvCancelled _ ft @ SndFileTransfer { recipientDisplayName = c } ->
2022-01-24 16:07:17 +00:00
[ ttyContact c <> " cancelled receiving " <> sndFile ft ]
2022-02-08 13:04:17 +04:00
CRContactConnecting _ -> []
2022-08-18 11:35:31 +04:00
CRContactConnected ct userCustomProfile -> viewContactConnected ct userCustomProfile testView
2022-01-26 16:18:27 +04:00
CRContactAnotherClient c -> [ ttyContact' c <> " : contact is connected to another client " ]
2022-08-13 11:53:53 +01:00
CRContactsDisconnected srv cs -> [ plain $ " server disconnected " <> showSMPServer srv <> " ( " <> contactList cs <> " ) " ]
CRContactsSubscribed srv cs -> [ plain $ " server connected " <> showSMPServer srv <> " ( " <> contactList cs <> " ) " ]
2022-01-26 16:18:27 +04:00
CRContactSubError c e -> [ ttyContact' c <> " : contact error " <> sShow e ]
2022-02-25 16:29:36 +04:00
CRContactSubSummary summary ->
2022-03-19 09:04:53 +00:00
[ sShow ( length subscribed ) <> " contacts connected (use " <> highlight' " /cs " <> " for the list) " | not ( null subscribed ) ] <> viewErrorsSummary errors " contact errors "
2022-02-25 16:29:36 +04:00
where
2022-02-26 20:21:32 +00:00
( errors , subscribed ) = partition ( isJust . contactError ) summary
2022-08-27 19:56:03 +04:00
CRGroupInvitation g -> [ groupInvitation' g ]
CRReceivedGroupInvitation g c role -> viewReceivedGroupInvitation g c role
CRUserJoinedGroup g _ -> viewUserJoinedGroup g
CRJoinedGroupMember g m -> viewJoinedGroupMember g m
2022-08-13 14:18:12 +01:00
CRHostConnected p h -> [ plain $ " connected to " <> viewHostEvent p h ]
CRHostDisconnected p h -> [ plain $ " disconnected from " <> viewHostEvent p h ]
2022-01-26 16:18:27 +04:00
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
CRDeletedMember g by m -> [ ttyGroup' g <> " : " <> ttyMember by <> " removed " <> ttyMember m <> " from the group " ]
CRLeftMember g m -> [ ttyGroup' g <> " : " <> ttyMember m <> " left the group " ]
2022-01-24 16:07:17 +00:00
CRGroupEmpty g -> [ ttyFullGroup g <> " : group is empty " ]
CRGroupRemoved g -> [ ttyFullGroup g <> " : you are no longer a member or group deleted " ]
2022-01-27 22:01:15 +00:00
CRGroupDeleted g m -> [ ttyGroup' g <> " : " <> ttyMember m <> " deleted the group " , " use " <> highlight ( " /d # " <> groupName' g ) <> " to delete the local copy of the group " ]
2022-07-29 19:04:32 +01:00
CRGroupUpdated g g' m -> viewGroupUpdated g g' m
2022-07-17 15:51:17 +01:00
CRMemberSubError g m e -> [ ttyGroup' g <> " member " <> ttyMember m <> " error: " <> sShow e ]
CRMemberSubSummary summary -> viewErrorsSummary ( filter ( isJust . memberError ) summary ) " group member errors "
2022-08-18 11:35:31 +04:00
CRGroupSubscribed g -> viewGroupSubscribed g
2022-02-27 18:16:38 +00:00
CRPendingSubSummary _ -> []
2022-01-24 16:07:17 +00:00
CRSndFileSubError SndFileTransfer { fileId , fileName } e ->
[ " sent file " <> sShow fileId <> " ( " <> plain fileName <> " ) error: " <> sShow e ]
CRRcvFileSubError RcvFileTransfer { fileId , fileInvitation = FileInvitation { fileName } } e ->
[ " received file " <> sShow fileId <> " ( " <> plain fileName <> " ) error: " <> sShow e ]
2022-07-04 11:15:25 +01:00
CRCallInvitation RcvCallInvitation { contact , callType , sharedKey } -> viewCallInvitation contact callType sharedKey
2022-05-17 08:37:00 +01:00
CRCallOffer { contact , callType , offer , sharedKey } -> viewCallOffer contact callType offer sharedKey
CRCallAnswer { contact , answer } -> viewCallAnswer contact answer
2022-05-04 13:31:00 +01:00
CRCallExtraInfo { contact } -> [ " call extra info from " <> ttyContact' contact ]
CRCallEnded { contact } -> [ " call with " <> ttyContact' contact <> " ended " ]
2022-07-04 11:15:25 +01:00
CRCallInvitations _ -> []
2022-01-24 16:07:17 +00:00
CRUserContactLinkSubscribed -> [ " Your address is active! To show: " <> highlight' " /sa " ]
CRUserContactLinkSubError e -> [ " user address error: " <> sShow e , " to delete your address: " <> highlight' " /da " ]
2022-04-23 17:32:40 +01:00
CRNewContactConnection _ -> []
2022-04-24 09:05:54 +01:00
CRContactConnectionDeleted PendingContactConnection { pccConnId } -> [ " connection : " <> sShow pccConnId <> " deleted " ]
2022-04-22 20:32:19 +01:00
CRNtfTokenStatus status -> [ " device token status: " <> plain ( smpEncode status ) ]
2022-06-25 17:02:16 +01:00
CRNtfToken _ status mode -> [ " device token status: " <> plain ( smpEncode status ) <> " , notifications mode: " <> plain ( strEncode mode ) ]
2022-06-19 14:44:13 +01:00
CRNtfMessages { } -> []
2022-09-17 16:06:27 +01:00
CRSQLResult rows -> map plain rows
2022-01-24 16:07:17 +00:00
CRMessageError prefix err -> [ plain prefix <> " : " <> plain err ]
CRChatError e -> viewChatError e
where
2022-02-09 20:58:02 +04:00
testViewChats :: [ AChat ] -> [ StyledString ]
testViewChats chats = [ sShow $ map toChatView chats ]
where
2022-04-24 09:05:54 +01:00
toChatView :: AChat -> ( Text , Text , Maybe ConnStatus )
toChatView ( AChat _ ( Chat ( DirectChat Contact { localDisplayName , activeConn } ) items _ ) ) = ( " @ " <> localDisplayName , toCIPreview items , Just $ connStatus activeConn )
toChatView ( AChat _ ( Chat ( GroupChat GroupInfo { localDisplayName } ) items _ ) ) = ( " # " <> localDisplayName , toCIPreview items , Nothing )
toChatView ( AChat _ ( Chat ( ContactRequest UserContactRequest { localDisplayName } ) items _ ) ) = ( " <@ " <> localDisplayName , toCIPreview items , Nothing )
toChatView ( AChat _ ( Chat ( ContactConnection PendingContactConnection { pccConnId , pccConnStatus } ) items _ ) ) = ( " : " <> T . pack ( show pccConnId ) , toCIPreview items , Just $ pccConnStatus )
2022-02-09 20:58:02 +04:00
toCIPreview :: [ CChatItem c ] -> Text
toCIPreview ( ( CChatItem _ ChatItem { meta } ) : _ ) = itemText meta
toCIPreview _ = " "
testViewChat :: AChat -> [ StyledString ]
testViewChat ( AChat _ Chat { chatItems } ) = [ sShow $ map toChatView chatItems ]
where
2022-04-10 13:30:58 +04:00
toChatView :: CChatItem c -> ( ( Int , Text ) , Maybe ( Int , Text ) , Maybe String )
toChatView ( CChatItem dir ChatItem { meta , quotedItem , file } ) =
( ( msgDirectionInt $ toMsgDirection dir , itemText meta ) , qItem , fPath )
where
qItem = case quotedItem of
Nothing -> Nothing
Just CIQuote { chatDir = quoteDir , content } ->
Just ( msgDirectionInt $ quoteMsgDirection quoteDir , msgContentText content )
fPath = case file of
Just CIFile { filePath = Just fp } -> Just fp
_ -> Nothing
2022-02-25 16:29:36 +04:00
viewErrorsSummary :: [ a ] -> StyledString -> [ StyledString ]
2022-03-19 09:04:53 +00:00
viewErrorsSummary summary s = [ ttyError ( T . pack . show $ length summary ) <> s <> " (run with -c option to show each error) " | not ( null summary ) ]
2022-04-25 09:17:12 +01:00
contactList :: [ ContactRef ] -> String
contactList cs = T . unpack . T . intercalate " , " $ map ( \ ContactRef { localDisplayName = n } -> " @ " <> n ) cs
2022-09-05 15:23:38 +01:00
unmuted :: ChatInfo c -> ChatItem c d -> [ StyledString ] -> [ StyledString ]
unmuted chat ChatItem { chatDir } s = case ( chat , chatDir ) of
( DirectChat Contact { chatSettings = DisableNtfs } , CIDirectRcv ) -> []
( GroupChat GroupInfo { chatSettings = DisableNtfs } , CIGroupRcv _ ) -> []
_ -> s
2021-06-25 18:18:24 +01:00
2022-08-18 11:35:31 +04:00
viewGroupSubscribed :: GroupInfo -> [ StyledString ]
viewGroupSubscribed g @ GroupInfo { membership } =
[ incognito <> ttyFullGroup g <> " : connected to server(s) " ]
where
incognito = if memberIncognito membership then incognitoPrefix else " "
2022-08-13 11:53:53 +01:00
showSMPServer :: SMPServer -> String
showSMPServer = B . unpack . strEncode . host
2022-08-13 14:18:12 +01:00
viewHostEvent :: AProtocolType -> TransportHost -> String
viewHostEvent p h = map toUpper ( B . unpack $ strEncode p ) <> " host " <> B . unpack ( strEncode h )
2022-07-20 16:56:55 +04:00
viewChatItem :: MsgDirectionI d => ChatInfo c -> ChatItem c d -> Bool -> [ StyledString ]
viewChatItem chat ChatItem { chatDir , meta , content , quotedItem , file } doShow = case chat of
2022-03-13 19:34:03 +00:00
DirectChat c -> case chatDir of
CIDirectSnd -> case content of
2022-04-10 13:30:58 +04:00
CISndMsgContent mc -> withSndFile to $ sndMsg to quote mc
2022-07-20 16:56:55 +04:00
CISndDeleted _ -> showSndItem to
CISndCall { } -> showSndItem to
CISndGroupInvitation { } -> showSndItem to
CISndGroupEvent { } -> showSndItemProhibited to
2022-03-13 19:34:03 +00:00
where
to = ttyToContact' c
CIDirectRcv -> case content of
2022-04-10 13:30:58 +04:00
CIRcvMsgContent mc -> withRcvFile from $ rcvMsg from quote mc
2022-07-20 16:56:55 +04:00
CIRcvDeleted _ -> showRcvItem from
CIRcvCall { } -> showRcvItem from
2022-05-28 19:13:07 +01:00
CIRcvIntegrityError err -> viewRcvIntegrityError from err meta
2022-07-20 16:56:55 +04:00
CIRcvGroupInvitation { } -> showRcvItem from
CIRcvGroupEvent { } -> showRcvItemProhibited from
2022-03-13 19:34:03 +00:00
where
from = ttyFromContact' c
2022-03-16 13:20:47 +00:00
where
quote = maybe [] ( directQuote chatDir ) quotedItem
2022-03-13 19:34:03 +00:00
GroupChat g -> case chatDir of
CIGroupSnd -> case content of
2022-04-10 13:30:58 +04:00
CISndMsgContent mc -> withSndFile to $ sndMsg to quote mc
2022-07-20 16:56:55 +04:00
CISndDeleted _ -> showSndItem to
CISndCall { } -> showSndItem to
CISndGroupInvitation { } -> showSndItemProhibited to
CISndGroupEvent { } -> showSndItem to
2022-03-13 19:34:03 +00:00
where
to = ttyToGroup g
CIGroupRcv m -> case content of
2022-04-10 13:30:58 +04:00
CIRcvMsgContent mc -> withRcvFile from $ rcvMsg from quote mc
2022-07-20 16:56:55 +04:00
CIRcvDeleted _ -> showRcvItem from
CIRcvCall { } -> showRcvItem from
2022-05-28 19:13:07 +01:00
CIRcvIntegrityError err -> viewRcvIntegrityError from err meta
2022-07-20 16:56:55 +04:00
CIRcvGroupInvitation { } -> showRcvItemProhibited from
CIRcvGroupEvent { } -> showRcvItem from
2022-03-13 19:34:03 +00:00
where
from = ttyFromGroup' g m
2022-01-26 16:18:27 +04:00
where
2022-03-16 13:20:47 +00:00
quote = maybe [] ( groupQuote g ) quotedItem
2022-03-13 19:34:03 +00:00
_ -> []
2022-04-10 13:30:58 +04:00
where
2022-04-19 12:29:03 +04:00
withSndFile = withFile viewSentFileInvitation
withRcvFile = withFile viewReceivedFileInvitation
withFile view dir l = maybe l ( \ f -> l <> view dir f meta ) file
sndMsg = msg viewSentMessage
rcvMsg = msg viewReceivedMessage
2022-05-05 11:52:32 +01:00
msg view dir quote mc = case ( msgContentText mc , file , quote ) of
( " " , Just _ , [] ) -> []
( " " , Just CIFile { fileName } , _ ) -> view dir quote ( MCText $ T . pack fileName ) meta
2022-04-19 12:29:03 +04:00
_ -> view dir quote mc meta
2022-07-20 16:56:55 +04:00
showSndItem to = showItem $ sentWithTime_ [ to <> plainContent content ] meta
showRcvItem from = showItem $ receivedWithTime_ from [] meta [ plainContent content ]
showSndItemProhibited to = showItem $ sentWithTime_ [ to <> plainContent content <> " " <> prohibited ] meta
showRcvItemProhibited from = showItem $ receivedWithTime_ from [] meta [ plainContent content <> " " <> prohibited ]
showItem ss = if doShow then ss else []
plainContent = plain . ciContentToText
prohibited = styled ( colored Red ) ( " [prohibited - it's a bug if this chat item was created in this context, please report it to dev team] " :: String )
2022-03-23 11:37:51 +00:00
2022-03-28 20:35:57 +04:00
viewItemUpdate :: MsgDirectionI d => ChatInfo c -> ChatItem c d -> [ StyledString ]
viewItemUpdate chat ChatItem { chatDir , meta , content , quotedItem } = case chat of
2022-03-23 11:37:51 +00:00
DirectChat Contact { localDisplayName = c } -> case chatDir of
CIDirectRcv -> case content of
2022-04-10 13:30:58 +04:00
CIRcvMsgContent mc -> viewReceivedMessage from quote mc meta
2022-03-23 11:37:51 +00:00
_ -> []
2022-03-13 19:34:03 +00:00
where
2022-03-23 11:37:51 +00:00
from = ttyFromContactEdited c
quote = maybe [] ( directQuote chatDir ) quotedItem
2022-04-03 09:44:23 +01:00
CIDirectSnd -> [ " message updated " ]
2022-03-23 11:37:51 +00:00
GroupChat g -> case chatDir of
CIGroupRcv GroupMember { localDisplayName = m } -> case content of
2022-04-10 13:30:58 +04:00
CIRcvMsgContent mc -> viewReceivedMessage from quote mc meta
2022-03-23 11:37:51 +00:00
_ -> []
where
from = ttyFromGroupEdited g m
quote = maybe [] ( groupQuote g ) quotedItem
2022-04-03 09:44:23 +01:00
CIGroupSnd -> [ " message updated " ]
2022-03-28 20:35:57 +04:00
_ -> []
viewItemDelete :: ChatInfo c -> ChatItem c d -> ChatItem c' d' -> [ StyledString ]
viewItemDelete chat ChatItem { chatDir , meta , content = deletedContent } ChatItem { content = toContent } = case chat of
DirectChat Contact { localDisplayName = c } -> case ( chatDir , deletedContent , toContent ) of
( CIDirectRcv , CIRcvMsgContent mc , CIRcvDeleted mode ) -> case mode of
2022-04-10 13:30:58 +04:00
CIDMBroadcast -> viewReceivedMessage ( ttyFromContactDeleted c ) [] mc meta
2022-04-03 09:44:23 +01:00
CIDMInternal -> [ " message deleted " ]
2022-05-17 11:22:09 +04:00
_ -> [ " message deleted " ]
2022-03-28 20:35:57 +04:00
GroupChat g -> case ( chatDir , deletedContent , toContent ) of
( CIGroupRcv GroupMember { localDisplayName = m } , CIRcvMsgContent mc , CIRcvDeleted mode ) -> case mode of
2022-04-10 13:30:58 +04:00
CIDMBroadcast -> viewReceivedMessage ( ttyFromGroupDeleted g m ) [] mc meta
2022-04-03 09:44:23 +01:00
CIDMInternal -> [ " message deleted " ]
2022-05-17 11:22:09 +04:00
_ -> [ " message deleted " ]
2022-03-23 11:37:51 +00:00
_ -> []
directQuote :: forall d' . MsgDirectionI d' => CIDirection 'CTDirect d' -> CIQuote 'CTDirect -> [ StyledString ]
directQuote _ CIQuote { content = qmc , chatDir = quoteDir } =
quoteText qmc $ if toMsgDirection ( msgDirection @ d' ) == quoteMsgDirection quoteDir then " >> " else " > "
groupQuote :: GroupInfo -> CIQuote 'CTGroup -> [ StyledString ]
groupQuote g CIQuote { content = qmc , chatDir = quoteDir } = quoteText qmc . ttyQuotedMember $ sentByMember g quoteDir
sentByMember :: GroupInfo -> CIQDirection 'CTGroup -> Maybe GroupMember
sentByMember GroupInfo { membership } = \ case
CIQGroupSnd -> Just membership
CIQGroupRcv m -> m
quoteText :: MsgContent -> StyledString -> [ StyledString ]
quoteText qmc sentBy = prependFirst ( sentBy <> " " ) $ msgPreview qmc
msgPreview :: MsgContent -> [ StyledString ]
msgPreview = msgPlain . preview . msgContentText
where
preview t
2022-03-30 08:57:42 +01:00
| T . length t <= 120 = t
| otherwise = T . take 120 t <> " ... "
2022-01-26 16:18:27 +04:00
2022-05-28 19:13:07 +01:00
viewRcvIntegrityError :: StyledString -> MsgErrorType -> CIMeta 'MDRcv -> [ StyledString ]
viewRcvIntegrityError from msgErr meta = receivedWithTime_ from [] meta $ viewMsgIntegrityError msgErr
2022-02-02 11:43:52 +00:00
viewMsgIntegrityError :: MsgErrorType -> [ StyledString ]
viewMsgIntegrityError err = msgError $ case err of
MsgSkipped fromId toId ->
" skipped message ID " <> show fromId
<> if fromId == toId then " " else " .. " <> show toId
MsgBadId msgId -> " unexpected message ID " <> show msgId
MsgBadHash -> " incorrect message hash "
MsgDuplicate -> " duplicate message ID "
where
msgError :: String -> [ StyledString ]
2022-03-13 19:34:03 +00:00
msgError s = [ ttyError s ]
2022-02-02 11:43:52 +00:00
2022-01-21 11:09:33 +00:00
viewInvalidConnReq :: [ StyledString ]
viewInvalidConnReq =
[ " " ,
" Connection link is invalid, possibly it was created in a previous version. " ,
" Please ask your contact to check " <> highlight' " /version " <> " and update if needed. " ,
plain updateStr
]
2021-08-22 15:56:36 +01:00
2022-01-21 11:09:33 +00:00
viewConnReqInvitation :: ConnReqInvitation -> [ StyledString ]
viewConnReqInvitation cReq =
2021-12-08 13:09:51 +00:00
[ " pass this invitation link to your contact (via another channel): " ,
2021-06-25 18:18:24 +01:00
" " ,
2022-01-11 08:50:44 +00:00
( plain . strEncode ) cReq ,
2021-06-25 18:18:24 +01:00
" " ,
2021-12-08 13:09:51 +00:00
" and ask them to connect: " <> highlight' " /c <invitation_link_above> "
2021-06-25 18:18:24 +01:00
]
2022-08-27 19:56:03 +04:00
viewSentGroupInvitation :: GroupInfo -> Contact -> [ StyledString ]
viewSentGroupInvitation g c =
[ " invitation to join the group " <> ttyGroup' g <> " sent to " <> ttyContact' c ]
2022-08-18 11:35:31 +04:00
2022-05-17 11:22:09 +04:00
viewChatCleared :: AChatInfo -> [ StyledString ]
viewChatCleared ( AChatInfo _ chatInfo ) = case chatInfo of
DirectChat ct -> [ ttyContact' ct <> " : all messages are removed locally ONLY " ]
GroupChat gi -> [ ttyGroup' gi <> " : all messages are removed locally ONLY " ]
_ -> []
2022-01-21 11:09:33 +00:00
viewContactsList :: [ Contact ] -> [ StyledString ]
viewContactsList =
2021-12-10 11:45:58 +00:00
let ldn = T . toLower . ( localDisplayName :: Contact -> ContactName )
2022-08-18 11:35:31 +04:00
incognito ct = if contactConnIncognito ct then incognitoPrefix else " "
2022-09-05 15:23:38 +01:00
in map ( \ ct -> incognito ct <> ttyFullContact ct <> muted ct ) . sortOn ldn
where
muted Contact { chatSettings , localDisplayName = ldn }
| enableNtfs chatSettings = " "
| otherwise = " (muted, you can " <> highlight ( " /unmute @ " <> ldn ) <> " ) "
2021-12-10 11:45:58 +00:00
2022-01-21 11:09:33 +00:00
viewUserContactLinkDeleted :: [ StyledString ]
viewUserContactLinkDeleted =
2021-12-08 13:09:51 +00:00
[ " Your chat address is deleted - accepted contacts will remain connected. " ,
" To create a new chat address use " <> highlight' " /ad "
]
connReqContact_ :: StyledString -> ConnReqContact -> [ StyledString ]
connReqContact_ intro cReq =
[ intro ,
" " ,
2022-01-11 08:50:44 +00:00
( plain . strEncode ) cReq ,
2021-12-08 13:09:51 +00:00
" " ,
" Anybody can send you contact requests with: " <> highlight' " /c <contact_link_above> " ,
" to show it again: " <> highlight' " /sa " ,
" to delete it: " <> highlight' " /da " <> " (accepted contacts will remain connected) "
]
2022-06-27 19:41:25 +01:00
autoAcceptStatus_ :: Bool -> Maybe MsgContent -> [ StyledString ]
autoAcceptStatus_ autoAccept autoReply =
( " auto_accept " <> if autoAccept then " on " else " off " ) :
maybe [] ( ( [ " auto reply: " ] <> ) . ttyMsgContent ) autoReply
2022-08-18 11:35:31 +04:00
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! " ]
2022-01-21 11:09:33 +00:00
viewReceivedContactRequest :: ContactName -> Profile -> [ StyledString ]
viewReceivedContactRequest c Profile { fullName } =
2021-12-08 13:09:51 +00:00
[ ttyFullName c fullName <> " wants to connect to you! " ,
" to accept: " <> highlight ( " /ac " <> c ) ,
" to reject: " <> highlight ( " /rc " <> c ) <> " (the sender will NOT be notified) "
]
2022-08-27 19:56:03 +04:00
viewGroupCreated :: GroupInfo -> [ StyledString ]
viewGroupCreated g @ GroupInfo { localDisplayName } =
[ " group " <> ttyFullGroup g <> " is created " ,
" use " <> highlight ( " /a " <> localDisplayName <> " <name> " ) <> " to add members "
]
2021-07-16 07:40:55 +01:00
2022-01-26 16:18:27 +04:00
viewCannotResendInvitation :: GroupInfo -> ContactName -> [ StyledString ]
viewCannotResendInvitation GroupInfo { localDisplayName = gn } c =
[ ttyContact c <> " is already invited to group " <> ttyGroup gn ,
" to re-send invitation: " <> highlight ( " /rm " <> gn <> " " <> c ) <> " , " <> highlight ( " /a " <> gn <> " " <> c )
2022-01-06 23:39:58 +04:00
]
2022-08-27 19:56:03 +04:00
viewUserJoinedGroup :: GroupInfo -> [ StyledString ]
viewUserJoinedGroup g @ GroupInfo { membership = membership @ GroupMember { memberProfile } } =
if memberIncognito membership
then [ ttyGroup' g <> " : you joined the group incognito as " <> incognitoProfile' ( fromLocalProfile memberProfile ) ]
2022-08-18 11:35:31 +04:00
else [ ttyGroup' g <> " : you joined the group " ]
2022-08-27 19:56:03 +04:00
viewJoinedGroupMember :: GroupInfo -> GroupMember -> [ StyledString ]
viewJoinedGroupMember g m =
[ ttyGroup' g <> " : " <> ttyMember m <> " joined the group " ]
2022-08-18 11:35:31 +04:00
2022-08-27 19:56:03 +04:00
viewReceivedGroupInvitation :: GroupInfo -> Contact -> GroupMemberRole -> [ StyledString ]
viewReceivedGroupInvitation g @ GroupInfo { membership = membership @ GroupMember { memberProfile } } c role =
ttyFullGroup g <> " : " <> ttyContact' c <> " invites you to join the group as " <> plain ( strEncode role ) :
if memberIncognito membership
then [ " use " <> highlight ( " /j " <> groupName' g ) <> " to join incognito as " <> incognitoProfile' ( fromLocalProfile memberProfile ) ]
else [ " use " <> highlight ( " /j " <> groupName' g ) <> " to accept " ]
2021-07-16 07:40:55 +01:00
2022-01-26 16:18:27 +04:00
groupPreserved :: GroupInfo -> [ StyledString ]
2022-01-27 22:01:15 +00:00
groupPreserved g = [ " use " <> highlight ( " /d # " <> groupName' g ) <> " to delete the group " ]
2021-08-02 20:10:24 +01:00
2021-07-24 10:26:28 +01:00
connectedMember :: GroupMember -> StyledString
connectedMember m = case memberCategory m of
GCPreMember -> " member " <> ttyFullMember m
GCPostMember -> " new member " <> ttyMember m -- without fullName as as it was shown in joinedGroupMemberConnecting
_ -> " member " <> ttyMember m -- these case is not used
2022-01-21 11:09:33 +00:00
viewGroupMembers :: Group -> [ StyledString ]
2022-01-26 16:18:27 +04:00
viewGroupMembers ( Group GroupInfo { membership } members ) = map groupMember . filter ( not . removedOrLeft ) $ membership : members
2021-07-27 08:08:05 +01:00
where
removedOrLeft m = let s = memberStatus m in s == GSMemRemoved || s == GSMemLeft
2022-08-18 11:35:31 +04:00
groupMember m = incognito m <> ttyFullMember m <> " : " <> role m <> " , " <> category m <> status m
incognito m = if memberIncognito m then incognitoPrefix else " "
2022-01-11 08:50:44 +00:00
role m = plain . strEncode $ memberRole ( m :: GroupMember )
2021-07-27 08:08:05 +01:00
category m = case memberCategory m of
GCUserMember -> " you, "
GCInviteeMember -> " invited, "
GCHostMember -> " host, "
_ -> " "
status m = case memberStatus m of
GSMemRemoved -> " removed "
GSMemLeft -> " left "
GSMemInvited -> " not yet joined "
GSMemConnected -> " connected "
GSMemComplete -> " connected "
GSMemCreator -> " created group "
_ -> " "
2022-08-18 11:35:31 +04:00
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 " ]
2022-01-24 16:07:17 +00:00
viewGroupsList :: [ GroupInfo ] -> [ StyledString ]
2022-01-21 11:09:33 +00:00
viewGroupsList [] = [ " you have no groups! " , " to create: " <> highlight' " /g <name> " ]
2022-01-24 16:07:17 +00:00
viewGroupsList gs = map groupSS $ sortOn ldn_ gs
2021-12-18 10:23:47 +00:00
where
2022-01-24 16:07:17 +00:00
ldn_ = T . toLower . ( localDisplayName :: GroupInfo -> GroupName )
2022-09-05 15:23:38 +01:00
groupSS g @ GroupInfo { localDisplayName = ldn , groupProfile = GroupProfile { fullName } , membership , chatSettings } =
2022-01-26 16:18:27 +04:00
case memberStatus membership of
2022-08-27 19:56:03 +04:00
GSMemInvited -> groupInvitation' g
2022-08-18 11:35:31 +04:00
s -> incognito <> ttyGroup ldn <> optFullName ldn fullName <> viewMemberStatus s
2022-07-31 18:54:49 +01:00
where
2022-08-18 11:35:31 +04:00
incognito = if memberIncognito membership then incognitoPrefix else " "
2022-07-31 18:54:49 +01:00
viewMemberStatus = \ case
GSMemRemoved -> delete " you are removed "
GSMemLeft -> delete " you left "
GSMemGroupDeleted -> delete " group deleted "
2022-09-05 15:23:38 +01:00
_
| enableNtfs chatSettings -> " "
| otherwise -> " (muted, you can " <> highlight ( " /unmute # " <> ldn ) <> " ) "
2022-07-31 18:54:49 +01:00
delete reason = " ( " <> reason <> " , delete local copy: " <> highlight ( " /d # " <> ldn ) <> " ) "
2022-01-21 11:09:33 +00:00
2022-08-27 19:56:03 +04:00
groupInvitation' :: GroupInfo -> StyledString
groupInvitation' GroupInfo { localDisplayName = ldn , groupProfile = GroupProfile { fullName } , membership = membership @ GroupMember { memberProfile } } =
highlight ( " # " <> ldn )
<> optFullName ldn fullName
<> " - you are invited ( "
<> highlight ( " /j " <> ldn )
<> joinText
<> highlight ( " /d # " <> ldn )
2022-01-06 14:24:33 +04:00
<> " to delete invitation) "
2022-08-18 11:35:31 +04:00
where
2022-08-27 19:56:03 +04:00
joinText =
if memberIncognito membership
then " to join as " <> incognitoProfile' ( fromLocalProfile memberProfile ) <> " , "
else " to join, "
2021-12-10 11:45:58 +00:00
2022-01-21 11:09:33 +00:00
viewContactsMerged :: Contact -> Contact -> [ StyledString ]
2022-01-24 16:07:17 +00:00
viewContactsMerged _into @ Contact { localDisplayName = c1 } _merged @ Contact { localDisplayName = c2 } =
2021-07-27 08:08:05 +01:00
[ " contact " <> ttyContact c2 <> " is merged into " <> ttyContact c1 ,
" use " <> ttyToContact c1 <> highlight' " <message> " <> " to send messages "
]
2022-01-21 11:09:33 +00:00
viewUserProfile :: Profile -> [ StyledString ]
viewUserProfile Profile { displayName , fullName } =
2021-08-22 15:56:36 +01:00
[ " user profile: " <> ttyFullName displayName fullName ,
2021-09-04 07:32:56 +01:00
" use " <> highlight' " /p <display name> [<full name>] " <> " to change it " ,
2021-08-22 15:56:36 +01:00
" (the updated profile will be sent to all your contacts) "
]
2022-03-10 15:45:40 +04:00
viewSMPServers :: [ SMPServer ] -> Bool -> [ StyledString ]
viewSMPServers smpServers testView =
if testView
then [ customSMPServers ]
else
[ customSMPServers ,
" " ,
" use " <> highlight' " /smp_servers <srv1[,srv2,...]> " <> " to switch to custom SMP servers " ,
" use " <> highlight' " /smp_servers default " <> " to remove custom SMP servers and use default " ,
" (chat option " <> highlight' " -s " <> " ( " <> highlight' " --server " <> " ) has precedence over saved SMP servers for chat session) "
]
where
customSMPServers =
if null smpServers
then " no custom SMP servers saved "
2022-07-20 14:57:16 +01:00
else viewServers smpServers
2022-07-25 14:04:27 +01:00
viewNetworkConfig :: NetworkConfig -> [ StyledString ]
viewNetworkConfig NetworkConfig { socksProxy , tcpTimeout } =
2022-07-26 07:29:28 +01:00
[ plain $ maybe " direct network connection " ( ( " using SOCKS5 proxy " <> ) . show ) socksProxy ,
" TCP timeout: " <> sShow tcpTimeout ,
" use `/network socks=<on/off/[ipv4]:port>[ timeout=<seconds>]` to change settings "
]
2022-07-25 14:04:27 +01:00
2022-08-18 11:35:31 +04:00
viewContactInfo :: Contact -> ConnectionStats -> Maybe Profile -> [ StyledString ]
2022-08-24 19:03:43 +04:00
viewContactInfo Contact { contactId , profile = LocalProfile { localAlias } } stats incognitoProfile =
2022-07-20 14:57:16 +01:00
[ " contact ID: " <> sShow contactId ] <> viewConnectionStats stats
2022-08-18 11:35:31 +04:00
<> maybe
[ " you've shared main profile with this contact " ]
( \ p -> [ " you've shared incognito profile with this contact: " <> incognitoProfile' p ] )
incognitoProfile
2022-08-24 19:03:43 +04:00
<> if localAlias /= " " then [ " alias: " <> plain localAlias ] else [ " alias not set " ]
2022-07-20 14:57:16 +01:00
2022-08-27 19:56:03 +04:00
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [ StyledString ]
viewGroupMemberInfo GroupInfo { groupId } GroupMember { groupMemberId , memberProfile = LocalProfile { localAlias } } stats =
2022-07-20 14:57:16 +01:00
[ " group ID: " <> sShow groupId ,
" member ID: " <> sShow groupMemberId
]
<> maybe [ " member not connected " ] viewConnectionStats stats
2022-08-27 19:56:03 +04:00
<> if localAlias /= " " then [ " alias: " <> plain localAlias ] else [ " no alias for contact " ]
2022-07-20 14:57:16 +01:00
viewConnectionStats :: ConnectionStats -> [ StyledString ]
viewConnectionStats ConnectionStats { rcvServers , sndServers } =
2022-07-26 07:29:28 +01:00
[ " receiving messages via: " <> viewServerHosts rcvServers | not $ null rcvServers ]
<> [ " sending messages via: " <> viewServerHosts sndServers | not $ null sndServers ]
2022-07-20 14:57:16 +01:00
viewServers :: [ SMPServer ] -> StyledString
viewServers = plain . intercalate " , " . map ( B . unpack . strEncode )
2022-03-10 15:45:40 +04:00
2022-07-26 07:29:28 +01:00
viewServerHosts :: [ SMPServer ] -> StyledString
2022-08-13 11:53:53 +01:00
viewServerHosts = plain . intercalate " , " . map showSMPServer
2022-07-26 07:29:28 +01:00
2022-01-24 16:07:17 +00:00
viewUserProfileUpdated :: Profile -> Profile -> [ StyledString ]
2022-03-10 15:45:40 +04:00
viewUserProfileUpdated Profile { displayName = n , fullName , image } Profile { displayName = n' , fullName = fullName' , image = image' }
| n == n' && fullName == fullName' && image == image' = []
| n == n' && fullName == fullName' = [ if isNothing image' then " profile image removed " else " profile image updated " ]
2022-01-24 16:07:17 +00:00
| n == n' = [ " user full name " <> ( if T . null fullName' || fullName' == n' then " removed " else " changed to " <> plain fullName' ) <> notified ]
| otherwise = [ " user profile is changed to " <> ttyFullName n' fullName' <> notified ]
where
notified = " (your contacts are notified) "
2021-08-22 15:56:36 +01:00
2022-07-29 19:04:32 +01:00
viewGroupUpdated :: GroupInfo -> GroupInfo -> Maybe GroupMember -> [ StyledString ]
viewGroupUpdated
GroupInfo { localDisplayName = n , groupProfile = GroupProfile { fullName , image } }
g' @ GroupInfo { localDisplayName = n' , groupProfile = GroupProfile { fullName = fullName' , image = image' } }
m
| n == n' && fullName == fullName' && image == image' = []
| n == n' && fullName == fullName' = [ " group " <> ttyGroup n <> " : profile image " <> ( if isNothing image' then " removed " else " updated " ) <> byMember ]
| n == n' = [ " group " <> ttyGroup n <> " : full name " <> if T . null fullName' || fullName' == n' then " removed " else " changed to " <> plain fullName' <> byMember ]
| otherwise = [ " group " <> ttyGroup n <> " is changed to " <> ttyFullGroup g' <> byMember ]
where
byMember = maybe " " ( ( " by " <> ) . ttyMember ) m
2022-08-24 19:03:43 +04:00
viewContactAliasUpdated :: Contact -> [ StyledString ]
viewContactAliasUpdated Contact { localDisplayName = n , profile = LocalProfile { localAlias } }
| localAlias == " " = [ " contact " <> ttyContact n <> " alias removed " ]
| otherwise = [ " contact " <> ttyContact n <> " alias updated: " <> plain localAlias ]
2022-01-21 11:09:33 +00:00
viewContactUpdated :: Contact -> Contact -> [ StyledString ]
viewContactUpdated
2022-08-18 11:35:31 +04:00
Contact { localDisplayName = n , profile = LocalProfile { fullName } }
Contact { localDisplayName = n' , profile = LocalProfile { fullName = fullName' } }
2021-08-22 15:56:36 +01:00
| n == n' && fullName == fullName' = []
| n == n' = [ " contact " <> ttyContact n <> fullNameUpdate ]
| otherwise =
[ " contact " <> ttyContact n <> " changed to " <> ttyFullName n' fullName' ,
" use " <> ttyToContact n' <> highlight' " <message> " <> " to send messages "
]
where
fullNameUpdate = if T . null fullName' || fullName' == n' then " removed full name " else " updated full name: " <> plain fullName'
2022-04-10 13:30:58 +04:00
viewReceivedMessage :: StyledString -> [ StyledString ] -> MsgContent -> CIMeta d -> [ StyledString ]
viewReceivedMessage from quote mc meta = receivedWithTime_ from quote meta ( ttyMsgContent mc )
2021-09-04 07:32:56 +01:00
2022-03-13 19:34:03 +00:00
receivedWithTime_ :: StyledString -> [ StyledString ] -> CIMeta d -> [ StyledString ] -> [ StyledString ]
receivedWithTime_ from quote CIMeta { localItemTs , createdAt } styledMsg = do
prependFirst ( formattedTime <> " " <> from ) ( quote <> prependFirst indent styledMsg )
2021-06-25 18:18:24 +01:00
where
2022-03-13 19:34:03 +00:00
indent = if null quote then " " else " "
2022-01-24 16:07:17 +00:00
formattedTime :: StyledString
formattedTime =
2022-01-26 16:18:27 +04:00
let localTime = zonedTimeToLocalTime localItemTs
tz = zonedTimeZone localItemTs
2021-06-25 18:18:24 +01:00
format =
2022-01-24 16:07:17 +00:00
if ( localDay localTime < localDay ( zonedTimeToLocalTime $ utcToZonedTime tz createdAt ) )
2021-06-25 18:18:24 +01:00
&& ( timeOfDayToTime ( localTimeOfDay localTime ) > ( 6 * 60 * 60 :: DiffTime ) )
then " %m-%d " -- if message is from yesterday or before and 6 hours has passed since midnight
else " %H:%M "
in styleTime $ formatTime defaultTimeLocale format localTime
2022-03-13 19:34:03 +00:00
viewSentMessage :: StyledString -> [ StyledString ] -> MsgContent -> CIMeta d -> [ StyledString ]
2022-04-10 13:30:58 +04:00
viewSentMessage to quote mc = sentWithTime_ ( prependFirst to $ quote <> prependFirst indent ( ttyMsgContent mc ) )
2022-03-13 19:34:03 +00:00
where
indent = if null quote then " " else " "
2022-01-21 11:09:33 +00:00
2022-03-29 08:53:30 +01:00
viewSentBroadcast :: MsgContent -> Int -> ZonedTime -> [ StyledString ]
viewSentBroadcast mc n ts = prependFirst ( highlight' " /feed " <> " ( " <> sShow n <> " ) " <> ttyMsgTime ts <> " " ) ( ttyMsgContent mc )
2022-04-19 12:29:03 +04:00
viewSentFileInvitation :: StyledString -> CIFile d -> CIMeta d -> [ StyledString ]
viewSentFileInvitation to CIFile { fileId , filePath } = case filePath of
Just fPath -> sentWithTime_ $ ttySentFile to fileId fPath
_ -> const []
2022-01-21 11:09:33 +00:00
2022-02-07 15:19:34 +04:00
sentWithTime_ :: [ StyledString ] -> CIMeta d -> [ StyledString ]
2022-01-28 10:41:09 +00:00
sentWithTime_ styledMsg CIMeta { localItemTs } =
2022-01-26 16:18:27 +04:00
prependFirst ( ttyMsgTime localItemTs <> " " ) styledMsg
2022-01-21 11:09:33 +00:00
2022-01-24 16:07:17 +00:00
ttyMsgTime :: ZonedTime -> StyledString
ttyMsgTime = styleTime . formatTime defaultTimeLocale " %H:%M "
2021-09-05 14:08:29 +01:00
2022-01-24 16:07:17 +00:00
ttyMsgContent :: MsgContent -> [ StyledString ]
2022-03-13 19:34:03 +00:00
ttyMsgContent = msgPlain . msgContentText
2021-09-05 14:08:29 +01:00
2022-01-24 16:07:17 +00:00
ttySentFile :: StyledString -> FileTransferId -> FilePath -> [ StyledString ]
ttySentFile to fId fPath = [ " /f " <> to <> ttyFilePath fPath , " use " <> highlight ( " /fc " <> show fId ) <> " to cancel sending " ]
2021-06-25 18:18:24 +01:00
prependFirst :: StyledString -> [ StyledString ] -> [ StyledString ]
prependFirst s [] = [ s ]
prependFirst s ( s' : ss ) = ( s <> s' ) : ss
2021-07-04 18:42:24 +01:00
msgPlain :: Text -> [ StyledString ]
2022-02-22 14:05:45 +00:00
msgPlain = map ( styleMarkdownList . parseMarkdownList ) . T . lines
2021-07-04 18:42:24 +01:00
2022-01-24 16:07:17 +00:00
viewRcvFileSndCancelled :: RcvFileTransfer -> [ StyledString ]
viewRcvFileSndCancelled ft @ RcvFileTransfer { senderDisplayName = c } =
[ ttyContact c <> " cancelled sending " <> rcvFile ft ]
2021-09-04 07:32:56 +01:00
2022-04-05 10:01:08 +04:00
viewSndGroupFileCancelled :: FileTransferMeta -> [ SndFileTransfer ] -> [ StyledString ]
viewSndGroupFileCancelled FileTransferMeta { fileId , fileName } fts =
2021-09-05 14:08:29 +01:00
case filter ( \ SndFileTransfer { fileStatus = s } -> s /= FSCancelled && s /= FSComplete ) fts of
2022-04-05 10:01:08 +04:00
[] -> [ " cancelled sending " <> fileTransferStr fileId fileName ]
ts -> [ " cancelled sending " <> fileTransferStr fileId fileName <> " to " <> listRecipients ts ]
2021-09-04 07:32:56 +01:00
2021-09-05 14:08:29 +01:00
sendingFile_ :: StyledString -> SndFileTransfer -> [ StyledString ]
sendingFile_ status ft @ SndFileTransfer { recipientDisplayName = c } =
[ status <> " sending " <> sndFile ft <> " to " <> ttyContact c ]
2021-09-04 07:32:56 +01:00
2021-09-05 14:08:29 +01:00
sndFile :: SndFileTransfer -> StyledString
2022-01-26 21:20:08 +00:00
sndFile SndFileTransfer { fileId , fileName } = fileTransferStr fileId fileName
2021-09-05 14:08:29 +01:00
2022-04-10 13:30:58 +04:00
viewReceivedFileInvitation :: StyledString -> CIFile d -> CIMeta d -> [ StyledString ]
viewReceivedFileInvitation from file meta = receivedWithTime_ from [] meta ( receivedFileInvitation_ file )
receivedFileInvitation_ :: CIFile d -> [ StyledString ]
receivedFileInvitation_ CIFile { fileId , fileName , fileSize } =
[ " sends file " <> ttyFilePath fileName <> " ( " <> humanReadableSize fileSize <> " / " <> sShow fileSize <> " bytes) " ,
-- below is printed for auto-accepted files as well; auto-accept is disabled in terminal though so in reality it never happens
" use " <> highlight ( " /fr " <> show fileId <> " [<dir>/ | <path>] " ) <> " to receive it "
]
-- TODO remove
viewReceivedFileInvitation' :: StyledString -> RcvFileTransfer -> CIMeta d -> [ StyledString ]
viewReceivedFileInvitation' from RcvFileTransfer { fileId , fileInvitation = FileInvitation { fileName , fileSize } } meta = receivedWithTime_ from [] meta ( receivedFileInvitation_' fileId fileName fileSize )
2022-01-21 11:09:33 +00:00
2022-04-10 13:30:58 +04:00
receivedFileInvitation_' :: Int64 -> String -> Integer -> [ StyledString ]
receivedFileInvitation_' fileId fileName fileSize =
2021-09-05 14:08:29 +01:00
[ " sends file " <> ttyFilePath fileName <> " ( " <> humanReadableSize fileSize <> " / " <> sShow fileSize <> " bytes) " ,
2021-09-04 07:32:56 +01:00
" use " <> highlight ( " /fr " <> show fileId <> " [<dir>/ | <path>] " ) <> " to receive it "
]
humanReadableSize :: Integer -> StyledString
humanReadableSize size
| size < kB = sShow size <> " bytes "
| size < mB = hrSize kB " KiB "
| size < gB = hrSize mB " MiB "
| otherwise = hrSize gB " GiB "
where
hrSize sB name = plain $ unwords [ showFFloat ( Just 1 ) ( fromIntegral size / ( fromIntegral sB :: Double ) ) " " , name ]
kB = 1024
mB = kB * 1024
gB = mB * 1024
2022-04-29 15:56:56 +04:00
savingFile' :: AChatItem -> [ StyledString ]
savingFile' ( AChatItem _ _ ( DirectChat Contact { localDisplayName = c } ) ChatItem { file = Just CIFile { fileId , filePath = Just filePath } , chatDir = CIDirectRcv } ) =
[ " saving file " <> sShow fileId <> " from " <> ttyContact c <> " to " <> plain filePath ]
savingFile' ( AChatItem _ _ _ ChatItem { file = Just CIFile { fileId , filePath = Just filePath } , chatDir = CIGroupRcv GroupMember { localDisplayName = m } } ) =
[ " saving file " <> sShow fileId <> " from " <> ttyContact m <> " to " <> plain filePath ]
savingFile' ( AChatItem _ _ _ ChatItem { file = Just CIFile { fileId , filePath = Just filePath } } ) =
[ " saving file " <> sShow fileId <> " to " <> plain filePath ]
savingFile' _ = [ " saving file " ] -- shouldn't happen
2022-04-15 09:36:38 +04:00
receivingFile_' :: StyledString -> AChatItem -> [ StyledString ]
receivingFile_' status ( AChatItem _ _ ( DirectChat Contact { localDisplayName = c } ) ChatItem { file = Just CIFile { fileId , fileName } , chatDir = CIDirectRcv } ) =
[ status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact c ]
receivingFile_' status ( AChatItem _ _ _ ChatItem { file = Just CIFile { fileId , fileName } , chatDir = CIGroupRcv GroupMember { localDisplayName = m } } ) =
[ status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact m ]
receivingFile_' status _ = [ status <> " receiving file " ] -- shouldn't happen
2021-09-05 14:08:29 +01:00
receivingFile_ :: StyledString -> RcvFileTransfer -> [ StyledString ]
receivingFile_ status ft @ RcvFileTransfer { senderDisplayName = c } =
[ status <> " receiving " <> rcvFile ft <> " from " <> ttyContact c ]
2021-09-04 07:32:56 +01:00
2021-09-05 14:08:29 +01:00
rcvFile :: RcvFileTransfer -> StyledString
2022-01-26 21:20:08 +00:00
rcvFile RcvFileTransfer { fileId , fileInvitation = FileInvitation { fileName } } = fileTransferStr fileId fileName
2021-09-05 14:08:29 +01:00
2022-01-26 21:20:08 +00:00
fileTransferStr :: Int64 -> String -> StyledString
fileTransferStr fileId fileName = " file " <> sShow fileId <> " ( " <> ttyFilePath fileName <> " ) "
2021-09-04 07:32:56 +01:00
2022-01-21 11:09:33 +00:00
viewFileTransferStatus :: ( FileTransfer , [ Integer ] ) -> [ StyledString ]
2022-04-05 10:01:08 +04:00
viewFileTransferStatus ( FTSnd FileTransferMeta { fileId , fileName , cancelled } [] , _ ) =
[ " sending " <> fileTransferStr fileId fileName <> " : no file transfers "
<> if cancelled then " , file transfer cancelled " else " "
]
viewFileTransferStatus ( FTSnd FileTransferMeta { cancelled } fts @ ( ft : _ ) , chunksNum ) =
recipientStatuses <> [ " file transfer cancelled " | cancelled ]
2021-09-05 14:08:29 +01:00
where
2022-04-05 10:01:08 +04:00
recipientStatuses =
case concatMap recipientsTransferStatus $ groupBy ( ( == ) ` on ` fs ) $ sortOn fs fts of
[ recipientsStatus ] -> [ " sending " <> sndFile ft <> " " <> recipientsStatus ]
recipientsStatuses -> ( " sending " <> sndFile ft <> " : " ) : map ( " " <> ) recipientsStatuses
2021-09-05 14:08:29 +01:00
fs = fileStatus :: SndFileTransfer -> FileStatus
2022-04-05 10:01:08 +04:00
recipientsTransferStatus [] = []
recipientsTransferStatus ts @ ( SndFileTransfer { fileStatus , fileSize , chunkSize } : _ ) = [ sndStatus <> " : " <> listRecipients ts ]
2021-09-05 14:08:29 +01:00
where
sndStatus = case fileStatus of
FSNew -> " not accepted "
FSAccepted -> " just started "
FSConnected -> " in progress ( " <> sShow ( sum chunksNum * chunkSize * 100 ` div ` ( toInteger ( length chunksNum ) * fileSize ) ) <> " %) "
FSComplete -> " complete "
FSCancelled -> " cancelled "
2022-01-21 11:09:33 +00:00
viewFileTransferStatus ( FTRcv ft @ RcvFileTransfer { fileId , fileInvitation = FileInvitation { fileSize } , fileStatus , chunkSize } , chunksNum ) =
2021-09-05 14:08:29 +01:00
[ " receiving " <> rcvFile ft <> " " <> rcvStatus ]
2021-09-04 07:32:56 +01:00
where
rcvStatus = case fileStatus of
2021-09-05 14:08:29 +01:00
RFSNew -> " not accepted yet, use " <> highlight ( " /fr " <> show fileId ) <> " to receive file "
2021-09-04 07:32:56 +01:00
RFSAccepted _ -> " just started "
2021-09-05 14:08:29 +01:00
RFSConnected _ -> " progress " <> fileProgress chunksNum chunkSize fileSize
RFSComplete RcvFileInfo { filePath } -> " complete, path: " <> plain filePath
2022-05-11 16:18:28 +04:00
RFSCancelled ( Just RcvFileInfo { filePath } ) -> " cancelled, received part path: " <> plain filePath
RFSCancelled Nothing -> " cancelled "
2021-09-05 14:08:29 +01:00
2022-04-05 10:01:08 +04:00
listRecipients :: [ SndFileTransfer ] -> StyledString
listRecipients = mconcat . intersperse " , " . map ( ttyContact . recipientDisplayName )
2021-09-04 07:32:56 +01:00
fileProgress :: [ Integer ] -> Integer -> Integer -> StyledString
fileProgress chunksNum chunkSize fileSize =
sShow ( sum chunksNum * chunkSize * 100 ` div ` fileSize ) <> " % of " <> humanReadableSize fileSize
2022-05-17 08:37:00 +01:00
viewCallInvitation :: Contact -> CallType -> Maybe C . Key -> [ StyledString ]
viewCallInvitation ct @ Contact { contactId } callType @ CallType { media } sharedKey =
2022-05-18 07:01:32 +01:00
[ ttyContact' ct <> " wants to connect with you via WebRTC " <> callMediaStr callType <> " call " <> encryptedCallText callType ,
2022-05-17 08:37:00 +01:00
" To accept the call, please open the link below in your browser " <> supporedBrowsers callType ,
" " ,
" https://simplex.chat/call# " <> plain queryString
]
where
aesKey = B . unpack . strEncode . C . unKey <$> sharedKey
queryString =
Q . renderSimpleQuery
False
[ ( " command " , LB . toStrict . J . encode $ WCCallStart { media , aesKey , useWorker = True } ) ,
( " contact_id " , B . pack $ show contactId )
]
viewCallOffer :: Contact -> CallType -> WebRTCSession -> Maybe C . Key -> [ StyledString ]
viewCallOffer ct @ Contact { contactId } callType @ CallType { media } WebRTCSession { rtcSession = offer , rtcIceCandidates = iceCandidates } sharedKey =
2022-05-18 07:01:32 +01:00
[ ttyContact' ct <> " accepted your WebRTC " <> callMediaStr callType <> " call " <> encryptedCallText callType ,
2022-05-17 08:37:00 +01:00
" To connect, please open the link below in your browser " <> supporedBrowsers callType ,
" " ,
" https://simplex.chat/call# " <> plain queryString
]
where
aesKey = B . unpack . strEncode . C . unKey <$> sharedKey
queryString =
Q . renderSimpleQuery
False
[ ( " command " , LB . toStrict . J . encode $ WCCallOffer { offer , iceCandidates , media , aesKey , useWorker = True } ) ,
( " contact_id " , B . pack $ show contactId )
]
viewCallAnswer :: Contact -> WebRTCSession -> [ StyledString ]
viewCallAnswer ct WebRTCSession { rtcSession = answer , rtcIceCandidates = iceCandidates } =
[ 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 }
]
callMediaStr :: CallType -> StyledString
callMediaStr CallType { media } = case media of
CMVideo -> " video "
CMAudio -> " audio "
2022-05-18 07:01:32 +01:00
encryptedCallText :: CallType -> StyledString
encryptedCallText callType
| encryptedCall callType = " (e2e encrypted) "
| otherwise = " (not e2e encrypted) "
2022-05-17 08:37:00 +01:00
supporedBrowsers :: CallType -> StyledString
2022-05-18 07:01:32 +01:00
supporedBrowsers callType
| encryptedCall callType = " (only Chrome and Safari support e2e encryption for WebRTC, Safari may require enabling WebRTC insertable streams) "
2022-05-17 08:37:00 +01:00
| otherwise = " "
data WCallCommand
= WCCallStart { media :: CallMedia , aesKey :: Maybe String , useWorker :: Bool }
| WCCallOffer { offer :: Text , iceCandidates :: Text , media :: CallMedia , aesKey :: Maybe String , useWorker :: Bool }
| WCCallAnswer { answer :: Text , iceCandidates :: Text }
deriving ( Generic )
instance ToJSON WCallCommand where
toEncoding = J . genericToEncoding . taggedObjectJSON $ dropPrefix " WCCall "
toJSON = J . genericToJSON . taggedObjectJSON $ dropPrefix " WCCall "
2022-01-21 11:09:33 +00:00
viewChatError :: ChatError -> [ StyledString ]
viewChatError = \ case
2021-07-16 07:40:55 +01:00
ChatError err -> case err of
2022-02-06 16:18:01 +00:00
CENoActiveUser -> [ " error: active user is required " ]
CEActiveUserExists -> [ " error: active user already exists " ]
CEChatNotStarted -> [ " error: chat not started " ]
2022-06-06 16:23:47 +01:00
CEChatNotStopped -> [ " error: chat not stopped " ]
2022-08-31 18:07:34 +01:00
CEChatStoreChanged -> [ " error: chat store changed, please restart chat " ]
2022-01-24 16:07:17 +00:00
CEInvalidConnReq -> viewInvalidConnReq
2022-02-06 16:18:01 +00:00
CEInvalidChatMessage e -> [ " chat message error: " <> sShow e ]
2022-02-14 18:49:42 +04:00
CEContactNotReady c -> [ ttyContact' c <> " : not ready " ]
CEContactGroups c gNames -> [ ttyContact' c <> " : contact cannot be deleted, it is a member of the group(s) " <> ttyGroups gNames ]
2021-07-16 07:40:55 +01:00
CEGroupDuplicateMember c -> [ " contact " <> ttyContact c <> " is already in the group " ]
CEGroupDuplicateMemberId -> [ " cannot add member - duplicate member ID " ]
2021-07-24 10:26:28 +01:00
CEGroupUserRole -> [ " you have insufficient permissions for this group command " ]
2022-08-27 19:56:03 +04:00
CEContactIncognitoCantInvite -> [ " you're using your main profile for this group - prohibited to invite contacts to whom you are connected incognito " ]
CEGroupIncognitoCantInvite -> [ " you've connected to this group using an incognito profile - prohibited to invite contacts " ]
2021-07-24 10:26:28 +01:00
CEGroupContactRole c -> [ " contact " <> ttyContact c <> " has insufficient permissions for this group action " ]
2022-01-27 22:01:15 +00:00
CEGroupNotJoined g -> [ " you did not join this group, use " <> highlight ( " /join # " <> groupName' g ) ]
2021-07-24 10:26:28 +01:00
CEGroupMemberNotActive -> [ " you cannot invite other members yet, try later " ]
2022-01-05 20:46:35 +04:00
CEGroupMemberUserRemoved -> [ " you are no longer a member of the group " ]
2022-07-12 19:20:56 +04:00
CEGroupMemberNotFound -> [ " group doesn't have this member " ]
2022-01-24 16:07:17 +00:00
CEGroupMemberIntroNotFound c -> [ " group member intro not found for " <> ttyContact c ]
CEGroupCantResendInvitation g c -> viewCannotResendInvitation g c
2021-07-16 07:40:55 +01:00
CEGroupInternal s -> [ " chat group bug: " <> plain s ]
2021-09-04 07:32:56 +01:00
CEFileNotFound f -> [ " file not found: " <> plain f ]
CEFileAlreadyReceiving f -> [ " file is already accepted: " <> plain f ]
2022-05-11 16:18:28 +04:00
CEFileCancelled f -> [ " file cancelled: " <> plain f ]
2021-09-04 07:32:56 +01:00
CEFileAlreadyExists f -> [ " file already exists: " <> plain f ]
CEFileRead f e -> [ " cannot read file " <> plain f , sShow e ]
CEFileWrite f e -> [ " cannot write file " <> plain f , sShow e ]
CEFileSend fileId e -> [ " error sending file " <> sShow fileId <> " : " <> sShow e ]
CEFileRcvChunk e -> [ " error receiving file: " <> plain e ]
CEFileInternal e -> [ " file error: " <> plain e ]
2022-05-21 18:17:15 +04:00
CEFileImageType _ -> [ " max image size: " <> sShow maxImageSize <> " bytes, resize it or send as a file using " <> highlight' " /f " ]
CEFileImageSize _ -> [ " image type must be JPG, send as a file using " <> highlight' " /f " ]
CEFileNotReceived fileId -> [ " file " <> sShow fileId <> " not received " ]
2022-03-13 19:34:03 +00:00
CEInvalidQuote -> [ " cannot reply to this message " ]
2022-03-28 20:35:57 +04:00
CEInvalidChatItemUpdate -> [ " cannot update this item " ]
CEInvalidChatItemDelete -> [ " cannot delete this item " ]
2022-05-03 10:22:35 +01:00
CEHasCurrentCall -> [ " call already in progress " ]
CENoCurrentCall -> [ " no call in progress " ]
CECallContact _ -> []
CECallState _ -> []
2022-01-11 08:50:44 +00:00
CEAgentVersion -> [ " unsupported agent version " ]
2022-07-17 15:51:17 +01:00
CEAgentNoSubResult connId -> [ " no subscription result for connection: " <> sShow connId ]
2022-01-24 16:07:17 +00:00
CECommandError e -> [ " bad chat command: " <> plain e ]
2022-09-14 19:45:21 +04:00
CEAgentCommandError e -> [ " agent command error: " <> plain e ]
2021-09-04 07:32:56 +01:00
-- e -> ["chat error: " <> sShow e]
2021-07-12 19:00:03 +01:00
ChatErrorStore err -> case err of
2021-07-14 20:11:41 +01:00
SEDuplicateName -> [ " this display name is already used by user, contact or group " ]
2022-01-29 16:06:08 +04:00
SEContactNotFoundByName c -> [ " no contact " <> ttyContact c ]
2021-07-12 19:00:03 +01:00
SEContactNotReady c -> [ " contact " <> ttyContact c <> " is not active yet " ]
2022-01-29 16:06:08 +04:00
SEGroupNotFoundByName g -> [ " no group " <> ttyGroup g ]
2021-07-16 07:40:55 +01:00
SEGroupAlreadyJoined -> [ " you already joined this group " ]
2021-09-04 07:32:56 +01:00
SEFileNotFound fileId -> fileNotFound fileId
SESndFileNotFound fileId -> fileNotFound fileId
SERcvFileNotFound fileId -> fileNotFound fileId
2021-12-08 13:09:51 +00:00
SEDuplicateContactLink -> [ " you already have chat address, to show: " <> highlight' " /sa " ]
SEUserContactLinkNotFound -> [ " no chat address, to create: " <> highlight' " /ad " ]
2022-01-31 21:53:53 +04:00
SEContactRequestNotFoundByName c -> [ " no contact request from " <> ttyContact c ]
2022-04-05 10:01:08 +04:00
SEFileIdNotFoundBySharedMsgId _ -> [] -- recipient tried to accept cancelled file
2022-02-02 23:50:43 +04:00
SEConnectionNotFound _ -> [] -- TODO mutes delete group error, but also mutes any error from getConnectionEntity
2022-03-13 19:34:03 +00:00
SEQuotedChatItemNotFound -> [ " message not found - reply is not sent " ]
2021-09-04 07:32:56 +01:00
e -> [ " chat db error: " <> sShow e ]
2022-08-31 18:07:34 +01:00
ChatErrorDatabase err -> case err of
2022-09-05 14:54:39 +01:00
DBErrorEncrypted -> [ " error: chat database is already encrypted " ]
DBErrorPlaintext -> [ " error: chat database is not encrypted " ]
2022-09-07 17:20:47 +01:00
DBErrorExport e -> [ " error encrypting database: " <> sqliteError' e ]
DBErrorOpen e -> [ " error opening database after encryption: " <> sqliteError' e ]
2022-08-31 18:07:34 +01:00
e -> [ " chat database error: " <> sShow e ]
2021-12-08 13:09:51 +00:00
ChatErrorAgent err -> case err of
2022-04-21 11:50:24 +04:00
SMP SMP . AUTH ->
[ " error: connection authorization failed - this could happen if connection was deleted, \
\ secured with different credentials , or due to a bug - please re - create the connection "
]
2022-08-04 20:59:05 +01:00
AGENT A_DUPLICATE -> []
2022-08-13 14:18:12 +01:00
AGENT A_PROHIBITED -> []
CONN NOT_FOUND -> []
2021-12-08 13:09:51 +00:00
e -> [ " smp agent error: " <> sShow e ]
2021-09-04 07:32:56 +01:00
where
fileNotFound fileId = [ " file " <> sShow fileId <> " not found " ]
2022-09-07 17:20:47 +01:00
sqliteError' = \ case
SQLiteErrorNotADatabase -> " wrong passphrase or invalid database file "
SQLiteError e -> sShow e
2021-06-25 18:18:24 +01:00
2021-07-14 20:11:41 +01:00
ttyContact :: ContactName -> StyledString
2022-03-13 19:34:03 +00:00
ttyContact = styled $ colored Green
2021-06-25 18:18:24 +01:00
2022-01-26 16:18:27 +04:00
ttyContact' :: Contact -> StyledString
ttyContact' Contact { localDisplayName = c } = ttyContact c
2021-07-16 07:40:55 +01:00
ttyFullContact :: Contact -> StyledString
2022-08-18 11:35:31 +04:00
ttyFullContact Contact { localDisplayName , profile = LocalProfile { fullName } } =
2021-07-24 10:26:28 +01:00
ttyFullName localDisplayName fullName
ttyMember :: GroupMember -> StyledString
ttyMember GroupMember { localDisplayName } = ttyContact localDisplayName
ttyFullMember :: GroupMember -> StyledString
2022-08-18 11:35:31 +04:00
ttyFullMember GroupMember { localDisplayName , memberProfile = LocalProfile { fullName } } =
2021-07-24 10:26:28 +01:00
ttyFullName localDisplayName fullName
ttyFullName :: ContactName -> Text -> StyledString
ttyFullName c fullName = ttyContact c <> optFullName c fullName
2021-07-16 07:40:55 +01:00
2021-07-14 20:11:41 +01:00
ttyToContact :: ContactName -> StyledString
2022-02-22 14:05:45 +00:00
ttyToContact c = styled ( colored Cyan ) $ " @ " <> c <> " "
2021-06-25 18:18:24 +01:00
2021-07-14 20:11:41 +01:00
ttyFromContact :: ContactName -> StyledString
2022-03-13 19:34:03 +00:00
ttyFromContact c = ttyFrom $ c <> " > "
2022-03-23 11:37:51 +00:00
ttyFromContactEdited :: ContactName -> StyledString
ttyFromContactEdited c = ttyFrom $ c <> " > [edited] "
2022-03-28 20:35:57 +04:00
ttyFromContactDeleted :: ContactName -> StyledString
ttyFromContactDeleted c = ttyFrom $ c <> " > [deleted] "
2022-03-13 19:34:03 +00:00
ttyToContact' :: Contact -> StyledString
2022-08-18 11:35:31 +04:00
ttyToContact' Contact { localDisplayName = c , activeConn = Connection { customUserProfileId } } =
maybe " " ( const incognitoPrefix ) customUserProfileId <> ttyToContact c
2022-03-13 19:34:03 +00:00
ttyQuotedContact :: Contact -> StyledString
ttyQuotedContact Contact { localDisplayName = c } = ttyFrom $ c <> " > "
2022-03-16 13:20:47 +00:00
ttyQuotedMember :: Maybe GroupMember -> StyledString
ttyQuotedMember ( Just GroupMember { localDisplayName = c } ) = " > " <> ttyFrom c
ttyQuotedMember _ = " > " <> ttyFrom " ? "
2022-03-13 19:34:03 +00:00
ttyFromContact' :: Contact -> StyledString
2022-08-18 11:35:31 +04:00
ttyFromContact' Contact { localDisplayName = c , activeConn = Connection { customUserProfileId } } =
maybe " " ( const incognitoPrefix ) customUserProfileId <> ttyFromContact c
2021-06-25 18:18:24 +01:00
2021-07-14 20:11:41 +01:00
ttyGroup :: GroupName -> StyledString
2022-02-22 14:05:45 +00:00
ttyGroup g = styled ( colored Blue ) $ " # " <> g
2021-06-25 18:18:24 +01:00
2022-01-26 16:18:27 +04:00
ttyGroup' :: GroupInfo -> StyledString
2022-01-27 22:01:15 +00:00
ttyGroup' = ttyGroup . groupName'
2022-01-26 16:18:27 +04:00
2022-01-24 16:07:17 +00:00
ttyGroups :: [ GroupName ] -> StyledString
ttyGroups [] = " "
ttyGroups [ g ] = ttyGroup g
ttyGroups ( g : gs ) = ttyGroup g <> " , " <> ttyGroups gs
2022-01-26 16:18:27 +04:00
ttyFullGroup :: GroupInfo -> StyledString
ttyFullGroup GroupInfo { localDisplayName = g , groupProfile = GroupProfile { fullName } } =
ttyGroup g <> optFullName g fullName
2021-07-16 07:40:55 +01:00
2022-01-26 16:18:27 +04:00
ttyFromGroup :: GroupInfo -> ContactName -> StyledString
2022-03-13 19:34:03 +00:00
ttyFromGroup GroupInfo { localDisplayName = g } c = ttyFrom $ " # " <> g <> " " <> c <> " > "
2022-03-23 11:37:51 +00:00
ttyFromGroupEdited :: GroupInfo -> ContactName -> StyledString
ttyFromGroupEdited GroupInfo { localDisplayName = g } c = ttyFrom $ " # " <> g <> " " <> c <> " > [edited] "
2022-03-28 20:35:57 +04:00
ttyFromGroupDeleted :: GroupInfo -> ContactName -> StyledString
ttyFromGroupDeleted GroupInfo { localDisplayName = g } c = ttyFrom $ " # " <> g <> " " <> c <> " > [deleted] "
2022-03-13 19:34:03 +00:00
ttyFrom :: Text -> StyledString
ttyFrom = styled $ colored Yellow
ttyFromGroup' :: GroupInfo -> GroupMember -> StyledString
2022-08-18 11:35:31 +04:00
ttyFromGroup' g @ GroupInfo { membership } GroupMember { localDisplayName = m } =
( if memberIncognito membership then incognitoPrefix else " " ) <> ttyFromGroup g m
2021-07-24 10:26:28 +01:00
2022-01-26 16:18:27 +04:00
ttyToGroup :: GroupInfo -> StyledString
2022-08-18 11:35:31 +04:00
ttyToGroup GroupInfo { localDisplayName = g , membership } =
( if memberIncognito membership then incognitoPrefix else " " ) <> styled ( colored Cyan ) ( " # " <> g <> " " )
2021-07-24 10:26:28 +01:00
2021-09-05 14:08:29 +01:00
ttyFilePath :: FilePath -> StyledString
ttyFilePath = plain
2021-07-24 10:26:28 +01:00
optFullName :: ContactName -> Text -> StyledString
2022-07-14 22:04:23 +04:00
optFullName localDisplayName fullName = plain $ optionalFullName localDisplayName fullName
2021-07-16 07:40:55 +01:00
2022-08-18 11:35:31 +04:00
incognitoPrefix :: StyledString
incognitoPrefix = styleIncognito' " i "
incognitoProfile' :: Profile -> StyledString
incognitoProfile' Profile { displayName } = styleIncognito displayName
2021-07-16 07:40:55 +01:00
highlight :: StyledFormat a => a -> StyledString
2022-03-13 19:34:03 +00:00
highlight = styled $ colored Cyan
2021-07-16 07:40:55 +01:00
highlight' :: String -> StyledString
highlight' = highlight
2022-08-18 11:35:31 +04:00
styleIncognito :: StyledFormat a => a -> StyledString
styleIncognito = styled $ colored Magenta
styleIncognito' :: String -> StyledString
styleIncognito' = styleIncognito
2021-06-25 18:18:24 +01:00
styleTime :: String -> StyledString
styleTime = Styled [ SetColor Foreground Vivid Black ]
2022-03-13 19:34:03 +00:00
ttyError :: StyledFormat a => a -> StyledString
ttyError = styled $ colored Red
ttyError' :: String -> StyledString
ttyError' = ttyError