mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 08:33:09 +00:00
core: incognito connections (#926)
This commit is contained in:
1 parent
404b7093b7
commit
5e67654249
19 files changed
+4408
-460
No files matched your search
@@ -24,7 +24,7 @@ chatBotRepl welcome answer _user cc = do
|
||||
race_ (forever $ void getLine) . forever $ do
|
||||
(_, resp) <- atomically . readTBQueue $ outputQ cc
|
||||
case resp of
|
||||
CRContactConnected contact -> do
|
||||
CRContactConnected contact _ -> do
|
||||
contactConnected contact
|
||||
void $ sendMsg contact welcome
|
||||
CRNewChatItem (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content}) -> do
|
||||
@@ -40,7 +40,7 @@ initializeBotAddress cc = do
|
||||
sendChatCmd cc "/show_address" >>= \case
|
||||
CRUserContactLink uri _ _ -> showBotAddress uri
|
||||
CRChatCmdError (ChatErrorStore SEUserContactLinkNotFound) -> do
|
||||
putStrLn $ "No bot address, creating..."
|
||||
putStrLn "No bot address, creating..."
|
||||
sendChatCmd cc "/address" >>= \case
|
||||
CRUserContactLinkCreated uri -> showBotAddress uri
|
||||
_ -> putStrLn "can't create bot address" >> exitFailure
|
||||
|
||||
@@ -88,7 +88,8 @@ data ChatController = ChatController
|
||||
rcvFiles :: TVar (Map Int64 Handle),
|
||||
currentCalls :: TMap ContactId Call,
|
||||
config :: ChatConfig,
|
||||
filesFolder :: TVar (Maybe FilePath) -- path to files folder for mobile apps
|
||||
filesFolder :: TVar (Maybe FilePath), -- path to files folder for mobile apps,
|
||||
incognitoMode :: TVar Bool
|
||||
}
|
||||
|
||||
data HelpSection = HSMain | HSFiles | HSGroups | HSMyAddress | HSMarkdown | HSMessages | HSSettings
|
||||
@@ -107,6 +108,7 @@ data ChatCommand
|
||||
| APISuspendChat {suspendTimeout :: Int}
|
||||
| ResubscribeAllConnections
|
||||
| SetFilesFolder FilePath
|
||||
| SetIncognito Bool
|
||||
| APIExportArchive ArchiveConfig
|
||||
| APIImportArchive ArchiveConfig
|
||||
| APIDeleteStorage
|
||||
@@ -210,8 +212,8 @@ data ChatResponse
|
||||
| CRApiParsedMarkdown {formattedText :: Maybe MarkdownList}
|
||||
| CRUserSMPServers {smpServers :: [SMPServer]}
|
||||
| CRNetworkConfig {networkConfig :: NetworkConfig}
|
||||
| CRContactInfo {contact :: Contact, connectionStats :: ConnectionStats}
|
||||
| CRGroupMemberInfo {groupInfo :: GroupInfo, member :: GroupMember, connectionStats_ :: Maybe ConnectionStats}
|
||||
| CRContactInfo {contact :: Contact, connectionStats :: ConnectionStats, customUserProfile :: Maybe Profile}
|
||||
| CRGroupMemberInfo {groupInfo :: GroupInfo, member :: GroupMember, connectionStats_ :: Maybe ConnectionStats, mainProfile :: Maybe Profile}
|
||||
| CRNewChatItem {chatItem :: AChatItem}
|
||||
| CRChatItemStatusUpdated {chatItem :: AChatItem}
|
||||
| CRChatItemUpdated {chatItem :: AChatItem}
|
||||
@@ -223,7 +225,7 @@ data ChatResponse
|
||||
| CRCmdOk
|
||||
| CRChatHelp {helpSection :: HelpSection}
|
||||
| CRWelcome {user :: User}
|
||||
| CRGroupCreated {groupInfo :: GroupInfo}
|
||||
| CRGroupCreated {groupInfo :: GroupInfo, customUserProfile :: Maybe Profile}
|
||||
| CRGroupMembers {group :: Group}
|
||||
| CRContactsList {contacts :: [Contact]}
|
||||
| CRUserContactLink {connReqContact :: ConnReqContact, autoAccept :: Bool, autoReply :: Maybe MsgContent}
|
||||
@@ -232,14 +234,14 @@ data ChatResponse
|
||||
| CRUserAcceptedGroupSent {groupInfo :: GroupInfo}
|
||||
| CRUserDeletedMember {groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRGroupsList {groups :: [GroupInfo]}
|
||||
| CRSentGroupInvitation {groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember}
|
||||
| CRSentGroupInvitation {groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember, sentCustomProfile :: Maybe Profile}
|
||||
| CRFileTransferStatus (FileTransfer, [Integer]) -- TODO refactor this type to FileTransferStatus
|
||||
| CRUserProfile {profile :: Profile}
|
||||
| CRUserProfileNoChange
|
||||
| CRVersionInfo {version :: String}
|
||||
| CRInvitation {connReqInvitation :: ConnReqInvitation}
|
||||
| CRSentConfirmation
|
||||
| CRSentInvitation
|
||||
| CRSentInvitation {customUserProfile :: Maybe Profile}
|
||||
| CRContactUpdated {fromContact :: Contact, toContact :: Contact}
|
||||
| CRContactsMerged {intoContact :: Contact, mergedContact :: Contact}
|
||||
| CRContactDeleted {contact :: Contact}
|
||||
@@ -265,7 +267,7 @@ data ChatResponse
|
||||
| CRSndGroupFileCancelled {chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]}
|
||||
| CRUserProfileUpdated {fromProfile :: Profile, toProfile :: Profile}
|
||||
| CRContactConnecting {contact :: Contact}
|
||||
| CRContactConnected {contact :: Contact}
|
||||
| CRContactConnected {contact :: Contact, userCustomProfile :: Maybe Profile}
|
||||
| CRContactAnotherClient {contact :: Contact}
|
||||
| CRContactsDisconnected {server :: SMPServer, contactRefs :: [ContactRef]}
|
||||
| CRContactsSubscribed {server :: SMPServer, contactRefs :: [ContactRef]}
|
||||
@@ -274,9 +276,9 @@ data ChatResponse
|
||||
| CRHostConnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
||||
| CRHostDisconnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
||||
| CRGroupInvitation {groupInfo :: GroupInfo}
|
||||
| CRReceivedGroupInvitation {groupInfo :: GroupInfo, contact :: Contact, memberRole :: GroupMemberRole}
|
||||
| CRUserJoinedGroup {groupInfo :: GroupInfo, hostMember :: GroupMember}
|
||||
| CRJoinedGroupMember {groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRReceivedGroupInvitation {groupInfo :: GroupInfo, contact :: Contact, memberRole :: GroupMemberRole, receivedCustomProfile :: Maybe Profile}
|
||||
| CRUserJoinedGroup {groupInfo :: GroupInfo, hostMember :: GroupMember, usedCustomProfile :: Bool}
|
||||
| CRJoinedGroupMember {groupInfo :: GroupInfo, member :: GroupMember, mainProfile :: Maybe Profile}
|
||||
| CRJoinedGroupMemberConnecting {groupInfo :: GroupInfo, hostMember :: GroupMember, member :: GroupMember}
|
||||
| CRConnectedToGroupMember {groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRDeletedMember {groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember}
|
||||
@@ -383,6 +385,7 @@ data ChatErrorType
|
||||
| CEContactNotReady {contact :: Contact}
|
||||
| CEContactGroups {contact :: Contact, groupNames :: [GroupName]}
|
||||
| CEGroupUserRole
|
||||
| CEGroupNotIncognitoCantInvite
|
||||
| CEGroupContactRole {contactName :: ContactName}
|
||||
| CEGroupDuplicateMember {contactName :: ContactName}
|
||||
| CEGroupDuplicateMemberId
|
||||
|
||||
@@ -18,7 +18,7 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.Markdown
|
||||
import Simplex.Chat.Styled
|
||||
import Simplex.Chat.Types (Profile (..), User (..))
|
||||
import Simplex.Chat.Types (User (..), LocalProfile (..))
|
||||
import System.Console.ANSI.Types
|
||||
|
||||
highlight :: Text -> Markdown
|
||||
@@ -55,7 +55,7 @@ chatWelcome user =
|
||||
"Type " <> highlight "/help" <> " for usage info, " <> highlight "/welcome" <> " to show this message"
|
||||
]
|
||||
where
|
||||
User {profile = Profile {displayName, fullName}} = user
|
||||
User {profile = LocalProfile {displayName, fullName}} = user
|
||||
userName = if T.null fullName then displayName else fullName
|
||||
|
||||
chatHelpInfo :: [StyledString]
|
||||
|
||||
@@ -501,22 +501,24 @@ ciGroupInvitationToText CIGroupInvitation {groupProfile = GroupProfile {displayN
|
||||
|
||||
rcvGroupEventToText :: RcvGroupEvent -> Text
|
||||
rcvGroupEventToText = \case
|
||||
RGEMemberAdded _ p -> "added " <> memberProfileToText p
|
||||
RGEMemberConnected -> "connected"
|
||||
RGEMemberAdded _ p -> "added " <> profileToText p
|
||||
RGEMemberConnected contactMainProfile -> case contactMainProfile of
|
||||
Just p -> profileToText p <> " connected incognito"
|
||||
Nothing -> "connected"
|
||||
RGEMemberLeft -> "left"
|
||||
RGEMemberDeleted _ p -> "removed " <> memberProfileToText p
|
||||
RGEMemberDeleted _ p -> "removed " <> profileToText p
|
||||
RGEUserDeleted -> "removed you"
|
||||
RGEGroupDeleted -> "deleted group"
|
||||
RGEGroupUpdated _ -> "group profile updated"
|
||||
|
||||
sndGroupEventToText :: SndGroupEvent -> Text
|
||||
sndGroupEventToText = \case
|
||||
SGEMemberDeleted _ p -> "removed " <> memberProfileToText p
|
||||
SGEMemberDeleted _ p -> "removed " <> profileToText p
|
||||
SGEUserLeft -> "left"
|
||||
SGEGroupUpdated _ -> "group profile updated"
|
||||
|
||||
memberProfileToText :: Profile -> Text
|
||||
memberProfileToText Profile {displayName, fullName} = displayName <> optionalFullName displayName fullName
|
||||
profileToText :: Profile -> Text
|
||||
profileToText Profile {displayName, fullName} = displayName <> optionalFullName displayName fullName
|
||||
|
||||
-- This type is used both in API and in DB, so we use different JSON encodings for the database and for the API
|
||||
data CIContent (d :: MsgDirection) where
|
||||
@@ -536,7 +538,7 @@ deriving instance Show (CIContent d)
|
||||
|
||||
data RcvGroupEvent
|
||||
= RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting
|
||||
| RGEMemberConnected -- CRUserJoinedGroup, CRJoinedGroupMember, CRConnectedToGroupMember
|
||||
| RGEMemberConnected {contactMainProfile :: Maybe Profile} -- CRUserJoinedGroup, CRJoinedGroupMember, CRConnectedToGroupMember
|
||||
| RGEMemberLeft -- CRLeftMember
|
||||
| RGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRDeletedMember
|
||||
| RGEUserDeleted -- CRDeletedMemberUser
|
||||
@@ -569,13 +571,14 @@ data CIGroupInvitation = CIGroupInvitation
|
||||
groupMemberId :: GroupMemberId,
|
||||
localDisplayName :: GroupName,
|
||||
groupProfile :: GroupProfile,
|
||||
status :: CIGroupInvitationStatus
|
||||
status :: CIGroupInvitationStatus,
|
||||
invitedIncognito :: Maybe Bool
|
||||
}
|
||||
deriving (Eq, Show, Generic, FromJSON)
|
||||
|
||||
instance ToJSON CIGroupInvitation where
|
||||
toJSON = J.genericToJSON J.defaultOptions
|
||||
toEncoding = J.genericToEncoding J.defaultOptions
|
||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||
|
||||
data CIGroupInvitationStatus
|
||||
= CIGISPending
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20220812_incognito_profiles where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20220812_incognito_profiles :: Query
|
||||
m20220812_incognito_profiles =
|
||||
[sql|
|
||||
ALTER TABLE connections ADD COLUMN custom_user_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL; -- only set for direct connections
|
||||
|
||||
ALTER TABLE group_members ADD COLUMN member_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL; -- member profile id if incognito profile was saved for member (used for hosts and invitees in incognito groups)
|
||||
|
||||
ALTER TABLE contact_profiles ADD COLUMN incognito INTEGER; -- 1 for incognito
|
||||
|]
|
||||
@@ -13,7 +13,8 @@ CREATE TABLE contact_profiles(
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||
image TEXT,
|
||||
user_id INTEGER DEFAULT NULL REFERENCES users ON DELETE CASCADE
|
||||
user_id INTEGER DEFAULT NULL REFERENCES users ON DELETE CASCADE,
|
||||
incognito INTEGER
|
||||
);
|
||||
CREATE INDEX contact_profiles_index ON contact_profiles(
|
||||
display_name,
|
||||
@@ -145,6 +146,7 @@ CREATE TABLE group_members(
|
||||
contact_id INTEGER REFERENCES contacts ON DELETE CASCADE,
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||
member_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON DELETE CASCADE
|
||||
@@ -236,6 +238,7 @@ CREATE TABLE connections(
|
||||
xcontact_id BLOB,
|
||||
via_user_contact_link INTEGER DEFAULT NULL
|
||||
REFERENCES user_contact_links(user_contact_link_id) ON DELETE SET NULL,
|
||||
custom_user_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
|
||||
FOREIGN KEY(snd_file_id, connection_id)
|
||||
REFERENCES snd_files(file_id, connection_id)
|
||||
ON DELETE CASCADE
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -124,7 +124,7 @@ data ChatMsgEvent
|
||||
| XInfo Profile
|
||||
| XContact Profile (Maybe XContactId)
|
||||
| XGrpInv GroupInvitation
|
||||
| XGrpAcpt MemberId
|
||||
| XGrpAcpt MemberId (Maybe Profile)
|
||||
| XGrpMemNew MemberInfo
|
||||
| XGrpMemIntro MemberInfo
|
||||
| XGrpMemInv MemberId IntroInvitation
|
||||
@@ -413,7 +413,7 @@ toCMEventTag = \case
|
||||
XInfo _ -> XInfo_
|
||||
XContact _ _ -> XContact_
|
||||
XGrpInv _ -> XGrpInv_
|
||||
XGrpAcpt _ -> XGrpAcpt_
|
||||
XGrpAcpt _ _ -> XGrpAcpt_
|
||||
XGrpMemNew _ -> XGrpMemNew_
|
||||
XGrpMemIntro _ -> XGrpMemIntro_
|
||||
XGrpMemInv _ _ -> XGrpMemInv_
|
||||
@@ -479,7 +479,7 @@ appToChatMessage AppMessage {msgId, event, params} = do
|
||||
XInfo_ -> XInfo <$> p "profile"
|
||||
XContact_ -> XContact <$> p "profile" <*> opt "contactReqId"
|
||||
XGrpInv_ -> XGrpInv <$> p "groupInvitation"
|
||||
XGrpAcpt_ -> XGrpAcpt <$> p "memberId"
|
||||
XGrpAcpt_ -> XGrpAcpt <$> p "memberId" <*> opt "memberProfile"
|
||||
XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo"
|
||||
XGrpMemIntro_ -> XGrpMemIntro <$> p "memberInfo"
|
||||
XGrpMemInv_ -> XGrpMemInv <$> p "memberId" <*> p "memberIntro"
|
||||
@@ -521,7 +521,7 @@ chatToAppMessage ChatMessage {msgId, chatMsgEvent} = AppMessage {msgId, event, p
|
||||
XInfo profile -> o ["profile" .= profile]
|
||||
XContact profile xContactId -> o $ ("contactReqId" .=? xContactId) ["profile" .= profile]
|
||||
XGrpInv groupInv -> o ["groupInvitation" .= groupInv]
|
||||
XGrpAcpt memId -> o ["memberId" .= memId]
|
||||
XGrpAcpt memId profile -> o $ ("memberProfile" .=? profile) ["memberId" .= memId]
|
||||
XGrpMemNew memInfo -> o ["memberInfo" .= memInfo]
|
||||
XGrpMemIntro memInfo -> o ["memberInfo" .= memInfo]
|
||||
XGrpMemInv memId memIntro -> o ["memberId" .= memId, "memberIntro" .= memIntro]
|
||||
|
||||
+363
-275
File diff suppressed because it is too large.
Load diff
@@ -22,6 +22,7 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Int (Int64)
|
||||
import Data.Maybe (isJust)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Time.Clock (UTCTime)
|
||||
@@ -39,7 +40,7 @@ import Simplex.Messaging.Util ((<$?>))
|
||||
|
||||
class IsContact a where
|
||||
contactId' :: a -> ContactId
|
||||
profile' :: a -> Profile
|
||||
profile' :: a -> LocalProfile
|
||||
localDisplayName' :: a -> ContactName
|
||||
|
||||
instance IsContact User where
|
||||
@@ -56,7 +57,7 @@ data User = User
|
||||
{ userId :: UserId,
|
||||
userContactId :: ContactId,
|
||||
localDisplayName :: ContactName,
|
||||
profile :: Profile,
|
||||
profile :: LocalProfile,
|
||||
activeUser :: Bool
|
||||
}
|
||||
deriving (Show, Generic, FromJSON)
|
||||
@@ -67,10 +68,12 @@ type UserId = ContactId
|
||||
|
||||
type ContactId = Int64
|
||||
|
||||
type ProfileId = Int64
|
||||
|
||||
data Contact = Contact
|
||||
{ contactId :: ContactId,
|
||||
localDisplayName :: ContactName,
|
||||
profile :: Profile,
|
||||
profile :: LocalProfile,
|
||||
activeConn :: Connection,
|
||||
viaGroup :: Maybe Int64,
|
||||
createdAt :: UTCTime,
|
||||
@@ -88,6 +91,9 @@ contactConn = activeConn
|
||||
contactConnId :: Contact -> ConnId
|
||||
contactConnId Contact {activeConn} = aConnId activeConn
|
||||
|
||||
contactConnIncognito :: Contact -> Bool
|
||||
contactConnIncognito Contact {activeConn = Connection {customUserProfileId}} = isJust customUserProfileId
|
||||
|
||||
data ContactRef = ContactRef
|
||||
{ contactId :: ContactId,
|
||||
localDisplayName :: ContactName
|
||||
@@ -198,6 +204,7 @@ data Profile = Profile
|
||||
{ displayName :: ContactName,
|
||||
fullName :: Text,
|
||||
image :: Maybe ImageData
|
||||
-- incognito field should not be read as is into this data type to prevent sending it as part of profile to contacts
|
||||
}
|
||||
deriving (Eq, Show, Generic, FromJSON)
|
||||
|
||||
@@ -205,6 +212,29 @@ instance ToJSON Profile where
|
||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||
|
||||
data LocalProfile = LocalProfile
|
||||
{ profileId :: ProfileId,
|
||||
displayName :: ContactName,
|
||||
fullName :: Text,
|
||||
image :: Maybe ImageData
|
||||
}
|
||||
deriving (Eq, Show, Generic, FromJSON)
|
||||
|
||||
instance ToJSON LocalProfile where
|
||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||
|
||||
localProfileId :: LocalProfile -> ProfileId
|
||||
localProfileId = profileId
|
||||
|
||||
toLocalProfile :: ProfileId -> Profile -> LocalProfile
|
||||
toLocalProfile profileId Profile {displayName, fullName, image} =
|
||||
LocalProfile {profileId, displayName, fullName, image}
|
||||
|
||||
fromLocalProfile :: LocalProfile -> Profile
|
||||
fromLocalProfile LocalProfile {displayName, fullName, image} =
|
||||
Profile {displayName, fullName, image}
|
||||
|
||||
data GroupProfile = GroupProfile
|
||||
{ displayName :: GroupName,
|
||||
fullName :: Text,
|
||||
@@ -232,13 +262,16 @@ instance FromField ImageData where fromField = fmap ImageData . fromField
|
||||
|
||||
data GroupInvitation = GroupInvitation
|
||||
{ fromMember :: MemberIdRole,
|
||||
fromMemberProfile :: Maybe Profile,
|
||||
invitedMember :: MemberIdRole,
|
||||
connRequest :: ConnReqInvitation,
|
||||
groupProfile :: GroupProfile
|
||||
}
|
||||
deriving (Eq, Show, Generic, FromJSON)
|
||||
|
||||
instance ToJSON GroupInvitation where toEncoding = J.genericToEncoding J.defaultOptions
|
||||
instance ToJSON GroupInvitation where
|
||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||
|
||||
data MemberIdRole = MemberIdRole
|
||||
{ memberId :: MemberId,
|
||||
@@ -267,7 +300,7 @@ instance ToJSON MemberInfo where toEncoding = J.genericToEncoding J.defaultOptio
|
||||
|
||||
memberInfo :: GroupMember -> MemberInfo
|
||||
memberInfo GroupMember {memberId, memberRole, memberProfile} =
|
||||
MemberInfo memberId memberRole memberProfile
|
||||
MemberInfo memberId memberRole (fromLocalProfile memberProfile)
|
||||
|
||||
data ReceivedGroupInvitation = ReceivedGroupInvitation
|
||||
{ fromMember :: GroupMember,
|
||||
@@ -278,6 +311,8 @@ data ReceivedGroupInvitation = ReceivedGroupInvitation
|
||||
|
||||
type GroupMemberId = Int64
|
||||
|
||||
-- memberProfile's profileId is COALESCE(member_profile_id, contact_profile_id), member_profile_id is non null
|
||||
-- if incognito profile was saved for member (used for hosts and invitees in incognito groups)
|
||||
data GroupMember = GroupMember
|
||||
{ groupMemberId :: GroupMemberId,
|
||||
groupId :: GroupId,
|
||||
@@ -287,8 +322,9 @@ data GroupMember = GroupMember
|
||||
memberStatus :: GroupMemberStatus,
|
||||
invitedBy :: InvitedBy,
|
||||
localDisplayName :: ContactName,
|
||||
memberProfile :: Profile,
|
||||
memberContactId :: Maybe Int64,
|
||||
memberProfile :: LocalProfile,
|
||||
memberContactId :: Maybe ContactId,
|
||||
memberContactProfileId :: ProfileId,
|
||||
activeConn :: Maybe Connection
|
||||
}
|
||||
deriving (Eq, Show, Generic)
|
||||
@@ -306,6 +342,9 @@ memberConnId GroupMember {activeConn} = aConnId <$> activeConn
|
||||
groupMemberId' :: GroupMember -> GroupMemberId
|
||||
groupMemberId' GroupMember {groupMemberId} = groupMemberId
|
||||
|
||||
memberIncognito :: GroupMember -> Bool
|
||||
memberIncognito GroupMember {memberProfile, memberContactProfileId} = localProfileId memberProfile /= memberContactProfileId
|
||||
|
||||
data NewGroupMember = NewGroupMember
|
||||
{ memInfo :: MemberInfo,
|
||||
memCategory :: GroupMemberCategory,
|
||||
@@ -695,6 +734,7 @@ data Connection = Connection
|
||||
connLevel :: Int,
|
||||
viaContact :: Maybe Int64, -- group member contact ID, if not direct connection
|
||||
viaUserContactLink :: Maybe Int64, -- user contact link ID, if connected via "user address"
|
||||
customUserProfileId :: Maybe Int64,
|
||||
connType :: ConnType,
|
||||
connStatus :: ConnStatus,
|
||||
entityId :: Maybe Int64, -- contact, group member, file ID or user contact ID
|
||||
@@ -705,6 +745,9 @@ data Connection = Connection
|
||||
aConnId :: Connection -> ConnId
|
||||
aConnId Connection {agentConnId = AgentConnId cId} = cId
|
||||
|
||||
connCustomUserProfileId :: Connection -> Maybe Int64
|
||||
connCustomUserProfileId Connection {customUserProfileId} = customUserProfileId
|
||||
|
||||
instance ToJSON Connection where
|
||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||
@@ -715,6 +758,7 @@ data PendingContactConnection = PendingContactConnection
|
||||
pccConnStatus :: ConnStatus,
|
||||
viaContactUri :: Bool,
|
||||
viaUserContactLink :: Maybe Int64,
|
||||
customUserProfileId :: Maybe Int64,
|
||||
createdAt :: UTCTime,
|
||||
updatedAt :: UTCTime
|
||||
}
|
||||
|
||||
+146
-42
@@ -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]
|
||||
|
||||
|
||||
Reference in new issue
Block a user